/* * OpenVPN -- An application to securely tunnel IP networks * over a single TCP/UDP port, with support for SSL/TLS-based * session authentication and key exchange, * packet encryption, packet authentication, and * packet compression. * * Copyright (C) 2002-2017 OpenVPN Technologies, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program (see the file COPYING included with this * distribution); if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* * This program allows one or more OpenVPN processes to be started * as a service. To build, you must get the service sample from the * Platform SDK and replace Simple.c with this file. * * You should also apply service.patch to * service.c and service.h from the Platform SDK service sample. * * This code is designed to be built with the mingw compiler. */ #include "service.h" #include #include #include /* bool definitions */ #define bool int #define true 1 #define false 0 static SERVICE_STATUS_HANDLE service; static SERVICE_STATUS status; openvpn_service_t automatic_service = { automatic, TEXT(PACKAGE_NAME "ServiceLegacy"), TEXT(PACKAGE_NAME " Legacy Service"), TEXT(SERVICE_DEPENDENCIES), SERVICE_DEMAND_START }; struct security_attributes { SECURITY_ATTRIBUTES sa; SECURITY_DESCRIPTOR sd; }; /* * Which registry key in HKLM should * we get config info from? */ #define REG_KEY "SOFTWARE\\" PACKAGE_NAME static HANDLE exit_event = NULL; /* clear an object */ #define CLEAR(x) memset(&(x), 0, sizeof(x)) bool init_security_attributes_allow_all(struct security_attributes *obj) { CLEAR(*obj); obj->sa.nLength = sizeof(SECURITY_ATTRIBUTES); obj->sa.lpSecurityDescriptor = &obj->sd; obj->sa.bInheritHandle = TRUE; if (!InitializeSecurityDescriptor(&obj->sd, SECURITY_DESCRIPTOR_REVISION)) { return false; } if (!SetSecurityDescriptorDacl(&obj->sd, TRUE, NULL, FALSE)) { return false; } return true; } /* * This event is initially created in the non-signaled * state. It will transition to the signaled state when * we have received a terminate signal from the Service * Control Manager which will cause an asynchronous call * of ServiceStop below. */ #define EXIT_EVENT_NAME TEXT(PACKAGE "_exit_1") HANDLE create_event(LPCTSTR name, bool allow_all, bool initial_state, bool manual_reset) { if (allow_all) { struct security_attributes sa; if (!init_security_attributes_allow_all(&sa)) { return NULL; } return CreateEvent(&sa.sa, (BOOL)manual_reset, (BOOL)initial_state, name); } else { return CreateEvent(NULL, (BOOL)manual_reset, (BOOL)initial_state, name); } } void close_if_open(HANDLE h) { if (h != NULL) { CloseHandle(h); } } static bool match(const WIN32_FIND_DATA *find, LPCTSTR ext) { int i; if (find->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { return false; } if (!_tcslen(ext)) { return true; } i = _tcslen(find->cFileName) - _tcslen(ext) - 1; if (i < 1) { return false; } return find->cFileName[i] == '.' && !_tcsicmp(find->cFileName + i + 1, ext); } /* * Modify the extension on a filename. */ static bool modext(LPTSTR dest, int size, LPCTSTR src, LPCTSTR newext) { int i; if (size > 0 && (_tcslen(src) + 1) <= size) { _tcscpy(dest, src); dest [size - 1] = TEXT('\0'); i = _tcslen(dest); while (--i >= 0) { if (dest[i] == TEXT('\\')) { break; } if (dest[i] == TEXT('.')) { dest[i] = TEXT('\0'); break; } } if (_tcslen(dest) + _tcslen(newext) + 2 <= size) { _tcscat(dest, TEXT(".")); _tcscat(dest, newext); return true; } dest[0] = TEXT('\0'); } return false; } static DWORD WINAPI ServiceCtrlAutomatic(DWORD ctrl_code, DWORD event, LPVOID data, LPVOID ctx) { SERVICE_STATUS *status = ctx; switch (ctrl_code) { case SERVICE_CONTROL_STOP: status->dwCurrentState = SERVICE_STOP_PENDING; ReportStatusToSCMgr(service, status); if (exit_event) { SetEvent(exit_event); } return NO_ERROR; case SERVICE_CONTROL_INTERROGATE: return NO_ERROR; default: return ERROR_CALL_NOT_IMPLEMENTED; } } VOID WINAPI ServiceStartAutomatic(DWORD dwArgc, LPTSTR *lpszArgv) { DWORD error = NO_ERROR; settings_t settings; service = RegisterServiceCtrlHandlerEx(automatic_service.name, ServiceCtrlAutomatic, &status); if (!service) { return; } status.dwServiceType = SERVICE_WIN32_SHARE_PROCESS; status.dwCurrentState = SERVICE_START_PENDING; status.dwServiceSpecificExitCode = NO_ERROR; status.dwWin32ExitCode = NO_ERROR; status.dwWaitHint = 3000; if (!ReportStatusToSCMgr(service, &status)) { MsgToEventLog(M_ERR, TEXT("ReportStatusToSCMgr #1 failed")); goto finish; } /* * Create our exit event */ exit_event = create_event(EXIT_EVENT_NAME, false, false, true); if (!exit_event) { MsgToEventLog(M_ERR, TEXT("CreateEvent failed")); goto finish; } /* * If exit event is already signaled, it means we were not * shut down properly. */ if (WaitForSingleObject(exit_event, 0) != WAIT_TIMEOUT) { MsgToEventLog(M_ERR, TEXT("Exit event is already signaled -- we were not shut down properly")); goto finish; } if (!ReportStatusToSCMgr(service, &status)) { MsgToEventLog(M_ERR, TEXT("ReportStatusToSCMgr #2 failed")); goto finish; } /* * Read info from registry in key HKLM\SOFTWARE\OpenVPN */ error = GetOpenvpnSettings(&settings); if (error != ERROR_SUCCESS) { goto finish; } /* * Instantiate an OpenVPN process for each configuration * file found. */ { WIN32_FIND_DATA find_obj; HANDLE find_handle; BOOL more_files; TCHAR find_string[MAX_PATH]; openvpn_sntprintf(find_string, MAX_PATH, TEXT("%s\\*"), settings.config_dir); find_handle = FindFirstFile(find_string, &find_obj); if (find_handle == INVALID_HANDLE_VALUE) { MsgToEventLog(M_ERR, TEXT("Cannot get configuration file list using: %s"), find_string); goto finish; } /* * Loop over each config file */ do { HANDLE log_handle = NULL; STARTUPINFO start_info; PROCESS_INFORMATION proc_info; struct security_attributes sa; TCHAR log_file[MAX_PATH]; TCHAR log_path[MAX_PATH]; TCHAR command_line[256]; CLEAR(start_info); CLEAR(proc_info); CLEAR(sa); if (!ReportStatusToSCMgr(service, &status)) { MsgToEventLog(M_ERR, TEXT("ReportStatusToSCMgr #3 failed")); FindClose(find_handle); goto finish; } /* does file have the correct type and extension? */ if (match(&find_obj, settings.ext_string)) { /* get log file pathname */ if (!modext(log_file, _countof(log_file), find_obj.cFileName, TEXT("log"))) { MsgToEventLog(M_ERR, TEXT("Cannot construct logfile name based on: %s"), find_obj.cFileName); FindClose(find_handle); goto finish; } openvpn_sntprintf(log_path, _countof(log_path), TEXT("%s\\%s"), settings.log_dir, log_file); /* construct command line */ openvpn_sntprintf(command_line, _countof(command_line), TEXT(PACKAGE " --service %s 1 --config \"%s\""), EXIT_EVENT_NAME, find_obj.cFileName); /* Make security attributes struct for logfile handle so it can * be inherited. */ if (!init_security_attributes_allow_all(&sa)) { error = MsgToEventLog(M_SYSERR, TEXT("InitializeSecurityDescriptor start_" PACKAGE " failed")); goto finish; } /* open logfile as stdout/stderr for soon-to-be-spawned subprocess */ log_handle = CreateFile(log_path, GENERIC_WRITE, FILE_SHARE_READ, &sa.sa, settings.append ? OPEN_ALWAYS : CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (log_handle == INVALID_HANDLE_VALUE) { error = MsgToEventLog(M_SYSERR, TEXT("Cannot open logfile: %s"), log_path); FindClose(find_handle); goto finish; } /* append to logfile? */ if (settings.append) { if (SetFilePointer(log_handle, 0, NULL, FILE_END) == INVALID_SET_FILE_POINTER) { error = MsgToEventLog(M_SYSERR, TEXT("Cannot seek to end of logfile: %s"), log_path); FindClose(find_handle); goto finish; } } /* fill in STARTUPINFO struct */ GetStartupInfo(&start_info); start_info.cb = sizeof(start_info); start_info.dwFlags = STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW; start_info.wShowWindow = SW_HIDE; start_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE); start_info.hStdOutput = start_info.hStdError = log_handle; /* create an OpenVPN process for one config file */ if (!CreateProcess(settings.exe_path, command_line, NULL, NULL, TRUE, settings.priority | CREATE_NEW_CONSOLE, NULL, settings.config_dir, &start_info, &proc_info)) { error = MsgToEventLog(M_SYSERR, TEXT("CreateProcess failed, exe='%s' cmdline='%s' dir='%s'"), settings.exe_path, command_line, settings.config_dir); FindClose(find_handle); CloseHandle(log_handle); goto finish; } /* close unneeded handles */ Sleep(1000); /* try to prevent race if we close logfile * handle before child process DUPs it */ if (!CloseHandle(proc_info.hProcess) || !CloseHandle(proc_info.hThread) || !CloseHandle(log_handle)) { error = MsgToEventLog(M_SYSERR, TEXT("CloseHandle failed")); goto finish; } } /* more files to process? */ more_files = FindNextFile(find_handle, &find_obj); } while (more_files); FindClose(find_handle); } /* we are now fully started */ status.dwCurrentState = SERVICE_RUNNING; status.dwWaitHint = 0; if (!ReportStatusToSCMgr(service, &status)) { MsgToEventLog(M_ERR, TEXT("ReportStatusToSCMgr SERVICE_RUNNING failed")); goto finish; } /* wait for our shutdown signal */ if (WaitForSingleObject(exit_event, INFINITE) != WAIT_OBJECT_0) { MsgToEventLog(M_ERR, TEXT("wait for shutdown signal failed")); } finish: if (exit_event) { CloseHandle(exit_event); } status.dwCurrentState = SERVICE_STOPPED; status.dwWin32ExitCode = error; ReportStatusToSCMgr(service, &status); }