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