*/
/*
* This file is part of Lustre, http://www.lustre.org/
- * Lustre is a trademark of Sun Microsystems, Inc.
*
* lustre/utils/gss/lgss_keyring.c
*
#include <keyutils.h>
#include <gssapi/gssapi.h>
#include <sys/wait.h>
+#include <getopt.h>
#include <libcfs/util/param.h>
#include <libcfs/util/string.h>
+#include <uapi/linux/lustre/lgss.h>
#include "lsupport.h"
#include "lgss_utils.h"
+#include "lgss_krb5_utils.h"
#include "write_bytes.h"
#include "context.h"
return rc;
}
-int do_nego_rpc(struct lgss_nego_data *lnd,
- gss_buffer_desc *gss_token,
- struct lgss_init_res *gr,
- int req_fd[2], int reply_fd[2])
+static int do_nego_rpc(struct lgss_nego_data *lnd,
+ gss_buffer_desc *gss_token,
+ struct lgss_init_res *gr,
+ int req_fd[2], int reply_fd[2])
{
struct lgssd_ioctl_param param;
struct passwd *pw;
logmsg(LL_TRACE, "do_nego_rpc: to parse reply\n");
if (param.status) {
logmsg(LL_ERR, "status: %ld (%s)\n",
- param.status, strerror((int)(-param.status)));
+ (long int)param.status, strerror((int)(-param.status)));
return param.status;
}
&ret_flags,
NULL); /* time rec */
+ logmsg_gss(LL_TRACE, lnd->lnd_mech, maj_stat, min_stat,
+ "gss_init_sec_context");
+
+ logmsg(LL_TRACE, "send_token:\n");
+ log_hexl(LL_TRACE, send_token.value, send_token.length);
+
if (recv_tokenp != GSS_C_NO_BUFFER) {
gss_release_buffer(&min_stat, &gr.gr_token);
recv_tokenp = GSS_C_NO_BUFFER;
return 0;
}
-void lgssc_fini_nego_data(struct lgss_nego_data *lnd)
+static void lgssc_fini_nego_data(struct lgss_nego_data *lnd)
{
OM_uint32 maj_stat, min_stat;
}
}
-static
-int error_kernel_key(key_serial_t keyid, int rpc_error, int gss_error)
+static int fork_and_switch_id(int uid, pid_t *child)
{
- int seqwin = 0;
- char buf[32];
- char *p, *end;
+ int status, rc = 0;
+
+ *child = fork();
+ if (*child == -1) {
+ logmsg(LL_ERR, "cannot fork child for user %u: %s\n",
+ uid, strerror(errno));
+ rc = errno;
+ } else if (*child == 0) {
+ /* switch identity */
+ rc = switch_identity(uid);
+ if (rc)
+ rc = errno;
+ } else {
+ if (wait(&status) < 0) {
+ rc = errno;
+ logmsg(LL_ERR, "child %d failed: %s\n",
+ *child, strerror(rc));
+ } else {
+ rc = WEXITSTATUS(status);
+ if (rc)
+ logmsg(LL_ERR, "child %d terminated with %d\n",
+ *child, rc);
+ }
+ }
+ return rc;
+}
- logmsg(LL_TRACE, "revoking kernel key %08x\n", keyid);
+static int do_keyctl_update(char *reason, key_serial_t keyid,
+ const void *payload, size_t plen)
+{
+ while (keyctl_update(keyid, payload, plen)) {
+ if (errno != EAGAIN) {
+ logmsg(LL_ERR, "%se key %08x: %s\n",
+ reason, keyid, strerror(errno));
+ return -1;
+ }
- p = buf;
- end = buf + sizeof(buf);
+ logmsg(LL_WARN, "key %08x: %sing too soon, try again\n",
+ keyid, reason);
+ sleep(2);
+ }
- WRITE_BYTES(&p, end, seqwin);
- WRITE_BYTES(&p, end, rpc_error);
- WRITE_BYTES(&p, end, gss_error);
+ logmsg(LL_INFO, "key %08x: %sed\n", keyid, reason);
+ return 0;
+}
-again:
- if (keyctl_update(keyid, buf, p - buf)) {
- if (errno != EAGAIN) {
- logmsg(LL_ERR, "revoke key %08x: %s\n",
- keyid, strerror(errno));
- return -1;
- }
+static int error_kernel_key(key_serial_t keyid, int rpc_error, int gss_error,
+ uid_t uid)
+{
+ key_serial_t inst_keyring = KEY_SPEC_SESSION_KEYRING;
+ pid_t child = 1;
+ int seqwin = 0;
+ char *p, *end;
+ char buf[32];
+ int rc;
- logmsg(LL_WARN, "key %08x: revoking too soon, try again\n",
- keyid);
- sleep(2);
- goto again;
- }
+ logmsg(LL_TRACE, "revoking kernel key %08x\n", keyid);
- logmsg(LL_INFO, "key %08x: revoked\n", keyid);
- return 0;
+ /* Only possessor and uid can update the key. So for a user key that is
+ * linked to the user keyring, switch uid/gid in a subprocess to not
+ * change identity in main process.
+ */
+ if (uid) {
+ rc = fork_and_switch_id(uid, &child);
+ if (rc || child)
+ goto out;
+ inst_keyring = KEY_SPEC_USER_KEYRING;
+ }
+
+ p = buf;
+ end = buf + sizeof(buf);
+
+ WRITE_BYTES(&p, end, seqwin);
+ WRITE_BYTES(&p, end, rpc_error);
+ WRITE_BYTES(&p, end, gss_error);
+
+ rc = do_keyctl_update("revok", keyid, buf, p - buf);
+ if (rc)
+ goto out;
+ rc = keyctl_unlink(keyid, inst_keyring);
+ if (rc)
+ logmsg(LL_ERR, "unlink key %08x from %d: %s\n",
+ keyid, inst_keyring, strerror(errno));
+ else
+ logmsg(LL_INFO, "key %08x: unlinked from %d\n",
+ keyid, inst_keyring);
+
+out:
+ if (child == 0)
+ /* job done for child */
+ exit(rc);
+ return rc;
}
-static
-int update_kernel_key(key_serial_t keyid,
- struct lgss_nego_data *lnd,
- gss_buffer_desc *ctx_token)
+static int update_kernel_key(key_serial_t keyid,
+ struct lgss_nego_data *lnd,
+ gss_buffer_desc *ctx_token)
{
- char *buf = NULL, *p = NULL, *end = NULL;
- unsigned int buf_size = 0;
- int rc;
-
- logmsg(LL_TRACE, "updating kernel key %08x\n", keyid);
-
- buf_size = sizeof(lnd->lnd_seq_win) +
- sizeof(lnd->lnd_rmt_ctx.length) + lnd->lnd_rmt_ctx.length +
- sizeof(ctx_token->length) + ctx_token->length;
- buf = malloc(buf_size);
- if (buf == NULL) {
- logmsg(LL_ERR, "key %08x: can't alloc update buf: size %d\n",
- keyid, buf_size);
- return 1;
- }
+ char *buf = NULL, *p = NULL, *end = NULL;
+ unsigned int buf_size = 0;
+ pid_t child = 1;
+ int uid, rc = 0;
- p = buf;
- end = buf + buf_size;
- rc = -1;
-
- if (WRITE_BYTES(&p, end, lnd->lnd_seq_win))
- goto out;
- if (write_buffer(&p, end, &lnd->lnd_rmt_ctx))
- goto out;
- if (write_buffer(&p, end, ctx_token))
- goto out;
-
-again:
- if (keyctl_update(keyid, buf, p - buf)) {
- if (errno != EAGAIN) {
- logmsg(LL_ERR, "update key %08x: %s\n",
- keyid, strerror(errno));
- goto out;
- }
+ logmsg(LL_TRACE, "updating kernel key %08x\n", keyid);
- logmsg(LL_DEBUG, "key %08x: updating too soon, try again\n",
- keyid);
- sleep(2);
- goto again;
- }
+ /* Only possessor and uid can update the key. So for a user key that is
+ * linked to the user keyring, switch uid/gid in a subprocess to not
+ * change identity in main process.
+ */
+ uid = lnd->lnd_uid;
+ if (uid) {
+ rc = fork_and_switch_id(uid, &child);
+ if (rc || child)
+ goto out;
+ }
+
+ buf_size = sizeof(lnd->lnd_seq_win) +
+ sizeof(lnd->lnd_rmt_ctx.length) + lnd->lnd_rmt_ctx.length +
+ sizeof(ctx_token->length) + ctx_token->length;
+ buf = malloc(buf_size);
+ if (buf == NULL) {
+ logmsg(LL_ERR, "key %08x: can't alloc update buf: size %d\n",
+ keyid, buf_size);
+ rc = ENOMEM;
+ goto out;
+ }
+
+ p = buf;
+ end = buf + buf_size;
+ rc = -1;
+
+ if (WRITE_BYTES(&p, end, lnd->lnd_seq_win))
+ goto out;
+ if (write_buffer(&p, end, &lnd->lnd_rmt_ctx))
+ goto out;
+ if (write_buffer(&p, end, ctx_token))
+ goto out;
+
+ rc = do_keyctl_update("updat", keyid, buf, p - buf);
- rc = 0;
- logmsg(LL_DEBUG, "key %08x: updated\n", keyid);
out:
- free(buf);
- return rc;
+ free(buf);
+ if (child == 0)
+ /* job done for child */
+ exit(rc);
+ return rc;
}
static int lgssc_kr_negotiate_krb(key_serial_t keyid, struct lgss_cred *cred,
if (lgss_get_service_str(&g_service, kup->kup_svc, kup->kup_nid)) {
logmsg(LL_ERR, "key %08x: failed to construct service "
"string\n", keyid);
- error_kernel_key(keyid, -EACCES, 0);
+ error_kernel_key(keyid, -EACCES, 0, cred->lc_uid);
goto out_cred;
}
if (lgss_using_cred(cred)) {
logmsg(LL_ERR, "key %08x: can't using cred\n", keyid);
- error_kernel_key(keyid, -EACCES, 0);
+ error_kernel_key(keyid, -EACCES, 0, cred->lc_uid);
goto out_cred;
}
if (lgssc_init_nego_data(&lnd, kup, cred->lc_mech->lmt_mech_n)) {
logmsg(LL_ERR, "key %08x: failed to initialize "
"negotiation data\n", keyid);
- error_kernel_key(keyid, lnd.lnd_rpc_err, lnd.lnd_gss_err);
+ error_kernel_key(keyid, lnd.lnd_rpc_err, lnd.lnd_gss_err,
+ cred->lc_uid);
goto out_cred;
}
goto retry_nego;
} else if (rc) {
logmsg(LL_ERR, "key %08x: failed to negotiation\n", keyid);
- error_kernel_key(keyid, lnd.lnd_rpc_err, lnd.lnd_gss_err);
+ error_kernel_key(keyid, lnd.lnd_rpc_err, lnd.lnd_gss_err,
+ cred->lc_uid);
goto out;
}
- rc = serialize_context_for_kernel(lnd.lnd_ctx, &lnd.lnd_ctx_token,
+ rc = serialize_context_for_kernel(&lnd.lnd_ctx, &lnd.lnd_ctx_token,
lnd.lnd_mech);
if (rc) {
logmsg(LL_ERR, "key %08x: failed to export context\n", keyid);
- error_kernel_key(keyid, rc, lnd.lnd_gss_err);
+ error_kernel_key(keyid, rc, lnd.lnd_gss_err, cred->lc_uid);
goto out;
}
keyid, kup->kup_uid);
out:
if (lnd.lnd_ctx_token.length != 0)
- gss_release_buffer(&min_stat, &lnd.lnd_ctx_token);
+ (void)gss_release_buffer(&min_stat, &lnd.lnd_ctx_token);
lgssc_fini_nego_data(&lnd);
if (rc) {
logmsg(LL_ERR, "key %08x: failed to construct service "
"string\n", keyid);
- error_kernel_key(keyid, -EACCES, 0);
+ error_kernel_key(keyid, -EACCES, 0, 0);
goto out_cred;
}
rc = lgss_using_cred(cred);
if (rc) {
logmsg(LL_ERR, "key %08x: can't use cred\n", keyid);
- error_kernel_key(keyid, -EACCES, 0);
+ error_kernel_key(keyid, -EACCES, 0, 0);
goto out_cred;
}
if (rc) {
logmsg(LL_ERR, "key %08x: failed to initialize "
"negotiation data\n", keyid);
- error_kernel_key(keyid, lnd.lnd_rpc_err, lnd.lnd_gss_err);
+ error_kernel_key(keyid, lnd.lnd_rpc_err, lnd.lnd_gss_err, 0);
goto out_cred;
}
goto retry;
} else if (rc) {
logmsg(LL_ERR, "key %08x: failed to negotiate\n", keyid);
- error_kernel_key(keyid, lnd.lnd_rpc_err, lnd.lnd_gss_err);
+ error_kernel_key(keyid, lnd.lnd_rpc_err, lnd.lnd_gss_err, 0);
goto out;
}
static int associate_with_ns(char *path)
{
-#ifdef HAVE_SETNS
int fd, rc = -1;
fd = open(path, O_RDONLY);
}
return rc;
-#else
- return -1;
-#endif /* HAVE_SETNS */
}
static int prepare_and_instantiate(struct lgss_cred *cred, key_serial_t keyid,
uint32_t uid)
{
key_serial_t inst_keyring;
+ bool prepared = true;
+ pid_t child = 1;
+ int rc = 0;
if (lgss_prepare_cred(cred)) {
logmsg(LL_ERR, "key %08x: failed to prepare credentials "
"for user %d\n", keyid, uid);
- return 1;
+ /* prepare failed, but still instantiate the key for regular
+ * user, so that it can be used to report the error later
+ * in the process
+ */
+ if (cred->lc_root_flags)
+ return 1;
+ prepared = false;
}
- /* pre initialize the key. note the keyring linked to is actually of the
- * original requesting process, not _this_ upcall process. if it's for
+ /* Pre initialize the key. Note the keyring linked to is actually of the
+ * original requesting process, not _this_ upcall process. If it's for
* root user, don't link to any keyrings because we want fully control
- * on it, and share it among all root sessions; otherswise link to
- * session keyring.
+ * on it, and share it among all root sessions.
+ * Otherswise link to user keyring, which requires switching uid/gid.
+ * Do this in a subprocess because other operations need privileges.
*/
- if (cred->lc_root_flags != 0)
+ if (cred->lc_root_flags) {
inst_keyring = 0;
- else
- inst_keyring = KEY_SPEC_SESSION_KEYRING;
+ } else {
+ inst_keyring = KEY_SPEC_USER_KEYRING;
+ /* fork to not change identity in main process */
+ rc = fork_and_switch_id(uid, &child);
+ if (rc || child)
+ goto out;
+ }
- if (keyctl_instantiate(keyid, NULL, 0, inst_keyring)) {
- logmsg(LL_ERR, "instantiate key %08x: %s\n",
- keyid, strerror(errno));
- return 1;
+ /* if dealing with a user key, grant user write permission,
+ * it will be required for key update
+ */
+ if (child == 0) {
+ key_perm_t perm;
+
+ perm = KEY_POS_VIEW | KEY_POS_WRITE | KEY_POS_SEARCH |
+ KEY_POS_LINK | KEY_POS_SETATTR |
+ KEY_USR_VIEW | KEY_USR_WRITE;
+ if (keyctl_setperm(keyid, perm))
+ logmsg(LL_ERR, "setperm %08x on key %08x: %s\n",
+ perm, keyid, strerror(errno));
}
- logmsg(LL_TRACE, "instantiated kernel key %08x\n", keyid);
+ rc = keyctl_instantiate(keyid, NULL, 0, inst_keyring);
+ if (rc) {
+ rc = errno;
+ logmsg(LL_ERR, "instantiate key %08x in keyring id %d: %s\n",
+ keyid, inst_keyring, strerror(rc));
+ } else {
+ logmsg(LL_TRACE,
+ "instantiated kernel key %08x in keyring id %d\n",
+ keyid, inst_keyring);
+ }
- return 0;
+out:
+ if (child == 0)
+ /* job done for child */
+ exit(rc);
+ return prepared ? rc : 1;
}
/****************************************
int main(int argc, char *argv[])
{
- struct keyring_upcall_param uparam;
- key_serial_t keyid;
- key_serial_t sring;
- pid_t child;
- int req_fd[2] = { -1, -1 };
- int reply_fd[2] = { -1, -1 };
- struct lgss_mech_type *mech;
- struct lgss_cred *cred;
- char path[PATH_MAX] = "";
- int other_ns = 0;
- int rc = 0;
-#ifdef HAVE_SETNS
- struct stat parent_ns = { .st_ino = 0 };
- struct stat caller_ns = { .st_ino = 0 };
-#endif
+ struct keyring_upcall_param uparam;
+ key_serial_t keyid;
+ key_serial_t sring;
+ pid_t child;
+ int req_fd[2] = { -1, -1 };
+ int reply_fd[2] = { -1, -1 };
+ struct lgss_mech_type *mech;
+ struct lgss_cred *cred;
+ char path[PATH_MAX] = "";
+ int other_ns = 0;
+ int rc = 0, opt;
+ struct stat parent_ns = { .st_ino = 0 };
+ struct stat caller_ns = { .st_ino = 0 };
+
+ static struct option long_opts[] = {
+ { .name = "realm", .has_arg = required_argument, .val = 'R'},
+ { .name = NULL, } };
set_log_level();
logmsg(LL_TRACE, "start parsing parameters\n");
+
+ /* one possible option before upcall parameters: -R REALM */
+ while ((opt = getopt_long(argc, argv, "R:", long_opts, NULL)) != EOF) {
+ switch (opt) {
+ case 'R':
+ lgss_client_realm = optarg;
+ break;
+ default:
+ logmsg(LL_ERR, "invalid parameter %s\n",
+ argv[optind - 1]);
+ return 1;
+ }
+ }
+
+ if (lgss_client_realm) {
+ /* shift args to meet expected upcall parameters */
+ argc -= optind - 1;
+ argv += optind - 1;
+ }
+
/*
* parse & sanity check upcall parameters
* expected to be called with:
return 1;
}
- logmsg(LL_INFO, "key %s, desc %s, ugid %s:%s, sring %s, coinfo %s\n",
- argv[2], argv[4], argv[6], argv[7], argv[10], argv[5]);
-
memset(&uparam, 0, sizeof(uparam));
if (strcmp(argv[1], "create") != 0) {
- logmsg(LL_ERR, "invalid OP %s\n", argv[1]);
+ logmsg(LL_ERR,
+ "invalid OP %s (key %s, desc %s, ugid %s:%s, sring %s, coinfo %s)\n",
+ argv[1], argv[2], argv[4], argv[6], argv[7], argv[10],
+ argv[5]);
return 1;
}
if (sscanf(argv[2], "%d", &keyid) != 1) {
- logmsg(LL_ERR, "can't extract KeyID: %s\n", argv[2]);
+ logmsg(LL_ERR,
+ "can't extract KeyID: %s (key %s, desc %s, ugid %s:%s, sring %s, coinfo %s)\n",
+ argv[2], argv[2], argv[4], argv[6], argv[7], argv[10],
+ argv[5]);
return 1;
}
+ logmsg(LL_INFO, "key %08x, desc %s, ugid %s:%s, sring %s, coinfo %s\n",
+ keyid, argv[4], argv[6], argv[7], argv[10], argv[5]);
+
if (sscanf(argv[6], "%d", &uparam.kup_fsuid) != 1) {
logmsg(LL_ERR, "can't extract UID: %s\n", argv[6]);
return 1;
cred->lc_svc_type = uparam.kup_svc_type;
cred->lc_self_nid = uparam.kup_selfnid;
-#ifdef HAVE_SETNS
/* Is caller in different namespace? */
- snprintf(path, sizeof(path), "/proc/%d/ns/mnt", getpid());
- if (stat(path, &parent_ns))
- logmsg(LL_ERR, "cannot stat %s: %s\n", path, strerror(errno));
- snprintf(path, sizeof(path), "/proc/%d/ns/mnt", uparam.kup_pid);
- if (stat(path, &caller_ns))
- logmsg(LL_ERR, "cannot stat %s: %s\n", path, strerror(errno));
- if (caller_ns.st_ino != parent_ns.st_ino) {
- other_ns = 1;
+ /* If passed caller's pid is 0, it means we have to stick
+ * with current namespace.
+ */
+ if (uparam.kup_pid) {
+ snprintf(path, sizeof(path), "/proc/%d/ns/mnt", getpid());
+ if (stat(path, &parent_ns)) {
+ logmsg(LL_DEBUG, "cannot stat %s: %s\n",
+ path, strerror(errno));
+ } else {
+ snprintf(path, sizeof(path), "/proc/%d/ns/mnt",
+ uparam.kup_pid);
+ if (stat(path, &caller_ns))
+ logmsg(LL_DEBUG, "cannot stat %s: %s\n",
+ path, strerror(errno));
+ else if (caller_ns.st_ino != parent_ns.st_ino)
+ other_ns = 1;
+ }
}
-#endif /* HAVE_SETNS */
/*
* if caller's namespace is different, fork a child and associate it
* with caller's namespace to do credentials preparation
*/
if (other_ns) {
- logmsg(LL_TRACE, "caller's namespace is diffent\n");
+ logmsg(LL_TRACE, "caller's namespace is different\n");
/* use pipes to pass info between child and parent processes */
if (pipe(req_fd) == -1) {
close(req_fd[1]);
close(reply_fd[0]);
close(reply_fd[1]);
+ lgss_fini(cred);
return rc;
} else {
- logmsg(LL_TRACE, "caller's namespace is the same\n");
-
+ if (uparam.kup_pid)
+ logmsg(LL_TRACE, "caller's namespace is the same\n");
+ else
+ logmsg(LL_TRACE, "stick with current namespace\n");
+
+ /* In case of prepare error, a key will be instantiated
+ * all the same. But then we will have to error this key
+ * instead of doing normal gss negotiation.
+ */
rc = prepare_and_instantiate(cred, keyid, uparam.kup_uid);
- if (rc != 0)
- return rc;
/*
* fork a child to do the real gss negotiation
if (child == -1) {
logmsg(LL_ERR, "key %08x: can't create child: %s\n",
keyid, strerror(errno));
- return 1;
+ rc = 1;
+ goto out_reg;
} else if (child == 0) {
- return lgssc_kr_negotiate(keyid, cred, &uparam,
- req_fd, reply_fd);
+ if (rc)
+ rc = error_kernel_key(keyid, -ENOKEY, 0,
+ cred->lc_uid);
+ else
+ rc = lgssc_kr_negotiate(keyid, cred, &uparam,
+ req_fd, reply_fd);
+ goto out_reg;
+ } else {
+ logmsg(LL_TRACE, "forked child %d\n", child);
+ return 0;
}
- logmsg(LL_TRACE, "forked child %d\n", child);
- return 0;
+out_reg:
+ lgss_fini(cred);
+ return rc;
}
}