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
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.
44 /* bool definitions */
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
53 #ifndef ABOVE_NORMAL_PRIORITY_CLASS
54 #define ABOVE_NORMAL_PRIORITY_CLASS 0x00008000
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
;
81 #define CLEAR(x) memset(&(x), 0, sizeof(x))
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, ...) \
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); \
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); \
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
, ...)
139 va_start (arglist
, format
);
140 ret
= vsnprintf (str
, size
, format
, arglist
);
149 init_security_attributes_allow_all (struct security_attributes
*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
))
158 if (!SetSecurityDescriptorDacl (&obj
->sd
, TRUE
, NULL
, FALSE
))
164 create_event (const char *name
, bool allow_all
, bool initial_state
, bool manual_reset
)
168 struct security_attributes sa
;
169 if (!init_security_attributes_allow_all (&sa
))
171 return CreateEvent (&sa
.sa
, (BOOL
)manual_reset
, (BOOL
)initial_state
, name
);
174 return CreateEvent (NULL
, (BOOL
)manual_reset
, (BOOL
)initial_state
, name
);
178 close_if_open (HANDLE h
)
185 match (const WIN32_FIND_DATA
*find
, const char *ext
)
189 if (find
->dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)
195 i
= strlen (find
->cFileName
) - strlen (ext
) - 1;
199 return find
->cFileName
[i
] == '.' && !strcasecmp (find
->cFileName
+ i
+ 1, ext
);
203 * Modify the extension on a filename.
206 modext (char *dest
, int size
, const char *src
, const char *newext
)
210 if (size
> 0 && (strlen (src
) + 1) <= size
)
213 dest
[size
- 1] = '\0';
225 if (strlen (dest
) + strlen(newext
) + 2 <= size
)
228 strcat (dest
, newext
);
236 VOID
ServiceStart (DWORD dwArgc
, LPTSTR
*lpszArgv
)
238 char exe_path
[MAX_PATH
];
239 char config_dir
[MAX_PATH
];
241 char log_dir
[MAX_PATH
];
242 char priority_string
[64];
243 char append_string
[2];
250 if (!ReportStatusToSCMgr(SERVICE_START_PENDING
, NO_ERROR
, 3000))
252 MSG (M_ERR
, "ReportStatusToSCMgr #1 failed");
257 * Create our exit event
259 exit_event
= create_event (EXIT_EVENT_NAME
, false, false, true);
262 MSG (M_ERR
, "CreateEvent failed");
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");
276 if (!ReportStatusToSCMgr(SERVICE_START_PENDING
, NO_ERROR
, 3000))
278 MSG (M_ERR
, "ReportStatusToSCMgr #2 failed");
283 * Read info from registry in key HKLM\SOFTWARE\OpenVPN
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(
304 if (status
!= ERROR_SUCCESS
)
306 SetLastError (status
);
307 MSG (M_SYSERR
, "Registry key HKLM\\" REG_KEY
" not found");
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
;
346 MSG (M_ERR
, "Unknown priority name: %s", priority_string
);
350 /* set log file append/truncate flag */
352 if (append_string
[0] == '0')
354 else if (append_string
[0] == '1')
358 MSG (M_ERR
, "Log file append flag (given as '%s') must be '0' or '1'", append_string
);
363 * Instantiate an OpenVPN process for each configuration
367 WIN32_FIND_DATA find_obj
;
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
);
382 * Loop over each config file
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];
397 if (!ReportStatusToSCMgr(SERVICE_START_PENDING
, NO_ERROR
, 3000))
399 MSG (M_ERR
, "ReportStatusToSCMgr #3 failed");
400 FindClose (find_handle
);
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
);
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\"",
422 /* Make security attributes struct for logfile handle so it can
424 if (!init_security_attributes_allow_all (&sa
))
426 MSG (M_SYSERR
, "InitializeSecurityDescriptor start_" PACKAGE
" failed");
430 /* open logfile as stdout/stderr for soon-to-be-spawned subprocess */
431 log_handle
= CreateFile (log_path
,
435 append
? OPEN_ALWAYS
: CREATE_ALWAYS
,
436 FILE_ATTRIBUTE_NORMAL
,
439 if (log_handle
== INVALID_HANDLE_VALUE
)
441 MSG (M_SYSERR
, "Cannot open logfile: %s", log_path
);
442 FindClose (find_handle
);
446 /* append to logfile? */
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
);
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
,
471 priority
| CREATE_NEW_CONSOLE
,
477 MSG (M_SYSERR
, "CreateProcess failed, exe='%s' cmdline='%s' dir='%s'",
482 FindClose (find_handle
);
483 CloseHandle (log_handle
);
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");
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");
514 /* wait for our shutdown signal */
515 if (WaitForSingleObject (exit_event
, INFINITE
) != WAIT_OBJECT_0
)
517 MSG (M_ERR
, "wait for shutdown signal failed");
523 CloseHandle (exit_event
);
529 SetEvent(exit_event
);