Whamcloud - gitweb
LU-16977 utils: access_log_reader accesses beyond batch array
[fs/lustre-release.git] / lustre / utils / gss / lgss_krb5_utils.c
1 /*
2  * Modifications for Lustre
3  *
4  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
5  *
6  * Author: Eric Mei <ericm@clusterfs.com>
7  */
8
9 /*
10  *  Adapted in part from MIT Kerberos 5-1.2.1 slave/kprop.c and from
11  *  http://docs.sun.com/?p=/doc/816-1331/6m7oo9sms&a=view
12  *
13  *  Copyright (c) 2002-2004 The Regents of the University of Michigan.
14  *  All rights reserved.
15  *
16  *  Andy Adamson <andros@umich.edu>
17  *  J. Bruce Fields <bfields@umich.edu>
18  *  Marius Aamodt Eriksen <marius@umich.edu>
19  *  Kevin Coffman <kwc@umich.edu>
20  */
21
22 /*
23  * slave/kprop.c
24  *
25  * Copyright 1990,1991 by the Massachusetts Institute of Technology.
26  * All Rights Reserved.
27  *
28  * Export of this software from the United States of America may
29  *   require a specific license from the United States Government.
30  *   It is the responsibility of any person or organization contemplating
31  *   export to obtain such a license before exporting.
32  *
33  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
34  * distribute this software and its documentation for any purpose and
35  * without fee is hereby granted, provided that the above copyright
36  * notice appear in all copies and that both that copyright notice and
37  * this permission notice appear in supporting documentation, and that
38  * the name of M.I.T. not be used in advertising or publicity pertaining
39  * to distribution of the software without specific, written prior
40  * permission.  Furthermore if you modify this software you must label
41  * your software as modified software and not distribute it in such a
42  * fashion that it might be confused with the original M.I.T. software.
43  * M.I.T. makes no representations about the suitability of
44  * this software for any purpose.  It is provided "as is" without express
45  * or implied warranty.
46  */
47
48 /*
49  * Copyright 1994 by OpenVision Technologies, Inc.
50  *
51  * Permission to use, copy, modify, distribute, and sell this software
52  * and its documentation for any purpose is hereby granted without fee,
53  * provided that the above copyright notice appears in all copies and
54  * that both that copyright notice and this permission notice appear in
55  * supporting documentation, and that the name of OpenVision not be used
56  * in advertising or publicity pertaining to distribution of the software
57  * without specific, written prior permission. OpenVision makes no
58  * representations about the suitability of this software for any
59  * purpose.  It is provided "as is" without express or implied warranty.
60  *
61  * OPENVISION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
62  * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
63  * EVENT SHALL OPENVISION BE LIABLE FOR ANY SPECIAL, INDIRECT OR
64  * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF
65  * USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
66  * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
67  * PERFORMANCE OF THIS SOFTWARE.
68  */
69 /*
70   krb5_util.c
71
72   Copyright (c) 2004 The Regents of the University of Michigan.
73   All rights reserved.
74
75   Redistribution and use in source and binary forms, with or without
76   modification, are permitted provided that the following conditions
77   are met:
78
79   1. Redistributions of source code must retain the above copyright
80      notice, this list of conditions and the following disclaimer.
81   2. Redistributions in binary form must reproduce the above copyright
82      notice, this list of conditions and the following disclaimer in the
83      documentation and/or other materials provided with the distribution.
84   3. Neither the name of the University nor the names of its
85      contributors may be used to endorse or promote products derived
86      from this software without specific prior written permission.
87
88   THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
89   WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
90   MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
91   DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
92   FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
93   CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
94   SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
95   BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
96   LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
97   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
98   SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
99
100 */
101
102 #ifndef _GNU_SOURCE
103 #define _GNU_SOURCE
104 #endif
105 #include "config.h"
106 #include <sys/param.h>
107 #include <sys/types.h>
108 #include <sys/stat.h>
109 #include <grp.h>
110 #include <pwd.h>
111 #include <sys/mman.h>
112 #include <sys/wait.h>
113 #include <sys/utsname.h>
114 #include <sys/socket.h>
115 #include <arpa/inet.h>
116
117 #include <unistd.h>
118 #include <stdio.h>
119 #include <stdlib.h>
120 #include <string.h>
121 #ifdef HAVE_NETDB_H
122 # include <netdb.h>
123 #endif
124 #include <dirent.h>
125 #include <fcntl.h>
126 #include <errno.h>
127 #include <time.h>
128 #include <gssapi/gssapi.h>
129 #include <gssapi/gssapi_krb5.h>
130 #include <krb5.h>
131
132 #include "lsupport.h"
133 #include "lgss_utils.h"
134 #include "lgss_krb5_utils.h"
135
136 static void lgss_krb5_mutex_lock(void)
137 {
138         if (lgss_mutex_lock(LGSS_MUTEX_KRB5)) {
139                 logmsg(LL_ERR, "can't lock process, abort!\n");
140                 exit(-1);
141         }
142 }
143
144 static void lgss_krb5_mutex_unlock(void)
145 {
146         if (lgss_mutex_unlock(LGSS_MUTEX_KRB5)) {
147                 logmsg(LL_WARN, "can't unlock process, other processes "
148                        "might need to wait long time\n");
149         }
150 }
151
152 #define krb5_err_msg(code)      error_message(code)
153
154 const char *krb5_cred_root_suffix  = "lustre_root";
155 const char *krb5_cred_mds_suffix   = "lustre_mds";
156 const char *krb5_cred_oss_suffix   = "lustre_oss";
157
158 char    *krb5_this_realm        = NULL;
159 char    *krb5_keytab_file       = "/etc/krb5.keytab";
160
161 static int lgss_krb5_set_ccache_name(const char *ccname)
162 {
163         unsigned int    maj_stat, min_stat;
164
165         maj_stat = gss_krb5_ccache_name(&min_stat, ccname, NULL);
166         if (maj_stat != GSS_S_COMPLETE) {
167                 logmsg(LL_ERR, "failed to set ccache name\n");
168                 return -1;
169         }
170
171         logmsg(LL_DEBUG, "set cc: %s\n", ccname);
172         return 0;
173 }
174
175 static
176 int lgss_krb5_get_local_realm(void)
177 {
178         krb5_context    context = NULL;
179         krb5_error_code code;
180         int             retval = -1;
181
182         if (krb5_this_realm != NULL)
183                 return 0;
184
185         code = krb5_init_context(&context);
186         if (code) {
187                 logmsg(LL_ERR, "init ctx: %s\n", krb5_err_msg(code));
188                 return -1;
189         }
190
191         code = krb5_get_default_realm(context, &krb5_this_realm);
192         if (code) {
193                 logmsg(LL_ERR, "get default realm: %s\n", krb5_err_msg(code));
194                 goto out;
195         }
196
197         logmsg(LL_DEBUG, "Local realm: %s\n", krb5_this_realm);
198         retval = 0;
199 out:
200         krb5_free_context(context);
201         return retval;
202 }
203
204 static
205 int princ_is_local_realm(krb5_context ctx, krb5_principal princ)
206 {
207         return (lgss_krb5_strcasecmp(krb5_princ_realm(ctx, princ),
208                                      krb5_this_realm) == 0);
209 }
210
211 static
212 int svc_princ_verify_host(krb5_context ctx,
213                           krb5_principal princ,
214                           uint64_t self_nid,
215                           loglevel_t loglevel)
216 {
217         struct utsname utsbuf;
218         struct hostent *host;
219         const int max_namelen = 512;
220         char namebuf[max_namelen];
221         char *h_name;
222
223         if (krb5_princ_component(ctx, princ, 1) == NULL) {
224                 logmsg(loglevel, "service principal has no host part\n");
225                 return -1;
226         }
227
228         if (self_nid != 0) {
229                 if (lnet_nid2hostname(self_nid, namebuf, max_namelen)) {
230                         logmsg(loglevel,
231                                "can't resolve hostname from nid %"PRIx64"\n",
232                                self_nid);
233                         return -1;
234                 }
235                 h_name = namebuf;
236         } else {
237                 if (uname(&utsbuf)) {
238                         logmsg(loglevel, "get UTS name: %s\n", strerror(errno));
239                         return -1;
240                 }
241
242                 host = gethostbyname(utsbuf.nodename);
243                 if (host == NULL) {
244                         logmsg(loglevel, "failed to get local hostname\n");
245                         return -1;
246                 }
247                 h_name = host->h_name;
248         }
249
250         if (lgss_krb5_strcasecmp(krb5_princ_component(ctx, princ, 1),
251                                  h_name)) {
252                 logmsg(loglevel, "service principal: hostname %.*s "
253                        "doesn't match localhost %s\n",
254                        krb5_princ_component(ctx, princ, 1)->length,
255                        krb5_princ_component(ctx, princ, 1)->data,
256                        h_name);
257                 return -1;
258         }
259
260         return 0;
261 }
262
263 static int lkrb5_cc_check_tgt_princ(krb5_context ctx,
264                              krb5_ccache ccache,
265                              krb5_principal princ,
266                              unsigned int flag,
267                              uint64_t self_nid)
268 {
269         const char     *princ_name;
270
271         logmsg(LL_DEBUG, "principal: realm %.*s, type %d, size %d, name %.*s\n",
272                krb5_princ_realm(ctx, princ)->length,
273                krb5_princ_realm(ctx, princ)->data,
274                krb5_princ_type(ctx, princ),
275                krb5_princ_size(ctx, princ),
276                krb5_princ_name(ctx, princ)->length,
277                krb5_princ_name(ctx, princ)->data);
278
279         /* check type */
280         if (krb5_princ_type(ctx, princ) != KRB5_NT_PRINCIPAL) {
281                 logmsg(LL_WARN, "principal type %d is not I want\n",
282                        krb5_princ_type(ctx, princ));
283                 return -1;
284         }
285
286         /* check local realm */
287         if (!princ_is_local_realm(ctx, princ)) {
288                 logmsg(LL_WARN, "principal realm %.*s not local: %s\n",
289                        krb5_princ_realm(ctx, princ)->length,
290                        krb5_princ_realm(ctx, princ)->data,
291                        krb5_this_realm);
292                 return -1;
293         }
294
295         /* check principal name, give priority to MDT/OST cred over ROOT */
296         if (flag & LGSS_ROOT_CRED_MDT)
297                 princ_name = LGSS_SVC_MDS_STR;
298         else if (flag & LGSS_ROOT_CRED_OST)
299                 princ_name = LGSS_SVC_OSS_STR;
300         else if (flag & LGSS_ROOT_CRED_ROOT)
301                 princ_name = LGSS_USR_ROOT_STR;
302         else
303                 return -1;
304
305         if (lgss_krb5_strcmp(krb5_princ_name(ctx, princ), princ_name) &&
306             (strcmp(princ_name, LGSS_USR_ROOT_STR) ||
307             lgss_krb5_strcmp(krb5_princ_name(ctx, princ), LGSS_SVC_HOST_STR))) {
308                 logmsg(LL_WARN, "%.*s: we expect %s instead\n",
309                        krb5_princ_name(ctx, princ)->length,
310                        krb5_princ_name(ctx, princ)->data,
311                        princ_name);
312                 return -1;
313         }
314
315         /*
316          * verify the hostname part of the principal, except we do allow
317          * lustre_root without binding to a host.
318          */
319         if (krb5_princ_component(ctx, princ, 1) == NULL) {
320                 if (flag != LGSS_ROOT_CRED_ROOT) {
321                         logmsg(LL_WARN, "%.*s: missing hostname\n",
322                                krb5_princ_name(ctx, princ)->length,
323                                krb5_princ_name(ctx, princ)->data);
324                         return -1;
325                 }
326         } else {
327                 if (svc_princ_verify_host(ctx, princ, self_nid, LL_WARN)) {
328                         logmsg(LL_DEBUG, "%.*s: doesn't belong to this node\n",
329                                krb5_princ_name(ctx, princ)->length,
330                                krb5_princ_name(ctx, princ)->data);
331                         return -1;
332                 }
333         }
334
335         logmsg(LL_TRACE, "principal is OK\n");
336         return 0;
337 }
338
339 static inline int lgss_krb5_get_default_ccache_name(krb5_context ctx,
340                                                     char *ccname, int size)
341 {
342         if (snprintf(ccname, size, "%s", krb5_cc_default_name(ctx)) >= size)
343                 return -ENAMETOOLONG;
344
345         return 0;
346 }
347
348 /**
349  * compose the TGT cc name, abiding to system configuration.
350  */
351 static int get_root_tgt_ccname(krb5_context ctx, char *ccname, int size)
352 {
353         return lgss_krb5_get_default_ccache_name(ctx, ccname, size);
354 }
355
356 static int switch_identity(uid_t uid)
357 {
358         struct passwd *pw;
359         int rc;
360
361         /* drop list of supp groups */
362         rc = setgroups(0, NULL);
363         if (rc == -1) {
364                 logmsg(LL_ERR, "cannot drop list of supp groups: %s\n",
365                        strerror(errno));
366                 rc = -errno;
367                 goto end_switch;
368         }
369
370         pw = getpwuid(uid);
371         if (!pw) {
372                 logmsg(LL_ERR, "cannot get pw entry for %u: %s\n",
373                        uid, strerror(errno));
374                 rc = -errno;
375                 goto end_switch;
376         }
377
378         rc = setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid);
379         if (rc == -1) {
380                 logmsg(LL_ERR, "cannot set real gid to %u: %s\n",
381                        pw->pw_gid, strerror(errno));
382                 rc = -errno;
383                 goto end_switch;
384         }
385
386         rc = setresuid(uid, uid, uid);
387         if (rc == -1) {
388                 logmsg(LL_ERR, "cannot set real uid to %u: %s\n",
389                        uid, strerror(errno));
390                 rc = -errno;
391                 goto end_switch;
392         }
393
394 end_switch:
395         return rc;
396 }
397
398 static int acquire_user_cred_and_check(char *ccname)
399 {
400         gss_OID mech = (gss_OID)&krb5oid;
401         gss_OID_set_desc desired_mechs = { 1, mech };
402         gss_cred_id_t gss_cred;
403         OM_uint32 maj_stat, min_stat, lifetime;
404         int rc = 0;
405
406         if (lgss_krb5_set_ccache_name(ccname)) {
407                 logmsg(LL_ERR, "cannot set ccache name: %s\n", ccname);
408                 return -1;
409         }
410
411         maj_stat = gss_acquire_cred(&min_stat, GSS_C_NO_NAME, GSS_C_INDEFINITE,
412                                     &desired_mechs, GSS_C_INITIATE,
413                                     &gss_cred, NULL, NULL);
414         if (maj_stat != GSS_S_COMPLETE) {
415                 logmsg_gss(LL_INFO, mech, maj_stat, min_stat,
416                            "failed gss_acquire_cred");
417                 return -1;
418         }
419
420         /* force validation of cred to check for expiry */
421         maj_stat = gss_inquire_cred(&min_stat, gss_cred,
422                                     NULL, &lifetime, NULL, NULL);
423         if (maj_stat != GSS_S_COMPLETE) {
424                 logmsg_gss(LL_INFO, mech, maj_stat, min_stat,
425                            "failed gss_inquire_cred");
426                 rc = -1;
427         }
428
429         if (gss_cred != GSS_C_NO_CREDENTIAL)
430                 gss_release_cred(&min_stat, &gss_cred);
431
432         return rc;
433 }
434
435 static int filter_krb5_ccache(const struct dirent *d)
436 {
437         if (strstr(d->d_name, LGSS_DEFAULT_CRED_PREFIX))
438                 return 1;
439         else
440                 return 0;
441 }
442
443 /*
444  * Look in dirname for a possibly valid ccache for uid.
445  *
446  * Returns 0 if a potential entry is found.
447  * Otherwise, a negative errno is returned.
448  */
449 static int find_existing_krb5_ccache(uid_t uid, char *dir,
450                                      char *ccname, int size)
451 {
452         struct dirent **namelist;
453         int found = 0;
454         struct stat tmp_stat;
455         char dirname[PATH_MAX], buf[PATH_MAX];
456         int num_ents, i, j = 0, rc = -1;
457
458         /* provided dir can be a pattern */
459         for (i = 0; dir[i] != '\0'; i++) {
460                 switch (dir[i]) {
461                 case '%':
462                         switch (dir[i + 1]) {
463                         case 'U':
464                                 j += sprintf(dirname + j, "%lu",
465                                              (unsigned long)uid);
466                                 i++;
467                                 break;
468                         }
469                         break;
470                 default:
471                         dirname[j++] = dir[i];
472                         break;
473                 }
474         }
475         dirname[j] = '\0';
476
477         num_ents = scandir(dirname, &namelist, filter_krb5_ccache, 0);
478         if (num_ents < 0) {
479                 logmsg(LL_INFO, "scandir %s failed: %s\n",
480                        dirname, strerror(errno));
481                 goto end_find;
482         }
483
484         for (i = 0; i < num_ents; i++) {
485                 if (found)
486                         goto next_find;
487
488                 if (snprintf(buf, sizeof(buf), "%s/%s",
489                              dirname, namelist[i]->d_name) >= sizeof(buf)) {
490                         logmsg(LL_INFO, "%s/%s name too long\n",
491                                dirname, namelist[i]->d_name);
492                         goto next_find;
493                 }
494
495                 if (lstat(buf, &tmp_stat)) {
496                         logmsg(LL_INFO, "lstat %s failed: %s\n",
497                                buf, strerror(errno));
498                         goto next_find;
499                 }
500
501                 /* we only look for files as credentials caches */
502                 if (!S_ISREG(tmp_stat.st_mode))
503                         goto next_find;
504
505                 /* make sure it is owned by uid */
506                 if (tmp_stat.st_uid != uid) {
507                         logmsg(LL_INFO, "%s not owned by %u\n",
508                                buf, uid);
509                         goto next_find;
510                 }
511
512                 /* check user has rw perms */
513                 if (!(tmp_stat.st_mode & S_IRUSR &&
514                       tmp_stat.st_mode & S_IWUSR)) {
515                         logmsg(LL_INFO, "%s does not have rw perms for %u\n",
516                                buf, uid);
517                         goto next_find;
518                 }
519
520                 if (snprintf(ccname, size, "FILE:%s", buf) >= size) {
521                         logmsg(LL_INFO, "FILE:%s name too long\n", buf);
522                         goto next_find;
523                 }
524
525                 rc = acquire_user_cred_and_check(ccname);
526                 if (!rc)
527                         found = 1;
528
529 next_find:
530                 free(namelist[i]);
531         }
532         free(namelist);
533
534 end_find:
535         return rc;
536 }
537
538 /**
539  * Compose the TGT cc name for user, needs to fork process and switch identity.
540  * For that reason, ccname buffer passed in must be mmapped with MAP_SHARED.
541  */
542 static int get_user_tgt_ccname(struct lgss_cred *cred, krb5_context ctx,
543                                char *ccname, int size)
544 {
545         pid_t child;
546         int status, rc = 0;
547
548         /* fork to not change identity in main process, it needs to stay root
549          * in order to proceed to ioctls
550          */
551         child = fork();
552         if (child == -1) {
553                 logmsg(LL_ERR, "cannot fork child for user %u: %s\n",
554                        cred->lc_uid, strerror(errno));
555                 rc = -errno;
556         } else if (child == 0) {
557                 /* switch identity */
558                 rc = switch_identity(cred->lc_uid);
559                 if (rc)
560                         exit(1);
561
562                 /* getting default ccname requires impersonating user */
563                 rc = lgss_krb5_get_default_ccache_name(ctx, ccname, size);
564                 if (rc) {
565                         logmsg(LL_ERR,
566                                "cannot get default ccname for user %u\n",
567                                cred->lc_uid);
568                         exit(1);
569                 }
570
571                 /* job done for child */
572                 exit(0);
573         } else {
574                 logmsg(LL_TRACE, "forked child %d\n", child);
575                 if (wait(&status) < 0) {
576                         logmsg(LL_ERR, "wait child %d failed: %s\n",
577                                child, strerror(errno));
578                         return -errno;
579                 }
580                 if (!WIFEXITED(status)) {
581                         logmsg(LL_ERR, "child %d terminated with %d\n",
582                                child, status);
583                         return status;
584                 }
585         }
586
587         /* try ccname as fetched by child */
588         rc = acquire_user_cred_and_check(ccname);
589         if (!rc)
590                 /* user's creds found in default ccache */
591                 goto end_ccache;
592
593         /* fallback: look at every file matching
594          * - /tmp/ *krb5cc*
595          * - /run/user/<uid>/ *krb5cc*
596          */
597         rc = find_existing_krb5_ccache(cred->lc_uid, LGSS_DEFAULT_CRED_DIR,
598                                        ccname, size);
599         if (!rc)
600                 /* user's creds found in LGSS_DEFAULT_CRED_DIR */
601                 goto end_ccache;
602
603         rc = find_existing_krb5_ccache(cred->lc_uid, LGSS_USER_CRED_DIR,
604                                        ccname, size);
605         if (!rc)
606                 /* user's creds found in LGSS_USER_CRED_DIR */
607                 goto end_ccache;
608
609         rc = -ENODATA;
610
611 end_ccache:
612         return rc;
613 }
614
615 /**
616  * find out whether current TGT cache is valid or not
617  */
618 static int lkrb5_check_root_tgt_cc(krb5_context ctx, unsigned int flag,
619                                    uint64_t self_nid)
620 {
621         krb5_ccache tgt_ccache;
622         krb5_principal princ;
623         krb5_cc_cursor cursor;
624         krb5_error_code code;
625         char ccname[PATH_MAX];
626         krb5_creds cred;
627         time_t now;
628         int found;
629
630         found = get_root_tgt_ccname(ctx, ccname, sizeof(ccname));
631         if (found)
632                 return found;
633         logmsg(LL_DEBUG, "root krb5 TGT ccname: %s\n", ccname);
634
635         /* prepare parsing the cache file */
636         code = krb5_cc_resolve(ctx, ccname, &tgt_ccache);
637         if (code) {
638                 logmsg(LL_ERR, "resolve krb5 cc %s: %s\n",
639                        ccname, krb5_err_msg(code));
640                 goto out_fail;
641         }
642
643         /* checks the principal */
644         code = krb5_cc_get_principal(ctx, tgt_ccache, &princ);
645         if (code) {
646                 logmsg(LL_ERR, "get cc principal: %s\n", krb5_err_msg(code));
647                 goto out_cc;
648         }
649
650         if (lkrb5_cc_check_tgt_princ(ctx, tgt_ccache, princ, flag, self_nid))
651                 goto out_princ;
652
653         /*
654          * find a valid entry
655          */
656         code = krb5_cc_start_seq_get(ctx, tgt_ccache, &cursor);
657         if (code) {
658                 logmsg(LL_ERR, "start cc iteration: %s\n", krb5_err_msg(code));
659                 goto out_princ;
660         }
661
662         now = time(0);
663         do {
664                 krb5_timestamp  duration, delta;
665
666                 code = krb5_cc_next_cred(ctx, tgt_ccache, &cursor, &cred);
667                 if (code != 0)
668                         break;
669
670                 logmsg(LL_DEBUG,
671                        "cred: server realm %.*s, type %d, name %.*s; time (%lld-%lld, renew till %lld), valid %lld\n",
672                        krb5_princ_realm(ctx, cred.server)->length,
673                        krb5_princ_realm(ctx, cred.server)->data,
674                        krb5_princ_type(ctx, cred.server),
675                        krb5_princ_name(ctx, cred.server)->length,
676                        krb5_princ_name(ctx, cred.server)->data,
677                        (long long)cred.times.starttime,
678                        (long long)cred.times.endtime,
679                        (long long)cred.times.renew_till,
680                        (long long)(cred.times.endtime - now));
681
682                 /* FIXME
683                  * we found the princ type is always 0 (KRB5_NT_UNKNOWN), why???
684                  */
685
686                 /* FIXME how about inter-realm TGT??? FIXME */
687                 if (lgss_krb5_strcasecmp(krb5_princ_name(ctx, cred.server),
688                                          "krbtgt"))
689                         continue;
690
691                 if (lgss_krb5_strcasecmp(krb5_princ_realm(ctx, cred.server),
692                                          krb5_this_realm))
693                         continue;
694
695                 /* check validity of time */
696                 delta = 60 * 30; /* half an hour */
697                 duration = cred.times.endtime - cred.times.starttime;
698                 if (duration / 4 < delta)
699                         delta = duration / 4;
700
701                 if (cred.times.starttime <= now &&
702                     cred.times.endtime >= now + delta) {
703                         found = 1;
704                         break;
705                 }
706         } while (1);
707
708         krb5_cc_end_seq_get(ctx, tgt_ccache, &cursor);
709 out_princ:
710         krb5_free_principal(ctx, princ);
711 out_cc:
712         krb5_cc_close(ctx, tgt_ccache);
713
714         if (found) {
715                 logmsg(LL_TRACE, "found good TGT cache\n");
716                 return lgss_krb5_set_ccache_name(ccname);
717         }
718
719 out_fail:
720         logmsg(LL_TRACE, "did not find good TGT cache\n");
721         return -1;
722 }
723
724 static int lkrb5_get_root_tgt_keytab(krb5_context ctx, krb5_keytab kt,
725                                      krb5_principal princ, const char *ccname)
726 {
727         krb5_get_init_creds_opt opts;
728         krb5_creds cred;
729         krb5_ccache tgt_ccache;
730         krb5_error_code code;
731         int rc = -1;
732
733         krb5_get_init_creds_opt_init(&opts);
734         krb5_get_init_creds_opt_set_address_list(&opts, NULL);
735         /*
736          * by default krb5 library obtain ticket with lifetime shorter
737          * than the max value. we can change it here if we want. but
738          * seems not necessary now.
739          *
740          * krb5_get_init_creds_opt_set_tkt_life(&opts, very-long-time);
741          *
742          */
743
744         /*
745          * obtain TGT and store into cache
746          */
747         code = krb5_get_init_creds_keytab(ctx, &cred, princ, kt,
748                                           0, NULL, &opts);
749         if (code) {
750                 logmsg(LL_ERR,
751                        "failed to get root TGT for principal %.*s: %s\n",
752                        krb5_princ_name(ctx, princ)->length,
753                        krb5_princ_name(ctx, princ)->data,
754                        krb5_err_msg(code));
755                 return -1;
756         }
757
758         code = krb5_cc_resolve(ctx, ccname, &tgt_ccache);
759         if (code) {
760                 logmsg(LL_ERR, "resolve cc %s: %s\n",
761                        ccname, krb5_err_msg(code));
762                 goto out_cred;
763         }
764
765         code = krb5_cc_initialize(ctx, tgt_ccache, princ);
766         if (code) {
767                 logmsg(LL_ERR, "initialize cc %s: %s\n",
768                        ccname, krb5_err_msg(code));
769                 goto out_cc;
770         }
771
772         code = krb5_cc_store_cred(ctx, tgt_ccache, &cred);
773         if (code) {
774                 logmsg(LL_ERR, "store cred to cc %s: %s\n",
775                        ccname, krb5_err_msg(code));
776                 goto out_cc;
777         }
778
779         logmsg(LL_INFO, "installed TGT of %.*s in cc %s\n",
780                krb5_princ_name(ctx, princ)->length,
781                krb5_princ_name(ctx, princ)->data,
782                ccname);
783
784         rc = lgss_krb5_set_ccache_name(ccname);
785
786 out_cc:
787         krb5_cc_close(ctx, tgt_ccache);
788 out_cred:
789         krb5_free_cred_contents(ctx, &cred);
790         return rc;
791 }
792
793 /*
794  * obtain a new root TGT
795  */
796 static int lkrb5_refresh_root_tgt_cc(krb5_context ctx, unsigned int root_flags,
797                                      uint64_t self_nid)
798 {
799         krb5_keytab kt;
800         krb5_keytab_entry kte;
801         krb5_kt_cursor cursor;
802         krb5_principal princ = NULL;
803         krb5_error_code code;
804         char ccname[PATH_MAX];
805         unsigned int flag = 0;
806         int rc = -1;
807
808         /* prepare parsing the keytab file */
809         code = krb5_kt_resolve(ctx, krb5_keytab_file, &kt);
810         if (code) {
811                 logmsg(LL_ERR, "resolve keytab %s: %s\n",
812                        krb5_keytab_file, krb5_err_msg(code));
813                 return -1;
814         }
815
816         code = krb5_kt_start_seq_get(ctx, kt, &cursor);
817         if (code) {
818                 logmsg(LL_ERR, "start kt iteration: %s\n", krb5_err_msg(code));
819                 goto out_kt;
820         }
821
822         /* iterate keytab to find proper an entry */
823         do {
824                 krb5_data      *princname;
825
826                 code = krb5_kt_next_entry(ctx, kt, &kte, &cursor);
827                 if (code != 0)
828                         break;
829
830                 logmsg(LL_TRACE,
831                        "kt entry: realm %.*s, type %d, size %d, name %.*s\n",
832                        krb5_princ_realm(ctx, kte.principal)->length,
833                        krb5_princ_realm(ctx, kte.principal)->data,
834                        krb5_princ_type(ctx, kte.principal),
835                        krb5_princ_size(ctx, kte.principal),
836                        krb5_princ_name(ctx, kte.principal)->length,
837                        krb5_princ_name(ctx, kte.principal)->data);
838
839                 if (!princ_is_local_realm(ctx, kte.principal))
840                         continue;
841
842                 princname = krb5_princ_name(ctx, kte.principal);
843
844                 if ((root_flags & LGSS_ROOT_CRED_ROOT) != 0 &&
845                     (!lgss_krb5_strcmp(princname, LGSS_USR_ROOT_STR) ||
846                      !lgss_krb5_strcmp(princname, LGSS_SVC_HOST_STR))) {
847                         flag = LGSS_ROOT_CRED_ROOT;
848                 } else if ((root_flags & LGSS_ROOT_CRED_MDT) != 0 &&
849                            !lgss_krb5_strcmp(princname, LGSS_SVC_MDS_STR)) {
850                         flag = LGSS_ROOT_CRED_MDT;
851                 } else if ((root_flags & LGSS_ROOT_CRED_OST) != 0 &&
852                            !lgss_krb5_strcmp(princname, LGSS_SVC_OSS_STR)) {
853                         flag = LGSS_ROOT_CRED_OST;
854                 } else {
855                         logmsg(LL_TRACE, "not what we want, skip\n");
856                         continue;
857                 }
858
859                 if (krb5_princ_component(ctx, kte.principal, 1) == NULL) {
860                         if (flag != LGSS_ROOT_CRED_ROOT) {
861                                 logmsg(LL_TRACE, "no hostname, skip\n");
862                                 continue;
863                         }
864                 } else {
865                         if (svc_princ_verify_host(ctx, kte.principal, self_nid,
866                                                   LL_TRACE)) {
867                                 logmsg(LL_TRACE, "doesn't belong to this "
868                                        "node, skip\n");
869                                 continue;
870                         }
871                 }
872
873                 code = krb5_copy_principal(ctx, kte.principal, &princ);
874                 if (code) {
875                         logmsg(LL_ERR, "copy princ: %s\n", krb5_err_msg(code));
876                         continue;
877                 }
878
879                 lassert(princ != NULL);
880                 break;
881         } while (1);
882
883         krb5_kt_end_seq_get(ctx, kt, &cursor);
884
885         if (princ == NULL) {
886                 logmsg(LL_ERR, "can't find proper keytab entry\n");
887                 goto out_kt;
888         }
889
890         /* obtain root TGT */
891         rc = get_root_tgt_ccname(ctx, ccname, sizeof(ccname));
892         if (!rc)
893                 rc = lkrb5_get_root_tgt_keytab(ctx, kt, princ, ccname);
894
895         krb5_free_principal(ctx, princ);
896 out_kt:
897         krb5_kt_close(ctx, kt);
898         return rc;
899 }
900
901 static int lkrb5_prepare_root_cred(struct lgss_cred *cred)
902 {
903         krb5_context ctx;
904         krb5_error_code code;
905         int rc = -1;
906
907         lassert(krb5_this_realm != NULL);
908
909         code = krb5_init_context(&ctx);
910         if (code) {
911                 logmsg(LL_ERR, "initialize krb5 context: %s\n",
912                        krb5_err_msg(code));
913                 return -1;
914         }
915
916         /*
917          * search and/or obtain root TGT credential.
918          * it touched global (on-disk) tgt cache, do it inside mutex locking
919          */
920         lgss_krb5_mutex_lock();
921         rc = lkrb5_check_root_tgt_cc(ctx, cred->lc_root_flags,
922                                      cred->lc_self_nid);
923         if (rc)
924                 rc = lkrb5_refresh_root_tgt_cc(ctx, cred->lc_root_flags,
925                                                cred->lc_self_nid);
926
927         lgss_krb5_mutex_unlock();
928         krb5_free_context(ctx);
929
930         logmsg(LL_DEBUG, "prepare root credentail %s\n", rc ? "failed" : "OK");
931         return rc;
932 }
933
934 static int lkrb5_prepare_user_cred(struct lgss_cred *cred)
935 {
936         krb5_context ctx;
937         krb5_error_code code;
938         int size = PATH_MAX;
939         void *ccname;
940         int rc;
941
942         lassert(krb5_this_realm == NULL);
943
944         code = krb5_init_context(&ctx);
945         if (code) {
946                 logmsg(LL_ERR, "initialize krb5 context: %s\n",
947                        krb5_err_msg(code));
948                 return -1;
949         }
950
951         /* buffer passed to get_user_tgt_ccname() must be mmapped with
952          * MAP_SHARED because it is accessed read/write from a child process
953          */
954         ccname = mmap(NULL, size, PROT_READ | PROT_WRITE,
955                       MAP_SHARED | MAP_ANONYMOUS, -1, 0);
956         if (ccname == MAP_FAILED) {
957                 logmsg(LL_ERR, "cannot mmap memory for user %u: %s\n",
958                        cred->lc_uid, strerror(errno));
959                 rc = -errno;
960                 goto free;
961         }
962
963         rc = get_user_tgt_ccname(cred, ctx, ccname, size);
964         if (rc)
965                 logmsg(LL_ERR, "cannot get user %u ccname: %s\n",
966                        cred->lc_uid, strerror(-rc));
967         else
968                 logmsg(LL_INFO, "using krb5 cache name: %s\n", (char *)ccname);
969
970         if (munmap(ccname, size) == -1) {
971                 logmsg(LL_ERR, "cannot munmap memory for user %u: %s\n",
972                        cred->lc_uid, strerror(errno));
973                 rc = rc ? rc : -errno;
974         }
975
976 free:
977         krb5_free_context(ctx);
978         return rc;
979 }
980
981 static int lgss_krb5_prepare_cred(struct lgss_cred *cred)
982 {
983         int rc;
984
985         cred->lc_mech_cred = NULL;
986
987         if (cred->lc_root_flags != 0) {
988                 if (lgss_krb5_get_local_realm())
989                         return -1;
990
991                 rc = lkrb5_prepare_root_cred(cred);
992         } else {
993                 rc = lkrb5_prepare_user_cred(cred);
994         }
995
996         return rc;
997 }
998
999 static
1000 void lgss_krb5_release_cred(struct lgss_cred *cred)
1001 {
1002         cred->lc_mech_cred = NULL;
1003 }
1004
1005 struct lgss_mech_type lgss_mech_krb5 = 
1006 {
1007         .lmt_name               = "krb5",
1008         .lmt_mech_n             = LGSS_MECH_KRB5,
1009         .lmt_prepare_cred       = lgss_krb5_prepare_cred,
1010         .lmt_release_cred       = lgss_krb5_release_cred,
1011 };