Работа со службами из программы со стандартными полномочиями

334
15 января 2017, 16:38

Имеется следующая ситуация:
1. Работаем под OS поддерживающими UAC.
2. Пользователь запускает основное приложение без повышенных полномочий (UAC).

Задача:
Из основного приложения, запущенного с «без повышенных полномочий», требуется запускать/останавливать службы. Это уже требует повышения полномочий UAC. Хотелось бы реализовать временное повышение полномочий, для операции запуска останова службы. При этом конечно повышать полномочия основного процесса «навсегда» не надо.

Испробованы следующие варианты решения:
1) Использование стандартных функций WinAPI управления службами: OpenService, ControlService, StartService.

Если делать «в лоб», то OpenService не показывает диалог повышения UAC и возвращает код 5 – нет доступа. (и никак не информирует о том, что это из-за не повышенного UAC).

Получается, что для дальнейшей работы по этому пути нужно делать:
1. Найти метод WinAPI который узнает, что для текущего оператора функции управления запуском службы в принципе разрешены, но «понижены» UAC-ом.
2. Как-то найти метод программно запустить запрос пользователя разрешения повысить UAC.
3. Получить повышенные реквизиты и используя их уже вызывать функции управления службами.

2) Вариант №2 использовать внешние консольные утилиты типа net, sc

(Через CreateProcess или ShellExecuteEx)

Что получается:
- если делать в лоб, то утилиты сами по себе не имеют манифеста с запросом повышения полномочий, поэтому они запускаются, но возвращают код ошибки «Access denied» (опять же без уточнения что это из-за не повышенного UAC).

Что можно делать:
- вариант «обходной»: сделать собственную утилиту, в манифесте которой будет запрос повышения полномочий, а сама утилита будет по сути получать и из-под своего аккаунта уже выполнять, например, sc start MyService или net start MyService. Так сделать получилось. Но сама идея с промежуточным exe-ником смотрится «не красиво»

Если же дорабатывать «красиво» этот вариант, то он опять же сводится к перечисленным выше задачам:
1. Узнать, что нам есть смысл запрашивать повышения UAC для управления этой службой.
2. Как-то запросить диалог повышения UAC и получить handle с которым уже:
3. Выполнить createProcess ShellExecute из-под полученного аккаунта

Кто-то может подсказать по пунктам один и два?

На всякий случай некоторые дополнительные ограничения:
1. Не хотелось бы использовать любые внешние решения/ утилиты кроме встроенных в OS (или собственных).
2. В задаче нам не требуется запрашивать пользователя ввести какой-то отличный от текущего логин/пароль. Только повысить полномочия.

То, что реализовано (ServiceManager - exe-файл, требующий повышенные полномочия - выполняет из командной строки sc start MyService):

int nRes = -1;
  // сначала попробуем запустить "по старом". Если будут ошибки "элевации", то
  // будем пытаться по другому:
  {
    STARTUPINFO si;
    PROCESS_INFORMATION pi;
    memset(&si, 0, sizeof(STARTUPINFO));
    si.cb = sizeof(STARTUPINFO);
    si.dwFlags = STARTF_USESHOWWINDOW;
    si.wShowWindow = SW_SHOW;
  AnsiString strProcess = "ServiceManager.exe sc start MyService";
    BOOL fApiRes = ::CreateProcess(
      NULL,
      strProcess.c_str(),
      NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
    if(!fApiRes)
    {
      // Были ошибки запуска:
      DWORD dwError = ::GetLastError();
      if(dwError != ERROR_ELEVATION_REQUIRED /*740*/)
      {
        ShowMessage("Оperation failed. Code: " + IntToStr(dwError));
        return dwError;
      }
      else
      {
        // Тут провалимся ниже, там обработка...
      }
    }
    else
    {
      // Всё запустилось без ошибок.
      return 0;
    }
  }
  // Если мы здесь, то будем повышать права
  SHELLEXECUTEINFO sei;
  memset(&sei, 0, sizeof(SHELLEXECUTEINFO));
  sei.cbSize = sizeof(SHELLEXECUTEINFO);
  sei.fMask = SEE_MASK_NOCLOSEPROCESS;
  sei.lpVerb = "runas";
  sei.lpFile = "ServiceManager.exe";
  sei.lpParameters = "sc start MyService"
  sei.nShow = SW_SHOWNORMAL;
  bool fResult = ShellExecuteEx(&sei);
  DWORD dwResult = ::WaitForSingleObject(sei.hProcess, INFINITE); // dwResult = 0
  nRes = 0; // На случай успешного выполнения. Если ошибка - рез-т поменяется ниже
  if(fResult)
  {
    DWORD dwCode = -1;
    fResult = ::GetExitCodeProcess(sei.hProcess, &dwCode);
    nRes = (int)dwCode;
    DWORD dwError = ::GetLastError();
    if(!fResult)
    {
      DWORD dwError = ::GetLastError();
      __L_BAD(m_pLog, "::GetExitCodeProcess return error: " +
      AnsiString(dwError));
      nRes = dwError;
    }
    else if(dwCode)
    {
      __L_BAD(m_pLog, "Application return error code: " +
        AnsiString(nRes));
    }
  }
  else
  {
    __L_BAD(m_pLog, "Error in WaitForSingleObject");
    ::TerminateProcess(sei.hProcess, -1);
    nRes = -11;
  }
  ::CloseHandle(sei.hProcess);
  return nRes;

Заранее большое спасибо

READ ALSO
Проблемы с реализацтей Entity–component–system Design Pattern [требует правки]

Проблемы с реализацтей Entity–component–system Design Pattern [требует правки]

Реализовал ECS pattern на примере игрыПока была одна сущность - Игрок (Player) было все хорошо, создал 10 компонентов Position, SpriteSheet, State, Movable, MovableMod, Controller,...

365
Рекурсивная функция

Рекурсивная функция

Дано натуральное число nВыведите все числа от 1 до n

372
Горизонтальный ScrollBar в QTreeView

Горизонтальный ScrollBar в QTreeView

Как можно включить отображение горизонтального ScrollBar в QTreeView с фиксированной шириной при выходе названия ветки за границы view'шки?

477