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