view usr/src/cmd/smbsrv/smbd/smbd_authsvc.c @ 21415:4526915ff3e9

10999 Misc. SMB changes missed in prior upstreams Reviewed by: Matt Barden <matt.barden@nexenta.com> Approved by: Richard Lowe <richlowe@richlowe.net>
author Gordon Ross <gwr@nexenta.com>
date Tue, 19 Mar 2019 14:28:28 -0400
parents ee1d27bbaacd
children 580a029f8ca0
line wrap: on
line source

/*
 * This file and its contents are supplied under the terms of the
 * Common Development and Distribution License ("CDDL"), version 1.0.
 * You may only use this file in accordance with the terms of version
 * 1.0 of the CDDL.
 *
 * A full copy of the text of the CDDL should have accompanied this
 * source.  A copy of the CDDL is also available via the Internet at
 * http://www.illumos.org/license/CDDL.
 */

/*
 * Copyright 2017 Nexenta Systems, Inc.  All rights reserved.
 */

/*
 * SMB authentication service
 *
 * This service listens on a local AF_UNIX socket, spawning a
 * thread to service each connection.  The client-side of such
 * connections is the in-kernel SMB service, with an open and
 * connect done in the SMB session setup handler.
 */

#include <sys/types.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <note.h>
#include <net/if.h>
#include <net/route.h>
#include <sys/sockio.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <pthread.h>
#include <syslog.h>
#include <smbsrv/libsmb.h>
#include <netsmb/spnego.h>

#include "smbd.h"
#include "smbd_authsvc.h"

/* Arbitrary value outside the (small) range of valid OIDs */
#define	special_mech_raw_NTLMSSP	(spnego_mech_oid_NTLMSSP + 100)

static struct sockaddr_un smbauth_sockname = {
	AF_UNIX, SMB_AUTHSVC_SOCKNAME };

typedef struct spnego_mech_handler {
	int mh_oid; /* SPNEGO_MECH_OID */
	int (*mh_init)(authsvc_context_t *);
	int (*mh_work)(authsvc_context_t *);
	void (*mh_fini)(authsvc_context_t *);
} spnego_mech_handler_t;

static int smbd_authsock_create(void);
static void smbd_authsock_destroy(void);
static void *smbd_authsvc_listen(void *);
static void *smbd_authsvc_work(void *);
static void smbd_authsvc_flood(void);

static int smbd_authsvc_oldreq(authsvc_context_t *);
static int smbd_authsvc_clinfo(authsvc_context_t *);
static int smbd_authsvc_esfirst(authsvc_context_t *);
static int smbd_authsvc_esnext(authsvc_context_t *);
static int smbd_authsvc_escmn(authsvc_context_t *);
static int smbd_authsvc_gettoken(authsvc_context_t *);
static int smbd_raw_ntlmssp_esfirst(authsvc_context_t *);
static int smbd_raw_ntlmssp_esnext(authsvc_context_t *);

/*
 * We can get relatively large tokens now, thanks to krb5 PAC.
 * Might be better to size these buffers dynamically, but these
 * are all short-lived so not bothering with that for now.
 */
int smbd_authsvc_bufsize = 65000;

static mutex_t smbd_authsvc_mutex = DEFAULTMUTEX;

/*
 * The maximum number of authentication thread is limited by the
 * smbsrv smb_threshold_...(->sv_ssetup_ct) mechanism.  However,
 * due to occasional delays closing these auth. sockets, we need
 * a little "slack" on the number of threads we'll allow, as
 * compared with the in-kernel limit.  We could perhaps just
 * remove this limit now, but want it for extra safety.
 */
int smbd_authsvc_maxthread = SMB_AUTHSVC_MAXTHREAD + 32;
int smbd_authsvc_thrcnt = 0;	/* current thrcnt */
int smbd_authsvc_hiwat = 0;	/* largest thrcnt seen */
#ifdef DEBUG
int smbd_authsvc_slowdown = 0;
#endif

/*
 * These are the mechanisms we support, in order of preference.
 * But note: it's really the _client's_ preference that matters.
 * See &pref in the spnegoIsMechTypeAvailable() calls below.
 * Careful with this table; the code below knows its format and
 * may skip the fist two entries to omit Kerberos.
 */
static const spnego_mech_handler_t
mech_table[] = {
	{
		spnego_mech_oid_Kerberos_V5,
		smbd_krb5ssp_init,
		smbd_krb5ssp_work,
		smbd_krb5ssp_fini
	},
	{
		spnego_mech_oid_Kerberos_V5_Legacy,
		smbd_krb5ssp_init,
		smbd_krb5ssp_work,
		smbd_krb5ssp_fini
	},
#define	MECH_TBL_IDX_NTLMSSP	2
	{
		spnego_mech_oid_NTLMSSP,
		smbd_ntlmssp_init,
		smbd_ntlmssp_work,
		smbd_ntlmssp_fini
	},
	{
		/* end marker */
		spnego_mech_oid_NotUsed,
		NULL, NULL, NULL
	},
};

static const spnego_mech_handler_t
smbd_auth_mech_raw_ntlmssp = {
	special_mech_raw_NTLMSSP,
	smbd_ntlmssp_init,
	smbd_ntlmssp_work,
	smbd_ntlmssp_fini
};


/*
 * Start the authentication service.
 * Returns non-zero on error.
 */
int
smbd_authsvc_start(void)
{
	pthread_attr_t	attr;
	pthread_t	tid;
	int		rc;

	rc = smbd_authsock_create();
	if (rc)
		return (rc);

	(void) pthread_attr_init(&attr);
	(void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
	rc = pthread_create(&tid, &attr, smbd_authsvc_listen, &smbd);
	(void) pthread_attr_destroy(&attr);
	if (rc) {
		smbd_authsock_destroy();
		return (rc);
	}

	smbd.s_authsvc_tid = tid;
	return (0);
}

void
smbd_authsvc_stop(void)
{

	if (smbd.s_authsvc_tid != 0) {
		(void) pthread_kill(smbd.s_authsvc_tid, SIGTERM);
		smbd.s_authsvc_tid = 0;
	}
}

static int
smbd_authsock_create(void)
{
	int sock = -1;

	sock = socket(AF_UNIX, SOCK_STREAM, 0);
	if (sock < 0) {
		smbd_report("authsvc, socket create failed, %d", errno);
		return (errno);
	}

	(void) unlink(smbauth_sockname.sun_path);
	if (bind(sock, (struct sockaddr *)&smbauth_sockname,
	    sizeof (smbauth_sockname)) < 0) {
		smbd_report("authsvc, socket bind failed, %d", errno);
		(void) close(sock);
		return (errno);
	}

	if (listen(sock, SOMAXCONN) < 0) {
		smbd_report("authsvc, socket listen failed, %d", errno);
		(void) close(sock);
		return (errno);
	}

	smbd.s_authsvc_sock = sock;
	return (0);
}

static void
smbd_authsock_destroy(void)
{
	int fid;

	if ((fid = smbd.s_authsvc_sock) != -1) {
		smbd.s_authsvc_sock = -1;
		(void) close(fid);
	}
}

static void *
smbd_authsvc_listen(void *arg)
{
	authsvc_context_t *ctx;
	pthread_attr_t	attr;
	pthread_t	tid;
	socklen_t	slen;
	int		ls, ns, rc;

	_NOTE(ARGUNUSED(arg))

	(void) pthread_attr_init(&attr);
	(void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

	ls = smbd.s_authsvc_sock;
	for (;;) {

		slen = 0;
		ns = accept(ls, NULL, &slen);
		if (ns < 0) {
			switch (errno) {
			case ECONNABORTED:
				continue;
			case EINTR:
				/* normal termination */
				goto out;
			default:
				smbd_report("authsvc, socket accept failed,"
				    " %d", errno);
				goto out;
			}
		}

		/*
		 * Limit the number of auth. sockets
		 * (and the threads that service them).
		 */
		(void) mutex_lock(&smbd_authsvc_mutex);
		if (smbd_authsvc_thrcnt >= smbd_authsvc_maxthread) {
			(void) mutex_unlock(&smbd_authsvc_mutex);
			(void) close(ns);
			smbd_authsvc_flood();
			continue;
		}
		smbd_authsvc_thrcnt++;
		if (smbd_authsvc_hiwat < smbd_authsvc_thrcnt)
			smbd_authsvc_hiwat = smbd_authsvc_thrcnt;
		(void) mutex_unlock(&smbd_authsvc_mutex);

		ctx = smbd_authctx_create();
		if (ctx == NULL) {
			smbd_report("authsvc, can't allocate context");
			(void) mutex_lock(&smbd_authsvc_mutex);
			smbd_authsvc_thrcnt--;
			(void) mutex_unlock(&smbd_authsvc_mutex);
			(void) close(ns);
			goto out;
		}
		ctx->ctx_socket = ns;

		rc = pthread_create(&tid, &attr, smbd_authsvc_work, ctx);
		if (rc) {
			smbd_report("authsvc, thread create failed, %d", rc);
			(void) mutex_lock(&smbd_authsvc_mutex);
			smbd_authsvc_thrcnt--;
			(void) mutex_unlock(&smbd_authsvc_mutex);
			smbd_authctx_destroy(ctx);
			goto out;
		}
		ctx = NULL; /* given to the new thread */
	}

out:
	(void) pthread_attr_destroy(&attr);
	smbd_authsock_destroy();
	return (NULL);
}

static void
smbd_authsvc_flood(void)
{
	static uint_t count;
	static time_t last_report;
	time_t now = time(NULL);

	count++;
	if (last_report + 60 < now) {
		last_report = now;
		smbd_report("authsvc: flooded %u", count);
		count = 0;
	}
}

authsvc_context_t *
smbd_authctx_create(void)
{
	authsvc_context_t *ctx;

	ctx = malloc(sizeof (*ctx));
	if (ctx == NULL)
		return (NULL);
	bzero(ctx, sizeof (*ctx));

	ctx->ctx_irawlen = smbd_authsvc_bufsize;
	ctx->ctx_irawbuf = malloc(ctx->ctx_irawlen);
	ctx->ctx_orawlen = smbd_authsvc_bufsize;
	ctx->ctx_orawbuf = malloc(ctx->ctx_orawlen);
	if (ctx->ctx_irawbuf == NULL || ctx->ctx_orawbuf == NULL)
		goto errout;

	ctx->ctx_ibodylen = smbd_authsvc_bufsize;
	ctx->ctx_ibodybuf = malloc(ctx->ctx_ibodylen);
	ctx->ctx_obodylen = smbd_authsvc_bufsize;
	ctx->ctx_obodybuf = malloc(ctx->ctx_obodylen);
	if (ctx->ctx_ibodybuf == NULL || ctx->ctx_obodybuf == NULL)
		goto errout;

	return (ctx);

errout:
	smbd_authctx_destroy(ctx);
	return (NULL);
}

void
smbd_authctx_destroy(authsvc_context_t *ctx)
{
	if (ctx->ctx_socket != -1) {
		(void) close(ctx->ctx_socket);
		ctx->ctx_socket = -1;
	}

	if (ctx->ctx_token != NULL)
		smb_token_destroy(ctx->ctx_token);

	if (ctx->ctx_itoken != NULL)
		spnegoFreeData(ctx->ctx_itoken);
	if (ctx->ctx_otoken != NULL)
		spnegoFreeData(ctx->ctx_otoken);

	free(ctx->ctx_irawbuf);
	free(ctx->ctx_orawbuf);
	free(ctx->ctx_ibodybuf);
	free(ctx->ctx_obodybuf);

	free(ctx);
}

/*
 * Limit how long smbd_authsvc_work will wait for the client to
 * send us the next part of the authentication sequence.
 */
static struct timeval recv_tmo = { 30, 0 };

/*
 * Also set a timeout for send, where we're sending a response to
 * the client side (in smbsrv).  That should always be waiting in
 * recv by the time we send, so a short timeout is OK.
 */
static struct timeval send_tmo = { 15, 0 };

static void *
smbd_authsvc_work(void *arg)
{
	authsvc_context_t *ctx = arg;
	smb_lsa_msg_hdr_t	hdr;
	int sock = ctx->ctx_socket;
	int len, rc;

	if (setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO,
	    (char *)&send_tmo,  sizeof (send_tmo)) != 0) {
		smbd_report("authsvc_work: set set timeout: %m");
		goto out;
	}

	if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO,
	    (char *)&recv_tmo,  sizeof (recv_tmo)) != 0) {
		smbd_report("authsvc_work: set recv timeout: %m");
		goto out;
	}

	for (;;) {

		len = recv(sock, &hdr, sizeof (hdr), MSG_WAITALL);
		if (len <= 0) {
			/* normal termination */
			break;
		}
		if (len != sizeof (hdr)) {
			smbd_report("authsvc_work: read header failed");
			break;
		}

		if (hdr.lmh_msglen > smbd_authsvc_bufsize) {
			smbd_report("authsvc_work: msg too large");
			break;
		}

		if (hdr.lmh_msglen > 0) {
			len = recv(sock, ctx->ctx_irawbuf, hdr.lmh_msglen,
			    MSG_WAITALL);
			if (len != hdr.lmh_msglen) {
				smbd_report("authsvc_work: read mesg failed");
				break;
			}
		}
		ctx->ctx_irawtype = hdr.lmh_msgtype;
		ctx->ctx_irawlen = hdr.lmh_msglen;
		ctx->ctx_orawlen = smbd_authsvc_bufsize;
		ctx->ctx_ibodylen = smbd_authsvc_bufsize;
		ctx->ctx_obodylen = smbd_authsvc_bufsize;

		/*
		 * The real work happens here.
		 */
		rc = smbd_authsvc_dispatch(ctx);
		if (rc)
			break;

		hdr.lmh_msgtype = ctx->ctx_orawtype;
		hdr.lmh_msglen = ctx->ctx_orawlen;
		len = send(sock, &hdr, sizeof (hdr), 0);
		if (len != sizeof (hdr)) {
			smbd_report("authsvc_work: send failed");
			break;
		}

		if (ctx->ctx_orawlen > 0) {
			len = send(sock, ctx->ctx_orawbuf,
			    ctx->ctx_orawlen, 0);
			if (len != ctx->ctx_orawlen) {
				smbd_report("authsvc_work: send failed");
				break;
			}
		}
	}

out:
	if (ctx->ctx_mh_fini)
		(ctx->ctx_mh_fini)(ctx);

	smbd_authctx_destroy(ctx);

	(void) mutex_lock(&smbd_authsvc_mutex);
	smbd_authsvc_thrcnt--;
	(void) mutex_unlock(&smbd_authsvc_mutex);

	return (NULL);	/* implied pthread_exit() */
}

/*
 * Dispatch based on message type LSA_MTYPE_...
 * Non-zero return here ends the conversation.
 */
int
smbd_authsvc_dispatch(authsvc_context_t *ctx)
{
	int rc;

	switch (ctx->ctx_irawtype) {

	case LSA_MTYPE_OLDREQ:
#ifdef DEBUG
		if (smbd_authsvc_slowdown)
			(void) sleep(smbd_authsvc_slowdown);
#endif
		rc = smbd_authsvc_oldreq(ctx);
		break;

	case LSA_MTYPE_CLINFO:
		rc = smbd_authsvc_clinfo(ctx);
		break;

	case LSA_MTYPE_ESFIRST:
		rc = smbd_authsvc_esfirst(ctx);
		break;

	case LSA_MTYPE_ESNEXT:
#ifdef DEBUG
		if (smbd_authsvc_slowdown)
			(void) sleep(smbd_authsvc_slowdown);
#endif
		rc = smbd_authsvc_esnext(ctx);
		break;

	case LSA_MTYPE_GETTOK:
		rc = smbd_authsvc_gettoken(ctx);
		break;

		/* response types */
	case LSA_MTYPE_OK:
	case LSA_MTYPE_ERROR:
	case LSA_MTYPE_TOKEN:
	case LSA_MTYPE_ES_CONT:
	case LSA_MTYPE_ES_DONE:
	default:
		return (-1);
	}

	if (rc != 0) {
		smb_lsa_eresp_t *er = ctx->ctx_orawbuf;
		ctx->ctx_orawtype = LSA_MTYPE_ERROR;
		ctx->ctx_orawlen = sizeof (*er);
		er->ler_ntstatus = rc;
		er->ler_errclass = 0;
		er->ler_errcode = 0;
	}
	return (0);
}

static int
smbd_authsvc_oldreq(authsvc_context_t *ctx)
{
	smb_logon_t	user_info;
	XDR		xdrs;
	smb_token_t	*token = NULL;
	int		rc = 0;

	bzero(&user_info, sizeof (user_info));
	xdrmem_create(&xdrs, ctx->ctx_irawbuf, ctx->ctx_irawlen,
	    XDR_DECODE);
	if (!smb_logon_xdr(&xdrs, &user_info)) {
		xdr_destroy(&xdrs);
		return (NT_STATUS_INVALID_PARAMETER);
	}
	xdr_destroy(&xdrs);

	token = smbd_user_auth_logon(&user_info);
	xdr_free(smb_logon_xdr, (char *)&user_info);
	if (token == NULL) {
		rc = user_info.lg_status;
		if (rc == 0) /* should not happen */
			rc = NT_STATUS_INTERNAL_ERROR;
		return (rc);
	}

	ctx->ctx_token = token;

	return (rc);
}

static int
smbd_authsvc_clinfo(authsvc_context_t *ctx)
{

	if (ctx->ctx_irawlen != sizeof (smb_lsa_clinfo_t))
		return (NT_STATUS_INTERNAL_ERROR);
	(void) memcpy(&ctx->ctx_clinfo, ctx->ctx_irawbuf,
	    sizeof (smb_lsa_clinfo_t));

	ctx->ctx_orawtype = LSA_MTYPE_OK;
	ctx->ctx_orawlen = 0;
	return (0);
}

/*
 * Handle a security blob we've received from the client.
 * Incoming type: LSA_MTYPE_ESFIRST
 * Outgoing types: LSA_MTYPE_ES_CONT, LSA_MTYPE_ES_DONE,
 *   LSA_MTYPE_ERROR
 */
static int
smbd_authsvc_esfirst(authsvc_context_t *ctx)
{
	const spnego_mech_handler_t *mh;
	int idx, pref, rc;
	int best_pref = 1000;
	int best_mhidx = -1;

	/*
	 * NTLMSSP header is 8+, SPNEGO is 10+
	 */
	if (ctx->ctx_irawlen < 8) {
		smbd_report("authsvc: short blob");
		return (NT_STATUS_INVALID_PARAMETER);
	}

	/*
	 * We could have "Raw NTLMSSP" here intead of SPNEGO.
	 */
	if (bcmp(ctx->ctx_irawbuf, "NTLMSSP", 8) == 0) {
		rc = smbd_raw_ntlmssp_esfirst(ctx);
		return (rc);
	}

	/*
	 * Parse the SPNEGO token, check its type.
	 */
	rc = spnegoInitFromBinary(ctx->ctx_irawbuf,
	    ctx->ctx_irawlen, &ctx->ctx_itoken);
	if (rc != 0) {
		smbd_report("authsvc: spnego parse failed");
		return (NT_STATUS_INVALID_PARAMETER);
	}

	rc = spnegoGetTokenType(ctx->ctx_itoken, &ctx->ctx_itoktype);
	if (rc != 0) {
		smbd_report("authsvc: spnego get token type failed");
		return (NT_STATUS_INVALID_PARAMETER);
	}

	if (ctx->ctx_itoktype != SPNEGO_TOKEN_INIT) {
		smbd_report("authsvc: spnego wrong token type %d",
		    ctx->ctx_itoktype);
		return (NT_STATUS_INVALID_PARAMETER);
	}

	/*
	 * Figure out which mech type to use.  We want to use the
	 * first of the client's supported mechanisms that we also
	 * support.  Unfortunately, the spnego code does not have an
	 * interface to walk the token's mech list, so we have to
	 * ask about each mech type we know and keep track of which
	 * was earliest in the token's mech list.
	 *
	 * Also, skip the Kerberos mechanisms in workgroup mode.
	 */
	idx = 0;
	mh = mech_table;
	if (smb_config_get_secmode() != SMB_SECMODE_DOMAIN) {
		idx = MECH_TBL_IDX_NTLMSSP;
		mh = &mech_table[idx];
	}
	for (; mh->mh_init != NULL; idx++, mh++) {

		if (spnegoIsMechTypeAvailable(ctx->ctx_itoken,
		    mh->mh_oid, &pref) != 0)
			continue;

		if (pref < best_pref) {
			best_pref = pref;
			best_mhidx = idx;
		}
	}
	if (best_mhidx == -1) {
		smbd_report("authsvc: no supported spnego mechanism");
		return (NT_STATUS_INVALID_PARAMETER);
	}

	/* Found a mutually agreeable mech. */
	mh = &mech_table[best_mhidx];
	ctx->ctx_mech_oid = mh->mh_oid;
	ctx->ctx_mh_work = mh->mh_work;
	ctx->ctx_mh_fini = mh->mh_fini;
	rc = mh->mh_init(ctx);
	if (rc != 0) {
		smbd_report("authsvc: mech init failed");
		return (rc);
	}

	/*
	 * Common to LSA_MTYPE_ESFIRST, LSA_MTYPE_ESNEXT
	 */
	rc = smbd_authsvc_escmn(ctx);
	return (rc);
}

/*
 * Handle a security blob we've received from the client.
 * Incoming type: LSA_MTYPE_ESNEXT
 * Outgoing types: LSA_MTYPE_ES_CONT, LSA_MTYPE_ES_DONE,
 *   LSA_MTYPE_ERROR
 */
static int
smbd_authsvc_esnext(authsvc_context_t *ctx)
{
	int rc;

	/*
	 * Make sure LSA_MTYPE_ESFIRST was handled
	 * previously, so we have a work function.
	 */
	if (ctx->ctx_mh_work == NULL)
		return (NT_STATUS_INVALID_PARAMETER);

	if (ctx->ctx_mech_oid == special_mech_raw_NTLMSSP) {
		rc = smbd_raw_ntlmssp_esnext(ctx);
		return (rc);
	}

	/*
	 * Cleanup state from previous calls.
	 */
	if (ctx->ctx_itoken != NULL) {
		spnegoFreeData(ctx->ctx_itoken);
		ctx->ctx_itoken = NULL;
	}

	/*
	 * Parse the SPNEGO token, check its type.
	 */
	rc = spnegoInitFromBinary(ctx->ctx_irawbuf,
	    ctx->ctx_irawlen, &ctx->ctx_itoken);
	if (rc != 0)
		return (NT_STATUS_INVALID_PARAMETER);

	rc = spnegoGetTokenType(ctx->ctx_itoken, &ctx->ctx_itoktype);
	if (rc != 0)
		return (NT_STATUS_INVALID_PARAMETER);

	if (ctx->ctx_itoktype != SPNEGO_TOKEN_TARG)
		return (NT_STATUS_INVALID_PARAMETER);

	rc = smbd_authsvc_escmn(ctx);
	return (rc);
}

static int
smbd_authsvc_escmn(authsvc_context_t *ctx)
{
	SPNEGO_MECH_OID oid;
	ulong_t toklen;
	int rc;

	/*
	 * Cleanup state from previous calls.
	 */
	if (ctx->ctx_otoken != NULL) {
		spnegoFreeData(ctx->ctx_otoken);
		ctx->ctx_otoken = NULL;
	}

	/*
	 * Extract the payload (mech token).
	 */
	toklen = ctx->ctx_ibodylen;
	rc = spnegoGetMechToken(ctx->ctx_itoken,
	    ctx->ctx_ibodybuf, &toklen);
	switch (rc) {
	case SPNEGO_E_SUCCESS:
		break;
	case SPNEGO_E_ELEMENT_UNAVAILABLE:
		toklen = 0;
		break;
	case SPNEGO_E_BUFFER_TOO_SMALL:
		return (NT_STATUS_BUFFER_TOO_SMALL);
	default:
		return (NT_STATUS_INTERNAL_ERROR);
	}
	ctx->ctx_ibodylen = toklen;

	/*
	 * Now that we have the incoming "body" (mech. token),
	 * call the back-end mech-specific work function to
	 * create the outgoing "body" (mech. token).
	 *
	 * The worker must fill in:  ctx->ctx_negresult,
	 * and: ctx->ctx_obodylen, but ctx->ctx_obodybuf
	 * is optional, and is typically NULL after the
	 * final message of an auth sequence, where
	 * negresult == spnego_negresult_complete.
	 */
	rc = ctx->ctx_mh_work(ctx);
	if (rc != 0)
		return (rc);

	/*
	 * Wrap the outgoing body in a negTokenTarg SPNEGO token.
	 * The selected mech. OID is returned only when the
	 * incoming token was of type SPNEGO_TOKEN_INIT.
	 */
	if (ctx->ctx_itoktype == SPNEGO_TOKEN_INIT) {
		/* tell the client the selected mech. */
		oid = ctx->ctx_mech_oid;
	} else {
		/* Omit the "supported mech." field. */
		oid = spnego_mech_oid_NotUsed;
	}

	/*
	 * Determine the spnego "negresult" from the
	 * reply message type (from the work func).
	 */
	switch (ctx->ctx_orawtype) {
	case LSA_MTYPE_ERROR:
		ctx->ctx_negresult = spnego_negresult_rejected;
		break;
	case LSA_MTYPE_ES_DONE:
		ctx->ctx_negresult = spnego_negresult_success;
		break;
	case LSA_MTYPE_ES_CONT:
		ctx->ctx_negresult = spnego_negresult_incomplete;
		break;
	default:
		return (-1);
	}

	rc = spnegoCreateNegTokenTarg(
	    oid,
	    ctx->ctx_negresult,
	    ctx->ctx_obodybuf, /* may be NULL */
	    ctx->ctx_obodylen,
	    NULL, 0,
	    &ctx->ctx_otoken);

	/*
	 * Convert the SPNEGO token into binary form,
	 * writing it to the output buffer.
	 */
	toklen = smbd_authsvc_bufsize;
	rc = spnegoTokenGetBinary(ctx->ctx_otoken,
	    (uchar_t *)ctx->ctx_orawbuf, &toklen);
	if (rc)
		rc = NT_STATUS_INTERNAL_ERROR;
	ctx->ctx_orawlen = (uint_t)toklen;

	return (rc);
}

/*
 * Wrapper for "Raw NTLMSSP", which is exactly like the
 * normal (SPNEGO-wrapped) NTLMSSP but without SPNEGO.
 * Setup back-end handler for: special_mech_raw_NTLMSSP
 * Compare with smbd_authsvc_esfirst().
 */
static int
smbd_raw_ntlmssp_esfirst(authsvc_context_t *ctx)
{
	const spnego_mech_handler_t *mh;
	int rc;

	mh = &smbd_auth_mech_raw_ntlmssp;
	rc = mh->mh_init(ctx);
	if (rc != 0)
		return (rc);

	ctx->ctx_mech_oid = mh->mh_oid;
	ctx->ctx_mh_work = mh->mh_work;
	ctx->ctx_mh_fini = mh->mh_fini;

	rc = smbd_raw_ntlmssp_esnext(ctx);

	return (rc);
}


/*
 * Wrapper for "Raw NTLMSSP", which is exactly like the
 * normal (SPNEGO-wrapped) NTLMSSP but without SPNEGO.
 * Just copy "raw" to "body", and vice versa.
 * Compare with smbd_authsvc_esnext, smbd_authsvc_escmn
 */
static int
smbd_raw_ntlmssp_esnext(authsvc_context_t *ctx)
{
	int rc;

	ctx->ctx_ibodylen = ctx->ctx_irawlen;
	(void) memcpy(ctx->ctx_ibodybuf,
	    ctx->ctx_irawbuf, ctx->ctx_irawlen);

	rc = ctx->ctx_mh_work(ctx);

	ctx->ctx_orawlen = ctx->ctx_obodylen;
	(void) memcpy(ctx->ctx_orawbuf,
	    ctx->ctx_obodybuf, ctx->ctx_obodylen);

	return (rc);
}


/*
 * After a successful authentication, request the access token.
 */
static int
smbd_authsvc_gettoken(authsvc_context_t *ctx)
{
	XDR		xdrs;
	smb_token_t	*token = NULL;
	int		rc = 0;
	int		len;

	if ((token = ctx->ctx_token) == NULL)
		return (NT_STATUS_ACCESS_DENIED);

	/*
	 * Encode the token response
	 */
	len = xdr_sizeof(smb_token_xdr, token);
	if (len > ctx->ctx_orawlen) {
		if ((ctx->ctx_orawbuf = realloc(ctx->ctx_orawbuf, len)) ==
		    NULL) {
			return (NT_STATUS_INTERNAL_ERROR);
		}
	}

	ctx->ctx_orawtype = LSA_MTYPE_TOKEN;
	ctx->ctx_orawlen = len;
	xdrmem_create(&xdrs, ctx->ctx_orawbuf, len, XDR_ENCODE);
	if (!smb_token_xdr(&xdrs, token))
		rc = NT_STATUS_INTERNAL_ERROR;
	xdr_destroy(&xdrs);

	return (rc);
}

/*
 * Initialization time code to figure out what mechanisms we support.
 * Careful with this table; the code below knows its format and may
 * skip the fist two entries to omit Kerberos.
 */
static SPNEGO_MECH_OID MechTypeList[] = {
	spnego_mech_oid_Kerberos_V5,
	spnego_mech_oid_Kerberos_V5_Legacy,
#define	MECH_OID_IDX_NTLMSSP	2
	spnego_mech_oid_NTLMSSP,
};
static int MechTypeCnt = sizeof (MechTypeList) /
	sizeof (MechTypeList[0]);

/* This string is just like Windows. */
static char IgnoreSPN[] = "not_defined_in_RFC4178@please_ignore";

/*
 * Build the SPNEGO "hint" token based on the
 * configured authentication mechanisms.
 * (NTLMSSP, and maybe Kerberos)
 */
void
smbd_get_authconf(smb_kmod_cfg_t *kcfg)
{
	SPNEGO_MECH_OID *mechList = MechTypeList;
	int mechCnt = MechTypeCnt;
	SPNEGO_TOKEN_HANDLE hSpnegoToken = NULL;
	uchar_t *pBuf = kcfg->skc_negtok;
	uint32_t *pBufLen = &kcfg->skc_negtok_len;
	ulong_t tLen = sizeof (kcfg->skc_negtok);
	int rc;

	/*
	 * In workgroup mode, skip Kerberos.
	 */
	if (smb_config_get_secmode() != SMB_SECMODE_DOMAIN) {
		mechList += MECH_OID_IDX_NTLMSSP;
		mechCnt  -= MECH_OID_IDX_NTLMSSP;
	}

	rc = spnegoCreateNegTokenHint(mechList, mechCnt,
	    (uchar_t *)IgnoreSPN, &hSpnegoToken);
	if (rc != SPNEGO_E_SUCCESS) {
		syslog(LOG_DEBUG, "smb_config_get_negtok: "
		    "spnegoCreateNegTokenHint, rc=%d", rc);
		*pBufLen = 0;
		return;
	}
	rc = spnegoTokenGetBinary(hSpnegoToken, pBuf, &tLen);
	if (rc != SPNEGO_E_SUCCESS) {
		syslog(LOG_DEBUG, "smb_config_get_negtok: "
		    "spnegoTokenGetBinary, rc=%d", rc);
		*pBufLen = 0;
	} else {
		*pBufLen = (uint32_t)tLen;
	}
	spnegoFreeData(hSpnegoToken);
}