smbd_authsvc.c revision 12b65585e720714b31036daaa2b30eb76014048e
/*
* 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
*/
/*
* Copyright 2014 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 <stdlib.h>
#include <errno.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <note.h>
#include <fcntl.h>
#include <pthread.h>
#include <syslog.h>
#include "smbd.h"
#include "smbd_authsvc.h"
/* Arbitrary value outside the (small) range of valid OIDs */
static struct sockaddr_un smbauth_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 *);
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;
/*
* 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_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 ommit Kerberos.
*/
static const spnego_mech_handler_t
mech_table[] = {
{
},
{
},
#define MECH_TBL_IDX_NTLMSSP 2
{
},
{
/* end marker */
},
};
static const spnego_mech_handler_t
};
/*
* Start the authentication service.
* Returns non-zero on error.
*/
int
smbd_authsvc_start(void)
{
int rc;
rc = smbd_authsock_create();
if (rc)
return (rc);
(void) pthread_attr_init(&attr);
(void) pthread_attr_destroy(&attr);
if (rc) {
return (rc);
}
return (0);
}
void
smbd_authsvc_stop(void)
{
if (smbd.s_authsvc_tid != 0) {
smbd.s_authsvc_tid = 0;
}
}
static int
smbd_authsock_create(void)
{
int sock = -1;
if (sock < 0) {
return (errno);
}
sizeof (smbauth_sockname)) < 0) {
return (errno);
}
return (errno);
}
return (0);
}
static void
smbd_authsock_destroy(void)
{
int fid;
}
}
static void *
smbd_authsvc_listen(void *arg)
{
(void) pthread_attr_init(&attr);
for (;;) {
slen = 0;
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);
continue;
}
(void) mutex_unlock(&smbd_authsvc_mutex);
ctx = smbd_authctx_create();
smbd_report("authsvc, can't allocate context");
(void) mutex_lock(&smbd_authsvc_mutex);
(void) mutex_unlock(&smbd_authsvc_mutex);
goto out;
}
if (rc) {
(void) mutex_lock(&smbd_authsvc_mutex);
(void) mutex_unlock(&smbd_authsvc_mutex);
goto out;
}
}
out:
(void) pthread_attr_destroy(&attr);
return (NULL);
}
static void
smbd_authsvc_flood(void)
{
static time_t last_report;
count++;
last_report = now;
count = 0;
}
}
smbd_authctx_create(void)
{
return (NULL);
goto errout;
goto errout;
return (ctx);
return (NULL);
}
void
{
}
}
/*
* Limit how long smbd_authsvc_work will wait for the client to
* send us the next part of the authentication sequence.
*/
/*
* 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 void *
smbd_authsvc_work(void *arg)
{
smbd_report("authsvc_work: set set timeout: %m");
goto out;
}
smbd_report("authsvc_work: set recv timeout: %m");
goto out;
}
for (;;) {
if (len <= 0) {
/* normal termination */
break;
}
smbd_report("authsvc_work: read header failed");
break;
}
smbd_report("authsvc_work: msg too large");
break;
}
if (hdr.lmh_msglen > 0) {
smbd_report("authsvc_work: read mesg failed");
break;
}
}
/*
* The real work happens here.
*/
if (rc)
break;
smbd_report("authsvc_work: send failed");
break;
}
if (ctx->ctx_orawlen > 0) {
ctx->ctx_orawlen, 0);
smbd_report("authsvc_work: send failed");
break;
}
}
}
out:
if (ctx->ctx_mh_fini)
(void) mutex_lock(&smbd_authsvc_mutex);
(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
{
int rc;
switch (ctx->ctx_irawtype) {
case LSA_MTYPE_OLDREQ:
#ifdef DEBUG
(void) sleep(smbd_authsvc_slowdown);
#endif
break;
case LSA_MTYPE_CLINFO:
break;
case LSA_MTYPE_ESFIRST:
break;
case LSA_MTYPE_ESNEXT:
#ifdef DEBUG
(void) sleep(smbd_authsvc_slowdown);
#endif
break;
case LSA_MTYPE_GETTOK:
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) {
er->ler_errclass = 0;
er->ler_errcode = 0;
}
return (0);
}
static int
{
int rc = 0;
xdr_destroy(&xdrs);
return (NT_STATUS_INVALID_PARAMETER);
}
xdr_destroy(&xdrs);
return (NT_STATUS_ACCESS_DENIED);
return (rc);
}
static int
{
return (NT_STATUS_INTERNAL_ERROR);
sizeof (smb_lsa_clinfo_t));
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
{
const spnego_mech_handler_t *mh;
int best_pref = 1000;
int best_mhidx = -1;
/*
* NTLMSSP header is 8+, SPNEGO is 10+
*/
smbd_report("authsvc: short blob");
return (NT_STATUS_INVALID_PARAMETER);
}
/*
* We could have "Raw NTLMSSP" here intead of SPNEGO.
*/
return (rc);
}
/*
* Parse the SPNEGO token, check its type.
*/
if (rc != 0) {
smbd_report("authsvc: spnego parse failed");
return (NT_STATUS_INVALID_PARAMETER);
}
if (rc != 0) {
smbd_report("authsvc: spnego get token type failed");
return (NT_STATUS_INVALID_PARAMETER);
}
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) {
}
continue;
best_mhidx = idx;
}
}
if (best_mhidx == -1) {
smbd_report("authsvc: no supported spnego mechanism");
return (NT_STATUS_INVALID_PARAMETER);
}
/* Found a mutually agreeable mech. */
if (rc != 0) {
smbd_report("authsvc: mech init failed");
return (rc);
}
/*
* Common to LSA_MTYPE_ESFIRST, LSA_MTYPE_ESNEXT
*/
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
{
int rc;
/*
* Make sure LSA_MTYPE_ESFIRST was handled
* previously, so we have a work function.
*/
return (NT_STATUS_INVALID_PARAMETER);
return (rc);
}
/*
* Cleanup state from previous calls.
*/
}
/*
* Parse the SPNEGO token, check its type.
*/
if (rc != 0)
return (NT_STATUS_INVALID_PARAMETER);
if (rc != 0)
return (NT_STATUS_INVALID_PARAMETER);
return (NT_STATUS_INVALID_PARAMETER);
return (rc);
}
static int
{
int rc;
/*
* Cleanup state from previous calls.
*/
}
/*
* Extract the payload (mech token).
*/
switch (rc) {
case SPNEGO_E_SUCCESS:
break;
toklen = 0;
break;
return (NT_STATUS_BUFFER_TOO_SMALL);
default:
return (NT_STATUS_INTERNAL_ERROR);
}
/*
* 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.
*/
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.
*/
/* tell the client the selected mech. */
} else {
/* Ommit the "supported mech." field. */
}
/*
* Determine the spnego "negresult" from the
* reply message type (from the work func).
*/
switch (ctx->ctx_orawtype) {
case LSA_MTYPE_ERROR:
break;
case LSA_MTYPE_ES_DONE:
break;
case LSA_MTYPE_ES_CONT:
break;
default:
return (-1);
}
oid,
NULL, 0,
&ctx->ctx_otoken);
/*
* Convert the SPNEGO token into binary form,
* writing it to the output buffer.
*/
if (rc)
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
{
const spnego_mech_handler_t *mh;
int rc;
if (rc != 0)
return (rc);
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
{
int rc;
return (rc);
}
/*
* After a successful authentication, request the access token.
*/
static int
{
int rc = 0;
int len;
return (NT_STATUS_ACCESS_DENIED);
/*
* Encode the token response
*/
NULL) {
return (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 ommit Kerberos.
*/
static SPNEGO_MECH_OID MechTypeList[] = {
#define MECH_OID_IDX_NTLMSSP 2
};
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
{
int mechCnt = MechTypeCnt;
int rc;
/*
* In workgroup mode, skip Kerberos.
*/
if (smb_config_get_secmode() != SMB_SECMODE_DOMAIN) {
}
if (rc != SPNEGO_E_SUCCESS) {
"spnegoCreateNegTokenHint, rc=%d", rc);
*pBufLen = 0;
return;
}
if (rc != SPNEGO_E_SUCCESS) {
"spnegoTokenGetBinary, rc=%d", rc);
*pBufLen = 0;
} else {
}
}