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