Browse code

openvpnserv: Add support for multi-instances

While openvpn.exe can run multiple concurrent processes, openvpnserv.exe
is usually only one single globally unique running process.

This patch extends openvpnserv.exe to support multiple service instances
in parallel allowing side-by-side OpenVPN installations.

Alternate instances must be installed as `SERVICE_WIN32_OWN_PROCESS`
(Type 0x10) and must use the newly introduced service command line
parameter:
-instance <name> <id>
<name> can be `automatic` or `interactive`.

- The service settings will be loaded from `HKLM\Software\OpenVPN<id>`
registry key.

- The automatic service will use `openvpn<id>_exit_1` exit event.

- The interactive service will accept requests on
`\\.\pipe\openvpn<id>\service` named pipe, and run IPC with
openvpn.exe on `\\.\pipe\openvpn<id>\service_<pid>`.

This patch preserves backward compatibility, by defaulting to
`SERVICE_WIN32_SHARE_PROCESS` and `<empty string>` as service ID.

Acked-by: Selva Nair <selva.nair@gmail.com>
Message-Id: <20171203211654.1044-1-simon@rozman.si>
URL: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg16002.html

Signed-off-by: Gert Doering <gert@greenie.muc.de>
(cherry picked from commit f3fec49b1c916a701058ef2445b4c07005c30673)

Simon Rozman authored on 2017/12/04 06:16:54
Showing 5 changed files
... ...
@@ -44,7 +44,7 @@
44 44
 #define false 0
45 45
 
46 46
 static SERVICE_STATUS_HANDLE service;
47
-static SERVICE_STATUS status;
47
+static SERVICE_STATUS status = { .dwServiceType = SERVICE_WIN32_SHARE_PROCESS };
48 48
 
49 49
 openvpn_service_t automatic_service = {
50 50
     automatic,
... ...
@@ -60,12 +60,6 @@ struct security_attributes
60 60
     SECURITY_DESCRIPTOR sd;
61 61
 };
62 62
 
63
-/*
64
- * Which registry key in HKLM should
65
- * we get config info from?
66
- */
67
-#define REG_KEY "SOFTWARE\\" PACKAGE_NAME
68
-
69 63
 static HANDLE exit_event = NULL;
70 64
 
71 65
 /* clear an object */
... ...
@@ -91,15 +85,6 @@ init_security_attributes_allow_all(struct security_attributes *obj)
91 91
     return true;
92 92
 }
93 93
 
94
-/*
95
- * This event is initially created in the non-signaled
96
- * state.  It will transition to the signaled state when
97
- * we have received a terminate signal from the Service
98
- * Control Manager which will cause an asynchronous call
99
- * of ServiceStop below.
100
- */
101
-#define EXIT_EVENT_NAME  TEXT(PACKAGE "_exit_1")
102
-
103 94
 HANDLE
104 95
 create_event(LPCTSTR name, bool allow_all, bool initial_state, bool manual_reset)
105 96
 {
... ...
@@ -212,10 +197,19 @@ ServiceCtrlAutomatic(DWORD ctrl_code, DWORD event, LPVOID data, LPVOID ctx)
212 212
 
213 213
 
214 214
 VOID WINAPI
215
+ServiceStartAutomaticOwn(DWORD dwArgc, LPTSTR *lpszArgv)
216
+{
217
+    status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
218
+    ServiceStartAutomatic(dwArgc, lpszArgv);
219
+}
220
+
221
+
222
+VOID WINAPI
215 223
 ServiceStartAutomatic(DWORD dwArgc, LPTSTR *lpszArgv)
216 224
 {
217 225
     DWORD error = NO_ERROR;
218 226
     settings_t settings;
227
+    TCHAR event_name[256];
219 228
 
220 229
     service = RegisterServiceCtrlHandlerEx(automatic_service.name, ServiceCtrlAutomatic, &status);
221 230
     if (!service)
... ...
@@ -223,7 +217,6 @@ ServiceStartAutomatic(DWORD dwArgc, LPTSTR *lpszArgv)
223 223
         return;
224 224
     }
225 225
 
226
-    status.dwServiceType = SERVICE_WIN32_SHARE_PROCESS;
227 226
     status.dwCurrentState = SERVICE_START_PENDING;
228 227
     status.dwServiceSpecificExitCode = NO_ERROR;
229 228
     status.dwWin32ExitCode = NO_ERROR;
... ...
@@ -237,8 +230,15 @@ ServiceStartAutomatic(DWORD dwArgc, LPTSTR *lpszArgv)
237 237
 
238 238
     /*
239 239
      * Create our exit event
240
+     * This event is initially created in the non-signaled
241
+     * state.  It will transition to the signaled state when
242
+     * we have received a terminate signal from the Service
243
+     * Control Manager which will cause an asynchronous call
244
+     * of ServiceStop below.
240 245
      */
241
-    exit_event = create_event(EXIT_EVENT_NAME, false, false, true);
246
+
247
+    openvpn_sntprintf(event_name, _countof(event_name), TEXT(PACKAGE "%s_exit_1"), service_instance);
248
+    exit_event = create_event(event_name, false, false, true);
242 249
     if (!exit_event)
243 250
     {
244 251
         MsgToEventLog(M_ERR, TEXT("CreateEvent failed"));
... ...
@@ -327,8 +327,8 @@ ServiceStartAutomatic(DWORD dwArgc, LPTSTR *lpszArgv)
327 327
                                   TEXT("%s\\%s"), settings.log_dir, log_file);
328 328
 
329 329
                 /* construct command line */
330
-                openvpn_sntprintf(command_line, _countof(command_line), TEXT(PACKAGE " --service %s 1 --config \"%s\""),
331
-                                  EXIT_EVENT_NAME,
330
+                openvpn_sntprintf(command_line, _countof(command_line), TEXT("openvpn --service \"" PACKAGE "%s_exit_1\" 1 --config \"%s\""),
331
+                                  service_instance,
332 332
                                   find_obj.cFileName);
333 333
 
334 334
                 /* Make security attributes struct for logfile handle so it can
... ...
@@ -24,6 +24,9 @@
24 24
 #include "service.h"
25 25
 #include "validate.h"
26 26
 
27
+LPCTSTR service_instance = TEXT("");
28
+
29
+
27 30
 /*
28 31
  * These are necessary due to certain buggy implementations of (v)snprintf,
29 32
  * that don't guarantee null termination for size > 0.
... ...
@@ -53,8 +56,6 @@ openvpn_sntprintf(LPTSTR str, size_t size, LPCTSTR format, ...)
53 53
     return len;
54 54
 }
55 55
 
56
-#define REG_KEY  TEXT("SOFTWARE\\" PACKAGE_NAME)
57
-
58 56
 static DWORD
59 57
 GetRegString(HKEY key, LPCTSTR value, LPTSTR data, DWORD size)
60 58
 {
... ...
@@ -69,7 +70,7 @@ GetRegString(HKEY key, LPCTSTR value, LPTSTR data, DWORD size)
69 69
     if (status != ERROR_SUCCESS)
70 70
     {
71 71
         SetLastError(status);
72
-        return MsgToEventLog(M_SYSERR, TEXT("Error querying registry value: HKLM\\%s\\%s"), REG_KEY, value);
72
+        return MsgToEventLog(M_SYSERR, TEXT("Error querying registry value: HKLM\\SOFTWARE\\" PACKAGE_NAME "%s\\%s"), service_instance, value);
73 73
     }
74 74
 
75 75
     return ERROR_SUCCESS;
... ...
@@ -79,16 +80,19 @@ GetRegString(HKEY key, LPCTSTR value, LPTSTR data, DWORD size)
79 79
 DWORD
80 80
 GetOpenvpnSettings(settings_t *s)
81 81
 {
82
+    TCHAR reg_path[256];
82 83
     TCHAR priority[64];
83 84
     TCHAR append[2];
84 85
     DWORD error;
85 86
     HKEY key;
86 87
 
87
-    LONG status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, REG_KEY, 0, KEY_READ, &key);
88
+    openvpn_sntprintf(reg_path, _countof(reg_path), TEXT("SOFTWARE\\" PACKAGE_NAME "%s"), service_instance);
89
+
90
+    LONG status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, reg_path, 0, KEY_READ, &key);
88 91
     if (status != ERROR_SUCCESS)
89 92
     {
90 93
         SetLastError(status);
91
-        return MsgToEventLog(M_SYSERR, TEXT("Could not open Registry key HKLM\\%s not found"), REG_KEY);
94
+        return MsgToEventLog(M_SYSERR, TEXT("Could not open Registry key HKLM\\%s not found"), reg_path);
92 95
     }
93 96
 
94 97
     error = GetRegString(key, TEXT("exe_path"), s->exe_path, sizeof(s->exe_path));
... ...
@@ -232,7 +236,7 @@ MsgToEventLog(DWORD flags, LPCTSTR format, ...)
232 232
     if (hEventSource != NULL)
233 233
     {
234 234
         openvpn_sntprintf(msg[0], _countof(msg[0]),
235
-                          TEXT("%s%s: %s"), APPNAME,
235
+                          TEXT("%s%s%s: %s"), APPNAME, service_instance,
236 236
                           (flags & MSG_FLAGS_ERROR) ? TEXT(" error") : TEXT(""), err_msg);
237 237
 
238 238
         va_start(arglist, format);
... ...
@@ -53,7 +53,7 @@
53 53
 #define ERROR_MESSAGE_TYPE     0x20000003
54 54
 
55 55
 static SERVICE_STATUS_HANDLE service;
56
-static SERVICE_STATUS status;
56
+static SERVICE_STATUS status = { .dwServiceType = SERVICE_WIN32_SHARE_PROCESS };
57 57
 static HANDLE exit_event = NULL;
58 58
 static settings_t settings;
59 59
 static HANDLE rdns_semaphore = NULL;
... ...
@@ -1325,7 +1325,7 @@ RunOpenvpn(LPVOID p)
1325 1325
     STARTUPINFOW startup_info;
1326 1326
     PROCESS_INFORMATION proc_info;
1327 1327
     LPVOID user_env = NULL;
1328
-    TCHAR ovpn_pipe_name[36];
1328
+    TCHAR ovpn_pipe_name[256]; /* The entire pipe name string can be up to 256 characters long according to MSDN. */
1329 1329
     LPCWSTR exe_path;
1330 1330
     WCHAR *cmdline = NULL;
1331 1331
     size_t cmdline_size;
... ...
@@ -1487,7 +1487,7 @@ RunOpenvpn(LPVOID p)
1487 1487
     }
1488 1488
 
1489 1489
     openvpn_sntprintf(ovpn_pipe_name, _countof(ovpn_pipe_name),
1490
-                      TEXT("\\\\.\\pipe\\openvpn\\service_%lu"), GetCurrentThreadId());
1490
+                      TEXT("\\\\.\\pipe\\" PACKAGE "%s\\service_%lu"), service_instance, GetCurrentThreadId());
1491 1491
     ovpn_pipe = CreateNamedPipe(ovpn_pipe_name,
1492 1492
                                 PIPE_ACCESS_DUPLEX | FILE_FLAG_FIRST_PIPE_INSTANCE | FILE_FLAG_OVERLAPPED,
1493 1493
                                 PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, 1, 128, 128, 0, NULL);
... ...
@@ -1654,6 +1654,7 @@ ServiceCtrlInteractive(DWORD ctrl_code, DWORD event, LPVOID data, LPVOID ctx)
1654 1654
 static HANDLE
1655 1655
 CreateClientPipeInstance(VOID)
1656 1656
 {
1657
+    TCHAR pipe_name[256]; /* The entire pipe name string can be up to 256 characters long according to MSDN. */
1657 1658
     HANDLE pipe = NULL;
1658 1659
     PACL old_dacl, new_dacl;
1659 1660
     PSECURITY_DESCRIPTOR sd;
... ...
@@ -1690,7 +1691,8 @@ CreateClientPipeInstance(VOID)
1690 1690
         initialized = TRUE;
1691 1691
     }
1692 1692
 
1693
-    pipe = CreateNamedPipe(TEXT("\\\\.\\pipe\\openvpn\\service"), flags,
1693
+    openvpn_sntprintf(pipe_name, _countof(pipe_name), TEXT("\\\\.\\pipe\\" PACKAGE "%s\\service"), service_instance);
1694
+    pipe = CreateNamedPipe(pipe_name, flags,
1694 1695
                            PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE,
1695 1696
                            PIPE_UNLIMITED_INSTANCES, 1024, 1024, 0, NULL);
1696 1697
     if (pipe == INVALID_HANDLE_VALUE)
... ...
@@ -1785,6 +1787,15 @@ CmpHandle(LPVOID item, LPVOID hnd)
1785 1785
     return item == hnd;
1786 1786
 }
1787 1787
 
1788
+
1789
+VOID WINAPI
1790
+ServiceStartInteractiveOwn(DWORD dwArgc, LPTSTR *lpszArgv)
1791
+{
1792
+    status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
1793
+    ServiceStartInteractive(dwArgc, lpszArgv);
1794
+}
1795
+
1796
+
1788 1797
 VOID WINAPI
1789 1798
 ServiceStartInteractive(DWORD dwArgc, LPTSTR *lpszArgv)
1790 1799
 {
... ...
@@ -1801,7 +1812,6 @@ ServiceStartInteractive(DWORD dwArgc, LPTSTR *lpszArgv)
1801 1801
         return;
1802 1802
     }
1803 1803
 
1804
-    status.dwServiceType = SERVICE_WIN32_SHARE_PROCESS;
1805 1804
     status.dwCurrentState = SERVICE_START_PENDING;
1806 1805
     status.dwServiceSpecificExitCode = NO_ERROR;
1807 1806
     status.dwWin32ExitCode = NO_ERROR;
... ...
@@ -223,46 +223,81 @@ out:
223 223
 int
224 224
 _tmain(int argc, TCHAR *argv[])
225 225
 {
226
-    SERVICE_TABLE_ENTRY dispatchTable[] = {
226
+    /*
227
+     * Automatic + Interactive service (as a SERVICE_WIN32_SHARE_PROCESS)
228
+     * This is the default.
229
+     */
230
+    const SERVICE_TABLE_ENTRY dispatchTable_shared[] = {
227 231
         { automatic_service.name, ServiceStartAutomatic },
228 232
         { interactive_service.name, ServiceStartInteractive },
229 233
         { NULL, NULL }
230 234
     };
231 235
 
236
+    /* Automatic service only (as a SERVICE_WIN32_OWN_PROCESS) */
237
+    const SERVICE_TABLE_ENTRY dispatchTable_automatic[] = {
238
+        { TEXT(""), ServiceStartAutomaticOwn },
239
+        { NULL, NULL }
240
+    };
241
+
242
+    /* Interactive service only (as a SERVICE_WIN32_OWN_PROCESS) */
243
+    const SERVICE_TABLE_ENTRY dispatchTable_interactive[] = {
244
+        { TEXT(""), ServiceStartInteractiveOwn },
245
+        { NULL, NULL }
246
+    };
247
+
248
+    const SERVICE_TABLE_ENTRY *dispatchTable = dispatchTable_shared;
249
+
232 250
     openvpn_service[0] = automatic_service;
233 251
     openvpn_service[1] = interactive_service;
234 252
 
235
-    if (argc > 1 && (*argv[1] == TEXT('-') || *argv[1] == TEXT('/')))
253
+    for (int i = 1; i < argc; i++)
236 254
     {
237
-        if (_tcsicmp(TEXT("install"), argv[1] + 1) == 0)
238
-        {
239
-            return CmdInstallServices();
240
-        }
241
-        else if (_tcsicmp(TEXT("remove"), argv[1] + 1) == 0)
242
-        {
243
-            return CmdRemoveServices();
244
-        }
245
-        else if (_tcsicmp(TEXT("start"), argv[1] + 1) == 0)
246
-        {
247
-            BOOL is_auto = argc < 3 || _tcsicmp(TEXT("interactive"), argv[2]) != 0;
248
-            return CmdStartService(is_auto ? automatic : interactive);
249
-        }
250
-        else
255
+        if (*argv[i] == TEXT('-') || *argv[i] == TEXT('/'))
251 256
         {
252
-            goto dispatch;
253
-        }
257
+            if (_tcsicmp(TEXT("install"), argv[i] + 1) == 0)
258
+            {
259
+                return CmdInstallServices();
260
+            }
261
+            else if (_tcsicmp(TEXT("remove"), argv[i] + 1) == 0)
262
+            {
263
+                return CmdRemoveServices();
264
+            }
265
+            else if (_tcsicmp(TEXT("start"), argv[i] + 1) == 0)
266
+            {
267
+                BOOL is_auto = argc < i + 2 || _tcsicmp(TEXT("interactive"), argv[i + 1]) != 0;
268
+                return CmdStartService(is_auto ? automatic : interactive);
269
+            }
270
+            else if (argc > i + 2 && _tcsicmp(TEXT("instance"), argv[i] + 1) == 0)
271
+            {
272
+                dispatchTable = _tcsicmp(TEXT("interactive"), argv[i + 1]) != 0 ?
273
+                    dispatchTable_automatic :
274
+                    dispatchTable_interactive;
254 275
 
255
-        return 0;
276
+                service_instance = argv[i + 2];
277
+                i += 2;
278
+            }
279
+            else
280
+            {
281
+                _tprintf(TEXT("%s -install        to install the services\n"), APPNAME);
282
+                _tprintf(TEXT("%s -start <name>   to start a service (\"automatic\" or \"interactive\")\n"), APPNAME);
283
+                _tprintf(TEXT("%s -remove         to remove the services\n"), APPNAME);
284
+
285
+                _tprintf(TEXT("\nService run-time parameters:\n"));
286
+                _tprintf(TEXT("-instance <name> <id>\n")
287
+                         TEXT("   Runs the service as an alternate instance. <name> can be \"automatic\" or\n")
288
+                         TEXT("   \"interactive\". The service settings will be loaded from\n")
289
+                         TEXT("   HKLM\\Software\\" PACKAGE_NAME "<id> registry key, and the interactive service will accept\n")
290
+                         TEXT("   requests on \\\\.\\pipe\\" PACKAGE "<id>\\service named pipe.\n"));
291
+
292
+                return 0;
293
+            }
294
+        }
256 295
     }
257 296
 
258 297
     /* If it doesn't match any of the above parameters
259 298
      * the service control manager may be starting the service
260 299
      * so we must call StartServiceCtrlDispatcher
261 300
      */
262
-dispatch:
263
-    _tprintf(TEXT("%s -install        to install the services\n"), APPNAME);
264
-    _tprintf(TEXT("%s -start <name>   to start a service (\"automatic\" or \"interactive\")\n"), APPNAME);
265
-    _tprintf(TEXT("%s -remove         to remove the services\n"), APPNAME);
266 301
     _tprintf(TEXT("\nStartServiceCtrlDispatcher being called.\n"));
267 302
     _tprintf(TEXT("This may take several seconds. Please wait.\n"));
268 303
 
... ...
@@ -73,10 +73,12 @@ typedef struct {
73 73
 
74 74
 extern openvpn_service_t automatic_service;
75 75
 extern openvpn_service_t interactive_service;
76
+extern LPCTSTR service_instance;
76 77
 
77
-
78
+VOID WINAPI ServiceStartAutomaticOwn(DWORD argc, LPTSTR *argv);
78 79
 VOID WINAPI ServiceStartAutomatic(DWORD argc, LPTSTR *argv);
79 80
 
81
+VOID WINAPI ServiceStartInteractiveOwn(DWORD argc, LPTSTR *argv);
80 82
 VOID WINAPI ServiceStartInteractive(DWORD argc, LPTSTR *argv);
81 83
 
82 84
 int openvpn_vsntprintf(LPTSTR str, size_t size, LPCTSTR format, va_list arglist);