(root)/
glibc-2.38/
resolv/
res_hconf.c
       1  /* Copyright (C) 1993-2023 Free Software Foundation, Inc.
       2     This file is part of the GNU C Library.
       3  
       4     The GNU C Library is free software; you can redistribute it and/or
       5     modify it under the terms of the GNU Lesser General Public
       6     License as published by the Free Software Foundation; either
       7     version 2.1 of the License, or (at your option) any later version.
       8  
       9     The GNU C Library is distributed in the hope that it will be useful,
      10     but WITHOUT ANY WARRANTY; without even the implied warranty of
      11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      12     Lesser General Public License for more details.
      13  
      14     You should have received a copy of the GNU Lesser General Public
      15     License along with the GNU C Library; if not, see
      16     <https://www.gnu.org/licenses/>.  */
      17  
      18  /* This file provides a Linux /etc/host.conf compatible front end to
      19     the various name resolvers (/etc/hosts, named, NIS server, etc.).
      20     Though mostly compatibly, the following differences exist compared
      21     to the original implementation:
      22  
      23  	- line comments can appear anywhere (not just at the beginning of
      24  	  a line)
      25  */
      26  
      27  #include <assert.h>
      28  #include <errno.h>
      29  #include <ctype.h>
      30  #include <libintl.h>
      31  #include <memory.h>
      32  #include <stdio.h>
      33  #include <stdio_ext.h>
      34  #include <stdlib.h>
      35  #include <string.h>
      36  #include <net/if.h>
      37  #include <sys/ioctl.h>
      38  #include <unistd.h>
      39  #include <netinet/in.h>
      40  #include <libc-lock.h>
      41  #include "ifreq.h"
      42  #include "res_hconf.h"
      43  #include <wchar.h>
      44  #include <atomic.h>
      45  #include <set-freeres.h>
      46  
      47  #if IS_IN (libc)
      48  # define fgets_unlocked __fgets_unlocked
      49  #endif
      50  
      51  #define _PATH_HOSTCONF	"/etc/host.conf"
      52  
      53  /* Environment vars that all user to override default behavior:  */
      54  #define ENV_HOSTCONF	"RESOLV_HOST_CONF"
      55  #define ENV_TRIM_OVERR	"RESOLV_OVERRIDE_TRIM_DOMAINS"
      56  #define ENV_TRIM_ADD	"RESOLV_ADD_TRIM_DOMAINS"
      57  #define ENV_MULTI	"RESOLV_MULTI"
      58  #define ENV_REORDER	"RESOLV_REORDER"
      59  
      60  enum parse_cbs
      61    {
      62      CB_none,
      63      CB_arg_trimdomain_list,
      64      CB_arg_bool
      65    };
      66  
      67  static const struct cmd
      68  {
      69    const char name[11];
      70    uint8_t cb;
      71    unsigned int arg;
      72  } cmd[] =
      73  {
      74    {"order",		CB_none,		0},
      75    {"trim",		CB_arg_trimdomain_list,	0},
      76    {"multi",		CB_arg_bool,		HCONF_FLAG_MULTI},
      77    {"reorder",		CB_arg_bool,		HCONF_FLAG_REORDER}
      78  };
      79  
      80  /* Structure containing the state.  */
      81  struct hconf _res_hconf;
      82  
      83  /* Skip white space.  */
      84  static const char *
      85  skip_ws (const char *str)
      86  {
      87    while (isspace (*str)) ++str;
      88    return str;
      89  }
      90  
      91  
      92  /* Skip until whitespace, comma, end of line, or comment character.  */
      93  static const char *
      94  skip_string (const char *str)
      95  {
      96    while (*str && !isspace (*str) && *str != '#' && *str != ',')
      97      ++str;
      98    return str;
      99  }
     100  
     101  
     102  static const char *
     103  arg_trimdomain_list (const char *fname, int line_num, const char *args)
     104  {
     105    const char * start;
     106    size_t len;
     107  
     108    do
     109      {
     110        start = args;
     111        args = skip_string (args);
     112        len = args - start;
     113  
     114        if (_res_hconf.num_trimdomains >= TRIMDOMAINS_MAX)
     115  	{
     116  	  char *buf;
     117  
     118  	  if (__asprintf (&buf, _("\
     119  %s: line %d: cannot specify more than %d trim domains"),
     120  			  fname, line_num, TRIMDOMAINS_MAX) < 0)
     121  	    return 0;
     122  
     123  	  __fxprintf (NULL, "%s", buf);
     124  
     125  	  free (buf);
     126  	  return 0;
     127  	}
     128        _res_hconf.trimdomain[_res_hconf.num_trimdomains++] =
     129  	__strndup (start, len);
     130        args = skip_ws (args);
     131        switch (*args)
     132  	{
     133  	case ',': case ';': case ':':
     134  	  args = skip_ws (++args);
     135  	  if (!*args || *args == '#')
     136  	    {
     137  	      char *buf;
     138  
     139  	      if (__asprintf (&buf, _("\
     140  %s: line %d: list delimiter not followed by domain"),
     141  			      fname, line_num) < 0)
     142  		return 0;
     143  
     144  	      __fxprintf (NULL, "%s", buf);
     145  
     146  	      free (buf);
     147  	      return 0;
     148  	    }
     149  	default:
     150  	  break;
     151  	}
     152      }
     153    while (*args && *args != '#');
     154    return args;
     155  }
     156  
     157  
     158  static const char *
     159  arg_bool (const char *fname, int line_num, const char *args, unsigned flag)
     160  {
     161    if (__strncasecmp (args, "on", 2) == 0)
     162      {
     163        args += 2;
     164        _res_hconf.flags |= flag;
     165      }
     166    else if (__strncasecmp (args, "off", 3) == 0)
     167      {
     168        args += 3;
     169        _res_hconf.flags &= ~flag;
     170      }
     171    else
     172      {
     173        char *buf;
     174  
     175        if (__asprintf (&buf,
     176  		      _("%s: line %d: expected `on' or `off', found `%s'\n"),
     177  		      fname, line_num, args) < 0)
     178  	return 0;
     179  
     180        __fxprintf (NULL, "%s", buf);
     181  
     182        free (buf);
     183        return 0;
     184      }
     185    return args;
     186  }
     187  
     188  
     189  static void
     190  parse_line (const char *fname, int line_num, const char *str)
     191  {
     192    const char *start;
     193    const struct cmd *c = 0;
     194    size_t len;
     195    size_t i;
     196  
     197    str = skip_ws (str);
     198  
     199    /* skip line comment and empty lines: */
     200    if (*str == '\0' || *str == '#') return;
     201  
     202    start = str;
     203    str = skip_string (str);
     204    len = str - start;
     205  
     206    for (i = 0; i < sizeof (cmd) / sizeof (cmd[0]); ++i)
     207      {
     208        if (__strncasecmp (start, cmd[i].name, len) == 0
     209  	  && strlen (cmd[i].name) == len)
     210  	{
     211  	  c = &cmd[i];
     212  	  break;
     213  	}
     214      }
     215    if (c == NULL)
     216      {
     217        char *buf;
     218  
     219        if (__asprintf (&buf, _("%s: line %d: bad command `%s'\n"),
     220  		      fname, line_num, start) < 0)
     221  	return;
     222  
     223        __fxprintf (NULL, "%s", buf);
     224  
     225        free (buf);
     226        return;
     227      }
     228  
     229    /* process args: */
     230    str = skip_ws (str);
     231  
     232    if (c->cb == CB_arg_trimdomain_list)
     233      str = arg_trimdomain_list (fname, line_num, str);
     234    else if (c->cb == CB_arg_bool)
     235      str = arg_bool (fname, line_num, str, c->arg);
     236    else
     237      /* Ignore the line.  */
     238      return;
     239  
     240    if (!str)
     241      return;
     242  
     243    /* rest of line must contain white space or comment only: */
     244    while (*str)
     245      {
     246        if (!isspace (*str)) {
     247  	if (*str != '#')
     248  	  {
     249  	    char *buf;
     250  
     251  	    if (__asprintf (&buf,
     252  			    _("%s: line %d: ignoring trailing garbage `%s'\n"),
     253  			    fname, line_num, str) < 0)
     254  	      break;
     255  
     256  	    __fxprintf (NULL, "%s", buf);
     257  
     258  	    free (buf);
     259  	  }
     260  	break;
     261        }
     262        ++str;
     263      }
     264  }
     265  
     266  
     267  static void
     268  do_init (void)
     269  {
     270    const char *hconf_name;
     271    int line_num = 0;
     272    char buf[256], *envval;
     273    FILE *fp;
     274  
     275    memset (&_res_hconf, '\0', sizeof (_res_hconf));
     276  
     277    hconf_name = getenv (ENV_HOSTCONF);
     278    if (hconf_name == NULL)
     279      hconf_name = _PATH_HOSTCONF;
     280  
     281    fp = fopen (hconf_name, "rce");
     282    if (fp)
     283      {
     284        /* No threads using this stream.  */
     285        __fsetlocking (fp, FSETLOCKING_BYCALLER);
     286  
     287        while (fgets_unlocked (buf, sizeof (buf), fp))
     288  	{
     289  	  ++line_num;
     290  	  *__strchrnul (buf, '\n') = '\0';
     291  	  parse_line (hconf_name, line_num, buf);
     292  	}
     293        fclose (fp);
     294      }
     295  
     296    envval = getenv (ENV_MULTI);
     297    if (envval)
     298      arg_bool (ENV_MULTI, 1, envval, HCONF_FLAG_MULTI);
     299  
     300    envval = getenv (ENV_REORDER);
     301    if (envval)
     302      arg_bool (ENV_REORDER, 1, envval, HCONF_FLAG_REORDER);
     303  
     304    envval = getenv (ENV_TRIM_ADD);
     305    if (envval)
     306      arg_trimdomain_list (ENV_TRIM_ADD, 1, envval);
     307  
     308    envval = getenv (ENV_TRIM_OVERR);
     309    if (envval)
     310      {
     311        _res_hconf.num_trimdomains = 0;
     312        arg_trimdomain_list (ENV_TRIM_OVERR, 1, envval);
     313      }
     314  
     315    /* See comments on the declaration of _res_hconf.  */
     316    atomic_store_release (&_res_hconf.initialized, 1);
     317  }
     318  
     319  
     320  /* Initialize hconf datastructure by reading host.conf file and
     321     environment variables.  */
     322  void
     323  _res_hconf_init (void)
     324  {
     325    __libc_once_define (static, once);
     326  
     327    __libc_once (once, do_init);
     328  }
     329  
     330  
     331  #if IS_IN (libc)
     332  # if defined SIOCGIFCONF && defined SIOCGIFNETMASK
     333  /* List of known interfaces.  */
     334  static struct netaddr *ifaddrs;
     335  weak_alias (ifaddrs, __libc_resolv_res_hconf_freemem_ptr)
     336  # endif
     337  
     338  /* Reorder addresses returned in a hostent such that the first address
     339     is an address on the local subnet, if there is such an address.
     340     Otherwise, nothing is changed.
     341  
     342     Note that this function currently only handles IPv4 addresses.  */
     343  
     344  void
     345  _res_hconf_reorder_addrs (struct hostent *hp)
     346  {
     347  #if defined SIOCGIFCONF && defined SIOCGIFNETMASK
     348    int i, j;
     349    /* Number of interfaces.  Also serves as a flag for the
     350       double-checked locking idiom.  */
     351    static int num_ifs = -1;
     352    /* Local copy of num_ifs, for non-atomic access.  */
     353    int num_ifs_local;
     354    /* We need to protect the dynamic buffer handling.  The lock is only
     355       acquired during initialization.  Afterwards, a positive num_ifs
     356       value indicates completed initialization.  */
     357    __libc_lock_define_initialized (static, lock);
     358  
     359    /* Only reorder if we're supposed to.  */
     360    if ((_res_hconf.flags & HCONF_FLAG_REORDER) == 0)
     361      return;
     362  
     363    /* Can't deal with anything but IPv4 for now...  */
     364    if (hp->h_addrtype != AF_INET)
     365      return;
     366  
     367    /* This load synchronizes with the release MO store in the
     368       initialization block below.  */
     369    num_ifs_local = atomic_load_acquire (&num_ifs);
     370    if (num_ifs_local <= 0)
     371      {
     372        struct ifreq *ifr, *cur_ifr;
     373        int sd, num, i;
     374        /* Save errno.  */
     375        int save = errno;
     376  
     377        /* Initialize interface table.  */
     378  
     379        /* The SIOCGIFNETMASK ioctl will only work on an AF_INET socket.  */
     380        sd = __socket (AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
     381        if (sd < 0)
     382  	return;
     383  
     384        /* Get lock.  */
     385        __libc_lock_lock (lock);
     386  
     387        /* Recheck, somebody else might have done the work by now.  No
     388  	 ordering is required for the load because we have the lock,
     389  	 and num_ifs is only updated under the lock.  Also see (3) in
     390  	 the analysis below.  */
     391        num_ifs_local = atomic_load_relaxed (&num_ifs);
     392        if (num_ifs_local <= 0)
     393  	{
     394  	  /* This is the only block which writes to num_ifs.  It can
     395  	     be executed several times (sequentially) if
     396  	     initialization does not yield any interfaces, and num_ifs
     397  	     remains zero.  However, once we stored a positive value
     398  	     in num_ifs below, this block cannot be entered again due
     399  	     to the condition above.  */
     400  	  int new_num_ifs = 0;
     401  
     402  	  /* Get a list of interfaces.  */
     403  	  __ifreq (&ifr, &num, sd);
     404  	  if (!ifr)
     405  	    goto cleanup;
     406  
     407  	  ifaddrs = malloc (num * sizeof (ifaddrs[0]));
     408  	  if (!ifaddrs)
     409  	    goto cleanup1;
     410  
     411  	  /* Copy usable interfaces in ifaddrs structure.  */
     412  	  for (cur_ifr = ifr, i = 0; i < num;
     413  	       cur_ifr = __if_nextreq (cur_ifr), ++i)
     414  	    {
     415  	      union
     416  	      {
     417  		struct sockaddr sa;
     418  		struct sockaddr_in sin;
     419  	      } ss;
     420  
     421  	      if (cur_ifr->ifr_addr.sa_family != AF_INET)
     422  		continue;
     423  
     424  	      ifaddrs[new_num_ifs].addrtype = AF_INET;
     425  	      ss.sa = cur_ifr->ifr_addr;
     426  	      ifaddrs[new_num_ifs].u.ipv4.addr = ss.sin.sin_addr.s_addr;
     427  
     428  	      if (__ioctl (sd, SIOCGIFNETMASK, cur_ifr) < 0)
     429  		continue;
     430  
     431  	      ss.sa = cur_ifr->ifr_netmask;
     432  	      ifaddrs[new_num_ifs].u.ipv4.mask = ss.sin.sin_addr.s_addr;
     433  
     434  	      /* Now we're committed to this entry.  */
     435  	      ++new_num_ifs;
     436  	    }
     437  	  /* Just keep enough memory to hold all the interfaces we want.  */
     438  	  ifaddrs = realloc (ifaddrs, new_num_ifs * sizeof (ifaddrs[0]));
     439  	  assert (ifaddrs != NULL);
     440  
     441  	cleanup1:
     442  	  __if_freereq (ifr, num);
     443  
     444  	cleanup:
     445  	  /* Release lock, preserve error value, and close socket.  */
     446  	  errno = save;
     447  
     448  	  /* Advertise successful initialization if new_num_ifs is
     449  	     positive (and no updates to ifaddrs are permitted after
     450  	     that).  Otherwise, num_ifs remains unchanged, at zero.
     451  	     This store synchronizes with the initial acquire MO
     452  	     load.  */
     453  	  atomic_store_release (&num_ifs, new_num_ifs);
     454  	  /* Keep the local copy current, to save another load.  */
     455  	  num_ifs_local = new_num_ifs;
     456  	}
     457  
     458        __libc_lock_unlock (lock);
     459  
     460        __close (sd);
     461      }
     462  
     463    /* num_ifs_local cannot be negative because the if statement above
     464       covered this case.  It can still be zero if we just performed
     465       initialization, but could not find any interfaces.  */
     466    if (num_ifs_local == 0)
     467      return;
     468  
     469    /* The code below accesses ifaddrs, so we need to ensure that the
     470       initialization happens-before this point.
     471  
     472       The actual initialization is sequenced-before the release store
     473       to num_ifs, and sequenced-before the end of the critical section.
     474  
     475       This means there are three possible executions:
     476  
     477       (1) The thread that initialized the data also uses it, so
     478           sequenced-before is sufficient to ensure happens-before.
     479  
     480       (2) The release MO store of num_ifs synchronizes-with the acquire
     481           MO load, and the acquire MO load is sequenced before the use
     482           of the initialized data below.
     483  
     484       (3) We enter the critical section, and the relaxed MO load of
     485           num_ifs yields a positive value.  The write to ifaddrs is
     486           sequenced-before leaving the critical section.  Leaving the
     487           critical section happens-before we entered the critical
     488           section ourselves, which means that the write to ifaddrs
     489           happens-before this point.
     490  
     491       Consequently, all potential writes to ifaddrs (and the data it
     492       points to) happens-before this point.  */
     493  
     494    /* Find an address for which we have a direct connection.  */
     495    for (i = 0; hp->h_addr_list[i]; ++i)
     496      {
     497        struct in_addr *haddr = (struct in_addr *) hp->h_addr_list[i];
     498  
     499        for (j = 0; j < num_ifs_local; ++j)
     500  	{
     501  	  uint32_t if_addr    = ifaddrs[j].u.ipv4.addr;
     502  	  uint32_t if_netmask = ifaddrs[j].u.ipv4.mask;
     503  
     504  	  if (((haddr->s_addr ^ if_addr) & if_netmask) == 0)
     505  	    {
     506  	      void *tmp;
     507  
     508  	      tmp = hp->h_addr_list[i];
     509  	      hp->h_addr_list[i] = hp->h_addr_list[0];
     510  	      hp->h_addr_list[0] = tmp;
     511  	      return;
     512  	    }
     513  	}
     514      }
     515  #endif /* defined(SIOCGIFCONF) && ... */
     516  }
     517  
     518  
     519  /* If HOSTNAME has a postfix matching any of the trimdomains, trim away
     520     that postfix.  Notice that HOSTNAME is modified inplace.  Also, the
     521     original code applied all trimdomains in order, meaning that the
     522     same domainname could be trimmed multiple times.  I believe this
     523     was unintentional.  */
     524  void
     525  _res_hconf_trim_domain (char *hostname)
     526  {
     527    size_t hostname_len, trim_len;
     528    int i;
     529  
     530    hostname_len = strlen (hostname);
     531  
     532    for (i = 0; i < _res_hconf.num_trimdomains; ++i)
     533      {
     534        const char *trim = _res_hconf.trimdomain[i];
     535  
     536        trim_len = strlen (trim);
     537        if (hostname_len > trim_len
     538  	  && __strcasecmp (&hostname[hostname_len - trim_len], trim) == 0)
     539  	{
     540  	  hostname[hostname_len - trim_len] = '\0';
     541  	  break;
     542  	}
     543      }
     544  }
     545  
     546  
     547  /* Trim all hostnames/aliases in HP according to the trimdomain list.
     548     Notice that HP is modified inplace!  */
     549  void
     550  _res_hconf_trim_domains (struct hostent *hp)
     551  {
     552    int i;
     553  
     554    if (_res_hconf.num_trimdomains == 0)
     555      return;
     556  
     557    _res_hconf_trim_domain (hp->h_name);
     558    for (i = 0; hp->h_aliases[i]; ++i)
     559      _res_hconf_trim_domain (hp->h_aliases[i]);
     560  }
     561  #endif