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