9873 SMB logon fails during 1st second after service start
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
Reviewed by: Garrett D'Amore <garrett@damore.org>
Approved by: Dan McDonald <danmcd@joyent.com>
diff --git a/usr/src/cmd/smbsrv/smbd/smbd_authsvc.c b/usr/src/cmd/smbsrv/smbd/smbd_authsvc.c
index 0b6af80..b507148 100644
--- a/usr/src/cmd/smbsrv/smbd/smbd_authsvc.c
+++ b/usr/src/cmd/smbsrv/smbd/smbd_authsvc.c
@@ -10,7 +10,7 @@
  */
 
 /*
- * Copyright 2014 Nexenta Systems, Inc.  All rights reserved.
+ * Copyright 2017 Nexenta Systems, Inc.  All rights reserved.
  */
 
 /*
@@ -549,8 +549,12 @@
 
 	token = smbd_user_auth_logon(&user_info);
 	xdr_free(smb_logon_xdr, (char *)&user_info);
-	if (token == NULL)
-		return (NT_STATUS_ACCESS_DENIED);
+	if (token == NULL) {
+		rc = user_info.lg_status;
+		if (rc == 0) /* should not happen */
+			rc = NT_STATUS_INTERNAL_ERROR;
+		return (rc);
+	}
 
 	ctx->ctx_token = token;
 
diff --git a/usr/src/cmd/smbsrv/smbd/smbd_logon.c b/usr/src/cmd/smbsrv/smbd/smbd_logon.c
index ab6d4c2..69b32b2 100644
--- a/usr/src/cmd/smbsrv/smbd/smbd_logon.c
+++ b/usr/src/cmd/smbsrv/smbd/smbd_logon.c
@@ -77,6 +77,8 @@
 /*
  * Invoked at user logon due to SmbSessionSetupX.  Authenticate the
  * user, start an audit session and audit the event.
+ *
+ * On error, returns NULL, and status in user_info->lg_status
  */
 smb_token_t *
 smbd_user_auth_logon(smb_logon_t *user_info)
@@ -101,9 +103,16 @@
 	if (user_info->lg_username == NULL ||
 	    user_info->lg_domain == NULL ||
 	    user_info->lg_workstation == NULL) {
+		user_info->lg_status = NT_STATUS_INVALID_PARAMETER;
 		return (NULL);
 	}
 
+	/*
+	 * Avoid modifying the caller-provided struct because it
+	 * may or may not point to allocated strings etc.
+	 * Copy to tmp_user, auth, then copy the (out) lg_status
+	 * member back to the caller-provided struct.
+	 */
 	tmp_user = *user_info;
 	if (tmp_user.lg_username[0] == '\0') {
 		tmp_user.lg_flags |= SMB_ATF_ANON;
@@ -126,7 +135,12 @@
 		tmp_user.lg_e_domain = tmp_user.lg_domain;
 	}
 
-	if ((token = smb_logon(&tmp_user)) == NULL) {
+	token = smb_logon(&tmp_user);
+	user_info->lg_status = tmp_user.lg_status;
+
+	if (token == NULL) {
+		if (user_info->lg_status == 0) /* should not happen */
+			user_info->lg_status = NT_STATUS_INTERNAL_ERROR;
 		uid = ADT_NO_ATTRIB;
 		gid = ADT_NO_ATTRIB;
 		sid = NT_NULL_SIDSTR;
@@ -147,12 +161,14 @@
 
 	if (adt_start_session(&ah, NULL, 0)) {
 		syslog(LOG_AUTH | LOG_ALERT, "adt_start_session: %m");
+		user_info->lg_status = NT_STATUS_AUDIT_FAILED;
 		goto errout;
 	}
 
 	if ((event = adt_alloc_event(ah, ADT_smbd_session)) == NULL) {
 		syslog(LOG_AUTH | LOG_ALERT,
 		    "adt_alloc_event(ADT_smbd_session): %m");
+		user_info->lg_status = NT_STATUS_AUDIT_FAILED;
 		goto errout;
 	}
 
@@ -172,6 +188,7 @@
 	if (adt_set_user(ah, uid, gid, uid, gid, NULL, ADT_NEW)) {
 		syslog(LOG_AUTH | LOG_ALERT, "adt_set_user: %m");
 		adt_free_event(event);
+		user_info->lg_status = NT_STATUS_AUDIT_FAILED;
 		goto errout;
 	}
 
@@ -187,6 +204,8 @@
 	if (token) {
 		if ((entry = malloc(sizeof (smb_audit_t))) == NULL) {
 			syslog(LOG_ERR, "smbd_user_auth_logon: %m");
+			user_info->lg_status =
+			    NT_STATUS_INSUFFICIENT_RESOURCES;
 			goto errout;
 		}
 
@@ -199,6 +218,8 @@
 		smb_autohome_add(token);
 		smbd_audit_link(entry);
 		token->tkn_audit_sid = entry->sa_audit_sid;
+
+		user_info->lg_status = NT_STATUS_SUCCESS;
 	}
 
 	free(buf);
diff --git a/usr/src/cmd/smbsrv/smbd/smbd_ntlmssp.c b/usr/src/cmd/smbsrv/smbd/smbd_ntlmssp.c
index 8027e32..80deaba 100644
--- a/usr/src/cmd/smbsrv/smbd/smbd_ntlmssp.c
+++ b/usr/src/cmd/smbsrv/smbd/smbd_ntlmssp.c
@@ -10,7 +10,7 @@
  */
 
 /*
- * Copyright 2015 Nexenta Systems, Inc.  All rights reserved.
+ * Copyright 2017 Nexenta Systems, Inc.  All rights reserved.
  */
 
 /*
@@ -36,8 +36,8 @@
 #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)
+#define	letohl(x)	BSWAP_32(x)
+#define	htolel(x)	BSWAP_32(x)
 #endif	/* (BYTE_ORDER == LITTLE_ENDIAN) */
 
 typedef struct ntlmssp_backend {
@@ -256,7 +256,7 @@
 	    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) */
+	    be->srv_challenge,		/* 24:		(8c) */
 	    0, 0,			/* 32: reserved (ll) */
 	    0, 0, 0);	/* filled later:   40: target info (wwl) */
 #define	TARGET_NAME_OFFSET	12
@@ -489,7 +489,9 @@
 	 */
 	token = smbd_user_auth_logon(&user_info);
 	if (token == NULL) {
-		status = NT_STATUS_ACCESS_DENIED;
+		status = user_info.lg_status;
+		if (status == 0) /* should not happen */
+			status = NT_STATUS_INTERNAL_ERROR;
 		goto errout;
 	}
 
diff --git a/usr/src/lib/smbsrv/libmlsvc/common/libmlsvc.h b/usr/src/lib/smbsrv/libmlsvc/common/libmlsvc.h
index 838353b..0506704 100644
--- a/usr/src/lib/smbsrv/libmlsvc/common/libmlsvc.h
+++ b/usr/src/lib/smbsrv/libmlsvc/common/libmlsvc.h
@@ -20,7 +20,7 @@
  */
 /*
  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2015 Nexenta Systems, Inc.  All rights reserved.
+ * Copyright 2017 Nexenta Systems, Inc.  All rights reserved.
  */
 
 #ifndef	_LIBMLSVC_H
@@ -66,7 +66,6 @@
 uint32_t smb_ddiscover_dns(char *, smb_domainex_t *);
 extern void smb_ddiscover_bad_dc(char *);
 extern void smb_ddiscover_refresh(void);
-extern int smb_ddiscover_wait(void);
 
 extern int dssetup_check_service(void);
 extern void dssetup_clear_domain_info(void);
diff --git a/usr/src/lib/smbsrv/libmlsvc/common/mlsvc.h b/usr/src/lib/smbsrv/libmlsvc/common/mlsvc.h
index 2afc62a..0aa45b0 100644
--- a/usr/src/lib/smbsrv/libmlsvc/common/mlsvc.h
+++ b/usr/src/lib/smbsrv/libmlsvc/common/mlsvc.h
@@ -20,7 +20,7 @@
  */
 /*
  * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2015 Nexenta Systems, Inc.  All rights reserved.
+ * Copyright 2018 Nexenta Systems, Inc.  All rights reserved.
  */
 
 #ifndef _SMBSRV_MLSVC_H
@@ -63,6 +63,8 @@
     struct netr_authenticator *);
 DWORD netr_validate_chain(struct netr_info *, struct netr_authenticator *);
 
+uint32_t smb_netlogon_check(char *, char *);
+
 int srvsvc_gettime(unsigned long *);
 void srvsvc_timecheck(char *, char *);
 
diff --git a/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_domain.c b/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_domain.c
index 74da88b..51bef15 100644
--- a/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_domain.c
+++ b/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_domain.c
@@ -21,7 +21,7 @@
 
 /*
  * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2015 Nexenta Systems, Inc.  All rights reserved.
+ * Copyright 2017 Nexenta Systems, Inc.  All rights reserved.
  */
 
 #include <syslog.h>
@@ -52,6 +52,9 @@
 #define	SMB_DCLOCATOR_TIMEOUT	45	/* seconds */
 #define	SMB_IS_FQDN(domain)	(strchr(domain, '.') != NULL)
 
+/* How long to pause after a failure to find any domain controllers. */
+int smb_ddiscover_failure_pause = 5; /* sec. */
+
 typedef struct smb_dclocator {
 	smb_dcinfo_t	sdl_dci; /* .dc_name .dc_addr */
 	char		sdl_domain[SMB_PI_MAX_DOMAIN];
@@ -90,6 +93,14 @@
 	pthread_attr_t tattr;
 	int rc;
 
+	/*
+	 * We need the smb_ddiscover_service to run on startup,
+	 * so it will enter smb_ddiscover_main() and put the
+	 * SMB "domain cache" into "updating" state so clients
+	 * trying to logon will wait while we're finding a DC.
+	 */
+	smb_dclocator.sdl_locate = B_TRUE;
+
 	(void) pthread_attr_init(&tattr);
 	(void) pthread_attr_setdetachstate(&tattr, PTHREAD_CREATE_DETACHED);
 	rc = pthread_create(&smb_dclocator_thr, &tattr,
@@ -241,6 +252,7 @@
 	 */
 	syslog(LOG_INFO, "smb_ddiscover, bad DC: %s", bad_dc);
 	smb_dclocator.sdl_bad_dc = B_TRUE;
+	smb_domain_bad_dc();
 
 	/* In-line smb_ddiscover_kick */
 	if (!smb_dclocator.sdl_locate) {
@@ -252,29 +264,6 @@
 	(void) mutex_unlock(&smb_dclocator.sdl_mtx);
 }
 
-/*
- * If domain discovery is running, wait for it to finish.
- */
-int
-smb_ddiscover_wait(void)
-{
-	timestruc_t to;
-	int rc = 0;
-
-	(void) mutex_lock(&smb_dclocator.sdl_mtx);
-
-	if (smb_dclocator.sdl_locate) {
-		to.tv_sec = SMB_DCLOCATOR_TIMEOUT;
-		to.tv_nsec = 0;
-		rc = cond_reltimedwait(&smb_dclocator.sdl_cv,
-		    &smb_dclocator.sdl_mtx, &to);
-	}
-
-	(void) mutex_unlock(&smb_dclocator.sdl_mtx);
-
-	return (rc);
-}
-
 
 /*
  * ==========================================================
@@ -354,10 +343,19 @@
 		status = smb_ddiscover_main(sdl->sdl_domain, &dxi);
 		if (status == 0)
 			smb_domain_save();
+
 		(void) mutex_lock(&sdl->sdl_mtx);
+
 		sdl->sdl_status = status;
-		if (status == 0)
+		if (status == 0) {
 			sdl->sdl_dci = dxi.d_dci;
+		} else {
+			syslog(LOG_DEBUG, "smb_ddiscover_service "
+			    "retry after STATUS_%s",
+			    xlate_nt_status(status));
+			(void) sleep(smb_ddiscover_failure_pause);
+			goto find_again;
+		}
 
 		/*
 		 * Run again if either of cfg_chg or bad_dc
@@ -405,11 +403,6 @@
 		return (NT_STATUS_INTERNAL_ERROR);
 	}
 
-	if (smb_domain_start_update() != SMB_DOMAIN_SUCCESS) {
-		syslog(LOG_DEBUG, "smb_ddiscover_main can't get lock");
-		return (NT_STATUS_INTERNAL_ERROR);
-	}
-
 	status = smb_ads_lookup_msdcs(domain, &dxi->d_dci);
 	if (status != 0) {
 		syslog(LOG_DEBUG, "smb_ddiscover_main can't find DC (%s)",
@@ -425,11 +418,15 @@
 		goto out;
 	}
 
-	smb_domain_update(dxi);
+	if (smb_domain_start_update() != SMB_DOMAIN_SUCCESS) {
+		syslog(LOG_DEBUG, "smb_ddiscover_main can't get lock");
+		status = NT_STATUS_INTERNAL_ERROR;
+	} else {
+		smb_domain_update(dxi);
+		smb_domain_end_update();
+	}
 
 out:
-	smb_domain_end_update();
-
 	/* Don't need the trusted domain list anymore. */
 	smb_domainex_free(dxi);
 
diff --git a/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_init.c b/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_init.c
index cdb6478..d31fdd5 100644
--- a/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_init.c
+++ b/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_init.c
@@ -20,7 +20,7 @@
  */
 /*
  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2014 Nexenta Systems, Inc.  All rights reserved.
+ * Copyright 2017 Nexenta Systems, Inc.  All rights reserved.
  */
 
 #include <sys/errno.h>
@@ -104,10 +104,6 @@
 		if (smb_config_get_secmode() != SMB_SECMODE_DOMAIN)
 			continue;
 
-		/* Avoid interfering with DC discovery. */
-		if (smb_ddiscover_wait() != 0)
-			continue;
-
 		if (!smb_domain_getinfo(&di))
 			continue;
 
diff --git a/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_util.c b/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_util.c
index b46cf99..956fbba 100644
--- a/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_util.c
+++ b/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_util.c
@@ -57,22 +57,16 @@
 mlsvc_join_noauth(smb_domainex_t *dxi,
 	char *machine_name, char *machine_pw);
 
-
+/*
+ * This is called by smbd_dc_update just after we've learned about a
+ * new domain controller.  Make sure we can authenticate with this DC.
+ */
 DWORD
 mlsvc_netlogon(char *server, char *domain)
 {
-	mlsvc_handle_t netr_handle;
 	DWORD status;
 
-	status = netr_open(server, domain, &netr_handle);
-	if (status != 0) {
-		syslog(LOG_NOTICE, "Failed to connect to %s "
-		    "for domain %s (%s)", server, domain,
-		    xlate_nt_status(status));
-		return (status);
-	}
-
-	status = netlogon_auth(server, &netr_handle, NETR_FLG_INIT);
+	status = smb_netlogon_check(server, domain);
 	if (status != NT_STATUS_SUCCESS) {
 		syslog(LOG_NOTICE, "Failed to establish NETLOGON "
 		    "credential chain with DC: %s (%s)", server,
@@ -81,7 +75,6 @@
 		    "domain controller does not match the local storage.");
 		syslog(LOG_NOTICE, "To correct this, use 'smbadm join'");
 	}
-	(void) netr_close(&netr_handle);
 
 	return (status);
 }
diff --git a/usr/src/lib/smbsrv/libmlsvc/common/netr_logon.c b/usr/src/lib/smbsrv/libmlsvc/common/netr_logon.c
index 6fdd3a9..0a60a1a 100644
--- a/usr/src/lib/smbsrv/libmlsvc/common/netr_logon.c
+++ b/usr/src/lib/smbsrv/libmlsvc/common/netr_logon.c
@@ -21,7 +21,7 @@
 
 /*
  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2015 Nexenta Systems, Inc.  All rights reserved.
+ * Copyright 2018 Nexenta Systems, Inc.  All rights reserved.
  */
 
 /*
@@ -46,9 +46,7 @@
 #include <smbsrv/smb_token.h>
 #include <mlsvc.h>
 
-#define	NETLOGON_ATTEMPTS	2
-
-static uint32_t netlogon_logon(smb_logon_t *, smb_token_t *);
+static uint32_t netlogon_logon(smb_logon_t *, smb_token_t *, smb_domainex_t *);
 static uint32_t netr_server_samlogon(mlsvc_handle_t *, netr_info_t *, char *,
     smb_logon_t *, smb_token_t *);
 static void netr_invalidate_chain(void);
@@ -176,12 +174,19 @@
  * If the user is successfully authenticated, we build an
  * access token and the status will be NT_STATUS_SUCCESS.
  * Otherwise, the token contents are invalid.
+ *
+ * This will retry a few times for errors indicating that the
+ * current DC might have gone off-line or become too busy etc.
+ * With such errors, smb_ddiscover_bad_dc is called and then
+ * the smb_domain_getinfo call here waits for new DC info.
  */
+int smb_netr_logon_retries = 3;
 void
 smb_logon_domain(smb_logon_t *user_info, smb_token_t *token)
 {
+	smb_domainex_t	di;
 	uint32_t	status;
-	int		i;
+	int		retries = smb_netr_logon_retries;
 
 	if (user_info->lg_secmode != SMB_SECMODE_DOMAIN)
 		return;
@@ -189,21 +194,28 @@
 	if (user_info->lg_domain_type == SMB_DOMAIN_LOCAL)
 		return;
 
-	for (i = 0; i < NETLOGON_ATTEMPTS; ++i) {
+	while (--retries > 0) {
+
+		if (!smb_domain_getinfo(&di)) {
+			syslog(LOG_ERR, "logon DC getinfo failed");
+			status = NT_STATUS_NO_LOGON_SERVERS;
+			goto out;
+		}
+
 		(void) mutex_lock(&netlogon_mutex);
 		while (netlogon_busy && !netlogon_abort)
 			(void) cond_wait(&netlogon_cv, &netlogon_mutex);
 
 		if (netlogon_abort) {
 			(void) mutex_unlock(&netlogon_mutex);
-			user_info->lg_status = NT_STATUS_REQUEST_ABORTED;
-			return;
+			status = NT_STATUS_REQUEST_ABORTED;
+			goto out;
 		}
 
 		netlogon_busy = B_TRUE;
 		(void) mutex_unlock(&netlogon_mutex);
 
-		status = netlogon_logon(user_info, token);
+		status = netlogon_logon(user_info, token, &di);
 
 		(void) mutex_lock(&netlogon_mutex);
 		netlogon_busy = B_FALSE;
@@ -212,71 +224,160 @@
 		(void) cond_signal(&netlogon_cv);
 		(void) mutex_unlock(&netlogon_mutex);
 
-		if (status != NT_STATUS_CANT_ACCESS_DOMAIN_INFO)
+		switch (status) {
+		case NT_STATUS_BAD_NETWORK_PATH:
+		case NT_STATUS_BAD_NETWORK_NAME:
+		case RPC_NT_SERVER_TOO_BUSY:
+			/*
+			 * May retry with a new DC, or if we're
+			 * out of retries, will return...
+			 */
+			status = NT_STATUS_NO_LOGON_SERVERS;
 			break;
+		default:
+			goto out;
+		}
 	}
 
+out:
 	if (status != NT_STATUS_SUCCESS)
 		syslog(LOG_INFO, "logon[%s\\%s]: %s", user_info->lg_e_domain,
 		    user_info->lg_e_username, xlate_nt_status(status));
-
 	user_info->lg_status = status;
 }
 
+/*
+ * Run a netr_server_samlogon call, dealing with the possible need to
+ * re-establish the NetLogon credential chain.  If that fails, return
+ * NT_STATUS_DOMAIN_TRUST_INCONSISTENT indicating the machine account
+ * needs it's password reset (or whatever).  Other errors are from the
+ * netr_server_samlogon() call including the many possibilities listed
+ * above that function.
+ */
 static uint32_t
-netlogon_logon(smb_logon_t *user_info, smb_token_t *token)
+netlogon_logon(smb_logon_t *user_info, smb_token_t *token, smb_domainex_t *di)
 {
-	char resource_domain[SMB_PI_MAX_DOMAIN];
 	char server[MAXHOSTNAMELEN];
 	mlsvc_handle_t netr_handle;
-	smb_domainex_t di;
 	uint32_t status;
-	int retries = 0;
+	boolean_t did_reauth = B_FALSE;
 
-	(void) smb_getdomainname(resource_domain, SMB_PI_MAX_DOMAIN);
-
-	/* Avoid interfering with DC discovery. */
-	if (smb_ddiscover_wait() != 0 ||
-	    !smb_domain_getinfo(&di)) {
-		netr_invalidate_chain();
-		return (NT_STATUS_CANT_ACCESS_DOMAIN_INFO);
+	/*
+	 * This netr_open call does the work to connect to the DC,
+	 * get the IPC share, open the named pipe, RPC bind, etc.
+	 */
+	status = netr_open(di->d_dci.dc_name, di->d_primary.di_nbname,
+	    &netr_handle);
+	if (status != 0) {
+		syslog(LOG_ERR, "netlogon remote open failed (%s)",
+		    xlate_nt_status(status));
+		return (status);
 	}
 
-	do {
-		if (netr_open(di.d_dci.dc_name, di.d_primary.di_nbname,
-		    &netr_handle) != 0)
-			return (NT_STATUS_OPEN_FAILED);
+	if (di->d_dci.dc_name[0] != '\0' &&
+	    (*netr_global_info.server != '\0')) {
+		(void) snprintf(server, sizeof (server),
+		    "\\\\%s", di->d_dci.dc_name);
+		if (strncasecmp(netr_global_info.server,
+		    server, strlen(server)) != 0)
+			netr_invalidate_chain();
+	}
 
-		if (di.d_dci.dc_name[0] != '\0' &&
-		    (*netr_global_info.server != '\0')) {
-			(void) snprintf(server, sizeof (server),
-			    "\\\\%s", di.d_dci.dc_name);
-			if (strncasecmp(netr_global_info.server,
-			    server, strlen(server)) != 0)
-				netr_invalidate_chain();
+reauth:
+	if ((netr_global_info.flags & NETR_FLG_VALID) == 0 ||
+	    !smb_match_netlogon_seqnum()) {
+		/*
+		 * This does netr_server_req_challenge() and
+		 * netr_server_authenticate2(), updating the
+		 * current netlogon sequence number.
+		 */
+		status = netlogon_auth(di->d_dci.dc_name, &netr_handle,
+		    NETR_FLG_NULL);
+
+		if (status != 0) {
+			syslog(LOG_ERR, "netlogon remote auth failed (%s)",
+			    xlate_nt_status(status));
+			(void) netr_close(&netr_handle);
+			return (NT_STATUS_DOMAIN_TRUST_INCONSISTENT);
 		}
 
-		if ((netr_global_info.flags & NETR_FLG_VALID) == 0 ||
-		    !smb_match_netlogon_seqnum()) {
-			status = netlogon_auth(di.d_dci.dc_name, &netr_handle,
-			    NETR_FLG_NULL);
+		netr_global_info.flags |= NETR_FLG_VALID;
+	}
 
-			if (status != 0) {
-				(void) netr_close(&netr_handle);
-				return (NT_STATUS_LOGON_FAILURE);
-			}
+	status = netr_server_samlogon(&netr_handle,
+	    &netr_global_info, di->d_dci.dc_name, user_info, token);
 
+	if (status == NT_STATUS_INSUFFICIENT_LOGON_INFO) {
+		if (!did_reauth) {
+			/* Call netlogon_auth() again, just once. */
+			did_reauth = B_TRUE;
+			goto reauth;
+		}
+		status = NT_STATUS_DOMAIN_TRUST_INCONSISTENT;
+	}
+
+	(void) netr_close(&netr_handle);
+
+	return (status);
+}
+
+/*
+ * Helper for mlsvc_netlogon
+ *
+ * Call netlogon_auth with appropriate locks etc.
+ * Serialize like smb_logon_domain does for
+ * netlogon_logon / netlogon_auth
+ */
+uint32_t
+smb_netlogon_check(char *server, char *domain)
+{
+	mlsvc_handle_t netr_handle;
+	uint32_t	status;
+
+	(void) mutex_lock(&netlogon_mutex);
+	while (netlogon_busy)
+		(void) cond_wait(&netlogon_cv, &netlogon_mutex);
+
+	netlogon_busy = B_TRUE;
+	(void) mutex_unlock(&netlogon_mutex);
+
+	/*
+	 * This section like netlogon_logon(), but only does
+	 * one pass and no netr_server_samlogon call.
+	 */
+
+	status = netr_open(server, domain,
+	    &netr_handle);
+	if (status != 0) {
+		syslog(LOG_ERR, "netlogon remote open failed (%s)",
+		    xlate_nt_status(status));
+		goto unlock_out;
+	}
+
+	if ((netr_global_info.flags & NETR_FLG_VALID) == 0 ||
+	    !smb_match_netlogon_seqnum()) {
+		/*
+		 * This does netr_server_req_challenge() and
+		 * netr_server_authenticate2(), updating the
+		 * current netlogon sequence number.
+		 */
+		status = netlogon_auth(server, &netr_handle,
+		    NETR_FLG_NULL);
+		if (status != 0) {
+			syslog(LOG_ERR, "netlogon remote auth failed (%s)",
+			    xlate_nt_status(status));
+		} else {
 			netr_global_info.flags |= NETR_FLG_VALID;
 		}
+	}
 
-		status = netr_server_samlogon(&netr_handle,
-		    &netr_global_info, di.d_dci.dc_name, user_info, token);
+	(void) netr_close(&netr_handle);
 
-		(void) netr_close(&netr_handle);
-	} while (status == NT_STATUS_INSUFFICIENT_LOGON_INFO && retries++ < 3);
-
-	if (retries >= 3)
-		status = NT_STATUS_LOGON_FAILURE;
+unlock_out:
+	(void) mutex_lock(&netlogon_mutex);
+	netlogon_busy = B_FALSE;
+	(void) cond_signal(&netlogon_cv);
+	(void) mutex_unlock(&netlogon_mutex);
 
 	return (status);
 }
diff --git a/usr/src/lib/smbsrv/libmlsvc/common/smb_logon.c b/usr/src/lib/smbsrv/libmlsvc/common/smb_logon.c
index 2379684..4ed50f2 100644
--- a/usr/src/lib/smbsrv/libmlsvc/common/smb_logon.c
+++ b/usr/src/lib/smbsrv/libmlsvc/common/smb_logon.c
@@ -20,8 +20,8 @@
  */
 /*
  * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2015 Nexenta Systems, Inc.  All rights reserved.
  * Copyright (c) 2016 by Delphix. All rights reserved.
+ * Copyright 2017 Nexenta Systems, Inc.  All rights reserved.
  */
 
 #include <unistd.h>
@@ -417,6 +417,7 @@
  * attempt to authenticate the user.
  *
  * On success, a pointer to a new access token is returned.
+ * On failure, NULL return and status in user_info->lg_status
  */
 smb_token_t *
 smb_logon(smb_logon_t *user_info)
@@ -433,7 +434,6 @@
 	int			i;
 
 	user_info->lg_secmode = smb_config_get_secmode();
-	user_info->lg_status = NT_STATUS_NO_SUCH_USER;
 
 	if (smb_domain_lookup_name(user_info->lg_e_domain, &domain))
 		user_info->lg_domain_type = domain.di_type;
@@ -446,6 +446,12 @@
 		return (NULL);
 	}
 
+	/*
+	 * If any logonop function takes significant action
+	 * (logon or authoratative failure) it will change
+	 * this status field to something else.
+	 */
+	user_info->lg_status = NT_STATUS_NO_SUCH_USER;
 	for (i = 0; i < n_op; ++i) {
 		(*ops[i])(user_info, token);
 
@@ -455,10 +461,25 @@
 
 	if (user_info->lg_status == NT_STATUS_SUCCESS) {
 		if (smb_token_setup_common(token))
-			return (token);
+			return (token); /* success */
+		/*
+		 * (else) smb_token_setup_common failed, which usually
+		 * means smb_token_sids2ids() failed to map some SIDs to
+		 * Unix IDs.  This indicates an idmap config problem.
+		 */
+		user_info->lg_status = NT_STATUS_INTERNAL_ERROR;
 	}
 
 	smb_token_destroy(token);
+
+	/*
+	 * Any unknown user or bad password should result in
+	 * NT_STATUS_LOGON_FAILURE (so we don't give hints).
+	 */
+	if (user_info->lg_status == NT_STATUS_NO_SUCH_USER ||
+	    user_info->lg_status == NT_STATUS_WRONG_PASSWORD)
+		user_info->lg_status = NT_STATUS_LOGON_FAILURE;
+
 	return (NULL);
 }
 
diff --git a/usr/src/lib/smbsrv/libmlsvc/common/smbrdr_glue.c b/usr/src/lib/smbsrv/libmlsvc/common/smbrdr_glue.c
index 724c509..7a467f3 100644
--- a/usr/src/lib/smbsrv/libmlsvc/common/smbrdr_glue.c
+++ b/usr/src/lib/smbsrv/libmlsvc/common/smbrdr_glue.c
@@ -21,7 +21,7 @@
 
 /*
  * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2015 Nexenta Systems, Inc.  All rights reserved.
+ * Copyright 2017 Nexenta Systems, Inc.  All rights reserved.
  */
 
 /*
@@ -158,7 +158,14 @@
 		goto errout;
 	}
 	if ((err = smb_ctx_get_ssn(ctx)) != 0) {
-		err = NT_STATUS_NETWORK_ACCESS_DENIED;
+		switch (err) {
+		case EAUTH:
+			err = NT_STATUS_NETWORK_ACCESS_DENIED;
+			break;
+		default:
+			err = NT_STATUS_BAD_NETWORK_PATH;
+			break;
+		}
 		goto errout;
 	}
 	if ((err = smb_ctx_get_tree(ctx)) != 0) {
diff --git a/usr/src/lib/smbsrv/libsmb/common/libsmb.h b/usr/src/lib/smbsrv/libsmb/common/libsmb.h
index 82d6bc1..db6f6c6 100644
--- a/usr/src/lib/smbsrv/libsmb/common/libsmb.h
+++ b/usr/src/lib/smbsrv/libsmb/common/libsmb.h
@@ -347,10 +347,10 @@
  * 0x0004 The name is a W2K Domain name (a DNS name).
  */
 #define	SMBAUTH_NAME_TYPE_LIST_END		0x0000
-#define	SMBAUTH_NAME_TYPE_SERVER_NETBIOS 	0x0001
-#define	SMBAUTH_NAME_TYPE_DOMAIN_NETBIOS 	0x0002
+#define	SMBAUTH_NAME_TYPE_SERVER_NETBIOS	0x0001
+#define	SMBAUTH_NAME_TYPE_DOMAIN_NETBIOS	0x0002
 #define	SMBAUTH_NAME_TYPE_SERVER_DNS		0x0003
-#define	SMBAUTH_NAME_TYPE_DOMAIN_DNS 		0x0004
+#define	SMBAUTH_NAME_TYPE_DOMAIN_DNS		0x0004
 
 /*
  * smb_auth_name_entry_t
@@ -662,6 +662,7 @@
 void smb_domain_set_trust_info(char *, char *, char *,
     uint32_t, uint32_t, uint32_t, smb_domain_t *);
 void smb_domain_current_dc(smb_dcinfo_t *);
+void smb_domain_bad_dc(void);
 
 typedef struct smb_gsid {
 	smb_sid_t *gs_sid;
diff --git a/usr/src/lib/smbsrv/libsmb/common/mapfile-vers b/usr/src/lib/smbsrv/libsmb/common/mapfile-vers
index 2af8c75..aaa9d36 100644
--- a/usr/src/lib/smbsrv/libsmb/common/mapfile-vers
+++ b/usr/src/lib/smbsrv/libsmb/common/mapfile-vers
@@ -129,6 +129,7 @@
 	smb_ctxbuf_printf;
 	smb_dlclose;
 	smb_dlopen;
+	smb_domain_bad_dc;
 	smb_domain_current_dc;
 	smb_domain_end_update;
 	smb_domain_fini;
diff --git a/usr/src/lib/smbsrv/libsmb/common/smb_domain.c b/usr/src/lib/smbsrv/libsmb/common/smb_domain.c
index 69769af..d920fcc 100644
--- a/usr/src/lib/smbsrv/libsmb/common/smb_domain.c
+++ b/usr/src/lib/smbsrv/libsmb/common/smb_domain.c
@@ -22,7 +22,7 @@
  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
  *
- * Copyright 2014 Nexenta Systems, Inc.  All rights reserved.
+ * Copyright 2017 Nexenta Systems, Inc.  All rights reserved.
  */
 
 /*
@@ -37,6 +37,7 @@
 #include <stdio.h>
 #include <strings.h>
 #include <string.h>
+#include <syslog.h>
 #include <unistd.h>
 #include <stdlib.h>
 #include <synch.h>
@@ -70,10 +71,13 @@
 typedef struct smb_domain_cache {
 	list_t		dc_cache;
 	rwlock_t	dc_cache_lck;
-	mutex_t		dc_mtx;
-	cond_t		dc_cv;
 	uint32_t	dc_state;
 	uint32_t	dc_nops;
+	mutex_t		dc_mtx;
+	cond_t		dc_cv;
+	/* domain controller information */
+	cond_t		dc_dci_cv;
+	boolean_t	dc_dci_valid;
 	smb_dcinfo_t	dc_dci;
 } smb_domain_cache_t;
 
@@ -90,7 +94,7 @@
 static void smb_dcache_unlock(void);
 static void smb_dcache_remove(smb_domain_t *);
 static uint32_t smb_dcache_add(smb_domain_t *);
-static boolean_t smb_dcache_getdc(smb_dcinfo_t *);
+static boolean_t smb_dcache_getdc(smb_dcinfo_t *, boolean_t);
 static void smb_dcache_setdc(const smb_dcinfo_t *);
 static boolean_t smb_dcache_wait(void);
 static uint32_t smb_dcache_updating(void);
@@ -113,6 +117,7 @@
 	if ((rc = smb_domain_add_local()) != 0)
 		return (rc);
 
+	bzero(&di, sizeof (di));
 	smb_domain_set_basic_info(NT_BUILTIN_DOMAIN_SIDSTR, "BUILTIN", "", &di);
 	(void) smb_domain_add(SMB_DOMAIN_BUILTIN, &di);
 
@@ -289,6 +294,8 @@
 /*
  * Returns primary domain information plus the name of
  * the selected domain controller.
+ *
+ * Returns TRUE on success.
  */
 boolean_t
 smb_domain_getinfo(smb_domainex_t *dxi)
@@ -297,10 +304,27 @@
 
 	/* Note: this waits for the dcache lock. */
 	rv = smb_domain_lookup_type(SMB_DOMAIN_PRIMARY, &dxi->d_primary);
-	if (rv)
-		rv = smb_dcache_getdc(&dxi->d_dci);
+	if (!rv) {
+		syslog(LOG_ERR, "smb_domain_getinfo: no primary domain");
+		return (B_FALSE);
+	}
 
-	return (rv);
+	/*
+	 * The 2nd arg TRUE means this will wait for DC info.
+	 *
+	 * Note that we do NOT hold the dcache rwlock here
+	 * (not even as reader) because we already have what we
+	 * need from the dcache (our primary domain) and we don't
+	 * want to interfere with the DC locator which will take
+	 * the dcache lock as writer to update the domain info.
+	 */
+	rv = smb_dcache_getdc(&dxi->d_dci, B_TRUE);
+	if (!rv) {
+		syslog(LOG_ERR, "smb_domain_getinfo: no DC info");
+		return (B_FALSE);
+	}
+
+	return (B_TRUE);
 }
 
 /*
@@ -310,7 +334,7 @@
 void
 smb_domain_current_dc(smb_dcinfo_t *dci)
 {
-	(void) smb_dcache_getdc(dci);
+	(void) smb_dcache_getdc(dci, B_FALSE);
 }
 
 /*
@@ -334,6 +358,18 @@
 }
 
 /*
+ * Mark the current domain controller (DC) info invalid
+ * until the DC locator calls smb_domain_update().
+ */
+void
+smb_domain_bad_dc(void)
+{
+	(void) mutex_lock(&smb_dcache.dc_mtx);
+	smb_dcache.dc_dci_valid = B_FALSE;
+	(void) mutex_unlock(&smb_dcache.dc_mtx);
+}
+
+/*
  * Updates the cache with given information for the primary
  * domain, possible trusted domains and the selected domain
  * controller.
@@ -484,6 +520,7 @@
 	if (di == NULL || forest == NULL || guid == NULL)
 		return;
 
+	/* Caller zeros out *di before this. */
 	smb_domain_set_basic_info(sid, nb_domain, fq_domain, di);
 	(void) strlcpy(di->di_u.di_dns.ddi_forest, forest, MAXHOSTNAMELEN);
 	(void) strlcpy(di->di_u.di_dns.ddi_guid, guid,
@@ -500,6 +537,7 @@
 	if (di == NULL)
 		return;
 
+	/* Caller zeros out *di before this. */
 	di->di_type = SMB_DOMAIN_TRUSTED;
 	ti = &di->di_u.di_trust;
 	smb_domain_set_basic_info(sid, nb_domain, fq_domain, di);
@@ -540,6 +578,7 @@
 		return (SMB_DOMAIN_NOMACHINE_SID);
 	}
 
+	bzero(&di, sizeof (di));
 	*fq_name = '\0';
 	(void) smb_getfqhostname(fq_name, MAXHOSTNAMELEN);
 	smb_domain_set_basic_info(lsidstr, hostname, fq_name, &di);
@@ -572,6 +611,7 @@
 	if ((rc != SMBD_SMF_OK) || (*nb_name == '\0'))
 		return (SMB_DOMAIN_NODOMAIN_NAME);
 
+	bzero(&di, sizeof (di));
 	(void) smb_getfqdomainname(fq_name, MAXHOSTNAMELEN);
 	smb_domain_set_basic_info(sidstr, nb_name, fq_name, &di);
 	(void) smb_domain_add(SMB_DOMAIN_PRIMARY, &di);
@@ -596,6 +636,7 @@
 
 	smb_dcache.dc_nops = 0;
 	bzero(&smb_dcache.dc_dci, sizeof (smb_dcache.dc_dci));
+	smb_dcache.dc_dci_valid = B_FALSE;
 	smb_dcache.dc_state = SMB_DCACHE_STATE_READY;
 	(void) mutex_unlock(&smb_dcache.dc_mtx);
 }
@@ -652,6 +693,7 @@
 	switch (smb_dcache.dc_state) {
 	case SMB_DCACHE_STATE_NONE:
 	case SMB_DCACHE_STATE_DESTROYING:
+	default:
 		(void) mutex_unlock(&smb_dcache.dc_mtx);
 		return (SMB_DOMAIN_INTERNAL_ERR);
 
@@ -668,7 +710,7 @@
 		}
 		/* FALLTHROUGH */
 
-	default:
+	case SMB_DCACHE_STATE_READY:
 		smb_dcache.dc_nops++;
 		break;
 	}
@@ -733,19 +775,40 @@
 {
 	(void) mutex_lock(&smb_dcache.dc_mtx);
 	smb_dcache.dc_dci = *dci; /* struct assignment! */
+	smb_dcache.dc_dci_valid = B_TRUE;
+	(void) cond_broadcast(&smb_dcache.dc_dci_cv);
 	(void) mutex_unlock(&smb_dcache.dc_mtx);
 }
 
 /*
- * Return B_TRUE if we have DC information.
+ * Get information about our domain controller.  If the wait arg
+ * is true, wait for the DC locator to finish before copying.
+ * Returns TRUE on success (have DC info).
  */
 static boolean_t
-smb_dcache_getdc(smb_dcinfo_t *dci)
+smb_dcache_getdc(smb_dcinfo_t *dci, boolean_t wait)
 {
+	timestruc_t to;
+	boolean_t rv;
+	int err;
+
+	to.tv_sec = time(NULL) + SMB_DCACHE_UPDATE_WAIT;
+	to.tv_nsec = 0;
+
 	(void) mutex_lock(&smb_dcache.dc_mtx);
+
+	while (wait && !smb_dcache.dc_dci_valid) {
+		err = cond_timedwait(&smb_dcache.dc_dci_cv,
+		    &smb_dcache.dc_mtx, &to);
+		if (err == ETIME)
+			break;
+	}
 	*dci = smb_dcache.dc_dci; /* struct assignment! */
+	rv = smb_dcache.dc_dci_valid;
+
 	(void) mutex_unlock(&smb_dcache.dc_mtx);
-	return (dci->dc_name[0] != '\0');
+
+	return (rv);
 }
 
 /*
@@ -759,10 +822,12 @@
 	timestruc_t to;
 	int err;
 
-	to.tv_sec = SMB_DCACHE_UPDATE_WAIT;
+	assert(MUTEX_HELD(&smb_dcache.dc_mtx));
+
+	to.tv_sec = time(NULL) + SMB_DCACHE_UPDATE_WAIT;
 	to.tv_nsec = 0;
 	while (smb_dcache.dc_state == SMB_DCACHE_STATE_UPDATING) {
-		err = cond_reltimedwait(&smb_dcache.dc_cv,
+		err = cond_timedwait(&smb_dcache.dc_cv,
 		    &smb_dcache.dc_mtx, &to);
 		if (err == ETIME)
 			break;