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