Browse code

win: match search domains when creating exclude rules

Compare local domains for exclude rules to search domains and skip
matching ones. This prevents the creation of exclude rules when the
server indicates that the domain should be resolved via the VPN, by
pushing the search domain.

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

Heiko Hund authored on 2025/05/20 19:51:12
Showing 1 changed files
... ...
@@ -2126,18 +2126,52 @@ GetItfDnsServersV6(HKEY itf_key, PSTR addrs, PDWORD size)
2126 2126
 }
2127 2127
 
2128 2128
 /**
2129
+ * Check if a domain is contained in a comma separated list of domains
2130
+ *
2131
+ * @param list      Comma separated list of domains
2132
+ * @param domain    Domain string to search for
2133
+ * @param len       Length of the domain string, excluding the '\0'
2134
+ *
2135
+ * @return TRUE when the domain was found in the list, FALSE otherwise.
2136
+ */
2137
+static BOOL
2138
+ListContainsDomain(PCWSTR list, PCWSTR domain, size_t len)
2139
+{
2140
+    PCWSTR match = list;
2141
+    while (TRUE)
2142
+    {
2143
+        match = wcsstr(match, domain);
2144
+        if (!match)
2145
+        {
2146
+            /* Domain has not matched */
2147
+            break;
2148
+        }
2149
+        if ((match == list || *(match - 1) == ',')
2150
+            && (*(match + len) == ',' || *(match + len) == '\0'))
2151
+        {
2152
+            /* Domain has matched fully */
2153
+            return TRUE;
2154
+        }
2155
+        match += len;
2156
+    }
2157
+    return FALSE;
2158
+}
2159
+
2160
+/**
2129 2161
  * Return interface specific domain suffix(es)
2130 2162
  *
2131 2163
  * The \p domains paramter will be set to a MULTI_SZ domains string.
2132 2164
  * In case of an error or if no domains are found for the interface
2133 2165
  * \p size is set to 0 and the contents of \p domains are invalid.
2134 2166
  * Note that the domains could have been set by DHCP or manually.
2167
+ * Note that domains are ignored if they match a pushed search domain.
2135 2168
  *
2136
- * @param  itf        HKEY of the interface to read from
2137
- * @param  domains    PWSTR buffer to return the domain(s) in
2138
- * @param  size       pointer to size of the domains buffer in bytes. Will be
2139
- *                    set to the size of the string returned, including
2140
- *                    the terminating zeros or 0.
2169
+ * @param  itf             HKEY of the interface to read from
2170
+ * @param  search_domains  optional list of search domains
2171
+ * @param  domains         PWSTR buffer to return the domain(s) in
2172
+ * @param  size            pointer to size of the domains buffer in bytes. Will be
2173
+ *                         set to the size of the string returned, including
2174
+ *                         the terminating zeros or 0.
2141 2175
  *
2142 2176
  * @return LSTATUS NO_ERROR if the domain suffix(es) were read successfully,
2143 2177
  *         ERROR_FILE_NOT_FOUND if no domain was found for the interface,
... ...
@@ -2145,7 +2179,7 @@ GetItfDnsServersV6(HKEY itf_key, PSTR addrs, PDWORD size)
2145 2145
  *         any other error indicates an error while reading from the registry.
2146 2146
  */
2147 2147
 static LSTATUS
2148
-GetItfDnsDomains(HKEY itf, PWSTR domains, PDWORD size)
2148
+GetItfDnsDomains(HKEY itf, PCWSTR search_domains, PWSTR domains, PDWORD size)
2149 2149
 {
2150 2150
     if (domains == NULL || size == 0)
2151 2151
     {
... ...
@@ -2179,9 +2213,27 @@ GetItfDnsDomains(HKEY itf, PWSTR domains, PDWORD size)
2179 2179
                     *comma = '\0';
2180 2180
                 }
2181 2181
 
2182
+                /* Ignore itf domains which match a pushed search domain */
2183
+                size_t domain_len = wcslen(pos);
2184
+                if (ListContainsDomain(search_domains, pos, domain_len))
2185
+                {
2186
+                    if (comma)
2187
+                    {
2188
+                        pos = comma + 1;
2189
+                        continue;
2190
+                    }
2191
+                    else
2192
+                    {
2193
+                        /* This was the last domain */
2194
+                        *pos = '\0';
2195
+                        *size += 1;
2196
+                        return wcslen(domains) ? NO_ERROR : ERROR_FILE_NOT_FOUND;
2197
+                    }
2198
+                }
2199
+
2182 2200
                 /* Check for enough space to convert this domain */
2201
+                domain_len += 1; /* leading dot */
2183 2202
                 size_t converted_size = pos - domains;
2184
-                size_t domain_len = wcslen(pos) + 1;
2185 2203
                 size_t domain_size = domain_len * one_glyph;
2186 2204
                 size_t extra_size = 2 * one_glyph;
2187 2205
                 if (converted_size + domain_size + extra_size > buf_size)
... ...
@@ -2265,11 +2317,12 @@ out:
2265 2265
  * needed so that local DNS keeps working even when a catch all NRPT rule is
2266 2266
  * installed by a VPN connection.
2267 2267
  *
2268
- * @param  data       pointer to the data structures the values are returned in
2269
- * @param  data_size  number of exclude data structures pointed to
2268
+ * @param  search_domains  optional list of search domains
2269
+ * @param  data            pointer to the data structures the values are returned in
2270
+ * @param  data_size       number of exclude data structures pointed to
2270 2271
  */
2271 2272
 static void
2272
-GetNrptExcludeData(nrpt_exclude_data_t *data, size_t data_size)
2273
+GetNrptExcludeData(PCWSTR search_domains, nrpt_exclude_data_t *data, size_t data_size)
2273 2274
 {
2274 2275
     HKEY v4_itfs = INVALID_HANDLE_VALUE;
2275 2276
     HKEY v6_itfs = INVALID_HANDLE_VALUE;
... ...
@@ -2313,7 +2366,7 @@ GetNrptExcludeData(nrpt_exclude_data_t *data, size_t data_size)
2313 2313
         /* Get the DNS domain(s) for exclude routing */
2314 2314
         data[i].domains_size = sizeof(data[0].domains);
2315 2315
         memset(data[i].domains, 0, data[i].domains_size);
2316
-        err = GetItfDnsDomains(v4_itf, data[i].domains, &data[i].domains_size);
2316
+        err = GetItfDnsDomains(v4_itf, search_domains, data[i].domains, &data[i].domains_size);
2317 2317
         if (err)
2318 2318
         {
2319 2319
             if (err != ERROR_FILE_NOT_FOUND)
... ...
@@ -2471,15 +2524,16 @@ out:
2471 2471
  * local resolution of names is not interfered with in case the VPN resolves
2472 2472
  * all names.
2473 2473
  *
2474
- * @param  nrpt_key   the registry key to set the rules under
2475
- * @param  ovpn_pid   the PID of the openvpn process
2474
+ * @param  nrpt_key        the registry key to set the rules under
2475
+ * @param  ovpn_pid        the PID of the openvpn process
2476
+ * @param  search_domains  optional list of search domains
2476 2477
  */
2477 2478
 static void
2478
-SetNrptExcludeRules(HKEY nrpt_key, DWORD ovpn_pid)
2479
+SetNrptExcludeRules(HKEY nrpt_key, DWORD ovpn_pid, PCWSTR search_domains)
2479 2480
 {
2480 2481
     nrpt_exclude_data_t data[8]; /* data from up to 8 interfaces */
2481 2482
     memset(data, 0, sizeof(data));
2482
-    GetNrptExcludeData(data, _countof(data));
2483
+    GetNrptExcludeData(search_domains, data, _countof(data));
2483 2484
 
2484 2485
     unsigned n = 0;
2485 2486
     for (int i = 0; i < _countof(data); ++i)
... ...
@@ -2504,17 +2558,18 @@ SetNrptExcludeRules(HKEY nrpt_key, DWORD ovpn_pid)
2504 2504
 /**
2505 2505
  * Set NRPT rules for a openvpn process
2506 2506
  *
2507
- * @param  nrpt_key   the registry key to set the rules under
2508
- * @param  addresses  name server addresses
2509
- * @param  domains    optional list of split routing domains
2510
- * @param  dnssec     boolean whether DNSSEC is to be used
2511
- * @param  ovpn_pid   the PID of the openvpn process
2507
+ * @param  nrpt_key          the registry key to set the rules under
2508
+ * @param  addresses         name server addresses
2509
+ * @param  domains           optional list of split routing domains
2510
+ * @param  search_domains    optional list of search domains
2511
+ * @param  dnssec            boolean whether DNSSEC is to be used
2512
+ * @param  ovpn_pid          the PID of the openvpn process
2512 2513
  *
2513 2514
  * @return NO_ERROR on success, or a Windows error code
2514 2515
  */
2515 2516
 static DWORD
2516
-SetNrptRules(HKEY nrpt_key, const nrpt_address_t *addresses,
2517
-             const char *domains, BOOL dnssec, DWORD ovpn_pid)
2517
+SetNrptRules(HKEY nrpt_key, const nrpt_address_t *addresses, const char *domains,
2518
+             const char *search_domains, BOOL dnssec, DWORD ovpn_pid)
2518 2519
 {
2519 2520
     DWORD err = NO_ERROR;
2520 2521
     PWSTR wide_domains = L".\0"; /* DNS route everything by default */
... ...
@@ -2543,7 +2598,14 @@ SetNrptRules(HKEY nrpt_key, const nrpt_address_t *addresses,
2543 2543
     }
2544 2544
     else
2545 2545
     {
2546
-        SetNrptExcludeRules(nrpt_key, ovpn_pid);
2546
+        PWSTR wide_search_domains;
2547
+        wide_search_domains = utf8to16(search_domains);
2548
+        if (!wide_search_domains)
2549
+        {
2550
+            return ERROR_OUTOFMEMORY;
2551
+        }
2552
+        SetNrptExcludeRules(nrpt_key, ovpn_pid, wide_search_domains);
2553
+        free(wide_search_domains);
2547 2554
     }
2548 2555
 
2549 2556
     /* Create address string list */
... ...
@@ -2803,7 +2865,7 @@ HandleDNSConfigNrptMessage(const nrpt_dns_cfg_message_t *msg,
2803 2803
 
2804 2804
     /* Set NRPT rules */
2805 2805
     BOOL dnssec = (msg->flags & nrpt_dnssec) != 0;
2806
-    err = SetNrptRules(key, msg->addresses, msg->resolve_domains, dnssec, ovpn_pid);
2806
+    err = SetNrptRules(key, msg->addresses, msg->resolve_domains, msg->search_domains, dnssec, ovpn_pid);
2807 2807
     if (err)
2808 2808
     {
2809 2809
         goto out;