auth-module-open-directory [plain text]
Index: samba/source/Makefile.in
===================================================================
--- samba/source/Makefile.in.orig
+++ samba/source/Makefile.in
@@ -455,6 +455,7 @@ AUTH_OBJ = auth/auth.o @AUTH_STATIC@ aut
$(PLAINTEXT_AUTH_OBJ) $(SLCACHE_OBJ) $(DCUTIL_OBJ)
ODSAM_PDB_OBJ = lib/opendirectory.o lib/opendirectory_sam.o passdb/pdb_odsam.o
+ODSAM_AUTH_OBJ = lib/opendirectory.o lib/opendirectory_sam.o auth/auth_odsam.o
MANGLE_OBJ = smbd/mangle.o smbd/mangle_hash.o smbd/mangle_map.o smbd/mangle_hash2.o
@@ -1325,6 +1326,13 @@ bin/sam.@SHLIBEXT@: proto_exists $(AUTH_
@echo "Building plugin $@"
@$(SHLD_MODULE) $(AUTH_SAM_OBJ)
+# Note: This module should not be built statically, due to the
+# external dependencies.
+bin/auth_odsam.@SHLIBEXT@: $(ODSAM_AUTH_OBJ)
+ @echo "Building plugin $@"
+ @$(SHLD_MODULE) $(ODSAM_AUTH_OBJ) \
+ -framework DirectoryService
+
bin/ldapsam.@SHLIBEXT@: passdb/pdb_ldap.o passdb/pdb_nds.o
@echo "Building plugin $@"
@$(SHLD_MODULE) passdb/pdb_ldap.o passdb/pdb_nds.o $(LDAP_LIBS)
Index: samba/source/configure.in
===================================================================
--- samba/source/configure.in.orig
+++ samba/source/configure.in
@@ -6247,6 +6247,7 @@ SMB_MODULE(auth_server, \$(AUTH_SERVER_O
SMB_MODULE(auth_domain, \$(AUTH_DOMAIN_OBJ), "bin/domain.$SHLIBEXT", AUTH)
SMB_MODULE(auth_builtin, \$(AUTH_BUILTIN_OBJ), "bin/builtin.$SHLIBEXT", AUTH)
SMB_MODULE(auth_script, \$(AUTH_SCRIPT_OBJ), "bin/script.$SHLIBEXT", AUTH)
+SMB_MODULE(auth_ods, \$(ODSAM_AUTH_OBJ), "bin/auth_odsam.$SHLIBEXT", AUTH)
SMB_SUBSYSTEM(AUTH,auth/auth.o)
SMB_MODULE(vfs_default, \$(VFS_DEFAULT_OBJ), "bin/default.$SHLIBEXT", VFS)
Index: samba/source/auth/auth_odsam.c
===================================================================
--- /dev/null
+++ samba/source/auth/auth_odsam.c
@@ -0,0 +1,1014 @@
+/*
+ Unix SMB/CIFS implementation.
+ Password and authentication handling
+ Copyright (C) Andrew Tridgell 1992-2000
+ Copyright (C) Luke Kenneth Casson Leighton 1996-2000
+ Copyright (C) Andrew Bartlett 2001
+
+ Copyright (C) 2003-2007 Apple Inc. All Rights Reserved.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include "includes.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_AUTH
+
+#undef u32
+#include "opendirectory.h"
+
+#define MODULE_NAME "odsam"
+
+static enum ds_trace_level ds_trace = DS_TRACE_ERRORS;
+static int module_debug;
+
+static tDirNodeReference getusernode(struct opendirectory_session *session,
+ const char *userName)
+{
+ tDirStatus status = eDSNoErr;
+ UInt32 returnCount = 0;
+ tDataBufferPtr dataBuffer = NULL;
+ tDataListPtr searchNodeName = NULL;
+ tDirNodeReference userNodeRef = 0;
+ tDataListPtr userNodePath = NULL;
+ char userNodePathStr[256] = {0};
+ char recUserName[128] = {0};
+ tDataListPtr recName = NULL;
+ tDataListPtr recType = NULL;
+ tDataListPtr attrType = NULL;
+
+ tAttributeListRef attributeListRef = 0;
+ tRecordEntryPtr outRecordEntryPtr = NULL;
+ tAttributeEntryPtr attributeInfo = NULL;
+ tAttributeValueListRef attributeValueListRef = 0;
+ tAttributeValueEntryPtr attrValue = NULL;
+ UInt32 i = 0;
+
+ dataBuffer = dsDataBufferAllocate(session->ref, DEFAULT_DS_BUFFER_SIZE);
+ if (dataBuffer == NULL) {
+ goto cleanup;
+ }
+
+ status = opendirectory_searchnode(session);
+ LOG_DS_ERROR(ds_trace, status, "opendirectory_searchnode");
+ if (status != eDSNoErr) {
+ goto cleanup;
+ }
+
+ recName = dsBuildListFromStrings(session->ref, userName, NULL);
+ recType = dsBuildListFromStrings(session->ref, kDSStdRecordTypeUsers, NULL);
+ attrType = dsBuildListFromStrings(session->ref, kDSNAttrMetaNodeLocation,
+ kDSNAttrRecordName, NULL);
+
+ status = dsGetRecordList(session->search, dataBuffer, recName, eDSiExact,
+ recType, attrType, 0, &returnCount, NULL);
+ LOG_DS_ERROR(ds_trace, status, "dsGetRecordList");
+ if (status != eDSNoErr) {
+ goto cleanup;
+ }
+
+ status = dsGetRecordEntry(session->search, dataBuffer, 1,
+ &attributeListRef, &outRecordEntryPtr);
+ LOG_DS_ERROR(ds_trace, status, "dsGetRecordEntry");
+ if (status != eDSNoErr) {
+ goto cleanup;
+ }
+
+ for (i = 1 ; i <= outRecordEntryPtr->fRecordAttributeCount; i++) {
+ status = dsGetAttributeEntry(session->search, dataBuffer,
+ attributeListRef, i, &attributeValueListRef,
+ &attributeInfo);
+ LOG_DS_ERROR(ds_trace, status, "dsGetAttributeEntry");
+
+ status = dsGetAttributeValue(session->search, dataBuffer, 1,
+ attributeValueListRef, &attrValue);
+ LOG_DS_ERROR(ds_trace, status, "dsGetAttributeValue");
+
+ if (status == eDSNoErr) {
+ if (strncmp(attributeInfo->fAttributeSignature.fBufferData,
+ kDSNAttrMetaNodeLocation,
+ strlen(kDSNAttrMetaNodeLocation)) == 0) {
+ SMB_ASSERT(attrValue->fAttributeValueData.fBufferSize <
+ sizeof(userNodePathStr));
+ strncpy(userNodePathStr,
+ attrValue->fAttributeValueData.fBufferData,
+ attrValue->fAttributeValueData.fBufferSize);
+ } else if (strncmp(attributeInfo->fAttributeSignature.fBufferData,
+ kDSNAttrRecordName, strlen(kDSNAttrRecordName)) == 0) {
+ SMB_ASSERT(attrValue->fAttributeValueData.fBufferSize <
+ sizeof(recUserName));
+ strncpy(recUserName,
+ attrValue->fAttributeValueData.fBufferData,
+ attrValue->fAttributeValueData.fBufferSize);
+ }
+ }
+
+ if (attrValue != NULL) {
+ dsDeallocAttributeValueEntry(session->ref, attrValue);
+ attrValue = NULL;
+ }
+
+ if (attributeValueListRef != 0) {
+ dsCloseAttributeValueList(attributeValueListRef);
+ attributeValueListRef = 0;
+ }
+
+ if (attributeInfo != NULL) {
+ dsDeallocAttributeEntry(session->ref, attributeInfo);
+ attributeInfo = NULL;
+ }
+ }
+
+ if (outRecordEntryPtr != NULL) {
+ dsDeallocRecordEntry(session->ref, outRecordEntryPtr);
+ outRecordEntryPtr = NULL;
+ }
+
+ if (strlen(userNodePathStr) != 0 && strlen(recUserName) != 0) {
+ userNodePath = dsBuildFromPath(session->ref, userNodePathStr, "/");
+
+ status = dsOpenDirNode(session->ref, userNodePath, &userNodeRef);
+ LOG_DS_ERROR(ds_trace, status, "dsOpenDirNode");
+
+ opendirectory_free_list(session, userNodePath);
+ }
+
+cleanup:
+ DS_CLOSE_NODE(session->search);
+
+ opendirectory_free_buffer(session, dataBuffer);
+ opendirectory_free_list(session, searchNodeName);
+ opendirectory_free_list(session, recName);
+ opendirectory_free_list(session, recType);
+ opendirectory_free_list(session, attrType);
+
+ return userNodeRef;
+}
+
+/* FIXME: this partial mapping should be completed and hoisted into library
+ * code -- jpeach
+ */
+static NTSTATUS map_dserr_to_nterr(tDirStatus dirStatus)
+{
+ switch (dirStatus) {
+ case (eDSAuthFailed):
+ case (eDSAuthBadPassword):
+ return NT_STATUS_WRONG_PASSWORD;
+ case (eDSAuthAccountInactive):
+ return NT_STATUS_ACCOUNT_DISABLED;
+ case (eDSAuthNewPasswordRequired):
+ case (eDSAuthPasswordExpired):
+ return NT_STATUS_PASSWORD_MUST_CHANGE;
+ default:
+ return NT_STATUS_WRONG_PASSWORD;
+ }
+}
+
+static tDirStatus opendirectory_auth_user(
+ struct opendirectory_session *session,
+ tDirNodeReference userNode,
+ const char* user,
+ u_int8_t *challenge,
+ u_int8_t *password,
+ const char *inAuthMethod)
+{
+ tDirStatus status = eDSAuthServerError;
+ size_t curr = 0;
+ UInt32 len = 0;
+ tDataBufferPtr authBuff = NULL;
+ tDataBufferPtr stepBuff = NULL;
+ tDataNodePtr authType = NULL;
+
+ authBuff = dsDataBufferAllocate( session->ref, 2048 );
+ if ( authBuff == NULL ) {
+ DEBUG(module_debug, ("*** dsDataBufferAllocate failed\n" ));
+ return eMemoryAllocError;
+ }
+
+ stepBuff = dsDataBufferAllocate( session->ref, 2048 );
+ if ( stepBuff == NULL ) {
+ dsDataBufferDeAllocate( session->ref, authBuff );
+ DEBUG(module_debug, ("*** dsDataBufferAllocate failed\n" ));
+ return eMemoryAllocError;
+ }
+
+ authType = dsDataNodeAllocateString( session->ref, inAuthMethod);
+ if ( authType != NULL ) {
+ // User Name
+ len = (UInt32)strlen( user );
+ memcpy( &(authBuff->fBufferData[ curr ]), &len, sizeof(len) );
+ curr += sizeof( len );
+ memcpy( &(authBuff->fBufferData[ curr ]), user, len );
+ curr += len;
+ // C8
+ len = 8;
+ memcpy( &(authBuff->fBufferData[ curr ]), &len, sizeof(len) );
+ curr += sizeof (len );
+ memcpy( &(authBuff->fBufferData[ curr ]), challenge, len );
+ curr += len;
+ // P24
+ len = 24;
+ memcpy( &(authBuff->fBufferData[ curr ]), &len, sizeof(len) );
+ curr += sizeof (len );
+ memcpy( &(authBuff->fBufferData[ curr ]), password, len );
+ curr += len;
+
+ if (curr > UINT32_MAX)
+ smb_panic("Looks like fBufferLength overflowed\n");
+ authBuff->fBufferLength = (UInt32)curr;
+
+ status = dsDoDirNodeAuth( userNode, authType, True,
+ authBuff, stepBuff, NULL );
+ LOG_DS_ERROR(ds_trace, status, "dsDoNodeAuth");
+ DEBUG(module_debug, ("User \"%s\" %s ""with \"%s\"\n", user,
+ status == eDSNoErr ? "authenticated successfully"
+ : "failed to authenticate",
+ inAuthMethod));
+ }
+
+ opendirectory_free_buffer(session, stepBuff);
+ opendirectory_free_buffer(session, authBuff);
+ opendirectory_free_node(session, authType);
+
+ return status;
+}
+
+static tDirStatus opendirectory_ntlmv2_auth_user(
+ struct opendirectory_session *session,
+ tDirNodeReference userNode,
+ const char* user,
+ const char* domain,
+ const DATA_BLOB *sec_blob,
+ const DATA_BLOB *ntv2_response,
+ DATA_BLOB *user_sess_key)
+{
+ static const char const method[] =
+ "dsAuthMethodStandard:dsAuthNodeNTLMv2";
+
+ tDirStatus status = eDSAuthServerError;
+ size_t curr = 0;
+ UInt32 len = 0;
+ tDataBufferPtr authBuff = NULL;
+ tDataBufferPtr stepBuff = NULL;
+ tDataNodePtr authType = NULL;
+
+/*
+
+
+ The auth method constant is: dsAuthMethodStandard:dsAuthNodeNTLMv2
+ The format for data in the step buffer is:
+ 4 byte len + directory-services name
+ 4 byte len + server challenge
+ 4 byte len + client "blob" - 16 bytes of client digest + the blob data
+ 4 byte len + user name used in the digest (usually the same as item #1 in the buffer)
+ 4 byte len + domain
+
+
+*/
+
+ authBuff = dsDataBufferAllocate( session->ref, 2048 );
+ if ( authBuff == NULL ) {
+ DEBUG(module_debug, ("*** dsDataBufferAllocate failed\n" ));
+ return eMemoryAllocError;
+ }
+
+ stepBuff = dsDataBufferAllocate( session->ref, 2048 );
+ if ( stepBuff == NULL ) {
+ dsDataBufferDeAllocate( session->ref, authBuff );
+ DEBUG(module_debug, ("*** dsDataBufferAllocate failed\n" ));
+ return eMemoryAllocError;
+ }
+
+ authType = dsDataNodeAllocateString( session->ref, method );
+ if ( authType != NULL ) {
+ // directory-services name
+ len = (UInt32)strlen( user );
+ memcpy( &(authBuff->fBufferData[ curr ]), &len, sizeof(len) );
+ curr += sizeof( len );
+ memcpy( &(authBuff->fBufferData[ curr ]), user, len );
+ curr += len;
+ // server challenge
+ len = 8;
+ memcpy( &(authBuff->fBufferData[ curr ]), &len, sizeof(len) );
+ curr += sizeof (len );
+ memcpy( &(authBuff->fBufferData[ curr ]), sec_blob->data, len );
+ curr += len;
+ // client "blob" - 16 bytes of client digest + the blob_data
+ len = ntv2_response->length;
+ memcpy( &(authBuff->fBufferData[ curr ]), &len, sizeof(len) );
+ curr += sizeof (len );
+ memcpy( &(authBuff->fBufferData[ curr ]), ntv2_response->data, len );
+ curr += len;
+ // user name used in the digest (usually the same as item #1 in the buffer)
+ len = (UInt32)strlen( user );
+ memcpy( &(authBuff->fBufferData[ curr ]), &len, sizeof(len) );
+ curr += sizeof( len );
+ memcpy( &(authBuff->fBufferData[ curr ]), user, len );
+ curr += len;
+ // domain
+ len = (UInt32)strlen( domain );
+ memcpy( &(authBuff->fBufferData[ curr ]), &len, sizeof(len) );
+ curr += sizeof( len );
+ memcpy( &(authBuff->fBufferData[ curr ]), domain, len );
+ curr += len;
+
+ if (curr > UINT32_MAX)
+ smb_panic("Looks like fBufferLength overflowed\n");
+ authBuff->fBufferLength = (UInt32)curr;
+
+ status = dsDoDirNodeAuth( userNode, authType, True,
+ authBuff, stepBuff, NULL );
+ LOG_DS_ERROR(ds_trace, status, "dsDoNodeAuth");
+ DEBUG(module_debug, ("User \"%s\" %s ""with \"%s\"\n", user,
+ status == eDSNoErr ? "authenticated successfully"
+ : "failed to authenticate",
+ method));
+ }
+
+ opendirectory_free_buffer(session, stepBuff);
+ opendirectory_free_buffer(session, authBuff);
+ opendirectory_free_node(session, authType);
+ return status;
+}
+
+/****************************************************************************
+core of smb password checking routine.
+****************************************************************************/
+static tDirStatus opendirectory_smb_pwd_check_ntlmv1(
+ struct opendirectory_session *session,
+ tDirNodeReference userNode,
+ const char *user,
+ const char *inAuthMethod,
+ const DATA_BLOB *nt_response,
+ const DATA_BLOB *sec_blob,
+ DATA_BLOB *user_sess_key)
+{
+ tDirStatus status = eDSAuthFailed;
+ tDirStatus keyStatus = eDSAuthFailed;
+ u_int32_t key_length = 0;
+
+
+ if (sec_blob->length != 8) {
+ DEBUG(module_debug, ("incorrect challenge size (%ld)\n",
+ sec_blob->length));
+ return eDSAuthFailed;
+ }
+
+ if (nt_response->length != 24) {
+ DEBUG(module_debug, ("incorrect password length (%ld)\n",
+ nt_response->length));
+ return eDSAuthFailed;
+ }
+
+ if ((user_sess_key != NULL) &&
+ (strcmp(inAuthMethod,kDSStdAuthSMB_NT_Key) == 0) ) {
+ *user_sess_key = data_blob(NULL, 16);
+ become_root();
+ status = opendirectory_user_auth_and_session_key(session,
+ userNode, user, sec_blob->data,
+ nt_response->data, user_sess_key->data,
+ &key_length);
+ unbecome_root();
+
+ LOG_DS_ERROR(ds_trace, status,
+ "opendirectory_user_auth_and_session_key");
+ }
+
+ if (eDSAuthMethodNotSupported == status ||
+ eNotHandledByThisNode == status ||
+ (strcmp(inAuthMethod,kDSStdAuthSMB_LM_Key) == 0)) {
+
+ status = opendirectory_auth_user(session, userNode, user,
+ sec_blob->data, nt_response->data, inAuthMethod);
+ LOG_DS_ERROR(ds_trace, status,
+ "opendirectory_auth_user");
+
+ if (user_sess_key != NULL) {
+ *user_sess_key = data_blob(NULL, 16);
+ become_root();
+ keyStatus = opendirectory_user_session_key(session,
+ userNode, user, user_sess_key->data);
+ unbecome_root();
+ LOG_DS_ERROR(ds_trace, status,
+ "opendirectory_user_session_key");
+ }
+ }
+
+ return status;
+}
+
+/****************************************************************************
+ Core of smb password checking routine. (NTLMv2, LMv2)
+ Note: The same code works with both NTLMv2 and LMv2.
+****************************************************************************/
+
+static tDirStatus opendirectory_smb_pwd_check_ntlmv2(
+ struct opendirectory_session *session,
+ tDirNodeReference userNode,
+ const DATA_BLOB *ntv2_response,
+ const DATA_BLOB *sec_blob,
+ const char *user, const char *domain,
+ /* should the domain be transformed into upper case? */
+ BOOL upper_case_domain,
+ DATA_BLOB *user_sess_key)
+{
+ tDirStatus status = eDSAuthFailed;
+ tDirStatus keyStatus = eDSNoErr;
+
+ u_int32_t session_key_len = 0;
+
+ if (sec_blob->length != 8) {
+ DEBUG(module_debug, ("incorrect challenge size (%lu)\n",
+ (unsigned long)sec_blob->length));
+ return False;
+ }
+
+ if (ntv2_response->length < 24) {
+ /* We MUST have more than 16 bytes, or the stuff below will go
+ crazy. No known implementation sends less than the 24 bytes
+ for LMv2, let alone NTLMv2. */
+ DEBUG(module_debug, ("incorrect password length (%lu)\n",
+ (unsigned long)ntv2_response->length));
+ return False;
+ }
+
+ status = opendirectory_ntlmv2_auth_user(session, userNode, user,
+ domain, sec_blob, ntv2_response, user_sess_key );
+ LOG_DS_ERROR(ds_trace, status, "opendirectory_ntlmv2_auth_user");
+
+ if (eDSNoErr != status) {
+ return status;
+ }
+
+ if (user_sess_key != NULL) {
+ *user_sess_key = data_blob(NULL, 16);
+ become_root();
+ keyStatus = opendirectory_ntlmv2user_session_key(user,
+ ntv2_response->length, ntv2_response->data,
+ domain, &session_key_len, user_sess_key->data);
+ unbecome_root();
+
+ LOG_DS_ERROR_MSG(ds_trace, status,
+ "opendirectory_ntlmv2user_session_key",
+ ("%u byte session key (%s)\n",
+ session_key_len,
+ ntv2_response->length == 24 ? "LMv2" : "NTLMv2"));
+
+ }
+
+ return (status);
+
+}
+
+
+/**
+ * Check a challenge-response password against the value of the NT or
+ * LM password hash.
+ *
+ * @param mem_ctx talloc context
+ * @param challenge 8-byte challenge. If all zero, forces plaintext comparison
+ * @param nt_response 'unicode' NT response to the challenge, or unicode password
+ * @param lm_response ASCII or LANMAN response to the challenge, or password in DOS code page
+ * @param username internal Samba username, for log messages
+ * @param client_username username the client used
+ * @param client_domain domain name the client used (may be mapped)
+ * @param nt_pw MD4 unicode password from our passdb or similar
+ * @param lm_pw LANMAN ASCII password from our passdb or similar
+ * @param user_sess_key User session key
+ * @param lm_sess_key LM session key (first 8 bytes of the LM hash)
+ */
+
+static NTSTATUS
+opendirectory_opendirectory_ntlm_password_check(
+ struct opendirectory_session *session,
+ tDirNodeReference userNode,
+ TALLOC_CTX *mem_ctx,
+ const DATA_BLOB *challenge,
+ const DATA_BLOB *lm_response,
+ const DATA_BLOB *nt_response,
+ const DATA_BLOB *lm_interactive_pwd,
+ const DATA_BLOB *nt_interactive_pwd,
+ const char *username,
+ const char *client_username,
+ const char *client_domain,
+ DATA_BLOB *user_sess_key,
+ DATA_BLOB *lm_sess_key)
+{
+ tDirStatus dirStatus = eDSAuthFailed;
+
+ if (nt_response->length > 24) {
+ /* We have the NT MD4 hash challenge available - see if we can
+ use it
+ */
+ DEBUG(module_debug,("Checking NTLMv2 password with domain [%s]\n",
+ client_domain));
+ dirStatus = opendirectory_smb_pwd_check_ntlmv2(session,
+ userNode, nt_response,
+ challenge,
+ client_username,
+ client_domain,
+ False,
+ user_sess_key);
+ LOG_DS_ERROR(ds_trace, dirStatus,
+ "opendirectory_smb_pwd_check_ntlmv2");
+
+ if (dirStatus == eDSNoErr) {
+
+#if DEBUG_LMv2
+ DEBUG(module_debug,("Checking LMv2 password with domain [%s]\n",
+ client_domain));
+ dirStatus = opendirectory_smb_pwd_check_ntlmv2(session,
+ userNode, lm_response,
+ challenge,
+ client_username,
+ client_domain,
+ False,
+ user_sess_key);
+ LOG_DS_ERROR(ds_trace, dirStatus,
+ "opendirectory_smb_pwd_check_ntlmv2");
+#endif
+ return NT_STATUS_OK;
+ }
+
+ DEBUG(module_debug,("Checking NTLMv2 password with uppercase domain [%s]\n",
+ client_domain));
+
+ dirStatus = opendirectory_smb_pwd_check_ntlmv2(session,
+ userNode, nt_response,
+ challenge,
+ client_username,
+ client_domain,
+ True,
+ user_sess_key);
+ LOG_DS_ERROR(ds_trace, dirStatus,
+ "opendirectory_smb_pwd_check_ntlmv2");
+
+ if (dirStatus == eDSNoErr) {
+ return NT_STATUS_OK;
+ }
+
+ DEBUG(module_debug,("Checking NTLMv2 password without a domain\n"));
+ dirStatus = opendirectory_smb_pwd_check_ntlmv2(session,
+ userNode, nt_response,
+ challenge,
+ client_username,
+ "",
+ False,
+ user_sess_key);
+ LOG_DS_ERROR(ds_trace, dirStatus,
+ "opendirectory_smb_pwd_check_ntlmv2");
+
+ if (dirStatus == eDSNoErr) {
+
+ return NT_STATUS_OK;
+ }
+
+ return map_dserr_to_nterr(dirStatus);
+ }
+
+ if (nt_response->length == 24) {
+
+ /* Why are we checking nt_interactive_pwd but not actually
+ * using it? -- jpeach
+ */
+ if (lp_ntlm_auth() ||
+ (nt_interactive_pwd && nt_interactive_pwd->length)) {
+ /* We have the NT MD4 hash challenge available - see
+ * if we can use it (ie. does it exist in the smbpasswd
+ * file).
+ */
+ DEBUG(module_debug,("Checking NT MD4 password\n"));
+ dirStatus =
+ opendirectory_smb_pwd_check_ntlmv1(session,
+ userNode, username,
+ kDSStdAuthSMB_NT_Key,
+ nt_response,
+ challenge,
+ user_sess_key);
+ LOG_DS_ERROR(ds_trace, dirStatus,
+ "opendirectory_smb_pwd_check_ntlmv1");
+
+ if (dirStatus == eDSNoErr) {
+ return NT_STATUS_OK;
+ }
+ DEBUG(module_debug,("NT MD4 password check failed for user %s\n",
+ username));
+ return map_dserr_to_nterr(dirStatus);
+ }
+
+ /* No return, because we might pick up LMv2 in the LM field. */
+ DEBUG(module_debug,("NTLMv1 passwords NOT PERMITTED for user %s\n",
+ username));
+
+ }
+
+
+ if (lm_response->length == 0) {
+ DEBUG(module_debug,("NEITHER LanMan nor NT password supplied for user %s\n",
+ username));
+ return NT_STATUS_ILL_FORMED_PASSWORD;
+ }
+
+ if (lm_response->length < 24) {
+ DEBUG(module_debug,("invalid LanMan password length (%lu) for user %s\n",
+ (unsigned long)nt_response->length, username));
+ return NT_STATUS_ILL_FORMED_PASSWORD;
+ }
+
+ if (lp_lanman_auth()) {
+ DEBUG(module_debug,("Checking LM password\n"));
+ dirStatus = opendirectory_smb_pwd_check_ntlmv1(session,
+ userNode, username,
+ kDSStdAuthSMB_LM_Key,
+ lm_response,
+ challenge,
+ NULL);
+ LOG_DS_ERROR(ds_trace, dirStatus,
+ "opendirectory_smb_pwd_check_ntlmv1");
+
+ if (dirStatus == eDSNoErr) {
+ return NT_STATUS_OK;
+ }
+
+ DEBUG(module_debug,("LM password check failed for user %s\n",username));
+ }
+
+ /* This is for 'LMv2' authentication. almost NTLMv2 but limited to
+ * 24 bytes - related to Win9X, legacy NAS pass-though authentication
+ */
+ DEBUG(module_debug,("Checking LMv2 password with domain %s\n", client_domain));
+ dirStatus = opendirectory_smb_pwd_check_ntlmv2(session, userNode,
+ lm_response,
+ challenge,
+ client_username,
+ client_domain,
+ False,
+ user_sess_key);
+ LOG_DS_ERROR(ds_trace, dirStatus,
+ "opendirectory_smb_pwd_check_ntlmv2");
+
+ if (dirStatus== eDSNoErr) {
+ return NT_STATUS_OK;
+ }
+
+ DEBUG(module_debug,("Checking LMv2 password with upper-cased domain %s\n",
+ client_domain));
+ dirStatus = opendirectory_smb_pwd_check_ntlmv2(session, userNode,
+ lm_response,
+ challenge,
+ client_username,
+ client_domain,
+ True,
+ user_sess_key);
+ LOG_DS_ERROR(ds_trace, dirStatus,
+ "opendirectory_smb_pwd_check_ntlmv2");
+
+ if (dirStatus== eDSNoErr) {
+ return NT_STATUS_OK;
+ }
+
+ DEBUG(module_debug,("Checking LMv2 password without a domain\n"));
+ dirStatus = opendirectory_smb_pwd_check_ntlmv2(session, userNode,
+ lm_response,
+ challenge,
+ client_username,
+ "",
+ False,
+ user_sess_key);
+ LOG_DS_ERROR(ds_trace, dirStatus,
+ "opendirectory_smb_pwd_check_ntlmv2");
+
+ if (dirStatus== eDSNoErr) {
+ return NT_STATUS_OK;
+ }
+
+ /* Apparently NT accepts NT responses in the LM field
+ - I think this is related to Win9X pass-though authentication
+ */
+ DEBUG(module_debug,("Checking NT MD4 password in LM field\n"));
+ if (lp_ntlm_auth()) {
+ dirStatus = opendirectory_smb_pwd_check_ntlmv1(session,
+ userNode, username,
+ kDSStdAuthSMB_NT_Key,
+ lm_response,
+ challenge,
+ user_sess_key);
+ LOG_DS_ERROR(ds_trace, dirStatus,
+ "opendirectory_smb_pwd_check_ntlmv1");
+
+ if (dirStatus== eDSNoErr) {
+ return NT_STATUS_OK;
+ }
+ DEBUG(module_debug,("LM password, NT MD4 password in LM field "
+ "and LMv2 failed for user %s\n",username));
+ }
+
+ DEBUG(module_debug,("LM password and LMv2 failed for user %s, "
+ "and NT MD4 password in LM field not permitted\n",username));
+ return NT_STATUS_WRONG_PASSWORD;
+}
+
+
+/****************************************************************************
+ Do a specific test for an smb password being correct, given a smb_password and
+ the lanman and NT responses.
+****************************************************************************/
+static NTSTATUS opendirectory_password_ok(
+ struct opendirectory_session *session,
+ tDirNodeReference userNode,
+ const struct auth_context *auth_context,
+ TALLOC_CTX *mem_ctx,
+ struct samu *sampass,
+ const auth_usersupplied_info *user_info,
+ DATA_BLOB *user_sess_key,
+ DATA_BLOB *lm_sess_key)
+{
+ uint16 acct_ctrl;
+ const char *username = pdb_get_username(sampass);
+
+ acct_ctrl = pdb_get_acct_ctrl(sampass);
+ if (acct_ctrl & ACB_PWNOTREQ) {
+ DEBUG(module_debug,("Account for user '%s' has no password "
+ "and null passwords %s allowed.\n",
+ lp_null_passwords() ? "are" : "are NOT",
+ username));
+ return lp_null_passwords() ? NT_STATUS_OK
+ : NT_STATUS_LOGON_FAILURE;
+ }
+
+ return opendirectory_opendirectory_ntlm_password_check(session,
+ userNode, mem_ctx, &auth_context->challenge,
+ &user_info->lm_resp, &user_info->nt_resp,
+ &user_info->lm_interactive_pwd,
+ &user_info->nt_interactive_pwd,
+ username,
+ user_info->smb_name,
+ user_info->client_domain,
+ user_sess_key, lm_sess_key);
+}
+
+/****************************************************************************
+ Do a specific test for a struct samu being vaild for this connection
+ (ie not disabled, expired and the like).
+****************************************************************************/
+static NTSTATUS opendirectory_account_ok(TALLOC_CTX *mem_ctx,
+ struct samu *sampass,
+ const auth_usersupplied_info *user_info)
+{
+ uint16 acct_ctrl = pdb_get_acct_ctrl(sampass);
+ char *workstation_list;
+ time_t kickoff_time;
+
+ DEBUG(module_debug,("Checking SMB password for user %s\n",
+ pdb_get_username(sampass)));
+
+ /* Quit if the account was disabled. */
+ if (acct_ctrl & ACB_DISABLED) {
+ DEBUG(module_debug,("Account for user '%s' was disabled.\n",
+ pdb_get_username(sampass)));
+ return NT_STATUS_ACCOUNT_DISABLED;
+ }
+
+ /* Test account expire time */
+
+ kickoff_time = pdb_get_kickoff_time(sampass);
+ if (kickoff_time != 0 && time(NULL) > kickoff_time) {
+ DEBUG(module_debug,("Account for user '%s' has expired.\n",
+ pdb_get_username(sampass)));
+ DEBUG(module_debug,("Account expired at '%ld' unix time.\n",
+ (long)kickoff_time));
+ return NT_STATUS_ACCOUNT_EXPIRED;
+ }
+
+ if (!(pdb_get_acct_ctrl(sampass) & ACB_PWNOEXP)) {
+ time_t must_change_time = pdb_get_pass_must_change_time(sampass);
+ time_t last_set_time = pdb_get_pass_last_set_time(sampass);
+
+ /* check for immediate expiry "must change at next logon" */
+ if (must_change_time == 0 && last_set_time != 0) {
+ DEBUG(module_debug,("Password for user '%s' must change!\n",
+ pdb_get_username(sampass)));
+ return NT_STATUS_PASSWORD_MUST_CHANGE;
+ }
+
+ /* check for expired password */
+ if (must_change_time < time(NULL) && must_change_time != 0) {
+ DEBUG(module_debug,("Password for user '%s' has expired!\n",
+ pdb_get_username(sampass)));
+ DEBUG(module_debug,("Password expired at '%s' (%ld) unix time.\n",
+ http_timestring(must_change_time),
+ (long)must_change_time));
+ return NT_STATUS_PASSWORD_EXPIRED;
+ }
+ }
+
+ /* Test workstation. Workstation list is comma separated. */
+
+ workstation_list = talloc_strdup(mem_ctx, pdb_get_workstations(sampass));
+
+ if (!workstation_list) return NT_STATUS_NO_MEMORY;
+
+ if (*workstation_list) {
+ BOOL invalid_ws = True;
+ const char *s = workstation_list;
+
+ fstring tok;
+
+ while (next_token(&s, tok, ",", sizeof(tok))) {
+ DEBUG(module_debug,("checking for workstation match %s and %s\n",
+ tok, user_info->wksta_name));
+ if(strequal(tok, user_info->wksta_name)) {
+ invalid_ws = False;
+ break;
+ }
+ }
+
+ if (invalid_ws)
+ return NT_STATUS_INVALID_WORKSTATION;
+ }
+
+ if (acct_ctrl & ACB_DOMTRUST) {
+ DEBUG(module_debug,("Domain trust account %s denied by server\n",
+ pdb_get_username(sampass)));
+ return NT_STATUS_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT;
+ }
+
+ if (acct_ctrl & ACB_SVRTRUST) {
+ DEBUG(module_debug,("Server trust account %s denied by server\n",
+ pdb_get_username(sampass)));
+ return NT_STATUS_NOLOGON_SERVER_TRUST_ACCOUNT;
+ }
+
+ if (acct_ctrl & ACB_WSTRUST) {
+ DEBUG(module_debug,("Wksta trust account %s denied by server\n",
+ pdb_get_username(sampass)));
+ return NT_STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT;
+ }
+
+ return NT_STATUS_OK;
+}
+
+
+/****************************************************************************
+check if a username/password is OK assuming the password is a 24 byte
+SMB hash supplied in the user_info structure
+return an NT_STATUS constant.
+****************************************************************************/
+
+static NTSTATUS check_opendirectory_security(
+ const struct auth_context *auth_context,
+ void *my_private_data,
+ TALLOC_CTX *mem_ctx,
+ const auth_usersupplied_info *user_info,
+ auth_serversupplied_info **server_info)
+{
+ struct samu *sampass=NULL;
+ BOOL ret = False;
+ NTSTATUS nt_status = NT_STATUS_OK;
+ DATA_BLOB user_sess_key = data_blob(NULL, 0);
+ DATA_BLOB lm_sess_key = data_blob(NULL, 0);
+ tDirStatus dirStatus = eDSNoErr;
+ tDirNodeReference userNodeRef = 0;
+ struct opendirectory_session session;
+
+ if (!user_info || !auth_context) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ sampass = samu_new(NULL);
+ if (!sampass) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* get the account information */
+
+ if (user_info->internal_username &&
+ strlen(user_info->internal_username)) {
+ become_root();
+ ret = pdb_getsampwnam(sampass, user_info->internal_username);
+ unbecome_root();
+ }
+
+ if (ret == False) {
+ DEBUG(module_debug, ("user '%s' is not in the password database\n",
+ user_info->internal_username));
+ TALLOC_FREE(sampass);
+ return NT_STATUS_NO_SUCH_USER;
+ }
+
+ /* FIXME: this would be a good place to check the SMB service ACL.
+ * Unfortunately, we don't ever get here if the client authenticated
+ * with Kerberos.
+ */
+
+ nt_status = opendirectory_account_ok(mem_ctx, sampass, user_info);
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ TALLOC_FREE(sampass);
+ return nt_status;
+ }
+
+ dirStatus = opendirectory_connect(&session);
+ LOG_DS_ERROR(ds_trace, dirStatus, "dsOpenDirService");
+ if (dirStatus != eDSNoErr) {
+ TALLOC_FREE(sampass);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ userNodeRef = getusernode(&session, pdb_get_username(sampass));
+ if (userNodeRef == 0) {
+ opendirectory_disconnect(&session);
+ TALLOC_FREE(sampass);
+ return NT_STATUS_NO_SUCH_USER;
+ }
+
+ nt_status = opendirectory_password_ok(&session, userNodeRef,
+ auth_context, mem_ctx, sampass, user_info,
+ &user_sess_key, &lm_sess_key);
+ DS_CLOSE_NODE( userNodeRef );
+ opendirectory_disconnect(&session);
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ TALLOC_FREE(sampass);
+ return nt_status;
+ }
+
+ nt_status = make_server_info_sam(server_info, sampass);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ TALLOC_FREE(sampass);
+ DEBUG(module_debug, ("make_server_info_sam() failed with '%s'\n",
+ nt_errstr(nt_status)));
+ return nt_status;
+ }
+
+ /* NOTE: There is no need to deal specially with
+ * (*server_info)->sam_account since server_info_dtor() cleans it
+ * up on a talloc_free().
+ */
+
+ (*server_info)->user_session_key = user_sess_key;
+ (*server_info)->lm_session_key = lm_sess_key;
+
+ return nt_status;
+}
+
+/* module initialisation */
+static NTSTATUS auth_init_opendirectory(struct auth_context *auth_context,
+ const char *param, auth_methods **auth_method)
+{
+ if (!make_auth_methods(auth_context, auth_method)) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ (*auth_method)->auth = check_opendirectory_security;
+ (*auth_method)->name = MODULE_NAME;
+ return NT_STATUS_OK;
+}
+
+/* module initialisation */
+static NTSTATUS auth_init_od_historic(struct auth_context *auth_context,
+ const char *param, auth_methods **auth_method)
+{
+ if (!make_auth_methods(auth_context, auth_method)) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ (*auth_method)->auth = check_opendirectory_security;
+ (*auth_method)->name = "opendirectory";
+ return NT_STATUS_OK;
+}
+
+NTSTATUS auth_ods_init(void)
+{
+ /* Use "odsam:traceall = yes" to turn on OD query tracing. */
+ if (lp_parm_bool(GLOBAL_SECTION_SNUM,
+ MODULE_NAME, "traceall", False)) {
+ ds_trace = DS_TRACE_ALL;
+ }
+
+ module_debug = lp_parm_int(GLOBAL_SECTION_SNUM,
+ MODULE_NAME, "msglevel", 100);
+
+ /* Register with the current module name. */
+ smb_register_auth(AUTH_INTERFACE_VERSION, MODULE_NAME,
+ auth_init_opendirectory);
+
+ /* Register the historic name for compatibility with 10.4 configs. */
+ smb_register_auth(AUTH_INTERFACE_VERSION, "opendirectory",
+ auth_init_od_historic);
+
+ return NT_STATUS_OK;
+}