|
|
/*++
Copyright (c) 1992 Microsoft Corporation
Module Name:
CfgAPI2.cxx
Abstract:
This file contains the Service Controller's extended Config API. RChangeServiceConfig2W RQueryServiceConfig2W COutBuf CUpdateOptionalString::Update CUpdateOptionalString::~CUpdateOptionalString PrintConfig2Parms
Author:
Anirudh Sahni (AnirudhS) 11-Oct-96
Environment:
User Mode - Win32
Revision History:
11-Oct-1996 AnirudhS Created.
--*/
//
// INCLUDES
//
#include "precomp.hxx"
#include <tstr.h> // Unicode string macros
#include <align.h> // COUNT_IS_ALIGNED
#include <valid.h> // ACTION_TYPE_INVALID
#include <sclib.h> // ScImagePathsMatch
#include <scwow.h> // 32/64-bit interop structures
#include "scconfig.h" // ScOpenServiceConfigKey, etc.
#include "scsec.h" // ScPrivilegeCheckAndAudit
#include "smartp.h" // CHeapPtr
#if DBG == 1
VOID PrintConfig2Parms( IN SC_RPC_HANDLE hService, IN SC_RPC_CONFIG_INFOW Info ); #endif
#define FLDREF(s,f) (s.f)
//
// Class definitions
//
//+-------------------------------------------------------------------------
//
// Class: COutBuf
//
// Purpose: Abstraction of an output buffer that is written sequentially
//
// History: 22-Nov-96 AnirudhS Created.
//
//--------------------------------------------------------------------------
class COutBuf { public: COutBuf(LPBYTE lpBuffer) : _Start(lpBuffer), _Used(0) { }
LPBYTE Next() const { return (_Start + _Used); } DWORD OffsetNext() const { return _Used; } void AddUsed(DWORD Bytes) { _Used += Bytes; }
void AppendBytes(void * Source, DWORD Bytes) { RtlCopyMemory(Next(), Source, Bytes); AddUsed(Bytes); } private: LPBYTE _Start; DWORD _Used; };
//+-------------------------------------------------------------------------
//
// Class: CUpdateOptionalString
//
// Purpose: An object of this class represents an update of an optional
// string value in the registry. The update takes place when
// the Update() method is called. When the object is destroyed
// the operation is undone, unless the Commit() method has been
// called.
//
// This class simplifies the writing of APIs like
// ChangeServiceConfig2.
//
// History: 27-Nov-96 AnirudhS Created.
//
//--------------------------------------------------------------------------
class CUpdateOptionalString { public: CUpdateOptionalString (HKEY Key, LPCWSTR ValueName) : _Key(Key), _ValueName(ValueName), _UndoNeeded(FALSE) { } ~CUpdateOptionalString(); DWORD Update (LPCWSTR NewValue); void Commit () { _UndoNeeded = FALSE; }
private: HKEY _Key; LPCWSTR _ValueName; CHeapPtr< LPWSTR > _OldValue; BOOL _UndoNeeded; };
DWORD CUpdateOptionalString::Update( IN LPCWSTR NewValue ) /*++
Routine Description:
See class definition.
--*/ { // This method should be called only once in the object's lifetime
SC_ASSERT(_UndoNeeded == FALSE && _OldValue == NULL);
//
// Read the old value.
//
DWORD Error = ScReadOptionalString(_Key, _ValueName, &_OldValue); if (Error != ERROR_SUCCESS) { return Error; }
//
// Write the new value. Note that NULL means no change.
//
Error = ScWriteOptionalString(_Key, _ValueName, NewValue);
//
// Remember whether the change needs to be undone.
//
if (Error == ERROR_SUCCESS && NewValue != NULL) { _UndoNeeded = TRUE; }
return Error; }
CUpdateOptionalString::~CUpdateOptionalString( ) /*++
Routine Description:
See class definition.
--*/ { if (_UndoNeeded) { DWORD Error = ScWriteOptionalString( _Key, _ValueName, _OldValue ? _OldValue : L"" );
if (Error != ERROR_SUCCESS) { // Nothing we can do about it
SC_LOG3(ERROR, "Couldn't roll back update to %ws value, error %lu." " Old value was \"%ws\".\n", _ValueName, Error, _OldValue); } } }
DWORD RChangeServiceConfig2W( IN SC_RPC_HANDLE hService, IN SC_RPC_CONFIG_INFOW Info )
/*++
Routine Description:
Arguments:
Return Value:
--*/ { SC_LOG(CONFIG_API, "In RChangeServiceConfig2W for service handle %#lx\n", hService);
#if DBG == 1
PrintConfig2Parms(hService, Info); #endif // DBG == 1
if (ScShutdownInProgress) { return ERROR_SHUTDOWN_IN_PROGRESS; }
//
// Check the handle.
//
if (!ScIsValidServiceHandle(hService)) { return ERROR_INVALID_HANDLE; }
//
// Do we have permission to do this?
//
if (!RtlAreAllAccessesGranted( ((LPSC_HANDLE_STRUCT) hService)->AccessGranted, SERVICE_CHANGE_CONFIG )) { return ERROR_ACCESS_DENIED; }
//
// Lock database, as we want to add stuff without other threads tripping
// on our feet.
//
CServiceRecordExclusiveLock RLock;
//
// Find the service record for this handle.
//
LPSERVICE_RECORD serviceRecord = ((LPSC_HANDLE_STRUCT) hService)->Type.ScServiceObject.ServiceRecord; SC_ASSERT(serviceRecord != NULL); SC_ASSERT(serviceRecord->Signature == SERVICE_SIGNATURE);
//
// Disallow this call if record is marked for delete.
//
if (DELETE_FLAG_IS_SET(serviceRecord)) { return ERROR_SERVICE_MARKED_FOR_DELETE; }
//-----------------------------
//
// Begin Updating the Registry
//
//-----------------------------
HKEY ServiceNameKey = NULL; DWORD ApiStatus = ScOpenServiceConfigKey( serviceRecord->ServiceName, KEY_WRITE | KEY_READ, FALSE, // don't create if missing
&ServiceNameKey ); if (ApiStatus != NO_ERROR) { goto Cleanup; }
switch (FLDREF(Info,dwInfoLevel)) { //-----------------------------
//
// Service Description
//
//-----------------------------
case SERVICE_CONFIG_DESCRIPTION:
//
// NULL means no change
//
if (FLDREF(Info,psd) == NULL) { ApiStatus = NO_ERROR; break; }
ApiStatus = ScWriteDescription(ServiceNameKey, FLDREF(Info,psd->lpDescription)); break;
//-----------------------------
//
// Service Failure Actions
//
//-----------------------------
case SERVICE_CONFIG_FAILURE_ACTIONS: { LPSERVICE_FAILURE_ACTIONSW psfa = FLDREF(Info,psfa);
//
// NULL means no change
//
if (psfa == NULL) { ApiStatus = NO_ERROR; break; }
//
// Validate the structure and permissions
//
if (psfa->lpsaActions != NULL && psfa->cActions != 0) { //
// These settings are only valid for Win32 services
//
if (! (serviceRecord->ServiceStatus.dwServiceType & SERVICE_WIN32)) { ApiStatus = ERROR_CANNOT_DETECT_DRIVER_FAILURE; break; }
BOOL RebootRequested = FALSE; BOOL RestartRequested = FALSE; for (DWORD i = 0; i < psfa->cActions; i++) { SC_ACTION_TYPE Type = psfa->lpsaActions[i].Type; if (Type == SC_ACTION_RESTART) { RestartRequested = TRUE; } else if (Type == SC_ACTION_REBOOT) { RebootRequested = TRUE; } else if (ACTION_TYPE_INVALID(Type)) { SC_LOG(ERROR, "RChangeServiceConfig2W: invalid action type %#lx\n", Type); ApiStatus = ERROR_INVALID_PARAMETER; goto Cleanup; } }
if (RestartRequested) { if (!RtlAreAllAccessesGranted( ((LPSC_HANDLE_STRUCT) hService)->AccessGranted, SERVICE_START )) { SC_LOG0(ERROR, "Service handle lacks start access\n"); ApiStatus = ERROR_ACCESS_DENIED; break; } }
if (RebootRequested) { NTSTATUS Status = ScPrivilegeCheckAndAudit( SE_SHUTDOWN_PRIVILEGE, hService, SERVICE_CHANGE_CONFIG ); if (!NT_SUCCESS(Status)) { SC_LOG0(ERROR, "Caller lacks shutdown privilege\n"); ApiStatus = ERROR_ACCESS_DENIED; break; } }
//
// Get the service's image path
//
CHeapPtr<LPWSTR> ImageName; ApiStatus = ScGetImageFileName(serviceRecord->ServiceName, &ImageName); if (ApiStatus != NO_ERROR) { SC_LOG(ERROR,"RChangeServiceConfig2W: GetImageFileName failed %lu\n", ApiStatus); break; }
//
// If the service runs in services.exe, we certainly won't
// detect if the service process dies, so don't pretend we will
//
if (ScImagePathsMatch(ImageName, ScGlobalThisExePath)) { ApiStatus = ERROR_CANNOT_DETECT_PROCESS_ABORT; break; } }
//
// Write the string values, followed by the non-string values.
// If anything fails, the values written up to that point will be
// backed out.
// (Backing out the update of the non-string values is a little
// more complicated than backing out the string updates. So we
// do the non-string update last, to avoid having to back it out.)
//
CUpdateOptionalString UpdateRebootMessage (ServiceNameKey, REBOOTMESSAGE_VALUENAME_W); CUpdateOptionalString UpdateFailureCommand (ServiceNameKey, FAILURECOMMAND_VALUENAME_W);
if ((ApiStatus = UpdateRebootMessage.Update(psfa->lpRebootMsg)) == ERROR_SUCCESS && (ApiStatus = UpdateFailureCommand.Update(psfa->lpCommand)) == ERROR_SUCCESS && (ApiStatus = ScWriteFailureActions(ServiceNameKey, psfa)) == ERROR_SUCCESS) { UpdateRebootMessage.Commit(); UpdateFailureCommand.Commit(); } } break;
//-----------------------------
//
// Other (invalid)
//
//-----------------------------
default: ApiStatus = ERROR_INVALID_LEVEL; break; }
Cleanup:
if (ServiceNameKey != NULL) { ScRegFlushKey(ServiceNameKey); ScRegCloseKey(ServiceNameKey); }
SC_LOG1(CONFIG_API, "RChangeServiceConfig2W returning %lu\n", ApiStatus);
return ApiStatus; }
DWORD RQueryServiceConfig2W( IN SC_RPC_HANDLE hService, IN DWORD dwInfoLevel, OUT LPBYTE lpBuffer, IN DWORD cbBufSize, OUT LPDWORD pcbBytesNeeded )
/*++
Routine Description:
Arguments:
Return Value:
--*/ { SC_LOG(CONFIG_API, "In RQueryServiceConfig2W for service handle %#lx\n", hService);
if (ScShutdownInProgress) { return ERROR_SHUTDOWN_IN_PROGRESS; }
//
// Check the handle.
//
if (!ScIsValidServiceHandle(hService)) { return ERROR_INVALID_HANDLE; }
//
// MIDL doesn't support optional [out] parameters efficiently, so
// we require these parameters.
//
if (lpBuffer == NULL || pcbBytesNeeded == NULL) { SC_ASSERT(!"RPC passed NULL for [out] pointers"); return ERROR_INVALID_PARAMETER; }
//
// Do we have permission to do this?
//
if (!RtlAreAllAccessesGranted( ((LPSC_HANDLE_STRUCT) hService)->AccessGranted, SERVICE_QUERY_CONFIG )) { return ERROR_ACCESS_DENIED; }
//
// Initialize *pcbBytesNeeded. It is incremented below.
// (For consistency with QueryServiceConfig, it is set even on a success
// return.)
//
*pcbBytesNeeded = 0;
//
// Lock database, as we want to look at stuff without other threads changing
// fields at the same time.
//
CServiceRecordSharedLock RLock;
LPSERVICE_RECORD serviceRecord = ((LPSC_HANDLE_STRUCT) hService)->Type.ScServiceObject.ServiceRecord; SC_ASSERT(serviceRecord != NULL);
//
// Open the service name key.
//
HKEY ServiceNameKey = NULL; DWORD ApiStatus = ScOpenServiceConfigKey( serviceRecord->ServiceName, KEY_READ, FALSE, // Create if missing
&ServiceNameKey );
if (ApiStatus != NO_ERROR) { return ApiStatus; }
switch (dwInfoLevel) { //-----------------------------
//
// Service Description
//
//-----------------------------
case SERVICE_CONFIG_DESCRIPTION: { *pcbBytesNeeded = sizeof(SERVICE_DESCRIPTION_WOW64);
//
// Read the string from the registry
//
CHeapPtr< LPWSTR > pszDescription; ApiStatus = ScReadDescription( ServiceNameKey, &pszDescription, pcbBytesNeeded );
SC_ASSERT(ApiStatus != ERROR_INSUFFICIENT_BUFFER);
if (ApiStatus != NO_ERROR) { break; }
//
// Check for sufficient buffer space
//
if (cbBufSize < *pcbBytesNeeded) { ApiStatus = ERROR_INSUFFICIENT_BUFFER; break; }
//
// Copy the information to the output buffer
// The format is:
// SERVICE_DESCRIPTION_WOW64 struct
// description string
// Pointers in the struct are replaced with offsets based at the
// start of the buffer. NULL pointers remain NULL.
//
COutBuf OutBuf(lpBuffer);
LPSERVICE_DESCRIPTION_WOW64 psdOut = (LPSERVICE_DESCRIPTION_WOW64) OutBuf.Next(); OutBuf.AddUsed(sizeof(SERVICE_DESCRIPTION_WOW64)); SC_ASSERT(COUNT_IS_ALIGNED(OutBuf.OffsetNext(), sizeof(WCHAR)));
if (pszDescription != NULL) { psdOut->dwDescriptionOffset = OutBuf.OffsetNext(); OutBuf.AppendBytes(pszDescription, (DWORD) WCSSIZE(pszDescription)); } else { psdOut->dwDescriptionOffset = 0; } } break;
//-----------------------------
//
// Service Failure Actions
//
//-----------------------------
case SERVICE_CONFIG_FAILURE_ACTIONS: { //
// Read the non-string info
//
CHeapPtr< LPSERVICE_FAILURE_ACTIONS_WOW64 > psfa;
ApiStatus = ScReadFailureActions(ServiceNameKey, &psfa, pcbBytesNeeded); if (ApiStatus != NO_ERROR) { break; } if (psfa == NULL) { SC_ASSERT(*pcbBytesNeeded == 0); *pcbBytesNeeded = sizeof(SERVICE_FAILURE_ACTIONS_WOW64); } SC_ASSERT(COUNT_IS_ALIGNED(*pcbBytesNeeded, sizeof(WCHAR)));
//
// Read the string values
//
CHeapPtr< LPWSTR > RebootMessage;
ApiStatus = ScReadRebootMessage( ServiceNameKey, &RebootMessage, pcbBytesNeeded ); if (ApiStatus != NO_ERROR) { break; } SC_ASSERT(COUNT_IS_ALIGNED(*pcbBytesNeeded, sizeof(WCHAR)));
CHeapPtr< LPWSTR > FailureCommand;
ApiStatus = ScReadFailureCommand( ServiceNameKey, &FailureCommand, pcbBytesNeeded ); if (ApiStatus != NO_ERROR) { break; }
//
// Check for sufficient buffer space
//
if (cbBufSize < *pcbBytesNeeded) { ApiStatus = ERROR_INSUFFICIENT_BUFFER; break; }
//
// Copy the information to the output buffer
// The format is:
// SERVICE_FAILURE_ACTIONS_WOW64 struct
// SC_ACTIONS array
// strings
// Pointers in the struct are replaced with offsets based at the
// start of the buffer. NULL pointers remain NULL.
//
COutBuf OutBuf(lpBuffer);
LPSERVICE_FAILURE_ACTIONS_WOW64 psfaOut = (LPSERVICE_FAILURE_ACTIONS_WOW64) OutBuf.Next();
if (psfa != NULL) { psfaOut->dwResetPeriod = ((LPSERVICE_FAILURE_ACTIONS_WOW64) psfa)->dwResetPeriod; psfaOut->cActions = ((LPSERVICE_FAILURE_ACTIONS_WOW64) psfa)->cActions; } else { psfaOut->dwResetPeriod = 0; psfaOut->cActions = 0; } OutBuf.AddUsed(sizeof(SERVICE_FAILURE_ACTIONS_WOW64));
if (psfaOut->cActions != 0) { psfaOut->dwsaActionsOffset = OutBuf.OffsetNext();
OutBuf.AppendBytes(psfa + 1, psfaOut->cActions * sizeof(SC_ACTION)); } else { psfaOut->dwsaActionsOffset = 0; } SC_ASSERT(COUNT_IS_ALIGNED(OutBuf.OffsetNext(), sizeof(WCHAR)));
if (RebootMessage != NULL) { psfaOut->dwRebootMsgOffset = OutBuf.OffsetNext(); OutBuf.AppendBytes(RebootMessage, (DWORD) WCSSIZE(RebootMessage)); } else { psfaOut->dwRebootMsgOffset = 0; }
if (FailureCommand != NULL) { psfaOut->dwCommandOffset = OutBuf.OffsetNext(); OutBuf.AppendBytes(FailureCommand, (DWORD) WCSSIZE(FailureCommand)); } else { psfaOut->dwCommandOffset = 0; } } break;
//-----------------------------
//
// Other (invalid)
//
//-----------------------------
default: ApiStatus = ERROR_INVALID_LEVEL; break; }
ScRegCloseKey(ServiceNameKey);
SC_LOG1(CONFIG_API, "RQueryServiceConfig2W returning %lu\n", ApiStatus);
return ApiStatus; }
#if DBG == 1
VOID PrintConfig2Parms( IN SC_RPC_HANDLE hService, IN SC_RPC_CONFIG_INFOW Info ) { KdPrintEx((DPFLTR_SCSERVER_ID, DEBUG_CONFIG_API, "Parameters to RChangeServiceConfig2W:\n"));
LPSTR psz; switch (Info.dwInfoLevel) { case SERVICE_CONFIG_DESCRIPTION: psz = "SERVICE_CONFIG_DESCRIPTION"; break; case SERVICE_CONFIG_FAILURE_ACTIONS: psz = "SERVICE_CONFIG_FAILURE_ACTIONS"; break; default: psz = "invalid"; break; } KdPrintEx((DPFLTR_SCSERVER_ID, DEBUG_CONFIG_API, " dwInfoLevel = %ld (%s)\n", Info.dwInfoLevel, psz));
switch (Info.dwInfoLevel) { case SERVICE_CONFIG_DESCRIPTION:
if (Info.psd == NULL) { KdPrintEx((DPFLTR_SCSERVER_ID, DEBUG_CONFIG_API, " NULL information pointer -- no action requested\n\n"));
break; }
KdPrintEx((DPFLTR_SCSERVER_ID, DEBUG_CONFIG_API, " pszDescription = \"%ws\"\n", Info.psd->lpDescription));
break;
case SERVICE_CONFIG_FAILURE_ACTIONS: if (Info.psfa == NULL) { KdPrintEx((DPFLTR_SCSERVER_ID, DEBUG_CONFIG_API, " NULL information pointer -- no action requested\n\n"));
break; }
KdPrintEx((DPFLTR_SCSERVER_ID, DEBUG_CONFIG_API, " dwResetPeriod = %ld\n", Info.psfa->dwResetPeriod));
KdPrintEx((DPFLTR_SCSERVER_ID, DEBUG_CONFIG_API, " lpRebootMsg = \"%ws\"\n", Info.psfa->lpRebootMsg));
KdPrintEx((DPFLTR_SCSERVER_ID, DEBUG_CONFIG_API, " lpCommand = \"%ws\"\n", Info.psfa->lpCommand));
KdPrintEx((DPFLTR_SCSERVER_ID, DEBUG_CONFIG_API, " %ld failure %s\n", Info.psfa->cActions, Info.psfa->cActions == 0 ? "actions." : Info.psfa->cActions == 1 ? "action:" : "actions:"));
if (Info.psfa->lpsaActions == NULL) { KdPrintEx((DPFLTR_SCSERVER_ID, DEBUG_CONFIG_API, " NULL array pointer -- no change in fixed info\n\n")); } else { for (DWORD i = 0; i < Info.psfa->cActions; i++) { SC_ACTION& sa = Info.psfa->lpsaActions[i]; KdPrintEx((DPFLTR_SCSERVER_ID, DEBUG_CONFIG_API, " %ld: Action %ld, Delay %ld\n", i, sa.Type, sa.Delay)); } } break; }
KdPrintEx((DPFLTR_SCSERVER_ID, DEBUG_CONFIG_API, "\n")); }
#endif // DBG == 1
|