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