]> git.ipfire.org Git - thirdparty/glibc.git/blob - resolv/res_hconf.c
Update copyright dates with scripts/update-copyrights.
[thirdparty/glibc.git] / resolv / res_hconf.c
1 /* Copyright (C) 1993-2015 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 <bits/libc-lock.h>
45 #include "ifreq.h"
46 #include "res_hconf.h"
47 #include <wchar.h>
48
49 #if IS_IN (libc)
50 # define fgets_unlocked __fgets_unlocked
51 #endif
52
53 #define _PATH_HOSTCONF "/etc/host.conf"
54
55 /* Environment vars that all user to override default behavior: */
56 #define ENV_HOSTCONF "RESOLV_HOST_CONF"
57 #define ENV_SPOOF "RESOLV_SPOOF_CHECK"
58 #define ENV_TRIM_OVERR "RESOLV_OVERRIDE_TRIM_DOMAINS"
59 #define ENV_TRIM_ADD "RESOLV_ADD_TRIM_DOMAINS"
60 #define ENV_MULTI "RESOLV_MULTI"
61 #define ENV_REORDER "RESOLV_REORDER"
62
63 enum parse_cbs
64 {
65 CB_none,
66 CB_arg_trimdomain_list,
67 CB_arg_spoof,
68 CB_arg_bool
69 };
70
71 static const struct cmd
72 {
73 const char name[11];
74 uint8_t cb;
75 unsigned int arg;
76 } cmd[] =
77 {
78 {"order", CB_none, 0},
79 {"trim", CB_arg_trimdomain_list, 0},
80 {"spoof", CB_arg_spoof, 0},
81 {"multi", CB_arg_bool, HCONF_FLAG_MULTI},
82 {"nospoof", CB_arg_bool, HCONF_FLAG_SPOOF},
83 {"spoofalert", CB_arg_bool, HCONF_FLAG_SPOOFALERT},
84 {"reorder", CB_arg_bool, HCONF_FLAG_REORDER}
85 };
86
87 /* Structure containing the state. */
88 struct hconf _res_hconf;
89
90 /* Skip white space. */
91 static const char *
92 skip_ws (const char *str)
93 {
94 while (isspace (*str)) ++str;
95 return str;
96 }
97
98
99 /* Skip until whitespace, comma, end of line, or comment character. */
100 static const char *
101 skip_string (const char *str)
102 {
103 while (*str && !isspace (*str) && *str != '#' && *str != ',')
104 ++str;
105 return str;
106 }
107
108
109 static const char *
110 arg_trimdomain_list (const char *fname, int line_num, const char *args)
111 {
112 const char * start;
113 size_t len;
114
115 do
116 {
117 start = args;
118 args = skip_string (args);
119 len = args - start;
120
121 if (_res_hconf.num_trimdomains >= TRIMDOMAINS_MAX)
122 {
123 char *buf;
124
125 if (__asprintf (&buf, _("\
126 %s: line %d: cannot specify more than %d trim domains"),
127 fname, line_num, TRIMDOMAINS_MAX) < 0)
128 return 0;
129
130 __fxprintf (NULL, "%s", buf);
131
132 free (buf);
133 return 0;
134 }
135 _res_hconf.trimdomain[_res_hconf.num_trimdomains++] =
136 __strndup (start, len);
137 args = skip_ws (args);
138 switch (*args)
139 {
140 case ',': case ';': case ':':
141 args = skip_ws (++args);
142 if (!*args || *args == '#')
143 {
144 char *buf;
145
146 if (__asprintf (&buf, _("\
147 %s: line %d: list delimiter not followed by domain"),
148 fname, line_num) < 0)
149 return 0;
150
151 __fxprintf (NULL, "%s", buf);
152
153 free (buf);
154 return 0;
155 }
156 default:
157 break;
158 }
159 }
160 while (*args && *args != '#');
161 return args;
162 }
163
164
165 static const char *
166 arg_spoof (const char *fname, int line_num, const char *args)
167 {
168 const char *start = args;
169 size_t len;
170
171 args = skip_string (args);
172 len = args - start;
173
174 if (len == 3 && __strncasecmp (start, "off", len) == 0)
175 _res_hconf.flags &= ~(HCONF_FLAG_SPOOF | HCONF_FLAG_SPOOFALERT);
176 else
177 {
178 _res_hconf.flags |= (HCONF_FLAG_SPOOF | HCONF_FLAG_SPOOFALERT);
179 if ((len == 6 && __strncasecmp (start, "nowarn", len) == 0)
180 || !(len == 4 && __strncasecmp (start, "warn", len) == 0))
181 _res_hconf.flags &= ~HCONF_FLAG_SPOOFALERT;
182 }
183 return args;
184 }
185
186
187 static const char *
188 arg_bool (const char *fname, int line_num, const char *args, unsigned flag)
189 {
190 if (__strncasecmp (args, "on", 2) == 0)
191 {
192 args += 2;
193 _res_hconf.flags |= flag;
194 }
195 else if (__strncasecmp (args, "off", 3) == 0)
196 {
197 args += 3;
198 _res_hconf.flags &= ~flag;
199 }
200 else
201 {
202 char *buf;
203
204 if (__asprintf (&buf,
205 _("%s: line %d: expected `on' or `off', found `%s'\n"),
206 fname, line_num, args) < 0)
207 return 0;
208
209 __fxprintf (NULL, "%s", buf);
210
211 free (buf);
212 return 0;
213 }
214 return args;
215 }
216
217
218 static void
219 parse_line (const char *fname, int line_num, const char *str)
220 {
221 const char *start;
222 const struct cmd *c = 0;
223 size_t len;
224 size_t i;
225
226 str = skip_ws (str);
227
228 /* skip line comment and empty lines: */
229 if (*str == '\0' || *str == '#') return;
230
231 start = str;
232 str = skip_string (str);
233 len = str - start;
234
235 for (i = 0; i < sizeof (cmd) / sizeof (cmd[0]); ++i)
236 {
237 if (__strncasecmp (start, cmd[i].name, len) == 0
238 && strlen (cmd[i].name) == len)
239 {
240 c = &cmd[i];
241 break;
242 }
243 }
244 if (c == NULL)
245 {
246 char *buf;
247
248 if (__asprintf (&buf, _("%s: line %d: bad command `%s'\n"),
249 fname, line_num, start) < 0)
250 return;
251
252 __fxprintf (NULL, "%s", buf);
253
254 free (buf);
255 return;
256 }
257
258 /* process args: */
259 str = skip_ws (str);
260
261 if (c->cb == CB_arg_trimdomain_list)
262 str = arg_trimdomain_list (fname, line_num, str);
263 else if (c->cb == CB_arg_spoof)
264 str = arg_spoof (fname, line_num, str);
265 else if (c->cb == CB_arg_bool)
266 str = arg_bool (fname, line_num, str, c->arg);
267 else
268 /* Ignore the line. */
269 return;
270
271 if (!str)
272 return;
273
274 /* rest of line must contain white space or comment only: */
275 while (*str)
276 {
277 if (!isspace (*str)) {
278 if (*str != '#')
279 {
280 char *buf;
281
282 if (__asprintf (&buf,
283 _("%s: line %d: ignoring trailing garbage `%s'\n"),
284 fname, line_num, str) < 0)
285 break;
286
287 __fxprintf (NULL, "%s", buf);
288
289 free (buf);
290 }
291 break;
292 }
293 ++str;
294 }
295 }
296
297
298 static void
299 do_init (void)
300 {
301 const char *hconf_name;
302 int line_num = 0;
303 char buf[256], *envval;
304 FILE *fp;
305
306 memset (&_res_hconf, '\0', sizeof (_res_hconf));
307
308 hconf_name = getenv (ENV_HOSTCONF);
309 if (hconf_name == NULL)
310 hconf_name = _PATH_HOSTCONF;
311
312 fp = fopen (hconf_name, "rce");
313 if (fp)
314 {
315 /* No threads using this stream. */
316 __fsetlocking (fp, FSETLOCKING_BYCALLER);
317
318 while (fgets_unlocked (buf, sizeof (buf), fp))
319 {
320 ++line_num;
321 *__strchrnul (buf, '\n') = '\0';
322 parse_line (hconf_name, line_num, buf);
323 }
324 fclose (fp);
325 }
326
327 envval = getenv (ENV_SPOOF);
328 if (envval)
329 arg_spoof (ENV_SPOOF, 1, envval);
330
331 envval = getenv (ENV_MULTI);
332 if (envval)
333 arg_bool (ENV_MULTI, 1, envval, HCONF_FLAG_MULTI);
334
335 envval = getenv (ENV_REORDER);
336 if (envval)
337 arg_bool (ENV_REORDER, 1, envval, HCONF_FLAG_REORDER);
338
339 envval = getenv (ENV_TRIM_ADD);
340 if (envval)
341 arg_trimdomain_list (ENV_TRIM_ADD, 1, envval);
342
343 envval = getenv (ENV_TRIM_OVERR);
344 if (envval)
345 {
346 _res_hconf.num_trimdomains = 0;
347 arg_trimdomain_list (ENV_TRIM_OVERR, 1, envval);
348 }
349
350 _res_hconf.initialized = 1;
351 }
352
353
354 /* Initialize hconf datastructure by reading host.conf file and
355 environment variables. */
356 void
357 _res_hconf_init (void)
358 {
359 __libc_once_define (static, once);
360
361 __libc_once (once, do_init);
362 }
363
364
365 #if IS_IN (libc)
366 # if defined SIOCGIFCONF && defined SIOCGIFNETMASK
367 /* List of known interfaces. */
368 libc_freeres_ptr (
369 static struct netaddr
370 {
371 int addrtype;
372 union
373 {
374 struct
375 {
376 u_int32_t addr;
377 u_int32_t mask;
378 } ipv4;
379 } u;
380 } *ifaddrs);
381 # endif
382
383 /* Reorder addresses returned in a hostent such that the first address
384 is an address on the local subnet, if there is such an address.
385 Otherwise, nothing is changed.
386
387 Note that this function currently only handles IPv4 addresses. */
388
389 void
390 _res_hconf_reorder_addrs (struct hostent *hp)
391 {
392 #if defined SIOCGIFCONF && defined SIOCGIFNETMASK
393 int i, j;
394 /* Number of interfaces. */
395 static int num_ifs = -1;
396 /* We need to protect the dynamic buffer handling. */
397 __libc_lock_define_initialized (static, lock);
398
399 /* Only reorder if we're supposed to. */
400 if ((_res_hconf.flags & HCONF_FLAG_REORDER) == 0)
401 return;
402
403 /* Can't deal with anything but IPv4 for now... */
404 if (hp->h_addrtype != AF_INET)
405 return;
406
407 if (num_ifs <= 0)
408 {
409 struct ifreq *ifr, *cur_ifr;
410 int sd, num, i;
411 /* Save errno. */
412 int save = errno;
413
414 /* Initialize interface table. */
415
416 /* The SIOCGIFNETMASK ioctl will only work on an AF_INET socket. */
417 sd = __socket (AF_INET, SOCK_DGRAM, 0);
418 if (sd < 0)
419 return;
420
421 /* Get lock. */
422 __libc_lock_lock (lock);
423
424 /* Recheck, somebody else might have done the work by done. */
425 if (num_ifs <= 0)
426 {
427 int new_num_ifs = 0;
428
429 /* Get a list of interfaces. */
430 __ifreq (&ifr, &num, sd);
431 if (!ifr)
432 goto cleanup;
433
434 ifaddrs = malloc (num * sizeof (ifaddrs[0]));
435 if (!ifaddrs)
436 goto cleanup1;
437
438 /* Copy usable interfaces in ifaddrs structure. */
439 for (cur_ifr = ifr, i = 0; i < num;
440 cur_ifr = __if_nextreq (cur_ifr), ++i)
441 {
442 if (cur_ifr->ifr_addr.sa_family != AF_INET)
443 continue;
444
445 ifaddrs[new_num_ifs].addrtype = AF_INET;
446 ifaddrs[new_num_ifs].u.ipv4.addr =
447 ((struct sockaddr_in *) &cur_ifr->ifr_addr)->sin_addr.s_addr;
448
449 if (__ioctl (sd, SIOCGIFNETMASK, cur_ifr) < 0)
450 continue;
451
452 ifaddrs[new_num_ifs].u.ipv4.mask =
453 ((struct sockaddr_in *) &cur_ifr->ifr_netmask)->sin_addr.s_addr;
454
455 /* Now we're committed to this entry. */
456 ++new_num_ifs;
457 }
458 /* Just keep enough memory to hold all the interfaces we want. */
459 ifaddrs = realloc (ifaddrs, new_num_ifs * sizeof (ifaddrs[0]));
460 assert (ifaddrs != NULL);
461
462 cleanup1:
463 __if_freereq (ifr, num);
464
465 cleanup:
466 /* Release lock, preserve error value, and close socket. */
467 errno = save;
468
469 num_ifs = new_num_ifs;
470
471 __libc_lock_unlock (lock);
472 }
473
474 __close (sd);
475 }
476
477 if (num_ifs == 0)
478 return;
479
480 /* Find an address for which we have a direct connection. */
481 for (i = 0; hp->h_addr_list[i]; ++i)
482 {
483 struct in_addr *haddr = (struct in_addr *) hp->h_addr_list[i];
484
485 for (j = 0; j < num_ifs; ++j)
486 {
487 u_int32_t if_addr = ifaddrs[j].u.ipv4.addr;
488 u_int32_t if_netmask = ifaddrs[j].u.ipv4.mask;
489
490 if (((haddr->s_addr ^ if_addr) & if_netmask) == 0)
491 {
492 void *tmp;
493
494 tmp = hp->h_addr_list[i];
495 hp->h_addr_list[i] = hp->h_addr_list[0];
496 hp->h_addr_list[0] = tmp;
497 return;
498 }
499 }
500 }
501 #endif /* defined(SIOCGIFCONF) && ... */
502 }
503
504
505 /* If HOSTNAME has a postfix matching any of the trimdomains, trim away
506 that postfix. Notice that HOSTNAME is modified inplace. Also, the
507 original code applied all trimdomains in order, meaning that the
508 same domainname could be trimmed multiple times. I believe this
509 was unintentional. */
510 void
511 _res_hconf_trim_domain (char *hostname)
512 {
513 size_t hostname_len, trim_len;
514 int i;
515
516 hostname_len = strlen (hostname);
517
518 for (i = 0; i < _res_hconf.num_trimdomains; ++i)
519 {
520 const char *trim = _res_hconf.trimdomain[i];
521
522 trim_len = strlen (trim);
523 if (hostname_len > trim_len
524 && __strcasecmp (&hostname[hostname_len - trim_len], trim) == 0)
525 {
526 hostname[hostname_len - trim_len] = '\0';
527 break;
528 }
529 }
530 }
531
532
533 /* Trim all hostnames/aliases in HP according to the trimdomain list.
534 Notice that HP is modified inplace! */
535 void
536 _res_hconf_trim_domains (struct hostent *hp)
537 {
538 int i;
539
540 if (_res_hconf.num_trimdomains == 0)
541 return;
542
543 _res_hconf_trim_domain (hp->h_name);
544 for (i = 0; hp->h_aliases[i]; ++i)
545 _res_hconf_trim_domain (hp->h_aliases[i]);
546 }
547 #endif