init_c.c revision 102644
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
40RCSID("$Id: init_c.c,v 1.44 2002/06/16 15:13:25 nectar Exp $");
41
42static void
43set_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
60kadm5_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
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    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
449kadm5_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
458static kadm5_ret_t
459kadm5_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
506static kadm5_ret_t
507init_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
545kadm5_ret_t
546kadm5_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
568kadm5_ret_t
569kadm5_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
589kadm5_ret_t
590kadm5_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
613kadm5_ret_t
614kadm5_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
634kadm5_ret_t
635kadm5_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
657kadm5_ret_t
658kadm5_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
679kadm5_ret_t
680kadm5_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