Browse code

win: implement --dns option support with NRPT

Implement support for setting options from --dns. This is hugely
different than what we had so far with DNS related --dhcp-option.

The main difference it that we support split DNS and DNSSEC by making
use of NRPT (Name Resolution Policy Table). Also OpenVPN tries to keep
local DNS resolution working when DNS is redirected into the tunnel. To
prevent this from happening we have --block-outside-dns, in case you
wonder. Basically we collect domains and name server addresses from
network adapters and add so called exclude NRPT rules in addition to the
catch all rule that is pushed by the server.

All is done via the interactive service, since modifying all this
requires the elevated privileges that the openvpn process hopefully
doesn't have.

Change-Id: I576e74f3276362606e9cbd50bb5adbebaaf209cc
Signed-off-by: Heiko Hund <heiko@ist.eigentlich.net>
Acked-by: Lev Stipakov <lstipakov@gmail.com>
Message-Id: <20250414180636.31936-1-gert@greenie.muc.de>
URL: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg31426.html
Signed-off-by: Gert Doering <gert@greenie.muc.de>

Heiko Hund authored on 2025/04/15 03:06:26
Showing 5 changed files
... ...
@@ -35,6 +35,8 @@ typedef enum {
35 35
     msg_del_route,
36 36
     msg_add_dns_cfg,
37 37
     msg_del_dns_cfg,
38
+    msg_add_nrpt_cfg,
39
+    msg_del_nrpt_cfg,
38 40
     msg_add_nbt_cfg,
39 41
     msg_del_nbt_cfg,
40 42
     msg_flush_neighbors,
... ...
@@ -96,6 +98,23 @@ typedef struct {
96 96
     inet_address_t addr[4]; /* support up to 4 dns addresses */
97 97
 } dns_cfg_message_t;
98 98
 
99
+
100
+typedef enum {
101
+    nrpt_dnssec
102
+} nrpt_flags_t;
103
+
104
+#define NRPT_ADDR_NUM 8   /* Max. number of addresses */
105
+#define NRPT_ADDR_SIZE 48 /* Max. address strlen + some */
106
+typedef char nrpt_address_t[NRPT_ADDR_SIZE];
107
+typedef struct {
108
+    message_header_t header;
109
+    interface_t iface;
110
+    nrpt_address_t addresses[NRPT_ADDR_NUM];
111
+    char resolve_domains[512]; /* double \0 terminated */
112
+    char search_domains[512];
113
+    nrpt_flags_t flags;
114
+} nrpt_dns_cfg_message_t;
115
+
99 116
 typedef struct {
100 117
     message_header_t header;
101 118
     interface_t iface;
... ...
@@ -29,6 +29,12 @@
29 29
 
30 30
 #include "dns.h"
31 31
 #include "socket.h"
32
+#include "options.h"
33
+
34
+#ifdef _WIN32
35
+#include "win32.h"
36
+#include "openvpn-msg.h"
37
+#endif
32 38
 
33 39
 /**
34 40
  * Parses a string as port and stores it
... ...
@@ -428,6 +434,122 @@ setenv_dns_options(const struct dns_options *o, struct env_set *es)
428 428
     gc_free(&gc);
429 429
 }
430 430
 
431
+#ifdef _WIN32
432
+
433
+static void
434
+make_domain_list(const char *what, const struct dns_domain *src,
435
+                 bool nrpt_domains, char *dst, size_t dst_size)
436
+{
437
+    /* NRPT domains need two \0 at the end for REG_MULTI_SZ
438
+     * and a leading '.' added in front of the domain name */
439
+    size_t term_size = nrpt_domains ? 2 : 1;
440
+    size_t leading_dot = nrpt_domains ? 1 : 0;
441
+    size_t offset = 0;
442
+
443
+    memset(dst, 0, dst_size);
444
+
445
+    while (src)
446
+    {
447
+        size_t len = strlen(src->name);
448
+        if (offset + leading_dot + len + term_size > dst_size)
449
+        {
450
+            msg(M_WARN, "WARNING: %s truncated", what);
451
+            if (offset)
452
+            {
453
+                /* Remove trailing comma */
454
+                *(dst + offset - 1) = '\0';
455
+            }
456
+            break;
457
+        }
458
+
459
+        if (leading_dot)
460
+        {
461
+            *(dst + offset++) = '.';
462
+        }
463
+        strncpy(dst + offset, src->name, len);
464
+        offset += len;
465
+
466
+        src = src->next;
467
+        if (src)
468
+        {
469
+            *(dst + offset++) = ',';
470
+        }
471
+    }
472
+}
473
+
474
+static void
475
+run_up_down_service(bool add, const struct options *o, const struct tuntap *tt)
476
+{
477
+    const struct dns_server *server = o->dns_options.servers;
478
+    const struct dns_domain *search_domains = o->dns_options.search_domains;
479
+
480
+    while (true)
481
+    {
482
+        if (!server)
483
+        {
484
+            if (add)
485
+            {
486
+                msg(M_WARN, "WARNING: setting DNS failed, no compatible server profile");
487
+            }
488
+            return;
489
+        }
490
+
491
+        bool only_standard_server_ports = true;
492
+        for (size_t i = 0; i < NRPT_ADDR_NUM; ++i)
493
+        {
494
+            if (server->addr[i].port && server->addr[i].port != 53)
495
+            {
496
+                only_standard_server_ports = false;
497
+                break;
498
+            }
499
+        }
500
+        if ((server->transport == DNS_TRANSPORT_UNSET || server->transport == DNS_TRANSPORT_PLAIN)
501
+            && only_standard_server_ports)
502
+        {
503
+            break; /* found compatible server */
504
+        }
505
+
506
+        server = server->next;
507
+    }
508
+
509
+    ack_message_t ack;
510
+    nrpt_dns_cfg_message_t nrpt = {
511
+        .header = {
512
+            (add ? msg_add_nrpt_cfg : msg_del_nrpt_cfg),
513
+            sizeof(nrpt_dns_cfg_message_t),
514
+            0
515
+        },
516
+        .iface = { .index = tt->adapter_index, .name = "" },
517
+        .flags = server->dnssec == DNS_SECURITY_NO ? 0 : nrpt_dnssec,
518
+    };
519
+    strncpynt(nrpt.iface.name, tt->actual_name, sizeof(nrpt.iface.name));
520
+
521
+    for (size_t i = 0; i < NRPT_ADDR_NUM; ++i)
522
+    {
523
+        if (server->addr[i].family == AF_UNSPEC)
524
+        {
525
+            /* No more addresses */
526
+            break;
527
+        }
528
+
529
+        if (inet_ntop(server->addr[i].family, &server->addr[i].in,
530
+                      nrpt.addresses[i], NRPT_ADDR_SIZE) == NULL)
531
+        {
532
+            msg(M_WARN, "WARNING: could not convert dns server address");
533
+        }
534
+    }
535
+
536
+    make_domain_list("dns server resolve domains", server->domains, true,
537
+                     nrpt.resolve_domains, sizeof(nrpt.resolve_domains));
538
+
539
+    make_domain_list("dns search domains", search_domains, false,
540
+                     nrpt.search_domains, sizeof(nrpt.search_domains));
541
+
542
+    send_msg_iservice(o->msg_channel, &nrpt, sizeof(nrpt), &ack, "DNS");
543
+}
544
+
545
+#endif /* _WIN32 */
546
+
431 547
 void
432 548
 show_dns_options(const struct dns_options *o)
433 549
 {
... ...
@@ -506,3 +628,43 @@ show_dns_options(const struct dns_options *o)
506 506
 
507 507
     gc_free(&gc);
508 508
 }
509
+
510
+void
511
+run_dns_up_down(bool up, struct options *o, const struct tuntap *tt)
512
+{
513
+    if (!o->dns_options.servers)
514
+    {
515
+        return;
516
+    }
517
+
518
+    /* Warn about adding servers of unsupported AF */
519
+    const struct dns_server *s = o->dns_options.servers;
520
+    while (up && s)
521
+    {
522
+        size_t bad_count = 0;
523
+        for (size_t i = 0; i < s->addr_count; ++i)
524
+        {
525
+            if ((s->addr[i].family == AF_INET6 && !tt->did_ifconfig_ipv6_setup)
526
+                || (s->addr[i].family == AF_INET && !tt->did_ifconfig_setup))
527
+            {
528
+                ++bad_count;
529
+            }
530
+        }
531
+        if (bad_count == s->addr_count)
532
+        {
533
+            msg(M_WARN, "DNS server %ld only has address(es) from a family "
534
+                "the tunnel is not configured for - it will not be reachable",
535
+                s->priority);
536
+        }
537
+        else if (bad_count)
538
+        {
539
+            msg(M_WARN, "DNS server %ld has address(es) from a family "
540
+                "the tunnel is not configured for", s->priority);
541
+        }
542
+        s = s->next;
543
+    }
544
+
545
+#ifdef _WIN32
546
+    run_up_down_service(up, o, tt);
547
+#endif /* ifdef _WIN32 */
548
+}
... ...
@@ -26,6 +26,7 @@
26 26
 
27 27
 #include "buffer.h"
28 28
 #include "env_set.h"
29
+#include "tun.h"
29 30
 
30 31
 enum dns_security {
31 32
     DNS_SECURITY_UNSET,
... ...
@@ -147,6 +148,14 @@ void dns_options_preprocess_pull(struct dns_options *o);
147 147
 void dns_options_postprocess_pull(struct dns_options *o);
148 148
 
149 149
 /**
150
+ * Invokes the action associated with bringing DNS up or down
151
+ * @param   up          Boolean to set this call to "up" when true
152
+ * @param   o           Pointer to the program options
153
+ * @param   tt          Pointer to the connection's tuntap struct
154
+ */
155
+void run_dns_up_down(bool up, struct options *o, const struct tuntap *tt);
156
+
157
+/**
150 158
  * Puts the DNS options into an environment set.
151 159
  *
152 160
  * @param   o           Pointer to the DNS options to set
... ...
@@ -2026,6 +2026,8 @@ do_open_tun(struct context *c, int *error_flags)
2026 2026
                         c->c2.frame.tun_mtu, c->c2.es, &c->net_ctx);
2027 2027
         }
2028 2028
 
2029
+        run_dns_up_down(true, &c->options, c->c1.tuntap);
2030
+
2029 2031
         /* run the up script */
2030 2032
         run_up_down(c->options.up_script,
2031 2033
                     c->plugins,
... ...
@@ -2064,6 +2066,8 @@ do_open_tun(struct context *c, int *error_flags)
2064 2064
         /* explicitly set the ifconfig_* env vars */
2065 2065
         do_ifconfig_setenv(c->c1.tuntap, c->c2.es);
2066 2066
 
2067
+        run_dns_up_down(true, &c->options, c->c1.tuntap);
2068
+
2067 2069
         /* run the up script if user specified --up-restart */
2068 2070
         if (c->options.up_restart)
2069 2071
         {
... ...
@@ -2152,6 +2156,8 @@ do_close_tun(struct context *c, bool force)
2152 2152
     adapter_index = c->c1.tuntap->adapter_index;
2153 2153
 #endif
2154 2154
 
2155
+    run_dns_up_down(false, &c->options, c->c1.tuntap);
2156
+
2155 2157
     if (force || !(c->sig->signal_received == SIGUSR1 && c->options.persist_tun))
2156 2158
     {
2157 2159
         static_context = NULL;
... ...
@@ -88,6 +88,7 @@ typedef enum {
88 88
     wfp_block,
89 89
     undo_dns4,
90 90
     undo_dns6,
91
+    undo_nrpt,
91 92
     undo_domains,
92 93
     undo_ring_buffer,
93 94
     undo_wins,
... ...
@@ -119,12 +120,20 @@ typedef union {
119 119
     flush_neighbors_message_t flush_neighbors;
120 120
     wfp_block_message_t wfp_block;
121 121
     dns_cfg_message_t dns;
122
+    nrpt_dns_cfg_message_t nrpt_dns;
122 123
     enable_dhcp_message_t dhcp;
123 124
     register_ring_buffers_message_t rrb;
124 125
     set_mtu_message_t mtu;
125 126
     wins_cfg_message_t wins;
126 127
 } pipe_message_t;
127 128
 
129
+typedef struct {
130
+    CHAR addresses[NRPT_ADDR_NUM * NRPT_ADDR_SIZE];
131
+    WCHAR domains[512]; /* MULTI_SZ string */
132
+    DWORD domains_size; /* bytes in domains */
133
+} nrpt_exclude_data_t;
134
+
135
+
128 136
 static DWORD
129 137
 AddListItem(list_item_t **pfirst, LPVOID data)
130 138
 {
... ...
@@ -1194,13 +1203,13 @@ ApplyDnsSettings(BOOL apply_gpol)
1194 1194
 
1195 1195
     if (apply_gpol && ApplyGpolSettings() == FALSE)
1196 1196
     {
1197
-        MsgToEventLog(M_ERR, L"%s: sending GPOL notification failed", __func__);
1197
+        MsgToEventLog(M_ERR, L"%S: sending GPOL notification failed", __func__);
1198 1198
     }
1199 1199
 
1200 1200
     scm = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
1201 1201
     if (scm == NULL)
1202 1202
     {
1203
-        MsgToEventLog(M_ERR, L"%s: OpenSCManager call failed (%lu)",
1203
+        MsgToEventLog(M_ERR, L"%S: OpenSCManager call failed (%lu)",
1204 1204
                       __func__, GetLastError());
1205 1205
         goto out;
1206 1206
     }
... ...
@@ -1208,7 +1217,7 @@ ApplyDnsSettings(BOOL apply_gpol)
1208 1208
     dnssvc = OpenServiceA(scm, "Dnscache", SERVICE_PAUSE_CONTINUE);
1209 1209
     if (dnssvc == NULL)
1210 1210
     {
1211
-        MsgToEventLog(M_ERR, L"%s: OpenService call failed (%lu)",
1211
+        MsgToEventLog(M_ERR, L"%S: OpenService call failed (%lu)",
1212 1212
                       __func__, GetLastError());
1213 1213
         goto out;
1214 1214
     }
... ...
@@ -1216,7 +1225,7 @@ ApplyDnsSettings(BOOL apply_gpol)
1216 1216
     SERVICE_STATUS status;
1217 1217
     if (ControlService(dnssvc, SERVICE_CONTROL_PARAMCHANGE, &status) == 0)
1218 1218
     {
1219
-        MsgToEventLog(M_ERR, L"%s: ControlService call failed (%lu)",
1219
+        MsgToEventLog(M_ERR, L"%S: ControlService call failed (%lu)",
1220 1220
                       __func__, GetLastError());
1221 1221
         goto out;
1222 1222
     }
... ...
@@ -1255,19 +1264,19 @@ InterfaceIdString(PCSTR itf_name, PWSTR str, size_t len)
1255 1255
     err = InterfaceLuid(itf_name, &luid);
1256 1256
     if (err)
1257 1257
     {
1258
-        MsgToEventLog(M_ERR, L"%s: failed to convert itf alias '%s'", __func__, itf_name);
1258
+        MsgToEventLog(M_ERR, L"%S: failed to convert itf alias '%s'", __func__, itf_name);
1259 1259
         goto out;
1260 1260
     }
1261 1261
     err = ConvertInterfaceLuidToGuid(&luid, &guid);
1262 1262
     if (err)
1263 1263
     {
1264
-        MsgToEventLog(M_ERR, L"%s: Failed to convert itf '%s' LUID", __func__, itf_name);
1264
+        MsgToEventLog(M_ERR, L"%S: Failed to convert itf '%s' LUID", __func__, itf_name);
1265 1265
         goto out;
1266 1266
     }
1267 1267
 
1268 1268
     if (StringFromIID(&guid, &iid_str) != S_OK)
1269 1269
     {
1270
-        MsgToEventLog(M_ERR, L"%s: Failed to convert itf '%s' IID", __func__, itf_name);
1270
+        MsgToEventLog(M_ERR, L"%S: Failed to convert itf '%s' IID", __func__, itf_name);
1271 1271
         err = ERROR_OUTOFMEMORY;
1272 1272
         goto out;
1273 1273
     }
... ...
@@ -1417,7 +1426,7 @@ InitialSearchListExists(HKEY key)
1417 1417
         {
1418 1418
             return FALSE;
1419 1419
         }
1420
-        MsgToEventLog(M_ERR, L"%s: failed to get InitialSearchList (%lu)",
1420
+        MsgToEventLog(M_ERR, L"%S: failed to get InitialSearchList (%lu)",
1421 1421
                       __func__, err);
1422 1422
     }
1423 1423
 
... ...
@@ -1439,7 +1448,7 @@ StoreInitialDnsSearchList(HKEY key, PCWSTR list)
1439 1439
 {
1440 1440
     if (!list || wcslen(list) == 0)
1441 1441
     {
1442
-        MsgToEventLog(M_ERR, L"StoreInitialDnsSearchList: empty search list");
1442
+        MsgToEventLog(M_ERR, L"%S: empty search list", __func__);
1443 1443
         return FALSE;
1444 1444
     }
1445 1445
 
... ...
@@ -1453,7 +1462,7 @@ StoreInitialDnsSearchList(HKEY key, PCWSTR list)
1453 1453
     LSTATUS err = RegSetValueExW(key, L"InitialSearchList", 0, REG_SZ, (PBYTE)list, size);
1454 1454
     if (err)
1455 1455
     {
1456
-        MsgToEventLog(M_ERR, L"%s: failed to set InitialSearchList value (%lu)",
1456
+        MsgToEventLog(M_ERR, L"%S: failed to set InitialSearchList value (%lu)",
1457 1457
                       __func__, err);
1458 1458
         return FALSE;
1459 1459
     }
... ...
@@ -1482,7 +1491,7 @@ AddDnsSearchDomains(HKEY key, BOOL have_list, PCWSTR domains)
1482 1482
         err = RegGetValueW(key, NULL, L"SearchList", RRF_RT_REG_SZ, NULL, list, &size);
1483 1483
         if (err)
1484 1484
         {
1485
-            MsgToEventLog(M_SYSERR, L"%s: could not get SearchList from registry (%lu)",
1485
+            MsgToEventLog(M_SYSERR, L"%S: could not get SearchList from registry (%lu)",
1486 1486
                           __func__, err);
1487 1487
             return FALSE;
1488 1488
         }
... ...
@@ -1496,7 +1505,7 @@ AddDnsSearchDomains(HKEY key, BOOL have_list, PCWSTR domains)
1496 1496
         size_t domlen = wcslen(domains);
1497 1497
         if (listlen + domlen + 2 > _countof(list))
1498 1498
         {
1499
-            MsgToEventLog(M_SYSERR, L"%s: not enough space in list for search domains (len=%lu)",
1499
+            MsgToEventLog(M_SYSERR, L"%S: not enough space in list for search domains (len=%lu)",
1500 1500
                           __func__, domlen);
1501 1501
             return FALSE;
1502 1502
         }
... ...
@@ -1515,7 +1524,7 @@ AddDnsSearchDomains(HKEY key, BOOL have_list, PCWSTR domains)
1515 1515
     err = RegSetValueExW(key, L"SearchList", 0, REG_SZ, (PBYTE)list, size);
1516 1516
     if (err)
1517 1517
     {
1518
-        MsgToEventLog(M_SYSERR, L"%s: could not set SearchList to registry (%lu)",
1518
+        MsgToEventLog(M_SYSERR, L"%S: could not set SearchList to registry (%lu)",
1519 1519
                       __func__, err);
1520 1520
         return FALSE;
1521 1521
     }
... ...
@@ -1547,7 +1556,7 @@ ResetDnsSearchDomains(HKEY key)
1547 1547
     {
1548 1548
         if (err != ERROR_FILE_NOT_FOUND)
1549 1549
         {
1550
-            MsgToEventLog(M_SYSERR, L"%s: could not get InitialSearchList from registry (%lu)",
1550
+            MsgToEventLog(M_SYSERR, L"%S: could not get InitialSearchList from registry (%lu)",
1551 1551
                           __func__, err);
1552 1552
         }
1553 1553
         goto out;
... ...
@@ -1557,7 +1566,7 @@ ResetDnsSearchDomains(HKEY key)
1557 1557
     err = RegSetValueExW(key, L"SearchList", 0, REG_SZ, (PBYTE)list, size);
1558 1558
     if (err)
1559 1559
     {
1560
-        MsgToEventLog(M_SYSERR, L"%s: could not set SearchList in registry (%lu)",
1560
+        MsgToEventLog(M_SYSERR, L"%S: could not set SearchList in registry (%lu)",
1561 1561
                       __func__, err);
1562 1562
         goto out;
1563 1563
     }
... ...
@@ -1585,7 +1594,7 @@ RemoveDnsSearchDomains(HKEY key, PCWSTR domains)
1585 1585
     err = RegGetValueW(key, NULL, L"SearchList", RRF_RT_REG_SZ, NULL, list, &size);
1586 1586
     if (err)
1587 1587
     {
1588
-        MsgToEventLog(M_SYSERR, L"%s: could not get SearchList from registry (%lu)",
1588
+        MsgToEventLog(M_SYSERR, L"%S: could not get SearchList from registry (%lu)",
1589 1589
                       __func__, err);
1590 1590
         return;
1591 1591
     }
... ...
@@ -1593,7 +1602,7 @@ RemoveDnsSearchDomains(HKEY key, PCWSTR domains)
1593 1593
     PWSTR dst = wcsstr(list, domains);
1594 1594
     if (!dst)
1595 1595
     {
1596
-        MsgToEventLog(M_ERR, L"%s: could not find domains in search list", __func__);
1596
+        MsgToEventLog(M_ERR, L"%S: could not find domains in search list", __func__);
1597 1597
         return;
1598 1598
     }
1599 1599
 
... ...
@@ -1613,7 +1622,7 @@ RemoveDnsSearchDomains(HKEY key, PCWSTR domains)
1613 1613
         err = RegGetValueW(key, NULL, L"InitialSearchList", RRF_RT_REG_SZ, NULL, initial, &size);
1614 1614
         if (err)
1615 1615
         {
1616
-            MsgToEventLog(M_SYSERR, L"%s: could not get InitialSearchList from registry (%lu)",
1616
+            MsgToEventLog(M_SYSERR, L"%S: could not get InitialSearchList from registry (%lu)",
1617 1617
                           __func__, err);
1618 1618
             return;
1619 1619
         }
... ...
@@ -1630,7 +1639,7 @@ RemoveDnsSearchDomains(HKEY key, PCWSTR domains)
1630 1630
     err = RegSetValueExW(key, L"SearchList", 0, REG_SZ, (PBYTE)list, size);
1631 1631
     if (err)
1632 1632
     {
1633
-        MsgToEventLog(M_SYSERR, L"%s: could not set SearchList in registry (%lu)",
1633
+        MsgToEventLog(M_SYSERR, L"%S: could not set SearchList in registry (%lu)",
1634 1634
                       __func__, err);
1635 1635
     }
1636 1636
 }
... ...
@@ -1687,7 +1696,7 @@ SetDnsSearchDomains(PCSTR itf_name, PCSTR domains, PBOOL gpol, undo_lists_t *lis
1687 1687
     BOOL have_list = GetDnsSearchListKey(itf_name, gpol, &list_key);
1688 1688
     if (list_key == INVALID_HANDLE_VALUE)
1689 1689
     {
1690
-        MsgToEventLog(M_SYSERR, L"%s: could not get search list registry key", __func__);
1690
+        MsgToEventLog(M_SYSERR, L"%S: could not get search list registry key", __func__);
1691 1691
         return ERROR_FILE_NOT_FOUND;
1692 1692
     }
1693 1693
 
... ...
@@ -1756,7 +1765,7 @@ GetInterfacesKey(short family, PHKEY key)
1756 1756
     if (err)
1757 1757
     {
1758 1758
         *key = INVALID_HANDLE_VALUE;
1759
-        MsgToEventLog(M_SYSERR, L"%s: could not open interfaces registry key for family %d (%lu)",
1759
+        MsgToEventLog(M_SYSERR, L"%S: could not open interfaces registry key for family %d (%lu)",
1760 1760
                       __func__, family, err);
1761 1761
     }
1762 1762
 
... ...
@@ -1787,7 +1796,7 @@ SetNameServersValue(PCWSTR itf_id, short family, PCSTR value)
1787 1787
     err = RegOpenKeyExW(itfs, itf_id, 0, KEY_ALL_ACCESS, &itf);
1788 1788
     if (err)
1789 1789
     {
1790
-        MsgToEventLog(M_SYSERR, L"%s: could not open interface key for %s family %d (%lu)",
1790
+        MsgToEventLog(M_SYSERR, L"%S: could not open interface key for %s family %d (%lu)",
1791 1791
                       __func__, itf_id, family, err);
1792 1792
         goto out;
1793 1793
     }
... ...
@@ -1795,7 +1804,7 @@ SetNameServersValue(PCWSTR itf_id, short family, PCSTR value)
1795 1795
     err = RegSetValueExA(itf, "NameServer", 0, REG_SZ, (PBYTE)value, strlen(value) + 1);
1796 1796
     if (err)
1797 1797
     {
1798
-        MsgToEventLog(M_SYSERR, L"%s: could not set name servers '%S' for %s family %d (%lu)",
1798
+        MsgToEventLog(M_SYSERR, L"%S: could not set name servers '%S' for %s family %d (%lu)",
1799 1799
                       __func__, value, itf_id, family, err);
1800 1800
     }
1801 1801
 
... ...
@@ -1947,6 +1956,903 @@ HandleDNSConfigMessage(const dns_cfg_message_t *msg, undo_lists_t *lists)
1947 1947
     return err;
1948 1948
 }
1949 1949
 
1950
+/**
1951
+ * Checks if DHCP is enabled for an interface
1952
+ *
1953
+ * @param  key        HKEY of the interface to check for
1954
+ *
1955
+ * @return BOOL set to TRUE if DHCP is enabled, or FALSE if
1956
+ *         disabled or an error occurred
1957
+ */
1958
+static BOOL
1959
+IsDhcpEnabled(HKEY key)
1960
+{
1961
+    DWORD dhcp;
1962
+    DWORD size = sizeof(dhcp);
1963
+    LSTATUS err;
1964
+
1965
+    err = RegGetValueA(key, NULL, "EnableDHCP", RRF_RT_REG_DWORD, NULL, (PBYTE)&dhcp, &size);
1966
+    if (err != NO_ERROR)
1967
+    {
1968
+        MsgToEventLog(M_SYSERR, L"%S: Could not read DHCP status (%lu)", __func__, err);
1969
+        return FALSE;
1970
+    }
1971
+
1972
+    return dhcp ? TRUE : FALSE;
1973
+}
1974
+
1975
+/**
1976
+ * Set name servers from a NRPT address list
1977
+ *
1978
+ * @param itf_id        the VPN interface ID to set the name servers for
1979
+ * @param addresses     the list of NRPT addresses
1980
+ *
1981
+ * @return LSTATUS NO_ERROR in case of success, a Windows error code otherwise
1982
+ */
1983
+static LSTATUS
1984
+SetNameServerAddresses(PWSTR itf_id, const nrpt_address_t *addresses)
1985
+{
1986
+    const short families[] = { AF_INET, AF_INET6 };
1987
+    for (int i = 0; i < _countof(families); i++)
1988
+    {
1989
+        short family = families[i];
1990
+
1991
+        /* Create a comma sparated list of addresses of this family */
1992
+        int offset = 0;
1993
+        char addr_list[NRPT_ADDR_SIZE * NRPT_ADDR_NUM];
1994
+        for (int j = 0; j < NRPT_ADDR_NUM && addresses[j][0]; j++)
1995
+        {
1996
+            if ((family == AF_INET6 && strchr(addresses[j], ':') == NULL)
1997
+                || (family == AF_INET && strchr(addresses[j], ':') != NULL))
1998
+            {
1999
+                /* Address family doesn't match, skip this one */
2000
+                continue;
2001
+            }
2002
+            if (offset)
2003
+            {
2004
+                addr_list[offset++] = ',';
2005
+            }
2006
+            strcpy(addr_list + offset, addresses[j]);
2007
+            offset += strlen(addresses[j]);
2008
+        }
2009
+
2010
+        if (offset == 0)
2011
+        {
2012
+            /* No address for this family to set */
2013
+            continue;
2014
+        }
2015
+
2016
+        /* Set name server addresses */
2017
+        LSTATUS err = SetNameServers(itf_id, family, addr_list);
2018
+        if (err)
2019
+        {
2020
+            return err;
2021
+        }
2022
+    }
2023
+    return NO_ERROR;
2024
+}
2025
+
2026
+/**
2027
+ * Get DNS server IPv4 addresses of an interface
2028
+ *
2029
+ * @param  itf_key    registry key of the IPv4 interface data
2030
+ * @param  addrs      pointer to the buffer addresses are returned in
2031
+ * @param  size       pointer to the size of the buffer, contains the
2032
+ *                    size of the addresses on return
2033
+ *
2034
+ * @return LSTATUS NO_ERROR on success, a Windows error code otherwise
2035
+ */
2036
+static LSTATUS
2037
+GetItfDnsServersV4(HKEY itf_key, PSTR addrs, PDWORD size)
2038
+{
2039
+    addrs[*size - 1] = '\0';
2040
+
2041
+    LSTATUS err;
2042
+    DWORD s = *size;
2043
+    err = RegGetValueA(itf_key, NULL, "NameServer", RRF_RT_REG_SZ, NULL, (PBYTE)addrs, &s);
2044
+    if (err && err != ERROR_FILE_NOT_FOUND)
2045
+    {
2046
+        *size = 0;
2047
+        return err;
2048
+    }
2049
+
2050
+    /* Try DHCP addresses if we don't have some already */
2051
+    if (!strchr(addrs, '.') && IsDhcpEnabled(itf_key))
2052
+    {
2053
+        s = *size;
2054
+        RegGetValueA(itf_key, NULL, "DhcpNameServer", RRF_RT_REG_SZ, NULL, (PBYTE)addrs, &s);
2055
+        if (err)
2056
+        {
2057
+            *size = 0;
2058
+            return err;
2059
+        }
2060
+    }
2061
+
2062
+    if (strchr(addrs, '.'))
2063
+    {
2064
+        *size = s;
2065
+        return NO_ERROR;
2066
+    }
2067
+
2068
+    *size = 0;
2069
+    return ERROR_FILE_NOT_FOUND;
2070
+}
2071
+
2072
+/**
2073
+ * Get DNS server IPv6 addresses of an interface
2074
+ *
2075
+ * @param  itf_key    registry key of the IPv6 interface data
2076
+ * @param  addrs      pointer to the buffer addresses are returned in
2077
+ * @param  size       pointer to the size of the buffer
2078
+ *
2079
+ * @return LSTATUS NO_ERROR on success, a Windows error code otherwise
2080
+ */
2081
+static LSTATUS
2082
+GetItfDnsServersV6(HKEY itf_key, PSTR addrs, PDWORD size)
2083
+{
2084
+    addrs[*size - 1] = '\0';
2085
+
2086
+    LSTATUS err;
2087
+    DWORD s = *size;
2088
+    err = RegGetValueA(itf_key, NULL, "NameServer", RRF_RT_REG_SZ, NULL, (PBYTE)addrs, &s);
2089
+    if (err && err != ERROR_FILE_NOT_FOUND)
2090
+    {
2091
+        *size = 0;
2092
+        return err;
2093
+    }
2094
+
2095
+    /* Try DHCP addresses if we don't have some already */
2096
+    if (!strchr(addrs, ':') && IsDhcpEnabled(itf_key))
2097
+    {
2098
+        IN6_ADDR in_addrs[8];
2099
+        DWORD in_addrs_size = sizeof(in_addrs);
2100
+        err = RegGetValueA(itf_key, NULL, "Dhcpv6DNSServers", RRF_RT_REG_BINARY, NULL,
2101
+                           (PBYTE)in_addrs, &in_addrs_size);
2102
+        if (err)
2103
+        {
2104
+            *size = 0;
2105
+            return err;
2106
+        }
2107
+
2108
+        s = *size;
2109
+        PSTR pos = addrs;
2110
+        size_t in_addrs_read = in_addrs_size / sizeof(IN6_ADDR);
2111
+        for (size_t i = 0; i < in_addrs_read; ++i)
2112
+        {
2113
+            if (i != 0)
2114
+            {
2115
+                /* Add separator */
2116
+                *pos++ = ',';
2117
+                s--;
2118
+            }
2119
+
2120
+            if (inet_ntop(AF_INET6, &in_addrs[i],
2121
+                          pos, s) != NULL)
2122
+            {
2123
+                *size = 0;
2124
+                return ERROR_MORE_DATA;
2125
+            }
2126
+
2127
+            size_t addr_len = strlen(pos);
2128
+            pos += addr_len;
2129
+            s -= addr_len;
2130
+        }
2131
+        s = strlen(addrs) + 1;
2132
+    }
2133
+
2134
+    if (strchr(addrs, ':'))
2135
+    {
2136
+        *size = s;
2137
+        return NO_ERROR;
2138
+    }
2139
+
2140
+    *size = 0;
2141
+    return ERROR_FILE_NOT_FOUND;
2142
+}
2143
+
2144
+/**
2145
+ * Return interface specific domain suffix(es)
2146
+ *
2147
+ * The \p domains paramter will be set to a MULTI_SZ domains string.
2148
+ * In case of an error or if no domains are found for the interface
2149
+ * \p size is set to 0 and the contents of \p domains are invalid.
2150
+ * Note that the domains could have been set by DHCP or manually.
2151
+ *
2152
+ * @param  itf        HKEY of the interface to read from
2153
+ * @param  domains    PWSTR buffer to return the domain(s) in
2154
+ * @param  size       pointer to size of the domains buffer in bytes. Will be
2155
+ *                    set to the size of the string returned, including
2156
+ *                    the terminating zeros or 0.
2157
+ *
2158
+ * @return LSTATUS NO_ERROR if the domain suffix(es) were read successfully,
2159
+ *         ERROR_FILE_NOT_FOUND if no domain was found for the interface,
2160
+ *         ERROR_MORE_DATA if the list did not fit into the buffer,
2161
+ *         any other error indicates an error while reading from the registry.
2162
+ */
2163
+static LSTATUS
2164
+GetItfDnsDomains(HKEY itf, PWSTR domains, PDWORD size)
2165
+{
2166
+    if (domains == NULL || size == 0)
2167
+    {
2168
+        return ERROR_INVALID_PARAMETER;
2169
+    }
2170
+
2171
+    LSTATUS err = ERROR_FILE_NOT_FOUND;
2172
+    const DWORD buf_size = *size;
2173
+    const size_t one_glyph = sizeof(*domains);
2174
+    PWSTR values[] = { L"SearchList", L"Domain", L"DhcpDomainSearchList", L"DhcpDomain", NULL};
2175
+
2176
+    for (int i = 0; values[i]; i++)
2177
+    {
2178
+        *size = buf_size;
2179
+        err = RegGetValueW(itf, NULL, values[i], RRF_RT_REG_SZ, NULL, (PBYTE)domains, size);
2180
+        if (!err && *size > one_glyph && wcschr(domains, '.'))
2181
+        {
2182
+            /*
2183
+             * Found domain(s), now convert them:
2184
+             *   - prefix each domain with a dot
2185
+             *   - convert comma separated list to MULTI_SZ
2186
+             */
2187
+            PWCHAR pos = domains;
2188
+            const DWORD buf_len = buf_size / one_glyph;
2189
+            while (TRUE)
2190
+            {
2191
+                /* Terminate the domain at the next comma */
2192
+                PWCHAR comma = wcschr(pos, ',');
2193
+                if (comma)
2194
+                {
2195
+                    *comma = '\0';
2196
+                }
2197
+
2198
+                /* Check for enough space to convert this domain */
2199
+                size_t converted_size = pos - domains;
2200
+                size_t domain_len = wcslen(pos) + 1;
2201
+                size_t domain_size = domain_len * one_glyph;
2202
+                size_t extra_size = 2 * one_glyph;
2203
+                if (converted_size + domain_size + extra_size > buf_size)
2204
+                {
2205
+                    /* Domain doesn't fit, bad luck if it's the first one */
2206
+                    *pos = '\0';
2207
+                    *size = converted_size == 0 ? 0 : *size + 1;
2208
+                    return ERROR_MORE_DATA;
2209
+                }
2210
+
2211
+                /* Prefix domain at pos with the dot */
2212
+                memmove(pos + 1, pos, buf_size - converted_size - one_glyph);
2213
+                domains[buf_len - 1] = '\0';
2214
+                *pos = '.';
2215
+                *size += 1;
2216
+
2217
+                if (!comma)
2218
+                {
2219
+                    /* Conversion is done */
2220
+                    *(pos + domain_len) = '\0';
2221
+                    *size += 1;
2222
+                    return NO_ERROR;
2223
+                }
2224
+
2225
+                pos = comma + 1;
2226
+            }
2227
+        }
2228
+    }
2229
+
2230
+    *size = 0;
2231
+    return err;
2232
+}
2233
+
2234
+/**
2235
+ * Check if an interface is connected and up
2236
+ *
2237
+ * @param  iid_str    the interface GUID as string
2238
+ *
2239
+ * @return TRUE if the interface is connected and up, FALSE otherwise or in
2240
+ *         case an error happened
2241
+ */
2242
+static BOOL
2243
+IsInterfaceConnected(PWSTR iid_str)
2244
+{
2245
+    GUID iid;
2246
+    BOOL res = FALSE;
2247
+    MIB_IF_ROW2 itf_row;
2248
+
2249
+    /* Get GUID from string */
2250
+    if (IIDFromString(iid_str, &iid) != S_OK)
2251
+    {
2252
+        MsgToEventLog(M_SYSERR, L"%S: could not convert interface %s GUID string", __func__, iid_str);
2253
+        goto out;
2254
+    }
2255
+
2256
+    /* Get LUID from GUID */
2257
+    if (ConvertInterfaceGuidToLuid(&iid, &itf_row.InterfaceLuid) != NO_ERROR)
2258
+    {
2259
+        goto out;
2260
+    }
2261
+
2262
+    /* Look up interface status */
2263
+    if (GetIfEntry2(&itf_row) != NO_ERROR)
2264
+    {
2265
+        MsgToEventLog(M_SYSERR, L"%S: could not get interface %s status", __func__, iid_str);
2266
+        goto out;
2267
+    }
2268
+
2269
+    if (itf_row.MediaConnectState == MediaConnectStateConnected
2270
+        && itf_row.OperStatus == IfOperStatusUp)
2271
+    {
2272
+        res = TRUE;
2273
+    }
2274
+
2275
+out:
2276
+    return res;
2277
+}
2278
+
2279
+/**
2280
+ * Collect interface DNS settings to be used in excluding NRPT rules. This is
2281
+ * needed so that local DNS keeps working even when a catch all NRPT rule is
2282
+ * installed by a VPN connection.
2283
+ *
2284
+ * @param  data       pointer to the data structures the values are returned in
2285
+ * @param  data_size  number of exclude data structures pointed to
2286
+ */
2287
+static void
2288
+GetNrptExcludeData(nrpt_exclude_data_t *data, size_t data_size)
2289
+{
2290
+    HKEY v4_itfs = INVALID_HANDLE_VALUE;
2291
+    HKEY v6_itfs = INVALID_HANDLE_VALUE;
2292
+
2293
+    if (!GetInterfacesKey(AF_INET, &v4_itfs)
2294
+        || !GetInterfacesKey(AF_INET6, &v6_itfs))
2295
+    {
2296
+        goto out;
2297
+    }
2298
+
2299
+    size_t i = 0;
2300
+    DWORD enum_index = 0;
2301
+    while (i < data_size)
2302
+    {
2303
+        WCHAR itf_guid[MAX_PATH];
2304
+        DWORD itf_guid_len = _countof(itf_guid);
2305
+        LSTATUS err = RegEnumKeyExW(v4_itfs, enum_index++, itf_guid, &itf_guid_len,
2306
+                                    NULL, NULL, NULL, NULL);
2307
+        if (err)
2308
+        {
2309
+            if (err != ERROR_NO_MORE_ITEMS)
2310
+            {
2311
+                MsgToEventLog(M_SYSERR, L"%S: could not enumerate interfaces (%lu)", __func__, err);
2312
+            }
2313
+            goto out;
2314
+        }
2315
+
2316
+        /* Ignore interfaces that are not connected or disabled */
2317
+        if (!IsInterfaceConnected(itf_guid))
2318
+        {
2319
+            continue;
2320
+        }
2321
+
2322
+        HKEY v4_itf;
2323
+        if (RegOpenKeyExW(v4_itfs, itf_guid, 0, KEY_READ, &v4_itf) != NO_ERROR)
2324
+        {
2325
+            MsgToEventLog(M_SYSERR, L"%S: could not open interface %s v4 registry key", __func__, itf_guid);
2326
+            goto out;
2327
+        }
2328
+
2329
+        /* Get the DNS domain(s) for exclude routing */
2330
+        data[i].domains_size = sizeof(data[0].domains);
2331
+        memset(data[i].domains, 0, data[i].domains_size);
2332
+        err = GetItfDnsDomains(v4_itf, data[i].domains, &data[i].domains_size);
2333
+        if (err)
2334
+        {
2335
+            if (err != ERROR_FILE_NOT_FOUND)
2336
+            {
2337
+                MsgToEventLog(M_SYSERR, L"%S: could not read interface %s domain suffix", __func__, itf_guid);
2338
+            }
2339
+            goto next_itf;
2340
+        }
2341
+
2342
+        /* Get the IPv4 DNS servers */
2343
+        DWORD v4_addrs_size = sizeof(data[0].addresses);
2344
+        err = GetItfDnsServersV4(v4_itf, data[i].addresses, &v4_addrs_size);
2345
+        if (err && err != ERROR_FILE_NOT_FOUND)
2346
+        {
2347
+            MsgToEventLog(M_SYSERR, L"%S: could not read interface %s v4 name servers (%ld)",
2348
+                          __func__, itf_guid, err);
2349
+            goto next_itf;
2350
+        }
2351
+
2352
+        /* Get the IPv6 DNS servers, if there's space left */
2353
+        PSTR v6_addrs = data[i].addresses + v4_addrs_size;
2354
+        DWORD v6_addrs_size = sizeof(data[0].addresses) - v4_addrs_size;
2355
+        if (v6_addrs_size > NRPT_ADDR_SIZE)
2356
+        {
2357
+            HKEY v6_itf;
2358
+            if (RegOpenKeyExW(v6_itfs, itf_guid, 0, KEY_READ, &v6_itf) != NO_ERROR)
2359
+            {
2360
+                MsgToEventLog(M_SYSERR, L"%S: could not open interface %s v6 registry key", __func__, itf_guid);
2361
+                goto next_itf;
2362
+            }
2363
+            err = GetItfDnsServersV6(v6_itf, v6_addrs, &v6_addrs_size);
2364
+            RegCloseKey(v6_itf);
2365
+            if (err && err != ERROR_FILE_NOT_FOUND)
2366
+            {
2367
+                MsgToEventLog(M_SYSERR, L"%S: could not read interface %s v6 name servers (%ld)",
2368
+                              __func__, itf_guid, err);
2369
+                goto next_itf;
2370
+            }
2371
+        }
2372
+
2373
+        if (v4_addrs_size || v6_addrs_size)
2374
+        {
2375
+            /* Replace comma-delimters with semicolons, as required by NRPT */
2376
+            for (int j = 0; j < sizeof(data[0].addresses) && data[i].addresses[j]; j++)
2377
+            {
2378
+                if (data[i].addresses[j] == ',')
2379
+                {
2380
+                    data[i].addresses[j] = ';';
2381
+                }
2382
+            }
2383
+            ++i;
2384
+        }
2385
+
2386
+next_itf:
2387
+        RegCloseKey(v4_itf);
2388
+    }
2389
+
2390
+out:
2391
+    RegCloseKey(v6_itfs);
2392
+    RegCloseKey(v4_itfs);
2393
+}
2394
+
2395
+/**
2396
+ * Set a NRPT rule (subkey) and its values in the registry
2397
+ *
2398
+ * @param  nrpt_key   NRPT registry key handle
2399
+ * @param  subkey     subkey string to create
2400
+ * @param  address    name server address string
2401
+ * @param  domains    domains to resolve by this server as MULTI_SZ
2402
+ * @param  dom_size   size of domains in bytes including the terminators
2403
+ * @param  dnssec     boolean to determine if DNSSEC is to be enabled
2404
+ *
2405
+ * @return NO_ERROR on success, or Windows error code
2406
+ */
2407
+static DWORD
2408
+SetNrptRule(HKEY nrpt_key, PCWSTR subkey, PCSTR address,
2409
+            PCWSTR domains, DWORD dom_size, BOOL dnssec)
2410
+{
2411
+    /* Create rule subkey */
2412
+    DWORD err = NO_ERROR;
2413
+    HKEY rule_key;
2414
+    err = RegCreateKeyExW(nrpt_key, subkey, 0, NULL, 0, KEY_ALL_ACCESS, NULL, &rule_key, NULL);
2415
+    if (err)
2416
+    {
2417
+        return err;
2418
+    }
2419
+
2420
+    /* Set name(s) for DNS routing */
2421
+    err = RegSetValueExW(rule_key, L"Name", 0, REG_MULTI_SZ, (PBYTE)domains, dom_size);
2422
+    if (err)
2423
+    {
2424
+        goto out;
2425
+    }
2426
+
2427
+    /* Set DNS Server address */
2428
+    err = RegSetValueExA(rule_key, "GenericDNSServers", 0, REG_SZ, (PBYTE)address, strlen(address) + 1);
2429
+    if (err)
2430
+    {
2431
+        goto out;
2432
+    }
2433
+
2434
+    DWORD reg_val;
2435
+    /* Set DNSSEC if required */
2436
+    if (dnssec)
2437
+    {
2438
+        reg_val = 1;
2439
+        err = RegSetValueExA(rule_key, "DNSSECValidationRequired", 0, REG_DWORD, (PBYTE)&reg_val, sizeof(reg_val));
2440
+        if (err)
2441
+        {
2442
+            goto out;
2443
+        }
2444
+
2445
+        reg_val = 0;
2446
+        err = RegSetValueExA(rule_key, "DNSSECQueryIPSECRequired", 0, REG_DWORD, (PBYTE)&reg_val, sizeof(reg_val));
2447
+        if (err)
2448
+        {
2449
+            goto out;
2450
+        }
2451
+
2452
+        reg_val = 0;
2453
+        err = RegSetValueExA(rule_key, "DNSSECQueryIPSECEncryption", 0, REG_DWORD, (PBYTE)&reg_val, sizeof(reg_val));
2454
+        if (err)
2455
+        {
2456
+            goto out;
2457
+        }
2458
+    }
2459
+
2460
+    /* Set NRPT config options */
2461
+    reg_val = dnssec ? 0x0000000A : 0x00000008;
2462
+    err = RegSetValueExA(rule_key, "ConfigOptions", 0, REG_DWORD, (const PBYTE)&reg_val, sizeof(reg_val));
2463
+    if (err)
2464
+    {
2465
+        goto out;
2466
+    }
2467
+
2468
+    /* Mandatory NRPT version */
2469
+    reg_val = 2;
2470
+    err = RegSetValueExA(rule_key, "Version", 0, REG_DWORD, (const PBYTE)&reg_val, sizeof(reg_val));
2471
+    if (err)
2472
+    {
2473
+        goto out;
2474
+    }
2475
+
2476
+out:
2477
+    if (err)
2478
+    {
2479
+        RegDeleteKeyW(nrpt_key, subkey);
2480
+    }
2481
+    RegCloseKey(rule_key);
2482
+    return err;
2483
+}
2484
+
2485
+/**
2486
+ * Set NRPT exclude rules to accompany a catch all rule. This is done so that
2487
+ * local resolution of names is not interfered with in case the VPN resolves
2488
+ * all names.
2489
+ *
2490
+ * @param  nrpt_key   the registry key to set the rules under
2491
+ * @param  ovpn_pid   the PID of the openvpn process
2492
+ */
2493
+static void
2494
+SetNrptExcludeRules(HKEY nrpt_key, DWORD ovpn_pid)
2495
+{
2496
+    nrpt_exclude_data_t data[8]; /* data from up to 8 interfaces */
2497
+    memset(data, 0, sizeof(data));
2498
+    GetNrptExcludeData(data, _countof(data));
2499
+
2500
+    unsigned n = 0;
2501
+    for (int i = 0; i < _countof(data); ++i)
2502
+    {
2503
+        nrpt_exclude_data_t *d = &data[i];
2504
+        if (d->domains_size == 0)
2505
+        {
2506
+            break;
2507
+        }
2508
+
2509
+        DWORD err;
2510
+        WCHAR subkey[48];
2511
+        swprintf(subkey, _countof(subkey), L"OpenVPNDNSRoutingX-%02x-%lu", ++n, ovpn_pid);
2512
+        err = SetNrptRule(nrpt_key, subkey, d->addresses, d->domains, d->domains_size, FALSE);
2513
+        if (err)
2514
+        {
2515
+            MsgToEventLog(M_ERR, L"%S: failed to set rule %s (%lu)", __func__, subkey, err);
2516
+        }
2517
+    }
2518
+}
2519
+
2520
+/**
2521
+ * Set NRPT rules for a openvpn process
2522
+ *
2523
+ * @param  nrpt_key   the registry key to set the rules under
2524
+ * @param  addresses  name server addresses
2525
+ * @param  domains    optional list of split routing domains
2526
+ * @param  dnssec     boolean whether DNSSEC is to be used
2527
+ * @param  ovpn_pid   the PID of the openvpn process
2528
+ *
2529
+ * @return NO_ERROR on success, or a Windows error code
2530
+ */
2531
+static DWORD
2532
+SetNrptRules(HKEY nrpt_key, const nrpt_address_t *addresses,
2533
+             const char *domains, BOOL dnssec, DWORD ovpn_pid)
2534
+{
2535
+    DWORD err = NO_ERROR;
2536
+    PWSTR wide_domains = L".\0"; /* DNS route everything by default */
2537
+    DWORD dom_size = 6;
2538
+
2539
+    /* Prepare DNS routing domains / split DNS */
2540
+    if (domains[0])
2541
+    {
2542
+        size_t domains_len = strlen(domains);
2543
+        dom_size = domains_len + 2; /* len + the trailing NULs */
2544
+
2545
+        wide_domains = utf8to16_size(domains, dom_size);
2546
+        dom_size *= sizeof(*wide_domains);
2547
+        if (!wide_domains)
2548
+        {
2549
+            return ERROR_OUTOFMEMORY;
2550
+        }
2551
+        /* Make a MULTI_SZ from a comma separated list */
2552
+        for (size_t i = 0; i < domains_len; ++i)
2553
+        {
2554
+            if (wide_domains[i] == ',')
2555
+            {
2556
+                wide_domains[i] = 0;
2557
+            }
2558
+        }
2559
+    }
2560
+    else
2561
+    {
2562
+        SetNrptExcludeRules(nrpt_key, ovpn_pid);
2563
+    }
2564
+
2565
+    /* Create address string list */
2566
+    CHAR addr_list[NRPT_ADDR_NUM * NRPT_ADDR_SIZE];
2567
+    PSTR pos = addr_list;
2568
+    for (int i = 0; i < NRPT_ADDR_NUM && addresses[i][0]; ++i)
2569
+    {
2570
+        if (i != 0)
2571
+        {
2572
+            *pos++ = ';';
2573
+        }
2574
+        strcpy(pos, addresses[i]);
2575
+        pos += strlen(pos);
2576
+    }
2577
+
2578
+    WCHAR subkey[MAX_PATH];
2579
+    swprintf(subkey, _countof(subkey), L"OpenVPNDNSRouting-%lu", ovpn_pid);
2580
+    err = SetNrptRule(nrpt_key, subkey, addr_list, wide_domains, dom_size, dnssec);
2581
+    if (err)
2582
+    {
2583
+        MsgToEventLog(M_ERR, L"%S: failed to set rule %s (%lu)", __func__, subkey, err);
2584
+    }
2585
+
2586
+    if (domains[0])
2587
+    {
2588
+        free(wide_domains);
2589
+    }
2590
+    return err;
2591
+}
2592
+
2593
+/**
2594
+ * Return the registry key where NRPT rules are stored
2595
+ *
2596
+ * @param  key        pointer to the HKEY it is returned in
2597
+ * @param  gpol       pointer to BOOL the use of GPOL hive is returned in
2598
+ *
2599
+ * @return NO_ERROR on success, or a Windows error code
2600
+ */
2601
+static LSTATUS
2602
+OpenNrptBaseKey(PHKEY key, PBOOL gpol)
2603
+{
2604
+    /*
2605
+     * Registry keys Name Service Policy Table (NRPT) rules can be stored at.
2606
+     * When the group policy key exists, NRPT rules must be placed there.
2607
+     * It is created when NRPT rules are pushed via group policy and it
2608
+     * remains in the registry even if the last GP-NRPT rule is deleted.
2609
+     */
2610
+    static PCSTR gpol_key = "SOFTWARE\\Policies\\Microsoft\\Windows NT\\DNSClient\\DnsPolicyConfig";
2611
+    static PCSTR sys_key = "SYSTEM\\CurrentControlSet\\Services\\Dnscache\\Parameters\\DnsPolicyConfig";
2612
+
2613
+    HKEY nrpt;
2614
+    *gpol = TRUE;
2615
+    LSTATUS err = RegOpenKeyExA(HKEY_LOCAL_MACHINE, gpol_key, 0, KEY_ALL_ACCESS, &nrpt);
2616
+    if (err == ERROR_FILE_NOT_FOUND)
2617
+    {
2618
+        *gpol = FALSE;
2619
+        err = RegOpenKeyExA(HKEY_LOCAL_MACHINE, sys_key, 0, KEY_ALL_ACCESS, &nrpt);
2620
+        if (err)
2621
+        {
2622
+            nrpt = INVALID_HANDLE_VALUE;
2623
+        }
2624
+    }
2625
+    *key = nrpt;
2626
+    return err;
2627
+}
2628
+
2629
+/**
2630
+ * Delete OpenVPN NRPT rules from the registry
2631
+ *
2632
+ * If the pid parameter is 0 all NRPT rules added by OpenVPN are deleted.
2633
+ * In all other cases only rules matching the pid are deleted.
2634
+ *
2635
+ * @param  pid        PID of the process to delete the rules for or 0
2636
+ * @param  gpol
2637
+ *
2638
+ * @return BOOL to indicate if rules were deleted
2639
+ */
2640
+static BOOL
2641
+DeleteNrptRules(DWORD pid, PBOOL gpol)
2642
+{
2643
+    HKEY key;
2644
+    LSTATUS err = OpenNrptBaseKey(&key, gpol);
2645
+    if (err)
2646
+    {
2647
+        MsgToEventLog(M_SYSERR, L"%S: could not open NRPT base key (%lu)", __func__, err);
2648
+        return FALSE;
2649
+    }
2650
+
2651
+    /* PID suffix string to compare against later */
2652
+    WCHAR pid_str[16];
2653
+    size_t pidlen = 0;
2654
+    if (pid)
2655
+    {
2656
+        swprintf(pid_str, _countof(pid_str), L"-%lu", pid);
2657
+        pidlen = wcslen(pid_str);
2658
+    }
2659
+
2660
+    int deleted = 0;
2661
+    DWORD enum_index = 0;
2662
+    while (TRUE)
2663
+    {
2664
+        WCHAR name[MAX_PATH];
2665
+        DWORD namelen = _countof(name);
2666
+        err = RegEnumKeyExW(key, enum_index++, name, &namelen, NULL, NULL, NULL, NULL);
2667
+        if (err)
2668
+        {
2669
+            if (err != ERROR_NO_MORE_ITEMS)
2670
+            {
2671
+                MsgToEventLog(M_SYSERR, L"%S: could not enumerate NRPT rules (%lu)", __func__, err);
2672
+            }
2673
+            break;
2674
+        }
2675
+
2676
+        /* Keep rule if name doesn't match */
2677
+        if (wcsncmp(name, L"OpenVPNDNSRouting", 17) != 0
2678
+            || (pid && wcsncmp(name + namelen - pidlen, pid_str, pidlen) != 0))
2679
+        {
2680
+            continue;
2681
+        }
2682
+
2683
+        if (RegDeleteKeyW(key, name) == NO_ERROR)
2684
+        {
2685
+            enum_index--;
2686
+            deleted++;
2687
+        }
2688
+    }
2689
+
2690
+    RegCloseKey(key);
2691
+    return deleted ? TRUE : FALSE;
2692
+}
2693
+
2694
+/**
2695
+ * Delete a process' NRPT rules and apply the reduced set of rules
2696
+ *
2697
+ * @param ovpn_pid  OpenVPN process id to delete rules for
2698
+ */
2699
+static void
2700
+UndoNrptRules(DWORD ovpn_pid)
2701
+{
2702
+    BOOL gpol;
2703
+    if (DeleteNrptRules(ovpn_pid, &gpol))
2704
+    {
2705
+        ApplyDnsSettings(gpol);
2706
+    }
2707
+}
2708
+
2709
+/**
2710
+ * Add Name Resolution Policy Table (NRPT) rules as documented in
2711
+ * https://msdn.microsoft.com/en-us/library/ff957356.aspx for DNS name
2712
+ * resolution, as well as DNS search domain(s), if given.
2713
+ *
2714
+ * @param  msg        config messages sent by the openvpn process
2715
+ * @param  ovpn_pid   process id of the sending openvpn process
2716
+ * @param  lists      undo lists for this process
2717
+ *
2718
+ * @return NO_ERROR on success, or a Windows error code
2719
+ */
2720
+static DWORD
2721
+HandleDNSConfigNrptMessage(const nrpt_dns_cfg_message_t *msg,
2722
+                           DWORD ovpn_pid, undo_lists_t *lists)
2723
+{
2724
+    /*
2725
+     * Use a non-const reference with limited scope to
2726
+     * enforce null-termination of strings from client
2727
+     */
2728
+    {
2729
+        nrpt_dns_cfg_message_t *msgptr = (nrpt_dns_cfg_message_t *) msg;
2730
+        msgptr->iface.name[_countof(msg->iface.name) - 1] = '\0';
2731
+        msgptr->search_domains[_countof(msg->search_domains) - 1] = '\0';
2732
+        msgptr->resolve_domains[_countof(msg->resolve_domains) - 1] = '\0';
2733
+        for (size_t i = 0; i < NRPT_ADDR_NUM; ++i)
2734
+        {
2735
+            msgptr->addresses[i][_countof(msg->addresses[0]) - 1] = '\0';
2736
+        }
2737
+    }
2738
+
2739
+    /* Make sure we have the VPN interface name */
2740
+    if (msg->iface.name[0] == 0)
2741
+    {
2742
+        return ERROR_MESSAGE_DATA;
2743
+    }
2744
+
2745
+    /* Some sanity checks on the add message data */
2746
+    if (msg->header.type == msg_add_nrpt_cfg)
2747
+    {
2748
+        /* At least one name server address is set */
2749
+        if (msg->addresses[0][0] == 0)
2750
+        {
2751
+            return ERROR_MESSAGE_DATA;
2752
+        }
2753
+        /* Resolve domains are double zero terminated (MULTI_SZ) */
2754
+        const char *rdom = msg->resolve_domains;
2755
+        size_t rdom_size = sizeof(msg->resolve_domains);
2756
+        size_t rdom_len = strlen(rdom);
2757
+        if (rdom_len && (rdom_len + 1 >= rdom_size || rdom[rdom_len + 2] != 0))
2758
+        {
2759
+            return ERROR_MESSAGE_DATA;
2760
+        }
2761
+    }
2762
+
2763
+    BOOL gpol_nrpt = FALSE;
2764
+    BOOL gpol_list = FALSE;
2765
+
2766
+    WCHAR iid[64];
2767
+    DWORD iid_err = InterfaceIdString(msg->iface.name, iid, _countof(iid));
2768
+    if (iid_err)
2769
+    {
2770
+        return iid_err;
2771
+    }
2772
+
2773
+    /* Delete previously set values for this instance first, if any */
2774
+    PDWORD undo_pid = RemoveListItem(&(*lists)[undo_nrpt], CmpAny, NULL);
2775
+    if (undo_pid)
2776
+    {
2777
+        if (*undo_pid != ovpn_pid)
2778
+        {
2779
+            MsgToEventLog(M_INFO,
2780
+                          L"%S: PID stored for undo doesn't match: %lu vs %lu. "
2781
+                          "This is likely an error. Cleaning up anyway.",
2782
+                          __func__, *undo_pid, ovpn_pid);
2783
+        }
2784
+        DeleteNrptRules(*undo_pid, &gpol_nrpt);
2785
+        free(undo_pid);
2786
+
2787
+        ResetNameServers(iid, AF_INET);
2788
+        ResetNameServers(iid, AF_INET6);
2789
+    }
2790
+    SetDnsSearchDomains(msg->iface.name, NULL, &gpol_list, lists);
2791
+
2792
+    if (msg->header.type == msg_del_nrpt_cfg)
2793
+    {
2794
+        ApplyDnsSettings(gpol_nrpt || gpol_list);
2795
+        return NO_ERROR; /* Done dealing with del message */
2796
+    }
2797
+
2798
+    HKEY key;
2799
+    LSTATUS err = OpenNrptBaseKey(&key, &gpol_nrpt);
2800
+    if (err)
2801
+    {
2802
+        goto out;
2803
+    }
2804
+
2805
+    /* Add undo information first in case there's no heap left */
2806
+    PDWORD pid = malloc(sizeof(ovpn_pid));
2807
+    if (!pid)
2808
+    {
2809
+        err = ERROR_OUTOFMEMORY;
2810
+        goto out;
2811
+    }
2812
+    *pid = ovpn_pid;
2813
+    if (AddListItem(&(*lists)[undo_nrpt], pid))
2814
+    {
2815
+        err = ERROR_OUTOFMEMORY;
2816
+        free(pid);
2817
+        goto out;
2818
+    }
2819
+
2820
+    /* Set NRPT rules */
2821
+    BOOL dnssec = (msg->flags & nrpt_dnssec) != 0;
2822
+    err = SetNrptRules(key, msg->addresses, msg->resolve_domains, dnssec, ovpn_pid);
2823
+    if (err)
2824
+    {
2825
+        goto out;
2826
+    }
2827
+
2828
+    /* Set name servers */
2829
+    err = SetNameServerAddresses(iid, msg->addresses);
2830
+    if (err)
2831
+    {
2832
+        goto out;
2833
+    }
2834
+
2835
+    /* Set search domains, if any */
2836
+    if (msg->search_domains[0])
2837
+    {
2838
+        err = SetDnsSearchDomains(msg->iface.name, msg->search_domains, &gpol_list, lists);
2839
+    }
2840
+
2841
+    ApplyDnsSettings(gpol_nrpt || gpol_list);
2842
+
2843
+out:
2844
+    return err;
2845
+}
2846
+
1950 2847
 static DWORD
1951 2848
 HandleWINSConfigMessage(const wins_cfg_message_t *msg, undo_lists_t *lists)
1952 2849
 {
... ...
@@ -2202,7 +3108,7 @@ HandleMTUMessage(const set_mtu_message_t *mtu)
2202 2202
 }
2203 2203
 
2204 2204
 static VOID
2205
-HandleMessage(HANDLE pipe, HANDLE ovpn_proc,
2205
+HandleMessage(HANDLE pipe, PPROCESS_INFORMATION proc_info,
2206 2206
               DWORD bytes, DWORD count, LPHANDLE events, undo_lists_t *lists)
2207 2207
 {
2208 2208
     pipe_message_t msg;
... ...
@@ -2265,6 +3171,14 @@ HandleMessage(HANDLE pipe, HANDLE ovpn_proc,
2265 2265
             ack.error_number = HandleDNSConfigMessage(&msg.dns, lists);
2266 2266
             break;
2267 2267
 
2268
+        case msg_add_nrpt_cfg:
2269
+        case msg_del_nrpt_cfg:
2270
+        {
2271
+            DWORD ovpn_pid = proc_info->dwProcessId;
2272
+            ack.error_number = HandleDNSConfigNrptMessage(&msg.nrpt_dns, ovpn_pid, lists);
2273
+        }
2274
+        break;
2275
+
2268 2276
         case msg_add_wins_cfg:
2269 2277
         case msg_del_wins_cfg:
2270 2278
             ack.error_number = HandleWINSConfigMessage(&msg.wins, lists);
... ...
@@ -2280,7 +3194,8 @@ HandleMessage(HANDLE pipe, HANDLE ovpn_proc,
2280 2280
         case msg_register_ring_buffers:
2281 2281
             if (msg.header.size == sizeof(msg.rrb))
2282 2282
             {
2283
-                ack.error_number = HandleRegisterRingBuffers(&msg.rrb, ovpn_proc, lists);
2283
+                HANDLE ovpn_hnd = proc_info->hProcess;
2284
+                ack.error_number = HandleRegisterRingBuffers(&msg.rrb, ovpn_hnd, lists);
2284 2285
             }
2285 2286
             break;
2286 2287
 
... ...
@@ -2331,6 +3246,10 @@ Undo(undo_lists_t *lists)
2331 2331
                     ResetNameServers(item->data, AF_INET6);
2332 2332
                     break;
2333 2333
 
2334
+                case undo_nrpt:
2335
+                    UndoNrptRules(*(PDWORD)item->data);
2336
+                    break;
2337
+
2334 2338
                 case undo_domains:
2335 2339
                     UndoDnsSearchDomains(item->data);
2336 2340
                     break;
... ...
@@ -2652,7 +3571,7 @@ RunOpenvpn(LPVOID p)
2652 2652
             break;
2653 2653
         }
2654 2654
 
2655
-        HandleMessage(ovpn_pipe, proc_info.hProcess, bytes, 1, &exit_event, &undo_lists);
2655
+        HandleMessage(ovpn_pipe, &proc_info, bytes, 1, &exit_event, &undo_lists);
2656 2656
     }
2657 2657
 
2658 2658
     WaitForSingleObject(proc_info.hProcess, IO_TIMEOUT);
... ...
@@ -2848,24 +3767,28 @@ ServiceStartInteractiveOwn(DWORD dwArgc, LPWSTR *lpszArgv)
2848 2848
 static void
2849 2849
 CleanupRegistry(void)
2850 2850
 {
2851
-    HKEY key;
2852
-    DWORD changed = 0;
2851
+    BOOL changed = FALSE;
2852
+
2853
+    /* Clean up leftover NRPT rules */
2854
+    BOOL gpol_nrpt;
2855
+    changed = DeleteNrptRules(0, &gpol_nrpt);
2853 2856
 
2854 2857
     /* Clean up leftover DNS search list fragments */
2858
+    HKEY key;
2855 2859
     BOOL gpol_list;
2856 2860
     GetDnsSearchListKey(NULL, &gpol_list, &key);
2857 2861
     if (key != INVALID_HANDLE_VALUE)
2858 2862
     {
2859 2863
         if (ResetDnsSearchDomains(key))
2860 2864
         {
2861
-            changed++;
2865
+            changed = TRUE;
2862 2866
         }
2863 2867
         RegCloseKey(key);
2864 2868
     }
2865 2869
 
2866 2870
     if (changed)
2867 2871
     {
2868
-        ApplyDnsSettings(gpol_list);
2872
+        ApplyDnsSettings(gpol_nrpt || gpol_list);
2869 2873
     }
2870 2874
 }
2871 2875