]> git.ipfire.org Git - thirdparty/glibc.git/blame - resolv/res_hconf.c
Update copyright dates with scripts/update-copyrights
[thirdparty/glibc.git] / resolv / res_hconf.c
CommitLineData
dff8da6b 1/* Copyright (C) 1993-2024 Free Software Foundation, Inc.
41bdb6e2 2 This file is part of the GNU C Library.
fa0bc87c 3
c84142e8 4 The GNU C Library is free software; you can redistribute it and/or
41bdb6e2
AJ
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.
fa0bc87c 8
c84142e8
UD
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
41bdb6e2 12 Lesser General Public License for more details.
fa0bc87c 13
41bdb6e2 14 You should have received a copy of the GNU Lesser General Public
59ba27a6 15 License along with the GNU C Library; if not, see
5a82c748 16 <https://www.gnu.org/licenses/>. */
fa0bc87c
RM
17
18/* This file provides a Linux /etc/host.conf compatible front end to
f720d3d2
UD
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:
fa0bc87c 22
fa0bc87c
RM
23 - line comments can appear anywhere (not just at the beginning of
24 a line)
25*/
f720d3d2 26
9be31a51 27#include <assert.h>
f720d3d2 28#include <errno.h>
fa0bc87c 29#include <ctype.h>
51028f34 30#include <libintl.h>
fa0bc87c
RM
31#include <memory.h>
32#include <stdio.h>
2706ee38 33#include <stdio_ext.h>
fa0bc87c
RM
34#include <stdlib.h>
35#include <string.h>
5edb9387 36#include <net/if.h>
f720d3d2
UD
37#include <sys/ioctl.h>
38#include <unistd.h>
39#include <netinet/in.h>
ec999b8e 40#include <libc-lock.h>
f720d3d2 41#include "ifreq.h"
fa0bc87c 42#include "res_hconf.h"
3ce1f295 43#include <wchar.h>
f463c7b1 44#include <atomic.h>
88677348 45#include <set-freeres.h>
fa0bc87c 46
84e5e756
JM
47#if IS_IN (libc)
48# define fgets_unlocked __fgets_unlocked
49#endif
50
fa0bc87c
RM
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"
fa0bc87c
RM
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
bc054367
UD
60enum parse_cbs
61 {
62 CB_none,
63 CB_arg_trimdomain_list,
bc054367
UD
64 CB_arg_bool
65 };
5edb9387 66
545f1b11 67static const struct cmd
5edb9387 68{
bc054367
UD
69 const char name[11];
70 uint8_t cb;
5edb9387
UD
71 unsigned int arg;
72} cmd[] =
73{
bc054367
UD
74 {"order", CB_none, 0},
75 {"trim", CB_arg_trimdomain_list, 0},
bc054367 76 {"multi", CB_arg_bool, HCONF_FLAG_MULTI},
bc054367 77 {"reorder", CB_arg_bool, HCONF_FLAG_REORDER}
fa0bc87c
RM
78};
79
5edb9387
UD
80/* Structure containing the state. */
81struct hconf _res_hconf;
fa0bc87c 82
fa0bc87c
RM
83/* Skip white space. */
84static const char *
5edb9387 85skip_ws (const char *str)
fa0bc87c
RM
86{
87 while (isspace (*str)) ++str;
88 return str;
89}
90
91
92/* Skip until whitespace, comma, end of line, or comment character. */
93static const char *
5edb9387 94skip_string (const char *str)
fa0bc87c 95{
5edb9387
UD
96 while (*str && !isspace (*str) && *str != '#' && *str != ',')
97 ++str;
fa0bc87c
RM
98 return str;
99}
100
101
a334319f 102static const char *
bc054367 103arg_trimdomain_list (const char *fname, int line_num, const char *args)
fa0bc87c
RM
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 {
51028f34
UD
116 char *buf;
117
322861e8 118 if (__asprintf (&buf, _("\
51028f34 119%s: line %d: cannot specify more than %d trim domains"),
322861e8
UD
120 fname, line_num, TRIMDOMAINS_MAX) < 0)
121 return 0;
51028f34 122
8a259a23 123 __fxprintf (NULL, "%s", buf);
51028f34 124
df6f8969 125 free (buf);
fa0bc87c
RM
126 return 0;
127 }
128 _res_hconf.trimdomain[_res_hconf.num_trimdomains++] =
5edb9387 129 __strndup (start, len);
fa0bc87c
RM
130 args = skip_ws (args);
131 switch (*args)
132 {
133 case ',': case ';': case ':':
134 args = skip_ws (++args);
135 if (!*args || *args == '#')
136 {
51028f34
UD
137 char *buf;
138
322861e8 139 if (__asprintf (&buf, _("\
51028f34 140%s: line %d: list delimiter not followed by domain"),
322861e8
UD
141 fname, line_num) < 0)
142 return 0;
51028f34 143
8a259a23 144 __fxprintf (NULL, "%s", buf);
51028f34
UD
145
146 free (buf);
fa0bc87c
RM
147 return 0;
148 }
149 default:
150 break;
151 }
152 }
153 while (*args && *args != '#');
154 return args;
155}
156
157
fa0bc87c 158static const char *
5edb9387 159arg_bool (const char *fname, int line_num, const char *args, unsigned flag)
fa0bc87c 160{
5edb9387 161 if (__strncasecmp (args, "on", 2) == 0)
fa0bc87c
RM
162 {
163 args += 2;
164 _res_hconf.flags |= flag;
165 }
5edb9387 166 else if (__strncasecmp (args, "off", 3) == 0)
fa0bc87c
RM
167 {
168 args += 3;
169 _res_hconf.flags &= ~flag;
170 }
171 else
172 {
51028f34
UD
173 char *buf;
174
322861e8
UD
175 if (__asprintf (&buf,
176 _("%s: line %d: expected `on' or `off', found `%s'\n"),
177 fname, line_num, args) < 0)
178 return 0;
51028f34 179
8a259a23 180 __fxprintf (NULL, "%s", buf);
51028f34
UD
181
182 free (buf);
fa0bc87c
RM
183 return 0;
184 }
185 return args;
186}
187
188
189static void
5edb9387 190parse_line (const char *fname, int line_num, const char *str)
fa0bc87c 191{
5edb9387 192 const char *start;
545f1b11 193 const struct cmd *c = 0;
fa0bc87c 194 size_t len;
57b36a0a 195 size_t i;
fa0bc87c
RM
196
197 str = skip_ws (str);
198
6dc25b55
UD
199 /* skip line comment and empty lines: */
200 if (*str == '\0' || *str == '#') return;
fa0bc87c
RM
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 {
4aebaa6b 208 if (__strncasecmp (start, cmd[i].name, len) == 0
fa0bc87c
RM
209 && strlen (cmd[i].name) == len)
210 {
211 c = &cmd[i];
212 break;
213 }
214 }
5edb9387 215 if (c == NULL)
fa0bc87c 216 {
51028f34
UD
217 char *buf;
218
322861e8
UD
219 if (__asprintf (&buf, _("%s: line %d: bad command `%s'\n"),
220 fname, line_num, start) < 0)
221 return;
51028f34 222
8a259a23 223 __fxprintf (NULL, "%s", buf);
51028f34
UD
224
225 free (buf);
fa0bc87c
RM
226 return;
227 }
228
229 /* process args: */
230 str = skip_ws (str);
bc054367
UD
231
232 if (c->cb == CB_arg_trimdomain_list)
233 str = arg_trimdomain_list (fname, line_num, str);
bc054367
UD
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
fa0bc87c
RM
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 != '#')
51028f34
UD
248 {
249 char *buf;
250
322861e8
UD
251 if (__asprintf (&buf,
252 _("%s: line %d: ignoring trailing garbage `%s'\n"),
253 fname, line_num, str) < 0)
254 break;
51028f34 255
8a259a23 256 __fxprintf (NULL, "%s", buf);
51028f34
UD
257
258 free (buf);
259 }
fa0bc87c
RM
260 break;
261 }
262 ++str;
263 }
264}
265
266
9d957ce2
UD
267static void
268do_init (void)
fa0bc87c 269{
5edb9387 270 const char *hconf_name;
fa0bc87c 271 int line_num = 0;
2c68584c 272 char buf[256], *envval;
5edb9387 273 FILE *fp;
fa0bc87c 274
5edb9387 275 memset (&_res_hconf, '\0', sizeof (_res_hconf));
fa0bc87c 276
74955460 277 hconf_name = getenv (ENV_HOSTCONF);
5edb9387 278 if (hconf_name == NULL)
fa0bc87c
RM
279 hconf_name = _PATH_HOSTCONF;
280
312be3f9 281 fp = fopen (hconf_name, "rce");
b9c65d09 282 if (fp)
fa0bc87c 283 {
2706ee38
UD
284 /* No threads using this stream. */
285 __fsetlocking (fp, FSETLOCKING_BYCALLER);
286
5edb9387 287 while (fgets_unlocked (buf, sizeof (buf), fp))
fa0bc87c
RM
288 {
289 ++line_num;
c4563d2d 290 *__strchrnul (buf, '\n') = '\0';
fa0bc87c
RM
291 parse_line (hconf_name, line_num, buf);
292 }
293 fclose (fp);
294 }
295
fa0bc87c
RM
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)
bc054367 306 arg_trimdomain_list (ENV_TRIM_ADD, 1, envval);
fa0bc87c
RM
307
308 envval = getenv (ENV_TRIM_OVERR);
309 if (envval)
310 {
311 _res_hconf.num_trimdomains = 0;
bc054367 312 arg_trimdomain_list (ENV_TRIM_OVERR, 1, envval);
fa0bc87c 313 }
5edb9387 314
6f9d4f59
TR
315 /* See comments on the declaration of _res_hconf. */
316 atomic_store_release (&_res_hconf.initialized, 1);
fa0bc87c
RM
317}
318
319
9d957ce2
UD
320/* Initialize hconf datastructure by reading host.conf file and
321 environment variables. */
322void
323_res_hconf_init (void)
324{
325 __libc_once_define (static, once);
326
327 __libc_once (once, do_init);
328}
329
330
4f41c682 331#if IS_IN (libc)
f890a59b 332# if defined SIOCGIFCONF && defined SIOCGIFNETMASK
f720d3d2 333/* List of known interfaces. */
88677348
AZN
334static struct netaddr *ifaddrs;
335weak_alias (ifaddrs, __libc_resolv_res_hconf_freemem_ptr)
f890a59b 336# endif
f720d3d2 337
fa0bc87c
RM
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.
f720d3d2
UD
340 Otherwise, nothing is changed.
341
342 Note that this function currently only handles IPv4 addresses. */
fa0bc87c
RM
343
344void
5edb9387 345_res_hconf_reorder_addrs (struct hostent *hp)
fa0bc87c 346{
5edb9387 347#if defined SIOCGIFCONF && defined SIOCGIFNETMASK
f720d3d2 348 int i, j;
f463c7b1
FW
349 /* Number of interfaces. Also serves as a flag for the
350 double-checked locking idiom. */
f720d3d2 351 static int num_ifs = -1;
f463c7b1
FW
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. */
5c3a3dba 357 __libc_lock_define_initialized (static, lock);
fa0bc87c 358
f720d3d2
UD
359 /* Only reorder if we're supposed to. */
360 if ((_res_hconf.flags & HCONF_FLAG_REORDER) == 0)
361 return;
4aebaa6b 362
f720d3d2 363 /* Can't deal with anything but IPv4 for now... */
fa0bc87c 364 if (hp->h_addrtype != AF_INET)
f720d3d2 365 return;
fa0bc87c 366
f463c7b1
FW
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)
fa0bc87c 371 {
f720d3d2
UD
372 struct ifreq *ifr, *cur_ifr;
373 int sd, num, i;
374 /* Save errno. */
375 int save = errno;
4aebaa6b 376
f720d3d2 377 /* Initialize interface table. */
fa0bc87c 378
7f1deee6 379 /* The SIOCGIFNETMASK ioctl will only work on an AF_INET socket. */
2f83a729 380 sd = __socket (AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
fa0bc87c
RM
381 if (sd < 0)
382 return;
383
f720d3d2
UD
384 /* Get lock. */
385 __libc_lock_lock (lock);
fa0bc87c 386
f463c7b1
FW
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)
5c3a3dba 393 {
f463c7b1
FW
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. */
5c3a3dba 400 int new_num_ifs = 0;
fa0bc87c 401
5c3a3dba
UD
402 /* Get a list of interfaces. */
403 __ifreq (&ifr, &num, sd);
404 if (!ifr)
405 goto cleanup;
4aebaa6b 406
5c3a3dba
UD
407 ifaddrs = malloc (num * sizeof (ifaddrs[0]));
408 if (!ifaddrs)
409 goto cleanup1;
4aebaa6b 410
5c3a3dba
UD
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 {
2483fa85
SE
415 union
416 {
417 struct sockaddr sa;
418 struct sockaddr_in sin;
419 } ss;
420
5c3a3dba
UD
421 if (cur_ifr->ifr_addr.sa_family != AF_INET)
422 continue;
fa0bc87c 423
5c3a3dba 424 ifaddrs[new_num_ifs].addrtype = AF_INET;
2483fa85
SE
425 ss.sa = cur_ifr->ifr_addr;
426 ifaddrs[new_num_ifs].u.ipv4.addr = ss.sin.sin_addr.s_addr;
fa0bc87c 427
5c3a3dba
UD
428 if (__ioctl (sd, SIOCGIFNETMASK, cur_ifr) < 0)
429 continue;
f720d3d2 430
2483fa85
SE
431 ss.sa = cur_ifr->ifr_netmask;
432 ifaddrs[new_num_ifs].u.ipv4.mask = ss.sin.sin_addr.s_addr;
fa0bc87c 433
5c3a3dba
UD
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. */
5615eaf2 446 errno = save;
5c3a3dba 447
f463c7b1
FW
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;
5c3a3dba 456 }
f720d3d2 457
b57525f1
DL
458 __libc_lock_unlock (lock);
459
4aebaa6b 460 __close (sd);
fa0bc87c
RM
461 }
462
f463c7b1
FW
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)
fa0bc87c
RM
467 return;
468
f463c7b1
FW
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
f720d3d2 494 /* Find an address for which we have a direct connection. */
fa0bc87c
RM
495 for (i = 0; hp->h_addr_list[i]; ++i)
496 {
f720d3d2 497 struct in_addr *haddr = (struct in_addr *) hp->h_addr_list[i];
fa0bc87c 498
f463c7b1 499 for (j = 0; j < num_ifs_local; ++j)
fa0bc87c 500 {
d9fee042
JM
501 uint32_t if_addr = ifaddrs[j].u.ipv4.addr;
502 uint32_t if_netmask = ifaddrs[j].u.ipv4.mask;
fa0bc87c 503
f720d3d2 504 if (((haddr->s_addr ^ if_addr) & if_netmask) == 0)
fa0bc87c 505 {
5edb9387 506 void *tmp;
fa0bc87c 507
5edb9387 508 tmp = hp->h_addr_list[i];
fa0bc87c
RM
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. */
524void
5edb9387 525_res_hconf_trim_domain (char *hostname)
fa0bc87c
RM
526{
527 size_t hostname_len, trim_len;
528 int i;
529
5edb9387 530 hostname_len = strlen (hostname);
fa0bc87c
RM
531
532 for (i = 0; i < _res_hconf.num_trimdomains; ++i)
533 {
5edb9387 534 const char *trim = _res_hconf.trimdomain[i];
fa0bc87c 535
5edb9387 536 trim_len = strlen (trim);
fa0bc87c 537 if (hostname_len > trim_len
4c8b8cc3 538 && __strcasecmp (&hostname[hostname_len - trim_len], trim) == 0)
fa0bc87c
RM
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! */
549void
5edb9387 550_res_hconf_trim_domains (struct hostent *hp)
fa0bc87c
RM
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}
1ce7d80d 561#endif