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 const std::wstring wname = wstring::from_utf8(config.name);
168
169 // store it in a raw wchar_t array
170 std::unique_ptr<wchar_t[]> wname_raw = wstring::to_wchar_t(wname);
171
172 const SERVICE_TABLE_ENTRYW dispatch_table[] = {
173 {wname_raw.get(), (LPSERVICE_MAIN_FUNCTIONW)svc_main_static},
174 {NULL, NULL}};
175
176 // This call returns when the service has stopped.
177 // The process should simply terminate when the call returns.
178 if (!::StartServiceCtrlDispatcherW(dispatch_table))
179 {
180 const Win::LastError err;
181 OPENVPN_THROW(winsvc_error, "StartServiceCtrlDispatcherW failed: " << err.message());
182 }
183 }
184
186 {
187 if (is_service())
188 report_service_status(SERVICE_RUNNING, NO_ERROR, 0);
189 }
190
191 // The work of the service.
192 virtual void service_work(DWORD argc, LPWSTR *argv) = 0;
193
194 // Called by service control manager in another thread
195 // to signal the service_work() method to exit.
196 virtual void service_stop() = 0;
197
198 private:
200 {
203
204 public:
206 : handle(nullptr)
207 {
208 }
209
210 explicit ScopedSCHandle(SC_HANDLE h)
211 : handle(h)
212 {
213 }
214
215 SC_HANDLE release()
216 {
217 const SC_HANDLE ret = handle;
218 handle = nullptr;
219 return ret;
220 }
221
222 bool defined() const
223 {
224 return bool(handle);
225 }
226
227 SC_HANDLE operator()() const
228 {
229 return handle;
230 }
231
232 SC_HANDLE *ref()
233 {
234 return &handle;
235 }
236
237 void reset(SC_HANDLE h)
238 {
239 close();
240 handle = h;
241 }
242
243 // unusual semantics: replace handle without closing it first
244 void replace(SC_HANDLE h)
245 {
246 handle = h;
247 }
248
249 bool close()
250 {
251 if (defined())
252 {
253 const BOOL ret = ::CloseServiceHandle(handle);
254 handle = nullptr;
255 return ret != 0;
256 }
257 else
258 return true;
259 }
260
262 {
263 close();
264 }
265
267 {
268 handle = other.handle;
269 other.handle = nullptr;
270 }
271
273 {
274 close();
275 handle = other.handle;
276 other.handle = nullptr;
277 return *this;
278 }
279
280 private:
281 SC_HANDLE handle;
282 };
283
284 static VOID WINAPI svc_main_static(DWORD argc, LPWSTR *argv)
285 {
286 service->svc_main(argc, argv);
287 }
288
289 void svc_main(DWORD argc, LPWSTR *argv)
290 {
291 try
292 {
293 // convert service name to wide string
294 const std::wstring wname = wstring::from_utf8(config.name);
295
296 // Register the handler function for the service
297 status_handle = ::RegisterServiceCtrlHandlerW(
298 wname.c_str(),
300 if (!status_handle)
301 {
302 const Win::LastError err;
303 OPENVPN_THROW(winsvc_error, "RegisterServiceCtrlHandlerW failed: " << err.message());
304 }
305
306 // These SERVICE_STATUS members remain as set here
307 status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
308 status.dwServiceSpecificExitCode = 0;
309
310 // Report initial status to the SCM
311 report_service_status(SERVICE_START_PENDING, NO_ERROR, 0);
312
313 // Perform service-specific initialization and work
314 service_work(argc, argv);
315
316 // Tell SCM we are done
317 report_service_status(SERVICE_STOPPED, NO_ERROR, 0);
318 }
319 catch (const std::exception &e)
320 {
321 OPENVPN_LOG("service exception: " << e.what());
322 report_service_status(SERVICE_STOPPED, NO_ERROR, 0);
323 }
324 }
325
326 // Purpose:
327 // Called by SCM whenever a control code is sent to the service
328 // using the ControlService function.
329 //
330 // Parameters:
331 // dwCtrl - control code
332 void svc_ctrl_handler(DWORD dwCtrl)
333 {
334 // Handle the requested control code.
335 switch (dwCtrl)
336 {
337 case SERVICE_CONTROL_STOP:
338 report_service_status(SERVICE_STOP_PENDING, NO_ERROR, 0);
339
340 // Signal the service to stop.
341 try
342 {
343 service_stop();
344 }
345 catch (const std::exception &e)
346 {
347 OPENVPN_LOG("service stop exception: " << e.what());
348 }
349 report_service_status(0, NO_ERROR, 0);
350 return;
351
352 case SERVICE_CONTROL_INTERROGATE:
353 break;
354
355 default:
356 break;
357 }
358 }
359
360 static VOID WINAPI svc_ctrl_handler_static(DWORD dwCtrl)
361 {
362 service->svc_ctrl_handler(dwCtrl);
363 }
364
365 // Purpose:
366 // Sets the current service status and reports it to the SCM.
367 //
368 // Parameters:
369 // dwCurrentState - The current state (see SERVICE_STATUS)
370 // dwWin32ExitCode - The system error code
371 // dwWaitHint - Estimated time for pending operation, in milliseconds
372 void report_service_status(DWORD dwCurrentState,
373 DWORD dwWin32ExitCode,
374 DWORD dwWaitHint)
375 {
376 std::lock_guard<std::mutex> lock(mutex);
377
378 // Fill in the SERVICE_STATUS structure.
379 if (dwCurrentState)
380 status.dwCurrentState = dwCurrentState;
381 status.dwWin32ExitCode = dwWin32ExitCode;
382 status.dwWaitHint = dwWaitHint;
383
384 if (dwCurrentState == SERVICE_START_PENDING)
385 status.dwControlsAccepted = 0;
386 else
387 status.dwControlsAccepted = SERVICE_ACCEPT_STOP;
388 if ((dwCurrentState == SERVICE_RUNNING) || (dwCurrentState == SERVICE_STOPPED))
389 status.dwCheckPoint = 0;
390 else
391 status.dwCheckPoint = checkpoint++;
392
393 // Report the status of the service to the SCM.
394 ::SetServiceStatus(status_handle, &status);
395 }
396
397 static Service *service; // GLOBAL
398
400
401 SERVICE_STATUS status;
402 SERVICE_STATUS_HANDLE status_handle;
404
405 std::mutex mutex;
406};
407
408Service *Service::service = nullptr; // GLOBAL
409} // namespace openvpn::Win
ScopedSCHandle & operator=(ScopedSCHandle &&other) noexcept
Definition winsvc.hpp:272
ScopedSCHandle(const ScopedSCHandle &)=delete
ScopedSCHandle(ScopedSCHandle &&other) noexcept
Definition winsvc.hpp:266
ScopedSCHandle & operator=(const ScopedSCHandle &)=delete
const Config config
Definition winsvc.hpp:399
static VOID WINAPI svc_ctrl_handler_static(DWORD dwCtrl)
Definition winsvc.hpp:360
SERVICE_STATUS_HANDLE status_handle
Definition winsvc.hpp:402
void svc_main(DWORD argc, LPWSTR *argv)
Definition winsvc.hpp:289
void svc_ctrl_handler(DWORD dwCtrl)
Definition winsvc.hpp:332
static Service * service
Definition winsvc.hpp:397
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:372
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:185
SERVICE_STATUS status
Definition winsvc.hpp:401
static VOID WINAPI svc_main_static(DWORD argc, LPWSTR *argv)
Definition winsvc.hpp:284
#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