From patchwork Wed Dec 4 12:43:45 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "stipa (Code Review)" X-Patchwork-Id: 3972 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a05:7000:a393:b0:5dc:2311:f747 with SMTP id jg19csp166830mab; Wed, 4 Dec 2024 04:44:08 -0800 (PST) X-Forwarded-Encrypted: i=2; AJvYcCXXZGl+ytF0sbtuFons/mvMHzn5rrqcAqJHRaSg7zImNr1bPSnBaqDxz9Jh1U1lLOd26WZ+XELIPXE=@openvpn.net X-Google-Smtp-Source: AGHT+IGVH2oBrH2qIdeB14rcz5cVtzaXPr9pDuFqdf1yYls8KbwZETrx3hh2UE0bpM+Opdtv0evR X-Received: by 2002:a05:6830:b93:b0:71d:4a91:9208 with SMTP id 46e09a7af769-71dad68753cmr5287130a34.17.1733316248273; Wed, 04 Dec 2024 04:44:08 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1733316248; cv=none; d=google.com; s=arc-20240605; b=Sf/I0syor6edQZnDPIgbyXvZcyNLXWONluMG/6Od8gP7lg3Cwi25KpCbKT7SdVUcgA Mq582zUIKROrtb3Rj1s+NTaffTUp0ccfn2aJrrC1CAqanZcygCAP53xLwj68XXl5NsDZ nshWiNSl4Vd1lOOh/h0YmEk9FR7qoEZBQlYe/mB6kbAqEmgniF6u7svBA7SSZK4a2pHP Bn/JwD+6qwBwuzFu2o9OQmj0nMP6sbkbm33hLrzjSXlbHB0MhdqE+FKf5H+hYU4ORZsi Y8ejelzqLphIjgs1JOuK5PAByVTVPixXnRQXxBlgF2tJTOoKskCdcuywMr4AZ+E3jsew 1JnA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20240605; h=errors-to:cc:reply-to:list-subscribe:list-help:list-post :list-archive:list-unsubscribe:list-id:precedence:subject:user-agent :mime-version:message-id:references:auto-submitted:to:date:from :dkim-signature:dkim-signature:dkim-signature; bh=qN7FVxJS4TMLQIBHnEkvF1pY2sDkqT5EMgNcbHxh0oM=; fh=U7wEyxtwz2o5+UdevFSA47vNeG9knhWH0KV//QhD5a0=; b=ZO7pN6dnd0f1q1Qh0Nj+kxeM0Xr6617BRYSmE8BrAb257NopOiWXY67bURbd2GKuog bfA7NYIrODJL/YtAuK2c7HBAFV9cAE4zDD+tCi2uJ7qJOrFwnEsn1rvO8px6TAO+PscG voALBVqOWfBGMh+ZDNRGAZU+AXAaMIvyDvM8J1jhgF6MCEWbaze4unf+TiSw8kjKFVvi 9S0QyuHb3dGTSB9G9UaTdRSU3zCpDz//gv/CtERGs1WUite8TNTA7OVYqwnu5qTFiMOG NpuJR/BXMGjwmWMBl565Zg71DWHuGkXhEcGqA9kF+wit3iko0Kt078XgxxyUIU4SaFu5 A97w==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=HavHRihH; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=kslcrMnB; dkim=neutral (body hash did not verify) header.i=@openvpn.net header.s=google header.b=InZ6Xr9f; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=openvpn.net; dara=fail header.i=@openvpn.net Received: from lists.sourceforge.net (lists.sourceforge.net. [216.105.38.7]) by mx.google.com with ESMTPS id 006d021491bc7-5f21a4daffdsi7150136eaf.61.2024.12.04.04.44.07 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Wed, 04 Dec 2024 04:44:08 -0800 (PST) Received-SPF: pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) client-ip=216.105.38.7; Authentication-Results: mx.google.com; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=HavHRihH; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=kslcrMnB; dkim=neutral (body hash did not verify) header.i=@openvpn.net header.s=google header.b=InZ6Xr9f; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=openvpn.net; dara=fail header.i=@openvpn.net Received: from [127.0.0.1] (helo=sfs-ml-1.v29.lw.sourceforge.com) by sfs-ml-1.v29.lw.sourceforge.com with esmtp (Exim 4.95) (envelope-from ) id 1tIojW-0008Ur-7C; Wed, 04 Dec 2024 12:43:58 +0000 Received: from [172.30.29.66] (helo=mx.sourceforge.net) by sfs-ml-1.v29.lw.sourceforge.com with esmtps (TLS1.2) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.95) (envelope-from ) id 1tIojU-0008Ul-2u for openvpn-devel@lists.sourceforge.net; Wed, 04 Dec 2024 12:43:56 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=Content-Type:Content-Transfer-Encoding:MIME-Version :Message-ID:Reply-To:References:Subject:List-Unsubscribe:List-Id:Cc:To:Date: From:Sender:Content-ID:Content-Description:Resent-Date:Resent-From: Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:In-Reply-To:List-Help: List-Subscribe:List-Post:List-Owner:List-Archive; bh=WNPWi/nU8N5LTJt1m25yigaofBMZYLBhYhGUNodVLMA=; b=HavHRihHzHvsVrpwmU8Zb4Bu3Y HrhjbT94zLUqLMy0hiANa67ycHW/P2VomawcugRa7qL9JzZ6ol8w0cnAQi5tBF2J0PuYlspw6SzzY FUK998wCgv5isjC5uli22rjprkswayeHIJbGIlUwY9RGKa0Q1sjXRfgtGS7JJJPa86AI=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=Content-Type:Content-Transfer-Encoding:MIME-Version:Message-ID:Reply-To: References:Subject:List-Unsubscribe:List-Id:Cc:To:Date:From:Sender:Content-ID :Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To: Resent-Cc:Resent-Message-ID:In-Reply-To:List-Help:List-Subscribe:List-Post: List-Owner:List-Archive; bh=WNPWi/nU8N5LTJt1m25yigaofBMZYLBhYhGUNodVLMA=; b=k slcrMnBAddf/8IikeMgTlUhrp2zY/0QbItokzOGTlrH1CZ0mh37WDOblyMZ6XwLlhhNBSOaSvjZAf XKUdytypOzwDu1dAWrw/DZA71UMDjAaEvB2TBE69PK3JyPhH8xqxa8WxhamMzZIAmzGUd9sYbkEq2 FLUcjW9W337AjKxI=; Received: from mail-wm1-f54.google.com ([209.85.128.54]) by sfi-mx-2.v28.lw.sourceforge.com with esmtps (TLS1.2:ECDHE-RSA-AES128-GCM-SHA256:128) (Exim 4.95) id 1tIojR-0005Vp-4b for openvpn-devel@lists.sourceforge.net; Wed, 04 Dec 2024 12:43:56 +0000 Received: by mail-wm1-f54.google.com with SMTP id 5b1f17b1804b1-434a8640763so55524925e9.1 for ; Wed, 04 Dec 2024 04:43:53 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=openvpn.net; s=google; t=1733316226; x=1733921026; darn=lists.sourceforge.net; h=user-agent:content-disposition:content-transfer-encoding :mime-version:message-id:reply-to:references:subject :list-unsubscribe:list-id:auto-submitted:cc:to:date:from:from:to:cc :subject:date:message-id:reply-to; bh=WNPWi/nU8N5LTJt1m25yigaofBMZYLBhYhGUNodVLMA=; b=InZ6Xr9fIAgTL07L+TTpOwwV3bl6DmGcLMOnfrOqgMnG6ugzqyMVsbktERf44tPugQ PVU23c+pejAofPsX2nRy/wTVrVWwNo05EYBloD/Jz1oeayRpp+lHu/cCCcZJjOTJVqbj oGQpyTBUObXI4gGmsytlcXUFQL8mcAjJGJMcytHIc7Q5534pn5ihRBT79AOIldmXfM7+ aWb0gTyStMeYg2+U5gGFVd5BTKSk6dffiF/3gl/kUT4EQzhcfu63o/ooagrS4+2mockr eKz9j+LHosJvpMIBkFa48Gl5WTKQAReJXpgcXc/VgSnd6x2/SvQj0DePy0LNhqRhq89g ZP6A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1733316226; x=1733921026; h=user-agent:content-disposition:content-transfer-encoding :mime-version:message-id:reply-to:references:subject :list-unsubscribe:list-id:auto-submitted:cc:to:date:from :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=WNPWi/nU8N5LTJt1m25yigaofBMZYLBhYhGUNodVLMA=; b=miojefIIKkhYMGBmuTba3gyDb+qEO0faOxkP9ntp8C6f//WYSIq9KKHC0ZNJEPzpH3 lQTId0Jlt2bjft5hr7UalpZtVEbYONZ0iTJT991+DzOYDMVevZrgmBUl4FCfwbMau+Rb 2ocBLtSYyWC2IOSgw+1rOijWWyRO1FIiuLNv5onR302o0tL2qVfDUM7gBVUP2xs6VB52 OoJ8ZGz2F0PXxXF5+gBSwqNwP00AxB0KUI6wouxaLW4jBOjTZS9MKVHYKXLL+zfdUd2+ QA29cP5kTWjunkPzW+7Ie8tlUqOaMHF8hZbOl7mw3l7o1lqfv0Rldb3WS6xdqj3AbAb+ BNtQ== X-Gm-Message-State: AOJu0YwYZbJqf0hwMlCQ93PmC+eqXhtuGekwRGMwXB8S9QUhcrmRhvnn roe5HeWMAXZ58y/44f4RW9ius7jzvrKeFH9B6kv/hm7i63j/sXSf420BDGsnhWU= X-Gm-Gg: ASbGncsNqPcFWNhHN9n/S8P1jQLd5eXiwZa/yUF8RQID5QXldSl0TQAdGviFwbRUUK/ iOyr4yyGi1MoCxz3fgFBIsODHr6pz3Nm3bu9OcQcT7JqJQZVZYzVWsEjeDKYwxt5OqI2YkBuszO 0vy88fBnT+LcewakgkK6SiLZcUKmpet0CaxP7PGX2CcJ4q0m/THgjZnSfjh6yWZW7XVtpfmgOQg ZarugYXjcri74Wqts9MQ429JpmLyxQhuCCModHWZBj7Q6UaI0G+vdFYUo54cyqrnPkOCPptZO/6 UDtkYS5sc7c+f2zIYS21SjVTO2wUAkROsZoYvDScZQ== X-Received: by 2002:a05:600c:3509:b0:434:a196:6377 with SMTP id 5b1f17b1804b1-434d09bf789mr56275925e9.14.1733316226081; Wed, 04 Dec 2024 04:43:46 -0800 (PST) Received: from gerrit.openvpn.in (ec2-18-159-0-78.eu-central-1.compute.amazonaws.com. [18.159.0.78]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-434d527252bsm24001915e9.2.2024.12.04.04.43.45 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 04 Dec 2024 04:43:45 -0800 (PST) From: "d12fk (Code Review)" X-Google-Original-From: "d12fk (Code Review)" X-Gerrit-PatchSet: 1 Date: Wed, 4 Dec 2024 12:43:45 +0000 To: plaisthos , flichtenheld Auto-Submitted: auto-generated X-Gerrit-MessageType: newchange X-Gerrit-Change-Id: Icaffbfa6b2e8efa2bd24a05537cb74b15f4fed96 X-Gerrit-Change-Number: 824 X-Gerrit-Project: openvpn X-Gerrit-ChangeURL: X-Gerrit-Commit: c0ef0575a3fdd85d1a58491c35d437341bb39c0c References: Message-ID: <7a8fea021d6b4c1cea79d7fc28ab2233b08a64af-HTML@gerrit.openvpn.net> MIME-Version: 1.0 User-Agent: Gerrit/3.8.2 X-Spam-Score: -0.2 (/) X-Spam-Report: Spam detection software, running on the system "util-spamd-2.v13.lw.sourceforge.com", has NOT identified this incoming email as spam. The original message has been attached to this so you can view it or label similar future email. If you have any questions, see the administrator of that system for details. Content preview: Attention is currently required from: flichtenheld, plaisthos. Hello plaisthos, flichtenheld, I'd like you to do a code review. Please visit Content analysis details: (-0.2 points, 6.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- -0.0 RCVD_IN_DNSWL_NONE RBL: Sender listed at https://www.dnswl.org/, no trust [209.85.128.54 listed in list.dnswl.org] 0.0 RCVD_IN_VALIDITY_RPBL_BLOCKED RBL: ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. [209.85.128.54 listed in bl.score.senderscore.com] 0.0 RCVD_IN_VALIDITY_CERTIFIED_BLOCKED RBL: ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. [209.85.128.54 listed in sa-accredit.habeas.com] -0.0 RCVD_IN_MSPIKE_H2 RBL: Average reputation (+2) [209.85.128.54 listed in wl.mailspike.net] -0.0 SPF_PASS SPF: sender matches SPF record 0.0 SPF_HELO_NONE SPF: HELO does not publish an SPF Record 0.0 WEIRD_PORT URI: Uses non-standard port number for HTTP 0.0 HTML_MESSAGE BODY: HTML included in message -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain 0.0 T_KAM_HTML_FONT_INVALID Test for Invalidly Named or Formatted Colors in HTML X-Headers-End: 1tIojR-0005Vp-4b Subject: [Openvpn-devel] [L] Change in openvpn[master]: dns: support multiple domains without DHCP X-BeenThere: openvpn-devel@lists.sourceforge.net X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: heiko@openvpn.net, arne-openvpn@rfc2549.org, openvpn-devel@lists.sourceforge.net, frank@lichtenheld.com Cc: openvpn-devel Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox X-GMAIL-THRID: =?utf-8?q?1817513818473648618?= X-GMAIL-MSGID: =?utf-8?q?1817513818473648618?= X-getmail-filter-classifier: gerrit message type newchange Attention is currently required from: flichtenheld, plaisthos. Hello plaisthos, flichtenheld, I'd like you to do a code review. Please visit http://gerrit.openvpn.net/c/openvpn/+/824?usp=email to review the following change. Change subject: dns: support multiple domains without DHCP ...................................................................... dns: support multiple domains without DHCP Instead of using wmic on Windows to set one (the first) DNS domain, modify the registry directly and let the resolver know that something changed. This fixes that more than one search domain suffix could only be applied when DHCP and the tap driver was used. Now this works as well in netsh mode with the interactive service. Change-Id: Icaffbfa6b2e8efa2bd24a05537cb74b15f4fed96 Signed-off-by: Heiko Hund --- M src/openvpn/tun.c M src/openvpnserv/interactive.c 2 files changed, 708 insertions(+), 110 deletions(-) git pull ssh://gerrit.openvpn.net:29418/openvpn refs/changes/24/824/1 diff --git a/src/openvpn/tun.c b/src/openvpn/tun.c index 1e67d37..53f3ebc 100644 --- a/src/openvpn/tun.c +++ b/src/openvpn/tun.c @@ -183,9 +183,10 @@ { ack_message_t ack; struct gc_arena gc = gc_new(); - HANDLE pipe = tt->options.msg_channel; + const struct tuntap_options *o = &tt->options; - if (!tt->options.domain) /* no domain to add or delete */ + /* no domains to add or delete */ + if (!o->domain && !o->domain_search_list[0]) { goto out; } @@ -203,28 +204,50 @@ .addr_len = 0 /* add/delete only the domain, not DNS servers */ }; + /* interface name is required */ strncpynt(dns.iface.name, tt->actual_name, sizeof(dns.iface.name)); - strncpynt(dns.domains, tt->options.domain, sizeof(dns.domains)); - /* truncation of domain name is not checked as it can't happen - * with 512 bytes room in dns.domains. - */ + dns.iface.name[sizeof(dns.iface.name) - 1] = '\0'; - msg(D_LOW, "%s dns domain on '%s' (if_index = %d) using service", + /* only use domain when there are no search domains */ + if (o->domain && !o->domain_search_list[0]) + { + strncpynt(dns.domains, o->domain, sizeof(dns.domains)); + } + + /* Create a comma separated list of search domains */ + for (int i = 0; i < N_SEARCH_LIST_LEN && o->domain_search_list[i]; ++i) + { + size_t dstlen = strlen(dns.domains); + size_t srclen = strlen(o->domain_search_list[i]); + size_t extra = dstlen ? 2 : 1; /* space for comma and NUL */ + if (dstlen + srclen + extra > sizeof(dns.domains)) + { + msg(M_WARN, "DNS search domains sent to service truncated to %d", i); + break; + } + if (dstlen) + { + dns.domains[dstlen++] = ','; + } + strncpynt(dns.domains + dstlen, o->domain_search_list[i], srclen); + } + + msg(D_LOW, "%s DNS domains on '%s' (if_index = %d) using service", (add ? "Setting" : "Deleting"), dns.iface.name, dns.iface.index); - if (!send_msg_iservice(pipe, &dns, sizeof(dns), &ack, "TUN")) + if (!send_msg_iservice(o->msg_channel, &dns, sizeof(dns), &ack, "TUN")) { goto out; } if (ack.error_number != NO_ERROR) { - msg(M_WARN, "TUN: %s dns domain failed using service: %s [status=%u if_name=%s]", + msg(M_WARN, "TUN: %s DNS domains failed using service: %s [status=%u if_name=%s]", (add ? "adding" : "deleting"), strerror_win32(ack.error_number, &gc), ack.error_number, dns.iface.name); goto out; } - msg(M_INFO, "DNS domain %s using service", (add ? "set" : "deleted")); + msg(M_INFO, "DNS domains %s using service", (add ? "set" : "deleted")); out: gc_free(&gc); @@ -1719,7 +1742,7 @@ argv_free(&argv); gc_free(&gc); #endif /* if defined(TARGET_LINUX) */ - /* Empty for _WIN32 and all other unixoid platforms */ + /* Empty for _WIN32 and all other unixoid platforms */ } static void @@ -1745,7 +1768,7 @@ argv_free(&argv); gc_free(&gc); #endif /* if defined(TARGET_LINUX) */ - /* Empty for _WIN32 and all other unixoid platforms */ + /* Empty for _WIN32 and all other unixoid platforms */ } void diff --git a/src/openvpnserv/interactive.c b/src/openvpnserv/interactive.c index 67f5bbc..cad8b02 100644 --- a/src/openvpnserv/interactive.c +++ b/src/openvpnserv/interactive.c @@ -88,7 +88,7 @@ wfp_block, undo_dns4, undo_dns6, - undo_domain, + undo_domains, undo_ring_buffer, undo_wins, _undo_type_max @@ -103,6 +103,11 @@ } wfp_block_data_t; typedef struct { + char itf_name[256]; + PWSTR domains; +} dns_domains_undo_data_t; + +typedef struct { struct tun_ring *send_ring; struct tun_ring *receive_ring; } ring_buffer_maps_t; @@ -565,24 +570,6 @@ return status; } -static DWORD -ConvertInterfaceNameToIndex(const wchar_t *ifname, NET_IFINDEX *index) -{ - NET_LUID luid; - DWORD err; - - err = ConvertInterfaceAliasToLuid(ifname, &luid); - if (err == ERROR_SUCCESS) - { - err = ConvertInterfaceLuidToIndex(&luid, index); - } - if (err != ERROR_SUCCESS) - { - MsgToEventLog(M_ERR, L"Failed to find interface index for <%ls>", ifname); - } - return err; -} - static BOOL CmpAddress(LPVOID item, LPVOID address) { @@ -1152,52 +1139,6 @@ return err; } -/** - * Run command: wmic nicconfig (InterfaceIndex=$if_index) call $action ($data) - * @param if_index "index of interface" - * @param action e.g., "SetDNSDomain" - * @param data data if required for action - * - a single word for SetDNSDomain, empty or NULL to delete - * - comma separated values for a list - */ -static DWORD -wmic_nicconfig_cmd(const wchar_t *action, const NET_IFINDEX if_index, - const wchar_t *data) -{ - DWORD err = 0; - wchar_t argv0[MAX_PATH]; - wchar_t *cmdline = NULL; - int timeout = 10000; /* in msec */ - - swprintf(argv0, _countof(argv0), L"%ls\\%ls", get_win_sys_path(), L"wbem\\wmic.exe"); - - const wchar_t *fmt; - /* comma separated list must be enclosed in parenthesis */ - if (data && wcschr(data, L',')) - { - fmt = L"wmic nicconfig where (InterfaceIndex=%ld) call %ls (%ls)"; - } - else - { - fmt = L"wmic nicconfig where (InterfaceIndex=%ld) call %ls \"%ls\""; - } - - size_t ncmdline = wcslen(fmt) + 20 + wcslen(action) /* max 20 for ifindex */ - + (data ? wcslen(data) + 1 : 1); - cmdline = malloc(ncmdline*sizeof(wchar_t)); - if (!cmdline) - { - return ERROR_OUTOFMEMORY; - } - - swprintf(cmdline, ncmdline, fmt, if_index, action, - data ? data : L""); - err = ExecCommand(argv0, cmdline, timeout); - - free(cmdline); - return err; -} - /* Delete all IPv4 or IPv6 dns servers for an interface */ static DWORD DeleteDNS(short family, wchar_t *if_name) @@ -1221,50 +1162,647 @@ } /** - * Set interface specific DNS domain suffix - * @param if_name name of the interface - * @param domain a single domain name - * @param lists pointer to the undo lists. If NULL - * undo lists are not altered. - * Will delete the currently set value if domain is empty. + * Signal the DNS resolver (and others potentially) to reload the + * group policy (DNS) settings on 32 bit Windows systems + * + * @return BOOL to indicate if the reload was initiated + */ +static BOOL +ApplyGpolSettings32() +{ + typedef NTSTATUS (__stdcall *publish_fn_t)( + DWORD StateNameLo, + DWORD StateNameHi, + DWORD TypeId, + DWORD Buffer, + DWORD Length, + DWORD ExplicitScope); + publish_fn_t RtlPublishWnfStateData; + const DWORD WNF_GPOL_SYSTEM_CHANGES_HI = 0x0D891E2A; + const DWORD WNF_GPOL_SYSTEM_CHANGES_LO = 0xA3BC0875; + + HMODULE ntdll = LoadLibraryA("ntdll.dll"); + if (ntdll == NULL) + { + return FALSE; + } + + RtlPublishWnfStateData = (publish_fn_t) GetProcAddress(ntdll, "RtlPublishWnfStateData"); + if (RtlPublishWnfStateData == NULL) + { + return FALSE; + } + + if (RtlPublishWnfStateData(WNF_GPOL_SYSTEM_CHANGES_LO, WNF_GPOL_SYSTEM_CHANGES_HI, 0, 0, 0, 0) != ERROR_SUCCESS) + { + return FALSE; + } + + return TRUE; +} + +/** + * Signal the DNS resolver (and others potentially) to reload the + * group policy (DNS) settings on 64 bit Windows systems + * + * @return BOOL to indicate if the reload was initiated + */ +static BOOL +ApplyGpolSettings64() +{ + typedef NTSTATUS (*publish_fn_t)( + INT64 StateName, + INT64 TypeId, + INT64 Buffer, + unsigned int Length, + INT64 ExplicitScope); + publish_fn_t RtlPublishWnfStateData; + const INT64 WNF_GPOL_SYSTEM_CHANGES = 0x0D891E2AA3BC0875; + + HMODULE ntdll = LoadLibraryA("ntdll.dll"); + if (ntdll == NULL) + { + return FALSE; + } + + RtlPublishWnfStateData = (publish_fn_t) GetProcAddress(ntdll, "RtlPublishWnfStateData"); + if (RtlPublishWnfStateData == NULL) + { + return FALSE; + } + + if (RtlPublishWnfStateData(WNF_GPOL_SYSTEM_CHANGES, 0, 0, 0, 0) != ERROR_SUCCESS) + { + return FALSE; + } + + return TRUE; +} + +/** + * Signal the DNS resolver (and others potentially) to reload the group policy (DNS) settings + * + * @return BOOL to indicate if the reload was initiated + */ +static BOOL +ApplyGpolSettings() +{ + SYSTEM_INFO si; + GetSystemInfo(&si); + const BOOL win_32bit = si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_INTEL; + return win_32bit ? ApplyGpolSettings32() : ApplyGpolSettings64(); +} + +/** + * Signal the DNS resolver to reload its settings + * + * @param apply_gpol BOOL reload setting from group policy hives as well + * + * @return BOOL to indicate if the reload was initiated + */ +static BOOL +ApplyDnsSettings(BOOL apply_gpol) +{ + BOOL res = FALSE; + SC_HANDLE scm = INVALID_HANDLE_VALUE; + SC_HANDLE dnssvc = INVALID_HANDLE_VALUE; + + if (apply_gpol && ApplyGpolSettings() == FALSE) + { + MsgToEventLog(M_ERR, TEXT("ApplyDnsSettings: sending GPOL notification failed")); + } + + scm = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); + if (scm == NULL) + { + MsgToEventLog(M_ERR, TEXT("ApplyDnsSettings: " + "OpenSCManager call failed (%lu)"), GetLastError()); + goto out; + } + + dnssvc = OpenServiceA(scm, "Dnscache", SERVICE_PAUSE_CONTINUE); + if (dnssvc == NULL) + { + MsgToEventLog(M_ERR, TEXT("ApplyDnsSettings: " + "OpenService call failed (%lu)"), GetLastError()); + goto out; + } + + SERVICE_STATUS status; + if (ControlService(dnssvc, SERVICE_CONTROL_PARAMCHANGE, &status) == 0) + { + MsgToEventLog(M_ERR, TEXT("ApplyDnsSettings: " + "ControlService call failed (%lu)"), GetLastError()); + goto out; + } + + res = TRUE; + +out: + CloseServiceHandle(dnssvc); + CloseServiceHandle(scm); + return res; +} + +/** + * Get the string interface UUID (with braces) for an interface alias name + * + * @param itf_name the interface alias name + * @param str pointer to the buffer the wide UUID is returned in + * @param len size of the str buffer in characters + * + * @return NO_ERROR on success, or the Windows error code for the failure */ static DWORD -SetDNSDomain(const wchar_t *if_name, const char *domain, undo_lists_t *lists) +InterfaceIdString(PCSTR itf_name, PWSTR str, size_t len) { - NET_IFINDEX if_index; + DWORD err; + GUID guid; + NET_LUID luid; + PWSTR iid_str = NULL; - DWORD err = ConvertInterfaceNameToIndex(if_name, &if_index); - if (err != ERROR_SUCCESS) + err = InterfaceLuid(itf_name, &luid); + if (err) { - return err; + MsgToEventLog(M_ERR, L"InterfaceIdString: " + "failed to convert itf alias '%s'", itf_name); + goto out; + } + err = ConvertInterfaceLuidToGuid(&luid, &guid); + if (err) + { + MsgToEventLog(M_ERR, L"InterfaceIdString: " + "Failed to convert itf '%s' LUID", itf_name); + goto out; } - wchar_t *wdomain = utf8to16(domain); /* utf8 to wide-char */ - if (!wdomain) + if (StringFromIID(&guid, &iid_str) != S_OK) { - return ERROR_OUTOFMEMORY; + MsgToEventLog(M_ERR, L"InterfaceIdString: " + "Failed to convert itf '%s' IID", itf_name); + err = ERROR_OUTOFMEMORY; + goto out; + } + if (wcslen(iid_str) + 1 > len) + { + err = ERROR_INVALID_PARAMETER; + goto out; } - /* free undo list if previously set */ - if (lists) + wcsncpy(str, iid_str, len); + +out: + if (iid_str) { - free(RemoveListItem(&(*lists)[undo_domain], CmpWString, (void *)if_name)); + CoTaskMemFree(iid_str); } + return err; +} - err = wmic_nicconfig_cmd(L"SetDNSDomain", if_index, wdomain); - - /* Add to undo list if domain is non-empty */ - if (err == 0 && wdomain[0] && lists) +/** + * Check for a valid search list in a certain key of the registry + * + * Valid means that a string value "SearchList" exists and that it + * contains one or more domains. We only check if the string contains + * a '.' character, but the main point is to prevent letting pass + * whitespace-only lists, so that check is good enough for that + * purpose. + * + * @param key HKEY in which to check for a valid search list + * + * @return BOOL to indicate if a valid search list has been found + */ +static BOOL +HasValidSearchList(HKEY key) +{ + char data[64]; + DWORD size = sizeof(data); + LSTATUS err = RegGetValueA(key, NULL, "SearchList", RRF_RT_REG_SZ, NULL, (PBYTE)data, &size); + if (!err || err == ERROR_MORE_DATA) { - wchar_t *tmp_name = _wcsdup(if_name); - if (!tmp_name || AddListItem(&(*lists)[undo_domain], tmp_name)) + data[sizeof(data) - 1] = '\0'; + if (strchr(data, '.') != NULL) { - free(tmp_name); - err = ERROR_OUTOFMEMORY; + return TRUE; + } + } + return FALSE; +} + +/** + * Find the registry key for storing the DNS domains for the VPN interface + * + * @param itf_name PCSTR that contains the alias name of the interface the domains + * are related to. If this is NULL the interface probing is skipped. + * @param gpol PBOOL to indicate if the key returned is the group policy hive + * @param key PHKEY in which the found registry key is returned in + * + * @return BOOL to indicate if a search list is already present at the location. + * If the key returned is INVALID_HANDLE_VALUE, this indicates an + * unrecoverable error. + * + * The correct location to add them is where a non-empty "SearchList" value exists, + * or in the interface configuration itself. However, the system-wide and then the + * group policy search lists overrule the previous one respectively, so we need to + * probe to find the effective list. + */ +static BOOL +GetDnsSearchListKey(PCSTR itf_name, PBOOL gpol, PHKEY key) +{ + LSTATUS err; + + *gpol = FALSE; + + /* Try the group policy search list */ + err = RegOpenKeyExA(HKEY_LOCAL_MACHINE, + "SOFTWARE\\Policies\\Microsoft\\Windows NT\\DNSClient", + 0, KEY_ALL_ACCESS, key); + if (!err) + { + if (HasValidSearchList(*key)) + { + *gpol = TRUE; + return TRUE; + } + RegCloseKey(*key); + } + + /* Try the system-wide search list */ + err = RegOpenKeyExA(HKEY_LOCAL_MACHINE, + "System\\CurrentControlSet\\Services\\TCPIP\\Parameters", + 0, KEY_ALL_ACCESS, key); + if (!err) + { + if (HasValidSearchList(*key)) + { + return TRUE; + } + RegCloseKey(*key); + } + + if (itf_name) + { + /* Always return the VPN interface key (if it exists) */ + WCHAR iid[64]; + DWORD iid_err = InterfaceIdString(itf_name, iid, _countof(iid)); + if (!iid_err) + { + HKEY itfs; + err = RegOpenKeyExA(HKEY_LOCAL_MACHINE, + "System\\CurrentControlSet\\Services\\TCPIP\\Parameters\\Interfaces", + 0, KEY_ALL_ACCESS, &itfs); + if (!err) + { + err = RegOpenKeyExW(itfs, iid, 0, KEY_ALL_ACCESS, key); + RegCloseKey(itfs); + if (!err) + { + return FALSE; /* No need to preserve the VPN itf search list */ + } + } } } - free(wdomain); + *key = INVALID_HANDLE_VALUE; + return FALSE; +} + +/** + * Check if a initial list had already been created + * + * @param key HKEY of the registry subkey to search in + * + * @return BOOL to indicate if the initial list is already present under key + */ +static BOOL +InitialSearchListExists(HKEY key) +{ + LSTATUS err; + + err = RegGetValueA(key, NULL, "InitialSearchList", RRF_RT_REG_SZ, NULL, NULL, NULL); + if (err) + { + if (err == ERROR_FILE_NOT_FOUND) + { + return FALSE; + } + MsgToEventLog(M_ERR, TEXT("InitialSearchListExists: " + "failed to get InitialSearchList (%lu)"), err); + } + + return TRUE; +} + +/** + * Prepare DNS domain "SearchList" registry value, so additional + * VPN domains can be added and its original state can be restored + * in case the system cannot clean up regularly. + * + * @param key registry subkey to store the list in + * @param list string of comma separated domains to use as the list + * + * @return boolean to indicate whether the list was stored successfully + */ +static BOOL +StoreInitialDnsSearchList(HKEY key, PCWSTR list) +{ + if (!list || wcslen(list) == 0) + { + MsgToEventLog(M_ERR, TEXT("StoreInitialDnsSearchList: empty search list")); + return FALSE; + } + + if (InitialSearchListExists(key)) + { + /* Initial list had already been stored */ + return TRUE; + } + + DWORD size = (wcslen(list) + 1) * sizeof(*list); + LSTATUS err = RegSetValueExW(key, L"InitialSearchList", 0, REG_SZ, (PBYTE)list, size); + if (err) + { + MsgToEventLog(M_ERR, TEXT("StoreInitialDnsSearchList: " + "failed to set InitialSearchList value (%lu)"), err); + return FALSE; + } + + return TRUE; +} + +/** + * Append domain suffixes to an existing search list + * + * @param key HKEY the list is stored at + * @param have_list BOOL to indicate if a search list already exists + * @param domains domain suffixes as comma separated string + * + * @return BOOL to indicate success or failure + */ +static BOOL +AddDnsSearchDomains(HKEY key, BOOL have_list, PCWSTR domains) +{ + LSTATUS err; + WCHAR list[4096]; + DWORD size = sizeof(list); + + if (have_list) + { + err = RegGetValueW(key, NULL, L"SearchList", RRF_RT_REG_SZ, NULL, list, &size); + if (err) + { + MsgToEventLog(M_SYSERR, TEXT("AddDnsSearchDomains: " + "could not get SearchList from registry (%lu)"), err); + return FALSE; + } + + if (!StoreInitialDnsSearchList(key, list)) + { + return FALSE; + } + + size_t listlen = (size / sizeof(list[0])) - 1; /* returned size is in bytes */ + size_t domlen = wcslen(domains); + if (listlen + domlen + 2 > _countof(list)) + { + MsgToEventLog(M_SYSERR, TEXT("AddDnsSearchDomains: " + "not enough space in list for search domains (len=%lu)"), + domlen); + return FALSE; + } + + /* Append to end of the search list */ + PWSTR pos = list + listlen; + *pos = ','; + wcsncpy(pos + 1, domains, domlen + 1); + } + else + { + wcsncpy(list, domains, wcslen(domains) + 1); + } + + size = (wcslen(list) + 1) * sizeof(list[0]); + err = RegSetValueExW(key, L"SearchList", 0, REG_SZ, (PBYTE)list, size); + if (err) + { + MsgToEventLog(M_SYSERR, TEXT("AddDnsSearchDomains: " + "could not set SearchList to registry (%lu)"), err); + return FALSE; + } + + return TRUE; +} + +/** + * Reset the DNS search list to its original value + * + * Looks for a "InitialSearchList" value as the one to reset to. + * If it doesn't exists, resets to empty, effectively disabling it. + * + * @param key HKEY of the location in the registry to reset + * + * @return BOOL to indicate if something was reset + */ +static BOOL +ResetDnsSearchDomains(HKEY key) +{ + LSTATUS err; + BOOL ret = FALSE; + WCHAR list[4096]; + DWORD size = sizeof(list); + + err = RegGetValueW(key, NULL, L"InitialSearchList", RRF_RT_REG_SZ, NULL, list, &size); + if (err) + { + if (err != ERROR_FILE_NOT_FOUND) + { + MsgToEventLog(M_SYSERR, TEXT("ResetDnsSearchDomains: " + "could not get InitialSearchList from registry (%lu)"), err); + } + goto out; + } + + size = (wcslen(list) + 1) * sizeof(list[0]); + err = RegSetValueExW(key, L"SearchList", 0, REG_SZ, (PBYTE)list, size); + if (err) + { + MsgToEventLog(M_SYSERR, TEXT("ResetDnsSearchDomains: " + "could not set SearchList in registry (%lu)"), err); + goto out; + } + + RegDeleteValueA(key, "InitialSearchList"); + ret = TRUE; + +out: + return ret; +} + +/** + * Remove domain suffixes from an existing search list + * + * @param key HKEY the list is stored at + * @param domains domain suffixes to remove as comma separated string + */ +static void +RemoveDnsSearchDomains(HKEY key, PCWSTR domains) +{ + LSTATUS err; + WCHAR list[4096]; + DWORD size = sizeof(list); + + err = RegGetValueW(key, NULL, L"SearchList", RRF_RT_REG_SZ, NULL, list, &size); + if (err) + { + MsgToEventLog(M_SYSERR, TEXT("RemoveDnsSearchDomains: " + "could not get SearchList from registry (%lu)"), err); + return; + } + + PWSTR dst = wcsstr(list, domains); + if (!dst) + { + MsgToEventLog(M_ERR, TEXT("RemoveDnsSearchDomains: " + "could not find domains in search list")); + return; + } + + /* Cut out domains from list */ + size_t domlen = wcslen(domains); + PCWSTR src = dst + domlen; + /* Also remove the leading comma, if there is one */ + dst = dst > list ? dst - 1 : dst; + wmemmove(dst, src, domlen); + + /* Now check if the shortened list equals the initial search list */ + WCHAR initial[4096]; + size = sizeof(initial); + err = RegGetValueW(key, NULL, L"InitialSearchList", RRF_RT_REG_SZ, NULL, initial, &size); + if (err) + { + MsgToEventLog(M_SYSERR, TEXT("RemoveDnsSearchDomains: " + "could not get InitialSearchList from registry (%lu)"), err); + return; + } + + /* If the search list is back to its initial state reset it */ + if (wcsncmp(list, initial, wcslen(list)) == 0) + { + ResetDnsSearchDomains(key); + } + else + { + size = (wcslen(list) + 1) * sizeof(list[0]); + err = RegSetValueExW(key, L"SearchList", 0, REG_SZ, (PBYTE)list, size); + if (err) + { + MsgToEventLog(M_SYSERR, TEXT("RemoveDnsSearchDomains: " + "could not set SearchList in registry (%lu)"), err); + } + } +} + +/** + * Removes DNS domains from a search list they were previously added to + * + * @param undo_data pointer to dns_domains_undo_data_t + */ +static void +UndoDnsSearchDomains(dns_domains_undo_data_t *undo_data) +{ + BOOL gpol; + HKEY dns_searchlist_key; + GetDnsSearchListKey(undo_data->itf_name, &gpol, &dns_searchlist_key); + if (dns_searchlist_key != INVALID_HANDLE_VALUE) + { + RemoveDnsSearchDomains(dns_searchlist_key, undo_data->domains); + RegCloseKey(dns_searchlist_key); + ApplyDnsSettings(gpol); + + free(undo_data->domains); + undo_data->domains = NULL; + } +} + +/** + * Add or remove DNS search domains + * + * @param itf_name alias name of the interface the domains are set for + * @param domains a comma separated list of domain name suffixes + * @param gpol PBOOL to indicate if group policy values were modified + * @param lists pointer to the undo lists + * + * @return NO_ERROR on success, an error status code otherwise + * + * If a SearchList is present in the registry already, the domains are added + * to that list. Otherwise the domains are added to the VPN interface specific list. + * A group policy search list takes precedence over a system-wide list, and that one + * itself takes precedence over interface specific ones. + * + * This function will remove previously set domains if the domains parameter + * is NULL or empty. + * + * The gpol value is only valid if the function returns no error. In the error + * case nothing is changed. + */ +static DWORD +SetDnsSearchDomains(PCSTR itf_name, PCSTR domains, PBOOL gpol, undo_lists_t *lists) +{ + DWORD err = ERROR_OUTOFMEMORY; + + HKEY list_key; + BOOL have_list = GetDnsSearchListKey(itf_name, gpol, &list_key); + if (list_key == INVALID_HANDLE_VALUE) + { + MsgToEventLog(M_SYSERR, TEXT("SetDnsSearchDomains: " + "could not get search list registry key")); + return ERROR_FILE_NOT_FOUND; + } + + /* Remove previously installed search domains */ + dns_domains_undo_data_t *undo_data = RemoveListItem(&(*lists)[undo_domains], CmpAny, NULL); + if (undo_data) + { + RemoveDnsSearchDomains(list_key, undo_data->domains); + free(undo_data->domains); + free(undo_data); + undo_data = NULL; + } + + /* If there are search domains, add them */ + if (domains && *domains) + { + wchar_t *wide_domains = utf8to16(domains); /* utf8 to wide-char */ + if (!wide_domains) + { + goto out; + } + + undo_data = malloc(sizeof(*undo_data)); + if (!undo_data) + { + free(wide_domains); + wide_domains = NULL; + goto out; + } + strncpy(undo_data->itf_name, itf_name, sizeof(undo_data->itf_name)); + undo_data->domains = wide_domains; + + if (AddDnsSearchDomains(list_key, have_list, wide_domains) == FALSE + || AddListItem(&(*lists)[undo_domains], undo_data) != NO_ERROR) + { + RemoveDnsSearchDomains(list_key, wide_domains); + free(wide_domains); + free(undo_data); + undo_data = NULL; + goto out; + } + } + + err = NO_ERROR; + +out: + RegCloseKey(list_key); return err; } @@ -1315,11 +1853,13 @@ if (msg->header.type == msg_del_dns_cfg) { + BOOL gpol = FALSE; if (msg->domains[0]) { - /* setting an empty domain removes any previous value */ - err = SetDNSDomain(wide_name, "", lists); + /* setting an empty domain list removes any previous value */ + err = SetDnsSearchDomains(msg->iface.name, NULL, &gpol, lists); } + ApplyDnsSettings(gpol); goto out; /* job done */ } @@ -1357,10 +1897,12 @@ } } + BOOL gpol = FALSE; if (msg->domains[0]) { - err = SetDNSDomain(wide_name, msg->domains, lists); + err = SetDnsSearchDomains(msg->iface.name, msg->domains, &gpol, lists); } + ApplyDnsSettings(gpol); out: free(wide_name); @@ -1751,12 +2293,14 @@ DeleteDNS(AF_INET6, item->data); break; - case undo_wins: - netsh_wins_cmd(L"delete", item->data, NULL); break; - case undo_domain: - SetDNSDomain(item->data, "", NULL); + case undo_domains: + UndoDnsSearchDomains(item->data); + break; + + case undo_wins: + netsh_wins_cmd(L"delete", item->data, NULL); break; case wfp_block: @@ -2260,6 +2804,34 @@ ServiceStartInteractive(dwArgc, lpszArgv); } +/** + * Clean up remains of previous sessions in registry. These remains can + * happen with unclean shutdowns or crashes and would interfere with + * normal operation of the system with and without active tunnels. + */ +static void +CleanupRegistry() +{ + HKEY key; + DWORD changed = 0; + + /* Clean up leftover DNS search list fragments */ + BOOL gpol_list; + GetDnsSearchListKey(NULL, &gpol_list, &key); + if (key != INVALID_HANDLE_VALUE) + { + if (ResetDnsSearchDomains(key)) + { + changed++; + } + RegCloseKey(key); + } + + if (changed) + { + ApplyDnsSettings(gpol_list); + } +} VOID WINAPI ServiceStartInteractive(DWORD dwArgc, LPTSTR *lpszArgv) @@ -2283,6 +2855,9 @@ status.dwWaitHint = 3000; ReportStatusToSCMgr(service, &status); + /* Clean up potentially left over registry values */ + CleanupRegistry(); + /* Read info from registry in key HKLM\SOFTWARE\OpenVPN */ error = GetOpenvpnSettings(&settings); if (error != ERROR_SUCCESS)