]> git.decadent.org.uk Git - nfs-utils.git/blob - utils/gssd/krb5_util.c
Kerberos credentials may be stored in multiple places. Make it
[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  *      void
898  */
899 void
900 gssd_setup_krb5_user_gss_ccache(uid_t uid, char *servername, char *dirname)
901 {
902         char                    buf[MAX_NETOBJ_SZ];
903         struct dirent           *d;
904
905         printerr(2, "getting credentials for client with uid %u for "
906                     "server %s\n", uid, servername);
907         memset(buf, 0, sizeof(buf));
908         if (gssd_find_existing_krb5_ccache(uid, dirname, &d)) {
909                 snprintf(buf, sizeof(buf), "FILE:%s/%s", dirname, d->d_name);
910                 free(d);
911         }
912         else
913                 snprintf(buf, sizeof(buf), "FILE:%s/%s%u",
914                         dirname, GSSD_DEFAULT_CRED_PREFIX, uid);
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 }
919
920 /*
921  * Let the gss code know where to find the machine credentials ccache.
922  *
923  * Returns:
924  *      void
925  */
926 void
927 gssd_setup_krb5_machine_gss_ccache(char *ccname)
928 {
929         printerr(2, "using %s as credentials cache for machine creds\n",
930                  ccname);
931         gssd_set_krb5_ccache_name(ccname);
932 }
933
934 /*
935  * Return an array of pointers to names of credential cache files
936  * which can be used to try to create gss contexts with a server.
937  *
938  * Returns:
939  *      0 => list is attached
940  *      nonzero => error
941  */
942 int
943 gssd_get_krb5_machine_cred_list(char ***list)
944 {
945         char **l;
946         int listinc = 10;
947         int listsize = listinc;
948         int i = 0;
949         int retval;
950         struct gssd_k5_kt_princ *ple;
951
952         /* Assume failure */
953         retval = -1;
954         *list = (char **) NULL;
955
956         if ((l = (char **) malloc(listsize * sizeof(char *))) == NULL) {
957                 retval = ENOMEM;
958                 goto out;
959         }
960
961         /* Need to serialize list if we ever become multi-threaded! */
962
963         for (ple = gssd_k5_kt_princ_list; ple; ple = ple->next) {
964                 if (ple->ccname) {
965                         /* Make sure cred is up-to-date before returning it */
966                         retval = gssd_refresh_krb5_machine_credential(NULL, ple);
967                         if (retval)
968                                 continue;
969                         if (i + 1 > listsize) {
970                                 listsize += listinc;
971                                 l = (char **)
972                                         realloc(l, listsize * sizeof(char *));
973                                 if (l == NULL) {
974                                         retval = ENOMEM;
975                                         goto out;
976                                 }
977                         }
978                         if ((l[i++] = strdup(ple->ccname)) == NULL) {
979                                 retval = ENOMEM;
980                                 goto out;
981                         }
982                 }
983         }
984         if (i > 0) {
985                 l[i] = NULL;
986                 *list = l;
987                 retval = 0;
988                 goto out;
989         }
990   out:
991         return retval;
992 }
993
994 /*
995  * Frees the list of names returned in get_krb5_machine_cred_list()
996  */
997 void
998 gssd_free_krb5_machine_cred_list(char **list)
999 {
1000         char **n;
1001
1002         if (list == NULL)
1003                 return;
1004         for (n = list; n && *n; n++) {
1005                 free(*n);
1006         }
1007         free(list);
1008 }
1009
1010 /*
1011  * Called upon exit.  Destroys machine credentials.
1012  */
1013 void
1014 gssd_destroy_krb5_machine_creds(void)
1015 {
1016         krb5_context context;
1017         krb5_error_code code = 0;
1018         krb5_ccache ccache;
1019         struct gssd_k5_kt_princ *ple;
1020
1021         code = krb5_init_context(&context);
1022         if (code) {
1023                 printerr(0, "ERROR: %s while initializing krb5\n",
1024                          gssd_k5_err_msg(NULL, code));
1025                 goto out;
1026         }
1027
1028         for (ple = gssd_k5_kt_princ_list; ple; ple = ple->next) {
1029                 if (!ple->ccname)
1030                         continue;
1031                 if ((code = krb5_cc_resolve(context, ple->ccname, &ccache))) {
1032                         printerr(0, "WARNING: %s while resolving credential "
1033                                     "cache '%s' for destruction\n",
1034                                  gssd_k5_err_msg(context, code), ple->ccname);
1035                         continue;
1036                 }
1037
1038                 if ((code = krb5_cc_destroy(context, ccache))) {
1039                         printerr(0, "WARNING: %s while destroying credential "
1040                                     "cache '%s'\n",
1041                                  gssd_k5_err_msg(context, code), ple->ccname);
1042                 }
1043         }
1044   out:
1045         krb5_free_context(context);
1046 }
1047
1048 /*
1049  * Obtain (or refresh if necessary) Kerberos machine credentials
1050  */
1051 int
1052 gssd_refresh_krb5_machine_credential(char *hostname,
1053                                      struct gssd_k5_kt_princ *ple)
1054 {
1055         krb5_error_code code = 0;
1056         krb5_context context;
1057         krb5_keytab kt = NULL;;
1058         int retval = 0;
1059
1060         if (hostname == NULL && ple == NULL)
1061                 return EINVAL;
1062
1063         code = krb5_init_context(&context);
1064         if (code) {
1065                 printerr(0, "ERROR: %s: %s while initializing krb5 context\n",
1066                          __FUNCTION__, gssd_k5_err_msg(NULL, code));
1067                 retval = code;
1068                 goto out;
1069         }
1070
1071         if ((code = krb5_kt_resolve(context, keytabfile, &kt))) {
1072                 printerr(0, "ERROR: %s: %s while resolving keytab '%s'\n",
1073                          __FUNCTION__, gssd_k5_err_msg(context, code),
1074                          keytabfile);
1075                 goto out;
1076         }
1077
1078         if (ple == NULL) {
1079                 krb5_keytab_entry kte;
1080
1081                 code = find_keytab_entry(context, kt, hostname, &kte);
1082                 if (code) {
1083                         printerr(0, "ERROR: %s: no usable keytab entry found "
1084                                  "in keytab %s for connection with host %s\n",
1085                                  __FUNCTION__, keytabfile, hostname);
1086                         retval = code;
1087                         goto out;
1088                 }
1089
1090                 ple = get_ple_by_princ(context, kte.principal);
1091                 k5_free_kt_entry(context, &kte);
1092                 if (ple == NULL) {
1093                         char *pname;
1094                         if ((krb5_unparse_name(context, kte.principal, &pname))) {
1095                                 pname = NULL;
1096                         }
1097                         printerr(0, "ERROR: %s: Could not locate or create "
1098                                  "ple struct for principal %s for connection "
1099                                  "with host %s\n",
1100                                  __FUNCTION__, pname ? pname : "<unparsable>",
1101                                  hostname);
1102                         if (pname) k5_free_unparsed_name(context, pname);
1103                         goto out;
1104                 }
1105         }
1106         retval = gssd_get_single_krb5_cred(context, kt, ple);
1107 out:
1108         if (kt)
1109                 krb5_kt_close(context, kt);
1110         krb5_free_context(context);
1111         return retval;
1112 }
1113
1114 /*
1115  * A common routine for getting the Kerberos error message
1116  */
1117 const char *
1118 gssd_k5_err_msg(krb5_context context, krb5_error_code code)
1119 {
1120         const char *msg = NULL;
1121 #if HAVE_KRB5_GET_ERROR_MESSAGE
1122         if (context != NULL)
1123                 msg = krb5_get_error_message(context, code);
1124 #endif
1125         if (msg != NULL)
1126                 return msg;
1127 #if HAVE_KRB5
1128         return error_message(code);
1129 #else
1130         if (context != NULL)
1131                 return krb5_get_err_text(context, code);
1132         else
1133                 return error_message(code);
1134 #endif
1135 }