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