a24dd2e3 |
/*
* 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.
* |
49979459 |
* Copyright (C) 2012-2018 Heiko Hund <heiko.hund@sophos.com> |
a24dd2e3 |
*
* 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.
* |
caa54ac3 |
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
a24dd2e3 |
*/
#include "service.h"
#include <ws2tcpip.h>
#include <iphlpapi.h>
#include <userenv.h>
#include <accctrl.h>
#include <aclapi.h>
#include <stdio.h>
#include <sddl.h> |
f3c8a04d |
#include <shellapi.h> |
55305a2f |
#include <mstcpip.h> |
a24dd2e3 |
|
c098016a |
#ifdef HAVE_VERSIONHELPERS_H
#include <versionhelpers.h>
#else
#include "compat-versionhelpers.h"
#endif
|
a24dd2e3 |
#include "openvpn-msg.h" |
f3c8a04d |
#include "validate.h" |
2282b1be |
#include "block_dns.h" |
a24dd2e3 |
#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; |
f3fec49b |
static SERVICE_STATUS status = { .dwServiceType = SERVICE_WIN32_SHARE_PROCESS }; |
a24dd2e3 |
static HANDLE exit_event = NULL;
static settings_t settings; |
3e42a558 |
static HANDLE rdns_semaphore = NULL;
#define RDNS_TIMEOUT 600 /* seconds to wait for the semaphore */
|
a24dd2e3 |
openvpn_service_t interactive_service = { |
81d882d5 |
interactive,
TEXT(PACKAGE_NAME "ServiceInteractive"),
TEXT(PACKAGE_NAME " Interactive Service"),
TEXT(SERVICE_DEPENDENCIES),
SERVICE_AUTO_START |
a24dd2e3 |
};
typedef struct { |
81d882d5 |
WCHAR *directory;
WCHAR *options;
WCHAR *std_input; |
a24dd2e3 |
} STARTUP_DATA;
/* Datatype for linked lists */
typedef struct _list_item { |
81d882d5 |
struct _list_item *next;
LPVOID data; |
a24dd2e3 |
} list_item_t;
/* Datatypes for undo information */
typedef enum { |
81d882d5 |
address,
route,
block_dns,
undo_dns4,
undo_dns6,
_undo_type_max |
a24dd2e3 |
} undo_type_t; |
81d882d5 |
typedef list_item_t *undo_lists_t[_undo_type_max]; |
a24dd2e3 |
|
27aa8728 |
typedef struct {
HANDLE engine;
int index;
int metric_v4;
int metric_v6;
} block_dns_data_t;
|
a24dd2e3 |
static DWORD |
81d882d5 |
AddListItem(list_item_t **pfirst, LPVOID data) |
a24dd2e3 |
{ |
81d882d5 |
list_item_t *new_item = malloc(sizeof(list_item_t));
if (new_item == NULL)
{
return ERROR_OUTOFMEMORY;
} |
a24dd2e3 |
|
81d882d5 |
new_item->next = *pfirst;
new_item->data = data; |
a24dd2e3 |
|
81d882d5 |
*pfirst = new_item;
return NO_ERROR; |
a24dd2e3 |
}
typedef BOOL (*match_fn_t) (LPVOID item, LPVOID ctx);
static LPVOID |
81d882d5 |
RemoveListItem(list_item_t **pfirst, match_fn_t match, LPVOID ctx) |
a24dd2e3 |
{ |
81d882d5 |
LPVOID data = NULL;
list_item_t **pnext; |
a24dd2e3 |
|
81d882d5 |
for (pnext = pfirst; *pnext; pnext = &(*pnext)->next) |
a24dd2e3 |
{ |
81d882d5 |
list_item_t *item = *pnext;
if (!match(item->data, ctx))
{
continue;
} |
a24dd2e3 |
|
81d882d5 |
/* Found item, remove from the list and free memory */
*pnext = item->next;
data = item->data;
free(item);
break; |
a24dd2e3 |
} |
81d882d5 |
return data; |
a24dd2e3 |
}
static HANDLE |
81d882d5 |
CloseHandleEx(LPHANDLE handle) |
a24dd2e3 |
{ |
81d882d5 |
if (handle && *handle && *handle != INVALID_HANDLE_VALUE) |
a24dd2e3 |
{ |
81d882d5 |
CloseHandle(*handle);
*handle = INVALID_HANDLE_VALUE; |
a24dd2e3 |
} |
81d882d5 |
return INVALID_HANDLE_VALUE; |
a24dd2e3 |
}
static HANDLE |
81d882d5 |
InitOverlapped(LPOVERLAPPED overlapped) |
a24dd2e3 |
{ |
81d882d5 |
ZeroMemory(overlapped, sizeof(OVERLAPPED));
overlapped->hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
return overlapped->hEvent; |
a24dd2e3 |
}
static BOOL |
81d882d5 |
ResetOverlapped(LPOVERLAPPED overlapped) |
a24dd2e3 |
{ |
81d882d5 |
HANDLE io_event = overlapped->hEvent;
if (!ResetEvent(io_event))
{
return FALSE;
}
ZeroMemory(overlapped, sizeof(OVERLAPPED));
overlapped->hEvent = io_event;
return TRUE; |
a24dd2e3 |
}
typedef enum { |
81d882d5 |
peek,
read,
write |
a24dd2e3 |
} async_op_t;
static DWORD |
81d882d5 |
AsyncPipeOp(async_op_t op, HANDLE pipe, LPVOID buffer, DWORD size, DWORD count, LPHANDLE events) |
a24dd2e3 |
{ |
f755c992 |
DWORD i; |
81d882d5 |
BOOL success;
HANDLE io_event;
DWORD res, bytes = 0;
OVERLAPPED overlapped;
LPHANDLE handles = NULL; |
a24dd2e3 |
|
81d882d5 |
io_event = InitOverlapped(&overlapped);
if (!io_event)
{
goto out;
} |
a24dd2e3 |
|
81d882d5 |
handles = malloc((count + 1) * sizeof(HANDLE));
if (!handles)
{
goto out;
} |
a24dd2e3 |
|
81d882d5 |
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;
} |
a24dd2e3 |
|
81d882d5 |
handles[0] = io_event;
for (i = 0; i < count; i++) |
4cd4899e |
{ |
81d882d5 |
handles[i + 1] = events[i]; |
4cd4899e |
} |
a24dd2e3 |
|
81d882d5 |
res = WaitForMultipleObjects(count + 1, handles, FALSE,
op == peek ? INFINITE : IO_TIMEOUT);
if (res != WAIT_OBJECT_0) |
a24dd2e3 |
{ |
81d882d5 |
CancelIo(pipe);
goto out; |
a24dd2e3 |
}
|
81d882d5 |
if (op == peek)
{
PeekNamedPipe(pipe, NULL, 0, NULL, &bytes, NULL);
}
else
{
GetOverlappedResult(pipe, &overlapped, &bytes, TRUE);
} |
a24dd2e3 |
out: |
81d882d5 |
CloseHandleEx(&io_event);
free(handles);
return bytes; |
a24dd2e3 |
}
static DWORD |
81d882d5 |
PeekNamedPipeAsync(HANDLE pipe, DWORD count, LPHANDLE events) |
a24dd2e3 |
{ |
81d882d5 |
return AsyncPipeOp(peek, pipe, NULL, 0, count, events); |
a24dd2e3 |
}
static DWORD |
81d882d5 |
ReadPipeAsync(HANDLE pipe, LPVOID buffer, DWORD size, DWORD count, LPHANDLE events) |
a24dd2e3 |
{ |
81d882d5 |
return AsyncPipeOp(read, pipe, buffer, size, count, events); |
a24dd2e3 |
}
static DWORD |
81d882d5 |
WritePipeAsync(HANDLE pipe, LPVOID data, DWORD size, DWORD count, LPHANDLE events) |
a24dd2e3 |
{ |
81d882d5 |
return AsyncPipeOp(write, pipe, data, size, count, events); |
a24dd2e3 |
}
|
e4c9bbe6 |
static VOID |
81d882d5 |
ReturnProcessId(HANDLE pipe, DWORD pid, DWORD count, LPHANDLE events) |
e4c9bbe6 |
{ |
81d882d5 |
const WCHAR msg[] = L"Process ID";
WCHAR buf[22 + _countof(msg)]; /* 10 chars each for error and PID and 2 for line breaks */ |
e4c9bbe6 |
|
81d882d5 |
/*
* 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
*/ |
43a5a4f3 |
openvpn_swprintf(buf, _countof(buf), L"0x%08x\n0x%08x\n%s", 0, pid, msg); |
e4c9bbe6 |
|
02fa8565 |
WritePipeAsync(pipe, buf, (DWORD)(wcslen(buf) * 2), count, events); |
e4c9bbe6 |
} |
a24dd2e3 |
static VOID |
81d882d5 |
ReturnError(HANDLE pipe, DWORD error, LPCWSTR func, DWORD count, LPHANDLE events) |
a24dd2e3 |
{ |
81d882d5 |
DWORD result_len;
LPWSTR result = L"0xffffffff\nFormatMessage failed\nCould not return result";
DWORD_PTR args[] = {
(DWORD_PTR) error,
(DWORD_PTR) func,
(DWORD_PTR) ""
}; |
a24dd2e3 |
|
81d882d5 |
if (error != ERROR_OPENVPN_STARTUP) |
a24dd2e3 |
{ |
81d882d5 |
FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM
|FORMAT_MESSAGE_ALLOCATE_BUFFER
|FORMAT_MESSAGE_IGNORE_INSERTS,
0, error, 0, (LPWSTR) &args[2], 0, NULL); |
a24dd2e3 |
}
|
81d882d5 |
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); |
a24dd2e3 |
|
02fa8565 |
WritePipeAsync(pipe, result, (DWORD)(wcslen(result) * 2), count, events); |
a24dd2e3 |
#ifdef UNICODE |
81d882d5 |
MsgToEventLog(MSG_FLAGS_ERROR, result); |
a24dd2e3 |
#else |
81d882d5 |
MsgToEventLog(MSG_FLAGS_ERROR, "%S", result); |
a24dd2e3 |
#endif
|
81d882d5 |
if (error != ERROR_OPENVPN_STARTUP)
{
LocalFree((LPVOID) args[2]);
}
if (result_len)
{
LocalFree(result);
} |
a24dd2e3 |
}
static VOID |
81d882d5 |
ReturnLastError(HANDLE pipe, LPCWSTR func) |
a24dd2e3 |
{ |
81d882d5 |
ReturnError(pipe, GetLastError(), func, 1, &exit_event); |
a24dd2e3 |
}
static VOID |
81d882d5 |
ReturnOpenvpnOutput(HANDLE pipe, HANDLE ovpn_output, DWORD count, LPHANDLE events) |
a24dd2e3 |
{ |
81d882d5 |
WCHAR *wide_output = NULL;
CHAR output[512];
DWORD size; |
a24dd2e3 |
|
81d882d5 |
ReadFile(ovpn_output, output, sizeof(output), &size, NULL);
if (size == 0)
{
return;
} |
a24dd2e3 |
|
81d882d5 |
wide_output = malloc((size) * sizeof(WCHAR));
if (wide_output) |
a24dd2e3 |
{ |
81d882d5 |
MultiByteToWideChar(CP_UTF8, 0, output, size, wide_output, size);
wide_output[size - 1] = 0; |
a24dd2e3 |
}
|
81d882d5 |
ReturnError(pipe, ERROR_OPENVPN_STARTUP, wide_output, count, events);
free(wide_output); |
a24dd2e3 |
}
|
f3c8a04d |
/*
* 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 |
81d882d5 |
ValidateOptions(HANDLE pipe, const WCHAR *workdir, const WCHAR *options) |
f3c8a04d |
{
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)" |
d6e09179 |
L" that requires admin approval. This error may be avoided"
L" by adding your account to the \"%s\" group"; |
f3c8a04d |
const WCHAR *msg2 = L"You have specified an option (%s) that may be used" |
d6e09179 |
L" only with admin approval. This error may be avoided"
L" by adding your account to the \"%s\" group"; |
f3c8a04d |
|
81d882d5 |
argv = CommandLineToArgvW(options, &argc); |
f3c8a04d |
if (!argv)
{ |
81d882d5 |
ReturnLastError(pipe, L"CommandLineToArgvW");
ReturnError(pipe, ERROR_STARTUP_DATA, L"Cannot validate options", 1, &exit_event); |
f3c8a04d |
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] };
|
81d882d5 |
if (!CheckOption(workdir, 2, argv_tmp, &settings)) |
f3c8a04d |
{ |
43a5a4f3 |
openvpn_swprintf(buf, _countof(buf), msg1, argv[0], workdir,
settings.ovpn_admin_group); |
81d882d5 |
ReturnError(pipe, ERROR_STARTUP_DATA, buf, 1, &exit_event); |
f3c8a04d |
}
goto out;
}
for (i = 0; i < argc; ++i)
{
if (!IsOption(argv[i])) |
81d882d5 |
{ |
f3c8a04d |
continue; |
81d882d5 |
} |
f3c8a04d |
|
81d882d5 |
if (!CheckOption(workdir, argc-i, &argv[i], &settings)) |
f3c8a04d |
{
if (wcscmp(L"--config", argv[i]) == 0 && argc-i > 1)
{ |
43a5a4f3 |
openvpn_swprintf(buf, _countof(buf), msg1, argv[i+1], workdir,
settings.ovpn_admin_group); |
81d882d5 |
ReturnError(pipe, ERROR_STARTUP_DATA, buf, 1, &exit_event); |
f3c8a04d |
}
else
{ |
43a5a4f3 |
openvpn_swprintf(buf, _countof(buf), msg2, argv[i],
settings.ovpn_admin_group); |
81d882d5 |
ReturnError(pipe, ERROR_STARTUP_DATA, buf, 1, &exit_event); |
f3c8a04d |
}
goto out;
}
}
/* all options passed */
ret = TRUE;
out:
if (argv) |
81d882d5 |
{
LocalFree(argv);
} |
f3c8a04d |
return ret;
} |
a24dd2e3 |
static BOOL |
81d882d5 |
GetStartupData(HANDLE pipe, STARTUP_DATA *sud) |
a24dd2e3 |
{ |
02fa8565 |
size_t size, len; |
81d882d5 |
WCHAR *data = NULL; |
02fa8565 |
DWORD bytes, read; |
a24dd2e3 |
|
81d882d5 |
bytes = PeekNamedPipeAsync(pipe, 1, &exit_event);
if (bytes == 0) |
a24dd2e3 |
{ |
81d882d5 |
MsgToEventLog(M_SYSERR, TEXT("PeekNamedPipeAsync failed"));
ReturnLastError(pipe, L"PeekNamedPipeAsync"); |
1394192b |
goto err; |
a24dd2e3 |
}
|
81d882d5 |
size = bytes / sizeof(*data); |
6f20808c |
if (size == 0)
{
MsgToEventLog(M_SYSERR, TEXT("malformed startup data: 1 byte received"));
ReturnError(pipe, ERROR_STARTUP_DATA, L"GetStartupData", 1, &exit_event); |
1394192b |
goto err; |
6f20808c |
}
|
81d882d5 |
data = malloc(bytes);
if (data == NULL) |
a24dd2e3 |
{ |
81d882d5 |
MsgToEventLog(M_SYSERR, TEXT("malloc failed"));
ReturnLastError(pipe, L"malloc"); |
1394192b |
goto err; |
a24dd2e3 |
}
|
81d882d5 |
read = ReadPipeAsync(pipe, data, bytes, 1, &exit_event);
if (bytes != read)
{
MsgToEventLog(M_SYSERR, TEXT("ReadPipeAsync failed"));
ReturnLastError(pipe, L"ReadPipeAsync"); |
1394192b |
goto err; |
81d882d5 |
} |
a24dd2e3 |
|
81d882d5 |
if (data[size - 1] != 0) |
a24dd2e3 |
{ |
81d882d5 |
MsgToEventLog(M_ERR, TEXT("Startup data is not NULL terminated"));
ReturnError(pipe, ERROR_STARTUP_DATA, L"GetStartupData", 1, &exit_event); |
1394192b |
goto err; |
a24dd2e3 |
}
|
81d882d5 |
sud->directory = data;
len = wcslen(sud->directory) + 1;
size -= len;
if (size <= 0) |
a24dd2e3 |
{ |
81d882d5 |
MsgToEventLog(M_ERR, TEXT("Startup data ends at working directory"));
ReturnError(pipe, ERROR_STARTUP_DATA, L"GetStartupData", 1, &exit_event); |
1394192b |
goto err; |
a24dd2e3 |
}
|
81d882d5 |
sud->options = sud->directory + len;
len = wcslen(sud->options) + 1;
size -= len;
if (size <= 0) |
a24dd2e3 |
{ |
81d882d5 |
MsgToEventLog(M_ERR, TEXT("Startup data ends at command line options"));
ReturnError(pipe, ERROR_STARTUP_DATA, L"GetStartupData", 1, &exit_event); |
1394192b |
goto err; |
a24dd2e3 |
}
|
81d882d5 |
sud->std_input = sud->options + len; |
1394192b |
return TRUE; |
a24dd2e3 |
|
1394192b |
err:
sud->directory = NULL; /* caller must not free() */ |
81d882d5 |
free(data); |
1394192b |
return FALSE; |
a24dd2e3 |
}
static VOID |
81d882d5 |
FreeStartupData(STARTUP_DATA *sud) |
a24dd2e3 |
{ |
81d882d5 |
free(sud->directory); |
a24dd2e3 |
}
static SOCKADDR_INET |
81d882d5 |
sockaddr_inet(short family, inet_address_t *addr) |
a24dd2e3 |
{ |
81d882d5 |
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; |
a24dd2e3 |
}
static DWORD |
81d882d5 |
InterfaceLuid(const char *iface_name, PNET_LUID luid) |
a24dd2e3 |
{ |
81d882d5 |
NETIO_STATUS status; |
f3d389a2 |
LPWSTR wide_name = utf8to16(iface_name); |
a24dd2e3 |
|
f3d389a2 |
if (wide_name)
{
status = ConvertInterfaceAliasToLuid(wide_name, luid);
free(wide_name);
}
else
{
status = ERROR_OUTOFMEMORY;
} |
81d882d5 |
return status; |
a24dd2e3 |
}
static BOOL |
81d882d5 |
CmpAddress(LPVOID item, LPVOID address) |
a24dd2e3 |
{ |
81d882d5 |
return memcmp(item, address, sizeof(MIB_UNICASTIPADDRESS_ROW)) == 0 ? TRUE : FALSE; |
a24dd2e3 |
}
static DWORD |
81d882d5 |
DeleteAddress(PMIB_UNICASTIPADDRESS_ROW addr_row) |
a24dd2e3 |
{ |
81d882d5 |
return DeleteUnicastIpAddressEntry(addr_row); |
a24dd2e3 |
}
static DWORD |
81d882d5 |
HandleAddressMessage(address_message_t *msg, undo_lists_t *lists) |
a24dd2e3 |
{ |
81d882d5 |
DWORD err;
PMIB_UNICASTIPADDRESS_ROW addr_row;
BOOL add = msg->header.type == msg_add_address; |
a24dd2e3 |
|
81d882d5 |
addr_row = malloc(sizeof(*addr_row));
if (addr_row == NULL)
{
return ERROR_OUTOFMEMORY;
} |
a24dd2e3 |
|
81d882d5 |
InitializeUnicastIpAddressEntry(addr_row);
addr_row->Address = sockaddr_inet(msg->family, &msg->address);
addr_row->OnLinkPrefixLength = (UINT8) msg->prefix_len; |
a24dd2e3 |
|
81d882d5 |
if (msg->iface.index != -1) |
a24dd2e3 |
{ |
81d882d5 |
addr_row->InterfaceIndex = msg->iface.index; |
a24dd2e3 |
} |
81d882d5 |
else |
a24dd2e3 |
{ |
81d882d5 |
NET_LUID luid;
err = InterfaceLuid(msg->iface.name, &luid);
if (err)
{
goto out;
}
addr_row->InterfaceLuid = luid; |
a24dd2e3 |
}
|
81d882d5 |
if (add) |
a24dd2e3 |
{ |
81d882d5 |
err = CreateUnicastIpAddressEntry(addr_row);
if (err)
{
goto out;
} |
a24dd2e3 |
|
81d882d5 |
err = AddListItem(&(*lists)[address], addr_row);
if (err)
{
DeleteAddress(addr_row);
} |
a24dd2e3 |
} |
81d882d5 |
else |
a24dd2e3 |
{ |
81d882d5 |
err = DeleteAddress(addr_row);
if (err)
{
goto out;
} |
a24dd2e3 |
|
81d882d5 |
free(RemoveListItem(&(*lists)[address], CmpAddress, addr_row)); |
a24dd2e3 |
}
out: |
81d882d5 |
if (!add || err)
{
free(addr_row);
} |
a24dd2e3 |
|
81d882d5 |
return err; |
a24dd2e3 |
}
static BOOL |
81d882d5 |
CmpRoute(LPVOID item, LPVOID route) |
a24dd2e3 |
{ |
81d882d5 |
return memcmp(item, route, sizeof(MIB_IPFORWARD_ROW2)) == 0 ? TRUE : FALSE; |
a24dd2e3 |
}
static DWORD |
81d882d5 |
DeleteRoute(PMIB_IPFORWARD_ROW2 fwd_row) |
a24dd2e3 |
{ |
81d882d5 |
return DeleteIpForwardEntry2(fwd_row); |
a24dd2e3 |
}
static DWORD |
81d882d5 |
HandleRouteMessage(route_message_t *msg, undo_lists_t *lists) |
a24dd2e3 |
{ |
81d882d5 |
DWORD err;
PMIB_IPFORWARD_ROW2 fwd_row;
BOOL add = msg->header.type == msg_add_route; |
a24dd2e3 |
|
81d882d5 |
fwd_row = malloc(sizeof(*fwd_row));
if (fwd_row == NULL)
{
return ERROR_OUTOFMEMORY;
} |
a24dd2e3 |
|
81d882d5 |
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); |
a24dd2e3 |
|
81d882d5 |
if (msg->iface.index != -1) |
a24dd2e3 |
{ |
81d882d5 |
fwd_row->InterfaceIndex = msg->iface.index; |
a24dd2e3 |
} |
81d882d5 |
else if (strlen(msg->iface.name)) |
a24dd2e3 |
{ |
81d882d5 |
NET_LUID luid;
err = InterfaceLuid(msg->iface.name, &luid);
if (err)
{
goto out;
}
fwd_row->InterfaceLuid = luid; |
a24dd2e3 |
}
|
81d882d5 |
if (add) |
a24dd2e3 |
{ |
81d882d5 |
err = CreateIpForwardEntry2(fwd_row);
if (err)
{
goto out;
} |
a24dd2e3 |
|
81d882d5 |
err = AddListItem(&(*lists)[route], fwd_row);
if (err)
{
DeleteRoute(fwd_row);
} |
a24dd2e3 |
} |
81d882d5 |
else |
a24dd2e3 |
{ |
81d882d5 |
err = DeleteRoute(fwd_row);
if (err)
{
goto out;
} |
a24dd2e3 |
|
81d882d5 |
free(RemoveListItem(&(*lists)[route], CmpRoute, fwd_row)); |
a24dd2e3 |
}
out: |
81d882d5 |
if (!add || err)
{
free(fwd_row);
} |
a24dd2e3 |
|
81d882d5 |
return err; |
a24dd2e3 |
}
static DWORD |
81d882d5 |
HandleFlushNeighborsMessage(flush_neighbors_message_t *msg) |
a24dd2e3 |
{ |
81d882d5 |
if (msg->family == AF_INET)
{
return FlushIpNetTable(msg->iface.index);
} |
a24dd2e3 |
|
a5d73667 |
return FlushIpNetTable2(msg->family, msg->iface.index); |
a24dd2e3 |
}
|
2282b1be |
static void |
81d882d5 |
BlockDNSErrHandler(DWORD err, const char *msg) |
2282b1be |
{ |
81d882d5 |
TCHAR buf[256];
LPCTSTR err_str; |
2282b1be |
|
81d882d5 |
if (!err)
{
return;
} |
2282b1be |
|
81d882d5 |
err_str = TEXT("Unknown Win32 Error"); |
2282b1be |
|
81d882d5 |
if (FormatMessage(FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM
| FORMAT_MESSAGE_ARGUMENT_ARRAY,
NULL, err, 0, buf, sizeof(buf), NULL)) |
2282b1be |
{ |
81d882d5 |
err_str = buf; |
2282b1be |
}
#ifdef UNICODE |
81d882d5 |
MsgToEventLog(M_ERR, L"%S (status = %lu): %s", msg, err, err_str); |
2282b1be |
#else |
81d882d5 |
MsgToEventLog(M_ERR, "%s (status = %lu): %s", msg, err, err_str); |
2282b1be |
#endif
}
/* Use an always-true match_fn to get the head of the list */
static BOOL |
81d882d5 |
CmpEngine(LPVOID item, LPVOID any) |
2282b1be |
{ |
81d882d5 |
return TRUE; |
2282b1be |
}
static DWORD |
81d882d5 |
HandleBlockDNSMessage(const block_dns_message_t *msg, undo_lists_t *lists) |
2282b1be |
{ |
81d882d5 |
DWORD err = 0; |
27aa8728 |
block_dns_data_t *interface_data; |
81d882d5 |
HANDLE engine = NULL;
LPCWSTR exe_path; |
2282b1be |
#ifdef UNICODE |
81d882d5 |
exe_path = settings.exe_path; |
2282b1be |
#else |
81d882d5 |
WCHAR wide_path[MAX_PATH];
MultiByteToWideChar(CP_UTF8, 0, settings.exe_path, MAX_PATH, wide_path, MAX_PATH);
exe_path = wide_path; |
2282b1be |
#endif
|
81d882d5 |
if (msg->header.type == msg_add_block_dns) |
2282b1be |
{ |
81d882d5 |
err = add_block_dns_filters(&engine, msg->iface.index, exe_path, BlockDNSErrHandler);
if (!err)
{ |
27aa8728 |
interface_data = malloc(sizeof(block_dns_data_t));
if (!interface_data)
{
return ERROR_OUTOFMEMORY;
}
interface_data->engine = engine;
interface_data->index = msg->iface.index; |
42292435 |
int is_auto = 0; |
27aa8728 |
interface_data->metric_v4 = get_interface_metric(msg->iface.index, |
42292435 |
AF_INET, &is_auto);
if (is_auto) |
27aa8728 |
{ |
42292435 |
interface_data->metric_v4 = 0; |
27aa8728 |
}
interface_data->metric_v6 = get_interface_metric(msg->iface.index, |
42292435 |
AF_INET6, &is_auto);
if (is_auto) |
27aa8728 |
{ |
42292435 |
interface_data->metric_v6 = 0; |
27aa8728 |
}
err = AddListItem(&(*lists)[block_dns], interface_data);
if (!err)
{
err = set_interface_metric(msg->iface.index, AF_INET,
BLOCK_DNS_IFACE_METRIC);
if (!err)
{
set_interface_metric(msg->iface.index, AF_INET6,
BLOCK_DNS_IFACE_METRIC);
}
} |
81d882d5 |
} |
2282b1be |
} |
81d882d5 |
else |
2282b1be |
{ |
27aa8728 |
interface_data = RemoveListItem(&(*lists)[block_dns], CmpEngine, NULL);
if (interface_data) |
81d882d5 |
{ |
27aa8728 |
engine = interface_data->engine; |
81d882d5 |
err = delete_block_dns_filters(engine);
engine = NULL; |
27aa8728 |
if (interface_data->metric_v4 >= 0)
{
set_interface_metric(msg->iface.index, AF_INET,
interface_data->metric_v4);
}
if (interface_data->metric_v6 >= 0)
{
set_interface_metric(msg->iface.index, AF_INET6,
interface_data->metric_v6);
}
free(interface_data); |
81d882d5 |
}
else |
2282b1be |
{ |
81d882d5 |
MsgToEventLog(M_ERR, TEXT("No previous block DNS filters to delete")); |
2282b1be |
}
}
|
81d882d5 |
if (err && engine) |
2282b1be |
{ |
81d882d5 |
delete_block_dns_filters(engine); |
2282b1be |
}
|
81d882d5 |
return err; |
2282b1be |
} |
a24dd2e3 |
|
3e42a558 |
/*
* 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 |
81d882d5 |
ExecCommand(const WCHAR *argv0, const WCHAR *cmdline, DWORD timeout) |
3e42a558 |
{ |
81d882d5 |
DWORD exit_code;
STARTUPINFOW si;
PROCESS_INFORMATION pi;
DWORD proc_flags = CREATE_NO_WINDOW|CREATE_UNICODE_ENVIRONMENT;
WCHAR *cmdline_dup = NULL; |
3e42a558 |
|
81d882d5 |
ZeroMemory(&si, sizeof(si));
ZeroMemory(&pi, sizeof(pi)); |
3e42a558 |
|
81d882d5 |
si.cb = sizeof(si); |
3e42a558 |
|
81d882d5 |
/* CreateProcess needs a modifiable cmdline: make a copy */
cmdline_dup = wcsdup(cmdline);
if (cmdline_dup && CreateProcessW(argv0, cmdline_dup, NULL, NULL, FALSE, |
3e42a558 |
proc_flags, NULL, NULL, &si, &pi) )
{ |
81d882d5 |
WaitForSingleObject(pi.hProcess, timeout ? timeout : INFINITE);
if (!GetExitCodeProcess(pi.hProcess, &exit_code)) |
3e42a558 |
{ |
81d882d5 |
MsgToEventLog(M_SYSERR, TEXT("ExecCommand: Error getting exit_code:"));
exit_code = GetLastError(); |
3e42a558 |
} |
81d882d5 |
else if (exit_code == STILL_ACTIVE) |
3e42a558 |
{ |
81d882d5 |
exit_code = WAIT_TIMEOUT; /* Windows error code 0x102 */ |
3e42a558 |
|
81d882d5 |
/* kill without impunity */
TerminateProcess(pi.hProcess, exit_code);
MsgToEventLog(M_ERR, TEXT("ExecCommand: \"%s %s\" killed after timeout"),
argv0, cmdline); |
3e42a558 |
} |
81d882d5 |
else if (exit_code)
{
MsgToEventLog(M_ERR, TEXT("ExecCommand: \"%s %s\" exited with status = %lu"), |
3e42a558 |
argv0, cmdline, exit_code); |
81d882d5 |
}
else
{
MsgToEventLog(M_INFO, TEXT("ExecCommand: \"%s %s\" completed"), argv0, cmdline);
} |
3e42a558 |
|
81d882d5 |
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread); |
3e42a558 |
} |
81d882d5 |
else |
3e42a558 |
{ |
81d882d5 |
exit_code = GetLastError();
MsgToEventLog(M_SYSERR, TEXT("ExecCommand: could not run \"%s %s\" :"),
argv0, cmdline); |
3e42a558 |
}
|
81d882d5 |
free(cmdline_dup);
return exit_code; |
3e42a558 |
}
/*
* Entry point for register-dns thread.
*/
static DWORD WINAPI |
81d882d5 |
RegisterDNS(LPVOID unused) |
3e42a558 |
{ |
81d882d5 |
DWORD err; |
f755c992 |
size_t i; |
81d882d5 |
DWORD timeout = RDNS_TIMEOUT * 1000; /* in milliseconds */ |
3e42a558 |
|
4eb46553 |
/* path of ipconfig command */
WCHAR ipcfg[MAX_PATH]; |
3e42a558 |
|
81d882d5 |
struct |
3e42a558 |
{ |
81d882d5 |
WCHAR *argv0;
WCHAR *cmdline;
DWORD timeout; |
3e42a558 |
} cmds [] = { |
81d882d5 |
{ ipcfg, L"ipconfig /flushdns", timeout },
{ ipcfg, L"ipconfig /registerdns", timeout },
}; |
3e42a558 |
|
81d882d5 |
HANDLE wait_handles[2] = {rdns_semaphore, exit_event}; |
3e42a558 |
|
43a5a4f3 |
openvpn_swprintf(ipcfg, MAX_PATH, L"%s\\%s", get_win_sys_path(), L"ipconfig.exe"); |
3e42a558 |
|
81d882d5 |
if (WaitForMultipleObjects(2, wait_handles, FALSE, timeout) == WAIT_OBJECT_0) |
3e42a558 |
{ |
81d882d5 |
/* Semaphore locked */ |
f755c992 |
for (i = 0; i < _countof(cmds); ++i) |
81d882d5 |
{
ExecCommand(cmds[i].argv0, cmds[i].cmdline, cmds[i].timeout);
}
err = 0;
if (!ReleaseSemaphore(rdns_semaphore, 1, NULL) ) |
3e42a558 |
{ |
81d882d5 |
err = MsgToEventLog(M_SYSERR, TEXT("RegisterDNS: Failed to release regsiter-dns semaphore:")); |
3e42a558 |
}
} |
81d882d5 |
else |
3e42a558 |
{ |
81d882d5 |
MsgToEventLog(M_ERR, TEXT("RegisterDNS: Failed to lock register-dns semaphore"));
err = ERROR_SEM_TIMEOUT; /* Windows error code 0x79 */ |
3e42a558 |
} |
81d882d5 |
return err; |
3e42a558 |
}
static DWORD |
81d882d5 |
HandleRegisterDNSMessage(void) |
3e42a558 |
{ |
81d882d5 |
DWORD err;
HANDLE thread = NULL; |
3e42a558 |
|
81d882d5 |
/* Delegate this job to a sub-thread */
thread = CreateThread(NULL, 0, RegisterDNS, NULL, 0, NULL); |
3e42a558 |
|
81d882d5 |
/*
* 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 |
3e42a558 |
{ |
81d882d5 |
err = GetLastError(); |
3e42a558 |
}
|
81d882d5 |
return err; |
3e42a558 |
}
|
c098016a |
/**
* Run the command: netsh interface $proto $action dns $if_name $addr [validate=no]
* @param action "delete" or "add"
* @param proto "ipv6" or "ip"
* @param if_name "name_of_interface"
* @param addr IPv4 (for proto = ip) or IPv6 address as a string
*
* If addr is null and action = "delete" all addresses are deleted.
*/
static DWORD |
81d882d5 |
netsh_dns_cmd(const wchar_t *action, const wchar_t *proto, const wchar_t *if_name, const wchar_t *addr) |
c098016a |
{ |
81d882d5 |
DWORD err = 0;
int timeout = 30000; /* in msec */
wchar_t argv0[MAX_PATH]; |
d1f0e2cf |
wchar_t *cmdline = NULL; |
c098016a |
|
81d882d5 |
if (!addr) |
c098016a |
{ |
81d882d5 |
if (wcscmp(action, L"delete") == 0)
{
addr = L"all";
}
else /* nothing to do -- return success*/
{
goto out;
} |
c098016a |
}
|
81d882d5 |
/* Path of netsh */ |
4eb46553 |
swprintf(argv0, _countof(argv0), L"%s\\%s", get_win_sys_path(), L"netsh.exe");
argv0[_countof(argv0) - 1] = L'\0'; |
c098016a |
|
81d882d5 |
/* cmd template:
* netsh interface $proto $action dns $if_name $addr [validate=no]
*/
const wchar_t *fmt = L"netsh interface %s %s dns \"%s\" %s"; |
c098016a |
|
81d882d5 |
/* max cmdline length in wchars -- include room for worst case and some */ |
02fa8565 |
size_t ncmdline = wcslen(fmt) + wcslen(if_name) + wcslen(addr) + 32 + 1; |
d1f0e2cf |
cmdline = malloc(ncmdline*sizeof(wchar_t)); |
81d882d5 |
if (!cmdline)
{
err = ERROR_OUTOFMEMORY;
goto out;
} |
c098016a |
|
81d882d5 |
openvpn_sntprintf(cmdline, ncmdline, fmt, proto, action, if_name, addr); |
c098016a |
|
81d882d5 |
if (IsWindows7OrGreater()) |
c098016a |
{ |
81d882d5 |
wcsncat(cmdline, L" validate=no", ncmdline - wcslen(cmdline) - 1); |
c098016a |
} |
81d882d5 |
err = ExecCommand(argv0, cmdline, timeout); |
c098016a |
out: |
81d882d5 |
free(cmdline);
return err; |
c098016a |
}
/* Delete all IPv4 or IPv6 dns servers for an interface */
static DWORD
DeleteDNS(short family, wchar_t *if_name)
{ |
81d882d5 |
wchar_t *proto = (family == AF_INET6) ? L"ipv6" : L"ip";
return netsh_dns_cmd(L"delete", proto, if_name, NULL); |
c098016a |
}
/* Add an IPv4 or IPv6 dns server to an interface */
static DWORD
AddDNS(short family, wchar_t *if_name, wchar_t *addr)
{ |
81d882d5 |
wchar_t *proto = (family == AF_INET6) ? L"ipv6" : L"ip";
return netsh_dns_cmd(L"add", proto, if_name, addr); |
c098016a |
}
static BOOL |
81d882d5 |
CmpWString(LPVOID item, LPVOID str) |
c098016a |
{ |
81d882d5 |
return (wcscmp(item, str) == 0) ? TRUE : FALSE; |
c098016a |
}
static DWORD |
81d882d5 |
HandleDNSConfigMessage(const dns_cfg_message_t *msg, undo_lists_t *lists) |
c098016a |
{ |
81d882d5 |
DWORD err = 0;
wchar_t addr[46]; /* large enough to hold string representation of an ipv4 / ipv6 address */
undo_type_t undo_type = (msg->family == AF_INET6) ? undo_dns4 : undo_dns6;
int addr_len = msg->addr_len; |
c098016a |
|
81d882d5 |
/* sanity check */
if (addr_len > _countof(msg->addr))
{
addr_len = _countof(msg->addr);
} |
c098016a |
|
81d882d5 |
if (!msg->iface.name[0]) /* interface name is required */
{
return ERROR_MESSAGE_DATA;
} |
c098016a |
|
81d882d5 |
wchar_t *wide_name = utf8to16(msg->iface.name); /* utf8 to wide-char */
if (!wide_name)
{
return ERROR_OUTOFMEMORY;
} |
c098016a |
|
81d882d5 |
/* We delete all current addresses before adding any
* OR if the message type is del_dns_cfg
*/
if (addr_len > 0 || msg->header.type == msg_del_dns_cfg) |
c098016a |
{ |
81d882d5 |
err = DeleteDNS(msg->family, wide_name);
if (err)
{
goto out;
}
free(RemoveListItem(&(*lists)[undo_type], CmpWString, wide_name)); |
c098016a |
}
|
81d882d5 |
if (msg->header.type == msg_del_dns_cfg) /* job done */
{
goto out;
} |
c098016a |
|
81d882d5 |
for (int i = 0; i < addr_len; ++i) |
c098016a |
{ |
81d882d5 |
if (msg->family == AF_INET6)
{
RtlIpv6AddressToStringW(&msg->addr[i].ipv6, addr);
}
else
{
RtlIpv4AddressToStringW(&msg->addr[i].ipv4, addr);
}
err = AddDNS(msg->family, wide_name, addr);
if (i == 0 && err)
{
goto out;
}
/* We do not check for duplicate addresses, so any error in adding
* additional addresses is ignored.
*/ |
c098016a |
}
|
81d882d5 |
if (msg->addr_len > 0) |
c098016a |
{ |
81d882d5 |
wchar_t *tmp_name = wcsdup(wide_name);
if (!tmp_name || AddListItem(&(*lists)[undo_type], tmp_name)) |
c098016a |
{ |
81d882d5 |
free(tmp_name);
DeleteDNS(msg->family, wide_name);
err = ERROR_OUTOFMEMORY;
goto out; |
c098016a |
}
}
|
81d882d5 |
err = 0; |
c098016a |
out: |
81d882d5 |
free(wide_name);
return err; |
c098016a |
}
|
b4fc8bbd |
static DWORD
HandleEnableDHCPMessage(const enable_dhcp_message_t *dhcp)
{
DWORD err = 0;
DWORD timeout = 5000; /* in milli seconds */
wchar_t argv0[MAX_PATH];
/* Path of netsh */
swprintf(argv0, _countof(argv0), L"%s\\%s", get_win_sys_path(), L"netsh.exe");
argv0[_countof(argv0) - 1] = L'\0';
/* cmd template:
* netsh interface ipv4 set address name=$if_index source=dhcp
*/
const wchar_t *fmt = L"netsh interface ipv4 set address name=\"%d\" source=dhcp";
/* max cmdline length in wchars -- include room for if index:
* 10 chars for 32 bit int in decimal and +1 for NUL
*/
size_t ncmdline = wcslen(fmt) + 10 + 1;
wchar_t *cmdline = malloc(ncmdline*sizeof(wchar_t));
if (!cmdline)
{
err = ERROR_OUTOFMEMORY;
return err;
}
openvpn_sntprintf(cmdline, ncmdline, fmt, dhcp->iface.index);
err = ExecCommand(argv0, cmdline, timeout);
/* Note: This could fail if dhcp is already enabled, so the caller
* may not want to treat errors as FATAL.
*/
free(cmdline);
return err;
}
|
a24dd2e3 |
static VOID |
81d882d5 |
HandleMessage(HANDLE pipe, DWORD bytes, DWORD count, LPHANDLE events, undo_lists_t *lists) |
a24dd2e3 |
{ |
81d882d5 |
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;
dns_cfg_message_t dns; |
b4fc8bbd |
enable_dhcp_message_t dhcp; |
81d882d5 |
} 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;
} |
3e42a558 |
|
81d882d5 |
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; |
c098016a |
|
81d882d5 |
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;
case msg_add_dns_cfg:
case msg_del_dns_cfg:
ack.error_number = HandleDNSConfigMessage(&msg.dns, lists);
break;
|
b4fc8bbd |
case msg_enable_dhcp:
if (msg.header.size == sizeof(msg.dhcp))
{
ack.error_number = HandleEnableDHCPMessage(&msg.dhcp);
}
break;
|
81d882d5 |
default:
ack.error_number = ERROR_MESSAGE_TYPE;
MsgToEventLog(MSG_FLAGS_ERROR, TEXT("Unknown message type %d"), msg.header.type);
break; |
a24dd2e3 |
}
out: |
81d882d5 |
WritePipeAsync(pipe, &ack, sizeof(ack), count, events); |
a24dd2e3 |
}
static VOID |
81d882d5 |
Undo(undo_lists_t *lists) |
a24dd2e3 |
{ |
81d882d5 |
undo_type_t type; |
27aa8728 |
block_dns_data_t *interface_data; |
81d882d5 |
for (type = 0; type < _undo_type_max; type++) |
a24dd2e3 |
{ |
81d882d5 |
list_item_t **pnext = &(*lists)[type];
while (*pnext) |
a24dd2e3 |
{ |
81d882d5 |
list_item_t *item = *pnext;
switch (type) |
a24dd2e3 |
{ |
81d882d5 |
case address:
DeleteAddress(item->data);
break;
case route:
DeleteRoute(item->data);
break;
case undo_dns4:
DeleteDNS(AF_INET, item->data);
break;
case undo_dns6:
DeleteDNS(AF_INET6, item->data);
break;
case block_dns: |
27aa8728 |
interface_data = (block_dns_data_t*)(item->data);
delete_block_dns_filters(interface_data->engine);
if (interface_data->metric_v4 >= 0)
{
set_interface_metric(interface_data->index, AF_INET,
interface_data->metric_v4);
}
if (interface_data->metric_v6 >= 0)
{
set_interface_metric(interface_data->index, AF_INET6,
interface_data->metric_v6);
} |
81d882d5 |
break; |
a24dd2e3 |
}
|
81d882d5 |
/* Remove from the list and free memory */
*pnext = item->next;
free(item->data);
free(item); |
a24dd2e3 |
}
}
}
static DWORD WINAPI |
81d882d5 |
RunOpenvpn(LPVOID p) |
a24dd2e3 |
{ |
81d882d5 |
HANDLE pipe = p;
HANDLE ovpn_pipe, svc_pipe; |
d1f0e2cf |
PTOKEN_USER svc_user = NULL, ovpn_user = NULL; |
81d882d5 |
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; |
f3fec49b |
TCHAR ovpn_pipe_name[256]; /* The entire pipe name string can be up to 256 characters long according to MSDN. */ |
81d882d5 |
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;
while (!GetTokenInformation(svc_token, TokenUser, svc_user, len, &len))
{
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) |
a24dd2e3 |
{ |
81d882d5 |
ReturnLastError(pipe, L"GetTokenInformation (service token)");
goto out; |
a24dd2e3 |
} |
81d882d5 |
free(svc_user);
svc_user = malloc(len);
if (svc_user == NULL) |
a24dd2e3 |
{ |
81d882d5 |
ReturnLastError(pipe, L"malloc (service token user)");
goto out; |
a24dd2e3 |
}
} |
81d882d5 |
if (!IsValidSid(svc_user->User.Sid)) |
a24dd2e3 |
{ |
81d882d5 |
ReturnLastError(pipe, L"IsValidSid (service token user)");
goto out; |
a24dd2e3 |
}
|
81d882d5 |
if (!ImpersonateNamedPipeClient(pipe)) |
a24dd2e3 |
{ |
81d882d5 |
ReturnLastError(pipe, L"ImpersonateNamedPipeClient");
goto out; |
a24dd2e3 |
} |
81d882d5 |
if (!OpenThreadToken(GetCurrentThread(), TOKEN_ALL_ACCESS, FALSE, &imp_token)) |
a24dd2e3 |
{ |
81d882d5 |
ReturnLastError(pipe, L"OpenThreadToken");
goto out; |
a24dd2e3 |
} |
81d882d5 |
len = 0;
while (!GetTokenInformation(imp_token, TokenUser, ovpn_user, len, &len)) |
a24dd2e3 |
{ |
81d882d5 |
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) |
a24dd2e3 |
{ |
81d882d5 |
ReturnLastError(pipe, L"GetTokenInformation (impersonation token)");
goto out; |
a24dd2e3 |
} |
81d882d5 |
free(ovpn_user);
ovpn_user = malloc(len);
if (ovpn_user == NULL) |
a24dd2e3 |
{ |
81d882d5 |
ReturnLastError(pipe, L"malloc (impersonation token user)");
goto out; |
a24dd2e3 |
}
} |
81d882d5 |
if (!IsValidSid(ovpn_user->User.Sid)) |
a24dd2e3 |
{ |
81d882d5 |
ReturnLastError(pipe, L"IsValidSid (impersonation token user)");
goto out; |
a24dd2e3 |
}
|
81d882d5 |
/* Check user is authorized or options are white-listed */ |
e82733a1 |
if (!IsAuthorizedUser(ovpn_user->User.Sid, imp_token, settings.ovpn_admin_group) |
81d882d5 |
&& !ValidateOptions(pipe, sud.directory, sud.options)) |
f3c8a04d |
{ |
81d882d5 |
goto out; |
f3c8a04d |
}
|
81d882d5 |
/* 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; |
a24dd2e3 |
} |
81d882d5 |
if (SetEntriesInAcl(2, ea, NULL, &ovpn_dacl) != ERROR_SUCCESS) |
a24dd2e3 |
{ |
81d882d5 |
ReturnLastError(pipe, L"SetEntriesInAcl");
goto out; |
a24dd2e3 |
} |
81d882d5 |
if (!SetSecurityDescriptorDacl(&ovpn_sd, TRUE, ovpn_dacl, FALSE)) |
a24dd2e3 |
{ |
81d882d5 |
ReturnLastError(pipe, L"SetSecurityDescriptorDacl");
goto out; |
a24dd2e3 |
}
|
81d882d5 |
/* Create primary token from impersonation token */
if (!DuplicateTokenEx(imp_token, TOKEN_ALL_ACCESS, NULL, 0, TokenPrimary, &pri_token)) |
a24dd2e3 |
{ |
81d882d5 |
ReturnLastError(pipe, L"DuplicateTokenEx");
goto out; |
a24dd2e3 |
}
|
81d882d5 |
/* 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) |
852f1e49 |
{ |
81d882d5 |
ReturnLastError(pipe, L"CreateFile for stdout");
goto out; |
852f1e49 |
}
|
81d882d5 |
if (!CreatePipe(&stdin_read, &stdin_write, &inheritable, 0)
|| !SetHandleInformation(stdin_write, HANDLE_FLAG_INHERIT, 0)) |
a24dd2e3 |
{ |
81d882d5 |
ReturnLastError(pipe, L"CreatePipe");
goto out; |
a24dd2e3 |
}
|
81d882d5 |
openvpn_sntprintf(ovpn_pipe_name, _countof(ovpn_pipe_name), |
f3fec49b |
TEXT("\\\\.\\pipe\\" PACKAGE "%s\\service_%lu"), service_instance, GetCurrentThreadId()); |
81d882d5 |
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) |
a24dd2e3 |
{ |
81d882d5 |
ReturnLastError(pipe, L"CreateNamedPipe");
goto out; |
a24dd2e3 |
}
|
81d882d5 |
svc_pipe = CreateFile(ovpn_pipe_name, GENERIC_READ | GENERIC_WRITE, 0,
&inheritable, OPEN_EXISTING, 0, NULL);
if (svc_pipe == INVALID_HANDLE_VALUE) |
a24dd2e3 |
{ |
81d882d5 |
ReturnLastError(pipe, L"CreateFile");
goto out; |
a24dd2e3 |
}
|
81d882d5 |
pipe_mode = PIPE_READMODE_MESSAGE;
if (!SetNamedPipeHandleState(svc_pipe, &pipe_mode, NULL, NULL)) |
a24dd2e3 |
{ |
81d882d5 |
ReturnLastError(pipe, L"SetNamedPipeHandleState");
goto out; |
a24dd2e3 |
}
|
81d882d5 |
cmdline_size = wcslen(sud.options) + 128;
cmdline = malloc(cmdline_size * sizeof(*cmdline));
if (cmdline == NULL) |
a24dd2e3 |
{ |
81d882d5 |
ReturnLastError(pipe, L"malloc");
goto out; |
a24dd2e3 |
} |
81d882d5 |
openvpn_sntprintf(cmdline, cmdline_size, L"openvpn %s --msg-channel %lu",
sud.options, svc_pipe); |
a24dd2e3 |
|
81d882d5 |
if (!CreateEnvironmentBlock(&user_env, imp_token, FALSE)) |
a24dd2e3 |
{ |
81d882d5 |
ReturnLastError(pipe, L"CreateEnvironmentBlock");
goto out; |
a24dd2e3 |
}
|
81d882d5 |
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; |
a24dd2e3 |
#ifdef UNICODE |
81d882d5 |
exe_path = settings.exe_path; |
a24dd2e3 |
#else |
81d882d5 |
WCHAR wide_path[MAX_PATH];
MultiByteToWideChar(CP_UTF8, 0, settings.exe_path, MAX_PATH, wide_path, MAX_PATH);
exe_path = wide_path; |
a24dd2e3 |
#endif
|
81d882d5 |
/* 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)) |
a24dd2e3 |
{ |
81d882d5 |
ReturnLastError(pipe, L"CreateProcessAsUser");
goto out; |
a24dd2e3 |
}
|
81d882d5 |
if (!RevertToSelf()) |
a24dd2e3 |
{ |
81d882d5 |
TerminateProcess(proc_info.hProcess, 1);
ReturnLastError(pipe, L"RevertToSelf");
goto out; |
a24dd2e3 |
}
|
81d882d5 |
ReturnProcessId(pipe, proc_info.dwProcessId, 1, &exit_event); |
e4c9bbe6 |
|
81d882d5 |
CloseHandleEx(&stdout_write);
CloseHandleEx(&stdin_read);
CloseHandleEx(&svc_pipe); |
a24dd2e3 |
|
81d882d5 |
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))) |
a24dd2e3 |
{ |
81d882d5 |
DWORD written;
WideCharToMultiByte(CP_UTF8, 0, sud.std_input, -1, input, input_size, NULL, NULL); |
02fa8565 |
WriteFile(stdin_write, input, (DWORD)strlen(input), &written, NULL); |
81d882d5 |
free(input); |
a24dd2e3 |
}
|
81d882d5 |
while (TRUE) |
a24dd2e3 |
{ |
81d882d5 |
DWORD bytes = PeekNamedPipeAsync(ovpn_pipe, 1, &exit_event);
if (bytes == 0)
{
break;
} |
a24dd2e3 |
|
81d882d5 |
HandleMessage(ovpn_pipe, bytes, 1, &exit_event, &undo_lists); |
a24dd2e3 |
}
|
81d882d5 |
WaitForSingleObject(proc_info.hProcess, IO_TIMEOUT);
GetExitCodeProcess(proc_info.hProcess, &exit_code);
if (exit_code == STILL_ACTIVE) |
852f1e49 |
{ |
81d882d5 |
TerminateProcess(proc_info.hProcess, 1); |
852f1e49 |
} |
81d882d5 |
else if (exit_code != 0)
{
WCHAR buf[256]; |
43a5a4f3 |
openvpn_swprintf(buf, _countof(buf),
L"OpenVPN exited with error: exit code = %lu", exit_code); |
81d882d5 |
ReturnError(pipe, ERROR_OPENVPN_STARTUP, buf, 1, &exit_event);
}
Undo(&undo_lists); |
a24dd2e3 |
out: |
81d882d5 |
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; |
a24dd2e3 |
}
static DWORD WINAPI |
81d882d5 |
ServiceCtrlInteractive(DWORD ctrl_code, DWORD event, LPVOID data, LPVOID ctx) |
a24dd2e3 |
{ |
81d882d5 |
SERVICE_STATUS *status = ctx;
switch (ctrl_code) |
a24dd2e3 |
{ |
81d882d5 |
case SERVICE_CONTROL_STOP:
status->dwCurrentState = SERVICE_STOP_PENDING;
ReportStatusToSCMgr(service, status);
if (exit_event)
{
SetEvent(exit_event);
}
return NO_ERROR; |
a24dd2e3 |
|
81d882d5 |
case SERVICE_CONTROL_INTERROGATE:
return NO_ERROR; |
a24dd2e3 |
|
81d882d5 |
default:
return ERROR_CALL_NOT_IMPLEMENTED; |
a24dd2e3 |
}
}
static HANDLE |
81d882d5 |
CreateClientPipeInstance(VOID) |
a24dd2e3 |
{ |
f3fec49b |
TCHAR pipe_name[256]; /* The entire pipe name string can be up to 256 characters long according to MSDN. */ |
81d882d5 |
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; |
a24dd2e3 |
|
81d882d5 |
if (!initialized) |
a24dd2e3 |
{ |
81d882d5 |
PSID everyone, anonymous; |
a24dd2e3 |
|
81d882d5 |
ConvertStringSidToSid(TEXT("S-1-1-0"), &everyone);
ConvertStringSidToSid(TEXT("S-1-5-7"), &anonymous); |
a24dd2e3 |
|
81d882d5 |
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; |
a24dd2e3 |
|
81d882d5 |
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; |
a24dd2e3 |
|
81d882d5 |
flags |= FILE_FLAG_FIRST_PIPE_INSTANCE;
initialized = TRUE; |
a24dd2e3 |
}
|
f3fec49b |
openvpn_sntprintf(pipe_name, _countof(pipe_name), TEXT("\\\\.\\pipe\\" PACKAGE "%s\\service"), service_instance);
pipe = CreateNamedPipe(pipe_name, flags, |
81d882d5 |
PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE,
PIPE_UNLIMITED_INSTANCES, 1024, 1024, 0, NULL);
if (pipe == INVALID_HANDLE_VALUE) |
a24dd2e3 |
{ |
81d882d5 |
MsgToEventLog(M_SYSERR, TEXT("Could not create named pipe"));
return INVALID_HANDLE_VALUE; |
a24dd2e3 |
}
|
81d882d5 |
if (GetSecurityInfo(pipe, SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION, |
a24dd2e3 |
NULL, NULL, &old_dacl, NULL, &sd) != ERROR_SUCCESS)
{ |
81d882d5 |
MsgToEventLog(M_SYSERR, TEXT("Could not get pipe security info"));
return CloseHandleEx(&pipe); |
a24dd2e3 |
}
|
81d882d5 |
if (SetEntriesInAcl(2, ea, old_dacl, &new_dacl) != ERROR_SUCCESS) |
a24dd2e3 |
{ |
81d882d5 |
MsgToEventLog(M_SYSERR, TEXT("Could not set entries in new acl"));
return CloseHandleEx(&pipe); |
a24dd2e3 |
}
|
81d882d5 |
if (SetSecurityInfo(pipe, SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION, |
a24dd2e3 |
NULL, NULL, new_dacl, NULL) != ERROR_SUCCESS)
{ |
81d882d5 |
MsgToEventLog(M_SYSERR, TEXT("Could not set pipe security info"));
return CloseHandleEx(&pipe); |
a24dd2e3 |
}
|
81d882d5 |
return pipe; |
a24dd2e3 |
}
static DWORD |
81d882d5 |
UpdateWaitHandles(LPHANDLE *handles_ptr, LPDWORD count,
HANDLE io_event, HANDLE exit_event, list_item_t *threads) |
a24dd2e3 |
{ |
81d882d5 |
static DWORD size = 10;
static LPHANDLE handles = NULL;
DWORD pos = 0; |
a24dd2e3 |
|
81d882d5 |
if (handles == NULL) |
a24dd2e3 |
{ |
81d882d5 |
handles = malloc(size * sizeof(HANDLE));
*handles_ptr = handles;
if (handles == NULL)
{
return ERROR_OUTOFMEMORY;
} |
a24dd2e3 |
}
|
81d882d5 |
handles[pos++] = io_event; |
a24dd2e3 |
|
81d882d5 |
if (!threads)
{
handles[pos++] = exit_event;
} |
a24dd2e3 |
|
81d882d5 |
while (threads) |
a24dd2e3 |
{ |
81d882d5 |
if (pos == size) |
a24dd2e3 |
{ |
81d882d5 |
LPHANDLE tmp;
size += 10;
tmp = realloc(handles, size * sizeof(HANDLE));
if (tmp == NULL) |
600dd9a1 |
{ |
81d882d5 |
size -= 10;
*count = pos;
return ERROR_OUTOFMEMORY; |
600dd9a1 |
} |
81d882d5 |
handles = tmp;
*handles_ptr = handles; |
a24dd2e3 |
} |
81d882d5 |
handles[pos++] = threads->data;
threads = threads->next; |
a24dd2e3 |
}
|
81d882d5 |
*count = pos;
return NO_ERROR; |
a24dd2e3 |
}
static VOID |
81d882d5 |
FreeWaitHandles(LPHANDLE h) |
a24dd2e3 |
{ |
81d882d5 |
free(h); |
a24dd2e3 |
}
|
0893b14a |
static BOOL
CmpHandle(LPVOID item, LPVOID hnd)
{
return item == hnd;
} |
a24dd2e3 |
|
f3fec49b |
VOID WINAPI
ServiceStartInteractiveOwn(DWORD dwArgc, LPTSTR *lpszArgv)
{
status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
ServiceStartInteractive(dwArgc, lpszArgv);
}
|
a24dd2e3 |
VOID WINAPI |
81d882d5 |
ServiceStartInteractive(DWORD dwArgc, LPTSTR *lpszArgv) |
a24dd2e3 |
{ |
81d882d5 |
HANDLE pipe, io_event = NULL;
OVERLAPPED overlapped;
DWORD error = NO_ERROR;
list_item_t *threads = NULL;
PHANDLE handles = NULL;
DWORD handle_count;
service = RegisterServiceCtrlHandlerEx(interactive_service.name, ServiceCtrlInteractive, &status);
if (!service)
{
return;
}
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) |
a24dd2e3 |
{ |
81d882d5 |
MsgToEventLog(M_SYSERR, TEXT("Could not connect pipe"));
break; |
a24dd2e3 |
}
|
81d882d5 |
error = WaitForMultipleObjects(handle_count, handles, FALSE, INFINITE);
if (error == WAIT_OBJECT_0) |
a24dd2e3 |
{ |
81d882d5 |
/* Client connected, spawn a worker thread for it */
HANDLE next_pipe = CreateClientPipeInstance();
HANDLE thread = CreateThread(NULL, 0, RunOpenvpn, pipe, CREATE_SUSPENDED, NULL);
if (thread) |
a24dd2e3 |
{ |
81d882d5 |
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 |
a24dd2e3 |
{ |
81d882d5 |
ResumeThread(thread); |
a24dd2e3 |
}
} |
81d882d5 |
else
{
CloseHandleEx(&pipe);
} |
a24dd2e3 |
|
81d882d5 |
ResetOverlapped(&overlapped);
pipe = next_pipe; |
a24dd2e3 |
} |
81d882d5 |
else |
a24dd2e3 |
{ |
81d882d5 |
CancelIo(pipe);
if (error == WAIT_FAILED) |
a24dd2e3 |
{ |
81d882d5 |
MsgToEventLog(M_SYSERR, TEXT("WaitForMultipleObjects failed"));
SetEvent(exit_event);
/* Give some time for worker threads to exit and then terminate */
Sleep(1000);
break; |
a24dd2e3 |
} |
81d882d5 |
if (!threads) |
a24dd2e3 |
{ |
81d882d5 |
/* exit event signaled */
CloseHandleEx(&pipe);
ResetEvent(exit_event);
error = NO_ERROR;
break; |
a24dd2e3 |
}
|
81d882d5 |
/* Worker thread ended */
HANDLE thread = RemoveListItem(&threads, CmpHandle, handles[error]);
UpdateWaitHandles(&handles, &handle_count, io_event, exit_event, threads);
CloseHandleEx(&thread); |
a24dd2e3 |
}
}
out: |
81d882d5 |
FreeWaitHandles(handles);
CloseHandleEx(&io_event);
CloseHandleEx(&exit_event);
CloseHandleEx(&rdns_semaphore);
status.dwCurrentState = SERVICE_STOPPED;
status.dwWin32ExitCode = error;
ReportStatusToSCMgr(service, &status); |
a24dd2e3 |
} |