1 /* 2 * Copyright (c) 1997-1999 Kungliga Tekniska H�gskolan 3 * (Royal Institute of Technology, Stockholm, Sweden). 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 17 * 3. Neither the name of the Institute nor the names of its contributors 18 * may be used to endorse or promote products derived from this software 19 * without specific prior written permission. 20 * 21 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND 22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 24 * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE 25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 31 * SUCH DAMAGE. 32 */ 33 34 #include "kpasswd_locl.h" 35 RCSID("$Id: kpasswdd.c,v 1.41 1999/12/02 17:05:00 joda Exp $"); 36 37 #include <kadm5/admin.h> 38 #ifdef HAVE_DLFCN_H 39 #include <dlfcn.h> 40 #endif 41 42 #include <hdb.h> 43 44 static krb5_context context; 45 static krb5_log_facility *log_facility; 46 47 static sig_atomic_t exit_flag = 0; 48 49 static void 50 send_reply (int s, 51 struct sockaddr *sa, 52 int sa_size, 53 krb5_data *ap_rep, 54 krb5_data *rest) 55 { 56 struct msghdr msghdr; 57 struct iovec iov[3]; 58 u_int16_t len, ap_rep_len; 59 u_char header[6]; 60 u_char *p; 61 62 if (ap_rep) 63 ap_rep_len = ap_rep->length; 64 else 65 ap_rep_len = 0; 66 67 len = 6 + ap_rep_len + rest->length; 68 p = header; 69 *p++ = (len >> 8) & 0xFF; 70 *p++ = (len >> 0) & 0xFF; 71 *p++ = 0; 72 *p++ = 1; 73 *p++ = (ap_rep_len >> 8) & 0xFF; 74 *p++ = (ap_rep_len >> 0) & 0xFF; 75 76 memset (&msghdr, 0, sizeof(msghdr)); 77 msghdr.msg_name = (void *)sa; 78 msghdr.msg_namelen = sa_size; 79 msghdr.msg_iov = iov; 80 msghdr.msg_iovlen = sizeof(iov)/sizeof(*iov); 81 #if 0 82 msghdr.msg_control = NULL; 83 msghdr.msg_controllen = 0; 84 #endif 85 86 iov[0].iov_base = (char *)header; 87 iov[0].iov_len = 6; 88 if (ap_rep_len) { 89 iov[1].iov_base = ap_rep->data; 90 iov[1].iov_len = ap_rep->length; 91 } else { 92 iov[1].iov_base = NULL; 93 iov[1].iov_len = 0; 94 } 95 iov[2].iov_base = rest->data; 96 iov[2].iov_len = rest->length; 97 98 if (sendmsg (s, &msghdr, 0) < 0) 99 krb5_warn (context, errno, "sendmsg"); 100 } 101 102 static int 103 make_result (krb5_data *data, 104 u_int16_t result_code, 105 const char *expl) 106 { 107 krb5_data_zero (data); 108 109 data->length = asprintf ((char **)&data->data, 110 "%c%c%s", 111 (result_code >> 8) & 0xFF, 112 result_code & 0xFF, 113 expl); 114 115 if (data->data == NULL) { 116 krb5_warnx (context, "Out of memory generating error reply"); 117 return 1; 118 } 119 return 0; 120 } 121 122 static void 123 reply_error (krb5_principal server, 124 int s, 125 struct sockaddr *sa, 126 int sa_size, 127 krb5_error_code error_code, 128 u_int16_t result_code, 129 const char *expl) 130 { 131 krb5_error_code ret; 132 krb5_data error_data; 133 krb5_data e_data; 134 135 if (make_result(&e_data, result_code, expl)) 136 return; 137 138 ret = krb5_mk_error (context, 139 error_code, 140 NULL, 141 &e_data, 142 NULL, 143 server, 144 0, 145 &error_data); 146 krb5_data_free (&e_data); 147 if (ret) { 148 krb5_warn (context, ret, "Could not even generate error reply"); 149 return; 150 } 151 send_reply (s, sa, sa_size, NULL, &error_data); 152 krb5_data_free (&error_data); 153 } 154 155 static void 156 reply_priv (krb5_auth_context auth_context, 157 int s, 158 struct sockaddr *sa, 159 int sa_size, 160 u_int16_t result_code, 161 const char *expl) 162 { 163 krb5_error_code ret; 164 krb5_data krb_priv_data; 165 krb5_data ap_rep_data; 166 krb5_data e_data; 167 168 ret = krb5_mk_rep (context, 169 &auth_context, 170 &ap_rep_data); 171 if (ret) { 172 krb5_warn (context, ret, "Could not even generate error reply"); 173 return; 174 } 175 176 if (make_result(&e_data, result_code, expl)) 177 return; 178 179 ret = krb5_mk_priv (context, 180 auth_context, 181 &e_data, 182 &krb_priv_data, 183 NULL); 184 krb5_data_free (&e_data); 185 if (ret) { 186 krb5_warn (context, ret, "Could not even generate error reply"); 187 return; 188 } 189 send_reply (s, sa, sa_size, &ap_rep_data, &krb_priv_data); 190 krb5_data_free (&ap_rep_data); 191 krb5_data_free (&krb_priv_data); 192 } 193 194 /* 195 * Change the password for `principal', sending the reply back on `s' 196 * (`sa', `sa_size') to `pwd_data'. 197 */ 198 199 static void 200 change (krb5_auth_context auth_context, 201 krb5_principal principal, 202 int s, 203 struct sockaddr *sa, 204 int sa_size, 205 krb5_data *pwd_data) 206 { 207 krb5_error_code ret; 208 char *client; 209 kadm5_principal_ent_rec ent; 210 krb5_key_data *kd; 211 krb5_salt salt; 212 krb5_keyblock new_keyblock; 213 const char *pwd_reason; 214 int unchanged; 215 kadm5_config_params conf; 216 void *kadm5_handle; 217 218 memset (&conf, 0, sizeof(conf)); 219 220 krb5_unparse_name (context, principal, &client); 221 222 ret = kadm5_init_with_password_ctx(context, 223 client, 224 NULL, 225 KADM5_ADMIN_SERVICE, 226 &conf, 0, 0, 227 &kadm5_handle); 228 if (ret) { 229 free (client); 230 krb5_warn (context, ret, "kadm5_init_with_password_ctx"); 231 reply_priv (auth_context, s, sa, sa_size, 2, 232 "Internal error"); 233 return; 234 } 235 236 krb5_warnx (context, "Changing password for %s", client); 237 free (client); 238 239 pwd_reason = kadm5_check_password_quality (context, principal, pwd_data); 240 if (pwd_reason != NULL ) { 241 krb5_warnx (context, "%s", pwd_reason); 242 reply_priv (auth_context, s, sa, sa_size, 4, pwd_reason); 243 kadm5_destroy (kadm5_handle); 244 return; 245 } 246 247 ret = kadm5_get_principal (kadm5_handle, 248 principal, 249 &ent, 250 KADM5_KEY_DATA); 251 if (ret) { 252 krb5_warn (context, ret, "kadm5_get_principal"); 253 reply_priv (auth_context, s, sa, sa_size, 2, 254 "Internal error"); 255 kadm5_destroy (kadm5_handle); 256 return; 257 } 258 259 /* 260 * Compare with the first key to see if it already has been 261 * changed. If it hasn't, store the new key in the database and 262 * string2key all the rest of them. 263 */ 264 265 kd = &ent.key_data[0]; 266 267 salt.salttype = kd->key_data_type[1]; 268 salt.saltvalue.length = kd->key_data_length[1]; 269 salt.saltvalue.data = kd->key_data_contents[1]; 270 271 memset (&new_keyblock, 0, sizeof(new_keyblock)); 272 krb5_string_to_key_data_salt (context, 273 kd->key_data_type[0], 274 *pwd_data, 275 salt, 276 &new_keyblock); 277 278 unchanged = new_keyblock.keytype == kd->key_data_type[0] 279 && new_keyblock.keyvalue.length == kd->key_data_length[0] 280 && memcmp(new_keyblock.keyvalue.data, 281 kd->key_data_contents[0], 282 new_keyblock.keyvalue.length) == 0; 283 284 krb5_free_keyblock_contents (context, &new_keyblock); 285 286 if (unchanged) { 287 ret = 0; 288 } else { 289 char *tmp; 290 291 tmp = malloc (pwd_data->length + 1); 292 if (tmp == NULL) { 293 krb5_warnx (context, "malloc: out of memory"); 294 reply_priv (auth_context, s, sa, sa_size, 2, 295 "Internal error"); 296 goto out; 297 } 298 memcpy (tmp, pwd_data->data, pwd_data->length); 299 tmp[pwd_data->length] = '\0'; 300 301 ret = kadm5_chpass_principal (kadm5_handle, 302 principal, 303 tmp); 304 memset (tmp, 0, pwd_data->length); 305 free (tmp); 306 if (ret) { 307 krb5_warn (context, ret, "kadm5_s_chpass_principal"); 308 reply_priv (auth_context, s, sa, sa_size, 2, 309 "Internal error"); 310 goto out; 311 } 312 } 313 reply_priv (auth_context, s, sa, sa_size, 0, "Password changed"); 314 out: 315 kadm5_free_principal_ent (kadm5_handle, &ent); 316 kadm5_destroy (kadm5_handle); 317 } 318 319 static int 320 verify (krb5_auth_context *auth_context, 321 krb5_principal server, 322 krb5_keytab keytab, 323 krb5_ticket **ticket, 324 krb5_data *out_data, 325 int s, 326 struct sockaddr *sa, 327 int sa_size, 328 u_char *msg, 329 size_t len) 330 { 331 krb5_error_code ret; 332 u_int16_t pkt_len, pkt_ver, ap_req_len; 333 krb5_data ap_req_data; 334 krb5_data krb_priv_data; 335 336 pkt_len = (msg[0] << 8) | (msg[1]); 337 pkt_ver = (msg[2] << 8) | (msg[3]); 338 ap_req_len = (msg[4] << 8) | (msg[5]); 339 if (pkt_len != len) { 340 krb5_warnx (context, "Strange len: %ld != %ld", 341 (long)pkt_len, (long)len); 342 reply_error (server, s, sa, sa_size, 0, 1, "Bad request"); 343 return 1; 344 } 345 if (pkt_ver != 0x0001) { 346 krb5_warnx (context, "Bad version (%d)", pkt_ver); 347 reply_error (server, s, sa, sa_size, 0, 1, "Wrong program version"); 348 return 1; 349 } 350 351 ap_req_data.data = msg + 6; 352 ap_req_data.length = ap_req_len; 353 354 ret = krb5_rd_req (context, 355 auth_context, 356 &ap_req_data, 357 server, 358 keytab, 359 NULL, 360 ticket); 361 if (ret) { 362 if(ret == KRB5_KT_NOTFOUND) { 363 char *name; 364 krb5_unparse_name(context, server, &name); 365 krb5_warnx (context, "krb5_rd_req: %s (%s)", 366 krb5_get_err_text(context, ret), name); 367 free(name); 368 } else 369 krb5_warn (context, ret, "krb5_rd_req"); 370 reply_error (server, s, sa, sa_size, ret, 3, "Authentication failed"); 371 return 1; 372 } 373 374 if (!(*ticket)->ticket.flags.initial) { 375 krb5_warnx (context, "initial flag not set"); 376 reply_error (server, s, sa, sa_size, ret, 1, 377 "Bad request"); 378 goto out; 379 } 380 krb_priv_data.data = msg + 6 + ap_req_len; 381 krb_priv_data.length = len - 6 - ap_req_len; 382 383 ret = krb5_rd_priv (context, 384 *auth_context, 385 &krb_priv_data, 386 out_data, 387 NULL); 388 389 if (ret) { 390 krb5_warn (context, ret, "krb5_rd_priv"); 391 reply_error (server, s, sa, sa_size, ret, 3, "Bad request"); 392 goto out; 393 } 394 return 0; 395 out: 396 krb5_free_ticket (context, *ticket); 397 return 1; 398 } 399 400 static void 401 process (krb5_principal server, 402 krb5_keytab keytab, 403 int s, 404 krb5_address *this_addr, 405 struct sockaddr *sa, 406 int sa_size, 407 u_char *msg, 408 int len) 409 { 410 krb5_error_code ret; 411 krb5_auth_context auth_context = NULL; 412 krb5_data out_data; 413 krb5_ticket *ticket; 414 krb5_address other_addr; 415 416 krb5_data_zero (&out_data); 417 418 ret = krb5_auth_con_init (context, &auth_context); 419 if (ret) { 420 krb5_warn (context, ret, "krb5_auth_con_init"); 421 return; 422 } 423 424 krb5_auth_con_setflags (context, auth_context, 425 KRB5_AUTH_CONTEXT_DO_SEQUENCE); 426 427 ret = krb5_sockaddr2address (sa, &other_addr); 428 if (ret) { 429 krb5_warn (context, ret, "krb5_sockaddr2address"); 430 goto out; 431 } 432 433 ret = krb5_auth_con_setaddrs (context, 434 auth_context, 435 this_addr, 436 &other_addr); 437 krb5_free_address (context, &other_addr); 438 if (ret) { 439 krb5_warn (context, ret, "krb5_auth_con_setaddr"); 440 goto out; 441 } 442 443 if (verify (&auth_context, server, keytab, &ticket, &out_data, 444 s, sa, sa_size, msg, len) == 0) { 445 change (auth_context, 446 ticket->client, 447 s, 448 sa, sa_size, 449 &out_data); 450 krb5_free_ticket (context, ticket); 451 free (ticket); 452 } 453 454 out: 455 krb5_data_free (&out_data); 456 krb5_auth_con_free (context, auth_context); 457 } 458 459 static int 460 doit (krb5_keytab keytab, 461 int port) 462 { 463 krb5_error_code ret; 464 krb5_principal server; 465 int *sockets; 466 int maxfd; 467 char *realm; 468 krb5_addresses addrs; 469 unsigned n, i; 470 fd_set real_fdset; 471 struct sockaddr_storage __ss; 472 struct sockaddr *sa = (struct sockaddr *)&__ss; 473 474 ret = krb5_get_default_realm (context, &realm); 475 if (ret) 476 krb5_err (context, 1, ret, "krb5_get_default_realm"); 477 478 ret = krb5_build_principal (context, 479 &server, 480 strlen(realm), 481 realm, 482 "kadmin", 483 "changepw", 484 NULL); 485 if (ret) 486 krb5_err (context, 1, ret, "krb5_build_principal"); 487 488 free (realm); 489 490 ret = krb5_get_all_server_addrs (context, &addrs); 491 if (ret) 492 krb5_err (context, 1, ret, "krb5_get_all_server_addrs"); 493 494 n = addrs.len; 495 496 sockets = malloc (n * sizeof(*sockets)); 497 if (sockets == NULL) 498 krb5_errx (context, 1, "out of memory"); 499 maxfd = 0; 500 FD_ZERO(&real_fdset); 501 for (i = 0; i < n; ++i) { 502 int sa_size; 503 504 krb5_addr2sockaddr (&addrs.val[i], sa, &sa_size, port); 505 506 507 sockets[i] = socket (sa->sa_family, SOCK_DGRAM, 0); 508 if (sockets[i] < 0) 509 krb5_err (context, 1, errno, "socket"); 510 if (bind (sockets[i], sa, sa_size) < 0) { 511 char str[128]; 512 size_t len; 513 ret = krb5_print_address (&addrs.val[i], str, sizeof(str), &len); 514 krb5_err (context, 1, errno, "bind(%s)", str); 515 } 516 maxfd = max (maxfd, sockets[i]); 517 FD_SET(sockets[i], &real_fdset); 518 } 519 520 while(exit_flag == 0) { 521 int ret; 522 fd_set fdset = real_fdset; 523 524 ret = select (maxfd + 1, &fdset, NULL, NULL, NULL); 525 if (ret < 0) { 526 if (errno == EINTR) 527 continue; 528 else 529 krb5_err (context, 1, errno, "select"); 530 } 531 for (i = 0; i < n; ++i) 532 if (FD_ISSET(sockets[i], &fdset)) { 533 u_char buf[BUFSIZ]; 534 int addrlen = sizeof(__ss); 535 536 ret = recvfrom (sockets[i], buf, sizeof(buf), 0, 537 sa, &addrlen); 538 if (ret < 0) { 539 if(errno == EINTR) 540 break; 541 else 542 krb5_err (context, 1, errno, "recvfrom"); 543 } 544 545 process (server, keytab, sockets[i], 546 &addrs.val[i], 547 sa, addrlen, 548 buf, ret); 549 } 550 } 551 krb5_free_addresses (context, &addrs); 552 krb5_free_principal (context, server); 553 krb5_free_context (context); 554 return 0; 555 } 556 557 static RETSIGTYPE 558 sigterm(int sig) 559 { 560 exit_flag = 1; 561 } 562 563 const char *check_library = NULL; 564 const char *check_function = NULL; 565 char *keytab_str = "HDB:"; 566 char *realm_str; 567 int version_flag; 568 int help_flag; 569 570 struct getargs args[] = { 571 #ifdef HAVE_DLOPEN 572 { "check-library", 0, arg_string, &check_library, 573 "library to load password check function from", "library" }, 574 { "check-function", 0, arg_string, &check_function, 575 "password check function to load", "function" }, 576 #endif 577 { "keytab", 'k', arg_string, &keytab_str, 578 "keytab to get authentication key from", "kspec" }, 579 { "realm", 'r', arg_string, &realm_str, "default realm", "realm" }, 580 { "version", 0, arg_flag, &version_flag }, 581 { "help", 0, arg_flag, &help_flag } 582 }; 583 int num_args = sizeof(args) / sizeof(args[0]); 584 585 int 586 main (int argc, char **argv) 587 { 588 int optind; 589 krb5_keytab keytab; 590 krb5_error_code ret; 591 592 optind = krb5_program_setup(&context, argc, argv, args, num_args, NULL); 593 594 if(help_flag) 595 krb5_std_usage(0, args, num_args); 596 if(version_flag) { 597 print_version(NULL); 598 exit(0); 599 } 600 601 if(realm_str) 602 krb5_set_default_realm(context, realm_str); 603 604 krb5_openlog (context, "kpasswdd", &log_facility); 605 krb5_set_warn_dest(context, log_facility); 606 607 ret = krb5_kt_register(context, &hdb_kt_ops); 608 if(ret) 609 krb5_err(context, 1, ret, "krb5_kt_register"); 610 611 ret = krb5_kt_resolve(context, keytab_str, &keytab); 612 if(ret) 613 krb5_err(context, 1, ret, "%s", keytab_str); 614 615 kadm5_setup_passwd_quality_check (context, check_library, check_function); 616 617 #ifdef HAVE_SIGACTION 618 { 619 struct sigaction sa; 620 621 sa.sa_flags = 0; 622 sa.sa_handler = sigterm; 623 sigemptyset(&sa.sa_mask); 624 625 sigaction(SIGINT, &sa, NULL); 626 } 627 #else 628 signal(SIGINT, sigterm); 629 #endif 630 631 return doit (keytab, 632 krb5_getportbyname (context, "kpasswd", 633 "udp", KPASSWD_PORT)); 634 } 635