Files
2022-09-01 15:42:34 +08:00

375 lines
9.6 KiB
C++

// WinArkSvc.cpp : This file contains the 'main' function. Program execution begins and ends there.
//
#include "pch.h"
#include "ServiceCommon.h"
#pragma comment(lib,"wtsapi32")
int Error(const char* msg, DWORD error = ::GetLastError());
void WINAPI ServiceMain(DWORD dwNumServicesArgs, LPTSTR* args);
DWORD WINAPI ServiceHandler(DWORD dwControl, DWORD dwEventType, LPVOID lpEventData, LPVOID lpContext);
void SetStatus(DWORD status);
void HandleMessage(const AlarmMessage& msg);
void NTAPI OnTimerExpired(
_Inout_ PTP_CALLBACK_INSTANCE Instance,
_Inout_opt_ PVOID Context,
_Inout_ PTP_TIMER Timer);
int HandleCommandLine(int argc, const char* argv[]);
bool IsRunningElevated();
// service control
int InstallService();
int StartWinArkService();
int StopService();
int UninstallService();
SERVICE_STATUS_HANDLE g_hService;
SERVICE_STATUS g_Status;
HANDLE g_hStopEvent;
HANDLE g_hMainsolt;
PTP_TIMER g_Timer;
int Error(const char* msg, DWORD error) {
printf("%s (%u)\n", msg, error);
return 1;
}
int main(int argc,const char* argv[]){
if (argc > 1)
return HandleCommandLine(argc, argv);
// Note that the service name is typed as LPTSTR, meaning it should not be part of readonly
// memory, and that¡¯s why the name is first set to a variable on the stack(which is
// always read / write), before passing it to the structure.
WCHAR name[] = L"winark";
const SERVICE_TABLE_ENTRY table[] = {
{name,ServiceMain},
{nullptr,nullptr}
};
if (!::StartServiceCtrlDispatcher(table))
return 1;
return 0;
}
void WINAPI ServiceMain(DWORD dwNumServicesArgs, LPTSTR* args) {
g_Status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
g_Status.dwWaitHint = 500;
bool error = true;
do { // non-loop for easy breaking
g_hService = ::RegisterServiceCtrlHandlerEx(L"WinArkSvc",
ServiceHandler, nullptr);
if (!g_hService)
break;
g_hStopEvent = ::CreateEvent(nullptr, FALSE, FALSE, nullptr);
if (!g_hStopEvent)
break;
SetStatus(SERVICE_START_PENDING);
// create a mailslotand configure
// its security descriptor to allow full control to the user running the service,
// with all other users having write/read access only
// create the Everyone SID
//
BYTE worldSid[SECURITY_MAX_SID_SIZE];
DWORD len = sizeof(worldSid);
auto pWorldSid = (PSID)worldSid;
if (!::CreateWellKnownSid(WinWorldSid, nullptr, pWorldSid, &len))
break;
//
// get SID of the user running this process
//
HANDLE hToken;
if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, &hToken))
break;
BYTE userBuffer[SECURITY_MAX_SID_SIZE + sizeof(TOKEN_USER) + sizeof(SID_AND_ATTRIBUTES)];
auto user = (TOKEN_USER*)userBuffer;
BOOL ok = ::GetTokenInformation(hToken, TokenUser, userBuffer, sizeof(userBuffer), &len);
::CloseHandle(hToken);
if (!ok)
break;
auto ownerSid = user->User.Sid;
//
// allocate and initialize a new security descriptor
//
PSECURITY_DESCRIPTOR sd = ::HeapAlloc(::GetProcessHeap(), 0, SECURITY_DESCRIPTOR_MIN_LENGTH);
if (!sd)
break;
if (!::InitializeSecurityDescriptor(sd, SECURITY_DESCRIPTOR_REVISION)) {
break;
}
//
// build ACEs
//
EXPLICIT_ACCESS ea[2] = { 0 };
ea[0].grfAccessPermissions = FILE_ALL_ACCESS;
ea[0].grfAccessMode = SET_ACCESS;
ea[0].grfInheritance = NO_INHERITANCE;
ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
ea[0].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;
ea[0].Trustee.ptstrName = (PWSTR)ownerSid;
ea[1].grfAccessPermissions =
FILE_GENERIC_WRITE | FILE_GENERIC_READ;
ea[1].grfAccessMode = SET_ACCESS;
ea[1].grfInheritance = NO_INHERITANCE;
ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID;
ea[1].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;
ea[1].Trustee.ptstrName = (PWSTR)pWorldSid;
//
// create DACL from ACEs
//
PACL dacl;
if (ERROR_SUCCESS != ::SetEntriesInAcl(_countof(ea), ea,
nullptr, &dacl))
break;
//
// apply owner and DACL
//
if (!::SetSecurityDescriptorOwner(sd, ownerSid, FALSE))
break;
if (!::SetSecurityDescriptorDacl(sd, TRUE, dacl, FALSE))
break;
//
// create mailsolt
//
SECURITY_ATTRIBUTES sa = { sizeof(sa) };
sa.lpSecurityDescriptor = sd;
g_hMainsolt = ::CreateMailslot(L"\\\\.\\mailslot\\winark", 1024,
MAILSLOT_WAIT_FOREVER, &sa);
if (g_hMainsolt == INVALID_HANDLE_VALUE)
break;
//
// cleanup
//
::HeapFree(::GetProcessHeap(), 0, sd);
::LocalFree(dacl);
error = false;
} while (false);
if (error) {
// The most common error reporting
// mechanism used by services is writing to the Windows event log.
SetStatus(SERVICE_STOPPED);
return;
}
SetStatus(SERVICE_RUNNING);
DWORD msgSize, count;
AlarmMessage msg;
while (::WaitForSingleObject(g_hStopEvent, 1000) == WAIT_TIMEOUT) {
do
{
if (!::GetMailslotInfo(g_hMainsolt, nullptr, &msgSize, &count, nullptr))
break;
if (msgSize == MAILSLOT_NO_MESSAGE)
break;
DWORD bytes;
if (msgSize == sizeof(msg) &&
::ReadFile(g_hMainsolt, &msg, sizeof(msg), &bytes, nullptr)) {
HandleMessage(msg);
}
count--;
} while (count>0);
}
SetStatus(SERVICE_STOPPED);
::CloseHandle(g_hStopEvent);
::CloseHandle(g_hMainsolt);
}
void SetStatus(DWORD status) {
g_Status.dwCurrentState = status;
g_Status.dwControlsAccepted = status == SERVICE_RUNNING ? SERVICE_ACCEPT_STOP : 0;
::SetServiceStatus(g_hService, &g_Status);
}
void HandleMessage(const AlarmMessage& msg) {
switch (msg.Type)
{
case MessageType::SetAlarm:
if (g_Timer == nullptr)
g_Timer = ::CreateThreadpoolTimer(OnTimerExpired, nullptr, nullptr);
::SetThreadpoolTimer(g_Timer, (PFILETIME)&msg.Time, 0, 1000);
break;
case MessageType::CancelAlarm:
if (g_Timer) {
::CloseThreadpoolTimer(g_Timer);
g_Timer = nullptr;
}
break;
default:
break;
}
}
_Use_decl_annotations_
void NTAPI OnTimerExpired(PTP_CALLBACK_INSTANCE, PVOID, PTP_TIMER) {
WCHAR title[] = L"!!! ALARM !!!";
WCHAR msg[] = L"It's time to wake up!";
DWORD response;
::WTSSendMessage(nullptr, ::WTSGetActiveConsoleSessionId(),
title, sizeof(title), msg, sizeof(msg),
MB_OK | MB_ICONEXCLAMATION, 0, &response, FALSE);
}
DWORD WINAPI ServiceHandler(DWORD dwControl, DWORD dwEventType, LPVOID lpEventData, LPVOID lpContext) {
switch (dwControl)
{
case SERVICE_CONTROL_STOP:
SetStatus(SERVICE_STOP_PENDING);
::SetEvent(g_hStopEvent);
break;
}
return 0;
}
bool IsRunningElevated() {
HANDLE hToken;
if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, &hToken))
return false;
ULONG elevated = 0;
DWORD len;
::GetTokenInformation(hToken, TokenElevation, &elevated, sizeof(elevated), &len);
::CloseHandle(hToken);
return elevated ? true : false;
}
int HandleCommandLine(int argc, const char* argv[]) {
if (!IsRunningElevated()) {
printf("You must run elevated to configure the service.\n");
return 1;
}
if (!_stricmp(argv[1], "install") || !_stricmp(argv[1], "/install")
||!_stricmp(argv[1],"-install")) {
bool start = argc > 2 && _stricmp(argv[2], "start") == 0;
auto error = InstallService();
if (error == 0 && start)
return StartWinArkService();
return error;
}
if (!_stricmp(argv[1], "uninstall")||!_stricmp(argv[1],"-uninstall")
||!_stricmp(argv[1],"/uinstall")){
return UninstallService();
}
if (!_stricmp(argv[1], "start")||!_stricmp(argv[1],"-start")) {
return StartWinArkService();
}
if (!_stricmp(argv[1], "stop")|| !_stricmp(argv[1], "-stop")) {
return StopService();
}
return 0;
}
int InstallService() {
SC_HANDLE hScm = ::OpenSCManager(nullptr, nullptr, SC_MANAGER_CREATE_SERVICE);
if (!hScm)
return Error("Failed to open SCM database");
WCHAR path[MAX_PATH];
::GetModuleFileName(nullptr, path, _countof(path));
SC_HANDLE hService = ::CreateService(hScm,
L"WinArkSvc",
L"WinArk Demo Service",
SERVICE_ALL_ACCESS,
SERVICE_WIN32_OWN_PROCESS,
SERVICE_DEMAND_START,
SERVICE_ERROR_NORMAL,
path,
L"Base",
nullptr, nullptr, L"NT AUTHORITY\\LocalService", nullptr);
if (!hService)
return Error("Failed to install service");
printf("Service installed successfully.\n");
::CloseServiceHandle(hService);
::CloseServiceHandle(hScm);
return 0;
}
int StartWinArkService() {
SC_HANDLE hScm = ::OpenSCManager(nullptr, nullptr, SC_MANAGER_CONNECT);
if (!hScm)
return Error("Failed to open SCM database");
SC_HANDLE hService = ::OpenService(hScm, L"WinArkSvc", SERVICE_START);
if (!hService)
return Error("Failed to open service");
if (::StartService(hService, 0, nullptr))
printf("Service started successfully.\n");
else
return Error("Failed to start service");
::CloseServiceHandle(hService);
::CloseServiceHandle(hScm);
return 0;
}
int StopService() {
auto hScm = ::OpenSCManager(nullptr, nullptr, SC_MANAGER_CONNECT);
if (!hScm)
return Error("Failed to open SCM database");
auto hService = ::OpenService(hScm, L"WinArkSvc", SERVICE_STOP);
if (!hService)
return Error("Failed to open service");
if (::ControlService(hService, SERVICE_CONTROL_STOP, &g_Status))
printf("Service stopped successfully.\n");
else
return Error("Failed to stop service");
::CloseServiceHandle(hService);
::CloseServiceHandle(hScm);
return 0;
}
int UninstallService() {
auto hScm = ::OpenSCManager(nullptr, nullptr, SC_MANAGER_CONNECT);
if (!hScm)
return Error("Failed to open SCM database");
auto hService = ::OpenService(hScm, L"WinArkSvc", DELETE | SERVICE_QUERY_STATUS);
if (!hService)
return Error("Failed to open service");
SERVICE_STATUS status;
if (::QueryServiceStatus(hService, &status) && status.dwCurrentState == SERVICE_RUNNING)
StopService();
if (::DeleteService(hService))
printf("Service uninstalled successfully.\n");
else
Error("Failed to uninstall service");
::CloseServiceHandle(hService);
::CloseServiceHandle(hScm);
return 0;
}