You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
11148 lines
354 KiB
11148 lines
354 KiB
/*++
|
|
|
|
Copyright (c) 1997-1999 Microsoft Corporation
|
|
|
|
Module Name:
|
|
ds.c
|
|
|
|
Abstract:
|
|
This command server manages the topology retrieved from the DS.
|
|
|
|
The entire DS tree is checked for consistency. But only a copy of
|
|
the information about this server is sent on to the replica control
|
|
command server.
|
|
|
|
The consistency of the DS topology is verified before the info
|
|
is merged into the current working topology. N**2 scans are
|
|
prevented by using several temporary tables during the verification.
|
|
|
|
Author:
|
|
Billy J. Fuller 3-Mar-1997
|
|
Sudarshan A. Chitre 20-Aug-2001 Cleanup: Removed all code that dealt with polling
|
|
that was not being used. Finally changed the prefix
|
|
of polling code from FrsNewDs to FrsDs.
|
|
|
|
Environment
|
|
User mode winnt
|
|
|
|
--*/
|
|
|
|
#include <ntreppch.h>
|
|
#pragma hdrstop
|
|
#include <perrepsr.h>
|
|
|
|
#undef DEBSUB
|
|
#define DEBSUB "DS:"
|
|
|
|
#include <ntdsapi.h>
|
|
#include <frs.h>
|
|
#include <ntdsapip.h> // ms internal flags for DsCrackNames()
|
|
#include <ntfrsapi.h>
|
|
#include <tablefcn.h>
|
|
#include <lmaccess.h>
|
|
#include <dsrole.h>
|
|
#include <lmapibuf.h>
|
|
|
|
#ifdef SECURITY_WIN32
|
|
#include <security.h>
|
|
#else
|
|
#define SECURITY_WIN32
|
|
#include <security.h>
|
|
#undef SECURITY_WIN32
|
|
#endif
|
|
|
|
|
|
#include <winsock2.h>
|
|
|
|
//
|
|
// No reason to hold memory if all we are doing is periodically
|
|
// polling the DS to find there is no replication work to be
|
|
// performed
|
|
//
|
|
// extern BOOL MainInitSucceeded;
|
|
|
|
|
|
ULONG
|
|
ActiveChildrenHashCalc(
|
|
PVOID Buf,
|
|
PULONGLONG QKey
|
|
);
|
|
|
|
BOOL
|
|
ActiveChildrenKeyMatch(
|
|
PVOID Buf,
|
|
PVOID QKey
|
|
);
|
|
|
|
|
|
//
|
|
// This is a reference counted structure that contains two tables.
|
|
// The tables are used to look up valid partners by name or by connection
|
|
// guid.
|
|
//
|
|
// Each time we poll, we create a new struct and then swap the pointer to the
|
|
// old struct with a pointer to the new struct. We want to minimize the amount of
|
|
// time that a lock needs to be held. We only write to the tables when they are
|
|
// being created. At that time, only the local thread will access them so no
|
|
// lock needs to be held. Later, we only read form the tables so no lock needs
|
|
// to be held either. The thing that needs to be controlled by the lock is
|
|
// access to this global pointer.
|
|
//
|
|
// Before using the struct you must aquire the crit sec, increase the reference
|
|
// count, and make a local copy of the pointer. Use the local pointer copy so
|
|
// that you do not get screwed up when the pointer is swapped for a new struct.
|
|
// We also will hold the crit sec while swapping the pointers.
|
|
//
|
|
// If you keep the local pointer around, you will not need to hold the lock to
|
|
// decrement the ref count. Nobody will be writing to the stuct itself and it
|
|
// will not be cleaned up while the reference count is greater than zero. Be
|
|
// sure to use InterlockedDecrement since other threads may be touching the ref
|
|
// count at the same time.
|
|
//
|
|
// For simplicity, use ACQUIRE_VALID_PARTNER_TABLE_POINTER,
|
|
// RELEASE_VALID_PARTNER_TABLE_POINTER, and SWAP_VALID_PARTNER_TABLE_POINTER.
|
|
//
|
|
PFRS_VALID_PARTNER_TABLE_STRUCT pValidPartnerTableStruct = NULL;
|
|
CRITICAL_SECTION CritSec_pValidPartnerTableStruct;
|
|
|
|
// List of old structs to be cleaned up when ref counts hit zero.
|
|
PFRS_VALID_PARTNER_TABLE_STRUCT OldValidPartnerTableStructListHead = NULL;
|
|
CRITICAL_SECTION OldValidPartnerTableStructListHeadLock;
|
|
|
|
extern BOOL NeedNewPartnerTable;
|
|
|
|
//
|
|
// We will re-read the DS every so often (adjustable with registry)
|
|
//
|
|
ULONG DsPollingInterval;
|
|
ULONG DsPollingShortInterval;
|
|
ULONG DsPollingLongInterval;
|
|
|
|
//
|
|
// Don't use a noisy DS; wait until it settles out
|
|
//
|
|
ULONGLONG ThisChange;
|
|
ULONGLONG LastChange;
|
|
|
|
//
|
|
// Dont't bother processing the same topology again
|
|
//
|
|
ULONGLONG NextChange;
|
|
ULONGLONG ActiveChange;
|
|
|
|
//
|
|
// Try to keep the same binding forever
|
|
//
|
|
PLDAP gLdap = NULL;
|
|
HANDLE DsHandle = NULL;
|
|
PWCHAR SitesDn = NULL;
|
|
PWCHAR ServicesDn = NULL;
|
|
PWCHAR SystemDn = NULL;
|
|
PWCHAR ComputersDn = NULL;
|
|
PWCHAR DomainControllersDn = NULL;
|
|
PWCHAR DefaultNcDn = NULL;
|
|
BOOL DsBindingsAreValid = FALSE;
|
|
|
|
|
|
BOOL DsCreateSysVolsHasRun = FALSE;
|
|
|
|
//
|
|
// Globals for the comm test
|
|
//
|
|
PWCHAR DsDomainControllerName;
|
|
|
|
//
|
|
// Have we initialized the rest of the service?
|
|
//
|
|
extern BOOL MainInitHasRun;
|
|
|
|
//
|
|
// Directory and file filter lists from registry.
|
|
//
|
|
extern PWCHAR RegistryFileExclFilterList;
|
|
extern PWCHAR RegistryFileInclFilterList;
|
|
|
|
extern PWCHAR RegistryDirExclFilterList;
|
|
extern PWCHAR RegistryDirInclFilterList;
|
|
|
|
//
|
|
// Stop polling the DS
|
|
//
|
|
BOOL DsIsShuttingDown;
|
|
HANDLE DsShutDownComplete;
|
|
|
|
//
|
|
// Remember the computer's DN to save calls to GetComputerObjectName().
|
|
//
|
|
PWCHAR ComputerCachedFqdn;
|
|
|
|
PGEN_TABLE SubscriberTable = NULL;
|
|
PGEN_TABLE SetTable = NULL;
|
|
PGEN_TABLE CxtionTable = NULL;
|
|
PGEN_TABLE AllCxtionsTable = NULL;
|
|
PGEN_TABLE PartnerComputerTable = NULL;
|
|
PGEN_TABLE MemberTable = NULL;
|
|
PGEN_TABLE VolSerialNumberToDriveTable = NULL; // Mapping of VolumeSerial Number to drive.
|
|
PWCHAR MemberSearchFilter = NULL;
|
|
|
|
//
|
|
// Collect the errors encountered during polling in this buffer and write it to the eventlog at the
|
|
// end of poll.
|
|
//
|
|
|
|
PWCHAR DsPollSummaryBuf = NULL;
|
|
DWORD DsPollSummaryBufLen = 0;
|
|
DWORD DsPollSummaryMaxBufLen = 0;
|
|
|
|
//
|
|
// Role information
|
|
//
|
|
PWCHAR Roles[DsRole_RolePrimaryDomainController + 1] = {
|
|
L"DsRole_RoleStandaloneWorkstation",
|
|
L"DsRole_RoleMemberWorkstation",
|
|
L"DsRole_RoleStandaloneServer",
|
|
L"DsRole_RoleMemberServer",
|
|
L"DsRole_RoleBackupDomainController",
|
|
L"DsRole_RolePrimaryDomainController"
|
|
};
|
|
|
|
|
|
//
|
|
// Flags to passed into DsGetDcName (see sdk\inc\dsgetdc.h)
|
|
//
|
|
FLAG_NAME_TABLE DsGetDcNameFlagNameTable[] = {
|
|
{DS_FORCE_REDISCOVERY , "FORCE_REDISCOVERY " },
|
|
{DS_DIRECTORY_SERVICE_REQUIRED , "DIRECTORY_SERVICE_REQUIRED " },
|
|
{DS_DIRECTORY_SERVICE_PREFERRED , "DIRECTORY_SERVICE_PREFERRED " },
|
|
{DS_GC_SERVER_REQUIRED , "GC_SERVER_REQUIRED " },
|
|
{DS_PDC_REQUIRED , "PDC_REQUIRED " },
|
|
{DS_BACKGROUND_ONLY , "DS_BACKGROUND_ONLY " },
|
|
{DS_IP_REQUIRED , "IP_REQUIRED " },
|
|
{DS_KDC_REQUIRED , "KDC_REQUIRED " },
|
|
{DS_TIMESERV_REQUIRED , "TIMESERV_REQUIRED " },
|
|
{DS_WRITABLE_REQUIRED , "WRITABLE_REQUIRED " },
|
|
{DS_GOOD_TIMESERV_PREFERRED , "GOOD_TIMESERV_PREFERRED " },
|
|
{DS_AVOID_SELF , "AVOID_SELF " },
|
|
{DS_ONLY_LDAP_NEEDED , "ONLY_LDAP_NEEDED " },
|
|
{DS_IS_FLAT_NAME , "IS_FLAT_NAME " },
|
|
{DS_IS_DNS_NAME , "IS_DNS_NAME " },
|
|
{DS_RETURN_DNS_NAME , "RETURN_DNS_NAME " },
|
|
{DS_RETURN_FLAT_NAME , "RETURN_FLAT_NAME " },
|
|
|
|
{0, NULL}
|
|
};
|
|
|
|
//
|
|
// return flags from DsGetDCInfo() & DsGetDcName() too?
|
|
//
|
|
FLAG_NAME_TABLE DsGetDcInfoFlagNameTable[] = {
|
|
{DS_PDC_FLAG , "DCisPDCofDomain " },
|
|
{DS_GC_FLAG , "DCIsGCofForest " },
|
|
{DS_LDAP_FLAG , "ServerSupportsLDAP_Server " },
|
|
{DS_DS_FLAG , "DCSupportsDSAndIsA_DC " },
|
|
{DS_KDC_FLAG , "DCIsRunningKDCSvc " },
|
|
{DS_TIMESERV_FLAG , "DCIsRunningTimeSvc " },
|
|
{DS_CLOSEST_FLAG , "DCIsInClosestSiteToClient " },
|
|
{DS_WRITABLE_FLAG , "DCHasWritableDS " },
|
|
{DS_GOOD_TIMESERV_FLAG , "DCRunningTimeSvcWithClockHW " },
|
|
{DS_DNS_CONTROLLER_FLAG , "DCNameIsDNSName " },
|
|
{DS_DNS_DOMAIN_FLAG , "DomainNameIsDNSName " },
|
|
{DS_DNS_FOREST_FLAG , "DnsForestNameIsDNSName " },
|
|
|
|
{0, NULL}
|
|
};
|
|
|
|
|
|
//
|
|
// Flags from Options Attribute in NTDS-Connection object.
|
|
//
|
|
FLAG_NAME_TABLE CxtionOptionsFlagNameTable[] = {
|
|
{NTDSCONN_OPT_IS_GENERATED , "AutoGenCxtion " },
|
|
{NTDSCONN_OPT_TWOWAY_SYNC , "TwoWaySync " },
|
|
{NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT , "OverrideNotifyDefault " },
|
|
// {NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION , "DisableIntersiteCompress " },
|
|
// {NTDSCONN_OPT_USER_OWNED_SCHEDULE , "UserOwnedSchedule " },
|
|
{NTDSCONN_OPT_IGNORE_SCHEDULE_MASK , "IgnoreSchedOnInitSync " },
|
|
|
|
{0, NULL}
|
|
};
|
|
|
|
|
|
//
|
|
// Name strings for config node object types. NOTE: Order must match enum in frs.h
|
|
//
|
|
PWCHAR DsConfigTypeName[] = {
|
|
L" ",
|
|
L"NTDS-Connection (in)",
|
|
L"NTFRS-Member",
|
|
L"NTFRS-Replica-Set",
|
|
L"NTFRS-Settings",
|
|
L"NTDS-Settings",
|
|
L"NTFRS-Subscriber",
|
|
L"NTFRS-Subscriptions",
|
|
L"NTDS-DSA",
|
|
L"COMPUTER",
|
|
L"USER",
|
|
L"SERVER",
|
|
L"<<SERVICES_ROOT>>",
|
|
L"<<Connection (Out)>>"
|
|
};
|
|
|
|
|
|
//
|
|
// Client side ldap_connect timeout in seconds. Reg value "Ldap Bind Timeout In Seconds". Default is 30 seconds.
|
|
//
|
|
extern DWORD LdapBindTimeoutInSeconds;
|
|
|
|
|
|
/******************************************************************************
|
|
*******************************************************************************
|
|
** **
|
|
** **
|
|
** F R S _ L D A P _ S E A R C H _ C O N T E X T **
|
|
** **
|
|
** **
|
|
*******************************************************************************
|
|
******************************************************************************/
|
|
|
|
//
|
|
// Client side ldap search timeout in minutes. Reg value "Ldap Search Timeout In Minutes". Default is 10 minutes.
|
|
//
|
|
extern DWORD LdapSearchTimeoutInMinutes;
|
|
|
|
//
|
|
// Ldap client timeout structure. Value is overwritten by the value of LdapSearchTimeoutInMinutes.
|
|
//
|
|
|
|
LDAP_TIMEVAL LdapTimeout = { 10 * 60 * 60, 0 }; //Default ldap timeout value. Overridden by registry param Ldap Search Timeout Value In Minutes
|
|
|
|
#define FRS_LDAP_SEARCH_PAGESIZE 1000
|
|
|
|
typedef struct _FRS_LDAP_SEARCH_CONTEXT {
|
|
|
|
ULONG EntriesInPage; // Number of entries in the current page.
|
|
ULONG CurrentEntry; // Location of the pointer into the page.
|
|
LDAPMessage * LdapMsg; // Returned from ldap_search_ext_s()
|
|
LDAPMessage * CurrentLdapMsg; // Current entry from current page.
|
|
PWCHAR Filter; // Filter to add to the DS query.
|
|
PWCHAR BaseDn; // Dn to start the query from.
|
|
DWORD Scope; // Scope of the search.
|
|
PWCHAR * Attrs; // Attributes requested by the search.
|
|
|
|
} FRS_LDAP_SEARCH_CONTEXT, *PFRS_LDAP_SEARCH_CONTEXT;
|
|
|
|
//
|
|
// Registry Command codes for FrsDsEnumerateSysVolKeys()
|
|
//
|
|
#define REGCMD_CREATE_PRIMARY_DOMAIN (1)
|
|
#define REGCMD_CREATE_MEMBERS (2)
|
|
#define REGCMD_DELETE_MEMBERS (3)
|
|
#define REGCMD_DELETE_KEYS (4)
|
|
|
|
|
|
#define MK_ATTRS_1(_attr_, _a1) \
|
|
_attr_[0] = _a1; _attr_[1] = NULL;
|
|
|
|
#define MK_ATTRS_2(_attr_, _a1, _a2) \
|
|
_attr_[0] = _a1; _attr_[1] = _a2; _attr_[2] = NULL;
|
|
|
|
#define MK_ATTRS_3(_attr_, _a1, _a2, _a3) \
|
|
_attr_[0] = _a1; _attr_[1] = _a2; _attr_[2] = _a3; _attr_[3] = NULL;
|
|
|
|
#define MK_ATTRS_4(_attr_, _a1, _a2, _a3, _a4) \
|
|
_attr_[0] = _a1; _attr_[1] = _a2; _attr_[2] = _a3; _attr_[3] = _a4; \
|
|
_attr_[4] = NULL;
|
|
|
|
#define MK_ATTRS_5(_attr_, _a1, _a2, _a3, _a4, _a5) \
|
|
_attr_[0] = _a1; _attr_[1] = _a2; _attr_[2] = _a3; _attr_[3] = _a4; \
|
|
_attr_[4] = _a5; _attr_[5] = NULL;
|
|
|
|
#define MK_ATTRS_6(_attr_, _a1, _a2, _a3, _a4, _a5, _a6) \
|
|
_attr_[0] = _a1; _attr_[1] = _a2; _attr_[2] = _a3; _attr_[3] = _a4; \
|
|
_attr_[4] = _a5; _attr_[5] = _a6; _attr_[6] = NULL;
|
|
|
|
#define MK_ATTRS_7(_attr_, _a1, _a2, _a3, _a4, _a5, _a6, _a7) \
|
|
_attr_[0] = _a1; _attr_[1] = _a2; _attr_[2] = _a3; _attr_[3] = _a4; \
|
|
_attr_[4] = _a5; _attr_[5] = _a6; _attr_[6] = _a7; _attr_[7] = NULL;
|
|
|
|
#define MK_ATTRS_8(_attr_, _a1, _a2, _a3, _a4, _a5, _a6, _a7, _a8) \
|
|
_attr_[0] = _a1; _attr_[1] = _a2; _attr_[2] = _a3; _attr_[3] = _a4; \
|
|
_attr_[4] = _a5; _attr_[5] = _a6; _attr_[6] = _a7; _attr_[7] = _a8; \
|
|
_attr_[8] = NULL;
|
|
|
|
#define MK_ATTRS_9(_attr_, _a1, _a2, _a3, _a4, _a5, _a6, _a7, _a8, _a9) \
|
|
_attr_[0] = _a1; _attr_[1] = _a2; _attr_[2] = _a3; _attr_[3] = _a4; \
|
|
_attr_[4] = _a5; _attr_[5] = _a6; _attr_[6] = _a7; _attr_[7] = _a8; \
|
|
_attr_[8] = _a9; _attr_[9] = NULL;
|
|
|
|
|
|
//
|
|
// Merging the information from the Ds with the active replicas.
|
|
//
|
|
CRITICAL_SECTION MergingReplicasWithDs;
|
|
|
|
ULONG
|
|
FrsProcessBackupRestore(
|
|
VOID
|
|
);
|
|
|
|
RcsSetSysvolReady(
|
|
IN DWORD NewSysvolReady
|
|
);
|
|
|
|
LONG
|
|
PmInitPerfmonRegistryKeys (
|
|
VOID
|
|
);
|
|
|
|
VOID
|
|
DbgQueryDynamicConfigParams(
|
|
);
|
|
|
|
DWORD
|
|
FrsDsGetRole(
|
|
VOID
|
|
);
|
|
|
|
|
|
VOID
|
|
FrsDsAddToPollSummary3ws(
|
|
IN DWORD idsCode,
|
|
IN PWCHAR WStr1,
|
|
IN PWCHAR WStr2,
|
|
IN PWCHAR WStr3
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Add to the poll summary event log.
|
|
|
|
Arguments:
|
|
idsCode - Code of data string from string.rc
|
|
WStr1 - Argument1
|
|
WStr2 - Argument2
|
|
WStr3 - Argument3
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsAddToPollSummary3ws:"
|
|
|
|
PWCHAR ResStr = NULL;
|
|
PWCHAR tempMessage = NULL;
|
|
DWORD tempMessageLen = 0;
|
|
|
|
ResStr = FrsGetResourceStr(idsCode);
|
|
tempMessageLen = (wcslen(ResStr) - wcslen(L"%ws%ws%ws") +
|
|
wcslen(WStr1) + wcslen(WStr2) +
|
|
wcslen(WStr3) + 1) * sizeof(WCHAR);
|
|
tempMessage = FrsAlloc(tempMessageLen);
|
|
wsprintf(tempMessage, ResStr, WStr1, WStr2, WStr3);
|
|
//
|
|
// Don't want to copy the trailing null to the event log buffer or else
|
|
// the next message will not be printed.
|
|
//
|
|
FRS_DS_ADD_TO_POLL_SUMMARY(DsPollSummaryBuf, tempMessage, tempMessageLen - 2);
|
|
FrsFree(ResStr);
|
|
FrsFree(tempMessage);
|
|
return;
|
|
}
|
|
|
|
VOID
|
|
FrsDsAddToPollSummary1ws(
|
|
IN DWORD idsCode,
|
|
IN PWCHAR WStr1
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Add to the poll summary event log.
|
|
|
|
Arguments:
|
|
idsCode - Code of data string from string.rc
|
|
WStr1 - Argument1
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsAddToPollSummary1ws:"
|
|
|
|
PWCHAR ResStr = NULL;
|
|
PWCHAR tempMessage = NULL;
|
|
DWORD tempMessageLen = 0;
|
|
|
|
ResStr = FrsGetResourceStr(idsCode);
|
|
tempMessageLen = (wcslen(ResStr) - wcslen(L"%ws") +
|
|
wcslen(WStr1) + 1) * sizeof(WCHAR);
|
|
tempMessage = FrsAlloc(tempMessageLen);
|
|
wsprintf(tempMessage, ResStr, WStr1);
|
|
//
|
|
// Don't want to copy the trailing null to the event log buffer or else
|
|
// the next message will not be printed.
|
|
//
|
|
FRS_DS_ADD_TO_POLL_SUMMARY(DsPollSummaryBuf, tempMessage, tempMessageLen - 2);
|
|
FrsFree(ResStr);
|
|
FrsFree(tempMessage);
|
|
return;
|
|
}
|
|
|
|
|
|
VOID
|
|
FrsDsAddToPollSummary(
|
|
IN DWORD idsCode
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Add to the poll summary event log.
|
|
|
|
Arguments:
|
|
idsCode - Code of data string from string.rc
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsAddToPollSummary:"
|
|
|
|
PWCHAR ResStr = NULL;
|
|
|
|
ResStr = FrsGetResourceStr(idsCode);
|
|
|
|
//
|
|
// Don't want to copy the trailing null to the event log buffer or else
|
|
// the next message will not be printed.
|
|
//
|
|
FRS_DS_ADD_TO_POLL_SUMMARY(DsPollSummaryBuf, ResStr, wcslen(ResStr) * sizeof(WCHAR));
|
|
FrsFree(ResStr);
|
|
return;
|
|
}
|
|
|
|
|
|
PVOID *
|
|
FrsDsFindValues(
|
|
IN PLDAP Ldap,
|
|
IN PLDAPMessage Entry,
|
|
IN PWCHAR DesiredAttr,
|
|
IN BOOL DoBerVals
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Return the DS values for one attribute in an entry.
|
|
|
|
Arguments:
|
|
Ldap - An open, bound Ldap port.
|
|
Entry - An Ldap entry returned by Ldap_search_s()
|
|
DesiredAttr - Return values for this attribute.
|
|
DoBerVals - Return the bervals (for binary data, v.s. WCHAR data)
|
|
|
|
Return Value:
|
|
An array of char pointers that represents the values for the attribute.
|
|
The caller must free the array with LDAP_FREE_VALUES().
|
|
NULL if unsuccessful.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsFindValues:"
|
|
PWCHAR Attr; // Retrieved from an Ldap entry
|
|
BerElement *Ber; // Needed for scanning attributes
|
|
|
|
//
|
|
// Search the entry for the desired attribute
|
|
//
|
|
for (Attr = ldap_first_attribute(Ldap, Entry, &Ber);
|
|
Attr != NULL;
|
|
Attr = ldap_next_attribute(Ldap, Entry, Ber)) {
|
|
|
|
if (WSTR_EQ(DesiredAttr, Attr)) {
|
|
//
|
|
// Return the values for DesiredAttr
|
|
//
|
|
if (DoBerVals) {
|
|
return ldap_get_values_len(Ldap, Entry, Attr);
|
|
} else {
|
|
return ldap_get_values(Ldap, Entry, Attr);
|
|
}
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
|
|
|
|
PWCHAR
|
|
FrsDsFindValue(
|
|
IN PLDAP Ldap,
|
|
IN PLDAPMessage Entry,
|
|
IN PWCHAR DesiredAttr
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Return a copy of the first DS value for one attribute in an entry.
|
|
|
|
Arguments:
|
|
ldap - An open, bound ldap port.
|
|
Entry - An ldap entry returned by ldap_search_s()
|
|
DesiredAttr - Return values for this attribute.
|
|
|
|
Return Value:
|
|
A zero-terminated string or NULL if the attribute or its value
|
|
doesn't exist. The string is freed with FREE_NO_HEADER().
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsFindValue:"
|
|
PWCHAR Val;
|
|
PWCHAR *Values;
|
|
|
|
// Get ldap's array of values
|
|
Values = (PWCHAR *)FrsDsFindValues(Ldap, Entry, DesiredAttr, FALSE);
|
|
|
|
// Copy the first value (if any)
|
|
Val = (Values) ? FrsWcsDup(Values[0]) : NULL;
|
|
|
|
// Free ldap's array of values
|
|
LDAP_FREE_VALUES(Values);
|
|
|
|
return Val;
|
|
}
|
|
|
|
|
|
GUID *
|
|
FrsDsFindGuid(
|
|
IN PLDAP Ldap,
|
|
IN PLDAPMessage LdapEntry
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Return a copy of the object's guid
|
|
|
|
Arguments:
|
|
ldap - An open, bound ldap port.
|
|
Entry - An ldap entry returned by ldap_search_s()
|
|
|
|
Return Value:
|
|
The address of a guid or NULL. Free with FrsFree().
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsFindGuid:"
|
|
GUID *Guid;
|
|
PLDAP_BERVAL *Values;
|
|
|
|
// Get ldap's array of values
|
|
Values = (PLDAP_BERVAL *)FrsDsFindValues(Ldap, LdapEntry, ATTR_OBJECT_GUID, TRUE);
|
|
|
|
// Copy the first value (if any)
|
|
Guid = (Values) ? FrsDupGuid((GUID *)Values[0]->bv_val) : NULL;
|
|
|
|
// Free ldap's array of values
|
|
LDAP_FREE_BER_VALUES(Values);
|
|
|
|
return Guid;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
PSCHEDULE
|
|
FrsDsFindSchedule(
|
|
IN PLDAP Ldap,
|
|
IN PLDAPMessage LdapEntry,
|
|
OUT PULONG Len
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Return a copy of the object's schedule
|
|
|
|
Arguments:
|
|
Ldap - An open, bound ldap port.
|
|
LdapEntry - An ldap entry returned by ldap_search_s()
|
|
Len - length of schedule blob
|
|
|
|
Return Value:
|
|
The address of a schedule or NULL. Free with FrsFree().
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsFindSchedule:"
|
|
PLDAP_BERVAL *Values;
|
|
PSCHEDULE Schedule;
|
|
|
|
//
|
|
// Get ldap's array of values
|
|
//
|
|
Values = (PLDAP_BERVAL *)FrsDsFindValues(Ldap, LdapEntry, ATTR_SCHEDULE, TRUE);
|
|
if (!Values)
|
|
return NULL;
|
|
|
|
//
|
|
// Return a copy of the schedule
|
|
//
|
|
*Len = Values[0]->bv_len;
|
|
if (*Len) {
|
|
//
|
|
// Need to check if *Len == 0 as FrsAlloc asserts if called with 0 as the first parameter (prefix fix).
|
|
//
|
|
Schedule = FrsAlloc(*Len);
|
|
CopyMemory(Schedule, Values[0]->bv_val, *Len);
|
|
} else {
|
|
Schedule = NULL;
|
|
}
|
|
LDAP_FREE_BER_VALUES(Values);
|
|
return Schedule;
|
|
}
|
|
|
|
|
|
BOOL
|
|
FrsDsLdapSearch(
|
|
IN PLDAP Ldap,
|
|
IN PWCHAR Base,
|
|
IN ULONG Scope,
|
|
IN PWCHAR Filter,
|
|
IN PWCHAR Attrs[],
|
|
IN ULONG AttrsOnly,
|
|
IN LDAPMessage **Msg
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Issue the ldap ldap_search_s call, check for errors, and check for
|
|
a shutdown in progress.
|
|
|
|
Arguments:
|
|
ldap Session handle to Ldap server.
|
|
|
|
Base The distinguished name of the entry at which to start the search
|
|
|
|
Scope
|
|
LDAP_SCOPE_BASE Search the base entry only.
|
|
LDAP_SCOPE_ONELEVEL Search the base entry and all entries in the first
|
|
level below the base.
|
|
LDAP_SCOPE_SUBTREE Search the base entry and all entries in the tree
|
|
below the base.
|
|
|
|
Filter The search filter.
|
|
|
|
Attrs A null-terminated array of strings indicating the attributes
|
|
to return for each matching entry. Pass NULL to retrieve all
|
|
available attributes.
|
|
|
|
AttrsOnly A boolean value that should be zero if both attribute types
|
|
and values are to be returned, nonzero if only types are wanted.
|
|
|
|
mSG Contains the results of the search upon completion of the call.
|
|
The ldap array of values or NULL if the Base, DesiredAttr, or its
|
|
values does not exist.
|
|
The ldap array is freed with LDAP_FREE_VALUES().
|
|
|
|
Return Value:
|
|
|
|
TRUE if not shutting down.
|
|
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsLdapSearch:"
|
|
|
|
DWORD LStatus;
|
|
|
|
*Msg = NULL;
|
|
|
|
//
|
|
// Increment the DS Searches counter
|
|
//
|
|
PM_INC_CTR_SERVICE(PMTotalInst, DSSearches, 1);
|
|
|
|
//
|
|
// Issue the ldap search
|
|
//
|
|
// LStatus = ldap_search_s(Ldap, Base, Scope, Filter, Attrs, AttrsOnly, Msg);
|
|
LStatus = ldap_search_ext_s(Ldap,
|
|
Base,
|
|
Scope,
|
|
Filter,
|
|
Attrs,
|
|
AttrsOnly,
|
|
NULL,
|
|
NULL,
|
|
&LdapTimeout,
|
|
0,
|
|
Msg);
|
|
|
|
//
|
|
// Check for errors
|
|
//
|
|
if (LStatus != LDAP_SUCCESS) {
|
|
PWCHAR ldapErrorString = NULL;
|
|
DPRINT2_LS(1, ":DS: WARN - Error searching %ws for %ws;", Base, Filter, LStatus);
|
|
|
|
//
|
|
// Increment the DS Searches in Error counter
|
|
//
|
|
PM_INC_CTR_SERVICE(PMTotalInst, DSSearchesError, 1);
|
|
|
|
//
|
|
// Add to the poll summary event log.
|
|
//
|
|
ldapErrorString = ldap_err2string(LStatus);
|
|
if(ldapErrorString) {
|
|
FrsDsAddToPollSummary3ws(IDS_POLL_SUM_SEARCH_ERROR, Filter, Base,
|
|
ldapErrorString);
|
|
}
|
|
|
|
LDAP_FREE_MSG(*Msg);
|
|
return FALSE;
|
|
}
|
|
//
|
|
// Return FALSE if shutting down.
|
|
//
|
|
if (FrsIsShuttingDown || DsIsShuttingDown) {
|
|
LDAP_FREE_MSG(*Msg);
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL
|
|
FrsDsLdapSearchInit(
|
|
PLDAP ldap,
|
|
PWCHAR Base,
|
|
ULONG Scope,
|
|
PWCHAR Filter,
|
|
PWCHAR Attrs[],
|
|
ULONG AttrsOnly,
|
|
PFRS_LDAP_SEARCH_CONTEXT FrsSearchContext
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Issue the ldap_create_page_control and ldap_search_ext_s calls,
|
|
FrsDsLdapSearchInit(), and FrsDsLdapSearchNext() APIs are used to
|
|
make ldap queries and retrieve the results in paged form.
|
|
|
|
Arguments:
|
|
ldap Session handle to Ldap server.
|
|
|
|
Base The distinguished name of the entry at which to start the search.
|
|
A copy of base is kept in the context.
|
|
|
|
Scope
|
|
|
|
LDAP_SCOPE_BASE Search the base entry only.
|
|
|
|
LDAP_SCOPE_ONELEVEL Search the base entry and all entries in the first
|
|
level below the base.
|
|
|
|
LDAP_SCOPE_SUBTREE Search the base entry and all entries in the tree
|
|
below the base.
|
|
|
|
Filter The search filter. A copy of filter is kept in the context.
|
|
|
|
Attrs A null-terminated array of strings indicating the attributes
|
|
to return for each matching entry. Pass NULL to retrieve all
|
|
available attributes.
|
|
|
|
AttrsOnly A boolean value that should be zero if both attribute types
|
|
and values are to be returned, nonzero if only types are wanted.
|
|
|
|
FrsSearchContext
|
|
An opaques structure that links the FrsDsLdapSearchInit() and
|
|
FrsDsLdapSearchNext() calls together. The structure contains
|
|
the information required to retrieve query results across pages.
|
|
|
|
Return Value:
|
|
|
|
BOOL result.
|
|
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsLdapSearchInit:"
|
|
|
|
DWORD LStatus = LDAP_SUCCESS;
|
|
PLDAPControl ServerControls[2];
|
|
PLDAPControl ServerControl = NULL;
|
|
UINT i;
|
|
LDAP_BERVAL cookie1 = { 0, NULL };
|
|
|
|
FrsSearchContext->LdapMsg = NULL;
|
|
FrsSearchContext->CurrentLdapMsg = NULL;
|
|
FrsSearchContext->EntriesInPage = 0;
|
|
FrsSearchContext->CurrentEntry = 0;
|
|
|
|
FrsSearchContext->BaseDn = FrsWcsDup(Base);
|
|
FrsSearchContext->Filter = FrsWcsDup(Filter);
|
|
FrsSearchContext->Scope = Scope;
|
|
FrsSearchContext->Attrs = Attrs;
|
|
|
|
|
|
LStatus = ldap_create_page_control(ldap,
|
|
FRS_LDAP_SEARCH_PAGESIZE,
|
|
&cookie1,
|
|
FALSE, // is critical
|
|
&ServerControl
|
|
);
|
|
|
|
ServerControls[0] = ServerControl;
|
|
ServerControls[1] = NULL;
|
|
|
|
if (LStatus != LDAP_SUCCESS) {
|
|
DPRINT2_LS(2, ":DS: WARN - Error creating page control %ws for %ws;", Base, Filter, LStatus);
|
|
FrsSearchContext->BaseDn = FrsFree(FrsSearchContext->BaseDn);
|
|
FrsSearchContext->Filter = FrsFree(FrsSearchContext->Filter);
|
|
return FALSE;
|
|
}
|
|
|
|
LStatus = ldap_search_ext_s(ldap,
|
|
FrsSearchContext->BaseDn,
|
|
FrsSearchContext->Scope,
|
|
FrsSearchContext->Filter,
|
|
FrsSearchContext->Attrs,
|
|
FALSE,
|
|
ServerControls,
|
|
NULL,
|
|
&LdapTimeout,
|
|
0,
|
|
&FrsSearchContext->LdapMsg);
|
|
|
|
ldap_control_free(ServerControl);
|
|
|
|
if (LStatus == LDAP_SUCCESS) {
|
|
FrsSearchContext->EntriesInPage = ldap_count_entries(ldap, FrsSearchContext->LdapMsg);
|
|
FrsSearchContext->CurrentEntry = 0;
|
|
}
|
|
|
|
|
|
if (LStatus != LDAP_SUCCESS) {
|
|
DPRINT2_LS(2, ":DS: WARN - Error searching %ws for %ws;", Base, Filter, LStatus);
|
|
FrsSearchContext->BaseDn = FrsFree(FrsSearchContext->BaseDn);
|
|
FrsSearchContext->Filter = FrsFree(FrsSearchContext->Filter);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
PLDAPMessage
|
|
FrsDsLdapSearchGetNextEntry(
|
|
PLDAP ldap,
|
|
PFRS_LDAP_SEARCH_CONTEXT FrsSearchContext
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Get the next entry form the current page of the results
|
|
returned. This call is only made if there is a entry
|
|
in the current page.
|
|
|
|
Arguments:
|
|
ldap Session handle to Ldap server.
|
|
|
|
FrsSearchContext
|
|
An opaques structure that links the FrsDsLdapSearchInit() and
|
|
FrsDsLdapSearchNext() calls together. The structure contains
|
|
the information required to retrieve query results across pages.
|
|
|
|
Return Value:
|
|
|
|
The first or the next entry from the current page.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsLdapSearchGetNextEntry:"
|
|
|
|
FrsSearchContext->CurrentEntry += 1;
|
|
if ( FrsSearchContext->CurrentEntry == 1 ) {
|
|
FrsSearchContext->CurrentLdapMsg = ldap_first_entry(ldap ,FrsSearchContext->LdapMsg);
|
|
} else {
|
|
FrsSearchContext->CurrentLdapMsg = ldap_next_entry(ldap ,FrsSearchContext->CurrentLdapMsg);
|
|
}
|
|
|
|
return FrsSearchContext->CurrentLdapMsg;
|
|
}
|
|
|
|
DWORD
|
|
FrsDsLdapSearchGetNextPage(
|
|
PLDAP ldap,
|
|
PFRS_LDAP_SEARCH_CONTEXT FrsSearchContext
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Get the next page from the results returned by ldap_search_ext_s..
|
|
|
|
Arguments:
|
|
ldap Session handle to Ldap server.
|
|
|
|
FrsSearchContext
|
|
An opaques structure that links the FrsDsLdapSearchInit() and
|
|
FrsDsLdapSearchNext() calls together. The structure contains
|
|
the information required to retrieve query results across pages.
|
|
|
|
Return Value:
|
|
WINSTATUS
|
|
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsLdapSearchGetNextPage:"
|
|
|
|
DWORD LStatus = LDAP_SUCCESS;
|
|
LDAP_BERVAL * CurrCookie = NULL;
|
|
PLDAPControl * CurrControls = NULL;
|
|
ULONG retcode = 0;
|
|
ULONG TotalEntries = 0;
|
|
PLDAPControl ServerControls[2];
|
|
PLDAPControl ServerControl= NULL;
|
|
|
|
|
|
|
|
// Get the server control from the message, and make a new control with the cookie from the server
|
|
LStatus = ldap_parse_result(ldap, FrsSearchContext->LdapMsg, &retcode,NULL,NULL,NULL,&CurrControls,FALSE);
|
|
LDAP_FREE_MSG(FrsSearchContext->LdapMsg);
|
|
|
|
if (LStatus != LDAP_SUCCESS) {
|
|
DPRINT2_LS(2, ":DS: WARN - Error in ldap_parse_result %ws for %ws;", FrsSearchContext->BaseDn, FrsSearchContext->Filter, LStatus);
|
|
return LdapMapErrorToWin32(LStatus);
|
|
}
|
|
|
|
LStatus = ldap_parse_page_control(ldap, CurrControls, &TotalEntries, &CurrCookie);
|
|
|
|
if (LStatus != LDAP_SUCCESS) {
|
|
DPRINT2_LS(2, ":DS: WARN - Error in ldap_parse_page_control %ws for %ws;", FrsSearchContext->BaseDn, FrsSearchContext->Filter, LStatus);
|
|
return LdapMapErrorToWin32(LStatus);
|
|
}
|
|
|
|
if ( CurrCookie->bv_len == 0 && CurrCookie->bv_val == 0 ) {
|
|
LStatus = LDAP_CONTROL_NOT_FOUND;
|
|
ldap_controls_free(CurrControls);
|
|
ber_bvfree(CurrCookie);
|
|
return LdapMapErrorToWin32(LStatus);
|
|
}
|
|
|
|
|
|
LStatus = ldap_create_page_control(ldap,
|
|
FRS_LDAP_SEARCH_PAGESIZE,
|
|
CurrCookie,
|
|
FALSE,
|
|
&ServerControl);
|
|
|
|
ServerControls[0] = ServerControl;
|
|
ServerControls[1] = NULL;
|
|
|
|
ldap_controls_free(CurrControls);
|
|
CurrControls = NULL;
|
|
ber_bvfree(CurrCookie);
|
|
CurrCookie = NULL;
|
|
|
|
if (LStatus != LDAP_SUCCESS) {
|
|
DPRINT2_LS(2, ":DS: WARN - Error in ldap_parse_page_control %ws for %ws;", FrsSearchContext->BaseDn, FrsSearchContext->Filter, LStatus);
|
|
return LdapMapErrorToWin32(LStatus);
|
|
}
|
|
|
|
// continue the search with the new cookie
|
|
LStatus = ldap_search_ext_s(ldap,
|
|
FrsSearchContext->BaseDn,
|
|
FrsSearchContext->Scope,
|
|
FrsSearchContext->Filter,
|
|
FrsSearchContext->Attrs,
|
|
FALSE,
|
|
ServerControls,
|
|
NULL,
|
|
&LdapTimeout,
|
|
0,
|
|
&FrsSearchContext->LdapMsg);
|
|
|
|
ldap_control_free(ServerControl);
|
|
|
|
//
|
|
// LDAP_CONTROL_NOT_FOUND means that we have reached the end of the search results
|
|
//
|
|
if ( (LStatus != LDAP_SUCCESS) && (LStatus != LDAP_CONTROL_NOT_FOUND) ) {
|
|
DPRINT2_LS(2, ":DS: WARN - Error searching %ws for %ws;", FrsSearchContext->BaseDn, FrsSearchContext->Filter, LStatus);
|
|
|
|
}
|
|
|
|
if (LStatus == LDAP_SUCCESS) {
|
|
FrsSearchContext->EntriesInPage = ldap_count_entries(ldap, FrsSearchContext->LdapMsg);
|
|
FrsSearchContext->CurrentEntry = 0;
|
|
|
|
}
|
|
|
|
return LdapMapErrorToWin32(LStatus);
|
|
}
|
|
|
|
PLDAPMessage
|
|
FrsDsLdapSearchNext(
|
|
PLDAP ldap,
|
|
PFRS_LDAP_SEARCH_CONTEXT FrsSearchContext
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Get the next entry form the current page of the results
|
|
returned or from the next page if we are at the end of the.
|
|
current page.
|
|
|
|
Arguments:
|
|
ldap Session handle to Ldap server.
|
|
|
|
FrsSearchContext
|
|
An opaques structure that links the FrsDsLdapSearchInit() and
|
|
FrsDsLdapSearchNext() calls together. The structure contains
|
|
the information required to retrieve query results across pages.
|
|
|
|
Return Value:
|
|
|
|
The next entry on this page or the first entry from the next page.
|
|
NULL if there are no more entries to return.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsLdapSearchNext:"
|
|
|
|
DWORD WStatus = ERROR_SUCCESS;
|
|
PLDAPMessage NextEntry = NULL;
|
|
|
|
if (FrsSearchContext->EntriesInPage > FrsSearchContext->CurrentEntry )
|
|
{
|
|
// return the next entry from the current page
|
|
return FrsDsLdapSearchGetNextEntry(ldap, FrsSearchContext);
|
|
}
|
|
else
|
|
{
|
|
// see if there are more pages of results to get
|
|
WStatus = FrsDsLdapSearchGetNextPage(ldap, FrsSearchContext);
|
|
if (WStatus == ERROR_SUCCESS)
|
|
{
|
|
return FrsDsLdapSearchGetNextEntry(ldap, FrsSearchContext);
|
|
}
|
|
}
|
|
|
|
return NextEntry;
|
|
}
|
|
|
|
VOID
|
|
FrsDsLdapSearchClose(
|
|
PFRS_LDAP_SEARCH_CONTEXT FrsSearchContext
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
The search is complete. Free the elemetns of the context and reset
|
|
them so the same context can be used for another search.
|
|
|
|
Arguments:
|
|
|
|
FrsSearchContext
|
|
An opaques structure that links the FrsDsLdapSearchInit() and
|
|
FrsDsLdapSearchNext() calls together. The structure contains
|
|
the information required to retrieve query results across pages.
|
|
|
|
Return Value:
|
|
|
|
NONE
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsLdapSearchClose:"
|
|
|
|
FrsSearchContext->EntriesInPage = 0;
|
|
FrsSearchContext->CurrentEntry = 0;
|
|
|
|
FrsSearchContext->BaseDn = FrsFree(FrsSearchContext->BaseDn);
|
|
FrsSearchContext->Filter = FrsFree(FrsSearchContext->Filter);
|
|
LDAP_FREE_MSG(FrsSearchContext->LdapMsg);
|
|
}
|
|
|
|
|
|
PWCHAR *
|
|
FrsDsGetValues(
|
|
IN PLDAP Ldap,
|
|
IN PWCHAR Base,
|
|
IN PWCHAR DesiredAttr
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Return all of the DS values for one attribute in an object.
|
|
|
|
Arguments:
|
|
ldap - An open, bound ldap port.
|
|
Base - The "pathname" of a DS object.
|
|
DesiredAttr - Return values for this attribute.
|
|
|
|
Return Value:
|
|
The ldap array of values or NULL if the Base, DesiredAttr, or its values
|
|
does not exist. The ldap array is freed with LDAP_FREE_VALUES().
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsGetValues:"
|
|
|
|
PLDAPMessage Msg = NULL; // Opaque stuff from ldap subsystem
|
|
PWCHAR *Values; // Array of values for desired attribute
|
|
|
|
//
|
|
// Search Base for all of this attribute + values (objectCategory=*)
|
|
//
|
|
if (!FrsDsLdapSearch(Ldap, Base, LDAP_SCOPE_BASE, CATEGORY_ANY,
|
|
NULL, 0, &Msg)) {
|
|
return NULL;
|
|
}
|
|
//
|
|
// Return the values for the desired attribute
|
|
//
|
|
Values = (PWCHAR *)FrsDsFindValues(Ldap,
|
|
ldap_first_entry(Ldap, Msg),
|
|
DesiredAttr,
|
|
FALSE);
|
|
LDAP_FREE_MSG(Msg);
|
|
return Values;
|
|
}
|
|
|
|
PWCHAR
|
|
FrsDsExtendDn(
|
|
IN PWCHAR Dn,
|
|
IN PWCHAR Cn
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Extend an existing DN with a new CN= component.
|
|
|
|
Arguments:
|
|
Dn - distinguished name
|
|
Cn - common name
|
|
|
|
Return Value:
|
|
CN=Cn,Dn
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsExtendDn:"
|
|
|
|
ULONG Len;
|
|
PWCHAR NewDn;
|
|
|
|
if ((Dn == NULL) || (Cn == NULL)) {
|
|
return NULL;
|
|
}
|
|
|
|
Len = wcslen(L"CN=,") + wcslen(Dn) + wcslen(Cn) + 1;
|
|
NewDn = (PWCHAR)FrsAlloc(Len * sizeof(WCHAR));
|
|
wcscpy(NewDn, L"CN=");
|
|
wcscat(NewDn, Cn);
|
|
wcscat(NewDn, L",");
|
|
wcscat(NewDn, Dn);
|
|
return NewDn;
|
|
}
|
|
|
|
|
|
PWCHAR
|
|
FrsDsExtendDnOu(
|
|
IN PWCHAR Dn,
|
|
IN PWCHAR Ou
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Extend an existing DN with a new OU= component.
|
|
|
|
Arguments:
|
|
Dn - distinguished name
|
|
Ou - orginizational name
|
|
|
|
Return Value:
|
|
OU=Ou,Dn
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsExtendDnOu:"
|
|
ULONG Len;
|
|
PWCHAR NewDn;
|
|
|
|
if ((Dn == NULL) || (Ou == NULL)) {
|
|
return NULL;
|
|
}
|
|
|
|
Len = wcslen(L"OU=,") + wcslen(Dn) + wcslen(Ou) + 1;
|
|
NewDn = (PWCHAR)FrsAlloc(Len * sizeof(WCHAR));
|
|
wcscpy(NewDn, L"OU=");
|
|
wcscat(NewDn, Ou);
|
|
wcscat(NewDn, L",");
|
|
wcscat(NewDn, Dn);
|
|
return NewDn;
|
|
}
|
|
|
|
|
|
PWCHAR
|
|
FrsDsMakeRdn(
|
|
IN PWCHAR DN
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Extract the base component (relative distinguished name) from a
|
|
distinguished name. The distinguished name is assumed to be in
|
|
DS format (CN=xyz,CN=next one,...). In this case, the returned
|
|
RDN is "xyz".
|
|
|
|
Arguments:
|
|
DN - distinguished name
|
|
|
|
Return Value:
|
|
A zero-terminated string. The string is freed with FrsFree().
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsMakeRdn:"
|
|
DWORD RDNLen;
|
|
PWCHAR RDN;
|
|
|
|
if (DN == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
// Skip the first CN=; if any
|
|
//
|
|
RDN = wcsstr(DN, L"cn=");
|
|
if (RDN == DN) {
|
|
DN += 3;
|
|
}
|
|
|
|
// Return the string up to the first delimiter or EOS
|
|
RDNLen = wcscspn(DN, L",");
|
|
RDN = (PWCHAR)FrsAlloc(sizeof(WCHAR) * (RDNLen + 1));
|
|
wcsncpy(RDN, DN, RDNLen);
|
|
RDN[RDNLen] = L'\0';
|
|
|
|
return _wcsupr(RDN);
|
|
}
|
|
|
|
|
|
|
|
VOID
|
|
FrsDsCloseDs(
|
|
VOID
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Unbind from the DS.
|
|
|
|
Arguments:
|
|
None.
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsCloseDs:"
|
|
DsBindingsAreValid = FALSE;
|
|
if (gLdap) {
|
|
ldap_unbind_s(gLdap);
|
|
gLdap = NULL;
|
|
}
|
|
if (HANDLE_IS_VALID(DsHandle)) {
|
|
DsUnBind(&DsHandle);
|
|
DsHandle = NULL;
|
|
}
|
|
SitesDn = FrsFree(SitesDn);
|
|
ServicesDn = FrsFree(ServicesDn);
|
|
SystemDn = FrsFree(SystemDn);
|
|
ComputersDn = FrsFree(ComputersDn);
|
|
DomainControllersDn = FrsFree(DomainControllersDn);
|
|
DefaultNcDn = FrsFree(DefaultNcDn);
|
|
}
|
|
|
|
|
|
DWORD
|
|
FrsDsGetDcInfo(
|
|
IN PDOMAIN_CONTROLLER_INFO *DcInfo,
|
|
IN DWORD Flags
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Open and bind to a dc
|
|
|
|
Arguments:
|
|
DcInfo - Dc Info
|
|
Flags - DsGetDcName(Flags)
|
|
|
|
Return Value:
|
|
DsGetDcName
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsGetDcInfo:"
|
|
DWORD WStatus;
|
|
PWCHAR DcName;
|
|
DWORD InfoFlags;
|
|
CHAR FlagBuffer[220];
|
|
|
|
|
|
FrsFlagsToStr(Flags, DsGetDcNameFlagNameTable, sizeof(FlagBuffer), FlagBuffer);
|
|
DPRINT2(4, ":DS: DsGetDcName (%08x) Flags [%s]\n", Flags, FlagBuffer);
|
|
|
|
|
|
WStatus = DsGetDcName(NULL, // Computer to remote to
|
|
NULL, // Domain - use our own
|
|
NULL, // Domain Guid
|
|
NULL, // Site Guid
|
|
Flags,
|
|
DcInfo); // Return info
|
|
|
|
CLEANUP1_WS(0, ":DS: ERROR - Could not get DC Info for %ws;",
|
|
ComputerName, WStatus, RETURN);
|
|
|
|
DcName = (*DcInfo)->DomainControllerName;
|
|
|
|
FrsFlagsToStr(Flags, DsGetDcNameFlagNameTable, sizeof(FlagBuffer), FlagBuffer);
|
|
DPRINT2(4, ":DS: DcInfo (flags are %08x) Flags [%s]\n", Flags, FlagBuffer);
|
|
|
|
DPRINT1(4, ":DS: DomainControllerName : %ws\n", DcName);
|
|
DPRINT1(4, ":DS: DomainControllerAddress: %ws\n", (*DcInfo)->DomainControllerAddress);
|
|
DPRINT1(4, ":DS: DomainControllerType : %08x\n",(*DcInfo)->DomainControllerAddressType);
|
|
DPRINT1(4, ":DS: DomainName : %ws\n", (*DcInfo)->DomainName);
|
|
DPRINT1(4, ":DS: DnsForestName : %ws\n", (*DcInfo)->DnsForestName);
|
|
DPRINT1(4, ":DS: DcSiteName : %ws\n", (*DcInfo)->DcSiteName);
|
|
DPRINT1(4, ":DS: ClientSiteName : %ws\n", (*DcInfo)->ClientSiteName);
|
|
|
|
InfoFlags = (*DcInfo)->Flags;
|
|
FrsFlagsToStr(InfoFlags, DsGetDcInfoFlagNameTable, sizeof(FlagBuffer), FlagBuffer);
|
|
DPRINT2(4, ":DS: InfoFlags : %08x Flags [%s]\n",InfoFlags, FlagBuffer);
|
|
|
|
DsDomainControllerName = FrsFree(DsDomainControllerName);
|
|
DsDomainControllerName = FrsWcsDup(DcName);
|
|
|
|
//
|
|
// DCs should bind to the local DS to avoid ACL problems.
|
|
//
|
|
if (IsADc && DcName && (wcslen(DcName) > 2) &&
|
|
_wcsnicmp(&DcName[2], ComputerName, wcslen(ComputerName))) {
|
|
|
|
DPRINT3(0, ":DS: ERROR - The DC %ws is using the DS on DC %ws "
|
|
"Some of the information in the DS"
|
|
" may be unavailable to %ws; possibly disabling "
|
|
"replication with some partners.\n",
|
|
ComputerName, &DcName[2], ComputerName);
|
|
}
|
|
|
|
RETURN:
|
|
return WStatus;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
VOID
|
|
FrsDsRegisterSpn(
|
|
IN PLDAP Ldap,
|
|
IN PCONFIG_NODE Computer
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Register the NtFrs SPN so that authenticated RPC calls can
|
|
use SPN/FQDN as the target principal name
|
|
|
|
Arguments:
|
|
Computer - Computer node from the ds
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsRegisterSpn:"
|
|
DWORD WStatus;
|
|
PWCHAR Spn = NULL;
|
|
PWCHAR SpnPrefix= NULL;
|
|
PWCHAR *SpnList = NULL;
|
|
DWORD SpnNum = 0;
|
|
static BOOL RegisteredSpn = FALSE;
|
|
|
|
//
|
|
// No Ds binding or no computer or already registered
|
|
//
|
|
if (RegisteredSpn ||
|
|
(ComputerDnsName[0] == L'\0') ||
|
|
!DsBindingsAreValid ||
|
|
!Computer ||
|
|
!Computer->Dn) {
|
|
return;
|
|
}
|
|
//
|
|
// Register the NtFrs SPN so that authenticated RPC calls can
|
|
// use SPN/FQDN as the target principal name
|
|
//
|
|
Spn = FrsAlloc((wcslen(ComputerDnsName) + wcslen(SERVICE_PRINCIPAL_NAME) + 2) * sizeof(WCHAR));
|
|
wcscpy(Spn, SERVICE_PRINCIPAL_NAME);
|
|
wcscat(Spn, L"/");
|
|
wcscat(Spn, ComputerDnsName);
|
|
|
|
SpnPrefix = FrsAlloc((wcslen(SERVICE_PRINCIPAL_NAME) + 1) * sizeof(WCHAR));
|
|
wcscpy(SpnPrefix, SERVICE_PRINCIPAL_NAME);
|
|
|
|
SpnList = FrsDsGetValues(Ldap, Computer->Dn, ATTR_SERVICE_PRINCIPAL_NAME);
|
|
|
|
SpnNum=0;
|
|
while ((SpnList != NULL)&& (SpnList[SpnNum] != NULL)) {
|
|
DPRINT2(5, "Spn list from DS[%d] = %ws\n", SpnNum, SpnList[SpnNum]);
|
|
if (!_wcsicmp(SpnList[SpnNum], Spn)) {
|
|
// Spn found for NtFrs.
|
|
DPRINT1(4, "SPN already registered for Ntfrs: %ws\n", SpnList[SpnNum]);
|
|
RegisteredSpn = TRUE;
|
|
} else if (!_wcsnicmp(SpnList[SpnNum], SpnPrefix, wcslen(SpnPrefix))) {
|
|
//
|
|
// An older SPN exists. Delete it.
|
|
//
|
|
DPRINT1(4, "Deleting stale SPN for Ntfrs: %ws\n", SpnList[SpnNum]);
|
|
|
|
WStatus = DsWriteAccountSpn(DsHandle, DS_SPN_DELETE_SPN_OP, Computer->Dn, 1, &SpnList[SpnNum]);
|
|
if (!WIN_SUCCESS(WStatus)) {
|
|
DPRINT2_WS(1, "WARN - Delete DsWriteAccountSpn(%ws, %ws);", SpnList[SpnNum], Computer->Dn, WStatus);
|
|
} else {
|
|
DPRINT2(5, "Delete DsWriteAccountSpn(%ws, %ws); success\n", SpnList[SpnNum], Computer->Dn);
|
|
}
|
|
}
|
|
++SpnNum;
|
|
}
|
|
|
|
if (!RegisteredSpn) {
|
|
|
|
DPRINT1(4, "Registering SPN for Ntfrs; %ws\n", Spn);
|
|
WStatus = DsWriteAccountSpn(DsHandle, DS_SPN_ADD_SPN_OP, Computer->Dn, 1, &Spn);
|
|
if (!WIN_SUCCESS(WStatus)) {
|
|
DPRINT2_WS(1, "WARN - Add DsWriteAccountSpn(%ws, %ws);", Spn, Computer->Dn, WStatus);
|
|
} else {
|
|
DPRINT2(5, "Add DsWriteAccountSpn(%ws, %ws); success\n", Spn, Computer->Dn);
|
|
RegisteredSpn = TRUE;
|
|
}
|
|
}
|
|
|
|
FrsFree(Spn);
|
|
FrsFree(SpnPrefix);
|
|
|
|
//
|
|
// Free ldap's array of values
|
|
//
|
|
LDAP_FREE_VALUES(SpnList);
|
|
|
|
}
|
|
|
|
|
|
BOOL
|
|
FrsDsBindDs(
|
|
IN DWORD Flags
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Open and bind to a domain controller.
|
|
|
|
Arguments:
|
|
Flags - For FrsDsGetDcInfo()
|
|
|
|
Return Value:
|
|
None. Sets global handles for FrsDsOpenDs().
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsBindDs:"
|
|
DWORD WStatus;
|
|
DWORD LStatus = LDAP_SUCCESS;
|
|
PWCHAR DcAddr;
|
|
PWCHAR DcName;
|
|
PWCHAR DcDnsName;
|
|
BOOL Bound = FALSE;
|
|
PDOMAIN_CONTROLLER_INFO DcInfo = NULL;
|
|
struct l_timeval Timeout;
|
|
|
|
#define MAX_DC_NAMELIST 8
|
|
ULONG NameListx, i;
|
|
PWCHAR NameList[MAX_DC_NAMELIST];
|
|
ULONG ulOptions;
|
|
|
|
|
|
//
|
|
// Bind to the local DS if this computer is a DC
|
|
//
|
|
gLdap = NULL;
|
|
if (IsADc) {
|
|
DcAddr = NULL;
|
|
DcName = ComputerName;
|
|
DcDnsName = ComputerDnsName;
|
|
} else {
|
|
//
|
|
// Not a DC; find any DC for this domain
|
|
//
|
|
WStatus = FrsDsGetDcInfo(&DcInfo, Flags);
|
|
CLEANUP2_WS(0, ":DS: ERROR - FrsDsGetDcInfo(%08x, %ws);",
|
|
Flags, ComputerName, WStatus, CLEANUP);
|
|
|
|
//
|
|
// Binding address
|
|
//
|
|
DcAddr = DcInfo->DomainControllerAddress;
|
|
DcName = NULL;
|
|
DcDnsName = DcInfo->DomainControllerName;
|
|
}
|
|
FRS_ASSERT(DcDnsName || DcName || DcAddr);
|
|
|
|
//
|
|
// Open the ldap server using various forms of the DC's name
|
|
//
|
|
NameListx = 0;
|
|
if (DcDnsName &&
|
|
(wcslen(DcDnsName) > 2) && DcDnsName[0] == L'\\' && DcDnsName[1] == L'\\') {
|
|
|
|
// Trim the "\\"
|
|
NameList[NameListx++] = DcDnsName + 2;
|
|
}
|
|
|
|
|
|
if (DcAddr &&
|
|
(wcslen(DcAddr) > 2) && DcAddr[0] == L'\\' && DcAddr[1] == L'\\') {
|
|
|
|
// Trim the "\\"
|
|
NameList[NameListx++] = DcAddr + 2;
|
|
}
|
|
|
|
NameList[NameListx++] = DcDnsName;
|
|
NameList[NameListx++] = DcName;
|
|
NameList[NameListx++] = DcAddr;
|
|
|
|
FRS_ASSERT(NameListx <= MAX_DC_NAMELIST);
|
|
|
|
|
|
ulOptions = PtrToUlong(LDAP_OPT_ON);
|
|
Timeout.tv_sec = LdapBindTimeoutInSeconds;
|
|
Timeout.tv_usec = 0;
|
|
|
|
for (i=0; i<NameListx; i++) {
|
|
if (NameList[i] != NULL) {
|
|
|
|
//
|
|
// if ldap_open is called with a server name the api will call DsGetDcName
|
|
// passing the server name as the domainname parm...bad, because
|
|
// DsGetDcName will make a load of DNS queries based on the server name,
|
|
// it is designed to construct these queries from a domain name...so all
|
|
// these queries will be bogus, meaning they will waste network bandwidth,
|
|
// time to fail, and worst case cause expensive on demand links to come up
|
|
// as referrals/forwarders are contacted to attempt to resolve the bogus
|
|
// names. By setting LDAP_OPT_AREC_EXCLUSIVE to on using ldap_set_option
|
|
// after the ldap_init but before any other operation using the ldap
|
|
// handle from ldap_init, the delayed connection setup will not call
|
|
// DsGetDcName, just gethostbyname, or if an IP is passed, the ldap client
|
|
// will detect that and use the address directly.
|
|
//
|
|
// gLdap = ldap_open(NameList[i], LDAP_PORT);
|
|
gLdap = ldap_init(NameList[i], LDAP_PORT);
|
|
if (gLdap != NULL) {
|
|
ldap_set_option(gLdap, LDAP_OPT_AREC_EXCLUSIVE, &ulOptions);
|
|
LStatus = ldap_connect(gLdap, &Timeout);
|
|
|
|
if (LStatus != LDAP_SUCCESS) {
|
|
DPRINT1_LS(1, ":DS: WARN - ldap_connect(%ws);", NameList[i], LStatus);
|
|
ldap_unbind_s(gLdap);
|
|
gLdap = NULL;
|
|
} else {
|
|
//
|
|
// Successfully connected.
|
|
//
|
|
DPRINT1(5, ":DS: ldap_connect(%ws) succeeded\n", NameList[i]);
|
|
break;
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Whatever it is, we can't find it.
|
|
//
|
|
if (!gLdap) {
|
|
// DPRINT3_WS(0, ":DS: ERROR - ldap_open(DNS %ws, BIOS %ws, IP %ws);",
|
|
// DcDnsName, DcName, DcAddr, WStatus);
|
|
DPRINT3_LS(0, ":DS: ERROR - ldap_init(DNS %ws, BIOS %ws, IP %ws);",
|
|
DcDnsName, DcName, DcAddr, LStatus);
|
|
goto CLEANUP;
|
|
}
|
|
|
|
//
|
|
// Bind to the ldap server
|
|
//
|
|
LStatus = ldap_bind_s(gLdap, NULL, NULL, LDAP_AUTH_NEGOTIATE);
|
|
CLEANUP_LS(0, ":DS: ERROR - Binding to DS.", LStatus, CLEANUP);
|
|
|
|
//
|
|
// Bind to the Ds (for various Ds calls such as DsCrackName())
|
|
//
|
|
|
|
NameListx = 0;
|
|
NameList[NameListx++] = DcDnsName;
|
|
NameList[NameListx++] = DcName;
|
|
NameList[NameListx++] = DcAddr;
|
|
|
|
FRS_ASSERT(NameListx <= MAX_DC_NAMELIST);
|
|
|
|
WStatus = ERROR_RETRY;
|
|
for (i=0; i<NameListx; i++) {
|
|
if (NameList[i] != NULL) {
|
|
WStatus = DsBind(NameList[i], NULL, &DsHandle);
|
|
if (!WIN_SUCCESS(WStatus)) {
|
|
DsHandle = NULL;
|
|
DPRINT1_WS(1, ":DS: WARN - DsBind(%ws);", NameList[i], WStatus);
|
|
} else {
|
|
DPRINT1(5, ":DS: DsBind(%ws) succeeded\n", NameList[i]);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Whatever it is, we can't find it
|
|
//
|
|
CLEANUP3_WS(0, ":DS: ERROR - DsBind(DNS %ws, BIOS %ws, IP %ws);",
|
|
DcDnsName, DcName, DcAddr, WStatus, CLEANUP);
|
|
|
|
//
|
|
// SUCCESS
|
|
//
|
|
Bound = TRUE;
|
|
|
|
CLEANUP:
|
|
//
|
|
// Cleanup
|
|
//
|
|
if (!Bound) {
|
|
//
|
|
// Close the connection to release resources if the above failed.
|
|
//
|
|
if (gLdap) {
|
|
ldap_unbind_s(gLdap);
|
|
gLdap = NULL;
|
|
}
|
|
}
|
|
|
|
if (DcInfo) {
|
|
NetApiBufferFree(DcInfo);
|
|
DcInfo = NULL;
|
|
}
|
|
|
|
return Bound;
|
|
}
|
|
|
|
|
|
BOOL
|
|
FrsDsOpenDs(
|
|
VOID
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Open and bind to a primary domain controller. The DN of the
|
|
sites container is a sideeffect.
|
|
|
|
Arguments:
|
|
DefaultDn
|
|
|
|
Return Value:
|
|
Bound ldap structure or NULL
|
|
|
|
Sets the following globals -
|
|
|
|
SitesDn
|
|
ServicesDn
|
|
SystemDn
|
|
ComputersDn
|
|
DomainControllersDn
|
|
DefaultNcDn
|
|
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsOpenDs:"
|
|
DWORD WStatus;
|
|
DWORD LStatus;
|
|
DWORD NumVals;
|
|
PWCHAR Config;
|
|
PLDAPMessage LdapEntry;
|
|
PLDAPMessage LdapMsg = NULL;
|
|
PWCHAR *Values = NULL;
|
|
PWCHAR Attrs[3];
|
|
|
|
//
|
|
// Time to clean up and exit
|
|
//
|
|
if (FrsIsShuttingDown || DsIsShuttingDown) {
|
|
goto ERROR_BINDING;
|
|
}
|
|
|
|
//
|
|
// Use existing bindings if possible
|
|
//
|
|
if (DsBindingsAreValid) {
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// The previous poll might have set DsBindingsAreValid to FALSE because one
|
|
// of the handle became invalid. In that case the other handles still need to be
|
|
// closed to prevent leak.
|
|
//
|
|
FrsDsCloseDs();
|
|
|
|
//
|
|
// Increment the DS Bindings counter
|
|
//
|
|
PM_INC_CTR_SERVICE(PMTotalInst, DSBindings, 1);
|
|
|
|
//
|
|
// Bind to a DS.
|
|
//
|
|
// Note the behavior of DsGetDcName for the following four flag combinations.
|
|
//
|
|
// DS_BACKGROUND_ONLY (as of 10/10/99)
|
|
// DS_FORCE_REDISCOVERY
|
|
// Zero Zero Netlogon will attempt to satisfy the request via
|
|
// cached info, negative cache entries will be
|
|
// returned if they are less than 5 minutes old.
|
|
// If it can't it will do a full discovery (dns
|
|
// queries, udp pings, poss netbt queries,
|
|
// mailslot datagrams, etc)
|
|
//
|
|
// Zero One Netlogon will do a full discovery.
|
|
//
|
|
// One Zero Netlogon will satisfy from the cache, unless the
|
|
// backoff routine allows for a retry at this time
|
|
// *and* the cache is insufficient.
|
|
//
|
|
// One One The DS_BACKGROUND_ONY flag is ignored, treated
|
|
// as a FORCE call.
|
|
//
|
|
if (!FrsDsBindDs(DS_DIRECTORY_SERVICE_REQUIRED |
|
|
DS_WRITABLE_REQUIRED |
|
|
DS_BACKGROUND_ONLY)) {
|
|
//
|
|
// Flush the cache and try again
|
|
//
|
|
DPRINT(1, ":DS: WARN - FrsDsBindDs(no force) failed\n");
|
|
//
|
|
// Because of the use of Dial-up lines at sites without a DC we don't
|
|
// want to use DS_FORCE_REDISCOVERY since that will defeat the generic
|
|
// DC discovery backoff algorithm thus causing FRS to constantly bring
|
|
// up the line. Bug 412620.
|
|
//FrsDsCloseDs(); // close ldap handle before doing reopen.
|
|
//if (!FrsDsBindDs(DS_DIRECTORY_SERVICE_REQUIRED |
|
|
// DS_WRITABLE_REQUIRED |
|
|
// DS_FORCE_REDISCOVERY)) {
|
|
// DPRINT(1, ":DS: WARN - FrsDsBindDs(force) failed\n");
|
|
goto ERROR_BINDING;
|
|
//}
|
|
}
|
|
DPRINT(4, ":DS: FrsDsBindDs() succeeded\n");
|
|
|
|
//
|
|
// Find the naming contexts and the default naming context (objectCategory=*)
|
|
//
|
|
MK_ATTRS_2(Attrs, ATTR_NAMING_CONTEXTS, ATTR_DEFAULT_NAMING_CONTEXT);
|
|
|
|
if (!FrsDsLdapSearch(gLdap, CN_ROOT, LDAP_SCOPE_BASE, CATEGORY_ANY,
|
|
Attrs, 0, &LdapMsg)) {
|
|
goto ERROR_BINDING;
|
|
}
|
|
|
|
LdapEntry = ldap_first_entry(gLdap, LdapMsg);
|
|
if (LdapEntry == NULL) {
|
|
goto ERROR_BINDING;
|
|
}
|
|
|
|
Values = (PWCHAR *)FrsDsFindValues(gLdap, LdapEntry, ATTR_NAMING_CONTEXTS, FALSE);
|
|
if (Values == NULL) {
|
|
goto ERROR_BINDING;
|
|
}
|
|
|
|
//
|
|
// Now, find the naming context that begins with "CN=Configuration"
|
|
//
|
|
NumVals = ldap_count_values(Values);
|
|
while (NumVals--) {
|
|
FRS_WCSLWR(Values[NumVals]);
|
|
Config = wcsstr(Values[NumVals], CONFIG_NAMING_CONTEXT);
|
|
if (Config && Config == Values[NumVals]) {
|
|
//
|
|
// Build the pathname for "configuration\sites & services"
|
|
//
|
|
SitesDn = FrsDsExtendDn(Config, CN_SITES);
|
|
ServicesDn = FrsDsExtendDn(Config, CN_SERVICES);
|
|
break;
|
|
}
|
|
}
|
|
LDAP_FREE_VALUES(Values);
|
|
|
|
|
|
|
|
//
|
|
// Finally, find the default naming context
|
|
//
|
|
Values = (PWCHAR *)FrsDsFindValues(gLdap, LdapEntry, ATTR_DEFAULT_NAMING_CONTEXT, FALSE);
|
|
if (Values == NULL) {
|
|
goto ERROR_BINDING;
|
|
}
|
|
DefaultNcDn = FrsWcsDup(Values[0]);
|
|
ComputersDn = FrsDsExtendDn(DefaultNcDn, CN_COMPUTERS);
|
|
SystemDn = FrsDsExtendDn(DefaultNcDn, CN_SYSTEM);
|
|
DomainControllersDn = FrsDsExtendDnOu(DefaultNcDn, CN_DOMAIN_CONTROLLERS);
|
|
LDAP_FREE_VALUES(Values);
|
|
LDAP_FREE_MSG(LdapMsg);
|
|
|
|
//
|
|
// Polling the ds requires all these distinguished names
|
|
//
|
|
if ((SitesDn == NULL) || (ServicesDn == NULL) || (SystemDn == NULL) ||
|
|
(DefaultNcDn == NULL) || (ComputersDn == NULL) || (DomainControllersDn == NULL)) {
|
|
goto ERROR_BINDING;
|
|
}
|
|
|
|
//
|
|
// SUCCESS
|
|
//
|
|
DsBindingsAreValid = TRUE;
|
|
return TRUE;
|
|
|
|
ERROR_BINDING:
|
|
//
|
|
// avoid extraneous error messages during shutdown
|
|
//
|
|
if (!FrsIsShuttingDown && !DsIsShuttingDown) {
|
|
DPRINT(0, ":DS: ERROR - Could not open the DS\n");
|
|
}
|
|
//
|
|
// Cleanup
|
|
//
|
|
LDAP_FREE_VALUES(Values);
|
|
LDAP_FREE_MSG(LdapMsg);
|
|
|
|
//
|
|
// No ds bindings
|
|
//
|
|
FrsDsCloseDs();
|
|
|
|
//
|
|
// Increment the DS Bindings in Error counter
|
|
//
|
|
PM_INC_CTR_SERVICE(PMTotalInst, DSBindingsError, 1);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
#if DBG
|
|
#define FRS_PRINT_TREE(_Hdr_, _Sites_) FrsDsFrsPrintTree(_Hdr_, _Sites_)
|
|
VOID
|
|
FrsDsFrsPrintTree(
|
|
IN PWCHAR Hdr,
|
|
IN PCONFIG_NODE Sites
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
print the tree.
|
|
|
|
Arguments:
|
|
Hdr - prettyprint
|
|
Sites
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsFrsPrintTree:"
|
|
PCONFIG_NODE Site;
|
|
PCONFIG_NODE Settings;
|
|
PCONFIG_NODE Set;
|
|
PCONFIG_NODE Server;
|
|
PCONFIG_NODE Cxtion;
|
|
CHAR Guid[GUID_CHAR_LEN + 1];
|
|
|
|
if (Sites == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (Hdr) {
|
|
DPRINT1(5, ":DS: %ws\n", Hdr);
|
|
}
|
|
|
|
//
|
|
// Print the tree
|
|
//
|
|
for (Site = Sites; Site; Site = Site->Peer) {
|
|
|
|
GuidToStr(Site->Name->Guid, Guid);
|
|
DPRINT2(5, ":DS: %ws (%ws)\n", Site->Name->Name,
|
|
(Site->Consistent) ? L"Consistent" : L"InConsistent");
|
|
|
|
for (Settings = Site->Children; Settings; Settings = Settings->Peer) {
|
|
|
|
if (Settings->Name) {
|
|
GuidToStr(Settings->Name->Guid, Guid);
|
|
DPRINT2(5, ":DS: %ws (%ws)\n", Settings->Name->Name,
|
|
(Settings->Consistent) ? L"Consistent" : L"InConsistent");
|
|
} else {
|
|
DPRINT(5, ":DS: nTDSSettings\n");
|
|
}
|
|
|
|
for (Set = Settings->Children; Set; Set = Set->Peer) {
|
|
|
|
GuidToStr(Set->Name->Guid, Guid);
|
|
DPRINT2(5, ":DS: %ws (%ws)\n", Set->Name->Name,
|
|
(Set->Consistent) ? L"Consistent" : L"InConsistent");
|
|
|
|
for (Server = Set->Children; Server; Server = Server->Peer) {
|
|
|
|
GuidToStr(Server->Name->Guid, Guid);
|
|
DPRINT3(5, ":DS: %ws %ws (%ws)\n",
|
|
Server->Name->Name, Server->Root,
|
|
(Server->Consistent) ? L"Consistent" : L"InConsistent");
|
|
|
|
for (Cxtion = Server->Children; Cxtion; Cxtion = Cxtion->Peer) {
|
|
|
|
GuidToStr(Cxtion->Name->Guid, Guid);
|
|
DPRINT4(5, ":DS: %ws %ws %ws) (%ws)\n",
|
|
Cxtion->Name->Name,
|
|
(Cxtion->Inbound) ? L"IN (From" : L"OUT (To",
|
|
Cxtion->PartnerName->Name,
|
|
(Cxtion->Consistent) ? L"Consistent" : L"InConsistent");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (Hdr) {
|
|
DPRINT1(5, ":DS: %ws DONE\n", Hdr);
|
|
} else {
|
|
DPRINT(5, ":DS: DONE\n");
|
|
}
|
|
}
|
|
#else DBG
|
|
#define FRS_PRINT_TREE(_Hdr_, _Sites_)
|
|
#endif DBG
|
|
|
|
|
|
VOID
|
|
FrsDsTreeLink(
|
|
IN PCONFIG_NODE Parent,
|
|
IN PCONFIG_NODE Node
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Link the node into the tree and keep a running "change checksum"
|
|
to compare with the previous tree. We don't use a DS that is in
|
|
flux. We wait until two polling cycles return the same "change
|
|
checksum" before using the DS data.
|
|
|
|
Arguments:
|
|
Entry - Current entry from the DS
|
|
Parent - Container which contains Base
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsTreeLink:"
|
|
ULONG i;
|
|
ULONG LenChanged; // length of Changed
|
|
|
|
DPRINT3(5, ":DS: Linking node type %ws, node name %ws to parent %ws\n",
|
|
DsConfigTypeName[Node->DsObjectType],
|
|
(Node->Name) ? Node->Name->Name : L"null",
|
|
(Parent->Name) ? Parent->Name->Name : L"null");
|
|
|
|
//
|
|
// Link into config
|
|
//
|
|
++Parent->NumChildren;
|
|
Node->Parent = Parent;
|
|
Node->Peer = Parent->Children;
|
|
Parent->Children = Node;
|
|
|
|
//
|
|
// Some indication that the DS is stable
|
|
//
|
|
if (Node->UsnChanged) {
|
|
LenChanged = wcslen(Node->UsnChanged);
|
|
for (i = 0; i < LenChanged; ++i) {
|
|
ThisChange += *(Node->UsnChanged + i); // sum
|
|
NextChange += ThisChange; // sum of sums (order dependent)
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
PCONFIG_NODE
|
|
FrsDsAllocBasicNode(
|
|
IN PLDAP Ldap,
|
|
IN PLDAPMessage LdapEntry,
|
|
IN ULONG NodeType
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Allocate a Node and fill in the fields common to all or most nodes.
|
|
(guid, name, dn, schedule, and usnchanged)
|
|
|
|
Arguments:
|
|
Ldap - opened and bound ldap connection
|
|
LdapEntry - from ldap_first/next_entry
|
|
NodeType - Internal type code for the object represented by this node.
|
|
|
|
Return Value:
|
|
NULL if basic node cannot be allocated
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsAllocBasicNode:"
|
|
PCONFIG_NODE Node;
|
|
|
|
//
|
|
// Increment the DS Objects counter
|
|
//
|
|
PM_INC_CTR_SERVICE(PMTotalInst, DSObjects, 1);
|
|
|
|
//
|
|
// Initially, the node is assumed to be consistent
|
|
//
|
|
Node = FrsAllocType(CONFIG_NODE_TYPE);
|
|
Node->Consistent = TRUE;
|
|
Node->DsObjectType = NodeType;
|
|
|
|
//
|
|
// A dummy entry can be created by passing NULL for LdapEntry.
|
|
//
|
|
if (LdapEntry == NULL) {
|
|
return Node;
|
|
}
|
|
//
|
|
// Distinguished name
|
|
//
|
|
Node->Dn = FrsDsFindValue(Ldap, LdapEntry, ATTR_DN);
|
|
FRS_WCSLWR(Node->Dn);
|
|
|
|
//
|
|
// Name = RDN + Object Guid
|
|
//
|
|
Node->Name = FrsBuildGName(FrsDsFindGuid(Ldap, LdapEntry),
|
|
FrsDsMakeRdn(Node->Dn));
|
|
|
|
//
|
|
// Schedule, if any
|
|
//
|
|
Node->Schedule = FrsDsFindSchedule(Ldap, LdapEntry, &Node->ScheduleLength);
|
|
|
|
//
|
|
// USN Changed
|
|
//
|
|
Node->UsnChanged = FrsDsFindValue(Ldap, LdapEntry, ATTR_USN_CHANGED);
|
|
|
|
if (!Node->Dn || !Node->Name->Name || !Node->Name->Guid) {
|
|
|
|
//
|
|
// Increment the DS Objects in Error counter
|
|
//
|
|
PM_INC_CTR_SERVICE(PMTotalInst, DSObjectsError, 1);
|
|
|
|
DPRINT3(0, ":DS: ERROR - Ignoring node; lacks dn (%08x), rdn (%08x), or guid (%08x)\n",
|
|
Node->Dn, Node->Name->Name, Node->Name->Guid);
|
|
Node = FrsFreeType(Node);
|
|
}
|
|
|
|
return Node;
|
|
}
|
|
|
|
|
|
#define NUM_EQUALS (4)
|
|
ULONG
|
|
FrsDsSameSite(
|
|
IN PWCHAR NtDsSettings1,
|
|
IN PWCHAR NtDsSettings2
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Are the ntds settings in the same site?
|
|
|
|
Arguments:
|
|
NtDsSettings1 - NtDs Settings FQDN
|
|
NtDsSettings2 - NtDs Settings FQDN
|
|
|
|
Return Value:
|
|
TRUE - Same site
|
|
FALSE - Not
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsSameSite:"
|
|
PWCHAR Equal1 = NULL;
|
|
PWCHAR Equal2 = NULL;
|
|
DWORD EqualsFound;
|
|
|
|
if (!NtDsSettings1 || !NtDsSettings2) {
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// Forth equals sign
|
|
//
|
|
for (EqualsFound = 0; *NtDsSettings1 != L'\0'; ++NtDsSettings1) {
|
|
if (*NtDsSettings1 != L'=') {
|
|
continue;
|
|
}
|
|
if (++EqualsFound == NUM_EQUALS) {
|
|
Equal1 = NtDsSettings1;
|
|
break;
|
|
}
|
|
}
|
|
//
|
|
// Forth equals sign
|
|
//
|
|
for (EqualsFound = 0; *NtDsSettings2 != L'\0'; ++NtDsSettings2) {
|
|
if (*NtDsSettings2 != L'=') {
|
|
continue;
|
|
}
|
|
if (++EqualsFound == NUM_EQUALS) {
|
|
Equal2 = NtDsSettings2;
|
|
break;
|
|
}
|
|
}
|
|
//
|
|
// Not the same length
|
|
//
|
|
if (!Equal1 || !Equal2) {
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// Compare up to the first comma
|
|
//
|
|
while (*Equal1 == *Equal2 && (*Equal1 && *Equal1 != L',')) {
|
|
++Equal1;
|
|
++Equal2;
|
|
}
|
|
DPRINT3(4, ":DS: %s: %ws %ws\n",
|
|
(*Equal1 == *Equal2) ? "SAME SITE" : "DIFF SITE", Equal1, Equal2);
|
|
|
|
return (*Equal1 == *Equal2);
|
|
}
|
|
|
|
|
|
DWORD
|
|
FrsDsResolveCxtionConflict(
|
|
IN PCONFIG_NODE OldCxtion,
|
|
IN PCONFIG_NODE NewCxtion,
|
|
IN PCONFIG_NODE *Winner,
|
|
IN PCONFIG_NODE *Loser
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Resolve the connection conflict.
|
|
|
|
Arguments:
|
|
OldCxtion
|
|
NewCxtion
|
|
Winner
|
|
Loser
|
|
|
|
Return Value:
|
|
WIN32 Status
|
|
--*/
|
|
{
|
|
|
|
//
|
|
// Compare the guids and pick a connection. This ensures that both the members
|
|
// at the ends of each connection pick the same one.
|
|
//
|
|
if ((OldCxtion != NULL) && (NewCxtion != NULL) &&
|
|
(OldCxtion->Name != NULL) && (NewCxtion->Name != NULL) &&
|
|
(memcmp(OldCxtion->Name->Guid, NewCxtion->Name->Guid, sizeof(GUID)) > 0) ) {
|
|
|
|
*Winner = NewCxtion;
|
|
*Loser = OldCxtion;
|
|
} else {
|
|
|
|
*Winner = OldCxtion;
|
|
*Loser = NewCxtion;
|
|
}
|
|
|
|
//
|
|
// Add to the poll summary event log.
|
|
//
|
|
FrsDsAddToPollSummary3ws(IDS_POLL_SUM_CXTION_CONFLICT, (*Winner)->Dn,
|
|
(*Loser)->Dn, (*Winner)->Dn);
|
|
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
|
|
DWORD
|
|
FrsDsResolveSubscriberConflict(
|
|
IN PCONFIG_NODE OldSubscriber,
|
|
IN PCONFIG_NODE NewSubscriber,
|
|
IN PCONFIG_NODE *Winner,
|
|
IN PCONFIG_NODE *Loser
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Resolve the subscriber conflict.
|
|
|
|
Arguments:
|
|
OldSubscriber
|
|
NewSubscriber
|
|
Winner
|
|
Loser
|
|
|
|
Return Value:
|
|
WIN32 Status
|
|
--*/
|
|
{
|
|
|
|
*Winner = OldSubscriber;
|
|
*Loser = NewSubscriber;
|
|
|
|
//
|
|
// Add to the poll summary event log.
|
|
//
|
|
FrsDsAddToPollSummary3ws(IDS_POLL_SUM_SUBSCRIBER_CONFLICT, (*Winner)->Dn,
|
|
(*Loser)->Dn, (*Winner)->Dn);
|
|
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
|
|
ULONG
|
|
FrsDsGetNonSysvolInboundCxtions(
|
|
IN PLDAP Ldap,
|
|
IN PWCHAR SetDn,
|
|
IN PWCHAR MemberRef
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Fetch the non-sysvol inbound connections and add them
|
|
to the CxtionTable. Check for multiple connections between the
|
|
same partners and resolve the conflict.
|
|
|
|
Part of NewDs poll APIs.
|
|
|
|
Arguments:
|
|
ldap - opened and bound ldap connection.
|
|
SetDn - Dn of the set being processed.
|
|
MemberRef - Member reference from the subscriber object.
|
|
|
|
Return Value:
|
|
ERROR_SUCCESS - config fetched successfully
|
|
Otherwise - couldn't get the DS config
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsGetNonSysvolInboundCxtions:"
|
|
PWCHAR Attrs[8];
|
|
PLDAPMessage Entry; // Opaque stuff from ldap subsystem
|
|
PCONFIG_NODE Node; // generic node for the tree
|
|
PWCHAR TempFilter = NULL;
|
|
FRS_LDAP_SEARCH_CONTEXT FrsSearchContext;
|
|
PWCHAR PartnerCn = NULL;
|
|
PGEN_ENTRY ConflictingNodeEntry = NULL;
|
|
PCONFIG_NODE ConflictingNode = NULL;
|
|
PCONFIG_NODE Winner = NULL;
|
|
PCONFIG_NODE Loser = NULL;
|
|
BOOL Inbound;
|
|
PWCHAR Options = NULL;
|
|
|
|
//
|
|
// Look for all the connections under our member object.
|
|
//
|
|
MK_ATTRS_7(Attrs, ATTR_DN, ATTR_SCHEDULE, ATTR_FROM_SERVER, ATTR_OBJECT_GUID,
|
|
ATTR_USN_CHANGED, ATTR_ENABLED_CXTION, ATTR_OPTIONS);
|
|
|
|
if (!FrsDsLdapSearchInit(Ldap, MemberRef, LDAP_SCOPE_ONELEVEL, CATEGORY_CXTION,
|
|
Attrs, 0, &FrsSearchContext)) {
|
|
return ERROR_ACCESS_DENIED;
|
|
}
|
|
if (FrsSearchContext.EntriesInPage == 0) {
|
|
DPRINT1(1, ":DS: WARN - There are no connection objects in %ws!\n", MemberRef);
|
|
}
|
|
|
|
//
|
|
// Scan the entries returned from ldap_search
|
|
//
|
|
for (Entry = FrsDsLdapSearchNext(Ldap, &FrsSearchContext);
|
|
Entry != NULL;
|
|
Entry = FrsDsLdapSearchNext(Ldap, &FrsSearchContext)) {
|
|
|
|
//
|
|
// Basic node info (guid, name, dn, schedule, and usnchanged)
|
|
//
|
|
Node = FrsDsAllocBasicNode(Ldap, Entry, CONFIG_TYPE_IN_CXTION);
|
|
if (!Node) {
|
|
DPRINT(4, ":DS: Cxtion lacks basic info; skipping\n");
|
|
continue;
|
|
}
|
|
Node->EnabledCxtion = FrsDsFindValue(Ldap, Entry, ATTR_ENABLED_CXTION);
|
|
if (Node->EnabledCxtion && WSTR_EQ(Node->EnabledCxtion, ATTR_FALSE)) {
|
|
DPRINT2(1, ":DS: WARN - enabledConnection set to %ws; Ignoring %ws\n",
|
|
Node->EnabledCxtion, Node->Name->Name);
|
|
Node = FrsFreeType(Node);
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Read the options value on the connection object.
|
|
// We are intersted in the NTDSCONN_OPT_TWOWAY_SYNC flag and the
|
|
// priority on connections.
|
|
//
|
|
Options = FrsDsFindValue(Ldap, Entry, ATTR_OPTIONS);
|
|
if (Options != NULL) {
|
|
Node->CxtionOptions = _wtoi(Options);
|
|
Options = FrsFree(Options);
|
|
} else {
|
|
Node->CxtionOptions = 0;
|
|
}
|
|
|
|
//
|
|
// These are inbound connections.
|
|
//
|
|
Node->Inbound = TRUE;
|
|
|
|
//
|
|
// Node's partner's name.
|
|
//
|
|
Node->PartnerDn = FrsDsFindValue(Ldap, Entry, ATTR_FROM_SERVER);
|
|
FRS_WCSLWR(Node->PartnerDn);
|
|
|
|
//
|
|
// Add the Inbound cxtion to the cxtion table.
|
|
//
|
|
ConflictingNodeEntry = GTabInsertUniqueEntry(CxtionTable, Node, Node->PartnerDn, &Node->Inbound);
|
|
GTabInsertUniqueEntry(AllCxtionsTable, Node, Node->PartnerDn, &Node->Inbound);
|
|
|
|
if (ConflictingNodeEntry) {
|
|
ConflictingNode = ConflictingNodeEntry->Data;
|
|
FrsDsResolveCxtionConflict(ConflictingNode, Node, &Winner, &Loser);
|
|
if (WSTR_EQ(Winner->Dn, Node->Dn)) {
|
|
//
|
|
// The new one is the winner. Remove old one and insert new one.
|
|
//
|
|
GTabDelete(CxtionTable,ConflictingNodeEntry->Key1,ConflictingNodeEntry->Key2, NULL);
|
|
GTabInsertUniqueEntry(CxtionTable, Node, Node->PartnerDn, &Node->Inbound);
|
|
|
|
GTabDelete(AllCxtionsTable,ConflictingNode->PartnerDn, (PVOID)&ConflictingNode->Inbound, NULL);
|
|
GTabInsertUniqueEntry(AllCxtionsTable, Node, Node->PartnerDn, &Node->Inbound);
|
|
|
|
FrsFreeType(ConflictingNode);
|
|
} else {
|
|
//
|
|
// The old one is the winner. Leave it in the table.
|
|
//
|
|
FrsFreeType(Node);
|
|
continue;
|
|
}
|
|
} else {
|
|
|
|
//
|
|
// If there is no conflict then we need to add this Member to the MemberSearchFilter
|
|
// if it is not already there. It could have been added while processing the oubound connections.
|
|
//
|
|
Inbound = FALSE;
|
|
if (GTabLookupTableString(CxtionTable, Node->PartnerDn, (PWCHAR)&Inbound) == NULL) {
|
|
PartnerCn = FrsDsMakeRdn(Node->PartnerDn);
|
|
if (MemberSearchFilter != NULL) {
|
|
TempFilter = FrsAlloc((wcslen(MemberSearchFilter) + wcslen(L"(=)" ATTR_CN) +
|
|
wcslen(PartnerCn) + 1 ) * sizeof(WCHAR));
|
|
wcscpy(TempFilter, MemberSearchFilter);
|
|
wcscat(TempFilter, L"(" ATTR_CN L"=" );
|
|
wcscat(TempFilter, PartnerCn);
|
|
wcscat(TempFilter, L")");
|
|
FrsFree(MemberSearchFilter);
|
|
MemberSearchFilter = TempFilter;
|
|
TempFilter = NULL;
|
|
} else {
|
|
MemberSearchFilter = FrsAlloc((wcslen(L"(|(=)" ATTR_CN) +
|
|
wcslen(PartnerCn) + 1 ) * sizeof(WCHAR));
|
|
wcscpy(MemberSearchFilter, L"(|(" ATTR_CN L"=" );
|
|
wcscat(MemberSearchFilter, PartnerCn);
|
|
wcscat(MemberSearchFilter, L")");
|
|
}
|
|
FrsFree(PartnerCn);
|
|
}
|
|
}
|
|
|
|
}
|
|
FrsDsLdapSearchClose(&FrsSearchContext);
|
|
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
|
|
ULONG
|
|
FrsDsGetNonSysvolOutboundCxtions(
|
|
IN PLDAP Ldap,
|
|
IN PWCHAR SetDn,
|
|
IN PWCHAR MemberRef
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Fetch the non-sysvol outbound connections and add them
|
|
to the CxtionTable. Check for multiple connections between the
|
|
same partners and resolve the conflict.
|
|
|
|
Part of NewDs poll APIs.
|
|
|
|
Arguments:
|
|
ldap - opened and bound ldap connection.
|
|
SetDn - Dn of the set being processed.
|
|
MemberRef - Member reference from the subscriber object.
|
|
|
|
Return Value:
|
|
ERROR_SUCCESS - config fetched successfully
|
|
Otherwise - couldn't get the DS config
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsGetNonSysvolOutboundCxtions:"
|
|
PWCHAR Attrs[8];
|
|
PLDAPMessage Entry; // Opaque stuff from ldap subsystem
|
|
PCONFIG_NODE Node; // generic node for the tree
|
|
PWCHAR SearchFilter = NULL;
|
|
PWCHAR TempFilter = NULL;
|
|
FRS_LDAP_SEARCH_CONTEXT FrsSearchContext;
|
|
PWCHAR PartnerCn = NULL;
|
|
PGEN_ENTRY ConflictingNodeEntry = NULL;
|
|
PCONFIG_NODE ConflictingNode = NULL;
|
|
PCONFIG_NODE Winner = NULL;
|
|
PCONFIG_NODE Loser = NULL;
|
|
PWCHAR Options = NULL;
|
|
|
|
//
|
|
// Look for all the connections that have our member as the from server.
|
|
// Filter will look like (&(objectCategory=nTDSConnection)(fromServer=cn=member1,cn=set1,...))
|
|
//
|
|
|
|
MK_ATTRS_7(Attrs, ATTR_DN, ATTR_SCHEDULE, ATTR_FROM_SERVER, ATTR_OBJECT_GUID,
|
|
ATTR_USN_CHANGED, ATTR_ENABLED_CXTION, ATTR_OPTIONS);
|
|
|
|
SearchFilter = FrsAlloc((wcslen(L"(&(=))" CATEGORY_CXTION ATTR_FROM_SERVER) +
|
|
wcslen(MemberRef) + 1) * sizeof(WCHAR));
|
|
wcscpy(SearchFilter,L"(&" CATEGORY_CXTION L"(" ATTR_FROM_SERVER L"=" );
|
|
wcscat(SearchFilter,MemberRef);
|
|
wcscat(SearchFilter,L"))");
|
|
|
|
if (!FrsDsLdapSearchInit(Ldap, SetDn, LDAP_SCOPE_SUBTREE, SearchFilter,
|
|
Attrs, 0, &FrsSearchContext)) {
|
|
SearchFilter = FrsFree(SearchFilter);
|
|
return ERROR_ACCESS_DENIED;
|
|
}
|
|
if (FrsSearchContext.EntriesInPage == 0) {
|
|
DPRINT1(1, ":DS: WARN - No outbound connections found for member %ws!\n", MemberRef);
|
|
}
|
|
|
|
SearchFilter = FrsFree(SearchFilter);
|
|
|
|
//
|
|
// Scan the entries returned from ldap_search
|
|
//
|
|
for (Entry = FrsDsLdapSearchNext(Ldap, &FrsSearchContext);
|
|
Entry != NULL;
|
|
Entry = FrsDsLdapSearchNext(Ldap, &FrsSearchContext)) {
|
|
|
|
//
|
|
// Basic node info (guid, name, dn, schedule, and usnchanged)
|
|
//
|
|
Node = FrsDsAllocBasicNode(Ldap, Entry, CONFIG_TYPE_IN_CXTION);
|
|
if (!Node) {
|
|
DPRINT(4, ":DS: Cxtion lacks basic info; skipping\n");
|
|
continue;
|
|
}
|
|
Node->EnabledCxtion = FrsDsFindValue(Ldap, Entry, ATTR_ENABLED_CXTION);
|
|
if (Node->EnabledCxtion && WSTR_EQ(Node->EnabledCxtion, ATTR_FALSE)) {
|
|
DPRINT2(1, ":DS: WARN - enabledConnection set to %ws; Ignoring %ws\n",
|
|
Node->EnabledCxtion, Node->Name->Name);
|
|
Node = FrsFreeType(Node);
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Read the options value on the connection object.
|
|
// We are only intersted in the NTDSCONN_OPT_TWOWAY_SYNC flag.
|
|
//
|
|
Options = FrsDsFindValue(Ldap, Entry, ATTR_OPTIONS);
|
|
if (Options != NULL) {
|
|
Node->CxtionOptions = _wtoi(Options);
|
|
Options = FrsFree(Options);
|
|
} else {
|
|
Node->CxtionOptions = 0;
|
|
}
|
|
//
|
|
// These are outbound connections.
|
|
//
|
|
Node->Inbound = FALSE;
|
|
|
|
//
|
|
// Node's partner's name. This is an outbound connection. Get the
|
|
// partners Dn by going one level up from the connection to the
|
|
// member Dn.
|
|
//
|
|
Node->PartnerDn = FrsWcsDup(wcsstr(Node->Dn + 3, L"cn="));
|
|
FRS_WCSLWR(Node->PartnerDn);
|
|
|
|
//
|
|
// Add the outbound cxtion to the cxtion table.
|
|
//
|
|
ConflictingNodeEntry = GTabInsertUniqueEntry(CxtionTable, Node, Node->PartnerDn, &Node->Inbound);
|
|
GTabInsertUniqueEntry(AllCxtionsTable, Node, Node->PartnerDn, &Node->Inbound);
|
|
|
|
if (ConflictingNodeEntry) {
|
|
ConflictingNode = ConflictingNodeEntry->Data;
|
|
FrsDsResolveCxtionConflict(ConflictingNode, Node, &Winner, &Loser);
|
|
if (WSTR_EQ(Winner->Dn, Node->Dn)) {
|
|
//
|
|
// The new one is the winner. Remove old one and insert new one.
|
|
//
|
|
GTabDelete(CxtionTable,ConflictingNodeEntry->Key1,ConflictingNodeEntry->Key2, NULL);
|
|
GTabInsertUniqueEntry(CxtionTable, Node, Node->PartnerDn, &Node->Inbound);
|
|
|
|
GTabDelete(AllCxtionsTable,ConflictingNode->PartnerDn, (PVOID)&ConflictingNode->Inbound, NULL);
|
|
GTabInsertUniqueEntry(AllCxtionsTable, Node, Node->PartnerDn, &Node->Inbound);
|
|
|
|
FrsFreeType(ConflictingNode);
|
|
} else {
|
|
//
|
|
// The old one is the winner. Leave it in the table.
|
|
//
|
|
FrsFreeType(Node);
|
|
continue;
|
|
}
|
|
} else {
|
|
//
|
|
// If there is no conflict then we need to add this Member to the MemberSearchFilter.
|
|
//
|
|
PartnerCn = FrsDsMakeRdn(Node->PartnerDn);
|
|
if (MemberSearchFilter != NULL) {
|
|
TempFilter = FrsAlloc((wcslen(MemberSearchFilter) + wcslen(L"(=)" ATTR_CN) +
|
|
wcslen(PartnerCn) + 1 ) * sizeof(WCHAR));
|
|
wcscpy(TempFilter, MemberSearchFilter);
|
|
wcscat(TempFilter, L"(" ATTR_CN L"=");
|
|
wcscat(TempFilter, PartnerCn);
|
|
wcscat(TempFilter, L")");
|
|
FrsFree(MemberSearchFilter);
|
|
MemberSearchFilter = TempFilter;
|
|
TempFilter = NULL;
|
|
} else {
|
|
MemberSearchFilter = FrsAlloc((wcslen(L"(|(=)" ATTR_CN) +
|
|
wcslen(PartnerCn) + 1 ) * sizeof(WCHAR));
|
|
wcscpy(MemberSearchFilter, L"(|(" ATTR_CN L"=");
|
|
wcscat(MemberSearchFilter, PartnerCn);
|
|
wcscat(MemberSearchFilter, L")");
|
|
}
|
|
FrsFree(PartnerCn);
|
|
}
|
|
|
|
}
|
|
|
|
FrsDsLdapSearchClose(&FrsSearchContext);
|
|
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
|
|
ULONG
|
|
FrsDsGetSysvolInboundCxtions(
|
|
IN PLDAP Ldap,
|
|
IN PWCHAR SettingsDn
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Fetch the sysvol inbound connections and add them
|
|
to the CxtionTable. Check for multiple connections between the
|
|
same partners and resolve the conflict.
|
|
|
|
Part of NewDs poll APIs.
|
|
|
|
Arguments:
|
|
ldap - opened and bound ldap connection.
|
|
SettingsDn - server reference from the member object.
|
|
|
|
Return Value:
|
|
ERROR_SUCCESS - config fetched successfully
|
|
Otherwise - couldn't get the DS config
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsGetSysvolInboundCxtions:"
|
|
PWCHAR Attrs[7];
|
|
PLDAPMessage Entry; // Opaque stuff from ldap subsystem
|
|
PCONFIG_NODE Node; // generic node for the tree
|
|
PWCHAR TempFilter = NULL;
|
|
FRS_LDAP_SEARCH_CONTEXT FrsSearchContext;
|
|
PGEN_ENTRY ConflictingNodeEntry = NULL;
|
|
PCONFIG_NODE ConflictingNode = NULL;
|
|
PCONFIG_NODE Winner = NULL;
|
|
PCONFIG_NODE Loser = NULL;
|
|
BOOL Inbound;
|
|
|
|
//
|
|
// Look for all the connections under our member object.
|
|
//
|
|
MK_ATTRS_6(Attrs, ATTR_DN, ATTR_SCHEDULE, ATTR_FROM_SERVER, ATTR_OBJECT_GUID,
|
|
ATTR_USN_CHANGED, ATTR_ENABLED_CXTION);
|
|
|
|
if (!FrsDsLdapSearchInit(Ldap, SettingsDn, LDAP_SCOPE_ONELEVEL, CATEGORY_CXTION,
|
|
Attrs, 0, &FrsSearchContext)) {
|
|
return ERROR_ACCESS_DENIED;
|
|
}
|
|
if (FrsSearchContext.EntriesInPage == 0) {
|
|
DPRINT1(1, ":DS: WARN - No sysvol inbound connections found for object %ws!\n", SettingsDn);
|
|
}
|
|
|
|
//
|
|
// Scan the entries returned from ldap_search
|
|
//
|
|
for (Entry = FrsDsLdapSearchNext(Ldap, &FrsSearchContext);
|
|
Entry != NULL;
|
|
Entry = FrsDsLdapSearchNext(Ldap, &FrsSearchContext)) {
|
|
|
|
//
|
|
// Basic node info (guid, name, dn, schedule, and usnchanged)
|
|
//
|
|
Node = FrsDsAllocBasicNode(Ldap, Entry, CONFIG_TYPE_IN_CXTION);
|
|
if (!Node) {
|
|
DPRINT(4, ":DS: Cxtion lacks basic info; skipping\n");
|
|
continue;
|
|
}
|
|
Node->EnabledCxtion = FrsDsFindValue(Ldap, Entry, ATTR_ENABLED_CXTION);
|
|
if (Node->EnabledCxtion && WSTR_EQ(Node->EnabledCxtion, ATTR_FALSE)) {
|
|
DPRINT2(1, ":DS: WARN - enabledConnection set to %ws; Ignoring %ws\n",
|
|
Node->EnabledCxtion, Node->Name->Name);
|
|
Node = FrsFreeType(Node);
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// These are inbound connections.
|
|
//
|
|
Node->Inbound = TRUE;
|
|
|
|
//
|
|
// Node's partner's name.
|
|
//
|
|
Node->PartnerDn = FrsDsFindValue(Ldap, Entry, ATTR_FROM_SERVER);
|
|
FRS_WCSLWR(Node->PartnerDn);
|
|
|
|
//
|
|
// Add the Inbound cxtion to the cxtion table.
|
|
//
|
|
ConflictingNodeEntry = GTabInsertUniqueEntry(CxtionTable, Node, Node->PartnerDn, &Node->Inbound);
|
|
GTabInsertUniqueEntry(AllCxtionsTable, Node, Node->PartnerDn, &Node->Inbound);
|
|
|
|
if (ConflictingNodeEntry) {
|
|
ConflictingNode = ConflictingNodeEntry->Data;
|
|
FrsDsResolveCxtionConflict(ConflictingNode, Node, &Winner, &Loser);
|
|
if (WSTR_EQ(Winner->Dn, Node->Dn)) {
|
|
//
|
|
// The new one is the winner. Remove old one and insert new one.
|
|
//
|
|
GTabDelete(CxtionTable,ConflictingNodeEntry->Key1,ConflictingNodeEntry->Key2, NULL);
|
|
GTabInsertUniqueEntry(CxtionTable, Node, Node->PartnerDn, &Node->Inbound);
|
|
|
|
GTabDelete(AllCxtionsTable,ConflictingNode->PartnerDn, (PVOID)&ConflictingNode->Inbound, NULL);
|
|
GTabInsertUniqueEntry(AllCxtionsTable, Node, Node->PartnerDn, &Node->Inbound);
|
|
|
|
FrsFreeType(ConflictingNode);
|
|
} else {
|
|
//
|
|
// The old one is the winner. Leave it in the table.
|
|
//
|
|
FrsFreeType(Node);
|
|
continue;
|
|
}
|
|
} else {
|
|
|
|
//
|
|
// If there is no conflict then we need to add this Member to the MemberSearchFilter
|
|
// if it is not already there. It could have been added while processing the oubound connections.
|
|
//
|
|
Inbound = FALSE;
|
|
if (GTabLookupTableString(CxtionTable, Node->PartnerDn, (PWCHAR)&Inbound) == NULL) {
|
|
if (MemberSearchFilter != NULL) {
|
|
TempFilter = FrsAlloc((wcslen(MemberSearchFilter) + wcslen(L"(=)" ATTR_SERVER_REF) +
|
|
wcslen(Node->PartnerDn) + 1 ) * sizeof(WCHAR));
|
|
wcscpy(TempFilter, MemberSearchFilter);
|
|
wcscat(TempFilter, L"(" ATTR_SERVER_REF L"=");
|
|
wcscat(TempFilter, Node->PartnerDn);
|
|
wcscat(TempFilter, L")");
|
|
FrsFree(MemberSearchFilter);
|
|
MemberSearchFilter = TempFilter;
|
|
TempFilter = NULL;
|
|
} else {
|
|
MemberSearchFilter = FrsAlloc((wcslen(L"(|(=)" ATTR_SERVER_REF) +
|
|
wcslen(Node->PartnerDn) + 1 ) * sizeof(WCHAR));
|
|
wcscpy(MemberSearchFilter, L"(|(" ATTR_SERVER_REF L"=");
|
|
wcscat(MemberSearchFilter, Node->PartnerDn);
|
|
wcscat(MemberSearchFilter, L")");
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// If sysvol, always on within a site
|
|
// Trigger schedule otherwise.
|
|
//
|
|
Node->SameSite = FrsDsSameSite(SettingsDn, Node->PartnerDn);
|
|
if (Node->SameSite) {
|
|
Node->Schedule = FrsFree(Node->Schedule);
|
|
}
|
|
|
|
}
|
|
FrsDsLdapSearchClose(&FrsSearchContext);
|
|
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
|
|
ULONG
|
|
FrsDsGetSysvolOutboundCxtions(
|
|
IN PLDAP Ldap,
|
|
IN PWCHAR SettingsDn
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Fetch the sysvol outbound connections and add them
|
|
to the CxtionTable. Check for multiple connections between the
|
|
same partners and resolve the conflict.
|
|
|
|
Part of NewDs poll APIs.
|
|
|
|
Arguments:
|
|
ldap - opened and bound ldap connection.
|
|
SettingsDn - server reference from the member object.
|
|
|
|
Return Value:
|
|
ERROR_SUCCESS - config fetched successfully
|
|
Otherwise - couldn't get the DS config
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsGetSysvolOutboundCxtions:"
|
|
PWCHAR Attrs[7];
|
|
PLDAPMessage Entry; // Opaque stuff from ldap subsystem
|
|
PCONFIG_NODE Node; // generic node for the tree
|
|
PWCHAR SearchFilter = NULL;
|
|
PWCHAR TempFilter = NULL;
|
|
FRS_LDAP_SEARCH_CONTEXT FrsSearchContext;
|
|
PGEN_ENTRY ConflictingNodeEntry = NULL;
|
|
PCONFIG_NODE ConflictingNode = NULL;
|
|
PCONFIG_NODE Winner = NULL;
|
|
PCONFIG_NODE Loser = NULL;
|
|
|
|
//
|
|
// Look for all the connections that have our member as the from server.
|
|
// Filter will look like (&(objectCategory=nTDSConnection)(fromServer=cn=member1,cn=set1,...))
|
|
//
|
|
MK_ATTRS_6(Attrs, ATTR_DN, ATTR_SCHEDULE, ATTR_FROM_SERVER, ATTR_OBJECT_GUID,
|
|
ATTR_USN_CHANGED, ATTR_ENABLED_CXTION);
|
|
|
|
SearchFilter = FrsAlloc((wcslen(L"(&(=))" CATEGORY_CXTION ATTR_FROM_SERVER) +
|
|
wcslen(SettingsDn) + 1) * sizeof(WCHAR));
|
|
wcscpy(SearchFilter,L"(&" CATEGORY_CXTION L"(" ATTR_FROM_SERVER L"=");
|
|
wcscat(SearchFilter,SettingsDn);
|
|
wcscat(SearchFilter,L"))");
|
|
|
|
|
|
if (!FrsDsLdapSearchInit(Ldap, SitesDn, LDAP_SCOPE_SUBTREE, SearchFilter,
|
|
Attrs, 0, &FrsSearchContext)) {
|
|
SearchFilter = FrsFree(SearchFilter);
|
|
return ERROR_ACCESS_DENIED;
|
|
}
|
|
if (FrsSearchContext.EntriesInPage == 0) {
|
|
DPRINT1(1, ":DS: WARN - No sysvol outbound connections found for member %ws!\n", SettingsDn);
|
|
}
|
|
|
|
SearchFilter = FrsFree(SearchFilter);
|
|
|
|
//
|
|
// Scan the entries returned from ldap_search
|
|
//
|
|
for (Entry = FrsDsLdapSearchNext(Ldap, &FrsSearchContext);
|
|
Entry != NULL;
|
|
Entry = FrsDsLdapSearchNext(Ldap, &FrsSearchContext)) {
|
|
|
|
//
|
|
// Basic node info (guid, name, dn, schedule, and usnchanged)
|
|
//
|
|
Node = FrsDsAllocBasicNode(Ldap, Entry, CONFIG_TYPE_IN_CXTION);
|
|
if (!Node) {
|
|
DPRINT(4, ":DS: Cxtion lacks basic info; skipping\n");
|
|
continue;
|
|
}
|
|
Node->EnabledCxtion = FrsDsFindValue(Ldap, Entry, ATTR_ENABLED_CXTION);
|
|
if (Node->EnabledCxtion && WSTR_EQ(Node->EnabledCxtion, ATTR_FALSE)) {
|
|
DPRINT2(1, ":DS: WARN - enabledConnection set to %ws; Ignoring %ws\n",
|
|
Node->EnabledCxtion, Node->Name->Name);
|
|
Node = FrsFreeType(Node);
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// These are outbound connections.
|
|
//
|
|
Node->Inbound = FALSE;
|
|
|
|
//
|
|
// Node's partner's name. This is an outbound connection. Get the
|
|
// partners Dn by going one level up from the connection to the
|
|
// member Dn.
|
|
//
|
|
Node->PartnerDn = FrsWcsDup(wcsstr(Node->Dn + 3, L"cn="));
|
|
FRS_WCSLWR(Node->PartnerDn);
|
|
|
|
//
|
|
// Add the outbound cxtion to the cxtion table.
|
|
//
|
|
ConflictingNodeEntry = GTabInsertUniqueEntry(CxtionTable, Node, Node->PartnerDn, &Node->Inbound);
|
|
GTabInsertUniqueEntry(AllCxtionsTable, Node, Node->PartnerDn, &Node->Inbound);
|
|
|
|
if (ConflictingNodeEntry) {
|
|
ConflictingNode = ConflictingNodeEntry->Data;
|
|
FrsDsResolveCxtionConflict(ConflictingNode, Node, &Winner, &Loser);
|
|
if (WSTR_EQ(Winner->Dn, Node->Dn)) {
|
|
//
|
|
// The new one is the winner. Remove old one and insert new one.
|
|
//
|
|
GTabDelete(CxtionTable,ConflictingNodeEntry->Key1,ConflictingNodeEntry->Key2, NULL);
|
|
GTabInsertUniqueEntry(CxtionTable, Node, Node->PartnerDn, &Node->Inbound);
|
|
|
|
GTabDelete(AllCxtionsTable,ConflictingNode->PartnerDn, (PVOID)&ConflictingNode->Inbound, NULL);
|
|
GTabInsertUniqueEntry(AllCxtionsTable, Node, Node->PartnerDn, &Node->Inbound);
|
|
|
|
FrsFreeType(ConflictingNode);
|
|
} else {
|
|
//
|
|
// The old one is the winner. Leave it in the table.
|
|
//
|
|
FrsFreeType(Node);
|
|
continue;
|
|
}
|
|
} else {
|
|
//
|
|
// If there is no conflict then we need to add this Member to the MemberSearchFilter.
|
|
//
|
|
if (MemberSearchFilter != NULL) {
|
|
TempFilter = FrsAlloc((wcslen(MemberSearchFilter) + wcslen(L"(=)" ATTR_SERVER_REF) +
|
|
wcslen(Node->PartnerDn) + 1 ) * sizeof(WCHAR));
|
|
wcscpy(TempFilter, MemberSearchFilter);
|
|
wcscat(TempFilter, L"(" ATTR_SERVER_REF L"=");
|
|
wcscat(TempFilter, Node->PartnerDn);
|
|
wcscat(TempFilter, L")");
|
|
FrsFree(MemberSearchFilter);
|
|
MemberSearchFilter = TempFilter;
|
|
TempFilter = NULL;
|
|
} else {
|
|
MemberSearchFilter = FrsAlloc((wcslen(L"(|(=)" ATTR_SERVER_REF) +
|
|
wcslen(Node->PartnerDn) + 1 ) * sizeof(WCHAR));
|
|
wcscpy(MemberSearchFilter, L"(|(" ATTR_SERVER_REF L"=");
|
|
wcscat(MemberSearchFilter, Node->PartnerDn);
|
|
wcscat(MemberSearchFilter, L")");
|
|
}
|
|
}
|
|
|
|
//
|
|
// If sysvol, always on within a site
|
|
//
|
|
Node->SameSite = FrsDsSameSite(SettingsDn, Node->PartnerDn);
|
|
if (Node->SameSite) {
|
|
Node->Schedule = FrsFree(Node->Schedule);
|
|
}
|
|
|
|
}
|
|
|
|
FrsDsLdapSearchClose(&FrsSearchContext);
|
|
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
|
|
VOID
|
|
FrsDsMergeTwoWaySchedules(
|
|
IN PSCHEDULE *pISchedule,
|
|
IN DWORD *pIScheduleLen,
|
|
IN OUT PSCHEDULE *pOSchedule,
|
|
IN OUT DWORD *pOScheduleLen,
|
|
IN PSCHEDULE *pRSchedule
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Set the output schedule by merging the input schedule with the.
|
|
output schedule.
|
|
Schedules are merged to support NTDSCONN_OPT_TWOWAY_SYNC flag
|
|
on the connection object.
|
|
|
|
This function only merges the interval schedule (SCHEDULE_INTERVAL).
|
|
Other schedules are ignored and may be overwritten during merging.
|
|
|
|
|
|
Input Output Replica Resultant output schedule.
|
|
----- ------ ----------------------------------
|
|
0 0 0 Schedule is absent. Considered to be always on.
|
|
0 0 1 Schedule is absent. Use replica sets schedule.
|
|
0 1 0 Schedule is absent. Considered to be always on.
|
|
0 1 1 Schedule is present.Merge replica set schedule with the schedule on the output.
|
|
1 0 0 Schedule is present.Same as the one on input.
|
|
1 0 1 Schedule is present.Merge replica set schedule with the schedule on the input.
|
|
1 1 0 Schedule is present.Merge the input and output schedule.
|
|
1 1 1 Schedule is present.Merge the input and output schedule.
|
|
|
|
Arguments:
|
|
|
|
pISchedule - Input schedule.
|
|
pIScheduleLen - Input schedule length.
|
|
pOSchedule - Resultant schedule.
|
|
pOScheduleLen - Resultant schedule length.
|
|
pRSchedule - Default replica set schedule.
|
|
|
|
Return Value:
|
|
NONE
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsMergeTwoWaySchedules:"
|
|
|
|
UINT i;
|
|
PUCHAR IScheduleData = NULL;
|
|
PUCHAR OScheduleData = NULL;
|
|
|
|
//
|
|
// Set the location of the data in the schedule structures that are
|
|
// non-null.
|
|
//
|
|
if (*pISchedule != NULL){
|
|
for (i=0; i< (*pISchedule)->NumberOfSchedules ; ++i) {
|
|
if ((*pISchedule)->Schedules[i].Type == SCHEDULE_INTERVAL) {
|
|
|
|
IScheduleData = ((PUCHAR)*pISchedule) + (*pISchedule)->Schedules[i].Offset;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (*pOSchedule != NULL){
|
|
for (i=0; i< (*pOSchedule)->NumberOfSchedules ; ++i) {
|
|
if ((*pOSchedule)->Schedules[i].Type == SCHEDULE_INTERVAL) {
|
|
|
|
OScheduleData = ((PUCHAR)*pOSchedule) + (*pOSchedule)->Schedules[i].Offset;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// If there is no output schedule then copy the schedule
|
|
// from input to output if there is one on input. Now if there
|
|
// is a schedule on the replica set merge it with the new ouput
|
|
// schedule.
|
|
//
|
|
if (*pOSchedule == NULL || OScheduleData == NULL) {
|
|
|
|
if (*pISchedule == NULL) {
|
|
return;
|
|
}
|
|
|
|
*pOScheduleLen = *pIScheduleLen;
|
|
*pOSchedule = FrsAlloc(*pOScheduleLen);
|
|
CopyMemory(*pOSchedule, *pISchedule, *pOScheduleLen);
|
|
|
|
if (*pRSchedule == NULL) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Update the location of output schedule data.
|
|
//
|
|
for (i=0; i< (*pOSchedule)->NumberOfSchedules ; ++i) {
|
|
if ((*pOSchedule)->Schedules[i].Type == SCHEDULE_INTERVAL) {
|
|
OScheduleData = ((PUCHAR)*pOSchedule) + (*pOSchedule)->Schedules[i].Offset;
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Update the location of input schedule data.
|
|
//
|
|
for (i=0; i< (*pRSchedule)->NumberOfSchedules ; ++i) {
|
|
if ((*pRSchedule)->Schedules[i].Type == SCHEDULE_INTERVAL) {
|
|
IScheduleData = ((PUCHAR)*pRSchedule) + (*pRSchedule)->Schedules[i].Offset;
|
|
break;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// If there is no input schedule then check if there is a schedule
|
|
// on the replica set. If there is then merge that with the output schedule.
|
|
//
|
|
if ((*pISchedule == NULL || IScheduleData == NULL)) {
|
|
|
|
//
|
|
// Update the location of input schedule data. Pick it from replica set.
|
|
//
|
|
if (*pRSchedule != NULL) {
|
|
for (i=0; i< (*pRSchedule)->NumberOfSchedules ; ++i) {
|
|
if ((*pRSchedule)->Schedules[i].Type == SCHEDULE_INTERVAL) {
|
|
IScheduleData = ((PUCHAR)*pRSchedule) + (*pRSchedule)->Schedules[i].Offset;
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
|
|
*pOSchedule = FrsFree(*pOSchedule);
|
|
*pOScheduleLen = 0;
|
|
return;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
for (i=0 ; i<7*24 ; ++i) {
|
|
*(OScheduleData + i) = *(OScheduleData + i) | *(IScheduleData + i);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
DWORD
|
|
FrsDsGetSysvolCxtions(
|
|
IN PLDAP Ldap,
|
|
IN PWCHAR SetDn,
|
|
IN PWCHAR MemberRef,
|
|
IN PCONFIG_NODE Parent,
|
|
IN PCONFIG_NODE Computer
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Fetch the members for the replica set identified by Base.
|
|
|
|
Part of NewDs poll APIs.
|
|
|
|
Arguments:
|
|
ldap : Handle to DS.
|
|
SetDn : Dn of the set being processed.
|
|
MemberRef : MemberRef from the subscriber object.
|
|
Parent : Pointer to the set node in the config tree that is being built,
|
|
|
|
Return Value:
|
|
WIN32 Status
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsGetSysvolCxtions:"
|
|
PWCHAR Attrs[7];
|
|
PLDAPMessage Entry; // Opaque stuff from ldap subsystem
|
|
PCONFIG_NODE Node = NULL; // generic node for the tree
|
|
PCONFIG_NODE Subscriber;
|
|
PCONFIG_NODE PartnerNode = NULL;
|
|
PCONFIG_NODE MemberNode = NULL;
|
|
PCONFIG_NODE Cxtion = NULL;
|
|
DWORD WStatus = ERROR_SUCCESS;
|
|
PVOID Key = NULL;
|
|
PWCHAR TempFilter = NULL;
|
|
FRS_LDAP_SEARCH_CONTEXT FrsSearchContext;
|
|
PWCHAR SettingsDn = NULL;
|
|
|
|
MK_ATTRS_6(Attrs, ATTR_OBJECT_GUID, ATTR_DN, ATTR_SCHEDULE, ATTR_USN_CHANGED,
|
|
ATTR_SERVER_REF, ATTR_COMPUTER_REF);
|
|
|
|
//
|
|
// Initialize the CxtionTable. We discard the table once we have
|
|
// loaded the replica set. We use the same variables for
|
|
// every replica set.
|
|
//
|
|
if (CxtionTable != NULL) {
|
|
CxtionTable = GTabFreeTable(CxtionTable, NULL);
|
|
}
|
|
|
|
CxtionTable = GTabAllocStringAndBoolTable();
|
|
|
|
|
|
//
|
|
// Initialize the MemberTable. We discard the table once we have
|
|
// loaded the replica set. We use the same variables for
|
|
// every replica set.
|
|
//
|
|
if (MemberTable != NULL) {
|
|
MemberTable = GTabFreeTable(MemberTable, NULL);
|
|
}
|
|
|
|
MemberTable = GTabAllocStringTable();
|
|
|
|
//
|
|
// We will form the MemberSearchFilter for this replica set.
|
|
//
|
|
if (MemberSearchFilter != NULL) {
|
|
MemberSearchFilter = FrsFree(MemberSearchFilter);
|
|
}
|
|
|
|
//
|
|
// We have to first get our member object to get the serverreference to
|
|
// know where to go to get the connections.
|
|
//
|
|
if (!FrsDsLdapSearchInit(Ldap, MemberRef, LDAP_SCOPE_BASE, CATEGORY_ANY,
|
|
Attrs, 0, &FrsSearchContext)) {
|
|
return ERROR_ACCESS_DENIED;
|
|
}
|
|
if (FrsSearchContext.EntriesInPage == 0) {
|
|
DPRINT1(1, ":DS: WARN - No member object found for member %ws!\n", MemberRef);
|
|
}
|
|
//
|
|
// Scan the entries returned from ldap_search
|
|
//
|
|
for (Entry = FrsDsLdapSearchNext(Ldap, &FrsSearchContext);
|
|
Entry != NULL && WIN_SUCCESS(WStatus);
|
|
Entry = FrsDsLdapSearchNext(Ldap, &FrsSearchContext)) {
|
|
|
|
//
|
|
// Basic node info (guid, name, dn, schedule, and usnchanged)
|
|
//
|
|
Node = FrsDsAllocBasicNode(Ldap, Entry, CONFIG_TYPE_MEMBER);
|
|
if (!Node) {
|
|
DPRINT(0, ":DS: Member lacks basic info; skipping\n");
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// NTDS Settings (DSA) Reference.
|
|
//
|
|
Node->SettingsDn = FrsDsFindValue(Ldap, Entry, ATTR_SERVER_REF);
|
|
if (Node->SettingsDn == NULL) {
|
|
DPRINT1(0, ":DS: WARN - Member (%ws) of sysvol replica set lacks server reference; skipping\n", Node->Dn);
|
|
Node->Consistent = FALSE;
|
|
|
|
//
|
|
// Add to the poll summary event log.
|
|
//
|
|
FrsDsAddToPollSummary3ws(IDS_POLL_SUM_INVALID_ATTRIBUTE, ATTR_MEMBER,
|
|
Node->Dn, ATTR_SERVER_REF);
|
|
|
|
Node = FrsFreeType(Node);
|
|
continue;
|
|
}
|
|
|
|
FRS_WCSLWR(Node->SettingsDn);
|
|
FrsFree(SettingsDn);
|
|
SettingsDn = FrsWcsDup(Node->SettingsDn);
|
|
//
|
|
// Computer Reference
|
|
//
|
|
Node->ComputerDn = FrsDsFindValue(Ldap, Entry, ATTR_COMPUTER_REF);
|
|
if (Node->ComputerDn == NULL) {
|
|
DPRINT1(0, ":DS: WARN - Member (%ws) of sysvol replica set lacks computer reference; skipping\n", Node->Dn);
|
|
Node->Consistent = FALSE;
|
|
|
|
//
|
|
// Add to the poll summary event log.
|
|
//
|
|
FrsDsAddToPollSummary3ws(IDS_POLL_SUM_INVALID_ATTRIBUTE, ATTR_MEMBER,
|
|
Node->Dn, ATTR_COMPUTER_REF);
|
|
|
|
Node = FrsFreeType(Node);
|
|
continue;
|
|
}
|
|
|
|
FRS_WCSLWR(Node->ComputerDn);
|
|
|
|
//
|
|
// Link into config and add to the running checksum
|
|
//
|
|
FrsDsTreeLink(Parent, Node);
|
|
|
|
//
|
|
// Insert the new member in the member table only if it is not there already.
|
|
// For sysvols insert the members with their settingsdn as the primary key
|
|
// because that is what is stored in the cxtion->PartnerDn structure at this time.
|
|
//
|
|
GTabInsertUniqueEntry(MemberTable, Node, Node->SettingsDn, NULL);
|
|
|
|
FRS_PRINT_TYPE_DEBSUB(5, ":DS: NodeMember", Node);
|
|
|
|
}
|
|
FrsDsLdapSearchClose(&FrsSearchContext);
|
|
|
|
//
|
|
// We can't do any further processing if the Node is not consistent.
|
|
//
|
|
if (Node == NULL || !Node->Consistent) {
|
|
FrsFree(SettingsDn);
|
|
return ERROR_INVALID_DATA;
|
|
}
|
|
|
|
//
|
|
// Get the outbound connections.
|
|
//
|
|
WStatus = FrsDsGetSysvolOutboundCxtions(Ldap, SettingsDn);
|
|
if (!WIN_SUCCESS(WStatus)) {
|
|
FrsFree(SettingsDn);
|
|
return WStatus;
|
|
}
|
|
|
|
//
|
|
// Get the inbound connections.
|
|
//
|
|
WStatus = FrsDsGetSysvolInboundCxtions(Ldap, SettingsDn);
|
|
if (!WIN_SUCCESS(WStatus)) {
|
|
FrsFree(SettingsDn);
|
|
return WStatus;
|
|
}
|
|
|
|
//
|
|
// The above two calls build the MemberFilter.
|
|
// MemberFilter is used to search the DS for all the member objects of
|
|
// interest. If there are no connections from or to this member then
|
|
// the filter will be NULL.
|
|
//
|
|
if (MemberSearchFilter == NULL) {
|
|
//
|
|
// Is this member linked to this computer
|
|
//
|
|
MemberNode = Node;
|
|
|
|
Subscriber = GTabLookupTableString(SubscriberTable, MemberNode->Dn, NULL);
|
|
//
|
|
// Yep; have a suscriber
|
|
//
|
|
if (Subscriber != NULL) {
|
|
MemberNode->ThisComputer = TRUE;
|
|
MemberNode->Root = FrsWcsDup(Subscriber->Root);
|
|
MemberNode->Stage = FrsWcsDup(Subscriber->Stage);
|
|
FRS_WCSLWR(MemberNode->Root);
|
|
FRS_WCSLWR(MemberNode->Stage);
|
|
MemberNode->DnsName = FrsWcsDup(Computer->DnsName);
|
|
|
|
}
|
|
FrsFree(SettingsDn);
|
|
return ERROR_SUCCESS;
|
|
} else {
|
|
//
|
|
// Add the closing ')' to the MemberSearchFilter.
|
|
//
|
|
TempFilter = FrsAlloc((wcslen(MemberSearchFilter) + wcslen(L")") + 1 ) * sizeof(WCHAR));
|
|
wcscpy(TempFilter, MemberSearchFilter);
|
|
wcscat(TempFilter, L")");
|
|
FrsFree(MemberSearchFilter);
|
|
MemberSearchFilter = TempFilter;
|
|
TempFilter = NULL;
|
|
}
|
|
|
|
if (!FrsDsLdapSearchInit(Ldap, SetDn, LDAP_SCOPE_ONELEVEL, MemberSearchFilter,
|
|
Attrs, 0, &FrsSearchContext)) {
|
|
FrsFree(SettingsDn);
|
|
return ERROR_ACCESS_DENIED;
|
|
}
|
|
if (FrsSearchContext.EntriesInPage == 0) {
|
|
DPRINT1(1, ":DS: WARN - No member objects of interest found under %ws!\n", SetDn);
|
|
}
|
|
//
|
|
// Scan the entries returned from ldap_search
|
|
//
|
|
for (Entry = FrsDsLdapSearchNext(Ldap, &FrsSearchContext);
|
|
Entry != NULL && WIN_SUCCESS(WStatus);
|
|
Entry = FrsDsLdapSearchNext(Ldap, &FrsSearchContext)) {
|
|
|
|
//
|
|
// Basic node info (guid, name, dn, schedule, and usnchanged)
|
|
//
|
|
Node = FrsDsAllocBasicNode(Ldap, Entry, CONFIG_TYPE_MEMBER);
|
|
if (!Node) {
|
|
DPRINT(0, ":DS: Member lacks basic info; skipping\n");
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// NTDS Settings (DSA) Reference.
|
|
//
|
|
Node->SettingsDn = FrsDsFindValue(Ldap, Entry, ATTR_SERVER_REF);
|
|
if (Node->SettingsDn == NULL) {
|
|
DPRINT1(0, ":DS: WARN - Member (%ws) of sysvol replica set lacks server reference; skipping\n", Node->Dn);
|
|
Node->Consistent = FALSE;
|
|
//
|
|
// Add to the poll summary event log.
|
|
//
|
|
FrsDsAddToPollSummary3ws(IDS_POLL_SUM_INVALID_ATTRIBUTE, ATTR_MEMBER,
|
|
Node->Dn, ATTR_SERVER_REF);
|
|
|
|
Node = FrsFreeType(Node);
|
|
continue;
|
|
}
|
|
|
|
FRS_WCSLWR(Node->SettingsDn);
|
|
|
|
//
|
|
// Computer Reference
|
|
//
|
|
Node->ComputerDn = FrsDsFindValue(Ldap, Entry, ATTR_COMPUTER_REF);
|
|
if (Node->ComputerDn == NULL) {
|
|
DPRINT1(0, ":DS: WARN - Member (%ws) of sysvol replica set lacks computer reference; skipping\n", Node->Dn);
|
|
//
|
|
// Add to the poll summary event log.
|
|
//
|
|
FrsDsAddToPollSummary3ws(IDS_POLL_SUM_INVALID_ATTRIBUTE, ATTR_MEMBER,
|
|
Node->Dn, ATTR_COMPUTER_REF);
|
|
|
|
Node = FrsFreeType(Node);
|
|
continue;
|
|
}
|
|
|
|
FRS_WCSLWR(Node->ComputerDn);
|
|
|
|
//
|
|
// Link into config and add to the running checksum
|
|
//
|
|
FrsDsTreeLink(Parent, Node);
|
|
|
|
//
|
|
// Insert the new member in the member table only if it is not there already.
|
|
// For sysvols insert the members with their settingsdn as the primary key
|
|
// because that is what is stored in the cxtion->PartnerDn structure at this time.
|
|
//
|
|
GTabInsertUniqueEntry(MemberTable, Node, Node->SettingsDn, NULL);
|
|
|
|
//
|
|
// Make a table of computers of interest to us so we can search for all
|
|
// the computers of interest at one time after we have polled all
|
|
// replica sets. Put empty entries in the table at this point.
|
|
// Do not add our computer in this table as we already have info about
|
|
// our computer.
|
|
//
|
|
if (WSTR_NE(Node->ComputerDn, Computer->Dn)) {
|
|
//
|
|
// This is not our computer. Add it to the table if it isn't already in the table.
|
|
//
|
|
PartnerNode = GTabLookupTableString(PartnerComputerTable, Node->ComputerDn, NULL);
|
|
if (PartnerNode == NULL) {
|
|
//
|
|
// There are no duplicates so enter this computer name in the table.
|
|
//
|
|
PartnerNode = FrsDsAllocBasicNode(Ldap, NULL, CONFIG_TYPE_COMPUTER);
|
|
PartnerNode->Dn = FrsWcsDup(Node->ComputerDn);
|
|
PartnerNode->MemberDn = FrsWcsDup(Node->Dn);
|
|
GTabInsertUniqueEntry(PartnerComputerTable, PartnerNode, PartnerNode->Dn, NULL);
|
|
}
|
|
}
|
|
|
|
FRS_PRINT_TYPE_DEBSUB(5, ":DS: NodeMember", Node);
|
|
|
|
}
|
|
FrsDsLdapSearchClose(&FrsSearchContext);
|
|
|
|
//
|
|
// Link the inbound and outbound connections to our member node.
|
|
//
|
|
MemberNode = GTabLookupTableString(MemberTable, SettingsDn, NULL);
|
|
if (MemberNode != NULL) {
|
|
//
|
|
// Is this member linked to this computer
|
|
//
|
|
Subscriber = GTabLookupTableString(SubscriberTable, MemberNode->Dn, NULL);
|
|
//
|
|
// Yep; have a suscriber
|
|
//
|
|
if (Subscriber != NULL) {
|
|
MemberNode->ThisComputer = TRUE;
|
|
MemberNode->Root = FrsWcsDup(Subscriber->Root);
|
|
MemberNode->Stage = FrsWcsDup(Subscriber->Stage);
|
|
FRS_WCSLWR(MemberNode->Root);
|
|
FRS_WCSLWR(MemberNode->Stage);
|
|
MemberNode->DnsName = FrsWcsDup(Computer->DnsName);
|
|
|
|
//
|
|
// This is us. Link all the cxtions to this Member.
|
|
//
|
|
if (CxtionTable != NULL) {
|
|
Key = NULL;
|
|
while ((Cxtion = GTabNextDatum(CxtionTable, &Key)) != NULL) {
|
|
//
|
|
// Get our Partners Node from the member table.
|
|
//
|
|
PartnerNode = GTabLookupTableString(MemberTable, Cxtion->PartnerDn, NULL);
|
|
if (PartnerNode != NULL) {
|
|
Cxtion->PartnerName = FrsDupGName(PartnerNode->Name);
|
|
Cxtion->PartnerCoDn = FrsWcsDup(PartnerNode->ComputerDn);
|
|
} else {
|
|
//
|
|
// This Cxtion does not have a valid member object for its
|
|
// partner. E.g. A sysvol topology that has connections under
|
|
// the NTDSSettings objects but there are no corresponding
|
|
// member objects.
|
|
//
|
|
DPRINT1(0, ":DS: Marking connection inconsistent.(%ws)\n",Cxtion->Dn);
|
|
Cxtion->Consistent = FALSE;
|
|
}
|
|
FrsDsTreeLink(MemberNode, Cxtion);
|
|
}
|
|
CxtionTable = GTabFreeTable(CxtionTable,NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
FrsFree(SettingsDn);
|
|
return WStatus;
|
|
}
|
|
|
|
|
|
DWORD
|
|
FrsDsGetNonSysvolCxtions(
|
|
IN PLDAP Ldap,
|
|
IN PWCHAR SetDn,
|
|
IN PWCHAR MemberRef,
|
|
IN PCONFIG_NODE Parent,
|
|
IN PCONFIG_NODE Computer
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Fetch the members and connections for the replica set identified by Base.
|
|
|
|
Part of NewDs poll APIs.
|
|
|
|
Arguments:
|
|
ldap : Handle to DS.
|
|
SetDn : Dn of the set being processed.
|
|
MemberRef : MemberRef from the subscriber object.
|
|
Parent : Pointer to the set node in the config tree that is being built,
|
|
|
|
Return Value:
|
|
WIN32 Status
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsGetNonSysvolCxtions:"
|
|
PWCHAR Attrs[7];
|
|
PLDAPMessage Entry; // Opaque stuff from ldap subsystem
|
|
PCONFIG_NODE Node; // generic node for the tree
|
|
PCONFIG_NODE Subscriber;
|
|
PCONFIG_NODE PartnerNode = NULL;
|
|
PCONFIG_NODE MemberNode = NULL;
|
|
PCONFIG_NODE Cxtion = NULL;
|
|
DWORD WStatus = ERROR_SUCCESS;
|
|
PVOID Key = NULL;
|
|
PWCHAR MemberCn = NULL;
|
|
PWCHAR TempFilter = NULL;
|
|
FRS_LDAP_SEARCH_CONTEXT FrsSearchContext;
|
|
|
|
//
|
|
// MemberRef must be non-NULL.
|
|
//
|
|
if(MemberRef == NULL) {
|
|
return ERROR_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// Initialize the CxtionTable. We discard the table once we have
|
|
// loaded the replica set. We use the same variables for
|
|
// every replica set.
|
|
//
|
|
if (CxtionTable != NULL) {
|
|
CxtionTable = GTabFreeTable(CxtionTable, NULL);
|
|
}
|
|
|
|
CxtionTable = GTabAllocStringAndBoolTable();
|
|
|
|
|
|
//
|
|
// Initialize the MemberTable. We discard the table once we have
|
|
// loaded the replica set. We use the same variables for
|
|
// every replica set.
|
|
//
|
|
if (MemberTable != NULL) {
|
|
MemberTable = GTabFreeTable(MemberTable, NULL);
|
|
}
|
|
|
|
MemberTable = GTabAllocStringTable();
|
|
|
|
//
|
|
// We will form the MemberSearchFilter for this replica set.
|
|
//
|
|
if (MemberSearchFilter != NULL) {
|
|
MemberSearchFilter = FrsFree(MemberSearchFilter);
|
|
}
|
|
|
|
//
|
|
// Add this members name to the member search filter.
|
|
//
|
|
|
|
MemberCn = FrsDsMakeRdn(MemberRef);
|
|
MemberSearchFilter = FrsAlloc((wcslen(L"(|(=)" ATTR_CN) +
|
|
wcslen(MemberCn) + 1 ) * sizeof(WCHAR));
|
|
wcscpy(MemberSearchFilter, L"(|(" ATTR_CN L"=");
|
|
wcscat(MemberSearchFilter, MemberCn);
|
|
wcscat(MemberSearchFilter, L")");
|
|
|
|
MemberCn = FrsFree(MemberCn);
|
|
|
|
|
|
//
|
|
// Get the outbound connections.
|
|
//
|
|
WStatus = FrsDsGetNonSysvolOutboundCxtions(Ldap, SetDn, MemberRef);
|
|
if (!WIN_SUCCESS(WStatus)) {
|
|
return WStatus;
|
|
}
|
|
|
|
//
|
|
// Get the inbound connections.
|
|
//
|
|
WStatus = FrsDsGetNonSysvolInboundCxtions(Ldap, SetDn, MemberRef);
|
|
if (!WIN_SUCCESS(WStatus)) {
|
|
return WStatus;
|
|
}
|
|
|
|
//
|
|
// The above twp calls build the MemberFilter.
|
|
// MemberFilter is used to search the DS for all the member objects of
|
|
// interest. If there are no connections from or to this member then
|
|
// the filter will will just have 1 entry.
|
|
//
|
|
//
|
|
// Add the closing ')' to the MemberSearchFilter.
|
|
//
|
|
TempFilter = FrsAlloc((wcslen(MemberSearchFilter) + wcslen(L")") + 1 ) * sizeof(WCHAR));
|
|
wcscpy(TempFilter, MemberSearchFilter);
|
|
wcscat(TempFilter, L")");
|
|
FrsFree(MemberSearchFilter);
|
|
MemberSearchFilter = TempFilter;
|
|
TempFilter = NULL;
|
|
|
|
MK_ATTRS_6(Attrs, ATTR_OBJECT_GUID, ATTR_DN, ATTR_SCHEDULE, ATTR_USN_CHANGED,
|
|
ATTR_SERVER_REF, ATTR_COMPUTER_REF);
|
|
|
|
if (!FrsDsLdapSearchInit(Ldap, SetDn, LDAP_SCOPE_ONELEVEL, MemberSearchFilter,
|
|
Attrs, 0, &FrsSearchContext)) {
|
|
return ERROR_ACCESS_DENIED;
|
|
}
|
|
if (FrsSearchContext.EntriesInPage == 0) {
|
|
DPRINT1(1, ":DS: WARN - No member objects of interest found under %ws!\n", SetDn);
|
|
}
|
|
//
|
|
// Scan the entries returned from ldap_search
|
|
//
|
|
for (Entry = FrsDsLdapSearchNext(Ldap, &FrsSearchContext);
|
|
Entry != NULL && WIN_SUCCESS(WStatus);
|
|
Entry = FrsDsLdapSearchNext(Ldap, &FrsSearchContext)) {
|
|
|
|
//
|
|
// Basic node info (guid, name, dn, schedule, and usnchanged)
|
|
//
|
|
Node = FrsDsAllocBasicNode(Ldap, Entry, CONFIG_TYPE_MEMBER);
|
|
if (!Node) {
|
|
DPRINT(4, ":DS: Member lacks basic info; skipping\n");
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Computer Reference
|
|
//
|
|
Node->ComputerDn = FrsDsFindValue(Ldap, Entry, ATTR_COMPUTER_REF);
|
|
if (Node->ComputerDn == NULL) {
|
|
DPRINT1(4, ":DS: WARN - Member (%ws) lacks computer reference; skipping\n", Node->Dn);
|
|
//
|
|
// Add to the poll summary event log.
|
|
//
|
|
FrsDsAddToPollSummary3ws(IDS_POLL_SUM_INVALID_ATTRIBUTE, ATTR_MEMBER,
|
|
Node->Dn, ATTR_COMPUTER_REF);
|
|
|
|
Node = FrsFreeType(Node);
|
|
continue;
|
|
}
|
|
|
|
FRS_WCSLWR(Node->ComputerDn);
|
|
|
|
//
|
|
// Link into config and add to the running checksum
|
|
//
|
|
FrsDsTreeLink(Parent, Node);
|
|
|
|
//
|
|
// Insert the new member in the member table only if it is not there already.
|
|
//
|
|
GTabInsertUniqueEntry(MemberTable, Node, Node->Dn, NULL);
|
|
|
|
//
|
|
// Make a table of computers of interest to us so we can search for all
|
|
// the computers of interest at one time after we have polled all
|
|
// replica sets. Put empty entries in the table at this point.
|
|
// Do not add our computer in this table as we already have info about
|
|
// our computer.
|
|
//
|
|
if (WSTR_NE(Node->ComputerDn, Computer->Dn)) {
|
|
//
|
|
// This is not our computer. Add it to the table if it isn't already in the table.
|
|
//
|
|
PartnerNode = GTabLookupTableString(PartnerComputerTable, Node->ComputerDn, NULL);
|
|
if (PartnerNode == NULL) {
|
|
//
|
|
// There are no duplicates so enter this computer name in the table.
|
|
//
|
|
PartnerNode = FrsDsAllocBasicNode(Ldap, NULL, CONFIG_TYPE_COMPUTER);
|
|
PartnerNode->Dn = FrsWcsDup(Node->ComputerDn);
|
|
PartnerNode->MemberDn = FrsWcsDup(Node->Dn);
|
|
GTabInsertUniqueEntry(PartnerComputerTable, PartnerNode, PartnerNode->Dn, NULL);
|
|
}
|
|
}
|
|
|
|
FRS_PRINT_TYPE_DEBSUB(5, ":DS: NodeMember", Node);
|
|
|
|
}
|
|
FrsDsLdapSearchClose(&FrsSearchContext);
|
|
|
|
//
|
|
// Link the inbound and outbound connections to our member node.
|
|
//
|
|
MemberNode = GTabLookupTableString(MemberTable, MemberRef, NULL);
|
|
if (MemberNode != NULL) {
|
|
//
|
|
// Is this member linked to this computer
|
|
//
|
|
Subscriber = GTabLookupTableString(SubscriberTable, MemberNode->Dn, NULL);
|
|
//
|
|
// Yep; have a suscriber
|
|
//
|
|
if (Subscriber != NULL) {
|
|
MemberNode->ThisComputer = TRUE;
|
|
MemberNode->Root = FrsWcsDup(Subscriber->Root);
|
|
MemberNode->Stage = FrsWcsDup(Subscriber->Stage);
|
|
FRS_WCSLWR(MemberNode->Root);
|
|
FRS_WCSLWR(MemberNode->Stage);
|
|
MemberNode->DnsName = FrsWcsDup(Computer->DnsName);
|
|
|
|
//
|
|
// This is us. Link all the cxtions to this Member.
|
|
//
|
|
if (CxtionTable != NULL) {
|
|
Key = NULL;
|
|
while ((Cxtion = GTabNextDatum(CxtionTable, &Key)) != NULL) {
|
|
//
|
|
// Get our Partners Node from the member table.
|
|
//
|
|
PartnerNode = GTabLookupTableString(MemberTable, Cxtion->PartnerDn, NULL);
|
|
if (PartnerNode != NULL) {
|
|
Cxtion->PartnerName = FrsDupGName(PartnerNode->Name);
|
|
Cxtion->PartnerCoDn = FrsWcsDup(PartnerNode->ComputerDn);
|
|
} else {
|
|
//
|
|
// This Cxtion does not have a valid member object for its
|
|
// partner. E.g. A sysvol topology that has connections under
|
|
// the NTDSSettings objects but there are no corresponding
|
|
// member objects.
|
|
//
|
|
DPRINT1(0, ":DS: Marking connection inconsistent.(%ws)\n",Cxtion->Dn);
|
|
Cxtion->Consistent = FALSE;
|
|
}
|
|
FrsDsTreeLink(MemberNode, Cxtion);
|
|
}
|
|
CxtionTable = GTabFreeTable(CxtionTable,NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
return WStatus;
|
|
}
|
|
|
|
|
|
DWORD
|
|
FrsDsGetSets(
|
|
IN PLDAP Ldap,
|
|
IN PWCHAR SetDnAddr,
|
|
IN PWCHAR MemberRef,
|
|
IN PCONFIG_NODE Parent,
|
|
IN PCONFIG_NODE Computer
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Recursively scan the DS tree beginning at
|
|
configuration\sites\settings\sets.
|
|
|
|
Part of NewDs poll APIs.
|
|
|
|
Arguments:
|
|
ldap - opened and bound ldap connection
|
|
SetDnAddr - From member reference from subscriber
|
|
Parent - Container which contains Base
|
|
Computer - for member back links
|
|
|
|
Return Value:
|
|
ERROR_SUCCESS - config fetched successfully
|
|
Otherwise - couldn't get the DS config
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsGetSets:"
|
|
PLDAPMessage Entry; // Opaque stuff from ldap subsystem
|
|
PCONFIG_NODE Node; // generic node for the tree
|
|
DWORD i;
|
|
DWORD WStatus = ERROR_SUCCESS;
|
|
FRS_LDAP_SEARCH_CONTEXT FrsSearchContext;
|
|
|
|
PWCHAR FlagsWStr = NULL;
|
|
PWCHAR Attrs[10];
|
|
|
|
//
|
|
// Have we processed this set before? If we have then don't process
|
|
// it again. This check prevents two subscribers to point to
|
|
// different member objects that are members of the same set.
|
|
//
|
|
Node = GTabLookupTableString(SetTable, SetDnAddr, NULL);
|
|
|
|
if (Node) {
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
//
|
|
// Search the DS beginning at Base for sets (objectCategory=nTFRSReplicaSet)
|
|
//
|
|
MK_ATTRS_9(Attrs, ATTR_OBJECT_GUID, ATTR_DN, ATTR_SCHEDULE, ATTR_USN_CHANGED, ATTR_FRS_FLAGS,
|
|
ATTR_SET_TYPE, ATTR_PRIMARY_MEMBER, ATTR_FILE_FILTER, ATTR_DIRECTORY_FILTER);
|
|
|
|
if (!FrsDsLdapSearchInit(Ldap, SetDnAddr, LDAP_SCOPE_BASE, CATEGORY_REPLICA_SET,
|
|
Attrs, 0, &FrsSearchContext)) {
|
|
return ERROR_ACCESS_DENIED;
|
|
}
|
|
if (FrsSearchContext.EntriesInPage == 0) {
|
|
DPRINT1(1, ":DS: WARN - No replica set objects found under %ws!\n", SetDnAddr);
|
|
}
|
|
|
|
//
|
|
// Scan the entries returned from ldap_search
|
|
//
|
|
for (Entry = FrsDsLdapSearchNext(Ldap, &FrsSearchContext);
|
|
Entry != NULL && WIN_SUCCESS(WStatus);
|
|
Entry = FrsDsLdapSearchNext(Ldap, &FrsSearchContext)) {
|
|
|
|
//
|
|
// Basic node info (guid, name, dn, schedule, and usnchanged)
|
|
//
|
|
Node = FrsDsAllocBasicNode(Ldap, Entry, CONFIG_TYPE_REPLICA_SET);
|
|
if (!Node) {
|
|
DPRINT(4, ":DS: Set lacks basic info; skipping\n");
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Replica set type
|
|
//
|
|
Node->SetType = FrsDsFindValue(Ldap, Entry, ATTR_SET_TYPE);
|
|
|
|
//
|
|
// Check the set type. It has to be one that we recognize.
|
|
//
|
|
if ((Node->SetType == NULL) ||
|
|
(WSTR_NE(Node->SetType, FRS_RSTYPE_OTHERW) &&
|
|
WSTR_NE(Node->SetType, FRS_RSTYPE_DFSW) &&
|
|
WSTR_NE(Node->SetType, FRS_RSTYPE_DOMAIN_SYSVOLW) &&
|
|
WSTR_NE(Node->SetType, FRS_RSTYPE_ENTERPRISE_SYSVOLW))){
|
|
|
|
DPRINT1(4, ":DS: ERROR - Invalid Set type for (%ws)\n", Node->Dn);
|
|
|
|
//
|
|
// Add to the poll summary event log.
|
|
//
|
|
FrsDsAddToPollSummary3ws(IDS_POLL_SUM_INVALID_ATTRIBUTE, ATTR_REPLICA_SET,
|
|
Node->Dn, ATTR_SET_TYPE);
|
|
|
|
Node = FrsFreeType(Node);
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Primary member
|
|
//
|
|
Node->MemberDn = FrsDsFindValue(Ldap, Entry, ATTR_PRIMARY_MEMBER);
|
|
|
|
//
|
|
// File filter
|
|
//
|
|
Node->FileFilterList = FrsDsFindValue(Ldap, Entry, ATTR_FILE_FILTER);
|
|
|
|
//
|
|
// Directory filter
|
|
//
|
|
Node->DirFilterList = FrsDsFindValue(Ldap, Entry, ATTR_DIRECTORY_FILTER);
|
|
|
|
//
|
|
// Read the FRS Flags value.
|
|
//
|
|
FlagsWStr = FrsDsFindValue(Ldap, Entry, ATTR_FRS_FLAGS);
|
|
if (FlagsWStr != NULL) {
|
|
Node->FrsRsoFlags = _wtoi(FlagsWStr);
|
|
FlagsWStr = FrsFree(FlagsWStr);
|
|
} else {
|
|
Node->FrsRsoFlags = 0;
|
|
}
|
|
|
|
//
|
|
// Link into config and add to the running checksum
|
|
//
|
|
FrsDsTreeLink(Parent, Node);
|
|
|
|
//
|
|
// Insert into the table of sets. We checked for duplicates above with
|
|
// GTabLookupTableString so there should not be any duplicates.
|
|
//
|
|
FRS_ASSERT(GTabInsertUniqueEntry(SetTable, Node, Node->Dn, NULL) == NULL);
|
|
|
|
FRS_PRINT_TYPE_DEBSUB(5, ":DS: NodeSet", Node);
|
|
|
|
//
|
|
// Get the replica set topology. We have to look at different places
|
|
// in the DS depending on the type of replica set. The cxtions for sysvol
|
|
// replica set are generated by KCC and they reside under the server object
|
|
// for the DC. We use the serverReference from the member object to get
|
|
// there.
|
|
//
|
|
if (FRS_RSTYPE_IS_SYSVOLW(Node->SetType)) {
|
|
WStatus = FrsDsGetSysvolCxtions(Ldap, SetDnAddr, MemberRef, Node, Computer);
|
|
} else {
|
|
WStatus = FrsDsGetNonSysvolCxtions(Ldap, SetDnAddr, MemberRef, Node, Computer);
|
|
}
|
|
|
|
}
|
|
FrsDsLdapSearchClose(&FrsSearchContext);
|
|
|
|
return WStatus;
|
|
}
|
|
|
|
|
|
DWORD
|
|
FrsDsGetSettings(
|
|
IN PLDAP Ldap,
|
|
IN PWCHAR MemberRef,
|
|
IN PCONFIG_NODE Parent,
|
|
IN PCONFIG_NODE Computer
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Scan the DS tree for NTFRS-Settings objects and their servers
|
|
|
|
Part of NewDs poll APIs.
|
|
|
|
Arguments:
|
|
ldap - opened and bound ldap connection
|
|
MemberRef - From the subscriber member reference
|
|
Parent - Container which contains Base
|
|
Computer
|
|
|
|
Return Value:
|
|
WIN32 Status
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsGetSettings:"
|
|
PWCHAR Attrs[5];
|
|
PLDAPMessage Entry; // Opaque stuff from ldap subsystem
|
|
PCONFIG_NODE Node; // generic node for the tree
|
|
PWCHAR MemberDnAddr;
|
|
PWCHAR SetDnAddr;
|
|
PWCHAR SettingsDnAddr;
|
|
FRS_LDAP_SEARCH_CONTEXT FrsSearchContext;
|
|
DWORD WStatus = ERROR_SUCCESS;
|
|
|
|
//
|
|
// Find the member component
|
|
//
|
|
MemberDnAddr = wcsstr(MemberRef, L"cn=");
|
|
if (!MemberDnAddr) {
|
|
DPRINT1(0, ":DS: ERROR - Missing member component in %ws\n", MemberRef);
|
|
return ERROR_ACCESS_DENIED;
|
|
}
|
|
//
|
|
// Find the set component
|
|
//
|
|
SetDnAddr = wcsstr(MemberDnAddr + 3, L"cn=");
|
|
if (!SetDnAddr) {
|
|
DPRINT1(0, ":DS: ERROR - Missing set component in %ws\n", MemberRef);
|
|
return ERROR_ACCESS_DENIED;
|
|
}
|
|
//
|
|
// Find the settings component
|
|
//
|
|
SettingsDnAddr = wcsstr(SetDnAddr + 3, L"cn=");
|
|
if (!SettingsDnAddr) {
|
|
DPRINT1(0, ":DS: ERROR - Missing settings component in %ws\n", MemberRef);
|
|
return ERROR_ACCESS_DENIED;
|
|
}
|
|
|
|
//
|
|
// Have we processed this settings before?
|
|
//
|
|
for (Node = Parent->Children; Node; Node = Node->Peer) {
|
|
if (WSTR_EQ(Node->Dn, SettingsDnAddr)) {
|
|
DPRINT1(4, ":DS: Settings hit on %ws\n", MemberRef);
|
|
break;
|
|
}
|
|
}
|
|
//
|
|
// Yep; get the sets
|
|
//
|
|
if (Node) {
|
|
return FrsDsGetSets(Ldap, SetDnAddr, MemberRef, Node, Computer);
|
|
}
|
|
|
|
//
|
|
// Search the DS beginning at Base for settings (objectCategory=nTFRSSettings)
|
|
//
|
|
MK_ATTRS_4(Attrs, ATTR_OBJECT_GUID, ATTR_DN, ATTR_SCHEDULE, ATTR_USN_CHANGED);
|
|
|
|
if (!FrsDsLdapSearchInit(Ldap, SettingsDnAddr, LDAP_SCOPE_BASE, CATEGORY_NTFRS_SETTINGS,
|
|
Attrs, 0, &FrsSearchContext)) {
|
|
return ERROR_ACCESS_DENIED;
|
|
}
|
|
if (FrsSearchContext.EntriesInPage == 0) {
|
|
DPRINT1(1, ":DS: WARN - No NTFRSSettings objects found under %ws!\n", SettingsDnAddr);
|
|
}
|
|
|
|
//
|
|
// Scan the entries returned from ldap_search
|
|
//
|
|
for (Entry = FrsDsLdapSearchNext(Ldap, &FrsSearchContext);
|
|
Entry != NULL && WIN_SUCCESS(WStatus);
|
|
Entry = FrsDsLdapSearchNext(Ldap, &FrsSearchContext)) {
|
|
|
|
//
|
|
// Basic node info (guid, name, dn, schedule, and usnchanged)
|
|
//
|
|
Node = FrsDsAllocBasicNode(Ldap, Entry, CONFIG_TYPE_NTFRS_SETTINGS);
|
|
if (!Node) {
|
|
DPRINT(4, ":DS: Frs Settings lacks basic info; skipping\n");
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Link into config and add to the running checksum
|
|
//
|
|
FrsDsTreeLink(Parent, Node);
|
|
FRS_PRINT_TYPE_DEBSUB(5, ":DS: NodeSettings", Node);
|
|
|
|
//
|
|
// Recurse to the next level in the DS hierarchy
|
|
//
|
|
WStatus = FrsDsGetSets(Ldap, SetDnAddr, MemberRef, Node, Computer);
|
|
}
|
|
FrsDsLdapSearchClose(&FrsSearchContext);
|
|
|
|
return WStatus;
|
|
}
|
|
|
|
|
|
DWORD
|
|
FrsDsGetServices(
|
|
IN PLDAP Ldap,
|
|
IN PCONFIG_NODE Computer,
|
|
OUT PCONFIG_NODE *Services
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Recursively scan the DS tree beginning at the settings from
|
|
the subscriber nodes.
|
|
|
|
The name is a misnomer because of evolution.
|
|
|
|
Part of NewDs poll APIs.
|
|
|
|
Arguments:
|
|
ldap - opened and bound ldap connection
|
|
Computer
|
|
Services - returned list of all Settings
|
|
|
|
Return Value:
|
|
WIN32 Status
|
|
--*/
|
|
{
|
|
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsGetServices:"
|
|
|
|
|
|
PCONFIG_NODE Node;
|
|
PCONFIG_NODE Subscriptions;
|
|
PCONFIG_NODE Subscriber;
|
|
PVOID SubKey = NULL;
|
|
DWORD WStatus = ERROR_SUCCESS;
|
|
|
|
*Services = NULL;
|
|
|
|
//
|
|
// Initialize the SubscriberTable.
|
|
//
|
|
if (SetTable != NULL) {
|
|
SetTable = GTabFreeTable(SetTable,NULL);
|
|
}
|
|
|
|
SetTable = GTabAllocStringTable();
|
|
|
|
//
|
|
// Initially, the node is assumed to be consistent
|
|
//
|
|
Node = FrsAllocType(CONFIG_NODE_TYPE);
|
|
Node->DsObjectType = CONFIG_TYPE_SERVICES_ROOT;
|
|
|
|
Node->Consistent = TRUE;
|
|
|
|
//
|
|
// Distinguished name
|
|
//
|
|
Node->Dn = FrsWcsDup(L"<<replica ds root>>");
|
|
FRS_WCSLWR(Node->Dn);
|
|
|
|
//
|
|
// Name = RDN + Object Guid
|
|
//
|
|
Node->Name = FrsBuildGName(FrsAlloc(sizeof(GUID)),
|
|
FrsWcsDup(L"<<replica ds root>>"));
|
|
|
|
FRS_PRINT_TYPE_DEBSUB(5, ":DS: NodeService", Node);
|
|
|
|
SubKey = NULL;
|
|
while ((Subscriber = GTabNextDatum(SubscriberTable, &SubKey)) != NULL) {
|
|
//
|
|
// Recurse to the next level in the DS hierarchy
|
|
//
|
|
WStatus = FrsDsGetSettings(Ldap, Subscriber->MemberDn, Node, Computer);
|
|
|
|
DPRINT1_WS(2, ":DS: WARN - Error getting topology for replica root (%ws);", Subscriber->Root, WStatus);
|
|
}
|
|
|
|
*Services = Node;
|
|
return WStatus;
|
|
}
|
|
|
|
|
|
PWCHAR
|
|
FrsDsGetDnsName(
|
|
IN PLDAP Ldap,
|
|
IN PWCHAR Dn
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Read the dNSHostName attribute from Dn
|
|
|
|
Arguments:
|
|
Ldap - opened and bound ldap connection
|
|
Dn - Base Dn for search
|
|
|
|
Return Value:
|
|
WIN32 Status
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsGetDnsName:"
|
|
PLDAPMessage LdapMsg = NULL;
|
|
PLDAPMessage LdapEntry;
|
|
PWCHAR DnsName = NULL;
|
|
PWCHAR Attrs[2];
|
|
DWORD WStatus = ERROR_SUCCESS;
|
|
|
|
//
|
|
// Search the DS beginning at Base for the entries of class (objectCategory=*)
|
|
//
|
|
|
|
MK_ATTRS_1(Attrs, ATTR_DNS_HOST_NAME);
|
|
//
|
|
// Note: Is it safe to turn off referrals re: back links?
|
|
// if so, use ldap_get/set_option in winldap.h
|
|
//
|
|
if (!FrsDsLdapSearch(Ldap, Dn, LDAP_SCOPE_BASE, CATEGORY_ANY,
|
|
Attrs, 0, &LdapMsg)) {
|
|
goto CLEANUP;
|
|
}
|
|
|
|
//
|
|
// Scan the entries returned from ldap_search
|
|
//
|
|
LdapEntry = ldap_first_entry(Ldap, LdapMsg);
|
|
if (!LdapEntry) {
|
|
goto CLEANUP;
|
|
}
|
|
|
|
//
|
|
// DNS name
|
|
//
|
|
DnsName = FrsDsFindValue(Ldap, LdapEntry, ATTR_DNS_HOST_NAME);
|
|
|
|
CLEANUP:
|
|
LDAP_FREE_MSG(LdapMsg);
|
|
DPRINT2(4, ":DS: DN %ws -> DNS %ws\n", Dn, DnsName);
|
|
return DnsName;
|
|
}
|
|
|
|
|
|
PWCHAR
|
|
FrsDsGuessPrincName(
|
|
IN PWCHAR Dn
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Derive the NT4 account name for Dn. Dn should be the Dn
|
|
of a computer object.
|
|
|
|
Arguments:
|
|
Dn
|
|
|
|
Return Value:
|
|
NT4 Account Name or NULL
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsGuessPrincName:"
|
|
DWORD Len = 0;
|
|
WCHAR HackPrincName[MAX_PATH];
|
|
PWCHAR Rdn;
|
|
PWCHAR Dc;
|
|
|
|
DPRINT1(4, ":DS: WARN: Guess NT4 Account Name for %ws\n", Dn);
|
|
|
|
//
|
|
// Computer's Dn not available
|
|
//
|
|
if (!Dn) {
|
|
return NULL;
|
|
}
|
|
Dc = wcsstr(Dn, L"dc=");
|
|
//
|
|
// No DC=?
|
|
//
|
|
if (!Dc) {
|
|
DPRINT1(4, ":DS: No DC= in %ws\n", Dn);
|
|
return NULL;
|
|
}
|
|
//
|
|
// DC= at eol?
|
|
//
|
|
Dc += 3;
|
|
if (!*Dc) {
|
|
DPRINT1(4, ":DS: No DC= at eol in %ws\n", Dn);
|
|
return NULL;
|
|
}
|
|
while (*Dc && *Dc != L',') {
|
|
HackPrincName[Len++] = *Dc++;
|
|
}
|
|
HackPrincName[Len++] = L'\\';
|
|
HackPrincName[Len++] = L'\0';
|
|
Rdn = FrsDsMakeRdn(Dn);
|
|
wcscat(HackPrincName, Rdn);
|
|
wcscat(HackPrincName, L"$");
|
|
DPRINT1(4, ":DS: Guessing %ws\n", HackPrincName);
|
|
FrsFree(Rdn);
|
|
return FrsWcsDup(HackPrincName);
|
|
}
|
|
|
|
|
|
PWCHAR
|
|
FrsDsFormUPN(
|
|
IN PWCHAR NT4AccountName,
|
|
IN PWCHAR DomainDnsName
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Forms the User Principal Name by combining the
|
|
Sam account name and the domain dns name in the form
|
|
shown below.
|
|
|
|
<SamAccountName>@<DnsDomainName>
|
|
|
|
You can get <SamAccountName> from the string to the right of the "\"
|
|
of the NT4AccountName.
|
|
|
|
Arguments:
|
|
NT4AccountName - DS_NT4_ACCOUNT_NAME returned from DsCrackNames.
|
|
DomainDnsName - Dns name of the domain.
|
|
|
|
Return Value:
|
|
Copy of name in desired format; free with FrsFree()
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsFormUPN:"
|
|
|
|
PWCHAR SamBegin = NULL;
|
|
PWCHAR FormedUPN = NULL;
|
|
|
|
if ((NT4AccountName == NULL ) || (DomainDnsName == NULL)) {
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
// Find the sam account name.
|
|
//
|
|
for (SamBegin = NT4AccountName; *SamBegin && *SamBegin != L'\\'; ++SamBegin);
|
|
|
|
if (*SamBegin && *(SamBegin+1)) {
|
|
SamBegin++;
|
|
} else {
|
|
return NULL;
|
|
}
|
|
|
|
FormedUPN = FrsAlloc((wcslen(SamBegin) + wcslen(DomainDnsName) + 2) * sizeof(WCHAR));
|
|
|
|
wcscpy(FormedUPN, SamBegin);
|
|
wcscat(FormedUPN, L"@");
|
|
wcscat(FormedUPN, DomainDnsName);
|
|
|
|
DPRINT1(5, "UPN formed is %ws\n", FormedUPN);
|
|
|
|
return FormedUPN;
|
|
}
|
|
|
|
|
|
PWCHAR
|
|
FrsDsConvertName(
|
|
IN HANDLE Handle,
|
|
IN PWCHAR InputName,
|
|
IN DWORD InputFormat,
|
|
IN PWCHAR DomainDnsName,
|
|
IN DWORD DesiredFormat
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Translate the input name into the desired format.
|
|
|
|
Arguments:
|
|
Handle - From DsBind
|
|
InputName - Supplied name.
|
|
InputFormat - Format of the supplied name.
|
|
DomainDnsName - If !NULL, produce new local handle
|
|
DesiredFormat - desired format. Eg. DS_USER_PRINCIPAL_NAME
|
|
|
|
Return Value:
|
|
Copy of name in desired format; free with FrsFree()
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsConvertName:"
|
|
|
|
DWORD WStatus;
|
|
DS_NAME_RESULT *Cracked = NULL;
|
|
HANDLE LocalHandle = NULL;
|
|
PWCHAR CrackedName = NULL;
|
|
PWCHAR CrackedDomain = NULL;
|
|
PWCHAR CrackedUPN = NULL;
|
|
DWORD RequestedFormat = 0;
|
|
|
|
DPRINT3(4, ":DS: Convert Name %ws From %08x To %08x\n", InputName, InputFormat, DesiredFormat);
|
|
|
|
//
|
|
// Input name not available.
|
|
//
|
|
if (!InputName) {
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
// Need something to go on!
|
|
//
|
|
if (!HANDLE_IS_VALID(Handle) && !DomainDnsName) {
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
// Bind to Ds
|
|
//
|
|
if (DomainDnsName) {
|
|
DPRINT3(4, ":DS: Get %08x Name from %ws for %ws\n",
|
|
DesiredFormat, DomainDnsName, InputName);
|
|
|
|
WStatus = DsBind(NULL, DomainDnsName, &LocalHandle);
|
|
CLEANUP2_WS(0, ":DS: ERROR - DsBind(%ws, %08x);",
|
|
DomainDnsName, DesiredFormat, WStatus, RETURN);
|
|
|
|
Handle = LocalHandle;
|
|
}
|
|
|
|
//
|
|
// Crack the computer's distinguished name into its NT4 Account Name
|
|
//
|
|
// If the Desired format is DS_USER_PRINCIPAL_NAME then we form it by
|
|
// getting the name from DS_NT4_ACCOUNT_NAME and the dns domain name
|
|
// from the "Cracked->rItems->pDomain"
|
|
// We could ask for DS_USER_PRINCIPAL_NAME directly but we don't because.
|
|
// Object can have implicit or explicit UPNs. If the object has an explicit UPN,
|
|
// the DsCrackNames will work. If the object has an implicit UPN,
|
|
// then you need to build it.
|
|
//
|
|
if (DesiredFormat == DS_USER_PRINCIPAL_NAME) {
|
|
RequestedFormat = DS_NT4_ACCOUNT_NAME;
|
|
} else {
|
|
RequestedFormat = DesiredFormat;
|
|
}
|
|
|
|
WStatus = DsCrackNames(Handle, // in hDS,
|
|
DS_NAME_NO_FLAGS, // in flags,
|
|
InputFormat , // in formatOffered,
|
|
RequestedFormat, // in formatDesired,
|
|
1, // in cNames,
|
|
&InputName, // in *rpNames,
|
|
&Cracked); // out *ppResult
|
|
|
|
if (!WIN_SUCCESS(WStatus)) {
|
|
DPRINT2_WS(0, ":DS: ERROR - DsCrackNames(%ws, %08x);", InputName, DesiredFormat, WStatus);
|
|
|
|
//
|
|
// Set DsBindingsAreValid to FALSE if the handle has become invalid.
|
|
// That will force us to rebind at the next poll. The guess below might still
|
|
// work so continue processing.
|
|
//
|
|
if (WStatus == ERROR_INVALID_HANDLE) {
|
|
DPRINT1(4, ":DS: Marking binding to %ws as invalid.\n",
|
|
(DsDomainControllerName) ? DsDomainControllerName : L"<null>");
|
|
|
|
DsBindingsAreValid = FALSE;
|
|
}
|
|
|
|
//
|
|
// What else can we do?
|
|
//
|
|
if (HANDLE_IS_VALID(LocalHandle)) {
|
|
DsUnBind(&LocalHandle);
|
|
LocalHandle = NULL;
|
|
}
|
|
|
|
if (DesiredFormat == DS_NT4_ACCOUNT_NAME) {
|
|
return FrsDsGuessPrincName(InputName);
|
|
} else {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Might have it
|
|
//
|
|
if (Cracked && Cracked->cItems && Cracked->rItems) {
|
|
//
|
|
// Got it!
|
|
//
|
|
if (Cracked->rItems->status == DS_NAME_NO_ERROR) {
|
|
DPRINT1(4, ":DS: Cracked Domain : %ws\n", Cracked->rItems->pDomain);
|
|
DPRINT2(4, ":DS: Cracked Name : %08x %ws\n",
|
|
DesiredFormat, Cracked->rItems->pName);
|
|
|
|
CrackedDomain = FrsWcsDup(Cracked->rItems->pDomain);
|
|
CrackedName = FrsWcsDup(Cracked->rItems->pName);
|
|
|
|
//
|
|
// Only got the domain; rebind and try again
|
|
//
|
|
} else
|
|
if (Cracked->rItems->status == DS_NAME_ERROR_DOMAIN_ONLY) {
|
|
|
|
CrackedName = FrsDsConvertName(NULL, InputName, InputFormat, Cracked->rItems->pDomain, DesiredFormat);
|
|
} else {
|
|
DPRINT3(0, ":DS: ERROR - DsCrackNames(%ws, %08x); internal status %d\n",
|
|
InputName, DesiredFormat, Cracked->rItems->status);
|
|
|
|
if (DesiredFormat == DS_NT4_ACCOUNT_NAME) {
|
|
CrackedName = FrsDsGuessPrincName(InputName);
|
|
}
|
|
}
|
|
|
|
|
|
} else {
|
|
DPRINT2(0, ":DS: ERROR - DsCrackNames(%ws, %08x); no status\n",
|
|
InputName, DesiredFormat);
|
|
|
|
if (DesiredFormat == DS_NT4_ACCOUNT_NAME) {
|
|
CrackedName = FrsDsGuessPrincName(InputName);
|
|
}
|
|
}
|
|
|
|
if (Cracked) {
|
|
DsFreeNameResult(Cracked);
|
|
Cracked = NULL;
|
|
}
|
|
|
|
if (HANDLE_IS_VALID(LocalHandle)) {
|
|
DsUnBind(&LocalHandle);
|
|
LocalHandle = NULL;
|
|
}
|
|
|
|
RETURN:
|
|
if ((DesiredFormat == DS_USER_PRINCIPAL_NAME) && (CrackedName != NULL) && (CrackedDomain != NULL)) {
|
|
CrackedUPN = FrsDsFormUPN(CrackedName, CrackedDomain);
|
|
FrsFree(CrackedName);
|
|
FrsFree(CrackedDomain);
|
|
return CrackedUPN;
|
|
|
|
} else {
|
|
|
|
FrsFree(CrackedDomain);
|
|
return CrackedName;
|
|
}
|
|
}
|
|
|
|
|
|
PWCHAR
|
|
FrsDsGetName(
|
|
IN PWCHAR Dn,
|
|
IN HANDLE Handle,
|
|
IN PWCHAR DomainDnsName,
|
|
IN DWORD DesiredFormat
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Translate the Dn into the desired format. Dn should be the Dn
|
|
of a computer object.
|
|
|
|
Arguments:
|
|
Dn - Of computer object
|
|
Handle - From DsBind
|
|
DomainDnsName - If !NULL, produce new local handle
|
|
DesiredFormat - DS_NT4_ACCOUNT_NAME or DS_STRING_SID_NAME
|
|
|
|
Return Value:
|
|
Copy of name in desired format; free with FrsFree()
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsGetName:"
|
|
|
|
DWORD WStatus;
|
|
DS_NAME_RESULT *Cracked = NULL;
|
|
HANDLE LocalHandle = NULL;
|
|
PWCHAR CrackedName = NULL;
|
|
PWCHAR CrackedDomain = NULL;
|
|
PWCHAR CrackedUPN = NULL;
|
|
DWORD RequestedFormat = 0;
|
|
|
|
DPRINT2(4, ":DS: Get %08x Name for %ws\n", DesiredFormat, Dn);
|
|
|
|
//
|
|
// Computer's Dn not available
|
|
//
|
|
if (!Dn) {
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
// Need something to go on!
|
|
//
|
|
if (!HANDLE_IS_VALID(Handle) && !DomainDnsName) {
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
// Bind to Ds
|
|
//
|
|
if (DomainDnsName) {
|
|
DPRINT3(4, ":DS: Get %08x Name from %ws for %ws\n",
|
|
DesiredFormat, DomainDnsName, Dn);
|
|
|
|
WStatus = DsBind(NULL, DomainDnsName, &LocalHandle);
|
|
CLEANUP2_WS(0, ":DS: ERROR - DsBind(%ws, %08x);",
|
|
DomainDnsName, DesiredFormat, WStatus, RETURN);
|
|
|
|
Handle = LocalHandle;
|
|
}
|
|
|
|
//
|
|
// Crack the computer's distinguished name into its NT4 Account Name
|
|
//
|
|
// If the Desired format is DS_USER_PRINCIPAL_NAME then we form it by
|
|
// getting the name from DS_NT4_ACCOUNT_NAME and the dns domain name
|
|
// from the "Cracked->rItems->pDomain"
|
|
// We could ask for DS_USER_PRINCIPAL_NAME directly but we don't because.
|
|
// Object can have implicit or explicit UPNs. If the object has an explicit UPN,
|
|
// the DsCrackNames will work. If the object has an implicit UPN,
|
|
// then you need to build it.
|
|
//
|
|
if (DesiredFormat == DS_USER_PRINCIPAL_NAME) {
|
|
RequestedFormat = DS_NT4_ACCOUNT_NAME;
|
|
} else {
|
|
RequestedFormat = DesiredFormat;
|
|
}
|
|
|
|
WStatus = DsCrackNames(Handle, // in hDS,
|
|
DS_NAME_NO_FLAGS, // in flags,
|
|
DS_FQDN_1779_NAME, // in formatOffered,
|
|
RequestedFormat, // in formatDesired,
|
|
1, // in cNames,
|
|
&Dn, // in *rpNames,
|
|
&Cracked); // out *ppResult
|
|
|
|
if (!WIN_SUCCESS(WStatus)) {
|
|
DPRINT2_WS(0, ":DS: ERROR - DsCrackNames(%ws, %08x);", Dn, DesiredFormat, WStatus);
|
|
|
|
//
|
|
// Set DsBindingsAreValid to FALSE if the handle has become invalid.
|
|
// That will force us to rebind at the next poll. The guess below might still
|
|
// work so continue processing.
|
|
//
|
|
if (WStatus == ERROR_INVALID_HANDLE) {
|
|
DPRINT1(4, ":DS: Marking binding to %ws as invalid.\n",
|
|
(DsDomainControllerName) ? DsDomainControllerName : L"<null>");
|
|
|
|
DsBindingsAreValid = FALSE;
|
|
}
|
|
|
|
//
|
|
// What else can we do?
|
|
//
|
|
if (HANDLE_IS_VALID(LocalHandle)) {
|
|
DsUnBind(&LocalHandle);
|
|
LocalHandle = NULL;
|
|
}
|
|
|
|
if (DesiredFormat == DS_NT4_ACCOUNT_NAME) {
|
|
return FrsDsGuessPrincName(Dn);
|
|
} else {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Might have it
|
|
//
|
|
if (Cracked && Cracked->cItems && Cracked->rItems) {
|
|
//
|
|
// Got it!
|
|
//
|
|
if (Cracked->rItems->status == DS_NAME_NO_ERROR) {
|
|
DPRINT1(4, ":DS: Cracked Domain : %ws\n", Cracked->rItems->pDomain);
|
|
DPRINT2(4, ":DS: Cracked Name : %08x %ws\n",
|
|
DesiredFormat, Cracked->rItems->pName);
|
|
|
|
CrackedDomain = FrsWcsDup(Cracked->rItems->pDomain);
|
|
CrackedName = FrsWcsDup(Cracked->rItems->pName);
|
|
|
|
//
|
|
// Only got the domain; rebind and try again
|
|
//
|
|
} else
|
|
if (Cracked->rItems->status == DS_NAME_ERROR_DOMAIN_ONLY) {
|
|
|
|
CrackedName = FrsDsGetName(Dn, NULL, Cracked->rItems->pDomain, DesiredFormat);
|
|
} else {
|
|
DPRINT3(0, ":DS: ERROR - DsCrackNames(%ws, %08x); internal status %d\n",
|
|
Dn, DesiredFormat, Cracked->rItems->status);
|
|
|
|
if (DesiredFormat == DS_NT4_ACCOUNT_NAME) {
|
|
CrackedName = FrsDsGuessPrincName(Dn);
|
|
}
|
|
}
|
|
|
|
|
|
} else {
|
|
DPRINT2(0, ":DS: ERROR - DsCrackNames(%ws, %08x); no status\n",
|
|
Dn, DesiredFormat);
|
|
|
|
if (DesiredFormat == DS_NT4_ACCOUNT_NAME) {
|
|
CrackedName = FrsDsGuessPrincName(Dn);
|
|
}
|
|
}
|
|
|
|
if (Cracked) {
|
|
DsFreeNameResult(Cracked);
|
|
Cracked = NULL;
|
|
}
|
|
|
|
if (HANDLE_IS_VALID(LocalHandle)) {
|
|
DsUnBind(&LocalHandle);
|
|
LocalHandle = NULL;
|
|
}
|
|
|
|
RETURN:
|
|
if ((DesiredFormat == DS_USER_PRINCIPAL_NAME) && (CrackedName != NULL) && (CrackedDomain != NULL)) {
|
|
CrackedUPN = FrsDsFormUPN(CrackedName, CrackedDomain);
|
|
FrsFree(CrackedName);
|
|
FrsFree(CrackedDomain);
|
|
return CrackedUPN;
|
|
|
|
} else {
|
|
|
|
FrsFree(CrackedDomain);
|
|
return CrackedName;
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
FrsDsCreatePartnerPrincName(
|
|
IN PCONFIG_NODE Sites
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Construct the server principal names for our partners.
|
|
|
|
Part of NewDs poll APIs.
|
|
|
|
Arguments:
|
|
Sites
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsCreatePartnerPrincName:"
|
|
PCONFIG_NODE Cxtion;
|
|
PCONFIG_NODE Partner;
|
|
PCONFIG_NODE Site;
|
|
PCONFIG_NODE Settings;
|
|
PCONFIG_NODE Set;
|
|
PCONFIG_NODE Server;
|
|
PVOID Key;
|
|
|
|
//
|
|
// Get all the required information for every computer in the PartnerComputerTable.
|
|
//
|
|
|
|
Key = NULL;
|
|
while ((Partner = GTabNextDatum(PartnerComputerTable, &Key)) != NULL) {
|
|
|
|
//
|
|
// Get the Server Principal Name.
|
|
//
|
|
if ((Partner->PrincName == NULL) ||
|
|
(*Partner->PrincName == UNICODE_NULL)) {
|
|
|
|
Partner->PrincName = FrsDsGetName(Partner->Dn, DsHandle, NULL, DS_NT4_ACCOUNT_NAME);
|
|
|
|
if ((Partner->PrincName == NULL) ||
|
|
(*Partner->PrincName == UNICODE_NULL)) {
|
|
//
|
|
// Setting active change to 0 will cause this code to be
|
|
// repeated at the next ds polling cycle. We do this because
|
|
// the partner's principal name may appear later.
|
|
//
|
|
ActiveChange = 0;
|
|
Partner->Consistent = FALSE;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Get the partners dnsHostName.
|
|
//
|
|
if (!Partner->DnsName) {
|
|
Partner->DnsName = FrsDsGetDnsName(gLdap, Partner->Dn);
|
|
}
|
|
|
|
//
|
|
// Get the partners SID.
|
|
//
|
|
if (!Partner->Sid) {
|
|
Partner->Sid = FrsDsGetName(Partner->Dn, DsHandle, NULL, DS_STRING_SID_NAME);
|
|
}
|
|
}
|
|
|
|
//
|
|
// For every cxtion in every replica set.
|
|
//
|
|
Key = NULL;
|
|
while((Cxtion = GTabNextDatum(AllCxtionsTable, &Key)) != NULL) {
|
|
|
|
//
|
|
// Ignore inconsistent cxtions
|
|
//
|
|
if (!Cxtion->Consistent) {
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Look for the Cxtion's partner using the PartnerCoDn.
|
|
//
|
|
|
|
//
|
|
// Mark this connection inconsistent if it lacks a PartnerCoDn.
|
|
//
|
|
if (Cxtion->PartnerCoDn == NULL) {
|
|
Cxtion->Consistent = FALSE;
|
|
continue;
|
|
}
|
|
|
|
Partner = GTabLookupTableString(PartnerComputerTable, Cxtion->PartnerCoDn, NULL);
|
|
|
|
//
|
|
// Inconsistent partner; continue
|
|
//
|
|
if (Partner == NULL || !Partner->Consistent) {
|
|
Cxtion->Consistent = FALSE;
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Get out partner's server principal name
|
|
//
|
|
if (!Cxtion->PrincName) {
|
|
Cxtion->PrincName = FrsWcsDup(Partner->PrincName);
|
|
}
|
|
|
|
//
|
|
// Get our partner's dns name
|
|
//
|
|
if (!Cxtion->PartnerDnsName) {
|
|
//
|
|
// The partner's DNS name is not critical; we can fall
|
|
// back on our partner's NetBios name.
|
|
//
|
|
if (Partner->DnsName) {
|
|
Cxtion->PartnerDnsName = FrsWcsDup(Partner->DnsName);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Get our partner's Sid
|
|
//
|
|
if (!Cxtion->PartnerSid) {
|
|
//
|
|
// The partner's DNS name is not critical; we can fall
|
|
// back on our partner's NetBios name.
|
|
//
|
|
if (Partner->Sid) {
|
|
Cxtion->PartnerSid = FrsWcsDup(Partner->Sid);
|
|
}
|
|
}
|
|
|
|
} // cxtion scan
|
|
|
|
}
|
|
|
|
|
|
ULONG
|
|
FrsHashCalcString (
|
|
PVOID Buffer,
|
|
PULONGLONG QKey
|
|
)
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsHashCalcString:"
|
|
|
|
PWCHAR Name = (PWCHAR) Buffer;
|
|
ULONG NameLength = 0;
|
|
PWCHAR p = NULL;
|
|
ULONG NameHashUL = 0;
|
|
ULONGLONG NameHashULL = QUADZERO;
|
|
ULONG Shift = 0;
|
|
|
|
FRS_ASSERT( Buffer != NULL );
|
|
FRS_ASSERT( QKey != NULL );
|
|
|
|
NameLength = wcslen(Name);
|
|
|
|
FRS_ASSERT( NameLength != 0 );
|
|
|
|
DPRINT1(0, "Name = %ws\n", Name);
|
|
|
|
//
|
|
// Combine each unicode character into the hash value, shifting 4 bits
|
|
// each time. Start at the end of the name so file names with different
|
|
// type codes will hash to different table offsets.
|
|
//
|
|
for( p = &Name[NameLength-1];
|
|
p >= Name;
|
|
p-- ) {
|
|
|
|
NameHashUL = NameHashUL ^ (((ULONG)towupper(*p)) << Shift);
|
|
NameHashULL = NameHashULL ^ (((ULONGLONG)towupper(*p)) << Shift);
|
|
|
|
Shift = (Shift < 16) ? Shift + 4 : 0;
|
|
}
|
|
|
|
*QKey = NameHashULL;
|
|
|
|
return NameHashUL;
|
|
}
|
|
|
|
ULONG
|
|
PrintPartnerTableEntry (
|
|
PQHASH_TABLE Table,
|
|
PQHASH_ENTRY BeforeNode,
|
|
PQHASH_ENTRY TargetNode,
|
|
PVOID Context
|
|
)
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "PrintPartnerTableEntry: "
|
|
|
|
DPRINT1(0, "PartnerTableEntry: %ws\n", (PWCHAR)(TargetNode->Flags));
|
|
return FrsErrorSuccess;
|
|
}
|
|
|
|
BOOL
|
|
StringKeyMatch(
|
|
PVOID Buf,
|
|
PVOID QKey
|
|
)
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "StringKeyMatch: "
|
|
|
|
PWCHAR String1 = (PWCHAR)Buf;
|
|
PWCHAR String2 = (PWCHAR)QKey;
|
|
|
|
return(_wcsicmp(String1, String2)?FALSE:TRUE);
|
|
}
|
|
|
|
|
|
|
|
VOID
|
|
FrsDsCreateNewValidPartnerTableStruct(
|
|
VOID
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Uses the AllCxtionsTable to build a new FRS_VALID_PARTNER_TABLE_STRUCT.
|
|
Swaps the new struct with the current one pointed to by the global var
|
|
pValidPartnerTableStruct.
|
|
The old struct gets put on the OldValidPartnerTableStructListHead list.
|
|
Items on the list are cleaned up by calling
|
|
FrsDsCleanupOldValidPartnerTableStructList.
|
|
|
|
Arguments
|
|
NONE
|
|
|
|
Return Value:
|
|
NONE
|
|
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsCreateNewValidPartnerTableStruct:"
|
|
|
|
PCXTION Cxtion = NULL;
|
|
PVOID Key = NULL;
|
|
PQHASH_TABLE pNewPartnerTable = NULL;
|
|
PQHASH_TABLE pNewPartnerConnectionTable = NULL;
|
|
PWCHAR PartnerSid = NULL;
|
|
PGNAME pGName = NULL;
|
|
GUID *pCxtionGuid = NULL;
|
|
PWCHAR NameEntry = NULL;
|
|
PFRS_VALID_PARTNER_TABLE_STRUCT pNewValidPartnerTableStruct = NULL;
|
|
PFRS_VALID_PARTNER_TABLE_STRUCT pOldValidPartnerTableStruct = NULL;
|
|
GHT_STATUS Status;
|
|
CHAR GuidStr[GUID_CHAR_LEN];
|
|
|
|
pNewPartnerTable = FrsAllocTypeSize(QHASH_TABLE_TYPE, PARTNER_NAME_TABLE_SIZE);
|
|
SET_QHASH_TABLE_FLAG(pNewPartnerTable, QHASH_FLAG_LARGE_KEY);
|
|
SET_QHASH_TABLE_HASH_CALC2(pNewPartnerTable, FrsHashCalcString);
|
|
SET_QHASH_TABLE_KEY_MATCH(pNewPartnerTable, StringKeyMatch);
|
|
SET_QHASH_TABLE_FREE(pNewPartnerTable, FrsFree);
|
|
|
|
|
|
pNewPartnerConnectionTable = FrsAllocTypeSize(QHASH_TABLE_TYPE, PARTNER_CONNECTION_TABLE_SIZE);
|
|
SET_QHASH_TABLE_FLAG(pNewPartnerConnectionTable, QHASH_FLAG_LARGE_KEY);
|
|
SET_QHASH_TABLE_HASH_CALC2(pNewPartnerConnectionTable, ActiveChildrenHashCalc);
|
|
SET_QHASH_TABLE_KEY_MATCH(pNewPartnerConnectionTable, ActiveChildrenKeyMatch);
|
|
SET_QHASH_TABLE_FREE(pNewPartnerConnectionTable, FrsFree);
|
|
|
|
|
|
//
|
|
// Check each active replica
|
|
//
|
|
ForEachListEntry( &ReplicaListHead, REPLICA, ReplicaList,
|
|
// Loop iterator pE is type PREPLICA.
|
|
|
|
//
|
|
// Need to lock Cxtion Table for enumeration.
|
|
// Don't need to hold replica lock - that only protects the filter list
|
|
//
|
|
LOCK_CXTION_TABLE(pE);
|
|
|
|
Key = NULL;
|
|
while((Cxtion = GTabNextDatumNoLock(pE->Cxtions, &Key)) != NULL) {
|
|
|
|
//
|
|
// Ignore the (local) journal connection.
|
|
//
|
|
if (Cxtion->JrnlCxtion) {
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Create an entry using partner's sid
|
|
//
|
|
PartnerSid = FrsWcsDup(Cxtion->PartnerSid);
|
|
QHashInsertLock(pNewPartnerTable,
|
|
PartnerSid,
|
|
NULL,
|
|
(ULONG_PTR)PartnerSid);
|
|
|
|
PartnerSid = FrsWcsDup(Cxtion->PartnerSid);
|
|
pCxtionGuid = FrsDupGuid(Cxtion->Name->Guid);
|
|
|
|
QHashInsertLock(pNewPartnerConnectionTable,
|
|
pCxtionGuid,
|
|
(PULONGLONG)&PartnerSid,
|
|
(ULONG_PTR)pCxtionGuid );
|
|
} // cxtion scan
|
|
|
|
UNLOCK_CXTION_TABLE(pE);
|
|
);
|
|
|
|
//
|
|
// also need to check replicas in error states
|
|
//
|
|
ForEachListEntry( &ReplicaFaultListHead, REPLICA, ReplicaList,
|
|
// Loop iterator pE is type PREPLICA.
|
|
|
|
//
|
|
// Need to lock Cxtion Table for enumeration.
|
|
// Don't need to hold replica lock - that only protects the filter list
|
|
//
|
|
LOCK_CXTION_TABLE(pE);
|
|
|
|
Key = NULL;
|
|
while((Cxtion = GTabNextDatumNoLock(pE->Cxtions, &Key)) != NULL) {
|
|
|
|
//
|
|
// Ignore the (local) journal connection.
|
|
//
|
|
if (Cxtion->JrnlCxtion) {
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Create an entry using partner's sid
|
|
//
|
|
PartnerSid = FrsWcsDup(Cxtion->PartnerSid);
|
|
QHashInsertLock(pNewPartnerTable,
|
|
PartnerSid,
|
|
NULL,
|
|
(ULONG_PTR)PartnerSid);
|
|
|
|
PartnerSid = FrsWcsDup(Cxtion->PartnerSid);
|
|
pCxtionGuid = FrsDupGuid(Cxtion->Name->Guid);
|
|
|
|
QHashInsertLock(pNewPartnerConnectionTable,
|
|
pCxtionGuid,
|
|
(PULONGLONG)&PartnerSid,
|
|
(ULONG_PTR)pCxtionGuid);
|
|
} // cxtion scan
|
|
|
|
UNLOCK_CXTION_TABLE(pE);
|
|
);
|
|
|
|
//
|
|
// Don't use stopped replicas. They have probably been deleted.
|
|
//
|
|
|
|
|
|
pNewValidPartnerTableStruct = FrsAlloc(sizeof(FRS_VALID_PARTNER_TABLE_STRUCT));
|
|
pNewValidPartnerTableStruct->pPartnerConnectionTable = pNewPartnerConnectionTable;
|
|
pNewValidPartnerTableStruct->pPartnerTable = pNewPartnerTable;
|
|
pNewValidPartnerTableStruct->ReferenceCount = 0;
|
|
pNewValidPartnerTableStruct->Next = NULL;
|
|
|
|
SWAP_VALID_PARTNER_TABLE_POINTER(pNewValidPartnerTableStruct,
|
|
&pOldValidPartnerTableStruct);
|
|
|
|
|
|
|
|
if (pOldValidPartnerTableStruct) {
|
|
EnterCriticalSection(&OldValidPartnerTableStructListHeadLock);
|
|
pOldValidPartnerTableStruct->Next = OldValidPartnerTableStructListHead;
|
|
OldValidPartnerTableStructListHead = pOldValidPartnerTableStruct;
|
|
LeaveCriticalSection(&OldValidPartnerTableStructListHeadLock);
|
|
}
|
|
|
|
}
|
|
|
|
VOID
|
|
FrsDsCleanupOldValidPartnerTableStructList(
|
|
VOID
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Cleanup items on the OldValidPartnerTableStructListHead list.
|
|
Items on the list are freed only if their reference count is at zero.
|
|
|
|
Arguments
|
|
NONE
|
|
|
|
Return Value:
|
|
NONE
|
|
|
|
--*/
|
|
{
|
|
PFRS_VALID_PARTNER_TABLE_STRUCT pListItem = NULL;
|
|
PFRS_VALID_PARTNER_TABLE_STRUCT pPreviousItem = NULL;
|
|
PFRS_VALID_PARTNER_TABLE_STRUCT pNextItem = NULL;
|
|
|
|
EnterCriticalSection(&OldValidPartnerTableStructListHeadLock);
|
|
pListItem = OldValidPartnerTableStructListHead;
|
|
|
|
while (pListItem != NULL) {
|
|
pNextItem = pListItem->Next;
|
|
if (pListItem->ReferenceCount == 0) {
|
|
// remove from list
|
|
if (pPreviousItem != NULL) {
|
|
pPreviousItem->Next = pNextItem;
|
|
} else {
|
|
OldValidPartnerTableStructListHead = pNextItem;
|
|
}
|
|
|
|
// cleanup
|
|
FREE_VALID_PARTNER_TABLE_STRUCT(pListItem);
|
|
} else {
|
|
pPreviousItem = pListItem;
|
|
}
|
|
pListItem = pNextItem;
|
|
|
|
}
|
|
LeaveCriticalSection(&OldValidPartnerTableStructListHeadLock);
|
|
}
|
|
|
|
|
|
BOOL
|
|
FrsDsDoesUserWantReplication(
|
|
IN PCONFIG_NODE Computer
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Does the topology imply that the user wants this server to replicate?
|
|
|
|
Part of NewDs poll APIs.
|
|
|
|
Arguments
|
|
Computer
|
|
|
|
Return Value:
|
|
TRUE - server may be replicating
|
|
FALSE - server is not replicating
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsDoesUserWantReplication:"
|
|
DWORD WStatus;
|
|
PCONFIG_NODE Subscriptions;
|
|
PCONFIG_NODE Subscriber;
|
|
|
|
//
|
|
// Ds polling thread is shutting down
|
|
//
|
|
if (DsIsShuttingDown) {
|
|
DPRINT(0, ":DS: Ds polling thread is shutting down\n");
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Can't find our computer; something is wrong. Don't start
|
|
//
|
|
if (!Computer) {
|
|
DPRINT(0, ":DS: no computer\n");
|
|
return FALSE;
|
|
} else {
|
|
DPRINT(4, ":DS: have a computer\n");
|
|
}
|
|
|
|
//
|
|
// We need to process the topology further if there is at least
|
|
// 1 valid subscriber.
|
|
//
|
|
if (SubscriberTable != NULL) {
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// Database exists; once was a member of a replica set
|
|
//
|
|
WStatus = FrsDoesFileExist(JetFile);
|
|
if (WIN_SUCCESS(WStatus)) {
|
|
DPRINT(4, ":DS: database exists\n");
|
|
return TRUE;
|
|
} else {
|
|
DPRINT(4, ":DS: database does not exists\n");
|
|
}
|
|
DPRINT1(4, ":DS: Not starting on %ws; nothing to do\n", ComputerName);
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
BOOL
|
|
FrsDsVerifyPath(
|
|
IN PWCHAR Path
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Verify the path syntax.
|
|
|
|
Arguments:
|
|
Path - Syntax is *<Drive Letter>:\*
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsVerifyPath:"
|
|
PWCHAR Colon;
|
|
|
|
//
|
|
// Null path is obviously invalid
|
|
//
|
|
if (!Path) {
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Find the :
|
|
//
|
|
for (Colon = Path; (*Colon != L':') && *Colon; ++Colon);
|
|
|
|
//
|
|
// No :
|
|
//
|
|
if (!*Colon) {
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// No drive letter
|
|
//
|
|
if (Colon == Path) {
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// No :\
|
|
//
|
|
if (*(Colon + 1) != L'\\') {
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Path exists and is valid
|
|
//
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
VOID
|
|
FrsDsCheckServerPaths(
|
|
IN PCONFIG_NODE Sites
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Look for nested paths and invalid path syntax.
|
|
|
|
Correct syntax is "*<drive letter>:\*".
|
|
|
|
Arguments:
|
|
Sites
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsCheckServerPaths:"
|
|
DWORD WStatus;
|
|
PCONFIG_NODE Site;
|
|
PCONFIG_NODE Settings;
|
|
PCONFIG_NODE Set;
|
|
PCONFIG_NODE Server;
|
|
PCONFIG_NODE NSite;
|
|
PCONFIG_NODE NSettings;
|
|
PCONFIG_NODE NSet;
|
|
PCONFIG_NODE NServer;
|
|
DWORD FileAttributes = 0xFFFFFFFF;
|
|
|
|
for (Site = Sites; Site; Site = Site->Peer) {
|
|
for (Settings = Site->Children; Settings; Settings = Settings->Peer) {
|
|
for (Set = Settings->Children; Set; Set = Set->Peer) {
|
|
for (Server = Set->Children; Server; Server = Server->Peer) {
|
|
|
|
//
|
|
// Not this computer; continue
|
|
//
|
|
if (!Server->ThisComputer) {
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Mark this server as processed. This forces the inner loop
|
|
// to skip this server node so that we don't end up comparing
|
|
// this node against itself. Also, this forces this node
|
|
// to be skipped in the inner loop to avoid unnecessary checks.
|
|
//
|
|
// In other words, set this field here, not later in the loop
|
|
// or in any other function.
|
|
//
|
|
Server->VerifiedOverlap = TRUE;
|
|
|
|
//
|
|
// Server is very inconsistent, ignore
|
|
//
|
|
if (!Server->Root || !Server->Stage) {
|
|
Server->Consistent = FALSE;
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Syntax of root path is invalid; continue
|
|
//
|
|
if (!FrsDsVerifyPath(Server->Root)) {
|
|
DPRINT2(3, ":DS: Invalid root %ws for %ws\n",
|
|
Server->Root, Set->Name->Name);
|
|
EPRINT1(EVENT_FRS_ROOT_NOT_VALID, Server->Root);
|
|
Server->Consistent = FALSE;
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Root does not exist or is inaccessable; continue
|
|
//
|
|
WStatus = FrsDoesDirectoryExist(Server->Root, &FileAttributes);
|
|
if (!WIN_SUCCESS(WStatus)) {
|
|
DPRINT2_WS(3, ":DS: Root path (%ws) for %ws does not exist;",
|
|
Server->Root, Set->Name->Name, WStatus);
|
|
EPRINT1(EVENT_FRS_ROOT_NOT_VALID, Server->Root);
|
|
Server->Consistent = FALSE;
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Does the volume exist and is it NTFS?
|
|
//
|
|
WStatus = FrsVerifyVolume(Server->Root,
|
|
Set->Name->Name,
|
|
FILE_PERSISTENT_ACLS | FILE_SUPPORTS_OBJECT_IDS);
|
|
if (!WIN_SUCCESS(WStatus)) {
|
|
DPRINT2_WS(3, ":DS: Root path Volume (%ws) for %ws does not exist or"
|
|
" does not support ACLs and Object IDs;",
|
|
Server->Root, Set->Name->Name, WStatus);
|
|
Server->Consistent = FALSE;
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Syntax of staging path is invalid; continue
|
|
//
|
|
if (!FrsDsVerifyPath(Server->Stage)) {
|
|
DPRINT2(3, ":DS: Invalid stage %ws for %ws\n",
|
|
Server->Stage, Set->Name->Name);
|
|
EPRINT2(EVENT_FRS_STAGE_NOT_VALID, Server->Root, Server->Stage);
|
|
Server->Consistent = FALSE;
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Stage does not exist or is inaccessable; continue
|
|
//
|
|
WStatus = FrsDoesDirectoryExist(Server->Stage, &FileAttributes);
|
|
if (!WIN_SUCCESS(WStatus)) {
|
|
DPRINT2_WS(3, ":DS: Stage path (%ws) for %ws does not exist;",
|
|
Server->Stage, Set->Name->Name, WStatus);
|
|
EPRINT2(EVENT_FRS_STAGE_NOT_VALID, Server->Root, Server->Stage);
|
|
Server->Consistent = FALSE;
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Does the staging volume exist and does it support ACLs?
|
|
// ACLs are required to protect against data theft/corruption
|
|
// in the staging dir.
|
|
//
|
|
WStatus = FrsVerifyVolume(Server->Stage,
|
|
Set->Name->Name,
|
|
FILE_PERSISTENT_ACLS);
|
|
if (!WIN_SUCCESS(WStatus)) {
|
|
DPRINT2_WS(3, ":DS: Stage path Volume (%ws) for %ws does not exist or does not support ACLs;",
|
|
Server->Stage, Set->Name->Name, WStatus);
|
|
Server->Consistent = FALSE;
|
|
continue;
|
|
}
|
|
//
|
|
// End of outer loop
|
|
//
|
|
} } } }
|
|
|
|
}
|
|
|
|
|
|
DWORD
|
|
FrsDsStartPromotionSeeding(
|
|
IN BOOL Inbound,
|
|
IN PWCHAR ReplicaSetName,
|
|
IN PWCHAR ReplicaSetType,
|
|
IN PWCHAR CxtionName,
|
|
IN PWCHAR PartnerName,
|
|
IN PWCHAR PartnerPrincName,
|
|
IN PWCHAR PartnerSid,
|
|
IN ULONG PartnerAuthLevel,
|
|
IN ULONG GuidSize,
|
|
IN UCHAR *CxtionGuid,
|
|
IN UCHAR *PartnerGuid,
|
|
OUT UCHAR *ParentGuid
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Start the promotion process by seeding the indicated sysvol.
|
|
|
|
Arguments:
|
|
Inbound - Inbound cxtion?
|
|
ReplicaSetName - Replica set name
|
|
ReplicaSetType - Replica set type
|
|
CxtionName - printable name for cxtion
|
|
PartnerName - RPC bindable name
|
|
PartnerPrincName - Server principal name for kerberos
|
|
PartnerAuthLevel - Authentication type and level
|
|
GuidSize - sizeof array addressed by Guid
|
|
CxtionGuid - temporary: used for volatile cxtion
|
|
PartnerGuid - temporary: used to find set on partner
|
|
ParentGuid - Used as partner guid on inbound cxtion
|
|
|
|
Return Value:
|
|
Win32 Status
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsStartPromotionSeeding:"
|
|
DWORD WStatus;
|
|
PREPLICA DbReplica;
|
|
PCXTION Cxtion = NULL;
|
|
|
|
//
|
|
// The caller has verified that the replica set exists, the
|
|
// active replication subsystem is active, and that some of
|
|
// the parameters are okay. Verify the rest.
|
|
//
|
|
|
|
if (!CxtionName ||
|
|
!PartnerName ||
|
|
!PartnerPrincName ||
|
|
!CxtionGuid ||
|
|
!PartnerGuid ||
|
|
!ParentGuid ||
|
|
(PartnerAuthLevel != CXTION_AUTH_KERBEROS_FULL &&
|
|
PartnerAuthLevel != CXTION_AUTH_NONE)) {
|
|
WStatus = ERROR_INVALID_PARAMETER;
|
|
goto CLEANUP;
|
|
}
|
|
|
|
//
|
|
// Find the sysvol
|
|
//
|
|
DbReplica = RcsFindSysVolByName(ReplicaSetName);
|
|
if (!DbReplica) {
|
|
DPRINT1(4, ":DS: Promotion failed; could not find %ws\n", ReplicaSetName);
|
|
WStatus = ERROR_INVALID_PARAMETER;
|
|
goto CLEANUP;
|
|
}
|
|
|
|
//
|
|
// To be used in the caller's cxtion
|
|
//
|
|
COPY_GUID(ParentGuid, DbReplica->ReplicaName->Guid);
|
|
|
|
//
|
|
// PRETEND WE ARE THE DS POLLING THREAD AND ARE ADDING A
|
|
// A CXTION TO AN EXISTING REPLICA.
|
|
|
|
//
|
|
// Create the volatile cxtion
|
|
// Set the state to "promoting" at this time because the
|
|
// seeding operation may finish and the state set to
|
|
// NTFRSAPI_SERVICE_DONE before the return of the
|
|
// call to RcsSubmitReplicaSync().
|
|
//
|
|
DbReplica->NtFrsApi_ServiceState = NTFRSAPI_SERVICE_PROMOTING;
|
|
Cxtion = FrsAllocType(CXTION_TYPE);
|
|
Cxtion->Inbound = Inbound;
|
|
|
|
SetCxtionFlag(Cxtion, CXTION_FLAGS_CONSISTENT | CXTION_FLAGS_VOLATILE);
|
|
|
|
Cxtion->Name = FrsBuildGName(FrsDupGuid((GUID *)CxtionGuid),
|
|
FrsWcsDup(CxtionName));
|
|
|
|
Cxtion->Partner = FrsBuildGName(FrsDupGuid((GUID *)PartnerGuid),
|
|
FrsWcsDup(PartnerName));
|
|
|
|
|
|
Cxtion->PartnerSid = FrsWcsDup(PartnerSid);
|
|
Cxtion->PartSrvName = FrsWcsDup(PartnerPrincName);
|
|
Cxtion->PartnerDnsName = FrsWcsDup(PartnerName);
|
|
Cxtion->PartnerAuthLevel = PartnerAuthLevel;
|
|
Cxtion->PartnerPrincName = FrsWcsDup(PartnerPrincName);
|
|
|
|
SetCxtionState(Cxtion, CxtionStateUnjoined);
|
|
|
|
WStatus = RcsSubmitReplicaSync(DbReplica, NULL, Cxtion, CMD_START);
|
|
//
|
|
// The active replication subsystem owns the cxtion, now
|
|
//
|
|
Cxtion = NULL;
|
|
CLEANUP1_WS(0, ":DS: ERROR - Creating cxtion for %ws;",
|
|
ReplicaSetName, WStatus, SYNC_FAIL);
|
|
|
|
//
|
|
// Submit a command to periodically check the promotion activity.
|
|
// If nothing has happened in awhile, stop the promotion process.
|
|
//
|
|
if (Inbound) {
|
|
DbReplica->NtFrsApi_HackCount++; // != 0
|
|
RcsSubmitReplica(DbReplica, NULL, CMD_CHECK_PROMOTION);
|
|
}
|
|
|
|
//
|
|
// SUCCESS
|
|
//
|
|
WStatus = ERROR_SUCCESS;
|
|
goto CLEANUP;
|
|
|
|
SYNC_FAIL:
|
|
DbReplica->NtFrsApi_ServiceState = NTFRSAPI_SERVICE_STATE_IS_UNKNOWN;
|
|
|
|
//
|
|
// CLEANUP
|
|
//
|
|
CLEANUP:
|
|
FrsFreeType(Cxtion);
|
|
return WStatus;
|
|
}
|
|
|
|
DWORD
|
|
FrsDsVerifyPromotionParent(
|
|
IN PWCHAR ReplicaSetName,
|
|
IN PWCHAR ReplicaSetType
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Start the promotion process by seeding the indicated sysvol.
|
|
|
|
Arguments:
|
|
ReplicaSetName - Replica set name
|
|
ReplicaSetType - Type of set (Enterprise or Domain)
|
|
|
|
Return Value:
|
|
Win32 Status
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsVerifyPromotionParent:"
|
|
DWORD WStatus;
|
|
PREPLICA DbReplica;
|
|
|
|
//
|
|
// This parent must be a Dc
|
|
//
|
|
FrsDsGetRole();
|
|
if (!IsADc) {
|
|
DPRINT1(0, ":S: Promotion aborted: %ws is not a dc.\n", ComputerName);
|
|
WStatus = ERROR_SERVICE_SPECIFIC_ERROR;
|
|
goto CLEANUP;
|
|
}
|
|
|
|
//
|
|
// WAIT FOR THE ACTIVE REPLICATION SUBSYSTEM TO START
|
|
//
|
|
MainInit();
|
|
if (!MainInitHasRun) {
|
|
WStatus = ERROR_SERVICE_NOT_ACTIVE;
|
|
goto CLEANUP;
|
|
}
|
|
//
|
|
// Let dcpromo determine the timeout
|
|
//
|
|
DPRINT(4, ":S: Waiting for replica command server to start.\n");
|
|
WStatus = WaitForSingleObject(ReplicaEvent, 10 * 60 * 1000);
|
|
CHECK_WAIT_ERRORS(3, WStatus, 1, ACTION_RETURN);
|
|
|
|
//
|
|
// Is the service shutting down?
|
|
//
|
|
if (FrsIsShuttingDown) {
|
|
WStatus = ERROR_SERVICE_NOT_ACTIVE;
|
|
goto CLEANUP;
|
|
}
|
|
|
|
//
|
|
// Verify the existence of the set
|
|
//
|
|
DbReplica = RcsFindSysVolByName(ReplicaSetName);
|
|
if (DbReplica && IS_TIME_ZERO(DbReplica->MembershipExpires)) {
|
|
//
|
|
// Sysvol exists; make sure it is the right type
|
|
//
|
|
if (_wcsicmp(ReplicaSetType, NTFRSAPI_REPLICA_SET_TYPE_ENTERPRISE)) {
|
|
if (DbReplica->ReplicaSetType != FRS_RSTYPE_DOMAIN_SYSVOL) {
|
|
DPRINT3(0, ":S: ERROR - %ws's type is %d; not %d\n",
|
|
ReplicaSetName, DbReplica->ReplicaSetType,
|
|
FRS_RSTYPE_DOMAIN_SYSVOL);
|
|
WStatus = ERROR_NOT_FOUND;
|
|
goto CLEANUP;
|
|
}
|
|
} else if (DbReplica->ReplicaSetType != FRS_RSTYPE_ENTERPRISE_SYSVOL) {
|
|
DPRINT3(0, ":S: ERROR - %ws's type is %d; not %d\n",
|
|
ReplicaSetName, DbReplica->ReplicaSetType,
|
|
FRS_RSTYPE_ENTERPRISE_SYSVOL);
|
|
WStatus = ERROR_NOT_FOUND;
|
|
goto CLEANUP;
|
|
}
|
|
} else {
|
|
DPRINT2(0, ":S: ERROR - %ws does not exist on %ws!\n",
|
|
ReplicaSetName, ComputerName);
|
|
WStatus = ERROR_NOT_FOUND;
|
|
goto CLEANUP;
|
|
}
|
|
//
|
|
// SUCCESS
|
|
//
|
|
WStatus = ERROR_SUCCESS;
|
|
|
|
//
|
|
// CLEANUP
|
|
//
|
|
CLEANUP:
|
|
return WStatus;
|
|
}
|
|
|
|
VOID
|
|
FrsDsVerifySchedule(
|
|
IN PCONFIG_NODE Node
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Check the schedule for consistency
|
|
|
|
Arguments:
|
|
Sites
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsVerifySchedule:"
|
|
ULONG i;
|
|
ULONG Num;
|
|
ULONG Len;
|
|
ULONG NumType;
|
|
PSCHEDULE Schedule = Node->Schedule;
|
|
|
|
if (!Schedule) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Too many schedules
|
|
//
|
|
Num = Schedule->NumberOfSchedules;
|
|
if (Num > 3) {
|
|
DPRINT2(4, ":DS: %ws has %d schedules\n", Node->Name->Name, Num);
|
|
Node->Consistent = FALSE;
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Too few schedules
|
|
//
|
|
if (Num < 1) {
|
|
DPRINT2(4, ":DS: %ws has %d schedules\n", Node->Name->Name, Num);
|
|
Node->Consistent = FALSE;
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Not enough memory
|
|
//
|
|
Len = sizeof(SCHEDULE) +
|
|
(sizeof(SCHEDULE_HEADER) * (Num - 1)) +
|
|
(SCHEDULE_DATA_BYTES * Num);
|
|
|
|
if (Node->ScheduleLength < Len) {
|
|
DPRINT2(4, ":DS: %ws is short (ds) by %d bytes\n",
|
|
Node->Name->Name, Len - Node->ScheduleLength);
|
|
Node->Consistent = FALSE;
|
|
return;
|
|
}
|
|
|
|
if (Node->Schedule->Size < Len) {
|
|
DPRINT2(4, ":DS: %ws is short (size) by %d bytes\n",
|
|
Node->Name->Name, Len - Node->Schedule->Size);
|
|
Node->Consistent = FALSE;
|
|
return;
|
|
}
|
|
Node->Schedule->Size = Len;
|
|
|
|
//
|
|
// Invalid type
|
|
//
|
|
for (i = 0; i < Num; ++i) {
|
|
switch (Schedule->Schedules[i].Type) {
|
|
case SCHEDULE_INTERVAL:
|
|
break;
|
|
case SCHEDULE_BANDWIDTH:
|
|
DPRINT1(4, ":DS: WARN Bandwidth schedule is not supported for %ws\n",
|
|
Node->Name->Name);
|
|
break;
|
|
case SCHEDULE_PRIORITY:
|
|
DPRINT1(4, ":DS: WARN Priority schedule is not supported for %ws\n",
|
|
Node->Name->Name);
|
|
break;
|
|
default:
|
|
DPRINT2(4, ":DS: %ws has an invalid schedule type (%d)\n",
|
|
Node->Name->Name, Schedule->Schedules[i].Type);
|
|
Node->Consistent = FALSE;
|
|
return;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Only 0 or 1 interval
|
|
//
|
|
for (NumType = i = 0; i < Num; ++i) {
|
|
if (Schedule->Schedules[i].Type == SCHEDULE_INTERVAL)
|
|
++NumType;
|
|
}
|
|
if (NumType > 1) {
|
|
DPRINT2(4, ":DS: %ws has %d interval schedules\n",
|
|
Node->Name->Name, NumType);
|
|
Node->Consistent = FALSE;
|
|
}
|
|
|
|
//
|
|
// Only 0 or 1 bandwidth
|
|
//
|
|
for (NumType = i = 0; i < Num; ++i) {
|
|
if (Schedule->Schedules[i].Type == SCHEDULE_BANDWIDTH)
|
|
++NumType;
|
|
}
|
|
if (NumType > 1) {
|
|
DPRINT2(4, ":DS: %ws has %d bandwidth schedules\n",
|
|
Node->Name->Name, NumType);
|
|
Node->Consistent = FALSE;
|
|
}
|
|
|
|
//
|
|
// Only 0 or 1 priority
|
|
//
|
|
for (NumType = i = 0; i < Num; ++i) {
|
|
if (Schedule->Schedules[i].Type == SCHEDULE_PRIORITY)
|
|
++NumType;
|
|
}
|
|
if (NumType > 1) {
|
|
DPRINT2(4, ":DS: %ws has %d priority schedules\n",
|
|
Node->Name->Name, NumType);
|
|
Node->Consistent = FALSE;
|
|
}
|
|
|
|
//
|
|
// Invalid offset
|
|
//
|
|
for (i = 0; i < Num; ++i) {
|
|
if (Schedule->Schedules[i].Offset >
|
|
Node->ScheduleLength - SCHEDULE_DATA_BYTES) {
|
|
DPRINT2(4, ":DS: %ws has an invalid offset (%d)\n",
|
|
Node->Name->Name, Schedule->Schedules[i].Offset);
|
|
Node->Consistent = FALSE;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
FrsDsCheckSchedules(
|
|
IN PCONFIG_NODE Root
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Check all of the schedules for consistency
|
|
|
|
Arguments:
|
|
Sites
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsCheckSchedules:"
|
|
PCONFIG_NODE Node;
|
|
|
|
for (Node = Root; Node; Node = Node->Peer) {
|
|
FrsDsVerifySchedule(Node);
|
|
FrsDsCheckSchedules(Node->Children);
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
FrsDsPushInConsistenciesDown(
|
|
IN PCONFIG_NODE Sites
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Mark the children of inconsistent parents as inconsistent
|
|
|
|
Arguments:
|
|
Sites
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsPushInConsistenciesDown:"
|
|
PCONFIG_NODE Site;
|
|
PCONFIG_NODE Settings;
|
|
PCONFIG_NODE Set;
|
|
PCONFIG_NODE Server;
|
|
PCONFIG_NODE Cxtion;
|
|
|
|
//
|
|
// Push a parent's inconsistency to its children
|
|
//
|
|
for (Site = Sites; Site; Site = Site->Peer) {
|
|
for (Settings = Site->Children; Settings; Settings = Settings->Peer) {
|
|
if (!Site->Consistent)
|
|
Settings->Consistent = FALSE;
|
|
for (Set = Settings->Children; Set; Set = Set->Peer) {
|
|
if (!Settings->Consistent)
|
|
Set->Consistent = FALSE;
|
|
for (Server = Set->Children; Server; Server = Server->Peer) {
|
|
if (!Set->Consistent)
|
|
Server->Consistent = FALSE;
|
|
for (Cxtion = Server->Children; Cxtion; Cxtion = Cxtion->Peer) {
|
|
if (!Server->Consistent)
|
|
Cxtion->Consistent = FALSE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#if DBG
|
|
#define CHECK_NODE_LINKAGE(_Nodes_) FrsDsCheckNodeLinkage(_Nodes)
|
|
BOOL
|
|
FrsDsCheckNodeLinkage(
|
|
PCONFIG_NODE Nodes
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Recursively check a configuration's site and table linkage
|
|
for incore consistency.
|
|
|
|
Arguments:
|
|
Nodes - linked list of nodes
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsCheckNodeLinkage:"
|
|
PCONFIG_NODE Node; // scan nodes list
|
|
PCONFIG_NODE Child; // scan children list
|
|
DWORD NumChildren; // Count children
|
|
|
|
for (Node = Nodes; Node; Node = Node->Peer) {
|
|
//
|
|
// Make sure the number of children matches the actual number
|
|
//
|
|
NumChildren = 0;
|
|
for (Child = Node->Children; Child; Child = Child->Peer) {
|
|
++NumChildren;
|
|
}
|
|
FRS_ASSERT(NumChildren == Node->NumChildren);
|
|
if (!FrsDsCheckNodeLinkage(Node->Children))
|
|
return FALSE;
|
|
}
|
|
return TRUE; // for Assert(DbgCheckLinkage);
|
|
}
|
|
#else DBG
|
|
#define CHECK_NODE_LINKAGE(_Nodes_)
|
|
#endif DBG
|
|
|
|
|
|
#define UF_IS_A_DC (UF_SERVER_TRUST_ACCOUNT)
|
|
|
|
BOOL
|
|
FrsDsIsPartnerADc(
|
|
IN PWCHAR PartnerName
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Check if the PartnerName's comptuer object indicates that it is a DC.
|
|
|
|
Arguments:
|
|
PartnerName - RPC bindable name
|
|
|
|
Return Value:
|
|
Win32 Status
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsIsPartnerADc:"
|
|
DWORD WStatus;
|
|
DWORD LStatus;
|
|
DWORD UserAccountFlags;
|
|
PLDAP LocalLdap = NULL;
|
|
PLDAPMessage LdapEntry = NULL;
|
|
PLDAPMessage LdapMsg = NULL;
|
|
PWCHAR *Values = NULL;
|
|
PWCHAR DefaultNamingContext = NULL;
|
|
BOOL PartnerIsADc = FALSE;
|
|
PWCHAR UserAccountControl = NULL;
|
|
PWCHAR Attrs[2];
|
|
WCHAR Filter[MAX_PATH + 1];
|
|
FRS_LDAP_SEARCH_CONTEXT FrsSearchContext;
|
|
PWCHAR SamAccountName = NULL;
|
|
ULONG ulOptions;
|
|
|
|
//
|
|
// Convert the passed in name to sam account name.
|
|
// passed in name is of the form FRS1\FRSTEST23$
|
|
// The sam account name is everything after the first '\'
|
|
//
|
|
SamAccountName = wcschr(PartnerName,L'\\');
|
|
if (SamAccountName == NULL) {
|
|
DPRINT1(0, "PartnerName name supplied is in invalid format; %ws\n", PartnerName);
|
|
goto CLEANUP;
|
|
}
|
|
SamAccountName++;
|
|
|
|
DPRINT2(4, ":DS: Converted %ws to %ws\n", PartnerName, SamAccountName);
|
|
|
|
//
|
|
// Bind to the DS on this DC
|
|
//
|
|
//
|
|
// if ldap_open is called with a server name the api will call DsGetDcName
|
|
// passing the server name as the domainname parm...bad, because
|
|
// DsGetDcName will make a load of DNS queries based on the server name,
|
|
// it is designed to construct these queries from a domain name...so all
|
|
// these queries will be bogus, meaning they will waste network bandwidth,
|
|
// time to fail, and worst case cause expensive on demand links to come up
|
|
// as referrals/forwarders are contacted to attempt to resolve the bogus
|
|
// names. By setting LDAP_OPT_AREC_EXCLUSIVE to on using ldap_set_option
|
|
// after the ldap_init but before any other operation using the ldap
|
|
// handle from ldap_init, the delayed connection setup will not call
|
|
// DsGetDcName, just gethostbyname, or if an IP is passed, the ldap client
|
|
// will detect that and use the address directly.
|
|
//
|
|
// LocalLdap = ldap_open(ComputerName, LDAP_PORT);
|
|
LocalLdap = ldap_init(ComputerName, LDAP_PORT);
|
|
if (LocalLdap == NULL) {
|
|
DPRINT1_WS(4, ":DS: WARN - Coult not open DS on %ws;", ComputerName, GetLastError());
|
|
goto CLEANUP;
|
|
}
|
|
|
|
ulOptions = PtrToUlong(LDAP_OPT_ON);
|
|
ldap_set_option(LocalLdap, LDAP_OPT_AREC_EXCLUSIVE, &ulOptions);
|
|
|
|
LStatus = ldap_bind_s(LocalLdap, NULL, NULL, LDAP_AUTH_NEGOTIATE);
|
|
CLEANUP1_LS(0, ":DS: WARN - Could not bind to the DS on %ws :",
|
|
ComputerName, LStatus, CLEANUP);
|
|
|
|
DPRINT1(4, ":DS: Bound to the DS on %ws\n", ComputerName);
|
|
|
|
//
|
|
// Find the default naming context (objectCategory=*)
|
|
//
|
|
MK_ATTRS_1(Attrs, ATTR_DEFAULT_NAMING_CONTEXT);
|
|
|
|
if (!FrsDsLdapSearch(LocalLdap, CN_ROOT, LDAP_SCOPE_BASE, CATEGORY_ANY,
|
|
Attrs, 0, &LdapMsg)) {
|
|
goto CLEANUP;
|
|
}
|
|
LdapEntry = ldap_first_entry(LocalLdap, LdapMsg);
|
|
if (!LdapEntry) {
|
|
goto CLEANUP;
|
|
}
|
|
Values = (PWCHAR *)FrsDsFindValues(LocalLdap,
|
|
LdapEntry,
|
|
ATTR_DEFAULT_NAMING_CONTEXT,
|
|
FALSE);
|
|
if (!Values) {
|
|
goto CLEANUP;
|
|
}
|
|
DefaultNamingContext = FrsWcsDup(Values[0]);
|
|
LDAP_FREE_VALUES(Values);
|
|
LDAP_FREE_MSG(LdapMsg);
|
|
|
|
DPRINT2(4, ":DS: Default naming context for %ws is %ws\n",
|
|
ComputerName, DefaultNamingContext);
|
|
|
|
//
|
|
// Find the account object for PartnerName
|
|
//
|
|
swprintf(Filter, L"(sAMAccountName=%s)", SamAccountName);
|
|
|
|
MK_ATTRS_1(Attrs, ATTR_USER_ACCOUNT_CONTROL);
|
|
|
|
if (!FrsDsLdapSearchInit(LocalLdap, DefaultNamingContext, LDAP_SCOPE_SUBTREE, Filter,
|
|
Attrs, 0, &FrsSearchContext)) {
|
|
goto CLEANUP;
|
|
}
|
|
|
|
//
|
|
// Scan the returned account objects for a valid DC
|
|
//
|
|
for (LdapEntry = FrsDsLdapSearchNext(LocalLdap, &FrsSearchContext);
|
|
LdapEntry != NULL;
|
|
LdapEntry = FrsDsLdapSearchNext(LocalLdap, &FrsSearchContext)) {
|
|
|
|
//
|
|
// No user account control flags
|
|
//
|
|
UserAccountControl = FrsDsFindValue(LocalLdap, LdapEntry, ATTR_USER_ACCOUNT_CONTROL);
|
|
if (!UserAccountControl) {
|
|
continue;
|
|
}
|
|
UserAccountFlags = wcstoul(UserAccountControl, NULL, 10);
|
|
DPRINT2(4, ":DS: UserAccountControl for %ws is 0x%08x\n",
|
|
SamAccountName, UserAccountFlags);
|
|
//
|
|
// IS A DC!
|
|
//
|
|
if (UserAccountFlags & UF_IS_A_DC) {
|
|
DPRINT1(4, ":DS: Partner %ws is really a DC!\n", SamAccountName);
|
|
PartnerIsADc = TRUE;
|
|
goto CLEANUP;
|
|
}
|
|
FrsFree(UserAccountControl);
|
|
}
|
|
FrsDsLdapSearchClose(&FrsSearchContext);
|
|
DPRINT1(0, ":DS: ERROR - Partner %ws is NOT a DC!\n", SamAccountName);
|
|
|
|
CLEANUP:
|
|
LDAP_FREE_VALUES(Values);
|
|
LDAP_FREE_MSG(LdapMsg);
|
|
FrsFree(DefaultNamingContext);
|
|
FrsFree(UserAccountControl);
|
|
if (LocalLdap) {
|
|
ldap_unbind_s(LocalLdap);
|
|
}
|
|
DPRINT2(4, ":DS: Partner %ws is %s a DC\n",
|
|
PartnerName, (PartnerIsADc) ? "assumed to be" : "NOT");
|
|
return PartnerIsADc;
|
|
}
|
|
|
|
|
|
|
|
DWORD
|
|
FrsDsGetRole(
|
|
VOID
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Get this computer's role in the domain.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
Win32 Status
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsGetRole:"
|
|
DWORD WStatus;
|
|
DWORD SysvolReady;
|
|
CHAR GuidStr[GUID_CHAR_LEN];
|
|
DSROLE_PRIMARY_DOMAIN_INFO_BASIC *DsRole;
|
|
|
|
//
|
|
// We already know our role; carry on
|
|
//
|
|
if (IsAMember) {
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
DPRINT(4, ":DS: Finding this computer's role in the domain.\n");
|
|
|
|
#if DBG
|
|
//
|
|
// Emulating multiple machines
|
|
//
|
|
if (ServerGuid) {
|
|
DPRINT(4, ":DS: Always a member with hardwired config\n");
|
|
IsAMember = TRUE;
|
|
return ERROR_SUCCESS;
|
|
}
|
|
#endif DBG
|
|
|
|
//
|
|
// Is this a domain controller?
|
|
//
|
|
WStatus = DsRoleGetPrimaryDomainInformation(NULL,
|
|
DsRolePrimaryDomainInfoBasic,
|
|
(PBYTE *)&DsRole);
|
|
CLEANUP_WS(4, ":DS: Can't get Ds role info;", WStatus, RETURN);
|
|
|
|
DPRINT1(4, ":DS: Ds Role : %ws\n", Roles[DsRole->MachineRole]);
|
|
DPRINT1(4, ":DS: Ds Role Flags : %08x\n", DsRole->Flags);
|
|
if (DsRole->Flags & DSROLE_PRIMARY_DS_RUNNING) {
|
|
DPRINT(4, ":DS: Ds Role Flag : DSROLE_PRIMARY_DS_RUNNING\n");
|
|
}
|
|
if (DsRole->Flags & DSROLE_PRIMARY_DOMAIN_GUID_PRESENT) {
|
|
DPRINT(4, ":DS: Ds Role Flag : DSROLE_PRIMARY_DOMAIN_GUID_PRESENT\n");
|
|
}
|
|
DPRINT1(4, ":DS: Ds Role DomainNameFlat: %ws\n", DsRole->DomainNameFlat);
|
|
DPRINT1(4, ":DS: Ds Role DomainNameDns : %ws\n", DsRole->DomainNameDns);
|
|
// DPRINT1(4, ":DS: Ds Role DomainForestName: %ws\n", DsRole->DomainForestName);
|
|
GuidToStr(&DsRole->DomainGuid, GuidStr);
|
|
DPRINT1(4, ":DS: Ds Role DomainGuid : %s\n", GuidStr);
|
|
|
|
//
|
|
// Backup Domain Controller (DC)
|
|
//
|
|
if (DsRole->MachineRole == DsRole_RoleBackupDomainController) {
|
|
DPRINT(4, ":DS: Computer is a backup DC; sysvol support is enabled.\n");
|
|
IsAMember = TRUE;
|
|
IsADc = TRUE;
|
|
//
|
|
// Primary Domain Controller (DC)
|
|
//
|
|
} else if (DsRole->MachineRole == DsRole_RolePrimaryDomainController) {
|
|
DPRINT(4, ":DS: Computer is a DC; sysvol support is enabled.\n");
|
|
IsAMember = TRUE;
|
|
IsADc = TRUE;
|
|
IsAPrimaryDc = TRUE;
|
|
//
|
|
// Member Server
|
|
//
|
|
} else if (DsRole->MachineRole == DsRole_RoleMemberServer) {
|
|
DPRINT(4, ":DS: Computer is just a member server.\n");
|
|
IsAMember = TRUE;
|
|
|
|
#ifdef DS_FREE
|
|
|
|
} else if ((DsRole->MachineRole == DsRole_RoleStandaloneServer) && (NoDs == TRUE)) {
|
|
DPRINT(4, ":DS: Computer is running in DS free environment.\n");
|
|
IsAMember = TRUE;
|
|
|
|
} else if ((DsRole->MachineRole == DsRole_RoleStandaloneWorkstation) && (NoDs == TRUE)) {
|
|
DPRINT(4, ":DS: Computer is running in DS free environment on non-server.\n");
|
|
IsAMember = TRUE;
|
|
|
|
} else if ((DsRole->MachineRole == DsRole_RoleMemberWorkstation) && (NoDs == TRUE)) {
|
|
DPRINT(4, ":DS: Computer is running in DS free environment on non-server.\n");
|
|
IsAMember = TRUE;
|
|
|
|
#endif DS_FREE
|
|
//
|
|
// Not in a server in a domain; stop the service
|
|
//
|
|
} else {
|
|
DPRINT(1, ":DS: Computer is not a server in a domain.\n");
|
|
}
|
|
DsRoleFreeMemory(DsRole);
|
|
|
|
//
|
|
// Has the sysvol been seeded?
|
|
//
|
|
if (IsADc) {
|
|
//
|
|
// Access the netlogon\parameters key to get the sysvol share status
|
|
//
|
|
WStatus = CfgRegReadDWord(FKC_SYSVOL_READY, NULL, 0, &SysvolReady);
|
|
|
|
if (WIN_SUCCESS(WStatus)) {
|
|
if (!SysvolReady) {
|
|
EPRINT1((IsAPrimaryDc) ? EVENT_FRS_SYSVOL_NOT_READY_PRIMARY_2 :
|
|
EVENT_FRS_SYSVOL_NOT_READY_2,
|
|
ComputerName);
|
|
}
|
|
} else {
|
|
DPRINT2_WS(0, "ERROR - reading %ws\\%ws :",
|
|
NETLOGON_SECTION, SYSVOL_READY, WStatus);
|
|
}
|
|
}
|
|
|
|
WStatus = ERROR_SUCCESS;
|
|
|
|
RETURN:
|
|
return WStatus;
|
|
}
|
|
|
|
|
|
DWORD
|
|
FrsDsCommitDemotion(
|
|
VOID
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Commit the demotion process by marking the tombstoned
|
|
sysvols as "do not animate".
|
|
|
|
Arguments:
|
|
None.
|
|
|
|
Return Value:
|
|
Win32 Status
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsCommitDemotion:"
|
|
DWORD WStatus;
|
|
DWORD i;
|
|
PREPLICA DbReplica;
|
|
PVOID Key;
|
|
DWORD SaveWStatus;
|
|
DWORD SysvolPathLen;
|
|
PWCHAR SysvolPath = NULL;
|
|
HANDLE FileHandle = INVALID_HANDLE_VALUE;
|
|
|
|
//
|
|
// SHUTDOWN THE DS POLLING THREAD
|
|
// Demotion can run in parallel with the Ds polling thread iff
|
|
// the polling thread never tries to merge the info in the Ds
|
|
// with the active replicas. This could result in the sysvol
|
|
// replica being animated. So, we tell the Ds polling thead to
|
|
// shut down, wake it up if it is asleep so it can see the shutdown
|
|
// request, and then synchronize with the merging code in the
|
|
// Ds polling thread. We don't want to wait for the polling
|
|
// thread to simply die because it may be stuck talking to the
|
|
// Ds. Alternatively, we could use async ldap but that would
|
|
// take too long and is overkill at this time.
|
|
//
|
|
// In any case, the service will be restarted after dcpromo/demote
|
|
// by a reboot or a restart-service by the ntfrsapi.
|
|
//
|
|
//
|
|
// PERF: should use async ldap in polling thread.
|
|
//
|
|
DsIsShuttingDown = TRUE;
|
|
SetEvent(DsPollEvent);
|
|
EnterCriticalSection(&MergingReplicasWithDs);
|
|
LeaveCriticalSection(&MergingReplicasWithDs);
|
|
|
|
//
|
|
// Is the service shutting down?
|
|
//
|
|
if (FrsIsShuttingDown) {
|
|
WStatus = ERROR_SERVICE_NOT_ACTIVE;
|
|
goto CLEANUP;
|
|
}
|
|
//
|
|
// WAIT FOR THE ACTIVE REPLICATION SUBSYSTEM TO START
|
|
//
|
|
MainInit();
|
|
if (!MainInitHasRun) {
|
|
WStatus = ERROR_SERVICE_NOT_ACTIVE;
|
|
goto CLEANUP;
|
|
}
|
|
//
|
|
// Let dcpromo determine the timeout
|
|
//
|
|
DPRINT(4, ":S: Waiting for replica command server to start.\n");
|
|
WStatus = WaitForSingleObject(ReplicaEvent, 30 * 60 * 1000);
|
|
CHECK_WAIT_ERRORS(3, WStatus, 1, ACTION_RETURN);
|
|
|
|
//
|
|
// Unshare the sysvol
|
|
//
|
|
RcsSetSysvolReady(0);
|
|
|
|
//
|
|
// Mark the tombstoned replica sets for sysvols as "do not animate".
|
|
//
|
|
SaveWStatus = ERROR_SUCCESS;
|
|
Key = NULL;
|
|
while (DbReplica = RcsFindNextReplica(&Key)) {
|
|
//
|
|
// Not a sysvol
|
|
//
|
|
if (!FRS_RSTYPE_IS_SYSVOL(DbReplica->ReplicaSetType)) {
|
|
continue;
|
|
}
|
|
//
|
|
// Not tombstoned
|
|
//
|
|
if (IS_TIME_ZERO(DbReplica->MembershipExpires)) {
|
|
continue;
|
|
}
|
|
//
|
|
// Mark as "do not animate"
|
|
//
|
|
WStatus = RcsSubmitReplicaSync(DbReplica, NULL, NULL, CMD_DELETE_NOW);
|
|
if (!WIN_SUCCESS(WStatus)) {
|
|
DPRINT1_WS(0, ":S: ERROR - Could not delete %ws now;",
|
|
DbReplica->ReplicaName->Name, WStatus);
|
|
SaveWStatus = WStatus;
|
|
continue;
|
|
}
|
|
DPRINT1(4, ":S: Deleted %ws in DB", DbReplica->ReplicaName->Name);
|
|
//
|
|
// Reset loop enum key because CMD_DELTE_NOW has removed the entry from
|
|
// the ReplicasByGuid table.
|
|
//
|
|
Key = NULL;
|
|
|
|
//
|
|
// Delete ALL OF THE SYSVOL DIRECTORY
|
|
//
|
|
// WARNING: makes assumptions about tree built by dcpromo.
|
|
//
|
|
if (DbReplica->Root) {
|
|
SysvolPath = FrsFree(SysvolPath);
|
|
SysvolPath = FrsWcsDup(DbReplica->Root);
|
|
SysvolPathLen = wcslen(SysvolPath);
|
|
if (SysvolPathLen) {
|
|
for (i = SysvolPathLen - 1; i; --i) {
|
|
if (*(SysvolPath + i) == L'\\') {
|
|
*(SysvolPath + i) = L'\0';
|
|
DPRINT1(4, ":S: Deleting sysvol path %ws\n", SysvolPath);
|
|
WStatus = FrsDeletePath(SysvolPath,
|
|
ENUMERATE_DIRECTORY_FLAGS_ERROR_CONTINUE);
|
|
if (!WIN_SUCCESS(WStatus)) {
|
|
DPRINT1_WS(3, ":S: Warn - FrsDeletePath(%ws); (IGNORED)",
|
|
SysvolPath, WStatus);
|
|
WStatus = ERROR_SUCCESS;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// The original code deleted the root and staging directories, not
|
|
// the entire sysvol tree. Allow the original code to execute in
|
|
// case the new code above runs into problems.
|
|
//
|
|
|
|
//
|
|
// Why wouldn't a replica set have a root path? But, BSTS.
|
|
//
|
|
if (!DbReplica->Root) {
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// DELETE THE CONTENTS OF THE ROOT DIRECTORY
|
|
// Always open the replica root by masking off the FILE_OPEN_REPARSE_POINT flag
|
|
// because we want to open the destination dir not the junction if the root
|
|
// happens to be a mount point.
|
|
//
|
|
WStatus = FrsOpenSourceFileW(&FileHandle,
|
|
DbReplica->Root,
|
|
// WRITE_ACCESS | READ_ACCESS,
|
|
DELETE | READ_ATTRIB_ACCESS | WRITE_ATTRIB_ACCESS | FILE_LIST_DIRECTORY,
|
|
OPEN_OPTIONS & ~FILE_OPEN_REPARSE_POINT);
|
|
if (!WIN_SUCCESS(WStatus)) {
|
|
DPRINT1_WS(0, ":S: ERROR - Cannot open root of replica tree %ws;",
|
|
DbReplica->Root, WStatus);
|
|
continue;
|
|
}
|
|
//
|
|
// Remove object id
|
|
//
|
|
WStatus = FrsDeleteFileObjectId(FileHandle, DbReplica->Root);
|
|
DPRINT1_WS(0, ":S: ERROR - Cannot remove object id from root "
|
|
"of replica tree %ws; Continue with delete",
|
|
DbReplica->Root, WStatus);
|
|
//
|
|
// Delete files/subdirs
|
|
//
|
|
FrsEnumerateDirectory(FileHandle,
|
|
DbReplica->Root,
|
|
0,
|
|
ENUMERATE_DIRECTORY_FLAGS_ERROR_CONTINUE,
|
|
NULL,
|
|
FrsEnumerateDirectoryDeleteWorker);
|
|
DPRINT1(4, ":S: Deleted files/subdirs for %ws\n", DbReplica->Root);
|
|
|
|
FRS_CLOSE(FileHandle);
|
|
|
|
//
|
|
// Why wouldn't a replica set have a stage path? But, BSTS.
|
|
//
|
|
if (!DbReplica->Stage) {
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// DELETE THE CONTENTS OF THE STAGE DIRECTORY
|
|
//
|
|
WStatus = FrsOpenSourceFileW(&FileHandle,
|
|
DbReplica->Stage,
|
|
// WRITE_ACCESS | READ_ACCESS,
|
|
DELETE | READ_ATTRIB_ACCESS | WRITE_ATTRIB_ACCESS | FILE_LIST_DIRECTORY,
|
|
OPEN_OPTIONS);
|
|
if (!WIN_SUCCESS(WStatus)) {
|
|
DPRINT1_WS(0, ":S: ERROR - Cannot open stage of replica tree %ws;",
|
|
DbReplica->Root, WStatus);
|
|
continue;
|
|
}
|
|
//
|
|
// Delete files/subdirs
|
|
//
|
|
FrsEnumerateDirectory(FileHandle,
|
|
DbReplica->Stage,
|
|
0,
|
|
ENUMERATE_DIRECTORY_FLAGS_ERROR_CONTINUE,
|
|
NULL,
|
|
FrsEnumerateDirectoryDeleteWorker);
|
|
DPRINT1(4, ":S: Deleted files/subdirs for %ws\n", DbReplica->Stage);
|
|
FRS_CLOSE(FileHandle);
|
|
}
|
|
WStatus = SaveWStatus;
|
|
if (!WIN_SUCCESS(WStatus)) {
|
|
goto CLEANUP;
|
|
}
|
|
|
|
//
|
|
// SUCCESS
|
|
//
|
|
WStatus = ERROR_SUCCESS;
|
|
DPRINT(4, ":S: Successfully marked tombstoned sysvols as do not animate.\n");
|
|
|
|
//
|
|
// CLEANUP
|
|
//
|
|
CLEANUP:
|
|
FRS_CLOSE(FileHandle);
|
|
SysvolPath = FrsFree(SysvolPath);
|
|
return WStatus;
|
|
}
|
|
|
|
|
|
DWORD
|
|
FrsDsStartDemotion(
|
|
IN PWCHAR ReplicaSetName
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Start the demotion process by tombstoning the sysvol.
|
|
|
|
Arguments:
|
|
ReplicaSetName - Replica set name
|
|
|
|
Return Value:
|
|
Win32 Status
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsStartDemotion:"
|
|
DWORD WStatus;
|
|
DWORD FStatus;
|
|
DWORD DbReplicaSetType;
|
|
PREPLICA DbReplica;
|
|
|
|
//
|
|
// SHUTDOWN THE DS POLLING THREAD
|
|
// Demotion can run in parallel with the Ds polling thread iff
|
|
// the polling thread never tries to merge the info in the Ds
|
|
// with the active replicas. This could result in the sysvol
|
|
// replica being animated. So, we tell the Ds polling thead to
|
|
// shut down, wake it up if it is asleep so it can see the shutdown
|
|
// request, and then synchronize with the merging code in the
|
|
// Ds polling thread. We don't want to wait for the polling
|
|
// thread to simply die because it may be stuck talking to the
|
|
// Ds. Alternatively, we could use async ldap but that would
|
|
// take too long and is overkill at this time.
|
|
//
|
|
// In any case, the service will be restarted after dcpromo/demote
|
|
// by a reboot or a restart-service by the ntfrsapi.
|
|
//
|
|
//
|
|
// PERF: should use async ldap in polling thread.
|
|
//
|
|
DsIsShuttingDown = TRUE;
|
|
SetEvent(DsPollEvent);
|
|
EnterCriticalSection(&MergingReplicasWithDs);
|
|
LeaveCriticalSection(&MergingReplicasWithDs);
|
|
|
|
//
|
|
// Is the service shutting down?
|
|
//
|
|
if (FrsIsShuttingDown) {
|
|
WStatus = ERROR_SERVICE_NOT_ACTIVE;
|
|
goto cleanup;
|
|
}
|
|
//
|
|
// WAIT FOR THE ACTIVE REPLICATION SUBSYSTEM TO START
|
|
//
|
|
MainInit();
|
|
if (!MainInitHasRun) {
|
|
WStatus = ERROR_SERVICE_NOT_ACTIVE;
|
|
goto cleanup;
|
|
}
|
|
//
|
|
// Let dcpromo determine the timeout
|
|
//
|
|
DPRINT(4, ":S: Waiting for replica command server to start.\n");
|
|
WStatus = WaitForSingleObject(ReplicaEvent, 30 * 60 * 1000);
|
|
CHECK_WAIT_ERRORS(3, WStatus, 1, ACTION_RETURN);
|
|
|
|
//
|
|
// TOMBSTONE THE REPLICA SET IN THE ACTIVE REPLICATION SUBSYSTEM
|
|
//
|
|
|
|
//
|
|
// Find the sysvol replica and tombstone it.
|
|
//
|
|
DbReplica = RcsFindSysVolByName(ReplicaSetName);
|
|
//
|
|
// Can't find by name, not the enterprise sysvol, and not the
|
|
// special call during promotion. See if the name of the domain
|
|
// sysvol was mapped into CN_DOMAIN_SYSVOL. (B3 naming)
|
|
//
|
|
if (!DbReplica &&
|
|
WSTR_NE(ReplicaSetName, L"enterprise") &&
|
|
WSTR_NE(ReplicaSetName, L"")) {
|
|
//
|
|
// domain name may have been mapped into CN_DOMAIN_SYSVOL (new B3 naming)
|
|
//
|
|
DbReplica = RcsFindSysVolByName(CN_DOMAIN_SYSVOL);
|
|
}
|
|
if (DbReplica) {
|
|
//
|
|
// Tombstone the replica set. The set won't actually be deleted
|
|
// until the tombstone expires. If dcdemote fails the replica set
|
|
// will be reanimated when the service restarts.
|
|
//
|
|
// If dcdemote succeeds, the tombstone expiration will be set to
|
|
// yesterday so the replica set will never be animated. See
|
|
// FrsDsCommitDemotion.
|
|
//
|
|
WStatus = RcsSubmitReplicaSync(DbReplica, NULL, NULL, CMD_DELETE);
|
|
CLEANUP2_WS(0, ":S: ERROR - can't delete %ws on %ws;",
|
|
DbReplica->ReplicaName->Name, ComputerName, WStatus, cleanup);
|
|
|
|
DPRINT2(0, ":S: Deleted %ws on %ws\n", ReplicaSetName, ComputerName);
|
|
} else if (!wcscmp(ReplicaSetName, L"")) {
|
|
//
|
|
// Special case called during promotion. Delete existing sysvols
|
|
// that may exist from a previous full install or stale database.
|
|
//
|
|
// Make sure the sysvol doesn't already exist. If it does but is
|
|
// tombstoned, set the tombstone to "do not animate". Otherwise,
|
|
// error off.
|
|
//
|
|
DbReplicaSetType = FRS_RSTYPE_ENTERPRISE_SYSVOL;
|
|
again:
|
|
DbReplica = RcsFindSysVolByType(DbReplicaSetType);
|
|
if (!DbReplica) {
|
|
if (DbReplicaSetType == FRS_RSTYPE_ENTERPRISE_SYSVOL) {
|
|
DbReplicaSetType = FRS_RSTYPE_DOMAIN_SYSVOL;
|
|
goto again;
|
|
}
|
|
}
|
|
if (DbReplica) {
|
|
DPRINT2(4, ":S: WARN - Sysvol %ws exists for %ws; deleting!\n",
|
|
DbReplica->ReplicaName->Name, ComputerName);
|
|
//
|
|
// Find our role. If we aren't a DC or the sysvol has been
|
|
// tombstoned, delete it now.
|
|
//
|
|
FrsDsGetRole();
|
|
if (!IS_TIME_ZERO(DbReplica->MembershipExpires) || !IsADc) {
|
|
//
|
|
// Once the MembershipExpires has been set to a time less
|
|
// than Now the replica set will never appear again. The
|
|
// replica sticks around for now since the RPC server
|
|
// may be putting command packets on this replica's queue.
|
|
// The packets will be ignored. The replica will be deleted
|
|
// from the database the next time the service starts. Even
|
|
// if the deletion fails, the rest of the service will
|
|
// not see the replica because the replica struct is not
|
|
// put in the table of active replicas. The deletion is
|
|
// retried at startup.
|
|
//
|
|
WStatus = RcsSubmitReplicaSync(DbReplica, NULL, NULL, CMD_DELETE_NOW);
|
|
CLEANUP1_WS(0, ":S: ERROR - can't delete %ws;",
|
|
DbReplica->ReplicaName->Name, WStatus, cleanup);
|
|
goto again;
|
|
} else {
|
|
DPRINT2(0, ":S: ERROR - Cannot delete %ws for %ws!\n",
|
|
DbReplica->ReplicaName->Name, ComputerName);
|
|
WStatus = ERROR_DUP_NAME;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
} else {
|
|
DPRINT1(0, ":S: Sysvol %ws not found; declaring victory\n", ReplicaSetName);
|
|
}
|
|
|
|
//
|
|
// SUCCESS
|
|
//
|
|
WStatus = ERROR_SUCCESS;
|
|
|
|
//
|
|
// CLEANUP
|
|
//
|
|
cleanup:
|
|
return WStatus;
|
|
}
|
|
|
|
|
|
VOID
|
|
FrsDsFreeTree(
|
|
PCONFIG_NODE Root
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Free every node in a tree
|
|
|
|
Arguments:
|
|
Root
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsFreeTree:"
|
|
PCONFIG_NODE Node;
|
|
|
|
while (Root != NULL) {
|
|
Node = Root;
|
|
Root = Root->Peer;
|
|
FrsDsFreeTree(Node->Children);
|
|
FrsFreeType(Node);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
VOID
|
|
FrsDsSwapPtrs(
|
|
PVOID *P1,
|
|
PVOID *P2
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Swap two pointers.
|
|
|
|
Arguments:
|
|
P1 - address of a pointer
|
|
P2 - address of a pointer
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsSwapPtrs:"
|
|
PVOID Tmp;
|
|
|
|
Tmp = *P2;
|
|
*P2 = *P1;
|
|
*P1 = Tmp;
|
|
}
|
|
|
|
|
|
#if DBG
|
|
//
|
|
// Hardwired configuration for testing w/o the DS
|
|
//
|
|
|
|
#define HW_MACHINES 8
|
|
#define THIS_COMPUTER L"[This Computer]"
|
|
|
|
typedef struct _HardWired{
|
|
PWCHAR Machine;
|
|
PWCHAR Server;
|
|
PWCHAR Replica;
|
|
BOOL IsPrimary;
|
|
PWCHAR FileFilterList;
|
|
PWCHAR DirFilterList;
|
|
PWCHAR InNames[HW_MACHINES];
|
|
PWCHAR InMachines[HW_MACHINES];
|
|
PWCHAR InServers[HW_MACHINES];
|
|
PWCHAR OutNames[HW_MACHINES];
|
|
PWCHAR OutMachines[HW_MACHINES];
|
|
PWCHAR OutServers[HW_MACHINES];
|
|
PWCHAR Stage;
|
|
PWCHAR Root;
|
|
PWCHAR JetPath;
|
|
} HARDWIRED, *PHARDWIRED;
|
|
|
|
|
|
//
|
|
// This hard wired configuration is loaded if a path
|
|
// to a ini file is provided at command line.
|
|
//
|
|
PHARDWIRED LoadedWired;
|
|
|
|
HARDWIRED DavidWired[] = {
|
|
/*
|
|
t:
|
|
cd \
|
|
md \staging
|
|
md \Replica-A\SERVO1
|
|
md \jet
|
|
md \jet\serv01
|
|
md \jet\serv01\sys
|
|
md \jet\serv01\temp
|
|
md \jet\serv01\log
|
|
|
|
u:
|
|
cd \
|
|
md \staging
|
|
md \Replica-A\SERVO2
|
|
md \jet
|
|
md \jet\serv02
|
|
md \jet\serv02\sys
|
|
md \jet\serv02\temp
|
|
md \jet\serv02\log
|
|
|
|
s:
|
|
cd \
|
|
md \staging
|
|
md \Replica-A\SERVO3
|
|
md \jet
|
|
md \jet\serv03
|
|
md \jet\serv03\sys
|
|
md \jet\serv03\temp
|
|
md \jet\serv03\log
|
|
|
|
*/
|
|
|
|
|
|
|
|
#define RSA L"Replica-A"
|
|
|
|
#define TEST_MACHINE_NAME THIS_COMPUTER
|
|
|
|
|
|
#define SERVER_1 L"SERV01"
|
|
#define MACHINE_1 TEST_MACHINE_NAME
|
|
|
|
#define SERVER_2 L"SERV02"
|
|
#define MACHINE_2 TEST_MACHINE_NAME
|
|
|
|
#define SERVER_3 L"SERV03"
|
|
#define MACHINE_3 TEST_MACHINE_NAME
|
|
|
|
#define SERVER_4 L"SERV04"
|
|
#define MACHINE_4 TEST_MACHINE_NAME
|
|
|
|
#define SERVER_5 L"SERV05"
|
|
#define MACHINE_5 TEST_MACHINE_NAME
|
|
|
|
#define SERVER_6 L"SERV06"
|
|
#define MACHINE_6 TEST_MACHINE_NAME
|
|
|
|
#define SERVER_7 L"SERV07"
|
|
#define MACHINE_7 TEST_MACHINE_NAME
|
|
|
|
#define SERVER_8 L"SERV08"
|
|
#define MACHINE_8 TEST_MACHINE_NAME
|
|
|
|
/*
|
|
// These are the old vol assignments
|
|
#define SERVER_1_VOL L"t:"
|
|
#define SERVER_2_VOL L"u:"
|
|
#define SERVER_3_VOL L"s:"
|
|
#define SERVER_4_VOL L"v:"
|
|
#define SERVER_5_VOL L"w:"
|
|
#define SERVER_6_VOL L"x:"
|
|
#define SERVER_7_VOL L"y:"
|
|
#define SERVER_8_VOL L"z:"
|
|
*/
|
|
|
|
// /*
|
|
// These are the new vol assignments
|
|
#define SERVER_1_VOL L"d:"
|
|
#define SERVER_2_VOL L"e:"
|
|
#define SERVER_3_VOL L"f:"
|
|
#define SERVER_4_VOL L"g:"
|
|
#define SERVER_5_VOL L"h:"
|
|
#define SERVER_6_VOL L"i:"
|
|
#define SERVER_7_VOL L"j:"
|
|
#define SERVER_8_VOL L"k:"
|
|
// */
|
|
|
|
|
|
/*
|
|
//
|
|
// NOTE: The following was generated from an excel spreadsheet
|
|
// \nt\private\net\svcimgs\ntrepl\topology.xls
|
|
// Hand generation is a bit tedious and error prone so use the spreadsheet.
|
|
//
|
|
|
|
//
|
|
// David's 8-way fully connected
|
|
//
|
|
TEST_MACHINE_NAME, // machine
|
|
SERVER_1, // server name
|
|
RSA, // replica
|
|
TRUE, // IsPrimary
|
|
NULL, NULL, // File/Dir Filter
|
|
{L"CXT2_1", L"CXT3_1", L"CXT4_1", L"CXT5_1", L"CXT6_1", L"CXT7_1", L"CXT8_1", NULL}, // inbound cxtions
|
|
{MACHINE_2, MACHINE_3, MACHINE_4, MACHINE_5, MACHINE_6, MACHINE_7, MACHINE_8, NULL}, // inbound machines
|
|
{SERVER_2, SERVER_3, SERVER_4, SERVER_5, SERVER_6, SERVER_7, SERVER_8, NULL}, // inbound servers
|
|
{L"CXT1_2", L"CXT1_3", L"CXT1_4", L"CXT1_5", L"CXT1_6", L"CXT1_7", L"CXT1_8", NULL}, // outbound cxtions
|
|
{MACHINE_2, MACHINE_3, MACHINE_4, MACHINE_5, MACHINE_6, MACHINE_7, MACHINE_8, NULL}, // outbound machines
|
|
{SERVER_2, SERVER_3, SERVER_4, SERVER_5, SERVER_6, SERVER_7, SERVER_8, NULL}, // outbound servers
|
|
SERVER_1_VOL L"\\staging", // stage
|
|
SERVER_1_VOL L"\\" RSA L"\\" SERVER_1, // root
|
|
SERVER_1_VOL L"\\jet\\" SERVER_1, // Jet Path
|
|
|
|
TEST_MACHINE_NAME, // machine
|
|
SERVER_2, // server name
|
|
RSA, // replica
|
|
FALSE, // IsPrimary
|
|
NULL, NULL, // File/Dir Filter
|
|
{L"CXT1_2", L"CXT3_2", L"CXT4_2", L"CXT5_2", L"CXT6_2", L"CXT7_2", L"CXT8_2", NULL}, // inbound cxtions
|
|
{MACHINE_1, MACHINE_3, MACHINE_4, MACHINE_5, MACHINE_6, MACHINE_7, MACHINE_8, NULL}, // inbound machines
|
|
{SERVER_1, SERVER_3, SERVER_4, SERVER_5, SERVER_6, SERVER_7, SERVER_8, NULL}, // inbound servers
|
|
{L"CXT2_1", L"CXT2_3", L"CXT2_4", L"CXT2_5", L"CXT2_6", L"CXT2_7", L"CXT2_8", NULL}, // outbound cxtions
|
|
{MACHINE_1, MACHINE_3, MACHINE_4, MACHINE_5, MACHINE_6, MACHINE_7, MACHINE_8, NULL}, // outbound machines
|
|
{SERVER_1, SERVER_3, SERVER_4, SERVER_5, SERVER_6, SERVER_7, SERVER_8, NULL}, // outbound servers
|
|
SERVER_2_VOL L"\\staging", // stage
|
|
SERVER_2_VOL L"\\" RSA L"\\" SERVER_2, // root
|
|
SERVER_2_VOL L"\\jet\\" SERVER_2, // Jet Path
|
|
|
|
TEST_MACHINE_NAME, // machine
|
|
SERVER_3, // server name
|
|
RSA, // replica
|
|
FALSE, // IsPrimary
|
|
NULL, NULL, // File/Dir Filter
|
|
{L"CXT1_3", L"CXT2_3", L"CXT4_3", L"CXT5_3", L"CXT6_3", L"CXT7_3", L"CXT8_3", NULL}, // inbound cxtions
|
|
{MACHINE_1, MACHINE_2, MACHINE_4, MACHINE_5, MACHINE_6, MACHINE_7, MACHINE_8, NULL}, // inbound machines
|
|
{SERVER_1, SERVER_2, SERVER_4, SERVER_5, SERVER_6, SERVER_7, SERVER_8, NULL}, // inbound servers
|
|
{L"CXT3_1", L"CXT3_2", L"CXT3_4", L"CXT3_5", L"CXT3_6", L"CXT3_7", L"CXT3_8", NULL}, // outbound cxtions
|
|
{MACHINE_1, MACHINE_2, MACHINE_4, MACHINE_5, MACHINE_6, MACHINE_7, MACHINE_8, NULL}, // outbound machines
|
|
{SERVER_1, SERVER_2, SERVER_4, SERVER_5, SERVER_6, SERVER_7, SERVER_8, NULL}, // outbound servers
|
|
SERVER_3_VOL L"\\staging", // stage
|
|
SERVER_3_VOL L"\\" RSA L"\\" SERVER_3, // root
|
|
SERVER_3_VOL L"\\jet\\" SERVER_3, // Jet Path
|
|
|
|
TEST_MACHINE_NAME, // machine
|
|
SERVER_4, // server name
|
|
RSA, // replica
|
|
FALSE, // IsPrimary
|
|
NULL, NULL, // File/Dir Filter
|
|
{L"CXT1_4", L"CXT2_4", L"CXT3_4", L"CXT5_4", L"CXT6_4", L"CXT7_4", L"CXT8_4", NULL}, // inbound cxtions
|
|
{MACHINE_1, MACHINE_2, MACHINE_3, MACHINE_5, MACHINE_6, MACHINE_7, MACHINE_8, NULL}, // inbound machines
|
|
{SERVER_1, SERVER_2, SERVER_3, SERVER_5, SERVER_6, SERVER_7, SERVER_8, NULL}, // inbound servers
|
|
{L"CXT4_1", L"CXT4_2", L"CXT4_3", L"CXT4_5", L"CXT4_6", L"CXT4_7", L"CXT4_8", NULL}, // outbound cxtions
|
|
{MACHINE_1, MACHINE_2, MACHINE_3, MACHINE_5, MACHINE_6, MACHINE_7, MACHINE_8, NULL}, // outbound machines
|
|
{SERVER_1, SERVER_2, SERVER_3, SERVER_5, SERVER_6, SERVER_7, SERVER_8, NULL}, // outbound servers
|
|
SERVER_4_VOL L"\\staging", // stage
|
|
SERVER_4_VOL L"\\" RSA L"\\" SERVER_4, // root
|
|
SERVER_4_VOL L"\\jet\\" SERVER_4, // Jet Path
|
|
|
|
TEST_MACHINE_NAME, // machine
|
|
SERVER_5, // server name
|
|
RSA, // replica
|
|
FALSE, // IsPrimary
|
|
NULL, NULL, // File/Dir Filter
|
|
{L"CXT1_5", L"CXT2_5", L"CXT3_5", L"CXT4_5", L"CXT6_5", L"CXT7_5", L"CXT8_5", NULL}, // inbound cxtions
|
|
{MACHINE_1, MACHINE_2, MACHINE_3, MACHINE_4, MACHINE_6, MACHINE_7, MACHINE_8, NULL}, // inbound machines
|
|
{SERVER_1, SERVER_2, SERVER_3, SERVER_4, SERVER_6, SERVER_7, SERVER_8, NULL}, // inbound servers
|
|
{L"CXT5_1", L"CXT5_2", L"CXT5_3", L"CXT5_4", L"CXT5_6", L"CXT5_7", L"CXT5_8", NULL}, // outbound cxtions
|
|
{MACHINE_1, MACHINE_2, MACHINE_3, MACHINE_4, MACHINE_6, MACHINE_7, MACHINE_8, NULL}, // outbound machines
|
|
{SERVER_1, SERVER_2, SERVER_3, SERVER_4, SERVER_6, SERVER_7, SERVER_8, NULL}, // outbound servers
|
|
SERVER_5_VOL L"\\staging", // stage
|
|
SERVER_5_VOL L"\\" RSA L"\\" SERVER_5, // root
|
|
SERVER_5_VOL L"\\jet\\" SERVER_5, // Jet Path
|
|
|
|
TEST_MACHINE_NAME, // machine
|
|
SERVER_6, // server name
|
|
RSA, // replica
|
|
FALSE, // IsPrimary
|
|
NULL, NULL, // File/Dir Filter
|
|
{L"CXT1_6", L"CXT2_6", L"CXT3_6", L"CXT4_6", L"CXT5_6", L"CXT7_6", L"CXT8_6", NULL}, // inbound cxtions
|
|
{MACHINE_1, MACHINE_2, MACHINE_3, MACHINE_4, MACHINE_5, MACHINE_7, MACHINE_8, NULL}, // inbound machines
|
|
{SERVER_1, SERVER_2, SERVER_3, SERVER_4, SERVER_5, SERVER_7, SERVER_8, NULL}, // inbound servers
|
|
{L"CXT6_1", L"CXT6_2", L"CXT6_3", L"CXT6_4", L"CXT6_5", L"CXT6_7", L"CXT6_8", NULL}, // outbound cxtions
|
|
{MACHINE_1, MACHINE_2, MACHINE_3, MACHINE_4, MACHINE_5, MACHINE_7, MACHINE_8, NULL}, // outbound machines
|
|
{SERVER_1, SERVER_2, SERVER_3, SERVER_4, SERVER_5, SERVER_7, SERVER_8, NULL}, // outbound servers
|
|
SERVER_6_VOL L"\\staging", // stage
|
|
SERVER_6_VOL L"\\" RSA L"\\" SERVER_6, // root
|
|
SERVER_6_VOL L"\\jet\\" SERVER_6, // Jet Path
|
|
|
|
TEST_MACHINE_NAME, // machine
|
|
SERVER_7, // server name
|
|
RSA, // replica
|
|
FALSE, // IsPrimary
|
|
NULL, NULL, // File/Dir Filter
|
|
{L"CXT1_7", L"CXT2_7", L"CXT3_7", L"CXT4_7", L"CXT5_7", L"CXT6_7", L"CXT8_7", NULL}, // inbound cxtions
|
|
{MACHINE_1, MACHINE_2, MACHINE_3, MACHINE_4, MACHINE_5, MACHINE_6, MACHINE_8, NULL}, // inbound machines
|
|
{SERVER_1, SERVER_2, SERVER_3, SERVER_4, SERVER_5, SERVER_6, SERVER_8, NULL}, // inbound servers
|
|
{L"CXT7_1", L"CXT7_2", L"CXT7_3", L"CXT7_4", L"CXT7_5", L"CXT7_6", L"CXT7_8", NULL}, // outbound cxtions
|
|
{MACHINE_1, MACHINE_2, MACHINE_3, MACHINE_4, MACHINE_5, MACHINE_6, MACHINE_8, NULL}, // outbound machines
|
|
{SERVER_1, SERVER_2, SERVER_3, SERVER_4, SERVER_5, SERVER_6, SERVER_8, NULL}, // outbound servers
|
|
SERVER_7_VOL L"\\staging", // stage
|
|
SERVER_7_VOL L"\\" RSA L"\\" SERVER_7, // root
|
|
SERVER_7_VOL L"\\jet\\" SERVER_7, // Jet Path
|
|
|
|
TEST_MACHINE_NAME, // machine
|
|
SERVER_8, // server name
|
|
RSA, // replica
|
|
FALSE, // IsPrimary
|
|
NULL, NULL, // File/Dir Filter
|
|
{L"CXT1_8", L"CXT2_8", L"CXT3_8", L"CXT4_8", L"CXT5_8", L"CXT6_8", L"CXT7_8", NULL}, // inbound cxtions
|
|
{MACHINE_1, MACHINE_2, MACHINE_3, MACHINE_4, MACHINE_5, MACHINE_6, MACHINE_7, NULL}, // inbound machines
|
|
{SERVER_1, SERVER_2, SERVER_3, SERVER_4, SERVER_5, SERVER_6, SERVER_7, NULL}, // inbound servers
|
|
{L"CXT8_1", L"CXT8_2", L"CXT8_3", L"CXT8_4", L"CXT8_5", L"CXT8_6", L"CXT8_7", NULL}, // outbound cxtions
|
|
{MACHINE_1, MACHINE_2, MACHINE_3, MACHINE_4, MACHINE_5, MACHINE_6, MACHINE_7, NULL}, // outbound machines
|
|
{SERVER_1, SERVER_2, SERVER_3, SERVER_4, SERVER_5, SERVER_6, SERVER_7, NULL}, // outbound servers
|
|
SERVER_8_VOL L"\\staging", // stage
|
|
SERVER_8_VOL L"\\" RSA L"\\" SERVER_8, // root
|
|
SERVER_8_VOL L"\\jet\\" SERVER_8, // Jet Path
|
|
|
|
*/
|
|
|
|
///*
|
|
//
|
|
// 8 way ring
|
|
//
|
|
|
|
TEST_MACHINE_NAME, // machine
|
|
SERVER_1, // server name
|
|
RSA, // replica
|
|
TRUE, // IsPrimary
|
|
NULL, NULL, // File/Dir Filter
|
|
{L"CXT2_1", L"CXT8_1", NULL }, // inbound cxtions
|
|
{MACHINE_2, MACHINE_8, NULL }, // inbound machines
|
|
{SERVER_2, SERVER_8, NULL }, // inbound servers
|
|
{L"CXT1_2", L"CXT1_8", NULL }, // outbound cxtions
|
|
{MACHINE_2, MACHINE_8, NULL }, // outbound machines
|
|
{SERVER_2, SERVER_8, NULL }, // outbound servers
|
|
SERVER_1_VOL L"\\staging", // stage
|
|
SERVER_1_VOL L"\\" RSA L"\\" SERVER_1, // root
|
|
SERVER_1_VOL L"\\jet\\" SERVER_1, // Jet Path
|
|
|
|
TEST_MACHINE_NAME, // machine
|
|
SERVER_2, // server name
|
|
RSA, // replica
|
|
FALSE, // IsPrimary
|
|
NULL, NULL, // File/Dir Filter
|
|
{L"CXT3_2", L"CXT1_2", NULL }, // inbound cxtions
|
|
{MACHINE_3, MACHINE_1, NULL }, // inbound machines
|
|
{SERVER_3, SERVER_1, NULL }, // inbound servers
|
|
{L"CXT2_3", L"CXT2_1", NULL }, // outbound cxtions
|
|
{MACHINE_3, MACHINE_1, NULL }, // outbound machines
|
|
{SERVER_3, SERVER_1, NULL }, // outbound servers
|
|
SERVER_2_VOL L"\\staging", // stage
|
|
SERVER_2_VOL L"\\" RSA L"\\" SERVER_2, // root
|
|
SERVER_2_VOL L"\\jet\\" SERVER_2, // Jet Path
|
|
|
|
TEST_MACHINE_NAME, // machine
|
|
SERVER_3, // server name
|
|
RSA, // replica
|
|
FALSE, // IsPrimary
|
|
NULL, NULL, // File/Dir Filter
|
|
{L"CXT4_3", L"CXT2_3", NULL }, // inbound cxtions
|
|
{MACHINE_4, MACHINE_2, NULL }, // inbound machines
|
|
{SERVER_4, SERVER_2, NULL }, // inbound servers
|
|
{L"CXT3_4", L"CXT3_2", NULL }, // outbound cxtions
|
|
{MACHINE_4, MACHINE_2, NULL }, // outbound machines
|
|
{SERVER_4, SERVER_2, NULL }, // outbound servers
|
|
SERVER_3_VOL L"\\staging", // stage
|
|
SERVER_3_VOL L"\\" RSA L"\\" SERVER_3, // root
|
|
SERVER_3_VOL L"\\jet\\" SERVER_3, // Jet Path
|
|
|
|
TEST_MACHINE_NAME, // machine
|
|
SERVER_4, // server name
|
|
RSA, // replica
|
|
FALSE, // IsPrimary
|
|
NULL, NULL, // File/Dir Filter
|
|
{L"CXT5_4", L"CXT3_4", NULL }, // inbound cxtions
|
|
{MACHINE_5, MACHINE_3, NULL }, // inbound machines
|
|
{SERVER_5, SERVER_3, NULL }, // inbound servers
|
|
{L"CXT4_5", L"CXT4_3", NULL }, // outbound cxtions
|
|
{MACHINE_5, MACHINE_3, NULL }, // outbound machines
|
|
{SERVER_5, SERVER_3, NULL }, // outbound servers
|
|
SERVER_4_VOL L"\\staging", // stage
|
|
SERVER_4_VOL L"\\" RSA L"\\" SERVER_4, // root
|
|
SERVER_4_VOL L"\\jet\\" SERVER_4, // Jet Path
|
|
|
|
TEST_MACHINE_NAME, // machine
|
|
SERVER_5, // server name
|
|
RSA, // replica
|
|
FALSE, // IsPrimary
|
|
NULL, NULL, // File/Dir Filter
|
|
{L"CXT6_5", L"CXT4_5", NULL }, // inbound cxtions
|
|
{MACHINE_6, MACHINE_4, NULL }, // inbound machines
|
|
{SERVER_6, SERVER_4, NULL }, // inbound servers
|
|
{L"CXT5_6", L"CXT5_4", NULL }, // outbound cxtions
|
|
{MACHINE_6, MACHINE_4, NULL }, // outbound machines
|
|
{SERVER_6, SERVER_4, NULL }, // outbound servers
|
|
SERVER_5_VOL L"\\staging", // stage
|
|
SERVER_5_VOL L"\\" RSA L"\\" SERVER_5, // root
|
|
SERVER_5_VOL L"\\jet\\" SERVER_5, // Jet Path
|
|
|
|
TEST_MACHINE_NAME, // machine
|
|
SERVER_6, // server name
|
|
RSA, // replica
|
|
FALSE, // IsPrimary
|
|
NULL, NULL, // File/Dir Filter
|
|
{L"CXT7_6", L"CXT5_6", NULL }, // inbound cxtions
|
|
{MACHINE_7, MACHINE_5, NULL }, // inbound machines
|
|
{SERVER_7, SERVER_5, NULL }, // inbound servers
|
|
{L"CXT6_7", L"CXT6_5", NULL }, // outbound cxtions
|
|
{MACHINE_7, MACHINE_5, NULL }, // outbound machines
|
|
{SERVER_7, SERVER_5, NULL }, // outbound servers
|
|
SERVER_6_VOL L"\\staging", // stage
|
|
SERVER_6_VOL L"\\" RSA L"\\" SERVER_6, // root
|
|
SERVER_6_VOL L"\\jet\\" SERVER_6, // Jet Path
|
|
|
|
TEST_MACHINE_NAME, // machine
|
|
SERVER_7, // server name
|
|
RSA, // replica
|
|
FALSE, // IsPrimary
|
|
NULL, NULL, // File/Dir Filter
|
|
{L"CXT8_7", L"CXT6_7", NULL }, // inbound cxtions
|
|
{MACHINE_8, MACHINE_6, NULL }, // inbound machines
|
|
{SERVER_8, SERVER_6, NULL }, // inbound servers
|
|
{L"CXT7_8", L"CXT7_6", NULL }, // outbound cxtions
|
|
{MACHINE_8, MACHINE_6, NULL }, // outbound machines
|
|
{SERVER_8, SERVER_6, NULL }, // outbound servers
|
|
SERVER_7_VOL L"\\staging", // stage
|
|
SERVER_7_VOL L"\\" RSA L"\\" SERVER_7, // root
|
|
SERVER_7_VOL L"\\jet\\" SERVER_7, // Jet Path
|
|
|
|
TEST_MACHINE_NAME, // machine
|
|
SERVER_8, // server name
|
|
RSA, // replica
|
|
FALSE, // IsPrimary
|
|
NULL, NULL, // File/Dir Filter
|
|
{L"CXT1_8", L"CXT7_8", NULL }, // inbound cxtions
|
|
{MACHINE_1, MACHINE_7, NULL }, // inbound machines
|
|
{SERVER_1, SERVER_7, NULL }, // inbound servers
|
|
{L"CXT8_1", L"CXT8_7", NULL }, // outbound cxtions
|
|
{MACHINE_1, MACHINE_7, NULL }, // outbound machines
|
|
{SERVER_1, SERVER_7, NULL }, // outbound servers
|
|
SERVER_8_VOL L"\\staging", // stage
|
|
SERVER_8_VOL L"\\" RSA L"\\" SERVER_8, // root
|
|
SERVER_8_VOL L"\\jet\\" SERVER_8, // Jet Path
|
|
|
|
|
|
//*/
|
|
|
|
|
|
#define CXT_2_FM_1 L"CXT1_2"
|
|
#define CXT_3_FM_1 L"CXT1_3"
|
|
#define CXT_4_FM_1 L"CXT1_4"
|
|
#define CXT_1_FM_2 L"CXT2_1"
|
|
#define CXT_3_FM_2 L"CXT2_3"
|
|
#define CXT_4_FM_2 L"CXT2_4"
|
|
#define CXT_1_FM_3 L"CXT3_1"
|
|
#define CXT_2_FM_3 L"CXT3_2"
|
|
#define CXT_4_FM_3 L"CXT3_4"
|
|
#define CXT_1_FM_4 L"CXT4_1"
|
|
#define CXT_2_FM_4 L"CXT4_2"
|
|
#define CXT_3_FM_4 L"CXT4_3"
|
|
|
|
#define CXT_1_TO_2 L"CXT1_2"
|
|
#define CXT_1_TO_3 L"CXT1_3"
|
|
#define CXT_1_TO_4 L"CXT1_4"
|
|
#define CXT_2_TO_1 L"CXT2_1"
|
|
#define CXT_2_TO_3 L"CXT2_3"
|
|
#define CXT_2_TO_4 L"CXT2_4"
|
|
#define CXT_3_TO_1 L"CXT3_1"
|
|
#define CXT_3_TO_2 L"CXT3_2"
|
|
#define CXT_3_TO_4 L"CXT3_4"
|
|
#define CXT_4_TO_1 L"CXT4_1"
|
|
#define CXT_4_TO_2 L"CXT4_2"
|
|
#define CXT_4_TO_3 L"CXT4_3"
|
|
|
|
|
|
/*
|
|
//
|
|
// David's 4-way
|
|
//
|
|
TEST_MACHINE_NAME, // machine
|
|
SERVER_1, // server name
|
|
RSA, // replica
|
|
TRUE, // IsPrimary
|
|
NULL, NULL, // File/Dir Filter
|
|
{ CXT_1_FM_2, CXT_1_FM_3, CXT_1_FM_4, NULL }, // inbound cxtions
|
|
{ MACHINE_2, MACHINE_3, MACHINE_4, NULL }, // inbound machines
|
|
{ SERVER_2, SERVER_3, SERVER_4, NULL }, // inbound servers
|
|
{ CXT_1_TO_2, CXT_1_TO_3, CXT_1_TO_4, NULL }, // outbound cxtions
|
|
{ MACHINE_2, MACHINE_3, MACHINE_4, NULL }, // outbound machines
|
|
{ SERVER_2, SERVER_3, SERVER_4, NULL }, // outbound servers
|
|
SERVER_1_VOL L"\\staging", // stage
|
|
SERVER_1_VOL L"\\" RSA L"\\" SERVER_1, // root
|
|
SERVER_1_VOL L"\\jet\\" SERVER_1, // Jet Path
|
|
|
|
TEST_MACHINE_NAME, // machine
|
|
SERVER_2, // server name
|
|
RSA, // replica
|
|
FALSE, // IsPrimary
|
|
NULL, NULL, // File/Dir Filter
|
|
{ CXT_2_FM_1, CXT_2_FM_3, CXT_2_FM_4, NULL }, // inbound cxtions
|
|
{ MACHINE_1, MACHINE_3, MACHINE_4, NULL }, // inbound machines
|
|
{ SERVER_1, SERVER_3, SERVER_4, NULL }, // inbound servers
|
|
{ CXT_2_TO_1, CXT_2_TO_3, CXT_2_TO_4, NULL }, // outbound cxtions
|
|
{ MACHINE_1, MACHINE_3, MACHINE_4, NULL }, // outbound machines
|
|
{ SERVER_1, SERVER_3, SERVER_4, NULL }, // outbound servers
|
|
SERVER_2_VOL L"\\staging", // stage
|
|
SERVER_2_VOL L"\\" RSA L"\\" SERVER_2, // root
|
|
SERVER_2_VOL L"\\jet\\" SERVER_2, // Jet Path
|
|
|
|
TEST_MACHINE_NAME, // machine
|
|
SERVER_3, // server name
|
|
RSA, // replica
|
|
FALSE, // IsPrimary
|
|
NULL, NULL, // File/Dir Filter
|
|
{ CXT_3_FM_1, CXT_3_FM_2, CXT_3_FM_4, NULL }, // inbound cxtions
|
|
{ MACHINE_1, MACHINE_2, MACHINE_4, NULL }, // inbound machines
|
|
{ SERVER_1, SERVER_2, SERVER_4, NULL }, // inbound servers
|
|
{ CXT_3_TO_1, CXT_3_TO_2, CXT_3_TO_4, NULL }, // outbound cxtions
|
|
{ MACHINE_1, MACHINE_2, MACHINE_4, NULL }, // outbound machines
|
|
{ SERVER_1, SERVER_2, SERVER_4, NULL }, // outbound servers
|
|
SERVER_3_VOL L"\\staging", // stage
|
|
SERVER_3_VOL L"\\" RSA L"\\" SERVER_3, // root
|
|
SERVER_3_VOL L"\\jet\\" SERVER_3, // Jet Path
|
|
|
|
TEST_MACHINE_NAME, // machine
|
|
SERVER_4, // server name
|
|
RSA, // replica
|
|
FALSE, // IsPrimary
|
|
NULL, NULL, // File/Dir Filter
|
|
{ CXT_4_FM_1, CXT_4_FM_2, CXT_4_FM_3, NULL }, // inbound cxtions
|
|
{ MACHINE_1, MACHINE_2, MACHINE_3, NULL }, // inbound machines
|
|
{ SERVER_1, SERVER_2, SERVER_3, NULL }, // inbound servers
|
|
{ CXT_4_TO_1, CXT_4_TO_2, CXT_4_TO_3, NULL }, // outbound cxtions
|
|
{ MACHINE_1, MACHINE_2, MACHINE_3, NULL }, // outbound machines
|
|
{ SERVER_1, SERVER_2, SERVER_3, NULL }, // outbound servers
|
|
SERVER_4_VOL L"\\staging", // stage
|
|
SERVER_4_VOL L"\\" RSA L"\\" SERVER_4, // root
|
|
SERVER_4_VOL L"\\jet\\" SERVER_4, // Jet Path
|
|
*/
|
|
|
|
|
|
/*
|
|
//
|
|
// David's 3-way
|
|
//
|
|
TEST_MACHINE_NAME, // machine
|
|
SERVER_1, // server name
|
|
RSA, // replica
|
|
TRUE, // IsPrimary
|
|
NULL, NULL, // File/Dir Filter
|
|
{ CXT_1_FM_2, CXT_1_FM_3, NULL }, // inbound cxtions
|
|
{ MACHINE_2, MACHINE_3, NULL }, // inbound machines
|
|
{ SERVER_2, SERVER_3, NULL }, // inbound servers
|
|
{ CXT_1_TO_2, CXT_1_TO_3, NULL }, // outbound cxtions
|
|
{ MACHINE_2, MACHINE_3, NULL }, // outbound machines
|
|
{ SERVER_2, SERVER_3, NULL }, // outbound servers
|
|
SERVER_1_VOL L"\\staging", // stage
|
|
SERVER_1_VOL L"\\" RSA L"\\" SERVER_1, // root
|
|
SERVER_1_VOL L"\\jet\\" SERVER_1, // Jet Path
|
|
|
|
TEST_MACHINE_NAME, // machine
|
|
SERVER_2, // server name
|
|
RSA, // replica
|
|
FALSE, // IsPrimary
|
|
NULL, NULL, // File/Dir Filter
|
|
{ CXT_2_FM_1, CXT_2_FM_3, NULL }, // inbound cxtions
|
|
{ MACHINE_1, MACHINE_3, NULL }, // inbound machines
|
|
{ SERVER_1, SERVER_3, NULL }, // inbound servers
|
|
{ CXT_2_TO_1, CXT_2_TO_3, NULL }, // outbound cxtions
|
|
{ MACHINE_1, MACHINE_3, NULL }, // outbound machines
|
|
{ SERVER_1, SERVER_3, NULL }, // outbound servers
|
|
SERVER_2_VOL L"\\staging", // stage
|
|
SERVER_2_VOL L"\\" RSA L"\\" SERVER_2, // root
|
|
SERVER_2_VOL L"\\jet\\" SERVER_2, // Jet Path
|
|
|
|
TEST_MACHINE_NAME, // machine
|
|
SERVER_3, // server name
|
|
RSA, // replica
|
|
FALSE, // IsPrimary
|
|
NULL, NULL, // File/Dir Filter
|
|
{ CXT_3_FM_1, CXT_3_FM_2, NULL }, // inbound cxtions
|
|
{ MACHINE_1, MACHINE_2, NULL }, // inbound machines
|
|
{ SERVER_1, SERVER_2, NULL }, // inbound servers
|
|
{ CXT_3_TO_1, CXT_3_TO_2, NULL }, // outbound cxtions
|
|
{ MACHINE_1, MACHINE_2, NULL }, // outbound machines
|
|
{ SERVER_1, SERVER_2, NULL }, // outbound servers
|
|
SERVER_3_VOL L"\\staging", // stage
|
|
SERVER_3_VOL L"\\" RSA L"\\" SERVER_3, // root
|
|
SERVER_3_VOL L"\\jet\\" SERVER_3, // Jet Path
|
|
*/
|
|
|
|
|
|
/*
|
|
//
|
|
// David's 1-way
|
|
//
|
|
|
|
TEST_MACHINE_NAME, // machine
|
|
SERVER_1, // server name
|
|
RSA, // replica
|
|
TRUE, // IsPrimary
|
|
NULL, NULL, // File/Dir Filter
|
|
{ NULL, NULL }, // inbound cxtions
|
|
{ NULL, NULL }, // inbound machines
|
|
{ NULL, NULL }, // inbound servers
|
|
{ CXT_1_TO_2, NULL }, // outbound cxtions
|
|
{ MACHINE_2, NULL }, // outbound machines
|
|
{ SERVER_2, NULL }, // outbound servers
|
|
SERVER_1_VOL L"\\staging", // stage
|
|
SERVER_1_VOL L"\\" RSA L"\\" SERVER_1, // root
|
|
SERVER_1_VOL L"\\jet\\" SERVER_1, // Jet Path
|
|
|
|
TEST_MACHINE_NAME, // machine
|
|
SERVER_2, // server name
|
|
RSA, // replica
|
|
FALSE, // IsPrimary
|
|
NULL, NULL, // File/Dir Filter
|
|
{ CXT_2_FM_1, NULL }, // inbound cxtions
|
|
{ MACHINE_1, NULL }, // inbound machines
|
|
{ SERVER_1, NULL }, // inbound servers
|
|
{ NULL, NULL }, // outbound cxtions
|
|
{ NULL, NULL }, // outbound machines
|
|
{ NULL, NULL }, // outbound servers
|
|
SERVER_2_VOL L"\\staging", // stage
|
|
SERVER_2_VOL L"\\" RSA L"\\" SERVER_2, // root
|
|
SERVER_2_VOL L"\\jet\\" SERVER_2, // Jet Path
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
//
|
|
// David's 2-way
|
|
//
|
|
|
|
TEST_MACHINE_NAME, // machine
|
|
SERVER_1, // server name
|
|
RSA, // replica
|
|
TRUE, // IsPrimary
|
|
NULL, NULL, // File/Dir Filter
|
|
{ CXT_1_FM_2, NULL }, // inbound cxtions
|
|
{ MACHINE_2, NULL }, // inbound machines
|
|
{ SERVER_2, NULL }, // inbound servers
|
|
{ CXT_1_TO_2, NULL }, // outbound cxtions
|
|
{ MACHINE_2, NULL }, // outbound machines
|
|
{ SERVER_2, NULL }, // outbound servers
|
|
SERVER_1_VOL L"\\staging", // stage
|
|
SERVER_1_VOL L"\\" RSA L"\\" SERVER_1, // root
|
|
SERVER_1_VOL L"\\jet\\" SERVER_1, // Jet Path
|
|
|
|
TEST_MACHINE_NAME, // machine
|
|
SERVER_2, // server name
|
|
RSA, // replica
|
|
FALSE, // IsPrimary
|
|
NULL, NULL, // File/Dir Filter
|
|
{ CXT_2_FM_1, NULL }, // inbound cxtions
|
|
{ MACHINE_1, NULL }, // inbound machines
|
|
{ SERVER_1, NULL }, // inbound servers
|
|
{ CXT_2_TO_1, NULL }, // outbound cxtions
|
|
{ MACHINE_1, NULL }, // outbound machines
|
|
{ SERVER_1, NULL }, // outbound servers
|
|
SERVER_2_VOL L"\\staging", // stage
|
|
SERVER_2_VOL L"\\" RSA L"\\" SERVER_2, // root
|
|
SERVER_2_VOL L"\\jet\\" SERVER_2, // Jet Path
|
|
|
|
*/
|
|
//
|
|
// End of Config
|
|
//
|
|
NULL, NULL
|
|
};
|
|
|
|
|
|
|
|
|
|
HARDWIRED DavidWired2[] = {
|
|
|
|
|
|
|
|
///*
|
|
//
|
|
// 8 way ring Without Server2
|
|
//
|
|
|
|
TEST_MACHINE_NAME, // machine
|
|
SERVER_1, // server name
|
|
RSA, // replica
|
|
TRUE, // IsPrimary
|
|
NULL, NULL, // File/Dir Filter
|
|
{L"CXT2_1", L"CXT8_1", NULL }, // inbound cxtions
|
|
{MACHINE_2, MACHINE_8, NULL }, // inbound machines
|
|
{SERVER_2, SERVER_8, NULL }, // inbound servers
|
|
{L"CXT1_2", L"CXT1_8", NULL }, // outbound cxtions
|
|
{MACHINE_2, MACHINE_8, NULL }, // outbound machines
|
|
{SERVER_2, SERVER_8, NULL }, // outbound servers
|
|
SERVER_1_VOL L"\\staging", // stage
|
|
SERVER_1_VOL L"\\" RSA L"\\" SERVER_1, // root
|
|
SERVER_1_VOL L"\\jet\\" SERVER_1, // Jet Path
|
|
|
|
#if 0
|
|
TEST_MACHINE_NAME, // machine
|
|
SERVER_2, // server name
|
|
RSA, // replica
|
|
FALSE, // IsPrimary
|
|
NULL, NULL, // File/Dir Filter
|
|
{L"CXT3_2", L"CXT1_2", NULL }, // inbound cxtions
|
|
{MACHINE_3, MACHINE_1, NULL }, // inbound machines
|
|
{SERVER_3, SERVER_1, NULL }, // inbound servers
|
|
{L"CXT2_3", L"CXT2_1", NULL }, // outbound cxtions
|
|
{MACHINE_3, MACHINE_1, NULL }, // outbound machines
|
|
{SERVER_3, SERVER_1, NULL }, // outbound servers
|
|
SERVER_2_VOL L"\\staging", // stage
|
|
SERVER_2_VOL L"\\" RSA L"\\" SERVER_2, // root
|
|
SERVER_2_VOL L"\\jet\\" SERVER_2, // Jet Path
|
|
#endif
|
|
|
|
TEST_MACHINE_NAME, // machine
|
|
SERVER_3, // server name
|
|
RSA, // replica
|
|
FALSE, // IsPrimary
|
|
NULL, NULL, // File/Dir Filter
|
|
{L"CXT4_3", L"CXT2_3", NULL }, // inbound cxtions
|
|
{MACHINE_4, MACHINE_2, NULL }, // inbound machines
|
|
{SERVER_4, SERVER_2, NULL }, // inbound servers
|
|
{L"CXT3_4", L"CXT3_2", NULL }, // outbound cxtions
|
|
{MACHINE_4, MACHINE_2, NULL }, // outbound machines
|
|
{SERVER_4, SERVER_2, NULL }, // outbound servers
|
|
SERVER_3_VOL L"\\staging", // stage
|
|
SERVER_3_VOL L"\\" RSA L"\\" SERVER_3, // root
|
|
SERVER_3_VOL L"\\jet\\" SERVER_3, // Jet Path
|
|
|
|
TEST_MACHINE_NAME, // machine
|
|
SERVER_4, // server name
|
|
RSA, // replica
|
|
FALSE, // IsPrimary
|
|
NULL, NULL, // File/Dir Filter
|
|
{L"CXT5_4", L"CXT3_4", NULL }, // inbound cxtions
|
|
{MACHINE_5, MACHINE_3, NULL }, // inbound machines
|
|
{SERVER_5, SERVER_3, NULL }, // inbound servers
|
|
{L"CXT4_5", L"CXT4_3", NULL }, // outbound cxtions
|
|
{MACHINE_5, MACHINE_3, NULL }, // outbound machines
|
|
{SERVER_5, SERVER_3, NULL }, // outbound servers
|
|
SERVER_4_VOL L"\\staging", // stage
|
|
SERVER_4_VOL L"\\" RSA L"\\" SERVER_4, // root
|
|
SERVER_4_VOL L"\\jet\\" SERVER_4, // Jet Path
|
|
|
|
TEST_MACHINE_NAME, // machine
|
|
SERVER_5, // server name
|
|
RSA, // replica
|
|
FALSE, // IsPrimary
|
|
NULL, NULL, // File/Dir Filter
|
|
{L"CXT6_5", L"CXT4_5", NULL }, // inbound cxtions
|
|
{MACHINE_6, MACHINE_4, NULL }, // inbound machines
|
|
{SERVER_6, SERVER_4, NULL }, // inbound servers
|
|
{L"CXT5_6", L"CXT5_4", NULL }, // outbound cxtions
|
|
{MACHINE_6, MACHINE_4, NULL }, // outbound machines
|
|
{SERVER_6, SERVER_4, NULL }, // outbound servers
|
|
SERVER_5_VOL L"\\staging", // stage
|
|
SERVER_5_VOL L"\\" RSA L"\\" SERVER_5, // root
|
|
SERVER_5_VOL L"\\jet\\" SERVER_5, // Jet Path
|
|
|
|
TEST_MACHINE_NAME, // machine
|
|
SERVER_6, // server name
|
|
RSA, // replica
|
|
FALSE, // IsPrimary
|
|
NULL, NULL, // File/Dir Filter
|
|
{L"CXT7_6", L"CXT5_6", NULL }, // inbound cxtions
|
|
{MACHINE_7, MACHINE_5, NULL }, // inbound machines
|
|
{SERVER_7, SERVER_5, NULL }, // inbound servers
|
|
{L"CXT6_7", L"CXT6_5", NULL }, // outbound cxtions
|
|
{MACHINE_7, MACHINE_5, NULL }, // outbound machines
|
|
{SERVER_7, SERVER_5, NULL }, // outbound servers
|
|
SERVER_6_VOL L"\\staging", // stage
|
|
SERVER_6_VOL L"\\" RSA L"\\" SERVER_6, // root
|
|
SERVER_6_VOL L"\\jet\\" SERVER_6, // Jet Path
|
|
|
|
TEST_MACHINE_NAME, // machine
|
|
SERVER_7, // server name
|
|
RSA, // replica
|
|
FALSE, // IsPrimary
|
|
NULL, NULL, // File/Dir Filter
|
|
{L"CXT8_7", L"CXT6_7", NULL }, // inbound cxtions
|
|
{MACHINE_8, MACHINE_6, NULL }, // inbound machines
|
|
{SERVER_8, SERVER_6, NULL }, // inbound servers
|
|
{L"CXT7_8", L"CXT7_6", NULL }, // outbound cxtions
|
|
{MACHINE_8, MACHINE_6, NULL }, // outbound machines
|
|
{SERVER_8, SERVER_6, NULL }, // outbound servers
|
|
SERVER_7_VOL L"\\staging", // stage
|
|
SERVER_7_VOL L"\\" RSA L"\\" SERVER_7, // root
|
|
SERVER_7_VOL L"\\jet\\" SERVER_7, // Jet Path
|
|
|
|
TEST_MACHINE_NAME, // machine
|
|
SERVER_8, // server name
|
|
RSA, // replica
|
|
FALSE, // IsPrimary
|
|
NULL, NULL, // File/Dir Filter
|
|
{L"CXT1_8", L"CXT7_8", NULL }, // inbound cxtions
|
|
{MACHINE_1, MACHINE_7, NULL }, // inbound machines
|
|
{SERVER_1, SERVER_7, NULL }, // inbound servers
|
|
{L"CXT8_1", L"CXT8_7", NULL }, // outbound cxtions
|
|
{MACHINE_1, MACHINE_7, NULL }, // outbound machines
|
|
{SERVER_1, SERVER_7, NULL }, // outbound servers
|
|
SERVER_8_VOL L"\\staging", // stage
|
|
SERVER_8_VOL L"\\" RSA L"\\" SERVER_8, // root
|
|
SERVER_8_VOL L"\\jet\\" SERVER_8, // Jet Path
|
|
|
|
|
|
//
|
|
// End of Config
|
|
//
|
|
NULL, NULL
|
|
};
|
|
|
|
|
|
#endif DBG
|
|
|
|
|
|
#if DBG
|
|
GUID *
|
|
FrsDsBuildGuidFromName(
|
|
IN PWCHAR OrigName
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Build a guid from a character string
|
|
|
|
Arguments:
|
|
Name
|
|
|
|
Return Value:
|
|
Guid
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsBuildGuidFromName:"
|
|
PULONG Guid;
|
|
ULONG Len;
|
|
ULONG *Sum;
|
|
ULONG *SumOfSum;
|
|
PWCHAR Name = OrigName;
|
|
|
|
Guid = FrsAlloc(sizeof(GUID));
|
|
|
|
//
|
|
// First four bytes are the sum of the chars in Name; the
|
|
// second four bytes are the sum of sums. The last 8 bytes
|
|
// are 0.
|
|
//
|
|
Len = wcslen(Name);
|
|
Sum = Guid;
|
|
SumOfSum = Guid + 1;
|
|
while (Len--) {
|
|
*Sum += *Name++ + 1;
|
|
*SumOfSum += *Sum;
|
|
}
|
|
return (GUID *)Guid;
|
|
}
|
|
#endif DBG
|
|
|
|
|
|
#if DBG
|
|
VOID
|
|
FrsDsInitializeHardWiredStructs(
|
|
IN PHARDWIRED Wired
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Initialize the hardwired config stuff. Must happen before any
|
|
of the command servers start.
|
|
|
|
Arguments:
|
|
Wired - struct to initialize
|
|
|
|
Return Value:
|
|
None
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsInitializeHardWiredStructs:"
|
|
ULONG i;
|
|
ULONG j;
|
|
|
|
//
|
|
// NULL entries for "machine name" fields are assigned
|
|
// this machine's name
|
|
//
|
|
for (i = 0; Wired[i].Replica; ++i) {
|
|
if (ServerName && WSTR_EQ(ServerName, Wired[i].Server)) {
|
|
//
|
|
// Adjust the default jet parameters. Also, reset the
|
|
// ServerName to match the server name in the hard
|
|
// wired config so that the phoney guids match.
|
|
//
|
|
FrsFree(ServerName);
|
|
FrsFree(JetPath);
|
|
ServerName = FrsWcsDup(Wired[i].Server);
|
|
JetPath = FrsWcsDup(Wired[i].JetPath);
|
|
}
|
|
//
|
|
// Assign this machine's name if the machine entry is NULL
|
|
//
|
|
if (!Wired[i].Machine ||
|
|
WSTR_EQ(Wired[i].Machine, THIS_COMPUTER)) {
|
|
Wired[i].Machine = ComputerName;
|
|
}
|
|
for (j = 0; Wired[i].InNames[j]; ++j) {
|
|
//
|
|
// Assign this machine's name if the machine entry is NULL
|
|
//
|
|
if (WSTR_NE(Wired[i].InMachines[j], THIS_COMPUTER)) {
|
|
continue;
|
|
}
|
|
Wired[i].InMachines[j] = ComputerName;
|
|
}
|
|
for (j = 0; Wired[i].OutNames[j]; ++j) {
|
|
//
|
|
// Assign this machine's name if the machine entry is NULL
|
|
//
|
|
if (WSTR_NE(Wired[i].OutMachines[j], THIS_COMPUTER)) {
|
|
continue;
|
|
}
|
|
Wired[i].OutMachines[j] = ComputerName;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
BOOL
|
|
FrsDsLoadHardWiredFromFile(
|
|
PHARDWIRED *pMemberList,
|
|
PWCHAR pIniFileName
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
|
|
Fills the hardwired structure array from data in a file. The file format
|
|
has the form of:
|
|
|
|
[MEMBER0]
|
|
MACHINE=[This Computer]
|
|
SERVER=SERV01
|
|
REPLICA=Replica-A
|
|
ISPRIMARY=TRUE
|
|
FILEFILTERLIST=*.tmp;*.bak
|
|
DIRFILTERLIST=obj
|
|
INNAME=CXT2_1, CXT3_1
|
|
INMACHINE=[This Computer], [This Computer]
|
|
INSERVER=SERV02, SERV03
|
|
OUTNAME=CXT1_2, CXT1_3
|
|
OUTMACHINE=[This Computer], [This Computer]
|
|
OUTSERVER=SERV02, SERV03
|
|
STAGE=d:\staging
|
|
ROOT=d:\Replica-A\SERV01
|
|
JETPATH=d:\jet
|
|
|
|
[MEMBER1]
|
|
MACHINE=[This Computer]
|
|
SERVER=SERV02
|
|
REPLICA=Replica-A
|
|
ISPRIMARY=FALSE
|
|
FILEFILTERLIST=*.tmp;*.bak
|
|
DIRFILTERLIST=obj
|
|
INNAME=CXT1_2, CXT3_2
|
|
INMACHINE=[This Computer], [This Computer]
|
|
INSERVER=SERV01, SERV03
|
|
OUTNAME=CXT2_1, CXT2_3
|
|
OUTMACHINE=[This Computer], [This Computer]
|
|
OUTSERVER=SERV01, SERV03
|
|
STAGE=e:\staging
|
|
ROOT=e:\Replica-A\SERV02
|
|
JETPATH=e:\jet
|
|
|
|
[MEMBER2]
|
|
MACHINE=[This Computer]
|
|
SERVER=SERV03
|
|
REPLICA=Replica-A
|
|
ISPRIMARY=FALSE
|
|
FILEFILTERLIST=*.tmp;*.bak
|
|
DIRFILTERLIST=obj
|
|
INNAME=CXT1_3, CXT2_3
|
|
INMACHINE=[This Computer], [This Computer]
|
|
INSERVER=SERV01, SERV02
|
|
OUTNAME=CXT3_1, CXT3_2
|
|
OUTMACHINE=[This Computer], [This Computer]
|
|
OUTSERVER=SERV01, SERV02
|
|
STAGE=f:\staging
|
|
ROOT=f:\Replica-A\SERV03
|
|
JETPATH=f:\jet
|
|
|
|
The string "[This Computer]" has a special meaning in that it refers to the
|
|
computer on which the server is running. You can substitute a specific
|
|
computer name.
|
|
|
|
The entries for INNAME, INMACHINE and INSERVER are lists in which the
|
|
corresponding entries in each list form a related triple that speicfy
|
|
the given inbound connection.
|
|
|
|
Ditto for OUTNAME, OUTMACHINE, and OUTSERVER.
|
|
|
|
The configuration above is for a fully connected mesh with three members.
|
|
It works only when three copies of NTFRS are run on the same machine since
|
|
all the IN and OUTMACHINE entries specify "[This Computer]". The SERVER names
|
|
distinguish each of the three copies of NTFRS for the purpose of providing RPC
|
|
endpoints.
|
|
|
|
If the members were actually run on separate physical machines then the
|
|
INMACHINES and the OUTMACHINES would need to specify the particular machine
|
|
names.
|
|
|
|
|
|
|
|
Arguments:
|
|
MemberList - Pointer to the pointer to the array of HardWired structures..
|
|
IniFileName - Name of the ini file to load from.
|
|
|
|
Return Value:
|
|
TRUE if data read ok.
|
|
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsLoadHardWiredFromFile:"
|
|
|
|
ULONG TotalMembers;
|
|
ULONG WStatus, Flag;
|
|
ULONG Len, RecordLen;
|
|
PWCHAR szIndex;
|
|
UINT i, k;
|
|
PHARDWIRED HwMember;
|
|
UNICODE_STRING UStr, ListArg;
|
|
PWCHAR pequal;
|
|
PWCHAR *ListArray;
|
|
WCHAR SectionNumber[16];
|
|
WCHAR SectionName[32];
|
|
WCHAR SectionBuffer[5000];
|
|
|
|
//
|
|
//Check if the ini file exists.
|
|
//
|
|
|
|
if (GetFileAttributes(pIniFileName) == 0xffffffff) {
|
|
DPRINT1(0, ":DS: Could not find ini file... %ws\n", IniFileName);
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Find the number of members in the replica set.
|
|
//
|
|
TotalMembers = 0;
|
|
while(TRUE) {
|
|
wcscpy(SectionName, L"MEMBER");
|
|
wcscpy(SectionNumber, _itow(TotalMembers, SectionNumber, 10));
|
|
wcscat(SectionName, SectionNumber);
|
|
|
|
//
|
|
//Read this section from the ini file.
|
|
//
|
|
Flag = GetPrivateProfileSection(SectionName,
|
|
SectionBuffer,
|
|
sizeof(SectionBuffer)/sizeof(WCHAR),
|
|
pIniFileName);
|
|
if (Flag == 0) {
|
|
WStatus = GetLastError();
|
|
break;
|
|
}
|
|
TotalMembers++;
|
|
}
|
|
|
|
if (TotalMembers == 0) {
|
|
DPRINT_WS(0, ":DS: No members found in inifile.", WStatus);
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Allocate memory. Then loop thru each member def in the ini file.
|
|
//
|
|
*pMemberList = (PHARDWIRED) FrsAlloc((TotalMembers + 1) * sizeof(HARDWIRED));
|
|
|
|
for ( i = 0 ; i < TotalMembers; ++i) {
|
|
|
|
wcscpy(SectionName, L"MEMBER");
|
|
wcscpy(SectionNumber, _itow(i, SectionNumber, 10));
|
|
wcscat(SectionName, SectionNumber);
|
|
|
|
WStatus = GetPrivateProfileSection(SectionName,
|
|
SectionBuffer,
|
|
sizeof(SectionBuffer)/sizeof(WCHAR),
|
|
pIniFileName);
|
|
HwMember = &(*pMemberList)[i];
|
|
|
|
for (szIndex = SectionBuffer; *szIndex != L'\0'; szIndex += RecordLen+1) {
|
|
|
|
RecordLen = wcslen(szIndex);
|
|
|
|
DPRINT3(5, ":DS: member %d: %ws [%d]\n", i, szIndex, RecordLen);
|
|
|
|
//
|
|
// Look for an arg of the form foo=bar.
|
|
//
|
|
pequal = wcschr(szIndex, L'=');
|
|
|
|
if (pequal == NULL) {
|
|
DPRINT1(0, ":DS: ERROR - Malformed parameter: %ws\n", szIndex);
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Null terminate and uppercase lefthand side.
|
|
//
|
|
*pequal = UNICODE_NULL;
|
|
_wcsupr(szIndex);
|
|
|
|
++pequal;
|
|
Len = wcslen(pequal);
|
|
if (Len == 0) {
|
|
DPRINT1(0, ":DS: ERROR - Malformed parameter: %ws\n", szIndex);
|
|
continue;
|
|
}
|
|
|
|
Len = (Len + 1) * sizeof(WCHAR);
|
|
FrsSetUnicodeStringFromRawString(&UStr,
|
|
Len,
|
|
FrsWcsDup(pequal),
|
|
Len - sizeof(WCHAR));
|
|
|
|
if(!wcsncmp(szIndex, L"MACHINE",7)){
|
|
HwMember->Machine = UStr.Buffer;
|
|
continue;
|
|
}
|
|
|
|
if(!wcsncmp(szIndex, L"SERVER",6)){
|
|
HwMember->Server = UStr.Buffer;
|
|
continue;
|
|
}
|
|
|
|
if(!wcsncmp(szIndex, L"REPLICA",7)){
|
|
HwMember->Replica = UStr.Buffer;
|
|
continue;
|
|
}
|
|
|
|
if(!wcsncmp(szIndex, L"ISPRIMARY",9)){
|
|
if (!wcscmp(UStr.Buffer, L"TRUE")) {
|
|
HwMember->IsPrimary = TRUE;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if(!wcsncmp(szIndex, L"FILEFILTERLIST",14)){
|
|
HwMember->FileFilterList = UStr.Buffer;
|
|
continue;
|
|
}
|
|
|
|
if(!wcsncmp(szIndex, L"DIRFILTERLIST",13)){
|
|
HwMember->DirFilterList = UStr.Buffer;
|
|
continue;
|
|
}
|
|
|
|
if(!wcsncmp(szIndex, L"STAGE",5)){
|
|
HwMember->Stage = UStr.Buffer;
|
|
continue;
|
|
}
|
|
|
|
if(!wcsncmp(szIndex, L"ROOT",4)){
|
|
HwMember->Root = UStr.Buffer;
|
|
continue;
|
|
}
|
|
|
|
if(!wcsncmp(szIndex, L"JETPATH",7)) {
|
|
HwMember->JetPath = UStr.Buffer;
|
|
continue;
|
|
}
|
|
|
|
if (!wcsncmp(szIndex, L"INNAME", 6)) {
|
|
ListArray = HwMember->InNames;
|
|
goto PARSE_COMMA_LIST;
|
|
}
|
|
|
|
if (!wcsncmp(szIndex, L"INMACHINE", 9)) {
|
|
ListArray = HwMember->InMachines;
|
|
goto PARSE_COMMA_LIST;
|
|
}
|
|
|
|
if (!wcsncmp(szIndex, L"INSERVER", 8)) {
|
|
ListArray = HwMember->InServers;
|
|
goto PARSE_COMMA_LIST;
|
|
}
|
|
|
|
if (!wcsncmp(szIndex, L"OUTNAME", 7)) {
|
|
ListArray = HwMember->OutNames;
|
|
goto PARSE_COMMA_LIST;
|
|
}
|
|
|
|
if (!wcsncmp(szIndex, L"OUTMACHINE", 10)) {
|
|
ListArray = HwMember->OutMachines;
|
|
goto PARSE_COMMA_LIST;
|
|
}
|
|
|
|
if (!wcsncmp(szIndex, L"OUTSERVER", 9)) {
|
|
ListArray = HwMember->OutServers;
|
|
goto PARSE_COMMA_LIST;
|
|
}
|
|
|
|
PARSE_COMMA_LIST:
|
|
|
|
//
|
|
// Parse the right hand side of args like
|
|
// INSERVER=machine1, machine2, machine3
|
|
// Code above determined what the left hand side was.
|
|
//
|
|
k = 0;
|
|
while (FrsDissectCommaList(UStr, &ListArg, &UStr) &&
|
|
(k < HW_MACHINES)) {
|
|
|
|
ListArray[k] = NULL;
|
|
|
|
if (ListArg.Length > 0) {
|
|
DPRINT2(5, ":DS: ListArg string: %ws {%d)\n",
|
|
(ListArg.Buffer != NULL) ? ListArg.Buffer : L"<NULL>",
|
|
ListArg.Length);
|
|
|
|
ListArray[k] = ListArg.Buffer;
|
|
|
|
// Replace the comma (or white space with a null)
|
|
ListArg.Buffer[ListArg.Length/sizeof(WCHAR)] = UNICODE_NULL;
|
|
}
|
|
|
|
k += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
VOID
|
|
FrsDsInitializeHardWired(
|
|
VOID
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Initialize the hardwired config stuff. Must happen before any
|
|
of the command servers start.
|
|
|
|
Arguments:
|
|
Jet - change the default jet directory from the registry
|
|
|
|
Return Value:
|
|
New jet directory
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsInitializeHardWired:"
|
|
|
|
//
|
|
// Using Ds, not the hard wired config
|
|
//
|
|
if (!NoDs) {
|
|
return;
|
|
}
|
|
|
|
|
|
//
|
|
// NULL entries for "machine name" fields are assigned
|
|
// this machine's name
|
|
//
|
|
if (IniFileName){
|
|
DPRINT1(0, ":DS: Reading hardwired config from ini file... %ws\n", IniFileName);
|
|
if (FrsDsLoadHardWiredFromFile(&LoadedWired, IniFileName)) {
|
|
DPRINT1(0, ":DS: Using hardwired config from ini file... %ws\n", IniFileName);
|
|
FrsDsInitializeHardWiredStructs(LoadedWired);
|
|
} else {
|
|
FrsFree(IniFileName);
|
|
IniFileName = NULL;
|
|
DPRINT(0, ":DS: Could not load topology from ini file\n");
|
|
DPRINT(0, ":DS: Using David's hardwired...\n");
|
|
FrsDsInitializeHardWiredStructs(DavidWired2);
|
|
FrsDsInitializeHardWiredStructs(DavidWired);
|
|
}
|
|
} else {
|
|
DPRINT(0, ":DS: Using David's hardwired...\n");
|
|
FrsDsInitializeHardWiredStructs(DavidWired2);
|
|
FrsDsInitializeHardWiredStructs(DavidWired);
|
|
}
|
|
|
|
//
|
|
// The ServerGuid is used as part of the rpc endpoint
|
|
//
|
|
if (ServerName) {
|
|
ServerGuid = FrsDsBuildGuidFromName(ServerName);
|
|
}
|
|
}
|
|
#endif DBG
|
|
|
|
|
|
#if DBG
|
|
VOID
|
|
FrsDsUseHardWired(
|
|
IN PHARDWIRED Wired
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Use the hardwired config instead of the DS config.
|
|
|
|
Arguments:
|
|
Wired - hand crafted config
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsUseHardWired:"
|
|
ULONG i, j;
|
|
ULONG WStatus;
|
|
PREPLICA Replica;
|
|
PCXTION Cxtion;
|
|
PSCHEDULE Schedule;
|
|
ULONG ScheduleLength;
|
|
PBYTE ScheduleData;
|
|
PHARDWIRED W;
|
|
DWORD FileAttributes = 0xFFFFFFFF;
|
|
|
|
DPRINT(1, ":DS: ------------ USING HARD WIRED CONFIG\n");
|
|
for (i = 0; Wired && Wired[i].Replica; ++i) {
|
|
if (i) {
|
|
DPRINT(1, ":DS: \n");
|
|
}
|
|
W = &Wired[i];
|
|
|
|
DPRINT1(1, ":DS: \tServer: %ws\n", W->Server);
|
|
DPRINT1(1, ":DS: \t Machine: %ws\n", W->Machine);
|
|
DPRINT1(1, ":DS: \t\tReplica : %ws\n", W->Replica);
|
|
|
|
DPRINT(1, ":DS: \n");
|
|
for (j=0; (j<HW_MACHINES) && W->InNames[j]; j++ ) {
|
|
DPRINT4(1, ":DS: \t\tInNames,machine,server [%d] : %ws, %ws, %ws\n", j,
|
|
(W->InNames[j]) ? W->InNames[j] : L"",
|
|
(W->InMachines[j]) ? W->InMachines[j] : L"",
|
|
(W->InServers[j]) ? W->InServers[j] : L"");
|
|
}
|
|
|
|
DPRINT(1, ":DS: \n");
|
|
for (j=0; (j<HW_MACHINES) && W->OutNames[j]; j++ ) {
|
|
DPRINT4(1, ":DS: \t\tOutNames,machine,server [%d] : %ws, %ws, %ws\n", j,
|
|
(W->OutNames[j]) ? W->OutNames[j] : L"",
|
|
(W->OutMachines[j]) ? W->OutMachines[j] : L"",
|
|
(W->OutServers[j]) ? W->OutServers[j] : L"");
|
|
}
|
|
|
|
DPRINT(1, ":DS: \n");
|
|
DPRINT1(1, ":DS: \t\tStage : %ws\n", W->Stage);
|
|
DPRINT1(1, ":DS: \t\tRoot : %ws\n", W->Root);
|
|
DPRINT1(1, ":DS: \t\tJetPath : %ws\n", W->JetPath);
|
|
}
|
|
|
|
//
|
|
// Coordinate with replica command server
|
|
//
|
|
RcsBeginMergeWithDs();
|
|
|
|
//
|
|
// Construct a replica for each hardwired configuration
|
|
//
|
|
for (i = 0; Wired && Wired[i].Replica; ++i) {
|
|
W = &Wired[i];
|
|
//
|
|
// This server does not match this machine's name; continue
|
|
//
|
|
if (ServerName) {
|
|
if (WSTR_NE(ServerName, W->Server)) {
|
|
continue;
|
|
}
|
|
} else if (WSTR_NE(ComputerName, W->Machine)) {
|
|
continue;
|
|
}
|
|
|
|
Replica = FrsAllocType(REPLICA_TYPE);
|
|
Replica->Consistent = TRUE;
|
|
|
|
//
|
|
// MATCH
|
|
//
|
|
|
|
//
|
|
// Construct a phoney schedule; always "on"
|
|
//
|
|
ScheduleLength = sizeof(SCHEDULE) +
|
|
(2 * sizeof(SCHEDULE_HEADER)) +
|
|
(3 * SCHEDULE_DATA_BYTES);
|
|
|
|
Schedule = FrsAlloc(ScheduleLength);
|
|
Schedule->NumberOfSchedules = 3;
|
|
Schedule->Schedules[0].Type = SCHEDULE_BANDWIDTH;
|
|
Schedule->Schedules[0].Offset = sizeof(SCHEDULE) +
|
|
(2 * sizeof(SCHEDULE_HEADER)) +
|
|
(0 * SCHEDULE_DATA_BYTES);
|
|
Schedule->Schedules[1].Type = SCHEDULE_PRIORITY;
|
|
Schedule->Schedules[1].Offset = sizeof(SCHEDULE) +
|
|
(2 * sizeof(SCHEDULE_HEADER)) +
|
|
(1 * SCHEDULE_DATA_BYTES);
|
|
Schedule->Schedules[2].Type = SCHEDULE_INTERVAL;
|
|
Schedule->Schedules[2].Offset = sizeof(SCHEDULE) +
|
|
(2 * sizeof(SCHEDULE_HEADER)) +
|
|
(2 * SCHEDULE_DATA_BYTES);
|
|
ScheduleData = ((PBYTE)Schedule);
|
|
FRS_ASSERT((ScheduleData +
|
|
Schedule->Schedules[2].Offset + SCHEDULE_DATA_BYTES)
|
|
==
|
|
(((PBYTE)Schedule) + ScheduleLength));
|
|
|
|
for (j = 0; j < (SCHEDULE_DATA_BYTES * 3); ++j) {
|
|
*(ScheduleData + Schedule->Schedules[0].Offset + j) = 0x0f;
|
|
}
|
|
|
|
Schedule->Size = ScheduleLength;
|
|
|
|
Replica->Schedule = Schedule;
|
|
|
|
//
|
|
// Construct the guid/names from the name
|
|
//
|
|
Replica->MemberName = FrsBuildGName(FrsDsBuildGuidFromName(W->Server),
|
|
FrsWcsDup(W->Server));
|
|
|
|
Replica->ReplicaName = FrsBuildGName(FrsDupGuid(Replica->MemberName->Guid),
|
|
FrsWcsDup(W->Replica));
|
|
|
|
Replica->SetName = FrsBuildGName(FrsDsBuildGuidFromName(W->Replica),
|
|
FrsWcsDup(W->Replica));
|
|
//
|
|
// Temporary; a new guid is assigned if this is a new set
|
|
//
|
|
Replica->ReplicaRootGuid = FrsDupGuid(Replica->SetName->Guid);
|
|
|
|
//
|
|
// Fill in the rest of the fields
|
|
//
|
|
Replica->Root = FrsWcsDup(W->Root);
|
|
Replica->Stage = FrsWcsDup(W->Stage);
|
|
FRS_WCSLWR(Replica->Root);
|
|
FRS_WCSLWR(Replica->Stage);
|
|
Replica->Volume = FrsWcsVolume(W->Root);
|
|
|
|
//
|
|
// Syntax of root path is invalid?
|
|
//
|
|
if (!FrsDsVerifyPath(Replica->Root)) {
|
|
DPRINT2(3, ":DS: Invalid root %ws for %ws\n",
|
|
Replica->Root, Replica->SetName->Name);
|
|
EPRINT1(EVENT_FRS_ROOT_NOT_VALID, Replica->Root);
|
|
Replica->Consistent = FALSE;
|
|
}
|
|
|
|
//
|
|
// Does the volume exist and is it NTFS?
|
|
//
|
|
WStatus = FrsVerifyVolume(Replica->Root,
|
|
Replica->SetName->Name,
|
|
FILE_PERSISTENT_ACLS | FILE_SUPPORTS_OBJECT_IDS);
|
|
if (!WIN_SUCCESS(WStatus)) {
|
|
DPRINT2_WS(3, ":DS: Root path Volume (%ws) for %ws does not exist or does not support ACLs and Object IDs;",
|
|
Replica->Root, Replica->SetName->Name, WStatus);
|
|
Replica->Consistent = FALSE;
|
|
}
|
|
|
|
//
|
|
// Root does not exist or is inaccessable; continue
|
|
//
|
|
WStatus = FrsDoesDirectoryExist(Replica->Root, &FileAttributes);
|
|
if (!WIN_SUCCESS(WStatus)) {
|
|
DPRINT2_WS(3, ":DS: Root path (%ws) for %ws does not exist;",
|
|
Replica->Root, Replica->SetName->Name, WStatus);
|
|
EPRINT1(EVENT_FRS_ROOT_NOT_VALID, Replica->Root);
|
|
Replica->Consistent = FALSE;
|
|
}
|
|
|
|
//
|
|
// Syntax of staging path is invalid; continue
|
|
//
|
|
if (!FrsDsVerifyPath(Replica->Stage)) {
|
|
DPRINT2(3, ":DS: Invalid stage %ws for %ws\n",
|
|
Replica->Stage, Replica->SetName->Name);
|
|
EPRINT2(EVENT_FRS_STAGE_NOT_VALID, Replica->Root, Replica->Stage);
|
|
Replica->Consistent = FALSE;
|
|
}
|
|
|
|
//
|
|
// Does the staging volume exist and does it support ACLs?
|
|
// ACLs are required to protect against data theft/corruption
|
|
// in the staging dir.
|
|
//
|
|
WStatus = FrsVerifyVolume(Replica->Stage,
|
|
Replica->SetName->Name,
|
|
FILE_PERSISTENT_ACLS);
|
|
if (!WIN_SUCCESS(WStatus)) {
|
|
DPRINT2_WS(3, ":DS: Stage path Volume (%ws) for %ws does not exist or does not support ACLs;",
|
|
Replica->Stage, Replica->SetName->Name, WStatus);
|
|
Replica->Consistent = FALSE;
|
|
}
|
|
|
|
//
|
|
// Stage does not exist or is inaccessable; continue
|
|
//
|
|
WStatus = FrsDoesDirectoryExist(Replica->Stage, &FileAttributes);
|
|
if (!WIN_SUCCESS(WStatus)) {
|
|
DPRINT2_WS(3, ":DS: Stage path (%ws) for %ws does not exist;",
|
|
Replica->Stage, Replica->SetName->Name, WStatus);
|
|
EPRINT2(EVENT_FRS_STAGE_NOT_VALID, Replica->Root, Replica->Stage);
|
|
Replica->Consistent = FALSE;
|
|
}
|
|
|
|
if (W->IsPrimary) {
|
|
SetFlag(Replica->CnfFlags, CONFIG_FLAG_PRIMARY);
|
|
}
|
|
|
|
//
|
|
// File Filter
|
|
//
|
|
Replica->FileFilterList = FRS_DS_COMPOSE_FILTER_LIST(
|
|
W->FileFilterList,
|
|
RegistryFileExclFilterList,
|
|
DEFAULT_FILE_FILTER_LIST);
|
|
Replica->FileInclFilterList = FrsWcsDup(RegistryFileInclFilterList);
|
|
|
|
//
|
|
// Directory Filter
|
|
//
|
|
Replica->DirFilterList = FRS_DS_COMPOSE_FILTER_LIST(
|
|
W->DirFilterList,
|
|
RegistryDirExclFilterList,
|
|
DEFAULT_DIR_FILTER_LIST);
|
|
Replica->DirInclFilterList = FrsWcsDup(RegistryDirInclFilterList);
|
|
|
|
//
|
|
// Build Inbound cxtions
|
|
//
|
|
Schedule = FrsAlloc(ScheduleLength);
|
|
CopyMemory(Schedule, Replica->Schedule, ScheduleLength);
|
|
Schedule->Schedules[0].Type = SCHEDULE_INTERVAL;
|
|
Schedule->Schedules[2].Type = SCHEDULE_BANDWIDTH;
|
|
|
|
for (j = 0; W->InNames[j]; ++j) {
|
|
Cxtion = FrsAllocType(CXTION_TYPE);
|
|
//
|
|
// Construct the guid/names from the name
|
|
//
|
|
Cxtion->Name = FrsBuildGName(FrsDsBuildGuidFromName(W->InNames[j]),
|
|
FrsWcsDup(W->InNames[j]));
|
|
|
|
Cxtion->Partner = FrsBuildGName(FrsDsBuildGuidFromName(W->InServers[j]),
|
|
FrsWcsDup(W->InMachines[j]));
|
|
|
|
Cxtion->PartnerDnsName = FrsWcsDup(W->InMachines[j]);
|
|
Cxtion->PartnerSid = FrsWcsDup(W->InMachines[j]);
|
|
Cxtion->PartSrvName = FrsWcsDup(W->InServers[j]);
|
|
DPRINT1(1, ":DS: Hardwired cxtion "FORMAT_CXTION_PATH2"\n",
|
|
PRINT_CXTION_PATH2(Replica, Cxtion));
|
|
|
|
Cxtion->PartnerPrincName = FrsWcsDup(Cxtion->PartSrvName);
|
|
//
|
|
// Fill in the rest of the fields
|
|
//
|
|
Cxtion->Inbound = TRUE;
|
|
SetCxtionFlag(Cxtion, CXTION_FLAGS_CONSISTENT);
|
|
Cxtion->Schedule = Schedule;
|
|
Schedule = NULL;
|
|
SetCxtionState(Cxtion, CxtionStateUnjoined);
|
|
GTabInsertEntry(Replica->Cxtions, Cxtion, Cxtion->Name->Guid, NULL);
|
|
}
|
|
|
|
//
|
|
// Build Outbound cxtions
|
|
//
|
|
Schedule = FrsAlloc(ScheduleLength);
|
|
CopyMemory(Schedule, Replica->Schedule, ScheduleLength);
|
|
Schedule->Schedules[0].Type = SCHEDULE_INTERVAL;
|
|
Schedule->Schedules[2].Type = SCHEDULE_BANDWIDTH;
|
|
|
|
for (j = 0; W->OutNames[j]; ++j) {
|
|
Cxtion = FrsAllocType(CXTION_TYPE);
|
|
//
|
|
// Construct the guid/names from the name
|
|
//
|
|
Cxtion->Name = FrsBuildGName(FrsDsBuildGuidFromName(W->OutNames[j]),
|
|
FrsWcsDup(W->OutNames[j]));
|
|
|
|
Cxtion->Partner = FrsBuildGName(FrsDsBuildGuidFromName(W->OutServers[j]),
|
|
FrsWcsDup(W->OutMachines[j]));
|
|
|
|
Cxtion->PartnerDnsName = FrsWcsDup(W->OutMachines[j]);
|
|
Cxtion->PartnerSid = FrsWcsDup(W->OutMachines[j]);
|
|
Cxtion->PartSrvName = FrsWcsDup(W->OutServers[j]);
|
|
DPRINT1(1, ":DS: Hardwired cxtion "FORMAT_CXTION_PATH2"\n",
|
|
PRINT_CXTION_PATH2(Replica, Cxtion));
|
|
|
|
Cxtion->PartnerPrincName = FrsWcsDup(Cxtion->PartSrvName);
|
|
|
|
//
|
|
// Fill in the rest of the fields
|
|
//
|
|
Cxtion->Inbound = FALSE;
|
|
SetCxtionFlag(Cxtion, CXTION_FLAGS_CONSISTENT);
|
|
Cxtion->Schedule = Schedule;
|
|
Schedule = NULL;
|
|
SetCxtionState(Cxtion, CxtionStateUnjoined);
|
|
GTabInsertEntry(Replica->Cxtions, Cxtion, Cxtion->Name->Guid, NULL);
|
|
}
|
|
if (Schedule) {
|
|
FrsFree(Schedule);
|
|
}
|
|
//
|
|
// Merge the replica with the active replicas
|
|
//
|
|
RcsMergeReplicaFromDs(Replica);
|
|
}
|
|
RcsEndMergeWithDs();
|
|
}
|
|
#endif DBG
|
|
|
|
|
|
DWORD
|
|
FrsDsGetSubscribers(
|
|
IN PLDAP Ldap,
|
|
IN PWCHAR SubscriptionDn,
|
|
IN PCONFIG_NODE Parent
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Recursively scan the DS tree beginning at computer
|
|
|
|
Part of NewDs poll APIs.
|
|
|
|
Arguments:
|
|
Ldap - opened and bound ldap connection
|
|
SubscriptionDn - distininguished name of subscriptions object
|
|
Parent - parent node
|
|
|
|
Return Value:
|
|
WIN32 Status
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsGetSubscribers:"
|
|
PWCHAR Attrs[8];
|
|
PLDAPMessage LdapEntry;
|
|
PCONFIG_NODE Node;
|
|
DWORD WStatus = ERROR_SUCCESS;
|
|
DWORD Status = ERROR_SUCCESS;
|
|
PGEN_ENTRY ConflictingNodeEntry = NULL;
|
|
PCONFIG_NODE ConflictingNode = NULL;
|
|
PCONFIG_NODE Winner = NULL;
|
|
PCONFIG_NODE Loser = NULL;
|
|
FRS_LDAP_SEARCH_CONTEXT FrsSearchContext;
|
|
HANDLE StageHandle = INVALID_HANDLE_VALUE;
|
|
DWORD FileAttributes = 0xFFFFFFFF;
|
|
DWORD CreateStatus = ERROR_SUCCESS;
|
|
|
|
//
|
|
// Search the DS beginning at Base for entries of (objectCategory=nTFRSSubscriber)
|
|
//
|
|
MK_ATTRS_7(Attrs, ATTR_OBJECT_GUID, ATTR_DN, ATTR_SCHEDULE, ATTR_USN_CHANGED,
|
|
ATTR_REPLICA_ROOT, ATTR_REPLICA_STAGE, ATTR_MEMBER_REF);
|
|
|
|
if (!FrsDsLdapSearchInit(Ldap, SubscriptionDn, LDAP_SCOPE_ONELEVEL, CATEGORY_SUBSCRIBER,
|
|
Attrs, 0, &FrsSearchContext)) {
|
|
return ERROR_ACCESS_DENIED;
|
|
}
|
|
if (FrsSearchContext.EntriesInPage == 0) {
|
|
DPRINT1(0, ":DS: No NTFRSSubscriber object found under %ws!\n", SubscriptionDn);
|
|
}
|
|
|
|
//
|
|
// Scan the entries returned from ldap_search
|
|
//
|
|
for (LdapEntry = FrsDsLdapSearchNext(Ldap, &FrsSearchContext);
|
|
LdapEntry != NULL && WIN_SUCCESS(WStatus);
|
|
LdapEntry = FrsDsLdapSearchNext(Ldap, &FrsSearchContext)) {
|
|
|
|
//
|
|
// Basic node info (guid, name, dn, schedule, and usnchanged)
|
|
//
|
|
Node = FrsDsAllocBasicNode(Ldap, LdapEntry, CONFIG_TYPE_SUBSCRIBER);
|
|
if (!Node) {
|
|
DPRINT(0, ":DS: Subscriber lacks basic info; skipping\n");
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Member reference
|
|
//
|
|
Node->MemberDn = FrsDsFindValue(Ldap, LdapEntry, ATTR_MEMBER_REF);
|
|
if (Node->MemberDn == NULL) {
|
|
DPRINT1(0, ":DS: ERROR - No Member Reference found on subscriber (%ws). Skipping\n", Node->Dn);
|
|
|
|
//
|
|
// Add to the poll summary event log.
|
|
//
|
|
FrsDsAddToPollSummary3ws(IDS_POLL_SUM_INVALID_ATTRIBUTE, ATTR_SUBSCRIBER,
|
|
Node->Dn, ATTR_MEMBER_REF);
|
|
|
|
FrsFreeType(Node);
|
|
continue;
|
|
}
|
|
|
|
FRS_WCSLWR(Node->MemberDn);
|
|
|
|
//
|
|
// Root pathname
|
|
//
|
|
Node->Root = FrsDsFindValue(Ldap, LdapEntry, ATTR_REPLICA_ROOT);
|
|
if (Node->Root == NULL) {
|
|
DPRINT1(0, ":DS: ERROR - No Root path found on subscriber (%ws). Skipping\n", Node->Dn);
|
|
FrsFreeType(Node);
|
|
continue;
|
|
}
|
|
|
|
FRS_WCSLWR(Node->Root);
|
|
|
|
|
|
//
|
|
// Staging pathname. No need to traverse reparse points on the staging dir.
|
|
//
|
|
Node->Stage = FrsDsFindValue(Ldap, LdapEntry, ATTR_REPLICA_STAGE);
|
|
if (Node->Stage == NULL) {
|
|
DPRINT1(0, ":DS: ERROR - No Staging path found on subscriber (%ws). Skipping\n", Node->Dn);
|
|
FrsFreeType(Node);
|
|
continue;
|
|
}
|
|
|
|
FRS_WCSLWR(Node->Stage);
|
|
|
|
//
|
|
// Create the staging directory if it does not exist.
|
|
//
|
|
Status = FrsDoesDirectoryExist(Node->Stage, &FileAttributes);
|
|
if (!WIN_SUCCESS(Status)) {
|
|
CreateStatus = FrsCreateDirectory(Node->Stage);
|
|
DPRINT1_WS(0, ":DS: ERROR - Can't create staging dir %ws;", Node->Stage, CreateStatus);
|
|
}
|
|
|
|
//
|
|
// If the staging dir was just created successfully or if it does not have the
|
|
// hidden attribute set then set the security on it.
|
|
//
|
|
if ((!WIN_SUCCESS(Status) && WIN_SUCCESS(CreateStatus)) ||
|
|
(WIN_SUCCESS(Status) && !BooleanFlagOn(FileAttributes, FILE_ATTRIBUTE_HIDDEN))) {
|
|
//
|
|
// Open the staging directory.
|
|
//
|
|
StageHandle = CreateFile(Node->Stage,
|
|
GENERIC_WRITE | WRITE_DAC | FILE_READ_ATTRIBUTES | FILE_TRAVERSE,
|
|
FILE_SHARE_READ,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
FILE_FLAG_BACKUP_SEMANTICS,
|
|
NULL);
|
|
|
|
if (!HANDLE_IS_VALID(StageHandle)) {
|
|
Status = GetLastError();
|
|
DPRINT1_WS(0, ":DS: WARN - CreateFile(%ws);", Node->Stage, Status);
|
|
} else {
|
|
Status = FrsRestrictAccessToFileOrDirectory(Node->Stage, StageHandle,
|
|
FALSE, // do not inherit acls from parent.
|
|
FALSE);// do not push acls to children.
|
|
DPRINT1_WS(0, ":DS: WARN - FrsRestrictAccessToFileOrDirectory(%ws) (IGNORED)", Node->Stage, Status);
|
|
FRS_CLOSE(StageHandle);
|
|
|
|
//
|
|
// Mark the staging dir hidden.
|
|
//
|
|
if (!SetFileAttributes(Node->Stage,
|
|
FILE_ATTRIBUTE_NORMAL | FILE_ATTRIBUTE_HIDDEN)) {
|
|
Status = GetLastError();
|
|
DPRINT1_WS(0, ":DS: ERROR - Can't set attrs on staging dir %ws;", Node->Stage, Status);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Add the subscriber to the subscriber table.
|
|
//
|
|
// ConflictingNodeEntry = GTabInsertUniqueEntry(SubscriberTable, Node, Node->MemberDn, Node->Root);
|
|
ConflictingNodeEntry = GTabInsertUniqueEntry(SubscriberTable, Node, Node->MemberDn, NULL);
|
|
|
|
if (ConflictingNodeEntry) {
|
|
ConflictingNode = ConflictingNodeEntry->Data;
|
|
FrsDsResolveSubscriberConflict(ConflictingNode, Node, &Winner, &Loser);
|
|
if (WSTR_EQ(Winner->Dn, Node->Dn)) {
|
|
//
|
|
// The new one is the winner. Remove old one and insert new one.
|
|
//
|
|
GTabDelete(SubscriberTable,ConflictingNodeEntry->Key1,ConflictingNodeEntry->Key2,NULL);
|
|
GTabInsertUniqueEntry(SubscriberTable, Node, Node->MemberDn, Node->Root);
|
|
FrsFreeType(ConflictingNode);
|
|
} else {
|
|
//
|
|
// The old one is the winner. Leave it in the table.
|
|
//
|
|
FrsFreeType(Node);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Link into config and add to the running checksum
|
|
//
|
|
FrsDsTreeLink(Parent, Node);
|
|
FRS_PRINT_TYPE_DEBSUB(5, ":DS: NodeSubscriber", Node);
|
|
}
|
|
FrsDsLdapSearchClose(&FrsSearchContext);
|
|
|
|
return WStatus;
|
|
}
|
|
|
|
|
|
DWORD
|
|
FrsDsGetSubscriptions(
|
|
IN PLDAP Ldap,
|
|
IN PWCHAR ComputerDn,
|
|
IN PCONFIG_NODE Parent
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Recursively scan the DS tree beginning at computer
|
|
|
|
Part of NewDs poll APIs.
|
|
|
|
Arguments:
|
|
Ldap - opened and bound ldap connection
|
|
DefaultNc - default naming context
|
|
Parent - parent node
|
|
|
|
Return Value:
|
|
WIN32 Status
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsGetSubscriptions:"
|
|
PWCHAR Attrs[6];
|
|
PLDAPMessage LdapMsg = NULL;
|
|
PLDAPMessage LdapEntry;
|
|
PCONFIG_NODE Node;
|
|
FRS_LDAP_SEARCH_CONTEXT FrsSearchContext;
|
|
DWORD WStatus = ERROR_SUCCESS;
|
|
|
|
//
|
|
// Search the DS beginning at Base for entries of (objectCategory=nTFRSSubscriptions)
|
|
//
|
|
MK_ATTRS_5(Attrs, ATTR_OBJECT_GUID, ATTR_DN, ATTR_SCHEDULE, ATTR_USN_CHANGED, ATTR_WORKING);
|
|
|
|
if (!FrsDsLdapSearchInit(Ldap, ComputerDn, LDAP_SCOPE_SUBTREE, CATEGORY_SUBSCRIPTIONS,
|
|
Attrs, 0, &FrsSearchContext)) {
|
|
return ERROR_ACCESS_DENIED;
|
|
}
|
|
if (FrsSearchContext.EntriesInPage == 0) {
|
|
DPRINT1(0, ":DS: No NTFRSSubscriptions object found under %ws!.\n", ComputerDn);
|
|
}
|
|
|
|
//
|
|
// Scan the entries returned from ldap_search
|
|
//
|
|
for (LdapEntry = FrsDsLdapSearchNext(Ldap, &FrsSearchContext);
|
|
LdapEntry != NULL && WIN_SUCCESS(WStatus);
|
|
LdapEntry = FrsDsLdapSearchNext(Ldap, &FrsSearchContext)) {
|
|
|
|
//
|
|
// Basic node info (guid, name, dn, schedule, and usnchanged)
|
|
//
|
|
Node = FrsDsAllocBasicNode(Ldap, LdapEntry, CONFIG_TYPE_SUBSCRIPTIONS);
|
|
if (!Node) {
|
|
DPRINT(4, ":DS: Subscriptions lacks basic info; skipping\n");
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Working Directory
|
|
//
|
|
Node->Working = FrsDsFindValue(Ldap, LdapEntry, ATTR_WORKING);
|
|
|
|
//
|
|
// Link into config and add to the running checksum
|
|
//
|
|
FrsDsTreeLink(Parent, Node);
|
|
FRS_PRINT_TYPE_DEBSUB(5, ":DS: NodeSubscription", Node);
|
|
|
|
//
|
|
// Recurse to the next level in the DS hierarchy
|
|
//
|
|
WStatus = FrsDsGetSubscribers(Ldap, Node->Dn, Node);
|
|
}
|
|
FrsDsLdapSearchClose(&FrsSearchContext);
|
|
|
|
return WStatus;
|
|
}
|
|
|
|
|
|
VOID
|
|
FrsDsAddLdapMod(
|
|
IN PWCHAR AttrType,
|
|
IN PWCHAR AttrValue,
|
|
IN OUT LDAPMod ***pppMod
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Add an attribute (plus values) to a structure that will eventually be
|
|
used in an ldap_add_s() function to add an object to the DS. The null-
|
|
terminated array referenced by pppMod grows with each call to this
|
|
routine. The array is freed by the caller using FrsDsFreeLdapMod().
|
|
|
|
Arguments:
|
|
AttrType - The object class of the object.
|
|
AttrValue - The value of the attribute.
|
|
pppMod - Address of an array of pointers to "attributes". Don't
|
|
give me that look -- this is an LDAP thing.
|
|
|
|
Return Value:
|
|
The pppMod array grows by one entry. The caller must free it with
|
|
FrsDsFreeLdapMod().
|
|
--*/
|
|
{
|
|
DWORD NumMod; // Number of entries in the Mod array
|
|
LDAPMod **ppMod; // Address of the first entry in the Mod array
|
|
LDAPMod *Attr; // An attribute structure
|
|
PWCHAR *Values; // An array of pointers to bervals
|
|
|
|
if (AttrValue == NULL)
|
|
return;
|
|
|
|
//
|
|
// The null-terminated array doesn't exist; create it
|
|
//
|
|
if (*pppMod == NULL) {
|
|
*pppMod = (LDAPMod **)FrsAlloc(sizeof (*pppMod));
|
|
**pppMod = NULL;
|
|
}
|
|
|
|
//
|
|
// Increase the array's size by 1
|
|
//
|
|
for (ppMod = *pppMod, NumMod = 2; *ppMod != NULL; ++ppMod, ++NumMod);
|
|
*pppMod = (LDAPMod **)FrsRealloc(*pppMod, sizeof (*pppMod) * NumMod);
|
|
|
|
//
|
|
// Add the new attribute + value to the Mod array
|
|
//
|
|
Values = (PWCHAR *)FrsAlloc(sizeof (PWCHAR) * 2);
|
|
Values[0] = FrsWcsDup(AttrValue);
|
|
Values[1] = NULL;
|
|
|
|
Attr = (LDAPMod *)FrsAlloc(sizeof (*Attr));
|
|
Attr->mod_values = Values;
|
|
Attr->mod_type = FrsWcsDup(AttrType);
|
|
Attr->mod_op = LDAP_MOD_ADD;
|
|
|
|
(*pppMod)[NumMod - 1] = NULL;
|
|
(*pppMod)[NumMod - 2] = Attr;
|
|
}
|
|
|
|
|
|
VOID
|
|
FrsDsAddLdapBerMod(
|
|
IN PWCHAR AttrType,
|
|
IN PCHAR AttrValue,
|
|
IN DWORD AttrValueLen,
|
|
IN OUT LDAPMod ***pppMod
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Add an attribute (plus values) to a structure that will eventually be
|
|
used in an ldap_add() function to add an object to the DS. The null-
|
|
terminated array referenced by pppMod grows with each call to this
|
|
routine. The array is freed by the caller using FrsDsFreeLdapMod().
|
|
|
|
Arguments:
|
|
AttrType - The object class of the object.
|
|
AttrValue - The value of the attribute.
|
|
AttrValueLen - length of the attribute
|
|
pppMod - Address of an array of pointers to "attributes". Don't
|
|
give me that look -- this is an LDAP thing.
|
|
|
|
Return Value:
|
|
The pppMod array grows by one entry. The caller must free it with
|
|
FrsDsFreeLdapMod().
|
|
--*/
|
|
{
|
|
DWORD NumMod; // Number of entries in the Mod array
|
|
LDAPMod **ppMod; // Address of the first entry in the Mod array
|
|
LDAPMod *Attr; // An attribute structure
|
|
PLDAP_BERVAL Berval;
|
|
PLDAP_BERVAL *Values; // An array of pointers to bervals
|
|
|
|
if (AttrValue == NULL)
|
|
return;
|
|
|
|
//
|
|
// The null-terminated array doesn't exist; create it
|
|
//
|
|
if (*pppMod == NULL) {
|
|
*pppMod = (LDAPMod **)FrsAlloc(sizeof (*pppMod));
|
|
**pppMod = NULL;
|
|
}
|
|
|
|
//
|
|
// Increase the array's size by 1
|
|
//
|
|
for (ppMod = *pppMod, NumMod = 2; *ppMod != NULL; ++ppMod, ++NumMod);
|
|
*pppMod = (LDAPMod **)FrsRealloc(*pppMod, sizeof (*pppMod) * NumMod);
|
|
|
|
//
|
|
// Construct a berval
|
|
//
|
|
Berval = (PLDAP_BERVAL)FrsAlloc(sizeof(LDAP_BERVAL));
|
|
Berval->bv_len = AttrValueLen;
|
|
Berval->bv_val = (PCHAR)FrsAlloc(AttrValueLen);
|
|
CopyMemory(Berval->bv_val, AttrValue, AttrValueLen);
|
|
|
|
//
|
|
// Add the new attribute + value to the Mod array
|
|
//
|
|
Values = (PLDAP_BERVAL *)FrsAlloc(sizeof (PLDAP_BERVAL) * 2);
|
|
Values[0] = Berval;
|
|
Values[1] = NULL;
|
|
|
|
Attr = (LDAPMod *)FrsAlloc(sizeof (*Attr));
|
|
Attr->mod_bvalues = Values;
|
|
Attr->mod_type = FrsWcsDup(AttrType);
|
|
Attr->mod_op = LDAP_MOD_BVALUES | LDAP_MOD_REPLACE;
|
|
|
|
(*pppMod)[NumMod - 1] = NULL;
|
|
(*pppMod)[NumMod - 2] = Attr;
|
|
}
|
|
|
|
|
|
VOID
|
|
FrsDsFreeLdapMod(
|
|
IN OUT LDAPMod ***pppMod
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Free the structure built by successive calls to FrsDsAddLdapMod().
|
|
|
|
Arguments:
|
|
pppMod - Address of a null-terminated array.
|
|
|
|
Return Value:
|
|
*pppMod set to NULL.
|
|
--*/
|
|
{
|
|
DWORD i, j;
|
|
LDAPMod **ppMod;
|
|
|
|
if (!pppMod || !*pppMod) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// For each attibute
|
|
//
|
|
ppMod = *pppMod;
|
|
for (i = 0; ppMod[i] != NULL; ++i) {
|
|
//
|
|
// For each value of the attribute
|
|
//
|
|
for (j = 0; (ppMod[i])->mod_values[j] != NULL; ++j) {
|
|
//
|
|
// Free the value
|
|
//
|
|
if (ppMod[i]->mod_op & LDAP_MOD_BVALUES) {
|
|
FrsFree(ppMod[i]->mod_bvalues[j]->bv_val);
|
|
}
|
|
FrsFree((ppMod[i])->mod_values[j]);
|
|
}
|
|
FrsFree((ppMod[i])->mod_values); // Free the array of pointers to values
|
|
FrsFree((ppMod[i])->mod_type); // Free the string identifying the attribute
|
|
FrsFree(ppMod[i]); // Free the attribute
|
|
}
|
|
FrsFree(ppMod); // Free the array of pointers to attributes
|
|
*pppMod = NULL; // Now ready for more calls to FrsDsAddLdapMod()
|
|
}
|
|
|
|
|
|
PWCHAR
|
|
FrsDsConvertToSettingsDn(
|
|
IN PWCHAR Dn
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Insure this Dn is for the server's settings and not the server and
|
|
that the Dn is in lower case for any call to wcsstr().
|
|
|
|
Arguments:
|
|
Dn - Server or settings dn
|
|
|
|
Return Value:
|
|
Settings Dn
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsConvertToSettingsDn:"
|
|
PWCHAR SettingsDn;
|
|
|
|
DPRINT1(4, ":DS: Begin settings Dn: %ws\n", Dn);
|
|
|
|
//
|
|
// No settings; done
|
|
//
|
|
if (!Dn) {
|
|
return Dn;
|
|
}
|
|
|
|
//
|
|
// Lower case for wcsstr
|
|
//
|
|
FRS_WCSLWR(Dn);
|
|
if (wcsstr(Dn, CN_NTDS_SETTINGS)) {
|
|
DPRINT1(4, ":DS: End settings Dn: %ws\n", Dn);
|
|
return Dn;
|
|
}
|
|
SettingsDn = FrsDsExtendDn(Dn, CN_NTDS_SETTINGS);
|
|
FRS_WCSLWR(SettingsDn);
|
|
FrsFree(Dn);
|
|
DPRINT1(4, ":DS: End settings Dn: %ws\n", SettingsDn);
|
|
return SettingsDn;
|
|
}
|
|
|
|
|
|
DWORD
|
|
FrsDsFindComputer(
|
|
IN PLDAP Ldap,
|
|
IN PWCHAR FindDn,
|
|
IN PWCHAR ObjectCategory,
|
|
IN ULONG Scope,
|
|
OUT PCONFIG_NODE *Computer
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
|
|
Find *one* computer object for this computer.
|
|
Then look for a subscriptons object and subscriber objects. A
|
|
DS configuration node is allocated for each object found. They are linked
|
|
together and the root of the "computer tree" is returned in Computer.
|
|
|
|
Part of NewDs poll APIs.
|
|
|
|
Arguments:
|
|
Ldap - opened and bound ldap connection
|
|
FindDn - Base Dn for search
|
|
ObjectCategory - Object class (computer or user)
|
|
A user object serves the same purpose as the computer
|
|
object *sometimes* following a NT4 to NT5 upgrade.
|
|
Scope - Scope of search (currently BASE or SUBTREE)
|
|
Computer - returned computer subtree
|
|
|
|
Return Value:
|
|
WIN32 Status
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsFindComputer:"
|
|
PLDAPMessage LdapEntry;
|
|
PCONFIG_NODE Node;
|
|
PWCHAR Attrs[8];
|
|
WCHAR Filter[MAX_PATH + 1];
|
|
FRS_LDAP_SEARCH_CONTEXT FrsSearchContext;
|
|
DWORD WStatus = ERROR_SUCCESS;
|
|
|
|
|
|
*Computer = NULL;
|
|
|
|
//
|
|
// Initialize the SubscriberTable.
|
|
//
|
|
if (SubscriberTable != NULL) {
|
|
SubscriberTable = GTabFreeTable(SubscriberTable,NULL);
|
|
}
|
|
|
|
SubscriberTable = GTabAllocStringTable();
|
|
|
|
//
|
|
// Filter that locates our computer object
|
|
//
|
|
swprintf(Filter, L"(&%s(sAMAccountName=%s$))", ObjectCategory, ComputerName);
|
|
|
|
//
|
|
// Search the DS beginning at Base for the entries of class "Filter"
|
|
//
|
|
MK_ATTRS_7(Attrs, ATTR_OBJECT_GUID, ATTR_DN, ATTR_SCHEDULE, ATTR_USN_CHANGED,
|
|
ATTR_SERVER_REF, ATTR_SERVER_REF_BL, ATTR_DNS_HOST_NAME);
|
|
//
|
|
// Note: Is it safe to turn off referrals re: back links?
|
|
// if so, use ldap_get/set_option in winldap.h
|
|
//
|
|
|
|
if (!FrsDsLdapSearchInit(Ldap, FindDn, Scope, Filter, Attrs, 0, &FrsSearchContext)) {
|
|
return ERROR_ACCESS_DENIED;
|
|
}
|
|
if (FrsSearchContext.EntriesInPage == 0) {
|
|
DPRINT1(0, ":DS: WARN - There is no computer object in %ws!\n", FindDn);
|
|
}
|
|
|
|
//
|
|
// Scan the entries returned from ldap_search
|
|
//
|
|
for (LdapEntry = FrsDsLdapSearchNext(Ldap, &FrsSearchContext);
|
|
LdapEntry != NULL && WIN_SUCCESS(WStatus);
|
|
LdapEntry = FrsDsLdapSearchNext(Ldap, &FrsSearchContext)) {
|
|
|
|
//
|
|
// Basic node info (guid, name, dn, schedule, and usnchanged)
|
|
//
|
|
Node = FrsDsAllocBasicNode(Ldap, LdapEntry, CONFIG_TYPE_COMPUTER);
|
|
if (!Node) {
|
|
DPRINT(0, ":DS: Computer lacks basic info; skipping\n");
|
|
continue;
|
|
}
|
|
DPRINT1(2, ":DS: Computer FQDN is %ws\n", Node->Dn);
|
|
|
|
//
|
|
// DNS name
|
|
//
|
|
Node->DnsName = FrsDsFindValue(Ldap, LdapEntry, ATTR_DNS_HOST_NAME);
|
|
DPRINT1(2, ":DS: Computer's dns name is %ws\n", Node->DnsName);
|
|
|
|
//
|
|
// Server reference
|
|
//
|
|
Node->SettingsDn = FrsDsFindValue(Ldap, LdapEntry, ATTR_SERVER_REF_BL);
|
|
if (!Node->SettingsDn) {
|
|
Node->SettingsDn = FrsDsFindValue(Ldap, LdapEntry, ATTR_SERVER_REF);
|
|
}
|
|
//
|
|
// Make sure it references the settings; not the server
|
|
//
|
|
Node->SettingsDn = FrsDsConvertToSettingsDn(Node->SettingsDn);
|
|
|
|
DPRINT1(2, ":DS: Settings reference is %ws\n", Node->SettingsDn);
|
|
|
|
//
|
|
// Link into config
|
|
//
|
|
Node->Peer = *Computer;
|
|
*Computer = Node;
|
|
FRS_PRINT_TYPE_DEBSUB(5, ":DS: NodeComputer", Node);
|
|
|
|
//
|
|
// Recurse to the next level in the DS hierarchy iff this
|
|
// computer is a member of some replica set
|
|
//
|
|
WStatus = FrsDsGetSubscriptions(Ldap, Node->Dn, Node);
|
|
}
|
|
FrsDsLdapSearchClose(&FrsSearchContext);
|
|
|
|
//
|
|
// There should only be one computer object with the indicated
|
|
// SAM account name. Otherwise, we are unable to authenticate
|
|
// properly. And it goes against the DS architecture.
|
|
//
|
|
if (WIN_SUCCESS(WStatus) && *Computer && (*Computer)->Peer) {
|
|
DPRINT(0, ":DS: ERROR - There is more than one computer object!\n");
|
|
WStatus = ERROR_INVALID_PARAMETER;
|
|
}
|
|
//
|
|
// Must have a computer
|
|
//
|
|
if (WIN_SUCCESS(WStatus) && !*Computer) {
|
|
DPRINT1(0, ":DS: WARN - There is no computer object in %ws!\n", FindDn);
|
|
WStatus = ERROR_INVALID_PARAMETER;
|
|
}
|
|
|
|
return WStatus;
|
|
}
|
|
|
|
|
|
DWORD
|
|
FrsDsGetComputer(
|
|
IN PLDAP Ldap,
|
|
OUT PCONFIG_NODE *Computer
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
|
|
|
|
Look in the Domain naming Context for our computer object.
|
|
Historically we did a deep search for an object with the sam account
|
|
name of our computer (SAM Account name is the netbios name with a $ appended).
|
|
That was expensive so before doing that we first look in the Domain
|
|
Controller container followed by a search of the Computer Container.
|
|
Then the DS guys came up with an API for the preferred way of doing this.
|
|
First call GetComputerObjectName() to get the Fully Qualified Distinguished
|
|
Name (FQDN) for the computer then use that in an LDAP search query (via
|
|
FrsDsFindComputer()). We only fall back on the full search when the
|
|
call to GetComputerObjectName() fails.
|
|
|
|
Part of NewDs poll APIs.
|
|
|
|
Arguments:
|
|
Ldap - opened and bound ldap connection
|
|
Computer - returned computer subtree
|
|
|
|
Return Value:
|
|
WIN32 Status
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsGetComputer:"
|
|
|
|
WCHAR CompFqdn[MAX_PATH + 1];
|
|
DWORD CompFqdnLen;
|
|
DWORD WStatus = ERROR_SUCCESS;
|
|
|
|
|
|
//
|
|
// Initialize return value
|
|
//
|
|
*Computer = NULL;
|
|
|
|
//
|
|
// Assume success
|
|
//
|
|
WStatus = ERROR_SUCCESS;
|
|
|
|
//
|
|
// Use computer's cached fully qualified Dn. This avoids repeated calls
|
|
// to GetComputerObjectName() which wants to rebind to the DS on each call.
|
|
// (it should have taken a binding handle as an arg).
|
|
//
|
|
if (ComputerCachedFqdn) {
|
|
DPRINT1(5, ":DS: ComputerCachedFqdn is %ws\n", ComputerCachedFqdn);
|
|
|
|
WStatus = FrsDsFindComputer(Ldap, ComputerCachedFqdn, CATEGORY_ANY,
|
|
LDAP_SCOPE_BASE, Computer);
|
|
if (*Computer) {
|
|
goto CLEANUP;
|
|
}
|
|
DPRINT2(1, ":DS: WARN - Could not find computer in Cachedfqdn %ws; WStatus %s\n",
|
|
ComputerCachedFqdn, ErrLabelW32(WStatus));
|
|
ComputerCachedFqdn = FrsFree(ComputerCachedFqdn);
|
|
}
|
|
|
|
//
|
|
// Retrieve the computer's fully qualified Dn
|
|
//
|
|
// NTRAID#70731-2000/03/29-sudarc (Call GetComputerObjectName() from a
|
|
// separate thread so that it does not hang the
|
|
// DS polling thread.)
|
|
//
|
|
// *Note*:
|
|
// The following call to GetComputerObjectName() can hang if the DS
|
|
// hangs. See bug 351139 for an example caused by a bug in another
|
|
// component. One way to protect ourself is to issue this call
|
|
// in its own thread. Then after a timeout period call RpcCancelThread()
|
|
// on the thread.
|
|
//
|
|
|
|
CompFqdnLen = MAX_PATH;
|
|
if (GetComputerObjectName(NameFullyQualifiedDN, CompFqdn, &CompFqdnLen)) {
|
|
|
|
DPRINT1(4, ":DS: ComputerFqdn is %ws\n", CompFqdn);
|
|
//
|
|
// Use CATEGORY_ANY in the search below because an NT4 to NT5 upgrade
|
|
// could result in the object type for the "computer object" to really
|
|
// be a USER object. So the FQDN above could resolve to a Computer
|
|
// or a User object.
|
|
//
|
|
WStatus = FrsDsFindComputer(Ldap, CompFqdn, CATEGORY_ANY,
|
|
LDAP_SCOPE_BASE, Computer);
|
|
if (*Computer == NULL) {
|
|
DPRINT2(1, ":DS: WARN - Could not find computer in fqdn %ws; WStatus %s\n",
|
|
CompFqdn, ErrLabelW32(WStatus));
|
|
} else {
|
|
//
|
|
// Found our computer object; refresh the cached fqdn.
|
|
//
|
|
FrsFree(ComputerCachedFqdn);
|
|
ComputerCachedFqdn = FrsWcsDup(CompFqdn);
|
|
}
|
|
|
|
//
|
|
// We got the fully qualified Dn so we are done. It should have
|
|
// given us a computer object but even if it didn't we won't find it
|
|
// anywhere else.
|
|
//
|
|
goto CLEANUP;
|
|
}
|
|
|
|
DPRINT3(1, ":DS: WARN - GetComputerObjectName(%ws); Len %d, WStatus %s\n",
|
|
ComputerName, CompFqdnLen, ErrLabelW32(GetLastError()));
|
|
|
|
//
|
|
// FQDN lookup failed so fall back on search of well known containers.
|
|
// First Look in domain controllers container.
|
|
//
|
|
if (DomainControllersDn) {
|
|
WStatus = FrsDsFindComputer(Ldap, DomainControllersDn, CATEGORY_COMPUTER,
|
|
LDAP_SCOPE_SUBTREE, Computer);
|
|
if (*Computer != NULL) {
|
|
goto CLEANUP;
|
|
}
|
|
DPRINT2(1, ":DS: WARN - Could not find computer in dc's %ws; WStatus %s\n",
|
|
DomainControllersDn, ErrLabelW32(WStatus));
|
|
}
|
|
|
|
//
|
|
// Look in computer container
|
|
//
|
|
if (ComputersDn) {
|
|
WStatus = FrsDsFindComputer(Ldap, ComputersDn, CATEGORY_COMPUTER,
|
|
LDAP_SCOPE_SUBTREE, Computer);
|
|
if (*Computer != NULL) {
|
|
goto CLEANUP;
|
|
}
|
|
DPRINT2(1, ":DS: WARN - Could not find computer in computers %ws; WStatus %s\n",
|
|
ComputersDn, ErrLabelW32(WStatus));
|
|
}
|
|
|
|
//
|
|
// Do a deep search of the default naming context (EXPENSIVE!)
|
|
//
|
|
if (DefaultNcDn) {
|
|
WStatus = FrsDsFindComputer(Ldap, DefaultNcDn, CATEGORY_COMPUTER,
|
|
LDAP_SCOPE_SUBTREE, Computer);
|
|
if (*Computer != NULL) {
|
|
goto CLEANUP;
|
|
}
|
|
DPRINT2(1, ":DS: WARN - Could not find computer in defaultnc %ws; WStatus %s\n",
|
|
DefaultNcDn, ErrLabelW32(WStatus));
|
|
}
|
|
|
|
//
|
|
// Getting desperate. Try looking for a user object because an
|
|
// NT4 to NT5 upgrade will sometimes leave the objectCategory
|
|
// as user on the computer object.
|
|
//
|
|
if (DefaultNcDn) {
|
|
WStatus = FrsDsFindComputer(Ldap, DefaultNcDn, CATEGORY_USER,
|
|
LDAP_SCOPE_SUBTREE, Computer);
|
|
if (*Computer != NULL) {
|
|
goto CLEANUP;
|
|
}
|
|
DPRINT2(1, ":DS: WARN - Could not find computer in defaultnc USER %ws; WStatus %s\n",
|
|
DefaultNcDn, ErrLabelW32(WStatus));
|
|
}
|
|
|
|
|
|
CLEANUP:
|
|
|
|
return WStatus;
|
|
}
|
|
|
|
|
|
DWORD
|
|
FrsDsDeleteSubTree(
|
|
IN PLDAP Ldap,
|
|
IN PWCHAR Dn
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Delete a DS subtree, including Dn
|
|
|
|
Arguments:
|
|
None.
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsDeleteSubTree:"
|
|
DWORD LStatus;
|
|
PWCHAR Attrs[2];
|
|
PWCHAR NextDn;
|
|
PLDAPMessage LdapMsg = NULL;
|
|
PLDAPMessage LdapEntry = NULL;
|
|
|
|
MK_ATTRS_1(Attrs, ATTR_DN);
|
|
|
|
LStatus = ldap_search_ext_s(Ldap,
|
|
Dn,
|
|
LDAP_SCOPE_ONELEVEL,
|
|
CATEGORY_ANY,
|
|
Attrs,
|
|
0,
|
|
NULL,
|
|
NULL,
|
|
&LdapTimeout,
|
|
0,
|
|
&LdapMsg);
|
|
|
|
if (LStatus != LDAP_NO_SUCH_OBJECT) {
|
|
CLEANUP1_LS(4, ":DS: Can't search %ws;", Dn, LStatus, CLEANUP);
|
|
}
|
|
LStatus = LDAP_SUCCESS;
|
|
|
|
//
|
|
// Scan the entries returned from ldap_search
|
|
//
|
|
for (LdapEntry = ldap_first_entry(Ldap, LdapMsg);
|
|
LdapEntry != NULL && LStatus == LDAP_SUCCESS;
|
|
LdapEntry = ldap_next_entry(Ldap, LdapEntry)) {
|
|
|
|
NextDn = FrsDsFindValue(Ldap, LdapEntry, ATTR_DN);
|
|
LStatus = FrsDsDeleteSubTree(Ldap, NextDn);
|
|
FrsFree(NextDn);
|
|
}
|
|
|
|
if (LStatus != LDAP_SUCCESS) {
|
|
goto CLEANUP;
|
|
}
|
|
|
|
LStatus = ldap_delete_s(Ldap, Dn);
|
|
if (LStatus != LDAP_NO_SUCH_OBJECT) {
|
|
CLEANUP1_LS(4, ":DS: Can't delete %ws;", Dn, LStatus, CLEANUP);
|
|
}
|
|
|
|
//
|
|
// SUCCESS
|
|
//
|
|
LStatus = LDAP_SUCCESS;
|
|
CLEANUP:
|
|
LDAP_FREE_MSG(LdapMsg);
|
|
return LStatus;
|
|
}
|
|
|
|
|
|
BOOL
|
|
FrsDsDeleteIfEmpty(
|
|
IN PLDAP Ldap,
|
|
IN PWCHAR Dn
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Delete the Dn if it is an empty container
|
|
|
|
Arguments:
|
|
Ldap
|
|
Dn
|
|
|
|
Return Value:
|
|
TRUE - Not empty or empty and deleted
|
|
FALSE - Can't search or can't delete
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsDeleteIfEmpty:"
|
|
DWORD LStatus;
|
|
PWCHAR Attrs[2];
|
|
PLDAPMessage LdapMsg = NULL;
|
|
|
|
MK_ATTRS_1(Attrs, ATTR_DN);
|
|
|
|
LStatus = ldap_search_ext_s(Ldap,
|
|
Dn,
|
|
LDAP_SCOPE_ONELEVEL,
|
|
CATEGORY_ANY,
|
|
Attrs,
|
|
0,
|
|
NULL,
|
|
NULL,
|
|
&LdapTimeout,
|
|
0,
|
|
&LdapMsg);
|
|
|
|
if (LStatus == LDAP_SUCCESS) {
|
|
|
|
//
|
|
// If there are any entries under this Dn then we don't want to
|
|
// delete it.
|
|
//
|
|
if (ldap_count_entries(Ldap, LdapMsg) > 0) {
|
|
LDAP_FREE_MSG(LdapMsg);
|
|
return TRUE;
|
|
}
|
|
|
|
LDAP_FREE_MSG(LdapMsg);
|
|
LStatus = ldap_delete_s(Ldap, Dn);
|
|
|
|
if (LStatus != LDAP_NO_SUCH_OBJECT) {
|
|
CLEANUP1_LS(4, ":DS: Can't delete %ws;", Dn, LStatus, CLEANUP);
|
|
}
|
|
} else if (LStatus != LDAP_NO_SUCH_OBJECT) {
|
|
DPRINT1_LS(4, ":DS: Can't search %ws;", Dn, LStatus);
|
|
LDAP_FREE_MSG(LdapMsg);
|
|
return FALSE;
|
|
} else {
|
|
//
|
|
// ldap_search can return failure but still allocated the LdapMsg buffer.
|
|
//
|
|
LDAP_FREE_MSG(LdapMsg);
|
|
}
|
|
return TRUE;
|
|
|
|
CLEANUP:
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
BOOL
|
|
FrsDsEnumerateSysVolKeys(
|
|
IN PLDAP Ldap,
|
|
IN DWORD Command,
|
|
IN PWCHAR ServicesDn,
|
|
IN PWCHAR SystemDn,
|
|
IN PCONFIG_NODE Computer,
|
|
OUT BOOL *RefetchComputer
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
|
|
Scan the sysvol registry keys and process them according to Command.
|
|
|
|
REGCMD_CREATE_PRIMARY_DOMAIN - Create domain wide objects
|
|
REGCMD_CREATE_MEMBERS - Create members + subscribers
|
|
REGCMD_DELETE_MEMBERS - delete members + subscribers
|
|
REGCMD_DELETE_KEYS - Done; delete all keys
|
|
|
|
|
|
Arguments:
|
|
Ldap
|
|
HKey
|
|
Command
|
|
ServicesDn
|
|
SystemDn
|
|
Computer
|
|
RefetchComputer - Objects were altered in the DS, refetch DS info
|
|
|
|
Return Value:
|
|
TRUE - No problems
|
|
FALSE - Stop processing the registry keys
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsEnumerateSysVolKeys:"
|
|
|
|
|
|
GUID Guid;
|
|
|
|
DWORD WStatus;
|
|
DWORD LStatus;
|
|
ULONG Index;
|
|
|
|
BOOL OldNaming;
|
|
BOOL RetStatus;
|
|
HKEY HSeedingsKey = INVALID_HANDLE_VALUE;
|
|
HKEY HKey = INVALID_HANDLE_VALUE;
|
|
|
|
LDAPMod **LdapMod = NULL;
|
|
PWCHAR SettingsDn = NULL;
|
|
PWCHAR SystemSettingsDn = NULL;
|
|
PWCHAR SetDn = NULL;
|
|
PWCHAR SystemSetDn = NULL;
|
|
PWCHAR SubsDn = NULL;
|
|
PWCHAR SubDn = NULL;
|
|
PWCHAR SystemSubDn = NULL;
|
|
PWCHAR MemberDn = NULL;
|
|
PWCHAR SystemMemberDn = NULL;
|
|
PWCHAR FileFilterList = NULL;
|
|
PWCHAR DirFilterList = NULL;
|
|
|
|
PWCHAR ReplicaSetCommand = NULL;
|
|
PWCHAR ReplicaSetName = NULL;
|
|
PWCHAR ReplicaSetParent = NULL;
|
|
PWCHAR ReplicaSetType = NULL;
|
|
PWCHAR ReplicationRootPath = NULL;
|
|
PWCHAR PrintableRealRoot = NULL;
|
|
PWCHAR SubstituteRealRoot = NULL;
|
|
PWCHAR ReplicationStagePath = NULL;
|
|
PWCHAR PrintableRealStage = NULL;
|
|
PWCHAR SubstituteRealStage = NULL;
|
|
DWORD ReplicaSetPrimary;
|
|
|
|
WCHAR RegBuf[MAX_PATH + 1];
|
|
|
|
|
|
//
|
|
// Open the system volume replica sets key.
|
|
// FRS_CONFIG_SECTION\SysVol
|
|
//
|
|
WStatus = CfgRegOpenKey(FKC_SYSVOL_SECTION_KEY, NULL, 0, &HKey);
|
|
if (!WIN_SUCCESS(WStatus)) {
|
|
DPRINT_WS(4, ":DS: WARN - Cannot open sysvol key.", WStatus);
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// ENUMERATE SYSVOL SUBKEYS
|
|
//
|
|
RetStatus = TRUE;
|
|
Index = 0;
|
|
|
|
while (RetStatus) {
|
|
|
|
WStatus = RegEnumKey(HKey, Index, RegBuf, MAX_PATH + 1);
|
|
|
|
if (WStatus == ERROR_NO_MORE_ITEMS) {
|
|
break;
|
|
}
|
|
if (!WIN_SUCCESS(WStatus)) {
|
|
DPRINT_WS(0, ":DS: ERROR - enumerating sysvol keys;", WStatus);
|
|
RetStatus = FALSE;
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Delete the registry key
|
|
//
|
|
if (Command == REGCMD_DELETE_KEYS) {
|
|
WStatus = RegDeleteKey(HKey, RegBuf);
|
|
if (!WIN_SUCCESS(WStatus)) {
|
|
DPRINT1_WS(0, ":DS: ERROR - Cannot delete registry key %ws;",
|
|
RegBuf, WStatus);
|
|
RetStatus = FALSE;
|
|
break;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Open the subkey
|
|
//
|
|
DPRINT1(4, ":DS: Processing SysVol Key: %ws\n", RegBuf);
|
|
|
|
//
|
|
// The registry will be updated with the LDAP error code
|
|
//
|
|
LStatus = LDAP_OTHER;
|
|
|
|
//
|
|
// READ THE VALUES FROM THE SUBKEY
|
|
//
|
|
// SysVol\<RegBuf>\Replica Set Command
|
|
//
|
|
CfgRegReadString(FKC_SET_N_SYSVOL_COMMAND, RegBuf, 0, &ReplicaSetCommand);
|
|
|
|
if (!ReplicaSetCommand) {
|
|
DPRINT(0, ":DS: ERROR - no command; cannot process sysvol\n");
|
|
goto CONTINUE;
|
|
}
|
|
|
|
// SysVol\<Guid>\Replica Set Name
|
|
CfgRegReadString(FKC_SET_N_SYSVOL_NAME, RegBuf, 0, &ReplicaSetName);
|
|
|
|
if (!ReplicaSetName) {
|
|
DPRINT(4, ":DS: WARN - no name; using subkey name\n");
|
|
ReplicaSetName = FrsWcsDup(RegBuf);
|
|
}
|
|
|
|
//
|
|
// Construct Settings, Set, Member, Subscriptions, and Subscriber names
|
|
// (both old and new values)
|
|
//
|
|
SettingsDn = FrsDsExtendDn(ServicesDn, CN_SYSVOLS);
|
|
SystemSettingsDn = FrsDsExtendDn(SystemDn, CN_NTFRS_SETTINGS);
|
|
|
|
SetDn = FrsDsExtendDn(SettingsDn, ReplicaSetName);
|
|
SystemSetDn = FrsDsExtendDn(SystemSettingsDn, CN_DOMAIN_SYSVOL);
|
|
|
|
MemberDn = FrsDsExtendDn(SetDn, ComputerName);
|
|
SystemMemberDn = FrsDsExtendDn(SystemSetDn, ComputerName);
|
|
|
|
SubsDn = FrsDsExtendDn(Computer->Dn, CN_SUBSCRIPTIONS);
|
|
SubDn = FrsDsExtendDn(SubsDn, ReplicaSetName);
|
|
SystemSubDn = FrsDsExtendDn(SubsDn, CN_DOMAIN_SYSVOL);
|
|
|
|
|
|
//
|
|
// DELETE REPLICA SET
|
|
//
|
|
if (WSTR_EQ(ReplicaSetCommand, L"Delete")) {
|
|
//
|
|
// But only if we are processing deletes during this enumeration
|
|
//
|
|
// Delete what we can; ignore errors
|
|
//
|
|
//
|
|
// All the deletes are done in ntfrsapi.c when we commit demotion.
|
|
// This function is never called with Command = REGCMD_DELETE_MEMBERS
|
|
//
|
|
/*
|
|
if (Command == REGCMD_DELETE_MEMBERS) {
|
|
//
|
|
// DELETE MEMBER
|
|
//
|
|
//
|
|
// Old member name under services in enterprise wide partition
|
|
//
|
|
LStatus = FrsDsDeleteSubTree(Ldap, MemberDn);
|
|
if (LStatus == LDAP_SUCCESS) {*RefetchComputer = TRUE;}
|
|
DPRINT1_LS(4, ":DS: WARN - Can't delete sysvol %ws;", MemberDn, LStatus);
|
|
|
|
//
|
|
// New member name under System in domain wide partition
|
|
//
|
|
LStatus = FrsDsDeleteSubTree(Ldap, SystemMemberDn);
|
|
if (LStatus == LDAP_SUCCESS) {*RefetchComputer = TRUE;}
|
|
DPRINT1_LS(4, ":DS: WARN - Can't delete sysvol %ws;", SystemMemberDn, LStatus);
|
|
|
|
//
|
|
// DELETE SET
|
|
//
|
|
//
|
|
// Ignore errors; no real harm leaving the set
|
|
// and settings around.
|
|
//
|
|
if (!FrsDsDeleteIfEmpty(Ldap, SetDn)) {
|
|
DPRINT1(4, ":DS: WARN - Can't delete sysvol %ws\n", SetDn);
|
|
}
|
|
if (!FrsDsDeleteIfEmpty(Ldap, SystemSetDn)) {
|
|
DPRINT1(4, ":DS: WARN - Can't delete sysvol %ws\n", SystemSetDn);
|
|
}
|
|
//
|
|
// DELETE SETTINGS (don't delete new settings, there
|
|
// may be other settings beneath it (such as DFS settings))
|
|
//
|
|
if (!FrsDsDeleteIfEmpty(Ldap, SettingsDn)) {
|
|
DPRINT1(4, ":DS: WARN - Can't delete sysvol %ws\n", SettingsDn);
|
|
}
|
|
|
|
LStatus = FrsDsDeleteSubTree(Ldap, SubDn);
|
|
if (LStatus == LDAP_SUCCESS) {*RefetchComputer = TRUE;}
|
|
DPRINT1_LS(4, ":DS: WARN - Can't delete sysvol %ws;", SubDn, LStatus);
|
|
|
|
LStatus = FrsDsDeleteSubTree(Ldap, SystemSubDn);
|
|
if (LStatus == LDAP_SUCCESS) {*RefetchComputer = TRUE;}
|
|
DPRINT1_LS(4, ":DS: WARN - Can't delete sysvol %ws;", SystemSubDn, LStatus);
|
|
|
|
//
|
|
// Ignore errors; no real harm leaving the subscriptions
|
|
//
|
|
if (!FrsDsDeleteIfEmpty(Ldap, SubsDn)) {
|
|
DPRINT1(4, ":DS: WARN - Can't delete sysvol %ws\n", SubsDn);
|
|
}
|
|
}
|
|
*/
|
|
LStatus = LDAP_SUCCESS;
|
|
goto CONTINUE;
|
|
}
|
|
//
|
|
// UNKNOWN COMMAND
|
|
//
|
|
else if (WSTR_NE(ReplicaSetCommand, L"Create")) {
|
|
DPRINT1(0, ":DS: ERROR - Don't understand sysvol command %ws; cannot process sysvol\n",
|
|
ReplicaSetCommand);
|
|
goto CONTINUE;
|
|
}
|
|
|
|
|
|
//
|
|
// CREATE
|
|
//
|
|
|
|
//
|
|
// Not processing creates this scan
|
|
//
|
|
if (Command != REGCMD_CREATE_PRIMARY_DOMAIN && Command != REGCMD_CREATE_MEMBERS) {
|
|
LStatus = LDAP_SUCCESS;
|
|
goto CONTINUE;
|
|
}
|
|
|
|
//
|
|
// Finish gathering the registry values for a Create
|
|
//
|
|
WStatus = CfgRegReadString(FKC_SET_N_SYSVOL_TYPE, RegBuf, 0, &ReplicaSetType);
|
|
CLEANUP_WS(0, ":DS: ERROR - no type; cannot process sysvol.", WStatus, CONTINUE);
|
|
|
|
WStatus = CfgRegReadDWord(FKC_SET_N_SYSVOL_PRIMARY, RegBuf, 0, &ReplicaSetPrimary);
|
|
CLEANUP_WS(0, ":DS: ERROR - no primary; cannot process sysvol.", WStatus, CONTINUE);
|
|
|
|
WStatus = CfgRegReadString(FKC_SET_N_SYSVOL_ROOT, RegBuf, 0, &ReplicationRootPath);
|
|
CLEANUP_WS(0, ":DS: ERROR - no root; cannot process sysvol.", WStatus, CONTINUE);
|
|
|
|
WStatus = CfgRegReadString(FKC_SET_N_SYSVOL_STAGE, RegBuf, 0, &ReplicationStagePath);
|
|
CLEANUP_WS(0, ":DS: ERROR - no stage; cannot process sysvol.", WStatus, CONTINUE);
|
|
|
|
WStatus = CfgRegReadString(FKC_SET_N_SYSVOL_PARENT, RegBuf, 0, &ReplicaSetParent);
|
|
DPRINT_WS(0, ":DS: WARN - no parent; cannot process seeding sysvol", WStatus);
|
|
|
|
if (Command == REGCMD_CREATE_PRIMARY_DOMAIN) {
|
|
//
|
|
// Not the primary domain sysvol
|
|
//
|
|
if (!ReplicaSetPrimary ||
|
|
WSTR_NE(ReplicaSetType, NTFRSAPI_REPLICA_SET_TYPE_DOMAIN)) {
|
|
LStatus = LDAP_SUCCESS;
|
|
goto CONTINUE;
|
|
}
|
|
|
|
//
|
|
// Domain wide Settings -- may already exist
|
|
//
|
|
FrsDsAddLdapMod(ATTR_CLASS, ATTR_NTFRS_SETTINGS, &LdapMod);
|
|
DPRINT1(4, ":DS: Creating Sysvol System Settings %ws\n", CN_NTFRS_SETTINGS);
|
|
LStatus = ldap_add_s(Ldap, SystemSettingsDn, LdapMod);
|
|
FrsDsFreeLdapMod(&LdapMod);
|
|
|
|
if (LStatus == LDAP_SUCCESS) {
|
|
*RefetchComputer = TRUE;
|
|
}
|
|
|
|
if (LStatus != LDAP_ALREADY_EXISTS && LStatus != LDAP_SUCCESS) {
|
|
DPRINT1_LS(0, ":DS: ERROR - Can't create %ws:", SystemSettingsDn, LStatus);
|
|
//
|
|
// May be an error like "Access Denied". As long as we
|
|
// can create objects under it; ignore errors. It should
|
|
// have been pre-created by default, anyway.
|
|
//
|
|
// goto CONTINUE;
|
|
}
|
|
|
|
//
|
|
// Domain wide Set -- may already exist
|
|
//
|
|
WStatus = UuidCreateNil(&Guid);
|
|
CLEANUP_WS(0, ":DS: ERROR - no UUID Created; cannot process sysvol.", WStatus, CONTINUE);
|
|
|
|
FrsDsAddLdapMod(ATTR_CLASS, ATTR_REPLICA_SET, &LdapMod);
|
|
FrsDsAddLdapMod(ATTR_SET_TYPE, FRS_RSTYPE_DOMAIN_SYSVOLW, &LdapMod);
|
|
|
|
//
|
|
// Create the replica set object with the default file
|
|
// and dir filter lists only if current default is non-null.
|
|
//
|
|
FileFilterList = FRS_DS_COMPOSE_FILTER_LIST(NULL,
|
|
RegistryFileExclFilterList,
|
|
DEFAULT_FILE_FILTER_LIST);
|
|
if (wcslen(FileFilterList) > 0) {
|
|
FrsDsAddLdapMod(ATTR_FILE_FILTER, FileFilterList, &LdapMod);
|
|
}
|
|
|
|
DirFilterList = FRS_DS_COMPOSE_FILTER_LIST(NULL,
|
|
RegistryDirExclFilterList,
|
|
DEFAULT_DIR_FILTER_LIST);
|
|
if (wcslen(DirFilterList) > 0) {
|
|
FrsDsAddLdapMod(ATTR_DIRECTORY_FILTER, DirFilterList, &LdapMod);
|
|
}
|
|
|
|
|
|
FrsDsAddLdapBerMod(ATTR_NEW_SET_GUID, (PCHAR)&Guid, sizeof(GUID), &LdapMod);
|
|
|
|
FrsDsAddLdapBerMod(ATTR_NEW_VERSION_GUID, (PCHAR)&Guid, sizeof(GUID), &LdapMod);
|
|
|
|
DPRINT1(4, ":DS: Creating Domain Set %ws\n", ReplicaSetName);
|
|
|
|
LStatus = ldap_add_s(Ldap, SystemSetDn, LdapMod);
|
|
FrsDsFreeLdapMod(&LdapMod);
|
|
|
|
if (LStatus == LDAP_SUCCESS) {
|
|
*RefetchComputer = TRUE;
|
|
}
|
|
|
|
if (LStatus != LDAP_ALREADY_EXISTS) {
|
|
CLEANUP1_LS(0, ":DS: ERROR - Can't create %ws:",
|
|
SystemSetDn, LStatus, CONTINUE);
|
|
}
|
|
|
|
LStatus = LDAP_SUCCESS;
|
|
goto CONTINUE;
|
|
}
|
|
|
|
if (Command != REGCMD_CREATE_MEMBERS) {
|
|
DPRINT1(0, ":DS: ERROR - Don't understand %d; can't process sysvols\n",
|
|
Command);
|
|
goto CONTINUE;
|
|
}
|
|
|
|
|
|
//
|
|
// CREATE MEMBER
|
|
//
|
|
// Member -- may already exist
|
|
// Delete old member in case it was left lying around after
|
|
// a demotion. This can happen because the service doesn't
|
|
// have permissions to alter the DS after a promotion.
|
|
// Leaving the old objects lying around after the demotion
|
|
// is confusing but doesn't cause replication to behave
|
|
// incorrectly.
|
|
//
|
|
DPRINT1(4, ":DS: Creating Member %ws\n", ComputerName);
|
|
OldNaming = FALSE;
|
|
//
|
|
// Delete old member
|
|
//
|
|
LStatus = FrsDsDeleteSubTree(Ldap, MemberDn);
|
|
CLEANUP1_LS(0, ":DS: ERROR - Can't free member %ws:",
|
|
ComputerName, LStatus, CONTINUE);
|
|
|
|
LStatus = FrsDsDeleteSubTree(Ldap, SystemMemberDn);
|
|
CLEANUP1_LS(0, ":DS: ERROR - Can't free system member %ws:",
|
|
ComputerName, LStatus, CONTINUE);
|
|
|
|
//
|
|
// Create new member
|
|
//
|
|
FrsDsAddLdapMod(ATTR_CLASS, ATTR_MEMBER, &LdapMod);
|
|
FrsDsAddLdapMod(ATTR_COMPUTER_REF, Computer->Dn, &LdapMod);
|
|
if (Computer->SettingsDn) {
|
|
FrsDsAddLdapMod(ATTR_SERVER_REF, Computer->SettingsDn, &LdapMod);
|
|
}
|
|
|
|
LStatus = ldap_add_s(Ldap, SystemMemberDn, LdapMod);
|
|
FrsDsFreeLdapMod(&LdapMod);
|
|
if (LStatus == LDAP_SUCCESS) {
|
|
*RefetchComputer = TRUE;
|
|
}
|
|
|
|
if (LStatus != LDAP_ALREADY_EXISTS && LStatus != LDAP_SUCCESS) {
|
|
//
|
|
// Try old B2 naming conventions
|
|
//
|
|
DPRINT1_LS(4, ":DS: WARN - Can't create system member ws:",
|
|
ComputerName, LStatus);
|
|
FrsDsAddLdapMod(ATTR_CLASS, ATTR_MEMBER, &LdapMod);
|
|
FrsDsAddLdapMod(ATTR_COMPUTER_REF, Computer->Dn, &LdapMod);
|
|
if (Computer->SettingsDn) {
|
|
FrsDsAddLdapMod(ATTR_SERVER_REF, Computer->SettingsDn, &LdapMod);
|
|
}
|
|
|
|
LStatus = ldap_add_s(Ldap, MemberDn, LdapMod);
|
|
FrsDsFreeLdapMod(&LdapMod);
|
|
if (LStatus == LDAP_SUCCESS) {
|
|
*RefetchComputer = TRUE;
|
|
}
|
|
|
|
if (LStatus != LDAP_ALREADY_EXISTS) {
|
|
CLEANUP1_LS(0, ":DS: ERROR - Can't create old member %ws:",
|
|
ComputerName, LStatus, CONTINUE);
|
|
}
|
|
|
|
OldNaming = TRUE;
|
|
}
|
|
|
|
//
|
|
// CREATE PRIMARY MEMBER REFERENCE
|
|
//
|
|
if (ReplicaSetPrimary) {
|
|
FrsDsAddLdapMod(ATTR_PRIMARY_MEMBER,
|
|
(OldNaming) ? MemberDn : SystemMemberDn,
|
|
&LdapMod);
|
|
|
|
DPRINT2(4, ":DS: Creating Member Reference %ws for %ws\n",
|
|
ComputerName, ReplicaSetName);
|
|
|
|
LdapMod[0]->mod_op = LDAP_MOD_REPLACE;
|
|
LStatus = ldap_modify_s(Ldap, (OldNaming) ? SetDn : SystemSetDn, LdapMod);
|
|
|
|
FrsDsFreeLdapMod(&LdapMod);
|
|
|
|
if (LStatus == LDAP_SUCCESS) {
|
|
*RefetchComputer = TRUE;
|
|
}
|
|
|
|
if (LStatus != LDAP_ATTRIBUTE_OR_VALUE_EXISTS) {
|
|
CLEANUP2_LS(0, ":DS: ERROR - Can't create priamry reference %ws\\%ws:",
|
|
ReplicaSetName, ComputerName, LStatus, CONTINUE);
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// Translate the symlinks. NtFrs requires true pathname to
|
|
// its directories (<drive letter>:\...)
|
|
// FrsChaseSymbolicLink returns both the PrintName and the SubstituteName.
|
|
// We use the PrintName as it is the Dos Type name of the destination.
|
|
// Substitute Name is ignored.
|
|
//
|
|
WStatus = FrsChaseSymbolicLink(ReplicationRootPath, &PrintableRealRoot, &SubstituteRealRoot);
|
|
if (!WIN_SUCCESS(WStatus)) {
|
|
DPRINT2(0, ":DS: ERROR - Accessing %ws; cannot process sysvol: WStatus = %d",
|
|
ReplicationRootPath, WStatus);
|
|
RetStatus = FALSE;
|
|
goto CONTINUE;
|
|
}
|
|
|
|
WStatus = FrsChaseSymbolicLink(ReplicationStagePath, &PrintableRealStage, &SubstituteRealStage);
|
|
if (!WIN_SUCCESS(WStatus)) {
|
|
DPRINT2(0, ":DS: ERROR - Accessing %ws; cannot process sysvol: WStatus = %d",
|
|
ReplicationRootPath, WStatus);
|
|
RetStatus = FALSE;
|
|
goto CONTINUE;
|
|
}
|
|
|
|
//
|
|
// Subscriptions (if needed)
|
|
//
|
|
DPRINT1(4, ":DS: Creating Subscriptions for %ws\n", ComputerName);
|
|
FrsDsAddLdapMod(ATTR_CLASS, ATTR_SUBSCRIPTIONS, &LdapMod);
|
|
FrsDsAddLdapMod(ATTR_WORKING, WorkingPath, &LdapMod);
|
|
LStatus = ldap_add_s(Ldap, SubsDn, LdapMod);
|
|
FrsDsFreeLdapMod(&LdapMod);
|
|
if (LStatus == LDAP_SUCCESS) {
|
|
*RefetchComputer = TRUE;
|
|
}
|
|
|
|
if (LStatus != LDAP_ALREADY_EXISTS) {
|
|
CLEANUP1_LS(0, ":DS: ERROR - Can't create %ws:",
|
|
SubsDn, LStatus, CONTINUE);
|
|
}
|
|
|
|
//
|
|
// Subscriber -- may alread exist
|
|
// Delete old subscriber in case it was left lying around
|
|
// after a demotion. This can happen because the service
|
|
// doesn't have permissions to alter the DS after a promotion.
|
|
// Leaving the old objects lying around after the demotion
|
|
// is confusing but doesn't cause replication to behave
|
|
// incorrectly; any sysvol in the DS without a corresponding
|
|
// sysvol in the DB is ignored by the Ds polling thread.
|
|
//
|
|
DPRINT1(4, ":DS: Creating Subscriber for %ws\n", ComputerName);
|
|
LStatus = FrsDsDeleteSubTree(Ldap, SubDn);
|
|
CLEANUP1_LS(4, ":DS: WARN - Can't delete %ws:", SubDn, LStatus, CONTINUE);
|
|
|
|
LStatus = FrsDsDeleteSubTree(Ldap, SystemSubDn);
|
|
CLEANUP1_LS(4, ":DS: WARN - Can't delete %ws:", SystemSubDn, LStatus, CONTINUE);
|
|
|
|
FrsDsAddLdapMod(ATTR_CLASS, ATTR_SUBSCRIBER, &LdapMod);
|
|
FrsDsAddLdapMod(ATTR_REPLICA_ROOT, PrintableRealRoot, &LdapMod);
|
|
FrsDsAddLdapMod(ATTR_REPLICA_STAGE, PrintableRealStage, &LdapMod);
|
|
FrsDsAddLdapMod(ATTR_MEMBER_REF,
|
|
(OldNaming) ? MemberDn : SystemMemberDn,
|
|
&LdapMod);
|
|
LStatus = ldap_add_s(Ldap, SystemSubDn, LdapMod);
|
|
|
|
FrsDsFreeLdapMod(&LdapMod);
|
|
if (LStatus == LDAP_SUCCESS) {
|
|
*RefetchComputer = TRUE;
|
|
}
|
|
|
|
if (LStatus != LDAP_ALREADY_EXISTS) {
|
|
CLEANUP1_LS(4, ":DS: ERROR - Can't create %ws:",
|
|
SystemSubDn, LStatus, CONTINUE);
|
|
}
|
|
|
|
|
|
//
|
|
// Seeding information
|
|
//
|
|
|
|
//
|
|
// Create the key for all seeding sysvols
|
|
//
|
|
|
|
WStatus = CfgRegOpenKey(FKC_SYSVOL_SEEDING_SECTION_KEY,
|
|
NULL,
|
|
FRS_RKF_CREATE_KEY,
|
|
&HSeedingsKey);
|
|
CLEANUP1_WS(0, ":DS: ERROR - Cannot create seedings key for %ws;",
|
|
ReplicaSetName, WStatus, SKIP_SEEDING);
|
|
|
|
//
|
|
// Create the seeding subkey for this sysvol
|
|
//
|
|
RegDeleteKey(HSeedingsKey, ReplicaSetName);
|
|
RegDeleteKey(HSeedingsKey, CN_DOMAIN_SYSVOL);
|
|
if (ReplicaSetParent) {
|
|
|
|
//
|
|
// Save the Replica Set Parent for this replica set under the
|
|
// "Sysvol Seeding\<rep set name>\Replica Set Parent"
|
|
//
|
|
WStatus = CfgRegWriteString(FKC_SYSVOL_SEEDING_N_PARENT,
|
|
(OldNaming) ? ReplicaSetName : CN_DOMAIN_SYSVOL,
|
|
FRS_RKF_CREATE_KEY,
|
|
ReplicaSetParent);
|
|
DPRINT1_WS(0, "WARN - Cannot create parent value for %ws;",
|
|
(OldNaming) ? ReplicaSetName : CN_DOMAIN_SYSVOL, WStatus);
|
|
}
|
|
|
|
|
|
//
|
|
// Save the Replica Set name for this replica set under the
|
|
// "Sysvol Seeding\<rep set name>\Replica Set Name"
|
|
//
|
|
WStatus = CfgRegWriteString(FKC_SYSVOL_SEEDING_N_RSNAME,
|
|
(OldNaming) ? ReplicaSetName : CN_DOMAIN_SYSVOL,
|
|
FRS_RKF_CREATE_KEY,
|
|
ReplicaSetName);
|
|
DPRINT1_WS(0, "WARN - Cannot create name value for %ws;",
|
|
(OldNaming) ? ReplicaSetName : CN_DOMAIN_SYSVOL, WStatus);
|
|
|
|
SKIP_SEEDING:
|
|
LStatus = LDAP_SUCCESS;
|
|
|
|
CONTINUE:
|
|
FRS_REG_CLOSE(HSeedingsKey);
|
|
|
|
//
|
|
// Something went wrong. Put the LDAP error status into the
|
|
// registry key for this replica set and move on to the next.
|
|
//
|
|
if (LStatus != LDAP_SUCCESS) {
|
|
|
|
CfgRegWriteDWord(FKC_SET_N_SYSVOL_STATUS, RegBuf, 0, LStatus);
|
|
RetStatus = FALSE;
|
|
}
|
|
|
|
//
|
|
// CLEANUP
|
|
//
|
|
ReplicaSetCommand = FrsFree(ReplicaSetCommand);
|
|
ReplicaSetName = FrsFree(ReplicaSetName);
|
|
ReplicaSetParent = FrsFree(ReplicaSetParent);
|
|
ReplicaSetType = FrsFree(ReplicaSetType);
|
|
ReplicationRootPath = FrsFree(ReplicationRootPath);
|
|
PrintableRealRoot = FrsFree(PrintableRealRoot);
|
|
SubstituteRealRoot = FrsFree(SubstituteRealRoot);
|
|
ReplicationStagePath = FrsFree(ReplicationStagePath);
|
|
PrintableRealStage = FrsFree(PrintableRealStage);
|
|
SubstituteRealStage = FrsFree(SubstituteRealStage);
|
|
|
|
SettingsDn = FrsFree(SettingsDn);
|
|
SystemSettingsDn = FrsFree(SystemSettingsDn);
|
|
SetDn = FrsFree(SetDn);
|
|
SystemSetDn = FrsFree(SystemSetDn);
|
|
SubsDn = FrsFree(SubsDn);
|
|
SubDn = FrsFree(SubDn);
|
|
SystemSubDn = FrsFree(SystemSubDn);
|
|
MemberDn = FrsFree(MemberDn);
|
|
SystemMemberDn = FrsFree(SystemMemberDn);
|
|
FileFilterList = FrsFree(FileFilterList);
|
|
DirFilterList = FrsFree(DirFilterList);
|
|
|
|
//
|
|
// Next SubKey
|
|
//
|
|
++Index;
|
|
} // End while (RetStatus)
|
|
|
|
|
|
if (HANDLE_IS_VALID(HKey)) {
|
|
//
|
|
// The flush here will make sure that the key is written to the disk.
|
|
// These are critical registry operations and we don't want the lazy flusher
|
|
// to delay the writes.
|
|
//
|
|
RegFlushKey(HKey);
|
|
FRS_REG_CLOSE(HKey);
|
|
}
|
|
|
|
return RetStatus;
|
|
}
|
|
|
|
|
|
DWORD
|
|
FrsDsCreateSysVols(
|
|
IN PLDAP Ldap,
|
|
IN PWCHAR ServicesDn,
|
|
IN PCONFIG_NODE Computer,
|
|
OUT BOOL *RefetchComputer
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Process the commands left in the Sysvol registry key by dcpromo.
|
|
|
|
Ignore the sysvol registry key if this machine is not a DC!
|
|
|
|
NOTE: this means the registry keys for a "delete sysvol"
|
|
after a demotion are pretty much ignored. So why have them?
|
|
Its historical and there is too little time before B3 to make
|
|
such a dramatic change. Besides, we may find a use for them.
|
|
And, to make matters worse, the "delete sysvol" keys could
|
|
not be processed because the ldap_delete() returned insufficient
|
|
rights errors since this computer is no longer a DC.
|
|
|
|
REGCMD_DELETE_MEMBERS is no longer used as all deletion is done
|
|
in ntfrsapi.c when demotion is committed.
|
|
|
|
Arguments:
|
|
Ldap
|
|
ServicesDn
|
|
Computer
|
|
RefetchComputer - Objects were altered in the DS, refetch DS info
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsCreateSysVols:"
|
|
DWORD WStatus;
|
|
DWORD SysVolInfoIsCommitted;
|
|
HKEY HKey = INVALID_HANDLE_VALUE;
|
|
|
|
//
|
|
// Refetch the computer subtree iff the contents of the DS
|
|
// are altered by this function
|
|
//
|
|
*RefetchComputer = FALSE;
|
|
|
|
//
|
|
// Already checked the registry or not a DC; done
|
|
//
|
|
if (DsCreateSysVolsHasRun || !IsADc) {
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
DPRINT(5, ":DS: Checking for SysVols commands\n");
|
|
|
|
//
|
|
// Open the system volume replica sets key.
|
|
// FRS_CONFIG_SECTION\SysVol
|
|
//
|
|
WStatus = CfgRegOpenKey(FKC_SYSVOL_SECTION_KEY, NULL, 0, &HKey);
|
|
if (!WIN_SUCCESS(WStatus)) {
|
|
DPRINT_WS(4, ":DS: WARN - Cannot open sysvol key.", WStatus);
|
|
DPRINT(4, ":DS: ERROR - Can't check for sysvols\n");
|
|
return WStatus;
|
|
}
|
|
|
|
|
|
WStatus = CfgRegReadDWord(FKC_SYSVOL_INFO_COMMITTED, NULL, 0, &SysVolInfoIsCommitted);
|
|
CLEANUP_WS(4, ":DS: Sysvol info is not committed.", WStatus, done);
|
|
|
|
DPRINT1(4, ":DS: Sysvol info is committed (%d)\n", SysVolInfoIsCommitted);
|
|
|
|
//
|
|
// Must have a computer; try again later
|
|
//
|
|
if (!Computer) {
|
|
DPRINT(4, ":DS: No computer; retry sysvols later\n");
|
|
WStatus = ERROR_RETRY;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Must have a server reference; try again later
|
|
//
|
|
if (!Computer->SettingsDn && RunningAsAService) {
|
|
DPRINT1(4, ":DS: %ws does not have a server reference; retry sysvols later\n",
|
|
Computer->Name->Name);
|
|
WStatus = ERROR_RETRY;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// assume failure
|
|
//
|
|
WIN_SET_FAIL(WStatus);
|
|
|
|
//
|
|
// Don't create the settings or set if this computer is not a DC
|
|
//
|
|
if (IsADc &&
|
|
!FrsDsEnumerateSysVolKeys(Ldap, REGCMD_CREATE_PRIMARY_DOMAIN,
|
|
ServicesDn, SystemDn, Computer, RefetchComputer)) {
|
|
goto cleanup;
|
|
}
|
|
//
|
|
// Don't create the member if this computer is not a DC
|
|
//
|
|
if (IsADc &&
|
|
!FrsDsEnumerateSysVolKeys(Ldap, REGCMD_CREATE_MEMBERS,
|
|
ServicesDn, SystemDn, Computer, RefetchComputer)) {
|
|
goto cleanup;
|
|
}
|
|
//
|
|
// Don't delete the sysvol if this computer is a DC.
|
|
//
|
|
// The following code is never executed because if we are not a DC then
|
|
// the function returns after the first check.
|
|
//
|
|
/*
|
|
if (!IsADc &&
|
|
!FrsDsEnumerateSysVolKeys(Ldap, REGCMD_DELETE_MEMBERS,
|
|
ServicesDn, SystemDn, Computer, RefetchComputer)) {
|
|
goto cleanup;
|
|
}
|
|
*/
|
|
|
|
//
|
|
// Discard the dcpromo keys
|
|
//
|
|
if (!FrsDsEnumerateSysVolKeys(Ldap, REGCMD_DELETE_KEYS,
|
|
ServicesDn, SystemDn, Computer, RefetchComputer)) {
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// sysvol info has been processed; don't process again
|
|
//
|
|
RegDeleteValue(HKey, SYSVOL_INFO_IS_COMMITTED);
|
|
|
|
done:
|
|
DsCreateSysVolsHasRun = TRUE;
|
|
WStatus = ERROR_SUCCESS;
|
|
|
|
cleanup:
|
|
//
|
|
// Cleanup
|
|
//
|
|
if (HANDLE_IS_VALID(HKey)) {
|
|
//
|
|
// The flush here will make sure that the key is written to the disk.
|
|
// These are critical registry operations and we don't want the lazy flusher
|
|
// to delay the writes.
|
|
//
|
|
RegFlushKey(HKey);
|
|
FRS_REG_CLOSE(HKey);
|
|
}
|
|
return WStatus;
|
|
}
|
|
|
|
|
|
PWCHAR
|
|
FrsDsPrincNameToBiosName(
|
|
IN PWCHAR PrincName
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Convert the principal name (domain.dns.name\SamAccountName) into
|
|
its equivalent NetBios name (SamAccountName - $).
|
|
|
|
Arguments:
|
|
PrincName - Domain Dns Name \ Sam Account Name
|
|
|
|
Return Value:
|
|
Sam Account Name - trailing $
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsPrincNameToBiosName:"
|
|
DWORD Len;
|
|
PWCHAR c;
|
|
PWCHAR BiosName = NULL;
|
|
|
|
if (!PrincName || !*PrincName) {
|
|
goto CLEANUP;
|
|
}
|
|
|
|
//
|
|
// Find the first char past the first whack
|
|
//
|
|
for (c = PrincName; *c && *c != L'\\'; ++c);
|
|
if (!*c) {
|
|
//
|
|
// No whack; use the entire principal name
|
|
//
|
|
c = PrincName;
|
|
} else {
|
|
//
|
|
// Skip the whack
|
|
//
|
|
++c;
|
|
}
|
|
//
|
|
// Elide the trailing $
|
|
//
|
|
Len = wcslen(c);
|
|
if (c[Len - 1] == L'$') {
|
|
--Len;
|
|
}
|
|
|
|
//
|
|
// Copy the chars between the whack and the dollar (append trailing null)
|
|
//
|
|
BiosName = FrsAlloc((Len + 1) * sizeof(WCHAR));
|
|
CopyMemory(BiosName, c, Len * sizeof(WCHAR));
|
|
BiosName[Len] = L'\0';
|
|
|
|
CLEANUP:
|
|
DPRINT2(4, ":DS: PrincName %ws to BiosName %ws\n", PrincName, BiosName);
|
|
|
|
return BiosName;
|
|
}
|
|
|
|
|
|
VOID
|
|
FrsDsMergeConfigWithReplicas(
|
|
IN PLDAP Ldap,
|
|
IN PCONFIG_NODE Sites
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Convert the portions of the DS tree that define the topology
|
|
and state for this machine into replicas and merge them with
|
|
the active replicas.
|
|
|
|
Arguments:
|
|
Sites
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsMergeConfigWithReplicas:"
|
|
PCONFIG_NODE Site;
|
|
PCONFIG_NODE Settings;
|
|
PCONFIG_NODE Set;
|
|
PCONFIG_NODE Server;
|
|
PCONFIG_NODE Node;
|
|
PCONFIG_NODE RevNode;
|
|
BOOL Inbound;
|
|
BOOL IsSysvol;
|
|
PCXTION Cxtion;
|
|
PREPLICA Replica;
|
|
PREPLICA DbReplica;
|
|
|
|
//
|
|
// Coordinate with replica command server
|
|
//
|
|
RcsBeginMergeWithDs();
|
|
|
|
//
|
|
// For every server
|
|
//
|
|
for (Site = Sites; Site; Site = Site->Peer) {
|
|
for (Settings = Site->Children; Settings; Settings = Settings->Peer) {
|
|
for (Set = Settings->Children; Set; Set = Set->Peer) {
|
|
for (Server = Set->Children; Server && !DsIsShuttingDown; Server = Server->Peer) {
|
|
//
|
|
// This server does not match this machine's name; continue
|
|
//
|
|
if (!Server->ThisComputer) {
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// MATCH
|
|
//
|
|
|
|
//
|
|
// CHECK FOR SYSVOL CONSISTENCY
|
|
// Leave the current DB state alone if a sysvol
|
|
// appears from the DS and the sysvol registry
|
|
// keys were not processed or the computer is not
|
|
// a dc.
|
|
//
|
|
if (FRS_RSTYPE_IS_SYSVOLW(Set->SetType)) {
|
|
//
|
|
// Not a DC or sysvol registry keys not processed
|
|
// Tombstone existing sysvols
|
|
// Ignore new sysvols
|
|
//
|
|
if (!IsADc || !DsCreateSysVolsHasRun) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Create a replica set
|
|
//
|
|
Replica = FrsAllocType(REPLICA_TYPE);
|
|
//
|
|
// Replica name (Set Name + Member Guid)
|
|
Replica->ReplicaName = FrsBuildGName(FrsDupGuid(Server->Name->Guid),
|
|
FrsWcsDup(Set->Name->Name));
|
|
//
|
|
// Member name + guid
|
|
//
|
|
Replica->MemberName = FrsDupGName(Server->Name);
|
|
//
|
|
// Set name + guid
|
|
//
|
|
Replica->SetName = FrsDupGName(Set->Name);
|
|
//
|
|
// Root guid (hammered onto the root directory)
|
|
// Temporary; a new guid is assigned if this is a new
|
|
// set.
|
|
//
|
|
Replica->ReplicaRootGuid = FrsDupGuid(Replica->SetName->Guid);
|
|
|
|
//
|
|
// File Filter
|
|
//
|
|
Replica->FileFilterList = FRS_DS_COMPOSE_FILTER_LIST(
|
|
Set->FileFilterList,
|
|
RegistryFileExclFilterList,
|
|
DEFAULT_FILE_FILTER_LIST);
|
|
Replica->FileInclFilterList = FrsWcsDup(RegistryFileInclFilterList);
|
|
|
|
//
|
|
// Directory Filter
|
|
//
|
|
Replica->DirFilterList = FRS_DS_COMPOSE_FILTER_LIST(
|
|
Set->DirFilterList,
|
|
RegistryDirExclFilterList,
|
|
DEFAULT_DIR_FILTER_LIST);
|
|
Replica->DirInclFilterList = FrsWcsDup(RegistryDirInclFilterList);
|
|
|
|
//
|
|
// Root and stage
|
|
//
|
|
Replica->Root = FrsWcsDup(Server->Root);
|
|
Replica->Stage = FrsWcsDup(Server->Stage);
|
|
FRS_WCSLWR(Replica->Root); // for wcsstr()
|
|
FRS_WCSLWR(Replica->Stage); // for wcsstr()
|
|
//
|
|
// Volume.
|
|
//
|
|
// Replica->Volume = FrsWcsVolume(Server->Root);
|
|
|
|
//
|
|
// Does the Set's primary member link match this
|
|
// member's Dn? Is this the primary member?
|
|
//
|
|
if (Set->MemberDn) {
|
|
ClearFlag(Replica->CnfFlags, CONFIG_FLAG_PRIMARY_UNDEFINED);
|
|
if(WSTR_EQ(Server->Dn, Set->MemberDn)) {
|
|
SetFlag(Replica->CnfFlags, CONFIG_FLAG_PRIMARY);
|
|
}
|
|
} else {
|
|
SetFlag(Replica->CnfFlags, CONFIG_FLAG_PRIMARY_UNDEFINED);
|
|
|
|
//
|
|
// DFS is not currently (Sept 2002) using the primary
|
|
// member, so it is okay if there is none.
|
|
// For now, don't spam the event log with this warning.
|
|
//
|
|
|
|
// FrsDsAddToPollSummary1ws(IDS_POLL_SUM_PRIMARY_UNDEFINED,
|
|
// Replica->ReplicaName->Name
|
|
// );
|
|
|
|
}
|
|
|
|
//
|
|
// Consistent
|
|
//
|
|
Replica->Consistent = Server->Consistent;
|
|
//
|
|
// Replica Set Type
|
|
//
|
|
if (Set->SetType) {
|
|
Replica->ReplicaSetType = wcstoul(Set->SetType, NULL, 10);
|
|
} else {
|
|
Replica->ReplicaSetType = FRS_RSTYPE_OTHER;
|
|
}
|
|
|
|
//
|
|
// FRS replica set object Flags
|
|
//
|
|
Replica->FrsRsoFlags = Set->FrsRsoFlags;
|
|
|
|
//
|
|
// Set default Schedule for replica set. Priority order is:
|
|
// 1. Server (sysvols only)
|
|
// 2. ReplicaSet object
|
|
// 3. Settings object
|
|
// 4. Site object.
|
|
//
|
|
Node = (Server->Schedule) ? Server :
|
|
(Set->Schedule) ? Set :
|
|
(Settings->Schedule) ? Settings :
|
|
(Site->Schedule) ? Site : NULL;
|
|
if (Node) {
|
|
Replica->Schedule = FrsAlloc(Node->ScheduleLength);
|
|
CopyMemory(Replica->Schedule, Node->Schedule, Node->ScheduleLength);
|
|
}
|
|
|
|
//
|
|
// Sysvol needs seeding
|
|
//
|
|
// The CnfFlags are ignored if the set already exists.
|
|
// Hence, only newly created sysvols are seeded.
|
|
//
|
|
IsSysvol = FRS_RSTYPE_IS_SYSVOL(Replica->ReplicaSetType);
|
|
if (IsSysvol &&
|
|
!BooleanFlagOn(Replica->CnfFlags, CONFIG_FLAG_PRIMARY)) {
|
|
SetFlag(Replica->CnfFlags, CONFIG_FLAG_SEEDING);
|
|
}
|
|
|
|
//
|
|
// Go through the connections and fix the schedule for
|
|
// two way replication.
|
|
//
|
|
for (Node = Server->Children; Node; Node = Node->Peer) {
|
|
if (!Node->Consistent) {
|
|
continue;
|
|
}
|
|
//
|
|
// If the NTDSCONN_OPT_TWOWAY_SYNC flag is set on the connection then
|
|
// merge the schedule on this connection with the schedule on the connection
|
|
// that is in the opposite direction and use the resultant schedule on the
|
|
// connection in the opposite direction.
|
|
//
|
|
if (Node->CxtionOptions & NTDSCONN_OPT_TWOWAY_SYNC) {
|
|
Inbound = !Node->Inbound;
|
|
//
|
|
// Loop through the connections and find the connection in
|
|
// the opposite direction.
|
|
//
|
|
for (RevNode = Server->Children; RevNode; RevNode = RevNode->Peer) {
|
|
if ((RevNode->Inbound == Inbound) &&
|
|
!_wcsicmp(Node->PartnerDn, RevNode->PartnerDn)) {
|
|
|
|
DPRINT1(4,"Two-way replication: Setting merged schedule on %ws\n",RevNode->Dn);
|
|
FrsDsMergeTwoWaySchedules(&Node->Schedule,
|
|
&Node->ScheduleLength,
|
|
&RevNode->Schedule,
|
|
&RevNode->ScheduleLength,
|
|
&Replica->Schedule);
|
|
break;
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Copy over the cxtions
|
|
//
|
|
for (Node = Server->Children; Node; Node = Node->Peer) {
|
|
|
|
if (!Node->Consistent) {
|
|
continue;
|
|
}
|
|
|
|
Cxtion = FrsAllocType(CXTION_TYPE);
|
|
|
|
Cxtion->Inbound = Node->Inbound;
|
|
if (Node->Consistent) {
|
|
SetCxtionFlag(Cxtion, CXTION_FLAGS_CONSISTENT);
|
|
}
|
|
Cxtion->Name = FrsDupGName(Node->Name);
|
|
Cxtion->Partner = FrsBuildGName(
|
|
FrsDupGuid(Node->PartnerName->Guid),
|
|
FrsDsPrincNameToBiosName(Node->PrincName));
|
|
//
|
|
// Partner's DNS name from ATTR_DNS_HOST_NAME on the computer
|
|
// object. Register an event if the attribute is missing
|
|
// or unavailable and try using the netbios name.
|
|
//
|
|
if (Node->PartnerDnsName) {
|
|
Cxtion->PartnerDnsName = FrsWcsDup(Node->PartnerDnsName);
|
|
} else {
|
|
if (Cxtion->Partner->Name && Cxtion->Partner->Name[0]) {
|
|
EPRINT3(EVENT_FRS_NO_DNS_ATTRIBUTE,
|
|
Cxtion->Partner->Name,
|
|
ATTR_DNS_HOST_NAME,
|
|
(Node->PartnerCoDn) ? Node->PartnerCoDn :
|
|
Cxtion->Partner->Name);
|
|
Cxtion->PartnerDnsName = FrsWcsDup(Cxtion->Partner->Name);
|
|
} else {
|
|
Cxtion->PartnerDnsName = FrsWcsDup(L"<unknown>");
|
|
}
|
|
}
|
|
//
|
|
// Partner's SID name from DsCrackNames() on the computer
|
|
// object. Register an event if the SID is unavailable.
|
|
//
|
|
if (Node->PartnerSid) {
|
|
Cxtion->PartnerSid = FrsWcsDup(Node->PartnerSid);
|
|
} else {
|
|
//
|
|
// Print the eventlog message only if DsBindingsAreValid is TRUE.
|
|
// If it is FALSE it means that the handle is invalid and we are
|
|
// scheduled to rebind at the next poll. In that case the rebind will
|
|
// probably fix the problem silently.
|
|
//
|
|
if (Cxtion->Partner->Name && Cxtion->Partner->Name[0] && DsBindingsAreValid) {
|
|
EPRINT3(EVENT_FRS_NO_SID,
|
|
Replica->Root,
|
|
Cxtion->Partner->Name,
|
|
(Node->PartnerCoDn) ? Node->PartnerCoDn :
|
|
Cxtion->Partner->Name);
|
|
}
|
|
Cxtion->PartnerSid = FrsWcsDup(L"<unknown>");
|
|
}
|
|
Cxtion->PartnerPrincName = FrsWcsDup(Node->PrincName);
|
|
Cxtion->PartSrvName = FrsWcsDup(Node->PrincName);
|
|
|
|
//
|
|
// Use the schedule on the cxtion object if provided.
|
|
// Otherwise it will default to the schedule on the replica struct
|
|
// that was set above.
|
|
//
|
|
if (Node->Schedule) {
|
|
Cxtion->Schedule = FrsAlloc(Node->ScheduleLength);
|
|
CopyMemory(Cxtion->Schedule, Node->Schedule, Node->ScheduleLength);
|
|
}
|
|
//
|
|
// Treat the schedule as a trigger schedule if the partner
|
|
// is in another site, if this is a sysvol, and if the node
|
|
// has a schedule.
|
|
//
|
|
// A missing schedule means, "always on" for both
|
|
// stop/start and trigger schedules.
|
|
//
|
|
if (IsSysvol && !Node->SameSite && Node->Schedule) {
|
|
SetCxtionFlag(Cxtion, CXTION_FLAGS_TRIGGER_SCHEDULE);
|
|
}
|
|
|
|
SetCxtionState(Cxtion, CxtionStateUnjoined);
|
|
GTabInsertEntry(Replica->Cxtions, Cxtion, Cxtion->Name->Guid, NULL);
|
|
|
|
//
|
|
// Copy over the value of options attribute of the connection object.
|
|
//
|
|
Cxtion->Options = Node->CxtionOptions;
|
|
Cxtion->Priority = FRSCONN_GET_PRIORITY(Cxtion->Options);
|
|
}
|
|
|
|
|
|
//
|
|
// Merge the replica with the active replicas
|
|
//
|
|
RcsMergeReplicaFromDs(Replica);
|
|
} } } }
|
|
|
|
RcsEndMergeWithDs();
|
|
|
|
//
|
|
// The above code is only executed when the DS changes. This should
|
|
// be an infrequent occurance. Any code we loaded to process the merge
|
|
// can now be discarded without undue impact on active replication.
|
|
//
|
|
SetProcessWorkingSetSize(ProcessHandle, (SIZE_T)-1, (SIZE_T)-1);
|
|
}
|
|
|
|
|
|
VOID
|
|
FrsDsPollDs(
|
|
VOID
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
New way to get the current configuration from the DS and merge it with
|
|
the active replicas.
|
|
|
|
Part of NewDs poll APIs.
|
|
|
|
Arguments:
|
|
None.
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsPollDs:"
|
|
BOOL RefetchComputer;
|
|
DWORD WStatus = ERROR_SUCCESS;
|
|
PCONFIG_NODE Services = NULL;
|
|
PCONFIG_NODE Computer = NULL;
|
|
PVOID Key = NULL;
|
|
PGEN_ENTRY Entry = NULL;
|
|
|
|
//
|
|
// Increment the DS Polls Counter
|
|
//
|
|
PM_INC_CTR_SERVICE(PMTotalInst, DSPolls, 1);
|
|
|
|
//
|
|
// Empty the VolSerialNumberToDriveTable before every poll so we have
|
|
// fresh information every time. The table is built as needed.
|
|
//
|
|
if (VolSerialNumberToDriveTable != NULL) {
|
|
GTabEmptyTable(VolSerialNumberToDriveTable, FrsFree);
|
|
}
|
|
|
|
#if DBG
|
|
//
|
|
// For test purposes, you can run without a DS
|
|
//
|
|
if (NoDs) {
|
|
//
|
|
// kick off the rest of the service
|
|
//
|
|
MainInit();
|
|
if (!MainInitHasRun) {
|
|
FRS_ASSERT(MainInitHasRun == TRUE);
|
|
}
|
|
//
|
|
// Use the hardwired config
|
|
//
|
|
if (IniFileName) {
|
|
DPRINT(0, ":DS: Hard wired config from ini file.\n");
|
|
FrsDsUseHardWired(LoadedWired);
|
|
} else {
|
|
DPRINT(0, ":DS: David's hard wired config.\n");
|
|
//
|
|
// Complete config
|
|
//
|
|
FrsDsUseHardWired(DavidWired);
|
|
#if 0
|
|
Sleep(60 * 1000);
|
|
|
|
//
|
|
// Take out the server 2 (E:)
|
|
//
|
|
FrsDsUseHardWired(DavidWired2);
|
|
Sleep(60 * 1000);
|
|
|
|
//
|
|
// Put back in E and but take out all cxtions
|
|
//
|
|
//FrsDsUseHardWired();
|
|
//Sleep(5 * 1000);
|
|
|
|
//
|
|
// Put everything back in
|
|
//
|
|
FrsDsUseHardWired(DavidWired);
|
|
Sleep(60 * 1000);
|
|
#endif
|
|
|
|
//
|
|
// Repeat in 30 seconds
|
|
//
|
|
DsPollingShortInterval = 30 * 1000;
|
|
DsPollingLongInterval = 30 * 1000;
|
|
DsPollingInterval = 30 * 1000;
|
|
}
|
|
|
|
//
|
|
// Periodically check the local resources like disk space etc.
|
|
//
|
|
FrsCheckLocalResources();
|
|
|
|
return;
|
|
}
|
|
#endif DBG
|
|
|
|
//
|
|
// Backup/Restore
|
|
//
|
|
WStatus = FrsProcessBackupRestore();
|
|
if (!WIN_SUCCESS(WStatus)) {
|
|
goto CLEANUP;
|
|
}
|
|
|
|
//
|
|
// Open and bind an ldap connection to the DS
|
|
//
|
|
if (!FrsDsOpenDs()) {
|
|
if (DsIsShuttingDown) {
|
|
goto CLEANUP;
|
|
}
|
|
DPRINT(4, ":DS: Wait 5 seconds and retry DS open.\n");
|
|
WaitForSingleObject(ShutDownEvent, 5 * 1000);
|
|
if (!FrsDsOpenDs()) {
|
|
if (DsIsShuttingDown) {
|
|
goto CLEANUP;
|
|
}
|
|
DPRINT(4, ":DS: Wait 30 seconds and retry DS open.\n");
|
|
WaitForSingleObject(ShutDownEvent, 30 * 1000);
|
|
if (!FrsDsOpenDs()) {
|
|
if (DsIsShuttingDown) {
|
|
goto CLEANUP;
|
|
}
|
|
DPRINT(4, ":DS: Wait 180 seconds and retry DS open.\n");
|
|
WaitForSingleObject(ShutDownEvent, 3 * 60 * 1000);
|
|
if (!FrsDsOpenDs()) {
|
|
//
|
|
// Add to the poll summary event log.
|
|
//
|
|
FrsDsAddToPollSummary(IDS_POLL_SUM_DSBIND_FAIL);
|
|
|
|
goto CLEANUP;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Keep a running checksum of the change usns for this polling cycle
|
|
// Ignore configurations whose checksum is not the same for two
|
|
// polling intervals (DS is in flux).
|
|
//
|
|
ThisChange = 0;
|
|
NextChange = 0;
|
|
|
|
//
|
|
// User side of the configuration. This function will build two table of subscribers.
|
|
// SubscribersByRootPath and SubscribersByMemberRef. It will resolve any duplicate
|
|
// conflicts.
|
|
//
|
|
//
|
|
// Initialize the PartnerComputerTable.
|
|
//
|
|
if (PartnerComputerTable != NULL) {
|
|
//
|
|
// Members of the PartnerComputerTable need to be freed seperately
|
|
// as they are not part of the tree. So call FrsFreeType for
|
|
// each node.
|
|
//
|
|
PartnerComputerTable = GTabFreeTable(PartnerComputerTable, FrsFreeType);
|
|
}
|
|
|
|
PartnerComputerTable = GTabAllocStringTable();
|
|
|
|
//
|
|
// Initialize the AllCxtionsTable.
|
|
//
|
|
if (AllCxtionsTable != NULL) {
|
|
AllCxtionsTable = GTabFreeTable(AllCxtionsTable, NULL);
|
|
}
|
|
|
|
AllCxtionsTable = GTabAllocStringAndBoolTable();
|
|
|
|
WStatus = FrsDsGetComputer(gLdap, &Computer);
|
|
if (!WIN_SUCCESS(WStatus)) {
|
|
//
|
|
// Add to the poll summary event log.
|
|
//
|
|
FrsDsAddToPollSummary(IDS_POLL_SUM_NO_COMPUTER);
|
|
|
|
goto CLEANUP;
|
|
}
|
|
|
|
if (!Computer) {
|
|
DPRINT(4, ":DS: NO COMPUTER OBJECT!\n");
|
|
//
|
|
// Add to the poll summary event log.
|
|
//
|
|
FrsDsAddToPollSummary(IDS_POLL_SUM_NO_COMPUTER);
|
|
|
|
}
|
|
|
|
//
|
|
// Register (once) our SPN using the global ds binding handle.
|
|
//
|
|
if (Computer) {
|
|
FrsDsRegisterSpn(gLdap, Computer);
|
|
}
|
|
|
|
//
|
|
// Create the sysvols, if any
|
|
//
|
|
if (IsADc && !DsCreateSysVolsHasRun) {
|
|
WStatus = FrsDsCreateSysVols(gLdap, ServicesDn, Computer, &RefetchComputer);
|
|
|
|
if (!WIN_SUCCESS(WStatus)) {
|
|
DPRINT1(4, ":DS: IGNORE Can't process sysvols; WStatus %s!\n", ErrLabelW32(WStatus));
|
|
WStatus = ERROR_SUCCESS;
|
|
} else if (RefetchComputer) {
|
|
//
|
|
// FrsDsCreateSysVols() may add/del objects from the user
|
|
// side of the configuration; refetch just in case.
|
|
//
|
|
ThisChange = 0;
|
|
NextChange = 0;
|
|
SubscriberTable = GTabFreeTable(SubscriberTable, NULL);
|
|
FrsDsFreeTree(Computer);
|
|
WStatus = FrsDsGetComputer(gLdap, &Computer);
|
|
if (!WIN_SUCCESS(WStatus)) {
|
|
goto CLEANUP;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Is there any possibility that a replica set exists or that
|
|
// an old replica set should be deleted?
|
|
//
|
|
if (!FrsDsDoesUserWantReplication(Computer)) {
|
|
//
|
|
// Nope, no new, existing, or deleted sets
|
|
//
|
|
DPRINT(4, ":DS: Nothing to do; don't start the rest of the system.\n");
|
|
|
|
//
|
|
// Add to the poll summary event log.
|
|
//
|
|
FrsDsAddToPollSummary(IDS_POLL_SUM_NO_REPLICASETS);
|
|
|
|
WStatus = ERROR_RETRY;
|
|
goto CLEANUP;
|
|
}
|
|
//
|
|
// kick off the rest of the service
|
|
//
|
|
MainInit();
|
|
|
|
if (!MainInitHasRun) {
|
|
FRS_ASSERT(MainInitHasRun == TRUE);
|
|
}
|
|
|
|
//
|
|
// Admin side of the configuration
|
|
//
|
|
WStatus = FrsDsGetServices(gLdap, Computer, &Services);
|
|
|
|
if (Services == NULL) {
|
|
goto CLEANUP;
|
|
}
|
|
|
|
//
|
|
// Increment the DS Polls with and without changes Counters
|
|
//
|
|
if ((LastChange == 0)|| (ThisChange != LastChange)) {
|
|
PM_INC_CTR_SERVICE(PMTotalInst, DSPollsWChanges, 1);
|
|
}
|
|
else {
|
|
PM_INC_CTR_SERVICE(PMTotalInst, DSPollsWOChanges, 1);
|
|
}
|
|
|
|
|
|
//
|
|
// Don't use the config if the DS is in flux unless
|
|
// this is the first successful polling cycle.
|
|
//
|
|
if (DsPollingInterval != DsPollingShortInterval &&
|
|
LastChange && ThisChange != LastChange) {
|
|
DPRINT(4, ":DS: Skipping noisy topology\n");
|
|
LastChange = ThisChange;
|
|
//
|
|
// Check for a stable DS configuration after a short interval
|
|
//
|
|
DsPollingInterval = DsPollingShortInterval;
|
|
goto CLEANUP;
|
|
} else {
|
|
LastChange = ThisChange;
|
|
}
|
|
|
|
//
|
|
// No reason to continue polling the DS quickly; we have all
|
|
// of the stable information currently in the DS.
|
|
//
|
|
// DsPollingInterval = DsPollingLongInterval;
|
|
|
|
//
|
|
// Don't process the same topology repeatedly
|
|
//
|
|
// NTRAID#23652-2000/03/29-sudarc (Perf - FRS merges the DS configuration
|
|
// with its interval DB everytime it polls.)
|
|
//
|
|
// *Note*: Disable ActiveChange for now; too unreliable and too
|
|
// many error conditions in replica.c, createdb.c, ...
|
|
// besides, configs are so small that cpu savings are minimal
|
|
// plus; rejoins not issued every startreplica()
|
|
//
|
|
ActiveChange = 0;
|
|
if (ActiveChange && NextChange == ActiveChange) {
|
|
DPRINT(4, ":DS: Skipping previously processed topology\n");
|
|
goto CLEANUP;
|
|
}
|
|
//
|
|
// *Note*: Inconsistencies detected below should reset ActiveChange
|
|
// to 0 if the inconsistencies may clear up with time and
|
|
// don't require a DS change to clear up or fix
|
|
//
|
|
ActiveChange = NextChange;
|
|
|
|
//
|
|
// Check for valid paths
|
|
//
|
|
FrsDsCheckServerPaths(Services);
|
|
|
|
//
|
|
// Create the server principal name for each cxtion
|
|
//
|
|
FrsDsCreatePartnerPrincName(Services);
|
|
|
|
//
|
|
// Check the schedules
|
|
//
|
|
FrsDsCheckSchedules(Services);
|
|
FrsDsCheckSchedules(Computer);
|
|
|
|
//
|
|
// Now comes the tricky part. The above checks were made without
|
|
// regard to a nodes consistency. Now is the time to propagate
|
|
// inconsistencies throughout the tree to avoid inconsistencies
|
|
// caused by inconsistencies. E.g., a valid cxtion with an
|
|
// inconsistent partner.
|
|
//
|
|
|
|
//
|
|
// Push the parent's inconsistent state to its children
|
|
//
|
|
FrsDsPushInConsistenciesDown(Services);
|
|
|
|
//
|
|
// Merge the new config with the active replicas
|
|
//
|
|
DPRINT(4, ":DS: Begin merging Ds with Db\n");
|
|
FrsDsMergeConfigWithReplicas(gLdap, Services);
|
|
DPRINT(4, ":DS: End merging Ds with Db\n");
|
|
|
|
//
|
|
// Periodically check the local resources like disk space etc.
|
|
//
|
|
FrsCheckLocalResources();
|
|
|
|
if(NeedNewPartnerTable) {
|
|
//
|
|
// Clear the flag
|
|
//
|
|
NeedNewPartnerTable = FALSE;
|
|
FrsDsCreateNewValidPartnerTableStruct();
|
|
}
|
|
|
|
FrsDsCleanupOldValidPartnerTableStructList();
|
|
|
|
CLEANUP:
|
|
//
|
|
// Free the tables that were pointing into the tree.
|
|
// This just frees the entries in the table not the nodes.
|
|
// but the nodes can not be freed before freeing the
|
|
// tables as the compare functions are needed while.
|
|
// emptying the table.
|
|
//
|
|
|
|
SubscriberTable = GTabFreeTable(SubscriberTable, NULL);
|
|
|
|
SetTable = GTabFreeTable(SetTable, NULL);
|
|
|
|
CxtionTable = GTabFreeTable(CxtionTable, NULL);
|
|
|
|
AllCxtionsTable = GTabFreeTable(AllCxtionsTable, NULL);
|
|
|
|
//
|
|
// Members of the PartnerComputerTable need to be freed seperately
|
|
// as they are not part of the tree. So call FrsFreeType for
|
|
// each node.
|
|
//
|
|
PartnerComputerTable = GTabFreeTable(PartnerComputerTable, FrsFreeType);
|
|
|
|
MemberTable = GTabFreeTable(MemberTable, NULL);
|
|
|
|
if (MemberSearchFilter != NULL) {
|
|
MemberSearchFilter = FrsFree(MemberSearchFilter);
|
|
}
|
|
|
|
//
|
|
// Free the incore resources of the config retrieved from the DS
|
|
//
|
|
FrsDsFreeTree(Services);
|
|
FrsDsFreeTree(Computer);
|
|
|
|
|
|
if (!WIN_SUCCESS(WStatus)) {
|
|
FrsDsCloseDs();
|
|
}
|
|
|
|
//
|
|
// If there were any errors or warnings generated during this poll then
|
|
// write the summary to the eventlog.
|
|
//
|
|
if ((DsPollSummaryBuf != NULL) && (DsPollSummaryBufLen > 0)) {
|
|
EPRINT2(EVENT_FRS_DS_POLL_ERROR_SUMMARY,
|
|
(IsADc) ? ComputerDnsName :
|
|
(DsDomainControllerName ? DsDomainControllerName : L"<null>"),
|
|
DsPollSummaryBuf);
|
|
|
|
DsPollSummaryBuf = FrsFree(DsPollSummaryBuf);
|
|
DsPollSummaryBufLen = 0;
|
|
DsPollSummaryMaxBufLen = 0;
|
|
}
|
|
}
|
|
|
|
|
|
DWORD
|
|
FrsDsSetDsPollingInterval(
|
|
IN ULONG UseShortInterval,
|
|
IN ULONG LongInterval,
|
|
IN ULONG ShortInterval
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Set the long and short polling intervals and kick of a new
|
|
polling cycle. If both intervals are set, then the new polling
|
|
cycle uses the short interval (short takes precedence over
|
|
long). A value of -1 sets the interval to its current value.
|
|
|
|
No new polling cycle is initiated if a polling cycle is in progress.
|
|
|
|
Arguments:
|
|
UseShortInterval - if non-zero, switch to short. Otherwise, long.
|
|
LongInterval - Long interval in minutes
|
|
ShortInterval - Short interval in minutes
|
|
|
|
Return Value:
|
|
Win32 Status
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsSetDsPollingInterval:"
|
|
DWORD WStatus;
|
|
|
|
DPRINT3(4, ":DS: Setting the polling intervals to %d/%d (use %s)\n",
|
|
LongInterval, ShortInterval, (UseShortInterval) ? "Short" : "Long");
|
|
//
|
|
// Don't change the polling intervals; simply kick off a new cycle
|
|
//
|
|
if (!LongInterval && !ShortInterval) {
|
|
DsPollingInterval = (UseShortInterval) ? DsPollingShortInterval :
|
|
DsPollingLongInterval;
|
|
SetEvent(DsPollEvent);
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
//
|
|
// ADJUST LONG INTERVAL
|
|
//
|
|
if (LongInterval) {
|
|
|
|
// FRS_CONFIG_SECTION\DS Polling Long Interval in Minutes
|
|
WStatus = CfgRegWriteDWord(FKC_DS_POLLING_LONG_INTERVAL,
|
|
NULL,
|
|
FRS_RKF_RANGE_SATURATE,
|
|
LongInterval);
|
|
CLEANUP_WS(4, ":DS: DS Polling Long Interval not written.", WStatus, RETURN);
|
|
|
|
//
|
|
// Adjust the long polling rate
|
|
//
|
|
DsPollingLongInterval = LongInterval * (60 * 1000);
|
|
}
|
|
//
|
|
// ADJUST SHORT INTERVAL
|
|
//
|
|
if (ShortInterval) {
|
|
//
|
|
// Sanity check
|
|
//
|
|
if (LongInterval && (ShortInterval > LongInterval)) {
|
|
ShortInterval = LongInterval;
|
|
}
|
|
|
|
// FRS_CONFIG_SECTION\DS Polling Short Interval in Minutes
|
|
WStatus = CfgRegWriteDWord(FKC_DS_POLLING_SHORT_INTERVAL,
|
|
NULL,
|
|
FRS_RKF_RANGE_SATURATE,
|
|
ShortInterval);
|
|
CLEANUP_WS(4, ":DS: DS Polling Short Interval not written.", WStatus, RETURN);
|
|
|
|
//
|
|
// Adjust the Short polling rate
|
|
//
|
|
DsPollingShortInterval = ShortInterval * (60 * 1000);
|
|
}
|
|
//
|
|
// Initiate a polling cycle
|
|
//
|
|
DsPollingInterval = (UseShortInterval) ? DsPollingShortInterval :
|
|
DsPollingLongInterval;
|
|
SetEvent(DsPollEvent);
|
|
|
|
return ERROR_SUCCESS;
|
|
|
|
RETURN:
|
|
return WStatus;
|
|
}
|
|
|
|
|
|
DWORD
|
|
FrsDsGetDsPollingInterval(
|
|
OUT ULONG *Interval,
|
|
OUT ULONG *LongInterval,
|
|
OUT ULONG *ShortInterval
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Return the current polling intervals.
|
|
|
|
Arguments:
|
|
Interval - Current interval in minutes
|
|
LongInterval - Long interval in minutes
|
|
ShortInterval - Short interval in minutes
|
|
|
|
Return Value:
|
|
Win32 Status
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsGetDsPollingInterval:"
|
|
|
|
*Interval = DsPollingInterval / (60 * 1000);
|
|
*LongInterval = DsPollingLongInterval / (60 * 1000);
|
|
*ShortInterval = DsPollingShortInterval / (60 * 1000);
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
|
|
#define DS_POLLING_MAX_SHORTS (8)
|
|
DWORD
|
|
FrsDsMainDsCs(
|
|
IN PVOID Ignored
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Entry point for a DS poller thread
|
|
|
|
Arguments:
|
|
Ignored
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsMainDsCs:"
|
|
DWORD WStatus;
|
|
DWORD DsPollingShorts = 0;
|
|
HANDLE WaitHandles[2];
|
|
|
|
DPRINT(0, ":DS: DsCs is starting.\n");
|
|
|
|
//
|
|
// DsPollingLongInterval
|
|
//
|
|
CfgRegReadDWord(FKC_DS_POLLING_LONG_INTERVAL, NULL, 0, &DsPollingLongInterval);
|
|
|
|
//
|
|
// Registry is specified in minutes; convert to milliseconds
|
|
//
|
|
DsPollingLongInterval *= (60 * 1000);
|
|
|
|
//
|
|
// DsPollingShortInterval
|
|
//
|
|
CfgRegReadDWord(FKC_DS_POLLING_SHORT_INTERVAL, NULL, 0, &DsPollingShortInterval);
|
|
|
|
//
|
|
// Registry is specified in minutes; convert to milliseconds
|
|
//
|
|
DsPollingShortInterval *= (60 * 1000);
|
|
|
|
|
|
DPRINT2(4, ":DS: DS long/short polling interval is %d/%d minutes\n",
|
|
(DsPollingLongInterval / 1000) / 60,
|
|
(DsPollingShortInterval / 1000) / 60);
|
|
|
|
DsPollingInterval = DsPollingShortInterval;
|
|
|
|
//
|
|
// Initialize the client side ldap search timeout value.
|
|
//
|
|
|
|
LdapTimeout.tv_sec = LdapSearchTimeoutInMinutes * 60;
|
|
|
|
//
|
|
// Handles to wait on
|
|
//
|
|
WaitHandles[0] = DsPollEvent;
|
|
WaitHandles[1] = ShutDownEvent;
|
|
|
|
//
|
|
// Set the registry keys and values necessary for the functioning of
|
|
// PERFMON and load the counter values into the registry
|
|
//
|
|
// Moved from main.c because this function invokes another exe that
|
|
// may cause frs to exceed its service startup time limit; resulting
|
|
// in incorrect "service cannot start" messages during intensive
|
|
// cpu activity (although frs does eventually start).
|
|
//
|
|
// NTRAID#70743-2000/03/29-sudarc (Retry initialization of perfmon registry keys
|
|
// if it fails during startup.)
|
|
//
|
|
DPRINT(0, "Init Perfmon registry keys (PmInitPerfmonRegistryKeys()).\n");
|
|
WStatus = PmInitPerfmonRegistryKeys();
|
|
|
|
DPRINT_WS(0, "ERROR - PmInitPerfmonRegistryKeys();", WStatus);
|
|
|
|
DPRINT(0, ":DS: FrsDs has started.\n");
|
|
|
|
try {
|
|
try {
|
|
//
|
|
// While the service is not shutting down
|
|
//
|
|
while (!FrsIsShuttingDown && !DsIsShuttingDown) {
|
|
//
|
|
// Reload registry parameters that can change while service is
|
|
// running.
|
|
//
|
|
DbgQueryDynamicConfigParams();
|
|
|
|
//
|
|
// What is this computer's role in the domain?
|
|
//
|
|
WStatus = FrsDsGetRole();
|
|
if (WIN_SUCCESS(WStatus) && !IsAMember) {
|
|
//
|
|
// Nothing to do
|
|
// BUT dcpromo may have started us so we
|
|
// must at least keep the service running.
|
|
//
|
|
// Perhaps we could die after running awhile
|
|
// if we still aren't a member?
|
|
//
|
|
// DPRINT(4, "Not a member, shutting down\n");
|
|
// FrsIsShuttingDown = TRUE;
|
|
// SetEvent(ShutDownEvent);
|
|
// break;
|
|
}
|
|
|
|
//
|
|
// Retrieve info from the DS and merge it with the
|
|
// acitve replicas
|
|
//
|
|
DPRINT(4, ":DS: Polling the DS\n");
|
|
if (IsAMember) {
|
|
FrsDsPollDs();
|
|
}
|
|
|
|
//
|
|
// No reason to hold memory if there isn't anything
|
|
// to do but wait for another ds polling cycle.
|
|
//
|
|
if (!MainInitHasRun) {
|
|
SetProcessWorkingSetSize(ProcessHandle, (SIZE_T)-1, (SIZE_T)-1);
|
|
}
|
|
//
|
|
// Poll often if a dc
|
|
//
|
|
if (IsADc) {
|
|
DsPollingInterval = DsPollingShortInterval;
|
|
}
|
|
|
|
//
|
|
// Wait for a bit or until the service is shutdown
|
|
//
|
|
DPRINT1(4, ":DS: Poll the DS in %d minutes\n",
|
|
DsPollingInterval / (60 * 1000));
|
|
ResetEvent(DsPollEvent);
|
|
if (!FrsIsShuttingDown && !DsIsShuttingDown) {
|
|
WaitForMultipleObjects(2, WaitHandles, FALSE, DsPollingInterval);
|
|
}
|
|
//
|
|
// The long interval can be reset to insure a high
|
|
// poll rate. The short interval is temporary; go
|
|
// back to long intervals after a few short intervals.
|
|
//
|
|
if (DsPollingInterval == DsPollingShortInterval) {
|
|
if (++DsPollingShorts > DS_POLLING_MAX_SHORTS) {
|
|
DsPollingInterval = DsPollingLongInterval;
|
|
DsPollingShorts = 0;
|
|
}
|
|
} else {
|
|
DsPollingShorts = 0;
|
|
}
|
|
|
|
}
|
|
} except (EXCEPTION_EXECUTE_HANDLER) {
|
|
GET_EXCEPTION_CODE(WStatus);
|
|
DPRINT_WS(0, ":DS: DsCs exception.", WStatus);
|
|
}
|
|
} finally {
|
|
//
|
|
// Shutdown
|
|
//
|
|
if (WIN_SUCCESS(WStatus)) {
|
|
if (AbnormalTermination()) {
|
|
WStatus = ERROR_OPERATION_ABORTED;
|
|
}
|
|
}
|
|
|
|
DPRINT_WS(0, ":DS: DsCs finally.", WStatus);
|
|
FrsDsCloseDs();
|
|
SetEvent(DsShutDownComplete);
|
|
DPRINT(0, ":DS: DsCs is exiting.\n");
|
|
}
|
|
return WStatus;
|
|
}
|
|
|
|
|
|
VOID
|
|
FrsDsInitialize(
|
|
VOID
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Initialize the thread that polls the DS
|
|
|
|
Arguments:
|
|
None.
|
|
|
|
Return Value:
|
|
TRUE - DS Poller has started
|
|
FALSE - Can't poll the DS
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "FrsDsInitialize:"
|
|
|
|
//
|
|
// Synchronizes with sysvol seeding
|
|
//
|
|
INITIALIZE_CRITICAL_SECTION(&MergingReplicasWithDs);
|
|
|
|
//
|
|
// Kick off the thread that polls the DS
|
|
//
|
|
ThSupCreateThread(L"FrsDs", NULL, FrsDsMainDsCs, ThSupExitWithTombstone);
|
|
}
|