gssd: free buffer allocated by gssd_k5_err_msg
[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);
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 {
364 #if HAVE_KRB5_GET_INIT_CREDS_OPT_SET_ADDRESSLESS
365         krb5_get_init_creds_opt *init_opts = NULL;
366 #else
367         krb5_get_init_creds_opt options;
368 #endif
369         krb5_get_init_creds_opt *opts;
370         krb5_creds my_creds;
371         krb5_ccache ccache = NULL;
372         char kt_name[BUFSIZ];
373         char cc_name[BUFSIZ];
374         int code;
375         time_t now = time(0);
376         char *cache_type;
377         char *pname = NULL;
378         char *k5err = NULL;
379
380         memset(&my_creds, 0, sizeof(my_creds));
381
382         if (ple->ccname && ple->endtime > now) {
383                 printerr(2, "INFO: Credentials in CC '%s' are good until %d\n",
384                          ple->ccname, ple->endtime);
385                 code = 0;
386                 goto out;
387         }
388
389         if ((code = krb5_kt_get_name(context, kt, kt_name, BUFSIZ))) {
390                 printerr(0, "ERROR: Unable to get keytab name in "
391                             "gssd_get_single_krb5_cred\n");
392                 goto out;
393         }
394
395         if ((krb5_unparse_name(context, ple->princ, &pname)))
396                 pname = NULL;
397
398 #if HAVE_KRB5_GET_INIT_CREDS_OPT_SET_ADDRESSLESS
399         code = krb5_get_init_creds_opt_alloc(context, &init_opts);
400         if (code) {
401                 k5err = gssd_k5_err_msg(context, code);
402                 printerr(0, "ERROR: %s allocating gic options\n", k5err);
403                 goto out;
404         }
405         if (krb5_get_init_creds_opt_set_addressless(context, init_opts, 1))
406                 printerr(1, "WARNING: Unable to set option for addressless "
407                          "tickets.  May have problems behind a NAT.\n");
408 #ifdef TEST_SHORT_LIFETIME
409         /* set a short lifetime (for debugging only!) */
410         printerr(0, "WARNING: Using (debug) short machine cred lifetime!\n");
411         krb5_get_init_creds_opt_set_tkt_life(init_opts, 5*60);
412 #endif
413         opts = init_opts;
414
415 #else   /* HAVE_KRB5_GET_INIT_CREDS_OPT_SET_ADDRESSLESS */
416
417         krb5_get_init_creds_opt_init(&options);
418         krb5_get_init_creds_opt_set_address_list(&options, NULL);
419 #ifdef TEST_SHORT_LIFETIME
420         /* set a short lifetime (for debugging only!) */
421         printerr(0, "WARNING: Using (debug) short machine cred lifetime!\n");
422         krb5_get_init_creds_opt_set_tkt_life(&options, 5*60);
423 #endif
424         opts = &options;
425 #endif
426
427         if ((code = krb5_get_init_creds_keytab(context, &my_creds, ple->princ,
428                                                kt, 0, NULL, opts))) {
429                 k5err = gssd_k5_err_msg(context, code);
430                 printerr(1, "WARNING: %s while getting initial ticket for "
431                          "principal '%s' using keytab '%s'\n", k5err,
432                          pname ? pname : "<unparsable>", kt_name);
433                 goto out;
434         }
435
436         /*
437          * Initialize cache file which we're going to be using
438          */
439
440         if (use_memcache)
441             cache_type = "MEMORY";
442         else
443             cache_type = "FILE";
444         snprintf(cc_name, sizeof(cc_name), "%s:%s/%s%s_%s",
445                 cache_type,
446                 ccachesearch[0], GSSD_DEFAULT_CRED_PREFIX,
447                 GSSD_DEFAULT_MACHINE_CRED_SUFFIX, ple->realm);
448         ple->endtime = my_creds.times.endtime;
449         if (ple->ccname != NULL)
450                 free(ple->ccname);
451         ple->ccname = strdup(cc_name);
452         if (ple->ccname == NULL) {
453                 printerr(0, "ERROR: no storage to duplicate credentials "
454                             "cache name '%s'\n", cc_name);
455                 code = ENOMEM;
456                 goto out;
457         }
458         if ((code = krb5_cc_resolve(context, cc_name, &ccache))) {
459                 k5err = gssd_k5_err_msg(context, code);
460                 printerr(0, "ERROR: %s while opening credential cache '%s'\n",
461                          k5err, cc_name);
462                 goto out;
463         }
464         if ((code = krb5_cc_initialize(context, ccache, ple->princ))) {
465                 k5err = gssd_k5_err_msg(context, code);
466                 printerr(0, "ERROR: %s while initializing credential "
467                          "cache '%s'\n", k5err, cc_name);
468                 goto out;
469         }
470         if ((code = krb5_cc_store_cred(context, ccache, &my_creds))) {
471                 k5err = gssd_k5_err_msg(context, code);
472                 printerr(0, "ERROR: %s while storing credentials in '%s'\n",
473                          k5err, cc_name);
474                 goto out;
475         }
476
477         code = 0;
478         printerr(2, "Successfully obtained machine credentials for "
479                  "principal '%s' stored in ccache '%s'\n", pname, cc_name);
480   out:
481 #if HAVE_KRB5_GET_INIT_CREDS_OPT_SET_ADDRESSLESS
482         if (init_opts)
483                 krb5_get_init_creds_opt_free(context, init_opts);
484 #endif
485         if (pname)
486                 k5_free_unparsed_name(context, pname);
487         if (ccache)
488                 krb5_cc_close(context, ccache);
489         krb5_free_cred_contents(context, &my_creds);
490         free(k5err);
491         return (code);
492 }
493
494 /*
495  * Depending on the version of Kerberos, we either need to use
496  * a private function, or simply set the environment variable.
497  */
498 static void
499 gssd_set_krb5_ccache_name(char *ccname)
500 {
501 #ifdef USE_GSS_KRB5_CCACHE_NAME
502         u_int   maj_stat, min_stat;
503
504         printerr(2, "using gss_krb5_ccache_name to select krb5 ccache %s\n",
505                  ccname);
506         maj_stat = gss_krb5_ccache_name(&min_stat, ccname, NULL);
507         if (maj_stat != GSS_S_COMPLETE) {
508                 printerr(0, "WARNING: gss_krb5_ccache_name with "
509                         "name '%s' failed (%s)\n",
510                         ccname, error_message(min_stat));
511         }
512 #else
513         /*
514          * Set the KRB5CCNAME environment variable to tell the krb5 code
515          * which credentials cache to use.  (Instead of using the private
516          * function above for which there is no generic gssapi
517          * equivalent.)
518          */
519         printerr(2, "using environment variable to select krb5 ccache %s\n",
520                  ccname);
521         setenv("KRB5CCNAME", ccname, 1);
522 #endif
523 }
524
525 /*
526  * Given a principal, find a matching ple structure
527  */
528 static struct gssd_k5_kt_princ *
529 find_ple_by_princ(krb5_context context, krb5_principal princ)
530 {
531         struct gssd_k5_kt_princ *ple;
532
533         for (ple = gssd_k5_kt_princ_list; ple != NULL; ple = ple->next) {
534                 if (krb5_principal_compare(context, ple->princ, princ))
535                         return ple;
536         }
537         /* no match found */
538         return NULL;
539 }
540
541 /*
542  * Create, initialize, and add a new ple structure to the global list
543  */
544 static struct gssd_k5_kt_princ *
545 new_ple(krb5_context context, krb5_principal princ)
546 {
547         struct gssd_k5_kt_princ *ple = NULL, *p;
548         krb5_error_code code;
549         char *default_realm;
550         int is_default_realm = 0;
551
552         ple = malloc(sizeof(struct gssd_k5_kt_princ));
553         if (ple == NULL)
554                 goto outerr;
555         memset(ple, 0, sizeof(*ple));
556
557 #ifdef HAVE_KRB5
558         ple->realm = strndup(princ->realm.data,
559                              princ->realm.length);
560 #else
561         ple->realm = strdup(princ->realm);
562 #endif
563         if (ple->realm == NULL)
564                 goto outerr;
565         code = krb5_copy_principal(context, princ, &ple->princ);
566         if (code)
567                 goto outerr;
568
569         /*
570          * Add new entry onto the list (if this is the default
571          * realm, always add to the front of the list)
572          */
573
574         code = krb5_get_default_realm(context, &default_realm);
575         if (code == 0) {
576                 if (strcmp(ple->realm, default_realm) == 0)
577                         is_default_realm = 1;
578                 k5_free_default_realm(context, default_realm);
579         }
580
581         if (is_default_realm) {
582                 ple->next = gssd_k5_kt_princ_list;
583                 gssd_k5_kt_princ_list = ple;
584         } else {
585                 p = gssd_k5_kt_princ_list;
586                 while (p != NULL && p->next != NULL)
587                         p = p->next;
588                 if (p == NULL)
589                         gssd_k5_kt_princ_list = ple;
590                 else
591                         p->next = ple;
592         }
593
594         return ple;
595 outerr:
596         if (ple) {
597                 if (ple->realm)
598                         free(ple->realm);
599                 free(ple);
600         }
601         return NULL;
602 }
603
604 /*
605  * Given a principal, find an existing ple structure, or create one
606  */
607 static struct gssd_k5_kt_princ *
608 get_ple_by_princ(krb5_context context, krb5_principal princ)
609 {
610         struct gssd_k5_kt_princ *ple;
611
612         /* Need to serialize list if we ever become multi-threaded! */
613
614         ple = find_ple_by_princ(context, princ);
615         if (ple == NULL) {
616                 ple = new_ple(context, princ);
617         }
618
619         return ple;
620 }
621
622 /*
623  * Given a (possibly unqualified) hostname,
624  * return the fully qualified (lower-case!) hostname
625  */
626 static int
627 get_full_hostname(const char *inhost, char *outhost, int outhostlen)
628 {
629         struct addrinfo *addrs = NULL;
630         struct addrinfo hints;
631         int retval;
632         char *c;
633
634         memset(&hints, 0, sizeof(hints));
635         hints.ai_socktype = SOCK_STREAM;
636         hints.ai_family = PF_UNSPEC;
637         hints.ai_flags = AI_CANONNAME;
638
639         /* Get full target hostname */
640         retval = getaddrinfo(inhost, NULL, &hints, &addrs);
641         if (retval) {
642                 printerr(1, "%s while getting full hostname for '%s'\n",
643                          gai_strerror(retval), inhost);
644                 goto out;
645         }
646         strncpy(outhost, addrs->ai_canonname, outhostlen);
647         freeaddrinfo(addrs);
648         for (c = outhost; *c != '\0'; c++)
649             *c = tolower(*c);
650
651         printerr(3, "Full hostname for '%s' is '%s'\n", inhost, outhost);
652         retval = 0;
653 out:
654         return retval;
655 }
656
657 /* 
658  * If principal matches the given realm and service name,
659  * and has *any* instance (hostname), return 1.
660  * Otherwise return 0, indicating no match.
661  */
662 static int
663 realm_and_service_match(krb5_context context, krb5_principal p,
664                         const char *realm, const char *service)
665 {
666 #ifdef HAVE_KRB5
667         /* Must have two components */
668         if (p->length != 2)
669                 return 0;
670         if ((strlen(realm) == p->realm.length)
671             && (strncmp(realm, p->realm.data, p->realm.length) == 0)
672             && (strlen(service) == p->data[0].length)
673             && (strncmp(service, p->data[0].data, p->data[0].length) == 0))
674                 return 1;
675 #else
676         const char *name, *inst;
677
678         if (p->name.name_string.len != 2)
679                 return 0;
680         name = krb5_principal_get_comp_string(context, p, 0);
681         inst = krb5_principal_get_comp_string(context, p, 1);
682         if (name == NULL || inst == NULL)
683                 return 0;
684         if ((strcmp(realm, p->realm) == 0)
685             && (strcmp(service, name) == 0))
686                 return 1;
687 #endif
688         return 0;
689 }
690
691 /*
692  * Search the given keytab file looking for an entry with the given
693  * service name and realm, ignoring hostname (instance).
694  *
695  * Returns:
696  *      0 => No error
697  *      non-zero => An error occurred
698  *
699  * If a keytab entry is found, "found" is set to one, and the keytab
700  * entry is returned in "kte".  Otherwise, "found" is zero, and the
701  * value of "kte" is unpredictable.
702  */
703 static int
704 gssd_search_krb5_keytab(krb5_context context, krb5_keytab kt,
705                         const char *realm, const char *service,
706                         int *found, krb5_keytab_entry *kte)
707 {
708         krb5_kt_cursor cursor;
709         krb5_error_code code;
710         struct gssd_k5_kt_princ *ple;
711         int retval = -1;
712         char kt_name[BUFSIZ];
713         char *pname;
714         char *k5err = NULL;
715
716         if (found == NULL) {
717                 retval = EINVAL;
718                 goto out;
719         }
720         *found = 0;
721
722         /*
723          * Look through each entry in the keytab file and determine
724          * if we might want to use it as machine credentials.  If so,
725          * save info in the global principal list (gssd_k5_kt_princ_list).
726          */
727         if ((code = krb5_kt_get_name(context, kt, kt_name, BUFSIZ))) {
728                 k5err = gssd_k5_err_msg(context, code);
729                 printerr(0, "ERROR: %s attempting to get keytab name\n", k5err);
730                 retval = code;
731                 goto out;
732         }
733         if ((code = krb5_kt_start_seq_get(context, kt, &cursor))) {
734                 k5err = gssd_k5_err_msg(context, code);
735                 printerr(0, "ERROR: %s while beginning keytab scan "
736                             "for keytab '%s'\n", k5err, kt_name);
737                 retval = code;
738                 goto out;
739         }
740
741         while ((code = krb5_kt_next_entry(context, kt, kte, &cursor)) == 0) {
742                 if ((code = krb5_unparse_name(context, kte->principal,
743                                               &pname))) {
744                         k5err = gssd_k5_err_msg(context, code);
745                         printerr(0, "WARNING: Skipping keytab entry because "
746                                  "we failed to unparse principal name: %s\n",
747                                  k5err);
748                         k5_free_kt_entry(context, kte);
749                         continue;
750                 }
751                 printerr(4, "Processing keytab entry for principal '%s'\n",
752                          pname);
753                 /* Use the first matching keytab entry found */
754                 if ((realm_and_service_match(context, kte->principal, realm,
755                                              service))) {
756                         printerr(4, "We WILL use this entry (%s)\n", pname);
757                         ple = get_ple_by_princ(context, kte->principal);
758                         /*
759                          * Return, don't free, keytab entry if
760                          * we were successful!
761                          */
762                         if (ple == NULL) {
763                                 retval = ENOMEM;
764                                 k5_free_kt_entry(context, kte);
765                         } else {
766                                 retval = 0;
767                                 *found = 1;
768                         }
769                         k5_free_unparsed_name(context, pname);
770                         break;
771                 }
772                 else {
773                         printerr(4, "We will NOT use this entry (%s)\n",
774                                 pname);
775                 }
776                 k5_free_unparsed_name(context, pname);
777                 k5_free_kt_entry(context, kte);
778         }
779
780         if ((code = krb5_kt_end_seq_get(context, kt, &cursor))) {
781                 k5err = gssd_k5_err_msg(context, code);
782                 printerr(0, "WARNING: %s while ending keytab scan for "
783                             "keytab '%s'\n", k5err, kt_name);
784         }
785
786         retval = 0;
787   out:
788         free(k5err);
789         return retval;
790 }
791
792 /*
793  * Find a keytab entry to use for a given target hostname.
794  * Tries to find the most appropriate keytab to use given the
795  * name of the host we are trying to connect with.
796  */
797 static int
798 find_keytab_entry(krb5_context context, krb5_keytab kt, const char *hostname,
799                   krb5_keytab_entry *kte)
800 {
801         krb5_error_code code;
802         const char *svcnames[] = { "root", "nfs", "host", NULL };
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                         if (retval)
1100                                 continue;
1101                         if (i + 1 > listsize) {
1102                                 listsize += listinc;
1103                                 l = (char **)
1104                                         realloc(l, listsize * sizeof(char *));
1105                                 if (l == NULL) {
1106                                         retval = ENOMEM;
1107                                         goto out;
1108                                 }
1109                         }
1110                         if ((l[i++] = strdup(ple->ccname)) == NULL) {
1111                                 retval = ENOMEM;
1112                                 goto out;
1113                         }
1114                 }
1115         }
1116         if (i > 0) {
1117                 l[i] = NULL;
1118                 *list = l;
1119                 retval = 0;
1120                 goto out;
1121         }
1122   out:
1123         return retval;
1124 }
1125
1126 /*
1127  * Frees the list of names returned in get_krb5_machine_cred_list()
1128  */
1129 void
1130 gssd_free_krb5_machine_cred_list(char **list)
1131 {
1132         char **n;
1133
1134         if (list == NULL)
1135                 return;
1136         for (n = list; n && *n; n++) {
1137                 free(*n);
1138         }
1139         free(list);
1140 }
1141
1142 /*
1143  * Called upon exit.  Destroys machine credentials.
1144  */
1145 void
1146 gssd_destroy_krb5_machine_creds(void)
1147 {
1148         krb5_context context;
1149         krb5_error_code code = 0;
1150         krb5_ccache ccache;
1151         struct gssd_k5_kt_princ *ple;
1152         char *k5err = NULL;
1153
1154         code = krb5_init_context(&context);
1155         if (code) {
1156                 k5err = gssd_k5_err_msg(NULL, code);
1157                 printerr(0, "ERROR: %s while initializing krb5\n", k5err);
1158                 goto out;
1159         }
1160
1161         for (ple = gssd_k5_kt_princ_list; ple; ple = ple->next) {
1162                 if (!ple->ccname)
1163                         continue;
1164                 if ((code = krb5_cc_resolve(context, ple->ccname, &ccache))) {
1165                         k5err = gssd_k5_err_msg(context, code);
1166                         printerr(0, "WARNING: %s while resolving credential "
1167                                     "cache '%s' for destruction\n", k5err,
1168                                     ple->ccname);
1169                         continue;
1170                 }
1171
1172                 if ((code = krb5_cc_destroy(context, ccache))) {
1173                         k5err = gssd_k5_err_msg(context, code);
1174                         printerr(0, "WARNING: %s while destroying credential "
1175                                     "cache '%s'\n", k5err, ple->ccname);
1176                 }
1177         }
1178   out:
1179         free(k5err);
1180         krb5_free_context(context);
1181 }
1182
1183 /*
1184  * Obtain (or refresh if necessary) Kerberos machine credentials
1185  */
1186 int
1187 gssd_refresh_krb5_machine_credential(char *hostname,
1188                                      struct gssd_k5_kt_princ *ple)
1189 {
1190         krb5_error_code code = 0;
1191         krb5_context context;
1192         krb5_keytab kt = NULL;;
1193         int retval = 0;
1194         char *k5err = NULL;
1195
1196         if (hostname == NULL && ple == NULL)
1197                 return EINVAL;
1198
1199         code = krb5_init_context(&context);
1200         if (code) {
1201                 k5err = gssd_k5_err_msg(NULL, code);
1202                 printerr(0, "ERROR: %s: %s while initializing krb5 context\n",
1203                          __func__, k5err);
1204                 retval = code;
1205                 goto out;
1206         }
1207
1208         if ((code = krb5_kt_resolve(context, keytabfile, &kt))) {
1209                 k5err = gssd_k5_err_msg(context, code);
1210                 printerr(0, "ERROR: %s: %s while resolving keytab '%s'\n",
1211                          __func__, k5err, keytabfile);
1212                 goto out;
1213         }
1214
1215         if (ple == NULL) {
1216                 krb5_keytab_entry kte;
1217
1218                 code = find_keytab_entry(context, kt, hostname, &kte);
1219                 if (code) {
1220                         printerr(0, "ERROR: %s: no usable keytab entry found "
1221                                  "in keytab %s for connection with host %s\n",
1222                                  __FUNCTION__, keytabfile, hostname);
1223                         retval = code;
1224                         goto out;
1225                 }
1226
1227                 ple = get_ple_by_princ(context, kte.principal);
1228                 k5_free_kt_entry(context, &kte);
1229                 if (ple == NULL) {
1230                         char *pname;
1231                         if ((krb5_unparse_name(context, kte.principal, &pname))) {
1232                                 pname = NULL;
1233                         }
1234                         printerr(0, "ERROR: %s: Could not locate or create "
1235                                  "ple struct for principal %s for connection "
1236                                  "with host %s\n",
1237                                  __FUNCTION__, pname ? pname : "<unparsable>",
1238                                  hostname);
1239                         if (pname) k5_free_unparsed_name(context, pname);
1240                         goto out;
1241                 }
1242         }
1243         retval = gssd_get_single_krb5_cred(context, kt, ple);
1244 out:
1245         if (kt)
1246                 krb5_kt_close(context, kt);
1247         krb5_free_context(context);
1248         free(k5err);
1249         return retval;
1250 }
1251
1252 /*
1253  * A common routine for getting the Kerberos error message
1254  */
1255 char *
1256 gssd_k5_err_msg(krb5_context context, krb5_error_code code)
1257 {
1258         const char *origmsg;
1259         char *msg = NULL;
1260
1261 #if HAVE_KRB5_GET_ERROR_MESSAGE
1262         if (context != NULL) {
1263                 origmsg = krb5_get_error_message(context, code);
1264                 msg = strdup(origmsg);
1265                 krb5_free_error_message(context, origmsg);
1266         }
1267 #endif
1268         if (msg != NULL)
1269                 return msg;
1270 #if HAVE_KRB5
1271         return strdup(error_message(code));
1272 #else
1273         if (context != NULL)
1274                 return strdup(krb5_get_err_text(context, code));
1275         else
1276                 return strdup(error_message(code));
1277 #endif
1278 }
1279
1280 /*
1281  * Return default Kerberos realm
1282  */
1283 void
1284 gssd_k5_get_default_realm(char **def_realm)
1285 {
1286         krb5_context context;
1287
1288         if (krb5_init_context(&context))
1289                 return;
1290
1291         krb5_get_default_realm(context, def_realm);
1292
1293         krb5_free_context(context);
1294 }