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