xref: /freebsd/crypto/heimdal/lib/kadm5/init_c.c (revision 090bc474c990f2be7db7660523576361508ef36d)
1 /*
2  * Copyright (c) 1997 - 2003 Kungliga Tekniska H�gskolan
3  * (Royal Institute of Technology, Stockholm, Sweden).
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  *
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  *
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.
16  *
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.
20  *
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
31  * SUCH DAMAGE.
32  */
33 
34 #include "kadm5_locl.h"
35 #include <sys/types.h>
36 #include <sys/socket.h>
37 #include <netinet/in.h>
38 #include <netdb.h>
39 
40 RCSID("$Id: init_c.c,v 1.45.2.1 2003/12/21 22:48:13 lha Exp $");
41 
42 static void
43 set_funcs(kadm5_client_context *c)
44 {
45 #define SET(C, F) (C)->funcs.F = kadm5 ## _c_ ## F
46     SET(c, chpass_principal);
47     SET(c, chpass_principal_with_key);
48     SET(c, create_principal);
49     SET(c, delete_principal);
50     SET(c, destroy);
51     SET(c, flush);
52     SET(c, get_principal);
53     SET(c, get_principals);
54     SET(c, get_privs);
55     SET(c, modify_principal);
56     SET(c, randkey_principal);
57     SET(c, rename_principal);
58 }
59 
60 kadm5_ret_t
61 _kadm5_c_init_context(kadm5_client_context **ctx,
62 		      kadm5_config_params *params,
63 		      krb5_context context)
64 {
65     krb5_error_code ret;
66     char *colon;
67 
68     *ctx = malloc(sizeof(**ctx));
69     if(*ctx == NULL)
70 	return ENOMEM;
71     memset(*ctx, 0, sizeof(**ctx));
72     krb5_add_et_list (context, initialize_kadm5_error_table_r);
73     set_funcs(*ctx);
74     (*ctx)->context = context;
75     if(params->mask & KADM5_CONFIG_REALM) {
76 	ret = 0;
77 	(*ctx)->realm = strdup(params->realm);
78 	if ((*ctx)->realm == NULL)
79 	    ret = ENOMEM;
80     } else
81 	ret = krb5_get_default_realm((*ctx)->context, &(*ctx)->realm);
82     if (ret) {
83 	free(*ctx);
84 	return ret;
85     }
86     if(params->mask & KADM5_CONFIG_ADMIN_SERVER)
87 	(*ctx)->admin_server = strdup(params->admin_server);
88     else {
89 	char **hostlist;
90 
91 	ret = krb5_get_krb_admin_hst (context, &(*ctx)->realm, &hostlist);
92 	if (ret) {
93 	    free((*ctx)->realm);
94 	    free(*ctx);
95 	    return ret;
96 	}
97 	(*ctx)->admin_server = strdup(*hostlist);
98 	krb5_free_krbhst (context, hostlist);
99     }
100 
101     if ((*ctx)->admin_server == NULL) {
102 	return ENOMEM;
103 	free((*ctx)->realm);
104 	free(*ctx);
105     }
106     colon = strchr ((*ctx)->admin_server, ':');
107     if (colon != NULL)
108 	*colon++ = '\0';
109 
110     (*ctx)->kadmind_port = 0;
111 
112     if(params->mask & KADM5_CONFIG_KADMIND_PORT)
113 	(*ctx)->kadmind_port = params->kadmind_port;
114     else if (colon != NULL) {
115 	char *end;
116 
117 	(*ctx)->kadmind_port = htons(strtol (colon, &end, 0));
118     }
119     if ((*ctx)->kadmind_port == 0)
120 	(*ctx)->kadmind_port = krb5_getportbyname (context, "kerberos-adm",
121 						   "tcp", 749);
122     return 0;
123 }
124 
125 static krb5_error_code
126 get_kadm_ticket(krb5_context context,
127 		krb5_ccache id,
128 		krb5_principal client,
129 		const char *server_name)
130 {
131     krb5_error_code ret;
132     krb5_creds in, *out;
133 
134     memset(&in, 0, sizeof(in));
135     in.client = client;
136     ret = krb5_parse_name(context, server_name, &in.server);
137     if(ret)
138 	return ret;
139     ret = krb5_get_credentials(context, 0, id, &in, &out);
140     if(ret == 0)
141 	krb5_free_creds(context, out);
142     krb5_free_principal(context, in.server);
143     return ret;
144 }
145 
146 static krb5_error_code
147 get_new_cache(krb5_context context,
148 	      krb5_principal client,
149 	      const char *password,
150 	      krb5_prompter_fct prompter,
151 	      const char *keytab,
152 	      const char *server_name,
153 	      krb5_ccache *ret_cache)
154 {
155     krb5_error_code ret;
156     krb5_creds cred;
157     krb5_get_init_creds_opt opt;
158     krb5_ccache id;
159 
160     krb5_get_init_creds_opt_init (&opt);
161 
162     krb5_get_init_creds_opt_set_default_flags(context, "kadmin",
163 					      krb5_principal_get_realm(context,
164 								       client),
165 					      &opt);
166 
167 
168     krb5_get_init_creds_opt_set_forwardable (&opt, FALSE);
169     krb5_get_init_creds_opt_set_proxiable (&opt, FALSE);
170 
171     if(password == NULL && prompter == NULL) {
172 	krb5_keytab kt;
173 	if(keytab == NULL)
174 	    ret = krb5_kt_default(context, &kt);
175 	else
176 	    ret = krb5_kt_resolve(context, keytab, &kt);
177 	if(ret)
178 	    return ret;
179 	ret = krb5_get_init_creds_keytab (context,
180 					  &cred,
181 					  client,
182 					  kt,
183 					  0,
184 					  server_name,
185 					  &opt);
186 	krb5_kt_close(context, kt);
187     } else {
188 	ret = krb5_get_init_creds_password (context,
189 					    &cred,
190 					    client,
191 					    password,
192 					    prompter,
193 					    NULL,
194 					    0,
195 					    server_name,
196 					    &opt);
197     }
198     switch(ret){
199     case 0:
200 	break;
201     case KRB5_LIBOS_PWDINTR:	/* don't print anything if it was just C-c:ed */
202     case KRB5KRB_AP_ERR_BAD_INTEGRITY:
203     case KRB5KRB_AP_ERR_MODIFIED:
204 	return KADM5_BAD_PASSWORD;
205     default:
206 	return ret;
207     }
208     ret = krb5_cc_gen_new(context, &krb5_mcc_ops, &id);
209     if(ret)
210 	return ret;
211     ret = krb5_cc_initialize (context, id, cred.client);
212     if (ret)
213 	return ret;
214     ret = krb5_cc_store_cred (context, id, &cred);
215     if (ret)
216 	return ret;
217     krb5_free_creds_contents (context, &cred);
218     *ret_cache = id;
219     return 0;
220 }
221 
222 static krb5_error_code
223 get_cred_cache(krb5_context context,
224 	       const char *client_name,
225 	       const char *server_name,
226 	       const char *password,
227 	       krb5_prompter_fct prompter,
228 	       const char *keytab,
229 	       krb5_ccache ccache,
230 	       krb5_ccache *ret_cache)
231 {
232     krb5_error_code ret;
233     krb5_ccache id = NULL;
234     krb5_principal default_client = NULL, client = NULL;
235 
236     /* treat empty password as NULL */
237     if(password && *password == '\0')
238 	password = NULL;
239     if(server_name == NULL)
240 	server_name = KADM5_ADMIN_SERVICE;
241 
242     if(client_name != NULL) {
243 	ret = krb5_parse_name(context, client_name, &client);
244 	if(ret)
245 	    return ret;
246     }
247 
248     if(password != NULL || prompter != NULL) {
249 	/* get principal from default cache, ok if this doesn't work */
250 	ret = krb5_cc_default(context, &id);
251 	if(ret == 0) {
252 	    ret = krb5_cc_get_principal(context, id, &default_client);
253 	    if(ret) {
254 		krb5_cc_close(context, id);
255 		id = NULL;
256 	    } else {
257 		const char *name, *inst;
258 		krb5_principal tmp;
259 		name = krb5_principal_get_comp_string(context,
260 						      default_client, 0);
261 		inst = krb5_principal_get_comp_string(context,
262 						      default_client, 1);
263 		if(inst == NULL || strcmp(inst, "admin") != 0) {
264 		    ret = krb5_make_principal(context, &tmp, NULL,
265 					      name, "admin", NULL);
266 		    if(ret != 0) {
267 			krb5_free_principal(context, default_client);
268 			krb5_cc_close(context, id);
269 			return ret;
270 		    }
271 		    krb5_free_principal(context, default_client);
272 		    default_client = tmp;
273 		    krb5_cc_close(context, id);
274 		    id = NULL;
275 		}
276 	    }
277 	}
278 
279 	if (client != NULL) {
280 	    /* A client was specified by the caller. */
281 	    if (default_client != NULL) {
282 		krb5_free_principal(context, default_client);
283 		default_client = NULL;
284 	    }
285 	}
286 	else if (default_client != NULL)
287 	    /* No client was specified by the caller, but we have a
288 	     * client from the default credentials cache.
289 	     */
290 	    client = default_client;
291 	else {
292 	    /* No client was specified by the caller and we cannot determine
293 	     * the client from a credentials cache.
294 	     */
295 	    const char *user;
296 
297 	    user = get_default_username ();
298 
299 	    if(user == NULL)
300 		return KADM5_FAILURE;
301 	    ret = krb5_make_principal(context, &client,
302 				      NULL, user, "admin", NULL);
303 	    if(ret)
304 		return ret;
305 	    if (id != NULL) {
306 		krb5_cc_close(context, id);
307 		id = NULL;
308 	    }
309 	}
310     } else if(ccache != NULL)
311 	id = ccache;
312 
313     if(id && (default_client == NULL ||
314 	      krb5_principal_compare(context, client, default_client))) {
315 	ret = get_kadm_ticket(context, id, client, server_name);
316 	if(ret == 0) {
317 	    *ret_cache = id;
318 	    krb5_free_principal(context, default_client);
319 	    if (default_client != client)
320 		krb5_free_principal(context, client);
321 	    return 0;
322 	}
323 	if(ccache != NULL)
324 	    /* couldn't get ticket from cache */
325 	    return -1;
326     }
327     /* get creds via AS request */
328     if(id)
329 	krb5_cc_close(context, id);
330     if (client != default_client)
331 	krb5_free_principal(context, default_client);
332 
333     ret = get_new_cache(context, client, password, prompter, keytab,
334 			server_name, ret_cache);
335     krb5_free_principal(context, client);
336     return ret;
337 }
338 
339 static kadm5_ret_t
340 kadm_connect(kadm5_client_context *ctx)
341 {
342     kadm5_ret_t ret;
343     krb5_principal server;
344     krb5_ccache cc;
345     int s;
346     struct addrinfo *ai, *a;
347     struct addrinfo hints;
348     int error;
349     char portstr[NI_MAXSERV];
350     char *hostname, *slash;
351     char *service_name;
352     krb5_context context = ctx->context;
353 
354     memset (&hints, 0, sizeof(hints));
355     hints.ai_socktype = SOCK_STREAM;
356     hints.ai_protocol = IPPROTO_TCP;
357 
358     snprintf (portstr, sizeof(portstr), "%u", ntohs(ctx->kadmind_port));
359 
360     hostname = ctx->admin_server;
361     slash = strchr (hostname, '/');
362     if (slash != NULL)
363 	hostname = slash + 1;
364 
365     error = getaddrinfo (hostname, portstr, &hints, &ai);
366     if (error)
367 	return KADM5_BAD_SERVER_NAME;
368 
369     for (a = ai; a != NULL; a = a->ai_next) {
370 	s = socket (a->ai_family, a->ai_socktype, a->ai_protocol);
371 	if (s < 0)
372 	    continue;
373 	if (connect (s, a->ai_addr, a->ai_addrlen) < 0) {
374 	    krb5_warn (context, errno, "connect(%s)", hostname);
375 	    close (s);
376 	    continue;
377 	}
378 	break;
379     }
380     if (a == NULL) {
381 	freeaddrinfo (ai);
382 	krb5_warnx (context, "failed to contact %s", hostname);
383 	return KADM5_FAILURE;
384     }
385     ret = get_cred_cache(context, ctx->client_name, ctx->service_name,
386 			 NULL, ctx->prompter, ctx->keytab,
387 			 ctx->ccache, &cc);
388 
389     if(ret) {
390 	freeaddrinfo (ai);
391 	close(s);
392 	return ret;
393     }
394 
395     if (ctx->realm)
396 	asprintf(&service_name, "%s@%s", KADM5_ADMIN_SERVICE, ctx->realm);
397     else
398 	asprintf(&service_name, "%s", KADM5_ADMIN_SERVICE);
399 
400     if (service_name == NULL) {
401 	freeaddrinfo (ai);
402 	close(s);
403 	return ENOMEM;
404     }
405 
406     ret = krb5_parse_name(context, service_name, &server);
407     free(service_name);
408     if(ret) {
409 	freeaddrinfo (ai);
410 	if(ctx->ccache == NULL)
411 	    krb5_cc_close(context, cc);
412 	close(s);
413 	return ret;
414     }
415     ctx->ac = NULL;
416 
417     ret = krb5_sendauth(context, &ctx->ac, &s,
418 			KADMIN_APPL_VERSION, NULL,
419 			server, AP_OPTS_MUTUAL_REQUIRED,
420 			NULL, NULL, cc, NULL, NULL, NULL);
421     if(ret == 0) {
422 	krb5_data params;
423 	kadm5_config_params p;
424 	memset(&p, 0, sizeof(p));
425 	if(ctx->realm) {
426 	    p.mask |= KADM5_CONFIG_REALM;
427 	    p.realm = ctx->realm;
428 	}
429 	ret = _kadm5_marshal_params(context, &p, &params);
430 
431 	ret = krb5_write_priv_message(context, ctx->ac, &s, &params);
432 	krb5_data_free(&params);
433 	if(ret) {
434 	    freeaddrinfo (ai);
435 	    close(s);
436 	    if(ctx->ccache == NULL)
437 		krb5_cc_close(context, cc);
438 	    return ret;
439 	}
440     } else if(ret == KRB5_SENDAUTH_BADAPPLVERS) {
441 	close(s);
442 
443 	s = socket (a->ai_family, a->ai_socktype, a->ai_protocol);
444 	if (s < 0) {
445 	    freeaddrinfo (ai);
446 	    return errno;
447 	}
448 	if (connect (s, a->ai_addr, a->ai_addrlen) < 0) {
449 	    close (s);
450 	    freeaddrinfo (ai);
451 	    return errno;
452 	}
453 	ret = krb5_sendauth(context, &ctx->ac, &s,
454 			    KADMIN_OLD_APPL_VERSION, NULL,
455 			    server, AP_OPTS_MUTUAL_REQUIRED,
456 			    NULL, NULL, cc, NULL, NULL, NULL);
457     }
458     freeaddrinfo (ai);
459     if(ret) {
460 	close(s);
461 	return ret;
462     }
463 
464     krb5_free_principal(context, server);
465     if(ctx->ccache == NULL)
466 	krb5_cc_close(context, cc);
467     if(ret) {
468 	close(s);
469 	return ret;
470     }
471     ctx->sock = s;
472 
473     return 0;
474 }
475 
476 kadm5_ret_t
477 _kadm5_connect(void *handle)
478 {
479     kadm5_client_context *ctx = handle;
480     if(ctx->sock == -1)
481 	return kadm_connect(ctx);
482     return 0;
483 }
484 
485 static kadm5_ret_t
486 kadm5_c_init_with_context(krb5_context context,
487 			  const char *client_name,
488 			  const char *password,
489 			  krb5_prompter_fct prompter,
490 			  const char *keytab,
491 			  krb5_ccache ccache,
492 			  const char *service_name,
493 			  kadm5_config_params *realm_params,
494 			  unsigned long struct_version,
495 			  unsigned long api_version,
496 			  void **server_handle)
497 {
498     kadm5_ret_t ret;
499     kadm5_client_context *ctx;
500     krb5_ccache cc;
501 
502     ret = _kadm5_c_init_context(&ctx, realm_params, context);
503     if(ret)
504 	return ret;
505 
506     if(password != NULL && *password != '\0') {
507 	ret = get_cred_cache(context, client_name, service_name,
508 			     password, prompter, keytab, ccache, &cc);
509 	if(ret)
510 	    return ret; /* XXX */
511 	ccache = cc;
512     }
513 
514 
515     if (client_name != NULL)
516 	ctx->client_name = strdup(client_name);
517     else
518 	ctx->client_name = NULL;
519     if (service_name != NULL)
520 	ctx->service_name = strdup(service_name);
521     else
522 	ctx->service_name = NULL;
523     ctx->prompter = prompter;
524     ctx->keytab = keytab;
525     ctx->ccache = ccache;
526     /* maybe we should copy the params here */
527     ctx->sock = -1;
528 
529     *server_handle = ctx;
530     return 0;
531 }
532 
533 static kadm5_ret_t
534 init_context(const char *client_name,
535 	     const char *password,
536 	     krb5_prompter_fct prompter,
537 	     const char *keytab,
538 	     krb5_ccache ccache,
539 	     const char *service_name,
540 	     kadm5_config_params *realm_params,
541 	     unsigned long struct_version,
542 	     unsigned long api_version,
543 	     void **server_handle)
544 {
545     krb5_context context;
546     kadm5_ret_t ret;
547     kadm5_server_context *ctx;
548 
549     ret = krb5_init_context(&context);
550     if (ret)
551 	return ret;
552     ret = kadm5_c_init_with_context(context,
553 				    client_name,
554 				    password,
555 				    prompter,
556 				    keytab,
557 				    ccache,
558 				    service_name,
559 				    realm_params,
560 				    struct_version,
561 				    api_version,
562 				    server_handle);
563     if(ret){
564 	krb5_free_context(context);
565 	return ret;
566     }
567     ctx = *server_handle;
568     ctx->my_context = 1;
569     return 0;
570 }
571 
572 kadm5_ret_t
573 kadm5_c_init_with_password_ctx(krb5_context context,
574 			       const char *client_name,
575 			       const char *password,
576 			       const char *service_name,
577 			       kadm5_config_params *realm_params,
578 			       unsigned long struct_version,
579 			       unsigned long api_version,
580 			       void **server_handle)
581 {
582     return kadm5_c_init_with_context(context,
583 				     client_name,
584 				     password,
585 				     krb5_prompter_posix,
586 				     NULL,
587 				     NULL,
588 				     service_name,
589 				     realm_params,
590 				     struct_version,
591 				     api_version,
592 				     server_handle);
593 }
594 
595 kadm5_ret_t
596 kadm5_c_init_with_password(const char *client_name,
597 			   const char *password,
598 			   const char *service_name,
599 			   kadm5_config_params *realm_params,
600 			   unsigned long struct_version,
601 			   unsigned long api_version,
602 			   void **server_handle)
603 {
604     return init_context(client_name,
605 			password,
606 			krb5_prompter_posix,
607 			NULL,
608 			NULL,
609 			service_name,
610 			realm_params,
611 			struct_version,
612 			api_version,
613 			server_handle);
614 }
615 
616 kadm5_ret_t
617 kadm5_c_init_with_skey_ctx(krb5_context context,
618 			   const char *client_name,
619 			   const char *keytab,
620 			   const char *service_name,
621 			   kadm5_config_params *realm_params,
622 			   unsigned long struct_version,
623 			   unsigned long api_version,
624 			   void **server_handle)
625 {
626     return kadm5_c_init_with_context(context,
627 				     client_name,
628 				     NULL,
629 				     NULL,
630 				     keytab,
631 				     NULL,
632 				     service_name,
633 				     realm_params,
634 				     struct_version,
635 				     api_version,
636 				     server_handle);
637 }
638 
639 
640 kadm5_ret_t
641 kadm5_c_init_with_skey(const char *client_name,
642 		     const char *keytab,
643 		     const char *service_name,
644 		     kadm5_config_params *realm_params,
645 		     unsigned long struct_version,
646 		     unsigned long api_version,
647 		     void **server_handle)
648 {
649     return init_context(client_name,
650 			NULL,
651 			NULL,
652 			keytab,
653 			NULL,
654 			service_name,
655 			realm_params,
656 			struct_version,
657 			api_version,
658 			server_handle);
659 }
660 
661 kadm5_ret_t
662 kadm5_c_init_with_creds_ctx(krb5_context context,
663 			    const char *client_name,
664 			    krb5_ccache ccache,
665 			    const char *service_name,
666 			    kadm5_config_params *realm_params,
667 			    unsigned long struct_version,
668 			    unsigned long api_version,
669 			    void **server_handle)
670 {
671     return kadm5_c_init_with_context(context,
672 				     client_name,
673 				     NULL,
674 				     NULL,
675 				     NULL,
676 				     ccache,
677 				     service_name,
678 				     realm_params,
679 				     struct_version,
680 				     api_version,
681 				     server_handle);
682 }
683 
684 kadm5_ret_t
685 kadm5_c_init_with_creds(const char *client_name,
686 			krb5_ccache ccache,
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)
692 {
693     return init_context(client_name,
694 			NULL,
695 			NULL,
696 			NULL,
697 			ccache,
698 			service_name,
699 			realm_params,
700 			struct_version,
701 			api_version,
702 			server_handle);
703 }
704 
705 #if 0
706 kadm5_ret_t
707 kadm5_init(char *client_name, char *pass,
708 	   char *service_name,
709 	   kadm5_config_params *realm_params,
710 	   unsigned long struct_version,
711 	   unsigned long api_version,
712 	   void **server_handle)
713 {
714 }
715 #endif
716 
717