/*++ Copyright (c) 1992 Microsoft Corporation Module Name: LockAPI.cxx Abstract: This file contains the Service Controller's lock APIs: RLockServiceDatabase RQueryServiceLockStatusW RUnlockServiceDatabase SC_RPC_LOCK_rundown Author: John Rogers (JohnRo) 14-Apr-1992 Environment: User Mode - Win32 Revision History: 26-Mar-1992 danl Created the stubbed out version for RPC. 17-Apr-1992 JohnRo Split lock APIs out from config API stubs in CfgAPI.c. Did initial coding of all lock APIs. 22-Apr-1992 JohnRo Made changes suggested by PC-LINT. Use SC_LOG0(), etc. 06-Aug-1992 ritaw Completed the code. --*/ // // INCLUDES // #include "precomp.hxx" extern "C" { #include // LsaLookupSids } #include // wide character c runtimes. #include // Unicode string macros #include // ScCopyStringToBufferW(). #include // time(). #include "account.h" // SCDOMAIN_USERNAME_SEPARATOR #include "lockapi.h" // ScLockDatabase #include "scsec.h" // ScGetClientSid #define SC_MANAGER_USERNAME L".\\NT Service Control Manager" #define ScDatabaseNamesMatch(a,b) (_wcsicmp( (a), (b) ) == 0) // Macros to lock and unlock the lock list: #define LOCK_API_LOCK_LIST_SHARED( comment ) \ { \ ScServiceRecordLock.GetShared(); \ } #define LOCK_API_LOCK_LIST_EXCLUSIVE( comment ) \ { \ ScServiceRecordLock.GetExclusive(); \ } #define UNLOCK_API_LOCK_LIST( comment ) \ { \ ScServiceRecordLock.Release(); \ } typedef struct _API_LOCK { struct _API_LOCK *Prev; struct _API_LOCK *Next; DWORD Signature; // Must be API_LOCK_SIGNATURE. LPWSTR DatabaseName; time_t TimeWhenLocked; // seconds since 1970. PSID LockOwnerSid; // SID. It is NULL if SC // Manager grabbed the lock } API_LOCK, *PAPI_LOCK, *LPAPI_LOCK; #define API_LOCK_SIGNATURE 0x4C697041 // "ApiL" in ASCII. // // List of API_LOCK structures. This list is locked by the macros above. // LPAPI_LOCK ScGlobalApiLockList = NULL; DWORD ScCreateLock( IN BOOL IsServiceController, IN LPWSTR DatabaseName, IN PSID UserSid OPTIONAL, OUT LPSC_RPC_LOCK lpLock ); #if DBG VOID ScDumpLockList( VOID ); #endif LPAPI_LOCK ScFindApiLockForDatabase( IN LPWSTR DatabaseName ) /*++ Routine Description: Arguments: Return Value: Pointer to entry in the list (or NULL if not found). Note: The caller must have a lock (shared or exclusive) for the api lock list. --*/ { LPAPI_LOCK apiLockEntry; apiLockEntry = ScGlobalApiLockList; while (apiLockEntry != NULL) { SC_ASSERT( apiLockEntry->Signature == API_LOCK_SIGNATURE ); if (ScDatabaseNamesMatch( DatabaseName, apiLockEntry->DatabaseName) ) { return (apiLockEntry); } apiLockEntry = apiLockEntry->Next; } return (NULL); } DWORD RLockServiceDatabase( IN SC_RPC_HANDLE hSCManager, OUT LPSC_RPC_LOCK lpLock ) /*++ Routine Description: Arguments: Return Value: --*/ { DWORD status; LPSC_HANDLE_STRUCT serviceHandleStruct = (LPSC_HANDLE_STRUCT) hSCManager; SC_ASSERT( lpLock != NULL ); *lpLock = NULL; if ( !ScIsValidScManagerHandle( hSCManager ) ) { return (ERROR_INVALID_HANDLE); } // // Do we have permission to do this? // if ( !RtlAreAllAccessesGranted( serviceHandleStruct->AccessGranted, SC_MANAGER_LOCK )) { return (ERROR_ACCESS_DENIED); } status = ScLockDatabase( FALSE, serviceHandleStruct->Type.ScManagerObject.DatabaseName, lpLock ); SC_LOG0( LOCK_API, "Database Lock is ON (from API)\n"); return status; } DWORD RQueryServiceLockStatusW( IN SC_RPC_HANDLE hSCManager, OUT LPQUERY_SERVICE_LOCK_STATUSW lpLockStatus, IN DWORD cbBufSize, OUT LPDWORD pcbBytesNeeded ) /*++ Routine Description: Arguments: Return Value: --*/ { DWORD status; LPAPI_LOCK apiLockEntry; DWORD allocSize; LPWSTR databaseName; LPWSTR endOfVariableData; LPWSTR fixedDataEnd; LPWSTR lockOwner; DWORD lockOwnerSize; LPSC_HANDLE_STRUCT serviceHandleStruct = (LPSC_HANDLE_STRUCT) hSCManager; if ( !ScIsValidScManagerHandle( hSCManager ) ) { return (ERROR_INVALID_HANDLE); } else if (lpLockStatus == NULL) { return (ERROR_INVALID_PARAMETER); } else if (pcbBytesNeeded == NULL) { return (ERROR_INVALID_PARAMETER); } // // Do we have permission to do this? // if ( !RtlAreAllAccessesGranted( serviceHandleStruct->AccessGranted, SC_MANAGER_QUERY_LOCK_STATUS )) { return (ERROR_ACCESS_DENIED); } LOCK_API_LOCK_LIST_SHARED( "RQueryServiceLockStatusW start" ); databaseName = serviceHandleStruct->Type.ScManagerObject.DatabaseName; SC_ASSERT( databaseName != NULL ); apiLockEntry = ScFindApiLockForDatabase( databaseName ); if (apiLockEntry == NULL) { allocSize = sizeof(QUERY_SERVICE_LOCK_STATUSW) + sizeof(WCHAR); *pcbBytesNeeded = allocSize; if (cbBufSize < allocSize) { UNLOCK_API_LOCK_LIST( "RQueryServiceLockStatusW too small" ); return (ERROR_INSUFFICIENT_BUFFER); } lpLockStatus->fIsLocked = FALSE; fixedDataEnd = (LPWSTR) (lpLockStatus + 1); endOfVariableData = (LPWSTR) ((LPBYTE)lpLockStatus + allocSize); if (! ScCopyStringToBufferW ( NULL, 0, fixedDataEnd, &endOfVariableData, &lpLockStatus->lpLockOwner, NULL )) { SC_ASSERT( FALSE ); } lpLockStatus->dwLockDuration = 0; UNLOCK_API_LOCK_LIST( "RQueryServiceLockStatusW not found" ); return (NO_ERROR); } SC_ASSERT( apiLockEntry->Signature == API_LOCK_SIGNATURE ); status = ScGetLockOwner( apiLockEntry->LockOwnerSid, &lockOwner ); if (status != NO_ERROR) { UNLOCK_API_LOCK_LIST( "RQueryServiceLockStatusW failed get owner" ); return status; } lockOwnerSize = (DWORD) WCSSIZE(lockOwner); SC_ASSERT( lockOwnerSize > 2 ); // min is ".\x" (domain\user). allocSize = sizeof(QUERY_SERVICE_LOCK_STATUSW) + lockOwnerSize; *pcbBytesNeeded = allocSize; if (allocSize > cbBufSize) { LocalFree(lockOwner); UNLOCK_API_LOCK_LIST( "RQueryServiceLockStatusW too small" ); return (ERROR_INSUFFICIENT_BUFFER); } // // Build the QUERY_SERVICE_LOCK_STATUS structure. // lpLockStatus->fIsLocked = TRUE; lpLockStatus->dwLockDuration = (DWORD)(time(NULL) - apiLockEntry->TimeWhenLocked); fixedDataEnd = (LPWSTR) (lpLockStatus + 1); endOfVariableData = (LPWSTR) ((LPBYTE)lpLockStatus + allocSize); if (! ScCopyStringToBufferW ( lockOwner, (DWORD) wcslen(lockOwner), fixedDataEnd, &endOfVariableData, &lpLockStatus->lpLockOwner, NULL )) { SC_ASSERT( FALSE ); } LocalFree(lockOwner); UNLOCK_API_LOCK_LIST( "RQueryServiceLockStatusW done" ); return (NO_ERROR); } DWORD RUnlockServiceDatabase( IN OUT LPSC_RPC_LOCK lpLock ) /*++ Routine Description: Arguments: Return Value: --*/ { LPAPI_LOCK apiLockEntry; if (lpLock == NULL) { return (ERROR_INVALID_SERVICE_LOCK); } apiLockEntry = * (LPAPI_LOCK *) (LPVOID) lpLock; if (apiLockEntry->Signature != API_LOCK_SIGNATURE) { SC_LOG1( ERROR, "RUnlockServiceDatabase: lock w/o signature at " FORMAT_LPVOID "\n", (LPVOID) lpLock ); return (ERROR_INVALID_SERVICE_LOCK); } // // We're going to update the linked list, so keep other threads out. // LOCK_API_LOCK_LIST_EXCLUSIVE( "RUnlockServiceDatabase start" ); // // Remove the entry from the lock list. This has the effect of // unlocking this database. // if (apiLockEntry->Prev != NULL) { apiLockEntry->Prev->Next = apiLockEntry->Next; } if (apiLockEntry->Next != NULL) { apiLockEntry->Next->Prev = apiLockEntry->Prev; } if ( (apiLockEntry->Next == NULL) && (apiLockEntry->Prev == NULL) ) { ScGlobalApiLockList = NULL; } // // Free the storage we allocated for this entry. // LocalFree( apiLockEntry ); *lpLock = NULL; #if DBG ScDumpLockList(); #endif // // OK, it's safe for other threads to muck with the lock list. // UNLOCK_API_LOCK_LIST( "RUnlockServiceDatabase done" ); SC_LOG0( LOCK_API,"Database Lock is OFF (from API)\n"); return(NO_ERROR); } VOID SC_RPC_LOCK_rundown( SC_RPC_LOCK lock ) /*++ Routine Description: This function is called by RPC when a connection is broken that had an outstanding context handle. The value of the context handle is passed in here so that we have an opportunity to clean up. Arguments: lock - This is the handle value of the context handle that is broken. Return Value: none. --*/ { RUnlockServiceDatabase(&lock); } VOID ScUnlockDatabase( IN OUT LPSC_RPC_LOCK lpLock ) /*++ Routine Description: This function is called by internally by ScStartServiceAndDependencies to unlock the SC Manager database lock when it is done starting services. Arguments: lpLock - Supplies the address of the pointer to the lock structure. On output, the pointer is set to NULL. Return Value: None. --*/ { RUnlockServiceDatabase(lpLock); } DWORD ScLockDatabase( IN BOOL IsServiceController, IN LPWSTR DatabaseName, OUT LPSC_RPC_LOCK lpLock ) /*++ Routine Description: This function grabs the external database lock which is used by setup programs to ensure serialization to the services' configuration. It is also called by the service controller itself from ScStartServiceAndDependencies. We need to grab the database lock internally when starting services so that setup programs know that when is an unsafe time to modify service configuration. When called by the service controller itself, the SID is not looked up. Arguments: IsServiceController - Supplies a flag which is TRUE if this routine is called by the service controller; FALSE all other times. DatabaseName - Supplies the name of the database which the lock is to be acquired. lpLock - Receives a pointer to the lock entry created. Return Value: NO_ERROR or reason for failure. --*/ { DWORD status; LPAPI_LOCK apiLockEntry; PTOKEN_USER UserInfo = NULL; SC_ASSERT(DatabaseName != NULL); LOCK_API_LOCK_LIST_EXCLUSIVE( "ScLockDatabase start" ); // // Check for another lock. // apiLockEntry = ScFindApiLockForDatabase(DatabaseName); if (apiLockEntry != NULL) { UNLOCK_API_LOCK_LIST( "ScLockDatabase already locked" ); SC_LOG0(LOCK_API, "ScLockDatabase: Database is already locked\n"); return ERROR_SERVICE_DATABASE_LOCKED; } if (! IsServiceController) { // // Get the caller's SID // if ((status = ScGetClientSid( &UserInfo )) != NO_ERROR) { UNLOCK_API_LOCK_LIST( "ScLockDatabase ScGetClientSid failed" ); return status; } status = ScCreateLock( FALSE, // Non-ScManager caller to grab lock DatabaseName, UserInfo->User.Sid, lpLock ); LocalFree(UserInfo); } else { status = ScCreateLock( TRUE, // ScManager caller to grab lock DatabaseName, NULL, lpLock ); } #if DBG ScDumpLockList(); #endif UNLOCK_API_LOCK_LIST("ScLockDatabase done"); return status; } DWORD ScCreateLock( IN BOOL IsServiceController, IN LPWSTR DatabaseName, IN PSID UserSid OPTIONAL, OUT LPSC_RPC_LOCK lpLock ) /*++ Routine Description: This function is creates a lock entry, fills in the information about the nature of the lock and insert it into the lock list. Arguments: IsServiceController - Supplies a flag which is TRUE if this routine is called by the service controller; FALSE all other times. DatabaseName - Supplies the name of the database which the lock is to be acquired. UserSid - Supplies the SID of the caller to claim the lock. This is NULL if IsServiceController is TRUE. lpLock - Receives a pointer to the lock entry created. Return Value: NO_ERROR or reason for failure. --*/ { NTSTATUS ntstatus; DWORD allocSize; LPAPI_LOCK newLockEntry; LPAPI_LOCK apiLockEntry; // // Build a structure to describe this lock. // if (IsServiceController) { allocSize = sizeof(API_LOCK) + (DWORD) WCSSIZE(DatabaseName); } else { if (! ARGUMENT_PRESENT(UserSid)) { SC_LOG0(ERROR, "ScCreateLock: UserSid is NULL!\n"); SC_ASSERT(FALSE); return ERROR_GEN_FAILURE; } allocSize = sizeof(API_LOCK) + (DWORD) WCSSIZE(DatabaseName) + RtlLengthSid(UserSid); } newLockEntry = (LPAPI_LOCK) LocalAlloc( LMEM_ZEROINIT, (UINT) allocSize ); if (newLockEntry == NULL) { SC_LOG1(ERROR,"ScCreateLock: Local Alloc FAILED " FORMAT_DWORD "\n", GetLastError()); return (ERROR_NOT_ENOUGH_MEMORY); } SC_LOG3(LOCK_API,"ScCreateLock: alloc'ed " FORMAT_DWORD " bytes at " FORMAT_LPVOID " Sid-size " FORMAT_DWORD ".\n", allocSize, (LPVOID) newLockEntry, (UserSid) ? RtlLengthSid(UserSid) : 0); // // Fill in fields of new lock entry // newLockEntry->Signature = API_LOCK_SIGNATURE; newLockEntry->DatabaseName = (LPWSTR) (newLockEntry + 1); wcscpy(newLockEntry->DatabaseName, DatabaseName); if (ARGUMENT_PRESENT(UserSid)) { newLockEntry->LockOwnerSid = (PSID) ((DWORD_PTR) newLockEntry->DatabaseName + WCSSIZE(DatabaseName)); SC_LOG1(LOCK_API, "ScCreateLock: Before RtlCopySid, bytes left " FORMAT_DWORD "\n", ((DWORD_PTR) newLockEntry + allocSize) - (DWORD_PTR) newLockEntry->LockOwnerSid); ntstatus = RtlCopySid( (ULONG)(((DWORD_PTR) newLockEntry + allocSize) - (DWORD_PTR) newLockEntry->LockOwnerSid), newLockEntry->LockOwnerSid, UserSid ); if (! NT_SUCCESS(ntstatus)) { SC_LOG1(ERROR, "ScCreateLock: RtlCopySid failed " FORMAT_NTSTATUS "\n", ntstatus); LocalFree(newLockEntry); return RtlNtStatusToDosError(ntstatus); } } else { newLockEntry->LockOwnerSid = (PSID) NULL; } newLockEntry->TimeWhenLocked = (DWORD) time( NULL ); // // Record this lock. // if (ScGlobalApiLockList != NULL) { // // List is not empty, so just add to end. // apiLockEntry = ScGlobalApiLockList; ADD_TO_LIST( apiLockEntry, newLockEntry ); } else { // // List is empty, so start with this (new) entry. // ScGlobalApiLockList = newLockEntry; newLockEntry->Next = NULL; newLockEntry->Prev = NULL; } *lpLock = newLockEntry; return NO_ERROR; } DWORD ScGetLockOwner( IN PSID UserSid OPTIONAL, OUT LPWSTR *LockOwnerName ) { DWORD status = NO_ERROR; NTSTATUS ntstatus; OBJECT_ATTRIBUTES ObjAttributes; LSA_HANDLE PolicyHandle; PLSA_REFERENCED_DOMAIN_LIST ReferencedDomain = NULL; PLSA_TRANSLATED_NAME Name = NULL; NT_PRODUCT_TYPE ProductType; if (! ARGUMENT_PRESENT(UserSid)) { *LockOwnerName = (LPWSTR)LocalAlloc( LMEM_ZEROINIT, WCSSIZE(SC_MANAGER_USERNAME) ); if (*LockOwnerName == NULL) { SC_LOG1(ERROR, "ScGetLockOwner: LocalAlloc failed " FORMAT_DWORD "\n", GetLastError()); return ERROR_NOT_ENOUGH_MEMORY; } wcscpy(*LockOwnerName, SC_MANAGER_USERNAME); return NO_ERROR; } // // Open a handle to the local security policy. Initialize the // objects attributes structure first. // InitializeObjectAttributes( &ObjAttributes, NULL, 0L, NULL, NULL ); ntstatus = LsaOpenPolicy( NULL, &ObjAttributes, POLICY_LOOKUP_NAMES, &PolicyHandle ); if (! NT_SUCCESS(ntstatus)) { SC_LOG(ERROR, "ScGetLockOwner: LsaOpenPolicy returned " FORMAT_NTSTATUS "\n", ntstatus); return RtlNtStatusToDosError(ntstatus); } // // Get the name of the specified SID // ntstatus = LsaLookupSids( PolicyHandle, 1, &UserSid, &ReferencedDomain, &Name ); if (! NT_SUCCESS(ntstatus)) { SC_LOG(ERROR, "ScGetLockOwner: LsaLookupNames returned " FORMAT_NTSTATUS "\n", ntstatus); status = RtlNtStatusToDosError(ntstatus); goto CleanExit; } if (ReferencedDomain == NULL || Name == NULL) { SC_LOG2(ERROR, "ScGetLockOwner: ReferencedDomain=%08lx, Name=%08lx\n", ReferencedDomain, Name); status = ERROR_GEN_FAILURE; goto CleanExit; } else { LPWSTR Ptr; if (Name->Use == SidTypeUnknown || Name->Use == SidTypeInvalid) { SC_LOG0(ERROR, "ScGetLockOwner: Sid is unknown or invalid\n"); status = ERROR_GEN_FAILURE; goto CleanExit; } if (Name->DomainIndex < 0) { SC_LOG1(ERROR, "ScGetLockOwner: DomainIndex is negative %ld\n", Name->DomainIndex); status = ERROR_GEN_FAILURE; goto CleanExit; } if (ReferencedDomain->Entries == 0) { SC_LOG0(ERROR, "ScGetLockOwner: No ReferencedDomain entry\n"); status = ERROR_GEN_FAILURE; goto CleanExit; } *LockOwnerName = (LPWSTR)LocalAlloc( LMEM_ZEROINIT, Name->Name.Length + ReferencedDomain->Domains[Name->DomainIndex].Name.Length + 2 * sizeof(WCHAR) ); if (*LockOwnerName == NULL) { SC_LOG1(ERROR, "ScGetLockOwner: LocalAlloc failed " FORMAT_DWORD "\n", GetLastError()); status = ERROR_NOT_ENOUGH_MEMORY; goto CleanExit; } if (! RtlGetNtProductType(&ProductType)) { status = GetLastError(); SC_LOG1(ERROR, "ScGetLockOwner: RtlGetNtProductType failed " FORMAT_DWORD "\n", status); LocalFree(*LockOwnerName); goto CleanExit; } if (ProductType != NtProductLanManNt) { status = ScGetAccountDomainInfo(); if (status != NO_ERROR) { LocalFree(*LockOwnerName); goto CleanExit; } if (RtlEqualUnicodeString( &(ReferencedDomain->Domains[Name->DomainIndex].Name), &ScAccountDomain, TRUE ) || RtlEqualUnicodeString( &(ReferencedDomain->Domains[Name->DomainIndex].Name), &ScComputerName, TRUE ) ) { // // We are WinNT and the user who has the lock is logged on to // a local account. Convert the local domain name to "." // wcscpy(*LockOwnerName, SC_LOCAL_DOMAIN_NAME); } else { goto ReturnRefDomain; } } else { ReturnRefDomain: memcpy( *LockOwnerName, ReferencedDomain->Domains[Name->DomainIndex].Name.Buffer, ReferencedDomain->Domains[Name->DomainIndex].Name.Length ); } Ptr = *LockOwnerName + wcslen(*LockOwnerName); *Ptr = SCDOMAIN_USERNAME_SEPARATOR; Ptr++; memcpy( Ptr, Name->Name.Buffer, Name->Name.Length ); } CleanExit: if (ReferencedDomain != NULL) { LsaFreeMemory(ReferencedDomain); } if (Name != NULL) { LsaFreeMemory(Name); } LsaClose(PolicyHandle); return status; } #if DBG VOID ScDumpLockList( VOID ) { LPAPI_LOCK LockEntry = ScGlobalApiLockList; LPWSTR LockOwner; if (LockEntry == NULL) { KdPrintEx((DPFLTR_SCSERVER_ID, DEBUG_LOCK_API, "\nLock list is NULL\n")); return; } KdPrintEx((DPFLTR_SCSERVER_ID, DEBUG_LOCK_API, "\nScDumpLockList:\n")); while (LockEntry != NULL) { if (ScGetLockOwner(LockEntry->LockOwnerSid, &LockOwner) == NO_ERROR) { KdPrintEx((DPFLTR_SCSERVER_ID, DEBUG_LOCK_API, "LockOwner: " FORMAT_LPWSTR "\n", LockOwner)); KdPrintEx((DPFLTR_SCSERVER_ID, DEBUG_LOCK_API, "LockDuration: " FORMAT_DWORD "\n", ((DWORD)time(NULL)) - LockEntry->TimeWhenLocked)); KdPrintEx((DPFLTR_SCSERVER_ID, DEBUG_LOCK_API, "LockDatabase: " FORMAT_LPWSTR "\n", LockEntry->DatabaseName)); LocalFree(LockOwner); } LockEntry = LockEntry->Next; } } #endif