Rewritten to use generic cache handling functions in cache.c.
[kopensolaris-gnu/glibc.git] / resolv / res_hconf.c
1 /* Copyright (C) 1993, 1995, 1996, 1997 Free Software Foundation, Inc.
2    Contributed by David Mosberger (davidm@azstarnet.com).
3
4    The GNU C Library is free software; you can redistribute it and/or
5    modify it under the terms of the GNU Library General Public License as
6    published by the Free Software Foundation; either version 2 of the
7    License, or (at your option) any later version.
8
9    The GNU C Library is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12    Library General Public License for more details.
13
14    You should have received a copy of the GNU Library General Public
15    License along with the GNU C Library; see the file COPYING.LIB.  If not,
16    write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17    Boston, MA 02111-1307, USA.  */
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 #include <ctype.h>
31 #include <memory.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35
36 #include "res_hconf.h"
37
38 #define _PATH_HOSTCONF  "/etc/host.conf"
39
40 /* Environment vars that all user to override default behavior:  */
41 #define ENV_HOSTCONF    "RESOLV_HOST_CONF"
42 #define ENV_SERVORDER   "RESOLV_SERV_ORDER"
43 #define ENV_SPOOF       "RESOLV_SPOOF_CHECK"
44 #define ENV_TRIM_OVERR  "RESOLV_OVERRIDE_TRIM_DOMAINS"
45 #define ENV_TRIM_ADD    "RESOLV_ADD_TRIM_DOMAINS"
46 #define ENV_MULTI       "RESOLV_MULTI"
47 #define ENV_REORDER     "RESOLV_REORDER"
48
49 static const char * arg_service_list (const char *, int, const char *,
50                                       unsigned);
51 static const char * arg_trimdomain_list (const char *, int, const char *,
52                                          unsigned);
53 static const char * arg_spoof (const char *, int, const char *, unsigned);
54 static const char * arg_bool (const char *, int, const char *, unsigned);
55
56 static struct cmd {
57   const char *  name;
58   const char *  (*parse_args)(const char * filename, int line_num,
59                               const char * args, unsigned arg);
60   unsigned      arg;;
61 } cmd[] = {
62   {"order",             arg_service_list,       0},
63   {"trim",              arg_trimdomain_list,    0},
64   {"spoof",             arg_spoof,              0},
65   {"multi",             arg_bool,               HCONF_FLAG_MULTI},
66   {"nospoof",           arg_bool,               HCONF_FLAG_SPOOF},
67   {"spoofalert",        arg_bool,               HCONF_FLAG_SPOOFALERT},
68   {"reorder",           arg_bool,               HCONF_FLAG_REORDER}
69 };
70
71
72 /* Skip white space.  */
73 static const char *
74 skip_ws (const char * str)
75 {
76   while (isspace (*str)) ++str;
77   return str;
78 }
79
80
81 /* Skip until whitespace, comma, end of line, or comment character.  */
82 static const char *
83 skip_string (const char * str)
84 {
85   while (*str && !isspace (*str) && *str != '#' && *str != ',') ++str;
86   return str;
87 }
88
89
90 static const char *
91 arg_service_list (const char * fname, int line_num, const char * args,
92                   unsigned arg)
93 {
94   enum Name_Service service;
95   const char * start;
96   size_t len;
97   int i;
98   static struct {
99     const char *        name;
100     enum Name_Service   service;
101   } svcs[] = {
102     {"bind",    SERVICE_BIND},
103     {"hosts",   SERVICE_HOSTS},
104     {"nis",     SERVICE_NIS},
105   };
106
107   do
108     {
109       start = args;
110       args = skip_string (args);
111       len = args - start;
112
113       service = SERVICE_NONE;
114       for (i = 0; i < sizeof (svcs) / sizeof (svcs[0]); ++i)
115         {
116           if (strncasecmp (start, svcs[i].name, len) == 0
117               && len == strlen (svcs[i].name))
118           {
119             service = svcs[i].service;
120             break;
121           }
122       }
123       if (service == SERVICE_NONE)
124         {
125           fprintf (stderr, "%s: line %d: expected service, found `%s'\n",
126                    fname, line_num, start);
127           return 0;
128         }
129       if (_res_hconf.num_services >= SERVICE_MAX)
130         {
131           fprintf (stderr, "%s: line %d: cannot specify more than %d services",
132                    fname, line_num, SERVICE_MAX);
133           return 0;
134         }
135       _res_hconf.service[_res_hconf.num_services++] = service;
136
137       args = skip_ws (args);
138       switch (*args)
139         {
140         case ',': case ';': case ':':
141           args = skip_ws (++args);
142           if (!*args || *args == '#')
143             {
144               fprintf (stderr,
145                        "%s: line %d: list delimiter not followed by keyword",
146                        fname, line_num);
147               return 0;
148             }
149         default:
150           break;
151         }
152     }
153   while (*args && *args != '#');
154   return args;
155 }
156
157
158 static const char *
159 arg_trimdomain_list (const char * fname, int line_num, const char * args,
160                      unsigned flag)
161 {
162   const char * start;
163   size_t len;
164
165   do
166     {
167       start = args;
168       args = skip_string (args);
169       len = args - start;
170
171       if (_res_hconf.num_trimdomains >= TRIMDOMAINS_MAX)
172         {
173           fprintf (stderr,
174                    "%s: line %d: cannot specify more than %d trim domains",
175                    fname, line_num, TRIMDOMAINS_MAX);
176           return 0;
177         }
178       _res_hconf.trimdomain[_res_hconf.num_trimdomains++] =
179           strndup (start, len);
180       args = skip_ws (args);
181       switch (*args)
182         {
183         case ',': case ';': case ':':
184           args = skip_ws (++args);
185           if (!*args || *args == '#')
186             {
187               fprintf (stderr,
188                        "%s: line %d: list delimiter not followed by domain",
189                        fname, line_num);
190               return 0;
191             }
192         default:
193           break;
194         }
195     }
196   while (*args && *args != '#');
197   return args;
198 }
199
200
201 static const char *
202 arg_spoof (const char * fname, int line_num, const char * args, unsigned flag)
203 {
204   const char * start = args;
205   size_t len;
206
207   args = skip_string (args);
208   len = args - start;
209
210   if (len == 3 && strncasecmp (start, "off", len) == 0)
211     _res_hconf.flags &= ~(HCONF_FLAG_SPOOF | HCONF_FLAG_SPOOFALERT);
212   else
213     {
214       _res_hconf.flags |= (HCONF_FLAG_SPOOF | HCONF_FLAG_SPOOFALERT);
215       if ((len == 6 && strncasecmp (start, "nowarn", len) == 0)
216           || !(len == 4 && strncasecmp (start, "warn", len) == 0))
217         _res_hconf.flags &= ~HCONF_FLAG_SPOOFALERT;
218     }
219   return args;
220 }
221
222
223 static const char *
224 arg_bool (const char * fname, int line_num, const char * args, unsigned flag)
225 {
226   if (strncasecmp (args, "on", 2) == 0)
227     {
228       args += 2;
229       _res_hconf.flags |= flag;
230     }
231   else if (strncasecmp (args, "off", 3) == 0)
232     {
233       args += 3;
234       _res_hconf.flags &= ~flag;
235     }
236   else
237     {
238       fprintf (stderr, "%s: line %d: expected `on' or `off', found `%s'\n",
239                fname, line_num, args);
240       return 0;
241     }
242   return args;
243 }
244
245
246 static void
247 parse_line (const char * fname, int line_num, const char * str)
248 {
249   const char * start;
250   struct cmd * c = 0;
251   size_t len;
252   int i;
253
254   str = skip_ws (str);
255
256   if (*str == '#') return;              /* skip line comment */
257
258   start = str;
259   str = skip_string (str);
260   len = str - start;
261
262   for (i = 0; i < sizeof (cmd) / sizeof (cmd[0]); ++i)
263     {
264       if (strncasecmp (start, cmd[i].name, len) == 0
265           && strlen (cmd[i].name) == len)
266         {
267           c = &cmd[i];
268           break;
269         }
270     }
271   if (!c)
272     {
273       fprintf (stderr, "%s: line %d: bad command `%s'\n",
274                fname, line_num, start);
275       return;
276     }
277
278   /* process args: */
279   str = skip_ws (str);
280   str = (*c->parse_args) (fname, line_num, str, c->arg);
281   if (!str)
282     return;
283
284   /* rest of line must contain white space or comment only: */
285   while (*str)
286     {
287       if (!isspace (*str)) {
288         if (*str != '#')
289           fprintf (stderr, "%s: line %d: ignoring trailing garbage `%s'\n",
290                    fname, line_num, str);
291         break;
292       }
293       ++str;
294     }
295 }
296
297
298 /* Initialize hconf datastructure by reading host.conf file and
299    environment variables.  */
300 void
301 _res_hconf_init (void)
302 {
303   const char * hconf_name;
304   int line_num = 0;
305   char buf[256], * end, * envval;
306   FILE * fp;
307
308   memset (&_res_hconf, 0, sizeof (_res_hconf));
309
310   hconf_name = getenv (ENV_HOSTCONF);
311   if (!hconf_name)
312     hconf_name = _PATH_HOSTCONF;
313
314   fp = fopen (hconf_name, "r");
315   if (!fp)
316     /* make up something reasonable: */
317     _res_hconf.service[_res_hconf.num_services++] = SERVICE_BIND;
318   else
319     {
320       while (fgets (buf, sizeof (buf), fp))
321         {
322           ++line_num;
323           end = strchr (buf, '\n');
324           if (end)
325             *end = '\0';
326           parse_line (hconf_name, line_num, buf);
327         }
328       fclose (fp);
329     }
330
331   envval = getenv (ENV_SERVORDER);
332   if (envval)
333     {
334       _res_hconf.num_services = 0;
335       arg_service_list (ENV_SERVORDER, 1, envval, 0);
336     }
337
338   envval = getenv (ENV_SPOOF);
339   if (envval)
340     arg_spoof (ENV_SPOOF, 1, envval, 0);
341
342   envval = getenv (ENV_MULTI);
343   if (envval)
344     arg_bool (ENV_MULTI, 1, envval, HCONF_FLAG_MULTI);
345
346   envval = getenv (ENV_REORDER);
347   if (envval)
348     arg_bool (ENV_REORDER, 1, envval, HCONF_FLAG_REORDER);
349
350   envval = getenv (ENV_TRIM_ADD);
351   if (envval)
352     arg_trimdomain_list (ENV_TRIM_ADD, 1, envval, 0);
353
354   envval = getenv (ENV_TRIM_OVERR);
355   if (envval)
356     {
357       _res_hconf.num_trimdomains = 0;
358       arg_trimdomain_list (ENV_TRIM_OVERR, 1, envval, 0);
359     }
360 }
361
362
363 /* Reorder addresses returned in a hostent such that the first address
364    is an address on the local subnet, if there is such an address.
365    Otherwise, nothing is changed.  */
366
367 void
368 _res_hconf_reorder_addrs (struct hostent * hp)
369 {
370 #if defined (SIOCGIFCONF) && defined (SIOCGIFNETMASK)
371   static int num_ifs = -1;      /* number of interfaces */
372   static struct netaddr {
373     int addrtype;
374     union {
375       struct {
376         u_int32_t       addr;
377         u_int32_t       mask;
378       } ipv4
379     } u;
380   } * ifaddrs;
381
382   if (hp->h_addrtype != AF_INET)
383     return;     /* can't deal with anything but IPv4 for now... */
384
385   if (num_ifs <= 0)
386     {
387       struct ifconf ifs;
388       struct ifreq * ifr;
389       size_t size, num;
390       int sd;
391
392       /* initialize interface table: */
393
394       num_ifs = 0;
395
396       sd = socket (AF_INET, SOCK_DGRAM, 0);
397       if (sd < 0)
398         return;
399
400       /* Now get list of interfaces.  Since we don't know how many
401          interfaces there are, we keep increasing the buffer size
402          until we have at least sizeof(struct ifreq) too many bytes.
403          That implies that the ioctl() return because it ran out of
404          interfaces, not memory */
405       size = 0;
406       ifs.ifc_buf = 0;
407       do {
408         size += 4 * sizeof (struct ifreq);
409         ifs.ifc_buf = realloc (ifs.ifs_buf, size);
410         if (!ifs.ifc_buf)
411           {
412             close (sd);
413             return;
414           }
415         ifs.ifc_len = size;
416         if (ioctl (sd, SIOCGIFCONF, &ifs) < 0)
417           goto cleanup;
418       } while (size - ifs.ifc_len < sizeof (struct ifreq));
419
420       num = ifs.ifc_len / sizeof (struct ifreq);
421
422       ifaddrs = malloc (num * sizeof (ifaddrs[0]));
423       if (!ifaddrs)
424         goto cleanup;
425
426       ifr = ifs.ifc_req;
427       for (i = 0; i < num; ++i) {
428         if (ifr->ifr_addr.sa_family != AF_INET)
429           continue;
430         ifaddrs[num_ifs].addrtype = AF_INET;
431
432         memcpy (&ifaddrs[num_ifs].u.ipv4.addr,
433                 &((struct sockaddr_in *)ifr->ifr_addr)->sin_addr, 4);
434
435         if (ioctl (sd, SIOCGIFNETMASK, if) < 0)
436           continue;
437         memcpy (&ifaddrs[num_ifs].u.ipv4.mask,
438                 ((struct sockaddr_in *)ifr->ifr_mask)->sin_addr, 4);
439
440         ++num_ifs;      /* now we're committed to this entry */
441       }
442       /* just keep enough memory to hold all the interfaces we want: */
443       ifaddrs = realloc (ifaddrs, num_ifs * sizeof (ifaddrs[0]));
444
445     cleanup:
446       close (sd);
447       free (ifs.ifc_buf);
448     }
449
450   if (num_ifs == 0)
451     return;
452
453   /* find an address for which we have a direct connection: */
454   for (i = 0; hp->h_addr_list[i]; ++i)
455     {
456       h_addr = (struct in_addr *) hp->h_addr_list[i];
457
458       for (j = 0; j < num_ifs; ++j)
459         {
460           if_addr    = ifaddrs[j].u.ipv4.addr;
461           if_netmask = ifaddrs[j].u.ipv4.mask;
462
463           if (((h_addr->s_addr ^ if_addr) & if_netmask) == 0)
464             {
465               void * tmp;
466
467               tmp                = hp->h_addr_list[i];
468               hp->h_addr_list[i] = hp->h_addr_list[0];
469               hp->h_addr_list[0] = tmp;
470               return;
471             }
472         }
473     }
474 #endif /* defined(SIOCGIFCONF) && ... */
475 }
476
477
478 /* If HOSTNAME has a postfix matching any of the trimdomains, trim away
479    that postfix.  Notice that HOSTNAME is modified inplace.  Also, the
480    original code applied all trimdomains in order, meaning that the
481    same domainname could be trimmed multiple times.  I believe this
482    was unintentional.  */
483 void
484 _res_hconf_trim_domain (char * hostname)
485 {
486   size_t hostname_len, trim_len;
487   int i;
488
489   hostname_len = strlen(hostname);
490
491   for (i = 0; i < _res_hconf.num_trimdomains; ++i)
492     {
493       const char * trim = _res_hconf.trimdomain[i];
494
495       trim_len = strlen(trim);
496       if (hostname_len > trim_len
497           && strcasecmp(&hostname[hostname_len - trim_len], trim) == 0)
498         {
499           hostname[hostname_len - trim_len] = '\0';
500           break;
501         }
502     }
503 }
504
505
506 /* Trim all hostnames/aliases in HP according to the trimdomain list.
507    Notice that HP is modified inplace!  */
508 void
509 _res_hconf_trim_domains (struct hostent * hp)
510 {
511   int i;
512
513   if (_res_hconf.num_trimdomains == 0)
514     return;
515
516   _res_hconf_trim_domain (hp->h_name);
517   for (i = 0; hp->h_aliases[i]; ++i)
518     _res_hconf_trim_domain (hp->h_aliases[i]);
519 }
520
521
522 #if 0
523
524 struct hostent *
525 _hconf_gethostent (void)
526 {
527 }
528
529
530 struct hostent *
531 _hconf_gethostbyname (const char * name)
532 {
533
534 }
535
536
537 struct hostent *
538 _hconf_gethostbyaddr (const char * addr, int len, int type)
539 {
540 }
541
542
543 struct hostent *
544 _hconf_gethtbyname (const char * name)
545 {
546 }
547
548 #endif