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