diff options
| author | Gordon Ross <gwr@nexenta.com> | 2013-06-21 15:59:58 -0400 |
|---|---|---|
| committer | Gordon Ross <gwr@nexenta.com> | 2015-10-26 10:16:22 -0400 |
| commit | 12b65585e720714b31036daaa2b30eb76014048e (patch) | |
| tree | c413afd3fb76e04e53ec04ce601d8c3ff35c65ab /usr/src/cmd/smbsrv | |
| parent | 056d3a7d553516b590a0543f4df3152a3144b42b (diff) | |
| download | illumos-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/Makefile | 15 | ||||
| -rwxr-xr-x | usr/src/cmd/smbsrv/fksmbd/Run.sh | 19 | ||||
| -rw-r--r-- | usr/src/cmd/smbsrv/fksmbd/fksmbd_kmod.c | 22 | ||||
| -rw-r--r-- | usr/src/cmd/smbsrv/smbd/Makefile | 14 | ||||
| -rw-r--r-- | usr/src/cmd/smbsrv/smbd/server.xml | 2 | ||||
| -rw-r--r-- | usr/src/cmd/smbsrv/smbd/smbd.h | 6 | ||||
| -rw-r--r-- | usr/src/cmd/smbsrv/smbd/smbd_authsvc.c | 973 | ||||
| -rw-r--r-- | usr/src/cmd/smbsrv/smbd/smbd_authsvc.h | 82 | ||||
| -rw-r--r-- | usr/src/cmd/smbsrv/smbd/smbd_doorsvc.c | 24 | ||||
| -rw-r--r-- | usr/src/cmd/smbsrv/smbd/smbd_krb5ssp.c | 350 | ||||
| -rw-r--r-- | usr/src/cmd/smbsrv/smbd/smbd_logon.c | 23 | ||||
| -rw-r--r-- | usr/src/cmd/smbsrv/smbd/smbd_main.c | 17 | ||||
| -rw-r--r-- | usr/src/cmd/smbsrv/smbd/smbd_ntlmssp.c | 595 |
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); +} |
