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