/* Hosts file parser in nss_files module. Copyright (C) 1996-2001, 2003-2007, 2008 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, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ #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 HOST_DB_LOOKUP(name, keysize, keypattern, break_if_match, proto...) \ enum nss_status \ _nss_files_get##name##_r (proto, \ struct STRUCTURE *result, char *buffer, \ size_t buflen, int *errnop H_ERRNO_PROTO) \ { \ uintptr_t pad = -(uintptr_t) buffer % __alignof__ (struct hostent_data); \ buffer += pad; \ buflen = buflen > pad ? buflen - pad : 0; \ \ __libc_lock_lock (lock); \ \ /* Reset file pointer to beginning or open file. */ \ enum nss_status status = internal_setent (keep_stream); \ \ if (status == NSS_STATUS_SUCCESS) \ { \ /* Tell getent function that we have repositioned the file pointer. */ \ last_use = getby; \ \ while ((status = internal_getent (result, buffer, buflen, errnop \ H_ERRNO_ARG EXTRA_ARGS_VALUE)) \ == NSS_STATUS_SUCCESS) \ { break_if_match } \ \ if (status == NSS_STATUS_SUCCESS \ && _res_hconf.flags & HCONF_FLAG_MULTI) \ { \ /* We have to get all host entries from the file. */ \ const size_t tmp_buflen = MIN (buflen, 4096); \ char tmp_buffer[tmp_buflen] \ __attribute__ ((__aligned__ (__alignof__ (struct hostent_data))));\ struct hostent tmp_result_buf; \ int naddrs = 1; \ int naliases = 0; \ char *bufferend; \ \ while (result->h_aliases[naliases] != NULL) \ ++naliases; \ \ bufferend = (char *) &result->h_aliases[naliases + 1]; \ \ while ((status = internal_getent (&tmp_result_buf, tmp_buffer, \ tmp_buflen, errnop H_ERRNO_ARG \ EXTRA_ARGS_VALUE)) \ == 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 \ `break_if_match' value. The optimizer should do its \ job. */ \ do \ { \ break_if_match \ 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 users \ 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; \ break; \ } \ \ 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) \ status = NSS_STATUS_SUCCESS; \ } \ \ \ if (! keep_stream) \ internal_endent (); \ } \ \ __libc_lock_unlock (lock); \ \ return status; \ } #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" HOST_DB_LOOKUP (hostbyname, ,, { LOOKUP_NAME_CASE (h_name, h_aliases) }, const char *name) #undef EXTRA_ARGS_VALUE /* XXX Is using _res to determine whether we want to convert IPv4 addresses to IPv6 addresses really the right thing to do? */ #define EXTRA_ARGS_VALUE \ , af, ((_res.options & RES_USE_INET6) ? AI_V4MAPPED : 0) HOST_DB_LOOKUP (hostbyname2, ,, { LOOKUP_NAME_CASE (h_name, h_aliases) }, const char *name, int af) #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_gethostbyname4_r (const char *name, struct gaih_addrtuple **pat, char *buffer, size_t buflen, int *errnop, int *herrnop, int32_t *ttlp) { __libc_lock_lock (lock); /* Reset file pointer to beginning or open file. */ enum nss_status status = internal_setent (keep_stream); if (status == NSS_STATUS_SUCCESS) { /* Tell getent function that we have repositioned the file pointer. */ last_use = getby; 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 (&result, buffer, buflen, errnop H_ERRNO_ARG, 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; } if (! keep_stream) internal_endent (); } __libc_lock_unlock (lock); return status; }