1 /* -*- mode: c; c-basic-offset: 8; indent-tabs-mode: nil; -*-
2 * vim:expandtab:shiftwidth=8:tabstop=8:
4 * Modifications for Lustre
6 * Copyright 2008 Sun Microsystems, Inc. All rights reserved
8 * Author: Eric Mei <ericm@clusterfs.com>
12 * Adapted in part from MIT Kerberos 5-1.2.1 slave/kprop.c and from
13 * http://docs.sun.com/?p=/doc/816-1331/6m7oo9sms&a=view
15 * Copyright (c) 2002-2004 The Regents of the University of Michigan.
16 * All rights reserved.
18 * Andy Adamson <andros@umich.edu>
19 * J. Bruce Fields <bfields@umich.edu>
20 * Marius Aamodt Eriksen <marius@umich.edu>
21 * Kevin Coffman <kwc@umich.edu>
27 * Copyright 1990,1991 by the Massachusetts Institute of Technology.
28 * All Rights Reserved.
30 * Export of this software from the United States of America may
31 * require a specific license from the United States Government.
32 * It is the responsibility of any person or organization contemplating
33 * export to obtain such a license before exporting.
35 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
36 * distribute this software and its documentation for any purpose and
37 * without fee is hereby granted, provided that the above copyright
38 * notice appear in all copies and that both that copyright notice and
39 * this permission notice appear in supporting documentation, and that
40 * the name of M.I.T. not be used in advertising or publicity pertaining
41 * to distribution of the software without specific, written prior
42 * permission. Furthermore if you modify this software you must label
43 * your software as modified software and not distribute it in such a
44 * fashion that it might be confused with the original M.I.T. software.
45 * M.I.T. makes no representations about the suitability of
46 * this software for any purpose. It is provided "as is" without express
47 * or implied warranty.
51 * Copyright 1994 by OpenVision Technologies, Inc.
53 * Permission to use, copy, modify, distribute, and sell this software
54 * and its documentation for any purpose is hereby granted without fee,
55 * provided that the above copyright notice appears in all copies and
56 * that both that copyright notice and this permission notice appear in
57 * supporting documentation, and that the name of OpenVision not be used
58 * in advertising or publicity pertaining to distribution of the software
59 * without specific, written prior permission. OpenVision makes no
60 * representations about the suitability of this software for any
61 * purpose. It is provided "as is" without express or implied warranty.
63 * OPENVISION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
64 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
65 * EVENT SHALL OPENVISION BE LIABLE FOR ANY SPECIAL, INDIRECT OR
66 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF
67 * USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
68 * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
69 * PERFORMANCE OF THIS SOFTWARE.
74 Copyright (c) 2004 The Regents of the University of Michigan.
77 Redistribution and use in source and binary forms, with or without
78 modification, are permitted provided that the following conditions
81 1. Redistributions of source code must retain the above copyright
82 notice, this list of conditions and the following disclaimer.
83 2. Redistributions in binary form must reproduce the above copyright
84 notice, this list of conditions and the following disclaimer in the
85 documentation and/or other materials provided with the distribution.
86 3. Neither the name of the University nor the names of its
87 contributors may be used to endorse or promote products derived
88 from this software without specific prior written permission.
90 THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
91 WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
92 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
93 DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
94 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
95 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
96 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
97 BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
98 LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
99 NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
100 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
108 #include <sys/param.h>
109 //#include <rpc/rpc.h>
110 #include <sys/types.h>
111 #include <sys/stat.h>
112 #include <sys/utsname.h>
113 #include <sys/socket.h>
114 #include <arpa/inet.h>
125 #include <gssapi/gssapi.h>
126 #ifdef USE_PRIVATE_KRB5_FUNCTIONS
127 #include <gssapi/gssapi_krb5.h>
131 #include "lgss_utils.h"
132 #include "lgss_krb5_utils.h"
134 static void lgss_krb5_mutex_lock(void)
136 if (lgss_mutex_lock(LGSS_MUTEX_KRB5)) {
137 logmsg(LL_ERR, "can't lock process, abort!\n");
142 static void lgss_krb5_mutex_unlock(void)
144 if (lgss_mutex_unlock(LGSS_MUTEX_KRB5)) {
145 logmsg(LL_WARN, "can't unlock process, other processes "
146 "might need to wait long time\n");
152 * - currently we only support "normal" cache types: "FILE" and "MEMORY".
155 #define krb5_err_msg(code) error_message(code)
157 const char *krb5_cc_type_mem = "MEMORY:";
158 const char *krb5_cc_type_file = "FILE:";
160 char *krb5_this_realm = NULL;
161 char *krb5_keytab_file = "/etc/krb5.keytab";
162 char *krb5_cc_type = "FILE:";
163 char *krb5_cc_dir = "/tmp";
164 char *krb5_cred_prefix = "krb5cc_";
165 char *krb5_cred_root_suffix = "lustre_root";
167 struct lgss_krb5_cred {
169 int kc_remove; /* remove cache upon release */
173 int lgss_krb5_set_ccache_name(const char *ccname)
175 #ifdef USE_GSS_KRB5_CCACHE_NAME
176 unsigned int maj_stat, min_stat;
178 maj_stat = gss_krb5_ccache_name(&min_stat, ccname, NULL);
179 if (maj_stat != GSS_S_COMPLETE) {
180 logmsg(LL_ERR, "failed to set ccache name\n");
185 * Set the KRB5CCNAME environment variable to tell the krb5 code
186 * which credentials cache to use. (Instead of using the private
187 * function above for which there is no generic gssapi equivalent)
189 if (setenv("KRB5CCNAME", ccname, 1)) {
190 logmsg(LL_ERR, "set env of krb5 ccname: %s\n",
195 logmsg(LL_DEBUG, "set cc: %s\n", ccname);
200 int lgss_krb5_get_local_realm(void)
202 krb5_context context = NULL;
203 krb5_error_code code;
206 if (krb5_this_realm != NULL)
209 code = krb5_init_context(&context);
211 logmsg(LL_ERR, "init ctx: %s\n", krb5_err_msg(code));
215 code = krb5_get_default_realm(context, &krb5_this_realm);
217 logmsg(LL_ERR, "get default realm: %s\n", krb5_err_msg(code));
221 logmsg(LL_DEBUG, "Local realm: %s\n", krb5_this_realm);
224 krb5_free_context(context);
229 int princ_is_local_realm(krb5_context ctx, krb5_principal princ)
231 return (lgss_krb5_strcasecmp(krb5_princ_realm(ctx, princ),
232 krb5_this_realm) == 0);
236 int svc_princ_verify_host(krb5_context ctx,
237 krb5_principal princ,
240 struct utsname utsbuf;
241 struct hostent *host;
243 if (krb5_princ_component(ctx, princ, 1) == NULL) {
244 logmsg(loglevel, "service principal has no host part\n");
248 if (uname(&utsbuf)) {
249 logmsg(loglevel, "get UTS name: %s\n", strerror(errno));
253 host = gethostbyname(utsbuf.nodename);
255 logmsg(loglevel, "failed to get local hostname\n");
259 if (lgss_krb5_strcasecmp(krb5_princ_component(ctx, princ, 1),
261 logmsg(loglevel, "service principal: hostname %.*s "
262 "doesn't match localhost %s\n",
263 krb5_princ_component(ctx, princ, 1)->length,
264 krb5_princ_component(ctx, princ, 1)->data,
273 int lkrb5_cc_check_tgt_princ(krb5_context ctx,
275 krb5_principal princ)
277 logmsg(LL_TRACE, "principal: realm %.*s, type %d, size %d, name %.*s\n",
278 krb5_princ_realm(ctx, princ)->length,
279 krb5_princ_realm(ctx, princ)->data,
280 krb5_princ_type(ctx, princ),
281 krb5_princ_size(ctx, princ),
282 krb5_princ_name(ctx, princ)->length,
283 krb5_princ_name(ctx, princ)->data);
286 if (krb5_princ_type(ctx, princ) != KRB5_NT_PRINCIPAL) {
287 logmsg(LL_WARN, "principal type %d is not I want\n",
288 krb5_princ_type(ctx, princ));
292 /* check local realm */
293 if (!princ_is_local_realm(ctx, princ)) {
294 logmsg(LL_WARN, "principal realm %.*s not local: %s\n",
295 krb5_princ_realm(ctx, princ)->length,
296 krb5_princ_realm(ctx, princ)->data,
301 /* if it's mds service principal, or lustre_root principal
302 * with host part, verify the hostname.
303 * note we allow lustre_root without host part */
304 if (lgss_krb5_strcmp(krb5_princ_name(ctx, princ),
305 LGSS_SVC_MDS_STR) == 0) {
306 if (svc_princ_verify_host(ctx, princ, LL_WARN)) {
307 logmsg(LL_WARN, "mds service principal doesn't belong "
311 } else if (lgss_krb5_strcmp(krb5_princ_name(ctx, princ),
312 LGSS_USR_ROOT_STR) == 0) {
313 if (krb5_princ_component(ctx, princ, 1) != NULL &&
314 svc_princ_verify_host(ctx, princ, LL_WARN)) {
315 logmsg(LL_WARN, "lustre_root principal doesn't belong "
320 logmsg(LL_WARN, "unexpected krb5 cc principal name %.*s\n",
321 krb5_princ_name(ctx, princ)->length,
322 krb5_princ_name(ctx, princ)->data);
326 logmsg(LL_TRACE, "principal is OK\n");
331 * find out whether current TGT cache is valid or not
334 int lkrb5_check_root_tgt_cc(krb5_context ctx,
339 krb5_ccache tgt_ccache;
341 krb5_principal princ;
342 krb5_cc_cursor cursor;
343 krb5_error_code code;
346 int rc = -1, found = 0;
348 if (strncmp(ccname, krb5_cc_type, strlen(krb5_cc_type_file))) {
349 logmsg(LL_ERR, "unexpected cc type\n");
353 ccfile = ccname + strlen(krb5_cc_type_file);
354 logmsg(LL_TRACE, "cc file name: %s\n", ccfile);
356 /* firstly make sure the cache file is there */
357 if (stat(ccfile, &statbuf)) {
358 logmsg(LL_DEBUG, "krb5 cc %s: %s\n", ccname, strerror(errno));
362 /* prepare parsing the cache file */
363 code = krb5_cc_resolve(ctx, ccname, &tgt_ccache);
365 logmsg(LL_ERR, "resolve krb5 cc %s: %s\n",
366 ccname, krb5_err_msg(code));
370 /* checks the principal */
371 code = krb5_cc_get_principal(ctx, tgt_ccache, &princ);
373 logmsg(LL_ERR, "get cc principal: %s\n", krb5_err_msg(code));
377 if (lkrb5_cc_check_tgt_princ(ctx, tgt_ccache, princ)) {
378 logmsg(LL_WARN, "cc principal is not valid\n");
385 code = krb5_cc_start_seq_get(ctx, tgt_ccache, &cursor);
387 logmsg(LL_ERR, "start cc iteration: %s\n", krb5_err_msg(code));
393 krb5_timestamp duration, delta;
395 code = krb5_cc_next_cred(ctx, tgt_ccache, &cursor, &cred);
399 logmsg(LL_DEBUG, "cred: server realm %.*s, type %d, name %.*s; "
400 "time (%d-%d, renew till %d), valid %d\n",
401 krb5_princ_realm(ctx, cred.server)->length,
402 krb5_princ_realm(ctx, cred.server)->data,
403 krb5_princ_type(ctx, cred.server),
404 krb5_princ_name(ctx, cred.server)->length,
405 krb5_princ_name(ctx, cred.server)->data,
406 cred.times.starttime, cred.times.endtime,
407 cred.times.renew_till, cred.times.endtime - now);
410 * we found the princ type is always 0 (KRB5_NT_UNKNOWN), why???
413 /* FIXME how about inter-realm TGT??? FIXME */
414 if (lgss_krb5_strcasecmp(krb5_princ_name(ctx, cred.server),
418 if (lgss_krb5_strcasecmp(krb5_princ_realm(ctx, cred.server),
422 /* check validity of time */
423 delta = 60 * 30; /* half an hour */
424 duration = cred.times.endtime - cred.times.starttime;
425 if (duration / 4 < delta)
426 delta = duration / 4;
428 if (cred.times.starttime <= now &&
429 cred.times.endtime >= now + delta) {
436 logmsg(LL_DEBUG, "doesn't find good TGT cache\n");
440 /* found a good cred, store it into @ccache */
441 logmsg(LL_DEBUG, "found good TGT cache\n");
443 code = krb5_cc_initialize(ctx, ccache, princ);
445 logmsg(LL_ERR, "init private cc: %s\n", krb5_err_msg(code));
449 code = krb5_cc_store_cred(ctx, ccache, &cred);
451 logmsg(LL_ERR, "store private cred: %s\n", krb5_err_msg(code));
455 logmsg(LL_DEBUG, "store private ccache OK\n");
459 krb5_cc_end_seq_get(ctx, tgt_ccache, &cursor);
461 krb5_free_principal(ctx, princ);
463 krb5_cc_close(ctx, tgt_ccache);
469 int lkrb5_get_root_tgt_keytab(krb5_context ctx,
472 krb5_principal princ,
475 krb5_get_init_creds_opt opts;
477 krb5_ccache tgt_ccache;
478 krb5_error_code code;
481 krb5_get_init_creds_opt_init(&opts);
482 krb5_get_init_creds_opt_set_address_list(&opts, NULL);
484 * by default krb5 library obtain ticket with lifetime shorter
485 * than the max value. we can change it here if we want. but
486 * seems not necessary now.
488 krb5_get_init_creds_opt_set_tkt_life(&opts, very-long-time);
493 * obtain TGT and store into global ccache
495 code = krb5_get_init_creds_keytab(ctx, &cred, princ, kt,
498 logmsg(LL_ERR, "failed to get root TGT for "
499 "principal %.*s: %s\n",
500 krb5_princ_name(ctx, princ)->length,
501 krb5_princ_name(ctx, princ)->data,
506 code = krb5_cc_resolve(ctx, ccname, &tgt_ccache);
508 logmsg(LL_ERR, "resolve cc %s: %s\n",
509 ccname, krb5_err_msg(code));
513 code = krb5_cc_initialize(ctx, tgt_ccache, princ);
515 logmsg(LL_ERR, "initialize cc %s: %s\n",
516 ccname, krb5_err_msg(code));
520 code = krb5_cc_store_cred(ctx, tgt_ccache, &cred);
522 logmsg(LL_ERR, "store cred to cc %s: %s\n",
523 ccname, krb5_err_msg(code));
527 logmsg(LL_INFO, "installed TGT of %.*s in cc %s\n",
528 krb5_princ_name(ctx, princ)->length,
529 krb5_princ_name(ctx, princ)->data,
533 * now store the cred into my own cc too
535 code = krb5_cc_initialize(ctx, ccache, princ);
537 logmsg(LL_ERR, "init mem cc: %s\n", krb5_err_msg(code));
541 code = krb5_cc_store_cred(ctx, ccache, &cred);
543 logmsg(LL_ERR, "store mm cred: %s\n", krb5_err_msg(code));
547 logmsg(LL_DEBUG, "stored TGT into mem cc OK\n");
550 krb5_cc_close(ctx, tgt_ccache);
552 krb5_free_cred_contents(ctx, &cred);
557 * obtain a new root TGT
560 int lkrb5_refresh_root_tgt_cc(krb5_context ctx,
565 krb5_keytab_entry kte;
566 krb5_kt_cursor cursor;
567 krb5_principal princ = NULL, princ2;
568 krb5_error_code code;
569 int general_root = 0;
572 /* prepare parsing the keytab file */
573 code = krb5_kt_resolve(ctx, krb5_keytab_file, &kt);
575 logmsg(LL_ERR, "resolve keytab %s: %s\n",
576 krb5_keytab_file, krb5_err_msg(code));
580 code = krb5_kt_start_seq_get(ctx, kt, &cursor);
582 logmsg(LL_ERR, "start kt iteration: %s\n", krb5_err_msg(code));
586 /* iterate keytab to find proper a entry */
588 code = krb5_kt_next_entry(ctx, kt, &kte, &cursor);
592 logmsg(LL_TRACE, "kt entry: realm %.*s, type %d, "
593 "size %d, name %.*s\n",
594 krb5_princ_realm(ctx, kte.principal)->length,
595 krb5_princ_realm(ctx, kte.principal)->data,
596 krb5_princ_type(ctx, kte.principal),
597 krb5_princ_size(ctx, kte.principal),
598 krb5_princ_name(ctx, kte.principal)->length,
599 krb5_princ_name(ctx, kte.principal)->data);
601 if (!princ_is_local_realm(ctx, kte.principal))
604 /* lustre_root[/host]@realm */
605 if (lgss_krb5_strcmp(krb5_princ_name(ctx, kte.principal),
606 LGSS_USR_ROOT_STR) == 0) {
607 int tmp_general_root = 0;
609 if (krb5_princ_component(ctx, kte.principal,1) == NULL){
611 logmsg(LL_TRACE, "lustre_root: "
612 "already picked one, skip\n");
616 tmp_general_root = 1;
618 if (svc_princ_verify_host(ctx, kte.principal,
620 logmsg(LL_TRACE, "lustre_root: "
621 "doesn't belong to this node\n");
625 if (princ != NULL && !general_root) {
626 logmsg(LL_TRACE, "lustre_root: already "
627 "have a host-specific one, "
633 code = krb5_copy_principal(ctx, kte.principal, &princ2);
635 logmsg(LL_ERR, "copy lustre_root princ: %s\n",
641 logmsg(LL_TRACE, "release a lustre_root one\n");
642 krb5_free_principal(ctx, princ);
646 general_root = tmp_general_root;
650 /* lustre_mds/host@realm */
651 if (lgss_krb5_strcmp(krb5_princ_name(ctx, kte.principal),
652 LGSS_SVC_MDS_STR) == 0) {
653 if (svc_princ_verify_host(ctx, kte.principal,
655 logmsg(LL_TRACE, "mds service principal: "
656 "doesn't belong to this node\n");
660 /* select this one */
661 code = krb5_copy_principal(ctx, kte.principal, &princ2);
663 logmsg(LL_ERR, "copy lustre_mds princ: %s\n",
669 logmsg(LL_TRACE, "release a lustre_root one\n");
670 krb5_free_principal(ctx, princ);
677 krb5_kt_end_seq_get(ctx, kt, &cursor);
680 logmsg(LL_ERR, "can't find proper keytab entry\n");
684 /* obtain root TGT */
685 rc = lkrb5_get_root_tgt_keytab(ctx, ccache, kt, princ, ccname);
687 krb5_free_principal(ctx, princ);
689 krb5_kt_close(ctx, kt);
694 int lkrb5_prepare_root_cred(struct lgss_cred *cred)
698 krb5_error_code code;
699 struct lgss_krb5_cred *kcred;
703 lassert(krb5_this_realm != NULL);
705 kcred = (struct lgss_krb5_cred *) cred->lc_mech_cred;
707 /* compose the TGT cc name */
708 snprintf(tgtcc, sizeof(tgtcc), "%s%s/%s%s_%s",
709 krb5_cc_type, krb5_cc_dir, krb5_cred_prefix,
710 krb5_cred_root_suffix, krb5_this_realm);
711 logmsg(LL_DEBUG, "root krb5 TGT ccname: %s\n", tgtcc);
713 /* compose the memory cc name, since the only user is myself,
714 * the name could be fixed
716 snprintf(kcred->kc_ccname, sizeof(kcred->kc_ccname),
717 "%s/self", krb5_cc_type_mem);
718 logmsg(LL_TRACE, "private cc: %s\n", kcred->kc_ccname);
720 code = krb5_init_context(&ctx);
722 logmsg(LL_ERR, "initialize krb5 context: %s\n",
727 code = krb5_cc_resolve(ctx, kcred->kc_ccname, &ccache);
729 logmsg(LL_ERR, "resolve krb5 cc %s: %s\n",
730 kcred->kc_ccname, krb5_err_msg(code));
735 * search and/or obtain root TGT credential.
736 * it touched global (on-disk) tgt cache, do it inside mutex locking
738 lgss_krb5_mutex_lock();
740 rc = lkrb5_check_root_tgt_cc(ctx, ccache, tgtcc);
742 rc = lkrb5_refresh_root_tgt_cc(ctx, ccache, tgtcc);
745 rc = lgss_krb5_set_ccache_name(kcred->kc_ccname);
747 lgss_krb5_mutex_unlock();
749 krb5_cc_close(ctx, ccache);
751 krb5_free_context(ctx);
753 logmsg(LL_DEBUG, "prepare root credentail %s\n", rc ? "failed" : "OK");
758 int lkrb5_prepare_user_cred(struct lgss_cred *cred)
760 struct lgss_krb5_cred *kcred;
763 lassert(krb5_this_realm == NULL);
765 kcred = (struct lgss_krb5_cred *) cred->lc_mech_cred;
768 * here we just specified a fix ccname, instead of searching
769 * entire cc dir. is this OK??
771 snprintf(kcred->kc_ccname, sizeof(kcred->kc_ccname),
773 krb5_cc_type, krb5_cc_dir, krb5_cred_prefix, cred->lc_uid);
774 logmsg(LL_DEBUG, "using krb5 cache name: %s\n", kcred->kc_ccname);
776 rc = lgss_krb5_set_ccache_name(kcred->kc_ccname);
778 logmsg(LL_ERR, "can't set krb5 ccache name: %s\n",
785 int lgss_krb5_prepare_cred(struct lgss_cred *cred)
787 struct lgss_krb5_cred *kcred;
790 kcred = malloc(sizeof(*kcred));
792 logmsg(LL_ERR, "can't allocate krb5 cred\n");
796 kcred->kc_ccname[0] = '\0';
797 kcred->kc_remove = 0;
798 cred->lc_mech_cred = kcred;
800 if (cred->lc_fl_root || cred->lc_fl_mds) {
801 if (lgss_krb5_get_local_realm())
804 rc = lkrb5_prepare_root_cred(cred);
806 rc = lkrb5_prepare_user_cred(cred);
813 void lgss_krb5_release_cred(struct lgss_cred *cred)
815 struct lgss_krb5_cred *kcred;
817 kcred = (struct lgss_krb5_cred *) cred->lc_mech_cred;
820 cred->lc_mech_cred = NULL;
823 struct lgss_mech_type lgss_mech_krb5 =
826 .lmt_mech_n = LGSS_MECH_KRB5,
827 .lmt_prepare_cred = lgss_krb5_prepare_cred,
828 .lmt_release_cred = lgss_krb5_release_cred,