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