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