2 * Copyright (c) 1997 - 2006 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
17 * 3. Neither the name of the Institute nor the names of its contributors
18 * may be used to endorse or promote products derived from this software
19 * without specific prior written permission.
21 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34 #include "kadm5_locl.h"
35 #include <sys/types.h>
36 #ifdef HAVE_SYS_SOCKET_H
37 #include <sys/socket.h>
39 #ifdef HAVE_NETINET_IN_H
40 #include <netinet/in.h>
49 kadm5_c_lock(void *server_handle)
55 kadm5_c_unlock(void *server_handle)
61 set_funcs(kadm5_client_context *c)
63 #define SET(C, F) (C)->funcs.F = kadm5 ## _c_ ## F
64 #define SETNOTIMP(C, F) (C)->funcs.F = 0
65 SET(c, chpass_principal);
66 SET(c, chpass_principal_with_key);
67 SET(c, create_principal);
68 SET(c, delete_principal);
71 SET(c, get_principal);
72 SET(c, get_principals);
74 SET(c, modify_principal);
75 SET(c, prune_principal);
76 SET(c, randkey_principal);
77 SET(c, rename_principal);
80 SETNOTIMP(c, setkey_principal_3);
84 _kadm5_c_init_context(kadm5_client_context **ctx,
85 kadm5_config_params *params,
91 *ctx = malloc(sizeof(**ctx));
93 return krb5_enomem(context);
94 memset(*ctx, 0, sizeof(**ctx));
95 krb5_add_et_list(context, initialize_kadm5_error_table_r);
97 (*ctx)->context = context;
98 if (params->mask & KADM5_CONFIG_REALM) {
100 (*ctx)->realm = strdup(params->realm);
101 if ((*ctx)->realm == NULL)
102 ret = krb5_enomem(context);
104 ret = krb5_get_default_realm((*ctx)->context, &(*ctx)->realm);
111 * FIXME: If we have a hostlist, we should use the hostlist so that if we
112 * can't reach one server we try another.
114 if (params->mask & KADM5_CONFIG_ADMIN_SERVER)
115 (*ctx)->admin_server = strdup(params->admin_server);
119 ret = krb5_get_krb_admin_hst(context, &(*ctx)->realm, &hostlist);
125 (*ctx)->admin_server = strdup(*hostlist);
126 krb5_free_krbhst(context, hostlist);
129 if ((*ctx)->admin_server == NULL) {
132 return krb5_enomem(context);
134 colon = strchr((*ctx)->admin_server, ':');
138 (*ctx)->kadmind_port = 0;
140 if (params->mask & KADM5_CONFIG_KADMIND_PORT)
141 (*ctx)->kadmind_port = params->kadmind_port;
142 else if (colon != NULL) {
145 (*ctx)->kadmind_port = htons(strtol(colon, &end, 0));
147 if ((*ctx)->kadmind_port == 0)
148 (*ctx)->kadmind_port = krb5_getportbyname(context, "kerberos-adm",
151 if (params->mask & KADM5_CONFIG_READONLY_ADMIN_SERVER) {
152 (*ctx)->readonly_admin_server = strdup(params->readonly_admin_server);
153 if ((*ctx)->readonly_admin_server == NULL) {
156 return krb5_enomem(context);
161 ret = krb5_get_krb_readonly_admin_hst(context, &(*ctx)->realm,
164 (*ctx)->readonly_admin_server = strdup(*hostlist);
165 krb5_free_krbhst(context, hostlist);
166 if ((*ctx)->readonly_admin_server == NULL) {
169 return krb5_enomem(context);
173 if ((*ctx)->readonly_admin_server) {
174 colon = strchr((*ctx)->readonly_admin_server, ':');
182 (*ctx)->readonly_kadmind_port = 0;
183 if (params->mask & KADM5_CONFIG_READONLY_KADMIN_PORT)
184 (*ctx)->readonly_kadmind_port = params->readonly_kadmind_port;
185 else if (colon != NULL) {
188 (*ctx)->readonly_kadmind_port = htons(strtol(colon, &end, 0));
190 if ((*ctx)->readonly_kadmind_port == 0)
191 (*ctx)->readonly_kadmind_port = (*ctx)->kadmind_port;
195 static krb5_error_code
196 get_kadm_ticket(krb5_context context,
198 krb5_principal client,
199 const char *server_name)
204 memset(&in, 0, sizeof(in));
206 ret = krb5_parse_name(context, server_name, &in.server);
209 ret = krb5_get_credentials(context, 0, id, &in, &out);
211 krb5_free_creds(context, out);
212 krb5_free_principal(context, in.server);
216 static krb5_error_code
217 get_new_cache(krb5_context context,
218 krb5_principal client,
219 const char *password,
220 krb5_prompter_fct prompter,
222 const char *server_name,
223 krb5_ccache *ret_cache)
227 krb5_get_init_creds_opt *opt;
230 ret = krb5_get_init_creds_opt_alloc (context, &opt);
234 krb5_get_init_creds_opt_set_default_flags(context, "kadmin",
235 krb5_principal_get_realm(context,
240 krb5_get_init_creds_opt_set_forwardable (opt, FALSE);
241 krb5_get_init_creds_opt_set_proxiable (opt, FALSE);
243 if(password == NULL && prompter == NULL) {
246 ret = krb5_kt_default(context, &kt);
248 ret = krb5_kt_resolve(context, keytab, &kt);
250 krb5_get_init_creds_opt_free(context, opt);
253 ret = krb5_get_init_creds_keytab (context,
260 krb5_kt_close(context, kt);
262 ret = krb5_get_init_creds_password (context,
272 krb5_get_init_creds_opt_free(context, opt);
276 case KRB5_LIBOS_PWDINTR: /* don't print anything if it was just C-c:ed */
277 case KRB5KRB_AP_ERR_BAD_INTEGRITY:
278 case KRB5KRB_AP_ERR_MODIFIED:
279 return KADM5_BAD_PASSWORD;
283 ret = krb5_cc_new_unique(context, krb5_cc_type_memory, NULL, &id);
286 ret = krb5_cc_initialize (context, id, cred.client);
289 ret = krb5_cc_store_cred (context, id, &cred);
292 krb5_free_cred_contents (context, &cred);
298 * Check the credential cache `id´ to figure out what principal to use
299 * when talking to the kadmind. If there is a initial kadmin/admin@
300 * credential in the cache, use that client principal. Otherwise, use
301 * the client principals first component and add /admin to the
305 static krb5_error_code
306 get_cache_principal(krb5_context context,
308 krb5_principal *client)
311 const char *name, *inst;
312 krb5_principal p1, p2;
314 ret = krb5_cc_default(context, id);
320 ret = krb5_cc_get_principal(context, *id, &p1);
322 krb5_cc_close(context, *id);
327 ret = krb5_make_principal(context, &p2, NULL,
328 "kadmin", "admin", NULL);
330 krb5_cc_close(context, *id);
332 krb5_free_principal(context, p1);
338 krb5_kdc_flags flags;
341 memset(&in, 0, sizeof(in));
346 /* check for initial ticket kadmin/admin */
347 ret = krb5_get_credentials_with_flags(context, KRB5_GC_CACHED, flags,
349 krb5_free_principal(context, p2);
351 if (out->flags.b.initial) {
353 krb5_free_creds(context, out);
356 krb5_free_creds(context, out);
359 krb5_cc_close(context, *id);
362 name = krb5_principal_get_comp_string(context, p1, 0);
363 inst = krb5_principal_get_comp_string(context, p1, 1);
364 if(inst == NULL || strcmp(inst, "admin") != 0) {
365 ret = krb5_make_principal(context, &p2, NULL, name, "admin", NULL);
366 krb5_free_principal(context, p1);
380 _kadm5_c_get_cred_cache(krb5_context context,
381 const char *client_name,
382 const char *server_name,
383 const char *password,
384 krb5_prompter_fct prompter,
387 krb5_ccache *ret_cache)
390 krb5_ccache id = NULL;
391 krb5_principal default_client = NULL, client = NULL;
393 /* treat empty password as NULL */
394 if(password && *password == '\0')
396 if(server_name == NULL)
397 server_name = KADM5_ADMIN_SERVICE;
399 if(client_name != NULL) {
400 ret = krb5_parse_name(context, client_name, &client);
407 ret = krb5_cc_get_principal(context, id, &client);
411 /* get principal from default cache, ok if this doesn't work */
413 ret = get_cache_principal(context, &id, &default_client);
416 * No client was specified by the caller and we cannot
417 * determine the client from a credentials cache.
420 const char *user = NULL;
424 user = roken_get_loginname(userbuf, sizeof(userbuf));
427 user = roken_get_username(userbuf, sizeof(userbuf));
429 krb5_set_error_message(context, KADM5_FAILURE, "Unable to find local user name");
430 return KADM5_FAILURE;
432 ret = krb5_make_principal(context, &default_client,
433 NULL, user, "admin", NULL);
441 * No client was specified by the caller, but we have a client
442 * from the default credentials cache.
444 if (client == NULL && default_client != NULL)
445 client = default_client;
448 if(id && client && (default_client == NULL ||
449 krb5_principal_compare(context, client, default_client) != 0)) {
450 ret = get_kadm_ticket(context, id, client, server_name);
453 krb5_free_principal(context, default_client);
454 if (default_client != client)
455 krb5_free_principal(context, client);
459 /* couldn't get ticket from cache */
462 /* get creds via AS request */
463 if(id && (id != ccache))
464 krb5_cc_close(context, id);
465 if (client != default_client)
466 krb5_free_principal(context, default_client);
468 ret = get_new_cache(context, client, password, prompter, keytab,
469 server_name, ret_cache);
470 krb5_free_principal(context, client);
475 kadm_connect(kadm5_client_context *ctx)
478 krb5_principal server = NULL;
479 krb5_ccache cc = NULL;
480 rk_socket_t s = rk_INVALID_SOCKET;
481 struct addrinfo *ai, *a;
482 struct addrinfo hints;
486 const char *admin_server = NULL;
487 char portstr[NI_MAXSERV];
488 const char *hostname, *slash;
489 char *service_name = NULL;
490 krb5_context context = ctx->context;
494 krb5_auth_con_free(context, ctx->ac);
497 if (!ctx->want_write) {
498 admin_server = ctx->readonly_admin_server;
499 kadmin_port = ctx->readonly_kadmind_port;
501 if (admin_server == NULL) {
502 admin_server = ctx->admin_server;
506 kadmin_port = ctx->kadmind_port;
508 memset(&hints, 0, sizeof(hints));
509 hints.ai_socktype = SOCK_STREAM;
510 hints.ai_protocol = IPPROTO_TCP;
512 snprintf(portstr, sizeof(portstr), "%u", ntohs(ctx->kadmind_port));
514 hostname = ctx->admin_server;
515 slash = strchr(hostname, '/');
517 hostname = slash + 1;
519 error = getaddrinfo(hostname, portstr, &hints, &ai);
521 ret = KADM5_BAD_SERVER_NAME;
526 for (a = ai; a != NULL; a = a->ai_next) {
527 s = socket(a->ai_family, a->ai_socktype, a->ai_protocol);
530 if (connect(s, a->ai_addr, a->ai_addrlen) < 0) {
531 krb5_warn(context, errno, "connect(%s)", hostname);
538 krb5_set_error_message(context, ret = KADM5_FAILURE,
539 "failed to contact %s", hostname);
542 ret = _kadm5_c_get_cred_cache(context,
545 NULL, ctx->prompter, ctx->keytab,
551 error = asprintf(&service_name, "%s@%s", KADM5_ADMIN_SERVICE,
554 error = asprintf(&service_name, "%s", KADM5_ADMIN_SERVICE);
556 if (error == -1 || service_name == NULL) {
557 ret = krb5_enomem(context);
561 ret = krb5_parse_name(context, service_name, &server);
565 ret = krb5_sendauth(context, &ctx->ac, &s,
566 KADMIN_APPL_VERSION, NULL,
567 server, AP_OPTS_MUTUAL_REQUIRED,
568 NULL, NULL, cc, NULL, NULL, NULL);
571 kadm5_config_params p;
572 memset(&p, 0, sizeof(p));
574 p.mask |= KADM5_CONFIG_REALM;
575 p.realm = ctx->realm;
577 ret = _kadm5_marshal_params(context, &p, ¶ms);
579 ret = krb5_write_priv_message(context, ctx->ac, &s, ¶ms);
580 krb5_data_free(¶ms);
586 ctx->connected_to_writable = !!writable;
591 krb5_cc_close(context, cc);
592 krb5_free_principal(context, server);
596 if (s != rk_INVALID_SOCKET)
598 krb5_auth_con_free(context, ctx->ac);
605 _kadm5_connect(void *handle, int want_write)
607 kadm5_client_context *ctx = handle;
610 * Reconnect? Note that we don't reconnect to read-only kadmin servers if
611 * we're already connected to a writable kadmin server because we sometimes
612 * get a principal record after writing it. We really need the application
613 * upstairs to tell us when to stop hogging writable kadmin servers.
615 * FIXME: Add an API for marking a kadm5_client_context as not needing to
616 * connect to writable kadmin servers.
618 ctx->want_write = !!want_write;
619 if (ctx->sock != rk_INVALID_SOCKET && want_write &&
620 !ctx->connected_to_writable) {
621 rk_closesocket(ctx->sock);
622 ctx->sock = rk_INVALID_SOCKET;
624 if (ctx->sock == rk_INVALID_SOCKET)
625 return kadm_connect(ctx);
630 kadm5_c_init_with_context(krb5_context context,
631 const char *client_name,
632 const char *password,
633 krb5_prompter_fct prompter,
636 const char *service_name,
637 kadm5_config_params *realm_params,
638 unsigned long struct_version,
639 unsigned long api_version,
640 void **server_handle)
643 kadm5_client_context *ctx;
646 ret = _kadm5_c_init_context(&ctx, realm_params, context);
650 if (password != NULL && *password != '\0') {
651 ret = _kadm5_c_get_cred_cache(context,
654 password, prompter, keytab, ccache, &cc);
656 kadm5_c_destroy(ctx);
663 if (client_name != NULL)
664 ctx->client_name = strdup(client_name);
666 ctx->client_name = NULL;
667 if (service_name != NULL)
668 ctx->service_name = strdup(service_name);
670 ctx->service_name = NULL;
671 ctx->prompter = prompter;
672 ctx->keytab = keytab;
673 ctx->ccache = ccache;
674 /* maybe we should copy the params here */
677 *server_handle = ctx;
682 init_context(const char *client_name,
683 const char *password,
684 krb5_prompter_fct prompter,
687 const char *service_name,
688 kadm5_config_params *realm_params,
689 unsigned long struct_version,
690 unsigned long api_version,
691 void **server_handle)
693 krb5_context context;
695 kadm5_server_context *ctx;
697 ret = krb5_init_context(&context);
700 ret = kadm5_c_init_with_context(context,
712 krb5_free_context(context);
715 ctx = *server_handle;
721 kadm5_c_init_with_password_ctx(krb5_context context,
722 const char *client_name,
723 const char *password,
724 const char *service_name,
725 kadm5_config_params *realm_params,
726 unsigned long struct_version,
727 unsigned long api_version,
728 void **server_handle)
730 return kadm5_c_init_with_context(context,
744 kadm5_c_init_with_password(const char *client_name,
745 const char *password,
746 const char *service_name,
747 kadm5_config_params *realm_params,
748 unsigned long struct_version,
749 unsigned long api_version,
750 void **server_handle)
752 return init_context(client_name,
765 kadm5_c_init_with_skey_ctx(krb5_context context,
766 const char *client_name,
768 const char *service_name,
769 kadm5_config_params *realm_params,
770 unsigned long struct_version,
771 unsigned long api_version,
772 void **server_handle)
774 return kadm5_c_init_with_context(context,
789 kadm5_c_init_with_skey(const char *client_name,
791 const char *service_name,
792 kadm5_config_params *realm_params,
793 unsigned long struct_version,
794 unsigned long api_version,
795 void **server_handle)
797 return init_context(client_name,
810 kadm5_c_init_with_creds_ctx(krb5_context context,
811 const char *client_name,
813 const char *service_name,
814 kadm5_config_params *realm_params,
815 unsigned long struct_version,
816 unsigned long api_version,
817 void **server_handle)
819 return kadm5_c_init_with_context(context,
833 kadm5_c_init_with_creds(const char *client_name,
835 const char *service_name,
836 kadm5_config_params *realm_params,
837 unsigned long struct_version,
838 unsigned long api_version,
839 void **server_handle)
841 return init_context(client_name,
855 kadm5_init(char *client_name, char *pass,
857 kadm5_config_params *realm_params,
858 unsigned long struct_version,
859 unsigned long api_version,
860 void **server_handle)