]> git.ipfire.org Git - thirdparty/glibc.git/blob - resolv/res_hconf.c
Clean up internal fopen uses
[thirdparty/glibc.git] / resolv / res_hconf.c
1 /* Copyright (C) 1993,1995-2006,2007,2009,2011
2 Free Software Foundation, Inc.
3 This file is part of the GNU C Library.
4 Contributed by David Mosberger (davidm@azstarnet.com).
5
6 The GNU C Library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Lesser General Public
8 License as published by the Free Software Foundation; either
9 version 2.1 of the License, or (at your option) any later version.
10
11 The GNU C Library is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
15
16 You should have received a copy of the GNU Lesser General Public
17 License along with the GNU C Library; if not, write to the Free
18 Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
19 02111-1307 USA. */
20
21 /* This file provides a Linux /etc/host.conf compatible front end to
22 the various name resolvers (/etc/hosts, named, NIS server, etc.).
23 Though mostly compatibly, the following differences exist compared
24 to the original implementation:
25
26 - new command "spoof" takes an arguments like RESOLV_SPOOF_CHECK
27 environment variable (i.e., `off', `nowarn', or `warn').
28
29 - line comments can appear anywhere (not just at the beginning of
30 a line)
31 */
32
33 #include <assert.h>
34 #include <errno.h>
35 #include <ctype.h>
36 #include <libintl.h>
37 #include <memory.h>
38 #include <stdio.h>
39 #include <stdio_ext.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include <net/if.h>
43 #include <sys/ioctl.h>
44 #include <unistd.h>
45 #include <netinet/in.h>
46 #include <bits/libc-lock.h>
47 #include "ifreq.h"
48 #include "res_hconf.h"
49 #include <wchar.h>
50
51 #define _PATH_HOSTCONF "/etc/host.conf"
52
53 /* Environment vars that all user to override default behavior: */
54 #define ENV_HOSTCONF "RESOLV_HOST_CONF"
55 #define ENV_SPOOF "RESOLV_SPOOF_CHECK"
56 #define ENV_TRIM_OVERR "RESOLV_OVERRIDE_TRIM_DOMAINS"
57 #define ENV_TRIM_ADD "RESOLV_ADD_TRIM_DOMAINS"
58 #define ENV_MULTI "RESOLV_MULTI"
59 #define ENV_REORDER "RESOLV_REORDER"
60
61 enum parse_cbs
62 {
63 CB_none,
64 CB_arg_trimdomain_list,
65 CB_arg_spoof,
66 CB_arg_bool
67 };
68
69 static const struct cmd
70 {
71 const char name[11];
72 uint8_t cb;
73 unsigned int arg;
74 } cmd[] =
75 {
76 {"order", CB_none, 0},
77 {"trim", CB_arg_trimdomain_list, 0},
78 {"spoof", CB_arg_spoof, 0},
79 {"multi", CB_arg_bool, HCONF_FLAG_MULTI},
80 {"nospoof", CB_arg_bool, HCONF_FLAG_SPOOF},
81 {"spoofalert", CB_arg_bool, HCONF_FLAG_SPOOFALERT},
82 {"reorder", CB_arg_bool, HCONF_FLAG_REORDER}
83 };
84
85 /* Structure containing the state. */
86 struct hconf _res_hconf;
87
88 /* Skip white space. */
89 static const char *
90 skip_ws (const char *str)
91 {
92 while (isspace (*str)) ++str;
93 return str;
94 }
95
96
97 /* Skip until whitespace, comma, end of line, or comment character. */
98 static const char *
99 skip_string (const char *str)
100 {
101 while (*str && !isspace (*str) && *str != '#' && *str != ',')
102 ++str;
103 return str;
104 }
105
106
107 static const char *
108 arg_trimdomain_list (const char *fname, int line_num, const char *args)
109 {
110 const char * start;
111 size_t len;
112
113 do
114 {
115 start = args;
116 args = skip_string (args);
117 len = args - start;
118
119 if (_res_hconf.num_trimdomains >= TRIMDOMAINS_MAX)
120 {
121 char *buf;
122
123 if (__asprintf (&buf, _("\
124 %s: line %d: cannot specify more than %d trim domains"),
125 fname, line_num, TRIMDOMAINS_MAX) < 0)
126 return 0;
127
128 __fxprintf (NULL, "%s", buf);
129
130 free (buf);
131 return 0;
132 }
133 _res_hconf.trimdomain[_res_hconf.num_trimdomains++] =
134 __strndup (start, len);
135 args = skip_ws (args);
136 switch (*args)
137 {
138 case ',': case ';': case ':':
139 args = skip_ws (++args);
140 if (!*args || *args == '#')
141 {
142 char *buf;
143
144 if (__asprintf (&buf, _("\
145 %s: line %d: list delimiter not followed by domain"),
146 fname, line_num) < 0)
147 return 0;
148
149 __fxprintf (NULL, "%s", buf);
150
151 free (buf);
152 return 0;
153 }
154 default:
155 break;
156 }
157 }
158 while (*args && *args != '#');
159 return args;
160 }
161
162
163 static const char *
164 arg_spoof (const char *fname, int line_num, const char *args)
165 {
166 const char *start = args;
167 size_t len;
168
169 args = skip_string (args);
170 len = args - start;
171
172 if (len == 3 && __strncasecmp (start, "off", len) == 0)
173 _res_hconf.flags &= ~(HCONF_FLAG_SPOOF | HCONF_FLAG_SPOOFALERT);
174 else
175 {
176 _res_hconf.flags |= (HCONF_FLAG_SPOOF | HCONF_FLAG_SPOOFALERT);
177 if ((len == 6 && __strncasecmp (start, "nowarn", len) == 0)
178 || !(len == 4 && __strncasecmp (start, "warn", len) == 0))
179 _res_hconf.flags &= ~HCONF_FLAG_SPOOFALERT;
180 }
181 return args;
182 }
183
184
185 static const char *
186 arg_bool (const char *fname, int line_num, const char *args, unsigned flag)
187 {
188 if (__strncasecmp (args, "on", 2) == 0)
189 {
190 args += 2;
191 _res_hconf.flags |= flag;
192 }
193 else if (__strncasecmp (args, "off", 3) == 0)
194 {
195 args += 3;
196 _res_hconf.flags &= ~flag;
197 }
198 else
199 {
200 char *buf;
201
202 if (__asprintf (&buf,
203 _("%s: line %d: expected `on' or `off', found `%s'\n"),
204 fname, line_num, args) < 0)
205 return 0;
206
207 __fxprintf (NULL, "%s", buf);
208
209 free (buf);
210 return 0;
211 }
212 return args;
213 }
214
215
216 static void
217 parse_line (const char *fname, int line_num, const char *str)
218 {
219 const char *start;
220 const struct cmd *c = 0;
221 size_t len;
222 size_t i;
223
224 str = skip_ws (str);
225
226 /* skip line comment and empty lines: */
227 if (*str == '\0' || *str == '#') return;
228
229 start = str;
230 str = skip_string (str);
231 len = str - start;
232
233 for (i = 0; i < sizeof (cmd) / sizeof (cmd[0]); ++i)
234 {
235 if (__strncasecmp (start, cmd[i].name, len) == 0
236 && strlen (cmd[i].name) == len)
237 {
238 c = &cmd[i];
239 break;
240 }
241 }
242 if (c == NULL)
243 {
244 char *buf;
245
246 if (__asprintf (&buf, _("%s: line %d: bad command `%s'\n"),
247 fname, line_num, start) < 0)
248 return;
249
250 __fxprintf (NULL, "%s", buf);
251
252 free (buf);
253 return;
254 }
255
256 /* process args: */
257 str = skip_ws (str);
258
259 if (c->cb == CB_arg_trimdomain_list)
260 str = arg_trimdomain_list (fname, line_num, str);
261 else if (c->cb == CB_arg_spoof)
262 str = arg_spoof (fname, line_num, str);
263 else if (c->cb == CB_arg_bool)
264 str = arg_bool (fname, line_num, str, c->arg);
265 else
266 /* Ignore the line. */
267 return;
268
269 if (!str)
270 return;
271
272 /* rest of line must contain white space or comment only: */
273 while (*str)
274 {
275 if (!isspace (*str)) {
276 if (*str != '#')
277 {
278 char *buf;
279
280 if (__asprintf (&buf,
281 _("%s: line %d: ignoring trailing garbage `%s'\n"),
282 fname, line_num, str) < 0)
283 break;
284
285 __fxprintf (NULL, "%s", buf);
286
287 free (buf);
288 }
289 break;
290 }
291 ++str;
292 }
293 }
294
295
296 static void
297 do_init (void)
298 {
299 const char *hconf_name;
300 int line_num = 0;
301 char buf[256], *envval;
302 FILE *fp;
303
304 memset (&_res_hconf, '\0', sizeof (_res_hconf));
305
306 hconf_name = getenv (ENV_HOSTCONF);
307 if (hconf_name == NULL)
308 hconf_name = _PATH_HOSTCONF;
309
310 fp = fopen (hconf_name, "rce");
311 if (fp)
312 {
313 /* No threads using this stream. */
314 __fsetlocking (fp, FSETLOCKING_BYCALLER);
315
316 while (fgets_unlocked (buf, sizeof (buf), fp))
317 {
318 ++line_num;
319 *__strchrnul (buf, '\n') = '\0';
320 parse_line (hconf_name, line_num, buf);
321 }
322 fclose (fp);
323 }
324
325 envval = getenv (ENV_SPOOF);
326 if (envval)
327 arg_spoof (ENV_SPOOF, 1, envval);
328
329 envval = getenv (ENV_MULTI);
330 if (envval)
331 arg_bool (ENV_MULTI, 1, envval, HCONF_FLAG_MULTI);
332
333 envval = getenv (ENV_REORDER);
334 if (envval)
335 arg_bool (ENV_REORDER, 1, envval, HCONF_FLAG_REORDER);
336
337 envval = getenv (ENV_TRIM_ADD);
338 if (envval)
339 arg_trimdomain_list (ENV_TRIM_ADD, 1, envval);
340
341 envval = getenv (ENV_TRIM_OVERR);
342 if (envval)
343 {
344 _res_hconf.num_trimdomains = 0;
345 arg_trimdomain_list (ENV_TRIM_OVERR, 1, envval);
346 }
347
348 _res_hconf.initialized = 1;
349 }
350
351
352 /* Initialize hconf datastructure by reading host.conf file and
353 environment variables. */
354 void
355 _res_hconf_init (void)
356 {
357 __libc_once_define (static, once);
358
359 __libc_once (once, do_init);
360 }
361
362
363 #ifndef NOT_IN_libc
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 /* 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