open-directory-library-code [plain text]
Index: samba/source/include/opendirectory.h
===================================================================
--- /dev/null
+++ samba/source/include/opendirectory.h
@@ -0,0 +1,261 @@
+/*
+ * opendirectory.h
+ *
+ * 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 <sys/types.h>
+
+#include <DirectoryService/DirServices.h>
+
+/* tdb.h #defines this, which collides with OSByteOrder.h in PPC builds. */
+#undef u32
+
+#include <DirectoryService/DirServicesConst.h>
+#include <DirectoryService/DirServicesUtils.h>
+
+/* Definitions */
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define DEFAULT_DS_BUFFER_SIZE (1024 * 10)
+#define SMALL_DS_BUFFER_SIZE (512)
+
+#define DS_CLOSE_NODE(noderef) \
+ do { \
+ if ((noderef) != 0) { dsCloseDirNode(noderef); } \
+ (noderef) = 0; \
+ } while (0);
+
+#define DS_DEFAULT_BUFFER(dref) \
+ dsDataBufferAllocate((dref), DEFAULT_DS_BUFFER_SIZE)
+
+/* Directory Services node references are not good across fork(2). We need to
+ * associate the reference with the process it was created in so we can figure
+ * out whether we might need to recreate it.
+ */
+struct opendirectory_session
+{
+ tDirReference ref; /* Service reference. */
+ tDirNodeReference search; /* Search node reference. */
+ pid_t pid; /* PID of owning process. */
+
+ const char ** local_path_cache;
+ CFMutableDictionaryRef domain_sid_cache;
+};
+
+/* Session management APIs. */
+
+tDirStatus opendirectory_connect(struct opendirectory_session *session);
+tDirStatus opendirectory_reconnect(struct opendirectory_session *session);
+void opendirectory_disconnect(struct opendirectory_session *session);
+tDirStatus opendirectory_searchnode(struct opendirectory_session *session);
+
+tDirStatus opendirectory_open_node(struct opendirectory_session *session,
+ const char* nodeName,
+ tDirNodeReference *nodeReference);
+
+/* Memory management APIs. */
+
+void opendirectory_free_list(struct opendirectory_session *session,
+ tDataListPtr list);
+void opendirectory_free_node(struct opendirectory_session *session,
+ tDataNodePtr node);
+void opendirectory_free_buffer(struct opendirectory_session *session,
+ tDataBufferPtr buffer);
+
+/* Search APIs. */
+
+tDirStatus opendirectory_sam_searchattr(
+ struct opendirectory_session *session,
+ CFMutableArrayRef *records,
+ const char *type,
+ const char *attr,
+ const char *value);
+tDirStatus opendirectory_sam_searchname(
+ struct opendirectory_session *session,
+ CFMutableArrayRef *records,
+ const char *type,
+ const char *name);
+
+tDirStatus opendirectory_insert_search_results(tDirNodeReference node,
+ CFMutableArrayRef recordsArray,
+ const unsigned long recordCount,
+ tDataBufferPtr dataBuffer);
+
+/* High-level SAM policy APIs. */
+
+BOOL opendirectory_find_usersid_from_record(
+ struct opendirectory_session *session,
+ CFDictionaryRef sam_record,
+ DOM_SID *sid);
+
+BOOL opendirectory_find_groupsid_from_record(
+ struct opendirectory_session *session,
+ CFDictionaryRef sam_record,
+ DOM_SID *sid);
+
+CFDictionaryRef opendirectory_find_record_from_usersid(
+ struct opendirectory_session *session,
+ const DOM_SID *user_sid);
+
+CFDictionaryRef opendirectory_find_record_from_groupsid(
+ struct opendirectory_session *session,
+ const DOM_SID *group_sid);
+
+/* Miscellaneous helper APIs. */
+
+tDataListPtr opendirectory_sam_attrlist(struct opendirectory_session *session);
+const char ** opendirectory_local_paths(struct opendirectory_session *session);
+
+char *opendirectory_get_record_attribute(void *talloc_ctx,
+ CFDictionaryRef record,
+ const char *attribute);
+char * opendirectory_talloc_cfstr(void *talloc_ctx, CFStringRef cfstring);
+
+/* Authentication APIs. */
+
+tDirStatus opendirectory_authenticate_node(
+ struct opendirectory_session *session,
+ tDirNodeReference nodeRef);
+
+tDirStatus opendirectory_user_auth_and_session_key(
+ struct opendirectory_session *session,
+ tDirNodeReference inUserNodeRef,
+ const char *account_name,
+ u_int8_t *challenge,
+ u_int8_t *client_response,
+ u_int8_t *session_key,
+ u_int32_t *key_length);
+
+tDirStatus opendirectory_user_session_key(
+ struct opendirectory_session *session,
+ tDirNodeReference inUserNodeRef,
+ const char *account_name,
+ u_int8_t *session_key);
+
+tDirStatus opendirectory_ntlmv2user_session_key(const char *account_name,
+ u_int32_t ntv2response_len,
+ u_int8_t* ntv2response,
+ const char* domain,
+ u_int32_t *session_key_len,
+ u_int8_t *session_key);
+
+tDirStatus opendirectory_cred_session_key(const DOM_CHAL *client_challenge,
+ const DOM_CHAL *server_challenge,
+ const char *machine_acct,
+ char *session_key);
+
+tDirStatus opendirectory_set_workstation_nthash(const char *account_name,
+ const char *nt_hash);
+
+tDirStatus opendirectory_lmchap2changepasswd(const char *account_name,
+ const char *passwordData,
+ const char *passwordHash,
+ u_int8_t passwordFormat,
+ const char *slot_id);
+
+#ifndef kDS1AttrSMBRID
+#define kDS1AttrSMBRID "dsAttrTypeNative:smb_rid"
+#define kDS1AttrSMBGroupRID "dsAttrTypeNative:smb_group_rid"
+#define kDS1AttrSMBPWDLastSet "dsAttrTypeNative:smb_pwd_last_set"
+#define kDS1AttrSMBLogonTime "dsAttrTypeNative:smb_logon_time"
+#define kDS1AttrSMBLogoffTime "dsAttrTypeNative:smb_logoff_time"
+#define kDS1AttrSMBKickoffTime "dsAttrTypeNative:smb_kickoff_time"
+#define kDS1AttrSMBHomeDrive "dsAttrTypeNative:smb_home_drive"
+#define kDS1AttrSMBHome "dsAttrTypeNative:smb_home"
+#define kDS1AttrSMBScriptPath "dsAttrTypeNative:smb_script_path"
+#define kDS1AttrSMBProfilePath "dsAttrTypeNative:smb_profile_path"
+#define kDS1AttrSMBUserWorkstations "dsAttrTypeNative:smb_user_workstations"
+#define kDS1AttrSMBAcctFlags "dsAttrTypeNative:smb_acctFlags"
+#endif
+
+#define kDS1AttrSMBLMPassword "dsAttrTypeNative:smb_lmPassword"
+#define kDS1AttrSMBNTPassword "dsAttrTypeNative:smb_ntPassword"
+#define kDS1AttrSMBPWDCanChange "dsAttrTypeNative:smb_pwd_can_change"
+#define kDS1AttrSMBPWDMustChange "dsAttrTypeNative:smb_pwd_must_change"
+
+#define kPlainTextPassword "plaintextpassword"
+
+/* Password Policy Attributes */
+#define kPWSIsDisabled "isDisabled"
+#define kPWSIsAdmin "isAdminUser"
+#define kPWSNewPasswordRequired "newPasswordRequired"
+#define kPWSIsUsingHistory "usingHistory"
+#define kPWSCanChangePassword "canModifyPasswordforSelf"
+#define kPWSExpiryDateEnabled "usingHardExpirationDate"
+#define kPWSRequiresAlpha "requiresAlpha"
+#define kPWSExpiryDate "expirationDateGMT"
+#define kPWSHardExpiryDate "hardExpireDateGMT"
+#define kPWSMaxMinChgPwd "maxMinutesUntilChangePassword"
+#define kPWSMaxMinActive "maxMinutesUntilDisabled"
+#define kPWSMaxMinInactive "maxMinutesOfNonUse"
+#define kPWSMaxFailedLogins "maxFailedLoginAttempts"
+#define kPWSMinChars "minChars"
+#define kPWSMaxChars "maxChars"
+#define kPWSPWDCannotBeName "passwordCannotBeName"
+
+#define kPWSPWDLastSetTime "passwordLastSetTime"
+#define kPWSLastLoginTime "lastLoginTime"
+#define kPWSLogOffTime "logOffTime"
+#define kPWSKickOffTime "kickOffTime"
+
+enum ds_trace_level
+{
+ DS_TRACE_ALL,
+ DS_TRACE_ERRORS
+};
+
+/* Squash a ds_trace_level to a DEBUG status, depending on whether the
+ * API result was an error or not.
+ */
+#define LOG_DS_ERRLEVEL(which, status) \
+ ( ((status) == eDSNoErr && (which) != DS_TRACE_ALL) ? 10 : 0 )
+
+/* We should never get this because we carefully obtain a new reference
+ * across fork(2). If this does happen, we need to know where.
+ */
+#define LOG_DS_CHECK_BADREF(status) \
+ { if ((status) == eDSInvalidReference) { log_stack_trace(); }}
+
+/* Log the results on an API call. */
+#define LOG_DS_ERROR(which, status, funcname) \
+ do { \
+ char * dserr = dsCopyDirStatusName(status); \
+ int level = LOG_DS_ERRLEVEL(which, status); \
+ DEBUG(level, ("%s gave %ld [%s]\n", \
+ funcname, (long)status, dserr ? dserr : "??")); \
+ SAFE_FREE(dserr); \
+ LOG_DS_CHECK_BADREF(status); \
+ } while(0)
+
+/* Log the results of an API call, and append an extra message. */
+#define LOG_DS_ERROR_MSG(which, status, funcname, msgfmt) \
+ do { \
+ char * dserr = dsCopyDirStatusName(status); \
+ int level = LOG_DS_ERRLEVEL(which, status); \
+ DEBUG(level, ("%s gave %ld [%s]: ", \
+ funcname, (long)status, dserr ? dserr : "??")); \
+ DEBUGADD(level, msgfmt); \
+ SAFE_FREE(dserr); \
+ LOG_DS_CHECK_BADREF(status); \
+ } while(0)
+
+#ifdef __cplusplus
+}
+#endif
Index: samba/source/lib/opendirectory.c
===================================================================
--- /dev/null
+++ samba/source/lib/opendirectory.c
@@ -0,0 +1,2827 @@
+/*
+ * opendirectory.c
+ *
+ * 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"
+#include "opendirectory.h"
+
+/* ======================================================================= */
+/* Miscellaneous APIs and utilities */
+/* ======================================================================= */
+
+static CFDictionaryRef sam_searchattr_first(
+ struct opendirectory_session *session,
+ const char *type,
+ const char *attr,
+ const char *value);
+static CFDictionaryRef sam_searchname_first(
+ struct opendirectory_session *session,
+ const char *type,
+ const char *name);
+
+/* Data free API. Most (all?) of the ds* API will safely ignore a NULL or zero
+ * argument. The only reason we need this is that tDataListPtr needs
+ * to be freed twice (!?!).
+ */
+ void opendirectory_free_list(struct opendirectory_session *session,
+ tDataListPtr list)
+{
+ if (list != NULL) {
+ dsDataListDeallocate(session->ref, list);
+ free(list);
+ }
+}
+
+ void opendirectory_free_node(struct opendirectory_session *session,
+ tDataNodePtr node)
+{
+ if (node != NULL) {
+ dsDataNodeDeAllocate(session->ref, node);
+ }
+}
+
+ void opendirectory_free_buffer(struct opendirectory_session *session,
+ tDataBufferPtr buffer)
+{
+ if (buffer != NULL) {
+ dsDataBufferDeAllocate(session->ref, buffer);
+ }
+}
+
+/* Connection / reconnection API. */
+
+ tDirStatus opendirectory_open_node(struct opendirectory_session *session,
+ const char* nodeName,
+ tDirNodeReference *nodeReference)
+{
+ tDirStatus status = eDSInvalidReference;
+ tDataListPtr node = NULL;
+
+ SMB_ASSERT(nodeReference != NULL);
+
+ node = dsBuildFromPath(session->ref, nodeName, "/");
+ if (node == NULL) {
+ return eDSAllocationFailed;
+ }
+
+ status = dsOpenDirNode(session->ref, node, nodeReference);
+ LOG_DS_ERROR(DS_TRACE_ERRORS, status, "dsOpenDirNode");
+
+ opendirectory_free_list(session, node);
+ return status;
+}
+
+ tDirStatus opendirectory_connect(struct opendirectory_session *session)
+{
+ tDirStatus dirStatus;
+
+ ZERO_STRUCTP(session);
+ session->pid = sys_getpid();
+ dirStatus = dsOpenDirService(&session->ref);
+ return dirStatus;
+}
+
+ tDirStatus opendirectory_reconnect(struct opendirectory_session *session)
+{
+ pid_t current = sys_getpid();
+
+ /* We need to reconnect if a session survived across a fork.
+ * NOTE: the search node is NOT preserved.
+ */
+ if (session->ref == 0 || current != session->pid) {
+ DS_CLOSE_NODE(session->search);
+ return opendirectory_connect(session);
+ }
+
+#if 0
+ /* We really should call dsVerifyDirRefNum here, but it's far to
+ * expensive to do on a regular basis.
+ */
+
+ if (dsVerifyDirRefNum(session->ref) != eDSNoErr) {
+ return opendirectory_connect(session);
+ }
+#endif
+
+ return eDSNoErr;
+}
+
+ void opendirectory_disconnect(struct opendirectory_session *session)
+{
+ DS_CLOSE_NODE(session->search);
+ dsCloseDirService(session->ref);
+ ZERO_STRUCTP(session);
+}
+
+/* ======================================================================= */
+/* Authentication APIs and utilities */
+/* ======================================================================= */
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_AUTH
+
+#define credentialfile "/var/db/samba/opendirectorysam"
+#define opendirectory_secret_sig 'odsa'
+
+typedef struct opendirectory_secret_header {
+ u_int32_t signature;
+ u_int32_t authenticator_len;
+ u_int32_t secret_len;
+ u_int32_t authenticatorid_len;
+} opendirectory_secret_header;
+
+#ifdef USES_KEYCHAIN
+/* Stop the Security framework defining fixed-sized types that we already
+ * define. Our definitions are incompatible since they are #defined rather
+ * that typedef'd.
+ */
+#define _UINT64
+#define _UINT32
+#define _UINT16
+#define _UINT8
+#include <CoreFoundation/CoreFoundation.h>
+#include <Security/Security.h>
+#include <SystemConfiguration/SystemConfiguration.h>
+//#include <CoreServices/CoreServices.h>
+
+#define KEYCHAIN_SERVICE "com.apple.samba"
+#define SAMBA_APP_ID CFSTR("com.apple.samba")
+
+static char * get_admin_account(void)
+{
+ CFPropertyListRef pref = NULL;
+ char * account = NULL;
+ int accountLength = 1024;
+
+ pref = CFPreferencesCopyAppValue (CFSTR("DomainAdmin"), SAMBA_APP_ID);
+ if (pref != 0) {
+ if (CFGetTypeID(pref) == CFStringGetTypeID()) {
+ account = calloc(1, accountLength);
+ if (!CFStringGetCString((CFStringRef)pref, account,
+ accountLength, kCFStringEncodingUTF8)) {
+ free(account);
+ account = NULL;
+ }
+ }
+ CFRelease(pref);
+ }
+
+ return account;
+}
+
+static void *get_password_from_keychain(char *account, int accountLength)
+{
+ OSStatus status ;
+ SecKeychainItemRef item;
+ void *passwordData = NULL;
+ UInt32 passwordLength = 0;
+ void *password = NULL;
+
+ // Set the domain to System (daemon)
+ status = SecKeychainSetPreferenceDomain(kSecPreferencesDomainSystem);
+ status = SecKeychainGetUserInteractionAllowed (False);
+
+ status = SecKeychainFindGenericPassword (
+ NULL, // default keychain
+ strlen(KEYCHAIN_SERVICE), // length of service name
+ KEYCHAIN_SERVICE, // service name
+ accountLength, // length of account name
+ account, // account name
+ &passwordLength, // length of password
+ &passwordData, // pointer to password data
+ &item // the item reference
+ );
+
+ if ((status == noErr) && (item != NULL) && passwordLength)
+ {
+ password = calloc(1, passwordLength + 1);
+ memcpy(password, (const void *)passwordData, passwordLength);
+ }
+ return password;
+}
+
+#endif /* USES_KEYCHAIN */
+
+static opendirectory_secret_header *
+get_opendirectory_secret_header(void *authenticator)
+{
+ opendirectory_secret_header *hdr;
+
+ if (authenticator) {
+ hdr = (opendirectory_secret_header *)authenticator;
+ if (hdr->signature == opendirectory_secret_sig) {
+ return hdr;
+ }
+ }
+ return NULL;
+}
+
+static void * get_opendirectory_authenticator(void)
+{
+ void *authenticator = NULL;
+ int authentriessize = 0;
+
+#ifdef USES_KEYCHAIN
+ opendirectory_secret_header *odhdr = NULL;
+ char *password = NULL;
+ char *account = NULL;
+
+ account = get_admin_account();
+ if (!account) {
+ return NULL;
+ }
+
+ password = get_password_from_keychain(account, strlen(account));
+ if (!password) {
+ free(account);
+ return NULL;
+ }
+
+ authentriessize = strlen(account) + strlen(password);
+ authenticator = calloc(1, sizeof(opendirectory_secret_header) +
+ authentriessize);
+ memcpy((uint8_t *)authenticator + sizeof(opendirectory_secret_header),
+ account, strlen(account));
+ memcpy((uint8_t *)authenticator + sizeof(opendirectory_secret_header)
+ + strlen(account),
+ password, strlen(password));
+ odhdr = (opendirectory_secret_header*)authenticator;
+ odhdr->authenticator_len = strlen(account);
+ odhdr->secret_len = strlen(password);
+ odhdr->signature = opendirectory_secret_sig;
+
+ free(password);
+ return authenticator;
+
+#else /* USES_KEYCHAIN */
+
+ int fd = -1;
+ ssize_t len;
+ opendirectory_secret_header hdr;
+
+ fd = open(credentialfile, O_RDONLY,0);
+ if (fd == -1) {
+ DEBUG(0, ("unable to open %s (%s)\n",
+ credentialfile, strerror(errno)));
+ return NULL;
+ }
+
+ len = read(fd, &hdr, sizeof(opendirectory_secret_header));
+ if (len != sizeof(opendirectory_secret_header)) {
+ goto cleanup;
+ }
+
+ if (hdr.signature != opendirectory_secret_sig) {
+ goto cleanup;
+ }
+
+ authentriessize = hdr.authenticator_len + hdr.secret_len;
+ authenticator = malloc(sizeof(opendirectory_secret_header) + authentriessize);
+ memset(authenticator, 0, sizeof(opendirectory_secret_header) + authentriessize);
+ memcpy(authenticator, &hdr, sizeof(opendirectory_secret_header));
+
+ len = read(fd, (uint8_t *)authenticator +
+ sizeof(opendirectory_secret_header), authentriessize);
+ if (len != authentriessize) {
+ goto cleanup;
+ }
+
+ return authenticator;
+
+cleanup:
+ if (authenticator) {
+ free(authenticator);
+ }
+
+ close(fd);
+ return NULL;
+
+#endif /* USES_KEYCHAIN */
+}
+
+
+static u_int32_t
+authenticator_accountlen(void *authenticator)
+{
+ opendirectory_secret_header *hdr = get_opendirectory_secret_header(authenticator);
+ u_int32_t len = 0;
+
+ if (hdr)
+ len = hdr->authenticator_len;
+
+ return len;
+}
+
+static void *
+authenticator_account(void *authenticator)
+{
+ opendirectory_secret_header *hdr = get_opendirectory_secret_header(authenticator);
+ void *result = NULL;
+
+ if (hdr)
+ result = (char *)authenticator + sizeof(opendirectory_secret_header);
+
+ return result;
+}
+
+static u_int32_t
+authenticator_secretlen(void *authenticator)
+{
+ opendirectory_secret_header *hdr = get_opendirectory_secret_header(authenticator);
+ u_int32_t len = 0;
+
+ if (hdr)
+ len = hdr->secret_len;
+
+ return len;
+
+}
+static void *
+authenticator_secret(void *authenticator)
+{
+ opendirectory_secret_header *hdr = get_opendirectory_secret_header(authenticator);
+ void *result = NULL;
+
+ if (hdr)
+ result = (char *)authenticator + sizeof(opendirectory_secret_header) + hdr->authenticator_len;
+
+ return result;
+}
+
+static void
+delete_opendirectory_authenticator(void*authenticator)
+{
+ opendirectory_secret_header *hdr = get_opendirectory_secret_header(authenticator);
+
+ if (hdr) {
+ bzero(authenticator, sizeof(opendirectory_secret_header) + hdr->authenticator_len + hdr->secret_len);
+ free(authenticator);
+ }
+}
+
+static tDirStatus get_node_ref_and_name(
+ struct opendirectory_session *session,
+ const char *name, const char *recordType,
+ tDirNodeReference *nodeRef, char *outRecordName)
+{
+ tDirStatus status = eDSNoErr;
+ unsigned long returnCount = 0;
+ tDataBufferPtr nodeBuffer = NULL;
+ tDataListPtr NodePath = NULL;
+ char NodePathStr[256] = {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;
+ long i = 0;
+
+ *nodeRef = 0;
+ *outRecordName = '\0';
+
+ nodeBuffer = DS_DEFAULT_BUFFER(session->ref);
+ if (nodeBuffer == NULL) {
+ goto cleanup;
+ }
+
+ status = opendirectory_searchnode(session);
+ if (status != eDSNoErr) {
+ goto cleanup;
+ }
+
+ recName = dsBuildListFromStrings(session->ref, name, NULL);
+ recType = dsBuildListFromStrings(session->ref, recordType, NULL);
+ attrType = dsBuildListFromStrings(session->ref,
+ kDSNAttrMetaNodeLocation, kDSNAttrRecordName, NULL);
+
+ status = dsGetRecordList(session->search, nodeBuffer, recName,
+ eDSiExact, recType, attrType, 0, &returnCount, NULL);
+ if (status != eDSNoErr) {
+ goto cleanup;
+ }
+
+ status = dsGetRecordEntry(session->search, nodeBuffer, 1,
+ &attributeListRef, &outRecordEntryPtr);
+ if (status != eDSNoErr) {
+ goto cleanup;
+ }
+
+ for (i = 1 ; i <= outRecordEntryPtr->fRecordAttributeCount; i++) {
+ status = dsGetAttributeEntry(session->search, nodeBuffer,
+ attributeListRef, i,
+ &attributeValueListRef, &attributeInfo);
+ status = dsGetAttributeValue(session->search, nodeBuffer, 1,
+ attributeValueListRef, &attrValue);
+ if (attributeValueListRef != 0) {
+ dsCloseAttributeValueList(attributeValueListRef);
+ attributeValueListRef = 0;
+ }
+
+ if (status != eDSNoErr) {
+ goto cleanup;
+ }
+
+ if (strncmp(attributeInfo->fAttributeSignature.fBufferData,
+ kDSNAttrMetaNodeLocation,
+ strlen(kDSNAttrMetaNodeLocation)) == 0) {
+ SMB_ASSERT(attrValue->fAttributeValueData.fBufferSize < sizeof(NodePathStr));
+ NodePathStr[sizeof(NodePathStr) -1] = '\0';
+ strncpy(NodePathStr,
+ attrValue->fAttributeValueData.fBufferData,
+ sizeof(NodePathStr) -1);
+ } else if (strncmp(attributeInfo->fAttributeSignature.fBufferData,
+ kDSNAttrRecordName, strlen(kDSNAttrRecordName)) == 0) {
+
+ /* XXX: We should be passing the record size. */
+ SMB_ASSERT(attrValue->fAttributeValueData.fBufferSize < 256);
+ outRecordName[255 - 1] = '\0';
+ strncpy(outRecordName,
+ attrValue->fAttributeValueData.fBufferData,
+ 256 - 1);
+ }
+
+ if (attrValue != NULL) {
+ dsDeallocAttributeValueEntry(session->ref, attrValue);
+ attrValue = NULL;
+ }
+ if (attributeInfo != NULL) {
+ dsDeallocAttributeEntry(session->ref, attributeInfo);
+ attributeInfo = NULL;
+ }
+ }
+
+ if (outRecordEntryPtr != NULL) {
+ dsDeallocRecordEntry(session->ref, outRecordEntryPtr);
+ outRecordEntryPtr = NULL;
+ }
+
+ if (strlen(NodePathStr) != 0 && strlen(outRecordName) != 0) {
+ NodePath = dsBuildFromPath(session->ref, NodePathStr, "/");
+ status = dsOpenDirNode(session->ref, NodePath, nodeRef);
+ opendirectory_free_list(session, NodePath);
+ }
+
+cleanup:
+
+ opendirectory_free_buffer(session, nodeBuffer);
+ opendirectory_free_list(session, recName);
+ opendirectory_free_list(session, recType);
+ opendirectory_free_list(session, attrType);
+
+ return status;
+}
+
+static void
+opendirectory_add_data_buffer_item(tDataBufferPtr dataBuffer,
+ u_int32_t len, const void *buffer)
+{
+
+ if (dataBuffer->fBufferLength + len + 4 > dataBuffer->fBufferSize) {
+ return;
+ }
+
+ memcpy(&(dataBuffer->fBufferData[dataBuffer->fBufferLength]), &len, 4);
+ dataBuffer->fBufferLength += 4;
+
+ if (len != 0) {
+ memcpy(&(dataBuffer->fBufferData[dataBuffer->fBufferLength]),
+ buffer, len);
+ dataBuffer->fBufferLength += len;
+ }
+}
+
+ tDirStatus opendirectory_cred_session_key(const DOM_CHAL *client_challenge,
+ const DOM_CHAL *server_challenge,
+ const char *machine_acct,
+ char *session_key)
+{
+ struct opendirectory_session session;
+ tDirStatus status = eDSNoErr;
+ unsigned long curr = 0;
+ unsigned long len = 0;
+ tDataBufferPtr authBuff = NULL;
+ tDataBufferPtr stepBuff = NULL;
+ tDataNodePtr authType = NULL;
+ tDataNodePtr recordType = NULL;
+ tDirNodeReference nodeRef = 0;
+ const char *targetaccount = NULL;
+ char recordName[256] = {0};
+
+ status = opendirectory_connect(&session);
+ if (status != eDSNoErr) {
+ return status;
+ }
+
+ status = get_node_ref_and_name(&session, machine_acct,
+ kDSStdRecordTypeComputers, &nodeRef, recordName);
+ if (status != eDSNoErr) {
+ goto cleanup;
+ }
+
+ status = opendirectory_authenticate_node(&session, nodeRef);
+ if (status != eDSNoErr) {
+ goto cleanup;
+ }
+
+ targetaccount = recordName;
+
+ authBuff = DS_DEFAULT_BUFFER(session.ref);
+ if (authBuff == NULL) {
+ goto cleanup;
+ }
+
+ authBuff->fBufferLength = 0;
+ stepBuff = DS_DEFAULT_BUFFER(session.ref);
+ if (stepBuff == NULL) {
+ goto cleanup;
+ }
+
+ authType = dsDataNodeAllocateString(session.ref,
+ kDSStdAuthSMBWorkstationCredentialSessionKey);
+ recordType = dsDataNodeAllocateString(session.ref,
+ kDSStdRecordTypeComputers);
+ if (authType == NULL || recordType == NULL) {
+ goto cleanup;
+ }
+
+ // Target account
+ len = strlen(targetaccount);
+ memcpy(&(authBuff->fBufferData[ curr ]), &len, 4);
+ curr += sizeof(long);
+ memcpy(&(authBuff->fBufferData[ curr ]), targetaccount, len);
+ curr += len;
+ // Client Challenge and Server Challenge
+ len = 16;
+ memcpy(&(authBuff->fBufferData[ curr ]), &len, 4);
+ curr += sizeof(long);
+ memcpy(&(authBuff->fBufferData[ curr ]), server_challenge->data, 8);
+ curr += 8;
+ memcpy(&(authBuff->fBufferData[ curr ]), client_challenge->data, 8);
+ curr += 8;
+
+ authBuff->fBufferLength = curr;
+ status = dsDoDirNodeAuthOnRecordType(nodeRef, authType, 1, authBuff,
+ stepBuff, NULL, recordType);
+
+ if (status == eDSNoErr) {
+ memcpy(&len, stepBuff->fBufferData, 4);
+ stepBuff->fBufferData[len+4] = '\0';
+ memcpy(session_key,stepBuff->fBufferData+4, 8);
+ }
+
+cleanup:
+
+ opendirectory_free_buffer(&session, stepBuff);
+ opendirectory_free_buffer(&session, authBuff);
+ opendirectory_free_node(&session, authType);
+ opendirectory_free_node(&session, recordType);
+
+ DS_CLOSE_NODE(nodeRef);
+ opendirectory_disconnect(&session);
+
+ return status;
+}
+
+ tDirStatus opendirectory_user_auth_and_session_key(
+ struct opendirectory_session *session,
+ tDirNodeReference inUserNodeRef,
+ const char *account_name,
+ u_int8_t *challenge,
+ u_int8_t *client_response,
+ u_int8_t *session_key,
+ u_int32_t *key_length)
+{
+ tDirStatus status = eDSNoErr;
+ u_int32_t len = 0;
+ tDataBufferPtr authBuff = NULL;
+ tDataBufferPtr stepBuff = NULL;
+ tDataNodePtr authType = NULL;
+ tDataNodePtr recordType = NULL;
+ void * authenticator = NULL;
+
+ authenticator = get_opendirectory_authenticator();
+
+#ifdef AUTH_NODE_BEFORE_AUTH_AND_SESS_KEY
+ status = opendirectory_authenticate_node(session->ref, inUserNodeRef);
+
+ if (status != eDSNoErr)
+ goto cleanup;
+#endif
+
+ authBuff = DS_DEFAULT_BUFFER(session->ref);
+ if (authBuff == NULL) {
+ goto cleanup;
+ }
+ authBuff->fBufferLength = 0;
+ stepBuff = DS_DEFAULT_BUFFER(session->ref);
+ if (stepBuff == NULL) {
+ goto cleanup;
+ }
+
+ /*
+ * New auth method:
+ * "dsAuthMethodStandard:dsAuthNTWithSessionKey"
+ *
+ * Buffer format:
+ * 4 byte len + user name/ID
+ * 4 byte len + challenge C8
+ * 4 byte len + response P24
+ * 4 byte len + authenticator name/ID
+ * 4 byte len + authenticator password
+ */
+
+ authType = dsDataNodeAllocateString(session->ref,
+ "dsAuthMethodStandard:dsAuthNTWithSessionKey");
+ recordType = dsDataNodeAllocateString(session->ref,
+ kDSStdRecordTypeUsers);
+ if (authType == NULL || recordType == NULL) {
+ goto cleanup;
+ }
+
+ // Target account
+ opendirectory_add_data_buffer_item(authBuff, strlen(account_name),
+ account_name);
+ opendirectory_add_data_buffer_item(authBuff, 8, challenge);
+ opendirectory_add_data_buffer_item(authBuff, 24, client_response);
+
+ if (authenticator != NULL) {
+ opendirectory_add_data_buffer_item(authBuff,
+ authenticator_accountlen(authenticator),
+ authenticator_account(authenticator));
+ opendirectory_add_data_buffer_item(authBuff,
+ authenticator_secretlen(authenticator),
+ authenticator_secret(authenticator));
+ }
+
+ status = dsDoDirNodeAuthOnRecordType(inUserNodeRef, authType, 1,
+ authBuff, stepBuff, NULL, recordType);
+ LOG_DS_ERROR(DS_TRACE_ERRORS, status, "dsDoDirNodeAuthOnRecordType");
+ if (status == eDSNoErr && stepBuff->fBufferLength >= 4) {
+ memcpy(&len, stepBuff->fBufferData, 4);
+ assert (len == 16);
+ *key_length = len;
+ stepBuff->fBufferData[len+4] = '\0';
+ memcpy(session_key,stepBuff->fBufferData+4, len);
+ } else {
+ *key_length = 0;
+ }
+
+cleanup:
+
+ opendirectory_free_buffer(session, stepBuff);
+ opendirectory_free_buffer(session, authBuff);
+ opendirectory_free_node(session, authType);
+ opendirectory_free_node(session, recordType);
+
+ return status;
+}
+
+ tDirStatus opendirectory_user_session_key(
+ struct opendirectory_session *session,
+ tDirNodeReference inUserNodeRef,
+ const char *account_name,
+ u_int8_t *session_key)
+{
+ tDirStatus status = eDSNoErr;
+ unsigned long len = 0;
+ tDataBufferPtr authBuff = NULL;
+ tDataBufferPtr stepBuff = NULL;
+ tDataNodePtr authType = NULL;
+ tDataNodePtr recordType = NULL;
+ tDirNodeReference nodeRef = inUserNodeRef;
+
+ status = opendirectory_authenticate_node(session, nodeRef);
+ if (status != eDSNoErr) {
+ goto cleanup;
+ }
+
+ authBuff = DS_DEFAULT_BUFFER(session->ref);
+ if (authBuff == NULL) {
+ goto cleanup;
+ }
+
+ authBuff->fBufferLength = 0;
+ stepBuff = DS_DEFAULT_BUFFER(session->ref);
+ if (stepBuff == NULL) {
+ goto cleanup;
+ }
+
+ authType = dsDataNodeAllocateString(session->ref,
+ kDSStdAuthSMB_NT_UserSessionKey);
+ recordType = dsDataNodeAllocateString(session->ref,
+ kDSStdRecordTypeUsers);
+ if (authType == NULL) {
+ goto cleanup;
+ }
+
+ // Target account
+ opendirectory_add_data_buffer_item(authBuff, strlen(account_name),
+ account_name);
+ /* null terminate ?? */
+ opendirectory_add_data_buffer_item(authBuff, 1, "");
+
+ status = dsDoDirNodeAuthOnRecordType(nodeRef, authType, 1, authBuff,
+ stepBuff, NULL, recordType);
+ if (status == eDSNoErr) {
+ memcpy(&len, stepBuff->fBufferData, 4);
+ stepBuff->fBufferData[len+4] = '\0';
+ memcpy(session_key, stepBuff->fBufferData + 4, len);
+ }
+
+cleanup:
+
+ opendirectory_free_buffer(session, stepBuff);
+ opendirectory_free_buffer(session, authBuff);
+ opendirectory_free_node(session, authType);
+ opendirectory_free_node(session, recordType);
+
+ return status;
+}
+
+ tDirStatus opendirectory_ntlmv2user_session_key(const char *account_name,
+ u_int32_t ntv2response_len,
+ u_int8_t *ntv2response,
+ const char *domain,
+ u_int32_t *session_key_len,
+ u_int8_t *session_key)
+{
+ struct opendirectory_session session;
+ tDirStatus status = eDSNoErr;
+ unsigned long len = 0;
+ tDataBufferPtr authBuff = NULL;
+ tDataBufferPtr stepBuff = NULL;
+ tDataNodePtr authType = NULL;
+ tDataNodePtr recordType = NULL;
+ tDirNodeReference nodeRef = 0;
+ char recordName[256] = {0};
+
+ status = opendirectory_connect(&session);
+ if (status != eDSNoErr) {
+ return status;
+ }
+
+ status = get_node_ref_and_name(&session, account_name,
+ kDSStdRecordTypeUsers, &nodeRef, recordName);
+ if (status != eDSNoErr) {
+ goto cleanup;
+ }
+
+ status = opendirectory_authenticate_node(&session, nodeRef);
+ if (status != eDSNoErr) {
+ goto cleanup;
+ }
+
+ authBuff = DS_DEFAULT_BUFFER(session.ref);
+ if (authBuff == NULL) {
+ goto cleanup;
+ }
+
+ authBuff->fBufferLength = 0;
+ stepBuff = DS_DEFAULT_BUFFER(session.ref);
+ if (stepBuff == NULL) {
+ goto cleanup;
+ }
+
+ authType = dsDataNodeAllocateString(session.ref,
+ /* "dsAuthMethodStandard:dsAuthNodeNTLMv2SessionKey" */
+ kDSStdAuthSMBNTv2UserSessionKey);
+ recordType = dsDataNodeAllocateString(session.ref,
+ kDSStdRecordTypeUsers);
+ if (authType == NULL || recordType == NULL) {
+ goto cleanup;
+ }
+
+ /*
+ * The buffer format is:
+ * 4 byte len + Directory Services user name
+ * 4 byte len + client blob
+ * 4 byte len + username
+ * 4 byte len + domain
+ */
+ // Target account
+ opendirectory_add_data_buffer_item(authBuff, strlen(recordName),
+ recordName);
+ opendirectory_add_data_buffer_item(authBuff, ntv2response_len,
+ ntv2response);
+ opendirectory_add_data_buffer_item(authBuff, strlen(recordName),
+ recordName);
+ opendirectory_add_data_buffer_item(authBuff, strlen(domain), domain);
+
+ status = dsDoDirNodeAuthOnRecordType(nodeRef, authType, 1, authBuff,
+ stepBuff, NULL, recordType);
+ LOG_DS_ERROR(DS_TRACE_ERRORS, status, "dsDoDirNodeAuthOnRecordType");
+ if (status == eDSNoErr) {
+ memcpy(&len, stepBuff->fBufferData, 4);
+ stepBuff->fBufferData[len+4] = '\0';
+ *session_key_len = len;
+ memcpy(session_key,stepBuff->fBufferData+4, len);
+ }
+
+cleanup:
+
+ opendirectory_free_buffer(&session, stepBuff);
+ opendirectory_free_buffer(&session, authBuff);
+ opendirectory_free_node(&session, authType);
+ opendirectory_free_node(&session, recordType);
+
+ DS_CLOSE_NODE(nodeRef);
+ opendirectory_disconnect(&session);
+ return status;
+}
+
+ tDirStatus opendirectory_set_workstation_nthash(const char *account_name,
+ const char *nt_hash)
+{
+ struct opendirectory_session session;
+ tDirStatus status = eDSNoErr;
+ tDataBufferPtr authBuff = NULL;
+ tDataBufferPtr stepBuff = NULL;
+ tDataNodePtr authType = NULL;
+ tDataNodePtr recordType = NULL;
+ tDirNodeReference nodeRef = 0;
+ char recordName[256] = {0};
+
+ status = opendirectory_connect(&session);
+ if (status != eDSNoErr) {
+ return status;
+ }
+
+ status = get_node_ref_and_name(&session, account_name,
+ kDSStdRecordTypeComputers, &nodeRef, recordName);
+ if (status != eDSNoErr) {
+ goto cleanup;
+ }
+
+ status = opendirectory_authenticate_node(&session, nodeRef);
+ if (status != eDSNoErr) {
+ goto cleanup;
+ }
+
+ authBuff = DS_DEFAULT_BUFFER(session.ref);
+ if (authBuff == NULL) {
+ goto cleanup;
+ }
+
+ authBuff->fBufferLength = 0;
+ stepBuff = DS_DEFAULT_BUFFER(session.ref);
+ if (stepBuff == NULL) {
+ goto cleanup;
+ }
+
+ authType = dsDataNodeAllocateString(session.ref,
+ /* kDSStdAuthSetNTHash */
+ kDSStdAuthSetWorkstationPasswd);
+ recordType = dsDataNodeAllocateString(session.ref,
+ kDSStdRecordTypeComputers);
+
+ if (authType == NULL || recordType == NULL) {
+ goto cleanup;
+ }
+
+ DEBUG(4, ("setting NT hash for %s\n", recordName));
+ opendirectory_add_data_buffer_item(authBuff, strlen(recordName),
+ recordName);
+ opendirectory_add_data_buffer_item(authBuff, 16, nt_hash);
+
+ status = dsDoDirNodeAuthOnRecordType(nodeRef, authType, 1,
+ authBuff, stepBuff, NULL, recordType);
+ LOG_DS_ERROR(DS_TRACE_ERRORS, status, "dsDoDirNodeAuthOnRecordType");
+
+cleanup:
+
+ opendirectory_free_buffer(&session, stepBuff);
+ opendirectory_free_buffer(&session, authBuff);
+ opendirectory_free_node(&session, authType);
+ opendirectory_free_node(&session, recordType);
+
+ DS_CLOSE_NODE(nodeRef);
+ opendirectory_disconnect(&session);
+ return status;
+}
+
+ tDirStatus opendirectory_lmchap2changepasswd(const char *account_name,
+ const char *passwordData,
+ const char *passwordHash,
+ u_int8_t passwordFormat,
+ const char *slot_id)
+{
+ struct opendirectory_session session;
+ tDirStatus status = eDSNoErr;
+ tDataBufferPtr authBuff = NULL;
+ tDataBufferPtr stepBuff = NULL;
+ tDataNodePtr authType = NULL;
+ tDataNodePtr recordType = NULL;
+ tDirNodeReference nodeRef = 0;
+ const char *targetaccount = NULL;
+ char recordName[256] = {0};
+
+ status = opendirectory_connect(&session);
+ if (status != eDSNoErr) {
+ return status;
+ }
+
+ status = get_node_ref_and_name(&session, account_name,
+ kDSStdRecordTypeUsers, &nodeRef, recordName);
+ if (status != eDSNoErr) {
+ goto cleanup;
+ }
+
+ status = opendirectory_authenticate_node(&session, nodeRef);
+ if (status != eDSNoErr) {
+ goto cleanup;
+ }
+
+ if (slot_id && *slot_id) {
+ targetaccount = slot_id;
+ } else {
+ targetaccount = recordName;
+ }
+
+ authBuff = DS_DEFAULT_BUFFER(session.ref);
+ if (authBuff == NULL) {
+ goto cleanup;
+ }
+
+ authBuff->fBufferLength = 0;
+ stepBuff = DS_DEFAULT_BUFFER(session.ref);
+ if (stepBuff == NULL) {
+ goto cleanup;
+ }
+
+ authType = dsDataNodeAllocateString(session.ref,
+ /*kDSStdAuthMSLMCHAP2ChangePasswd*/
+ "dsAuthMethodStandard:dsAuthMSLMCHAP2ChangePasswd");
+ recordType = dsDataNodeAllocateString(session.ref,
+ kDSStdRecordTypeUsers);
+ if (authType == NULL) {
+ goto cleanup;
+ }
+
+ // Target account
+ opendirectory_add_data_buffer_item(authBuff, strlen(targetaccount),
+ targetaccount);
+ opendirectory_add_data_buffer_item(authBuff, 1, &passwordFormat);
+ opendirectory_add_data_buffer_item(authBuff, 516, passwordData);
+
+ status = dsDoDirNodeAuthOnRecordType(nodeRef, authType, 1,
+ authBuff, stepBuff, NULL, recordType);
+
+cleanup:
+
+ opendirectory_free_buffer(&session, stepBuff);
+ opendirectory_free_buffer(&session, authBuff);
+ opendirectory_free_node(&session, authType);
+ opendirectory_free_node(&session, recordType);
+
+ DS_CLOSE_NODE(nodeRef);
+ opendirectory_disconnect(&session);
+ return status;
+}
+
+ tDirStatus opendirectory_authenticate_node(
+ struct opendirectory_session *session,
+ tDirNodeReference nodeRef)
+{
+ tDirStatus status = eDSNoErr;
+ tDataBufferPtr authBuff = NULL;
+ tDataBufferPtr stepBuff = NULL;
+ tDataNodePtr authType = NULL;
+ tDataNodePtr recordType = NULL;
+ void * authenticator = NULL;
+
+ authenticator = get_opendirectory_authenticator();
+
+ if (authenticator == NULL ||
+ authenticator_accountlen(authenticator) == 0 ||
+ authenticator_secretlen(authenticator) == 0) {
+ return eDSNullParameter;
+ }
+
+ authBuff = DS_DEFAULT_BUFFER(session->ref);
+ if (authBuff == NULL) {
+ status = eDSDeAllocateFailed;
+ goto cleanup;
+ }
+
+ authBuff->fBufferLength = 0;
+ stepBuff = DS_DEFAULT_BUFFER(session->ref);
+ if (stepBuff == NULL) {
+ status = eDSDeAllocateFailed;
+ goto cleanup;
+ }
+
+ authType = dsDataNodeAllocateString(session->ref,
+ kDSStdAuthNodeNativeClearTextOK);
+ recordType = dsDataNodeAllocateString(session->ref,
+ kDSStdRecordTypeUsers);
+
+ if (authType == NULL || recordType == NULL) {
+ status = eDSDeAllocateFailed;
+ goto cleanup;
+ }
+
+ // Account Name (authenticator)
+ opendirectory_add_data_buffer_item(authBuff,
+ authenticator_accountlen(authenticator),
+ authenticator_account(authenticator));
+
+ // Password (authenticator password)
+ opendirectory_add_data_buffer_item(authBuff,
+ authenticator_secretlen(authenticator),
+ authenticator_secret(authenticator));
+
+ status = dsDoDirNodeAuthOnRecordType(nodeRef, authType, 0,
+ authBuff, stepBuff, NULL, recordType);
+
+cleanup:
+
+ opendirectory_free_buffer(session, stepBuff);
+ opendirectory_free_buffer(session,authBuff);
+ opendirectory_free_node(session, authType);
+ opendirectory_free_node(session, recordType);
+
+ delete_opendirectory_authenticator(authenticator);
+ return status;
+}
+
+/* ======================================================================= */
+/* ID mapping APIs and utilities */
+/* ======================================================================= */
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_IDMAP
+
+static CFStringRef
+opendirectory_attr_to_cfstr(tAttributeEntryPtr attr)
+{
+ if (attr == NULL ||
+ attr->fAttributeSignature.fBufferLength == 0) {
+ return NULL;
+ }
+
+ return CFStringCreateWithBytes(NULL,
+ (const u_int8_t*)attr->fAttributeSignature.fBufferData,
+ attr->fAttributeSignature.fBufferLength,
+ kCFStringEncodingUTF8,
+ False);
+}
+
+static CFStringRef
+opendirectory_valattr_to_cfstr(tAttributeValueEntryPtr valattr)
+{
+ if (valattr == NULL ||
+ valattr->fAttributeValueData.fBufferLength == 0) {
+ return NULL;
+ }
+
+ return CFStringCreateWithBytes(NULL,
+ (const u_int8_t*)valattr->fAttributeValueData.fBufferData,
+ valattr->fAttributeValueData.fBufferLength,
+ kCFStringEncodingUTF8,
+ False);
+}
+
+static CFMutableDictionaryRef create_sam_record(tDirNodeReference node,
+ tDataBufferPtr dataBuffer,
+ tAttributeListRef attributeList,
+ tRecordEntryPtr recordEntry)
+{
+ CFMutableDictionaryRef dsrecord = NULL;
+ tAttributeValueListRef valueList = 0;
+ tAttributeEntryPtr attributeEntry = NULL;
+
+ unsigned long attributeIndex;
+ unsigned long valueIndex;
+ tDirStatus status;
+
+ dsrecord = CFDictionaryCreateMutable(NULL, 0,
+ &kCFCopyStringDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+ if (dsrecord == NULL) {
+ return NULL;
+ }
+
+ /* Walk the returned attributes and build a dictionary of string keys
+ * to array values. This is a best-effort approach - if something
+ * fails, we still want to return as many attributes as possible.
+ */
+ for (attributeIndex = 1;
+ attributeIndex <= recordEntry->fRecordAttributeCount;
+ ++attributeIndex) {
+
+ CFMutableArrayRef valueArray = NULL;
+ CFStringRef key = NULL;
+
+ /* Get the next attribute from the list. */
+ status = dsGetAttributeEntry(node, dataBuffer, attributeList,
+ attributeIndex, &valueList,
+ &attributeEntry);
+ LOG_DS_ERROR(DS_TRACE_ERRORS, status,
+ "dsGetAttributeEntry");
+ if (status != eDSNoErr) {
+ goto next_attribute;
+ }
+
+ valueArray = CFArrayCreateMutable(NULL, 0,
+ &kCFTypeArrayCallBacks);
+ if (valueArray == NULL) {
+ goto next_attribute;
+ }
+
+ /* Build an array of values for this key. */
+ for (valueIndex = 1;
+ valueIndex <= attributeEntry->fAttributeValueCount;
+ valueIndex++) {
+ tAttributeValueEntryPtr valueEntry = NULL;
+ CFStringRef value = NULL;
+
+ status = dsGetAttributeValue(node, dataBuffer,
+ valueIndex, valueList, &valueEntry);
+ LOG_DS_ERROR(DS_TRACE_ERRORS, status,
+ "dsGetAttributeValue");
+ if (status != eDSNoErr) {
+ continue;
+ }
+
+ value = opendirectory_valattr_to_cfstr(valueEntry);
+ if (value != NULL) {
+ CFArrayAppendValue(valueArray, value);
+ }
+
+ if (value) {
+ CFRelease(value);
+ value = NULL;
+ }
+
+ if (valueEntry) {
+ dsDeallocAttributeValueEntry(node, valueEntry);
+ valueEntry = NULL;
+ }
+ }
+
+ /* Stash the key/value pair in our record. */
+ key = opendirectory_attr_to_cfstr(attributeEntry);
+ if (key && CFArrayGetCount(valueArray)) {
+ CFDictionaryAddValue(dsrecord, key, valueArray);
+ }
+
+next_attribute:
+ if (key) {
+ CFRelease(key);
+ key = NULL;
+ }
+
+ if (valueArray) {
+ CFRelease(valueArray);
+ valueArray = NULL;
+ }
+
+ if (valueList) {
+ dsCloseAttributeValueList(valueList);
+ valueList = 0;
+ }
+
+ if (attributeEntry) {
+ dsDeallocAttributeEntry(node, attributeEntry);
+ attributeEntry = NULL;
+ }
+ }
+
+
+ if (attributeList) {
+ dsCloseAttributeList(attributeList);
+ }
+
+ if (recordEntry) {
+ dsDeallocRecordEntry(node, recordEntry);
+ }
+
+ /* Bail if we were't able to add any record attributes. */
+ if (CFDictionaryGetCount(dsrecord) == 0) {
+ CFRelease(dsrecord);
+ return NULL;
+ }
+
+ return dsrecord;
+}
+
+ tDirStatus opendirectory_insert_search_results(tDirNodeReference node,
+ CFMutableArrayRef recordsArray,
+ const unsigned long recordCount,
+ tDataBufferPtr dataBuffer)
+{
+ tAttributeListRef attributeList;
+ tRecordEntryPtr recordEntry;
+ unsigned long recordIndex;
+ CFMutableDictionaryRef dsrecord;
+ tDirStatus status;
+
+ for (recordIndex = 1;
+ recordIndex <= recordCount;
+ recordIndex++) {
+ status = dsGetRecordEntry(node, dataBuffer,
+ recordIndex, &attributeList,
+ &recordEntry);
+ LOG_DS_ERROR(DS_TRACE_ERRORS, status,
+ "dsGetRecordEntry");
+ if (status != eDSNoErr) {
+ return status;
+ }
+
+ dsrecord = create_sam_record(node,
+ dataBuffer,
+ attributeList,
+ recordEntry);
+ if (dsrecord) {
+ CFArrayAppendValue(recordsArray, dsrecord);
+ CFRelease(dsrecord);
+ }
+ }
+
+ return eDSNoErr;
+}
+
+/* Search the directory for a exact matches given by the record type, search
+ * type and search value.
+ *
+ * If successful, recordsArray is filled in with a list of dictionaries. Each
+ * dictionary corresponds to a search result and consists of a number of
+ * key/array pairs. The names of the keys correspond to the requested
+ * attributes.
+ */
+static tDirStatus opendirectory_search_attributes(
+ struct opendirectory_session *session,
+ CFMutableArrayRef recordsArray,
+ tDataListPtr recordType,
+ tDataNodePtr searchAttr,
+ tDataNodePtr searchValue,
+ tDataListPtr requestedAttrs)
+{
+ tDirStatus status;
+ tDataBufferPtr dataBuffer;
+
+ tContextData currentContextData = NULL;
+
+ dataBuffer = DS_DEFAULT_BUFFER(session->ref);
+ if (dataBuffer == NULL) {
+ status = eDSAllocationFailed;
+ goto cleanup;
+ }
+
+ do {
+ unsigned long recordCount;
+
+ status = dsDoAttributeValueSearchWithData(
+ session->search, dataBuffer,
+ recordType, searchAttr, eDSExact, searchValue,
+ requestedAttrs,
+ False /* we want attributes and values */,
+ &recordCount, ¤tContextData);
+
+ LOG_DS_ERROR(DS_TRACE_ERRORS, status,
+ "dsDoAttributeValueSearchWithData");
+ if (status != eDSNoErr) {
+ break;
+ }
+
+ opendirectory_insert_search_results(session->search,
+ recordsArray, recordCount, dataBuffer);
+ } while (status == eDSNoErr && currentContextData != NULL);
+
+cleanup:
+ opendirectory_free_buffer(session, dataBuffer);
+ return status;
+}
+
+/* Search the directory for a case-insensitive matches given by the
+ * record type, and name.
+ *
+ * If successful, recordsArray is filled in with a list of dictionaries. Each
+ * dictionary corresponds to a search result and consists of a number of
+ * key/array pairs. The keys correspond the the names of the requested
+ * attributes.
+ */
+static tDirStatus opendirectory_search_names(
+ struct opendirectory_session *session,
+ tDirNodeReference node,
+ CFMutableArrayRef recordsArray,
+ tDataListPtr recordType,
+ tDataListPtr searchName,
+ tDataListPtr requestedAttrs)
+{
+ tDirStatus status;
+ tDataBufferPtr dataBuffer;
+
+ tContextData currentContextData = NULL;
+
+ dataBuffer = DS_DEFAULT_BUFFER(session->ref);
+ if (dataBuffer == NULL) {
+ status = eDSAllocationFailed;
+ goto cleanup;
+ }
+
+ do {
+ unsigned long recordCount;
+
+ status = dsGetRecordList(node, dataBuffer,
+ searchName, eDSiExact, recordType,
+ requestedAttrs,
+ False /* we want attributes and values */,
+ &recordCount, ¤tContextData);
+
+ LOG_DS_ERROR(DS_TRACE_ERRORS, status, "dsGetRecordList");
+ if (status != eDSNoErr) {
+ break;
+ }
+
+ opendirectory_insert_search_results(node, recordsArray,
+ recordCount, dataBuffer);
+
+ } while (status == eDSNoErr && currentContextData != NULL);
+
+cleanup:
+ opendirectory_free_buffer(session, dataBuffer);
+ return status;
+}
+
+ tDirStatus opendirectory_sam_searchattr(
+ struct opendirectory_session *session,
+ CFMutableArrayRef *records,
+ const char *type,
+ const char *attr,
+ const char *value)
+{
+ tDirStatus status;
+ tDataListPtr recordType = NULL;
+ tDataListPtr samAttributes = NULL;
+ tDataNodePtr searchAttr = NULL;
+ tDataNodePtr searchValue = NULL;
+
+ DEBUG(8, ("searching %s records for %s = %s\n",
+ type, attr, value));
+
+ *records = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+ if (*records == NULL) {
+ return eDSAllocationFailed;
+ }
+
+ status = opendirectory_searchnode(session);
+ if (status != eDSNoErr) {
+ return status;
+ }
+
+ SMB_ASSERT(session->ref != 0);
+ SMB_ASSERT(session->search != 0);
+ if (session->ref == 0 || session->search == 0) {
+ return eDSInvalidRefType;
+ }
+
+ samAttributes = opendirectory_sam_attrlist(session);
+ if (samAttributes == NULL) {
+ status = eDSAllocationFailed;
+ goto cleanup;
+ }
+
+ recordType = dsBuildListFromStrings(session->ref, type, NULL);
+ searchAttr = dsDataNodeAllocateString(session->ref, attr);
+ searchValue = dsDataNodeAllocateString(session->ref, value);
+
+ if (recordType && searchAttr && searchValue) {
+ status = opendirectory_search_attributes(session,
+ *records, recordType,
+ searchAttr, searchValue,
+ samAttributes);
+ } else {
+ status = eDSAllocationFailed;
+ }
+
+ /* We guarantee a result with a successful return. */
+ if (CFArrayGetCount(*records) == 0) {
+ CFRelease(*records);
+ *records = NULL;
+
+ if (status == eDSNoErr) {
+ status = eDSRecordNotFound;
+ }
+ }
+
+cleanup:
+
+ /* Make sure caller doesn't have to clean up on failure. */
+ if (status != eDSNoErr && *records != NULL) {
+ CFRelease(*records);
+ *records = NULL;
+ }
+
+ opendirectory_free_list(session, recordType);
+ opendirectory_free_list(session, samAttributes);
+ opendirectory_free_node(session, searchAttr);
+ opendirectory_free_node(session, searchValue);
+ return status;
+}
+
+/* Look up a record of the given name and type. */
+ tDirStatus opendirectory_sam_searchname(
+ struct opendirectory_session *session,
+ CFMutableArrayRef *records,
+ const char *type,
+ const char *name)
+{
+ tDirStatus status;
+ tDataListPtr recordType = NULL;
+ tDataListPtr searchName = NULL;
+ tDataListPtr samAttributes = NULL;
+
+ *records = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+ if (*records == NULL) {
+ return eDSAllocationFailed;
+ }
+
+ status = opendirectory_searchnode(session);
+ if (status != eDSNoErr) {
+ return status;
+ }
+
+ samAttributes = opendirectory_sam_attrlist(session);
+ if (samAttributes == NULL) {
+ status = eDSAllocationFailed;
+ goto cleanup;
+ }
+
+ name = name ? name : kDSRecordsAll;
+ recordType = dsBuildListFromStrings(session->ref, type, NULL);
+ searchName = dsBuildListFromStrings(session->ref, name, NULL);
+
+ if (recordType && searchName) {
+ status = opendirectory_search_names(session, session->search,
+ *records, recordType, searchName,
+ samAttributes);
+ } else {
+ status = eDSAllocationFailed;
+ }
+
+ /* We guarantee a result with a successful return. */
+ if (CFArrayGetCount(*records) == 0) {
+ CFRelease(*records);
+ *records = NULL;
+ if (status == eDSNoErr) {
+ status = eDSRecordNotFound;
+ }
+ }
+
+cleanup:
+
+ /* Make sure caller doesn't have to clean up on failure. */
+ if (status != eDSNoErr && *records != NULL) {
+ CFRelease(*records);
+ *records = NULL;
+ status = eDSRecordNotFound;
+ }
+
+ opendirectory_free_list(session, recordType);
+ opendirectory_free_list(session, searchName);
+ opendirectory_free_list(session, samAttributes);
+ return status;
+}
+
+ char * opendirectory_talloc_cfstr(void *talloc_ctx, CFStringRef cfstring)
+{
+ char * data;
+ size_t maxsz;
+
+ if ((data = (char *)CFStringGetCStringPtr(cfstring, kCFStringEncodingUTF8))) {
+ return talloc_strdup(talloc_ctx, data);
+ }
+
+ maxsz = (CFStringGetLength(cfstring) + 1) * sizeof(UniChar);;
+ data = talloc_array(talloc_ctx, char, maxsz);
+ if (data == NULL) {
+ return NULL;
+ }
+
+ /* Note: this might fail because the length is the number of UTF-16
+ * code pairs and we don't know how many bytes each code pair might
+ * consume when converted to UTF8. Generally, the string should be in
+ * UTF8 already, so we will be OK.
+ */
+ if (!CFStringGetCString(cfstring, data, maxsz,
+ kCFStringEncodingUTF8)) {
+ talloc_free(data);
+ return NULL;
+ }
+
+ return data;
+}
+
+/* Walk the record data structure returned by the search API and pull out
+ * the named attribute.
+ */
+ char *opendirectory_get_record_attribute(void *talloc_ctx,
+ CFDictionaryRef record,
+ const char *attribute)
+{
+ CFStringRef attrRef = NULL;
+ const void *opaque_value = NULL;
+ char * result = NULL;
+
+ attrRef = CFStringCreateWithCString(NULL, attribute,
+ kCFStringEncodingUTF8);
+ if (!attrRef) {
+ return NULL;
+ }
+
+ if (!CFDictionaryGetValueIfPresent(record, attrRef, &opaque_value)) {
+ goto cleanup;
+ }
+
+ if (CFGetTypeID(opaque_value) == CFArrayGetTypeID()) {
+ CFArrayRef valueList = (CFArrayRef)opaque_value;
+ CFStringRef cfstrRef;
+
+ if (CFArrayGetCount(valueList) == 0) {
+ goto cleanup;
+ }
+
+ /* We should not be using this API to look at multi-valued
+ * attributes.
+ */
+ if (CFArrayGetCount(valueList) > 1 &&
+ strcmp(attribute, kDSNAttrRecordName) != 0) {
+ DEBUG(0, ("WARNING: returning first of %d values "
+ "for %s attribute\n",
+ CFArrayGetCount(valueList), attribute));
+ }
+
+ cfstrRef = (CFStringRef)CFArrayGetValueAtIndex(valueList, 0);
+ if (cfstrRef == NULL) {
+ goto cleanup;
+ }
+
+ result = opendirectory_talloc_cfstr(talloc_ctx, cfstrRef);
+
+ } else if (CFGetTypeID(opaque_value) == CFStringGetTypeID()) {
+ CFStringRef cfstrRef = (CFStringRef)opaque_value;
+
+ if (cfstrRef == NULL) {
+ goto cleanup;
+ }
+
+ result = opendirectory_talloc_cfstr(talloc_ctx, cfstrRef);
+ }
+
+cleanup:
+
+ CFRelease(attrRef);
+ return result;
+}
+
+/* Walk the record data structure returned by the search API and return True
+ * if "value" matches any of the values of "attribute".
+ */
+ BOOL opendirectory_match_record_attribute(CFDictionaryRef record,
+ const char *attribute,
+ const char * value)
+{
+ CFStringRef attrRef = NULL;
+ CFStringRef valRef = NULL;
+ const void *opaque_value = NULL;
+
+ BOOL ret = False;
+
+ attrRef = CFStringCreateWithCString(NULL, attribute,
+ kCFStringEncodingUTF8);
+ if (!attrRef) {
+ goto cleanup;
+ }
+
+ valRef = CFStringCreateWithCString(NULL, value,
+ kCFStringEncodingUTF8);
+ if (!valRef) {
+ goto cleanup;
+ }
+
+ if (!CFDictionaryGetValueIfPresent(record, attrRef, &opaque_value)) {
+ goto cleanup;
+ }
+
+ if (CFGetTypeID(opaque_value) == CFArrayGetTypeID()) {
+ CFArrayRef valueList = (CFArrayRef)opaque_value;
+ CFStringRef cfstrRef;
+ int i;
+ CFComparisonResult eq;
+
+ for (i = 0; i < CFArrayGetCount(valueList); ++i) {
+ cfstrRef =
+ (CFStringRef)CFArrayGetValueAtIndex(valueList, 0);
+
+ if (cfstrRef == NULL) {
+ continue;
+ }
+
+ eq = CFStringCompare(valRef, cfstrRef,
+ kCFCompareCaseInsensitive);
+ if (eq == kCFCompareEqualTo) {
+ ret = True;
+ goto cleanup;
+ }
+
+ }
+ } else if (CFGetTypeID(opaque_value) == CFStringGetTypeID()) {
+ CFStringRef cfstrRef = (CFStringRef)opaque_value;
+ CFComparisonResult eq;
+
+ eq = CFStringCompare(valRef, cfstrRef,
+ kCFCompareCaseInsensitive);
+ if (eq == kCFCompareEqualTo) {
+ ret = True;
+ goto cleanup;
+ }
+ }
+
+cleanup:
+
+ if (attrRef) {
+ CFRelease(attrRef);
+ }
+
+ if (valRef) {
+ CFRelease(valRef);
+ }
+
+ return ret;
+}
+
+ tDirStatus opendirectory_map_unix_to_sid(
+ struct opendirectory_session *session,
+ const struct unixid *user,
+ DOM_SID *sid)
+{
+ return eNotYetImplemented;
+}
+
+ tDirStatus opendirectory_map_sid_to_unix(
+ struct opendirectory_session *session,
+ const DOM_SID *sid,
+ struct unixid *user)
+{
+ return eNotYetImplemented;
+}
+
+ tDataListPtr opendirectory_sam_attrlist(struct opendirectory_session *session)
+{
+ return dsBuildListFromStrings(session->ref,
+ kDSNAttrRecordName,
+ kDSNAttrRecordType,
+ kDSNAttrMetaNodeLocation,
+ kDSNAttrAuthenticationAuthority,
+ kDS1AttrUniqueID,
+ kDS1AttrPrimaryGroupID,
+ kDS1AttrNFSHomeDirectory,
+ kDS1AttrDistinguishedName,
+ kDS1AttrComment,
+
+ kDS1AttrSMBRID,
+ kDS1AttrSMBGroupRID,
+
+ kDS1AttrSMBSID,
+ kDS1AttrSMBPrimaryGroupSID,
+
+ kDS1AttrSMBPWDLastSet,
+ kDS1AttrSMBLogonTime,
+ kDS1AttrSMBLogoffTime,
+ kDS1AttrSMBKickoffTime,
+ kDS1AttrSMBHome,
+ kDS1AttrSMBHomeDrive,
+ kDS1AttrSMBScriptPath,
+ kDS1AttrSMBProfilePath,
+ kDS1AttrSMBUserWorkstations,
+ kDS1AttrSMBLMPassword,
+ kDS1AttrSMBNTPassword,
+ kDS1AttrSMBAcctFlags,
+
+ NULL);
+}
+
+/* Open the Open Directory search node. */
+ tDirStatus opendirectory_searchnode(struct opendirectory_session *session)
+{
+ tDirStatus status = eDSNoErr;
+ unsigned long returnCount = 0;
+ tDataBufferPtr nodeBuffer = NULL;
+ tDataListPtr searchNodeName = NULL;
+
+ if (session->search != 0) {
+ return eDSNoErr;
+ }
+
+ nodeBuffer = dsDataBufferAllocate(session->ref, SMALL_DS_BUFFER_SIZE);
+ if (nodeBuffer == NULL) {
+ return eDSAllocationFailed;
+ }
+
+ status = dsFindDirNodes(session->ref, nodeBuffer, NULL,
+ eDSSearchNodeName, &returnCount, NULL);
+ LOG_DS_ERROR(DS_TRACE_ERRORS, status, "dsFindDirNodes");
+ if (status != eDSNoErr) {
+ goto cleanup;
+ }
+
+ /* This should never happen. */
+ if (returnCount == 0) {
+ status = eDSNodeNotFound;
+ goto cleanup;
+ }
+
+ status = dsGetDirNodeName(session->ref, nodeBuffer, 1,
+ &searchNodeName);
+ LOG_DS_ERROR(DS_TRACE_ERRORS, status, "dsGetDirNodeName");
+ if (status != eDSNoErr) {
+ goto cleanup;
+ }
+
+ status = dsOpenDirNode(session->ref, searchNodeName, &session->search);
+ LOG_DS_ERROR(DS_TRACE_ERRORS, status, "dsOpenDirNode");
+
+cleanup:
+ opendirectory_free_buffer(session, nodeBuffer);
+ opendirectory_free_list(session, searchNodeName);
+ return status;
+}
+
+/* Return a NULL-terminated addar of node paths that are local. This can be
+ * used to figure out whether records came from a local DS node or a remote
+ * one.
+ */
+ const char ** opendirectory_local_paths(struct opendirectory_session *session)
+{
+ tDirStatus status;
+ tDataBufferPtr nodeBuffer;
+ tDataListPtr localNodeName;
+ unsigned long returnCount;
+
+ int current, i;
+ const char ** path_list = NULL;
+
+ nodeBuffer = DS_DEFAULT_BUFFER(session->ref);
+ if (!nodeBuffer) {
+ return NULL;
+ }
+
+ status = dsFindDirNodes(session->ref, nodeBuffer, NULL,
+ eDSLocalNodeNames, &returnCount, NULL);
+ LOG_DS_ERROR(DS_TRACE_ERRORS, status, "dsFindDirNodes");
+ if (status != eDSNoErr || returnCount == 0) {
+ goto cleanup;
+ }
+
+ path_list = SMB_CALLOC_ARRAY(const char *, returnCount + 1);
+ if (!path_list) {
+ goto cleanup;
+ }
+
+ for (i = 1, current = 0; i <= returnCount; ++i) {
+ status = dsGetDirNodeName(session->ref, nodeBuffer, i,
+ &localNodeName);
+ LOG_DS_ERROR(DS_TRACE_ERRORS, status, "dsGetDirNodeName");
+ if (status != eDSNoErr) {
+ continue;
+ }
+
+ /* FYI: dsGetPathFromList mallocs it's result. */
+ path_list[current] = dsGetPathFromList(session->ref,
+ localNodeName, "/" );
+ if (path_list[current]) {
+ ++current;
+ }
+
+ opendirectory_free_list(session, localNodeName);
+ }
+
+cleanup:
+ opendirectory_free_buffer(session, nodeBuffer);
+ return path_list;
+}
+
+
+static CFMutableDictionaryRef convert_xml_to_dict(const char *xml)
+{
+ CFPropertyListRef xmlDict;
+ CFDataRef xmlData;
+
+ xmlData = CFDataCreate(NULL, (const uint8_t *)xml, strlen(xml));
+ if (!xmlData) {
+ return NULL;
+ }
+
+ xmlDict = CFPropertyListCreateFromXMLData(NULL, xmlData,
+ kCFPropertyListMutableContainersAndLeaves, NULL);
+
+ CFRelease(xmlData);
+
+ /* The return value of the CFPropertyListCreateFromXMLData function
+ * depends on the contents of the given XML data. CFPropertyListRef can
+ * be a reference to any of the property list objects: CFData,
+ * CFString, CFArray, CFDictionary, CFDate, CFBoolean, and CFNumber.
+ */
+ return (CFMutableDictionaryRef)xmlDict;
+}
+
+/* Given a string containing a SID, truncate it after the end of any valid SID
+ * characters. This is needed for cases when we pull the SID from the
+ * CIFSServer config record and it contains trailing junk.
+ */
+static BOOL chop_sid_string(char * sid_string)
+{
+ char * c;
+
+ if (*sid_string != 'S' && *sid_string != 's') {
+ return False;
+ }
+
+ for (c = &sid_string[1]; *c != '\0' && (*c == '-' || isdigit(*c)); ++c) {
+ /* skip it */
+ }
+
+ *c = '\0';
+ return True;
+}
+
+static BOOL allocate_domain_sid_cache(struct opendirectory_session *session)
+{
+
+ if (session->domain_sid_cache == NULL) {
+ session->domain_sid_cache =
+ CFDictionaryCreateMutable(NULL, 0,
+ &kCFCopyStringDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+ }
+
+ if (session->domain_sid_cache == NULL) {
+ DEBUG(4, ("unable to allocate domain SID cache\n"));
+ return False;
+ }
+
+ return True;
+}
+
+/* Given a raw /Config/CIFSServer record, update the domain_sid cache. */
+static BOOL update_domain_sid_cache(void *mem_ctx,
+ struct opendirectory_session *session,
+ CFDictionaryRef configRecord,
+ DOM_SID *samsid)
+{
+ BOOL ret = False;
+ CFMutableDictionaryRef xmlDict = NULL;
+ CFStringRef nodeLocation = NULL;
+ char * xml_string;
+ char * domain_string;
+ char * domain_sid_string;
+ char * node_path;
+
+ if (configRecord == NULL) {
+ return False;
+ }
+
+ xml_string = opendirectory_get_record_attribute(mem_ctx,
+ configRecord, kDS1AttrXMLPlist);
+ if (!xml_string) {
+ goto cleanup;
+ }
+
+ /* The cache is keyed by the path of the DS node that owns the config
+ * record.
+ */
+ node_path = opendirectory_get_record_attribute(mem_ctx,
+ configRecord, kDSNAttrMetaNodeLocation);
+ if (node_path) {
+ nodeLocation = CFStringCreateWithCString(NULL, node_path,
+ kCFStringEncodingUTF8);
+ }
+
+ /* Convert the XML to a CFDictionary so we can find records in it. */
+ if (!(xmlDict = convert_xml_to_dict(xml_string))) {
+ goto cleanup;
+ }
+
+ /* The plist should have "domain" and "SID" keys. It's noce if we can
+ * get the domain key, because we can dump it to the log.
+ */
+ domain_string = opendirectory_get_record_attribute(mem_ctx,
+ xmlDict, "domain");
+
+ domain_sid_string = opendirectory_get_record_attribute(mem_ctx,
+ xmlDict, "SID");
+ if (domain_sid_string &&
+ chop_sid_string(domain_sid_string) &&
+ string_to_sid(samsid, domain_sid_string)) {
+ /* We have a result. Also try and cache it. */
+ ret = True;
+
+ if (session->domain_sid_cache == NULL) {
+ DEBUG(4, ("unable to cache domain SID for %s: %s\n",
+ node_path ? node_path : "unknown node",
+ domain_sid_string));
+ goto cleanup;
+ }
+
+ DEBUG(4, ("caching domain SID for %s: %s\n",
+ domain_string ? domain_string : "unknown domain",
+ domain_sid_string));
+
+ if (nodeLocation) {
+ CFDictionaryAddValue(session->domain_sid_cache,
+ nodeLocation, xmlDict);
+ }
+ }
+
+cleanup:
+ if (nodeLocation) {
+ CFRelease(nodeLocation);
+ }
+
+ if (xmlDict) {
+ CFRelease(xmlDict);
+ }
+
+ return ret;
+}
+
+typedef CFDictionaryRef (*domain_sid_search_func) (
+ struct opendirectory_session *session,
+ const DOM_SID *domain_sid,
+ const DOM_SID *match_sid);
+
+struct search_context {
+ domain_sid_search_func match;
+ struct opendirectory_session * session;
+ const DOM_SID * match_sid;
+ CFDictionaryRef result;
+};
+
+static void search_domain_sid_cache_cb(const CFStringRef nodeLocation,
+ const CFDictionaryRef domainRecord,
+ struct search_context ctx)
+{
+ DOM_SID domain_sid;
+ char * domain_sid_string;
+
+ if (ctx.result != NULL) {
+ return;
+ }
+
+ domain_sid_string = opendirectory_get_record_attribute(NULL,
+ domainRecord, "SID");
+ if (domain_sid_string &&
+ chop_sid_string(domain_sid_string) &&
+ string_to_sid(&domain_sid, domain_sid_string)) {
+ ctx.result = ctx.match(ctx.session, &domain_sid,
+ ctx.match_sid);
+ }
+
+ TALLOC_FREE(domain_sid_string);
+
+}
+
+/* Apply the given function to each entry on the domain SID cache. We use this
+ * to try any figure out the RID when all we have is an unadorned SID.
+ */
+static CFDictionaryRef search_domain_sid_cache(
+ struct opendirectory_session *session,
+ const DOM_SID *match_sid,
+ domain_sid_search_func match)
+{
+ struct search_context ctx =
+ {
+ .match = match,
+ .session = session,
+ .match_sid = match_sid,
+ .result = NULL
+ };
+
+ if (session->domain_sid_cache == NULL) {
+ return NULL;
+ }
+
+ CFDictionaryApplyFunction(session->domain_sid_cache,
+ (CFDictionaryApplierFunction)search_domain_sid_cache_cb,
+ &ctx);
+
+ return ctx.result;
+}
+
+/* Fill the domain SID cache with the /Congif/CIFSServer records of all
+ * the domains that the directory knows about.
+ */
+static void fill_domain_sid_cache(void *mem_ctx,
+ struct opendirectory_session *session)
+{
+ tDirStatus status;
+ CFMutableArrayRef records = NULL;
+ DOM_SID sid;
+ int i;
+
+ /* Reset the cache is we had already filled it. */
+ if (session->domain_sid_cache != NULL) {
+ CFRelease(session->domain_sid_cache);
+ session->domain_sid_cache = NULL;
+ }
+
+ allocate_domain_sid_cache(session);
+ if (session->domain_sid_cache == NULL) {
+ DEBUG(0, ("failed to initialize domain SID cache\n"));
+ return;
+ }
+
+ /* Find all the /Config/CIFSServer records. */
+ status = opendirectory_sam_searchname(session, &records,
+ kDSStdRecordTypeConfig, "CIFSServer");
+
+ LOG_DS_ERROR(DS_TRACE_ERRORS, status,
+ "opendirectory_sam_searchname[/Config/CIFSServer]");
+ if (status != eDSNoErr) {
+ return;
+ }
+
+ for (i = 0; i < CFArrayGetCount(records); ++i) {
+ CFDictionaryRef r = CFArrayGetValueAtIndex(records, i);
+ update_domain_sid_cache(mem_ctx, session, r, &sid);
+ }
+
+ CFRelease(records);
+}
+
+static BOOL get_domain_sid_from_path(void *mem_ctx,
+ struct opendirectory_session *session,
+ const char * node_path,
+ DOM_SID *samsid)
+{
+ CFMutableArrayRef recordsArray = NULL;
+ tDirStatus status;
+ tDirNodeReference nodeReference = 0;
+ tDataListPtr recordName = NULL;
+ tDataListPtr recordType = NULL;
+ tDataListPtr attributes = NULL;
+ CFMutableDictionaryRef configRecord = NULL;
+ CFStringRef nodelocationRef;
+ CFDictionaryRef domainInfo = NULL;
+ char * domain_sid_string;
+
+ BOOL ret = False;
+
+ nodelocationRef = CFStringCreateWithCString(NULL, node_path,
+ kCFStringEncodingUTF8);
+ if (!nodelocationRef) {
+ return False;
+ }
+
+ if (session->domain_sid_cache == NULL) {
+ fill_domain_sid_cache(mem_ctx, session);
+ }
+
+ /* Check whether we have cached this domain SID already. */
+ if (session->domain_sid_cache &&
+ (domainInfo = CFDictionaryGetValue(session->domain_sid_cache,
+ nodelocationRef))) {
+ domain_sid_string =
+ opendirectory_get_record_attribute(mem_ctx,
+ domainInfo, "SID");
+ if (domain_sid_string &&
+ chop_sid_string(domain_sid_string) &&
+ string_to_sid(samsid, domain_sid_string)) {
+ DEBUG(4, ("found cached domain SID for %s: %s\n",
+ node_path, domain_sid_string));
+ ret = True;
+ goto cleanup;
+ }
+ }
+
+ /* SID for this node path was not in the cache. We need to find it from
+ * the /<node_path>/Config/CIFSServer records.
+ */
+ status = opendirectory_open_node(session, node_path, &nodeReference);
+ if (status != eDSNoErr) {
+ goto cleanup;
+ }
+
+ recordsArray = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+ recordType = dsBuildListFromStrings(session->ref,
+ kDSStdRecordTypeConfig, NULL);
+ recordName = dsBuildListFromStrings(session->ref,
+ "CIFSServer", NULL);
+ attributes = dsBuildListFromStrings(session->ref,
+ kDS1AttrXMLPlist , NULL);
+
+ if (!recordName || !recordType || !attributes || !recordsArray) {
+ goto cleanup;
+ }
+
+ status = opendirectory_search_names(session,
+ nodeReference, recordsArray,
+ recordType, recordName, attributes);
+
+ LOG_DS_ERROR(DS_TRACE_ERRORS, status,
+ "opendirectory_search_names[CIFSServer]");
+ if (status != eDSNoErr || CFArrayGetCount(recordsArray) == 0) {
+ goto cleanup;
+ }
+
+ /* Retrieve the CIFSServer record as XML data. */
+ configRecord =
+ (CFMutableDictionaryRef) CFArrayGetValueAtIndex(recordsArray, 0);
+
+ ret = update_domain_sid_cache(mem_ctx, session, configRecord, samsid);
+
+cleanup:
+ DS_CLOSE_NODE(nodeReference);
+ opendirectory_free_list(session, recordName);
+ opendirectory_free_list(session, recordType);
+ opendirectory_free_list(session, attributes);
+
+ if (nodelocationRef) {
+ CFRelease(nodelocationRef);
+ }
+
+ if (recordsArray) {
+ CFRelease(recordsArray);
+ }
+
+ return ret;
+}
+
+/* Match the given node path against our cached set of local paths to
+ * figure out whether it it local or not.
+ */
+static BOOL node_path_is_local(const char ** local_paths,
+ const char * node_path)
+{
+ const char ** path;
+
+ for (path = local_paths; path && *path; ++path) {
+ /* XXX: should this reallly be case-sensitive? -- jpeach */
+ if (strcmp(node_path, *path) == 0) {
+ return True;
+ }
+ }
+
+ return False;
+}
+
+static BOOL get_sid_for_samrecord(void *mem_ctx,
+ struct opendirectory_session *session,
+ CFDictionaryRef sam_record,
+ DOM_SID *record_sid)
+{
+ fstring sidstr;
+ char * record_name;
+ char * record_path;
+
+ record_path = opendirectory_get_record_attribute(mem_ctx,
+ sam_record, kDSNAttrMetaNodeLocation);
+ record_name = opendirectory_get_record_attribute(mem_ctx,
+ sam_record, kDSNAttrRecordName);
+
+ if (!record_path || !record_name) {
+ goto done;
+ }
+
+ DEBUG (5, ("resolving SID for record=%s within %s\n",
+ record_name, record_path));
+
+ /* By default, we will make this RID relative to the server SID. If
+ * the user record came from a directory path where we can resolve a
+ * domain SID, we will use that instead.
+ */
+ sid_copy(record_sid, get_global_sam_sid());
+
+ /* Local Node - All Users are relative to the server sid */
+ if (node_path_is_local(session->local_path_cache, record_path)) {
+ DEBUG(4, ("record=%s is local\n", record_name));
+ goto done;
+ }
+
+ if (get_domain_sid_from_path(mem_ctx, session,
+ record_path, record_sid)) {
+ DEBUG(4, ("record=%s is relative to domain SID=%s\n",
+ record_name, sid_to_string(sidstr, record_sid)));
+ } else {
+ DEBUG(4, ("no domain SID for %s, assuming local SID=%s\n",
+ record_path, sid_to_string(sidstr, record_sid)));
+ }
+
+done:
+ return True;
+}
+
+ BOOL opendirectory_find_usersid_from_record(
+ struct opendirectory_session *session,
+ CFDictionaryRef sam_record,
+ DOM_SID *sid)
+{
+ void * mem_ctx = talloc_init("opendirectory_find_usersid_from_record");
+ DOM_SID samsid;
+ char * strval;
+ BOOL ret = False;
+
+ char * record_type;
+
+ /* Make sure we are dealing with a user record. */
+ record_type = opendirectory_get_record_attribute(mem_ctx,
+ sam_record, kDSNAttrRecordType);
+ if (!record_type) {
+ goto done;
+ }
+
+ DEBUG(6, ("determining user SID for %s record\n", record_type));
+ if (strcmp(record_type, kDSStdRecordTypeUsers) != 0 &&
+ strcmp(record_type, kDSStdRecordTypeComputers) != 0) {
+ goto done;
+ }
+
+ if (!session->local_path_cache) {
+ session->local_path_cache = opendirectory_local_paths(session);
+ }
+
+ strval = opendirectory_get_record_attribute(mem_ctx,
+ sam_record, kDS1AttrSMBSID);
+ if (strval) {
+ string_to_sid(sid, strval);
+ ret = True;
+ goto done;
+ }
+
+ /* Since there is no SID attribute available, we will need to
+ * construct something by using an attribute as a RID. We can't do
+ * this unless we have a base SID for this record.
+ */
+ if (!get_sid_for_samrecord(mem_ctx, session, sam_record, &samsid)) {
+ goto done;
+ }
+
+ sid_copy(sid, &samsid);
+
+ strval = opendirectory_get_record_attribute(mem_ctx,
+ sam_record, kDS1AttrSMBRID);
+ if (strval) {
+ uint32_t rid = (uint32_t)strtoul(strval, NULL, 10 /* base */);
+ sid_append_rid(sid, rid);
+ ret = True;
+ goto done;
+ }
+
+ strval = opendirectory_get_record_attribute(mem_ctx,
+ sam_record, kDS1AttrUniqueID);
+ if (strval) {
+ int32_t uid = (int32_t)strtol(strval, NULL, 10 /* base */);
+ uint32_t rid;
+
+ if (opendirectory_match_record_attribute(sam_record,
+ kDSNAttrRecordName, lp_guestaccount())) {
+ rid = DOMAIN_USER_RID_GUEST;
+ } else {
+ rid = algorithmic_pdb_uid_to_user_rid(uid);
+ }
+
+ sid_append_rid(sid, rid);
+ ret = True;
+ goto done;
+ }
+
+ /* Nothing in the directory that we can map this record with. */
+
+done:
+ TALLOC_FREE(mem_ctx);
+ return ret;
+}
+
+static BOOL find_groupsid_from_user(void *mem_ctx,
+ struct opendirectory_session *session,
+ CFDictionaryRef sam_record,
+ DOM_SID *group_sid)
+{
+ CFDictionaryRef group_record;
+ char * strval;
+
+ /* Both user and group records can have a kDS1AttrSMBPrimaryGroupSID
+ * attribute.
+ */
+ strval = opendirectory_get_record_attribute(mem_ctx,
+ sam_record, kDS1AttrSMBPrimaryGroupSID);
+ if (strval) {
+ string_to_sid(group_sid, strval);
+ return True;
+ }
+
+ /* The group SID wasn't explicitly overridden, so we have to look up
+ * the group record and work from there.
+ */
+ strval = opendirectory_get_record_attribute(mem_ctx,
+ sam_record, kDS1AttrPrimaryGroupID);
+ if (!strval) {
+ return False;
+ }
+
+ group_record = sam_searchattr_first(session, kDSStdRecordTypeGroups,
+ kDS1AttrPrimaryGroupID, strval);
+ if (group_record) {
+ BOOL ret;
+
+ ret = opendirectory_find_groupsid_from_record(session,
+ group_record, group_sid);
+ CFRelease(group_record);
+ return ret;
+ }
+
+ /* Nothing in the directory that we can map this record with, but
+ * it's a user record so we can put them in Domain Users.
+ */
+ sid_copy(group_sid, get_global_sam_sid());
+ sid_append_rid(group_sid, DOMAIN_GROUP_RID_USERS);
+
+ return True;
+}
+
+ BOOL opendirectory_find_groupsid_from_record(
+ struct opendirectory_session *session,
+ CFDictionaryRef sam_record,
+ DOM_SID *sid)
+{
+ void * mem_ctx = talloc_init("opendirectory_find_groupsid_from_record");
+ DOM_SID samsid;
+ char * strval;
+ BOOL ret = False;
+
+ char * record_type;
+
+ record_type = opendirectory_get_record_attribute(mem_ctx,
+ sam_record, kDSNAttrRecordType);
+ if (!record_type) {
+ goto done;
+ }
+
+ DEBUG(6, ("determining group SID for %s record\n", record_type));
+
+ /* Make sure we are dealing with a user, group or computer record. */
+ if (strcmp(record_type, kDSStdRecordTypeGroups) != 0 &&
+ strcmp(record_type, kDSStdRecordTypeUsers) != 0 &&
+ strcmp(record_type, kDSStdRecordTypeComputers) != 0) {
+ goto done;
+ }
+
+ if (!session->local_path_cache) {
+ session->local_path_cache = opendirectory_local_paths(session);
+ }
+
+ /* If we are finding the primary group sid with only a user record,
+ * punt it becasue we might need to look up the group record.
+ */
+ if (strcmp(record_type, kDSStdRecordTypeUsers) == 0) {
+ ret = find_groupsid_from_user(mem_ctx, session,
+ sam_record, sid);
+ goto done;
+ }
+
+ strval = opendirectory_get_record_attribute(mem_ctx,
+ sam_record, kDS1AttrSMBPrimaryGroupSID);
+ if (strval) {
+ string_to_sid(sid, strval);
+ ret = True;
+ goto done;
+ }
+
+ strval = opendirectory_get_record_attribute(mem_ctx,
+ sam_record, kDS1AttrSMBSID);
+ if (strval) {
+ string_to_sid(sid, strval);
+ ret = True;
+ goto done;
+ }
+
+ /* Since there is no SID attribute available, we will need to
+ * construct something by using an attribute as a RID. We can't do
+ * this unless we have a base SID for this record.
+ */
+ if (!get_sid_for_samrecord(mem_ctx, session, sam_record, &samsid)) {
+ goto done;
+ }
+
+ sid_copy(sid, &samsid);
+
+ strval = opendirectory_get_record_attribute(mem_ctx,
+ sam_record, kDS1AttrSMBGroupRID);
+ if (strval) {
+ uint32_t rid = (uint32_t)strtoul(strval, NULL, 10 /* base */);
+ sid_append_rid(sid, rid);
+ ret = True;
+ goto done;
+ }
+
+ strval = opendirectory_get_record_attribute(mem_ctx,
+ sam_record, kDS1AttrSMBRID);
+ if (strval) {
+ uint32_t rid = (uint32_t)strtoul(strval, NULL, 10 /* base */);
+ sid_append_rid(sid, rid);
+ ret = True;
+ goto done;
+ }
+
+ strval = opendirectory_get_record_attribute(mem_ctx,
+ sam_record, kDS1AttrPrimaryGroupID);
+ if (strval) {
+ int32_t gid = (int32_t)strtol(strval, NULL, 10 /* base */);
+ uint32_t rid;
+
+ /* Note that this implies that we have matching user and group
+ * names for lp_guestaccount(). This is true for common choices
+ * like "unknown" and "nobody".
+ */
+ if (opendirectory_match_record_attribute(sam_record,
+ kDSNAttrRecordName, lp_guestaccount())) {
+ rid = DOMAIN_GROUP_RID_GUESTS;
+ } else {
+ rid = algorithmic_pdb_gid_to_group_rid(gid);
+ }
+
+ sid_append_rid(sid, rid);
+ ret = True;
+ goto done;
+ }
+
+ if (strcmp(record_type, kDSStdRecordTypeComputers) == 0) {
+ sid_append_rid(sid, DOMAIN_GROUP_RID_COMPUTERS);
+ ret = True;
+ }
+
+done:
+ TALLOC_FREE(mem_ctx);
+ return ret;
+}
+
+static CFDictionaryRef sam_searchname_first(
+ struct opendirectory_session *session,
+ const char *type,
+ const char *name)
+{
+ CFMutableArrayRef records = NULL;
+ tDirStatus status;
+
+ status = opendirectory_sam_searchname(session,
+ &records, type, name);
+
+ if (records) {
+ CFDictionaryRef first =
+ (CFDictionaryRef)CFArrayGetValueAtIndex(records, 0);
+
+ CFRetain(first);
+ CFRelease(records);
+ return first;
+ }
+
+ return NULL;
+}
+
+static CFDictionaryRef sam_searchattr_first(
+ struct opendirectory_session *session,
+ const char *type,
+ const char *attr,
+ const char *value)
+{
+ CFMutableArrayRef records = NULL;
+ tDirStatus status;
+
+ status = opendirectory_sam_searchattr(session,
+ &records, type, attr, value);
+
+ if (records) {
+ CFDictionaryRef first =
+ (CFDictionaryRef)CFArrayGetValueAtIndex(records, 0);
+
+ CFRetain(first);
+ CFRelease(records);
+ return first;
+ }
+
+ return NULL;
+}
+
+static CFDictionaryRef find_record_from_usersid_and_domsid(
+ struct opendirectory_session *session,
+ const DOM_SID *domain_sid,
+ const DOM_SID *sid)
+{
+ CFDictionaryRef sam_record;
+ fstring rid_string;
+ fstring uid_string;
+ uint32_t rid;
+
+ if (!sid_peek_check_rid(domain_sid, sid, &rid)) {
+ return NULL;
+ }
+
+ if (rid == DOMAIN_USER_RID_GUEST) {
+ return sam_searchname_first(session, kDSStdRecordTypeUsers,
+ lp_guestaccount());
+ }
+
+ snprintf(rid_string, sizeof(rid_string) - 1, "%u", rid);
+ snprintf(uid_string, sizeof(uid_string) - 1, "%u",
+ algorithmic_pdb_user_rid_to_uid(rid));
+
+ sam_record = sam_searchattr_first(session, kDSStdRecordTypeUsers,
+ kDS1AttrSMBRID, rid_string);
+ if (sam_record) {
+ return sam_record;
+ }
+
+ sam_record = sam_searchattr_first(session, kDSStdRecordTypeComputers,
+ kDS1AttrSMBRID, rid_string);
+ if (sam_record) {
+ return sam_record;
+ }
+
+ /* If it's not a generated user RID, searching won't help. */
+ if (!algorithmic_pdb_rid_is_user(rid)) {
+ return NULL;
+ }
+
+ sam_record = sam_searchattr_first(session, kDSStdRecordTypeUsers,
+ kDS1AttrUniqueID, uid_string);
+ if (sam_record) {
+ return sam_record;
+ }
+
+ sam_record = sam_searchattr_first(session, kDSStdRecordTypeComputers,
+ kDS1AttrUniqueID, uid_string);
+ if (sam_record) {
+ return sam_record;
+ }
+
+ return NULL;
+}
+
+/* Map SIDS that are relative to the SAM SID to the "well-known" Apple builtin
+ * SID of the form S-1-5-21-RID. Arguably we should also do this for the domain
+ * SID if we are a domain controller.
+ */
+static BOOL apple_wellknown_sid(const DOM_SID *sid, DOM_SID *apple_sid)
+{
+ uint32_t rid;
+ DOM_SID apple_wellknown =
+ { 1, 1, {0,0,0,0,0,5}, {21,0,0,0,0,0,0,0,0,0,0,0,0,0,0}};
+
+ if (!sid_peek_check_rid(get_global_sam_sid(), sid, &rid)) {
+ return False;
+ }
+
+ return sid_compose(apple_sid, &apple_wellknown, rid);
+}
+
+ CFDictionaryRef opendirectory_find_record_from_usersid(
+ struct opendirectory_session *session,
+ const DOM_SID *sid)
+{
+ CFDictionaryRef sam_record;
+ fstring sid_string;
+
+ DOM_SID domain_sid = {0};
+ DOM_SID apple_user_sid = {0};
+ DOM_SID sam_sid = {0};
+
+ secrets_fetch_domain_sid(lp_workgroup(), &domain_sid);
+ sam_sid = *get_global_sam_sid();
+
+ sid_to_string(sid_string, sid);
+
+ sam_record = sam_searchattr_first(session, kDSStdRecordTypeUsers,
+ kDS1AttrSMBSID, sid_string);
+ if (sam_record) {
+ return sam_record;
+ }
+
+ /* Check whether this SID is an Apple "well-known" SID. Since there are
+ * no well-known Computers, we don't need to repeat this check.
+ */
+ if (apple_wellknown_sid(sid, &apple_user_sid)) {
+ sid_to_string(sid_string, &apple_user_sid);
+ sam_record = sam_searchattr_first(session,
+ kDSStdRecordTypeUsers,
+ kDS1AttrSMBSID, sid_string);
+ if (sam_record) {
+ return sam_record;
+ }
+ }
+
+ sam_record = sam_searchattr_first(session, kDSStdRecordTypeComputers,
+ kDS1AttrSMBSID, sid_string);
+ if (sam_record) {
+ return sam_record;
+ }
+
+ /* The SID might be in a domain we know about, so we can try poking
+ * around for something to match the RID.
+ */
+
+ sam_record = find_record_from_usersid_and_domsid(session,
+ &sam_sid, sid);
+ if (sam_record) {
+ return sam_record;
+ }
+
+ /* Also try the domain SID if it is not the same as our SAM SID. */
+ if (sid_compare_domain(&domain_sid, &sam_sid) != 0) {
+ sam_record = find_record_from_usersid_and_domsid(session,
+ &domain_sid, sid);
+ if (sam_record) {
+ return sam_record;
+ }
+ }
+
+ /* As a last resort, iterate over all the domains that are available in
+ * the directory and see whether we can match the SID in any of those.
+ */
+ if (session->domain_sid_cache == NULL) {
+ void * mem_ctx = talloc_init(__FUNCTION__);
+ fill_domain_sid_cache(mem_ctx, session);
+ TALLOC_FREE(mem_ctx);
+ }
+
+ return search_domain_sid_cache(session, sid,
+ find_record_from_usersid_and_domsid);
+}
+
+static CFDictionaryRef find_record_from_groupsid_and_domsid(
+ struct opendirectory_session *session,
+ const DOM_SID *domain_sid,
+ const DOM_SID *group_sid)
+{
+ CFDictionaryRef sam_record;
+ fstring rid_string;
+ fstring gid_string;
+ uint32_t rid;
+
+ if (!sid_peek_check_rid(domain_sid, group_sid, &rid)) {
+ return NULL;
+ }
+
+ DEBUG(8, ("searching domain %s for group record with SID %s\n",
+ sid_to_string(gid_string, domain_sid),
+ sid_to_string(rid_string, group_sid)));
+
+ snprintf(rid_string, sizeof(rid_string) - 1, "%u", rid);
+
+ /* First, search for a record with a matching group RID. If that fails,
+ * we can check to see whether it's a well-known RID for which we have
+ * a builtin default.
+ */
+ sam_record = sam_searchattr_first(session, kDSStdRecordTypeGroups,
+ kDS1AttrSMBRID, rid_string);
+ if (sam_record) {
+ return sam_record;
+ }
+
+ sam_record = sam_searchattr_first(session, kDSStdRecordTypeComputers,
+ kDS1AttrSMBGroupRID, rid_string);
+ if (sam_record) {
+ return sam_record;
+ }
+
+ switch (rid) {
+ case DOMAIN_GROUP_RID_USERS:
+ case BUILTIN_ALIAS_RID_USERS:
+ /* The local group "staff" has a SID of S-1-5-32-545, which is
+ * the well-known SID for "Users". Additionally, the "staff"
+ * group has a RealName attribute of "Users", so this seems
+ * like a good match.
+ */
+ return sam_searchname_first(session, kDSStdRecordTypeGroups,
+ "staff");
+ case DOMAIN_GROUP_RID_GUESTS:
+ case BUILTIN_ALIAS_RID_GUESTS:
+ case DOMAIN_GROUP_RID_COMPUTERS:
+ /* The default config has "guest account = nobody", so we can
+ * use this for both the guest user and the guest group. We
+ * can't guarantee that the guest account is also a group
+ * however.
+ *
+ * Computer accounts get mapped to nobody as well because they
+ * are untrusted as far as filesystem access goes.
+ */
+ sam_record = sam_searchname_first(session,
+ kDSStdRecordTypeGroups, lp_guestaccount());
+
+ if (!sam_record) {
+ sam_record = sam_searchname_first(session,
+ kDSStdRecordTypeGroups, "nobody");
+ }
+
+ return sam_record;
+
+ case DOMAIN_GROUP_RID_ADMINS:
+ case DOMAIN_GROUP_RID_CONTROLLERS:
+ case DOMAIN_GROUP_RID_CERT_ADMINS:
+ case DOMAIN_GROUP_RID_SCHEMA_ADMINS:
+ case DOMAIN_GROUP_RID_ENTERPRISE_ADMINS:
+ case BUILTIN_ALIAS_RID_ADMINS:
+ case BUILTIN_ALIAS_RID_POWER_USERS:
+ return sam_searchname_first(session, kDSStdRecordTypeGroups,
+ "admin");
+ }
+
+ if (rid >= BASE_RID) {
+ snprintf(gid_string, sizeof(gid_string) - 1, "%u",
+ pdb_group_rid_to_gid(rid));
+
+ /* If it a generated user RID, searching won't help because
+ * we are looking for a group.
+ */
+ if (algorithmic_pdb_rid_is_user(rid)) {
+ return NULL;
+ }
+
+ sam_record = sam_searchattr_first(session,
+ kDSStdRecordTypeGroups,
+ kDS1AttrPrimaryGroupID, gid_string);
+ if (sam_record) {
+ return sam_record;
+ }
+ }
+
+ return NULL;
+}
+
+ CFDictionaryRef opendirectory_find_record_from_groupsid(
+ struct opendirectory_session *session,
+ const DOM_SID *group_sid)
+{
+ CFDictionaryRef sam_record;
+ fstring sid_string;
+
+ DOM_SID domain_sid = {0};
+ DOM_SID sam_sid = {0};
+ DOM_SID apple_group_sid = {0};
+
+ secrets_fetch_domain_sid(lp_workgroup(), &domain_sid);
+ sam_sid = *get_global_sam_sid();
+
+ sid_to_string(sid_string, group_sid);
+
+ sam_record = sam_searchattr_first(session, kDSStdRecordTypeGroups,
+ kDS1AttrSMBSID, sid_string);
+ if (sam_record) {
+ return sam_record;
+ }
+
+ if (apple_wellknown_sid(group_sid, &apple_group_sid)) {
+ sid_to_string(sid_string, &apple_group_sid);
+ sam_record = sam_searchattr_first(session,
+ kDSStdRecordTypeGroups,
+ kDS1AttrSMBSID, sid_string);
+ if (sam_record) {
+ return sam_record;
+ }
+ }
+
+ sam_record = find_record_from_groupsid_and_domsid(session,
+ &sam_sid, group_sid);
+ if (sam_record) {
+ return sam_record;
+ }
+
+ if (sid_compare(&domain_sid, &sam_sid) != 0) {
+ sam_record = find_record_from_groupsid_and_domsid(session,
+ &domain_sid, group_sid);
+ if (sam_record) {
+ return sam_record;
+ }
+ }
+
+ if (session->domain_sid_cache == NULL) {
+ void * mem_ctx = talloc_init(__FUNCTION__);
+ fill_domain_sid_cache(mem_ctx, session);
+ TALLOC_FREE(mem_ctx);
+ }
+
+ return search_domain_sid_cache(session, group_sid,
+ find_record_from_groupsid_and_domsid);
+}
+