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