]> git.decadent.org.uk Git - nfs-utils.git/blob - utils/gssd/krb5_util.c
87bd7e41cc563d89527c7b56b4c50c98fc7edbbd
[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         int num_enctypes = sizeof(enctypes) / sizeof(enctypes[0]);
282
283         /* We only care about getting a krb5 cred */
284         desired_mechs.count = 1;
285         desired_mechs.elements = &krb5oid;
286
287         maj_stat = gss_acquire_cred(&min_stat, NULL, 0,
288                                     &desired_mechs, GSS_C_INITIATE,
289                                     &credh, NULL, NULL);
290
291         if (maj_stat != GSS_S_COMPLETE) {
292                 pgsserr("gss_acquire_cred",
293                         maj_stat, min_stat, &krb5oid);
294                 return -1;
295         }
296
297         maj_stat = gss_set_allowable_enctypes(&min_stat, credh, &krb5oid,
298                                              num_enctypes, &enctypes);
299         if (maj_stat != GSS_S_COMPLETE) {
300                 pgsserr("gss_set_allowable_enctypes",
301                         maj_stat, min_stat, &krb5oid);
302                 gss_release_cred(&min_stat, &credh);
303                 return -1;
304         }
305         sec->cred = credh;
306
307         return 0;
308 }
309 #endif  /* HAVE_SET_ALLOWABLE_ENCTYPES */
310
311 /*
312  * Obtain credentials via a key in the keytab given
313  * a keytab handle and a gssd_k5_kt_princ structure.
314  * Checks to see if current credentials are expired,
315  * if not, uses the keytab to obtain new credentials.
316  *
317  * Returns:
318  *      0 => success (or credentials have not expired)
319  *      nonzero => error
320  */
321 static int
322 gssd_get_single_krb5_cred(krb5_context context,
323                           krb5_keytab kt,
324                           struct gssd_k5_kt_princ *ple)
325 {
326         krb5_get_init_creds_opt options;
327         krb5_creds my_creds;
328         krb5_ccache ccache = NULL;
329         char kt_name[BUFSIZ];
330         char cc_name[BUFSIZ];
331         int code;
332         time_t now = time(0);
333         char *cache_type;
334         char *pname = NULL;
335
336         memset(&my_creds, 0, sizeof(my_creds));
337
338         if (ple->ccname && ple->endtime > now) {
339                 printerr(2, "INFO: Credentials in CC '%s' are good until %d\n",
340                          ple->ccname, ple->endtime);
341                 code = 0;
342                 goto out;
343         }
344
345         if ((code = krb5_kt_get_name(context, kt, kt_name, BUFSIZ))) {
346                 printerr(0, "ERROR: Unable to get keytab name in "
347                             "gssd_get_single_krb5_cred\n");
348                 goto out;
349         }
350
351         if ((krb5_unparse_name(context, ple->princ, &pname)))
352                 pname = NULL;
353
354         krb5_get_init_creds_opt_init(&options);
355         krb5_get_init_creds_opt_set_address_list(&options, NULL);
356
357 #ifdef TEST_SHORT_LIFETIME
358         /* set a short lifetime (for debugging only!) */
359         printerr(0, "WARNING: Using (debug) short machine cred lifetime!\n");
360         krb5_get_init_creds_opt_set_tkt_life(&options, 5*60);
361 #endif
362         if ((code = krb5_get_init_creds_keytab(context, &my_creds, ple->princ,
363                                                kt, 0, NULL, &options))) {
364                 printerr(0, "WARNING: %s while getting initial ticket for "
365                          "principal '%s' using keytab '%s'\n",
366                          gssd_k5_err_msg(context, code),
367                          pname ? pname : "<unparsable>", kt_name);
368                 goto out;
369         }
370
371         /*
372          * Initialize cache file which we're going to be using
373          */
374
375         if (use_memcache)
376             cache_type = "MEMORY";
377         else
378             cache_type = "FILE";
379         snprintf(cc_name, sizeof(cc_name), "%s:%s/%s%s_%s",
380                 cache_type,
381                 GSSD_DEFAULT_CRED_DIR, GSSD_DEFAULT_CRED_PREFIX,
382                 GSSD_DEFAULT_MACHINE_CRED_SUFFIX, ple->realm);
383         ple->endtime = my_creds.times.endtime;
384         if (ple->ccname != NULL)
385                 free(ple->ccname);
386         ple->ccname = strdup(cc_name);
387         if (ple->ccname == NULL) {
388                 printerr(0, "ERROR: no storage to duplicate credentials "
389                             "cache name '%s'\n", cc_name);
390                 code = ENOMEM;
391                 goto out;
392         }
393         if ((code = krb5_cc_resolve(context, cc_name, &ccache))) {
394                 printerr(0, "ERROR: %s while opening credential cache '%s'\n",
395                          gssd_k5_err_msg(context, code), cc_name);
396                 goto out;
397         }
398         if ((code = krb5_cc_initialize(context, ccache, ple->princ))) {
399                 printerr(0, "ERROR: %s while initializing credential "
400                          "cache '%s'\n", gssd_k5_err_msg(context, code),
401                          cc_name);
402                 goto out;
403         }
404         if ((code = krb5_cc_store_cred(context, ccache, &my_creds))) {
405                 printerr(0, "ERROR: %s while storing credentials in '%s'\n",
406                          gssd_k5_err_msg(context, code), cc_name);
407                 goto out;
408         }
409
410         code = 0;
411         printerr(2, "Successfully obtained machine credentials for "
412                  "principal '%s' stored in ccache '%s'\n", pname, cc_name);
413   out:
414         if (pname)
415                 k5_free_unparsed_name(context, pname);
416         if (ccache)
417                 krb5_cc_close(context, ccache);
418         krb5_free_cred_contents(context, &my_creds);
419         return (code);
420 }
421
422 /*
423  * Depending on the version of Kerberos, we either need to use
424  * a private function, or simply set the environment variable.
425  */
426 static void
427 gssd_set_krb5_ccache_name(char *ccname)
428 {
429 #ifdef USE_GSS_KRB5_CCACHE_NAME
430         u_int   maj_stat, min_stat;
431
432         printerr(2, "using gss_krb5_ccache_name to select krb5 ccache %s\n",
433                  ccname);
434         maj_stat = gss_krb5_ccache_name(&min_stat, ccname, NULL);
435         if (maj_stat != GSS_S_COMPLETE) {
436                 printerr(0, "WARNING: gss_krb5_ccache_name with "
437                         "name '%s' failed (%s)\n",
438                         ccname, error_message(min_stat));
439         }
440 #else
441         /*
442          * Set the KRB5CCNAME environment variable to tell the krb5 code
443          * which credentials cache to use.  (Instead of using the private
444          * function above for which there is no generic gssapi
445          * equivalent.)
446          */
447         printerr(2, "using environment variable to select krb5 ccache %s\n",
448                  ccname);
449         setenv("KRB5CCNAME", ccname, 1);
450 #endif
451 }
452
453 /*
454  * Given a principal, find a matching ple structure
455  */
456 static struct gssd_k5_kt_princ *
457 find_ple_by_princ(krb5_context context, krb5_principal princ)
458 {
459         struct gssd_k5_kt_princ *ple;
460
461         for (ple = gssd_k5_kt_princ_list; ple != NULL; ple = ple->next) {
462                 if (krb5_principal_compare(context, ple->princ, princ))
463                         return ple;
464         }
465         /* no match found */
466         return NULL;
467 }
468
469 /*
470  * Create, initialize, and add a new ple structure to the global list
471  */
472 static struct gssd_k5_kt_princ *
473 new_ple(krb5_context context, krb5_principal princ)
474 {
475         struct gssd_k5_kt_princ *ple = NULL, *p;
476         krb5_error_code code;
477         char *default_realm;
478         int is_default_realm = 0;
479
480         ple = malloc(sizeof(struct gssd_k5_kt_princ));
481         if (ple == NULL)
482                 goto outerr;
483         memset(ple, 0, sizeof(*ple));
484
485 #ifdef HAVE_KRB5
486         ple->realm = strndup(princ->realm.data,
487                              princ->realm.length);
488 #else
489         ple->realm = strdup(princ->realm);
490 #endif
491         if (ple->realm == NULL)
492                 goto outerr;
493         code = krb5_copy_principal(context, princ, &ple->princ);
494         if (code)
495                 goto outerr;
496
497         /*
498          * Add new entry onto the list (if this is the default
499          * realm, always add to the front of the list)
500          */
501
502         code = krb5_get_default_realm(context, &default_realm);
503         if (code == 0) {
504                 if (strcmp(ple->realm, default_realm) == 0)
505                         is_default_realm = 1;
506                 k5_free_default_realm(context, default_realm);
507         }
508
509         if (is_default_realm) {
510                 ple->next = gssd_k5_kt_princ_list;
511                 gssd_k5_kt_princ_list = ple;
512         } else {
513                 p = gssd_k5_kt_princ_list;
514                 while (p != NULL && p->next != NULL)
515                         p = p->next;
516                 if (p == NULL)
517                         gssd_k5_kt_princ_list = ple;
518                 else
519                         p->next = ple;
520         }
521
522         return ple;
523 outerr:
524         if (ple) {
525                 if (ple->realm)
526                         free(ple->realm);
527                 free(ple);
528         }
529         return NULL;
530 }
531
532 /*
533  * Given a principal, find an existing ple structure, or create one
534  */
535 static struct gssd_k5_kt_princ *
536 get_ple_by_princ(krb5_context context, krb5_principal princ)
537 {
538         struct gssd_k5_kt_princ *ple;
539
540         /* Need to serialize list if we ever become multi-threaded! */
541
542         ple = find_ple_by_princ(context, princ);
543         if (ple == NULL) {
544                 ple = new_ple(context, princ);
545         }
546
547         return ple;
548 }
549
550 /*
551  * Given a (possibly unqualified) hostname,
552  * return the fully qualified (lower-case!) hostname
553  */
554 static int
555 get_full_hostname(const char *inhost, char *outhost, int outhostlen)
556 {
557         struct addrinfo *addrs = NULL;
558         struct addrinfo hints;
559         int retval;
560         char *c;
561
562         memset(&hints, 0, sizeof(hints));
563         hints.ai_socktype = SOCK_STREAM;
564         hints.ai_family = PF_UNSPEC;
565         hints.ai_flags = AI_CANONNAME;
566
567         /* Get full target hostname */
568         retval = getaddrinfo(inhost, NULL, &hints, &addrs);
569         if (retval) {
570                 printerr(0, "%s while getting full hostname for '%s'\n",
571                          gai_strerror(retval), inhost);
572                 goto out;
573         }
574         strncpy(outhost, addrs->ai_canonname, outhostlen);
575         freeaddrinfo(addrs);
576         for (c = outhost; *c != '\0'; c++)
577             *c = tolower(*c);
578
579         printerr(3, "Full hostname for '%s' is '%s'\n", inhost, outhost);
580         retval = 0;
581 out:
582         return retval;
583 }
584
585 /* 
586  * If principal matches the given realm and service name,
587  * and has *any* instance (hostname), return 1.
588  * Otherwise return 0, indicating no match.
589  */
590 static int
591 realm_and_service_match(krb5_context context, krb5_principal p,
592                         const char *realm, const char *service)
593 {
594 #ifdef HAVE_KRB5
595         /* Must have two components */
596         if (p->length != 2)
597                 return 0;
598         if ((strlen(realm) == p->realm.length)
599             && (strncmp(realm, p->realm.data, p->realm.length) == 0)
600             && (strlen(service) == p->data[0].length)
601             && (strncmp(service, p->data[0].data, p->data[0].length) == 0))
602                 return 1;
603 #else
604         const char *name, *inst;
605
606         if (p->name.name_string.len != 2)
607                 return 0;
608         name = krb5_principal_get_comp_string(context, p, 0);
609         inst = krb5_principal_get_comp_string(context, p, 1);
610         if (name == NULL || inst == NULL)
611                 return 0;
612         if ((strcmp(realm, p->realm) == 0)
613             && (strcmp(service, name) == 0))
614                 return 1;
615 #endif
616         return 0;
617 }
618
619 /*
620  * Search the given keytab file looking for an entry with the given
621  * service name and realm, ignoring hostname (instance).
622  *
623  * Returns:
624  *      0 => No error
625  *      non-zero => An error occurred
626  *
627  * If a keytab entry is found, "found" is set to one, and the keytab
628  * entry is returned in "kte".  Otherwise, "found" is zero, and the
629  * value of "kte" is unpredictable.
630  */
631 static int
632 gssd_search_krb5_keytab(krb5_context context, krb5_keytab kt,
633                         const char *realm, const char *service,
634                         int *found, krb5_keytab_entry *kte)
635 {
636         krb5_kt_cursor cursor;
637         krb5_error_code code;
638         struct gssd_k5_kt_princ *ple;
639         int retval = -1;
640         char kt_name[BUFSIZ];
641         char *pname;
642
643         if (found == NULL) {
644                 retval = EINVAL;
645                 goto out;
646         }
647         *found = 0;
648
649         /*
650          * Look through each entry in the keytab file and determine
651          * if we might want to use it as machine credentials.  If so,
652          * save info in the global principal list (gssd_k5_kt_princ_list).
653          */
654         if ((code = krb5_kt_get_name(context, kt, kt_name, BUFSIZ))) {
655                 printerr(0, "ERROR: %s attempting to get keytab name\n",
656                          gssd_k5_err_msg(context, code));
657                 retval = code;
658                 goto out;
659         }
660         if ((code = krb5_kt_start_seq_get(context, kt, &cursor))) {
661                 printerr(0, "ERROR: %s while beginning keytab scan "
662                             "for keytab '%s'\n",
663                         gssd_k5_err_msg(context, code), kt_name);
664                 retval = code;
665                 goto out;
666         }
667
668         while ((code = krb5_kt_next_entry(context, kt, kte, &cursor)) == 0) {
669                 if ((code = krb5_unparse_name(context, kte->principal,
670                                               &pname))) {
671                         printerr(0, "WARNING: Skipping keytab entry because "
672                                  "we failed to unparse principal name: %s\n",
673                                  gssd_k5_err_msg(context, code));
674                         k5_free_kt_entry(context, kte);
675                         continue;
676                 }
677                 printerr(4, "Processing keytab entry for principal '%s'\n",
678                          pname);
679                 /* Use the first matching keytab entry found */
680                 if ((realm_and_service_match(context, kte->principal, realm,
681                                              service))) {
682                         printerr(4, "We WILL use this entry (%s)\n", pname);
683                         ple = get_ple_by_princ(context, kte->principal);
684                         /*
685                          * Return, don't free, keytab entry if
686                          * we were successful!
687                          */
688                         if (ple == NULL) {
689                                 retval = ENOMEM;
690                                 k5_free_kt_entry(context, kte);
691                         } else {
692                                 retval = 0;
693                                 *found = 1;
694                         }
695                         k5_free_unparsed_name(context, pname);
696                         break;
697                 }
698                 else {
699                         printerr(4, "We will NOT use this entry (%s)\n",
700                                 pname);
701                 }
702                 k5_free_unparsed_name(context, pname);
703                 k5_free_kt_entry(context, kte);
704         }
705
706         if ((code = krb5_kt_end_seq_get(context, kt, &cursor))) {
707                 printerr(0, "WARNING: %s while ending keytab scan for "
708                             "keytab '%s'\n",
709                          gssd_k5_err_msg(context, code), kt_name);
710         }
711
712         retval = 0;
713   out:
714         return retval;
715 }
716
717 /*
718  * Find a keytab entry to use for a given target hostname.
719  * Tries to find the most appropriate keytab to use given the
720  * name of the host we are trying to connect with.
721  */
722 static int
723 find_keytab_entry(krb5_context context, krb5_keytab kt, const char *hostname,
724                   krb5_keytab_entry *kte)
725 {
726         krb5_error_code code;
727         const char *svcnames[] = { "root", "nfs", "host", NULL };
728         char **realmnames = NULL;
729         char myhostname[NI_MAXHOST], targethostname[NI_MAXHOST];
730         int i, j, retval;
731         char *default_realm = NULL;
732         char *realm;
733         int tried_all = 0, tried_default = 0;
734         krb5_principal princ;
735
736
737         /* Get full target hostname */
738         retval = get_full_hostname(hostname, targethostname,
739                                    sizeof(targethostname));
740         if (retval)
741                 goto out;
742
743         /* Get full local hostname */
744         retval = gethostname(myhostname, sizeof(myhostname));
745         if (retval) {
746                 printerr(1, "%s while getting local hostname\n",
747                          gssd_k5_err_msg(context, retval));
748                 goto out;
749         }
750         retval = get_full_hostname(myhostname, myhostname, sizeof(myhostname));
751         if (retval)
752                 goto out;
753
754         code = krb5_get_default_realm(context, &default_realm);
755         if (code) {
756                 retval = code;
757                 printerr(1, "%s while getting default realm name\n",
758                          gssd_k5_err_msg(context, code));
759                 goto out;
760         }
761
762         /*
763          * Get the realm name(s) for the target hostname.
764          * In reality, this function currently only returns a
765          * single realm, but we code with the assumption that
766          * someday it may actually return a list.
767          */
768         code = krb5_get_host_realm(context, targethostname, &realmnames);
769         if (code) {
770                 printerr(0, "ERROR: %s while getting realm(s) for host '%s'\n",
771                          gssd_k5_err_msg(context, code), targethostname);
772                 retval = code;
773                 goto out;
774         }
775
776         /*
777          * Try the "appropriate" realm first, and if nothing found for that
778          * realm, try the default realm (if it hasn't already been tried).
779          */
780         i = 0;
781         realm = realmnames[i];
782         while (1) {
783                 if (realm == NULL) {
784                         tried_all = 1;
785                         if (!tried_default)
786                                 realm = default_realm;
787                 }
788                 if (tried_all && tried_default)
789                         break;
790                 if (strcmp(realm, default_realm) == 0)
791                         tried_default = 1;
792                 for (j = 0; svcnames[j] != NULL; j++) {
793                         code = krb5_build_principal_ext(context, &princ,
794                                                         strlen(realm),
795                                                         realm,
796                                                         strlen(svcnames[j]),
797                                                         svcnames[j],
798                                                         strlen(myhostname),
799                                                         myhostname,
800                                                         NULL);
801                         if (code) {
802                                 printerr(1, "%s while building principal for "
803                                          "'%s/%s@%s'\n",
804                                          gssd_k5_err_msg(context, code),
805                                          svcnames[j], myhostname, realm);
806                                 continue;
807                         }
808                         code = krb5_kt_get_entry(context, kt, princ, 0, 0, kte);
809                         krb5_free_principal(context, princ);
810                         if (code) {
811                                 printerr(3, "%s while getting keytab entry for "
812                                          "'%s/%s@%s'\n",
813                                          gssd_k5_err_msg(context, code),
814                                          svcnames[j], myhostname, realm);
815                         } else {
816                                 printerr(3, "Success getting keytab entry for "
817                                          "'%s/%s@%s'\n",
818                                          svcnames[j], myhostname, realm);
819                                 retval = 0;
820                                 goto out;
821                         }
822                         retval = code;
823                 }
824                 /*
825                  * Nothing found with our hostname instance, now look for
826                  * names with any instance (they must have an instance)
827                  */
828                 for (j = 0; svcnames[j] != NULL; j++) {
829                         int found = 0;
830                         code = gssd_search_krb5_keytab(context, kt, realm,
831                                                        svcnames[j], &found, kte);
832                         if (!code && found) {
833                                 printerr(3, "Success getting keytab entry for "
834                                          "%s/*@%s\n", svcnames[j], realm);
835                                 retval = 0;
836                                 goto out;
837                         }
838                 }
839                 if (!tried_all) {
840                         i++;
841                         realm = realmnames[i];
842                 }
843         }
844 out:
845         if (default_realm)
846                 k5_free_default_realm(context, default_realm);
847         if (realmnames)
848                 krb5_free_host_realm(context, realmnames);
849         return retval;
850 }
851
852 /*==========================*/
853 /*===  External routines ===*/
854 /*==========================*/
855
856 /*
857  * Attempt to find the best match for a credentials cache file
858  * given only a UID.  We really need more information, but we
859  * do the best we can.
860  *
861  * Returns:
862  *      void
863  */
864 void
865 gssd_setup_krb5_user_gss_ccache(uid_t uid, char *servername)
866 {
867         char                    buf[MAX_NETOBJ_SZ];
868         struct dirent           *d;
869
870         printerr(2, "getting credentials for client with uid %u for "
871                     "server %s\n", uid, servername);
872         memset(buf, 0, sizeof(buf));
873         if (gssd_find_existing_krb5_ccache(uid, &d)) {
874                 snprintf(buf, sizeof(buf), "FILE:%s/%s",
875                         ccachedir, d->d_name);
876                 free(d);
877         }
878         else
879                 snprintf(buf, sizeof(buf), "FILE:%s/%s%u",
880                         ccachedir, GSSD_DEFAULT_CRED_PREFIX, uid);
881         printerr(2, "using %s as credentials cache for client with "
882                     "uid %u for server %s\n", buf, uid, servername);
883         gssd_set_krb5_ccache_name(buf);
884 }
885
886 /*
887  * Let the gss code know where to find the machine credentials ccache.
888  *
889  * Returns:
890  *      void
891  */
892 void
893 gssd_setup_krb5_machine_gss_ccache(char *ccname)
894 {
895         printerr(2, "using %s as credentials cache for machine creds\n",
896                  ccname);
897         gssd_set_krb5_ccache_name(ccname);
898 }
899
900 /*
901  * Return an array of pointers to names of credential cache files
902  * which can be used to try to create gss contexts with a server.
903  *
904  * Returns:
905  *      0 => list is attached
906  *      nonzero => error
907  */
908 int
909 gssd_get_krb5_machine_cred_list(char ***list)
910 {
911         char **l;
912         int listinc = 10;
913         int listsize = listinc;
914         int i = 0;
915         int retval;
916         struct gssd_k5_kt_princ *ple;
917
918         /* Assume failure */
919         retval = -1;
920         *list = (char **) NULL;
921
922         if ((l = (char **) malloc(listsize * sizeof(char *))) == NULL) {
923                 retval = ENOMEM;
924                 goto out;
925         }
926
927         /* Need to serialize list if we ever become multi-threaded! */
928
929         for (ple = gssd_k5_kt_princ_list; ple; ple = ple->next) {
930                 if (ple->ccname) {
931                         /* Make sure cred is up-to-date before returning it */
932                         retval = gssd_refresh_krb5_machine_credential(NULL, ple);
933                         if (retval)
934                                 continue;
935                         if (i + 1 > listsize) {
936                                 listsize += listinc;
937                                 l = (char **)
938                                         realloc(l, listsize * sizeof(char *));
939                                 if (l == NULL) {
940                                         retval = ENOMEM;
941                                         goto out;
942                                 }
943                         }
944                         if ((l[i++] = strdup(ple->ccname)) == NULL) {
945                                 retval = ENOMEM;
946                                 goto out;
947                         }
948                 }
949         }
950         if (i > 0) {
951                 l[i] = NULL;
952                 *list = l;
953                 retval = 0;
954                 goto out;
955         }
956   out:
957         return retval;
958 }
959
960 /*
961  * Frees the list of names returned in get_krb5_machine_cred_list()
962  */
963 void
964 gssd_free_krb5_machine_cred_list(char **list)
965 {
966         char **n;
967
968         if (list == NULL)
969                 return;
970         for (n = list; n && *n; n++) {
971                 free(*n);
972         }
973         free(list);
974 }
975
976 /*
977  * Called upon exit.  Destroys machine credentials.
978  */
979 void
980 gssd_destroy_krb5_machine_creds(void)
981 {
982         krb5_context context;
983         krb5_error_code code = 0;
984         krb5_ccache ccache;
985         struct gssd_k5_kt_princ *ple;
986
987         code = krb5_init_context(&context);
988         if (code) {
989                 printerr(0, "ERROR: %s while initializing krb5\n",
990                          gssd_k5_err_msg(NULL, code));
991                 goto out;
992         }
993
994         for (ple = gssd_k5_kt_princ_list; ple; ple = ple->next) {
995                 if (!ple->ccname)
996                         continue;
997                 if ((code = krb5_cc_resolve(context, ple->ccname, &ccache))) {
998                         printerr(0, "WARNING: %s while resolving credential "
999                                     "cache '%s' for destruction\n",
1000                                  gssd_k5_err_msg(context, code), ple->ccname);
1001                         continue;
1002                 }
1003
1004                 if ((code = krb5_cc_destroy(context, ccache))) {
1005                         printerr(0, "WARNING: %s while destroying credential "
1006                                     "cache '%s'\n",
1007                                  gssd_k5_err_msg(context, code), ple->ccname);
1008                 }
1009         }
1010   out:
1011         krb5_free_context(context);
1012 }
1013
1014 /*
1015  * Obtain (or refresh if necessary) Kerberos machine credentials
1016  */
1017 int
1018 gssd_refresh_krb5_machine_credential(char *hostname,
1019                                      struct gssd_k5_kt_princ *ple)
1020 {
1021         krb5_error_code code = 0;
1022         krb5_context context;
1023         krb5_keytab kt = NULL;;
1024         int retval = 0;
1025
1026         if (hostname == NULL && ple == NULL)
1027                 return EINVAL;
1028
1029         code = krb5_init_context(&context);
1030         if (code) {
1031                 printerr(0, "ERROR: %s: %s while initializing krb5 context\n",
1032                          __FUNCTION__, gssd_k5_err_msg(NULL, code));
1033                 retval = code;
1034                 goto out;
1035         }
1036
1037         if ((code = krb5_kt_resolve(context, keytabfile, &kt))) {
1038                 printerr(0, "ERROR: %s: %s while resolving keytab '%s'\n",
1039                          __FUNCTION__, gssd_k5_err_msg(context, code),
1040                          keytabfile);
1041                 goto out;
1042         }
1043
1044         if (ple == NULL) {
1045                 krb5_keytab_entry kte;
1046
1047                 code = find_keytab_entry(context, kt, hostname, &kte);
1048                 if (code) {
1049                         printerr(0, "ERROR: %s: no usable keytab entry found "
1050                                  "in keytab %s for connection with host %s\n",
1051                                  __FUNCTION__, keytabfile, hostname);
1052                         retval = code;
1053                         goto out;
1054                 }
1055
1056                 ple = get_ple_by_princ(context, kte.principal);
1057                 k5_free_kt_entry(context, &kte);
1058                 if (ple == NULL) {
1059                         char *pname;
1060                         if ((krb5_unparse_name(context, kte.principal, &pname))) {
1061                                 pname = NULL;
1062                         }
1063                         printerr(0, "ERROR: %s: Could not locate or create "
1064                                  "ple struct for principal %s for connection "
1065                                  "with host %s\n",
1066                                  __FUNCTION__, pname ? pname : "<unparsable>",
1067                                  hostname);
1068                         if (pname) k5_free_unparsed_name(context, pname);
1069                         goto out;
1070                 }
1071         }
1072         retval = gssd_get_single_krb5_cred(context, kt, ple);
1073 out:
1074         if (kt)
1075                 krb5_kt_close(context, kt);
1076         krb5_free_context(context);
1077         return retval;
1078 }
1079
1080 /*
1081  * A common routine for getting the Kerberos error message
1082  */
1083 const char *
1084 gssd_k5_err_msg(krb5_context context, krb5_error_code code)
1085 {
1086         const char *msg = NULL;
1087 #if HAVE_KRB5_GET_ERROR_MESSAGE
1088         if (context != NULL)
1089                 msg = krb5_get_error_message(context, code);
1090 #endif
1091         if (msg != NULL)
1092                 return msg;
1093 #if HAVE_KRB5
1094         return error_message(code);
1095 #else
1096         if (context != NULL)
1097                 return krb5_get_err_text(context, code);
1098         else
1099                 return error_message(code);
1100 #endif
1101 }