Viewing: svcgssd_proc.c

/*
 * svc_in_gssd_proc.c
 *
 * Copyright (c) 2000 The Regents of the University of Michigan.
 * All rights reserved.
 *
 * Copyright (c) 2002 Bruce Fields <bfields@UMICH.EDU>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the University nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/param.h>
#include <sys/stat.h>

#include <inttypes.h>
#include <pwd.h>
#include <stdio.h>
#include <unistd.h>
#include <ctype.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#ifdef HAVE_NETDB_H
# include <netdb.h>
#endif

#include <stdbool.h>

#include "svcgssd.h"
#include "gss_util.h"
#include "err_util.h"
#include "context.h"
#include "cacheio.h"
#include "lsupport.h"
#include "gss_oids.h"
#include <time.h>
#include <linux/lustre/lustre_idl.h>
#include "sk_utils.h"
#include <sys/time.h>
#include <gssapi/gssapi_krb5.h>
#include <libcfs/util/param.h>

struct svc_cred {
	uint32_t cr_remote;
	uint32_t cr_usr_root;
	uint32_t cr_usr_mds;
	uint32_t cr_usr_oss;
	uid_t    cr_uid;
	uid_t    cr_mapped_uid;
	uid_t    cr_gid;
};

struct svc_nego_data {
	/* kernel data*/
	uint32_t	lustre_svc;
	lnet_nid_t	nid;
	uint64_t	handle_seq;
	char		nm_name[LUSTRE_NODEMAP_NAME_LENGTH + 1];
	gss_buffer_desc	in_tok;
	gss_buffer_desc	out_tok;
	gss_buffer_desc	in_handle;
	gss_buffer_desc	out_handle;
	uint32_t	maj_stat;
	uint32_t	min_stat;

	/* userspace data */
	gss_OID			mech;
	gss_ctx_id_t		ctx;
	gss_buffer_desc		ctx_token;
};

static int do_svc_downcall(gss_buffer_desc *out_handle, struct svc_cred *cred,
			   gss_OID mechoid, gss_buffer_desc *ctx_token,
			   char *nodemap)
{
	struct rsc_downcall_data *rsc_dd;
	int blen, fd, size, rc = -1;
	const char *mechname;
	glob_t path;
	char *bp;

	printerr(LL_INFO, "doing downcall\n");

	size = out_handle->length + sizeof(__u32) +
		ctx_token->length + sizeof(__u32);
	blen = size;

	size += offsetof(struct rsc_downcall_data, scd_val[0]);
	rsc_dd = calloc(1, size);
	if (!rsc_dd) {
		printerr(LL_ERR, "malloc downcall data (%d) failed\n", size);
		return -ENOMEM;
	}
	rsc_dd->scd_magic = RSC_DOWNCALL_MAGIC;
	rsc_dd->scd_err = 0;

	rsc_dd->scd_flags =
		(cred->cr_remote ? RSC_DATA_FLAG_REMOTE : 0) |
		(cred->cr_usr_root ? RSC_DATA_FLAG_ROOT : 0) |
		(cred->cr_usr_mds ? RSC_DATA_FLAG_MDS : 0) |
		(cred->cr_usr_oss ? RSC_DATA_FLAG_OSS : 0);
	rsc_dd->scd_mapped_uid = cred->cr_mapped_uid;
	rsc_dd->scd_uid = cred->cr_uid;
	rsc_dd->scd_gid = cred->cr_gid;
	mechname = gss_OID_mech_name(mechoid);
	if (mechname == NULL)
		goto out;
	if (snprintf(rsc_dd->scd_mechname, sizeof(rsc_dd->scd_mechname),
		     "%s", mechname) >= sizeof(rsc_dd->scd_mechname))
		goto out;
	if (nodemap && nodemap[0] != '\0') {
		if (snprintf(rsc_dd->scd_nmname, sizeof(rsc_dd->scd_nmname),
			     "%s", nodemap) >= sizeof(rsc_dd->scd_nmname))
			goto out;
	} else {
		rsc_dd->scd_nmname[0] = '\0';
	}

	bp = rsc_dd->scd_val;
	gss_buffer_write(&bp, &blen, out_handle->value, out_handle->length);
	gss_buffer_write(&bp, &blen, ctx_token->value, ctx_token->length);
	if (blen < 0) {
		printerr(LL_ERR, "ERROR: %s: message too long > %d\n",
			 __func__, size);
		rc = -EMSGSIZE;
		goto out;
	}
	rsc_dd->scd_len = bp - rsc_dd->scd_val;

	rc = cfs_get_param_paths(&path, RSC_DOWNCALL_PATH);
	if (rc != 0) {
		rc = -errno;
		goto out;
	}

	fd = open(path.gl_pathv[0], O_WRONLY);
	if (fd == -1) {
		rc = -errno;
		printerr(LL_ERR, "ERROR: %s: open %s failed: %s\n",
			 __func__, RSC_DOWNCALL_PATH, strerror(-rc));
		goto out_path;
	}
	size = offsetof(struct rsc_downcall_data,
			scd_val[bp - rsc_dd->scd_val]);
	printerr(LL_DEBUG, "writing downcall data, size %d\n", size);
	if (write(fd, rsc_dd, size) == -1) {
		rc = -errno;
		printerr(LL_ERR, "ERROR: %s failed: %s\n",
			 __func__, strerror(-rc));
	}
	printerr(LL_DEBUG, "downcall data written ok\n");

	close(fd);
out_path:
	cfs_free_param_data(&path);
out:
	free(rsc_dd);
	if (rc)
		printerr(LL_ERR, "ERROR: downcall failed\n");
	return rc;
}

#define RPCSEC_GSS_SEQ_WIN	5

static int send_response(int auth_res, __u64 hash,
			gss_buffer_desc *in_handle, gss_buffer_desc *in_token,
			u_int32_t maj_stat, u_int32_t min_stat,
			gss_buffer_desc *out_handle, gss_buffer_desc *out_token)
{
	struct rsi_downcall_data *rsi_dd;
	int blen, fd, size, rc = 0;
	glob_t path;
	char *bp;

	printerr(LL_INFO, "sending reply\n");

	size = in_handle->length + sizeof(__u32) +
		in_token->length + sizeof(__u32) +
		sizeof(__u32) + sizeof(__u32);
	if (!auth_res)
		size += out_handle->length + out_token->length;
	blen = size;

	size += offsetof(struct rsi_downcall_data, sid_val[0]);
	rsi_dd = calloc(1, size);
	if (!rsi_dd) {
		printerr(LL_ERR, "malloc downcall data (%d) failed\n", size);
		return -ENOMEM;
	}
	rsi_dd->sid_magic = RSI_DOWNCALL_MAGIC;
	rsi_dd->sid_hash = hash;
	rsi_dd->sid_maj_stat = maj_stat;
	rsi_dd->sid_min_stat = min_stat;

	bp = rsi_dd->sid_val;
	gss_buffer_write(&bp, &blen, in_handle->value, in_handle->length);
	gss_buffer_write(&bp, &blen, in_token->value, in_token->length);
	if (!auth_res) {
		gss_buffer_write(&bp, &blen, out_handle->value,
				 out_handle->length);
		gss_buffer_write(&bp, &blen, out_token->value,
				 out_token->length);
	} else {
		rsi_dd->sid_err = -EACCES;
		gss_buffer_write(&bp, &blen, NULL, 0);
		gss_buffer_write(&bp, &blen, NULL, 0);
	}
	if (blen < 0) {
		printerr(LL_ERR, "ERROR: %s: message too long > %d\n",
			 __func__, size);
		rc = -EMSGSIZE;
		goto out;
	}
	rsi_dd->sid_len = bp - rsi_dd->sid_val;

	rc = cfs_get_param_paths(&path, RSI_DOWNCALL_PATH);
	if (rc != 0) {
		rc = -errno;
		printerr(LL_ERR, "ERROR: %s: cannot get param path %s: %s\n",
			 __func__, RSI_DOWNCALL_PATH, strerror(-rc));
		goto out;
	}

	fd = open(path.gl_pathv[0], O_WRONLY);
	if (fd == -1) {
		rc = -errno;
		printerr(LL_ERR, "ERROR: %s: open %s failed: %s\n",
			 __func__, RSI_DOWNCALL_PATH, strerror(-rc));
		goto out_path;
	}
	size = offsetof(struct rsi_downcall_data,
			sid_val[bp - rsi_dd->sid_val]);
	printerr(LL_DEBUG, "writing response, size %d\n", size);
	if (write(fd, rsi_dd, size) == -1) {
		rc = -errno;
		printerr(LL_ERR, "ERROR: %s failed: %s\n",
			 __func__, strerror(-rc));
	} else {
		printerr(LL_DEBUG, "response written ok\n");
	}

	close(fd);
out_path:
	cfs_free_param_data(&path);
out:
	free(rsi_dd);
	return rc;
}

#define rpc_auth_ok			0
#define rpc_autherr_badcred		1
#define rpc_autherr_rejectedcred	2
#define rpc_autherr_badverf		3
#define rpc_autherr_rejectedverf	4
#define rpc_autherr_tooweak		5
#define rpcsec_gsserr_credproblem	13
#define rpcsec_gsserr_ctxproblem	14

static int lookup_localname(gss_name_t client_name, char *princ, lnet_nid_t nid,
			    uid_t *uid)
{
	u_int32_t maj_stat, min_stat;
	gss_buffer_desc	localname;
	char *sname;
	int rc = -1;

	*uid = -1;
	maj_stat = gss_localname(&min_stat, client_name, GSS_C_NO_OID,
				 &localname);
	if (maj_stat != GSS_S_COMPLETE) {
		printerr(LL_INFO, "no local name for %s/%#Lx\n", princ, nid);
		return rc;
	}

	sname = calloc(localname.length + 1, 1);
	if (!sname) {
		printerr(LL_ERR, "%s: error allocating %zu bytes\n",
			 __func__, localname.length + 1);
		goto free;
	}
	memcpy(sname, localname.value, localname.length);
	sname[localname.length] = '\0';

	*uid = parse_uid(sname);
	free(sname);
	printerr(LL_WARN, "found local uid: %s ==> %d\n", princ, *uid);
	rc = 0;

free:
	gss_release_buffer(&min_stat, &localname);
	return rc;
}

static int lookup_id(gss_name_t client_name, char *princ, lnet_nid_t nid,
		     uid_t *uid)
{
	return lookup_localname(client_name, princ, nid, uid);
}

static int get_ids(gss_name_t client_name, gss_OID mech, struct svc_cred *cred,
		   lnet_nid_t nid, uint32_t lustre_svc, char *nm_buf,
		   size_t nm_buflen)
{
	char *sname, *host, *realm, *service_nm = NULL;
	gss_OID name_type = GSS_C_NO_OID;
	u_int32_t maj_stat, min_stat;
	const int max_namelen = 512;
	char hostname[max_namelen];
	gss_buffer_desc name;
	struct passwd *pw;
	int res = -1;

	cred->cr_remote = 0;
	cred->cr_usr_root = cred->cr_usr_mds = cred->cr_usr_oss = 0;
	cred->cr_uid = cred->cr_mapped_uid = cred->cr_gid = -1;
	nm_buf[0] = '\0';

	maj_stat = gss_display_name(&min_stat, client_name, &name, &name_type);
	if (maj_stat != GSS_S_COMPLETE) {
		pgsserr("get_ids: gss_display_name",
			maj_stat, min_stat, mech);
		return -1;
	}
	/* be certain name.length+1 doesn't overflow */
	if (name.length >= 0xffff ||
	    !(sname = calloc(name.length + 1, 1))) {
		printerr(LL_ERR,
			 "ERROR: %s: error allocating %zu bytes for sname\n",
			 __func__, name.length + 1);
		gss_release_buffer(&min_stat, &name);
		return -1;
	}
	memcpy(sname, name.value, name.length);
	sname[name.length] = '\0';
	gss_release_buffer(&min_stat, &name);

	if ((lustre_svc == LUSTRE_GSS_SVC_MDS ||
	     lustre_svc == LUSTRE_GSS_SVC_MGS) &&
	    lookup_id(client_name, sname, nid, &cred->cr_mapped_uid))
		printerr(LL_DEBUG, "no id found for %s\n", sname);

	realm = strchr(sname, '@');
	if (realm) {
		*realm++ = '\0';
	} else {
		printerr(LL_ERR, "ERROR: %s has no realm name\n", sname);
		goto out_free;
	}

	host = strchr(sname, '/');
	if (host)
		*host++ = '\0';

	if (strcmp(sname, GSSD_SERVICE_MGS) == 0) {
		printerr(LL_ERR, "forbid %s as a user name\n", sname);
		goto out_free;
	}

	/* No host for GSSD_SERVICE_ROLE, just <NODEMAP_NAME> */
	if (strcmp(sname, GSSD_SERVICE_ROLE) == 0) {
		service_nm = host;
		host = NULL;
		if (nm_buflen < LUSTRE_NODEMAP_NAME_LENGTH + 1 ||
		    strlen(service_nm) > LUSTRE_NODEMAP_NAME_LENGTH) {
			printerr(LL_ERR, "invalid creds %s/%s@%s from %s\n",
				 sname, service_nm, realm, libcfs_nid2str(nid));
			goto out_free;
		}
		snprintf(nm_buf, nm_buflen, "%s", service_nm);
	}

	/* 1. check host part */
	if (host) {
		/* host part is in the form <HOSTNAME>/<NODEMAP_NAME> */
		service_nm = strchr(host, '/');
		if (!service_nm)
			goto hostcheck;

		*service_nm++ = '\0';
		if (nm_buflen < LUSTRE_NODEMAP_NAME_LENGTH + 1 ||
		    strlen(service_nm) > LUSTRE_NODEMAP_NAME_LENGTH) {
			/* ignore invalid nm name, could be something else */
			*(service_nm - 1) = '/';
			service_nm = NULL;
			goto hostcheck;
		}
		snprintf(nm_buf, nm_buflen, "%s", service_nm);

hostcheck:
		if (lnet_nid2hostname(nid, hostname, max_namelen)) {
			printerr(LL_ERR,
				 "ERROR: failed to resolve hostname for %s/%s%s%s@%s from %s\n",
				 sname, host, service_nm ? "/" : "",
				 service_nm ?: "", realm, libcfs_nid2str(nid));
			goto out_free;
		}

		if (strcasecmp(host, hostname)) {
			printerr(LL_ERR,
				 "ERROR: %s/%s%s%s@%s claimed hostname doesn't match %s, nid %s\n",
				 sname, host, service_nm ? "/" : "",
				 service_nm ?: "", realm,
				 hostname, libcfs_nid2str(nid));
			goto out_free;
		}
	} else {
		if (strcmp(sname, GSSD_SERVICE_MDS) == 0 ||
		    strcmp(sname, GSSD_SERVICE_OSS) == 0) {
			printerr(LL_ERR,
				 "ERROR: %s@%s from %s doesn't bind with hostname\n",
				 sname, realm, libcfs_nid2str(nid));
			goto out_free;
		}
	}

	/* 2. check realm and user */
	switch (lustre_svc) {
	case LUSTRE_GSS_SVC_MDS:
		if (strcasecmp(mds_local_realm, realm) != 0) {
			/* Remote realm case */
			cred->cr_remote = 1;

			/* Prevent access to unmapped user from remote realm */
			if (cred->cr_mapped_uid == -1) {
				printerr(LL_ERR,
					 "ERROR: %s%s%s%s%s@%s from %s is remote but without mapping\n",
					 sname, host ? "/" : "",
					 host ?: "", service_nm ? "/" : "",
					 service_nm ?: "", realm,
					 libcfs_nid2str(nid));
				break;
			}
			goto valid;
		}

		/* Now we know we are dealing with a local realm */

		if (strcmp(sname, LUSTRE_ROOT_NAME) == 0 ||
		    strcmp(sname, GSSD_SERVICE_HOST) == 0 ||
		    strcmp(sname, GSSD_SERVICE_ROLE) == 0) {
			cred->cr_uid = 0;
			cred->cr_usr_root = 1;
			goto valid;
		}
		if (!strcmp(sname, GSSD_SERVICE_MDS)) {
			cred->cr_uid = 0;
			cred->cr_usr_mds = 1;
			goto valid;
		}
		if (!strcmp(sname, GSSD_SERVICE_OSS)) {
			cred->cr_uid = 0;
			cred->cr_usr_oss = 1;
			goto valid;
		}
		if (cred->cr_mapped_uid != -1) {
			printerr(LL_INFO,
				 "user %s from %s is mapped to %u\n",
				 sname, libcfs_nid2str(nid),
				 cred->cr_mapped_uid);
			goto valid;
		}
		pw = getpwnam(sname);
		if (pw != NULL) {
			cred->cr_uid = pw->pw_uid;
			printerr(LL_INFO, "%s resolve to uid %u\n",
				 sname, cred->cr_uid);
			goto valid;
		}
		printerr(LL_ERR, "ERROR: invalid user, %s%s%s%s%s@%s from %s\n",
			 sname, host ? "/" : "", host ?: "",
			 service_nm ? "/" : "", service_nm ?: "",
			 realm, libcfs_nid2str(nid));
		break;

valid:
		res = 0;
		break;
	case LUSTRE_GSS_SVC_MGS:
		if (!strcmp(sname, GSSD_SERVICE_OSS)) {
			cred->cr_uid = 0;
			cred->cr_usr_oss = 1;
		}
		fallthrough;
	case LUSTRE_GSS_SVC_OSS:
		if (strcmp(sname, LUSTRE_ROOT_NAME) == 0 ||
		    strcmp(sname, GSSD_SERVICE_HOST) == 0 ||
		    strcmp(sname, GSSD_SERVICE_ROLE) == 0) {
			cred->cr_uid = 0;
			cred->cr_usr_root = 1;
		} else if (!strcmp(sname, GSSD_SERVICE_MDS)) {
			cred->cr_uid = 0;
			cred->cr_usr_mds = 1;
		}
		if (cred->cr_mapped_uid != -1) {
			printerr(LL_INFO,
				 "user %s from %s is mapped to %u\n",
				 sname, libcfs_nid2str(nid),
				 cred->cr_mapped_uid);
			goto valid;
		}
		if (cred->cr_uid == -1) {
			printerr(LL_ERR,
				 "ERROR: svc %d doesn't accept user %s from %s\n",
				 lustre_svc, sname, libcfs_nid2str(nid));
			break;
		}
		res = 0;
		break;
	default:
		assert(0);
	}

out_free:
	if (!res)
		printerr(LL_WARN, "%s: authenticated %s%s%s%s%s@%s from %s\n",
			 lustre_svc_name[lustre_svc], sname,
			 host ? "/" : "", host ?: "",
			 service_nm ? "/" : "", service_nm ?: "",
			 realm, libcfs_nid2str(nid));
	free(sname);
	return res;
}

/**
 * nodemap_lookup_by_sha() - Send a nodemap ioctl that takes the sha256 of name
 *			     as input and receives a nodemap name in the reply.
 * @sha:		 sha256 of nodemap name
 * @nodemap_name:	 buffer to store returned nodemap name
 * @nodemap_name_bufsz:	 size of the nodemap_name buffer
 *
 * Return:
 * * %0		success
 * * %-errno	on failure
 */
int nodemap_lookup_by_sha(gss_buffer_desc *sha, char *nodemap_name,
			  int nodemap_name_bufsz)
{
	struct lustre_cfg_bufs bufs;
	struct lustre_cfg *lcfg = NULL;
	struct obd_ioctl_data data;
	char rawbuf[MAX_IOC_BUFLEN];
	char *buf = rawbuf;
	int rc;

	if (!sha->value || !sha->length || !nodemap_name ||
	    nodemap_name_bufsz < LUSTRE_NODEMAP_NAME_LENGTH + 1)
		return -EINVAL;

	llapi_register_ioc_dev(OBD_DEV_ID, OBD_DEV_PATH);
	if (srv_ioc_dev < 0) {
		rc = set_srv_ioc_dev();
		if (rc < 0) {
			printerr(LL_ERR, "no device for ioctl: %s\n",
				 strerror(-rc));
			goto out;
		}
	}

	memset(&data, 0, sizeof(data));
	data.ioc_dev = srv_ioc_dev;
	data.ioc_version = OBD_IOCTL_VERSION;

	lustre_cfg_bufs_reset(&bufs, NULL);
	lustre_cfg_bufs_set(&bufs, 1, sha->value, sha->length);

	lcfg = malloc(lustre_cfg_len(bufs.lcfg_bufcount, bufs.lcfg_buflen));
	if (!lcfg) {
		errno = ENOMEM;
		rc = -errno;
		goto out;
	}
	lustre_cfg_init(lcfg, LCFG_NODEMAP_LOOKUP_SHA, &bufs);
	rc = lustre_cfg_sanity_check(lcfg, lustre_cfg_len(bufs.lcfg_bufcount,
							  bufs.lcfg_buflen));
	if (rc)
		goto out;
	data.ioc_type = LUSTRE_CFG_TYPE;
	data.ioc_plen1 = lustre_cfg_len(lcfg->lcfg_bufcount,
					lcfg->lcfg_buflens);
	data.ioc_pbuf1 = (void *)lcfg;

	memset(buf, 0, sizeof(rawbuf));
	rc = llapi_ioctl_pack(&data, &buf, sizeof(rawbuf));
	if (rc) {
		printerr(LL_ERR, "invalid ioctl input: %s\n", strerror(-rc));
		goto out;
	}

	rc = l_ioctl(OBD_DEV_ID, OBD_IOC_NODEMAP, buf);
	if (rc < 0) {
		rc = -errno;
		printerr(LL_ERR, "nodemap ioctl failed: %s\n", strerror(-rc));
		goto out;
	}

	rc = llapi_ioctl_unpack(&data, buf, sizeof(rawbuf));
	if (rc) {
		printerr(LL_ERR, "cannot unpack ioctl response: %s\n",
			 strerror(-rc));
		goto out;
	}

	if (data.ioc_plen1 < LUSTRE_NODEMAP_NAME_LENGTH + 1) {
		rc = -EINVAL;
		goto out;
	}
	memcpy(nodemap_name, data.ioc_pbuf1, LUSTRE_NODEMAP_NAME_LENGTH + 1);

out:
	/* close OBD_DEV_ID now that we do not need it anymore */
	llapi_unregister_ioc_dev(OBD_DEV_ID);
	free(lcfg);
	return rc;
}

static int handle_sk(struct svc_nego_data *snd)
{
#ifdef HAVE_OPENSSL_SSK
	struct sk_cred *skc = NULL;
	struct svc_cred cred;
	gss_buffer_desc bufs[SK_INIT_BUFFERS];
	gss_buffer_desc remote_pub_key = GSS_C_EMPTY_BUFFER;
	char *target, *nmname_out = NULL;
	uint32_t rc = GSS_S_DEFECTIVE_TOKEN;
	uint32_t version;
	uint32_t flags;
	void *user_keys = NULL;
	void *keyp = NULL;
	int attempts = 0, i, ret;

	printerr(LL_DEBUG, "Handling sk request\n");
	memset(bufs, 0, sizeof(gss_buffer_desc) * SK_INIT_BUFFERS);

	/* See lgss_sk_using_cred() for client side token formation.
	 * Decoding initiator buffers */
	i = sk_decode_netstring(bufs, SK_INIT_BUFFERS, &snd->in_tok);
	if (i < SK_INIT_BUFFERS) {
		printerr(LL_ERR,
			 "Invalid netstring token received from peer\n");
		goto cleanup_buffers;
	}

	/* Allowing for a larger length first buffer in the future */
	if (bufs[SK_INIT_VERSION].length < sizeof(version)) {
		printerr(LL_ERR, "Invalid version received (wrong size)\n");
		goto cleanup_buffers;
	}
	memcpy(&version, bufs[SK_INIT_VERSION].value, sizeof(version));
	version = be32toh(version);
	if (version != SK_MSG_VERSION) {
		printerr(LL_ERR, "Invalid version received: %d\n", version);
		goto cleanup_buffers;
	}

	rc = GSS_S_FAILURE;

	/* target must be a null terminated string */
	i = bufs[SK_INIT_TARGET].length - 1;
	target = bufs[SK_INIT_TARGET].value;
	if (i >= 0 && target[i] != '\0') {
		printerr(LL_ERR, "Invalid target from netstring\n");
		goto cleanup_buffers;
	}

	if (bufs[SK_INIT_FLAGS].length != sizeof(flags)) {
		printerr(LL_ERR, "Invalid flags from netstring\n");
		goto cleanup_buffers;
	}
	memcpy(&flags, bufs[SK_INIT_FLAGS].value, sizeof(flags));

	/* Check that the cluster hash matches the hash of nodemap name */
	rc = sk_verify_hash(snd->nm_name, EVP_sha256(), &bufs[SK_INIT_NODEMAP]);
	if (rc != GSS_S_COMPLETE) {
		/* sha256 of nodemap name contained in request from client does
		 * not match nodemap inferred by server from client NID.
		 * So try to fetch nodemap name corresponding to client sha256.
		 */
		ret = nodemap_lookup_by_sha(&bufs[SK_INIT_NODEMAP],
					    snd->nm_name, sizeof(snd->nm_name));
		if (ret) {
			printerr(LL_ERR,
				 "Cluster hash failed validation: 0x%x\n", rc);
			goto cleanup_buffers;
		}
	}
	nmname_out = snd->nm_name;
	printerr(LL_DEBUG, "Using nodemap name %s for authentication\n",
		 snd->nm_name);

create_cred:
	sk_free_cred(skc);
	skc = sk_create_cred(target, snd->nm_name, NULL, be32toh(flags),
			     &user_keys, &keyp);
	if (!skc) {
		printerr(LL_ERR, "Failed to create sk credentials\n");
		goto cleanup_buffers;
	}

	/* Verify that the peer has used a prime size greater or equal to
	 * the size specified in the key file which may contain only zero
	 * fill but the size specifies the mimimum supported size on
	 * servers */
	if (skc->sc_flags & LGSS_SVC_PRIV &&
	    bufs[SK_INIT_P].length < skc->sc_p.length) {
		printerr(LL_ERR,
			 "Peer DHKE prime does not meet the size required by keyfile: %zd bits\n",
			 skc->sc_p.length * 8);
		goto create_cred;
	}

	/* Throw out the p from the server and use the wire data */
	free(skc->sc_p.value);
	skc->sc_p.value = NULL;
	skc->sc_p.length = 0;

	/* Take control of all the allocated buffers from decoding */
	if (bufs[SK_INIT_RANDOM].length !=
	    sizeof(skc->sc_kctx.skc_peer_random)) {
		printerr(LL_ERR, "Invalid size for client random\n");
		goto create_cred;
	}

	memcpy(&skc->sc_kctx.skc_peer_random, bufs[SK_INIT_RANDOM].value,
	       sizeof(skc->sc_kctx.skc_peer_random));
	skc->sc_p = bufs[SK_INIT_P];
	remote_pub_key = bufs[SK_INIT_PUB_KEY];
	skc->sc_nodemap_hash = bufs[SK_INIT_NODEMAP];
	skc->sc_hmac = bufs[SK_INIT_HMAC];

	/* Verify HMAC from peer.  Ideally this would happen before anything
	 * else but we don't have enough information to lookup key without the
	 * token (fsname and cluster_hash) so it's done after. */
	rc = sk_verify_hmac(skc, bufs, SK_INIT_BUFFERS - 1, EVP_sha256(),
			    &skc->sc_hmac);
	if (rc != GSS_S_COMPLETE) {
		printerr(LL_ERR, "HMAC verification error: 0x%x from peer %s\n",
			 rc, libcfs_nid2str((lnet_nid_t)snd->nid));
		skc->sc_p.value = NULL;
		skc->sc_p.length = 0;
		skc->sc_nodemap_hash.value = NULL;
		skc->sc_nodemap_hash.length = 0;
		skc->sc_hmac.value = NULL;
		skc->sc_hmac.length = 0;
		goto create_cred;
	}

redo:
	rc = sk_gen_params(skc, *sk_dh_checks);
	if (rc != GSS_S_COMPLETE) {
		printerr(LL_ERR,
			 "Failed to generate DH params for responder\n");
		goto cleanup_partial;
	}
	rc = sk_compute_dh_key(skc, &remote_pub_key);
	if (rc == GSS_S_BAD_QOP && attempts < 2) {
		/* GSS_S_BAD_QOP means the generated shared key was shorter
		 * than expected. Just retry twice before giving up.
		 */
		attempts++;
		if (skc->sc_params) {
			EVP_PKEY_free(skc->sc_params);
			skc->sc_params = NULL;
		}
		if (skc->sc_pub_key.value) {
			free(skc->sc_pub_key.value);
			skc->sc_pub_key.value = NULL;
		}
		skc->sc_pub_key.length = 0;
		if (skc->sc_dh_shared_key.value) {
			/* erase secret key before freeing memory */
			memset(skc->sc_dh_shared_key.value, 0,
			       skc->sc_dh_shared_key.length);
			free(skc->sc_dh_shared_key.value);
			skc->sc_dh_shared_key.value = NULL;
		}
		skc->sc_dh_shared_key.length = 0;
		goto redo;
	} else if (rc != GSS_S_COMPLETE) {
		printerr(LL_ERR,
			 "Failed to compute session key from DH params\n");
		goto cleanup_partial;
	}

	/* Cleanup init buffers we have copied or don't need anymore */
	free(bufs[SK_INIT_VERSION].value);
	free(bufs[SK_INIT_RANDOM].value);
	free(bufs[SK_INIT_TARGET].value);
	free(bufs[SK_INIT_FLAGS].value);

	/* Server reply contains the servers public key, random,  and HMAC */
	version = htobe32(SK_MSG_VERSION);
	bufs[SK_RESP_VERSION].value = &version;
	bufs[SK_RESP_VERSION].length = sizeof(version);
	bufs[SK_RESP_RANDOM].value = &skc->sc_kctx.skc_host_random;
	bufs[SK_RESP_RANDOM].length = sizeof(skc->sc_kctx.skc_host_random);
	bufs[SK_RESP_PUB_KEY] = skc->sc_pub_key;
	if (sk_sign_bufs(&skc->sc_kctx.skc_shared_key, bufs,
			 SK_RESP_BUFFERS - 1, EVP_sha256(),
			 &skc->sc_hmac)) {
		printerr(LL_ERR, "Failed to sign parameters\n");
		goto out_err;
	}
	bufs[SK_RESP_HMAC] = skc->sc_hmac;
	if (sk_encode_netstring(bufs, SK_RESP_BUFFERS, &snd->out_tok)) {
		printerr(LL_ERR, "Failed to encode netstring for token\n");
		goto out_err;
	}
	printerr(LL_INFO, "Created netstring of %zd bytes\n",
		 snd->out_tok.length);

	if (sk_session_kdf(skc, snd->nid, &snd->in_tok, &snd->out_tok)) {
		printerr(LL_ERR, "Failed to calculate derived session key\n");
		goto out_err;
	}
	if (sk_compute_keys(skc)) {
		printerr(LL_ERR,
			 "Failed to compute HMAC and encryption keys\n");
		goto out_err;
	}
	if (sk_serialize_kctx(skc, &snd->ctx_token)) {
		printerr(LL_ERR, "Failed to serialize context for kernel\n");
		goto out_err;
	}

	snd->out_handle.length = sizeof(snd->handle_seq);
	memcpy(snd->out_handle.value, &snd->handle_seq,
	       sizeof(snd->handle_seq));
	snd->maj_stat = GSS_S_COMPLETE;

	/* fix credentials */
	memset(&cred, 0, sizeof(cred));
	cred.cr_mapped_uid = -1;

	if (skc->sc_flags & LGSS_ROOT_CRED_ROOT)
		cred.cr_usr_root = 1;
	if (skc->sc_flags & LGSS_ROOT_CRED_MDT)
		cred.cr_usr_mds = 1;
	if (skc->sc_flags & LGSS_ROOT_CRED_OST)
		cred.cr_usr_oss = 1;

	do_svc_downcall(&snd->out_handle, &cred, snd->mech, &snd->ctx_token,
			nmname_out);

	/* cleanup ctx_token, out_tok is cleaned up in handle_channel_request */
	if (remote_pub_key.length != 0) {
		free(remote_pub_key.value);
		remote_pub_key.value = NULL;
		remote_pub_key.length = 0;
	}
	if (snd->ctx_token.value) {
		free(snd->ctx_token.value);
		snd->ctx_token.value = NULL;
		snd->ctx_token.length = 0;
	}

	printerr(LL_DEBUG, "sk returning success\n");
	return 0;

cleanup_buffers:
	for (i = 0; i < SK_INIT_BUFFERS; i++)
		free(bufs[i].value);
	sk_free_cred(skc);
	free(user_keys);
	snd->maj_stat = rc;
	return -1;

cleanup_partial:
	free(bufs[SK_INIT_VERSION].value);
	free(bufs[SK_INIT_RANDOM].value);
	free(bufs[SK_INIT_TARGET].value);
	free(bufs[SK_INIT_FLAGS].value);
	if (remote_pub_key.length != 0) {
		free(remote_pub_key.value);
		remote_pub_key.value = NULL;
		remote_pub_key.length = 0;
	}
	sk_free_cred(skc);
	snd->maj_stat = rc;
	return -1;

out_err:
	snd->maj_stat = rc;
	if (snd->ctx_token.value) {
		free(snd->ctx_token.value);
		snd->ctx_token.value = NULL;
		snd->ctx_token.length = 0;
	}
	if (remote_pub_key.length != 0) {
		free(remote_pub_key.value);
		remote_pub_key.value = NULL;
		remote_pub_key.length = 0;
	}
	sk_free_cred(skc);
	printerr(LL_DEBUG, "sk returning failure\n");
#else /* !HAVE_OPENSSL_SSK */
	printerr(LL_ERR, "ERROR: shared key subflavour is not enabled\n");
#endif /* HAVE_OPENSSL_SSK */
	return -1;
}

static int handle_null(struct svc_nego_data *snd)
{
	struct svc_cred cred;
	uint64_t tmp;
	uint32_t flags;

	/* null just uses the same token as the return token and for
	 * for sending to the kernel.  It is a single uint64_t. */
	if (snd->in_tok.length != sizeof(uint64_t)) {
		snd->maj_stat = GSS_S_DEFECTIVE_TOKEN;
		printerr(LL_ERR, "Invalid token size (%zd) received\n",
			 snd->in_tok.length);
		return -1;
	}
	snd->out_tok.length = snd->in_tok.length;
	snd->out_tok.value = malloc(snd->out_tok.length);
	if (!snd->out_tok.value) {
		snd->maj_stat = GSS_S_FAILURE;
		printerr(LL_ERR, "Failed to allocate out_tok\n");
		return -1;
	}

	snd->ctx_token.length = snd->in_tok.length;
	snd->ctx_token.value = malloc(snd->ctx_token.length);
	if (!snd->ctx_token.value) {
		snd->maj_stat = GSS_S_FAILURE;
		printerr(LL_ERR, "Failed to allocate ctx_token\n");
		return -1;
	}

	snd->out_handle.length = sizeof(snd->handle_seq);
	memcpy(snd->out_handle.value, &snd->handle_seq,
	       sizeof(snd->handle_seq));
	snd->maj_stat = GSS_S_COMPLETE;

	memcpy(&tmp, snd->in_tok.value, sizeof(tmp));
	tmp = be64toh(tmp);
	flags = (uint32_t)(tmp & 0x00000000ffffffff);
	memset(&cred, 0, sizeof(cred));
	cred.cr_mapped_uid = -1;

	if (flags & LGSS_ROOT_CRED_ROOT)
		cred.cr_usr_root = 1;
	if (flags & LGSS_ROOT_CRED_MDT)
		cred.cr_usr_mds = 1;
	if (flags & LGSS_ROOT_CRED_OST)
		cred.cr_usr_oss = 1;

	do_svc_downcall(&snd->out_handle, &cred, snd->mech, &snd->ctx_token,
			NULL);

	/* cleanup ctx_token, out_tok is cleaned up in handle_channel_req */
	free(snd->ctx_token.value);
	snd->ctx_token.length = 0;

	return 0;
}

static int handle_krb(struct svc_nego_data *snd)
{
	gss_buffer_desc ignore_out_tok = {.value = NULL};
	char nodemap[LUSTRE_NODEMAP_NAME_LENGTH + 1];
	gss_OID mech = GSS_C_NO_OID;
	u_int32_t ignore_min_stat;
	gss_name_t client_name;
	gss_cred_id_t svc_cred;
	struct svc_cred cred;
	u_int32_t ret_flags;

	svc_cred = gssd_select_svc_cred(snd->lustre_svc);
	if (!svc_cred) {
		printerr(LL_ERR, "no service credential for svc %u\n",
			 snd->lustre_svc);
		goto out_err;
	}

	snd->maj_stat = gss_accept_sec_context(&snd->min_stat, &snd->ctx,
					       svc_cred, &snd->in_tok,
					       GSS_C_NO_CHANNEL_BINDINGS,
					       &client_name, &mech,
					       &snd->out_tok, &ret_flags, NULL,
					       NULL);

	if (snd->maj_stat == GSS_S_CONTINUE_NEEDED) {
		printerr(LL_WARN,
			 "gss_accept_sec_context GSS_S_CONTINUE_NEEDED\n");

		/* Save the context handle for future calls */
		snd->out_handle.length = sizeof(snd->ctx);
		memcpy(snd->out_handle.value, &snd->ctx, sizeof(snd->ctx));
		return 0;
	} else if (snd->maj_stat != GSS_S_COMPLETE) {
		printerr(LL_ERR, "ERROR: gss_accept_sec_context failed\n");
		pgsserr("handle_krb: gss_accept_sec_context",
			snd->maj_stat, snd->min_stat, mech);
		goto out_err;
	}

	if (get_ids(client_name, mech, &cred, snd->nid,
		    snd->lustre_svc, nodemap, sizeof(nodemap))) {
		/* get_ids() prints error msg */
		snd->maj_stat = GSS_S_BAD_NAME; /* XXX ? */
		gss_release_name(&ignore_min_stat, &client_name);
		goto out_err;
	}
	gss_release_name(&ignore_min_stat, &client_name);
	if (nodemap[0])
		printerr(LL_DEBUG, "using nodemap %s for authentication\n",
			 nodemap);

	/* Context complete. Pass handle_seq in out_handle to use
	 * for context lookup in the kernel. */
	snd->out_handle.length = sizeof(snd->handle_seq);
	memcpy(snd->out_handle.value, &snd->handle_seq,
	       sizeof(snd->handle_seq));

	/* kernel needs ctx to calculate verifier on null response, so
	 * must give it context before doing null call: */
	if (serialize_context_for_kernel(&snd->ctx, &snd->ctx_token, mech)) {
		printerr(LL_ERR,
			 "ERROR: %s: serialize_context_for_kernel failed\n",
			__func__);
		snd->maj_stat = GSS_S_FAILURE;
		goto out_err;
	}

	/* heimdal/MIT implementations do not delete context at all */
	if (snd->ctx != GSS_C_NO_CONTEXT)
		gss_delete_sec_context(&ignore_min_stat, &snd->ctx,
				       &ignore_out_tok);

	do_svc_downcall(&snd->out_handle, &cred, mech,
			&snd->ctx_token, nodemap);
	/* We no longer need the context token */
	if (snd->ctx_token.length)
		(void)gss_release_buffer(&ignore_min_stat, &snd->ctx_token);
	return 0;

out_err:
	if (snd->ctx != GSS_C_NO_CONTEXT)
		gss_delete_sec_context(&ignore_min_stat, &snd->ctx,
				       &ignore_out_tok);

	return 1;
}

int handle_channel_request(int fd)
{
	char in_handle_buf[15];
	char out_handle_buf[15];
	uint32_t lustre_mech;
	static char *lbuf;
	static ssize_t lbuflen;
	static char *cp;
	int get_len;
	int rc;
	u_int32_t ignore_min_stat;
	struct svc_nego_data snd = {
		.in_tok.value		= NULL,
		.in_handle.value	= in_handle_buf,
		.out_handle.value	= out_handle_buf,
		.maj_stat		= GSS_S_FAILURE,
		.ctx			= GSS_C_NO_CONTEXT,
	};
	__u64 hash = 0;
	__u64 tmp_lustre_svc = 0;

	printerr(LL_INFO, "handling request\n");
	if (readline(fd, &lbuf, &lbuflen) != 1) {
		printerr(LL_ERR, "ERROR: failed reading request\n");
		return -1;
	}

	cp = lbuf;

	/* see rsi_do_upcall() for the format of data being input here */
	rc = gss_u64_read_string(&cp, &hash);
	if (rc < 0) {
		printerr(LL_ERR, "ERROR: failed parsing request: hash\n");
		goto out_err;
	}
	rc = gss_u64_read_string(&cp, &tmp_lustre_svc);
	if (rc < 0) {
		printerr(LL_ERR, "ERROR: failed parsing request: lustre svc\n");
		goto out_err;
	}
	snd.lustre_svc = tmp_lustre_svc;
	/* lustre_svc is the svc and gss subflavor */
	lustre_mech = (snd.lustre_svc & LUSTRE_GSS_MECH_MASK) >>
		LUSTRE_GSS_MECH_SHIFT;
	snd.lustre_svc = snd.lustre_svc & LUSTRE_GSS_SVC_MASK;
	switch (lustre_mech) {
	case LGSS_MECH_KRB5:
		if (!krb_enabled) {
			static time_t next_krb;

			if (time(NULL) > next_krb) {
				printerr(LL_WARN,
					 "warning: Request for kerberos but service support not enabled\n");
				next_krb = time(NULL) + 3600;
			}
			goto ignore;
		}
		snd.mech = &krb5oid;
		break;
	case LGSS_MECH_NULL:
		if (!null_enabled) {
			static time_t next_null;

			if (time(NULL) > next_null) {
				printerr(LL_WARN,
					 "warning: Request for gssnull but service support not enabled\n");
				next_null = time(NULL) + 3600;
			}
			goto ignore;
		}
		snd.mech = &nulloid;
		break;
	case LGSS_MECH_SK:
		if (!sk_enabled) {
			static time_t next_ssk;

			if (time(NULL) > next_ssk) {
				printerr(LL_WARN,
					 "warning: Request for SSK but service support not %s\n",
#ifdef HAVE_OPENSSL_SSK
					 "enabled"
#else
					 "included"
#endif
					);
				next_ssk = time(NULL) + 3600;
			}

			goto ignore;
		}
		snd.mech = &skoid;
		break;
	default:
		printerr(LL_ERR, "WARNING: invalid mechanism recevied: %d\n",
			 lustre_mech);
		goto out_err;
		break;
	}

	rc = gss_u64_read_string(&cp, (__u64 *)&snd.nid);
	if (rc < 0) {
		printerr(LL_ERR, "ERROR: failed parsing request: source nid\n");
		goto out_err;
	}
	rc = gss_u64_read_string(&cp, (__u64 *)&snd.handle_seq);
	if (rc < 0) {
		printerr(LL_ERR, "ERROR: failed parsing request: handle seq\n");
		goto out_err;
	}
	get_len = gss_string_read(&cp, snd.nm_name, sizeof(snd.nm_name), 0);
	if (get_len <= 0) {
		printerr(LL_ERR,
			 "ERROR: failed parsing request: nodemap name\n");
		goto out_err;
	}
	snd.nm_name[get_len] = '\0';
	printerr(LL_INFO,
		 "handling req: svc %u, nid %016llx, idx %"PRIx64" nodemap %s\n",
		 snd.lustre_svc, snd.nid, snd.handle_seq, snd.nm_name);

	get_len = gss_base64url_decode(&cp, snd.in_handle.value,
				       sizeof(in_handle_buf));
	if (get_len < 0) {
		printerr(LL_ERR, "ERROR: failed parsing request: in handle\n");
		goto out_err;
	}
	snd.in_handle.length = (size_t)get_len;

	printerr(LL_DEBUG, "in_handle:\n");
	print_hexl(3, snd.in_handle.value, snd.in_handle.length);

	snd.in_tok.value = malloc(strlen(cp));
	if (!snd.in_tok.value) {
		printerr(LL_ERR, "ERROR: failed alloc for in token\n");
		goto out_err;
	}
	get_len = gss_base64url_decode(&cp, snd.in_tok.value, strlen(cp));
	if (get_len < 0) {
		printerr(LL_ERR, "ERROR: failed parsing request: in token\n");
		goto out_err;
	}
	snd.in_tok.length = (size_t)get_len;

	printerr(LL_DEBUG, "in_tok:\n");
	print_hexl(3, snd.in_tok.value, snd.in_tok.length);

	if (snd.in_handle.length != 0) { /* CONTINUE_INIT case */
		if (snd.in_handle.length != sizeof(snd.ctx)) {
			printerr(LL_ERR,
				 "ERROR: input handle has unexpected length %zu\n",
				 snd.in_handle.length);
			goto out_err;
		}
		/* in_handle is the context id stored in the out_handle
		 * for the GSS_S_CONTINUE_NEEDED case below.  */
		memcpy(&snd.ctx, snd.in_handle.value, snd.in_handle.length);
	}

	rc = -1;
	if (lustre_mech == LGSS_MECH_KRB5)
		rc = handle_krb(&snd);
	else if (lustre_mech == LGSS_MECH_SK)
		rc = handle_sk(&snd);
	else if (lustre_mech == LGSS_MECH_NULL)
		rc = handle_null(&snd);
	else
		printerr(LL_ERR,
			 "ERROR: Received or request for subflavor that is not enabled: %d\n",
			 lustre_mech);

out_err:
	printerr(LL_INFO, "to send response with rc=%d\n", rc ? -EACCES : 0);
	/* Failures send a null token */
	rc = send_response(rc, hash, &snd.in_handle, &snd.in_tok,
			   snd.maj_stat, snd.min_stat,
			   &snd.out_handle, &snd.out_tok);

	/* cleanup buffers */
	if (snd.in_tok.value)
		free(snd.in_tok.value);
	if (snd.out_tok.value != NULL)
		gss_release_buffer(&ignore_min_stat, &snd.out_tok);

	/* For junk wire data just ignore */
ignore:
	return rc;
}