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