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-2010 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 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 23 */ 24 25/* 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. 29 * 30 * You should also apply service.patch to 31 * service.c and service.h from the Platform SDK service sample. 32 * 33 * This code is designed to be built with the mingw compiler. 34 */ 35 36#ifdef HAVE_CONFIG_H 37#include "config.h" 38#elif defined(_MSC_VER) 39#include "config-msvc.h" 40#endif 41#include <windows.h> 42#include <stdlib.h> 43#include <stdio.h> 44#include <stdarg.h> 45#include <process.h> 46#include "service.h" 47 48/* bool definitions */ 49#define bool int 50#define true 1 51#define false 0 52 53/* These are new for 2000/XP, so they aren't in the mingw headers yet */ 54#ifndef BELOW_NORMAL_PRIORITY_CLASS 55#define BELOW_NORMAL_PRIORITY_CLASS 0x00004000 56#endif 57#ifndef ABOVE_NORMAL_PRIORITY_CLASS 58#define ABOVE_NORMAL_PRIORITY_CLASS 0x00008000 59#endif 60 61struct security_attributes 62{ 63 SECURITY_ATTRIBUTES sa; 64 SECURITY_DESCRIPTOR sd; 65}; 66 67/* 68 * This event is initially created in the non-signaled 69 * state. It will transition to the signaled state when 70 * we have received a terminate signal from the Service 71 * Control Manager which will cause an asynchronous call 72 * of ServiceStop below. 73 */ 74#define EXIT_EVENT_NAME PACKAGE "_exit_1" 75 76/* 77 * Which registry key in HKLM should 78 * we get config info from? 79 */ 80#define REG_KEY "SOFTWARE\\" PACKAGE_NAME 81 82static HANDLE exit_event = NULL; 83 84/* clear an object */ 85#define CLEAR(x) memset(&(x), 0, sizeof(x)) 86 87/* 88 * Message handling 89 */ 90#define M_INFO (0) /* informational */ 91#define M_SYSERR (MSG_FLAGS_ERROR|MSG_FLAGS_SYS_CODE) /* error + system code */ 92#define M_ERR (MSG_FLAGS_ERROR) /* error */ 93 94/* write error to event log */ 95#define MSG(flags, ...) \ 96 { \ 97 char x_msg[256]; \ 98 openvpn_snprintf (x_msg, sizeof(x_msg), __VA_ARGS__); \ 99 AddToMessageLog ((flags), x_msg); \ 100 } 101 102/* get a registry string */ 103#define QUERY_REG_STRING(name, data) \ 104 { \ 105 len = sizeof (data); \ 106 status = RegQueryValueEx(openvpn_key, name, NULL, &type, data, &len); \ 107 if (status != ERROR_SUCCESS || type != REG_SZ) \ 108 { \ 109 SetLastError (status); \ 110 MSG (M_SYSERR, error_format_str, name); \ 111 RegCloseKey (openvpn_key); \ 112 goto finish; \ 113 } \ 114 } 115 116/* get a registry string */ 117#define QUERY_REG_DWORD(name, data) \ 118 { \ 119 len = sizeof (DWORD); \ 120 status = RegQueryValueEx(openvpn_key, name, NULL, &type, (LPBYTE)&data, &len); \ 121 if (status != ERROR_SUCCESS || type != REG_DWORD || len != sizeof (DWORD)) \ 122 { \ 123 SetLastError (status); \ 124 MSG (M_SYSERR, error_format_dword, name); \ 125 RegCloseKey (openvpn_key); \ 126 goto finish; \ 127 } \ 128 } 129 130/* 131 * This is necessary due to certain buggy implementations of snprintf, 132 * that don't guarantee null termination for size > 0. 133 * (copied from ../buffer.c, line 217) 134 * (git: 100644 blob e2f8caab0a5b2a870092c6cd508a1a50c21c3ba3 buffer.c) 135 */ 136 137int openvpn_snprintf(char *str, size_t size, const char *format, ...) 138{ 139 va_list arglist; 140 int len = -1; 141 if (size > 0) 142 { 143 va_start (arglist, format); 144 len = vsnprintf (str, size, format, arglist); 145 va_end (arglist); 146 str[size - 1] = 0; 147 } 148 return (len >= 0 && len < size); 149} 150 151 152bool 153init_security_attributes_allow_all (struct security_attributes *obj) 154{ 155 CLEAR (*obj); 156 157 obj->sa.nLength = sizeof (SECURITY_ATTRIBUTES); 158 obj->sa.lpSecurityDescriptor = &obj->sd; 159 obj->sa.bInheritHandle = TRUE; 160 if (!InitializeSecurityDescriptor (&obj->sd, SECURITY_DESCRIPTOR_REVISION)) 161 return false; 162 if (!SetSecurityDescriptorDacl (&obj->sd, TRUE, NULL, FALSE)) 163 return false; 164 return true; 165} 166 167HANDLE 168create_event (const char *name, bool allow_all, bool initial_state, bool manual_reset) 169{ 170 if (allow_all) 171 { 172 struct security_attributes sa; 173 if (!init_security_attributes_allow_all (&sa)) 174 return NULL; 175 return CreateEvent (&sa.sa, (BOOL)manual_reset, (BOOL)initial_state, name); 176 } 177 else 178 return CreateEvent (NULL, (BOOL)manual_reset, (BOOL)initial_state, name); 179} 180 181void 182close_if_open (HANDLE h) 183{ 184 if (h != NULL) 185 CloseHandle (h); 186} 187 188static bool 189match (const WIN32_FIND_DATA *find, const char *ext) 190{ 191 int i; 192 193 if (find->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) 194 return false; 195 196 if (!strlen (ext)) 197 return true; 198 199 i = strlen (find->cFileName) - strlen (ext) - 1; 200 if (i < 1) 201 return false; 202 203 return find->cFileName[i] == '.' && !_stricmp (find->cFileName + i + 1, ext); 204} 205 206/* 207 * Modify the extension on a filename. 208 */ 209static bool 210modext (char *dest, int size, const char *src, const char *newext) 211{ 212 int i; 213 214 if (size > 0 && (strlen (src) + 1) <= size) 215 { 216 strcpy (dest, src); 217 dest [size - 1] = '\0'; 218 i = strlen (dest); 219 while (--i >= 0) 220 { 221 if (dest[i] == '\\') 222 break; 223 if (dest[i] == '.') 224 { 225 dest[i] = '\0'; 226 break; 227 } 228 } 229 if (strlen (dest) + strlen(newext) + 2 <= size) 230 { 231 strcat (dest, "."); 232 strcat (dest, newext); 233 return true; 234 } 235 dest [0] = '\0'; 236 } 237 return false; 238} 239 240VOID ServiceStart (DWORD dwArgc, LPTSTR *lpszArgv) 241{ 242 char exe_path[MAX_PATH]; 243 char config_dir[MAX_PATH]; 244 char ext_string[16]; 245 char log_dir[MAX_PATH]; 246 char priority_string[64]; 247 char append_string[2]; 248 249 DWORD priority; 250 bool append; 251 252 ResetError (); 253 254 if (!ReportStatusToSCMgr(SERVICE_START_PENDING, NO_ERROR, 3000)) 255 { 256 MSG (M_ERR, "ReportStatusToSCMgr #1 failed"); 257 goto finish; 258 } 259 260 /* 261 * Create our exit event 262 */ 263 exit_event = create_event (EXIT_EVENT_NAME, false, false, true); 264 if (!exit_event) 265 { 266 MSG (M_ERR, "CreateEvent failed"); 267 goto finish; 268 } 269 270 /* 271 * If exit event is already signaled, it means we were not 272 * shut down properly. 273 */ 274 if (WaitForSingleObject (exit_event, 0) != WAIT_TIMEOUT) 275 { 276 MSG (M_ERR, "Exit event is already signaled -- we were not shut down properly"); 277 goto finish; 278 } 279 280 if (!ReportStatusToSCMgr(SERVICE_START_PENDING, NO_ERROR, 3000)) 281 { 282 MSG (M_ERR, "ReportStatusToSCMgr #2 failed"); 283 goto finish; 284 } 285 286 /* 287 * Read info from registry in key HKLM\SOFTWARE\OpenVPN 288 */ 289 { 290 HKEY openvpn_key; 291 LONG status; 292 DWORD len; 293 DWORD type; 294 295 static const char error_format_str[] = 296 "Error querying registry key of type REG_SZ: HKLM\\" REG_KEY "\\%s"; 297 298 static const char error_format_dword[] = 299 "Error querying registry key of type REG_DWORD: HKLM\\" REG_KEY "\\%s"; 300 301 status = RegOpenKeyEx( 302 HKEY_LOCAL_MACHINE, 303 REG_KEY, 304 0, 305 KEY_READ, 306 &openvpn_key); 307 308 if (status != ERROR_SUCCESS) 309 { 310 SetLastError (status); 311 MSG (M_SYSERR, "Registry key HKLM\\" REG_KEY " not found"); 312 goto finish; 313 } 314 315 /* get path to openvpn.exe */ 316 QUERY_REG_STRING ("exe_path", exe_path); 317 318 /* get path to configuration directory */ 319 QUERY_REG_STRING ("config_dir", config_dir); 320 321 /* get extension on configuration files */ 322 QUERY_REG_STRING ("config_ext", ext_string); 323 324 /* get path to log directory */ 325 QUERY_REG_STRING ("log_dir", log_dir); 326 327 /* get priority for spawned OpenVPN subprocesses */ 328 QUERY_REG_STRING ("priority", priority_string); 329 330 /* should we truncate or append to logfile? */ 331 QUERY_REG_STRING ("log_append", append_string); 332 333 RegCloseKey (openvpn_key); 334 } 335 336 /* set process priority */ 337 priority = NORMAL_PRIORITY_CLASS; 338 if (!_stricmp (priority_string, "IDLE_PRIORITY_CLASS")) 339 priority = IDLE_PRIORITY_CLASS; 340 else if (!_stricmp (priority_string, "BELOW_NORMAL_PRIORITY_CLASS")) 341 priority = BELOW_NORMAL_PRIORITY_CLASS; 342 else if (!_stricmp (priority_string, "NORMAL_PRIORITY_CLASS")) 343 priority = NORMAL_PRIORITY_CLASS; 344 else if (!_stricmp (priority_string, "ABOVE_NORMAL_PRIORITY_CLASS")) 345 priority = ABOVE_NORMAL_PRIORITY_CLASS; 346 else if (!_stricmp (priority_string, "HIGH_PRIORITY_CLASS")) 347 priority = HIGH_PRIORITY_CLASS; 348 else 349 { 350 MSG (M_ERR, "Unknown priority name: %s", priority_string); 351 goto finish; 352 } 353 354 /* set log file append/truncate flag */ 355 append = false; 356 if (append_string[0] == '0') 357 append = false; 358 else if (append_string[0] == '1') 359 append = true; 360 else 361 { 362 MSG (M_ERR, "Log file append flag (given as '%s') must be '0' or '1'", append_string); 363 goto finish; 364 } 365 366 /* 367 * Instantiate an OpenVPN process for each configuration 368 * file found. 369 */ 370 { 371 WIN32_FIND_DATA find_obj; 372 HANDLE find_handle; 373 BOOL more_files; 374 char find_string[MAX_PATH]; 375 376 openvpn_snprintf (find_string, MAX_PATH, "%s\\*", config_dir); 377 378 find_handle = FindFirstFile (find_string, &find_obj); 379 if (find_handle == INVALID_HANDLE_VALUE) 380 { 381 MSG (M_ERR, "Cannot get configuration file list using: %s", find_string); 382 goto finish; 383 } 384 385 /* 386 * Loop over each config file 387 */ 388 do { 389 HANDLE log_handle = NULL; 390 STARTUPINFO start_info; 391 PROCESS_INFORMATION proc_info; 392 struct security_attributes sa; 393 char log_file[MAX_PATH]; 394 char log_path[MAX_PATH]; 395 char command_line[256]; 396 397 CLEAR (start_info); 398 CLEAR (proc_info); 399 CLEAR (sa); 400 401 if (!ReportStatusToSCMgr(SERVICE_START_PENDING, NO_ERROR, 3000)) 402 { 403 MSG (M_ERR, "ReportStatusToSCMgr #3 failed"); 404 FindClose (find_handle); 405 goto finish; 406 } 407 408 /* does file have the correct type and extension? */ 409 if (match (&find_obj, ext_string)) 410 { 411 /* get log file pathname */ 412 if (!modext (log_file, sizeof (log_file), find_obj.cFileName, "log")) 413 { 414 MSG (M_ERR, "Cannot construct logfile name based on: %s", find_obj.cFileName); 415 FindClose (find_handle); 416 goto finish; 417 } 418 openvpn_snprintf (log_path, sizeof(log_path), 419 "%s\\%s", log_dir, log_file); 420 421 /* construct command line */ 422 openvpn_snprintf (command_line, sizeof(command_line), PACKAGE " --service %s 1 --config \"%s\"", 423 EXIT_EVENT_NAME, 424 find_obj.cFileName); 425 426 /* Make security attributes struct for logfile handle so it can 427 be inherited. */ 428 if (!init_security_attributes_allow_all (&sa)) 429 { 430 MSG (M_SYSERR, "InitializeSecurityDescriptor start_" PACKAGE " failed"); 431 goto finish; 432 } 433 434 /* open logfile as stdout/stderr for soon-to-be-spawned subprocess */ 435 log_handle = CreateFile (log_path, 436 GENERIC_WRITE, 437 FILE_SHARE_READ, 438 &sa.sa, 439 append ? OPEN_ALWAYS : CREATE_ALWAYS, 440 FILE_ATTRIBUTE_NORMAL, 441 NULL); 442 443 if (log_handle == INVALID_HANDLE_VALUE) 444 { 445 MSG (M_SYSERR, "Cannot open logfile: %s", log_path); 446 FindClose (find_handle); 447 goto finish; 448 } 449 450 /* append to logfile? */ 451 if (append) 452 { 453 if (SetFilePointer (log_handle, 0, NULL, FILE_END) == INVALID_SET_FILE_POINTER) 454 { 455 MSG (M_SYSERR, "Cannot seek to end of logfile: %s", log_path); 456 FindClose (find_handle); 457 goto finish; 458 } 459 } 460 461 /* fill in STARTUPINFO struct */ 462 GetStartupInfo(&start_info); 463 start_info.cb = sizeof(start_info); 464 start_info.dwFlags = STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW; 465 start_info.wShowWindow = SW_HIDE; 466 start_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE); 467 start_info.hStdOutput = start_info.hStdError = log_handle; 468 469 /* create an OpenVPN process for one config file */ 470 if (!CreateProcess(exe_path, 471 command_line, 472 NULL, 473 NULL, 474 TRUE, 475 priority | CREATE_NEW_CONSOLE, 476 NULL, 477 config_dir, 478 &start_info, 479 &proc_info)) 480 { 481 MSG (M_SYSERR, "CreateProcess failed, exe='%s' cmdline='%s' dir='%s'", 482 exe_path, 483 command_line, 484 config_dir); 485 486 FindClose (find_handle); 487 CloseHandle (log_handle); 488 goto finish; 489 } 490 491 /* close unneeded handles */ 492 Sleep (1000); /* try to prevent race if we close logfile 493 handle before child process DUPs it */ 494 if (!CloseHandle (proc_info.hProcess) 495 || !CloseHandle (proc_info.hThread) 496 || !CloseHandle (log_handle)) 497 { 498 MSG (M_SYSERR, "CloseHandle failed"); 499 goto finish; 500 } 501 } 502 503 /* more files to process? */ 504 more_files = FindNextFile (find_handle, &find_obj); 505 506 } while (more_files); 507 508 FindClose (find_handle); 509 } 510 511 /* we are now fully started */ 512 if (!ReportStatusToSCMgr(SERVICE_RUNNING, NO_ERROR, 0)) 513 { 514 MSG (M_ERR, "ReportStatusToSCMgr SERVICE_RUNNING failed"); 515 goto finish; 516 } 517 518 /* wait for our shutdown signal */ 519 if (WaitForSingleObject (exit_event, INFINITE) != WAIT_OBJECT_0) 520 { 521 MSG (M_ERR, "wait for shutdown signal failed"); 522 } 523 524 finish: 525 ServiceStop (); 526 if (exit_event) 527 CloseHandle (exit_event); 528} 529 530VOID ServiceStop() 531{ 532 if (exit_event) 533 SetEvent(exit_event); 534} 535