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