/*
* 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);
}