]> git.ipfire.org Git - thirdparty/glibc.git/blob - resolv/res_hconf.c
* resolv/res_hconf.c (arg_service_list): Reduce size of svcs and
[thirdparty/glibc.git] / resolv / res_hconf.c
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).
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_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"
62
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);
69
70 static const struct cmd
71 {
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[] =
77 {
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}
85 };
86
87 /* Structure containing the state. */
88 struct hconf _res_hconf;
89
90 /* Skip white space. */
91 static const char *
92 skip_ws (const char *str)
93 {
94 while (isspace (*str)) ++str;
95 return str;
96 }
97
98
99 /* Skip until whitespace, comma, end of line, or comment character. */
100 static const char *
101 skip_string (const char *str)
102 {
103 while (*str && !isspace (*str) && *str != '#' && *str != ',')
104 ++str;
105 return str;
106 }
107
108
109 static const char *
110 arg_service_list (const char *fname, int line_num, const char *args,
111 unsigned int arg)
112 {
113 enum Name_Service service;
114 const char *start;
115 size_t len;
116 size_t i;
117 static const struct
118 {
119 const char name[6];
120 int16_t service;
121 } svcs[] =
122 {
123 {"bind", SERVICE_BIND},
124 {"hosts", SERVICE_HOSTS},
125 {"nis", SERVICE_NIS},
126 };
127
128 do
129 {
130 start = args;
131 args = skip_string (args);
132 len = args - start;
133
134 service = SERVICE_NONE;
135 for (i = 0; i < sizeof (svcs) / sizeof (svcs[0]); ++i)
136 {
137 if (__strncasecmp (start, svcs[i].name, len) == 0
138 && len == strlen (svcs[i].name))
139 {
140 service = svcs[i].service;
141 break;
142 }
143 }
144 if (service == SERVICE_NONE)
145 {
146 char *buf;
147
148 if (__asprintf (&buf,
149 _("%s: line %d: expected service, found `%s'\n"),
150 fname, line_num, start) < 0)
151 return 0;
152
153 __fxprintf (NULL, "%s", buf);
154
155 free (buf);
156 return 0;
157 }
158 if (_res_hconf.num_services >= SERVICE_MAX)
159 {
160 char *buf;
161
162 if (__asprintf (&buf, _("\
163 %s: line %d: cannot specify more than %d services"),
164 fname, line_num, SERVICE_MAX) < 0)
165 return 0;
166
167 __fxprintf (NULL, "%s", buf);
168
169 free (buf);
170 return 0;
171 }
172 _res_hconf.service[_res_hconf.num_services++] = service;
173
174 args = skip_ws (args);
175 switch (*args)
176 {
177 case ',':
178 case ';':
179 case ':':
180 args = skip_ws (++args);
181 if (!*args || *args == '#')
182 {
183 char *buf;
184
185 if (__asprintf (&buf, _("\
186 %s: line %d: list delimiter not followed by keyword"),
187 fname, line_num) < 0)
188 return 0;
189
190 __fxprintf (NULL, "%s", buf);
191
192 free (buf);
193 return 0;
194 }
195 default:
196 break;
197 }
198 }
199 while (*args && *args != '#');
200 return args;
201 }
202
203
204 static const char *
205 arg_trimdomain_list (const char *fname, int line_num, const char *args,
206 unsigned int flag)
207 {
208 const char * start;
209 size_t len;
210
211 do
212 {
213 start = args;
214 args = skip_string (args);
215 len = args - start;
216
217 if (_res_hconf.num_trimdomains >= TRIMDOMAINS_MAX)
218 {
219 char *buf;
220
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;
225
226 __fxprintf (NULL, "%s", buf);
227
228 free (buf);
229 return 0;
230 }
231 _res_hconf.trimdomain[_res_hconf.num_trimdomains++] =
232 __strndup (start, len);
233 args = skip_ws (args);
234 switch (*args)
235 {
236 case ',': case ';': case ':':
237 args = skip_ws (++args);
238 if (!*args || *args == '#')
239 {
240 char *buf;
241
242 if (__asprintf (&buf, _("\
243 %s: line %d: list delimiter not followed by domain"),
244 fname, line_num) < 0)
245 return 0;
246
247 __fxprintf (NULL, "%s", buf);
248
249 free (buf);
250 return 0;
251 }
252 default:
253 break;
254 }
255 }
256 while (*args && *args != '#');
257 return args;
258 }
259
260
261 static const char *
262 arg_spoof (const char *fname, int line_num, const char *args, unsigned flag)
263 {
264 const char *start = args;
265 size_t len;
266
267 args = skip_string (args);
268 len = args - start;
269
270 if (len == 3 && __strncasecmp (start, "off", len) == 0)
271 _res_hconf.flags &= ~(HCONF_FLAG_SPOOF | HCONF_FLAG_SPOOFALERT);
272 else
273 {
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;
278 }
279 return args;
280 }
281
282
283 static const char *
284 arg_bool (const char *fname, int line_num, const char *args, unsigned flag)
285 {
286 if (__strncasecmp (args, "on", 2) == 0)
287 {
288 args += 2;
289 _res_hconf.flags |= flag;
290 }
291 else if (__strncasecmp (args, "off", 3) == 0)
292 {
293 args += 3;
294 _res_hconf.flags &= ~flag;
295 }
296 else
297 {
298 char *buf;
299
300 if (__asprintf (&buf,
301 _("%s: line %d: expected `on' or `off', found `%s'\n"),
302 fname, line_num, args) < 0)
303 return 0;
304
305 __fxprintf (NULL, "%s", buf);
306
307 free (buf);
308 return 0;
309 }
310 return args;
311 }
312
313
314 static void
315 parse_line (const char *fname, int line_num, const char *str)
316 {
317 const char *start;
318 const struct cmd *c = 0;
319 size_t len;
320 size_t i;
321
322 str = skip_ws (str);
323
324 /* skip line comment and empty lines: */
325 if (*str == '\0' || *str == '#') return;
326
327 start = str;
328 str = skip_string (str);
329 len = str - start;
330
331 for (i = 0; i < sizeof (cmd) / sizeof (cmd[0]); ++i)
332 {
333 if (__strncasecmp (start, cmd[i].name, len) == 0
334 && strlen (cmd[i].name) == len)
335 {
336 c = &cmd[i];
337 break;
338 }
339 }
340 if (c == NULL)
341 {
342 char *buf;
343
344 if (__asprintf (&buf, _("%s: line %d: bad command `%s'\n"),
345 fname, line_num, start) < 0)
346 return;
347
348 __fxprintf (NULL, "%s", buf);
349
350 free (buf);
351 return;
352 }
353
354 /* process args: */
355 str = skip_ws (str);
356 str = (*c->parse_args) (fname, line_num, str, c->arg);
357 if (!str)
358 return;
359
360 /* rest of line must contain white space or comment only: */
361 while (*str)
362 {
363 if (!isspace (*str)) {
364 if (*str != '#')
365 {
366 char *buf;
367
368 if (__asprintf (&buf,
369 _("%s: line %d: ignoring trailing garbage `%s'\n"),
370 fname, line_num, str) < 0)
371 break;
372
373 __fxprintf (NULL, "%s", buf);
374
375 free (buf);
376 }
377 break;
378 }
379 ++str;
380 }
381 }
382
383
384 static void
385 do_init (void)
386 {
387 const char *hconf_name;
388 int line_num = 0;
389 char buf[256], *envval;
390 FILE *fp;
391
392 memset (&_res_hconf, '\0', sizeof (_res_hconf));
393
394 hconf_name = getenv (ENV_HOSTCONF);
395 if (hconf_name == NULL)
396 hconf_name = _PATH_HOSTCONF;
397
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
403 {
404 /* No threads using this stream. */
405 __fsetlocking (fp, FSETLOCKING_BYCALLER);
406
407 while (fgets_unlocked (buf, sizeof (buf), fp))
408 {
409 ++line_num;
410 *__strchrnul (buf, '\n') = '\0';
411 parse_line (hconf_name, line_num, buf);
412 }
413 fclose (fp);
414 }
415
416 envval = getenv (ENV_SERVORDER);
417 if (envval)
418 {
419 _res_hconf.num_services = 0;
420 arg_service_list (ENV_SERVORDER, 1, envval, 0);
421 }
422
423 envval = getenv (ENV_SPOOF);
424 if (envval)
425 arg_spoof (ENV_SPOOF, 1, envval, 0);
426
427 envval = getenv (ENV_MULTI);
428 if (envval)
429 arg_bool (ENV_MULTI, 1, envval, HCONF_FLAG_MULTI);
430
431 envval = getenv (ENV_REORDER);
432 if (envval)
433 arg_bool (ENV_REORDER, 1, envval, HCONF_FLAG_REORDER);
434
435 envval = getenv (ENV_TRIM_ADD);
436 if (envval)
437 arg_trimdomain_list (ENV_TRIM_ADD, 1, envval, 0);
438
439 envval = getenv (ENV_TRIM_OVERR);
440 if (envval)
441 {
442 _res_hconf.num_trimdomains = 0;
443 arg_trimdomain_list (ENV_TRIM_OVERR, 1, envval, 0);
444 }
445
446 _res_hconf.initialized = 1;
447 }
448
449
450 /* Initialize hconf datastructure by reading host.conf file and
451 environment variables. */
452 void
453 _res_hconf_init (void)
454 {
455 __libc_once_define (static, once);
456
457 __libc_once (once, do_init);
458 }
459
460
461 /* List of known interfaces. */
462 libc_freeres_ptr (
463 static struct netaddr
464 {
465 int addrtype;
466 union
467 {
468 struct
469 {
470 u_int32_t addr;
471 u_int32_t mask;
472 } ipv4;
473 } u;
474 } *ifaddrs);
475
476 /* We need to protect the dynamic buffer handling. */
477 __libc_lock_define_initialized (static, lock);
478
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.
482
483 Note that this function currently only handles IPv4 addresses. */
484
485 void
486 _res_hconf_reorder_addrs (struct hostent *hp)
487 {
488 #if defined SIOCGIFCONF && defined SIOCGIFNETMASK
489 int i, j;
490 /* Number of interfaces. */
491 static int num_ifs = -1;
492
493 /* Only reorder if we're supposed to. */
494 if ((_res_hconf.flags & HCONF_FLAG_REORDER) == 0)
495 return;
496
497 /* Can't deal with anything but IPv4 for now... */
498 if (hp->h_addrtype != AF_INET)
499 return;
500
501 if (num_ifs <= 0)
502 {
503 struct ifreq *ifr, *cur_ifr;
504 int sd, num, i;
505 /* Save errno. */
506 int save = errno;
507
508 /* Initialize interface table. */
509
510 num_ifs = 0;
511
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;
516
517 /* Get lock. */
518 __libc_lock_lock (lock);
519
520 /* Get a list of interfaces. */
521 __ifreq (&ifr, &num, sd);
522 if (!ifr)
523 goto cleanup;
524
525 ifaddrs = malloc (num * sizeof (ifaddrs[0]));
526 if (!ifaddrs)
527 goto cleanup1;
528
529 /* Copy usable interfaces in ifaddrs structure. */
530 for (cur_ifr = ifr, i = 0; i < num; cur_ifr = __if_nextreq (cur_ifr), ++i)
531 {
532 if (cur_ifr->ifr_addr.sa_family != AF_INET)
533 continue;
534
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;
538
539 if (__ioctl (sd, SIOCGIFNETMASK, cur_ifr) < 0)
540 continue;
541
542 ifaddrs[num_ifs].u.ipv4.mask =
543 ((struct sockaddr_in *) &cur_ifr->ifr_netmask)->sin_addr.s_addr;
544
545 /* Now we're committed to this entry. */
546 ++num_ifs;
547 }
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);
551
552 cleanup1:
553 __if_freereq (ifr, num);
554
555 cleanup:
556 /* Release lock, preserve error value, and close socket. */
557 save = errno;
558 __libc_lock_unlock (lock);
559 __close (sd);
560 }
561
562 if (num_ifs == 0)
563 return;
564
565 /* Find an address for which we have a direct connection. */
566 for (i = 0; hp->h_addr_list[i]; ++i)
567 {
568 struct in_addr *haddr = (struct in_addr *) hp->h_addr_list[i];
569
570 for (j = 0; j < num_ifs; ++j)
571 {
572 u_int32_t if_addr = ifaddrs[j].u.ipv4.addr;
573 u_int32_t if_netmask = ifaddrs[j].u.ipv4.mask;
574
575 if (((haddr->s_addr ^ if_addr) & if_netmask) == 0)
576 {
577 void *tmp;
578
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;
583 }
584 }
585 }
586 #endif /* defined(SIOCGIFCONF) && ... */
587 }
588
589
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)
597 {
598 size_t hostname_len, trim_len;
599 int i;
600
601 hostname_len = strlen (hostname);
602
603 for (i = 0; i < _res_hconf.num_trimdomains; ++i)
604 {
605 const char *trim = _res_hconf.trimdomain[i];
606
607 trim_len = strlen (trim);
608 if (hostname_len > trim_len
609 && __strcasecmp (&hostname[hostname_len - trim_len], trim) == 0)
610 {
611 hostname[hostname_len - trim_len] = '\0';
612 break;
613 }
614 }
615 }
616
617
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)
622 {
623 int i;
624
625 if (_res_hconf.num_trimdomains == 0)
626 return;
627
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]);
631 }