1 /* -*- mode: c; c-basic-offset: 8; indent-tabs-mode: nil; -*-
2 * vim:expandtab:shiftwidth=8:tabstop=8:
4 * Modifications for Lustre
5 * Copyright 2007, Cluster File Systems, Inc.
7 * Author: Eric Mei <ericm@clusterfs.com>
11 * Adapted in part from MIT Kerberos 5-1.2.1 slave/kprop.c and from
12 * http://docs.sun.com/?p=/doc/816-1331/6m7oo9sms&a=view
14 * Copyright (c) 2002-2004 The Regents of the University of Michigan.
15 * All rights reserved.
17 * Andy Adamson <andros@umich.edu>
18 * J. Bruce Fields <bfields@umich.edu>
19 * Marius Aamodt Eriksen <marius@umich.edu>
20 * Kevin Coffman <kwc@umich.edu>
26 * Copyright 1990,1991 by the Massachusetts Institute of Technology.
27 * All Rights Reserved.
29 * Export of this software from the United States of America may
30 * require a specific license from the United States Government.
31 * It is the responsibility of any person or organization contemplating
32 * export to obtain such a license before exporting.
34 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
35 * distribute this software and its documentation for any purpose and
36 * without fee is hereby granted, provided that the above copyright
37 * notice appear in all copies and that both that copyright notice and
38 * this permission notice appear in supporting documentation, and that
39 * the name of M.I.T. not be used in advertising or publicity pertaining
40 * to distribution of the software without specific, written prior
41 * permission. Furthermore if you modify this software you must label
42 * your software as modified software and not distribute it in such a
43 * fashion that it might be confused with the original M.I.T. software.
44 * M.I.T. makes no representations about the suitability of
45 * this software for any purpose. It is provided "as is" without express
46 * or implied warranty.
50 * Copyright 1994 by OpenVision Technologies, Inc.
52 * Permission to use, copy, modify, distribute, and sell this software
53 * and its documentation for any purpose is hereby granted without fee,
54 * provided that the above copyright notice appears in all copies and
55 * that both that copyright notice and this permission notice appear in
56 * supporting documentation, and that the name of OpenVision not be used
57 * in advertising or publicity pertaining to distribution of the software
58 * without specific, written prior permission. OpenVision makes no
59 * representations about the suitability of this software for any
60 * purpose. It is provided "as is" without express or implied warranty.
62 * OPENVISION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
63 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
64 * EVENT SHALL OPENVISION BE LIABLE FOR ANY SPECIAL, INDIRECT OR
65 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF
66 * USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
67 * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
68 * PERFORMANCE OF THIS SOFTWARE.
73 Copyright (c) 2004 The Regents of the University of Michigan.
76 Redistribution and use in source and binary forms, with or without
77 modification, are permitted provided that the following conditions
80 1. Redistributions of source code must retain the above copyright
81 notice, this list of conditions and the following disclaimer.
82 2. Redistributions in binary form must reproduce the above copyright
83 notice, this list of conditions and the following disclaimer in the
84 documentation and/or other materials provided with the distribution.
85 3. Neither the name of the University nor the names of its
86 contributors may be used to endorse or promote products derived
87 from this software without specific prior written permission.
89 THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
90 WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
91 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
92 DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
93 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
94 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
95 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
96 BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
97 LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
98 NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
99 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
107 #include <sys/param.h>
108 //#include <rpc/rpc.h>
109 #include <sys/types.h>
110 #include <sys/stat.h>
111 #include <sys/utsname.h>
112 #include <sys/socket.h>
113 #include <arpa/inet.h>
124 #include <gssapi/gssapi.h>
125 #ifdef USE_PRIVATE_KRB5_FUNCTIONS
126 #include <gssapi/gssapi_krb5.h>
130 #include "lgss_utils.h"
131 #include "lgss_krb5_utils.h"
133 static void lgss_krb5_mutex_lock(void)
135 if (lgss_mutex_lock(LGSS_MUTEX_KRB5)) {
136 logmsg(LL_ERR, "can't lock process, abort!\n");
141 static void lgss_krb5_mutex_unlock(void)
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");
151 * - currently we only support "normal" cache types: "FILE" and "MEMORY".
154 #define krb5_err_msg(code) error_message(code)
156 const char *krb5_cc_type_mem = "MEMORY:";
157 const char *krb5_cc_type_file = "FILE:";
159 char *krb5_this_realm = NULL;
160 char *krb5_keytab_file = "/etc/krb5.keytab";
161 char *krb5_cc_type = "FILE:";
162 char *krb5_cc_dir = "/tmp";
163 char *krb5_cred_prefix = "krb5cc_";
164 char *krb5_cred_root_suffix = "lustre_root";
166 struct lgss_krb5_cred {
168 int kc_remove; /* remove cache upon release */
172 int lgss_krb5_set_ccache_name(const char *ccname)
174 #ifdef USE_GSS_KRB5_CCACHE_NAME
175 unsigned int maj_stat, min_stat;
177 maj_stat = gss_krb5_ccache_name(&min_stat, ccname, NULL);
178 if (maj_stat != GSS_S_COMPLETE) {
179 logmsg(LL_ERR, "failed to set ccache name\n");
184 * Set the KRB5CCNAME environment variable to tell the krb5 code
185 * which credentials cache to use. (Instead of using the private
186 * function above for which there is no generic gssapi equivalent)
188 if (setenv("KRB5CCNAME", ccname, 1)) {
189 logmsg(LL_ERR, "set env of krb5 ccname: %s\n",
194 logmsg(LL_DEBUG, "set cc: %s\n", ccname);
199 int lgss_krb5_get_local_realm(void)
201 krb5_context context = NULL;
202 krb5_error_code code;
205 if (krb5_this_realm != NULL)
208 code = krb5_init_context(&context);
210 logmsg(LL_ERR, "init ctx: %s\n", krb5_err_msg(code));
214 code = krb5_get_default_realm(context, &krb5_this_realm);
216 logmsg(LL_ERR, "get default realm: %s\n", krb5_err_msg(code));
220 logmsg(LL_DEBUG, "Local realm: %s\n", krb5_this_realm);
223 krb5_free_context(context);
228 int princ_is_local_realm(krb5_context ctx, krb5_principal princ)
230 return (lgss_krb5_strcasecmp(krb5_princ_realm(ctx, princ),
231 krb5_this_realm) == 0);
235 int svc_princ_verify_host(krb5_context ctx,
236 krb5_principal princ,
239 struct utsname utsbuf;
240 struct hostent *host;
242 if (krb5_princ_component(ctx, princ, 1) == NULL) {
243 logmsg(loglevel, "service principal has no host part\n");
247 if (uname(&utsbuf)) {
248 logmsg(loglevel, "get UTS name: %s\n", strerror(errno));
252 host = gethostbyname(utsbuf.nodename);
254 logmsg(loglevel, "failed to get local hostname\n");
258 if (lgss_krb5_strcasecmp(krb5_princ_component(ctx, princ, 1),
260 logmsg(loglevel, "service principal: hostname %.*s "
261 "doesn't match localhost %s\n",
262 krb5_princ_component(ctx, princ, 1)->length,
263 krb5_princ_component(ctx, princ, 1)->data,
272 int lkrb5_cc_check_tgt_princ(krb5_context ctx,
274 krb5_principal princ)
276 logmsg(LL_TRACE, "principal: realm %.*s, type %d, size %d, name %.*s\n",
277 krb5_princ_realm(ctx, princ)->length,
278 krb5_princ_realm(ctx, princ)->data,
279 krb5_princ_type(ctx, princ),
280 krb5_princ_size(ctx, princ),
281 krb5_princ_name(ctx, princ)->length,
282 krb5_princ_name(ctx, princ)->data);
285 if (krb5_princ_type(ctx, princ) != KRB5_NT_PRINCIPAL) {
286 logmsg(LL_WARN, "principal type %d is not I want\n",
287 krb5_princ_type(ctx, princ));
291 /* check local realm */
292 if (!princ_is_local_realm(ctx, princ)) {
293 logmsg(LL_WARN, "principal realm %.*s not local: %s\n",
294 krb5_princ_realm(ctx, princ)->length,
295 krb5_princ_realm(ctx, princ)->data,
300 /* if it's mds service principal, or lustre_root principal
301 * with host part, verify the hostname.
302 * note we allow lustre_root without host part */
303 if (lgss_krb5_strcmp(krb5_princ_name(ctx, princ),
304 LGSS_SVC_MDS_STR) == 0) {
305 if (svc_princ_verify_host(ctx, princ, LL_WARN)) {
306 logmsg(LL_WARN, "mds service principal doesn't belong "
310 } else if (lgss_krb5_strcmp(krb5_princ_name(ctx, princ),
311 LGSS_USR_ROOT_STR) == 0) {
312 if (krb5_princ_component(ctx, princ, 1) != NULL &&
313 svc_princ_verify_host(ctx, princ, LL_WARN)) {
314 logmsg(LL_WARN, "lustre_root principal doesn't belong "
319 logmsg(LL_WARN, "unexpected krb5 cc principal name %.*s\n",
320 krb5_princ_name(ctx, princ)->length,
321 krb5_princ_name(ctx, princ)->data);
325 logmsg(LL_TRACE, "principal is OK\n");
330 * find out whether current TGT cache is valid or not
333 int lkrb5_check_root_tgt_cc(krb5_context ctx,
338 krb5_ccache tgt_ccache;
340 krb5_principal princ;
341 krb5_cc_cursor cursor;
342 krb5_error_code code;
345 int rc = -1, found = 0;
347 if (strncmp(ccname, krb5_cc_type, strlen(krb5_cc_type_file))) {
348 logmsg(LL_ERR, "unexpected cc type\n");
352 ccfile = ccname + strlen(krb5_cc_type_file);
353 logmsg(LL_TRACE, "cc file name: %s\n", ccfile);
355 /* firstly make sure the cache file is there */
356 if (stat(ccfile, &statbuf)) {
357 logmsg(LL_DEBUG, "krb5 cc %s: %s\n", ccname, strerror(errno));
361 /* prepare parsing the cache file */
362 code = krb5_cc_resolve(ctx, ccname, &tgt_ccache);
364 logmsg(LL_ERR, "resolve krb5 cc %s: %s\n",
365 ccname, krb5_err_msg(code));
369 /* checks the principal */
370 code = krb5_cc_get_principal(ctx, tgt_ccache, &princ);
372 logmsg(LL_ERR, "get cc principal: %s\n", krb5_err_msg(code));
376 if (lkrb5_cc_check_tgt_princ(ctx, tgt_ccache, princ)) {
377 logmsg(LL_WARN, "cc principal is not valid\n");
384 code = krb5_cc_start_seq_get(ctx, tgt_ccache, &cursor);
386 logmsg(LL_ERR, "start cc iteration: %s\n", krb5_err_msg(code));
392 krb5_timestamp duration, delta;
394 code = krb5_cc_next_cred(ctx, tgt_ccache, &cursor, &cred);
398 logmsg(LL_DEBUG, "cred: server realm %.*s, type %d, name %.*s; "
399 "time (%d-%d, renew till %d), valid %d\n",
400 krb5_princ_realm(ctx, cred.server)->length,
401 krb5_princ_realm(ctx, cred.server)->data,
402 krb5_princ_type(ctx, cred.server),
403 krb5_princ_name(ctx, cred.server)->length,
404 krb5_princ_name(ctx, cred.server)->data,
405 cred.times.starttime, cred.times.endtime,
406 cred.times.renew_till, cred.times.endtime - now);
409 * we found the princ type is always 0 (KRB5_NT_UNKNOWN), why???
412 /* FIXME how about inter-realm TGT??? FIXME */
413 if (lgss_krb5_strcasecmp(krb5_princ_name(ctx, cred.server),
417 if (lgss_krb5_strcasecmp(krb5_princ_realm(ctx, cred.server),
421 /* check validity of time */
422 delta = 60 * 30; /* half an hour */
423 duration = cred.times.endtime - cred.times.starttime;
424 if (duration / 4 < delta)
425 delta = duration / 4;
427 if (cred.times.starttime <= now &&
428 cred.times.endtime >= now + delta) {
435 logmsg(LL_DEBUG, "doesn't find good TGT cache\n");
439 /* found a good cred, store it into @ccache */
440 logmsg(LL_DEBUG, "found good TGT cache\n");
442 code = krb5_cc_initialize(ctx, ccache, princ);
444 logmsg(LL_ERR, "init private cc: %s\n", krb5_err_msg(code));
448 code = krb5_cc_store_cred(ctx, ccache, &cred);
450 logmsg(LL_ERR, "store private cred: %s\n", krb5_err_msg(code));
454 logmsg(LL_DEBUG, "store private ccache OK\n");
458 krb5_cc_end_seq_get(ctx, tgt_ccache, &cursor);
460 krb5_free_principal(ctx, princ);
462 krb5_cc_close(ctx, tgt_ccache);
468 int lkrb5_get_root_tgt_keytab(krb5_context ctx,
471 krb5_principal princ,
474 krb5_get_init_creds_opt opts;
476 krb5_ccache tgt_ccache;
477 krb5_error_code code;
480 krb5_get_init_creds_opt_init(&opts);
481 krb5_get_init_creds_opt_set_address_list(&opts, NULL);
483 * by default krb5 library obtain ticket with lifetime shorter
484 * than the max value. we can change it here if we want. but
485 * seems not necessary now.
487 krb5_get_init_creds_opt_set_tkt_life(&opts, very-long-time);
492 * obtain TGT and store into global ccache
494 code = krb5_get_init_creds_keytab(ctx, &cred, princ, kt,
497 logmsg(LL_ERR, "failed to get root TGT for "
498 "principal %.*s: %s\n",
499 krb5_princ_name(ctx, princ)->length,
500 krb5_princ_name(ctx, princ)->data,
505 code = krb5_cc_resolve(ctx, ccname, &tgt_ccache);
507 logmsg(LL_ERR, "resolve cc %s: %s\n",
508 ccname, krb5_err_msg(code));
512 code = krb5_cc_initialize(ctx, tgt_ccache, princ);
514 logmsg(LL_ERR, "initialize cc %s: %s\n",
515 ccname, krb5_err_msg(code));
519 code = krb5_cc_store_cred(ctx, tgt_ccache, &cred);
521 logmsg(LL_ERR, "store cred to cc %s: %s\n",
522 ccname, krb5_err_msg(code));
526 logmsg(LL_INFO, "installed TGT of %.*s in cc %s\n",
527 krb5_princ_name(ctx, princ)->length,
528 krb5_princ_name(ctx, princ)->data,
532 * now store the cred into my own cc too
534 code = krb5_cc_initialize(ctx, ccache, princ);
536 logmsg(LL_ERR, "init mem cc: %s\n", krb5_err_msg(code));
540 code = krb5_cc_store_cred(ctx, ccache, &cred);
542 logmsg(LL_ERR, "store mm cred: %s\n", krb5_err_msg(code));
546 logmsg(LL_DEBUG, "stored TGT into mem cc OK\n");
549 krb5_cc_close(ctx, tgt_ccache);
551 krb5_free_cred_contents(ctx, &cred);
556 * obtain a new root TGT
559 int lkrb5_refresh_root_tgt_cc(krb5_context ctx,
564 krb5_keytab_entry kte;
565 krb5_kt_cursor cursor;
566 krb5_principal princ = NULL, princ2;
567 krb5_error_code code;
568 int general_root = 0;
571 /* prepare parsing the keytab file */
572 code = krb5_kt_resolve(ctx, krb5_keytab_file, &kt);
574 logmsg(LL_ERR, "resolve keytab %s: %s\n",
575 krb5_keytab_file, krb5_err_msg(code));
579 code = krb5_kt_start_seq_get(ctx, kt, &cursor);
581 logmsg(LL_ERR, "start kt iteration: %s\n", krb5_err_msg(code));
585 /* iterate keytab to find proper a entry */
587 code = krb5_kt_next_entry(ctx, kt, &kte, &cursor);
591 logmsg(LL_TRACE, "kt entry: realm %.*s, type %d, "
592 "size %d, name %.*s\n",
593 krb5_princ_realm(ctx, kte.principal)->length,
594 krb5_princ_realm(ctx, kte.principal)->data,
595 krb5_princ_type(ctx, kte.principal),
596 krb5_princ_size(ctx, kte.principal),
597 krb5_princ_name(ctx, kte.principal)->length,
598 krb5_princ_name(ctx, kte.principal)->data);
600 if (!princ_is_local_realm(ctx, kte.principal))
603 /* lustre_root[/host]@realm */
604 if (lgss_krb5_strcmp(krb5_princ_name(ctx, kte.principal),
605 LGSS_USR_ROOT_STR) == 0) {
606 int tmp_general_root = 0;
608 if (krb5_princ_component(ctx, kte.principal,1) == NULL){
610 logmsg(LL_TRACE, "lustre_root: "
611 "already picked one, skip\n");
615 tmp_general_root = 1;
617 if (svc_princ_verify_host(ctx, kte.principal,
619 logmsg(LL_TRACE, "lustre_root: "
620 "doesn't belong to this node\n");
624 if (princ != NULL && !general_root) {
625 logmsg(LL_TRACE, "lustre_root: already "
626 "have a host-specific one, "
632 code = krb5_copy_principal(ctx, kte.principal, &princ2);
634 logmsg(LL_ERR, "copy lustre_root princ: %s\n",
640 logmsg(LL_TRACE, "release a lustre_root one\n");
641 krb5_free_principal(ctx, princ);
645 general_root = tmp_general_root;
649 /* lustre_mds/host@realm */
650 if (lgss_krb5_strcmp(krb5_princ_name(ctx, kte.principal),
651 LGSS_SVC_MDS_STR) == 0) {
652 if (svc_princ_verify_host(ctx, kte.principal,
654 logmsg(LL_TRACE, "mds service principal: "
655 "doesn't belong to this node\n");
659 /* select this one */
660 code = krb5_copy_principal(ctx, kte.principal, &princ2);
662 logmsg(LL_ERR, "copy lustre_mds princ: %s\n",
668 logmsg(LL_TRACE, "release a lustre_root one\n");
669 krb5_free_principal(ctx, princ);
676 krb5_kt_end_seq_get(ctx, kt, &cursor);
679 logmsg(LL_ERR, "can't find proper keytab entry\n");
683 /* obtain root TGT */
684 rc = lkrb5_get_root_tgt_keytab(ctx, ccache, kt, princ, ccname);
686 krb5_free_principal(ctx, princ);
688 krb5_kt_close(ctx, kt);
693 int lkrb5_prepare_root_cred(struct lgss_cred *cred)
697 krb5_error_code code;
698 struct lgss_krb5_cred *kcred;
702 lassert(krb5_this_realm != NULL);
704 kcred = (struct lgss_krb5_cred *) cred->lc_mech_cred;
706 /* compose the TGT cc name */
707 snprintf(tgtcc, sizeof(tgtcc), "%s%s/%s%s_%s",
708 krb5_cc_type, krb5_cc_dir, krb5_cred_prefix,
709 krb5_cred_root_suffix, krb5_this_realm);
710 logmsg(LL_DEBUG, "root krb5 TGT ccname: %s\n", tgtcc);
712 /* compose the memory cc name, since the only user is myself,
713 * the name could be fixed
715 snprintf(kcred->kc_ccname, sizeof(kcred->kc_ccname),
716 "%s/self", krb5_cc_type_mem);
717 logmsg(LL_TRACE, "private cc: %s\n", kcred->kc_ccname);
719 code = krb5_init_context(&ctx);
721 logmsg(LL_ERR, "initialize krb5 context: %s\n",
726 code = krb5_cc_resolve(ctx, kcred->kc_ccname, &ccache);
728 logmsg(LL_ERR, "resolve krb5 cc %s: %s\n",
729 kcred->kc_ccname, krb5_err_msg(code));
734 * search and/or obtain root TGT credential.
735 * it touched global (on-disk) tgt cache, do it inside mutex locking
737 lgss_krb5_mutex_lock();
739 rc = lkrb5_check_root_tgt_cc(ctx, ccache, tgtcc);
741 rc = lkrb5_refresh_root_tgt_cc(ctx, ccache, tgtcc);
744 rc = lgss_krb5_set_ccache_name(kcred->kc_ccname);
746 lgss_krb5_mutex_unlock();
748 krb5_cc_close(ctx, ccache);
750 krb5_free_context(ctx);
752 logmsg(LL_DEBUG, "prepare root credentail %s\n", rc ? "failed" : "OK");
757 int lkrb5_prepare_user_cred(struct lgss_cred *cred)
759 struct lgss_krb5_cred *kcred;
762 lassert(krb5_this_realm == NULL);
764 kcred = (struct lgss_krb5_cred *) cred->lc_mech_cred;
767 * here we just specified a fix ccname, instead of searching
768 * entire cc dir. is this OK??
770 snprintf(kcred->kc_ccname, sizeof(kcred->kc_ccname),
772 krb5_cc_type, krb5_cc_dir, krb5_cred_prefix, cred->lc_uid);
773 logmsg(LL_DEBUG, "using krb5 cache name: %s\n", kcred->kc_ccname);
775 rc = lgss_krb5_set_ccache_name(kcred->kc_ccname);
777 logmsg(LL_ERR, "can't set krb5 ccache name: %s\n",
784 int lgss_krb5_prepare_cred(struct lgss_cred *cred)
786 struct lgss_krb5_cred *kcred;
789 kcred = malloc(sizeof(*kcred));
791 logmsg(LL_ERR, "can't allocate krb5 cred\n");
795 kcred->kc_ccname[0] = '\0';
796 kcred->kc_remove = 0;
797 cred->lc_mech_cred = kcred;
799 if (cred->lc_fl_root || cred->lc_fl_mds) {
800 if (lgss_krb5_get_local_realm())
803 rc = lkrb5_prepare_root_cred(cred);
805 rc = lkrb5_prepare_user_cred(cred);
812 void lgss_krb5_release_cred(struct lgss_cred *cred)
814 struct lgss_krb5_cred *kcred;
816 kcred = (struct lgss_krb5_cred *) cred->lc_mech_cred;
819 cred->lc_mech_cred = NULL;
822 struct lgss_mech_type lgss_mech_krb5 =
825 .lmt_mech_n = LGSS_MECH_KRB5,
826 .lmt_prepare_cred = lgss_krb5_prepare_cred,
827 .lmt_release_cred = lgss_krb5_release_cred,