1/*	$OpenBSD: getnameinfo.c,v 1.11 2022/12/27 17:10:06 jmc Exp $	*/
2/*
3 * Copyright (c) 2012 Eric Faurot <eric@openbsd.org>
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17
18#include <sys/types.h>
19#include <sys/socket.h>
20#include <net/if.h>
21#include <arpa/inet.h>
22#include <netinet/in.h>
23#include <netdb.h>
24
25#include <asr.h>
26#include <errno.h>
27#include <resolv.h>
28#include <string.h>
29
30static size_t asr_print_addr(const struct sockaddr *, char *, size_t);
31static size_t asr_print_port(const struct sockaddr *, const char *, char *, size_t);
32
33#define SA_IN(sa) ((struct sockaddr_in*)(sa))
34#define SA_IN6(sa) ((struct sockaddr_in6*)(sa))
35
36/*
37 * Print the textual representation (as given by inet_ntop(3)) of the address
38 * set in "sa".
39 *
40 * Return the total length of the string it tried to create or 0 if an error
41 * occurred, in which case errno is set.  On success, the constructed string
42 * is guaranteed to be NUL-terminated.  Overflow must be detected by checking
43 * the returned size against buflen.
44 *
45 */
46static size_t
47asr_print_addr(const struct sockaddr *sa, char *buf, size_t buflen)
48{
49	unsigned int ifidx;
50	char tmp[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")];
51	char scope[IF_NAMESIZE + 1], *ifname;
52	const void *addr;
53	size_t s;
54
55	switch(sa->sa_family) {
56	case AF_INET:
57		addr = &SA_IN(sa)->sin_addr;
58		break;
59	case AF_INET6:
60		addr = &SA_IN6(sa)->sin6_addr;
61		break;
62	default:
63		errno = EINVAL;
64		return (0);
65	}
66
67	if (inet_ntop(sa->sa_family, addr, tmp, sizeof(tmp)) == NULL)
68		return (0); /* errno set */
69
70	s = strlcpy(buf, tmp, buflen);
71
72	if (sa->sa_family == AF_INET6 && SA_IN6(sa)->sin6_scope_id) {
73
74		scope[0] = SCOPE_DELIMITER;
75		scope[1] = '\0';
76
77		ifidx = SA_IN6(sa)->sin6_scope_id;
78		ifname = NULL;
79
80		if (IN6_IS_ADDR_LINKLOCAL(&(SA_IN6(sa)->sin6_addr)) ||
81		    IN6_IS_ADDR_MC_LINKLOCAL(&(SA_IN6(sa)->sin6_addr)) ||
82		    IN6_IS_ADDR_MC_INTFACELOCAL(&(SA_IN6(sa)->sin6_addr)))
83			ifname = if_indextoname(ifidx, scope + 1);
84
85		if (ifname == NULL)
86			(void)snprintf(scope + 1, sizeof(scope) - 1, "%u", ifidx);
87
88		if (s < buflen)
89			(void)strlcat(buf, scope, buflen);
90
91		s += strlen(scope);
92	}
93
94	return (s);
95}
96
97/*
98 * Print the textual representation of the port set on "sa".
99 *
100 * If proto is not NULL, it is used as parameter to "getservbyport_r(3)" to
101 * return a service name. If it's not set, or if no matching service is found,
102 * it prints the portno.
103 *
104 * Return the total length of the string it tried to create or 0 if an error
105 * occurred, in which case errno is set.  On success, the constructed string
106 * is guaranteed to be NUL-terminated.  Overflow must be detected by checking
107 * the returned size against buflen.
108 */
109static size_t
110asr_print_port(const struct sockaddr *sa, const char *proto, char *buf, size_t buflen)
111{
112	struct servent s;
113	struct servent_data sd;
114	int port, r, saved_errno;
115	size_t n;
116
117	switch(sa->sa_family) {
118	case AF_INET:
119		port = SA_IN(sa)->sin_port;
120		break;
121	case AF_INET6:
122		port = SA_IN6(sa)->sin6_port;
123		break;
124	default:
125		errno = EINVAL;
126		return (0);
127	}
128
129	if (proto) {
130		memset(&sd, 0, sizeof (sd));
131		saved_errno = errno;
132		r = getservbyport_r(port, proto, &s, &sd);
133		if (r == 0)
134			n = strlcpy(buf, s.s_name, buflen);
135		endservent_r(&sd);
136		errno = saved_errno;
137		if (r == 0)
138			return (n);
139	}
140
141	r = snprintf(buf, buflen, "%u", ntohs(port));
142	if (r < 0 || r >= buflen) 	/* Actually, this can not happen */
143		return (0);
144
145	return (r);
146}
147
148int
149getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host,
150    size_t hostlen, char *serv, size_t servlen, int flags)
151{
152	struct asr_query *as;
153	struct asr_result ar;
154	int saved_errno = errno;
155	const char *proto;
156	size_t r;
157
158	/*
159	 * Take a shortcut if we don't care about hostname,
160	 * or if NI_NUMERICHOST is set.
161	 */
162	if (host == NULL || hostlen == 0 ||
163	    (host && hostlen && (flags & NI_NUMERICHOST))) {
164		if (host) {
165			r = asr_print_addr(sa, host, hostlen);
166			if (r == 0)
167				return (EAI_SYSTEM); /* errno set */
168			if (r >= hostlen)
169				return (EAI_OVERFLOW);
170		}
171
172		if (serv && servlen) {
173			if (flags & NI_NUMERICSERV)
174				proto = NULL;
175			else
176				proto = (flags & NI_DGRAM) ? "udp" : "tcp";
177			r = asr_print_port(sa, proto, serv, servlen);
178			if (r == 0)
179				return (EAI_SYSTEM); /* errno set */
180			if (r >= servlen)
181				return (EAI_OVERFLOW);
182		}
183
184		errno = saved_errno;
185		return (0);
186	}
187
188	res_init();
189
190	as = getnameinfo_async(sa, salen, host, hostlen, serv, servlen, flags,
191	    NULL);
192	if (as == NULL) {
193		if (errno == ENOMEM) {
194			errno = saved_errno;
195			return (EAI_MEMORY);
196		}
197		return (EAI_SYSTEM);
198	}
199
200	asr_run_sync(as, &ar);
201	if (ar.ar_gai_errno == EAI_SYSTEM)
202		errno = ar.ar_errno;
203
204	return (ar.ar_gai_errno);
205}
206DEF_WEAK(getnameinfo);
207