]> git.decadent.org.uk Git - nfs-utils.git/blob - utils/gssd/krb5_util.c
gssd: Use /run/user/${UID} instead of /run/user/${USER}
[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 hostname.
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 static int
782 find_keytab_entry(krb5_context context, krb5_keytab kt, const char *hostname,
783                   krb5_keytab_entry *kte, const char **svcnames)
784 {
785         krb5_error_code code;
786         char **realmnames = NULL;
787         char myhostname[NI_MAXHOST], targethostname[NI_MAXHOST];
788         char myhostad[NI_MAXHOST+1];
789         int i, j, retval;
790         char *default_realm = NULL;
791         char *realm;
792         char *k5err = NULL;
793         int tried_all = 0, tried_default = 0;
794         krb5_principal princ;
795
796
797         /* Get full target hostname */
798         retval = get_full_hostname(hostname, targethostname,
799                                    sizeof(targethostname));
800         if (retval)
801                 goto out;
802
803         /* Get full local hostname */
804         retval = gethostname(myhostname, sizeof(myhostname));
805         if (retval) {
806                 k5err = gssd_k5_err_msg(context, retval);
807                 printerr(1, "%s while getting local hostname\n", k5err);
808                 goto out;
809         }
810
811         /* Compute the active directory machine name HOST$ */
812         strcpy(myhostad, myhostname);
813         for (i = 0; myhostad[i] != 0; ++i)
814                 myhostad[i] = toupper(myhostad[i]);
815         myhostad[i] = '$';
816         myhostad[i+1] = 0;
817
818         retval = get_full_hostname(myhostname, myhostname, sizeof(myhostname));
819         if (retval)
820                 goto out;
821
822         code = krb5_get_default_realm(context, &default_realm);
823         if (code) {
824                 retval = code;
825                 k5err = gssd_k5_err_msg(context, code);
826                 printerr(1, "%s while getting default realm name\n", k5err);
827                 goto out;
828         }
829
830         /*
831          * Get the realm name(s) for the target hostname.
832          * In reality, this function currently only returns a
833          * single realm, but we code with the assumption that
834          * someday it may actually return a list.
835          */
836         code = krb5_get_host_realm(context, targethostname, &realmnames);
837         if (code) {
838                 k5err = gssd_k5_err_msg(context, code);
839                 printerr(0, "ERROR: %s while getting realm(s) for host '%s'\n",
840                          k5err, targethostname);
841                 retval = code;
842                 goto out;
843         }
844
845         /*
846          * Try the "appropriate" realm first, and if nothing found for that
847          * realm, try the default realm (if it hasn't already been tried).
848          */
849         i = 0;
850         realm = realmnames[i];
851         while (1) {
852                 if (realm == NULL) {
853                         tried_all = 1;
854                         if (!tried_default)
855                                 realm = default_realm;
856                 }
857                 if (tried_all && tried_default)
858                         break;
859                 if (strcmp(realm, default_realm) == 0)
860                         tried_default = 1;
861                 for (j = 0; svcnames[j] != NULL; j++) {
862                         char spn[300];
863
864                         /*
865                          * The special svcname "$" means 'try the active
866                          * directory machine account'
867                          */
868                         if (strcmp(svcnames[j],"$") == 0) {
869                                 snprintf(spn, sizeof(spn), "%s@%s", myhostad, realm);
870                                 code = krb5_build_principal_ext(context, &princ,
871                                                                 strlen(realm),
872                                                                 realm,
873                                                                 strlen(myhostad),
874                                                                 myhostad,
875                                                                 NULL);
876                         } else {
877                                 snprintf(spn, sizeof(spn), "%s/%s@%s",
878                                          svcnames[j], myhostname, realm);
879                                 code = krb5_build_principal_ext(context, &princ,
880                                                                 strlen(realm),
881                                                                 realm,
882                                                                 strlen(svcnames[j]),
883                                                                 svcnames[j],
884                                                                 strlen(myhostname),
885                                                                 myhostname,
886                                                                 NULL);
887                         }
888
889                         if (code) {
890                                 k5err = gssd_k5_err_msg(context, code);
891                                 printerr(1, "%s while building principal for '%s'\n",
892                                          k5err, spn);
893                                 continue;
894                         }
895                         code = krb5_kt_get_entry(context, kt, princ, 0, 0, kte);
896                         krb5_free_principal(context, princ);
897                         if (code) {
898                                 k5err = gssd_k5_err_msg(context, code);
899                                 printerr(3, "%s while getting keytab entry for '%s'\n",
900                                          k5err, spn);
901                         } else {
902                                 printerr(3, "Success getting keytab entry for '%s'\n",spn);
903                                 retval = 0;
904                                 goto out;
905                         }
906                         retval = code;
907                 }
908                 /*
909                  * Nothing found with our hostname instance, now look for
910                  * names with any instance (they must have an instance)
911                  */
912                 for (j = 0; svcnames[j] != NULL; j++) {
913                         int found = 0;
914                         if (strcmp(svcnames[j],"$") == 0)
915                                 continue;
916                         code = gssd_search_krb5_keytab(context, kt, realm,
917                                                        svcnames[j], &found, kte);
918                         if (!code && found) {
919                                 printerr(3, "Success getting keytab entry for "
920                                          "%s/*@%s\n", svcnames[j], realm);
921                                 retval = 0;
922                                 goto out;
923                         }
924                 }
925                 if (!tried_all) {
926                         i++;
927                         realm = realmnames[i];
928                 }
929         }
930 out:
931         if (default_realm)
932                 k5_free_default_realm(context, default_realm);
933         if (realmnames)
934                 krb5_free_host_realm(context, realmnames);
935         free(k5err);
936         return retval;
937 }
938
939
940 static inline int data_is_equal(krb5_data d1, krb5_data d2)
941 {
942         return (d1.length == d2.length
943                 && memcmp(d1.data, d2.data, d1.length) == 0);
944 }
945
946 static int
947 check_for_tgt(krb5_context context, krb5_ccache ccache,
948               krb5_principal principal)
949 {
950         krb5_error_code ret;
951         krb5_creds creds;
952         krb5_cc_cursor cur;
953         int found = 0;
954
955         ret = krb5_cc_start_seq_get(context, ccache, &cur);
956         if (ret) 
957                 return 0;
958
959         while (!found &&
960                 (ret = krb5_cc_next_cred(context, ccache, &cur, &creds)) == 0) {
961                 if (creds.server->length == 2 &&
962                                 data_is_equal(creds.server->realm,
963                                               principal->realm) &&
964                                 creds.server->data[0].length == 6 &&
965                                 memcmp(creds.server->data[0].data,
966                                                 "krbtgt", 6) == 0 &&
967                                 data_is_equal(creds.server->data[1],
968                                               principal->realm) &&
969                                 creds.times.endtime > time(NULL))
970                         found = 1;
971                 krb5_free_cred_contents(context, &creds);
972         }
973         krb5_cc_end_seq_get(context, ccache, &cur);
974
975         return found;
976 }
977
978 static int
979 query_krb5_ccache(const char* cred_cache, char **ret_princname,
980                   char **ret_realm)
981 {
982         krb5_error_code ret;
983         krb5_context context;
984         krb5_ccache ccache;
985         krb5_principal principal;
986         int found = 0;
987         char *str = NULL;
988         char *princstring;
989
990         ret = krb5_init_context(&context);
991         if (ret) 
992                 return 0;
993
994         if(!cred_cache || krb5_cc_resolve(context, cred_cache, &ccache))
995                 goto err_cache;
996
997         if (krb5_cc_set_flags(context, ccache, 0))
998                 goto err_princ;
999
1000         ret = krb5_cc_get_principal(context, ccache, &principal);
1001         if (ret) 
1002                 goto err_princ;
1003
1004         found = check_for_tgt(context, ccache, principal);
1005         if (found) {
1006                 ret = krb5_unparse_name(context, principal, &princstring);
1007                 if (ret == 0) {
1008                     if ((str = strchr(princstring, '@')) != NULL) {
1009                             *str = '\0';
1010                             *ret_princname = strdup(princstring);
1011                             *ret_realm = strdup(str+1);
1012                     }
1013                     k5_free_unparsed_name(context, princstring);
1014                 } else {
1015                         found = 0;
1016                 }
1017         }
1018         krb5_free_principal(context, principal);
1019 err_princ:
1020         krb5_cc_set_flags(context, ccache,  KRB5_TC_OPENCLOSE);
1021         krb5_cc_close(context, ccache);
1022 err_cache:
1023         krb5_free_context(context);
1024         return found;
1025 }
1026
1027 /*==========================*/
1028 /*===  External routines ===*/
1029 /*==========================*/
1030
1031 /*
1032  * Attempt to find the best match for a credentials cache file
1033  * given only a UID.  We really need more information, but we
1034  * do the best we can.
1035  *
1036  * Returns 0 if a ccache was found, and a non-zero error code otherwise.
1037  */
1038 int
1039 gssd_setup_krb5_user_gss_ccache(uid_t uid, char *servername, char *dirpattern)
1040 {
1041         char                    buf[MAX_NETOBJ_SZ], dirname[PATH_MAX];
1042         const char              *cctype;
1043         struct dirent           *d;
1044         int                     err, i, j;
1045
1046         printerr(2, "getting credentials for client with uid %u for "
1047                     "server %s\n", uid, servername);
1048
1049         for (i = 0, j = 0; dirpattern[i] != '\0'; i++) {
1050                 switch (dirpattern[i]) {
1051                 case '%':
1052                         switch (dirpattern[i + 1]) {
1053                         case '%':
1054                                 dirname[j++] = dirpattern[i];
1055                                 i++;
1056                                 break;
1057                         case 'U':
1058                                 j += sprintf(dirname + j, "%lu",
1059                                              (unsigned long) uid);
1060                                 i++;
1061                                 break;
1062                         }
1063                         break;
1064                 default:
1065                         dirname[j++] = dirpattern[i];
1066                         break;
1067                 }
1068         }
1069         dirname[j] = '\0';
1070
1071         err = gssd_find_existing_krb5_ccache(uid, dirname, &cctype, &d);
1072         if (err)
1073                 return err;
1074
1075         snprintf(buf, sizeof(buf), "%s:%s/%s", cctype, dirname, d->d_name);
1076         free(d);
1077
1078         printerr(2, "using %s as credentials cache for client with "
1079                     "uid %u for server %s\n", buf, uid, servername);
1080         gssd_set_krb5_ccache_name(buf);
1081         return err;
1082 }
1083
1084 /*
1085  * Let the gss code know where to find the machine credentials ccache.
1086  *
1087  * Returns:
1088  *      void
1089  */
1090 void
1091 gssd_setup_krb5_machine_gss_ccache(char *ccname)
1092 {
1093         printerr(2, "using %s as credentials cache for machine creds\n",
1094                  ccname);
1095         gssd_set_krb5_ccache_name(ccname);
1096 }
1097
1098 /*
1099  * Return an array of pointers to names of credential cache files
1100  * which can be used to try to create gss contexts with a server.
1101  *
1102  * Returns:
1103  *      0 => list is attached
1104  *      nonzero => error
1105  */
1106 int
1107 gssd_get_krb5_machine_cred_list(char ***list)
1108 {
1109         char **l;
1110         int listinc = 10;
1111         int listsize = listinc;
1112         int i = 0;
1113         int retval;
1114         struct gssd_k5_kt_princ *ple;
1115
1116         /* Assume failure */
1117         retval = -1;
1118         *list = (char **) NULL;
1119
1120         if ((l = (char **) malloc(listsize * sizeof(char *))) == NULL) {
1121                 retval = ENOMEM;
1122                 goto out;
1123         }
1124
1125         /* Need to serialize list if we ever become multi-threaded! */
1126
1127         for (ple = gssd_k5_kt_princ_list; ple; ple = ple->next) {
1128                 if (ple->ccname) {
1129                         /* Make sure cred is up-to-date before returning it */
1130                         retval = gssd_refresh_krb5_machine_credential(NULL, ple,
1131                                 NULL);
1132                         if (retval)
1133                                 continue;
1134                         if (i + 1 > listsize) {
1135                                 listsize += listinc;
1136                                 l = (char **)
1137                                         realloc(l, listsize * sizeof(char *));
1138                                 if (l == NULL) {
1139                                         retval = ENOMEM;
1140                                         goto out;
1141                                 }
1142                         }
1143                         if ((l[i++] = strdup(ple->ccname)) == NULL) {
1144                                 retval = ENOMEM;
1145                                 goto out;
1146                         }
1147                 }
1148         }
1149         if (i > 0) {
1150                 l[i] = NULL;
1151                 *list = l;
1152                 retval = 0;
1153                 goto out;
1154         }
1155   out:
1156         return retval;
1157 }
1158
1159 /*
1160  * Frees the list of names returned in get_krb5_machine_cred_list()
1161  */
1162 void
1163 gssd_free_krb5_machine_cred_list(char **list)
1164 {
1165         char **n;
1166
1167         if (list == NULL)
1168                 return;
1169         for (n = list; n && *n; n++) {
1170                 free(*n);
1171         }
1172         free(list);
1173 }
1174
1175 /*
1176  * Called upon exit.  Destroys machine credentials.
1177  */
1178 void
1179 gssd_destroy_krb5_machine_creds(void)
1180 {
1181         krb5_context context;
1182         krb5_error_code code = 0;
1183         krb5_ccache ccache;
1184         struct gssd_k5_kt_princ *ple;
1185         char *k5err = NULL;
1186
1187         code = krb5_init_context(&context);
1188         if (code) {
1189                 k5err = gssd_k5_err_msg(NULL, code);
1190                 printerr(0, "ERROR: %s while initializing krb5\n", k5err);
1191                 goto out;
1192         }
1193
1194         for (ple = gssd_k5_kt_princ_list; ple; ple = ple->next) {
1195                 if (!ple->ccname)
1196                         continue;
1197                 if ((code = krb5_cc_resolve(context, ple->ccname, &ccache))) {
1198                         k5err = gssd_k5_err_msg(context, code);
1199                         printerr(0, "WARNING: %s while resolving credential "
1200                                     "cache '%s' for destruction\n", k5err,
1201                                     ple->ccname);
1202                         continue;
1203                 }
1204
1205                 if ((code = krb5_cc_destroy(context, ccache))) {
1206                         k5err = gssd_k5_err_msg(context, code);
1207                         printerr(0, "WARNING: %s while destroying credential "
1208                                     "cache '%s'\n", k5err, ple->ccname);
1209                 }
1210         }
1211   out:
1212         free(k5err);
1213         krb5_free_context(context);
1214 }
1215
1216 /*
1217  * Obtain (or refresh if necessary) Kerberos machine credentials
1218  */
1219 int
1220 gssd_refresh_krb5_machine_credential(char *hostname,
1221                                      struct gssd_k5_kt_princ *ple, 
1222                                          char *service)
1223 {
1224         krb5_error_code code = 0;
1225         krb5_context context;
1226         krb5_keytab kt = NULL;;
1227         int retval = 0;
1228         char *k5err = NULL;
1229         const char *svcnames[5] = { "$", "root", "nfs", "host", NULL };
1230
1231         /*
1232          * If a specific service name was specified, use it.
1233          * Otherwise, use the default list.
1234          */
1235         if (service != NULL && strcmp(service, "*") != 0) {
1236                 svcnames[0] = service;
1237                 svcnames[1] = NULL;
1238         }
1239         if (hostname == NULL && ple == NULL)
1240                 return EINVAL;
1241
1242         code = krb5_init_context(&context);
1243         if (code) {
1244                 k5err = gssd_k5_err_msg(NULL, code);
1245                 printerr(0, "ERROR: %s: %s while initializing krb5 context\n",
1246                          __func__, k5err);
1247                 retval = code;
1248                 goto out;
1249         }
1250
1251         if ((code = krb5_kt_resolve(context, keytabfile, &kt))) {
1252                 k5err = gssd_k5_err_msg(context, code);
1253                 printerr(0, "ERROR: %s: %s while resolving keytab '%s'\n",
1254                          __func__, k5err, keytabfile);
1255                 goto out;
1256         }
1257
1258         if (ple == NULL) {
1259                 krb5_keytab_entry kte;
1260
1261                 code = find_keytab_entry(context, kt, hostname, &kte, svcnames);
1262                 if (code) {
1263                         printerr(0, "ERROR: %s: no usable keytab entry found "
1264                                  "in keytab %s for connection with host %s\n",
1265                                  __FUNCTION__, keytabfile, hostname);
1266                         retval = code;
1267                         goto out;
1268                 }
1269
1270                 ple = get_ple_by_princ(context, kte.principal);
1271                 k5_free_kt_entry(context, &kte);
1272                 if (ple == NULL) {
1273                         char *pname;
1274                         if ((krb5_unparse_name(context, kte.principal, &pname))) {
1275                                 pname = NULL;
1276                         }
1277                         printerr(0, "ERROR: %s: Could not locate or create "
1278                                  "ple struct for principal %s for connection "
1279                                  "with host %s\n",
1280                                  __FUNCTION__, pname ? pname : "<unparsable>",
1281                                  hostname);
1282                         if (pname) k5_free_unparsed_name(context, pname);
1283                         goto out;
1284                 }
1285         }
1286         retval = gssd_get_single_krb5_cred(context, kt, ple, 0);
1287 out:
1288         if (kt)
1289                 krb5_kt_close(context, kt);
1290         krb5_free_context(context);
1291         free(k5err);
1292         return retval;
1293 }
1294
1295 /*
1296  * A common routine for getting the Kerberos error message
1297  */
1298 char *
1299 gssd_k5_err_msg(krb5_context context, krb5_error_code code)
1300 {
1301         const char *origmsg;
1302         char *msg = NULL;
1303
1304 #if HAVE_KRB5_GET_ERROR_MESSAGE
1305         if (context != NULL) {
1306                 origmsg = krb5_get_error_message(context, code);
1307                 msg = strdup(origmsg);
1308                 krb5_free_error_message(context, origmsg);
1309         }
1310 #endif
1311         if (msg != NULL)
1312                 return msg;
1313 #if HAVE_KRB5
1314         return strdup(error_message(code));
1315 #else
1316         if (context != NULL)
1317                 return strdup(krb5_get_err_text(context, code));
1318         else
1319                 return strdup(error_message(code));
1320 #endif
1321 }
1322
1323 /*
1324  * Return default Kerberos realm
1325  */
1326 void
1327 gssd_k5_get_default_realm(char **def_realm)
1328 {
1329         krb5_context context;
1330
1331         if (krb5_init_context(&context))
1332                 return;
1333
1334         krb5_get_default_realm(context, def_realm);
1335
1336         krb5_free_context(context);
1337 }
1338
1339 #ifdef HAVE_SET_ALLOWABLE_ENCTYPES
1340 /*
1341  * this routine obtains a credentials handle via gss_acquire_cred()
1342  * then calls gss_krb5_set_allowable_enctypes() to limit the encryption
1343  * types negotiated.
1344  *
1345  * XXX Should call some function to determine the enctypes supported
1346  * by the kernel. (Only need to do that once!)
1347  *
1348  * Returns:
1349  *      0 => all went well
1350  *     -1 => there was an error
1351  */
1352
1353 int
1354 limit_krb5_enctypes(struct rpc_gss_sec *sec)
1355 {
1356         u_int maj_stat, min_stat;
1357         gss_cred_id_t credh;
1358         gss_OID_set_desc  desired_mechs;
1359         krb5_enctype enctypes[] = { ENCTYPE_DES_CBC_CRC,
1360                                     ENCTYPE_DES_CBC_MD5,
1361                                     ENCTYPE_DES_CBC_MD4 };
1362         int num_enctypes = sizeof(enctypes) / sizeof(enctypes[0]);
1363         extern int num_krb5_enctypes;
1364         extern krb5_enctype *krb5_enctypes;
1365
1366         /* We only care about getting a krb5 cred */
1367         desired_mechs.count = 1;
1368         desired_mechs.elements = &krb5oid;
1369
1370         maj_stat = gss_acquire_cred(&min_stat, NULL, 0,
1371                                     &desired_mechs, GSS_C_INITIATE,
1372                                     &credh, NULL, NULL);
1373
1374         if (maj_stat != GSS_S_COMPLETE) {
1375                 if (get_verbosity() > 0)
1376                         pgsserr("gss_acquire_cred",
1377                                 maj_stat, min_stat, &krb5oid);
1378                 return -1;
1379         }
1380
1381         /*
1382          * If we failed for any reason to produce global
1383          * list of supported enctypes, use local default here.
1384          */
1385         if (krb5_enctypes == NULL || limit_to_legacy_enctypes)
1386                 maj_stat = gss_set_allowable_enctypes(&min_stat, credh,
1387                                         &krb5oid, num_enctypes, enctypes);
1388         else
1389                 maj_stat = gss_set_allowable_enctypes(&min_stat, credh,
1390                                         &krb5oid, num_krb5_enctypes, krb5_enctypes);
1391
1392         if (maj_stat != GSS_S_COMPLETE) {
1393                 pgsserr("gss_set_allowable_enctypes",
1394                         maj_stat, min_stat, &krb5oid);
1395                 gss_release_cred(&min_stat, &credh);
1396                 return -1;
1397         }
1398         sec->cred = credh;
1399
1400         return 0;
1401 }
1402 #endif  /* HAVE_SET_ALLOWABLE_ENCTYPES */