/* Hosts file parser in nss_files module. Copyright (C) 1996-2016 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. The GNU C Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with the GNU C Library; if not, see . */ #include #include #include #include #include #include /* Get implementation for some internal functions. */ #include "../resolv/mapv4v6addr.h" #include "../resolv/res_hconf.h" #define ENTNAME hostent #define DATABASE "hosts" #define NEED_H_ERRNO #define EXTRA_ARGS , af, flags #define EXTRA_ARGS_DECL , int af, int flags #define ENTDATA hostent_data struct hostent_data { unsigned char host_addr[16]; /* IPv4 or IPv6 address. */ char *h_addr_ptrs[2]; /* Points to that and null terminator. */ }; #define TRAILING_LIST_MEMBER h_aliases #define TRAILING_LIST_SEPARATOR_P isspace #include "files-parse.c" LINE_PARSER ("#", { char *addr; STRING_FIELD (addr, isspace, 1); /* Parse address. */ if (inet_pton (af == AF_UNSPEC ? AF_INET : af, addr, entdata->host_addr) > 0) af = af == AF_UNSPEC ? AF_INET : af; else { if (af == AF_INET6 && (flags & AI_V4MAPPED) != 0 && inet_pton (AF_INET, addr, entdata->host_addr) > 0) map_v4v6_address ((char *) entdata->host_addr, (char *) entdata->host_addr); else if (af == AF_INET && inet_pton (AF_INET6, addr, entdata->host_addr) > 0) { if (IN6_IS_ADDR_V4MAPPED (entdata->host_addr)) memcpy (entdata->host_addr, entdata->host_addr + 12, INADDRSZ); else if (IN6_IS_ADDR_LOOPBACK (entdata->host_addr)) { in_addr_t localhost = htonl (INADDR_LOOPBACK); memcpy (entdata->host_addr, &localhost, sizeof (localhost)); } else /* Illegal address: ignore line. */ return 0; } else if (af == AF_UNSPEC && inet_pton (AF_INET6, addr, entdata->host_addr) > 0) af = AF_INET6; else /* Illegal address: ignore line. */ return 0; } /* We always return entries of the requested form. */ result->h_addrtype = af; result->h_length = af == AF_INET ? INADDRSZ : IN6ADDRSZ; /* Store a pointer to the address in the expected form. */ entdata->h_addr_ptrs[0] = (char *) entdata->host_addr; entdata->h_addr_ptrs[1] = NULL; result->h_addr_list = entdata->h_addr_ptrs; STRING_FIELD (result->h_name, isspace, 1); }) #define EXTRA_ARGS_VALUE \ , ((_res.options & RES_USE_INET6) ? AF_INET6 : AF_INET), \ ((_res.options & RES_USE_INET6) ? AI_V4MAPPED : 0) #include "files-XXX.c" #undef EXTRA_ARGS_VALUE /* We only need to consider IPv4 mapped addresses if the input to the gethostbyaddr() function is an IPv6 address. */ #define EXTRA_ARGS_VALUE \ , af, (len == IN6ADDRSZ ? AI_V4MAPPED : 0) DB_LOOKUP (hostbyaddr, ,,, { if (result->h_length == (int) len && ! memcmp (addr, result->h_addr_list[0], len)) break; }, const void *addr, socklen_t len, int af) #undef EXTRA_ARGS_VALUE 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) { FILE *stream = NULL; uintptr_t pad = -(uintptr_t) buffer % __alignof__ (struct hostent_data); buffer += pad; buflen = buflen > pad ? buflen - pad : 0; /* Open file. */ enum nss_status status = internal_setent (&stream); if (status == NSS_STATUS_SUCCESS) { /* XXX Is using _res to determine whether we want to convert IPv4 addresses to IPv6 addresses really the right thing to do? */ int flags = ((_res.options & RES_USE_INET6) ? AI_V4MAPPED : 0); while ((status = internal_getent (stream, result, buffer, buflen, errnop, herrnop, af, flags)) == NSS_STATUS_SUCCESS) { LOOKUP_NAME_CASE (h_name, h_aliases) } if (status == NSS_STATUS_SUCCESS && _res_hconf.flags & HCONF_FLAG_MULTI) { /* We have to get all host entries from the file. */ size_t tmp_buflen = MIN (buflen, 4096); char tmp_buffer_stack[tmp_buflen] __attribute__ ((__aligned__ (__alignof__ (struct hostent_data)))); char *tmp_buffer = tmp_buffer_stack; struct hostent tmp_result_buf; int naddrs = 1; int naliases = 0; char *bufferend; bool tmp_buffer_malloced = false; while (result->h_aliases[naliases] != NULL) ++naliases; bufferend = (char *) &result->h_aliases[naliases + 1]; again: while ((status = internal_getent (stream, &tmp_result_buf, tmp_buffer, tmp_buflen, errnop, herrnop, af, flags)) == NSS_STATUS_SUCCESS) { int matches = 1; struct hostent *old_result = result; result = &tmp_result_buf; /* The following piece is a bit clumsy but we want to use the `LOOKUP_NAME_CASE' value. The optimizer should do its job. */ do { LOOKUP_NAME_CASE (h_name, h_aliases) result = old_result; } while ((matches = 0)); if (matches) { /* We could be very clever and try to recycle a few bytes in the buffer instead of generating new arrays. But we are not doing this here since it's more work than it's worth. Simply let the user provide a bit bigger buffer. */ char **new_h_addr_list; char **new_h_aliases; int newaliases = 0; size_t newstrlen = 0; int cnt; /* Count the new aliases and the length of the strings. */ while (tmp_result_buf.h_aliases[newaliases] != NULL) { char *cp = tmp_result_buf.h_aliases[newaliases]; ++newaliases; newstrlen += strlen (cp) + 1; } /* If the real name is different add it also to the aliases. This means that there is a duplication in the alias list but this is really the user's problem. */ if (strcmp (old_result->h_name, tmp_result_buf.h_name) != 0) { ++newaliases; newstrlen += strlen (tmp_result_buf.h_name) + 1; } /* Make sure bufferend is aligned. */ assert ((bufferend - (char *) 0) % sizeof (char *) == 0); /* Now we can check whether the buffer is large enough. 16 is the maximal size of the IP address. */ if (bufferend + 16 + (naddrs + 2) * sizeof (char *) + roundup (newstrlen, sizeof (char *)) + (naliases + newaliases + 1) * sizeof (char *) >= buffer + buflen) { *errnop = ERANGE; *herrnop = NETDB_INTERNAL; status = NSS_STATUS_TRYAGAIN; goto out; } new_h_addr_list = (char **) (bufferend + roundup (newstrlen, sizeof (char *)) + 16); new_h_aliases = (char **) ((char *) new_h_addr_list + (naddrs + 2) * sizeof (char *)); /* Copy the old data in the new arrays. */ for (cnt = 0; cnt < naddrs; ++cnt) new_h_addr_list[cnt] = old_result->h_addr_list[cnt]; for (cnt = 0; cnt < naliases; ++cnt) new_h_aliases[cnt] = old_result->h_aliases[cnt]; /* Store the new strings. */ cnt = 0; while (tmp_result_buf.h_aliases[cnt] != NULL) { new_h_aliases[naliases++] = bufferend; bufferend = (__stpcpy (bufferend, tmp_result_buf.h_aliases[cnt]) + 1); ++cnt; } if (cnt < newaliases) { new_h_aliases[naliases++] = bufferend; bufferend = __stpcpy (bufferend, tmp_result_buf.h_name) + 1; } /* Final NULL pointer. */ new_h_aliases[naliases] = NULL; /* Round up the buffer end address. */ bufferend += (sizeof (char *) - ((bufferend - (char *) 0) % sizeof (char *))) % sizeof (char *); /* Now the new address. */ new_h_addr_list[naddrs++] = memcpy (bufferend, tmp_result_buf.h_addr, tmp_result_buf.h_length); /* Also here a final NULL pointer. */ new_h_addr_list[naddrs] = NULL; /* Store the new array pointers. */ old_result->h_aliases = new_h_aliases; old_result->h_addr_list = new_h_addr_list; /* Compute the new buffer end. */ bufferend = (char *) &new_h_aliases[naliases + 1]; assert (bufferend <= buffer + buflen); result = old_result; } } if (status == NSS_STATUS_TRYAGAIN) { size_t newsize = 2 * tmp_buflen; if (tmp_buffer_malloced) { char *newp = realloc (tmp_buffer, newsize); if (newp != NULL) { assert ((((uintptr_t) newp) & (__alignof__ (struct hostent_data) - 1)) == 0); tmp_buffer = newp; tmp_buflen = newsize; goto again; } } else if (!__libc_use_alloca (buflen + newsize)) { tmp_buffer = malloc (newsize); if (tmp_buffer != NULL) { assert ((((uintptr_t) tmp_buffer) & (__alignof__ (struct hostent_data) - 1)) == 0); tmp_buffer_malloced = true; tmp_buflen = newsize; goto again; } } else { tmp_buffer = extend_alloca (tmp_buffer, tmp_buflen, newsize + __alignof__ (struct hostent_data)); tmp_buffer = (char *) (((uintptr_t) tmp_buffer + __alignof__ (struct hostent_data) - 1) & ~(__alignof__ (struct hostent_data) - 1)); goto again; } } else status = NSS_STATUS_SUCCESS; out: if (tmp_buffer_malloced) free (tmp_buffer); } internal_endent (&stream); } if (canonp && status == NSS_STATUS_SUCCESS) *canonp = result->h_name; return status; } enum nss_status _nss_files_gethostbyname_r (const char *name, struct hostent *result, char *buffer, size_t buflen, int *errnop, int *herrnop) { int af = ((_res.options & RES_USE_INET6) ? AF_INET6 : AF_INET); return _nss_files_gethostbyname3_r (name, af, result, buffer, buflen, errnop, herrnop, NULL, NULL); } enum nss_status _nss_files_gethostbyname2_r (const char *name, int af, struct hostent *result, char *buffer, size_t buflen, int *errnop, int *herrnop) { return _nss_files_gethostbyname3_r (name, af, result, buffer, buflen, errnop, herrnop, NULL, NULL); } enum nss_status _nss_files_gethostbyname4_r (const char *name, struct gaih_addrtuple **pat, char *buffer, size_t buflen, int *errnop, int *herrnop, int32_t *ttlp) { FILE *stream = NULL; /* Open file. */ enum nss_status status = internal_setent (&stream); if (status == NSS_STATUS_SUCCESS) { bool any = false; bool got_canon = false; while (1) { /* Align the buffer for the next record. */ uintptr_t pad = (-(uintptr_t) buffer % __alignof__ (struct hostent_data)); buffer += pad; buflen = buflen > pad ? buflen - pad : 0; struct hostent result; status = internal_getent (stream, &result, buffer, buflen, errnop, herrnop, AF_UNSPEC, 0); if (status != NSS_STATUS_SUCCESS) break; int naliases = 0; if (__strcasecmp (name, result.h_name) != 0) { for (; result.h_aliases[naliases] != NULL; ++naliases) if (! __strcasecmp (name, result.h_aliases[naliases])) break; if (result.h_aliases[naliases] == NULL) continue; /* We know this alias exist. Count it. */ ++naliases; } /* Determine how much memory has been used so far. */ // XXX It is not necessary to preserve the aliases array while (result.h_aliases[naliases] != NULL) ++naliases; char *bufferend = (char *) &result.h_aliases[naliases + 1]; assert (buflen >= bufferend - buffer); buflen -= bufferend - buffer; buffer = bufferend; /* We found something. */ any = true; /* Create the record the caller expects. There is only one address. */ assert (result.h_addr_list[1] == NULL); if (*pat == NULL) { uintptr_t pad = (-(uintptr_t) buffer % __alignof__ (struct gaih_addrtuple)); buffer += pad; buflen = buflen > pad ? buflen - pad : 0; if (__builtin_expect (buflen < sizeof (struct gaih_addrtuple), 0)) { *errnop = ERANGE; *herrnop = NETDB_INTERNAL; status = NSS_STATUS_TRYAGAIN; break; } *pat = (struct gaih_addrtuple *) buffer; buffer += sizeof (struct gaih_addrtuple); buflen -= sizeof (struct gaih_addrtuple); } (*pat)->next = NULL; (*pat)->name = got_canon ? NULL : result.h_name; got_canon = true; (*pat)->family = result.h_addrtype; memcpy ((*pat)->addr, result.h_addr_list[0], result.h_length); (*pat)->scopeid = 0; pat = &((*pat)->next); /* If we only look for the first matching entry we are done. */ if ((_res_hconf.flags & HCONF_FLAG_MULTI) == 0) break; } /* If we have to look for multiple records and found one, this is a success. */ if (status == NSS_STATUS_NOTFOUND && any) { assert ((_res_hconf.flags & HCONF_FLAG_MULTI) != 0); status = NSS_STATUS_SUCCESS; } internal_endent (&stream); } else if (status == NSS_STATUS_TRYAGAIN) { *errnop = errno; *herrnop = TRY_AGAIN; } else { *errnop = errno; *herrnop = NO_DATA; } return status; }