diff options
Diffstat (limited to 'src/openvpnserv/interactive.c')
-rw-r--r-- | src/openvpnserv/interactive.c | 1652 |
1 files changed, 1652 insertions, 0 deletions
diff --git a/src/openvpnserv/interactive.c b/src/openvpnserv/interactive.c new file mode 100644 index 0000000..ffaa171 --- /dev/null +++ b/src/openvpnserv/interactive.c @@ -0,0 +1,1652 @@ +/* + * 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) 2012 Heiko Hund <heiko.hund@sophos.com> + * + * 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 + */ + + +#include "service.h" + +#include <winsock2.h> +#include <ws2tcpip.h> +#include <iphlpapi.h> +#include <userenv.h> +#include <accctrl.h> +#include <aclapi.h> +#include <stdio.h> +#include <sddl.h> +#include <shellapi.h> + +#include "openvpn-msg.h" +#include "validate.h" +#include "block_dns.h" + +#define IO_TIMEOUT 2000 /*ms*/ + +#define ERROR_OPENVPN_STARTUP 0x20000000 +#define ERROR_STARTUP_DATA 0x20000001 +#define ERROR_MESSAGE_DATA 0x20000002 +#define ERROR_MESSAGE_TYPE 0x20000003 + +static SERVICE_STATUS_HANDLE service; +static SERVICE_STATUS status; +static HANDLE exit_event = NULL; +static settings_t settings; +static HANDLE rdns_semaphore = NULL; +#define RDNS_TIMEOUT 600 /* seconds to wait for the semaphore */ + + +openvpn_service_t interactive_service = { + interactive, + TEXT(PACKAGE_NAME "ServiceInteractive"), + TEXT(PACKAGE_NAME " Interactive Service"), + TEXT(SERVICE_DEPENDENCIES), + SERVICE_AUTO_START +}; + + +typedef struct { + WCHAR *directory; + WCHAR *options; + WCHAR *std_input; +} STARTUP_DATA; + + +/* Datatype for linked lists */ +typedef struct _list_item { + struct _list_item *next; + LPVOID data; +} list_item_t; + + +/* Datatypes for undo information */ +typedef enum { + address, + route, + block_dns, + _undo_type_max +} undo_type_t; +typedef list_item_t* undo_lists_t[_undo_type_max]; + + +static DWORD +AddListItem (list_item_t **pfirst, LPVOID data) +{ + list_item_t *new_item = malloc (sizeof (list_item_t)); + if (new_item == NULL) + return ERROR_OUTOFMEMORY; + + new_item->next = *pfirst; + new_item->data = data; + + *pfirst = new_item; + return NO_ERROR; +} + +typedef BOOL (*match_fn_t) (LPVOID item, LPVOID ctx); + +static LPVOID +RemoveListItem (list_item_t **pfirst, match_fn_t match, LPVOID ctx) +{ + LPVOID data = NULL; + list_item_t **pnext; + + for (pnext = pfirst; *pnext; pnext = &(*pnext)->next) + { + list_item_t *item = *pnext; + if (!match (item->data, ctx)) + continue; + + /* Found item, remove from the list and free memory */ + *pnext = item->next; + data = item->data; + free (item); + break; + } + return data; +} + + +static HANDLE +CloseHandleEx (LPHANDLE handle) +{ + if (handle && *handle && *handle != INVALID_HANDLE_VALUE) + { + CloseHandle (*handle); + *handle = INVALID_HANDLE_VALUE; + } + return INVALID_HANDLE_VALUE; +} + + +static HANDLE +InitOverlapped (LPOVERLAPPED overlapped) +{ + ZeroMemory (overlapped, sizeof (OVERLAPPED)); + overlapped->hEvent = CreateEvent (NULL, TRUE, FALSE, NULL); + return overlapped->hEvent; +} + + +static BOOL +ResetOverlapped (LPOVERLAPPED overlapped) +{ + HANDLE io_event = overlapped->hEvent; + if (!ResetEvent (io_event)) + return FALSE; + ZeroMemory (overlapped, sizeof (OVERLAPPED)); + overlapped->hEvent = io_event; + return TRUE; +} + + +typedef enum { + peek, + read, + write +} async_op_t; + +static DWORD +AsyncPipeOp (async_op_t op, HANDLE pipe, LPVOID buffer, DWORD size, DWORD count, LPHANDLE events) +{ + int i; + BOOL success; + HANDLE io_event; + DWORD res, bytes = 0; + OVERLAPPED overlapped; + LPHANDLE handles = NULL; + + io_event = InitOverlapped (&overlapped); + if (!io_event) + goto out; + + handles = malloc ((count + 1) * sizeof (HANDLE)); + if (!handles) + goto out; + + if (op == write) + success = WriteFile (pipe, buffer, size, NULL, &overlapped); + else + success = ReadFile (pipe, buffer, size, NULL, &overlapped); + if (!success && GetLastError () != ERROR_IO_PENDING && GetLastError () != ERROR_MORE_DATA) + goto out; + + handles[0] = io_event; + for (i = 0; i < count; i++) + handles[i + 1] = events[i]; + + res = WaitForMultipleObjects (count + 1, handles, FALSE, + op == peek ? INFINITE : IO_TIMEOUT); + if (res != WAIT_OBJECT_0) + { + CancelIo (pipe); + goto out; + } + + if (op == peek) + PeekNamedPipe (pipe, NULL, 0, NULL, &bytes, NULL); + else + GetOverlappedResult (pipe, &overlapped, &bytes, TRUE); + +out: + CloseHandleEx (&io_event); + free (handles); + return bytes; +} + +static DWORD +PeekNamedPipeAsync (HANDLE pipe, DWORD count, LPHANDLE events) +{ + return AsyncPipeOp (peek, pipe, NULL, 0, count, events); +} + +static DWORD +ReadPipeAsync (HANDLE pipe, LPVOID buffer, DWORD size, DWORD count, LPHANDLE events) +{ + return AsyncPipeOp (read, pipe, buffer, size, count, events); +} + +static DWORD +WritePipeAsync (HANDLE pipe, LPVOID data, DWORD size, DWORD count, LPHANDLE events) +{ + return AsyncPipeOp (write, pipe, data, size, count, events); +} + +static VOID +ReturnProcessId (HANDLE pipe, DWORD pid, DWORD count, LPHANDLE events) +{ + const WCHAR msg[] = L"Process ID"; + WCHAR buf[22 + _countof(msg)]; /* 10 chars each for error and PID and 2 for line breaks */ + + /* + * Same format as error messages (3 line string) with error = 0 in + * 0x%08x format, PID on line 2 and a description "Process ID" on line 3 + */ + _snwprintf (buf, _countof(buf), L"0x%08x\n0x%08x\n%s", 0, pid, msg); + buf[_countof(buf) - 1] = '\0'; + + WritePipeAsync (pipe, buf, wcslen (buf) * 2, count, events); +} + +static VOID +ReturnError (HANDLE pipe, DWORD error, LPCWSTR func, DWORD count, LPHANDLE events) +{ + DWORD result_len; + LPWSTR result = L"0xffffffff\nFormatMessage failed\nCould not return result"; + DWORD_PTR args[] = { + (DWORD_PTR) error, + (DWORD_PTR) func, + (DWORD_PTR) "" + }; + + if (error != ERROR_OPENVPN_STARTUP) + { + FormatMessageW (FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_IGNORE_INSERTS, + 0, error, 0, (LPWSTR) &args[2], 0, NULL); + } + + result_len = FormatMessageW (FORMAT_MESSAGE_FROM_STRING | + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_ARGUMENT_ARRAY, + L"0x%1!08x!\n%2!s!\n%3!s!", 0, 0, + (LPWSTR) &result, 0, (va_list*) args); + + WritePipeAsync (pipe, result, wcslen (result) * 2, count, events); +#ifdef UNICODE + MsgToEventLog (MSG_FLAGS_ERROR, result); +#else + MsgToEventLog (MSG_FLAGS_ERROR, "%S", result); +#endif + + if (error != ERROR_OPENVPN_STARTUP) + LocalFree ((LPVOID) args[2]); + if (result_len) + LocalFree (result); +} + + +static VOID +ReturnLastError (HANDLE pipe, LPCWSTR func) +{ + ReturnError (pipe, GetLastError (), func, 1, &exit_event); +} + + +static VOID +ReturnOpenvpnOutput (HANDLE pipe, HANDLE ovpn_output, DWORD count, LPHANDLE events) +{ + WCHAR *wide_output = NULL; + CHAR output[512]; + DWORD size; + + ReadFile (ovpn_output, output, sizeof (output), &size, NULL); + if (size == 0) + return; + + wide_output = malloc ((size) * sizeof (WCHAR)); + if (wide_output) + { + MultiByteToWideChar (CP_UTF8, 0, output, size, wide_output, size); + wide_output[size - 1] = 0; + } + + ReturnError (pipe, ERROR_OPENVPN_STARTUP, wide_output, count, events); + free (wide_output); +} + +/* + * Validate options against a white list. Also check the config_file is + * inside the config_dir. The white list is defined in validate.c + * Returns true on success + */ +static BOOL +ValidateOptions (HANDLE pipe, const WCHAR *workdir, const WCHAR *options) +{ + WCHAR **argv; + int argc; + WCHAR buf[256]; + BOOL ret = FALSE; + int i; + const WCHAR *msg1 = L"You have specified a config file location (%s relative to %s)" + " that requires admin approval. This error may be avoided" + " by adding your account to the \"%s\" group"; + + const WCHAR *msg2 = L"You have specified an option (%s) that may be used" + " only with admin approval. This error may be avoided" + " by adding your account to the \"%s\" group"; + + argv = CommandLineToArgvW (options, &argc); + + if (!argv) + { + ReturnLastError (pipe, L"CommandLineToArgvW"); + ReturnError (pipe, ERROR_STARTUP_DATA, L"Cannot validate options", 1, &exit_event); + goto out; + } + + /* Note: argv[0] is the first option */ + if (argc < 1) /* no options */ + { + ret = TRUE; + goto out; + } + + /* + * If only one argument, it is the config file + */ + if (argc == 1) + { + WCHAR *argv_tmp[2] = { L"--config", argv[0] }; + + if (!CheckOption (workdir, 2, argv_tmp, &settings)) + { + snwprintf (buf, _countof(buf), msg1, argv[0], workdir, + settings.ovpn_admin_group); + buf[_countof(buf) - 1] = L'\0'; + ReturnError (pipe, ERROR_STARTUP_DATA, buf, 1, &exit_event); + } + goto out; + } + + for (i = 0; i < argc; ++i) + { + if (!IsOption(argv[i])) + continue; + + if (!CheckOption (workdir, argc-i, &argv[i], &settings)) + { + if (wcscmp(L"--config", argv[i]) == 0 && argc-i > 1) + { + snwprintf (buf, _countof(buf), msg1, argv[i+1], workdir, + settings.ovpn_admin_group); + buf[_countof(buf) - 1] = L'\0'; + ReturnError (pipe, ERROR_STARTUP_DATA, buf, 1, &exit_event); + } + else + { + snwprintf (buf, _countof(buf), msg2, argv[i], + settings.ovpn_admin_group); + buf[_countof(buf) - 1] = L'\0'; + ReturnError (pipe, ERROR_STARTUP_DATA, buf, 1, &exit_event); + } + goto out; + } + } + + /* all options passed */ + ret = TRUE; + +out: + if (argv) + LocalFree (argv); + return ret; +} + +static BOOL +GetStartupData (HANDLE pipe, STARTUP_DATA *sud) +{ + size_t len; + BOOL ret = FALSE; + WCHAR *data = NULL; + DWORD size, bytes, read; + + bytes = PeekNamedPipeAsync (pipe, 1, &exit_event); + if (bytes == 0) + { + MsgToEventLog (M_SYSERR, TEXT("PeekNamedPipeAsync failed")); + ReturnLastError (pipe, L"PeekNamedPipeAsync"); + goto out; + } + + size = bytes / sizeof (*data); + data = malloc (bytes); + if (data == NULL) + { + MsgToEventLog (M_SYSERR, TEXT("malloc failed")); + ReturnLastError (pipe, L"malloc"); + goto out; + } + + read = ReadPipeAsync (pipe, data, bytes, 1, &exit_event); + if (bytes != read) + { + MsgToEventLog (M_SYSERR, TEXT("ReadPipeAsync failed")); + ReturnLastError (pipe, L"ReadPipeAsync"); + goto out; + } + + if (data[size - 1] != 0) + { + MsgToEventLog (M_ERR, TEXT("Startup data is not NULL terminated")); + ReturnError (pipe, ERROR_STARTUP_DATA, L"GetStartupData", 1, &exit_event); + goto out; + } + + sud->directory = data; + len = wcslen (sud->directory) + 1; + size -= len; + if (size <= 0) + { + MsgToEventLog (M_ERR, TEXT("Startup data ends at working directory")); + ReturnError (pipe, ERROR_STARTUP_DATA, L"GetStartupData", 1, &exit_event); + goto out; + } + + sud->options = sud->directory + len; + len = wcslen (sud->options) + 1; + size -= len; + if (size <= 0) + { + MsgToEventLog (M_ERR, TEXT("Startup data ends at command line options")); + ReturnError (pipe, ERROR_STARTUP_DATA, L"GetStartupData", 1, &exit_event); + goto out; + } + + sud->std_input = sud->options + len; + data = NULL; /* don't free data */ + ret = TRUE; + +out: + free (data); + return ret; +} + + +static VOID +FreeStartupData (STARTUP_DATA *sud) +{ + free (sud->directory); +} + + +static SOCKADDR_INET +sockaddr_inet (short family, inet_address_t *addr) +{ + SOCKADDR_INET sa_inet; + ZeroMemory (&sa_inet, sizeof (sa_inet)); + sa_inet.si_family = family; + if (family == AF_INET) + sa_inet.Ipv4.sin_addr = addr->ipv4; + else if (family == AF_INET6) + sa_inet.Ipv6.sin6_addr = addr->ipv6; + return sa_inet; +} + +static DWORD +InterfaceLuid (const char *iface_name, PNET_LUID luid) +{ + NETIO_STATUS status; + LPWSTR wide_name; + int n; + + typedef NETIO_STATUS WINAPI (*ConvertInterfaceAliasToLuidFn) (LPCWSTR, PNET_LUID); + static ConvertInterfaceAliasToLuidFn ConvertInterfaceAliasToLuid = NULL; + if (!ConvertInterfaceAliasToLuid) + { + HMODULE iphlpapi = GetModuleHandle (TEXT("iphlpapi.dll")); + if (iphlpapi == NULL) + return GetLastError (); + + ConvertInterfaceAliasToLuid = (ConvertInterfaceAliasToLuidFn) GetProcAddress (iphlpapi, "ConvertInterfaceAliasToLuid"); + if (!ConvertInterfaceAliasToLuid) + return GetLastError (); + } + + n = MultiByteToWideChar (CP_UTF8, 0, iface_name, -1, NULL, 0); + wide_name = malloc (n * sizeof (WCHAR)); + MultiByteToWideChar (CP_UTF8, 0, iface_name, -1, wide_name, n); + status = ConvertInterfaceAliasToLuid (wide_name, luid); + free (wide_name); + + return status; +} + +static BOOL +CmpAddress (LPVOID item, LPVOID address) +{ + return memcmp (item, address, sizeof (MIB_UNICASTIPADDRESS_ROW)) == 0 ? TRUE : FALSE; +} + +static DWORD +DeleteAddress (PMIB_UNICASTIPADDRESS_ROW addr_row) +{ + typedef NETIOAPI_API (*DeleteUnicastIpAddressEntryFn) (const PMIB_UNICASTIPADDRESS_ROW); + static DeleteUnicastIpAddressEntryFn DeleteUnicastIpAddressEntry = NULL; + + if (!DeleteUnicastIpAddressEntry) + { + HMODULE iphlpapi = GetModuleHandle (TEXT("iphlpapi.dll")); + if (iphlpapi == NULL) + return GetLastError (); + + DeleteUnicastIpAddressEntry = (DeleteUnicastIpAddressEntryFn) GetProcAddress (iphlpapi, "DeleteUnicastIpAddressEntry"); + if (!DeleteUnicastIpAddressEntry) + return GetLastError (); + } + + return DeleteUnicastIpAddressEntry (addr_row); +} + +static DWORD +HandleAddressMessage (address_message_t *msg, undo_lists_t *lists) +{ + DWORD err; + PMIB_UNICASTIPADDRESS_ROW addr_row; + BOOL add = msg->header.type == msg_add_address; + + typedef NETIOAPI_API (*CreateUnicastIpAddressEntryFn) (const PMIB_UNICASTIPADDRESS_ROW); + typedef NETIOAPI_API (*InitializeUnicastIpAddressEntryFn) (PMIB_UNICASTIPADDRESS_ROW); + static CreateUnicastIpAddressEntryFn CreateUnicastIpAddressEntry = NULL; + static InitializeUnicastIpAddressEntryFn InitializeUnicastIpAddressEntry = NULL; + + if (!CreateUnicastIpAddressEntry || !InitializeUnicastIpAddressEntry) + { + HMODULE iphlpapi = GetModuleHandle (TEXT("iphlpapi.dll")); + if (iphlpapi == NULL) + return GetLastError (); + + CreateUnicastIpAddressEntry = (CreateUnicastIpAddressEntryFn) GetProcAddress (iphlpapi, "CreateUnicastIpAddressEntry"); + if (!CreateUnicastIpAddressEntry) + return GetLastError (); + + InitializeUnicastIpAddressEntry = (InitializeUnicastIpAddressEntryFn) GetProcAddress (iphlpapi, "InitializeUnicastIpAddressEntry"); + if (!InitializeUnicastIpAddressEntry) + return GetLastError (); + } + + addr_row = malloc (sizeof (*addr_row)); + if (addr_row == NULL) + return ERROR_OUTOFMEMORY; + + InitializeUnicastIpAddressEntry (addr_row); + addr_row->Address = sockaddr_inet (msg->family, &msg->address); + addr_row->OnLinkPrefixLength = (UINT8) msg->prefix_len; + + if (msg->iface.index != -1) + { + addr_row->InterfaceIndex = msg->iface.index; + } + else + { + NET_LUID luid; + err = InterfaceLuid (msg->iface.name, &luid); + if (err) + goto out; + addr_row->InterfaceLuid = luid; + } + + if (add) + { + err = CreateUnicastIpAddressEntry (addr_row); + if (err) + goto out; + + err = AddListItem (&(*lists)[address], addr_row); + if (err) + DeleteAddress (addr_row); + } + else + { + err = DeleteAddress (addr_row); + if (err) + goto out; + + free (RemoveListItem (&(*lists)[address], CmpAddress, addr_row)); + } + +out: + if (!add || err) + free (addr_row); + + return err; +} + +static BOOL +CmpRoute (LPVOID item, LPVOID route) +{ + return memcmp (item, route, sizeof (MIB_IPFORWARD_ROW2)) == 0 ? TRUE : FALSE; +} + +static DWORD +DeleteRoute (PMIB_IPFORWARD_ROW2 fwd_row) +{ + typedef NETIOAPI_API (*DeleteIpForwardEntry2Fn) (PMIB_IPFORWARD_ROW2); + static DeleteIpForwardEntry2Fn DeleteIpForwardEntry2 = NULL; + + if (!DeleteIpForwardEntry2) + { + HMODULE iphlpapi = GetModuleHandle (TEXT("iphlpapi.dll")); + if (iphlpapi == NULL) + return GetLastError (); + + DeleteIpForwardEntry2 = (DeleteIpForwardEntry2Fn) GetProcAddress (iphlpapi, "DeleteIpForwardEntry2"); + if (!DeleteIpForwardEntry2) + return GetLastError (); + } + + return DeleteIpForwardEntry2 (fwd_row); +} + +static DWORD +HandleRouteMessage (route_message_t *msg, undo_lists_t *lists) +{ + DWORD err; + PMIB_IPFORWARD_ROW2 fwd_row; + BOOL add = msg->header.type == msg_add_route; + + typedef NETIOAPI_API (*CreateIpForwardEntry2Fn) (PMIB_IPFORWARD_ROW2); + static CreateIpForwardEntry2Fn CreateIpForwardEntry2 = NULL; + + if (!CreateIpForwardEntry2) + { + HMODULE iphlpapi = GetModuleHandle (TEXT("iphlpapi.dll")); + if (iphlpapi == NULL) + return GetLastError (); + + CreateIpForwardEntry2 = (CreateIpForwardEntry2Fn) GetProcAddress (iphlpapi, "CreateIpForwardEntry2"); + if (!CreateIpForwardEntry2) + return GetLastError (); + } + + fwd_row = malloc (sizeof (*fwd_row)); + if (fwd_row == NULL) + return ERROR_OUTOFMEMORY; + + ZeroMemory (fwd_row, sizeof (*fwd_row)); + fwd_row->ValidLifetime = 0xffffffff; + fwd_row->PreferredLifetime = 0xffffffff; + fwd_row->Protocol = MIB_IPPROTO_NETMGMT; + fwd_row->Metric = msg->metric; + fwd_row->DestinationPrefix.Prefix = sockaddr_inet (msg->family, &msg->prefix); + fwd_row->DestinationPrefix.PrefixLength = (UINT8) msg->prefix_len; + fwd_row->NextHop = sockaddr_inet (msg->family, &msg->gateway); + + if (msg->iface.index != -1) + { + fwd_row->InterfaceIndex = msg->iface.index; + } + else if (strlen (msg->iface.name)) + { + NET_LUID luid; + err = InterfaceLuid (msg->iface.name, &luid); + if (err) + goto out; + fwd_row->InterfaceLuid = luid; + } + + if (add) + { + err = CreateIpForwardEntry2 (fwd_row); + if (err) + goto out; + + err = AddListItem (&(*lists)[route], fwd_row); + if (err) + DeleteRoute (fwd_row); + } + else + { + err = DeleteRoute (fwd_row); + if (err) + goto out; + + free (RemoveListItem (&(*lists)[route], CmpRoute, fwd_row)); + } + +out: + if (!add || err) + free (fwd_row); + + return err; +} + + +static DWORD +HandleFlushNeighborsMessage (flush_neighbors_message_t *msg) +{ + typedef NETIOAPI_API (*FlushIpNetTable2Fn) (ADDRESS_FAMILY, NET_IFINDEX); + static FlushIpNetTable2Fn flush_fn = NULL; + + if (msg->family == AF_INET) + return FlushIpNetTable (msg->iface.index); + + if (!flush_fn) + { + HMODULE iphlpapi = GetModuleHandle (TEXT("iphlpapi.dll")); + if (iphlpapi == NULL) + return GetLastError (); + + flush_fn = (FlushIpNetTable2Fn) GetProcAddress (iphlpapi, "FlushIpNetTable2"); + if (!flush_fn) + { + if (GetLastError () == ERROR_PROC_NOT_FOUND) + return WSAEPFNOSUPPORT; + else + return GetLastError (); + } + } + return flush_fn (msg->family, msg->iface.index); +} + +static void +BlockDNSErrHandler (DWORD err, const char *msg) +{ + TCHAR buf[256]; + LPCTSTR err_str; + + if (!err) return; + + err_str = TEXT("Unknown Win32 Error"); + + if (FormatMessage (FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM + | FORMAT_MESSAGE_ARGUMENT_ARRAY, + NULL, err, 0, buf, sizeof (buf), NULL)) + { + err_str = buf; + } + +#ifdef UNICODE + MsgToEventLog (M_ERR, L"%S (status = %lu): %s", msg, err, err_str); +#else + MsgToEventLog (M_ERR, "%s (status = %lu): %s", msg, err, err_str); +#endif + +} + +/* Use an always-true match_fn to get the head of the list */ +static BOOL +CmpEngine (LPVOID item, LPVOID any) +{ + return TRUE; +} + +static DWORD +HandleBlockDNSMessage (const block_dns_message_t *msg, undo_lists_t *lists) +{ + DWORD err = 0; + HANDLE engine = NULL; + LPCWSTR exe_path; + +#ifdef UNICODE + exe_path = settings.exe_path; +#else + WCHAR wide_path[MAX_PATH]; + MultiByteToWideChar (CP_UTF8, 0, settings.exe_path, MAX_PATH, wide_path, MAX_PATH); + exe_path = wide_path; +#endif + + if (msg->header.type == msg_add_block_dns) + { + err = add_block_dns_filters (&engine, msg->iface.index, exe_path, BlockDNSErrHandler); + if (!err) + err = AddListItem (&(*lists)[block_dns], engine); + } + else + { + engine = RemoveListItem (&(*lists)[block_dns], CmpEngine, NULL); + if (engine) + { + err = delete_block_dns_filters (engine); + engine = NULL; + } + else + MsgToEventLog (M_ERR, TEXT("No previous block DNS filters to delete")); + } + + if (err && engine) + { + delete_block_dns_filters (engine); + } + + return err; +} + +/* + * Execute a command and return its exit code. If timeout > 0, terminate + * the process if still running after timeout milliseconds. In that case + * the return value is the windows error code WAIT_TIMEOUT = 0x102 + */ +static DWORD +ExecCommand (const WCHAR *argv0, const WCHAR *cmdline, DWORD timeout) +{ + DWORD exit_code; + STARTUPINFOW si; + PROCESS_INFORMATION pi; + DWORD proc_flags = CREATE_NO_WINDOW|CREATE_UNICODE_ENVIRONMENT; + WCHAR *cmdline_dup = NULL; + + ZeroMemory (&si, sizeof(si)); + ZeroMemory (&pi, sizeof(pi)); + + si.cb = sizeof(si); + + /* CreateProcess needs a modifiable cmdline: make a copy */ + cmdline_dup = wcsdup (cmdline); + if ( cmdline_dup && CreateProcessW (argv0, cmdline_dup, NULL, NULL, FALSE, + proc_flags, NULL, NULL, &si, &pi) ) + { + WaitForSingleObject (pi.hProcess, timeout ? timeout : INFINITE); + if (!GetExitCodeProcess (pi.hProcess, &exit_code)) + { + MsgToEventLog (M_SYSERR, TEXT("ExecCommand: Error getting exit_code:")); + exit_code = GetLastError(); + } + else if (exit_code == STILL_ACTIVE) + { + exit_code = WAIT_TIMEOUT; /* Windows error code 0x102 */ + + /* kill without impunity */ + TerminateProcess (pi.hProcess, exit_code); + MsgToEventLog (M_ERR, TEXT("ExecCommand: \"%s %s\" killed after timeout"), + argv0, cmdline); + } + else if (exit_code) + MsgToEventLog (M_ERR, TEXT("ExecCommand: \"%s %s\" exited with status = %lu"), + argv0, cmdline, exit_code); + else + MsgToEventLog (M_INFO, TEXT("ExecCommand: \"%s %s\" completed"), argv0, cmdline); + + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + } + else + { + exit_code = GetLastError(); + MsgToEventLog (M_SYSERR, TEXT("ExecCommand: could not run \"%s %s\" :"), + argv0, cmdline); + } + + free (cmdline_dup); + return exit_code; +} + +/* + * Entry point for register-dns thread. + */ +static DWORD WINAPI +RegisterDNS (LPVOID unused) +{ + DWORD err; + DWORD i; + WCHAR sys_path[MAX_PATH]; + DWORD timeout = RDNS_TIMEOUT * 1000; /* in milliseconds */ + + /* default paths of net and ipconfig commands */ + WCHAR net[MAX_PATH] = L"C:\\Windows\\system32\\net.exe"; + WCHAR ipcfg[MAX_PATH] = L"C:\\Windows\\system32\\ipconfig.exe"; + + struct + { + WCHAR *argv0; + WCHAR *cmdline; + DWORD timeout; + } cmds [] = { + { net, L"net stop dnscache", timeout }, + { net, L"net start dnscache", timeout }, + { ipcfg, L"ipconfig /flushdns", timeout }, + { ipcfg, L"ipconfig /registerdns", timeout }, + }; + int ncmds = sizeof (cmds) / sizeof (cmds[0]); + + HANDLE wait_handles[2] = {rdns_semaphore, exit_event}; + + if(GetSystemDirectory(sys_path, MAX_PATH)) + { + _snwprintf (net, MAX_PATH, L"%s\\%s", sys_path, L"net.exe"); + net[MAX_PATH-1] = L'\0'; + + _snwprintf (ipcfg, MAX_PATH, L"%s\\%s", sys_path, L"ipconfig.exe"); + ipcfg[MAX_PATH-1] = L'\0'; + } + + if (WaitForMultipleObjects (2, wait_handles, FALSE, timeout) == WAIT_OBJECT_0) + { + /* Semaphore locked */ + for (i = 0; i < ncmds; ++i) + { + ExecCommand (cmds[i].argv0, cmds[i].cmdline, cmds[i].timeout); + } + err = 0; + if ( !ReleaseSemaphore (rdns_semaphore, 1, NULL) ) + err = MsgToEventLog (M_SYSERR, TEXT("RegisterDNS: Failed to release regsiter-dns semaphore:")); + } + else + { + MsgToEventLog (M_ERR, TEXT("RegisterDNS: Failed to lock register-dns semaphore")); + err = ERROR_SEM_TIMEOUT; /* Windows error code 0x79 */ + } + return err; +} + +static DWORD +HandleRegisterDNSMessage (void) +{ + DWORD err; + HANDLE thread = NULL; + + /* Delegate this job to a sub-thread */ + thread = CreateThread (NULL, 0, RegisterDNS, NULL, 0, NULL); + + /* + * We don't add these thread handles to the undo list -- the thread and + * processes it spawns are all supposed to terminate or timeout by themselves. + */ + if (thread) + { + err = 0; + CloseHandle (thread); + } + else + err = GetLastError(); + + return err; +} + +static VOID +HandleMessage (HANDLE pipe, DWORD bytes, DWORD count, LPHANDLE events, undo_lists_t *lists) +{ + DWORD read; + union { + message_header_t header; + address_message_t address; + route_message_t route; + flush_neighbors_message_t flush_neighbors; + block_dns_message_t block_dns; + } msg; + ack_message_t ack = { + .header = { + .type = msg_acknowledgement, + .size = sizeof (ack), + .message_id = -1 + }, + .error_number = ERROR_MESSAGE_DATA + }; + + read = ReadPipeAsync (pipe, &msg, bytes, count, events); + if (read != bytes || read < sizeof (msg.header) || read != msg.header.size) + goto out; + + ack.header.message_id = msg.header.message_id; + + switch (msg.header.type) + { + case msg_add_address: + case msg_del_address: + if (msg.header.size == sizeof (msg.address)) + ack.error_number = HandleAddressMessage (&msg.address, lists); + break; + + case msg_add_route: + case msg_del_route: + if (msg.header.size == sizeof (msg.route)) + ack.error_number = HandleRouteMessage (&msg.route, lists); + break; + + case msg_flush_neighbors: + if (msg.header.size == sizeof (msg.flush_neighbors)) + ack.error_number = HandleFlushNeighborsMessage (&msg.flush_neighbors); + break; + + case msg_add_block_dns: + case msg_del_block_dns: + if (msg.header.size == sizeof (msg.block_dns)) + ack.error_number = HandleBlockDNSMessage (&msg.block_dns, lists); + break; + + case msg_register_dns: + ack.error_number = HandleRegisterDNSMessage (); + break; + + default: + ack.error_number = ERROR_MESSAGE_TYPE; + MsgToEventLog (MSG_FLAGS_ERROR, TEXT("Unknown message type %d"), msg.header.type); + break; + } + +out: + WritePipeAsync (pipe, &ack, sizeof (ack), count, events); +} + + +static VOID +Undo (undo_lists_t *lists) +{ + undo_type_t type; + for (type = 0; type < _undo_type_max; type++) + { + list_item_t **pnext = &(*lists)[type]; + while (*pnext) + { + list_item_t *item = *pnext; + switch (type) + { + case address: + DeleteAddress (item->data); + break; + + case route: + DeleteRoute (item->data); + break; + + case block_dns: + delete_block_dns_filters (item->data); + item->data = NULL; + break; + } + + /* Remove from the list and free memory */ + *pnext = item->next; + free (item->data); + free (item); + } + } +} + +static DWORD WINAPI +RunOpenvpn (LPVOID p) +{ + HANDLE pipe = p; + HANDLE ovpn_pipe, svc_pipe; + PTOKEN_USER svc_user, ovpn_user; + HANDLE svc_token = NULL, imp_token = NULL, pri_token = NULL; + HANDLE stdin_read = NULL, stdin_write = NULL; + HANDLE stdout_write = NULL; + DWORD pipe_mode, len, exit_code = 0; + STARTUP_DATA sud = { 0, 0, 0 }; + STARTUPINFOW startup_info; + PROCESS_INFORMATION proc_info; + LPVOID user_env = NULL; + TCHAR ovpn_pipe_name[36]; + LPCWSTR exe_path; + WCHAR *cmdline = NULL; + size_t cmdline_size; + undo_lists_t undo_lists; + + SECURITY_ATTRIBUTES inheritable = { + .nLength = sizeof (inheritable), + .lpSecurityDescriptor = NULL, + .bInheritHandle = TRUE + }; + + PACL ovpn_dacl; + EXPLICIT_ACCESS ea[2]; + SECURITY_DESCRIPTOR ovpn_sd; + SECURITY_ATTRIBUTES ovpn_sa = { + .nLength = sizeof (ovpn_sa), + .lpSecurityDescriptor = &ovpn_sd, + .bInheritHandle = FALSE + }; + + ZeroMemory (&ea, sizeof (ea)); + ZeroMemory (&startup_info, sizeof (startup_info)); + ZeroMemory (&undo_lists, sizeof (undo_lists)); + ZeroMemory (&proc_info, sizeof (proc_info)); + + if (!GetStartupData (pipe, &sud)) + goto out; + + if (!InitializeSecurityDescriptor (&ovpn_sd, SECURITY_DESCRIPTOR_REVISION)) + { + ReturnLastError (pipe, L"InitializeSecurityDescriptor"); + goto out; + } + + /* Get SID of user the service is running under */ + if (!OpenProcessToken (GetCurrentProcess (), TOKEN_QUERY, &svc_token)) + { + ReturnLastError (pipe, L"OpenProcessToken"); + goto out; + } + len = 0; + svc_user = NULL; + while (!GetTokenInformation (svc_token, TokenUser, svc_user, len, &len)) + { + if (GetLastError () != ERROR_INSUFFICIENT_BUFFER) + { + ReturnLastError (pipe, L"GetTokenInformation (service token)"); + goto out; + } + free (svc_user); + svc_user = malloc (len); + if (svc_user == NULL) + { + ReturnLastError (pipe, L"malloc (service token user)"); + goto out; + } + } + if (!IsValidSid (svc_user->User.Sid)) + { + ReturnLastError (pipe, L"IsValidSid (service token user)"); + goto out; + } + + if (!ImpersonateNamedPipeClient (pipe)) + { + ReturnLastError (pipe, L"ImpersonateNamedPipeClient"); + goto out; + } + if (!OpenThreadToken (GetCurrentThread (), TOKEN_ALL_ACCESS, FALSE, &imp_token)) + { + ReturnLastError (pipe, L"OpenThreadToken"); + goto out; + } + len = 0; + ovpn_user = NULL; + while (!GetTokenInformation (imp_token, TokenUser, ovpn_user, len, &len)) + { + if (GetLastError () != ERROR_INSUFFICIENT_BUFFER) + { + ReturnLastError (pipe, L"GetTokenInformation (impersonation token)"); + goto out; + } + free (ovpn_user); + ovpn_user = malloc (len); + if (ovpn_user == NULL) + { + ReturnLastError (pipe, L"malloc (impersonation token user)"); + goto out; + } + } + if (!IsValidSid (ovpn_user->User.Sid)) + { + ReturnLastError (pipe, L"IsValidSid (impersonation token user)"); + goto out; + } + + /* Check user is authorized or options are white-listed */ + if (!IsAuthorizedUser (ovpn_user->User.Sid, &settings) && + !ValidateOptions (pipe, sud.directory, sud.options)) + { + goto out; + } + + /* OpenVPN process DACL entry for access by service and user */ + ea[0].grfAccessPermissions = SPECIFIC_RIGHTS_ALL | STANDARD_RIGHTS_ALL; + ea[0].grfAccessMode = SET_ACCESS; + ea[0].grfInheritance = NO_INHERITANCE; + ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID; + ea[0].Trustee.TrusteeType = TRUSTEE_IS_UNKNOWN; + ea[0].Trustee.ptstrName = (LPTSTR) svc_user->User.Sid; + ea[1].grfAccessPermissions = READ_CONTROL | SYNCHRONIZE | PROCESS_VM_READ | + SYNCHRONIZE | PROCESS_TERMINATE | PROCESS_QUERY_INFORMATION; + ea[1].grfAccessMode = SET_ACCESS; + ea[1].grfInheritance = NO_INHERITANCE; + ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID; + ea[1].Trustee.TrusteeType = TRUSTEE_IS_UNKNOWN; + ea[1].Trustee.ptstrName = (LPTSTR) ovpn_user->User.Sid; + + /* Set owner and DACL of OpenVPN security descriptor */ + if (!SetSecurityDescriptorOwner (&ovpn_sd, svc_user->User.Sid, FALSE)) + { + ReturnLastError (pipe, L"SetSecurityDescriptorOwner"); + goto out; + } + if (SetEntriesInAcl (2, ea, NULL, &ovpn_dacl) != ERROR_SUCCESS) + { + ReturnLastError (pipe, L"SetEntriesInAcl"); + goto out; + } + if (!SetSecurityDescriptorDacl (&ovpn_sd, TRUE, ovpn_dacl, FALSE)) + { + ReturnLastError (pipe, L"SetSecurityDescriptorDacl"); + goto out; + } + + /* Create primary token from impersonation token */ + if (!DuplicateTokenEx (imp_token, TOKEN_ALL_ACCESS, NULL, 0, TokenPrimary, &pri_token)) + { + ReturnLastError (pipe, L"DuplicateTokenEx"); + goto out; + } + + /* use /dev/null for stdout of openvpn (client should use --log for output) */ + stdout_write = CreateFile(_T("NUL"), GENERIC_WRITE, FILE_SHARE_WRITE, + &inheritable, OPEN_EXISTING, 0, NULL); + if (stdout_write == INVALID_HANDLE_VALUE) + { + ReturnLastError (pipe, L"CreateFile for stdout"); + goto out; + } + + if (!CreatePipe(&stdin_read, &stdin_write, &inheritable, 0) || + !SetHandleInformation(stdin_write, HANDLE_FLAG_INHERIT, 0)) + { + ReturnLastError (pipe, L"CreatePipe"); + goto out; + } + + openvpn_sntprintf (ovpn_pipe_name, _countof (ovpn_pipe_name), + TEXT("\\\\.\\pipe\\openvpn\\service_%lu"), GetCurrentThreadId ()); + ovpn_pipe = CreateNamedPipe (ovpn_pipe_name, + PIPE_ACCESS_DUPLEX | FILE_FLAG_FIRST_PIPE_INSTANCE | FILE_FLAG_OVERLAPPED, + PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, 1, 128, 128, 0, NULL); + if (ovpn_pipe == INVALID_HANDLE_VALUE) + { + ReturnLastError (pipe, L"CreateNamedPipe"); + goto out; + } + + svc_pipe = CreateFile (ovpn_pipe_name, GENERIC_READ | GENERIC_WRITE, 0, + &inheritable, OPEN_EXISTING, 0, NULL); + if (svc_pipe == INVALID_HANDLE_VALUE) + { + ReturnLastError (pipe, L"CreateFile"); + goto out; + } + + pipe_mode = PIPE_READMODE_MESSAGE; + if (!SetNamedPipeHandleState (svc_pipe, &pipe_mode, NULL, NULL)) + { + ReturnLastError (pipe, L"SetNamedPipeHandleState"); + goto out; + } + + cmdline_size = wcslen (sud.options) + 128; + cmdline = malloc (cmdline_size * sizeof (*cmdline)); + if (cmdline == NULL) + { + ReturnLastError (pipe, L"malloc"); + goto out; + } + openvpn_sntprintf (cmdline, cmdline_size, L"openvpn %s --msg-channel %lu", + sud.options, svc_pipe); + + if (!CreateEnvironmentBlock (&user_env, imp_token, FALSE)) + { + ReturnLastError (pipe, L"CreateEnvironmentBlock"); + goto out; + } + + startup_info.cb = sizeof (startup_info); + startup_info.lpDesktop = L"winsta0\\default"; + startup_info.dwFlags = STARTF_USESTDHANDLES; + startup_info.hStdInput = stdin_read; + startup_info.hStdOutput = stdout_write; + startup_info.hStdError = stdout_write; + +#ifdef UNICODE + exe_path = settings.exe_path; +#else + WCHAR wide_path[MAX_PATH]; + MultiByteToWideChar (CP_UTF8, 0, settings.exe_path, MAX_PATH, wide_path, MAX_PATH); + exe_path = wide_path; +#endif + + // TODO: make sure HKCU is correct or call LoadUserProfile() + if (!CreateProcessAsUserW (pri_token, exe_path, cmdline, &ovpn_sa, NULL, TRUE, + settings.priority | CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT, + user_env, sud.directory, &startup_info, &proc_info)) + { + ReturnLastError (pipe, L"CreateProcessAsUser"); + goto out; + } + + if (!RevertToSelf ()) + { + TerminateProcess (proc_info.hProcess, 1); + ReturnLastError (pipe, L"RevertToSelf"); + goto out; + } + + ReturnProcessId (pipe, proc_info.dwProcessId, 1, &exit_event); + + CloseHandleEx (&stdout_write); + CloseHandleEx (&stdin_read); + CloseHandleEx (&svc_pipe); + + DWORD input_size = WideCharToMultiByte (CP_UTF8, 0, sud.std_input, -1, NULL, 0, NULL, NULL); + LPSTR input = NULL; + if (input_size && (input = malloc (input_size))) + { + DWORD written; + WideCharToMultiByte (CP_UTF8, 0, sud.std_input, -1, input, input_size, NULL, NULL); + WriteFile (stdin_write, input, strlen (input), &written, NULL); + free (input); + } + + while (TRUE) + { + DWORD bytes = PeekNamedPipeAsync (ovpn_pipe, 1, &exit_event); + if (bytes == 0) + break; + + HandleMessage (ovpn_pipe, bytes, 1, &exit_event, &undo_lists); + } + + WaitForSingleObject (proc_info.hProcess, IO_TIMEOUT); + GetExitCodeProcess (proc_info.hProcess, &exit_code); + if (exit_code == STILL_ACTIVE) + TerminateProcess (proc_info.hProcess, 1); + else if (exit_code != 0) + { + WCHAR buf[256]; + int len = _snwprintf (buf, _countof (buf), + L"OpenVPN exited with error: exit code = %lu", exit_code); + buf[_countof (buf) - 1] = L'\0'; + ReturnError (pipe, ERROR_OPENVPN_STARTUP, buf, 1, &exit_event); + } + Undo (&undo_lists); + +out: + FlushFileBuffers (pipe); + DisconnectNamedPipe (pipe); + + free (ovpn_user); + free (svc_user); + free (cmdline); + DestroyEnvironmentBlock (user_env); + FreeStartupData (&sud); + CloseHandleEx (&proc_info.hProcess); + CloseHandleEx (&proc_info.hThread); + CloseHandleEx (&stdin_read); + CloseHandleEx (&stdin_write); + CloseHandleEx (&stdout_write); + CloseHandleEx (&svc_token); + CloseHandleEx (&imp_token); + CloseHandleEx (&pri_token); + CloseHandleEx (&ovpn_pipe); + CloseHandleEx (&svc_pipe); + CloseHandleEx (&pipe); + + return 0; +} + + +static DWORD WINAPI +ServiceCtrlInteractive (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; + } +} + + +static HANDLE +CreateClientPipeInstance (VOID) +{ + HANDLE pipe = NULL; + PACL old_dacl, new_dacl; + PSECURITY_DESCRIPTOR sd; + static EXPLICIT_ACCESS ea[2]; + static BOOL initialized = FALSE; + DWORD flags = PIPE_ACCESS_DUPLEX | WRITE_DAC | FILE_FLAG_OVERLAPPED; + + if (!initialized) + { + PSID everyone, anonymous; + + ConvertStringSidToSid (TEXT("S-1-1-0"), &everyone); + ConvertStringSidToSid (TEXT("S-1-5-7"), &anonymous); + + ea[0].grfAccessPermissions = FILE_GENERIC_WRITE; + ea[0].grfAccessMode = GRANT_ACCESS; + ea[0].grfInheritance = NO_INHERITANCE; + ea[0].Trustee.pMultipleTrustee = NULL; + ea[0].Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE; + ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID; + ea[0].Trustee.TrusteeType = TRUSTEE_IS_UNKNOWN; + ea[0].Trustee.ptstrName = (LPTSTR) everyone; + + ea[1].grfAccessPermissions = 0; + ea[1].grfAccessMode = REVOKE_ACCESS; + ea[1].grfInheritance = NO_INHERITANCE; + ea[1].Trustee.pMultipleTrustee = NULL; + ea[1].Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE; + ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID; + ea[1].Trustee.TrusteeType = TRUSTEE_IS_UNKNOWN; + ea[1].Trustee.ptstrName = (LPTSTR) anonymous; + + flags |= FILE_FLAG_FIRST_PIPE_INSTANCE; + initialized = TRUE; + } + + pipe = CreateNamedPipe (TEXT("\\\\.\\pipe\\openvpn\\service"), flags, + PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE, + PIPE_UNLIMITED_INSTANCES, 1024, 1024, 0, NULL); + if (pipe == INVALID_HANDLE_VALUE) + { + MsgToEventLog (M_SYSERR, TEXT("Could not create named pipe")); + return INVALID_HANDLE_VALUE; + } + + if (GetSecurityInfo (pipe, SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION, + NULL, NULL, &old_dacl, NULL, &sd) != ERROR_SUCCESS) + { + MsgToEventLog (M_SYSERR, TEXT("Could not get pipe security info")); + return CloseHandleEx (&pipe); + } + + if (SetEntriesInAcl (2, ea, old_dacl, &new_dacl) != ERROR_SUCCESS) + { + MsgToEventLog (M_SYSERR, TEXT("Could not set entries in new acl")); + return CloseHandleEx (&pipe); + } + + if (SetSecurityInfo (pipe, SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION, + NULL, NULL, new_dacl, NULL) != ERROR_SUCCESS) + { + MsgToEventLog (M_SYSERR, TEXT("Could not set pipe security info")); + return CloseHandleEx (&pipe); + } + + return pipe; +} + + +static DWORD +UpdateWaitHandles (LPHANDLE *handles_ptr, LPDWORD count, + HANDLE io_event, HANDLE exit_event, list_item_t *threads) +{ + static DWORD size = 10; + static LPHANDLE handles = NULL; + DWORD pos = 0; + + if (handles == NULL) + { + handles = malloc (size * sizeof (HANDLE)); + *handles_ptr = handles; + if (handles == NULL) + return ERROR_OUTOFMEMORY; + } + + handles[pos++] = io_event; + + if (!threads) + handles[pos++] = exit_event; + + while (threads) + { + if (pos == size) + { + LPHANDLE tmp; + size += 10; + tmp = realloc (handles, size * sizeof (HANDLE)); + if (tmp == NULL) + { + size -= 10; + *count = pos; + return ERROR_OUTOFMEMORY; + } + handles = tmp; + *handles_ptr = handles; + } + handles[pos++] = threads->data; + threads = threads->next; + } + + *count = pos; + return NO_ERROR; +} + + +static VOID +FreeWaitHandles (LPHANDLE h) +{ + free (h); +} + + +VOID WINAPI +ServiceStartInteractive (DWORD dwArgc, LPTSTR *lpszArgv) +{ + HANDLE pipe, io_event = NULL; + OVERLAPPED overlapped; + DWORD error = NO_ERROR; + list_item_t *threads = NULL; + PHANDLE handles = NULL; + DWORD handle_count; + BOOL CmpHandle (LPVOID item, LPVOID hnd) { return item == hnd; } + + service = RegisterServiceCtrlHandlerEx (interactive_service.name, ServiceCtrlInteractive, &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; + ReportStatusToSCMgr (service, &status); + + /* Read info from registry in key HKLM\SOFTWARE\OpenVPN */ + error = GetOpenvpnSettings (&settings); + if (error != ERROR_SUCCESS) + goto out; + + io_event = InitOverlapped (&overlapped); + exit_event = CreateEvent (NULL, TRUE, FALSE, NULL); + if (!exit_event || !io_event) + { + error = MsgToEventLog (M_SYSERR, TEXT("Could not create event")); + goto out; + } + + rdns_semaphore = CreateSemaphoreW (NULL, 1, 1, NULL); + if (!rdns_semaphore) + { + error = MsgToEventLog (M_SYSERR, TEXT("Could not create semaphore for register-dns")); + goto out; + } + + error = UpdateWaitHandles (&handles, &handle_count, io_event, exit_event, threads); + if (error != NO_ERROR) + goto out; + + pipe = CreateClientPipeInstance (); + if (pipe == INVALID_HANDLE_VALUE) + goto out; + + status.dwCurrentState = SERVICE_RUNNING; + status.dwWaitHint = 0; + ReportStatusToSCMgr (service, &status); + + while (TRUE) + { + if (ConnectNamedPipe (pipe, &overlapped) == FALSE && + GetLastError () != ERROR_PIPE_CONNECTED && + GetLastError () != ERROR_IO_PENDING) + { + MsgToEventLog (M_SYSERR, TEXT("Could not connect pipe")); + break; + } + + error = WaitForMultipleObjects (handle_count, handles, FALSE, INFINITE); + if (error == WAIT_OBJECT_0) + { + /* Client connected, spawn a worker thread for it */ + HANDLE next_pipe = CreateClientPipeInstance (); + HANDLE thread = CreateThread (NULL, 0, RunOpenvpn, pipe, CREATE_SUSPENDED, NULL); + if (thread) + { + error = AddListItem (&threads, thread); + if (!error) + error = UpdateWaitHandles (&handles, &handle_count, io_event, exit_event, threads); + if (error) + { + ReturnError (pipe, error, L"Insufficient resources to service new clients", 1, &exit_event); + /* Update wait handles again after removing the last worker thread */ + RemoveListItem (&threads, CmpHandle, thread); + UpdateWaitHandles (&handles, &handle_count, io_event, exit_event, threads); + TerminateThread (thread, 1); + CloseHandleEx (&thread); + CloseHandleEx (&pipe); + } + else + ResumeThread (thread); + } + else + CloseHandleEx (&pipe); + + ResetOverlapped (&overlapped); + pipe = next_pipe; + } + else + { + CancelIo (pipe); + if (error == WAIT_FAILED) + { + MsgToEventLog (M_SYSERR, TEXT("WaitForMultipleObjects failed")); + SetEvent (exit_event); + /* Give some time for worker threads to exit and then terminate */ + Sleep (1000); + break; + } + if (!threads) + { + /* exit event signaled */ + CloseHandleEx (&pipe); + ResetEvent (exit_event); + error = NO_ERROR; + break; + } + + /* Worker thread ended */ + HANDLE thread = RemoveListItem (&threads, CmpHandle, handles[error]); + UpdateWaitHandles (&handles, &handle_count, io_event, exit_event, threads); + CloseHandleEx (&thread); + } + } + +out: + FreeWaitHandles (handles); + CloseHandleEx (&io_event); + CloseHandleEx (&exit_event); + CloseHandleEx (&rdns_semaphore); + + status.dwCurrentState = SERVICE_STOPPED; + status.dwWin32ExitCode = error; + ReportStatusToSCMgr (service, &status); +} |