1/* Copyright (C) 1993-2018 Free Software Foundation, Inc.
2 This file is part of the GNU C Library.
3 Contributed by David Mosberger (davidm@azstarnet.com).
4
5 The GNU C Library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) any later version.
9
10 The GNU C Library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public
16 License along with the GNU C Library; if not, see
17 <http://www.gnu.org/licenses/>. */
18
19/* This file provides a Linux /etc/host.conf compatible front end to
20 the various name resolvers (/etc/hosts, named, NIS server, etc.).
21 Though mostly compatibly, the following differences exist compared
22 to the original implementation:
23
24 - line comments can appear anywhere (not just at the beginning of
25 a line)
26*/
27
28#include <assert.h>
29#include <errno.h>
30#include <ctype.h>
31#include <libintl.h>
32#include <memory.h>
33#include <stdio.h>
34#include <stdio_ext.h>
35#include <stdlib.h>
36#include <string.h>
37#include <net/if.h>
38#include <sys/ioctl.h>
39#include <unistd.h>
40#include <netinet/in.h>
41#include <libc-lock.h>
42#include "ifreq.h"
43#include "res_hconf.h"
44#include <wchar.h>
45#include <atomic.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
60enum parse_cbs
61 {
62 CB_none,
63 CB_arg_trimdomain_list,
64 CB_arg_bool
65 };
66
67static 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. */
81struct hconf _res_hconf;
82
83/* Skip white space. */
84static const char *
85skip_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. */
93static const char *
94skip_string (const char *str)
95{
96 while (*str && !isspace (*str) && *str != '#' && *str != ',')
97 ++str;
98 return str;
99}
100
101
102static const char *
103arg_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
158static const char *
159arg_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
189static void
190parse_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
267static void
268do_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. */
322void
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. */
334libc_freeres_ptr (
335static struct netaddr
336{
337 int addrtype;
338 union
339 {
340 struct
341 {
342 uint32_t addr;
343 uint32_t mask;
344 } ipv4;
345 } u;
346} *ifaddrs);
347# endif
348
349/* Reorder addresses returned in a hostent such that the first address
350 is an address on the local subnet, if there is such an address.
351 Otherwise, nothing is changed.
352
353 Note that this function currently only handles IPv4 addresses. */
354
355void
356_res_hconf_reorder_addrs (struct hostent *hp)
357{
358#if defined SIOCGIFCONF && defined SIOCGIFNETMASK
359 int i, j;
360 /* Number of interfaces. Also serves as a flag for the
361 double-checked locking idiom. */
362 static int num_ifs = -1;
363 /* Local copy of num_ifs, for non-atomic access. */
364 int num_ifs_local;
365 /* We need to protect the dynamic buffer handling. The lock is only
366 acquired during initialization. Afterwards, a positive num_ifs
367 value indicates completed initialization. */
368 __libc_lock_define_initialized (static, lock);
369
370 /* Only reorder if we're supposed to. */
371 if ((_res_hconf.flags & HCONF_FLAG_REORDER) == 0)
372 return;
373
374 /* Can't deal with anything but IPv4 for now... */
375 if (hp->h_addrtype != AF_INET)
376 return;
377
378 /* This load synchronizes with the release MO store in the
379 initialization block below. */
380 num_ifs_local = atomic_load_acquire (&num_ifs);
381 if (num_ifs_local <= 0)
382 {
383 struct ifreq *ifr, *cur_ifr;
384 int sd, num, i;
385 /* Save errno. */
386 int save = errno;
387
388 /* Initialize interface table. */
389
390 /* The SIOCGIFNETMASK ioctl will only work on an AF_INET socket. */
391 sd = __socket (AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
392 if (sd < 0)
393 return;
394
395 /* Get lock. */
396 __libc_lock_lock (lock);
397
398 /* Recheck, somebody else might have done the work by now. No
399 ordering is required for the load because we have the lock,
400 and num_ifs is only updated under the lock. Also see (3) in
401 the analysis below. */
402 num_ifs_local = atomic_load_relaxed (&num_ifs);
403 if (num_ifs_local <= 0)
404 {
405 /* This is the only block which writes to num_ifs. It can
406 be executed several times (sequentially) if
407 initialization does not yield any interfaces, and num_ifs
408 remains zero. However, once we stored a positive value
409 in num_ifs below, this block cannot be entered again due
410 to the condition above. */
411 int new_num_ifs = 0;
412
413 /* Get a list of interfaces. */
414 __ifreq (&ifr, &num, sd);
415 if (!ifr)
416 goto cleanup;
417
418 ifaddrs = malloc (num * sizeof (ifaddrs[0]));
419 if (!ifaddrs)
420 goto cleanup1;
421
422 /* Copy usable interfaces in ifaddrs structure. */
423 for (cur_ifr = ifr, i = 0; i < num;
424 cur_ifr = __if_nextreq (cur_ifr), ++i)
425 {
426 union
427 {
428 struct sockaddr sa;
429 struct sockaddr_in sin;
430 } ss;
431
432 if (cur_ifr->ifr_addr.sa_family != AF_INET)
433 continue;
434
435 ifaddrs[new_num_ifs].addrtype = AF_INET;
436 ss.sa = cur_ifr->ifr_addr;
437 ifaddrs[new_num_ifs].u.ipv4.addr = ss.sin.sin_addr.s_addr;
438
439 if (__ioctl (sd, SIOCGIFNETMASK, cur_ifr) < 0)
440 continue;
441
442 ss.sa = cur_ifr->ifr_netmask;
443 ifaddrs[new_num_ifs].u.ipv4.mask = ss.sin.sin_addr.s_addr;
444
445 /* Now we're committed to this entry. */
446 ++new_num_ifs;
447 }
448 /* Just keep enough memory to hold all the interfaces we want. */
449 ifaddrs = realloc (ifaddrs, new_num_ifs * sizeof (ifaddrs[0]));
450 assert (ifaddrs != NULL);
451
452 cleanup1:
453 __if_freereq (ifr, num);
454
455 cleanup:
456 /* Release lock, preserve error value, and close socket. */
457 errno = save;
458
459 /* Advertise successful initialization if new_num_ifs is
460 positive (and no updates to ifaddrs are permitted after
461 that). Otherwise, num_ifs remains unchanged, at zero.
462 This store synchronizes with the initial acquire MO
463 load. */
464 atomic_store_release (&num_ifs, new_num_ifs);
465 /* Keep the local copy current, to save another load. */
466 num_ifs_local = new_num_ifs;
467 }
468
469 __libc_lock_unlock (lock);
470
471 __close (sd);
472 }
473
474 /* num_ifs_local cannot be negative because the if statement above
475 covered this case. It can still be zero if we just performed
476 initialization, but could not find any interfaces. */
477 if (num_ifs_local == 0)
478 return;
479
480 /* The code below accesses ifaddrs, so we need to ensure that the
481 initialization happens-before this point.
482
483 The actual initialization is sequenced-before the release store
484 to num_ifs, and sequenced-before the end of the critical section.
485
486 This means there are three possible executions:
487
488 (1) The thread that initialized the data also uses it, so
489 sequenced-before is sufficient to ensure happens-before.
490
491 (2) The release MO store of num_ifs synchronizes-with the acquire
492 MO load, and the acquire MO load is sequenced before the use
493 of the initialized data below.
494
495 (3) We enter the critical section, and the relaxed MO load of
496 num_ifs yields a positive value. The write to ifaddrs is
497 sequenced-before leaving the critical section. Leaving the
498 critical section happens-before we entered the critical
499 section ourselves, which means that the write to ifaddrs
500 happens-before this point.
501
502 Consequently, all potential writes to ifaddrs (and the data it
503 points to) happens-before this point. */
504
505 /* Find an address for which we have a direct connection. */
506 for (i = 0; hp->h_addr_list[i]; ++i)
507 {
508 struct in_addr *haddr = (struct in_addr *) hp->h_addr_list[i];
509
510 for (j = 0; j < num_ifs_local; ++j)
511 {
512 uint32_t if_addr = ifaddrs[j].u.ipv4.addr;
513 uint32_t if_netmask = ifaddrs[j].u.ipv4.mask;
514
515 if (((haddr->s_addr ^ if_addr) & if_netmask) == 0)
516 {
517 void *tmp;
518
519 tmp = hp->h_addr_list[i];
520 hp->h_addr_list[i] = hp->h_addr_list[0];
521 hp->h_addr_list[0] = tmp;
522 return;
523 }
524 }
525 }
526#endif /* defined(SIOCGIFCONF) && ... */
527}
528
529
530/* If HOSTNAME has a postfix matching any of the trimdomains, trim away
531 that postfix. Notice that HOSTNAME is modified inplace. Also, the
532 original code applied all trimdomains in order, meaning that the
533 same domainname could be trimmed multiple times. I believe this
534 was unintentional. */
535void
536_res_hconf_trim_domain (char *hostname)
537{
538 size_t hostname_len, trim_len;
539 int i;
540
541 hostname_len = strlen (hostname);
542
543 for (i = 0; i < _res_hconf.num_trimdomains; ++i)
544 {
545 const char *trim = _res_hconf.trimdomain[i];
546
547 trim_len = strlen (trim);
548 if (hostname_len > trim_len
549 && __strcasecmp (&hostname[hostname_len - trim_len], trim) == 0)
550 {
551 hostname[hostname_len - trim_len] = '\0';
552 break;
553 }
554 }
555}
556
557
558/* Trim all hostnames/aliases in HP according to the trimdomain list.
559 Notice that HP is modified inplace! */
560void
561_res_hconf_trim_domains (struct hostent *hp)
562{
563 int i;
564
565 if (_res_hconf.num_trimdomains == 0)
566 return;
567
568 _res_hconf_trim_domain (hp->h_name);
569 for (i = 0; hp->h_aliases[i]; ++i)
570 _res_hconf_trim_domain (hp->h_aliases[i]);
571}
572#endif
573