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>
... | ... |
@@ -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); |