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