Whamcloud - gitweb
52318a588e06b2defd89207049aa7a3a25ea1839
[fs/lustre-release.git] / lustre / utils / gss / 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 #ifndef _GNU_SOURCE
95 #define _GNU_SOURCE
96 #endif
97 #include "config.h"
98 #include <sys/param.h>
99 #include <rpc/rpc.h>
100 #include <sys/types.h>
101 #include <sys/stat.h>
102 #include <sys/utsname.h>
103 #include <sys/socket.h>
104 #include <arpa/inet.h>
105
106 #include <unistd.h>
107 #include <stdio.h>
108 #include <stdlib.h>
109 #include <string.h>
110 #ifdef HAVE_NETDB_H
111 # include <netdb.h>
112 #endif
113 #include <dirent.h>
114 #include <fcntl.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
123 #include "gssd.h"
124 #include "err_util.h"
125 #include "gss_util.h"
126 #include "gss_oids.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 /* Encryption types supported by the kernel rpcsec_gss code */
133 int num_krb5_enctypes = 0;
134 krb5_enctype *krb5_enctypes = NULL;
135
136 /* credential expire time in advance */
137 unsigned long machine_cred_expire_advance = 300; /* 5 mins */
138
139 /*==========================*/
140 /*===  Internal routines ===*/
141 /*==========================*/
142
143 static int select_krb5_ccache(const struct dirent *d);
144 static int gssd_find_existing_krb5_ccache(uid_t uid, struct dirent **d);
145 static int gssd_get_single_krb5_cred(krb5_context context,
146                 krb5_keytab kt, struct gssd_k5_kt_princ *ple);
147 static int gssd_process_krb5_keytab(krb5_context context, krb5_keytab kt,
148                 char *kt_name);
149
150 /*
151  * convenient macros, these perhaps need further cleanup
152  */
153 #ifdef HAVE_KRB5
154
155 #define KEYTAB_ENTRY_MATCH(kte, name)                                          \
156         (                                                                      \
157          (kte).principal->data[0].length == (sizeof(name)-1) &&                \
158          strncmp((kte).principal->data[0].data, (name), sizeof(name)-1) == 0   \
159         )
160 #define KRB5_FREE_UNPARSED_NAME(ctx, name)                                     \
161                 krb5_free_unparsed_name((ctx), (name));
162 #define KRB5_STRDUP(str)                                                       \
163                 strndup((str).data, (str).length)
164 #define KRB5_STRCMP(str, name)                                                 \
165         (                                                                      \
166          (str)->length != strlen(name) ||                                      \
167          strncmp((str)->data, (name), (str)->length) != 0                      \
168         )
169 #define KRB5_STRCASECMP(str, name)                                             \
170         (                                                                      \
171          (str)->length != strlen(name) ||                                      \
172          strncasecmp((str)->data, (name), (str)->length) != 0                  \
173         )
174
175 #else /* !HAVE_KRB5 */
176
177 #define KEYTAB_ENTRY_MATCH(kte, name)                                          \
178         (                                                                      \
179          strlen((kte).principal->name.name_string.val[0]) ==                   \
180          (sizeof(name)-1) &&                                                   \
181          strncmp(kte.principal->name.name_string.val[0], (name),               \
182                  sizeof(name)-1) == 0                                          \
183         )
184 #define KRB5_FREE_UNPARSED_NAME(ctx, name)                                     \
185                 free(pname);
186 #define KRB5_STRDUP(str)                                                       \
187                 strdup(str)
188 #define KRB5_STRCMP(str, name)                                                 \
189                 strcmp((str), (name))
190 #define KRB5_STRCASECMP(str, name)                                             \
191                 strcmp((str), (name))
192
193 #endif /* HAVE_KRB5 */
194
195 /*
196  * Called from the scandir function to weed out potential krb5
197  * credentials cache files
198  *
199  * Returns:
200  *      0 => don't select this one
201  *      1 => select this one
202  */
203 static int
204 select_krb5_ccache(const struct dirent *d)
205 {
206         /*
207          * Note: We used to check d->d_type for DT_REG here,
208          * but apparenlty reiser4 always has DT_UNKNOWN.
209          * Check for IS_REG after stat() call instead.
210          */
211         if (strstr(d->d_name, GSSD_DEFAULT_CRED_PREFIX))
212                 return 1;
213         else
214                 return 0;
215 }
216
217 /*
218  * Look in the ccachedir for files that look like they
219  * are Kerberos Credential Cache files for a given UID.  Return
220  * non-zero and the dirent pointer for the entry most likely to be
221  * what we want. Otherwise, return zero and no dirent pointer.
222  * The caller is responsible for freeing the dirent if one is returned.
223  *
224  * Returns:
225  *      0 => could not find an existing entry
226  *      1 => found an existing entry
227  */
228 static int
229 gssd_find_existing_krb5_ccache(uid_t uid, struct dirent **d)
230 {
231         struct dirent **namelist;
232         int n;
233         int i;
234         int found = 0;
235         struct dirent *best_match_dir = NULL;
236         struct stat best_match_stat, tmp_stat;
237
238         memset(&best_match_stat, 0, sizeof(best_match_stat));
239         *d = NULL;
240         n = scandir(ccachedir, &namelist, select_krb5_ccache, 0);
241         if (n < 0) {
242                 perror("scandir looking for krb5 credentials caches");
243         }
244         else if (n > 0) {
245                 char statname[1024];
246                 for (i = 0; i < n; i++) {
247                         printerr(3, "CC file '%s' being considered\n",
248                                  namelist[i]->d_name);
249                         snprintf(statname, sizeof(statname),
250                                  "%s/%s", ccachedir, namelist[i]->d_name);
251                         if (stat(statname, &tmp_stat)) {
252                                 printerr(0, "Error doing stat on file '%s'\n",
253                                          statname);
254                                 free(namelist[i]);
255                                 continue;
256                         }
257                         /* Only pick caches owned by the user (uid) */
258                         if (tmp_stat.st_uid != uid) {
259                                 printerr(3, "'%s' owned by %u, not %u\n",
260                                          statname, tmp_stat.st_uid, uid);
261                                 free(namelist[i]);
262                                 continue;
263                         }
264                         if (!S_ISREG(tmp_stat.st_mode)) {
265                                 printerr(3, "'%s' is not a regular file\n",
266                                          statname);
267                                 free(namelist[i]);
268                                 continue;
269                         }
270                         printerr(3, "CC file '%s' matches owner check and has "
271                                  "mtime of %u\n",
272                                  namelist[i]->d_name, tmp_stat.st_mtime);
273                         /*
274                          * if more than one match is found, return the most
275                          * recent (the one with the latest mtime), and
276                          * don't free the dirent
277                          */
278                         if (!found) {
279                                 best_match_dir = namelist[i];
280                                 best_match_stat = tmp_stat;
281                                 found++;
282                         }
283                         else {
284                                 /*
285                                  * If the current match has an mtime later
286                                  * than the one we are looking at, then use
287                                  * the current match.  Otherwise, we still
288                                  * have the best match.
289                                  */
290                                 if (tmp_stat.st_mtime >
291                                             best_match_stat.st_mtime) {
292                                         free(best_match_dir);
293                                         best_match_dir = namelist[i];
294                                         best_match_stat = tmp_stat;
295                                 }
296                                 else {
297                                         free(namelist[i]);
298                                 }
299                                 printerr(3, "CC file '%s' is our "
300                                             "current best match "
301                                             "with mtime of %u\n",
302                                          best_match_dir->d_name,
303                                          best_match_stat.st_mtime);
304                         }
305                 }
306                 free(namelist);
307         }
308         if (found)
309         {
310                 *d = best_match_dir;
311         }
312         return found;
313 }
314
315
316 /*
317  * Obtain credentials via a key in the keytab given
318  * a keytab handle and a gssd_k5_kt_princ structure.
319  * Checks to see if current credentials are expired,
320  * if not, uses the keytab to obtain new credentials.
321  *
322  * Returns:
323  *      0 => success (or credentials have not expired)
324  *      nonzero => error
325  */
326 static int
327 gssd_get_single_krb5_cred(krb5_context context,
328                           krb5_keytab kt,
329                           struct gssd_k5_kt_princ *ple)
330 {
331         krb5_get_init_creds_opt options;
332         krb5_creds my_creds;
333         krb5_ccache ccache = NULL;
334         char kt_name[BUFSIZ];
335         char cc_name[BUFSIZ];
336         int code;
337         time_t now = time(0);
338         char *cache_type;
339
340         memset(&my_creds, 0, sizeof(my_creds));
341
342         if (ple->ccname && ple->endtime > now + machine_cred_expire_advance) {
343                 printerr(2, "INFO: Credentials in CC '%s' are good until %d\n",
344                          ple->ccname, ple->endtime);
345                 code = 0;
346                 goto out;
347         }
348
349         if ((code = krb5_kt_get_name(context, kt, kt_name, BUFSIZ))) {
350                 printerr(0, "ERROR: Unable to get keytab name in "
351                             "gssd_get_single_krb5_cred\n");
352                 goto out;
353         }
354
355         krb5_get_init_creds_opt_init(&options);
356         krb5_get_init_creds_opt_set_address_list(&options, NULL);
357
358 #ifdef TEST_SHORT_LIFETIME
359         /* set a short lifetime (for debugging only!) */
360         printerr(0, "WARNING: Using (debug) short machine cred lifetime!\n");
361         krb5_get_init_creds_opt_set_tkt_life(&options, 5*60);
362 #else
363         /* FIXME try to get the ticket with lifetime as long as possible,
364          * to work around ticket-expiry + recovery problem in cmd3-11
365          * remove this!!!
366          */
367         krb5_get_init_creds_opt_set_tkt_life(&options, 30*24*60*60);
368 #endif
369         if ((code = krb5_get_init_creds_keytab(context, &my_creds, ple->princ,
370                                           kt, 0, NULL, &options))) {
371                 char *pname;
372                 if ((krb5_unparse_name(context, ple->princ, &pname))) {
373                         pname = NULL;
374                 }
375                 printerr(0, "WARNING: %s while getting initial ticket for "
376                             "principal '%s' from keytab '%s'\n",
377                          error_message(code),
378                          pname ? pname : "<unparsable>", kt_name);
379                 if (pname) KRB5_FREE_UNPARSED_NAME(context, pname);
380                 goto out;
381         }
382
383         /*
384          * Initialize cache file which we're going to be using
385          */
386
387         if (use_memcache)
388             cache_type = "MEMORY";
389         else
390             cache_type = "FILE";
391         snprintf(cc_name, sizeof(cc_name), "%s:%s/%s%s_%s",
392                 cache_type,
393                 GSSD_DEFAULT_CRED_DIR, GSSD_DEFAULT_CRED_PREFIX,
394                 GSSD_DEFAULT_MACHINE_CRED_SUFFIX, ple->realm);
395         ple->endtime = my_creds.times.endtime;
396         ple->ccname = strdup(cc_name);
397         if (ple->ccname == NULL) {
398                 printerr(0, "ERROR: no storage to duplicate credentials "
399                             "cache name\n");
400                 code = ENOMEM;
401                 goto out;
402         }
403         if ((code = krb5_cc_resolve(context, cc_name, &ccache))) {
404                 printerr(0, "ERROR: %s while opening credential cache '%s'\n",
405                          error_message(code), cc_name);
406                 goto out;
407         }
408         if ((code = krb5_cc_initialize(context, ccache, ple->princ))) {
409                 printerr(0, "ERROR: %s while initializing credential "
410                          "cache '%s'\n", error_message(code), cc_name);
411                 goto out;
412         }
413         if ((code = krb5_cc_store_cred(context, ccache, &my_creds))) {
414                 printerr(0, "ERROR: %s while storing credentials in '%s'\n",
415                          error_message(code), cc_name);
416                 goto out;
417         }
418
419         code = 0;
420         printerr(1, "Using (machine) credentials cache: '%s'\n", cc_name);
421   out:
422         if (ccache)
423                 krb5_cc_close(context, ccache);
424         krb5_free_cred_contents(context, &my_creds);
425         return (code);
426 }
427
428 static struct gssd_k5_kt_princ * gssd_get_realm_ple(void *r)
429 {
430         struct gssd_k5_kt_princ *ple;
431 #ifdef HAVE_KRB5
432         krb5_data *realm = (krb5_data *)r;
433 #else
434         char *realm = (char *)r;
435 #endif
436
437         for (ple = gssd_k5_kt_princ_list; ple; ple = ple->next) {
438                 if (KRB5_STRCMP(realm, ple->realm) == 0)
439                     return ple;
440         }
441         return NULL;
442 }
443
444 static void gssd_free_ple(krb5_context kctx, struct gssd_k5_kt_princ *ple)
445 {
446         if (ple->princ)
447                 krb5_free_principal(kctx, ple->princ);
448         if (ple->realm)
449                 free(ple->realm);
450         if (ple->ccname)
451                 free(ple->ccname);
452         free(ple);
453 }
454
455 static int gssd_remove_ple(krb5_context kctx, struct gssd_k5_kt_princ *ple)
456 {
457         struct gssd_k5_kt_princ **prev = &gssd_k5_kt_princ_list;
458         struct gssd_k5_kt_princ  *ent = gssd_k5_kt_princ_list;
459
460         for (; ent; prev = &ent->next, ent = ent->next) {
461                 if (ent != ple)
462                         continue;
463
464                 *prev = ent->next;
465                 gssd_free_ple(kctx, ent);
466                 return 1;
467         }
468         return 0;
469 }
470
471 static
472 struct gssd_k5_kt_princ *gssd_create_ple(krb5_context kctx,
473                                          krb5_principal principal)
474 {
475         struct gssd_k5_kt_princ *ple;
476         krb5_error_code          code;
477
478         ple = malloc(sizeof(*ple));
479         if (ple == NULL) {
480                 printerr(0, "ERROR: could not allocate storage "
481                             "for principal list entry\n");
482                 return NULL;
483         }
484
485         memset(ple, 0, sizeof(*ple));
486
487         ple->realm = KRB5_STRDUP(principal->realm);
488         if (ple->realm == NULL) {
489                 printerr(0, "ERROR: not enough memory while copying realm to "
490                             "principal list entry\n");
491                 goto err_free;
492         }
493
494         code = krb5_copy_principal(kctx, principal, &ple->princ);
495         if (code) {
496                 printerr(0, "ERROR: %s while copying principal "
497                             "to principal list entry\n",
498                          error_message(code));
499                 goto err_free;
500         }
501
502         return ple;
503 err_free:
504         gssd_free_ple(kctx, ple);
505         return NULL;
506 }
507
508 /*
509  * Process the given keytab file and create a list of principals we
510  * might use to perform mount operations.
511  *
512  * Returns:
513  *      0 => Success
514  *      nonzero => Error
515  */
516 static int
517 gssd_process_krb5_keytab(krb5_context context, krb5_keytab kt, char *kt_name)
518 {
519         krb5_kt_cursor cursor;
520         krb5_keytab_entry kte;
521         krb5_error_code code;
522         struct gssd_k5_kt_princ *ple;
523         int retval = -1;
524
525         /*
526          * Look through each entry in the keytab file and determine
527          * if we might want to use it later to do a mount.  If so,
528          * save info in the global principal list
529          * (gssd_k5_kt_princ_list).
530          * Note: (ple == principal list entry)
531          */
532         if ((code = krb5_kt_start_seq_get(context, kt, &cursor))) {
533                 printerr(0, "ERROR: %s while beginning keytab scan "
534                             "for keytab '%s'\n",
535                         error_message(code), kt_name);
536                 retval = code;
537                 goto out;
538         }
539
540         while ((code = krb5_kt_next_entry(context, kt, &kte, &cursor)) == 0) {
541                 char *pname;
542                 if ((code = krb5_unparse_name(context, kte.principal,
543                                               &pname))) {
544                         printerr(0, "WARNING: Skipping keytab entry because "
545                                     "we failed to unparse principal name: %s\n",
546                                  error_message(code));
547                         continue;
548                 }
549                 printerr(2, "Processing keytab entry for principal '%s'\n",
550                          pname);
551
552                 /* mds service entry:
553                  *   - hostname and realm should match this node
554                  *   - replace existing non-mds entry of this realm
555                  */
556                 if (KEYTAB_ENTRY_MATCH(kte, GSSD_SERVICE_MDS)) {
557                         krb5_principal princ = kte.principal;
558                         krb5_data *princ_host;
559                         struct utsname utsbuf;
560                         struct hostent *host;
561
562                         if (KRB5_STRCASECMP(krb5_princ_realm(context, princ),
563                                             this_realm) != 0) {
564                                 printerr(2, "alien mds service entry, skip\n");
565                                 goto next;
566                         }
567
568                         princ_host = krb5_princ_component(context, princ, 1);
569                         if (princ_host == NULL) {
570                                 printerr(2, "mds service entry: no hostname in "
571                                          "principal, skip\n");
572                                 goto next;
573                         }
574
575                         if (uname(&utsbuf)) {
576                                 printerr(2, "mds service entry: unable to get "
577                                          "UTS name, skip\n");
578                                 goto next;
579                         }
580                         host = gethostbyname(utsbuf.nodename);
581                         if (host == NULL) {
582                                 printerr(2, "mds service entry: unable to get "
583                                          "local hostname, skip\n");
584                                 goto next;
585                         }
586
587                         if (KRB5_STRCASECMP(princ_host, host->h_name) != 0) {
588                                 printerr(2, "mds service entry: hostname "
589                                          "doesn't match: %s - %.*s, skip\n",
590                                          host->h_name,
591                                          princ_host->length, princ_host->data);
592                                 goto next;
593                         }
594
595                         ple = gssd_get_realm_ple((void *)&kte.principal->realm);
596                         if (ple) {
597                                 if (ple->fl_mds) {
598                                         printerr(2,"mds service entry: found a"
599                                                  "duplicated one, it's like a "
600                                                  "mis-configuration, skip\n");
601                                         goto next;
602                                 }
603
604                                 gssd_remove_ple(context, ple);
605                                 printerr(2, "mds service entry: replace an "
606                                          "existed non-mds one\n");
607                         }
608                 } else if (KEYTAB_ENTRY_MATCH(kte, LUSTRE_ROOT_NAME)) {
609                         ple = gssd_get_realm_ple((void *)&kte.principal->realm);
610                         if (ple) {
611                                 if (ple->fl_mds || ple->fl_root) {
612                                         printerr(2, "root entry: found a "
613                                                  "existed %s entry, skip\n",
614                                                  ple->fl_mds ? "mds" : "root");
615                                         goto next;
616                                 }
617
618                                 gssd_remove_ple(context, ple);
619                                 printerr(2, "root entry: replace an existed "
620                                          "non-mds non-root one\n");
621                         }
622                 } else {
623                         printerr(2, "We will NOT use this entry (%s)\n",
624                                 pname);
625                         goto next;
626                 }
627
628                 /* construct ple */
629                 printerr(2, "We will use this entry (%s)\n", pname);
630                 ple = gssd_create_ple(context, kte.principal);
631                 if (ple == NULL) {
632                         KRB5_FREE_UNPARSED_NAME(context, pname);
633                         goto out;
634                 }
635
636                 /* add proper flags */
637                 if (KEYTAB_ENTRY_MATCH(kte, GSSD_SERVICE_MDS))
638                         ple->fl_mds = 1;
639                 else if (KEYTAB_ENTRY_MATCH(kte, LUSTRE_ROOT_NAME))
640                         ple->fl_root = 1;
641
642                 /* enqueue */
643                 if (gssd_k5_kt_princ_list == NULL)
644                         gssd_k5_kt_princ_list = ple;
645                 else {
646                         ple->next = gssd_k5_kt_princ_list;
647                         gssd_k5_kt_princ_list = ple;
648                 }
649  next:
650                 KRB5_FREE_UNPARSED_NAME(context, pname);
651         }
652
653         if ((code = krb5_kt_end_seq_get(context, kt, &cursor))) {
654                 printerr(0, "WARNING: %s while ending keytab scan for "
655                             "keytab '%s'\n",
656                          error_message(code), kt_name);
657         }
658
659         retval = 0;
660   out:
661         return retval;
662 }
663
664 /*
665  * Depending on the version of Kerberos, we either need to use
666  * a private function, or simply set the environment variable.
667  */
668 static void
669 gssd_set_krb5_ccache_name(char *ccname)
670 {
671 #ifdef USE_GSS_KRB5_CCACHE_NAME
672         unsigned int    maj_stat, min_stat;
673
674         printerr(2, "using gss_krb5_ccache_name to select krb5 ccache %s\n",
675                  ccname);
676         maj_stat = gss_krb5_ccache_name(&min_stat, ccname, NULL);
677         if (maj_stat != GSS_S_COMPLETE) {
678                 printerr(0, "WARNING: gss_krb5_ccache_name with "
679                         "name '%s' failed (%s)\n",
680                         ccname, error_message(min_stat));
681         }
682 #else
683         /*
684          * Set the KRB5CCNAME environment variable to tell the krb5 code
685          * which credentials cache to use.  (Instead of using the private
686          * function above for which there is no generic gssapi
687          * equivalent.)
688          */
689         printerr(2, "using environment variable to select krb5 ccache %s\n",
690                  ccname);
691         setenv("KRB5CCNAME", ccname, 1);
692 #endif
693 }
694
695 /*
696  * Parse the supported encryption type information
697  */
698 static int
699 parse_enctypes(char *enctypes)
700 {
701         int n = 0;
702         char *curr, *comma;
703         int i;
704
705         /* Just in case this ever gets called more than once */
706         if (krb5_enctypes != NULL) {
707                 free(krb5_enctypes);
708                 krb5_enctypes = NULL;
709                 num_krb5_enctypes = 0;
710         }
711
712         /* count the number of commas */
713         for (curr = enctypes; curr && *curr != '\0'; curr = ++comma) {
714                 comma = strchr(curr, ',');
715                 if (comma != NULL)
716                         n++;
717                 else
718                         break;
719         }
720         /* If no more commas and we're not at the end, there's one more value */
721         if (*curr != '\0')
722                 n++;
723
724         /* Empty string, return an error */
725         if (n == 0)
726                 return ENOENT;
727
728         /* Allocate space for enctypes array */
729         if ((krb5_enctypes = (int *) calloc(n, sizeof(int))) == NULL) {
730                 return ENOMEM;
731         }
732
733         /* Now parse each value into the array */
734         for (curr = enctypes, i = 0; curr && *curr != '\0'; curr = ++comma) {
735                 krb5_enctypes[i++] = atoi(curr);
736                 comma = strchr(curr, ',');
737                 if (comma == NULL)
738                         break;
739         }
740
741         num_krb5_enctypes = n;
742         return 0;
743 }
744
745 /*==========================*/
746 /*===  External routines ===*/
747 /*==========================*/
748
749 /*
750  * Attempt to find the best match for a credentials cache file
751  * given only a UID.  We really need more information, but we
752  * do the best we can.
753  *
754  * Returns:
755  *      void
756  */
757 void
758 gssd_setup_krb5_user_gss_ccache(uid_t uid, char *servername)
759 {
760         char                    buf[MAX_NETOBJ_SZ];
761         struct dirent           *d;
762
763         printerr(2, "getting credentials for client with uid %u for "
764                     "server %s\n", uid, servername);
765         memset(buf, 0, sizeof(buf));
766
767         if (gssd_find_existing_krb5_ccache(uid, &d)) {
768                 snprintf(buf, sizeof(buf), "FILE:%s/%s",
769                         ccachedir, d->d_name);
770                 free(d);
771         }
772         else
773                 snprintf(buf, sizeof(buf), "FILE:%s/%s%u",
774                         ccachedir, GSSD_DEFAULT_CRED_PREFIX, uid);
775         printerr(2, "using %s as credentials cache for client with "
776                     "uid %u for server %s\n", buf, uid, servername);
777         gssd_set_krb5_ccache_name(buf);
778 }
779
780 /*
781  * Let the gss code know where to find the machine credentials ccache.
782  *
783  * Returns:
784  *      void
785  */
786 void
787 gssd_setup_krb5_machine_gss_ccache(char *ccname)
788 {
789         printerr(2, "using %s as credentials cache for machine creds\n",
790                  ccname);
791         gssd_set_krb5_ccache_name(ccname);
792 }
793
794 /*
795  * The first time through this routine, go through the keytab and
796  * determine which keys we will try to use as machine credentials.
797  * Every time through this routine, try to obtain credentials using
798  * the keytab entries selected the first time through.
799  *
800  * Returns:
801  *      0 => obtained one or more credentials
802  *      nonzero => error
803  *
804  */
805
806 int
807 gssd_refresh_krb5_machine_creds(void)
808 {
809         krb5_context context = NULL;
810         krb5_keytab kt = NULL;;
811         krb5_error_code code;
812         int retval = -1;
813         struct gssd_k5_kt_princ *ple;
814         int gotone = 0;
815         static int processed_keytab = 0;
816
817
818         code = krb5_init_context(&context);
819         if (code) {
820                 printerr(0, "ERROR: %s while initializing krb5 in "
821                             "gssd_refresh_krb5_machine_creds\n",
822                          error_message(code));
823                 retval = code;
824                 goto out;
825         }
826
827         printerr(2, "Using keytab file '%s'\n", keytabfile);
828
829         if ((code = krb5_kt_resolve(context, keytabfile, &kt))) {
830                 printerr(0, "ERROR: %s while resolving keytab '%s'\n",
831                          error_message(code), keytabfile);
832                 goto out;
833         }
834
835         /* Only go through the keytab file once.  Only print messages once. */
836         if (gssd_k5_kt_princ_list == NULL && !processed_keytab) {
837                 processed_keytab = 1;
838                 gssd_process_krb5_keytab(context, kt, keytabfile);
839                 if (gssd_k5_kt_princ_list == NULL) {
840                         printerr(0, "ERROR: No usable keytab entries found in "
841                                     "keytab '%s'\n", keytabfile);
842                         printerr(0, "You must have a valid keytab entry for "
843                                     "%s/<your.host>@<YOUR.REALM> on MDT nodes, "
844                                     "and %s@<YOUR.REALM> on client nodes, in "
845                                     "keytab file %s ?\n",
846                                     GSSD_SERVICE_MDS, LUSTRE_ROOT_NAME,
847                                     keytabfile);
848                 }
849         }
850
851         /*
852          * If we don't have any keytab entries we liked, then we have a problem
853          */
854         if (gssd_k5_kt_princ_list == NULL) {
855                 retval = ENOENT;
856                 goto out;
857         }
858
859         /*
860          * Now go through the list of saved entries and get initial
861          * credentials for them (We can't do this while making the
862          * list because it messes up the keytab iteration cursor
863          * when we use the keytab to get credentials.)
864          */
865         for (ple = gssd_k5_kt_princ_list; ple; ple = ple->next) {
866                 if ((gssd_get_single_krb5_cred(context, kt, ple)) == 0) {
867                         gotone++;
868                 }
869         }
870         if (!gotone) {
871                 printerr(0, "ERROR: No usable machine credentials obtained\n");
872                 goto out;
873         }
874
875         retval = 0;
876   out:
877         if (kt) krb5_kt_close(context, kt);
878         krb5_free_context(context);
879
880         return retval;
881 }
882
883
884 /*
885  * Return an array of pointers to names of credential cache files
886  * which can be used to try to create gss contexts with a server.
887  *
888  * Returns:
889  *      0 => list is attached
890  *      nonzero => error
891  */
892 int
893 gssd_get_krb5_machine_cred_list(char ***list)
894 {
895         char **l;
896         int listinc = 10;
897         int listsize = listinc;
898         int i = 0;
899         int retval;
900         struct gssd_k5_kt_princ *ple;
901
902         /* Assume failure */
903         *list = NULL;
904
905         /* Refresh machine credentials */
906         retval = gssd_refresh_krb5_machine_creds();
907         if (retval)
908                 goto out;
909
910         l = malloc(listsize * sizeof(char *));
911         if (l == NULL) {
912                 retval = ENOMEM;
913                 goto out;
914         }
915
916         for (ple = gssd_k5_kt_princ_list; ple; ple = ple->next) {
917                 if (ple->ccname) {
918                         if (i + 1 > listsize) {
919                                 void *tmp;
920
921                                 listsize += listinc;
922                                 tmp = realloc(l, listsize * sizeof(char *));
923                                 if (tmp == NULL) {
924                                         retval = ENOMEM;
925                                         goto out_free;
926                                 }
927                                 l = tmp;
928                         }
929                         l[i] = strdup(ple->ccname);
930                         if (l[i++] == NULL) {
931                                 retval = ENOMEM;
932                                 goto out_free;
933                         }
934                 }
935         }
936         if (i > 0) {
937                 l[i] = NULL;
938                 *list = l;
939                 return 0;
940         }
941 out_free:
942         while (i > 0)
943                 free(l[i--]);
944         free(l);
945 out:
946         return retval;
947 }
948
949 /*
950  * Frees the list of names returned in get_krb5_machine_cred_list()
951  */
952 void
953 gssd_free_krb5_machine_cred_list(char **list)
954 {
955         char **n;
956
957         if (list == NULL)
958                 return;
959         for (n = list; n && *n; n++) {
960                 free(*n);
961         }
962         free(list);
963 }
964
965 /*
966  * Called upon exit.  Destroys machine credentials.
967  */
968 void
969 gssd_destroy_krb5_machine_creds(void)
970 {
971         krb5_context context;
972         krb5_error_code code = 0;
973         krb5_ccache ccache;
974         struct gssd_k5_kt_princ *ple;
975
976         code = krb5_init_context(&context);
977         if (code) {
978                 printerr(0, "ERROR: %s while initializing krb5\n",
979                          error_message(code));
980                 goto out;
981         }
982
983         for (ple = gssd_k5_kt_princ_list; ple; ple = ple->next) {
984                 if (!ple->ccname)
985                         continue;
986                 if ((code = krb5_cc_resolve(context, ple->ccname, &ccache))) {
987                         printerr(0, "WARNING: %s while resolving credential "
988                                     "cache '%s' for destruction\n",
989                                  error_message(code), ple->ccname);
990                         continue;
991                 }
992
993                 if ((code = krb5_cc_destroy(context, ccache))) {
994                         printerr(0, "WARNING: %s while destroying credential "
995                                     "cache '%s'\n",
996                                  error_message(code), ple->ccname);
997                 }
998         }
999   out:
1000         krb5_free_context(context);
1001 }
1002
1003 #if 0
1004 #ifdef HAVE_SET_ALLOWABLE_ENCTYPES
1005 /*
1006  * this routine obtains a credentials handle via gss_acquire_cred()
1007  * then calls gss_krb5_set_allowable_enctypes() to limit the encryption
1008  * types negotiated.
1009  *
1010  * Returns:
1011  *      0 => all went well
1012  *     -1 => there was an error
1013  */
1014
1015 int
1016 limit_krb5_enctypes(struct rpc_gss_sec *sec, uid_t uid)
1017 {
1018         unsigned int maj_stat, min_stat;
1019         gss_cred_id_t credh;
1020         gss_OID_set_desc  desired_mechs;
1021         krb5_enctype enctypes[] = {ENCTYPE_DES_CBC_CRC};
1022         int num_enctypes = sizeof(enctypes) / sizeof(enctypes[0]);
1023
1024         /* We only care about getting a krb5 cred */
1025         desired_mechs.count = 1;
1026         desired_mechs.elements = &krb5oid;
1027
1028         maj_stat = gss_acquire_cred(&min_stat, NULL, 0,
1029                                     &desired_mechs, GSS_C_INITIATE,
1030                                     &credh, NULL, NULL);
1031
1032         if (maj_stat != GSS_S_COMPLETE) {
1033                 pgsserr("gss_acquire_cred",
1034                         maj_stat, min_stat, &krb5oid);
1035                 return -1;
1036         }
1037
1038         /*
1039          * If we failed for any reason to produce global
1040          * list of supported enctypes, use local default here.
1041          */
1042         if (krb5_enctypes == NULL)
1043                 maj_stat = gss_set_allowable_enctypes(&min_stat, credh,
1044                                         &krb5oid, num_enctypes, &enctypes);
1045         else
1046                 maj_stat = gss_set_allowable_enctypes(&min_stat, credh,
1047                                         &krb5oid, num_krb5_enctypes,
1048                                         krb5_enctypes);
1049         if (maj_stat != GSS_S_COMPLETE) {
1050                 pgsserr("gss_set_allowable_enctypes",
1051                         maj_stat, min_stat, &krb5oid);
1052                 return -1;
1053         }
1054         sec->cred = credh;
1055
1056         return 0;
1057 }
1058 #endif  /* HAVE_SET_ALLOWABLE_ENCTYPES */
1059 #endif
1060
1061 #if 0
1062 /*
1063  * Obtain supported enctypes from kernel.
1064  * Set defaults if info is not available.
1065  */
1066 void
1067 gssd_obtain_kernel_krb5_info(void)
1068 {
1069         char enctype_file_name[128];
1070         char buf[1024];
1071         char enctypes[128];
1072         int nscanned;
1073         int fd;
1074         int use_default_enctypes = 0;
1075         int nbytes, numfields;
1076         char default_enctypes[] = "1,3,2";
1077         int code;
1078
1079         snprintf(enctype_file_name, sizeof(enctype_file_name),
1080                  "%s/%s", pipefs_dir, "krb5_info");
1081
1082         if ((fd = open(enctype_file_name, O_RDONLY)) == -1) {
1083                 printerr(1, "WARNING: gssd_obtain_kernel_krb5_info: "
1084                          "Unable to open '%s'. Unable to determine "
1085                          "Kerberos encryption types supported by the "
1086                          "kernel; using defaults (%s).\n",
1087                          enctype_file_name, default_enctypes);
1088                 use_default_enctypes = 1;
1089                 goto do_the_parse;
1090         }
1091         memset(buf, 0, sizeof(buf));
1092         if ((nbytes = read(fd, buf, sizeof(buf)-1)) == -1) {
1093                 printerr(0, "WARNING: gssd_obtain_kernel_krb5_info: "
1094                          "Error reading Kerberos encryption type "
1095                          "information file '%s'; using defaults (%s).\n",
1096                          enctype_file_name, default_enctypes);
1097                 use_default_enctypes = 1;
1098                 close(fd);
1099                 goto do_the_parse;
1100         }
1101         close(fd);
1102         numfields = sscanf(buf, "enctypes: %s\n%n", enctypes, &nscanned);
1103         if (numfields < 1) {
1104                 printerr(0, "WARNING: gssd_obtain_kernel_krb5_info: "
1105                          "error parsing Kerberos encryption type "
1106                          "information from file '%s'; using defaults (%s).\n",
1107                          enctype_file_name, default_enctypes);
1108                 use_default_enctypes = 1;
1109                 goto do_the_parse;
1110         }
1111         if (nbytes > nscanned) {
1112                 printerr(2, "gssd_obtain_kernel_krb5_info: "
1113                          "Ignoring extra information, '%s', from '%s'\n",
1114                          buf+nscanned, enctype_file_name);
1115                 goto do_the_parse;
1116         }
1117   do_the_parse:
1118         if (use_default_enctypes)
1119                 strcpy(enctypes, default_enctypes);
1120
1121         if ((code = parse_enctypes(enctypes)) != 0) {
1122                 printerr(0, "ERROR: gssd_obtain_kernel_krb5_info: "
1123                          "parse_enctypes%s failed with code %d\n",
1124                          use_default_enctypes ? " (with default enctypes)" : "",
1125                          code);
1126         }
1127 }
1128 #endif