Whamcloud - gitweb
LU-6356 gss: call out info must include 'self nid'
[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 <rpc/rpc.h>
108 #include <sys/types.h>
109 #include <sys/stat.h>
110 #include <sys/utsname.h>
111 #include <sys/socket.h>
112 #include <arpa/inet.h>
113
114 #include <unistd.h>
115 #include <stdio.h>
116 #include <stdlib.h>
117 #include <string.h>
118 #include <netdb.h>
119 #include <dirent.h>
120 #include <fcntl.h>
121 #include <errno.h>
122 #include <time.h>
123 #include <gssapi/gssapi.h>
124 #ifdef USE_PRIVATE_KRB5_FUNCTIONS
125 #include <gssapi/gssapi_krb5.h>
126 #endif
127 #include <krb5.h>
128
129 #include "lsupport.h"
130 #include "lgss_utils.h"
131 #include "lgss_krb5_utils.h"
132
133 static void lgss_krb5_mutex_lock(void)
134 {
135         if (lgss_mutex_lock(LGSS_MUTEX_KRB5)) {
136                 logmsg(LL_ERR, "can't lock process, abort!\n");
137                 exit(-1);
138         }
139 }
140
141 static void lgss_krb5_mutex_unlock(void)
142 {
143         if (lgss_mutex_unlock(LGSS_MUTEX_KRB5)) {
144                 logmsg(LL_WARN, "can't unlock process, other processes "
145                        "might need to wait long time\n");
146         }
147 }
148
149 /*
150  * NOTE
151  *  - currently we only support "normal" cache types: "FILE" and "MEMORY".
152  */
153
154 #define krb5_err_msg(code)      error_message(code)
155
156 const char *krb5_cc_type_mem    = "MEMORY:";
157 const char *krb5_cc_type_file   = "FILE:";
158 const char *krb5_cred_root_suffix  = "lustre_root";
159 const char *krb5_cred_mds_suffix   = "lustre_mds";
160 const char *krb5_cred_oss_suffix   = "lustre_oss";
161
162 char    *krb5_this_realm        = NULL;
163 char    *krb5_keytab_file       = "/etc/krb5.keytab";
164 char    *krb5_cc_type           = "FILE:";
165 char    *krb5_cc_dir            = "/tmp";
166 char    *krb5_cred_prefix       = "krb5cc_";
167
168 struct lgss_krb5_cred {
169         char            kc_ccname[128];
170         int             kc_remove;        /* remove cache upon release */
171 };
172
173 static
174 int lgss_krb5_set_ccache_name(const char *ccname)
175 {
176 #ifdef USE_GSS_KRB5_CCACHE_NAME
177         unsigned int    maj_stat, min_stat;
178
179         maj_stat = gss_krb5_ccache_name(&min_stat, ccname, NULL);
180         if (maj_stat != GSS_S_COMPLETE) {
181                 logmsg(LL_ERR, "failed to set ccache name\n");
182                 return -1;
183         }
184 #else
185         /*
186          * Set the KRB5CCNAME environment variable to tell the krb5 code
187          * which credentials cache to use.  (Instead of using the private
188          * function above for which there is no generic gssapi equivalent)
189          */
190         if (setenv("KRB5CCNAME", ccname, 1)) {
191                 logmsg(LL_ERR, "set env of krb5 ccname: %s\n",
192                        strerror(errno));
193                 return -1;
194         }
195 #endif
196         logmsg(LL_DEBUG, "set cc: %s\n", ccname);
197         return 0;
198 }
199
200 static
201 int lgss_krb5_get_local_realm(void)
202 {
203         krb5_context    context = NULL;
204         krb5_error_code code;
205         int             retval = -1;
206
207         if (krb5_this_realm != NULL)
208                 return 0;
209
210         code = krb5_init_context(&context);
211         if (code) {
212                 logmsg(LL_ERR, "init ctx: %s\n", krb5_err_msg(code));
213                 return -1;
214         }
215
216         code = krb5_get_default_realm(context, &krb5_this_realm);
217         if (code) {
218                 logmsg(LL_ERR, "get default realm: %s\n", krb5_err_msg(code));
219                 goto out;
220         }
221
222         logmsg(LL_DEBUG, "Local realm: %s\n", krb5_this_realm);
223         retval = 0;
224 out:
225         krb5_free_context(context);
226         return retval;
227 }
228
229 static
230 int princ_is_local_realm(krb5_context ctx, krb5_principal princ)
231 {
232         return (lgss_krb5_strcasecmp(krb5_princ_realm(ctx, princ),
233                                      krb5_this_realm) == 0);
234 }
235
236 static
237 int svc_princ_verify_host(krb5_context ctx,
238                           krb5_principal princ,
239                           uint64_t self_nid,
240                           loglevel_t loglevel)
241 {
242         struct utsname utsbuf;
243         struct hostent *host;
244         const int max_namelen = 512;
245         char namebuf[max_namelen];
246         char *h_name;
247
248         if (krb5_princ_component(ctx, princ, 1) == NULL) {
249                 logmsg(loglevel, "service principal has no host part\n");
250                 return -1;
251         }
252
253         if (self_nid != 0) {
254                 if (lnet_nid2hostname(self_nid, namebuf, max_namelen)) {
255                         logmsg(loglevel,
256                                "can't resolve hostname from nid %llx\n",
257                                self_nid);
258                         return -1;
259                 }
260                 h_name = namebuf;
261         } else {
262                 if (uname(&utsbuf)) {
263                         logmsg(loglevel, "get UTS name: %s\n", strerror(errno));
264                         return -1;
265                 }
266
267                 host = gethostbyname(utsbuf.nodename);
268                 if (host == NULL) {
269                         logmsg(loglevel, "failed to get local hostname\n");
270                         return -1;
271                 }
272                 h_name = host->h_name;
273         }
274
275         if (lgss_krb5_strcasecmp(krb5_princ_component(ctx, princ, 1),
276                                  h_name)) {
277                 logmsg(loglevel, "service principal: hostname %.*s "
278                        "doesn't match localhost %s\n",
279                        krb5_princ_component(ctx, princ, 1)->length,
280                        krb5_princ_component(ctx, princ, 1)->data,
281                        h_name);
282                 return -1;
283         }
284
285         return 0;
286 }
287
288 static
289 int lkrb5_cc_check_tgt_princ(krb5_context ctx,
290                              krb5_ccache ccache,
291                              krb5_principal princ,
292                              unsigned int flag,
293                              uint64_t self_nid)
294 {
295         const char     *princ_name;
296
297         logmsg(LL_DEBUG, "principal: realm %.*s, type %d, size %d, name %.*s\n",
298                krb5_princ_realm(ctx, princ)->length,
299                krb5_princ_realm(ctx, princ)->data,
300                krb5_princ_type(ctx, princ),
301                krb5_princ_size(ctx, princ),
302                krb5_princ_name(ctx, princ)->length,
303                krb5_princ_name(ctx, princ)->data);
304
305         /* check type */
306         if (krb5_princ_type(ctx, princ) != KRB5_NT_PRINCIPAL) {
307                 logmsg(LL_WARN, "principal type %d is not I want\n",
308                        krb5_princ_type(ctx, princ));
309                 return -1;
310         }
311
312         /* check local realm */
313         if (!princ_is_local_realm(ctx, princ)) {
314                 logmsg(LL_WARN, "principal realm %.*s not local: %s\n",
315                        krb5_princ_realm(ctx, princ)->length,
316                        krb5_princ_realm(ctx, princ)->data,
317                        krb5_this_realm);
318                 return -1;
319         }
320
321         /* check principal name */
322         switch (flag) {
323         case LGSS_ROOT_CRED_ROOT:
324                 princ_name = LGSS_USR_ROOT_STR;
325                 break;
326         case LGSS_ROOT_CRED_MDT:
327                 princ_name = LGSS_SVC_MDS_STR;
328                 break;
329         case LGSS_ROOT_CRED_OST:
330                 princ_name = LGSS_SVC_OSS_STR;
331                 break;
332         default:
333                 lassert(0);
334         }
335
336         if (lgss_krb5_strcmp(krb5_princ_name(ctx, princ), princ_name)) {
337                 logmsg(LL_WARN, "%.*s: we expect %s instead\n",
338                        krb5_princ_name(ctx, princ)->length,
339                        krb5_princ_name(ctx, princ)->data,
340                        princ_name);
341                 return -1;
342         }
343
344         /*
345          * verify the hostname part of the principal, except we do allow
346          * lustre_root without binding to a host.
347          */
348         if (krb5_princ_component(ctx, princ, 1) == NULL) {
349                 if (flag != LGSS_ROOT_CRED_ROOT) {
350                         logmsg(LL_WARN, "%.*s: missing hostname\n",
351                                krb5_princ_name(ctx, princ)->length,
352                                krb5_princ_name(ctx, princ)->data);
353                         return -1;
354                 }
355         } else {
356                 if (svc_princ_verify_host(ctx, princ, self_nid, LL_WARN)) {
357                         logmsg(LL_DEBUG, "%.*s: doesn't belong to this node\n",
358                                krb5_princ_name(ctx, princ)->length,
359                                krb5_princ_name(ctx, princ)->data);
360                         return -1;
361                 }
362         }
363
364         logmsg(LL_TRACE, "principal is OK\n");
365         return 0;
366 }
367
368 /**
369  * compose the TGT cc name, according to the root flags.
370  */
371 static
372 void get_root_tgt_ccname(char *ccname, int size, unsigned int flag)
373 {
374         const char *suffix;
375
376         switch (flag) {
377         case LGSS_ROOT_CRED_ROOT:
378                 suffix = krb5_cred_root_suffix;
379                 break;
380         case LGSS_ROOT_CRED_MDT:
381                 suffix = krb5_cred_mds_suffix;
382                 break;
383         case LGSS_ROOT_CRED_OST:
384                 suffix = krb5_cred_oss_suffix;
385                 break;
386         default:
387                 lassert(0);
388         }
389
390         snprintf(ccname, size, "%s%s/%s%s_%s",
391                  krb5_cc_type, krb5_cc_dir, krb5_cred_prefix,
392                  suffix, krb5_this_realm);
393 }
394
395 static
396 int lkrb5_check_root_tgt_cc_base(krb5_context ctx,
397                                  krb5_ccache ccache,
398                                  char *ccname,
399                                  unsigned int flag,
400                                  uint64_t self_nid)
401 {
402         krb5_ccache             tgt_ccache;
403         krb5_creds              cred;
404         krb5_principal          princ;
405         krb5_cc_cursor          cursor;
406         krb5_error_code         code;
407         time_t                  now;
408         int                     rc = -1, found = 0;
409
410         /* prepare parsing the cache file */
411         code = krb5_cc_resolve(ctx, ccname, &tgt_ccache);
412         if (code) {
413                 logmsg(LL_ERR, "resolve krb5 cc %s: %s\n",
414                        ccname, krb5_err_msg(code));
415                 return -1;
416         }
417
418         /* checks the principal */
419         code = krb5_cc_get_principal(ctx, tgt_ccache, &princ);
420         if (code) {
421                 logmsg(LL_ERR, "get cc principal: %s\n", krb5_err_msg(code));
422                 goto out_cc;
423         }
424
425         if (lkrb5_cc_check_tgt_princ(ctx, tgt_ccache, princ, flag, self_nid))
426                 goto out_princ;
427
428         /*
429          * find a valid entry
430          */
431         code = krb5_cc_start_seq_get(ctx, tgt_ccache, &cursor);
432         if (code) {
433                 logmsg(LL_ERR, "start cc iteration: %s\n", krb5_err_msg(code));
434                 goto out_princ;
435         }
436
437         now = time(0);
438         do {
439                 krb5_timestamp  duration, delta;
440
441                 code = krb5_cc_next_cred(ctx, tgt_ccache, &cursor, &cred);
442                 if (code != 0)
443                         break;
444
445                 logmsg(LL_DEBUG, "cred: server realm %.*s, type %d, name %.*s; "
446                        "time (%d-%d, renew till %d), valid %d\n",
447                        krb5_princ_realm(ctx, cred.server)->length,
448                        krb5_princ_realm(ctx, cred.server)->data,
449                        krb5_princ_type(ctx, cred.server),
450                        krb5_princ_name(ctx, cred.server)->length,
451                        krb5_princ_name(ctx, cred.server)->data,
452                        cred.times.starttime, cred.times.endtime,
453                        cred.times.renew_till, cred.times.endtime - now);
454
455                 /* FIXME
456                  * we found the princ type is always 0 (KRB5_NT_UNKNOWN), why???
457                  */
458
459                 /* FIXME how about inter-realm TGT??? FIXME */
460                 if (lgss_krb5_strcasecmp(krb5_princ_name(ctx, cred.server),
461                                          "krbtgt"))
462                         continue;
463
464                 if (lgss_krb5_strcasecmp(krb5_princ_realm(ctx, cred.server),
465                                          krb5_this_realm))
466                         continue;
467
468                 /* check validity of time */
469                 delta = 60 * 30; /* half an hour */
470                 duration = cred.times.endtime - cred.times.starttime;
471                 if (duration / 4 < delta)
472                         delta = duration / 4;
473
474                 if (cred.times.starttime <= now &&
475                     cred.times.endtime >= now + delta) {
476                         found = 1;
477                         break;
478                 }
479         } while (1);
480
481         if (!found) {
482                 logmsg(LL_DEBUG, "doesn't find good TGT cache\n");
483                 goto out_seq;
484         }
485
486         /* found a good cred, store it into @ccache */
487         logmsg(LL_DEBUG, "found good TGT cache\n");
488
489         code = krb5_cc_initialize(ctx, ccache, princ);
490         if (code) {
491                 logmsg(LL_ERR, "init private cc: %s\n", krb5_err_msg(code));
492                 goto out_seq;
493         }
494
495         code = krb5_cc_store_cred(ctx, ccache, &cred);
496         if (code) {
497                 logmsg(LL_ERR, "store private cred: %s\n", krb5_err_msg(code));
498                 goto out_seq;
499         }
500
501         logmsg(LL_DEBUG, "store private ccache OK\n");
502         rc = 0;
503
504 out_seq:
505         krb5_cc_end_seq_get(ctx, tgt_ccache, &cursor);
506 out_princ:
507         krb5_free_principal(ctx, princ);
508 out_cc:
509         krb5_cc_close(ctx, tgt_ccache);
510
511         return rc;
512 }
513
514 /**
515  * find out whether current TGT cache is valid or not
516  */
517 static
518 int lkrb5_check_root_tgt_cc(krb5_context ctx,
519                             krb5_ccache ccache,
520                             unsigned int root_flags,
521                             uint64_t self_nid)
522 {
523         struct stat             statbuf;
524         unsigned int            flag;
525         char                    ccname[1024];
526         char                   *ccfile;
527         int                     i, rc;
528
529         for (i = 0; i < LGSS_ROOT_CRED_NR; i++) {
530                 flag = 1 << i;
531
532                 if ((root_flags & flag) == 0)
533                         continue;
534
535                 get_root_tgt_ccname(ccname, sizeof(ccname), flag);
536                 logmsg(LL_DEBUG, "root krb5 TGT ccname: %s\n", ccname);
537
538                 /* currently we only support type "FILE", firstly make sure
539                  * the cache file is there */
540                 ccfile = ccname + strlen(krb5_cc_type);
541                 if (stat(ccfile, &statbuf)) {
542                         logmsg(LL_DEBUG, "krb5 cc %s: %s\n",
543                                ccname, strerror(errno));
544                         continue;
545                 }
546
547                 rc = lkrb5_check_root_tgt_cc_base(ctx, ccache, ccname, flag,
548                                                   self_nid);
549                 if (rc == 0)
550                         return 0;
551         }
552
553         logmsg(LL_TRACE, "doesn't find a valid tgt cc\n");
554         return -1;
555 }
556
557 static
558 int lkrb5_get_root_tgt_keytab(krb5_context ctx,
559                               krb5_ccache ccache,
560                               krb5_keytab kt,
561                               krb5_principal princ,
562                               const char *ccname)
563 {
564         krb5_get_init_creds_opt opts;
565         krb5_creds              cred;
566         krb5_ccache             tgt_ccache;
567         krb5_error_code         code;
568         int                     rc = -1;
569
570         krb5_get_init_creds_opt_init(&opts);
571         krb5_get_init_creds_opt_set_address_list(&opts, NULL);
572         /*
573          * by default krb5 library obtain ticket with lifetime shorter
574          * than the max value. we can change it here if we want. but
575          * seems not necessary now.
576          *
577         krb5_get_init_creds_opt_set_tkt_life(&opts, very-long-time);
578          *
579          */
580
581         /*
582          * obtain TGT and store into global ccache
583          */
584         code = krb5_get_init_creds_keytab(ctx, &cred, princ, kt,
585                                           0, NULL, &opts);
586         if (code) {
587                 logmsg(LL_ERR, "failed to get root TGT for "
588                        "principal %.*s: %s\n",
589                        krb5_princ_name(ctx, princ)->length,
590                        krb5_princ_name(ctx, princ)->data,
591                        krb5_err_msg(code));
592                 return -1;
593         }
594
595         code = krb5_cc_resolve(ctx, ccname, &tgt_ccache);
596         if (code) {
597                 logmsg(LL_ERR, "resolve cc %s: %s\n",
598                        ccname, krb5_err_msg(code));
599                 goto out_cred;
600         }
601
602         code = krb5_cc_initialize(ctx, tgt_ccache, princ);
603         if (code) {
604                 logmsg(LL_ERR, "initialize cc %s: %s\n",
605                        ccname, krb5_err_msg(code));
606                 goto out_cc;
607         }
608
609         code = krb5_cc_store_cred(ctx, tgt_ccache, &cred);
610         if (code) {
611                 logmsg(LL_ERR, "store cred to cc %s: %s\n",
612                        ccname, krb5_err_msg(code));
613                 goto out_cc;
614         }
615
616         logmsg(LL_INFO, "installed TGT of %.*s in cc %s\n",
617                krb5_princ_name(ctx, princ)->length,
618                krb5_princ_name(ctx, princ)->data,
619                ccname);
620
621         /*
622          * now store the cred into my own cc too
623          */
624         code = krb5_cc_initialize(ctx, ccache, princ);
625         if (code) {
626                 logmsg(LL_ERR, "init mem cc: %s\n", krb5_err_msg(code));
627                 goto out_cc;
628         }
629
630         code = krb5_cc_store_cred(ctx, ccache, &cred);
631         if (code) {
632                 logmsg(LL_ERR, "store mm cred: %s\n", krb5_err_msg(code));
633                 goto out_cc;
634         }
635
636         logmsg(LL_DEBUG, "stored TGT into mem cc OK\n");
637         rc = 0;
638 out_cc:
639         krb5_cc_close(ctx, tgt_ccache);
640 out_cred:
641         krb5_free_cred_contents(ctx, &cred);
642         return rc;
643 }
644
645 /*
646  * obtain a new root TGT
647  */
648 static
649 int lkrb5_refresh_root_tgt_cc(krb5_context ctx,
650                               krb5_ccache ccache,
651                               unsigned int root_flags,
652                               uint64_t self_nid)
653 {
654         krb5_keytab             kt;
655         krb5_keytab_entry       kte;
656         krb5_kt_cursor          cursor;
657         krb5_principal          princ = NULL;
658         krb5_error_code         code;
659         char                    ccname[1024];
660         unsigned int            flag = 0;
661         int                     rc = -1;
662
663         /* prepare parsing the keytab file */
664         code = krb5_kt_resolve(ctx, krb5_keytab_file, &kt);
665         if (code) {
666                 logmsg(LL_ERR, "resolve keytab %s: %s\n",
667                        krb5_keytab_file, krb5_err_msg(code));
668                 return -1;
669         }
670
671         code = krb5_kt_start_seq_get(ctx, kt, &cursor);
672         if (code) {
673                 logmsg(LL_ERR, "start kt iteration: %s\n", krb5_err_msg(code));
674                 goto out_kt;
675         }
676
677         /* iterate keytab to find proper an entry */
678         do {
679                 krb5_data      *princname;
680
681                 code = krb5_kt_next_entry(ctx, kt, &kte, &cursor);
682                 if (code != 0)
683                         break;
684
685                 logmsg(LL_TRACE, "kt entry: realm %.*s, type %d, "
686                        "size %d, name %.*s\n",
687                        krb5_princ_realm(ctx, kte.principal)->length,
688                        krb5_princ_realm(ctx, kte.principal)->data,
689                        krb5_princ_type(ctx, kte.principal),
690                        krb5_princ_size(ctx, kte.principal),
691                        krb5_princ_name(ctx, kte.principal)->length,
692                        krb5_princ_name(ctx, kte.principal)->data);
693
694                 if (!princ_is_local_realm(ctx, kte.principal))
695                         continue;
696
697                 princname = krb5_princ_name(ctx, kte.principal);
698
699                 if ((root_flags & LGSS_ROOT_CRED_ROOT) != 0 &&
700                     lgss_krb5_strcmp(princname, LGSS_USR_ROOT_STR) == 0) {
701                         flag = LGSS_ROOT_CRED_ROOT;
702                 } else if ((root_flags & LGSS_ROOT_CRED_MDT) != 0 &&
703                            lgss_krb5_strcmp(princname, LGSS_SVC_MDS_STR) == 0) {
704                         flag = LGSS_ROOT_CRED_MDT;
705                 } else if ((root_flags & LGSS_ROOT_CRED_OST) != 0 &&
706                            lgss_krb5_strcmp(princname, LGSS_SVC_OSS_STR) == 0) {
707                         flag = LGSS_ROOT_CRED_OST;
708                 } else {
709                         logmsg(LL_TRACE, "not what we want, skip\n");
710                         continue;
711                 }
712
713                 if (krb5_princ_component(ctx, kte.principal, 1) == NULL) {
714                         if (flag != LGSS_ROOT_CRED_ROOT) {
715                                 logmsg(LL_TRACE, "no hostname, skip\n");
716                                 continue;
717                         }
718                 } else {
719                         if (svc_princ_verify_host(ctx, kte.principal, self_nid,
720                                                   LL_TRACE)) {
721                                 logmsg(LL_TRACE, "doesn't belong to this "
722                                        "node, skip\n");
723                                 continue;
724                         }
725                 }
726
727                 code = krb5_copy_principal(ctx, kte.principal, &princ);
728                 if (code) {
729                         logmsg(LL_ERR, "copy princ: %s\n", krb5_err_msg(code));
730                         continue;
731                 }
732
733                 lassert(princ != NULL);
734                 break;
735         } while (1);
736
737         krb5_kt_end_seq_get(ctx, kt, &cursor);
738
739         if (princ == NULL) {
740                 logmsg(LL_ERR, "can't find proper keytab entry\n");
741                 goto out_kt;
742         }
743
744         /* obtain root TGT */
745         get_root_tgt_ccname(ccname, sizeof(ccname), flag);
746         rc = lkrb5_get_root_tgt_keytab(ctx, ccache, kt, princ, ccname);
747
748         krb5_free_principal(ctx, princ);
749 out_kt:
750         krb5_kt_close(ctx, kt);
751         return rc;
752 }
753
754 static
755 int lkrb5_prepare_root_cred(struct lgss_cred *cred)
756 {
757         krb5_context            ctx;
758         krb5_ccache             ccache;
759         krb5_error_code         code;
760         struct lgss_krb5_cred  *kcred;
761         int                     rc = -1;
762
763         lassert(krb5_this_realm != NULL);
764
765         kcred = (struct lgss_krb5_cred *) cred->lc_mech_cred;
766
767         /* compose the memory cc name, since the only user is myself,
768          * the name could be fixed */
769         snprintf(kcred->kc_ccname, sizeof(kcred->kc_ccname),
770                  "%s/self", krb5_cc_type_mem);
771         logmsg(LL_TRACE, "private cc: %s\n", kcred->kc_ccname);
772
773         code = krb5_init_context(&ctx);
774         if (code) {
775                 logmsg(LL_ERR, "initialize krb5 context: %s\n",
776                        krb5_err_msg(code));
777                 return -1;
778         }
779
780         code = krb5_cc_resolve(ctx, kcred->kc_ccname, &ccache);
781         if (code) {
782                 logmsg(LL_ERR, "resolve krb5 cc %s: %s\n",
783                        kcred->kc_ccname, krb5_err_msg(code));
784                 goto out_ctx;
785         }
786
787         /*
788          * search and/or obtain root TGT credential.
789          * it touched global (on-disk) tgt cache, do it inside mutex locking
790          */
791         lgss_krb5_mutex_lock();
792
793         rc = lkrb5_check_root_tgt_cc(ctx, ccache, cred->lc_root_flags,
794                                      cred->lc_self_nid);
795         if (rc != 0)
796                 rc = lkrb5_refresh_root_tgt_cc(ctx, ccache,
797                                                cred->lc_root_flags,
798                                                cred->lc_self_nid);
799
800         if (rc == 0)
801                 rc = lgss_krb5_set_ccache_name(kcred->kc_ccname);
802
803         lgss_krb5_mutex_unlock();
804
805         krb5_cc_close(ctx, ccache);
806 out_ctx:
807         krb5_free_context(ctx);
808
809         logmsg(LL_DEBUG, "prepare root credentail %s\n", rc ? "failed" : "OK");
810         return rc;
811 }
812
813 static
814 int lkrb5_prepare_user_cred(struct lgss_cred *cred)
815 {
816         struct lgss_krb5_cred   *kcred;
817         int                      rc;
818
819         lassert(krb5_this_realm == NULL);
820
821         kcred = (struct lgss_krb5_cred *) cred->lc_mech_cred;
822
823         /*
824          * here we just specified a fix ccname, instead of searching
825          * entire cc dir. is this OK??
826          */
827         snprintf(kcred->kc_ccname, sizeof(kcred->kc_ccname),
828                  "%s%s/%s%u",
829                  krb5_cc_type, krb5_cc_dir, krb5_cred_prefix, cred->lc_uid);
830         logmsg(LL_DEBUG, "using krb5 cache name: %s\n", kcred->kc_ccname);
831
832         rc = lgss_krb5_set_ccache_name(kcred->kc_ccname);
833         if (rc)
834                 logmsg(LL_ERR, "can't set krb5 ccache name: %s\n",
835                        kcred->kc_ccname);
836
837         return rc;
838 }
839
840 static
841 int lgss_krb5_prepare_cred(struct lgss_cred *cred)
842 {
843         struct lgss_krb5_cred  *kcred;
844         int                     rc;
845
846         kcred = malloc(sizeof(*kcred));
847         if (kcred == NULL) {
848                 logmsg(LL_ERR, "can't allocate krb5 cred\n");
849                 return -1;
850         }
851
852         kcred->kc_ccname[0] = '\0';
853         kcred->kc_remove = 0;
854         cred->lc_mech_cred = kcred;
855
856         if (cred->lc_root_flags != 0) {
857                 if (lgss_krb5_get_local_realm())
858                         return -1;
859
860                 rc = lkrb5_prepare_root_cred(cred);
861         } else {
862                 rc = lkrb5_prepare_user_cred(cred);
863         }
864
865         return rc;
866 }
867
868 static
869 void lgss_krb5_release_cred(struct lgss_cred *cred)
870 {
871         struct lgss_krb5_cred   *kcred;
872
873         kcred = (struct lgss_krb5_cred *) cred->lc_mech_cred;
874
875         free(kcred);
876         cred->lc_mech_cred = NULL;
877 }
878
879 struct lgss_mech_type lgss_mech_krb5 = 
880 {
881         .lmt_name               = "krb5",
882         .lmt_mech_n             = LGSS_MECH_KRB5,
883         .lmt_prepare_cred       = lgss_krb5_prepare_cred,
884         .lmt_release_cred       = lgss_krb5_release_cred,
885 };