/* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING * * Openvision retains the copyright to derivative works of * this source code. Do *NOT* create a derivative of this * source code before consulting with your legal department. * Do *NOT* integrate *ANY* of this source code into another * product before consulting with your legal department. * * For further information, read the top-level Openvision * copyright which is contained in the top-level MIT Kerberos * copyright. * * WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING * */ /* * Copyright 1993 OpenVision Technologies, Inc., All Rights Reserved * */ /* * Copyright (C) 1998 by the FundsXpress, INC. * * All rights reserved. * * Export of this software from the United States of America may require * a specific license from the United States Government. It is the * responsibility of any person or organization contemplating export to * obtain such a license before exporting. * * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and * distribute this software and its documentation for any purpose and * without fee is hereby granted, provided that the above copyright * notice appear in all copies and that both that copyright notice and * this permission notice appear in supporting documentation, and that * the name of FundsXpress. not be used in advertising or publicity pertaining * to distribution of the software without specific, written prior * permission. FundsXpress makes no representations about the suitability of * this software for any purpose. It is provided "as is" without express * or implied warranty. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ /* * SUNWresync121 XXX * Beware future resyncers, this file is much diff from MIT (1.0...) */ #include #include #include #include #include #ifdef _AIX #include #endif #include #include #include #include #include /* inet_ntoa */ #include #include "gssapiP_krb5.h" /* for kg_get_context */ #include #include #include #include #include #include "kdb_kt.h" /* for krb5_ktkdb_set_context */ #include #include #include #include #include #include #include #include #include #include "misc.h" #ifdef PURIFY #include "purify.h" int signal_pure_report = 0; int signal_pure_clear = 0; void request_pure_report(int); void request_pure_clear(int); #endif /* PURIFY */ #ifndef FD_SETSIZE #define FD_SETSIZE 256 #endif #ifndef MAX #define MAX(a, b) (((a) > (b)) ? (a) : (b)) #endif #if defined(NEED_DAEMON_PROTO) extern int daemon(int, int); #endif static int signal_request_exit = 0; kadm5_config_params chgpw_params; void setup_signal_handlers(iprop_role iproprole); void request_exit(int); void sig_pipe(int); void kadm_svc_run(void); #ifdef POSIX_SIGNALS static struct sigaction s_action; #endif /* POSIX_SIGNALS */ #define TIMEOUT 15 typedef struct _auth_gssapi_name { char *name; gss_OID type; } auth_gssapi_name; gss_name_t gss_changepw_name = NULL, gss_oldchangepw_name = NULL; void *global_server_handle; /* * This is a kludge, but the server needs these constants to be * compatible with old clients. They are defined in , * but only if USE_KADM5_API_VERSION == 1. */ #define OVSEC_KADM_ADMIN_SERVICE_P "ovsec_adm@admin" #define OVSEC_KADM_CHANGEPW_SERVICE_P "ovsec_adm@changepw" extern void krb5_iprop_prog_1(); extern kadm5_ret_t kiprop_get_adm_host_srv_name( krb5_context, const char *, char **); static int schpw; in_port_t l_port = 0; /* global local port num, for BSM audits */ int nofork = 0; /* global; don't fork (debug mode) */ /* * Function: usage * * Purpose: print out the server usage message * * Arguments: * Requires: * Effects: * Modifies: */ static void usage() { fprintf(stderr, gettext("Usage: kadmind [-x db_args]* [-r realm] [-m] [-d] " "[-p port-number]\n")); exit(1); } /* * Function: display_status * * Purpose: displays GSS-API messages * * Arguments: * * msg a string to be displayed with the message * maj_stat the GSS-API major status code * min_stat the GSS-API minor status code * * Effects: * * The GSS-API messages associated with maj_stat and min_stat are * displayed on stderr, each preceeded by "GSS-API error : " and * followed by a newline. */ static void display_status_1(char *, OM_uint32, int); static void display_status(msg, maj_stat, min_stat) char *msg; OM_uint32 maj_stat; OM_uint32 min_stat; { display_status_1(msg, maj_stat, GSS_C_GSS_CODE); display_status_1(msg, min_stat, GSS_C_MECH_CODE); } static void display_status_1(m, code, type) char *m; OM_uint32 code; int type; { OM_uint32 min_stat; gss_buffer_desc msg; OM_uint32 msg_ctx; msg_ctx = 0; while (1) { (void) gss_display_status(&min_stat, code, type, GSS_C_NULL_OID, &msg_ctx, &msg); fprintf(stderr, "GSS-API error %s: %s\n", m, (char *)msg.value); (void) gss_release_buffer(&min_stat, &msg); if (!msg_ctx) break; } } /* * Solaris Kerberos: the following prototypes are needed because these are * private interfaces that do not have prototypes in any .h */ extern struct hostent *res_getipnodebyaddr(const void *, size_t, int, int *); extern void res_freehostent(struct hostent *); static void freedomnames(char **npp) { char **tpp; if (npp) { tpp = npp; while (*tpp++) { free(*(tpp-1)); } free(npp); } } /* * Construct a list of uniq FQDNs of all the net interfaces (except * krb5.conf master dups) and return it in arg 'dnames'. * * On successful return (0), caller must call freedomnames() * to free memory. */ static int getdomnames(krb5_context ctx, char *realm, char ***dnames) { krb5_address **addresses = NULL; krb5_address *a = NULL; struct hostent *hp = NULL; int ret, i, result=0, error; char **npp = NULL, **tpp=NULL; int dup=0, n = 0; char *cfhost = NULL; /* krb5 conf file master hostname */ if (ret = kadm5_get_master(ctx, realm, &cfhost)) { return (ret); } ret = krb5_os_localaddr(ctx, &addresses); if (ret != 0) { if (nofork) (void) fprintf(stderr, "kadmind: get localaddrs failed: %s", error_message(ret)); result = ret; goto err; } for (i=0; addresses[i]; i++) { a = addresses[i]; hp = res_getipnodebyaddr(a->contents, a->length, a->addrtype == ADDRTYPE_INET ? AF_INET : AF_INET6, &error); if (hp != NULL) { /* skip master host in krb5.conf */ if (strcasecmp(cfhost, hp->h_name) == 0) { res_freehostent(hp); hp = NULL; continue; } dup = 0; tpp = npp; /* skip if hostname already exists in list */ while (tpp && *tpp++) { if (strcasecmp(*(tpp-1), hp->h_name) == 0) { dup++; break; } } if (dup) { res_freehostent(hp); hp = NULL; continue; } npp = realloc(npp, sizeof(char *) * (n + 2)); if (!npp) { result = ENOMEM; goto err; } npp[n] = strdup(hp->h_name); if (!npp[n]) { result = ENOMEM; goto err; } npp[n+1] = NULL; n++; res_freehostent(hp); hp = NULL; result = 0; } } #ifdef DEBUG printf("getdomnames: n=%d, i=%d, npp=%p\n", n, i, npp); tpp = npp; while (tpp && *tpp++) { printf("tpp=%s\n", *(tpp-1)); } #endif goto out; err: if (npp) { freedomnames(npp); npp = NULL; } if (hp) { res_freehostent(hp); hp = NULL; } out: if (cfhost) { free (cfhost); cfhost = NULL; } if (addresses) { krb5_free_addresses(ctx, addresses); addresses = NULL; } if (result == 0) *dnames = npp; return (result); } /* * Set the rpcsec_gss svc names for all net interfaces. */ static void set_svc_domnames(char *svcname, char **dnames, u_int program, u_int version) { bool_t ret; char **tpp = dnames; if (!tpp) return; while (*tpp++) { /* MAX_NAME_LEN from rpc/rpcsec_gss.h */ char name[MAXHOSTNAMELEN+MAX_NAME_LEN+2] = {0}; (void) snprintf(name, sizeof(name), "%s@%s", svcname, *(tpp-1)); ret = rpc_gss_set_svc_name(name, "kerberos_v5", 0, program, version); if (nofork && ret) (void) fprintf(stderr, "rpc_gss_set_svc_name success: %s\n", name); } } /* XXX yuck. the signal handlers need this */ static krb5_context context; static krb5_context hctx; int main(int argc, char *argv[]) { register SVCXPRT *transp; extern char *optarg; extern int optind, opterr; int ret, rlen, oldnames = 0; OM_uint32 OMret, major_status, minor_status; char *whoami; FILE *acl_file; gss_buffer_desc in_buf; struct servent *srv; struct sockaddr_in addr; struct sockaddr_in *sin; int s; auth_gssapi_name names[6]; gss_buffer_desc gssbuf; gss_OID nt_krb5_name_oid; int optchar; struct netconfig *nconf; void *handlep; int fd; struct t_info tinfo; struct t_bind tbindstr, *tres; struct t_optmgmt req, resp; struct opthdr *opt; char reqbuf[128]; struct rlimit rl; char *kiprop_name = NULL; /* IProp svc name */ kdb_log_context *log_ctx; kadm5_server_handle_t handle; krb5_context ctx; kadm5_config_params params; char **db_args = NULL; int db_args_size = 0; const char *errmsg; char **dnames = NULL; int retdn; int iprop_supported; /* Solaris Kerberos: Stores additional error messages */ char *emsg = NULL; /* Solaris Kerberos: Indicates whether loalhost is master or not */ krb5_boolean is_master; /* Solaris Kerberos: Used for checking acl file */ gss_name_t name; /* This is OID value the Krb5_Name NameType */ gssbuf.value = "{1 2 840 113554 1 2 2 1}"; gssbuf.length = strlen(gssbuf.value); major_status = gss_str_to_oid(&minor_status, &gssbuf, &nt_krb5_name_oid); if (major_status != GSS_S_COMPLETE) { fprintf(stderr, gettext("Couldn't create KRB5 Name NameType OID\n")); display_status("str_to_oid", major_status, minor_status); exit(1); } names[0].name = names[1].name = names[2].name = names[3].name = NULL; names[4].name = names[5].name =NULL; names[0].type = names[1].type = names[2].type = names[3].type = (gss_OID) nt_krb5_name_oid; names[4].type = names[5].type = (gss_OID) nt_krb5_name_oid; #ifdef PURIFY purify_start_batch(); #endif /* PURIFY */ whoami = (strrchr(argv[0], '/') ? strrchr(argv[0], '/')+1 : argv[0]); (void) setlocale(LC_ALL, ""); #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */ #define TEXT_DOMAIN "SYS_TEST" /* Use this only if it weren't */ #endif (void) textdomain(TEXT_DOMAIN); nofork = 0; memset((char *) ¶ms, 0, sizeof(params)); while ((optchar = getopt(argc, argv, "r:mdp:x:")) != EOF) { switch (optchar) { case 'r': if (!optarg) usage(); params.realm = optarg; params.mask |= KADM5_CONFIG_REALM; break; case 'm': params.mkey_from_kbd = 1; params.mask |= KADM5_CONFIG_MKEY_FROM_KBD; break; case 'd': nofork = 1; break; case 'p': if (!optarg) usage(); params.kadmind_port = atoi(optarg); params.mask |= KADM5_CONFIG_KADMIND_PORT; break; case 'x': if (!optarg) usage(); db_args_size++; { char **temp = realloc( db_args, sizeof(char*) * (db_args_size+1)); /* one for NULL */ if( temp == NULL ) { fprintf(stderr, gettext("%s: cannot initialize. Not enough memory\n"), whoami); exit(1); } db_args = temp; } db_args[db_args_size-1] = optarg; db_args[db_args_size] = NULL; break; case '?': default: usage(); } } if (getrlimit(RLIMIT_NOFILE, &rl) == 0) { rl.rlim_cur = rl.rlim_max = MAX(rl.rlim_max, FD_SETSIZE); (void) setrlimit(RLIMIT_NOFILE, &rl); (void) enable_extended_FILE_stdio(-1, -1); } if ((ret = kadm5_init_krb5_context(&context))) { fprintf(stderr, gettext("%s: %s while initializing context, aborting\n"), whoami, error_message(ret)); exit(1); } krb5_klog_init(context, "admin_server", whoami, 1); /* Solaris Kerberos */ if((ret = kadm5_init2("kadmind", NULL, NULL, ¶ms, KADM5_STRUCT_VERSION, KADM5_API_VERSION_2, db_args, &global_server_handle, &emsg)) != KADM5_OK) { krb5_klog_syslog(LOG_ERR, gettext("%s while initializing, aborting"), (emsg ? emsg : error_message(ret))); fprintf(stderr, gettext("%s: %s while initializing, aborting\n"), whoami, (emsg ? emsg : error_message(ret))); if (emsg) free(emsg); krb5_klog_close(context); exit(1); } if( db_args ) { free(db_args), db_args=NULL; } if ((ret = kadm5_get_config_params(context, 1, ¶ms, ¶ms))) { const char *e_txt = krb5_get_error_message (context, ret); /* Solaris Kerberos: Remove double "whoami" */ krb5_klog_syslog(LOG_ERR, gettext("%s while initializing, aborting"), e_txt); fprintf(stderr, gettext("%s: %s while initializing, aborting\n"), whoami, e_txt); kadm5_destroy(global_server_handle); krb5_klog_close(context); exit(1); } #define REQUIRED_PARAMS (KADM5_CONFIG_REALM | KADM5_CONFIG_ACL_FILE) if ((params.mask & REQUIRED_PARAMS) != REQUIRED_PARAMS) { /* Solaris Kerberos: Keep error messages consistent */ krb5_klog_syslog(LOG_ERR, gettext("Missing required configuration values (%lx)" "while initializing, aborting"), (params.mask & REQUIRED_PARAMS) ^ REQUIRED_PARAMS); fprintf(stderr, gettext("%s: Missing required configuration values " "(%lx) while initializing, aborting\n"), whoami, (params.mask & REQUIRED_PARAMS) ^ REQUIRED_PARAMS); krb5_klog_close(context); kadm5_destroy(global_server_handle); exit(1); } /* * When using the Horowitz/IETF protocol for * password changing, the default port is 464 * (officially recognized by IANA) * * DEFAULT_KPASSWD_PORT -> 464 */ chgpw_params.kpasswd_port = DEFAULT_KPASSWD_PORT; chgpw_params.mask |= KADM5_CONFIG_KPASSWD_PORT; chgpw_params.kpasswd_protocol = KRB5_CHGPWD_CHANGEPW_V2; chgpw_params.mask |= KADM5_CONFIG_KPASSWD_PROTOCOL; if (ret = kadm5_get_config_params(context, 1, &chgpw_params, &chgpw_params)) { /* Solaris Kerberos: Remove double "whoami" */ krb5_klog_syslog(LOG_ERR, gettext("%s while initializing," " aborting"), error_message(ret)); fprintf(stderr, gettext("%s: %s while initializing, aborting\n"), whoami, error_message(ret)); krb5_klog_close(context); exit(1); } /* * We now setup the socket and bind() to port 464, so that * kadmind can now listen to and process change-pwd requests * from non-Solaris Kerberos V5 clients such as Microsoft, * MIT, AIX, HP etc */ if ((schpw = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { const char *e_txt = krb5_get_error_message (context, ret); krb5_klog_syslog(LOG_ERR, gettext( "Cannot create simple " "chpw socket: %s"), e_txt); fprintf(stderr, gettext("Cannot create simple chpw socket: %s"), e_txt); kadm5_destroy(global_server_handle); krb5_klog_close(context); exit(1); } /* Solaris Kerberos: Ensure that kadmind is only run on a master kdc */ if (ret = kadm5_is_master(context, params.realm, &is_master)){ krb5_klog_syslog(LOG_ERR, gettext("Failed to determine whether host is master " "KDC for realm %s: %s"), params.realm, error_message(ret)); fprintf(stderr, gettext("%s: Failed to determine whether host is master " "KDC for realm %s: %s\n"), whoami, params.realm, error_message(ret)); krb5_klog_close(context); exit(1); } if (is_master == FALSE) { char *master = NULL; kadm5_get_master(context, params.realm, &master); krb5_klog_syslog(LOG_ERR, gettext("%s can only be run on the master KDC, %s, for " "realm %s"), whoami, master ? master : "unknown", params.realm); fprintf(stderr, gettext("%s: %s can only be run on the master KDC, %s, for " "realm %s\n"), whoami, whoami, master ? master: "unknown", params.realm); krb5_klog_close(context); exit(1); } memset((char *) &addr, 0, sizeof (struct sockaddr_in)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = INADDR_ANY; l_port = addr.sin_port = htons(params.kadmind_port); sin = &addr; if ((handlep = setnetconfig()) == (void *) NULL) { (void) krb5_klog_syslog(LOG_ERR, gettext("cannot get any transport information")); krb5_klog_close(context); exit(1); } while (nconf = getnetconfig(handlep)) { if ((nconf->nc_semantics == NC_TPI_COTS_ORD) && (strcmp(nconf->nc_protofmly, NC_INET) == 0) && (strcmp(nconf->nc_proto, NC_TCP) == 0)) break; } if (nconf == (struct netconfig *) NULL) { (void) endnetconfig(handlep); krb5_klog_close(context); exit(1); } fd = t_open(nconf->nc_device, O_RDWR, &tinfo); if (fd == -1) { krb5_klog_syslog(LOG_ERR, gettext("unable to open connection for ADMIN server")); krb5_klog_close(context); exit(1); } /* LINTED */ opt = (struct opthdr *) reqbuf; opt->level = SOL_SOCKET; opt->name = SO_REUSEADDR; opt->len = sizeof (int); /* * The option value is "1". This will allow the server to restart * whilst the previous process is cleaning up after itself in a * FIN_WAIT_2 or TIME_WAIT state. If another process is started * outside of smf(5) then bind will fail anyway, which is what we want. */ reqbuf[sizeof (struct opthdr)] = 1; req.flags = T_NEGOTIATE; req.opt.len = sizeof (struct opthdr) + opt->len; req.opt.buf = (char *) opt; resp.flags = 0; resp.opt.buf = reqbuf; resp.opt.maxlen = sizeof (reqbuf); if (t_optmgmt(fd, &req, &resp) < 0 || resp.flags != T_SUCCESS) { t_error("t_optmgmt"); exit(1); } /* Transform addr to netbuf */ tres = (struct t_bind *) t_alloc(fd, T_BIND, T_ADDR); if (tres == NULL) { (void) t_close(fd); (void) krb5_klog_syslog(LOG_ERR, gettext("cannot allocate netbuf")); krb5_klog_close(context); exit(1); } tbindstr.qlen = 8; tbindstr.addr.buf = (char *) sin; tbindstr.addr.len = tbindstr.addr.maxlen = __rpc_get_a_size(tinfo.addr); sin = (struct sockaddr_in *) tbindstr.addr.buf; /* SUNWresync121 XXX (void) memset(&addr, 0, sizeof(addr)); */ if (t_bind(fd, &tbindstr, tres) < 0) { int oerrno = errno; const char *e_txt = krb5_get_error_message (context, errno); fprintf(stderr, gettext("%s: Cannot bind socket.\n"), whoami); fprintf(stderr, gettext("bind: %s\n"), e_txt); errno = oerrno; krb5_klog_syslog(LOG_ERR, gettext("Cannot bind socket: %s"), e_txt); if(oerrno == EADDRINUSE) { char *w = strrchr(whoami, '/'); if (w) { w++; } else { w = whoami; } fprintf(stderr, gettext( "This probably means that another %s process is already\n" "running, or that another program is using the server port (number %d)\n" "after being assigned it by the RPC portmap daemon. If another\n" "%s is already running, you should kill it before\n" "restarting the server. If, on the other hand, another program is\n" "using the server port, you should kill it before running\n" "%s, and ensure that the conflict does not occur in the\n" "future by making sure that %s is started on reboot\n" "before portmap.\n"), w, ntohs(addr.sin_port), w, w, w); krb5_klog_syslog(LOG_ERR, gettext("Check for already-running %s or for " "another process using port %d"), w, htons(addr.sin_port)); } kadm5_destroy(global_server_handle); krb5_klog_close(context); exit(1); } memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = INADDR_ANY; addr.sin_port = htons(chgpw_params.kpasswd_port); if (bind(schpw, (struct sockaddr *)&addr, sizeof(addr)) < 0) { char portbuf[32]; int oerrno = errno; const char *e_txt = krb5_get_error_message (context, errno); fprintf(stderr, gettext("%s: Cannot bind socket.\n"), whoami); fprintf(stderr, gettext("bind: %s\n"), e_txt); errno = oerrno; (void) snprintf(portbuf, sizeof (portbuf), "%d", ntohs(addr.sin_port)); krb5_klog_syslog(LOG_ERR, gettext("cannot bind simple chpw socket: %s"), e_txt); if(oerrno == EADDRINUSE) { char *w = strrchr(whoami, '/'); if (w) { w++; } else { w = whoami; } fprintf(stderr, gettext( "This probably means that another %s process is already\n" "running, or that another program is using the server port (number %d).\n" "If another %s is already running, you should kill it before\n" "restarting the server.\n"), w, ntohs(addr.sin_port), w); } krb5_klog_close(context); exit(1); } transp = svc_tli_create(fd, nconf, NULL, 0, 0); (void) t_free((char *) tres, T_BIND); (void) endnetconfig(handlep); if(transp == NULL) { fprintf(stderr, gettext("%s: Cannot create RPC service.\n"), whoami); krb5_klog_syslog(LOG_ERR, gettext("Cannot create RPC service: %m")); kadm5_destroy(global_server_handle); krb5_klog_close(context); exit(1); } if(!svc_register(transp, KADM, KADMVERS, kadm_1, 0)) { fprintf(stderr, gettext("%s: Cannot register RPC service.\n"), whoami); krb5_klog_syslog(LOG_ERR, gettext("Cannot register RPC service, failing.")); kadm5_destroy(global_server_handle); krb5_klog_close(context); exit(1); } /* Solaris Kerberos: * The only service principals which matter here are * -> names[0].name (kadmin/) * -> names[1].name (changepw/) * KADM5_ADMIN_SERVICE_P, KADM5_CHANGEPW_SERVICE_P, * OVSEC_KADM_ADMIN_SERVICE_P, OVSEC_KADM_CHANGEPW_SERVICE_P * are all legacy service princs and calls to rpc_gss_set_svc_name() * using these principals will always fail as they are not host * based principals. */ if (ret = kadm5_get_adm_host_srv_name(context, params.realm, &names[0].name)) { krb5_klog_syslog(LOG_ERR, gettext("Cannot get host based service name for admin " "principal in realm %s: %s"), params.realm, error_message(ret)); fprintf(stderr, gettext("%s: Cannot get host based service name for admin " "principal in realm %s: %s\n"), whoami, params.realm, error_message(ret)); krb5_klog_close(context); exit(1); } if (ret = kadm5_get_cpw_host_srv_name(context, params.realm, &names[1].name)) { krb5_klog_syslog(LOG_ERR, gettext("Cannot get host based service name for changepw " "principal in realm %s: %s"), params.realm, error_message(ret)); fprintf(stderr, gettext("%s: Cannot get host based service name for " "changepw principal in realm %s: %s\n"), whoami, params.realm, error_message(ret)); krb5_klog_close(context); exit(1); } names[2].name = KADM5_ADMIN_SERVICE_P; names[3].name = KADM5_CHANGEPW_SERVICE_P; names[4].name = OVSEC_KADM_ADMIN_SERVICE_P; names[5].name = OVSEC_KADM_CHANGEPW_SERVICE_P; if (names[0].name == NULL || names[1].name == NULL || names[2].name == NULL || names[3].name == NULL || names[4].name == NULL || names[5].name == NULL) { krb5_klog_syslog(LOG_ERR, gettext("Cannot initialize GSS-API authentication, " "failing.")); fprintf(stderr, gettext("%s: Cannot initialize " "GSS-API authentication.\n"), whoami); krb5_klog_close(context); exit(1); } /* * Go through some contortions to point gssapi at a kdb keytab. * This prevents kadmind from needing to use an actual file-based * keytab. */ /* XXX extract kadm5's krb5_context */ hctx = ((kadm5_server_handle_t)global_server_handle)->context; /* Set ktkdb's internal krb5_context. */ ret = krb5_ktkdb_set_context(hctx); if (ret) { krb5_klog_syslog(LOG_ERR, "Can't set kdb keytab's internal context."); goto kterr; } /* Solaris Kerberos */ ret = krb5_db_set_mkey(hctx, &((kadm5_server_handle_t)global_server_handle)->master_keyblock); if (ret) { krb5_klog_syslog(LOG_ERR, "Can't set master key for kdb keytab."); goto kterr; } ret = krb5_kt_register(context, &krb5_kt_kdb_ops); if (ret) { krb5_klog_syslog(LOG_ERR, "Can't register kdb keytab."); goto kterr; } /* Tell gssapi about the kdb keytab. */ ret = krb5_gss_register_acceptor_identity("KDB:"); if (ret) { krb5_klog_syslog(LOG_ERR, "Can't register acceptor keytab."); goto kterr; } kterr: if (ret) { krb5_klog_syslog(LOG_ERR, "%s", krb5_get_error_message (context, ret)); fprintf(stderr, "%s: Can't set up keytab for RPC.\n", whoami); kadm5_destroy(global_server_handle); krb5_klog_close(context); exit(1); } /* * Try to acquire creds for the old OV services as well as the * new names, but if that fails just fall back on the new names. */ if (rpc_gss_set_svc_name(names[5].name, "kerberos_v5", 0, KADM, KADMVERS) && rpc_gss_set_svc_name(names[4].name, "kerberos_v5", 0, KADM, KADMVERS)) oldnames++; if (rpc_gss_set_svc_name(names[3].name, "kerberos_v5", 0, KADM, KADMVERS)) oldnames++; if (rpc_gss_set_svc_name(names[2].name, "kerberos_v5", 0, KADM, KADMVERS)) oldnames++; /* If rpc_gss_set_svc_name() fails for either kadmin/ or * for changepw/ then try to determine if this is caused * by a missing keytab file or entry. If so, log it and continue. */ if (rpc_gss_set_svc_name(names[0].name, "kerberos_v5", 0, KADM, KADMVERS)) oldnames++; if (rpc_gss_set_svc_name(names[1].name, "kerberos_v5", 0, KADM, KADMVERS)) oldnames++; retdn = getdomnames(context, params.realm, &dnames); if (retdn == 0 && dnames) { /* * Multi-homed KDCs sometimes may need to set svc names * for multiple net interfaces so we set them for * all interfaces just in case. */ set_svc_domnames(KADM5_ADMIN_HOST_SERVICE, dnames, KADM, KADMVERS); set_svc_domnames(KADM5_CHANGEPW_HOST_SERVICE, dnames, KADM, KADMVERS); } /* if set_names succeeded, this will too */ in_buf.value = names[1].name; in_buf.length = strlen(names[1].name) + 1; (void) gss_import_name(&OMret, &in_buf, (gss_OID) nt_krb5_name_oid, &gss_changepw_name); if (oldnames) { in_buf.value = names[3].name; in_buf.length = strlen(names[3].name) + 1; (void) gss_import_name(&OMret, &in_buf, (gss_OID) nt_krb5_name_oid, &gss_oldchangepw_name); } if ((ret = kadm5int_acl_init(context, 0, params.acl_file))) { errmsg = krb5_get_error_message (context, ret); krb5_klog_syslog(LOG_ERR, gettext("Cannot initialize acl file: %s"), errmsg); fprintf(stderr, gettext("%s: Cannot initialize acl file: %s\n"), whoami, errmsg); kadm5_destroy(global_server_handle); krb5_klog_close(context); exit(1); } /* * Solaris Kerberos: * Warn if the acl file contains an entry for a principal matching the * default (unconfigured) acl rule. */ gssbuf.length = strlen("x/admin@___default_realm___"); gssbuf.value = "x/admin@___default_realm___"; /* Use any value as the first component - 'x' in this case */ if (gss_import_name(&minor_status, &gssbuf, GSS_C_NT_USER_NAME, &name) == GSS_S_COMPLETE) { if (kadm5int_acl_check(context, name, ACL_MODIFY, NULL, NULL)) { krb5_klog_syslog(LOG_WARNING, gettext("acls may not be properly configured: " "found an acl matching \"___default_realm___\" in " " %s"), params.acl_file); (void) fprintf(stderr, gettext("%s: Warning: " "acls may not be properly configured: found an acl " "matching \"___default_realm___\" in %s\n"), whoami, params.acl_file); } (void) gss_release_name(&minor_status, &name); } gssbuf.value = NULL; gssbuf.length = 0; /* * Solaris Kerberos: * List the logs (FILE, STDERR, etc) which are currently being * logged to and print to stderr. Useful when trying to * track down a failure via SMF. */ if (ret = krb5_klog_list_logs(whoami)) { fprintf(stderr, gettext("%s: %s while listing logs\n"), whoami, error_message(ret)); krb5_klog_syslog(LOG_ERR, gettext("%s while listing logs"), error_message(ret)); } if (!nofork && (ret = daemon(0, 0))) { ret = errno; errmsg = krb5_get_error_message (context, ret); krb5_klog_syslog(LOG_ERR, gettext("Cannot detach from tty: %s"), errmsg); fprintf(stderr, gettext("%s: Cannot detach from tty: %s\n"), whoami, errmsg); kadm5_destroy(global_server_handle); krb5_klog_close(context); exit(1); } /* SUNW14resync */ #if 0 krb5_klog_syslog(LOG_INFO, "Seeding random number generator"); ret = krb5_c_random_os_entropy(context, 1, NULL); if (ret) { krb5_klog_syslog(LOG_ERR, "Error getting random seed: %s, aborting", krb5_get_error_message(context, ret)); kadm5_destroy(global_server_handle); krb5_klog_close(context); exit(1); } #endif handle = global_server_handle; ctx = handle->context; if (params.iprop_enabled == TRUE) { if (ret = krb5_db_supports_iprop(ctx, &iprop_supported)) { fprintf(stderr, gettext("%s: %s while trying to determine if KDB " "plugin supports iprop\n"), whoami, error_message(ret)); krb5_klog_syslog(LOG_ERR, gettext("%s while trying to determine if KDB " "plugin supports iprop"), error_message(ret)); krb5_klog_close(ctx); exit(1); } if (!iprop_supported) { fprintf(stderr, gettext("%s: Warning, current KDB " "plugin does not support iprop, continuing " "with iprop disabled\n"), whoami); krb5_klog_syslog(LOG_WARNING, gettext("Warning, current KDB " "plugin does not support iprop, continuing " "with iprop disabled")); ulog_set_role(ctx, IPROP_NULL); } else ulog_set_role(ctx, IPROP_MASTER); } else ulog_set_role(ctx, IPROP_NULL); log_ctx = ctx->kdblog_context; if (log_ctx && (log_ctx->iproprole == IPROP_MASTER)) { /* * IProp is enabled, so let's map in the update log * and setup the service. */ if (ret = ulog_map(ctx, ¶ms, FKADMIND)) { fprintf(stderr, gettext("%s: %s while mapping update log " "(`%s.ulog')\n"), whoami, error_message(ret), params.dbname); krb5_klog_syslog(LOG_ERR, gettext("%s while mapping update log " "(`%s.ulog')"), error_message(ret), params.dbname); krb5_klog_close(ctx); exit(1); } if (nofork) fprintf(stderr, "%s: create IPROP svc (PROG=%d, VERS=%d)\n", whoami, KRB5_IPROP_PROG, KRB5_IPROP_VERS); if (!svc_create(krb5_iprop_prog_1, KRB5_IPROP_PROG, KRB5_IPROP_VERS, "circuit_v")) { fprintf(stderr, gettext("%s: Cannot create IProp RPC service (PROG=%d, VERS=%d)\n"), whoami, KRB5_IPROP_PROG, KRB5_IPROP_VERS); krb5_klog_syslog(LOG_ERR, gettext("Cannot create IProp RPC service (PROG=%d, VERS=%d), failing."), KRB5_IPROP_PROG, KRB5_IPROP_VERS); krb5_klog_close(ctx); exit(1); } if (ret = kiprop_get_adm_host_srv_name(ctx, params.realm, &kiprop_name)) { krb5_klog_syslog(LOG_ERR, gettext("%s while getting IProp svc name, failing"), error_message(ret)); fprintf(stderr, gettext("%s: %s while getting IProp svc name, failing\n"), whoami, error_message(ret)); krb5_klog_close(ctx); exit(1); } if (!rpc_gss_set_svc_name(kiprop_name, "kerberos_v5", 0, KRB5_IPROP_PROG, KRB5_IPROP_VERS)) { rpc_gss_error_t err; (void) rpc_gss_get_error(&err); krb5_klog_syslog(LOG_ERR, gettext("Unable to set RPCSEC_GSS service name (`%s'), failing."), kiprop_name ? kiprop_name : ""); fprintf(stderr, gettext("%s: Unable to set RPCSEC_GSS service name (`%s'), failing.\n"), whoami, kiprop_name ? kiprop_name : ""); if (nofork) { fprintf(stderr, "%s: set svc name (rpcsec err=%d, sys err=%d)\n", whoami, err.rpc_gss_error, err.system_error); } exit(1); } free(kiprop_name); if (retdn == 0 && dnames) { set_svc_domnames(KADM5_KIPROP_HOST_SERVICE, dnames, KRB5_IPROP_PROG, KRB5_IPROP_VERS); } } else { if (!oldnames) { /* rpc_gss_set_svc_name failed for both kadmin/ and * changepw/. */ krb5_klog_syslog(LOG_ERR, gettext("Unable to set RPCSEC_GSS service names " "('%s, %s')"), names[0].name, names[1].name); fprintf(stderr, gettext("%s: Unable to set RPCSEC_GSS service names " "('%s, %s')\n"), whoami, names[0].name, names[1].name); krb5_klog_close(context); exit(1); } } if (dnames) freedomnames(dnames); setup_signal_handlers(log_ctx->iproprole); krb5_klog_syslog(LOG_INFO, gettext("starting")); if (nofork) fprintf(stderr, "%s: starting...\n", whoami); /* * We now call our own customized async event processing * function kadm_svc_run(), as opposed to svc_run() earlier, * since this enables kadmind to also listen-to/process * non-RPCSEC_GSS based change-pwd requests apart from the * regular, RPCSEC_GSS kpasswd requests from Solaris Krb5 clients. */ kadm_svc_run(); krb5_klog_syslog(LOG_INFO, gettext("finished, exiting")); kadm5_destroy(global_server_handle); t_close(fd); krb5_klog_close(context); exit(0); } /* * Function: kadm_svc_run * * Purpose: modified version of sunrpc svc_run. * which closes the database every TIMEOUT seconds. * * Arguments: * Requires: * Effects: * Modifies: */ void kadm_svc_run(void) { struct pollfd *rfd = 0; struct timeval timeout; int pollret; int nfds = 0; int i; while(signal_request_exit == 0) { timeout.tv_sec = TIMEOUT; timeout.tv_usec = 0; if (nfds != svc_max_pollfd) { rfd = realloc(rfd, sizeof (pollfd_t) * svc_max_pollfd); nfds = svc_max_pollfd; } (void) memcpy(rfd, svc_pollfd, sizeof (pollfd_t) * svc_max_pollfd); for (i = 0; i < nfds; i++) { if (rfd[i].fd == -1) { rfd[i].fd = schpw; rfd[i].events = POLLIN; break; } } switch(pollret = poll(rfd, nfds, __rpc_timeval_to_msec(&timeout))) { case -1: if(errno == EINTR) continue; perror("poll"); return; case 0: continue; default: for (i = 0; i < nfds; i++) { if (rfd[i].revents & POLLIN) { if (rfd[i].fd == schpw) handle_chpw(context, schpw, global_server_handle, &chgpw_params); else svc_getreq_poll(rfd, pollret); break; } else { if (i == (nfds - 1)) perror("poll"); } } break; } } } /* * Function: setup_signal_handlers * * Purpose: Setup signal handling functions with either * System V's signal() or POSIX_SIGNALS. */ void setup_signal_handlers(iprop_role iproprole) { #ifdef POSIX_SIGNALS (void) sigemptyset(&s_action.sa_mask); s_action.sa_handler = request_exit; (void) sigaction(SIGINT, &s_action, (struct sigaction *) NULL); (void) sigaction(SIGTERM, &s_action, (struct sigaction *) NULL); (void) sigaction(SIGQUIT, &s_action, (struct sigaction *) NULL); s_action.sa_handler = sig_pipe; (void) sigaction(SIGPIPE, &s_action, (struct sigaction *) NULL); /* * IProp will fork for a full-resync, we don't want to * wait on it and we don't want the living dead procs either. */ if (iproprole == IPROP_MASTER) { s_action.sa_handler = SIG_IGN; (void) sigaction(SIGCHLD, &s_action, (struct sigaction *) NULL); } #else signal(SIGINT, request_exit); signal(SIGTERM, request_exit); signal(SIGQUIT, request_exit); signal(SIGPIPE, sig_pipe); /* * IProp will fork for a full-resync, we don't want to * wait on it and we don't want the living dead procs either. */ if (iproprole == IPROP_MASTER) (void) signal(SIGCHLD, SIG_IGN); #endif /* POSIX_SIGNALS */ return; } /* * Function: request_exit * * Purpose: sets flags saying the server got a signal and that it * should exit when convient. * * Arguments: * Requires: * Effects: * modifies signal_request_exit which ideally makes the server exit * at some point. * * Modifies: * signal_request_exit */ void request_exit(int signum) { krb5_klog_syslog(LOG_NOTICE, gettext("Got signal to request exit")); signal_request_exit = 1; return; } /* * Function: sig_pipe * * Purpose: SIGPIPE handler * * Effects: krb5_klog_syslogs a message that a SIGPIPE occurred and returns, * thus causing the read() or write() to fail and, presumable, the RPC * to recover. Otherwise, the process aborts. */ void sig_pipe(int unused) { #ifndef POSIX_SIGNALS signal(SIGPIPE, sig_pipe); #endif /* POSIX_SIGNALS */ krb5_klog_syslog(LOG_NOTICE, gettext("Warning: Received a SIGPIPE; " "probably a client aborted. Continuing.")); return; }