1 /* 2 * Copyright (c) 1997-2001 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.49 2001/01/11 21:33:53 assar Exp $"); 36 37 #include <kadm5/admin.h> 38 39 #include <hdb.h> 40 41 static krb5_context context; 42 static krb5_log_facility *log_facility; 43 44 static sig_atomic_t exit_flag = 0; 45 46 static void 47 send_reply (int s, 48 struct sockaddr *sa, 49 int sa_size, 50 krb5_data *ap_rep, 51 krb5_data *rest) 52 { 53 struct msghdr msghdr; 54 struct iovec iov[3]; 55 u_int16_t len, ap_rep_len; 56 u_char header[6]; 57 u_char *p; 58 59 if (ap_rep) 60 ap_rep_len = ap_rep->length; 61 else 62 ap_rep_len = 0; 63 64 len = 6 + ap_rep_len + rest->length; 65 p = header; 66 *p++ = (len >> 8) & 0xFF; 67 *p++ = (len >> 0) & 0xFF; 68 *p++ = 0; 69 *p++ = 1; 70 *p++ = (ap_rep_len >> 8) & 0xFF; 71 *p++ = (ap_rep_len >> 0) & 0xFF; 72 73 memset (&msghdr, 0, sizeof(msghdr)); 74 msghdr.msg_name = (void *)sa; 75 msghdr.msg_namelen = sa_size; 76 msghdr.msg_iov = iov; 77 msghdr.msg_iovlen = sizeof(iov)/sizeof(*iov); 78 #if 0 79 msghdr.msg_control = NULL; 80 msghdr.msg_controllen = 0; 81 #endif 82 83 iov[0].iov_base = (char *)header; 84 iov[0].iov_len = 6; 85 if (ap_rep_len) { 86 iov[1].iov_base = ap_rep->data; 87 iov[1].iov_len = ap_rep->length; 88 } else { 89 iov[1].iov_base = NULL; 90 iov[1].iov_len = 0; 91 } 92 iov[2].iov_base = rest->data; 93 iov[2].iov_len = rest->length; 94 95 if (sendmsg (s, &msghdr, 0) < 0) 96 krb5_warn (context, errno, "sendmsg"); 97 } 98 99 static int 100 make_result (krb5_data *data, 101 u_int16_t result_code, 102 const char *expl) 103 { 104 krb5_data_zero (data); 105 106 data->length = asprintf ((char **)&data->data, 107 "%c%c%s", 108 (result_code >> 8) & 0xFF, 109 result_code & 0xFF, 110 expl); 111 112 if (data->data == NULL) { 113 krb5_warnx (context, "Out of memory generating error reply"); 114 return 1; 115 } 116 return 0; 117 } 118 119 static void 120 reply_error (krb5_principal server, 121 int s, 122 struct sockaddr *sa, 123 int sa_size, 124 krb5_error_code error_code, 125 u_int16_t result_code, 126 const char *expl) 127 { 128 krb5_error_code ret; 129 krb5_data error_data; 130 krb5_data e_data; 131 132 if (make_result(&e_data, result_code, expl)) 133 return; 134 135 ret = krb5_mk_error (context, 136 error_code, 137 NULL, 138 &e_data, 139 NULL, 140 server, 141 0, 142 &error_data); 143 krb5_data_free (&e_data); 144 if (ret) { 145 krb5_warn (context, ret, "Could not even generate error reply"); 146 return; 147 } 148 send_reply (s, sa, sa_size, NULL, &error_data); 149 krb5_data_free (&error_data); 150 } 151 152 static void 153 reply_priv (krb5_auth_context auth_context, 154 int s, 155 struct sockaddr *sa, 156 int sa_size, 157 u_int16_t result_code, 158 const char *expl) 159 { 160 krb5_error_code ret; 161 krb5_data krb_priv_data; 162 krb5_data ap_rep_data; 163 krb5_data e_data; 164 165 ret = krb5_mk_rep (context, 166 auth_context, 167 &ap_rep_data); 168 if (ret) { 169 krb5_warn (context, ret, "Could not even generate error reply"); 170 return; 171 } 172 173 if (make_result(&e_data, result_code, expl)) 174 return; 175 176 ret = krb5_mk_priv (context, 177 auth_context, 178 &e_data, 179 &krb_priv_data, 180 NULL); 181 krb5_data_free (&e_data); 182 if (ret) { 183 krb5_warn (context, ret, "Could not even generate error reply"); 184 return; 185 } 186 send_reply (s, sa, sa_size, &ap_rep_data, &krb_priv_data); 187 krb5_data_free (&ap_rep_data); 188 krb5_data_free (&krb_priv_data); 189 } 190 191 /* 192 * Change the password for `principal', sending the reply back on `s' 193 * (`sa', `sa_size') to `pwd_data'. 194 */ 195 196 static void 197 change (krb5_auth_context auth_context, 198 krb5_principal principal, 199 int s, 200 struct sockaddr *sa, 201 int sa_size, 202 krb5_data *pwd_data) 203 { 204 krb5_error_code ret; 205 char *client; 206 const char *pwd_reason; 207 kadm5_config_params conf; 208 void *kadm5_handle; 209 char *tmp; 210 211 memset (&conf, 0, sizeof(conf)); 212 213 krb5_unparse_name (context, principal, &client); 214 215 ret = kadm5_init_with_password_ctx(context, 216 client, 217 NULL, 218 KADM5_ADMIN_SERVICE, 219 &conf, 0, 0, 220 &kadm5_handle); 221 if (ret) { 222 free (client); 223 krb5_warn (context, ret, "kadm5_init_with_password_ctx"); 224 reply_priv (auth_context, s, sa, sa_size, 2, 225 "Internal error"); 226 return; 227 } 228 229 krb5_warnx (context, "Changing password for %s", client); 230 free (client); 231 232 pwd_reason = kadm5_check_password_quality (context, principal, pwd_data); 233 if (pwd_reason != NULL ) { 234 krb5_warnx (context, "%s", pwd_reason); 235 reply_priv (auth_context, s, sa, sa_size, 4, pwd_reason); 236 kadm5_destroy (kadm5_handle); 237 return; 238 } 239 240 tmp = malloc (pwd_data->length + 1); 241 if (tmp == NULL) { 242 krb5_warnx (context, "malloc: out of memory"); 243 reply_priv (auth_context, s, sa, sa_size, 2, 244 "Internal error"); 245 goto out; 246 } 247 memcpy (tmp, pwd_data->data, pwd_data->length); 248 tmp[pwd_data->length] = '\0'; 249 250 ret = kadm5_s_chpass_principal_cond (kadm5_handle, principal, tmp); 251 memset (tmp, 0, pwd_data->length); 252 free (tmp); 253 if (ret) { 254 krb5_warn (context, ret, "kadm5_s_chpass_principal_cond"); 255 reply_priv (auth_context, s, sa, sa_size, 2, 256 "Internal error"); 257 goto out; 258 } 259 reply_priv (auth_context, s, sa, sa_size, 0, "Password changed"); 260 out: 261 kadm5_destroy (kadm5_handle); 262 } 263 264 static int 265 verify (krb5_auth_context *auth_context, 266 krb5_principal server, 267 krb5_keytab keytab, 268 krb5_ticket **ticket, 269 krb5_data *out_data, 270 int s, 271 struct sockaddr *sa, 272 int sa_size, 273 u_char *msg, 274 size_t len) 275 { 276 krb5_error_code ret; 277 u_int16_t pkt_len, pkt_ver, ap_req_len; 278 krb5_data ap_req_data; 279 krb5_data krb_priv_data; 280 281 pkt_len = (msg[0] << 8) | (msg[1]); 282 pkt_ver = (msg[2] << 8) | (msg[3]); 283 ap_req_len = (msg[4] << 8) | (msg[5]); 284 if (pkt_len != len) { 285 krb5_warnx (context, "Strange len: %ld != %ld", 286 (long)pkt_len, (long)len); 287 reply_error (server, s, sa, sa_size, 0, 1, "Bad request"); 288 return 1; 289 } 290 if (pkt_ver != 0x0001) { 291 krb5_warnx (context, "Bad version (%d)", pkt_ver); 292 reply_error (server, s, sa, sa_size, 0, 1, "Wrong program version"); 293 return 1; 294 } 295 296 ap_req_data.data = msg + 6; 297 ap_req_data.length = ap_req_len; 298 299 ret = krb5_rd_req (context, 300 auth_context, 301 &ap_req_data, 302 server, 303 keytab, 304 NULL, 305 ticket); 306 if (ret) { 307 if(ret == KRB5_KT_NOTFOUND) { 308 char *name; 309 krb5_unparse_name(context, server, &name); 310 krb5_warnx (context, "krb5_rd_req: %s (%s)", 311 krb5_get_err_text(context, ret), name); 312 free(name); 313 } else 314 krb5_warn (context, ret, "krb5_rd_req"); 315 reply_error (server, s, sa, sa_size, ret, 3, "Authentication failed"); 316 return 1; 317 } 318 319 if (!(*ticket)->ticket.flags.initial) { 320 krb5_warnx (context, "initial flag not set"); 321 reply_error (server, s, sa, sa_size, ret, 1, 322 "Bad request"); 323 goto out; 324 } 325 krb_priv_data.data = msg + 6 + ap_req_len; 326 krb_priv_data.length = len - 6 - ap_req_len; 327 328 ret = krb5_rd_priv (context, 329 *auth_context, 330 &krb_priv_data, 331 out_data, 332 NULL); 333 334 if (ret) { 335 krb5_warn (context, ret, "krb5_rd_priv"); 336 reply_error (server, s, sa, sa_size, ret, 3, "Bad request"); 337 goto out; 338 } 339 return 0; 340 out: 341 krb5_free_ticket (context, *ticket); 342 return 1; 343 } 344 345 static void 346 process (krb5_principal server, 347 krb5_keytab keytab, 348 int s, 349 krb5_address *this_addr, 350 struct sockaddr *sa, 351 int sa_size, 352 u_char *msg, 353 int len) 354 { 355 krb5_error_code ret; 356 krb5_auth_context auth_context = NULL; 357 krb5_data out_data; 358 krb5_ticket *ticket; 359 krb5_address other_addr; 360 361 krb5_data_zero (&out_data); 362 363 ret = krb5_auth_con_init (context, &auth_context); 364 if (ret) { 365 krb5_warn (context, ret, "krb5_auth_con_init"); 366 return; 367 } 368 369 ret = krb5_sockaddr2address (sa, &other_addr); 370 if (ret) { 371 krb5_warn (context, ret, "krb5_sockaddr2address"); 372 goto out; 373 } 374 375 ret = krb5_auth_con_setaddrs (context, 376 auth_context, 377 this_addr, 378 &other_addr); 379 krb5_free_address (context, &other_addr); 380 if (ret) { 381 krb5_warn (context, ret, "krb5_auth_con_setaddr"); 382 goto out; 383 } 384 385 if (verify (&auth_context, server, keytab, &ticket, &out_data, 386 s, sa, sa_size, msg, len) == 0) { 387 change (auth_context, 388 ticket->client, 389 s, 390 sa, sa_size, 391 &out_data); 392 memset (out_data.data, 0, out_data.length); 393 krb5_free_ticket (context, ticket); 394 free (ticket); 395 } 396 397 out: 398 krb5_data_free (&out_data); 399 krb5_auth_con_free (context, auth_context); 400 } 401 402 static int 403 doit (krb5_keytab keytab, int port) 404 { 405 krb5_error_code ret; 406 krb5_principal server; 407 int *sockets; 408 int maxfd; 409 char *realm; 410 krb5_addresses addrs; 411 unsigned n, i; 412 fd_set real_fdset; 413 struct sockaddr_storage __ss; 414 struct sockaddr *sa = (struct sockaddr *)&__ss; 415 416 ret = krb5_get_default_realm (context, &realm); 417 if (ret) 418 krb5_err (context, 1, ret, "krb5_get_default_realm"); 419 420 ret = krb5_build_principal (context, 421 &server, 422 strlen(realm), 423 realm, 424 "kadmin", 425 "changepw", 426 NULL); 427 if (ret) 428 krb5_err (context, 1, ret, "krb5_build_principal"); 429 430 free (realm); 431 432 ret = krb5_get_all_server_addrs (context, &addrs); 433 if (ret) 434 krb5_err (context, 1, ret, "krb5_get_all_server_addrs"); 435 436 n = addrs.len; 437 438 sockets = malloc (n * sizeof(*sockets)); 439 if (sockets == NULL) 440 krb5_errx (context, 1, "out of memory"); 441 maxfd = 0; 442 FD_ZERO(&real_fdset); 443 for (i = 0; i < n; ++i) { 444 int sa_size; 445 446 krb5_addr2sockaddr (&addrs.val[i], sa, &sa_size, port); 447 448 449 sockets[i] = socket (sa->sa_family, SOCK_DGRAM, 0); 450 if (sockets[i] < 0) 451 krb5_err (context, 1, errno, "socket"); 452 if (bind (sockets[i], sa, sa_size) < 0) { 453 char str[128]; 454 size_t len; 455 ret = krb5_print_address (&addrs.val[i], str, sizeof(str), &len); 456 krb5_err (context, 1, errno, "bind(%s)", str); 457 } 458 maxfd = max (maxfd, sockets[i]); 459 if (maxfd >= FD_SETSIZE) 460 krb5_errx (context, 1, "fd too large"); 461 FD_SET(sockets[i], &real_fdset); 462 } 463 464 while(exit_flag == 0) { 465 int ret; 466 fd_set fdset = real_fdset; 467 468 ret = select (maxfd + 1, &fdset, NULL, NULL, NULL); 469 if (ret < 0) { 470 if (errno == EINTR) 471 continue; 472 else 473 krb5_err (context, 1, errno, "select"); 474 } 475 for (i = 0; i < n; ++i) 476 if (FD_ISSET(sockets[i], &fdset)) { 477 u_char buf[BUFSIZ]; 478 socklen_t addrlen = sizeof(__ss); 479 480 ret = recvfrom (sockets[i], buf, sizeof(buf), 0, 481 sa, &addrlen); 482 if (ret < 0) { 483 if(errno == EINTR) 484 break; 485 else 486 krb5_err (context, 1, errno, "recvfrom"); 487 } 488 489 process (server, keytab, sockets[i], 490 &addrs.val[i], 491 sa, addrlen, 492 buf, ret); 493 } 494 } 495 krb5_free_addresses (context, &addrs); 496 krb5_free_principal (context, server); 497 krb5_free_context (context); 498 return 0; 499 } 500 501 static RETSIGTYPE 502 sigterm(int sig) 503 { 504 exit_flag = 1; 505 } 506 507 const char *check_library = NULL; 508 const char *check_function = NULL; 509 char *keytab_str = "HDB:"; 510 char *realm_str; 511 int version_flag; 512 int help_flag; 513 char *port_str; 514 515 struct getargs args[] = { 516 #ifdef HAVE_DLOPEN 517 { "check-library", 0, arg_string, &check_library, 518 "library to load password check function from", "library" }, 519 { "check-function", 0, arg_string, &check_function, 520 "password check function to load", "function" }, 521 #endif 522 { "keytab", 'k', arg_string, &keytab_str, 523 "keytab to get authentication key from", "kspec" }, 524 { "realm", 'r', arg_string, &realm_str, "default realm", "realm" }, 525 { "port", 'p', arg_string, &port_str, "port" }, 526 { "version", 0, arg_flag, &version_flag }, 527 { "help", 0, arg_flag, &help_flag } 528 }; 529 int num_args = sizeof(args) / sizeof(args[0]); 530 531 int 532 main (int argc, char **argv) 533 { 534 int optind; 535 krb5_keytab keytab; 536 krb5_error_code ret; 537 int port; 538 539 optind = krb5_program_setup(&context, argc, argv, args, num_args, NULL); 540 541 if(help_flag) 542 krb5_std_usage(0, args, num_args); 543 if(version_flag) { 544 print_version(NULL); 545 exit(0); 546 } 547 548 if(realm_str) 549 krb5_set_default_realm(context, realm_str); 550 551 krb5_openlog (context, "kpasswdd", &log_facility); 552 krb5_set_warn_dest(context, log_facility); 553 554 if (port_str != NULL) { 555 struct servent *s = roken_getservbyname (port_str, "udp"); 556 557 if (s != NULL) 558 port = s->s_port; 559 else { 560 char *ptr; 561 562 port = strtol (port_str, &ptr, 10); 563 if (port == 0 && ptr == port_str) 564 krb5_errx (context, 1, "bad port `%s'", port_str); 565 port = htons(port); 566 } 567 } else 568 port = krb5_getportbyname (context, "kpasswd", "udp", KPASSWD_PORT); 569 570 ret = krb5_kt_register(context, &hdb_kt_ops); 571 if(ret) 572 krb5_err(context, 1, ret, "krb5_kt_register"); 573 574 ret = krb5_kt_resolve(context, keytab_str, &keytab); 575 if(ret) 576 krb5_err(context, 1, ret, "%s", keytab_str); 577 578 kadm5_setup_passwd_quality_check (context, check_library, check_function); 579 580 #ifdef HAVE_SIGACTION 581 { 582 struct sigaction sa; 583 584 sa.sa_flags = 0; 585 sa.sa_handler = sigterm; 586 sigemptyset(&sa.sa_mask); 587 588 sigaction(SIGINT, &sa, NULL); 589 sigaction(SIGTERM, &sa, NULL); 590 } 591 #else 592 signal(SIGINT, sigterm); 593 signal(SIGTERM, sigterm); 594 #endif 595 596 pidfile(NULL); 597 598 return doit (keytab, port); 599 } 600