/*++

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

//
// 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 (Info.dwInfoLevel)
    {
    //-----------------------------
    //
    // Service Description
    //
    //-----------------------------
    case SERVICE_CONFIG_DESCRIPTION:

        //
        // NULL means no change
        //
        if (Info.psd == NULL)
        {
            ApiStatus = NO_ERROR;
            break;
        }

        ApiStatus = ScWriteDescription(ServiceNameKey, Info.psd->lpDescription);
        break;

    //-----------------------------
    //
    // Service Failure Actions
    //
    //-----------------------------
    case SERVICE_CONFIG_FAILURE_ACTIONS:
        {
            LPSERVICE_FAILURE_ACTIONSW psfa = 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