#include "precomp.h"
#pragma hdrstop
/*++

Copyright (c) 1991  Microsoft Corporation

Module Name:

    sc.c

Abstract:

    This contains all the service controller functions.

Author:

    Sunil Pai (sunilp) December 1992

--*/


//
// Private function prototypes.
//
VOID
AddServiceToModifiedServiceList(
    IN LPSTR ServiceName
    );



BOOL
TestAdminWorker(
    )
/*++

Routine Description:

    Tests for admin privileges by opening the service control manager
    with read/write/execute access.  Note that this is not a conclusive
    test that setup can do whatever it wants.  There may still be
    operations which fail for lack of security privilege.

Arguments:

    None

Return value:

    Returns TRUE always.  ReturnTextBuffer has "YES" if the current process
    has administrative privileges, "NO" otherwise.

--*/
{
    SC_HANDLE hSC;

    hSC = OpenSCManager(
              NULL,
              NULL,
              GENERIC_READ | GENERIC_WRITE | GENERIC_EXECUTE
              );

    if( hSC != NULL ) {
        SetReturnText( "YES" );
        CloseServiceHandle( hSC );
    }
    else {
        SetReturnText( "NO" );
    }

    return( TRUE );
}

BOOL
SetupCreateServiceWorker(
    LPSTR   lpServiceName,
    LPSTR   lpDisplayName,
    DWORD   dwServiceType,
    DWORD   dwStartType,
    DWORD   dwErrorControl,
    LPSTR   lpBinaryPathName,
    LPSTR   lpLoadOrderGroup,
    LPSTR   lpDependencies,
    LPSTR   lpServiceStartName,
    LPSTR   lpPassword
    )
/*++

Routine Description:

    Setupdll stub for calling CreateService.  If CreateService fails with
    the error code indicating that the service already exists, this routine
    calls the Setupdll stub for ChangeServiceConfig to ensure that the
    parameters passed in are reflected in the services database.

Arguments:

    lpServiceName      - Name of service

    lpDisplayName      - Localizable name of Service or ""

    dwServiceType      - Service type, e.g. SERVICE_KERNEL_DRIVER

    dwStartType        - Service Start value, e.g. SERVICE_BOOT_START

    dwErrorControl     - Error control value, e.g. SERVICE_ERROR_NORMAL

    lpBinaryPathName   - Full Path of the binary image containing service

    lpLoadOrderGroup   - Group name for load ordering or ""

    lpDependencies     - Multisz string having dependencies.  Any dependency
                         component having + as the first character is a
                         group dependency.  The others are service
                         dependencies.

    lpServiceStartName - Service Start name ( account name in which this
                         service is run ).

    lpPassword         - Password used for starting the service.


Return value:

    Returns TRUE if successful. FALSE otherwise.

    if TRUE then ReturnTextBuffer has "SUCCESS" else it has the error text
    to be displayed by the caller.

--*/
{
    SC_HANDLE hSC;
    SC_HANDLE hSCService;
    DWORD     dwTag, dw;
    BOOL      Status = TRUE;

    //
    // Open a handle to the service controller manager
    //

    hSC = OpenSCManager(
              NULL,
              NULL,
              SC_MANAGER_ALL_ACCESS
              );

    if( hSC == NULL ) {
        Status = FALSE;
        dw = GetLastError();
        KdPrint(("SETUPDLL: OpenSCManager Last Error Code: %x", dw));
        switch( dw ) {
        case ERROR_ACCESS_DENIED:
            SetErrorText( IDS_ERROR_PRIVILEGE );
            break;

        case ERROR_DATABASE_DOES_NOT_EXIST:
        case ERROR_INVALID_PARAMETER:
        default:
            SetErrorText( IDS_ERROR_SCOPEN );
            break;
        }
        return( Status );
    }


    //
    // Create the service using the parameters passed in.  Process the optional
    // "" parameters passed in and make them NULL.
    //

    hSCService = CreateService(
                     hSC,
                     lpServiceName,
                     lstrcmpi(lpDisplayName, "")      ? lpDisplayName      : NULL,
                     0,
                     dwServiceType,
                     dwStartType,
                     dwErrorControl,
                     lpBinaryPathName,
                     lstrcmpi(lpLoadOrderGroup, "")   ? lpLoadOrderGroup   : NULL,
                     lstrcmpi(lpLoadOrderGroup, "")   ? &dwTag             : NULL,
                     lpDependencies,
                     lstrcmpi(lpServiceStartName, "") ? lpServiceStartName : NULL,
                     lstrcmpi(lpPassword, "")         ? lpPassword         : NULL
                     );


    //
    // If we were unable to create the service, check if the service already
    // exists in which case all we need to do is change the configuration
    // parameters in the service.  If it is any other error return the error
    // to the inf
    //

    if( hSCService != NULL ) {

        //
        // Note that we won't do anything with the tag.  The person calling
        // this function will have to do something intelligently with the
        // tag.  Most of our services have tag values stored in the inf which
        // we will directly plop down into the registry entry created. Since
        // tags are not important for this boot, the fact that the service
        // controller has some tag and the registry entry may have a different
        // tag is not important.
        //
        //

        CloseServiceHandle( hSCService );

        //
        // Log this service name in our 'modified services' list.
        //
        AddServiceToModifiedServiceList(lpServiceName);

        SetReturnText( "SUCCESS" );
        Status = TRUE;

    }
    else {

        dw = GetLastError();

        if (dw == ERROR_SERVICE_EXISTS) {

            Status = SetupChangeServiceConfigWorker(
                         lpServiceName,
                         dwServiceType,
                         dwStartType,
                         dwErrorControl,
                         lpBinaryPathName,
                         lpLoadOrderGroup,
                         lpDependencies,
                         lpServiceStartName,
                         lpPassword,
                         lpDisplayName
                         );

        }
        else {

            KdPrint(("SETUPDLL: CreateService Last Error Code: %x", dw));
            //
            // process error returned
            //
            switch( dw ) {
            case ERROR_ACCESS_DENIED:
                SetErrorText( IDS_ERROR_PRIVILEGE );
                break;

            case ERROR_INVALID_HANDLE:
            case ERROR_INVALID_NAME:
            case ERROR_INVALID_SERVICE_ACCOUNT:
            case ERROR_INVALID_PARAMETER:
            case ERROR_CIRCULAR_DEPENDENCY:
            default:
                SetErrorText( IDS_ERROR_SCSCREATE );
                break;
            }

            Status = FALSE;
        }
    }

    CloseServiceHandle( hSC );
    return( Status );
}


BOOL
SetupChangeServiceStartWorker(
    LPSTR   lpServiceName,
    DWORD   dwStartType
    )

/*++

Routine Description:

    Routine to change the start value of a service.  This turns
    around and calls the setupdll stub to ChangeServiceConfig.

Arguments:

    lpServiceName      - Name of service

    dwStartType        - Service Start value, e.g. SERVICE_BOOT_START

Return value:

    Returns TRUE if successful. FALSE otherwise.

    if TRUE then ReturnTextBuffer has "SUCCESS" else it has the error text
    to be displayed by the caller.

--*/
{
    return( SetupChangeServiceConfigWorker(
                lpServiceName,
                SERVICE_NO_CHANGE,
                dwStartType,
                SERVICE_NO_CHANGE,
                "",
                "",
                NULL,
                "",
                "",
                ""
                ));
}




BOOL
SetupChangeServiceConfigWorker(
    LPSTR  lpServiceName,
    DWORD  dwServiceType,
    DWORD  dwStartType,
    DWORD  dwErrorControl,
    LPSTR  lpBinaryPathName,
    LPSTR  lpLoadOrderGroup,
    LPSTR  lpDependencies,
    LPSTR  lpServiceStartName,
    LPSTR  lpPassword,
    LPSTR  lpDisplayName
    )

/*++

Routine Description:

    Setupdll stub for ChangeServiceConfig.

Arguments:

    lpServiceName      - Name of service

    dwServiceType      - Service type, e.g. SERVICE_KERNEL_DRIVER

    dwStartType        - Service Start value, e.g. SERVICE_BOOT_START

    dwErrorControl     - Error control value, e.g. SERVICE_ERROR_NORMAL

    lpBinaryPathName   - Full Path of the binary image containing service

    lpLoadOrderGroup   - Group name for load ordering or ""

    lpDependencies     - Multisz string having dependencies.  Any dependency
                         component having + as the first character is a
                         group dependency.  The others are service
                         dependencies.

    lpServiceStartName - Service Start name ( account name in which this
                         service is run ).

    lpPassword         - Password used for starting the service.

    lpDisplayName      - Localizable name of Service or ""


Return value:

    Returns TRUE if successful. FALSE otherwise.

    if TRUE then ReturnTextBuffer has "SUCCESS" else it has the error text
    to be displayed by the caller.

--*/

{
    SC_LOCK   sclLock;
    SC_HANDLE hSC;
    SC_HANDLE hSCService;
    DWORD     dw;
    BOOL      Status = TRUE;

    //
    // Open a handle to the service controller manager
    //

    hSC = OpenSCManager(
              NULL,
              NULL,
              SC_MANAGER_ALL_ACCESS
              );

    if( hSC == NULL ) {
        Status = FALSE;
        dw = GetLastError();
        KdPrint(("SETUPDLL: OpenSCManager Last Error Code: %x", dw));
        switch( dw ) {
        case ERROR_ACCESS_DENIED:
            SetErrorText( IDS_ERROR_PRIVILEGE );
            break;
        case ERROR_DATABASE_DOES_NOT_EXIST:
        case ERROR_INVALID_PARAMETER:
        default:
            SetErrorText( IDS_ERROR_SCOPEN );
            break;
        }
        return( Status );
    }

    //
    // Try to lock the database, if possible.  if we are not able to lock
    // the database we will still modify the services entry.  this is because
    // we are just modifying a single service and chances are very low that
    // anybody else is manipulating the same entry at the same time.
    //

    sclLock = LockServiceDatabase( hSC );

    //
    // Open the service with SERVICE_CHANGE_CONFIG access
    //

    hSCService = OpenService(
                     hSC,
                     lpServiceName,
                     SERVICE_CHANGE_CONFIG
                     );

    if( hSCService != NULL ) {

        if( ChangeServiceConfig(
                hSCService,
                dwServiceType,
                dwStartType,
                dwErrorControl,
                lstrcmpi(lpBinaryPathName, "")   ? lpBinaryPathName   : NULL,
                lstrcmpi(lpLoadOrderGroup, "")   ? lpLoadOrderGroup   : NULL,
                NULL,
                lpDependencies,
                lstrcmpi(lpServiceStartName, "") ? lpServiceStartName : NULL,
                lstrcmpi(lpPassword, "")         ? lpPassword         : NULL,
                lstrcmpi(lpDisplayName, "")      ? lpDisplayName      : NULL
                ))
        {
            //
            // Log this service name in our 'modified services' list.
            //
            AddServiceToModifiedServiceList(lpServiceName);

            SetReturnText("SUCCESS");
        }
        else {
            dw = GetLastError();
            KdPrint(("SETUPDLL: ChangeServiceConfig Last Error Code: %x", dw));
            switch( dw ) {
            case ERROR_ACCESS_DENIED:
                SetErrorText( IDS_ERROR_PRIVILEGE );
                break;

            case ERROR_SERVICE_MARKED_FOR_DELETE:
                SetErrorText( IDS_ERROR_SERVDEL );
                break;

            case ERROR_INVALID_HANDLE:
            case ERROR_INVALID_SERVICE_ACCOUNT:
            case ERROR_INVALID_PARAMETER:
            case ERROR_CIRCULAR_DEPENDENCY:
            default:
                SetErrorText( IDS_ERROR_SCSCHANGE );
                break;
            }
            Status = FALSE;
        }

        CloseServiceHandle( hSCService );
    }
    else {
        dw = GetLastError();
        KdPrint(("SETUPDLL: OpenService Last Error Code: %x", dw));
        switch( dw ) {
        case ERROR_ACCESS_DENIED:
            SetErrorText( IDS_ERROR_PRIVILEGE );
            break;

        case ERROR_INVALID_HANDLE:
        case ERROR_INVALID_NAME:
        case ERROR_SERVICE_DOES_NOT_EXIST:
        default:
            SetErrorText( IDS_ERROR_SCSOPEN );
            break;
        }
        Status = FALSE;
    }


    //
    // Unlock the database if locked and then close the service controller
    // handle
    //


    if( sclLock != NULL ) {
        UnlockServiceDatabase( sclLock );
    }

    CloseServiceHandle( hSC );
    return( Status );

}

LPSTR
ProcessDependencyList(
    LPSTR lpDependenciesList
    )

/*++

Routine Description:

    This processes an LPSTR containing a list of dependencies {...}
    and converts it into a MULTI_SZ string.

Arguments:

    lpDependenciesList - List of dependencies. {blah, blah, ... }

Return value:

    Returns LPSTR containing a MULTI_SZ string which has the dependencies.
    NULL otherwise.  Caller should free the string after use.

--*/

{
    LPSTR lp = NULL;
    RGSZ  rgsz;
    PSZ   psz;
    SZ    sz;

    if ( !lstrcmpi( lpDependenciesList, "" ) ) {
        return( lp );
    }

    rgsz = RgszFromSzListValue( lpDependenciesList );
    if( rgsz ) {

        //
        // process GROUP_IDENTIFIER character
        //

        psz = rgsz;
        while( sz = *psz++ ) {
            if( *sz == '\\' ) {
                *sz = SC_GROUP_IDENTIFIER;
            }
        }

        //
        // Convert the rgsz into a multi sz
        //

        lp = RgszToMultiSz( rgsz );
        RgszFree( rgsz );
    }
    return ( lp );

}


DWORD
LegacyInfGetModifiedSvcList(
    IN  LPSTR SvcNameBuffer,
    IN  UINT  SvcNameBufferSize,
    OUT PUINT RequiredSize
    )
/*++

Routine Description:

    This routine is provided solely for use by the Device Installer APIs to be
    used immediately after installing a device via a legacy INF (i.e., using
    LegacyInfInterpret).  The Device Installer sets a variable, LEGACY_DODEVINSTALL,
    that causes the service modifications to be logged (the service names are
    written to a global multi-sz buffer).  If the legacy INF runs successfully,
    then the Device Installer calls this API to retrieve the list of services that
    were modified.  Upon successful completion of this call (i.e., the caller supplies
    us with a large enough buffer), we will free our list, and subsequent calls will
    return ERROR_NO_MORE_ITEMS.

Arguments:

    SvcNameBuffer - Supplies the address of the character buffer that will receive
        the list of service names.  If this parameter is NULL, then SvcNameBufferSize
        must be zero, and the call will fail with ERROR_INSUFFICIENT_BUFFER if there
        is a list to retrieve.

    SvcNameBufferSize - Supplies the size, in characters, of SvcNameBuffer.

    RequiredSize - Supplies the address of a variable that receives the size, in characters,
        required to store the service name list.

Return Value:

    If the function succeeds (i.e., there's a list to return, and the caller's buffer
    was big enough to hold it), the return value is NO_ERROR.

    If the function fails, the return value will be one of the following error codes:

        ERROR_NO_MORE_ITEMS : Either no services were modified during this INF run,
                              or the list has already been successfully retrieved via
                              this API.

        ERROR_INSUFFICIENT_BUFFER : The caller-supplied buffer was not large enough to
                                    hold the service name list.  In this case, 'RequiredSize'
                                    will contain the necessary buffer size upon return.

--*/
{
    //
    // If we don't have a service name list, then there's nothing to do.
    //
    if(!ServicesModified) {
        return ERROR_NO_MORE_ITEMS;
    }

    *RequiredSize = ServicesModifiedSize;

    if(ServicesModifiedSize > SvcNameBufferSize) {
        return ERROR_INSUFFICIENT_BUFFER;
    }

    //
    // OK, the caller's buffer is large enough to hold our list.  Store the list in their
    // buffer, and get rid of ours.
    //
    CopyMemory(SvcNameBuffer,
               ServicesModified,
               ServicesModifiedSize * sizeof(CHAR)
              );

    SFree(ServicesModified);
    ServicesModified = NULL;
    ServicesModifiedSize = 0;

    return NO_ERROR;
}


VOID
AddServiceToModifiedServiceList(
    IN LPSTR ServiceName
    )
/*++

Routine Description:

    This routine adds the specified service name to our global 'modified services' list,
    if it's not already in the list.

    This is only done if the global variable, !LEGACY_DODEVINSTALL, is set.

Arguments:

    ServiceName - Specifies the service name to be added to the list.

Return Value:

    None.

--*/
{
    LPSTR CurSvcName, TempPtr;
    DWORD ServiceNameLen, NewBufferSize;

    //
    // First, check to make sure that we're supposed to be logging service modifications.
    //
    if(!(TempPtr = SzFindSymbolValueInSymTab("!LEGACY_DODEVINSTALL")) ||
       lstrcmp(TempPtr, "YES")) {
        //
        // Then we weren't invoked by the Device Installer, so no logging should be done.
        //
        return;
    }

    //
    // Now, search through the existing list to see if this service name is already
    // present.
    //
    if(ServicesModified) {

        for(CurSvcName = ServicesModified;
            *CurSvcName;
            CurSvcName += lstrlen(CurSvcName) + 1) {

            if(!lstrcmpi(ServiceName, CurSvcName)) {
                //
                // The service is already in the list--nothing to do.
                //
                return;
            }
        }
    }

    //
    // The service wasn't already in the list.  Allocate/grow the buffer to accommodate
    // this new name.
    //
    ServiceNameLen = lstrlen(ServiceName);
    if(ServicesModified) {

        TempPtr = SRealloc(ServicesModified,
                           (NewBufferSize = ServicesModifiedSize + ServiceNameLen + 1) * sizeof(CHAR)
                          );

        if(TempPtr) {
            ServicesModified = TempPtr;
            lstrcpy(&(ServicesModified[ServicesModifiedSize-1]), ServiceName);
            ServicesModifiedSize = NewBufferSize;
            //
            // Now add extra terminating NULL at the end.
            //
            ServicesModified[ServicesModifiedSize-1] = '\0';
        }

    } else {
        if(ServicesModified = SAlloc((NewBufferSize = ServiceNameLen + 2) * sizeof(CHAR))) {

            ServicesModifiedSize = NewBufferSize;

            lstrcpy(ServicesModified, ServiceName);
            //
            // Now add extra terminating NULL at the end.
            //
            ServicesModified[ServiceNameLen+1] = '\0';
        }
    }
}