summaryrefslogtreecommitdiff
path: root/src/openvpnserv/automatic.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/openvpnserv/automatic.c')
-rw-r--r--src/openvpnserv/automatic.c415
1 files changed, 415 insertions, 0 deletions
diff --git a/src/openvpnserv/automatic.c b/src/openvpnserv/automatic.c
new file mode 100644
index 0000000..aa7618f
--- /dev/null
+++ b/src/openvpnserv/automatic.c
@@ -0,0 +1,415 @@
+/*
+ * 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-2010 OpenVPN Technologies, Inc. <sales@openvpn.net>
+ *
+ * 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 <stdio.h>
+#include <stdarg.h>
+#include <process.h>
+
+/* 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);
+}