2N/A/*
2N/A * Copyright (c) 1998, 2012, Oracle and/or its affiliates. All rights reserved.
2N/A */
2N/A
2N/A/*
2N/A * Copyright 1993 OpenVision Technologies, Inc., All Rights Reserved
2N/A */
2N/A
2N/A/*
2N/A * Copyright (C) 1998 by the FundsXpress, INC.
2N/A *
2N/A * All rights reserved.
2N/A *
2N/A * Export of this software from the United States of America may require
2N/A * a specific license from the United States Government. It is the
2N/A * responsibility of any person or organization contemplating export to
2N/A * obtain such a license before exporting.
2N/A *
2N/A * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
2N/A * distribute this software and its documentation for any purpose and
2N/A * without fee is hereby granted, provided that the above copyright
2N/A * notice appear in all copies and that both that copyright notice and
2N/A * this permission notice appear in supporting documentation, and that
2N/A * the name of FundsXpress. not be used in advertising or publicity pertaining
2N/A * to distribution of the software without specific, written prior
2N/A * permission. FundsXpress makes no representations about the suitability of
2N/A * this software for any purpose. It is provided "as is" without express
2N/A * or implied warranty.
2N/A *
2N/A * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
2N/A * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
2N/A * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
2N/A */
2N/A
2N/A#include <stdio.h>
2N/A#include <netdb.h>
2N/A#include "autoconf.h"
2N/A#ifdef HAVE_MEMORY_H
2N/A#include <memory.h>
2N/A#endif
2N/A#include <string.h>
2N/A#include <com_err.h>
2N/A#include <sys/types.h>
2N/A#include <sys/socket.h>
2N/A#include <netinet/in.h>
2N/A#include <k5-int.h> /* for KRB5_ADM_DEFAULT_PORT */
2N/A#include <krb5.h>
2N/A#ifdef __STDC__
2N/A#include <stdlib.h>
2N/A#endif
2N/A#include <libintl.h>
2N/A
2N/A#include <kadm5/admin.h>
2N/A#include <kadm5/kadm_rpc.h>
2N/A#include "client_internal.h"
2N/A
2N/A#include <syslog.h>
2N/A#include <gssapi/gssapi.h>
2N/A#include <gssapi_krb5.h>
2N/A#include <gssapiP_krb5.h>
2N/A#include <rpc/clnt.h>
2N/A
2N/A#include <iprop_hdr.h>
2N/A#include "iprop.h"
2N/A
2N/A#define ADM_CCACHE "/tmp/ovsec_adm.XXXXXX"
2N/A
2N/Astatic int old_auth_gssapi = 0;
2N/A/* connection timeout to kadmind in seconds */
2N/A#define KADMIND_CONNECT_TIMEOUT 25
2N/A
2N/Aint _kadm5_check_handle();
2N/A
2N/Aenum init_type { INIT_PASS, INIT_SKEY, INIT_CREDS, INIT_ANONYMOUS };
2N/A
2N/Astatic kadm5_ret_t _kadm5_init_any(krb5_context context,
2N/A char *client_name,
2N/A enum init_type init_type,
2N/A char *pass,
2N/A krb5_ccache ccache_in,
2N/A char **service_names,
2N/A kadm5_config_params *params,
2N/A krb5_ui_4 struct_version,
2N/A krb5_ui_4 api_version,
2N/A char **db_args,
2N/A void **server_handle);
2N/A
2N/Akadm5_ret_t kadm5_init_with_creds(krb5_context context,
2N/A char *client_name,
2N/A krb5_ccache ccache,
2N/A char **service_names,
2N/A kadm5_config_params *params,
2N/A krb5_ui_4 struct_version,
2N/A krb5_ui_4 api_version,
2N/A char **db_args,
2N/A void **server_handle)
2N/A{
2N/A return _kadm5_init_any(context, client_name, INIT_CREDS, NULL, ccache,
2N/A service_names, params,
2N/A struct_version, api_version, db_args,
2N/A server_handle);
2N/A}
2N/A
2N/A
2N/Akadm5_ret_t kadm5_init_with_password(krb5_context context, char *client_name,
2N/A char *pass, char **service_names,
2N/A kadm5_config_params *params,
2N/A krb5_ui_4 struct_version,
2N/A krb5_ui_4 api_version,
2N/A char **db_args,
2N/A void **server_handle)
2N/A{
2N/A return _kadm5_init_any(context, client_name, INIT_PASS, pass, NULL,
2N/A service_names, params, struct_version,
2N/A api_version, db_args, server_handle);
2N/A}
2N/A
2N/Akadm5_ret_t kadm5_init_anonymous(krb5_context context, char *client_name,
2N/A char **service_names,
2N/A kadm5_config_params *params,
2N/A krb5_ui_4 struct_version,
2N/A krb5_ui_4 api_version,
2N/A char **db_args,
2N/A void **server_handle)
2N/A{
2N/A return _kadm5_init_any(context, client_name, INIT_ANONYMOUS, NULL, NULL,
2N/A service_names, params, struct_version,
2N/A api_version, db_args, server_handle);
2N/A}
2N/A
2N/A/* Solaris Kerberos: don't need this */
2N/A#if 0 /* ************ Begin IFDEF'ed OUT ***************************** */
2N/Akadm5_ret_t kadm5_init(krb5_context context, char *client_name, char *pass,
2N/A char **service_names,
2N/A kadm5_config_params *params,
2N/A krb5_ui_4 struct_version,
2N/A krb5_ui_4 api_version,
2N/A char **db_args,
2N/A void **server_handle)
2N/A{
2N/A return _kadm5_init_any(context, client_name, INIT_PASS, pass, NULL,
2N/A service_names, params, struct_version,
2N/A api_version, db_args, server_handle);
2N/A}
2N/A#endif /* ************** END IFDEF'ed OUT ***************************** */
2N/A
2N/Akadm5_ret_t kadm5_init_with_skey(krb5_context context, char *client_name,
2N/A char *keytab, char **service_names,
2N/A kadm5_config_params *params,
2N/A krb5_ui_4 struct_version,
2N/A krb5_ui_4 api_version,
2N/A char **db_args,
2N/A void **server_handle)
2N/A{
2N/A return _kadm5_init_any(context, client_name, INIT_SKEY, keytab, NULL,
2N/A service_names, params, struct_version,
2N/A api_version, db_args, server_handle);
2N/A}
2N/A
2N/Akrb5_error_code kadm5_free_config_params();
2N/A
2N/Astatic void
2N/Adisplay_status_1(m, code, type, mech)
2N/Achar *m;
2N/AOM_uint32 code;
2N/Aint type;
2N/Aconst gss_OID mech;
2N/A{
2N/A OM_uint32 maj_stat, min_stat;
2N/A gss_buffer_desc msg = GSS_C_EMPTY_BUFFER;
2N/A OM_uint32 msg_ctx;
2N/A
2N/A msg_ctx = 0;
2N/A /* LINTED */
2N/A while (1) {
2N/A maj_stat = gss_display_status(&min_stat, code,
2N/A type, mech,
2N/A &msg_ctx, &msg);
2N/A if (maj_stat != GSS_S_COMPLETE) {
2N/A syslog(LOG_ERR,
2N/A dgettext(TEXT_DOMAIN,
2N/A "error in gss_display_status"
2N/A " called from <%s>\n"), m);
2N/A break;
2N/A } else
2N/A syslog(LOG_ERR, dgettext(TEXT_DOMAIN,
2N/A "GSS-API error : %s\n"),
2N/A m);
2N/A syslog(LOG_ERR, dgettext(TEXT_DOMAIN,
2N/A "GSS-API error : %s\n"),
2N/A (char *)msg.value);
2N/A if (msg.length != 0)
2N/A (void) gss_release_buffer(&min_stat, &msg);
2N/A
2N/A if (!msg_ctx)
2N/A break;
2N/A }
2N/A}
2N/A
2N/A/*
2N/A * Function: display_status
2N/A *
2N/A * Purpose: displays GSS-API messages
2N/A *
2N/A * Arguments:
2N/A *
2N/A * msg a string to be displayed with the message
2N/A * maj_stat the GSS-API major status code
2N/A * min_stat the GSS-API minor status code
2N/A * mech kerberos mech
2N/A * Effects:
2N/A *
2N/A * The GSS-API messages associated with maj_stat and min_stat are
2N/A * displayed on stderr, each preceeded by "GSS-API error <msg>: " and
2N/A * followed by a newline.
2N/A */
2N/Avoid
2N/Adisplay_status(msg, maj_stat, min_stat, mech)
2N/Achar *msg;
2N/AOM_uint32 maj_stat;
2N/AOM_uint32 min_stat;
2N/Achar *mech;
2N/A{
2N/A gss_OID mech_oid;
2N/A
2N/A if (!rpc_gss_mech_to_oid(mech, (rpc_gss_OID *)&mech_oid)) {
2N/A return;
2N/A }
2N/A
2N/A display_status_1(msg, maj_stat, GSS_C_GSS_CODE, mech_oid);
2N/A display_status_1(msg, min_stat, GSS_C_MECH_CODE, mech_oid);
2N/A}
2N/A
2N/A/*
2N/A * Open an fd for the given address and connect asynchronously. Wait
2N/A * KADMIND_CONNECT_TIMEOUT seconds or till it succeeds. If it succeeds
2N/A * change fd to blocking and return it, else return -1.
2N/A */
2N/Astatic int
2N/Aget_connection(struct netconfig *nconf, struct netbuf netaddr)
2N/A{
2N/A struct t_info tinfo;
2N/A struct t_call sndcall;
2N/A struct t_call *rcvcall = NULL;
2N/A int connect_time;
2N/A int flags;
2N/A int fd;
2N/A
2N/A (void) memset(&tinfo, 0, sizeof (tinfo));
2N/A
2N/A /* we'l open with O_NONBLOCK and avoid an fcntl */
2N/A fd = t_open(nconf->nc_device, O_RDWR | O_NONBLOCK, &tinfo);
2N/A if (fd == -1) {
2N/A return (-1);
2N/A }
2N/A
2N/A if (t_bind(fd, (struct t_bind *)NULL, (struct t_bind *)NULL) == -1) {
2N/A (void) close(fd);
2N/A return (-1);
2N/A }
2N/A
2N/A /* we can't connect unless fd is in IDLE state */
2N/A if (t_getstate(fd) != T_IDLE) {
2N/A (void) close(fd);
2N/A return (-1);
2N/A }
2N/A
2N/A /* setup connect parameters */
2N/A netaddr.len = netaddr.maxlen = __rpc_get_a_size(tinfo.addr);
2N/A sndcall.addr = netaddr;
2N/A sndcall.opt.len = sndcall.udata.len = 0;
2N/A
2N/A /* we wait for KADMIND_CONNECT_TIMEOUT seconds from now */
2N/A connect_time = time(NULL) + KADMIND_CONNECT_TIMEOUT;
2N/A if (t_connect(fd, &sndcall, rcvcall) != 0) {
2N/A if (t_errno != TNODATA) {
2N/A (void) close(fd);
2N/A return (-1);
2N/A }
2N/A }
2N/A
2N/A /* loop till success or timeout */
2N/A for (;;) {
2N/A if (t_rcvconnect(fd, rcvcall) == 0)
2N/A break;
2N/A
2N/A if (t_errno != TNODATA || time(NULL) > connect_time) {
2N/A /* we have either timed out or caught an error */
2N/A (void) close(fd);
2N/A if (rcvcall != NULL)
2N/A t_free((char *)rcvcall, T_CALL);
2N/A return (-1);
2N/A }
2N/A sleep(1);
2N/A }
2N/A
2N/A /* make the fd blocking (synchronous) */
2N/A flags = fcntl(fd, F_GETFL, 0);
2N/A (void) fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
2N/A if (rcvcall != NULL)
2N/A t_free((char *)rcvcall, T_CALL);
2N/A return (fd);
2N/A}
2N/A
2N/A/*
2N/A * Open an RPCSEC_GSS connection and
2N/A * get a client handle to use for future RPCSEC calls.
2N/A *
2N/A * This function is only used when changing passwords and
2N/A * the kpasswd_protocol is RPCSEC_GSS
2N/A */
2N/Astatic int
2N/A_kadm5_initialize_rpcsec_gss_handle(kadm5_server_handle_t handle,
2N/A char *client_name,
2N/A char *service_name)
2N/A{
2N/A struct netbuf netaddr;
2N/A struct hostent *hp;
2N/A int fd;
2N/A struct sockaddr_in addr;
2N/A struct sockaddr_in *sin;
2N/A struct netconfig *nconf;
2N/A int code = 0;
2N/A generic_ret *r;
2N/A /* Solaris Kerberos */
2N/A char *ccname_orig = NULL;
2N/A char *iprop_svc;
2N/A boolean_t iprop_enable = B_FALSE;
2N/A char mech[] = "kerberos_v5";
2N/A gss_OID mech_oid;
2N/A gss_OID_set_desc oid_set;
2N/A gss_name_t gss_client;
2N/A gss_buffer_desc input_name;
2N/A gss_cred_id_t gss_client_creds = GSS_C_NO_CREDENTIAL;
2N/A rpc_gss_options_req_t options_req;
2N/A rpc_gss_options_ret_t options_ret;
2N/A rpc_gss_service_t service = rpc_gss_svc_privacy;
2N/A OM_uint32 gssstat, minor_stat;
2N/A void *handlep;
2N/A enum clnt_stat rpc_err_code;
2N/A char *server;
2N/A
2N/A /* service name is service@host */
2N/A server = strpbrk(service_name, "@");
2N/A if (!server) {
2N/A code = KADM5_BAD_SERVER_NAME;
2N/A goto cleanup;
2N/A }
2N/A
2N/A hp = gethostbyname(++server);
2N/A if (hp == (struct hostent *)NULL) {
2N/A code = KADM5_BAD_SERVER_NAME;
2N/A goto cleanup;
2N/A }
2N/A
2N/A memset(&addr, 0, sizeof (addr));
2N/A addr.sin_family = hp->h_addrtype;
2N/A (void) memcpy((char *)&addr.sin_addr, (char *)hp->h_addr,
2N/A sizeof (addr.sin_addr));
2N/A addr.sin_port = htons((ushort_t)handle->params.kadmind_port);
2N/A sin = &addr;
2N/A#ifdef DEBUG
2N/A printf("kadmin_port %d\n", handle->params.kadmind_port);
2N/A printf("addr: sin_port: %d, sin_family: %d, sin_zero %s\n",
2N/A addr.sin_port, addr.sin_family, addr.sin_zero);
2N/A printf("sin_addr %d:%d\n", addr.sin_addr.S_un.S_un_w.s_w1,
2N/A addr.sin_addr.S_un.S_un_w.s_w2);
2N/A#endif
2N/A if ((handlep = setnetconfig()) == (void *) NULL) {
2N/A (void) syslog(LOG_ERR,
2N/A dgettext(TEXT_DOMAIN,
2N/A "cannot get any transport information"));
2N/A goto error;
2N/A }
2N/A
2N/A while (nconf = getnetconfig(handlep)) {
2N/A if ((nconf->nc_semantics == NC_TPI_COTS_ORD) &&
2N/A (strcmp(nconf->nc_protofmly, NC_INET) == 0) &&
2N/A (strcmp(nconf->nc_proto, NC_TCP) == 0))
2N/A break;
2N/A }
2N/A
2N/A if (nconf == (struct netconfig *)NULL)
2N/A goto error;
2N/A
2N/A /* Transform addr to netbuf */
2N/A (void) memset(&netaddr, 0, sizeof (netaddr));
2N/A netaddr.buf = (char *)sin;
2N/A
2N/A /* get an fd connected to the given address */
2N/A fd = get_connection(nconf, netaddr);
2N/A if (fd == -1) {
2N/A syslog(LOG_ERR, dgettext(TEXT_DOMAIN,
2N/A "unable to open connection to ADMIN server "
2N/A "(t_error %i)"), t_errno);
2N/A code = KADM5_RPC_ERROR;
2N/A goto error;
2N/A }
2N/A
2N/A#ifdef DEBUG
2N/A printf("fd: %d, KADM: %d, KADMVERS %d\n", fd, KADM, KADMVERS);
2N/A printf("nconf: nc_netid: %s, nc_semantics: %d, nc_flag: %d, "
2N/A "nc_protofmly: %s\n",
2N/A nconf->nc_netid, nconf->nc_semantics, nconf->nc_flag,
2N/A nconf->nc_protofmly);
2N/A printf("nc_proto: %s, nc_device: %s, nc_nlookups: %d, nc_used: %d\n",
2N/A nconf->nc_proto, nconf->nc_device, nconf->nc_nlookups,
2N/A nconf->nc_unused);
2N/A printf("netaddr: maxlen %d, buf: %s, len: %d\n", netaddr.maxlen,
2N/A netaddr.buf, netaddr.len);
2N/A#endif
2N/A /*
2N/A * Tell clnt_tli_create that given fd is already connected
2N/A *
2N/A * If the service_name and client_name are iprop-centric,
2N/A * we need to clnt_tli_create to the appropriate RPC prog
2N/A */
2N/A iprop_svc = strdup(KIPROP_SVC_NAME);
2N/A if (iprop_svc == NULL)
2N/A return (ENOMEM);
2N/A
2N/A if ((strstr(service_name, iprop_svc) != NULL) &&
2N/A (strstr(client_name, iprop_svc) != NULL)) {
2N/A iprop_enable = B_TRUE;
2N/A handle->clnt = clnt_tli_create(fd, nconf, NULL,
2N/A KRB5_IPROP_PROG, KRB5_IPROP_VERS, 0, 0);
2N/A }
2N/A else
2N/A handle->clnt = clnt_tli_create(fd, nconf, NULL,
2N/A KADM, KADMVERS, 0, 0);
2N/A
2N/A if (iprop_svc)
2N/A free(iprop_svc);
2N/A
2N/A if (handle->clnt == NULL) {
2N/A syslog(LOG_ERR, dgettext(TEXT_DOMAIN,
2N/A "clnt_tli_create failed\n"));
2N/A code = KADM5_RPC_ERROR;
2N/A (void) close(fd);
2N/A goto error;
2N/A }
2N/A /*
2N/A * The rpc-handle was created on an fd opened and connected
2N/A * by us, so we have to explicitly tell rpc to close it.
2N/A */
2N/A if (clnt_control(handle->clnt, CLSET_FD_CLOSE, NULL) != TRUE) {
2N/A clnt_pcreateerror("ERROR:");
2N/A syslog(LOG_ERR, dgettext(TEXT_DOMAIN,
2N/A "clnt_control failed to set CLSET_FD_CLOSE"));
2N/A code = KADM5_RPC_ERROR;
2N/A (void) close(fd);
2N/A goto error;
2N/A }
2N/A
2N/A handle->lhandle->clnt = handle->clnt;
2N/A
2N/A /* now that handle->clnt is set, we can check the handle */
2N/A if (code = _kadm5_check_handle((void *) handle))
2N/A goto error;
2N/A
2N/A /*
2N/A * The RPC connection is open; establish the GSS-API
2N/A * authentication context.
2N/A */
2N/A /* use the kadm5 cache */
2N/A ccname_orig = getenv("KRB5CCNAME");
2N/A if (ccname_orig)
2N/A ccname_orig = strdup(ccname_orig);
2N/A
2N/A (void) krb5_setenv("KRB5CCNAME", handle->cache_name, 1);
2N/A
2N/A input_name.value = client_name;
2N/A input_name.length = strlen((char *)input_name.value) + 1;
2N/A gssstat = gss_import_name(&minor_stat, &input_name,
2N/A (gss_OID)gss_nt_krb5_name, &gss_client);
2N/A if (gssstat != GSS_S_COMPLETE) {
2N/A code = KADM5_GSS_ERROR;
2N/A goto error;
2N/A }
2N/A
2N/A if (!rpc_gss_mech_to_oid(mech, (rpc_gss_OID *)&mech_oid)) {
2N/A goto error;
2N/A }
2N/A
2N/A oid_set.count = 1;
2N/A oid_set.elements = mech_oid;
2N/A
2N/A gssstat = gss_acquire_cred(&minor_stat, gss_client, 0,
2N/A &oid_set, GSS_C_INITIATE,
2N/A &gss_client_creds, NULL, NULL);
2N/A (void) gss_release_name(&minor_stat, &gss_client);
2N/A if (gssstat != GSS_S_COMPLETE) {
2N/A code = KADM5_GSS_ERROR;
2N/A goto error;
2N/A }
2N/A handle->my_cred = gss_client_creds;
2N/A options_req.my_cred = gss_client_creds;
2N/A options_req.req_flags = GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG;
2N/A options_req.time_req = 0;
2N/A options_req.input_channel_bindings = NULL;
2N/A#ifndef INIT_TEST
2N/A handle->clnt->cl_auth = rpc_gss_seccreate(handle->clnt,
2N/A service_name,
2N/A mech,
2N/A service,
2N/A NULL,
2N/A &options_req,
2N/A &options_ret);
2N/A#endif /* ! INIT_TEST */
2N/A
2N/A if (ccname_orig) {
2N/A (void) krb5_setenv("KRB5CCNAME", ccname_orig, 1);
2N/A /* Solaris Kerberos */
2N/A } else
2N/A (void) krb5_unsetenv("KRB5CCNAME");
2N/A
2N/A if (handle->clnt->cl_auth == NULL) {
2N/A code = KADM5_GSS_ERROR;
2N/A display_status(dgettext(TEXT_DOMAIN,
2N/A "rpc_gss_seccreate failed\n"),
2N/A options_ret.major_status,
2N/A options_ret.minor_status,
2N/A mech);
2N/A goto error;
2N/A }
2N/A
2N/A /*
2N/A * Bypass the remainder of the code and return straightaway
2N/A * if the gss service requested is kiprop
2N/A */
2N/A if (iprop_enable == B_TRUE) {
2N/A code = 0;
2N/A goto cleanup;
2N/A }
2N/A
2N/A r = init_2(&handle->api_version, handle->clnt);
2N/A /* Solaris Kerberos: 163 resync */
2N/A if (r == NULL) {
2N/A code = KADM5_RPC_ERROR;
2N/A goto error;
2N/A }
2N/A
2N/A /* Drop down to v2 wire protocol if server does not support v3 */
2N/A if (r->code == KADM5_NEW_SERVER_API_VERSION &&
2N/A handle->api_version == KADM5_API_VERSION_3) {
2N/A handle->api_version = KADM5_API_VERSION_2;
2N/A r = init_2(&handle->api_version, handle->clnt);
2N/A if (r == NULL) {
2N/A code = KADM5_RPC_ERROR;
2N/A goto error;
2N/A }
2N/A }
2N/A
2N/A if (r->code) {
2N/A code = r->code;
2N/A goto error;
2N/A }
2N/Aerror:
2N/Acleanup:
2N/A /* Solaris Kerberos */
2N/A if (ccname_orig != NULL)
2N/A free(ccname_orig);
2N/A
2N/A if (handlep != (void *) NULL)
2N/A (void) endnetconfig(handlep);
2N/A /*
2N/A * gss_client_creds is freed only when there is an error condition,
2N/A * given that rpc_gss_seccreate() will assign the cred pointer to the
2N/A * my_cred member in the auth handle's private data structure.
2N/A */
2N/A if (code && (gss_client_creds != GSS_C_NO_CREDENTIAL))
2N/A (void) gss_release_cred(&minor_stat, &gss_client_creds);
2N/A
2N/A return (code);
2N/A}
2N/A
2N/A/* Solaris Kerberos: utility function used below */
2N/Astatic void
2N/Aclean_up(kadm5_server_handle_t handle,
2N/A enum init_type init_type,
2N/A char **server,
2N/A krb5_creds *creds,
2N/A krb5_principal *clientp,
2N/A krb5_principal *serverp,
2N/A krb5_ccache *ccache)
2N/A{
2N/A if (init_type != INIT_CREDS) {
2N/A krb5_cc_close(handle->context, *ccache);
2N/A *ccache = NULL;
2N/A } else if (handle->destroy_cache && *ccache) {
2N/A krb5_cc_destroy(handle->context, *ccache);
2N/A *ccache = NULL;
2N/A }
2N/A if (handle->cache_name) {
2N/A free(handle->cache_name);
2N/A handle->cache_name = NULL;
2N/A }
2N/A if(handle->clnt && handle->clnt->cl_auth)
2N/A AUTH_DESTROY(handle->clnt->cl_auth);
2N/A if(handle->clnt) {
2N/A clnt_destroy(handle->clnt);
2N/A handle->clnt = NULL;
2N/A }
2N/A if (*server) {
2N/A free(*server);
2N/A *server = NULL;
2N/A }
2N/A if (*clientp != NULL) {
2N/A if (*clientp != creds->client)
2N/A krb5_free_principal(handle->context, *clientp);
2N/A *clientp = NULL;
2N/A }
2N/A if (*serverp != NULL) {
2N/A if (*serverp != creds->server)
2N/A krb5_free_principal(handle->context, *serverp);
2N/A *serverp = NULL;
2N/A }
2N/A krb5_free_cred_contents(handle->context, creds);
2N/A memset(creds, 0, sizeof(*creds));
2N/A}
2N/A
2N/Astatic kadm5_ret_t _kadm5_init_any(krb5_context context, char *client_name,
2N/A enum init_type init_type,
2N/A char *pass,
2N/A krb5_ccache ccache_in,
2N/A char **service_names,
2N/A kadm5_config_params *params_in,
2N/A krb5_ui_4 struct_version,
2N/A krb5_ui_4 api_version,
2N/A char **db_args,
2N/A void **server_handle)
2N/A{
2N/A int i;
2N/A krb5_creds creds;
2N/A krb5_ccache ccache = NULL;
2N/A krb5_timestamp now;
2N/A OM_uint32 gssstat, minor_stat;
2N/A kadm5_server_handle_t handle;
2N/A kadm5_config_params params_local;
2N/A int code = 0;
2N/A krb5_get_init_creds_opt opt;
2N/A gss_buffer_desc input_name;
2N/A krb5_error_code kret;
2N/A krb5_int32 starttime;
2N/A char *server = NULL;
2N/A krb5_principal serverp = NULL, clientp = NULL;
2N/A krb5_principal saved_server = NULL;
2N/A bool_t cpw = FALSE;
2N/A
2N/A if (! server_handle) {
2N/A return EINVAL;
2N/A }
2N/A
2N/A if (! (handle = malloc(sizeof(*handle)))) {
2N/A return ENOMEM;
2N/A }
2N/A if (! (handle->lhandle = malloc(sizeof(*handle)))) {
2N/A free(handle);
2N/A return ENOMEM;
2N/A }
2N/A
2N/A handle->magic_number = KADM5_SERVER_HANDLE_MAGIC;
2N/A handle->struct_version = struct_version;
2N/A handle->api_version = api_version;
2N/A handle->clnt = 0;
2N/A handle->cache_name = 0;
2N/A handle->destroy_cache = 0;
2N/A *handle->lhandle = *handle;
2N/A handle->lhandle->api_version = KADM5_API_VERSION_3;
2N/A handle->lhandle->struct_version = KADM5_STRUCT_VERSION;
2N/A handle->lhandle->lhandle = handle->lhandle;
2N/A
2N/A handle->context = context;
2N/A
2N/A if(service_names == NULL || service_names[0] == NULL ||
2N/A client_name == NULL) {
2N/A free(handle->lhandle);
2N/A free(handle);
2N/A return EINVAL;
2N/A }
2N/A memset((char *) &creds, 0, sizeof(creds));
2N/A
2N/A /*
2N/A * Verify the version numbers before proceeding; we can't use
2N/A * CHECK_HANDLE because not all fields are set yet.
2N/A */
2N/A /* Solaris Kerberos */
2N/A GENERIC_CHECK_HANDLE_WITH_RET(handle, KADM5_OLD_LIB_API_VERSION,
2N/A KADM5_NEW_LIB_API_VERSION, code);
2N/A if (code != 0) {
2N/A free(handle->lhandle);
2N/A free(handle);
2N/A return (code);
2N/A }
2N/A
2N/A /*
2N/A * Acquire relevant profile entries. In version 2, merge values
2N/A * in params_in with values from profile, based on
2N/A * params_in->mask.
2N/A *
2N/A * In version 1, we've given a realm (which may be NULL) instead
2N/A * of params_in. So use that realm, make params_in contain an
2N/A * empty mask, and behave like version 2.
2N/A */
2N/A memset(&params_local, 0, sizeof(params_local));
2N/A
2N/A#if 0 /* Since KDC config params can now be put in krb5.conf, these
2N/A could show up even when you're just using the remote kadmin
2N/A client. */
2N/A#define ILLEGAL_PARAMS (KADM5_CONFIG_DBNAME | KADM5_CONFIG_ADBNAME | \
2N/A KADM5_CONFIG_ADB_LOCKFILE | \
2N/A KADM5_CONFIG_ACL_FILE | KADM5_CONFIG_DICT_FILE \
2N/A | KADM5_CONFIG_ADMIN_KEYTAB | \
2N/A KADM5_CONFIG_STASH_FILE | \
2N/A KADM5_CONFIG_MKEY_NAME | KADM5_CONFIG_ENCTYPE \
2N/A | KADM5_CONFIG_MAX_LIFE | \
2N/A KADM5_CONFIG_MAX_RLIFE | \
2N/A KADM5_CONFIG_EXPIRATION | KADM5_CONFIG_FLAGS | \
2N/A KADM5_CONFIG_ENCTYPES | KADM5_CONFIG_MKEY_FROM_KBD)
2N/A
2N/A if (params_in && params_in->mask & ILLEGAL_PARAMS) {
2N/A free(handle);
2N/A return KADM5_BAD_CLIENT_PARAMS;
2N/A }
2N/A#endif
2N/A
2N/A if ((code = kadm5_get_config_params(handle->context, 0,
2N/A params_in, &handle->params))) {
2N/A free(handle->lhandle);
2N/A free(handle);
2N/A return(code);
2N/A }
2N/A
2N/A#define REQUIRED_PARAMS (KADM5_CONFIG_REALM | \
2N/A KADM5_CONFIG_ADMIN_SERVER | \
2N/A KADM5_CONFIG_KADMIND_PORT)
2N/A#define KPW_REQUIRED_PARAMS (KADM5_CONFIG_REALM | \
2N/A KADM5_CONFIG_KPASSWD_SERVER | \
2N/A KADM5_CONFIG_KPASSWD_PORT)
2N/A
2N/A if (((handle->params.mask & REQUIRED_PARAMS) != REQUIRED_PARAMS) &&
2N/A ((handle->params.mask & KPW_REQUIRED_PARAMS) != KPW_REQUIRED_PARAMS)) {
2N/A (void) kadm5_free_config_params(handle->context,
2N/A &handle->params);
2N/A free(handle->lhandle);
2N/A free(handle);
2N/A return KADM5_MISSING_KRB5_CONF_PARAMS;
2N/A }
2N/A
2N/A /*
2N/A * Acquire a service ticket for service_name@realm in the name of
2N/A * client_name, using password pass (which could be NULL), and
2N/A * create a ccache to store them in. If INIT_CREDS, use the
2N/A * ccache we were provided instead.
2N/A */
2N/A
2N/A /* Assumption: all service names refer to the same fundamental service */
2N/A if (strncmp(service_names[0], KADM5_CHANGEPW_HOST_SERVICE,
2N/A strlen(KADM5_CHANGEPW_HOST_SERVICE)) == 0)
2N/A cpw = TRUE;
2N/A
2N/A /*
2N/A * Client side multi-master support: loop through service names, stopping
2N/A * either if rpcsec not being done (set-change protocol has it's own logic
2N/A * for dealing with multiple admin_servers) or a rpcsec gss handle is
2N/A * successfully initialized with one of the admin servers specified listed
2N/A * in service_names.
2N/A */
2N/A for (i = 0; service_names[i] != NULL; i++) {
2N/A
2N/A if ((code = krb5_parse_name(handle->context, client_name,
2N/A &creds.client))) {
2N/A goto error;
2N/A }
2N/A clientp = creds.client;
2N/A
2N/A if (init_type == INIT_PASS &&
2N/A handle->params.kpasswd_protocol == KRB5_CHGPWD_CHANGEPW_V2 &&
2N/A cpw == TRUE) {
2N/A /*
2N/A * The 'service_name' is constructed by the caller
2N/A * but its done before the parameter which determines
2N/A * the kpasswd_protocol is found. The servers that
2N/A * support the SET/CHANGE password protocol expect
2N/A * a slightly different service principal than
2N/A * the normal SEAM kadmind so construct the correct
2N/A * name here and then forget it.
2N/A */
2N/A char *newsvcname = NULL;
2N/A newsvcname = malloc(strlen(KADM5_CHANGEPW_SERVICE) +
2N/A strlen(handle->params.realm) + 2);
2N/A if (newsvcname == NULL) {
2N/A code = ENOMEM;
2N/A goto error;
2N/A }
2N/A sprintf(newsvcname, "%s@%s", KADM5_CHANGEPW_SERVICE,
2N/A handle->params.realm);
2N/A
2N/A if ((code = krb5_parse_name(handle->context, newsvcname,
2N/A &creds.server))) {
2N/A free(newsvcname);
2N/A newsvcname = NULL;
2N/A goto error;
2N/A }
2N/A free(newsvcname);
2N/A } else {
2N/A /* Solaris Kerberos */
2N/A krb5_gss_name_t name = NULL;
2N/A
2N/A input_name.value = service_names[i];
2N/A input_name.length = strlen((char *)input_name.value) + 1;
2N/A gssstat = krb5_gss_import_name(&minor_stat,
2N/A &input_name,
2N/A (gss_OID)GSS_C_NT_HOSTBASED_SERVICE,
2N/A (gss_name_t *)&name);
2N/A
2N/A if (gssstat != GSS_S_COMPLETE) {
2N/A code = KADM5_GSS_ERROR;
2N/A goto error;
2N/A }
2N/A
2N/A creds.server = name->princ;
2N/A }
2N/A serverp = creds.server;
2N/A
2N/A /* XXX temporarily fix a bug in krb5_cc_get_type */
2N/A#undef krb5_cc_get_type
2N/A#define krb5_cc_get_type(context, cache) ((cache)->ops->prefix)
2N/A
2N/A
2N/A if (init_type == INIT_CREDS) {
2N/A ccache = ccache_in;
2N/A handle->cache_name =
2N/A malloc(strlen(krb5_cc_get_type(handle->context, ccache)) +
2N/A strlen(krb5_cc_get_name(handle->context, ccache)) + 2);
2N/A if (handle->cache_name == NULL) {
2N/A code = ENOMEM;
2N/A goto error;
2N/A }
2N/A sprintf(handle->cache_name, "%s:%s",
2N/A krb5_cc_get_type(handle->context, ccache),
2N/A krb5_cc_get_name(handle->context, ccache));
2N/A } else {
2N/A#if 0
2N/A handle->cache_name =
2N/A malloc(strlen(ADM_CCACHE)+strlen("FILE:")+1);
2N/A if (handle->cache_name == NULL) {
2N/A code = ENOMEM;
2N/A goto error;
2N/A }
2N/A sprintf(handle->cache_name, "FILE:%s", ADM_CCACHE);
2N/A mktemp(handle->cache_name + strlen("FILE:"));
2N/A#endif
2N/A {
2N/A static int counter = 0;
2N/A handle->cache_name = malloc(sizeof("MEMORY:kadm5_")
2N/A + (3 * sizeof(counter)));
2N/A sprintf(handle->cache_name, "MEMORY:kadm5_%u", counter++);
2N/A }
2N/A
2N/A if ((code = krb5_cc_resolve(handle->context, handle->cache_name,
2N/A &ccache)))
2N/A goto error;
2N/A
2N/A if ((code = krb5_cc_initialize (handle->context, ccache,
2N/A creds.client)))
2N/A goto error;
2N/A
2N/A handle->destroy_cache = 1;
2N/A }
2N/A handle->lhandle->cache_name = handle->cache_name;
2N/A
2N/A if ((code = krb5_timeofday(handle->context, &now)))
2N/A goto error;
2N/A
2N/A /*
2N/A * Get a ticket, use the method specified in init_type.
2N/A */
2N/A
2N/A creds.times.starttime = 0; /* start timer at KDC */
2N/A creds.times.endtime = 0; /* endtime will be limited by service */
2N/A
2N/A memset(&opt, 0, sizeof (opt));
2N/A krb5_get_init_creds_opt_init(&opt);
2N/A
2N/A if (creds.times.endtime) {
2N/A if (creds.times.starttime)
2N/A starttime = creds.times.starttime;
2N/A else
2N/A starttime = now;
2N/A
2N/A krb5_get_init_creds_opt_set_tkt_life(&opt,
2N/A creds.times.endtime - starttime);
2N/A }
2N/A code = krb5_unparse_name(handle->context, creds.server, &server);
2N/A if (code)
2N/A goto error;
2N/A
2N/A /*
2N/A * Solaris Kerberos:
2N/A * Save the original creds.server as krb5_get_init_creds*() always
2N/A * sets the realm of the server to the client realm.
2N/A */
2N/A code = krb5_copy_principal(handle->context, creds.server, &saved_server);
2N/A if (code)
2N/A goto error;
2N/A
2N/A if (init_type == INIT_PASS) {
2N/A code = krb5_get_init_creds_password(handle->context,
2N/A &creds, creds.client, pass, NULL,
2N/A NULL, creds.times.starttime,
2N/A server, &opt);
2N/A } else if (init_type == INIT_SKEY) {
2N/A krb5_keytab kt = NULL;
2N/A
2N/A if (!(pass && (code = krb5_kt_resolve(handle->context,
2N/A pass, &kt)))) {
2N/A
2N/A code = krb5_get_init_creds_keytab(handle->context,
2N/A &creds, creds.client, kt,
2N/A creds.times.starttime,
2N/A server, &opt);
2N/A
2N/A if (pass) krb5_kt_close(handle->context, kt);
2N/A }
2N/A }
2N/A
2N/A /* Improved error messages */
2N/A if (code == KRB5KRB_AP_ERR_BAD_INTEGRITY) code = KADM5_BAD_PASSWORD;
2N/A if (code == KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN) {
2N/A code = KADM5_SECURE_PRINC_MISSING;
2N/A /* clean up for another go around */
2N/A clean_up(handle, init_type, &server, &creds, &clientp, &serverp, &ccache);
2N/A krb5_free_principal(handle->context, saved_server);
2N/A saved_server = NULL;
2N/A continue;
2N/A }
2N/A
2N/A if (code != 0) {
2N/A krb5_free_principal(handle->context, saved_server);
2N/A goto error;
2N/A }
2N/A
2N/A /*
2N/A * Solaris Kerberos:
2N/A * If the server principal had an empty realm then store that in
2N/A * the cred cache and not the server realm as returned by
2N/A * krb5_get_init_creds_{keytab|password}(). This ensures that rpcsec_gss
2N/A * will find the credential in the cred cache even if a "fallback"
2N/A * method is being used to determine the realm.
2N/A */
2N/A if (init_type != INIT_CREDS) {
2N/A krb5_free_principal(handle->context, creds.server);
2N/A }
2N/A creds.server = saved_server;
2N/A
2N/A /*
2N/A * If we got this far, save the creds in the cache.
2N/A */
2N/A if (ccache) {
2N/A code = krb5_cc_store_cred(handle->context, ccache, &creds);
2N/A }
2N/A
2N/A
2N/A#ifdef ZEROPASSWD
2N/A if (pass != NULL)
2N/A memset(pass, 0, strlen(pass));
2N/A#endif
2N/A
2N/A if (init_type != INIT_PASS ||
2N/A handle->params.kpasswd_protocol == KRB5_CHGPWD_RPCSEC ||
2N/A cpw == FALSE) {
2N/A code = _kadm5_initialize_rpcsec_gss_handle(handle,
2N/A client_name,
2N/A service_names[i]);
2N/A /*
2N/A * Solaris Kerberos:
2N/A * If _kadm5_initialize_rpcsec_gss_handle() fails it will have
2N/A * called krb5_gss_release_cred(). If the credential cache is a
2N/A * MEMORY cred cache krb5_gss_release_cred() destroys the
2N/A * cred cache data. Make sure that the cred-cache is closed
2N/A * to prevent a double free in the "error" code.
2N/A */
2N/A if (code != 0) {
2N/A /* clean up for another go around */
2N/A clean_up(handle, init_type, &server, &creds, &clientp, &serverp, &ccache);
2N/A } else {
2N/A /* inited the rpcsec_gss handle, can stop looping now */
2N/A break;
2N/A }
2N/A } else {
2N/A /* if not initing the rpcsec_gss handle no reason to loop */
2N/A break;
2N/A }
2N/A } /* end for (i = 0; service_names[i] != NULL; i++) */
2N/A
2N/A if (service_names[i] == NULL) {
2N/A /* wasn't able to setup a handle so bail */
2N/A goto error;
2N/A }
2N/A
2N/A *server_handle = (void *) handle;
2N/A
2N/A if (init_type != INIT_CREDS)
2N/A krb5_cc_close(handle->context, ccache);
2N/A
2N/A goto cleanup;
2N/A
2N/Aerror:
2N/A /*
2N/A * Note that it is illegal for this code to execute if "handle"
2N/A * has not been allocated and initialized. I.e., don't use "goto
2N/A * error" before the block of code at the top of the function
2N/A * that allocates and initializes "handle".
2N/A */
2N/A if (handle->cache_name)
2N/A free(handle->cache_name);
2N/A if (handle->destroy_cache && ccache)
2N/A krb5_cc_destroy(handle->context, ccache);
2N/A if(handle->clnt && handle->clnt->cl_auth)
2N/A AUTH_DESTROY(handle->clnt->cl_auth);
2N/A if(handle->clnt)
2N/A clnt_destroy(handle->clnt);
2N/A (void) kadm5_free_config_params(handle->context, &handle->params);
2N/A
2N/Acleanup:
2N/A if (server)
2N/A free(server);
2N/A
2N/A /*
2N/A * cred's server and client pointers could have been overwritten
2N/A * by the krb5_get_init_* functions. If the addresses are different
2N/A * before and after the calls then we must free the memory that
2N/A * was allocated before the call.
2N/A */
2N/A if (clientp && clientp != creds.client)
2N/A krb5_free_principal(handle->context, clientp);
2N/A
2N/A if (serverp && serverp != creds.server)
2N/A krb5_free_principal(handle->context, serverp);
2N/A
2N/A krb5_free_cred_contents(handle->context, &creds);
2N/A
2N/A /*
2N/A * Dont clean up the handle if the code is OK (code==0)
2N/A * because it is returned to the caller in the 'server_handle'
2N/A * ptr.
2N/A */
2N/A if (code) {
2N/A free(handle->lhandle);
2N/A free(handle);
2N/A }
2N/A
2N/A return code;
2N/A}
2N/A
2N/Akadm5_ret_t
2N/Akadm5_destroy(void *server_handle)
2N/A{
2N/A krb5_ccache ccache = NULL;
2N/A int code = KADM5_OK;
2N/A kadm5_server_handle_t handle =
2N/A (kadm5_server_handle_t) server_handle;
2N/A OM_uint32 min_stat;
2N/A
2N/A CHECK_HANDLE(server_handle);
2N/A/* SUNW14resync:
2N/A * krb5_cc_resolve() will resolve a ccache with the same data that
2N/A * handle->my_cred points to. If the ccache is a MEMORY ccache then
2N/A * gss_release_cred() will free that data (it doesn't do this when ccache
2N/A * is a FILE ccache).
2N/A * if'ed out to avoid the double free.
2N/A */
2N/A#if 0
2N/A if (handle->destroy_cache && handle->cache_name) {
2N/A if ((code = krb5_cc_resolve(handle->context,
2N/A handle->cache_name, &ccache)) == 0)
2N/A code = krb5_cc_destroy (handle->context, ccache);
2N/A }
2N/A#endif
2N/A if (handle->cache_name)
2N/A free(handle->cache_name);
2N/A if (handle->clnt && handle->clnt->cl_auth) {
2N/A /*
2N/A * Since kadm5 doesn't use the default credentials we
2N/A * must clean this up manually.
2N/A */
2N/A if (handle->my_cred != GSS_C_NO_CREDENTIAL)
2N/A (void) gss_release_cred(&min_stat, &handle->my_cred);
2N/A AUTH_DESTROY(handle->clnt->cl_auth);
2N/A }
2N/A if (handle->clnt)
2N/A clnt_destroy(handle->clnt);
2N/A if (handle->lhandle)
2N/A free (handle->lhandle);
2N/A
2N/A kadm5_free_config_params(handle->context, &handle->params);
2N/A
2N/A handle->magic_number = 0;
2N/A free(handle);
2N/A
2N/A return code;
2N/A}
2N/A/* not supported on client */
2N/Akadm5_ret_t kadm5_lock(void *server_handle)
2N/A{
2N/A return EINVAL;
2N/A}
2N/A
2N/A/* not supported on client */
2N/Akadm5_ret_t kadm5_unlock(void *server_handle)
2N/A{
2N/A return EINVAL;
2N/A}
2N/A
2N/Akadm5_ret_t kadm5_flush(void *server_handle)
2N/A{
2N/A return KADM5_OK;
2N/A}
2N/A
2N/Aint _kadm5_check_handle(void *handle)
2N/A{
2N/A CHECK_HANDLE(handle);
2N/A return 0;
2N/A}
2N/A
2N/Akrb5_error_code kadm5_init_krb5_context (krb5_context *ctx)
2N/A{
2N/A return krb5_init_context(ctx);
2N/A}
2N/A
2N/A/*
2N/A * Stub function for kadmin. It was created to eliminate the dependency on
2N/A * libkdb's ulog functions. The srv equivalent makes the actual calls.
2N/A */
2N/Akrb5_error_code
2N/Akadm5_init_iprop(void *handle, char **db_args)
2N/A{
2N/A return (0);
2N/A}