OpenVPN
interactive.c
Go to the documentation of this file.
1/*
2 * OpenVPN -- An application to securely tunnel IP networks
3 * over a single TCP/UDP port, with support for SSL/TLS-based
4 * session authentication and key exchange,
5 * packet encryption, packet authentication, and
6 * packet compression.
7 *
8 * Copyright (C) 2012-2026 Heiko Hund <heiko.hund@sophos.com>
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License version 2
12 * as published by the Free Software Foundation.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License along
20 * with this program; if not, see <https://www.gnu.org/licenses/>.
21 */
22
23
24#include "service.h"
25
26#include <ws2tcpip.h>
27#include <iphlpapi.h>
28#include <userenv.h>
29#include <accctrl.h>
30#include <aclapi.h>
31#include <stdio.h>
32#include <sddl.h>
33#include <shellapi.h>
34#include <mstcpip.h>
35#include <inttypes.h>
36#include <malloc.h>
37
38#include <versionhelpers.h>
39
40#include "openvpn-msg.h"
41#include "validate.h"
42#include "wfp_block.h"
43
44#define IO_TIMEOUT 2000 /*ms*/
45
46#define ERROR_OPENVPN_STARTUP 0x20000000
47#define ERROR_STARTUP_DATA 0x20000001
48#define ERROR_MESSAGE_DATA 0x20000002
49#define ERROR_MESSAGE_TYPE 0x20000003
50
51static SERVICE_STATUS_HANDLE service;
52static SERVICE_STATUS status = { .dwServiceType = SERVICE_WIN32_SHARE_PROCESS };
53static HANDLE exit_event = NULL;
55static HANDLE rdns_semaphore = NULL;
56#define RDNS_TIMEOUT 600 /* seconds to wait for the semaphore */
57
58#define TUN_IOCTL_REGISTER_RINGS \
59 CTL_CODE(51820U, 0x970U, METHOD_BUFFERED, FILE_READ_DATA | FILE_WRITE_DATA)
60
62 _L(PACKAGE_NAME) L" Interactive Service",
63 SERVICE_DEPENDENCIES, SERVICE_AUTO_START };
64
65
66typedef struct
67{
68 WCHAR *directory;
69 WCHAR *options;
70 WCHAR *std_input;
72
73
74/* Datatype for linked lists */
75typedef struct _list_item
76{
78 LPVOID data;
80
81
82/* Datatypes for undo information */
96
97typedef struct
98{
99 HANDLE engine;
100 DWORD index;
104
105typedef struct
106{
107 char itf_name[256];
108 PWSTR domains;
110
125
126typedef struct
127{
128 CHAR addresses[NRPT_ADDR_NUM * NRPT_ADDR_SIZE];
129 WCHAR domains[512]; /* MULTI_SZ string */
130 DWORD domains_size; /* bytes in domains */
132
133
134static DWORD
135AddListItem(list_item_t **pfirst, LPVOID data)
136{
137 list_item_t *new_item = malloc(sizeof(list_item_t));
138 if (new_item == NULL)
139 {
140 return ERROR_OUTOFMEMORY;
141 }
142
143 new_item->next = *pfirst;
144 new_item->data = data;
145
146 *pfirst = new_item;
147 return NO_ERROR;
148}
149
150typedef BOOL (*match_fn_t)(LPVOID item, LPVOID ctx);
151
152static LPVOID
153RemoveListItem(list_item_t **pfirst, match_fn_t match, LPVOID ctx)
154{
155 LPVOID data = NULL;
156 list_item_t **pnext;
157
158 for (pnext = pfirst; *pnext; pnext = &(*pnext)->next)
159 {
160 list_item_t *item = *pnext;
161 if (!match(item->data, ctx))
162 {
163 continue;
164 }
165
166 /* Found item, remove from the list and free memory */
167 *pnext = item->next;
168 data = item->data;
169 free(item);
170 break;
171 }
172 return data;
173}
174
175
176static HANDLE
177CloseHandleEx(LPHANDLE handle)
178{
179 if (handle && *handle && *handle != INVALID_HANDLE_VALUE)
180 {
181 CloseHandle(*handle);
182 *handle = INVALID_HANDLE_VALUE;
183 }
184 return INVALID_HANDLE_VALUE;
185}
186
187static HANDLE
188InitOverlapped(LPOVERLAPPED overlapped)
189{
190 ZeroMemory(overlapped, sizeof(OVERLAPPED));
191 overlapped->hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
192 return overlapped->hEvent;
193}
194
195static BOOL
196ResetOverlapped(LPOVERLAPPED overlapped)
197{
198 HANDLE io_event = overlapped->hEvent;
199 if (!ResetEvent(io_event))
200 {
201 return FALSE;
202 }
203 ZeroMemory(overlapped, sizeof(OVERLAPPED));
204 overlapped->hEvent = io_event;
205 return TRUE;
206}
207
208
216
217static DWORD
218AsyncPipeOp(async_op_t op, HANDLE pipe, LPVOID buffer, DWORD size, DWORD count, LPHANDLE events)
219{
220 DWORD i;
221 BOOL success;
222 HANDLE io_event;
223 DWORD res, bytes = 0;
224 OVERLAPPED overlapped;
225 LPHANDLE handles = NULL;
226
227 io_event = InitOverlapped(&overlapped);
228 if (!io_event)
229 {
230 goto out;
231 }
232
233 handles = malloc((count + 1) * sizeof(HANDLE));
234 if (!handles)
235 {
236 goto out;
237 }
238
239 if (op == write)
240 {
241 success = WriteFile(pipe, buffer, size, NULL, &overlapped);
242 }
243 else
244 {
245 success = ReadFile(pipe, buffer, size, NULL, &overlapped);
246 }
247 if (!success && GetLastError() != ERROR_IO_PENDING && GetLastError() != ERROR_MORE_DATA)
248 {
249 goto out;
250 }
251
252 handles[0] = io_event;
253 for (i = 0; i < count; i++)
254 {
255 handles[i + 1] = events[i];
256 }
257
258 res = WaitForMultipleObjects(count + 1, handles, FALSE, op == peek ? INFINITE : IO_TIMEOUT);
259 if (res != WAIT_OBJECT_0)
260 {
261 CancelIo(pipe);
262 goto out;
263 }
264
265 if (op == peek || op == peek_timed)
266 {
267 PeekNamedPipe(pipe, NULL, 0, NULL, &bytes, NULL);
268 }
269 else
270 {
271 GetOverlappedResult(pipe, &overlapped, &bytes, TRUE);
272 }
273
274out:
275 CloseHandleEx(&io_event);
276 free(handles);
277 return bytes;
278}
279
280static DWORD
281PeekNamedPipeAsync(HANDLE pipe, DWORD count, LPHANDLE events)
282{
283 return AsyncPipeOp(peek, pipe, NULL, 0, count, events);
284}
285
286static DWORD
287PeekNamedPipeAsyncTimed(HANDLE pipe, DWORD count, LPHANDLE events)
288{
289 return AsyncPipeOp(peek_timed, pipe, NULL, 0, count, events);
290}
291
292static DWORD
293ReadPipeAsync(HANDLE pipe, LPVOID buffer, DWORD size, DWORD count, LPHANDLE events)
294{
295 return AsyncPipeOp(read, pipe, buffer, size, count, events);
296}
297
298static DWORD
299WritePipeAsync(HANDLE pipe, LPVOID data, DWORD size, DWORD count, LPHANDLE events)
300{
301 return AsyncPipeOp(write, pipe, data, size, count, events);
302}
303
304static VOID
305ReturnProcessId(HANDLE pipe, DWORD pid, DWORD count, LPHANDLE events)
306{
307 const WCHAR msg[] = L"Process ID";
308 WCHAR buf[22 + _countof(msg)]; /* 10 chars each for error and PID and 2 for line breaks */
309
310 /*
311 * Same format as error messages (3 line string) with error = 0 in
312 * 0x%08x format, PID on line 2 and a description "Process ID" on line 3
313 */
314 swprintf(buf, _countof(buf), L"0x%08x\n0x%08lx\n%ls", 0, pid, msg);
315
316 WritePipeAsync(pipe, buf, (DWORD)(wcslen(buf) * 2), count, events);
317}
318
319static VOID
320ReturnError(HANDLE pipe, DWORD error, LPCWSTR func, DWORD count, LPHANDLE events)
321{
322 DWORD result_len;
323 LPWSTR result = L"0xffffffff\nFormatMessage failed\nCould not return result";
324 DWORD_PTR args[] = { (DWORD_PTR)error, (DWORD_PTR)func, (DWORD_PTR) "" };
325
326 if (error != ERROR_OPENVPN_STARTUP)
327 {
328 FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER
329 | FORMAT_MESSAGE_IGNORE_INSERTS,
330 0, error, 0, (LPWSTR)&args[2], 0, NULL);
331 }
332
333 result_len = FormatMessageW(
334 FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_ARGUMENT_ARRAY,
335 L"0x%1!08x!\n%2!s!\n%3!s!", 0, 0, (LPWSTR)&result, 0, (va_list *)args);
336
337 WritePipeAsync(pipe, result, (DWORD)(wcslen(result) * 2), count, events);
339
340 if (error != ERROR_OPENVPN_STARTUP)
341 {
342 LocalFree((LPVOID)args[2]);
343 }
344 if (result_len)
345 {
346 LocalFree(result);
347 }
348}
349
350
351static VOID
352ReturnLastError(HANDLE pipe, LPCWSTR func)
353{
354 ReturnError(pipe, GetLastError(), func, 1, &exit_event);
355}
356
357/*
358 * Validate options against a white list. Also check the config_file is
359 * inside the config_dir. The white list is defined in validate.c
360 * The options are taken from the command line openvpn is started with,
361 * so that they are split exactly like openvpn.exe does it.
362 * Returns true on success, false on error with reason set in errmsg.
363 */
364static BOOL
365ValidateOptions(HANDLE pipe, const WCHAR *workdir, const WCHAR *cmdline, WCHAR *errmsg,
366 DWORD capacity)
367{
368 WCHAR **argv;
369 int argc;
370 BOOL ret = FALSE;
371 int i;
372 const WCHAR *msg1 = L"You have specified a config file location (%ls relative to %ls)"
373 L" that requires admin approval. This error may be avoided"
374 L" by adding your account to the \"%ls\" group";
375
376 const WCHAR *msg2 = L"You have specified an option (%ls) that may be used"
377 L" only with admin approval. This error may be avoided"
378 L" by adding your account to the \"%ls\" group";
379
380 /* Skip "openvpn --msg-channel <handle>" added by the service */
381 const int first = 3;
382
383 argv = CommandLineToArgvW(cmdline, &argc);
384
385 if (!argv)
386 {
387 swprintf(errmsg, capacity,
388 L"Cannot validate options: CommandLineToArgvW failed with error = 0x%08lx",
389 GetLastError());
390 goto out;
391 }
392
393 if (argc <= first) /* no options */
394 {
395 ret = TRUE;
396 goto out;
397 }
398
399 /*
400 * If only one argument, it is the config file
401 */
402 if (argc == first + 1)
403 {
404 WCHAR *argv_tmp[2] = { L"--config", argv[first] };
405
406 if (!CheckOption(workdir, 2, argv_tmp, &settings))
407 {
408 swprintf(errmsg, capacity, msg1, argv[first], workdir, settings.ovpn_admin_group);
409 }
410 goto out;
411 }
412
413 for (i = first; i < argc; ++i)
414 {
415 if (!IsOption(argv[i]))
416 {
417 continue;
418 }
419
420 if (!CheckOption(workdir, argc - i, &argv[i], &settings))
421 {
422 if (wcscmp(L"--config", argv[i]) == 0 && argc - i > 1)
423 {
424 swprintf(errmsg, capacity, msg1, argv[i + 1], workdir, settings.ovpn_admin_group);
425 }
426 else
427 {
428 swprintf(errmsg, capacity, msg2, argv[i], settings.ovpn_admin_group);
429 }
430 goto out;
431 }
432 }
433
434 /* all options passed */
435 ret = TRUE;
436
437out:
438 if (argv)
439 {
440 LocalFree(argv);
441 }
442 return ret;
443}
444
445static BOOL
446GetStartupData(HANDLE pipe, STARTUP_DATA *sud)
447{
448 size_t size, len;
449 WCHAR *data = NULL;
450 DWORD bytes, read;
451
452 bytes = PeekNamedPipeAsyncTimed(pipe, 1, &exit_event);
453 if (bytes == 0)
454 {
455 MsgToEventLog(M_ERR, L"Timeout waiting for startup data");
456 ReturnError(pipe, ERROR_STARTUP_DATA, L"GetStartupData (timeout)", 1, &exit_event);
457 goto err;
458 }
459
460 size = bytes / sizeof(*data);
461 if ((size == 0) || (size > 4096)) /* our startup data is 1024 wchars at the moment */
462 {
463 MsgToEventLog(M_SYSERR, L"malformed startup data: %lu bytes received", size);
464 ReturnError(pipe, ERROR_STARTUP_DATA, L"GetStartupData", 1, &exit_event);
465 goto err;
466 }
467
468 data = malloc(bytes);
469 if (data == NULL)
470 {
471 MsgToEventLog(M_SYSERR, L"malloc failed");
472 ReturnLastError(pipe, L"malloc");
473 goto err;
474 }
475
476 read = ReadPipeAsync(pipe, data, bytes, 1, &exit_event);
477 if (bytes != read)
478 {
479 MsgToEventLog(M_SYSERR, L"ReadPipeAsync failed");
480 ReturnLastError(pipe, L"ReadPipeAsync");
481 goto err;
482 }
483
484 if (data[size - 1] != 0)
485 {
486 MsgToEventLog(M_ERR, L"Startup data is not NULL terminated");
487 ReturnError(pipe, ERROR_STARTUP_DATA, L"GetStartupData", 1, &exit_event);
488 goto err;
489 }
490
491 sud->directory = data;
492 len = wcslen(sud->directory) + 1;
493 size -= len;
494 if (size == 0)
495 {
496 MsgToEventLog(M_ERR, L"Startup data ends at working directory");
497 ReturnError(pipe, ERROR_STARTUP_DATA, L"GetStartupData", 1, &exit_event);
498 goto err;
499 }
500
501 sud->options = sud->directory + len;
502 len = wcslen(sud->options) + 1;
503 size -= len;
504 if (size == 0)
505 {
506 MsgToEventLog(M_ERR, L"Startup data ends at command line options");
507 ReturnError(pipe, ERROR_STARTUP_DATA, L"GetStartupData", 1, &exit_event);
508 goto err;
509 }
510
511 sud->std_input = sud->options + len;
512 return TRUE;
513
514err:
515 sud->directory = NULL; /* caller must not free() */
516 free(data);
517 return FALSE;
518}
519
520
521static VOID
523{
524 free(sud->directory);
525}
526
527
528static SOCKADDR_INET
529sockaddr_inet(short family, const inet_address_t *addr)
530{
531 SOCKADDR_INET sa_inet;
532 ZeroMemory(&sa_inet, sizeof(sa_inet));
533 sa_inet.si_family = family;
534 if (family == AF_INET)
535 {
536 sa_inet.Ipv4.sin_addr = addr->ipv4;
537 }
538 else if (family == AF_INET6)
539 {
540 sa_inet.Ipv6.sin6_addr = addr->ipv6;
541 }
542 return sa_inet;
543}
544
545static DWORD
546InterfaceLuid(const char *iface_name, PNET_LUID luid)
547{
548 NETIO_STATUS convert_status;
549 LPWSTR wide_name = utf8to16(iface_name);
550
551 if (wide_name)
552 {
553 convert_status = ConvertInterfaceAliasToLuid(wide_name, luid);
554 free(wide_name);
555 }
556 else
557 {
558 convert_status = ERROR_OUTOFMEMORY;
559 }
560 return convert_status;
561}
562
563static BOOL
564CmpAddress(LPVOID item, LPVOID address)
565{
566 return memcmp(item, address, sizeof(MIB_UNICASTIPADDRESS_ROW)) == 0 ? TRUE : FALSE;
567}
568
569static DWORD
570DeleteAddress(PMIB_UNICASTIPADDRESS_ROW addr_row)
571{
572 return DeleteUnicastIpAddressEntry(addr_row);
573}
574
575static DWORD
577{
578 DWORD err;
579 PMIB_UNICASTIPADDRESS_ROW addr_row;
580 BOOL add = msg->header.type == msg_add_address;
581
582 /* enforce null-termination of the interface name from client */
583 msg->iface.name[_countof(msg->iface.name) - 1] = '\0';
584
585 addr_row = malloc(sizeof(*addr_row));
586 if (addr_row == NULL)
587 {
588 return ERROR_OUTOFMEMORY;
589 }
590
591 InitializeUnicastIpAddressEntry(addr_row);
592 addr_row->Address = sockaddr_inet(msg->family, &msg->address);
593 addr_row->OnLinkPrefixLength = (UINT8)msg->prefix_len;
594
595 if (msg->iface.index != TUN_ADAPTER_INDEX_INVALID)
596 {
597 addr_row->InterfaceIndex = msg->iface.index;
598 }
599 else
600 {
601 NET_LUID luid;
602 err = InterfaceLuid(msg->iface.name, &luid);
603 if (err)
604 {
605 goto out;
606 }
607 addr_row->InterfaceLuid = luid;
608 }
609
610 if (add)
611 {
612 err = CreateUnicastIpAddressEntry(addr_row);
613 if (err)
614 {
615 goto out;
616 }
617
618 err = AddListItem(&(*lists)[address], addr_row);
619 if (err)
620 {
621 DeleteAddress(addr_row);
622 }
623 }
624 else
625 {
626 err = DeleteAddress(addr_row);
627 if (err)
628 {
629 goto out;
630 }
631
632 free(RemoveListItem(&(*lists)[address], CmpAddress, addr_row));
633 }
634
635out:
636 if (!add || err)
637 {
638 free(addr_row);
639 }
640
641 return err;
642}
643
644static BOOL
645CmpRoute(LPVOID item, LPVOID route)
646{
647 return memcmp(item, route, sizeof(MIB_IPFORWARD_ROW2)) == 0 ? TRUE : FALSE;
648}
649
650static DWORD
651DeleteRoute(PMIB_IPFORWARD_ROW2 fwd_row)
652{
653 return DeleteIpForwardEntry2(fwd_row);
654}
655
656static DWORD
658{
659 DWORD err;
660 PMIB_IPFORWARD_ROW2 fwd_row;
661 BOOL add = msg->header.type == msg_add_route;
662
663 /* enforce null-termination of the interface name from client */
664 msg->iface.name[_countof(msg->iface.name) - 1] = '\0';
665
666 fwd_row = malloc(sizeof(*fwd_row));
667 if (fwd_row == NULL)
668 {
669 return ERROR_OUTOFMEMORY;
670 }
671
672 ZeroMemory(fwd_row, sizeof(*fwd_row));
673 fwd_row->ValidLifetime = 0xffffffff;
674 fwd_row->PreferredLifetime = 0xffffffff;
675 fwd_row->Protocol = MIB_IPPROTO_NETMGMT;
676 fwd_row->Metric = msg->metric;
677 fwd_row->DestinationPrefix.Prefix = sockaddr_inet(msg->family, &msg->prefix);
678 fwd_row->DestinationPrefix.PrefixLength = (UINT8)msg->prefix_len;
679 fwd_row->NextHop = sockaddr_inet(msg->family, &msg->gateway);
680
681 if (msg->iface.index != TUN_ADAPTER_INDEX_INVALID)
682 {
683 fwd_row->InterfaceIndex = msg->iface.index;
684 }
685 else if (strlen(msg->iface.name))
686 {
687 NET_LUID luid;
688 err = InterfaceLuid(msg->iface.name, &luid);
689 if (err)
690 {
691 goto out;
692 }
693 fwd_row->InterfaceLuid = luid;
694 }
695
696 if (add)
697 {
698 err = CreateIpForwardEntry2(fwd_row);
699 if (err)
700 {
701 goto out;
702 }
703
704 err = AddListItem(&(*lists)[route], fwd_row);
705 if (err)
706 {
707 DeleteRoute(fwd_row);
708 }
709 }
710 else
711 {
712 err = DeleteRoute(fwd_row);
713 if (err)
714 {
715 goto out;
716 }
717
718 free(RemoveListItem(&(*lists)[route], CmpRoute, fwd_row));
719 }
720
721out:
722 if (!add || err)
723 {
724 free(fwd_row);
725 }
726
727 return err;
728}
729
730
731static DWORD
733{
734 if (msg->family == AF_INET)
735 {
736 return FlushIpNetTable(msg->iface.index);
737 }
738
739 return FlushIpNetTable2(msg->family, msg->iface.index);
740}
741
742static void
743BlockDNSErrHandler(DWORD err, const char *msg)
744{
745 WCHAR buf[256];
746 LPCWSTR err_str;
747
748 if (!err)
749 {
750 return;
751 }
752
753 err_str = L"Unknown Win32 Error";
754
755 if (FormatMessageW(FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM,
756 NULL, err, 0, buf, _countof(buf), NULL))
757 {
758 err_str = buf;
759 }
760
761 MsgToEventLog(M_ERR, L"%hs (status = %lu): %ls", msg, err, err_str);
762}
763
764/* Use an always-true match_fn to get the head of the list */
765static BOOL
766CmpAny(LPVOID item, LPVOID any)
767{
768 return TRUE;
769}
770
771static DWORD
773{
774 DWORD err = 0;
775 wfp_block_data_t *block_data = RemoveListItem(&(*lists)[wfp_block], CmpAny, NULL);
776
777 if (block_data)
778 {
779 err = delete_wfp_block_filters(block_data->engine);
780 if (block_data->metric_v4 >= 0)
781 {
782 set_interface_metric(block_data->index, AF_INET, block_data->metric_v4);
783 }
784 if (block_data->metric_v6 >= 0)
785 {
786 set_interface_metric(block_data->index, AF_INET6, block_data->metric_v6);
787 }
788 free(block_data);
789 }
790 else
791 {
792 MsgToEventLog(M_ERR, L"No previous block filters to delete");
793 }
794
795 return err;
796}
797
798static DWORD
800{
801 DWORD err = 0;
802 wfp_block_data_t *block_data = NULL;
803 HANDLE engine = NULL;
804 LPCWSTR exe_path;
805 BOOL dns_only;
806
807 exe_path = settings.exe_path;
808 dns_only = (msg->flags == wfp_block_dns);
809
810 err = add_wfp_block_filters(&engine, msg->iface.index, exe_path, BlockDNSErrHandler, dns_only);
811 if (!err)
812 {
813 block_data = malloc(sizeof(wfp_block_data_t));
814 if (!block_data)
815 {
816 err = ERROR_OUTOFMEMORY;
817 goto out;
818 }
819 block_data->engine = engine;
820 block_data->index = msg->iface.index;
821 int is_auto = 0;
822 block_data->metric_v4 = get_interface_metric(msg->iface.index, AF_INET, &is_auto);
823 if (is_auto)
824 {
825 block_data->metric_v4 = 0;
826 }
827 block_data->metric_v6 = get_interface_metric(msg->iface.index, AF_INET6, &is_auto);
828 if (is_auto)
829 {
830 block_data->metric_v6 = 0;
831 }
832
833 err = AddListItem(&(*lists)[wfp_block], block_data);
834 if (!err)
835 {
836 err = set_interface_metric(msg->iface.index, AF_INET, WFP_BLOCK_IFACE_METRIC);
837 if (!err)
838 {
839 /* for IPv6, we intentionally ignore errors, because
840 * otherwise block-dns activation will fail if a user or
841 * admin has disabled IPv6 on the tun/tap/dco interface
842 * (if OpenVPN wants IPv6 ifconfig, we'll fail there)
843 */
844 set_interface_metric(msg->iface.index, AF_INET6, WFP_BLOCK_IFACE_METRIC);
845 }
846 if (err)
847 {
848 /* delete the filters, remove undo item and free interface data */
849 DeleteWfpBlock(lists);
850 engine = NULL;
851 }
852 }
853 }
854
855out:
856 if (err && engine)
857 {
859 free(block_data);
860 }
861
862 return err;
863}
864
865static DWORD
867{
868 if (msg->header.type == msg_add_wfp_block)
869 {
870 return AddWfpBlock(msg, lists);
871 }
872 else
873 {
874 return DeleteWfpBlock(lists);
875 }
876}
877
878/*
879 * Execute a command and return its exit code. If timeout > 0, terminate
880 * the process if still running after timeout milliseconds. In that case
881 * the return value is the windows error code WAIT_TIMEOUT = 0x102
882 */
883static DWORD
884ExecCommand(const WCHAR *argv0, const WCHAR *cmdline, DWORD timeout)
885{
886 DWORD exit_code;
887 STARTUPINFOW si;
888 PROCESS_INFORMATION pi;
889 DWORD proc_flags = CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT;
890 WCHAR *cmdline_dup = NULL;
891
892 ZeroMemory(&si, sizeof(si));
893 ZeroMemory(&pi, sizeof(pi));
894
895 si.cb = sizeof(si);
896
897 /* CreateProcess needs a modifiable cmdline: make a copy */
898 cmdline_dup = _wcsdup(cmdline);
899 if (cmdline_dup
900 && CreateProcessW(argv0, cmdline_dup, NULL, NULL, FALSE, proc_flags, NULL, NULL, &si, &pi))
901 {
902 WaitForSingleObject(pi.hProcess, timeout ? timeout : INFINITE);
903 if (!GetExitCodeProcess(pi.hProcess, &exit_code))
904 {
905 MsgToEventLog(M_SYSERR, L"ExecCommand: Error getting exit_code:");
906 exit_code = GetLastError();
907 }
908 else if (exit_code == STILL_ACTIVE)
909 {
910 exit_code = WAIT_TIMEOUT; /* Windows error code 0x102 */
911
912 /* kill without impunity */
913 TerminateProcess(pi.hProcess, exit_code);
914 MsgToEventLog(M_ERR, L"ExecCommand: \"%ls %ls\" killed after timeout", argv0, cmdline);
915 }
916 else if (exit_code)
917 {
918 MsgToEventLog(M_ERR, L"ExecCommand: \"%ls %ls\" exited with status = %lu", argv0,
919 cmdline, exit_code);
920 }
921 else
922 {
923 MsgToEventLog(M_INFO, L"ExecCommand: \"%ls %ls\" completed", argv0, cmdline);
924 }
925
926 CloseHandle(pi.hProcess);
927 CloseHandle(pi.hThread);
928 }
929 else
930 {
931 exit_code = GetLastError();
932 MsgToEventLog(M_SYSERR, L"ExecCommand: could not run \"%ls %ls\" :", argv0, cmdline);
933 }
934
935 free(cmdline_dup);
936 return exit_code;
937}
938
939/*
940 * Entry point for register-dns thread.
941 */
942static DWORD WINAPI
943RegisterDNS(LPVOID unused)
944{
945 DWORD err;
946 DWORD timeout = RDNS_TIMEOUT * 1000; /* in milliseconds */
947
948 /* path of ipconfig command */
949 WCHAR ipcfg[MAX_PATH];
950
951 struct
952 {
953 WCHAR *argv0;
954 WCHAR *cmdline;
955 DWORD timeout;
956 } cmds[] = {
957 { ipcfg, L"ipconfig /flushdns", timeout },
958 { ipcfg, L"ipconfig /registerdns", timeout },
959 };
960
961 HANDLE wait_handles[2] = { rdns_semaphore, exit_event };
962
963 swprintf(ipcfg, MAX_PATH, L"%ls\\%ls", get_win_sys_path(), L"ipconfig.exe");
964
965 if (WaitForMultipleObjects(2, wait_handles, FALSE, timeout) == WAIT_OBJECT_0)
966 {
967 /* Semaphore locked */
968 for (size_t i = 0; i < _countof(cmds); ++i)
969 {
970 ExecCommand(cmds[i].argv0, cmds[i].cmdline, cmds[i].timeout);
971 }
972 err = 0;
973 if (!ReleaseSemaphore(rdns_semaphore, 1, NULL))
974 {
975 err =
976 MsgToEventLog(M_SYSERR, L"RegisterDNS: Failed to release regsiter-dns semaphore:");
977 }
978 }
979 else
980 {
981 MsgToEventLog(M_ERR, L"RegisterDNS: Failed to lock register-dns semaphore");
982 err = ERROR_SEM_TIMEOUT; /* Windows error code 0x79 */
983 }
984 return err;
985}
986
987static DWORD
989{
990 DWORD err;
991 HANDLE thread = NULL;
992
993 /* Delegate this job to a sub-thread */
994 thread = CreateThread(NULL, 0, RegisterDNS, NULL, 0, NULL);
995
996 /*
997 * We don't add these thread handles to the undo list -- the thread and
998 * processes it spawns are all supposed to terminate or timeout by themselves.
999 */
1000 if (thread)
1001 {
1002 err = 0;
1003 CloseHandle(thread);
1004 }
1005 else
1006 {
1007 err = GetLastError();
1008 }
1009
1010 return err;
1011}
1012
1022static DWORD
1023netsh_wins_cmd(const wchar_t *action, DWORD if_index, const wchar_t *addr)
1024{
1025 DWORD err = 0;
1026 int timeout = 30000; /* in msec */
1027 wchar_t argv0[MAX_PATH];
1028 wchar_t *cmdline = NULL;
1029 const wchar_t *addr_static = (wcscmp(action, L"set") == 0) ? L"static" : L"";
1030
1031 if (!addr)
1032 {
1033 if (wcscmp(action, L"delete") == 0)
1034 {
1035 addr = L"all";
1036 }
1037 else /* nothing to do -- return success*/
1038 {
1039 goto out;
1040 }
1041 }
1042
1043 /* Path of netsh */
1044 swprintf(argv0, _countof(argv0), L"%ls\\%ls", get_win_sys_path(), L"netsh.exe");
1045
1046 /* cmd template:
1047 * netsh interface ip $action wins $if_name $static $addr
1048 */
1049 const wchar_t *fmt = L"netsh interface ip %ls wins %lu %ls %ls";
1050
1051 /* max cmdline length in wchars -- include room for worst case and some */
1052 size_t ncmdline = wcslen(fmt) + 11 /*if_index*/ + wcslen(action) + wcslen(addr)
1053 + wcslen(addr_static) + 32 + 1;
1054 cmdline = malloc(ncmdline * sizeof(wchar_t));
1055 if (!cmdline)
1056 {
1057 err = ERROR_OUTOFMEMORY;
1058 goto out;
1059 }
1060
1061 swprintf(cmdline, ncmdline, fmt, action, if_index, addr_static, addr);
1062
1063 err = ExecCommand(argv0, cmdline, timeout);
1064
1065out:
1066 free(cmdline);
1067 return err;
1068}
1069
1076static BOOL
1078{
1079 typedef NTSTATUS(__stdcall * publish_fn_t)(DWORD StateNameLo, DWORD StateNameHi, DWORD TypeId,
1080 DWORD Buffer, DWORD Length, DWORD ExplicitScope);
1081 publish_fn_t RtlPublishWnfStateData;
1082 const DWORD WNF_GPOL_SYSTEM_CHANGES_HI = 0x0D891E2A;
1083 const DWORD WNF_GPOL_SYSTEM_CHANGES_LO = 0xA3BC0875;
1084 BOOL ret = FALSE;
1085
1086 HMODULE ntdll = LoadLibraryA("ntdll.dll");
1087 if (ntdll == NULL)
1088 {
1089 return FALSE;
1090 }
1091
1092 RtlPublishWnfStateData = (publish_fn_t)GetProcAddress(ntdll, "RtlPublishWnfStateData");
1093 if (RtlPublishWnfStateData == NULL)
1094 {
1095 goto cleanup;
1096 }
1097
1098 if (RtlPublishWnfStateData(WNF_GPOL_SYSTEM_CHANGES_LO, WNF_GPOL_SYSTEM_CHANGES_HI, 0, 0, 0, 0)
1099 != ERROR_SUCCESS)
1100 {
1101 goto cleanup;
1102 }
1103
1104 ret = TRUE;
1105cleanup:
1106 FreeLibrary(ntdll);
1107 return ret;
1108}
1109
1116static BOOL
1118{
1119 typedef NTSTATUS (*publish_fn_t)(INT64 StateName, INT64 TypeId, INT64 Buffer,
1120 unsigned int Length, INT64 ExplicitScope);
1121 publish_fn_t RtlPublishWnfStateData;
1122 const INT64 WNF_GPOL_SYSTEM_CHANGES = 0x0D891E2AA3BC0875;
1123 BOOL ret = FALSE;
1124
1125 HMODULE ntdll = LoadLibraryA("ntdll.dll");
1126 if (ntdll == NULL)
1127 {
1128 return FALSE;
1129 }
1130
1131 RtlPublishWnfStateData = (publish_fn_t)GetProcAddress(ntdll, "RtlPublishWnfStateData");
1132 if (RtlPublishWnfStateData == NULL)
1133 {
1134 goto cleanup;
1135 }
1136
1137 if (RtlPublishWnfStateData(WNF_GPOL_SYSTEM_CHANGES, 0, 0, 0, 0) != ERROR_SUCCESS)
1138 {
1139 goto cleanup;
1140 }
1141
1142 ret = TRUE;
1143cleanup:
1144 FreeLibrary(ntdll);
1145 return ret;
1146}
1147
1153static BOOL
1155{
1156 SYSTEM_INFO si;
1157 GetSystemInfo(&si);
1158 const BOOL win_32bit = si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_INTEL;
1159 return win_32bit ? ApplyGpolSettings32() : ApplyGpolSettings64();
1160}
1161
1169static BOOL
1170ApplyDnsSettings(BOOL apply_gpol)
1171{
1172 BOOL res = FALSE;
1173 SC_HANDLE scm = NULL;
1174 SC_HANDLE dnssvc = NULL;
1175
1176 if (apply_gpol && ApplyGpolSettings() == FALSE)
1177 {
1178 MsgToEventLog(M_ERR, L"%S: sending GPOL notification failed", __func__);
1179 }
1180
1181 scm = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
1182 if (scm == NULL)
1183 {
1184 MsgToEventLog(M_ERR, L"%S: OpenSCManager call failed (%lu)", __func__, GetLastError());
1185 goto out;
1186 }
1187
1188 dnssvc = OpenServiceA(scm, "Dnscache", SERVICE_PAUSE_CONTINUE);
1189 if (dnssvc == NULL)
1190 {
1191 MsgToEventLog(M_ERR, L"%S: OpenService call failed (%lu)", __func__, GetLastError());
1192 goto out;
1193 }
1194
1195 SERVICE_STATUS control_status;
1196 if (ControlService(dnssvc, SERVICE_CONTROL_PARAMCHANGE, &control_status) == 0)
1197 {
1198 MsgToEventLog(M_ERR, L"%S: ControlService call failed (%lu)", __func__, GetLastError());
1199 goto out;
1200 }
1201
1202 res = TRUE;
1203
1204out:
1205 if (dnssvc)
1206 {
1207 CloseServiceHandle(dnssvc);
1208 }
1209 if (scm)
1210 {
1211 CloseServiceHandle(scm);
1212 }
1213 return res;
1214}
1215
1225static DWORD
1226InterfaceIdString(PCSTR itf_name, PWSTR str, size_t len)
1227{
1228 DWORD err;
1229 GUID guid;
1230 NET_LUID luid;
1231 PWSTR iid_str = NULL;
1232
1233 err = InterfaceLuid(itf_name, &luid);
1234 if (err)
1235 {
1236 PWSTR wide_name = utf8to16(itf_name);
1237 MsgToEventLog(M_ERR, L"%S: failed to convert itf alias '%s'", __func__, wide_name);
1238 free(wide_name);
1239 goto out;
1240 }
1241 err = ConvertInterfaceLuidToGuid(&luid, &guid);
1242 if (err)
1243 {
1244 PWSTR wide_name = utf8to16(itf_name);
1245 MsgToEventLog(M_ERR, L"%S: Failed to convert itf '%s' LUID", __func__, wide_name);
1246 free(wide_name);
1247 goto out;
1248 }
1249
1250 if (StringFromIID(&guid, &iid_str) != S_OK)
1251 {
1252 PWSTR wide_name = utf8to16(itf_name);
1253 MsgToEventLog(M_ERR, L"%S: Failed to convert itf '%s' IID", __func__, wide_name);
1254 free(wide_name);
1255 err = ERROR_OUTOFMEMORY;
1256 goto out;
1257 }
1258 if (wcslen(iid_str) + 1 > len)
1259 {
1260 err = ERROR_INVALID_PARAMETER;
1261 goto out;
1262 }
1263
1264 wcsncpy(str, iid_str, len);
1265
1266out:
1267 if (iid_str)
1268 {
1269 CoTaskMemFree(iid_str);
1270 }
1271 return err;
1272}
1273
1287static BOOL
1289{
1290 char data[64];
1291 DWORD size = sizeof(data);
1292 LSTATUS err = RegGetValueA(key, NULL, "SearchList", RRF_RT_REG_SZ, NULL, (PBYTE)data, &size);
1293 if (!err || err == ERROR_MORE_DATA)
1294 {
1295 data[sizeof(data) - 1] = '\0';
1296 for (size_t i = 0; i < strlen(data); ++i)
1297 {
1298 if (isalnum(data[i]) || data[i] == '-' || data[i] == '.')
1299 {
1300 return TRUE;
1301 }
1302 }
1303 }
1304 return FALSE;
1305}
1306
1324static BOOL
1325GetDnsSearchListKey(PCSTR itf_name, PBOOL gpol, PHKEY key)
1326{
1327 LSTATUS err;
1328
1329 *gpol = FALSE;
1330
1331 /* Try the group policy search list */
1332 err = RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SOFTWARE\\Policies\\Microsoft\\Windows NT\\DNSClient",
1333 0, KEY_ALL_ACCESS, key);
1334 if (!err)
1335 {
1336 if (HasValidSearchList(*key))
1337 {
1338 *gpol = TRUE;
1339 return TRUE;
1340 }
1341 RegCloseKey(*key);
1342 }
1343
1344 /* Try the system-wide search list */
1345 err =
1346 RegOpenKeyExA(HKEY_LOCAL_MACHINE, "System\\CurrentControlSet\\Services\\TCPIP\\Parameters",
1347 0, KEY_ALL_ACCESS, key);
1348 if (!err)
1349 {
1350 if (HasValidSearchList(*key))
1351 {
1352 return TRUE;
1353 }
1354 RegCloseKey(*key);
1355 }
1356
1357 if (itf_name)
1358 {
1359 /* Always return the VPN interface key (if it exists) */
1360 WCHAR iid[64];
1361 DWORD iid_err = InterfaceIdString(itf_name, iid, _countof(iid));
1362 if (!iid_err)
1363 {
1364 HKEY itfs;
1365 err =
1366 RegOpenKeyExA(HKEY_LOCAL_MACHINE,
1367 "System\\CurrentControlSet\\Services\\TCPIP\\Parameters\\Interfaces",
1368 0, KEY_ALL_ACCESS, &itfs);
1369 if (!err)
1370 {
1371 err = RegOpenKeyExW(itfs, iid, 0, KEY_ALL_ACCESS, key);
1372 RegCloseKey(itfs);
1373 if (!err)
1374 {
1375 return FALSE; /* No need to preserve the VPN itf search list */
1376 }
1377 }
1378 }
1379 }
1380
1381 *key = INVALID_HANDLE_VALUE;
1382 return FALSE;
1383}
1384
1392static BOOL
1394{
1395 LSTATUS err;
1396
1397 err = RegGetValueA(key, NULL, "InitialSearchList", RRF_RT_REG_SZ, NULL, NULL, NULL);
1398 if (err)
1399 {
1400 if (err == ERROR_FILE_NOT_FOUND)
1401 {
1402 return FALSE;
1403 }
1404 MsgToEventLog(M_ERR, L"%S: failed to get InitialSearchList (%lu)", __func__, err);
1405 }
1406
1407 return TRUE;
1408}
1409
1414static DWORD
1415RegWStringSize(PCWSTR string)
1416{
1417 size_t length = (wcslen(string) + 1) * sizeof(wchar_t);
1418 if (length > UINT_MAX)
1419 {
1420 length = UINT_MAX;
1421 }
1422 return (DWORD)length;
1423}
1424
1435static BOOL
1437{
1438 if (!list || wcslen(list) == 0)
1439 {
1440 MsgToEventLog(M_ERR, L"%S: empty search list", __func__);
1441 return FALSE;
1442 }
1443
1445 {
1446 /* Initial list had already been stored */
1447 return TRUE;
1448 }
1449
1450 DWORD size = RegWStringSize(list);
1451 LSTATUS err = RegSetValueExW(key, L"InitialSearchList", 0, REG_SZ, (PBYTE)list, size);
1452 if (err)
1453 {
1454 MsgToEventLog(M_ERR, L"%S: failed to set InitialSearchList value (%lu)", __func__, err);
1455 return FALSE;
1456 }
1457
1458 return TRUE;
1459}
1460
1475static BOOL
1476AppendSearchList(PWSTR list, size_t list_cap, PCWSTR add)
1477{
1478 size_t list_len = wcslen(list);
1479 size_t add_len = wcslen(add);
1480 if (add_len == 0)
1481 {
1482 return TRUE;
1483 }
1484
1485 size_t sep_len = (list_len > 0) ? 1 : 0;
1486 if (list_len + sep_len + add_len + 1 > list_cap)
1487 {
1488 return FALSE;
1489 }
1490
1491 if (sep_len)
1492 {
1493 list[list_len++] = L',';
1494 }
1495 wmemcpy(list + list_len, add, add_len + 1);
1496 return TRUE;
1497}
1498
1522static size_t
1523RemoveSearchListTokens(PWSTR list, PCWSTR remove)
1524{
1525 size_t removed = 0;
1526 PCWSTR domain = remove;
1527 while (*domain)
1528 {
1529 PCWSTR comma = wcschr(domain, L',');
1530 size_t domain_len = comma ? (size_t)(comma - domain) : wcslen(domain);
1531 if (domain_len > 0)
1532 {
1533 /* Find the last token in @p list that exactly equals @p domain. */
1534 PWSTR match = NULL;
1535 PWSTR match_end = NULL;
1536 for (PWSTR p = list; *p;)
1537 {
1538 PWSTR tok_end = wcschr(p, L',');
1539 size_t tok_len = tok_end ? (size_t)(tok_end - p) : wcslen(p);
1540 if (tok_len == domain_len && wcsncmp(p, domain, domain_len) == 0)
1541 {
1542 match = p;
1543 match_end = tok_end;
1544 }
1545 if (!tok_end)
1546 {
1547 break;
1548 }
1549 p = tok_end + 1;
1550 }
1551 if (match)
1552 {
1553 /* Splice the token out, eating its leading comma if it has
1554 * one, otherwise its trailing comma. */
1555 PWSTR cut_start, cut_end;
1556 if (match == list)
1557 {
1558 cut_start = match;
1559 cut_end = match_end ? match_end + 1 : match + domain_len;
1560 }
1561 else
1562 {
1563 cut_start = match - 1;
1564 cut_end = match + domain_len;
1565 }
1566 wmemmove(cut_start, cut_end, wcslen(cut_end) + 1);
1567 removed++;
1568 }
1569 }
1570 if (!comma)
1571 {
1572 break;
1573 }
1574 domain = comma + 1;
1575 }
1576 return removed;
1577}
1578
1595static BOOL
1596AddDnsSearchDomains(HKEY key, BOOL have_list, PCWSTR domains)
1597{
1598 WCHAR list[2048] = { 0 };
1599
1600 if (have_list)
1601 {
1602 DWORD size = sizeof(list);
1603 LSTATUS err =
1604 RegGetValueW(key, NULL, L"SearchList", RRF_RT_REG_SZ, NULL, list, &size);
1605 if (err)
1606 {
1607 MsgToEventLog(M_SYSERR, L"%S: could not get SearchList from registry (%lu)", __func__,
1608 err);
1609 return FALSE;
1610 }
1611
1612 if (!StoreInitialDnsSearchList(key, list))
1613 {
1614 return FALSE;
1615 }
1616 }
1617
1618 if (!AppendSearchList(list, _countof(list), domains))
1619 {
1620 MsgToEventLog(M_SYSERR, L"%S: not enough space in list for search domains", __func__);
1621 return FALSE;
1622 }
1623
1624 DWORD size = RegWStringSize(list);
1625 LSTATUS err = RegSetValueExW(key, L"SearchList", 0, REG_SZ, (PBYTE)list, size);
1626 if (err)
1627 {
1628 MsgToEventLog(M_SYSERR, L"%S: could not set SearchList to registry (%lu)", __func__, err);
1629 return FALSE;
1630 }
1631
1632 return TRUE;
1633}
1634
1646static BOOL
1648{
1649 LSTATUS err;
1650 BOOL ret = FALSE;
1651 WCHAR list[2048];
1652 DWORD size = sizeof(list);
1653
1654 err = RegGetValueW(key, NULL, L"InitialSearchList", RRF_RT_REG_SZ, NULL, list, &size);
1655 if (err)
1656 {
1657 if (err != ERROR_FILE_NOT_FOUND)
1658 {
1659 MsgToEventLog(M_SYSERR, L"%S: could not get InitialSearchList from registry (%lu)",
1660 __func__, err);
1661 }
1662 goto out;
1663 }
1664
1665 size = RegWStringSize(list);
1666 err = RegSetValueExW(key, L"SearchList", 0, REG_SZ, (PBYTE)list, size);
1667 if (err)
1668 {
1669 MsgToEventLog(M_SYSERR, L"%S: could not set SearchList in registry (%lu)", __func__, err);
1670 goto out;
1671 }
1672
1673 RegDeleteValueA(key, "InitialSearchList");
1674 ret = TRUE;
1675
1676out:
1677 return ret;
1678}
1679
1694static void
1695RemoveDnsSearchDomains(HKEY key, PCWSTR domains)
1696{
1697 WCHAR list[2048];
1698 DWORD size = sizeof(list);
1699 LSTATUS err = RegGetValueW(key, NULL, L"SearchList", RRF_RT_REG_SZ, NULL, list, &size);
1700 if (err)
1701 {
1702 MsgToEventLog(M_SYSERR, L"%S: could not get SearchList from registry (%lu)", __func__, err);
1703 return;
1704 }
1705
1706 if (RemoveSearchListTokens(list, domains) == 0)
1707 {
1708 MsgToEventLog(M_ERR, L"%S: could not find domains in search list", __func__);
1709 return;
1710 }
1711
1712 if (list[0] != L'\0')
1713 {
1714 /* If the shortened list equals the snapshot we took at first
1715 * touch, the user's pre-VPN state is fully restored -- wipe both
1716 * SearchList and InitialSearchList. */
1717 WCHAR initial[2048];
1718 size = sizeof(initial);
1719 err = RegGetValueW(key, NULL, L"InitialSearchList", RRF_RT_REG_SZ, NULL, initial, &size);
1720 if (!err && wcscmp(list, initial) == 0)
1721 {
1723 return;
1724 }
1725 if (err && err != ERROR_FILE_NOT_FOUND)
1726 {
1727 MsgToEventLog(M_SYSERR, L"%S: could not get InitialSearchList from registry (%lu)",
1728 __func__, err);
1729 return;
1730 }
1731 }
1732
1733 size = RegWStringSize(list);
1734 err = RegSetValueExW(key, L"SearchList", 0, REG_SZ, (PBYTE)list, size);
1735 if (err)
1736 {
1737 MsgToEventLog(M_SYSERR, L"%S: could not set SearchList in registry (%lu)", __func__, err);
1738 }
1739}
1740
1746static void
1748{
1749 BOOL gpol;
1750 HKEY dns_searchlist_key;
1751 GetDnsSearchListKey(undo_data->itf_name, &gpol, &dns_searchlist_key);
1752 if (dns_searchlist_key != INVALID_HANDLE_VALUE)
1753 {
1754 RemoveDnsSearchDomains(dns_searchlist_key, undo_data->domains);
1755 RegCloseKey(dns_searchlist_key);
1756 ApplyDnsSettings(gpol);
1757
1758 free(undo_data->domains);
1759 undo_data->domains = NULL;
1760 }
1761}
1762
1784static DWORD
1785SetDnsSearchDomains(PCSTR itf_name, PCSTR domains, PBOOL gpol, undo_lists_t *lists)
1786{
1787 DWORD err = ERROR_OUTOFMEMORY;
1788
1789 HKEY list_key;
1790 BOOL have_list = GetDnsSearchListKey(itf_name, gpol, &list_key);
1791 if (list_key == INVALID_HANDLE_VALUE)
1792 {
1793 MsgToEventLog(M_SYSERR, L"%S: could not get search list registry key", __func__);
1794 return ERROR_FILE_NOT_FOUND;
1795 }
1796
1797 /* Remove previously installed search domains */
1798 dns_domains_undo_data_t *undo_data = RemoveListItem(&(*lists)[undo_domains], CmpAny, NULL);
1799 if (undo_data)
1800 {
1801 RemoveDnsSearchDomains(list_key, undo_data->domains);
1802 free(undo_data->domains);
1803 free(undo_data);
1804 undo_data = NULL;
1805 }
1806
1807 /* If there are search domains, add them */
1808 if (domains && *domains)
1809 {
1810 wchar_t *wide_domains = utf8to16(domains); /* utf8 to wide-char */
1811 if (!wide_domains)
1812 {
1813 goto out;
1814 }
1815
1816 undo_data = malloc(sizeof(*undo_data));
1817 if (!undo_data)
1818 {
1819 free(wide_domains);
1820 wide_domains = NULL;
1821 goto out;
1822 }
1823 strncpy_s(undo_data->itf_name, sizeof(undo_data->itf_name), itf_name, _TRUNCATE);
1824 undo_data->domains = wide_domains;
1825
1826 if (AddDnsSearchDomains(list_key, have_list, wide_domains) == FALSE
1827 || AddListItem(&(*lists)[undo_domains], undo_data) != NO_ERROR)
1828 {
1829 RemoveDnsSearchDomains(list_key, wide_domains);
1830 free(wide_domains);
1831 free(undo_data);
1832 undo_data = NULL;
1833 goto out;
1834 }
1835 }
1836
1837 err = NO_ERROR;
1838
1839out:
1840 RegCloseKey(list_key);
1841 return err;
1842}
1843
1851static BOOL
1852GetInterfacesKey(short family, PHKEY key)
1853{
1854 PCSTR itfs_key = family == AF_INET6
1855 ? "SYSTEM\\CurrentControlSet\\Services\\Tcpip6\\Parameters\\Interfaces"
1856 : "SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces";
1857
1858 LSTATUS err = RegOpenKeyExA(HKEY_LOCAL_MACHINE, itfs_key, 0, KEY_ALL_ACCESS, key);
1859 if (err)
1860 {
1861 *key = INVALID_HANDLE_VALUE;
1862 MsgToEventLog(M_SYSERR, L"%S: could not open interfaces registry key for family %d (%lu)",
1863 __func__, family, err);
1864 }
1865
1866 return err ? FALSE : TRUE;
1867}
1868
1878static DWORD
1879SetNameServersValue(PCWSTR itf_id, short family, PCSTR value)
1880{
1881 DWORD err;
1882
1883 HKEY itfs;
1884 if (!GetInterfacesKey(family, &itfs))
1885 {
1886 return ERROR_FILE_NOT_FOUND;
1887 }
1888
1889 HKEY itf = INVALID_HANDLE_VALUE;
1890 err = RegOpenKeyExW(itfs, itf_id, 0, KEY_ALL_ACCESS, &itf);
1891 if (err)
1892 {
1893 MsgToEventLog(M_SYSERR, L"%S: could not open interface key for %s family %d (%lu)",
1894 __func__, itf_id, family, err);
1895 goto out;
1896 }
1897
1898 err = RegSetValueExA(itf, "NameServer", 0, REG_SZ, (PBYTE)value, (DWORD)strlen(value) + 1);
1899 if (err)
1900 {
1901 MsgToEventLog(M_SYSERR, L"%S: could not set name servers '%S' for %s family %d (%lu)",
1902 __func__, value, itf_id, family, err);
1903 }
1904
1905out:
1906 if (itf != INVALID_HANDLE_VALUE)
1907 {
1908 RegCloseKey(itf);
1909 }
1910 if (itfs != INVALID_HANDLE_VALUE)
1911 {
1912 RegCloseKey(itfs);
1913 }
1914 return err;
1915}
1916
1926static DWORD
1927SetNameServers(PCWSTR itf_id, short family, PCSTR addrs)
1928{
1929 return SetNameServersValue(itf_id, family, addrs);
1930}
1931
1940static DWORD
1941ResetNameServers(PCWSTR itf_id, short family)
1942{
1943 return SetNameServersValue(itf_id, family, "");
1944}
1945
1946static DWORD
1948{
1949 DWORD err = 0;
1950 undo_type_t undo_type = (msg->family == AF_INET6) ? undo_dns6 : undo_dns4;
1951 unsigned int addr_len = msg->addr_len;
1952
1953 /* sanity check */
1954 const unsigned int max_addrs = _countof(msg->addr);
1955 if (addr_len > max_addrs)
1956 {
1957 addr_len = max_addrs;
1958 }
1959
1960 if (!msg->iface.name[0]) /* interface name is required */
1961 {
1962 return ERROR_MESSAGE_DATA;
1963 }
1964
1965 /* use a non-const reference with limited scope to enforce null-termination of strings from
1966 * client */
1967 {
1969 msgptr->iface.name[_countof(msg->iface.name) - 1] = '\0';
1970 msgptr->domains[_countof(msg->domains) - 1] = '\0';
1971 }
1972
1973 WCHAR iid[64];
1974 err = InterfaceIdString(msg->iface.name, iid, _countof(iid));
1975 if (err)
1976 {
1977 return err;
1978 }
1979
1980 /* We delete all current addresses before adding any
1981 * OR if the message type is del_dns_cfg
1982 */
1983 if (addr_len > 0 || msg->header.type == msg_del_dns_cfg)
1984 {
1985 err = ResetNameServers(iid, msg->family);
1986 if (err)
1987 {
1988 return err;
1989 }
1990 free(RemoveListItem(&(*lists)[undo_type], CmpAny, iid));
1991 }
1992
1993 if (msg->header.type == msg_del_dns_cfg)
1994 {
1995 BOOL gpol = FALSE;
1996 if (msg->domains[0])
1997 {
1998 /* setting an empty domain list removes any previous value */
1999 err = SetDnsSearchDomains(msg->iface.name, NULL, &gpol, lists);
2000 }
2001 ApplyDnsSettings(gpol);
2002 return err; /* job done */
2003 }
2004
2005 if (addr_len > 0)
2006 {
2007 /* prepare the comma separated address list */
2008 /* cannot use max_addrs here as that is not considered compile
2009 * time constant by all compilers and constexpr is C23 */
2010 CHAR addrs[_countof(msg->addr) * 64]; /* 64 is enough for one IPv4/6 address */
2011 size_t offset = 0;
2012 for (unsigned int i = 0; i < addr_len; ++i)
2013 {
2014 if (i != 0)
2015 {
2016 addrs[offset++] = ',';
2017 }
2018 if (msg->family == AF_INET6)
2019 {
2020 RtlIpv6AddressToStringA(&msg->addr[i].ipv6, addrs + offset);
2021 }
2022 else
2023 {
2024 RtlIpv4AddressToStringA(&msg->addr[i].ipv4, addrs + offset);
2025 }
2026 offset = strlen(addrs);
2027 }
2028
2029 err = SetNameServers(iid, msg->family, addrs);
2030 if (err)
2031 {
2032 return err;
2033 }
2034
2035 wchar_t *tmp_iid = _wcsdup(iid);
2036 if (!tmp_iid || AddListItem(&(*lists)[undo_type], tmp_iid))
2037 {
2038 free(tmp_iid);
2039 ResetNameServers(iid, msg->family);
2040 return ERROR_OUTOFMEMORY;
2041 }
2042 }
2043
2044 BOOL gpol = FALSE;
2045 if (msg->domains[0])
2046 {
2047 err = SetDnsSearchDomains(msg->iface.name, msg->domains, &gpol, lists);
2048 }
2049 ApplyDnsSettings(gpol);
2050
2051 return err;
2052}
2053
2062static BOOL
2064{
2065 DWORD dhcp;
2066 DWORD size = sizeof(dhcp);
2067 LSTATUS err;
2068
2069 err = RegGetValueA(key, NULL, "EnableDHCP", RRF_RT_REG_DWORD, NULL, (PBYTE)&dhcp, &size);
2070 if (err != NO_ERROR)
2071 {
2072 MsgToEventLog(M_SYSERR, L"%S: Could not read DHCP status (%lu)", __func__, err);
2073 return FALSE;
2074 }
2075
2076 return dhcp ? TRUE : FALSE;
2077}
2078
2087static LSTATUS
2088SetNameServerAddresses(PWSTR itf_id, const nrpt_address_t *addresses)
2089{
2090 const short families[] = { AF_INET, AF_INET6 };
2091 for (size_t i = 0; i < _countof(families); i++)
2092 {
2093 short family = families[i];
2094
2095 /* Create a comma sparated list of addresses of this family */
2096 size_t offset = 0;
2097 char addr_list[NRPT_ADDR_SIZE * NRPT_ADDR_NUM];
2098 for (int j = 0; j < NRPT_ADDR_NUM && addresses[j][0]; j++)
2099 {
2100 if ((family == AF_INET6 && strchr(addresses[j], ':') == NULL)
2101 || (family == AF_INET && strchr(addresses[j], ':') != NULL))
2102 {
2103 /* Address family doesn't match, skip this one */
2104 continue;
2105 }
2106 if (offset)
2107 {
2108 addr_list[offset++] = ',';
2109 }
2110 strcpy(addr_list + offset, addresses[j]);
2111 offset += strlen(addresses[j]);
2112 }
2113
2114 if (offset == 0)
2115 {
2116 /* No address for this family to set */
2117 continue;
2118 }
2119
2120 /* Set name server addresses */
2121 LSTATUS err = SetNameServers(itf_id, family, addr_list);
2122 if (err)
2123 {
2124 return err;
2125 }
2126 }
2127 return NO_ERROR;
2128}
2129
2140static LSTATUS
2141GetItfDnsServersV4(HKEY itf_key, PSTR addrs, PDWORD size)
2142{
2143 addrs[*size - 1] = '\0';
2144
2145 LSTATUS err;
2146 DWORD s = *size;
2147 err = RegGetValueA(itf_key, NULL, "NameServer", RRF_RT_REG_SZ, NULL, (PBYTE)addrs, &s);
2148 if (err && err != ERROR_FILE_NOT_FOUND)
2149 {
2150 *size = 0;
2151 return err;
2152 }
2153
2154 /* Try DHCP addresses if we don't have some already */
2155 if (!strchr(addrs, '.') && IsDhcpEnabled(itf_key))
2156 {
2157 s = *size;
2158 err = RegGetValueA(itf_key, NULL, "DhcpNameServer", RRF_RT_REG_SZ, NULL, (PBYTE)addrs, &s);
2159 if (err)
2160 {
2161 *size = 0;
2162 return err;
2163 }
2164 }
2165
2166 if (strchr(addrs, '.'))
2167 {
2168 *size = s;
2169 return NO_ERROR;
2170 }
2171
2172 *size = 0;
2173 return ERROR_FILE_NOT_FOUND;
2174}
2175
2185static LSTATUS
2186GetItfDnsServersV6(HKEY itf_key, PSTR addrs, PDWORD size)
2187{
2188 addrs[*size - 1] = '\0';
2189
2190 LSTATUS err;
2191 DWORD s = *size;
2192 err = RegGetValueA(itf_key, NULL, "NameServer", RRF_RT_REG_SZ, NULL, (PBYTE)addrs, &s);
2193 if (err && err != ERROR_FILE_NOT_FOUND)
2194 {
2195 *size = 0;
2196 return err;
2197 }
2198
2199 /* Try DHCP addresses if we don't have some already */
2200 if (!strchr(addrs, ':') && IsDhcpEnabled(itf_key))
2201 {
2202 IN6_ADDR in_addrs[8];
2203 DWORD in_addrs_size = sizeof(in_addrs);
2204 err = RegGetValueA(itf_key, NULL, "Dhcpv6DNSServers", RRF_RT_REG_BINARY, NULL,
2205 (PBYTE)in_addrs, &in_addrs_size);
2206 if (err)
2207 {
2208 *size = 0;
2209 return err;
2210 }
2211
2212 s = *size;
2213 PSTR pos = addrs;
2214 size_t in_addrs_read = in_addrs_size / sizeof(IN6_ADDR);
2215 for (size_t i = 0; i < in_addrs_read; ++i)
2216 {
2217 if (i != 0)
2218 {
2219 /* Add separator */
2220 *pos++ = ',';
2221 s--;
2222 }
2223
2224 if (inet_ntop(AF_INET6, &in_addrs[i], pos, s) == NULL)
2225 {
2226 *size = 0;
2227 return ERROR_MORE_DATA;
2228 }
2229
2230 size_t addr_len = strlen(pos);
2231 pos += addr_len;
2232 s -= (DWORD)addr_len;
2233 }
2234 s = (DWORD)strlen(addrs) + 1;
2235 }
2236
2237 if (strchr(addrs, ':'))
2238 {
2239 *size = s;
2240 return NO_ERROR;
2241 }
2242
2243 *size = 0;
2244 return ERROR_FILE_NOT_FOUND;
2245}
2246
2256static BOOL
2257ListContainsDomain(PCWSTR list, PCWSTR domain, size_t len)
2258{
2259 PCWSTR entry = list;
2260 while (entry && *entry)
2261 {
2262 PCWSTR comma = wcschr(entry, L',');
2263 size_t entry_len = comma ? (size_t)(comma - entry) : wcslen(entry);
2264 if (entry_len == len && wcsncmp(entry, domain, len) == 0)
2265 {
2266 return TRUE;
2267 }
2268 if (!comma)
2269 {
2270 break;
2271 }
2272 entry = comma + 1;
2273 }
2274 return FALSE;
2275}
2276
2300static LSTATUS
2301ConvertItfDnsDomains(PCWSTR search_domains, PWSTR domains, PDWORD size, const DWORD capacity)
2302{
2303 const size_t glyph_size = sizeof(*domains);
2304 const size_t max_len = (size_t)capacity / glyph_size;
2305
2306 /* Space required for leading dot and two terminating zeros */
2307 const size_t dot_len = 1;
2308 const size_t term_len = 2;
2309
2310 LSTATUS ret = NO_ERROR;
2311 size_t tmp_len = 0;
2312 WCHAR *tmp = malloc(capacity);
2313 if (tmp == NULL)
2314 {
2315 ret = ERROR_OUTOFMEMORY;
2316 goto done;
2317 }
2318
2319 PWCHAR tmp_pos = tmp;
2320 PCWCHAR domain = domains;
2321
2322 while (domain && *domain)
2323 {
2324 PWCHAR comma = wcschr(domain, L',');
2325 size_t domain_len = comma ? (size_t)(comma - domain) : wcslen(domain);
2326
2327 if (domain_len == 0 || ListContainsDomain(search_domains, domain, domain_len))
2328 {
2329 /* Skip this domain */
2330 domain = comma ? comma + 1 : domain + domain_len;
2331 continue;
2332 }
2333
2334 /* Check for enough space to convert this domain */
2335 if (tmp_len + dot_len + domain_len + term_len > max_len)
2336 {
2337 /* Domain doesn't fit, bad luck if it's the first one */
2338 *tmp_pos = L'\0';
2339 if (tmp_len > 0)
2340 {
2341 tmp_len += 1;
2342 }
2343 ret = ERROR_MORE_DATA;
2344 goto done;
2345 }
2346
2347 /* Write leading dot and domain into tmp buffer */
2348 *tmp_pos++ = L'.';
2349 wcsncpy(tmp_pos, domain, domain_len);
2350 tmp_pos += domain_len;
2351 *tmp_pos++ = L'\0';
2352 tmp_len += dot_len + domain_len + 1;
2353
2354 domain = comma ? comma + 1 : domain + domain_len;
2355 }
2356
2357 if (tmp_len == 0)
2358 {
2359 ret = ERROR_FILE_NOT_FOUND;
2360 goto done;
2361 }
2362
2363 /* REG_MULTI_SZ second zero terminator */
2364 *tmp_pos = L'\0';
2365 tmp_len += 1;
2366
2367done:
2368 if (tmp)
2369 {
2370 wmemcpy(domains, tmp, tmp_len);
2371 free(tmp);
2372 }
2373 *size = (DWORD)(tmp_len * glyph_size);
2374 return ret;
2375}
2376
2398static LSTATUS
2399GetItfDnsDomains(HKEY itf, PCWSTR search_domains, PWSTR domains, PDWORD size)
2400{
2401 if (domains == NULL || size == NULL || *size == 0)
2402 {
2403 return ERROR_INVALID_PARAMETER;
2404 }
2405
2406 LSTATUS err = ERROR_FILE_NOT_FOUND;
2407 const DWORD buf_size = *size;
2408 const DWORD glyph_size = sizeof(*domains);
2409 PWSTR values[] = { L"SearchList", L"Domain", L"DhcpDomainSearchList", L"DhcpDomain", NULL };
2410
2411 for (int i = 0; values[i]; i++)
2412 {
2413 *size = buf_size;
2414 err = RegGetValueW(itf, NULL, values[i], RRF_RT_REG_SZ, NULL, (PBYTE)domains, size);
2415 if (!err && *size > glyph_size && domains[(*size / glyph_size) - 1] == '\0' && wcschr(domains, '.'))
2416 {
2417 return ConvertItfDnsDomains(search_domains, domains, size, buf_size);
2418 }
2419 }
2420
2421 *size = 0;
2422 return err;
2423}
2424
2433static BOOL
2435{
2436 GUID iid;
2437 BOOL res = FALSE;
2438 MIB_IF_ROW2 itf_row;
2439
2440 /* Get GUID from string */
2441 if (IIDFromString(iid_str, &iid) != S_OK)
2442 {
2443 MsgToEventLog(M_SYSERR, L"%S: could not convert interface %s GUID string", __func__,
2444 iid_str);
2445 goto out;
2446 }
2447
2448 /* Get LUID from GUID */
2449 if (ConvertInterfaceGuidToLuid(&iid, &itf_row.InterfaceLuid) != NO_ERROR)
2450 {
2451 goto out;
2452 }
2453
2454 /* Look up interface status */
2455 if (GetIfEntry2(&itf_row) != NO_ERROR)
2456 {
2457 MsgToEventLog(M_SYSERR, L"%S: could not get interface %s status", __func__, iid_str);
2458 goto out;
2459 }
2460
2461 if (itf_row.MediaConnectState == MediaConnectStateConnected
2462 && itf_row.OperStatus == IfOperStatusUp)
2463 {
2464 res = TRUE;
2465 }
2466
2467out:
2468 return res;
2469}
2470
2480static void
2481GetNrptExcludeData(PCWSTR search_domains, nrpt_exclude_data_t *data, size_t data_size)
2482{
2483 HKEY v4_itfs = INVALID_HANDLE_VALUE;
2484 HKEY v6_itfs = INVALID_HANDLE_VALUE;
2485
2486 if (!GetInterfacesKey(AF_INET, &v4_itfs) || !GetInterfacesKey(AF_INET6, &v6_itfs))
2487 {
2488 goto out;
2489 }
2490
2491 size_t i = 0;
2492 DWORD enum_index = 0;
2493 while (i < data_size)
2494 {
2495 WCHAR itf_guid[MAX_PATH];
2496 DWORD itf_guid_len = _countof(itf_guid);
2497 LSTATUS err =
2498 RegEnumKeyExW(v4_itfs, enum_index++, itf_guid, &itf_guid_len, NULL, NULL, NULL, NULL);
2499 if (err)
2500 {
2501 if (err != ERROR_NO_MORE_ITEMS)
2502 {
2503 MsgToEventLog(M_SYSERR, L"%S: could not enumerate interfaces (%lu)", __func__, err);
2504 }
2505 goto out;
2506 }
2507
2508 /* Ignore interfaces that are not connected or disabled */
2509 if (!IsInterfaceConnected(itf_guid))
2510 {
2511 continue;
2512 }
2513
2514 HKEY v4_itf;
2515 if (RegOpenKeyExW(v4_itfs, itf_guid, 0, KEY_READ, &v4_itf) != NO_ERROR)
2516 {
2517 MsgToEventLog(M_SYSERR, L"%S: could not open interface %s v4 registry key", __func__,
2518 itf_guid);
2519 goto out;
2520 }
2521
2522 /* Get the DNS domain(s) for exclude routing */
2523 data[i].domains_size = sizeof(data[0].domains);
2524 memset(data[i].domains, 0, data[i].domains_size);
2525 err = GetItfDnsDomains(v4_itf, search_domains, data[i].domains, &data[i].domains_size);
2526 if (err)
2527 {
2528 if (err != ERROR_FILE_NOT_FOUND)
2529 {
2530 MsgToEventLog(M_SYSERR, L"%S: could not read interface %s domain suffix", __func__,
2531 itf_guid);
2532 }
2533 goto next_itf;
2534 }
2535
2536 /* Get the IPv4 DNS servers */
2537 DWORD v4_addrs_size = sizeof(data[0].addresses);
2538 err = GetItfDnsServersV4(v4_itf, data[i].addresses, &v4_addrs_size);
2539 if (err && err != ERROR_FILE_NOT_FOUND)
2540 {
2541 MsgToEventLog(M_SYSERR, L"%S: could not read interface %s v4 name servers (%ld)",
2542 __func__, itf_guid, err);
2543 goto next_itf;
2544 }
2545
2546 /* Get the IPv6 DNS servers, if there's space left */
2547 PSTR v6_addrs = data[i].addresses + v4_addrs_size;
2548 DWORD v6_addrs_size = sizeof(data[0].addresses) - v4_addrs_size;
2549 if (v6_addrs_size > NRPT_ADDR_SIZE)
2550 {
2551 HKEY v6_itf;
2552 if (RegOpenKeyExW(v6_itfs, itf_guid, 0, KEY_READ, &v6_itf) != NO_ERROR)
2553 {
2554 MsgToEventLog(M_SYSERR, L"%S: could not open interface %s v6 registry key",
2555 __func__, itf_guid);
2556 goto next_itf;
2557 }
2558 err = GetItfDnsServersV6(v6_itf, v6_addrs, &v6_addrs_size);
2559 RegCloseKey(v6_itf);
2560 if (err && err != ERROR_FILE_NOT_FOUND)
2561 {
2562 MsgToEventLog(M_SYSERR, L"%S: could not read interface %s v6 name servers (%ld)",
2563 __func__, itf_guid, err);
2564 goto next_itf;
2565 }
2566 }
2567
2568 if (v4_addrs_size || v6_addrs_size)
2569 {
2570 /* Replace delimiters with semicolons, as required by NRPT */
2571 for (size_t j = 0; j < sizeof(data[0].addresses) && data[i].addresses[j]; j++)
2572 {
2573 if (data[i].addresses[j] == ',' || data[i].addresses[j] == ' ')
2574 {
2575 data[i].addresses[j] = ';';
2576 }
2577 }
2578 ++i;
2579 }
2580
2581next_itf:
2582 RegCloseKey(v4_itf);
2583 }
2584
2585out:
2586 RegCloseKey(v6_itfs);
2587 RegCloseKey(v4_itfs);
2588}
2589
2602static DWORD
2603SetNrptRule(HKEY nrpt_key, PCWSTR subkey, PCSTR address, PCWSTR domains, DWORD dom_size,
2604 BOOL dnssec)
2605{
2606 /* Create rule subkey */
2607 DWORD err = NO_ERROR;
2608 HKEY rule_key;
2609 err = RegCreateKeyExW(nrpt_key, subkey, 0, NULL, 0, KEY_ALL_ACCESS, NULL, &rule_key, NULL);
2610 if (err)
2611 {
2612 return err;
2613 }
2614
2615 /* Set name(s) for DNS routing */
2616 err = RegSetValueExW(rule_key, L"Name", 0, REG_MULTI_SZ, (PBYTE)domains, dom_size);
2617 if (err)
2618 {
2619 goto out;
2620 }
2621
2622 /* Set DNS Server address */
2623 err = RegSetValueExA(rule_key, "GenericDNSServers", 0, REG_SZ, (PBYTE)address,
2624 (DWORD)strlen(address) + 1);
2625 if (err)
2626 {
2627 goto out;
2628 }
2629
2630 DWORD reg_val;
2631 /* Set DNSSEC if required */
2632 if (dnssec)
2633 {
2634 reg_val = 1;
2635 err = RegSetValueExA(rule_key, "DNSSECValidationRequired", 0, REG_DWORD, (PBYTE)&reg_val,
2636 sizeof(reg_val));
2637 if (err)
2638 {
2639 goto out;
2640 }
2641
2642 reg_val = 0;
2643 err = RegSetValueExA(rule_key, "DNSSECQueryIPSECRequired", 0, REG_DWORD, (PBYTE)&reg_val,
2644 sizeof(reg_val));
2645 if (err)
2646 {
2647 goto out;
2648 }
2649
2650 reg_val = 0;
2651 err = RegSetValueExA(rule_key, "DNSSECQueryIPSECEncryption", 0, REG_DWORD, (PBYTE)&reg_val,
2652 sizeof(reg_val));
2653 if (err)
2654 {
2655 goto out;
2656 }
2657 }
2658
2659 /* Set NRPT config options */
2660 reg_val = dnssec ? 0x0000000A : 0x00000008;
2661 err = RegSetValueExA(rule_key, "ConfigOptions", 0, REG_DWORD, (const PBYTE)&reg_val,
2662 sizeof(reg_val));
2663 if (err)
2664 {
2665 goto out;
2666 }
2667
2668 /* Mandatory NRPT version */
2669 reg_val = 2;
2670 err = RegSetValueExA(rule_key, "Version", 0, REG_DWORD, (const PBYTE)&reg_val, sizeof(reg_val));
2671 if (err)
2672 {
2673 goto out;
2674 }
2675
2676out:
2677 if (err)
2678 {
2679 RegDeleteKeyW(nrpt_key, subkey);
2680 }
2681 RegCloseKey(rule_key);
2682 return err;
2683}
2684
2694static void
2695SetNrptExcludeRules(HKEY nrpt_key, DWORD ovpn_pid, PCWSTR search_domains)
2696{
2697 nrpt_exclude_data_t data[8]; /* data from up to 8 interfaces */
2698 memset(data, 0, sizeof(data));
2699 GetNrptExcludeData(search_domains, data, _countof(data));
2700
2701 unsigned n = 0;
2702 for (size_t i = 0; i < _countof(data); ++i)
2703 {
2704 const nrpt_exclude_data_t *d = &data[i];
2705 if (d->domains_size == 0)
2706 {
2707 break;
2708 }
2709
2710 DWORD err;
2711 WCHAR subkey[48];
2712 swprintf(subkey, _countof(subkey), L"OpenVPNDNSRoutingX-%02x-%lu", ++n, ovpn_pid);
2713 err = SetNrptRule(nrpt_key, subkey, d->addresses, d->domains, d->domains_size, FALSE);
2714 if (err)
2715 {
2716 MsgToEventLog(M_ERR, L"%S: failed to set rule %s (%lu)", __func__, subkey, err);
2717 }
2718 }
2719}
2720
2733static DWORD
2734SetNrptRules(HKEY nrpt_key, const nrpt_address_t *addresses, const char *domains,
2735 const char *search_domains, BOOL dnssec, DWORD ovpn_pid)
2736{
2737 DWORD err = NO_ERROR;
2738 PWSTR wide_domains = L".\0"; /* DNS route everything by default */
2739 DWORD dom_size = 6;
2740
2741 /* Prepare DNS routing domains / split DNS */
2742 if (domains[0])
2743 {
2744 size_t domains_len = strlen(domains);
2745 dom_size = (DWORD)domains_len + 2; /* len + the trailing NULs */
2746
2747 wide_domains = utf8to16_size(domains, dom_size);
2748 if (!wide_domains)
2749 {
2750 return ERROR_OUTOFMEMORY;
2751 }
2752 domains_len = wcslen(wide_domains);
2753 dom_size = (DWORD)(domains_len + 2) * sizeof(*wide_domains);
2754
2755 /* Make a MULTI_SZ from a comma separated list */
2756 for (size_t i = 0; i < domains_len; ++i)
2757 {
2758 if (wide_domains[i] == ',')
2759 {
2760 wide_domains[i] = 0;
2761 }
2762 }
2763 }
2764 else
2765 {
2766 PWSTR wide_search_domains;
2767 wide_search_domains = utf8to16(search_domains);
2768 if (!wide_search_domains)
2769 {
2770 return ERROR_OUTOFMEMORY;
2771 }
2772 SetNrptExcludeRules(nrpt_key, ovpn_pid, wide_search_domains);
2773 free(wide_search_domains);
2774 }
2775
2776 if (addresses[0][0])
2777 {
2778 /* Create address string list */
2779 CHAR addr_list[NRPT_ADDR_NUM * NRPT_ADDR_SIZE];
2780 PSTR pos = addr_list;
2781 for (int i = 0; i < NRPT_ADDR_NUM && addresses[i][0]; ++i)
2782 {
2783 if (i != 0)
2784 {
2785 *pos++ = ';';
2786 }
2787 strcpy(pos, addresses[i]);
2788 pos += strlen(pos);
2789 }
2790
2791 WCHAR subkey[MAX_PATH];
2792 swprintf(subkey, _countof(subkey), L"OpenVPNDNSRouting-%lu", ovpn_pid);
2793 err = SetNrptRule(nrpt_key, subkey, addr_list, wide_domains, dom_size, dnssec);
2794 if (err)
2795 {
2796 MsgToEventLog(M_ERR, L"%S: failed to set rule %s (%lu)", __func__, subkey, err);
2797 }
2798 }
2799
2800 if (domains[0])
2801 {
2802 free(wide_domains);
2803 }
2804 return err;
2805}
2806
2815static LSTATUS
2816OpenNrptBaseKey(PHKEY key, PBOOL gpol)
2817{
2818 /*
2819 * Registry keys Name Service Policy Table (NRPT) rules can be stored at.
2820 * When the group policy key exists, NRPT rules must be placed there.
2821 * It is created when NRPT rules are pushed via group policy and it
2822 * remains in the registry even if the last GP-NRPT rule is deleted.
2823 */
2824 static PCSTR gpol_key = "SOFTWARE\\Policies\\Microsoft\\Windows NT\\DNSClient\\DnsPolicyConfig";
2825 static PCSTR sys_key =
2826 "SYSTEM\\CurrentControlSet\\Services\\Dnscache\\Parameters\\DnsPolicyConfig";
2827
2828 HKEY nrpt;
2829 *gpol = TRUE;
2830 LSTATUS err = RegOpenKeyExA(HKEY_LOCAL_MACHINE, gpol_key, 0, KEY_ALL_ACCESS, &nrpt);
2831 if (err == ERROR_FILE_NOT_FOUND)
2832 {
2833 *gpol = FALSE;
2834 err = RegCreateKeyExA(HKEY_LOCAL_MACHINE, sys_key, 0, NULL, 0, KEY_ALL_ACCESS, NULL, &nrpt,
2835 NULL);
2836 if (err)
2837 {
2838 nrpt = INVALID_HANDLE_VALUE;
2839 }
2840 }
2841 *key = nrpt;
2842 return err;
2843}
2844
2856static BOOL
2857DeleteNrptRules(DWORD pid, PBOOL gpol)
2858{
2859 HKEY key;
2860 LSTATUS err = OpenNrptBaseKey(&key, gpol);
2861 if (err)
2862 {
2863 MsgToEventLog(M_SYSERR, L"%S: could not open NRPT base key (%lu)", __func__, err);
2864 return FALSE;
2865 }
2866
2867 /* PID suffix string to compare against later */
2868 WCHAR pid_str[16];
2869 size_t pidlen = 0;
2870 if (pid)
2871 {
2872 swprintf(pid_str, _countof(pid_str), L"-%lu", pid);
2873 pidlen = wcslen(pid_str);
2874 }
2875
2876 int deleted = 0;
2877 DWORD enum_index = 0;
2878 while (TRUE)
2879 {
2880 WCHAR name[MAX_PATH];
2881 DWORD namelen = _countof(name);
2882 err = RegEnumKeyExW(key, enum_index++, name, &namelen, NULL, NULL, NULL, NULL);
2883 if (err)
2884 {
2885 if (err != ERROR_NO_MORE_ITEMS)
2886 {
2887 MsgToEventLog(M_SYSERR, L"%S: could not enumerate NRPT rules (%lu)", __func__, err);
2888 }
2889 break;
2890 }
2891
2892 /* Keep rule if name doesn't match */
2893 if (wcsncmp(name, L"OpenVPNDNSRouting", 17) != 0
2894 || (pid && wcsncmp(name + namelen - pidlen, pid_str, pidlen) != 0))
2895 {
2896 continue;
2897 }
2898
2899 if (RegDeleteKeyW(key, name) == NO_ERROR)
2900 {
2901 enum_index--;
2902 deleted++;
2903 }
2904 }
2905
2906 RegCloseKey(key);
2907 return deleted ? TRUE : FALSE;
2908}
2909
2915static void
2916UndoNrptRules(DWORD ovpn_pid)
2917{
2918 BOOL gpol;
2919 if (DeleteNrptRules(ovpn_pid, &gpol))
2920 {
2921 ApplyDnsSettings(gpol);
2922 }
2923}
2924
2936static DWORD
2938{
2939 /*
2940 * Use a non-const reference with limited scope to
2941 * enforce null-termination of strings from client
2942 */
2943 {
2945 msgptr->iface.name[_countof(msg->iface.name) - 1] = '\0';
2946 msgptr->search_domains[_countof(msg->search_domains) - 1] = '\0';
2947 msgptr->resolve_domains[_countof(msg->resolve_domains) - 1] = '\0';
2948 for (size_t i = 0; i < NRPT_ADDR_NUM; ++i)
2949 {
2950 msgptr->addresses[i][_countof(msg->addresses[0]) - 1] = '\0';
2951 }
2952 }
2953
2954 /* Make sure we have the VPN interface name */
2955 if (msg->iface.name[0] == 0)
2956 {
2957 return ERROR_MESSAGE_DATA;
2958 }
2959
2960 /* Some sanity checks on the add message data */
2961 if (msg->header.type == msg_add_nrpt_cfg)
2962 {
2963 /* At least one name server address is set */
2964 if (msg->addresses[0][0] == 0)
2965 {
2966 return ERROR_MESSAGE_DATA;
2967 }
2968 /* Resolve domains are double zero terminated (MULTI_SZ) */
2969 const char *rdom = msg->resolve_domains;
2970 size_t rdom_size = sizeof(msg->resolve_domains);
2971 size_t rdom_len = strlen(rdom);
2972 if (rdom_len && (rdom_len + 1 >= rdom_size || rdom[rdom_len + 1] != 0))
2973 {
2974 return ERROR_MESSAGE_DATA;
2975 }
2976 }
2977
2978 BOOL gpol_nrpt = FALSE;
2979 BOOL gpol_list = FALSE;
2980
2981 WCHAR iid[64];
2982 DWORD iid_err = InterfaceIdString(msg->iface.name, iid, _countof(iid));
2983 if (iid_err)
2984 {
2985 return iid_err;
2986 }
2987
2988 /* Delete previously set values for this instance first, if any */
2989 PDWORD undo_pid = RemoveListItem(&(*lists)[undo_nrpt], CmpAny, NULL);
2990 if (undo_pid)
2991 {
2992 if (*undo_pid != ovpn_pid)
2993 {
2995 L"%S: PID stored for undo doesn't match: %lu vs %lu. "
2996 "This is likely an error. Cleaning up anyway.",
2997 __func__, *undo_pid, ovpn_pid);
2998 }
2999 DeleteNrptRules(*undo_pid, &gpol_nrpt);
3000 free(undo_pid);
3001
3002 ResetNameServers(iid, AF_INET);
3003 ResetNameServers(iid, AF_INET6);
3004 }
3005 SetDnsSearchDomains(msg->iface.name, NULL, &gpol_list, lists);
3006
3007 if (msg->header.type == msg_del_nrpt_cfg)
3008 {
3009 ApplyDnsSettings(gpol_nrpt || gpol_list);
3010 return NO_ERROR; /* Done dealing with del message */
3011 }
3012
3013 HKEY key;
3014 LSTATUS err = OpenNrptBaseKey(&key, &gpol_nrpt);
3015 if (err)
3016 {
3017 goto out;
3018 }
3019
3020 /* Add undo information first in case there's no heap left */
3021 PDWORD pid = malloc(sizeof(ovpn_pid));
3022 if (!pid)
3023 {
3024 err = ERROR_OUTOFMEMORY;
3025 goto out;
3026 }
3027 *pid = ovpn_pid;
3028 if (AddListItem(&(*lists)[undo_nrpt], pid))
3029 {
3030 err = ERROR_OUTOFMEMORY;
3031 free(pid);
3032 goto out;
3033 }
3034
3035 /* Set NRPT rules */
3036 BOOL dnssec = (msg->flags & nrpt_dnssec) != 0;
3037 err = SetNrptRules(key, msg->addresses, msg->resolve_domains, msg->search_domains, dnssec,
3038 ovpn_pid);
3039 if (err)
3040 {
3041 goto out;
3042 }
3043
3044 /*
3045 * Set DNS on the adapter for search domains to be considered.
3046 * If split DNS is configured, do this only when search domains
3047 * are given, so that look-ups for other domains do not go over
3048 * the VPN all the time.
3049 */
3050 if (msg->search_domains[0] || !msg->resolve_domains[0])
3051 {
3052 err = SetNameServerAddresses(iid, msg->addresses);
3053 if (err)
3054 {
3055 goto out;
3056 }
3057 }
3058
3059 /* Set search domains, if any */
3060 if (msg->search_domains[0])
3061 {
3062 err = SetDnsSearchDomains(msg->iface.name, msg->search_domains, &gpol_list, lists);
3063 }
3064
3065 ApplyDnsSettings(gpol_nrpt || gpol_list);
3066
3067out:
3068 return err;
3069}
3070
3071static DWORD
3073{
3074 DWORD err = NO_ERROR;
3075 wchar_t addr[16]; /* large enough to hold string representation of an ipv4 */
3076 unsigned int addr_len = msg->addr_len;
3077
3078 /* sanity check */
3079 if (addr_len > _countof(msg->addr))
3080 {
3081 addr_len = _countof(msg->addr);
3082 }
3083
3084 if (!msg->iface.index) /* interface index is required */
3085 {
3086 return ERROR_MESSAGE_DATA;
3087 }
3088
3089 /* We delete all current addresses before adding any
3090 * OR if the message type is del_wins_cfg
3091 */
3092 if (addr_len > 0 || msg->header.type == msg_del_wins_cfg)
3093 {
3094 err = netsh_wins_cmd(L"delete", msg->iface.index, NULL);
3095 if (err)
3096 {
3097 goto out;
3098 }
3099 free(RemoveListItem(&(*lists)[undo_wins], CmpAny, NULL));
3100 }
3101
3102 if (addr_len == 0 || msg->header.type == msg_del_wins_cfg)
3103 {
3104 goto out; /* job done */
3105 }
3106
3107 for (unsigned int i = 0; i < addr_len; ++i)
3108 {
3109 RtlIpv4AddressToStringW(&msg->addr[i].ipv4, addr);
3110 err = netsh_wins_cmd(i == 0 ? L"set" : L"add", msg->iface.index, addr);
3111 if (i == 0 && err)
3112 {
3113 goto out;
3114 }
3115 /* We do not check for duplicate addresses, so any error in adding
3116 * additional addresses is ignored.
3117 */
3118 }
3119
3120 PDWORD if_index = malloc(sizeof(msg->iface.index));
3121 if (if_index)
3122 {
3123 *if_index = msg->iface.index;
3124 }
3125
3126 if (!if_index || AddListItem(&(*lists)[undo_wins], if_index))
3127 {
3128 free(if_index);
3129 netsh_wins_cmd(L"delete", msg->iface.index, NULL);
3130 err = ERROR_OUTOFMEMORY;
3131 goto out;
3132 }
3133
3134 err = 0;
3135
3136out:
3137 return err;
3138}
3139
3140static DWORD
3142{
3143 DWORD err = 0;
3144 DWORD timeout = 5000; /* in milli seconds */
3145 wchar_t argv0[MAX_PATH];
3146
3147 /* Path of netsh */
3148 swprintf(argv0, _countof(argv0), L"%ls\\%ls", get_win_sys_path(), L"netsh.exe");
3149
3150 /* cmd template:
3151 * netsh interface ipv4 set address name=$if_index source=dhcp
3152 */
3153 const wchar_t *fmt = L"netsh interface ipv4 set address name=\"%lu\" source=dhcp";
3154
3155 /* max cmdline length in wchars -- include room for if index:
3156 * 10 chars for 32 bit int in decimal and +1 for NUL
3157 */
3158 size_t ncmdline = wcslen(fmt) + 10 + 1;
3159 wchar_t *cmdline = malloc(ncmdline * sizeof(wchar_t));
3160 if (!cmdline)
3161 {
3162 err = ERROR_OUTOFMEMORY;
3163 return err;
3164 }
3165
3166 swprintf(cmdline, ncmdline, fmt, dhcp->iface.index);
3167
3168 err = ExecCommand(argv0, cmdline, timeout);
3169
3170 /* Note: This could fail if dhcp is already enabled, so the caller
3171 * may not want to treat errors as FATAL.
3172 */
3173
3174 free(cmdline);
3175 return err;
3176}
3177
3178static DWORD
3180{
3181 DWORD err = 0;
3182 MIB_IPINTERFACE_ROW ipiface;
3183 InitializeIpInterfaceEntry(&ipiface);
3184 ipiface.Family = mtu->family;
3185 ipiface.InterfaceIndex = mtu->iface.index;
3186 err = GetIpInterfaceEntry(&ipiface);
3187 if (err != NO_ERROR)
3188 {
3189 return err;
3190 }
3191 if (mtu->family == AF_INET)
3192 {
3193 ipiface.SitePrefixLength = 0;
3194 }
3195 ipiface.NlMtu = mtu->mtu;
3196
3197 err = SetIpInterfaceEntry(&ipiface);
3198 return err;
3199}
3200
3208static DWORD
3210{
3211 const WCHAR *hwid;
3212
3213 switch (msg->adapter_type)
3214 {
3215 case ADAPTER_TYPE_DCO:
3216 hwid = L"ovpn-dco";
3217 break;
3218
3219 case ADAPTER_TYPE_TAP:
3220 hwid = L"root\\tap0901";
3221 break;
3222
3223 default:
3224 return ERROR_INVALID_PARAMETER;
3225 }
3226
3227 WCHAR cmd[MAX_PATH];
3228 WCHAR args[MAX_PATH];
3229
3230 if (swprintf_s(cmd, _countof(cmd), L"%s\\tapctl.exe", settings.bin_dir) < 0)
3231 {
3232 return ERROR_BUFFER_OVERFLOW;
3233 }
3234
3235 if (swprintf_s(args, _countof(args), L"tapctl create --hwid %s", hwid) < 0)
3236 {
3237 return ERROR_BUFFER_OVERFLOW;
3238 }
3239
3240 return ExecCommand(cmd, args, 10000);
3241}
3242
3243static VOID
3244HandleMessage(HANDLE pipe, PPROCESS_INFORMATION proc_info, DWORD bytes, DWORD count,
3245 LPHANDLE events, undo_lists_t *lists)
3246{
3248 ack_message_t ack = {
3249 .header = { .type = msg_acknowledgement, .size = sizeof(ack), .message_id = -1 },
3250 .error_number = ERROR_MESSAGE_DATA
3251 };
3252
3253 DWORD read = ReadPipeAsync(pipe, &msg, bytes, count, events);
3254 if (read != bytes || read < sizeof(msg.header) || read != msg.header.size)
3255 {
3256 goto out;
3257 }
3258
3259 ack.header.message_id = msg.header.message_id;
3260
3261 switch (msg.header.type)
3262 {
3263 case msg_add_address:
3264 case msg_del_address:
3265 if (msg.header.size == sizeof(msg.address))
3266 {
3267 ack.error_number = HandleAddressMessage(&msg.address, lists);
3268 }
3269 break;
3270
3271 case msg_add_route:
3272 case msg_del_route:
3273 if (msg.header.size == sizeof(msg.route))
3274 {
3275 ack.error_number = HandleRouteMessage(&msg.route, lists);
3276 }
3277 break;
3278
3280 if (msg.header.size == sizeof(msg.flush_neighbors))
3281 {
3282 ack.error_number = HandleFlushNeighborsMessage(&msg.flush_neighbors);
3283 }
3284 break;
3285
3286 case msg_add_wfp_block:
3287 case msg_del_wfp_block:
3288 if (msg.header.size == sizeof(msg.wfp_block))
3289 {
3290 ack.error_number = HandleWfpBlockMessage(&msg.wfp_block, lists);
3291 }
3292 break;
3293
3294 case msg_register_dns:
3296 break;
3297
3298 case msg_add_dns_cfg:
3299 case msg_del_dns_cfg:
3300 if (msg.header.size == sizeof(msg.dns))
3301 {
3302 ack.error_number = HandleDNSConfigMessage(&msg.dns, lists);
3303 }
3304 break;
3305
3306 case msg_add_nrpt_cfg:
3307 case msg_del_nrpt_cfg:
3308 if (msg.header.size == sizeof(msg.nrpt_dns))
3309 {
3310 DWORD ovpn_pid = proc_info->dwProcessId;
3311 ack.error_number = HandleDNSConfigNrptMessage(&msg.nrpt_dns, ovpn_pid, lists);
3312 }
3313 break;
3314
3315 case msg_add_wins_cfg:
3316 case msg_del_wins_cfg:
3317 if (msg.header.size == sizeof(msg.wins))
3318 {
3319 ack.error_number = HandleWINSConfigMessage(&msg.wins, lists);
3320 }
3321 break;
3322
3323 case msg_enable_dhcp:
3324 if (msg.header.size == sizeof(msg.dhcp))
3325 {
3327 }
3328 break;
3329
3330 case msg_set_mtu:
3331 if (msg.header.size == sizeof(msg.mtu))
3332 {
3333 ack.error_number = HandleMTUMessage(&msg.mtu);
3334 }
3335 break;
3336
3337 case msg_create_adapter:
3338 if (msg.header.size == sizeof(msg.create_adapter))
3339 {
3340 ack.error_number = HandleCreateAdapterMessage(&msg.create_adapter);
3341 }
3342 break;
3343
3344 default:
3346 MsgToEventLog(MSG_FLAGS_ERROR, L"Unknown message type %d", msg.header.type);
3347 break;
3348 }
3349
3350out:
3351 WritePipeAsync(pipe, &ack, sizeof(ack), count, events);
3352}
3353
3354
3355static VOID
3357{
3358 undo_type_t type;
3359 wfp_block_data_t *interface_data;
3360 for (type = 0; type < _undo_type_max; type++)
3361 {
3362 list_item_t **pnext = &(*lists)[type];
3363 while (*pnext)
3364 {
3365 list_item_t *item = *pnext;
3366 switch (type)
3367 {
3368 case address:
3369 DeleteAddress(item->data);
3370 break;
3371
3372 case route:
3373 DeleteRoute(item->data);
3374 break;
3375
3376 case undo_dns4:
3377 ResetNameServers(item->data, AF_INET);
3378 break;
3379
3380 case undo_dns6:
3381 ResetNameServers(item->data, AF_INET6);
3382 break;
3383
3384 case undo_nrpt:
3385 UndoNrptRules(*(PDWORD)item->data);
3386 break;
3387
3388 case undo_domains:
3390 break;
3391
3392 case undo_wins:
3393 netsh_wins_cmd(L"delete", *(PDWORD)item->data, NULL);
3394 break;
3395
3396 case wfp_block:
3397 interface_data = (wfp_block_data_t *)(item->data);
3398 delete_wfp_block_filters(interface_data->engine);
3399 if (interface_data->metric_v4 >= 0)
3400 {
3401 set_interface_metric(interface_data->index, AF_INET,
3402 interface_data->metric_v4);
3403 }
3404 if (interface_data->metric_v6 >= 0)
3405 {
3406 set_interface_metric(interface_data->index, AF_INET6,
3407 interface_data->metric_v6);
3408 }
3409 break;
3410
3411 case _undo_type_max:
3412 /* unreachable */
3413 break;
3414 }
3415
3416 /* Remove from the list and free memory */
3417 *pnext = item->next;
3418 free(item->data);
3419 free(item);
3420 }
3421 }
3422}
3423
3431static BOOL
3432IsUserEnvVar(PCWSTR var)
3433{
3434 static PCWSTR user_vars[] = { L"APPDATA=", L"LOCALAPPDATA=", L"TEMP=", L"TMP=",
3435 L"USERDOMAIN=", L"USERNAME=", L"USERPROFILE=" };
3436
3437 for (size_t i = 0; i < _countof(user_vars); ++i)
3438 {
3439 if (wcsncmp(var, user_vars[i], wcslen(user_vars[i])) == 0)
3440 {
3441 return TRUE;
3442 }
3443 }
3444 return FALSE;
3445}
3446
3454static size_t
3455EnvBlockSize(PCWSTR env)
3456{
3457 PCWSTR var = env;
3458 while (*var)
3459 {
3460 var += wcslen(var) + 1;
3461 }
3462 return ((size_t)(var - env) + 1) * sizeof(*env);
3463}
3464
3479static PWSTR
3480MergeOpenvpnEnvironment(PCWSTR sys_env, PCWSTR usr_env)
3481{
3482 PWSTR block = malloc(EnvBlockSize(sys_env) + EnvBlockSize(usr_env));
3483 if (!block)
3484 {
3485 return NULL;
3486 }
3487
3488 PWSTR pos = block;
3489 for (PCWSTR var = sys_env; *var; var += wcslen(var) + 1)
3490 {
3491 if (!IsUserEnvVar(var))
3492 {
3493 size_t len = wcslen(var) + 1;
3494 wmemcpy(pos, var, len);
3495 pos += len;
3496 }
3497 }
3498 for (PCWSTR var = usr_env; *var; var += wcslen(var) + 1)
3499 {
3500 if (IsUserEnvVar(var))
3501 {
3502 size_t len = wcslen(var) + 1;
3503 wmemcpy(pos, var, len);
3504 pos += len;
3505 }
3506 }
3507 *pos = L'\0';
3508
3509 return block;
3510}
3511
3524static BOOL
3525CreateOpenvpnEnvironment(HANDLE token, PWSTR *env)
3526{
3527 LPVOID sys_env = NULL;
3528 LPVOID usr_env = NULL;
3529 BOOL ret = FALSE;
3530
3531 if (!CreateEnvironmentBlock(&sys_env, NULL, FALSE)
3532 || !CreateEnvironmentBlock(&usr_env, token, FALSE))
3533 {
3534 goto out;
3535 }
3536
3537 *env = MergeOpenvpnEnvironment(sys_env, usr_env);
3538 if (!*env)
3539 {
3540 SetLastError(ERROR_OUTOFMEMORY);
3541 goto out;
3542 }
3543 ret = TRUE;
3544
3545out:
3546 DestroyEnvironmentBlock(sys_env);
3547 DestroyEnvironmentBlock(usr_env);
3548 return ret;
3549}
3550
3551static DWORD WINAPI
3552RunOpenvpn(LPVOID p)
3553{
3554 HANDLE pipe = p;
3555 HANDLE ovpn_pipe = NULL, svc_pipe = NULL;
3556 PTOKEN_USER svc_user = NULL, ovpn_user = NULL;
3557 HANDLE svc_token = NULL, imp_token = NULL, pri_token = NULL;
3558 HANDLE stdin_read = NULL, stdin_write = NULL;
3559 HANDLE stdout_write = NULL;
3560 DWORD pipe_mode, len, exit_code = 0;
3561 STARTUP_DATA sud = { 0, 0, 0 };
3562 STARTUPINFOW startup_info;
3563 PROCESS_INFORMATION proc_info;
3564 PWSTR ovpn_env = NULL;
3565 WCHAR ovpn_pipe_name[256]; /* The entire pipe name string can be up to 256 characters long
3566 according to MSDN. */
3567 LPCWSTR exe_path;
3568 WCHAR *cmdline = NULL;
3569 size_t cmdline_size;
3570 undo_lists_t undo_lists;
3571 WCHAR errmsg[512] = L"";
3572 BOOL flush_pipe = TRUE;
3573
3574 SECURITY_ATTRIBUTES inheritable = { .nLength = sizeof(inheritable),
3575 .lpSecurityDescriptor = NULL,
3576 .bInheritHandle = TRUE };
3577
3578 PACL ovpn_dacl = NULL;
3579 PSECURITY_DESCRIPTOR pSD = NULL;
3580 EXPLICIT_ACCESS ea[2];
3581 SECURITY_DESCRIPTOR ovpn_sd;
3582 SECURITY_ATTRIBUTES ovpn_sa = { .nLength = sizeof(ovpn_sa),
3583 .lpSecurityDescriptor = &ovpn_sd,
3584 .bInheritHandle = FALSE };
3585
3586 ZeroMemory(&ea, sizeof(ea));
3587 ZeroMemory(&startup_info, sizeof(startup_info));
3588 ZeroMemory(&undo_lists, sizeof(undo_lists));
3589 ZeroMemory(&proc_info, sizeof(proc_info));
3590
3591 if (!GetStartupData(pipe, &sud))
3592 {
3593 flush_pipe = FALSE; /* client did not provide startup data */
3594 goto out;
3595 }
3596
3597 if (!InitializeSecurityDescriptor(&ovpn_sd, SECURITY_DESCRIPTOR_REVISION))
3598 {
3599 ReturnLastError(pipe, L"InitializeSecurityDescriptor");
3600 goto out;
3601 }
3602
3603 /* Get SID of user the service is running under */
3604 if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &svc_token))
3605 {
3606 ReturnLastError(pipe, L"OpenProcessToken");
3607 goto out;
3608 }
3609 len = 0;
3610 while (!GetTokenInformation(svc_token, TokenUser, svc_user, len, &len))
3611 {
3612 if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
3613 {
3614 ReturnLastError(pipe, L"GetTokenInformation (service token)");
3615 goto out;
3616 }
3617 free(svc_user);
3618 svc_user = malloc(len);
3619 if (svc_user == NULL)
3620 {
3621 ReturnLastError(pipe, L"malloc (service token user)");
3622 goto out;
3623 }
3624 }
3625 if (!IsValidSid(svc_user->User.Sid))
3626 {
3627 ReturnLastError(pipe, L"IsValidSid (service token user)");
3628 goto out;
3629 }
3630
3631 if (!ImpersonateNamedPipeClient(pipe))
3632 {
3633 ReturnLastError(pipe, L"ImpersonateNamedPipeClient");
3634 goto out;
3635 }
3636 if (!OpenThreadToken(GetCurrentThread(), TOKEN_ALL_ACCESS, FALSE, &imp_token))
3637 {
3638 ReturnLastError(pipe, L"OpenThreadToken");
3639 goto out;
3640 }
3641 len = 0;
3642 while (!GetTokenInformation(imp_token, TokenUser, ovpn_user, len, &len))
3643 {
3644 if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
3645 {
3646 ReturnLastError(pipe, L"GetTokenInformation (impersonation token)");
3647 goto out;
3648 }
3649 free(ovpn_user);
3650 ovpn_user = malloc(len);
3651 if (ovpn_user == NULL)
3652 {
3653 ReturnLastError(pipe, L"malloc (impersonation token user)");
3654 goto out;
3655 }
3656 }
3657 if (!IsValidSid(ovpn_user->User.Sid))
3658 {
3659 ReturnLastError(pipe, L"IsValidSid (impersonation token user)");
3660 goto out;
3661 }
3662
3663 /* OpenVPN process DACL entry for access by service and user */
3664 ea[0].grfAccessPermissions = SPECIFIC_RIGHTS_ALL | STANDARD_RIGHTS_ALL;
3665 ea[0].grfAccessMode = SET_ACCESS;
3666 ea[0].grfInheritance = NO_INHERITANCE;
3667 ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
3668 ea[0].Trustee.TrusteeType = TRUSTEE_IS_UNKNOWN;
3669 ea[0].Trustee.ptstrName = (LPWSTR)svc_user->User.Sid;
3670 ea[1].grfAccessPermissions = READ_CONTROL | PROCESS_VM_READ | SYNCHRONIZE
3671 | PROCESS_TERMINATE | PROCESS_QUERY_INFORMATION;
3672 ea[1].grfAccessMode = SET_ACCESS;
3673 ea[1].grfInheritance = NO_INHERITANCE;
3674 ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID;
3675 ea[1].Trustee.TrusteeType = TRUSTEE_IS_UNKNOWN;
3676 ea[1].Trustee.ptstrName = (LPWSTR)ovpn_user->User.Sid;
3677
3678 /* Set owner and DACL of OpenVPN security descriptor */
3679 if (!SetSecurityDescriptorOwner(&ovpn_sd, svc_user->User.Sid, FALSE))
3680 {
3681 ReturnLastError(pipe, L"SetSecurityDescriptorOwner");
3682 goto out;
3683 }
3684 if (SetEntriesInAcl(2, ea, NULL, &ovpn_dacl) != ERROR_SUCCESS)
3685 {
3686 ReturnLastError(pipe, L"SetEntriesInAcl");
3687 goto out;
3688 }
3689 if (!SetSecurityDescriptorDacl(&ovpn_sd, TRUE, ovpn_dacl, FALSE))
3690 {
3691 ReturnLastError(pipe, L"SetSecurityDescriptorDacl");
3692 goto out;
3693 }
3694
3695 /*
3696 * Add a SACL to the openvpn security descriptor with a medium integrity label ACE.
3697 * This makes it possible to still query the process info and terminate the process
3698 * from the GUI. The high integrity label assigned to the process' primary token
3699 * would otherwise prevent this.
3700 */
3701 BYTE medium_sid[SECURITY_MAX_SID_SIZE];
3702 DWORD medium_sid_size = sizeof(medium_sid);
3703 DWORD sacl_buf[(sizeof(ACL) + sizeof(SYSTEM_MANDATORY_LABEL_ACE) + SECURITY_MAX_SID_SIZE) / sizeof(DWORD) + 1]; /* must be DWORD aligned */
3704 PACL sacl = (PACL)sacl_buf;
3705 if (!CreateWellKnownSid(WinMediumLabelSid, NULL, medium_sid, &medium_sid_size)
3706 || !InitializeAcl(sacl, (DWORD)sizeof(sacl_buf), ACL_REVISION)
3707 || !AddMandatoryAce(sacl, ACL_REVISION, 0, SYSTEM_MANDATORY_LABEL_NO_WRITE_UP, medium_sid)
3708 || !SetSecurityDescriptorSacl(&ovpn_sd, TRUE, sacl, FALSE))
3709 {
3710 ReturnLastError(pipe, L"SetSecurityDescriptorSacl");
3711 goto out;
3712 }
3713
3714 /* Create primary token from impersonation token */
3715 if (!DuplicateTokenEx(imp_token, TOKEN_ALL_ACCESS, NULL, 0, TokenPrimary, &pri_token))
3716 {
3717 ReturnLastError(pipe, L"DuplicateTokenEx");
3718 goto out;
3719 }
3720
3721 /*
3722 * Run openvpn at high integrity, so that the user cannot write to its threads.
3723 * Raising the level requires the privileges of the service, so stop
3724 * impersonating the user while doing it.
3725 */
3726 BYTE high_sid[SECURITY_MAX_SID_SIZE];
3727 DWORD high_sid_size = sizeof(high_sid);
3728 if (!RevertToSelf())
3729 {
3730 ReturnLastError(pipe, L"RevertToSelf");
3731 goto out;
3732 }
3733 if (!CreateWellKnownSid(WinHighLabelSid, NULL, high_sid, &high_sid_size))
3734 {
3735 ReturnLastError(pipe, L"CreateWellKnownSid");
3736 goto out;
3737 }
3738 TOKEN_MANDATORY_LABEL label = { .Label = { .Sid = high_sid, .Attributes = SE_GROUP_INTEGRITY } };
3739 DWORD label_size = sizeof(label) + GetLengthSid(high_sid);
3740 if (!SetTokenInformation(pri_token, TokenIntegrityLevel, &label, label_size))
3741 {
3742 ReturnLastError(pipe, L"SetTokenInformation");
3743 goto out;
3744 }
3745 if (!SetThreadToken(NULL, imp_token))
3746 {
3747 ReturnLastError(pipe, L"SetThreadToken");
3748 goto out;
3749 }
3750
3751 /* use /dev/null for stdout of openvpn (client should use --log for output) */
3752 stdout_write = CreateFile(_L("NUL"), GENERIC_WRITE, FILE_SHARE_WRITE, &inheritable,
3753 OPEN_EXISTING, 0, NULL);
3754 if (stdout_write == INVALID_HANDLE_VALUE)
3755 {
3756 ReturnLastError(pipe, L"CreateFile for stdout");
3757 goto out;
3758 }
3759
3760 if (!CreatePipe(&stdin_read, &stdin_write, &inheritable, 0)
3761 || !SetHandleInformation(stdin_write, HANDLE_FLAG_INHERIT, 0))
3762 {
3763 ReturnLastError(pipe, L"CreatePipe");
3764 goto out;
3765 }
3766
3767 UUID pipe_uuid;
3768 RPC_STATUS rpc_stat = UuidCreate(&pipe_uuid);
3769 if (rpc_stat != RPC_S_OK)
3770 {
3771 ReturnError(pipe, rpc_stat, L"UuidCreate", 1, &exit_event);
3772 goto out;
3773 }
3774
3775 RPC_WSTR pipe_uuid_str = NULL;
3776 rpc_stat = UuidToStringW(&pipe_uuid, &pipe_uuid_str);
3777 if (rpc_stat != RPC_S_OK)
3778 {
3779 ReturnError(pipe, rpc_stat, L"UuidToString", 1, &exit_event);
3780 goto out;
3781 }
3782 swprintf(ovpn_pipe_name, _countof(ovpn_pipe_name),
3783 // cppcheck-suppress unknownMacro ; FP, complains about _L only here...
3784 L"\\\\.\\pipe\\" _L(PACKAGE) L"%ls\\service_%lu_%ls", service_instance,
3785 GetCurrentThreadId(), pipe_uuid_str);
3786 RpcStringFreeW(&pipe_uuid_str);
3787
3788 /* make a security descriptor for the named pipe with access
3789 * restricted to the user and SYSTEM
3790 */
3791
3792 SECURITY_ATTRIBUTES sa;
3793 LPCWSTR szSDDL = L"D:(A;;GA;;;SY)(A;;GA;;;OW)";
3794 if (!ConvertStringSecurityDescriptorToSecurityDescriptorW(
3795 szSDDL, SDDL_REVISION_1, &pSD, NULL))
3796 {
3797 ReturnLastError(pipe, L"ConvertSDDL");
3798 goto out;
3799 }
3800 sa.nLength = sizeof(sa);
3801 sa.lpSecurityDescriptor = pSD;
3802 sa.bInheritHandle = FALSE;
3803
3804 ovpn_pipe = CreateNamedPipe(
3805 ovpn_pipe_name, PIPE_ACCESS_DUPLEX | FILE_FLAG_FIRST_PIPE_INSTANCE | FILE_FLAG_OVERLAPPED,
3806 PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT | PIPE_REJECT_REMOTE_CLIENTS, 1, 128, 128, 0, &sa);
3807 if (ovpn_pipe == INVALID_HANDLE_VALUE)
3808 {
3809 ReturnLastError(pipe, L"CreateNamedPipe");
3810 goto out;
3811 }
3812
3813 svc_pipe = CreateFile(ovpn_pipe_name, GENERIC_READ | GENERIC_WRITE, 0, &inheritable,
3814 OPEN_EXISTING, 0, NULL);
3815 if (svc_pipe == INVALID_HANDLE_VALUE)
3816 {
3817 ReturnLastError(pipe, L"CreateFile");
3818 goto out;
3819 }
3820
3821 pipe_mode = PIPE_READMODE_MESSAGE;
3822 if (!SetNamedPipeHandleState(svc_pipe, &pipe_mode, NULL, NULL))
3823 {
3824 ReturnLastError(pipe, L"SetNamedPipeHandleState");
3825 goto out;
3826 }
3827
3828 cmdline_size = wcslen(sud.options) + 128;
3829 cmdline = malloc(cmdline_size * sizeof(*cmdline));
3830 if (cmdline == NULL)
3831 {
3832 ReturnLastError(pipe, L"malloc");
3833 goto out;
3834 }
3835 /* there seem to be no common printf specifier that works on all
3836 * mingw/msvc platforms without trickery, so convert to void* and use
3837 * PRIuPTR to print that as best compromise */
3838 swprintf(cmdline, cmdline_size, L"openvpn --msg-channel %" PRIuPTR L" %ls",
3839 (uintptr_t)svc_pipe, sud.options);
3840
3841 /*
3842 * Only authorized users are allowed to use any command line options or
3843 * have the config file in locations other than the global config directory.
3844 *
3845 * Check options are white-listed and config is in the global directory
3846 * OR user is authorized to run any config.
3847 */
3848 if (!ValidateOptions(pipe, sud.directory, cmdline, errmsg, _countof(errmsg))
3849 && !IsAuthorizedUser(ovpn_user->User.Sid, imp_token, settings.ovpn_admin_group,
3851 {
3852 ReturnError(pipe, ERROR_STARTUP_DATA, errmsg, 1, &exit_event);
3853 goto out;
3854 }
3855
3856 if (!CreateOpenvpnEnvironment(imp_token, &ovpn_env))
3857 {
3858 ReturnLastError(pipe, L"CreateOpenvpnEnvironment");
3859 goto out;
3860 }
3861
3862 startup_info.cb = sizeof(startup_info);
3863 startup_info.dwFlags = STARTF_USESTDHANDLES;
3864 startup_info.hStdInput = stdin_read;
3865 startup_info.hStdOutput = stdout_write;
3866 startup_info.hStdError = stdout_write;
3867
3868 exe_path = settings.exe_path;
3869
3870 /* TODO: make sure HKCU is correct or call LoadUserProfile() */
3871 if (!CreateProcessAsUserW(pri_token, exe_path, cmdline, &ovpn_sa, NULL, TRUE,
3872 settings.priority | CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT,
3873 ovpn_env, sud.directory, &startup_info, &proc_info))
3874 {
3875 ReturnLastError(pipe, L"CreateProcessAsUser");
3876 goto out;
3877 }
3878
3879 if (!RevertToSelf())
3880 {
3881 TerminateProcess(proc_info.hProcess, 1);
3882 ReturnLastError(pipe, L"RevertToSelf");
3883 goto out;
3884 }
3885
3886 ReturnProcessId(pipe, proc_info.dwProcessId, 1, &exit_event);
3887
3888 CloseHandleEx(&stdout_write);
3889 CloseHandleEx(&stdin_read);
3890 CloseHandleEx(&svc_pipe);
3891
3892 DWORD input_size = WideCharToMultiByte(CP_UTF8, 0, sud.std_input, -1, NULL, 0, NULL, NULL);
3893 LPSTR input = NULL;
3894 if (input_size && (input = malloc(input_size)))
3895 {
3896 DWORD written;
3897 WideCharToMultiByte(CP_UTF8, 0, sud.std_input, -1, input, input_size, NULL, NULL);
3898 WriteFile(stdin_write, input, (DWORD)strlen(input), &written, NULL);
3899 free(input);
3900 }
3901
3902 while (TRUE)
3903 {
3904 DWORD bytes = PeekNamedPipeAsync(ovpn_pipe, 1, &exit_event);
3905 if (bytes == 0)
3906 {
3907 break;
3908 }
3909
3910 if (bytes > sizeof(pipe_message_t))
3911 {
3912 /* process at the other side of the pipe is misbehaving, shut it down */
3915 L"OpenVPN process sent too large payload length to the pipe (%lu bytes), it will be terminated",
3916 bytes);
3917 break;
3918 }
3919
3920 HandleMessage(ovpn_pipe, &proc_info, bytes, 1, &exit_event, &undo_lists);
3921 }
3922
3923 WaitForSingleObject(proc_info.hProcess, IO_TIMEOUT);
3924 GetExitCodeProcess(proc_info.hProcess, &exit_code);
3925 if (exit_code == STILL_ACTIVE)
3926 {
3927 TerminateProcess(proc_info.hProcess, 1);
3928 }
3929 else if (exit_code != 0)
3930 {
3931 WCHAR buf[256];
3932 swprintf(buf, _countof(buf), L"OpenVPN exited with error: exit code = %lu", exit_code);
3934 }
3935 Undo(&undo_lists);
3936
3937out:
3938 if (flush_pipe)
3939 {
3940 FlushFileBuffers(pipe);
3941 }
3942 DisconnectNamedPipe(pipe);
3943
3944 free(ovpn_user);
3945 free(svc_user);
3946 free(cmdline);
3947 LocalFree(pSD);
3948 LocalFree(ovpn_dacl);
3949 free(ovpn_env);
3950 FreeStartupData(&sud);
3951 CloseHandleEx(&proc_info.hProcess);
3952 CloseHandleEx(&proc_info.hThread);
3953 CloseHandleEx(&stdin_read);
3954 CloseHandleEx(&stdin_write);
3955 CloseHandleEx(&stdout_write);
3956 CloseHandleEx(&svc_token);
3957 CloseHandleEx(&imp_token);
3958 CloseHandleEx(&pri_token);
3959 CloseHandleEx(&ovpn_pipe);
3960 CloseHandleEx(&svc_pipe);
3961 CloseHandleEx(&pipe);
3962
3963 return 0;
3964}
3965
3966
3967static DWORD WINAPI
3968ServiceCtrlInteractive(DWORD ctrl_code, DWORD event, LPVOID data, LPVOID ctx)
3969{
3970 SERVICE_STATUS *svc_status = ctx;
3971 switch (ctrl_code)
3972 {
3973 case SERVICE_CONTROL_STOP:
3974 svc_status->dwCurrentState = SERVICE_STOP_PENDING;
3975 ReportStatusToSCMgr(service, svc_status);
3976 if (exit_event)
3977 {
3978 SetEvent(exit_event);
3979 }
3980 return NO_ERROR;
3981
3982 case SERVICE_CONTROL_INTERROGATE:
3983 return NO_ERROR;
3984
3985 default:
3986 return ERROR_CALL_NOT_IMPLEMENTED;
3987 }
3988}
3989
3990
3991static HANDLE
3993{
3994 /*
3995 * allow all access for local system
3996 * deny FILE_CREATE_PIPE_INSTANCE for everyone
3997 * allow read/write for authenticated users
3998 * deny all access to anonymous
3999 */
4000 const WCHAR *sddlString =
4001 L"D:(A;OICI;GA;;;S-1-5-18)(D;OICI;0x4;;;S-1-1-0)(A;OICI;GRGW;;;S-1-5-11)(D;;GA;;;S-1-5-7)";
4002
4003 PSECURITY_DESCRIPTOR sd = NULL;
4004 if (!ConvertStringSecurityDescriptorToSecurityDescriptor(sddlString, SDDL_REVISION_1, &sd,
4005 NULL))
4006 {
4007 MsgToEventLog(M_SYSERR, L"ConvertStringSecurityDescriptorToSecurityDescriptor failed.");
4008 return INVALID_HANDLE_VALUE;
4009 }
4010
4011 /* Set up SECURITY_ATTRIBUTES */
4012 SECURITY_ATTRIBUTES sa = { 0 };
4013 sa.nLength = sizeof(SECURITY_ATTRIBUTES);
4014 sa.lpSecurityDescriptor = sd;
4015 sa.bInheritHandle = FALSE;
4016
4017 DWORD flags = PIPE_ACCESS_DUPLEX | WRITE_DAC | FILE_FLAG_OVERLAPPED;
4018
4019 static BOOL first = TRUE;
4020 if (first)
4021 {
4022 flags |= FILE_FLAG_FIRST_PIPE_INSTANCE;
4023 first = FALSE;
4024 }
4025
4026 WCHAR pipe_name[256]; /* The entire pipe name string can be up to 256 characters long according
4027 to MSDN. */
4028 swprintf(pipe_name, _countof(pipe_name), L"\\\\.\\pipe\\" _L(PACKAGE) L"%ls\\service",
4030 HANDLE pipe = CreateNamedPipe(
4031 pipe_name, flags, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_REJECT_REMOTE_CLIENTS,
4032 PIPE_UNLIMITED_INSTANCES, 1024, 1024, 0, &sa);
4033
4034 LocalFree(sd);
4035
4036 if (pipe == INVALID_HANDLE_VALUE)
4037 {
4038 MsgToEventLog(M_SYSERR, L"Could not create named pipe");
4039 return INVALID_HANDLE_VALUE;
4040 }
4041
4042 return pipe;
4043}
4044
4045
4046static DWORD
4047UpdateWaitHandles(LPHANDLE *handles_ptr, LPDWORD count, HANDLE io_event,
4048 const list_item_t *threads)
4049{
4050 static DWORD size = 10;
4051 static LPHANDLE handles = NULL;
4052 DWORD pos = 0;
4053
4054 if (handles == NULL)
4055 {
4056 handles = malloc(size * sizeof(HANDLE));
4057 *handles_ptr = handles;
4058 if (handles == NULL)
4059 {
4060 return ERROR_OUTOFMEMORY;
4061 }
4062 }
4063
4064 handles[pos++] = io_event;
4065
4066 if (!threads)
4067 {
4068 handles[pos++] = exit_event;
4069 }
4070
4071 while (threads)
4072 {
4073 if (pos == size)
4074 {
4075 LPHANDLE tmp;
4076 size += 10;
4077 tmp = realloc(handles, size * sizeof(HANDLE));
4078 if (tmp == NULL)
4079 {
4080 size -= 10;
4081 *count = pos;
4082 return ERROR_OUTOFMEMORY;
4083 }
4084 handles = tmp;
4085 *handles_ptr = handles;
4086 }
4087 handles[pos++] = threads->data;
4088 threads = threads->next;
4089 }
4090
4091 *count = pos;
4092 return NO_ERROR;
4093}
4094
4095
4096static VOID
4098{
4099 free(h);
4100}
4101
4102static BOOL
4103CmpHandle(LPVOID item, LPVOID hnd)
4104{
4105 return item == hnd;
4106}
4107
4108
4109VOID WINAPI
4110ServiceStartInteractiveOwn(DWORD dwArgc, LPWSTR *lpszArgv)
4111{
4112 status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
4113 ServiceStartInteractive(dwArgc, lpszArgv);
4114}
4115
4121static void
4123{
4124 BOOL changed = FALSE;
4125
4126 /* Clean up leftover NRPT rules */
4127 BOOL gpol_nrpt;
4128 changed = DeleteNrptRules(0, &gpol_nrpt);
4129
4130 /* Clean up leftover DNS search list fragments */
4131 HKEY key;
4132 BOOL gpol_list;
4133 GetDnsSearchListKey(NULL, &gpol_list, &key);
4134 if (key != INVALID_HANDLE_VALUE)
4135 {
4137 {
4138 changed = TRUE;
4139 }
4140 RegCloseKey(key);
4141 }
4142
4143 if (changed)
4144 {
4145 ApplyDnsSettings(gpol_nrpt || gpol_list);
4146 }
4147}
4148
4149VOID WINAPI
4150ServiceStartInteractive(DWORD dwArgc, LPWSTR *lpszArgv)
4151{
4152 HANDLE pipe, io_event = NULL;
4153 OVERLAPPED overlapped;
4154 DWORD error = NO_ERROR;
4155 list_item_t *threads = NULL;
4156 PHANDLE handles = NULL;
4157 DWORD handle_count;
4158
4159 service =
4160 RegisterServiceCtrlHandlerEx(interactive_service.name, ServiceCtrlInteractive, &status);
4161 if (!service)
4162 {
4163 return;
4164 }
4165
4166 status.dwCurrentState = SERVICE_START_PENDING;
4167 status.dwServiceSpecificExitCode = NO_ERROR;
4168 status.dwWin32ExitCode = NO_ERROR;
4169 status.dwWaitHint = 3000;
4171
4172 /* Clean up potentially left over registry values */
4174
4175 /* Read info from registry in key HKLM\SOFTWARE\OpenVPN */
4176 error = GetOpenvpnSettings(&settings);
4177 if (error != ERROR_SUCCESS)
4178 {
4179 goto out;
4180 }
4181
4182 io_event = InitOverlapped(&overlapped);
4183 exit_event = CreateEvent(NULL, TRUE, FALSE, NULL);
4184 if (!exit_event || !io_event)
4185 {
4186 error = MsgToEventLog(M_SYSERR, L"Could not create event");
4187 goto out;
4188 }
4189
4190 rdns_semaphore = CreateSemaphoreW(NULL, 1, 1, NULL);
4191 if (!rdns_semaphore)
4192 {
4193 error = MsgToEventLog(M_SYSERR, L"Could not create semaphore for register-dns");
4194 goto out;
4195 }
4196
4197 error = UpdateWaitHandles(&handles, &handle_count, io_event, threads);
4198 if (error != NO_ERROR)
4199 {
4200 goto out;
4201 }
4202
4203 pipe = CreateClientPipeInstance();
4204 if (pipe == INVALID_HANDLE_VALUE)
4205 {
4206 goto out;
4207 }
4208
4209 status.dwCurrentState = SERVICE_RUNNING;
4210 status.dwWaitHint = 0;
4212
4213 while (TRUE)
4214 {
4215 if (!ConnectNamedPipe(pipe, &overlapped))
4216 {
4217 DWORD connect_error = GetLastError();
4218 if (connect_error == ERROR_NO_DATA)
4219 {
4220 /*
4221 * Client connected and disconnected before we could process it.
4222 * Disconnect and retry instead of aborting the service.
4223 */
4224 MsgToEventLog(M_ERR, L"ConnectNamedPipe returned ERROR_NO_DATA (client dropped)");
4225 DisconnectNamedPipe(pipe);
4226 ResetOverlapped(&overlapped);
4227 continue;
4228 }
4229 else if (connect_error == ERROR_PIPE_CONNECTED)
4230 {
4231 /* No async I/O pending in this case; signal manually. */
4232 SetEvent(overlapped.hEvent);
4233 }
4234 else if (connect_error != ERROR_IO_PENDING)
4235 {
4236 MsgToEventLog(M_SYSERR, L"Could not connect pipe");
4237 break;
4238 }
4239 }
4240
4241 error = WaitForMultipleObjects(handle_count, handles, FALSE, INFINITE);
4242 if (error == WAIT_OBJECT_0)
4243 {
4244 /* Client connected, spawn a worker thread for it */
4245 HANDLE next_pipe = CreateClientPipeInstance();
4246
4247 /* Avoid exceeding WaitForMultipleObjects MAXIMUM_WAIT_OBJECTS */
4248 if (handle_count + 1 > MAXIMUM_WAIT_OBJECTS)
4249 {
4250 ReturnError(pipe, ERROR_CANT_WAIT, L"Too many concurrent clients", 1, &exit_event);
4251 CloseHandleEx(&pipe);
4252 pipe = next_pipe;
4253 ResetOverlapped(&overlapped);
4254 continue;
4255 }
4256
4257 HANDLE thread = CreateThread(NULL, 0, RunOpenvpn, pipe, CREATE_SUSPENDED, NULL);
4258 if (thread)
4259 {
4260 error = AddListItem(&threads, thread);
4261 if (!error)
4262 {
4263 error =
4264 UpdateWaitHandles(&handles, &handle_count, io_event, threads);
4265 }
4266 if (error)
4267 {
4268 ReturnError(pipe, error, L"Insufficient resources to service new clients", 1,
4269 &exit_event);
4270 /* Update wait handles again after removing the last worker thread */
4271 RemoveListItem(&threads, CmpHandle, thread);
4272 UpdateWaitHandles(&handles, &handle_count, io_event, threads);
4273 TerminateThread(thread, 1);
4274 CloseHandleEx(&thread);
4275 CloseHandleEx(&pipe);
4276 }
4277 else
4278 {
4279 ResumeThread(thread);
4280 }
4281 }
4282 else
4283 {
4284 CloseHandleEx(&pipe);
4285 }
4286
4287 ResetOverlapped(&overlapped);
4288 pipe = next_pipe;
4289 }
4290 else
4291 {
4292 CancelIo(pipe);
4293 if (error == WAIT_FAILED)
4294 {
4295 MsgToEventLog(M_SYSERR, L"WaitForMultipleObjects failed");
4296 SetEvent(exit_event);
4297 /* Give some time for worker threads to exit and then terminate */
4298 Sleep(1000);
4299 break;
4300 }
4301 if (!threads)
4302 {
4303 /* exit event signaled */
4304 CloseHandleEx(&pipe);
4305 ResetEvent(exit_event);
4306 error = NO_ERROR;
4307 break;
4308 }
4309
4310 /* Worker thread ended */
4311 HANDLE thread = RemoveListItem(&threads, CmpHandle, handles[error]);
4312 UpdateWaitHandles(&handles, &handle_count, io_event, threads);
4313 CloseHandleEx(&thread);
4314 }
4315 }
4316
4317out:
4318 FreeWaitHandles(handles);
4319 CloseHandleEx(&io_event);
4322
4323 status.dwCurrentState = SERVICE_STOPPED;
4324 status.dwWin32ExitCode = error;
4326}
wchar_t * utf8to16_size(const char *utf8, int size)
Convert a UTF-8 string to UTF-16.
Definition common.c:296
DWORD MsgToEventLog(DWORD flags, LPCWSTR format,...)
Definition common.c:254
LPCWSTR service_instance
Definition common.c:30
DWORD GetOpenvpnSettings(settings_t *s)
Definition common.c:77
#define PACKAGE_NAME
Definition config.h:492
#define PACKAGE
Definition config.h:486
#define M_INFO
Definition errlevel.h:54
static LSTATUS GetItfDnsServersV4(HKEY itf_key, PSTR addrs, PDWORD size)
Get DNS server IPv4 addresses of an interface.
static LSTATUS SetNameServerAddresses(PWSTR itf_id, const nrpt_address_t *addresses)
Set name servers from a NRPT address list.
static BOOL ValidateOptions(HANDLE pipe, const WCHAR *workdir, const WCHAR *cmdline, WCHAR *errmsg, DWORD capacity)
static VOID ReturnLastError(HANDLE pipe, LPCWSTR func)
static BOOL GetInterfacesKey(short family, PHKEY key)
Return the interfaces registry key for the specified address family.
static DWORD ReadPipeAsync(HANDLE pipe, LPVOID buffer, DWORD size, DWORD count, LPHANDLE events)
static void UndoNrptRules(DWORD ovpn_pid)
Delete a process' NRPT rules and apply the reduced set of rules.
static BOOL ApplyGpolSettings(void)
Signal the DNS resolver (and others potentially) to reload the group policy (DNS) settings.
static VOID ReturnProcessId(HANDLE pipe, DWORD pid, DWORD count, LPHANDLE events)
static BOOL GetDnsSearchListKey(PCSTR itf_name, PBOOL gpol, PHKEY key)
Find the registry key for storing the DNS domains for the VPN interface.
static DWORD HandleWINSConfigMessage(const wins_cfg_message_t *msg, undo_lists_t *lists)
static BOOL CmpAddress(LPVOID item, LPVOID address)
static LSTATUS GetItfDnsDomains(HKEY itf, PCWSTR search_domains, PWSTR domains, PDWORD size)
Return interface specific domain suffix(es)
static DWORD PeekNamedPipeAsyncTimed(HANDLE pipe, DWORD count, LPHANDLE events)
static DWORD PeekNamedPipeAsync(HANDLE pipe, DWORD count, LPHANDLE events)
static BOOL ResetOverlapped(LPOVERLAPPED overlapped)
static DWORD SetNameServers(PCWSTR itf_id, short family, PCSTR addrs)
Set the DNS name servers in a registry interface configuration.
static void SetNrptExcludeRules(HKEY nrpt_key, DWORD ovpn_pid, PCWSTR search_domains)
Set NRPT exclude rules to accompany a catch all rule.
static DWORD ExecCommand(const WCHAR *argv0, const WCHAR *cmdline, DWORD timeout)
static DWORD HandleEnableDHCPMessage(const enable_dhcp_message_t *dhcp)
static BOOL ResetDnsSearchDomains(HKEY key)
Reset the DNS search list to its original value.
static DWORD AddWfpBlock(const wfp_block_message_t *msg, undo_lists_t *lists)
static HANDLE CreateClientPipeInstance(VOID)
static DWORD DeleteWfpBlock(undo_lists_t *lists)
static void GetNrptExcludeData(PCWSTR search_domains, nrpt_exclude_data_t *data, size_t data_size)
Collect interface DNS settings to be used in excluding NRPT rules.
static DWORD SetNameServersValue(PCWSTR itf_id, short family, PCSTR value)
Set the DNS name servers in a registry interface configuration.
static BOOL GetStartupData(HANDLE pipe, STARTUP_DATA *sud)
static BOOL DeleteNrptRules(DWORD pid, PBOOL gpol)
Delete OpenVPN NRPT rules from the registry.
static VOID Undo(undo_lists_t *lists)
static BOOL ApplyDnsSettings(BOOL apply_gpol)
Signal the DNS resolver to reload its settings.
#define ERROR_STARTUP_DATA
Definition interactive.c:47
static DWORD WINAPI RunOpenvpn(LPVOID p)
static settings_t settings
Definition interactive.c:54
VOID WINAPI ServiceStartInteractive(DWORD dwArgc, LPWSTR *lpszArgv)
static BOOL IsUserEnvVar(PCWSTR var)
Check if an environment variable is user-specific.
static PWSTR MergeOpenvpnEnvironment(PCWSTR sys_env, PCWSTR usr_env)
Merge the environment for the openvpn process.
static DWORD DeleteRoute(PMIB_IPFORWARD_ROW2 fwd_row)
static SERVICE_STATUS status
Definition interactive.c:52
static DWORD HandleDNSConfigNrptMessage(const nrpt_dns_cfg_message_t *msg, DWORD ovpn_pid, undo_lists_t *lists)
Add Name Resolution Policy Table (NRPT) rules as documented in https://msdn.microsoft....
static DWORD SetDnsSearchDomains(PCSTR itf_name, PCSTR domains, PBOOL gpol, undo_lists_t *lists)
Add or remove DNS search domains.
static void CleanupRegistry(void)
Clean up remains of previous sessions in registry.
static DWORD netsh_wins_cmd(const wchar_t *action, DWORD if_index, const wchar_t *addr)
Run the command: netsh interface ip $action wins $if_index [static] $addr.
#define ERROR_MESSAGE_TYPE
Definition interactive.c:49
static LPVOID RemoveListItem(list_item_t **pfirst, match_fn_t match, LPVOID ctx)
static BOOL CmpHandle(LPVOID item, LPVOID hnd)
static BOOL ApplyGpolSettings64(void)
Signal the DNS resolver (and others potentially) to reload the group policy (DNS) settings on 64 bit ...
static DWORD HandleAddressMessage(address_message_t *msg, undo_lists_t *lists)
static VOID ReturnError(HANDLE pipe, DWORD error, LPCWSTR func, DWORD count, LPHANDLE events)
static DWORD AddListItem(list_item_t **pfirst, LPVOID data)
static void BlockDNSErrHandler(DWORD err, const char *msg)
static DWORD ResetNameServers(PCWSTR itf_id, short family)
Delete all DNS name servers from a registry interface configuration.
static LSTATUS OpenNrptBaseKey(PHKEY key, PBOOL gpol)
Return the registry key where NRPT rules are stored.
#define RDNS_TIMEOUT
Definition interactive.c:56
undo_type_t
Definition interactive.c:84
@ wfp_block
Definition interactive.c:87
@ _undo_type_max
Definition interactive.c:93
@ undo_dns6
Definition interactive.c:89
@ undo_dns4
Definition interactive.c:88
@ undo_wins
Definition interactive.c:92
@ route
Definition interactive.c:86
@ undo_nrpt
Definition interactive.c:90
@ address
Definition interactive.c:85
@ undo_domains
Definition interactive.c:91
static BOOL HasValidSearchList(HKEY key)
Check for a valid search list in a certain key of the registry.
static DWORD HandleRouteMessage(route_message_t *msg, undo_lists_t *lists)
static DWORD WINAPI RegisterDNS(LPVOID unused)
static HANDLE InitOverlapped(LPOVERLAPPED overlapped)
BOOL(* match_fn_t)(LPVOID item, LPVOID ctx)
static HANDLE CloseHandleEx(LPHANDLE handle)
static DWORD WINAPI ServiceCtrlInteractive(DWORD ctrl_code, DWORD event, LPVOID data, LPVOID ctx)
static BOOL StoreInitialDnsSearchList(HKEY key, PCWSTR list)
Prepare DNS domain "SearchList" registry value, so additional VPN domains can be added and its origin...
struct _list_item list_item_t
static DWORD RegWStringSize(PCWSTR string)
Return correct size for registry value to set for string.
static DWORD DeleteAddress(PMIB_UNICASTIPADDRESS_ROW addr_row)
static BOOL IsInterfaceConnected(PWSTR iid_str)
Check if an interface is connected and up.
#define ERROR_OPENVPN_STARTUP
Definition interactive.c:46
static DWORD SetNrptRules(HKEY nrpt_key, const nrpt_address_t *addresses, const char *domains, const char *search_domains, BOOL dnssec, DWORD ovpn_pid)
Set NRPT rules for a openvpn process.
static LSTATUS GetItfDnsServersV6(HKEY itf_key, PSTR addrs, PDWORD size)
Get DNS server IPv6 addresses of an interface.
static size_t EnvBlockSize(PCWSTR env)
Return the size of an environment block.
static BOOL AppendSearchList(PWSTR list, size_t list_cap, PCWSTR add)
Append a comma-separated list of domains to another comma-separated list, in place.
static DWORD SetNrptRule(HKEY nrpt_key, PCWSTR subkey, PCSTR address, PCWSTR domains, DWORD dom_size, BOOL dnssec)
Set a NRPT rule (subkey) and its values in the registry.
static BOOL AddDnsSearchDomains(HKEY key, BOOL have_list, PCWSTR domains)
Append domain suffixes to an existing search list.
static VOID FreeWaitHandles(LPHANDLE h)
openvpn_service_t interactive_service
Definition interactive.c:61
VOID WINAPI ServiceStartInteractiveOwn(DWORD dwArgc, LPWSTR *lpszArgv)
static size_t RemoveSearchListTokens(PWSTR list, PCWSTR remove)
Remove tokens from a comma-separated search list with multiset semantics: for each comma-separated to...
static DWORD AsyncPipeOp(async_op_t op, HANDLE pipe, LPVOID buffer, DWORD size, DWORD count, LPHANDLE events)
#define IO_TIMEOUT
Definition interactive.c:44
static BOOL ListContainsDomain(PCWSTR list, PCWSTR domain, size_t len)
Check if a domain is contained in a comma separated list of domains.
static BOOL IsDhcpEnabled(HKEY key)
Checks if DHCP is enabled for an interface.
static BOOL CreateOpenvpnEnvironment(HANDLE token, PWSTR *env)
Create the environment for the openvpn process.
static DWORD HandleFlushNeighborsMessage(flush_neighbors_message_t *msg)
static BOOL ApplyGpolSettings32(void)
Signal the DNS resolver (and others potentially) to reload the group policy (DNS) settings on 32 bit ...
static DWORD HandleMTUMessage(const set_mtu_message_t *mtu)
list_item_t * undo_lists_t[_undo_type_max]
Definition interactive.c:95
static VOID HandleMessage(HANDLE pipe, PPROCESS_INFORMATION proc_info, DWORD bytes, DWORD count, LPHANDLE events, undo_lists_t *lists)
static DWORD HandleRegisterDNSMessage(void)
static void RemoveDnsSearchDomains(HKEY key, PCWSTR domains)
Remove domain suffixes from an existing search list.
static BOOL InitialSearchListExists(HKEY key)
Check if a initial list had already been created.
#define ERROR_MESSAGE_DATA
Definition interactive.c:48
static HANDLE exit_event
Definition interactive.c:53
static VOID FreeStartupData(STARTUP_DATA *sud)
static DWORD HandleWfpBlockMessage(const wfp_block_message_t *msg, undo_lists_t *lists)
static HANDLE rdns_semaphore
Definition interactive.c:55
static DWORD UpdateWaitHandles(LPHANDLE *handles_ptr, LPDWORD count, HANDLE io_event, const list_item_t *threads)
static DWORD InterfaceLuid(const char *iface_name, PNET_LUID luid)
static LSTATUS ConvertItfDnsDomains(PCWSTR search_domains, PWSTR domains, PDWORD size, const DWORD capacity)
Convert interface specific domain suffix(es) from comma-separated string to MULTI_SZ string.
static BOOL CmpRoute(LPVOID item, LPVOID route)
static DWORD HandleDNSConfigMessage(const dns_cfg_message_t *msg, undo_lists_t *lists)
static BOOL CmpAny(LPVOID item, LPVOID any)
async_op_t
@ peek
@ write
@ peek_timed
@ read
static DWORD HandleCreateAdapterMessage(const create_adapter_message_t *msg)
Creates a VPN adapter of the specified type by invoking tapctl.exe.
static DWORD InterfaceIdString(PCSTR itf_name, PWSTR str, size_t len)
Get the string interface UUID (with braces) for an interface alias name.
static SERVICE_STATUS_HANDLE service
Definition interactive.c:51
static DWORD WritePipeAsync(HANDLE pipe, LPVOID data, DWORD size, DWORD count, LPHANDLE events)
static SOCKADDR_INET sockaddr_inet(short family, const inet_address_t *addr)
static void UndoDnsSearchDomains(dns_domains_undo_data_t *undo_data)
Removes DNS domains from a search list they were previously added to.
@ nrpt_dnssec
@ wfp_block_dns
Definition openvpn-msg.h:77
#define TUN_ADAPTER_INDEX_INVALID
Definition openvpn-msg.h:69
char nrpt_address_t[NRPT_ADDR_SIZE]
@ msg_add_nrpt_cfg
Definition openvpn-msg.h:38
@ msg_del_address
Definition openvpn-msg.h:33
@ msg_add_wins_cfg
Definition openvpn-msg.h:49
@ msg_add_address
Definition openvpn-msg.h:32
@ msg_del_wfp_block
Definition openvpn-msg.h:44
@ msg_enable_dhcp
Definition openvpn-msg.h:46
@ msg_add_wfp_block
Definition openvpn-msg.h:43
@ msg_add_route
Definition openvpn-msg.h:34
@ msg_create_adapter
Definition openvpn-msg.h:51
@ msg_del_wins_cfg
Definition openvpn-msg.h:50
@ msg_acknowledgement
Definition openvpn-msg.h:31
@ msg_add_dns_cfg
Definition openvpn-msg.h:36
@ msg_register_dns
Definition openvpn-msg.h:45
@ msg_del_nrpt_cfg
Definition openvpn-msg.h:39
@ msg_del_route
Definition openvpn-msg.h:35
@ msg_set_mtu
Definition openvpn-msg.h:48
@ msg_flush_neighbors
Definition openvpn-msg.h:42
@ msg_del_dns_cfg
Definition openvpn-msg.h:37
@ ADAPTER_TYPE_DCO
@ ADAPTER_TYPE_TAP
#define NRPT_ADDR_SIZE
#define NRPT_ADDR_NUM
#define M_ERR
Definition error.h:106
#define msg(flags,...)
Definition error.h:152
BOOL ReportStatusToSCMgr(SERVICE_STATUS_HANDLE service, SERVICE_STATUS *status)
Definition service.c:22
#define SERVICE_DEPENDENCIES
Definition service.h:37
#define M_SYSERR
Definition service.h:45
#define MSG_FLAGS_ERROR
Definition service.h:42
@ interactive
Definition service.h:50
static wchar_t * utf8to16(const char *utf8)
Convert a zero terminated UTF-8 string to UTF-16.
Definition service.h:122
static int pos(char c)
Definition base64.c:104
LPVOID data
Definition interactive.c:78
struct _list_item * next
Definition interactive.c:77
WCHAR * directory
Definition interactive.c:68
WCHAR * options
Definition interactive.c:69
WCHAR * std_input
Definition interactive.c:70
message_header_t header
Definition argv.h:35
Wrapper structure for dynamically allocated memory.
Definition buffer.h:71
Definition dhcp.h:62
interface_t iface
char name[256]
Definition openvpn-msg.h:71
Container for unidirectional cipher and HMAC key material.
Definition crypto.h:152
message_type_t type
Definition openvpn-msg.h:56
nrpt_address_t addresses[NRPT_ADDR_NUM]
CHAR addresses[NRPT_ADDR_NUM *NRPT_ADDR_SIZE]
interface_t iface
WCHAR ovpn_admin_group[MAX_NAME]
Definition service.h:71
WCHAR bin_dir[MAX_PATH]
Definition service.h:68
WCHAR ovpn_service_user[MAX_NAME]
Definition service.h:72
DWORD priority
Definition service.h:73
WCHAR exe_path[MAX_PATH]
Definition service.h:66
#define _L(q)
Definition basic.h:38
static int cleanup(void **state)
const char * msg2
const char * msg1
struct in6_addr ipv6
Definition openvpn-msg.h:64
struct in_addr ipv4
Definition openvpn-msg.h:63
dns_cfg_message_t dns
address_message_t address
flush_neighbors_message_t flush_neighbors
wfp_block_message_t wfp_block
message_header_t header
wins_cfg_message_t wins
enable_dhcp_message_t dhcp
route_message_t route
nrpt_dns_cfg_message_t nrpt_dns
set_mtu_message_t mtu
create_adapter_message_t create_adapter
BOOL IsAuthorizedUser(PSID sid, const HANDLE token, const WCHAR *ovpn_admin_group, const WCHAR *ovpn_service_user)
Definition validate.c:149
BOOL CheckOption(const WCHAR *workdir, int argc, WCHAR *argv[], const settings_t *s)
Definition validate.c:329
static BOOL IsOption(const WCHAR *o)
Definition validate.h:46
int get_interface_metric(const NET_IFINDEX index, const ADDRESS_FAMILY family, int *is_auto)
Return interface metric value for the specified interface index.
Definition wfp_block.c:369
DWORD set_interface_metric(const NET_IFINDEX index, const ADDRESS_FAMILY family, const ULONG metric)
Sets interface metric value for specified interface index.
Definition wfp_block.c:408
DWORD delete_wfp_block_filters(HANDLE engine_handle)
Definition wfp_block.c:344
DWORD add_wfp_block_filters(HANDLE *engine_handle, int index, const WCHAR *exe_path, wfp_block_msg_handler_t msg_handler, BOOL dns_only)
Definition wfp_block.c:153
#define WFP_BLOCK_IFACE_METRIC
Definition wfp_block.h:33
char * get_win_sys_path(void)
Definition win32.c:1157