지난 내용
[강좌로 보는 서비스 프로그래밍] 서비스란 무엇인가? [1/?]

[강좌로 보는 서비스 프로그래밍] 외적으로 보여지는 서비스 [2/?]
[강좌로 보는 서비스 프로그래밍] 설치와 제거 [3/?]

지난 장에서는 실제 서비스 몸통을 만들기 전에 만들어진 서비스를 설치/제거하는 아주 초간단 구현을 살펴보았다.
원래는 이번장에서 서비스를 중지, 일시중지, 재시작 상태를 변경시키는 기능에 대하여 진행하려고 하였으나
몇몇 분의 요청에 의하여 지금까지 열거된 기능중에 필요한 함수 몇개를 먼저 소개하고자 한다.

1. 사용자 계정에 권한을 할당하는 UserPrivileges 함수.
#define TARGET_SYSTEM_NAME L"."
LSA_HANDLE GetPolicyHandle()
{
    LSA_OBJECT_ATTRIBUTES ObjectAttributes;
    WCHAR SystemName[] = TARGET_SYSTEM_NAME;
    USHORT SystemNameLength;
    LSA_UNICODE_STRING lusSystemName;
    NTSTATUS ntsResult;
    LSA_HANDLE lsahPolicyHandle;

    // Object attributes are reserved, so initialize to zeros.
    ZeroMemory(&ObjectAttributes, sizeof(ObjectAttributes));

    //Initialize an LSA_UNICODE_STRING to the server name.
    SystemNameLength = wcslen(SystemName);
    lusSystemName.Buffer = SystemName;
    lusSystemName.Length = SystemNameLength * sizeof(WCHAR);
    lusSystemName.MaximumLength = (SystemNameLength+1) * sizeof(WCHAR);

    // Get a handle to the Policy object.
    ntsResult = LsaOpenPolicy(
       &lusSystemName,    //Name of the target system.
       &ObjectAttributes, //Object attributes.
       POLICY_ALL_ACCESS, //Desired access permissions.
       &lsahPolicyHandle  //Receives the policy handle.
        );

    if (ntsResult != ERROR_SUCCESS)
        return NULL;
    return lsahPolicyHandle;
}

BOOL InitLsaString(PLSA_UNICODE_STRING pLsaString, LPCWSTR pwszString)
{
    DWORD dwLen = 0;

    if (NULL == pLsaString)
        return FALSE;

    if (NULL != pwszString)
    {
        dwLen = wcslen(pwszString);
        if (dwLen > 0x7ffe)   // String is too large
            return FALSE;
    }

    // Store the string.
    pLsaString->Buffer = (WCHAR *)pwszString;
    pLsaString->Length =  (USHORT)dwLen * sizeof(WCHAR);
    pLsaString->MaximumLength= (USHORT)(dwLen+1) * sizeof(WCHAR);

    return TRUE;
}

BOOL GetUserSID(PSID sid, const char* username)
{
   DWORD SidBufSz;
   char DomainNameBuf[256] = {0};
   DWORD DomainNameBufSz;
   SID_NAME_USE SNU;
   DomainNameBufSz = 256;

   if(!LookupAccountName(NULL, username, sid, &SidBufSz, DomainNameBuf, &DomainNameBufSz, &SNU))
       return FALSE;
    return TRUE;
}

BOOL UserPrivileges(PSID AccountSID, LSA_HANDLE PolicyHandle, wchar_t* pszPrivilege)
{
    LSA_UNICODE_STRING lucPrivilege;
    NTSTATUS ntsResult;

    // Create an LSA_UNICODE_STRING for the privilege names.
    if (!InitLsaString(&lucPrivilege, pszPrivilege))
        return FALSE;

    ntsResult = LsaAddAccountRights(
        PolicyHandle,  // An open policy handle.
        AccountSID,    // The target SID.
        &lucPrivilege, // The privileges.
        1              // Number of privileges.
        );

    if (ntsResult == ERROR_SUCCESS)
        return TRUE;
    else
        return FALSE;
}
MSDN에 있는 샘플 소스를 가져다가 추려서 사용하고 있는 기능입니다. 윈도우에서 계정이나 보안에 상관있는 API 등을 호출하거나 사용하려면
권한에 관한 문제에 자주 걸리는데, 저것 말고도 알아야 할 기능들이 참 많죠.

2. 프로세스에 권한을 할당하는 ProcessPrivilege 함수
BOOL ProcessPrivilege(HANDLE pid, char* pszPrivilege, BOOL bEnable)
{
    HANDLE hToken;
    TOKEN_PRIVILEGES tkp;

    // Get a token for this process.
    if (!OpenProcessToken(pid, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
        return FALSE;

    // Get the LUID for the shutdown privilege.
    LookupPrivilegeValue(NULL, pszPrivilege, &tkp.Privileges[0].Luid);

    tkp.PrivilegeCount = 1;  // one privilege to set
    tkp.Privileges[0].Attributes = bEnable ? SE_PRIVILEGE_ENABLED : SE_PRIVILEGE_REMOVED;

    // Get the shutdown privilege for this process.

    return AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, (PTOKEN_PRIVILEGES)NULL, 0);
}
유저도 권한을 가져야 하겠지만, 유저가 실행시킨 프로세스도 권한이 있어야 할때가 많습니다.

3. 사용자 데스크탑에 GUI를 띄워주는 CreateProcessToDesktop 함수.
BOOL CreateProcessToDesktop(char* pszExecute, UINT sampling)
{
    HANDLE hd = OpenProcess(PROCESS_ALL_ACCESS, FALSE, sampling);
    if(hd == NULL)
        return FALSE;

    HANDLE token = NULL;
    if(OpenProcessToken(hd, TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE, &token) == FALSE)
    {
        CloseHandle(hd);
        return FALSE;
    }

    HANDLE newtoken = NULL;
    if(::DuplicateTokenEx(token, TOKEN_ASSIGN_PRIMARY | TOKEN_ALL_ACCESS, NULL,
          SecurityImpersonation, TokenPrimary, &newtoken) == FALSE)
    {
        CloseHandle(hd);
        CloseHandle(token);
        return FALSE;
    }

    CloseHandle(token);

    void* EnvBlock = NULL;
    CreateEnvironmentBlock(&EnvBlock, newtoken, FALSE);

    STARTUPINFO si = {0};
    PROCESS_INFORMATION pi = {0};

    if(::CreateProcessAsUser(newtoken,
        pszExecute,
        NULL,
        NULL,
        NULL,
        FALSE,
        NORMAL_PRIORITY_CLASS | CREATE_UNICODE_ENVIRONMENT |
        CREATE_NEW_CONSOLE  | CREATE_SEPARATE_WOW_VDM |
        CREATE_NEW_PROCESS_GROUP,
        EnvBlock,
        NULL,
        &si,
        &pi) == TRUE)
    {
        CloseHandle(hd);
        CloseHandle(newtoken);
        return TRUE;
    }

    CloseHandle(hd);
    CloseHandle(newtoken);
    return FALSE;
}
예전에는 저걸 구현하기 위해 OpenWindowStation 과 OpenDesktop 을 이용해서 오만가지 필드를 채워가며 굉장히 있어보이는 긴 코드를
이용해서 구현했었습니다. 위 함수는 뛰우고 싶은 윈도우 세션을 구해서, 해당 세션에 있는 프로세스 아무거나 하나 가져온다음에, 그 핸들을
복사해서 그 핸들이 존재하는 세션에 그냥 띄워 버리는 겁니다.

4. 프로그램이 서비스로 구동중인지 알아보는 IsServiceMode 함수
BOOL IsServiceMode()
{
    // need _WIN32_WINNT as 0x0500 or later
    return GetConsoleWindow() ? FALSE : TRUE;
}
서비스 일경우는 해당 GetConsoleWindow 가 NULL 을 리턴하고, 응용프로그램 모드에서 돌아가면 해당 콘솔의 핸들이 리턴됩니다.
이걸 이용해서 서비스로 구동중인지, 그냥 응용프로그램 모드로 구동중인지를 내부적으로 감지해서 다르게 동작하도록 구현하는게 가능합니다.

5. 해당 서비스에 걸린 디펜던시를 찾는 FindDependencyService 함수
UINT FindDependencyService(char* pszServiceName, LPENUM_SERVICE_STATUS& lpDependencies)
{
    SC_HANDLE hScm = OpenSCManager(".", NULL, SC_MANAGER_CREATE_SERVICE);
    if(hScm == NULL)
        return 0;

    SC_HANDLE hSrv = OpenService(hScm, pszServiceName, GENERIC_READ);
    if(hSrv == NULL)
    {
        CloseServiceHandle(hScm);
        return 0;
    }

    DWORD dwBytesNeeded = 0, dwCount = 0;
    if(EnumDependentServices(hSrv, SERVICE_STATE_ALL, NULL, 0, &dwBytesNeeded, &dwCount))
        goto ERROR_LABEL;
    else if(GetLastError() == ERROR_MORE_DATA)
    {
        lpDependencies = (LPENUM_SERVICE_STATUS) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwBytesNeeded );
        if(lpDependencies == NULL)
            goto ERROR_LABEL;

        if ( !EnumDependentServices( hSrv, SERVICE_STATE_ALL, lpDependencies,
            dwBytesNeeded, &dwBytesNeeded, &dwCount ) )
        {
            HeapFree(GetProcessHeap(), 0, lpDependencies);
            lpDependencies = NULL;
            goto ERROR_LABEL;
        }
    }

ERROR_LABEL:
    CloseServiceHandle(hSrv);
    CloseServiceHandle(hScm);
    return dwCount;
}
그냥 지정된 서비스에 디펜던시가 걸린 목록을 쭉 뽑아주는 기능을 담당합니다.

#. 그외 윈도우 보안과 관련된 링크들
    [자료] 로그온 사용자, 이름과 유저그룹 정보 
    [MSDN] 윈도우 보안 카테고리
    [MSDN] Authentication Functions and Samples
    [MSDN] Authorization Functions and Samples
    [codeguru] 서비스 카테고리
    [codeproject] System Programming - Windows Services

추릴 만한 함수들과 관련 링크들은 해당 페이지에 지속적으로 업데이트를 하겠습니다.

+ Recent posts