Browse code

Set DNS Domain using iservice

Use wmic instead of directly editing the registry
as the former does not take full effect unless the dns
client service is restarted.

Editing the registry appears to work erratically depending
on whether its followed with a dchp renew or ipconfig /registerdns
etc.

DOMAIN-SEARCH is not handled here as wmic only supports
setting the global search list which will over-ride all
interface specific values. Editing the registry directly
combined with a wmic command to reset the global SearchList
is an option that could be considered in a separate patch.

Trac # 1209, 1331

v2 changes
- Separate DNS domain setting from DNS server setting and call
only once either during IPv4 processing or IPv6 processing
if the former is not active. (file changed: tun.c)
- Null terminate domain and interface_name received from the
client. (file changed: interactive.c)
Its done using a const cast-away of msg in a limited scope.
Not pretty, but alternatives are no better.

Signed-off-by: Selva Nair <selva.nair@gmail.com>
Acked-by: Lev Stipakov <lstipakov@gmail.com>
Message-Id: <1601085886-10351-1-git-send-email-selva.nair@gmail.com>
URL: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg21097.html
Signed-off-by: Gert Doering <gert@greenie.muc.de>

Selva Nair authored on 2020/09/26 11:04:46
Showing 2 changed files
... ...
@@ -149,6 +149,61 @@ out:
149 149
 }
150 150
 
151 151
 static bool
152
+do_dns_domain_service(bool add, const struct tuntap *tt)
153
+{
154
+    bool ret = false;
155
+    ack_message_t ack;
156
+    struct gc_arena gc = gc_new();
157
+    HANDLE pipe = tt->options.msg_channel;
158
+
159
+    if (!tt->options.domain) /* no  domain to add or delete */
160
+    {
161
+        return true;
162
+    }
163
+
164
+    /* Use dns_cfg_msg with addr_len = 0 for setting only the DOMAIN */
165
+    dns_cfg_message_t dns = {
166
+        .header = {
167
+            (add ? msg_add_dns_cfg : msg_del_dns_cfg),
168
+            sizeof(dns_cfg_message_t),
169
+            0
170
+        },
171
+        .iface = { .index = tt->adapter_index, .name = "" },
172
+        .domains = "",      /* set below */
173
+        .family = AF_INET,  /* unused */
174
+        .addr_len = 0       /* add/delete only the domain, not DNS servers */
175
+    };
176
+
177
+    strncpynt(dns.iface.name, tt->actual_name, sizeof(dns.iface.name));
178
+    strncpynt(dns.domains, tt->options.domain, sizeof(dns.domains));
179
+    /* truncation of domain name is not checked as it can't happen
180
+     * with 512 bytes room in dns.domains.
181
+     */
182
+
183
+    msg(D_LOW, "%s dns domain on '%s' (if_index = %d) using service",
184
+            (add ? "Setting" : "Deleting"), dns.iface.name, dns.iface.index);
185
+    if (!send_msg_iservice(pipe, &dns, sizeof(dns), &ack, "TUN"))
186
+    {
187
+        goto out;
188
+    }
189
+
190
+    if (ack.error_number != NO_ERROR)
191
+    {
192
+        msg(M_WARN, "TUN: %s dns domain failed using service: %s [status=%u if_name=%s]",
193
+            (add ? "adding" : "deleting"), strerror_win32(ack.error_number, &gc),
194
+            ack.error_number, dns.iface.name);
195
+        goto out;
196
+    }
197
+
198
+    msg(M_INFO, "DNS domain %s using service", (add ? "set" : "deleted"));
199
+    ret = true;
200
+
201
+out:
202
+    gc_free(&gc);
203
+    return ret;
204
+}
205
+
206
+static bool
152 207
 do_dns_service(bool add, const short family, const struct tuntap *tt)
153 208
 {
154 209
     bool ret = false;
... ...
@@ -164,6 +219,7 @@ do_dns_service(bool add, const short family, const struct tuntap *tt)
164 164
         return true;
165 165
     }
166 166
 
167
+    /* Use dns_cfg_msg with domain = "" for setting only the DNS servers */
167 168
     dns_cfg_message_t dns = {
168 169
         .header = {
169 170
             (add ? msg_add_dns_cfg : msg_del_dns_cfg),
... ...
@@ -1100,6 +1156,11 @@ do_ifconfig_ipv6(struct tuntap *tt, const char *ifname, int tun_mtu,
1100 1100
         }
1101 1101
         do_dns_service(true, AF_INET6, tt);
1102 1102
         do_set_mtu_service(tt, AF_INET6, tun_mtu);
1103
+        /* If IPv4 is not enabled, set DNS domain here */
1104
+        if (!tt->did_ifconfig_setup)
1105
+        {
1106
+           do_dns_domain_service(true, tt);
1107
+        }
1103 1108
     }
1104 1109
     else
1105 1110
     {
... ...
@@ -1485,6 +1546,7 @@ do_ifconfig_ipv4(struct tuntap *tt, const char *ifname, int tun_mtu,
1485 1485
     {
1486 1486
         do_address_service(true, AF_INET, tt);
1487 1487
         do_dns_service(true, AF_INET, tt);
1488
+        do_dns_domain_service(true, tt);
1488 1489
     }
1489 1490
     else if (tt->options.ip_win32_type == IPW32_SET_NETSH)
1490 1491
     {
... ...
@@ -6761,6 +6823,11 @@ close_tun(struct tuntap *tt, openvpn_net_ctx_t *ctx)
6761 6761
         }
6762 6762
         else if (tt->options.msg_channel)
6763 6763
         {
6764
+            /* If IPv4 is not enabled, delete DNS domain here */
6765
+            if (!tt->did_ifconfig_setup)
6766
+            {
6767
+                do_dns_domain_service(false, tt);
6768
+            }
6764 6769
             if (tt->options.dns6_len > 0)
6765 6770
             {
6766 6771
                 do_dns_service(false, AF_INET6, tt);
... ...
@@ -6786,6 +6853,7 @@ close_tun(struct tuntap *tt, openvpn_net_ctx_t *ctx)
6786 6786
         }
6787 6787
         else if (tt->options.msg_channel)
6788 6788
         {
6789
+            do_dns_domain_service(false, tt);
6789 6790
             do_dns_service(false, AF_INET, tt);
6790 6791
             do_address_service(false, AF_INET, tt);
6791 6792
         }
... ...
@@ -91,6 +91,7 @@ typedef enum {
91 91
     block_dns,
92 92
     undo_dns4,
93 93
     undo_dns6,
94
+    undo_domain,
94 95
     _undo_type_max
95 96
 } undo_type_t;
96 97
 typedef list_item_t *undo_lists_t[_undo_type_max];
... ...
@@ -564,6 +565,24 @@ InterfaceLuid(const char *iface_name, PNET_LUID luid)
564 564
     return status;
565 565
 }
566 566
 
567
+static DWORD
568
+ConvertInterfaceNameToIndex(const wchar_t *ifname, NET_IFINDEX *index)
569
+{
570
+   NET_LUID luid;
571
+   DWORD err;
572
+
573
+   err = ConvertInterfaceAliasToLuid(ifname, &luid);
574
+   if (err == ERROR_SUCCESS)
575
+   {
576
+       err = ConvertInterfaceLuidToIndex(&luid, index);
577
+   }
578
+   if (err != ERROR_SUCCESS)
579
+   {
580
+       MsgToEventLog(M_ERR, L"Failed to find interface index for <%s>", ifname);
581
+   }
582
+   return err;
583
+}
584
+
567 585
 static BOOL
568 586
 CmpAddress(LPVOID item, LPVOID address)
569 587
 {
... ...
@@ -1057,6 +1076,53 @@ out:
1057 1057
     return err;
1058 1058
 }
1059 1059
 
1060
+/**
1061
+ * Run command: wmic nicconfig (InterfaceIndex=$if_index) call $action ($data)
1062
+ * @param  if_index    "index of interface"
1063
+ * @param  action      e.g., "SetDNSDomain"
1064
+ * @param  data        data if required for action
1065
+ *                     - a single word for SetDNSDomain, empty or NULL to delete
1066
+ *                     - comma separated values for a list
1067
+ */
1068
+static DWORD
1069
+wmic_nicconfig_cmd(const wchar_t *action, const NET_IFINDEX if_index,
1070
+                   const wchar_t *data)
1071
+{
1072
+    DWORD err = 0;
1073
+    wchar_t argv0[MAX_PATH];
1074
+    wchar_t *cmdline = NULL;
1075
+    int timeout = 10000; /* in msec */
1076
+
1077
+    swprintf(argv0, _countof(argv0), L"%s\\%s", get_win_sys_path(), L"wbem\\wmic.exe");
1078
+    argv0[_countof(argv0) - 1] = L'\0';
1079
+
1080
+    const wchar_t *fmt;
1081
+    /* comma separated list must be enclosed in parenthesis */
1082
+    if (data && wcschr(data, L','))
1083
+    {
1084
+       fmt = L"wmic nicconfig where (InterfaceIndex=%ld) call %s (%s)";
1085
+    }
1086
+    else
1087
+    {
1088
+       fmt = L"wmic nicconfig where (InterfaceIndex=%ld) call %s %s";
1089
+    }
1090
+
1091
+    size_t ncmdline = wcslen(fmt) + 20 + wcslen(action) /* max 20 for ifindex */
1092
+                    + (data ? wcslen(data) + 1 : 1);
1093
+    cmdline = malloc(ncmdline*sizeof(wchar_t));
1094
+    if (!cmdline)
1095
+    {
1096
+        return ERROR_OUTOFMEMORY;
1097
+    }
1098
+
1099
+    openvpn_sntprintf(cmdline, ncmdline, fmt, if_index, action,
1100
+                      data? data : L"");
1101
+    err = ExecCommand(argv0, cmdline, timeout);
1102
+
1103
+    free(cmdline);
1104
+    return err;
1105
+}
1106
+
1060 1107
 /* Delete all IPv4 or IPv6 dns servers for an interface */
1061 1108
 static DWORD
1062 1109
 DeleteDNS(short family, wchar_t *if_name)
... ...
@@ -1079,6 +1145,54 @@ CmpWString(LPVOID item, LPVOID str)
1079 1079
     return (wcscmp(item, str) == 0) ? TRUE : FALSE;
1080 1080
 }
1081 1081
 
1082
+/**
1083
+ * Set interface specific DNS domain suffix
1084
+ * @param  if_name    name of the the interface
1085
+ * @param  domain     a single domain name
1086
+ * @param  lists      pointer to the undo lists. If NULL
1087
+ *                    undo lists are not altered.
1088
+ * Will delete the currently set value if domain is empty.
1089
+ */
1090
+static DWORD
1091
+SetDNSDomain(const wchar_t *if_name, const char *domain, undo_lists_t *lists)
1092
+{
1093
+   NET_IFINDEX if_index;
1094
+
1095
+   DWORD err  = ConvertInterfaceNameToIndex(if_name, &if_index);
1096
+   if (err != ERROR_SUCCESS)
1097
+   {
1098
+       return err;
1099
+   }
1100
+
1101
+   wchar_t *wdomain = utf8to16(domain); /* utf8 to wide-char */
1102
+   if (!wdomain)
1103
+   {
1104
+       return ERROR_OUTOFMEMORY;
1105
+   }
1106
+
1107
+   /* free undo list if previously set */
1108
+   if (lists)
1109
+   {
1110
+       free(RemoveListItem(&(*lists)[undo_domain], CmpWString, (void *)if_name));
1111
+   }
1112
+
1113
+   err = wmic_nicconfig_cmd(L"SetDNSDomain", if_index, wdomain);
1114
+
1115
+   /* Add to undo list if domain is non-empty */
1116
+   if (err == 0 && wdomain[0] && lists)
1117
+   {
1118
+        wchar_t *tmp_name = wcsdup(if_name);
1119
+        if (!tmp_name || AddListItem(&(*lists)[undo_domain], tmp_name))
1120
+        {
1121
+            free(tmp_name);
1122
+            err = ERROR_OUTOFMEMORY;
1123
+        }
1124
+   }
1125
+
1126
+   free(wdomain);
1127
+   return err;
1128
+}
1129
+
1082 1130
 static DWORD
1083 1131
 HandleDNSConfigMessage(const dns_cfg_message_t *msg, undo_lists_t *lists)
1084 1132
 {
... ...
@@ -1098,6 +1212,13 @@ HandleDNSConfigMessage(const dns_cfg_message_t *msg, undo_lists_t *lists)
1098 1098
         return ERROR_MESSAGE_DATA;
1099 1099
     }
1100 1100
 
1101
+    /* use a non-const reference with limited scope to enforce null-termination of strings from client */
1102
+    {
1103
+        dns_cfg_message_t *msgptr = (dns_cfg_message_t *) msg;
1104
+        msgptr->iface.name[_countof(msg->iface.name)-1] = '\0';
1105
+        msgptr->domains[_countof(msg->domains)-1] = '\0';
1106
+    }
1107
+
1101 1108
     wchar_t *wide_name = utf8to16(msg->iface.name); /* utf8 to wide-char */
1102 1109
     if (!wide_name)
1103 1110
     {
... ...
@@ -1117,9 +1238,14 @@ HandleDNSConfigMessage(const dns_cfg_message_t *msg, undo_lists_t *lists)
1117 1117
         free(RemoveListItem(&(*lists)[undo_type], CmpWString, wide_name));
1118 1118
     }
1119 1119
 
1120
-    if (msg->header.type == msg_del_dns_cfg) /* job done */
1120
+    if (msg->header.type == msg_del_dns_cfg)
1121 1121
     {
1122
-        goto out;
1122
+        if (msg->domains[0])
1123
+        {
1124
+            /* setting an empty domain removes any previous value */
1125
+            err = SetDNSDomain(wide_name, "", lists);
1126
+        }
1127
+        goto out;  /* job done */
1123 1128
     }
1124 1129
 
1125 1130
     for (int i = 0; i < addr_len; ++i)
... ...
@@ -1142,6 +1268,8 @@ HandleDNSConfigMessage(const dns_cfg_message_t *msg, undo_lists_t *lists)
1142 1142
          */
1143 1143
     }
1144 1144
 
1145
+    err = 0;
1146
+
1145 1147
     if (msg->addr_len > 0)
1146 1148
     {
1147 1149
         wchar_t *tmp_name = wcsdup(wide_name);
... ...
@@ -1154,7 +1282,10 @@ HandleDNSConfigMessage(const dns_cfg_message_t *msg, undo_lists_t *lists)
1154 1154
         }
1155 1155
     }
1156 1156
 
1157
-    err = 0;
1157
+    if (msg->domains[0])
1158
+    {
1159
+        err = SetDNSDomain(wide_name, msg->domains, lists);
1160
+    }
1158 1161
 
1159 1162
 out:
1160 1163
     free(wide_name);
... ...
@@ -1445,6 +1576,10 @@ Undo(undo_lists_t *lists)
1445 1445
                     DeleteDNS(AF_INET6, item->data);
1446 1446
                     break;
1447 1447
 
1448
+                case undo_domain:
1449
+                    SetDNSDomain(item->data, "", NULL);
1450
+                    break;
1451
+
1448 1452
                 case block_dns:
1449 1453
                     interface_data = (block_dns_data_t *)(item->data);
1450 1454
                     delete_block_dns_filters(interface_data->engine);