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
);
81 SET(c
, iter_principals
);
86 _kadm5_c_init_context(kadm5_client_context
**ctx
,
87 kadm5_config_params
*params
,
93 *ctx
= malloc(sizeof(**ctx
));
95 return krb5_enomem(context
);
96 memset(*ctx
, 0, sizeof(**ctx
));
97 krb5_add_et_list(context
, initialize_kadm5_error_table_r
);
99 (*ctx
)->context
= context
;
100 if (params
->mask
& KADM5_CONFIG_REALM
) {
102 (*ctx
)->realm
= strdup(params
->realm
);
103 if ((*ctx
)->realm
== NULL
)
104 ret
= krb5_enomem(context
);
106 ret
= krb5_get_default_realm((*ctx
)->context
, &(*ctx
)->realm
);
113 * FIXME: If we have a hostlist, we should use the hostlist so that if we
114 * can't reach one server we try another.
116 if (params
->mask
& KADM5_CONFIG_ADMIN_SERVER
)
117 (*ctx
)->admin_server
= strdup(params
->admin_server
);
121 ret
= krb5_get_krb_admin_hst(context
, &(*ctx
)->realm
, &hostlist
);
127 (*ctx
)->admin_server
= strdup(*hostlist
);
128 krb5_free_krbhst(context
, hostlist
);
131 if ((*ctx
)->admin_server
== NULL
) {
134 return krb5_enomem(context
);
136 colon
= strchr((*ctx
)->admin_server
, ':');
140 (*ctx
)->kadmind_port
= 0;
142 if (params
->mask
& KADM5_CONFIG_KADMIND_PORT
)
143 (*ctx
)->kadmind_port
= params
->kadmind_port
;
144 else if (colon
!= NULL
) {
147 (*ctx
)->kadmind_port
= htons(strtol(colon
, &end
, 0));
149 if ((*ctx
)->kadmind_port
== 0)
150 (*ctx
)->kadmind_port
= krb5_getportbyname(context
, "kerberos-adm",
153 if (params
->mask
& KADM5_CONFIG_READONLY_ADMIN_SERVER
) {
154 (*ctx
)->readonly_admin_server
= strdup(params
->readonly_admin_server
);
155 if ((*ctx
)->readonly_admin_server
== NULL
) {
158 return krb5_enomem(context
);
163 ret
= krb5_get_krb_readonly_admin_hst(context
, &(*ctx
)->realm
,
166 (*ctx
)->readonly_admin_server
= strdup(*hostlist
);
167 krb5_free_krbhst(context
, hostlist
);
168 if ((*ctx
)->readonly_admin_server
== NULL
) {
171 return krb5_enomem(context
);
175 if ((*ctx
)->readonly_admin_server
) {
176 colon
= strchr((*ctx
)->readonly_admin_server
, ':');
184 (*ctx
)->readonly_kadmind_port
= 0;
185 if (params
->mask
& KADM5_CONFIG_READONLY_KADMIN_PORT
)
186 (*ctx
)->readonly_kadmind_port
= params
->readonly_kadmind_port
;
187 else if (colon
!= NULL
) {
190 (*ctx
)->readonly_kadmind_port
= htons(strtol(colon
, &end
, 0));
192 if ((*ctx
)->readonly_kadmind_port
== 0)
193 (*ctx
)->readonly_kadmind_port
= (*ctx
)->kadmind_port
;
198 kadm5_c_dup_context(void *vin
, void **out
)
201 kadm5_client_context
*in
= vin
;
202 krb5_context context
= in
->context
;
203 kadm5_client_context
*ctx
;
206 ctx
= malloc(sizeof(*ctx
));
208 return krb5_enomem(in
->context
);
211 memset(ctx
, 0, sizeof(*ctx
));
213 ctx
->readonly_kadmind_port
= in
->readonly_kadmind_port
;
214 ctx
->kadmind_port
= in
->kadmind_port
;
216 ret
= krb5_copy_context(context
, &(ctx
->context
));
218 ctx
->my_context
= TRUE
;
219 ret
= krb5_add_et_list(ctx
->context
, initialize_kadm5_error_table_r
);
221 if (ret
== 0 && (ctx
->realm
= strdup(in
->realm
)) == NULL
)
222 ret
= krb5_enomem(context
);
224 (ctx
->admin_server
= strdup(in
->admin_server
)) == NULL
)
225 ret
= krb5_enomem(context
);
226 if (in
->readonly_admin_server
&&
227 (ctx
->readonly_admin_server
= strdup(in
->readonly_admin_server
)) == NULL
)
228 ret
= krb5_enomem(context
);
229 if (in
->keytab
&& (ctx
->keytab
= strdup(in
->keytab
)) == NULL
)
230 ret
= krb5_enomem(context
);
232 char *fullname
= NULL
;
234 ret
= krb5_cc_get_full_name(context
, in
->ccache
, &fullname
);
236 ret
= krb5_cc_resolve(context
, fullname
, &ctx
->ccache
);
243 kadm5_c_destroy(ctx
);
247 static krb5_error_code
248 get_kadm_ticket(krb5_context context
,
250 krb5_principal client
,
251 const char *server_name
)
256 memset(&in
, 0, sizeof(in
));
258 ret
= krb5_parse_name(context
, server_name
, &in
.server
);
261 ret
= krb5_get_credentials(context
, 0, id
, &in
, &out
);
263 krb5_free_creds(context
, out
);
264 krb5_free_principal(context
, in
.server
);
268 static krb5_error_code
269 get_new_cache(krb5_context context
,
270 krb5_principal client
,
271 const char *password
,
272 krb5_prompter_fct prompter
,
274 const char *server_name
,
275 krb5_ccache
*ret_cache
)
279 krb5_get_init_creds_opt
*opt
;
282 ret
= krb5_get_init_creds_opt_alloc (context
, &opt
);
286 krb5_get_init_creds_opt_set_default_flags(context
, "kadmin",
287 krb5_principal_get_realm(context
,
292 krb5_get_init_creds_opt_set_forwardable (opt
, FALSE
);
293 krb5_get_init_creds_opt_set_proxiable (opt
, FALSE
);
295 if(password
== NULL
&& prompter
== NULL
) {
298 ret
= krb5_kt_default(context
, &kt
);
300 ret
= krb5_kt_resolve(context
, keytab
, &kt
);
302 krb5_get_init_creds_opt_free(context
, opt
);
305 ret
= krb5_get_init_creds_keytab (context
,
312 krb5_kt_close(context
, kt
);
314 ret
= krb5_get_init_creds_password (context
,
324 krb5_get_init_creds_opt_free(context
, opt
);
328 case KRB5_LIBOS_PWDINTR
: /* don't print anything if it was just C-c:ed */
329 case KRB5KRB_AP_ERR_BAD_INTEGRITY
:
330 case KRB5KRB_AP_ERR_MODIFIED
:
331 return KADM5_BAD_PASSWORD
;
335 ret
= krb5_cc_new_unique(context
, krb5_cc_type_memory
, NULL
, &id
);
338 ret
= krb5_cc_initialize (context
, id
, cred
.client
);
341 ret
= krb5_cc_store_cred (context
, id
, &cred
);
344 krb5_free_cred_contents (context
, &cred
);
350 * Check the credential cache `id´ to figure out what principal to use
351 * when talking to the kadmind. If there is a initial kadmin/admin@
352 * credential in the cache, use that client principal. Otherwise, use
353 * the client principals first component and add /admin to the
357 static krb5_error_code
358 get_cache_principal(krb5_context context
,
360 krb5_principal
*client
)
363 const char *name
, *inst
;
364 krb5_principal p1
, p2
;
366 ret
= krb5_cc_default(context
, id
);
372 ret
= krb5_cc_get_principal(context
, *id
, &p1
);
374 krb5_cc_close(context
, *id
);
379 ret
= krb5_make_principal(context
, &p2
, NULL
,
380 "kadmin", "admin", NULL
);
382 krb5_cc_close(context
, *id
);
384 krb5_free_principal(context
, p1
);
390 krb5_kdc_flags flags
;
393 memset(&in
, 0, sizeof(in
));
398 /* check for initial ticket kadmin/admin */
399 ret
= krb5_get_credentials_with_flags(context
, KRB5_GC_CACHED
, flags
,
401 krb5_free_principal(context
, p2
);
403 if (out
->flags
.b
.initial
) {
405 krb5_free_creds(context
, out
);
408 krb5_free_creds(context
, out
);
411 krb5_cc_close(context
, *id
);
414 name
= krb5_principal_get_comp_string(context
, p1
, 0);
415 inst
= krb5_principal_get_comp_string(context
, p1
, 1);
416 if(inst
== NULL
|| strcmp(inst
, "admin") != 0) {
417 ret
= krb5_make_principal(context
, &p2
, NULL
, name
, "admin", NULL
);
418 krb5_free_principal(context
, p1
);
432 _kadm5_c_get_cred_cache(krb5_context context
,
433 const char *client_name
,
434 const char *server_name
,
435 const char *password
,
436 krb5_prompter_fct prompter
,
439 krb5_ccache
*ret_cache
)
442 krb5_ccache id
= NULL
;
443 krb5_principal default_client
= NULL
, client
= NULL
;
445 /* treat empty password as NULL */
446 if(password
&& *password
== '\0')
448 if(server_name
== NULL
)
449 server_name
= KADM5_ADMIN_SERVICE
;
451 if(client_name
!= NULL
) {
452 ret
= krb5_parse_name(context
, client_name
, &client
);
459 ret
= krb5_cc_get_principal(context
, id
, &client
);
463 /* get principal from default cache, ok if this doesn't work */
465 ret
= get_cache_principal(context
, &id
, &default_client
);
468 * No client was specified by the caller and we cannot
469 * determine the client from a credentials cache.
472 const char *user
= NULL
;
476 user
= roken_get_loginname(userbuf
, sizeof(userbuf
));
479 user
= roken_get_username(userbuf
, sizeof(userbuf
));
481 krb5_set_error_message(context
, KADM5_FAILURE
, "Unable to find local user name");
482 krb5_free_principal(context
, client
);
483 return KADM5_FAILURE
;
485 ret
= krb5_make_principal(context
, &default_client
,
486 NULL
, user
, "admin", NULL
);
488 krb5_free_principal(context
, client
);
496 * No client was specified by the caller, but we have a client
497 * from the default credentials cache.
499 if (client
== NULL
&& default_client
!= NULL
)
500 client
= default_client
;
503 if(id
&& client
&& (default_client
== NULL
||
504 krb5_principal_compare(context
, client
, default_client
) != 0)) {
505 ret
= get_kadm_ticket(context
, id
, client
, server_name
);
508 krb5_free_principal(context
, default_client
);
509 if (default_client
!= client
)
510 krb5_free_principal(context
, client
);
514 /* couldn't get ticket from cache */
517 /* get creds via AS request */
518 if(id
&& (id
!= ccache
))
519 krb5_cc_close(context
, id
);
520 if (client
!= default_client
)
521 krb5_free_principal(context
, default_client
);
523 ret
= get_new_cache(context
, client
, password
, prompter
, keytab
,
524 server_name
, ret_cache
);
525 krb5_free_principal(context
, client
);
530 kadm_connect(kadm5_client_context
*ctx
)
533 krb5_principal server
= NULL
;
534 krb5_ccache cc
= NULL
;
535 rk_socket_t s
= rk_INVALID_SOCKET
;
536 struct addrinfo
*ai
, *a
;
537 struct addrinfo hints
;
541 const char *admin_server
= NULL
;
542 char portstr
[NI_MAXSERV
];
543 const char *hostname
, *slash
;
544 char *service_name
= NULL
;
545 krb5_context context
= ctx
->context
;
549 krb5_auth_con_free(context
, ctx
->ac
);
552 if (!ctx
->want_write
) {
553 admin_server
= ctx
->readonly_admin_server
;
554 kadmin_port
= ctx
->readonly_kadmind_port
;
556 if (admin_server
== NULL
) {
557 admin_server
= ctx
->admin_server
;
561 kadmin_port
= ctx
->kadmind_port
;
563 memset(&hints
, 0, sizeof(hints
));
564 hints
.ai_socktype
= SOCK_STREAM
;
565 hints
.ai_protocol
= IPPROTO_TCP
;
567 snprintf(portstr
, sizeof(portstr
), "%u", ntohs(kadmin_port
));
569 hostname
= admin_server
;
570 slash
= strchr(hostname
, '/');
572 hostname
= slash
+ 1;
574 if (krb5_config_get_bool(context
, NULL
, "libdefaults", "block_dns",
576 hints
.ai_flags
&= ~AI_CANONNAME
;
577 hints
.ai_flags
|= AI_NUMERICHOST
|AI_NUMERICSERV
;
579 error
= getaddrinfo(hostname
, portstr
, &hints
, &ai
);
581 ret
= KADM5_BAD_SERVER_NAME
;
586 for (a
= ai
; a
!= NULL
; a
= a
->ai_next
) {
587 s
= socket(a
->ai_family
, a
->ai_socktype
, a
->ai_protocol
);
590 if (connect(s
, a
->ai_addr
, a
->ai_addrlen
) < 0) {
591 krb5_warn(context
, errno
, "connect(%s)", hostname
);
593 s
= rk_INVALID_SOCKET
;
599 krb5_set_error_message(context
, ret
= KADM5_FAILURE
,
600 "failed to contact %s", hostname
);
603 ret
= _kadm5_c_get_cred_cache(context
,
606 NULL
, ctx
->prompter
, ctx
->keytab
,
612 error
= asprintf(&service_name
, "%s@%s", KADM5_ADMIN_SERVICE
,
615 error
= asprintf(&service_name
, "%s", KADM5_ADMIN_SERVICE
);
617 if (error
== -1 || service_name
== NULL
) {
618 ret
= krb5_enomem(context
);
622 ret
= krb5_parse_name(context
, service_name
, &server
);
626 ret
= krb5_sendauth(context
, &ctx
->ac
, &s
,
627 KADMIN_APPL_VERSION
, NULL
,
628 server
, AP_OPTS_MUTUAL_REQUIRED
,
629 NULL
, NULL
, cc
, NULL
, NULL
, NULL
);
632 kadm5_config_params p
;
633 memset(&p
, 0, sizeof(p
));
635 p
.mask
|= KADM5_CONFIG_REALM
;
636 p
.realm
= ctx
->realm
;
638 ret
= _kadm5_marshal_params(context
, &p
, ¶ms
);
640 ret
= krb5_write_priv_message(context
, ctx
->ac
, &s
, ¶ms
);
641 krb5_data_free(¶ms
);
647 ctx
->connected_to_writable
= !!writable
;
652 krb5_cc_close(context
, cc
);
653 krb5_free_principal(context
, server
);
657 if (s
!= rk_INVALID_SOCKET
)
659 krb5_auth_con_free(context
, ctx
->ac
);
666 _kadm5_connect(void *handle
, int want_write
)
668 kadm5_client_context
*ctx
= handle
;
671 * Reconnect? Note that we don't reconnect to read-only kadmin servers if
672 * we're already connected to a writable kadmin server because we sometimes
673 * get a principal record after writing it. We really need the application
674 * upstairs to tell us when to stop hogging writable kadmin servers.
676 * FIXME: Add an API for marking a kadm5_client_context as not needing to
677 * connect to writable kadmin servers.
679 ctx
->want_write
= !!want_write
;
680 if (ctx
->sock
!= rk_INVALID_SOCKET
&& want_write
&&
681 !ctx
->connected_to_writable
) {
682 rk_closesocket(ctx
->sock
);
683 ctx
->sock
= rk_INVALID_SOCKET
;
685 if (ctx
->sock
== rk_INVALID_SOCKET
)
686 return kadm_connect(ctx
);
691 kadm5_c_init_with_context(krb5_context context
,
692 const char *client_name
,
693 const char *password
,
694 krb5_prompter_fct prompter
,
697 const char *service_name
,
698 kadm5_config_params
*realm_params
,
699 unsigned long struct_version
,
700 unsigned long api_version
,
701 void **server_handle
)
704 kadm5_client_context
*ctx
= NULL
;
707 ret
= _kadm5_c_init_context(&ctx
, realm_params
, context
);
711 if (password
!= NULL
&& *password
!= '\0') {
712 ret
= _kadm5_c_get_cred_cache(context
,
715 password
, prompter
, keytab
, ccache
, &cc
);
717 kadm5_c_destroy(ctx
);
724 if (client_name
!= NULL
)
725 ctx
->client_name
= strdup(client_name
);
727 ctx
->client_name
= NULL
;
728 if (service_name
!= NULL
)
729 ctx
->service_name
= strdup(service_name
);
731 ctx
->service_name
= NULL
;
732 ctx
->prompter
= prompter
;
733 ctx
->keytab
= keytab
;
734 ctx
->ccache
= ccache
;
735 /* maybe we should copy the params here */
738 *server_handle
= ctx
;
743 init_context(const char *client_name
,
744 const char *password
,
745 krb5_prompter_fct prompter
,
748 const char *service_name
,
749 kadm5_config_params
*realm_params
,
750 unsigned long struct_version
,
751 unsigned long api_version
,
752 void **server_handle
)
754 krb5_context context
;
756 kadm5_server_context
*ctx
;
758 ret
= krb5_init_context(&context
);
761 ret
= kadm5_c_init_with_context(context
,
773 krb5_free_context(context
);
776 ctx
= *server_handle
;
782 kadm5_c_init_with_password_ctx(krb5_context context
,
783 const char *client_name
,
784 const char *password
,
785 const char *service_name
,
786 kadm5_config_params
*realm_params
,
787 unsigned long struct_version
,
788 unsigned long api_version
,
789 void **server_handle
)
791 return kadm5_c_init_with_context(context
,
805 kadm5_c_init_with_password(const char *client_name
,
806 const char *password
,
807 const char *service_name
,
808 kadm5_config_params
*realm_params
,
809 unsigned long struct_version
,
810 unsigned long api_version
,
811 void **server_handle
)
813 return init_context(client_name
,
826 kadm5_c_init_with_skey_ctx(krb5_context context
,
827 const char *client_name
,
829 const char *service_name
,
830 kadm5_config_params
*realm_params
,
831 unsigned long struct_version
,
832 unsigned long api_version
,
833 void **server_handle
)
835 return kadm5_c_init_with_context(context
,
850 kadm5_c_init_with_skey(const char *client_name
,
852 const char *service_name
,
853 kadm5_config_params
*realm_params
,
854 unsigned long struct_version
,
855 unsigned long api_version
,
856 void **server_handle
)
858 return init_context(client_name
,
871 kadm5_c_init_with_creds_ctx(krb5_context context
,
872 const char *client_name
,
874 const char *service_name
,
875 kadm5_config_params
*realm_params
,
876 unsigned long struct_version
,
877 unsigned long api_version
,
878 void **server_handle
)
880 return kadm5_c_init_with_context(context
,
894 kadm5_c_init_with_creds(const char *client_name
,
896 const char *service_name
,
897 kadm5_config_params
*realm_params
,
898 unsigned long struct_version
,
899 unsigned long api_version
,
900 void **server_handle
)
902 return init_context(client_name
,
916 kadm5_init(char *client_name
, char *pass
,
918 kadm5_config_params
*realm_params
,
919 unsigned long struct_version
,
920 unsigned long api_version
,
921 void **server_handle
)