// OpenVPN -- An application to securely tunnel IP networks // over a single port, with support for SSL/TLS-based // session authentication and key exchange, // packet encryption, packet authentication, and // packet compression. // // Copyright (C) 2012-2022 OpenVPN Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License Version 3 // as published by the Free Software Foundation. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program in the COPYING file. // If not, see . #pragma once // windows service utilities #include #include #include #include #include #include #include #include #include #include namespace openvpn::Win { class Service { public: OPENVPN_EXCEPTION(winsvc_error); struct Config { std::string name; std::string display_name; std::vector dependencies; bool autostart = false; bool restart_on_fail = false; }; Service(const Config &config_arg) : config(config_arg) { std::memset(&status, 0, sizeof(status)); status_handle = nullptr; checkpoint = 1; } bool is_service() const { return bool(service); } void install() { // open service control manager ScopedSCHandle scmgr(::OpenSCManagerW( NULL, // local computer NULL, // ServicesActive database SC_MANAGER_ALL_ACCESS)); // full access rights if (!scmgr.defined()) { const Win::LastError err; OPENVPN_THROW(winsvc_error, "OpenSCManagerW failed: " << err.message()); } // convert service names to wide string const std::wstring wname = wstring::from_utf8(config.name); const std::wstring wdisplay_name = wstring::from_utf8(config.display_name); // get dependencies const std::wstring deps = wstring::pack_string_vector(config.dependencies); // create the service // as stated here https://docs.microsoft.com/en-us/windows/win32/api/winsvc/nf-winsvc-createservicew // path must be quoted if it contains a space const std::wstring binary_path = L"\"" + Win::module_name() + L"\""; ScopedSCHandle svc(::CreateServiceW( scmgr(), // SCM database wname.c_str(), // name of service wdisplay_name.c_str(), // service name to display SERVICE_ALL_ACCESS, // desired access SERVICE_WIN32_OWN_PROCESS, // service type config.autostart ? SERVICE_AUTO_START : SERVICE_DEMAND_START, // start type SERVICE_ERROR_NORMAL, // error control type binary_path.c_str(), // path to service's binary NULL, // no load ordering group NULL, // no tag identifier deps.c_str(), // dependencies NULL, // LocalSystem account NULL)); // no password if (!svc.defined()) { const Win::LastError err; OPENVPN_THROW(winsvc_error, "CreateServiceW failed: " << err.message()); } if (config.restart_on_fail) { // http://stackoverflow.com/questions/3242505/how-to-create-service-which-restarts-on-crash SERVICE_FAILURE_ACTIONS servFailActions; SC_ACTION failActions[3]; failActions[0].Type = SC_ACTION_RESTART; // Failure action: Restart Service failActions[0].Delay = 1000; // number of seconds to wait before performing failure action, in milliseconds failActions[1].Type = SC_ACTION_RESTART; failActions[1].Delay = 5000; failActions[2].Type = SC_ACTION_RESTART; failActions[2].Delay = 30000; servFailActions.dwResetPeriod = 86400; // Reset Failures Counter, in Seconds servFailActions.lpCommand = NULL; // Command to perform due to service failure, not used servFailActions.lpRebootMsg = NULL; // Message during rebooting computer due to service failure, not used servFailActions.cActions = 3; // Number of failure action to manage servFailActions.lpsaActions = failActions; ::ChangeServiceConfig2(svc(), SERVICE_CONFIG_FAILURE_ACTIONS, &servFailActions); // Apply above settings ::StartService(svc(), 0, NULL); } } void remove() { // convert service name to wide string const std::wstring wname = wstring::from_utf8(config.name); // open service control manager ScopedSCHandle scmgr(::OpenSCManagerW( NULL, // local computer NULL, // ServicesActive database SC_MANAGER_ALL_ACCESS)); // full access rights if (!scmgr.defined()) { const Win::LastError err; OPENVPN_THROW(winsvc_error, "OpenSCManagerW failed: " << err.message()); } // open the service ScopedSCHandle svc(::OpenServiceW( scmgr(), // SCM database wname.c_str(), // name of service SC_MANAGER_ALL_ACCESS)); // access requested if (!svc.defined()) { const Win::LastError err; OPENVPN_THROW(winsvc_error, "OpenServiceW failed: " << err.message()); } // remove the service if (!::DeleteService(svc())) { const Win::LastError err; OPENVPN_THROW(winsvc_error, "DeleteService failed: " << err.message()); } } void start() { service = this; // convert service name to wide string const std::wstring wname = wstring::from_utf8(config.name); // store it in a raw wchar_t array std::unique_ptr wname_raw = wstring::to_wchar_t(wname); const SERVICE_TABLE_ENTRYW dispatch_table[] = { {wname_raw.get(), (LPSERVICE_MAIN_FUNCTIONW)svc_main_static}, {NULL, NULL}}; // This call returns when the service has stopped. // The process should simply terminate when the call returns. if (!::StartServiceCtrlDispatcherW(dispatch_table)) { const Win::LastError err; OPENVPN_THROW(winsvc_error, "StartServiceCtrlDispatcherW failed: " << err.message()); } } void report_service_running() { if (is_service()) report_service_status(SERVICE_RUNNING, NO_ERROR, 0); } // The work of the service. virtual void service_work(DWORD argc, LPWSTR *argv) = 0; // Called by service control manager in another thread // to signal the service_work() method to exit. virtual void service_stop() = 0; private: class ScopedSCHandle { ScopedSCHandle(const ScopedSCHandle &) = delete; ScopedSCHandle &operator=(const ScopedSCHandle &) = delete; public: ScopedSCHandle() : handle(nullptr) { } explicit ScopedSCHandle(SC_HANDLE h) : handle(h) { } SC_HANDLE release() { const SC_HANDLE ret = handle; handle = nullptr; return ret; } bool defined() const { return bool(handle); } SC_HANDLE operator()() const { return handle; } SC_HANDLE *ref() { return &handle; } void reset(SC_HANDLE h) { close(); handle = h; } // unusual semantics: replace handle without closing it first void replace(SC_HANDLE h) { handle = h; } bool close() { if (defined()) { const BOOL ret = ::CloseServiceHandle(handle); handle = nullptr; return ret != 0; } else return true; } ~ScopedSCHandle() { close(); } ScopedSCHandle(ScopedSCHandle &&other) noexcept { handle = other.handle; other.handle = nullptr; } ScopedSCHandle &operator=(ScopedSCHandle &&other) noexcept { close(); handle = other.handle; other.handle = nullptr; return *this; } private: SC_HANDLE handle; }; static VOID WINAPI svc_main_static(DWORD argc, LPWSTR *argv) { service->svc_main(argc, argv); } void svc_main(DWORD argc, LPWSTR *argv) { try { // convert service name to wide string const std::wstring wname = wstring::from_utf8(config.name); // Register the handler function for the service status_handle = ::RegisterServiceCtrlHandlerW( wname.c_str(), svc_ctrl_handler_static); if (!status_handle) { const Win::LastError err; OPENVPN_THROW(winsvc_error, "RegisterServiceCtrlHandlerW failed: " << err.message()); } // These SERVICE_STATUS members remain as set here status.dwServiceType = SERVICE_WIN32_OWN_PROCESS; status.dwServiceSpecificExitCode = 0; // Report initial status to the SCM report_service_status(SERVICE_START_PENDING, NO_ERROR, 0); // Perform service-specific initialization and work service_work(argc, argv); // Tell SCM we are done report_service_status(SERVICE_STOPPED, NO_ERROR, 0); } catch (const std::exception &e) { OPENVPN_LOG("service exception: " << e.what()); report_service_status(SERVICE_STOPPED, NO_ERROR, 0); } } // Purpose: // Called by SCM whenever a control code is sent to the service // using the ControlService function. // // Parameters: // dwCtrl - control code void svc_ctrl_handler(DWORD dwCtrl) { // Handle the requested control code. switch (dwCtrl) { case SERVICE_CONTROL_STOP: report_service_status(SERVICE_STOP_PENDING, NO_ERROR, 0); // Signal the service to stop. try { service_stop(); } catch (const std::exception &e) { OPENVPN_LOG("service stop exception: " << e.what()); } report_service_status(0, NO_ERROR, 0); return; case SERVICE_CONTROL_INTERROGATE: break; default: break; } } static VOID WINAPI svc_ctrl_handler_static(DWORD dwCtrl) { service->svc_ctrl_handler(dwCtrl); } // Purpose: // Sets the current service status and reports it to the SCM. // // Parameters: // dwCurrentState - The current state (see SERVICE_STATUS) // dwWin32ExitCode - The system error code // dwWaitHint - Estimated time for pending operation, in milliseconds void report_service_status(DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD dwWaitHint) { std::lock_guard lock(mutex); // Fill in the SERVICE_STATUS structure. if (dwCurrentState) status.dwCurrentState = dwCurrentState; status.dwWin32ExitCode = dwWin32ExitCode; status.dwWaitHint = dwWaitHint; if (dwCurrentState == SERVICE_START_PENDING) status.dwControlsAccepted = 0; else status.dwControlsAccepted = SERVICE_ACCEPT_STOP; if ((dwCurrentState == SERVICE_RUNNING) || (dwCurrentState == SERVICE_STOPPED)) status.dwCheckPoint = 0; else status.dwCheckPoint = checkpoint++; // Report the status of the service to the SCM. ::SetServiceStatus(status_handle, &status); } static Service *service; // GLOBAL const Config config; SERVICE_STATUS status; SERVICE_STATUS_HANDLE status_handle; DWORD checkpoint; std::mutex mutex; }; Service *Service::service = nullptr; // GLOBAL } // namespace openvpn::Win