1 /* 2 * Copyright 2008 Sun Microsystems, Inc. All rights reserved. 3 * Use is subject to license terms. 4 */ 5 6 #pragma ident "%Z%%M% %I% %E% SMI" 7 8 /* 9 * WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING 10 * 11 * Openvision retains the copyright to derivative works of 12 * this source code. Do *NOT* create a derivative of this 13 * source code before consulting with your legal department. 14 * Do *NOT* integrate *ANY* of this source code into another 15 * product before consulting with your legal department. 16 * 17 * For further information, read the top-level Openvision 18 * copyright which is contained in the top-level MIT Kerberos 19 * copyright. 20 * 21 * WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING 22 * 23 */ 24 25 /* 26 * Copyright 1993 OpenVision Technologies, Inc., All Rights Reserved 27 */ 28 29 /* 30 * Copyright (C) 1998 by the FundsXpress, INC. 31 * 32 * All rights reserved. 33 * 34 * Export of this software from the United States of America may require 35 * a specific license from the United States Government. It is the 36 * responsibility of any person or organization contemplating export to 37 * obtain such a license before exporting. 38 * 39 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and 40 * distribute this software and its documentation for any purpose and 41 * without fee is hereby granted, provided that the above copyright 42 * notice appear in all copies and that both that copyright notice and 43 * this permission notice appear in supporting documentation, and that 44 * the name of FundsXpress. not be used in advertising or publicity pertaining 45 * to distribution of the software without specific, written prior 46 * permission. FundsXpress makes no representations about the suitability of 47 * this software for any purpose. It is provided "as is" without express 48 * or implied warranty. 49 * 50 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR 51 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED 52 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. 53 */ 54 55 56 /* 57 * SUNWresync121 XXX 58 * Beware future resyncers, this file is much diff from MIT (1.0...) 59 */ 60 61 #include <stdio.h> 62 #include <stdio_ext.h> 63 #include <signal.h> 64 #include <syslog.h> 65 #include <sys/types.h> 66 #ifdef _AIX 67 #include <sys/select.h> 68 #endif 69 #include <sys/time.h> 70 #include <sys/socket.h> 71 #include <unistd.h> 72 #include <netinet/in.h> 73 #include <arpa/inet.h> /* inet_ntoa */ 74 #include <gssapi/gssapi.h> 75 #include <rpc/rpc.h> 76 #include <kadm5/admin.h> 77 #include <kadm5/kadm_rpc.h> 78 #include <server_acl.h> 79 #include <krb5/adm_proto.h> 80 #include <string.h> 81 #include <kadm5/server_internal.h> 82 #include <gssapi_krb5.h> 83 #include <libintl.h> 84 #include <locale.h> 85 #include <sys/resource.h> 86 #include <kdb/kdb_log.h> 87 #include <kdb_kt.h> 88 89 #include <rpc/rpcsec_gss.h> 90 #include "misc.h" 91 92 #ifndef FD_SETSIZE 93 #define FD_SETSIZE 256 94 #endif 95 96 #ifndef MAX 97 #define MAX(a, b) (((a) > (b)) ? (a) : (b)) 98 #endif 99 100 #if defined(NEED_DAEMON_PROTO) 101 extern int daemon(int, int); 102 #endif 103 104 105 106 static int signal_request_exit = 0; 107 static int schpw; 108 kadm5_config_params chgpw_params; 109 void kadm_svc_run(void); 110 void setup_signal_handlers(iprop_role iproprole); 111 void sig_exit(int); 112 void sig_pipe(int); 113 krb5_error_code log_kt_error(char*, char*); 114 115 #ifdef POSIX_SIGNALS 116 static struct sigaction s_action; 117 #endif /* POSIX_SIGNALS */ 118 119 120 #define TIMEOUT 15 121 122 typedef struct _auth_gssapi_name { 123 char *name; 124 gss_OID type; 125 } auth_gssapi_name; 126 127 gss_name_t gss_changepw_name = NULL, gss_oldchangepw_name = NULL; 128 void *global_server_handle; 129 130 /* 131 * This is a kludge, but the server needs these constants to be 132 * compatible with old clients. They are defined in <kadm5/admin.h>, 133 * but only if USE_KADM5_API_VERSION == 1. 134 */ 135 #define OVSEC_KADM_ADMIN_SERVICE_P "ovsec_adm@admin" 136 #define OVSEC_KADM_CHANGEPW_SERVICE_P "ovsec_adm@changepw" 137 138 /* 139 * This enables us to set the keytab that gss_acquire_cred uses, but 140 * it also restricts us to linking against the Kv5 GSS-API library. 141 * Since this is *k*admind, that shouldn't be a problem. 142 */ 143 extern char *krb5_overridekeyname; 144 145 extern void krb5_iprop_prog_1(); 146 extern kadm5_ret_t kiprop_get_adm_host_srv_name( 147 krb5_context, 148 const char *, 149 char **); 150 151 static krb5_context context; /* XXX yuck. the signal handlers need this */ 152 153 static krb5_context hctx; 154 155 in_port_t l_port = 0; /* global local port num, for BSM audits */ 156 157 int nofork = 0; /* global; don't fork (debug mode) */ 158 159 160 /* 161 * Function: usage 162 * 163 * Purpose: print out the server usage message 164 * 165 * Arguments: 166 * Requires: 167 * Effects: 168 * Modifies: 169 */ 170 171 static void usage() 172 { 173 fprintf(stderr, gettext("Usage: kadmind [-x db_args]* [-r realm] [-m] [-d] " 174 "[-p port-number]\n")); 175 exit(1); 176 } 177 178 /* 179 * Function: display_status 180 * 181 * Purpose: displays GSS-API messages 182 * 183 * Arguments: 184 * 185 * msg a string to be displayed with the message 186 * maj_stat the GSS-API major status code 187 * min_stat the GSS-API minor status code 188 * 189 * Effects: 190 * 191 * The GSS-API messages associated with maj_stat and min_stat are 192 * displayed on stderr, each preceeded by "GSS-API error <msg>: " and 193 * followed by a newline. 194 */ 195 static void display_status_1(char *, OM_uint32, int); 196 197 static void display_status(msg, maj_stat, min_stat) 198 char *msg; 199 OM_uint32 maj_stat; 200 OM_uint32 min_stat; 201 { 202 display_status_1(msg, maj_stat, GSS_C_GSS_CODE); 203 display_status_1(msg, min_stat, GSS_C_MECH_CODE); 204 } 205 206 static void display_status_1(m, code, type) 207 char *m; 208 OM_uint32 code; 209 int type; 210 { 211 OM_uint32 maj_stat, min_stat; 212 gss_buffer_desc msg; 213 OM_uint32 msg_ctx; 214 215 msg_ctx = 0; 216 while (1) { 217 maj_stat = gss_display_status(&min_stat, code, 218 type, GSS_C_NULL_OID, 219 &msg_ctx, &msg); 220 fprintf(stderr, "GSS-API error %s: %s\n", m, 221 (char *)msg.value); 222 (void) gss_release_buffer(&min_stat, &msg); 223 224 if (!msg_ctx) 225 break; 226 } 227 } 228 229 230 /* 231 * Solaris Kerberos: the following prototypes are needed because these are 232 * private interfaces that do not have prototypes in any .h 233 */ 234 235 extern struct hostent *res_getipnodebyaddr(const void *, size_t, int, int *); 236 extern void res_freehostent(struct hostent *); 237 238 static void 239 freedomnames(char **npp) 240 { 241 char **tpp; 242 243 if (npp) { 244 tpp = npp; 245 while (*tpp++) { 246 free(*(tpp-1)); 247 } 248 free(npp); 249 } 250 } 251 252 /* 253 * Construct a list of uniq FQDNs of all the net interfaces (except 254 * krb5.conf master dups) and return it in arg 'dnames'. 255 * 256 * On successful return (0), caller must call freedomnames() 257 * to free memory. 258 */ 259 static int 260 getdomnames(krb5_context ctx, char *realm, char ***dnames) 261 { 262 krb5_address **addresses = NULL; 263 krb5_address *a = NULL; 264 struct hostent *hp = NULL; 265 int ret, i, result=0, error; 266 char **npp = NULL, **tpp=NULL; 267 int dup=0, n = 0; 268 char *cfhost = NULL; /* krb5 conf file master hostname */ 269 270 if (ret = kadm5_get_master(ctx, realm, &cfhost)) { 271 return (ret); 272 } 273 274 ret = krb5_os_localaddr(ctx, &addresses); 275 if (ret != 0) { 276 if (nofork) 277 (void) fprintf(stderr, 278 "kadmind: get localaddrs failed: %s", 279 error_message(ret)); 280 result = ret; 281 goto err; 282 } 283 284 285 for (i=0; addresses[i]; i++) { 286 a = addresses[i]; 287 hp = res_getipnodebyaddr(a->contents, a->length, 288 a->addrtype == ADDRTYPE_INET 289 ? AF_INET : AF_INET6, 290 &error); 291 if (hp != NULL) { 292 293 /* skip master host in krb5.conf */ 294 if (strcasecmp(cfhost, hp->h_name) == 0) { 295 res_freehostent(hp); 296 hp = NULL; 297 continue; 298 } 299 300 dup = 0; 301 tpp = npp; 302 /* skip if hostname already exists in list */ 303 while (tpp && *tpp++) { 304 if (strcasecmp(*(tpp-1), hp->h_name) == 0) { 305 dup++; 306 break; 307 } 308 } 309 310 if (dup) { 311 res_freehostent(hp); 312 hp = NULL; 313 continue; 314 } 315 316 npp = realloc(npp, sizeof(char *) * (n + 2)); 317 if (!npp) { 318 result = ENOMEM; 319 goto err; 320 } 321 npp[n] = strdup(hp->h_name); 322 if (!npp[n]) { 323 result = ENOMEM; 324 goto err; 325 } 326 npp[n+1] = NULL; 327 n++; 328 329 res_freehostent(hp); 330 hp = NULL; 331 result = 0; 332 } 333 334 } 335 336 #ifdef DEBUG 337 printf("getdomnames: n=%d, i=%d, npp=%p\n", n, i, npp); 338 tpp = npp; 339 while (tpp && *tpp++) { 340 printf("tpp=%s\n", *(tpp-1)); 341 } 342 #endif 343 344 goto out; 345 346 err: 347 if (npp) { 348 freedomnames(npp); 349 npp = NULL; 350 } 351 352 if (hp) { 353 res_freehostent(hp); 354 hp = NULL; 355 } 356 357 out: 358 if (cfhost) { 359 free (cfhost); 360 cfhost = NULL; 361 } 362 if (addresses) { 363 krb5_free_addresses(ctx, addresses); 364 addresses = NULL; 365 } 366 367 if (result == 0) 368 *dnames = npp; 369 370 return (result); 371 } 372 373 /* 374 * Set the rpcsec_gss svc names for all net interfaces. 375 */ 376 static void 377 set_svc_domnames(char *svcname, char **dnames, 378 u_int program, u_int version) 379 { 380 bool_t ret; 381 char **tpp = dnames; 382 383 if (!tpp) 384 return; 385 386 while (*tpp++) { 387 /* MAX_NAME_LEN from rpc/rpcsec_gss.h */ 388 char name[MAXHOSTNAMELEN+MAX_NAME_LEN+2] = {0}; 389 (void) snprintf(name, sizeof(name), "%s@%s", 390 svcname, *(tpp-1)); 391 ret = rpc_gss_set_svc_name(name, 392 "kerberos_v5", 0, 393 program, version); 394 if (nofork && ret) 395 (void) fprintf(stderr, 396 "rpc_gss_set_svc_name success: %s\n", 397 name); 398 } 399 } 400 401 402 403 404 int 405 main(int argc, char *argv[]) 406 { 407 SVCXPRT *transp; 408 extern char *optarg; 409 extern int optind, opterr; 410 int ret, rlen, oldnames = 0; 411 OM_uint32 OMret, major_status, minor_status; 412 char *whoami; 413 FILE *acl_file; 414 gss_buffer_desc in_buf; 415 struct servent *srv; 416 struct sockaddr_in addr; 417 struct sockaddr_in *sin; 418 int s; 419 int optchar; 420 struct netconfig *nconf; 421 void *handlep; 422 int fd; 423 struct t_info tinfo; 424 struct t_bind tbindstr, *tres; 425 426 struct t_optmgmt req, resp; 427 struct opthdr *opt; 428 char reqbuf[128]; 429 struct rlimit rl; 430 431 char *kiprop_name = NULL; /* IProp svc name */ 432 kdb_log_context *log_ctx; 433 kadm5_server_handle_t handle; 434 krb5_context ctx; 435 436 kadm5_config_params params; 437 char **db_args = NULL; 438 int db_args_size = 0; 439 auth_gssapi_name names[6]; 440 gss_buffer_desc gssbuf; 441 gss_OID nt_krb5_name_oid; 442 443 char **dnames = NULL; 444 int retdn; 445 int iprop_supported; 446 447 /* Solaris Kerberos: Stores additional error messages */ 448 char *emsg = NULL; 449 450 /* Solaris Kerberos: Indicates whether loalhost is master or not */ 451 krb5_boolean is_master; 452 453 /* Solaris Kerberos: Used for checking acl file */ 454 gss_name_t name; 455 456 /* This is OID value the Krb5_Name NameType */ 457 gssbuf.value = "{1 2 840 113554 1 2 2 1}"; 458 gssbuf.length = strlen(gssbuf.value); 459 major_status = gss_str_to_oid(&minor_status, &gssbuf, 460 &nt_krb5_name_oid); 461 if (major_status != GSS_S_COMPLETE) { 462 fprintf(stderr, 463 gettext("Couldn't create KRB5 Name NameType OID\n")); 464 display_status("str_to_oid", major_status, minor_status); 465 exit(1); 466 } 467 468 names[0].name = names[1].name = names[2].name = 469 names[3].name = names[4].name = names[5].name =NULL; 470 names[0].type = names[1].type = names[2].type = 471 names[3].type = names[4].type = names[5].type = 472 (gss_OID) nt_krb5_name_oid; 473 474 whoami = (strrchr(argv[0], '/') ? strrchr(argv[0], '/') + 1 : argv[0]); 475 476 (void) setlocale(LC_ALL, ""); 477 478 #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */ 479 #define TEXT_DOMAIN "SYS_TEST" /* Use this only if it weren't */ 480 #endif 481 482 (void) textdomain(TEXT_DOMAIN); 483 484 nofork = 0; 485 486 memset((char *) ¶ms, 0, sizeof (params)); 487 488 while ((optchar = getopt(argc, argv, "r:mdp:x:")) != EOF) { 489 switch (optchar) { 490 case 'r': 491 if (!optarg) 492 usage(); 493 params.realm = optarg; 494 params.mask |= KADM5_CONFIG_REALM; 495 break; 496 case 'm': 497 params.mkey_from_kbd = 1; 498 params.mask |= KADM5_CONFIG_MKEY_FROM_KBD; 499 break; 500 case 'd': 501 nofork = 1; 502 break; 503 case 'p': 504 if (!optarg) 505 usage(); 506 params.kadmind_port = atoi(optarg); 507 params.mask |= KADM5_CONFIG_KADMIND_PORT; 508 break; 509 case 'x': 510 if (!optarg) 511 usage(); 512 db_args_size++; 513 { 514 char **temp = realloc( db_args, 515 sizeof(char*) * (db_args_size+1)); /* one for NULL */ 516 if( temp == NULL ) 517 { 518 fprintf(stderr, gettext("%s: cannot initialize. Not enough memory\n"), 519 whoami); 520 exit(1); 521 } 522 db_args = temp; 523 } 524 db_args[db_args_size-1] = optarg; 525 db_args[db_args_size] = NULL; 526 break; 527 case '?': 528 default: 529 usage(); 530 } 531 } 532 533 534 if (getrlimit(RLIMIT_NOFILE, &rl) == 0) { 535 rl.rlim_cur = rl.rlim_max = MAX(rl.rlim_max, FD_SETSIZE); 536 (void) setrlimit(RLIMIT_NOFILE, &rl); 537 (void) enable_extended_FILE_stdio(-1, -1); 538 } 539 540 if (ret = krb5_init_context(&context)) { 541 fprintf(stderr, 542 gettext("%s: %s while initializing context, aborting\n"), 543 whoami, error_message(ret)); 544 exit(1); 545 } 546 547 krb5_klog_init(context, "admin_server", whoami, 1); 548 /* SUNW14resync */ 549 #if 0 550 krb5_klog_syslog(LOG_INFO, "Seeding random number generator"); 551 ret = krb5_c_random_os_entropy(context, 1, NULL); 552 if(ret) { 553 krb5_klog_syslog(LOG_ERR, "Error getting random seed: %s, aborting", 554 error_message(ret)); 555 exit(1); 556 } 557 #endif 558 559 /* 560 * When using the Horowitz/IETF protocol for 561 * password changing, the default port is 464 562 * (officially recognized by IANA) 563 * 564 * DEFAULT_KPASSWD_PORT -> 464 565 */ 566 chgpw_params.kpasswd_port = DEFAULT_KPASSWD_PORT; 567 chgpw_params.mask |= KADM5_CONFIG_KPASSWD_PORT; 568 chgpw_params.kpasswd_protocol = KRB5_CHGPWD_CHANGEPW_V2; 569 chgpw_params.mask |= KADM5_CONFIG_KPASSWD_PROTOCOL; 570 571 if (ret = kadm5_get_config_params(context, NULL, NULL, &chgpw_params, 572 &chgpw_params)) { 573 /* Solaris Kerberos: Remove double "whoami" */ 574 krb5_klog_syslog(LOG_ERR, gettext("%s while initializing," 575 " aborting"), error_message(ret)); 576 fprintf(stderr, 577 gettext("%s: %s while initializing, aborting\n"), 578 whoami, error_message(ret)); 579 krb5_klog_close(context); 580 exit(1); 581 } 582 583 /* 584 * We now setup the socket and bind() to port 464, so that 585 * kadmind can now listen to and process change-pwd requests 586 * from non-Solaris Kerberos V5 clients such as Microsoft, 587 * MIT, AIX, HP etc 588 */ 589 if ((schpw = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { 590 krb5_klog_syslog(LOG_ERR, gettext( "cannot create simple " 591 "chpw socket: %s"), error_message(errno)); 592 fprintf(stderr, gettext("Cannot create simple chpw " 593 "socket: %s"), error_message(errno)); 594 krb5_klog_close(context); 595 exit(1); 596 } 597 598 memset(&addr, 0, sizeof(addr)); 599 addr.sin_family = AF_INET; 600 addr.sin_addr.s_addr = INADDR_ANY; 601 addr.sin_port = htons(chgpw_params.kpasswd_port); 602 603 if (bind(schpw, (struct sockaddr *)&addr, sizeof(addr)) < 0) { 604 char portbuf[32]; 605 int oerrno = errno; 606 fprintf(stderr, gettext("%s: Cannot bind socket.\n"), whoami); 607 fprintf(stderr, gettext("bind: %s\n"), error_message(oerrno)); 608 errno = oerrno; 609 (void) snprintf(portbuf, sizeof (portbuf), "%d", 610 ntohs(addr.sin_port)); 611 krb5_klog_syslog(LOG_ERR, gettext("cannot bind simple " 612 "chpw socket: %s"), error_message(oerrno)); 613 if(oerrno == EADDRINUSE) { 614 char *w = strrchr(whoami, '/'); 615 if (w) { 616 w++; 617 } 618 else { 619 w = whoami; 620 } 621 fprintf(stderr, gettext( 622 "This probably means that another %s process\n" 623 "is already running, or that another program\n" 624 "is using the server port (number %d).\n" 625 "If another %s is already running, you should\n" 626 "kill it before restarting the server.\n"), 627 w, ntohs(addr.sin_port), w); 628 } 629 krb5_klog_close(context); 630 exit(1); 631 } 632 633 if (ret = kadm5_get_config_params(context, NULL, NULL, ¶ms, 634 ¶ms)) { 635 /* Solaris Kerberos: Remove double "whoami" */ 636 krb5_klog_syslog(LOG_ERR, gettext("%s while initializing," 637 " aborting"), error_message(ret)); 638 fprintf(stderr, 639 gettext("%s: %s while initializing, aborting\n"), 640 whoami, error_message(ret)); 641 krb5_klog_close(context); 642 exit(1); 643 } 644 #define REQUIRED_PARAMS (KADM5_CONFIG_REALM | KADM5_CONFIG_ACL_FILE) 645 646 if ((params.mask & REQUIRED_PARAMS) != REQUIRED_PARAMS) { 647 /* Solaris Kerberos: Keep error messages consistent */ 648 krb5_klog_syslog(LOG_ERR, 649 gettext("Missing required configuration values " 650 "(%lx) while initializing, aborting"), 651 (params.mask & REQUIRED_PARAMS) ^ REQUIRED_PARAMS); 652 fprintf(stderr, 653 gettext("%s: Missing required configuration values " 654 "(%lx) while initializing, aborting\n"), whoami, 655 (params.mask & REQUIRED_PARAMS) ^ REQUIRED_PARAMS); 656 krb5_klog_close(context); 657 exit(1); 658 } 659 660 /* Solaris Kerberos: Ensure that kadmind is only run on a master kdc */ 661 if (ret = kadm5_is_master(context, params.realm, &is_master)){ 662 krb5_klog_syslog(LOG_ERR, 663 gettext("Failed to determine whether host is master " 664 "KDC for realm %s: %s"), params.realm, 665 error_message(ret)); 666 fprintf(stderr, 667 gettext("%s: Failed to determine whether host is master " 668 "KDC for realm %s: %s\n"), whoami, params.realm, 669 error_message(ret)); 670 krb5_klog_close(context); 671 exit(1); 672 } 673 674 if (is_master == FALSE) { 675 char *master = NULL; 676 kadm5_get_master(context, params.realm, &master); 677 678 krb5_klog_syslog(LOG_ERR, 679 gettext("%s can only be run on the master KDC, %s, for " 680 "realm %s"), whoami, master ? master : "unknown", 681 params.realm); 682 fprintf(stderr, 683 gettext("%s: %s can only be run on the master KDC, %s, for " 684 "realm %s\n"), whoami, whoami, master ? master: "unknown", 685 params.realm); 686 krb5_klog_close(context); 687 exit(1); 688 } 689 690 memset((char *) &addr, 0, sizeof (struct sockaddr_in)); 691 addr.sin_family = AF_INET; 692 addr.sin_addr.s_addr = INADDR_ANY; 693 l_port = addr.sin_port = htons(params.kadmind_port); 694 sin = &addr; 695 696 if ((handlep = setnetconfig()) == (void *) NULL) { 697 (void) krb5_klog_syslog(LOG_ERR, 698 gettext("cannot get any transport information")); 699 krb5_klog_close(context); 700 exit(1); 701 } 702 while (nconf = getnetconfig(handlep)) { 703 if ((nconf->nc_semantics == NC_TPI_COTS_ORD) && 704 (strcmp(nconf->nc_protofmly, NC_INET) == 0) && 705 (strcmp(nconf->nc_proto, NC_TCP) == 0)) 706 break; 707 } 708 709 if (nconf == (struct netconfig *) NULL) { 710 (void) endnetconfig(handlep); 711 krb5_klog_close(context); 712 exit(1); 713 } 714 fd = t_open(nconf->nc_device, O_RDWR, &tinfo); 715 if (fd == -1) { 716 krb5_klog_syslog(LOG_ERR, 717 gettext("unable to open connection for ADMIN server")); 718 krb5_klog_close(context); 719 exit(1); 720 } 721 /* LINTED */ 722 opt = (struct opthdr *) reqbuf; 723 opt->level = SOL_SOCKET; 724 opt->name = SO_REUSEADDR; 725 opt->len = sizeof (int); 726 727 /* 728 * The option value is "1". This will allow the server to restart 729 * whilst the previous process is cleaning up after itself in a 730 * FIN_WAIT_2 or TIME_WAIT state. If another process is started 731 * outside of smf(5) then bind will fail anyway, which is what we want. 732 */ 733 reqbuf[sizeof (struct opthdr)] = 1; 734 735 req.flags = T_NEGOTIATE; 736 req.opt.len = sizeof (struct opthdr) + opt->len; 737 req.opt.buf = (char *) opt; 738 739 resp.flags = 0; 740 resp.opt.buf = reqbuf; 741 resp.opt.maxlen = sizeof (reqbuf); 742 743 if (t_optmgmt(fd, &req, &resp) < 0 || resp.flags != T_SUCCESS) { 744 t_error("t_optmgmt"); 745 exit(1); 746 } 747 /* Transform addr to netbuf */ 748 749 tres = (struct t_bind *) t_alloc(fd, T_BIND, T_ADDR); 750 if (tres == NULL) { 751 (void) t_close(fd); 752 (void) krb5_klog_syslog(LOG_ERR, 753 gettext("cannot allocate netbuf")); 754 krb5_klog_close(context); 755 exit(1); 756 } 757 tbindstr.qlen = 8; 758 tbindstr.addr.buf = (char *) sin; 759 tbindstr.addr.len = tbindstr.addr.maxlen = __rpc_get_a_size(tinfo.addr); 760 sin = (struct sockaddr_in *) tbindstr.addr.buf; 761 /* SUNWresync121 XXX (void) memset(&addr, 0, sizeof(addr)); */ 762 763 if (t_bind(fd, &tbindstr, tres) < 0) { 764 int oerrno = errno; 765 fprintf(stderr, gettext("%s: Cannot bind socket.\n"), whoami); 766 fprintf(stderr, gettext("bind: %s\n"), error_message(oerrno)); 767 errno = oerrno; 768 krb5_klog_syslog(LOG_ERR, gettext("Cannot bind socket: %s"), 769 error_message(errno)); 770 if (oerrno == EADDRINUSE) { 771 char *w = strrchr(whoami, '/'); 772 773 if (w) { 774 w++; 775 } else { 776 w = whoami; 777 } 778 fprintf(stderr, gettext( 779 "This probably means that another %s " 780 "process is already\n" 781 "running, or that another program is using " 782 "the server port (number %d)\n" 783 "after being assigned it by the RPC " 784 "portmap deamon. If another\n" 785 "%s is already running, you should kill " 786 "it before\n" 787 "restarting the server. If, on the other hand, " 788 "another program is\n" 789 "using the server port, you should kill it " 790 "before running\n" 791 "%s, and ensure that the conflict does " 792 "not occur in the\n" 793 "future by making sure that %s is started " 794 "on reboot\n" 795 "before portmap.\n"), 796 w, ntohs(addr.sin_port), w, w, w); 797 krb5_klog_syslog(LOG_ERR, 798 gettext("Check for already-running %s or for " 799 "another process using port %d"), w, 800 htons(addr.sin_port)); 801 } 802 krb5_klog_close(context); 803 exit(1); 804 } 805 transp = svc_tli_create(fd, nconf, NULL, 0, 0); 806 (void) t_free((char *) tres, T_BIND); 807 if (transp == NULL) { 808 fprintf(stderr, gettext("%s: Cannot create RPC service.\n"), 809 whoami); 810 krb5_klog_syslog(LOG_ERR, gettext("Cannot create RPC service: %m")); 811 krb5_klog_close(context); 812 exit(1); 813 } 814 if (!svc_register(transp, KADM, KADMVERS, kadm_1, 0)) { 815 fprintf(stderr, 816 gettext("%s: Cannot register RPC service.\n"), whoami); 817 krb5_klog_syslog(LOG_ERR, 818 gettext("Cannot register RPC service, failing.")); 819 krb5_klog_close(context); 820 exit(1); 821 } 822 823 /* 824 * XXX krb5_defkeyname is an internal library global and should go 825 * away 826 */ 827 krb5_overridekeyname = params.admin_keytab; 828 829 /* Solaris Kerberos: 830 * The only service principals which matter here are 831 * -> names[0].name (kadmin/<fqdn>) 832 * -> names[1].name (changepw/<fqdn>) 833 * KADM5_ADMIN_SERVICE_P, KADM5_CHANGEPW_SERVICE_P, 834 * OVSEC_KADM_ADMIN_SERVICE_P, OVSEC_KADM_CHANGEPW_SERVICE_P 835 * are all legacy service princs and calls to rpc_gss_set_svc_name() 836 * using these principals will always fail as they are not host 837 * based principals. 838 */ 839 840 if (ret = kadm5_get_adm_host_srv_name(context, params.realm, 841 &names[0].name)) { 842 krb5_klog_syslog(LOG_ERR, 843 gettext("Cannot get host based service name for admin " 844 "principal in realm %s: %s"), params.realm, 845 error_message(ret)); 846 fprintf(stderr, 847 gettext("%s: Cannot get host based service name for admin " 848 "principal in realm %s: %s\n"), whoami, params.realm, 849 error_message(ret)); 850 krb5_klog_close(context); 851 exit(1); 852 } 853 854 if (ret = kadm5_get_cpw_host_srv_name(context, params.realm, 855 &names[1].name)) { 856 krb5_klog_syslog(LOG_ERR, 857 gettext("Cannot get host based service name for changepw " 858 "principal in realm %s: %s"), params.realm, 859 error_message(ret)); 860 fprintf(stderr, 861 gettext("%s: Cannot get host based service name for " 862 "changepw principal in realm %s: %s\n"), whoami, params.realm, 863 error_message(ret)); 864 krb5_klog_close(context); 865 exit(1); 866 } 867 868 names[2].name = KADM5_ADMIN_SERVICE_P; 869 names[3].name = KADM5_CHANGEPW_SERVICE_P; 870 names[4].name = OVSEC_KADM_ADMIN_SERVICE_P; 871 names[5].name = OVSEC_KADM_CHANGEPW_SERVICE_P; 872 873 /* 874 * Try to acquire creds for the old OV services as well as the new 875 * names, but if that fails just fall back on the new names. 876 */ 877 878 if (rpc_gss_set_svc_name(names[5].name, 879 "kerberos_v5", 0, KADM, KADMVERS) && 880 rpc_gss_set_svc_name(names[4].name, 881 "kerberos_v5", 0, KADM, KADMVERS)) 882 oldnames++; 883 if (rpc_gss_set_svc_name(names[3].name, 884 "kerberos_v5", 0, KADM, KADMVERS)) 885 oldnames++; 886 if (rpc_gss_set_svc_name(names[2].name, 887 "kerberos_v5", 0, KADM, KADMVERS)) 888 oldnames++; 889 890 /* If rpc_gss_set_svc_name() fails for either kadmin/<fqdn> or 891 * for changepw/<fqdn> then try to determine if this is caused 892 * by a missing keytab file or entry. If so, log it and continue. 893 */ 894 if (rpc_gss_set_svc_name(names[0].name, 895 "kerberos_v5", 0, KADM, KADMVERS)) 896 oldnames++; 897 else 898 log_kt_error(names[0].name, whoami); 899 if (rpc_gss_set_svc_name(names[1].name, 900 "kerberos_v5", 0, KADM, KADMVERS)) 901 oldnames++; 902 else 903 log_kt_error(names[1].name, whoami); 904 905 retdn = getdomnames(context, params.realm, &dnames); 906 if (retdn == 0 && dnames) { 907 /* 908 * Multi-homed KDCs sometimes may need to set svc names 909 * for multiple net interfaces so we set them for 910 * all interfaces just in case. 911 */ 912 set_svc_domnames(KADM5_ADMIN_HOST_SERVICE, 913 dnames, KADM, KADMVERS); 914 set_svc_domnames(KADM5_CHANGEPW_HOST_SERVICE, 915 dnames, KADM, KADMVERS); 916 } 917 918 /* if set_names succeeded, this will too */ 919 in_buf.value = names[1].name; 920 in_buf.length = strlen(names[1].name) + 1; 921 (void) gss_import_name(&OMret, &in_buf, (gss_OID) nt_krb5_name_oid, 922 &gss_changepw_name); 923 if (oldnames) { 924 in_buf.value = names[3].name; 925 in_buf.length = strlen(names[3].name) + 1; 926 (void) gss_import_name(&OMret, &in_buf, 927 (gss_OID) nt_krb5_name_oid, 928 &gss_oldchangepw_name); 929 } 930 if (ret = kadm5int_acl_init(context, 0, params.acl_file)) { 931 krb5_klog_syslog(LOG_ERR, gettext("Cannot initialize acl file: %s"), 932 error_message(ret)); 933 fprintf(stderr, gettext("%s: Cannot initialize acl file: %s\n"), 934 whoami, error_message(ret)); 935 krb5_klog_close(context); 936 exit(1); 937 } 938 939 /* 940 * Solaris Kerberos: 941 * Warn if the acl file doesn't contain an entry for a principal in the 942 * default realm. 943 */ 944 gssbuf.length = strlen("joe/admin@") + strlen(params.realm) + 1; 945 gssbuf.value = malloc(gssbuf.length); 946 if (gssbuf.value != NULL) { 947 /* Use any value as the first component - joe in this case */ 948 (void) snprintf(gssbuf.value, gssbuf.length, "joe/admin@%s", 949 params.realm); 950 951 if (gss_import_name(&minor_status, &gssbuf, GSS_C_NT_USER_NAME, 952 &name) == GSS_S_COMPLETE) { 953 954 if (kadm5int_acl_check(context, name, ACL_MODIFY, NULL, 955 NULL) == 0) { 956 krb5_klog_syslog(LOG_WARNING, 957 gettext("acls may not be properly " 958 "configured: failed to find an acl " 959 "matching the default realm \"%s\" in %s"), 960 params.realm, params.acl_file); 961 (void) fprintf(stderr, gettext("%s: Warning: " 962 "acls may not be properly configured: " 963 "failed to find an acl matching the " 964 "default realm \"%s\" in %s\n"), 965 whoami, params.realm, 966 params.acl_file); 967 } 968 (void) gss_release_name(&minor_status, &name); 969 } 970 free(gssbuf.value); 971 gssbuf.value = NULL; 972 } 973 gssbuf.length = 0; 974 975 /* 976 * Solaris Kerberos: 977 * Call a private version of kadm5_init which potentially returns an 978 * additional error string in case of failure. 979 */ 980 if ((ret = kadm5_init2("kadmind", NULL, 981 NULL, ¶ms, 982 KADM5_STRUCT_VERSION, 983 KADM5_API_VERSION_2, 984 db_args, 985 &global_server_handle, 986 &emsg)) != KADM5_OK) { 987 988 krb5_klog_syslog(LOG_ERR, 989 gettext("%s while initializing, aborting"), 990 (emsg ? emsg : error_message(ret))); 991 fprintf(stderr, 992 gettext("%s: %s while initializing, aborting\n"), 993 whoami, (emsg ? emsg : error_message(ret))); 994 if (emsg) 995 free(emsg); 996 997 krb5_klog_close(context); 998 kadm5_destroy(context); 999 exit(1); 1000 } 1001 1002 /* 1003 * Solaris Kerberos: 1004 * List the logs (FILE, STDERR, etc) which are currently being 1005 * logged to and print to stderr. Useful when trying to 1006 * track down a failure via SMF. 1007 */ 1008 if (ret = krb5_klog_list_logs(whoami)) { 1009 fprintf(stderr, gettext("%s: %s while listing logs\n"), 1010 whoami, error_message(ret)); 1011 krb5_klog_syslog(LOG_ERR, gettext("%s while listing logs"), 1012 error_message(ret)); 1013 } 1014 1015 if (!nofork && (ret = daemon(0, 0))) { 1016 ret = errno; 1017 krb5_klog_syslog(LOG_ERR, 1018 gettext("Cannot detach from tty: %s"), 1019 error_message(ret)); 1020 fprintf(stderr, gettext("%s: Cannot detach from tty: %s\n"), 1021 whoami, error_message(ret)); 1022 krb5_klog_close(context); 1023 exit(1); 1024 } 1025 1026 if( db_args ) 1027 { 1028 free(db_args), db_args=NULL; 1029 } 1030 1031 handle = global_server_handle; 1032 ctx = handle->context; 1033 if (params.iprop_enabled == TRUE) { 1034 if (ret = krb5_db_supports_iprop(ctx, &iprop_supported)) { 1035 fprintf(stderr, 1036 gettext("%s: %s while trying to determine if KDB " 1037 "plugin supports iprop\n"), whoami, 1038 error_message(ret)); 1039 krb5_klog_syslog(LOG_ERR, 1040 gettext("%s while trying to determine if KDB " 1041 "plugin supports iprop"), error_message(ret)); 1042 krb5_klog_close(ctx); 1043 exit(1); 1044 } 1045 1046 if (!iprop_supported) { 1047 fprintf(stderr, 1048 gettext("%s: Warning, current KDB " 1049 "plugin does not support iprop, continuing " 1050 "with iprop disabled\n"), whoami); 1051 krb5_klog_syslog(LOG_WARNING, 1052 gettext("Warning, current KDB " 1053 "plugin does not support iprop, continuing " 1054 "with iprop disabled")); 1055 1056 ulog_set_role(ctx, IPROP_NULL); 1057 } else 1058 ulog_set_role(ctx, IPROP_MASTER); 1059 } else 1060 ulog_set_role(ctx, IPROP_NULL); 1061 1062 log_ctx = ctx->kdblog_context; 1063 1064 if (log_ctx && (log_ctx->iproprole == IPROP_MASTER)) { 1065 /* 1066 * IProp is enabled, so let's map in the update log 1067 * and setup the service. 1068 */ 1069 if (ret = ulog_map(ctx, ¶ms, FKADMIND)) { 1070 fprintf(stderr, 1071 gettext("%s: %s while mapping update log " 1072 "(`%s.ulog')\n"), whoami, error_message(ret), 1073 params.dbname); 1074 krb5_klog_syslog(LOG_ERR, 1075 gettext("%s while mapping update log " 1076 "(`%s.ulog')"), error_message(ret), 1077 params.dbname); 1078 krb5_klog_close(ctx); 1079 exit(1); 1080 } 1081 1082 1083 if (nofork) 1084 fprintf(stderr, 1085 "%s: create IPROP svc (PROG=%d, VERS=%d)\n", 1086 whoami, KRB5_IPROP_PROG, KRB5_IPROP_VERS); 1087 1088 if (!svc_create(krb5_iprop_prog_1, 1089 KRB5_IPROP_PROG, KRB5_IPROP_VERS, 1090 "circuit_v")) { 1091 fprintf(stderr, 1092 gettext("%s: Cannot create IProp RPC service (PROG=%d, VERS=%d)\n"), 1093 whoami, 1094 KRB5_IPROP_PROG, KRB5_IPROP_VERS); 1095 krb5_klog_syslog(LOG_ERR, 1096 gettext("Cannot create IProp RPC service (PROG=%d, VERS=%d), failing."), 1097 KRB5_IPROP_PROG, KRB5_IPROP_VERS); 1098 krb5_klog_close(ctx); 1099 exit(1); 1100 } 1101 1102 if (ret = kiprop_get_adm_host_srv_name(ctx, 1103 params.realm, 1104 &kiprop_name)) { 1105 krb5_klog_syslog(LOG_ERR, 1106 gettext("%s while getting IProp svc name, failing"), 1107 error_message(ret)); 1108 fprintf(stderr, 1109 gettext("%s: %s while getting IProp svc name, failing\n"), 1110 whoami, error_message(ret)); 1111 krb5_klog_close(ctx); 1112 exit(1); 1113 } 1114 1115 if (!rpc_gss_set_svc_name(kiprop_name, "kerberos_v5", 0, 1116 KRB5_IPROP_PROG, KRB5_IPROP_VERS)) { 1117 rpc_gss_error_t err; 1118 (void) rpc_gss_get_error(&err); 1119 1120 /* Try to determine if the error was caused by a missing keytab or 1121 * missing keytab entries (and log it). 1122 */ 1123 log_kt_error(kiprop_name, whoami); 1124 krb5_klog_syslog(LOG_ERR, 1125 gettext("Unable to set RPCSEC_GSS service name (`%s'), failing."), 1126 kiprop_name ? kiprop_name : "<null>"); 1127 fprintf(stderr, 1128 gettext("%s: Unable to set RPCSEC_GSS service name (`%s'), failing.\n"), 1129 whoami, 1130 kiprop_name ? kiprop_name : "<null>"); 1131 1132 if (nofork) { 1133 fprintf(stderr, 1134 "%s: set svc name (rpcsec err=%d, sys err=%d)\n", 1135 whoami, 1136 err.rpc_gss_error, 1137 err.system_error); 1138 } 1139 1140 exit(1); 1141 } 1142 free(kiprop_name); 1143 1144 if (retdn == 0 && dnames) { 1145 set_svc_domnames(KADM5_KIPROP_HOST_SERVICE, 1146 dnames, 1147 KRB5_IPROP_PROG, KRB5_IPROP_VERS); 1148 } 1149 1150 } else { 1151 if (!oldnames) { 1152 /* rpc_gss_set_svc_name failed for both kadmin/<fqdn> and 1153 * changepw/<fqdn>. 1154 */ 1155 krb5_klog_syslog(LOG_ERR, 1156 gettext("Unable to set RPCSEC_GSS service names " 1157 "('%s, %s')"), 1158 names[0].name, names[1].name); 1159 fprintf(stderr, 1160 gettext("%s: Unable to set RPCSEC_GSS service names " 1161 "('%s, %s')\n"), 1162 whoami, 1163 names[0].name, names[1].name); 1164 krb5_klog_close(context); 1165 exit(1); 1166 } 1167 } 1168 1169 if (dnames) 1170 freedomnames(dnames); 1171 1172 setup_signal_handlers(log_ctx->iproprole); 1173 krb5_klog_syslog(LOG_INFO, gettext("starting")); 1174 if (nofork) 1175 fprintf(stderr, "%s: starting...\n", whoami); 1176 1177 1178 /* 1179 * We now call our own customized async event processing 1180 * function kadm_svc_run(), as opposed to svc_run() earlier, 1181 * since this enables kadmind to also listen-to/process 1182 * non-RPCSEC_GSS based change-pwd requests apart from the 1183 * regular, RPCSEC_GSS kpasswd requests from Solaris Krb5 clients. 1184 */ 1185 kadm_svc_run(); 1186 1187 krb5_klog_syslog(LOG_INFO, gettext("finished, exiting")); 1188 kadm5_destroy(global_server_handle); 1189 t_close(fd); 1190 krb5_klog_close(context); 1191 exit(0); 1192 } 1193 1194 1195 /* 1196 * Function: kadm_svc_run 1197 * 1198 * Purpose: modified version of sunrpc svc_run. 1199 * which closes the database every TIMEOUT seconds. 1200 * 1201 * Arguments: 1202 * Requires: 1203 * Effects: 1204 * Modifies: 1205 */ 1206 void 1207 kadm_svc_run(void) 1208 { 1209 struct pollfd *rfd = 0; 1210 struct timeval timeout; 1211 int pollret; 1212 int nfds = 0; 1213 int i; 1214 1215 while(signal_request_exit == 0) { 1216 timeout.tv_sec = TIMEOUT; 1217 timeout.tv_usec = 0; 1218 1219 if (nfds != svc_max_pollfd) { 1220 rfd = realloc(rfd, sizeof (pollfd_t) * svc_max_pollfd); 1221 nfds = svc_max_pollfd; 1222 } 1223 1224 (void) memcpy(rfd, svc_pollfd, 1225 sizeof (pollfd_t) * svc_max_pollfd); 1226 1227 for (i = 0; i < nfds; i++) { 1228 if (rfd[i].fd == -1) { 1229 rfd[i].fd = schpw; 1230 rfd[i].events = POLLIN; 1231 break; 1232 } 1233 } 1234 1235 switch(pollret = poll(rfd, nfds, 1236 __rpc_timeval_to_msec(&timeout))) { 1237 case -1: 1238 if(errno == EINTR) 1239 continue; 1240 perror("poll"); 1241 return; 1242 case 0: 1243 continue; 1244 default: 1245 for (i = 0; i < nfds; i++) { 1246 if (rfd[i].revents & POLLIN) { 1247 if (rfd[i].fd == schpw) 1248 handle_chpw(context, schpw, 1249 global_server_handle, 1250 &chgpw_params); 1251 else 1252 svc_getreq_poll(rfd, pollret); 1253 break; 1254 } else { 1255 if (i == (nfds - 1)) 1256 perror("poll"); 1257 } 1258 } 1259 break; 1260 } 1261 } 1262 } 1263 1264 1265 /* 1266 * Function: setup_signal_handlers 1267 * 1268 * Purpose: Setup signal handling functions with System V's signal(). 1269 */ 1270 void setup_signal_handlers(iprop_role iproprole) { 1271 signal(SIGINT, sig_exit); 1272 signal(SIGTERM, sig_exit); 1273 signal(SIGQUIT, sig_exit); 1274 signal(SIGPIPE, sig_pipe); 1275 1276 /* 1277 * IProp will fork for a full-resync, we don't want to 1278 * wait on it and we don't want the living dead procs either. 1279 */ 1280 if (iproprole == IPROP_MASTER) 1281 (void) signal(SIGCHLD, SIG_IGN); 1282 1283 return; 1284 } 1285 1286 1287 /* 1288 * Function: sig_exit 1289 * 1290 * Purpose: sets flags saying the server got a signal and that it 1291 * should exit when convenient. 1292 * 1293 * Effects: 1294 * Modifies signal_request_exit which ideally makes the server exit 1295 * at some point. 1296 * 1297 * Modifies: 1298 * Signal_request_exit 1299 */ 1300 void sig_exit(int signum) 1301 { 1302 krb5_klog_syslog(LOG_NOTICE, gettext("Got signal to request exit")); 1303 signal_request_exit = 1; 1304 return; 1305 } 1306 1307 1308 /* 1309 * Function: sig_pipe 1310 * 1311 * Purpose: SIGPIPE handler 1312 * 1313 * Effects: krb5_klog_syslog a message that a SIGPIPE occurred and returns, 1314 * thus causing the read() or write() to fail and, presumable, the RPC 1315 * to recover. Otherwise, the process aborts. 1316 */ 1317 void 1318 sig_pipe(int unused) 1319 { 1320 krb5_klog_syslog(LOG_NOTICE, gettext("Warning: Received a SIGPIPE; " 1321 "probably a client aborted. Continuing.")); 1322 } 1323 1324 1325 /* 1326 * Given a service name (s_name) determine if the keytab file exists 1327 * and if the keytab entry is present. Log missing keytab 1328 * at LOG_ERR and log missing keytab entries at LOG_WARNING. 1329 * If any of krb5_* (or strdup) fail it will return the failure. 1330 */ 1331 krb5_error_code log_kt_error(char *s_name, char *whoami) { 1332 krb5_keytab kt; 1333 krb5_principal princ; 1334 krb5_keytab_entry entry; 1335 krb5_error_code code = 0; 1336 char kt_name[MAX_KEYTAB_NAME_LEN]; 1337 char *service; 1338 char *host; 1339 1340 service = strdup(s_name); 1341 if(!service) 1342 return ENOMEM; 1343 1344 host = strchr(service, '@'); 1345 *host++ = '\0'; 1346 if (code = krb5_sname_to_principal(context, host, 1347 service, KRB5_NT_SRV_HST, &princ)) { 1348 krb5_klog_syslog(LOG_ERR, 1349 gettext("krb5_sname_to_principal failed: %s"), 1350 error_message(code)); 1351 fprintf(stderr, gettext("%s: krb5_sname_to_principal failed: %s\n"), 1352 whoami, error_message(code)); 1353 free(service); 1354 return code; 1355 } 1356 1357 if (code = krb5_kt_default_name(context, kt_name, sizeof (kt_name))) { 1358 krb5_klog_syslog(LOG_ERR, 1359 gettext("krb5_kt_default_name failed: %s"), 1360 error_message(code)); 1361 fprintf(stderr, gettext("%s: krb5_kt_default_name failed: %s\n"), 1362 whoami, error_message(code)); 1363 krb5_free_principal(context, princ); 1364 free(service); 1365 return code; 1366 } 1367 1368 if (code = krb5_kt_default(context, &kt)) { 1369 krb5_klog_syslog(LOG_ERR, 1370 gettext("krb5_kt_default failed: %s"), 1371 error_message(code)); 1372 fprintf(stderr, gettext("%s: krb5_kt_default failed: %s\n"), 1373 whoami, error_message(code)); 1374 krb5_free_principal(context, princ); 1375 free(service); 1376 return code; 1377 } 1378 1379 code = krb5_kt_get_entry(context, kt, princ, 0, 0, &entry); 1380 1381 switch (code) { 1382 case 0: 1383 krb5_kt_free_entry(context, &entry); 1384 break; 1385 case KRB5_KT_NOTFOUND: 1386 krb5_klog_syslog(LOG_WARNING, 1387 gettext("Keytab entry \"%s/%s\" is missing from \"%s\""), 1388 service, host, 1389 kt_name); 1390 fprintf(stderr, gettext("%s: Keytab entry \"%s/%s\" is missing from \"%s\".\n"), 1391 whoami, 1392 service, host, 1393 kt_name); 1394 break; 1395 case ENOENT: 1396 krb5_klog_syslog(LOG_ERR, 1397 gettext("Keytab file \"%s\" does not exist"), 1398 kt_name); 1399 fprintf(stderr, gettext("%s: Keytab file \"%s\" does not exist.\n"), 1400 whoami, 1401 kt_name); 1402 break; 1403 } 1404 krb5_kt_close(context,kt); 1405 krb5_free_principal(context, princ); 1406 free(service); 1407 return code; 1408 } 1409 1410