/*++ Copyright (c) 1995 Microsoft Corporation Module Name: security.c Abstract: Routines to deal with security, user accounts, etc. Externally exposed routines: SignalLsa CreateSamEvent WaitForSam SetAccountsDomainSid CreateLocalAdminAccount CreateLocalUserAccount SetLocalUserPassword Author: Ted Miller (tedm) 5-Apr-1995 adapted from legacy\dll\security.c Revision History: --*/ #include "setupp.h" #include #pragma hdrstop PCWSTR SamEventName = L"\\SAM_SERVICE_STARTED"; PCWSTR SsiAccountNamePostfix = L"$"; PCWSTR SsiSecretName = L"$MACHINE.ACC"; #define DOMAIN_NAME_MAX 33 #define PASSWORD_MAX 14 // // Constants used for logging that are specific to this source file. // PCWSTR szLsaOpenPolicy = L"LsaOpenPolicy"; PCWSTR szLsaSetInformationPolicy = L"LsaSetInformationPolicy"; PCWSTR szLsaQueryInformationPolicy = L"LsaQueryInformationPolicy"; PCWSTR szNtSetEvent = L"NtSetEvent"; PCWSTR szNtCreateEvent = L"NtCreateEvent"; PCWSTR szSamConnect = L"SamConnect"; PCWSTR szGetAccountsDomainName = L"GetAccountsDomainName"; PCWSTR szSamLookupDomainInSamServer = L"SamLookupDomainInSamServer"; PCWSTR szSamOpenDomain = L"SamOpenDomain"; PCWSTR szSamEnumerateUsersInDomain = L"SamEnumerateUsersInDomain"; PCWSTR szSamOpenUser = L"SamOpenUser"; PCWSTR szSamChangePasswordUser = L"SamChangePasswordUser"; PCWSTR szSamCreateUserInDomain = L"SamCreateUserInDomain"; PCWSTR szSamQueryInformationUser = L"SamQueryInformationUser"; PCWSTR szSamSetInformationUser = L"SamSetInformationUser"; PCWSTR szMyAddLsaSecretObject = L"MyAddLsaSecretObject"; VOID SetupLsaInitObjectAttributes( IN OUT POBJECT_ATTRIBUTES ObjectAttributes, IN OUT PSECURITY_QUALITY_OF_SERVICE SecurityQualityOfService ); BOOL GetAccountsDomainName( IN LSA_HANDLE hPolicy, OPTIONAL OUT PWSTR Name, IN DWORD NameBufferSize ); LSA_HANDLE OpenLsaPolicy( VOID ); PSID CreateSidFromSidAndRid( IN PSID DomainSid, IN DWORD Rid ); NTSTATUS MyAddLsaSecretObject( IN PCWSTR Password ); BOOL SetAccountsDomainSid( IN DWORD Seed, IN PCWSTR DomainName ) /*++ Routine Description: Routine to set the sid of the AccountDomain. Arguments: Seed - The seed is used to generate a unique Sid. The seed should be generated by looking at the systemtime before and after a dialog and subtracting the milliseconds field. DomainName - supplies name to give to local domain Return value: Boolean value indicating outcome. --*/ { PSID Sid; PSID SidPrimary ; OBJECT_ATTRIBUTES ObjectAttributes; SECURITY_QUALITY_OF_SERVICE SecurityQualityOfService; LSA_HANDLE PolicyHandle = NULL; PPOLICY_ACCOUNT_DOMAIN_INFO PolicyCurrentAccountDomainInfo = NULL; NTSTATUS Status; BOOL bResult; // // // Open the LSA Policy object to set the account domain sid. The access // mask needed for this is POLICY_TRUST_ADMIN. // SetupLsaInitObjectAttributes(&ObjectAttributes,&SecurityQualityOfService); Status = LsaOpenPolicy(NULL,&ObjectAttributes,MAXIMUM_ALLOWED,&PolicyHandle); if(!NT_SUCCESS(Status)) { SetuplogError( LogSevError, SETUPLOG_USE_MESSAGEID, MSG_LOG_SETACCOUNTDOMAINSID, NULL, SETUPLOG_USE_MESSAGEID, MSG_LOG_X_RETURNED_NTSTATUS, szLsaOpenPolicy, Status, NULL,NULL); return(FALSE); } Status = LsaQueryInformationPolicy( PolicyHandle, PolicyAccountDomainInformation, &PolicyCurrentAccountDomainInfo ); if(NT_SUCCESS(Status)) { RtlInitUnicodeString(&PolicyCurrentAccountDomainInfo->DomainName,DomainName); Status = LsaSetInformationPolicy( PolicyHandle, PolicyAccountDomainInformation, (PVOID) PolicyCurrentAccountDomainInfo ); LsaFreeMemory( PolicyCurrentAccountDomainInfo ); } if(NT_SUCCESS(Status)) { bResult = TRUE; } else { bResult = FALSE; SetuplogError( LogSevError, SETUPLOG_USE_MESSAGEID, MSG_LOG_SETACCOUNTDOMAINSID, NULL, SETUPLOG_USE_MESSAGEID, MSG_LOG_X_RETURNED_NTSTATUS, szLsaSetInformationPolicy, Status, NULL,NULL); } LsaClose(PolicyHandle); return(bResult); } VOID SetupLsaInitObjectAttributes( IN OUT POBJECT_ATTRIBUTES ObjectAttributes, IN OUT PSECURITY_QUALITY_OF_SERVICE SecurityQualityOfService ) /*++ Routine Description: This function initializes the given Object Attributes structure, including Security Quality Of Service. Memory must be allcated for both ObjectAttributes and Security QOS by the caller. Borrowed from lsa Arguments: ObjectAttributes - Pointer to Object Attributes to be initialized. SecurityQualityOfService - Pointer to Security QOS to be initialized. Return Value: None. --*/ { SecurityQualityOfService->Length = sizeof(SECURITY_QUALITY_OF_SERVICE); SecurityQualityOfService->ImpersonationLevel = SecurityImpersonation; SecurityQualityOfService->ContextTrackingMode = SECURITY_DYNAMIC_TRACKING; SecurityQualityOfService->EffectiveOnly = FALSE; InitializeObjectAttributes(ObjectAttributes,NULL,0,NULL,NULL); // // The InitializeObjectAttributes macro presently stores NULL for // the SecurityQualityOfService field, so we must manually copy that // structure for now. // ObjectAttributes->SecurityQualityOfService = SecurityQualityOfService; } BOOL CreateLocalUserAccount( IN PCWSTR UserName, IN PCWSTR Password, IN PSID* PointerToUserSid OPTIONAL ) /*++ Routine Description: Routine to add a local user account to the AccountDomain. This account is created with the password indicated. Arguments: UserName - supplies name for user account Password - supplies initial password for user account. PointerToUserSid - If this argument is present, then on return it will contain the pointer to the user sid. It is the responsibility of the caller to free the Sid, using MyFree. Return value: Boolean value indicating outcome. --*/ { return (NT_SUCCESS(CreateLocalAdminAccount(UserName, Password, PointerToUserSid ) ) ); } NTSTATUS CreateLocalAdminAccountEx( IN PCWSTR UserName, IN PCWSTR Password, IN PCWSTR Description, IN PSID* PointerToUserSid OPTIONAL ) /*++ Routine Description: Routine to add a local user account to the AccountDomain. This account has ADMINISTRATOR priveledges and is created with the password indicated. Arguments: UserName - supplies name for user account Password - supplies initial password for user account. Description - Description that appears in user manager. PointerToUserSid - If this argument is present, then on return it will contain the pointer to the user sid. It is the responsibility of the caller to free the Sid, using MyFree. Return value: Boolean value indicating outcome. --*/ { OBJECT_ATTRIBUTES ObjectAttributes; SECURITY_QUALITY_OF_SERVICE SecurityQualityOfService; UNICODE_STRING UnicodeString; SAM_HANDLE ServerHandle; SAM_HANDLE DomainHandle; SAM_HANDLE UserHandle; SAM_HANDLE AliasHandle; SAM_HANDLE BuiltinDomainHandle; WCHAR AccountsDomainName[DOMAIN_NAME_MAX]; NTSTATUS Status; PSID BuiltinDomainId; PSID UserSid; ULONG User_RID; PUSER_CONTROL_INFORMATION UserControlInfo; USER_SET_PASSWORD_INFORMATION UserPasswordInfo; LSA_HANDLE PolicyHandle = NULL; PPOLICY_ACCOUNT_DOMAIN_INFO PolicyCurrentAccountDomainInfo = NULL; USER_ADMIN_COMMENT_INFORMATION AdminCommentInfo; // // Use SamConnect to connect to the local domain ("") and get a handle // to the local sam server. // SetupLsaInitObjectAttributes(&ObjectAttributes,&SecurityQualityOfService); RtlInitUnicodeString(&UnicodeString,L""); Status = SamConnect( &UnicodeString, &ServerHandle, SAM_SERVER_CONNECT | SAM_SERVER_LOOKUP_DOMAIN, &ObjectAttributes ); if(!NT_SUCCESS(Status)) { goto err0; } // // Use the LSA to retrieve the name of the Accounts domain. // if(!GetAccountsDomainName(NULL,AccountsDomainName,DOMAIN_NAME_MAX)) { goto err1; } // // Open the AccountDomain. First find the Sid for this // in the Sam and then open the domain using this sid // // // Open the LSA Policy object to set the account domain sid. // SetupLsaInitObjectAttributes(&ObjectAttributes,&SecurityQualityOfService); Status = LsaOpenPolicy(NULL,&ObjectAttributes,MAXIMUM_ALLOWED,&PolicyHandle); if(NT_SUCCESS(Status)) { Status = LsaQueryInformationPolicy( PolicyHandle, PolicyAccountDomainInformation, &PolicyCurrentAccountDomainInfo ); if(NT_SUCCESS(Status)) { Status = SamOpenDomain( ServerHandle, DOMAIN_READ | DOMAIN_LIST_ACCOUNTS | DOMAIN_LOOKUP | DOMAIN_READ_PASSWORD_PARAMETERS | DOMAIN_CREATE_USER, PolicyCurrentAccountDomainInfo->DomainSid, &DomainHandle ); } LsaClose( PolicyHandle ); } if (!NT_SUCCESS(Status)) { goto err2; } // // Use SamCreateUserInDomain to create a new user with the username // specified. This user account is created disabled with the // password not required. // RtlInitUnicodeString(&UnicodeString,UserName); Status = SamCreateUserInDomain( DomainHandle, &UnicodeString, //USER_READ_ACCOUNT | USER_WRITE_ACCOUNT | USER_FORCE_PASSWORD_CHANGE, USER_ALL_ACCESS, &UserHandle, &User_RID ); if(!NT_SUCCESS(Status)) { goto err3; } // // Query all the default control information about the user added // Status = SamQueryInformationUser(UserHandle,UserControlInformation,&UserControlInfo); if(!NT_SUCCESS(Status)) { goto err4; } // // If the password is a Null password, make sure the // password_not required bit is set before the null // password is set. // if(!Password[0]) { UserControlInfo->UserAccountControl |= USER_PASSWORD_NOT_REQUIRED; Status = SamSetInformationUser(UserHandle,UserControlInformation,UserControlInfo); if(!NT_SUCCESS(Status)) { goto err5; } } // // Set the password ( NULL or non NULL ) // RtlInitUnicodeString(&UserPasswordInfo.Password,Password); UserPasswordInfo.PasswordExpired = FALSE; Status = SamSetInformationUser(UserHandle,UserSetPasswordInformation,&UserPasswordInfo); if(!NT_SUCCESS(Status)) { goto err5; } // // Set the information bits - User Password not required is cleared // The normal account bit is enabled and the account disabled bit // is also reset // UserControlInfo->UserAccountControl &= ~USER_PASSWORD_NOT_REQUIRED; UserControlInfo->UserAccountControl &= ~USER_ACCOUNT_DISABLED; UserControlInfo->UserAccountControl |= USER_NORMAL_ACCOUNT; Status = SamSetInformationUser(UserHandle,UserControlInformation,UserControlInfo); if(!NT_SUCCESS(Status)) { goto err5; } // Set the description is one is given // if ( Description[0]) { // Convert description to unicode string // RtlInitUnicodeString(&AdminCommentInfo.AdminComment,Description); // We do not care if this fails and therefore will not set the status // SamSetInformationUser(UserHandle,UserAdminCommentInformation,&AdminCommentInfo); } // // If this is a non-standlone server we're done. // if(ISDC(ProductType)) { Status = STATUS_SUCCESS; goto err5; } // // Finally add this to the administrators alias in the BuiltIn Domain // RtlInitUnicodeString(&UnicodeString,L"Builtin"); Status = SamLookupDomainInSamServer(ServerHandle,&UnicodeString,&BuiltinDomainId); if(!NT_SUCCESS(Status)) { goto err5; } Status = SamOpenDomain( ServerHandle, DOMAIN_READ | DOMAIN_ADMINISTER_SERVER | DOMAIN_EXECUTE, BuiltinDomainId, &BuiltinDomainHandle ); if(!NT_SUCCESS(Status)) { goto err6; } UserSid = CreateSidFromSidAndRid(PolicyCurrentAccountDomainInfo->DomainSid,User_RID); if(!UserSid) { goto err7; } Status = SamOpenAlias(BuiltinDomainHandle,ALIAS_ADD_MEMBER,DOMAIN_ALIAS_RID_ADMINS,&AliasHandle); if(!NT_SUCCESS(Status)) { goto err8; } Status = SamAddMemberToAlias(AliasHandle,UserSid); if(!NT_SUCCESS(Status)) { goto err9; } MYASSERT(NT_SUCCESS(Status)); err9: SamCloseHandle(AliasHandle); err8: if(NT_SUCCESS(Status) && (PointerToUserSid != NULL )) { *PointerToUserSid = UserSid; } else { MyFree(UserSid); } err7: SamCloseHandle(BuiltinDomainHandle); err6: SamFreeMemory(BuiltinDomainId); err5: SamFreeMemory(UserControlInfo); err4: SamCloseHandle(UserHandle); err3: SamCloseHandle(DomainHandle); err2: LsaFreeMemory( PolicyCurrentAccountDomainInfo ); err1: SamCloseHandle(ServerHandle); err0: return(Status); } NTSTATUS CreateLocalAdminAccount( IN PCWSTR UserName, IN PCWSTR Password, IN PSID* PointerToUserSid OPTIONAL ) /*++ Routine Description: Please see CreateLocalAdminAccountEx description. --*/ { return ( CreateLocalAdminAccountEx(UserName, Password, L"", PointerToUserSid) ); } BOOL GetAccountsDomainName( IN LSA_HANDLE PolicyHandle, OPTIONAL OUT PWSTR Name, IN DWORD NameBufferSize ) { POLICY_ACCOUNT_DOMAIN_INFO *pPadi; NTSTATUS Status ; BOOL PolicyOpened; PolicyOpened = FALSE; if(PolicyHandle == NULL) { if((PolicyHandle = OpenLsaPolicy()) == NULL) { return(FALSE); } PolicyOpened = TRUE; } Status = LsaQueryInformationPolicy(PolicyHandle,PolicyAccountDomainInformation,&pPadi); if(NT_SUCCESS(Status)) { if(NameBufferSize <= (pPadi->DomainName.Length/sizeof(WCHAR))) { Status = STATUS_BUFFER_TOO_SMALL; } else { wcsncpy(Name,pPadi->DomainName.Buffer,pPadi->DomainName.Length/sizeof(WCHAR)); Name[pPadi->DomainName.Length/sizeof(WCHAR)] = 0; } LsaFreeMemory(pPadi); } if(PolicyOpened) { LsaClose(PolicyHandle); } return(NT_SUCCESS(Status)); } LSA_HANDLE OpenLsaPolicy( VOID ) { OBJECT_ATTRIBUTES ObjectAttributes; SECURITY_QUALITY_OF_SERVICE SecurityQualityOfService; LSA_HANDLE PolicyHandle; NTSTATUS Status; PolicyHandle = NULL; SetupLsaInitObjectAttributes(&ObjectAttributes,&SecurityQualityOfService); Status = LsaOpenPolicy(NULL,&ObjectAttributes,GENERIC_EXECUTE,&PolicyHandle); return(NT_SUCCESS(Status) ? PolicyHandle : NULL); } PSID CreateSidFromSidAndRid( IN PSID DomainSid, IN DWORD Rid ) /*++ Routine Description: This function creates a domain account sid given a domain sid and the relative id of the account within the domain. Arguments: DomainSid - supplies sid for domain of account Rid - supplies relative id of account Return Value: Pointer to Sid, or NULL on failure. The returned Sid must be freed with MyFree. --*/ { NTSTATUS Status; PSID AccountSid; UCHAR AccountSubAuthorityCount; ULONG AccountSidLength; PULONG RidLocation; AccountSubAuthorityCount = *RtlSubAuthorityCountSid(DomainSid) + (UCHAR)1; AccountSidLength = RtlLengthRequiredSid(AccountSubAuthorityCount); if(AccountSid = (PSID)MyMalloc(AccountSidLength)) { // // Copy the domain sid into the first part of the account sid // Status = RtlCopySid(AccountSidLength, AccountSid, DomainSid); // // Increment the account sid sub-authority count // *RtlSubAuthorityCountSid(AccountSid) = AccountSubAuthorityCount; // // Add the rid as the final sub-authority // RidLocation = RtlSubAuthoritySid(AccountSid, AccountSubAuthorityCount - 1); *RidLocation = Rid; } return(AccountSid); } BOOL SetLocalUserPassword( IN PCWSTR AccountName, IN PCWSTR OldPassword, IN PCWSTR NewPassword ) /*++ Routine Description: Change the password for the local user account. Arguments: Return value: Boolean value indicating outcome. --*/ { NTSTATUS Status; BOOL b; UNICODE_STRING UnicodeString; UNICODE_STRING OtherUnicodeString; SECURITY_QUALITY_OF_SERVICE SecurityQualityOfService; OBJECT_ATTRIBUTES ObjectAttributes; WCHAR AccountsDomainName[DOMAIN_NAME_MAX]; SAM_HANDLE ServerHandle; SAM_HANDLE DomainHandle; SAM_HANDLE UserHandle; BOOL UserFound; SAM_ENUMERATE_HANDLE EnumerationContext; SAM_RID_ENUMERATION *SamRidEnumeration; UINT i; UINT CountOfEntries; ULONG UserRid; LSA_HANDLE PolicyHandle = NULL; PPOLICY_ACCOUNT_DOMAIN_INFO PolicyCurrentAccountDomainInfo = NULL; b = FALSE; // // Use SamConnect to connect to the local domain ("") and get a handle // to the local sam server // SetupLsaInitObjectAttributes(&ObjectAttributes,&SecurityQualityOfService); RtlInitUnicodeString(&UnicodeString,L""); Status = SamConnect( &UnicodeString, &ServerHandle, SAM_SERVER_CONNECT | SAM_SERVER_LOOKUP_DOMAIN, &ObjectAttributes ); if(!NT_SUCCESS(Status)) { SetuplogError( LogSevWarning, SETUPLOG_USE_MESSAGEID, MSG_LOG_CHANGING_PW_FAIL, AccountName, NULL, SETUPLOG_USE_MESSAGEID, MSG_LOG_X_RETURNED_NTSTATUS, szSamConnect, Status, NULL,NULL); goto err0; } // // Use the LSA to retrieve the name of the Accounts domain. // if(!GetAccountsDomainName(NULL,AccountsDomainName,DOMAIN_NAME_MAX)) { SetuplogError( LogSevWarning, SETUPLOG_USE_MESSAGEID, MSG_LOG_CHANGING_PW_FAIL, AccountName, NULL, SETUPLOG_USE_MESSAGEID, MSG_LOG_X_RETURNED_STRING, szGetAccountsDomainName, szFALSE, NULL,NULL); goto err1; } // // Open the AccountDomain. First find the Sid for this // in the Sam and then open the domain using this sid. // // // Get the AccountDomainSid from the Lsa // // // // Open the LSA Policy object to set the account domain sid. // SetupLsaInitObjectAttributes(&ObjectAttributes,&SecurityQualityOfService); Status = LsaOpenPolicy(NULL,&ObjectAttributes,MAXIMUM_ALLOWED,&PolicyHandle); if(NT_SUCCESS(Status)) { Status = LsaQueryInformationPolicy( PolicyHandle, PolicyAccountDomainInformation, &PolicyCurrentAccountDomainInfo ); if(NT_SUCCESS(Status)) { Status = SamOpenDomain( ServerHandle, DOMAIN_READ | DOMAIN_LIST_ACCOUNTS | DOMAIN_LOOKUP | DOMAIN_READ_PASSWORD_PARAMETERS, PolicyCurrentAccountDomainInfo->DomainSid, &DomainHandle ); LsaFreeMemory( PolicyCurrentAccountDomainInfo ); } LsaClose( PolicyHandle ); } if(!NT_SUCCESS(Status)) { SetuplogError( LogSevWarning, SETUPLOG_USE_MESSAGEID, MSG_LOG_CHANGING_PW_FAIL, AccountName, NULL, SETUPLOG_USE_MESSAGEID, MSG_LOG_X_PARAM_RETURNED_NTSTATUS, szSamOpenDomain, Status, AccountsDomainName, NULL,NULL); goto err2; } // // Find the account name in this domain - and extract the rid. // UserFound = FALSE; EnumerationContext = 0; RtlInitUnicodeString(&UnicodeString,AccountName); do { Status = SamEnumerateUsersInDomain( DomainHandle, &EnumerationContext, 0L, &SamRidEnumeration, 0L, &CountOfEntries ); if(!NT_SUCCESS(Status) && (Status != STATUS_MORE_ENTRIES)) { SetuplogError( LogSevWarning, SETUPLOG_USE_MESSAGEID, MSG_LOG_CHANGING_PW_FAIL, AccountName, NULL, SETUPLOG_USE_MESSAGEID, MSG_LOG_X_RETURNED_NTSTATUS, szSamEnumerateUsersInDomain, Status, NULL,NULL); goto err3; } // // go through the the SamRidEnumeration buffer for count entries. // for(i = 0; (iusri1_flags |= UF_ACCOUNTDISABLE; rc = NetUserSetInfo( NULL, AccountName, 1, (PBYTE)ui1, NULL ); NetApiBufferFree((PVOID)ui1); } return rc; } NTSTATUS DisableLocalAdminAccount( VOID ) /*++ Routine Description: This routine disables the local administrator account. Arguments: None. Return Value: NTSTATUS, depending on the outcome of the operations. --*/ { NTSTATUS Status = STATUS_SUCCESS; WCHAR AdminAccountName[MAX_PATH]; GetAdminAccountName( AdminAccountName ); Status = DisableLocalUserAccount( AdminAccountName ); return Status; }