/*++ Copyright (c) 1997 Microsoft Corporation Module Name: domain.c Abstract: This code implements a set of linked list data structures used to resolve domain name lookups. It achieves a similar structure to memdb but provides the ability to move an item from list to list efficiently. Author: Jim Schmidt (jimschm) 18-Jun-1997 Revision History: jimschm 23-Sep-1998 Fixed trusted domains jimschm 17-Feb-1998 Updated share security for NT 5 changes --*/ #include "pch.h" #include "migmainp.h" #include "security.h" #define DBG_ACCTLIST "Accounts" PACCT_DOMAINS g_FirstDomain; POOLHANDLE g_DomainPool; INT g_RetryCount; BOOL pAddAllLogonDomains ( IN PCTSTR DCName OPTIONAL ); VOID InitAccountList ( VOID ) /*++ Routine Description: Initializer for the account list that is used during account lookup. This memory is freed after all accounts are found. Arguments: none Return value: none --*/ { g_FirstDomain = NULL; g_DomainPool = PoolMemInitNamedPool ("Domain List"); PoolMemDisableTracking (g_DomainPool); } VOID TerminateAccountList ( VOID ) /*++ Routine Description: Termination routine for the account lookup code. Arguments: none Return value: none --*/ { g_FirstDomain = NULL; PoolMemDestroyPool (g_DomainPool); } PCWSTR pReturnDomainFromEnum ( IN PACCT_ENUM EnumPtr ) /*++ Routine Description: Common code for ListFirstDomain and ListNextDomain. Implements return parameter check. Arguments: EnumPtr - The current enumeration pointer supplied by ListFirstDomain and ListNextDomain. Return value: A pointer to the domain name allocated in our private pool, or NULL if no more domains remain. --*/ { if (!EnumPtr->DomainPtr) { return NULL; } return EnumPtr->DomainPtr->Domain; } /*++ Routine Description: ListFirstDomain and ListNextDomain are enumerators that list trusted domains added by BuildDomainList. They return a pointer to the domain name (managed by a private pool). The enumeration structure can be passed to all other functions that take a DomainEnumPtr as a parameter. Arguments: DomainEnumPtr - A pointer to a caller-allocated ACCT_ENUM structure, typically allocated on the stack. It does not need to be initialized. Return value: A pointer to the domain name, or NULL if no more domains exist in the list. --*/ PCWSTR ListFirstDomain ( OUT PACCT_ENUM DomainEnumPtr ) { DomainEnumPtr->DomainPtr = g_FirstDomain; return pReturnDomainFromEnum (DomainEnumPtr); } PCWSTR ListNextDomain ( IN OUT PACCT_ENUM DomainEnumPtr ) { DomainEnumPtr->DomainPtr = DomainEnumPtr->DomainPtr->Next; return pReturnDomainFromEnum (DomainEnumPtr); } BOOL FindDomainInList ( OUT PACCT_ENUM DomainEnumPtr, IN PCWSTR DomainToFind ) /*++ Routine Description: FindDomainInList searches (sequentially) through all trusted domains for the specified domain. If found, enumeration stops and TRUE is returned. If not found, the enumeration pointer is invalid and FALSE is returned. Use this function to obtain an enumeration pointer that is used in subsequent calls to the user list. The search is case-insensitive. Arguments: DomainEnumPtr - An uninitialized, caller-allocated ACCT_ENUM structure, typically allocated on the stack. When a search match is found, this structure can be used with any other function that requires a DomainEnumPtr. DomainToFind - The name of the domain to find. Return value: TRUE if a match was found (and DomainEnumPtr is valid), or FALSE if a match was not found (and DomainEnumPtr is not valid). --*/ { PCWSTR DomainName; DomainName = ListFirstDomain (DomainEnumPtr); while (DomainName) { if (StringIMatchW (DomainName, DomainToFind)) { return TRUE; } DomainName = ListNextDomain (DomainEnumPtr); } return FALSE; } PCWSTR pReturnUserFromEnum ( IN PACCT_ENUM UserEnumPtr ) /*++ Routine Description: Implements common code for ListFirstUserInDomain and ListNextUserInDomain. Performs return parameter validation. Arguments: UserEnumPtr - The current enum pointer supplied by ListFirstUserInDomain or ListNextUserInDomain. Return value: The name of the user being enumerated (not domain-qualified), or NULL if no more users exist in the domain. --*/ { if (UserEnumPtr->UserPtr) { return UserEnumPtr->UserPtr->User; } return NULL; } /*++ Routine Description: ListFirstUserInDomain and ListNextUserInDomain enumerate all users in the specified domain. Arguments: DomainEnumPtr - The caller-allocated ACCT_ENUM structure that has been initialized by a domain lookup function above. UserEnumPtr - Used to keep track of the current user. May be the same pointer as DomainEnumPtr. Return value: The name of the user being enumerated (not domain-qualified), or NULL if no more users exist in the domain. --*/ PCWSTR ListFirstUserInDomain ( IN PACCT_ENUM DomainEnumPtr, OUT PACCT_ENUM UserEnumPtr ) { UserEnumPtr->UserPtr = DomainEnumPtr->DomainPtr->FirstUserPtr; return pReturnUserFromEnum (UserEnumPtr); } PCWSTR ListNextUserInDomain ( IN OUT PACCT_ENUM UserEnumPtr ) { if (UserEnumPtr->UserPtr) { UserEnumPtr->UserPtr = UserEnumPtr->UserPtr->Next; } else { UserEnumPtr->UserPtr = UserEnumPtr->DomainPtr->FirstUserPtr; } return pReturnUserFromEnum (UserEnumPtr); } BOOL IsTrustedDomain ( IN PACCT_ENUM DomainEnumPtr ) /*++ Routine Description: Returns TRUE if the domain is an officially trusted domain, or FALSE if the domain is an artificially added domain. The account lookup code adds artificial domains to track the state of users. For example, the domain \unknown is used to track users who need auto-lookup. The domain \failed is used to track users who aren't in the domain they were expected to be in. All artifical domains start with a backslash. Arguments: DomainEnumPtr - Specifies the domain to examine. This structure must be the return of a domain enumeration function above. Return value: TRUE - The domain is a trusted domain FALSE - The domain is not really a domain but is instead an artifically added domain --*/ { PCWSTR Domain; Domain = DomainEnumPtr->DomainPtr->Domain; // // Test domain name to see if it is one of the reserved names // if (*Domain == TEXT('\\')) { return FALSE; } return TRUE; } BOOL FindUserInDomain ( IN PACCT_ENUM DomainEnumPtr, OUT PACCT_ENUM UserEnumPtr, IN PCWSTR UserToFind ) /*++ Routine Description: Uses ListFirstUserInDomain and ListNextUserInDomain to sequentially search for a user. The search is case-insensitive. Arguments: DomainEnumPtr - Specifies the domain to search. This structure must be the return of a domain enumeration function above. UserEnumPtr - Receives the results of the search if a user match is found. Can be the same as DomainEnumPtr. UserToFind - Specifies the name of the user to find (not domain-qualified). Return value: TRUE - A match was found and UserEnumPtr is valid FALSE - A match was not found and UserEnumPtr is not valid --*/ { PCWSTR UserName; UserName = ListFirstUserInDomain (DomainEnumPtr, UserEnumPtr); while (UserName) { if (StringIMatchW (UserName, UserToFind)) { return TRUE; } UserName = ListNextUserInDomain (UserEnumPtr); } return FALSE; } INT CountUsersInDomain ( IN PACCT_ENUM DomainEnumPtr ) /*++ Routine Description: Returns the number of users in our domain enumeration structure. Arguments: DomainEnumPtr - Specifies the domain to search. This structure must be the return of a domain enumeration function above. Return value: The count of the users in the domain. --*/ { return DomainEnumPtr->DomainPtr->UserCount; } VOID AddDomainToList ( IN PCWSTR Domain ) /*++ Routine Description: Allows domains to be added to the list of trusted domains. Normally, BuildDomainList is the only caller to this API, because it is the one who knows what the trusted domains are. However, artificial domains are added in other places through this call. Arguments: Domain - Specifies the name of the domain to add Return value: none --*/ { PACCT_DOMAINS NewDomain; DEBUGMSG ((DBG_ACCTLIST, "Adding domain '%s' to domain list", Domain)); NewDomain = (PACCT_DOMAINS) PoolMemGetAlignedMemory ( g_DomainPool, sizeof (ACCT_DOMAINS) ); ZeroMemory (NewDomain, sizeof (ACCT_DOMAINS)); NewDomain->Next = g_FirstDomain; g_FirstDomain = NewDomain; NewDomain->Domain = PoolMemDuplicateString (g_DomainPool, Domain); } VOID pLinkUser ( IN PACCT_USERS UserPtr, IN PACCT_DOMAINS DomainPtr ) /*++ Routine Description: The memory structures in this file are linked-list based. There is a linked-list of domains, and for each domain there is a linked- list of users. Each user has a linked list of possible domains. The linked lists are designed to be changed while enumerations are in progress. This function performs the simple link operation for the user list. Arguments: UserPtr - A pointer to the internally maintained ACCT_USERS structure. DomainPtr - Specifies the domain in which UserPtr is linked to. Return value: none --*/ { UserPtr->Next = DomainPtr->FirstUserPtr; if (UserPtr->Next) { UserPtr->Next->Prev = UserPtr; } DomainPtr->FirstUserPtr = UserPtr; UserPtr->DomainPtr = DomainPtr; DomainPtr->UserCount++; } BOOL AddUserToDomainList ( IN PCWSTR User, IN PCWSTR Domain ) /*++ Routine Description: This function searches for the domain name specified and adds the user to the user list for that domain. If the domain cannot be found, the function fails. Arguments: User - Specifies the name of the user to add Domain - Specifies the name of the domain that the user is added to Return value: TRUE if the user was added successfully, or FALSE if the domain is not a trusted domain. --*/ { ACCT_ENUM e; PACCT_DOMAINS DomainPtr; PACCT_USERS NewUser; // // Find Domain (it must exist in the list) // if (!FindDomainInList (&e, Domain)) { return FALSE; } DomainPtr = e.DomainPtr; // // Allocate structure for the user // NewUser = (PACCT_USERS) PoolMemGetAlignedMemory ( g_DomainPool, sizeof (ACCT_USERS) ); ZeroMemory (NewUser, sizeof (ACCT_USERS)); pLinkUser (NewUser, DomainPtr); NewUser->User = PoolMemDuplicateString (g_DomainPool, User); return TRUE; } VOID pDelinkUser ( IN PACCT_USERS UserPtr ) /*++ Routine Description: The memory structures in this file are linked-list based. There is a linked-list of domains, and for each domain there is a linked- list of users. Each user has a linked list of possible domains. The linked lists are designed to be changed while enumerations are in progress. This function performs the simple delink operation for the user list. Arguments: UserPtr - A pointer to the internally maintained ACCT_USERS structure. Return value: none --*/ { if (UserPtr->Prev) { UserPtr->Prev->Next = UserPtr->Next; } else { UserPtr->DomainPtr->FirstUserPtr = UserPtr->Next; } if (UserPtr->Next) { UserPtr->Next->Prev = UserPtr->Prev; } UserPtr->DomainPtr->UserCount--; } VOID DeleteUserFromDomainList ( IN PACCT_ENUM UserEnumPtr ) /*++ Routine Description: Performs a delete operation for a user in a domain's user list. The memory for this user is not freed right away, because doing so may cause enumeration positions to become invalid. Instead, the links are adjusted to skip over this user. Memory is freed at termination. Arguments: UserEnumPtr - A pointer to the user to delete, obtained by calling a user enumeration or user search function that returns UserEnumPtr as an OUT. Return value: none --*/ { // // Don't actually delete, just delink. This allows all in-progress // enumerations to continue working. // pDelinkUser (UserEnumPtr->UserPtr); } BOOL MoveUserToNewDomain ( IN OUT PACCT_ENUM UserEnumPtr, IN PCWSTR NewDomain ) /*++ Routine Description: Moves a user from one domain to another by adjusting links only. The current enumeration pointer is adjusted to point to the previous user so enumeration can continue. This function may change the behavoir other enumerations that are pointing to this user, so be careful. It will never break an enumeration though. Arguments: UserEnumPtr - A pointer to the user to move, obtained by calling a user enumeration or user search function that returns UserEnumPtr as an OUT. NewDomain - The name of the new domain to move the user to. Return value: TRUE if NewDomain is a trusted domain, or FALSE if it is not. The user can only be moved to domains in the trust list. --*/ { ACCT_ENUM e; PACCT_DOMAINS DomainPtr; PACCT_DOMAINS OrgDomainPtr; PACCT_USERS PrevUser; // // Find NewDomain (it must exist in the list) // if (!FindDomainInList (&e, NewDomain)) { return FALSE; } DomainPtr = e.DomainPtr; OrgDomainPtr = UserEnumPtr->UserPtr->DomainPtr; // // Remove user from original domain // PrevUser = UserEnumPtr->UserPtr->Prev; pDelinkUser (UserEnumPtr->UserPtr); // // Add user to new domain // pLinkUser (UserEnumPtr->UserPtr, DomainPtr); if (!PrevUser) { UserEnumPtr->DomainPtr = OrgDomainPtr; } UserEnumPtr->UserPtr = PrevUser; return TRUE; } VOID UserMayBeInDomain ( IN PACCT_ENUM UserEnumPtr, IN PACCT_ENUM DomainEnumPtr ) /*++ Routine Description: Provides the caller with a way to flag a domain as a possible domain holding the account. During search, all trusted domains are queried, and because an account can be in more than one, a list of possible domains is developed. If the final list of possible domains has only one entry, that domain is used for the user. Otherwise, a dialog is presented, allowing the installer to choose an action to take for the user. The action can be to retry, make a local account, or select one of the possible domains. Arguments: UserEnumPtr - Specifies the user that may be in a domain DomainEnumPtr - Specifies the domain that the user may be in Return value: none --*/ { PACCT_POSSIBLE_DOMAINS PossibleDomainPtr; PossibleDomainPtr = (PACCT_POSSIBLE_DOMAINS) PoolMemGetAlignedMemory ( g_DomainPool, sizeof (ACCT_POSSIBLE_DOMAINS) ); PossibleDomainPtr->DomainPtr = DomainEnumPtr->DomainPtr; PossibleDomainPtr->Next = UserEnumPtr->UserPtr->FirstPossibleDomain; UserEnumPtr->UserPtr->FirstPossibleDomain = PossibleDomainPtr; UserEnumPtr->UserPtr->PossibleDomains++; } VOID ClearPossibleDomains ( IN PACCT_ENUM UserEnumPtr ) /*++ Routine Description: Provides the caller with a way to reset the possible domain list. This is required if the installer chose to retry the search. Arguments: UserEnumPtr - Specifies the user to reset Return value: none --*/ { PACCT_POSSIBLE_DOMAINS This, Next; This = UserEnumPtr->UserPtr->FirstPossibleDomain; while (This) { Next = This->Next; PoolMemReleaseMemory (g_DomainPool, This); This = Next; } UserEnumPtr->UserPtr->FirstPossibleDomain = 0; UserEnumPtr->UserPtr->PossibleDomains = 0; } PCWSTR pReturnPossibleDomainFromEnum ( IN PACCT_ENUM EnumPtr ) /*++ Routine Description: Common code for ListFirstPossibleDomain and ListNextPossibleDomain. Implements return parameter checking. Arguments: EnumPtr - The current enumeration pointer as supplied by ListFirstPossibleDomain or ListNextPossibleDomain. Return value: The name of the domain enumerated, or NULL if no more possible domains exist. (A possible domain is one that a user may or may not be in, but a matching account was found in the domain.) --*/ { if (EnumPtr->PossibleDomainPtr) { EnumPtr->DomainPtr = EnumPtr->PossibleDomainPtr->DomainPtr; return EnumPtr->DomainPtr->Domain; } return NULL; } /*++ Routine Description: ListFirstPossibleDomain and ListNextPossibleDomain are enumerators that list domains added by UserMayBeInDomain. They return a pointer to the domain name (managed by a private pool). A possible domain list is maintained to allow the installer to choose between multiple domains when a user has an account on more than one domain. Arguments: UserEnumPtr - A pointer to a caller-allocated ACCT_ENUM structure, typically allocated on the stack. It must be initialized by a user enum or user search function. PossibleDomainEnumPtr - Maintains the state of the possible domain enumeration. It can be the same pointer as UserEnumPtr. Return value: A pointer to the possible domain name, or NULL if no more domains exist in the list. --*/ PCWSTR ListFirstPossibleDomain ( IN PACCT_ENUM UserEnumPtr, OUT PACCT_ENUM PossibleDomainEnumPtr ) { PossibleDomainEnumPtr->PossibleDomainPtr = UserEnumPtr->UserPtr->FirstPossibleDomain; return pReturnPossibleDomainFromEnum (PossibleDomainEnumPtr); } PCWSTR ListNextPossibleDomain ( IN OUT PACCT_ENUM PossibleDomainEnumPtr ) { PossibleDomainEnumPtr->PossibleDomainPtr = PossibleDomainEnumPtr-> PossibleDomainPtr->Next; return pReturnPossibleDomainFromEnum (PossibleDomainEnumPtr); } INT CountPossibleDomains ( IN PACCT_ENUM UserEnumPtr ) /*++ Routine Description: Returns the number of possible domains in a user. Arguments: UserEnumPtr - Specifies the user to examine. This structure must be the return of a user enumeration or user search function above. Return value: The count of the possible domains for a user. --*/ { return UserEnumPtr->UserPtr->PossibleDomains; } NET_API_STATUS pGetDcNameAllowingRetry ( IN PCWSTR DomainName, OUT PWSTR ServerName, IN BOOL ForceNewServer ) /*++ Routine Description: Implements NetGetDCName, but provides a retry capability. Arguments: DomainName - Specifies the domain to obtain the server name for ServerName - Specifies a buffer to receive the name of the server ForceNewServer - Specifies TRUE if the function should obtain a new server for the domain. Specifies FALSE if the function should use any existing connection if available. Return value: Win32 error code indicating outcome --*/ { NET_API_STATUS nas; UINT ShortCircuitRetries = 1; //PCWSTR ArgArray[1]; do { nas = GetAnyDC ( DomainName, ServerName, ForceNewServer ); if (nas != NO_ERROR) { // // Short-circuited, so user isn't bothered. The alternate behavior // is to prompt the user for retry when any domain is down. (See // RetryMessageBox code below.) // ShortCircuitRetries--; if (!ShortCircuitRetries) { DEBUGMSG ((DBG_WARNING, "Unable to connect to domain %s", DomainName)); break; } #if 0 ArgArray[0] = DomainName; if (!RetryMessageBox (MSG_GETPRIMARYDC_RETRY, ArgArray)) { DEBUGMSG ((DBG_WARNING, "Unable to connect to domain %s; user chose to cancel", DomainName)); break; } #endif ForceNewServer = TRUE; } } while (nas != NO_ERROR); return nas; } VOID pDisableDomain ( IN OUT PACCT_DOMAINS DomainPtr ) /*++ Routine Description: Disable the specified domain. Arguments: DomainPtr - A pointer to the internally maintained ACCT_DOMAINS structure. This structure is updated to contain an empty server name upon return. Return value: None --*/ { g_DomainProblem = TRUE; if (DomainPtr->Server && *DomainPtr->Server) { PoolMemReleaseMemory (g_DomainPool, (PVOID) DomainPtr->Server); } DomainPtr->Server = S_EMPTY; } NET_API_STATUS pNetUseAddAllowingRetry ( IN OUT PACCT_DOMAINS DomainPtr ) /*++ Routine Description: Implements NetUseAdd, but provides a retry capability. Arguments: DomainPtr - A pointer to the internally maintained ACCT_DOMAINS structure. This structure is updated to contain the server name upon success. Return value: Win32 error code indicating outcome --*/ { NET_API_STATUS rc; DWORD DontCare; PCWSTR ReplacementName; NET_API_STATUS nas; USE_INFO_2 ui2; WCHAR LocalShareName[72]; WCHAR NewServerBuf[MAX_SERVER_NAMEW]; ReplacementName = NULL; do { // // Initialize USE_INFO_2 structure // ZeroMemory (&ui2, sizeof (ui2)); StringCopyW (LocalShareName, ReplacementName ? ReplacementName : DomainPtr->Server); StringCatW (LocalShareName, L"\\IPC$"); ui2.ui2_remote = LocalShareName; ui2.ui2_asg_type = USE_IPC; rc = NetUseAdd (NULL, 2, (PBYTE) &ui2, &DontCare); // // If NetUseAdd fails, give the user a chance to retry with a different server // if (rc != NO_ERROR) { PCWSTR ArgArray[2]; DEBUGMSG (( DBG_WARNING, "User was alerted to problem establishing nul session to %s (domain %s), rc=%u", DomainPtr->Server, DomainPtr->Domain, rc )); ArgArray[0] = DomainPtr->Server; ArgArray[1] = DomainPtr->Domain; if (!RetryMessageBox (MSG_NULSESSION_RETRY, ArgArray)) { DEBUGMSG ((DBG_WARNING, "Unable to connect to domain %s; user chose to cancel", DomainPtr->Domain)); pDisableDomain (DomainPtr); ReplacementName = NULL; break; } if (ReplacementName) { ReplacementName = NULL; } // // Get a new server because current server is not responding. If we fail to // obtain a server name, allow the user to try again. // do { nas = GetAnyDC (DomainPtr->Domain, NewServerBuf, TRUE); if (nas != NO_ERROR) { PCWSTR ArgArray[1]; DEBUGMSG ((DBG_WARNING, "User was alerted to problem locating server for domain %s", DomainPtr->Domain)); ArgArray[0] = DomainPtr->Domain; if (!RetryMessageBox (MSG_GETANYDC_RETRY, ArgArray)) { DEBUGMSG ((DBG_WARNING, "Unable to find a server for domain %s; user chose to cancel", DomainPtr->Domain)); // Disable domain and return an error pDisableDomain (DomainPtr); ReplacementName = NULL; break; } } else { ReplacementName = NewServerBuf; } } while (nas != NO_ERROR); } } while (rc != NO_ERROR); // // If ReplacementName is not NULL, we need to free the buffer. Also, if the // NetUseAdd call succeeded, we now have to use another server to query the // domain. // if (ReplacementName) { if (rc == NO_ERROR) { if (DomainPtr->Server && *DomainPtr->Server) { PoolMemReleaseMemory (g_DomainPool, (PVOID) DomainPtr->Server); } DomainPtr->Server = PoolMemDuplicateString (g_DomainPool, ReplacementName); } } return rc; } PCWSTR pLsaStringToCString ( IN PLSA_UNICODE_STRING UnicodeString, OUT PWSTR Buffer ) /*++ Routine Description: A safe string extraction that takes the string in UnicodeString and copies it to Buffer. The caller must ensure Buffer is big enough. Arguments: UnicodeString - Specifies the source string to convert Buffer - Specifies the buffer that receives the converted string Return value: The Buffer pointer --*/ { StringCopyABW ( Buffer, UnicodeString->Buffer, (PWSTR) ((PBYTE) UnicodeString->Buffer + UnicodeString->Length) ); return Buffer; } BOOL BuildDomainList( VOID ) /*++ Routine Description: Creates a trusted domain list by: 1. Determining the computer domain in which the machine participates in 2. Opening the DC's policy 3. Querying the trust list 4. Adding it to our internal domain list (ACCT_DOMAINS) This function will fail if the machine does not participate in a domain, or if the domain controller cannot be contacted. Arguments: none Return value: TRUE if the trust list was completely built, or FALSE if an error occurred and the trust list is incomplete and is probably empty. GetLastError provides the failure code. --*/ { LSA_HANDLE PolicyHandle; BOOL DomainControllerFlag = FALSE; NTSTATUS Status; NET_API_STATUS nas = NO_ERROR; BOOL b = FALSE; WCHAR PrimaryDomainName[MAX_SERVER_NAMEW]; PPOLICY_PRIMARY_DOMAIN_INFO PrimaryDomain; WCHAR ServerName[MAX_SERVER_NAMEW]; #if DOMAINCONTROLLER PPOLICY_ACCOUNT_DOMAIN_INFO AccountDomain; #endif if (!IsMemberOfDomain()) { DEBUGMSG ((DBG_VERBOSE, "Workstation does not participate in a domain.")); return FALSE; } // // Add special domains used for management of user state // // users whos domain is not known AddDomainToList (S_UNKNOWN_DOMAIN); // users whos domain was known but the account doesn't exist AddDomainToList (S_FAILED_DOMAIN); // // Open the policy on this machine // Status = OpenPolicy ( NULL, POLICY_VIEW_LOCAL_INFORMATION, &PolicyHandle ); if (Status != STATUS_SUCCESS) { SetLastError (LsaNtStatusToWinError (Status)); return FALSE; } #if DOMAINCONTROLLER // disabled, but may be needed for DC installation // // Obtain the AccountDomain, which is common to all three cases // Status = LsaQueryInformationPolicy ( PolicyHandle, PolicyAccountDomainInformation, &AccountDomain ); if (Status != STATUS_SUCCESS) goto cleanup; // // Note: AccountDomain->DomainSid will contain binary Sid // AddDomainToList (pLsaStringToCString (&AccountDomain->DomainName, PrimaryDomainName)); // // Free memory allocated for account domain // LsaFreeMemory (AccountDomain); // // Find out if this machine is a domain controller // if (!IsDomainController (NULL, &DomainControllerFlag)) { // IsDomainController couldn't find the answer goto cleanup; } #endif // If not a domain controller... if(!DomainControllerFlag) { // // Get the primary domain // Status = LsaQueryInformationPolicy ( PolicyHandle, POLICY_PRIMARY_DOMAIN_INFORMATION, &PrimaryDomain ); if (Status != STATUS_SUCCESS) { goto cleanup; } // // If the primary domain SID is NULL, we are a non-member, and // our work is done. // if (!PrimaryDomain->Sid) { LsaFreeMemory (PrimaryDomain); b = TRUE; goto cleanup; } // // We found our computer domain, add it to our list // AddDomainToList (pLsaStringToCString (&PrimaryDomain->Name, PrimaryDomainName)); LsaFreeMemory (PrimaryDomain); // // Get the primary domain controller computer name. If the API fails, // alert the user and allow them to retry. ServerName is allocated by // the Net APIs. // nas = pGetDcNameAllowingRetry (PrimaryDomainName, ServerName, FALSE); if (nas != NO_ERROR) { goto cleanup; } // // Re-enable the code to open the policy on the domain controller // // // Close the prev policy handle, because we don't need it anymore. // LsaClose (PolicyHandle); PolicyHandle = INVALID_HANDLE_VALUE; // invalidate handle value // // Open the policy on the domain controller // Status = OpenPolicy( ServerName, POLICY_VIEW_LOCAL_INFORMATION, &PolicyHandle ); if (Status != STATUS_SUCCESS) { goto cleanup; } } // // Build additional trusted logon domain(s) list and // indicate if successful // b = pAddAllLogonDomains (DomainControllerFlag ? NULL : ServerName); cleanup: // // Close the policy handle // if (PolicyHandle != INVALID_HANDLE_VALUE && PolicyHandle) { LsaClose (PolicyHandle); } if (!b) { if (Status != STATUS_SUCCESS) SetLastError (LsaNtStatusToWinError (Status)); else if (nas != NO_ERROR) SetLastError (nas); } return b; } BOOL pAddAllLogonDomains ( IN PCTSTR DCName OPTIONAL ) { NET_API_STATUS rc; PWSTR Domains; PCWSTR p; rc = NetEnumerateTrustedDomains ((PTSTR)DCName, &Domains); if (rc != ERROR_SUCCESS) { SetLastError (rc); return FALSE; } for (p = Domains ; *p ; p = GetEndOfStringW (p) + 1) { AddDomainToList (p); } NetApiBufferFree (Domains); return TRUE; } BOOL pEstablishNulSessionWithDomain ( IN OUT PACCT_DOMAINS DomainPtr, IN BOOL ForceNewServer ) /*++ Routine Description: If a nul session has not been established with a domain, this routine finds a server in the domain and establishes the nul session. Every network call is wrapped within a retry loop, so the user can retry when network failures occur. Arguments: DomainPtr - Specifies a pointer to our private domain structure. This structure indicates the domain to establish the nul session with, and it receives the name of the server upon successful connection. ForceNewServer - Specifies TRUE if the function should obtain a new server for the domain. Return value: TRUE if a nul session was established, or FALSE if an error occurred while establishing the nul session. GetLastError provides a failure code. --*/ { NET_API_STATUS nas; WCHAR ServerName[MAX_SERVER_NAMEW]; DWORD rc; // // Release old name if necessary // if (ForceNewServer && DomainPtr->Server) { if (*DomainPtr->Server) { PoolMemReleaseMemory (g_DomainPool, (PVOID) DomainPtr->Server); } DomainPtr->Server = NULL; } // // Obtain a server name if necessary // if (!DomainPtr->Server) { // // Get the primary DC name // nas = pGetDcNameAllowingRetry (DomainPtr->Domain, ServerName, ForceNewServer); if (nas != NO_ERROR) { pDisableDomain (DomainPtr); return FALSE; } DomainPtr->Server = PoolMemDuplicateString (g_DomainPool, ServerName); // // Connect to the server, possibly finding a server that will // service us. // rc = pNetUseAddAllowingRetry (DomainPtr); if (rc != NO_ERROR) { // // Remove the server name because we never connected // pDisableDomain (DomainPtr); SetLastError (rc); LOG ((LOG_ERROR, "NetUseAdd failed")); return FALSE; } } return *DomainPtr->Server != 0; } BOOL QueryDomainForUser ( IN PACCT_ENUM DomainEnumPtr, IN PACCT_ENUM UserEnumPtr ) /*++ Routine Description: Checks the domain controller for a user account via NetUserGetInfo. Arguments: DomainEnumPtr - Specifies the domain to search. This structure must be the return of a domain enumeration function above. UserEnumPtr - Specifies the user to look up over the network. Return value: TRUE if the user exists, or FALSE if the user does not exist. If an error occurs, a retry popup appears, allowing the installer to retry the search if necessary. --*/ { PACCT_DOMAINS DomainPtr; PACCT_USERS UserPtr; NET_API_STATUS rc; BOOL ForceNewServer = FALSE; TCHAR DomainQualifiedUserName[MAX_USER_NAME]; BYTE SidBuf[MAX_SID_SIZE]; DWORD SizeOfSidBuf; TCHAR DontCareStr[MAX_SERVER_NAME]; DWORD DontCareSize; SID_NAME_USE SidNameUse; DomainPtr = DomainEnumPtr->DomainPtr; UserPtr = UserEnumPtr->UserPtr; do { if (!pEstablishNulSessionWithDomain (DomainPtr, ForceNewServer)) { LOG ((LOG_ERROR, "Could not query domain %s for user %s.", DomainPtr->Domain, UserPtr->User)); return FALSE; } // // Do query // DEBUGMSG ((DBG_ACCTLIST, "Querying %s for %s", DomainPtr->Server, UserPtr->User)); rc = NO_ERROR; wsprintf (DomainQualifiedUserName, TEXT("%s\\%s"), DomainPtr->Domain, UserPtr->User); SizeOfSidBuf = sizeof (SidBuf); DontCareSize = sizeof (DontCareStr); if (!LookupAccountName ( DomainPtr->Server, DomainQualifiedUserName, SidBuf, &SizeOfSidBuf, DontCareStr, &DontCareSize, &SidNameUse )) { rc = GetLastError(); } if (rc != NO_ERROR && rc != ERROR_NONE_MAPPED) { PCWSTR ArgArray[2]; DEBUGMSG (( DBG_WARNING, "User was alerted to problem querying account %s (domain %s), rc=%u", DomainPtr->Server, DomainPtr->Domain, rc )); ArgArray[0] = DomainPtr->Server; ArgArray[1] = DomainPtr->Domain; if (!RetryMessageBox (MSG_NULSESSION_RETRY, ArgArray)) { DEBUGMSG ((DBG_WARNING, "Unable to connect to domain %s; user chose to cancel", DomainPtr->Domain)); pDisableDomain (DomainPtr); break; } ForceNewServer = TRUE; } } while (rc != NO_ERROR && rc != ERROR_NONE_MAPPED); if (rc == NO_ERROR && SidNameUse != SidTypeUser) { rc = ERROR_NONE_MAPPED; } if (rc != NO_ERROR && rc != ERROR_NONE_MAPPED) { LOG (( LOG_ERROR, "User %s not found in %s, rc=%u", UserPtr->User, DomainPtr->Domain, rc )); } return rc == NO_ERROR; } BOOL pGetUserSecurityInfo ( IN PCWSTR User, IN PCWSTR Domain, IN OUT PGROWBUFFER SidBufPtr, OUT SID_NAME_USE *UseType OPTIONAL ) /*++ Routine Description: A common routine that searches for a user in our private structures and returns the SID and/or the type of user via LookupAccountName. The lookup operation is enclosed in a retry loop. Arguments: User - The name of the user to get security info on Domain - The name of the domain where the user exists. Can be NULL for the local machine. SidBufPtr - A pointer to a GROWBUFFER. The SID is appended to the GROWBUFFER. UseType - Specifies the address of a SID_NAME_USE variable, or NULL if use type is not needed. Return value: TRUE if the lookup succeeded, or FALSE if an error occurred from either establishing a nul session for a domain or looking up an account on the domain. GetLastError provides the failure code. --*/ { ACCT_ENUM e; PACCT_DOMAINS DomainPtr; PSID Sid; DWORD Size; PCWSTR FullUserName = NULL; WCHAR DomainName[MAX_SERVER_NAMEW]; DWORD DomainNameSize; SID_NAME_USE use = 0; DWORD End; BOOL b = FALSE; DWORD rc; __try { End = SidBufPtr->End; if (Domain) { // // Domain account case -- verify domain is in trust list // if (!FindDomainInList (&e, Domain)) { __leave; } DomainPtr = e.DomainPtr; FullUserName = JoinPaths (Domain, User); } else { // // Local account case // DomainPtr = NULL; if (StringIMatch (User, g_EveryoneStr) || StringIMatch (User, g_NoneGroupStr) || StringIMatch (User, g_AdministratorsGroupStr) ) { FullUserName = DuplicatePathString (User, 0); } else { FullUserName = JoinPaths (g_ComputerName, User); } } Sid = (PSID) GrowBuffer (SidBufPtr, MAX_SID_SIZE); if (DomainPtr && !pEstablishNulSessionWithDomain (DomainPtr, FALSE)) { LOG (( LOG_ERROR, "Could not query domain %s for user %s security info.", Domain, User )); __leave; } Size = MAX_SID_SIZE; DomainNameSize = MAX_SERVER_NAMEW; do { // // Look up account name in form of domain\user or computer\user // b = LookupAccountName ( DomainPtr ? DomainPtr->Server : NULL, FullUserName, Sid, &Size, DomainName, &DomainNameSize, &use ); if (!b) { rc = GetLastError(); // // In the local account case, try the lookup again, without // the computer name decoration. This works around a // GetComputerName bug. // if (rc != ERROR_INSUFFICIENT_BUFFER) { if (!DomainPtr) { b = LookupAccountName ( NULL, User, Sid, &Size, DomainName, &DomainNameSize, &use ); rc = GetLastError(); } } if (!b) { if (rc == ERROR_INSUFFICIENT_BUFFER) { // // Grow the buffer if necessary, then try again // SidBufPtr->End = End; Sid = (PSID) GrowBuffer (SidBufPtr, Size); continue; } // // API failed with an error // LOG (( LOG_ERROR, "Lookup Account On Network: LookupAccountName failed for %s (domain: %s)", FullUserName, Domain ? Domain : TEXT("*local*") )); // // Ignore the error in the case of the local account "none" // if (StringIMatch (User, g_NoneGroupStr)) { b = TRUE; } __leave; } } } while (!b); // // We now have successfully gotten a sid. Adjust pointers, return type. // if (UseType) { *UseType = use; } SidBufPtr->End = End + GetLengthSid (Sid); // // As a debugging aid, output the type // DEBUGMSG_IF ((use == SidTypeUser, DBG_VERBOSE, "%s is SidTypeUser", FullUserName)); DEBUGMSG_IF ((use == SidTypeGroup, DBG_VERBOSE, "%s is SidTypeGroup", FullUserName)); DEBUGMSG_IF ((use == SidTypeDomain, DBG_VERBOSE, "%s is SidTypeDomain", FullUserName)); DEBUGMSG_IF ((use == SidTypeAlias, DBG_VERBOSE, "%s is SidTypeAlias", FullUserName)); DEBUGMSG_IF ((use == SidTypeWellKnownGroup, DBG_VERBOSE, "%s is SidTypeWellKnownGroup", FullUserName)); DEBUGMSG_IF ((use == SidTypeDeletedAccount, DBG_VERBOSE, "%s is SidTypeDeletedAccount", FullUserName)); DEBUGMSG_IF ((use == SidTypeInvalid, DBG_VERBOSE, "%s is SidTypeInvalid", FullUserName)); DEBUGMSG_IF ((use == SidTypeUnknown, DBG_VERBOSE, "%s is SidTypeUnknown", FullUserName)); DEBUGMSG_IF ((use == SidTypeComputer, DBG_VERBOSE, "%s is SidTypeComputer", FullUserName)); } __finally { FreePathString (FullUserName); } return b; } BOOL GetUserSid ( IN PCWSTR User, IN PCWSTR Domain, IN OUT PGROWBUFFER SidBufPtr ) /*++ Routine Description: This routine is vaild only after the domain list is perpared. It queries a domain for a user SID. Arguments: User - Specifies name of user to look up Domain - Specifies name of domain to query, or NULL for local machine SidBufPtr - A ponter to a GROWBUFFER. The SID is appended to the GROWBUFFER. Return value: TRUE if the lookup succeeded, or FALSE if an error occurred from either establishing a nul session for a domain or looking up an account on the domain. GetLastError provides the failure code. --*/ { return pGetUserSecurityInfo (User, Domain, SidBufPtr, NULL); } BOOL GetUserType ( IN PCWSTR User, IN PCWSTR Domain, OUT SID_NAME_USE *UseType ) /*++ Routine Description: This routine is valid only after the domain list is prepared. It queries a domain for a user SID type. Arguments: User - Specifies name of user to look up Domain - Specifies name of domain to query, or NULL for local machine UseType - Specifies the address of a SID_NAME_USE variable Return value: TRUE if the lookup succeeded, or FALSE if an error occurred from either establishing a nul session for a domain or looking up an account on the domain. GetLastError provides the failure code. --*/ { GROWBUFFER SidBuf = GROWBUF_INIT; BOOL b; b = pGetUserSecurityInfo (User, Domain, &SidBuf, UseType); FreeGrowBuffer (&SidBuf); return b; } VOID PrepareForRetry ( VOID ) /*++ Routine Description: Provides caller a way to reset the abandoned domains. When an error occurs during account lookup, and the installer chooses not to retry, the domain is disabled for the rest of the lookup until the installer is presented with a dialog detailing the problems. If they choose to retry the search, all disabled domains must be re-enabled, and thats what this routine does. Arguments: none Return value: none --*/ { ACCT_ENUM Domain; // // Enumerate all domains and remove any empty server names // if (ListFirstDomain (&Domain)) { do { if (Domain.DomainPtr->Server && Domain.DomainPtr->Server[0] == 0) { Domain.DomainPtr->Server = NULL; } } while (ListNextDomain (&Domain)); } // // Reset domain lookup retry count // g_RetryCount = DOMAIN_RETRY_RESET; } BOOL RetryMessageBox ( DWORD Id, PCTSTR *ArgArray ) /*++ Routine Description: A wrapper that allows retry message box code to be simplified. Arguments: Id - Specifies the message ID ArgArray - Specifies the argument array eventually passed to FormatMessage Return value: TRUE if the user chooses YES, FALSE if the user chooses NO --*/ { DWORD rc; if (g_RetryCount < 0) { // Either DOMAIN_RETRY_ABORT or DOMAIN_RETRY_NO return FALSE; } if (g_ConfigOptions.IgnoreNetworkErrors) { return FALSE; } rc = ResourceMessageBox ( g_ParentWnd, Id, MB_YESNO|MB_ICONQUESTION, ArgArray ); if (rc == IDNO && g_RetryCount < DOMAIN_RETRY_MAX) { // disabled so the IDD_NETWORK_DOWN dialog never appears //g_RetryCount++; if (g_RetryCount == DOMAIN_RETRY_MAX) { DWORD Result; Result = DialogBoxParam ( g_hInst, MAKEINTRESOURCE (IDD_NETWORK_DOWN), g_ParentWnd, NetworkDownDlgProc, (LPARAM) (PINT) &g_RetryCount ); if (Result == IDC_STOP) { g_RetryCount = DOMAIN_RETRY_ABORT; } else if (Result == IDC_NO_RETRY) { g_RetryCount = DOMAIN_RETRY_NO; } else { g_RetryCount = DOMAIN_RETRY_RESET; } } } return rc != IDNO; }