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