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