Path: blob/main/crypto/krb5/src/lib/kadm5/clnt/client_init.c
39566 views
/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */1/*2* Copyright 1993 OpenVision Technologies, Inc., All Rights Reserved3*/45/*6* Copyright (C) 1998 by the FundsXpress, INC.7*8* All rights reserved.9*10* Export of this software from the United States of America may require11* a specific license from the United States Government. It is the12* responsibility of any person or organization contemplating export to13* obtain such a license before exporting.14*15* WITHIN THAT CONSTRAINT, permission to use, copy, modify, and16* distribute this software and its documentation for any purpose and17* without fee is hereby granted, provided that the above copyright18* notice appear in all copies and that both that copyright notice and19* this permission notice appear in supporting documentation, and that20* the name of FundsXpress. not be used in advertising or publicity pertaining21* to distribution of the software without specific, written prior22* permission. FundsXpress makes no representations about the suitability of23* this software for any purpose. It is provided "as is" without express24* or implied warranty.25*26* THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR27* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED28* WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.29*/3031#include <k5-int.h>32#include <netdb.h>33#include <com_err.h>34#include <sys/types.h>35#include <sys/socket.h>36#include <netinet/in.h>37#include <fake-addrinfo.h>38#include <krb5.h>3940#include <kadm5/admin.h>41#include <kadm5/kadm_rpc.h>42#include "client_internal.h"43#include <iprop_hdr.h>44#include "iprop.h"4546#include <gssrpc/rpc.h>47#include <gssapi/gssapi.h>48#include <gssapi/gssapi_krb5.h>49#include <gssrpc/auth_gssapi.h>5051#define ADM_CCACHE "/tmp/ovsec_adm.XXXXXX"5253enum init_type { INIT_PASS, INIT_SKEY, INIT_CREDS, INIT_ANONYMOUS };5455static kadm5_ret_t56init_any(krb5_context context, char *client_name, enum init_type init_type,57char *pass, krb5_ccache ccache_in, char *service_name,58kadm5_config_params *params, krb5_ui_4 struct_version,59krb5_ui_4 api_version, char **db_args, void **server_handle);6061static kadm5_ret_t62get_init_creds(kadm5_server_handle_t handle, krb5_principal client,63enum init_type init_type, char *pass, krb5_ccache ccache_in,64char *svcname_in, char *realm, krb5_principal *server_out);6566static kadm5_ret_t67gic_iter(kadm5_server_handle_t handle, enum init_type init_type,68krb5_ccache ccache, krb5_principal client, char *pass,69char *svcname, char *realm, krb5_principal *server_out);7071static kadm5_ret_t72connect_to_server(const char *hostname, int port, int *fd);7374static kadm5_ret_t75setup_gss(kadm5_server_handle_t handle, kadm5_config_params *params_in,76krb5_principal client, krb5_principal server);7778static void79rpc_auth(kadm5_server_handle_t handle, kadm5_config_params *params_in,80gss_cred_id_t gss_client_creds, gss_name_t gss_target);8182kadm5_ret_t83kadm5_init_with_creds(krb5_context context, char *client_name,84krb5_ccache ccache, char *service_name,85kadm5_config_params *params, krb5_ui_4 struct_version,86krb5_ui_4 api_version, char **db_args,87void **server_handle)88{89return init_any(context, client_name, INIT_CREDS, NULL, ccache,90service_name, params, struct_version, api_version, db_args,91server_handle);92}9394kadm5_ret_t95kadm5_init_with_password(krb5_context context, char *client_name,96char *pass, char *service_name,97kadm5_config_params *params, krb5_ui_4 struct_version,98krb5_ui_4 api_version, char **db_args,99void **server_handle)100{101return init_any(context, client_name, INIT_PASS, pass, NULL, service_name,102params, struct_version, api_version, db_args,103server_handle);104}105106kadm5_ret_t107kadm5_init_anonymous(krb5_context context, char *client_name,108char *service_name, kadm5_config_params *params,109krb5_ui_4 struct_version, krb5_ui_4 api_version,110char **db_args, void **server_handle)111{112return init_any(context, client_name, INIT_ANONYMOUS, NULL, NULL,113service_name, params, struct_version, api_version,114db_args, server_handle);115}116117kadm5_ret_t118kadm5_init(krb5_context context, char *client_name, char *pass,119char *service_name, kadm5_config_params *params,120krb5_ui_4 struct_version, krb5_ui_4 api_version, char **db_args,121void **server_handle)122{123return init_any(context, client_name, INIT_PASS, pass, NULL, service_name,124params, struct_version, api_version, db_args,125server_handle);126}127128kadm5_ret_t129kadm5_init_with_skey(krb5_context context, char *client_name,130char *keytab, char *service_name,131kadm5_config_params *params, krb5_ui_4 struct_version,132krb5_ui_4 api_version, char **db_args,133void **server_handle)134{135return init_any(context, client_name, INIT_SKEY, keytab, NULL,136service_name, params, struct_version, api_version, db_args,137server_handle);138}139140static kadm5_ret_t141free_handle(kadm5_server_handle_t handle)142{143kadm5_ret_t ret = 0;144OM_uint32 minor_stat;145krb5_ccache ccache;146147if (handle == NULL)148return 0;149150if (handle->destroy_cache && handle->cache_name != NULL) {151ret = krb5_cc_resolve(handle->context, handle->cache_name, &ccache);152if (!ret)153ret = krb5_cc_destroy(handle->context, ccache);154}155free(handle->cache_name);156(void)gss_release_cred(&minor_stat, &handle->cred);157if (handle->clnt != NULL && handle->clnt->cl_auth != NULL)158AUTH_DESTROY(handle->clnt->cl_auth);159if (handle->clnt != NULL)160clnt_destroy(handle->clnt);161if (handle->client_socket != -1)162close(handle->client_socket);163free(handle->lhandle);164kadm5_free_config_params(handle->context, &handle->params);165free(handle);166167return ret;168}169170static kadm5_ret_t171init_any(krb5_context context, char *client_name, enum init_type init_type,172char *pass, krb5_ccache ccache_in, char *service_name,173kadm5_config_params *params_in, krb5_ui_4 struct_version,174krb5_ui_4 api_version, char **db_args, void **server_handle)175{176int fd = -1;177krb5_boolean iprop_enable;178int port;179rpcprog_t rpc_prog;180rpcvers_t rpc_vers;181krb5_principal client = NULL, server = NULL;182struct timeval timeout;183184kadm5_server_handle_t handle = NULL;185kadm5_config_params params_local;186187krb5_error_code code;188generic_ret r = { 0, 0 };189190initialize_ovk_error_table();191initialize_ovku_error_table();192193if (server_handle == NULL || client_name == NULL)194return EINVAL;195196CHECK_VERSIONS(struct_version, api_version, KADM5_OLD_LIB_API_VERSION,197KADM5_NEW_LIB_API_VERSION);198199handle = k5alloc(sizeof(*handle), &code);200if (handle == NULL)201goto cleanup;202handle->lhandle = k5alloc(sizeof(*handle), &code);203if (handle->lhandle == NULL)204goto cleanup;205206handle->magic_number = KADM5_SERVER_HANDLE_MAGIC;207handle->struct_version = struct_version;208handle->api_version = api_version;209handle->clnt = 0;210handle->client_socket = -1;211handle->cache_name = 0;212handle->destroy_cache = 0;213handle->context = 0;214handle->cred = GSS_C_NO_CREDENTIAL;215*handle->lhandle = *handle;216handle->lhandle->api_version = KADM5_API_VERSION_4;217handle->lhandle->struct_version = KADM5_STRUCT_VERSION;218handle->lhandle->lhandle = handle->lhandle;219220handle->context = context;221222memset(¶ms_local, 0, sizeof(params_local));223224code = kadm5_get_config_params(handle->context, 0, params_in,225&handle->params);226if (code)227goto cleanup;228229#define REQUIRED_PARAMS (KADM5_CONFIG_REALM | \230KADM5_CONFIG_ADMIN_SERVER | \231KADM5_CONFIG_KADMIND_PORT)232233if ((handle->params.mask & REQUIRED_PARAMS) != REQUIRED_PARAMS) {234code = KADM5_MISSING_KRB5_CONF_PARAMS;235goto cleanup;236}237238/*239* Parse the client name. If it has an empty realm, it is almost certainly240* a host-based principal using DNS fallback processing or the referral241* realm, so give it the appropriate name type for canonicalization. Also242* check for iprop client principals as kpropd sets the realm on the243* sn2princ result.244*/245code = krb5_parse_name(handle->context, client_name, &client);246if (code)247goto cleanup;248if ((init_type == INIT_SKEY && client->realm.length == 0) ||249(client->length == 2 &&250data_eq_string(client->data[0], KIPROP_SVC_NAME)))251client->type = KRB5_NT_SRV_HST;252253/*254* Get credentials. Also does some fallbacks in case kadmin/fqdn255* principal doesn't exist.256*/257code = get_init_creds(handle, client, init_type, pass, ccache_in,258service_name, handle->params.realm, &server);259if (code)260goto cleanup;261262/* If the service_name and client_name are iprop-centric, use the iprop263* port and RPC identifiers. */264iprop_enable = (service_name != NULL &&265strstr(service_name, KIPROP_SVC_NAME) != NULL &&266strstr(client_name, KIPROP_SVC_NAME) != NULL);267if (iprop_enable) {268port = handle->params.iprop_port;269rpc_prog = KRB5_IPROP_PROG;270rpc_vers = KRB5_IPROP_VERS;271} else {272port = handle->params.kadmind_port;273rpc_prog = KADM;274rpc_vers = KADMVERS;275}276277code = connect_to_server(handle->params.admin_server, port, &fd);278if (code)279goto cleanup;280281handle->clnt = clnttcp_create(NULL, rpc_prog, rpc_vers, &fd, 0, 0);282if (handle->clnt == NULL) {283code = KADM5_RPC_ERROR;284#ifdef DEBUG285clnt_pcreateerror("clnttcp_create");286#endif287goto cleanup;288}289290/* Set a one-hour timeout. */291timeout.tv_sec = 3600;292timeout.tv_usec = 0;293(void)clnt_control(handle->clnt, CLSET_TIMEOUT, &timeout);294295handle->client_socket = fd;296handle->lhandle->clnt = handle->clnt;297handle->lhandle->client_socket = fd;298299/*300* The RPC connection is open; establish the GSS-API301* authentication context.302*/303code = setup_gss(handle, params_in,304(init_type == INIT_CREDS) ? client : NULL, server);305if (code)306goto cleanup;307308/*309* Bypass the remainder of the code and return straight away310* if the gss service requested is kiprop311*/312if (iprop_enable) {313code = 0;314*server_handle = handle;315handle = NULL;316goto cleanup;317}318319if (init_2(&handle->api_version, &r, handle->clnt)) {320code = KADM5_RPC_ERROR;321#ifdef DEBUG322clnt_perror(handle->clnt, "init_2 null resp");323#endif324goto cleanup;325}326/* Drop down to v3 wire protocol if server does not support v4 */327if (r.code == KADM5_NEW_SERVER_API_VERSION &&328handle->api_version == KADM5_API_VERSION_4) {329handle->api_version = KADM5_API_VERSION_3;330memset(&r, 0, sizeof(generic_ret));331if (init_2(&handle->api_version, &r, handle->clnt)) {332code = KADM5_RPC_ERROR;333goto cleanup;334}335}336/* Drop down to v2 wire protocol if server does not support v3 */337if (r.code == KADM5_NEW_SERVER_API_VERSION &&338handle->api_version == KADM5_API_VERSION_3) {339handle->api_version = KADM5_API_VERSION_2;340memset(&r, 0, sizeof(generic_ret));341if (init_2(&handle->api_version, &r, handle->clnt)) {342code = KADM5_RPC_ERROR;343goto cleanup;344}345}346if (r.code) {347code = r.code;348goto cleanup;349}350351*server_handle = handle;352handle = NULL;353354cleanup:355krb5_free_principal(context, client);356krb5_free_principal(context, server);357(void)free_handle(handle);358359return code;360}361362/* Get initial credentials for authenticating to server. Perform fallback from363* kadmin/fqdn to kadmin/admin if svcname_in is NULL. */364static kadm5_ret_t365get_init_creds(kadm5_server_handle_t handle, krb5_principal client,366enum init_type init_type, char *pass, krb5_ccache ccache_in,367char *svcname_in, char *realm, krb5_principal *server_out)368{369kadm5_ret_t code;370krb5_ccache ccache = NULL;371char *svcname, svcbuf[BUFSIZ];372373*server_out = NULL;374375/*376* Acquire a service ticket for svcname@realm for client, using password377* pass (which could be NULL), and create a ccache to store them in. If378* INIT_CREDS, use the ccache we were provided instead.379*/380if (init_type == INIT_CREDS) {381ccache = ccache_in;382if (asprintf(&handle->cache_name, "%s:%s",383krb5_cc_get_type(handle->context, ccache),384krb5_cc_get_name(handle->context, ccache)) < 0) {385handle->cache_name = NULL;386code = ENOMEM;387goto error;388}389} else {390static int counter = 0;391392if (asprintf(&handle->cache_name, "MEMORY:kadm5_%u", counter++) < 0) {393handle->cache_name = NULL;394code = ENOMEM;395goto error;396}397code = krb5_cc_resolve(handle->context, handle->cache_name,398&ccache);399if (code)400goto error;401402code = krb5_cc_initialize (handle->context, ccache, client);403if (code)404goto error;405406handle->destroy_cache = 1;407}408handle->lhandle->cache_name = handle->cache_name;409410svcname = (svcname_in != NULL) ? svcname_in : KADM5_ADMIN_SERVICE;411code = gic_iter(handle, init_type, ccache, client, pass, svcname, realm,412server_out);413if ((code == KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN414|| code == KRB5_CC_NOTFOUND) && svcname_in == NULL) {415/* Retry with host-based service principal. */416code = kadm5_get_admin_service_name(handle->context,417handle->params.realm,418svcbuf, sizeof(svcbuf));419if (code)420goto error;421code = gic_iter(handle, init_type, ccache, client, pass, svcbuf, realm,422server_out);423}424/* Improved error messages */425if (code == KRB5KRB_AP_ERR_BAD_INTEGRITY) code = KADM5_BAD_PASSWORD;426if (code == KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN)427code = KADM5_SECURE_PRINC_MISSING;428429error:430if (ccache != NULL && init_type != INIT_CREDS)431krb5_cc_close(handle->context, ccache);432return code;433}434435/* Perform one iteration of attempting to get credentials. This includes436* searching existing ccache for requested service if INIT_CREDS. */437static kadm5_ret_t438gic_iter(kadm5_server_handle_t handle, enum init_type init_type,439krb5_ccache ccache, krb5_principal client, char *pass, char *svcname,440char *realm, krb5_principal *server_out)441{442kadm5_ret_t code;443krb5_context ctx;444krb5_keytab kt;445krb5_get_init_creds_opt *opt = NULL;446krb5_creds mcreds, outcreds;447448*server_out = NULL;449ctx = handle->context;450kt = NULL;451memset(&opt, 0, sizeof(opt));452memset(&mcreds, 0, sizeof(mcreds));453memset(&outcreds, 0, sizeof(outcreds));454455/* Credentials for kadmin don't need to be forwardable or proxiable. */456if (init_type != INIT_CREDS) {457code = krb5_get_init_creds_opt_alloc(ctx, &opt);458if (code)459goto error;460461krb5_get_init_creds_opt_set_forwardable(opt, 0);462krb5_get_init_creds_opt_set_proxiable(opt, 0);463krb5_get_init_creds_opt_set_out_ccache(ctx, opt, ccache);464if (init_type == INIT_ANONYMOUS)465krb5_get_init_creds_opt_set_anonymous(opt, 1);466}467468if (init_type == INIT_PASS || init_type == INIT_ANONYMOUS) {469code = krb5_get_init_creds_password(ctx, &outcreds, client, pass,470krb5_prompter_posix,471NULL, 0, svcname, opt);472if (code)473goto error;474} else if (init_type == INIT_SKEY) {475if (pass) {476code = krb5_kt_resolve(ctx, pass, &kt);477if (code)478goto error;479}480code = krb5_get_init_creds_keytab(ctx, &outcreds, client, kt,4810, svcname, opt);482if (pass)483krb5_kt_close(ctx, kt);484if (code)485goto error;486} else if (init_type == INIT_CREDS) {487mcreds.client = client;488code = krb5_parse_name_flags(ctx, svcname,489KRB5_PRINCIPAL_PARSE_IGNORE_REALM,490&mcreds.server);491if (code)492goto error;493code = krb5_set_principal_realm(ctx, mcreds.server, realm);494if (code)495goto error;496code = krb5_cc_retrieve_cred(ctx, ccache, 0,497&mcreds, &outcreds);498krb5_free_principal(ctx, mcreds.server);499if (code)500goto error;501} else {502code = EINVAL;503goto error;504}505506/* Steal the server principal of the creds we acquired and return it to the507* caller, which needs to knows what service to authenticate to. */508*server_out = outcreds.server;509outcreds.server = NULL;510511error:512krb5_free_cred_contents(ctx, &outcreds);513if (opt)514krb5_get_init_creds_opt_free(ctx, opt);515return code;516}517518/* Set *fd to a socket connected to hostname and port. */519static kadm5_ret_t520connect_to_server(const char *hostname, int port, int *fd)521{522struct addrinfo hint, *addrs, *a;523char portbuf[32];524int err, s;525kadm5_ret_t code;526527/* Look up the server's addresses. */528(void) snprintf(portbuf, sizeof(portbuf), "%d", port);529memset(&hint, 0, sizeof(hint));530hint.ai_socktype = SOCK_STREAM;531hint.ai_flags = AI_ADDRCONFIG;532#ifdef AI_NUMERICSERV533hint.ai_flags |= AI_NUMERICSERV;534#endif535err = getaddrinfo(hostname, portbuf, &hint, &addrs);536if (err != 0)537return KADM5_CANT_RESOLVE;538539/* Try to connect to each address until we succeed. */540for (a = addrs; a != NULL; a = a->ai_next) {541s = socket(a->ai_family, a->ai_socktype, 0);542if (s == -1) {543code = KADM5_FAILURE;544goto cleanup;545}546err = connect(s, a->ai_addr, a->ai_addrlen);547if (err == 0) {548*fd = s;549code = 0;550goto cleanup;551}552close(s);553}554555/* We didn't succeed on any address. */556code = KADM5_RPC_ERROR;557cleanup:558freeaddrinfo(addrs);559return code;560}561562/* Acquire GSSAPI credentials and set up RPC auth flavor. */563static kadm5_ret_t564setup_gss(kadm5_server_handle_t handle, kadm5_config_params *params_in,565krb5_principal client, krb5_principal server)566{567OM_uint32 gssstat, minor_stat;568gss_buffer_desc buf;569gss_name_t gss_client;570gss_name_t gss_target;571const char *c_ccname_orig;572char *ccname_orig;573574ccname_orig = NULL;575gss_client = gss_target = GSS_C_NO_NAME;576577/* Temporarily use the kadm5 cache. */578gssstat = gss_krb5_ccache_name(&minor_stat, handle->cache_name,579&c_ccname_orig);580if (gssstat != GSS_S_COMPLETE)581goto error;582if (c_ccname_orig)583ccname_orig = strdup(c_ccname_orig);584else585ccname_orig = 0;586587buf.value = &server;588buf.length = sizeof(server);589gssstat = gss_import_name(&minor_stat, &buf,590(gss_OID)gss_nt_krb5_principal, &gss_target);591if (gssstat != GSS_S_COMPLETE)592goto error;593594if (client != NULL) {595buf.value = &client;596buf.length = sizeof(client);597gssstat = gss_import_name(&minor_stat, &buf,598(gss_OID)gss_nt_krb5_principal, &gss_client);599} else gss_client = GSS_C_NO_NAME;600601if (gssstat != GSS_S_COMPLETE)602goto error;603604gssstat = gss_acquire_cred(&minor_stat, gss_client, 0,605GSS_C_NULL_OID_SET, GSS_C_INITIATE,606&handle->cred, NULL, NULL);607if (gssstat != GSS_S_COMPLETE)608goto error;609610/*611* Do actual creation of RPC auth handle. Implements auth flavor612* fallback.613*/614rpc_auth(handle, params_in, handle->cred, gss_target);615616error:617if (gss_client)618gss_release_name(&minor_stat, &gss_client);619if (gss_target)620gss_release_name(&minor_stat, &gss_target);621622/* Revert to prior gss_krb5 ccache. */623if (ccname_orig) {624gssstat = gss_krb5_ccache_name(&minor_stat, ccname_orig, NULL);625if (gssstat) {626return KADM5_GSS_ERROR;627}628free(ccname_orig);629} else {630gssstat = gss_krb5_ccache_name(&minor_stat, NULL, NULL);631if (gssstat) {632return KADM5_GSS_ERROR;633}634}635636if (handle->clnt->cl_auth == NULL) {637return KADM5_GSS_ERROR;638}639return 0;640}641642/* Create RPC auth handle. Do auth flavor fallback if needed. */643static void644rpc_auth(kadm5_server_handle_t handle, kadm5_config_params *params_in,645gss_cred_id_t gss_client_creds, gss_name_t gss_target)646{647OM_uint32 gssstat, minor_stat;648struct rpc_gss_sec sec;649650/* Allow unauthenticated option for testing. */651if (params_in != NULL && (params_in->mask & KADM5_CONFIG_NO_AUTH))652return;653654/* Use RPCSEC_GSS by default. */655if (params_in == NULL ||656!(params_in->mask & KADM5_CONFIG_OLD_AUTH_GSSAPI)) {657sec.mech = (gss_OID)gss_mech_krb5;658sec.qop = GSS_C_QOP_DEFAULT;659sec.svc = RPCSEC_GSS_SVC_PRIVACY;660sec.cred = gss_client_creds;661sec.req_flags = GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG;662663handle->clnt->cl_auth = authgss_create(handle->clnt,664gss_target, &sec);665if (handle->clnt->cl_auth != NULL)666return;667}668669if (params_in != NULL && (params_in->mask & KADM5_CONFIG_AUTH_NOFALLBACK))670return;671672/* Fall back to old AUTH_GSSAPI. */673handle->clnt->cl_auth = auth_gssapi_create(handle->clnt,674&gssstat,675&minor_stat,676gss_client_creds,677gss_target,678(gss_OID) gss_mech_krb5,679GSS_C_MUTUAL_FLAG680| GSS_C_REPLAY_FLAG,6810, NULL, NULL, NULL);682}683684kadm5_ret_t685kadm5_destroy(void *server_handle)686{687CHECK_HANDLE(server_handle);688return free_handle(server_handle);689}690/* not supported on client */691kadm5_ret_t kadm5_lock(void *server_handle)692{693return EINVAL;694}695696/* not supported on client */697kadm5_ret_t kadm5_unlock(void *server_handle)698{699return EINVAL;700}701702kadm5_ret_t kadm5_flush(void *server_handle)703{704return KADM5_OK;705}706707int _kadm5_check_handle(void *handle)708{709CHECK_HANDLE(handle);710return 0;711}712713krb5_error_code kadm5_init_krb5_context (krb5_context *ctx)714{715return krb5_init_context(ctx);716}717718/*719* Stub function for kadmin. It was created to eliminate the dependency on720* libkdb's ulog functions. The srv equivalent makes the actual calls.721*/722krb5_error_code723kadm5_init_iprop(void *handle, char **db_args)724{725return (0);726}727728729