Update copyright notices with scripts/update-copyrights
[jlayton/glibc.git] / resolv / res_hconf.c
1 /* Copyright (C) 1993-2014 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 # if defined SIOCGIFCONF && defined SIOCGIFNETMASK
363 /* List of known interfaces.  */
364 libc_freeres_ptr (
365 static struct netaddr
366 {
367   int addrtype;
368   union
369   {
370     struct
371     {
372       u_int32_t addr;
373       u_int32_t mask;
374     } ipv4;
375   } u;
376 } *ifaddrs);
377 # endif
378
379 /* Reorder addresses returned in a hostent such that the first address
380    is an address on the local subnet, if there is such an address.
381    Otherwise, nothing is changed.
382
383    Note that this function currently only handles IPv4 addresses.  */
384
385 void
386 _res_hconf_reorder_addrs (struct hostent *hp)
387 {
388 #if defined SIOCGIFCONF && defined SIOCGIFNETMASK
389   int i, j;
390   /* Number of interfaces.  */
391   static int num_ifs = -1;
392   /* We need to protect the dynamic buffer handling.  */
393   __libc_lock_define_initialized (static, lock);
394
395   /* Only reorder if we're supposed to.  */
396   if ((_res_hconf.flags & HCONF_FLAG_REORDER) == 0)
397     return;
398
399   /* Can't deal with anything but IPv4 for now...  */
400   if (hp->h_addrtype != AF_INET)
401     return;
402
403   if (num_ifs <= 0)
404     {
405       struct ifreq *ifr, *cur_ifr;
406       int sd, num, i;
407       /* Save errno.  */
408       int save = errno;
409
410       /* Initialize interface table.  */
411
412       /* The SIOCGIFNETMASK ioctl will only work on an AF_INET socket.  */
413       sd = __socket (AF_INET, SOCK_DGRAM, 0);
414       if (sd < 0)
415         return;
416
417       /* Get lock.  */
418       __libc_lock_lock (lock);
419
420       /* Recheck, somebody else might have done the work by done.  */
421       if (num_ifs <= 0)
422         {
423           int new_num_ifs = 0;
424
425           /* Get a list of interfaces.  */
426           __ifreq (&ifr, &num, sd);
427           if (!ifr)
428             goto cleanup;
429
430           ifaddrs = malloc (num * sizeof (ifaddrs[0]));
431           if (!ifaddrs)
432             goto cleanup1;
433
434           /* Copy usable interfaces in ifaddrs structure.  */
435           for (cur_ifr = ifr, i = 0; i < num;
436                cur_ifr = __if_nextreq (cur_ifr), ++i)
437             {
438               if (cur_ifr->ifr_addr.sa_family != AF_INET)
439                 continue;
440
441               ifaddrs[new_num_ifs].addrtype = AF_INET;
442               ifaddrs[new_num_ifs].u.ipv4.addr =
443                 ((struct sockaddr_in *) &cur_ifr->ifr_addr)->sin_addr.s_addr;
444
445               if (__ioctl (sd, SIOCGIFNETMASK, cur_ifr) < 0)
446                 continue;
447
448               ifaddrs[new_num_ifs].u.ipv4.mask =
449                 ((struct sockaddr_in *) &cur_ifr->ifr_netmask)->sin_addr.s_addr;
450
451               /* Now we're committed to this entry.  */
452               ++new_num_ifs;
453             }
454           /* Just keep enough memory to hold all the interfaces we want.  */
455           ifaddrs = realloc (ifaddrs, new_num_ifs * sizeof (ifaddrs[0]));
456           assert (ifaddrs != NULL);
457
458         cleanup1:
459           __if_freereq (ifr, num);
460
461         cleanup:
462           /* Release lock, preserve error value, and close socket.  */
463           errno = save;
464
465           num_ifs = new_num_ifs;
466
467           __libc_lock_unlock (lock);
468         }
469
470       __close (sd);
471     }
472
473   if (num_ifs == 0)
474     return;
475
476   /* Find an address for which we have a direct connection.  */
477   for (i = 0; hp->h_addr_list[i]; ++i)
478     {
479       struct in_addr *haddr = (struct in_addr *) hp->h_addr_list[i];
480
481       for (j = 0; j < num_ifs; ++j)
482         {
483           u_int32_t if_addr    = ifaddrs[j].u.ipv4.addr;
484           u_int32_t if_netmask = ifaddrs[j].u.ipv4.mask;
485
486           if (((haddr->s_addr ^ if_addr) & if_netmask) == 0)
487             {
488               void *tmp;
489
490               tmp = hp->h_addr_list[i];
491               hp->h_addr_list[i] = hp->h_addr_list[0];
492               hp->h_addr_list[0] = tmp;
493               return;
494             }
495         }
496     }
497 #endif /* defined(SIOCGIFCONF) && ... */
498 }
499
500
501 /* If HOSTNAME has a postfix matching any of the trimdomains, trim away
502    that postfix.  Notice that HOSTNAME is modified inplace.  Also, the
503    original code applied all trimdomains in order, meaning that the
504    same domainname could be trimmed multiple times.  I believe this
505    was unintentional.  */
506 void
507 _res_hconf_trim_domain (char *hostname)
508 {
509   size_t hostname_len, trim_len;
510   int i;
511
512   hostname_len = strlen (hostname);
513
514   for (i = 0; i < _res_hconf.num_trimdomains; ++i)
515     {
516       const char *trim = _res_hconf.trimdomain[i];
517
518       trim_len = strlen (trim);
519       if (hostname_len > trim_len
520           && __strcasecmp (&hostname[hostname_len - trim_len], trim) == 0)
521         {
522           hostname[hostname_len - trim_len] = '\0';
523           break;
524         }
525     }
526 }
527
528
529 /* Trim all hostnames/aliases in HP according to the trimdomain list.
530    Notice that HP is modified inplace!  */
531 void
532 _res_hconf_trim_domains (struct hostent *hp)
533 {
534   int i;
535
536   if (_res_hconf.num_trimdomains == 0)
537     return;
538
539   _res_hconf_trim_domain (hp->h_name);
540   for (i = 0; hp->h_aliases[i]; ++i)
541     _res_hconf_trim_domain (hp->h_aliases[i]);
542 }
543 #endif