From patchwork Wed Mar 23 03:34:52 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Heiko Hund X-Patchwork-Id: 2350 Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director11.mail.ord1d.rsapps.net ([172.30.191.6]) by backend41.mail.ord1d.rsapps.net with LMTP id MMHUILcwO2LdewAAqwncew (envelope-from ) for ; Wed, 23 Mar 2022 10:37:43 -0400 Received: from proxy3.mail.ord1d.rsapps.net ([172.30.191.6]) by director11.mail.ord1d.rsapps.net with LMTP id gArLLLcwO2ISPAAAvGGmqA (envelope-from ) for ; Wed, 23 Mar 2022 10:37:43 -0400 Received: from smtp8.gate.ord1d ([172.30.191.6]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy3.mail.ord1d.rsapps.net with LMTPS id 2O9WLLcwO2JqSQAA7WKfLA (envelope-from ) for ; Wed, 23 Mar 2022 10:37:43 -0400 X-Spam-Threshold: 95 X-Spam-Score: 0 X-Spam-Flag: NO X-Virus-Scanned: OK X-Orig-To: openvpnslackdevel@openvpn.net X-Originating-Ip: [216.105.38.7] Authentication-Results: smtp8.gate.ord1d.rsapps.net; iprev=pass policy.iprev="216.105.38.7"; spf=pass smtp.mailfrom="openvpn-devel-bounces@lists.sourceforge.net" smtp.helo="lists.sourceforge.net"; dkim=fail (signature verification failed) header.d=sourceforge.net; dkim=fail (signature verification failed) header.d=sf.net; dmarc=none (p=nil; dis=none) header.from=ist.eigentlich.net X-Suspicious-Flag: YES X-Classification-ID: cacd0b80-aab6-11ec-9ce4-5254001e5a60-1-1 Received: from [216.105.38.7] ([216.105.38.7:59972] helo=lists.sourceforge.net) by smtp8.gate.ord1d.rsapps.net (envelope-from ) (ecelerity 4.2.38.62370 r(:)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id B4/EF-02350-4B03B326; Wed, 23 Mar 2022 10:37:41 -0400 Received: from [127.0.0.1] (helo=sfs-ml-4.v29.lw.sourceforge.com) by sfs-ml-4.v29.lw.sourceforge.com with esmtp (Exim 4.94.2) (envelope-from ) id 1nX1wy-0004DZ-Nd; Wed, 23 Mar 2022 14:35:12 +0000 Received: from [172.30.20.202] (helo=mx.sourceforge.net) by sfs-ml-4.v29.lw.sourceforge.com with esmtps (TLS1.2) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1nX1wx-0004DT-5p for openvpn-devel@lists.sourceforge.net; Wed, 23 Mar 2022 14:35:10 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=Content-Transfer-Encoding:MIME-Version:References: In-Reply-To:Message-Id:Date:Subject:To:From:Sender:Reply-To:Cc:Content-Type: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=IBEl/Pvz4u9o2BGd2y3WVZFySoTefKyqpCFidFmuQ1E=; b=iae1nS+t0+QP5k+jMNiiLohRf4 GDN51cbpDcpymYjQcpzHN3q1wjMmAiMGfL0zL4L9LWhjXWxJhuY9nJJ/aVFUwjWt+7Bcnld4lrX+2 THzk+M3utJ1Yrs/yo0jvCF0znQKEDBJZFVrHm7XQsr34jlAZx4m5TA0hB+OHY3EeXZUI=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=Content-Transfer-Encoding:MIME-Version:References:In-Reply-To:Message-Id: Date:Subject:To:From:Sender:Reply-To:Cc:Content-Type:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=IBEl/Pvz4u9o2BGd2y3WVZFySoTefKyqpCFidFmuQ1E=; b=TCC8Rl6dDpwLznbROdZbc53Frj rbtXFR6tN/F7hlwAExSfkl0qQmziR0aSyP6S7Gx8tD2f+SSy1VjjZhDiNAZZJLxNh23kmhBavynlv Ki/Wp693f0/sQuZX/Sg+75qwA4/Vo4trDR5ivY03uv4UueZtLmXAyMFaTLQIzC9+GEWI=; Received: from exit0.net ([85.25.119.185]) by sfi-mx-1.v28.lw.sourceforge.com with esmtps (TLS1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.94.2) id 1nX24o-009Ym2-EJ for openvpn-devel@lists.sourceforge.net; Wed, 23 Mar 2022 14:35:10 +0000 Received: from coruscant.fritz.box (unknown [87.123.245.243]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by exit0.net (Postfix) with ESMTPSA id A10366480157 for ; Wed, 23 Mar 2022 15:34:56 +0100 (CET) From: Heiko Hund To: openvpn-devel@lists.sourceforge.net Date: Wed, 23 Mar 2022 15:34:52 +0100 Message-Id: <20220323143452.1100446-1-heiko@ist.eigentlich.net> X-Mailer: git-send-email 2.32.0 In-Reply-To: <20220308230621.1532809-1-heiko@ist.eigentlich.net> References: <20220308230621.1532809-1-heiko@ist.eigentlich.net> MIME-Version: 1.0 X-Spam-Report: Spam detection software, running on the system "util-spamd-1.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: As a first step towards DNS configuration in openvpn and a unified way to push DNS related settings to clients in v2 and v3, this commit adds support for parsing the new --dns option. Later commits wi [...] Content analysis details: (0.2 points, 6.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- 0.2 HEADER_FROM_DIFFERENT_DOMAINS From and EnvelopeFrom 2nd level mail domains are different -0.0 SPF_HELO_PASS SPF: HELO matches SPF record -0.0 SPF_PASS SPF: sender matches SPF record X-Headers-End: 1nX24o-009Ym2-EJ Subject: [Openvpn-devel] [PATCH v3] add support for --dns option 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: , Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox As a first step towards DNS configuration in openvpn and a unified way to push DNS related settings to clients in v2 and v3, this commit adds support for parsing the new --dns option. Later commits will add support for setting up DNS on different platforms. For now, --dns and DNS related --dhcp-option can be used together for smoother transition. Settings from --dns will override ones --dhcp-option where applicable. For detailed information about the option consult the documentation in this commit. Signed-off-by: Heiko Hund Acked-by: Gert Doering --- doc/man-sections/client-options.rst | 59 ++++ doc/man-sections/script-options.rst | 19 ++ doc/man-sections/server-options.rst | 2 +- src/openvpn/Makefile.am | 1 + src/openvpn/dns.c | 510 ++++++++++++++++++++++++++++ src/openvpn/dns.h | 164 +++++++++ src/openvpn/openvpn.vcxproj | 4 +- src/openvpn/openvpn.vcxproj.filters | 8 +- src/openvpn/options.c | 221 ++++++++++++ src/openvpn/options.h | 7 + src/openvpn/push.c | 4 + src/openvpn/socket.c | 11 + src/openvpn/socket.h | 2 + 13 files changed, 1009 insertions(+), 3 deletions(-) create mode 100644 src/openvpn/dns.c create mode 100644 src/openvpn/dns.h diff --git a/doc/man-sections/client-options.rst b/doc/man-sections/client-options.rst index e53b5262..8e0e4f18 100644 --- a/doc/man-sections/client-options.rst +++ b/doc/man-sections/client-options.rst @@ -154,6 +154,65 @@ configuration. --connect-timeout n See ``--server-poll-timeout``. +--dns args + Client DNS configuration to be used with the connection. + + Valid syntaxes: + :: + + dns search-domains domain [domain ...] + dns server n address addr[:port] [addr[:port]] + dns server n resolve-domains|exclude-domains domain [domain ...] + dns server n dnssec yes|optional|no + dns server n transport DoH|DoT|plain + dns server n sni server-name + + The ``--dns search-domains`` directive takes one or more domain names + to be added as DNS domain suffixes. If it is repeated multiple times within + a configuration the domains are appended, thus e.g. domain names pushed by + a server will amend locally defined ones. + + The ``--dns server`` directive is used to configure DNS server ``n``. + The server id ``n`` must be a value between -128 and 127. For pushed + DNS server options it must be between 0 and 127. The server id is used + to group options and also for ordering the list of configured DNS servers; + lower numbers come first. DNS servers being pushed to a client replace + already configured DNS servers with the same server id. + + The ``address`` option configures the IPv4 and / or IPv6 address of + the DNS server. Optionally a port can be appended after a colon. IPv6 + addresses need to be enclosed in brackets if a port is appended. + + The ``resolve-domains`` and ``exclude-domains`` options take one or + more DNS domains which are explicitly resolved or explicitly not resolved + by a server. Only one of the options can be configured for a server. + ``resolve-domains`` is used to define a split-dns setup, where only + given domains are resolved by a server. ``exclude-domains`` is used to + define domains which will never be resolved by a server (e.g. domains + which can only be resolved locally). Systems which do not support fine + grained DNS domain configuration, will ignore these settings. + + The ``dnssec`` option is used to configure validation of DNSSEC records. + While the exact semantics may differ for resolvers on different systems, + ``yes`` likely makes validation mandatory, ``no`` disables it, and ``optional`` + uses it opportunistically. + + The ``transport`` option enables DNS-over-HTTPS (``DoH``) or DNS-over-TLS (``DoT``) + for a DNS server. The ``sni`` option can be used with them to specify the + ``server-name`` for TLS server name indication. + + Each server has to have at least one address configured for a configuration + to be valid. All the other options can be omitted. + + Note that not all options may be supported on all platforms. As soon support + for different systems is implemented, information will be added here how + unsupported options are treated. + + The ``--dns`` option will eventually obsolete the ``--dhcp-option`` directive. + Until then it will replace configuration at the places ``--dhcp-option`` puts it, + so that ``--dns`` overrides ``--dhcp-option``. Thus, ``--dns`` can be used today + to migrate from ``--dhcp-option``. + --explicit-exit-notify n In UDP client mode or point-to-point mode, send server/peer an exit notification if tunnel is restarted or OpenVPN process is exited. In diff --git a/doc/man-sections/script-options.rst b/doc/man-sections/script-options.rst index 77877a5d..6be0686d 100644 --- a/doc/man-sections/script-options.rst +++ b/doc/man-sections/script-options.rst @@ -588,6 +588,25 @@ instances. netsh.exe calls which sometimes just do not work right with interface names). Set prior to ``--up`` or ``--down`` script execution. +:code:`dns_*` + The ``--dns`` configuration options will be made available to script + execution through this set of environment variables. Variables appear + only if the corresponding option has a value assigned. For the semantics + of each individual variable, please refer to the documentation for ``--dns``. + + :: + + dns_search_domain_{n} + dns_server_{n}_address4 + dns_server_{n}_port4 + dns_server_{n}_address6 + dns_server_{n}_port6 + dns_server_{n}_resolve_domain_{m} + dns_server_{n}_exclude_domain_{m} + dns_server_{n}_dnssec + dns_server_{n}_transport + dns_server_{n}_sni + :code:`foreign_option_{n}` An option pushed via ``--push`` to a client which does not natively support it, such as ``--dhcp-option`` on a non-Windows system, will be diff --git a/doc/man-sections/server-options.rst b/doc/man-sections/server-options.rst index 8a030294..08ee7bd3 100644 --- a/doc/man-sections/server-options.rst +++ b/doc/man-sections/server-options.rst @@ -412,7 +412,7 @@ fast hardware. SSL/TLS authentication must be used in this mode. This is a partial list of options which can currently be pushed: ``--route``, ``--route-gateway``, ``--route-delay``, - ``--redirect-gateway``, ``--ip-win32``, ``--dhcp-option``, + ``--redirect-gateway``, ``--ip-win32``, ``--dhcp-option``, ``--dns``, ``--inactive``, ``--ping``, ``--ping-exit``, ``--ping-restart``, ``--setenv``, ``--auth-token``, ``--persist-key``, ``--persist-tun``, ``--echo``, ``--comp-lzo``, ``--socket-flags``, ``--sndbuf``, diff --git a/src/openvpn/Makefile.am b/src/openvpn/Makefile.am index 279ee94d..fc22feb9 100644 --- a/src/openvpn/Makefile.am +++ b/src/openvpn/Makefile.am @@ -54,6 +54,7 @@ openvpn_SOURCES = \ crypto_openssl.c crypto_openssl.h \ crypto_mbedtls.c crypto_mbedtls.h \ dhcp.c dhcp.h \ + dns.c dns.h \ env_set.c env_set.h \ errlevel.h \ error.c error.h \ diff --git a/src/openvpn/dns.c b/src/openvpn/dns.c new file mode 100644 index 00000000..3c31a14e --- /dev/null +++ b/src/openvpn/dns.c @@ -0,0 +1,510 @@ +/* + * OpenVPN -- An application to securely tunnel IP networks + * over a single UDP port, with support for SSL/TLS-based + * session authentication and key exchange, + * packet encryption, packet authentication, and + * packet compression. + * + * Copyright (C) 2022 OpenVPN Inc + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#elif defined(_MSC_VER) +#include "config-msvc.h" +#endif + +#include "syshead.h" + +#include "dns.h" +#include "socket.h" + +/** + * Parses a string as port and stores it + * + * @param port Pointer to in_port_t where the port value is stored + * @param addr Port number as string + * @return True if parsing was successful + */ +static bool +dns_server_port_parse(in_port_t *port, char *port_str) +{ + char *endptr; + errno = 0; + unsigned long tmp = strtoul(port_str, &endptr, 10); + if (errno || *endptr != '\0' || tmp == 0 || tmp > UINT16_MAX) + { + return false; + } + *port = (in_port_t)tmp; + return true; +} + +bool +dns_server_addr_parse(struct dns_server *server, const char *addr) +{ + if (!addr) + { + return false; + } + + char addrcopy[INET6_ADDRSTRLEN] = {0}; + size_t copylen = 0; + in_port_t port = 0; + int af; + + char *first_colon = strchr(addr, ':'); + char *last_colon = strrchr(addr, ':'); + + if (!first_colon || first_colon == last_colon) + { + /* IPv4 address with optional port, e.g. 1.2.3.4 or 1.2.3.4:853 */ + if (last_colon) + { + if (last_colon == addr || !dns_server_port_parse(&port, last_colon + 1)) + { + return false; + } + copylen = first_colon - addr; + } + af = AF_INET; + } + else + { + /* IPv6 address with optional port, e.g. ab::cd or [ab::cd]:853 */ + if (addr[0] == '[') + { + addr += 1; + char *bracket = last_colon - 1; + if (*bracket != ']' || bracket == addr || !dns_server_port_parse(&port, last_colon + 1)) + { + return false; + } + copylen = bracket - addr; + } + af = AF_INET6; + } + + /* Copy the address part into a temporary buffer and use that */ + if (copylen) + { + if (copylen >= sizeof(addrcopy)) + { + return false; + } + strncpy(addrcopy, addr, copylen); + addr = addrcopy; + } + + struct addrinfo *ai = NULL; + if (openvpn_getaddrinfo(0, addr, NULL, 0, NULL, af, &ai) != 0) + { + return false; + } + + if (ai->ai_family == AF_INET) + { + struct sockaddr_in *sin = (struct sockaddr_in *)ai->ai_addr; + server->addr4_defined = true; + server->addr4.s_addr = ntohl(sin->sin_addr.s_addr); + server->port4 = port; + } + else + { + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)ai->ai_addr; + server->addr6_defined = true; + server->addr6 = sin6->sin6_addr; + server->port6 = port; + } + + freeaddrinfo(ai); + return true; +} + +void +dns_domain_list_append(struct dns_domain **entry, char **domains, struct gc_arena *gc) +{ + /* Fast forward to the end of the list */ + while (*entry) + { + entry = &((*entry)->next); + } + + /* Append all domains to the end of the list */ + while (*domains) + { + ALLOC_OBJ_CLEAR_GC(*entry, struct dns_domain, gc); + struct dns_domain *new = *entry; + new->name = *domains++; + entry = &new->next; + } +} + +bool +dns_server_priority_parse(long *priority, const char *str, bool pulled) +{ + char *endptr; + const long min = pulled ? 0 : INT8_MIN; + const long max = INT8_MAX; + long prio = strtol(str, &endptr, 10); + if (*endptr != '\0' || prio < min || prio > max) + { + return false; + } + *priority = prio; + return true; +} + +struct dns_server* +dns_server_get(struct dns_server **entry, long priority, struct gc_arena *gc) +{ + struct dns_server *obj = *entry; + while (true) + { + if (!obj || obj->priority > priority) + { + ALLOC_OBJ_CLEAR_GC(*entry, struct dns_server, gc); + (*entry)->next = obj; + (*entry)->priority = priority; + return *entry; + } + else if (obj->priority == priority) + { + return obj; + } + entry = &obj->next; + obj = *entry; + } +} + +bool +dns_options_verify(int msglevel, const struct dns_options *o) +{ + const struct dns_server *server = + o->servers ? o->servers : o->servers_prepull; + while (server) + { + if (!server->addr4_defined && !server->addr6_defined) + { + msg(msglevel, "ERROR: dns server %ld does not have an address assigned", server->priority); + return false; + } + server = server->next; + } + return true; +} + +static struct dns_domain* +clone_dns_domains(const struct dns_domain *domain, struct gc_arena* gc) +{ + struct dns_domain *new_list = NULL; + struct dns_domain **new_entry = &new_list; + + while (domain) + { + ALLOC_OBJ_CLEAR_GC(*new_entry, struct dns_domain, gc); + struct dns_domain *new_domain = *new_entry; + *new_domain = *domain; + new_entry = &new_domain->next; + domain = domain->next; + } + + return new_list; +} + +static struct dns_server* +clone_dns_servers(const struct dns_server *server, struct gc_arena* gc) +{ + struct dns_server *new_list = NULL; + struct dns_server **new_entry = &new_list; + + while (server) + { + ALLOC_OBJ_CLEAR_GC(*new_entry, struct dns_server, gc); + struct dns_server *new_server = *new_entry; + *new_server = *server; + new_server->domains = clone_dns_domains(server->domains, gc); + new_entry = &new_server->next; + server = server->next; + } + + return new_list; +} + +struct dns_options +clone_dns_options(const struct dns_options o, struct gc_arena* gc) +{ + struct dns_options clone; + memset(&clone, 0, sizeof(clone)); + clone.search_domains = clone_dns_domains(o.search_domains, gc); + clone.servers = clone_dns_servers(o.servers, gc); + clone.servers_prepull = clone_dns_servers(o.servers_prepull, gc); + return clone; +} + +void +dns_options_preprocess_pull(struct dns_options *o) +{ + o->servers_prepull = o->servers; + o->servers = NULL; +} + +void +dns_options_postprocess_pull(struct dns_options *o) +{ + struct dns_server **entry = &o->servers; + struct dns_server *server = *entry; + struct dns_server *server_pp = o->servers_prepull; + + while (server && server_pp) + { + if (server->priority > server_pp->priority) + { + /* Merge static server in front of pulled one */ + struct dns_server *next_pp = server_pp->next; + server_pp->next = server; + *entry = server_pp; + server = *entry; + server_pp = next_pp; + } + else if (server->priority == server_pp->priority) + { + /* Pulled server overrides static one */ + server_pp = server_pp->next; + } + entry = &server->next; + server = *entry; + } + + /* Append remaining local servers */ + if (server_pp) + { + *entry = server_pp; + } + + o->servers_prepull = NULL; +} + +static const char * +dnssec_value(const enum dns_security dnssec) +{ + switch (dnssec) + { + case DNS_SECURITY_YES: + return "yes"; + case DNS_SECURITY_OPTIONAL: + return "optional"; + case DNS_SECURITY_NO: + return "no"; + default: + return "unset"; + } +} + +static const char * +transport_value(const enum dns_server_transport transport) +{ + switch (transport) + { + case DNS_TRANSPORT_HTTPS: + return "DoH"; + case DNS_TRANSPORT_TLS: + return "DoT"; + case DNS_TRANSPORT_PLAIN: + return "plain"; + default: + return "unset"; + } +} + +static void +setenv_dns_option(struct env_set *es, + const char *format, int i, int j, + const char *value) +{ + char name[64]; + bool name_ok = false; + + if (j < 0) + { + name_ok = openvpn_snprintf(name, sizeof(name), format, i); + } + else + { + name_ok = openvpn_snprintf(name, sizeof(name), format, i, j); + } + + if (!name_ok) + { + msg(M_WARN, "WARNING: dns option setenv name buffer overflow"); + } + + setenv_str(es, name, value); +} + +void +setenv_dns_options(const struct dns_options *o, struct env_set *es) +{ + struct gc_arena gc = gc_new(); + const struct dns_server *s; + const struct dns_domain *d; + int i, j; + + for (i = 1, d = o->search_domains; d != NULL; i++, d = d->next) + { + setenv_dns_option(es, "dns_search_domain_%d", i, -1, d->name); + } + + for (i = 1, s = o->servers; s != NULL; i++, s = s->next) + { + if (s->addr4_defined) + { + setenv_dns_option(es, "dns_server_%d_address4", i, -1, + print_in_addr_t(s->addr4.s_addr, 0, &gc)); + } + if (s->port4) + { + setenv_dns_option(es, "dns_server_%d_port4", i, -1, + print_in_port_t(s->port4, &gc)); + } + + if (s->addr6_defined) + { + setenv_dns_option(es, "dns_server_%d_address6", i, -1, + print_in6_addr(s->addr6, 0, &gc)); + } + if (s->port6) + { + setenv_dns_option(es, "dns_server_%d_port6", i, -1, + print_in_port_t(s->port6, &gc)); + } + + if (s->domains) + { + const char* format = s->domain_type == DNS_RESOLVE_DOMAINS ? + "dns_server_%d_resolve_domain_%d" : "dns_server_%d_exclude_domain_%d"; + for (j = 1, d = s->domains; d != NULL; j++, d = d->next) + { + setenv_dns_option(es, format, i, j, d->name); + } + } + + if (s->dnssec) + { + setenv_dns_option(es, "dns_server_%d_dnssec", i, -1, + dnssec_value(s->dnssec)); + } + + if (s->transport) + { + setenv_dns_option(es, "dns_server_%d_transport", i, -1, + transport_value(s->transport)); + } + if (s->sni) + { + setenv_dns_option(es, "dns_server_%d_sni", i, -1, s->sni); + } + } + + gc_free(&gc); +} + +void +show_dns_options(const struct dns_options *o) +{ + struct gc_arena gc = gc_new(); + + int i = 1; + struct dns_server *server = o->servers_prepull ? o->servers_prepull : o->servers; + while (server) + { + msg(D_SHOW_PARMS, " DNS server #%d:", i++); + + if (server->addr4_defined) + { + const char *addr = print_in_addr_t(server->addr4.s_addr, 0, &gc); + if (server->port4) + { + const char *port = print_in_port_t(server->port4, &gc); + msg(D_SHOW_PARMS, " address4 = %s:%s", addr, port); + } + else + { + msg(D_SHOW_PARMS, " address4 = %s", addr); + } + } + if (server->addr6_defined) + { + const char *addr = print_in6_addr(server->addr6, 0, &gc); + if (server->port6) + { + const char *port = print_in_port_t(server->port6, &gc); + msg(D_SHOW_PARMS, " address6 = [%s]:%s", addr, port); + } + else + { + msg(D_SHOW_PARMS, " address6 = %s", addr); + } + } + + if (server->dnssec) + { + msg(D_SHOW_PARMS, " dnssec = %s", dnssec_value(server->dnssec)); + } + + if (server->transport) + { + msg(D_SHOW_PARMS, " transport = %s", transport_value(server->transport)); + } + if (server->sni) + { + msg(D_SHOW_PARMS, " sni = %s", server->sni); + } + + struct dns_domain *domain = server->domains; + if (domain) + { + if (server->domain_type == DNS_RESOLVE_DOMAINS) + { + msg(D_SHOW_PARMS, " resolve domains:"); + } + else + { + msg(D_SHOW_PARMS, " exclude domains:"); + } + while(domain) + { + msg(D_SHOW_PARMS, " %s", domain->name); + domain = domain->next; + } + } + + server = server->next; + } + + struct dns_domain *search_domain = o->search_domains; + if (search_domain) + { + msg(D_SHOW_PARMS, " DNS search domains:"); + while(search_domain) + { + msg(D_SHOW_PARMS, " %s", search_domain->name); + search_domain = search_domain->next; + } + } + + gc_free(&gc); +} diff --git a/src/openvpn/dns.h b/src/openvpn/dns.h new file mode 100644 index 00000000..1c6bfc6f --- /dev/null +++ b/src/openvpn/dns.h @@ -0,0 +1,164 @@ +/* + * OpenVPN -- An application to securely tunnel IP networks + * over a single UDP port, with support for SSL/TLS-based + * session authentication and key exchange, + * packet encryption, packet authentication, and + * packet compression. + * + * Copyright (C) 2022 OpenVPN Inc + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef DNS_H +#define DNS_H + +#include "buffer.h" +#include "env_set.h" + +enum dns_domain_type { + DNS_DOMAINS_UNSET, + DNS_RESOLVE_DOMAINS, + DNS_EXCLUDE_DOMAINS +}; + +enum dns_security { + DNS_SECURITY_UNSET, + DNS_SECURITY_NO, + DNS_SECURITY_YES, + DNS_SECURITY_OPTIONAL +}; + +enum dns_server_transport { + DNS_TRANSPORT_UNSET, + DNS_TRANSPORT_PLAIN, + DNS_TRANSPORT_HTTPS, + DNS_TRANSPORT_TLS +}; + +struct dns_domain { + struct dns_domain *next; + const char *name; +}; + +struct dns_server { + struct dns_server *next; + long priority; + bool addr4_defined; + bool addr6_defined; + struct in_addr addr4; + struct in6_addr addr6; + in_port_t port4; + in_port_t port6; + struct dns_domain *domains; + enum dns_domain_type domain_type; + enum dns_security dnssec; + enum dns_server_transport transport; + const char *sni; +}; + +struct dns_options { + struct dns_domain *search_domains; + struct dns_server *servers_prepull; + struct dns_server *servers; + struct gc_arena gc; +}; + +/** + * Parses a string DNS server priority and validates it. + * + * @param priority Pointer to where the priority should be stored + * @param str Priority string to parse + * @param pulled Whether this was pulled from a server + * @return True if priority in string is valid + */ +bool dns_server_priority_parse(long *priority, const char *str, bool pulled); + +/** + * Find or create DNS server with priority in a linked list. + * The list is ordered by priority. + * + * @param entry Address of the first list entry pointer + * @param priority Priority of the DNS server to find / create + * @param gc The gc new list items should be allocated in + */ +struct dns_server* dns_server_get(struct dns_server **entry, long priority, struct gc_arena *gc); + +/** + * Appends DNS domain parameters to a linked list. + * + * @param entry Address of the first list entry pointer + * @param domains Address of the first domain parameter + * @param gc The gc the new list items should be allocated in + */ +void dns_domain_list_append(struct dns_domain **entry, char **domains, struct gc_arena *gc); + +/** + * Parses a string IPv4 or IPv6 address and optional colon separated port, + * into a in_addr or in6_addr respectively plus a in_port_t port. + * + * @param server Pointer to DNS server the address is parsed for + * @param addr Address as string + * @return True if parsing was successful + */ +bool dns_server_addr_parse(struct dns_server *server, const char *addr); + +/** + * Checks validity of DNS options + * + * @param msglevel The message level to log errors with + * @param o Pointer to the DNS options to validate + * @return True if no error was found + */ +bool dns_options_verify(int msglevel, const struct dns_options *o); + +/** + * Makes a deep copy of the passed DNS options. + * + * @param o Pointer to the DNS options to clone + * @param gc Pointer to the gc_arena to use for the clone + * @return The dns_options clone + */ +struct dns_options clone_dns_options(const struct dns_options o, struct gc_arena *gc); + +/** + * Saves and resets the server options, so that pulled ones don't mix in. + * + * @param o Pointer to the DNS options to modify + */ +void dns_options_preprocess_pull(struct dns_options *o); + +/** + * Merges pulled DNS servers with static ones into an ordered list. + * + * @param o Pointer to the DNS options to modify + */ +void dns_options_postprocess_pull(struct dns_options *o); + +/** + * Puts the DNS options into an environment set. + * + * @param o Pointer to the DNS options to set + * @param es Pointer to the env_set to set the options into + */ +void setenv_dns_options(const struct dns_options *o, struct env_set *es); + +/** + * Prints configured DNS options. + * + * @param o Pointer to the DNS options to print + */ +void show_dns_options(const struct dns_options *o); + +#endif diff --git a/src/openvpn/openvpn.vcxproj b/src/openvpn/openvpn.vcxproj index 1d32c41f..a43cbd81 100644 --- a/src/openvpn/openvpn.vcxproj +++ b/src/openvpn/openvpn.vcxproj @@ -269,6 +269,7 @@ + @@ -352,6 +353,7 @@ + @@ -444,4 +446,4 @@ - \ No newline at end of file + diff --git a/src/openvpn/openvpn.vcxproj.filters b/src/openvpn/openvpn.vcxproj.filters index 4cf0bb00..abc45225 100644 --- a/src/openvpn/openvpn.vcxproj.filters +++ b/src/openvpn/openvpn.vcxproj.filters @@ -39,6 +39,9 @@ Source Files + + Source Files + Source Files @@ -296,6 +299,9 @@ Header Files + + Header Files + Header Files @@ -535,4 +541,4 @@ Resource Files - \ No newline at end of file + diff --git a/src/openvpn/options.c b/src/openvpn/options.c index 7d7b8dc1..d6d94f60 100644 --- a/src/openvpn/options.c +++ b/src/openvpn/options.c @@ -499,6 +499,16 @@ static const char usage_message[] = " ignore or reject causes the option to be allowed, removed or\n" " rejected with error. May be specified multiple times, and\n" " each filter is applied in the order of appearance.\n" + "--dns server