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
59enum parse_cbs
60 {
61 CB_none,
62 CB_arg_trimdomain_list,
63 CB_arg_bool
64 };
65
66static 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. */
80struct hconf _res_hconf;
81
82/* Skip white space. */
83static const char *
84skip_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. */
92static const char *
93skip_string (const char *str)
94{
95 while (*str && !isspace (*str) && *str != '#' && *str != ',')
96 ++str;
97 return str;
98}
99
100
101static const char *
102arg_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 (str: 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 (ptr: buf);
125 return 0;
126 }
127 _res_hconf.trimdomain[_res_hconf.num_trimdomains++] =
128 __strndup (string: start, n: len);
129 args = skip_ws (str: args);
130 switch (*args)
131 {
132 case ',': case ';': case ':':
133 args = skip_ws (str: ++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 (ptr: buf);
146 return 0;
147 }
148 default:
149 break;
150 }
151 }
152 while (*args && *args != '#');
153 return args;
154}
155
156
157static const char *
158arg_bool (const char *fname, int line_num, const char *args, unsigned flag)
159{
160 if (__strncasecmp (s1: args, s2: "on", n: 2) == 0)
161 {
162 args += 2;
163 _res_hconf.flags |= flag;
164 }
165 else if (__strncasecmp (s1: args, s2: "off", n: 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 (ptr: buf);
182 return 0;
183 }
184 return args;
185}
186
187
188static void
189parse_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 (s1: start, s2: cmd[i].name, n: len) == 0
208 && strlen (s: 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 (ptr: 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, args: str);
233 else if (c->cb == CB_arg_bool)
234 str = arg_bool (fname, line_num, args: str, flag: 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 (ptr: buf);
258 }
259 break;
260 }
261 ++str;
262 }
263}
264
265
266static void
267do_init (void)
268{
269 const char *hconf_name;
270 int line_num = 0;
271 char buf[256], *envval;
272 FILE *fp;
273
274 memset (dest: &_res_hconf, ch: '\0', len: sizeof (_res_hconf));
275
276 hconf_name = getenv (ENV_HOSTCONF);
277 if (hconf_name == NULL)
278 hconf_name = _PATH_HOSTCONF;
279
280 fp = fopen (filename: hconf_name, modes: "rce");
281 if (fp)
282 {
283 /* No threads using this stream. */
284 __fsetlocking (fp, FSETLOCKING_BYCALLER);
285
286 while (fgets_unlocked (s: buf, n: sizeof (buf), stream: fp))
287 {
288 ++line_num;
289 *__strchrnul (s: buf, c: '\n') = '\0';
290 parse_line (fname: hconf_name, line_num, str: buf);
291 }
292 fclose (stream: fp);
293 }
294
295 envval = getenv (ENV_MULTI);
296 if (envval)
297 arg_bool (ENV_MULTI, line_num: 1, args: envval, HCONF_FLAG_MULTI);
298
299 envval = getenv (ENV_REORDER);
300 if (envval)
301 arg_bool (ENV_REORDER, line_num: 1, args: envval, HCONF_FLAG_REORDER);
302
303 envval = getenv (ENV_TRIM_ADD);
304 if (envval)
305 arg_trimdomain_list (ENV_TRIM_ADD, line_num: 1, args: 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, line_num: 1, args: 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. */
321void
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. */
333libc_freeres_ptr (
334static 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
354void
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. */
534void
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! */
559void
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

source code of glibc/resolv/res_hconf.c