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