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

[강좌로 보는 서비스 프로그래밍] 외적으로 보여지는 서비스 [2/?]
[강좌로 보는 서비스 프로그래밍] 설치와 제거 [3/?]
[강좌로 보는 서비스 프로그래밍] 가끔 쓸모있는 관련 함수들 [4/?]
[강좌로 보는 서비스 프로그래밍] 시작/중지/일시정지 [5/?]

이번 장에서는 실제로 서비스 프로그램이 어떻게 구성되어 있는지 간단한 샘플을 이용하여 살펴보고자 한다.
샘플에 구현된 기능은 다음과 같다.
1. 설치/제거
2. 시작/중지/일시중지/재시작
3. 설치시 각종 파라미터 설정
4. 디버깅을 위한 간단한 조작
5. 전체 서비스 프로그램의 흐름
6. 기능은 지난 서비스 설명시 잠깐 나온 땡땡거리는 서비스를 기반으로 한다.

소스의 구성 품목은 다음과 같다.
1. main.cpp 서비스 구조를 가진 소스
2. ServiceUtil.h / cpp 서비스를 개발할 때 간혹 쓸모있는 함수 모음 클래스
3. ServiceMgr.h / cpp  서비스의 설치나 동작을 제어하는 기능을 가진 모음 클래스

아래는 접어놓은 부분은 main.cpp 와 두개 클래스의 헤더만 설명을 위하여 올려 놓았다.
전체 샘플은 압축하여 첨부 파일로 추가할 것이다.




서비스 본체를 구성하고 있는 main.cpp 를 살펴보기로 한다.
int main(int argc, char** argv)
{
#if 0
    /////////////////////////////////////////////////////////////////////
    // #define 로 쌓인 이 구문은 첨부된 클래스 2개의 간단한 사용법과
    // 정상적인 지를 확인하기 위한 테스트 코드이다. 불필요하면 스킵해도 된다.
    
/////////////////////////////////////////////////////////////////////

    CServiceUtility u;
    u.ServiceUserBlankPassword(FALSE);

    u.UserPrivileges("Administrtor", L"SeServiceLogonRight");
    u.ProcessPrivileges(GetCurrentProcess(), SE_SHUTDOWN_NAME, TRUE);

    PWTS_PROCESS_INFO sinfo = NULL;
    DWORD count = 0;
    u.WTSEnumProcesses(sinfo, &count);
    u.WTSFree(sinfo);

    PWTS_SESSION_INFO info = NULL;
    count = 0;
    u.WTSEnumSessions(info, &count);
    u.WTSFree(info);

    CServiceUtility::CWTSSession ws(WTS_CURRENT_SERVER_HANDLE, 0);

    char buffer[512] = {0};
    if(u.GetOSDisplayString(buffer))
        printf("%s\n", buffer);

    CServiceUtility su;
    STARTUPINFO si = {0};
    PROCESS_INFORMATION pi = {0};
    u.CreateProcessToDesktop("C:\\Windows\\System32\\cmd.exe", NULL, si, pi, 0);
    WaitForSingleObject(pi.hProcess, INFINITE);

#endif

    // 서비스를 관리하는 매니저와 구현을 도와주는 유틸 클래스
    
CServiceManager manager;
    CServiceUtility utility;

    // 아래 항목은 설정하지 않으면, 파일정보를 읽어서 자동으로채운다.
    manager.ConfigServiceName("CROWBACK");
    manager.ConfigServiceDisp("CROWBACK SERVICE");
    manager.ConfigServiceDesc("CROWBACK's test service, forever ting... ting...");

    // 먼저 서비스로 구동중인지, 응용 어플리케이션 모드로 구동 중인지를 확인한다.
    if(utility.IsServiceMode() == FALSE)
    {
        // 응용 어플리케이션 모드로 돌아갈 때는 인자를 하나 받는다. 인자는 아래와 같다.
        // 인자가 없을 경우는 직접 인자를 입력 받는다. 지정된 값이 아니면 구동 상태로 돌입한다.
        char ch = 0;
        if(argc != 2)
        {
            printf("sample.exe [option: i, u, s, t, p, c]\n");
            printf(" i - install\n");
            printf(" u - uninstall\n");
            printf(" s - start\n");
            printf(" t - stop\n");
            printf(" p - pause\n");
            printf(" c - continue\n\n");

            printf("input command: ");
            ch = getch();
        }
        else
            ch = argv[1][0];

        // 인자에 따라 아래와 같은 동작을 수행한다.
    
    switch(ch)
        {
        case 'i':
            manager.Install();
            return 0;
        case 'u':
            manager.Uninstall();
            return 0;
        case 's':
            manager.Start();
            return 0;
        case 't':
            manager.Stop();
            return 0;
        case 'p':
            manager.Pause();
            return 0;
        case 'c':
            manager.Continue();
            return 0;

        // 위의 값이 아니면 그냥 응용 어플리케이션 모드로 구동한다.

        default:
            return service_main(argc, argv);
        }
    }

   // 여기로 왔다는 것은 현재 서비스 모드로 구동중인 상태인 것이다.
   SERVICE_TABLE_ENTRY STE[] =
   {
       //SCM (Service Control Manager)에 등록하기 위하여 지정된 구조체 정보를 채운다.
       { manager.m_InstallParam.lpServiceName, (LPSERVICE_MAIN_FUNCTION)service_main },
       { NULL, NULL}
   };
      
   // 해당 채워진 구조체를 이용하여 서비스 메인 함수를 SCM이 호출할 수 있도록 등록작업을 마친다.
   if(StartServiceCtrlDispatcher(STE) == FALSE)
       return -1;

   return 0;
}


여기서는 콘솔 응용프로그램의 main 함수에서 어떻게 서비스 메인함수를 등록하는지 간단한 절차를 처리해보았다.
하나의 페이지에 꽤 긴 내용이 들어가서 편집작업에 어려움이 있어 추가 설명과 나머지 내용을 다음장에서 정리할 것이다.

상당히 많은 시간을 날렸네요.. ㅜㅜ

이전 내용하고 비슷한데요..
이전에 서비스계정에 이름은 있고, 암호는 없는 사용자를 처리할 때는

HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\Lsa
의 limitblankpassworduse 값을 1로 변경하면서 마무리 지었습니다.
윈도우즈 XP프로와, 서버계열에서는 아주 잘되던데..

윈도우즈 XP 홈에서는 저기까지는 잘되는데 나머지 난관이 발생하더군요..
사용자 계정 자체에 서비스로 로그온할 수 있는 보안 권한을 상승 시켜주어야 했습니다.

계정과 암호 (비어있을 경우) 정상적인데도 불구하고 서비스가 시작되지 않더군요.
서비스 관리자를 열어서 다시 서비스의 로그온 정보에서 암호를 지워주고 시작을
누르면 계정을 "서비스로 로그온 권한을 상승하였습니다." 라는 메시지가 나오면서
시작이 잘되더군요.

문제는 이걸 프로그램적으로 자동화 시켜야한다는게 문제였는데.. ^^;


#include <Ntsecapi.h>
#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 AddPrivileges(PSID AccountSID, LSA_HANDLE PolicyHandle)
{
 LSA_UNICODE_STRING lucPrivilege;
 NTSTATUS ntsResult;
 
 // Create an LSA_UNICODE_STRING for the privilege names.
 if (!InitLsaString(&lucPrivilege, L"SeServiceLogonRight"))
  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;
}

사용법은 아래와 같습니다.
 LSA_HANDLE Policy = GetPolicyHandle();
 BYTE buffer[1024] = {0};
 PSID sid = (PSID)buffer;
 if(GetUserSID(sid, "까마귀"))
  AddPrivileges(sid, Policy);

위와 같은 방법으로 사용자 계정에다가 직접 SeServiceLogonRight 권한을 넣어
주는 방법으로 해결했습니다.

자료가 없어서 헤딩좀 했는데.. 도움이 되시기를...

+ Recent posts