/*++ Copyright (c) 1991 Microsoft Corporation Module Name: uasp.c Abstract: Private functions shared by the UAS API routines. Author: Cliff Van Dyke (cliffv) 20-Feb-1991 Environment: User mode only. Contains NT-specific code. Requires ANSI C extensions: slash-slash comments, long external names. Revision History: 17-Apr-1991 (cliffv) Incorporated review comments. --*/ #include #include #include #undef DOMAIN_ALL_ACCESS // defined in both ntsam.h and ntwinapi.h #include #include #include #include #include #include #include #include #include #include #include #include #include // NetpRemoteComputerSupports(), SUPPORTS_ stuff #include // SERVICE_WORKSTATION. #include #include #include #include #include #include #include #include // NetAllocWStrFromWStr SID_IDENTIFIER_AUTHORITY UaspBuiltinAuthority = SECURITY_NT_AUTHORITY; #ifdef UAS_DEBUG DWORD UasTrace = 0; #endif // UAS_DEBUG NET_API_STATUS UaspOpenSam( IN LPCWSTR ServerName OPTIONAL, IN BOOL AllowNullSession, OUT PSAM_HANDLE SamServerHandle ) /*++ Routine Description: Open a handle to a Sam server. Arguments: ServerName - A pointer to a string containing the name of the Domain Controller (DC) to query. A NULL pointer or string specifies the local machine. AllowNullSession - TRUE if we should fall back to the NULL session if we cannot connect using current credentials SamServerHandle - Returns the SAM connection handle if the caller wants it. Close this handle by calling SamCloseHandle Return Value: Error code for the operation. --*/ { NET_API_STATUS NetStatus; NTSTATUS Status; BOOLEAN ImpersonatingAnonymous = FALSE; HANDLE CurrentToken = NULL; UNICODE_STRING ServerNameString; // // Sanity check the server name // if ( ServerName == NULL ) { ServerName = L""; } #ifdef notdef if ( *ServerName != L'\0' && (ServerName[0] != L'\\' || ServerName[1] != L'\\') ) { return NERR_InvalidComputer; } #endif // notdef // // Connect to the SAM server // RtlInitUnicodeString( &ServerNameString, ServerName ); Status = SamConnect( &ServerNameString, SamServerHandle, SAM_SERVER_LOOKUP_DOMAIN | SAM_SERVER_ENUMERATE_DOMAINS, NULL); // // If the caller would rather use the null session than fail, // impersonate the anonymous token. // if ( AllowNullSession && Status == STATUS_ACCESS_DENIED ) { *SamServerHandle = NULL; // // Check to see if we're already impsonating // Status = NtOpenThreadToken( NtCurrentThread(), TOKEN_IMPERSONATE, TRUE, // as self to ensure we never fail &CurrentToken ); if ( Status == STATUS_NO_TOKEN ) { // // We're not already impersonating CurrentToken = NULL; } else if ( !NT_SUCCESS(Status) ) { IF_DEBUG( UAS_DEBUG_UASP ) { NetpKdPrint(( "UaspOpenSam: cannot NtOpenThreadToken: 0x%lx\n", Status )); } NetStatus = NetpNtStatusToApiStatus( Status ); goto Cleanup; } // // Impersonate the anonymous token // Status = NtImpersonateAnonymousToken( NtCurrentThread() ); if ( !NT_SUCCESS( Status)) { IF_DEBUG( UAS_DEBUG_UASP ) { NetpKdPrint(( "UaspOpenSam: cannot NtImpersonateAnonymousToken: 0x%lx\n", Status )); } NetStatus = NetpNtStatusToApiStatus( Status ); goto Cleanup; } ImpersonatingAnonymous = TRUE; // // Connect again now that we're impersonating anonymous // Status = SamConnect( &ServerNameString, SamServerHandle, SAM_SERVER_LOOKUP_DOMAIN | SAM_SERVER_ENUMERATE_DOMAINS, NULL); } if ( !NT_SUCCESS(Status)) { IF_DEBUG( UAS_DEBUG_UASP ) { NetpKdPrint(( "UaspOpenSam: Cannot connect to Sam %lX\n", Status )); } *SamServerHandle = NULL; NetStatus = NetpNtStatusToApiStatus( Status ); goto Cleanup; } NetStatus = NERR_Success; // // Cleanup locally used resources // Cleanup: if ( ImpersonatingAnonymous ) { Status = NtSetInformationThread( NtCurrentThread(), ThreadImpersonationToken, &CurrentToken, sizeof(HANDLE) ); if ( !NT_SUCCESS( Status)) { IF_DEBUG( UAS_DEBUG_UASP ) { NetpKdPrint(( "UaspOpenSam: cannot NtSetInformationThread: 0x%lx\n", Status )); } } } if ( CurrentToken != NULL ) { NtClose( CurrentToken ); } return NetStatus; } NET_API_STATUS UaspGetDomainId( IN SAM_HANDLE SamServerHandle, OUT PSID *DomainId ) /*++ Routine Description: Return a domain ID of the account domain of a server. Arguments: SamServerHandle - A handle to the SAM server to open the domain on DomainId - Receives a pointer to the domain ID. Caller must deallocate buffer using NetpMemoryFree. Return Value: Error code for the operation. --*/ { NET_API_STATUS NetStatus; NTSTATUS Status; SAM_ENUMERATE_HANDLE EnumContext; PSAM_RID_ENUMERATION EnumBuffer = NULL; DWORD CountReturned = 0; PSID LocalDomainId = NULL; DWORD LocalBuiltinDomainSid[sizeof(SID)/sizeof(DWORD) + SID_MAX_SUB_AUTHORITIES ]; BOOL AllDone = FALSE; ULONG i; // // Compute the builtin domain sid. // RtlInitializeSid( (PSID) LocalBuiltinDomainSid, &UaspBuiltinAuthority, 1 ); *(RtlSubAuthoritySid( (PSID)LocalBuiltinDomainSid, 0 )) = SECURITY_BUILTIN_DOMAIN_RID; // // Loop getting the list of domain ids from SAM // EnumContext = 0; do { // // Get several domain names. // Status = SamEnumerateDomainsInSamServer( SamServerHandle, &EnumContext, &EnumBuffer, 8192, // PrefMaxLen &CountReturned ); if ( !NT_SUCCESS( Status ) ) { IF_DEBUG( UAS_DEBUG_UASP ) { NetpKdPrint(( "UaspGetDomainId: Cannot SamEnumerateDomainsInSamServer %lX\n", Status )); } NetStatus = NetpNtStatusToApiStatus( Status ); goto Cleanup; } if( Status != STATUS_MORE_ENTRIES ) { AllDone = TRUE; } // // Lookup the domain ids for the domains // for( i = 0; i < CountReturned; i++ ) { IF_DEBUG( UAS_DEBUG_UASP ) { NetpKdPrint(( "UaspGetDomainId: %wZ: domain name\n", &EnumBuffer[i].Name )); } // // Free the sid from the previous iteration. // if ( LocalDomainId != NULL ) { SamFreeMemory( LocalDomainId ); LocalDomainId = NULL; } // // Lookup the domain id // Status = SamLookupDomainInSamServer( SamServerHandle, &EnumBuffer[i].Name, &LocalDomainId ); if ( !NT_SUCCESS( Status ) ) { IF_DEBUG( UAS_DEBUG_UASP ) { NetpKdPrint(( "UaspGetDomainId: Cannot SamLookupDomainInSamServer %lX\n", Status )); } NetStatus = NetpNtStatusToApiStatus( Status ); goto Cleanup; } // // If this is the builtin domain, // ignore it. // if ( RtlEqualSid( (PSID)LocalBuiltinDomainSid, LocalDomainId ) ) { continue; } // // Found it. // *DomainId = LocalDomainId; LocalDomainId = NULL; NetStatus = NO_ERROR; goto Cleanup; } // // free up current EnumBuffer and get another EnumBuffer. // Status = SamFreeMemory( EnumBuffer ); NetpAssert( NT_SUCCESS(Status) ); EnumBuffer = NULL; } while ( !AllDone ); NetStatus = ERROR_NO_SUCH_DOMAIN; // // Cleanup locally used resources // Cleanup: if ( EnumBuffer != NULL ) { Status = SamFreeMemory( EnumBuffer ); NetpAssert( NT_SUCCESS(Status) ); } return NetStatus; } // UaspGetDomainId NET_API_STATUS UaspOpenDomain( IN SAM_HANDLE SamServerHandle, IN ULONG DesiredAccess, IN BOOL AccountDomain, OUT PSAM_HANDLE DomainHandle, OUT PSID *DomainId OPTIONAL ) /*++ Routine Description: Return a domain handle given the server name and the access desired to the domain. Arguments: SamServerHandle - A handle to the SAM server to open the domain on DesiredAccess - Supplies the access mask indicating which access types are desired to the domain. This routine always requests DOMAIN_LOOKUP access in addition to those specified. AccountDomain - TRUE to open the Account domain. FALSE to open the builtin domain. DomainHandle - Receives the Domain handle to be used on future calls to the SAM server. DomainId - Recieves a pointer to the Sid of the domain. This domain ID must be freed using NetpMemoryFree. Return Value: Error code for the operation. NULL means initialization was successful. --*/ { NET_API_STATUS NetStatus; NTSTATUS Status; PSID LocalDomainId; PSID AccountDomainId = NULL; DWORD LocalBuiltinDomainSid[sizeof(SID)/sizeof(DWORD) + SID_MAX_SUB_AUTHORITIES ]; // // Give everyone DOMAIN_LOOKUP access. // DesiredAccess |= DOMAIN_LOOKUP; // // Choose the domain ID for the right SAM domain. // if ( AccountDomain ) { NetStatus = UaspGetDomainId( SamServerHandle, &AccountDomainId ); if ( NetStatus != NO_ERROR ) { goto Cleanup; } LocalDomainId = AccountDomainId; } else { RtlInitializeSid( (PSID) LocalBuiltinDomainSid, &UaspBuiltinAuthority, 1 ); *(RtlSubAuthoritySid( (PSID)LocalBuiltinDomainSid, 0 )) = SECURITY_BUILTIN_DOMAIN_RID; LocalDomainId = (PSID) LocalBuiltinDomainSid; } // // Open the domain. // Status = SamOpenDomain( SamServerHandle, DesiredAccess, LocalDomainId, DomainHandle ); if ( !NT_SUCCESS( Status ) ) { IF_DEBUG( UAS_DEBUG_UASP ) { NetpKdPrint(( "UaspOpenDomain: Cannot SamOpenDomain %lX\n", Status )); } *DomainHandle = NULL; NetStatus = NetpNtStatusToApiStatus( Status ); goto Cleanup; } // // Return the DomainId to the caller in an allocated buffer // if (ARGUMENT_PRESENT( DomainId ) ) { // // If we've already allocated the sid, // just return it. // if ( AccountDomainId != NULL ) { *DomainId = AccountDomainId; AccountDomainId = NULL; // // Otherwise make a copy. // } else { ULONG SidSize; SidSize = RtlLengthSid( LocalDomainId ); *DomainId = NetpMemoryAllocate( SidSize ); if ( *DomainId == NULL ) { (VOID) SamCloseHandle( *DomainHandle ); *DomainHandle = NULL; NetStatus = ERROR_NOT_ENOUGH_MEMORY; goto Cleanup; } if ( !NT_SUCCESS( RtlCopySid( SidSize, *DomainId, LocalDomainId) ) ) { (VOID) SamCloseHandle( *DomainHandle ); *DomainHandle = NULL; NetpMemoryFree( *DomainId ); *DomainId = NULL; NetStatus = NERR_InternalError; goto Cleanup; } } } NetStatus = NERR_Success; Cleanup: if ( AccountDomainId != NULL ) { NetpMemoryFree( AccountDomainId ); } return NetStatus; } NET_API_STATUS UaspOpenDomainWithDomainName( IN LPCWSTR DomainName, IN ULONG DesiredAccess, IN BOOL AccountDomain, OUT PSAM_HANDLE DomainHandle, OUT PSID *DomainId OPTIONAL ) /*++ Routine Description: Returns the name of a DC in the specified domain. The Server is guaranteed to be up at the instance of this call. Arguments: DoaminName - A pointer to a string containing the name of the remote domain containing the SAM database. A NULL pointer or string specifies the local machine. DesiredAccess - Supplies the access mask indicating which access types are desired to the domain. This routine always requests DOMAIN_LOOKUP access in addition to those specified. AccountDomain - TRUE to open the Account domain. FALSE to open the builtin domain. DomainHandle - Receives the Domain handle to be used on future calls to the SAM server. DomainId - Recieves a pointer to the Sid of the domain. This domain ID must be freed using NetpMemoryFree. Return Value: NERR_Success - Operation completed successfully NERR_DCNotFound - DC for the specified domain could not be found. etc. --*/ { NET_API_STATUS NetStatus; NT_PRODUCT_TYPE NtProductType; LPWSTR ServerName; LPWSTR MyDomainName = NULL; ULONG Flags; ULONG i; PDOMAIN_CONTROLLER_INFOW DcInfo = NULL; SAM_HANDLE SamServerHandle = NULL; // // Check to see if the domain specified refers to this machine. // if ( DomainName == NULL || *DomainName == L'\0' ) { // // Connect to the SAM server // NetStatus = UaspOpenSam( NULL, FALSE, // Don't try null session &SamServerHandle ); if ( NetStatus != NERR_Success ) { IF_DEBUG( UAS_DEBUG_UASP ) { NetpKdPrint(( "UaspOpenDomainWithDomainName: Cannot UaspOpenSam %ld\n", NetStatus )); } } goto Cleanup; } // // Validate the DomainName // if ( !NetpIsDomainNameValid( (LPWSTR)DomainName) ) { NetStatus = NERR_DCNotFound; IF_DEBUG( UAS_DEBUG_UASP ) { NetpKdPrint(( "UaspOpenDomainWithDomainName: %ws: Cannot SamOpenDomain %ld\n", DomainName, NetStatus )); } goto Cleanup; } // // Grab the product type once. // if ( !RtlGetNtProductType( &NtProductType ) ) { NtProductType = NtProductWinNt; } // // If this machine is a DC, this machine is refered to by domain name. // if ( NtProductType == NtProductLanManNt ) { NetStatus = NetpGetDomainName( &MyDomainName ); if ( NetStatus != NERR_Success ) { IF_DEBUG( UAS_DEBUG_UASP ) { NetpKdPrint(( "UaspOpenDomainWithDomainName: %ws: Cannot NetpGetDomainName %ld\n", DomainName, NetStatus )); } goto Cleanup; } // // If this machine is not a DC, this machine is refered to by computer name. // } else { NetStatus = NetpGetComputerName( &MyDomainName ); if ( NetStatus != NERR_Success ) { IF_DEBUG( UAS_DEBUG_UASP ) { NetpKdPrint(( "UaspOpenDomainWithDomainName: %ws: Cannot NetpGetComputerName %ld\n", DomainName, NetStatus )); } goto Cleanup; } } if ( UaspNameCompare( MyDomainName, (LPWSTR) DomainName, NAMETYPE_DOMAIN ) == 0 ) { // // Connect to the SAM server // NetStatus = UaspOpenSam( NULL, FALSE, // Don't try null session &SamServerHandle ); if ( NetStatus != NERR_Success ) { IF_DEBUG( UAS_DEBUG_UASP ) { NetpKdPrint(( "UaspOpenDomainWithDomainName: Cannot UaspOpenSam %ld\n", NetStatus )); } } goto Cleanup; } // // Try at least twice to find a DC. // Flags = 0; for ( i=0; i<2; i++ ) { // // Get the name of a DC in the domain. // NetStatus = DsGetDcNameW( NULL, DomainName, NULL, // No domain GUID NULL, // No site name Flags | DS_IS_FLAT_NAME | DS_RETURN_FLAT_NAME, &DcInfo ); if ( NetStatus != NO_ERROR ) { IF_DEBUG( UAS_DEBUG_UASP ) { NetpKdPrint(( "UaspOpenDomainWithDomainName: %ws: Cannot DsGetDcName %ld\n", DomainName, NetStatus )); } goto Cleanup; } // // Connect to the SAM server on that DC // NetStatus = UaspOpenSam( DcInfo->DomainControllerName, TRUE, // Try null session &SamServerHandle ); if ( NetStatus != NERR_Success ) { IF_DEBUG( UAS_DEBUG_UASP ) { NetpKdPrint(( "UaspOpenDomainWithDomainName: Cannot UaspOpenSam %ld\n", NetStatus )); } } // // If we got a definitive answer back from this DC, // use it. // switch ( NetStatus ) { case NO_ERROR: case ERROR_ACCESS_DENIED: case ERROR_NOT_ENOUGH_MEMORY: case NERR_InvalidComputer: goto Cleanup; } // // Otherwise, force rediscovery of a new DC. // Flags |= DS_FORCE_REDISCOVERY; } // // Delete locally used resources // Cleanup: // // If we've successfully gotten this far, // we have a SamServer handle. // // Just open the domain. // if ( NetStatus == NO_ERROR && SamServerHandle != NULL ) { NetStatus = UaspOpenDomain( SamServerHandle, DesiredAccess, AccountDomain, DomainHandle, DomainId ); } // // The SamServerHandle has outlived its usefulness // if ( SamServerHandle != NULL ) { (VOID) SamCloseHandle( SamServerHandle ); } if ( MyDomainName != NULL ) { NetApiBufferFree( MyDomainName ); } if ( DcInfo != NULL) { NetApiBufferFree( DcInfo ); } if ( NetStatus != NERR_Success ) { *DomainHandle = NULL; } return NetStatus; } // UaspOpenDomainWithDomainName VOID UaspCloseDomain( IN SAM_HANDLE DomainHandle OPTIONAL ) /*++ Routine Description: Close a Domain handle opened by UaspOpenDomain. Arguments: DomainHandle - Supplies the Domain Handle to close. Return Value: None. --*/ { // // Close the Domain Handle // if ( DomainHandle != NULL ) { (VOID) SamCloseHandle( DomainHandle ); } return; } // UaspCloseDomain NET_API_STATUS UaspDownlevel( IN LPCWSTR ServerName OPTIONAL, IN NET_API_STATUS OriginalError, OUT LPBOOL TryDownLevel ) /*++ Routine Description: This routine is based on NetpHandleRpcFailure (courtesy of JohnRo). It is different in that it doesn't handle RPC failures. Rather, it tries to determine if a Sam call should go downlevel simply by calling using the specified ServerName. Arguments: ServerName - The server name to handle the call. OriginalError - Error gotten from RPC attempt. TryDownLevel - Returns TRUE if we should try down-level. Return Value: NERR_Success - Use SAM to handle the call. Other - Return the error to the caller. --*/ { NET_API_STATUS NetStatus; DWORD OptionsSupported = 0; *TryDownLevel = FALSE; // // Learn about the machine. This is fairly easy since the // NetRemoteComputerSupports also handles the local machine (whether // or not a server name is given). // NetStatus = NetRemoteComputerSupports( (LPWSTR) ServerName, SUPPORTS_RPC | SUPPORTS_LOCAL | SUPPORTS_SAM_PROTOCOL, &OptionsSupported); if (NetStatus != NERR_Success) { // This is where machine not found gets handled. return NetStatus; } // // If the machine supports SAM, // just return now. // if (OptionsSupported & SUPPORTS_SAM_PROTOCOL) { // SAM is only supported over RPC NetpAssert((OptionsSupported & SUPPORTS_RPC) == SUPPORTS_RPC ); return OriginalError; } // The local system should always support SAM NetpAssert((OptionsSupported & SUPPORTS_LOCAL) == 0 ); // // Local workstation is not started? (It must be in order to // remote APIs to the other system.) // if ( ! NetpIsServiceStarted(SERVICE_WORKSTATION) ) { return (NERR_WkstaNotStarted); } // // Tell the caller to try the RxNet routine. // *TryDownLevel = TRUE; return OriginalError; } // UaspDownlevel NET_API_STATUS UaspLSASetServerRole( IN LPCWSTR ServerName, IN PDOMAIN_SERVER_ROLE_INFORMATION DomainServerRole ) /*++ Routine Description: This function sets the server role in LSA. Arguments: ServerName - The server name to handle the call. ServerRole - The server role information. Return Value: NERR_Success - if the server role is successfully set in LSA. Error code for the operation - if the operation was unsuccessful. --*/ { NTSTATUS Status; NET_API_STATUS NetStatus; UNICODE_STRING UnicodeStringServerName; ACCESS_MASK LSADesiredAccess; LSA_HANDLE LSAPolicyHandle = NULL; OBJECT_ATTRIBUTES LSAObjectAttributes; POLICY_LSA_SERVER_ROLE_INFO PolicyLsaServerRoleInfo; RtlInitUnicodeString( &UnicodeStringServerName, ServerName ); // // set desired access mask. // LSADesiredAccess = POLICY_SERVER_ADMIN; InitializeObjectAttributes( &LSAObjectAttributes, NULL, // Name 0, // Attributes NULL, // Root NULL ); // Security Descriptor Status = LsaOpenPolicy( &UnicodeStringServerName, &LSAObjectAttributes, LSADesiredAccess, &LSAPolicyHandle ); if( !NT_SUCCESS(Status) ) { IF_DEBUG( UAS_DEBUG_UASP ) { NetpKdPrint(( "UaspLSASetServerRole: " "Cannot open LSA Policy %lX\n", Status )); } NetStatus = NetpNtStatusToApiStatus( Status ); goto Cleanup; } // // make PolicyLsaServerRoleInfo // switch( DomainServerRole->DomainServerRole ) { case DomainServerRoleBackup : PolicyLsaServerRoleInfo.LsaServerRole = PolicyServerRoleBackup; break; case DomainServerRolePrimary : PolicyLsaServerRoleInfo.LsaServerRole = PolicyServerRolePrimary; break; default: IF_DEBUG( UAS_DEBUG_UASP ) { NetpKdPrint(( "UaspLSASetServerRole: " "Unknown Server Role %lX\n", DomainServerRole->DomainServerRole )); } NetStatus = NERR_InternalError; goto Cleanup; } // // now set PolicyLsaServerRoleInformation // Status = LsaSetInformationPolicy( LSAPolicyHandle, PolicyLsaServerRoleInformation, (PVOID) &PolicyLsaServerRoleInfo ); if( !NT_SUCCESS(Status) ) { IF_DEBUG( UAS_DEBUG_UASP ) { NetpKdPrint(( "UaspLSASetServerRole: " "Cannot set Information Policy %lX\n", Status )); } NetStatus = NetpNtStatusToApiStatus( Status ); goto Cleanup; } // // Successfully done // NetStatus = NERR_Success; Cleanup: if( LSAPolicyHandle != NULL ) { Status = LsaClose( LSAPolicyHandle ); NetpAssert( NT_SUCCESS( Status ) ); } return NetStatus; } NET_API_STATUS UaspBuiltinDomainSetServerRole( IN SAM_HANDLE SamServerHandle, IN PDOMAIN_SERVER_ROLE_INFORMATION DomainServerRole ) /*++ Routine Description: This function sets the server role in builtin domain. Arguments: SamServerHandle - A handle to the SAM server to set the role on ServerRole - The server role information. Return Value: NERR_Success - if the server role is successfully set in LSA. Error code for the operation - if the operation was unsuccessful. --*/ { NTSTATUS Status; NET_API_STATUS NetStatus; SAM_HANDLE BuiltinDomainHandle = NULL; // // Open the domain asking for accumulated desired access // NetStatus = UaspOpenDomain( SamServerHandle, DOMAIN_ADMINISTER_SERVER, FALSE, // Builtin Domain &BuiltinDomainHandle, NULL ); // DomainId if ( NetStatus != NERR_Success ) { IF_DEBUG( UAS_DEBUG_UASP ) { NetpKdPrint(( "UaspBuiltinSetServerRole: " "Cannot UaspOpenDomain [Builtin] %ld\n", NetStatus )); } goto Cleanup; } // // now we have open the builtin domain, update server role. // Status = SamSetInformationDomain( BuiltinDomainHandle, DomainServerRoleInformation, DomainServerRole ); if ( !NT_SUCCESS( Status ) ) { IF_DEBUG( UAS_DEBUG_UASP ) { NetpKdPrint(( "UaspBuiltinSetServerRole: " "Cannot SamSetInformationDomain %lX\n", Status )); } NetStatus = NetpNtStatusToApiStatus( Status ); goto Cleanup; } NetStatus = NERR_Success; Cleanup: // // Close DomainHandle. // if ( BuiltinDomainHandle != NULL ) { (VOID) SamCloseHandle( BuiltinDomainHandle ); } return NetStatus; }