]> CyberLeo.Net >> Repos - FreeBSD/releng/9.2.git/blob - crypto/heimdal/lib/kadm5/init_c.c
- Copy stable/9 to releng/9.2 as part of the 9.2-RELEASE cycle.
[FreeBSD/releng/9.2.git] / crypto / heimdal / lib / kadm5 / init_c.c
1 /*
2  * Copyright (c) 1997 - 2006 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 21972 2007-10-18 19:11:15Z lha $");
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         free((*ctx)->realm);
103         free(*ctx);
104         return ENOMEM;
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     ret = krb5_get_init_creds_opt_alloc (context, &opt);
161     if (ret)
162         return ret;
163
164     krb5_get_init_creds_opt_set_default_flags(context, "kadmin", 
165                                               krb5_principal_get_realm(context, 
166                                                                        client), 
167                                               opt);
168
169
170     krb5_get_init_creds_opt_set_forwardable (opt, FALSE);
171     krb5_get_init_creds_opt_set_proxiable (opt, FALSE);
172
173     if(password == NULL && prompter == NULL) {
174         krb5_keytab kt;
175         if(keytab == NULL)
176             ret = krb5_kt_default(context, &kt);
177         else
178             ret = krb5_kt_resolve(context, keytab, &kt);
179         if(ret) {
180             krb5_get_init_creds_opt_free(context, opt);
181             return ret;
182         }
183         ret = krb5_get_init_creds_keytab (context,
184                                           &cred,
185                                           client,
186                                           kt,
187                                           0,
188                                           server_name,
189                                           opt);
190         krb5_kt_close(context, kt);
191     } else {
192         ret = krb5_get_init_creds_password (context,
193                                             &cred,
194                                             client,
195                                             password,
196                                             prompter,
197                                             NULL,
198                                             0,
199                                             server_name,
200                                             opt);
201     }
202     krb5_get_init_creds_opt_free(context, opt);
203     switch(ret){
204     case 0:
205         break;
206     case KRB5_LIBOS_PWDINTR:    /* don't print anything if it was just C-c:ed */
207     case KRB5KRB_AP_ERR_BAD_INTEGRITY:
208     case KRB5KRB_AP_ERR_MODIFIED:
209         return KADM5_BAD_PASSWORD;
210     default:
211         return ret;
212     }
213     ret = krb5_cc_gen_new(context, &krb5_mcc_ops, &id);
214     if(ret)
215         return ret;
216     ret = krb5_cc_initialize (context, id, cred.client);
217     if (ret)
218         return ret;
219     ret = krb5_cc_store_cred (context, id, &cred);
220     if (ret)
221         return ret;
222     krb5_free_cred_contents (context, &cred);
223     *ret_cache = id;
224     return 0;
225 }
226
227 /*
228  * Check the credential cache `id´ to figure out what principal to use
229  * when talking to the kadmind. If there is a initial kadmin/admin@
230  * credential in the cache, use that client principal. Otherwise, use
231  * the client principals first component and add /admin to the
232  * principal.
233  */
234
235 static krb5_error_code
236 get_cache_principal(krb5_context context,
237                     krb5_ccache *id,
238                     krb5_principal *client)
239 {
240     krb5_error_code ret;
241     const char *name, *inst;
242     krb5_principal p1, p2;
243
244     ret = krb5_cc_default(context, id);
245     if(ret) {
246         *id = NULL;
247         return ret;
248     }
249     
250     ret = krb5_cc_get_principal(context, *id, &p1);
251     if(ret) {
252         krb5_cc_close(context, *id);
253         *id = NULL;
254         return ret;
255     }
256
257     ret = krb5_make_principal(context, &p2, NULL, 
258                               "kadmin", "admin", NULL);
259     if (ret) {
260         krb5_cc_close(context, *id);
261         *id = NULL;
262         krb5_free_principal(context, p1);
263         return ret;
264     }
265
266     {
267         krb5_creds in, *out;
268         krb5_kdc_flags flags;
269
270         flags.i = 0;
271         memset(&in, 0, sizeof(in));
272
273         in.client = p1;
274         in.server = p2;
275
276         /* check for initial ticket kadmin/admin */
277         ret = krb5_get_credentials_with_flags(context, KRB5_GC_CACHED, flags,
278                                               *id, &in, &out);
279         krb5_free_principal(context, p2);
280         if (ret == 0) {
281             if (out->flags.b.initial) {
282                 *client = p1;
283                 krb5_free_creds(context, out);
284                 return 0;
285             }
286             krb5_free_creds(context, out);
287         }
288     }
289     krb5_cc_close(context, *id);
290     *id = NULL;
291
292     name = krb5_principal_get_comp_string(context, p1, 0);
293     inst = krb5_principal_get_comp_string(context, p1, 1);
294     if(inst == NULL || strcmp(inst, "admin") != 0) {
295         ret = krb5_make_principal(context, &p2, NULL, name, "admin", NULL);
296         krb5_free_principal(context, p1);
297         if(ret != 0)
298             return ret;
299
300         *client = p2;
301         return 0;
302     }
303
304     *client = p1;
305
306     return 0;
307 }
308
309 krb5_error_code
310 _kadm5_c_get_cred_cache(krb5_context context,
311                         const char *client_name,
312                         const char *server_name,
313                         const char *password,
314                         krb5_prompter_fct prompter,
315                         const char *keytab,
316                         krb5_ccache ccache,
317                         krb5_ccache *ret_cache)
318 {
319     krb5_error_code ret;
320     krb5_ccache id = NULL;
321     krb5_principal default_client = NULL, client = NULL;
322     
323     /* treat empty password as NULL */
324     if(password && *password == '\0')
325         password = NULL;
326     if(server_name == NULL)
327         server_name = KADM5_ADMIN_SERVICE;
328     
329     if(client_name != NULL) {
330         ret = krb5_parse_name(context, client_name, &client);
331         if(ret) 
332             return ret;
333     }
334
335     if(ccache != NULL) {
336         id = ccache;
337         ret = krb5_cc_get_principal(context, id, &client);
338         if(ret)
339             return ret;
340     } else {
341         /* get principal from default cache, ok if this doesn't work */
342
343         ret = get_cache_principal(context, &id, &default_client);
344         if (ret) {
345             /* 
346              * No client was specified by the caller and we cannot
347              * determine the client from a credentials cache.
348              */
349             const char *user;
350
351             user = get_default_username ();
352
353             if(user == NULL) {
354                 krb5_set_error_string(context, "Unable to find local user name");
355                 return KADM5_FAILURE;
356             }
357             ret = krb5_make_principal(context, &default_client, 
358                                       NULL, user, "admin", NULL);
359             if(ret)
360                 return ret;
361         }
362     }
363
364
365     /*
366      * No client was specified by the caller, but we have a client
367      * from the default credentials cache.
368      */
369     if (client == NULL && default_client != NULL)
370         client = default_client;
371
372     
373     if(id && (default_client == NULL || 
374               krb5_principal_compare(context, client, default_client))) {
375         ret = get_kadm_ticket(context, id, client, server_name);
376         if(ret == 0) {
377             *ret_cache = id;
378             krb5_free_principal(context, default_client);
379             if (default_client != client)
380                 krb5_free_principal(context, client);
381             return 0;
382         }
383         if(ccache != NULL)
384             /* couldn't get ticket from cache */
385             return -1;
386     }
387     /* get creds via AS request */
388     if(id && (id != ccache))
389         krb5_cc_close(context, id);
390     if (client != default_client)
391         krb5_free_principal(context, default_client);
392
393     ret = get_new_cache(context, client, password, prompter, keytab, 
394                         server_name, ret_cache);
395     krb5_free_principal(context, client);
396     return ret;
397 }
398
399 static kadm5_ret_t
400 kadm_connect(kadm5_client_context *ctx)
401 {
402     kadm5_ret_t ret;
403     krb5_principal server;
404     krb5_ccache cc;
405     int s;
406     struct addrinfo *ai, *a;
407     struct addrinfo hints;
408     int error;
409     char portstr[NI_MAXSERV];
410     char *hostname, *slash;
411     char *service_name;
412     krb5_context context = ctx->context;
413
414     memset (&hints, 0, sizeof(hints));
415     hints.ai_socktype = SOCK_STREAM;
416     hints.ai_protocol = IPPROTO_TCP;
417     
418     snprintf (portstr, sizeof(portstr), "%u", ntohs(ctx->kadmind_port));
419
420     hostname = ctx->admin_server;
421     slash = strchr (hostname, '/');
422     if (slash != NULL)
423         hostname = slash + 1;
424
425     error = getaddrinfo (hostname, portstr, &hints, &ai);
426     if (error) {
427         krb5_clear_error_string(context);
428         return KADM5_BAD_SERVER_NAME;
429     }
430     
431     for (a = ai; a != NULL; a = a->ai_next) {
432         s = socket (a->ai_family, a->ai_socktype, a->ai_protocol);
433         if (s < 0)
434             continue;
435         if (connect (s, a->ai_addr, a->ai_addrlen) < 0) {
436             krb5_clear_error_string(context);
437             krb5_warn (context, errno, "connect(%s)", hostname);
438             close (s);
439             continue;
440         }
441         break;
442     }
443     if (a == NULL) {
444         freeaddrinfo (ai);
445         krb5_clear_error_string(context);
446         krb5_warnx (context, "failed to contact %s", hostname);
447         return KADM5_FAILURE;
448     }
449     ret = _kadm5_c_get_cred_cache(context,
450                                   ctx->client_name, 
451                                   ctx->service_name, 
452                                   NULL, ctx->prompter, ctx->keytab, 
453                                   ctx->ccache, &cc);
454     
455     if(ret) {
456         freeaddrinfo (ai);
457         close(s);
458         return ret;
459     }
460
461     if (ctx->realm)
462         asprintf(&service_name, "%s@%s", KADM5_ADMIN_SERVICE, ctx->realm);
463     else
464         asprintf(&service_name, "%s", KADM5_ADMIN_SERVICE);
465
466     if (service_name == NULL) {
467         freeaddrinfo (ai);
468         close(s);
469         krb5_clear_error_string(context);
470         return ENOMEM;
471     }
472
473     ret = krb5_parse_name(context, service_name, &server);
474     free(service_name);
475     if(ret) {
476         freeaddrinfo (ai);
477         if(ctx->ccache == NULL)
478             krb5_cc_close(context, cc);
479         close(s);
480         return ret;
481     }
482     ctx->ac = NULL;
483
484     ret = krb5_sendauth(context, &ctx->ac, &s, 
485                         KADMIN_APPL_VERSION, NULL, 
486                         server, AP_OPTS_MUTUAL_REQUIRED, 
487                         NULL, NULL, cc, NULL, NULL, NULL);
488     if(ret == 0) {
489         krb5_data params;
490         kadm5_config_params p;
491         memset(&p, 0, sizeof(p));
492         if(ctx->realm) {
493             p.mask |= KADM5_CONFIG_REALM;
494             p.realm = ctx->realm;
495         }
496         ret = _kadm5_marshal_params(context, &p, &params);
497         
498         ret = krb5_write_priv_message(context, ctx->ac, &s, &params);
499         krb5_data_free(&params);
500         if(ret) {
501             freeaddrinfo (ai);
502             close(s);
503             if(ctx->ccache == NULL)
504                 krb5_cc_close(context, cc);
505             return ret;
506         }
507     } else if(ret == KRB5_SENDAUTH_BADAPPLVERS) {
508         close(s);
509
510         s = socket (a->ai_family, a->ai_socktype, a->ai_protocol);
511         if (s < 0) {
512             freeaddrinfo (ai);
513             krb5_clear_error_string(context);
514             return errno;
515         }
516         if (connect (s, a->ai_addr, a->ai_addrlen) < 0) {
517             close (s);
518             freeaddrinfo (ai);
519             krb5_clear_error_string(context);
520             return errno;
521         }
522         ret = krb5_sendauth(context, &ctx->ac, &s, 
523                             KADMIN_OLD_APPL_VERSION, NULL, 
524                             server, AP_OPTS_MUTUAL_REQUIRED, 
525                             NULL, NULL, cc, NULL, NULL, NULL);
526     }
527     freeaddrinfo (ai);
528     if(ret) {
529         close(s);
530         return ret;
531     }
532     
533     krb5_free_principal(context, server);
534     if(ctx->ccache == NULL)
535         krb5_cc_close(context, cc);
536     ctx->sock = s;
537     
538     return 0;
539 }
540
541 kadm5_ret_t
542 _kadm5_connect(void *handle)
543 {
544     kadm5_client_context *ctx = handle;
545     if(ctx->sock == -1)
546         return kadm_connect(ctx);
547     return 0;
548 }
549
550 static kadm5_ret_t 
551 kadm5_c_init_with_context(krb5_context context,
552                           const char *client_name, 
553                           const char *password,
554                           krb5_prompter_fct prompter,
555                           const char *keytab,
556                           krb5_ccache ccache,
557                           const char *service_name,
558                           kadm5_config_params *realm_params,
559                           unsigned long struct_version,
560                           unsigned long api_version,
561                           void **server_handle)
562 {
563     kadm5_ret_t ret;
564     kadm5_client_context *ctx;
565     krb5_ccache cc;
566
567     ret = _kadm5_c_init_context(&ctx, realm_params, context);
568     if(ret)
569         return ret;
570
571     if(password != NULL && *password != '\0') {
572         ret = _kadm5_c_get_cred_cache(context, 
573                                       client_name,
574                                       service_name, 
575                                       password, prompter, keytab, ccache, &cc);
576         if(ret)
577             return ret; /* XXX */
578         ccache = cc;
579     }
580     
581
582     if (client_name != NULL)
583         ctx->client_name = strdup(client_name);
584     else
585         ctx->client_name = NULL;
586     if (service_name != NULL)
587         ctx->service_name = strdup(service_name);
588     else
589         ctx->service_name = NULL;
590     ctx->prompter = prompter;
591     ctx->keytab = keytab;
592     ctx->ccache = ccache;
593     /* maybe we should copy the params here */
594     ctx->sock = -1;
595     
596     *server_handle = ctx;
597     return 0;
598 }
599
600 static kadm5_ret_t 
601 init_context(const char *client_name, 
602              const char *password,
603              krb5_prompter_fct prompter,
604              const char *keytab,
605              krb5_ccache ccache,
606              const char *service_name,
607              kadm5_config_params *realm_params,
608              unsigned long struct_version,
609              unsigned long api_version,
610              void **server_handle)
611 {
612     krb5_context context;
613     kadm5_ret_t ret;
614     kadm5_server_context *ctx;
615     
616     ret = krb5_init_context(&context);
617     if (ret)
618         return ret;
619     ret = kadm5_c_init_with_context(context,
620                                     client_name,
621                                     password,
622                                     prompter,
623                                     keytab,
624                                     ccache,
625                                     service_name,
626                                     realm_params,
627                                     struct_version,
628                                     api_version,
629                                     server_handle);
630     if(ret){
631         krb5_free_context(context);
632         return ret;
633     }
634     ctx = *server_handle;
635     ctx->my_context = 1;
636     return 0;
637 }
638
639 kadm5_ret_t 
640 kadm5_c_init_with_password_ctx(krb5_context context,
641                                const char *client_name, 
642                                const char *password,
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 kadm5_c_init_with_context(context,
650                                      client_name,
651                                      password,
652                                      krb5_prompter_posix,
653                                      NULL,
654                                      NULL,
655                                      service_name,
656                                      realm_params,
657                                      struct_version,
658                                      api_version,
659                                      server_handle);
660 }
661
662 kadm5_ret_t 
663 kadm5_c_init_with_password(const char *client_name, 
664                            const char *password,
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 init_context(client_name, 
672                         password, 
673                         krb5_prompter_posix,
674                         NULL,
675                         NULL,
676                         service_name, 
677                         realm_params, 
678                         struct_version, 
679                         api_version, 
680                         server_handle);
681 }
682
683 kadm5_ret_t 
684 kadm5_c_init_with_skey_ctx(krb5_context context,
685                            const char *client_name, 
686                            const char *keytab,
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 kadm5_c_init_with_context(context,
694                                      client_name,
695                                      NULL,
696                                      NULL,
697                                      keytab,
698                                      NULL,
699                                      service_name,
700                                      realm_params,
701                                      struct_version,
702                                      api_version,
703                                      server_handle);
704 }
705
706
707 kadm5_ret_t 
708 kadm5_c_init_with_skey(const char *client_name, 
709                      const char *keytab,
710                      const char *service_name,
711                      kadm5_config_params *realm_params,
712                      unsigned long struct_version,
713                      unsigned long api_version,
714                      void **server_handle)
715 {
716     return init_context(client_name, 
717                         NULL,
718                         NULL,
719                         keytab,
720                         NULL,
721                         service_name, 
722                         realm_params, 
723                         struct_version, 
724                         api_version, 
725                         server_handle);
726 }
727
728 kadm5_ret_t 
729 kadm5_c_init_with_creds_ctx(krb5_context context,
730                             const char *client_name,
731                             krb5_ccache ccache,
732                             const char *service_name,
733                             kadm5_config_params *realm_params,
734                             unsigned long struct_version,
735                             unsigned long api_version,
736                             void **server_handle)
737 {
738     return kadm5_c_init_with_context(context,
739                                      client_name,
740                                      NULL,
741                                      NULL,
742                                      NULL,
743                                      ccache,
744                                      service_name,
745                                      realm_params,
746                                      struct_version,
747                                      api_version,
748                                      server_handle);
749 }
750
751 kadm5_ret_t 
752 kadm5_c_init_with_creds(const char *client_name,
753                         krb5_ccache ccache,
754                         const char *service_name,
755                         kadm5_config_params *realm_params,
756                         unsigned long struct_version,
757                         unsigned long api_version,
758                         void **server_handle)
759 {
760     return init_context(client_name, 
761                         NULL,
762                         NULL,
763                         NULL,
764                         ccache,
765                         service_name, 
766                         realm_params, 
767                         struct_version, 
768                         api_version, 
769                         server_handle);
770 }
771
772 #if 0
773 kadm5_ret_t 
774 kadm5_init(char *client_name, char *pass,
775            char *service_name,
776            kadm5_config_params *realm_params,
777            unsigned long struct_version,
778            unsigned long api_version,
779            void **server_handle)
780 {
781 }
782 #endif
783