Whamcloud - gitweb
LU-14462 gss: fix support for namespace in lgss_keyring
[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,
599                                                  "mds service entry: found a duplicated one, it's like a mis-configuration, skip\n");
600                                         goto next;
601                                 }
602
603                                 gssd_remove_ple(context, ple);
604                                 printerr(2, "mds service entry: replace an "
605                                          "existed non-mds one\n");
606                         }
607                 } else if (KEYTAB_ENTRY_MATCH(kte, LUSTRE_ROOT_NAME)) {
608                         ple = gssd_get_realm_ple((void *)&kte.principal->realm);
609                         if (ple) {
610                                 if (ple->fl_mds || ple->fl_root) {
611                                         printerr(2, "root entry: found a "
612                                                  "existed %s entry, skip\n",
613                                                  ple->fl_mds ? "mds" : "root");
614                                         goto next;
615                                 }
616
617                                 gssd_remove_ple(context, ple);
618                                 printerr(2, "root entry: replace an existed "
619                                          "non-mds non-root one\n");
620                         }
621                 } else {
622                         printerr(2, "We will NOT use this entry (%s)\n",
623                                 pname);
624                         goto next;
625                 }
626
627                 /* construct ple */
628                 printerr(2, "We will use this entry (%s)\n", pname);
629                 ple = gssd_create_ple(context, kte.principal);
630                 if (ple == NULL) {
631                         KRB5_FREE_UNPARSED_NAME(context, pname);
632                         goto out;
633                 }
634
635                 /* add proper flags */
636                 if (KEYTAB_ENTRY_MATCH(kte, GSSD_SERVICE_MDS))
637                         ple->fl_mds = 1;
638                 else if (KEYTAB_ENTRY_MATCH(kte, LUSTRE_ROOT_NAME))
639                         ple->fl_root = 1;
640
641                 /* enqueue */
642                 if (gssd_k5_kt_princ_list == NULL)
643                         gssd_k5_kt_princ_list = ple;
644                 else {
645                         ple->next = gssd_k5_kt_princ_list;
646                         gssd_k5_kt_princ_list = ple;
647                 }
648  next:
649                 KRB5_FREE_UNPARSED_NAME(context, pname);
650         }
651
652         if ((code = krb5_kt_end_seq_get(context, kt, &cursor))) {
653                 printerr(0, "WARNING: %s while ending keytab scan for "
654                             "keytab '%s'\n",
655                          error_message(code), kt_name);
656         }
657
658         retval = 0;
659   out:
660         return retval;
661 }
662
663 /*
664  * Depending on the version of Kerberos, we either need to use
665  * a private function, or simply set the environment variable.
666  */
667 static void
668 gssd_set_krb5_ccache_name(char *ccname)
669 {
670 #ifdef USE_GSS_KRB5_CCACHE_NAME
671         unsigned int    maj_stat, min_stat;
672
673         printerr(2, "using gss_krb5_ccache_name to select krb5 ccache %s\n",
674                  ccname);
675         maj_stat = gss_krb5_ccache_name(&min_stat, ccname, NULL);
676         if (maj_stat != GSS_S_COMPLETE) {
677                 printerr(0, "WARNING: gss_krb5_ccache_name with "
678                         "name '%s' failed (%s)\n",
679                         ccname, error_message(min_stat));
680         }
681 #else
682         /*
683          * Set the KRB5CCNAME environment variable to tell the krb5 code
684          * which credentials cache to use.  (Instead of using the private
685          * function above for which there is no generic gssapi
686          * equivalent.)
687          */
688         printerr(2, "using environment variable to select krb5 ccache %s\n",
689                  ccname);
690         setenv("KRB5CCNAME", ccname, 1);
691 #endif
692 }
693
694 /*
695  * Parse the supported encryption type information
696  */
697 static int
698 parse_enctypes(char *enctypes)
699 {
700         int n = 0;
701         char *curr, *comma;
702         int i;
703
704         /* Just in case this ever gets called more than once */
705         if (krb5_enctypes != NULL) {
706                 free(krb5_enctypes);
707                 krb5_enctypes = NULL;
708                 num_krb5_enctypes = 0;
709         }
710
711         /* count the number of commas */
712         for (curr = enctypes; curr && *curr != '\0'; curr = ++comma) {
713                 comma = strchr(curr, ',');
714                 if (comma != NULL)
715                         n++;
716                 else
717                         break;
718         }
719         /* If no more commas and we're not at the end, there's one more value */
720         if (*curr != '\0')
721                 n++;
722
723         /* Empty string, return an error */
724         if (n == 0)
725                 return ENOENT;
726
727         /* Allocate space for enctypes array */
728         if ((krb5_enctypes = (int *) calloc(n, sizeof(int))) == NULL) {
729                 return ENOMEM;
730         }
731
732         /* Now parse each value into the array */
733         for (curr = enctypes, i = 0; curr && *curr != '\0'; curr = ++comma) {
734                 krb5_enctypes[i++] = atoi(curr);
735                 comma = strchr(curr, ',');
736                 if (comma == NULL)
737                         break;
738         }
739
740         num_krb5_enctypes = n;
741         return 0;
742 }
743
744 /*==========================*/
745 /*===  External routines ===*/
746 /*==========================*/
747
748 /*
749  * Attempt to find the best match for a credentials cache file
750  * given only a UID.  We really need more information, but we
751  * do the best we can.
752  *
753  * Returns:
754  *      void
755  */
756 void
757 gssd_setup_krb5_user_gss_ccache(uid_t uid, char *servername)
758 {
759         char                    buf[MAX_NETOBJ_SZ];
760         struct dirent           *d;
761
762         printerr(2, "getting credentials for client with uid %u for "
763                     "server %s\n", uid, servername);
764         memset(buf, 0, sizeof(buf));
765
766         if (gssd_find_existing_krb5_ccache(uid, &d)) {
767                 snprintf(buf, sizeof(buf), "FILE:%s/%s",
768                         ccachedir, d->d_name);
769                 free(d);
770         }
771         else
772                 snprintf(buf, sizeof(buf), "FILE:%s/%s%u",
773                         ccachedir, GSSD_DEFAULT_CRED_PREFIX, uid);
774         printerr(2, "using %s as credentials cache for client with "
775                     "uid %u for server %s\n", buf, uid, servername);
776         gssd_set_krb5_ccache_name(buf);
777 }
778
779 /*
780  * Let the gss code know where to find the machine credentials ccache.
781  *
782  * Returns:
783  *      void
784  */
785 void
786 gssd_setup_krb5_machine_gss_ccache(char *ccname)
787 {
788         printerr(2, "using %s as credentials cache for machine creds\n",
789                  ccname);
790         gssd_set_krb5_ccache_name(ccname);
791 }
792
793 /*
794  * The first time through this routine, go through the keytab and
795  * determine which keys we will try to use as machine credentials.
796  * Every time through this routine, try to obtain credentials using
797  * the keytab entries selected the first time through.
798  *
799  * Returns:
800  *      0 => obtained one or more credentials
801  *      nonzero => error
802  *
803  */
804
805 int
806 gssd_refresh_krb5_machine_creds(void)
807 {
808         krb5_context context = NULL;
809         krb5_keytab kt = NULL;;
810         krb5_error_code code;
811         int retval = -1;
812         struct gssd_k5_kt_princ *ple;
813         int gotone = 0;
814         static int processed_keytab = 0;
815
816
817         code = krb5_init_context(&context);
818         if (code) {
819                 printerr(0, "ERROR: %s while initializing krb5 in "
820                             "gssd_refresh_krb5_machine_creds\n",
821                          error_message(code));
822                 retval = code;
823                 goto out;
824         }
825
826         printerr(2, "Using keytab file '%s'\n", keytabfile);
827
828         if ((code = krb5_kt_resolve(context, keytabfile, &kt))) {
829                 printerr(0, "ERROR: %s while resolving keytab '%s'\n",
830                          error_message(code), keytabfile);
831                 goto out;
832         }
833
834         /* Only go through the keytab file once.  Only print messages once. */
835         if (gssd_k5_kt_princ_list == NULL && !processed_keytab) {
836                 processed_keytab = 1;
837                 gssd_process_krb5_keytab(context, kt, keytabfile);
838                 if (gssd_k5_kt_princ_list == NULL) {
839                         printerr(0, "ERROR: No usable keytab entries found in "
840                                     "keytab '%s'\n", keytabfile);
841                         printerr(0, "You must have a valid keytab entry for "
842                                     "%s/<your.host>@<YOUR.REALM> on MDT nodes, "
843                                     "and %s@<YOUR.REALM> on client nodes, in "
844                                     "keytab file %s ?\n",
845                                     GSSD_SERVICE_MDS, LUSTRE_ROOT_NAME,
846                                     keytabfile);
847                 }
848         }
849
850         /*
851          * If we don't have any keytab entries we liked, then we have a problem
852          */
853         if (gssd_k5_kt_princ_list == NULL) {
854                 retval = ENOENT;
855                 goto out;
856         }
857
858         /*
859          * Now go through the list of saved entries and get initial
860          * credentials for them (We can't do this while making the
861          * list because it messes up the keytab iteration cursor
862          * when we use the keytab to get credentials.)
863          */
864         for (ple = gssd_k5_kt_princ_list; ple; ple = ple->next) {
865                 if ((gssd_get_single_krb5_cred(context, kt, ple)) == 0) {
866                         gotone++;
867                 }
868         }
869         if (!gotone) {
870                 printerr(0, "ERROR: No usable machine credentials obtained\n");
871                 goto out;
872         }
873
874         retval = 0;
875   out:
876         if (kt) krb5_kt_close(context, kt);
877         krb5_free_context(context);
878
879         return retval;
880 }
881
882
883 /*
884  * Return an array of pointers to names of credential cache files
885  * which can be used to try to create gss contexts with a server.
886  *
887  * Returns:
888  *      0 => list is attached
889  *      nonzero => error
890  */
891 int
892 gssd_get_krb5_machine_cred_list(char ***list)
893 {
894         char **l;
895         int listinc = 10;
896         int listsize = listinc;
897         int i = 0;
898         int retval;
899         struct gssd_k5_kt_princ *ple;
900
901         /* Assume failure */
902         *list = NULL;
903
904         /* Refresh machine credentials */
905         retval = gssd_refresh_krb5_machine_creds();
906         if (retval)
907                 goto out;
908
909         l = malloc(listsize * sizeof(char *));
910         if (l == NULL) {
911                 retval = ENOMEM;
912                 goto out;
913         }
914
915         for (ple = gssd_k5_kt_princ_list; ple; ple = ple->next) {
916                 if (ple->ccname) {
917                         if (i + 1 > listsize) {
918                                 void *tmp;
919
920                                 listsize += listinc;
921                                 tmp = realloc(l, listsize * sizeof(char *));
922                                 if (tmp == NULL) {
923                                         retval = ENOMEM;
924                                         goto out_free;
925                                 }
926                                 l = tmp;
927                         }
928                         l[i] = strdup(ple->ccname);
929                         if (l[i++] == NULL) {
930                                 retval = ENOMEM;
931                                 goto out_free;
932                         }
933                 }
934         }
935         if (i > 0) {
936                 l[i] = NULL;
937                 *list = l;
938                 return 0;
939         }
940 out_free:
941         while (i > 0)
942                 free(l[i--]);
943         free(l);
944 out:
945         return retval;
946 }
947
948 /*
949  * Frees the list of names returned in get_krb5_machine_cred_list()
950  */
951 void
952 gssd_free_krb5_machine_cred_list(char **list)
953 {
954         char **n;
955
956         if (list == NULL)
957                 return;
958         for (n = list; n && *n; n++) {
959                 free(*n);
960         }
961         free(list);
962 }
963
964 /*
965  * Called upon exit.  Destroys machine credentials.
966  */
967 void
968 gssd_destroy_krb5_machine_creds(void)
969 {
970         krb5_context context;
971         krb5_error_code code = 0;
972         krb5_ccache ccache;
973         struct gssd_k5_kt_princ *ple;
974
975         code = krb5_init_context(&context);
976         if (code) {
977                 printerr(0, "ERROR: %s while initializing krb5\n",
978                          error_message(code));
979                 goto out;
980         }
981
982         for (ple = gssd_k5_kt_princ_list; ple; ple = ple->next) {
983                 if (!ple->ccname)
984                         continue;
985                 if ((code = krb5_cc_resolve(context, ple->ccname, &ccache))) {
986                         printerr(0, "WARNING: %s while resolving credential "
987                                     "cache '%s' for destruction\n",
988                                  error_message(code), ple->ccname);
989                         continue;
990                 }
991
992                 if ((code = krb5_cc_destroy(context, ccache))) {
993                         printerr(0, "WARNING: %s while destroying credential "
994                                     "cache '%s'\n",
995                                  error_message(code), ple->ccname);
996                 }
997         }
998   out:
999         krb5_free_context(context);
1000 }
1001
1002