1 /* $OpenBSD: radiusd_file.c,v 1.8 2024/11/21 13:43:10 claudio Exp $ */ 2 3 /* 4 * Copyright (c) 2024 YASUOKA Masahiko <yasuoka@yasuoka.net> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19 #include <sys/types.h> 20 #include <sys/cdefs.h> 21 #include <sys/queue.h> 22 #include <sys/socket.h> 23 #include <sys/wait.h> 24 #include <netinet/in.h> 25 26 #include <err.h> 27 #include <errno.h> 28 #include <fcntl.h> 29 #include <imsg.h> 30 #include <limits.h> 31 #include <md5.h> 32 #include <radius.h> 33 #include <stddef.h> 34 #include <stdint.h> 35 #include <stdlib.h> 36 #include <string.h> 37 #include <unistd.h> 38 39 #include "chap_ms.h" 40 #include "imsg_subr.h" 41 #include "log.h" 42 #include "radiusd.h" 43 #include "radiusd_module.h" 44 45 struct module_file_params { 46 int debug; 47 char path[PATH_MAX]; 48 }; 49 50 struct module_file { 51 struct module_base *base; 52 struct imsgbuf ibuf; 53 struct module_file_params 54 params; 55 }; 56 57 struct module_file_userinfo { 58 struct in_addr frame_ip_address; 59 char password[0]; 60 }; 61 62 /* IPC between priv and main */ 63 enum { 64 IMSG_RADIUSD_FILE_OK = 1000, 65 IMSG_RADIUSD_FILE_NG, 66 IMSG_RADIUSD_FILE_PARAMS, 67 IMSG_RADIUSD_FILE_USERINFO 68 }; 69 70 static void parent_dispatch_main(struct module_file_params *, 71 struct imsgbuf *, struct imsg *); 72 static void module_file_main(void) __dead; 73 static pid_t start_child(char *, int); 74 static void module_file_config_set(void *, const char *, int, 75 char * const *); 76 static void module_file_start(void *); 77 static void module_file_access_request(void *, u_int, const u_char *, 78 size_t); 79 static void auth_pap(struct module_file *, u_int, RADIUS_PACKET *, char *, 80 struct module_file_userinfo *); 81 static void auth_md5chap(struct module_file *, u_int, RADIUS_PACKET *, 82 char *, struct module_file_userinfo *); 83 static void auth_mschapv2(struct module_file *, u_int, RADIUS_PACKET *, 84 char *, struct module_file_userinfo *); 85 static void auth_reject(struct module_file *, u_int, RADIUS_PACKET *, 86 char *, struct module_file_userinfo *); 87 88 static struct module_handlers module_file_handlers = { 89 .access_request = module_file_access_request, 90 .config_set = module_file_config_set, 91 .start = module_file_start 92 }; 93 94 int 95 main(int argc, char *argv[]) 96 { 97 int ch, pairsock[2], status; 98 pid_t pid; 99 char *saved_argv0; 100 struct imsgbuf ibuf; 101 struct imsg imsg; 102 ssize_t n; 103 size_t datalen; 104 struct module_file_params *paramsp, params; 105 char pathdb[PATH_MAX]; 106 107 while ((ch = getopt(argc, argv, "M")) != -1) 108 switch (ch) { 109 case 'M': 110 module_file_main(); 111 /* not reached */ 112 break; 113 } 114 saved_argv0 = argv[0]; 115 116 argc -= optind; 117 argv += optind; 118 119 if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, PF_UNSPEC, 120 pairsock) == -1) 121 err(EXIT_FAILURE, "socketpair"); 122 123 log_init(0); 124 125 pid = start_child(saved_argv0, pairsock[1]); 126 127 /* Privileged process */ 128 if (pledge("stdio rpath unveil", NULL) == -1) 129 err(EXIT_FAILURE, "pledge"); 130 setproctitle("[priv]"); 131 if (imsgbuf_init(&ibuf, pairsock[0]) == -1) 132 err(EXIT_FAILURE, "imsgbuf_init"); 133 134 /* Receive parameters from the main process. */ 135 if (imsg_sync_read(&ibuf, 2000) <= 0 || 136 (n = imsg_get(&ibuf, &imsg)) <= 0) 137 exit(EXIT_FAILURE); 138 if (imsg.hdr.type != IMSG_RADIUSD_FILE_PARAMS) 139 err(EXIT_FAILURE, "Receieved unknown message type %d", 140 imsg.hdr.type); 141 datalen = imsg.hdr.len - IMSG_HEADER_SIZE; 142 if (datalen < sizeof(params)) 143 err(EXIT_FAILURE, "Receieved IMSG_RADIUSD_FILE_PARAMS " 144 "message is wrong size"); 145 paramsp = imsg.data; 146 if (paramsp->path[0] != '\0') { 147 strlcpy(pathdb, paramsp->path, sizeof(pathdb)); 148 strlcat(pathdb, ".db", sizeof(pathdb)); 149 if (unveil(paramsp->path, "r") == -1 || 150 unveil(pathdb, "r") == -1) 151 err(EXIT_FAILURE, "unveil"); 152 } 153 if (paramsp->debug) 154 log_init(1); 155 156 if (unveil(NULL, NULL) == -1) 157 err(EXIT_FAILURE, "unveil"); 158 159 memcpy(¶ms, paramsp, sizeof(params)); 160 161 for (;;) { 162 if (imsgbuf_read(&ibuf) != 1) 163 break; 164 for (;;) { 165 if ((n = imsg_get(&ibuf, &imsg)) == -1) 166 break; 167 if (n == 0) 168 break; 169 parent_dispatch_main(¶ms, &ibuf, &imsg); 170 imsg_free(&imsg); 171 imsgbuf_flush(&ibuf); 172 } 173 imsgbuf_flush(&ibuf); 174 } 175 imsgbuf_clear(&ibuf); 176 177 while (waitpid(pid, &status, 0) == -1) { 178 if (errno != EINTR) 179 break; 180 } 181 exit(WEXITSTATUS(status)); 182 } 183 184 void 185 parent_dispatch_main(struct module_file_params *params, struct imsgbuf *ibuf, 186 struct imsg *imsg) 187 { 188 size_t datalen, entsz, passz; 189 const char *username; 190 char *buf, *db[2], *str; 191 int ret; 192 struct module_file_userinfo *ent; 193 194 datalen = imsg->hdr.len - IMSG_HEADER_SIZE; 195 switch (imsg->hdr.type) { 196 case IMSG_RADIUSD_FILE_USERINFO: 197 if (datalen == 0 || 198 *((char *)imsg->data + datalen - 1) != '\0') { 199 log_warn("%s: received IMSG_RADIUSD_FILE_USERINFO " 200 "is wrong", __func__); 201 goto on_error; 202 } 203 username = imsg->data; 204 db[0] = params->path; 205 db[1] = NULL; 206 if ((ret = cgetent(&buf, db, username)) < 0) { 207 log_info("user `%s' is not configured", username); 208 goto on_error; 209 } 210 if ((ret = cgetstr(buf, "password", &str)) < 0) { 211 log_info("password for `%s' is not configured", 212 username); 213 goto on_error; 214 } 215 passz = strlen(str) + 1; 216 entsz = offsetof(struct module_file_userinfo, password[passz]); 217 if ((ent = calloc(1, entsz)) == NULL) { 218 log_warn("%s; calloc", __func__); 219 goto on_error; 220 } 221 strlcpy(ent->password, str, passz); 222 imsg_compose(ibuf, IMSG_RADIUSD_FILE_USERINFO, 0, -1, -1, 223 ent, entsz); 224 freezero(ent, entsz); 225 break; 226 } 227 return; 228 on_error: 229 imsg_compose(ibuf, IMSG_RADIUSD_FILE_NG, 0, -1, -1, NULL, 0); 230 } 231 232 /* main process */ 233 void 234 module_file_main(void) 235 { 236 struct module_file module_file; 237 238 setproctitle("[main]"); 239 240 memset(&module_file, 0, sizeof(module_file)); 241 if ((module_file.base = module_create(STDIN_FILENO, &module_file, 242 &module_file_handlers)) == NULL) 243 err(1, "Could not create a module instance"); 244 245 module_drop_privilege(module_file.base, 0); 246 247 module_load(module_file.base); 248 if (imsgbuf_init(&module_file.ibuf, 3) == -1) 249 err(EXIT_FAILURE, "imsgbuf_init"); 250 251 if (pledge("stdio", NULL) == -1) 252 err(EXIT_FAILURE, "pledge"); 253 while (module_run(module_file.base) == 0) 254 ; 255 256 module_destroy(module_file.base); 257 258 exit(0); 259 } 260 261 pid_t 262 start_child(char *argv0, int fd) 263 { 264 char *argv[5]; 265 int argc = 0; 266 pid_t pid; 267 268 switch (pid = fork()) { 269 case -1: 270 fatal("cannot fork"); 271 case 0: 272 break; 273 default: 274 close(fd); 275 return (pid); 276 } 277 278 if (fd != 3) { 279 if (dup2(fd, 3) == -1) 280 fatal("cannot setup imsg fd"); 281 } else if (fcntl(fd, F_SETFD, 0) == -1) 282 fatal("cannot setup imsg fd"); 283 284 argv[argc++] = argv0; 285 argv[argc++] = "-M"; /* main proc */ 286 argv[argc++] = NULL; 287 execvp(argv0, argv); 288 fatal("execvp"); 289 } 290 291 void 292 module_file_config_set(void *ctx, const char *name, int valc, 293 char * const * valv) 294 { 295 struct module_file *module = ctx; 296 char *errmsg; 297 298 if (strcmp(name, "path") == 0) { 299 SYNTAX_ASSERT(valc == 1, "`path' must have a argument"); 300 if (strlcpy(module->params.path, valv[0], sizeof( 301 module->params.path)) >= sizeof(module->params.path)) { 302 module_send_message(module->base, IMSG_NG, 303 "`path' is too long"); 304 return; 305 } 306 module_send_message(module->base, IMSG_OK, NULL); 307 } else if (strcmp(name, "_debug") == 0) { 308 log_init(1); 309 module->params.debug = 1; 310 module_send_message(module->base, IMSG_OK, NULL); 311 } else if (strncmp(name, "_", 1) == 0) 312 /* ignore all internal messages */ 313 module_send_message(module->base, IMSG_OK, NULL); 314 else 315 module_send_message(module->base, IMSG_NG, 316 "Unknown config parameter `%s'", name); 317 return; 318 syntax_error: 319 module_send_message(module->base, IMSG_NG, "%s", errmsg); 320 return; 321 } 322 323 void 324 module_file_start(void *ctx) 325 { 326 struct module_file *module = ctx; 327 328 /* Send parameters to parent */ 329 if (module->params.path[0] == '\0') { 330 module_send_message(module->base, IMSG_NG, 331 "`path' is not configured"); 332 return; 333 } 334 imsg_compose(&module->ibuf, IMSG_RADIUSD_FILE_PARAMS, 0, -1, -1, 335 &module->params, sizeof(module->params)); 336 imsgbuf_flush(&module->ibuf); 337 338 module_send_message(module->base, IMSG_OK, NULL); 339 } 340 341 void 342 module_file_access_request(void *ctx, u_int query_id, const u_char *pkt, 343 size_t pktlen) 344 { 345 size_t datalen; 346 struct module_file *self = ctx; 347 RADIUS_PACKET *radpkt = NULL; 348 char username[256]; 349 ssize_t n; 350 struct imsg imsg; 351 struct module_file_userinfo *ent; 352 353 memset(&imsg, 0, sizeof(imsg)); 354 355 if ((radpkt = radius_convert_packet(pkt, pktlen)) == NULL) { 356 log_warn("%s: radius_convert_packet()", __func__); 357 goto out; 358 } 359 radius_get_string_attr(radpkt, RADIUS_TYPE_USER_NAME, username, 360 sizeof(username)); 361 362 imsg_compose(&self->ibuf, IMSG_RADIUSD_FILE_USERINFO, 0, -1, -1, 363 username, strlen(username) + 1); 364 imsgbuf_flush(&self->ibuf); 365 if (imsgbuf_read(&self->ibuf) != 1) { 366 log_warn("%s: imsgbuf_read()", __func__); 367 goto out; 368 } 369 if ((n = imsg_get(&self->ibuf, &imsg)) <= 0) { 370 log_warn("%s: imsg_get()", __func__); 371 goto out; 372 } 373 374 datalen = imsg.hdr.len - IMSG_HEADER_SIZE; 375 if (imsg.hdr.type == IMSG_RADIUSD_FILE_USERINFO) { 376 if (datalen <= offsetof(struct module_file_userinfo, 377 password[0])) { 378 log_warn("%s: received IMSG_RADIUSD_FILE_USERINFO is " 379 "invalid", __func__); 380 goto out; 381 } 382 ent = imsg.data; 383 if (radius_has_attr(radpkt, RADIUS_TYPE_USER_PASSWORD)) 384 auth_pap(self, query_id, radpkt, username, ent); 385 else if (radius_has_attr(radpkt, RADIUS_TYPE_CHAP_PASSWORD)) 386 auth_md5chap(self, query_id, radpkt, username, ent); 387 else if (radius_has_vs_attr(radpkt, RADIUS_VENDOR_MICROSOFT, 388 RADIUS_VTYPE_MS_CHAP2_RESPONSE)) 389 auth_mschapv2(self, query_id, radpkt, username, ent); 390 else 391 auth_reject(self, query_id, radpkt, username, ent); 392 } else 393 auth_reject(self, query_id, radpkt, username, NULL); 394 out: 395 if (radpkt != NULL) 396 radius_delete_packet(radpkt); 397 imsg_free(&imsg); 398 return; 399 } 400 401 void 402 auth_pap(struct module_file *self, u_int q_id, RADIUS_PACKET *radpkt, 403 char *username, struct module_file_userinfo *ent) 404 { 405 RADIUS_PACKET *respkt = NULL; 406 char pass[256]; 407 int ret; 408 409 if (radius_get_string_attr(radpkt, RADIUS_TYPE_USER_PASSWORD, pass, 410 sizeof(pass)) != 0) { 411 log_warnx("%s: radius_get_string_attr", __func__); 412 return; 413 } 414 ret = strcmp(ent->password, pass); 415 explicit_bzero(ent->password, strlen(ent->password)); 416 log_info("q=%u User `%s' authentication %s (PAP)", q_id, username, 417 (ret == 0)? "succeeded" : "failed"); 418 if ((respkt = radius_new_response_packet((ret == 0)? 419 RADIUS_CODE_ACCESS_ACCEPT : RADIUS_CODE_ACCESS_REJECT, radpkt)) 420 == NULL) { 421 log_warn("%s: radius_new_response_packet()", __func__); 422 return; 423 } 424 module_accsreq_answer(self->base, q_id, 425 radius_get_data(respkt), radius_get_length(respkt)); 426 radius_delete_packet(respkt); 427 } 428 429 void 430 auth_md5chap(struct module_file *self, u_int q_id, RADIUS_PACKET *radpkt, 431 char *username, struct module_file_userinfo *ent) 432 { 433 RADIUS_PACKET *respkt = NULL; 434 size_t attrlen, challlen; 435 u_char chall[256], idpass[17], digest[16]; 436 int ret; 437 MD5_CTX md5; 438 439 attrlen = sizeof(idpass); 440 if (radius_get_raw_attr(radpkt, RADIUS_TYPE_CHAP_PASSWORD, idpass, 441 &attrlen) != 0) { 442 log_warnx("%s: radius_get_string_attr", __func__); 443 return; 444 } 445 challlen = sizeof(chall); 446 if (radius_get_raw_attr(radpkt, RADIUS_TYPE_CHAP_CHALLENGE, chall, 447 &challlen) != 0) { 448 log_warnx("%s: radius_get_string_attr", __func__); 449 return; 450 } 451 MD5Init(&md5); 452 MD5Update(&md5, idpass, 1); 453 MD5Update(&md5, ent->password, strlen(ent->password)); 454 MD5Update(&md5, chall, challlen); 455 MD5Final(digest, &md5); 456 457 ret = timingsafe_bcmp(idpass + 1, digest, sizeof(digest)); 458 log_info("q=%u User `%s' authentication %s (CHAP)", q_id, username, 459 (ret == 0)? "succeeded" : "failed"); 460 if ((respkt = radius_new_response_packet((ret == 0)? 461 RADIUS_CODE_ACCESS_ACCEPT : RADIUS_CODE_ACCESS_REJECT, radpkt)) 462 == NULL) { 463 log_warn("%s: radius_new_response_packet()", __func__); 464 return; 465 } 466 module_accsreq_answer(self->base, q_id, 467 radius_get_data(respkt), radius_get_length(respkt)); 468 radius_delete_packet(respkt); 469 } 470 471 void 472 auth_mschapv2(struct module_file *self, u_int q_id, RADIUS_PACKET *radpkt, 473 char *username, struct module_file_userinfo *ent) 474 { 475 RADIUS_PACKET *respkt = NULL; 476 size_t attrlen; 477 int i, lpass; 478 char *pass = NULL; 479 uint8_t chall[MSCHAPV2_CHALLENGE_SZ]; 480 uint8_t ntresponse[24], authenticator[16]; 481 uint8_t pwhash[16], pwhash2[16], master[64]; 482 struct { 483 uint8_t salt[2]; 484 uint8_t len; 485 uint8_t key[16]; 486 uint8_t pad[15]; 487 } __packed rcvkey, sndkey; 488 struct { 489 uint8_t ident; 490 uint8_t flags; 491 uint8_t peerchall[16]; 492 uint8_t reserved[8]; 493 uint8_t ntresponse[24]; 494 } __packed resp; 495 struct authresp { 496 uint8_t ident; 497 uint8_t authresp[42]; 498 } __packed authresp; 499 500 501 attrlen = sizeof(chall); 502 if (radius_get_vs_raw_attr(radpkt, RADIUS_VENDOR_MICROSOFT, 503 RADIUS_VTYPE_MS_CHAP_CHALLENGE, chall, &attrlen) != 0) { 504 log_info("q=%u failed to retribute MS-CHAP-Challenge", q_id); 505 goto on_error; 506 } 507 attrlen = sizeof(resp); 508 if (radius_get_vs_raw_attr(radpkt, RADIUS_VENDOR_MICROSOFT, 509 RADIUS_VTYPE_MS_CHAP2_RESPONSE, &resp, &attrlen) != 0) { 510 log_info("q=%u failed to retribute MS-CHAP2-Response", q_id); 511 goto on_error; 512 } 513 514 /* convert the password to UTF16-LE */ 515 lpass = strlen(ent->password); 516 if ((pass = calloc(1, lpass * 2)) == NULL) { 517 log_warn("%s: calloc()", __func__); 518 goto on_error; 519 } 520 for (i = 0; i < lpass; i++) { 521 pass[i * 2] = ent->password[i]; 522 pass[i * 2 + 1] = '\0'; 523 } 524 525 /* calculate NT-Response by the password */ 526 mschap_nt_response(chall, resp.peerchall, 527 username, strlen(username), pass, lpass * 2, ntresponse); 528 529 if (timingsafe_bcmp(ntresponse, resp.ntresponse, 24) != 0) { 530 log_info("q=%u User `%s' authentication failed (MSCHAPv2)", 531 q_id, username); 532 if ((respkt = radius_new_response_packet( 533 RADIUS_CODE_ACCESS_REJECT, radpkt)) == NULL) { 534 log_warn("%s: radius_new_response_packet()", __func__); 535 goto on_error; 536 } 537 authresp.ident = resp.ident; 538 strlcpy(authresp.authresp, "E=691 R=0 V=3", 539 sizeof(authresp.authresp)); 540 radius_put_vs_raw_attr(respkt, RADIUS_VENDOR_MICROSOFT, 541 RADIUS_VTYPE_MS_CHAP_ERROR, &authresp, 542 offsetof(struct authresp, authresp[13])); 543 } else { 544 log_info("q=%u User `%s' authentication succeeded (MSCHAPv2)", 545 q_id, username); 546 if ((respkt = radius_new_response_packet( 547 RADIUS_CODE_ACCESS_ACCEPT, radpkt)) == NULL) { 548 log_warn("%s: radius_new_response_packet()", __func__); 549 goto on_error; 550 } 551 mschap_auth_response(pass, lpass * 2, ntresponse, chall, 552 resp.peerchall, username, strlen(username), 553 authresp.authresp); 554 authresp.ident = resp.ident; 555 556 radius_put_vs_raw_attr(respkt, RADIUS_VENDOR_MICROSOFT, 557 RADIUS_VTYPE_MS_CHAP2_SUCCESS, &authresp, 558 offsetof(struct authresp, authresp[42])); 559 560 mschap_ntpassword_hash(pass, lpass * 2, pwhash); 561 mschap_ntpassword_hash(pwhash, sizeof(pwhash), pwhash2); 562 mschap_masterkey(pwhash2, ntresponse, master); 563 radius_get_authenticator(radpkt, authenticator); 564 565 /* MS-MPPE-Recv-Key */ 566 memset(&rcvkey, 0, sizeof(rcvkey)); 567 arc4random_buf(rcvkey.salt, sizeof(rcvkey.salt)); 568 rcvkey.salt[0] |= 0x80; 569 rcvkey.len = 16; 570 mschap_asymetric_startkey(master, rcvkey.key, 16, 0, 1); 571 radius_put_vs_raw_attr(respkt, RADIUS_VENDOR_MICROSOFT, 572 RADIUS_VTYPE_MPPE_RECV_KEY, &rcvkey, sizeof(rcvkey)); 573 574 /* MS-MPPE-Send-Key */ 575 memset(&sndkey, 0, sizeof(sndkey)); 576 arc4random_buf(sndkey.salt, sizeof(sndkey.salt)); 577 sndkey.salt[0] |= 0x80; 578 sndkey.len = 16; 579 mschap_asymetric_startkey(master, sndkey.key, 16, 1, 1); 580 radius_put_vs_raw_attr(respkt, RADIUS_VENDOR_MICROSOFT, 581 RADIUS_VTYPE_MPPE_SEND_KEY, &sndkey, sizeof(sndkey)); 582 } 583 584 module_accsreq_answer(self->base, q_id, 585 radius_get_data(respkt), radius_get_length(respkt)); 586 on_error: 587 /* bzero password */ 588 explicit_bzero(ent->password, strlen(ent->password)); 589 if (pass != NULL) 590 explicit_bzero(pass, lpass * 2); 591 free(pass); 592 if (respkt != NULL) 593 radius_delete_packet(respkt); 594 } 595 596 void 597 auth_reject(struct module_file *self, u_int q_id, RADIUS_PACKET *radpkt, 598 char *username, struct module_file_userinfo *ent) 599 { 600 RADIUS_PACKET *respkt = NULL; 601 602 if (ent != NULL) 603 explicit_bzero(ent->password, strlen(ent->password)); 604 605 log_info("q=%u User `%s' authentication failed", q_id, 606 username); 607 if ((respkt = radius_new_response_packet(RADIUS_CODE_ACCESS_REJECT, 608 radpkt)) == NULL) { 609 log_warn("%s: radius_new_response_packet()", __func__); 610 return; 611 } 612 module_accsreq_answer(self->base, q_id, 613 radius_get_data(respkt), radius_get_length(respkt)); 614 radius_delete_packet(respkt); 615 } 616