src/openvpnserv/interactive.c
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
 }