/*++ Copyright (c) 1991 Microsoft Corporation Module Name: wrappers.c Abstract: This file contains all SAM rpc wrapper routines. Author: Jim Kelly (JimK) 4-July-1991 Environment: User Mode - Win32 Revision History: --*/ /////////////////////////////////////////////////////////////////////////////// // // // Includes // // // /////////////////////////////////////////////////////////////////////////////// #include "samclip.h" /////////////////////////////////////////////////////////////////////////////// // // // Private defines // // // /////////////////////////////////////////////////////////////////////////////// #define SAMP_MAXIMUM_SUB_LOOKUP_COUNT ((ULONG) 0x00000200) /////////////////////////////////////////////////////////////////////////////// // // // Local data types // // // /////////////////////////////////////////////////////////////////////////////// // // This structure is used to pass a (potentially) very large // user requested name lookup into (possibly many) smaller // remote lookup requests. This is necessary to prevent the // server from being asked to allocate huge chunks of memory, // potentially running out of memory. // // There will be one of these structures for each server call // that is necessary. // typedef struct _SAMP_NAME_LOOKUP_CALL { // // Each call is represented by one of these structures. // The structures are chained together to show the order // the calls were made (allowing an easy way to build the // buffer that is to be returned to the user). // LIST_ENTRY Link; // // These fields define the beginning and ending indexes into // the user passed Names buffer that are being represented by // this call. // ULONG StartIndex; ULONG Count; // // These fields will receive the looked up RIDs and USE buffers. // Notice that the .Element fields of these fields will receive // pointers to the bulk of the returned information. // SAMPR_ULONG_ARRAY RidBuffer; SAMPR_ULONG_ARRAY UseBuffer; } SAMP_NAME_LOOKUP_CALL, *PSAMP_NAME_LOOKUP_CALL; /////////////////////////////////////////////////////////////////////////////// // // // private service prototypes // // // /////////////////////////////////////////////////////////////////////////////// NTSTATUS SampLookupIdsInDomain( IN SAM_HANDLE DomainHandle, IN ULONG Count, IN PULONG RelativeIds, OUT PUNICODE_STRING *Names, OUT PSID_NAME_USE *Use OPTIONAL ); NTSTATUS SampMapCompletionStatus( IN NTSTATUS Status ); NTSTATUS SampCalculateLmPassword( IN PUNICODE_STRING NtPassword, OUT PCHAR *LmPasswordBuffer ); NTSTATUS SampCheckPasswordRestrictions( IN SAMPR_HANDLE UserHandle, IN PUNICODE_STRING NewNtPassword, OUT PBOOLEAN UseOwfPasswords ); /////////////////////////////////////////////////////////////////////////////// // // // Routines // // // /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// // // // General services // // // /////////////////////////////////////////////////////////////////////////////// NTSTATUS SamFreeMemory( IN PVOID Buffer ) /*++ Routine Description: Some SAM services that return a potentially large amount of memory, such as an enumeration might, allocate the buffer in which the data is returned. This function is used to free those buffers when they are no longer needed. Parameters: Buffer - Pointer to the buffer to be freed. This buffer must have been allocated by a previous SAM service call. Return Values: STATUS_SUCCESS - normal, successful completion. --*/ { MIDL_user_free( Buffer ); return(STATUS_SUCCESS); } NTSTATUS SamSetSecurityObject( IN SAM_HANDLE ObjectHandle, IN SECURITY_INFORMATION SecurityInformation, IN PSECURITY_DESCRIPTOR SecurityDescriptor ) /*++ Routine Description: This function (SamSetSecurityObject) takes a well formed Security Descriptor provided by the caller and assigns specified portions of it to an object. Based on the flags set in the SecurityInformation parameter and the caller's access rights, this procedure will replace any or all of the security information associated with an object. This is the only function available to users and applications for changing security information, including the owner ID, group ID, and the discretionary and system ACLs of an object. The caller must have WRITE_OWNER access to the object to change the owner or primary group of the object. The caller must have WRITE_DAC access to the object to change the discretionary ACL. The caller must have ACCESS_SYSTEM_SECURITY access to an object to assign a system ACL to the object. This API is modelled after the NtSetSecurityObject() system service. Parameters: ObjectHandle - A handle to an existing object. SecurityInformation - Indicates which security information is to be applied to the object. The value(s) to be assigned are passed in the SecurityDescriptor parameter. SecurityDescriptor - A pointer to a well formed Security Descriptor. Return Values: STATUS_SUCCESS - normal, successful completion. STATUS_ACCESS_DENIED - The specified handle was not opened for either WRITE_OWNER, WRITE_DAC, or ACCESS_SYSTEM_SECURITY access. STATUS_INVALID_HANDLE - The specified handle is not that of an opened SAM object. STATUS_BAD_DESCRIPTOR_FORMAT - Indicates something about security descriptor is not valid. This may indicate that the structure of the descriptor is not valid or that a component of the descriptor specified via the SecurityInformation parameter is not present in the security descriptor. STATUS_INVALID_PARAMETER - Indicates no security information was specified. --*/ { NTSTATUS NtStatus; ULONG SDLength; SAMPR_SR_SECURITY_DESCRIPTOR DescriptorToPass; // // Make a self relative security descriptor for use in the RPC call.. // SDLength = 0; NtStatus = RtlMakeSelfRelativeSD( SecurityDescriptor, NULL, &SDLength ); if (NtStatus != STATUS_BUFFER_TOO_SMALL) { return(STATUS_INVALID_PARAMETER); } else { DescriptorToPass.SecurityDescriptor = MIDL_user_allocate( SDLength ); DescriptorToPass.Length = SDLength; if (DescriptorToPass.SecurityDescriptor == NULL) { NtStatus = STATUS_INSUFFICIENT_RESOURCES; } else { // // make an appropriate self-relative security descriptor // NtStatus = RtlMakeSelfRelativeSD( SecurityDescriptor, (PSECURITY_DESCRIPTOR)DescriptorToPass.SecurityDescriptor, &SDLength ); } } // // Call the server ... // if (NT_SUCCESS(NtStatus)) { RpcTryExcept{ NtStatus = SamrSetSecurityObject( (SAMPR_HANDLE)ObjectHandle, SecurityInformation, &DescriptorToPass ); } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) { NtStatus = I_RpcMapWin32Status(RpcExceptionCode()); } RpcEndExcept; } MIDL_user_free( DescriptorToPass.SecurityDescriptor ); return(SampMapCompletionStatus(NtStatus)); } NTSTATUS SamQuerySecurityObject( IN SAM_HANDLE ObjectHandle, IN SECURITY_INFORMATION SecurityInformation, OUT PSECURITY_DESCRIPTOR * SecurityDescriptor ) /*++ Routine Description: This function (SamQuerySecurityObject) returns to the caller requested security information currently assigned to an object. Based on the caller's access rights this procedure will return a security descriptor containing any or all of the object's owner ID, group ID, discretionary ACL or system ACL. To read the owner ID, group ID, or the discretionary ACL the caller must be granted READ_CONTROL access to the object. To read the system ACL the caller must be granted ACCESS_SYSTEM_SECURITY access. This API is modelled after the NtQuerySecurityObject() system service. Parameters: ObjectHandle - A handle to an existing object. SecurityInformation - Supplies a value describing which pieces of security information are being queried. SecurityDescriptor - Receives a pointer to the buffer containing the requested security information. This information is returned in the form of a self-relative security descriptor. The caller is responsible for freeing the returned buffer (using SamFreeMemory()) when the security descriptor is no longer needed. Return Values: STATUS_SUCCESS - normal, successful completion. STATUS_ACCESS_DENIED - The specified handle was not opened for either READ_CONTROL or ACCESS_SYSTEM_SECURITY access. STATUS_INVALID_HANDLE - The specified handle is not that of an opened SAM object. --*/ { NTSTATUS NtStatus; SAMPR_SR_SECURITY_DESCRIPTOR ReturnedSD; PSAMPR_SR_SECURITY_DESCRIPTOR PReturnedSD; // // The retrieved security descriptor is returned via a data structure that // looks like: // // +-----------------------+ // | Length (bytes) | // |-----------------------| +--------------+ // | SecurityDescriptor ---|--------->| Self-Relative| // +-----------------------+ | Security | // | Descriptor | // +--------------+ // // The first of these buffers is a local stack variable. The buffer containing // the self-relative security descriptor is allocated by the RPC runtime. The // pointer to the self-relative security descriptor is what is passed back to our // caller. // // // // To prevent RPC from trying to marshal a self-relative security descriptor, // make sure its field values are appropriately initialized to zero and null. // ReturnedSD.Length = 0; ReturnedSD.SecurityDescriptor = NULL; // // Call the server ... // RpcTryExcept{ PReturnedSD = &ReturnedSD; NtStatus = SamrQuerySecurityObject( (SAMPR_HANDLE)ObjectHandle, SecurityInformation, &PReturnedSD ); } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) { NtStatus = I_RpcMapWin32Status(RpcExceptionCode()); } RpcEndExcept; (*SecurityDescriptor) = ReturnedSD.SecurityDescriptor; return(SampMapCompletionStatus(NtStatus)); } NTSTATUS SamCloseHandle( OUT SAM_HANDLE SamHandle ) /*++ Routine Description: This API closes a currently opened SAM object. Arguments: SamHandle - Specifies the handle of a previously opened SAM object to close. Return Value: STATUS_SUCCESS - The object was successfully closed. STATUS_INVALID_HANDLE - The handle passed is invalid. --*/ { NTSTATUS NtStatus; SAMPR_HANDLE TmpHandle; if (SamHandle == NULL) { return(STATUS_INVALID_HANDLE); } // // Call the server ... // TmpHandle = (SAMPR_HANDLE)SamHandle; RpcTryExcept{ NtStatus = SamrCloseHandle( (SAMPR_HANDLE *)(&TmpHandle) ); } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) { NtStatus = I_RpcMapWin32Status(RpcExceptionCode()); if ((NtStatus != RPC_NT_SS_CONTEXT_MISMATCH) && (NtStatus != RPC_NT_INVALID_BINDING)) { (void) RpcSsDestroyClientContext( &TmpHandle); } } RpcEndExcept; return(SampMapCompletionStatus(NtStatus)); } /////////////////////////////////////////////////////////////////////////////// // // // Server object related services // // // /////////////////////////////////////////////////////////////////////////////// NTSTATUS SamConnect( IN PUNICODE_STRING ServerName, OUT PSAM_HANDLE ServerHandle, IN ACCESS_MASK DesiredAccess, IN POBJECT_ATTRIBUTES ObjectAttributes ) /*++ Routine Description: Establish a session with a SAM subsystem and subsequently open the SamServer object of that subsystem. The caller must have SAM_SERVER_CONNECT access to the SamServer object of the subsystem being connected to. The handle returned is for use in future calls. Arguments: ServerName - Name of the server to use, or NULL if local. ServerHandle - A handle to be used in future requests. This handle represents both the handle to the SamServer object and the RPC context handle for the connection to the SAM subsystem. DesiredAccess - Is an access mask indicating which access types are desired to the SamServer. These access types are reconciled with the Discretionary Access Control list of the SamServer to determine whether the accesses will be granted or denied. The access type of SAM_SERVER_CONNECT is always implicitly included in this access mask. ObjectAttributes - Pointer to the set of object attributes to use for this connection. Only the security Quality Of Service information is used and should provide SecurityIdentification level of impersonation. Return Value: STATUS_SUCCESS - The Service completed successfully. STATUS_ACCESS_DENIED - Access was denied. --*/ { NTSTATUS NtStatus; PSAMPR_SERVER_NAME RServerName; PSAMPR_SERVER_NAME RServerNameWithNull; USHORT RServerNameWithNullLength; // // Hmmm - what to do with security QOS??? // // // Call the server, passing either a NULL Server Name pointer, or // a pointer to a Unicode Buffer with a Wide Character NULL terminator. // Since the input name is contained in a counted Unicode String, there // is no NULL terminator necessarily provided, so we must append one. // RServerNameWithNull = NULL; if (ARGUMENT_PRESENT(ServerName)) { RServerName = (PSAMPR_SERVER_NAME)(ServerName->Buffer); RServerNameWithNullLength = ServerName->Length + (USHORT) sizeof(WCHAR); RServerNameWithNull = MIDL_user_allocate( RServerNameWithNullLength ); if (RServerNameWithNull == NULL) { return(STATUS_INSUFFICIENT_RESOURCES); } RtlCopyMemory( RServerNameWithNull, RServerName, ServerName->Length); RServerNameWithNull[ServerName->Length/sizeof(WCHAR)] = L'\0'; } RpcTryExcept { NtStatus = SamrConnect2( RServerNameWithNull, (SAMPR_HANDLE *)ServerHandle, DesiredAccess ); } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) { NtStatus = I_RpcMapWin32Status(RpcExceptionCode()); } RpcEndExcept; // // If the new connect call failed because it didn't exist, // try the old one. // if ((NtStatus == RPC_NT_UNKNOWN_IF) || (NtStatus == RPC_NT_PROCNUM_OUT_OF_RANGE)) { RpcTryExcept { NtStatus = SamrConnect( RServerNameWithNull, (SAMPR_HANDLE *)ServerHandle, DesiredAccess ); } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) { NtStatus = I_RpcMapWin32Status(RpcExceptionCode()); } RpcEndExcept; } if (RServerNameWithNull != NULL) { MIDL_user_free( RServerNameWithNull ); } return(SampMapCompletionStatus(NtStatus)); DBG_UNREFERENCED_PARAMETER(ObjectAttributes); } NTSTATUS SamShutdownSamServer( IN SAM_HANDLE ServerHandle ) /*++ Routine Description: This is the wrapper routine for SamShutdownSamServer(). Arguments: ServerHandle - Handle from a previous SamConnect() call. Return Value: STATUS_SUCCESS The service completed successfully or the server has already shutdown. STATUS_ACCESS_DENIED - Caller does not have the appropriate access to complete the operation. --*/ { NTSTATUS NtStatus; // // Call the server ... // RpcTryExcept{ NtStatus = SamrShutdownSamServer(ServerHandle); } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) { NtStatus = I_RpcMapWin32Status(RpcExceptionCode()); // // If the error status is one that would result from a server // not being there, then replace it with success. // if (NtStatus == RPC_NT_CALL_FAILED) { NtStatus = STATUS_SUCCESS; } } RpcEndExcept; return(SampMapCompletionStatus(NtStatus)); } NTSTATUS SamLookupDomainInSamServer( IN SAM_HANDLE ServerHandle, IN PUNICODE_STRING Name, OUT PSID *DomainId ) /*++ Routine Description: This service returns the SID corresponding to the specified domain. The domain is specified by name. Arguments: ServerHandle - Handle from a previous SamConnect() call. Name - The name of the domain whose ID is to be looked up. A case-insensitive comparison of this name will be performed for the lookup operation. DomainId - Receives a pointer to a buffer containing the SID of the looked up domain. This buffer must be freed using SamFreeMemory() when no longer needed. Return Value: STATUS_SUCCESS - The service completed successfully. STATUS_ACCESS_DENIED - Caller does not have the appropriate access to complete the operation. SAM_SERVER_LOOKUP_DOMAIN access is needed. STATUS_NO_SUCH_DOMAIN - The specified domain does not exist at this server. STATUS_INVALID_SERVER_STATE - Indicates the SAM server is currently disabled. --*/ { NTSTATUS NtStatus; // // Call the server ... // RpcTryExcept{ (*DomainId) = 0; NtStatus = SamrLookupDomainInSamServer( (SAMPR_HANDLE)ServerHandle, (PRPC_UNICODE_STRING)Name, (PRPC_SID *)DomainId ); } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) { NtStatus = I_RpcMapWin32Status(RpcExceptionCode()); } RpcEndExcept; return(SampMapCompletionStatus(NtStatus)); } NTSTATUS SamEnumerateDomainsInSamServer( IN SAM_HANDLE ServerHandle, IN OUT PSAM_ENUMERATE_HANDLE EnumerationContext, OUT PVOID * Buffer, IN ULONG PreferedMaximumLength, OUT PULONG CountReturned ) /*++ Routine Description: This API lists all the domains defined in the account database. Since there may be more domains than can fit into a buffer, the caller is provided with a handle that can be used across calls to the API. On the initial call, EnumerationContext should point to a SAM_ENUMERATE_HANDLE variable that is set to 0. If the API returns STATUS_MORE_ENTRIES, then the API should be called again with EnumerationContext. When the API returns STATUS_SUCCESS or any error return, the handle becomes invalid for future use. This API requires SAM_SERVER_ENUMERATE_DOMAINS access to the SamServer object. Parameters: ServerHandle - Handle obtained from a previous SamConnect call. EnumerationContext - API specific handle to allow multiple calls (see routine description). This is a zero based index. Buffer - Receives a pointer to the buffer where the information is placed. The information returned is contiguous SAM_RID_ENUMERATION data structures. However, the RelativeId field of each of these structures is not valid. This buffer must be freed when no longer needed using SamFreeMemory(). PreferedMaximumLength - Prefered maximum length of returned data (in 8-bit bytes). This is not a hard upper limit, but serves as a guide to the server. Due to data conversion between systems with different natural data sizes, the actual amount of data returned may be greater than this value. CountReturned - Number of entries returned. Return Values: STATUS_SUCCESS - The Service completed successfully, and there are no additional entries. STATUS_MORE_ENTRIES - There are more entries, so call again. This is a successful return. STATUS_ACCESS_DENIED - Caller does not have the access required to enumerate the domains. STATUS_INVALID_HANDLE - The handle passed is invalid. STATUS_INVALID_SERVER_STATE - Indicates the SAM server is currently disabled. --*/ { NTSTATUS NtStatus; PSAMPR_ENUMERATION_BUFFER LocalBuffer; // // Make sure we aren't trying to have RPC allocate the EnumerationContext. // if ( !ARGUMENT_PRESENT(EnumerationContext) ) { return(STATUS_INVALID_PARAMETER); } if ( !ARGUMENT_PRESENT(Buffer) ) { return(STATUS_INVALID_PARAMETER); } if ( !ARGUMENT_PRESENT(CountReturned) ) { return(STATUS_INVALID_PARAMETER); } // // Call the server ... // (*Buffer) = NULL; LocalBuffer = NULL; RpcTryExcept{ NtStatus = SamrEnumerateDomainsInSamServer( (SAMPR_HANDLE)ServerHandle, EnumerationContext, (PSAMPR_ENUMERATION_BUFFER *)&LocalBuffer, PreferedMaximumLength, CountReturned ); if (LocalBuffer != NULL) { // // What comes back is a three level structure: // // Local +-------------+ // Buffer ---> | EntriesRead | // |-------------| +-------+ // | Enumeration |--->| Name0 | --- > (NameBuffer0) // | Return | |-------| o // | Buffer | | ... | o // +-------------+ |-------| o // | NameN | --- > (NameBufferN) // +-------+ // // The buffer containing the EntriesRead field is not returned // to our caller. Only the buffers containing name information // are returned. // if (LocalBuffer->Buffer != NULL) { (*Buffer) = LocalBuffer->Buffer; } MIDL_user_free( LocalBuffer); } } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) { NtStatus = I_RpcMapWin32Status(RpcExceptionCode()); } RpcEndExcept; return(SampMapCompletionStatus(NtStatus)); } /////////////////////////////////////////////////////////////////////////////// // // // Domain object related services // // // /////////////////////////////////////////////////////////////////////////////// NTSTATUS SamOpenDomain( IN SAM_HANDLE ServerHandle, IN ACCESS_MASK DesiredAccess, IN PSID DomainId, OUT PSAM_HANDLE DomainHandle ) /*++ Routine Description: This API opens a domain object. It returns a handle to the newly opened domain that must be used for successive operations on the domain. This handle may be closed with the SamCloseHandle API. Arguments: ServerHandle - Handle from a previous SamConnect() call. DesiredAccess - Is an access mask indicating which access types are desired to the domain. These access types are reconciled with the Discretionary Access Control list of the domain to determine whether the accesses will be granted or denied. DomainId - The SID assigned to the domain to open. DomainHandle - Receives a handle referencing the newly opened domain. This handle will be required in successive calls to operate on the domain. Return Value: STATUS_SUCCESS - The domain was successfully opened. STATUS_ACCESS_DENIED - Caller does not have the appropriate access to complete the operation. STATUS_INVALID_SERVER_STATE - Indicates the SAM server is currently disabled. --*/ { NTSTATUS NtStatus; // // Call the server ... // RpcTryExcept{ (*DomainHandle) = 0; NtStatus = SamrOpenDomain( (SAMPR_HANDLE)ServerHandle, DesiredAccess, (PRPC_SID)DomainId, (SAMPR_HANDLE *)DomainHandle ); } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) { NtStatus = I_RpcMapWin32Status(RpcExceptionCode()); } RpcEndExcept; return(SampMapCompletionStatus(NtStatus)); } NTSTATUS SamQueryInformationDomain( IN SAM_HANDLE DomainHandle, IN DOMAIN_INFORMATION_CLASS DomainInformationClass, OUT PVOID *Buffer ) /*++ Routine Description: This API retrieves the domain information. This API requires either DOMAIN_READ_PASSWORD_PARAMETERS or DOMAIN_READ_OTHER_PARAMETERS. Arguments: DomainHandle - Handle from a previous SamOpenDomain() call. DomainInformationClass - Class of information desired. The accesses required for each class is shown below: Info Level Required Access Type --------------------------- ------------------------------- DomainGeneralInformation DOMAIN_READ_OTHER_PARAMETERS DomainPasswordInformation DOMAIN_READ_PASSWORD_PARAMS DomainLogoffInformation DOMAIN_READ_OTHER_PARAMETERS DomainOemInformation DOMAIN_READ_OTHER_PARAMETERS DomainNameInformation DOMAIN_READ_OTHER_PARAMETERS DomainServerRoleInformation DOMAIN_READ_OTHER_PARAMETERS DomainReplicationInformation DOMAIN_READ_OTHER_PARAMETERS DomainModifiedInformation DOMAIN_READ_OTHER_PARAMETERS DomainStateInformation DOMAIN_READ_OTHER_PARAMETERS DomainUasInformation DOMAIN_READ_OTHER_PARAMETERS Added for NT1.0A... DomainGeneralInformation2 DOMAIN_READ_OTHER_PARAMETERS DomainLockoutInformation DOMAIN_READ_OTHER_PARAMETERS Buffer - Receives a pointer to a buffer containing the requested information. When this information is no longer needed, this buffer must be freed using SamFreeMemory(). Return Value: STATUS_SUCCESS - The Service completed successfully. STATUS_ACCESS_DENIED - Caller does not have the appropriate access to complete the operation. STATUS_INVALID_HANDLE - The handle passed is invalid. STATUS_INVALID_INFO_CLASS - The class provided was invalid. --*/ { NTSTATUS NtStatus; // // Call the server ... // (*Buffer) = NULL; RpcTryExcept{ if (DomainInformationClass <= DomainUasInformation) { NtStatus = SamrQueryInformationDomain( (SAMPR_HANDLE)DomainHandle, DomainInformationClass, (PSAMPR_DOMAIN_INFO_BUFFER *)Buffer ); } else { NtStatus = SamrQueryInformationDomain2( (SAMPR_HANDLE)DomainHandle, DomainInformationClass, (PSAMPR_DOMAIN_INFO_BUFFER *)Buffer ); } } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) { // // If the exception indicates the server doesn't have // the selected api, that means the server doesn't know // about the info level we passed. Set our completion // status appropriately. // if (RpcExceptionCode() == RPC_S_INVALID_LEVEL || RpcExceptionCode() == RPC_S_PROCNUM_OUT_OF_RANGE || RpcExceptionCode() == RPC_NT_PROCNUM_OUT_OF_RANGE ) { NtStatus = STATUS_INVALID_INFO_CLASS; } else { NtStatus = I_RpcMapWin32Status(RpcExceptionCode()); } } RpcEndExcept; return(SampMapCompletionStatus(NtStatus)); } NTSTATUS SamSetInformationDomain( IN SAM_HANDLE DomainHandle, IN DOMAIN_INFORMATION_CLASS DomainInformationClass, IN PVOID DomainInformation ) /*++ Routine Description: This API sets the domain information to the values passed in the buffer. Parameters: DomainHandle - A domain handle returned from a previous call to SamOpenDomain. DomainInformationClass - Class of information desired. The accesses required for each class is shown below: Info Level Required Access Type ------------------------- ---------------------------- DomainPasswordInformation DOMAIN_WRITE_PASSWORD_PARAMS DomainLogoffInformation DOMAIN_WRITE_OTHER_PARAMETERS DomainOemInformation DOMAIN_WRITE_OTHER_PARAMETERS DomainNameInformation (not valid for set operations.) DomainServerRoleInformation DOMAIN_ADMINISTER_SERVER DomainReplicationInformation DOMAIN_ADMINISTER_SERVER DomainModifiedInformation (not valid for set operations.) DomainStateInformation DOMAIN_ADMINISTER_SERVER DomainUasInformation DOMAIN_WRITE_OTHER_PARAMETERS DomainInformation - Buffer where the domain information can be found. Return Values: STATUS_SUCCESS - The Service completed successfully. STATUS_ACCESS_DENIED - Caller does not have the appropriate access to complete the operation. STATUS_INVALID_HANDLE - The handle passed is invalid. STATUS_INVALID_INFO_CLASS - The class provided was invalid. STATUS_INVALID_DOMAIN_STATE - The domain server is not in the correct state (disabled or enabled) to perform the requested operation. The domain server must be disabled before role changes can be made. STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the incorrect role (primary or backup) to perform the requested operation. --*/ { NTSTATUS NtStatus; // // Call the server ... // RpcTryExcept{ NtStatus = SamrSetInformationDomain( (SAMPR_HANDLE)DomainHandle, DomainInformationClass, (PSAMPR_DOMAIN_INFO_BUFFER)DomainInformation ); } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) { NtStatus = I_RpcMapWin32Status(RpcExceptionCode()); } RpcEndExcept; return(SampMapCompletionStatus(NtStatus)); } NTSTATUS SamCreateGroupInDomain( IN SAM_HANDLE DomainHandle, IN PUNICODE_STRING AccountName, IN ACCESS_MASK DesiredAccess, OUT PSAM_HANDLE GroupHandle, OUT PULONG RelativeId ) /*++ Routine Description: This API creates a new group in the account database. Initially, this group does not contain any users. Note that creating a group is a protected operation, and requires the DOMAIN_CREATE_GROUP access type. This call returns a handle to the newly created group that may be used for successive operations on the group. This handle may be closed with the SamCloseHandle API. A newly created group will have the following initial field value settings. If another value is desired, it must be explicitly changed using the group object manipulation services. Name - The name of the group will be as specified in the creation API. Attributes - The following attributes will be set: Mandatory EnabledByDefault MemberCount - Zero. Initially the group has no members. RelativeId - will be a uniquelly allocated ID. Parameters: DomainHandle - A domain handle returned from a previous call to SamOpenDomain. AccountName - Points to the name of the new account. A case-insensitive comparison must not find a group, alias or user with this name already defined. DesiredAccess - Is an access mask indicating which access types are desired to the group. GroupHandle - Receives a handle referencing the newly created group. This handle will be required in successive calls to operate on the group. RelativeId - Receives the relative ID of the newly created group account. The SID of the new group account is this relative ID value prefixed with the domain's SID value. Return Values: STATUS_SUCCESS - The group was added successfully. STATUS_ACCESS_DENIED - Caller does not have the appropriate access to complete the operation. STATUS_INVALID_HANDLE - The handle passed is invalid. STATUS_INVALID_ACCOUNT_NAME - The name was poorly formed, e.g. contains non-printable characters. STATUS_GROUP_EXISTS - The name is already in use as a group. STATUS_USER_EXISTS - The name is already in use as a user. STATUS_ALIAS_EXISTS - The name is already in use as an alias. STATUS_INVALID_DOMAIN_STATE - The domain server is not in the correct state (disabled or enabled) to perform the requested operation. The domain server must be enabled before groups can be created in it. STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the incorrect role (primary or backup) to perform the requested operation. The domain server must be a primary server to create group accounts. --*/ { NTSTATUS NtStatus; // // Call the server ... // (*GroupHandle) = NULL; (*RelativeId) = 0; RpcTryExcept{ NtStatus = SamrCreateGroupInDomain( (SAMPR_HANDLE)DomainHandle, (PRPC_UNICODE_STRING)AccountName, DesiredAccess, (SAMPR_HANDLE *)GroupHandle, RelativeId ); } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) { NtStatus = I_RpcMapWin32Status(RpcExceptionCode()); } RpcEndExcept; return(SampMapCompletionStatus(NtStatus)); } NTSTATUS SamEnumerateGroupsInDomain( IN SAM_HANDLE DomainHandle, IN OUT PSAM_ENUMERATE_HANDLE EnumerationContext, IN PVOID * Buffer, IN ULONG PreferedMaximumLength, OUT PULONG CountReturned ) /*++ Routine Description: This API lists all the groups defined in the account database. Since there may be more groups than can fit into a buffer, the caller is provided with a handle that can be used across calls to the API. On the initial call, EnumerationContext should point to a SAM_ENUMERATE_HANDLE variable that is set to 0. If the API returns STATUS_MORE_ENTRIES, then the API should be called again with EnumerationContext. When the API returns STATUS_SUCCESS or any error return, the handle becomes invalid for future use. This API requires DOMAIN_LIST_ACCOUNTS access to the Domain object. Parameters: DomainHandle - A domain handle returned from a previous call to SamOpenDomain. EnumerationContext - API specific handle to allow multiple calls (see routine description). This is a zero based index. Buffer - Receives a pointer to the buffer containing the requested information. The information returned is structured as an array of SAM_RID_ENUMERATION data structures. When this information is no longer needed, the buffer must be freed using SamFreeMemory(). PreferedMaximumLength - Prefered maximum length of returned data (in 8-bit bytes). This is not a hard upper limit, but serves as a guide to the server. Due to data conversion between systems with different natural data sizes, the actual amount of data returned may be greater than this value. CountReturned - Number of entries returned. Return Values: STATUS_SUCCESS - The Service completed successfully, and there are no additional entries. STATUS_MORE_ENTRIES - There are more entries, so call again. This is a successful return. STATUS_ACCESS_DENIED - Caller does not have privilege required to request that data. STATUS_INVALID_HANDLE - The handle passed is invalid. --*/ { NTSTATUS NtStatus; PSAMPR_ENUMERATION_BUFFER LocalBuffer; // // Make sure we aren't trying to have RPC allocate the EnumerationContext. // if ( !ARGUMENT_PRESENT(EnumerationContext) ) { return(STATUS_INVALID_PARAMETER); } if ( !ARGUMENT_PRESENT(Buffer) ) { return(STATUS_INVALID_PARAMETER); } if ( !ARGUMENT_PRESENT(CountReturned) ) { return(STATUS_INVALID_PARAMETER); } // // Call the server ... // (*Buffer) = NULL; LocalBuffer = NULL; RpcTryExcept{ NtStatus = SamrEnumerateGroupsInDomain( (SAMPR_HANDLE)DomainHandle, EnumerationContext, (PSAMPR_ENUMERATION_BUFFER *)&LocalBuffer, PreferedMaximumLength, CountReturned ); if (LocalBuffer != NULL) { // // What comes back is a three level structure: // // Local +-------------+ // Buffer ---> | EntriesRead | // |-------------| +-------+ // | Enumeration |--->| Name0 | --- > (NameBuffer0) // | Return | |-------| o // | Buffer | | ... | o // +-------------+ |-------| o // | NameN | --- > (NameBufferN) // +-------+ // // The buffer containing the EntriesRead field is not returned // to our caller. Only the buffers containing name information // are returned. // if (LocalBuffer->Buffer != NULL) { (*Buffer) = LocalBuffer->Buffer; } MIDL_user_free( LocalBuffer); } } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) { NtStatus = I_RpcMapWin32Status(RpcExceptionCode()); } RpcEndExcept; return(SampMapCompletionStatus(NtStatus)); } NTSTATUS SamCreateUser2InDomain( IN SAM_HANDLE DomainHandle, IN PUNICODE_STRING AccountName, IN ULONG AccountType, IN ACCESS_MASK DesiredAccess, OUT PSAM_HANDLE UserHandle, OUT PULONG GrantedAccess, OUT PULONG RelativeId ) /*++ Routine Description: This API adds a new user to the account database. The account is created in a disabled state. Default information is assigned to all fields except the account name. A password must be provided before the account may be enabled, unless the PasswordNotRequired control field is set. This api may be used in either of two ways: 1) An administrative utility may use this api to create any type of user account. In this case, the DomainHandle is expected to be open for DOMAIN_CREATE_USER access. 2) A non-administrative user may use this api to create a machine account. In this case, the caller is expected to have the SE_CREATE_MACHINE_ACCOUNT_PRIV privilege and the DomainHandle is expected to be open for DOMAIN_LOOKUP access. For the normal administrative model ( #1 above), the creator will be assigned as the owner of the created user account. Furthermore, the new account will be give USER_WRITE access to itself. For the special machine-account creation model (#2 above), the "Administrators" will be assigned as the owner of the account. Furthermore, the new account will be given NO access to itself. Instead, the creator of the account will be give USER_WRITE and DELETE access to the account. This call returns a handle to the newly created user that may be used for successive operations on the user. This handle may be closed with the SamCloseHandle() API. If a machine account is being created using model #2 above, then this handle will have only USER_WRITE and DELETE access. Otherwise, it will be open for USER_ALL_ACCESS. A newly created user will automatically be made a member of the DOMAIN_USERS group. A newly created user will have the following initial field value settings. If another value is desired, it must be explicitly changed using the user object manipulation services. UserName - the name of the account will be as specified in the creation API. FullName - will be null. UserComment - will be null. Parameters - will be null. CountryCode - will be zero. UserId - will be a uniquelly allocated ID. PrimaryGroupId - Will be DOMAIN_USERS. PasswordLastSet - will be the time the account was created. HomeDirectory - will be null. HomeDirectoryDrive - will be null. UserAccountControl - will have the following flags set: UserAccountDisable, UserPasswordNotRequired, and the passed account type. ScriptPath - will be null. WorkStations - will be null. CaseInsensitiveDbcs - will be null. CaseSensitiveUnicode - will be null. LastLogon - will be zero delta time. LastLogoff - will be zero delta time AccountExpires - will be very far into the future. BadPasswordCount - will be negative 1 (-1). LastBadPasswordTime - will be SampHasNeverTime ( [High,Low] = [0,0] ). LogonCount - will be negative 1 (-1). AdminCount - will be zero. AdminComment - will be null. Password - will be "". Parameters: DomainHandle - A domain handle returned from a previous call to SamOpenDomain. AccountName - Points to the name of the new account. A case-insensitive comparison must not find a group or user with this name already defined. AccountType - Indicates what type of account is being created. Exactly one account type must be provided: USER_INTERDOMAIN_TRUST_ACCOUNT USER_WORKSTATION_TRUST_ACCOUNT USER_SERVER_TRUST_ACCOUNT USER_TEMP_DUPLICATE_ACCOUNT USER_NORMAL_ACCOUNT USER_MACHINE_ACCOUNT_MASK DesiredAccess - Is an access mask indicating which access types are desired to the user. UserHandle - Receives a handle referencing the newly created user. This handle will be required in successive calls to operate on the user. GrantedAccess - Receives the accesses actually granted to via the UserHandle. When creating an account on a down-level server, this value may be unattainable. In this case, it will be returned as zero (0). RelativeId - Receives the relative ID of the newly created user account. The SID of the new user account is this relative ID value prefixed with the domain's SID value. Return Values: STATUS_SUCCESS - The Service completed successfully. STATUS_ACCESS_DENIED - Caller does not have the appropriate access to complete the operation. STATUS_INVALID_HANDLE - The handle passed is invalid. STATUS_GROUP_EXISTS - The name is already in use as a group. STATUS_USER_EXISTS - The name is already in use as a user. STATUS_ALIAS_EXISTS - The name is already in use as an alias. STATUS_INVALID_ACCOUNT_NAME - The name was poorly formed, e.g. contains non-printable characters. STATUS_INVALID_DOMAIN_STATE - The domain server is not in the correct state (disabled or enabled) to perform the requested operation. The domain server must be enabled before users can be created in it. STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the incorrect role (primary or backup) to perform the requested operation. The domain server must be a primary server to create user accounts. --*/ { NTSTATUS NtStatus, IgnoreStatus; USER_CONTROL_INFORMATION UserControlInfoBuffer; // // Call the server ... // (*UserHandle) = NULL; (*RelativeId) = 0; RpcTryExcept{ NtStatus = SamrCreateUser2InDomain( (SAMPR_HANDLE)DomainHandle, (PRPC_UNICODE_STRING)AccountName, AccountType, DesiredAccess, (SAMPR_HANDLE *)UserHandle, GrantedAccess, RelativeId ); } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) { if (RpcExceptionCode() == RPC_S_PROCNUM_OUT_OF_RANGE || RpcExceptionCode() == RPC_NT_PROCNUM_OUT_OF_RANGE ) { NtStatus = RPC_NT_PROCNUM_OUT_OF_RANGE; } else { NtStatus = I_RpcMapWin32Status(RpcExceptionCode()); } } RpcEndExcept; // // If the server doesn't support the new api, then // do the equivalent work with the old apis. // if (NtStatus == RPC_NT_PROCNUM_OUT_OF_RANGE) { DesiredAccess = DesiredAccess | USER_WRITE_ACCOUNT; NtStatus = SamCreateUserInDomain( DomainHandle, AccountName, DesiredAccess, UserHandle, RelativeId ); if (NT_SUCCESS(NtStatus)) { // // Set the AccountType (unless it is normal) // if (~(AccountType & USER_NORMAL_ACCOUNT)) { UserControlInfoBuffer.UserAccountControl = AccountType | USER_ACCOUNT_DISABLED | USER_PASSWORD_NOT_REQUIRED; NtStatus = SamSetInformationUser( (*UserHandle), UserControlInformation, &UserControlInfoBuffer ); if (!NT_SUCCESS(NtStatus)) { IgnoreStatus = SamDeleteUser( UserHandle ); } // // We can't be positive what accesses have been // granted, so don't try lying. // (*GrantedAccess) = 0; } } } return(SampMapCompletionStatus(NtStatus)); } NTSTATUS SamCreateUserInDomain( IN SAM_HANDLE DomainHandle, IN PUNICODE_STRING AccountName, IN ACCESS_MASK DesiredAccess, OUT PSAM_HANDLE UserHandle, OUT PULONG RelativeId ) /*++ Routine Description: This API adds a new user to the account database. The account is created in a disabled state. Default information is assigned to all fields except the account name. A password must be provided before the account may be enabled, unless the PasswordNotRequired control field is set. Note that DOMAIN_CREATE_USER access type is needed by this API. Also, the caller of this API becomes the owner of the user object upon creation. This call returns a handle to the newly created user that may be used for successive operations on the user. This handle may be closed with the SamCloseHandle() API. A newly created user will automatically be made a member of the DOMAIN_USERS group. A newly created user will have the following initial field value settings. If another value is desired, it must be explicitly changed using the user object manipulation services. UserName - the name of the account will be as specified in the creation API. FullName - will be null. UserComment - will be null. Parameters - will be null. CountryCode - will be zero. UserId - will be a uniquelly allocated ID. PrimaryGroupId - Will be DOMAIN_USERS. PasswordLastSet - will be the time the account was created. HomeDirectory - will be null. HomeDirectoryDrive - will be null. UserAccountControl - will have the following flags set: USER_ACCOUNT_DISABLED, USER_NORMAL_ACCOUNT, USER_PASSWORD_NOT_REQUIRED ScriptPath - will be null. WorkStations - will be null. CaseInsensitiveDbcs - will be null. CaseSensitiveUnicode - will be null. LastLogon - will be zero. LastLogoff - will be zero. AccountExpires - will be very far into the future. BadPasswordCount - will be negative 1 (-1). LogonCount - will be negative 1 (-1). AdminComment - will be null. Password - will contain any value, but is not used because the USER_PASSWORD_NOT_REQUIRED control flag is set. If a password is to be required, then this field must be set to a specific value and the USER_PASSWORD_NOT_REQUIRED flag must be cleared. Parameters: DomainHandle - A domain handle returned from a previous call to SamOpenDomain. AccountName - The name to be assigned to the new account. DesiredAccess - Is an access mask indicating which access types are desired to the user. UserHandle - Receives a handle referencing the newly created user. This handle will be required in successive calls to operate on the user. RelativeId - Receives the relative ID of the newly created user account. The SID of the new user account is this relative ID value prefixed with the domain's SID value. Return Values: STATUS_SUCCESS - The Service completed successfully. STATUS_ACCESS_DENIED - Caller does not have the appropriate access to complete the operation. STATUS_INVALID_HANDLE - The handle passed is invalid. STATUS_GROUP_EXISTS - The name is already in use as a group. STATUS_USER_EXISTS - The name is already in use as a user. STATUS_ALIAS_EXISTS - The name is already in use as an alias. STATUS_INVALID_ACCOUNT_NAME - The name was poorly formed, e.g. contains non-printable characters. STATUS_INVALID_DOMAIN_STATE - The domain server is not in the correct state (disabled or enabled) to perform the requested operation. The domain server must be enabled before users can be created in it. STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the incorrect role (primary or backup) to perform the requested operation. The domain server must be a primary server to create user accounts. --*/ { NTSTATUS NtStatus; // // Call the server ... // (*UserHandle) = NULL; (*RelativeId) = 0; RpcTryExcept{ NtStatus = SamrCreateUserInDomain( (SAMPR_HANDLE)DomainHandle, (PRPC_UNICODE_STRING)AccountName, DesiredAccess, (SAMPR_HANDLE *)UserHandle, RelativeId ); } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) { NtStatus = I_RpcMapWin32Status(RpcExceptionCode()); } RpcEndExcept; return(SampMapCompletionStatus(NtStatus)); } NTSTATUS SamEnumerateUsersInDomain( IN SAM_HANDLE DomainHandle, IN OUT PSAM_ENUMERATE_HANDLE EnumerationContext, IN ULONG UserAccountControl, OUT PVOID * Buffer, IN ULONG PreferedMaximumLength, OUT PULONG CountReturned ) /*++ Routine Description: This API lists all the users defined in the account database. Since there may be more users than can fit into a buffer, the caller is provided with a handle that can be used across calls to the API. On the initial call, EnumerationContext should point to a SAM_ENUMERATE_HANDLE variable that is set to 0. If the API returns STATUS_MORE_ENTRIES, then the API should be called again with EnumerationContext. When the API returns STATUS_SUCCESS or any error return, the handle becomes invalid for future use. This API requires DOMAIN_LIST_ACCOUNTS access to the Domain object. Parameters: DomainHandle - A domain handle returned from a previous call to SamOpenDomain. EnumerationContext - API specific handle to allow multiple calls (see routine description). This is a zero based index. UserAccountControl - Provides enumeration filtering information. Any characteristics specified here will cause that type of User account to be included in the enumeration process. Buffer - Receives a pointer to the buffer containing the requested information. The information returned is structured as an array of SAM_RID_ENUMERATION data structures. When this information is no longer needed, the buffer must be freed using SamFreeMemory(). PreferedMaximumLength - Prefered maximum length of returned data (in 8-bit bytes). This is not a hard upper limit, but serves as a guide to the server. Due to data conversion between systems with different natural data sizes, the actual amount of data returned may be greater than this value. CountReturned - Number of entries returned. Return Values: STATUS_SUCCESS - The Service completed successfully, and there are no additional entries. STATUS_MORE_ENTRIES - There are more entries, so call again. This is a successful return. STATUS_ACCESS_DENIED - Caller does not have privilege required to request that data. STATUS_INVALID_HANDLE - The handle passed is invalid. --*/ { NTSTATUS NtStatus; PSAMPR_ENUMERATION_BUFFER LocalBuffer; // // Make sure we aren't trying to have RPC allocate the EnumerationContext. // if ( !ARGUMENT_PRESENT(EnumerationContext) ) { return(STATUS_INVALID_PARAMETER); } if ( !ARGUMENT_PRESENT(Buffer) ) { return(STATUS_INVALID_PARAMETER); } if ( !ARGUMENT_PRESENT(CountReturned) ) { return(STATUS_INVALID_PARAMETER); } // // Call the server ... // (*Buffer) = NULL; LocalBuffer = NULL; RpcTryExcept{ NtStatus = SamrEnumerateUsersInDomain( (SAMPR_HANDLE)DomainHandle, EnumerationContext, UserAccountControl, (PSAMPR_ENUMERATION_BUFFER *)&LocalBuffer, PreferedMaximumLength, CountReturned ); if (LocalBuffer != NULL) { // // What comes back is a three level structure: // // Local +-------------+ // Buffer ---> | EntriesRead | // |-------------| +-------+ // | Enumeration |--->| Name0 | --- > (NameBuffer0) // | Return | |-------| o // | Buffer | | ... | o // +-------------+ |-------| o // | NameN | --- > (NameBufferN) // +-------+ // // The buffer containing the EntriesRead field is not returned // to our caller. Only the buffers containing name information // are returned. // if (LocalBuffer->Buffer != NULL) { (*Buffer) = LocalBuffer->Buffer; } MIDL_user_free( LocalBuffer); } } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) { NtStatus = I_RpcMapWin32Status(RpcExceptionCode()); } RpcEndExcept; return(SampMapCompletionStatus(NtStatus)); } NTSTATUS SamCreateAliasInDomain( IN SAM_HANDLE DomainHandle, IN PUNICODE_STRING AccountName, IN ACCESS_MASK DesiredAccess, OUT PSAM_HANDLE AliasHandle, OUT PULONG RelativeId ) /*++ Routine Description: This API adds a new alias to the account database. Initially, this alias does not contain any members. This call returns a handle to the newly created account that may be used for successive operations on the object. This handle may be closed with the SamCloseHandle API. A newly created group will have the following initial field value settings. If another value is desired, it must be explicitly changed using the Alias object manipulation services. Name - the name of the account will be as specified in the creation API. MemberCount - Zero. Initially the alias has no members. RelativeId - will be a uniquelly allocated ID. Parameters: DomainHandle - A domain handle returned from a previous call to SamOpenDomain. The handle must be open for DOMAIN_CREATE_ALIAS access. AccountName - The name of the alias to be added. DesiredAccess - Is an access mask indicating which access types are desired to the alias. AliasHandle - Receives a handle referencing the newly created alias. This handle will be required in successive calls to operate on the alias. RelativeId - Receives the relative ID of the newly created alias. The SID of the new alias is this relative ID value prefixed with the domain's SID value. Return Values: STATUS_SUCCESS - The account was added successfully. STATUS_ACCESS_DENIED - Caller does not have the appropriate access to complete the operation. STATUS_INVALID_HANDLE - The handle passed is invalid. STATUS_INVALID_ACCOUNT_NAME - The name was poorly formed, e.g. contains non-printable characters. STATUS_GROUP_EXISTS - The name is already in use as a group. STATUS_USER_EXISTS - The name is already in use as a user. STATUS_ALIAS_EXISTS - The name is already in use as an alias. STATUS_INVALID_DOMAIN_STATE - The domain server is not in the correct state (disabled or enabled) to perform the requested operation. The domain server must be enabled before aliases can be created in it. STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the incorrect role (primary or backup) to perform the requested operation. The domain server must be a primary server to create aliases. --*/ { NTSTATUS NtStatus; // // Call the server ... // (*AliasHandle) = NULL; (*RelativeId) = 0; RpcTryExcept{ NtStatus = SamrCreateAliasInDomain( (SAMPR_HANDLE)DomainHandle, (PRPC_UNICODE_STRING)AccountName, DesiredAccess, (SAMPR_HANDLE *)AliasHandle, RelativeId ); } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) { NtStatus = I_RpcMapWin32Status(RpcExceptionCode()); } RpcEndExcept; return(SampMapCompletionStatus(NtStatus)); } NTSTATUS SamEnumerateAliasesInDomain( IN SAM_HANDLE DomainHandle, IN OUT PSAM_ENUMERATE_HANDLE EnumerationContext, IN PVOID *Buffer, IN ULONG PreferedMaximumLength, OUT PULONG CountReturned ) /*++ Routine Description: This API lists all the aliases defined in the account database. Since there may be more aliases than can fit into a buffer, the caller is provided with a handle that can be used across calls to the API. On the initial call, EnumerationContext should point to a SAM_ENUMERATE_HANDLE variable that is set to 0. If the API returns STATUS_MORE_ENTRIES, then the API should be called again with EnumerationContext. When the API returns STATUS_SUCCESS or any error return, the handle becomes invalid for future use. This API requires DOMAIN_LIST_ACCOUNTS access to the Domain object. Parameters: DomainHandle - A domain handle returned from a previous call to SamOpenDomain. EnumerationContext - API specific handle to allow multiple calls (see routine description). This is a zero based index. Buffer - Receives a pointer to the buffer containing the requested information. The information returned is structured as an array of SAM_RID_ENUMERATION data structures. When this information is no longer needed, the buffer must be freed using SamFreeMemory(). PreferedMaximumLength - Prefered maximum length of returned data (in 8-bit bytes). This is not a hard upper limit, but serves as a guide to the server. Due to data conversion between systems with different natural data sizes, the actual amount of data returned may be greater than this value. CountReturned - Number of entries returned. Return Values: STATUS_SUCCESS - The Service completed successfully, and there are no additional entries. STATUS_MORE_ENTRIES - There are more entries, so call again. This is a successful return. STATUS_ACCESS_DENIED - Caller does not have privilege required to request that data. STATUS_INVALID_HANDLE - The handle passed is invalid. --*/ { NTSTATUS NtStatus; PSAMPR_ENUMERATION_BUFFER LocalBuffer; // // Make sure we aren't trying to have RPC allocate the EnumerationContext. // if ( !ARGUMENT_PRESENT(EnumerationContext) ) { return(STATUS_INVALID_PARAMETER); } if ( !ARGUMENT_PRESENT(Buffer) ) { return(STATUS_INVALID_PARAMETER); } if ( !ARGUMENT_PRESENT(CountReturned) ) { return(STATUS_INVALID_PARAMETER); } // // Call the server ... // (*Buffer) = NULL; LocalBuffer = NULL; RpcTryExcept{ NtStatus = SamrEnumerateAliasesInDomain( (SAMPR_HANDLE)DomainHandle, EnumerationContext, (PSAMPR_ENUMERATION_BUFFER *)&LocalBuffer, PreferedMaximumLength, CountReturned ); if (LocalBuffer != NULL) { // // What comes back is a three level structure: // // Local +-------------+ // Buffer ---> | EntriesRead | // |-------------| +-------+ // | Enumeration |--->| Name0 | --- > (NameBuffer0) // | Return | |-------| o // | Buffer | | ... | o // +-------------+ |-------| o // | NameN | --- > (NameBufferN) // +-------+ // // The buffer containing the EntriesRead field is not returned // to our caller. Only the buffers containing name information // are returned. // if (LocalBuffer->Buffer != NULL) { (*Buffer) = LocalBuffer->Buffer; } MIDL_user_free( LocalBuffer); } } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) { NtStatus = I_RpcMapWin32Status(RpcExceptionCode()); } RpcEndExcept; return(SampMapCompletionStatus(NtStatus)); } NTSTATUS SamGetAliasMembership( IN SAM_HANDLE DomainHandle, IN ULONG PassedCount, IN PSID *Sids, OUT PULONG MembershipCount, OUT PULONG *Aliases ) /*++ Routine Description: This API searches the set of aliases in the specified domain to see which aliases, if any, the passed SIDs are members of. Any aliases that any of the SIDs are found to be members of are returned. Parameters: DomainHandle - A domain handle returned from a previous call to SamOpenDomain. PassedCount - Specifies the number of Sids being passed. Sids - Pointer to an array of Count pointers to Sids whose alias memberships are to be looked up. MembershipCount - Receives the number of aliases that are being returned via the Aliases parameter. Aliases - Receives a pointer to an array of SIDs. This is the set of aliases the passed SIDs were found to be members of. If MembershipCount is returned as zero, then a null value will be returned here. When this information is no longer needed, it must be released by passing the returned pointer to SamFreeMemory(). Return Values: STATUS_SUCCESS - The Service completed successfully. STATUS_ACCESS_DENIED - Caller does not have privilege required to request that data. STATUS_INVALID_HANDLE - The handle passed is invalid. --*/ { NTSTATUS NtStatus; SAMPR_PSID_ARRAY Accounts; SAMPR_ULONG_ARRAY Membership; // // Make sure we aren't trying to have RPC allocate the EnumerationContext. // if ( !ARGUMENT_PRESENT(Sids) ) { return(STATUS_INVALID_PARAMETER); } if ( !ARGUMENT_PRESENT(MembershipCount) ) { return(STATUS_INVALID_PARAMETER); } if ( !ARGUMENT_PRESENT(Aliases) ) { return(STATUS_INVALID_PARAMETER); } // // Call the server ... // Membership.Element = NULL; RpcTryExcept{ Accounts.Count = PassedCount; Accounts.Sids = (PSAMPR_SID_INFORMATION)Sids; NtStatus = SamrGetAliasMembership( (SAMPR_HANDLE)DomainHandle, &Accounts, &Membership ); if (NT_SUCCESS(NtStatus)) { (*MembershipCount) = Membership.Count; (*Aliases) = Membership.Element; } else { // // Deallocate any returned buffers on error // if (Membership.Element != NULL) { MIDL_user_free(Membership.Element); } } } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) { NtStatus = I_RpcMapWin32Status(RpcExceptionCode()); } RpcEndExcept; return(SampMapCompletionStatus(NtStatus)); } NTSTATUS SamLookupNamesInDomain( IN SAM_HANDLE DomainHandle, IN ULONG Count, IN PUNICODE_STRING Names, OUT PULONG *RelativeIds, OUT PSID_NAME_USE *Use ) /*++ Routine Description: This API attempts to find relative IDs corresponding to name strings. If a name can not be mapped to a relative ID, a zero is placed in the corresponding relative ID array entry, and translation continues. DOMAIN_LOOKUP access to the domain is needed to use this service. Parameters: DomainHandle - A domain handle returned from a previous call to SamOpenDomain. Count - Number of names to translate. Names - Pointer to an array of Count UNICODE_STRINGs that contain the names to map to relative IDs. Case-insensitive comparisons of these names will be performed for the lookup operation. RelativeIds - Receives a pointer to an array of Count Relative IDs that have been filled in. The relative ID of the nth name will be the nth entry in this array. Any names that could not be translated will have a zero relative ID. This buffer must be freed when no longer needed using SamFreeMemory(). Use - Recieves a pointer to an array of Count SID_NAME_USE entries that have been filled in with what significance each name has. The nth entry in this array indicates the meaning of the nth name passed. This buffer must be freed when no longer needed using SamFreeMemory(). Return Values: STATUS_SUCCESS - The Service completed successfully. STATUS_ACCESS_DENIED - Caller does not have the appropriate access to complete the operation. STATUS_INVALID_HANDLE - The domain handle passed is invalid. STATUS_SOME_NOT_MAPPED - Some of the names provided could not be mapped. This is a successful return. STATUS_NONE_MAPPED - No names could be mapped. This is an error return. --*/ { NTSTATUS ReturnStatus, NtStatus; LIST_ENTRY CallHead; PSAMP_NAME_LOOKUP_CALL Next; PSID_NAME_USE UseBuffer; PULONG RidBuffer; ULONG Calls, CallLength, i; BOOLEAN NoneMapped = TRUE, SomeNotMapped = FALSE; if ( (Count == 0) || (Names == NULL) ) { return(STATUS_INVALID_PARAMETER); } // // Default error return // (*Use) = UseBuffer = NULL; (*RelativeIds) = RidBuffer = NULL; // // Set up the call structures list // InitializeListHead( &CallHead ); Calls = 0; // // By default we will return NONE_MAPPED. // This will get superseded by either STATUS_SUCCESS // or STATUS_SOME_NOT_MAPPED. // // // Now build up and make each call // i = 0; while ( i < Count ) { // // Make sure the next entry isn't too long. // That would put us in an infinite loop. // if (Names[i].Length > SAM_MAXIMUM_LOOKUP_LENGTH) { ReturnStatus = STATUS_INVALID_PARAMETER; goto SampNameLookupFreeAndReturn; } // // Get the next call structure // Next = (PSAMP_NAME_LOOKUP_CALL)MIDL_user_allocate( sizeof(SAMP_NAME_LOOKUP_CALL) ); if (Next == NULL) { ReturnStatus = STATUS_INSUFFICIENT_RESOURCES; goto SampNameLookupFreeAndReturn; } // // Fill in the call structure. // It takes a little to figure out how many entries to send in // this call. It is limited by both Count (sam_MAXIMUM_LOOKUP_COUNT) // and by size (SAM_MAXIMUM_LOOKUP_LENGTH). // Next->Count = 0; Next->StartIndex = i; Next->RidBuffer.Element = NULL; Next->UseBuffer.Element = NULL; CallLength = 0; for ( i=i; ( (i < Count) && (CallLength+Names[i].Length < SAM_MAXIMUM_LOOKUP_LENGTH) && (Next->Count < SAM_MAXIMUM_LOOKUP_COUNT) ); i++ ) { // // Add in the next length and increment the number of entries // being processed by this call. // CallLength += Names[i].Length; Next->Count ++; } // // Add this call structure to the list of call structures // Calls ++; InsertTailList( &CallHead, &Next->Link ); // // Now make the call // RpcTryExcept{ NtStatus = SamrLookupNamesInDomain( (SAMPR_HANDLE)DomainHandle, Next->Count, (PRPC_UNICODE_STRING)(&Names[Next->StartIndex]), &Next->RidBuffer, &Next->UseBuffer ); } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) { NtStatus = I_RpcMapWin32Status(RpcExceptionCode()); } RpcEndExcept; // // Keep track of what our completion status should be. // if (!NT_SUCCESS(NtStatus) && NtStatus != STATUS_NONE_MAPPED) { ReturnStatus = NtStatus; // Unexpected error goto SampNameLookupFreeAndReturn; } if (NT_SUCCESS(NtStatus)) { NoneMapped = FALSE; if (NtStatus == STATUS_SOME_NOT_MAPPED) { SomeNotMapped = TRUE; } } } // // Set our return status... // if (NoneMapped) { ASSERT(SomeNotMapped == FALSE); ReturnStatus = STATUS_NONE_MAPPED; } else if (SomeNotMapped) { ReturnStatus = STATUS_SOME_NOT_MAPPED; } else { ReturnStatus = STATUS_SUCCESS; } // // At this point we have (potentially) a lot of call structures. // The RidBuffer and UseBuffer elements of each call structure // is allocated and returned by the RPC call and looks // like: // // RidBuffer // +-------------+ // | Count | // |-------------| +-------+ * // | Element ---|--->| Rid-0 | | / // +-------------+ |-------| | / Only this part // | ... | > < is allocated by // |-------| | \ the rpc call. // | Rid-N | | \ // +-------+ * // // If only one RPC call was made, we can return this information // directly. Otherwise, we need to copy the information from // all the calls into a single large buffer and return that buffer // (freeing all the individual call buffers). // // The user is responsible for freeing whichever buffer we do // return. // ASSERT(Calls != 0); // Error go around this path, success always has calls // // Optimize for a single call // if (Calls == 1) { (*Use) = (PSID_NAME_USE) (((PSAMP_NAME_LOOKUP_CALL)(CallHead.Flink))-> UseBuffer.Element); (*RelativeIds) = ((PSAMP_NAME_LOOKUP_CALL)(CallHead.Flink))-> RidBuffer.Element; MIDL_user_free( CallHead.Flink ); // Free the call structure return(ReturnStatus); } // // More than one call. // Allocate return buffers large enough to copy all the information into. // RidBuffer = MIDL_user_allocate( sizeof(ULONG) * Count ); if (RidBuffer == NULL) { ReturnStatus = STATUS_INSUFFICIENT_RESOURCES; goto SampNameLookupFreeAndReturn; } UseBuffer = MIDL_user_allocate( sizeof(SID_NAME_USE) * Count ); if (UseBuffer == NULL) { MIDL_user_free( RidBuffer ); RidBuffer = NULL; ReturnStatus = STATUS_INSUFFICIENT_RESOURCES; goto SampNameLookupFreeAndReturn; } SampNameLookupFreeAndReturn: // // Walk the list of calls. // For each call: // // If we have a return buffer, copy the results into it. // Free the call buffers. // Free the call structure itself. // // Completion status has already been set appropriatly in ReturnStatus. // Next = (PSAMP_NAME_LOOKUP_CALL)RemoveHeadList( &CallHead ); while (Next != (PSAMP_NAME_LOOKUP_CALL)&CallHead) { // // Copy RID information and then free the call buffer // if (RidBuffer != NULL) { RtlMoveMemory( &RidBuffer[ Next->StartIndex ], // Destination &Next->RidBuffer.Element[0], // Source Next->Count * sizeof(ULONG) // Length ); } if (Next->RidBuffer.Element != NULL) { MIDL_user_free( Next->RidBuffer.Element ); } // // Copy USE information and then free the call buffer // if (UseBuffer != NULL) { RtlMoveMemory( &UseBuffer[ Next->StartIndex ], // Destination &Next->UseBuffer.Element[0], // Source Next->Count * sizeof(SID_NAME_USE) // Length ); } if (Next->UseBuffer.Element != NULL) { MIDL_user_free( Next->UseBuffer.Element ); } // // Free the call structure itself // MIDL_user_free( Next ); Next = (PSAMP_NAME_LOOKUP_CALL)RemoveHeadList( &CallHead ); } // end-while // // For better or worse, we're all done // (*Use) = UseBuffer; (*RelativeIds) = RidBuffer; return(SampMapCompletionStatus(NtStatus)); } NTSTATUS SamLookupIdsInDomain( IN SAM_HANDLE DomainHandle, IN ULONG Count, IN PULONG RelativeIds, OUT PUNICODE_STRING *Names, OUT PSID_NAME_USE *Use OPTIONAL ) /*++ Routine Description: This API maps a number of relative IDs to their corresponding names. The use of the name (domain, group, alias, user, or unknown) is also returned. The API stores the actual names in Buffer, then creates an array of UNICODE_STRINGs in the Names OUT parameter. If a relative ID can not be mapped, a NULL value is placed in the slot for the UNICODE_STRING, and STATUS_SOME_NOT_MAPPED is returned. DOMAIN_LOOKUP access to the domain is needed to use this service. Parameters: DomainHandle - A domain handle returned from a previous call to SamOpenDomain. Count - Provides the number of relative IDs to translate. RelativeIds - Array of Count relative IDs to be mapped. Names - Receives a pointer to an array of Count UNICODE_STRINGs that have been filled in. The nth pointer within this array will correspond the nth relative id passed . Each name string buffer will be in a separately allocated block of memory. Any entry is not successfully translated will have a NULL name buffer pointer returned. This Names buffer must be freed using SamFreeMemory() when no longer needed. Use - Optionally, receives a pointer to an array of Count SID_NAME_USE entries that have been filled in with what significance each name has. The nth entry in this array indicates the meaning of the nth name passed. This buffer must be freed when no longer needed using SamFreeMemory(). Return Values: STATUS_SUCCESS - The Service completed successfully. STATUS_ACCESS_DENIED - Caller does not have the appropriate access to complete the operation. STATUS_INVALID_HANDLE - The domain handle passed is invalid. STATUS_SOME_NOT_MAPPED - Some of the names provided could not be mapped. This is a successful return. STATUS_NONE_MAPPED - No names could be mapped. This is an error return. --*/ { NTSTATUS NtStatus; ULONG SubRequest, SubRequests; ULONG TotalCountToDate; ULONG Index, UsedLength, Length, NamesLength; ULONG UsesLength, LastSubRequestCount; PULONG UstringStructDisps; PULONG Counts = NULL; PULONG RidIndices = NULL; PUNICODE_STRING *SubRequestNames = NULL; PSID_NAME_USE *SubRequestUses = NULL; PUNICODE_STRING OutputNames = NULL; PSID_NAME_USE OutputUses = NULL; PUCHAR Destination = NULL, Source = NULL; PUNICODE_STRING DestUstring = NULL; ULONG SomeNotMappedStatusCount = 0; ULONG NoneMappedStatusCount = 0; // // If the Count for this request does not exceed the maximum limit that // can be looked up in a single call, just call the Sub Request version // of the routine. // if (Count <= SAM_MAXIMUM_LOOKUP_COUNT) { NtStatus = SampLookupIdsInDomain( DomainHandle, Count, RelativeIds, Names, Use ); return(NtStatus); } // // Break down larger requests into smaller chunks // SubRequests = Count / SAMP_MAXIMUM_SUB_LOOKUP_COUNT; LastSubRequestCount = Count % SAMP_MAXIMUM_SUB_LOOKUP_COUNT; if (LastSubRequestCount > 0) { SubRequests++; } // // Allocate memory for array of starting Rid Indices, Rid Counts and // Unicode String block offsets for each SubRequest. // NtStatus = STATUS_NO_MEMORY; RidIndices = MIDL_user_allocate( SubRequests * sizeof(ULONG) ); if (RidIndices == NULL) { goto LookupIdsInDomainError; } Counts = MIDL_user_allocate( SubRequests * sizeof(ULONG) ); if (Counts == NULL) { goto LookupIdsInDomainError; } SubRequestNames = MIDL_user_allocate( SubRequests * sizeof(PUNICODE_STRING) ); if (SubRequestNames == NULL) { goto LookupIdsInDomainError; } SubRequestUses = MIDL_user_allocate( SubRequests * sizeof(SID_NAME_USE) ); if (SubRequestUses == NULL) { goto LookupIdsInDomainError; } UstringStructDisps = MIDL_user_allocate( SubRequests * sizeof(ULONG) ); if (UstringStructDisps == NULL) { goto LookupIdsInDomainError; } NtStatus = STATUS_SUCCESS; TotalCountToDate = 0; for (SubRequest = 0; SubRequest < SubRequests; SubRequest++) { RidIndices[SubRequest] = TotalCountToDate; if ((Count - TotalCountToDate) > SAMP_MAXIMUM_SUB_LOOKUP_COUNT) { Counts[SubRequest] = SAMP_MAXIMUM_SUB_LOOKUP_COUNT; } else { Counts[SubRequest] = Count - TotalCountToDate; } TotalCountToDate += Counts[SubRequest]; NtStatus = SampLookupIdsInDomain( DomainHandle, Counts[SubRequest], &RelativeIds[RidIndices[SubRequest]], &SubRequestNames[SubRequest], &SubRequestUses[SubRequest] ); // // We keep a tally of the number of times STATUS_SOME_NOT_MAPPED // and STATUS_NONE_MAPPED were returned. This is so that we // can return the appropriate status at the end based on the // global picture. We continue lookups after either status code // is encountered. // if (NtStatus == STATUS_SOME_NOT_MAPPED) { SomeNotMappedStatusCount++; } else if (NtStatus == STATUS_NONE_MAPPED) { NoneMappedStatusCount++; NtStatus = STATUS_SUCCESS; } if (!NT_SUCCESS(NtStatus)) { break; } } if (!NT_SUCCESS(NtStatus)) { goto LookupIdsInDomainError; } // // Now allocate a single buffer for the Names // NamesLength = Count * sizeof(UNICODE_STRING); for (SubRequest = 0; SubRequest < SubRequests; SubRequest++) { for (Index = 0; Index < Counts[SubRequest]; Index++) { NamesLength += (SubRequestNames[SubRequest] + Index)->MaximumLength; } } NtStatus = STATUS_INSUFFICIENT_RESOURCES; OutputNames = MIDL_user_allocate( NamesLength ); if (OutputNames == NULL) { goto LookupIdsInDomainError; } NtStatus = STATUS_SUCCESS; // // Now copy in the Unicode String Structures for the Names returned from // each subrequest. We will later overwrite the Buffer fields in them // when we assign space and move in each Unicode String. // Destination = (PUCHAR) OutputNames; UsedLength = 0; for (SubRequest = 0; SubRequest < SubRequests; SubRequest++) { Source = (PUCHAR) SubRequestNames[SubRequest]; Length = Counts[SubRequest] * sizeof(UNICODE_STRING); UstringStructDisps[SubRequest] = (Destination - Source); RtlMoveMemory( Destination, Source, Length ); Destination += Length; UsedLength += Length; } // // Now copy in the Unicode Strings themselves. These are appended to // the array of Unicode String structures. As we go, update the // Unicode string buffer pointers to point to the copied version // of each string. // for (SubRequest = 0; SubRequest < SubRequests; SubRequest++) { for (Index = 0; Index < Counts[SubRequest]; Index++) { Source = (PUCHAR)(SubRequestNames[SubRequest] + Index)->Buffer; Length = (ULONG)(SubRequestNames[SubRequest] + Index)->MaximumLength; // // It is possible that a returned Unicode String has 0 length // because an Id was not mapped. In this case, skip to the next // one. // if (Length == 0) { continue; } DestUstring = (PUNICODE_STRING) (((PUCHAR)(SubRequestNames[SubRequest] + Index)) + UstringStructDisps[SubRequest]); DestUstring->Buffer = (PWSTR) Destination; ASSERT(UsedLength + Length <= NamesLength); RtlMoveMemory( Destination, Source, Length ); Destination += Length; UsedLength += Length; continue; } } if (!NT_SUCCESS(NtStatus)) { goto LookupIdsInDomainError; } // // Now allocate a single buffer for the Uses // UsesLength = Count * sizeof(SID_NAME_USE); NtStatus = STATUS_INSUFFICIENT_RESOURCES; OutputUses = MIDL_user_allocate( UsesLength ); if (OutputUses == NULL) { goto LookupIdsInDomainError; } NtStatus = STATUS_SUCCESS; // // Now copy in the SID_NAME_USE Structures for the Uses returned from // each subrequest. // Destination = (PUCHAR) OutputUses; UsedLength = 0; for (SubRequest = 0; SubRequest < SubRequests; SubRequest++) { Source = (PUCHAR) SubRequestUses[SubRequest]; Length = Counts[SubRequest] * sizeof(SID_NAME_USE); RtlMoveMemory( Destination, Source, Length ); Destination += Length; UsedLength += Length; } if (!NT_SUCCESS(NtStatus)) { goto LookupIdsInDomainError; } *Names = OutputNames; *Use = OutputUses; // // Determine the final status to return. This is the normal NtStatus code // except that if NtStatus is a success status and none/not all Ids were // mapped, NtStatus will be set to either STATUS_SOME_NOT_MAPPED or // STATUS_NONE_MAPPED as appropriate. If STATUS_SOME_NOT_MAPPED was // returned on at least one call, return that status. If STATUS_NONE_MAPPED // was returned on all calls, return that status. // if (NT_SUCCESS(NtStatus)) { if (SomeNotMappedStatusCount > 0) { NtStatus = STATUS_SOME_NOT_MAPPED; } else if (NoneMappedStatusCount == SubRequests) { NtStatus = STATUS_NONE_MAPPED; } else if (NoneMappedStatusCount > 0) { // // One or more calls returned STATUS_NONE_MAPPED but not all. // So at least one Id was mapped, but not all ids. // NtStatus = STATUS_SOME_NOT_MAPPED; } } LookupIdsInDomainFinish: // // If necessary, free the buffer containing the starting Rid Indices for // each Sub Request. // if (RidIndices != NULL) { MIDL_user_free(RidIndices); RidIndices = NULL; } // // If necessary, free the buffer containing the Rid Counts for // each Sub Request. // if (Counts != NULL) { MIDL_user_free(Counts); Counts = NULL; } // // If necessary, free the buffer containing the offsets from the // source and destination Unicode String structures. // if (UstringStructDisps != NULL) { MIDL_user_free(UstringStructDisps); UstringStructDisps = NULL; } // // If necessary, free the SubRequestNames output returned for each // Sub Request. // if (SubRequestNames != NULL) { for (SubRequest = 0; SubRequest < SubRequests; SubRequest++) { if (SubRequestNames[SubRequest] != NULL) { MIDL_user_free(SubRequestNames[SubRequest]); SubRequestNames[SubRequest] = NULL; } } MIDL_user_free(SubRequestNames); SubRequestNames = NULL; } // // If necessary, free the SubRequestUses output returned for each // Sub Request. // if (SubRequestUses != NULL) { for (SubRequest = 0; SubRequest < SubRequests; SubRequest++) { if (SubRequestUses[SubRequest] != NULL) { MIDL_user_free(SubRequestUses[SubRequest]); SubRequestUses[SubRequest] = NULL; } } MIDL_user_free(SubRequestUses); SubRequestUses = NULL; } return(NtStatus); LookupIdsInDomainError: // // If necessary, free the buffers we would hande returned for // Names and Use. // if (OutputNames != NULL) { MIDL_user_free(OutputNames); OutputNames = NULL; } if (OutputUses != NULL) { MIDL_user_free(OutputUses); OutputUses = NULL; } *Names = NULL; *Use = NULL; goto LookupIdsInDomainFinish; } NTSTATUS SampLookupIdsInDomain( IN SAM_HANDLE DomainHandle, IN ULONG Count, IN PULONG RelativeIds, OUT PUNICODE_STRING *Names, OUT PSID_NAME_USE *Use OPTIONAL ) /*++ Routine Description: This API maps a number of relative IDs to their corresponding names. The use of the name (domain, group, alias, user, or unknown) is also returned. The API stores the actual names in Buffer, then creates an array of UNICODE_STRINGs in the Names OUT parameter. If a relative ID can not be mapped, a NULL value is placed in the slot for the UNICODE_STRING, and STATUS_SOME_NOT_MAPPED is returned. DOMAIN_LOOKUP access to the domain is needed to use this service. Parameters: DomainHandle - A domain handle returned from a previous call to SamOpenDomain. Count - Provides the number of relative IDs to translate. RelativeIds - Array of Count relative IDs to be mapped. Names - Receives a pointer to an array of Count UNICODE_STRINGs that have been filled in. The nth pointer within this array will correspond the nth relative id passed . Each name string buffer will be in a separately allocated block of memory. Any entry is not successfully translated will have a NULL name buffer pointer returned. This Names buffer must be freed using SamFreeMemory() when no longer needed. Use - Optionally, receives a pointer to an array of Count SID_NAME_USE entries that have been filled in with what significance each name has. The nth entry in this array indicates the meaning of the nth name passed. This buffer must be freed when no longer needed using SamFreeMemory(). Return Values: STATUS_SUCCESS - The Service completed successfully. STATUS_ACCESS_DENIED - Caller does not have the appropriate access to complete the operation. STATUS_INVALID_HANDLE - The domain handle passed is invalid. STATUS_SOME_NOT_MAPPED - Some of the names provided could not be mapped. This is a successful return. STATUS_NONE_MAPPED - No names could be mapped. This is an error return. --*/ { NTSTATUS NtStatus; SAMPR_RETURNED_USTRING_ARRAY NameBuffer; SAMPR_ULONG_ARRAY UseBuffer; // // Make sure our parameters are within bounds // if (RelativeIds == NULL) { return(STATUS_INVALID_PARAMETER); } if (Count > SAM_MAXIMUM_LOOKUP_COUNT) { return(STATUS_INSUFFICIENT_RESOURCES); } // // Call the server ... // NameBuffer.Element = NULL; UseBuffer.Element = NULL; RpcTryExcept{ NtStatus = SamrLookupIdsInDomain( (SAMPR_HANDLE)DomainHandle, Count, RelativeIds, &NameBuffer, &UseBuffer ); } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) { NtStatus = I_RpcMapWin32Status(RpcExceptionCode()); } RpcEndExcept; // // What comes back for the "Names" OUT parameter is a two level // structure, the first level of which is on our stack: // // NameBuffer // +-------------+ // | Count | // |-------------| +-------+ // | Element ---|--->| Name0 | --- > (NameBuffer0) // +-------------+ |-------| o // | ... | o // |-------| o // | NameN | --- > (NameBufferN) // +-------+ // // The buffer containing the EntriesRead field is not returned // to our caller. Only the buffers containing name information // are returned. // // NOTE: The buffers containing name information are allocated // by the RPC runtime in a single buffer. This is caused // by a line the the client side .acf file. // // // What comes back for the "Use" OUT parameter is a two level // structure, the first level of which is on our stack: // // UseBuffer // +-------------+ // | Count | // |-------------| +-------+ // | Element ---|--->| Use-0 | // +-------------+ |-------| // | ... | // |-------| // | Use-N | // +-------+ // // The Pointer in the Element field is what gets returned // to our caller. The caller is responsible for deallocating // this when no longer needed. // (*Names) = (PUNICODE_STRING)NameBuffer.Element; if ( ARGUMENT_PRESENT(Use) ) { (*Use) = (PSID_NAME_USE) UseBuffer.Element; } else { if (UseBuffer.Element != NULL) { MIDL_user_free(UseBuffer.Element); UseBuffer.Element = NULL; } } // // Don't force our caller to deallocate things on unexpected // return value. // if (NtStatus != STATUS_SUCCESS && NtStatus != STATUS_SOME_NOT_MAPPED ) { if ( ARGUMENT_PRESENT(Use) ) { (*Use) = NULL; } if (UseBuffer.Element != NULL) { MIDL_user_free(UseBuffer.Element); UseBuffer.Element = NULL; } (*Names) = NULL; if (NameBuffer.Element != NULL) { MIDL_user_free(NameBuffer.Element); NameBuffer.Element = NULL; } } return(SampMapCompletionStatus(NtStatus)); } NTSTATUS SamQueryDisplayInformation ( IN SAM_HANDLE DomainHandle, IN DOMAIN_DISPLAY_INFORMATION DisplayInformation, IN ULONG Index, IN ULONG EntryCount, IN ULONG PreferredMaximumLength, OUT PULONG TotalAvailable, OUT PULONG TotalReturned, OUT PULONG ReturnedEntryCount, OUT PVOID *SortedBuffer ) /*++ Routine Description: This routine provides fast return of information commonly needed to be displayed in user interfaces. NT User Interface has a requirement for quick enumeration of SAM accounts for display in list boxes. (Replication has similar but broader requirements.) Parameters: DomainHandle - A handle to an open domain for DOMAIN_LIST_ACCOUNTS. DisplayInformation - Indicates which information is to be enumerated. Index - The index of the first entry to be retrieved. PreferedMaximumLength - A recommended upper limit to the number of bytes to be returned. The returned information is allocated by this routine. TotalAvailable - Total number of bytes availabe in the specified info class. This parameter is optional (and is not returned) for the following info levels: DomainDisplayOemUser DomainDisplayOemGroup TotalReturned - Number of bytes actually returned for this call. Zero indicates there are no entries with an index as large as that specified. ReturnedEntryCount - Number of entries returned by this call. Zero indicates there are no entries with an index as large as that specified. SortedBuffer - Receives a pointer to a buffer containing a sorted list of the requested information. This buffer is allocated by this routine and contains the following structure: DomainDisplayUser --> An array of ReturnedEntryCount elements of type DOMAIN_DISPLAY_USER. This is followed by the bodies of the various strings pointed to from within the DOMAIN_DISPLAY_USER structures. DomainDisplayMachine --> An array of ReturnedEntryCount elements of type DOMAIN_DISPLAY_MACHINE. This is followed by the bodies of the various strings pointed to from within the DOMAIN_DISPLAY_MACHINE structures. DomainDisplayGroup --> An array of ReturnedEntryCount elements of type DOMAIN_DISPLAY_GROUP. This is followed by the bodies of the various strings pointed to from within the DOMAIN_DISPLAY_GROUP structures. DomainDisplayOemUser --> An array of ReturnedEntryCount elements of type DOMAIN_DISPLAY_OEM_USER. This is followed by the bodies of the various strings pointed to from within the DOMAIN_DISPLAY_OEM_user structures. DomainDisplayOemGroup --> An array of ReturnedEntryCount elements of type DOMAIN_DISPLAY_OEM_GROUP. This is followed by the bodies of the various strings pointed to from within the DOMAIN_DISPLAY_OEM_GROUP structures. Return Values: STATUS_SUCCESS - normal, successful completion. STATUS_ACCESS_DENIED - The specified handle was not opened for the necessary access. STATUS_INVALID_HANDLE - The specified handle is not that of an opened Domain object. STATUS_INVALID_INFO_CLASS - The requested class of information is not legitimate for this service. --*/ { NTSTATUS NtStatus; SAMPR_DISPLAY_INFO_BUFFER DisplayInformationBuffer; ULONG LocalTotalAvailable; // // Check parameters // if ( !ARGUMENT_PRESENT(TotalAvailable) && (DisplayInformation != DomainDisplayOemUser) && (DisplayInformation != DomainDisplayOemGroup) ) { return(STATUS_INVALID_PARAMETER); } if ( !ARGUMENT_PRESENT(TotalReturned) ) { return(STATUS_INVALID_PARAMETER); } if ( !ARGUMENT_PRESENT(ReturnedEntryCount) ) { return(STATUS_INVALID_PARAMETER); } if ( !ARGUMENT_PRESENT(SortedBuffer) ) { return(STATUS_INVALID_PARAMETER); } // // Call the server ... // RpcTryExcept{ if (DisplayInformation <= DomainDisplayMachine) { NtStatus = SamrQueryDisplayInformation( (SAMPR_HANDLE)DomainHandle, DisplayInformation, Index, EntryCount, PreferredMaximumLength, &LocalTotalAvailable, TotalReturned, &DisplayInformationBuffer); } else if (DisplayInformation <= DomainDisplayGroup) { NtStatus = SamrQueryDisplayInformation2( (SAMPR_HANDLE)DomainHandle, DisplayInformation, Index, EntryCount, PreferredMaximumLength, &LocalTotalAvailable, TotalReturned, &DisplayInformationBuffer); } else { NtStatus = SamrQueryDisplayInformation3( (SAMPR_HANDLE)DomainHandle, DisplayInformation, Index, EntryCount, PreferredMaximumLength, &LocalTotalAvailable, TotalReturned, &DisplayInformationBuffer); } if (NT_SUCCESS(NtStatus)) { if (ARGUMENT_PRESENT(TotalAvailable)) { (*TotalAvailable) = LocalTotalAvailable; } switch (DisplayInformation) { case DomainDisplayUser: *ReturnedEntryCount = DisplayInformationBuffer.UserInformation.EntriesRead; *SortedBuffer = DisplayInformationBuffer.UserInformation.Buffer; break; case DomainDisplayMachine: *ReturnedEntryCount = DisplayInformationBuffer.MachineInformation.EntriesRead; *SortedBuffer = DisplayInformationBuffer.MachineInformation.Buffer; break; case DomainDisplayGroup: *ReturnedEntryCount = DisplayInformationBuffer.GroupInformation.EntriesRead; *SortedBuffer = DisplayInformationBuffer.GroupInformation.Buffer; break; case DomainDisplayOemUser: *ReturnedEntryCount = DisplayInformationBuffer.OemUserInformation.EntriesRead; *SortedBuffer = DisplayInformationBuffer.OemUserInformation.Buffer; break; case DomainDisplayOemGroup: *ReturnedEntryCount = DisplayInformationBuffer.OemGroupInformation.EntriesRead; *SortedBuffer = DisplayInformationBuffer.OemGroupInformation.Buffer; break; } } else { *ReturnedEntryCount = 0; *SortedBuffer = NULL; } } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) { // // If the exception indicates the server doesn't have // the selected api, that means the server doesn't know // about the info level we passed. Set our completion // status appropriately. // if (RpcExceptionCode() == RPC_S_INVALID_LEVEL || RpcExceptionCode() == RPC_S_PROCNUM_OUT_OF_RANGE || RpcExceptionCode() == RPC_NT_PROCNUM_OUT_OF_RANGE ) { NtStatus = STATUS_INVALID_INFO_CLASS; } else { NtStatus = I_RpcMapWin32Status(RpcExceptionCode()); } } RpcEndExcept; return(SampMapCompletionStatus(NtStatus)); } NTSTATUS SamGetDisplayEnumerationIndex ( IN SAM_HANDLE DomainHandle, IN DOMAIN_DISPLAY_INFORMATION DisplayInformation, IN PUNICODE_STRING Prefix, OUT PULONG Index ) /*++ Routine Description: This routine returns the index of the entry which alphabetically immediatly preceeds a specified prefix. If no such entry exists, then zero is returned as the index. Parameters: DomainHandle - A handle to an open domain for DOMAIN_LIST_ACCOUNTS. DisplayInformation - Indicates which sorted information class is to be searched. Prefix - The prefix to compare. Index - Receives the index of the entry of the information class with a LogonName (or MachineName) which immediatly preceeds the provided prefix string. If there are no elements which preceed the prefix, then zero is returned. Return Values: STATUS_SUCCESS - normal, successful completion. STATUS_ACCESS_DENIED - The specified handle was not opened for the necessary access. STATUS_INVALID_HANDLE - The specified handle is not that of an opened Domain object. STATUS_NO_MORE_ENTRIES - There are no entries for this information class. The returned index is invalid. --*/ { NTSTATUS NtStatus; // // Check parameters // if ( !ARGUMENT_PRESENT(Prefix) ) { return(STATUS_INVALID_PARAMETER); } if ( !ARGUMENT_PRESENT(Index) ) { return(STATUS_INVALID_PARAMETER); } // // Call the server ... // RpcTryExcept{ if (DisplayInformation <= DomainDisplayMachine) { // // Info levels supported via original API in NT1.0 // NtStatus = SamrGetDisplayEnumerationIndex ( (SAMPR_HANDLE)DomainHandle, DisplayInformation, (PRPC_UNICODE_STRING)Prefix, Index ); } else { // // Info levels added in NT1.0A via new API // NtStatus = SamrGetDisplayEnumerationIndex2 ( (SAMPR_HANDLE)DomainHandle, DisplayInformation, (PRPC_UNICODE_STRING)Prefix, Index ); } } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) { NtStatus = I_RpcMapWin32Status(RpcExceptionCode()); } RpcEndExcept; return(SampMapCompletionStatus(NtStatus)); } NTSTATUS SamOpenGroup( IN SAM_HANDLE DomainHandle, IN ACCESS_MASK DesiredAccess, IN ULONG GroupId, OUT PSAM_HANDLE GroupHandle ) /*++ Routine Description: This API opens an existing group in the account database. The group is specified by a ID value that is relative to the SID of the domain. The operations that will be performed on the group must be declared at this time. This call returns a handle to the newly opened group that may be used for successive operations on the group. This handle may be closed with the SamCloseHandle API. Parameters: DomainHandle - A domain handle returned from a previous call to SamOpenDomain. DesiredAccess - Is an access mask indicating which access types are desired to the group. These access types are reconciled with the Discretionary Access Control list of the group to determine whether the accesses will be granted or denied. GroupId - Specifies the relative ID value of the group to be opened. GroupHandle - Receives a handle referencing the newly opened group. This handle will be required in successive calls to operate on the group. Return Values: STATUS_SUCCESS - The group was successfully opened. STATUS_ACCESS_DENIED - Caller does not have the appropriate access to complete the operation. STATUS_NO_SUCH_GROUP - The specified group does not exist. STATUS_INVALID_HANDLE - The domain handle passed is invalid. --*/ { NTSTATUS NtStatus; // // Call the server ... // RpcTryExcept{ (*GroupHandle) = 0; NtStatus = SamrOpenGroup( (SAMPR_HANDLE)DomainHandle, DesiredAccess, GroupId, (SAMPR_HANDLE *)GroupHandle ); } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) { NtStatus = I_RpcMapWin32Status(RpcExceptionCode()); } RpcEndExcept; return(SampMapCompletionStatus(NtStatus)); } NTSTATUS SamQueryInformationGroup( IN SAM_HANDLE GroupHandle, IN GROUP_INFORMATION_CLASS GroupInformationClass, OUT PVOID * Buffer ) /*++ Routine Description: This API retrieves information on the group specified. Parameters: GroupHandle - The handle of an opened group to operate on. GroupInformationClass - Class of information to retrieve. The accesses required for each class is shown below: Info Level Required Access Type ---------------------- ---------------------- GroupGeneralInformation GROUP_READ_INFORMATION GroupNameInformation GROUP_READ_INFORMATION GroupAttributeInformation GROUP_READ_INFORMATION GroupAdminInformation GROUP_READ_INFORMATION Buffer - Receives a pointer to a buffer containing the requested information. When this information is no longer needed, this buffer must be freed using SamFreeMemory(). Return Values: STATUS_SUCCESS - The Service completed successfully. STATUS_ACCESS_DENIED - Caller does not have the appropriate access to complete the operation. STATUS_INVALID_HANDLE - The handle passed is invalid. STATUS_INVALID_INFO_CLASS - The class provided was invalid. --*/ { NTSTATUS NtStatus; // // Call the server ... // (*Buffer) = NULL; RpcTryExcept{ NtStatus = SamrQueryInformationGroup( (SAMPR_HANDLE)GroupHandle, GroupInformationClass, (PSAMPR_GROUP_INFO_BUFFER *)Buffer ); } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) { NtStatus = I_RpcMapWin32Status(RpcExceptionCode()); } RpcEndExcept; return(SampMapCompletionStatus(NtStatus)); } NTSTATUS SamSetInformationGroup( IN SAM_HANDLE GroupHandle, IN GROUP_INFORMATION_CLASS GroupInformationClass, IN PVOID Buffer ) /*++ Routine Description: This API allows the caller to modify group information. Parameters: GroupHandle - The handle of an opened group to operate on. GroupInformationClass - Class of information to retrieve. The accesses required for each class is shown below: Info Level Required Access Type ------------------------ ------------------------- GroupGeneralInformation (can't write) GroupNameInformation GROUP_WRITE_ACCOUNT GroupAttributeInformation GROUP_WRITE_ACCOUNT GroupAdminInformation GROUP_WRITE_ACCOUNT Buffer - Buffer where information retrieved is placed. Return Values: STATUS_SUCCESS - The Service completed successfully. STATUS_ACCESS_DENIED - Caller does not have the appropriate access to complete the operation. STATUS_INVALID_INFO_CLASS - The class provided was invalid. STATUS_INVALID_HANDLE - The handle passed is invalid. STATUS_NO_SUCH_GROUP - The group specified is unknown. STATUS_SPECIAL_GROUP - The group specified is a special group and cannot be operated on in the requested fashion. STATUS_INVALID_DOMAIN_STATE - The domain server is not in the correct state (disabled or enabled) to perform the requested operation. The domain server must be enabled for this operation STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the incorrect role (primary or backup) to perform the requested operation. --*/ { NTSTATUS NtStatus; // // Call the server ... // RpcTryExcept{ NtStatus = SamrSetInformationGroup( (SAMPR_HANDLE)GroupHandle, GroupInformationClass, Buffer ); } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) { NtStatus = I_RpcMapWin32Status(RpcExceptionCode()); } RpcEndExcept; return(SampMapCompletionStatus(NtStatus)); } NTSTATUS SamAddMemberToGroup( IN SAM_HANDLE GroupHandle, IN ULONG MemberId, IN ULONG Attributes ) /*++ Routine Description: This API adds a member to a group. Note that this API requires the GROUP_ADD_MEMBER access type for the group. Parameters: GroupHandle - The handle of an opened group to operate on. MemberId - Relative ID of the member to add. Attributes - The attributes of the group assigned to the user. The attributes assigned here must be compatible with the attributes assigned to the group as a whole. Compatible attribute assignments are: Mandatory - If the Mandatory attribute is assigned to the group as a whole, then it must be assigned to the group for each member of the group. EnabledByDefault - This attribute may be set to any value for each member of the group. It does not matter what the attribute value for the group as a whole is. Enabled - This attribute may be set to any value for each member of the group. It does not matter what the attribute value for the group as a whole is. Owner - The Owner attribute may be assigned any value. However, if the Owner attribute of the group as a whole is not set, then the value assigned to members is ignored. Return Values: STATUS_SUCCESS - The Service completed successfully. STATUS_ACCESS_DENIED - Caller does not have the appropriate access to complete the operation. STATUS_INVALID_HANDLE - The handle passed is invalid. STATUS_NO_SUCH_MEMBER - The member specified is unknown. STATUS_MEMBER_IN_GROUP - The member already belongs to the group. STATUS_INVALID_GROUP_ATTRIBUTES - Indicates the group attribute values being assigned to the member are not compatible with the attribute values of the group as a whole. STATUS_INVALID_DOMAIN_STATE - The domain server is not in the correct state (disabled or enabled) to perform the requested operation. The domain server must be enabled for this operation STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the incorrect role (primary or backup) to perform the requested operation. --*/ { NTSTATUS NtStatus; // // Call the server ... // RpcTryExcept{ NtStatus = SamrAddMemberToGroup( (SAMPR_HANDLE)GroupHandle, MemberId, Attributes ); } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) { NtStatus = I_RpcMapWin32Status(RpcExceptionCode()); } RpcEndExcept; return(SampMapCompletionStatus(NtStatus)); } NTSTATUS SamDeleteGroup( IN SAM_HANDLE GroupHandle ) /*++ Routine Description: This API removes a group from the account database. There may be no members in the group or the deletion request will be rejected. Note that this API requires DELETE access to the specific group being deleted. Parameters: GroupHandle - The handle of an opened group to operate on. Return Values: STATUS_SUCCESS - The Service completed successfully. STATUS_ACCESS_DENIED - Caller does not have the appropriate access to complete the operation. STATUS_INVALID_HANDLE - The handle passed is invalid. STATUS_SPECIAL_GROUP - The group specified is a special group and cannot be operated on in the requested fashion. STATUS_MEMBER_IN_GROUP - The group still has members. A group may not be deleted unless it has no members. STATUS_INVALID_DOMAIN_STATE - The domain server is not in the correct state (disabled or enabled) to perform the requested operation. The domain server must be enabled for this operation STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the incorrect role (primary or backup) to perform the requested operation. --*/ { NTSTATUS NtStatus; SAMPR_HANDLE LocalHandle; LocalHandle = (SAMPR_HANDLE)GroupHandle; if (LocalHandle == 0) { return(STATUS_INVALID_HANDLE); } // // Call the server ... // RpcTryExcept{ NtStatus = SamrDeleteGroup( &LocalHandle ); } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) { NtStatus = I_RpcMapWin32Status(RpcExceptionCode()); } RpcEndExcept; return(SampMapCompletionStatus(NtStatus)); } NTSTATUS SamRemoveMemberFromGroup( IN SAM_HANDLE GroupHandle, IN ULONG MemberId ) /*++ Routine Description: This API removes a single member from a group. Note that this API requires the GROUP_REMOVE_MEMBER access type. Parameters: GroupHandle - The handle of an opened group to operate on. MemberId - Relative ID of the member to remove. Return Values: STATUS_SUCCESS - The Service completed successfully. STATUS_ACCESS_DENIED - Caller does not have the appropriate access to complete the operation. STATUS_INVALID_HANDLE - The handle passed is invalid. STATUS_SPECIAL_GROUP - The group specified is a special group and cannot be operated on in the requested fashion. STATUS_MEMBER_NOT_IN_GROUP - The specified user does not belong to the group. STATUS_MEMBERS_PRIMARY_GROUP - A user may not be removed from its primary group. The primary group of the user account must first be changed. STATUS_INVALID_DOMAIN_STATE - The domain server is not in the correct state (disabled or enabled) to perform the requested operation. The domain server must be enabled for this operation STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the incorrect role (primary or backup) to perform the requested operation. --*/ { NTSTATUS NtStatus; // // Call the server ... // RpcTryExcept{ NtStatus = SamrRemoveMemberFromGroup( (SAMPR_HANDLE)GroupHandle, MemberId ); } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) { NtStatus = I_RpcMapWin32Status(RpcExceptionCode()); } RpcEndExcept; return(SampMapCompletionStatus(NtStatus)); } NTSTATUS SamGetMembersInGroup( IN SAM_HANDLE GroupHandle, OUT PULONG * MemberIds, OUT PULONG * Attributes, OUT PULONG MemberCount ) /*++ Routine Description: This API lists all the members in a group. This API requires GROUP_LIST_MEMBERS access to the group. Parameters: GroupHandle - The handle of an opened group to operate on. MemberIds - Receives a pointer to a buffer containing An array of ULONGs. These ULONGs contain the relative IDs of the members of the group. When this information is no longer needed, this buffer must be freed using SamFreeMemory(). Attributes - Receives a pointer to a buffer containing an array of ULONGs. These ULONGs contain the attributes of the corresponding member ID returned via the MemberId paramter. MemberCount - number of members in the group (and, thus, the number relative IDs returned). Return Values: STATUS_SUCCESS - The Service completed successfully, and there are no additional entries. STATUS_ACCESS_DENIED - Caller does not have privilege required to request that data. STATUS_INVALID_HANDLE - The handle passed is invalid. --*/ { NTSTATUS NtStatus; PSAMPR_GET_MEMBERS_BUFFER GetMembersBuffer; // // Call the server ... // GetMembersBuffer = NULL; RpcTryExcept{ NtStatus = SamrGetMembersInGroup( (SAMPR_HANDLE)GroupHandle, &GetMembersBuffer ); // // What we get back is the following: // // +-------------+ // --------->| MemberCount | // |-------------+ +-------+ // | Members --|------------------->| Rid-0 | // |-------------| +------------+ | ... | // | Attributes-|-->| Attribute0 | | | // +-------------+ | ... | | Rid-N | // | AttributeN | +-------+ // +------------+ // // Each block allocated with MIDL_user_allocate. We return the // RID and ATTRIBUTE blocks, and free the block containing // the MemberCount ourselves. // if (NT_SUCCESS(NtStatus)) { (*MemberCount) = GetMembersBuffer->MemberCount; (*MemberIds) = GetMembersBuffer->Members; (*Attributes) = GetMembersBuffer->Attributes; MIDL_user_free( GetMembersBuffer ); } else { // // Deallocate any returned buffers on error // if (GetMembersBuffer != NULL) { if (GetMembersBuffer->Members != NULL) { MIDL_user_free(GetMembersBuffer->Members); } if (GetMembersBuffer->Attributes != NULL) { MIDL_user_free(GetMembersBuffer->Attributes); } MIDL_user_free(GetMembersBuffer); } } } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) { NtStatus = I_RpcMapWin32Status(RpcExceptionCode()); } RpcEndExcept; return(SampMapCompletionStatus(NtStatus)); } NTSTATUS SamSetMemberAttributesOfGroup( IN SAM_HANDLE GroupHandle, IN ULONG MemberId, IN ULONG Attributes ) /*++ Routine Description: This routine modifies the group attributes of a member of the group. Parameters: GroupHandle - The handle of an opened group to operate on. This handle must be open for GROUP_ADD_MEMBER access to the group. MemberId - Contains the relative ID of member whose attributes are to be modified. Attributes - The group attributes to set for the member. These attributes must not conflict with the attributes of the group as a whole. See SamAddMemberToGroup() for more information on compatible attribute settings. Return Values: STATUS_SUCCESS - The Service completed successfully, and there are no additional entries. STATUS_INVALID_INFO_CLASS - The class provided was invalid. STATUS_ACCESS_DENIED - Caller does not have the appropriate access to complete the operation. STATUS_INVALID_HANDLE - The handle passed is invalid. STATUS_NO_SUCH_USER - The user specified does not exist. STATUS_MEMBER_NOT_IN_GROUP - Indicates the specified relative ID is not a member of the group. STATUS_INVALID_DOMAIN_STATE - The domain server is not in the correct state (disabled or enabled) to perform the requested operation. The domain server must be enabled for this operation STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the incorrect role (primary or backup) to perform the requested operation. --*/ { NTSTATUS NtStatus; // // Call the server ... // RpcTryExcept{ NtStatus = SamrSetMemberAttributesOfGroup( (SAMPR_HANDLE)GroupHandle, MemberId, Attributes ); } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) { NtStatus = I_RpcMapWin32Status(RpcExceptionCode()); } RpcEndExcept; return(SampMapCompletionStatus(NtStatus)); } NTSTATUS SamOpenAlias( IN SAM_HANDLE DomainHandle, IN ACCESS_MASK DesiredAccess, IN ULONG AliasId, OUT PSAM_HANDLE AliasHandle ) /*++ Routine Description: This API opens an existing Alias object. The Alias is specified by a ID value that is relative to the SID of the domain. The operations that will be performed on the Alias must be declared at this time. This call returns a handle to the newly opened Alias that may be used for successive operations on the Alias. This handle may be closed with the SamCloseHandle API. Parameters: DomainHandle - A domain handle returned from a previous call to SamOpenDomain. DesiredAccess - Is an access mask indicating which access types are desired to the alias. AliasId - Specifies the relative ID value of the Alias to be opened. AliasHandle - Receives a handle referencing the newly opened Alias. This handle will be required in successive calls to operate on the Alias. Return Values: STATUS_SUCCESS - The Alias was successfully opened. STATUS_ACCESS_DENIED - Caller does not have the appropriate access to complete the operation. STATUS_NO_SUCH_ALIAS - The specified Alias does not exist. STATUS_INVALID_HANDLE - The domain handle passed is invalid. --*/ { NTSTATUS NtStatus; // // Call the server ... // RpcTryExcept{ (*AliasHandle) = 0; NtStatus = SamrOpenAlias( (SAMPR_HANDLE)DomainHandle, DesiredAccess, AliasId, (SAMPR_HANDLE *)AliasHandle ); } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) { NtStatus = I_RpcMapWin32Status(RpcExceptionCode()); } RpcEndExcept; return(SampMapCompletionStatus(NtStatus)); } NTSTATUS SamQueryInformationAlias( IN SAM_HANDLE AliasHandle, IN ALIAS_INFORMATION_CLASS AliasInformationClass, OUT PVOID * Buffer ) /*++ Routine Description: This API retrieves information on the alias specified. Parameters: AliasHandle - The handle of an opened alias to operate on. AliasInformationClass - Class of information to retrieve. The accesses required for each class is shown below: Info Level Required Access Type ---------------------- ---------------------- AliasGeneralInformation ALIAS_READ_INFORMATION AliasNameInformation ALIAS_READ_INFORMATION AliasAdminInformation ALIAS_READ_INFORMATION Buffer - Receives a pointer to a buffer containing the requested information. When this information is no longer needed, this buffer and any memory pointed to through this buffer must be freed using SamFreeMemory(). Return Values: STATUS_SUCCESS - The Service completed successfully. STATUS_ACCESS_DENIED - Caller does not have the appropriate access to complete the operation. STATUS_INVALID_HANDLE - The handle passed is invalid. STATUS_INVALID_INFO_CLASS - The class provided was invalid. --*/ { NTSTATUS NtStatus; // // Call the server ... // (*Buffer) = NULL; RpcTryExcept{ NtStatus = SamrQueryInformationAlias( (SAMPR_HANDLE)AliasHandle, AliasInformationClass, (PSAMPR_ALIAS_INFO_BUFFER *)Buffer ); } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) { NtStatus = I_RpcMapWin32Status(RpcExceptionCode()); } RpcEndExcept; return(SampMapCompletionStatus(NtStatus)); } NTSTATUS SamSetInformationAlias( IN SAM_HANDLE AliasHandle, IN ALIAS_INFORMATION_CLASS AliasInformationClass, IN PVOID Buffer ) /*++ Routine Description: This API allows the caller to modify alias information. Parameters: AliasHandle - The handle of an opened alias to operate on. AliasInformationClass - Class of information to retrieve. The accesses required for each class is shown below: Info Level Required Access Type ------------------------ ------------------------- AliasGeneralInformation (can't write) AliasNameInformation ALIAS_WRITE_ACCOUNT AliasAdminInformation ALIAS_WRITE_ACCOUNT Buffer - Buffer where information retrieved is placed. Return Values: STATUS_SUCCESS - The Service completed successfully. STATUS_ACCESS_DENIED - Caller does not have the appropriate access to complete the operation. STATUS_INVALID_INFO_CLASS - The class provided was invalid. STATUS_INVALID_HANDLE - The handle passed is invalid. STATUS_NO_SUCH_ALIAS - The alias specified is unknown. STATUS_SPECIAL_ALIAS - The alias specified is a special alias and cannot be operated on in the requested fashion. STATUS_INVALID_DOMAIN_STATE - The domain server is not in the correct state (disabled or enabled) to perform the requested operation. The domain server must be enabled for this operation STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the incorrect role (primary or backup) to perform the requested operation. --*/ { NTSTATUS NtStatus; // // Call the server ... // RpcTryExcept{ NtStatus = SamrSetInformationAlias( (SAMPR_HANDLE)AliasHandle, AliasInformationClass, Buffer ); } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) { NtStatus = I_RpcMapWin32Status(RpcExceptionCode()); } RpcEndExcept; return(SampMapCompletionStatus(NtStatus)); } NTSTATUS SamDeleteAlias( IN SAM_HANDLE AliasHandle ) /*++ Routine Description: This API deletes an Alias from the account database. The Alias does not have to be empty. Note that following this call, the AliasHandle is no longer valid. Parameters: AliasHandle - The handle of an opened Alias to operate on. Return Values: STATUS_SUCCESS - The Service completed successfully. STATUS_ACCESS_DENIED - Caller does not have the appropriate access to complete the operation. STATUS_INVALID_HANDLE - The handle passed is invalid. STATUS_INVALID_DOMAIN_STATE - The domain server is not in the correct state (disabled or enabled) to perform the requested operation. The domain server must be enabled for this operation STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the incorrect role (primary or backup) to perform the requested operation. --*/ { NTSTATUS NtStatus; SAMPR_HANDLE LocalHandle; LocalHandle = (SAMPR_HANDLE)AliasHandle; if (LocalHandle == 0) { return(STATUS_INVALID_HANDLE); } // // Call the server ... // RpcTryExcept{ NtStatus = SamrDeleteAlias( &LocalHandle ); } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) { NtStatus = I_RpcMapWin32Status(RpcExceptionCode()); } RpcEndExcept; return(SampMapCompletionStatus(NtStatus)); } NTSTATUS SamAddMemberToAlias( IN SAM_HANDLE AliasHandle, IN PSID MemberId ) /*++ Routine Description: This API adds a member to an Alias. Parameters: AliasHandle - The handle of an opened Alias object to operate on. The handle must be open for ALIAS_ADD_MEMBER. MemberId - The SID of the member to add. Return Values: STATUS_SUCCESS - The Service completed successfully. STATUS_ACCESS_DENIED - Caller does not have the appropriate access to complete the operation. STATUS_INVALID_HANDLE - The handle passed is invalid. STATUS_MEMBER_IN_ALIAS - The member already belongs to the Alias. STATUS_INVALID_DOMAIN_STATE - The domain server is not in the correct state (disabled or enabled) to perform the requested operation. The domain server must be enabled for this operation. STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the incorrect role (primary or backup) to perform the requested operation. --*/ { NTSTATUS NtStatus; // // Call the server ... // RpcTryExcept{ NtStatus = SamrAddMemberToAlias( (SAMPR_HANDLE)AliasHandle, MemberId ); } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) { NtStatus = I_RpcMapWin32Status(RpcExceptionCode()); } RpcEndExcept; return(SampMapCompletionStatus(NtStatus)); } NTSTATUS SamRemoveMemberFromAlias( IN SAM_HANDLE AliasHandle, IN PSID MemberId ) /*++ Routine Description: This API removes a member from an Alias. Parameters: AliasHandle - The handle of an opened Alias object to operate on. The handle must be open for ALIAS_REMOVE_MEMBER. MemberId - The SID of the member to remove. Return Values: STATUS_SUCCESS - The Service completed successfully. STATUS_ACCESS_DENIED - Caller does not have the appropriate access to complete the operation. STATUS_INVALID_HANDLE - The handle passed is invalid. STATUS_SPECIAL_ALIAS - The group specified is a special alias and cannot be operated on in the requested fashion. STATUS_MEMBER_NOT_IN_ALIAS - The specified member does not belong to the Alias. STATUS_INVALID_DOMAIN_STATE - The domain server is not in the correct state (disabled or enabled) to perform the requested operation. The domain server must be enabled for this operation. STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the incorrect role (primary or backup) to perform the requested operation. --*/ { NTSTATUS NtStatus; // // Call the server ... // RpcTryExcept{ NtStatus = SamrRemoveMemberFromAlias( (SAMPR_HANDLE)AliasHandle, MemberId ); } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) { NtStatus = I_RpcMapWin32Status(RpcExceptionCode()); } RpcEndExcept; return(SampMapCompletionStatus(NtStatus)); } NTSTATUS SamRemoveMemberFromForeignDomain( IN SAM_HANDLE DomainHandle, IN PSID MemberId ) /*++ Routine Description: This API removes a member from an all Aliases in the domain specified. Parameters: DomainHandle - The handle of an opened domain to operate in. All aliases in the domain that the member is a part of must be accessible by the caller with ALIAS_REMOVE_MEMBER. MemberId - The SID of the member to remove. Return Values: STATUS_SUCCESS - The Service completed successfully. STATUS_ACCESS_DENIED - Caller does not have the appropriate access to complete the operation. STATUS_INVALID_HANDLE - The handle passed is invalid. STATUS_INVALID_DOMAIN_STATE - The domain server is not in the correct state (disabled or enabled) to perform the requested operation. The domain server must be enabled for this operation. STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the incorrect role (primary or backup) to perform the requested operation. --*/ { NTSTATUS NtStatus; // // Call the server ... // RpcTryExcept{ NtStatus = SamrRemoveMemberFromForeignDomain( (SAMPR_HANDLE)DomainHandle, MemberId ); } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) { NtStatus = I_RpcMapWin32Status(RpcExceptionCode()); } RpcEndExcept; return(SampMapCompletionStatus(NtStatus)); } NTSTATUS SamGetMembersInAlias( IN SAM_HANDLE AliasHandle, OUT PSID **MemberIds, OUT PULONG MemberCount ) /*++ Routine Description: This API lists all members in an Alias. This API requires ALIAS_LIST_MEMBERS access to the Alias. Parameters: AliasHandle - The handle of an opened Alias to operate on. MemberIds - Receives a pointer to a buffer containing an array of pointers to SIDs. These SIDs are the SIDs of the members of the Alias. When this information is no longer needed, this buffer must be freed using SamFreeMemory(). MemberCount - number of members in the Alias (and, thus, the number of relative IDs returned). Return Values: STATUS_SUCCESS - The Service completed successfully, and there are no additional entries. STATUS_ACCESS_DENIED - Caller does not have privilege required to request that data. STATUS_INVALID_HANDLE - The handle passed is invalid. --*/ { NTSTATUS NtStatus; SAMPR_PSID_ARRAY MembersBuffer; // // Validate parameters // if ( !ARGUMENT_PRESENT(MemberIds) ) { return(STATUS_INVALID_PARAMETER_2); } if ( !ARGUMENT_PRESENT(MemberCount) ) { return(STATUS_INVALID_PARAMETER_3); } RpcTryExcept{ // // Prepare for failure // *MemberIds = NULL; *MemberCount = 0; // // Call the server ... // MembersBuffer.Sids = NULL; NtStatus = SamrGetMembersInAlias( (SAMPR_HANDLE)AliasHandle, &MembersBuffer ); if (NT_SUCCESS(NtStatus)) { // // Return the member list // *MemberCount = MembersBuffer.Count; *MemberIds = (PSID *)(MembersBuffer.Sids); } } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) { NtStatus = I_RpcMapWin32Status(RpcExceptionCode()); } RpcEndExcept; return(SampMapCompletionStatus(NtStatus)); } NTSTATUS SamAddMultipleMembersToAlias( IN SAM_HANDLE AliasHandle, IN PSID *MemberIds, IN ULONG MemberCount ) /*++ Routine Description: This API adds the SIDs listed in MemberIds to the specified Alias. Parameters: AliasHandle - The handle of an opened Alias to operate on. MemberIds - Points to an array of SID pointers containing MemberCount entries. MemberCount - number of members in the array. Return Values: STATUS_SUCCESS - The Service completed successfully. All of the listed members are now members of the alias. However, some of the members may already have been members of the alias (this is NOT an error or warning condition). STATUS_ACCESS_DENIED - Caller does not have the object open for the required access. STATUS_INVALID_HANDLE - The handle passed is invalid. STATUS_INVALID_MEMBER - The member has the wrong account type. STATUS_INVALID_SID - The member sid is corrupted. STATUS_INVALID_DOMAIN_STATE - The domain server is not in the correct state (disabled or enabled) to perform the requested operation. The domain server must be enabled for this operation STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the incorrect role (primary or backup) to perform the requested operation. --*/ { NTSTATUS NtStatus; SAMPR_PSID_ARRAY MembersBuffer; // // Validate parameters // if ( !ARGUMENT_PRESENT(MemberIds) ) { return(STATUS_INVALID_PARAMETER_2); } RpcTryExcept{ // // Call the server ... // MembersBuffer.Count = MemberCount; MembersBuffer.Sids = (PSAMPR_SID_INFORMATION)MemberIds; NtStatus = SamrAddMultipleMembersToAlias( (SAMPR_HANDLE)AliasHandle, &MembersBuffer ); } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) { NtStatus = I_RpcMapWin32Status(RpcExceptionCode()); } RpcEndExcept; return(SampMapCompletionStatus(NtStatus)); } NTSTATUS SamRemoveMultipleMembersFromAlias( IN SAM_HANDLE AliasHandle, IN PSID *MemberIds, IN ULONG MemberCount ) /*++ Routine Description: This API Removes the SIDs listed in MemberIds from the specified Alias. Parameters: AliasHandle - The handle of an opened Alias to operate on. MemberIds - Points to an array of SID pointers containing MemberCount entries. MemberCount - number of members in the array. Return Values: STATUS_SUCCESS - The Service completed successfully. All of the listed members are now NOT members of the alias. However, some of the members may already have not been members of the alias (this is NOT an error or warning condition). STATUS_ACCESS_DENIED - Caller does not have the object open for the required access. STATUS_INVALID_HANDLE - The handle passed is invalid. --*/ { NTSTATUS NtStatus; SAMPR_PSID_ARRAY MembersBuffer; // // Validate parameters // if ( !ARGUMENT_PRESENT(MemberIds) ) { return(STATUS_INVALID_PARAMETER_2); } RpcTryExcept{ // // Call the server ... // MembersBuffer.Count = MemberCount; MembersBuffer.Sids = (PSAMPR_SID_INFORMATION)MemberIds; NtStatus = SamrRemoveMultipleMembersFromAlias( (SAMPR_HANDLE)AliasHandle, &MembersBuffer ); } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) { NtStatus = I_RpcMapWin32Status(RpcExceptionCode()); } RpcEndExcept; return(SampMapCompletionStatus(NtStatus)); } NTSTATUS SamOpenUser( IN SAM_HANDLE DomainHandle, IN ACCESS_MASK DesiredAccess, IN ULONG UserId, OUT PSAM_HANDLE UserHandle ) /*++ Routine Description: This API opens an existing user in the account database. The user is specified by SID value. The operations that will be performed on the user must be declared at this time. This call returns a handle to the newly opened user that may be used for successive operations on the user. This handle may be closed with the SamCloseHandle API. Parameters: DomainHandle - A domain handle returned from a previous call to SamOpenDomain. DesiredAccess - Is an access mask indicating which access types are desired to the user. These access types are reconciled with the Discretionary Access Control list of the user to determine whether the accesses will be granted or denied. UserId - Specifies the relative ID value of the user account to be opened. UserHandle - Receives a handle referencing the newly opened User. This handle will be required in successive calls to operate on the user. Return Values: STATUS_SUCCESS - The group was successfully opened. STATUS_ACCESS_DENIED - Caller does not have the appropriate access to complete the operation. STATUS_NO_SUCH_USER - The specified user does not exist. STATUS_INVALID_HANDLE - The domain handle passed is invalid. --*/ { NTSTATUS NtStatus; // // Call the server ... // RpcTryExcept{ (*UserHandle) = 0; NtStatus = SamrOpenUser( (SAMPR_HANDLE)DomainHandle, DesiredAccess, UserId, (SAMPR_HANDLE *)UserHandle ); } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) { NtStatus = I_RpcMapWin32Status(RpcExceptionCode()); } RpcEndExcept; return(SampMapCompletionStatus(NtStatus)); } NTSTATUS SamDeleteUser( IN SAM_HANDLE UserHandle ) /*++ Routine Description: This API deletes a user from the account database. If the account being deleted is the last account in the database in the ADMIN group, then STATUS_LAST_ADMIN is returned, and the Delete fails. Note that this API required DOMAIN_DELETE_USER access. Note that following this call, the UserHandle is no longer valid. Parameters: UserHandle - The handle of an opened user to operate on. The handle must be opened for DELETE access. Return Values: STATUS_SUCCESS - The Service completed successfully. STATUS_ACCESS_DENIED - Caller does not have the appropriate access to complete the operation. STATUS_INVALID_HANDLE - The handle passed is invalid. STATUS_LAST_ADMIN - Cannot delete the last administrator. STATUS_INVALID_DOMAIN_STATE - The domain server is not in the correct state (disabled or enabled) to perform the requested operation. The domain server must be enabled for this operation STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the incorrect role (primary or backup) to perform the requested operation. --*/ { NTSTATUS NtStatus; SAMPR_HANDLE LocalHandle; LocalHandle = (SAMPR_HANDLE)UserHandle; if (LocalHandle == 0) { return(STATUS_INVALID_HANDLE); } // // Call the server ... // RpcTryExcept{ NtStatus = SamrDeleteUser( &LocalHandle ); } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) { NtStatus = I_RpcMapWin32Status(RpcExceptionCode()); } RpcEndExcept; return(SampMapCompletionStatus(NtStatus)); } NTSTATUS SamQueryInformationUser( IN SAM_HANDLE UserHandle, IN USER_INFORMATION_CLASS UserInformationClass, OUT PVOID * Buffer ) /*++ Routine Description: This API looks up some level of information about a particular user. Parameters: UserHandle - The handle of an opened user to operate on. UserInformationClass - Class of information desired about this user. The accesses required for each class is shown below: Info Level Required Access Type ---------------------- -------------------------- UserGeneralInformation USER_READ_GENERAL UserPreferencesInformation USER_READ_PREFERENCES UserLogonInformation USER_READ_GENERAL and USER_READ_PREFERENCES and USER_READ_LOGON UserLogonHoursInformation USER_READ_LOGON UserAccountInformation USER_READ_GENERAL and USER_READ_PREFERENCES and USER_READ_LOGON and USER_READ_ACCOUNT UserParametersInformation USER_READ_ACCOUNT UserNameInformation USER_READ_GENERAL UserAccountNameInformation USER_READ_GENERAL UserFullNameInformation USER_READ_GENERAL UserPrimaryGroupInformation USER_READ_GENERAL UserHomeInformation USER_READ_LOGON UserScriptInformation USER_READ_LOGON UserProfileInformation USER_READ_LOGON UserAdminCommentInformation USER_READ_GENERAL UserWorkStationsInformation USER_READ_LOGON UserSetPasswordInformation (Can't query) UserControlInformation USER_READ_ACCOUNT UserExpiresInformation USER_READ_ACCOUNT UserInternal1Information (trusted client use only) UserInternal2Information (trusted client use only) UserAllInformation Will return fields that user has access to. Buffer - Receives a pointer to a buffer containing the requested information. When this information is no longer needed, this buffer must be freed using SamFreeMemory(). Return Values: STATUS_SUCCESS - The Service completed successfully. STATUS_ACCESS_DENIED - Caller does not have the appropriate access to complete the operation. STATUS_INVALID_HANDLE - The handle passed is invalid. STATUS_INVALID_INFO_CLASS - The class provided was invalid. --*/ { NTSTATUS NtStatus; // // Call the server ... // (*Buffer) = NULL; RpcTryExcept{ NtStatus = SamrQueryInformationUser( (SAMPR_HANDLE)UserHandle, UserInformationClass, (PSAMPR_USER_INFO_BUFFER *)Buffer ); } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) { NtStatus = I_RpcMapWin32Status(RpcExceptionCode()); } RpcEndExcept; return(SampMapCompletionStatus(NtStatus)); } NTSTATUS SampOwfPassword( IN SAM_HANDLE UserHandle, IN PUNICODE_STRING UnicodePassword, IN BOOLEAN IgnorePasswordRestrictions, OUT PBOOLEAN NtPasswordPresent, OUT PNT_OWF_PASSWORD NtOwfPassword, OUT PBOOLEAN LmPasswordPresent, OUT PLM_OWF_PASSWORD LmOwfPassword ) /*++ Routine Description: This routine takes a cleartext unicode NT password from the user, makes sure it meets our high standards for password quality, converts it to an LM password if possible, and runs both passwords through a one-way function (OWF). Parameters: UserHandle - The handle of an opened user to operate on. UnicodePassword - the cleartext unicode NT password. IgnorePasswordRestrictions - When TRUE, indicates that the password should be accepted as legitimate regardless of what the domain's password restrictions indicate (e.g., can be less than required password length). This is expected to be used when setting up a new machine account. NtPasswordPresent - receives a boolean that says whether the NT password is present or not. NtOwfPassword - receives the OWF'd version of the NT password. LmPasswordPresent - receives a boolean that says whether the LM password is present or not. LmOwfPassword - receives the OWF'd version of the LM password. Return Values: STATUS_SUCCESS - the service has completed. The booleans say which of the OWFs are valid. Errors are returned by SampCheckPasswordRestrictions(), RtlCalculateNtOwfPassword(), SampCalculateLmPassword(), and RtlCalculateLmOwfPassword(). --*/ { NTSTATUS NtStatus; PCHAR LmPasswordBuffer; BOOLEAN UseOwfPasswords; // // We ignore the UseOwfPasswords flag since we already are. // if (IgnorePasswordRestrictions) { NtStatus = STATUS_SUCCESS; } else { NtStatus = SampCheckPasswordRestrictions( UserHandle, UnicodePassword, &UseOwfPasswords ); } // // Compute the NT One-Way-Function of the password // if ( NT_SUCCESS( NtStatus ) ) { *NtPasswordPresent = TRUE; NtStatus = RtlCalculateNtOwfPassword( UnicodePassword, NtOwfPassword ); if ( NT_SUCCESS( NtStatus ) ) { // // Calculate the LM version of the password // NtStatus = SampCalculateLmPassword( UnicodePassword, &LmPasswordBuffer); if (NT_SUCCESS(NtStatus)) { // // Compute the One-Way-Function of the LM password // *LmPasswordPresent = TRUE; NtStatus = RtlCalculateLmOwfPassword( LmPasswordBuffer, LmOwfPassword); // // We're finished with the LM password // MIDL_user_free(LmPasswordBuffer); } } } return( NtStatus ); } NTSTATUS SampRandomFill( IN ULONG BufferSize, IN OUT PUCHAR Buffer ) /*++ Routine Description: This routine fills a buffer with random data. Parameters: BufferSize - Length of the input buffer, in bytes. Buffer - Input buffer to be filled with random data. Return Values: Errors from NtQuerySystemTime() --*/ { ULONG Index; LARGE_INTEGER Time; ULONG Seed; NTSTATUS NtStatus; NtStatus = NtQuerySystemTime(&Time); if (!NT_SUCCESS(NtStatus)) { return(NtStatus); } Seed = Time.LowPart ^ Time.HighPart; for (Index = 0 ; Index < BufferSize ; Index++ ) { *Buffer++ = (UCHAR) (RtlRandom(&Seed) % 256); } return(STATUS_SUCCESS); } NTSTATUS SampEncryptClearPassword( IN SAM_HANDLE UserHandle, IN PUNICODE_STRING UnicodePassword, OUT PSAMPR_ENCRYPTED_USER_PASSWORD EncryptedUserPassword ) /*++ Routine Description: This routine takes a cleartext unicode NT password from the user, and encrypts it with the session key. Parameters: UserHandle - SAM_HANDLE used to acquiring a session key. UnicodePassword - the cleartext unicode NT password. EncryptedUserPassword - receives the encrypted cleartext password. Return Values: STATUS_SUCCESS - the service has completed. The booleans say which of the OWFs are valid. --*/ { NTSTATUS NtStatus; USER_SESSION_KEY UserSessionKey; struct RC4_KEYSTRUCT Rc4Key; PSAMPR_USER_PASSWORD UserPassword = (PSAMPR_USER_PASSWORD) EncryptedUserPassword; if (UnicodePassword->Length > SAM_MAX_PASSWORD_LENGTH * sizeof(WCHAR)) { return(STATUS_PASSWORD_RESTRICTION); } NtStatus = RtlGetUserSessionKeyClient( (RPC_BINDING_HANDLE)UserHandle, &UserSessionKey ); // // Convert the session key into an RC4 key // if (NT_SUCCESS(NtStatus)) { rc4_key( &Rc4Key, sizeof(USER_SESSION_KEY), (PUCHAR) &UserSessionKey ); RtlCopyMemory( ((PCHAR) UserPassword->Buffer) + (SAM_MAX_PASSWORD_LENGTH * sizeof(WCHAR)) - UnicodePassword->Length, UnicodePassword->Buffer, UnicodePassword->Length ); UserPassword->Length = UnicodePassword->Length; NtStatus = SampRandomFill( (SAM_MAX_PASSWORD_LENGTH * sizeof(WCHAR)) - UnicodePassword->Length, (PUCHAR) UserPassword->Buffer ); if (NT_SUCCESS(NtStatus)) { rc4( &Rc4Key, sizeof(SAMPR_ENCRYPTED_USER_PASSWORD), (PUCHAR) UserPassword ); } } return( NtStatus ); } NTSTATUS SampEncryptOwfs( IN SAM_HANDLE UserHandle, IN BOOLEAN NtPasswordPresent, IN PNT_OWF_PASSWORD NtOwfPassword, OUT PENCRYPTED_NT_OWF_PASSWORD EncryptedNtOwfPassword, IN BOOLEAN LmPasswordPresent, IN PLM_OWF_PASSWORD LmOwfPassword, OUT PENCRYPTED_LM_OWF_PASSWORD EncryptedLmOwfPassword ) /*++ Routine Description: This routine takes NT and LM passwords that have already been OWF'd, and encrypts them so that they can be safely sent to the server. Parameters: UserHandle - The handle of an opened user to operate on. NtPasswordPresent - indicates whether NtOwfPassword is valid or not. NtOwfPassword - an OWF'd NT password, if NtPasswordPresent is true. EncryptedNtOwfPassword - an encrypted version of the OWF'd NT password that can be safely sent to the server. LmPasswordPresent - indicates whether LmOwfPassword is valid or not. LmOwfPassword - an OWF'd LM password, if LmPasswordPresent is true. EncryptedLmOwfPassword - an encrypted version of the OWF'd LM password that can be safely sent to the server. Return Values: STATUS_SUCCESS - the passwords were encrypted and may be sent to the server. Errors may be returned by RtlGetUserSessionKeyClient(), RtlEncryptNtOwfPwdWithUserKey(), and RtlEncryptLmOwfPwdWithUserKey(). --*/ { NTSTATUS NtStatus; USER_SESSION_KEY UserSessionKey; NtStatus = RtlGetUserSessionKeyClient( (RPC_BINDING_HANDLE)UserHandle, &UserSessionKey ); if ( NT_SUCCESS( NtStatus ) ) { if (NtPasswordPresent) { // // Encrypt the Nt OWF Password with the user session key // and store it the buffer to send // NtStatus = RtlEncryptNtOwfPwdWithUserKey( NtOwfPassword, &UserSessionKey, EncryptedNtOwfPassword ); } if ( NT_SUCCESS( NtStatus ) ) { if (LmPasswordPresent) { // // Encrypt the Lm OWF Password with the user session key // and store it the buffer to send // NtStatus = RtlEncryptLmOwfPwdWithUserKey( LmOwfPassword, &UserSessionKey, EncryptedLmOwfPassword ); } } } return( NtStatus ); } NTSTATUS SamSetInformationUser( IN SAM_HANDLE UserHandle, IN USER_INFORMATION_CLASS UserInformationClass, IN PVOID Buffer ) /*++ Routine Description: This API modifies information in a user record. The data modified is determined by the UserInformationClass parameter. To change information here requires access to the user object defined above. Each structure has both a read and write access type associated with it. In general, a user may call GetInformation with class UserLogonInformation, but may only call SetInformation with class UserPreferencesInformation. Access type USER_WRITE_ACCOUNT allows changes to be made to all fields. NOTE: If the password is set to a new password then the password- set timestamp is reset as well. Parameters: UserHandle - The handle of an opened user to operate on. UserInformationClass - Class of information provided. The accesses required for each class is shown below: Info Level Required Access Type ----------------------- ------------------------ UserGeneralInformation (Can't set) UserPreferencesInformation USER_WRITE_PREFERENCES UserParametersInformation USER_WRITE_ACCOUNT UserLogonInformation (Can't set) UserLogonHoursInformation USER_WRITE_ACCOUNT UserAccountInformation (Can't set) UserNameInformation USER_WRITE_ACCOUNT UserAccountNameInformation USER_WRITE_ACCOUNT UserFullNameInformation USER_WRITE_ACCOUNT UserPrimaryGroupInformation USER_WRITE_ACCOUNT UserHomeInformation USER_WRITE_ACCOUNT UserScriptInformation USER_WRITE_ACCOUNT UserProfileInformation USER_WRITE_ACCOUNT UserAdminCommentInformation USER_WRITE_ACCOUNT UserWorkStationsInformation USER_WRITE_ACCOUNT UserSetPasswordInformation USER_FORCE_PASSWORD_CHANGE (also see note below) UserControlInformation USER_WRITE_ACCOUNT UserExpiresInformation USER_WRITE_ACCOUNT UserInternal1Information USER_FORCE_PASSWORD_CHANGE (also see note below) UserInternal2Information (trusted client use only) UserAllInformation Will set fields that user specifies, if accesses are held as described above. NOTE: When setting a password (with either UserSetPasswordInformation or UserInternal1Information), you MUST open the user account via a DomainHandle that was opened for DOMAIN_READ_PASSWORD_PARAMETERS. Buffer - Buffer containing a user info struct. Return Values: STATUS_SUCCESS - The Service completed successfully. STATUS_ACCESS_DENIED - Caller does not have the appropriate access to complete the operation. STATUS_INVALID_HANDLE - The handle passed is invalid. STATUS_INVALID_INFO_CLASS - The class provided was invalid. STATUS_INVALID_DOMAIN_STATE - The domain server is not in the correct state (disabled or enabled) to perform the requested operation. The domain server must be enabled for this operation STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the incorrect role (primary or backup) to perform the requested operation. --*/ { SAMPR_USER_INTERNAL1_INFORMATION Internal1RpcBuffer; USER_INTERNAL1_INFORMATION Internal1Buffer; SAMPR_USER_INTERNAL4_INFORMATION Internal4RpcBuffer; SAMPR_USER_INTERNAL5_INFORMATION Internal5RpcBuffer; PVOID BufferToPass; PUSER_ALL_INFORMATION UserAll; USER_ALL_INFORMATION LocalAll; NTSTATUS NtStatus = STATUS_SUCCESS; BOOLEAN IgnorePasswordRestrictions; ULONG Pass = 0; USER_INFORMATION_CLASS ClassToUse = UserInformationClass; BOOLEAN SendOwfs = FALSE; do { RpcTryExcept{ // // Normally just pass the info buffer through to rpc // BufferToPass = Buffer; // // Deal with special cases // switch (UserInformationClass) { case UserPreferencesInformation: { // // Field is unused, but make sure RPC doesn't choke on it. // ((PUSER_PREFERENCES_INFORMATION)(Buffer))->Reserved1.Length = 0; ((PUSER_PREFERENCES_INFORMATION)(Buffer))->Reserved1.MaximumLength = 0; ((PUSER_PREFERENCES_INFORMATION)(Buffer))->Reserved1.Buffer = NULL; break; } case UserSetPasswordInformation: if (Pass == 0) { // // On the zeroth pass try sending a UserInternal5 structure. // This is only available on 3.51 and above releases. // // // Check password restrictions. // NtStatus = SampCheckPasswordRestrictions( UserHandle, &((PUSER_SET_PASSWORD_INFORMATION)(Buffer))->Password, &SendOwfs ); // // If password restrictions told us we could send reversibly // encrypted passwords, compute them. Otherwise drop through // to the OWF case. // if (!SendOwfs) { // // Encrypt the cleatext password - we don't need to // restrictions because that can be done on the server. // NtStatus = SampEncryptClearPassword( UserHandle, &((PUSER_SET_PASSWORD_INFORMATION)(Buffer))->Password, &Internal5RpcBuffer.UserPassword ); if (!NT_SUCCESS(NtStatus)) { break; } Internal5RpcBuffer.PasswordExpired = ((PUSER_SET_PASSWORD_INFORMATION)(Buffer))->PasswordExpired; // // Set the class and buffer to send over. // ClassToUse = UserInternal5Information; BufferToPass = &Internal5RpcBuffer; break; } } else { // // Set the pass counter to one since we aren't trying a new // interface and don't want to retry. // Pass = 1; SendOwfs = TRUE; } ASSERT(SendOwfs); // // We're going to calculate the OWFs for the password and // turn this into an INTERNAL1 set info request by dropping // through to the INTERNAL1 code with Buffer pointing at our // local INTERNAL1 buffer. First, make sure that the password // meets our quality requirements. // NtStatus = SampOwfPassword( UserHandle, &((PUSER_SET_PASSWORD_INFORMATION)(Buffer))->Password, FALSE, // Don't ignore password restrictions &Internal1Buffer.NtPasswordPresent, &Internal1Buffer.NtOwfPassword, &Internal1Buffer.LmPasswordPresent, &Internal1Buffer.LmOwfPassword ); if (!NT_SUCCESS(NtStatus)) { break; } // // Copy the PasswordExpired flag // Internal1Buffer.PasswordExpired = ((PUSER_SET_PASSWORD_INFORMATION)(Buffer))->PasswordExpired; // // We now have a USER_INTERNAL1_INFO buffer in Internal1Buffer. // Point Buffer at Internal1buffer and drop through to the code // that handles INTERNAL1 requests Buffer = &Internal1Buffer; ClassToUse = UserInternal1Information; // // drop through..... // case UserInternal1Information: // // We're going to pass a different data structure to rpc // BufferToPass = &Internal1RpcBuffer; // // Copy the password present flags // Internal1RpcBuffer.NtPasswordPresent = ((PUSER_INTERNAL1_INFORMATION)Buffer)->NtPasswordPresent; Internal1RpcBuffer.LmPasswordPresent = ((PUSER_INTERNAL1_INFORMATION)Buffer)->LmPasswordPresent; // // Copy the PasswordExpired flag // Internal1RpcBuffer.PasswordExpired = ((PUSER_INTERNAL1_INFORMATION)Buffer)->PasswordExpired; // // Encrypt the OWFs with the user session key before we send // them over the Rpc link // NtStatus = SampEncryptOwfs( UserHandle, Internal1RpcBuffer.NtPasswordPresent, &((PUSER_INTERNAL1_INFORMATION)Buffer)->NtOwfPassword, &Internal1RpcBuffer.EncryptedNtOwfPassword, Internal1RpcBuffer.LmPasswordPresent, &((PUSER_INTERNAL1_INFORMATION)Buffer)->LmOwfPassword, &Internal1RpcBuffer.EncryptedLmOwfPassword ); break; case UserAllInformation: UserAll = (PUSER_ALL_INFORMATION)Buffer; // // If the caller is passing passwords we need to convert them // into OWFs and encrypt them. // if (UserAll->WhichFields & (USER_ALL_LMPASSWORDPRESENT | USER_ALL_NTPASSWORDPRESENT) ) { // // We'll need a private copy of the buffer which we can edit // and then send over RPC. // if (UserAll->WhichFields & USER_ALL_OWFPASSWORD) { LocalAll = *UserAll; BufferToPass = &LocalAll; SendOwfs = TRUE; // // The caller is passing OWFS directly // Check they're valid and copy them into the // Internal1Buffer in preparation for encryption. // if (LocalAll.WhichFields & USER_ALL_NTPASSWORDPRESENT) { if (LocalAll.NtPasswordPresent) { if (LocalAll.NtPassword.Length != NT_OWF_PASSWORD_LENGTH) { NtStatus = STATUS_INVALID_PARAMETER; } else { Internal1Buffer.NtOwfPassword = *((PNT_OWF_PASSWORD)LocalAll.NtPassword.Buffer); } } else { LocalAll.NtPasswordPresent = FALSE; } } if (LocalAll.WhichFields & USER_ALL_LMPASSWORDPRESENT) { if (LocalAll.LmPasswordPresent) { if (LocalAll.LmPassword.Length != LM_OWF_PASSWORD_LENGTH) { NtStatus = STATUS_INVALID_PARAMETER; } else { Internal1Buffer.LmOwfPassword = *((PNT_OWF_PASSWORD)LocalAll.LmPassword.Buffer); } } else { LocalAll.LmPasswordPresent = FALSE; } } // // Always remove the OWF_PASSWORDS flag. This is used // only on the client side to determine the mode // of password input and will be rejected by the server // LocalAll.WhichFields &= ~USER_ALL_OWFPASSWORD; } else { // // The caller is passing text passwords. // Check for validity and convert to OWFs. // if (UserAll->WhichFields & USER_ALL_LMPASSWORDPRESENT) { // // User clients are only allowed to put a unicode string // in the NT password. We always calculate the LM password // NtStatus = STATUS_INVALID_PARAMETER; } else { // // The caller might be simultaneously setting // the password and changing the account to be // a machine or trust account. In this case, // we don't validate the password (e.g., length). // IgnorePasswordRestrictions = FALSE; if (UserAll->WhichFields & USER_ALL_USERACCOUNTCONTROL) { if (UserAll->UserAccountControl & (USER_WORKSTATION_TRUST_ACCOUNT | USER_SERVER_TRUST_ACCOUNT) ) { IgnorePasswordRestrictions = TRUE; } } SendOwfs = TRUE; if (Pass == 0) { // // On the first pass, try sending the cleatext // password. // Internal4RpcBuffer.I1 = *(PSAMPR_USER_ALL_INFORMATION) UserAll; BufferToPass = &Internal4RpcBuffer; ClassToUse = UserInternal4Information; SendOwfs = FALSE; // // Check the password restrictions. We also // want to get the information on whether // we can send reversibly encrypted passwords. // NtStatus = SampCheckPasswordRestrictions( UserHandle, &UserAll->NtPassword, &SendOwfs ); if (IgnorePasswordRestrictions) { NtStatus = STATUS_SUCCESS; } if (!SendOwfs) { // // Encrypt the clear password // NtStatus = SampEncryptClearPassword( UserHandle, &UserAll->NtPassword, &Internal4RpcBuffer.UserPassword ); if (!NT_SUCCESS(NtStatus)) { break; } // // Zero the password NT password // RtlZeroMemory( &Internal4RpcBuffer.I1.NtOwfPassword, sizeof(UNICODE_STRING) ); } } if (SendOwfs) { // // On the second pass, do the normal thing. // LocalAll = *UserAll; BufferToPass = &LocalAll; SendOwfs = TRUE; ClassToUse = UserAllInformation; if ( LocalAll.WhichFields & USER_ALL_NTPASSWORDPRESENT ) { // // The user specified a password. We must validate // it, convert it to LM, and calculate the OWFs // LocalAll.WhichFields |= USER_ALL_LMPASSWORDPRESENT; // // Stick the OWFs in the Internal1Buffer - just // until we use them in the SampEncryptOwfs(). // NtStatus = SampOwfPassword( UserHandle, &LocalAll.NtPassword, IgnorePasswordRestrictions, &LocalAll.NtPasswordPresent, &(Internal1Buffer.NtOwfPassword), &LocalAll.LmPasswordPresent, &(Internal1Buffer.LmOwfPassword) ); } } } } // // We now have one or more OWFs in Internal1 buffer. // We got these either directly or we calculated them // from the text strings. // Encrypt these OWFs with the session key and // store the result in Internal1RpcBuffer. // // Note the Password present flags are in LocalAll. // (The ones in Internal1Buffer are not used.) // if ( NT_SUCCESS( NtStatus ) && SendOwfs ) { // // Make all LocalAll's password strings point to // the buffers in Internal1RpcBuffer // LocalAll.NtPassword.Length = sizeof( ENCRYPTED_NT_OWF_PASSWORD ); LocalAll.NtPassword.MaximumLength = sizeof( ENCRYPTED_NT_OWF_PASSWORD ); LocalAll.NtPassword.Buffer = (PWSTR) &Internal1RpcBuffer.EncryptedNtOwfPassword; LocalAll.LmPassword.Length = sizeof( ENCRYPTED_LM_OWF_PASSWORD ); LocalAll.LmPassword.MaximumLength = sizeof( ENCRYPTED_LM_OWF_PASSWORD ); LocalAll.LmPassword.Buffer = (PWSTR) &Internal1RpcBuffer.EncryptedLmOwfPassword; // // Encrypt the Owfs // NtStatus = SampEncryptOwfs( UserHandle, LocalAll.NtPasswordPresent, &Internal1Buffer.NtOwfPassword, &Internal1RpcBuffer.EncryptedNtOwfPassword, LocalAll.LmPasswordPresent, &Internal1Buffer.LmOwfPassword, &Internal1RpcBuffer.EncryptedLmOwfPassword ); } } break; default: break; } // switch // // Call the server ... // if ( NT_SUCCESS( NtStatus ) ) { // // If we are trying one of the new info levels, use the new // api. // if ((ClassToUse == UserInternal4Information) || (ClassToUse == UserInternal5Information)) { NtStatus = SamrSetInformationUser2( (SAMPR_HANDLE)UserHandle, ClassToUse, BufferToPass ); } else { NtStatus = SamrSetInformationUser( (SAMPR_HANDLE)UserHandle, ClassToUse, BufferToPass ); } } } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) { NtStatus = I_RpcMapWin32Status(RpcExceptionCode()); } RpcEndExcept; Pass++; // // If this is the first pass and the status indicated that the // server did not support the info class or the api // and we were trying one of the new info levels, try again. // } while ( (Pass < 2) && ((NtStatus == RPC_NT_INVALID_TAG) || (NtStatus == RPC_NT_UNKNOWN_IF) || (NtStatus == RPC_NT_PROCNUM_OUT_OF_RANGE))); return(SampMapCompletionStatus(NtStatus)); } NTSTATUS SamiLmChangePasswordUser( IN SAM_HANDLE UserHandle, IN PENCRYPTED_LM_OWF_PASSWORD LmOldEncryptedWithLmNew, IN PENCRYPTED_LM_OWF_PASSWORD LmNewEncryptedWithLmOld ) /*++ Routine Description: Changes the password of a user account. This routine is intended to be called by down-level system clients who have only the cross-encrypted LM passwords available to them. Password will be set to NewPassword only if OldPassword matches the current user password for this user and the NewPassword is not the same as the domain password parameter PasswordHistoryLength passwords. This call allows users to change their own password if they have access USER_CHANGE_PASSWORD. Password update restrictions apply. This api will fail unless UAS Compatibility is enabled for the domain. Parameters: UserHandle - The handle of an opened user to operate on. LmOldEncryptedWithLmNew - the OWF of the old LM password encrypted using the OWF of the new LM password as a key. LmNewEncryptedWithLmOld - the OWF of the new LM password encrypted using the OWF of the old LM password as a key. Return Values: STATUS_SUCCESS - The Service completed successfully. STATUS_ACCESS_DENIED - Caller does not have the appropriate access to complete the operation. STATUS_INVALID_HANDLE - The handle passed is invalid. STATUS_PASSWORD_RESTRICTION - A restriction prevents the password from being changed. This may be for a number of reasons, including time restrictions on how often a password may be changed or length restrictions on the provided password. This error might also be returned if the new password matched a password in the recent history log for the account. Security administrators indicate how many of the most recently used passwords may not be re-used. These are kept in the password recent history log. STATUS_WRONG_PASSWORD - The old password is incorrect. STATUS_INVALID_DOMAIN_STATE - The domain server is not in the correct state (disabled or enabled) to perform the requested operation. The domain server must be enabled for this operation STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the incorrect role (primary or backup) to perform the requested operation. --*/ { NTSTATUS NtStatus; // // Check parameter validity // if (LmOldEncryptedWithLmNew == NULL) { return(STATUS_INVALID_PARAMETER_1); } if (LmNewEncryptedWithLmOld == NULL) { return(STATUS_INVALID_PARAMETER_2); } // // Call the server ... // RpcTryExcept{ NtStatus = SamrChangePasswordUser( (SAMPR_HANDLE)UserHandle, TRUE, // LmOldPresent LmOldEncryptedWithLmNew, LmNewEncryptedWithLmOld, FALSE, // NtPresent NULL, // NtOldEncryptedWithNtNew NULL, // NtNewEncryptedWithNtOld FALSE, // NtCrossEncryptionPresent NULL, FALSE, // LmCrossEncryptionPresent NULL ); // // We should never get asked for cross-encrypted data // since the server knows we don't have any NT data. // ASSERT (NtStatus != STATUS_NT_CROSS_ENCRYPTION_REQUIRED); ASSERT (NtStatus != STATUS_LM_CROSS_ENCRYPTION_REQUIRED); } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) { NtStatus = I_RpcMapWin32Status(RpcExceptionCode()); } RpcEndExcept; return(SampMapCompletionStatus(NtStatus)); } NTSTATUS SamiChangePasswordUser( IN SAM_HANDLE UserHandle, IN BOOLEAN LmOldPresent, IN PLM_OWF_PASSWORD LmOldOwfPassword, IN PLM_OWF_PASSWORD LmNewOwfPassword, IN BOOLEAN NtPresent, IN PNT_OWF_PASSWORD NtOldOwfPassword, IN PNT_OWF_PASSWORD NtNewOwfPassword ) /*++ Routine Description: Changes the password of a user account. This is the worker routine for SamChangePasswordUser and can be called by OWF-aware clients. Password will be set to NewPassword only if OldPassword matches the current user password for this user and the NewPassword is not the same as the domain password parameter PasswordHistoryLength passwords. This call allows users to change their own password if they have access USER_CHANGE_PASSWORD. Password update restrictions apply. Parameters: UserHandle - The handle of an opened user to operate on. LMOldPresent - TRUE if the LmOldOwfPassword is valid. This should only be FALSE if the old password is too long to be represented by a LM password. (Complex NT password). Note the LMNewOwfPassword must always be valid. If the new password is complex, the LMNewOwfPassword should be the well-known LM OWF of a NULL password. LmOldOwfPassword - One-way-function of the current LM password for the user. - Ignored if LmOldPresent == FALSE LmNewOwfPassword - One-way-function of the new LM password for the user. NtPresent - TRUE if the NT one-way-functions are valid. - i.e. This will be FALSE if we're called from a down-level client. NtOldOwfPassword - One-way-function of the current NT password for the user. NtNewOwfPassword - One-way-function of the new NT password for the user. Return Values: STATUS_SUCCESS - The Service completed successfully. STATUS_ACCESS_DENIED - Caller does not have the appropriate access to complete the operation. STATUS_INVALID_HANDLE - The handle passed is invalid. STATUS_ILL_FORMED_PASSWORD - The new password is poorly formed, e.g. contains characters that can't be entered from the keyboard, etc. STATUS_PASSWORD_RESTRICTION - A restriction prevents the password from being changed. This may be for a number of reasons, including time restrictions on how often a password may be changed or length restrictions on the provided password. This error might also be returned if the new password matched a password in the recent history log for the account. Security administrators indicate how many of the most recently used passwords may not be re-used. These are kept in the password recent history log. STATUS_WRONG_PASSWORD - OldPassword does not contain the user's current password. STATUS_INVALID_DOMAIN_STATE - The domain server is not in the correct state (disabled or enabled) to perform the requested operation. The domain server must be enabled for this operation STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the incorrect role (primary or backup) to perform the requested operation. STATUS_INVALID_PARAMETER_MIX - LmOldPresent or NtPresent or both must be TRUE. --*/ { NTSTATUS NtStatus; ENCRYPTED_NT_OWF_PASSWORD NtNewEncryptedWithNtOld; ENCRYPTED_NT_OWF_PASSWORD NtOldEncryptedWithNtNew; ENCRYPTED_NT_OWF_PASSWORD NtNewEncryptedWithLmNew; ENCRYPTED_LM_OWF_PASSWORD LmNewEncryptedWithLmOld; ENCRYPTED_LM_OWF_PASSWORD LmOldEncryptedWithLmNew; ENCRYPTED_LM_OWF_PASSWORD LmNewEncryptedWithNtNew; PENCRYPTED_NT_OWF_PASSWORD pNtNewEncryptedWithNtOld; PENCRYPTED_NT_OWF_PASSWORD pNtOldEncryptedWithNtNew; PENCRYPTED_LM_OWF_PASSWORD pLmNewEncryptedWithLmOld; PENCRYPTED_LM_OWF_PASSWORD pLmOldEncryptedWithLmNew; // // Check parameter validity // if (!LmOldPresent && !NtPresent) { return(STATUS_INVALID_PARAMETER_MIX); } // // Call the server ... // RpcTryExcept{ // // We're going to encrypt the oldLM with the newLM and vice-versa. // We're going to encrypt the oldNT with the newNT and vice-versa. // We're going to send these 4 encryptions and see if we're successful. // // If we get a return code of STATUS_LM_CROSS_ENCRYPTION_REQUIRED, // we'll also encrypt the newLM with the newNT and send it all again. // // If we get a return code of STATUS_NT_CROSS_ENCRYPTION_REQUIRED, // we'll also encrypt the newNT with the newLM and send it all again. // // We don't always send the cross-encryption otherwise we would be // compromising security on pure NT systems with long passwords. // // // Do the LM Encryption // if (!LmOldPresent) { pLmOldEncryptedWithLmNew = NULL; pLmNewEncryptedWithLmOld = NULL; } else { pLmOldEncryptedWithLmNew = &LmOldEncryptedWithLmNew; pLmNewEncryptedWithLmOld = &LmNewEncryptedWithLmOld; NtStatus = RtlEncryptLmOwfPwdWithLmOwfPwd( LmOldOwfPassword, LmNewOwfPassword, &LmOldEncryptedWithLmNew); if (NT_SUCCESS(NtStatus)) { NtStatus = RtlEncryptLmOwfPwdWithLmOwfPwd( LmNewOwfPassword, LmOldOwfPassword, &LmNewEncryptedWithLmOld); } } // // Do the NT Encryption // if (NT_SUCCESS(NtStatus)) { if (!NtPresent) { pNtOldEncryptedWithNtNew = NULL; pNtNewEncryptedWithNtOld = NULL; } else { pNtOldEncryptedWithNtNew = &NtOldEncryptedWithNtNew; pNtNewEncryptedWithNtOld = &NtNewEncryptedWithNtOld; NtStatus = RtlEncryptNtOwfPwdWithNtOwfPwd( NtOldOwfPassword, NtNewOwfPassword, &NtOldEncryptedWithNtNew); if (NT_SUCCESS(NtStatus)) { NtStatus = RtlEncryptNtOwfPwdWithNtOwfPwd( NtNewOwfPassword, NtOldOwfPassword, &NtNewEncryptedWithNtOld); } } } // // Call the server (with no cross-encryption) // if ( NT_SUCCESS( NtStatus ) ) { NtStatus = SamrChangePasswordUser( (SAMPR_HANDLE)UserHandle, LmOldPresent, pLmOldEncryptedWithLmNew, pLmNewEncryptedWithLmOld, NtPresent, pNtOldEncryptedWithNtNew, pNtNewEncryptedWithNtOld, FALSE, // NtCrossEncryptionPresent NULL, FALSE, // LmCrossEncryptionPresent NULL ); if (NtStatus == STATUS_NT_CROSS_ENCRYPTION_REQUIRED) { // // We should only get this if we have both LM and NT data // (This is not obvious - it results from the server-side logic) // ASSERT(NtPresent && LmOldPresent); // // Compute the cross-encryption of the new Nt password // ASSERT(LM_OWF_PASSWORD_LENGTH == NT_OWF_PASSWORD_LENGTH); NtStatus = RtlEncryptNtOwfPwdWithNtOwfPwd( NtNewOwfPassword, (PNT_OWF_PASSWORD)LmNewOwfPassword, &NtNewEncryptedWithLmNew); // // Call the server (with NT cross-encryption) // if ( NT_SUCCESS( NtStatus ) ) { NtStatus = SamrChangePasswordUser( (SAMPR_HANDLE)UserHandle, LmOldPresent, pLmOldEncryptedWithLmNew, pLmNewEncryptedWithLmOld, NtPresent, pNtOldEncryptedWithNtNew, pNtNewEncryptedWithNtOld, TRUE, &NtNewEncryptedWithLmNew, FALSE, NULL ); } } else { if (NtStatus == STATUS_LM_CROSS_ENCRYPTION_REQUIRED) { // // We should only get this if we have NT but no old LM data // (This is not obvious - it results from the server-side logic) // ASSERT(NtPresent && !LmOldPresent); // // Compute the cross-encryption of the new Nt password // ASSERT(LM_OWF_PASSWORD_LENGTH == NT_OWF_PASSWORD_LENGTH); NtStatus = RtlEncryptLmOwfPwdWithLmOwfPwd( LmNewOwfPassword, (PLM_OWF_PASSWORD)NtNewOwfPassword, &LmNewEncryptedWithNtNew); // // Call the server (with LM cross-encryption) // if ( NT_SUCCESS( NtStatus ) ) { NtStatus = SamrChangePasswordUser( (SAMPR_HANDLE)UserHandle, LmOldPresent, pLmOldEncryptedWithLmNew, pLmNewEncryptedWithLmOld, NtPresent, pNtOldEncryptedWithNtNew, pNtNewEncryptedWithNtOld, FALSE, NULL, TRUE, &LmNewEncryptedWithNtNew ); } } } } } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) { NtStatus = I_RpcMapWin32Status(RpcExceptionCode()); } RpcEndExcept; return(SampMapCompletionStatus(NtStatus)); } NTSTATUS SamChangePasswordUser( IN SAM_HANDLE UserHandle, IN PUNICODE_STRING OldNtPassword, IN PUNICODE_STRING NewNtPassword ) /*++ Routine Description: Password will be set to NewPassword only if OldPassword matches the current user password for this user and the NewPassword is not the same as the domain password parameter PasswordHistoryLength passwords. This call allows users to change their own password if they have access USER_CHANGE_PASSWORD. Password update restrictions apply. Parameters: UserHandle - The handle of an opened user to operate on. OldPassword - Current password for the user. NewPassword - Desired new password for the user. Return Values: STATUS_SUCCESS - The Service completed successfully. STATUS_ACCESS_DENIED - Caller does not have the appropriate access to complete the operation. STATUS_INVALID_HANDLE - The handle passed is invalid. STATUS_ILL_FORMED_PASSWORD - The new password is poorly formed, e.g. contains characters that can't be entered from the keyboard, etc. STATUS_PASSWORD_RESTRICTION - A restriction prevents the password from being changed. This may be for a number of reasons, including time restrictions on how often a password may be changed or length restrictions on the provided password. This error might also be returned if the new password matched a password in the recent history log for the account. Security administrators indicate how many of the most recently used passwords may not be re-used. These are kept in the password recent history log. STATUS_WRONG_PASSWORD - OldPassword does not contain the user's current password. STATUS_INVALID_DOMAIN_STATE - The domain server is not in the correct state (disabled or enabled) to perform the requested operation. The domain server must be enabled for this operation STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the incorrect role (primary or backup) to perform the requested operation. --*/ { LM_OWF_PASSWORD NewLmOwfPassword, OldLmOwfPassword; NT_OWF_PASSWORD NewNtOwfPassword, OldNtOwfPassword; BOOLEAN LmOldPresent; PCHAR LmPassword; NTSTATUS NtStatus; BOOLEAN UseOwfPasswords; // // Call the server ... // RpcTryExcept{ NtStatus = SampCheckPasswordRestrictions( UserHandle, NewNtPassword, &UseOwfPasswords ); if ( NT_SUCCESS( NtStatus ) ) { // // Calculate the one-way-functions of the NT passwords // NtStatus = RtlCalculateNtOwfPassword( OldNtPassword, &OldNtOwfPassword ); if ( NT_SUCCESS( NtStatus ) ) { NtStatus = RtlCalculateNtOwfPassword( NewNtPassword, &NewNtOwfPassword ); } // // Calculate the one-way-functions of the LM passwords // if ( NT_SUCCESS( NtStatus ) ) { // // Calculate the LM version of the old password // NtStatus = SampCalculateLmPassword( OldNtPassword, &LmPassword); if (NT_SUCCESS(NtStatus)) { if (NtStatus == STATUS_NULL_LM_PASSWORD) { LmOldPresent = FALSE; } else { LmOldPresent = TRUE; // // Compute the One-Way-Function of the old LM password // NtStatus = RtlCalculateLmOwfPassword( LmPassword, &OldLmOwfPassword); } // // We're finished with the LM password // MIDL_user_free(LmPassword); } // // Calculate the LM version of the new password // if (NT_SUCCESS(NtStatus)) { NtStatus = SampCalculateLmPassword( NewNtPassword, &LmPassword); if (NT_SUCCESS(NtStatus)) { // // Compute the One-Way-Function of the new LM password // NtStatus = RtlCalculateLmOwfPassword( LmPassword, &NewLmOwfPassword); // // We're finished with the LM password // MIDL_user_free(LmPassword); } } } // // Call our worker routine with the one-way-functions // if (NT_SUCCESS(NtStatus)) { NtStatus = SamiChangePasswordUser( UserHandle, LmOldPresent, &OldLmOwfPassword, &NewLmOwfPassword, TRUE, // NT present &OldNtOwfPassword, &NewNtOwfPassword ); } } } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) { NtStatus = I_RpcMapWin32Status(RpcExceptionCode()); } RpcEndExcept; return(SampMapCompletionStatus(NtStatus)); } NTSTATUS SamGetGroupsForUser( IN SAM_HANDLE UserHandle, OUT PGROUP_MEMBERSHIP * Groups, OUT PULONG MembershipCount ) /*++ Routine Description: This service returns the list of groups that a user is a member of. It returns a structure for each group that includes the relative ID of the group, and the attributes of the group that are assigned to the user. This service requires USER_LIST_GROUPS access to the user account object. Parameters: UserHandle - The handle of an opened user to operate on. Groups - Receives a pointer to a buffer containing an array of GROUP_MEMBERSHIPs data structures. When this information is no longer needed, this buffer must be freed using SamFreeMemory(). MembershipCount - Receives the number of groups the user is a member of, and, thus, the number elements returned in the Groups array. Return Values: STATUS_SUCCESS - The Service completed successfully. STATUS_ACCESS_DENIED - Caller does not have the appropriate access to complete the operation. STATUS_INVALID_HANDLE - The handle passed is invalid. --*/ { NTSTATUS NtStatus; PSAMPR_GET_GROUPS_BUFFER GetGroupsBuffer; // // Call the server ... // GetGroupsBuffer = NULL; RpcTryExcept{ NtStatus = SamrGetGroupsForUser( (SAMPR_HANDLE)UserHandle, &GetGroupsBuffer ); if (NT_SUCCESS(NtStatus)) { (*MembershipCount) = GetGroupsBuffer->MembershipCount; (*Groups) = GetGroupsBuffer->Groups; MIDL_user_free( GetGroupsBuffer ); } else { // // Deallocate any returned buffers on error // if (GetGroupsBuffer != NULL) { if (GetGroupsBuffer->Groups != NULL) { MIDL_user_free(GetGroupsBuffer->Groups); } MIDL_user_free(GetGroupsBuffer); } } } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) { NtStatus = I_RpcMapWin32Status(RpcExceptionCode()); } RpcEndExcept; return(SampMapCompletionStatus(NtStatus)); } NTSTATUS SamTestPrivateFunctionsDomain( IN SAMPR_HANDLE DomainHandle ) /*++ Routine Description: This service is called to test functions that are normally only accessible inside the security process. Arguments: DomainHandle - Handle to a domain to be tested. Return Value: STATUS_SUCCESS - The tests completed successfully. Any errors are as propogated from the tests. --*/ { #ifdef SAM_SERVER_TESTS return( SamrTestPrivateFunctionsDomain( DomainHandle ) ); #else return( STATUS_NOT_IMPLEMENTED ); UNREFERENCED_PARAMETER(DomainHandle); #endif } NTSTATUS SamTestPrivateFunctionsUser( IN SAMPR_HANDLE UserHandle ) /*++ Routine Description: This service is called to test functions that are normally only accessible inside the security process. Arguments: UserHandle - Handle to a user to be tested. Return Value: STATUS_SUCCESS - The tests completed successfully. Any errors are as propogated from the tests. --*/ { #ifdef SAM_SERVER_TESTS return( SamrTestPrivateFunctionsUser( UserHandle ) ); #else return( STATUS_NOT_IMPLEMENTED ); UNREFERENCED_PARAMETER(UserHandle); #endif } /////////////////////////////////////////////////////////////////////////////// // // // private services // // // /////////////////////////////////////////////////////////////////////////////// NTSTATUS SampMapCompletionStatus( IN NTSTATUS Status ) /*++ Routine Description: This service maps completion status received back from an RPC call into a completion status to be returned from SAM api. Parameters: Status - Status value to be mapped. Return Values: The mapped SAM status value. --*/ { if (Status == RPC_NT_INVALID_BINDING) { Status = STATUS_INVALID_HANDLE; } // if (Status == RPC_ACCESS_DENIED) { // Status = STATUS_ACCESS_DENIED; // } return( Status ); } NTSTATUS SampCalculateLmPassword( IN PUNICODE_STRING NtPassword, OUT PCHAR *LmPasswordBuffer ) /*++ Routine Description: This service converts an NT password into a LM password. Parameters: NtPassword - The Nt password to be converted. LmPasswordBuffer - On successful return, points at the LM password The buffer should be freed using MIDL_user_free Return Values: STATUS_SUCCESS - LMPassword contains the LM version of the password. STATUS_NULL_LM_PASSWORD - The password is too complex to be represented by a LM password. The LM password returned is a NULL string. --*/ { #define LM_BUFFER_LENGTH (LM20_PWLEN + 1) NTSTATUS NtStatus; ANSI_STRING LmPassword; // // Prepare for failure // *LmPasswordBuffer = NULL; // // Compute the Ansi version to the Unicode password. // // The Ansi version of the Cleartext password is at most 14 bytes long, // exists in a trailing zero filled 15 byte buffer, // is uppercased. // LmPassword.Buffer = MIDL_user_allocate(LM_BUFFER_LENGTH); if (LmPassword.Buffer == NULL) { return(STATUS_INSUFFICIENT_RESOURCES); } LmPassword.MaximumLength = LmPassword.Length = LM_BUFFER_LENGTH; RtlZeroMemory( LmPassword.Buffer, LM_BUFFER_LENGTH ); NtStatus = RtlUpcaseUnicodeStringToOemString( &LmPassword, NtPassword, FALSE ); if ( !NT_SUCCESS(NtStatus) ) { // // The password is longer than the max LM password length // NtStatus = STATUS_NULL_LM_PASSWORD; // Informational return code RtlZeroMemory( LmPassword.Buffer, LM_BUFFER_LENGTH ); } // // Return a pointer to the allocated LM password // if (NT_SUCCESS(NtStatus)) { *LmPasswordBuffer = LmPassword.Buffer; } else { MIDL_user_free(LmPassword.Buffer); } return(NtStatus); } NTSTATUS SampCheckPasswordRestrictions( IN SAMPR_HANDLE UserHandle, IN PUNICODE_STRING NewNtPassword, OUT PBOOLEAN UseOwfPasswords ) /*++ Routine Description: This service is called to make sure that the password presented meets our quality requirements. Arguments: UserHandle - Handle to a user. NewNtPassword - Pointer to the UNICODE_STRING containing the new password. UseOwfPasswords - Indicates that reversibly encrypted passwords should not be sent over the network. Return Value: STATUS_SUCCESS - The password is acceptable. STATUS_PASSWORD_RESTRICTION - The password is too short, or is not complex enough, etc. STATUS_INVALID_RESOURCES - There was not enough memory to do the password checking. --*/ { USER_DOMAIN_PASSWORD_INFORMATION DomainPasswordInformationBuffer; NTSTATUS NtStatus; PWORD CharInfoBuffer = NULL; ULONG i; // // If the new password is zero length the server side will do // the necessary checking. // if (NewNtPassword->Length == 0) { return(STATUS_SUCCESS); } *UseOwfPasswords = FALSE; // // Query information domain to get password length and // complexity requirements. // NtStatus = SamrGetUserDomainPasswordInformation( UserHandle, &DomainPasswordInformationBuffer ); if ( NT_SUCCESS( NtStatus ) ) { if ( (USHORT)( NewNtPassword->Length / sizeof(WCHAR) ) < DomainPasswordInformationBuffer.MinPasswordLength ) { NtStatus = STATUS_PASSWORD_RESTRICTION; } else { // // Check whether policy allows us to send reversibly encrypted // passwords. // if ( DomainPasswordInformationBuffer.PasswordProperties & DOMAIN_PASSWORD_NO_CLEAR_CHANGE ) { *UseOwfPasswords = TRUE; } // // Check password complexity. // if ( DomainPasswordInformationBuffer.PasswordProperties & DOMAIN_PASSWORD_COMPLEX ) { // // Make sure that the password meets our requirements for // complexity. If it's got an odd byte count, it's // obviously not a hand-entered UNICODE string so we'll // consider it complex by default. // if ( !( NewNtPassword->Length & 1 ) ) { USHORT NumsInPassword = 0; USHORT UppersInPassword = 0; USHORT LowersInPassword = 0; USHORT OthersInPassword = 0; CharInfoBuffer = MIDL_user_allocate( NewNtPassword->Length ); if ( CharInfoBuffer == NULL ) { NtStatus = STATUS_INSUFFICIENT_RESOURCES; } else { if ( GetStringTypeW( CT_CTYPE1, NewNtPassword->Buffer, NewNtPassword->Length / 2, CharInfoBuffer ) ) { for ( i = 0; i < (ULONG)( NewNtPassword->Length / sizeof(WCHAR) ); i++ ) { if ( CharInfoBuffer[i] & C1_DIGIT ) { NumsInPassword = 1; } if ( CharInfoBuffer[i] & C1_UPPER ) { UppersInPassword = 1; } if ( CharInfoBuffer[i] & C1_LOWER ) { LowersInPassword = 1; } if ( !( CharInfoBuffer[i] & ( C1_ALPHA | C1_DIGIT ) ) ) { // // Having any "other" characters is // sufficient to make the password // complex. // OthersInPassword = 2; } } if ( ( NumsInPassword + UppersInPassword + LowersInPassword + OthersInPassword ) < 2 ) { // // It didn't have at least two of the four // types of characters, so it's not complex // enough. // NtStatus = STATUS_PASSWORD_RESTRICTION; } } else { // // GetStringTypeW failed; dunno why. Perhaps the // password is binary. Consider it complex by // default. // NtStatus = STATUS_SUCCESS; } MIDL_user_free( CharInfoBuffer ); } } } } } return( NtStatus ); } NTSTATUS SamiEncryptPasswords( IN PUNICODE_STRING OldPassword, IN PUNICODE_STRING NewPassword, OUT PSAMPR_ENCRYPTED_USER_PASSWORD NewEncryptedWithOldNt, OUT PENCRYPTED_NT_OWF_PASSWORD OldNtOwfEncryptedWithNewNt, OUT PBOOLEAN LmPresent, OUT PSAMPR_ENCRYPTED_USER_PASSWORD NewEncryptedWithOldLm, OUT PENCRYPTED_NT_OWF_PASSWORD OldLmOwfEncryptedWithNewNt ) /*++ Routine Description: This routine takes old and new cleartext passwords, converts them to LM passwords, generates OWF passwords, and produces reversibly encrypted cleartext and OWF passwords. Arguments: OldPassword - The current cleartext password for the user. NewPassword - The new cleartext password for the user. NewEncryptedWithOldNt - The new password, in an SAMPR_USER_PASSWORD structure, reversibly encrypted with the old NT OWF password. OldNtOwfEncryptedWithNewNt - The old NT OWF password reversibly encrypted with the new NT OWF password. LmPresent - Indicates whether or not LM versions of the passwords could be calculated. NewEncryptedWithOldLm - The new password, in an SAMPR_USER_PASSWORD structure, reversibly encrypted with the old LM OWF password. OldLmOwfEncryptedWithNewNt - The old LM OWF password reversibly encrypted with the new NT OWF password. Return Value: Errors from RtlEncryptXXX functions --*/ { PCHAR OldLmPassword = NULL; PCHAR NewLmPassword = NULL; LM_OWF_PASSWORD OldLmOwfPassword; NT_OWF_PASSWORD OldNtOwfPassword; NT_OWF_PASSWORD NewNtOwfPassword; PSAMPR_USER_PASSWORD NewNt = (PSAMPR_USER_PASSWORD) NewEncryptedWithOldNt; PSAMPR_USER_PASSWORD NewLm = (PSAMPR_USER_PASSWORD) NewEncryptedWithOldLm; struct RC4_KEYSTRUCT Rc4Key; NTSTATUS NtStatus; BOOLEAN OldLmPresent = TRUE; BOOLEAN NewLmPresent = TRUE; // // Initialization // *LmPresent = TRUE; // // Make sure the password isn't too long. // if (NewPassword->Length > SAM_MAX_PASSWORD_LENGTH * sizeof(WCHAR)) { return(STATUS_PASSWORD_RESTRICTION); } // // Calculate the LM passwords. This may fail because the passwords are // too complex, but we can deal with that, so just remember what failed. // NtStatus = SampCalculateLmPassword( OldPassword, &OldLmPassword ); if (NtStatus != STATUS_SUCCESS) { OldLmPresent = FALSE; *LmPresent = FALSE; // // If the error was that it couldn't calculate the password, that // is o.k. // if (NtStatus == STATUS_NULL_LM_PASSWORD) { NtStatus = STATUS_SUCCESS; } } // // Calculate the LM OWF passwords // if (NT_SUCCESS(NtStatus) && OldLmPresent) { NtStatus = RtlCalculateLmOwfPassword( OldLmPassword, &OldLmOwfPassword ); } // // Calculate the NT OWF passwords // if (NT_SUCCESS(NtStatus)) { NtStatus = RtlCalculateNtOwfPassword( OldPassword, &OldNtOwfPassword ); } if (NT_SUCCESS(NtStatus)) { NtStatus = RtlCalculateNtOwfPassword( NewPassword, &NewNtOwfPassword ); } // // Calculate the encrypted old passwords // if (NT_SUCCESS(NtStatus)) { NtStatus = RtlEncryptNtOwfPwdWithNtOwfPwd( &OldNtOwfPassword, &NewNtOwfPassword, OldNtOwfEncryptedWithNewNt ); } // // Compute the encrypted old LM password. Always use the new NT OWF // to encrypt it, since we may not have a new LM OWF password. // if (NT_SUCCESS(NtStatus) && OldLmPresent) { ASSERT(LM_OWF_PASSWORD_LENGTH == NT_OWF_PASSWORD_LENGTH); NtStatus = RtlEncryptLmOwfPwdWithLmOwfPwd( &OldLmOwfPassword, (PLM_OWF_PASSWORD) &NewNtOwfPassword, OldLmOwfEncryptedWithNewNt ); } // // Calculate the encrypted new passwords // if (NT_SUCCESS(NtStatus)) { ASSERT(sizeof(SAMPR_ENCRYPTED_USER_PASSWORD) == sizeof(SAMPR_USER_PASSWORD)); // // Compute the encrypted new password with NT key. // rc4_key( &Rc4Key, NT_OWF_PASSWORD_LENGTH, (PUCHAR) &OldNtOwfPassword ); RtlCopyMemory( ((PUCHAR) NewNt->Buffer) + SAM_MAX_PASSWORD_LENGTH * sizeof(WCHAR) - NewPassword->Length, NewPassword->Buffer, NewPassword->Length ); *(ULONG UNALIGNED *) &NewNt->Length = NewPassword->Length; // // Fill the rest of the buffer with random numbers // NtStatus = SampRandomFill( (SAM_MAX_PASSWORD_LENGTH * sizeof(WCHAR)) - NewPassword->Length, (PUCHAR) NewNt->Buffer ); } if (NT_SUCCESS(NtStatus)) { rc4(&Rc4Key, sizeof(SAMPR_USER_PASSWORD), (PUCHAR) NewEncryptedWithOldNt ); } // // Compute the encrypted new password with LM key if it exists. // if (NT_SUCCESS(NtStatus) && OldLmPresent) { rc4_key( &Rc4Key, LM_OWF_PASSWORD_LENGTH, (PUCHAR) &OldLmOwfPassword ); RtlCopyMemory( ((PUCHAR) NewLm->Buffer) + (SAM_MAX_PASSWORD_LENGTH * sizeof(WCHAR)) - NewPassword->Length, NewPassword->Buffer, NewPassword->Length ); *(ULONG UNALIGNED *) &NewLm->Length = NewPassword->Length; NtStatus = SampRandomFill( (SAM_MAX_PASSWORD_LENGTH * sizeof(WCHAR)) - NewPassword->Length, (PUCHAR) NewLm->Buffer ); } // // Encrypt the password (or, if the old LM OWF password does not exist, // zero it). if (NT_SUCCESS(NtStatus) && OldLmPresent) { rc4(&Rc4Key, sizeof(SAMPR_USER_PASSWORD), (PUCHAR) NewEncryptedWithOldLm ); } else { RtlZeroMemory( NewLm, sizeof(SAMPR_ENCRYPTED_USER_PASSWORD) ); } // // Make sure to zero the passwords before freeing so we don't have // passwords floating around in the page file. // if (OldLmPassword != NULL) { RtlZeroMemory( OldLmPassword, lstrlenA(OldLmPassword) ); MIDL_user_free(OldLmPassword); } return(NtStatus); } NTSTATUS SampChangePasswordUser2( IN PUNICODE_STRING ServerName, IN PUNICODE_STRING UserName, IN PUNICODE_STRING OldPassword, IN PUNICODE_STRING NewPassword ) /*++ Routine Description: Password will be set to NewPassword only if OldPassword matches the current user password for this user and the NewPassword is not the same as the domain password parameter PasswordHistoryLength passwords. This call allows users to change their own password if they have access USER_CHANGE_PASSWORD. Password update restrictions apply. Parameters: UserHandle - The handle of an opened user to operate on. OldPassword - Current password for the user. NewPassword - Desired new password for the user. Return Values: STATUS_SUCCESS - The Service completed successfully. STATUS_ACCESS_DENIED - Caller does not have the appropriate access to complete the operation. STATUS_INVALID_HANDLE - The handle passed is invalid. STATUS_ILL_FORMED_PASSWORD - The new password is poorly formed, e.g. contains characters that can't be entered from the keyboard, etc. STATUS_PASSWORD_RESTRICTION - A restriction prevents the password from being changed. This may be for a number of reasons, including time restrictions on how often a password may be changed or length restrictions on the provided password. This error might also be returned if the new password matched a password in the recent history log for the account. Security administrators indicate how many of the most recently used passwords may not be re-used. These are kept in the password recent history log. STATUS_WRONG_PASSWORD - OldPassword does not contain the user's current password. STATUS_INVALID_DOMAIN_STATE - The domain server is not in the correct state (disabled or enabled) to perform the requested operation. The domain server must be enabled for this operation STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the incorrect role (primary or backup) to perform the requested operation. --*/ { NTSTATUS NtStatus; SAM_HANDLE SamServerHandle = NULL; SAM_HANDLE DomainHandle = NULL; SAM_HANDLE UserHandle = NULL; LSA_HANDLE PolicyHandle = NULL; OBJECT_ATTRIBUTES ObjectAttributes; SECURITY_QUALITY_OF_SERVICE SecurityQualityOfService; PPOLICY_ACCOUNT_DOMAIN_INFO AccountDomainInfo = NULL; PULONG UserId = NULL; PSID_NAME_USE NameUse = NULL; InitializeObjectAttributes( &ObjectAttributes, NULL, 0, NULL, NULL ); // // The InitializeObjectAttributes call doesn't initialize the // quality of serivce, so do that separately. // SecurityQualityOfService.Length = sizeof(SECURITY_QUALITY_OF_SERVICE); SecurityQualityOfService.ImpersonationLevel = SecurityImpersonation; SecurityQualityOfService.ContextTrackingMode = SECURITY_DYNAMIC_TRACKING; SecurityQualityOfService.EffectiveOnly = FALSE; ObjectAttributes.SecurityQualityOfService = &SecurityQualityOfService; NtStatus = LsaOpenPolicy( ServerName, &ObjectAttributes, POLICY_VIEW_LOCAL_INFORMATION, &PolicyHandle ); if (!NT_SUCCESS(NtStatus)) { goto Cleanup; } NtStatus = LsaQueryInformationPolicy( PolicyHandle, PolicyAccountDomainInformation, &AccountDomainInfo ); if (!NT_SUCCESS(NtStatus)) { goto Cleanup; } NtStatus = SamConnect( ServerName, &SamServerHandle, SAM_SERVER_LOOKUP_DOMAIN, &ObjectAttributes ); if (!NT_SUCCESS(NtStatus)) { goto Cleanup; } NtStatus = SamOpenDomain( SamServerHandle, GENERIC_EXECUTE, AccountDomainInfo->DomainSid, &DomainHandle ); if (!NT_SUCCESS(NtStatus)) { goto Cleanup; } NtStatus = SamLookupNamesInDomain( DomainHandle, 1, UserName, &UserId, &NameUse ); if (!NT_SUCCESS(NtStatus)) { if (NtStatus == STATUS_NONE_MAPPED) { NtStatus = STATUS_NO_SUCH_USER; } goto Cleanup; } NtStatus = SamOpenUser( DomainHandle, USER_CHANGE_PASSWORD, *UserId, &UserHandle ); if (!NT_SUCCESS(NtStatus)) { goto Cleanup; } NtStatus = SamChangePasswordUser( UserHandle, OldPassword, NewPassword ); Cleanup: if (UserHandle != NULL) { SamCloseHandle(UserHandle); } if (DomainHandle != NULL) { SamCloseHandle(DomainHandle); } if (SamServerHandle != NULL) { SamCloseHandle(SamServerHandle); } if (PolicyHandle != NULL){ LsaClose(PolicyHandle); } if (AccountDomainInfo != NULL) { LsaFreeMemory(AccountDomainInfo); } if (UserId != NULL) { SamFreeMemory(UserId); } if (NameUse != NULL) { SamFreeMemory(NameUse); } return(NtStatus); } NTSTATUS SamiChangePasswordUser2( PUNICODE_STRING ServerName, PUNICODE_STRING UserName, PSAMPR_ENCRYPTED_USER_PASSWORD NewPasswordEncryptedWithOldNt, PENCRYPTED_NT_OWF_PASSWORD OldNtOwfPasswordEncryptedWithNewNt, BOOLEAN LmPresent, PSAMPR_ENCRYPTED_USER_PASSWORD NewPasswordEncryptedWithOldLm, PENCRYPTED_LM_OWF_PASSWORD OldLmOwfPasswordEncryptedWithNewLmOrNt ) /*++ Routine Description: Changes the password of a user account. This is the worker routine for SamChangePasswordUser2 and can be called by OWF-aware clients. Password will be set to NewPassword only if OldPassword matches the current user password for this user and the NewPassword is not the same as the domain password parameter PasswordHistoryLength passwords. This call allows users to change their own password if they have access USER_CHANGE_PASSWORD. Password update restrictions apply. Parameters: ServerName - The server to operate on, or NULL for this machine. UserName - Name of user whose password is to be changed NewPasswordEncryptedWithOldNt - The new cleartext password encrypted with the old NT OWF password. OldNtOwfPasswordEncryptedWithNewNt - The old NT OWF password encrypted with the new NT OWF password. LmPresent - If TRUE, indicates that the following two last parameter was encrypted with the LM OWF password not the NT OWF password. NewPasswordEncryptedWithOldLm - The new cleartext password encrypted with the old LM OWF password. OldLmOwfPasswordEncryptedWithNewLmOrNt - The old LM OWF password encrypted with the new LM OWF password. Return Values: STATUS_SUCCESS - The Service completed successfully. STATUS_ACCESS_DENIED - Caller does not have the appropriate access to complete the operation. STATUS_INVALID_HANDLE - The handle passed is invalid. STATUS_ILL_FORMED_PASSWORD - The new password is poorly formed, e.g. contains characters that can't be entered from the keyboard, etc. STATUS_PASSWORD_RESTRICTION - A restriction prevents the password from being changed. This may be for a number of reasons, including time restrictions on how often a password may be changed or length restrictions on the provided password. This error might also be returned if the new password matched a password in the recent history log for the account. Security administrators indicate how many of the most recently used passwords may not be re-used. These are kept in the password recent history log. STATUS_WRONG_PASSWORD - OldPassword does not contain the user's current password. STATUS_INVALID_DOMAIN_STATE - The domain server is not in the correct state (disabled or enabled) to perform the requested operation. The domain server must be enabled for this operation STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the incorrect role (primary or backup) to perform the requested operation. --*/ { handle_t BindingHandle; PSAMPR_SERVER_NAME RServerNameWithNull; USHORT RServerNameWithNullLength; PSAMPR_SERVER_NAME RServerName; ULONG Tries = 2; NTSTATUS NtStatus; USER_DOMAIN_PASSWORD_INFORMATION PasswordInformation; RServerNameWithNull = NULL; if (ARGUMENT_PRESENT(ServerName)) { RServerName = (PSAMPR_SERVER_NAME)(ServerName->Buffer); RServerNameWithNullLength = ServerName->Length + (USHORT) sizeof(WCHAR); RServerNameWithNull = MIDL_user_allocate( RServerNameWithNullLength ); if (RServerNameWithNull == NULL) { return(STATUS_INSUFFICIENT_RESOURCES); } RtlCopyMemory( RServerNameWithNull, RServerName, ServerName->Length); RServerNameWithNull[ServerName->Length/sizeof(WCHAR)] = L'\0'; } do { // // Try privacy level first, and if that failed with unknown authn // level or invalid binding try with a lower level (none). // if (Tries == 2) { BindingHandle = SampSecureBind( RServerNameWithNull, RPC_C_AUTHN_LEVEL_PKT_PRIVACY ); } else if ((NtStatus == RPC_NT_UNKNOWN_AUTHN_LEVEL) || (NtStatus == RPC_NT_UNKNOWN_AUTHN_TYPE) || (NtStatus == RPC_NT_UNKNOWN_AUTHN_SERVICE) || (NtStatus == RPC_NT_INVALID_BINDING) || (NtStatus == STATUS_ACCESS_DENIED) ) { SampSecureUnbind(BindingHandle); BindingHandle = SampSecureBind( RServerNameWithNull, RPC_C_AUTHN_LEVEL_NONE ); } else { break; } if (BindingHandle != NULL) { RpcTryExcept{ // // Get password information to make sure this operation // is allowed. We do it now because we wanted to bind // before trying it. // NtStatus = SamrGetDomainPasswordInformation( BindingHandle, (PRPC_UNICODE_STRING) ServerName, &PasswordInformation ); if (NtStatus == STATUS_SUCCESS) { if (!( PasswordInformation.PasswordProperties & DOMAIN_PASSWORD_NO_CLEAR_CHANGE) ) { NtStatus = SamrUnicodeChangePasswordUser2( BindingHandle, (PRPC_UNICODE_STRING) ServerName, (PRPC_UNICODE_STRING) UserName, NewPasswordEncryptedWithOldNt, OldNtOwfPasswordEncryptedWithNewNt, LmPresent, NewPasswordEncryptedWithOldLm, OldLmOwfPasswordEncryptedWithNewLmOrNt ); } else { // // Set the error to indicate that we should try the // downlevel way to change passwords. // NtStatus = STATUS_NOT_SUPPORTED; } } } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) { // // The mapping function doesn't handle this error so // special case it by hand. // NtStatus = RpcExceptionCode(); if (NtStatus == RPC_S_SEC_PKG_ERROR) { NtStatus = STATUS_ACCESS_DENIED; } else { NtStatus = I_RpcMapWin32Status(NtStatus); } } RpcEndExcept; } else { NtStatus = RPC_NT_INVALID_BINDING; } Tries--; } while ( (Tries > 0) && (!NT_SUCCESS(NtStatus)) ); if (RServerNameWithNull != NULL) { MIDL_user_free( RServerNameWithNull ); } if (BindingHandle != NULL) { SampSecureUnbind(BindingHandle); } // // Map these errors to STATUS_NOT_SUPPORTED // if ((NtStatus == RPC_NT_UNKNOWN_IF) || (NtStatus == RPC_NT_PROCNUM_OUT_OF_RANGE)) { NtStatus = STATUS_NOT_SUPPORTED; } return(SampMapCompletionStatus(NtStatus)); } NTSTATUS SamiOemChangePasswordUser2( PSTRING ServerName, PSTRING UserName, PSAMPR_ENCRYPTED_USER_PASSWORD NewPasswordEncryptedWithOldLm, PENCRYPTED_LM_OWF_PASSWORD OldLmOwfPasswordEncryptedWithNewLm ) /*++ Routine Description: Changes the password of a user account. This can be called by OWF-aware clients. Password will be set to NewPassword only if OldPassword matches the current user password for this user and the NewPassword is not the same as the domain password parameter PasswordHistoryLength passwords. This call allows users to change their own password if they have access USER_CHANGE_PASSWORD. Password update restrictions apply. Parameters: ServerName - The server to operate on, or NULL for this machine. UserName - Name of user whose password is to be changed NewPasswordEncryptedWithOldLm - The new cleartext password encrypted with the old LM OWF password. OldLmOwfPasswordEncryptedWithNewLm - The old LM OWF password encrypted with the new LM OWF password. Return Values: STATUS_SUCCESS - The Service completed successfully. STATUS_ACCESS_DENIED - Caller does not have the appropriate access to complete the operation. STATUS_INVALID_HANDLE - The handle passed is invalid. STATUS_ILL_FORMED_PASSWORD - The new password is poorly formed, e.g. contains characters that can't be entered from the keyboard, etc. STATUS_PASSWORD_RESTRICTION - A restriction prevents the password from being changed. This may be for a number of reasons, including time restrictions on how often a password may be changed or length restrictions on the provided password. This error might also be returned if the new password matched a password in the recent history log for the account. Security administrators indicate how many of the most recently used passwords may not be re-used. These are kept in the password recent history log. STATUS_WRONG_PASSWORD - OldPassword does not contain the user's current password. STATUS_INVALID_DOMAIN_STATE - The domain server is not in the correct state (disabled or enabled) to perform the requested operation. The domain server must be enabled for this operation STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the incorrect role (primary or backup) to perform the requested operation. --*/ { handle_t BindingHandle; UNICODE_STRING RemoteServerName; ULONG Tries = 2; NTSTATUS NtStatus; USER_DOMAIN_PASSWORD_INFORMATION PasswordInformation; RemoteServerName.Buffer = NULL; RemoteServerName.Length = 0; if (ARGUMENT_PRESENT(ServerName)) { NtStatus = RtlAnsiStringToUnicodeString( &RemoteServerName, ServerName, TRUE // allocate destination ); if (!NT_SUCCESS(NtStatus)) { return(NtStatus); } ASSERT(RemoteServerName.Buffer[RemoteServerName.Length/sizeof(WCHAR)] == L'\0'); } do { // // Try privacy level first, and if that failed with unknown authn // level or invalid binding try with a lower level (none). // if (Tries == 2) { BindingHandle = SampSecureBind( RemoteServerName.Buffer, RPC_C_AUTHN_LEVEL_PKT_PRIVACY ); } else if ((NtStatus == RPC_NT_UNKNOWN_AUTHN_LEVEL) || (NtStatus == RPC_NT_UNKNOWN_AUTHN_TYPE) || (NtStatus == RPC_NT_INVALID_BINDING) || (NtStatus == STATUS_ACCESS_DENIED) ) { SampSecureUnbind(BindingHandle); BindingHandle = SampSecureBind( RemoteServerName.Buffer, RPC_C_AUTHN_LEVEL_NONE ); } else { break; } if (BindingHandle != NULL) { RpcTryExcept{ // // Get password information to make sure this operation // is allowed. We do it now because we wanted to bind // before trying it. // NtStatus = SamrGetDomainPasswordInformation( BindingHandle, (PRPC_UNICODE_STRING) ServerName, &PasswordInformation ); if (NtStatus == STATUS_SUCCESS) { if (!( PasswordInformation.PasswordProperties & DOMAIN_PASSWORD_NO_CLEAR_CHANGE) ) { NtStatus = SamrOemChangePasswordUser2( BindingHandle, (PRPC_STRING) ServerName, (PRPC_STRING) UserName, NewPasswordEncryptedWithOldLm, OldLmOwfPasswordEncryptedWithNewLm ); } else { // // Set the error to indicate that we should try the // downlevel way to change passwords. // NtStatus = STATUS_NOT_SUPPORTED; } } } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) { // // The mappin function doesn't handle this error so // special case it by hand. // if (NtStatus == RPC_S_SEC_PKG_ERROR) { NtStatus = STATUS_ACCESS_DENIED; } else { NtStatus = I_RpcMapWin32Status(RpcExceptionCode()); } } RpcEndExcept; } else { NtStatus = RPC_NT_INVALID_BINDING; } Tries--; } while ( (Tries > 0) && (!NT_SUCCESS(NtStatus)) ); RtlFreeUnicodeString( &RemoteServerName ); if (BindingHandle != NULL) { SampSecureUnbind(BindingHandle); } // // Map these errors to STATUS_NOT_SUPPORTED // if ((NtStatus == RPC_NT_UNKNOWN_IF) || (NtStatus == RPC_NT_PROCNUM_OUT_OF_RANGE)) { NtStatus = STATUS_NOT_SUPPORTED; } return(SampMapCompletionStatus(NtStatus)); } NTSTATUS SamChangePasswordUser2( IN PUNICODE_STRING ServerName, IN PUNICODE_STRING UserName, IN PUNICODE_STRING OldPassword, IN PUNICODE_STRING NewPassword ) /*++ Routine Description: Password will be set to NewPassword only if OldPassword matches the current user password for this user and the NewPassword is not the same as the domain password parameter PasswordHistoryLength passwords. This call allows users to change their own password if they have access USER_CHANGE_PASSWORD. Password update restrictions apply. Parameters: ServerName - The server to operate on, or NULL for this machine. UserName - Name of user whose password is to be changed OldPassword - Current password for the user. NewPassword - Desired new password for the user. Return Values: STATUS_SUCCESS - The Service completed successfully. STATUS_ACCESS_DENIED - Caller does not have the appropriate access to complete the operation. STATUS_INVALID_HANDLE - The handle passed is invalid. STATUS_ILL_FORMED_PASSWORD - The new password is poorly formed, e.g. contains characters that can't be entered from the keyboard, etc. STATUS_PASSWORD_RESTRICTION - A restriction prevents the password from being changed. This may be for a number of reasons, including time restrictions on how often a password may be changed or length restrictions on the provided password. This error might also be returned if the new password matched a password in the recent history log for the account. Security administrators indicate how many of the most recently used passwords may not be re-used. These are kept in the password recent history log. STATUS_WRONG_PASSWORD - OldPassword does not contain the user's current password. STATUS_INVALID_DOMAIN_STATE - The domain server is not in the correct state (disabled or enabled) to perform the requested operation. The domain server must be enabled for this operation STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the incorrect role (primary or backup) to perform the requested operation. --*/ { SAMPR_ENCRYPTED_USER_PASSWORD NewNtEncryptedWithOldNt; SAMPR_ENCRYPTED_USER_PASSWORD NewNtEncryptedWithOldLm; ENCRYPTED_NT_OWF_PASSWORD OldNtOwfEncryptedWithNewNt; ENCRYPTED_NT_OWF_PASSWORD OldLmOwfEncryptedWithNewNt; NTSTATUS NtStatus; BOOLEAN LmPresent = TRUE; ULONG AuthnLevel; ULONG Tries = 2; USER_DOMAIN_PASSWORD_INFORMATION PasswordInformation; // // Call the server, passing either a NULL Server Name pointer, or // a pointer to a Unicode Buffer with a Wide Character NULL terminator. // Since the input name is contained in a counted Unicode String, there // is no NULL terminator necessarily provided, so we must append one. // // // Encrypted the passwords // NtStatus = SamiEncryptPasswords( OldPassword, NewPassword, &NewNtEncryptedWithOldNt, &OldNtOwfEncryptedWithNewNt, &LmPresent, &NewNtEncryptedWithOldLm, &OldLmOwfEncryptedWithNewNt ); if (!NT_SUCCESS(NtStatus)) { return(NtStatus); } // // Try the remote call... // NtStatus = SamiChangePasswordUser2( ServerName, UserName, &NewNtEncryptedWithOldNt, &OldNtOwfEncryptedWithNewNt, LmPresent, &NewNtEncryptedWithOldLm, &OldLmOwfEncryptedWithNewNt ); // // If the new API failed, try calling the old API. // if (NtStatus == STATUS_NOT_SUPPORTED) { NtStatus = SampChangePasswordUser2( ServerName, UserName, OldPassword, NewPassword ); } return(SampMapCompletionStatus(NtStatus)); }