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