nfs-utils: Include legacy or TI-RPC headers, not both
[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
379         memset(&my_creds, 0, sizeof(my_creds));
380
381         if (ple->ccname && ple->endtime > now) {
382                 printerr(2, "INFO: Credentials in CC '%s' are good until %d\n",
383                          ple->ccname, ple->endtime);
384                 code = 0;
385                 goto out;
386         }
387
388         if ((code = krb5_kt_get_name(context, kt, kt_name, BUFSIZ))) {
389                 printerr(0, "ERROR: Unable to get keytab name in "
390                             "gssd_get_single_krb5_cred\n");
391                 goto out;
392         }
393
394         if ((krb5_unparse_name(context, ple->princ, &pname)))
395                 pname = NULL;
396
397 #if HAVE_KRB5_GET_INIT_CREDS_OPT_SET_ADDRESSLESS
398         code = krb5_get_init_creds_opt_alloc(context, &init_opts);
399         if (code) {
400                 printerr(0, "ERROR: %s allocating gic options\n",
401                          gssd_k5_err_msg(context, code));
402                 goto out;
403         }
404         if (krb5_get_init_creds_opt_set_addressless(context, init_opts, 1))
405                 printerr(1, "WARNING: Unable to set option for addressless "
406                          "tickets.  May have problems behind a NAT.\n");
407 #ifdef TEST_SHORT_LIFETIME
408         /* set a short lifetime (for debugging only!) */
409         printerr(0, "WARNING: Using (debug) short machine cred lifetime!\n");
410         krb5_get_init_creds_opt_set_tkt_life(init_opts, 5*60);
411 #endif
412         opts = init_opts;
413
414 #else   /* HAVE_KRB5_GET_INIT_CREDS_OPT_SET_ADDRESSLESS */
415
416         krb5_get_init_creds_opt_init(&options);
417         krb5_get_init_creds_opt_set_address_list(&options, NULL);
418 #ifdef TEST_SHORT_LIFETIME
419         /* set a short lifetime (for debugging only!) */
420         printerr(0, "WARNING: Using (debug) short machine cred lifetime!\n");
421         krb5_get_init_creds_opt_set_tkt_life(&options, 5*60);
422 #endif
423         opts = &options;
424 #endif
425
426         if ((code = krb5_get_init_creds_keytab(context, &my_creds, ple->princ,
427                                                kt, 0, NULL, opts))) {
428                 printerr(1, "WARNING: %s while getting initial ticket for "
429                          "principal '%s' using keytab '%s'\n",
430                          gssd_k5_err_msg(context, code),
431                          pname ? pname : "<unparsable>", kt_name);
432                 goto out;
433         }
434
435         /*
436          * Initialize cache file which we're going to be using
437          */
438
439         if (use_memcache)
440             cache_type = "MEMORY";
441         else
442             cache_type = "FILE";
443         snprintf(cc_name, sizeof(cc_name), "%s:%s/%s%s_%s",
444                 cache_type,
445                 ccachesearch[0], GSSD_DEFAULT_CRED_PREFIX,
446                 GSSD_DEFAULT_MACHINE_CRED_SUFFIX, ple->realm);
447         ple->endtime = my_creds.times.endtime;
448         if (ple->ccname != NULL)
449                 free(ple->ccname);
450         ple->ccname = strdup(cc_name);
451         if (ple->ccname == NULL) {
452                 printerr(0, "ERROR: no storage to duplicate credentials "
453                             "cache name '%s'\n", cc_name);
454                 code = ENOMEM;
455                 goto out;
456         }
457         if ((code = krb5_cc_resolve(context, cc_name, &ccache))) {
458                 printerr(0, "ERROR: %s while opening credential cache '%s'\n",
459                          gssd_k5_err_msg(context, code), cc_name);
460                 goto out;
461         }
462         if ((code = krb5_cc_initialize(context, ccache, ple->princ))) {
463                 printerr(0, "ERROR: %s while initializing credential "
464                          "cache '%s'\n", gssd_k5_err_msg(context, code),
465                          cc_name);
466                 goto out;
467         }
468         if ((code = krb5_cc_store_cred(context, ccache, &my_creds))) {
469                 printerr(0, "ERROR: %s while storing credentials in '%s'\n",
470                          gssd_k5_err_msg(context, code), cc_name);
471                 goto out;
472         }
473
474         code = 0;
475         printerr(2, "Successfully obtained machine credentials for "
476                  "principal '%s' stored in ccache '%s'\n", pname, cc_name);
477   out:
478 #if HAVE_KRB5_GET_INIT_CREDS_OPT_SET_ADDRESSLESS
479         if (init_opts)
480                 krb5_get_init_creds_opt_free(context, init_opts);
481 #endif
482         if (pname)
483                 k5_free_unparsed_name(context, pname);
484         if (ccache)
485                 krb5_cc_close(context, ccache);
486         krb5_free_cred_contents(context, &my_creds);
487         return (code);
488 }
489
490 /*
491  * Depending on the version of Kerberos, we either need to use
492  * a private function, or simply set the environment variable.
493  */
494 static void
495 gssd_set_krb5_ccache_name(char *ccname)
496 {
497 #ifdef USE_GSS_KRB5_CCACHE_NAME
498         u_int   maj_stat, min_stat;
499
500         printerr(2, "using gss_krb5_ccache_name to select krb5 ccache %s\n",
501                  ccname);
502         maj_stat = gss_krb5_ccache_name(&min_stat, ccname, NULL);
503         if (maj_stat != GSS_S_COMPLETE) {
504                 printerr(0, "WARNING: gss_krb5_ccache_name with "
505                         "name '%s' failed (%s)\n",
506                         ccname, error_message(min_stat));
507         }
508 #else
509         /*
510          * Set the KRB5CCNAME environment variable to tell the krb5 code
511          * which credentials cache to use.  (Instead of using the private
512          * function above for which there is no generic gssapi
513          * equivalent.)
514          */
515         printerr(2, "using environment variable to select krb5 ccache %s\n",
516                  ccname);
517         setenv("KRB5CCNAME", ccname, 1);
518 #endif
519 }
520
521 /*
522  * Given a principal, find a matching ple structure
523  */
524 static struct gssd_k5_kt_princ *
525 find_ple_by_princ(krb5_context context, krb5_principal princ)
526 {
527         struct gssd_k5_kt_princ *ple;
528
529         for (ple = gssd_k5_kt_princ_list; ple != NULL; ple = ple->next) {
530                 if (krb5_principal_compare(context, ple->princ, princ))
531                         return ple;
532         }
533         /* no match found */
534         return NULL;
535 }
536
537 /*
538  * Create, initialize, and add a new ple structure to the global list
539  */
540 static struct gssd_k5_kt_princ *
541 new_ple(krb5_context context, krb5_principal princ)
542 {
543         struct gssd_k5_kt_princ *ple = NULL, *p;
544         krb5_error_code code;
545         char *default_realm;
546         int is_default_realm = 0;
547
548         ple = malloc(sizeof(struct gssd_k5_kt_princ));
549         if (ple == NULL)
550                 goto outerr;
551         memset(ple, 0, sizeof(*ple));
552
553 #ifdef HAVE_KRB5
554         ple->realm = strndup(princ->realm.data,
555                              princ->realm.length);
556 #else
557         ple->realm = strdup(princ->realm);
558 #endif
559         if (ple->realm == NULL)
560                 goto outerr;
561         code = krb5_copy_principal(context, princ, &ple->princ);
562         if (code)
563                 goto outerr;
564
565         /*
566          * Add new entry onto the list (if this is the default
567          * realm, always add to the front of the list)
568          */
569
570         code = krb5_get_default_realm(context, &default_realm);
571         if (code == 0) {
572                 if (strcmp(ple->realm, default_realm) == 0)
573                         is_default_realm = 1;
574                 k5_free_default_realm(context, default_realm);
575         }
576
577         if (is_default_realm) {
578                 ple->next = gssd_k5_kt_princ_list;
579                 gssd_k5_kt_princ_list = ple;
580         } else {
581                 p = gssd_k5_kt_princ_list;
582                 while (p != NULL && p->next != NULL)
583                         p = p->next;
584                 if (p == NULL)
585                         gssd_k5_kt_princ_list = ple;
586                 else
587                         p->next = ple;
588         }
589
590         return ple;
591 outerr:
592         if (ple) {
593                 if (ple->realm)
594                         free(ple->realm);
595                 free(ple);
596         }
597         return NULL;
598 }
599
600 /*
601  * Given a principal, find an existing ple structure, or create one
602  */
603 static struct gssd_k5_kt_princ *
604 get_ple_by_princ(krb5_context context, krb5_principal princ)
605 {
606         struct gssd_k5_kt_princ *ple;
607
608         /* Need to serialize list if we ever become multi-threaded! */
609
610         ple = find_ple_by_princ(context, princ);
611         if (ple == NULL) {
612                 ple = new_ple(context, princ);
613         }
614
615         return ple;
616 }
617
618 /*
619  * Given a (possibly unqualified) hostname,
620  * return the fully qualified (lower-case!) hostname
621  */
622 static int
623 get_full_hostname(const char *inhost, char *outhost, int outhostlen)
624 {
625         struct addrinfo *addrs = NULL;
626         struct addrinfo hints;
627         int retval;
628         char *c;
629
630         memset(&hints, 0, sizeof(hints));
631         hints.ai_socktype = SOCK_STREAM;
632         hints.ai_family = PF_UNSPEC;
633         hints.ai_flags = AI_CANONNAME;
634
635         /* Get full target hostname */
636         retval = getaddrinfo(inhost, NULL, &hints, &addrs);
637         if (retval) {
638                 printerr(1, "%s while getting full hostname for '%s'\n",
639                          gai_strerror(retval), inhost);
640                 goto out;
641         }
642         strncpy(outhost, addrs->ai_canonname, outhostlen);
643         freeaddrinfo(addrs);
644         for (c = outhost; *c != '\0'; c++)
645             *c = tolower(*c);
646
647         printerr(3, "Full hostname for '%s' is '%s'\n", inhost, outhost);
648         retval = 0;
649 out:
650         return retval;
651 }
652
653 /* 
654  * If principal matches the given realm and service name,
655  * and has *any* instance (hostname), return 1.
656  * Otherwise return 0, indicating no match.
657  */
658 static int
659 realm_and_service_match(krb5_context context, krb5_principal p,
660                         const char *realm, const char *service)
661 {
662 #ifdef HAVE_KRB5
663         /* Must have two components */
664         if (p->length != 2)
665                 return 0;
666         if ((strlen(realm) == p->realm.length)
667             && (strncmp(realm, p->realm.data, p->realm.length) == 0)
668             && (strlen(service) == p->data[0].length)
669             && (strncmp(service, p->data[0].data, p->data[0].length) == 0))
670                 return 1;
671 #else
672         const char *name, *inst;
673
674         if (p->name.name_string.len != 2)
675                 return 0;
676         name = krb5_principal_get_comp_string(context, p, 0);
677         inst = krb5_principal_get_comp_string(context, p, 1);
678         if (name == NULL || inst == NULL)
679                 return 0;
680         if ((strcmp(realm, p->realm) == 0)
681             && (strcmp(service, name) == 0))
682                 return 1;
683 #endif
684         return 0;
685 }
686
687 /*
688  * Search the given keytab file looking for an entry with the given
689  * service name and realm, ignoring hostname (instance).
690  *
691  * Returns:
692  *      0 => No error
693  *      non-zero => An error occurred
694  *
695  * If a keytab entry is found, "found" is set to one, and the keytab
696  * entry is returned in "kte".  Otherwise, "found" is zero, and the
697  * value of "kte" is unpredictable.
698  */
699 static int
700 gssd_search_krb5_keytab(krb5_context context, krb5_keytab kt,
701                         const char *realm, const char *service,
702                         int *found, krb5_keytab_entry *kte)
703 {
704         krb5_kt_cursor cursor;
705         krb5_error_code code;
706         struct gssd_k5_kt_princ *ple;
707         int retval = -1;
708         char kt_name[BUFSIZ];
709         char *pname;
710
711         if (found == NULL) {
712                 retval = EINVAL;
713                 goto out;
714         }
715         *found = 0;
716
717         /*
718          * Look through each entry in the keytab file and determine
719          * if we might want to use it as machine credentials.  If so,
720          * save info in the global principal list (gssd_k5_kt_princ_list).
721          */
722         if ((code = krb5_kt_get_name(context, kt, kt_name, BUFSIZ))) {
723                 printerr(0, "ERROR: %s attempting to get keytab name\n",
724                          gssd_k5_err_msg(context, code));
725                 retval = code;
726                 goto out;
727         }
728         if ((code = krb5_kt_start_seq_get(context, kt, &cursor))) {
729                 printerr(0, "ERROR: %s while beginning keytab scan "
730                             "for keytab '%s'\n",
731                         gssd_k5_err_msg(context, code), kt_name);
732                 retval = code;
733                 goto out;
734         }
735
736         while ((code = krb5_kt_next_entry(context, kt, kte, &cursor)) == 0) {
737                 if ((code = krb5_unparse_name(context, kte->principal,
738                                               &pname))) {
739                         printerr(0, "WARNING: Skipping keytab entry because "
740                                  "we failed to unparse principal name: %s\n",
741                                  gssd_k5_err_msg(context, code));
742                         k5_free_kt_entry(context, kte);
743                         continue;
744                 }
745                 printerr(4, "Processing keytab entry for principal '%s'\n",
746                          pname);
747                 /* Use the first matching keytab entry found */
748                 if ((realm_and_service_match(context, kte->principal, realm,
749                                              service))) {
750                         printerr(4, "We WILL use this entry (%s)\n", pname);
751                         ple = get_ple_by_princ(context, kte->principal);
752                         /*
753                          * Return, don't free, keytab entry if
754                          * we were successful!
755                          */
756                         if (ple == NULL) {
757                                 retval = ENOMEM;
758                                 k5_free_kt_entry(context, kte);
759                         } else {
760                                 retval = 0;
761                                 *found = 1;
762                         }
763                         k5_free_unparsed_name(context, pname);
764                         break;
765                 }
766                 else {
767                         printerr(4, "We will NOT use this entry (%s)\n",
768                                 pname);
769                 }
770                 k5_free_unparsed_name(context, pname);
771                 k5_free_kt_entry(context, kte);
772         }
773
774         if ((code = krb5_kt_end_seq_get(context, kt, &cursor))) {
775                 printerr(0, "WARNING: %s while ending keytab scan for "
776                             "keytab '%s'\n",
777                          gssd_k5_err_msg(context, code), kt_name);
778         }
779
780         retval = 0;
781   out:
782         return retval;
783 }
784
785 /*
786  * Find a keytab entry to use for a given target hostname.
787  * Tries to find the most appropriate keytab to use given the
788  * name of the host we are trying to connect with.
789  */
790 static int
791 find_keytab_entry(krb5_context context, krb5_keytab kt, const char *hostname,
792                   krb5_keytab_entry *kte)
793 {
794         krb5_error_code code;
795         const char *svcnames[] = { "root", "nfs", "host", NULL };
796         char **realmnames = NULL;
797         char myhostname[NI_MAXHOST], targethostname[NI_MAXHOST];
798         int i, j, retval;
799         char *default_realm = NULL;
800         char *realm;
801         int tried_all = 0, tried_default = 0;
802         krb5_principal princ;
803
804
805         /* Get full target hostname */
806         retval = get_full_hostname(hostname, targethostname,
807                                    sizeof(targethostname));
808         if (retval)
809                 goto out;
810
811         /* Get full local hostname */
812         retval = gethostname(myhostname, sizeof(myhostname));
813         if (retval) {
814                 printerr(1, "%s while getting local hostname\n",
815                          gssd_k5_err_msg(context, retval));
816                 goto out;
817         }
818         retval = get_full_hostname(myhostname, myhostname, sizeof(myhostname));
819         if (retval)
820                 goto out;
821
822         code = krb5_get_default_realm(context, &default_realm);
823         if (code) {
824                 retval = code;
825                 printerr(1, "%s while getting default realm name\n",
826                          gssd_k5_err_msg(context, code));
827                 goto out;
828         }
829
830         /*
831          * Get the realm name(s) for the target hostname.
832          * In reality, this function currently only returns a
833          * single realm, but we code with the assumption that
834          * someday it may actually return a list.
835          */
836         code = krb5_get_host_realm(context, targethostname, &realmnames);
837         if (code) {
838                 printerr(0, "ERROR: %s while getting realm(s) for host '%s'\n",
839                          gssd_k5_err_msg(context, code), targethostname);
840                 retval = code;
841                 goto out;
842         }
843
844         /*
845          * Try the "appropriate" realm first, and if nothing found for that
846          * realm, try the default realm (if it hasn't already been tried).
847          */
848         i = 0;
849         realm = realmnames[i];
850         while (1) {
851                 if (realm == NULL) {
852                         tried_all = 1;
853                         if (!tried_default)
854                                 realm = default_realm;
855                 }
856                 if (tried_all && tried_default)
857                         break;
858                 if (strcmp(realm, default_realm) == 0)
859                         tried_default = 1;
860                 for (j = 0; svcnames[j] != NULL; j++) {
861                         code = krb5_build_principal_ext(context, &princ,
862                                                         strlen(realm),
863                                                         realm,
864                                                         strlen(svcnames[j]),
865                                                         svcnames[j],
866                                                         strlen(myhostname),
867                                                         myhostname,
868                                                         NULL);
869                         if (code) {
870                                 printerr(1, "%s while building principal for "
871                                          "'%s/%s@%s'\n",
872                                          gssd_k5_err_msg(context, code),
873                                          svcnames[j], myhostname, realm);
874                                 continue;
875                         }
876                         code = krb5_kt_get_entry(context, kt, princ, 0, 0, kte);
877                         krb5_free_principal(context, princ);
878                         if (code) {
879                                 printerr(3, "%s while getting keytab entry for "
880                                          "'%s/%s@%s'\n",
881                                          gssd_k5_err_msg(context, code),
882                                          svcnames[j], myhostname, realm);
883                         } else {
884                                 printerr(3, "Success getting keytab entry for "
885                                          "'%s/%s@%s'\n",
886                                          svcnames[j], myhostname, realm);
887                                 retval = 0;
888                                 goto out;
889                         }
890                         retval = code;
891                 }
892                 /*
893                  * Nothing found with our hostname instance, now look for
894                  * names with any instance (they must have an instance)
895                  */
896                 for (j = 0; svcnames[j] != NULL; j++) {
897                         int found = 0;
898                         code = gssd_search_krb5_keytab(context, kt, realm,
899                                                        svcnames[j], &found, kte);
900                         if (!code && found) {
901                                 printerr(3, "Success getting keytab entry for "
902                                          "%s/*@%s\n", svcnames[j], realm);
903                                 retval = 0;
904                                 goto out;
905                         }
906                 }
907                 if (!tried_all) {
908                         i++;
909                         realm = realmnames[i];
910                 }
911         }
912 out:
913         if (default_realm)
914                 k5_free_default_realm(context, default_realm);
915         if (realmnames)
916                 krb5_free_host_realm(context, realmnames);
917         return retval;
918 }
919
920
921 static inline int data_is_equal(krb5_data d1, krb5_data d2)
922 {
923         return (d1.length == d2.length
924                 && memcmp(d1.data, d2.data, d1.length) == 0);
925 }
926
927 static int
928 check_for_tgt(krb5_context context, krb5_ccache ccache,
929               krb5_principal principal)
930 {
931         krb5_error_code ret;
932         krb5_creds creds;
933         krb5_cc_cursor cur;
934         int found = 0;
935
936         ret = krb5_cc_start_seq_get(context, ccache, &cur);
937         if (ret) 
938                 return 0;
939
940         while (!found &&
941                 (ret = krb5_cc_next_cred(context, ccache, &cur, &creds)) == 0) {
942                 if (creds.server->length == 2 &&
943                                 data_is_equal(creds.server->realm,
944                                               principal->realm) &&
945                                 creds.server->data[0].length == 6 &&
946                                 memcmp(creds.server->data[0].data,
947                                                 "krbtgt", 6) == 0 &&
948                                 data_is_equal(creds.server->data[1],
949                                               principal->realm) &&
950                                 creds.times.endtime > time(NULL))
951                         found = 1;
952                 krb5_free_cred_contents(context, &creds);
953         }
954         krb5_cc_end_seq_get(context, ccache, &cur);
955
956         return found;
957 }
958
959 static int
960 query_krb5_ccache(const char* cred_cache, char **ret_princname,
961                   char **ret_realm)
962 {
963         krb5_error_code ret;
964         krb5_context context;
965         krb5_ccache ccache;
966         krb5_principal principal;
967         int found = 0;
968         char *str = NULL;
969         char *princstring;
970
971         ret = krb5_init_context(&context);
972         if (ret) 
973                 return 0;
974
975         if(!cred_cache || krb5_cc_resolve(context, cred_cache, &ccache))
976                 goto err_cache;
977
978         if (krb5_cc_set_flags(context, ccache, 0))
979                 goto err_princ;
980
981         ret = krb5_cc_get_principal(context, ccache, &principal);
982         if (ret) 
983                 goto err_princ;
984
985         found = check_for_tgt(context, ccache, principal);
986         if (found) {
987                 ret = krb5_unparse_name(context, principal, &princstring);
988                 if (ret == 0) {
989                     if ((str = strchr(princstring, '@')) != NULL) {
990                             *str = '\0';
991                             *ret_princname = strdup(princstring);
992                             *ret_realm = strdup(str+1);
993                     }
994                     k5_free_unparsed_name(context, princstring);
995                 } else {
996                         found = 0;
997                 }
998         }
999         krb5_free_principal(context, principal);
1000 err_princ:
1001         krb5_cc_set_flags(context, ccache,  KRB5_TC_OPENCLOSE);
1002         krb5_cc_close(context, ccache);
1003 err_cache:
1004         krb5_free_context(context);
1005         return found;
1006 }
1007
1008 /*==========================*/
1009 /*===  External routines ===*/
1010 /*==========================*/
1011
1012 /*
1013  * Attempt to find the best match for a credentials cache file
1014  * given only a UID.  We really need more information, but we
1015  * do the best we can.
1016  *
1017  * Returns:
1018  *      0 => a ccache was found
1019  *      1 => no ccache was found
1020  */
1021 int
1022 gssd_setup_krb5_user_gss_ccache(uid_t uid, char *servername, char *dirname)
1023 {
1024         char                    buf[MAX_NETOBJ_SZ];
1025         struct dirent           *d;
1026
1027         printerr(2, "getting credentials for client with uid %u for "
1028                     "server %s\n", uid, servername);
1029         memset(buf, 0, sizeof(buf));
1030         if (gssd_find_existing_krb5_ccache(uid, dirname, &d)) {
1031                 snprintf(buf, sizeof(buf), "FILE:%s/%s", dirname, d->d_name);
1032                 free(d);
1033         }
1034         else
1035                 return 1;
1036         printerr(2, "using %s as credentials cache for client with "
1037                     "uid %u for server %s\n", buf, uid, servername);
1038         gssd_set_krb5_ccache_name(buf);
1039         return 0;
1040 }
1041
1042 /*
1043  * Let the gss code know where to find the machine credentials ccache.
1044  *
1045  * Returns:
1046  *      void
1047  */
1048 void
1049 gssd_setup_krb5_machine_gss_ccache(char *ccname)
1050 {
1051         printerr(2, "using %s as credentials cache for machine creds\n",
1052                  ccname);
1053         gssd_set_krb5_ccache_name(ccname);
1054 }
1055
1056 /*
1057  * Return an array of pointers to names of credential cache files
1058  * which can be used to try to create gss contexts with a server.
1059  *
1060  * Returns:
1061  *      0 => list is attached
1062  *      nonzero => error
1063  */
1064 int
1065 gssd_get_krb5_machine_cred_list(char ***list)
1066 {
1067         char **l;
1068         int listinc = 10;
1069         int listsize = listinc;
1070         int i = 0;
1071         int retval;
1072         struct gssd_k5_kt_princ *ple;
1073
1074         /* Assume failure */
1075         retval = -1;
1076         *list = (char **) NULL;
1077
1078         if ((l = (char **) malloc(listsize * sizeof(char *))) == NULL) {
1079                 retval = ENOMEM;
1080                 goto out;
1081         }
1082
1083         /* Need to serialize list if we ever become multi-threaded! */
1084
1085         for (ple = gssd_k5_kt_princ_list; ple; ple = ple->next) {
1086                 if (ple->ccname) {
1087                         /* Make sure cred is up-to-date before returning it */
1088                         retval = gssd_refresh_krb5_machine_credential(NULL, ple);
1089                         if (retval)
1090                                 continue;
1091                         if (i + 1 > listsize) {
1092                                 listsize += listinc;
1093                                 l = (char **)
1094                                         realloc(l, listsize * sizeof(char *));
1095                                 if (l == NULL) {
1096                                         retval = ENOMEM;
1097                                         goto out;
1098                                 }
1099                         }
1100                         if ((l[i++] = strdup(ple->ccname)) == NULL) {
1101                                 retval = ENOMEM;
1102                                 goto out;
1103                         }
1104                 }
1105         }
1106         if (i > 0) {
1107                 l[i] = NULL;
1108                 *list = l;
1109                 retval = 0;
1110                 goto out;
1111         }
1112   out:
1113         return retval;
1114 }
1115
1116 /*
1117  * Frees the list of names returned in get_krb5_machine_cred_list()
1118  */
1119 void
1120 gssd_free_krb5_machine_cred_list(char **list)
1121 {
1122         char **n;
1123
1124         if (list == NULL)
1125                 return;
1126         for (n = list; n && *n; n++) {
1127                 free(*n);
1128         }
1129         free(list);
1130 }
1131
1132 /*
1133  * Called upon exit.  Destroys machine credentials.
1134  */
1135 void
1136 gssd_destroy_krb5_machine_creds(void)
1137 {
1138         krb5_context context;
1139         krb5_error_code code = 0;
1140         krb5_ccache ccache;
1141         struct gssd_k5_kt_princ *ple;
1142
1143         code = krb5_init_context(&context);
1144         if (code) {
1145                 printerr(0, "ERROR: %s while initializing krb5\n",
1146                          gssd_k5_err_msg(NULL, code));
1147                 goto out;
1148         }
1149
1150         for (ple = gssd_k5_kt_princ_list; ple; ple = ple->next) {
1151                 if (!ple->ccname)
1152                         continue;
1153                 if ((code = krb5_cc_resolve(context, ple->ccname, &ccache))) {
1154                         printerr(0, "WARNING: %s while resolving credential "
1155                                     "cache '%s' for destruction\n",
1156                                  gssd_k5_err_msg(context, code), ple->ccname);
1157                         continue;
1158                 }
1159
1160                 if ((code = krb5_cc_destroy(context, ccache))) {
1161                         printerr(0, "WARNING: %s while destroying credential "
1162                                     "cache '%s'\n",
1163                                  gssd_k5_err_msg(context, code), ple->ccname);
1164                 }
1165         }
1166   out:
1167         krb5_free_context(context);
1168 }
1169
1170 /*
1171  * Obtain (or refresh if necessary) Kerberos machine credentials
1172  */
1173 int
1174 gssd_refresh_krb5_machine_credential(char *hostname,
1175                                      struct gssd_k5_kt_princ *ple)
1176 {
1177         krb5_error_code code = 0;
1178         krb5_context context;
1179         krb5_keytab kt = NULL;;
1180         int retval = 0;
1181
1182         if (hostname == NULL && ple == NULL)
1183                 return EINVAL;
1184
1185         code = krb5_init_context(&context);
1186         if (code) {
1187                 printerr(0, "ERROR: %s: %s while initializing krb5 context\n",
1188                          __FUNCTION__, gssd_k5_err_msg(NULL, code));
1189                 retval = code;
1190                 goto out;
1191         }
1192
1193         if ((code = krb5_kt_resolve(context, keytabfile, &kt))) {
1194                 printerr(0, "ERROR: %s: %s while resolving keytab '%s'\n",
1195                          __FUNCTION__, gssd_k5_err_msg(context, code),
1196                          keytabfile);
1197                 goto out;
1198         }
1199
1200         if (ple == NULL) {
1201                 krb5_keytab_entry kte;
1202
1203                 code = find_keytab_entry(context, kt, hostname, &kte);
1204                 if (code) {
1205                         printerr(0, "ERROR: %s: no usable keytab entry found "
1206                                  "in keytab %s for connection with host %s\n",
1207                                  __FUNCTION__, keytabfile, hostname);
1208                         retval = code;
1209                         goto out;
1210                 }
1211
1212                 ple = get_ple_by_princ(context, kte.principal);
1213                 k5_free_kt_entry(context, &kte);
1214                 if (ple == NULL) {
1215                         char *pname;
1216                         if ((krb5_unparse_name(context, kte.principal, &pname))) {
1217                                 pname = NULL;
1218                         }
1219                         printerr(0, "ERROR: %s: Could not locate or create "
1220                                  "ple struct for principal %s for connection "
1221                                  "with host %s\n",
1222                                  __FUNCTION__, pname ? pname : "<unparsable>",
1223                                  hostname);
1224                         if (pname) k5_free_unparsed_name(context, pname);
1225                         goto out;
1226                 }
1227         }
1228         retval = gssd_get_single_krb5_cred(context, kt, ple);
1229 out:
1230         if (kt)
1231                 krb5_kt_close(context, kt);
1232         krb5_free_context(context);
1233         return retval;
1234 }
1235
1236 /*
1237  * A common routine for getting the Kerberos error message
1238  */
1239 const char *
1240 gssd_k5_err_msg(krb5_context context, krb5_error_code code)
1241 {
1242         const char *msg = NULL;
1243 #if HAVE_KRB5_GET_ERROR_MESSAGE
1244         if (context != NULL)
1245                 msg = krb5_get_error_message(context, code);
1246 #endif
1247         if (msg != NULL)
1248                 return msg;
1249 #if HAVE_KRB5
1250         return error_message(code);
1251 #else
1252         if (context != NULL)
1253                 return krb5_get_err_text(context, code);
1254         else
1255                 return error_message(code);
1256 #endif
1257 }
1258
1259 /*
1260  * Return default Kerberos realm
1261  */
1262 void
1263 gssd_k5_get_default_realm(char **def_realm)
1264 {
1265         krb5_context context;
1266
1267         if (krb5_init_context(&context))
1268                 return;
1269
1270         krb5_get_default_realm(context, def_realm);
1271
1272         krb5_free_context(context);
1273 }