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