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