init_c.c revision 120945
1178476Sjb/*
2178476Sjb * Copyright (c) 1997 - 2003 Kungliga Tekniska H�gskolan
3178476Sjb * (Royal Institute of Technology, Stockholm, Sweden).
4178476Sjb * All rights reserved.
5178476Sjb *
6178476Sjb * Redistribution and use in source and binary forms, with or without
7178476Sjb * modification, are permitted provided that the following conditions
8178476Sjb * are met:
9178476Sjb *
10178476Sjb * 1. Redistributions of source code must retain the above copyright
11178476Sjb *    notice, this list of conditions and the following disclaimer.
12178476Sjb *
13178476Sjb * 2. Redistributions in binary form must reproduce the above copyright
14178476Sjb *    notice, this list of conditions and the following disclaimer in the
15178476Sjb *    documentation and/or other materials provided with the distribution.
16178476Sjb *
17178476Sjb * 3. Neither the name of the Institute nor the names of its contributors
18178476Sjb *    may be used to endorse or promote products derived from this software
19178476Sjb *    without specific prior written permission.
20178476Sjb *
21178476Sjb * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
22178476Sjb * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23178476Sjb * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24178476Sjb * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
25178476Sjb * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26178476Sjb * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27178476Sjb * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28178476Sjb * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29178476Sjb * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30178476Sjb * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31178476Sjb * SUCH DAMAGE.
32178476Sjb */
33178476Sjb
34178476Sjb#include "kadm5_locl.h"
35178476Sjb#include <sys/types.h>
36178476Sjb#include <sys/socket.h>
37178476Sjb#include <netinet/in.h>
38178476Sjb#include <netdb.h>
39178476Sjb
40178476SjbRCSID("$Id: init_c.c,v 1.45 2003/04/01 15:06:41 lha Exp $");
41178476Sjb
42178476Sjbstatic void
43178476Sjbset_funcs(kadm5_client_context *c)
44178476Sjb{
45178476Sjb#define SET(C, F) (C)->funcs.F = kadm5 ## _c_ ## F
46178476Sjb    SET(c, chpass_principal);
47178476Sjb    SET(c, chpass_principal_with_key);
48178476Sjb    SET(c, create_principal);
49178476Sjb    SET(c, delete_principal);
50178476Sjb    SET(c, destroy);
51178476Sjb    SET(c, flush);
52178476Sjb    SET(c, get_principal);
53178476Sjb    SET(c, get_principals);
54178476Sjb    SET(c, get_privs);
55178476Sjb    SET(c, modify_principal);
56178476Sjb    SET(c, randkey_principal);
57178476Sjb    SET(c, rename_principal);
58178476Sjb}
59178476Sjb
60178476Sjbkadm5_ret_t
61178476Sjb_kadm5_c_init_context(kadm5_client_context **ctx,
62178476Sjb		      kadm5_config_params *params,
63178476Sjb		      krb5_context context)
64178476Sjb{
65178476Sjb    krb5_error_code ret;
66178476Sjb    char *colon;
67178476Sjb
68178476Sjb    *ctx = malloc(sizeof(**ctx));
69178476Sjb    if(*ctx == NULL)
70178476Sjb	return ENOMEM;
71178476Sjb    memset(*ctx, 0, sizeof(**ctx));
72178476Sjb    krb5_add_et_list (context, initialize_kadm5_error_table_r);
73178476Sjb    set_funcs(*ctx);
74178476Sjb    (*ctx)->context = context;
75178476Sjb    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
112static krb5_error_code
113get_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
133static krb5_error_code
134get_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
209static krb5_error_code
210get_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
326static kadm5_ret_t
327kadm_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    char *service_name;
339    krb5_context context = ctx->context;
340
341    memset (&hints, 0, sizeof(hints));
342    hints.ai_socktype = SOCK_STREAM;
343    hints.ai_protocol = IPPROTO_TCP;
344
345    snprintf (portstr, sizeof(portstr), "%u", ntohs(ctx->kadmind_port));
346
347    hostname = ctx->admin_server;
348    slash = strchr (hostname, '/');
349    if (slash != NULL)
350	hostname = slash + 1;
351
352    error = getaddrinfo (hostname, portstr, &hints, &ai);
353    if (error)
354	return KADM5_BAD_SERVER_NAME;
355
356    for (a = ai; a != NULL; a = a->ai_next) {
357	s = socket (a->ai_family, a->ai_socktype, a->ai_protocol);
358	if (s < 0)
359	    continue;
360	if (connect (s, a->ai_addr, a->ai_addrlen) < 0) {
361	    krb5_warn (context, errno, "connect(%s)", hostname);
362	    close (s);
363	    continue;
364	}
365	break;
366    }
367    if (a == NULL) {
368	freeaddrinfo (ai);
369	krb5_warnx (context, "failed to contact %s", hostname);
370	return KADM5_FAILURE;
371    }
372    ret = get_cred_cache(context, ctx->client_name, ctx->service_name,
373			 NULL, ctx->prompter, ctx->keytab,
374			 ctx->ccache, &cc);
375
376    if(ret) {
377	freeaddrinfo (ai);
378	close(s);
379	return ret;
380    }
381
382    if (ctx->realm)
383	asprintf(&service_name, "%s@%s", KADM5_ADMIN_SERVICE, ctx->realm);
384    else
385	asprintf(&service_name, "%s", KADM5_ADMIN_SERVICE);
386
387    if (service_name == NULL) {
388	freeaddrinfo (ai);
389	close(s);
390	return ENOMEM;
391    }
392
393    ret = krb5_parse_name(context, service_name, &server);
394    free(service_name);
395    if(ret) {
396	freeaddrinfo (ai);
397	if(ctx->ccache == NULL)
398	    krb5_cc_close(context, cc);
399	close(s);
400	return ret;
401    }
402    ctx->ac = NULL;
403
404    ret = krb5_sendauth(context, &ctx->ac, &s,
405			KADMIN_APPL_VERSION, NULL,
406			server, AP_OPTS_MUTUAL_REQUIRED,
407			NULL, NULL, cc, NULL, NULL, NULL);
408    if(ret == 0) {
409	krb5_data params;
410	kadm5_config_params p;
411	memset(&p, 0, sizeof(p));
412	if(ctx->realm) {
413	    p.mask |= KADM5_CONFIG_REALM;
414	    p.realm = ctx->realm;
415	}
416	ret = _kadm5_marshal_params(context, &p, &params);
417
418	ret = krb5_write_priv_message(context, ctx->ac, &s, &params);
419	krb5_data_free(&params);
420	if(ret) {
421	    freeaddrinfo (ai);
422	    close(s);
423	    if(ctx->ccache == NULL)
424		krb5_cc_close(context, cc);
425	    return ret;
426	}
427    } else if(ret == KRB5_SENDAUTH_BADAPPLVERS) {
428	close(s);
429
430	s = socket (a->ai_family, a->ai_socktype, a->ai_protocol);
431	if (s < 0) {
432	    freeaddrinfo (ai);
433	    return errno;
434	}
435	if (connect (s, a->ai_addr, a->ai_addrlen) < 0) {
436	    close (s);
437	    freeaddrinfo (ai);
438	    return errno;
439	}
440	ret = krb5_sendauth(context, &ctx->ac, &s,
441			    KADMIN_OLD_APPL_VERSION, NULL,
442			    server, AP_OPTS_MUTUAL_REQUIRED,
443			    NULL, NULL, cc, NULL, NULL, NULL);
444    }
445    freeaddrinfo (ai);
446    if(ret) {
447	close(s);
448	return ret;
449    }
450
451    krb5_free_principal(context, server);
452    if(ctx->ccache == NULL)
453	krb5_cc_close(context, cc);
454    if(ret) {
455	close(s);
456	return ret;
457    }
458    ctx->sock = s;
459
460    return 0;
461}
462
463kadm5_ret_t
464_kadm5_connect(void *handle)
465{
466    kadm5_client_context *ctx = handle;
467    if(ctx->sock == -1)
468	return kadm_connect(ctx);
469    return 0;
470}
471
472static kadm5_ret_t
473kadm5_c_init_with_context(krb5_context context,
474			  const char *client_name,
475			  const char *password,
476			  krb5_prompter_fct prompter,
477			  const char *keytab,
478			  krb5_ccache ccache,
479			  const char *service_name,
480			  kadm5_config_params *realm_params,
481			  unsigned long struct_version,
482			  unsigned long api_version,
483			  void **server_handle)
484{
485    kadm5_ret_t ret;
486    kadm5_client_context *ctx;
487    krb5_ccache cc;
488
489    ret = _kadm5_c_init_context(&ctx, realm_params, context);
490    if(ret)
491	return ret;
492
493    if(password != NULL && *password != '\0') {
494	ret = get_cred_cache(context, client_name, service_name,
495			     password, prompter, keytab, ccache, &cc);
496	if(ret)
497	    return ret; /* XXX */
498	ccache = cc;
499    }
500
501
502    if (client_name != NULL)
503	ctx->client_name = strdup(client_name);
504    else
505	ctx->client_name = NULL;
506    if (service_name != NULL)
507	ctx->service_name = strdup(service_name);
508    else
509	ctx->service_name = NULL;
510    ctx->prompter = prompter;
511    ctx->keytab = keytab;
512    ctx->ccache = ccache;
513    /* maybe we should copy the params here */
514    ctx->sock = -1;
515
516    *server_handle = ctx;
517    return 0;
518}
519
520static kadm5_ret_t
521init_context(const char *client_name,
522	     const char *password,
523	     krb5_prompter_fct prompter,
524	     const char *keytab,
525	     krb5_ccache ccache,
526	     const char *service_name,
527	     kadm5_config_params *realm_params,
528	     unsigned long struct_version,
529	     unsigned long api_version,
530	     void **server_handle)
531{
532    krb5_context context;
533    kadm5_ret_t ret;
534    kadm5_server_context *ctx;
535
536    ret = krb5_init_context(&context);
537    if (ret)
538	return ret;
539    ret = kadm5_c_init_with_context(context,
540				    client_name,
541				    password,
542				    prompter,
543				    keytab,
544				    ccache,
545				    service_name,
546				    realm_params,
547				    struct_version,
548				    api_version,
549				    server_handle);
550    if(ret){
551	krb5_free_context(context);
552	return ret;
553    }
554    ctx = *server_handle;
555    ctx->my_context = 1;
556    return 0;
557}
558
559kadm5_ret_t
560kadm5_c_init_with_password_ctx(krb5_context context,
561			       const char *client_name,
562			       const char *password,
563			       const char *service_name,
564			       kadm5_config_params *realm_params,
565			       unsigned long struct_version,
566			       unsigned long api_version,
567			       void **server_handle)
568{
569    return kadm5_c_init_with_context(context,
570				     client_name,
571				     password,
572				     krb5_prompter_posix,
573				     NULL,
574				     NULL,
575				     service_name,
576				     realm_params,
577				     struct_version,
578				     api_version,
579				     server_handle);
580}
581
582kadm5_ret_t
583kadm5_c_init_with_password(const char *client_name,
584			   const char *password,
585			   const char *service_name,
586			   kadm5_config_params *realm_params,
587			   unsigned long struct_version,
588			   unsigned long api_version,
589			   void **server_handle)
590{
591    return init_context(client_name,
592			password,
593			krb5_prompter_posix,
594			NULL,
595			NULL,
596			service_name,
597			realm_params,
598			struct_version,
599			api_version,
600			server_handle);
601}
602
603kadm5_ret_t
604kadm5_c_init_with_skey_ctx(krb5_context context,
605			   const char *client_name,
606			   const char *keytab,
607			   const char *service_name,
608			   kadm5_config_params *realm_params,
609			   unsigned long struct_version,
610			   unsigned long api_version,
611			   void **server_handle)
612{
613    return kadm5_c_init_with_context(context,
614				     client_name,
615				     NULL,
616				     NULL,
617				     keytab,
618				     NULL,
619				     service_name,
620				     realm_params,
621				     struct_version,
622				     api_version,
623				     server_handle);
624}
625
626
627kadm5_ret_t
628kadm5_c_init_with_skey(const char *client_name,
629		     const char *keytab,
630		     const char *service_name,
631		     kadm5_config_params *realm_params,
632		     unsigned long struct_version,
633		     unsigned long api_version,
634		     void **server_handle)
635{
636    return init_context(client_name,
637			NULL,
638			NULL,
639			keytab,
640			NULL,
641			service_name,
642			realm_params,
643			struct_version,
644			api_version,
645			server_handle);
646}
647
648kadm5_ret_t
649kadm5_c_init_with_creds_ctx(krb5_context context,
650			    const char *client_name,
651			    krb5_ccache ccache,
652			    const char *service_name,
653			    kadm5_config_params *realm_params,
654			    unsigned long struct_version,
655			    unsigned long api_version,
656			    void **server_handle)
657{
658    return kadm5_c_init_with_context(context,
659				     client_name,
660				     NULL,
661				     NULL,
662				     NULL,
663				     ccache,
664				     service_name,
665				     realm_params,
666				     struct_version,
667				     api_version,
668				     server_handle);
669}
670
671kadm5_ret_t
672kadm5_c_init_with_creds(const char *client_name,
673			krb5_ccache ccache,
674			const char *service_name,
675			kadm5_config_params *realm_params,
676			unsigned long struct_version,
677			unsigned long api_version,
678			void **server_handle)
679{
680    return init_context(client_name,
681			NULL,
682			NULL,
683			NULL,
684			ccache,
685			service_name,
686			realm_params,
687			struct_version,
688			api_version,
689			server_handle);
690}
691
692#if 0
693kadm5_ret_t
694kadm5_init(char *client_name, char *pass,
695	   char *service_name,
696	   kadm5_config_params *realm_params,
697	   unsigned long struct_version,
698	   unsigned long api_version,
699	   void **server_handle)
700{
701}
702#endif
703
704