[BZ #2389]
[glibc.git] / resolv / res_hconf.c
blob6c7cdb6c8296f671ef83fc59275a7bbca8d601c0
1 /* Copyright (C) 1993, 1995-2005, 2006 Free Software Foundation, Inc.
2 This file is part of the GNU C Library.
3 Contributed by David Mosberger (davidm@azstarnet.com).
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.
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.
15 You should have received a copy of the GNU Lesser General Public
16 License along with the GNU C Library; if not, write to the Free
17 Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
18 02111-1307 USA. */
20 /* This file provides a Linux /etc/host.conf compatible front end to
21 the various name resolvers (/etc/hosts, named, NIS server, etc.).
22 Though mostly compatibly, the following differences exist compared
23 to the original implementation:
25 - new command "spoof" takes an arguments like RESOLV_SPOOF_CHECK
26 environment variable (i.e., `off', `nowarn', or `warn').
28 - line comments can appear anywhere (not just at the beginning of
29 a line)
32 #include <assert.h>
33 #include <errno.h>
34 #include <ctype.h>
35 #include <libintl.h>
36 #include <memory.h>
37 #include <stdio.h>
38 #include <stdio_ext.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <net/if.h>
42 #include <sys/ioctl.h>
43 #include <unistd.h>
44 #include <netinet/in.h>
45 #include <bits/libc-lock.h>
46 #include "ifreq.h"
47 #include "res_hconf.h"
48 #ifdef USE_IN_LIBIO
49 # include <wchar.h>
50 #endif
52 #define _PATH_HOSTCONF "/etc/host.conf"
54 /* Environment vars that all user to override default behavior: */
55 #define ENV_HOSTCONF "RESOLV_HOST_CONF"
56 #define ENV_SPOOF "RESOLV_SPOOF_CHECK"
57 #define ENV_TRIM_OVERR "RESOLV_OVERRIDE_TRIM_DOMAINS"
58 #define ENV_TRIM_ADD "RESOLV_ADD_TRIM_DOMAINS"
59 #define ENV_MULTI "RESOLV_MULTI"
60 #define ENV_REORDER "RESOLV_REORDER"
62 static const char *arg_trimdomain_list (const char *, int, const char *,
63 unsigned int);
64 static const char *arg_spoof (const char *, int, const char *, unsigned int);
65 static const char *arg_bool (const char *, int, const char *, unsigned int);
67 static const struct cmd
69 const char *name;
70 const char *(*parse_args) (const char * filename, int line_num,
71 const char * args, unsigned int arg);
72 unsigned int arg;
73 } cmd[] =
75 {"order", NULL, 0},
76 {"trim", arg_trimdomain_list, 0},
77 {"spoof", arg_spoof, 0},
78 {"multi", arg_bool, HCONF_FLAG_MULTI},
79 {"nospoof", arg_bool, HCONF_FLAG_SPOOF},
80 {"spoofalert", arg_bool, HCONF_FLAG_SPOOFALERT},
81 {"reorder", arg_bool, HCONF_FLAG_REORDER}
84 /* Structure containing the state. */
85 struct hconf _res_hconf;
87 /* Skip white space. */
88 static const char *
89 skip_ws (const char *str)
91 while (isspace (*str)) ++str;
92 return str;
96 /* Skip until whitespace, comma, end of line, or comment character. */
97 static const char *
98 skip_string (const char *str)
100 while (*str && !isspace (*str) && *str != '#' && *str != ',')
101 ++str;
102 return str;
106 static const char *
107 arg_trimdomain_list (const char *fname, int line_num, const char *args,
108 unsigned int flag)
110 const char * start;
111 size_t len;
115 start = args;
116 args = skip_string (args);
117 len = args - start;
119 if (_res_hconf.num_trimdomains >= TRIMDOMAINS_MAX)
121 char *buf;
123 if (__asprintf (&buf, _("\
124 %s: line %d: cannot specify more than %d trim domains"),
125 fname, line_num, TRIMDOMAINS_MAX) < 0)
126 return 0;
128 __fxprintf (NULL, "%s", buf);
130 free (buf);
131 return 0;
133 _res_hconf.trimdomain[_res_hconf.num_trimdomains++] =
134 __strndup (start, len);
135 args = skip_ws (args);
136 switch (*args)
138 case ',': case ';': case ':':
139 args = skip_ws (++args);
140 if (!*args || *args == '#')
142 char *buf;
144 if (__asprintf (&buf, _("\
145 %s: line %d: list delimiter not followed by domain"),
146 fname, line_num) < 0)
147 return 0;
149 __fxprintf (NULL, "%s", buf);
151 free (buf);
152 return 0;
154 default:
155 break;
158 while (*args && *args != '#');
159 return args;
163 static const char *
164 arg_spoof (const char *fname, int line_num, const char *args, unsigned flag)
166 const char *start = args;
167 size_t len;
169 args = skip_string (args);
170 len = args - start;
172 if (len == 3 && __strncasecmp (start, "off", len) == 0)
173 _res_hconf.flags &= ~(HCONF_FLAG_SPOOF | HCONF_FLAG_SPOOFALERT);
174 else
176 _res_hconf.flags |= (HCONF_FLAG_SPOOF | HCONF_FLAG_SPOOFALERT);
177 if ((len == 6 && __strncasecmp (start, "nowarn", len) == 0)
178 || !(len == 4 && __strncasecmp (start, "warn", len) == 0))
179 _res_hconf.flags &= ~HCONF_FLAG_SPOOFALERT;
181 return args;
185 static const char *
186 arg_bool (const char *fname, int line_num, const char *args, unsigned flag)
188 if (__strncasecmp (args, "on", 2) == 0)
190 args += 2;
191 _res_hconf.flags |= flag;
193 else if (__strncasecmp (args, "off", 3) == 0)
195 args += 3;
196 _res_hconf.flags &= ~flag;
198 else
200 char *buf;
202 if (__asprintf (&buf,
203 _("%s: line %d: expected `on' or `off', found `%s'\n"),
204 fname, line_num, args) < 0)
205 return 0;
207 __fxprintf (NULL, "%s", buf);
209 free (buf);
210 return 0;
212 return args;
216 static void
217 parse_line (const char *fname, int line_num, const char *str)
219 const char *start;
220 const struct cmd *c = 0;
221 size_t len;
222 size_t i;
224 str = skip_ws (str);
226 /* skip line comment and empty lines: */
227 if (*str == '\0' || *str == '#') return;
229 start = str;
230 str = skip_string (str);
231 len = str - start;
233 for (i = 0; i < sizeof (cmd) / sizeof (cmd[0]); ++i)
235 if (__strncasecmp (start, cmd[i].name, len) == 0
236 && strlen (cmd[i].name) == len)
238 c = &cmd[i];
239 break;
242 if (c == NULL)
244 char *buf;
246 if (__asprintf (&buf, _("%s: line %d: bad command `%s'\n"),
247 fname, line_num, start) < 0)
248 return;
250 __fxprintf (NULL, "%s", buf);
252 free (buf);
253 return;
256 /* Ignore lines for which no parser is set. This is used for
257 obsolete commands. */
258 if (c->parse_args == NULL)
259 return;
261 /* process args: */
262 str = skip_ws (str);
263 str = (*c->parse_args) (fname, line_num, str, c->arg);
264 if (!str)
265 return;
267 /* rest of line must contain white space or comment only: */
268 while (*str)
270 if (!isspace (*str)) {
271 if (*str != '#')
273 char *buf;
275 if (__asprintf (&buf,
276 _("%s: line %d: ignoring trailing garbage `%s'\n"),
277 fname, line_num, str) < 0)
278 break;
280 __fxprintf (NULL, "%s", buf);
282 free (buf);
284 break;
286 ++str;
291 static void
292 do_init (void)
294 const char *hconf_name;
295 int line_num = 0;
296 char buf[256], *envval;
297 FILE *fp;
299 memset (&_res_hconf, '\0', sizeof (_res_hconf));
301 hconf_name = getenv (ENV_HOSTCONF);
302 if (hconf_name == NULL)
303 hconf_name = _PATH_HOSTCONF;
305 fp = fopen (hconf_name, "rc");
306 if (fp)
308 /* No threads using this stream. */
309 __fsetlocking (fp, FSETLOCKING_BYCALLER);
311 while (fgets_unlocked (buf, sizeof (buf), fp))
313 ++line_num;
314 *__strchrnul (buf, '\n') = '\0';
315 parse_line (hconf_name, line_num, buf);
317 fclose (fp);
320 envval = getenv (ENV_SPOOF);
321 if (envval)
322 arg_spoof (ENV_SPOOF, 1, envval, 0);
324 envval = getenv (ENV_MULTI);
325 if (envval)
326 arg_bool (ENV_MULTI, 1, envval, HCONF_FLAG_MULTI);
328 envval = getenv (ENV_REORDER);
329 if (envval)
330 arg_bool (ENV_REORDER, 1, envval, HCONF_FLAG_REORDER);
332 envval = getenv (ENV_TRIM_ADD);
333 if (envval)
334 arg_trimdomain_list (ENV_TRIM_ADD, 1, envval, 0);
336 envval = getenv (ENV_TRIM_OVERR);
337 if (envval)
339 _res_hconf.num_trimdomains = 0;
340 arg_trimdomain_list (ENV_TRIM_OVERR, 1, envval, 0);
343 _res_hconf.initialized = 1;
347 /* Initialize hconf datastructure by reading host.conf file and
348 environment variables. */
349 void
350 _res_hconf_init (void)
352 __libc_once_define (static, once);
354 __libc_once (once, do_init);
358 /* List of known interfaces. */
359 libc_freeres_ptr (
360 static struct netaddr
362 int addrtype;
363 union
365 struct
367 u_int32_t addr;
368 u_int32_t mask;
369 } ipv4;
370 } u;
371 } *ifaddrs);
373 /* We need to protect the dynamic buffer handling. */
374 __libc_lock_define_initialized (static, lock);
376 /* Reorder addresses returned in a hostent such that the first address
377 is an address on the local subnet, if there is such an address.
378 Otherwise, nothing is changed.
380 Note that this function currently only handles IPv4 addresses. */
382 void
383 _res_hconf_reorder_addrs (struct hostent *hp)
385 #if defined SIOCGIFCONF && defined SIOCGIFNETMASK
386 int i, j;
387 /* Number of interfaces. */
388 static int num_ifs = -1;
390 /* Only reorder if we're supposed to. */
391 if ((_res_hconf.flags & HCONF_FLAG_REORDER) == 0)
392 return;
394 /* Can't deal with anything but IPv4 for now... */
395 if (hp->h_addrtype != AF_INET)
396 return;
398 if (num_ifs <= 0)
400 struct ifreq *ifr, *cur_ifr;
401 int sd, num, i;
402 /* Save errno. */
403 int save = errno;
405 /* Initialize interface table. */
407 num_ifs = 0;
409 /* The SIOCGIFNETMASK ioctl will only work on an AF_INET socket. */
410 sd = __socket (AF_INET, SOCK_DGRAM, 0);
411 if (sd < 0)
412 return;
414 /* Get lock. */
415 __libc_lock_lock (lock);
417 /* Get a list of interfaces. */
418 __ifreq (&ifr, &num, sd);
419 if (!ifr)
420 goto cleanup;
422 ifaddrs = malloc (num * sizeof (ifaddrs[0]));
423 if (!ifaddrs)
424 goto cleanup1;
426 /* Copy usable interfaces in ifaddrs structure. */
427 for (cur_ifr = ifr, i = 0; i < num; cur_ifr = __if_nextreq (cur_ifr), ++i)
429 if (cur_ifr->ifr_addr.sa_family != AF_INET)
430 continue;
432 ifaddrs[num_ifs].addrtype = AF_INET;
433 ifaddrs[num_ifs].u.ipv4.addr =
434 ((struct sockaddr_in *) &cur_ifr->ifr_addr)->sin_addr.s_addr;
436 if (__ioctl (sd, SIOCGIFNETMASK, cur_ifr) < 0)
437 continue;
439 ifaddrs[num_ifs].u.ipv4.mask =
440 ((struct sockaddr_in *) &cur_ifr->ifr_netmask)->sin_addr.s_addr;
442 /* Now we're committed to this entry. */
443 ++num_ifs;
445 /* Just keep enough memory to hold all the interfaces we want. */
446 ifaddrs = realloc (ifaddrs, num_ifs * sizeof (ifaddrs[0]));
447 assert (ifaddrs != NULL);
449 cleanup1:
450 __if_freereq (ifr, num);
452 cleanup:
453 /* Release lock, preserve error value, and close socket. */
454 save = errno;
455 __libc_lock_unlock (lock);
456 __close (sd);
459 if (num_ifs == 0)
460 return;
462 /* Find an address for which we have a direct connection. */
463 for (i = 0; hp->h_addr_list[i]; ++i)
465 struct in_addr *haddr = (struct in_addr *) hp->h_addr_list[i];
467 for (j = 0; j < num_ifs; ++j)
469 u_int32_t if_addr = ifaddrs[j].u.ipv4.addr;
470 u_int32_t if_netmask = ifaddrs[j].u.ipv4.mask;
472 if (((haddr->s_addr ^ if_addr) & if_netmask) == 0)
474 void *tmp;
476 tmp = hp->h_addr_list[i];
477 hp->h_addr_list[i] = hp->h_addr_list[0];
478 hp->h_addr_list[0] = tmp;
479 return;
483 #endif /* defined(SIOCGIFCONF) && ... */
487 /* If HOSTNAME has a postfix matching any of the trimdomains, trim away
488 that postfix. Notice that HOSTNAME is modified inplace. Also, the
489 original code applied all trimdomains in order, meaning that the
490 same domainname could be trimmed multiple times. I believe this
491 was unintentional. */
492 void
493 _res_hconf_trim_domain (char *hostname)
495 size_t hostname_len, trim_len;
496 int i;
498 hostname_len = strlen (hostname);
500 for (i = 0; i < _res_hconf.num_trimdomains; ++i)
502 const char *trim = _res_hconf.trimdomain[i];
504 trim_len = strlen (trim);
505 if (hostname_len > trim_len
506 && __strcasecmp (&hostname[hostname_len - trim_len], trim) == 0)
508 hostname[hostname_len - trim_len] = '\0';
509 break;
515 /* Trim all hostnames/aliases in HP according to the trimdomain list.
516 Notice that HP is modified inplace! */
517 void
518 _res_hconf_trim_domains (struct hostent *hp)
520 int i;
522 if (_res_hconf.num_trimdomains == 0)
523 return;
525 _res_hconf_trim_domain (hp->h_name);
526 for (i = 0; hp->h_aliases[i]; ++i)
527 _res_hconf_trim_domain (hp->h_aliases[i]);