From: Heiko Hund <heiko.h...@sophos.com>

v1: Heiko Hund
 - Message-ID: <2215306.x9ci9DhAZ9@de-gn-40970>
 - extend openvpn service to provide "automatic service" and "interactive
   service" (which is used by GUI and OpenVPN to run openvpn non-privileged
   and still be able to install routes and configure IPv6 addresses)
 - add --msg-channel <n> option to openvpn to tell it which pipe to use
   to talk to the interactive service (used in tun.c for ifconfig + ARP flush,
   and route.c for routing)
 - add openvpn-msg.h with message definitions for talking to interactive service
 - routing in openvpn uses message-pipe automatically if --msg-channel <n> is
   configured, no other option needed
 - today, the integration in route.c and tun.c is windows-only, but could be 
adapted
   to other platforms

v2: Steffan Karger
 - Message-ID: <548d9046.5000...@karger.me>
 - include "openvpn-msg.h" not "include/openvpn-msg.h"
 - add $(top_srcdir)/include to openvpnsrv build for out-of-tree builds

v3: Gert Doering, rebasing and integrating review feedback
 - rebased to 417fe4a72c
 - r->metric_defined is now r->flags & RT_METRIC_DEFINED (c3ef2d2333fb)
 - move "openvpn-msg.h" include inside #ifdef WIN32 (windows-only right now)
 - hide "msg_channel" extra option inside tt->tuntap_options, so we do not
   need an extra argument to all the add/del_route...() functions
 - do_route_ipv6_service(): use r->adapter index (if set) for RGI6 routes

Signed-off-by: Heiko Hund <heiko.h...@sophos.com>
Signed-off-by: Gert Doering <g...@greenie.muc.de>
---
 configure.ac                  |    1 +
 include/Makefile.am           |    4 +-
 include/openvpn-msg.h         |  108 ++++
 src/openvpn/buffer.c          |    2 +-
 src/openvpn/init.c            |    6 +
 src/openvpn/options.c         |   18 +
 src/openvpn/options.h         |    1 +
 src/openvpn/route.c           |  317 +++++++---
 src/openvpn/route.h           |    1 +
 src/openvpn/tun.c             |  171 ++++--
 src/openvpn/tun.h             |    4 +
 src/openvpnserv/Makefile.am   |   14 +-
 src/openvpnserv/automatic.c   |  416 ++++++++++++++
 src/openvpnserv/common.c      |  211 +++++++
 src/openvpnserv/interactive.c | 1273 +++++++++++++++++++++++++++++++++++++++++
 src/openvpnserv/openvpnserv.c |  534 -----------------
 src/openvpnserv/service.c     |  861 +++++++---------------------
 src/openvpnserv/service.h     |  213 +++----
 18 files changed, 2706 insertions(+), 1449 deletions(-)
 create mode 100644 include/openvpn-msg.h
 create mode 100644 src/openvpnserv/automatic.c
 create mode 100644 src/openvpnserv/common.c
 create mode 100644 src/openvpnserv/interactive.c
 delete mode 100755 src/openvpnserv/openvpnserv.c

diff --git a/configure.ac b/configure.ac
index 7ff2435..6597194 100644
--- a/configure.ac
+++ b/configure.ac
@@ -58,6 +58,7 @@ m4_define([serial_tests], [
 AM_INIT_AUTOMAKE(foreign serial_tests) dnl NB: Do not [quote] this parameter.
 AC_CANONICAL_HOST
 AC_USE_SYSTEM_EXTENSIONS
+AM_PROG_CC_C_O

 AC_ARG_ENABLE(
        [lzo],
diff --git a/include/Makefile.am b/include/Makefile.am
index c5a91b1..498b3b5 100644
--- a/include/Makefile.am
+++ b/include/Makefile.am
@@ -13,4 +13,6 @@ MAINTAINERCLEANFILES = \
        $(srcdir)/Makefile.in \
        $(srcdir)/openvpn-plugin.h.in

-include_HEADERS = openvpn-plugin.h
+include_HEADERS = \
+       openvpn-plugin.h \
+       openvpn-msg.h
diff --git a/include/openvpn-msg.h b/include/openvpn-msg.h
new file mode 100644
index 0000000..0dcc72f
--- /dev/null
+++ b/include/openvpn-msg.h
@@ -0,0 +1,108 @@
+/*
+ *  OpenVPN -- An application to securely tunnel IP networks
+ *             over a single TCP/UDP port, with support for SSL/TLS-based
+ *             session authentication and key exchange,
+ *             packet encryption, packet authentication, and
+ *             packet compression.
+ *
+ *  Copyright (C) 2013 Heiko Hund <heiko.h...@sophos.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2
+ *  as published by the Free Software Foundation.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program (see the file COPYING included with this
+ *  distribution); if not, write to the Free Software Foundation, Inc.,
+ *  59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifndef OPENVPN_MSG_H_
+#define OPENVPN_MSG_H_
+
+typedef enum {
+  msg_acknowledgement,
+  msg_add_address,
+  msg_del_address,
+  msg_add_route,
+  msg_del_route,
+  msg_add_dns_cfg,
+  msg_del_dns_cfg,
+  msg_add_nbt_cfg,
+  msg_del_nbt_cfg,
+  msg_flush_neighbors
+} message_type_t;
+
+typedef struct {
+  message_type_t type;
+  size_t size;
+  int message_id;
+} message_header_t;
+
+typedef union {
+  struct in_addr ipv4;
+  struct in6_addr ipv6;
+} inet_address_t;
+
+typedef struct {
+  int index;
+  char name[256];
+} interface_t;
+
+typedef struct {
+  message_header_t header;
+  short family;
+  inet_address_t address;
+  int prefix_len;
+  interface_t iface;
+} address_message_t;
+
+typedef struct {
+  message_header_t header;
+  short family;
+  inet_address_t prefix;
+  int prefix_len;
+  inet_address_t gateway;
+  interface_t iface;
+  int metric;
+} route_message_t;
+
+typedef struct {
+  message_header_t header;
+  interface_t iface;
+  char domains[512];
+  struct in_addr primary_ipv4;
+  struct in_addr secondary_ipv4;
+  struct in_addr6 primary_ipv6;
+  struct in_addr6 secondary_ipv6;
+} dns_cfg_message_t;
+
+typedef struct {
+  message_header_t header;
+  interface_t iface;
+  int disable_nbt;
+  int nbt_type;
+  char scope_id[256];
+  struct in_addr primary_nbns;
+  struct in_addr secondary_nbns;
+} nbt_cfg_message_t;
+
+// TODO: NTP
+
+typedef struct {
+  message_header_t header;
+  short family;
+  interface_t iface;
+} flush_neighbors_message_t;
+
+typedef struct {
+  message_header_t header;
+  int error_number;
+} ack_message_t;
+
+#endif
diff --git a/src/openvpn/buffer.c b/src/openvpn/buffer.c
index 421d60e..bc67d65 100644
--- a/src/openvpn/buffer.c
+++ b/src/openvpn/buffer.c
@@ -254,7 +254,7 @@ buf_puts(struct buffer *buf, const char *str)
  *
  * Return false on overflow.
  *
- * This function is duplicated into service-win32/openvpnserv.c
+ * This functionality is duplicated in src/openvpnserv/common.c
  * Any modifications here should be done to the other place as well.
  */

diff --git a/src/openvpn/init.c b/src/openvpn/init.c
index 2beec72..d0020b7 100644
--- a/src/openvpn/init.c
+++ b/src/openvpn/init.c
@@ -1433,6 +1433,12 @@ do_open_tun (struct context *c)
       /* initialize (but do not open) tun/tap object */
       do_init_tun (c);

+#ifdef WIN32
+      /* store (hide) interactive service handle in tuntap_options */
+      c->c1.tuntap->options.msg_channel = c->options.msg_channel;
+      msg (D_ROUTE, "interactive service msg_channel=%u", (unsigned int) 
c->options.msg_channel);
+#endif
+
       /* allocate route list structure */
       do_alloc_route_list (c);

diff --git a/src/openvpn/options.c b/src/openvpn/options.c
index b710c9c..6d97b4f 100644
--- a/src/openvpn/options.c
+++ b/src/openvpn/options.c
@@ -6015,6 +6015,24 @@ add_option (struct options *options,
     }
 #endif
 #endif
+  else if (streq (p[0], "msg-channel") && p[1])
+    {
+#ifdef WIN32
+      VERIFY_PERMISSION (OPT_P_GENERAL);
+      HANDLE process = GetCurrentProcess ();
+      HANDLE handle = (HANDLE) atoi (p[1]);
+      if (!DuplicateHandle (process, handle, process, &options->msg_channel, 0,
+                            FALSE, DUPLICATE_CLOSE_SOURCE | 
DUPLICATE_SAME_ACCESS))
+        {
+          msg (msglevel, "could not duplicate service pipe handle");
+          goto err;
+        }
+      options->route_method = ROUTE_METHOD_SERVICE;
+#else
+      msg (msglevel, "--msg-channel is only supported on Windows");
+      goto err;
+#endif
+    }
 #ifdef WIN32
   else if (streq (p[0], "win-sys") && p[1] && !p[2])
     {
diff --git a/src/openvpn/options.h b/src/openvpn/options.h
index 9e8a6a0..23d3992 100644
--- a/src/openvpn/options.h
+++ b/src/openvpn/options.h
@@ -581,6 +581,7 @@ struct options
   int foreign_option_index;

 #ifdef WIN32
+  HANDLE msg_channel;
   const char *exit_event_name;
   bool exit_event_initial_state;
   bool show_net_up;
diff --git a/src/openvpn/route.c b/src/openvpn/route.c
index 2012b5c..0815176 100644
--- a/src/openvpn/route.c
+++ b/src/openvpn/route.c
@@ -50,7 +50,13 @@
 #endif

 #ifdef WIN32
+#include "openvpn-msg.h"
+
 #define METRIC_NOT_USED ((DWORD)-1)
+static bool add_route_service (const struct route_ipv4 *, const struct tuntap 
*);
+static bool del_route_service (const struct route_ipv4 *, const struct tuntap 
*);
+static bool add_route_ipv6_service (const struct route_ipv6 *, const struct 
tuntap *);
+static bool del_route_ipv6_service (const struct route_ipv6 *, const struct 
tuntap *);
 #endif

 static void delete_route (struct route_ipv4 *r, const struct tuntap *tt, 
unsigned int flags, const struct route_gateway_info *rgi, const struct env_set 
*es);
@@ -1468,7 +1474,12 @@ add_route (struct route_ipv4 *r,

     argv_msg (D_ROUTE, &argv);

-    if ((flags & ROUTE_METHOD_MASK) == ROUTE_METHOD_IPAPI)
+    if ((flags & ROUTE_METHOD_MASK) == ROUTE_METHOD_SERVICE)
+      {
+        status = add_route_service (r, tt);
+        msg (D_ROUTE, "Route addition via service %s", status ? "succeeded" : 
"failed");
+      }
+    else if ((flags & ROUTE_METHOD_MASK) == ROUTE_METHOD_IPAPI)
       {
        status = add_route_ipapi (r, tt, ai);
        msg (D_ROUTE, "Route addition via IPAPI %s", status ? "succeeded" : 
"failed");
@@ -1637,26 +1648,23 @@ add_route (struct route_ipv4 *r,
 }


-static const char * 
-print_in6_addr_netbits_only( struct in6_addr network_copy, int netbits, 
-                             struct gc_arena * gc)
+static void
+route_ipv6_clear_host_bits( struct route_ipv6 *r6 )
 {
   /* clear host bit parts of route 
    * (needed if routes are specified improperly, or if we need to 
    * explicitely setup/clear the "connected" network routes on some OSes)
    */
   int byte = 15;
-  int bits_to_clear = 128 - netbits;
+  int bits_to_clear = 128 - r6->netbits;

   while( byte >= 0 && bits_to_clear > 0 )
     {
       if ( bits_to_clear >= 8 )
-       { network_copy.s6_addr[byte--] = 0; bits_to_clear -= 8; }
+       { r6->network.s6_addr[byte--] = 0; bits_to_clear -= 8; }
       else
-       { network_copy.s6_addr[byte--] &= (0xff << bits_to_clear); 
bits_to_clear = 0; }
+       { r6->network.s6_addr[byte--] &= (0xff << bits_to_clear); bits_to_clear 
= 0; }
     }
-
-  return print_in6_addr( network_copy, 0, gc);
 }

 void
@@ -1687,7 +1695,9 @@ add_route_ipv6 (struct route_ipv6 *r6, const struct 
tuntap *tt, unsigned int fla
   gc_init (&gc);
   argv_init (&argv);

-  network = print_in6_addr_netbits_only( r6->network, r6->netbits, &gc);
+  route_ipv6_clear_host_bits (r6);
+
+  network = print_in6_addr( r6->network, 0, &gc);
   gateway = print_in6_addr( r6->gateway, 0, &gc);

 #if defined(TARGET_DARWIN) || \
@@ -1770,51 +1780,56 @@ add_route_ipv6 (struct route_ipv6 *r6, const struct 
tuntap *tt, unsigned int fla

 #elif defined (WIN32)

-  struct buffer out = alloc_buf_gc (64, &gc);
-  if ( r6->adapter_index )             /* vpn server special route */
-    {
-      buf_printf (&out, "interface=%d", r6->adapter_index );
-      gateway_needed = true;
-    }
+  if (tt->options.msg_channel)
+    status = add_route_ipv6_service (r6, tt);
   else
     {
-      buf_printf (&out, "interface=%d", tt->adapter_index );
-    }
-  device = buf_bptr(&out);
-
-  /* netsh interface ipv6 add route 2001:db8::/32 MyTunDevice */
-  argv_printf (&argv, "%s%sc interface ipv6 add route %s/%d %s",
-              get_win_sys_path(),
-              NETSH_PATH_SUFFIX,
-              network,
-              r6->netbits,
-              device);
+      struct buffer out = alloc_buf_gc (64, &gc);
+      if ( r6->adapter_index )         /* vpn server special route */
+       {
+         buf_printf (&out, "interface=%d", r6->adapter_index );
+         gateway_needed = true;
+       }
+      else
+       {
+         buf_printf (&out, "interface=%d", tt->adapter_index );
+       }
+      device = buf_bptr(&out);

-  /* next-hop depends on TUN or TAP mode:
-   * - in TAP mode, we use the "real" next-hop
-   * - in TUN mode we use a special-case link-local address that the tapdrvr
-   *   knows about and will answer ND (neighbor discovery) packets for
-   */
-  if ( tt->type == DEV_TYPE_TUN && !gateway_needed )
-       argv_printf_cat( &argv, " %s", "fe80::8" );
-  else if ( !IN6_IS_ADDR_UNSPECIFIED(&r6->gateway) )
-       argv_printf_cat( &argv, " %s", gateway );
+      /* netsh interface ipv6 add route 2001:db8::/32 MyTunDevice */
+      argv_printf (&argv, "%s%sc interface ipv6 add route %s/%d %s",
+                  get_win_sys_path(),
+                  NETSH_PATH_SUFFIX,
+                  network,
+                  r6->netbits,
+                  device);
+
+      /* next-hop depends on TUN or TAP mode:
+       * - in TAP mode, we use the "real" next-hop
+       * - in TUN mode we use a special-case link-local address that the 
tapdrvr
+       *   knows about and will answer ND (neighbor discovery) packets for
+       */
+      if ( tt->type == DEV_TYPE_TUN && !gateway_needed )
+           argv_printf_cat( &argv, " %s", "fe80::8" );
+      else if ( !IN6_IS_ADDR_UNSPECIFIED(&r6->gateway) )
+           argv_printf_cat( &argv, " %s", gateway );

 #if 0
-  if (r6->flags & RT_METRIC_DEFINED)
-    argv_printf_cat (&argv, " METRIC %d", r->metric);
+      if (r6->flags & RT_METRIC_DEFINED)
+       argv_printf_cat (&argv, " METRIC %d", r->metric);
 #endif

-  /* in some versions of Windows, routes are persistent across reboots by
-   * default, unless "store=active" is set (pointed out by Tony Lim, thanks)
-   */
-  argv_printf_cat( &argv, " store=active" );
+      /* in some versions of Windows, routes are persistent across reboots by
+       * default, unless "store=active" is set (pointed out by Tony Lim, 
thanks)
+       */
+      argv_printf_cat( &argv, " store=active" );

-  argv_msg (D_ROUTE, &argv);
+      argv_msg (D_ROUTE, &argv);

-  netcmd_semaphore_lock ();
-  status = openvpn_execve_check (&argv, es, 0, "ERROR: Windows route add ipv6 
command failed");
-  netcmd_semaphore_release ();
+      netcmd_semaphore_lock ();
+      status = openvpn_execve_check (&argv, es, 0, "ERROR: Windows route add 
ipv6 command failed");
+      netcmd_semaphore_release ();
+    }

 #elif defined (TARGET_SOLARIS)

@@ -1965,7 +1980,12 @@ delete_route (struct route_ipv4 *r,

   argv_msg (D_ROUTE, &argv);

-  if ((flags & ROUTE_METHOD_MASK) == ROUTE_METHOD_IPAPI)
+  if ((flags & ROUTE_METHOD_MASK) == ROUTE_METHOD_SERVICE)
+    {
+      const bool status = del_route_service (r, tt);
+      msg (D_ROUTE, "Route deletion via service %s", status ? "succeeded" : 
"failed");
+    }
+  else if ((flags & ROUTE_METHOD_MASK) == ROUTE_METHOD_IPAPI)
     {
       const bool status = del_route_ipapi (r, tt);
       msg (D_ROUTE, "Route deletion via IPAPI %s", status ? "succeeded" : 
"failed");
@@ -2107,7 +2127,7 @@ delete_route_ipv6 (const struct route_ipv6 *r6, const 
struct tuntap *tt, unsigne
   gc_init (&gc);
   argv_init (&argv);

-  network = print_in6_addr_netbits_only( r6->network, r6->netbits, &gc);
+  network = print_in6_addr( r6->network, 0, &gc);
   gateway = print_in6_addr( r6->gateway, 0, &gc);

 #if defined(TARGET_DARWIN) || \
@@ -2172,53 +2192,58 @@ delete_route_ipv6 (const struct route_ipv6 *r6, const 
struct tuntap *tt, unsigne

 #elif defined (WIN32)

-  struct buffer out = alloc_buf_gc (64, &gc);
-  if ( r6->adapter_index )             /* vpn server special route */
-    {
-      buf_printf (&out, "interface=%d", r6->adapter_index );
-      gateway_needed = true;
-    }
+  if (tt->options.msg_channel)
+    del_route_ipv6_service (r6, tt);
   else
     {
-      buf_printf (&out, "interface=%d", tt->adapter_index );
-    }
-  device = buf_bptr(&out);
+      struct buffer out = alloc_buf_gc (64, &gc);
+      if ( r6->adapter_index )         /* vpn server special route */
+       {
+         buf_printf (&out, "interface=%d", r6->adapter_index );
+         gateway_needed = true;
+       }
+      else
+       {
+         buf_printf (&out, "interface=%d", tt->adapter_index );
+       }
+      device = buf_bptr(&out);

-  /* netsh interface ipv6 delete route 2001:db8::/32 MyTunDevice */
-  argv_printf (&argv, "%s%sc interface ipv6 delete route %s/%d %s",
-              get_win_sys_path(),
-              NETSH_PATH_SUFFIX,
-              network,
-              r6->netbits,
-              device);
-
-  /* next-hop depends on TUN or TAP mode:
-   * - in TAP mode, we use the "real" next-hop
-   * - in TUN mode we use a special-case link-local address that the tapdrvr
-   *   knows about and will answer ND (neighbor discovery) packets for
-   * (and "route deletion without specifying next-hop" does not work...)
-   */
-  if ( tt->type == DEV_TYPE_TUN && !gateway_needed )
-       argv_printf_cat( &argv, " %s", "fe80::8" );
-  else if ( !IN6_IS_ADDR_UNSPECIFIED(&r6->gateway) )
-       argv_printf_cat( &argv, " %s", gateway );
+      /* netsh interface ipv6 delete route 2001:db8::/32 MyTunDevice */
+      argv_printf (&argv, "%s%sc interface ipv6 delete route %s/%d %s",
+                  get_win_sys_path(),
+                  NETSH_PATH_SUFFIX,
+                  network,
+                  r6->netbits,
+                  device);
+
+      /* next-hop depends on TUN or TAP mode:
+       * - in TAP mode, we use the "real" next-hop
+       * - in TUN mode we use a special-case link-local address that the 
tapdrvr
+       *   knows about and will answer ND (neighbor discovery) packets for
+       * (and "route deletion without specifying next-hop" does not work...)
+       */
+      if ( tt->type == DEV_TYPE_TUN && !gateway_needed )
+           argv_printf_cat( &argv, " %s", "fe80::8" );
+      else if ( !IN6_IS_ADDR_UNSPECIFIED(&r6->gateway) )
+           argv_printf_cat( &argv, " %s", gateway );

 #if 0
-  if (r6->flags & RT_METRIC_DEFINED)
-    argv_printf_cat (&argv, "METRIC %d", r->metric);
+      if (r6->flags & RT_METRIC_DEFINED)
+       argv_printf_cat (&argv, "METRIC %d", r->metric);
 #endif

-  /* Windows XP to 7 "just delete" routes, wherever they came from, but
-   * in Windows 8(.1?), if you create them with "store=active", this is
-   * how you should delete them as well (pointed out by Cedric Tabary)
-   */
-  argv_printf_cat( &argv, " store=active" );
+      /* Windows XP to 7 "just delete" routes, wherever they came from, but
+       * in Windows 8(.1?), if you create them with "store=active", this is
+       * how you should delete them as well (pointed out by Cedric Tabary)
+       */
+      argv_printf_cat( &argv, " store=active" );

-  argv_msg (D_ROUTE, &argv);
+      argv_msg (D_ROUTE, &argv);

-  netcmd_semaphore_lock ();
-  openvpn_execve_check (&argv, es, 0, "ERROR: Windows route delete ipv6 
command failed");
-  netcmd_semaphore_release ();
+      netcmd_semaphore_lock ();
+      openvpn_execve_check (&argv, es, 0, "ERROR: Windows route delete ipv6 
command failed");
+      netcmd_semaphore_release ();
+    }

 #elif defined (TARGET_SOLARIS)

@@ -2705,6 +2730,126 @@ del_route_ipapi (const struct route_ipv4 *r, const 
struct tuntap *tt)
   return ret;
 }

+static bool
+do_route_service (const bool add, const route_message_t *rt, const size_t 
size, HANDLE pipe)
+{
+  DWORD len;
+  bool ret = false;
+  ack_message_t ack;
+  struct gc_arena gc = gc_new ();
+
+  if (!WriteFile (pipe, rt, size, &len, NULL) ||
+      !ReadFile (pipe, &ack, sizeof (ack), &len, NULL))
+    {
+      msg (M_WARN, "ROUTE: could not talk to service: %s [%lu]",
+           strerror_win32 (GetLastError (), &gc), GetLastError ());
+      goto out;
+    }
+
+  if (ack.error_number != NO_ERROR)
+    {
+      msg (M_WARN, "ROUTE: route %s failed using service: %s [status=%u 
if_index=%lu]",
+           (add ? "addition" : "deletion"), strerror_win32 (ack.error_number, 
&gc),
+           ack.error_number, rt->iface.index);
+      goto out;
+    }
+
+  ret = true;
+
+out:
+  gc_free (&gc);
+  return ret;
+}
+
+static bool
+do_route_ipv4_service (const bool add, const struct route_ipv4 *r, const 
struct tuntap *tt)
+{
+  DWORD if_index = windows_route_find_if_index (r, tt);
+  if (if_index == ~0)
+    return false;
+
+  route_message_t msg = {
+    .header = {
+      (add ? msg_add_route : msg_del_route),
+      sizeof (route_message_t),
+      0 },
+    .family = AF_INET,
+    .prefix.ipv4.s_addr = htonl(r->network),
+    .gateway.ipv4.s_addr = htonl(r->gateway),
+    .iface = { .index = if_index, .name = "" },
+    .metric = (r->flags & RT_METRIC_DEFINED ? r->metric : -1)
+  };
+
+  netmask_to_netbits (r->network, r->netmask, &msg.prefix_len);
+  if (msg.prefix_len == -1)
+    msg.prefix_len = 32;
+
+  return do_route_service (add, &msg, sizeof (msg), tt->options.msg_channel);
+}
+
+static bool
+do_route_ipv6_service (const bool add, const struct route_ipv6 *r, const 
struct tuntap *tt)
+{
+  bool status;
+  route_message_t msg = {
+    .header = {
+      (add ? msg_add_route : msg_del_route),
+      sizeof (route_message_t),
+      0 },
+    .family = AF_INET6,
+    .prefix.ipv6 = r->network,
+    .prefix_len = r->netbits,
+    .gateway.ipv6 = r->gateway,
+    .iface = { .index = tt->adapter_index, .name = "" },
+    .metric = ( (r->flags & RT_METRIC_DEFINED) ? r->metric : -1)
+  };
+
+  if ( r->adapter_index )              /* vpn server special route */
+    msg.iface.index = r->adapter_index;
+
+  /* In TUN mode we use a special link-local address as the next hop.
+   * The tapdrvr knows about it and will answer neighbor discovery packets.
+   */
+  if (tt->type == DEV_TYPE_TUN)
+    inet_pton (AF_INET6, "fe80::8", &msg.gateway.ipv6);
+
+  if (msg.iface.index == TUN_ADAPTER_INDEX_INVALID)
+    {
+      strncpy (msg.iface.name, tt->actual_name, sizeof (msg.iface.name));
+      msg.iface.name[sizeof (msg.iface.name) - 1] = '\0';
+    }
+
+  status = do_route_service (add, &msg, sizeof (msg), tt->options.msg_channel);
+  msg (D_ROUTE, "IPv6 route %s via service %s",
+                       add ? "addition" : "deletion",
+                       status ? "succeeded" : "failed");
+  return status;
+}
+
+static bool
+add_route_service (const struct route_ipv4 *r, const struct tuntap *tt)
+{
+  return do_route_ipv4_service (true, r, tt);
+}
+
+static bool
+del_route_service (const struct route_ipv4 *r, const struct tuntap *tt)
+{
+  return do_route_ipv4_service (false, r, tt);
+}
+
+static bool
+add_route_ipv6_service (const struct route_ipv6 *r, const struct tuntap *tt)
+{
+  return do_route_ipv6_service (true, r, tt);
+}
+
+static bool
+del_route_ipv6_service (const struct route_ipv6 *r, const struct tuntap *tt)
+{
+  return do_route_ipv6_service (false, r, tt);
+}
+
 static const char *
 format_route_entry (const MIB_IPFORWARDROW *r, struct gc_arena *gc)
 {
diff --git a/src/openvpn/route.h b/src/openvpn/route.h
index 4bbcdb7..58a5748 100644
--- a/src/openvpn/route.h
+++ b/src/openvpn/route.h
@@ -40,6 +40,7 @@
 #define ROUTE_METHOD_ADAPTIVE  0  /* try IP helper first then route.exe */
 #define ROUTE_METHOD_IPAPI     1  /* use IP helper API */
 #define ROUTE_METHOD_EXE       2  /* use route.exe */
+#define ROUTE_METHOD_SERVICE   3  /* use the privileged Windows service */
 #define ROUTE_METHOD_MASK      3
 #endif

diff --git a/src/openvpn/tun.c b/src/openvpn/tun.c
index efcd225..eaeb6cc 100644
--- a/src/openvpn/tun.c
+++ b/src/openvpn/tun.c
@@ -48,6 +48,11 @@
 #include "win32.h"

 #include "memdbg.h"
+
+#ifdef WIN32
+#include "openvpn-msg.h"
+#endif
+
 #include <string.h>

 #ifdef WIN32
@@ -69,6 +74,64 @@ static const char *netsh_get_id (const char *dev_node, 
struct gc_arena *gc);

 static DWORD get_adapter_index_flexible (const char *name);

+static bool
+do_address_service (const bool add, const short family, const struct tuntap 
*tt)
+{
+  DWORD len;
+  bool ret = false;
+  ack_message_t ack;
+  struct gc_arena gc = gc_new ();
+  HANDLE pipe = tt->options.msg_channel;
+
+  address_message_t addr = {
+    .header = {
+      (add ? msg_add_address : msg_del_address),
+      sizeof (address_message_t),
+      0 },
+    .family = family,
+    .iface = { .index = tt->adapter_index, .name = "" }
+  };
+
+  if (addr.iface.index == TUN_ADAPTER_INDEX_INVALID)
+    {
+      strncpy (addr.iface.name, tt->actual_name, sizeof (addr.iface.name));
+      addr.iface.name[sizeof (addr.iface.name) - 1] = '\0';
+    }
+
+  if (addr.family == AF_INET)
+    {
+      addr.address.ipv4.s_addr = tt->local;
+      addr.prefix_len = 32;
+    }
+  else
+    {
+      addr.address.ipv6 = tt->local_ipv6;
+      addr.prefix_len = tt->netbits_ipv6;
+    }
+
+  if (!WriteFile (pipe, &addr, sizeof (addr), &len, NULL) ||
+      !ReadFile (pipe, &ack, sizeof (ack), &len, NULL))
+    {
+      msg (M_WARN, "TUN: could not talk to service: %s [%lu]",
+           strerror_win32 (GetLastError (), &gc), GetLastError ());
+      goto out;
+    }
+
+  if (ack.error_number != NO_ERROR)
+    {
+      msg (M_WARN, "TUN: %s address failed using service: %s [status=%u 
if_index=%lu]",
+           (add ? "adding" : "deleting"), strerror_win32 (ack.error_number, 
&gc),
+           ack.error_number, addr.iface.index);
+      goto out;
+    }
+
+  ret = true;
+
+out:
+  gc_free (&gc);
+  return ret;
+}
+
 #endif

 #ifdef TARGET_SOLARIS
@@ -1301,7 +1364,6 @@ do_ifconfig (struct tuntap *tt,
        tt->did_ifconfig = true;
       }

-    /* IPv6 always uses "netsh" interface */
     if ( do_ipv6 )
       {
        char * saved_actual;
@@ -1314,16 +1376,6 @@ do_ifconfig (struct tuntap *tt,
        idx = get_adapter_index_flexible(actual);
        openvpn_snprintf(iface, sizeof(iface), "interface=%lu", idx);

-       /* example: netsh interface ipv6 set address interface=42 
2001:608:8003::d store=active */
-       argv_printf (&argv,
-                    "%s%sc interface ipv6 set address %s %s store=active",
-                    get_win_sys_path(),
-                    NETSH_PATH_SUFFIX,
-                    iface,
-                    ifconfig_ipv6_local);
-       netsh_command (&argv, 4);
-
-       /* explicit route needed */
        /* on windows, OpenVPN does ifconfig first, open_tun later, so
         * tt->actual_name might not yet be initialized, but routing code
         * needs to know interface name - point to "actual", restore later
@@ -1332,6 +1384,24 @@ do_ifconfig (struct tuntap *tt,
        tt->actual_name = (char*) actual;
        /* we use adapter_index in add_route_ipv6 */
        tt->adapter_index = idx;
+
+       if (tt->options.msg_channel)
+         {
+           do_address_service (true, AF_INET6, tt);
+         }
+       else
+         {
+           /* example: netsh interface ipv6 set address interface=42 
2001:608:8003::d store=active */
+           argv_printf (&argv,
+                        "%s%sc interface ipv6 set address %s %s store=active",
+                        get_win_sys_path(),
+                        NETSH_PATH_SUFFIX,
+                        iface,
+                        ifconfig_ipv6_local );
+           netsh_command (&argv, 4);
+         }
+
+       /* explicit route needed */
        add_route_connected_v6_net(tt, es);
        tt->actual_name = saved_actual;
       }
@@ -5403,13 +5473,35 @@ open_tun (const char *dev, const char *dev_type, const 
char *dev_node, struct tu
     /* flush arp cache */
     if (index != TUN_ADAPTER_INDEX_INVALID)
       {
-       DWORD status;
+        DWORD status = -1;

-       if ((status = FlushIpNetTable (index)) == NO_ERROR)
+        if (tt->options.msg_channel)
+          {
+            ack_message_t ack;
+            flush_neighbors_message_t msg = {
+              .header = {
+                msg_flush_neighbors,
+                sizeof (flush_neighbors_message_t),
+                0 },
+              .family = AF_INET,
+              .iface = { .index = index, .name = "" }
+            };
+
+            if (!WriteFile (tt->options.msg_channel, &msg, sizeof (msg), &len, 
NULL) ||
+                !ReadFile (tt->options.msg_channel, &ack, sizeof (ack), &len, 
NULL))
+              msg (M_WARN, "TUN: could not talk to service: %s [%lu]",
+                   strerror_win32 (GetLastError (), &gc), GetLastError ());
+
+            status = ack.error_number;
+          }
+        else
+          status = FlushIpNetTable (index);
+
+       if (status == NO_ERROR)
          msg (M_INFO, "Successful ARP Flush on interface [%u] %s",
               (unsigned int)index,
               device_guid);
-       else
+       else if (status != -1)
          msg (D_TUNTAP_INFO, "NOTE: FlushIpNetTable failed on interface [%u] 
%s (status=%u) : %s",
               (unsigned int)index,
               device_guid,
@@ -5530,28 +5622,35 @@ close_tun (struct tuntap *tt)
     {
       if ( tt->ipv6 && tt->did_ifconfig_ipv6_setup )
         {
-         const char *ifconfig_ipv6_local;
-         struct argv argv;
-         argv_init (&argv);
-
-         /* remove route pointing to interface */
-         delete_route_connected_v6_net(tt, NULL);
-
-         /* "store=active" is needed in Windows 8(.1) to delete the
-          * address we added (pointed out by Cedric Tabary).
-          */
-
-         /* netsh interface ipv6 delete address \"%s\" %s */
-         ifconfig_ipv6_local = print_in6_addr (tt->local_ipv6, 0,  &gc);
-         argv_printf (&argv,
-                   "%s%sc interface ipv6 delete address %s %s store=active",
-                    get_win_sys_path(),
-                    NETSH_PATH_SUFFIX,
-                    tt->actual_name,
-                    ifconfig_ipv6_local );
-
-         netsh_command (&argv, 1);
-          argv_reset (&argv);
+          if (tt->options.msg_channel)
+            {
+              do_address_service (false, AF_INET6, tt);
+            }
+          else
+            {
+              const char *ifconfig_ipv6_local;
+              struct argv argv;
+              argv_init (&argv);
+
+              /* remove route pointing to interface */
+              delete_route_connected_v6_net(tt, NULL);
+
+              /* "store=active" is needed in Windows 8(.1) to delete the
+               * address we added (pointed out by Cedric Tabary).
+               */
+
+              /* netsh interface ipv6 delete address \"%s\" %s */
+              ifconfig_ipv6_local = print_in6_addr (tt->local_ipv6, 0,  &gc);
+              argv_printf (&argv,
+                           "%s%sc interface ipv6 delete address %s %s 
store=active",
+                           get_win_sys_path(),
+                           NETSH_PATH_SUFFIX,
+                           tt->actual_name,
+                           ifconfig_ipv6_local);
+
+              netsh_command (&argv, 1);
+              argv_reset (&argv);
+            }
        }
 #if 1
       if (tt->ipapi_context_defined)
diff --git a/src/openvpn/tun.h b/src/openvpn/tun.h
index 65bacac..4e93a3f 100644
--- a/src/openvpn/tun.h
+++ b/src/openvpn/tun.h
@@ -58,6 +58,10 @@ struct tuntap_options {
 # define IPW32_SET_N            5
   int ip_win32_type;

+#ifdef WIN32
+  HANDLE msg_channel;
+#endif
+
   /* --ip-win32 dynamic options */
   bool dhcp_masq_custom_offset;
   int dhcp_masq_offset;
diff --git a/src/openvpnserv/Makefile.am b/src/openvpnserv/Makefile.am
index a989c25..4bb1c27 100644
--- a/src/openvpnserv/Makefile.am
+++ b/src/openvpnserv/Makefile.am
@@ -17,11 +17,21 @@ EXTRA_DIST = \
        openvpnserv.vcxproj \
        openvpnserv.vcxproj.filters

+AM_CPPFLAGS = \
+       -I$(top_srcdir)/include
+
 if WIN32
 sbin_PROGRAMS = openvpnserv
+openvpnserv_CFLAGS = \
+       -municode -D_UNICODE \
+       -UNTDDI_VERSION -U_WIN32_WINNT \
+       -D_WIN32_WINNT=_WIN32_WINNT_VISTA
+openvpnserv_LDADD = -ladvapi32 -luserenv -liphlpapi -lws2_32
 endif

 openvpnserv_SOURCES = \
-       openvpnserv.c \
-       service.h service.c \
+        common.c \
+       automatic.c \
+       interactive.c \
+       service.c service.h \
        openvpnserv_resources.rc
diff --git a/src/openvpnserv/automatic.c b/src/openvpnserv/automatic.c
new file mode 100644
index 0000000..4bd63fd
--- /dev/null
+++ b/src/openvpnserv/automatic.c
@@ -0,0 +1,416 @@
+/*
+ *  OpenVPN -- An application to securely tunnel IP networks
+ *             over a single TCP/UDP port, with support for SSL/TLS-based
+ *             session authentication and key exchange,
+ *             packet encryption, packet authentication, and
+ *             packet compression.
+ *
+ *  Copyright (C) 2002-2010 OpenVPN Technologies, Inc. <sa...@openvpn.net>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2
+ *  as published by the Free Software Foundation.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program (see the file COPYING included with this
+ *  distribution); if not, write to the Free Software Foundation, Inc.,
+ *  59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/*
+ * This program allows one or more OpenVPN processes to be started
+ * as a service.  To build, you must get the service sample from the
+ * Platform SDK and replace Simple.c with this file.
+ *
+ * You should also apply service.patch to
+ * service.c and service.h from the Platform SDK service sample.
+ *
+ * This code is designed to be built with the mingw compiler.
+ */
+
+#include "service.h"
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <process.h>
+
+/* bool definitions */
+#define bool int
+#define true 1
+#define false 0
+
+static SERVICE_STATUS_HANDLE service;
+static SERVICE_STATUS status;
+
+openvpn_service_t automatic_service = {
+  automatic,
+  TEXT(PACKAGE_NAME "Service"),
+  TEXT(PACKAGE_NAME " Service"),
+  TEXT(SERVICE_DEPENDENCIES),
+  SERVICE_DEMAND_START
+};
+
+struct security_attributes
+{
+  SECURITY_ATTRIBUTES sa;
+  SECURITY_DESCRIPTOR sd;
+};
+
+/*
+ * Which registry key in HKLM should
+ * we get config info from?
+ */
+#define REG_KEY "SOFTWARE\\" PACKAGE_NAME
+
+static HANDLE exit_event = NULL;
+
+/* clear an object */
+#define CLEAR(x) memset(&(x), 0, sizeof(x))
+
+
+bool
+init_security_attributes_allow_all (struct security_attributes *obj)
+{
+  CLEAR (*obj);
+
+  obj->sa.nLength = sizeof (SECURITY_ATTRIBUTES);
+  obj->sa.lpSecurityDescriptor = &obj->sd;
+  obj->sa.bInheritHandle = TRUE;
+  if (!InitializeSecurityDescriptor (&obj->sd, SECURITY_DESCRIPTOR_REVISION))
+    return false;
+  if (!SetSecurityDescriptorDacl (&obj->sd, TRUE, NULL, FALSE))
+    return false;
+  return true;
+}
+
+/*
+ * This event is initially created in the non-signaled
+ * state.  It will transition to the signaled state when
+ * we have received a terminate signal from the Service
+ * Control Manager which will cause an asynchronous call
+ * of ServiceStop below.
+ */
+#define EXIT_EVENT_NAME  TEXT(PACKAGE "_exit_1")
+
+HANDLE
+create_event (LPCTSTR name, bool allow_all, bool initial_state, bool 
manual_reset)
+{
+  if (allow_all)
+    {
+      struct security_attributes sa;
+      if (!init_security_attributes_allow_all (&sa))
+        return NULL;
+      return CreateEvent (&sa.sa, (BOOL)manual_reset, (BOOL)initial_state, 
name);
+    }
+  else
+    return CreateEvent (NULL, (BOOL)manual_reset, (BOOL)initial_state, name);
+}
+
+void
+close_if_open (HANDLE h)
+{
+  if (h != NULL)
+    CloseHandle (h);
+}
+
+static bool
+match (const WIN32_FIND_DATA *find, LPCTSTR ext)
+{
+  int i;
+
+  if (find->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+    return false;
+
+  if (!_tcslen (ext))
+    return true;
+
+  i = _tcslen (find->cFileName) - _tcslen (ext) - 1;
+  if (i < 1)
+    return false;
+
+  return find->cFileName[i] == '.' && !_tcsicmp (find->cFileName + i + 1, ext);
+}
+
+/*
+ * Modify the extension on a filename.
+ */
+static bool
+modext (LPTSTR dest, int size, LPCTSTR src, LPCTSTR newext)
+{
+  int i;
+
+  if (size > 0 && (_tcslen (src) + 1) <= size)
+    {
+      _tcscpy (dest, src);
+      dest [size - 1] = TEXT('\0');
+      i = _tcslen (dest);
+      while (--i >= 0)
+        {
+          if (dest[i] == TEXT('\\'))
+            break;
+          if (dest[i] == TEXT('.'))
+            {
+              dest[i] = TEXT('\0');
+              break;
+            }
+        }
+      if (_tcslen (dest) + _tcslen(newext) + 2 <= size)
+        {
+          _tcscat (dest, TEXT("."));
+          _tcscat (dest, newext);
+          return true;
+        }
+      dest[0] = TEXT('\0');
+    }
+  return false;
+}
+
+static DWORD WINAPI
+ServiceCtrlAutomatic (DWORD ctrl_code, DWORD event, LPVOID data, LPVOID ctx)
+{
+  SERVICE_STATUS *status = ctx;
+  switch (ctrl_code)
+    {
+    case SERVICE_CONTROL_STOP:
+      status->dwCurrentState = SERVICE_STOP_PENDING;
+      ReportStatusToSCMgr (service, status);
+      if (exit_event)
+        SetEvent (exit_event);
+      return NO_ERROR;
+
+    case SERVICE_CONTROL_INTERROGATE:
+      return NO_ERROR;
+
+    default:
+      return ERROR_CALL_NOT_IMPLEMENTED;
+    }
+}
+
+
+VOID WINAPI
+ServiceStartAutomatic (DWORD dwArgc, LPTSTR *lpszArgv)
+{
+  DWORD error = NO_ERROR;
+  settings_t settings;
+
+  service = RegisterServiceCtrlHandlerEx (automatic_service.name, 
ServiceCtrlAutomatic, &status);
+  if (!service)
+    return;
+
+  status.dwServiceType = SERVICE_WIN32_SHARE_PROCESS;
+  status.dwCurrentState = SERVICE_START_PENDING;
+  status.dwServiceSpecificExitCode = NO_ERROR;
+  status.dwWin32ExitCode = NO_ERROR;
+  status.dwWaitHint = 3000;
+
+  if (!ReportStatusToSCMgr(service, &status))
+    {
+      MsgToEventLog (M_ERR, TEXT("ReportStatusToSCMgr #1 failed"));
+      goto finish;
+    }
+
+  /*
+   * Create our exit event
+   */
+  exit_event = create_event (EXIT_EVENT_NAME, false, false, true);
+  if (!exit_event)
+    {
+      MsgToEventLog (M_ERR, TEXT("CreateEvent failed"));
+      goto finish;
+    }
+
+  /*
+   * If exit event is already signaled, it means we were not
+   * shut down properly.
+   */
+  if (WaitForSingleObject (exit_event, 0) != WAIT_TIMEOUT)
+    {
+      MsgToEventLog (M_ERR, TEXT("Exit event is already signaled -- we were 
not shut down properly"));
+      goto finish;
+    }
+
+  if (!ReportStatusToSCMgr(service, &status))
+    {
+      MsgToEventLog (M_ERR, TEXT("ReportStatusToSCMgr #2 failed"));
+      goto finish;
+    }
+
+  /*
+   * Read info from registry in key HKLM\SOFTWARE\OpenVPN
+   */
+  error = GetOpenvpnSettings (&settings);
+  if (error != ERROR_SUCCESS)
+    goto finish;
+
+  /*
+   * Instantiate an OpenVPN process for each configuration
+   * file found.
+   */
+  {
+    WIN32_FIND_DATA find_obj;
+    HANDLE find_handle;
+    BOOL more_files;
+    TCHAR find_string[MAX_PATH];
+
+    openvpn_sntprintf (find_string, MAX_PATH, TEXT("%s\\*"), 
settings.config_dir);
+
+    find_handle = FindFirstFile (find_string, &find_obj);
+    if (find_handle == INVALID_HANDLE_VALUE)
+      {
+        MsgToEventLog (M_ERR, TEXT("Cannot get configuration file list using: 
%s"), find_string);
+        goto finish;
+      }
+
+    /*
+     * Loop over each config file
+     */
+    do {
+      HANDLE log_handle = NULL;
+      STARTUPINFO start_info;
+      PROCESS_INFORMATION proc_info;
+      struct security_attributes sa;
+      TCHAR log_file[MAX_PATH];
+      TCHAR log_path[MAX_PATH];
+      TCHAR command_line[256];
+
+      CLEAR (start_info);
+      CLEAR (proc_info);
+      CLEAR (sa);
+
+      if (!ReportStatusToSCMgr(service, &status))
+        {
+          MsgToEventLog (M_ERR, TEXT("ReportStatusToSCMgr #3 failed"));
+          FindClose (find_handle);
+          goto finish;
+        }
+
+      /* does file have the correct type and extension? */
+      if (match (&find_obj, settings.ext_string))
+        {
+          /* get log file pathname */
+          if (!modext (log_file, _countof (log_file), find_obj.cFileName, 
TEXT("log")))
+            {
+              MsgToEventLog (M_ERR, TEXT("Cannot construct logfile name based 
on: %s"), find_obj.cFileName);
+              FindClose (find_handle);
+              goto finish;
+            }
+          openvpn_sntprintf (log_path, _countof (log_path),
+                             TEXT("%s\\%s"), settings.log_dir, log_file);
+
+          /* construct command line */
+          openvpn_sntprintf (command_line, _countof (command_line), 
TEXT(PACKAGE " --service %s 1 --config \"%s\""),
+                      EXIT_EVENT_NAME,
+                      find_obj.cFileName);
+
+          /* Make security attributes struct for logfile handle so it can
+             be inherited. */
+          if (!init_security_attributes_allow_all (&sa))
+            {
+              error = MsgToEventLog (M_SYSERR, 
TEXT("InitializeSecurityDescriptor start_" PACKAGE " failed"));
+              goto finish;
+            }
+
+          /* open logfile as stdout/stderr for soon-to-be-spawned subprocess */
+          log_handle = CreateFile (log_path,
+                                   GENERIC_WRITE,
+                                   FILE_SHARE_READ,
+                                   &sa.sa,
+                                   settings.append ? OPEN_ALWAYS : 
CREATE_ALWAYS,
+                                   FILE_ATTRIBUTE_NORMAL,
+                                   NULL);
+
+          if (log_handle == INVALID_HANDLE_VALUE)
+            {
+              error = MsgToEventLog (M_SYSERR, TEXT("Cannot open logfile: 
%s"), log_path);
+              FindClose (find_handle);
+              goto finish;
+            }
+
+          /* append to logfile? */
+          if (settings.append)
+            {
+              if (SetFilePointer (log_handle, 0, NULL, FILE_END) == 
INVALID_SET_FILE_POINTER)
+                {
+                  error = MsgToEventLog (M_SYSERR, TEXT("Cannot seek to end of 
logfile: %s"), log_path);
+                  FindClose (find_handle);
+                  goto finish;
+                }
+            }
+
+          /* fill in STARTUPINFO struct */
+          GetStartupInfo(&start_info);
+          start_info.cb = sizeof(start_info);
+          start_info.dwFlags = STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW;
+          start_info.wShowWindow = SW_HIDE;
+          start_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
+          start_info.hStdOutput = start_info.hStdError = log_handle;
+
+          /* create an OpenVPN process for one config file */
+          if (!CreateProcess(settings.exe_path,
+                             command_line,
+                             NULL,
+                             NULL,
+                             TRUE,
+                             settings.priority | CREATE_NEW_CONSOLE,
+                             NULL,
+                             settings.config_dir,
+                             &start_info,
+                             &proc_info))
+            {
+              error = MsgToEventLog (M_SYSERR, TEXT("CreateProcess failed, 
exe='%s' cmdline='%s' dir='%s'"),
+                   settings.exe_path,
+                   command_line,
+                   settings.config_dir);
+
+              FindClose (find_handle);
+              CloseHandle (log_handle);
+              goto finish;
+            }
+
+          /* close unneeded handles */
+          Sleep (1000); /* try to prevent race if we close logfile
+                           handle before child process DUPs it */
+          if (!CloseHandle (proc_info.hProcess)
+              || !CloseHandle (proc_info.hThread)
+              || !CloseHandle (log_handle))
+            {
+              error = MsgToEventLog (M_SYSERR, TEXT("CloseHandle failed"));
+              goto finish;
+            }
+        }
+
+      /* more files to process? */
+      more_files = FindNextFile (find_handle, &find_obj);
+
+    } while (more_files);
+
+    FindClose (find_handle);
+  }
+
+  /* we are now fully started */
+  status.dwCurrentState = SERVICE_RUNNING;
+  status.dwWaitHint = 0;
+  if (!ReportStatusToSCMgr(service, &status))
+    {
+      MsgToEventLog (M_ERR, TEXT("ReportStatusToSCMgr SERVICE_RUNNING 
failed"));
+      goto finish;
+    }
+
+  /* wait for our shutdown signal */
+  if (WaitForSingleObject (exit_event, INFINITE) != WAIT_OBJECT_0)
+    MsgToEventLog (M_ERR, TEXT("wait for shutdown signal failed"));
+
+finish:
+  if (exit_event)
+    CloseHandle (exit_event);
+
+  status.dwCurrentState = SERVICE_STOPPED;
+  status.dwWin32ExitCode = error;
+  ReportStatusToSCMgr (service, &status);
+}
+
diff --git a/src/openvpnserv/common.c b/src/openvpnserv/common.c
new file mode 100644
index 0000000..a293796
--- /dev/null
+++ b/src/openvpnserv/common.c
@@ -0,0 +1,211 @@
+/*
+ *  OpenVPN -- An application to securely tunnel IP networks
+ *             over a single TCP/UDP port, with support for SSL/TLS-based
+ *             session authentication and key exchange,
+ *             packet encryption, packet authentication, and
+ *             packet compression.
+ *
+ *  Copyright (C) 2011 Heiko Hund <heiko.h...@sophos.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2
+ *  as published by the Free Software Foundation.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program (see the file COPYING included with this
+ *  distribution); if not, write to the Free Software Foundation, Inc.,
+ *  59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <service.h>
+
+/*
+ * These are necessary due to certain buggy implementations of (v)snprintf,
+ * that don't guarantee null termination for size > 0.
+ */
+int
+openvpn_vsntprintf (LPTSTR str, size_t size, LPCTSTR format, va_list arglist)
+{
+  int len = -1;
+  if (size > 0)
+    {
+      len = _vsntprintf (str, size, format, arglist);
+      str[size - 1] = 0;
+    }
+  return (len >= 0 && len < size);
+}
+int
+openvpn_sntprintf (LPTSTR str, size_t size, LPCTSTR format, ...)
+{
+  va_list arglist;
+  int len = -1;
+  if (size > 0)
+    {
+      va_start (arglist, format);
+      len = openvpn_vsntprintf (str, size, format, arglist);
+      va_end (arglist);
+    }
+  return len;
+}
+
+
+#define REG_KEY  TEXT("SOFTWARE\\" PACKAGE_NAME)
+
+static DWORD
+GetRegString (HKEY key, LPCTSTR value, LPTSTR data, DWORD size)
+{
+  DWORD type;
+  LONG status = RegQueryValueEx (key, value, NULL, &type, (LPBYTE) data, 
&size);
+
+  if (status == ERROR_SUCCESS && type != REG_SZ)
+    status = ERROR_DATATYPE_MISMATCH;
+
+  if (status != ERROR_SUCCESS)
+    {
+      SetLastError (status);
+      return MsgToEventLog (M_SYSERR, TEXT("Error querying registry value: 
HKLM\\%s\\%s"), REG_KEY, value);
+    }
+
+  return ERROR_SUCCESS;
+}
+
+
+DWORD
+GetOpenvpnSettings (settings_t *s)
+{
+  TCHAR priority[64];
+  TCHAR append[2];
+  DWORD error;
+  HKEY key;
+
+  LONG status = RegOpenKeyEx (HKEY_LOCAL_MACHINE, REG_KEY, 0, KEY_READ, &key);
+  if (status != ERROR_SUCCESS)
+    {
+      SetLastError (status);
+      return MsgToEventLog (M_SYSERR, TEXT("Could not open Registry key 
HKLM\\%s not found"), REG_KEY);
+    }
+
+  error = GetRegString (key, TEXT("exe_path"), s->exe_path, sizeof 
(s->exe_path));
+  if (error != ERROR_SUCCESS)
+    goto out;
+
+  error = GetRegString (key, TEXT("config_dir"), s->config_dir, sizeof 
(s->config_dir));
+  if (error != ERROR_SUCCESS)
+    goto out;
+
+  error = GetRegString (key, TEXT("config_ext"), s->ext_string, sizeof 
(s->ext_string));
+  if (error != ERROR_SUCCESS)
+    goto out;
+
+  error = GetRegString (key, TEXT("log_dir"), s->log_dir, sizeof (s->log_dir));
+  if (error != ERROR_SUCCESS)
+    goto out;
+
+  error = GetRegString (key, TEXT("priority"), priority, sizeof (priority));
+  if (error != ERROR_SUCCESS)
+    goto out;
+
+  error = GetRegString (key, TEXT("log_append"), append, sizeof (append));
+  if (error != ERROR_SUCCESS)
+    goto out;
+
+  /* set process priority */
+  if (!_tcsicmp (priority, TEXT("IDLE_PRIORITY_CLASS")))
+    s->priority = IDLE_PRIORITY_CLASS;
+  else if (!_tcsicmp (priority, TEXT("BELOW_NORMAL_PRIORITY_CLASS")))
+    s->priority = BELOW_NORMAL_PRIORITY_CLASS;
+  else if (!_tcsicmp (priority, TEXT("NORMAL_PRIORITY_CLASS")))
+    s->priority = NORMAL_PRIORITY_CLASS;
+  else if (!_tcsicmp (priority, TEXT("ABOVE_NORMAL_PRIORITY_CLASS")))
+    s->priority = ABOVE_NORMAL_PRIORITY_CLASS;
+  else if (!_tcsicmp (priority, TEXT("HIGH_PRIORITY_CLASS")))
+    s->priority = HIGH_PRIORITY_CLASS;
+  else
+    {
+      SetLastError (ERROR_INVALID_DATA);
+      error = MsgToEventLog (M_SYSERR, TEXT("Unknown priority name: %s"), 
priority);
+      goto out;
+    }
+
+  /* set log file append/truncate flag */
+  if (append[0] == TEXT('0'))
+    s->append = FALSE;
+  else if (append[0] == TEXT('1'))
+    s->append = TRUE;
+  else
+    {
+      SetLastError (ERROR_INVALID_DATA);
+      error = MsgToEventLog (M_ERR, TEXT("Log file append flag (given as '%s') 
must be '0' or '1'"), append);
+      goto out;
+    }
+
+out:
+  RegCloseKey (key);
+  return error;
+}
+
+
+LPCTSTR
+GetLastErrorText ()
+{
+  static TCHAR buf[256];
+  DWORD len;
+  LPTSTR tmp = NULL;
+
+  len = FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER | 
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ARGUMENT_ARRAY,
+                       NULL, GetLastError(), LANG_NEUTRAL, (LPTSTR)&tmp, 0, 
NULL);
+
+  if (len == 0 || (long) _countof (buf) < (long) len + 14)
+    buf[0] = TEXT('\0');
+  else
+    {
+      tmp[_tcslen (tmp) - 2] = TEXT('\0');  /* remove CR/LF characters */
+      openvpn_sntprintf (buf, _countof (buf), TEXT("%s (0x%x)"), tmp, 
GetLastError());
+    }
+
+  if (tmp)
+    LocalFree (tmp);
+
+  return buf;
+}
+
+
+DWORD
+MsgToEventLog (DWORD flags, LPCTSTR format, ...)
+{
+  HANDLE hEventSource;
+  TCHAR msg[2][256];
+  DWORD error = 0;
+  LPCTSTR err_msg = TEXT("");
+  va_list arglist;
+
+  if (flags & MSG_FLAGS_SYS_CODE)
+    {
+      error = GetLastError ();
+      err_msg = GetLastErrorText ();
+    }
+
+  hEventSource = RegisterEventSource (NULL, APPNAME);
+  if (hEventSource != NULL)
+    {
+      openvpn_sntprintf (msg[0], _countof (msg[0]),
+                         TEXT("%s error: %s"), APPNAME, err_msg);
+
+      va_start (arglist, format);
+      openvpn_vsntprintf (msg[1], _countof (msg[1]), format, arglist);
+      va_end (arglist);
+
+      const TCHAR *mesg[] = { msg[0], msg[1] };
+      ReportEvent (hEventSource, flags & MSG_FLAGS_ERROR ?
+                   EVENTLOG_ERROR_TYPE : EVENTLOG_INFORMATION_TYPE,
+                   0, 0, NULL, 2, 0, mesg, NULL);
+      DeregisterEventSource (hEventSource);
+    }
+
+  return error;
+}
diff --git a/src/openvpnserv/interactive.c b/src/openvpnserv/interactive.c
new file mode 100644
index 0000000..0040d85
--- /dev/null
+++ b/src/openvpnserv/interactive.c
@@ -0,0 +1,1273 @@
+/*
+ *  OpenVPN -- An application to securely tunnel IP networks
+ *             over a single TCP/UDP port, with support for SSL/TLS-based
+ *             session authentication and key exchange,
+ *             packet encryption, packet authentication, and
+ *             packet compression.
+ *
+ *  Copyright (C) 2012 Heiko Hund <heiko.h...@sophos.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2
+ *  as published by the Free Software Foundation.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program (see the file COPYING included with this
+ *  distribution); if not, write to the Free Software Foundation, Inc.,
+ *  59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+
+#include "service.h"
+
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#include <iphlpapi.h>
+#include <userenv.h>
+#include <accctrl.h>
+#include <aclapi.h>
+#include <stdio.h>
+#include <sddl.h>
+
+#include "openvpn-msg.h"
+
+#define IO_TIMEOUT  2000 /*ms*/
+
+#define ERROR_OPENVPN_STARTUP  0x20000000
+#define ERROR_STARTUP_DATA     0x20000001
+#define ERROR_MESSAGE_DATA     0x20000002
+#define ERROR_MESSAGE_TYPE     0x20000003
+
+static SERVICE_STATUS_HANDLE service;
+static SERVICE_STATUS status;
+static HANDLE exit_event = NULL;
+static settings_t settings;
+
+openvpn_service_t interactive_service = {
+  interactive,
+  TEXT(PACKAGE_NAME "ServiceInteractive"),
+  TEXT(PACKAGE_NAME " Interactive Service"),
+  TEXT(SERVICE_DEPENDENCIES),
+  SERVICE_AUTO_START
+};
+
+
+typedef struct {
+  WCHAR *directory;
+  WCHAR *options;
+  WCHAR *std_input;
+} STARTUP_DATA;
+
+
+/* Datatype for linked lists */
+typedef struct _list_item {
+  struct _list_item *next;
+  LPVOID data;
+} list_item_t;
+
+
+/* Datatypes for undo information */
+typedef enum {
+  address,
+  route,
+  _undo_type_max
+} undo_type_t;
+typedef list_item_t* undo_lists_t[_undo_type_max];
+
+
+static DWORD
+AddListItem (list_item_t **pfirst, LPVOID data)
+{
+  list_item_t *new_item = malloc (sizeof (list_item_t));
+  if (new_item == NULL)
+    return ERROR_OUTOFMEMORY;
+
+  new_item->next = *pfirst;
+  new_item->data = data;
+
+  *pfirst = new_item;
+  return NO_ERROR;
+}
+
+typedef BOOL (*match_fn_t) (LPVOID item, LPVOID ctx);
+
+static LPVOID
+RemoveListItem (list_item_t **pfirst, match_fn_t match, LPVOID ctx)
+{
+  LPVOID data = NULL;
+  list_item_t **pnext;
+
+  for (pnext = pfirst; *pnext; pnext = &(*pnext)->next)
+    {
+      list_item_t *item = *pnext;
+      if (!match (item->data, ctx))
+        continue;
+
+      /* Found item, remove from the list and free memory */
+      *pnext = item->next;
+      data = item->data;
+      free (item);
+      break;
+    }
+  return data;
+}
+
+
+static HANDLE
+CloseHandleEx (LPHANDLE handle)
+{
+  if (handle && *handle && *handle != INVALID_HANDLE_VALUE)
+    {
+      CloseHandle (*handle);
+      *handle = INVALID_HANDLE_VALUE;
+    }
+  return INVALID_HANDLE_VALUE;
+}
+
+
+static HANDLE
+InitOverlapped (LPOVERLAPPED overlapped)
+{
+  ZeroMemory (overlapped, sizeof (OVERLAPPED));
+  overlapped->hEvent = CreateEvent (NULL, TRUE, FALSE, NULL);
+  return overlapped->hEvent;
+}
+
+
+static BOOL
+ResetOverlapped (LPOVERLAPPED overlapped)
+{
+  HANDLE io_event = overlapped->hEvent;
+  if (!ResetEvent (io_event))
+    return FALSE;
+  ZeroMemory (overlapped, sizeof (OVERLAPPED));
+  overlapped->hEvent = io_event;
+  return TRUE;
+}
+
+
+typedef enum {
+  peek,
+  read,
+  write
+} async_op_t;
+
+static DWORD
+AsyncPipeOp (async_op_t op, HANDLE pipe, LPVOID buffer, DWORD size, DWORD 
count, LPHANDLE events)
+{
+  int i;
+  BOOL success;
+  HANDLE io_event;
+  DWORD res, bytes = 0;
+  OVERLAPPED overlapped;
+  LPHANDLE handles = NULL;
+
+  io_event = InitOverlapped (&overlapped);
+  if (!io_event)
+    goto out;
+
+  handles = malloc ((count + 1) * sizeof (HANDLE));
+  if (!handles)
+    goto out;
+
+  if (op == write)
+    success = WriteFile (pipe, buffer, size, NULL, &overlapped);
+  else
+    success = ReadFile (pipe, buffer, size, NULL, &overlapped);
+  if (!success && GetLastError () != ERROR_IO_PENDING && GetLastError () != 
ERROR_MORE_DATA)
+    goto out;
+
+  handles[0] = io_event;
+  for (i = 0; i < count; i++)
+    handles[i + 1] = events[i];
+
+  res = WaitForMultipleObjects (count + 1, handles, FALSE,
+                                op == peek ? INFINITE : IO_TIMEOUT);
+  if (res != WAIT_OBJECT_0)
+    {
+      CancelIo (pipe);
+      goto out;
+    }
+
+  if (op == peek)
+    PeekNamedPipe (pipe, NULL, 0, NULL, &bytes, NULL);
+  else
+    GetOverlappedResult (pipe, &overlapped, &bytes, TRUE);
+
+out:
+  CloseHandleEx (&io_event);
+  free (handles);
+  return bytes;
+}
+
+static DWORD
+PeekNamedPipeAsync (HANDLE pipe, DWORD count, LPHANDLE events)
+{
+  return AsyncPipeOp (peek, pipe, NULL, 0, count, events);
+}
+
+static DWORD
+ReadPipeAsync (HANDLE pipe, LPVOID buffer, DWORD size, DWORD count, LPHANDLE 
events)
+{
+  return AsyncPipeOp (read, pipe, buffer, size, count, events);
+}
+
+static DWORD
+WritePipeAsync (HANDLE pipe, LPVOID data, DWORD size, DWORD count, LPHANDLE 
events)
+{
+  return AsyncPipeOp (write, pipe, data, size, count, events);
+}
+
+
+static VOID
+ReturnError (HANDLE pipe, DWORD error, LPCWSTR func, DWORD count, LPHANDLE 
events)
+{
+  DWORD result_len;
+  LPWSTR result = L"0xffffffff\nFormatMessage failed\nCould not return result";
+  DWORD_PTR args[] = {
+    (DWORD_PTR) error,
+    (DWORD_PTR) func,
+    (DWORD_PTR) ""
+  };
+
+  if (error != ERROR_OPENVPN_STARTUP)
+    {
+      FormatMessageW (FORMAT_MESSAGE_FROM_SYSTEM |
+                      FORMAT_MESSAGE_ALLOCATE_BUFFER |
+                      FORMAT_MESSAGE_IGNORE_INSERTS,
+                      0, error, 0, (LPWSTR) &args[2], 0, NULL);
+    }
+
+  result_len = FormatMessageW (FORMAT_MESSAGE_FROM_STRING |
+                               FORMAT_MESSAGE_ALLOCATE_BUFFER |
+                               FORMAT_MESSAGE_ARGUMENT_ARRAY,
+                               L"0x%1!08x!\n%2!s!\n%3!s!", 0, 0,
+                               (LPWSTR) &result, 0, (va_list*) args);
+
+  WritePipeAsync (pipe, result, wcslen (result) * 2, count, events);
+#ifdef UNICODE
+  MsgToEventLog (MSG_FLAGS_ERROR, result);
+#else
+  MsgToEventLog (MSG_FLAGS_ERROR, "%S", result);
+#endif
+
+  if (error != ERROR_OPENVPN_STARTUP)
+    LocalFree ((LPVOID) args[2]);
+  if (result_len)
+    LocalFree (result);
+}
+
+
+static VOID
+ReturnLastError (HANDLE pipe, LPCWSTR func)
+{
+  ReturnError (pipe, GetLastError (), func, 1, &exit_event);
+}
+
+
+static VOID
+ReturnOpenvpnOutput (HANDLE pipe, HANDLE ovpn_output, DWORD count, LPHANDLE 
events)
+{
+  WCHAR *wide_output = NULL;
+  CHAR output[512];
+  DWORD size;
+
+  ReadFile (ovpn_output, output, sizeof (output), &size, NULL);
+  if (size == 0)
+    return;
+
+  wide_output = malloc ((size) * sizeof (WCHAR));
+  if (wide_output)
+    {
+      MultiByteToWideChar (CP_UTF8, 0, output, size, wide_output, size);
+      wide_output[size - 1] = 0;
+    }
+
+  ReturnError (pipe, ERROR_OPENVPN_STARTUP, wide_output, count, events);
+  free (wide_output);
+}
+
+
+static BOOL
+GetStartupData (HANDLE pipe, STARTUP_DATA *sud)
+{
+  size_t len;
+  BOOL ret = FALSE;
+  WCHAR *data = NULL;
+  DWORD size, bytes, read;
+
+  bytes = PeekNamedPipeAsync (pipe, 1, &exit_event);
+  if (bytes == 0)
+    {
+      MsgToEventLog (M_SYSERR, TEXT("PeekNamedPipeAsync failed"));
+      ReturnLastError (pipe, L"PeekNamedPipeAsync");
+      goto out;
+    }
+
+  size = bytes / sizeof (*data);
+  data = malloc (bytes);
+  if (data == NULL)
+    {
+      MsgToEventLog (M_SYSERR, TEXT("malloc failed"));
+      ReturnLastError (pipe, L"malloc");
+      goto out;
+    }
+
+  read = ReadPipeAsync (pipe, data, bytes, 1, &exit_event);
+  if (bytes != read)
+  {
+      MsgToEventLog (M_SYSERR, TEXT("ReadPipeAsync failed"));
+      ReturnLastError (pipe, L"ReadPipeAsync");
+      goto out;
+  }
+
+  if (data[size - 1] != 0)
+    {
+      MsgToEventLog (M_ERR, TEXT("Startup data is not NULL terminated"));
+      ReturnError (pipe, ERROR_STARTUP_DATA, L"GetStartupData", 1, 
&exit_event);
+      goto out;
+    }
+
+  sud->directory = data;
+  len = wcslen (sud->directory) + 1;
+  size -= len;
+  if (size <= 0)
+    {
+      MsgToEventLog (M_ERR, TEXT("Startup data ends at working directory"));
+      ReturnError (pipe, ERROR_STARTUP_DATA, L"GetStartupData", 1, 
&exit_event);
+      goto out;
+    }
+
+  sud->options = sud->directory + len;
+  len = wcslen (sud->options) + 1;
+  size -= len;
+  if (size <= 0)
+    {
+      MsgToEventLog (M_ERR, TEXT("Startup data ends at command line options"));
+      ReturnError (pipe, ERROR_STARTUP_DATA, L"GetStartupData", 1, 
&exit_event);
+      goto out;
+    }
+
+  sud->std_input = sud->options + len;
+  data = NULL; /* don't free data */
+  ret = TRUE;
+
+out:
+  free (data);
+  return ret;
+}
+
+
+static VOID
+FreeStartupData (STARTUP_DATA *sud)
+{
+  free (sud->directory);
+}
+
+
+static SOCKADDR_INET
+sockaddr_inet (short family, inet_address_t *addr)
+{
+  SOCKADDR_INET sa_inet;
+  ZeroMemory (&sa_inet, sizeof (sa_inet));
+  sa_inet.si_family = family;
+  if (family == AF_INET)
+    sa_inet.Ipv4.sin_addr = addr->ipv4;
+  else if (family == AF_INET6)
+    sa_inet.Ipv6.sin6_addr = addr->ipv6;
+  return sa_inet;
+}
+
+static DWORD
+InterfaceLuid (const char *iface_name, PNET_LUID luid)
+{
+  NETIO_STATUS status;
+  LPWSTR wide_name;
+  int n;
+
+  typedef NETIO_STATUS WINAPI (*ConvertInterfaceAliasToLuidFn) (LPCWSTR, 
PNET_LUID);
+  static ConvertInterfaceAliasToLuidFn ConvertInterfaceAliasToLuid = NULL;
+  if (!ConvertInterfaceAliasToLuid)
+    {
+      HMODULE iphlpapi = GetModuleHandle (TEXT("iphlpapi.dll"));
+      if (iphlpapi == NULL)
+        return GetLastError ();
+
+      ConvertInterfaceAliasToLuid = (ConvertInterfaceAliasToLuidFn) 
GetProcAddress (iphlpapi, "ConvertInterfaceAliasToLuid");
+      if (!ConvertInterfaceAliasToLuid)
+        return GetLastError ();
+    }
+
+  n = MultiByteToWideChar (CP_UTF8, 0, iface_name, -1, NULL, 0);
+  wide_name = malloc (n * sizeof (WCHAR));
+  MultiByteToWideChar (CP_UTF8, 0, iface_name, -1, wide_name, n);
+  status = ConvertInterfaceAliasToLuid (wide_name, luid);
+  free (wide_name);
+
+  return status;
+}
+
+static BOOL
+CmpAddress (LPVOID item, LPVOID address)
+{
+  return memcmp (item, address, sizeof (MIB_UNICASTIPADDRESS_ROW)) == 0 ? TRUE 
: FALSE;
+}
+
+static DWORD
+DeleteAddress (PMIB_UNICASTIPADDRESS_ROW addr_row)
+{
+  typedef NETIOAPI_API (*DeleteUnicastIpAddressEntryFn) (const 
PMIB_UNICASTIPADDRESS_ROW);
+  static DeleteUnicastIpAddressEntryFn DeleteUnicastIpAddressEntry = NULL;
+
+  if (!DeleteUnicastIpAddressEntry)
+    {
+      HMODULE iphlpapi = GetModuleHandle (TEXT("iphlpapi.dll"));
+      if (iphlpapi == NULL)
+        return GetLastError ();
+
+      DeleteUnicastIpAddressEntry = (DeleteUnicastIpAddressEntryFn) 
GetProcAddress (iphlpapi, "DeleteUnicastIpAddressEntry");
+      if (!DeleteUnicastIpAddressEntry)
+        return GetLastError ();
+    }
+
+  return DeleteUnicastIpAddressEntry (addr_row);
+}
+
+static DWORD
+HandleAddressMessage (address_message_t *msg, undo_lists_t *lists)
+{
+  DWORD err;
+  PMIB_UNICASTIPADDRESS_ROW addr_row;
+  BOOL add = msg->header.type == msg_add_address;
+
+  typedef NETIOAPI_API (*CreateUnicastIpAddressEntryFn) (const 
PMIB_UNICASTIPADDRESS_ROW);
+  typedef NETIOAPI_API (*InitializeUnicastIpAddressEntryFn) 
(PMIB_UNICASTIPADDRESS_ROW);
+  static CreateUnicastIpAddressEntryFn CreateUnicastIpAddressEntry = NULL;
+  static InitializeUnicastIpAddressEntryFn InitializeUnicastIpAddressEntry = 
NULL;
+
+  if (!CreateUnicastIpAddressEntry || !InitializeUnicastIpAddressEntry)
+    {
+      HMODULE iphlpapi = GetModuleHandle (TEXT("iphlpapi.dll"));
+      if (iphlpapi == NULL)
+        return GetLastError ();
+
+      CreateUnicastIpAddressEntry = (CreateUnicastIpAddressEntryFn) 
GetProcAddress (iphlpapi, "CreateUnicastIpAddressEntry");
+      if (!CreateUnicastIpAddressEntry)
+        return GetLastError ();
+
+      InitializeUnicastIpAddressEntry = (InitializeUnicastIpAddressEntryFn) 
GetProcAddress (iphlpapi, "InitializeUnicastIpAddressEntry");
+      if (!InitializeUnicastIpAddressEntry)
+        return GetLastError ();
+    }
+
+  addr_row = malloc (sizeof (*addr_row));
+  if (addr_row == NULL)
+    return ERROR_OUTOFMEMORY;
+
+  InitializeUnicastIpAddressEntry (addr_row);
+  addr_row->Address = sockaddr_inet (msg->family, &msg->address);
+  addr_row->OnLinkPrefixLength = (UINT8) msg->prefix_len;
+
+  if (msg->iface.index != -1)
+    {
+      addr_row->InterfaceIndex = msg->iface.index;
+    }
+  else
+    {
+      NET_LUID luid;
+      err = InterfaceLuid (msg->iface.name, &luid);
+      if (err)
+        goto out;
+      addr_row->InterfaceLuid = luid;
+    }
+
+  if (add)
+    {
+      err = CreateUnicastIpAddressEntry (addr_row);
+      if (err)
+        goto out;
+
+      err = AddListItem (&(*lists)[address], addr_row);
+      if (err)
+        DeleteAddress (addr_row);
+    }
+  else
+    {
+      err = DeleteAddress (addr_row);
+      if (err)
+        goto out;
+
+      free (RemoveListItem (&(*lists)[address], CmpAddress, addr_row));
+    }
+
+out:
+  if (!add || err)
+    free (addr_row);
+
+  return err;
+}
+
+
+static BOOL
+CmpRoute (LPVOID item, LPVOID route)
+{
+  return memcmp (item, route, sizeof (MIB_IPFORWARD_ROW2)) == 0 ? TRUE : FALSE;
+}
+
+static DWORD
+DeleteRoute (PMIB_IPFORWARD_ROW2 fwd_row)
+{
+  typedef NETIOAPI_API (*DeleteIpForwardEntry2Fn) (PMIB_IPFORWARD_ROW2);
+  static DeleteIpForwardEntry2Fn DeleteIpForwardEntry2 = NULL;
+
+  if (!DeleteIpForwardEntry2)
+    {
+      HMODULE iphlpapi = GetModuleHandle (TEXT("iphlpapi.dll"));
+      if (iphlpapi == NULL)
+        return GetLastError ();
+
+      DeleteIpForwardEntry2 = (DeleteIpForwardEntry2Fn) GetProcAddress 
(iphlpapi, "DeleteIpForwardEntry2");
+      if (!DeleteIpForwardEntry2)
+        return GetLastError ();
+    }
+
+  return DeleteIpForwardEntry2 (fwd_row);
+}
+
+static DWORD
+HandleRouteMessage (route_message_t *msg, undo_lists_t *lists)
+{
+  DWORD err;
+  PMIB_IPFORWARD_ROW2 fwd_row;
+  BOOL add = msg->header.type == msg_add_route;
+
+  typedef NETIOAPI_API (*CreateIpForwardEntry2Fn) (PMIB_IPFORWARD_ROW2);
+  static CreateIpForwardEntry2Fn CreateIpForwardEntry2 = NULL;
+
+  if (!CreateIpForwardEntry2)
+    {
+      HMODULE iphlpapi = GetModuleHandle (TEXT("iphlpapi.dll"));
+      if (iphlpapi == NULL)
+        return GetLastError ();
+
+      CreateIpForwardEntry2 = (CreateIpForwardEntry2Fn) GetProcAddress 
(iphlpapi, "CreateIpForwardEntry2");
+      if (!CreateIpForwardEntry2)
+        return GetLastError ();
+    }
+
+  fwd_row = malloc (sizeof (*fwd_row));
+  if (fwd_row == NULL)
+    return ERROR_OUTOFMEMORY;
+
+  ZeroMemory (fwd_row, sizeof (*fwd_row));
+  fwd_row->ValidLifetime = 0xffffffff;
+  fwd_row->PreferredLifetime = 0xffffffff;
+  fwd_row->Protocol = MIB_IPPROTO_NETMGMT;
+  fwd_row->Metric = msg->metric;
+  fwd_row->DestinationPrefix.Prefix = sockaddr_inet (msg->family, 
&msg->prefix);
+  fwd_row->DestinationPrefix.PrefixLength = (UINT8) msg->prefix_len;
+  fwd_row->NextHop = sockaddr_inet (msg->family, &msg->gateway);
+
+  if (msg->iface.index != -1)
+    {
+      fwd_row->InterfaceIndex = msg->iface.index;
+    }
+  else if (strlen (msg->iface.name))
+    {
+      NET_LUID luid;
+      err = InterfaceLuid (msg->iface.name, &luid);
+      if (err)
+        goto out;
+      fwd_row->InterfaceLuid = luid;
+    }
+
+  if (add)
+    {
+      err = CreateIpForwardEntry2 (fwd_row);
+      if (err)
+        goto out;
+
+      err = AddListItem (&(*lists)[route], fwd_row);
+      if (err)
+        DeleteRoute (fwd_row);
+    }
+  else
+    {
+      err = DeleteRoute (fwd_row);
+      if (err)
+        goto out;
+
+      free (RemoveListItem (&(*lists)[route], CmpRoute, fwd_row));
+    }
+
+out:
+  if (!add || err)
+    free (fwd_row);
+
+  return err;
+}
+
+
+static DWORD
+HandleFlushNeighborsMessage (flush_neighbors_message_t *msg)
+{
+  typedef NETIOAPI_API (*FlushIpNetTable2Fn) (ADDRESS_FAMILY, NET_IFINDEX);
+  static FlushIpNetTable2Fn flush_fn = NULL;
+
+  if (msg->family == AF_INET)
+    return FlushIpNetTable (msg->iface.index);
+
+  if (!flush_fn)
+    {
+      HMODULE iphlpapi = GetModuleHandle (TEXT("iphlpapi.dll"));
+      if (iphlpapi == NULL)
+        return GetLastError ();
+
+      flush_fn = (FlushIpNetTable2Fn) GetProcAddress (iphlpapi, 
"FlushIpNetTable2");
+      if (!flush_fn)
+        {
+          if (GetLastError () == ERROR_PROC_NOT_FOUND)
+            return WSAEPFNOSUPPORT;
+          else
+            return GetLastError ();
+        }
+    }
+  return flush_fn (msg->family, msg->iface.index);
+}
+
+
+static VOID
+HandleMessage (HANDLE pipe, DWORD bytes, DWORD count, LPHANDLE events, 
undo_lists_t *lists)
+{
+  DWORD read;
+  union {
+    message_header_t header;
+    address_message_t address;
+    route_message_t route;
+    flush_neighbors_message_t flush_neighbors;
+  } msg;
+  ack_message_t ack = {
+    .header = {
+      .type = msg_acknowledgement,
+      .size = sizeof (ack),
+      .message_id = -1
+    },
+    .error_number = ERROR_MESSAGE_DATA
+  };
+
+  read = ReadPipeAsync (pipe, &msg, bytes, count, events);
+  if (read != bytes || read < sizeof (msg.header) || read != msg.header.size)
+    goto out;
+
+  ack.header.message_id = msg.header.message_id;
+
+  switch (msg.header.type)
+    {
+    case msg_add_address:
+    case msg_del_address:
+      if (msg.header.size == sizeof (msg.address))
+        ack.error_number = HandleAddressMessage (&msg.address, lists);
+      break;
+
+    case msg_add_route:
+    case msg_del_route:
+      if (msg.header.size == sizeof (msg.route))
+        ack.error_number = HandleRouteMessage (&msg.route, lists);
+      break;
+
+    case msg_flush_neighbors:
+      if (msg.header.size == sizeof (msg.flush_neighbors))
+        ack.error_number = HandleFlushNeighborsMessage (&msg.flush_neighbors);
+      break;
+
+    default:
+      ack.error_number = ERROR_MESSAGE_TYPE;
+      break;
+    }
+
+out:
+  WritePipeAsync (pipe, &ack, sizeof (ack), count, events);
+}
+
+
+static VOID
+Undo (undo_lists_t *lists)
+{
+  undo_type_t type;
+  for (type = 0; type < _undo_type_max; type++)
+    {
+      list_item_t **pnext = &(*lists)[type];
+      while (*pnext)
+        {
+          list_item_t *item = *pnext;
+          switch (type)
+            {
+            case address:
+              DeleteAddress (item->data);
+              break;
+
+            case route:
+              DeleteRoute (item->data);
+              break;
+            }
+
+          /* Remove from the list and free memory */
+          *pnext = item->next;
+          free (item->data);
+          free (item);
+        }
+    }
+}
+
+static DWORD WINAPI
+RunOpenvpn (LPVOID p)
+{
+  HANDLE pipe = p;
+  HANDLE ovpn_pipe, svc_pipe;
+  PTOKEN_USER svc_user, ovpn_user;
+  HANDLE svc_token = NULL, imp_token = NULL, pri_token = NULL;
+  HANDLE stdin_read = NULL, stdin_write = NULL;
+  HANDLE stdout_read = NULL, stdout_write = NULL;
+  DWORD pipe_mode, len, exit_code = 0;
+  STARTUP_DATA sud = { 0, 0, 0 };
+  STARTUPINFOW startup_info;
+  PROCESS_INFORMATION proc_info;
+  LPVOID user_env = NULL;
+  TCHAR ovpn_pipe_name[36];
+  LPCWSTR exe_path;
+  WCHAR *cmdline = NULL;
+  size_t cmdline_size;
+  undo_lists_t undo_lists;
+
+  SECURITY_ATTRIBUTES inheritable = {
+    .nLength = sizeof (inheritable),
+    .lpSecurityDescriptor = NULL,
+    .bInheritHandle = TRUE
+  };
+
+  PACL ovpn_dacl;
+  EXPLICIT_ACCESS ea[2];
+  SECURITY_DESCRIPTOR ovpn_sd;
+  SECURITY_ATTRIBUTES ovpn_sa = {
+    .nLength = sizeof (ovpn_sa),
+    .lpSecurityDescriptor = &ovpn_sd,
+    .bInheritHandle = FALSE
+  };
+
+  ZeroMemory (&ea, sizeof (ea));
+  ZeroMemory (&startup_info, sizeof (startup_info));
+  ZeroMemory (&undo_lists, sizeof (undo_lists));
+  ZeroMemory (&proc_info, sizeof (proc_info));
+
+  if (!GetStartupData (pipe, &sud))
+    goto out;
+
+  if (!InitializeSecurityDescriptor (&ovpn_sd, SECURITY_DESCRIPTOR_REVISION))
+    {
+      ReturnLastError (pipe, L"InitializeSecurityDescriptor");
+      goto out;
+    }
+
+  /* Get SID of user the service is running under */
+  if (!OpenProcessToken (GetCurrentProcess (), TOKEN_QUERY, &svc_token))
+    {
+      ReturnLastError (pipe, L"OpenProcessToken");
+      goto out;
+    }
+  len = 0;
+  svc_user = NULL;
+  while (!GetTokenInformation (svc_token, TokenUser, svc_user, len, &len))
+    {
+      if (GetLastError () != ERROR_INSUFFICIENT_BUFFER)
+        {
+          ReturnLastError (pipe, L"GetTokenInformation (service token)");
+          goto out;
+        }
+      free (svc_user);
+      svc_user = malloc (len);
+      if (svc_user == NULL)
+        {
+          ReturnLastError (pipe, L"malloc (service token user)");
+          goto out;
+        }
+    }
+  if (!IsValidSid (svc_user->User.Sid))
+    {
+      ReturnLastError (pipe, L"IsValidSid (service token user)");
+      goto out;
+    }
+
+  if (!ImpersonateNamedPipeClient (pipe))
+    {
+      ReturnLastError (pipe, L"ImpersonateNamedPipeClient");
+      goto out;
+    }
+  if (!OpenThreadToken (GetCurrentThread (), TOKEN_ALL_ACCESS, FALSE, 
&imp_token))
+    {
+      ReturnLastError (pipe, L"OpenThreadToken");
+      goto out;
+    }
+  len = 0;
+  ovpn_user = NULL;
+  while (!GetTokenInformation (imp_token, TokenUser, ovpn_user, len, &len))
+    {
+      if (GetLastError () != ERROR_INSUFFICIENT_BUFFER)
+        {
+          ReturnLastError (pipe, L"GetTokenInformation (impersonation token)");
+          goto out;
+        }
+      free (ovpn_user);
+      ovpn_user = malloc (len);
+      if (ovpn_user == NULL)
+        {
+          ReturnLastError (pipe, L"malloc (impersonation token user)");
+          goto out;
+        }
+    }
+  if (!IsValidSid (ovpn_user->User.Sid))
+    {
+      ReturnLastError (pipe, L"IsValidSid (impersonation token user)");
+      goto out;
+    }
+
+  /* OpenVPN process DACL entry for access by service and user */
+  ea[0].grfAccessPermissions = SPECIFIC_RIGHTS_ALL | STANDARD_RIGHTS_ALL;
+  ea[0].grfAccessMode = SET_ACCESS;
+  ea[0].grfInheritance = NO_INHERITANCE;
+  ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
+  ea[0].Trustee.TrusteeType = TRUSTEE_IS_UNKNOWN;
+  ea[0].Trustee.ptstrName = (LPTSTR) svc_user->User.Sid;
+  ea[1].grfAccessPermissions = READ_CONTROL | SYNCHRONIZE | PROCESS_VM_READ |
+                    SYNCHRONIZE | PROCESS_TERMINATE | 
PROCESS_QUERY_INFORMATION;
+  ea[1].grfAccessMode = SET_ACCESS;
+  ea[1].grfInheritance = NO_INHERITANCE;
+  ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID;
+  ea[1].Trustee.TrusteeType = TRUSTEE_IS_UNKNOWN;
+  ea[1].Trustee.ptstrName = (LPTSTR) ovpn_user->User.Sid;
+
+  /* Set owner and DACL of OpenVPN security descriptor */
+  if (!SetSecurityDescriptorOwner (&ovpn_sd, svc_user->User.Sid, FALSE))
+    {
+      ReturnLastError (pipe, L"SetSecurityDescriptorOwner");
+      goto out;
+    }
+  if (SetEntriesInAcl (2, ea, NULL, &ovpn_dacl) != ERROR_SUCCESS)
+    {
+      ReturnLastError (pipe, L"SetEntriesInAcl");
+      goto out;
+    }
+  if (!SetSecurityDescriptorDacl (&ovpn_sd, TRUE, ovpn_dacl, FALSE))
+    {
+      ReturnLastError (pipe, L"SetSecurityDescriptorDacl");
+      goto out;
+    }
+
+  /* Create primary token from impersonation token */
+  if (!DuplicateTokenEx (imp_token, TOKEN_ALL_ACCESS, NULL, 0, TokenPrimary, 
&pri_token))
+    {
+      ReturnLastError (pipe, L"DuplicateTokenEx");
+      goto out;
+    }
+
+  if (!CreatePipe(&stdin_read, &stdin_write, &inheritable, 0) ||
+      !CreatePipe(&stdout_read, &stdout_write, &inheritable, 0) ||
+      !SetHandleInformation(stdin_write, HANDLE_FLAG_INHERIT, 0) ||
+      !SetHandleInformation(stdout_read, HANDLE_FLAG_INHERIT, 0))
+    {
+      ReturnLastError (pipe, L"CreatePipe");
+      goto out;
+    }
+
+  openvpn_sntprintf (ovpn_pipe_name, _countof (ovpn_pipe_name),
+    TEXT("\\\\.\\pipe\\openvpn\\service_%lu"), GetCurrentThreadId ());
+  ovpn_pipe = CreateNamedPipe (ovpn_pipe_name,
+    PIPE_ACCESS_DUPLEX | FILE_FLAG_FIRST_PIPE_INSTANCE | FILE_FLAG_OVERLAPPED,
+    PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, 1, 128, 128, 0, 
NULL);
+  if (ovpn_pipe == INVALID_HANDLE_VALUE)
+    {
+      ReturnLastError (pipe, L"CreateNamedPipe");
+      goto out;
+    }
+
+  svc_pipe = CreateFile (ovpn_pipe_name, GENERIC_READ | GENERIC_WRITE, 0,
+                         &inheritable, OPEN_EXISTING, 0, NULL);
+  if (svc_pipe == INVALID_HANDLE_VALUE)
+    {
+      ReturnLastError (pipe, L"CreateFile");
+      goto out;
+    }
+
+  pipe_mode = PIPE_READMODE_MESSAGE;
+  if (!SetNamedPipeHandleState (svc_pipe, &pipe_mode, NULL, NULL))
+    {
+      ReturnLastError (pipe, L"SetNamedPipeHandleState");
+      goto out;
+    }
+
+  cmdline_size = wcslen (sud.options) + 128;
+  cmdline = malloc (cmdline_size * sizeof (*cmdline));
+  if (cmdline == NULL)
+    {
+      ReturnLastError (pipe, L"malloc");
+      goto out;
+    }
+  openvpn_sntprintf (cmdline, cmdline_size, L"openvpn %s --msg-channel %lu",
+                     sud.options, svc_pipe);
+
+  if (!CreateEnvironmentBlock (&user_env, imp_token, FALSE))
+    {
+      ReturnLastError (pipe, L"CreateEnvironmentBlock");
+      goto out;
+    }
+
+  startup_info.cb = sizeof (startup_info);
+  startup_info.lpDesktop = L"winsta0\\default";
+  startup_info.dwFlags = STARTF_USESTDHANDLES;
+  startup_info.hStdInput = stdin_read;
+  startup_info.hStdOutput = stdout_write;
+  startup_info.hStdError = stdout_write;
+
+#ifdef UNICODE
+  exe_path = settings.exe_path;
+#else
+  WCHAR wide_path[MAX_PATH];
+  MultiByteToWideChar (CP_UTF8, 0, settings.exe_path, MAX_PATH, wide_path, 
MAX_PATH);
+  exe_path = wide_path;
+#endif
+
+  // TODO: make sure HKCU is correct or call LoadUserProfile()
+  if (!CreateProcessAsUserW (pri_token, exe_path, cmdline, &ovpn_sa, NULL, 
TRUE,
+                             settings.priority | CREATE_NO_WINDOW | 
CREATE_UNICODE_ENVIRONMENT,
+                             user_env, sud.directory, &startup_info, 
&proc_info))
+    {
+      ReturnLastError (pipe, L"CreateProcessAsUser");
+      goto out;
+    }
+
+  if (!RevertToSelf ())
+    {
+      TerminateProcess (proc_info.hProcess, 1);
+      ReturnLastError (pipe, L"RevertToSelf");
+      goto out;
+    }
+
+  CloseHandleEx (&stdout_write);
+  CloseHandleEx (&stdin_read);
+  CloseHandleEx (&svc_pipe);
+
+  DWORD input_size = wcslen (sud.std_input) * 2;
+  if (input_size)
+    {
+      DWORD written;
+      LPSTR input = malloc (input_size);
+      WideCharToMultiByte (CP_UTF8, 0, sud.std_input, -1, input, input_size, 
NULL, NULL);
+      WriteFile (stdin_write, input, strlen (input), &written, NULL);
+      free (input);
+    }
+
+
+  while (TRUE)
+    {
+      DWORD bytes = PeekNamedPipeAsync (ovpn_pipe, 1, &exit_event);
+      if (bytes == 0)
+        break;
+
+      HandleMessage (ovpn_pipe, bytes, 1, &exit_event, &undo_lists);
+    }
+
+  WaitForSingleObject (proc_info.hProcess, IO_TIMEOUT);
+  GetExitCodeProcess (proc_info.hProcess, &exit_code);
+  if (exit_code == STILL_ACTIVE)
+    TerminateProcess (proc_info.hProcess, 1);
+  else if (exit_code != 0)
+    ReturnOpenvpnOutput (pipe, stdout_read, 1, &exit_event);
+
+  Undo (&undo_lists);
+
+out:
+  FlushFileBuffers (pipe);
+  DisconnectNamedPipe (pipe);
+
+  free (ovpn_user);
+  free (svc_user);
+  free (cmdline);
+  DestroyEnvironmentBlock (user_env);
+  FreeStartupData (&sud);
+  CloseHandleEx (&proc_info.hProcess);
+  CloseHandleEx (&proc_info.hThread);
+  CloseHandleEx (&stdin_read);
+  CloseHandleEx (&stdin_write);
+  CloseHandleEx (&stdout_read);
+  CloseHandleEx (&stdout_write);
+  CloseHandleEx (&svc_token);
+  CloseHandleEx (&imp_token);
+  CloseHandleEx (&pri_token);
+  CloseHandleEx (&ovpn_pipe);
+  CloseHandleEx (&svc_pipe);
+  CloseHandleEx (&pipe);
+
+  return 0;
+}
+
+
+static DWORD WINAPI
+ServiceCtrlInteractive (DWORD ctrl_code, DWORD event, LPVOID data, LPVOID ctx)
+{
+  SERVICE_STATUS *status = ctx;
+  switch (ctrl_code)
+    {
+    case SERVICE_CONTROL_STOP:
+      status->dwCurrentState = SERVICE_STOP_PENDING;
+      ReportStatusToSCMgr (service, status);
+      if (exit_event)
+        SetEvent (exit_event);
+      return NO_ERROR;
+
+    case SERVICE_CONTROL_INTERROGATE:
+      return NO_ERROR;
+
+    default:
+      return ERROR_CALL_NOT_IMPLEMENTED;
+    }
+}
+
+
+static HANDLE
+CreateClientPipeInstance (VOID)
+{
+  HANDLE pipe = NULL;
+  PACL old_dacl, new_dacl;
+  PSECURITY_DESCRIPTOR sd;
+  static EXPLICIT_ACCESS ea[2];
+  static BOOL initialized = FALSE;
+  DWORD flags = PIPE_ACCESS_DUPLEX | WRITE_DAC | FILE_FLAG_OVERLAPPED;
+
+  if (!initialized)
+    {
+      PSID everyone, anonymous;
+
+      ConvertStringSidToSid (TEXT("S-1-1-0"), &everyone);
+      ConvertStringSidToSid (TEXT("S-1-5-7"), &anonymous);
+
+      ea[0].grfAccessPermissions = FILE_GENERIC_WRITE;
+      ea[0].grfAccessMode = GRANT_ACCESS;
+      ea[0].grfInheritance = NO_INHERITANCE;
+      ea[0].Trustee.pMultipleTrustee = NULL;
+      ea[0].Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE;
+      ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
+      ea[0].Trustee.TrusteeType = TRUSTEE_IS_UNKNOWN;
+      ea[0].Trustee.ptstrName = (LPTSTR) everyone;
+
+      ea[1].grfAccessPermissions = 0;
+      ea[1].grfAccessMode = REVOKE_ACCESS;
+      ea[1].grfInheritance = NO_INHERITANCE;
+      ea[1].Trustee.pMultipleTrustee = NULL;
+      ea[1].Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE;
+      ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID;
+      ea[1].Trustee.TrusteeType = TRUSTEE_IS_UNKNOWN;
+      ea[1].Trustee.ptstrName = (LPTSTR) anonymous;
+
+      flags |= FILE_FLAG_FIRST_PIPE_INSTANCE;
+      initialized = TRUE;
+    }
+
+  pipe = CreateNamedPipe (TEXT("\\\\.\\pipe\\openvpn\\service"), flags,
+                PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE,
+                PIPE_UNLIMITED_INSTANCES, 1024, 1024, 0, NULL);
+  if (pipe == INVALID_HANDLE_VALUE)
+    {
+      MsgToEventLog (M_SYSERR, TEXT("Could not create named pipe"));
+      return INVALID_HANDLE_VALUE;
+    }
+
+  if (GetSecurityInfo (pipe, SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION,
+                        NULL, NULL, &old_dacl, NULL, &sd) != ERROR_SUCCESS)
+    {
+      MsgToEventLog (M_SYSERR, TEXT("Could not get pipe security info"));
+      return CloseHandleEx (&pipe);
+    }
+
+  if (SetEntriesInAcl (2, ea, old_dacl, &new_dacl) != ERROR_SUCCESS)
+    {
+      MsgToEventLog (M_SYSERR, TEXT("Could not set entries in new acl"));
+      return CloseHandleEx (&pipe);
+    }
+
+  if (SetSecurityInfo (pipe, SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION,
+                        NULL, NULL, new_dacl, NULL) != ERROR_SUCCESS)
+    {
+      MsgToEventLog (M_SYSERR, TEXT("Could not set pipe security info"));
+      return CloseHandleEx (&pipe);
+    }
+
+  return pipe;
+}
+
+
+static DWORD
+UpdateWaitHandles (LPHANDLE *handles_ptr, LPDWORD count,
+                   HANDLE io_event, HANDLE exit_event, list_item_t *threads)
+{
+  static DWORD size = 10;
+  static LPHANDLE handles = NULL;
+  DWORD pos = 0;
+
+  if (handles == NULL)
+    {
+      handles = malloc (size * sizeof (HANDLE));
+      if (handles == NULL)
+        return ERROR_OUTOFMEMORY;
+    }
+
+  handles[pos++] = io_event;
+
+  if (!threads)
+    handles[pos++] = exit_event;
+
+  while (threads)
+    {
+      if (pos == size)
+        {
+          size += 10;
+          handles = realloc (handles, size * sizeof (HANDLE));
+          if (handles == NULL)
+            return ERROR_OUTOFMEMORY;
+        }
+      handles[pos++] = threads->data;
+      threads = threads->next;
+    }
+
+  *handles_ptr = handles;
+  *count = pos;
+  return NO_ERROR;
+}
+
+
+static VOID
+FreeWaitHandles (LPHANDLE h)
+{
+  free (h);
+}
+
+
+VOID WINAPI
+ServiceStartInteractive (DWORD dwArgc, LPTSTR *lpszArgv)
+{
+  HANDLE pipe, io_event = NULL;
+  OVERLAPPED overlapped;
+  DWORD error = NO_ERROR;
+  list_item_t *threads = NULL;
+  PHANDLE handles;
+  DWORD handle_count;
+
+  service = RegisterServiceCtrlHandlerEx (interactive_service.name, 
ServiceCtrlInteractive, &status);
+  if (!service)
+    return;
+
+  status.dwServiceType = SERVICE_WIN32_SHARE_PROCESS;
+  status.dwCurrentState = SERVICE_START_PENDING;
+  status.dwServiceSpecificExitCode = NO_ERROR;
+  status.dwWin32ExitCode = NO_ERROR;
+  status.dwWaitHint = 3000;
+  ReportStatusToSCMgr (service, &status);
+
+  /* Read info from registry in key HKLM\SOFTWARE\OpenVPN */
+  error = GetOpenvpnSettings (&settings);
+  if (error != ERROR_SUCCESS)
+    goto out;
+
+  io_event = InitOverlapped (&overlapped);
+  exit_event = CreateEvent (NULL, FALSE, FALSE, NULL);
+  if (!exit_event || !io_event)
+    {
+      error = MsgToEventLog (M_SYSERR, TEXT("Could not create event"));
+      goto out;
+    }
+
+  error = UpdateWaitHandles (&handles, &handle_count, io_event, exit_event, 
threads);
+  if (error != NO_ERROR)
+    goto out;
+
+  pipe = CreateClientPipeInstance ();
+  if (pipe == INVALID_HANDLE_VALUE)
+    goto out;
+
+  status.dwCurrentState = SERVICE_RUNNING;
+  status.dwWaitHint = 0;
+  ReportStatusToSCMgr (service, &status);
+
+  while (TRUE)
+    {
+      if (ConnectNamedPipe (pipe, &overlapped) == FALSE &&
+          GetLastError () != ERROR_PIPE_CONNECTED &&
+          GetLastError () != ERROR_IO_PENDING)
+        {
+          MsgToEventLog (M_SYSERR, TEXT("Could not connect pipe"));
+          break;
+        }
+
+      error = WaitForMultipleObjects (handle_count, handles, FALSE, INFINITE);
+      if (error == WAIT_OBJECT_0)
+        {
+          /* Client connected, spawn a worker thread for it */
+          HANDLE next_pipe = CreateClientPipeInstance ();
+          HANDLE thread = CreateThread (NULL, 0, RunOpenvpn, pipe, 
CREATE_SUSPENDED, NULL);
+          if (thread)
+            {
+              error = AddListItem (&threads, thread) ||
+                UpdateWaitHandles (&handles, &handle_count, io_event, 
exit_event, threads);
+              if (error)
+                {
+                  TerminateThread (thread, 1);
+                  CloseHandleEx (&thread);
+                  CloseHandleEx (&pipe);
+                  SetEvent (exit_event);
+                }
+              else
+                ResumeThread (thread);
+            }
+          else
+            CloseHandleEx (&pipe);
+
+          ResetOverlapped (&overlapped);
+          pipe = next_pipe;
+        }
+      else
+        {
+          CancelIo (pipe);
+          if (error == WAIT_FAILED)
+            {
+              MsgToEventLog (M_SYSERR, TEXT("WaitForMultipleObjects failed"));
+              continue;
+            }
+          if (!threads)
+            {
+              /* exit event signaled */
+              CloseHandleEx (&pipe);
+              error = NO_ERROR;
+              break;
+            }
+
+          /* Worker thread ended */
+          BOOL CmpHandle (LPVOID item, LPVOID hnd) { return item == hnd; }
+          HANDLE thread = RemoveListItem (&threads, CmpHandle, handles[error]);
+          UpdateWaitHandles (&handles, &handle_count, io_event, exit_event, 
threads);
+          CloseHandleEx (&thread);
+        }
+    }
+
+out:
+  FreeWaitHandles (handles);
+  CloseHandleEx (&io_event);
+  CloseHandleEx (&exit_event);
+
+  status.dwCurrentState = SERVICE_STOPPED;
+  status.dwWin32ExitCode = error;
+  ReportStatusToSCMgr (service, &status);
+}
+
diff --git a/src/openvpnserv/openvpnserv.c b/src/openvpnserv/openvpnserv.c
deleted file mode 100755
index 56f5a02..0000000
--- a/src/openvpnserv/openvpnserv.c
+++ /dev/null
@@ -1,534 +0,0 @@
-/*
- *  OpenVPN -- An application to securely tunnel IP networks
- *             over a single TCP/UDP port, with support for SSL/TLS-based
- *             session authentication and key exchange,
- *             packet encryption, packet authentication, and
- *             packet compression.
- *
- *  Copyright (C) 2002-2010 OpenVPN Technologies, Inc. <sa...@openvpn.net>
- *
- *  This program is free software; you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License version 2
- *  as published by the Free Software Foundation.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program (see the file COPYING included with this
- *  distribution); if not, write to the Free Software Foundation, Inc.,
- *  59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- */
-
-/*
- * This program allows one or more OpenVPN processes to be started
- * as a service.  To build, you must get the service sample from the
- * Platform SDK and replace Simple.c with this file.
- *
- * You should also apply service.patch to
- * service.c and service.h from the Platform SDK service sample.
- *
- * This code is designed to be built with the mingw compiler.
- */
-
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#elif defined(_MSC_VER)
-#include "config-msvc.h"
-#endif
-#include <windows.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <stdarg.h>
-#include <process.h>
-#include "service.h"
-
-/* bool definitions */
-#define bool int
-#define true 1
-#define false 0
-
-/* These are new for 2000/XP, so they aren't in the mingw headers yet */
-#ifndef BELOW_NORMAL_PRIORITY_CLASS
-#define BELOW_NORMAL_PRIORITY_CLASS 0x00004000
-#endif
-#ifndef ABOVE_NORMAL_PRIORITY_CLASS
-#define ABOVE_NORMAL_PRIORITY_CLASS 0x00008000
-#endif
-
-struct security_attributes
-{
-  SECURITY_ATTRIBUTES sa;
-  SECURITY_DESCRIPTOR sd;
-};
-
-/*
- * This event is initially created in the non-signaled
- * state.  It will transition to the signaled state when
- * we have received a terminate signal from the Service
- * Control Manager which will cause an asynchronous call
- * of ServiceStop below.
- */
-#define EXIT_EVENT_NAME PACKAGE "_exit_1"
-
-/*
- * Which registry key in HKLM should
- * we get config info from?
- */
-#define REG_KEY "SOFTWARE\\" PACKAGE_NAME
-
-static HANDLE exit_event = NULL;
-
-/* clear an object */
-#define CLEAR(x) memset(&(x), 0, sizeof(x))
-
-/*
- * Message handling
- */
-#define M_INFO    (0)                                  /* informational */
-#define M_SYSERR  (MSG_FLAGS_ERROR|MSG_FLAGS_SYS_CODE) /* error + system code 
*/
-#define M_ERR     (MSG_FLAGS_ERROR)                    /* error */
-
-/* write error to event log */
-#define MSG(flags, ...) \
-        { \
-           char x_msg[256]; \
-           openvpn_snprintf (x_msg, sizeof(x_msg), __VA_ARGS__);      \
-           AddToMessageLog ((flags), x_msg); \
-        }
-
-/* get a registry string */
-#define QUERY_REG_STRING(name, data) \
-  { \
-    len = sizeof (data); \
-    status = RegQueryValueEx(openvpn_key, name, NULL, &type, data, &len); \
-    if (status != ERROR_SUCCESS || type != REG_SZ) \
-      { \
-        SetLastError (status); \
-        MSG (M_SYSERR, error_format_str, name); \
-       RegCloseKey (openvpn_key); \
-       goto finish; \
-      } \
-  }
-
-/* get a registry string */
-#define QUERY_REG_DWORD(name, data) \
-  { \
-    len = sizeof (DWORD); \
-    status = RegQueryValueEx(openvpn_key, name, NULL, &type, (LPBYTE)&data, 
&len); \
-    if (status != ERROR_SUCCESS || type != REG_DWORD || len != sizeof (DWORD)) 
\
-      { \
-        SetLastError (status); \
-        MSG (M_SYSERR, error_format_dword, name); \
-       RegCloseKey (openvpn_key); \
-       goto finish; \
-      } \
-  }
-
-/*
- * This is necessary due to certain buggy implementations of snprintf,
- * that don't guarantee null termination for size > 0.
- * (copied from ../buffer.c, line 217)
- * (git: 100644 blob e2f8caab0a5b2a870092c6cd508a1a50c21c3ba3  buffer.c)
- */
-
-int openvpn_snprintf(char *str, size_t size, const char *format, ...)
-{
-  va_list arglist;
-  int len = -1;
-  if (size > 0)
-    {
-      va_start (arglist, format);
-      len = vsnprintf (str, size, format, arglist);
-      va_end (arglist);
-      str[size - 1] = 0;
-    }
-  return (len >= 0 && len < size);
-}
-
-
-bool
-init_security_attributes_allow_all (struct security_attributes *obj)
-{
-  CLEAR (*obj);
-
-  obj->sa.nLength = sizeof (SECURITY_ATTRIBUTES);
-  obj->sa.lpSecurityDescriptor = &obj->sd;
-  obj->sa.bInheritHandle = TRUE;
-  if (!InitializeSecurityDescriptor (&obj->sd, SECURITY_DESCRIPTOR_REVISION))
-    return false;
-  if (!SetSecurityDescriptorDacl (&obj->sd, TRUE, NULL, FALSE))
-    return false;
-  return true;
-}
-
-HANDLE
-create_event (const char *name, bool allow_all, bool initial_state, bool 
manual_reset)
-{
-  if (allow_all)
-    {
-      struct security_attributes sa;
-      if (!init_security_attributes_allow_all (&sa))
-       return NULL;
-      return CreateEvent (&sa.sa, (BOOL)manual_reset, (BOOL)initial_state, 
name);
-    }
-  else
-    return CreateEvent (NULL, (BOOL)manual_reset, (BOOL)initial_state, name);
-}
-
-void
-close_if_open (HANDLE h)
-{
-  if (h != NULL)
-    CloseHandle (h);
-}
-
-static bool
-match (const WIN32_FIND_DATA *find, const char *ext)
-{
-  int i;
-
-  if (find->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
-    return false;
-
-  if (!strlen (ext))
-    return true;
-
-  i = strlen (find->cFileName) - strlen (ext) - 1;
-  if (i < 1)
-    return false;
-
-  return find->cFileName[i] == '.' && !_stricmp (find->cFileName + i + 1, ext);
-}
-
-/*
- * Modify the extension on a filename.
- */
-static bool
-modext (char *dest, int size, const char *src, const char *newext)
-{
-  int i;
-
-  if (size > 0 && (strlen (src) + 1) <= size)
-    {
-      strcpy (dest, src);
-      dest [size - 1] = '\0';
-      i = strlen (dest);
-      while (--i >= 0)
-       {
-         if (dest[i] == '\\')
-           break;
-         if (dest[i] == '.')
-           {
-             dest[i] = '\0';
-             break;
-           }
-       }
-      if (strlen (dest) + strlen(newext) + 2 <= size)
-       {
-         strcat (dest, ".");
-         strcat (dest, newext);
-         return true;
-       }
-      dest [0] = '\0';
-    }
-  return false;
-}
-
-VOID ServiceStart (DWORD dwArgc, LPTSTR *lpszArgv)
-{
-  char exe_path[MAX_PATH];
-  char config_dir[MAX_PATH];
-  char ext_string[16];
-  char log_dir[MAX_PATH];
-  char priority_string[64];
-  char append_string[2];
-
-  DWORD priority;
-  bool append;
-
-  ResetError ();
-
-  if (!ReportStatusToSCMgr(SERVICE_START_PENDING, NO_ERROR, 3000))
-    {
-      MSG (M_ERR, "ReportStatusToSCMgr #1 failed");
-      goto finish;
-    }
-
-  /*
-   * Create our exit event
-   */
-  exit_event = create_event (EXIT_EVENT_NAME, false, false, true);
-  if (!exit_event)
-    {
-      MSG (M_ERR, "CreateEvent failed");
-      goto finish;
-    }
-
-  /*
-   * If exit event is already signaled, it means we were not
-   * shut down properly.
-   */
-  if (WaitForSingleObject (exit_event, 0) != WAIT_TIMEOUT)
-    {
-      MSG (M_ERR, "Exit event is already signaled -- we were not shut down 
properly");
-      goto finish;
-    }
-
-  if (!ReportStatusToSCMgr(SERVICE_START_PENDING, NO_ERROR, 3000))
-    {
-      MSG (M_ERR, "ReportStatusToSCMgr #2 failed");
-      goto finish;
-    }
-
-  /*
-   * Read info from registry in key HKLM\SOFTWARE\OpenVPN
-   */
-  {
-    HKEY openvpn_key;
-    LONG status;
-    DWORD len;
-    DWORD type;
-
-    static const char error_format_str[] =
-      "Error querying registry key of type REG_SZ: HKLM\\" REG_KEY "\\%s";
-
-    static const char error_format_dword[] =
-      "Error querying registry key of type REG_DWORD: HKLM\\" REG_KEY "\\%s";
-
-    status = RegOpenKeyEx(
-                         HKEY_LOCAL_MACHINE,
-                         REG_KEY,
-                         0,
-                         KEY_READ,
-                         &openvpn_key);
-
-    if (status != ERROR_SUCCESS)
-      {
-       SetLastError (status);
-       MSG (M_SYSERR, "Registry key HKLM\\" REG_KEY " not found");
-       goto finish;
-      }
-
-    /* get path to openvpn.exe */
-    QUERY_REG_STRING ("exe_path", exe_path);
-
-    /* get path to configuration directory */
-    QUERY_REG_STRING ("config_dir", config_dir);
-
-    /* get extension on configuration files */
-    QUERY_REG_STRING ("config_ext", ext_string);
-
-    /* get path to log directory */
-    QUERY_REG_STRING ("log_dir", log_dir);
-
-    /* get priority for spawned OpenVPN subprocesses */
-    QUERY_REG_STRING ("priority", priority_string);
-
-    /* should we truncate or append to logfile? */
-    QUERY_REG_STRING ("log_append", append_string);
-
-    RegCloseKey (openvpn_key);
-  }
-
-  /* set process priority */
-  priority = NORMAL_PRIORITY_CLASS;
-  if (!_stricmp (priority_string, "IDLE_PRIORITY_CLASS"))
-    priority = IDLE_PRIORITY_CLASS;
-  else if (!_stricmp (priority_string, "BELOW_NORMAL_PRIORITY_CLASS"))
-    priority = BELOW_NORMAL_PRIORITY_CLASS;
-  else if (!_stricmp (priority_string, "NORMAL_PRIORITY_CLASS"))
-    priority = NORMAL_PRIORITY_CLASS;
-  else if (!_stricmp (priority_string, "ABOVE_NORMAL_PRIORITY_CLASS"))
-    priority = ABOVE_NORMAL_PRIORITY_CLASS;
-  else if (!_stricmp (priority_string, "HIGH_PRIORITY_CLASS"))
-    priority = HIGH_PRIORITY_CLASS;
-  else
-    {
-      MSG (M_ERR, "Unknown priority name: %s", priority_string);
-      goto finish;
-    }
-
-  /* set log file append/truncate flag */
-  append = false;
-  if (append_string[0] == '0')
-    append = false;
-  else if (append_string[0] == '1')
-    append = true;
-  else
-    {
-      MSG (M_ERR, "Log file append flag (given as '%s') must be '0' or '1'", 
append_string);
-      goto finish;
-    }
-
-  /*
-   * Instantiate an OpenVPN process for each configuration
-   * file found.
-   */
-  {
-    WIN32_FIND_DATA find_obj;
-    HANDLE find_handle;
-    BOOL more_files;
-    char find_string[MAX_PATH];
-
-    openvpn_snprintf (find_string, MAX_PATH, "%s\\*", config_dir);
-
-    find_handle = FindFirstFile (find_string, &find_obj);
-    if (find_handle == INVALID_HANDLE_VALUE)
-      {
-        MSG (M_ERR, "Cannot get configuration file list using: %s", 
find_string);
-       goto finish;
-      }
-
-    /*
-     * Loop over each config file
-     */
-    do {
-      HANDLE log_handle = NULL;
-      STARTUPINFO start_info;
-      PROCESS_INFORMATION proc_info;
-      struct security_attributes sa;
-      char log_file[MAX_PATH];
-      char log_path[MAX_PATH];
-      char command_line[256];
-
-      CLEAR (start_info);
-      CLEAR (proc_info);
-      CLEAR (sa);
-
-      if (!ReportStatusToSCMgr(SERVICE_START_PENDING, NO_ERROR, 3000))
-       {
-         MSG (M_ERR, "ReportStatusToSCMgr #3 failed");
-         FindClose (find_handle);
-         goto finish;
-       }
-
-      /* does file have the correct type and extension? */
-      if (match (&find_obj, ext_string))
-       {
-         /* get log file pathname */
-         if (!modext (log_file, sizeof (log_file), find_obj.cFileName, "log"))
-           {
-             MSG (M_ERR, "Cannot construct logfile name based on: %s", 
find_obj.cFileName);
-             FindClose (find_handle);
-             goto finish;
-           }
-         openvpn_snprintf (log_path, sizeof(log_path),
-                            "%s\\%s", log_dir, log_file);
-
-         /* construct command line */
-         openvpn_snprintf (command_line, sizeof(command_line), PACKAGE " 
--service %s 1 --config \"%s\"",
-                     EXIT_EVENT_NAME,
-                     find_obj.cFileName);
-
-         /* Make security attributes struct for logfile handle so it can
-            be inherited. */
-         if (!init_security_attributes_allow_all (&sa))
-           {
-             MSG (M_SYSERR, "InitializeSecurityDescriptor start_" PACKAGE " 
failed");
-             goto finish;
-           }
-
-         /* open logfile as stdout/stderr for soon-to-be-spawned subprocess */
-         log_handle = CreateFile (log_path,
-                                  GENERIC_WRITE,
-                                  FILE_SHARE_READ,
-                                  &sa.sa,
-                                  append ? OPEN_ALWAYS : CREATE_ALWAYS,
-                                  FILE_ATTRIBUTE_NORMAL,
-                                  NULL);
-
-         if (log_handle == INVALID_HANDLE_VALUE)
-           {
-             MSG (M_SYSERR, "Cannot open logfile: %s", log_path);
-             FindClose (find_handle);
-             goto finish;
-           }
-
-         /* append to logfile? */
-         if (append)
-           {
-             if (SetFilePointer (log_handle, 0, NULL, FILE_END) == 
INVALID_SET_FILE_POINTER)
-               {
-                 MSG (M_SYSERR, "Cannot seek to end of logfile: %s", log_path);
-                 FindClose (find_handle);
-                 goto finish;
-               }
-           }
-
-         /* fill in STARTUPINFO struct */
-         GetStartupInfo(&start_info);
-         start_info.cb = sizeof(start_info);
-         start_info.dwFlags = STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW;
-         start_info.wShowWindow = SW_HIDE;
-         start_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
-         start_info.hStdOutput = start_info.hStdError = log_handle;
-
-         /* create an OpenVPN process for one config file */
-         if (!CreateProcess(exe_path,
-                            command_line,
-                            NULL,
-                            NULL,
-                            TRUE,
-                            priority | CREATE_NEW_CONSOLE,
-                            NULL,
-                            config_dir,
-                            &start_info,
-                            &proc_info))
-           {
-             MSG (M_SYSERR, "CreateProcess failed, exe='%s' cmdline='%s' 
dir='%s'",
-                  exe_path,
-                  command_line,
-                  config_dir);
-
-             FindClose (find_handle);
-             CloseHandle (log_handle);
-             goto finish;
-           }
-
-         /* close unneeded handles */
-         Sleep (1000); /* try to prevent race if we close logfile
-                          handle before child process DUPs it */
-         if (!CloseHandle (proc_info.hProcess)
-             || !CloseHandle (proc_info.hThread)
-             || !CloseHandle (log_handle))
-           {
-             MSG (M_SYSERR, "CloseHandle failed");
-             goto finish;
-           }
-       }
-
-      /* more files to process? */
-      more_files = FindNextFile (find_handle, &find_obj);
-
-    } while (more_files);
-    
-    FindClose (find_handle);
-  }
-
-  /* we are now fully started */
-  if (!ReportStatusToSCMgr(SERVICE_RUNNING, NO_ERROR, 0))
-    {
-      MSG (M_ERR, "ReportStatusToSCMgr SERVICE_RUNNING failed");
-      goto finish;
-    }
-
-  /* wait for our shutdown signal */
-  if (WaitForSingleObject (exit_event, INFINITE) != WAIT_OBJECT_0)
-    {
-      MSG (M_ERR, "wait for shutdown signal failed");
-    }
-
- finish:
-  ServiceStop ();
-  if (exit_event)
-    CloseHandle (exit_event);
-}
-
-VOID ServiceStop()
-{
-  if (exit_event)
-    SetEvent(exit_event);
-}
diff --git a/src/openvpnserv/service.c b/src/openvpnserv/service.c
index d7562b3..82f5551 100644
--- a/src/openvpnserv/service.c
+++ b/src/openvpnserv/service.c
@@ -1,700 +1,245 @@
-/*---------------------------------------------------------------------------
-THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
-ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
-TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
-PARTICULAR PURPOSE.
-
-Copyright (C) 1993 - 2000.  Microsoft Corporation.  All rights reserved.
-
-MODULE:   service.c
-
-PURPOSE:  Implements functions required by all Windows NT services
-
-FUNCTIONS:
-  main(int argc, char **argv);
-  service_ctrl(DWORD dwCtrlCode);
-  service_main(DWORD dwArgc, LPTSTR *lpszArgv);
-  CmdInstallService();
-  CmdRemoveService();
-  CmdStartService();
-  CmdDebugService(int argc, char **argv);
-  ControlHandler ( DWORD dwCtrlType );
-  GetLastErrorText( LPTSTR lpszBuf, DWORD dwSize );
-
----------------------------------------------------------------------------*/
-
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#elif defined(_MSC_VER)
-#include "config-msvc.h"
-#endif
-#include <windows.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <process.h>
-#include <tchar.h>
+/*
+ * THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
+ * ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
+ * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
+ * PARTICULAR PURPOSE.
+ *
+ * Copyright (C) 1993 - 2000.  Microsoft Corporation.  All rights reserved.
+ *                      2013 Heiko Hund <heiko.h...@sophos.com>
+ */

 #include "service.h"

-// internal variables
-SERVICE_STATUS          ssStatus;       // current status of the service
-SERVICE_STATUS_HANDLE   sshStatusHandle;
-DWORD                   dwErr = 0;
-BOOL                    bDebug = FALSE;
-TCHAR                   szErr[256];
-
-// internal function prototypes
-VOID WINAPI service_ctrl(DWORD dwCtrlCode);
-VOID WINAPI service_main(DWORD dwArgc, LPTSTR *lpszArgv);
-int CmdInstallService();
-int CmdRemoveService();
-int CmdStartService();
-VOID CmdDebugService(int argc, char **argv);
-BOOL WINAPI ControlHandler ( DWORD dwCtrlType );
-LPTSTR GetLastErrorText( LPTSTR lpszBuf, DWORD dwSize );
-
-//
-//  FUNCTION: main
-//
-//  PURPOSE: entrypoint for service
-//
-//  PARAMETERS:
-//    argc - number of command line arguments
-//    argv - array of command line arguments
-//
-//  RETURN VALUE:
-//    none
-//
-//  COMMENTS:
-//    main() either performs the command line task, or
-//    call StartServiceCtrlDispatcher to register the
-//    main service thread.  When the this call returns,
-//    the service has stopped, so exit.
-//
-int __cdecl main(int argc, char **argv)
-{
-   SERVICE_TABLE_ENTRY dispatchTable[] =
-   {
-      { TEXT(SZSERVICENAME), (LPSERVICE_MAIN_FUNCTION)service_main},
-      { NULL, NULL}
-   };
-
-   if ( (argc > 1) &&
-        ((*argv[1] == '-') || (*argv[1] == '/')) )
-   {
-      if ( _stricmp( "install", argv[1]+1 ) == 0 )
-      {
-         return CmdInstallService();
-      }
-      else if ( _stricmp( "remove", argv[1]+1 ) == 0 )
-      {
-         return CmdRemoveService();
-      }
-         else if ( _stricmp( "start", argv[1]+1 ) == 0)
-         {
-                 return CmdStartService();
-      }
-      else if ( _stricmp( "debug", argv[1]+1 ) == 0 )
-      {
-         bDebug = TRUE;
-         CmdDebugService(argc, argv);
-      }
-      else
-      {
-         goto dispatch;
-      }
-      return 0;
-   }
-
-   // if it doesn't match any of the above parameters
-   // the service control manager may be starting the service
-   // so we must call StartServiceCtrlDispatcher
-   dispatch:
-   // this is just to be friendly
-   printf( "%s -install          to install the service\n", SZAPPNAME );
-   printf( "%s -start                   to start the service\n", SZAPPNAME );
-   printf( "%s -remove           to remove the service\n", SZAPPNAME );
-   printf( "%s -debug <params>   to run as a console app for debugging\n", 
SZAPPNAME );
-   printf( "\nStartServiceCtrlDispatcher being called.\n" );
-   printf( "This may take several seconds.  Please wait.\n" );
-
-   if (!StartServiceCtrlDispatcher(dispatchTable))
-      AddToMessageLog(MSG_FLAGS_ERROR, TEXT("StartServiceCtrlDispatcher 
failed."));
-
-   return 0;
-}
-
-
-
-//
-//  FUNCTION: service_main
-//
-//  PURPOSE: To perform actual initialization of the service
-//
-//  PARAMETERS:
-//    dwArgc   - number of command line arguments
-//    lpszArgv - array of command line arguments
-//
-//  RETURN VALUE:
-//    none
-//
-//  COMMENTS:
-//    This routine performs the service initialization and then calls
-//    the user defined ServiceStart() routine to perform majority
-//    of the work.
-//
-void WINAPI service_main(DWORD dwArgc, LPTSTR *lpszArgv)
-{
-
-   // register our service control handler:
-   //
-   sshStatusHandle = RegisterServiceCtrlHandler( TEXT(SZSERVICENAME), 
service_ctrl);
-
-   if (!sshStatusHandle)
-      goto cleanup;
-
-   // SERVICE_STATUS members that don't change in example
-   //
-   ssStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
-   ssStatus.dwServiceSpecificExitCode = 0;
-
-
-   // report the status to the service control manager.
-   //
-   if (!ReportStatusToSCMgr(
-                           SERVICE_START_PENDING, // service state
-                           NO_ERROR,              // exit code
-                           3000))                 // wait hint
-      goto cleanup;
-
-
-   ServiceStart( dwArgc, lpszArgv );
-
-   cleanup:
+#include <windows.h>
+#include <stdio.h>
+#include <process.h>

-   // try to report the stopped status to the service control manager.
-   //
-   if (sshStatusHandle)
-      (VOID)ReportStatusToSCMgr(
-                               SERVICE_STOPPED,
-                               dwErr,
-                               0);

-   return;
-}
+openvpn_service_t openvpn_service[_service_max];


-
-//
-//  FUNCTION: service_ctrl
-//
-//  PURPOSE: This function is called by the SCM whenever
-//           ControlService() is called on this service.
-//
-//  PARAMETERS:
-//    dwCtrlCode - type of control requested
-//
-//  RETURN VALUE:
-//    none
-//
-//  COMMENTS:
-//
-VOID WINAPI service_ctrl(DWORD dwCtrlCode)
+BOOL
+ReportStatusToSCMgr (SERVICE_STATUS_HANDLE service, SERVICE_STATUS *status)
 {
-   // Handle the requested control code.
-   //
-   switch (dwCtrlCode)
-   {
-   // Stop the service.
-   //
-   // SERVICE_STOP_PENDING should be reported before
-   // setting the Stop Event - hServerStopEvent - in
-   // ServiceStop().  This avoids a race condition
-   // which may result in a 1053 - The Service did not respond...
-   // error.
-   case SERVICE_CONTROL_STOP:
-      ReportStatusToSCMgr(SERVICE_STOP_PENDING, NO_ERROR, 0);
-      ServiceStop();
-      return;
-
-      // Update the service status.
-      //
-   case SERVICE_CONTROL_INTERROGATE:
-      break;
-
-      // invalid control code
-      //
-   default:
-      break;
-
-   }
-
-   ReportStatusToSCMgr(ssStatus.dwCurrentState, NO_ERROR, 0);
+  static DWORD dwCheckPoint = 1;
+  BOOL res = TRUE;
+
+  if (status->dwCurrentState == SERVICE_START_PENDING)
+    status->dwControlsAccepted = 0;
+  else
+    status->dwControlsAccepted = SERVICE_ACCEPT_STOP;
+
+  if (status->dwCurrentState == SERVICE_RUNNING ||
+      status->dwCurrentState == SERVICE_STOPPED)
+    status->dwCheckPoint = 0;
+  else
+    status->dwCheckPoint = dwCheckPoint++;
+
+  /* Report the status of the service to the service control manager. */
+  res = SetServiceStatus (service, status);
+  if (!res)
+    MsgToEventLog(MSG_FLAGS_ERROR, TEXT("SetServiceStatus"));
+
+  return res;
 }

-
-
-//
-//  FUNCTION: ReportStatusToSCMgr()
-//
-//  PURPOSE: Sets the current status of the service and
-//           reports it to the Service Control Manager
-//
-//  PARAMETERS:
-//    dwCurrentState - the state of the service
-//    dwWin32ExitCode - error code to report
-//    dwWaitHint - worst case estimate to next checkpoint
-//
-//  RETURN VALUE:
-//    TRUE  - success
-//    FALSE - failure
-//
-//  COMMENTS:
-//
-BOOL ReportStatusToSCMgr(DWORD dwCurrentState,
-                         DWORD dwWin32ExitCode,
-                         DWORD dwWaitHint)
+static int
+CmdInstallServices ()
 {
-   static DWORD dwCheckPoint = 1;
-   BOOL fResult = TRUE;
-
-
-   if ( !bDebug ) // when debugging we don't report to the SCM
-   {
-      if (dwCurrentState == SERVICE_START_PENDING)
-         ssStatus.dwControlsAccepted = 0;
-      else
-         ssStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
-
-      ssStatus.dwCurrentState = dwCurrentState;
-      ssStatus.dwWin32ExitCode = dwWin32ExitCode;
-      ssStatus.dwWaitHint = dwWaitHint;
+  SC_HANDLE service;
+  SC_HANDLE svc_ctl_mgr;
+  TCHAR path[512];
+  int i, ret = _service_max;
+
+  if (GetModuleFileName (NULL, path + 1, 510) == 0)
+  {
+    _tprintf (TEXT("Unable to install service - %s\n"), GetLastErrorText ());
+    return 1;
+  }
+
+  path[0] = TEXT('\"');
+  _tcscat (path, TEXT("\""));
+
+  svc_ctl_mgr = OpenSCManager (NULL, NULL, SC_MANAGER_CONNECT | 
SC_MANAGER_CREATE_SERVICE);
+  if (svc_ctl_mgr == NULL)
+    {
+      _tprintf (TEXT("OpenSCManager failed - %s\n"), GetLastErrorText ());
+      return 1;
+    }

-      if ( ( dwCurrentState == SERVICE_RUNNING ) ||
-           ( dwCurrentState == SERVICE_STOPPED ) )
-         ssStatus.dwCheckPoint = 0;
+  for (i = 0; i < _service_max; i++)
+    {
+      service = CreateService (svc_ctl_mgr,
+                               openvpn_service[i].name,
+                               openvpn_service[i].display_name,
+                               SERVICE_QUERY_STATUS,
+                               SERVICE_WIN32_SHARE_PROCESS,
+                               openvpn_service[i].start_type,
+                               SERVICE_ERROR_NORMAL,
+                               path, NULL, NULL,
+                               openvpn_service[i].dependencies,
+                               NULL, NULL);
+      if (service)
+        {
+          _tprintf (TEXT("%s installed.\n"), openvpn_service[i].display_name);
+          CloseServiceHandle (service);
+          --ret;
+        }
       else
-         ssStatus.dwCheckPoint = dwCheckPoint++;
-
+        _tprintf (TEXT("CreateService failed - %s\n"), GetLastErrorText ());
+    }

-      // Report the status of the service to the service control manager.
-      //
-      if (!(fResult = SetServiceStatus( sshStatusHandle, &ssStatus)))
-      {
-         AddToMessageLog(MSG_FLAGS_ERROR, TEXT("SetServiceStatus"));
-      }
-   }
-   return fResult;
+  CloseServiceHandle (svc_ctl_mgr);
+  return ret;
 }


-
-//
-//  FUNCTION: AddToMessageLog(LPTSTR lpszMsg)
-//
-//  PURPOSE: Allows any thread to log an error message
-//
-//  PARAMETERS:
-//    lpszMsg - text for message
-//
-//  RETURN VALUE:
-//    none
-//
-//  COMMENTS:
-//
-void AddToMessageLog(DWORD flags, LPTSTR lpszMsg)
+static int
+CmdStartService (openvpn_service_type type)
 {
-   TCHAR szMsg [(sizeof(SZSERVICENAME) / sizeof(TCHAR)) + 100 ];
-   HANDLE  hEventSource;
-   LPCSTR  lpszStrings[2];
-
-   if ( !bDebug )
-   {
-     if (flags & MSG_FLAGS_SYS_CODE)
-      dwErr = GetLastError();
-     else
-       dwErr = 0;
-
-      // Use event logging to log the error.
-      //
-      hEventSource = RegisterEventSource(NULL, TEXT(SZSERVICENAME));
-
-      _stprintf(szMsg, TEXT("%s error: %d"), TEXT(SZSERVICENAME), (int)dwErr);
-      lpszStrings[0] = szMsg;
-      lpszStrings[1] = lpszMsg;
-
-      if (hEventSource != NULL)
-      {
-         ReportEvent(hEventSource, // handle of event source
-                    // event type
-                     (flags & MSG_FLAGS_ERROR)
-                      ? EVENTLOG_ERROR_TYPE : EVENTLOG_INFORMATION_TYPE,
-                     0,                    // event category
-                     0,                    // event ID
-                     NULL,                 // current user's SID
-                     2,                    // strings in lpszStrings
-                     0,                    // no bytes of raw data
-                     lpszStrings,          // array of error strings
-                     NULL);                // no raw data
-
-         (VOID) DeregisterEventSource(hEventSource);
-      }
-   }
-}
-
-void ResetError (void)
-{
-  dwErr = 0;
-}
+  int ret = 1;
+  SC_HANDLE svc_ctl_mgr;
+  SC_HANDLE service;

-///////////////////////////////////////////////////////////////////
-//
-//  The following code handles service installation and removal
-//
-
-
-//
-//  FUNCTION: CmdInstallService()
-//
-//  PURPOSE: Installs the service
-//
-//  PARAMETERS:
-//    none
-//
-//  RETURN VALUE:
-//    0 if success
-//
-//  COMMENTS:
-//
-int CmdInstallService()
-{
-   SC_HANDLE   schService;
-   SC_HANDLE   schSCManager;
-
-   TCHAR szPath[512];
-
-   int ret = 0;
-
-   if ( GetModuleFileName( NULL, szPath+1, 510 ) == 0 )
-   {
-      _tprintf(TEXT("Unable to install %s - %s\n"), 
TEXT(SZSERVICEDISPLAYNAME), GetLastErrorText(szErr, 256));
+  svc_ctl_mgr = OpenSCManager (NULL, NULL, SC_MANAGER_ALL_ACCESS);
+  if (svc_ctl_mgr == NULL)
+    {
+      _tprintf (TEXT("OpenSCManager failed - %s\n"), GetLastErrorText ());
       return 1;
-   }
-   szPath[0] = '\"';
-   strcat(szPath, "\"");
-
-   schSCManager = OpenSCManager(
-                               NULL,                   // machine (NULL == 
local)
-                               NULL,                   // database (NULL == 
default)
-                               SC_MANAGER_CONNECT | SC_MANAGER_CREATE_SERVICE  
// access required
-                               );
-   if ( schSCManager )
-   {
-      schService = CreateService(
-                                schSCManager,               // SCManager 
database
-                                TEXT(SZSERVICENAME),        // name of service
-                                TEXT(SZSERVICEDISPLAYNAME), // name to display
-                                SERVICE_QUERY_STATUS,         // desired access
-                                SERVICE_WIN32_OWN_PROCESS,  // service type
-                               SERVICE_DEMAND_START,        // start type -- 
alternative: SERVICE_AUTO_START
-                                SERVICE_ERROR_NORMAL,       // error control 
type
-                                szPath,                     // service's binary
-                                NULL,                       // no load 
ordering group
-                                NULL,                       // no tag 
identifier
-                                TEXT(SZDEPENDENCIES),       // dependencies
-                                NULL,                       // LocalSystem 
account
-                                NULL);                      // no password
-
-      if ( schService )
-      {
-         _tprintf(TEXT("%s installed.\n"), TEXT(SZSERVICEDISPLAYNAME) );
-         CloseServiceHandle(schService);
-      }
-      else
-      {
-         _tprintf(TEXT("CreateService failed - %s\n"), GetLastErrorText(szErr, 
256));
-        ret = 1;
-      }
-
-      CloseServiceHandle(schSCManager);
-   }
-   else
-     {
-      _tprintf(TEXT("OpenSCManager failed - %s\n"), 
GetLastErrorText(szErr,256));
-       ret = 1;
-     }
-   return ret;
-}
-
-//
-//  FUNCTION: CmdStartService()
-//
-//  PURPOSE: Start the service
-//
-//  PARAMETERS:
-//    none
-//
-//  RETURN VALUE:
-//    0 if success
-//
-//  COMMENTS:
-
-int CmdStartService()
-{
-  int ret = 0;
-
-  SC_HANDLE schSCManager;
-  SC_HANDLE schService;
-
-
-    // Open a handle to the SC Manager database. 
-    schSCManager = OpenSCManager( 
-       NULL,                    // local machine 
-       NULL,                    // ServicesActive database 
-       SC_MANAGER_ALL_ACCESS);  // full access rights 
-   
-    if (NULL == schSCManager) {
-       _tprintf(TEXT("OpenSCManager failed - %s\n"), 
GetLastErrorText(szErr,256));
-       ret = 1;
     }

-    schService = OpenService( 
-        schSCManager,          // SCM database 
-        SZSERVICENAME,         // service name
-        SERVICE_ALL_ACCESS); 
+  service = OpenService (svc_ctl_mgr, openvpn_service[type].name, 
SERVICE_ALL_ACCESS);
+  if (service)
+    {
+      if (StartService (service, 0, NULL))
+        {
+          _tprintf (TEXT("Service Started\n"));
+          ret = 0;
+        }
+      else
+        _tprintf (TEXT("StartService failed - %s\n"), GetLastErrorText ());

-    if (schService == NULL) {
-      _tprintf(TEXT("OpenService failed - %s\n"), GetLastErrorText(szErr,256));
-       ret = 1;
+      CloseServiceHandle(service);
     }
- 
-    if (!StartService(
-            schService,  // handle to service 
-            0,           // number of arguments 
-            NULL) )      // no arguments 
+  else
     {
-      _tprintf(TEXT("StartService failed - %s\n"), 
GetLastErrorText(szErr,256));
-       ret = 1;
+      _tprintf (TEXT("OpenService failed - %s\n"), GetLastErrorText ());
     }
-    else
-       {
-               _tprintf(TEXT("Service Started\n"));
-       ret = 0;
-       }
-    CloseServiceHandle(schService); 
-    CloseServiceHandle(schSCManager);
-    return ret;
-}

-//
-//  FUNCTION: CmdRemoveService()
-//
-//  PURPOSE: Stops and removes the service
-//
-//  PARAMETERS:
-//    none
-//
-//  RETURN VALUE:
-//    0 if success
-//
-//  COMMENTS:
-//
-int CmdRemoveService()
-{
-   SC_HANDLE   schService;
-   SC_HANDLE   schSCManager;
+  CloseServiceHandle(svc_ctl_mgr);
+  return ret;
+}

-   int ret = 0;

-   schSCManager = OpenSCManager(
-                               NULL,                   // machine (NULL == 
local)
-                               NULL,                   // database (NULL == 
default)
-                               SC_MANAGER_CONNECT   // access required
-                               );
-   if ( schSCManager )
-   {
-      schService = OpenService(schSCManager, TEXT(SZSERVICENAME), DELETE | 
SERVICE_STOP | SERVICE_QUERY_STATUS);
+static int
+CmdRemoveServices ()
+{
+  SC_HANDLE service;
+  SC_HANDLE svc_ctl_mgr;
+  SERVICE_STATUS status;
+  int i, ret = _service_max;

-      if (schService)
-      {
-         // try to stop the service
-         if ( ControlService( schService, SERVICE_CONTROL_STOP, &ssStatus ) )
-         {
-            _tprintf(TEXT("Stopping %s."), TEXT(SZSERVICEDISPLAYNAME));
-            Sleep( 1000 );
+  svc_ctl_mgr = OpenSCManager (NULL, NULL, SC_MANAGER_CONNECT);
+  if (svc_ctl_mgr == NULL)
+    {
+      _tprintf(TEXT("OpenSCManager failed - %s\n"), GetLastErrorText ());
+      return 1;
+    }

-            while ( QueryServiceStatus( schService, &ssStatus ) )
+  for (i = 0; i < _service_max; i++)
+    {
+      openvpn_service_t *ovpn_svc = &openvpn_service[i];
+      service = OpenService (svc_ctl_mgr, ovpn_svc->name,
+                             DELETE | SERVICE_STOP | SERVICE_QUERY_STATUS);
+      if (service == NULL)
+        {
+          _tprintf (TEXT("OpenService failed - %s\n"), GetLastErrorText ());
+          goto out;
+        }
+
+      /* try to stop the service */
+      if (ControlService (service, SERVICE_CONTROL_STOP, &status))
+        {
+          _tprintf (TEXT("Stopping %s."), ovpn_svc->display_name);
+          Sleep (1000);
+
+          while (QueryServiceStatus (service, &status))
             {
-               if ( ssStatus.dwCurrentState == SERVICE_STOP_PENDING )
-               {
-                  _tprintf(TEXT("."));
-                  Sleep( 1000 );
-               }
-               else
-                  break;
+              if (status.dwCurrentState == SERVICE_STOP_PENDING)
+                {
+                  _tprintf (TEXT("."));
+                  Sleep (1000);
+                }
+              else
+                break;
             }

-            if ( ssStatus.dwCurrentState == SERVICE_STOPPED )
-               _tprintf(TEXT("\n%s stopped.\n"), TEXT(SZSERVICEDISPLAYNAME) );
-            else
-             {
-               _tprintf(TEXT("\n%s failed to stop.\n"), 
TEXT(SZSERVICEDISPLAYNAME) );
-               ret = 1;
-             }
-
-         }
-
-         // now remove the service
-         if ( DeleteService(schService) )
-            _tprintf(TEXT("%s removed.\n"), TEXT(SZSERVICEDISPLAYNAME) );
-         else
-          {
-            _tprintf(TEXT("DeleteService failed - %s\n"), 
GetLastErrorText(szErr,256));
-            ret = 1;
-          }
-
-
-         CloseServiceHandle(schService);
-      }
+          if (status.dwCurrentState == SERVICE_STOPPED)
+            _tprintf (TEXT("\n%s stopped.\n"), ovpn_svc->display_name);
+          else
+            _tprintf (TEXT("\n%s failed to stop.\n"), ovpn_svc->display_name);
+        }
+
+      /* now remove the service */
+      if (DeleteService (service))
+        {
+          _tprintf (TEXT("%s removed.\n"), ovpn_svc->display_name);
+          --ret;
+        }
       else
-       {
-         _tprintf(TEXT("OpenService failed - %s\n"), 
GetLastErrorText(szErr,256));
-         ret = 1;
-       }
-
-      CloseServiceHandle(schSCManager);
-   }
-   else
-     {
-      _tprintf(TEXT("OpenSCManager failed - %s\n"), 
GetLastErrorText(szErr,256));
-       ret = 1;
-     }
-   return ret;
-}
-
-
-
-
-///////////////////////////////////////////////////////////////////
-//
-//  The following code is for running the service as a console app
-//
-
-
-//
-//  FUNCTION: CmdDebugService(int argc, char ** argv)
-//
-//  PURPOSE: Runs the service as a console application
-//
-//  PARAMETERS:
-//    argc - number of command line arguments
-//    argv - array of command line arguments
-//
-//  RETURN VALUE:
-//    none
-//
-//  COMMENTS:
-//
-void CmdDebugService(int argc, char ** argv)
-{
-   DWORD dwArgc;
-   LPTSTR *lpszArgv;
-
-#ifdef UNICODE
-   lpszArgv = CommandLineToArgvW(GetCommandLineW(), &(dwArgc) );
-   if (NULL == lpszArgv)
-   {
-       // CommandLineToArvW failed!!
-       _tprintf(TEXT("CmdDebugService CommandLineToArgvW returned NULL\n"));
-       return;
-   }
-#else
-   dwArgc   = (DWORD) argc;
-   lpszArgv = argv;
-#endif
-
-   _tprintf(TEXT("Debugging %s.\n"), TEXT(SZSERVICEDISPLAYNAME));
-
-   SetConsoleCtrlHandler( ControlHandler, TRUE );
+        _tprintf (TEXT("DeleteService failed - %s\n"), GetLastErrorText ());

-   ServiceStart( dwArgc, lpszArgv );
-
-#ifdef UNICODE
-// Must free memory allocated for arguments
-
-   GlobalFree(lpszArgv);
-#endif // UNICODE
+      CloseServiceHandle (service);
+    }

+out:
+  CloseServiceHandle (svc_ctl_mgr);
+  return ret;
 }


-//
-//  FUNCTION: ControlHandler ( DWORD dwCtrlType )
-//
-//  PURPOSE: Handled console control events
-//
-//  PARAMETERS:
-//    dwCtrlType - type of control event
-//
-//  RETURN VALUE:
-//    True - handled
-//    False - unhandled
-//
-//  COMMENTS:
-//
-BOOL WINAPI ControlHandler ( DWORD dwCtrlType )
+int
+_tmain (int argc, TCHAR *argv[])
 {
-   switch ( dwCtrlType )
-   {
-   case CTRL_BREAK_EVENT:  // use Ctrl+C or Ctrl+Break to simulate
-   case CTRL_C_EVENT:      // SERVICE_CONTROL_STOP in debug mode
-      _tprintf(TEXT("Stopping %s.\n"), TEXT(SZSERVICEDISPLAYNAME));
-      ServiceStop();
-      return TRUE;
-      break;
-
-   }
-   return FALSE;
-}
-
-//
-//  FUNCTION: GetLastErrorText
-//
-//  PURPOSE: copies error message text to string
-//
-//  PARAMETERS:
-//    lpszBuf - destination buffer
-//    dwSize - size of buffer
-//
-//  RETURN VALUE:
-//    destination buffer
-//
-//  COMMENTS:
-//
-LPTSTR GetLastErrorText( LPTSTR lpszBuf, DWORD dwSize )
-{
-   DWORD dwRet;
-   LPTSTR lpszTemp = NULL;
-
-   dwRet = FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | 
FORMAT_MESSAGE_FROM_SYSTEM |FORMAT_MESSAGE_ARGUMENT_ARRAY,
-                          NULL,
-                          GetLastError(),
-                          LANG_NEUTRAL,
-                          (LPTSTR)&lpszTemp,
-                          0,
-                          NULL );
-
-   // supplied buffer is not long enough
-   if ( !dwRet || ( (long)dwSize < (long)dwRet+14 ) )
-      lpszBuf[0] = TEXT('\0');
-   else
-   {
-      lpszTemp[lstrlen(lpszTemp)-2] = TEXT('\0');  //remove cr and newline 
character
-      _stprintf( lpszBuf, TEXT("%s (0x%x)"), lpszTemp, (int)GetLastError() );
-   }
-
-   if ( lpszTemp )
-      LocalFree((HLOCAL) lpszTemp );
-
-   return lpszBuf;
+  SERVICE_TABLE_ENTRY dispatchTable[] = {
+    { automatic_service.name, ServiceStartAutomatic },
+    { interactive_service.name, ServiceStartInteractive },
+    { NULL, NULL }
+  };
+
+  openvpn_service[0] = automatic_service;
+  openvpn_service[1] = interactive_service;
+
+  if (argc > 1 && (*argv[1] == TEXT('-') || *argv[1] == TEXT('/')))
+  {
+    if (_tcsicmp (TEXT("install"), argv[1] + 1) == 0)
+      return CmdInstallServices ();
+    else if (_tcsicmp (TEXT("remove"), argv[1] + 1) == 0)
+      return CmdRemoveServices ();
+    else if (_tcsicmp (TEXT("start"), argv[1] + 1) == 0)
+      {
+        BOOL is_auto = argc < 3 || _tcsicmp (TEXT("interactive"), argv[2]) != 
0;
+        return CmdStartService (is_auto ? automatic : interactive);
+      }
+    else
+      goto dispatch;
+
+    return 0;
+  }
+
+  /* If it doesn't match any of the above parameters
+   * the service control manager may be starting the service
+   * so we must call StartServiceCtrlDispatcher
+   */
+dispatch:
+  _tprintf (TEXT("%s -install        to install the services\n"), APPNAME);
+  _tprintf (TEXT("%s -start <name>   to start a service (\"automatic\" or 
\"interactive\")\n"), APPNAME);
+  _tprintf (TEXT("%s -remove         to remove the services\n"), APPNAME);
+  _tprintf (TEXT("\nStartServiceCtrlDispatcher being called.\n"));
+  _tprintf (TEXT("This may take several seconds. Please wait.\n"));
+
+  if (!StartServiceCtrlDispatcher (dispatchTable))
+    MsgToEventLog (MSG_FLAGS_ERROR, TEXT("StartServiceCtrlDispatcher 
failed."));
+
+  return 0;
 }
diff --git a/src/openvpnserv/service.h b/src/openvpnserv/service.h
index e89a89f..5249b31 100644
--- a/src/openvpnserv/service.h
+++ b/src/openvpnserv/service.h
@@ -1,139 +1,90 @@
-/*---------------------------------------------------------------------------
-THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
-ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
-TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
-PARTICULAR PURPOSE.
-
-Copyright (C) 1993 - 2000.  Microsoft Corporation.  All rights reserved.
-
- MODULE: service.h
-
- Comments:  The use of this header file and the accompanying service.c
- file simplifies the process of writting a service.  You as a developer
- simply need to follow the TODO's outlined in this header file, and
- implement the ServiceStart() and ServiceStop() functions.
-
- There is no need to modify the code in service.c.  Just add service.c
- to your project and link with the following libraries...
-
- libcmt.lib kernel32.lib advapi.lib shell32.lib
-
- This code also supports unicode.  Be sure to compile both service.c and
- and code #include "service.h" with the same Unicode setting.
-
- Upon completion, your code will have the following command line interface
-
- <service exe> -?                to display this list
- <service exe> -install          to install the service
- <service exe> -remove           to remove the service
- <service exe> -debug <params>   to run as a console app for debugging
-
- Note: This code also implements Ctrl+C and Ctrl+Break handlers
-       when using the debug option.  These console events cause
-       your ServiceStop routine to be called
-
-       Also, this code only handles the OWN_SERVICE service type
-       running in the LOCAL_SYSTEM security context.
-
-       To control your service ( start, stop, etc ) you may use the
-       Services control panel applet or the NET.EXE program.
-
-       To aid in writing/debugging service, the
-       SDK contains a utility (MSTOOLS\BIN\SC.EXE) that
-       can be used to control, configure, or obtain service status.
-       SC displays complete status for any service/driver
-       in the service database, and allows any of the configuration
-       parameters to be easily changed at the command line.
-       For more information on SC.EXE, type SC at the command line.
-
-
-------------------------------------------------------------------------------*/
+/*
+ *  OpenVPN -- An application to securely tunnel IP networks
+ *             over a single TCP/UDP port, with support for SSL/TLS-based
+ *             session authentication and key exchange,
+ *             packet encryption, packet authentication, and
+ *             packet compression.
+ *
+ *  Copyright (C) 2013 Heiko Hund <heiko.h...@sophos.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2
+ *  as published by the Free Software Foundation.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program (see the file COPYING included with this
+ *  distribution); if not, write to the Free Software Foundation, Inc.,
+ *  59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */

 #ifndef _SERVICE_H
 #define _SERVICE_H

-
-#ifdef __cplusplus
-extern "C" {
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#elif defined(_MSC_VER)
+#include "config-msvc.h"
 #endif

-//////////////////////////////////////////////////////////////////////////////
-//// todo: change to desired strings
-////
-// name of the executable
-#define SZAPPNAME            PACKAGE "serv"
-// internal name of the service
-#define SZSERVICENAME        PACKAGE_NAME "Service"
-// displayed name of the service
-#define SZSERVICEDISPLAYNAME PACKAGE_NAME " Service"
-// list of service dependencies - "dep1\0dep2\0\0"
-#define SZDEPENDENCIES       TAP_WIN_COMPONENT_ID "\0Dhcp\0\0"
-//////////////////////////////////////////////////////////////////////////////
-
-
-
-//////////////////////////////////////////////////////////////////////////////
-//// todo: ServiceStart()must be defined by in your code.
-////       The service should use ReportStatusToSCMgr to indicate
-////       progress.  This routine must also be used by StartService()
-////       to report to the SCM when the service is running.
-////
-////       If a ServiceStop procedure is going to take longer than
-////       3 seconds to execute, it should spawn a thread to
-////       execute the stop code, and return.  Otherwise, the
-////       ServiceControlManager will believe that the service has
-////       stopped responding
-////
-   VOID ServiceStart(DWORD dwArgc, LPTSTR *lpszArgv);
-   VOID ServiceStop();
-//////////////////////////////////////////////////////////////////////////////
-
-
-
-//////////////////////////////////////////////////////////////////////////////
-//// The following are procedures which
-//// may be useful to call within the above procedures,
-//// but require no implementation by the user.
-//// They are implemented in service.c
-
-//
-//  FUNCTION: ReportStatusToSCMgr()
-//
-//  PURPOSE: Sets the current status of the service and
-//           reports it to the Service Control Manager
-//
-//  PARAMETERS:
-//    dwCurrentState - the state of the service
-//    dwWin32ExitCode - error code to report
-//    dwWaitHint - worst case estimate to next checkpoint
-//
-//  RETURN VALUE:
-//    TRUE  - success
-//    FALSE - failure
-//
-   BOOL ReportStatusToSCMgr(DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD 
dwWaitHint);
-
-
-//
-//  FUNCTION: AddToMessageLog(LPTSTR lpszMsg)
-//
-//  PURPOSE: Allows any thread to log an error message
-//
-//  PARAMETERS:
-//    lpszMsg - text for message
-//
-//  RETURN VALUE:
-//    none
-//
-#  define MSG_FLAGS_ERROR     (1<<0)
-#  define MSG_FLAGS_SYS_CODE  (1<<1)
-   void AddToMessageLog(DWORD flags, LPTSTR lpszMsg);
-   void ResetError (void);
-//////////////////////////////////////////////////////////////////////////////
-
-
-#ifdef __cplusplus
-}
-#endif
+#include <windows.h>
+#include <stdlib.h>
+#include <tchar.h>
+
+#define APPNAME  TEXT(PACKAGE "serv")
+#define SERVICE_DEPENDENCIES  TAP_WIN_COMPONENT_ID "\0Dhcp\0\0"
+
+/*
+ * Message handling
+ */
+#define MSG_FLAGS_ERROR     (1<<0)
+#define MSG_FLAGS_SYS_CODE  (1<<1)
+#define M_INFO    (0)                                  /* informational */
+#define M_SYSERR  (MSG_FLAGS_ERROR|MSG_FLAGS_SYS_CODE) /* error + system code 
*/
+#define M_ERR     (MSG_FLAGS_ERROR)                    /* error */
+
+typedef enum {
+  automatic,
+  interactive,
+  _service_max
+} openvpn_service_type;
+
+typedef struct {
+  openvpn_service_type type;
+  TCHAR *name;
+  TCHAR *display_name;
+  TCHAR *dependencies;
+  DWORD start_type;
+} openvpn_service_t;
+
+typedef struct {
+  TCHAR exe_path[MAX_PATH];
+  TCHAR config_dir[MAX_PATH];
+  TCHAR ext_string[16];
+  TCHAR log_dir[MAX_PATH];
+  DWORD priority;
+  BOOL append;
+} settings_t;
+
+extern openvpn_service_t automatic_service;
+extern openvpn_service_t interactive_service;
+
+
+VOID WINAPI ServiceStartAutomatic (DWORD argc, LPTSTR *argv);
+VOID WINAPI ServiceStartInteractive (DWORD argc, LPTSTR *argv);
+
+int openvpn_vsntprintf (LPTSTR str, size_t size, LPCTSTR format, va_list 
arglist);
+int openvpn_sntprintf (LPTSTR str, size_t size, LPCTSTR format, ...);
+
+DWORD GetOpenvpnSettings (settings_t *s);
+
+BOOL ReportStatusToSCMgr (SERVICE_STATUS_HANDLE service, SERVICE_STATUS 
*status);
+
+LPCTSTR GetLastErrorText ();
+DWORD MsgToEventLog (DWORD flags, LPCTSTR lpszMsg, ...);

 #endif
-- 
1.9.1


Reply via email to