2006-01-08 Jakub Jelinek <jakub@redhat.com>
[glibc.git] / resolv / res_hconf.c
blobc54b28dadd4200a7ea00372d671150d51736f159
1 /* Copyright (C) 1993, 1995-2003, 2004, 2005 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_SERVORDER "RESOLV_SERV_ORDER"
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"
63 static const char *arg_service_list (const char *, int, const char *,
64 unsigned int);
65 static const char *arg_trimdomain_list (const char *, int, const char *,
66 unsigned int);
67 static const char *arg_spoof (const char *, int, const char *, unsigned int);
68 static const char *arg_bool (const char *, int, const char *, unsigned int);
70 static const struct cmd
72 const char *name;
73 const char *(*parse_args) (const char * filename, int line_num,
74 const char * args, unsigned int arg);
75 unsigned int arg;
76 } cmd[] =
78 {"order", arg_service_list, 0},
79 {"trim", arg_trimdomain_list, 0},
80 {"spoof", arg_spoof, 0},
81 {"multi", arg_bool, HCONF_FLAG_MULTI},
82 {"nospoof", arg_bool, HCONF_FLAG_SPOOF},
83 {"spoofalert", arg_bool, HCONF_FLAG_SPOOFALERT},
84 {"reorder", arg_bool, HCONF_FLAG_REORDER}
87 /* Structure containing the state. */
88 struct hconf _res_hconf;
90 /* Skip white space. */
91 static const char *
92 skip_ws (const char *str)
94 while (isspace (*str)) ++str;
95 return str;
99 /* Skip until whitespace, comma, end of line, or comment character. */
100 static const char *
101 skip_string (const char *str)
103 while (*str && !isspace (*str) && *str != '#' && *str != ',')
104 ++str;
105 return str;
109 static const char *
110 arg_service_list (const char *fname, int line_num, const char *args,
111 unsigned int arg)
113 enum Name_Service service;
114 const char *start;
115 size_t len;
116 size_t i;
117 static const struct
119 const char name[6];
120 int16_t service;
121 } svcs[] =
123 {"bind", SERVICE_BIND},
124 {"hosts", SERVICE_HOSTS},
125 {"nis", SERVICE_NIS},
130 start = args;
131 args = skip_string (args);
132 len = args - start;
134 service = SERVICE_NONE;
135 for (i = 0; i < sizeof (svcs) / sizeof (svcs[0]); ++i)
137 if (__strncasecmp (start, svcs[i].name, len) == 0
138 && len == strlen (svcs[i].name))
140 service = svcs[i].service;
141 break;
144 if (service == SERVICE_NONE)
146 char *buf;
148 if (__asprintf (&buf,
149 _("%s: line %d: expected service, found `%s'\n"),
150 fname, line_num, start) < 0)
151 return 0;
153 __fxprintf (NULL, "%s", buf);
155 free (buf);
156 return 0;
158 if (_res_hconf.num_services >= SERVICE_MAX)
160 char *buf;
162 if (__asprintf (&buf, _("\
163 %s: line %d: cannot specify more than %d services"),
164 fname, line_num, SERVICE_MAX) < 0)
165 return 0;
167 __fxprintf (NULL, "%s", buf);
169 free (buf);
170 return 0;
172 _res_hconf.service[_res_hconf.num_services++] = service;
174 args = skip_ws (args);
175 switch (*args)
177 case ',':
178 case ';':
179 case ':':
180 args = skip_ws (++args);
181 if (!*args || *args == '#')
183 char *buf;
185 if (__asprintf (&buf, _("\
186 %s: line %d: list delimiter not followed by keyword"),
187 fname, line_num) < 0)
188 return 0;
190 __fxprintf (NULL, "%s", buf);
192 free (buf);
193 return 0;
195 default:
196 break;
199 while (*args && *args != '#');
200 return args;
204 static const char *
205 arg_trimdomain_list (const char *fname, int line_num, const char *args,
206 unsigned int flag)
208 const char * start;
209 size_t len;
213 start = args;
214 args = skip_string (args);
215 len = args - start;
217 if (_res_hconf.num_trimdomains >= TRIMDOMAINS_MAX)
219 char *buf;
221 if (__asprintf (&buf, _("\
222 %s: line %d: cannot specify more than %d trim domains"),
223 fname, line_num, TRIMDOMAINS_MAX) < 0)
224 return 0;
226 __fxprintf (NULL, "%s", buf);
228 free (buf);
229 return 0;
231 _res_hconf.trimdomain[_res_hconf.num_trimdomains++] =
232 __strndup (start, len);
233 args = skip_ws (args);
234 switch (*args)
236 case ',': case ';': case ':':
237 args = skip_ws (++args);
238 if (!*args || *args == '#')
240 char *buf;
242 if (__asprintf (&buf, _("\
243 %s: line %d: list delimiter not followed by domain"),
244 fname, line_num) < 0)
245 return 0;
247 __fxprintf (NULL, "%s", buf);
249 free (buf);
250 return 0;
252 default:
253 break;
256 while (*args && *args != '#');
257 return args;
261 static const char *
262 arg_spoof (const char *fname, int line_num, const char *args, unsigned flag)
264 const char *start = args;
265 size_t len;
267 args = skip_string (args);
268 len = args - start;
270 if (len == 3 && __strncasecmp (start, "off", len) == 0)
271 _res_hconf.flags &= ~(HCONF_FLAG_SPOOF | HCONF_FLAG_SPOOFALERT);
272 else
274 _res_hconf.flags |= (HCONF_FLAG_SPOOF | HCONF_FLAG_SPOOFALERT);
275 if ((len == 6 && __strncasecmp (start, "nowarn", len) == 0)
276 || !(len == 4 && __strncasecmp (start, "warn", len) == 0))
277 _res_hconf.flags &= ~HCONF_FLAG_SPOOFALERT;
279 return args;
283 static const char *
284 arg_bool (const char *fname, int line_num, const char *args, unsigned flag)
286 if (__strncasecmp (args, "on", 2) == 0)
288 args += 2;
289 _res_hconf.flags |= flag;
291 else if (__strncasecmp (args, "off", 3) == 0)
293 args += 3;
294 _res_hconf.flags &= ~flag;
296 else
298 char *buf;
300 if (__asprintf (&buf,
301 _("%s: line %d: expected `on' or `off', found `%s'\n"),
302 fname, line_num, args) < 0)
303 return 0;
305 __fxprintf (NULL, "%s", buf);
307 free (buf);
308 return 0;
310 return args;
314 static void
315 parse_line (const char *fname, int line_num, const char *str)
317 const char *start;
318 const struct cmd *c = 0;
319 size_t len;
320 size_t i;
322 str = skip_ws (str);
324 /* skip line comment and empty lines: */
325 if (*str == '\0' || *str == '#') return;
327 start = str;
328 str = skip_string (str);
329 len = str - start;
331 for (i = 0; i < sizeof (cmd) / sizeof (cmd[0]); ++i)
333 if (__strncasecmp (start, cmd[i].name, len) == 0
334 && strlen (cmd[i].name) == len)
336 c = &cmd[i];
337 break;
340 if (c == NULL)
342 char *buf;
344 if (__asprintf (&buf, _("%s: line %d: bad command `%s'\n"),
345 fname, line_num, start) < 0)
346 return;
348 __fxprintf (NULL, "%s", buf);
350 free (buf);
351 return;
354 /* process args: */
355 str = skip_ws (str);
356 str = (*c->parse_args) (fname, line_num, str, c->arg);
357 if (!str)
358 return;
360 /* rest of line must contain white space or comment only: */
361 while (*str)
363 if (!isspace (*str)) {
364 if (*str != '#')
366 char *buf;
368 if (__asprintf (&buf,
369 _("%s: line %d: ignoring trailing garbage `%s'\n"),
370 fname, line_num, str) < 0)
371 break;
373 __fxprintf (NULL, "%s", buf);
375 free (buf);
377 break;
379 ++str;
384 static void
385 do_init (void)
387 const char *hconf_name;
388 int line_num = 0;
389 char buf[256], *envval;
390 FILE *fp;
392 memset (&_res_hconf, '\0', sizeof (_res_hconf));
394 hconf_name = getenv (ENV_HOSTCONF);
395 if (hconf_name == NULL)
396 hconf_name = _PATH_HOSTCONF;
398 fp = fopen (hconf_name, "rc");
399 if (!fp)
400 /* make up something reasonable: */
401 _res_hconf.service[_res_hconf.num_services++] = SERVICE_BIND;
402 else
404 /* No threads using this stream. */
405 __fsetlocking (fp, FSETLOCKING_BYCALLER);
407 while (fgets_unlocked (buf, sizeof (buf), fp))
409 ++line_num;
410 *__strchrnul (buf, '\n') = '\0';
411 parse_line (hconf_name, line_num, buf);
413 fclose (fp);
416 envval = getenv (ENV_SERVORDER);
417 if (envval)
419 _res_hconf.num_services = 0;
420 arg_service_list (ENV_SERVORDER, 1, envval, 0);
423 envval = getenv (ENV_SPOOF);
424 if (envval)
425 arg_spoof (ENV_SPOOF, 1, envval, 0);
427 envval = getenv (ENV_MULTI);
428 if (envval)
429 arg_bool (ENV_MULTI, 1, envval, HCONF_FLAG_MULTI);
431 envval = getenv (ENV_REORDER);
432 if (envval)
433 arg_bool (ENV_REORDER, 1, envval, HCONF_FLAG_REORDER);
435 envval = getenv (ENV_TRIM_ADD);
436 if (envval)
437 arg_trimdomain_list (ENV_TRIM_ADD, 1, envval, 0);
439 envval = getenv (ENV_TRIM_OVERR);
440 if (envval)
442 _res_hconf.num_trimdomains = 0;
443 arg_trimdomain_list (ENV_TRIM_OVERR, 1, envval, 0);
446 _res_hconf.initialized = 1;
450 /* Initialize hconf datastructure by reading host.conf file and
451 environment variables. */
452 void
453 _res_hconf_init (void)
455 __libc_once_define (static, once);
457 __libc_once (once, do_init);
461 /* List of known interfaces. */
462 libc_freeres_ptr (
463 static struct netaddr
465 int addrtype;
466 union
468 struct
470 u_int32_t addr;
471 u_int32_t mask;
472 } ipv4;
473 } u;
474 } *ifaddrs);
476 /* We need to protect the dynamic buffer handling. */
477 __libc_lock_define_initialized (static, lock);
479 /* Reorder addresses returned in a hostent such that the first address
480 is an address on the local subnet, if there is such an address.
481 Otherwise, nothing is changed.
483 Note that this function currently only handles IPv4 addresses. */
485 void
486 _res_hconf_reorder_addrs (struct hostent *hp)
488 #if defined SIOCGIFCONF && defined SIOCGIFNETMASK
489 int i, j;
490 /* Number of interfaces. */
491 static int num_ifs = -1;
493 /* Only reorder if we're supposed to. */
494 if ((_res_hconf.flags & HCONF_FLAG_REORDER) == 0)
495 return;
497 /* Can't deal with anything but IPv4 for now... */
498 if (hp->h_addrtype != AF_INET)
499 return;
501 if (num_ifs <= 0)
503 struct ifreq *ifr, *cur_ifr;
504 int sd, num, i;
505 /* Save errno. */
506 int save = errno;
508 /* Initialize interface table. */
510 num_ifs = 0;
512 /* The SIOCGIFNETMASK ioctl will only work on an AF_INET socket. */
513 sd = __socket (AF_INET, SOCK_DGRAM, 0);
514 if (sd < 0)
515 return;
517 /* Get lock. */
518 __libc_lock_lock (lock);
520 /* Get a list of interfaces. */
521 __ifreq (&ifr, &num, sd);
522 if (!ifr)
523 goto cleanup;
525 ifaddrs = malloc (num * sizeof (ifaddrs[0]));
526 if (!ifaddrs)
527 goto cleanup1;
529 /* Copy usable interfaces in ifaddrs structure. */
530 for (cur_ifr = ifr, i = 0; i < num; cur_ifr = __if_nextreq (cur_ifr), ++i)
532 if (cur_ifr->ifr_addr.sa_family != AF_INET)
533 continue;
535 ifaddrs[num_ifs].addrtype = AF_INET;
536 ifaddrs[num_ifs].u.ipv4.addr =
537 ((struct sockaddr_in *) &cur_ifr->ifr_addr)->sin_addr.s_addr;
539 if (__ioctl (sd, SIOCGIFNETMASK, cur_ifr) < 0)
540 continue;
542 ifaddrs[num_ifs].u.ipv4.mask =
543 ((struct sockaddr_in *) &cur_ifr->ifr_netmask)->sin_addr.s_addr;
545 /* Now we're committed to this entry. */
546 ++num_ifs;
548 /* Just keep enough memory to hold all the interfaces we want. */
549 ifaddrs = realloc (ifaddrs, num_ifs * sizeof (ifaddrs[0]));
550 assert (ifaddrs != NULL);
552 cleanup1:
553 __if_freereq (ifr, num);
555 cleanup:
556 /* Release lock, preserve error value, and close socket. */
557 save = errno;
558 __libc_lock_unlock (lock);
559 __close (sd);
562 if (num_ifs == 0)
563 return;
565 /* Find an address for which we have a direct connection. */
566 for (i = 0; hp->h_addr_list[i]; ++i)
568 struct in_addr *haddr = (struct in_addr *) hp->h_addr_list[i];
570 for (j = 0; j < num_ifs; ++j)
572 u_int32_t if_addr = ifaddrs[j].u.ipv4.addr;
573 u_int32_t if_netmask = ifaddrs[j].u.ipv4.mask;
575 if (((haddr->s_addr ^ if_addr) & if_netmask) == 0)
577 void *tmp;
579 tmp = hp->h_addr_list[i];
580 hp->h_addr_list[i] = hp->h_addr_list[0];
581 hp->h_addr_list[0] = tmp;
582 return;
586 #endif /* defined(SIOCGIFCONF) && ... */
590 /* If HOSTNAME has a postfix matching any of the trimdomains, trim away
591 that postfix. Notice that HOSTNAME is modified inplace. Also, the
592 original code applied all trimdomains in order, meaning that the
593 same domainname could be trimmed multiple times. I believe this
594 was unintentional. */
595 void
596 _res_hconf_trim_domain (char *hostname)
598 size_t hostname_len, trim_len;
599 int i;
601 hostname_len = strlen (hostname);
603 for (i = 0; i < _res_hconf.num_trimdomains; ++i)
605 const char *trim = _res_hconf.trimdomain[i];
607 trim_len = strlen (trim);
608 if (hostname_len > trim_len
609 && __strcasecmp (&hostname[hostname_len - trim_len], trim) == 0)
611 hostname[hostname_len - trim_len] = '\0';
612 break;
618 /* Trim all hostnames/aliases in HP according to the trimdomain list.
619 Notice that HP is modified inplace! */
620 void
621 _res_hconf_trim_domains (struct hostent *hp)
623 int i;
625 if (_res_hconf.num_trimdomains == 0)
626 return;
628 _res_hconf_trim_domain (hp->h_name);
629 for (i = 0; hp->h_aliases[i]; ++i)
630 _res_hconf_trim_domain (hp->h_aliases[i]);