OpenVPN 3 Core Library
Loading...
Searching...
No Matches
winsvc.hpp
Go to the documentation of this file.
1// OpenVPN -- An application to securely tunnel IP networks
2// over a single port, with support for SSL/TLS-based
3// session authentication and key exchange,
4// packet encryption, packet authentication, and
5// packet compression.
6//
7// Copyright (C) 2012- OpenVPN Inc.
8//
9// SPDX-License-Identifier: MPL-2.0 OR AGPL-3.0-only WITH openvpn3-openssl-exception
10//
11
12#pragma once
13
14// windows service utilities
15
16#include <windows.h>
17
18#include <string>
19#include <cstring>
20#include <vector>
21#include <memory>
22#include <mutex>
23
28
29namespace openvpn::Win {
31{
32 public:
33 OPENVPN_EXCEPTION(winsvc_error);
34
35 struct Config
36 {
37 std::string name;
38 std::string display_name;
39 std::vector<std::string> dependencies;
40 bool autostart = false;
41 bool restart_on_fail = false;
42 };
43
44 Service(const Config &config_arg)
45 : config(config_arg)
46 {
47 std::memset(&status, 0, sizeof(status));
48 status_handle = nullptr;
49 checkpoint = 1;
50 }
51
52 virtual ~Service() = default;
53
54 bool is_service() const
55 {
56 return bool(service);
57 }
58
59 void install()
60 {
61 // open service control manager
62 ScopedSCHandle scmgr(::OpenSCManagerW(
63 NULL, // local computer
64 NULL, // ServicesActive database
65 SC_MANAGER_ALL_ACCESS)); // full access rights
66 if (!scmgr.defined())
67 {
68 const Win::LastError err;
69 OPENVPN_THROW(winsvc_error, "OpenSCManagerW failed: " << err.message());
70 }
71
72 // convert service names to wide string
73 const std::wstring wname = wstring::from_utf8(config.name);
74 const std::wstring wdisplay_name = wstring::from_utf8(config.display_name);
75
76 // get dependencies
77 const std::wstring deps = wstring::pack_string_vector(config.dependencies);
78
79 // create the service
80
81 // as stated here https://docs.microsoft.com/en-us/windows/win32/api/winsvc/nf-winsvc-createservicew
82 // path must be quoted if it contains a space
83 const std::wstring binary_path = L"\"" + Win::module_name() + L"\"";
84 ScopedSCHandle svc(::CreateServiceW(
85 scmgr(), // SCM database
86 wname.c_str(), // name of service
87 wdisplay_name.c_str(), // service name to display
88 SERVICE_ALL_ACCESS, // desired access
89 SERVICE_WIN32_OWN_PROCESS, // service type
90 config.autostart ? SERVICE_AUTO_START : SERVICE_DEMAND_START, // start type
91 SERVICE_ERROR_NORMAL, // error control type
92 binary_path.c_str(), // path to service's binary
93 NULL, // no load ordering group
94 NULL, // no tag identifier
95 deps.c_str(), // dependencies
96 NULL, // LocalSystem account
97 NULL)); // no password
98 if (!svc.defined())
99 {
100 const Win::LastError err;
101 OPENVPN_THROW(winsvc_error, "CreateServiceW failed: " << err.message());
102 }
104 {
105 // http://stackoverflow.com/questions/3242505/how-to-create-service-which-restarts-on-crash
106 SERVICE_FAILURE_ACTIONS servFailActions;
107 SC_ACTION failActions[3];
108
109 failActions[0].Type = SC_ACTION_RESTART; // Failure action: Restart Service
110 failActions[0].Delay = 1000; // number of seconds to wait before performing failure action, in milliseconds
111 failActions[1].Type = SC_ACTION_RESTART;
112 failActions[1].Delay = 5000;
113 failActions[2].Type = SC_ACTION_RESTART;
114 failActions[2].Delay = 30000;
115
116 servFailActions.dwResetPeriod = 86400; // Reset Failures Counter, in Seconds
117 servFailActions.lpCommand = NULL; // Command to perform due to service failure, not used
118 servFailActions.lpRebootMsg = NULL; // Message during rebooting computer due to service failure, not used
119 servFailActions.cActions = 3; // Number of failure action to manage
120 servFailActions.lpsaActions = failActions;
121
122 ::ChangeServiceConfig2(svc(), SERVICE_CONFIG_FAILURE_ACTIONS, &servFailActions); // Apply above settings
123 ::StartService(svc(), 0, NULL);
124 }
125 }
126
127 void remove()
128 {
129 // convert service name to wide string
130 const std::wstring wname = wstring::from_utf8(config.name);
131
132 // open service control manager
133 ScopedSCHandle scmgr(::OpenSCManagerW(
134 NULL, // local computer
135 NULL, // ServicesActive database
136 SC_MANAGER_ALL_ACCESS)); // full access rights
137 if (!scmgr.defined())
138 {
139 const Win::LastError err;
140 OPENVPN_THROW(winsvc_error, "OpenSCManagerW failed: " << err.message());
141 }
142
143 // open the service
144 ScopedSCHandle svc(::OpenServiceW(
145 scmgr(), // SCM database
146 wname.c_str(), // name of service
147 SC_MANAGER_ALL_ACCESS)); // access requested
148 if (!svc.defined())
149 {
150 const Win::LastError err;
151 OPENVPN_THROW(winsvc_error, "OpenServiceW failed: " << err.message());
152 }
153
154 // remove the service
155 if (!::DeleteService(svc()))
156 {
157 const Win::LastError err;
158 OPENVPN_THROW(winsvc_error, "DeleteService failed: " << err.message());
159 }
160 }
161
162 void start()
163 {
164 service = this;
165
166 // convert service name to wide string
167 std::wstring wname = wstring::from_utf8(config.name);
168
169 const SERVICE_TABLE_ENTRYW dispatch_table[] = {
170 {wname.data(), (LPSERVICE_MAIN_FUNCTIONW)svc_main_static},
171 {NULL, NULL}};
172
173 // This call returns when the service has stopped.
174 // The process should simply terminate when the call returns.
175 if (!::StartServiceCtrlDispatcherW(dispatch_table))
176 {
177 const Win::LastError err;
178 OPENVPN_THROW(winsvc_error, "StartServiceCtrlDispatcherW failed: " << err.message());
179 }
180 }
181
183 {
184 if (is_service())
185 report_service_status(SERVICE_RUNNING, NO_ERROR, 0);
186 }
187
188 // The work of the service.
189 virtual void service_work(DWORD argc, LPWSTR *argv) = 0;
190
191 // Called by service control manager in another thread
192 // to signal the service_work() method to exit.
193 virtual void service_stop() = 0;
194
195 private:
197 {
200
201 public:
203 : handle(nullptr)
204 {
205 }
206
207 explicit ScopedSCHandle(SC_HANDLE h)
208 : handle(h)
209 {
210 }
211
212 SC_HANDLE release()
213 {
214 const SC_HANDLE ret = handle;
215 handle = nullptr;
216 return ret;
217 }
218
219 bool defined() const
220 {
221 return bool(handle);
222 }
223
224 SC_HANDLE operator()() const
225 {
226 return handle;
227 }
228
229 SC_HANDLE *ref()
230 {
231 return &handle;
232 }
233
234 void reset(SC_HANDLE h)
235 {
236 close();
237 handle = h;
238 }
239
240 // unusual semantics: replace handle without closing it first
241 void replace(SC_HANDLE h)
242 {
243 handle = h;
244 }
245
246 bool close()
247 {
248 if (defined())
249 {
250 const BOOL ret = ::CloseServiceHandle(handle);
251 handle = nullptr;
252 return ret != 0;
253 }
254 else
255 return true;
256 }
257
259 {
260 close();
261 }
262
264 {
265 handle = other.handle;
266 other.handle = nullptr;
267 }
268
270 {
271 close();
272 handle = other.handle;
273 other.handle = nullptr;
274 return *this;
275 }
276
277 private:
278 SC_HANDLE handle;
279 };
280
281 static VOID WINAPI svc_main_static(DWORD argc, LPWSTR *argv)
282 {
283 service->svc_main(argc, argv);
284 }
285
286 void svc_main(DWORD argc, LPWSTR *argv)
287 {
288 try
289 {
290 // convert service name to wide string
291 const std::wstring wname = wstring::from_utf8(config.name);
292
293 // Register the handler function for the service
294 status_handle = ::RegisterServiceCtrlHandlerW(
295 wname.c_str(),
297 if (!status_handle)
298 {
299 const Win::LastError err;
300 OPENVPN_THROW(winsvc_error, "RegisterServiceCtrlHandlerW failed: " << err.message());
301 }
302
303 // These SERVICE_STATUS members remain as set here
304 status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
305 status.dwServiceSpecificExitCode = 0;
306
307 // Report initial status to the SCM
308 report_service_status(SERVICE_START_PENDING, NO_ERROR, 0);
309
310 // Perform service-specific initialization and work
311 service_work(argc, argv);
312
313 // Tell SCM we are done
314 report_service_status(SERVICE_STOPPED, NO_ERROR, 0);
315 }
316 catch (const std::exception &e)
317 {
318 OPENVPN_LOG("service exception: " << e.what());
319 report_service_status(SERVICE_STOPPED, NO_ERROR, 0);
320 }
321 }
322
323 // Purpose:
324 // Called by SCM whenever a control code is sent to the service
325 // using the ControlService function.
326 //
327 // Parameters:
328 // dwCtrl - control code
329 void svc_ctrl_handler(DWORD dwCtrl)
330 {
331 // Handle the requested control code.
332 switch (dwCtrl)
333 {
334 case SERVICE_CONTROL_STOP:
335 report_service_status(SERVICE_STOP_PENDING, NO_ERROR, 0);
336
337 // Signal the service to stop.
338 try
339 {
340 service_stop();
341 }
342 catch (const std::exception &e)
343 {
344 OPENVPN_LOG("service stop exception: " << e.what());
345 }
346 report_service_status(0, NO_ERROR, 0);
347 return;
348
349 case SERVICE_CONTROL_INTERROGATE:
350 break;
351
352 default:
353 break;
354 }
355 }
356
357 static VOID WINAPI svc_ctrl_handler_static(DWORD dwCtrl)
358 {
359 service->svc_ctrl_handler(dwCtrl);
360 }
361
362 // Purpose:
363 // Sets the current service status and reports it to the SCM.
364 //
365 // Parameters:
366 // dwCurrentState - The current state (see SERVICE_STATUS)
367 // dwWin32ExitCode - The system error code
368 // dwWaitHint - Estimated time for pending operation, in milliseconds
369 void report_service_status(DWORD dwCurrentState,
370 DWORD dwWin32ExitCode,
371 DWORD dwWaitHint)
372 {
373 std::lock_guard<std::mutex> lock(mutex);
374
375 // Fill in the SERVICE_STATUS structure.
376 if (dwCurrentState)
377 status.dwCurrentState = dwCurrentState;
378 status.dwWin32ExitCode = dwWin32ExitCode;
379 status.dwWaitHint = dwWaitHint;
380
381 if (dwCurrentState == SERVICE_START_PENDING)
382 status.dwControlsAccepted = 0;
383 else
384 status.dwControlsAccepted = SERVICE_ACCEPT_STOP;
385 if ((dwCurrentState == SERVICE_RUNNING) || (dwCurrentState == SERVICE_STOPPED))
386 status.dwCheckPoint = 0;
387 else
388 status.dwCheckPoint = checkpoint++;
389
390 // Report the status of the service to the SCM.
391 ::SetServiceStatus(status_handle, &status);
392 }
393
394 static Service *service; // GLOBAL
395
397
398 SERVICE_STATUS status;
399 SERVICE_STATUS_HANDLE status_handle;
401
402 std::mutex mutex;
403};
404
405Service *Service::service = nullptr; // GLOBAL
406} // namespace openvpn::Win
ScopedSCHandle & operator=(ScopedSCHandle &&other) noexcept
Definition winsvc.hpp:269
ScopedSCHandle(const ScopedSCHandle &)=delete
ScopedSCHandle(ScopedSCHandle &&other) noexcept
Definition winsvc.hpp:263
ScopedSCHandle & operator=(const ScopedSCHandle &)=delete
const Config config
Definition winsvc.hpp:396
static VOID WINAPI svc_ctrl_handler_static(DWORD dwCtrl)
Definition winsvc.hpp:357
SERVICE_STATUS_HANDLE status_handle
Definition winsvc.hpp:399
void svc_main(DWORD argc, LPWSTR *argv)
Definition winsvc.hpp:286
void svc_ctrl_handler(DWORD dwCtrl)
Definition winsvc.hpp:329
static Service * service
Definition winsvc.hpp:394
Service(const Config &config_arg)
Definition winsvc.hpp:44
virtual void service_work(DWORD argc, LPWSTR *argv)=0
void report_service_status(DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD dwWaitHint)
Definition winsvc.hpp:369
OPENVPN_EXCEPTION(winsvc_error)
virtual void service_stop()=0
bool is_service() const
Definition winsvc.hpp:54
virtual ~Service()=default
void report_service_running()
Definition winsvc.hpp:182
SERVICE_STATUS status
Definition winsvc.hpp:398
static VOID WINAPI svc_main_static(DWORD argc, LPWSTR *argv)
Definition winsvc.hpp:281
#define OPENVPN_THROW(exc, stuff)
#define OPENVPN_LOG(args)
std::wstring module_name()
Definition modname.hpp:29
std::vector< std::string > dependencies
Definition winsvc.hpp:39
std::string ret
std::vector< std::complex< double > > svc