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