]> git.decadent.org.uk Git - nfs-utils.git/blob - utils/gssd/krb5_util.c
Clean up gssd_get_single_krb5_cred and its debugging messages
[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 static int gssd_have_realm_ple(void *realm);
138 static int gssd_process_krb5_keytab(krb5_context context, krb5_keytab kt,
139                 char *kt_name);
140
141
142 /*
143  * Called from the scandir function to weed out potential krb5
144  * credentials cache files
145  *
146  * Returns:
147  *      0 => don't select this one
148  *      1 => select this one
149  */
150 static int
151 select_krb5_ccache(const struct dirent *d)
152 {
153         /*
154          * Note: We used to check d->d_type for DT_REG here,
155          * but apparenlty reiser4 always has DT_UNKNOWN.
156          * Check for IS_REG after stat() call instead.
157          */
158         if (strstr(d->d_name, GSSD_DEFAULT_CRED_PREFIX))
159                 return 1;
160         else
161                 return 0;
162 }
163
164 /*
165  * Look in the ccachedir for files that look like they
166  * are Kerberos Credential Cache files for a given UID.  Return
167  * non-zero and the dirent pointer for the entry most likely to be
168  * what we want. Otherwise, return zero and no dirent pointer.
169  * The caller is responsible for freeing the dirent if one is returned.
170  *
171  * Returns:
172  *      0 => could not find an existing entry
173  *      1 => found an existing entry
174  */
175 static int
176 gssd_find_existing_krb5_ccache(uid_t uid, struct dirent **d)
177 {
178         struct dirent **namelist;
179         int n;
180         int i;
181         int found = 0;
182         struct dirent *best_match_dir = NULL;
183         struct stat best_match_stat, tmp_stat;
184
185         memset(&best_match_stat, 0, sizeof(best_match_stat));
186         *d = NULL;
187         n = scandir(ccachedir, &namelist, select_krb5_ccache, 0);
188         if (n < 0) {
189                 perror("scandir looking for krb5 credentials caches");
190         }
191         else if (n > 0) {
192                 char statname[1024];
193                 for (i = 0; i < n; i++) {
194                         printerr(3, "CC file '%s' being considered\n",
195                                  namelist[i]->d_name);
196                         snprintf(statname, sizeof(statname),
197                                  "%s/%s", ccachedir, namelist[i]->d_name);
198                         if (lstat(statname, &tmp_stat)) {
199                                 printerr(0, "Error doing stat on file '%s'\n",
200                                          statname);
201                                 free(namelist[i]);
202                                 continue;
203                         }
204                         /* Only pick caches owned by the user (uid) */
205                         if (tmp_stat.st_uid != uid) {
206                                 printerr(3, "'%s' owned by %u, not %u\n",
207                                          statname, tmp_stat.st_uid, uid);
208                                 free(namelist[i]);
209                                 continue;
210                         }
211                         if (!S_ISREG(tmp_stat.st_mode)) {
212                                 printerr(3, "'%s' is not a regular file\n",
213                                          statname);
214                                 free(namelist[i]);
215                                 continue;
216                         }
217                         printerr(3, "CC file '%s' matches owner check and has "
218                                  "mtime of %u\n",
219                                  namelist[i]->d_name, tmp_stat.st_mtime);
220                         /*
221                          * if more than one match is found, return the most
222                          * recent (the one with the latest mtime), and
223                          * don't free the dirent
224                          */
225                         if (!found) {
226                                 best_match_dir = namelist[i];
227                                 best_match_stat = tmp_stat;
228                                 found++;
229                         }
230                         else {
231                                 /*
232                                  * If the current match has an mtime later
233                                  * than the one we are looking at, then use
234                                  * the current match.  Otherwise, we still
235                                  * have the best match.
236                                  */
237                                 if (tmp_stat.st_mtime >
238                                             best_match_stat.st_mtime) {
239                                         free(best_match_dir);
240                                         best_match_dir = namelist[i];
241                                         best_match_stat = tmp_stat;
242                                 }
243                                 else {
244                                         free(namelist[i]);
245                                 }
246                                 printerr(3, "CC file '%s' is our "
247                                             "current best match "
248                                             "with mtime of %u\n",
249                                          best_match_dir->d_name,
250                                          best_match_stat.st_mtime);
251                         }
252                 }
253                 free(namelist);
254         }
255         if (found)
256         {
257                 *d = best_match_dir;
258         }
259         return found;
260 }
261
262
263 #ifdef HAVE_SET_ALLOWABLE_ENCTYPES
264 /*
265  * this routine obtains a credentials handle via gss_acquire_cred()
266  * then calls gss_krb5_set_allowable_enctypes() to limit the encryption
267  * types negotiated.
268  *
269  * XXX Should call some function to determine the enctypes supported
270  * by the kernel. (Only need to do that once!)
271  *
272  * Returns:
273  *      0 => all went well
274  *     -1 => there was an error
275  */
276
277 int
278 limit_krb5_enctypes(struct rpc_gss_sec *sec, uid_t uid)
279 {
280         u_int maj_stat, min_stat;
281         gss_cred_id_t credh;
282         gss_OID_set_desc  desired_mechs;
283         krb5_enctype enctypes[] = { ENCTYPE_DES_CBC_CRC };
284         int num_enctypes = sizeof(enctypes) / sizeof(enctypes[0]);
285
286         /* We only care about getting a krb5 cred */
287         desired_mechs.count = 1;
288         desired_mechs.elements = &krb5oid;
289
290         maj_stat = gss_acquire_cred(&min_stat, NULL, 0,
291                                     &desired_mechs, GSS_C_INITIATE,
292                                     &credh, NULL, NULL);
293
294         if (maj_stat != GSS_S_COMPLETE) {
295                 pgsserr("gss_acquire_cred",
296                         maj_stat, min_stat, &krb5oid);
297                 return -1;
298         }
299
300         maj_stat = gss_set_allowable_enctypes(&min_stat, credh, &krb5oid,
301                                              num_enctypes, &enctypes);
302         if (maj_stat != GSS_S_COMPLETE) {
303                 pgsserr("gss_set_allowable_enctypes",
304                         maj_stat, min_stat, &krb5oid);
305                 gss_release_cred(&min_stat, &credh);
306                 return -1;
307         }
308         sec->cred = credh;
309
310         return 0;
311 }
312 #endif  /* HAVE_SET_ALLOWABLE_ENCTYPES */
313
314 /*
315  * Obtain credentials via a key in the keytab given
316  * a keytab handle and a gssd_k5_kt_princ structure.
317  * Checks to see if current credentials are expired,
318  * if not, uses the keytab to obtain new credentials.
319  *
320  * Returns:
321  *      0 => success (or credentials have not expired)
322  *      nonzero => error
323  */
324 static int
325 gssd_get_single_krb5_cred(krb5_context context,
326                           krb5_keytab kt,
327                           struct gssd_k5_kt_princ *ple)
328 {
329         krb5_get_init_creds_opt options;
330         krb5_creds my_creds;
331         krb5_ccache ccache = NULL;
332         char kt_name[BUFSIZ];
333         char cc_name[BUFSIZ];
334         int code;
335         time_t now = time(0);
336         char *cache_type;
337         char *pname = NULL;
338
339         memset(&my_creds, 0, sizeof(my_creds));
340
341         if (ple->ccname && ple->endtime > now) {
342                 printerr(2, "INFO: Credentials in CC '%s' are good until %d\n",
343                          ple->ccname, ple->endtime);
344                 code = 0;
345                 goto out;
346         }
347
348         if ((code = krb5_kt_get_name(context, kt, kt_name, BUFSIZ))) {
349                 printerr(0, "ERROR: Unable to get keytab name in "
350                             "gssd_get_single_krb5_cred\n");
351                 goto out;
352         }
353
354         if ((krb5_unparse_name(context, ple->princ, &pname)))
355                 pname = NULL;
356
357         krb5_get_init_creds_opt_init(&options);
358         krb5_get_init_creds_opt_set_address_list(&options, NULL);
359
360 #ifdef TEST_SHORT_LIFETIME
361         /* set a short lifetime (for debugging only!) */
362         printerr(0, "WARNING: Using (debug) short machine cred lifetime!\n");
363         krb5_get_init_creds_opt_set_tkt_life(&options, 5*60);
364 #endif
365         if ((code = krb5_get_init_creds_keytab(context, &my_creds, ple->princ,
366                                                kt, 0, NULL, &options))) {
367                 printerr(0, "WARNING: %s while getting initial ticket for "
368                          "principal '%s' using keytab '%s'\n",
369                          error_message(code),
370                          pname ? pname : "<unparsable>", kt_name);
371                 goto out;
372         }
373
374         /*
375          * Initialize cache file which we're going to be using
376          */
377
378         if (use_memcache)
379             cache_type = "MEMORY";
380         else
381             cache_type = "FILE";
382         snprintf(cc_name, sizeof(cc_name), "%s:%s/%s%s_%s",
383                 cache_type,
384                 GSSD_DEFAULT_CRED_DIR, GSSD_DEFAULT_CRED_PREFIX,
385                 GSSD_DEFAULT_MACHINE_CRED_SUFFIX, ple->realm);
386         ple->endtime = my_creds.times.endtime;
387         if (ple->ccname != NULL)
388                 free(ple->ccname);
389         ple->ccname = strdup(cc_name);
390         if (ple->ccname == NULL) {
391                 printerr(0, "ERROR: no storage to duplicate credentials "
392                             "cache name '%s'\n", cc_name);
393                 code = ENOMEM;
394                 goto out;
395         }
396         if ((code = krb5_cc_resolve(context, cc_name, &ccache))) {
397                 printerr(0, "ERROR: %s while opening credential cache '%s'\n",
398                          error_message(code), cc_name);
399                 goto out;
400         }
401         if ((code = krb5_cc_initialize(context, ccache, ple->princ))) {
402                 printerr(0, "ERROR: %s while initializing credential "
403                          "cache '%s'\n", error_message(code), cc_name);
404                 goto out;
405         }
406         if ((code = krb5_cc_store_cred(context, ccache, &my_creds))) {
407                 printerr(0, "ERROR: %s while storing credentials in '%s'\n",
408                          error_message(code), cc_name);
409                 goto out;
410         }
411
412         code = 0;
413         printerr(2, "Successfully obtained machine credentials for "
414                  "principal '%s' stored in ccache '%s'\n", pname, cc_name);
415   out:
416         if (pname)
417                 k5_free_unparsed_name(context, pname);
418         if (ccache)
419                 krb5_cc_close(context, ccache);
420         krb5_free_cred_contents(context, &my_creds);
421         return (code);
422 }
423
424 /*
425  * Determine if we already have a ple for the given realm
426  *
427  * Returns:
428  *      0 => no ple found for given realm
429  *      1 => found ple for given realm
430  */
431 static int
432 gssd_have_realm_ple(void *r)
433 {
434         struct gssd_k5_kt_princ *ple;
435 #ifdef HAVE_KRB5
436         krb5_data *realm = (krb5_data *)r;
437 #else
438         char *realm = (char *)r;
439 #endif
440
441         for (ple = gssd_k5_kt_princ_list; ple; ple = ple->next) {
442 #ifdef HAVE_KRB5
443                 if ((realm->length == strlen(ple->realm)) &&
444                     (strncmp(realm->data, ple->realm, realm->length) == 0)) {
445 #else
446                 if (strcmp(realm, ple->realm) == 0) {
447 #endif
448                     return 1;
449                 }
450         }
451         return 0;
452 }
453
454 /*
455  * Process the given keytab file and create a list of principals we
456  * might use as machine credentials.
457  *
458  * Returns:
459  *      0 => Sucess
460  *      nonzero => Error
461  */
462 static int
463 gssd_process_krb5_keytab(krb5_context context, krb5_keytab kt, char *kt_name)
464 {
465         krb5_kt_cursor cursor;
466         krb5_keytab_entry kte;
467         krb5_error_code code;
468         struct gssd_k5_kt_princ *ple;
469         int retval = -1;
470
471         /*
472          * Look through each entry in the keytab file and determine
473          * if we might want to use it as machine credentials.  If so,
474          * save info in the global principal list (gssd_k5_kt_princ_list).
475          * Note: (ple == principal list entry)
476          */
477         if ((code = krb5_kt_start_seq_get(context, kt, &cursor))) {
478                 printerr(0, "ERROR: %s while beginning keytab scan "
479                             "for keytab '%s'\n",
480                         error_message(code), kt_name);
481                 retval = code;
482                 goto out;
483         }
484
485         while ((code = krb5_kt_next_entry(context, kt, &kte, &cursor)) == 0) {
486                 char *pname;
487                 if ((code = krb5_unparse_name(context, kte.principal,
488                                               &pname))) {
489                         printerr(0, "WARNING: Skipping keytab entry because "
490                                     "we failed to unparse principal name: %s\n",
491                                  error_message(code));
492                         krb5_kt_free_entry(context, &kte);
493                         continue;
494                 }
495                 printerr(2, "Processing keytab entry for principal '%s'\n",
496                          pname);
497                 /* Just use the first keytab entry found for each realm */
498                 if ((!gssd_have_realm_ple((void *)&kte.principal->realm)) ) {
499                         printerr(2, "We WILL use this entry (%s)\n", pname);
500                         ple = malloc(sizeof(struct gssd_k5_kt_princ));
501                         if (ple == NULL) {
502                                 printerr(0, "ERROR: could not allocate storage "
503                                             "for principal list entry\n");
504                                 k5_free_unparsed_name(context, pname);
505                                 krb5_kt_free_entry(context, &kte);
506                                 retval = ENOMEM;
507                                 goto out;
508                         }
509                         /* These will be filled in later */
510                         ple->next = NULL;
511                         ple->ccname = NULL;
512                         ple->endtime = 0;
513                         if ((ple->realm =
514 #ifdef HAVE_KRB5
515                                 strndup(kte.principal->realm.data,
516                                         kte.principal->realm.length))
517 #else
518                                 strdup(kte.principal->realm))
519 #endif
520                                         == NULL) {
521                                 printerr(0, "ERROR: %s while copying realm to "
522                                             "principal list entry\n",
523                                          "not enough memory");
524                                 k5_free_unparsed_name(context, pname);
525                                 krb5_kt_free_entry(context, &kte);
526                                 retval = ENOMEM;
527                                 goto out;
528                         }
529                         if ((code = krb5_copy_principal(context,
530                                         kte.principal, &ple->princ))) {
531                                 printerr(0, "ERROR: %s while copying principal "
532                                             "to principal list entry\n",
533                                         error_message(code));
534                                 k5_free_unparsed_name(context, pname);
535                                 krb5_kt_free_entry(context, &kte);
536                                 retval = code;
537                                 goto out;
538                         }
539                         if (gssd_k5_kt_princ_list == NULL)
540                                 gssd_k5_kt_princ_list = ple;
541                         else {
542                                 ple->next = gssd_k5_kt_princ_list;
543                                 gssd_k5_kt_princ_list = ple;
544                         }
545                 }
546                 else {
547                         printerr(2, "We will NOT use this entry (%s)\n",
548                                 pname);
549                 }
550                 k5_free_unparsed_name(context, pname);
551                 krb5_kt_free_entry(context, &kte);
552         }
553
554         if ((code = krb5_kt_end_seq_get(context, kt, &cursor))) {
555                 printerr(0, "WARNING: %s while ending keytab scan for "
556                             "keytab '%s'\n",
557                          error_message(code), kt_name);
558         }
559
560         retval = 0;
561   out:
562         return retval;
563 }
564
565 /*
566  * Depending on the version of Kerberos, we either need to use
567  * a private function, or simply set the environment variable.
568  */
569 static void
570 gssd_set_krb5_ccache_name(char *ccname)
571 {
572 #ifdef USE_GSS_KRB5_CCACHE_NAME
573         u_int   maj_stat, min_stat;
574
575         printerr(2, "using gss_krb5_ccache_name to select krb5 ccache %s\n",
576                  ccname);
577         maj_stat = gss_krb5_ccache_name(&min_stat, ccname, NULL);
578         if (maj_stat != GSS_S_COMPLETE) {
579                 printerr(0, "WARNING: gss_krb5_ccache_name with "
580                         "name '%s' failed (%s)\n",
581                         ccname, error_message(min_stat));
582         }
583 #else
584         /*
585          * Set the KRB5CCNAME environment variable to tell the krb5 code
586          * which credentials cache to use.  (Instead of using the private
587          * function above for which there is no generic gssapi
588          * equivalent.)
589          */
590         printerr(2, "using environment variable to select krb5 ccache %s\n",
591                  ccname);
592         setenv("KRB5CCNAME", ccname, 1);
593 #endif
594 }
595
596 /*
597  * Given a principal, find a matching ple structure
598  */
599 static struct gssd_k5_kt_princ *
600 find_ple_by_princ(krb5_context context, krb5_principal princ)
601 {
602         struct gssd_k5_kt_princ *ple;
603
604         for (ple = gssd_k5_kt_princ_list; ple != NULL; ple = ple->next) {
605                 if (krb5_principal_compare(context, ple->princ, princ))
606                         return ple;
607         }
608         /* no match found */
609         return NULL;
610 }
611
612 /*
613  * Create, initialize, and add a new ple structure to the global list
614  */
615 static struct gssd_k5_kt_princ *
616 new_ple(krb5_context context, krb5_principal princ)
617 {
618         struct gssd_k5_kt_princ *ple = NULL, *p;
619         krb5_error_code code;
620         char *default_realm;
621         int is_default_realm = 0;
622
623         ple = malloc(sizeof(struct gssd_k5_kt_princ));
624         if (ple == NULL)
625                 goto outerr;
626         memset(ple, 0, sizeof(*ple));
627
628 #ifdef HAVE_KRB5
629         ple->realm = strndup(princ->realm.data,
630                              princ->realm.length);
631 #else
632         ple->realm = strdup(princ->realm);
633 #endif
634         if (ple->realm == NULL)
635                 goto outerr;
636         code = krb5_copy_principal(context, princ, &ple->princ);
637         if (code)
638                 goto outerr;
639
640         /*
641          * Add new entry onto the list (if this is the default
642          * realm, always add to the front of the list)
643          */
644
645         code = krb5_get_default_realm(context, &default_realm);
646         if (code == 0) {
647                 if (strcmp(ple->realm, default_realm) == 0)
648                         is_default_realm = 1;
649                 k5_free_default_realm(context, default_realm);
650         }
651
652         if (is_default_realm) {
653                 ple->next = gssd_k5_kt_princ_list;
654                 gssd_k5_kt_princ_list = ple;
655         } else {
656                 p = gssd_k5_kt_princ_list;
657                 while (p != NULL && p->next != NULL)
658                         p = p->next;
659                 if (p == NULL)
660                         gssd_k5_kt_princ_list = ple;
661                 else
662                         p->next = ple;
663         }
664
665         return ple;
666 outerr:
667         if (ple) {
668                 if (ple->realm)
669                         free(ple->realm);
670                 free(ple);
671         }
672         return NULL;
673 }
674
675 /*
676  * Given a principal, find an existing ple structure, or create one
677  */
678 static struct gssd_k5_kt_princ *
679 get_ple_by_princ(krb5_context context, krb5_principal princ)
680 {
681         struct gssd_k5_kt_princ *ple;
682
683         /* Need to serialize list if we ever become multi-threaded! */
684
685         ple = find_ple_by_princ(context, princ);
686         if (ple == NULL) {
687                 ple = new_ple(context, princ);
688         }
689
690         return ple;
691 }
692
693 /*
694  * Given a (possibly unqualified) hostname,
695  * return the fully qualified (lower-case!) hostname
696  */
697 static int
698 get_full_hostname(const char *inhost, char *outhost, int outhostlen)
699 {
700         struct addrinfo *addrs = NULL;
701         struct addrinfo hints;
702         int retval;
703         char *c;
704
705         memset(&hints, 0, sizeof(hints));
706         hints.ai_socktype = SOCK_STREAM;
707         hints.ai_family = PF_UNSPEC;
708         hints.ai_flags = AI_CANONNAME;
709
710         /* Get full target hostname */
711         retval = getaddrinfo(inhost, NULL, &hints, &addrs);
712         if (retval) {
713                 printerr(0, "%s while getting full hostname for '%s'\n",
714                          gai_strerror(retval), inhost);
715                 goto out;
716         }
717         strncpy(outhost, addrs->ai_canonname, outhostlen);
718         freeaddrinfo(addrs);
719         for (c = outhost; *c != '\0'; c++)
720             *c = tolower(*c);
721
722         printerr(3, "Full hostname for '%s' is '%s'\n", inhost, outhost);
723         retval = 0;
724 out:
725         return retval;
726 }
727
728 /* 
729  * If principal matches the given realm and service name,
730  * and has *any* instance (hostname), return 1.
731  * Otherwise return 0, indicating no match.
732  */
733 static int
734 realm_and_service_match(krb5_context context, krb5_principal p,
735                         const char *realm, const char *service)
736 {
737 #ifdef HAVE_KRB5
738         /* Must have two components */
739         if (p->length != 2)
740                 return 0;
741         if ((strlen(realm) == p->realm.length)
742             && (strncmp(realm, p->realm.data, p->realm.length) == 0)
743             && (strlen(service) == p->data[0].length)
744             && (strncmp(service, p->data[0].data, p->data[0].length) == 0))
745                 return 1;
746 #else
747         const char *name, *inst;
748
749         if (p->name.name_string.len != 2)
750                 return 0;
751         name = krb5_principal_get_comp_string(context, p, 0);
752         inst = krb5_principal_get_comp_string(context, p, 1);
753         if (name == NULL || inst == NULL)
754                 return 0;
755         if ((strcmp(realm, p->realm) == 0)
756             && (strcmp(service, name) == 0))
757                 return 1;
758 #endif
759         return 0;
760 }
761
762 /*
763  * Search the given keytab file looking for an entry with the given
764  * service name and realm, ignoring hostname (instance).
765  *
766  * Returns:
767  *      0 => No error
768  *      non-zero => An error occurred
769  *
770  * If a keytab entry is found, "found" is set to one, and the keytab
771  * entry is returned in "kte".  Otherwise, "found" is zero, and the
772  * value of "kte" is unpredictable.
773  */
774 static int
775 gssd_search_krb5_keytab(krb5_context context, krb5_keytab kt,
776                         const char *realm, const char *service,
777                         int *found, krb5_keytab_entry *kte)
778 {
779         krb5_kt_cursor cursor;
780         krb5_error_code code;
781         struct gssd_k5_kt_princ *ple;
782         int retval = -1;
783         char kt_name[BUFSIZ];
784         char *pname;
785
786         if (found == NULL) {
787                 retval = EINVAL;
788                 goto out;
789         }
790         *found = 0;
791
792         /*
793          * Look through each entry in the keytab file and determine
794          * if we might want to use it as machine credentials.  If so,
795          * save info in the global principal list (gssd_k5_kt_princ_list).
796          */
797         if ((code = krb5_kt_get_name(context, kt, kt_name, BUFSIZ))) {
798                 printerr(0, "ERROR: %s attempting to get keytab name\n",
799                          error_message(code));
800                 retval = code;
801                 goto out;
802         }
803         if ((code = krb5_kt_start_seq_get(context, kt, &cursor))) {
804                 printerr(0, "ERROR: %s while beginning keytab scan "
805                             "for keytab '%s'\n",
806                         error_message(code), kt_name);
807                 retval = code;
808                 goto out;
809         }
810
811         while ((code = krb5_kt_next_entry(context, kt, kte, &cursor)) == 0) {
812                 if ((code = krb5_unparse_name(context, kte->principal,
813                                               &pname))) {
814                         printerr(0, "WARNING: Skipping keytab entry because "
815                                  "we failed to unparse principal name: %s\n",
816                                  error_message(code));
817                         k5_free_kt_entry(context, kte);
818                         continue;
819                 }
820                 printerr(4, "Processing keytab entry for principal '%s'\n",
821                          pname);
822                 /* Use the first matching keytab entry found */
823                 if ((realm_and_service_match(context, kte->principal, realm,
824                                              service))) {
825                         printerr(4, "We WILL use this entry (%s)\n", pname);
826                         ple = get_ple_by_princ(context, kte->principal);
827                         /*
828                          * Return, don't free, keytab entry if
829                          * we were successful!
830                          */
831                         if (ple == NULL) {
832                                 retval = ENOMEM;
833                                 k5_free_kt_entry(context, kte);
834                         } else {
835                                 retval = 0;
836                                 *found = 1;
837                         }
838                         k5_free_unparsed_name(context, pname);
839                         break;
840                 }
841                 else {
842                         printerr(4, "We will NOT use this entry (%s)\n",
843                                 pname);
844                 }
845                 k5_free_unparsed_name(context, pname);
846                 k5_free_kt_entry(context, kte);
847         }
848
849         if ((code = krb5_kt_end_seq_get(context, kt, &cursor))) {
850                 printerr(0, "WARNING: %s while ending keytab scan for "
851                             "keytab '%s'\n",
852                          error_message(code), kt_name);
853         }
854
855         retval = 0;
856   out:
857         return retval;
858 }
859
860 /*
861  * Find a keytab entry to use for a given target hostname.
862  * Tries to find the most appropriate keytab to use given the
863  * name of the host we are trying to connect with.
864  */
865 static int
866 find_keytab_entry(krb5_context context, krb5_keytab kt, const char *hostname,
867                   krb5_keytab_entry *kte)
868 {
869         krb5_error_code code;
870         const char *svcnames[] = { "root", "nfs", "host", NULL };
871         char **realmnames = NULL;
872         char myhostname[NI_MAXHOST], targethostname[NI_MAXHOST];
873         int i, j, retval;
874         char *default_realm = NULL;
875         char *realm;
876         int tried_all = 0, tried_default = 0;
877         krb5_principal princ;
878
879
880         /* Get full target hostname */
881         retval = get_full_hostname(hostname, targethostname,
882                                    sizeof(targethostname));
883         if (retval)
884                 goto out;
885
886         /* Get full local hostname */
887         retval = gethostname(myhostname, sizeof(myhostname));
888         if (retval) {
889                 printerr(1, "%s while getting local hostname\n",
890                          error_message(retval));
891                 goto out;
892         }
893         retval = get_full_hostname(myhostname, myhostname, sizeof(myhostname));
894         if (retval)
895                 goto out;
896
897         code = krb5_get_default_realm(context, &default_realm);
898         if (code) {
899                 retval = code;
900                 printerr(1, "%s while getting default realm name\n",
901                          error_message(code));
902                 goto out;
903         }
904
905         /*
906          * Get the realm name(s) for the target hostname.
907          * In reality, this function currently only returns a
908          * single realm, but we code with the assumption that
909          * someday it may actually return a list.
910          */
911         code = krb5_get_host_realm(context, targethostname, &realmnames);
912         if (code) {
913                 printerr(0, "ERROR: %s while getting realm(s) for host '%s'\n",
914                          error_message(code), targethostname);
915                 retval = code;
916                 goto out;
917         }
918
919         /*
920          * Try the "appropriate" realm first, and if nothing found for that
921          * realm, try the default realm (if it hasn't already been tried).
922          */
923         i = 0;
924         realm = realmnames[i];
925         while (1) {
926                 if (realm == NULL) {
927                         tried_all = 1;
928                         if (!tried_default)
929                                 realm = default_realm;
930                 }
931                 if (tried_all && tried_default)
932                         break;
933                 if (strcmp(realm, default_realm) == 0)
934                         tried_default = 1;
935                 for (j = 0; svcnames[j] != NULL; j++) {
936                         code = krb5_build_principal_ext(context, &princ,
937                                                         strlen(realm),
938                                                         realm,
939                                                         strlen(svcnames[j]),
940                                                         svcnames[j],
941                                                         strlen(myhostname),
942                                                         myhostname,
943                                                         NULL);
944                         if (code) {
945                                 printerr(1, "%s while building principal for "
946                                          "'%s/%s@%s'\n", error_message(code),
947                                          svcnames[j], myhostname, realm);
948                                 continue;
949                         }
950                         code = krb5_kt_get_entry(context, kt, princ, 0, 0, kte);
951                         krb5_free_principal(context, princ);
952                         if (code) {
953                                 printerr(3, "%s while getting keytab entry for "
954                                          "'%s/%s@%s'\n", error_message(code),
955                                          svcnames[j], myhostname, realm);
956                         } else {
957                                 printerr(3, "Success getting keytab entry for "
958                                          "'%s/%s@%s'\n",
959                                          svcnames[j], myhostname, realm);
960                                 retval = 0;
961                                 goto out;
962                         }
963                         retval = code;
964                 }
965                 /*
966                  * Nothing found with our hostname instance, now look for
967                  * names with any instance (they must have an instance)
968                  */
969                 for (j = 0; svcnames[j] != NULL; j++) {
970                         int found = 0;
971                         code = gssd_search_krb5_keytab(context, kt, realm,
972                                                        svcnames[j], &found, kte);
973                         if (!code && found) {
974                                 printerr(3, "Success getting keytab entry for "
975                                          "%s/*@%s\n", svcnames[j], realm);
976                                 retval = 0;
977                                 goto out;
978                         }
979                 }
980                 if (!tried_all) {
981                         i++;
982                         realm = realmnames[i];
983                 }
984         }
985 out:
986         if (default_realm)
987                 k5_free_default_realm(context, default_realm);
988         if (realmnames)
989                 krb5_free_host_realm(context, realmnames);
990         return retval;
991 }
992
993 /*==========================*/
994 /*===  External routines ===*/
995 /*==========================*/
996
997 /*
998  * Attempt to find the best match for a credentials cache file
999  * given only a UID.  We really need more information, but we
1000  * do the best we can.
1001  *
1002  * Returns:
1003  *      void
1004  */
1005 void
1006 gssd_setup_krb5_user_gss_ccache(uid_t uid, char *servername)
1007 {
1008         char                    buf[MAX_NETOBJ_SZ];
1009         struct dirent           *d;
1010
1011         printerr(2, "getting credentials for client with uid %u for "
1012                     "server %s\n", uid, servername);
1013         memset(buf, 0, sizeof(buf));
1014         if (gssd_find_existing_krb5_ccache(uid, &d)) {
1015                 snprintf(buf, sizeof(buf), "FILE:%s/%s",
1016                         ccachedir, d->d_name);
1017                 free(d);
1018         }
1019         else
1020                 snprintf(buf, sizeof(buf), "FILE:%s/%s%u",
1021                         ccachedir, GSSD_DEFAULT_CRED_PREFIX, uid);
1022         printerr(2, "using %s as credentials cache for client with "
1023                     "uid %u for server %s\n", buf, uid, servername);
1024         gssd_set_krb5_ccache_name(buf);
1025 }
1026
1027 /*
1028  * Let the gss code know where to find the machine credentials ccache.
1029  *
1030  * Returns:
1031  *      void
1032  */
1033 void
1034 gssd_setup_krb5_machine_gss_ccache(char *ccname)
1035 {
1036         printerr(2, "using %s as credentials cache for machine creds\n",
1037                  ccname);
1038         gssd_set_krb5_ccache_name(ccname);
1039 }
1040
1041 /*
1042  * The first time through this routine, go through the keytab and
1043  * determine which keys we will try to use as machine credentials.
1044  * Every time through this routine, try to obtain credentials using
1045  * the keytab entries selected the first time through.
1046  *
1047  * Returns:
1048  *      0 => obtained one or more credentials
1049  *      nonzero => error
1050  *
1051  */
1052
1053 int
1054 gssd_refresh_krb5_machine_creds(void)
1055 {
1056         krb5_context context = NULL;
1057         krb5_keytab kt = NULL;;
1058         krb5_error_code code;
1059         int retval = -1;
1060         struct gssd_k5_kt_princ *ple;
1061         int gotone = 0;
1062         static int processed_keytab = 0;
1063
1064
1065         code = krb5_init_context(&context);
1066         if (code) {
1067                 printerr(0, "ERROR: %s while initializing krb5 in "
1068                             "gssd_refresh_krb5_machine_creds\n",
1069                          error_message(code));
1070                 retval = code;
1071                 goto out;
1072         }
1073
1074         printerr(1, "Using keytab file '%s'\n", keytabfile);
1075
1076         if ((code = krb5_kt_resolve(context, keytabfile, &kt))) {
1077                 printerr(0, "ERROR: %s while resolving keytab '%s'\n",
1078                          error_message(code), keytabfile);
1079                 goto out;
1080         }
1081
1082         /* Only go through the keytab file once.  Only print messages once. */
1083         if (gssd_k5_kt_princ_list == NULL && !processed_keytab) {
1084                 processed_keytab = 1;
1085                 gssd_process_krb5_keytab(context, kt, keytabfile);
1086                 if (gssd_k5_kt_princ_list == NULL) {
1087                         printerr(0, "ERROR: No usable keytab entries found in "
1088                                     "keytab '%s'\n", keytabfile);
1089                         printerr(0, "Do you have a valid keytab entry for "
1090                                     "%s/<your.host>@<YOUR.REALM> in "
1091                                     "keytab file %s ?\n",
1092                                     GSSD_SERVICE_NAME, keytabfile);
1093                         printerr(0, "Continuing without (machine) credentials "
1094                                     "- nfs4 mounts with Kerberos will fail\n");
1095                 }
1096         }
1097
1098         /*
1099          * If we don't have any keytab entries we liked, then we have a problem
1100          */
1101         if (gssd_k5_kt_princ_list == NULL) {
1102                 retval = ENOENT;
1103                 goto out;
1104         }
1105
1106         /*
1107          * Now go through the list of saved entries and get initial
1108          * credentials for them (We can't do this while making the
1109          * list because it messes up the keytab iteration cursor
1110          * when we use the keytab to get credentials.)
1111          */
1112         for (ple = gssd_k5_kt_princ_list; ple; ple = ple->next) {
1113                 if ((gssd_get_single_krb5_cred(context, kt, ple)) == 0) {
1114                         gotone++;
1115                 }
1116         }
1117         if (!gotone) {
1118                 printerr(0, "ERROR: No usable machine credentials obtained\n");
1119                 goto out;
1120         }
1121
1122         retval = 0;
1123   out:
1124         if (kt) krb5_kt_close(context, kt);
1125         krb5_free_context(context);
1126
1127         return retval;
1128 }
1129
1130
1131 /*
1132  * Return an array of pointers to names of credential cache files
1133  * which can be used to try to create gss contexts with a server.
1134  *
1135  * Returns:
1136  *      0 => list is attached
1137  *      nonzero => error
1138  */
1139 int
1140 gssd_get_krb5_machine_cred_list(char ***list)
1141 {
1142         char **l;
1143         int listinc = 10;
1144         int listsize = listinc;
1145         int i = 0;
1146         int retval;
1147         struct gssd_k5_kt_princ *ple;
1148
1149         /* Assume failure */
1150         retval = -1;
1151         *list = (char **) NULL;
1152
1153         if ((l = (char **) malloc(listsize * sizeof(char *))) == NULL) {
1154                 retval = ENOMEM;
1155                 goto out;
1156         }
1157
1158         /* Need to serialize list if we ever become multi-threaded! */
1159
1160         for (ple = gssd_k5_kt_princ_list; ple; ple = ple->next) {
1161                 if (ple->ccname) {
1162                         /* Make sure cred is up-to-date before returning it */
1163                         retval = gssd_refresh_krb5_machine_credential(NULL, ple);
1164                         if (retval)
1165                                 continue;
1166                         if (i + 1 > listsize) {
1167                                 listsize += listinc;
1168                                 l = (char **)
1169                                         realloc(l, listsize * sizeof(char *));
1170                                 if (l == NULL) {
1171                                         retval = ENOMEM;
1172                                         goto out;
1173                                 }
1174                         }
1175                         if ((l[i++] = strdup(ple->ccname)) == NULL) {
1176                                 retval = ENOMEM;
1177                                 goto out;
1178                         }
1179                 }
1180         }
1181         if (i > 0) {
1182                 l[i] = NULL;
1183                 *list = l;
1184                 retval = 0;
1185                 goto out;
1186         }
1187   out:
1188         return retval;
1189 }
1190
1191 /*
1192  * Frees the list of names returned in get_krb5_machine_cred_list()
1193  */
1194 void
1195 gssd_free_krb5_machine_cred_list(char **list)
1196 {
1197         char **n;
1198
1199         if (list == NULL)
1200                 return;
1201         for (n = list; n && *n; n++) {
1202                 free(*n);
1203         }
1204         free(list);
1205 }
1206
1207 /*
1208  * Called upon exit.  Destroys machine credentials.
1209  */
1210 void
1211 gssd_destroy_krb5_machine_creds(void)
1212 {
1213         krb5_context context;
1214         krb5_error_code code = 0;
1215         krb5_ccache ccache;
1216         struct gssd_k5_kt_princ *ple;
1217
1218         code = krb5_init_context(&context);
1219         if (code) {
1220                 printerr(0, "ERROR: %s while initializing krb5\n",
1221                          error_message(code));
1222                 goto out;
1223         }
1224
1225         for (ple = gssd_k5_kt_princ_list; ple; ple = ple->next) {
1226                 if (!ple->ccname)
1227                         continue;
1228                 if ((code = krb5_cc_resolve(context, ple->ccname, &ccache))) {
1229                         printerr(0, "WARNING: %s while resolving credential "
1230                                     "cache '%s' for destruction\n",
1231                                  error_message(code), ple->ccname);
1232                         continue;
1233                 }
1234
1235                 if ((code = krb5_cc_destroy(context, ccache))) {
1236                         printerr(0, "WARNING: %s while destroying credential "
1237                                     "cache '%s'\n",
1238                                  error_message(code), ple->ccname);
1239                 }
1240         }
1241   out:
1242         krb5_free_context(context);
1243 }
1244
1245 /*
1246  * Obtain (or refresh if necessary) Kerberos machine credentials
1247  */
1248 int
1249 gssd_refresh_krb5_machine_credential(char *hostname,
1250                                      struct gssd_k5_kt_princ *ple)
1251 {
1252         krb5_error_code code = 0;
1253         krb5_context context;
1254         krb5_keytab kt = NULL;;
1255         int retval = 0;
1256
1257         if (hostname == NULL && ple == NULL)
1258                 return EINVAL;
1259
1260         code = krb5_init_context(&context);
1261         if (code) {
1262                 printerr(0, "ERROR: %s: %s while initializing krb5 context\n",
1263                          __FUNCTION__, error_message(code));
1264                 retval = code;
1265                 goto out;
1266         }
1267
1268         if ((code = krb5_kt_resolve(context, keytabfile, &kt))) {
1269                 printerr(0, "ERROR: %s: %s while resolving keytab '%s'\n",
1270                          __FUNCTION__, error_message(code), keytabfile);
1271                 goto out;
1272         }
1273
1274         if (ple == NULL) {
1275                 krb5_keytab_entry kte;
1276
1277                 code = find_keytab_entry(context, kt, hostname, &kte);
1278                 if (code) {
1279                         printerr(0, "ERROR: %s: no usable keytab entry found "
1280                                  "in keytab %s for connection with host %s\n",
1281                                  __FUNCTION__, keytabfile, hostname);
1282                         retval = code;
1283                         goto out;
1284                 }
1285
1286                 ple = get_ple_by_princ(context, kte.principal);
1287                 k5_free_kt_entry(context, &kte);
1288                 if (ple == NULL) {
1289                         char *pname;
1290                         if ((krb5_unparse_name(context, kte.principal, &pname))) {
1291                                 pname = NULL;
1292                         }
1293                         printerr(0, "ERROR: %s: Could not locate or create "
1294                                  "ple struct for principal %s for connection "
1295                                  "with host %s\n",
1296                                  __FUNCTION__, pname ? pname : "<unparsable>",
1297                                  hostname);
1298                         if (pname) k5_free_unparsed_name(context, pname);
1299                         goto out;
1300                 }
1301         }
1302         retval = gssd_get_single_krb5_cred(context, kt, ple);
1303 out:
1304         if (kt)
1305                 krb5_kt_close(context, kt);
1306         krb5_free_context(context);
1307         return retval;
1308 }
1309