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
61openvpn_service_t interactive_service = { interactive, _L(PACKAGE_NAME) L"ServiceInteractive",
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{
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 * Returns true on success, false on error with reason set in errmsg.
361 */
362static BOOL
363ValidateOptions(HANDLE pipe, const WCHAR *workdir, const WCHAR *options, WCHAR *errmsg,
364 DWORD capacity)
365{
366 WCHAR **argv;
367 int argc;
368 BOOL ret = FALSE;
369 int i;
370 const WCHAR *msg1 = L"You have specified a config file location (%ls relative to %ls)"
371 L" that requires admin approval. This error may be avoided"
372 L" by adding your account to the \"%ls\" group";
373
374 const WCHAR *msg2 = L"You have specified an option (%ls) that may be used"
375 L" only with admin approval. This error may be avoided"
376 L" by adding your account to the \"%ls\" group";
377
378 argv = CommandLineToArgvW(options, &argc);
379
380 if (!argv)
381 {
382 swprintf(errmsg, capacity,
383 L"Cannot validate options: CommandLineToArgvW failed with error = 0x%08lx",
384 GetLastError());
385 goto out;
386 }
387
388 /* Note: argv[0] is the first option */
389 if (argc < 1) /* no options */
390 {
391 ret = TRUE;
392 goto out;
393 }
394
395 /*
396 * If only one argument, it is the config file
397 */
398 if (argc == 1)
399 {
400 WCHAR *argv_tmp[2] = { L"--config", argv[0] };
401
402 if (!CheckOption(workdir, 2, argv_tmp, &settings))
403 {
404 swprintf(errmsg, capacity, msg1, argv[0], workdir, settings.ovpn_admin_group);
405 }
406 goto out;
407 }
408
409 for (i = 0; i < argc; ++i)
410 {
411 if (!IsOption(argv[i]))
412 {
413 continue;
414 }
415
416 if (!CheckOption(workdir, argc - i, &argv[i], &settings))
417 {
418 if (wcscmp(L"--config", argv[i]) == 0 && argc - i > 1)
419 {
420 swprintf(errmsg, capacity, msg1, argv[i + 1], workdir, settings.ovpn_admin_group);
421 }
422 else
423 {
424 swprintf(errmsg, capacity, msg2, argv[i], settings.ovpn_admin_group);
425 }
426 goto out;
427 }
428 }
429
430 /* all options passed */
431 ret = TRUE;
432
433out:
434 if (argv)
435 {
436 LocalFree(argv);
437 }
438 return ret;
439}
440
441static BOOL
442GetStartupData(HANDLE pipe, STARTUP_DATA *sud)
443{
444 size_t size, len;
445 WCHAR *data = NULL;
446 DWORD bytes, read;
447
448 bytes = PeekNamedPipeAsyncTimed(pipe, 1, &exit_event);
449 if (bytes == 0)
450 {
451 MsgToEventLog(M_ERR, L"Timeout waiting for startup data");
452 ReturnError(pipe, ERROR_STARTUP_DATA, L"GetStartupData (timeout)", 1, &exit_event);
453 goto err;
454 }
455
456 size = bytes / sizeof(*data);
457 if ((size == 0) || (size > 4096)) /* our startup data is 1024 wchars at the moment */
458 {
459 MsgToEventLog(M_SYSERR, L"malformed startup data: %lu bytes received", size);
460 ReturnError(pipe, ERROR_STARTUP_DATA, L"GetStartupData", 1, &exit_event);
461 goto err;
462 }
463
464 data = malloc(bytes);
465 if (data == NULL)
466 {
467 MsgToEventLog(M_SYSERR, L"malloc failed");
468 ReturnLastError(pipe, L"malloc");
469 goto err;
470 }
471
472 read = ReadPipeAsync(pipe, data, bytes, 1, &exit_event);
473 if (bytes != read)
474 {
475 MsgToEventLog(M_SYSERR, L"ReadPipeAsync failed");
476 ReturnLastError(pipe, L"ReadPipeAsync");
477 goto err;
478 }
479
480 if (data[size - 1] != 0)
481 {
482 MsgToEventLog(M_ERR, L"Startup data is not NULL terminated");
483 ReturnError(pipe, ERROR_STARTUP_DATA, L"GetStartupData", 1, &exit_event);
484 goto err;
485 }
486
487 sud->directory = data;
488 len = wcslen(sud->directory) + 1;
489 size -= len;
490 if (size == 0)
491 {
492 MsgToEventLog(M_ERR, L"Startup data ends at working directory");
493 ReturnError(pipe, ERROR_STARTUP_DATA, L"GetStartupData", 1, &exit_event);
494 goto err;
495 }
496
497 sud->options = sud->directory + len;
498 len = wcslen(sud->options) + 1;
499 size -= len;
500 if (size == 0)
501 {
502 MsgToEventLog(M_ERR, L"Startup data ends at command line options");
503 ReturnError(pipe, ERROR_STARTUP_DATA, L"GetStartupData", 1, &exit_event);
504 goto err;
505 }
506
507 sud->std_input = sud->options + len;
508 return TRUE;
509
510err:
511 sud->directory = NULL; /* caller must not free() */
512 free(data);
513 return FALSE;
514}
515
516
517static VOID
519{
520 free(sud->directory);
521}
522
523
524static SOCKADDR_INET
525sockaddr_inet(short family, inet_address_t *addr)
526{
527 SOCKADDR_INET sa_inet;
528 ZeroMemory(&sa_inet, sizeof(sa_inet));
529 sa_inet.si_family = family;
530 if (family == AF_INET)
531 {
532 sa_inet.Ipv4.sin_addr = addr->ipv4;
533 }
534 else if (family == AF_INET6)
535 {
536 sa_inet.Ipv6.sin6_addr = addr->ipv6;
537 }
538 return sa_inet;
539}
540
541static DWORD
542InterfaceLuid(const char *iface_name, PNET_LUID luid)
543{
544 NETIO_STATUS convert_status;
545 LPWSTR wide_name = utf8to16(iface_name);
546
547 if (wide_name)
548 {
549 convert_status = ConvertInterfaceAliasToLuid(wide_name, luid);
550 free(wide_name);
551 }
552 else
553 {
554 convert_status = ERROR_OUTOFMEMORY;
555 }
556 return convert_status;
557}
558
559static BOOL
560CmpAddress(LPVOID item, LPVOID address)
561{
562 return memcmp(item, address, sizeof(MIB_UNICASTIPADDRESS_ROW)) == 0 ? TRUE : FALSE;
563}
564
565static DWORD
566DeleteAddress(PMIB_UNICASTIPADDRESS_ROW addr_row)
567{
568 return DeleteUnicastIpAddressEntry(addr_row);
569}
570
571static DWORD
573{
574 DWORD err;
575 PMIB_UNICASTIPADDRESS_ROW addr_row;
576 BOOL add = msg->header.type == msg_add_address;
577
578 addr_row = malloc(sizeof(*addr_row));
579 if (addr_row == NULL)
580 {
581 return ERROR_OUTOFMEMORY;
582 }
583
584 InitializeUnicastIpAddressEntry(addr_row);
585 addr_row->Address = sockaddr_inet(msg->family, &msg->address);
586 addr_row->OnLinkPrefixLength = (UINT8)msg->prefix_len;
587
588 if (msg->iface.index != TUN_ADAPTER_INDEX_INVALID)
589 {
590 addr_row->InterfaceIndex = msg->iface.index;
591 }
592 else
593 {
594 NET_LUID luid;
595 err = InterfaceLuid(msg->iface.name, &luid);
596 if (err)
597 {
598 goto out;
599 }
600 addr_row->InterfaceLuid = luid;
601 }
602
603 if (add)
604 {
605 err = CreateUnicastIpAddressEntry(addr_row);
606 if (err)
607 {
608 goto out;
609 }
610
611 err = AddListItem(&(*lists)[address], addr_row);
612 if (err)
613 {
614 DeleteAddress(addr_row);
615 }
616 }
617 else
618 {
619 err = DeleteAddress(addr_row);
620 if (err)
621 {
622 goto out;
623 }
624
625 free(RemoveListItem(&(*lists)[address], CmpAddress, addr_row));
626 }
627
628out:
629 if (!add || err)
630 {
631 free(addr_row);
632 }
633
634 return err;
635}
636
637static BOOL
638CmpRoute(LPVOID item, LPVOID route)
639{
640 return memcmp(item, route, sizeof(MIB_IPFORWARD_ROW2)) == 0 ? TRUE : FALSE;
641}
642
643static DWORD
644DeleteRoute(PMIB_IPFORWARD_ROW2 fwd_row)
645{
646 return DeleteIpForwardEntry2(fwd_row);
647}
648
649static DWORD
651{
652 DWORD err;
653 PMIB_IPFORWARD_ROW2 fwd_row;
654 BOOL add = msg->header.type == msg_add_route;
655
656 fwd_row = malloc(sizeof(*fwd_row));
657 if (fwd_row == NULL)
658 {
659 return ERROR_OUTOFMEMORY;
660 }
661
662 ZeroMemory(fwd_row, sizeof(*fwd_row));
663 fwd_row->ValidLifetime = 0xffffffff;
664 fwd_row->PreferredLifetime = 0xffffffff;
665 fwd_row->Protocol = MIB_IPPROTO_NETMGMT;
666 fwd_row->Metric = msg->metric;
667 fwd_row->DestinationPrefix.Prefix = sockaddr_inet(msg->family, &msg->prefix);
668 fwd_row->DestinationPrefix.PrefixLength = (UINT8)msg->prefix_len;
669 fwd_row->NextHop = sockaddr_inet(msg->family, &msg->gateway);
670
671 if (msg->iface.index != TUN_ADAPTER_INDEX_INVALID)
672 {
673 fwd_row->InterfaceIndex = msg->iface.index;
674 }
675 else if (strlen(msg->iface.name))
676 {
677 NET_LUID luid;
678 err = InterfaceLuid(msg->iface.name, &luid);
679 if (err)
680 {
681 goto out;
682 }
683 fwd_row->InterfaceLuid = luid;
684 }
685
686 if (add)
687 {
688 err = CreateIpForwardEntry2(fwd_row);
689 if (err)
690 {
691 goto out;
692 }
693
694 err = AddListItem(&(*lists)[route], fwd_row);
695 if (err)
696 {
697 DeleteRoute(fwd_row);
698 }
699 }
700 else
701 {
702 err = DeleteRoute(fwd_row);
703 if (err)
704 {
705 goto out;
706 }
707
708 free(RemoveListItem(&(*lists)[route], CmpRoute, fwd_row));
709 }
710
711out:
712 if (!add || err)
713 {
714 free(fwd_row);
715 }
716
717 return err;
718}
719
720
721static DWORD
723{
724 if (msg->family == AF_INET)
725 {
726 return FlushIpNetTable(msg->iface.index);
727 }
728
729 return FlushIpNetTable2(msg->family, msg->iface.index);
730}
731
732static void
733BlockDNSErrHandler(DWORD err, const char *msg)
734{
735 WCHAR buf[256];
736 LPCWSTR err_str;
737
738 if (!err)
739 {
740 return;
741 }
742
743 err_str = L"Unknown Win32 Error";
744
745 if (FormatMessageW(FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM,
746 NULL, err, 0, buf, _countof(buf), NULL))
747 {
748 err_str = buf;
749 }
750
751 MsgToEventLog(M_ERR, L"%hs (status = %lu): %ls", msg, err, err_str);
752}
753
754/* Use an always-true match_fn to get the head of the list */
755static BOOL
756CmpAny(LPVOID item, LPVOID any)
757{
758 return TRUE;
759}
760
761static DWORD
763{
764 DWORD err = 0;
765 wfp_block_data_t *block_data = RemoveListItem(&(*lists)[wfp_block], CmpAny, NULL);
766
767 if (block_data)
768 {
769 err = delete_wfp_block_filters(block_data->engine);
770 if (block_data->metric_v4 >= 0)
771 {
772 set_interface_metric(block_data->index, AF_INET, block_data->metric_v4);
773 }
774 if (block_data->metric_v6 >= 0)
775 {
776 set_interface_metric(block_data->index, AF_INET6, block_data->metric_v6);
777 }
778 free(block_data);
779 }
780 else
781 {
782 MsgToEventLog(M_ERR, L"No previous block filters to delete");
783 }
784
785 return err;
786}
787
788static DWORD
790{
791 DWORD err = 0;
792 wfp_block_data_t *block_data = NULL;
793 HANDLE engine = NULL;
794 LPCWSTR exe_path;
795 BOOL dns_only;
796
797 exe_path = settings.exe_path;
798 dns_only = (msg->flags == wfp_block_dns);
799
800 err = add_wfp_block_filters(&engine, msg->iface.index, exe_path, BlockDNSErrHandler, dns_only);
801 if (!err)
802 {
803 block_data = malloc(sizeof(wfp_block_data_t));
804 if (!block_data)
805 {
806 err = ERROR_OUTOFMEMORY;
807 goto out;
808 }
809 block_data->engine = engine;
810 block_data->index = msg->iface.index;
811 int is_auto = 0;
812 block_data->metric_v4 = get_interface_metric(msg->iface.index, AF_INET, &is_auto);
813 if (is_auto)
814 {
815 block_data->metric_v4 = 0;
816 }
817 block_data->metric_v6 = get_interface_metric(msg->iface.index, AF_INET6, &is_auto);
818 if (is_auto)
819 {
820 block_data->metric_v6 = 0;
821 }
822
823 err = AddListItem(&(*lists)[wfp_block], block_data);
824 if (!err)
825 {
826 err = set_interface_metric(msg->iface.index, AF_INET, WFP_BLOCK_IFACE_METRIC);
827 if (!err)
828 {
829 /* for IPv6, we intentionally ignore errors, because
830 * otherwise block-dns activation will fail if a user or
831 * admin has disabled IPv6 on the tun/tap/dco interface
832 * (if OpenVPN wants IPv6 ifconfig, we'll fail there)
833 */
834 set_interface_metric(msg->iface.index, AF_INET6, WFP_BLOCK_IFACE_METRIC);
835 }
836 if (err)
837 {
838 /* delete the filters, remove undo item and free interface data */
839 DeleteWfpBlock(lists);
840 engine = NULL;
841 }
842 }
843 }
844
845out:
846 if (err && engine)
847 {
849 free(block_data);
850 }
851
852 return err;
853}
854
855static DWORD
857{
858 if (msg->header.type == msg_add_wfp_block)
859 {
860 return AddWfpBlock(msg, lists);
861 }
862 else
863 {
864 return DeleteWfpBlock(lists);
865 }
866}
867
868/*
869 * Execute a command and return its exit code. If timeout > 0, terminate
870 * the process if still running after timeout milliseconds. In that case
871 * the return value is the windows error code WAIT_TIMEOUT = 0x102
872 */
873static DWORD
874ExecCommand(const WCHAR *argv0, const WCHAR *cmdline, DWORD timeout)
875{
876 DWORD exit_code;
877 STARTUPINFOW si;
878 PROCESS_INFORMATION pi;
879 DWORD proc_flags = CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT;
880 WCHAR *cmdline_dup = NULL;
881
882 ZeroMemory(&si, sizeof(si));
883 ZeroMemory(&pi, sizeof(pi));
884
885 si.cb = sizeof(si);
886
887 /* CreateProcess needs a modifiable cmdline: make a copy */
888 cmdline_dup = _wcsdup(cmdline);
889 if (cmdline_dup
890 && CreateProcessW(argv0, cmdline_dup, NULL, NULL, FALSE, proc_flags, NULL, NULL, &si, &pi))
891 {
892 WaitForSingleObject(pi.hProcess, timeout ? timeout : INFINITE);
893 if (!GetExitCodeProcess(pi.hProcess, &exit_code))
894 {
895 MsgToEventLog(M_SYSERR, L"ExecCommand: Error getting exit_code:");
896 exit_code = GetLastError();
897 }
898 else if (exit_code == STILL_ACTIVE)
899 {
900 exit_code = WAIT_TIMEOUT; /* Windows error code 0x102 */
901
902 /* kill without impunity */
903 TerminateProcess(pi.hProcess, exit_code);
904 MsgToEventLog(M_ERR, L"ExecCommand: \"%ls %ls\" killed after timeout", argv0, cmdline);
905 }
906 else if (exit_code)
907 {
908 MsgToEventLog(M_ERR, L"ExecCommand: \"%ls %ls\" exited with status = %lu", argv0,
909 cmdline, exit_code);
910 }
911 else
912 {
913 MsgToEventLog(M_INFO, L"ExecCommand: \"%ls %ls\" completed", argv0, cmdline);
914 }
915
916 CloseHandle(pi.hProcess);
917 CloseHandle(pi.hThread);
918 }
919 else
920 {
921 exit_code = GetLastError();
922 MsgToEventLog(M_SYSERR, L"ExecCommand: could not run \"%ls %ls\" :", argv0, cmdline);
923 }
924
925 free(cmdline_dup);
926 return exit_code;
927}
928
929/*
930 * Entry point for register-dns thread.
931 */
932static DWORD WINAPI
933RegisterDNS(LPVOID unused)
934{
935 DWORD err;
936 DWORD timeout = RDNS_TIMEOUT * 1000; /* in milliseconds */
937
938 /* path of ipconfig command */
939 WCHAR ipcfg[MAX_PATH];
940
941 struct
942 {
943 WCHAR *argv0;
944 WCHAR *cmdline;
945 DWORD timeout;
946 } cmds[] = {
947 { ipcfg, L"ipconfig /flushdns", timeout },
948 { ipcfg, L"ipconfig /registerdns", timeout },
949 };
950
951 HANDLE wait_handles[2] = { rdns_semaphore, exit_event };
952
953 swprintf(ipcfg, MAX_PATH, L"%ls\\%ls", get_win_sys_path(), L"ipconfig.exe");
954
955 if (WaitForMultipleObjects(2, wait_handles, FALSE, timeout) == WAIT_OBJECT_0)
956 {
957 /* Semaphore locked */
958 for (size_t i = 0; i < _countof(cmds); ++i)
959 {
960 ExecCommand(cmds[i].argv0, cmds[i].cmdline, cmds[i].timeout);
961 }
962 err = 0;
963 if (!ReleaseSemaphore(rdns_semaphore, 1, NULL))
964 {
965 err =
966 MsgToEventLog(M_SYSERR, L"RegisterDNS: Failed to release regsiter-dns semaphore:");
967 }
968 }
969 else
970 {
971 MsgToEventLog(M_ERR, L"RegisterDNS: Failed to lock register-dns semaphore");
972 err = ERROR_SEM_TIMEOUT; /* Windows error code 0x79 */
973 }
974 return err;
975}
976
977static DWORD
979{
980 DWORD err;
981 HANDLE thread = NULL;
982
983 /* Delegate this job to a sub-thread */
984 thread = CreateThread(NULL, 0, RegisterDNS, NULL, 0, NULL);
985
986 /*
987 * We don't add these thread handles to the undo list -- the thread and
988 * processes it spawns are all supposed to terminate or timeout by themselves.
989 */
990 if (thread)
991 {
992 err = 0;
993 CloseHandle(thread);
994 }
995 else
996 {
997 err = GetLastError();
998 }
999
1000 return err;
1001}
1002
1012static DWORD
1013netsh_wins_cmd(const wchar_t *action, DWORD if_index, const wchar_t *addr)
1014{
1015 DWORD err = 0;
1016 int timeout = 30000; /* in msec */
1017 wchar_t argv0[MAX_PATH];
1018 wchar_t *cmdline = NULL;
1019 const wchar_t *addr_static = (wcscmp(action, L"set") == 0) ? L"static" : L"";
1020
1021 if (!addr)
1022 {
1023 if (wcscmp(action, L"delete") == 0)
1024 {
1025 addr = L"all";
1026 }
1027 else /* nothing to do -- return success*/
1028 {
1029 goto out;
1030 }
1031 }
1032
1033 /* Path of netsh */
1034 swprintf(argv0, _countof(argv0), L"%ls\\%ls", get_win_sys_path(), L"netsh.exe");
1035
1036 /* cmd template:
1037 * netsh interface ip $action wins $if_name $static $addr
1038 */
1039 const wchar_t *fmt = L"netsh interface ip %ls wins %lu %ls %ls";
1040
1041 /* max cmdline length in wchars -- include room for worst case and some */
1042 size_t ncmdline = wcslen(fmt) + 11 /*if_index*/ + wcslen(action) + wcslen(addr)
1043 + wcslen(addr_static) + 32 + 1;
1044 cmdline = malloc(ncmdline * sizeof(wchar_t));
1045 if (!cmdline)
1046 {
1047 err = ERROR_OUTOFMEMORY;
1048 goto out;
1049 }
1050
1051 swprintf(cmdline, ncmdline, fmt, action, if_index, addr_static, addr);
1052
1053 err = ExecCommand(argv0, cmdline, timeout);
1054
1055out:
1056 free(cmdline);
1057 return err;
1058}
1059
1066static BOOL
1068{
1069 typedef NTSTATUS(__stdcall * publish_fn_t)(DWORD StateNameLo, DWORD StateNameHi, DWORD TypeId,
1070 DWORD Buffer, DWORD Length, DWORD ExplicitScope);
1071 publish_fn_t RtlPublishWnfStateData;
1072 const DWORD WNF_GPOL_SYSTEM_CHANGES_HI = 0x0D891E2A;
1073 const DWORD WNF_GPOL_SYSTEM_CHANGES_LO = 0xA3BC0875;
1074 BOOL ret = FALSE;
1075
1076 HMODULE ntdll = LoadLibraryA("ntdll.dll");
1077 if (ntdll == NULL)
1078 {
1079 return FALSE;
1080 }
1081
1082 RtlPublishWnfStateData = (publish_fn_t)GetProcAddress(ntdll, "RtlPublishWnfStateData");
1083 if (RtlPublishWnfStateData == NULL)
1084 {
1085 goto cleanup;
1086 }
1087
1088 if (RtlPublishWnfStateData(WNF_GPOL_SYSTEM_CHANGES_LO, WNF_GPOL_SYSTEM_CHANGES_HI, 0, 0, 0, 0)
1089 != ERROR_SUCCESS)
1090 {
1091 goto cleanup;
1092 }
1093
1094 ret = TRUE;
1095cleanup:
1096 FreeLibrary(ntdll);
1097 return ret;
1098}
1099
1106static BOOL
1108{
1109 typedef NTSTATUS (*publish_fn_t)(INT64 StateName, INT64 TypeId, INT64 Buffer,
1110 unsigned int Length, INT64 ExplicitScope);
1111 publish_fn_t RtlPublishWnfStateData;
1112 const INT64 WNF_GPOL_SYSTEM_CHANGES = 0x0D891E2AA3BC0875;
1113 BOOL ret = FALSE;
1114
1115 HMODULE ntdll = LoadLibraryA("ntdll.dll");
1116 if (ntdll == NULL)
1117 {
1118 return FALSE;
1119 }
1120
1121 RtlPublishWnfStateData = (publish_fn_t)GetProcAddress(ntdll, "RtlPublishWnfStateData");
1122 if (RtlPublishWnfStateData == NULL)
1123 {
1124 goto cleanup;
1125 }
1126
1127 if (RtlPublishWnfStateData(WNF_GPOL_SYSTEM_CHANGES, 0, 0, 0, 0) != ERROR_SUCCESS)
1128 {
1129 goto cleanup;
1130 }
1131
1132 ret = TRUE;
1133cleanup:
1134 FreeLibrary(ntdll);
1135 return ret;
1136}
1137
1143static BOOL
1145{
1146 SYSTEM_INFO si;
1147 GetSystemInfo(&si);
1148 const BOOL win_32bit = si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_INTEL;
1149 return win_32bit ? ApplyGpolSettings32() : ApplyGpolSettings64();
1150}
1151
1159static BOOL
1160ApplyDnsSettings(BOOL apply_gpol)
1161{
1162 BOOL res = FALSE;
1163 SC_HANDLE scm = NULL;
1164 SC_HANDLE dnssvc = NULL;
1165
1166 if (apply_gpol && ApplyGpolSettings() == FALSE)
1167 {
1168 MsgToEventLog(M_ERR, L"%S: sending GPOL notification failed", __func__);
1169 }
1170
1171 scm = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
1172 if (scm == NULL)
1173 {
1174 MsgToEventLog(M_ERR, L"%S: OpenSCManager call failed (%lu)", __func__, GetLastError());
1175 goto out;
1176 }
1177
1178 dnssvc = OpenServiceA(scm, "Dnscache", SERVICE_PAUSE_CONTINUE);
1179 if (dnssvc == NULL)
1180 {
1181 MsgToEventLog(M_ERR, L"%S: OpenService call failed (%lu)", __func__, GetLastError());
1182 goto out;
1183 }
1184
1185 SERVICE_STATUS control_status;
1186 if (ControlService(dnssvc, SERVICE_CONTROL_PARAMCHANGE, &control_status) == 0)
1187 {
1188 MsgToEventLog(M_ERR, L"%S: ControlService call failed (%lu)", __func__, GetLastError());
1189 goto out;
1190 }
1191
1192 res = TRUE;
1193
1194out:
1195 if (dnssvc)
1196 {
1197 CloseServiceHandle(dnssvc);
1198 }
1199 if (scm)
1200 {
1201 CloseServiceHandle(scm);
1202 }
1203 return res;
1204}
1205
1215static DWORD
1216InterfaceIdString(PCSTR itf_name, PWSTR str, size_t len)
1217{
1218 DWORD err;
1219 GUID guid;
1220 NET_LUID luid;
1221 PWSTR iid_str = NULL;
1222
1223 err = InterfaceLuid(itf_name, &luid);
1224 if (err)
1225 {
1226 PWSTR wide_name = utf8to16(itf_name);
1227 MsgToEventLog(M_ERR, L"%S: failed to convert itf alias '%s'", __func__, wide_name);
1228 free(wide_name);
1229 goto out;
1230 }
1231 err = ConvertInterfaceLuidToGuid(&luid, &guid);
1232 if (err)
1233 {
1234 PWSTR wide_name = utf8to16(itf_name);
1235 MsgToEventLog(M_ERR, L"%S: Failed to convert itf '%s' LUID", __func__, wide_name);
1236 free(wide_name);
1237 goto out;
1238 }
1239
1240 if (StringFromIID(&guid, &iid_str) != S_OK)
1241 {
1242 PWSTR wide_name = utf8to16(itf_name);
1243 MsgToEventLog(M_ERR, L"%S: Failed to convert itf '%s' IID", __func__, wide_name);
1244 free(wide_name);
1245 err = ERROR_OUTOFMEMORY;
1246 goto out;
1247 }
1248 if (wcslen(iid_str) + 1 > len)
1249 {
1250 err = ERROR_INVALID_PARAMETER;
1251 goto out;
1252 }
1253
1254 wcsncpy(str, iid_str, len);
1255
1256out:
1257 if (iid_str)
1258 {
1259 CoTaskMemFree(iid_str);
1260 }
1261 return err;
1262}
1263
1277static BOOL
1279{
1280 char data[64];
1281 DWORD size = sizeof(data);
1282 LSTATUS err = RegGetValueA(key, NULL, "SearchList", RRF_RT_REG_SZ, NULL, (PBYTE)data, &size);
1283 if (!err || err == ERROR_MORE_DATA)
1284 {
1285 data[sizeof(data) - 1] = '\0';
1286 for (size_t i = 0; i < strlen(data); ++i)
1287 {
1288 if (isalnum(data[i]) || data[i] == '-' || data[i] == '.')
1289 {
1290 return TRUE;
1291 }
1292 }
1293 }
1294 return FALSE;
1295}
1296
1314static BOOL
1315GetDnsSearchListKey(PCSTR itf_name, PBOOL gpol, PHKEY key)
1316{
1317 LSTATUS err;
1318
1319 *gpol = FALSE;
1320
1321 /* Try the group policy search list */
1322 err = RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SOFTWARE\\Policies\\Microsoft\\Windows NT\\DNSClient",
1323 0, KEY_ALL_ACCESS, key);
1324 if (!err)
1325 {
1326 if (HasValidSearchList(*key))
1327 {
1328 *gpol = TRUE;
1329 return TRUE;
1330 }
1331 RegCloseKey(*key);
1332 }
1333
1334 /* Try the system-wide search list */
1335 err =
1336 RegOpenKeyExA(HKEY_LOCAL_MACHINE, "System\\CurrentControlSet\\Services\\TCPIP\\Parameters",
1337 0, KEY_ALL_ACCESS, key);
1338 if (!err)
1339 {
1340 if (HasValidSearchList(*key))
1341 {
1342 return TRUE;
1343 }
1344 RegCloseKey(*key);
1345 }
1346
1347 if (itf_name)
1348 {
1349 /* Always return the VPN interface key (if it exists) */
1350 WCHAR iid[64];
1351 DWORD iid_err = InterfaceIdString(itf_name, iid, _countof(iid));
1352 if (!iid_err)
1353 {
1354 HKEY itfs;
1355 err =
1356 RegOpenKeyExA(HKEY_LOCAL_MACHINE,
1357 "System\\CurrentControlSet\\Services\\TCPIP\\Parameters\\Interfaces",
1358 0, KEY_ALL_ACCESS, &itfs);
1359 if (!err)
1360 {
1361 err = RegOpenKeyExW(itfs, iid, 0, KEY_ALL_ACCESS, key);
1362 RegCloseKey(itfs);
1363 if (!err)
1364 {
1365 return FALSE; /* No need to preserve the VPN itf search list */
1366 }
1367 }
1368 }
1369 }
1370
1371 *key = INVALID_HANDLE_VALUE;
1372 return FALSE;
1373}
1374
1382static BOOL
1384{
1385 LSTATUS err;
1386
1387 err = RegGetValueA(key, NULL, "InitialSearchList", RRF_RT_REG_SZ, NULL, NULL, NULL);
1388 if (err)
1389 {
1390 if (err == ERROR_FILE_NOT_FOUND)
1391 {
1392 return FALSE;
1393 }
1394 MsgToEventLog(M_ERR, L"%S: failed to get InitialSearchList (%lu)", __func__, err);
1395 }
1396
1397 return TRUE;
1398}
1399
1404static DWORD
1405RegWStringSize(PCWSTR string)
1406{
1407 size_t length = (wcslen(string) + 1) * sizeof(wchar_t);
1408 if (length > UINT_MAX)
1409 {
1410 length = UINT_MAX;
1411 }
1412 return (DWORD)length;
1413}
1414
1425static BOOL
1427{
1428 if (!list || wcslen(list) == 0)
1429 {
1430 MsgToEventLog(M_ERR, L"%S: empty search list", __func__);
1431 return FALSE;
1432 }
1433
1435 {
1436 /* Initial list had already been stored */
1437 return TRUE;
1438 }
1439
1440 DWORD size = RegWStringSize(list);
1441 LSTATUS err = RegSetValueExW(key, L"InitialSearchList", 0, REG_SZ, (PBYTE)list, size);
1442 if (err)
1443 {
1444 MsgToEventLog(M_ERR, L"%S: failed to set InitialSearchList value (%lu)", __func__, err);
1445 return FALSE;
1446 }
1447
1448 return TRUE;
1449}
1450
1465static BOOL
1466AppendSearchList(PWSTR list, size_t list_cap, PCWSTR add)
1467{
1468 size_t list_len = wcslen(list);
1469 size_t add_len = wcslen(add);
1470 if (add_len == 0)
1471 {
1472 return TRUE;
1473 }
1474
1475 size_t sep_len = (list_len > 0) ? 1 : 0;
1476 if (list_len + sep_len + add_len + 1 > list_cap)
1477 {
1478 return FALSE;
1479 }
1480
1481 if (sep_len)
1482 {
1483 list[list_len++] = L',';
1484 }
1485 wmemcpy(list + list_len, add, add_len + 1);
1486 return TRUE;
1487}
1488
1512static size_t
1513RemoveSearchListTokens(PWSTR list, PCWSTR remove)
1514{
1515 size_t removed = 0;
1516 PCWSTR domain = remove;
1517 while (*domain)
1518 {
1519 PCWSTR comma = wcschr(domain, L',');
1520 size_t domain_len = comma ? (size_t)(comma - domain) : wcslen(domain);
1521 if (domain_len > 0)
1522 {
1523 /* Find the last token in @p list that exactly equals @p domain. */
1524 PWSTR match = NULL;
1525 PWSTR match_end = NULL;
1526 for (PWSTR p = list; *p;)
1527 {
1528 PWSTR tok_end = wcschr(p, L',');
1529 size_t tok_len = tok_end ? (size_t)(tok_end - p) : wcslen(p);
1530 if (tok_len == domain_len && wcsncmp(p, domain, domain_len) == 0)
1531 {
1532 match = p;
1533 match_end = tok_end;
1534 }
1535 if (!tok_end)
1536 {
1537 break;
1538 }
1539 p = tok_end + 1;
1540 }
1541 if (match)
1542 {
1543 /* Splice the token out, eating its leading comma if it has
1544 * one, otherwise its trailing comma. */
1545 PWSTR cut_start, cut_end;
1546 if (match == list)
1547 {
1548 cut_start = match;
1549 cut_end = match_end ? match_end + 1 : match + domain_len;
1550 }
1551 else
1552 {
1553 cut_start = match - 1;
1554 cut_end = match + domain_len;
1555 }
1556 wmemmove(cut_start, cut_end, wcslen(cut_end) + 1);
1557 removed++;
1558 }
1559 }
1560 if (!comma)
1561 {
1562 break;
1563 }
1564 domain = comma + 1;
1565 }
1566 return removed;
1567}
1568
1585static BOOL
1586AddDnsSearchDomains(HKEY key, BOOL have_list, PCWSTR domains)
1587{
1588 WCHAR list[2048] = { 0 };
1589
1590 if (have_list)
1591 {
1592 DWORD size = sizeof(list);
1593 LSTATUS err =
1594 RegGetValueW(key, NULL, L"SearchList", RRF_RT_REG_SZ, NULL, list, &size);
1595 if (err)
1596 {
1597 MsgToEventLog(M_SYSERR, L"%S: could not get SearchList from registry (%lu)", __func__,
1598 err);
1599 return FALSE;
1600 }
1601
1602 if (!StoreInitialDnsSearchList(key, list))
1603 {
1604 return FALSE;
1605 }
1606 }
1607
1608 if (!AppendSearchList(list, _countof(list), domains))
1609 {
1610 MsgToEventLog(M_SYSERR, L"%S: not enough space in list for search domains", __func__);
1611 return FALSE;
1612 }
1613
1614 DWORD size = RegWStringSize(list);
1615 LSTATUS err = RegSetValueExW(key, L"SearchList", 0, REG_SZ, (PBYTE)list, size);
1616 if (err)
1617 {
1618 MsgToEventLog(M_SYSERR, L"%S: could not set SearchList to registry (%lu)", __func__, err);
1619 return FALSE;
1620 }
1621
1622 return TRUE;
1623}
1624
1636static BOOL
1638{
1639 LSTATUS err;
1640 BOOL ret = FALSE;
1641 WCHAR list[2048];
1642 DWORD size = sizeof(list);
1643
1644 err = RegGetValueW(key, NULL, L"InitialSearchList", RRF_RT_REG_SZ, NULL, list, &size);
1645 if (err)
1646 {
1647 if (err != ERROR_FILE_NOT_FOUND)
1648 {
1649 MsgToEventLog(M_SYSERR, L"%S: could not get InitialSearchList from registry (%lu)",
1650 __func__, err);
1651 }
1652 goto out;
1653 }
1654
1655 size = RegWStringSize(list);
1656 err = RegSetValueExW(key, L"SearchList", 0, REG_SZ, (PBYTE)list, size);
1657 if (err)
1658 {
1659 MsgToEventLog(M_SYSERR, L"%S: could not set SearchList in registry (%lu)", __func__, err);
1660 goto out;
1661 }
1662
1663 RegDeleteValueA(key, "InitialSearchList");
1664 ret = TRUE;
1665
1666out:
1667 return ret;
1668}
1669
1684static void
1685RemoveDnsSearchDomains(HKEY key, PCWSTR domains)
1686{
1687 WCHAR list[2048];
1688 DWORD size = sizeof(list);
1689 LSTATUS err = RegGetValueW(key, NULL, L"SearchList", RRF_RT_REG_SZ, NULL, list, &size);
1690 if (err)
1691 {
1692 MsgToEventLog(M_SYSERR, L"%S: could not get SearchList from registry (%lu)", __func__, err);
1693 return;
1694 }
1695
1696 if (RemoveSearchListTokens(list, domains) == 0)
1697 {
1698 MsgToEventLog(M_ERR, L"%S: could not find domains in search list", __func__);
1699 return;
1700 }
1701
1702 if (list[0] != L'\0')
1703 {
1704 /* If the shortened list equals the snapshot we took at first
1705 * touch, the user's pre-VPN state is fully restored -- wipe both
1706 * SearchList and InitialSearchList. */
1707 WCHAR initial[2048];
1708 size = sizeof(initial);
1709 err = RegGetValueW(key, NULL, L"InitialSearchList", RRF_RT_REG_SZ, NULL, initial, &size);
1710 if (!err && wcscmp(list, initial) == 0)
1711 {
1713 return;
1714 }
1715 if (err && err != ERROR_FILE_NOT_FOUND)
1716 {
1717 MsgToEventLog(M_SYSERR, L"%S: could not get InitialSearchList from registry (%lu)",
1718 __func__, err);
1719 return;
1720 }
1721 }
1722
1723 size = RegWStringSize(list);
1724 err = RegSetValueExW(key, L"SearchList", 0, REG_SZ, (PBYTE)list, size);
1725 if (err)
1726 {
1727 MsgToEventLog(M_SYSERR, L"%S: could not set SearchList in registry (%lu)", __func__, err);
1728 }
1729}
1730
1736static void
1738{
1739 BOOL gpol;
1740 HKEY dns_searchlist_key;
1741 GetDnsSearchListKey(undo_data->itf_name, &gpol, &dns_searchlist_key);
1742 if (dns_searchlist_key != INVALID_HANDLE_VALUE)
1743 {
1744 RemoveDnsSearchDomains(dns_searchlist_key, undo_data->domains);
1745 RegCloseKey(dns_searchlist_key);
1746 ApplyDnsSettings(gpol);
1747
1748 free(undo_data->domains);
1749 undo_data->domains = NULL;
1750 }
1751}
1752
1774static DWORD
1775SetDnsSearchDomains(PCSTR itf_name, PCSTR domains, PBOOL gpol, undo_lists_t *lists)
1776{
1777 DWORD err = ERROR_OUTOFMEMORY;
1778
1779 HKEY list_key;
1780 BOOL have_list = GetDnsSearchListKey(itf_name, gpol, &list_key);
1781 if (list_key == INVALID_HANDLE_VALUE)
1782 {
1783 MsgToEventLog(M_SYSERR, L"%S: could not get search list registry key", __func__);
1784 return ERROR_FILE_NOT_FOUND;
1785 }
1786
1787 /* Remove previously installed search domains */
1788 dns_domains_undo_data_t *undo_data = RemoveListItem(&(*lists)[undo_domains], CmpAny, NULL);
1789 if (undo_data)
1790 {
1791 RemoveDnsSearchDomains(list_key, undo_data->domains);
1792 free(undo_data->domains);
1793 free(undo_data);
1794 undo_data = NULL;
1795 }
1796
1797 /* If there are search domains, add them */
1798 if (domains && *domains)
1799 {
1800 wchar_t *wide_domains = utf8to16(domains); /* utf8 to wide-char */
1801 if (!wide_domains)
1802 {
1803 goto out;
1804 }
1805
1806 undo_data = malloc(sizeof(*undo_data));
1807 if (!undo_data)
1808 {
1809 free(wide_domains);
1810 wide_domains = NULL;
1811 goto out;
1812 }
1813 strncpy(undo_data->itf_name, itf_name, sizeof(undo_data->itf_name));
1814 undo_data->domains = wide_domains;
1815
1816 if (AddDnsSearchDomains(list_key, have_list, wide_domains) == FALSE
1817 || AddListItem(&(*lists)[undo_domains], undo_data) != NO_ERROR)
1818 {
1819 RemoveDnsSearchDomains(list_key, wide_domains);
1820 free(wide_domains);
1821 free(undo_data);
1822 undo_data = NULL;
1823 goto out;
1824 }
1825 }
1826
1827 err = NO_ERROR;
1828
1829out:
1830 RegCloseKey(list_key);
1831 return err;
1832}
1833
1841static BOOL
1842GetInterfacesKey(short family, PHKEY key)
1843{
1844 PCSTR itfs_key = family == AF_INET6
1845 ? "SYSTEM\\CurrentControlSet\\Services\\Tcpip6\\Parameters\\Interfaces"
1846 : "SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces";
1847
1848 LSTATUS err = RegOpenKeyExA(HKEY_LOCAL_MACHINE, itfs_key, 0, KEY_ALL_ACCESS, key);
1849 if (err)
1850 {
1851 *key = INVALID_HANDLE_VALUE;
1852 MsgToEventLog(M_SYSERR, L"%S: could not open interfaces registry key for family %d (%lu)",
1853 __func__, family, err);
1854 }
1855
1856 return err ? FALSE : TRUE;
1857}
1858
1868static DWORD
1869SetNameServersValue(PCWSTR itf_id, short family, PCSTR value)
1870{
1871 DWORD err;
1872
1873 HKEY itfs;
1874 if (!GetInterfacesKey(family, &itfs))
1875 {
1876 return ERROR_FILE_NOT_FOUND;
1877 }
1878
1879 HKEY itf = INVALID_HANDLE_VALUE;
1880 err = RegOpenKeyExW(itfs, itf_id, 0, KEY_ALL_ACCESS, &itf);
1881 if (err)
1882 {
1883 MsgToEventLog(M_SYSERR, L"%S: could not open interface key for %s family %d (%lu)",
1884 __func__, itf_id, family, err);
1885 goto out;
1886 }
1887
1888 err = RegSetValueExA(itf, "NameServer", 0, REG_SZ, (PBYTE)value, (DWORD)strlen(value) + 1);
1889 if (err)
1890 {
1891 MsgToEventLog(M_SYSERR, L"%S: could not set name servers '%S' for %s family %d (%lu)",
1892 __func__, value, itf_id, family, err);
1893 }
1894
1895out:
1896 if (itf != INVALID_HANDLE_VALUE)
1897 {
1898 RegCloseKey(itf);
1899 }
1900 if (itfs != INVALID_HANDLE_VALUE)
1901 {
1902 RegCloseKey(itfs);
1903 }
1904 return err;
1905}
1906
1916static DWORD
1917SetNameServers(PCWSTR itf_id, short family, PCSTR addrs)
1918{
1919 return SetNameServersValue(itf_id, family, addrs);
1920}
1921
1930static DWORD
1931ResetNameServers(PCWSTR itf_id, short family)
1932{
1933 return SetNameServersValue(itf_id, family, "");
1934}
1935
1936static DWORD
1938{
1939 DWORD err = 0;
1940 undo_type_t undo_type = (msg->family == AF_INET6) ? undo_dns6 : undo_dns4;
1941 unsigned int addr_len = msg->addr_len;
1942
1943 /* sanity check */
1944 const unsigned int max_addrs = _countof(msg->addr);
1945 if (addr_len > max_addrs)
1946 {
1947 addr_len = max_addrs;
1948 }
1949
1950 if (!msg->iface.name[0]) /* interface name is required */
1951 {
1952 return ERROR_MESSAGE_DATA;
1953 }
1954
1955 /* use a non-const reference with limited scope to enforce null-termination of strings from
1956 * client */
1957 {
1959 msgptr->iface.name[_countof(msg->iface.name) - 1] = '\0';
1960 msgptr->domains[_countof(msg->domains) - 1] = '\0';
1961 }
1962
1963 WCHAR iid[64];
1964 err = InterfaceIdString(msg->iface.name, iid, _countof(iid));
1965 if (err)
1966 {
1967 return err;
1968 }
1969
1970 /* We delete all current addresses before adding any
1971 * OR if the message type is del_dns_cfg
1972 */
1973 if (addr_len > 0 || msg->header.type == msg_del_dns_cfg)
1974 {
1975 err = ResetNameServers(iid, msg->family);
1976 if (err)
1977 {
1978 return err;
1979 }
1980 free(RemoveListItem(&(*lists)[undo_type], CmpAny, iid));
1981 }
1982
1983 if (msg->header.type == msg_del_dns_cfg)
1984 {
1985 BOOL gpol = FALSE;
1986 if (msg->domains[0])
1987 {
1988 /* setting an empty domain list removes any previous value */
1989 err = SetDnsSearchDomains(msg->iface.name, NULL, &gpol, lists);
1990 }
1991 ApplyDnsSettings(gpol);
1992 return err; /* job done */
1993 }
1994
1995 if (addr_len > 0)
1996 {
1997 /* prepare the comma separated address list */
1998 /* cannot use max_addrs here as that is not considered compile
1999 * time constant by all compilers and constexpr is C23 */
2000 CHAR addrs[_countof(msg->addr) * 64]; /* 64 is enough for one IPv4/6 address */
2001 size_t offset = 0;
2002 for (unsigned int i = 0; i < addr_len; ++i)
2003 {
2004 if (i != 0)
2005 {
2006 addrs[offset++] = ',';
2007 }
2008 if (msg->family == AF_INET6)
2009 {
2010 RtlIpv6AddressToStringA(&msg->addr[i].ipv6, addrs + offset);
2011 }
2012 else
2013 {
2014 RtlIpv4AddressToStringA(&msg->addr[i].ipv4, addrs + offset);
2015 }
2016 offset = strlen(addrs);
2017 }
2018
2019 err = SetNameServers(iid, msg->family, addrs);
2020 if (err)
2021 {
2022 return err;
2023 }
2024
2025 wchar_t *tmp_iid = _wcsdup(iid);
2026 if (!tmp_iid || AddListItem(&(*lists)[undo_type], tmp_iid))
2027 {
2028 free(tmp_iid);
2029 ResetNameServers(iid, msg->family);
2030 return ERROR_OUTOFMEMORY;
2031 }
2032 }
2033
2034 BOOL gpol = FALSE;
2035 if (msg->domains[0])
2036 {
2037 err = SetDnsSearchDomains(msg->iface.name, msg->domains, &gpol, lists);
2038 }
2039 ApplyDnsSettings(gpol);
2040
2041 return err;
2042}
2043
2052static BOOL
2054{
2055 DWORD dhcp;
2056 DWORD size = sizeof(dhcp);
2057 LSTATUS err;
2058
2059 err = RegGetValueA(key, NULL, "EnableDHCP", RRF_RT_REG_DWORD, NULL, (PBYTE)&dhcp, &size);
2060 if (err != NO_ERROR)
2061 {
2062 MsgToEventLog(M_SYSERR, L"%S: Could not read DHCP status (%lu)", __func__, err);
2063 return FALSE;
2064 }
2065
2066 return dhcp ? TRUE : FALSE;
2067}
2068
2077static LSTATUS
2078SetNameServerAddresses(PWSTR itf_id, const nrpt_address_t *addresses)
2079{
2080 const short families[] = { AF_INET, AF_INET6 };
2081 for (size_t i = 0; i < _countof(families); i++)
2082 {
2083 short family = families[i];
2084
2085 /* Create a comma sparated list of addresses of this family */
2086 size_t offset = 0;
2087 char addr_list[NRPT_ADDR_SIZE * NRPT_ADDR_NUM];
2088 for (int j = 0; j < NRPT_ADDR_NUM && addresses[j][0]; j++)
2089 {
2090 if ((family == AF_INET6 && strchr(addresses[j], ':') == NULL)
2091 || (family == AF_INET && strchr(addresses[j], ':') != NULL))
2092 {
2093 /* Address family doesn't match, skip this one */
2094 continue;
2095 }
2096 if (offset)
2097 {
2098 addr_list[offset++] = ',';
2099 }
2100 strcpy(addr_list + offset, addresses[j]);
2101 offset += strlen(addresses[j]);
2102 }
2103
2104 if (offset == 0)
2105 {
2106 /* No address for this family to set */
2107 continue;
2108 }
2109
2110 /* Set name server addresses */
2111 LSTATUS err = SetNameServers(itf_id, family, addr_list);
2112 if (err)
2113 {
2114 return err;
2115 }
2116 }
2117 return NO_ERROR;
2118}
2119
2130static LSTATUS
2131GetItfDnsServersV4(HKEY itf_key, PSTR addrs, PDWORD size)
2132{
2133 addrs[*size - 1] = '\0';
2134
2135 LSTATUS err;
2136 DWORD s = *size;
2137 err = RegGetValueA(itf_key, NULL, "NameServer", RRF_RT_REG_SZ, NULL, (PBYTE)addrs, &s);
2138 if (err && err != ERROR_FILE_NOT_FOUND)
2139 {
2140 *size = 0;
2141 return err;
2142 }
2143
2144 /* Try DHCP addresses if we don't have some already */
2145 if (!strchr(addrs, '.') && IsDhcpEnabled(itf_key))
2146 {
2147 s = *size;
2148 RegGetValueA(itf_key, NULL, "DhcpNameServer", RRF_RT_REG_SZ, NULL, (PBYTE)addrs, &s);
2149 if (err)
2150 {
2151 *size = 0;
2152 return err;
2153 }
2154 }
2155
2156 if (strchr(addrs, '.'))
2157 {
2158 *size = s;
2159 return NO_ERROR;
2160 }
2161
2162 *size = 0;
2163 return ERROR_FILE_NOT_FOUND;
2164}
2165
2175static LSTATUS
2176GetItfDnsServersV6(HKEY itf_key, PSTR addrs, PDWORD size)
2177{
2178 addrs[*size - 1] = '\0';
2179
2180 LSTATUS err;
2181 DWORD s = *size;
2182 err = RegGetValueA(itf_key, NULL, "NameServer", RRF_RT_REG_SZ, NULL, (PBYTE)addrs, &s);
2183 if (err && err != ERROR_FILE_NOT_FOUND)
2184 {
2185 *size = 0;
2186 return err;
2187 }
2188
2189 /* Try DHCP addresses if we don't have some already */
2190 if (!strchr(addrs, ':') && IsDhcpEnabled(itf_key))
2191 {
2192 IN6_ADDR in_addrs[8];
2193 DWORD in_addrs_size = sizeof(in_addrs);
2194 err = RegGetValueA(itf_key, NULL, "Dhcpv6DNSServers", RRF_RT_REG_BINARY, NULL,
2195 (PBYTE)in_addrs, &in_addrs_size);
2196 if (err)
2197 {
2198 *size = 0;
2199 return err;
2200 }
2201
2202 s = *size;
2203 PSTR pos = addrs;
2204 size_t in_addrs_read = in_addrs_size / sizeof(IN6_ADDR);
2205 for (size_t i = 0; i < in_addrs_read; ++i)
2206 {
2207 if (i != 0)
2208 {
2209 /* Add separator */
2210 *pos++ = ',';
2211 s--;
2212 }
2213
2214 if (inet_ntop(AF_INET6, &in_addrs[i], pos, s) != NULL)
2215 {
2216 *size = 0;
2217 return ERROR_MORE_DATA;
2218 }
2219
2220 size_t addr_len = strlen(pos);
2221 pos += addr_len;
2222 s -= (DWORD)addr_len;
2223 }
2224 s = (DWORD)strlen(addrs) + 1;
2225 }
2226
2227 if (strchr(addrs, ':'))
2228 {
2229 *size = s;
2230 return NO_ERROR;
2231 }
2232
2233 *size = 0;
2234 return ERROR_FILE_NOT_FOUND;
2235}
2236
2246static BOOL
2247ListContainsDomain(PCWSTR list, PCWSTR domain, size_t len)
2248{
2249 PCWSTR entry = list;
2250 while (entry && *entry)
2251 {
2252 PCWSTR comma = wcschr(entry, L',');
2253 size_t entry_len = comma ? (size_t)(comma - entry) : wcslen(entry);
2254 if (entry_len == len && wcsncmp(entry, domain, len) == 0)
2255 {
2256 return TRUE;
2257 }
2258 if (!comma)
2259 {
2260 break;
2261 }
2262 entry = comma + 1;
2263 }
2264 return FALSE;
2265}
2266
2289static LSTATUS
2290ConvertItfDnsDomains(PCWSTR search_domains, PWSTR domains, PDWORD size, const DWORD capacity)
2291{
2292 const size_t glyph_size = sizeof(*domains);
2293 const size_t max_len = (size_t)capacity / glyph_size;
2294
2295 /* Space required for leading dot and two terminating zeros */
2296 const size_t dot_len = 1;
2297 const size_t term_len = 2;
2298
2299 LSTATUS ret = NO_ERROR;
2300 size_t tmp_len = 0;
2301 WCHAR *tmp = malloc(capacity);
2302 if (tmp == NULL)
2303 {
2304 ret = ERROR_OUTOFMEMORY;
2305 goto done;
2306 }
2307
2308 PWCHAR tmp_pos = tmp;
2309 PCWCHAR domain = domains;
2310
2311 while (domain && *domain)
2312 {
2313 PWCHAR comma = wcschr(domain, L',');
2314 size_t domain_len = comma ? (size_t)(comma - domain) : wcslen(domain);
2315
2316 if (ListContainsDomain(search_domains, domain, domain_len))
2317 {
2318 /* Skip this domain */
2319 domain = comma ? comma + 1 : domain + domain_len;
2320 continue;
2321 }
2322
2323 /* Check for enough space to convert this domain */
2324 if (tmp_len + dot_len + domain_len + term_len > max_len)
2325 {
2326 /* Domain doesn't fit, bad luck if it's the first one */
2327 *tmp_pos = L'\0';
2328 if (tmp_len > 0)
2329 {
2330 tmp_len += 1;
2331 }
2332 ret = ERROR_MORE_DATA;
2333 goto done;
2334 }
2335
2336 /* Write leading dot and domain into tmp buffer */
2337 *tmp_pos++ = L'.';
2338 wcsncpy(tmp_pos, domain, domain_len);
2339 tmp_pos += domain_len;
2340 *tmp_pos++ = L'\0';
2341 tmp_len += dot_len + domain_len + 1;
2342
2343 domain = comma ? comma + 1 : domain + domain_len;
2344 }
2345
2346 if (tmp_len == 0)
2347 {
2348 ret = ERROR_FILE_NOT_FOUND;
2349 goto done;
2350 }
2351
2352 /* REG_MULTI_SZ second zero terminator */
2353 *tmp_pos = L'\0';
2354 tmp_len += 1;
2355
2356done:
2357 if (tmp)
2358 {
2359 wmemcpy(domains, tmp, tmp_len);
2360 free(tmp);
2361 }
2362 *size = (DWORD)(tmp_len * glyph_size);
2363 return ret;
2364}
2365
2387static LSTATUS
2388GetItfDnsDomains(HKEY itf, PCWSTR search_domains, PWSTR domains, PDWORD size)
2389{
2390 if (domains == NULL || size == NULL || *size == 0)
2391 {
2392 return ERROR_INVALID_PARAMETER;
2393 }
2394
2395 LSTATUS err = ERROR_FILE_NOT_FOUND;
2396 const DWORD buf_size = *size;
2397 const DWORD glyph_size = sizeof(*domains);
2398 PWSTR values[] = { L"SearchList", L"Domain", L"DhcpDomainSearchList", L"DhcpDomain", NULL };
2399
2400 for (int i = 0; values[i]; i++)
2401 {
2402 *size = buf_size;
2403 err = RegGetValueW(itf, NULL, values[i], RRF_RT_REG_SZ, NULL, (PBYTE)domains, size);
2404 if (!err && *size > glyph_size && domains[(*size / glyph_size) - 1] == '\0' && wcschr(domains, '.'))
2405 {
2406 return ConvertItfDnsDomains(search_domains, domains, size, buf_size);
2407 }
2408 }
2409
2410 *size = 0;
2411 return err;
2412}
2413
2422static BOOL
2424{
2425 GUID iid;
2426 BOOL res = FALSE;
2427 MIB_IF_ROW2 itf_row;
2428
2429 /* Get GUID from string */
2430 if (IIDFromString(iid_str, &iid) != S_OK)
2431 {
2432 MsgToEventLog(M_SYSERR, L"%S: could not convert interface %s GUID string", __func__,
2433 iid_str);
2434 goto out;
2435 }
2436
2437 /* Get LUID from GUID */
2438 if (ConvertInterfaceGuidToLuid(&iid, &itf_row.InterfaceLuid) != NO_ERROR)
2439 {
2440 goto out;
2441 }
2442
2443 /* Look up interface status */
2444 if (GetIfEntry2(&itf_row) != NO_ERROR)
2445 {
2446 MsgToEventLog(M_SYSERR, L"%S: could not get interface %s status", __func__, iid_str);
2447 goto out;
2448 }
2449
2450 if (itf_row.MediaConnectState == MediaConnectStateConnected
2451 && itf_row.OperStatus == IfOperStatusUp)
2452 {
2453 res = TRUE;
2454 }
2455
2456out:
2457 return res;
2458}
2459
2469static void
2470GetNrptExcludeData(PCWSTR search_domains, nrpt_exclude_data_t *data, size_t data_size)
2471{
2472 HKEY v4_itfs = INVALID_HANDLE_VALUE;
2473 HKEY v6_itfs = INVALID_HANDLE_VALUE;
2474
2475 if (!GetInterfacesKey(AF_INET, &v4_itfs) || !GetInterfacesKey(AF_INET6, &v6_itfs))
2476 {
2477 goto out;
2478 }
2479
2480 size_t i = 0;
2481 DWORD enum_index = 0;
2482 while (i < data_size)
2483 {
2484 WCHAR itf_guid[MAX_PATH];
2485 DWORD itf_guid_len = _countof(itf_guid);
2486 LSTATUS err =
2487 RegEnumKeyExW(v4_itfs, enum_index++, itf_guid, &itf_guid_len, NULL, NULL, NULL, NULL);
2488 if (err)
2489 {
2490 if (err != ERROR_NO_MORE_ITEMS)
2491 {
2492 MsgToEventLog(M_SYSERR, L"%S: could not enumerate interfaces (%lu)", __func__, err);
2493 }
2494 goto out;
2495 }
2496
2497 /* Ignore interfaces that are not connected or disabled */
2498 if (!IsInterfaceConnected(itf_guid))
2499 {
2500 continue;
2501 }
2502
2503 HKEY v4_itf;
2504 if (RegOpenKeyExW(v4_itfs, itf_guid, 0, KEY_READ, &v4_itf) != NO_ERROR)
2505 {
2506 MsgToEventLog(M_SYSERR, L"%S: could not open interface %s v4 registry key", __func__,
2507 itf_guid);
2508 goto out;
2509 }
2510
2511 /* Get the DNS domain(s) for exclude routing */
2512 data[i].domains_size = sizeof(data[0].domains);
2513 memset(data[i].domains, 0, data[i].domains_size);
2514 err = GetItfDnsDomains(v4_itf, search_domains, data[i].domains, &data[i].domains_size);
2515 if (err)
2516 {
2517 if (err != ERROR_FILE_NOT_FOUND)
2518 {
2519 MsgToEventLog(M_SYSERR, L"%S: could not read interface %s domain suffix", __func__,
2520 itf_guid);
2521 }
2522 goto next_itf;
2523 }
2524
2525 /* Get the IPv4 DNS servers */
2526 DWORD v4_addrs_size = sizeof(data[0].addresses);
2527 err = GetItfDnsServersV4(v4_itf, data[i].addresses, &v4_addrs_size);
2528 if (err && err != ERROR_FILE_NOT_FOUND)
2529 {
2530 MsgToEventLog(M_SYSERR, L"%S: could not read interface %s v4 name servers (%ld)",
2531 __func__, itf_guid, err);
2532 goto next_itf;
2533 }
2534
2535 /* Get the IPv6 DNS servers, if there's space left */
2536 PSTR v6_addrs = data[i].addresses + v4_addrs_size;
2537 DWORD v6_addrs_size = sizeof(data[0].addresses) - v4_addrs_size;
2538 if (v6_addrs_size > NRPT_ADDR_SIZE)
2539 {
2540 HKEY v6_itf;
2541 if (RegOpenKeyExW(v6_itfs, itf_guid, 0, KEY_READ, &v6_itf) != NO_ERROR)
2542 {
2543 MsgToEventLog(M_SYSERR, L"%S: could not open interface %s v6 registry key",
2544 __func__, itf_guid);
2545 goto next_itf;
2546 }
2547 err = GetItfDnsServersV6(v6_itf, v6_addrs, &v6_addrs_size);
2548 RegCloseKey(v6_itf);
2549 if (err && err != ERROR_FILE_NOT_FOUND)
2550 {
2551 MsgToEventLog(M_SYSERR, L"%S: could not read interface %s v6 name servers (%ld)",
2552 __func__, itf_guid, err);
2553 goto next_itf;
2554 }
2555 }
2556
2557 if (v4_addrs_size || v6_addrs_size)
2558 {
2559 /* Replace delimiters with semicolons, as required by NRPT */
2560 for (size_t j = 0; j < sizeof(data[0].addresses) && data[i].addresses[j]; j++)
2561 {
2562 if (data[i].addresses[j] == ',' || data[i].addresses[j] == ' ')
2563 {
2564 data[i].addresses[j] = ';';
2565 }
2566 }
2567 ++i;
2568 }
2569
2570next_itf:
2571 RegCloseKey(v4_itf);
2572 }
2573
2574out:
2575 RegCloseKey(v6_itfs);
2576 RegCloseKey(v4_itfs);
2577}
2578
2591static DWORD
2592SetNrptRule(HKEY nrpt_key, PCWSTR subkey, PCSTR address, PCWSTR domains, DWORD dom_size,
2593 BOOL dnssec)
2594{
2595 /* Create rule subkey */
2596 DWORD err = NO_ERROR;
2597 HKEY rule_key;
2598 err = RegCreateKeyExW(nrpt_key, subkey, 0, NULL, 0, KEY_ALL_ACCESS, NULL, &rule_key, NULL);
2599 if (err)
2600 {
2601 return err;
2602 }
2603
2604 /* Set name(s) for DNS routing */
2605 err = RegSetValueExW(rule_key, L"Name", 0, REG_MULTI_SZ, (PBYTE)domains, dom_size);
2606 if (err)
2607 {
2608 goto out;
2609 }
2610
2611 /* Set DNS Server address */
2612 err = RegSetValueExA(rule_key, "GenericDNSServers", 0, REG_SZ, (PBYTE)address,
2613 (DWORD)strlen(address) + 1);
2614 if (err)
2615 {
2616 goto out;
2617 }
2618
2619 DWORD reg_val;
2620 /* Set DNSSEC if required */
2621 if (dnssec)
2622 {
2623 reg_val = 1;
2624 err = RegSetValueExA(rule_key, "DNSSECValidationRequired", 0, REG_DWORD, (PBYTE)&reg_val,
2625 sizeof(reg_val));
2626 if (err)
2627 {
2628 goto out;
2629 }
2630
2631 reg_val = 0;
2632 err = RegSetValueExA(rule_key, "DNSSECQueryIPSECRequired", 0, REG_DWORD, (PBYTE)&reg_val,
2633 sizeof(reg_val));
2634 if (err)
2635 {
2636 goto out;
2637 }
2638
2639 reg_val = 0;
2640 err = RegSetValueExA(rule_key, "DNSSECQueryIPSECEncryption", 0, REG_DWORD, (PBYTE)&reg_val,
2641 sizeof(reg_val));
2642 if (err)
2643 {
2644 goto out;
2645 }
2646 }
2647
2648 /* Set NRPT config options */
2649 reg_val = dnssec ? 0x0000000A : 0x00000008;
2650 err = RegSetValueExA(rule_key, "ConfigOptions", 0, REG_DWORD, (const PBYTE)&reg_val,
2651 sizeof(reg_val));
2652 if (err)
2653 {
2654 goto out;
2655 }
2656
2657 /* Mandatory NRPT version */
2658 reg_val = 2;
2659 err = RegSetValueExA(rule_key, "Version", 0, REG_DWORD, (const PBYTE)&reg_val, sizeof(reg_val));
2660 if (err)
2661 {
2662 goto out;
2663 }
2664
2665out:
2666 if (err)
2667 {
2668 RegDeleteKeyW(nrpt_key, subkey);
2669 }
2670 RegCloseKey(rule_key);
2671 return err;
2672}
2673
2683static void
2684SetNrptExcludeRules(HKEY nrpt_key, DWORD ovpn_pid, PCWSTR search_domains)
2685{
2686 nrpt_exclude_data_t data[8]; /* data from up to 8 interfaces */
2687 memset(data, 0, sizeof(data));
2688 GetNrptExcludeData(search_domains, data, _countof(data));
2689
2690 unsigned n = 0;
2691 for (size_t i = 0; i < _countof(data); ++i)
2692 {
2693 const nrpt_exclude_data_t *d = &data[i];
2694 if (d->domains_size == 0)
2695 {
2696 break;
2697 }
2698
2699 DWORD err;
2700 WCHAR subkey[48];
2701 swprintf(subkey, _countof(subkey), L"OpenVPNDNSRoutingX-%02x-%lu", ++n, ovpn_pid);
2702 err = SetNrptRule(nrpt_key, subkey, d->addresses, d->domains, d->domains_size, FALSE);
2703 if (err)
2704 {
2705 MsgToEventLog(M_ERR, L"%S: failed to set rule %s (%lu)", __func__, subkey, err);
2706 }
2707 }
2708}
2709
2722static DWORD
2723SetNrptRules(HKEY nrpt_key, const nrpt_address_t *addresses, const char *domains,
2724 const char *search_domains, BOOL dnssec, DWORD ovpn_pid)
2725{
2726 DWORD err = NO_ERROR;
2727 PWSTR wide_domains = L".\0"; /* DNS route everything by default */
2728 DWORD dom_size = 6;
2729
2730 /* Prepare DNS routing domains / split DNS */
2731 if (domains[0])
2732 {
2733 size_t domains_len = strlen(domains);
2734 dom_size = (DWORD)domains_len + 2; /* len + the trailing NULs */
2735
2736 wide_domains = utf8to16_size(domains, dom_size);
2737 if (!wide_domains)
2738 {
2739 return ERROR_OUTOFMEMORY;
2740 }
2741 domains_len = wcslen(wide_domains);
2742 dom_size = (DWORD)(domains_len + 2) * sizeof(*wide_domains);
2743
2744 /* Make a MULTI_SZ from a comma separated list */
2745 for (size_t i = 0; i < domains_len; ++i)
2746 {
2747 if (wide_domains[i] == ',')
2748 {
2749 wide_domains[i] = 0;
2750 }
2751 }
2752 }
2753 else
2754 {
2755 PWSTR wide_search_domains;
2756 wide_search_domains = utf8to16(search_domains);
2757 if (!wide_search_domains)
2758 {
2759 return ERROR_OUTOFMEMORY;
2760 }
2761 SetNrptExcludeRules(nrpt_key, ovpn_pid, wide_search_domains);
2762 free(wide_search_domains);
2763 }
2764
2765 if (addresses[0][0])
2766 {
2767 /* Create address string list */
2768 CHAR addr_list[NRPT_ADDR_NUM * NRPT_ADDR_SIZE];
2769 PSTR pos = addr_list;
2770 for (int i = 0; i < NRPT_ADDR_NUM && addresses[i][0]; ++i)
2771 {
2772 if (i != 0)
2773 {
2774 *pos++ = ';';
2775 }
2776 strcpy(pos, addresses[i]);
2777 pos += strlen(pos);
2778 }
2779
2780 WCHAR subkey[MAX_PATH];
2781 swprintf(subkey, _countof(subkey), L"OpenVPNDNSRouting-%lu", ovpn_pid);
2782 err = SetNrptRule(nrpt_key, subkey, addr_list, wide_domains, dom_size, dnssec);
2783 if (err)
2784 {
2785 MsgToEventLog(M_ERR, L"%S: failed to set rule %s (%lu)", __func__, subkey, err);
2786 }
2787 }
2788
2789 if (domains[0])
2790 {
2791 free(wide_domains);
2792 }
2793 return err;
2794}
2795
2804static LSTATUS
2805OpenNrptBaseKey(PHKEY key, PBOOL gpol)
2806{
2807 /*
2808 * Registry keys Name Service Policy Table (NRPT) rules can be stored at.
2809 * When the group policy key exists, NRPT rules must be placed there.
2810 * It is created when NRPT rules are pushed via group policy and it
2811 * remains in the registry even if the last GP-NRPT rule is deleted.
2812 */
2813 static PCSTR gpol_key = "SOFTWARE\\Policies\\Microsoft\\Windows NT\\DNSClient\\DnsPolicyConfig";
2814 static PCSTR sys_key =
2815 "SYSTEM\\CurrentControlSet\\Services\\Dnscache\\Parameters\\DnsPolicyConfig";
2816
2817 HKEY nrpt;
2818 *gpol = TRUE;
2819 LSTATUS err = RegOpenKeyExA(HKEY_LOCAL_MACHINE, gpol_key, 0, KEY_ALL_ACCESS, &nrpt);
2820 if (err == ERROR_FILE_NOT_FOUND)
2821 {
2822 *gpol = FALSE;
2823 err = RegCreateKeyExA(HKEY_LOCAL_MACHINE, sys_key, 0, NULL, 0, KEY_ALL_ACCESS, NULL, &nrpt,
2824 NULL);
2825 if (err)
2826 {
2827 nrpt = INVALID_HANDLE_VALUE;
2828 }
2829 }
2830 *key = nrpt;
2831 return err;
2832}
2833
2845static BOOL
2846DeleteNrptRules(DWORD pid, PBOOL gpol)
2847{
2848 HKEY key;
2849 LSTATUS err = OpenNrptBaseKey(&key, gpol);
2850 if (err)
2851 {
2852 MsgToEventLog(M_SYSERR, L"%S: could not open NRPT base key (%lu)", __func__, err);
2853 return FALSE;
2854 }
2855
2856 /* PID suffix string to compare against later */
2857 WCHAR pid_str[16];
2858 size_t pidlen = 0;
2859 if (pid)
2860 {
2861 swprintf(pid_str, _countof(pid_str), L"-%lu", pid);
2862 pidlen = wcslen(pid_str);
2863 }
2864
2865 int deleted = 0;
2866 DWORD enum_index = 0;
2867 while (TRUE)
2868 {
2869 WCHAR name[MAX_PATH];
2870 DWORD namelen = _countof(name);
2871 err = RegEnumKeyExW(key, enum_index++, name, &namelen, NULL, NULL, NULL, NULL);
2872 if (err)
2873 {
2874 if (err != ERROR_NO_MORE_ITEMS)
2875 {
2876 MsgToEventLog(M_SYSERR, L"%S: could not enumerate NRPT rules (%lu)", __func__, err);
2877 }
2878 break;
2879 }
2880
2881 /* Keep rule if name doesn't match */
2882 if (wcsncmp(name, L"OpenVPNDNSRouting", 17) != 0
2883 || (pid && wcsncmp(name + namelen - pidlen, pid_str, pidlen) != 0))
2884 {
2885 continue;
2886 }
2887
2888 if (RegDeleteKeyW(key, name) == NO_ERROR)
2889 {
2890 enum_index--;
2891 deleted++;
2892 }
2893 }
2894
2895 RegCloseKey(key);
2896 return deleted ? TRUE : FALSE;
2897}
2898
2904static void
2905UndoNrptRules(DWORD ovpn_pid)
2906{
2907 BOOL gpol;
2908 if (DeleteNrptRules(ovpn_pid, &gpol))
2909 {
2910 ApplyDnsSettings(gpol);
2911 }
2912}
2913
2925static DWORD
2927{
2928 /*
2929 * Use a non-const reference with limited scope to
2930 * enforce null-termination of strings from client
2931 */
2932 {
2934 msgptr->iface.name[_countof(msg->iface.name) - 1] = '\0';
2935 msgptr->search_domains[_countof(msg->search_domains) - 1] = '\0';
2936 msgptr->resolve_domains[_countof(msg->resolve_domains) - 1] = '\0';
2937 for (size_t i = 0; i < NRPT_ADDR_NUM; ++i)
2938 {
2939 msgptr->addresses[i][_countof(msg->addresses[0]) - 1] = '\0';
2940 }
2941 }
2942
2943 /* Make sure we have the VPN interface name */
2944 if (msg->iface.name[0] == 0)
2945 {
2946 return ERROR_MESSAGE_DATA;
2947 }
2948
2949 /* Some sanity checks on the add message data */
2950 if (msg->header.type == msg_add_nrpt_cfg)
2951 {
2952 /* At least one name server address is set */
2953 if (msg->addresses[0][0] == 0)
2954 {
2955 return ERROR_MESSAGE_DATA;
2956 }
2957 /* Resolve domains are double zero terminated (MULTI_SZ) */
2958 const char *rdom = msg->resolve_domains;
2959 size_t rdom_size = sizeof(msg->resolve_domains);
2960 size_t rdom_len = strlen(rdom);
2961 if (rdom_len && (rdom_len + 1 >= rdom_size || rdom[rdom_len + 1] != 0))
2962 {
2963 return ERROR_MESSAGE_DATA;
2964 }
2965 }
2966
2967 BOOL gpol_nrpt = FALSE;
2968 BOOL gpol_list = FALSE;
2969
2970 WCHAR iid[64];
2971 DWORD iid_err = InterfaceIdString(msg->iface.name, iid, _countof(iid));
2972 if (iid_err)
2973 {
2974 return iid_err;
2975 }
2976
2977 /* Delete previously set values for this instance first, if any */
2978 PDWORD undo_pid = RemoveListItem(&(*lists)[undo_nrpt], CmpAny, NULL);
2979 if (undo_pid)
2980 {
2981 if (*undo_pid != ovpn_pid)
2982 {
2984 L"%S: PID stored for undo doesn't match: %lu vs %lu. "
2985 "This is likely an error. Cleaning up anyway.",
2986 __func__, *undo_pid, ovpn_pid);
2987 }
2988 DeleteNrptRules(*undo_pid, &gpol_nrpt);
2989 free(undo_pid);
2990
2991 ResetNameServers(iid, AF_INET);
2992 ResetNameServers(iid, AF_INET6);
2993 }
2994 SetDnsSearchDomains(msg->iface.name, NULL, &gpol_list, lists);
2995
2996 if (msg->header.type == msg_del_nrpt_cfg)
2997 {
2998 ApplyDnsSettings(gpol_nrpt || gpol_list);
2999 return NO_ERROR; /* Done dealing with del message */
3000 }
3001
3002 HKEY key;
3003 LSTATUS err = OpenNrptBaseKey(&key, &gpol_nrpt);
3004 if (err)
3005 {
3006 goto out;
3007 }
3008
3009 /* Add undo information first in case there's no heap left */
3010 PDWORD pid = malloc(sizeof(ovpn_pid));
3011 if (!pid)
3012 {
3013 err = ERROR_OUTOFMEMORY;
3014 goto out;
3015 }
3016 *pid = ovpn_pid;
3017 if (AddListItem(&(*lists)[undo_nrpt], pid))
3018 {
3019 err = ERROR_OUTOFMEMORY;
3020 free(pid);
3021 goto out;
3022 }
3023
3024 /* Set NRPT rules */
3025 BOOL dnssec = (msg->flags & nrpt_dnssec) != 0;
3026 err = SetNrptRules(key, msg->addresses, msg->resolve_domains, msg->search_domains, dnssec,
3027 ovpn_pid);
3028 if (err)
3029 {
3030 goto out;
3031 }
3032
3033 /*
3034 * Set DNS on the adapter for search domains to be considered.
3035 * If split DNS is configured, do this only when search domains
3036 * are given, so that look-ups for other domains do not go over
3037 * the VPN all the time.
3038 */
3039 if (msg->search_domains[0] || !msg->resolve_domains[0])
3040 {
3041 err = SetNameServerAddresses(iid, msg->addresses);
3042 if (err)
3043 {
3044 goto out;
3045 }
3046 }
3047
3048 /* Set search domains, if any */
3049 if (msg->search_domains[0])
3050 {
3051 err = SetDnsSearchDomains(msg->iface.name, msg->search_domains, &gpol_list, lists);
3052 }
3053
3054 ApplyDnsSettings(gpol_nrpt || gpol_list);
3055
3056out:
3057 return err;
3058}
3059
3060static DWORD
3062{
3063 DWORD err = NO_ERROR;
3064 wchar_t addr[16]; /* large enough to hold string representation of an ipv4 */
3065 unsigned int addr_len = msg->addr_len;
3066
3067 /* sanity check */
3068 if (addr_len > _countof(msg->addr))
3069 {
3070 addr_len = _countof(msg->addr);
3071 }
3072
3073 if (!msg->iface.index) /* interface index is required */
3074 {
3075 return ERROR_MESSAGE_DATA;
3076 }
3077
3078 /* We delete all current addresses before adding any
3079 * OR if the message type is del_wins_cfg
3080 */
3081 if (addr_len > 0 || msg->header.type == msg_del_wins_cfg)
3082 {
3083 err = netsh_wins_cmd(L"delete", msg->iface.index, NULL);
3084 if (err)
3085 {
3086 goto out;
3087 }
3088 free(RemoveListItem(&(*lists)[undo_wins], CmpAny, NULL));
3089 }
3090
3091 if (addr_len == 0 || msg->header.type == msg_del_wins_cfg)
3092 {
3093 goto out; /* job done */
3094 }
3095
3096 for (unsigned int i = 0; i < addr_len; ++i)
3097 {
3098 RtlIpv4AddressToStringW(&msg->addr[i].ipv4, addr);
3099 err = netsh_wins_cmd(i == 0 ? L"set" : L"add", msg->iface.index, addr);
3100 if (i == 0 && err)
3101 {
3102 goto out;
3103 }
3104 /* We do not check for duplicate addresses, so any error in adding
3105 * additional addresses is ignored.
3106 */
3107 }
3108
3109 PDWORD if_index = malloc(sizeof(msg->iface.index));
3110 if (if_index)
3111 {
3112 *if_index = msg->iface.index;
3113 }
3114
3115 if (!if_index || AddListItem(&(*lists)[undo_wins], if_index))
3116 {
3117 free(if_index);
3118 netsh_wins_cmd(L"delete", msg->iface.index, NULL);
3119 err = ERROR_OUTOFMEMORY;
3120 goto out;
3121 }
3122
3123 err = 0;
3124
3125out:
3126 return err;
3127}
3128
3129static DWORD
3131{
3132 DWORD err = 0;
3133 DWORD timeout = 5000; /* in milli seconds */
3134 wchar_t argv0[MAX_PATH];
3135
3136 /* Path of netsh */
3137 swprintf(argv0, _countof(argv0), L"%ls\\%ls", get_win_sys_path(), L"netsh.exe");
3138
3139 /* cmd template:
3140 * netsh interface ipv4 set address name=$if_index source=dhcp
3141 */
3142 const wchar_t *fmt = L"netsh interface ipv4 set address name=\"%lu\" source=dhcp";
3143
3144 /* max cmdline length in wchars -- include room for if index:
3145 * 10 chars for 32 bit int in decimal and +1 for NUL
3146 */
3147 size_t ncmdline = wcslen(fmt) + 10 + 1;
3148 wchar_t *cmdline = malloc(ncmdline * sizeof(wchar_t));
3149 if (!cmdline)
3150 {
3151 err = ERROR_OUTOFMEMORY;
3152 return err;
3153 }
3154
3155 swprintf(cmdline, ncmdline, fmt, dhcp->iface.index);
3156
3157 err = ExecCommand(argv0, cmdline, timeout);
3158
3159 /* Note: This could fail if dhcp is already enabled, so the caller
3160 * may not want to treat errors as FATAL.
3161 */
3162
3163 free(cmdline);
3164 return err;
3165}
3166
3167static DWORD
3169{
3170 DWORD err = 0;
3171 MIB_IPINTERFACE_ROW ipiface;
3172 InitializeIpInterfaceEntry(&ipiface);
3173 ipiface.Family = mtu->family;
3174 ipiface.InterfaceIndex = mtu->iface.index;
3175 err = GetIpInterfaceEntry(&ipiface);
3176 if (err != NO_ERROR)
3177 {
3178 return err;
3179 }
3180 if (mtu->family == AF_INET)
3181 {
3182 ipiface.SitePrefixLength = 0;
3183 }
3184 ipiface.NlMtu = mtu->mtu;
3185
3186 err = SetIpInterfaceEntry(&ipiface);
3187 return err;
3188}
3189
3197static DWORD
3199{
3200 const WCHAR *hwid;
3201
3202 switch (msg->adapter_type)
3203 {
3204 case ADAPTER_TYPE_DCO:
3205 hwid = L"ovpn-dco";
3206 break;
3207
3208 case ADAPTER_TYPE_TAP:
3209 hwid = L"root\\tap0901";
3210 break;
3211
3212 default:
3213 return ERROR_INVALID_PARAMETER;
3214 }
3215
3216 WCHAR cmd[MAX_PATH];
3217 WCHAR args[MAX_PATH];
3218
3219 if (swprintf_s(cmd, _countof(cmd), L"%s\\tapctl.exe", settings.bin_dir) < 0)
3220 {
3221 return ERROR_BUFFER_OVERFLOW;
3222 }
3223
3224 if (swprintf_s(args, _countof(args), L"tapctl create --hwid %s", hwid) < 0)
3225 {
3226 return ERROR_BUFFER_OVERFLOW;
3227 }
3228
3229 return ExecCommand(cmd, args, 10000);
3230}
3231
3232static VOID
3233HandleMessage(HANDLE pipe, PPROCESS_INFORMATION proc_info, DWORD bytes, DWORD count,
3234 LPHANDLE events, undo_lists_t *lists)
3235{
3237 ack_message_t ack = {
3238 .header = { .type = msg_acknowledgement, .size = sizeof(ack), .message_id = -1 },
3239 .error_number = ERROR_MESSAGE_DATA
3240 };
3241
3242 DWORD read = ReadPipeAsync(pipe, &msg, bytes, count, events);
3243 if (read != bytes || read < sizeof(msg.header) || read != msg.header.size)
3244 {
3245 goto out;
3246 }
3247
3248 ack.header.message_id = msg.header.message_id;
3249
3250 switch (msg.header.type)
3251 {
3252 case msg_add_address:
3253 case msg_del_address:
3254 if (msg.header.size == sizeof(msg.address))
3255 {
3256 ack.error_number = HandleAddressMessage(&msg.address, lists);
3257 }
3258 break;
3259
3260 case msg_add_route:
3261 case msg_del_route:
3262 if (msg.header.size == sizeof(msg.route))
3263 {
3264 ack.error_number = HandleRouteMessage(&msg.route, lists);
3265 }
3266 break;
3267
3269 if (msg.header.size == sizeof(msg.flush_neighbors))
3270 {
3271 ack.error_number = HandleFlushNeighborsMessage(&msg.flush_neighbors);
3272 }
3273 break;
3274
3275 case msg_add_wfp_block:
3276 case msg_del_wfp_block:
3277 if (msg.header.size == sizeof(msg.wfp_block))
3278 {
3279 ack.error_number = HandleWfpBlockMessage(&msg.wfp_block, lists);
3280 }
3281 break;
3282
3283 case msg_register_dns:
3285 break;
3286
3287 case msg_add_dns_cfg:
3288 case msg_del_dns_cfg:
3289 ack.error_number = HandleDNSConfigMessage(&msg.dns, lists);
3290 break;
3291
3292 case msg_add_nrpt_cfg:
3293 case msg_del_nrpt_cfg:
3294 {
3295 DWORD ovpn_pid = proc_info->dwProcessId;
3296 ack.error_number = HandleDNSConfigNrptMessage(&msg.nrpt_dns, ovpn_pid, lists);
3297 }
3298 break;
3299
3300 case msg_add_wins_cfg:
3301 case msg_del_wins_cfg:
3302 ack.error_number = HandleWINSConfigMessage(&msg.wins, lists);
3303 break;
3304
3305 case msg_enable_dhcp:
3306 if (msg.header.size == sizeof(msg.dhcp))
3307 {
3309 }
3310 break;
3311
3312 case msg_set_mtu:
3313 if (msg.header.size == sizeof(msg.mtu))
3314 {
3315 ack.error_number = HandleMTUMessage(&msg.mtu);
3316 }
3317 break;
3318
3319 case msg_create_adapter:
3320 if (msg.header.size == sizeof(msg.create_adapter))
3321 {
3322 ack.error_number = HandleCreateAdapterMessage(&msg.create_adapter);
3323 }
3324 break;
3325
3326 default:
3328 MsgToEventLog(MSG_FLAGS_ERROR, L"Unknown message type %d", msg.header.type);
3329 break;
3330 }
3331
3332out:
3333 WritePipeAsync(pipe, &ack, sizeof(ack), count, events);
3334}
3335
3336
3337static VOID
3339{
3340 undo_type_t type;
3341 wfp_block_data_t *interface_data;
3342 for (type = 0; type < _undo_type_max; type++)
3343 {
3344 list_item_t **pnext = &(*lists)[type];
3345 while (*pnext)
3346 {
3347 list_item_t *item = *pnext;
3348 switch (type)
3349 {
3350 case address:
3351 DeleteAddress(item->data);
3352 break;
3353
3354 case route:
3355 DeleteRoute(item->data);
3356 break;
3357
3358 case undo_dns4:
3359 ResetNameServers(item->data, AF_INET);
3360 break;
3361
3362 case undo_dns6:
3363 ResetNameServers(item->data, AF_INET6);
3364 break;
3365
3366 case undo_nrpt:
3367 UndoNrptRules(*(PDWORD)item->data);
3368 break;
3369
3370 case undo_domains:
3372 break;
3373
3374 case undo_wins:
3375 netsh_wins_cmd(L"delete", *(PDWORD)item->data, NULL);
3376 break;
3377
3378 case wfp_block:
3379 interface_data = (wfp_block_data_t *)(item->data);
3380 delete_wfp_block_filters(interface_data->engine);
3381 if (interface_data->metric_v4 >= 0)
3382 {
3383 set_interface_metric(interface_data->index, AF_INET,
3384 interface_data->metric_v4);
3385 }
3386 if (interface_data->metric_v6 >= 0)
3387 {
3388 set_interface_metric(interface_data->index, AF_INET6,
3389 interface_data->metric_v6);
3390 }
3391 break;
3392
3393 case _undo_type_max:
3394 /* unreachable */
3395 break;
3396 }
3397
3398 /* Remove from the list and free memory */
3399 *pnext = item->next;
3400 free(item->data);
3401 free(item);
3402 }
3403 }
3404}
3405
3406static DWORD WINAPI
3407RunOpenvpn(LPVOID p)
3408{
3409 HANDLE pipe = p;
3410 HANDLE ovpn_pipe = NULL, svc_pipe = NULL;
3411 PTOKEN_USER svc_user = NULL, ovpn_user = NULL;
3412 HANDLE svc_token = NULL, imp_token = NULL, pri_token = NULL;
3413 HANDLE stdin_read = NULL, stdin_write = NULL;
3414 HANDLE stdout_write = NULL;
3415 DWORD pipe_mode, len, exit_code = 0;
3416 STARTUP_DATA sud = { 0, 0, 0 };
3417 STARTUPINFOW startup_info;
3418 PROCESS_INFORMATION proc_info;
3419 LPVOID user_env = NULL;
3420 WCHAR ovpn_pipe_name[256]; /* The entire pipe name string can be up to 256 characters long
3421 according to MSDN. */
3422 LPCWSTR exe_path;
3423 WCHAR *cmdline = NULL;
3424 size_t cmdline_size;
3425 undo_lists_t undo_lists;
3426 WCHAR errmsg[512] = L"";
3427 BOOL flush_pipe = TRUE;
3428
3429 SECURITY_ATTRIBUTES inheritable = { .nLength = sizeof(inheritable),
3430 .lpSecurityDescriptor = NULL,
3431 .bInheritHandle = TRUE };
3432
3433 PACL ovpn_dacl;
3434 EXPLICIT_ACCESS ea[2];
3435 SECURITY_DESCRIPTOR ovpn_sd;
3436 SECURITY_ATTRIBUTES ovpn_sa = { .nLength = sizeof(ovpn_sa),
3437 .lpSecurityDescriptor = &ovpn_sd,
3438 .bInheritHandle = FALSE };
3439
3440 ZeroMemory(&ea, sizeof(ea));
3441 ZeroMemory(&startup_info, sizeof(startup_info));
3442 ZeroMemory(&undo_lists, sizeof(undo_lists));
3443 ZeroMemory(&proc_info, sizeof(proc_info));
3444
3445 if (!GetStartupData(pipe, &sud))
3446 {
3447 flush_pipe = FALSE; /* client did not provide startup data */
3448 goto out;
3449 }
3450
3451 if (!InitializeSecurityDescriptor(&ovpn_sd, SECURITY_DESCRIPTOR_REVISION))
3452 {
3453 ReturnLastError(pipe, L"InitializeSecurityDescriptor");
3454 goto out;
3455 }
3456
3457 /* Get SID of user the service is running under */
3458 if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &svc_token))
3459 {
3460 ReturnLastError(pipe, L"OpenProcessToken");
3461 goto out;
3462 }
3463 len = 0;
3464 while (!GetTokenInformation(svc_token, TokenUser, svc_user, len, &len))
3465 {
3466 if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
3467 {
3468 ReturnLastError(pipe, L"GetTokenInformation (service token)");
3469 goto out;
3470 }
3471 free(svc_user);
3472 svc_user = malloc(len);
3473 if (svc_user == NULL)
3474 {
3475 ReturnLastError(pipe, L"malloc (service token user)");
3476 goto out;
3477 }
3478 }
3479 if (!IsValidSid(svc_user->User.Sid))
3480 {
3481 ReturnLastError(pipe, L"IsValidSid (service token user)");
3482 goto out;
3483 }
3484
3485 if (!ImpersonateNamedPipeClient(pipe))
3486 {
3487 ReturnLastError(pipe, L"ImpersonateNamedPipeClient");
3488 goto out;
3489 }
3490 if (!OpenThreadToken(GetCurrentThread(), TOKEN_ALL_ACCESS, FALSE, &imp_token))
3491 {
3492 ReturnLastError(pipe, L"OpenThreadToken");
3493 goto out;
3494 }
3495 len = 0;
3496 while (!GetTokenInformation(imp_token, TokenUser, ovpn_user, len, &len))
3497 {
3498 if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
3499 {
3500 ReturnLastError(pipe, L"GetTokenInformation (impersonation token)");
3501 goto out;
3502 }
3503 free(ovpn_user);
3504 ovpn_user = malloc(len);
3505 if (ovpn_user == NULL)
3506 {
3507 ReturnLastError(pipe, L"malloc (impersonation token user)");
3508 goto out;
3509 }
3510 }
3511 if (!IsValidSid(ovpn_user->User.Sid))
3512 {
3513 ReturnLastError(pipe, L"IsValidSid (impersonation token user)");
3514 goto out;
3515 }
3516
3517 /*
3518 * Only authorized users are allowed to use any command line options or
3519 * have the config file in locations other than the global config directory.
3520 *
3521 * Check options are white-listed and config is in the global directory
3522 * OR user is authorized to run any config.
3523 */
3524 if (!ValidateOptions(pipe, sud.directory, sud.options, errmsg, _countof(errmsg))
3525 && !IsAuthorizedUser(ovpn_user->User.Sid, imp_token, settings.ovpn_admin_group,
3526 settings.ovpn_service_user))
3527 {
3528 ReturnError(pipe, ERROR_STARTUP_DATA, errmsg, 1, &exit_event);
3529 goto out;
3530 }
3531
3532 /* OpenVPN process DACL entry for access by service and user */
3533 ea[0].grfAccessPermissions = SPECIFIC_RIGHTS_ALL | STANDARD_RIGHTS_ALL;
3534 ea[0].grfAccessMode = SET_ACCESS;
3535 ea[0].grfInheritance = NO_INHERITANCE;
3536 ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
3537 ea[0].Trustee.TrusteeType = TRUSTEE_IS_UNKNOWN;
3538 ea[0].Trustee.ptstrName = (LPWSTR)svc_user->User.Sid;
3539 ea[1].grfAccessPermissions = READ_CONTROL | PROCESS_VM_READ | SYNCHRONIZE
3540 | PROCESS_TERMINATE | PROCESS_QUERY_INFORMATION;
3541 ea[1].grfAccessMode = SET_ACCESS;
3542 ea[1].grfInheritance = NO_INHERITANCE;
3543 ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID;
3544 ea[1].Trustee.TrusteeType = TRUSTEE_IS_UNKNOWN;
3545 ea[1].Trustee.ptstrName = (LPWSTR)ovpn_user->User.Sid;
3546
3547 /* Set owner and DACL of OpenVPN security descriptor */
3548 if (!SetSecurityDescriptorOwner(&ovpn_sd, svc_user->User.Sid, FALSE))
3549 {
3550 ReturnLastError(pipe, L"SetSecurityDescriptorOwner");
3551 goto out;
3552 }
3553 if (SetEntriesInAcl(2, ea, NULL, &ovpn_dacl) != ERROR_SUCCESS)
3554 {
3555 ReturnLastError(pipe, L"SetEntriesInAcl");
3556 goto out;
3557 }
3558 if (!SetSecurityDescriptorDacl(&ovpn_sd, TRUE, ovpn_dacl, FALSE))
3559 {
3560 ReturnLastError(pipe, L"SetSecurityDescriptorDacl");
3561 goto out;
3562 }
3563
3564 /* Create primary token from impersonation token */
3565 if (!DuplicateTokenEx(imp_token, TOKEN_ALL_ACCESS, NULL, 0, TokenPrimary, &pri_token))
3566 {
3567 ReturnLastError(pipe, L"DuplicateTokenEx");
3568 goto out;
3569 }
3570
3571 /* use /dev/null for stdout of openvpn (client should use --log for output) */
3572 stdout_write = CreateFile(_L("NUL"), GENERIC_WRITE, FILE_SHARE_WRITE, &inheritable,
3573 OPEN_EXISTING, 0, NULL);
3574 if (stdout_write == INVALID_HANDLE_VALUE)
3575 {
3576 ReturnLastError(pipe, L"CreateFile for stdout");
3577 goto out;
3578 }
3579
3580 if (!CreatePipe(&stdin_read, &stdin_write, &inheritable, 0)
3581 || !SetHandleInformation(stdin_write, HANDLE_FLAG_INHERIT, 0))
3582 {
3583 ReturnLastError(pipe, L"CreatePipe");
3584 goto out;
3585 }
3586
3587 UUID pipe_uuid;
3588 RPC_STATUS rpc_stat = UuidCreate(&pipe_uuid);
3589 if (rpc_stat != RPC_S_OK)
3590 {
3591 ReturnError(pipe, rpc_stat, L"UuidCreate", 1, &exit_event);
3592 goto out;
3593 }
3594
3595 RPC_WSTR pipe_uuid_str = NULL;
3596 rpc_stat = UuidToStringW(&pipe_uuid, &pipe_uuid_str);
3597 if (rpc_stat != RPC_S_OK)
3598 {
3599 ReturnError(pipe, rpc_stat, L"UuidToString", 1, &exit_event);
3600 goto out;
3601 }
3602 swprintf(ovpn_pipe_name, _countof(ovpn_pipe_name),
3603 // cppcheck-suppress unknownMacro ; FP, complains about _L only here...
3604 L"\\\\.\\pipe\\" _L(PACKAGE) L"%ls\\service_%lu_%ls", service_instance,
3605 GetCurrentThreadId(), pipe_uuid_str);
3606 RpcStringFreeW(&pipe_uuid_str);
3607
3608 /* make a security descriptor for the named pipe with access
3609 * restricted to the user and SYSTEM
3610 */
3611
3612 SECURITY_ATTRIBUTES sa;
3613 PSECURITY_DESCRIPTOR pSD = NULL;
3614 LPCWSTR szSDDL = L"D:(A;;GA;;;SY)(A;;GA;;;OW)";
3615 if (!ConvertStringSecurityDescriptorToSecurityDescriptorW(
3616 szSDDL, SDDL_REVISION_1, &pSD, NULL))
3617 {
3618 ReturnLastError(pipe, L"ConvertSDDL");
3619 goto out;
3620 }
3621 sa.nLength = sizeof(sa);
3622 sa.lpSecurityDescriptor = pSD;
3623 sa.bInheritHandle = FALSE;
3624
3625 ovpn_pipe = CreateNamedPipe(
3626 ovpn_pipe_name, PIPE_ACCESS_DUPLEX | FILE_FLAG_FIRST_PIPE_INSTANCE | FILE_FLAG_OVERLAPPED,
3627 PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT | PIPE_REJECT_REMOTE_CLIENTS, 1, 128, 128, 0, &sa);
3628 if (ovpn_pipe == INVALID_HANDLE_VALUE)
3629 {
3630 ReturnLastError(pipe, L"CreateNamedPipe");
3631 goto out;
3632 }
3633
3634 svc_pipe = CreateFile(ovpn_pipe_name, GENERIC_READ | GENERIC_WRITE, 0, &inheritable,
3635 OPEN_EXISTING, 0, NULL);
3636 if (svc_pipe == INVALID_HANDLE_VALUE)
3637 {
3638 ReturnLastError(pipe, L"CreateFile");
3639 goto out;
3640 }
3641
3642 pipe_mode = PIPE_READMODE_MESSAGE;
3643 if (!SetNamedPipeHandleState(svc_pipe, &pipe_mode, NULL, NULL))
3644 {
3645 ReturnLastError(pipe, L"SetNamedPipeHandleState");
3646 goto out;
3647 }
3648
3649 cmdline_size = wcslen(sud.options) + 128;
3650 cmdline = malloc(cmdline_size * sizeof(*cmdline));
3651 if (cmdline == NULL)
3652 {
3653 ReturnLastError(pipe, L"malloc");
3654 goto out;
3655 }
3656 /* there seem to be no common printf specifier that works on all
3657 * mingw/msvc platforms without trickery, so convert to void* and use
3658 * PRIuPTR to print that as best compromise */
3659 swprintf(cmdline, cmdline_size, L"openvpn %ls --msg-channel %" PRIuPTR, sud.options,
3660 (uintptr_t)svc_pipe);
3661
3662 if (!CreateEnvironmentBlock(&user_env, imp_token, FALSE))
3663 {
3664 ReturnLastError(pipe, L"CreateEnvironmentBlock");
3665 goto out;
3666 }
3667
3668 startup_info.cb = sizeof(startup_info);
3669 startup_info.dwFlags = STARTF_USESTDHANDLES;
3670 startup_info.hStdInput = stdin_read;
3671 startup_info.hStdOutput = stdout_write;
3672 startup_info.hStdError = stdout_write;
3673
3674 exe_path = settings.exe_path;
3675
3676 /* TODO: make sure HKCU is correct or call LoadUserProfile() */
3677 if (!CreateProcessAsUserW(pri_token, exe_path, cmdline, &ovpn_sa, NULL, TRUE,
3678 settings.priority | CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT,
3679 user_env, sud.directory, &startup_info, &proc_info))
3680 {
3681 ReturnLastError(pipe, L"CreateProcessAsUser");
3682 goto out;
3683 }
3684
3685 if (!RevertToSelf())
3686 {
3687 TerminateProcess(proc_info.hProcess, 1);
3688 ReturnLastError(pipe, L"RevertToSelf");
3689 goto out;
3690 }
3691
3692 ReturnProcessId(pipe, proc_info.dwProcessId, 1, &exit_event);
3693
3694 CloseHandleEx(&stdout_write);
3695 CloseHandleEx(&stdin_read);
3696 CloseHandleEx(&svc_pipe);
3697
3698 DWORD input_size = WideCharToMultiByte(CP_UTF8, 0, sud.std_input, -1, NULL, 0, NULL, NULL);
3699 LPSTR input = NULL;
3700 if (input_size && (input = malloc(input_size)))
3701 {
3702 DWORD written;
3703 WideCharToMultiByte(CP_UTF8, 0, sud.std_input, -1, input, input_size, NULL, NULL);
3704 WriteFile(stdin_write, input, (DWORD)strlen(input), &written, NULL);
3705 free(input);
3706 }
3707
3708 while (TRUE)
3709 {
3710 DWORD bytes = PeekNamedPipeAsync(ovpn_pipe, 1, &exit_event);
3711 if (bytes == 0)
3712 {
3713 break;
3714 }
3715
3716 if (bytes > sizeof(pipe_message_t))
3717 {
3718 /* process at the other side of the pipe is misbehaving, shut it down */
3721 L"OpenVPN process sent too large payload length to the pipe (%lu bytes), it will be terminated",
3722 bytes);
3723 break;
3724 }
3725
3726 HandleMessage(ovpn_pipe, &proc_info, bytes, 1, &exit_event, &undo_lists);
3727 }
3728
3729 WaitForSingleObject(proc_info.hProcess, IO_TIMEOUT);
3730 GetExitCodeProcess(proc_info.hProcess, &exit_code);
3731 if (exit_code == STILL_ACTIVE)
3732 {
3733 TerminateProcess(proc_info.hProcess, 1);
3734 }
3735 else if (exit_code != 0)
3736 {
3737 WCHAR buf[256];
3738 swprintf(buf, _countof(buf), L"OpenVPN exited with error: exit code = %lu", exit_code);
3740 }
3741 Undo(&undo_lists);
3742
3743out:
3744 if (flush_pipe)
3745 {
3746 FlushFileBuffers(pipe);
3747 }
3748 DisconnectNamedPipe(pipe);
3749
3750 free(ovpn_user);
3751 free(svc_user);
3752 free(cmdline);
3753 DestroyEnvironmentBlock(user_env);
3754 FreeStartupData(&sud);
3755 CloseHandleEx(&proc_info.hProcess);
3756 CloseHandleEx(&proc_info.hThread);
3757 CloseHandleEx(&stdin_read);
3758 CloseHandleEx(&stdin_write);
3759 CloseHandleEx(&stdout_write);
3760 CloseHandleEx(&svc_token);
3761 CloseHandleEx(&imp_token);
3762 CloseHandleEx(&pri_token);
3763 CloseHandleEx(&ovpn_pipe);
3764 CloseHandleEx(&svc_pipe);
3765 CloseHandleEx(&pipe);
3766
3767 return 0;
3768}
3769
3770
3771static DWORD WINAPI
3772ServiceCtrlInteractive(DWORD ctrl_code, DWORD event, LPVOID data, LPVOID ctx)
3773{
3774 SERVICE_STATUS *svc_status = ctx;
3775 switch (ctrl_code)
3776 {
3777 case SERVICE_CONTROL_STOP:
3778 svc_status->dwCurrentState = SERVICE_STOP_PENDING;
3779 ReportStatusToSCMgr(service, svc_status);
3780 if (exit_event)
3781 {
3782 SetEvent(exit_event);
3783 }
3784 return NO_ERROR;
3785
3786 case SERVICE_CONTROL_INTERROGATE:
3787 return NO_ERROR;
3788
3789 default:
3790 return ERROR_CALL_NOT_IMPLEMENTED;
3791 }
3792}
3793
3794
3795static HANDLE
3797{
3798 /*
3799 * allow all access for local system
3800 * deny FILE_CREATE_PIPE_INSTANCE for everyone
3801 * allow read/write for authenticated users
3802 * deny all access to anonymous
3803 */
3804 const WCHAR *sddlString =
3805 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)";
3806
3807 PSECURITY_DESCRIPTOR sd = NULL;
3808 if (!ConvertStringSecurityDescriptorToSecurityDescriptor(sddlString, SDDL_REVISION_1, &sd,
3809 NULL))
3810 {
3811 MsgToEventLog(M_SYSERR, L"ConvertStringSecurityDescriptorToSecurityDescriptor failed.");
3812 return INVALID_HANDLE_VALUE;
3813 }
3814
3815 /* Set up SECURITY_ATTRIBUTES */
3816 SECURITY_ATTRIBUTES sa = { 0 };
3817 sa.nLength = sizeof(SECURITY_ATTRIBUTES);
3818 sa.lpSecurityDescriptor = sd;
3819 sa.bInheritHandle = FALSE;
3820
3821 DWORD flags = PIPE_ACCESS_DUPLEX | WRITE_DAC | FILE_FLAG_OVERLAPPED;
3822
3823 static BOOL first = TRUE;
3824 if (first)
3825 {
3826 flags |= FILE_FLAG_FIRST_PIPE_INSTANCE;
3827 first = FALSE;
3828 }
3829
3830 WCHAR pipe_name[256]; /* The entire pipe name string can be up to 256 characters long according
3831 to MSDN. */
3832 swprintf(pipe_name, _countof(pipe_name), L"\\\\.\\pipe\\" _L(PACKAGE) L"%ls\\service",
3834 HANDLE pipe = CreateNamedPipe(
3835 pipe_name, flags, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_REJECT_REMOTE_CLIENTS,
3836 PIPE_UNLIMITED_INSTANCES, 1024, 1024, 0, &sa);
3837
3838 LocalFree(sd);
3839
3840 if (pipe == INVALID_HANDLE_VALUE)
3841 {
3842 MsgToEventLog(M_SYSERR, L"Could not create named pipe");
3843 return INVALID_HANDLE_VALUE;
3844 }
3845
3846 return pipe;
3847}
3848
3849
3850static DWORD
3851UpdateWaitHandles(LPHANDLE *handles_ptr, LPDWORD count, HANDLE io_event,
3852 const list_item_t *threads)
3853{
3854 static DWORD size = 10;
3855 static LPHANDLE handles = NULL;
3856 DWORD pos = 0;
3857
3858 if (handles == NULL)
3859 {
3860 handles = malloc(size * sizeof(HANDLE));
3861 *handles_ptr = handles;
3862 if (handles == NULL)
3863 {
3864 return ERROR_OUTOFMEMORY;
3865 }
3866 }
3867
3868 handles[pos++] = io_event;
3869
3870 if (!threads)
3871 {
3872 handles[pos++] = exit_event;
3873 }
3874
3875 while (threads)
3876 {
3877 if (pos == size)
3878 {
3879 LPHANDLE tmp;
3880 size += 10;
3881 tmp = realloc(handles, size * sizeof(HANDLE));
3882 if (tmp == NULL)
3883 {
3884 size -= 10;
3885 *count = pos;
3886 return ERROR_OUTOFMEMORY;
3887 }
3888 handles = tmp;
3889 *handles_ptr = handles;
3890 }
3891 handles[pos++] = threads->data;
3892 threads = threads->next;
3893 }
3894
3895 *count = pos;
3896 return NO_ERROR;
3897}
3898
3899
3900static VOID
3902{
3903 free(h);
3904}
3905
3906static BOOL
3907CmpHandle(LPVOID item, LPVOID hnd)
3908{
3909 return item == hnd;
3910}
3911
3912
3913VOID WINAPI
3914ServiceStartInteractiveOwn(DWORD dwArgc, LPWSTR *lpszArgv)
3915{
3916 status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
3917 ServiceStartInteractive(dwArgc, lpszArgv);
3918}
3919
3925static void
3927{
3928 BOOL changed = FALSE;
3929
3930 /* Clean up leftover NRPT rules */
3931 BOOL gpol_nrpt;
3932 changed = DeleteNrptRules(0, &gpol_nrpt);
3933
3934 /* Clean up leftover DNS search list fragments */
3935 HKEY key;
3936 BOOL gpol_list;
3937 GetDnsSearchListKey(NULL, &gpol_list, &key);
3938 if (key != INVALID_HANDLE_VALUE)
3939 {
3941 {
3942 changed = TRUE;
3943 }
3944 RegCloseKey(key);
3945 }
3946
3947 if (changed)
3948 {
3949 ApplyDnsSettings(gpol_nrpt || gpol_list);
3950 }
3951}
3952
3953VOID WINAPI
3954ServiceStartInteractive(DWORD dwArgc, LPWSTR *lpszArgv)
3955{
3956 HANDLE pipe, io_event = NULL;
3957 OVERLAPPED overlapped;
3958 DWORD error = NO_ERROR;
3959 list_item_t *threads = NULL;
3960 PHANDLE handles = NULL;
3961 DWORD handle_count;
3962
3963 service =
3964 RegisterServiceCtrlHandlerEx(interactive_service.name, ServiceCtrlInteractive, &status);
3965 if (!service)
3966 {
3967 return;
3968 }
3969
3970 status.dwCurrentState = SERVICE_START_PENDING;
3971 status.dwServiceSpecificExitCode = NO_ERROR;
3972 status.dwWin32ExitCode = NO_ERROR;
3973 status.dwWaitHint = 3000;
3975
3976 /* Clean up potentially left over registry values */
3978
3979 /* Read info from registry in key HKLM\SOFTWARE\OpenVPN */
3980 error = GetOpenvpnSettings(&settings);
3981 if (error != ERROR_SUCCESS)
3982 {
3983 goto out;
3984 }
3985
3986 io_event = InitOverlapped(&overlapped);
3987 exit_event = CreateEvent(NULL, TRUE, FALSE, NULL);
3988 if (!exit_event || !io_event)
3989 {
3990 error = MsgToEventLog(M_SYSERR, L"Could not create event");
3991 goto out;
3992 }
3993
3994 rdns_semaphore = CreateSemaphoreW(NULL, 1, 1, NULL);
3995 if (!rdns_semaphore)
3996 {
3997 error = MsgToEventLog(M_SYSERR, L"Could not create semaphore for register-dns");
3998 goto out;
3999 }
4000
4001 error = UpdateWaitHandles(&handles, &handle_count, io_event, threads);
4002 if (error != NO_ERROR)
4003 {
4004 goto out;
4005 }
4006
4007 pipe = CreateClientPipeInstance();
4008 if (pipe == INVALID_HANDLE_VALUE)
4009 {
4010 goto out;
4011 }
4012
4013 status.dwCurrentState = SERVICE_RUNNING;
4014 status.dwWaitHint = 0;
4016
4017 while (TRUE)
4018 {
4019 if (!ConnectNamedPipe(pipe, &overlapped))
4020 {
4021 DWORD connect_error = GetLastError();
4022 if (connect_error == ERROR_NO_DATA)
4023 {
4024 /*
4025 * Client connected and disconnected before we could process it.
4026 * Disconnect and retry instead of aborting the service.
4027 */
4028 MsgToEventLog(M_ERR, L"ConnectNamedPipe returned ERROR_NO_DATA (client dropped)");
4029 DisconnectNamedPipe(pipe);
4030 ResetOverlapped(&overlapped);
4031 continue;
4032 }
4033 else if (connect_error == ERROR_PIPE_CONNECTED)
4034 {
4035 /* No async I/O pending in this case; signal manually. */
4036 SetEvent(overlapped.hEvent);
4037 }
4038 else if (connect_error != ERROR_IO_PENDING)
4039 {
4040 MsgToEventLog(M_SYSERR, L"Could not connect pipe");
4041 break;
4042 }
4043 }
4044
4045 error = WaitForMultipleObjects(handle_count, handles, FALSE, INFINITE);
4046 if (error == WAIT_OBJECT_0)
4047 {
4048 /* Client connected, spawn a worker thread for it */
4049 HANDLE next_pipe = CreateClientPipeInstance();
4050
4051 /* Avoid exceeding WaitForMultipleObjects MAXIMUM_WAIT_OBJECTS */
4052 if (handle_count + 1 > MAXIMUM_WAIT_OBJECTS)
4053 {
4054 ReturnError(pipe, ERROR_CANT_WAIT, L"Too many concurrent clients", 1, &exit_event);
4055 CloseHandleEx(&pipe);
4056 pipe = next_pipe;
4057 ResetOverlapped(&overlapped);
4058 continue;
4059 }
4060
4061 HANDLE thread = CreateThread(NULL, 0, RunOpenvpn, pipe, CREATE_SUSPENDED, NULL);
4062 if (thread)
4063 {
4064 error = AddListItem(&threads, thread);
4065 if (!error)
4066 {
4067 error =
4068 UpdateWaitHandles(&handles, &handle_count, io_event, threads);
4069 }
4070 if (error)
4071 {
4072 ReturnError(pipe, error, L"Insufficient resources to service new clients", 1,
4073 &exit_event);
4074 /* Update wait handles again after removing the last worker thread */
4075 RemoveListItem(&threads, CmpHandle, thread);
4076 UpdateWaitHandles(&handles, &handle_count, io_event, threads);
4077 TerminateThread(thread, 1);
4078 CloseHandleEx(&thread);
4079 CloseHandleEx(&pipe);
4080 }
4081 else
4082 {
4083 ResumeThread(thread);
4084 }
4085 }
4086 else
4087 {
4088 CloseHandleEx(&pipe);
4089 }
4090
4091 ResetOverlapped(&overlapped);
4092 pipe = next_pipe;
4093 }
4094 else
4095 {
4096 CancelIo(pipe);
4097 if (error == WAIT_FAILED)
4098 {
4099 MsgToEventLog(M_SYSERR, L"WaitForMultipleObjects failed");
4100 SetEvent(exit_event);
4101 /* Give some time for worker threads to exit and then terminate */
4102 Sleep(1000);
4103 break;
4104 }
4105 if (!threads)
4106 {
4107 /* exit event signaled */
4108 CloseHandleEx(&pipe);
4109 ResetEvent(exit_event);
4110 error = NO_ERROR;
4111 break;
4112 }
4113
4114 /* Worker thread ended */
4115 HANDLE thread = RemoveListItem(&threads, CmpHandle, handles[error]);
4116 UpdateWaitHandles(&handles, &handle_count, io_event, threads);
4117 CloseHandleEx(&thread);
4118 }
4119 }
4120
4121out:
4122 FreeWaitHandles(handles);
4123 CloseHandleEx(&io_event);
4126
4127 status.dwCurrentState = SERVICE_STOPPED;
4128 status.dwWin32ExitCode = error;
4130}
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 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 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 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 SOCKADDR_INET sockaddr_inet(short family, inet_address_t *addr)
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 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 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 ValidateOptions(HANDLE pipe, const WCHAR *workdir, const WCHAR *options, WCHAR *errmsg, DWORD capacity)
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 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
nrpt_address_t addresses[NRPT_ADDR_NUM]
CHAR addresses[NRPT_ADDR_NUM *NRPT_ADDR_SIZE]
interface_t iface
#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:328
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