]> git.decadent.org.uk Git - nfs-utils.git/blob - utils/gssd/krb5_util.c
4a4d10badb58e751b9db98049ed50981d3a0976e
[nfs-utils.git] / utils / gssd / krb5_util.c
1 /*
2  *  Adapted in part from MIT Kerberos 5-1.2.1 slave/kprop.c and from
3  *  http://docs.sun.com/?p=/doc/816-1331/6m7oo9sms&a=view
4  *
5  *  Copyright (c) 2002-2004 The Regents of the University of Michigan.
6  *  All rights reserved.
7  *
8  *  Andy Adamson <andros@umich.edu>
9  *  J. Bruce Fields <bfields@umich.edu>
10  *  Marius Aamodt Eriksen <marius@umich.edu>
11  *  Kevin Coffman <kwc@umich.edu>
12  */
13
14 /*
15  * slave/kprop.c
16  *
17  * Copyright 1990,1991 by the Massachusetts Institute of Technology.
18  * All Rights Reserved.
19  *
20  * Export of this software from the United States of America may
21  *   require a specific license from the United States Government.
22  *   It is the responsibility of any person or organization contemplating
23  *   export to obtain such a license before exporting.
24  *
25  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
26  * distribute this software and its documentation for any purpose and
27  * without fee is hereby granted, provided that the above copyright
28  * notice appear in all copies and that both that copyright notice and
29  * this permission notice appear in supporting documentation, and that
30  * the name of M.I.T. not be used in advertising or publicity pertaining
31  * to distribution of the software without specific, written prior
32  * permission.  Furthermore if you modify this software you must label
33  * your software as modified software and not distribute it in such a
34  * fashion that it might be confused with the original M.I.T. software.
35  * M.I.T. makes no representations about the suitability of
36  * this software for any purpose.  It is provided "as is" without express
37  * or implied warranty.
38  */
39
40 /*
41  * Copyright 1994 by OpenVision Technologies, Inc.
42  *
43  * Permission to use, copy, modify, distribute, and sell this software
44  * and its documentation for any purpose is hereby granted without fee,
45  * provided that the above copyright notice appears in all copies and
46  * that both that copyright notice and this permission notice appear in
47  * supporting documentation, and that the name of OpenVision not be used
48  * in advertising or publicity pertaining to distribution of the software
49  * without specific, written prior permission. OpenVision makes no
50  * representations about the suitability of this software for any
51  * purpose.  It is provided "as is" without express or implied warranty.
52  *
53  * OPENVISION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
54  * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
55  * EVENT SHALL OPENVISION BE LIABLE FOR ANY SPECIAL, INDIRECT OR
56  * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF
57  * USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
58  * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
59  * PERFORMANCE OF THIS SOFTWARE.
60  */
61 /*
62   krb5_util.c
63
64   Copyright (c) 2004 The Regents of the University of Michigan.
65   All rights reserved.
66
67   Redistribution and use in source and binary forms, with or without
68   modification, are permitted provided that the following conditions
69   are met:
70
71   1. Redistributions of source code must retain the above copyright
72      notice, this list of conditions and the following disclaimer.
73   2. Redistributions in binary form must reproduce the above copyright
74      notice, this list of conditions and the following disclaimer in the
75      documentation and/or other materials provided with the distribution.
76   3. Neither the name of the University nor the names of its
77      contributors may be used to endorse or promote products derived
78      from this software without specific prior written permission.
79
80   THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
81   WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
82   MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
83   DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
84   FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
85   CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
86   SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
87   BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
88   LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
89   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
90   SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
91
92 */
93
94 #ifndef _GNU_SOURCE
95 #define _GNU_SOURCE
96 #endif
97 #include "config.h"
98 #include <sys/param.h>
99 #include <rpc/rpc.h>
100 #include <sys/stat.h>
101 #include <sys/socket.h>
102 #include <arpa/inet.h>
103
104 #include <stdio.h>
105 #include <stdlib.h>
106 #include <unistd.h>
107 #include <string.h>
108 #include <dirent.h>
109 #include <netdb.h>
110 #include <ctype.h>
111 #include <errno.h>
112 #include <time.h>
113 #include <gssapi/gssapi.h>
114 #ifdef USE_PRIVATE_KRB5_FUNCTIONS
115 #include <gssapi/gssapi_krb5.h>
116 #endif
117 #include <krb5.h>
118 #include <rpc/auth_gss.h>
119
120 #include "gssd.h"
121 #include "err_util.h"
122 #include "gss_util.h"
123 #include "gss_oids.h"
124 #include "krb5_util.h"
125
126 /* Global list of principals/cache file names for machine credentials */
127 struct gssd_k5_kt_princ *gssd_k5_kt_princ_list = NULL;
128
129 /*==========================*/
130 /*===  Internal routines ===*/
131 /*==========================*/
132
133 static int select_krb5_ccache(const struct dirent *d);
134 static int gssd_find_existing_krb5_ccache(uid_t uid, char *dirname,
135                 struct dirent **d);
136 static int gssd_get_single_krb5_cred(krb5_context context,
137                 krb5_keytab kt, struct gssd_k5_kt_princ *ple);
138
139
140 /*
141  * Called from the scandir function to weed out potential krb5
142  * credentials cache files
143  *
144  * Returns:
145  *      0 => don't select this one
146  *      1 => select this one
147  */
148 static int
149 select_krb5_ccache(const struct dirent *d)
150 {
151         /*
152          * Note: We used to check d->d_type for DT_REG here,
153          * but apparenlty reiser4 always has DT_UNKNOWN.
154          * Check for IS_REG after stat() call instead.
155          */
156         if (strstr(d->d_name, GSSD_DEFAULT_CRED_PREFIX))
157                 return 1;
158         else
159                 return 0;
160 }
161
162 /*
163  * Look in directory "dirname" for files that look like they
164  * are Kerberos Credential Cache files for a given UID.  Return
165  * non-zero and the dirent pointer for the entry most likely to be
166  * what we want. Otherwise, return zero and no dirent pointer.
167  * The caller is responsible for freeing the dirent if one is returned.
168  *
169  * Returns:
170  *      0 => could not find an existing entry
171  *      1 => found an existing entry
172  */
173 static int
174 gssd_find_existing_krb5_ccache(uid_t uid, char *dirname, struct dirent **d)
175 {
176         struct dirent **namelist;
177         int n;
178         int i;
179         int found = 0;
180         struct dirent *best_match_dir = NULL;
181         struct stat best_match_stat, tmp_stat;
182
183         memset(&best_match_stat, 0, sizeof(best_match_stat));
184         *d = NULL;
185         n = scandir(dirname, &namelist, select_krb5_ccache, 0);
186         if (n < 0) {
187                 printerr(1, "Error doing scandir on directory '%s': %s\n",
188                         dirname, strerror(errno));
189         }
190         else if (n > 0) {
191                 char statname[1024];
192                 for (i = 0; i < n; i++) {
193                         printerr(3, "CC file '%s' being considered\n",
194                                  namelist[i]->d_name);
195                         snprintf(statname, sizeof(statname),
196                                  "%s/%s", dirname, namelist[i]->d_name);
197                         if (lstat(statname, &tmp_stat)) {
198                                 printerr(0, "Error doing stat on file '%s'\n",
199                                          statname);
200                                 free(namelist[i]);
201                                 continue;
202                         }
203                         /* Only pick caches owned by the user (uid) */
204                         if (tmp_stat.st_uid != uid) {
205                                 printerr(3, "'%s' owned by %u, not %u\n",
206                                          statname, tmp_stat.st_uid, uid);
207                                 free(namelist[i]);
208                                 continue;
209                         }
210                         if (!S_ISREG(tmp_stat.st_mode)) {
211                                 printerr(3, "'%s' is not a regular file\n",
212                                          statname);
213                                 free(namelist[i]);
214                                 continue;
215                         }
216                         printerr(3, "CC file '%s' matches owner check and has "
217                                  "mtime of %u\n",
218                                  namelist[i]->d_name, tmp_stat.st_mtime);
219                         /*
220                          * if more than one match is found, return the most
221                          * recent (the one with the latest mtime), and
222                          * don't free the dirent
223                          */
224                         if (!found) {
225                                 best_match_dir = namelist[i];
226                                 best_match_stat = tmp_stat;
227                                 found++;
228                         }
229                         else {
230                                 /*
231                                  * If the current match has an mtime later
232                                  * than the one we are looking at, then use
233                                  * the current match.  Otherwise, we still
234                                  * have the best match.
235                                  */
236                                 if (tmp_stat.st_mtime >
237                                             best_match_stat.st_mtime) {
238                                         free(best_match_dir);
239                                         best_match_dir = namelist[i];
240                                         best_match_stat = tmp_stat;
241                                 }
242                                 else {
243                                         free(namelist[i]);
244                                 }
245                                 printerr(3, "CC file '%s' is our "
246                                             "current best match "
247                                             "with mtime of %u\n",
248                                          best_match_dir->d_name,
249                                          best_match_stat.st_mtime);
250                         }
251                 }
252                 free(namelist);
253         }
254         if (found)
255         {
256                 *d = best_match_dir;
257         }
258         return found;
259 }
260
261
262 #ifdef HAVE_SET_ALLOWABLE_ENCTYPES
263 /*
264  * this routine obtains a credentials handle via gss_acquire_cred()
265  * then calls gss_krb5_set_allowable_enctypes() to limit the encryption
266  * types negotiated.
267  *
268  * XXX Should call some function to determine the enctypes supported
269  * by the kernel. (Only need to do that once!)
270  *
271  * Returns:
272  *      0 => all went well
273  *     -1 => there was an error
274  */
275
276 int
277 limit_krb5_enctypes(struct rpc_gss_sec *sec, uid_t uid)
278 {
279         u_int maj_stat, min_stat;
280         gss_cred_id_t credh;
281         gss_OID_set_desc  desired_mechs;
282         krb5_enctype enctypes[] = { ENCTYPE_DES_CBC_CRC,
283                                     ENCTYPE_DES_CBC_MD5,
284                                     ENCTYPE_DES_CBC_MD4 };
285         int num_enctypes = sizeof(enctypes) / sizeof(enctypes[0]);
286
287         /* We only care about getting a krb5 cred */
288         desired_mechs.count = 1;
289         desired_mechs.elements = &krb5oid;
290
291         maj_stat = gss_acquire_cred(&min_stat, NULL, 0,
292                                     &desired_mechs, GSS_C_INITIATE,
293                                     &credh, NULL, NULL);
294
295         if (maj_stat != GSS_S_COMPLETE) {
296                 if (get_verbosity() > 0)
297                         pgsserr("gss_acquire_cred",
298                                 maj_stat, min_stat, &krb5oid);
299                 return -1;
300         }
301
302         maj_stat = gss_set_allowable_enctypes(&min_stat, credh, &krb5oid,
303                                              num_enctypes, &enctypes);
304         if (maj_stat != GSS_S_COMPLETE) {
305                 pgsserr("gss_set_allowable_enctypes",
306                         maj_stat, min_stat, &krb5oid);
307                 gss_release_cred(&min_stat, &credh);
308                 return -1;
309         }
310         sec->cred = credh;
311
312         return 0;
313 }
314 #endif  /* HAVE_SET_ALLOWABLE_ENCTYPES */
315
316 /*
317  * Obtain credentials via a key in the keytab given
318  * a keytab handle and a gssd_k5_kt_princ structure.
319  * Checks to see if current credentials are expired,
320  * if not, uses the keytab to obtain new credentials.
321  *
322  * Returns:
323  *      0 => success (or credentials have not expired)
324  *      nonzero => error
325  */
326 static int
327 gssd_get_single_krb5_cred(krb5_context context,
328                           krb5_keytab kt,
329                           struct gssd_k5_kt_princ *ple)
330 {
331 #if HAVE_KRB5_GET_INIT_CREDS_OPT_SET_ADDRESSLESS
332         krb5_get_init_creds_opt *init_opts = NULL;
333 #else
334         krb5_get_init_creds_opt options;
335 #endif
336         krb5_get_init_creds_opt *opts;
337         krb5_creds my_creds;
338         krb5_ccache ccache = NULL;
339         char kt_name[BUFSIZ];
340         char cc_name[BUFSIZ];
341         int code;
342         time_t now = time(0);
343         char *cache_type;
344         char *pname = NULL;
345
346         memset(&my_creds, 0, sizeof(my_creds));
347
348         if (ple->ccname && ple->endtime > now) {
349                 printerr(2, "INFO: Credentials in CC '%s' are good until %d\n",
350                          ple->ccname, ple->endtime);
351                 code = 0;
352                 goto out;
353         }
354
355         if ((code = krb5_kt_get_name(context, kt, kt_name, BUFSIZ))) {
356                 printerr(0, "ERROR: Unable to get keytab name in "
357                             "gssd_get_single_krb5_cred\n");
358                 goto out;
359         }
360
361         if ((krb5_unparse_name(context, ple->princ, &pname)))
362                 pname = NULL;
363
364 #if HAVE_KRB5_GET_INIT_CREDS_OPT_SET_ADDRESSLESS
365         code = krb5_get_init_creds_opt_alloc(context, &init_opts);
366         if (code) {
367                 printerr(0, "ERROR: %s allocating gic options\n",
368                          gssd_k5_err_msg(context, code));
369                 goto out;
370         }
371         if (krb5_get_init_creds_opt_set_addressless(context, init_opts, 1))
372                 printerr(0, "WARNING: Unable to set option for addressless "
373                          "tickets.  May have problems behind a NAT.\n");
374 #ifdef TEST_SHORT_LIFETIME
375         /* set a short lifetime (for debugging only!) */
376         printerr(0, "WARNING: Using (debug) short machine cred lifetime!\n");
377         krb5_get_init_creds_opt_set_tkt_life(init_opts, 5*60);
378 #endif
379         opts = init_opts;
380
381 #else   /* HAVE_KRB5_GET_INIT_CREDS_OPT_SET_ADDRESSLESS */
382
383         krb5_get_init_creds_opt_init(&options);
384         krb5_get_init_creds_opt_set_address_list(&options, NULL);
385 #ifdef TEST_SHORT_LIFETIME
386         /* set a short lifetime (for debugging only!) */
387         printerr(0, "WARNING: Using (debug) short machine cred lifetime!\n");
388         krb5_get_init_creds_opt_set_tkt_life(&options, 5*60);
389 #endif
390         opts = &options;
391 #endif
392
393         if ((code = krb5_get_init_creds_keytab(context, &my_creds, ple->princ,
394                                                kt, 0, NULL, opts))) {
395                 printerr(0, "WARNING: %s while getting initial ticket for "
396                          "principal '%s' using keytab '%s'\n",
397                          gssd_k5_err_msg(context, code),
398                          pname ? pname : "<unparsable>", kt_name);
399                 goto out;
400         }
401
402         /*
403          * Initialize cache file which we're going to be using
404          */
405
406         if (use_memcache)
407             cache_type = "MEMORY";
408         else
409             cache_type = "FILE";
410         snprintf(cc_name, sizeof(cc_name), "%s:%s/%s%s_%s",
411                 cache_type,
412                 ccachesearch[0], GSSD_DEFAULT_CRED_PREFIX,
413                 GSSD_DEFAULT_MACHINE_CRED_SUFFIX, ple->realm);
414         ple->endtime = my_creds.times.endtime;
415         if (ple->ccname != NULL)
416                 free(ple->ccname);
417         ple->ccname = strdup(cc_name);
418         if (ple->ccname == NULL) {
419                 printerr(0, "ERROR: no storage to duplicate credentials "
420                             "cache name '%s'\n", cc_name);
421                 code = ENOMEM;
422                 goto out;
423         }
424         if ((code = krb5_cc_resolve(context, cc_name, &ccache))) {
425                 printerr(0, "ERROR: %s while opening credential cache '%s'\n",
426                          gssd_k5_err_msg(context, code), cc_name);
427                 goto out;
428         }
429         if ((code = krb5_cc_initialize(context, ccache, ple->princ))) {
430                 printerr(0, "ERROR: %s while initializing credential "
431                          "cache '%s'\n", gssd_k5_err_msg(context, code),
432                          cc_name);
433                 goto out;
434         }
435         if ((code = krb5_cc_store_cred(context, ccache, &my_creds))) {
436                 printerr(0, "ERROR: %s while storing credentials in '%s'\n",
437                          gssd_k5_err_msg(context, code), cc_name);
438                 goto out;
439         }
440
441         code = 0;
442         printerr(2, "Successfully obtained machine credentials for "
443                  "principal '%s' stored in ccache '%s'\n", pname, cc_name);
444   out:
445 #if HAVE_KRB5_GET_INIT_CREDS_OPT_SET_ADDRESSLESS
446         if (init_opts)
447                 krb5_get_init_creds_opt_free(context, init_opts);
448 #endif
449         if (pname)
450                 k5_free_unparsed_name(context, pname);
451         if (ccache)
452                 krb5_cc_close(context, ccache);
453         krb5_free_cred_contents(context, &my_creds);
454         return (code);
455 }
456
457 /*
458  * Depending on the version of Kerberos, we either need to use
459  * a private function, or simply set the environment variable.
460  */
461 static void
462 gssd_set_krb5_ccache_name(char *ccname)
463 {
464 #ifdef USE_GSS_KRB5_CCACHE_NAME
465         u_int   maj_stat, min_stat;
466
467         printerr(2, "using gss_krb5_ccache_name to select krb5 ccache %s\n",
468                  ccname);
469         maj_stat = gss_krb5_ccache_name(&min_stat, ccname, NULL);
470         if (maj_stat != GSS_S_COMPLETE) {
471                 printerr(0, "WARNING: gss_krb5_ccache_name with "
472                         "name '%s' failed (%s)\n",
473                         ccname, error_message(min_stat));
474         }
475 #else
476         /*
477          * Set the KRB5CCNAME environment variable to tell the krb5 code
478          * which credentials cache to use.  (Instead of using the private
479          * function above for which there is no generic gssapi
480          * equivalent.)
481          */
482         printerr(2, "using environment variable to select krb5 ccache %s\n",
483                  ccname);
484         setenv("KRB5CCNAME", ccname, 1);
485 #endif
486 }
487
488 /*
489  * Given a principal, find a matching ple structure
490  */
491 static struct gssd_k5_kt_princ *
492 find_ple_by_princ(krb5_context context, krb5_principal princ)
493 {
494         struct gssd_k5_kt_princ *ple;
495
496         for (ple = gssd_k5_kt_princ_list; ple != NULL; ple = ple->next) {
497                 if (krb5_principal_compare(context, ple->princ, princ))
498                         return ple;
499         }
500         /* no match found */
501         return NULL;
502 }
503
504 /*
505  * Create, initialize, and add a new ple structure to the global list
506  */
507 static struct gssd_k5_kt_princ *
508 new_ple(krb5_context context, krb5_principal princ)
509 {
510         struct gssd_k5_kt_princ *ple = NULL, *p;
511         krb5_error_code code;
512         char *default_realm;
513         int is_default_realm = 0;
514
515         ple = malloc(sizeof(struct gssd_k5_kt_princ));
516         if (ple == NULL)
517                 goto outerr;
518         memset(ple, 0, sizeof(*ple));
519
520 #ifdef HAVE_KRB5
521         ple->realm = strndup(princ->realm.data,
522                              princ->realm.length);
523 #else
524         ple->realm = strdup(princ->realm);
525 #endif
526         if (ple->realm == NULL)
527                 goto outerr;
528         code = krb5_copy_principal(context, princ, &ple->princ);
529         if (code)
530                 goto outerr;
531
532         /*
533          * Add new entry onto the list (if this is the default
534          * realm, always add to the front of the list)
535          */
536
537         code = krb5_get_default_realm(context, &default_realm);
538         if (code == 0) {
539                 if (strcmp(ple->realm, default_realm) == 0)
540                         is_default_realm = 1;
541                 k5_free_default_realm(context, default_realm);
542         }
543
544         if (is_default_realm) {
545                 ple->next = gssd_k5_kt_princ_list;
546                 gssd_k5_kt_princ_list = ple;
547         } else {
548                 p = gssd_k5_kt_princ_list;
549                 while (p != NULL && p->next != NULL)
550                         p = p->next;
551                 if (p == NULL)
552                         gssd_k5_kt_princ_list = ple;
553                 else
554                         p->next = ple;
555         }
556
557         return ple;
558 outerr:
559         if (ple) {
560                 if (ple->realm)
561                         free(ple->realm);
562                 free(ple);
563         }
564         return NULL;
565 }
566
567 /*
568  * Given a principal, find an existing ple structure, or create one
569  */
570 static struct gssd_k5_kt_princ *
571 get_ple_by_princ(krb5_context context, krb5_principal princ)
572 {
573         struct gssd_k5_kt_princ *ple;
574
575         /* Need to serialize list if we ever become multi-threaded! */
576
577         ple = find_ple_by_princ(context, princ);
578         if (ple == NULL) {
579                 ple = new_ple(context, princ);
580         }
581
582         return ple;
583 }
584
585 /*
586  * Given a (possibly unqualified) hostname,
587  * return the fully qualified (lower-case!) hostname
588  */
589 static int
590 get_full_hostname(const char *inhost, char *outhost, int outhostlen)
591 {
592         struct addrinfo *addrs = NULL;
593         struct addrinfo hints;
594         int retval;
595         char *c;
596
597         memset(&hints, 0, sizeof(hints));
598         hints.ai_socktype = SOCK_STREAM;
599         hints.ai_family = PF_UNSPEC;
600         hints.ai_flags = AI_CANONNAME;
601
602         /* Get full target hostname */
603         retval = getaddrinfo(inhost, NULL, &hints, &addrs);
604         if (retval) {
605                 printerr(0, "%s while getting full hostname for '%s'\n",
606                          gai_strerror(retval), inhost);
607                 goto out;
608         }
609         strncpy(outhost, addrs->ai_canonname, outhostlen);
610         freeaddrinfo(addrs);
611         for (c = outhost; *c != '\0'; c++)
612             *c = tolower(*c);
613
614         printerr(3, "Full hostname for '%s' is '%s'\n", inhost, outhost);
615         retval = 0;
616 out:
617         return retval;
618 }
619
620 /* 
621  * If principal matches the given realm and service name,
622  * and has *any* instance (hostname), return 1.
623  * Otherwise return 0, indicating no match.
624  */
625 static int
626 realm_and_service_match(krb5_context context, krb5_principal p,
627                         const char *realm, const char *service)
628 {
629 #ifdef HAVE_KRB5
630         /* Must have two components */
631         if (p->length != 2)
632                 return 0;
633         if ((strlen(realm) == p->realm.length)
634             && (strncmp(realm, p->realm.data, p->realm.length) == 0)
635             && (strlen(service) == p->data[0].length)
636             && (strncmp(service, p->data[0].data, p->data[0].length) == 0))
637                 return 1;
638 #else
639         const char *name, *inst;
640
641         if (p->name.name_string.len != 2)
642                 return 0;
643         name = krb5_principal_get_comp_string(context, p, 0);
644         inst = krb5_principal_get_comp_string(context, p, 1);
645         if (name == NULL || inst == NULL)
646                 return 0;
647         if ((strcmp(realm, p->realm) == 0)
648             && (strcmp(service, name) == 0))
649                 return 1;
650 #endif
651         return 0;
652 }
653
654 /*
655  * Search the given keytab file looking for an entry with the given
656  * service name and realm, ignoring hostname (instance).
657  *
658  * Returns:
659  *      0 => No error
660  *      non-zero => An error occurred
661  *
662  * If a keytab entry is found, "found" is set to one, and the keytab
663  * entry is returned in "kte".  Otherwise, "found" is zero, and the
664  * value of "kte" is unpredictable.
665  */
666 static int
667 gssd_search_krb5_keytab(krb5_context context, krb5_keytab kt,
668                         const char *realm, const char *service,
669                         int *found, krb5_keytab_entry *kte)
670 {
671         krb5_kt_cursor cursor;
672         krb5_error_code code;
673         struct gssd_k5_kt_princ *ple;
674         int retval = -1;
675         char kt_name[BUFSIZ];
676         char *pname;
677
678         if (found == NULL) {
679                 retval = EINVAL;
680                 goto out;
681         }
682         *found = 0;
683
684         /*
685          * Look through each entry in the keytab file and determine
686          * if we might want to use it as machine credentials.  If so,
687          * save info in the global principal list (gssd_k5_kt_princ_list).
688          */
689         if ((code = krb5_kt_get_name(context, kt, kt_name, BUFSIZ))) {
690                 printerr(0, "ERROR: %s attempting to get keytab name\n",
691                          gssd_k5_err_msg(context, code));
692                 retval = code;
693                 goto out;
694         }
695         if ((code = krb5_kt_start_seq_get(context, kt, &cursor))) {
696                 printerr(0, "ERROR: %s while beginning keytab scan "
697                             "for keytab '%s'\n",
698                         gssd_k5_err_msg(context, code), kt_name);
699                 retval = code;
700                 goto out;
701         }
702
703         while ((code = krb5_kt_next_entry(context, kt, kte, &cursor)) == 0) {
704                 if ((code = krb5_unparse_name(context, kte->principal,
705                                               &pname))) {
706                         printerr(0, "WARNING: Skipping keytab entry because "
707                                  "we failed to unparse principal name: %s\n",
708                                  gssd_k5_err_msg(context, code));
709                         k5_free_kt_entry(context, kte);
710                         continue;
711                 }
712                 printerr(4, "Processing keytab entry for principal '%s'\n",
713                          pname);
714                 /* Use the first matching keytab entry found */
715                 if ((realm_and_service_match(context, kte->principal, realm,
716                                              service))) {
717                         printerr(4, "We WILL use this entry (%s)\n", pname);
718                         ple = get_ple_by_princ(context, kte->principal);
719                         /*
720                          * Return, don't free, keytab entry if
721                          * we were successful!
722                          */
723                         if (ple == NULL) {
724                                 retval = ENOMEM;
725                                 k5_free_kt_entry(context, kte);
726                         } else {
727                                 retval = 0;
728                                 *found = 1;
729                         }
730                         k5_free_unparsed_name(context, pname);
731                         break;
732                 }
733                 else {
734                         printerr(4, "We will NOT use this entry (%s)\n",
735                                 pname);
736                 }
737                 k5_free_unparsed_name(context, pname);
738                 k5_free_kt_entry(context, kte);
739         }
740
741         if ((code = krb5_kt_end_seq_get(context, kt, &cursor))) {
742                 printerr(0, "WARNING: %s while ending keytab scan for "
743                             "keytab '%s'\n",
744                          gssd_k5_err_msg(context, code), kt_name);
745         }
746
747         retval = 0;
748   out:
749         return retval;
750 }
751
752 /*
753  * Find a keytab entry to use for a given target hostname.
754  * Tries to find the most appropriate keytab to use given the
755  * name of the host we are trying to connect with.
756  */
757 static int
758 find_keytab_entry(krb5_context context, krb5_keytab kt, const char *hostname,
759                   krb5_keytab_entry *kte)
760 {
761         krb5_error_code code;
762         const char *svcnames[] = { "root", "nfs", "host", NULL };
763         char **realmnames = NULL;
764         char myhostname[NI_MAXHOST], targethostname[NI_MAXHOST];
765         int i, j, retval;
766         char *default_realm = NULL;
767         char *realm;
768         int tried_all = 0, tried_default = 0;
769         krb5_principal princ;
770
771
772         /* Get full target hostname */
773         retval = get_full_hostname(hostname, targethostname,
774                                    sizeof(targethostname));
775         if (retval)
776                 goto out;
777
778         /* Get full local hostname */
779         retval = gethostname(myhostname, sizeof(myhostname));
780         if (retval) {
781                 printerr(1, "%s while getting local hostname\n",
782                          gssd_k5_err_msg(context, retval));
783                 goto out;
784         }
785         retval = get_full_hostname(myhostname, myhostname, sizeof(myhostname));
786         if (retval)
787                 goto out;
788
789         code = krb5_get_default_realm(context, &default_realm);
790         if (code) {
791                 retval = code;
792                 printerr(1, "%s while getting default realm name\n",
793                          gssd_k5_err_msg(context, code));
794                 goto out;
795         }
796
797         /*
798          * Get the realm name(s) for the target hostname.
799          * In reality, this function currently only returns a
800          * single realm, but we code with the assumption that
801          * someday it may actually return a list.
802          */
803         code = krb5_get_host_realm(context, targethostname, &realmnames);
804         if (code) {
805                 printerr(0, "ERROR: %s while getting realm(s) for host '%s'\n",
806                          gssd_k5_err_msg(context, code), targethostname);
807                 retval = code;
808                 goto out;
809         }
810
811         /*
812          * Try the "appropriate" realm first, and if nothing found for that
813          * realm, try the default realm (if it hasn't already been tried).
814          */
815         i = 0;
816         realm = realmnames[i];
817         while (1) {
818                 if (realm == NULL) {
819                         tried_all = 1;
820                         if (!tried_default)
821                                 realm = default_realm;
822                 }
823                 if (tried_all && tried_default)
824                         break;
825                 if (strcmp(realm, default_realm) == 0)
826                         tried_default = 1;
827                 for (j = 0; svcnames[j] != NULL; j++) {
828                         code = krb5_build_principal_ext(context, &princ,
829                                                         strlen(realm),
830                                                         realm,
831                                                         strlen(svcnames[j]),
832                                                         svcnames[j],
833                                                         strlen(myhostname),
834                                                         myhostname,
835                                                         NULL);
836                         if (code) {
837                                 printerr(1, "%s while building principal for "
838                                          "'%s/%s@%s'\n",
839                                          gssd_k5_err_msg(context, code),
840                                          svcnames[j], myhostname, realm);
841                                 continue;
842                         }
843                         code = krb5_kt_get_entry(context, kt, princ, 0, 0, kte);
844                         krb5_free_principal(context, princ);
845                         if (code) {
846                                 printerr(3, "%s while getting keytab entry for "
847                                          "'%s/%s@%s'\n",
848                                          gssd_k5_err_msg(context, code),
849                                          svcnames[j], myhostname, realm);
850                         } else {
851                                 printerr(3, "Success getting keytab entry for "
852                                          "'%s/%s@%s'\n",
853                                          svcnames[j], myhostname, realm);
854                                 retval = 0;
855                                 goto out;
856                         }
857                         retval = code;
858                 }
859                 /*
860                  * Nothing found with our hostname instance, now look for
861                  * names with any instance (they must have an instance)
862                  */
863                 for (j = 0; svcnames[j] != NULL; j++) {
864                         int found = 0;
865                         code = gssd_search_krb5_keytab(context, kt, realm,
866                                                        svcnames[j], &found, kte);
867                         if (!code && found) {
868                                 printerr(3, "Success getting keytab entry for "
869                                          "%s/*@%s\n", svcnames[j], realm);
870                                 retval = 0;
871                                 goto out;
872                         }
873                 }
874                 if (!tried_all) {
875                         i++;
876                         realm = realmnames[i];
877                 }
878         }
879 out:
880         if (default_realm)
881                 k5_free_default_realm(context, default_realm);
882         if (realmnames)
883                 krb5_free_host_realm(context, realmnames);
884         return retval;
885 }
886
887 /*==========================*/
888 /*===  External routines ===*/
889 /*==========================*/
890
891 /*
892  * Attempt to find the best match for a credentials cache file
893  * given only a UID.  We really need more information, but we
894  * do the best we can.
895  *
896  * Returns:
897  *      0 => a ccache was found
898  *      1 => no ccache was found
899  */
900 int
901 gssd_setup_krb5_user_gss_ccache(uid_t uid, char *servername, char *dirname)
902 {
903         char                    buf[MAX_NETOBJ_SZ];
904         struct dirent           *d;
905
906         printerr(2, "getting credentials for client with uid %u for "
907                     "server %s\n", uid, servername);
908         memset(buf, 0, sizeof(buf));
909         if (gssd_find_existing_krb5_ccache(uid, dirname, &d)) {
910                 snprintf(buf, sizeof(buf), "FILE:%s/%s", dirname, d->d_name);
911                 free(d);
912         }
913         else
914                 return 1;
915         printerr(2, "using %s as credentials cache for client with "
916                     "uid %u for server %s\n", buf, uid, servername);
917         gssd_set_krb5_ccache_name(buf);
918         return 0;
919 }
920
921 /*
922  * Let the gss code know where to find the machine credentials ccache.
923  *
924  * Returns:
925  *      void
926  */
927 void
928 gssd_setup_krb5_machine_gss_ccache(char *ccname)
929 {
930         printerr(2, "using %s as credentials cache for machine creds\n",
931                  ccname);
932         gssd_set_krb5_ccache_name(ccname);
933 }
934
935 /*
936  * Return an array of pointers to names of credential cache files
937  * which can be used to try to create gss contexts with a server.
938  *
939  * Returns:
940  *      0 => list is attached
941  *      nonzero => error
942  */
943 int
944 gssd_get_krb5_machine_cred_list(char ***list)
945 {
946         char **l;
947         int listinc = 10;
948         int listsize = listinc;
949         int i = 0;
950         int retval;
951         struct gssd_k5_kt_princ *ple;
952
953         /* Assume failure */
954         retval = -1;
955         *list = (char **) NULL;
956
957         if ((l = (char **) malloc(listsize * sizeof(char *))) == NULL) {
958                 retval = ENOMEM;
959                 goto out;
960         }
961
962         /* Need to serialize list if we ever become multi-threaded! */
963
964         for (ple = gssd_k5_kt_princ_list; ple; ple = ple->next) {
965                 if (ple->ccname) {
966                         /* Make sure cred is up-to-date before returning it */
967                         retval = gssd_refresh_krb5_machine_credential(NULL, ple);
968                         if (retval)
969                                 continue;
970                         if (i + 1 > listsize) {
971                                 listsize += listinc;
972                                 l = (char **)
973                                         realloc(l, listsize * sizeof(char *));
974                                 if (l == NULL) {
975                                         retval = ENOMEM;
976                                         goto out;
977                                 }
978                         }
979                         if ((l[i++] = strdup(ple->ccname)) == NULL) {
980                                 retval = ENOMEM;
981                                 goto out;
982                         }
983                 }
984         }
985         if (i > 0) {
986                 l[i] = NULL;
987                 *list = l;
988                 retval = 0;
989                 goto out;
990         }
991   out:
992         return retval;
993 }
994
995 /*
996  * Frees the list of names returned in get_krb5_machine_cred_list()
997  */
998 void
999 gssd_free_krb5_machine_cred_list(char **list)
1000 {
1001         char **n;
1002
1003         if (list == NULL)
1004                 return;
1005         for (n = list; n && *n; n++) {
1006                 free(*n);
1007         }
1008         free(list);
1009 }
1010
1011 /*
1012  * Called upon exit.  Destroys machine credentials.
1013  */
1014 void
1015 gssd_destroy_krb5_machine_creds(void)
1016 {
1017         krb5_context context;
1018         krb5_error_code code = 0;
1019         krb5_ccache ccache;
1020         struct gssd_k5_kt_princ *ple;
1021
1022         code = krb5_init_context(&context);
1023         if (code) {
1024                 printerr(0, "ERROR: %s while initializing krb5\n",
1025                          gssd_k5_err_msg(NULL, code));
1026                 goto out;
1027         }
1028
1029         for (ple = gssd_k5_kt_princ_list; ple; ple = ple->next) {
1030                 if (!ple->ccname)
1031                         continue;
1032                 if ((code = krb5_cc_resolve(context, ple->ccname, &ccache))) {
1033                         printerr(0, "WARNING: %s while resolving credential "
1034                                     "cache '%s' for destruction\n",
1035                                  gssd_k5_err_msg(context, code), ple->ccname);
1036                         continue;
1037                 }
1038
1039                 if ((code = krb5_cc_destroy(context, ccache))) {
1040                         printerr(0, "WARNING: %s while destroying credential "
1041                                     "cache '%s'\n",
1042                                  gssd_k5_err_msg(context, code), ple->ccname);
1043                 }
1044         }
1045   out:
1046         krb5_free_context(context);
1047 }
1048
1049 /*
1050  * Obtain (or refresh if necessary) Kerberos machine credentials
1051  */
1052 int
1053 gssd_refresh_krb5_machine_credential(char *hostname,
1054                                      struct gssd_k5_kt_princ *ple)
1055 {
1056         krb5_error_code code = 0;
1057         krb5_context context;
1058         krb5_keytab kt = NULL;;
1059         int retval = 0;
1060
1061         if (hostname == NULL && ple == NULL)
1062                 return EINVAL;
1063
1064         code = krb5_init_context(&context);
1065         if (code) {
1066                 printerr(0, "ERROR: %s: %s while initializing krb5 context\n",
1067                          __FUNCTION__, gssd_k5_err_msg(NULL, code));
1068                 retval = code;
1069                 goto out;
1070         }
1071
1072         if ((code = krb5_kt_resolve(context, keytabfile, &kt))) {
1073                 printerr(0, "ERROR: %s: %s while resolving keytab '%s'\n",
1074                          __FUNCTION__, gssd_k5_err_msg(context, code),
1075                          keytabfile);
1076                 goto out;
1077         }
1078
1079         if (ple == NULL) {
1080                 krb5_keytab_entry kte;
1081
1082                 code = find_keytab_entry(context, kt, hostname, &kte);
1083                 if (code) {
1084                         printerr(0, "ERROR: %s: no usable keytab entry found "
1085                                  "in keytab %s for connection with host %s\n",
1086                                  __FUNCTION__, keytabfile, hostname);
1087                         retval = code;
1088                         goto out;
1089                 }
1090
1091                 ple = get_ple_by_princ(context, kte.principal);
1092                 k5_free_kt_entry(context, &kte);
1093                 if (ple == NULL) {
1094                         char *pname;
1095                         if ((krb5_unparse_name(context, kte.principal, &pname))) {
1096                                 pname = NULL;
1097                         }
1098                         printerr(0, "ERROR: %s: Could not locate or create "
1099                                  "ple struct for principal %s for connection "
1100                                  "with host %s\n",
1101                                  __FUNCTION__, pname ? pname : "<unparsable>",
1102                                  hostname);
1103                         if (pname) k5_free_unparsed_name(context, pname);
1104                         goto out;
1105                 }
1106         }
1107         retval = gssd_get_single_krb5_cred(context, kt, ple);
1108 out:
1109         if (kt)
1110                 krb5_kt_close(context, kt);
1111         krb5_free_context(context);
1112         return retval;
1113 }
1114
1115 /*
1116  * A common routine for getting the Kerberos error message
1117  */
1118 const char *
1119 gssd_k5_err_msg(krb5_context context, krb5_error_code code)
1120 {
1121         const char *msg = NULL;
1122 #if HAVE_KRB5_GET_ERROR_MESSAGE
1123         if (context != NULL)
1124                 msg = krb5_get_error_message(context, code);
1125 #endif
1126         if (msg != NULL)
1127                 return msg;
1128 #if HAVE_KRB5
1129         return error_message(code);
1130 #else
1131         if (context != NULL)
1132                 return krb5_get_err_text(context, code);
1133         else
1134                 return error_message(code);
1135 #endif
1136 }