debug: Add fortify dprintf tests
[glibc.git] / resolv / res_hconf.c
blob89bd20be3802323639260549c6bbac425df15a1f
1 /* Copyright (C) 1993-2023 Free Software Foundation, Inc.
2 This file is part of the GNU C Library.
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.
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.
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/>. */
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:
23 - line comments can appear anywhere (not just at the beginning of
24 a line)
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>
47 #if IS_IN (libc)
48 # define fgets_unlocked __fgets_unlocked
49 #endif
51 #define _PATH_HOSTCONF "/etc/host.conf"
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"
60 enum parse_cbs
62 CB_none,
63 CB_arg_trimdomain_list,
64 CB_arg_bool
67 static const struct cmd
69 const char name[11];
70 uint8_t cb;
71 unsigned int arg;
72 } cmd[] =
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}
80 /* Structure containing the state. */
81 struct hconf _res_hconf;
83 /* Skip white space. */
84 static const char *
85 skip_ws (const char *str)
87 while (isspace (*str)) ++str;
88 return str;
92 /* Skip until whitespace, comma, end of line, or comment character. */
93 static const char *
94 skip_string (const char *str)
96 while (*str && !isspace (*str) && *str != '#' && *str != ',')
97 ++str;
98 return str;
102 static const char *
103 arg_trimdomain_list (const char *fname, int line_num, const char *args)
105 const char * start;
106 size_t len;
110 start = args;
111 args = skip_string (args);
112 len = args - start;
114 if (_res_hconf.num_trimdomains >= TRIMDOMAINS_MAX)
116 char *buf;
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;
123 __fxprintf (NULL, "%s", buf);
125 free (buf);
126 return 0;
128 _res_hconf.trimdomain[_res_hconf.num_trimdomains++] =
129 __strndup (start, len);
130 args = skip_ws (args);
131 switch (*args)
133 case ',': case ';': case ':':
134 args = skip_ws (++args);
135 if (!*args || *args == '#')
137 char *buf;
139 if (__asprintf (&buf, _("\
140 %s: line %d: list delimiter not followed by domain"),
141 fname, line_num) < 0)
142 return 0;
144 __fxprintf (NULL, "%s", buf);
146 free (buf);
147 return 0;
149 default:
150 break;
153 while (*args && *args != '#');
154 return args;
158 static const char *
159 arg_bool (const char *fname, int line_num, const char *args, unsigned flag)
161 if (__strncasecmp (args, "on", 2) == 0)
163 args += 2;
164 _res_hconf.flags |= flag;
166 else if (__strncasecmp (args, "off", 3) == 0)
168 args += 3;
169 _res_hconf.flags &= ~flag;
171 else
173 char *buf;
175 if (__asprintf (&buf,
176 _("%s: line %d: expected `on' or `off', found `%s'\n"),
177 fname, line_num, args) < 0)
178 return 0;
180 __fxprintf (NULL, "%s", buf);
182 free (buf);
183 return 0;
185 return args;
189 static void
190 parse_line (const char *fname, int line_num, const char *str)
192 const char *start;
193 const struct cmd *c = 0;
194 size_t len;
195 size_t i;
197 str = skip_ws (str);
199 /* skip line comment and empty lines: */
200 if (*str == '\0' || *str == '#') return;
202 start = str;
203 str = skip_string (str);
204 len = str - start;
206 for (i = 0; i < sizeof (cmd) / sizeof (cmd[0]); ++i)
208 if (__strncasecmp (start, cmd[i].name, len) == 0
209 && strlen (cmd[i].name) == len)
211 c = &cmd[i];
212 break;
215 if (c == NULL)
217 char *buf;
219 if (__asprintf (&buf, _("%s: line %d: bad command `%s'\n"),
220 fname, line_num, start) < 0)
221 return;
223 __fxprintf (NULL, "%s", buf);
225 free (buf);
226 return;
229 /* process args: */
230 str = skip_ws (str);
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;
240 if (!str)
241 return;
243 /* rest of line must contain white space or comment only: */
244 while (*str)
246 if (!isspace (*str)) {
247 if (*str != '#')
249 char *buf;
251 if (__asprintf (&buf,
252 _("%s: line %d: ignoring trailing garbage `%s'\n"),
253 fname, line_num, str) < 0)
254 break;
256 __fxprintf (NULL, "%s", buf);
258 free (buf);
260 break;
262 ++str;
267 static void
268 do_init (void)
270 const char *hconf_name;
271 int line_num = 0;
272 char buf[256], *envval;
273 FILE *fp;
275 memset (&_res_hconf, '\0', sizeof (_res_hconf));
277 hconf_name = getenv (ENV_HOSTCONF);
278 if (hconf_name == NULL)
279 hconf_name = _PATH_HOSTCONF;
281 fp = fopen (hconf_name, "rce");
282 if (fp)
284 /* No threads using this stream. */
285 __fsetlocking (fp, FSETLOCKING_BYCALLER);
287 while (fgets_unlocked (buf, sizeof (buf), fp))
289 ++line_num;
290 *__strchrnul (buf, '\n') = '\0';
291 parse_line (hconf_name, line_num, buf);
293 fclose (fp);
296 envval = getenv (ENV_MULTI);
297 if (envval)
298 arg_bool (ENV_MULTI, 1, envval, HCONF_FLAG_MULTI);
300 envval = getenv (ENV_REORDER);
301 if (envval)
302 arg_bool (ENV_REORDER, 1, envval, HCONF_FLAG_REORDER);
304 envval = getenv (ENV_TRIM_ADD);
305 if (envval)
306 arg_trimdomain_list (ENV_TRIM_ADD, 1, envval);
308 envval = getenv (ENV_TRIM_OVERR);
309 if (envval)
311 _res_hconf.num_trimdomains = 0;
312 arg_trimdomain_list (ENV_TRIM_OVERR, 1, envval);
315 /* See comments on the declaration of _res_hconf. */
316 atomic_store_release (&_res_hconf.initialized, 1);
320 /* Initialize hconf datastructure by reading host.conf file and
321 environment variables. */
322 void
323 _res_hconf_init (void)
325 __libc_once_define (static, once);
327 __libc_once (once, do_init);
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
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.
342 Note that this function currently only handles IPv4 addresses. */
344 void
345 _res_hconf_reorder_addrs (struct hostent *hp)
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);
359 /* Only reorder if we're supposed to. */
360 if ((_res_hconf.flags & HCONF_FLAG_REORDER) == 0)
361 return;
363 /* Can't deal with anything but IPv4 for now... */
364 if (hp->h_addrtype != AF_INET)
365 return;
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)
372 struct ifreq *ifr, *cur_ifr;
373 int sd, num, i;
374 /* Save errno. */
375 int save = errno;
377 /* Initialize interface table. */
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;
384 /* Get lock. */
385 __libc_lock_lock (lock);
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)
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;
402 /* Get a list of interfaces. */
403 __ifreq (&ifr, &num, sd);
404 if (!ifr)
405 goto cleanup;
407 ifaddrs = malloc (num * sizeof (ifaddrs[0]));
408 if (!ifaddrs)
409 goto cleanup1;
411 /* Copy usable interfaces in ifaddrs structure. */
412 for (cur_ifr = ifr, i = 0; i < num;
413 cur_ifr = __if_nextreq (cur_ifr), ++i)
415 union
417 struct sockaddr sa;
418 struct sockaddr_in sin;
419 } ss;
421 if (cur_ifr->ifr_addr.sa_family != AF_INET)
422 continue;
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;
428 if (__ioctl (sd, SIOCGIFNETMASK, cur_ifr) < 0)
429 continue;
431 ss.sa = cur_ifr->ifr_netmask;
432 ifaddrs[new_num_ifs].u.ipv4.mask = ss.sin.sin_addr.s_addr;
434 /* Now we're committed to this entry. */
435 ++new_num_ifs;
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);
441 cleanup1:
442 __if_freereq (ifr, num);
444 cleanup:
445 /* Release lock, preserve error value, and close socket. */
446 errno = save;
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;
458 __libc_lock_unlock (lock);
460 __close (sd);
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;
469 /* The code below accesses ifaddrs, so we need to ensure that the
470 initialization happens-before this point.
472 The actual initialization is sequenced-before the release store
473 to num_ifs, and sequenced-before the end of the critical section.
475 This means there are three possible executions:
477 (1) The thread that initialized the data also uses it, so
478 sequenced-before is sufficient to ensure happens-before.
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.
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.
491 Consequently, all potential writes to ifaddrs (and the data it
492 points to) happens-before this point. */
494 /* Find an address for which we have a direct connection. */
495 for (i = 0; hp->h_addr_list[i]; ++i)
497 struct in_addr *haddr = (struct in_addr *) hp->h_addr_list[i];
499 for (j = 0; j < num_ifs_local; ++j)
501 uint32_t if_addr = ifaddrs[j].u.ipv4.addr;
502 uint32_t if_netmask = ifaddrs[j].u.ipv4.mask;
504 if (((haddr->s_addr ^ if_addr) & if_netmask) == 0)
506 void *tmp;
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;
515 #endif /* defined(SIOCGIFCONF) && ... */
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)
527 size_t hostname_len, trim_len;
528 int i;
530 hostname_len = strlen (hostname);
532 for (i = 0; i < _res_hconf.num_trimdomains; ++i)
534 const char *trim = _res_hconf.trimdomain[i];
536 trim_len = strlen (trim);
537 if (hostname_len > trim_len
538 && __strcasecmp (&hostname[hostname_len - trim_len], trim) == 0)
540 hostname[hostname_len - trim_len] = '\0';
541 break;
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)
552 int i;
554 if (_res_hconf.num_trimdomains == 0)
555 return;
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]);
561 #endif