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 | |
64 | enum parse_cbs |
65 | { |
66 | CB_none, |
67 | CB_arg_trimdomain_list, |
68 | CB_arg_spoof, |
69 | CB_arg_bool |
70 | }; |
71 | |
72 | static 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. */ |
89 | struct hconf _res_hconf; |
90 | |
91 | /* Skip white space. */ |
92 | static const char * |
93 | skip_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. */ |
101 | static const char * |
102 | skip_string (const char *str) |
103 | { |
104 | while (*str && !isspace (*str) && *str != '#' && *str != ',') |
105 | ++str; |
106 | return str; |
107 | } |
108 | |
109 | |
110 | static const char * |
111 | arg_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 | |
166 | static const char * |
167 | arg_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 | |
188 | static const char * |
189 | arg_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 | |
219 | static void |
220 | parse_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 | |
299 | static void |
300 | do_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. */ |
357 | void |
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. */ |
369 | libc_freeres_ptr ( |
370 | static 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 | |
390 | void |
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. */ |
570 | void |
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! */ |
595 | void |
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 | |