LCOV - code coverage report
Current view: top level - resolve - resolved-resolv-conf.c (source / functions) Hit Total Coverage
Test: systemd_full.info Lines: 0 179 0.0 %
Date: 2019-08-23 13:36:53 Functions: 0 8 0.0 %
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 0 158 0.0 %

           Branch data     Line data    Source code
       1                 :            : /* SPDX-License-Identifier: LGPL-2.1+ */
       2                 :            : 
       3                 :            : #include <resolv.h>
       4                 :            : #include <sys/stat.h>
       5                 :            : #include <sys/types.h>
       6                 :            : #include <unistd.h>
       7                 :            : 
       8                 :            : #include "alloc-util.h"
       9                 :            : #include "dns-domain.h"
      10                 :            : #include "fd-util.h"
      11                 :            : #include "fileio.h"
      12                 :            : #include "ordered-set.h"
      13                 :            : #include "resolved-conf.h"
      14                 :            : #include "resolved-dns-server.h"
      15                 :            : #include "resolved-resolv-conf.h"
      16                 :            : #include "string-util.h"
      17                 :            : #include "strv.h"
      18                 :            : #include "tmpfile-util-label.h"
      19                 :            : 
      20                 :            : /* A resolv.conf file containing the DNS server and domain data we learnt from uplink, i.e. the full uplink data */
      21                 :            : #define PRIVATE_UPLINK_RESOLV_CONF "/run/systemd/resolve/resolv.conf"
      22                 :            : 
      23                 :            : /* A resolv.conf file containing the domain data we learnt from uplink, but our own DNS server address. */
      24                 :            : #define PRIVATE_STUB_RESOLV_CONF "/run/systemd/resolve/stub-resolv.conf"
      25                 :            : 
      26                 :            : /* A static resolv.conf file containing no domains, but only our own DNS server address */
      27                 :            : #define PRIVATE_STATIC_RESOLV_CONF ROOTLIBEXECDIR "/resolv.conf"
      28                 :            : 
      29                 :          0 : int manager_check_resolv_conf(const Manager *m) {
      30                 :            :         const char *path;
      31                 :            :         struct stat st;
      32                 :            :         int r;
      33                 :            : 
      34         [ #  # ]:          0 :         assert(m);
      35                 :            : 
      36                 :            :         /* This warns only when our stub listener is disabled and /etc/resolv.conf is a symlink to
      37                 :            :          * PRIVATE_STATIC_RESOLV_CONF or PRIVATE_STUB_RESOLV_CONF. */
      38                 :            : 
      39         [ #  # ]:          0 :         if (m->dns_stub_listener_mode != DNS_STUB_LISTENER_NO)
      40                 :          0 :                 return 0;
      41                 :            : 
      42                 :          0 :         r = stat("/etc/resolv.conf", &st);
      43         [ #  # ]:          0 :         if (r < 0) {
      44         [ #  # ]:          0 :                 if (errno == ENOENT)
      45                 :          0 :                         return 0;
      46                 :            : 
      47         [ #  # ]:          0 :                 return log_warning_errno(errno, "Failed to stat /etc/resolv.conf: %m");
      48                 :            :         }
      49                 :            : 
      50         [ #  # ]:          0 :         FOREACH_STRING(path,
      51                 :            :                        PRIVATE_STUB_RESOLV_CONF,
      52                 :            :                        PRIVATE_STATIC_RESOLV_CONF) {
      53                 :            : 
      54                 :            :                 struct stat own;
      55                 :            : 
      56                 :            :                 /* Is it symlinked to our own uplink file? */
      57         [ #  # ]:          0 :                 if (stat(path, &own) >= 0 &&
      58         [ #  # ]:          0 :                     st.st_dev == own.st_dev &&
      59         [ #  # ]:          0 :                     st.st_ino == own.st_ino) {
      60         [ #  # ]:          0 :                         log_warning("DNSStubListener= is disabled, but /etc/resolv.conf is a symlink to %s "
      61                 :            :                                     "which expects DNSStubListener= to be enabled.", path);
      62                 :          0 :                         return -EOPNOTSUPP;
      63                 :            :                 }
      64                 :            :         }
      65                 :            : 
      66                 :          0 :         return 0;
      67                 :            : }
      68                 :            : 
      69                 :          0 : static bool file_is_our_own(const struct stat *st) {
      70                 :            :         const char *path;
      71                 :            : 
      72         [ #  # ]:          0 :         assert(st);
      73                 :            : 
      74         [ #  # ]:          0 :         FOREACH_STRING(path,
      75                 :            :                        PRIVATE_UPLINK_RESOLV_CONF,
      76                 :            :                        PRIVATE_STUB_RESOLV_CONF,
      77                 :            :                        PRIVATE_STATIC_RESOLV_CONF) {
      78                 :            : 
      79                 :            :                 struct stat own;
      80                 :            : 
      81                 :            :                 /* Is it symlinked to our own uplink file? */
      82         [ #  # ]:          0 :                 if (stat(path, &own) >= 0 &&
      83         [ #  # ]:          0 :                     st->st_dev == own.st_dev &&
      84         [ #  # ]:          0 :                     st->st_ino == own.st_ino)
      85                 :          0 :                         return true;
      86                 :            :         }
      87                 :            : 
      88                 :          0 :         return false;
      89                 :            : }
      90                 :            : 
      91                 :          0 : int manager_read_resolv_conf(Manager *m) {
      92                 :          0 :         _cleanup_fclose_ FILE *f = NULL;
      93                 :            :         struct stat st;
      94                 :          0 :         unsigned n = 0;
      95                 :            :         int r;
      96                 :            : 
      97         [ #  # ]:          0 :         assert(m);
      98                 :            : 
      99                 :            :         /* Reads the system /etc/resolv.conf, if it exists and is not
     100                 :            :          * symlinked to our own resolv.conf instance */
     101                 :            : 
     102         [ #  # ]:          0 :         if (!m->read_resolv_conf)
     103                 :          0 :                 return 0;
     104                 :            : 
     105                 :          0 :         r = stat("/etc/resolv.conf", &st);
     106         [ #  # ]:          0 :         if (r < 0) {
     107         [ #  # ]:          0 :                 if (errno == ENOENT)
     108                 :          0 :                         return 0;
     109                 :            : 
     110         [ #  # ]:          0 :                 r = log_warning_errno(errno, "Failed to stat /etc/resolv.conf: %m");
     111                 :          0 :                 goto clear;
     112                 :            :         }
     113                 :            : 
     114                 :            :         /* Have we already seen the file? */
     115         [ #  # ]:          0 :         if (timespec_load(&st.st_mtim) == m->resolv_conf_mtime)
     116                 :          0 :                 return 0;
     117                 :            : 
     118         [ #  # ]:          0 :         if (file_is_our_own(&st))
     119                 :          0 :                 return 0;
     120                 :            : 
     121                 :          0 :         f = fopen("/etc/resolv.conf", "re");
     122         [ #  # ]:          0 :         if (!f) {
     123         [ #  # ]:          0 :                 if (errno == ENOENT)
     124                 :          0 :                         return 0;
     125                 :            : 
     126         [ #  # ]:          0 :                 r = log_warning_errno(errno, "Failed to open /etc/resolv.conf: %m");
     127                 :          0 :                 goto clear;
     128                 :            :         }
     129                 :            : 
     130         [ #  # ]:          0 :         if (fstat(fileno(f), &st) < 0) {
     131         [ #  # ]:          0 :                 r = log_error_errno(errno, "Failed to stat open file: %m");
     132                 :          0 :                 goto clear;
     133                 :            :         }
     134                 :            : 
     135         [ #  # ]:          0 :         if (file_is_our_own(&st))
     136                 :          0 :                 return 0;
     137                 :            : 
     138                 :          0 :         dns_server_mark_all(m->dns_servers);
     139                 :          0 :         dns_search_domain_mark_all(m->search_domains);
     140                 :            : 
     141                 :          0 :         for (;;) {
     142   [ #  #  #  # ]:          0 :                 _cleanup_free_ char *line = NULL;
     143                 :            :                 const char *a;
     144                 :            :                 char *l;
     145                 :            : 
     146                 :          0 :                 r = read_line(f, LONG_LINE_MAX, &line);
     147         [ #  # ]:          0 :                 if (r < 0) {
     148         [ #  # ]:          0 :                         log_error_errno(r, "Failed to read /etc/resolv.conf: %m");
     149                 :          0 :                         goto clear;
     150                 :            :                 }
     151         [ #  # ]:          0 :                 if (r == 0)
     152                 :          0 :                         break;
     153                 :            : 
     154                 :          0 :                 n++;
     155                 :            : 
     156                 :          0 :                 l = strstrip(line);
     157   [ #  #  #  # ]:          0 :                 if (IN_SET(*l, '#', ';', 0))
     158                 :          0 :                         continue;
     159                 :            : 
     160                 :          0 :                 a = first_word(l, "nameserver");
     161         [ #  # ]:          0 :                 if (a) {
     162                 :          0 :                         r = manager_parse_dns_server_string_and_warn(m, DNS_SERVER_SYSTEM, a);
     163         [ #  # ]:          0 :                         if (r < 0)
     164         [ #  # ]:          0 :                                 log_warning_errno(r, "Failed to parse DNS server address '%s', ignoring.", a);
     165                 :            : 
     166                 :          0 :                         continue;
     167                 :            :                 }
     168                 :            : 
     169                 :          0 :                 a = first_word(l, "domain");
     170         [ #  # ]:          0 :                 if (!a) /* We treat "domain" lines, and "search" lines as equivalent, and add both to our list. */
     171                 :          0 :                         a = first_word(l, "search");
     172         [ #  # ]:          0 :                 if (a) {
     173                 :          0 :                         r = manager_parse_search_domains_and_warn(m, a);
     174         [ #  # ]:          0 :                         if (r < 0)
     175         [ #  # ]:          0 :                                 log_warning_errno(r, "Failed to parse search domain string '%s', ignoring.", a);
     176                 :            :                 }
     177                 :            : 
     178         [ #  # ]:          0 :                 log_syntax(NULL, LOG_DEBUG, "/etc/resolv.conf", n, 0, "Ignoring resolv.conf line: %s", l);
     179                 :            :         }
     180                 :            : 
     181                 :          0 :         m->resolv_conf_mtime = timespec_load(&st.st_mtim);
     182                 :            : 
     183                 :            :         /* Flush out all servers and search domains that are still
     184                 :            :          * marked. Those are then ones that didn't appear in the new
     185                 :            :          * /etc/resolv.conf */
     186                 :          0 :         dns_server_unlink_marked(m->dns_servers);
     187                 :          0 :         dns_search_domain_unlink_marked(m->search_domains);
     188                 :            : 
     189                 :            :         /* Whenever /etc/resolv.conf changes, start using the first
     190                 :            :          * DNS server of it. This is useful to deal with broken
     191                 :            :          * network managing implementations (like NetworkManager),
     192                 :            :          * that when connecting to a VPN place both the VPN DNS
     193                 :            :          * servers and the local ones in /etc/resolv.conf. Without
     194                 :            :          * resetting the DNS server to use back to the first entry we
     195                 :            :          * will continue to use the local one thus being unable to
     196                 :            :          * resolve VPN domains. */
     197                 :          0 :         manager_set_dns_server(m, m->dns_servers);
     198                 :            : 
     199                 :            :         /* Unconditionally flush the cache when /etc/resolv.conf is
     200                 :            :          * modified, even if the data it contained was completely
     201                 :            :          * identical to the previous version we used. We do this
     202                 :            :          * because altering /etc/resolv.conf is typically done when
     203                 :            :          * the network configuration changes, and that should be
     204                 :            :          * enough to flush the global unicast DNS cache. */
     205         [ #  # ]:          0 :         if (m->unicast_scope)
     206                 :          0 :                 dns_cache_flush(&m->unicast_scope->cache);
     207                 :            : 
     208                 :            :         /* If /etc/resolv.conf changed, make sure to forget everything we learned about the DNS servers. After all we
     209                 :            :          * might now talk to a very different DNS server that just happens to have the same IP address as an old one
     210                 :            :          * (think 192.168.1.1). */
     211                 :          0 :         dns_server_reset_features_all(m->dns_servers);
     212                 :            : 
     213                 :          0 :         return 0;
     214                 :            : 
     215                 :          0 : clear:
     216                 :          0 :         dns_server_unlink_all(m->dns_servers);
     217                 :          0 :         dns_search_domain_unlink_all(m->search_domains);
     218                 :          0 :         return r;
     219                 :            : }
     220                 :            : 
     221                 :          0 : static void write_resolv_conf_server(DnsServer *s, FILE *f, unsigned *count) {
     222                 :            :         DnsScope *scope;
     223                 :            : 
     224         [ #  # ]:          0 :         assert(s);
     225         [ #  # ]:          0 :         assert(f);
     226         [ #  # ]:          0 :         assert(count);
     227                 :            : 
     228         [ #  # ]:          0 :         if (!dns_server_string(s)) {
     229         [ #  # ]:          0 :                 log_warning("Out of memory, or invalid DNS address. Ignoring server.");
     230                 :          0 :                 return;
     231                 :            :         }
     232                 :            : 
     233                 :            :         /* Check if the scope this DNS server belongs to is suitable as 'default' route for lookups; resolv.conf does
     234                 :            :          * not have a syntax to express that, so it must not appear as a global name server to avoid routing unrelated
     235                 :            :          * domains to it (which is a privacy violation, will most probably fail anyway, and adds unnecessary load) */
     236                 :          0 :         scope = dns_server_scope(s);
     237   [ #  #  #  # ]:          0 :         if (scope && !dns_scope_is_default_route(scope)) {
     238         [ #  # ]:          0 :                 log_debug("Scope of DNS server %s has only route-only domains, not using as global name server", dns_server_string(s));
     239                 :          0 :                 return;
     240                 :            :         }
     241                 :            : 
     242         [ #  # ]:          0 :         if (*count == MAXNS)
     243                 :          0 :                 fputs("# Too many DNS servers configured, the following entries may be ignored.\n", f);
     244                 :          0 :         (*count)++;
     245                 :            : 
     246                 :          0 :         fprintf(f, "nameserver %s\n", dns_server_string(s));
     247                 :            : }
     248                 :            : 
     249                 :          0 : static void write_resolv_conf_search(
     250                 :            :                 OrderedSet *domains,
     251                 :            :                 FILE *f) {
     252                 :          0 :         unsigned length = 0, count = 0;
     253                 :            :         Iterator i;
     254                 :            :         char *domain;
     255                 :            : 
     256         [ #  # ]:          0 :         assert(domains);
     257         [ #  # ]:          0 :         assert(f);
     258                 :            : 
     259                 :          0 :         fputs("search", f);
     260                 :            : 
     261         [ #  # ]:          0 :         ORDERED_SET_FOREACH(domain, domains, i) {
     262         [ #  # ]:          0 :                 if (++count > MAXDNSRCH) {
     263                 :          0 :                         fputs("\n# Too many search domains configured, remaining ones ignored.", f);
     264                 :          0 :                         break;
     265                 :            :                 }
     266                 :          0 :                 length += strlen(domain) + 1;
     267         [ #  # ]:          0 :                 if (length > 256) {
     268                 :          0 :                         fputs("\n# Total length of all search domains is too long, remaining ones ignored.", f);
     269                 :          0 :                         break;
     270                 :            :                 }
     271                 :          0 :                 fputc(' ', f);
     272                 :          0 :                 fputs(domain, f);
     273                 :            :         }
     274                 :            : 
     275                 :          0 :         fputs("\n", f);
     276                 :          0 : }
     277                 :            : 
     278                 :          0 : static int write_uplink_resolv_conf_contents(FILE *f, OrderedSet *dns, OrderedSet *domains) {
     279                 :            :         Iterator i;
     280                 :            : 
     281                 :          0 :         fputs("# This file is managed by man:systemd-resolved(8). Do not edit.\n"
     282                 :            :               "#\n"
     283                 :            :               "# This is a dynamic resolv.conf file for connecting local clients directly to\n"
     284                 :            :               "# all known uplink DNS servers. This file lists all configured search domains.\n"
     285                 :            :               "#\n"
     286                 :            :               "# Third party programs must not access this file directly, but only through the\n"
     287                 :            :               "# symlink at /etc/resolv.conf. To manage man:resolv.conf(5) in a different way,\n"
     288                 :            :               "# replace this symlink by a static file or a different symlink.\n"
     289                 :            :               "#\n"
     290                 :            :               "# See man:systemd-resolved.service(8) for details about the supported modes of\n"
     291                 :            :               "# operation for /etc/resolv.conf.\n"
     292                 :            :               "\n", f);
     293                 :            : 
     294         [ #  # ]:          0 :         if (ordered_set_isempty(dns))
     295                 :          0 :                 fputs("# No DNS servers known.\n", f);
     296                 :            :         else {
     297                 :          0 :                 unsigned count = 0;
     298                 :            :                 DnsServer *s;
     299                 :            : 
     300         [ #  # ]:          0 :                 ORDERED_SET_FOREACH(s, dns, i)
     301                 :          0 :                         write_resolv_conf_server(s, f, &count);
     302                 :            :         }
     303                 :            : 
     304         [ #  # ]:          0 :         if (!ordered_set_isempty(domains))
     305                 :          0 :                 write_resolv_conf_search(domains, f);
     306                 :            : 
     307                 :          0 :         return fflush_and_check(f);
     308                 :            : }
     309                 :            : 
     310                 :          0 : static int write_stub_resolv_conf_contents(FILE *f, OrderedSet *dns, OrderedSet *domains) {
     311                 :          0 :         fputs_unlocked("# This file is managed by man:systemd-resolved(8). Do not edit.\n"
     312                 :            :                        "#\n"
     313                 :            :                        "# This is a dynamic resolv.conf file for connecting local clients to the\n"
     314                 :            :                        "# internal DNS stub resolver of systemd-resolved. This file lists all\n"
     315                 :            :                        "# configured search domains.\n"
     316                 :            :                        "#\n"
     317                 :            :                        "# Run \"resolvectl status\" to see details about the uplink DNS servers\n"
     318                 :            :                        "# currently in use.\n"
     319                 :            :                        "#\n"
     320                 :            :                        "# Third party programs must not access this file directly, but only through the\n"
     321                 :            :                        "# symlink at /etc/resolv.conf. To manage man:resolv.conf(5) in a different way,\n"
     322                 :            :                        "# replace this symlink by a static file or a different symlink.\n"
     323                 :            :                        "#\n"
     324                 :            :                        "# See man:systemd-resolved.service(8) for details about the supported modes of\n"
     325                 :            :                        "# operation for /etc/resolv.conf.\n"
     326                 :            :                        "\n"
     327                 :            :                        "nameserver 127.0.0.53\n"
     328                 :            :                        "options edns0\n", f);
     329                 :            : 
     330         [ #  # ]:          0 :         if (!ordered_set_isempty(domains))
     331                 :          0 :                 write_resolv_conf_search(domains, f);
     332                 :            : 
     333                 :          0 :         return fflush_and_check(f);
     334                 :            : }
     335                 :            : 
     336                 :          0 : int manager_write_resolv_conf(Manager *m) {
     337                 :            : 
     338                 :          0 :         _cleanup_ordered_set_free_ OrderedSet *dns = NULL, *domains = NULL;
     339                 :          0 :         _cleanup_free_ char *temp_path_uplink = NULL, *temp_path_stub = NULL;
     340                 :          0 :         _cleanup_fclose_ FILE *f_uplink = NULL, *f_stub = NULL;
     341                 :            :         int r;
     342                 :            : 
     343         [ #  # ]:          0 :         assert(m);
     344                 :            : 
     345                 :            :         /* Read the system /etc/resolv.conf first */
     346                 :          0 :         (void) manager_read_resolv_conf(m);
     347                 :            : 
     348                 :            :         /* Add the full list to a set, to filter out duplicates */
     349                 :          0 :         r = manager_compile_dns_servers(m, &dns);
     350         [ #  # ]:          0 :         if (r < 0)
     351         [ #  # ]:          0 :                 return log_warning_errno(r, "Failed to compile list of DNS servers: %m");
     352                 :            : 
     353                 :          0 :         r = manager_compile_search_domains(m, &domains, false);
     354         [ #  # ]:          0 :         if (r < 0)
     355         [ #  # ]:          0 :                 return log_warning_errno(r, "Failed to compile list of search domains: %m");
     356                 :            : 
     357                 :          0 :         r = fopen_temporary_label(PRIVATE_UPLINK_RESOLV_CONF, PRIVATE_UPLINK_RESOLV_CONF, &f_uplink, &temp_path_uplink);
     358         [ #  # ]:          0 :         if (r < 0)
     359         [ #  # ]:          0 :                 return log_warning_errno(r, "Failed to open private resolv.conf file for writing: %m");
     360                 :            : 
     361                 :          0 :         (void) fchmod(fileno(f_uplink), 0644);
     362                 :            : 
     363                 :          0 :         r = fopen_temporary_label(PRIVATE_STUB_RESOLV_CONF, PRIVATE_STUB_RESOLV_CONF, &f_stub, &temp_path_stub);
     364         [ #  # ]:          0 :         if (r < 0)
     365         [ #  # ]:          0 :                 return log_warning_errno(r, "Failed to open private stub-resolv.conf file for writing: %m");
     366                 :            : 
     367                 :          0 :         (void) fchmod(fileno(f_stub), 0644);
     368                 :            : 
     369                 :          0 :         r = write_uplink_resolv_conf_contents(f_uplink, dns, domains);
     370         [ #  # ]:          0 :         if (r < 0) {
     371         [ #  # ]:          0 :                 log_error_errno(r, "Failed to write private resolv.conf contents: %m");
     372                 :          0 :                 goto fail;
     373                 :            :         }
     374                 :            : 
     375         [ #  # ]:          0 :         if (rename(temp_path_uplink, PRIVATE_UPLINK_RESOLV_CONF) < 0) {
     376         [ #  # ]:          0 :                 r = log_error_errno(errno, "Failed to move private resolv.conf file into place: %m");
     377                 :          0 :                 goto fail;
     378                 :            :         }
     379                 :            : 
     380                 :          0 :         r = write_stub_resolv_conf_contents(f_stub, dns, domains);
     381         [ #  # ]:          0 :         if (r < 0) {
     382         [ #  # ]:          0 :                 log_error_errno(r, "Failed to write private stub-resolv.conf contents: %m");
     383                 :          0 :                 goto fail;
     384                 :            :         }
     385                 :            : 
     386         [ #  # ]:          0 :         if (rename(temp_path_stub, PRIVATE_STUB_RESOLV_CONF) < 0) {
     387         [ #  # ]:          0 :                 r = log_error_errno(errno, "Failed to move private stub-resolv.conf file into place: %m");
     388                 :          0 :                 goto fail;
     389                 :            :         }
     390                 :            : 
     391                 :          0 :         return 0;
     392                 :            : 
     393                 :          0 : fail:
     394                 :          0 :         (void) unlink(PRIVATE_UPLINK_RESOLV_CONF);
     395                 :          0 :         (void) unlink(temp_path_uplink);
     396                 :          0 :         (void) unlink(PRIVATE_STUB_RESOLV_CONF);
     397                 :          0 :         (void) unlink(temp_path_stub);
     398                 :            : 
     399                 :          0 :         return r;
     400                 :            : }

Generated by: LCOV version 1.14