windows-nt/Source/XPSP1/NT/base/screg/sc/server/account.cxx

2325 lines
60 KiB
C++
Raw Permalink Normal View History

2020-09-26 03:20:57 -05:00
/*++
Copyright (c) 1992 Microsoft Corporation
Module Name:
account.cxx
Abstract:
This module contains service account related routines:
ScInitServiceAccount
ScEndServiceAccount
ScCanonAccountName
ScValidateAndSaveAccount
ScValidateAndChangeAccount
ScRemoveAccount
ScLookupAccount
ScSetPassword
ScDeletePassword
ScOpenPolicy
ScFormSecretName
ScLookupServiceAccount
ScLogonService
ScLoadUserProfile
ScUPNToAccountName
Author:
Rita Wong (ritaw) 19-Apr-1992
Environment:
Calls NT native APIs.
Revision History:
24-Jan-1993 Danl
Added call to WNetLogonNotify when logging on a service (ScLogonService).
29-Apr-1993 Danl
ScGetAccountDomainInfo() is now only called at init time. Otherwise,
we risked race conditions because it updates a global location.
(ScLocalDomain).
17-Jan-1995 AnirudhS
Added call to LsaOpenSecret when the secret already exists in
ScCreatePassword.
29-Nov-1995 AnirudhS
Added call to LoadUserProfile when logging on a service.
14-May-1996 AnirudhS
Changed to simpler Lsa PrivateData APIs instead of Lsa Secret APIs
for storing secrets and removed the use of OldPassword (as done in
the _CAIRO_ version of this file on 05-Apr-1995).
22-Oct-1997 JSchwart (after AnirudhS in _CAIRO_ 10-Apr-1995)
Split out ScLookupServiceAccount from ScLogonService.
04-Mar-1999 jschwart
Added support for UPNs
--*/
#include "precomp.hxx"
#include <stdlib.h> // srand, rand
extern "C" {
#include <ntlsa.h> // LsaOpenPolicy, LsaCreateSecret
}
#include <winerror.h>
#include <userenv.h> // LoadUserProfile
#include <userenvp.h> // PI_HIDEPROFILE flag
#include <tstr.h> // WCSSIZE
#include <ntdsapi.h> // DsCrackNames
#include <sclib.h> // _wcsicmp
#include <scseclib.h> // LocalSid
#include "scconfig.h" // ScWriteStartName
#include "account.h" // Exported function prototypes
//-------------------------------------------------------------------//
// //
// Constants and Macros //
// //
//-------------------------------------------------------------------//
#define SC_SECRET_PREFIX L"_SC_"
#define SC_UPN_SYMBOL L'@'
//-------------------------------------------------------------------//
// //
// Static global variables //
// //
//-------------------------------------------------------------------//
//
// Mutex to serialize access to secret objects
//
HANDLE ScSecretObjectsMutex = (HANDLE) NULL;
//
// LSA Authentication Package expects the local computername to
// be specified for the account domain if the system is WinNT,
// and the primary domain to be specified if the system is LanmanNT.
// Set ScLocalDomain to point to either ScComputerName or
// ScAccountDomain depending on the product type.
//
PUNICODE_STRING ScLocalDomain;
UNICODE_STRING ScComputerName;
UNICODE_STRING ScAccountDomain;
//-------------------------------------------------------------------//
// //
// Local function prototypes //
// //
//-------------------------------------------------------------------//
DWORD
ScLookupAccount(
IN LPWSTR AccountName,
OUT LPWSTR *DomainName,
OUT LPWSTR *UserName
);
DWORD
ScSetPassword(
IN LPWSTR ServiceName,
IN LPWSTR Password
);
DWORD
ScDeletePassword(
IN LPWSTR ServiceName
);
DWORD
ScOpenPolicy(
IN ACCESS_MASK DesiredAccess,
OUT LSA_HANDLE *PolicyHandle
);
DWORD
ScFormSecretName(
IN LPWSTR ServiceName,
OUT LPWSTR *LsaSecretName
);
VOID
ScLoadUserProfile(
IN HANDLE LogonToken,
IN LPWSTR DomainName,
IN LPWSTR UserName,
OUT PHANDLE pProfileHandle OPTIONAL
);
DWORD
ScUPNToAccountName(
IN LPWSTR lpUPN,
OUT LPWSTR *ppAccountName
);
//-------------------------------------------------------------------//
// //
// Functions //
// //
//-------------------------------------------------------------------//
BOOL
ScGetComputerNameAndMutex(
VOID
)
/*++
Routine Description:
This function allocates the memory for the ScComputerName global
pointer and retrieves the current computer name into it. This
functionality used to be in the ScInitAccount routine but has to
be put into its own routine because the main initialization code
needs to call this before ScInitDatabase() since the computername
is needed for deleting service entries that have the persistent
delete flag set.
This function also creates ScSecretObjectsMutex because it is used
to remove accounts early in the init process.
If successful, the pointer to the computername must be freed when
done. This is freed by the ScEndServiceAccount routine called
by SvcctrlMain(). The handle to ScSecretObjectsMutex is closed
by ScSecretObjectsMutex.
Arguments:
None
Return Value:
TRUE - The operation was completely successful.
FALSE - An error occurred.
--*/
{
DWORD ComputerNameSize = MAX_COMPUTERNAME_LENGTH + 1;
ScComputerName.Buffer = NULL;
//
// Allocate the exact size needed to hold the computername
//
if ((ScComputerName.Buffer = (LPWSTR)LocalAlloc(
LMEM_ZEROINIT,
(UINT) ComputerNameSize * sizeof(WCHAR)
)) == NULL) {
SC_LOG1(ERROR, "ScInitServiceAccount: LocalAlloc failed %lu\n", GetLastError());
return FALSE;
}
ScComputerName.MaximumLength = (USHORT) ComputerNameSize * sizeof(WCHAR);
if (! GetComputerNameW(
ScComputerName.Buffer,
&ComputerNameSize
)) {
SC_LOG2(ERROR, "GetComputerNameW returned %lu, required size=%lu\n",
GetLastError(), ComputerNameSize);
LocalFree(ScComputerName.Buffer);
ScComputerName.Buffer = NULL;
return FALSE;
}
ScComputerName.Length = (USHORT) (wcslen(ScComputerName.Buffer) * sizeof(WCHAR));
SC_LOG(ACCOUNT, "ScInitServiceAccount: ScComputerName is "
FORMAT_LPWSTR "\n", ScComputerName.Buffer);
//
// Create a mutex to serialize accesses to all secret objects. A secret
// object can be created, deleted, or set by installation programs, set
// by the service controller during periodic password changes, and queried
// or set by a start service operation.
//
ScSecretObjectsMutex = CreateMutex(NULL, FALSE, NULL);
if (ScSecretObjectsMutex == NULL) {
SC_LOG1(ERROR, "ScInitServiceAccount: CreateMutex failed "
FORMAT_DWORD "\n", GetLastError());
return FALSE;
}
return TRUE;
}
BOOL
ScInitServiceAccount(
VOID
)
/*++
Routine Description:
This function initializes accounts for services by
2) Register service controller as an LSA logon process and
lookup the MS V 1.0 authentication package.
Arguments:
None
Return Value:
TRUE - The operation was completely successful.
FALSE - An error occurred.
--*/
{
DWORD status;
//
// Initialize the account domain buffer so that we know if it has
// been filled in.
//
ScAccountDomain.Buffer = NULL;
status = ScGetAccountDomainInfo();
if (status != NO_ERROR)
{
SC_LOG1(ERROR, "ScInitServiceAccount: ScGetAccountDomainInfo failed "
FORMAT_DWORD "\n", status);
return FALSE;
}
return TRUE;
}
VOID
ScEndServiceAccount(
VOID
)
/*++
Routine Description:
This function frees the memory for the ScComputerName global pointer,
and closes the ScSecretObjectsMutex.
Arguments:
None.
Return Value:
None.
--*/
{
//
// Free computer name buffer allocated by ScGetComputerName
//
LocalFree(ScComputerName.Buffer);
ScComputerName.Buffer = NULL;
if (ScSecretObjectsMutex != (HANDLE) NULL)
{
CloseHandle(ScSecretObjectsMutex);
}
LocalFree(ScAccountDomain.Buffer);
}
DWORD
ScValidateAndSaveAccount(
IN LPWSTR ServiceName,
IN HKEY ServiceNameKey,
IN LPWSTR CanonAccountName,
IN LPWSTR Password OPTIONAL
)
/*++
Routine Description:
This function verifies that the account is valid, and then saves
the account information away. The account name is saved in the
registry under the service node in the ObjectName value. The
password is saved in an LSA secret object created which can be
looked up based on the name string formed with the service name.
This function can only be called for the installation of a Win32
service (CreateService).
NOTE: The registry ServiceNameKey is NOT flushed by this function.
Arguments:
ServiceName - Supplies the name of the service to save away account
info for. This makes up part of the secret object name to tuck
away the password.
ServiceNameKey - Supplies an opened registry key handle for the service.
CanonAccountName - Supplies a canonicalized account name string in the
format of DomainName\Username, LocalSystem, or UPN
Password - Supplies the password of the account, if any. This is
ignored if LocalSystem is specified for the account name.
Return Value:
NO_ERROR - The operation was successful.
ERROR_INVALID_SERVICE_ACCOUNT - The account name is invalid.
ERROR_NOT_ENOUGH_MEMORY - Failed to allocate work buffer.
Registry error codes caused by failure to read old account name
string.
--*/
{
DWORD status;
LPWSTR DomainName;
LPWSTR UserName;
LPWSTR lpNameToParse = CanonAccountName;
//
// Empty account name is invalid.
//
if ((CanonAccountName == NULL) || (*CanonAccountName == 0)) {
return ERROR_INVALID_SERVICE_ACCOUNT;
}
if (_wcsicmp(CanonAccountName, SC_LOCAL_SYSTEM_USER_NAME) == 0) {
//
// CanonAccountName is LocalSystem. Write to the registry and
// we are done.
//
return ScWriteStartName(
ServiceNameKey,
SC_LOCAL_SYSTEM_USER_NAME
);
}
//
// Account name is DomainName\UserName or a UPN
//
if (wcschr(CanonAccountName, SCDOMAIN_USERNAME_SEPARATOR) == NULL
&&
wcschr(CanonAccountName, SC_UPN_SYMBOL) != NULL)
{
//
// It's a UPN -- we need to crack it
//
status = ScUPNToAccountName(CanonAccountName, &lpNameToParse);
if (status != NO_ERROR) {
return status;
}
}
//
// Look up the account to see if it exists.
//
if ((status = ScLookupAccount(
lpNameToParse,
&DomainName,
&UserName
)) != NO_ERROR) {
if (lpNameToParse != CanonAccountName) {
LocalFree(lpNameToParse);
}
return status;
}
//
// Write the new account name to the registry.
// Note -- for UPNs, write the UPN to the registry, not
// the cracked UPN
//
if ((status = ScWriteStartName(
ServiceNameKey,
CanonAccountName
)) != NO_ERROR) {
if (lpNameToParse != CanonAccountName) {
LocalFree(lpNameToParse);
}
LocalFree(DomainName);
return status;
}
//
// Create the password for the new account.
//
status = ScSetPassword(
ServiceName,
Password
);
if (lpNameToParse != CanonAccountName) {
LocalFree(lpNameToParse);
}
LocalFree(DomainName);
return status;
//
// Don't have to worry about removing the account name written to
// the registry if ScSetPassword returned an error because the
// entire service key will be deleted by the caller of this routine.
//
}
DWORD
ScValidateAndChangeAccount(
IN LPSERVICE_RECORD ServiceRecord,
IN HKEY ServiceNameKey,
IN LPWSTR OldAccountName,
IN LPWSTR CanonAccountName,
IN LPWSTR Password OPTIONAL
)
/*++
Routine Description:
This function validates that the account is valid, and then replaces
the old account information. The account name is saved in the
registry under the service node in the ObjectName value. The
password is saved in an LSA secret object created which can be
looked up based on the name string formed with the service name.
This function can only be called for the reconfiguration of a Win32
service (ChangeServiceConfig).
NOTE: The registry ServiceNameKey is NOT flushed by this function.
Arguments:
ServiceRecord - Supplies the record of the service to change account
info. This makes up part of the secret object name to tuck
away the password.
ServiceNameKey - Supplies an opened registry key handle for the service.
OldAccountName - Supplies the string to the old account name.
CanonAccountName - Supplies a canonicalized account name string in the
format of DomainName\Username, LocalSystem, or a UPN.
Password - Supplies the password of the account, if any. This is
ignored if LocalSystem is specified for the account name.
Return Value:
NO_ERROR - The operation was successful.
ERROR_INVALID_SERVICE_ACCOUNT - The account name is invalid.
ERROR_ALREADY_EXISTS - Attempt to create an LSA secret object that
already exists.
Registry error codes caused by failure to read old account name
string.
--*/
{
DWORD status;
LPWSTR DomainName;
LPWSTR UserName;
BOOL fIsUPN = FALSE;
LPWSTR lpNameToParse = CanonAccountName;
if ((CanonAccountName == OldAccountName) ||
(_wcsicmp(CanonAccountName, OldAccountName) == 0)) {
//
// Newly specified account name is identical to existing
// account name.
//
if (Password == NULL) {
//
// Not changing account name or password.
//
return NO_ERROR;
}
if (_wcsicmp(CanonAccountName, SC_LOCAL_SYSTEM_USER_NAME) == 0) {
//
// Account name is LocalSystem and password is specified.
// Just ignore.
//
return NO_ERROR;
}
else {
//
// Account name is DomainName\UserName or a UPN.
// Set the specified password.
//
status = ScSetPassword(
ServiceRecord->ServiceName,
Password);
return status;
}
}
//
// Newly specified account name is different from existing
// account name.
//
if (Password == NULL) {
//
// Cannot specify new account name without specifying
// the password also.
//
return ERROR_INVALID_SERVICE_ACCOUNT;
}
if (_wcsicmp(CanonAccountName, SC_LOCAL_SYSTEM_USER_NAME) == 0) {
//
// Change from DomainName\UserName or UPN to LocalSystem
//
//
// Write the new account name to the registry.
//
if ((status = ScWriteStartName(
ServiceNameKey,
SC_LOCAL_SYSTEM_USER_NAME
)) != NO_ERROR) {
return status;
}
//
// Account name is LocalSystem and password is specified.
// Ignore the password specified, and delete the password
// for the old account.
//
status = ScDeletePassword(ServiceRecord->ServiceName);
if (status != NO_ERROR) {
//
// Restore the old account name to the registry.
//
ScWriteStartName(ServiceNameKey,
OldAccountName);
}
else {
LPWSTR CurrentDependencies;
//
// Get rid of the implicit dependency on NetLogon since this
// service no longer runs in an account. Since the dependency
// on NetLogon is soft (i.e., not stored in the registry),
// simply read in the dependencies and update the service record
//
status = ScReadDependencies(ServiceNameKey,
&CurrentDependencies,
ServiceRecord->ServiceName);
if (status == NO_ERROR) {
//
// Dynamically update the dependencies
//
status = ScUpdateServiceRecordConfig(
ServiceRecord,
SERVICE_NO_CHANGE,
SERVICE_NO_CHANGE,
SERVICE_NO_CHANGE,
NULL,
(LPBYTE) CurrentDependencies);
if (status != NO_ERROR) {
SC_LOG1(ERROR,
"ScValidateAndChangeAccount: ScUpdateServiceRecordConfig "
"FAILED %d\n",
status);
}
LocalFree(CurrentDependencies);
}
else {
SC_LOG1(ERROR,
"ScValidateAndChangeAccount: ScReadDependencies "
"FAILED %d\n",
status);
}
}
return status;
}
if (_wcsicmp(OldAccountName, SC_LOCAL_SYSTEM_USER_NAME) == 0) {
//
// Change from LocalSystem to DomainName\UserName or UPN.
//
if (wcschr(CanonAccountName, SCDOMAIN_USERNAME_SEPARATOR) == NULL
&&
wcschr(CanonAccountName, SC_UPN_SYMBOL) != NULL)
{
fIsUPN = TRUE;
status = ScUPNToAccountName(CanonAccountName, &lpNameToParse);
if (status != NO_ERROR) {
return status;
}
}
if ((status = ScLookupAccount(
lpNameToParse,
&DomainName,
&UserName
)) != NO_ERROR) {
if (fIsUPN) {
LocalFree(lpNameToParse);
}
return status;
}
//
// Write the new account name to the registry.
//
if ((status = ScWriteStartName(
ServiceNameKey,
CanonAccountName
)) != NO_ERROR) {
if (fIsUPN) {
LocalFree(lpNameToParse);
}
LocalFree(DomainName);
return status;
}
//
// Create the password for the new account.
//
status = ScSetPassword(
ServiceRecord->ServiceName,
Password);
if (status != NO_ERROR) {
//
// Restore the old account name to the registry.
//
(void) ScWriteStartName(
ServiceNameKey,
SC_LOCAL_SYSTEM_USER_NAME);
}
if (fIsUPN) {
LocalFree(lpNameToParse);
}
LocalFree(DomainName);
return status;
}
//
// Must be changing an account of DomainName\UserName or UPN to
// DomainName\UserName or UPN
//
if (wcschr(CanonAccountName, SCDOMAIN_USERNAME_SEPARATOR) == NULL
&&
wcschr(CanonAccountName, SC_UPN_SYMBOL) != NULL)
{
fIsUPN = TRUE;
status = ScUPNToAccountName(CanonAccountName, &lpNameToParse);
if (status != NO_ERROR) {
return status;
}
}
if ((status = ScLookupAccount(
lpNameToParse,
&DomainName,
&UserName
)) != NO_ERROR) {
if (fIsUPN) {
LocalFree(lpNameToParse);
}
return status;
}
//
// Write the new account name to the registry.
//
if ((status = ScWriteStartName(
ServiceNameKey,
CanonAccountName
)) != NO_ERROR) {
if (fIsUPN) {
LocalFree(lpNameToParse);
}
LocalFree(DomainName);
return status;
}
//
// Set the password for the new account.
//
status = ScSetPassword(ServiceRecord->ServiceName,
Password);
if (status != NO_ERROR) {
//
// Restore the old account name to the registry.
//
ScWriteStartName(ServiceNameKey,
OldAccountName);
}
else if (*DomainName == L'.' && !fIsUPN) {
LPWSTR CurrentDependencies;
//
// Get rid of the implicit dependency on NetLogon since this
// service now runs in a local account (domain is ".\")
//
status = ScReadDependencies(ServiceNameKey,
&CurrentDependencies,
ServiceRecord->ServiceName);
if (status == NO_ERROR) {
//
// Dynamically update the dependencies
//
status = ScUpdateServiceRecordConfig(
ServiceRecord,
SERVICE_NO_CHANGE,
SERVICE_NO_CHANGE,
SERVICE_NO_CHANGE,
NULL,
(LPBYTE) CurrentDependencies);
if (status != NO_ERROR) {
SC_LOG1(ERROR,
"ScValidateAndChangeAccount: ScUpdateServiceRecordConfig "
"FAILED %d\n",
status);
}
LocalFree(CurrentDependencies);
}
else {
SC_LOG1(ERROR,
"ScValidateAndChangeAccount: ScReadDependencies "
"FAILED %d\n",
status);
}
}
if (fIsUPN) {
LocalFree(lpNameToParse);
}
LocalFree(DomainName);
return status;
}
VOID
ScRemoveAccount(
IN LPWSTR ServiceName
)
{
(void) ScDeletePassword(ServiceName);
}
DWORD
ScCanonAccountName(
IN LPWSTR AccountName,
OUT LPWSTR *CanonAccountName
)
/*++
Routine Description:
This function canonicalizes the account name and allocates the
returned buffer for returning the canonicalized string.
AccountName *CanonAccountName
----------- -----------------
.\UserName .\UserName
ComputerName\UserName .\UserName
LocalSystem LocalSystem
.\LocalSystem LocalSystem
ComputerName\LocalSystem LocalSystem
DomainName\UserName DomainName\UserName
DomainName\LocalSystem Error!
UPN (foo@bar) UPN (foo@bar)
Caller must free the CanonAccountName pointer with LocalFree when done.
Arguments:
AccountName - Supplies a pointer to the account name.
CanonAccountName - Receives a pointer to the buffer (allocated by this
routine) which contains the canonicalized account name. Must
free this pointer with LocalFree.
Return Value:
NO_ERROR - Successful canonicalization.
ERROR_NOT_ENOUGH_MEMORY - Out of memory trying to allocate CanonAccountName
buffer.
ERROR_INVALID_SERVICE_ACCOUNT - Invalid account name.
--*/
{
LPWSTR BufPtr = wcschr(AccountName, SCDOMAIN_USERNAME_SEPARATOR);
//
// Allocate buffer for receiving the canonicalized account name.
//
if ((*CanonAccountName = (LPWSTR)LocalAlloc(
0,
WCSSIZE(AccountName) +
ScComputerName.MaximumLength
)) == NULL) {
SC_LOG1(ERROR, "ScCanonAccountName: LocalAlloc failed %lu\n",
GetLastError());
return ERROR_NOT_ENOUGH_MEMORY;
}
if (BufPtr == NULL) {
//
// Backslash is not found.
//
if (_wcsicmp(AccountName, SC_LOCAL_SYSTEM_USER_NAME) == 0
||
wcschr(AccountName, SC_UPN_SYMBOL) != NULL)
{
//
// Account name is LocalSystem or a UPN
//
wcscpy(*CanonAccountName, AccountName);
return NO_ERROR;
}
else {
//
// The AccountName is neither LocalSystem nor a UPN -- invalid.
//
SC_LOG1(ERROR,
"Account name %ws is not LocalSystem and has no \\ or @\n",
AccountName);
LocalFree(*CanonAccountName);
*CanonAccountName = NULL;
return ERROR_INVALID_SERVICE_ACCOUNT;
}
}
//
// BufPtr points to the first occurrence of backslash in
// AccountName.
//
//
// If first portion of the AccountName matches ".\" or "ComputerName\"
//
if ((wcsncmp(AccountName, L".\\", 2) == 0) ||
((_wcsnicmp(AccountName, ScComputerName.Buffer,
ScComputerName.Length / sizeof(WCHAR)) == 0) &&
((LPWSTR) ((DWORD_PTR) AccountName + ScComputerName.Length) == BufPtr))) {
if (_wcsicmp(BufPtr + 1, SC_LOCAL_SYSTEM_USER_NAME) == 0) {
//
// .\LocalSystem -> LocalSystem OR
// Computer\LocalSystem -> LocalSystem
//
wcscpy(*CanonAccountName, SC_LOCAL_SYSTEM_USER_NAME);
return NO_ERROR;
}
//
// .\XXX -> .\XXX
// ComputerName\XXX -> .\XXX
//
wcscpy(*CanonAccountName, SC_LOCAL_DOMAIN_NAME);
wcscat(*CanonAccountName, BufPtr);
return NO_ERROR;
}
//
// First portion of the AccountName specifies a domain name other than
// the local one. This domain name will be validated later in
// ScValidateAndSaveAccount.
//
if (_wcsicmp(BufPtr + 1, SC_LOCAL_SYSTEM_USER_NAME) == 0) {
//
// XXX\LocalSystem is invalid.
//
LocalFree(*CanonAccountName);
*CanonAccountName = NULL;
SC_LOG0(ERROR, "Account name is LocalSystem but is not local\n");
return ERROR_INVALID_SERVICE_ACCOUNT;
}
wcscpy(*CanonAccountName, AccountName);
return NO_ERROR;
}
BOOL
GetDefaultDomainName(
LPWSTR DomainName
)
{
OBJECT_ATTRIBUTES ObjectAttributes;
NTSTATUS NtStatus;
LSA_HANDLE LsaPolicyHandle = NULL;
PPOLICY_ACCOUNT_DOMAIN_INFO DomainInfo = NULL;
//
// Open a handle to the local machine's LSA policy object.
//
InitializeObjectAttributes( &ObjectAttributes, // object attributes
NULL, // name
0L, // attributes
NULL, // root directory
NULL ); // security descriptor
NtStatus = LsaOpenPolicy( NULL, // system name
&ObjectAttributes, // object attributes
POLICY_EXECUTE, // access mask
&LsaPolicyHandle ); // policy handle
if( !NT_SUCCESS( NtStatus ) )
{
return(FALSE);
}
//
// Query the domain information from the policy object.
//
NtStatus = LsaQueryInformationPolicy( LsaPolicyHandle,
PolicyAccountDomainInformation,
(PVOID *) &DomainInfo );
if (!NT_SUCCESS(NtStatus))
{
LsaClose(LsaPolicyHandle);
return(FALSE);
}
(void) LsaClose(LsaPolicyHandle);
//
// Copy the domain name into our cache, and
//
CopyMemory( DomainName,
DomainInfo->DomainName.Buffer,
DomainInfo->DomainName.Length );
//
// Null terminate it appropriately
//
DomainName[DomainInfo->DomainName.Length / sizeof(WCHAR)] = L'\0';
//
// Clean up
//
LsaFreeMemory( (PVOID)DomainInfo );
return TRUE;
}
DWORD
ScLookupAccount(
IN LPWSTR AccountName,
OUT LPWSTR *DomainName,
OUT LPWSTR *UserName
)
/*++
Routine Description:
This function calls LsaLookupNames to see if the specified username
exists in the specified domain name. If this function returns
NO_ERROR, DomainName and UserName pointers will be set to the
domain name and username strings in the buffer allocated by this
function.
The caller must free the returned buffer by calling LocalFree
on the pointer returned in DomainName.
Arguments:
AccountName - Supplies the account name in the format of
DomainName\UserName to look up.
DomainName - Receives a pointer to the allocated buffer which
contains the NULL-terminated domain name string, followed
by the NULL-terminated user name string.
UserName - Receives a pointer to the username in the returned
buffer allocated by this routine.
Return Value:
NO_ERROR - UserName is found in the DomainName.
ERROR_NOT_ENOUGH_MEMORY - Failed to allocate work buffer.
ERROR_INVALID_SERVICE_ACCOUNT - any other error that is encountered
in this function.
--*/
{
DWORD status;
NTSTATUS ntstatus;
LSA_HANDLE PolicyHandle;
UNICODE_STRING AccountNameString;
PLSA_REFERENCED_DOMAIN_LIST ReferencedDomains;
PLSA_TRANSLATED_SID Sids;
LPWSTR BackSlashPtr;
LPWSTR LocalAccount = NULL;
WCHAR Domain[MAX_COMPUTERNAME_LENGTH+1];
//
// Allocate buffer for separating AccountName into DomainName and
// UserName.
//
if ((*DomainName = (LPWSTR) LocalAlloc(
0,
WCSSIZE(AccountName)
)) == NULL) {
SC_LOG1(ERROR, "ScLookupAccount: LocalAlloc failed %lu\n",
GetLastError());
return ERROR_NOT_ENOUGH_MEMORY;
}
//
// Find the backslash character in the specified account name
//
wcscpy(*DomainName, AccountName);
BackSlashPtr = wcschr(*DomainName, SCDOMAIN_USERNAME_SEPARATOR);
if (BackSlashPtr == NULL) {
SC_LOG0(ERROR, "ScLookupAccount: No backslash in account name!\n");
ScLogEvent(NEVENT_BAD_ACCOUNT_NAME);
SC_ASSERT(FALSE);
status = ERROR_GEN_FAILURE;
goto CleanExit;
}
*UserName = BackSlashPtr + 1; // Skip the backslash
if (_wcsnicmp(*DomainName, SC_LOCAL_DOMAIN_NAME, wcslen(SC_LOCAL_DOMAIN_NAME)) == 0) {
//
// DomainName is "." (local domain), so convert "." to the
// local domain name, which on WinNT systems is the computername,
// and on Adv Server systems it's the account domain name.
//
//
// Allocate buffer to hold Domain\UserName string.
//
//
// this code does not use the global var LocalDomain because it
// contains invalid data during gui mode setup. if a service
// that needs an account/password is created during gui mode
// setup, the create would fail with the LocalDomain value. calling
// the GetDefaultDomainName funtion guarantees that we have
// the correct value in all cases.
//
if (!GetDefaultDomainName( Domain )) {
SC_LOG0( ERROR, "ScLookupAccount: GetDefaultDomainName failed\n");
status = ERROR_GEN_FAILURE;
goto CleanExit;
}
if ((LocalAccount = (LPWSTR) LocalAlloc(
LMEM_ZEROINIT,
WCSSIZE(Domain) + WCSSIZE(*UserName)
)) == NULL)
{
SC_LOG1(ERROR, "ScLookupAccount: LocalAlloc failed %lu\n", GetLastError());
status = ERROR_NOT_ENOUGH_MEMORY;
goto CleanExit;
}
wcscpy( LocalAccount, Domain );
wcscat( LocalAccount, BackSlashPtr );
RtlInitUnicodeString( &AccountNameString, LocalAccount );
}
else {
//
// Lookup the domain-qualified name.
//
RtlInitUnicodeString(&AccountNameString, *DomainName);
}
//
// Open a handle to the local security policy.
//
if (ScOpenPolicy(
POLICY_LOOKUP_NAMES |
POLICY_VIEW_LOCAL_INFORMATION,
&PolicyHandle
) != NO_ERROR) {
SC_LOG0(ERROR, "ScLookupAccount: ScOpenPolicy failed\n");
status = ERROR_INVALID_SERVICE_ACCOUNT;
goto CleanExit;
}
ntstatus = LsaLookupNames(
PolicyHandle,
1,
&AccountNameString,
&ReferencedDomains,
&Sids
);
if (! NT_SUCCESS(ntstatus)) {
SC_LOG1(ERROR,
"ScLookupAccount: LsaLookupNames returned " FORMAT_NTSTATUS "\n",
ntstatus);
(void) LsaClose(PolicyHandle);
status = ERROR_INVALID_SERVICE_ACCOUNT;
goto CleanExit;
}
//
// Don't need PolicyHandle anymore
//
(void) LsaClose(PolicyHandle);
//
// Free the returned SIDs since we don't look at them.
//
if (Sids != NULL) {
LsaFreeMemory((PVOID) Sids);
}
if (ReferencedDomains == NULL) {
SC_LOG1(ERROR, "ScLookupAccount: Did not find " FORMAT_LPWSTR
" in any domain\n", AccountNameString.Buffer);
status = ERROR_INVALID_SERVICE_ACCOUNT;
goto CleanExit;
}
else {
LsaFreeMemory((PVOID) ReferencedDomains);
}
status = NO_ERROR;
//
// Convert DomainName\UserName into DomainName0UserName.
//
*BackSlashPtr = 0;
CleanExit:
LocalFree(LocalAccount);
if (status != NO_ERROR) {
LocalFree(*DomainName);
*DomainName = NULL;
}
return status;
}
DWORD
ScSetPassword(
IN LPWSTR ServiceName,
IN LPWSTR Password
)
/*++
Routine Description:
This function sets the secret object for the service with the specified
password. If the secret object doesn't already exist, it is created.
Arguments:
ServiceName - Supplies the service name which is part of the secret
object name to be created.
Password - Supplies the user specified password for an account.
Return Value:
NO_ERROR - Secret object for the password is created and set with new value.
ERROR_INVALID_SERVICE_ACCOUNT - for any error encountered in this
function. The true error is written to the event log.
--*/
{
DWORD status;
NTSTATUS ntstatus;
LSA_HANDLE PolicyHandle;
LPWSTR LsaSecretName;
UNICODE_STRING SecretNameString;
UNICODE_STRING NewPasswordString;
//
// Open a handle to the local security policy.
//
if (ScOpenPolicy(
POLICY_CREATE_SECRET,
&PolicyHandle
) != NO_ERROR) {
SC_LOG0(ERROR, "ScSetPassword: ScOpenPolicy failed\n");
return ERROR_INVALID_SERVICE_ACCOUNT;
}
//
// Create the secret object. But first, let's form a secret
// name that is very difficult to guess.
//
if ((status = ScFormSecretName(
ServiceName,
&LsaSecretName
)) != NO_ERROR) {
LsaClose(PolicyHandle);
return status;
}
//
// Serialize secret object operations
//
if (WaitForSingleObject(ScSecretObjectsMutex, INFINITE) == MAXULONG) {
status = GetLastError();
SC_LOG1(ERROR, "ScSetPassword: WaitForSingleObject failed "
FORMAT_DWORD "\n", status);
LocalFree(LsaSecretName);
(void) LsaClose(PolicyHandle);
return status;
}
RtlInitUnicodeString(&SecretNameString, LsaSecretName);
RtlInitUnicodeString(&NewPasswordString, Password);
ntstatus = LsaStorePrivateData(
PolicyHandle,
&SecretNameString,
&NewPasswordString
);
if (NT_SUCCESS(ntstatus)) {
SC_LOG1(ACCOUNT, "ScSetPassword " FORMAT_LPWSTR " success\n",
ServiceName);
status = NO_ERROR;
}
else {
SC_LOG2(ERROR,
"ScSetPassword: LsaStorePrivateData returned " FORMAT_NTSTATUS
" for " FORMAT_LPWSTR "\n", ntstatus, LsaSecretName);
//
// The ntstatus code was not mapped to a windows error because it wasn't
// clear if all the mappings made sense, and the feeling was that
// information would be lost during the mapping.
//
ScLogEvent(
NEVENT_CALL_TO_FUNCTION_FAILED,
SC_LSA_STOREPRIVATEDATA,
ntstatus);
status = ERROR_INVALID_SERVICE_ACCOUNT;
}
LocalFree(LsaSecretName);
(void) LsaClose(PolicyHandle);
(void) ReleaseMutex(ScSecretObjectsMutex);
return status;
}
DWORD
ScDeletePassword(
IN LPWSTR ServiceName
)
/*++
Routine Description:
This function deletes the LSA secret object whose name is derived
from the specified ServiceName.
Arguments:
ServiceName - Supplies the service name which is part of the secret
object name to be deleted.
Return Value:
NO_ERROR - Secret object for password is deleted.
ERROR_INVALID_SERVICE_ACCOUNT - for any error encountered in this
function. The true error is written to the event log.
--*/
{
DWORD status;
NTSTATUS ntstatus;
LSA_HANDLE PolicyHandle;
UNICODE_STRING SecretNameString;
LPWSTR LsaSecretName;
//
// Open a handle to the local security policy.
//
if (ScOpenPolicy(
POLICY_VIEW_LOCAL_INFORMATION,
&PolicyHandle
) != NO_ERROR) {
SC_LOG0(ERROR, "ScDeletePassword: ScOpenPolicy failed\n");
return ERROR_INVALID_SERVICE_ACCOUNT;
}
//
// Get the secret object name from the specified service name.
//
if ((status = ScFormSecretName(
ServiceName,
&LsaSecretName
)) != NO_ERROR) {
(void) LsaClose(PolicyHandle);
return status;
}
//
// Serialize secret object operations
//
if (WaitForSingleObject(ScSecretObjectsMutex, INFINITE) == MAXULONG) {
status = GetLastError();
SC_LOG1(ERROR, "ScDeletePassword: WaitForSingleObject failed "
FORMAT_DWORD "\n", status);
LocalFree(LsaSecretName);
LsaClose(PolicyHandle);
return status;
}
RtlInitUnicodeString(&SecretNameString, LsaSecretName);
ntstatus = LsaStorePrivateData(
PolicyHandle,
&SecretNameString,
NULL
);
//
// Treat STATUS_OBJECT_NAME_NOT_FOUND as success since the
// password's already deleted (effectively) in that case.
//
if (NT_SUCCESS(ntstatus) || (ntstatus == STATUS_OBJECT_NAME_NOT_FOUND))
{
SC_LOG1(ACCOUNT, "ScDeletePassword " FORMAT_LPWSTR " success\n",
ServiceName);
status = NO_ERROR;
}
else
{
SC_LOG2(ERROR,
"ScDeletePassword: LsaStorePrivateData returned " FORMAT_NTSTATUS
" for " FORMAT_LPWSTR "\n", ntstatus, LsaSecretName);
//
// The ntstatus code was not mapped to a windows error because it wasn't
// clear if all the mappings made sense, and the feeling was that
// information would be lost during the mapping.
//
ScLogEvent(
NEVENT_CALL_TO_FUNCTION_FAILED,
SC_LSA_STOREPRIVATEDATA,
ntstatus);
status = ERROR_INVALID_SERVICE_ACCOUNT;
}
LocalFree(LsaSecretName);
LsaClose(PolicyHandle);
ReleaseMutex(ScSecretObjectsMutex);
return status;
}
DWORD
ScOpenPolicy(
IN ACCESS_MASK DesiredAccess,
OUT LSA_HANDLE *PolicyHandle
)
/*++
Routine Description:
This function gets a handle to the local security policy by calling
LsaOpenPolicy.
Arguments:
DesiredAccess - Supplies the desired access to the local security
policy.
PolicyHandle - Receives a handle to the opened policy.
Return Value:
NO_ERROR - Policy handle is returned.
ERROR_INVALID_SERVICE_ACCOUNT - for any error encountered in this
function.
--*/
{
NTSTATUS ntstatus;
OBJECT_ATTRIBUTES ObjAttributes;
//
// Open a handle to the local security policy. Initialize the
// objects attributes structure first.
//
InitializeObjectAttributes(
&ObjAttributes,
NULL,
0L,
NULL,
NULL
);
ntstatus = LsaOpenPolicy(
NULL,
&ObjAttributes,
DesiredAccess,
PolicyHandle
);
if (! NT_SUCCESS(ntstatus)) {
SC_LOG1(ERROR,
"ScOpenPolicy: LsaOpenPolicy returned " FORMAT_NTSTATUS "\n",
ntstatus);
//
// The ntstatus code was not mapped to a windows error because it wasn't
// clear if all the mappings made sense, and the feeling was that
// information would be lost during the mapping.
//
ScLogEvent(
NEVENT_CALL_TO_FUNCTION_FAILED,
SC_LSA_OPENPOLICY,
ntstatus
);
return ERROR_INVALID_SERVICE_ACCOUNT;
}
return NO_ERROR;
}
DWORD
ScFormSecretName(
IN LPWSTR ServiceName,
OUT LPWSTR *LsaSecretName
)
/*++
Routine Description:
This function creates a secret name from the service name.
It also allocates the buffer to return the created secret name which
must be freed by the caller using LocalFree when done with it.
Arguments:
ServiceName - Supplies the service name which is part of the secret
object name we are creating.
LsaSecretName - Receives a pointer to the buffer which contains the
secret object name.
Return Value:
NO_ERROR - Successfully returned secret name.
ERROR_NOT_ENOUGH_MEMORY - Failed to allocate buffer to hold the secret
name.
--*/
{
if ((*LsaSecretName = (LPWSTR)LocalAlloc(
0,
(wcslen(SC_SECRET_PREFIX) +
wcslen(ServiceName) +
1) * sizeof(WCHAR)
)) == NULL) {
SC_LOG1(ERROR, "ScFormSecretName: LocalAlloc failed %lu\n",
GetLastError());
return ERROR_NOT_ENOUGH_MEMORY;
}
wcscpy(*LsaSecretName, SC_SECRET_PREFIX);
wcscat(*LsaSecretName, ServiceName);
return NO_ERROR;
}
DWORD
ScLookupServiceAccount(
IN LPWSTR ServiceName,
OUT LPWSTR *AccountName
)
/*++
Routine Description:
This function looks up the service account from the registry.
Arguments:
ServiceName - Supplies the service name to logon.
AccountName - Receives a pointer to a string containing the name of
the account that the service is configured to logon under. The
pointer returned is NULL if the service account is LocalSystem.
Otherwise the string is in the form .\UserName or
DomainName\UserName where DomainName != the local computername.
It must be freed with LocalAlloc when done.
Return Value:
NO_ERROR - Secret object for password is changed to new value.
ERROR_INVALID_SERVICE_ACCOUNT - The account name obtained from the
registry is invalid.
Other errors from registry APIs.
--*/
{
DWORD status;
HKEY ServiceNameKey;
LPWSTR DomainName = NULL;
LPWSTR UserName;
LPWSTR Separator;
LPWSTR lpNameToParse;
*AccountName = NULL;
//
// Open the service name key.
//
status = ScOpenServiceConfigKey(
ServiceName,
KEY_READ,
FALSE, // Create if missing
&ServiceNameKey
);
if (status != NO_ERROR) {
return status;
}
//
// Read the account name from the registry.
//
status = ScReadStartName(
ServiceNameKey,
&lpNameToParse
);
ScRegCloseKey(ServiceNameKey);
if (status != NO_ERROR) {
return status;
}
if (lpNameToParse == NULL) {
return ERROR_INVALID_SERVICE_ACCOUNT;
}
//
// Check if the account name is LocalSystem.
//
if (_wcsicmp(lpNameToParse, SC_LOCAL_SYSTEM_USER_NAME) == 0) {
LocalFree(lpNameToParse);
return NO_ERROR;
}
//
// If it isn't LocalSystem, it must be in the form
// Domain\User or .\User.
//
Separator = wcsrchr(lpNameToParse, SCDOMAIN_USERNAME_SEPARATOR);
if (Separator == NULL) {
if (wcsrchr(lpNameToParse, SC_UPN_SYMBOL) != NULL) {
//
// It's a UPN -- crack it
//
status = ScUPNToAccountName(lpNameToParse, &DomainName);
LocalFree(lpNameToParse);
if (status != NO_ERROR
||
(Separator = wcschr(DomainName, SCDOMAIN_USERNAME_SEPARATOR)) == NULL)
{
SC_LOG1(ERROR,
"ScLookupServiceAccount: ScUPNToAccountName failed %d\n",
status);
if (status == NO_ERROR) {
SC_LOG1(ACCOUNT,
"Cracked account name was %ws\n",
DomainName);
}
LocalFree(DomainName);
return ERROR_INVALID_SERVICE_ACCOUNT;
}
}
else {
SC_LOG1(ERROR,
"ScLookupServiceAccount: No \\ or @ in account name %ws\n",
lpNameToParse);
LocalFree(lpNameToParse);
return ERROR_INVALID_SERVICE_ACCOUNT;
}
}
else {
DomainName = lpNameToParse;
}
*Separator = 0;
UserName = Separator + 1;
//
// Translate ComputerName into . (to facilitate subsequent comparison
// of account names)
//
if (_wcsicmp(DomainName, ScComputerName.Buffer) == 0)
{
WCHAR *Dest, *Src;
// Assumption: "." is no longer than any computer name
SC_ASSERT(wcslen(SC_LOCAL_DOMAIN_NAME) == 1 &&
wcslen(DomainName) >= 1);
wcscpy(DomainName, SC_LOCAL_DOMAIN_NAME);
Separator = DomainName + wcslen(SC_LOCAL_DOMAIN_NAME);
// Shift username left
Src = UserName;
UserName = Separator + 1;
Dest = UserName;
while (*Dest++ = *Src++)
;
}
//
// Check if the user name is LocalSystem
//
if (_wcsicmp(UserName, SC_LOCAL_SYSTEM_USER_NAME) == 0) {
//
// This is only acceptable if DomainName is "."
//
if (_wcsicmp(DomainName, SC_LOCAL_DOMAIN_NAME) == 0) {
status = NO_ERROR;
}
else {
status = ERROR_INVALID_SERVICE_ACCOUNT;
}
LocalFree(DomainName);
return status;
}
//
// Restore the "\"
//
*Separator = SCDOMAIN_USERNAME_SEPARATOR;
*AccountName = DomainName;
return NO_ERROR;
}
DWORD
ScLogonService(
IN LPWSTR ServiceName,
IN LPWSTR AccountName,
OUT LPHANDLE ServiceToken,
OUT LPHANDLE pProfileHandle OPTIONAL,
OUT PSID *ServiceSid
)
/*++
Routine Description:
This function looks up the service account from the registry and
the password from the secret object to logon the service. If
successful, the handle to the logon token is returned.
Arguments:
ServiceName - Supplies the service name to logon.
AccountName - Supplies the account name to logon the service under.
(Supplied as an optimization since ScLookupServiceAccount will
have been called before calling this routine.) It must be of
the form .\UserName or DomainName\UserName, where DomainName !=
the local computer name and UserName != LocalSystem.
ServiceToken - Receives a handle to the logon token for the
service. The handle returned is NULL if the service account
is LocalSystem (i.e. spawn as child process of the service
controller).
ServiceSid - Receives a pointer to the logon SID of the service.
This must be freed with LocalAlloc when done.
Return Value:
NO_ERROR - Secret object for password is changed to new value.
ERROR_SERVICE_LOGON_FAILED - for any error encountered in this
function.
--*/
{
DWORD status;
LPWSTR Separator;
LPWSTR LsaSecretName = NULL;
*ServiceToken = NULL;
*ServiceSid = NULL;
status = ScFormSecretName(ServiceName, &LsaSecretName);
if (status != NO_ERROR)
{
SC_LOG(ERROR, "ScLogonService: ScFormSecretname failed %lu\n", status);
return ERROR_SERVICE_LOGON_FAILED;
}
Separator = wcsrchr(AccountName, SCDOMAIN_USERNAME_SEPARATOR);
if (Separator == NULL)
{
SC_ASSERT(Separator != NULL);
LocalFree(LsaSecretName);
return ERROR_INVALID_SERVICE_ACCOUNT;
}
*Separator = 0;
//
// Get the service token
//
if (!LogonUserEx(Separator + 1, // Username
AccountName, // Domain
LsaSecretName, // Password
LOGON32_LOGON_SERVICE, // Logon type
LOGON32_PROVIDER_DEFAULT, // Default logon provider
ServiceToken, // Pointer to token handle
ServiceSid, // Logon Sid
NULL, // Profile buffer
NULL, // Length of profile buffer
NULL)) // Quota limits
{
status = GetLastError();
*Separator = SCDOMAIN_USERNAME_SEPARATOR;
SC_LOG2(ERROR,
"ScLogonService: LogonUser for %ws service failed %d\n",
ServiceName,
status);
ScLogEvent(NEVENT_FIRST_LOGON_FAILED_II,
ServiceName,
AccountName,
status);
goto Cleanup;
}
if (ARGUMENT_PRESENT(pProfileHandle))
{
//
// Load the user profile for the service
// (Errors are written to the event log, but otherwise ignored)
//
ScLoadUserProfile(*ServiceToken,
AccountName, // Domain
Separator + 1, // Username
pProfileHandle);
}
LocalFree(LsaSecretName);
*Separator = SCDOMAIN_USERNAME_SEPARATOR;
return NO_ERROR;
Cleanup:
LocalFree(LsaSecretName);
*Separator = SCDOMAIN_USERNAME_SEPARATOR;
LocalFree(*ServiceSid);
*ServiceSid = NULL;
if (*ServiceToken != NULL)
{
CloseHandle(*ServiceToken);
*ServiceToken = NULL;
}
return ERROR_SERVICE_LOGON_FAILED;
}
DWORD
ScGetAccountDomainInfo(
VOID
)
{
NTSTATUS ntstatus;
LSA_HANDLE PolicyHandle;
PPOLICY_ACCOUNT_DOMAIN_INFO AccountDomainInfo;
NT_PRODUCT_TYPE ProductType;
//
// Account domain info is cached. Look it up it this is the first
// time.
//
if (ScAccountDomain.Buffer == NULL) {
if (! RtlGetNtProductType(&ProductType)) {
SC_LOG1(ERROR, "ScGetAccountDomainInfo: RtlGetNtProductType failed "
FORMAT_DWORD "\n", GetLastError());
return ERROR_INVALID_SERVICE_ACCOUNT;
}
if (ProductType == NtProductLanManNt) {
ScLocalDomain = &ScAccountDomain;
}
else {
ScLocalDomain = &ScComputerName;
}
//
// Open a handle to the local security policy.
//
if (ScOpenPolicy(
POLICY_VIEW_LOCAL_INFORMATION,
&PolicyHandle
) != NO_ERROR) {
SC_LOG0(ERROR, "ScGetAccountDomainInfo: ScOpenPolicy failed\n");
return ERROR_INVALID_SERVICE_ACCOUNT;
}
//
// Get the name of the account domain from LSA if we have
// not done it already.
//
ntstatus = LsaQueryInformationPolicy(
PolicyHandle,
PolicyAccountDomainInformation,
(PVOID *) &AccountDomainInfo
);
if (! NT_SUCCESS(ntstatus)) {
SC_LOG1(ERROR, "ScGetAccountDomainInfo: LsaQueryInformationPolicy failed "
FORMAT_NTSTATUS "\n", ntstatus);
(void) LsaClose(PolicyHandle);
return ERROR_INVALID_SERVICE_ACCOUNT;
}
(void) LsaClose(PolicyHandle);
if ((ScAccountDomain.Buffer = (LPWSTR)LocalAlloc(
LMEM_ZEROINIT,
(UINT) (AccountDomainInfo->DomainName.Length +
sizeof(WCHAR))
)) == NULL) {
(void) LsaFreeMemory((PVOID) AccountDomainInfo);
return ERROR_NOT_ENOUGH_MEMORY;
}
ScAccountDomain.MaximumLength = (USHORT) (AccountDomainInfo->DomainName.Length +
sizeof(WCHAR));
RtlCopyUnicodeString(&ScAccountDomain, &AccountDomainInfo->DomainName);
SC_LOG1(ACCOUNT, "ScGetAccountDomainInfo got " FORMAT_LPWSTR "\n",
ScAccountDomain.Buffer);
(void) LsaFreeMemory((PVOID) AccountDomainInfo);
}
return NO_ERROR;
}
VOID
ScLoadUserProfile(
IN HANDLE LogonToken,
IN LPWSTR DomainName,
IN LPWSTR UserName,
OUT PHANDLE pProfileHandle
)
/*++
Routine Description:
This function loads the user profile for the account that a service
process will run under, so that the process has an HKEY_CURRENT_USER.
Arguments:
LogonToken - The token handle returned by LogonUser.
UserName - The account's user name. (Used by LoadUserProfile to
generate a profile directory name.)
pProfileHandle - A handle to the profile is returned here. It must
be closed by calling UnloadUserProfile after the service process
exits.
Return Value:
None. Errors from LoadUserProfile are written to the event log.
--*/
{
PROFILEINFO ProfileInfo =
{
sizeof(ProfileInfo), // dwSize
PI_NOUI, // dwFlags - no UI
UserName, // lpUserName (used for dir name)
NULL, // lpProfilePath
NULL, // lpDefaultPath
NULL, // lpServerName (used to get group info - N/A)
NULL, // lpPolicyPath
NULL // hProfile (filled in by LoadUserProfile)
};
SC_ASSERT(pProfileHandle != NULL);
if (_wcsicmp(DomainName, SC_LOCAL_NTAUTH_NAME) == 0)
{
//
// Hide LocalService/NetworkService profiles from the Admin
// (i.e., they won't show up via "dir", etc).
//
ProfileInfo.dwFlags |= PI_HIDEPROFILE;
}
//
// NOTE: This ignores a service with a roaming profile. May need
// to use the profile path from the LogonUserEx call.
//
if (LoadUserProfile(LogonToken, &ProfileInfo))
{
SC_ASSERT(ProfileInfo.hProfile != NULL);
*pProfileHandle = ProfileInfo.hProfile;
}
else
{
DWORD Error = GetLastError();
SC_LOG(ERROR, "LoadUserProfile failed %lu\n", Error);
ScLogEvent(
NEVENT_CALL_TO_FUNCTION_FAILED,
SC_LOAD_USER_PROFILE,
Error);
*pProfileHandle = NULL;
}
}
DWORD
ScUPNToAccountName(
IN LPWSTR lpUPN,
OUT LPWSTR *ppAccountName
)
/*++
Routine Description:
This function attempts to convert a UPN into Domain\User
Arguments:
lpUPN - The UPN
ppAccountName - Pointer to the location to create/copy the account name
Return Value:
NO_ERROR -- Success (ppAccountName contains the converted UPN)
Any other Win32 error -- error at some stage of conversion
--*/
{
DWORD dwError;
HANDLE hDS;
PDS_NAME_RESULT pdsResult;
SC_ASSERT(ppAccountName != NULL);
SC_LOG1(ACCOUNT, "ScUPNToAccountName: Converting %ws\n", lpUPN);
//
// Get a binding handle to the DS
//
dwError = DsBind(NULL, NULL, &hDS);
if (dwError != NO_ERROR)
{
SC_LOG1(ERROR, "ScUPNToAccountName: DsBind failed %d\n", dwError);
return dwError;
}
dwError = DsCrackNames(hDS, // Handle to the DS
DS_NAME_NO_FLAGS, // No parsing flags
DS_USER_PRINCIPAL_NAME, // We have a UPN
DS_NT4_ACCOUNT_NAME, // We want Domain\User
1, // Number of names to crack
&lpUPN, // Array of name(s)
&pdsResult); // Filled in by API
if (dwError != NO_ERROR)
{
SC_LOG1(ERROR,
"ScUPNToAccountName: DsCrackNames failed %d\n",
dwError);
DsUnBind(&hDS);
return dwError;
}
SC_ASSERT(pdsResult->cItems == 1);
SC_ASSERT(pdsResult->rItems != NULL);
if (pdsResult->rItems[0].status == DS_NAME_ERROR_DOMAIN_ONLY)
{
//
// Couldn't crack the name but we got the name of
// the domain where it is -- let's try it
//
DsUnBind(&hDS);
SC_ASSERT(pdsResult->rItems[0].pDomain != NULL);
SC_LOG1(ACCOUNT,
"Retrying DsBind on domain %ws\n",
pdsResult->rItems[0].pDomain);
dwError = DsBind(NULL, pdsResult->rItems[0].pDomain, &hDS);
//
// Free up the structure holding the old info
//
DsFreeNameResult(pdsResult);
if (dwError != NO_ERROR)
{
SC_LOG1(ERROR,
"ScUPNToAccountName: DsBind #2 failed %d\n",
dwError);
return dwError;
}
dwError = DsCrackNames(hDS, // Handle to the DS
DS_NAME_NO_FLAGS, // No parsing flags
DS_USER_PRINCIPAL_NAME, // We have a UPN
DS_NT4_ACCOUNT_NAME, // We want Domain\User
1, // Number of names to crack
&lpUPN, // Array of name(s)
&pdsResult); // Filled in by API
if (dwError != NO_ERROR)
{
SC_LOG1(ERROR,
"ScUPNToAccountName: DsCrackNames #2 failed %d\n",
dwError);
DsUnBind(&hDS);
return dwError;
}
SC_ASSERT(pdsResult->cItems == 1);
SC_ASSERT(pdsResult->rItems != NULL);
}
if (pdsResult->rItems[0].status != DS_NAME_NO_ERROR)
{
SC_LOG1(ERROR,
"ScUPNToAccountName: DsCrackNames failure (status %#x)\n",
pdsResult->rItems[0].status);
//
// DS errors don't map to Win32 errors -- this is the best we can do
//
dwError = ERROR_INVALID_SERVICE_ACCOUNT;
}
else
{
*ppAccountName = (LPWSTR)LocalAlloc(
LPTR,
(wcslen(pdsResult->rItems[0].pName) + 1) * sizeof(WCHAR));
if (*ppAccountName != NULL)
{
wcscpy(*ppAccountName, pdsResult->rItems[0].pName);
}
else
{
dwError = GetLastError();
SC_LOG1(ERROR, "ScUPNToAccountName: LocalAlloc failed %d\n", dwError);
}
}
DsUnBind(&hDS);
DsFreeNameResult(pdsResult);
return dwError;
}