diff options
| author | Arjun Shankar <arjun@redhat.com> | 2023-10-02 14:55:27 +0200 |
|---|---|---|
| committer | Arjun Shankar <arjun@redhat.com> | 2023-10-24 12:31:00 +0200 |
| commit | 7f602256ab5b85db1dbfb5f40bd109c4b37b68c8 (patch) | |
| tree | 88cf9fb3bc04eae6ae9ca3816fac75d78f691740 /nss | |
| parent | 1d74d2f042a405982661267394e16126db70dc5f (diff) | |
| download | glibc-7f602256ab5b85db1dbfb5f40bd109c4b37b68c8.tar.xz glibc-7f602256ab5b85db1dbfb5f40bd109c4b37b68c8.zip | |
Move getaddrinfo from 'posix' into 'nss'
getaddrinfo is an entry point for nss functionality. This commit moves
it from 'sysdeps/posix' to 'nss', gets rid of the stub in 'posix', and
moves all associated tests as well.
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
Diffstat (limited to 'nss')
| -rw-r--r-- | nss/Makefile | 19 | ||||
| -rw-r--r-- | nss/Versions | 1 | ||||
| -rw-r--r-- | nss/getaddrinfo.c | 2625 | ||||
| -rw-r--r-- | nss/tst-getaddrinfo.c | 68 | ||||
| -rw-r--r-- | nss/tst-getaddrinfo2.c | 78 | ||||
| -rw-r--r-- | nss/tst-getaddrinfo3.c | 151 | ||||
| -rw-r--r-- | nss/tst-getaddrinfo4.c | 67 | ||||
| -rw-r--r-- | nss/tst-getaddrinfo5.c | 69 | ||||
| -rw-r--r-- | nss/tst-rfc3484-2.c | 189 | ||||
| -rw-r--r-- | nss/tst-rfc3484-3.c | 161 | ||||
| -rw-r--r-- | nss/tst-rfc3484.c | 153 |
11 files changed, 3579 insertions, 2 deletions
diff --git a/nss/Makefile b/nss/Makefile index e88754bf51..da5bd207aa 100644 --- a/nss/Makefile +++ b/nss/Makefile @@ -127,6 +127,7 @@ endif # hosts routines: routines += \ + getaddrinfo \ gethstbyad \ gethstbyad_r \ gethstbynm \ @@ -138,6 +139,7 @@ routines += \ # routines ifeq ($(have-thread-library),yes) +CFLAGS-getaddrinfo.c += -fexceptions CFLAGS-gethstbyad.c += -fexceptions CFLAGS-gethstbyad_r.c += -fexceptions CFLAGS-gethstbynm.c += -fexceptions @@ -300,7 +302,13 @@ others-extras = $(makedb-modules) extra-objs += $(makedb-modules:=.o) tests-static = tst-field -tests-internal = tst-field + +tests-internal := \ + tst-field \ + tst-rfc3484 \ + tst-rfc3484-2 \ + tst-rfc3484-3 \ + # tests-internal tests := \ bug17079 \ @@ -309,6 +317,9 @@ tests := \ test-rpcent \ testgrp \ tst-fgetsgent_r \ + tst-getaddrinfo \ + tst-getaddrinfo2 \ + tst-getaddrinfo3 \ tst-gethnm \ tst-getpw \ tst-gshadow \ @@ -327,7 +338,11 @@ tests := \ tst-shadow \ # tests -xtests = bug-erange +xtests := \ + bug-erange \ + tst-getaddrinfo4 \ + tst-getaddrinfo5 \ + # xtests tests-container := \ tst-initgroups1 \ diff --git a/nss/Versions b/nss/Versions index d8c4e373c9..158a9175a4 100644 --- a/nss/Versions +++ b/nss/Versions @@ -19,6 +19,7 @@ libc { fgetspent; fgetspent_r; # g* + getaddrinfo; getaliasbyname; getaliasbyname_r; getaliasent; getaliasent_r; getgrent; getgrent_r; getgrgid; getgrgid_r; getgrnam; getgrnam_r; getgroups; diff --git a/nss/getaddrinfo.c b/nss/getaddrinfo.c new file mode 100644 index 0000000000..531124958d --- /dev/null +++ b/nss/getaddrinfo.c @@ -0,0 +1,2625 @@ +/* Host and service name lookups using Name Service Switch modules. + Copyright (C) 1996-2023 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 + <https://www.gnu.org/licenses/>. */ + +/* The Inner Net License, Version 2.00 + + The author(s) grant permission for redistribution and use in source and +binary forms, with or without modification, of the software and documentation +provided that the following conditions are met: + +0. If you receive a version of the software that is specifically labelled + as not being for redistribution (check the version message and/or README), + you are not permitted to redistribute that version of the software in any + way or form. +1. All terms of the all other applicable copyrights and licenses must be + followed. +2. Redistributions of source code must retain the authors' copyright + notice(s), this list of conditions, and the following disclaimer. +3. Redistributions in binary form must reproduce the authors' copyright + notice(s), this list of conditions, and the following disclaimer in the + documentation and/or other materials provided with the distribution. +4. [The copyright holder has authorized the removal of this clause.] +5. Neither the name(s) of the author(s) nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY ITS AUTHORS AND CONTRIBUTORS ``AS IS'' AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + If these license terms cause you a real problem, contact the author. */ + +/* This software is Copyright 1996 by Craig Metz, All Rights Reserved. */ + +#include <assert.h> +#include <ctype.h> +#include <errno.h> +#include <ifaddrs.h> +#include <netdb.h> +#include <nss.h> +#include <resolv/resolv-internal.h> +#include <resolv/resolv_context.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdio_ext.h> +#include <stdlib.h> +#include <string.h> +#include <stdint.h> +#include <arpa/inet.h> +#include <net/if.h> +#include <netinet/in.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/un.h> +#include <sys/utsname.h> +#include <unistd.h> +#include <nsswitch.h> +#include <libc-lock.h> +#include <not-cancel.h> +#include <nscd/nscd-client.h> +#include <nscd/nscd_proto.h> +#include <scratch_buffer.h> +#include <inet/net-internal.h> + +/* Former AI_IDN_ALLOW_UNASSIGNED and AI_IDN_USE_STD3_ASCII_RULES + flags, now ignored. */ +#define DEPRECATED_AI_IDN 0x300 + +#if IS_IN (libc) +# define feof_unlocked(fp) __feof_unlocked (fp) +#endif + +struct gaih_service + { + const char *name; + int num; + }; + +struct gaih_servtuple + { + int socktype; + int protocol; + int port; + bool set; + }; + + +struct gaih_typeproto + { + int socktype; + int protocol; + uint8_t protoflag; + bool defaultflag; + char name[8]; + }; + +struct gaih_result +{ + struct gaih_addrtuple *at; + char *canon; + char *h_name; + bool free_at; + bool got_ipv6; +}; + +/* Values for `protoflag'. */ +#define GAI_PROTO_NOSERVICE 1 +#define GAI_PROTO_PROTOANY 2 + +static const struct gaih_typeproto gaih_inet_typeproto[] = +{ + { 0, 0, 0, false, "" }, + { SOCK_STREAM, IPPROTO_TCP, 0, true, "tcp" }, + { SOCK_DGRAM, IPPROTO_UDP, 0, true, "udp" }, +#if defined SOCK_DCCP && defined IPPROTO_DCCP + { SOCK_DCCP, IPPROTO_DCCP, 0, false, "dccp" }, +#endif +#ifdef IPPROTO_UDPLITE + { SOCK_DGRAM, IPPROTO_UDPLITE, 0, false, "udplite" }, +#endif +#ifdef IPPROTO_SCTP + { SOCK_STREAM, IPPROTO_SCTP, 0, false, "sctp" }, + { SOCK_SEQPACKET, IPPROTO_SCTP, 0, false, "sctp" }, +#endif + { SOCK_RAW, 0, GAI_PROTO_PROTOANY|GAI_PROTO_NOSERVICE, true, "raw" }, + { 0, 0, 0, false, "" } +}; + +static const struct addrinfo default_hints = + { + .ai_flags = AI_DEFAULT, + .ai_family = PF_UNSPEC, + .ai_socktype = 0, + .ai_protocol = 0, + .ai_addrlen = 0, + .ai_addr = NULL, + .ai_canonname = NULL, + .ai_next = NULL + }; + +static void +gaih_result_reset (struct gaih_result *res) +{ + if (res->free_at) + free (res->at); + free (res->canon); + free (res->h_name); + memset (res, 0, sizeof (*res)); +} + +static int +gaih_inet_serv (const char *servicename, const struct gaih_typeproto *tp, + const struct addrinfo *req, struct gaih_servtuple *st, + struct scratch_buffer *tmpbuf) +{ + struct servent *s; + struct servent ts; + int r; + + do + { + r = __getservbyname_r (servicename, tp->name, &ts, + tmpbuf->data, tmpbuf->length, &s); + if (r != 0 || s == NULL) + { + if (r == ERANGE) + { + if (!scratch_buffer_grow (tmpbuf)) + return -EAI_MEMORY; + } + else + return -EAI_SERVICE; + } + } + while (r); + + st->socktype = tp->socktype; + st->protocol = ((tp->protoflag & GAI_PROTO_PROTOANY) + ? req->ai_protocol : tp->protocol); + st->port = s->s_port; + st->set = true; + + return 0; +} + +/* Convert struct hostent to a list of struct gaih_addrtuple objects. The new + addresses are appended to the tuple array in RES. */ +static bool +convert_hostent_to_gaih_addrtuple (const struct addrinfo *req, int family, + struct hostent *h, struct gaih_result *res) +{ + /* Count the number of addresses in h->h_addr_list. */ + size_t count = 0; + for (char **p = h->h_addr_list; *p != NULL; ++p) + ++count; + + /* Report no data if no addresses are available, or if the incoming + address size is larger than what we can store. */ + if (count == 0 || h->h_length > sizeof (((struct gaih_addrtuple) {}).addr)) + return true; + + struct gaih_addrtuple *array = res->at; + size_t old = 0; + + while (array != NULL) + { + old++; + array = array->next; + } + + array = realloc (res->at, (old + count) * sizeof (*array)); + + if (array == NULL) + return false; + + res->got_ipv6 = family == AF_INET6; + res->at = array; + res->free_at = true; + + /* Duplicate h_name because it may get reclaimed when the underlying storage + is freed. */ + if (res->h_name == NULL) + { + res->h_name = __strdup (h->h_name); + if (res->h_name == NULL) + return false; + } + + /* Update the next pointers on reallocation. */ + for (size_t i = 0; i < old; i++) + array[i].next = array + i + 1; + + array += old; + + memset (array, 0, count * sizeof (*array)); + + for (size_t i = 0; i < count; ++i) + { + if (family == AF_INET && req->ai_family == AF_INET6) + { + /* Perform address mapping. */ + array[i].family = AF_INET6; + memcpy(array[i].addr + 3, h->h_addr_list[i], sizeof (uint32_t)); + array[i].addr[2] = htonl (0xffff); + } + else + { + array[i].family = family; + memcpy (array[i].addr, h->h_addr_list[i], h->h_length); + } + array[i].next = array + i + 1; + } + array[count - 1].next = NULL; + + return true; +} + +static int +gethosts (nss_gethostbyname3_r fct, int family, const char *name, + const struct addrinfo *req, struct scratch_buffer *tmpbuf, + struct gaih_result *res, enum nss_status *statusp, int *no_datap) +{ + struct hostent th; + char *localcanon = NULL; + enum nss_status status; + + *no_datap = 0; + while (1) + { + *statusp = status = DL_CALL_FCT (fct, (name, family, &th, + tmpbuf->data, tmpbuf->length, + &errno, &h_errno, NULL, + &localcanon)); + if (status != NSS_STATUS_TRYAGAIN || h_errno != NETDB_INTERNAL + || errno != ERANGE) + break; + if (!scratch_buffer_grow (tmpbuf)) + return -EAI_MEMORY; + } + if (status == NSS_STATUS_NOTFOUND + || status == NSS_STATUS_TRYAGAIN || status == NSS_STATUS_UNAVAIL) + { + if (h_errno == NETDB_INTERNAL) + return -EAI_SYSTEM; + if (h_errno == TRY_AGAIN) + *no_datap = EAI_AGAIN; + else + *no_datap = h_errno == NO_DATA; + } + else if (status == NSS_STATUS_SUCCESS) + { + if (!convert_hostent_to_gaih_addrtuple (req, family, &th, res)) + return -EAI_MEMORY; + + if (localcanon != NULL && res->canon == NULL) + { + char *canonbuf = __strdup (localcanon); + if (canonbuf == NULL) + return -EAI_MEMORY; + res->canon = canonbuf; + } + } + + return 0; +} + +/* This function is called if a canonical name is requested, but if + the service function did not provide it. It tries to obtain the + name using getcanonname_r from the same service NIP. If the name + cannot be canonicalized, return a copy of NAME. Return NULL on + memory allocation failure. The returned string is allocated on the + heap; the caller has to free it. */ +static char * +getcanonname (nss_action_list nip, const char *hname, const char *name) +{ + nss_getcanonname_r *cfct = __nss_lookup_function (nip, "getcanonname_r"); + char *s = (char *) name; + if (cfct != NULL) + { + char buf[256]; + if (DL_CALL_FCT (cfct, (hname ?: name, buf, sizeof (buf), &s, &errno, + &h_errno)) != NSS_STATUS_SUCCESS) + /* If the canonical name cannot be determined, use the passed + string. */ + s = (char *) name; + } + return __strdup (s); +} + +/* Process looked up canonical name and if necessary, decode to IDNA. Result + is a new string written to CANONP and the earlier string is freed. */ + +static int +process_canonname (const struct addrinfo *req, const char *orig_name, + struct gaih_result *res) +{ + char *canon = res->canon; + + if ((req->ai_flags & AI_CANONNAME) != 0) + { + bool do_idn = req->ai_flags & AI_CANONIDN; + if (do_idn) + { + char *out; + int rc = __idna_from_dns_encoding (canon ?: orig_name, &out); + if (rc == 0) + { + free (canon); + canon = out; + } + else if (rc == EAI_IDN_ENCODE) + /* Use the punycode name as a fallback. */ + do_idn = false; + else + return -rc; + } + if (!do_idn && canon == NULL && (canon = __strdup (orig_name)) == NULL) + return -EAI_MEMORY; + } + + res->canon = canon; + return 0; +} + +static int +get_servtuples (const struct gaih_service *service, const struct addrinfo *req, + struct gaih_servtuple *st, struct scratch_buffer *tmpbuf) +{ + int i; + const struct gaih_typeproto *tp = gaih_inet_typeproto; + + if (req->ai_protocol || req->ai_socktype) + { + ++tp; + + while (tp->name[0] + && ((req->ai_socktype != 0 && req->ai_socktype != tp->socktype) + || (req->ai_protocol != 0 + && !(tp->protoflag & GAI_PROTO_PROTOANY) + && req->ai_protocol != tp->protocol))) + ++tp; + + if (! tp->name[0]) + { + if (req->ai_socktype) + return -EAI_SOCKTYPE; + else + return -EAI_SERVICE; + } + } + + if (service != NULL && (tp->protoflag & GAI_PROTO_NOSERVICE) != 0) + return -EAI_SERVICE; + + if (service == NULL || service->num >= 0) + { + int port = service != NULL ? htons (service->num) : 0; + + if (req->ai_socktype || req->ai_protocol) + { + st[0].socktype = tp->socktype; + st[0].protocol = ((tp->protoflag & GAI_PROTO_PROTOANY) + ? req->ai_protocol : tp->protocol); + st[0].port = port; + st[0].set = true; + + return 0; + } + + /* Neither socket type nor protocol is set. Return all socket types + we know about. */ + for (i = 0, ++tp; tp->name[0]; ++tp) + if (tp->defaultflag) + { + st[i].socktype = tp->socktype; + st[i].protocol = tp->protocol; + st[i].port = port; + st[i++].set = true; + } + + return 0; + } + + if (tp->name[0]) + return gaih_inet_serv (service->name, tp, req, st, tmpbuf); + + for (i = 0, tp++; tp->name[0]; tp++) + { + if ((tp->protoflag & GAI_PROTO_NOSERVICE) != 0) + continue; + + if (req->ai_socktype != 0 + && req->ai_socktype != tp->socktype) + continue; + if (req->ai_protocol != 0 + && !(tp->protoflag & GAI_PROTO_PROTOANY) + && req->ai_protocol != tp->protocol) + continue; + + if (gaih_inet_serv (service->name, + tp, req, &st[i], tmpbuf) != 0) + continue; + + i++; + } + + if (!st[0].set) + return -EAI_SERVICE; + + return 0; +} + +#ifdef USE_NSCD +/* Query addresses from nscd cache, returning a non-zero value on error. + RES members have the lookup result; RES->AT is NULL if there were no errors + but also no results. */ + +static int +get_nscd_addresses (const char *name, const struct addrinfo *req, + struct gaih_result *res) +{ + if (__nss_not_use_nscd_hosts > 0 + && ++__nss_not_use_nscd_hosts > NSS_NSCD_RETRY) + __nss_not_use_nscd_hosts = 0; + + res->at = NULL; + + if (__nss_not_use_nscd_hosts || __nss_database_custom[NSS_DBSIDX_hosts]) + return 0; + + /* Try to use nscd. */ + struct nscd_ai_result *air = NULL; + int err = __nscd_getai (name, &air, &h_errno); + + if (__glibc_unlikely (air == NULL)) + { + /* The database contains a negative entry. */ + if (err == 0) + return -EAI_NONAME; + if (__nss_not_use_nscd_hosts == 0) + { + if (h_errno == NETDB_INTERNAL && errno == ENOMEM) + return -EAI_MEMORY; + if (h_errno == TRY_AGAIN) + return -EAI_AGAIN; + return -EAI_SYSTEM; + } + return 0; + } + + /* Transform into gaih_addrtuple list. */ + int result = 0; + char *addrs = air->addrs; + + struct gaih_addrtuple *addrfree = calloc (air->naddrs, sizeof (*addrfree)); + struct gaih_addrtuple *at = calloc (air->naddrs, sizeof (*at)); + if (at == NULL) + { + result = -EAI_MEMORY; + goto out; + } + + res->free_at = true; + + int count = 0; + for (int i = 0; i < air->naddrs; ++i) + { + socklen_t size = (air->family[i] == AF_INET + ? INADDRSZ : IN6ADDRSZ); + + if (!((air->family[i] == AF_INET + && req->ai_family == AF_INET6 + && (req->ai_flags & AI_V4MAPPED) != 0) + || req->ai_family == AF_UNSPEC + || air->family[i] == req->ai_family)) + { + /* Skip over non-matching result. */ + addrs += size; + continue; + } + + if (air->family[i] == AF_INET && req->ai_family == AF_INET6 + && (req->ai_flags & AI_V4MAPPED)) + { + at[count].family = AF_INET6; + at[count].addr[3] = *(uint32_t *) addrs; + at[count].addr[2] = htonl (0xffff); + } + else if (req->ai_family == AF_UNSPEC + || air->family[i] == req->ai_family) + { + at[count].family = air->family[i]; + memcpy (at[count].addr, addrs, size); + if (air->family[i] == AF_INET6) + res->got_ipv6 = true; + } + at[count].next = at + count + 1; + count++; + addrs += size; + } + + if ((req->ai_flags & AI_CANONNAME) && air->canon != NULL) + { + char *canonbuf = __strdup (air->canon); + if (canonbuf == NULL) + { + result = -EAI_MEMORY; + goto out; + } + res->canon = canonbuf; + } + + if (count == 0) + { + result = -EAI_NONAME; + goto out; + } + + at[count - 1].next = NULL; + + res->at = at; + +out: + free (air); + if (result != 0) + { + free (at); + res->free_at = false; + } + + return result; +} +#endif + +static int +get_nss_addresses (const char *name, const struct addrinfo *req, + struct scratch_buffer *tmpbuf, struct gaih_result *res) +{ + int no_data = 0; + int no_inet6_data = 0; + nss_action_list nip; + enum nss_status inet6_status = NSS_STATUS_UNAVAIL; + enum nss_status status = NSS_STATUS_UNAVAIL; + int no_more; + struct resolv_context *res_ctx = NULL; + bool do_merge = false; + int result = 0; + + no_more = !__nss_database_get (nss_database_hosts, &nip); + + /* If we are looking for both IPv4 and IPv6 address we don't + want the lookup functions to automatically promote IPv4 + addresses to IPv6 addresses, so we use the no_inet6 + function variant. */ + res_ctx = __resolv_context_get (); + if (r |
