1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
3 * Copyright 1993 OpenVision Technologies, Inc., All Rights Reserved
7 * Copyright (C) 1998 by the FundsXpress, INC.
11 * Export of this software from the United States of America may require
12 * a specific license from the United States Government. It is the
13 * responsibility of any person or organization contemplating export to
14 * obtain such a license before exporting.
16 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
17 * distribute this software and its documentation for any purpose and
18 * without fee is hereby granted, provided that the above copyright
19 * notice appear in all copies and that both that copyright notice and
20 * this permission notice appear in supporting documentation, and that
21 * the name of FundsXpress. not be used in advertising or publicity pertaining
22 * to distribution of the software without specific, written prior
23 * permission. FundsXpress makes no representations about the suitability of
24 * this software for any purpose. It is provided "as is" without express
25 * or implied warranty.
27 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
28 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
29 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
40 #include <sys/types.h>
41 #include <sys/socket.h>
42 #include <netinet/in.h>
43 #include <fake-addrinfo.h>
44 #include <k5-int.h> /* for KRB5_ADM_DEFAULT_PORT */
50 #include <kadm5/admin.h>
51 #include <kadm5/kadm_rpc.h>
52 #include "client_internal.h"
53 #include <iprop_hdr.h>
56 #include <gssrpc/rpc.h>
57 #include <gssapi/gssapi.h>
58 #include <gssapi/gssapi_krb5.h>
59 #include <gssrpc/auth_gssapi.h>
61 #define ADM_CCACHE "/tmp/ovsec_adm.XXXXXX"
63 enum init_type { INIT_PASS, INIT_SKEY, INIT_CREDS, INIT_ANONYMOUS };
66 init_any(krb5_context context, char *client_name, enum init_type init_type,
67 char *pass, krb5_ccache ccache_in, char *service_name,
68 kadm5_config_params *params, krb5_ui_4 struct_version,
69 krb5_ui_4 api_version, char **db_args, void **server_handle);
72 get_init_creds(kadm5_server_handle_t handle, char *client_name,
73 enum init_type init_type, char *pass, krb5_ccache ccache_in,
74 char *svcname_in, char *realm, char *full_svcname,
75 unsigned int full_svcname_len);
78 gic_iter(kadm5_server_handle_t handle, enum init_type init_type,
79 krb5_ccache ccache, krb5_principal client, char *pass,
80 char *svcname, char *realm, char *full_svcname,
81 unsigned int full_svcname_len);
84 connect_to_server(const char *hostname, int port, int *fd);
87 setup_gss(kadm5_server_handle_t handle, kadm5_config_params *params_in,
88 char *client_name, char *full_svcname);
91 rpc_auth(kadm5_server_handle_t handle, kadm5_config_params *params_in,
92 gss_cred_id_t gss_client_creds, gss_name_t gss_target);
95 kadm5_init_with_creds(krb5_context context, char *client_name,
96 krb5_ccache ccache, char *service_name,
97 kadm5_config_params *params, krb5_ui_4 struct_version,
98 krb5_ui_4 api_version, char **db_args,
101 return init_any(context, client_name, INIT_CREDS, NULL, ccache,
102 service_name, params, struct_version, api_version, db_args,
107 kadm5_init_with_password(krb5_context context, char *client_name,
108 char *pass, char *service_name,
109 kadm5_config_params *params, krb5_ui_4 struct_version,
110 krb5_ui_4 api_version, char **db_args,
111 void **server_handle)
113 return init_any(context, client_name, INIT_PASS, pass, NULL, service_name,
114 params, struct_version, api_version, db_args,
119 kadm5_init_anonymous(krb5_context context, char *client_name,
120 char *service_name, kadm5_config_params *params,
121 krb5_ui_4 struct_version, krb5_ui_4 api_version,
122 char **db_args, void **server_handle)
124 return init_any(context, client_name, INIT_ANONYMOUS, NULL, NULL,
125 service_name, params, struct_version, api_version,
126 db_args, server_handle);
130 kadm5_init(krb5_context context, char *client_name, char *pass,
131 char *service_name, kadm5_config_params *params,
132 krb5_ui_4 struct_version, krb5_ui_4 api_version, char **db_args,
133 void **server_handle)
135 return init_any(context, client_name, INIT_PASS, pass, NULL, service_name,
136 params, struct_version, api_version, db_args,
141 kadm5_init_with_skey(krb5_context context, char *client_name,
142 char *keytab, char *service_name,
143 kadm5_config_params *params, krb5_ui_4 struct_version,
144 krb5_ui_4 api_version, char **db_args,
145 void **server_handle)
147 return init_any(context, client_name, INIT_SKEY, keytab, NULL,
148 service_name, params, struct_version, api_version, db_args,
153 init_any(krb5_context context, char *client_name, enum init_type init_type,
154 char *pass, krb5_ccache ccache_in, char *service_name,
155 kadm5_config_params *params_in, krb5_ui_4 struct_version,
156 krb5_ui_4 api_version, char **db_args, void **server_handle)
160 krb5_boolean iprop_enable;
164 char full_svcname[BUFSIZ];
168 kadm5_server_handle_t handle;
169 kadm5_config_params params_local;
174 initialize_ovk_error_table();
175 /* initialize_adb_error_table(); */
176 initialize_ovku_error_table();
178 if (! server_handle) {
182 if (! (handle = malloc(sizeof(*handle)))) {
185 memset(handle, 0, sizeof(*handle));
186 if (! (handle->lhandle = malloc(sizeof(*handle)))) {
191 handle->magic_number = KADM5_SERVER_HANDLE_MAGIC;
192 handle->struct_version = struct_version;
193 handle->api_version = api_version;
195 handle->cache_name = 0;
196 handle->destroy_cache = 0;
198 *handle->lhandle = *handle;
199 handle->lhandle->api_version = KADM5_API_VERSION_3;
200 handle->lhandle->struct_version = KADM5_STRUCT_VERSION;
201 handle->lhandle->lhandle = handle->lhandle;
203 handle->context = context;
205 if(client_name == NULL) {
211 * Verify the version numbers before proceeding; we can't use
212 * CHECK_HANDLE because not all fields are set yet.
214 GENERIC_CHECK_HANDLE(handle, KADM5_OLD_LIB_API_VERSION,
215 KADM5_NEW_LIB_API_VERSION);
218 * Acquire relevant profile entries. In version 2, merge values
219 * in params_in with values from profile, based on
222 * In version 1, we've given a realm (which may be NULL) instead
223 * of params_in. So use that realm, make params_in contain an
224 * empty mask, and behave like version 2.
226 memset(¶ms_local, 0, sizeof(params_local));
227 if (params_in && (params_in->mask & KADM5_CONFIG_REALM))
228 realm = params_in->realm;
232 #if 0 /* Since KDC config params can now be put in krb5.conf, these
233 could show up even when you're just using the remote kadmin
235 #define ILLEGAL_PARAMS (KADM5_CONFIG_DBNAME | KADM5_CONFIG_ADBNAME | \
236 KADM5_CONFIG_ADB_LOCKFILE | \
237 KADM5_CONFIG_ACL_FILE | KADM5_CONFIG_DICT_FILE \
238 | KADM5_CONFIG_ADMIN_KEYTAB | \
239 KADM5_CONFIG_STASH_FILE | \
240 KADM5_CONFIG_MKEY_NAME | KADM5_CONFIG_ENCTYPE \
241 | KADM5_CONFIG_MAX_LIFE | \
242 KADM5_CONFIG_MAX_RLIFE | \
243 KADM5_CONFIG_EXPIRATION | KADM5_CONFIG_FLAGS | \
244 KADM5_CONFIG_ENCTYPES | KADM5_CONFIG_MKEY_FROM_KBD)
246 if (params_in && params_in->mask & ILLEGAL_PARAMS) {
248 return KADM5_BAD_CLIENT_PARAMS;
252 if ((code = kadm5_get_config_params(handle->context, 0,
253 params_in, &handle->params))) {
258 #define REQUIRED_PARAMS (KADM5_CONFIG_REALM | \
259 KADM5_CONFIG_ADMIN_SERVER | \
260 KADM5_CONFIG_KADMIND_PORT)
262 if ((handle->params.mask & REQUIRED_PARAMS) != REQUIRED_PARAMS) {
264 return KADM5_MISSING_KRB5_CONF_PARAMS;
268 * Get credentials. Also does some fallbacks in case kadmin/fqdn
269 * principal doesn't exist.
271 code = get_init_creds(handle, client_name, init_type, pass, ccache_in,
272 service_name, realm, full_svcname,
273 sizeof(full_svcname));
277 /* If the service_name and client_name are iprop-centric, use the iprop
278 * port and RPC identifiers. */
279 iprop_enable = (service_name != NULL &&
280 strstr(service_name, KIPROP_SVC_NAME) != NULL &&
281 strstr(client_name, KIPROP_SVC_NAME) != NULL);
283 port = handle->params.iprop_port;
284 rpc_prog = KRB5_IPROP_PROG;
285 rpc_vers = KRB5_IPROP_VERS;
287 port = handle->params.kadmind_port;
292 code = connect_to_server(handle->params.admin_server, port, &fd);
296 handle->clnt = clnttcp_create(NULL, rpc_prog, rpc_vers, &fd, 0, 0);
297 if (handle->clnt == NULL) {
298 code = KADM5_RPC_ERROR;
300 clnt_pcreateerror("clnttcp_create");
304 handle->lhandle->clnt = handle->clnt;
306 /* now that handle->clnt is set, we can check the handle */
307 if ((code = _kadm5_check_handle((void *) handle)))
311 * The RPC connection is open; establish the GSS-API
312 * authentication context.
314 code = setup_gss(handle, params_in,
315 (init_type == INIT_CREDS) ? client_name : NULL,
321 * Bypass the remainder of the code and return straightaway
322 * if the gss service requested is kiprop
326 *server_handle = (void *) handle;
330 r = init_2(&handle->api_version, handle->clnt);
332 code = KADM5_RPC_ERROR;
334 clnt_perror(handle->clnt, "init_2 null resp");
338 /* Drop down to v2 wire protocol if server does not support v3 */
339 if (r->code == KADM5_NEW_SERVER_API_VERSION &&
340 handle->api_version == KADM5_API_VERSION_3) {
341 handle->api_version = KADM5_API_VERSION_2;
342 r = init_2(&handle->api_version, handle->clnt);
344 code = KADM5_RPC_ERROR;
353 *server_handle = (void *) handle;
359 * Note that it is illegal for this code to execute if "handle"
360 * has not been allocated and initialized. I.e., don't use "goto
361 * error" before the block of code at the top of the function
362 * that allocates and initializes "handle".
364 if (handle->destroy_cache && handle->cache_name) {
365 if (krb5_cc_resolve(handle->context,
366 handle->cache_name, &ccache) == 0)
367 (void) krb5_cc_destroy (handle->context, ccache);
369 if (handle->cache_name)
370 free(handle->cache_name);
371 if(handle->clnt && handle->clnt->cl_auth)
372 AUTH_DESTROY(handle->clnt->cl_auth);
374 clnt_destroy(handle->clnt);
376 kadm5_free_config_params(handle->context, &handle->params);
385 /* Get initial credentials for authenticating to server. Perform fallback from
386 * kadmin/fqdn to kadmin/admin if svcname_in is NULL. */
388 get_init_creds(kadm5_server_handle_t handle, char *client_name,
389 enum init_type init_type, char *pass, krb5_ccache ccache_in,
390 char *svcname_in, char *realm, char *full_svcname,
391 unsigned int full_svcname_len)
394 krb5_principal client = NULL;
395 krb5_ccache ccache = NULL;
396 char svcname[BUFSIZ];
398 /* NULL svcname means use host-based. */
399 if (svcname_in == NULL) {
400 code = kadm5_get_admin_service_name(handle->context,
401 handle->params.realm,
402 svcname, sizeof(svcname));
406 strncpy(svcname, svcname_in, sizeof(svcname));
407 svcname[sizeof(svcname)-1] = '\0';
410 * Acquire a service ticket for svcname@realm in the name of
411 * client_name, using password pass (which could be NULL), and
412 * create a ccache to store them in. If INIT_CREDS, use the
413 * ccache we were provided instead.
415 code = krb5_parse_name(handle->context, client_name, &client);
419 if (init_type == INIT_CREDS) {
421 if (asprintf(&handle->cache_name, "%s:%s",
422 krb5_cc_get_type(handle->context, ccache),
423 krb5_cc_get_name(handle->context, ccache)) < 0) {
424 handle->cache_name = NULL;
429 static int counter = 0;
431 if (asprintf(&handle->cache_name, "MEMORY:kadm5_%u", counter++) < 0) {
432 handle->cache_name = NULL;
436 code = krb5_cc_resolve(handle->context, handle->cache_name,
441 code = krb5_cc_initialize (handle->context, ccache, client);
445 handle->destroy_cache = 1;
447 handle->lhandle->cache_name = handle->cache_name;
449 code = gic_iter(handle, init_type, ccache, client, pass, svcname, realm,
450 full_svcname, full_svcname_len);
451 if ((code == KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN
452 || code == KRB5_CC_NOTFOUND) && svcname_in == NULL) {
453 /* Retry with old host-independent service principal. */
454 code = gic_iter(handle, init_type, ccache, client, pass,
455 KADM5_ADMIN_SERVICE, realm, full_svcname,
458 /* Improved error messages */
459 if (code == KRB5KRB_AP_ERR_BAD_INTEGRITY) code = KADM5_BAD_PASSWORD;
460 if (code == KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN)
461 code = KADM5_SECURE_PRINC_MISSING;
464 krb5_free_principal(handle->context, client);
465 if (ccache != NULL && init_type != INIT_CREDS)
466 krb5_cc_close(handle->context, ccache);
470 /* Perform one iteration of attempting to get credentials. This includes
471 * searching existing ccache for requested service if INIT_CREDS. */
473 gic_iter(kadm5_server_handle_t handle, enum init_type init_type,
474 krb5_ccache ccache, krb5_principal client, char *pass, char *svcname,
475 char *realm, char *full_svcname, unsigned int full_svcname_len)
480 krb5_get_init_creds_opt *opt = NULL;
481 krb5_creds mcreds, outcreds;
484 ctx = handle->context;
486 memset(full_svcname, 0, full_svcname_len);
487 memset(&opt, 0, sizeof(opt));
488 memset(&mcreds, 0, sizeof(mcreds));
489 memset(&outcreds, 0, sizeof(outcreds));
493 n = snprintf(full_svcname, full_svcname_len, "%s@%s",
495 if (n < 0 || n >= (int) full_svcname_len)
498 /* krb5_princ_realm(client) is not null terminated */
499 n = snprintf(full_svcname, full_svcname_len, "%s@%.*s",
500 svcname, krb5_princ_realm(ctx, client)->length,
501 krb5_princ_realm(ctx, client)->data);
502 if (n < 0 || n >= (int) full_svcname_len)
506 /* Credentials for kadmin don't need to be forwardable or proxiable. */
507 if (init_type != INIT_CREDS) {
508 code = krb5_get_init_creds_opt_alloc(ctx, &opt);
509 krb5_get_init_creds_opt_set_forwardable(opt, 0);
510 krb5_get_init_creds_opt_set_proxiable(opt, 0);
511 krb5_get_init_creds_opt_set_out_ccache(ctx, opt, ccache);
512 if (init_type == INIT_ANONYMOUS)
513 krb5_get_init_creds_opt_set_anonymous(opt, 1);
516 if (init_type == INIT_PASS || init_type == INIT_ANONYMOUS) {
517 code = krb5_get_init_creds_password(ctx, &outcreds, client, pass,
523 } else if (init_type == INIT_SKEY) {
525 code = krb5_kt_resolve(ctx, pass, &kt);
529 code = krb5_get_init_creds_keytab(ctx, &outcreds, client, kt,
530 0, full_svcname, opt);
532 krb5_kt_close(ctx, kt);
535 } else if (init_type == INIT_CREDS) {
536 mcreds.client = client;
537 code = krb5_parse_name(ctx, full_svcname, &mcreds.server);
540 code = krb5_cc_retrieve_cred(ctx, ccache, 0,
542 krb5_free_principal(ctx, mcreds.server);
547 krb5_free_cred_contents(ctx, &outcreds);
549 krb5_get_init_creds_opt_free(ctx, opt);
553 /* Set *fd to a socket connected to hostname and port. */
555 connect_to_server(const char *hostname, int port, int *fd)
557 struct addrinfo hint, *addrs, *a;
562 /* Look up the server's addresses. */
563 (void) snprintf(portbuf, sizeof(portbuf), "%d", port);
564 memset(&hint, 0, sizeof(hint));
565 hint.ai_socktype = SOCK_STREAM;
566 #ifdef AI_NUMERICSERV
567 hint.ai_flags = AI_NUMERICSERV;
569 err = getaddrinfo(hostname, portbuf, &hint, &addrs);
571 return KADM5_CANT_RESOLVE;
573 /* Try to connect to each address until we succeed. */
574 for (a = addrs; a != NULL; a = a->ai_next) {
575 s = socket(a->ai_family, a->ai_socktype, 0);
577 code = KADM5_FAILURE;
580 err = connect(s, a->ai_addr, a->ai_addrlen);
589 /* We didn't succeed on any address. */
590 code = KADM5_RPC_ERROR;
596 /* Acquire GSSAPI credentials and set up RPC auth flavor. */
598 setup_gss(kadm5_server_handle_t handle, kadm5_config_params *params_in,
599 char *client_name, char *full_svcname)
602 OM_uint32 gssstat, minor_stat;
604 gss_name_t gss_client;
605 gss_name_t gss_target;
606 gss_cred_id_t gss_client_creds;
607 const char *c_ccname_orig;
610 code = KADM5_GSS_ERROR;
611 gss_client_creds = GSS_C_NO_CREDENTIAL;
613 gss_client = gss_target = GSS_C_NO_NAME;
615 /* Temporarily use the kadm5 cache. */
616 gssstat = gss_krb5_ccache_name(&minor_stat, handle->cache_name,
618 if (gssstat != GSS_S_COMPLETE) {
619 code = KADM5_GSS_ERROR;
623 ccname_orig = strdup(c_ccname_orig);
627 buf.value = full_svcname;
628 buf.length = strlen((char *)buf.value) + 1;
629 gssstat = gss_import_name(&minor_stat, &buf,
630 (gss_OID) gss_nt_krb5_name, &gss_target);
631 if (gssstat != GSS_S_COMPLETE) {
632 code = KADM5_GSS_ERROR;
637 buf.value = client_name;
638 buf.length = strlen((char *)buf.value) + 1;
639 gssstat = gss_import_name(&minor_stat, &buf,
640 (gss_OID) gss_nt_krb5_name, &gss_client);
641 } else gss_client = GSS_C_NO_NAME;
643 if (gssstat != GSS_S_COMPLETE) {
644 code = KADM5_GSS_ERROR;
648 gssstat = gss_acquire_cred(&minor_stat, gss_client, 0,
649 GSS_C_NULL_OID_SET, GSS_C_INITIATE,
650 &gss_client_creds, NULL, NULL);
651 if (gssstat != GSS_S_COMPLETE) {
652 code = KADM5_GSS_ERROR;
653 #if 0 /* for debugging only */
655 OM_uint32 maj_status, min_status, message_context = 0;
656 gss_buffer_desc status_string;
658 maj_status = gss_display_status(&min_status,
664 if (maj_status == GSS_S_COMPLETE) {
665 fprintf(stderr, "MAJ: %.*s\n",
666 (int) status_string.length,
667 (char *)status_string.value);
668 gss_release_buffer(&min_status, &status_string);
671 "MAJ? gss_display_status returns 0x%lx?!\n",
672 (unsigned long) maj_status);
675 } while (message_context != 0);
677 maj_status = gss_display_status(&min_status,
683 if (maj_status == GSS_S_COMPLETE) {
684 fprintf(stderr, "MIN: %.*s\n",
685 (int) status_string.length,
686 (char *)status_string.value);
687 gss_release_buffer(&min_status, &status_string);
690 "MIN? gss_display_status returns 0x%lx?!\n",
691 (unsigned long) maj_status);
694 } while (message_context != 0);
701 * Do actual creation of RPC auth handle. Implements auth flavor
704 rpc_auth(handle, params_in, gss_client_creds, gss_target);
707 if (gss_client_creds != GSS_C_NO_CREDENTIAL)
708 (void) gss_release_cred(&minor_stat, &gss_client_creds);
711 gss_release_name(&minor_stat, &gss_client);
713 gss_release_name(&minor_stat, &gss_target);
715 /* Revert to prior gss_krb5 ccache. */
717 gssstat = gss_krb5_ccache_name(&minor_stat, ccname_orig, NULL);
719 return KADM5_GSS_ERROR;
723 gssstat = gss_krb5_ccache_name(&minor_stat, NULL, NULL);
725 return KADM5_GSS_ERROR;
729 if (handle->clnt->cl_auth == NULL) {
730 return KADM5_GSS_ERROR;
735 /* Create RPC auth handle. Do auth flavor fallback if needed. */
737 rpc_auth(kadm5_server_handle_t handle, kadm5_config_params *params_in,
738 gss_cred_id_t gss_client_creds, gss_name_t gss_target)
740 OM_uint32 gssstat, minor_stat;
741 struct rpc_gss_sec sec;
743 /* Allow unauthenticated option for testing. */
744 if (params_in != NULL && (params_in->mask & KADM5_CONFIG_NO_AUTH))
747 /* Use RPCSEC_GSS by default. */
748 if (params_in == NULL ||
749 !(params_in->mask & KADM5_CONFIG_OLD_AUTH_GSSAPI)) {
750 sec.mech = gss_mech_krb5;
751 sec.qop = GSS_C_QOP_DEFAULT;
752 sec.svc = RPCSEC_GSS_SVC_PRIVACY;
753 sec.cred = gss_client_creds;
754 sec.req_flags = GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG;
756 handle->clnt->cl_auth = authgss_create(handle->clnt,
758 if (handle->clnt->cl_auth != NULL)
762 if (params_in != NULL && (params_in->mask & KADM5_CONFIG_AUTH_NOFALLBACK))
765 /* Fall back to old AUTH_GSSAPI. */
766 handle->clnt->cl_auth = auth_gssapi_create(handle->clnt,
771 (gss_OID) gss_mech_krb5,
774 0, NULL, NULL, NULL);
778 kadm5_destroy(void *server_handle)
780 krb5_ccache ccache = NULL;
782 kadm5_server_handle_t handle =
783 (kadm5_server_handle_t) server_handle;
785 CHECK_HANDLE(server_handle);
787 if (handle->destroy_cache && handle->cache_name) {
788 if ((code = krb5_cc_resolve(handle->context,
789 handle->cache_name, &ccache)) == 0)
790 code = krb5_cc_destroy (handle->context, ccache);
792 if (handle->cache_name)
793 free(handle->cache_name);
794 if (handle->clnt && handle->clnt->cl_auth)
795 AUTH_DESTROY(handle->clnt->cl_auth);
797 clnt_destroy(handle->clnt);
799 free (handle->lhandle);
801 kadm5_free_config_params(handle->context, &handle->params);
803 handle->magic_number = 0;
808 /* not supported on client */
809 kadm5_ret_t kadm5_lock(void *server_handle)
814 /* not supported on client */
815 kadm5_ret_t kadm5_unlock(void *server_handle)
820 kadm5_ret_t kadm5_flush(void *server_handle)
825 int _kadm5_check_handle(void *handle)
827 CHECK_HANDLE(handle);
831 krb5_error_code kadm5_init_krb5_context (krb5_context *ctx)
833 return krb5_init_context(ctx);
837 * Stub function for kadmin. It was created to eliminate the dependency on
838 * libkdb's ulog functions. The srv equivalent makes the actual calls.
841 kadm5_init_iprop(void *handle, char **db_args)