1 /* Hosts file parser in nss_files module.
2 Copyright (C) 1996-2022 Free Software Foundation, Inc.
3 This file is part of the GNU C Library.
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 <https://www.gnu.org/licenses/>. */
18
19 #include <assert.h>
20 #include <netinet/in.h>
21 #include <arpa/inet.h>
22 #include <arpa/nameser.h>
23 #include <netdb.h>
24 #include <resolv/resolv-internal.h>
25 #include <scratch_buffer.h>
26 #include <alloc_buffer.h>
27 #include <nss.h>
28
29 /* Get implementation for some internal functions. */
30 #include "../resolv/res_hconf.h"
31
32
33 #define ENTNAME hostent
34 #define DATABASE "hosts"
35 #define NEED_H_ERRNO
36
37 #define EXTRA_ARGS , af
38 #define EXTRA_ARGS_DECL , int af
39
40 #define ENTDATA hostent_data
41 struct hostent_data
42 {
43 unsigned char host_addr[16]; /* IPv4 or IPv6 address. */
44 char *h_addr_ptrs[2]; /* Points to that and null terminator. */
45 };
46
47 #define TRAILING_LIST_MEMBER h_aliases
48 #define TRAILING_LIST_SEPARATOR_P isspace
49 #include "files-parse.c"
50 LINE_PARSER
51 ("#",
52 {
53 char *addr;
54
55 STRING_FIELD (addr, isspace, 1);
56
57 /* Parse address. */
58 if (__inet_pton (af == AF_UNSPEC ? AF_INET : af, addr, entdata->host_addr)
59 > 0)
60 af = af == AF_UNSPEC ? AF_INET : af;
61 else
62 {
63 if (af == AF_INET
64 && __inet_pton (AF_INET6, addr, entdata->host_addr) > 0)
65 {
66 if (IN6_IS_ADDR_V4MAPPED (entdata->host_addr))
67 memcpy (entdata->host_addr, entdata->host_addr + 12, INADDRSZ);
68 else if (IN6_IS_ADDR_LOOPBACK (entdata->host_addr))
69 {
70 in_addr_t localhost = htonl (INADDR_LOOPBACK);
71 memcpy (entdata->host_addr, &localhost, sizeof (localhost));
72 }
73 else
74 /* Illegal address: ignore line. */
75 return 0;
76 }
77 else if (af == AF_UNSPEC
78 && __inet_pton (AF_INET6, addr, entdata->host_addr) > 0)
79 af = AF_INET6;
80 else
81 /* Illegal address: ignore line. */
82 return 0;
83 }
84
85 /* We always return entries of the requested form. */
86 result->h_addrtype = af;
87 result->h_length = af == AF_INET ? INADDRSZ : IN6ADDRSZ;
88
89 /* Store a pointer to the address in the expected form. */
90 entdata->h_addr_ptrs[0] = (char *) entdata->host_addr;
91 entdata->h_addr_ptrs[1] = NULL;
92 result->h_addr_list = entdata->h_addr_ptrs;
93
94 STRING_FIELD (result->h_name, isspace, 1);
95 })
96
97 #define EXTRA_ARGS_VALUE , AF_INET
98 #include "files-XXX.c"
99 #undef EXTRA_ARGS_VALUE
100
101 /* We only need to consider IPv4 mapped addresses if the input to the
102 gethostbyaddr() function is an IPv6 address. */
103 #define EXTRA_ARGS_VALUE , af
104 DB_LOOKUP (hostbyaddr, ,,,
105 {
106 if (result->h_length == (int) len
107 && ! memcmp (addr, result->h_addr_list[0], len))
108 break;
109 }, const void *addr, socklen_t len, int af)
110 #undef EXTRA_ARGS_VALUE
111
112 /* Type of the address and alias arrays. */
113 #define DYNARRAY_STRUCT array
114 #define DYNARRAY_ELEMENT char *
115 #define DYNARRAY_PREFIX array_
116 #include <malloc/dynarray-skeleton.c>
117
118 static enum nss_status
gethostbyname3_multi(FILE * stream,const char * name,int af,struct hostent * result,char * buffer,size_t buflen,int * errnop,int * herrnop)119 gethostbyname3_multi (FILE * stream, const char *name, int af,
120 struct hostent *result, char *buffer, size_t buflen,
121 int *errnop, int *herrnop)
122 {
123 assert (af == AF_INET || af == AF_INET6);
124
125 /* We have to get all host entries from the file. */
126 struct scratch_buffer tmp_buffer;
127 scratch_buffer_init (&tmp_buffer);
128 struct hostent tmp_result_buf;
129 struct array addresses;
130 array_init (&addresses);
131 struct array aliases;
132 array_init (&aliases);
133 enum nss_status status;
134
135 /* Preserve the addresses and aliases encountered so far. */
136 for (size_t i = 0; result->h_addr_list[i] != NULL; ++i)
137 array_add (&addresses, result->h_addr_list[i]);
138 for (size_t i = 0; result->h_aliases[i] != NULL; ++i)
139 array_add (&aliases, result->h_aliases[i]);
140
141 /* The output buffer re-uses now-unused space at the end of the
142 buffer, starting with the aliases array. It comes last in the
143 data produced by internal_getent. (The alias names themselves
144 are still located in the line read in internal_getent, which is
145 stored at the beginning of the buffer.) */
146 struct alloc_buffer outbuf;
147 {
148 char *bufferend = (char *) result->h_aliases;
149 outbuf = alloc_buffer_create (bufferend, buffer + buflen - bufferend);
150 }
151
152 while (true)
153 {
154 status = internal_getent (stream, &tmp_result_buf, tmp_buffer.data,
155 tmp_buffer.length, errnop, herrnop, af);
156 /* Enlarge the buffer if necessary. */
157 if (status == NSS_STATUS_TRYAGAIN && *herrnop == NETDB_INTERNAL
158 && *errnop == ERANGE)
159 {
160 if (!scratch_buffer_grow (&tmp_buffer))
161 {
162 *errnop = ENOMEM;
163 /* *herrnop and status already have the right value. */
164 break;
165 }
166 /* Loop around and retry with a larger buffer. */
167 }
168 else if (status == NSS_STATUS_SUCCESS)
169 {
170 /* A line was read. Check that it matches the search
171 criteria. */
172
173 int matches = 1;
174 struct hostent *old_result = result;
175 result = &tmp_result_buf;
176 /* The following piece is a bit clumsy but we want to use
177 the `LOOKUP_NAME_CASE' value. The optimizer should do
178 its job. */
179 do
180 {
181 LOOKUP_NAME_CASE (h_name, h_aliases)
182 result = old_result;
183 }
184 while ((matches = 0));
185
186 /* If the line matches, we need to copy the addresses and
187 aliases, so that we can reuse tmp_buffer for the next
188 line. */
189 if (matches)
190 {
191 /* Record the addresses. */
192 for (size_t i = 0; tmp_result_buf.h_addr_list[i] != NULL; ++i)
193 {
194 /* Allocate the target space in the output buffer,
195 depending on the address family. */
196 void *target;
197 if (af == AF_INET)
198 {
199 assert (tmp_result_buf.h_length == 4);
200 target = alloc_buffer_alloc (&outbuf, struct in_addr);
201 }
202 else if (af == AF_INET6)
203 {
204 assert (tmp_result_buf.h_length == 16);
205 target = alloc_buffer_alloc (&outbuf, struct in6_addr);
206 }
207 else
208 __builtin_unreachable ();
209
210 if (target == NULL)
211 {
212 /* Request a larger output buffer. */
213 *errnop = ERANGE;
214 *herrnop = NETDB_INTERNAL;
215 status = NSS_STATUS_TRYAGAIN;
216 break;
217 }
218 memcpy (target, tmp_result_buf.h_addr_list[i],
219 tmp_result_buf.h_length);
220 array_add (&addresses, target);
221 }
222
223 /* Record the aliases. */
224 for (size_t i = 0; tmp_result_buf.h_aliases[i] != NULL; ++i)
225 {
226 char *alias = tmp_result_buf.h_aliases[i];
227 array_add (&aliases,
228 alloc_buffer_copy_string (&outbuf, alias));
229 }
230
231 /* If the real name is different add, it also to the
232 aliases. This means that there is a duplication in
233 the alias list but this is really the user's
234 problem. */
235 {
236 char *new_name = tmp_result_buf.h_name;
237 if (strcmp (old_result->h_name, new_name) != 0)
238 array_add (&aliases,
239 alloc_buffer_copy_string (&outbuf, new_name));
240 }
241
242 /* Report memory allocation failures during the
243 expansion of the temporary arrays. */
244 if (array_has_failed (&addresses) || array_has_failed (&aliases))
245 {
246 *errnop = ENOMEM;
247 *herrnop = NETDB_INTERNAL;
248 status = NSS_STATUS_UNAVAIL;
249 break;
250 }
251
252 /* Request a larger output buffer if we ran out of room. */
253 if (alloc_buffer_has_failed (&outbuf))
254 {
255 *errnop = ERANGE;
256 *herrnop = NETDB_INTERNAL;
257 status = NSS_STATUS_TRYAGAIN;
258 break;
259 }
260
261 result = old_result;
262 } /* If match was found. */
263
264 /* If no match is found, loop around and fetch another
265 line. */
266
267 } /* status == NSS_STATUS_SUCCESS. */
268 else
269 /* internal_getent returned an error. */
270 break;
271 } /* while (true) */
272
273 /* Propagate the NSS_STATUS_TRYAGAIN error to the caller. It means
274 that we may not have loaded the complete result.
275 NSS_STATUS_NOTFOUND, however, means that we reached the end of
276 the file successfully. */
277 if (status != NSS_STATUS_TRYAGAIN)
278 status = NSS_STATUS_SUCCESS;
279
280 if (status == NSS_STATUS_SUCCESS)
281 {
282 /* Copy the address and alias arrays into the output buffer and
283 add NULL terminators. The pointed-to elements were directly
284 written into the output buffer above and do not need to be
285 copied again. */
286 size_t addresses_count = array_size (&addresses);
287 size_t aliases_count = array_size (&aliases);
288 char **out_addresses = alloc_buffer_alloc_array
289 (&outbuf, char *, addresses_count + 1);
290 char **out_aliases = alloc_buffer_alloc_array
291 (&outbuf, char *, aliases_count + 1);
292 if (out_addresses == NULL || out_aliases == NULL)
293 {
294 /* The output buffer is not large enough. */
295 *errnop = ERANGE;
296 *herrnop = NETDB_INTERNAL;
297 status = NSS_STATUS_TRYAGAIN;
298 /* Fall through to function exit. */
299 }
300 else
301 {
302 /* Everything is allocated in place. Make the copies and
303 adjust the array pointers. */
304 memcpy (out_addresses, array_begin (&addresses),
305 addresses_count * sizeof (char *));
306 out_addresses[addresses_count] = NULL;
307 memcpy (out_aliases, array_begin (&aliases),
308 aliases_count * sizeof (char *));
309 out_aliases[aliases_count] = NULL;
310
311 result->h_addr_list = out_addresses;
312 result->h_aliases = out_aliases;
313
314 status = NSS_STATUS_SUCCESS;
315 }
316 }
317
318 scratch_buffer_free (&tmp_buffer);
319 array_free (&addresses);
320 array_free (&aliases);
321 return status;
322 }
323
324 enum nss_status
_nss_files_gethostbyname3_r(const char * name,int af,struct hostent * result,char * buffer,size_t buflen,int * errnop,int * herrnop,int32_t * ttlp,char ** canonp)325 _nss_files_gethostbyname3_r (const char *name, int af, struct hostent *result,
326 char *buffer, size_t buflen, int *errnop,
327 int *herrnop, int32_t *ttlp, char **canonp)
328 {
329 FILE *stream = NULL;
330 uintptr_t pad = -(uintptr_t) buffer % __alignof__ (struct hostent_data);
331 buffer += pad;
332 buflen = buflen > pad ? buflen - pad : 0;
333
334 /* Open file. */
335 enum nss_status status = internal_setent (&stream);
336
337 if (status == NSS_STATUS_SUCCESS)
338 {
339 while ((status = internal_getent (stream, result, buffer, buflen, errnop,
340 herrnop, af))
341 == NSS_STATUS_SUCCESS)
342 {
343 LOOKUP_NAME_CASE (h_name, h_aliases)
344 }
345
346 if (status == NSS_STATUS_SUCCESS
347 && _res_hconf.flags & HCONF_FLAG_MULTI)
348 status = gethostbyname3_multi
349 (stream, name, af, result, buffer, buflen, errnop, herrnop);
350
351 fclose (stream);
352 }
353
354 if (canonp && status == NSS_STATUS_SUCCESS)
355 *canonp = result->h_name;
356
357 return status;
358 }
libc_hidden_def(_nss_files_gethostbyname3_r)359 libc_hidden_def (_nss_files_gethostbyname3_r)
360
361 enum nss_status
362 _nss_files_gethostbyname_r (const char *name, struct hostent *result,
363 char *buffer, size_t buflen, int *errnop,
364 int *herrnop)
365 {
366 return _nss_files_gethostbyname3_r (name, AF_INET, result, buffer, buflen,
367 errnop, herrnop, NULL, NULL);
368 }
libc_hidden_def(_nss_files_gethostbyname_r)369 libc_hidden_def (_nss_files_gethostbyname_r)
370
371 enum nss_status
372 _nss_files_gethostbyname2_r (const char *name, int af, struct hostent *result,
373 char *buffer, size_t buflen, int *errnop,
374 int *herrnop)
375 {
376 return _nss_files_gethostbyname3_r (name, af, result, buffer, buflen,
377 errnop, herrnop, NULL, NULL);
378 }
libc_hidden_def(_nss_files_gethostbyname2_r)379 libc_hidden_def (_nss_files_gethostbyname2_r)
380
381 enum nss_status
382 _nss_files_gethostbyname4_r (const char *name, struct gaih_addrtuple **pat,
383 char *buffer, size_t buflen, int *errnop,
384 int *herrnop, int32_t *ttlp)
385 {
386 FILE *stream = NULL;
387
388 /* Open file. */
389 enum nss_status status = internal_setent (&stream);
390
391 if (status == NSS_STATUS_SUCCESS)
392 {
393 bool any = false;
394 bool got_canon = false;
395 while (1)
396 {
397 /* Align the buffer for the next record. */
398 uintptr_t pad = (-(uintptr_t) buffer
399 % __alignof__ (struct hostent_data));
400 buffer += pad;
401 buflen = buflen > pad ? buflen - pad : 0;
402
403 struct hostent result;
404 status = internal_getent (stream, &result, buffer, buflen, errnop,
405 herrnop, AF_UNSPEC);
406 if (status != NSS_STATUS_SUCCESS)
407 break;
408
409 int naliases = 0;
410 if (__strcasecmp (name, result.h_name) != 0)
411 {
412 for (; result.h_aliases[naliases] != NULL; ++naliases)
413 if (! __strcasecmp (name, result.h_aliases[naliases]))
414 break;
415 if (result.h_aliases[naliases] == NULL)
416 continue;
417
418 /* We know this alias exist. Count it. */
419 ++naliases;
420 }
421
422 /* Determine how much memory has been used so far. */
423 // XXX It is not necessary to preserve the aliases array
424 while (result.h_aliases[naliases] != NULL)
425 ++naliases;
426 char *bufferend = (char *) &result.h_aliases[naliases + 1];
427 assert (buflen >= bufferend - buffer);
428 buflen -= bufferend - buffer;
429 buffer = bufferend;
430
431 /* We found something. */
432 any = true;
433
434 /* Create the record the caller expects. There is only one
435 address. */
436 assert (result.h_addr_list[1] == NULL);
437 if (*pat == NULL)
438 {
439 uintptr_t pad = (-(uintptr_t) buffer
440 % __alignof__ (struct gaih_addrtuple));
441 buffer += pad;
442 buflen = buflen > pad ? buflen - pad : 0;
443
444 if (__builtin_expect (buflen < sizeof (struct gaih_addrtuple),
445 0))
446 {
447 *errnop = ERANGE;
448 *herrnop = NETDB_INTERNAL;
449 status = NSS_STATUS_TRYAGAIN;
450 break;
451 }
452
453 *pat = (struct gaih_addrtuple *) buffer;
454 buffer += sizeof (struct gaih_addrtuple);
455 buflen -= sizeof (struct gaih_addrtuple);
456 }
457
458 (*pat)->next = NULL;
459 (*pat)->name = got_canon ? NULL : result.h_name;
460 got_canon = true;
461 (*pat)->family = result.h_addrtype;
462 memcpy ((*pat)->addr, result.h_addr_list[0], result.h_length);
463 (*pat)->scopeid = 0;
464
465 pat = &((*pat)->next);
466
467 /* If we only look for the first matching entry we are done. */
468 if ((_res_hconf.flags & HCONF_FLAG_MULTI) == 0)
469 break;
470 }
471
472 /* If we have to look for multiple records and found one, this
473 is a success. */
474 if (status == NSS_STATUS_NOTFOUND && any)
475 {
476 assert ((_res_hconf.flags & HCONF_FLAG_MULTI) != 0);
477 status = NSS_STATUS_SUCCESS;
478 }
479
480 fclose (stream);
481 }
482 else if (status == NSS_STATUS_TRYAGAIN)
483 {
484 *errnop = errno;
485 *herrnop = TRY_AGAIN;
486 }
487 else
488 {
489 *errnop = errno;
490 *herrnop = NO_DATA;
491 }
492
493 return status;
494 }
495 libc_hidden_def (_nss_files_gethostbyname4_r)
496