OpenVPN
automatic.c
Go to the documentation of this file.
1 /*
2  * OpenVPN -- An application to securely tunnel IP networks
3  * over a single TCP/UDP port, with support for SSL/TLS-based
4  * session authentication and key exchange,
5  * packet encryption, packet authentication, and
6  * packet compression.
7  *
8  * Copyright (C) 2002-2018 OpenVPN Inc <sales@openvpn.net>
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License version 2
12  * as published by the Free Software Foundation.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License along
20  * with this program; if not, write to the Free Software Foundation, Inc.,
21  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22  */
23 
24 /*
25  * This program allows one or more OpenVPN processes to be started
26  * as a service. To build, you must get the service sample from the
27  * Platform SDK and replace Simple.c with this file.
28  *
29  * You should also apply service.patch to
30  * service.c and service.h from the Platform SDK service sample.
31  *
32  * This code is designed to be built with the mingw compiler.
33  */
34 
35 #include "service.h"
36 
37 #include <stdio.h>
38 #include <stdarg.h>
39 #include <stdbool.h>
40 #include <process.h>
41 
42 static SERVICE_STATUS_HANDLE service;
43 static SERVICE_STATUS status = { .dwServiceType = SERVICE_WIN32_SHARE_PROCESS };
44 
46  automatic,
47  TEXT(PACKAGE_NAME "ServiceLegacy"),
48  TEXT(PACKAGE_NAME " Legacy Service"),
50  SERVICE_DEMAND_START
51 };
52 
54 {
55  SECURITY_ATTRIBUTES sa;
56  SECURITY_DESCRIPTOR sd;
57 };
58 
59 static HANDLE exit_event = NULL;
60 
61 /* clear an object */
62 #define CLEAR(x) memset(&(x), 0, sizeof(x))
63 
64 
65 bool
67 {
68  CLEAR(*obj);
69 
70  obj->sa.nLength = sizeof(SECURITY_ATTRIBUTES);
71  obj->sa.lpSecurityDescriptor = &obj->sd;
72  obj->sa.bInheritHandle = TRUE;
73  if (!InitializeSecurityDescriptor(&obj->sd, SECURITY_DESCRIPTOR_REVISION))
74  {
75  return false;
76  }
77  if (!SetSecurityDescriptorDacl(&obj->sd, TRUE, NULL, FALSE))
78  {
79  return false;
80  }
81  return true;
82 }
83 
84 HANDLE
85 create_event(LPCTSTR name, bool allow_all, bool initial_state, bool manual_reset)
86 {
87  if (allow_all)
88  {
89  struct security_attributes sa;
91  {
92  return NULL;
93  }
94  return CreateEvent(&sa.sa, (BOOL)manual_reset, (BOOL)initial_state, name);
95  }
96  else
97  {
98  return CreateEvent(NULL, (BOOL)manual_reset, (BOOL)initial_state, name);
99  }
100 }
101 
102 void
103 close_if_open(HANDLE h)
104 {
105  if (h != NULL)
106  {
107  CloseHandle(h);
108  }
109 }
110 
111 static bool
112 match(const WIN32_FIND_DATA *find, LPCTSTR ext)
113 {
114  if (find->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
115  {
116  return false;
117  }
118 
119  if (*ext == TEXT('\0'))
120  {
121  return true;
122  }
123 
124  /* find the pointer to that last '.' in filename and match ext against the rest */
125 
126  const TCHAR *p = _tcsrchr(find->cFileName, TEXT('.'));
127  return p && p != find->cFileName && _tcsicmp(p + 1, ext) == 0;
128 }
129 
130 /*
131  * Modify the extension on a filename.
132  */
133 static bool
134 modext(LPTSTR dest, size_t size, LPCTSTR src, LPCTSTR newext)
135 {
136  size_t i;
137 
138  if (size > 0 && (_tcslen(src) + 1) <= size)
139  {
140  _tcscpy_s(dest, size, src);
141  dest [size - 1] = TEXT('\0');
142  i = _tcslen(dest);
143  while (i-- > 0)
144  {
145  if (dest[i] == TEXT('\\'))
146  {
147  break;
148  }
149  if (dest[i] == TEXT('.'))
150  {
151  dest[i] = TEXT('\0');
152  break;
153  }
154  }
155  if (_tcslen(dest) + _tcslen(newext) + 2 <= size)
156  {
157  _tcscat_s(dest, size, TEXT("."));
158  _tcscat_s(dest, size, newext);
159  return true;
160  }
161  dest[0] = TEXT('\0');
162  }
163  return false;
164 }
165 
166 static DWORD WINAPI
167 ServiceCtrlAutomatic(DWORD ctrl_code, DWORD event, LPVOID data, LPVOID ctx)
168 {
169  SERVICE_STATUS *status = ctx;
170  switch (ctrl_code)
171  {
172  case SERVICE_CONTROL_STOP:
173  status->dwCurrentState = SERVICE_STOP_PENDING;
174  ReportStatusToSCMgr(service, status);
175  if (exit_event)
176  {
177  SetEvent(exit_event);
178  }
179  return NO_ERROR;
180 
181  case SERVICE_CONTROL_INTERROGATE:
182  return NO_ERROR;
183 
184  default:
185  return ERROR_CALL_NOT_IMPLEMENTED;
186  }
187 }
188 
189 
190 VOID WINAPI
191 ServiceStartAutomaticOwn(DWORD dwArgc, LPTSTR *lpszArgv)
192 {
193  status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
194  ServiceStartAutomatic(dwArgc, lpszArgv);
195 }
196 
197 
198 VOID WINAPI
199 ServiceStartAutomatic(DWORD dwArgc, LPTSTR *lpszArgv)
200 {
201  DWORD error = NO_ERROR;
203  TCHAR event_name[256];
204 
205  service = RegisterServiceCtrlHandlerEx(automatic_service.name, ServiceCtrlAutomatic, &status);
206  if (!service)
207  {
208  return;
209  }
210 
211  status.dwCurrentState = SERVICE_START_PENDING;
212  status.dwServiceSpecificExitCode = NO_ERROR;
213  status.dwWin32ExitCode = NO_ERROR;
214  status.dwWaitHint = 3000;
215 
217  {
218  MsgToEventLog(M_ERR, TEXT("ReportStatusToSCMgr #1 failed"));
219  goto finish;
220  }
221 
222  /*
223  * Create our exit event
224  * This event is initially created in the non-signaled
225  * state. It will transition to the signaled state when
226  * we have received a terminate signal from the Service
227  * Control Manager which will cause an asynchronous call
228  * of ServiceStop below.
229  */
230 
231  openvpn_sntprintf(event_name, _countof(event_name), TEXT(PACKAGE "%s_exit_1"), service_instance);
232  exit_event = create_event(event_name, false, false, true);
233  if (!exit_event)
234  {
235  MsgToEventLog(M_ERR, TEXT("CreateEvent failed"));
236  goto finish;
237  }
238 
239  /*
240  * If exit event is already signaled, it means we were not
241  * shut down properly.
242  */
243  if (WaitForSingleObject(exit_event, 0) != WAIT_TIMEOUT)
244  {
245  MsgToEventLog(M_ERR, TEXT("Exit event is already signaled -- we were not shut down properly"));
246  goto finish;
247  }
248 
250  {
251  MsgToEventLog(M_ERR, TEXT("ReportStatusToSCMgr #2 failed"));
252  goto finish;
253  }
254 
255  /*
256  * Read info from registry in key HKLM\SOFTWARE\OpenVPN
257  */
258  error = GetOpenvpnSettings(&settings);
259  if (error != ERROR_SUCCESS)
260  {
261  goto finish;
262  }
263 
264  /*
265  * Instantiate an OpenVPN process for each configuration
266  * file found.
267  */
268  {
269  WIN32_FIND_DATA find_obj;
270  HANDLE find_handle;
271  BOOL more_files;
272  TCHAR find_string[MAX_PATH];
273 
274  openvpn_sntprintf(find_string, _countof(find_string), TEXT("%s\\*"), settings.config_dir);
275 
276  find_handle = FindFirstFile(find_string, &find_obj);
277  if (find_handle == INVALID_HANDLE_VALUE)
278  {
279  MsgToEventLog(M_ERR, TEXT("Cannot get configuration file list using: %s"), find_string);
280  goto finish;
281  }
282 
283  /*
284  * Loop over each config file
285  */
286  do
287  {
288  HANDLE log_handle = NULL;
289  STARTUPINFO start_info;
290  PROCESS_INFORMATION proc_info;
291  struct security_attributes sa;
292  TCHAR log_file[MAX_PATH];
293  TCHAR log_path[MAX_PATH];
294  TCHAR command_line[256];
295 
296  CLEAR(start_info);
297  CLEAR(proc_info);
298  CLEAR(sa);
299 
301  {
302  MsgToEventLog(M_ERR, TEXT("ReportStatusToSCMgr #3 failed"));
303  FindClose(find_handle);
304  goto finish;
305  }
306 
307  /* does file have the correct type and extension? */
308  if (match(&find_obj, settings.ext_string))
309  {
310  /* get log file pathname */
311  if (!modext(log_file, _countof(log_file), find_obj.cFileName, TEXT("log")))
312  {
313  MsgToEventLog(M_ERR, TEXT("Cannot construct logfile name based on: %s"), find_obj.cFileName);
314  FindClose(find_handle);
315  goto finish;
316  }
317  openvpn_sntprintf(log_path, _countof(log_path),
318  TEXT("%s\\%s"), settings.log_dir, log_file);
319 
320  /* construct command line */
321  openvpn_sntprintf(command_line, _countof(command_line), TEXT("openvpn --service \"" PACKAGE "%s_exit_1\" 1 --config \"%s\""),
323  find_obj.cFileName);
324 
325  /* Make security attributes struct for logfile handle so it can
326  * be inherited. */
328  {
329  error = MsgToEventLog(M_SYSERR, TEXT("InitializeSecurityDescriptor start_" PACKAGE " failed"));
330  goto finish;
331  }
332 
333  /* open logfile as stdout/stderr for soon-to-be-spawned subprocess */
334  log_handle = CreateFile(log_path,
335  GENERIC_WRITE,
336  FILE_SHARE_READ,
337  &sa.sa,
338  settings.append ? OPEN_ALWAYS : CREATE_ALWAYS,
339  FILE_ATTRIBUTE_NORMAL,
340  NULL);
341 
342  if (log_handle == INVALID_HANDLE_VALUE)
343  {
344  error = MsgToEventLog(M_SYSERR, TEXT("Cannot open logfile: %s"), log_path);
345  FindClose(find_handle);
346  goto finish;
347  }
348 
349  /* append to logfile? */
350  if (settings.append)
351  {
352  if (SetFilePointer(log_handle, 0, NULL, FILE_END) == INVALID_SET_FILE_POINTER)
353  {
354  error = MsgToEventLog(M_SYSERR, TEXT("Cannot seek to end of logfile: %s"), log_path);
355  FindClose(find_handle);
356  goto finish;
357  }
358  }
359 
360  /* fill in STARTUPINFO struct */
361  GetStartupInfo(&start_info);
362  start_info.cb = sizeof(start_info);
363  start_info.dwFlags = STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW;
364  start_info.wShowWindow = SW_HIDE;
365  start_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
366  start_info.hStdOutput = start_info.hStdError = log_handle;
367 
368  /* create an OpenVPN process for one config file */
369  if (!CreateProcess(settings.exe_path,
370  command_line,
371  NULL,
372  NULL,
373  TRUE,
374  settings.priority | CREATE_NEW_CONSOLE,
375  NULL,
376  settings.config_dir,
377  &start_info,
378  &proc_info))
379  {
380  error = MsgToEventLog(M_SYSERR, TEXT("CreateProcess failed, exe='%s' cmdline='%s' dir='%s'"),
381  settings.exe_path,
382  command_line,
383  settings.config_dir);
384 
385  FindClose(find_handle);
386  CloseHandle(log_handle);
387  goto finish;
388  }
389 
390  /* close unneeded handles */
391  Sleep(1000); /* try to prevent race if we close logfile
392  * handle before child process DUPs it */
393  if (!CloseHandle(proc_info.hProcess)
394  || !CloseHandle(proc_info.hThread)
395  || !CloseHandle(log_handle))
396  {
397  error = MsgToEventLog(M_SYSERR, TEXT("CloseHandle failed"));
398  goto finish;
399  }
400  }
401 
402  /* more files to process? */
403  more_files = FindNextFile(find_handle, &find_obj);
404 
405  } while (more_files);
406 
407  FindClose(find_handle);
408  }
409 
410  /* we are now fully started */
411  status.dwCurrentState = SERVICE_RUNNING;
412  status.dwWaitHint = 0;
414  {
415  MsgToEventLog(M_ERR, TEXT("ReportStatusToSCMgr SERVICE_RUNNING failed"));
416  goto finish;
417  }
418 
419  /* wait for our shutdown signal */
420  if (WaitForSingleObject(exit_event, INFINITE) != WAIT_OBJECT_0)
421  {
422  MsgToEventLog(M_ERR, TEXT("wait for shutdown signal failed"));
423  }
424 
425 finish:
426  if (exit_event)
427  {
428  CloseHandle(exit_event);
429  }
430 
431  status.dwCurrentState = SERVICE_STOPPED;
432  status.dwWin32ExitCode = error;
434 }
BOOL openvpn_sntprintf(LPTSTR str, size_t size, LPCTSTR format,...)
Definition: common.c:47
VOID WINAPI ServiceStartAutomatic(DWORD dwArgc, LPTSTR *lpszArgv)
Definition: automatic.c:199
DWORD MsgToEventLog(DWORD flags, LPCTSTR format,...)
Definition: common.c:260
openvpn_service_t automatic_service
Definition: automatic.c:45
LPCTSTR service_instance
Definition: common.c:27
TCHAR config_dir[MAX_PATH]
Definition: service.h:67
VOID WINAPI ServiceStartAutomaticOwn(DWORD dwArgc, LPTSTR *lpszArgv)
Definition: automatic.c:191
TCHAR exe_path[MAX_PATH]
Definition: service.h:66
static HANDLE exit_event
Definition: automatic.c:59
BOOL append
Definition: service.h:72
static bool match(const WIN32_FIND_DATA *find, LPCTSTR ext)
Definition: automatic.c:112
DWORD priority
Definition: service.h:71
TCHAR ext_string[16]
Definition: service.h:68
TCHAR * name
Definition: service.h:58
static bool modext(LPTSTR dest, size_t size, LPCTSTR src, LPCTSTR newext)
Definition: automatic.c:134
#define SERVICE_DEPENDENCIES
Definition: service.h:39
#define PACKAGE_NAME
Definition: config.h:730
static DWORD WINAPI ServiceCtrlAutomatic(DWORD ctrl_code, DWORD event, LPVOID data, LPVOID ctx)
Definition: automatic.c:167
SECURITY_ATTRIBUTES sa
Definition: win32.h:60
static SERVICE_STATUS status
Definition: automatic.c:43
DWORD GetOpenvpnSettings(settings_t *s)
Definition: common.c:101
static SERVICE_STATUS_HANDLE service
Definition: automatic.c:42
HANDLE create_event(LPCTSTR name, bool allow_all, bool initial_state, bool manual_reset)
Definition: automatic.c:85
SECURITY_DESCRIPTOR sd
Definition: win32.h:61
#define M_ERR
Definition: error.h:110
static settings_t settings
Definition: interactive.c:58
static int finish(RSA *rsa)
Definition: cryptoapi.c:526
#define CLEAR(x)
Definition: automatic.c:62
#define M_SYSERR
Definition: service.h:47
TCHAR log_dir[MAX_PATH]
Definition: service.h:69
#define PACKAGE
Definition: config.h:724
void close_if_open(HANDLE h)
Definition: automatic.c:103
BOOL ReportStatusToSCMgr(SERVICE_STATUS_HANDLE service, SERVICE_STATUS *status)
Definition: service.c:22
bool init_security_attributes_allow_all(struct security_attributes *obj)
Definition: automatic.c:66