Column sorting fixes, thanks to Tony550
[tomato.git] / release / src / router / openvpn / service-win32 / openvpnserv.c
blob5b0eb6e559edbe11659e98372f75ab9303e463c7
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.
8 * Copyright (C) 2002-2010 OpenVPN Technologies, Inc. <sales@openvpn.net>
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.
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.
19 * You should have received a copy of the GNU General Public License
20 * along with this program (see the file COPYING included with this
21 * distribution); if not, write to the Free Software Foundation, Inc.,
22 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
26 * This program allows one or more OpenVPN processes to be started
27 * as a service. To build, you must get the service sample from the
28 * Platform SDK and replace Simple.c with this file.
30 * You should also apply service.patch to
31 * service.c and service.h from the Platform SDK service sample.
33 * This code is designed to be built with the mingw compiler.
36 #include "config.h"
37 #include <windows.h>
38 #include <stdlib.h>
39 #include <stdio.h>
40 #include <stdarg.h>
41 #include <process.h>
42 #include "service.h"
44 /* bool definitions */
45 #define bool int
46 #define true 1
47 #define false 0
49 /* These are new for 2000/XP, so they aren't in the mingw headers yet */
50 #ifndef BELOW_NORMAL_PRIORITY_CLASS
51 #define BELOW_NORMAL_PRIORITY_CLASS 0x00004000
52 #endif
53 #ifndef ABOVE_NORMAL_PRIORITY_CLASS
54 #define ABOVE_NORMAL_PRIORITY_CLASS 0x00008000
55 #endif
57 struct security_attributes
59 SECURITY_ATTRIBUTES sa;
60 SECURITY_DESCRIPTOR sd;
64 * This event is initially created in the non-signaled
65 * state. It will transition to the signaled state when
66 * we have received a terminate signal from the Service
67 * Control Manager which will cause an asynchronous call
68 * of ServiceStop below.
70 #define EXIT_EVENT_NAME PACKAGE "_exit_1"
73 * Which registry key in HKLM should
74 * we get config info from?
76 #define REG_KEY "SOFTWARE\\" PACKAGE_NAME
78 static HANDLE exit_event = NULL;
80 /* clear an object */
81 #define CLEAR(x) memset(&(x), 0, sizeof(x))
84 * Message handling
86 #define M_INFO (0) // informational
87 #define M_SYSERR (MSG_FLAGS_ERROR|MSG_FLAGS_SYS_CODE) // error + system code
88 #define M_ERR (MSG_FLAGS_ERROR) // error
90 /* write error to event log */
91 #define MSG(flags, ...) \
92 { \
93 char x_msg[256]; \
94 openvpn_snprintf (x_msg, sizeof(x_msg), __VA_ARGS__); \
95 AddToMessageLog ((flags), x_msg); \
98 /* get a registry string */
99 #define QUERY_REG_STRING(name, data) \
101 len = sizeof (data); \
102 status = RegQueryValueEx(openvpn_key, name, NULL, &type, data, &len); \
103 if (status != ERROR_SUCCESS || type != REG_SZ) \
105 SetLastError (status); \
106 MSG (M_SYSERR, error_format_str, name); \
107 RegCloseKey (openvpn_key); \
108 goto finish; \
112 /* get a registry string */
113 #define QUERY_REG_DWORD(name, data) \
115 len = sizeof (DWORD); \
116 status = RegQueryValueEx(openvpn_key, name, NULL, &type, (LPBYTE)&data, &len); \
117 if (status != ERROR_SUCCESS || type != REG_DWORD || len != sizeof (DWORD)) \
119 SetLastError (status); \
120 MSG (M_SYSERR, error_format_dword, name); \
121 RegCloseKey (openvpn_key); \
122 goto finish; \
127 * This is necessary due to certain buggy implementations of snprintf,
128 * that don't guarantee null termination for size > 0.
129 * (copied from ../buffer.c, line 217)
130 * (git: 100644 blob e2f8caab0a5b2a870092c6cd508a1a50c21c3ba3 buffer.c)
133 int openvpn_snprintf(char *str, size_t size, const char *format, ...)
135 va_list arglist;
136 int ret = 0;
137 if (size > 0)
139 va_start (arglist, format);
140 ret = vsnprintf (str, size, format, arglist);
141 va_end (arglist);
142 str[size - 1] = 0;
144 return ret;
148 bool
149 init_security_attributes_allow_all (struct security_attributes *obj)
151 CLEAR (*obj);
153 obj->sa.nLength = sizeof (SECURITY_ATTRIBUTES);
154 obj->sa.lpSecurityDescriptor = &obj->sd;
155 obj->sa.bInheritHandle = TRUE;
156 if (!InitializeSecurityDescriptor (&obj->sd, SECURITY_DESCRIPTOR_REVISION))
157 return false;
158 if (!SetSecurityDescriptorDacl (&obj->sd, TRUE, NULL, FALSE))
159 return false;
160 return true;
163 HANDLE
164 create_event (const char *name, bool allow_all, bool initial_state, bool manual_reset)
166 if (allow_all)
168 struct security_attributes sa;
169 if (!init_security_attributes_allow_all (&sa))
170 return NULL;
171 return CreateEvent (&sa.sa, (BOOL)manual_reset, (BOOL)initial_state, name);
173 else
174 return CreateEvent (NULL, (BOOL)manual_reset, (BOOL)initial_state, name);
177 void
178 close_if_open (HANDLE h)
180 if (h != NULL)
181 CloseHandle (h);
184 static bool
185 match (const WIN32_FIND_DATA *find, const char *ext)
187 int i;
189 if (find->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
190 return false;
192 if (!strlen (ext))
193 return true;
195 i = strlen (find->cFileName) - strlen (ext) - 1;
196 if (i < 1)
197 return false;
199 return find->cFileName[i] == '.' && !strcasecmp (find->cFileName + i + 1, ext);
203 * Modify the extension on a filename.
205 static bool
206 modext (char *dest, int size, const char *src, const char *newext)
208 int i;
210 if (size > 0 && (strlen (src) + 1) <= size)
212 strcpy (dest, src);
213 dest [size - 1] = '\0';
214 i = strlen (dest);
215 while (--i >= 0)
217 if (dest[i] == '\\')
218 break;
219 if (dest[i] == '.')
221 dest[i] = '\0';
222 break;
225 if (strlen (dest) + strlen(newext) + 2 <= size)
227 strcat (dest, ".");
228 strcat (dest, newext);
229 return true;
231 dest [0] = '\0';
233 return false;
236 VOID ServiceStart (DWORD dwArgc, LPTSTR *lpszArgv)
238 char exe_path[MAX_PATH];
239 char config_dir[MAX_PATH];
240 char ext_string[16];
241 char log_dir[MAX_PATH];
242 char priority_string[64];
243 char append_string[2];
245 DWORD priority;
246 bool append;
248 ResetError ();
250 if (!ReportStatusToSCMgr(SERVICE_START_PENDING, NO_ERROR, 3000))
252 MSG (M_ERR, "ReportStatusToSCMgr #1 failed");
253 goto finish;
257 * Create our exit event
259 exit_event = create_event (EXIT_EVENT_NAME, false, false, true);
260 if (!exit_event)
262 MSG (M_ERR, "CreateEvent failed");
263 goto finish;
267 * If exit event is already signaled, it means we were not
268 * shut down properly.
270 if (WaitForSingleObject (exit_event, 0) != WAIT_TIMEOUT)
272 MSG (M_ERR, "Exit event is already signaled -- we were not shut down properly");
273 goto finish;
276 if (!ReportStatusToSCMgr(SERVICE_START_PENDING, NO_ERROR, 3000))
278 MSG (M_ERR, "ReportStatusToSCMgr #2 failed");
279 goto finish;
283 * Read info from registry in key HKLM\SOFTWARE\OpenVPN
286 HKEY openvpn_key;
287 LONG status;
288 DWORD len;
289 DWORD type;
291 static const char error_format_str[] =
292 "Error querying registry key of type REG_SZ: HKLM\\" REG_KEY "\\%s";
294 static const char error_format_dword[] =
295 "Error querying registry key of type REG_DWORD: HKLM\\" REG_KEY "\\%s";
297 status = RegOpenKeyEx(
298 HKEY_LOCAL_MACHINE,
299 REG_KEY,
301 KEY_READ,
302 &openvpn_key);
304 if (status != ERROR_SUCCESS)
306 SetLastError (status);
307 MSG (M_SYSERR, "Registry key HKLM\\" REG_KEY " not found");
308 goto finish;
311 /* get path to openvpn.exe */
312 QUERY_REG_STRING ("exe_path", exe_path);
314 /* get path to configuration directory */
315 QUERY_REG_STRING ("config_dir", config_dir);
317 /* get extension on configuration files */
318 QUERY_REG_STRING ("config_ext", ext_string);
320 /* get path to log directory */
321 QUERY_REG_STRING ("log_dir", log_dir);
323 /* get priority for spawned OpenVPN subprocesses */
324 QUERY_REG_STRING ("priority", priority_string);
326 /* should we truncate or append to logfile? */
327 QUERY_REG_STRING ("log_append", append_string);
329 RegCloseKey (openvpn_key);
332 /* set process priority */
333 priority = NORMAL_PRIORITY_CLASS;
334 if (!strcasecmp (priority_string, "IDLE_PRIORITY_CLASS"))
335 priority = IDLE_PRIORITY_CLASS;
336 else if (!strcasecmp (priority_string, "BELOW_NORMAL_PRIORITY_CLASS"))
337 priority = BELOW_NORMAL_PRIORITY_CLASS;
338 else if (!strcasecmp (priority_string, "NORMAL_PRIORITY_CLASS"))
339 priority = NORMAL_PRIORITY_CLASS;
340 else if (!strcasecmp (priority_string, "ABOVE_NORMAL_PRIORITY_CLASS"))
341 priority = ABOVE_NORMAL_PRIORITY_CLASS;
342 else if (!strcasecmp (priority_string, "HIGH_PRIORITY_CLASS"))
343 priority = HIGH_PRIORITY_CLASS;
344 else
346 MSG (M_ERR, "Unknown priority name: %s", priority_string);
347 goto finish;
350 /* set log file append/truncate flag */
351 append = false;
352 if (append_string[0] == '0')
353 append = false;
354 else if (append_string[0] == '1')
355 append = true;
356 else
358 MSG (M_ERR, "Log file append flag (given as '%s') must be '0' or '1'", append_string);
359 goto finish;
363 * Instantiate an OpenVPN process for each configuration
364 * file found.
367 WIN32_FIND_DATA find_obj;
368 HANDLE find_handle;
369 BOOL more_files;
370 char find_string[MAX_PATH];
372 openvpn_snprintf (find_string, MAX_PATH, "%s\\*", config_dir);
374 find_handle = FindFirstFile (find_string, &find_obj);
375 if (find_handle == INVALID_HANDLE_VALUE)
377 MSG (M_ERR, "Cannot get configuration file list using: %s", find_string);
378 goto finish;
382 * Loop over each config file
384 do {
385 HANDLE log_handle = NULL;
386 STARTUPINFO start_info;
387 PROCESS_INFORMATION proc_info;
388 struct security_attributes sa;
389 char log_file[MAX_PATH];
390 char log_path[MAX_PATH];
391 char command_line[256];
393 CLEAR (start_info);
394 CLEAR (proc_info);
395 CLEAR (sa);
397 if (!ReportStatusToSCMgr(SERVICE_START_PENDING, NO_ERROR, 3000))
399 MSG (M_ERR, "ReportStatusToSCMgr #3 failed");
400 FindClose (find_handle);
401 goto finish;
404 /* does file have the correct type and extension? */
405 if (match (&find_obj, ext_string))
407 /* get log file pathname */
408 if (!modext (log_file, sizeof (log_file), find_obj.cFileName, "log"))
410 MSG (M_ERR, "Cannot construct logfile name based on: %s", find_obj.cFileName);
411 FindClose (find_handle);
412 goto finish;
414 openvpn_snprintf (log_path, sizeof(log_path),
415 "%s\\%s", log_dir, log_file);
417 /* construct command line */
418 openvpn_snprintf (command_line, sizeof(command_line), PACKAGE " --service %s 1 --config \"%s\"",
419 EXIT_EVENT_NAME,
420 find_obj.cFileName);
422 /* Make security attributes struct for logfile handle so it can
423 be inherited. */
424 if (!init_security_attributes_allow_all (&sa))
426 MSG (M_SYSERR, "InitializeSecurityDescriptor start_" PACKAGE " failed");
427 goto finish;
430 /* open logfile as stdout/stderr for soon-to-be-spawned subprocess */
431 log_handle = CreateFile (log_path,
432 GENERIC_WRITE,
433 FILE_SHARE_READ,
434 &sa.sa,
435 append ? OPEN_ALWAYS : CREATE_ALWAYS,
436 FILE_ATTRIBUTE_NORMAL,
437 NULL);
439 if (log_handle == INVALID_HANDLE_VALUE)
441 MSG (M_SYSERR, "Cannot open logfile: %s", log_path);
442 FindClose (find_handle);
443 goto finish;
446 /* append to logfile? */
447 if (append)
449 if (SetFilePointer (log_handle, 0, NULL, FILE_END) == INVALID_SET_FILE_POINTER)
451 MSG (M_SYSERR, "Cannot seek to end of logfile: %s", log_path);
452 FindClose (find_handle);
453 goto finish;
457 /* fill in STARTUPINFO struct */
458 GetStartupInfo(&start_info);
459 start_info.cb = sizeof(start_info);
460 start_info.dwFlags = STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW;
461 start_info.wShowWindow = SW_HIDE;
462 start_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
463 start_info.hStdOutput = start_info.hStdError = log_handle;
465 /* create an OpenVPN process for one config file */
466 if (!CreateProcess(exe_path,
467 command_line,
468 NULL,
469 NULL,
470 TRUE,
471 priority | CREATE_NEW_CONSOLE,
472 NULL,
473 config_dir,
474 &start_info,
475 &proc_info))
477 MSG (M_SYSERR, "CreateProcess failed, exe='%s' cmdline='%s' dir='%s'",
478 exe_path,
479 command_line,
480 config_dir);
482 FindClose (find_handle);
483 CloseHandle (log_handle);
484 goto finish;
487 /* close unneeded handles */
488 Sleep (1000); /* try to prevent race if we close logfile
489 handle before child process DUPs it */
490 if (!CloseHandle (proc_info.hProcess)
491 || !CloseHandle (proc_info.hThread)
492 || !CloseHandle (log_handle))
494 MSG (M_SYSERR, "CloseHandle failed");
495 goto finish;
499 /* more files to process? */
500 more_files = FindNextFile (find_handle, &find_obj);
502 } while (more_files);
504 FindClose (find_handle);
507 /* we are now fully started */
508 if (!ReportStatusToSCMgr(SERVICE_RUNNING, NO_ERROR, 0))
510 MSG (M_ERR, "ReportStatusToSCMgr SERVICE_RUNNING failed");
511 goto finish;
514 /* wait for our shutdown signal */
515 if (WaitForSingleObject (exit_event, INFINITE) != WAIT_OBJECT_0)
517 MSG (M_ERR, "wait for shutdown signal failed");
520 finish:
521 ServiceStop ();
522 if (exit_event)
523 CloseHandle (exit_event);
526 VOID ServiceStop()
528 if (exit_event)
529 SetEvent(exit_event);