]> git.decadent.org.uk Git - nfs-utils.git/blob - utils/gssd/krb5_util.c
Add the other two DES encryption types to the default list of
[nfs-utils.git] / utils / gssd / krb5_util.c
1 /*
2  *  Adapted in part from MIT Kerberos 5-1.2.1 slave/kprop.c and from
3  *  http://docs.sun.com/?p=/doc/816-1331/6m7oo9sms&a=view
4  *
5  *  Copyright (c) 2002-2004 The Regents of the University of Michigan.
6  *  All rights reserved.
7  *
8  *  Andy Adamson <andros@umich.edu>
9  *  J. Bruce Fields <bfields@umich.edu>
10  *  Marius Aamodt Eriksen <marius@umich.edu>
11  *  Kevin Coffman <kwc@umich.edu>
12  */
13
14 /*
15  * slave/kprop.c
16  *
17  * Copyright 1990,1991 by the Massachusetts Institute of Technology.
18  * All Rights Reserved.
19  *
20  * Export of this software from the United States of America may
21  *   require a specific license from the United States Government.
22  *   It is the responsibility of any person or organization contemplating
23  *   export to obtain such a license before exporting.
24  *
25  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
26  * distribute this software and its documentation for any purpose and
27  * without fee is hereby granted, provided that the above copyright
28  * notice appear in all copies and that both that copyright notice and
29  * this permission notice appear in supporting documentation, and that
30  * the name of M.I.T. not be used in advertising or publicity pertaining
31  * to distribution of the software without specific, written prior
32  * permission.  Furthermore if you modify this software you must label
33  * your software as modified software and not distribute it in such a
34  * fashion that it might be confused with the original M.I.T. software.
35  * M.I.T. makes no representations about the suitability of
36  * this software for any purpose.  It is provided "as is" without express
37  * or implied warranty.
38  */
39
40 /*
41  * Copyright 1994 by OpenVision Technologies, Inc.
42  *
43  * Permission to use, copy, modify, distribute, and sell this software
44  * and its documentation for any purpose is hereby granted without fee,
45  * provided that the above copyright notice appears in all copies and
46  * that both that copyright notice and this permission notice appear in
47  * supporting documentation, and that the name of OpenVision not be used
48  * in advertising or publicity pertaining to distribution of the software
49  * without specific, written prior permission. OpenVision makes no
50  * representations about the suitability of this software for any
51  * purpose.  It is provided "as is" without express or implied warranty.
52  *
53  * OPENVISION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
54  * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
55  * EVENT SHALL OPENVISION BE LIABLE FOR ANY SPECIAL, INDIRECT OR
56  * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF
57  * USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
58  * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
59  * PERFORMANCE OF THIS SOFTWARE.
60  */
61 /*
62   krb5_util.c
63
64   Copyright (c) 2004 The Regents of the University of Michigan.
65   All rights reserved.
66
67   Redistribution and use in source and binary forms, with or without
68   modification, are permitted provided that the following conditions
69   are met:
70
71   1. Redistributions of source code must retain the above copyright
72      notice, this list of conditions and the following disclaimer.
73   2. Redistributions in binary form must reproduce the above copyright
74      notice, this list of conditions and the following disclaimer in the
75      documentation and/or other materials provided with the distribution.
76   3. Neither the name of the University nor the names of its
77      contributors may be used to endorse or promote products derived
78      from this software without specific prior written permission.
79
80   THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
81   WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
82   MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
83   DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
84   FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
85   CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
86   SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
87   BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
88   LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
89   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
90   SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
91
92 */
93
94 #ifndef _GNU_SOURCE
95 #define _GNU_SOURCE
96 #endif
97 #include "config.h"
98 #include <sys/param.h>
99 #include <rpc/rpc.h>
100 #include <sys/stat.h>
101 #include <sys/socket.h>
102 #include <arpa/inet.h>
103
104 #include <stdio.h>
105 #include <stdlib.h>
106 #include <unistd.h>
107 #include <string.h>
108 #include <dirent.h>
109 #include <netdb.h>
110 #include <ctype.h>
111 #include <errno.h>
112 #include <time.h>
113 #include <gssapi/gssapi.h>
114 #ifdef USE_PRIVATE_KRB5_FUNCTIONS
115 #include <gssapi/gssapi_krb5.h>
116 #endif
117 #include <krb5.h>
118 #include <rpc/auth_gss.h>
119
120 #include "gssd.h"
121 #include "err_util.h"
122 #include "gss_util.h"
123 #include "gss_oids.h"
124 #include "krb5_util.h"
125
126 /* Global list of principals/cache file names for machine credentials */
127 struct gssd_k5_kt_princ *gssd_k5_kt_princ_list = NULL;
128
129 /*==========================*/
130 /*===  Internal routines ===*/
131 /*==========================*/
132
133 static int select_krb5_ccache(const struct dirent *d);
134 static int gssd_find_existing_krb5_ccache(uid_t uid, struct dirent **d);
135 static int gssd_get_single_krb5_cred(krb5_context context,
136                 krb5_keytab kt, struct gssd_k5_kt_princ *ple);
137
138
139 /*
140  * Called from the scandir function to weed out potential krb5
141  * credentials cache files
142  *
143  * Returns:
144  *      0 => don't select this one
145  *      1 => select this one
146  */
147 static int
148 select_krb5_ccache(const struct dirent *d)
149 {
150         /*
151          * Note: We used to check d->d_type for DT_REG here,
152          * but apparenlty reiser4 always has DT_UNKNOWN.
153          * Check for IS_REG after stat() call instead.
154          */
155         if (strstr(d->d_name, GSSD_DEFAULT_CRED_PREFIX))
156                 return 1;
157         else
158                 return 0;
159 }
160
161 /*
162  * Look in the ccachedir for files that look like they
163  * are Kerberos Credential Cache files for a given UID.  Return
164  * non-zero and the dirent pointer for the entry most likely to be
165  * what we want. Otherwise, return zero and no dirent pointer.
166  * The caller is responsible for freeing the dirent if one is returned.
167  *
168  * Returns:
169  *      0 => could not find an existing entry
170  *      1 => found an existing entry
171  */
172 static int
173 gssd_find_existing_krb5_ccache(uid_t uid, struct dirent **d)
174 {
175         struct dirent **namelist;
176         int n;
177         int i;
178         int found = 0;
179         struct dirent *best_match_dir = NULL;
180         struct stat best_match_stat, tmp_stat;
181
182         memset(&best_match_stat, 0, sizeof(best_match_stat));
183         *d = NULL;
184         n = scandir(ccachedir, &namelist, select_krb5_ccache, 0);
185         if (n < 0) {
186                 perror("scandir looking for krb5 credentials caches");
187         }
188         else if (n > 0) {
189                 char statname[1024];
190                 for (i = 0; i < n; i++) {
191                         printerr(3, "CC file '%s' being considered\n",
192                                  namelist[i]->d_name);
193                         snprintf(statname, sizeof(statname),
194                                  "%s/%s", ccachedir, namelist[i]->d_name);
195                         if (lstat(statname, &tmp_stat)) {
196                                 printerr(0, "Error doing stat on file '%s'\n",
197                                          statname);
198                                 free(namelist[i]);
199                                 continue;
200                         }
201                         /* Only pick caches owned by the user (uid) */
202                         if (tmp_stat.st_uid != uid) {
203                                 printerr(3, "'%s' owned by %u, not %u\n",
204                                          statname, tmp_stat.st_uid, uid);
205                                 free(namelist[i]);
206                                 continue;
207                         }
208                         if (!S_ISREG(tmp_stat.st_mode)) {
209                                 printerr(3, "'%s' is not a regular file\n",
210                                          statname);
211                                 free(namelist[i]);
212                                 continue;
213                         }
214                         printerr(3, "CC file '%s' matches owner check and has "
215                                  "mtime of %u\n",
216                                  namelist[i]->d_name, tmp_stat.st_mtime);
217                         /*
218                          * if more than one match is found, return the most
219                          * recent (the one with the latest mtime), and
220                          * don't free the dirent
221                          */
222                         if (!found) {
223                                 best_match_dir = namelist[i];
224                                 best_match_stat = tmp_stat;
225                                 found++;
226                         }
227                         else {
228                                 /*
229                                  * If the current match has an mtime later
230                                  * than the one we are looking at, then use
231                                  * the current match.  Otherwise, we still
232                                  * have the best match.
233                                  */
234                                 if (tmp_stat.st_mtime >
235                                             best_match_stat.st_mtime) {
236                                         free(best_match_dir);
237                                         best_match_dir = namelist[i];
238                                         best_match_stat = tmp_stat;
239                                 }
240                                 else {
241                                         free(namelist[i]);
242                                 }
243                                 printerr(3, "CC file '%s' is our "
244                                             "current best match "
245                                             "with mtime of %u\n",
246                                          best_match_dir->d_name,
247                                          best_match_stat.st_mtime);
248                         }
249                 }
250                 free(namelist);
251         }
252         if (found)
253         {
254                 *d = best_match_dir;
255         }
256         return found;
257 }
258
259
260 #ifdef HAVE_SET_ALLOWABLE_ENCTYPES
261 /*
262  * this routine obtains a credentials handle via gss_acquire_cred()
263  * then calls gss_krb5_set_allowable_enctypes() to limit the encryption
264  * types negotiated.
265  *
266  * XXX Should call some function to determine the enctypes supported
267  * by the kernel. (Only need to do that once!)
268  *
269  * Returns:
270  *      0 => all went well
271  *     -1 => there was an error
272  */
273
274 int
275 limit_krb5_enctypes(struct rpc_gss_sec *sec, uid_t uid)
276 {
277         u_int maj_stat, min_stat;
278         gss_cred_id_t credh;
279         gss_OID_set_desc  desired_mechs;
280         krb5_enctype enctypes[] = { ENCTYPE_DES_CBC_CRC,
281                                     ENCTYPE_DES_CBC_MD5,
282                                     ENCTYPE_DES_CBC_MD4 };
283         int num_enctypes = sizeof(enctypes) / sizeof(enctypes[0]);
284
285         /* We only care about getting a krb5 cred */
286         desired_mechs.count = 1;
287         desired_mechs.elements = &krb5oid;
288
289         maj_stat = gss_acquire_cred(&min_stat, NULL, 0,
290                                     &desired_mechs, GSS_C_INITIATE,
291                                     &credh, NULL, NULL);
292
293         if (maj_stat != GSS_S_COMPLETE) {
294                 pgsserr("gss_acquire_cred",
295                         maj_stat, min_stat, &krb5oid);
296                 return -1;
297         }
298
299         maj_stat = gss_set_allowable_enctypes(&min_stat, credh, &krb5oid,
300                                              num_enctypes, &enctypes);
301         if (maj_stat != GSS_S_COMPLETE) {
302                 pgsserr("gss_set_allowable_enctypes",
303                         maj_stat, min_stat, &krb5oid);
304                 gss_release_cred(&min_stat, &credh);
305                 return -1;
306         }
307         sec->cred = credh;
308
309         return 0;
310 }
311 #endif  /* HAVE_SET_ALLOWABLE_ENCTYPES */
312
313 /*
314  * Obtain credentials via a key in the keytab given
315  * a keytab handle and a gssd_k5_kt_princ structure.
316  * Checks to see if current credentials are expired,
317  * if not, uses the keytab to obtain new credentials.
318  *
319  * Returns:
320  *      0 => success (or credentials have not expired)
321  *      nonzero => error
322  */
323 static int
324 gssd_get_single_krb5_cred(krb5_context context,
325                           krb5_keytab kt,
326                           struct gssd_k5_kt_princ *ple)
327 {
328 #if HAVE_KRB5_GET_INIT_CREDS_OPT_SET_ADDRESSLESS
329         krb5_get_init_creds_opt *init_opts = NULL;
330 #else
331         krb5_get_init_creds_opt options;
332 #endif
333         krb5_get_init_creds_opt *opts;
334         krb5_creds my_creds;
335         krb5_ccache ccache = NULL;
336         char kt_name[BUFSIZ];
337         char cc_name[BUFSIZ];
338         int code;
339         time_t now = time(0);
340         char *cache_type;
341         char *pname = NULL;
342
343         memset(&my_creds, 0, sizeof(my_creds));
344
345         if (ple->ccname && ple->endtime > now) {
346                 printerr(2, "INFO: Credentials in CC '%s' are good until %d\n",
347                          ple->ccname, ple->endtime);
348                 code = 0;
349                 goto out;
350         }
351
352         if ((code = krb5_kt_get_name(context, kt, kt_name, BUFSIZ))) {
353                 printerr(0, "ERROR: Unable to get keytab name in "
354                             "gssd_get_single_krb5_cred\n");
355                 goto out;
356         }
357
358         if ((krb5_unparse_name(context, ple->princ, &pname)))
359                 pname = NULL;
360
361 #if HAVE_KRB5_GET_INIT_CREDS_OPT_SET_ADDRESSLESS
362         code = krb5_get_init_creds_opt_alloc(context, &init_opts);
363         if (code) {
364                 printerr(0, "ERROR: %s allocating gic options\n",
365                          gssd_k5_err_msg(context, code));
366                 goto out;
367         }
368         if (krb5_get_init_creds_opt_set_addressless(context, init_opts, 1))
369                 printerr(0, "WARNING: Unable to set option for addressless "
370                          "tickets.  May have problems behind a NAT.\n");
371 #ifdef TEST_SHORT_LIFETIME
372         /* set a short lifetime (for debugging only!) */
373         printerr(0, "WARNING: Using (debug) short machine cred lifetime!\n");
374         krb5_get_init_creds_opt_set_tkt_life(init_opts, 5*60);
375 #endif
376         opts = init_opts;
377
378 #else   /* HAVE_KRB5_GET_INIT_CREDS_OPT_SET_ADDRESSLESS */
379
380         krb5_get_init_creds_opt_init(&options);
381         krb5_get_init_creds_opt_set_address_list(&options, NULL);
382 #ifdef TEST_SHORT_LIFETIME
383         /* set a short lifetime (for debugging only!) */
384         printerr(0, "WARNING: Using (debug) short machine cred lifetime!\n");
385         krb5_get_init_creds_opt_set_tkt_life(&options, 5*60);
386 #endif
387         opts = &options;
388 #endif
389
390         if ((code = krb5_get_init_creds_keytab(context, &my_creds, ple->princ,
391                                                kt, 0, NULL, opts))) {
392                 printerr(0, "WARNING: %s while getting initial ticket for "
393                          "principal '%s' using keytab '%s'\n",
394                          gssd_k5_err_msg(context, code),
395                          pname ? pname : "<unparsable>", kt_name);
396                 goto out;
397         }
398
399         /*
400          * Initialize cache file which we're going to be using
401          */
402
403         if (use_memcache)
404             cache_type = "MEMORY";
405         else
406             cache_type = "FILE";
407         snprintf(cc_name, sizeof(cc_name), "%s:%s/%s%s_%s",
408                 cache_type,
409                 ccachedir, GSSD_DEFAULT_CRED_PREFIX,
410                 GSSD_DEFAULT_MACHINE_CRED_SUFFIX, ple->realm);
411         ple->endtime = my_creds.times.endtime;
412         if (ple->ccname != NULL)
413                 free(ple->ccname);
414         ple->ccname = strdup(cc_name);
415         if (ple->ccname == NULL) {
416                 printerr(0, "ERROR: no storage to duplicate credentials "
417                             "cache name '%s'\n", cc_name);
418                 code = ENOMEM;
419                 goto out;
420         }
421         if ((code = krb5_cc_resolve(context, cc_name, &ccache))) {
422                 printerr(0, "ERROR: %s while opening credential cache '%s'\n",
423                          gssd_k5_err_msg(context, code), cc_name);
424                 goto out;
425         }
426         if ((code = krb5_cc_initialize(context, ccache, ple->princ))) {
427                 printerr(0, "ERROR: %s while initializing credential "
428                          "cache '%s'\n", gssd_k5_err_msg(context, code),
429                          cc_name);
430                 goto out;
431         }
432         if ((code = krb5_cc_store_cred(context, ccache, &my_creds))) {
433                 printerr(0, "ERROR: %s while storing credentials in '%s'\n",
434                          gssd_k5_err_msg(context, code), cc_name);
435                 goto out;
436         }
437
438         code = 0;
439         printerr(2, "Successfully obtained machine credentials for "
440                  "principal '%s' stored in ccache '%s'\n", pname, cc_name);
441   out:
442 #if HAVE_KRB5_GET_INIT_CREDS_OPT_SET_ADDRESSLESS
443         if (init_opts)
444                 krb5_get_init_creds_opt_free(context, init_opts);
445 #endif
446         if (pname)
447                 k5_free_unparsed_name(context, pname);
448         if (ccache)
449                 krb5_cc_close(context, ccache);
450         krb5_free_cred_contents(context, &my_creds);
451         return (code);
452 }
453
454 /*
455  * Depending on the version of Kerberos, we either need to use
456  * a private function, or simply set the environment variable.
457  */
458 static void
459 gssd_set_krb5_ccache_name(char *ccname)
460 {
461 #ifdef USE_GSS_KRB5_CCACHE_NAME
462         u_int   maj_stat, min_stat;
463
464         printerr(2, "using gss_krb5_ccache_name to select krb5 ccache %s\n",
465                  ccname);
466         maj_stat = gss_krb5_ccache_name(&min_stat, ccname, NULL);
467         if (maj_stat != GSS_S_COMPLETE) {
468                 printerr(0, "WARNING: gss_krb5_ccache_name with "
469                         "name '%s' failed (%s)\n",
470                         ccname, error_message(min_stat));
471         }
472 #else
473         /*
474          * Set the KRB5CCNAME environment variable to tell the krb5 code
475          * which credentials cache to use.  (Instead of using the private
476          * function above for which there is no generic gssapi
477          * equivalent.)
478          */
479         printerr(2, "using environment variable to select krb5 ccache %s\n",
480                  ccname);
481         setenv("KRB5CCNAME", ccname, 1);
482 #endif
483 }
484
485 /*
486  * Given a principal, find a matching ple structure
487  */
488 static struct gssd_k5_kt_princ *
489 find_ple_by_princ(krb5_context context, krb5_principal princ)
490 {
491         struct gssd_k5_kt_princ *ple;
492
493         for (ple = gssd_k5_kt_princ_list; ple != NULL; ple = ple->next) {
494                 if (krb5_principal_compare(context, ple->princ, princ))
495                         return ple;
496         }
497         /* no match found */
498         return NULL;
499 }
500
501 /*
502  * Create, initialize, and add a new ple structure to the global list
503  */
504 static struct gssd_k5_kt_princ *
505 new_ple(krb5_context context, krb5_principal princ)
506 {
507         struct gssd_k5_kt_princ *ple = NULL, *p;
508         krb5_error_code code;
509         char *default_realm;
510         int is_default_realm = 0;
511
512         ple = malloc(sizeof(struct gssd_k5_kt_princ));
513         if (ple == NULL)
514                 goto outerr;
515         memset(ple, 0, sizeof(*ple));
516
517 #ifdef HAVE_KRB5
518         ple->realm = strndup(princ->realm.data,
519                              princ->realm.length);
520 #else
521         ple->realm = strdup(princ->realm);
522 #endif
523         if (ple->realm == NULL)
524                 goto outerr;
525         code = krb5_copy_principal(context, princ, &ple->princ);
526         if (code)
527                 goto outerr;
528
529         /*
530          * Add new entry onto the list (if this is the default
531          * realm, always add to the front of the list)
532          */
533
534         code = krb5_get_default_realm(context, &default_realm);
535         if (code == 0) {
536                 if (strcmp(ple->realm, default_realm) == 0)
537                         is_default_realm = 1;
538                 k5_free_default_realm(context, default_realm);
539         }
540
541         if (is_default_realm) {
542                 ple->next = gssd_k5_kt_princ_list;
543                 gssd_k5_kt_princ_list = ple;
544         } else {
545                 p = gssd_k5_kt_princ_list;
546                 while (p != NULL && p->next != NULL)
547                         p = p->next;
548                 if (p == NULL)
549                         gssd_k5_kt_princ_list = ple;
550                 else
551                         p->next = ple;
552         }
553
554         return ple;
555 outerr:
556         if (ple) {
557                 if (ple->realm)
558                         free(ple->realm);
559                 free(ple);
560         }
561         return NULL;
562 }
563
564 /*
565  * Given a principal, find an existing ple structure, or create one
566  */
567 static struct gssd_k5_kt_princ *
568 get_ple_by_princ(krb5_context context, krb5_principal princ)
569 {
570         struct gssd_k5_kt_princ *ple;
571
572         /* Need to serialize list if we ever become multi-threaded! */
573
574         ple = find_ple_by_princ(context, princ);
575         if (ple == NULL) {
576                 ple = new_ple(context, princ);
577         }
578
579         return ple;
580 }
581
582 /*
583  * Given a (possibly unqualified) hostname,
584  * return the fully qualified (lower-case!) hostname
585  */
586 static int
587 get_full_hostname(const char *inhost, char *outhost, int outhostlen)
588 {
589         struct addrinfo *addrs = NULL;
590         struct addrinfo hints;
591         int retval;
592         char *c;
593
594         memset(&hints, 0, sizeof(hints));
595         hints.ai_socktype = SOCK_STREAM;
596         hints.ai_family = PF_UNSPEC;
597         hints.ai_flags = AI_CANONNAME;
598
599         /* Get full target hostname */
600         retval = getaddrinfo(inhost, NULL, &hints, &addrs);
601         if (retval) {
602                 printerr(0, "%s while getting full hostname for '%s'\n",
603                          gai_strerror(retval), inhost);
604                 goto out;
605         }
606         strncpy(outhost, addrs->ai_canonname, outhostlen);
607         freeaddrinfo(addrs);
608         for (c = outhost; *c != '\0'; c++)
609             *c = tolower(*c);
610
611         printerr(3, "Full hostname for '%s' is '%s'\n", inhost, outhost);
612         retval = 0;
613 out:
614         return retval;
615 }
616
617 /* 
618  * If principal matches the given realm and service name,
619  * and has *any* instance (hostname), return 1.
620  * Otherwise return 0, indicating no match.
621  */
622 static int
623 realm_and_service_match(krb5_context context, krb5_principal p,
624                         const char *realm, const char *service)
625 {
626 #ifdef HAVE_KRB5
627         /* Must have two components */
628         if (p->length != 2)
629                 return 0;
630         if ((strlen(realm) == p->realm.length)
631             && (strncmp(realm, p->realm.data, p->realm.length) == 0)
632             && (strlen(service) == p->data[0].length)
633             && (strncmp(service, p->data[0].data, p->data[0].length) == 0))
634                 return 1;
635 #else
636         const char *name, *inst;
637
638         if (p->name.name_string.len != 2)
639                 return 0;
640         name = krb5_principal_get_comp_string(context, p, 0);
641         inst = krb5_principal_get_comp_string(context, p, 1);
642         if (name == NULL || inst == NULL)
643                 return 0;
644         if ((strcmp(realm, p->realm) == 0)
645             && (strcmp(service, name) == 0))
646                 return 1;
647 #endif
648         return 0;
649 }
650
651 /*
652  * Search the given keytab file looking for an entry with the given
653  * service name and realm, ignoring hostname (instance).
654  *
655  * Returns:
656  *      0 => No error
657  *      non-zero => An error occurred
658  *
659  * If a keytab entry is found, "found" is set to one, and the keytab
660  * entry is returned in "kte".  Otherwise, "found" is zero, and the
661  * value of "kte" is unpredictable.
662  */
663 static int
664 gssd_search_krb5_keytab(krb5_context context, krb5_keytab kt,
665                         const char *realm, const char *service,
666                         int *found, krb5_keytab_entry *kte)
667 {
668         krb5_kt_cursor cursor;
669         krb5_error_code code;
670         struct gssd_k5_kt_princ *ple;
671         int retval = -1;
672         char kt_name[BUFSIZ];
673         char *pname;
674
675         if (found == NULL) {
676                 retval = EINVAL;
677                 goto out;
678         }
679         *found = 0;
680
681         /*
682          * Look through each entry in the keytab file and determine
683          * if we might want to use it as machine credentials.  If so,
684          * save info in the global principal list (gssd_k5_kt_princ_list).
685          */
686         if ((code = krb5_kt_get_name(context, kt, kt_name, BUFSIZ))) {
687                 printerr(0, "ERROR: %s attempting to get keytab name\n",
688                          gssd_k5_err_msg(context, code));
689                 retval = code;
690                 goto out;
691         }
692         if ((code = krb5_kt_start_seq_get(context, kt, &cursor))) {
693                 printerr(0, "ERROR: %s while beginning keytab scan "
694                             "for keytab '%s'\n",
695                         gssd_k5_err_msg(context, code), kt_name);
696                 retval = code;
697                 goto out;
698         }
699
700         while ((code = krb5_kt_next_entry(context, kt, kte, &cursor)) == 0) {
701                 if ((code = krb5_unparse_name(context, kte->principal,
702                                               &pname))) {
703                         printerr(0, "WARNING: Skipping keytab entry because "
704                                  "we failed to unparse principal name: %s\n",
705                                  gssd_k5_err_msg(context, code));
706                         k5_free_kt_entry(context, kte);
707                         continue;
708                 }
709                 printerr(4, "Processing keytab entry for principal '%s'\n",
710                          pname);
711                 /* Use the first matching keytab entry found */
712                 if ((realm_and_service_match(context, kte->principal, realm,
713                                              service))) {
714                         printerr(4, "We WILL use this entry (%s)\n", pname);
715                         ple = get_ple_by_princ(context, kte->principal);
716                         /*
717                          * Return, don't free, keytab entry if
718                          * we were successful!
719                          */
720                         if (ple == NULL) {
721                                 retval = ENOMEM;
722                                 k5_free_kt_entry(context, kte);
723                         } else {
724                                 retval = 0;
725                                 *found = 1;
726                         }
727                         k5_free_unparsed_name(context, pname);
728                         break;
729                 }
730                 else {
731                         printerr(4, "We will NOT use this entry (%s)\n",
732                                 pname);
733                 }
734                 k5_free_unparsed_name(context, pname);
735                 k5_free_kt_entry(context, kte);
736         }
737
738         if ((code = krb5_kt_end_seq_get(context, kt, &cursor))) {
739                 printerr(0, "WARNING: %s while ending keytab scan for "
740                             "keytab '%s'\n",
741                          gssd_k5_err_msg(context, code), kt_name);
742         }
743
744         retval = 0;
745   out:
746         return retval;
747 }
748
749 /*
750  * Find a keytab entry to use for a given target hostname.
751  * Tries to find the most appropriate keytab to use given the
752  * name of the host we are trying to connect with.
753  */
754 static int
755 find_keytab_entry(krb5_context context, krb5_keytab kt, const char *hostname,
756                   krb5_keytab_entry *kte)
757 {
758         krb5_error_code code;
759         const char *svcnames[] = { "root", "nfs", "host", NULL };
760         char **realmnames = NULL;
761         char myhostname[NI_MAXHOST], targethostname[NI_MAXHOST];
762         int i, j, retval;
763         char *default_realm = NULL;
764         char *realm;
765         int tried_all = 0, tried_default = 0;
766         krb5_principal princ;
767
768
769         /* Get full target hostname */
770         retval = get_full_hostname(hostname, targethostname,
771                                    sizeof(targethostname));
772         if (retval)
773                 goto out;
774
775         /* Get full local hostname */
776         retval = gethostname(myhostname, sizeof(myhostname));
777         if (retval) {
778                 printerr(1, "%s while getting local hostname\n",
779                          gssd_k5_err_msg(context, retval));
780                 goto out;
781         }
782         retval = get_full_hostname(myhostname, myhostname, sizeof(myhostname));
783         if (retval)
784                 goto out;
785
786         code = krb5_get_default_realm(context, &default_realm);
787         if (code) {
788                 retval = code;
789                 printerr(1, "%s while getting default realm name\n",
790                          gssd_k5_err_msg(context, code));
791                 goto out;
792         }
793
794         /*
795          * Get the realm name(s) for the target hostname.
796          * In reality, this function currently only returns a
797          * single realm, but we code with the assumption that
798          * someday it may actually return a list.
799          */
800         code = krb5_get_host_realm(context, targethostname, &realmnames);
801         if (code) {
802                 printerr(0, "ERROR: %s while getting realm(s) for host '%s'\n",
803                          gssd_k5_err_msg(context, code), targethostname);
804                 retval = code;
805                 goto out;
806         }
807
808         /*
809          * Try the "appropriate" realm first, and if nothing found for that
810          * realm, try the default realm (if it hasn't already been tried).
811          */
812         i = 0;
813         realm = realmnames[i];
814         while (1) {
815                 if (realm == NULL) {
816                         tried_all = 1;
817                         if (!tried_default)
818                                 realm = default_realm;
819                 }
820                 if (tried_all && tried_default)
821                         break;
822                 if (strcmp(realm, default_realm) == 0)
823                         tried_default = 1;
824                 for (j = 0; svcnames[j] != NULL; j++) {
825                         code = krb5_build_principal_ext(context, &princ,
826                                                         strlen(realm),
827                                                         realm,
828                                                         strlen(svcnames[j]),
829                                                         svcnames[j],
830                                                         strlen(myhostname),
831                                                         myhostname,
832                                                         NULL);
833                         if (code) {
834                                 printerr(1, "%s while building principal for "
835                                          "'%s/%s@%s'\n",
836                                          gssd_k5_err_msg(context, code),
837                                          svcnames[j], myhostname, realm);
838                                 continue;
839                         }
840                         code = krb5_kt_get_entry(context, kt, princ, 0, 0, kte);
841                         krb5_free_principal(context, princ);
842                         if (code) {
843                                 printerr(3, "%s while getting keytab entry for "
844                                          "'%s/%s@%s'\n",
845                                          gssd_k5_err_msg(context, code),
846                                          svcnames[j], myhostname, realm);
847                         } else {
848                                 printerr(3, "Success getting keytab entry for "
849                                          "'%s/%s@%s'\n",
850                                          svcnames[j], myhostname, realm);
851                                 retval = 0;
852                                 goto out;
853                         }
854                         retval = code;
855                 }
856                 /*
857                  * Nothing found with our hostname instance, now look for
858                  * names with any instance (they must have an instance)
859                  */
860                 for (j = 0; svcnames[j] != NULL; j++) {
861                         int found = 0;
862                         code = gssd_search_krb5_keytab(context, kt, realm,
863                                                        svcnames[j], &found, kte);
864                         if (!code && found) {
865                                 printerr(3, "Success getting keytab entry for "
866                                          "%s/*@%s\n", svcnames[j], realm);
867                                 retval = 0;
868                                 goto out;
869                         }
870                 }
871                 if (!tried_all) {
872                         i++;
873                         realm = realmnames[i];
874                 }
875         }
876 out:
877         if (default_realm)
878                 k5_free_default_realm(context, default_realm);
879         if (realmnames)
880                 krb5_free_host_realm(context, realmnames);
881         return retval;
882 }
883
884 /*==========================*/
885 /*===  External routines ===*/
886 /*==========================*/
887
888 /*
889  * Attempt to find the best match for a credentials cache file
890  * given only a UID.  We really need more information, but we
891  * do the best we can.
892  *
893  * Returns:
894  *      void
895  */
896 void
897 gssd_setup_krb5_user_gss_ccache(uid_t uid, char *servername)
898 {
899         char                    buf[MAX_NETOBJ_SZ];
900         struct dirent           *d;
901
902         printerr(2, "getting credentials for client with uid %u for "
903                     "server %s\n", uid, servername);
904         memset(buf, 0, sizeof(buf));
905         if (gssd_find_existing_krb5_ccache(uid, &d)) {
906                 snprintf(buf, sizeof(buf), "FILE:%s/%s",
907                         ccachedir, d->d_name);
908                 free(d);
909         }
910         else
911                 snprintf(buf, sizeof(buf), "FILE:%s/%s%u",
912                         ccachedir, GSSD_DEFAULT_CRED_PREFIX, uid);
913         printerr(2, "using %s as credentials cache for client with "
914                     "uid %u for server %s\n", buf, uid, servername);
915         gssd_set_krb5_ccache_name(buf);
916 }
917
918 /*
919  * Let the gss code know where to find the machine credentials ccache.
920  *
921  * Returns:
922  *      void
923  */
924 void
925 gssd_setup_krb5_machine_gss_ccache(char *ccname)
926 {
927         printerr(2, "using %s as credentials cache for machine creds\n",
928                  ccname);
929         gssd_set_krb5_ccache_name(ccname);
930 }
931
932 /*
933  * Return an array of pointers to names of credential cache files
934  * which can be used to try to create gss contexts with a server.
935  *
936  * Returns:
937  *      0 => list is attached
938  *      nonzero => error
939  */
940 int
941 gssd_get_krb5_machine_cred_list(char ***list)
942 {
943         char **l;
944         int listinc = 10;
945         int listsize = listinc;
946         int i = 0;
947         int retval;
948         struct gssd_k5_kt_princ *ple;
949
950         /* Assume failure */
951         retval = -1;
952         *list = (char **) NULL;
953
954         if ((l = (char **) malloc(listsize * sizeof(char *))) == NULL) {
955                 retval = ENOMEM;
956                 goto out;
957         }
958
959         /* Need to serialize list if we ever become multi-threaded! */
960
961         for (ple = gssd_k5_kt_princ_list; ple; ple = ple->next) {
962                 if (ple->ccname) {
963                         /* Make sure cred is up-to-date before returning it */
964                         retval = gssd_refresh_krb5_machine_credential(NULL, ple);
965                         if (retval)
966                                 continue;
967                         if (i + 1 > listsize) {
968                                 listsize += listinc;
969                                 l = (char **)
970                                         realloc(l, listsize * sizeof(char *));
971                                 if (l == NULL) {
972                                         retval = ENOMEM;
973                                         goto out;
974                                 }
975                         }
976                         if ((l[i++] = strdup(ple->ccname)) == NULL) {
977                                 retval = ENOMEM;
978                                 goto out;
979                         }
980                 }
981         }
982         if (i > 0) {
983                 l[i] = NULL;
984                 *list = l;
985                 retval = 0;
986                 goto out;
987         }
988   out:
989         return retval;
990 }
991
992 /*
993  * Frees the list of names returned in get_krb5_machine_cred_list()
994  */
995 void
996 gssd_free_krb5_machine_cred_list(char **list)
997 {
998         char **n;
999
1000         if (list == NULL)
1001                 return;
1002         for (n = list; n && *n; n++) {
1003                 free(*n);
1004         }
1005         free(list);
1006 }
1007
1008 /*
1009  * Called upon exit.  Destroys machine credentials.
1010  */
1011 void
1012 gssd_destroy_krb5_machine_creds(void)
1013 {
1014         krb5_context context;
1015         krb5_error_code code = 0;
1016         krb5_ccache ccache;
1017         struct gssd_k5_kt_princ *ple;
1018
1019         code = krb5_init_context(&context);
1020         if (code) {
1021                 printerr(0, "ERROR: %s while initializing krb5\n",
1022                          gssd_k5_err_msg(NULL, code));
1023                 goto out;
1024         }
1025
1026         for (ple = gssd_k5_kt_princ_list; ple; ple = ple->next) {
1027                 if (!ple->ccname)
1028                         continue;
1029                 if ((code = krb5_cc_resolve(context, ple->ccname, &ccache))) {
1030                         printerr(0, "WARNING: %s while resolving credential "
1031                                     "cache '%s' for destruction\n",
1032                                  gssd_k5_err_msg(context, code), ple->ccname);
1033                         continue;
1034                 }
1035
1036                 if ((code = krb5_cc_destroy(context, ccache))) {
1037                         printerr(0, "WARNING: %s while destroying credential "
1038                                     "cache '%s'\n",
1039                                  gssd_k5_err_msg(context, code), ple->ccname);
1040                 }
1041         }
1042   out:
1043         krb5_free_context(context);
1044 }
1045
1046 /*
1047  * Obtain (or refresh if necessary) Kerberos machine credentials
1048  */
1049 int
1050 gssd_refresh_krb5_machine_credential(char *hostname,
1051                                      struct gssd_k5_kt_princ *ple)
1052 {
1053         krb5_error_code code = 0;
1054         krb5_context context;
1055         krb5_keytab kt = NULL;;
1056         int retval = 0;
1057
1058         if (hostname == NULL && ple == NULL)
1059                 return EINVAL;
1060
1061         code = krb5_init_context(&context);
1062         if (code) {
1063                 printerr(0, "ERROR: %s: %s while initializing krb5 context\n",
1064                          __FUNCTION__, gssd_k5_err_msg(NULL, code));
1065                 retval = code;
1066                 goto out;
1067         }
1068
1069         if ((code = krb5_kt_resolve(context, keytabfile, &kt))) {
1070                 printerr(0, "ERROR: %s: %s while resolving keytab '%s'\n",
1071                          __FUNCTION__, gssd_k5_err_msg(context, code),
1072                          keytabfile);
1073                 goto out;
1074         }
1075
1076         if (ple == NULL) {
1077                 krb5_keytab_entry kte;
1078
1079                 code = find_keytab_entry(context, kt, hostname, &kte);
1080                 if (code) {
1081                         printerr(0, "ERROR: %s: no usable keytab entry found "
1082                                  "in keytab %s for connection with host %s\n",
1083                                  __FUNCTION__, keytabfile, hostname);
1084                         retval = code;
1085                         goto out;
1086                 }
1087
1088                 ple = get_ple_by_princ(context, kte.principal);
1089                 k5_free_kt_entry(context, &kte);
1090                 if (ple == NULL) {
1091                         char *pname;
1092                         if ((krb5_unparse_name(context, kte.principal, &pname))) {
1093                                 pname = NULL;
1094                         }
1095                         printerr(0, "ERROR: %s: Could not locate or create "
1096                                  "ple struct for principal %s for connection "
1097                                  "with host %s\n",
1098                                  __FUNCTION__, pname ? pname : "<unparsable>",
1099                                  hostname);
1100                         if (pname) k5_free_unparsed_name(context, pname);
1101                         goto out;
1102                 }
1103         }
1104         retval = gssd_get_single_krb5_cred(context, kt, ple);
1105 out:
1106         if (kt)
1107                 krb5_kt_close(context, kt);
1108         krb5_free_context(context);
1109         return retval;
1110 }
1111
1112 /*
1113  * A common routine for getting the Kerberos error message
1114  */
1115 const char *
1116 gssd_k5_err_msg(krb5_context context, krb5_error_code code)
1117 {
1118         const char *msg = NULL;
1119 #if HAVE_KRB5_GET_ERROR_MESSAGE
1120         if (context != NULL)
1121                 msg = krb5_get_error_message(context, code);
1122 #endif
1123         if (msg != NULL)
1124                 return msg;
1125 #if HAVE_KRB5
1126         return error_message(code);
1127 #else
1128         if (context != NULL)
1129                 return krb5_get_err_text(context, code);
1130         else
1131                 return error_message(code);
1132 #endif
1133 }