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