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>

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
 {
... ...
@@ -207,10 +192,19 @@ ServiceCtrlAutomatic(DWORD ctrl_code, DWORD event, LPVOID data, LPVOID ctx)
207 207
 
208 208
 
209 209
 VOID WINAPI
210
+ServiceStartAutomaticOwn(DWORD dwArgc, LPTSTR *lpszArgv)
211
+{
212
+    status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
213
+    ServiceStartAutomatic(dwArgc, lpszArgv);
214
+}
215
+
216
+
217
+VOID WINAPI
210 218
 ServiceStartAutomatic(DWORD dwArgc, LPTSTR *lpszArgv)
211 219
 {
212 220
     DWORD error = NO_ERROR;
213 221
     settings_t settings;
222
+    TCHAR event_name[256];
214 223
 
215 224
     service = RegisterServiceCtrlHandlerEx(automatic_service.name, ServiceCtrlAutomatic, &status);
216 225
     if (!service)
... ...
@@ -218,7 +212,6 @@ ServiceStartAutomatic(DWORD dwArgc, LPTSTR *lpszArgv)
218 218
         return;
219 219
     }
220 220
 
221
-    status.dwServiceType = SERVICE_WIN32_SHARE_PROCESS;
222 221
     status.dwCurrentState = SERVICE_START_PENDING;
223 222
     status.dwServiceSpecificExitCode = NO_ERROR;
224 223
     status.dwWin32ExitCode = NO_ERROR;
... ...
@@ -232,8 +225,15 @@ ServiceStartAutomatic(DWORD dwArgc, LPTSTR *lpszArgv)
232 232
 
233 233
     /*
234 234
      * Create our exit event
235
+     * This event is initially created in the non-signaled
236
+     * state.  It will transition to the signaled state when
237
+     * we have received a terminate signal from the Service
238
+     * Control Manager which will cause an asynchronous call
239
+     * of ServiceStop below.
235 240
      */
236
-    exit_event = create_event(EXIT_EVENT_NAME, false, false, true);
241
+
242
+    openvpn_sntprintf(event_name, _countof(event_name), TEXT(PACKAGE "%s_exit_1"), service_instance);
243
+    exit_event = create_event(event_name, false, false, true);
237 244
     if (!exit_event)
238 245
     {
239 246
         MsgToEventLog(M_ERR, TEXT("CreateEvent failed"));
... ...
@@ -322,8 +322,8 @@ ServiceStartAutomatic(DWORD dwArgc, LPTSTR *lpszArgv)
322 322
                                   TEXT("%s\\%s"), settings.log_dir, log_file);
323 323
 
324 324
                 /* construct command line */
325
-                openvpn_sntprintf(command_line, _countof(command_line), TEXT(PACKAGE " --service %s 1 --config \"%s\""),
326
-                                  EXIT_EVENT_NAME,
325
+                openvpn_sntprintf(command_line, _countof(command_line), TEXT("openvpn --service \"" PACKAGE "%s_exit_1\" 1 --config \"%s\""),
326
+                                  service_instance,
327 327
                                   find_obj.cFileName);
328 328
 
329 329
                 /* 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);