]> git.decadent.org.uk Git - nfs-utils.git/blob - utils/gssd/krb5_util.c
Added the following files to .gitignore
[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:
174  *      0 => could not find an existing entry
175  *      1 => found an existing entry
176  */
177 static int
178 gssd_find_existing_krb5_ccache(uid_t uid, char *dirname, struct dirent **d)
179 {
180         struct dirent **namelist;
181         int n;
182         int i;
183         int found = 0;
184         struct dirent *best_match_dir = NULL;
185         struct stat best_match_stat, tmp_stat;
186         char buf[1030];
187         char *princname = NULL;
188         char *realm = NULL;
189         int score, best_match_score = 0;
190
191         memset(&best_match_stat, 0, sizeof(best_match_stat));
192         *d = NULL;
193         n = scandir(dirname, &namelist, select_krb5_ccache, 0);
194         if (n < 0) {
195                 printerr(1, "Error doing scandir on directory '%s': %s\n",
196                         dirname, strerror(errno));
197         }
198         else if (n > 0) {
199                 char statname[1024];
200                 for (i = 0; i < n; i++) {
201                         snprintf(statname, sizeof(statname),
202                                  "%s/%s", dirname, namelist[i]->d_name);
203                         printerr(3, "CC file '%s' being considered, "
204                                  "with preferred realm '%s'\n",
205                                  statname, preferred_realm ?
206                                         preferred_realm : "<none selected>");
207                         snprintf(buf, sizeof(buf), "FILE:%s/%s", dirname, 
208                                         namelist[i]->d_name);
209                         if (lstat(statname, &tmp_stat)) {
210                                 printerr(0, "Error doing stat on file '%s'\n",
211                                          statname);
212                                 free(namelist[i]);
213                                 continue;
214                         }
215                         /* Only pick caches owned by the user (uid) */
216                         if (tmp_stat.st_uid != uid) {
217                                 printerr(3, "CC file '%s' owned by %u, not %u\n",
218                                          statname, tmp_stat.st_uid, uid);
219                                 free(namelist[i]);
220                                 continue;
221                         }
222                         if (!S_ISREG(tmp_stat.st_mode)) {
223                                 printerr(3, "CC file '%s' is not a regular file\n",
224                                          statname);
225                                 free(namelist[i]);
226                                 continue;
227                         }
228                         if (!query_krb5_ccache(buf, &princname, &realm)) {
229                                 printerr(3, "CC file '%s' is expired or corrupt\n",
230                                          statname);
231                                 free(namelist[i]);
232                                 continue;
233                         }
234
235                         score = 0;
236                         if (preferred_realm &&
237                                         strcmp(realm, preferred_realm) == 0) 
238                                 score++;
239
240                         printerr(3, "CC file '%s'(%s@%s) passed all checks and"
241                                     " has mtime of %u\n",
242                                  statname, princname, realm, 
243                                  tmp_stat.st_mtime);
244                         /*
245                          * if more than one match is found, return the most
246                          * recent (the one with the latest mtime), and
247                          * don't free the dirent
248                          */
249                         if (!found) {
250                                 best_match_dir = namelist[i];
251                                 best_match_stat = tmp_stat;
252                                 best_match_score = score;
253                                 found++;
254                         }
255                         else {
256                                 /*
257                                  * If current score is higher than best match 
258                                  * score, we use the current match. Otherwise,
259                                  * if the current match has an mtime later
260                                  * than the one we are looking at, then use
261                                  * the current match.  Otherwise, we still
262                                  * have the best match.
263                                  */
264                                 if (best_match_score < score ||
265                                     (best_match_score == score && 
266                                        tmp_stat.st_mtime >
267                                             best_match_stat.st_mtime)) {
268                                         free(best_match_dir);
269                                         best_match_dir = namelist[i];
270                                         best_match_stat = tmp_stat;
271                                         best_match_score = score;
272                                 }
273                                 else {
274                                         free(namelist[i]);
275                                 }
276                                 printerr(3, "CC file '%s/%s' is our "
277                                             "current best match "
278                                             "with mtime of %u\n",
279                                          dirname, best_match_dir->d_name,
280                                          best_match_stat.st_mtime);
281                         }
282                         free(princname);
283                         free(realm);
284                 }
285                 free(namelist);
286         }
287         if (found)
288         {
289                 *d = best_match_dir;
290         }
291         return found;
292 }
293
294
295 #ifdef HAVE_SET_ALLOWABLE_ENCTYPES
296 /*
297  * this routine obtains a credentials handle via gss_acquire_cred()
298  * then calls gss_krb5_set_allowable_enctypes() to limit the encryption
299  * types negotiated.
300  *
301  * XXX Should call some function to determine the enctypes supported
302  * by the kernel. (Only need to do that once!)
303  *
304  * Returns:
305  *      0 => all went well
306  *     -1 => there was an error
307  */
308
309 int
310 limit_krb5_enctypes(struct rpc_gss_sec *sec, uid_t uid)
311 {
312         u_int maj_stat, min_stat;
313         gss_cred_id_t credh;
314         gss_OID_set_desc  desired_mechs;
315         krb5_enctype enctypes[] = { ENCTYPE_DES_CBC_CRC,
316                                     ENCTYPE_DES_CBC_MD5,
317                                     ENCTYPE_DES_CBC_MD4 };
318         int num_enctypes = sizeof(enctypes) / sizeof(enctypes[0]);
319
320         /* We only care about getting a krb5 cred */
321         desired_mechs.count = 1;
322         desired_mechs.elements = &krb5oid;
323
324         maj_stat = gss_acquire_cred(&min_stat, NULL, 0,
325                                     &desired_mechs, GSS_C_INITIATE,
326                                     &credh, NULL, NULL);
327
328         if (maj_stat != GSS_S_COMPLETE) {
329                 if (get_verbosity() > 0)
330                         pgsserr("gss_acquire_cred",
331                                 maj_stat, min_stat, &krb5oid);
332                 return -1;
333         }
334
335         maj_stat = gss_set_allowable_enctypes(&min_stat, credh, &krb5oid,
336                                              num_enctypes, &enctypes);
337         if (maj_stat != GSS_S_COMPLETE) {
338                 pgsserr("gss_set_allowable_enctypes",
339                         maj_stat, min_stat, &krb5oid);
340                 gss_release_cred(&min_stat, &credh);
341                 return -1;
342         }
343         sec->cred = credh;
344
345         return 0;
346 }
347 #endif  /* HAVE_SET_ALLOWABLE_ENCTYPES */
348
349 /*
350  * Obtain credentials via a key in the keytab given
351  * a keytab handle and a gssd_k5_kt_princ structure.
352  * Checks to see if current credentials are expired,
353  * if not, uses the keytab to obtain new credentials.
354  *
355  * Returns:
356  *      0 => success (or credentials have not expired)
357  *      nonzero => error
358  */
359 static int
360 gssd_get_single_krb5_cred(krb5_context context,
361                           krb5_keytab kt,
362                           struct gssd_k5_kt_princ *ple,
363                           int nocache)
364 {
365 #if HAVE_KRB5_GET_INIT_CREDS_OPT_SET_ADDRESSLESS
366         krb5_get_init_creds_opt *init_opts = NULL;
367 #else
368         krb5_get_init_creds_opt options;
369 #endif
370         krb5_get_init_creds_opt *opts;
371         krb5_creds my_creds;
372         krb5_ccache ccache = NULL;
373         char kt_name[BUFSIZ];
374         char cc_name[BUFSIZ];
375         int code;
376         time_t now = time(0);
377         char *cache_type;
378         char *pname = NULL;
379         char *k5err = NULL;
380
381         memset(&my_creds, 0, sizeof(my_creds));
382
383         if (ple->ccname && ple->endtime > now && !nocache) {
384                 printerr(2, "INFO: Credentials in CC '%s' are good until %d\n",
385                          ple->ccname, ple->endtime);
386                 code = 0;
387                 goto out;
388         }
389
390         if ((code = krb5_kt_get_name(context, kt, kt_name, BUFSIZ))) {
391                 printerr(0, "ERROR: Unable to get keytab name in "
392                             "gssd_get_single_krb5_cred\n");
393                 goto out;
394         }
395
396         if ((krb5_unparse_name(context, ple->princ, &pname)))
397                 pname = NULL;
398
399 #if HAVE_KRB5_GET_INIT_CREDS_OPT_SET_ADDRESSLESS
400         code = krb5_get_init_creds_opt_alloc(context, &init_opts);
401         if (code) {
402                 k5err = gssd_k5_err_msg(context, code);
403                 printerr(0, "ERROR: %s allocating gic options\n", k5err);
404                 goto out;
405         }
406         if (krb5_get_init_creds_opt_set_addressless(context, init_opts, 1))
407                 printerr(1, "WARNING: Unable to set option for addressless "
408                          "tickets.  May have problems behind a NAT.\n");
409 #ifdef TEST_SHORT_LIFETIME
410         /* set a short lifetime (for debugging only!) */
411         printerr(0, "WARNING: Using (debug) short machine cred lifetime!\n");
412         krb5_get_init_creds_opt_set_tkt_life(init_opts, 5*60);
413 #endif
414         opts = init_opts;
415
416 #else   /* HAVE_KRB5_GET_INIT_CREDS_OPT_SET_ADDRESSLESS */
417
418         krb5_get_init_creds_opt_init(&options);
419         krb5_get_init_creds_opt_set_address_list(&options, NULL);
420 #ifdef TEST_SHORT_LIFETIME
421         /* set a short lifetime (for debugging only!) */
422         printerr(0, "WARNING: Using (debug) short machine cred lifetime!\n");
423         krb5_get_init_creds_opt_set_tkt_life(&options, 5*60);
424 #endif
425         opts = &options;
426 #endif
427
428         if ((code = krb5_get_init_creds_keytab(context, &my_creds, ple->princ,
429                                                kt, 0, NULL, opts))) {
430                 k5err = gssd_k5_err_msg(context, code);
431                 printerr(1, "WARNING: %s while getting initial ticket for "
432                          "principal '%s' using keytab '%s'\n", k5err,
433                          pname ? pname : "<unparsable>", kt_name);
434                 goto out;
435         }
436
437         /*
438          * Initialize cache file which we're going to be using
439          */
440
441         if (use_memcache)
442             cache_type = "MEMORY";
443         else
444             cache_type = "FILE";
445         snprintf(cc_name, sizeof(cc_name), "%s:%s/%s%s_%s",
446                 cache_type,
447                 ccachesearch[0], GSSD_DEFAULT_CRED_PREFIX,
448                 GSSD_DEFAULT_MACHINE_CRED_SUFFIX, ple->realm);
449         ple->endtime = my_creds.times.endtime;
450         if (ple->ccname != NULL)
451                 free(ple->ccname);
452         ple->ccname = strdup(cc_name);
453         if (ple->ccname == NULL) {
454                 printerr(0, "ERROR: no storage to duplicate credentials "
455                             "cache name '%s'\n", cc_name);
456                 code = ENOMEM;
457                 goto out;
458         }
459         if ((code = krb5_cc_resolve(context, cc_name, &ccache))) {
460                 k5err = gssd_k5_err_msg(context, code);
461                 printerr(0, "ERROR: %s while opening credential cache '%s'\n",
462                          k5err, cc_name);
463                 goto out;
464         }
465         if ((code = krb5_cc_initialize(context, ccache, ple->princ))) {
466                 k5err = gssd_k5_err_msg(context, code);
467                 printerr(0, "ERROR: %s while initializing credential "
468                          "cache '%s'\n", k5err, cc_name);
469                 goto out;
470         }
471         if ((code = krb5_cc_store_cred(context, ccache, &my_creds))) {
472                 k5err = gssd_k5_err_msg(context, code);
473                 printerr(0, "ERROR: %s while storing credentials in '%s'\n",
474                          k5err, cc_name);
475                 goto out;
476         }
477
478         code = 0;
479         printerr(2, "Successfully obtained machine credentials for "
480                  "principal '%s' stored in ccache '%s'\n", pname, cc_name);
481   out:
482 #if HAVE_KRB5_GET_INIT_CREDS_OPT_SET_ADDRESSLESS
483         if (init_opts)
484                 krb5_get_init_creds_opt_free(context, init_opts);
485 #endif
486         if (pname)
487                 k5_free_unparsed_name(context, pname);
488         if (ccache)
489                 krb5_cc_close(context, ccache);
490         krb5_free_cred_contents(context, &my_creds);
491         free(k5err);
492         return (code);
493 }
494
495 /*
496  * Depending on the version of Kerberos, we either need to use
497  * a private function, or simply set the environment variable.
498  */
499 static void
500 gssd_set_krb5_ccache_name(char *ccname)
501 {
502 #ifdef USE_GSS_KRB5_CCACHE_NAME
503         u_int   maj_stat, min_stat;
504
505         printerr(2, "using gss_krb5_ccache_name to select krb5 ccache %s\n",
506                  ccname);
507         maj_stat = gss_krb5_ccache_name(&min_stat, ccname, NULL);
508         if (maj_stat != GSS_S_COMPLETE) {
509                 printerr(0, "WARNING: gss_krb5_ccache_name with "
510                         "name '%s' failed (%s)\n",
511                         ccname, error_message(min_stat));
512         }
513 #else
514         /*
515          * Set the KRB5CCNAME environment variable to tell the krb5 code
516          * which credentials cache to use.  (Instead of using the private
517          * function above for which there is no generic gssapi
518          * equivalent.)
519          */
520         printerr(2, "using environment variable to select krb5 ccache %s\n",
521                  ccname);
522         setenv("KRB5CCNAME", ccname, 1);
523 #endif
524 }
525
526 /*
527  * Given a principal, find a matching ple structure
528  */
529 static struct gssd_k5_kt_princ *
530 find_ple_by_princ(krb5_context context, krb5_principal princ)
531 {
532         struct gssd_k5_kt_princ *ple;
533
534         for (ple = gssd_k5_kt_princ_list; ple != NULL; ple = ple->next) {
535                 if (krb5_principal_compare(context, ple->princ, princ))
536                         return ple;
537         }
538         /* no match found */
539         return NULL;
540 }
541
542 /*
543  * Create, initialize, and add a new ple structure to the global list
544  */
545 static struct gssd_k5_kt_princ *
546 new_ple(krb5_context context, krb5_principal princ)
547 {
548         struct gssd_k5_kt_princ *ple = NULL, *p;
549         krb5_error_code code;
550         char *default_realm;
551         int is_default_realm = 0;
552
553         ple = malloc(sizeof(struct gssd_k5_kt_princ));
554         if (ple == NULL)
555                 goto outerr;
556         memset(ple, 0, sizeof(*ple));
557
558 #ifdef HAVE_KRB5
559         ple->realm = strndup(princ->realm.data,
560                              princ->realm.length);
561 #else
562         ple->realm = strdup(princ->realm);
563 #endif
564         if (ple->realm == NULL)
565                 goto outerr;
566         code = krb5_copy_principal(context, princ, &ple->princ);
567         if (code)
568                 goto outerr;
569
570         /*
571          * Add new entry onto the list (if this is the default
572          * realm, always add to the front of the list)
573          */
574
575         code = krb5_get_default_realm(context, &default_realm);
576         if (code == 0) {
577                 if (strcmp(ple->realm, default_realm) == 0)
578                         is_default_realm = 1;
579                 k5_free_default_realm(context, default_realm);
580         }
581
582         if (is_default_realm) {
583                 ple->next = gssd_k5_kt_princ_list;
584                 gssd_k5_kt_princ_list = ple;
585         } else {
586                 p = gssd_k5_kt_princ_list;
587                 while (p != NULL && p->next != NULL)
588                         p = p->next;
589                 if (p == NULL)
590                         gssd_k5_kt_princ_list = ple;
591                 else
592                         p->next = ple;
593         }
594
595         return ple;
596 outerr:
597         if (ple) {
598                 if (ple->realm)
599                         free(ple->realm);
600                 free(ple);
601         }
602         return NULL;
603 }
604
605 /*
606  * Given a principal, find an existing ple structure, or create one
607  */
608 static struct gssd_k5_kt_princ *
609 get_ple_by_princ(krb5_context context, krb5_principal princ)
610 {
611         struct gssd_k5_kt_princ *ple;
612
613         /* Need to serialize list if we ever become multi-threaded! */
614
615         ple = find_ple_by_princ(context, princ);
616         if (ple == NULL) {
617                 ple = new_ple(context, princ);
618         }
619
620         return ple;
621 }
622
623 /*
624  * Given a (possibly unqualified) hostname,
625  * return the fully qualified (lower-case!) hostname
626  */
627 static int
628 get_full_hostname(const char *inhost, char *outhost, int outhostlen)
629 {
630         struct addrinfo *addrs = NULL;
631         struct addrinfo hints;
632         int retval;
633         char *c;
634
635         memset(&hints, 0, sizeof(hints));
636         hints.ai_socktype = SOCK_STREAM;
637         hints.ai_family = PF_UNSPEC;
638         hints.ai_flags = AI_CANONNAME;
639
640         /* Get full target hostname */
641         retval = getaddrinfo(inhost, NULL, &hints, &addrs);
642         if (retval) {
643                 printerr(1, "%s while getting full hostname for '%s'\n",
644                          gai_strerror(retval), inhost);
645                 goto out;
646         }
647         strncpy(outhost, addrs->ai_canonname, outhostlen);
648         freeaddrinfo(addrs);
649         for (c = outhost; *c != '\0'; c++)
650             *c = tolower(*c);
651
652         printerr(3, "Full hostname for '%s' is '%s'\n", inhost, outhost);
653         retval = 0;
654 out:
655         return retval;
656 }
657
658 /* 
659  * If principal matches the given realm and service name,
660  * and has *any* instance (hostname), return 1.
661  * Otherwise return 0, indicating no match.
662  */
663 static int
664 realm_and_service_match(krb5_context context, krb5_principal p,
665                         const char *realm, const char *service)
666 {
667 #ifdef HAVE_KRB5
668         /* Must have two components */
669         if (p->length != 2)
670                 return 0;
671         if ((strlen(realm) == p->realm.length)
672             && (strncmp(realm, p->realm.data, p->realm.length) == 0)
673             && (strlen(service) == p->data[0].length)
674             && (strncmp(service, p->data[0].data, p->data[0].length) == 0))
675                 return 1;
676 #else
677         const char *name, *inst;
678
679         if (p->name.name_string.len != 2)
680                 return 0;
681         name = krb5_principal_get_comp_string(context, p, 0);
682         inst = krb5_principal_get_comp_string(context, p, 1);
683         if (name == NULL || inst == NULL)
684                 return 0;
685         if ((strcmp(realm, p->realm) == 0)
686             && (strcmp(service, name) == 0))
687                 return 1;
688 #endif
689         return 0;
690 }
691
692 /*
693  * Search the given keytab file looking for an entry with the given
694  * service name and realm, ignoring hostname (instance).
695  *
696  * Returns:
697  *      0 => No error
698  *      non-zero => An error occurred
699  *
700  * If a keytab entry is found, "found" is set to one, and the keytab
701  * entry is returned in "kte".  Otherwise, "found" is zero, and the
702  * value of "kte" is unpredictable.
703  */
704 static int
705 gssd_search_krb5_keytab(krb5_context context, krb5_keytab kt,
706                         const char *realm, const char *service,
707                         int *found, krb5_keytab_entry *kte)
708 {
709         krb5_kt_cursor cursor;
710         krb5_error_code code;
711         struct gssd_k5_kt_princ *ple;
712         int retval = -1;
713         char kt_name[BUFSIZ];
714         char *pname;
715         char *k5err = NULL;
716
717         if (found == NULL) {
718                 retval = EINVAL;
719                 goto out;
720         }
721         *found = 0;
722
723         /*
724          * Look through each entry in the keytab file and determine
725          * if we might want to use it as machine credentials.  If so,
726          * save info in the global principal list (gssd_k5_kt_princ_list).
727          */
728         if ((code = krb5_kt_get_name(context, kt, kt_name, BUFSIZ))) {
729                 k5err = gssd_k5_err_msg(context, code);
730                 printerr(0, "ERROR: %s attempting to get keytab name\n", k5err);
731                 retval = code;
732                 goto out;
733         }
734         if ((code = krb5_kt_start_seq_get(context, kt, &cursor))) {
735                 k5err = gssd_k5_err_msg(context, code);
736                 printerr(0, "ERROR: %s while beginning keytab scan "
737                             "for keytab '%s'\n", k5err, kt_name);
738                 retval = code;
739                 goto out;
740         }
741
742         while ((code = krb5_kt_next_entry(context, kt, kte, &cursor)) == 0) {
743                 if ((code = krb5_unparse_name(context, kte->principal,
744                                               &pname))) {
745                         k5err = gssd_k5_err_msg(context, code);
746                         printerr(0, "WARNING: Skipping keytab entry because "
747                                  "we failed to unparse principal name: %s\n",
748                                  k5err);
749                         k5_free_kt_entry(context, kte);
750                         continue;
751                 }
752                 printerr(4, "Processing keytab entry for principal '%s'\n",
753                          pname);
754                 /* Use the first matching keytab entry found */
755                 if ((realm_and_service_match(context, kte->principal, realm,
756                                              service))) {
757                         printerr(4, "We WILL use this entry (%s)\n", pname);
758                         ple = get_ple_by_princ(context, kte->principal);
759                         /*
760                          * Return, don't free, keytab entry if
761                          * we were successful!
762                          */
763                         if (ple == NULL) {
764                                 retval = ENOMEM;
765                                 k5_free_kt_entry(context, kte);
766                         } else {
767                                 retval = 0;
768                                 *found = 1;
769                         }
770                         k5_free_unparsed_name(context, pname);
771                         break;
772                 }
773                 else {
774                         printerr(4, "We will NOT use this entry (%s)\n",
775                                 pname);
776                 }
777                 k5_free_unparsed_name(context, pname);
778                 k5_free_kt_entry(context, kte);
779         }
780
781         if ((code = krb5_kt_end_seq_get(context, kt, &cursor))) {
782                 k5err = gssd_k5_err_msg(context, code);
783                 printerr(0, "WARNING: %s while ending keytab scan for "
784                             "keytab '%s'\n", k5err, kt_name);
785         }
786
787         retval = 0;
788   out:
789         free(k5err);
790         return retval;
791 }
792
793 /*
794  * Find a keytab entry to use for a given target hostname.
795  * Tries to find the most appropriate keytab to use given the
796  * name of the host we are trying to connect with.
797  */
798 static int
799 find_keytab_entry(krb5_context context, krb5_keytab kt, const char *hostname,
800                   krb5_keytab_entry *kte, const char **svcnames)
801 {
802         krb5_error_code code;
803         char **realmnames = NULL;
804         char myhostname[NI_MAXHOST], targethostname[NI_MAXHOST];
805         int i, j, retval;
806         char *default_realm = NULL;
807         char *realm;
808         char *k5err = NULL;
809         int tried_all = 0, tried_default = 0;
810         krb5_principal princ;
811
812
813         /* Get full target hostname */
814         retval = get_full_hostname(hostname, targethostname,
815                                    sizeof(targethostname));
816         if (retval)
817                 goto out;
818
819         /* Get full local hostname */
820         retval = gethostname(myhostname, sizeof(myhostname));
821         if (retval) {
822                 k5err = gssd_k5_err_msg(context, retval);
823                 printerr(1, "%s while getting local hostname\n", k5err);
824                 goto out;
825         }
826         retval = get_full_hostname(myhostname, myhostname, sizeof(myhostname));
827         if (retval)
828                 goto out;
829
830         code = krb5_get_default_realm(context, &default_realm);
831         if (code) {
832                 retval = code;
833                 k5err = gssd_k5_err_msg(context, code);
834                 printerr(1, "%s while getting default realm name\n", k5err);
835                 goto out;
836         }
837
838         /*
839          * Get the realm name(s) for the target hostname.
840          * In reality, this function currently only returns a
841          * single realm, but we code with the assumption that
842          * someday it may actually return a list.
843          */
844         code = krb5_get_host_realm(context, targethostname, &realmnames);
845         if (code) {
846                 k5err = gssd_k5_err_msg(context, code);
847                 printerr(0, "ERROR: %s while getting realm(s) for host '%s'\n",
848                          k5err, targethostname);
849                 retval = code;
850                 goto out;
851         }
852
853         /*
854          * Try the "appropriate" realm first, and if nothing found for that
855          * realm, try the default realm (if it hasn't already been tried).
856          */
857         i = 0;
858         realm = realmnames[i];
859         while (1) {
860                 if (realm == NULL) {
861                         tried_all = 1;
862                         if (!tried_default)
863                                 realm = default_realm;
864                 }
865                 if (tried_all && tried_default)
866                         break;
867                 if (strcmp(realm, default_realm) == 0)
868                         tried_default = 1;
869                 for (j = 0; svcnames[j] != NULL; j++) {
870                         code = krb5_build_principal_ext(context, &princ,
871                                                         strlen(realm),
872                                                         realm,
873                                                         strlen(svcnames[j]),
874                                                         svcnames[j],
875                                                         strlen(myhostname),
876                                                         myhostname,
877                                                         NULL);
878                         if (code) {
879                                 k5err = gssd_k5_err_msg(context, code);
880                                 printerr(1, "%s while building principal for "
881                                          "'%s/%s@%s'\n", k5err, svcnames[j],
882                                          myhostname, realm);
883                                 continue;
884                         }
885                         code = krb5_kt_get_entry(context, kt, princ, 0, 0, kte);
886                         krb5_free_principal(context, princ);
887                         if (code) {
888                                 k5err = gssd_k5_err_msg(context, code);
889                                 printerr(3, "%s while getting keytab entry for "
890                                          "'%s/%s@%s'\n", k5err, svcnames[j],
891                                          myhostname, realm);
892                         } else {
893                                 printerr(3, "Success getting keytab entry for "
894                                          "'%s/%s@%s'\n",
895                                          svcnames[j], myhostname, realm);
896                                 retval = 0;
897                                 goto out;
898                         }
899                         retval = code;
900                 }
901                 /*
902                  * Nothing found with our hostname instance, now look for
903                  * names with any instance (they must have an instance)
904                  */
905                 for (j = 0; svcnames[j] != NULL; j++) {
906                         int found = 0;
907                         code = gssd_search_krb5_keytab(context, kt, realm,
908                                                        svcnames[j], &found, kte);
909                         if (!code && found) {
910                                 printerr(3, "Success getting keytab entry for "
911                                          "%s/*@%s\n", svcnames[j], realm);
912                                 retval = 0;
913                                 goto out;
914                         }
915                 }
916                 if (!tried_all) {
917                         i++;
918                         realm = realmnames[i];
919                 }
920         }
921 out:
922         if (default_realm)
923                 k5_free_default_realm(context, default_realm);
924         if (realmnames)
925                 krb5_free_host_realm(context, realmnames);
926         free(k5err);
927         return retval;
928 }
929
930
931 static inline int data_is_equal(krb5_data d1, krb5_data d2)
932 {
933         return (d1.length == d2.length
934                 && memcmp(d1.data, d2.data, d1.length) == 0);
935 }
936
937 static int
938 check_for_tgt(krb5_context context, krb5_ccache ccache,
939               krb5_principal principal)
940 {
941         krb5_error_code ret;
942         krb5_creds creds;
943         krb5_cc_cursor cur;
944         int found = 0;
945
946         ret = krb5_cc_start_seq_get(context, ccache, &cur);
947         if (ret) 
948                 return 0;
949
950         while (!found &&
951                 (ret = krb5_cc_next_cred(context, ccache, &cur, &creds)) == 0) {
952                 if (creds.server->length == 2 &&
953                                 data_is_equal(creds.server->realm,
954                                               principal->realm) &&
955                                 creds.server->data[0].length == 6 &&
956                                 memcmp(creds.server->data[0].data,
957                                                 "krbtgt", 6) == 0 &&
958                                 data_is_equal(creds.server->data[1],
959                                               principal->realm) &&
960                                 creds.times.endtime > time(NULL))
961                         found = 1;
962                 krb5_free_cred_contents(context, &creds);
963         }
964         krb5_cc_end_seq_get(context, ccache, &cur);
965
966         return found;
967 }
968
969 static int
970 query_krb5_ccache(const char* cred_cache, char **ret_princname,
971                   char **ret_realm)
972 {
973         krb5_error_code ret;
974         krb5_context context;
975         krb5_ccache ccache;
976         krb5_principal principal;
977         int found = 0;
978         char *str = NULL;
979         char *princstring;
980
981         ret = krb5_init_context(&context);
982         if (ret) 
983                 return 0;
984
985         if(!cred_cache || krb5_cc_resolve(context, cred_cache, &ccache))
986                 goto err_cache;
987
988         if (krb5_cc_set_flags(context, ccache, 0))
989                 goto err_princ;
990
991         ret = krb5_cc_get_principal(context, ccache, &principal);
992         if (ret) 
993                 goto err_princ;
994
995         found = check_for_tgt(context, ccache, principal);
996         if (found) {
997                 ret = krb5_unparse_name(context, principal, &princstring);
998                 if (ret == 0) {
999                     if ((str = strchr(princstring, '@')) != NULL) {
1000                             *str = '\0';
1001                             *ret_princname = strdup(princstring);
1002                             *ret_realm = strdup(str+1);
1003                     }
1004                     k5_free_unparsed_name(context, princstring);
1005                 } else {
1006                         found = 0;
1007                 }
1008         }
1009         krb5_free_principal(context, principal);
1010 err_princ:
1011         krb5_cc_set_flags(context, ccache,  KRB5_TC_OPENCLOSE);
1012         krb5_cc_close(context, ccache);
1013 err_cache:
1014         krb5_free_context(context);
1015         return found;
1016 }
1017
1018 /*==========================*/
1019 /*===  External routines ===*/
1020 /*==========================*/
1021
1022 /*
1023  * Attempt to find the best match for a credentials cache file
1024  * given only a UID.  We really need more information, but we
1025  * do the best we can.
1026  *
1027  * Returns:
1028  *      0 => a ccache was found
1029  *      1 => no ccache was found
1030  */
1031 int
1032 gssd_setup_krb5_user_gss_ccache(uid_t uid, char *servername, char *dirname)
1033 {
1034         char                    buf[MAX_NETOBJ_SZ];
1035         struct dirent           *d;
1036
1037         printerr(2, "getting credentials for client with uid %u for "
1038                     "server %s\n", uid, servername);
1039         memset(buf, 0, sizeof(buf));
1040         if (gssd_find_existing_krb5_ccache(uid, dirname, &d)) {
1041                 snprintf(buf, sizeof(buf), "FILE:%s/%s", dirname, d->d_name);
1042                 free(d);
1043         }
1044         else
1045                 return 1;
1046         printerr(2, "using %s as credentials cache for client with "
1047                     "uid %u for server %s\n", buf, uid, servername);
1048         gssd_set_krb5_ccache_name(buf);
1049         return 0;
1050 }
1051
1052 /*
1053  * Let the gss code know where to find the machine credentials ccache.
1054  *
1055  * Returns:
1056  *      void
1057  */
1058 void
1059 gssd_setup_krb5_machine_gss_ccache(char *ccname)
1060 {
1061         printerr(2, "using %s as credentials cache for machine creds\n",
1062                  ccname);
1063         gssd_set_krb5_ccache_name(ccname);
1064 }
1065
1066 /*
1067  * Return an array of pointers to names of credential cache files
1068  * which can be used to try to create gss contexts with a server.
1069  *
1070  * Returns:
1071  *      0 => list is attached
1072  *      nonzero => error
1073  */
1074 int
1075 gssd_get_krb5_machine_cred_list(char ***list)
1076 {
1077         char **l;
1078         int listinc = 10;
1079         int listsize = listinc;
1080         int i = 0;
1081         int retval;
1082         struct gssd_k5_kt_princ *ple;
1083
1084         /* Assume failure */
1085         retval = -1;
1086         *list = (char **) NULL;
1087
1088         if ((l = (char **) malloc(listsize * sizeof(char *))) == NULL) {
1089                 retval = ENOMEM;
1090                 goto out;
1091         }
1092
1093         /* Need to serialize list if we ever become multi-threaded! */
1094
1095         for (ple = gssd_k5_kt_princ_list; ple; ple = ple->next) {
1096                 if (ple->ccname) {
1097                         /* Make sure cred is up-to-date before returning it */
1098                         retval = gssd_refresh_krb5_machine_credential(NULL, ple,
1099                                 NULL);
1100                         if (retval)
1101                                 continue;
1102                         if (i + 1 > listsize) {
1103                                 listsize += listinc;
1104                                 l = (char **)
1105                                         realloc(l, listsize * sizeof(char *));
1106                                 if (l == NULL) {
1107                                         retval = ENOMEM;
1108                                         goto out;
1109                                 }
1110                         }
1111                         if ((l[i++] = strdup(ple->ccname)) == NULL) {
1112                                 retval = ENOMEM;
1113                                 goto out;
1114                         }
1115                 }
1116         }
1117         if (i > 0) {
1118                 l[i] = NULL;
1119                 *list = l;
1120                 retval = 0;
1121                 goto out;
1122         }
1123   out:
1124         return retval;
1125 }
1126
1127 /*
1128  * Frees the list of names returned in get_krb5_machine_cred_list()
1129  */
1130 void
1131 gssd_free_krb5_machine_cred_list(char **list)
1132 {
1133         char **n;
1134
1135         if (list == NULL)
1136                 return;
1137         for (n = list; n && *n; n++) {
1138                 free(*n);
1139         }
1140         free(list);
1141 }
1142
1143 /*
1144  * Called upon exit.  Destroys machine credentials.
1145  */
1146 void
1147 gssd_destroy_krb5_machine_creds(void)
1148 {
1149         krb5_context context;
1150         krb5_error_code code = 0;
1151         krb5_ccache ccache;
1152         struct gssd_k5_kt_princ *ple;
1153         char *k5err = NULL;
1154
1155         code = krb5_init_context(&context);
1156         if (code) {
1157                 k5err = gssd_k5_err_msg(NULL, code);
1158                 printerr(0, "ERROR: %s while initializing krb5\n", k5err);
1159                 goto out;
1160         }
1161
1162         for (ple = gssd_k5_kt_princ_list; ple; ple = ple->next) {
1163                 if (!ple->ccname)
1164                         continue;
1165                 if ((code = krb5_cc_resolve(context, ple->ccname, &ccache))) {
1166                         k5err = gssd_k5_err_msg(context, code);
1167                         printerr(0, "WARNING: %s while resolving credential "
1168                                     "cache '%s' for destruction\n", k5err,
1169                                     ple->ccname);
1170                         continue;
1171                 }
1172
1173                 if ((code = krb5_cc_destroy(context, ccache))) {
1174                         k5err = gssd_k5_err_msg(context, code);
1175                         printerr(0, "WARNING: %s while destroying credential "
1176                                     "cache '%s'\n", k5err, ple->ccname);
1177                 }
1178         }
1179   out:
1180         free(k5err);
1181         krb5_free_context(context);
1182 }
1183
1184 /*
1185  * Obtain (or refresh if necessary) Kerberos machine credentials
1186  */
1187 int
1188 gssd_refresh_krb5_machine_credential(char *hostname,
1189                                      struct gssd_k5_kt_princ *ple, 
1190                                          char *service)
1191 {
1192         krb5_error_code code = 0;
1193         krb5_context context;
1194         krb5_keytab kt = NULL;;
1195         int retval = 0;
1196         char *k5err = NULL;
1197         const char *svcnames[4] = { "root", "nfs", "host", NULL };
1198
1199         /*
1200          * If a specific service name was specified, use it.
1201          * Otherwise, use the default list.
1202          */
1203         if (service != NULL && strcmp(service, "*") != 0) {
1204                 svcnames[0] = service;
1205                 svcnames[1] = NULL;
1206         }
1207         if (hostname == NULL && ple == NULL)
1208                 return EINVAL;
1209
1210         code = krb5_init_context(&context);
1211         if (code) {
1212                 k5err = gssd_k5_err_msg(NULL, code);
1213                 printerr(0, "ERROR: %s: %s while initializing krb5 context\n",
1214                          __func__, k5err);
1215                 retval = code;
1216                 goto out;
1217         }
1218
1219         if ((code = krb5_kt_resolve(context, keytabfile, &kt))) {
1220                 k5err = gssd_k5_err_msg(context, code);
1221                 printerr(0, "ERROR: %s: %s while resolving keytab '%s'\n",
1222                          __func__, k5err, keytabfile);
1223                 goto out;
1224         }
1225
1226         if (ple == NULL) {
1227                 krb5_keytab_entry kte;
1228
1229                 code = find_keytab_entry(context, kt, hostname, &kte, svcnames);
1230                 if (code) {
1231                         printerr(0, "ERROR: %s: no usable keytab entry found "
1232                                  "in keytab %s for connection with host %s\n",
1233                                  __FUNCTION__, keytabfile, hostname);
1234                         retval = code;
1235                         goto out;
1236                 }
1237
1238                 ple = get_ple_by_princ(context, kte.principal);
1239                 k5_free_kt_entry(context, &kte);
1240                 if (ple == NULL) {
1241                         char *pname;
1242                         if ((krb5_unparse_name(context, kte.principal, &pname))) {
1243                                 pname = NULL;
1244                         }
1245                         printerr(0, "ERROR: %s: Could not locate or create "
1246                                  "ple struct for principal %s for connection "
1247                                  "with host %s\n",
1248                                  __FUNCTION__, pname ? pname : "<unparsable>",
1249                                  hostname);
1250                         if (pname) k5_free_unparsed_name(context, pname);
1251                         goto out;
1252                 }
1253         }
1254         retval = gssd_get_single_krb5_cred(context, kt, ple, 0);
1255 out:
1256         if (kt)
1257                 krb5_kt_close(context, kt);
1258         krb5_free_context(context);
1259         free(k5err);
1260         return retval;
1261 }
1262
1263 /*
1264  * A common routine for getting the Kerberos error message
1265  */
1266 char *
1267 gssd_k5_err_msg(krb5_context context, krb5_error_code code)
1268 {
1269         const char *origmsg;
1270         char *msg = NULL;
1271
1272 #if HAVE_KRB5_GET_ERROR_MESSAGE
1273         if (context != NULL) {
1274                 origmsg = krb5_get_error_message(context, code);
1275                 msg = strdup(origmsg);
1276                 krb5_free_error_message(context, origmsg);
1277         }
1278 #endif
1279         if (msg != NULL)
1280                 return msg;
1281 #if HAVE_KRB5
1282         return strdup(error_message(code));
1283 #else
1284         if (context != NULL)
1285                 return strdup(krb5_get_err_text(context, code));
1286         else
1287                 return strdup(error_message(code));
1288 #endif
1289 }
1290
1291 /*
1292  * Return default Kerberos realm
1293  */
1294 void
1295 gssd_k5_get_default_realm(char **def_realm)
1296 {
1297         krb5_context context;
1298
1299         if (krb5_init_context(&context))
1300                 return;
1301
1302         krb5_get_default_realm(context, def_realm);
1303
1304         krb5_free_context(context);
1305 }