summaryrefslogtreecommitdiff
path: root/usr/src/cmd/smbsrv
diff options
context:
space:
mode:
authorGordon Ross <gwr@nexenta.com>2013-06-21 15:59:58 -0400
committerGordon Ross <gwr@nexenta.com>2015-10-26 10:16:22 -0400
commit12b65585e720714b31036daaa2b30eb76014048e (patch)
treec413afd3fb76e04e53ec04ce601d8c3ff35c65ab /usr/src/cmd/smbsrv
parent056d3a7d553516b590a0543f4df3152a3144b42b (diff)
downloadillumos-joyent-12b65585e720714b31036daaa2b30eb76014048e.tar.gz
1122 smbsrv should use SPNEGO (inbound authentication)
Portions contributed by: Matt Barden <Matt.Barden@nexenta.com> Portions contributed by: Kevin Crowe <kevin.crowe@nexenta.com> Portions contributed by: Alek Pinchuk <alek@nexenta.com> Reviewed by: Bayard Bell <bayard.bell@nexenta.com> Reviewed by: Dan Fields <dan.fields@nexenta.com> Reviewed by: Kevin Crowe <kevin.crowe@nexenta.com> Reviewed by: Matt Barden <Matt.Barden@nexenta.com> Approved by: Robert Mustacchi <rm@joyent.com>
Diffstat (limited to 'usr/src/cmd/smbsrv')
-rw-r--r--usr/src/cmd/smbsrv/fksmbd/Makefile15
-rwxr-xr-xusr/src/cmd/smbsrv/fksmbd/Run.sh19
-rw-r--r--usr/src/cmd/smbsrv/fksmbd/fksmbd_kmod.c22
-rw-r--r--usr/src/cmd/smbsrv/smbd/Makefile14
-rw-r--r--usr/src/cmd/smbsrv/smbd/server.xml2
-rw-r--r--usr/src/cmd/smbsrv/smbd/smbd.h6
-rw-r--r--usr/src/cmd/smbsrv/smbd/smbd_authsvc.c973
-rw-r--r--usr/src/cmd/smbsrv/smbd/smbd_authsvc.h82
-rw-r--r--usr/src/cmd/smbsrv/smbd/smbd_doorsvc.c24
-rw-r--r--usr/src/cmd/smbsrv/smbd/smbd_krb5ssp.c350
-rw-r--r--usr/src/cmd/smbsrv/smbd/smbd_logon.c23
-rw-r--r--usr/src/cmd/smbsrv/smbd/smbd_main.c17
-rw-r--r--usr/src/cmd/smbsrv/smbd/smbd_ntlmssp.c595
13 files changed, 2110 insertions, 32 deletions
diff --git a/usr/src/cmd/smbsrv/fksmbd/Makefile b/usr/src/cmd/smbsrv/fksmbd/Makefile
index 615818e44a..a245c2422e 100644
--- a/usr/src/cmd/smbsrv/fksmbd/Makefile
+++ b/usr/src/cmd/smbsrv/fksmbd/Makefile
@@ -26,11 +26,14 @@
PROG= fksmbd
OBJS_SMBD= \
+ smbd_authsvc.o \
smbd_doorsvc.o \
smbd_join.o \
+ smbd_krb5ssp.o \
smbd_logon.o \
smbd_main.o \
smbd_nicmon.o \
+ smbd_ntlmssp.o \
smbd_pipesvc.o \
smbd_share_doorsvc.o \
smbd_spool.o \
@@ -59,6 +62,9 @@ INCS += -I../../../uts/common
INCS += -I../../../uts/common/smbsrv
INCS += -I../../../common/smbsrv
+# Should not have to do this, but the Kerberos includes are a mess.
+INCS += -I $(ROOT)/usr/include/kerberosv5
+
C99MODE= -xc99=%all
C99LMODE= -Xc99=%all
@@ -74,12 +80,15 @@ CPPFLAGS += $(INCS)
LDFLAGS += $(ZNOLAZYLOAD)
LDFLAGS += -R/usr/lib/smbsrv
-LDLIBS += -L$(ROOT)/usr/lib/smbsrv
+LDLIBS += -L$(ROOT)/usr/lib/smbsrv
LDLIBS += -lfksmbsrv -lfakekernel
-LDLIBS += -lmlsvc -lmlrpc -lsmbns -lsmb
-LDLIBS += -lzfs -lcmdutils -lbsm -lsocket -lnsl -lscf -lumem
+# prefer to keep libs ordered by dependence
+LDLIBS += -lmlsvc -lmlrpc -lsmbns -lsmb -lsmbfs -lgss
+LDLIBS += -lzfs -lbsm -lscf -lcmdutils -lsocket -lnsl -lumem
+$(PROG) := LDLIBS += -lkrb5
LINTFLAGS += -xerroff=E_NAME_DEF_NOT_USED2
+LINTFLAGS += -xerroff=E_NAME_USED_NOT_DEF2
LINTFLAGS += -xerroff=E_INCONS_ARG_DECL2
LINTFLAGS += -xerroff=E_INCONS_VAL_TYPE_DECL2
diff --git a/usr/src/cmd/smbsrv/fksmbd/Run.sh b/usr/src/cmd/smbsrv/fksmbd/Run.sh
index f1f4b5973b..0e42825dab 100755
--- a/usr/src/cmd/smbsrv/fksmbd/Run.sh
+++ b/usr/src/cmd/smbsrv/fksmbd/Run.sh
@@ -12,7 +12,7 @@
#
#
-# Copyright 2013 Nexenta Systems, Inc. All rights reserved.
+# Copyright 2014 Nexenta Systems, Inc. All rights reserved.
#
# Helper program to run fksmbd (user-space smbd for debugging)
@@ -32,6 +32,23 @@ then
exit 1;
fi
+if [[ ! -r /var/smb/smbpasswd ]]
+then
+ echo "Need readable /var/smb/smbpasswd, i.e."
+ echo 'chgrp staff /var/smb/smbpasswd'
+ echo 'chmod 440 /var/smb/smbpasswd'
+ exit 1;
+fi
+
+if [[ -e /var/smb/.pwd.lock && ! -w /var/smb/.pwd.lock ]]
+then
+ echo "Need to cleanup /var/smb/.pwd.lock, i.e."
+ echo "rm -f /var/smb/.pwd.lock"
+ exit 1;
+fi
+
+# OK, setup env. to run it.
+
export SMBD_DOOR_NAME="/tmp/fksmbd_door"
export SMB_SHARE_DNAME="/tmp/fksmbshare_door"
diff --git a/usr/src/cmd/smbsrv/fksmbd/fksmbd_kmod.c b/usr/src/cmd/smbsrv/fksmbd/fksmbd_kmod.c
index 65fc7cecbb..4e4b17fcf1 100644
--- a/usr/src/cmd/smbsrv/fksmbd/fksmbd_kmod.c
+++ b/usr/src/cmd/smbsrv/fksmbd/fksmbd_kmod.c
@@ -10,7 +10,7 @@
*/
/*
- * Copyright 2014 Nexenta Systems, Inc. All rights reserved.
+ * Copyright 2015 Nexenta Systems, Inc. All rights reserved.
*/
/*
@@ -62,11 +62,31 @@ static void
fksmbd_adjust_config(smb_ioc_header_t *ioc_hdr)
{
smb_ioc_cfg_t *ioc = (smb_ioc_cfg_t *)ioc_hdr;
+ char *s;
ioc->maxconnections = 10;
ioc->maxworkers = 20;
smbd_report("maxconnections=%d, maxworkers=%d",
ioc->maxconnections, ioc->maxworkers);
+
+ if ((s = getenv("SMB_SIGNING")) != NULL) {
+ ioc->signing_enable = 0;
+ ioc->signing_required = 0;
+ switch (s[0]) {
+ case 'e':
+ ioc->signing_enable = 1;
+ break;
+ case 'r':
+ ioc->signing_enable = 1;
+ ioc->signing_required = 1;
+ break;
+ default:
+ smbd_report("env SMB_SIGNING invalid");
+ break;
+ }
+ }
+ smbd_report("signing: enable=%d, required=%d",
+ ioc->signing_enable, ioc->signing_required);
}
boolean_t
diff --git a/usr/src/cmd/smbsrv/smbd/Makefile b/usr/src/cmd/smbsrv/smbd/Makefile
index 8fd9ccb74a..134a317fd8 100644
--- a/usr/src/cmd/smbsrv/smbd/Makefile
+++ b/usr/src/cmd/smbsrv/smbd/Makefile
@@ -26,11 +26,14 @@
PROG= smbd
OBJS= \
+ smbd_authsvc.o \
smbd_doorsvc.o \
smbd_join.o \
+ smbd_krb5ssp.o \
smbd_logon.o \
smbd_main.o \
smbd_nicmon.o \
+ smbd_ntlmssp.o \
smbd_pipesvc.o \
smbd_share_doorsvc.o \
smbd_spool.o \
@@ -54,18 +57,25 @@ $(ROOTSVCMETHOD):= FILEMODE = 0555
$(ROOTVARSMBDLL):= FILEMODE = 0755
LINTFLAGS += -xerroff=E_NAME_DEF_NOT_USED2
+LINTFLAGS += -xerroff=E_NAME_USED_NOT_DEF2
CFLAGS += $(CCVERBOSE)
CPPFLAGS += -D_REENTRANT
CPPFLAGS += -Dsyslog=smb_syslog
$(NOT_RELEASE_BUILD)CPPFLAGS += -DDEBUG
+# Should not have to do this, but the Kerberos includes are a mess.
+CPPFLAGS += -I $(ROOT)/usr/include/kerberosv5
+
C99MODE = -xc99=%all
C99LMODE = -Xc99=%all
-LDLIBS += -L$(ROOT)/usr/lib/smbsrv -lmlsvc -lmlrpc -lsmbns -lsmb \
- -lzfs -lbsm -lsocket -lnsl -lscf -lumem -lcmdutils
LDFLAGS += -R/usr/lib/smbsrv
+LDLIBS += -L$(ROOT)/usr/lib/smbsrv
+# prefer to keep libs ordered by dependence
+LDLIBS += -lmlsvc -lmlrpc -lsmbns -lsmb -lsmbfs -lgss
+LDLIBS += -lzfs -lbsm -lscf -lcmdutils -lsocket -lnsl -lumem
+$(PROG) := LDLIBS += -lkrb5
$(ENABLE_SMB_PRINTING) CPPFLAGS += -DHAVE_CUPS
diff --git a/usr/src/cmd/smbsrv/smbd/server.xml b/usr/src/cmd/smbsrv/smbd/server.xml
index 10b0c82a87..d8dc32c114 100644
--- a/usr/src/cmd/smbsrv/smbd/server.xml
+++ b/usr/src/cmd/smbsrv/smbd/server.xml
@@ -181,7 +181,7 @@ file.
<propval name='restrict_anonymous' type='boolean'
value='false' override='true'/>
<propval name='signing_enabled' type='boolean'
- value='false' override='true'/>
+ value='true' override='true'/>
<propval name='signing_required' type='boolean'
value='false' override='true'/>
<propval name='signing_check' type='boolean'
diff --git a/usr/src/cmd/smbsrv/smbd/smbd.h b/usr/src/cmd/smbsrv/smbd/smbd.h
index 3ec5877fac..f3c1351851 100644
--- a/usr/src/cmd/smbsrv/smbd/smbd.h
+++ b/usr/src/cmd/smbsrv/smbd/smbd.h
@@ -56,6 +56,7 @@ uint32_t smbd_join(smb_joininfo_t *);
void smbd_set_secmode(int);
boolean_t smbd_online(void);
void smbd_online_wait(const char *);
+void smbd_get_authconf(smb_kmod_cfg_t *);
void smbd_spool_start(void);
void smbd_spool_stop(void);
@@ -81,6 +82,7 @@ typedef struct smbd {
boolean_t s_shutting_down; /* shutdown control */
volatile uint_t s_refreshes;
boolean_t s_kbound; /* B_TRUE if bound to kernel */
+ int s_authsvc_sock;
int s_door_lmshr;
int s_door_srv;
int s_door_opipe;
@@ -89,6 +91,7 @@ typedef struct smbd {
smb_inaddr_t s_pdc;
boolean_t s_pdc_changed;
pthread_t s_refresh_tid;
+ pthread_t s_authsvc_tid;
pthread_t s_localtime_tid;
pthread_t s_spool_tid;
pthread_t s_dc_monitor_tid;
@@ -140,6 +143,9 @@ void smbd_door_return(smbd_door_t *, char *, size_t, door_desc_t *, uint_t);
void *smbd_door_dispatch_op(void *);
+int smbd_authsvc_start(void);
+void smbd_authsvc_stop(void);
+
/* For fksmbd */
void fksmbd_init(void);
int fksmbd_door_dispatch(smb_doorarg_t *);
diff --git a/usr/src/cmd/smbsrv/smbd/smbd_authsvc.c b/usr/src/cmd/smbsrv/smbd/smbd_authsvc.c
new file mode 100644
index 0000000000..0b6af80bd8
--- /dev/null
+++ b/usr/src/cmd/smbsrv/smbd/smbd_authsvc.c
@@ -0,0 +1,973 @@
+/*
+ * 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 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 <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 ommit 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)
+ return (NT_STATUS_ACCESS_DENIED);
+
+ 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 {
+ /* Ommit 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 ommit 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);
+}
diff --git a/usr/src/cmd/smbsrv/smbd/smbd_authsvc.h b/usr/src/cmd/smbsrv/smbd/smbd_authsvc.h
new file mode 100644
index 0000000000..116eebfc84
--- /dev/null
+++ b/usr/src/cmd/smbsrv/smbd/smbd_authsvc.h
@@ -0,0 +1,82 @@
+/*
+ * 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 2014 Nexenta Systems, Inc. All rights reserved.
+ */
+
+#ifndef _SMBD_AUTHSVC_H
+#define _SMBD_AUTHSVC_H
+
+/*
+ * Declarations shared with authsvc modules.
+ */
+
+#include <sys/types.h>
+#include <smbsrv/libsmb.h>
+
+/*
+ * This is the common authsvc_context shared by all back-ends.
+ * Note that ctx_mech_oid is really SPNEGO_MECH_OID, and the
+ * ctx_itoken, ctx_otoken members are SPNEGO_TOKEN_HANDLE,
+ * but this is using the underlying types so as to avoid
+ * dragging in spnego.h here.
+ */
+typedef struct authsvc_context {
+ int ctx_socket;
+ int ctx_mech_oid;
+ int (*ctx_mh_work)(struct authsvc_context *);
+ void (*ctx_mh_fini)(struct authsvc_context *);
+ int ctx_itoktype;
+ int ctx_negresult;
+
+ /* (in,out) SPNEGO token handles */
+ void *ctx_itoken;
+ void *ctx_otoken;
+
+ /* (in,out) raw (buf,len,type) */
+ void *ctx_irawbuf;
+ uint_t ctx_irawlen;
+ int ctx_irawtype;
+ void *ctx_orawbuf;
+ uint_t ctx_orawlen;
+ int ctx_orawtype;
+
+ /* (in,out) body (buf,len) */
+ void *ctx_ibodybuf;
+ uint_t ctx_ibodylen;
+ void *ctx_obodybuf;
+ uint_t ctx_obodylen;
+
+ /* who is the client */
+ smb_lsa_clinfo_t ctx_clinfo;
+
+ /* final authentication token */
+ struct smb_token *ctx_token;
+
+ /* private data for the back-end */
+ void *ctx_backend;
+} authsvc_context_t;
+
+int smbd_krb5ssp_init(authsvc_context_t *);
+int smbd_krb5ssp_work(authsvc_context_t *);
+void smbd_krb5ssp_fini(authsvc_context_t *);
+
+int smbd_ntlmssp_init(authsvc_context_t *);
+int smbd_ntlmssp_work(authsvc_context_t *);
+void smbd_ntlmssp_fini(authsvc_context_t *);
+
+/* Exposed for unit tests. */
+int smbd_authsvc_dispatch(authsvc_context_t *);
+authsvc_context_t *smbd_authctx_create(void);
+void smbd_authctx_destroy(authsvc_context_t *);
+
+#endif /* _SMBD_AUTHSVC_H */
diff --git a/usr/src/cmd/smbsrv/smbd/smbd_doorsvc.c b/usr/src/cmd/smbsrv/smbd/smbd_doorsvc.c
index e21a9beaf4..b8705b1d91 100644
--- a/usr/src/cmd/smbsrv/smbd/smbd_doorsvc.c
+++ b/usr/src/cmd/smbsrv/smbd/smbd_doorsvc.c
@@ -38,6 +38,7 @@
#include <fcntl.h>
#include <pthread.h>
#include <strings.h>
+#include <note.h>
#include <smbsrv/smb_door.h>
#include <smbsrv/smb_xdr.h>
#include <smbsrv/smb_token.h>
@@ -572,27 +573,10 @@ smbd_dop_user_auth_logoff(smbd_arg_t *arg)
static int
smbd_dop_user_auth_logon(smbd_arg_t *arg)
{
- smb_logon_t *user_info;
- smb_token_t *token;
+ _NOTE(ARGUNUSED(arg))
- user_info = smb_logon_decode((uint8_t *)arg->data,
- arg->datalen);
- if (user_info == NULL)
- return (SMB_DOP_DECODE_ERROR);
-
- token = smbd_user_auth_logon(user_info);
-
- smb_logon_free(user_info);
-
- if (token == NULL)
- return (SMB_DOP_EMPTYBUF);
-
- arg->rbuf = (char *)smb_token_encode(token, &arg->rsize);
- smb_token_destroy(token);
-
- if (arg->rbuf == NULL)
- return (SMB_DOP_ENCODE_ERROR);
- return (SMB_DOP_SUCCESS);
+ /* No longer used */
+ return (SMB_DOP_EMPTYBUF);
}
static int
diff --git a/usr/src/cmd/smbsrv/smbd/smbd_krb5ssp.c b/usr/src/cmd/smbsrv/smbd/smbd_krb5ssp.c
new file mode 100644
index 0000000000..ff0dff251b
--- /dev/null
+++ b/usr/src/cmd/smbsrv/smbd/smbd_krb5ssp.c
@@ -0,0 +1,350 @@
+/*
+ * 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 2015 Nexenta Systems, Inc. All rights reserved.
+ */
+
+/*
+ * SPNEGO back-end for Kerberos. See [MS-KILE]
+ */
+
+#include <sys/types.h>
+#include <gssapi/gssapi_ext.h>
+#include <gssapi/gssapi_krb5.h>
+#include <krb5.h>
+#include "smbd.h"
+#include "smbd_authsvc.h"
+
+/* From krb5/krb/pac.c (should have been exported) */
+#define PAC_LOGON_INFO 1
+
+typedef struct krb5ssp_backend {
+ gss_ctx_id_t be_gssctx;
+ char *be_username;
+ gss_buffer_desc be_authz_pac;
+ krb5_context be_kctx;
+ krb5_pac be_kpac;
+ krb5_data be_pac;
+} krb5ssp_backend_t;
+
+static uint32_t
+get_authz_data_pac(
+ gss_ctx_id_t context_handle,
+ gss_buffer_t ad_data);
+
+static uint32_t
+get_ssnkey(authsvc_context_t *ctx);
+
+
+/*
+ * Initialize this context for Kerberos, if possible.
+ *
+ * Should not get here unless libsmb smb_config_get_negtok
+ * includes the Kerberos5 Mech OIDs in our spnego hint.
+ *
+ * Todo: allocate ctx->ctx_backend
+ * See: krb5_gss_accept_sec_context()
+ */
+int
+smbd_krb5ssp_init(authsvc_context_t *ctx)
+{
+ krb5ssp_backend_t *be;
+
+ be = malloc(sizeof (*be));
+ if (be == 0)
+ return (NT_STATUS_NO_MEMORY);
+ bzero(be, sizeof (*be));
+ be->be_gssctx = GSS_C_NO_CONTEXT;
+ ctx->ctx_backend = be;
+
+ return (0);
+}
+
+/*
+ * Todo: free ctx->ctx_backend
+ */
+void
+smbd_krb5ssp_fini(authsvc_context_t *ctx)
+{
+ krb5ssp_backend_t *be = ctx->ctx_backend;
+ uint32_t minor;
+
+ if (be == NULL)
+ return;
+
+ if (be->be_kctx != NULL) {
+ krb5_free_data_contents(be->be_kctx, &be->be_pac);
+
+ if (be->be_kpac != NULL)
+ krb5_pac_free(be->be_kctx, be->be_kpac);
+
+ krb5_free_context(be->be_kctx);
+ }
+
+ (void) gss_release_buffer(NULL, &be->be_authz_pac);
+
+ free(be->be_username);
+
+ if (be->be_gssctx != GSS_C_NO_CONTEXT) {
+ (void) gss_delete_sec_context(&minor, &be->be_gssctx,
+ GSS_C_NO_BUFFER);
+ }
+
+ free(be);
+}
+
+/*
+ * Handle a Kerberos auth message.
+ *
+ * State across messages is in ctx->ctx_backend
+ */
+int
+smbd_krb5ssp_work(authsvc_context_t *ctx)
+{
+ gss_buffer_desc intok, outtok;
+ gss_buffer_desc namebuf;
+ krb5ssp_backend_t *be = ctx->ctx_backend;
+ gss_name_t gname = NULL;
+ OM_uint32 major, minor, ret_flags;
+ gss_OID name_type = GSS_C_NULL_OID;
+ gss_OID mech_type = GSS_C_NULL_OID;
+ krb5_error_code kerr;
+ uint32_t status;
+
+ intok.length = ctx->ctx_ibodylen;
+ intok.value = ctx->ctx_ibodybuf;
+ bzero(&outtok, sizeof (gss_buffer_desc));
+ bzero(&namebuf, sizeof (gss_buffer_desc));
+
+ /* Do this early, for error message support. */
+ kerr = krb5_init_context(&be->be_kctx);
+ if (kerr != 0) {
+ smbd_report("krb5ssp, krb5_init_ctx: %s",
+ krb5_get_error_message(be->be_kctx, kerr));
+ return (NT_STATUS_INTERNAL_ERROR);
+ }
+
+ major = gss_accept_sec_context(&minor, &be->be_gssctx,
+ GSS_C_NO_CREDENTIAL, &intok,
+ GSS_C_NO_CHANNEL_BINDINGS, &gname, &mech_type, &outtok,
+ &ret_flags, NULL, NULL);
+
+ if (outtok.length == 0)
+ ctx->ctx_obodylen = 0;
+ else if (outtok.length <= ctx->ctx_obodylen) {
+ ctx->ctx_obodylen = outtok.length;
+ (void) memcpy(ctx->ctx_obodybuf, outtok.value, outtok.length);
+ free(outtok.value);
+ outtok.value = NULL;
+ } else {
+ free(ctx->ctx_obodybuf);
+ ctx->ctx_obodybuf = outtok.value;
+ ctx->ctx_obodylen = outtok.length;
+ outtok.value = NULL;
+ }
+
+ if (GSS_ERROR(major)) {
+ smbd_report("krb5ssp: gss_accept_sec_context, "
+ "mech=0x%x, major=0x%x, minor=0x%x",
+ (int)mech_type, major, minor);
+ smbd_report(" krb5: %s",
+ krb5_get_error_message(be->be_kctx, minor));
+ return (NT_STATUS_WRONG_PASSWORD);
+ }
+
+ switch (major) {
+ case GSS_S_COMPLETE:
+ break;
+ case GSS_S_CONTINUE_NEEDED:
+ if (outtok.length > 0) {
+ ctx->ctx_orawtype = LSA_MTYPE_ES_CONT;
+ /* becomes NT_STATUS_MORE_PROCESSING_REQUIRED */
+ return (0);
+ }
+ return (NT_STATUS_WRONG_PASSWORD);
+ default:
+ return (NT_STATUS_WRONG_PASSWORD);
+ }
+
+ /*
+ * OK, we got GSS_S_COMPLETE. Get the name so we can use it
+ * in log messages if we get failures decoding the PAC etc.
+ * Then get the PAC, decode it, build the logon token.
+ */
+
+ if (gname != NULL && GSS_S_COMPLETE ==
+ gss_display_name(&minor, gname, &namebuf, &name_type)) {
+ /* Save the user name. */
+ be->be_username = strdup(namebuf.value);
+ (void) gss_release_buffer(&minor, &namebuf);
+ (void) gss_release_name(&minor, &gname);
+ if (be->be_username == NULL) {
+ return (NT_STATUS_NO_MEMORY);
+ }
+ }
+
+ /*
+ * Extract the KRB5_AUTHDATA_WIN2K_PAC data.
+ */
+ status = get_authz_data_pac(be->be_gssctx,
+ &be->be_authz_pac);
+ if (status)
+ return (status);
+
+ kerr = krb5_pac_parse(be->be_kctx, be->be_authz_pac.value,
+ be->be_authz_pac.length, &be->be_kpac);
+ if (kerr) {
+ smbd_report("krb5ssp, krb5_pac_parse: %s",
+ krb5_get_error_message(be->be_kctx, kerr));
+ return (NT_STATUS_UNSUCCESSFUL);
+ }
+
+ kerr = krb5_pac_get_buffer(be->be_kctx, be->be_kpac,
+ PAC_LOGON_INFO, &be->be_pac);
+ if (kerr) {
+ smbd_report("krb5ssp, krb5_pac_get_buffer: %s",
+ krb5_get_error_message(be->be_kctx, kerr));
+ return (NT_STATUS_UNSUCCESSFUL);
+ }
+
+ ctx->ctx_token = calloc(1, sizeof (smb_token_t));
+ if (ctx->ctx_token == NULL)
+ return (NT_STATUS_NO_MEMORY);
+
+ status = smb_decode_krb5_pac(ctx->ctx_token, be->be_pac.data,
+ be->be_pac.length);
+ if (status)
+ return (status);
+
+ status = get_ssnkey(ctx);
+ if (status)
+ return (status);
+
+ if (!smb_token_setup_common(ctx->ctx_token))
+ return (NT_STATUS_UNSUCCESSFUL);
+
+ /* Success! */
+ ctx->ctx_orawtype = LSA_MTYPE_ES_DONE;
+
+ return (0);
+}
+
+/*
+ * See: GSS_KRB5_EXTRACT_AUTHZ_DATA_FROM_SEC_CONTEXT_OID
+ * and: KRB5_AUTHDATA_WIN2K_PAC
+ */
+static const gss_OID_desc
+oid_ex_authz_data_pac = {
+ 13, "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02\x05\x0a\x81\x00" };
+
+/*
+ * See: krb5_gss_inquire_sec_context_by_oid()
+ * and krb5_gss_inquire_sec_context_by_oid_ops[],
+ * gss_krb5int_extract_authz_data_from_sec_context()
+ */
+static uint32_t
+get_authz_data_pac(
+ gss_ctx_id_t context_handle,
+ gss_buffer_t ad_data)
+{
+ gss_buffer_set_t data_set = GSS_C_NO_BUFFER_SET;
+ OM_uint32 major, minor;
+ uint32_t status = NT_STATUS_UNSUCCESSFUL;
+
+ if (ad_data == NULL)
+ goto out;
+
+ major = gss_inquire_sec_context_by_oid(
+ &minor,
+ context_handle,
+ (gss_OID)&oid_ex_authz_data_pac,
+ &data_set);
+ if (GSS_ERROR(major)) {
+ smbd_report("krb5ssp, gss_inquire...PAC, "
+ "major=0x%x, minor=0x%x", major, minor);
+ goto out;
+ }
+
+ if ((data_set == GSS_C_NO_BUFFER_SET) || (data_set->count == 0)) {
+ goto out;
+ }
+
+ /* Only need the first element? */
+ ad_data->length = data_set->elements[0].length;
+ ad_data->value = malloc(ad_data->length);
+ if (ad_data->value == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ bcopy(data_set->elements[0].value, ad_data->value, ad_data->length);
+ status = 0;
+
+out:
+ (void) gss_release_buffer_set(&minor, &data_set);
+
+ return (status);
+}
+
+/*
+ * Get the session key, and save it in the token.
+ *
+ * See: krb5_gss_inquire_sec_context_by_oid(),
+ * krb5_gss_inquire_sec_context_by_oid_ops[], and
+ * gss_krb5int_inq_session_key
+ */
+static uint32_t
+get_ssnkey(authsvc_context_t *ctx)
+{
+ krb5ssp_backend_t *be = ctx->ctx_backend;
+ gss_buffer_set_t data_set = GSS_C_NO_BUFFER_SET;
+ OM_uint32 major, minor;
+ size_t keylen;
+ uint32_t status = NT_STATUS_UNSUCCESSFUL;
+
+ major = gss_inquire_sec_context_by_oid(&minor,
+ be->be_gssctx, GSS_C_INQ_SSPI_SESSION_KEY, &data_set);
+ if (GSS_ERROR(major)) {
+ smbd_report("krb5ssp, failed to get session key, "
+ "major=0x%x, minor=0x%x", major, minor);
+ goto out;
+ }
+
+ /*
+ * The key is in the first element
+ */
+ if (data_set == GSS_C_NO_BUFFER_SET ||
+ data_set->count == 0 ||
+ data_set->elements[0].length == 0 ||
+ data_set->elements[0].value == NULL) {
+ smbd_report("krb5ssp: Session key is missing");
+ goto out;
+ }
+ if ((keylen = data_set->elements[0].length) < SMBAUTH_HASH_SZ) {
+ smbd_report("krb5ssp: Session key too short (%d)",
+ data_set->elements[0].length);
+ goto out;
+ }
+
+ ctx->ctx_token->tkn_ssnkey.val = malloc(keylen);
+ if (ctx->ctx_token->tkn_ssnkey.val == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ ctx->ctx_token->tkn_ssnkey.len = keylen;
+ bcopy(data_set->elements[0].value,
+ ctx->ctx_token->tkn_ssnkey.val, keylen);
+ status = 0;
+
+out:
+ (void) gss_release_buffer_set(&minor, &data_set);
+ return (status);
+}
diff --git a/usr/src/cmd/smbsrv/smbd/smbd_logon.c b/usr/src/cmd/smbsrv/smbd/smbd_logon.c
index ad19f46655..fa7dae801b 100644
--- a/usr/src/cmd/smbsrv/smbd/smbd_logon.c
+++ b/usr/src/cmd/smbsrv/smbd/smbd_logon.c
@@ -20,6 +20,7 @@
*/
/*
* Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright 2014 Nexenta Systems, Inc. All rights reserved.
*/
#include <sys/types.h>
@@ -84,6 +85,7 @@ smbd_user_auth_logon(smb_logon_t *user_info)
smb_audit_t *entry;
adt_session_data_t *ah;
adt_event_data_t *event;
+ smb_logon_t tmp_user;
au_tid_addr_t termid;
char sidbuf[SMB_SID_STRSZ];
char *username;
@@ -94,12 +96,27 @@ smbd_user_auth_logon(smb_logon_t *user_info)
int status;
int retval;
- if ((token = smb_logon(user_info)) == NULL) {
+ if (user_info->lg_username == NULL ||
+ user_info->lg_domain == NULL ||
+ user_info->lg_workstation == NULL) {
+ return (NULL);
+ }
+
+ tmp_user = *user_info;
+ if (tmp_user.lg_username[0] == '\0') {
+ tmp_user.lg_flags |= SMB_ATF_ANON;
+ tmp_user.lg_e_username = "anonymous";
+ } else {
+ tmp_user.lg_e_username = tmp_user.lg_username;
+ }
+ tmp_user.lg_e_domain = tmp_user.lg_domain;
+
+ if ((token = smb_logon(&tmp_user)) == NULL) {
uid = ADT_NO_ATTRIB;
gid = ADT_NO_ATTRIB;
sid = NT_NULL_SIDSTR;
- username = user_info->lg_e_username;
- domain = user_info->lg_e_domain;
+ username = tmp_user.lg_e_username;
+ domain = tmp_user.lg_e_domain;
status = ADT_FAILURE;
retval = ADT_FAIL_VALUE_AUTH;
} else {
diff --git a/usr/src/cmd/smbsrv/smbd/smbd_main.c b/usr/src/cmd/smbsrv/smbd/smbd_main.c
index 59b11eb702..b1c9dbc770 100644
--- a/usr/src/cmd/smbsrv/smbd/smbd_main.c
+++ b/usr/src/cmd/smbsrv/smbd/smbd_main.c
@@ -20,7 +20,7 @@
*/
/*
* Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2013 Nexenta Systems, Inc. All rights reserved.
+ * Copyright 2014 Nexenta Systems, Inc. All rights reserved.
*/
#include <sys/types.h>
@@ -433,6 +433,7 @@ smbd_service_init(void)
{ SMB_SYSTEM32, 0755 },
{ SMB_VSS, 0755 },
{ SMB_PIPE_DIR, 0755 },
+ { "/var/smb/lipc", 0755 },
};
int rc, i;
@@ -457,6 +458,12 @@ smbd_service_init(void)
}
}
+ /*
+ * This environment variable tells mech_krb5 to give us
+ * MS-compatible behavior.
+ */
+ (void) putenv("MS_INTEROP=1");
+
if ((rc = smb_ccache_init(SMB_VARRUN_DIR, SMB_CCACHE_FILE)) != 0) {
if (rc == -1)
smbd_report("mkdir %s: %s", SMB_VARRUN_DIR,
@@ -503,6 +510,11 @@ smbd_service_init(void)
return (-1);
}
+ if (smbd_authsvc_start() != 0) {
+ smbd_report("authsvc initialization failed");
+ return (-1);
+ }
+
smbd.s_door_srv = smbd_door_start();
if (smbd.s_door_srv < 0) {
smbd_report("door initialization failed %s", strerror(errno));
@@ -555,6 +567,7 @@ smbd_service_fini(void)
smb_lgrp_stop();
smbd_pipesvc_stop();
smbd_door_stop();
+ smbd_authsvc_stop();
smbd_spool_stop();
smbd_kernel_unbind();
smbd_share_stop();
@@ -708,6 +721,7 @@ smbd_kernel_bind(void)
if (smbd.s_kbound) {
smb_load_kconfig(&cfg);
+ smbd_get_authconf(&cfg);
rc = smb_kmod_setcfg(&cfg);
if (rc < 0)
smbd_report("kernel configuration update failed: %s",
@@ -738,6 +752,7 @@ smbd_kernel_start(void)
int rc;
smb_load_kconfig(&cfg);
+ smbd_get_authconf(&cfg);
rc = smb_kmod_setcfg(&cfg);
if (rc != 0) {
smbd_report("kernel config ioctl error: %s", strerror(rc));
diff --git a/usr/src/cmd/smbsrv/smbd/smbd_ntlmssp.c b/usr/src/cmd/smbsrv/smbd/smbd_ntlmssp.c
new file mode 100644
index 0000000000..8027e3272b
--- /dev/null
+++ b/usr/src/cmd/smbsrv/smbd/smbd_ntlmssp.c
@@ -0,0 +1,595 @@
+/*
+ * 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 2015 Nexenta Systems, Inc. All rights reserved.
+ */
+
+/*
+ * SPNEGO back-end for NTLMSSP. See [MS-NLMP]
+ */
+
+#include <sys/types.h>
+#include <sys/byteorder.h>
+#include <strings.h>
+#include "smbd.h"
+#include "smbd_authsvc.h"
+#include "netsmb/ntlmssp.h"
+#include <assert.h>
+
+/* A shorter alias for a crazy long name from [MS-NLMP] */
+#define NTLMSSP_NEGOTIATE_NTLM2 \
+ NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY
+
+/* Need this in a header somewhere */
+#ifdef _LITTLE_ENDIAN
+/* little-endian values on little-endian */
+#define htolel(x) ((uint32_t)(x))
+#define letohl(x) ((uint32_t)(x))
+#else /* (BYTE_ORDER == LITTLE_ENDIAN) */
+/* little-endian values on big-endian (swap) */
+#define letohl(x) BSWAP_32(x)
+#define htolel(x) BSWAP_32(x)
+#endif /* (BYTE_ORDER == LITTLE_ENDIAN) */
+
+typedef struct ntlmssp_backend {
+ uint32_t expect_type;
+ uint32_t clnt_flags;
+ uint32_t srv_flags;
+ char srv_challenge[8];
+} ntlmssp_backend_t;
+
+struct genhdr {
+ char h_id[8]; /* "NTLMSSP" */
+ uint32_t h_type;
+};
+
+struct sec_buf {
+ uint16_t sb_length;
+ uint16_t sb_maxlen;
+ uint32_t sb_offset;
+};
+
+struct nego_hdr {
+ char h_id[8];
+ uint32_t h_type;
+ uint32_t h_flags;
+ /* workstation domain, name (place holders) */
+ uint16_t ws_dom[4];
+ uint16_t ws_name[4];
+};
+
+struct auth_hdr {
+ char h_id[8];
+ uint32_t h_type;
+ struct sec_buf h_lm_resp;
+ struct sec_buf h_nt_resp;
+ struct sec_buf h_domain;
+ struct sec_buf h_user;
+ struct sec_buf h_wksta;
+ struct sec_buf h_essn_key; /* encrypted session key */
+ uint32_t h_flags;
+ /* Version struct (optional) */
+ /* MIC hash (optional) */
+};
+
+/* Allow turning these off for debugging, etc. */
+int smbd_signing_enabled = 1;
+
+int smbd_constant_challenge = 0;
+static uint8_t constant_chal[8] = {
+ 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88 };
+
+static int smbd_ntlmssp_negotiate(authsvc_context_t *);
+static int smbd_ntlmssp_authenticate(authsvc_context_t *);
+static int encode_avpair_str(smb_msgbuf_t *, uint16_t, char *);
+static int decode_secbuf_bin(smb_msgbuf_t *, struct sec_buf *, void **);
+static int decode_secbuf_str(smb_msgbuf_t *, struct sec_buf *, char **);
+
+/*
+ * Initialize this context for NTLMSSP, if possible.
+ */
+int
+smbd_ntlmssp_init(authsvc_context_t *ctx)
+{
+ ntlmssp_backend_t *be;
+
+ be = malloc(sizeof (*be));
+ if (be == 0)
+ return (NT_STATUS_NO_MEMORY);
+ bzero(be, sizeof (*be));
+ be->expect_type = NTLMSSP_MSGTYPE_NEGOTIATE;
+ ctx->ctx_backend = be;
+
+ return (0);
+}
+
+void
+smbd_ntlmssp_fini(authsvc_context_t *ctx)
+{
+ free(ctx->ctx_backend);
+}
+
+/*
+ * Handle an auth message
+ */
+int
+smbd_ntlmssp_work(authsvc_context_t *ctx)
+{
+ struct genhdr *ihdr = ctx->ctx_ibodybuf;
+ ntlmssp_backend_t *be = ctx->ctx_backend;
+ uint32_t mtype;
+ int rc;
+
+ if (ctx->ctx_ibodylen < sizeof (*ihdr))
+ return (NT_STATUS_INVALID_PARAMETER);
+
+ if (bcmp(ihdr->h_id, "NTLMSSP", 8))
+ return (NT_STATUS_INVALID_PARAMETER);
+ mtype = letohl(ihdr->h_type);
+ if (mtype != be->expect_type)
+ return (NT_STATUS_INVALID_PARAMETER);
+
+ switch (mtype) {
+ case NTLMSSP_MSGTYPE_NEGOTIATE:
+ ctx->ctx_orawtype = LSA_MTYPE_ES_CONT;
+ rc = smbd_ntlmssp_negotiate(ctx);
+ break;
+ case NTLMSSP_MSGTYPE_AUTHENTICATE:
+ ctx->ctx_orawtype = LSA_MTYPE_ES_DONE;
+ rc = smbd_ntlmssp_authenticate(ctx);
+ break;
+
+ default:
+ case NTLMSSP_MSGTYPE_CHALLENGE:
+ /* Sent by servers, not received. */
+ rc = NT_STATUS_INVALID_PARAMETER;
+ break;
+ }
+
+ return (rc);
+}
+
+#if (MAXHOSTNAMELEN < NETBIOS_NAME_SZ)
+#error "MAXHOSTNAMELEN < NETBIOS_NAME_SZ"
+#endif
+
+/*
+ * Handle an NTLMSSP_MSGTYPE_NEGOTIATE message, and reply
+ * with an NTLMSSP_MSGTYPE_CHALLENGE message.
+ * See: [MS-NLMP] 2.2.1.1, 3.2.5.1.1
+ */
+static int
+smbd_ntlmssp_negotiate(authsvc_context_t *ctx)
+{
+ char tmp_name[MAXHOSTNAMELEN];
+ ntlmssp_backend_t *be = ctx->ctx_backend;
+ struct nego_hdr *ihdr = ctx->ctx_ibodybuf;
+ smb_msgbuf_t mb;
+ uint8_t *save_scan;
+ int secmode;
+ int mbflags;
+ int rc;
+ size_t var_start, var_end;
+ uint16_t var_size;
+
+ if (ctx->ctx_ibodylen < sizeof (*ihdr))
+ return (NT_STATUS_INVALID_PARAMETER);
+ be->clnt_flags = letohl(ihdr->h_flags);
+
+ /*
+ * Looks like we can ignore ws_dom, ws_name.
+ * Otherwise would parse those here.
+ */
+
+ secmode = smb_config_get_secmode();
+ if (smbd_constant_challenge) {
+ (void) memcpy(be->srv_challenge, constant_chal,
+ sizeof (be->srv_challenge));
+ } else {
+ randomize(be->srv_challenge, sizeof (be->srv_challenge));
+ }
+
+ /*
+ * Compute srv_flags
+ */
+ be->srv_flags =
+ NTLMSSP_REQUEST_TARGET |
+ NTLMSSP_NEGOTIATE_NTLM |
+ NTLMSSP_NEGOTIATE_TARGET_INFO;
+ be->srv_flags |= be->clnt_flags & (
+ NTLMSSP_NEGOTIATE_NTLM2 |
+ NTLMSSP_NEGOTIATE_128 |
+ NTLMSSP_NEGOTIATE_KEY_EXCH |
+ NTLMSSP_NEGOTIATE_56);
+
+ if (smbd_signing_enabled) {
+ be->srv_flags |= be->clnt_flags & (
+ NTLMSSP_NEGOTIATE_SIGN |
+ NTLMSSP_NEGOTIATE_SEAL |
+ NTLMSSP_NEGOTIATE_ALWAYS_SIGN);
+ }
+
+ if (be->clnt_flags & NTLMSSP_NEGOTIATE_UNICODE)
+ be->srv_flags |= NTLMSSP_NEGOTIATE_UNICODE;
+ else if (be->clnt_flags & NTLMSSP_NEGOTIATE_OEM)
+ be->srv_flags |= NTLMSSP_NEGOTIATE_OEM;
+
+ /* LM Key is mutually exclusive with NTLM2 */
+ if ((be->srv_flags & NTLMSSP_NEGOTIATE_NTLM2) == 0 &&
+ (be->clnt_flags & NTLMSSP_NEGOTIATE_LM_KEY) != 0)
+ be->srv_flags |= NTLMSSP_NEGOTIATE_LM_KEY;
+
+ /* Get our "target name" */
+ if (secmode == SMB_SECMODE_DOMAIN) {
+ be->srv_flags |= NTLMSSP_TARGET_TYPE_DOMAIN;
+ rc = smb_getdomainname(tmp_name, NETBIOS_NAME_SZ);
+ } else {
+ be->srv_flags |= NTLMSSP_TARGET_TYPE_SERVER;
+ rc = smb_getnetbiosname(tmp_name, NETBIOS_NAME_SZ);
+ }
+ if (rc)
+ goto errout;
+
+ /*
+ * Build the NTLMSSP_MSGTYPE_CHALLENGE message.
+ */
+ mbflags = SMB_MSGBUF_NOTERM;
+ if (be->srv_flags & NTLMSSP_NEGOTIATE_UNICODE)
+ mbflags |= SMB_MSGBUF_UNICODE;
+ smb_msgbuf_init(&mb, ctx->ctx_obodybuf, ctx->ctx_obodylen, mbflags);
+
+ /*
+ * Fixed size parts
+ */
+ rc = smb_msgbuf_encode(
+ &mb, "8clwwll8cllwwl", /* offset, name (fmt) */
+ "NTLMSSP", /* 0: signature (8c) */
+ NTLMSSP_MSGTYPE_CHALLENGE, /* 8: type (l) */
+ 0, 0, 0, /* filled later: 12: target name (wwl) */
+ be->srv_flags, /* 20: flags (l) */
+ be->srv_challenge, /* 24: (8c) */
+ 0, 0, /* 32: reserved (ll) */
+ 0, 0, 0); /* filled later: 40: target info (wwl) */
+#define TARGET_NAME_OFFSET 12
+#define TARGET_INFO_OFFSET 40
+ if (rc < 0)
+ goto errout;
+
+ /*
+ * Variable length parts.
+ *
+ * Target name
+ */
+ var_start = smb_msgbuf_used(&mb);
+ rc = smb_msgbuf_encode(&mb, "u", tmp_name);
+ var_end = smb_msgbuf_used(&mb);
+ var_size = (uint16_t)(var_end - var_start);
+ if (rc < 0)
+ goto errout;
+
+ /* overwrite target name offset+lengths */
+ save_scan = mb.scan;
+ mb.scan = mb.base + TARGET_NAME_OFFSET;
+ (void) smb_msgbuf_encode(&mb, "wwl", var_size, var_size, var_start);
+ mb.scan = save_scan;
+
+ /*
+ * Target info (AvPairList)
+ *
+ * These AV pairs are like our name/value pairs, but have
+ * numeric identifiers instead of names. There are many
+ * of these, but we put only the four expected by Windows:
+ * NetBIOS computer name
+ * NetBIOS domain name
+ * DNS computer name
+ * DNS domain name
+ * Note that "domain" above (even "DNS domain") refers to
+ * the AD domain of which we're a member, which may be
+ * _different_ from the configured DNS domain.
+ *
+ * Also note that in "workgroup" mode (not a domain member)
+ * all "domain" fields should be set to the same values as
+ * the "computer" fields ("bare" host name, not FQDN).
+ */
+ var_start = smb_msgbuf_used(&mb);
+
+ /* NetBIOS Computer Name */
+ if (smb_getnetbiosname(tmp_name, NETBIOS_NAME_SZ))
+ goto errout;
+ if (encode_avpair_str(&mb, MsvAvNbComputerName, tmp_name) < 0)
+ goto errout;
+
+ if (secmode != SMB_SECMODE_DOMAIN) {
+ /*
+ * Workgroup mode. Set all to hostname.
+ * tmp_name = netbios hostname from above.
+ */
+ if (encode_avpair_str(&mb, MsvAvNbDomainName, tmp_name) < 0)
+ goto errout;
+ /*
+ * Want the bare computer name here (not FQDN).
+ */
+ if (smb_gethostname(tmp_name, MAXHOSTNAMELEN, SMB_CASE_LOWER))
+ goto errout;
+ if (encode_avpair_str(&mb, MsvAvDnsComputerName, tmp_name) < 0)
+ goto errout;
+ if (encode_avpair_str(&mb, MsvAvDnsDomainName, tmp_name) < 0)
+ goto errout;
+ } else {
+ /*
+ * Domain mode. Use real host and domain values.
+ */
+
+ /* NetBIOS Domain Name */
+ if (smb_getdomainname(tmp_name, NETBIOS_NAME_SZ))
+ goto errout;
+ if (encode_avpair_str(&mb, MsvAvNbDomainName, tmp_name) < 0)
+ goto errout;
+
+ /* DNS Computer Name */
+ if (smb_getfqhostname(tmp_name, MAXHOSTNAMELEN))
+ goto errout;
+ if (encode_avpair_str(&mb, MsvAvDnsComputerName, tmp_name) < 0)
+ goto errout;
+
+ /* DNS Domain Name */
+ if (smb_getfqdomainname(tmp_name, MAXHOSTNAMELEN))
+ goto errout;
+ if (encode_avpair_str(&mb, MsvAvDnsDomainName, tmp_name) < 0)
+ goto errout;
+ }
+
+ /* End marker */
+ if (smb_msgbuf_encode(&mb, "ww", MsvAvEOL, 0) < 0)
+ goto errout;
+ var_end = smb_msgbuf_used(&mb);
+ var_size = (uint16_t)(var_end - var_start);
+
+ /* overwrite target offset+lengths */
+ save_scan = mb.scan;
+ mb.scan = mb.base + TARGET_INFO_OFFSET;
+ (void) smb_msgbuf_encode(&mb, "wwl", var_size, var_size, var_start);
+ mb.scan = save_scan;
+
+ ctx->ctx_obodylen = smb_msgbuf_used(&mb);
+ smb_msgbuf_term(&mb);
+
+ be->expect_type = NTLMSSP_MSGTYPE_AUTHENTICATE;
+
+ return (0);
+
+errout:
+ smb_msgbuf_term(&mb);
+ return (NT_STATUS_INTERNAL_ERROR);
+}
+
+static int
+encode_avpair_str(smb_msgbuf_t *mb, uint16_t AvId, char *name)
+{
+ int rc;
+ uint16_t len;
+
+ len = smb_wcequiv_strlen(name);
+ rc = smb_msgbuf_encode(mb, "wwU", AvId, len, name);
+ return (rc);
+}
+
+/*
+ * Handle an NTLMSSP_MSGTYPE_AUTHENTICATE message.
+ * See: [MS-NLMP] 2.2.1.3, 3.2.5.1.2
+ */
+static int
+smbd_ntlmssp_authenticate(authsvc_context_t *ctx)
+{
+ struct auth_hdr hdr;
+ smb_msgbuf_t mb;
+ smb_logon_t user_info;
+ smb_token_t *token = NULL;
+ ntlmssp_backend_t *be = ctx->ctx_backend;
+ void *lm_resp;
+ void *nt_resp;
+ char *domain;
+ char *user;
+ char *wksta;
+ void *essn_key; /* encrypted session key (optional) */
+ int mbflags;
+ uint_t status = NT_STATUS_INTERNAL_ERROR;
+ char combined_challenge[SMBAUTH_CHAL_SZ];
+ unsigned char kxkey[SMBAUTH_HASH_SZ];
+ boolean_t ntlm_v1x = B_FALSE;
+
+ bzero(&user_info, sizeof (user_info));
+
+ /*
+ * Parse the NTLMSSP_MSGTYPE_AUTHENTICATE message.
+ */
+ if (ctx->ctx_ibodylen < sizeof (hdr))
+ return (NT_STATUS_INVALID_PARAMETER);
+ mbflags = SMB_MSGBUF_NOTERM;
+ if (be->srv_flags & NTLMSSP_NEGOTIATE_UNICODE)
+ mbflags |= SMB_MSGBUF_UNICODE;
+ smb_msgbuf_init(&mb, ctx->ctx_ibodybuf, ctx->ctx_ibodylen, mbflags);
+ bzero(&hdr, sizeof (hdr));
+
+ if (smb_msgbuf_decode(&mb, "12.") < 0)
+ goto errout;
+ if (decode_secbuf_bin(&mb, &hdr.h_lm_resp, &lm_resp) < 0)
+ goto errout;
+ if (decode_secbuf_bin(&mb, &hdr.h_nt_resp, &nt_resp) < 0)
+ goto errout;
+ if (decode_secbuf_str(&mb, &hdr.h_domain, &domain) < 0)
+ goto errout;
+ if (decode_secbuf_str(&mb, &hdr.h_user, &user) < 0)
+ goto errout;
+ if (decode_secbuf_str(&mb, &hdr.h_wksta, &wksta) < 0)
+ goto errout;
+ if (decode_secbuf_bin(&mb, &hdr.h_essn_key, &essn_key) < 0)
+ goto errout;
+ if (smb_msgbuf_decode(&mb, "l", &be->clnt_flags) < 0)
+ goto errout;
+
+ if (be->clnt_flags & NTLMSSP_NEGOTIATE_KEY_EXCH) {
+ if (hdr.h_essn_key.sb_length < 16 || essn_key == NULL)
+ goto errout;
+ }
+
+ user_info.lg_level = NETR_NETWORK_LOGON;
+ user_info.lg_flags = 0;
+
+ user_info.lg_ntlm_flags = be->clnt_flags;
+ user_info.lg_username = (user) ? user : "";
+ user_info.lg_domain = (domain) ? domain : "";
+ user_info.lg_workstation = (wksta) ? wksta : "";
+
+ user_info.lg_clnt_ipaddr =
+ ctx->ctx_clinfo.lci_clnt_ipaddr;
+ user_info.lg_local_port = 445;
+
+ user_info.lg_challenge_key.len = SMBAUTH_CHAL_SZ;
+ user_info.lg_challenge_key.val = (uint8_t *)be->srv_challenge;
+
+ user_info.lg_nt_password.len = hdr.h_nt_resp.sb_length;
+ user_info.lg_nt_password.val = nt_resp;
+
+ user_info.lg_lm_password.len = hdr.h_lm_resp.sb_length;
+ user_info.lg_lm_password.val = lm_resp;
+
+ user_info.lg_native_os = ctx->ctx_clinfo.lci_native_os;
+ user_info.lg_native_lm = ctx->ctx_clinfo.lci_native_lm;
+
+ /*
+ * If we're doing extended session security, the challenge
+ * this OWF was computed with is different. [MS-NLMP 3.3.1]
+ * It's: MD5(concat(ServerChallenge,ClientChallenge))
+ * where the ClientChallenge is in the LM resp. field.
+ */
+ if (user_info.lg_nt_password.len == SMBAUTH_LM_RESP_SZ &&
+ user_info.lg_lm_password.len >= SMBAUTH_CHAL_SZ &&
+ (be->clnt_flags & NTLMSSP_NEGOTIATE_NTLM2) != 0) {
+ smb_auth_ntlm2_mkchallenge(combined_challenge,
+ be->srv_challenge, lm_resp);
+ user_info.lg_challenge_key.val =
+ (uint8_t *)combined_challenge;
+ user_info.lg_lm_password.len = 0;
+ ntlm_v1x = B_TRUE;
+ }
+
+ /*
+ * This (indirectly) calls smb_auth_validate() to
+ * check that the client gave us a valid hash.
+ */
+ token = smbd_user_auth_logon(&user_info);
+ if (token == NULL) {
+ status = NT_STATUS_ACCESS_DENIED;
+ goto errout;
+ }
+
+ if (token->tkn_ssnkey.val != NULL &&
+ token->tkn_ssnkey.len == SMBAUTH_HASH_SZ) {
+
+ /*
+ * At this point, token->tkn_session_key is the
+ * "Session Base Key" [MS-NLMP] 3.2.5.1.2
+ * Compute the final session key. First need the
+ * "Key Exchange Key" [MS-NLMP] 3.4.5.1
+ */
+ if (ntlm_v1x) {
+ smb_auth_ntlm2_kxkey(kxkey,
+ be->srv_challenge, lm_resp,
+ token->tkn_ssnkey.val);
+ } else {
+ /* KXKEY is the Session Base Key. */
+ (void) memcpy(kxkey, token->tkn_ssnkey.val,
+ SMBAUTH_HASH_SZ);
+ }
+
+ /*
+ * If the client give us an encrypted session key,
+ * decrypt it (RC4) using the "key exchange key".
+ */
+ if (be->clnt_flags & NTLMSSP_NEGOTIATE_KEY_EXCH) {
+ /* RC4 args: result, key, data */
+ (void) smb_auth_RC4(token->tkn_ssnkey.val,
+ SMBAUTH_HASH_SZ, kxkey, SMBAUTH_HASH_SZ,
+ essn_key, hdr.h_essn_key.sb_length);
+ } else {
+ /* Final key is the KXKEY */
+ (void) memcpy(token->tkn_ssnkey.val, kxkey,
+ SMBAUTH_HASH_SZ);
+ }
+ }
+
+ ctx->ctx_token = token;
+ ctx->ctx_obodylen = 0;
+
+ smb_msgbuf_term(&mb);
+ return (0);
+
+errout:
+ smb_msgbuf_term(&mb);
+ return (status);
+}
+
+static int
+decode_secbuf_bin(smb_msgbuf_t *mb, struct sec_buf *sb, void **binp)
+{
+ int rc;
+
+ *binp = NULL;
+ rc = smb_msgbuf_decode(
+ mb, "wwl",
+ &sb->sb_length,
+ &sb->sb_maxlen,
+ &sb->sb_offset);
+ if (rc < 0)
+ return (rc);
+
+ if (sb->sb_offset > mb->max)
+ return (SMB_MSGBUF_UNDERFLOW);
+ if (sb->sb_length > (mb->max - sb->sb_offset))
+ return (SMB_MSGBUF_UNDERFLOW);
+ if (sb->sb_length == 0)
+ return (rc);
+
+ *binp = mb->base + sb->sb_offset;
+ return (0);
+}
+
+static int
+decode_secbuf_str(smb_msgbuf_t *mb, struct sec_buf *sb, char **cpp)
+{
+ uint8_t *save_scan;
+ int rc;
+
+ *cpp = NULL;
+ rc = smb_msgbuf_decode(
+ mb, "wwl",
+ &sb->sb_length,
+ &sb->sb_maxlen,
+ &sb->sb_offset);
+ if (rc < 0)
+ return (rc);
+
+ if (sb->sb_offset > mb->max)
+ return (SMB_MSGBUF_UNDERFLOW);
+ if (sb->sb_length > (mb->max - sb->sb_offset))
+ return (SMB_MSGBUF_UNDERFLOW);
+ if (sb->sb_length == 0)
+ return (rc);
+
+ save_scan = mb->scan;
+ mb->scan = mb->base + sb->sb_offset;
+ rc = smb_msgbuf_decode(mb, "#u", (int)sb->sb_length, cpp);
+ mb->scan = save_scan;
+
+ return (rc);
+}