/* * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * 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. */ #include #include #include "autoconf.h" #ifdef HAVE_MEMORY_H #include #endif #include #include #include #include #include #include /* for KRB5_ADM_DEFAULT_PORT */ #include #ifdef __STDC__ #include #endif #include #include #include #include "client_internal.h" #include #include #include #include #include #include #include "iprop.h" #define ADM_CCACHE "/tmp/ovsec_adm.XXXXXX" static int old_auth_gssapi = 0; /* connection timeout to kadmind in seconds */ #define KADMIND_CONNECT_TIMEOUT 25 int _kadm5_check_handle(); enum init_type { INIT_PASS, INIT_SKEY, INIT_CREDS }; static kadm5_ret_t _kadm5_init_any(char *client_name, enum init_type init_type, char *pass, krb5_ccache ccache_in, char *service_name, kadm5_config_params *params, krb5_ui_4 struct_version, krb5_ui_4 api_version, char **db_args, void **server_handle); kadm5_ret_t kadm5_init_with_creds(char *client_name, krb5_ccache ccache, char *service_name, kadm5_config_params *params, krb5_ui_4 struct_version, krb5_ui_4 api_version, char **db_args, void **server_handle) { return _kadm5_init_any(client_name, INIT_CREDS, NULL, ccache, service_name, params, struct_version, api_version, db_args, server_handle); } kadm5_ret_t kadm5_init_with_password(char *client_name, char *pass, char *service_name, kadm5_config_params *params, krb5_ui_4 struct_version, krb5_ui_4 api_version, char **db_args, void **server_handle) { return _kadm5_init_any(client_name, INIT_PASS, pass, NULL, service_name, params, struct_version, api_version, db_args, server_handle); } kadm5_ret_t kadm5_init(char *client_name, char *pass, char *service_name, kadm5_config_params *params, krb5_ui_4 struct_version, krb5_ui_4 api_version, char **db_args, void **server_handle) { return _kadm5_init_any(client_name, INIT_PASS, pass, NULL, service_name, params, struct_version, api_version, db_args, server_handle); } kadm5_ret_t kadm5_init_with_skey(char *client_name, char *keytab, char *service_name, kadm5_config_params *params, krb5_ui_4 struct_version, krb5_ui_4 api_version, char **db_args, void **server_handle) { return _kadm5_init_any(client_name, INIT_SKEY, keytab, NULL, service_name, params, struct_version, api_version, db_args, server_handle); } krb5_error_code kadm5_free_config_params(); static void display_status_1(m, code, type, mech) char *m; OM_uint32 code; int type; const gss_OID mech; { OM_uint32 maj_stat, min_stat; gss_buffer_desc msg = GSS_C_EMPTY_BUFFER; OM_uint32 msg_ctx; msg_ctx = 0; ADMIN_LOG(LOG_ERR, "%s\n", m); /* LINTED */ while (1) { maj_stat = gss_display_status(&min_stat, code, type, mech, &msg_ctx, &msg); if (maj_stat != GSS_S_COMPLETE) { syslog(LOG_ERR, dgettext(TEXT_DOMAIN, "error in gss_display_status" " called from <%s>\n"), m); break; } else syslog(LOG_ERR, dgettext(TEXT_DOMAIN, "GSS-API error : %s\n"), m); syslog(LOG_ERR, dgettext(TEXT_DOMAIN, "GSS-API error : %s\n"), (char *)msg.value); if (msg.length != 0) (void) gss_release_buffer(&min_stat, &msg); if (!msg_ctx) break; } } /* * 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 * mech kerberos mech * 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. */ void display_status(msg, maj_stat, min_stat, mech) char *msg; OM_uint32 maj_stat; OM_uint32 min_stat; char *mech; { gss_OID mech_oid; if (!rpc_gss_mech_to_oid(mech, (rpc_gss_OID *)&mech_oid)) { ADMIN_LOG(LOG_ERR, dgettext(TEXT_DOMAIN, "Invalid mechanism oid <%s>"), mech); return; } display_status_1(msg, maj_stat, GSS_C_GSS_CODE, mech_oid); display_status_1(msg, min_stat, GSS_C_MECH_CODE, mech_oid); } /* * Open an fd for the given address and connect asynchronously. Wait * KADMIND_CONNECT_TIMEOUT seconds or till it succeeds. If it succeeds * change fd to blocking and return it, else return -1. */ static int get_connection(struct netconfig *nconf, struct netbuf netaddr) { struct t_info tinfo; struct t_call sndcall; struct t_call *rcvcall = NULL; int connect_time; int flags; int fd; (void) memset(&tinfo, 0, sizeof (tinfo)); /* we'l open with O_NONBLOCK and avoid an fcntl */ fd = t_open(nconf->nc_device, O_RDWR | O_NONBLOCK, &tinfo); if (fd == -1) { return (-1); } if (t_bind(fd, (struct t_bind *)NULL, (struct t_bind *)NULL) == -1) { (void) close(fd); return (-1); } /* we can't connect unless fd is in IDLE state */ if (t_getstate(fd) != T_IDLE) { (void) close(fd); return (-1); } /* setup connect parameters */ netaddr.len = netaddr.maxlen = __rpc_get_a_size(tinfo.addr); sndcall.addr = netaddr; sndcall.opt.len = sndcall.udata.len = 0; /* we wait for KADMIND_CONNECT_TIMEOUT seconds from now */ connect_time = time(NULL) + KADMIND_CONNECT_TIMEOUT; if (t_connect(fd, &sndcall, rcvcall) != 0) { if (t_errno != TNODATA) { (void) close(fd); return (-1); } } /* loop till success or timeout */ for (;;) { if (t_rcvconnect(fd, rcvcall) == 0) break; if (t_errno != TNODATA || time(NULL) > connect_time) { /* we have either timed out or caught an error */ (void) close(fd); if (rcvcall != NULL) t_free((char *)rcvcall, T_CALL); return (-1); } sleep(1); } /* make the fd blocking (synchronous) */ flags = fcntl(fd, F_GETFL, 0); (void) fcntl(fd, F_SETFL, flags & ~O_NONBLOCK); if (rcvcall != NULL) t_free((char *)rcvcall, T_CALL); return (fd); } /* * Open an RPCSEC_GSS connection and * get a client handle to use for future RPCSEC calls. * * This function is only used when changing passwords and * the kpasswd_protocol is RPCSEC_GSS */ static int _kadm5_initialize_rpcsec_gss_handle(kadm5_server_handle_t handle, char *client_name, char *service_name) { struct netbuf netaddr; struct hostent *hp; int fd; struct sockaddr_in addr; struct sockaddr_in *sin; struct netconfig *nconf; int code = 0; generic_ret *r; char *ccname_orig; char *iprop_svc; boolean_t iprop_enable = B_FALSE; char mech[] = "kerberos_v5"; gss_OID mech_oid; gss_OID_set_desc oid_set; gss_name_t gss_client; gss_buffer_desc input_name; gss_cred_id_t gss_client_creds = GSS_C_NO_CREDENTIAL; rpc_gss_options_req_t options_req; rpc_gss_options_ret_t options_ret; rpc_gss_service_t service = rpc_gss_svc_privacy; OM_uint32 gssstat, minor_stat; void *handlep; enum clnt_stat rpc_err_code; char *server = handle->params.admin_server; /* * Try to find the kpasswd_server first if this is for the changepw * service. If defined then it should be resolvable else return error. */ if (strncmp(service_name, KADM5_CHANGEPW_HOST_SERVICE, strlen(KADM5_CHANGEPW_HOST_SERVICE)) == 0) { if (handle->params.kpasswd_server != NULL) server = handle->params.kpasswd_server; } hp = gethostbyname(server); if (hp == (struct hostent *)NULL) { code = KADM5_BAD_SERVER_NAME; ADMIN_LOGO(LOG_ERR, dgettext(TEXT_DOMAIN, "bad server name\n")); goto cleanup; } memset(&addr, 0, sizeof (addr)); addr.sin_family = hp->h_addrtype; (void) memcpy((char *)&addr.sin_addr, (char *)hp->h_addr, sizeof (addr.sin_addr)); addr.sin_port = htons((ushort_t)handle->params.kadmind_port); sin = &addr; #ifdef DEBUG printf("kadmin_port %d\n", handle->params.kadmind_port); printf("addr: sin_port: %d, sin_family: %d, sin_zero %s\n", addr.sin_port, addr.sin_family, addr.sin_zero); printf("sin_addr %d:%d\n", addr.sin_addr.S_un.S_un_w.s_w1, addr.sin_addr.S_un.S_un_w.s_w2); #endif if ((handlep = setnetconfig()) == (void *) NULL) { (void) syslog(LOG_ERR, dgettext(TEXT_DOMAIN, "cannot get any transport information")); goto error; } 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) goto error; /* Transform addr to netbuf */ (void) memset(&netaddr, 0, sizeof (netaddr)); netaddr.buf = (char *)sin; /* get an fd connected to the given address */ fd = get_connection(nconf, netaddr); if (fd == -1) { syslog(LOG_ERR, dgettext(TEXT_DOMAIN, "unable to open connection to ADMIN server " "(t_error %i)"), t_errno); code = KADM5_RPC_ERROR; goto error; } #ifdef DEBUG printf("fd: %d, KADM: %d, KADMVERS %d\n", fd, KADM, KADMVERS); printf("nconf: nc_netid: %s, nc_semantics: %d, nc_flag: %d, " "nc_protofmly: %s\n", nconf->nc_netid, nconf->nc_semantics, nconf->nc_flag, nconf->nc_protofmly); printf("nc_proto: %s, nc_device: %s, nc_nlookups: %d, nc_used: %d\n", nconf->nc_proto, nconf->nc_device, nconf->nc_nlookups, nconf->nc_unused); printf("netaddr: maxlen %d, buf: %s, len: %d\n", netaddr.maxlen, netaddr.buf, netaddr.len); #endif /* * Tell clnt_tli_create that given fd is already connected * * If the service_name and client_name are iprop-centric, * we need to clnt_tli_create to the appropriate RPC prog */ iprop_svc = strdup(KIPROP_SVC_NAME); if (iprop_svc == NULL) return (ENOMEM); if ((strstr(service_name, iprop_svc) != NULL) && (strstr(client_name, iprop_svc) != NULL)) { iprop_enable = B_TRUE; handle->clnt = clnt_tli_create(fd, nconf, NULL, KRB5_IPROP_PROG, KRB5_IPROP_VERS, 0, 0); } else handle->clnt = clnt_tli_create(fd, nconf, NULL, KADM, KADMVERS, 0, 0); if (iprop_svc) free(iprop_svc); if (handle->clnt == NULL) { syslog(LOG_ERR, dgettext(TEXT_DOMAIN, "clnt_tli_create failed\n")); code = KADM5_RPC_ERROR; (void) close(fd); goto error; } /* * The rpc-handle was created on an fd opened and connected * by us, so we have to explicitly tell rpc to close it. */ if (clnt_control(handle->clnt, CLSET_FD_CLOSE, NULL) != TRUE) { clnt_pcreateerror("ERROR:"); syslog(LOG_ERR, dgettext(TEXT_DOMAIN, "clnt_control failed to set CLSET_FD_CLOSE")); code = KADM5_RPC_ERROR; (void) close(fd); goto error; } handle->lhandle->clnt = handle->clnt; /* now that handle->clnt is set, we can check the handle */ if (code = _kadm5_check_handle((void *) handle)) goto error; /* * The RPC connection is open; establish the GSS-API * authentication context. */ ADMIN_LOGO(LOG_ERR, dgettext(TEXT_DOMAIN, "have an rpc connection open\n")); /* use the kadm5 cache */ ccname_orig = getenv("KRB5CCNAME"); if (ccname_orig) ccname_orig = strdup(ccname_orig); (void) krb5_setenv("KRB5CCNAME", handle->cache_name, 1); ADMIN_LOG(LOG_ERR, dgettext(TEXT_DOMAIN, "current credential cache: %s"), handle->cache_name); input_name.value = client_name; input_name.length = strlen((char *)input_name.value) + 1; gssstat = gss_import_name(&minor_stat, &input_name, (gss_OID)gss_nt_krb5_name, &gss_client); if (gssstat != GSS_S_COMPLETE) { code = KADM5_GSS_ERROR; ADMIN_LOGO(LOG_ERR, dgettext(TEXT_DOMAIN, "gss_import_name failed for client name\n")); goto error; } if (!rpc_gss_mech_to_oid(mech, (rpc_gss_OID *)&mech_oid)) { ADMIN_LOG(LOG_ERR, dgettext(TEXT_DOMAIN, "Invalid mechanism oid <%s>"), mech); goto error; } oid_set.count = 1; oid_set.elements = mech_oid; gssstat = gss_acquire_cred(&minor_stat, gss_client, 0, &oid_set, GSS_C_INITIATE, &gss_client_creds, NULL, NULL); (void) gss_release_name(&minor_stat, &gss_client); if (gssstat != GSS_S_COMPLETE) { code = KADM5_GSS_ERROR; ADMIN_LOG(LOG_ERR, dgettext(TEXT_DOMAIN, "could not acquire credentials, " "major error code: %d\n"), gssstat); goto error; } handle->my_cred = gss_client_creds; options_req.my_cred = gss_client_creds; options_req.req_flags = GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG; options_req.time_req = 0; options_req.input_channel_bindings = NULL; #ifndef INIT_TEST handle->clnt->cl_auth = rpc_gss_seccreate(handle->clnt, service_name, mech, service, NULL, &options_req, &options_ret); #endif /* ! INIT_TEST */ if (ccname_orig) { (void) krb5_setenv("KRB5CCNAME", ccname_orig, 1); free(ccname_orig); } else (void) krb5_unsetenv("KRB5CCNAME"); if (handle->clnt->cl_auth == NULL) { code = KADM5_GSS_ERROR; display_status(dgettext(TEXT_DOMAIN, "rpc_gss_seccreate failed\n"), options_ret.major_status, options_ret.minor_status, mech); goto error; } /* * Bypass the remainder of the code and return straightaway * if the gss service requested is kiprop */ if (iprop_enable == B_TRUE) { code = 0; goto cleanup; } r = init_2(&handle->api_version, handle->clnt); /* Solaris Kerberos: 163 resync */ if (r == NULL) { ADMIN_LOGO(LOG_ERR, dgettext(TEXT_DOMAIN, "error during admin api initialization\n")); code = KADM5_RPC_ERROR; goto error; } if (r->code) { code = r->code; ADMIN_LOG(LOG_ERR, dgettext(TEXT_DOMAIN, "error during admin api initialization: %d\n"), r->code); goto error; } error: cleanup: if (handlep != (void *) NULL) (void) endnetconfig(handlep); /* * gss_client_creds is freed only when there is an error condition, * given that rpc_gss_seccreate() will assign the cred pointer to the * my_cred member in the auth handle's private data structure. */ if (code && (gss_client_creds != GSS_C_NO_CREDENTIAL)) (void) gss_release_cred(&minor_stat, &gss_client_creds); return (code); } static kadm5_ret_t _kadm5_init_any(char *client_name, enum init_type init_type, char *pass, krb5_ccache ccache_in, char *service_name, kadm5_config_params *params_in, krb5_ui_4 struct_version, krb5_ui_4 api_version, char **db_args, void **server_handle) { int i; krb5_creds creds; krb5_ccache ccache = NULL; krb5_timestamp now; OM_uint32 gssstat, minor_stat; kadm5_server_handle_t handle; kadm5_config_params params_local; int code = 0; krb5_get_init_creds_opt opt; gss_buffer_desc input_name; krb5_error_code kret; krb5_int32 starttime; char *server = NULL; krb5_principal serverp = NULL, clientp = NULL; krb5_principal saved_server = NULL; bool_t cpw = FALSE; ADMIN_LOGO(LOG_ERR, dgettext(TEXT_DOMAIN, "entering kadm5_init_any\n")); if (! server_handle) { return EINVAL; } if (! (handle = malloc(sizeof(*handle)))) { return ENOMEM; } if (! (handle->lhandle = malloc(sizeof(*handle)))) { free(handle); return ENOMEM; } handle->magic_number = KADM5_SERVER_HANDLE_MAGIC; handle->struct_version = struct_version; handle->api_version = api_version; handle->clnt = 0; handle->cache_name = 0; handle->destroy_cache = 0; *handle->lhandle = *handle; handle->lhandle->api_version = KADM5_API_VERSION_2; handle->lhandle->struct_version = KADM5_STRUCT_VERSION; handle->lhandle->lhandle = handle->lhandle; kret = krb5_init_context(&handle->context); if (kret) { free(handle->lhandle); free(handle); return (kret); } if(service_name == NULL || client_name == NULL) { krb5_free_context(handle->context); free(handle->lhandle); free(handle); return EINVAL; } memset((char *) &creds, 0, sizeof(creds)); /* * Verify the version numbers before proceeding; we can't use * CHECK_HANDLE because not all fields are set yet. */ GENERIC_CHECK_HANDLE(handle, KADM5_OLD_LIB_API_VERSION, KADM5_NEW_LIB_API_VERSION); /* * Acquire relevant profile entries. In version 2, merge values * in params_in with values from profile, based on * params_in->mask. * * In version 1, we've given a realm (which may be NULL) instead * of params_in. So use that realm, make params_in contain an * empty mask, and behave like version 2. */ memset((char *) ¶ms_local, 0, sizeof(params_local)); if (api_version == KADM5_API_VERSION_1) { if (params_in) params_local.mask = KADM5_CONFIG_REALM; params_in = ¶ms_local; } #define ILLEGAL_PARAMS ( \ KADM5_CONFIG_ACL_FILE | KADM5_CONFIG_ADB_LOCKFILE | \ KADM5_CONFIG_DBNAME | KADM5_CONFIG_ADBNAME | \ KADM5_CONFIG_DICT_FILE | KADM5_CONFIG_ADMIN_KEYTAB | \ KADM5_CONFIG_STASH_FILE | KADM5_CONFIG_MKEY_NAME | \ KADM5_CONFIG_ENCTYPE | KADM5_CONFIG_MAX_LIFE | \ KADM5_CONFIG_MAX_RLIFE | KADM5_CONFIG_EXPIRATION | \ KADM5_CONFIG_FLAGS | KADM5_CONFIG_ENCTYPES | \ KADM5_CONFIG_MKEY_FROM_KBD) if (params_in && params_in->mask & ILLEGAL_PARAMS) { krb5_free_context(handle->context); free(handle->lhandle); free(handle); ADMIN_LOG(LOG_ERR, dgettext(TEXT_DOMAIN, "bad client parameters, returning %d"), KADM5_BAD_CLIENT_PARAMS); return KADM5_BAD_CLIENT_PARAMS; } if ((code = kadm5_get_config_params(handle->context, 0, params_in, &handle->params))) { krb5_free_context(handle->context); free(handle->lhandle); free(handle); ADMIN_LOG(LOG_ERR, dgettext(TEXT_DOMAIN, "failed to get config_params, return: %d\n"), code); return(code); } #define REQUIRED_PARAMS (KADM5_CONFIG_REALM | \ KADM5_CONFIG_ADMIN_SERVER | \ KADM5_CONFIG_KADMIND_PORT) #define KPW_REQUIRED_PARAMS (KADM5_CONFIG_REALM | \ KADM5_CONFIG_KPASSWD_SERVER | \ KADM5_CONFIG_KPASSWD_PORT) if (((handle->params.mask & REQUIRED_PARAMS) != REQUIRED_PARAMS) && ((handle->params.mask & KPW_REQUIRED_PARAMS) != KPW_REQUIRED_PARAMS)) { (void) kadm5_free_config_params(handle->context, &handle->params); krb5_free_context(handle->context); free(handle->lhandle); free(handle); ADMIN_LOGO(LOG_ERR, dgettext(TEXT_DOMAIN, "missing config parameters\n")); return KADM5_MISSING_KRB5_CONF_PARAMS; } /* * Acquire a service ticket for service_name@realm in the name of * client_name, using password pass (which could be NULL), and * create a ccache to store them in. If INIT_CREDS, use the * ccache we were provided instead. */ if ((code = krb5_parse_name(handle->context, client_name, &creds.client))) { ADMIN_LOGO(LOG_ERR, dgettext(TEXT_DOMAIN, "could not parse client name\n")); goto error; } clientp = creds.client; if (strncmp(service_name, KADM5_CHANGEPW_HOST_SERVICE, strlen(KADM5_CHANGEPW_HOST_SERVICE)) == 0) cpw = TRUE; if (init_type == INIT_PASS && handle->params.kpasswd_protocol == KRB5_CHGPWD_CHANGEPW_V2 && cpw == TRUE) { /* * The 'service_name' is constructed by the caller * but its done before the parameter which determines * the kpasswd_protocol is found. The servers that * support the SET/CHANGE password protocol expect * a slightly different service principal than * the normal SEAM kadmind so construct the correct * name here and then forget it. */ char *newsvcname = NULL; newsvcname = malloc(strlen(KADM5_CHANGEPW_SERVICE) + strlen(handle->params.realm) + 2); if (newsvcname == NULL) { ADMIN_LOGO(LOG_ERR, dgettext(TEXT_DOMAIN, "could not malloc\n")); code = ENOMEM; goto error; } sprintf(newsvcname, "%s@%s", KADM5_CHANGEPW_SERVICE, handle->params.realm); if ((code = krb5_parse_name(handle->context, newsvcname, &creds.server))) { ADMIN_LOGO(LOG_ERR, dgettext(TEXT_DOMAIN, "could not parse server " "name\n")); free(newsvcname); goto error; } free(newsvcname); } else { input_name.value = service_name; input_name.length = strlen((char *)input_name.value) + 1; gssstat = krb5_gss_import_name(&minor_stat, &input_name, (gss_OID)GSS_C_NT_HOSTBASED_SERVICE, (gss_name_t *)&creds.server); if (gssstat != GSS_S_COMPLETE) { code = KADM5_GSS_ERROR; ADMIN_LOGO(LOG_ERR, dgettext(TEXT_DOMAIN, "gss_import_name failed for client name\n")); goto error; } } serverp = creds.server; /* XXX temporarily fix a bug in krb5_cc_get_type */ #undef krb5_cc_get_type #define krb5_cc_get_type(context, cache) ((cache)->ops->prefix) if (init_type == INIT_CREDS) { ccache = ccache_in; handle->cache_name = (char *) malloc(strlen(krb5_cc_get_type(handle->context, ccache)) + strlen(krb5_cc_get_name(handle->context, ccache)) + 2); if (handle->cache_name == NULL) { code = ENOMEM; goto error; } sprintf(handle->cache_name, "%s:%s", krb5_cc_get_type(handle->context, ccache), krb5_cc_get_name(handle->context, ccache)); } else { #if 0 handle->cache_name = (char *) malloc(strlen(ADM_CCACHE)+strlen("FILE:")+1); if (handle->cache_name == NULL) { code = ENOMEM; goto error; } sprintf(handle->cache_name, "FILE:%s", ADM_CCACHE); mktemp(handle->cache_name + strlen("FILE:")); #endif { static int counter = 0; handle->cache_name = malloc(sizeof("MEMORY:kadm5_") + 3*sizeof(counter)); sprintf(handle->cache_name, "MEMORY:kadm5_%u", counter++); } if ((code = krb5_cc_resolve(handle->context, handle->cache_name, &ccache))) goto error; if ((code = krb5_cc_initialize (handle->context, ccache, creds.client))) goto error; handle->destroy_cache = 1; } handle->lhandle->cache_name = handle->cache_name; ADMIN_LOG(LOG_ERR, dgettext(TEXT_DOMAIN, "cache created: %s\n"), handle->cache_name); if ((code = krb5_timeofday(handle->context, &now))) goto error; /* * Get a ticket, use the method specified in init_type. */ creds.times.starttime = 0; /* start timer at KDC */ creds.times.endtime = 0; /* endtime will be limited by service */ memset(&opt, 0, sizeof (opt)); krb5_get_init_creds_opt_init(&opt); if (creds.times.endtime) { if (creds.times.starttime) starttime = creds.times.starttime; else starttime = now; krb5_get_init_creds_opt_set_tkt_life(&opt, creds.times.endtime - starttime); } code = krb5_unparse_name(handle->context, creds.server, &server); if (code) goto error; /* * Solaris Kerberos: * Save the original creds.server as krb5_get_init_creds*() always * sets the realm of the server to the client realm. */ code = krb5_copy_principal(handle->context, creds.server, &saved_server); if (code) goto error; if (init_type == INIT_PASS) { code = krb5_get_init_creds_password(handle->context, &creds, creds.client, pass, NULL, NULL, creds.times.starttime, server, &opt); } else if (init_type == INIT_SKEY) { krb5_keytab kt = NULL; if (!(pass && (code = krb5_kt_resolve(handle->context, pass, &kt)))) { code = krb5_get_init_creds_keytab( handle->context, &creds, creds.client, kt, creds.times.starttime, server, &opt); if (pass) krb5_kt_close(handle->context, kt); } } /* Improved error messages */ if (code == KRB5KRB_AP_ERR_BAD_INTEGRITY) code = KADM5_BAD_PASSWORD; if (code == KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN) code = KADM5_SECURE_PRINC_MISSING; if (code != 0) { ADMIN_LOGO(LOG_ERR, dgettext(TEXT_DOMAIN, "failed to obtain credentials cache\n")); krb5_free_principal(handle->context, saved_server); goto error; } /* * Solaris Kerberos: * If the server principal had an empty realm then store that in * the cred cache and not the server realm as returned by * krb5_get_init_creds_{keytab|password}(). This ensures that rpcsec_gss * will find the credential in the cred cache even if a "fallback" * method is being used to determine the realm. */ if (init_type != INIT_CREDS) { krb5_free_principal(handle->context, creds.server); } creds.server = saved_server; /* * If we got this far, save the creds in the cache. */ if (ccache) { code = krb5_cc_store_cred(handle->context, ccache, &creds); } ADMIN_LOGO(LOG_ERR, dgettext(TEXT_DOMAIN, "obtained credentials cache\n")); #ifdef ZEROPASSWD if (pass != NULL) memset(pass, 0, strlen(pass)); #endif if (init_type != INIT_PASS || handle->params.kpasswd_protocol == KRB5_CHGPWD_RPCSEC || cpw == FALSE) { code = _kadm5_initialize_rpcsec_gss_handle(handle, client_name, service_name); /* * Solaris Kerberos: * If _kadm5_initialize_rpcsec_gss_handle() fails it will have * called krb5_gss_release_cred(). If the credential cache is a * MEMORY cred cache krb5_gss_release_cred() destroys the * cred cache data. Make sure that the cred-cache is closed * to prevent a double free in the "error" code. */ if (code != 0) { if (init_type != INIT_CREDS) { krb5_cc_close(handle->context, ccache); ccache = NULL; } goto error; } } *server_handle = (void *) handle; if (init_type != INIT_CREDS) krb5_cc_close(handle->context, ccache); goto cleanup; error: /* * Note that it is illegal for this code to execute if "handle" * has not been allocated and initialized. I.e., don't use "goto * error" before the block of code at the top of the function * that allocates and initializes "handle". */ if (handle->cache_name) free(handle->cache_name); if (handle->destroy_cache && ccache) krb5_cc_destroy(handle->context, ccache); if(handle->clnt && handle->clnt->cl_auth) AUTH_DESTROY(handle->clnt->cl_auth); if(handle->clnt) clnt_destroy(handle->clnt); (void) kadm5_free_config_params(handle->context, &handle->params); cleanup: if (server) free(server); /* * cred's server and client pointers could have been overwritten * by the krb5_get_init_* functions. If the addresses are different * before and after the calls then we must free the memory that * was allocated before the call. */ if (clientp && clientp != creds.client) krb5_free_principal(handle->context, clientp); if (serverp && serverp != creds.server) krb5_free_principal(handle->context, serverp); krb5_free_cred_contents(handle->context, &creds); /* * Dont clean up the handle if the code is OK (code==0) * because it is returned to the caller in the 'server_handle' * ptr. */ if (code) { krb5_free_context(handle->context); free(handle->lhandle); free(handle); } return code; } kadm5_ret_t kadm5_destroy(void *server_handle) { krb5_ccache ccache = NULL; int code = KADM5_OK; kadm5_server_handle_t handle = (kadm5_server_handle_t) server_handle; OM_uint32 min_stat; CHECK_HANDLE(server_handle); /* SUNW14resync: * krb5_cc_resolve() will resolve a ccache with the same data that * handle->my_cred points to. If the ccache is a MEMORY ccache then * gss_release_cred() will free that data (it doesn't do this when ccache * is a FILE ccache). * if'ed out to avoid the double free. */ #if 0 if (handle->destroy_cache && handle->cache_name) { if ((code = krb5_cc_resolve(handle->context, handle->cache_name, &ccache)) == 0) code = krb5_cc_destroy (handle->context, ccache); } #endif if (handle->cache_name) free(handle->cache_name); if (handle->clnt && handle->clnt->cl_auth) { /* * Since kadm5 doesn't use the default credentials we * must clean this up manually. */ if (handle->my_cred != GSS_C_NO_CREDENTIAL) (void) gss_release_cred(&min_stat, &handle->my_cred); AUTH_DESTROY(handle->clnt->cl_auth); } if (handle->clnt) clnt_destroy(handle->clnt); if (handle->lhandle) free (handle->lhandle); kadm5_free_config_params(handle->context, &handle->params); krb5_free_context(handle->context); handle->magic_number = 0; free(handle); return code; } /* not supported on client */ kadm5_ret_t kadm5_lock(void *server_handle) { return EINVAL; } /* not supported on client */ kadm5_ret_t kadm5_unlock(void *server_handle) { return EINVAL; } kadm5_ret_t kadm5_flush(void *server_handle) { return KADM5_OK; } int _kadm5_check_handle(void *handle) { CHECK_HANDLE(handle); return 0; } krb5_error_code kadm5_init_krb5_context (krb5_context *ctx) { return krb5_init_context(ctx); } /* * Stub function for kadmin. It was created to eliminate the dependency on * libkdb's ulog functions. The srv equivalent makes the actual calls. */ krb5_error_code kadm5_init_iprop(void *handle) { return (0); }