1/*	$OpenBSD: getnameinfo_async.c,v 1.15 2020/12/21 09:40:35 eric 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 <netinet/in.h>
22#include <arpa/inet.h>
23#include <arpa/nameser.h>
24#include <netdb.h>
25
26#include <asr.h>
27#include <errno.h>
28#include <stdlib.h>
29#include <string.h>
30#include <unistd.h>
31
32#include "asr_private.h"
33
34static int getnameinfo_async_run(struct asr_query *, struct asr_result *);
35static int _servname(struct asr_query *);
36static int _numerichost(struct asr_query *);
37
38struct asr_query *
39getnameinfo_async(const struct sockaddr *sa, socklen_t slen, char *host,
40    size_t hostlen, char *serv, size_t servlen, int flags, void *asr)
41{
42	struct asr_ctx	 *ac;
43	struct asr_query *as;
44
45	ac = _asr_use_resolver(asr);
46	if ((as = _asr_async_new(ac, ASR_GETNAMEINFO)) == NULL)
47		goto abort; /* errno set */
48	as->as_run = getnameinfo_async_run;
49
50	if (sa->sa_family == AF_INET)
51		memmove(&as->as.ni.sa.sa, sa, sizeof (as->as.ni.sa.sain));
52	else if (sa->sa_family == AF_INET6)
53		memmove(&as->as.ni.sa.sa, sa, sizeof (as->as.ni.sa.sain6));
54
55	as->as.ni.sa.sa.sa_len = slen;
56	as->as.ni.hostname = host;
57	as->as.ni.hostnamelen = hostlen;
58	as->as.ni.servname = serv;
59	as->as.ni.servnamelen = servlen;
60	as->as.ni.flags = flags;
61
62	_asr_ctx_unref(ac);
63	return (as);
64
65    abort:
66	if (as)
67		_asr_async_free(as);
68	_asr_ctx_unref(ac);
69	return (NULL);
70}
71DEF_WEAK(getnameinfo_async);
72
73static int
74getnameinfo_async_run(struct asr_query *as, struct asr_result *ar)
75{
76	void		*addr;
77	socklen_t	 addrlen;
78	int		 r;
79
80    next:
81	switch (as->as_state) {
82
83	case ASR_STATE_INIT:
84
85		/* Make sure the parameters are all valid. */
86
87		if (as->as.ni.sa.sa.sa_family != AF_INET &&
88		    as->as.ni.sa.sa.sa_family != AF_INET6) {
89			ar->ar_gai_errno = EAI_FAMILY;
90			async_set_state(as, ASR_STATE_HALT);
91			break;
92		}
93
94		if ((as->as.ni.sa.sa.sa_family == AF_INET &&
95		    (as->as.ni.sa.sa.sa_len != sizeof (as->as.ni.sa.sain))) ||
96		    (as->as.ni.sa.sa.sa_family == AF_INET6 &&
97		    (as->as.ni.sa.sa.sa_len != sizeof (as->as.ni.sa.sain6)))) {
98			ar->ar_gai_errno = EAI_FAIL;
99			async_set_state(as, ASR_STATE_HALT);
100			break;
101		}
102
103		/* Set the service name first, if needed. */
104		if (_servname(as) == -1) {
105			ar->ar_gai_errno = EAI_OVERFLOW;
106			async_set_state(as, ASR_STATE_HALT);
107			break;
108		}
109
110		if (as->as.ni.hostname == NULL || as->as.ni.hostnamelen == 0) {
111			ar->ar_gai_errno = 0;
112			async_set_state(as, ASR_STATE_HALT);
113			break;
114		}
115
116		if (as->as.ni.flags & NI_NUMERICHOST) {
117			if (_numerichost(as) == -1) {
118				if (errno == ENOMEM)
119					ar->ar_gai_errno = EAI_MEMORY;
120				else if (errno == ENOSPC)
121					ar->ar_gai_errno = EAI_OVERFLOW;
122				else {
123					ar->ar_errno = errno;
124					ar->ar_gai_errno = EAI_SYSTEM;
125				}
126			} else
127				ar->ar_gai_errno = 0;
128			async_set_state(as, ASR_STATE_HALT);
129			break;
130		}
131
132		if (as->as.ni.sa.sa.sa_family == AF_INET) {
133			addrlen = sizeof(as->as.ni.sa.sain.sin_addr);
134			addr = &as->as.ni.sa.sain.sin_addr;
135		} else {
136			addrlen = sizeof(as->as.ni.sa.sain6.sin6_addr);
137			addr = &as->as.ni.sa.sain6.sin6_addr;
138		}
139
140		/*
141		 * Create a subquery to lookup the address.
142		 */
143		as->as_subq = _gethostbyaddr_async_ctx(addr, addrlen,
144		    as->as.ni.sa.sa.sa_family,
145		    as->as_ctx);
146		if (as->as_subq == NULL) {
147			ar->ar_gai_errno = EAI_MEMORY;
148			async_set_state(as, ASR_STATE_HALT);
149			break;
150		}
151
152		async_set_state(as, ASR_STATE_SUBQUERY);
153		break;
154
155	case ASR_STATE_SUBQUERY:
156
157		if ((r = asr_run(as->as_subq, ar)) == ASYNC_COND)
158			return (ASYNC_COND);
159
160		/*
161		 * Request done.
162		 */
163		as->as_subq = NULL;
164
165		if (ar->ar_hostent == NULL) {
166			if (as->as.ni.flags & NI_NAMEREQD) {
167				ar->ar_gai_errno = EAI_NONAME;
168			} else if (_numerichost(as) == -1) {
169				if (errno == ENOMEM)
170					ar->ar_gai_errno = EAI_MEMORY;
171				else if (errno == ENOSPC)
172					ar->ar_gai_errno = EAI_OVERFLOW;
173				else {
174					ar->ar_errno = errno;
175					ar->ar_gai_errno = EAI_SYSTEM;
176				}
177			} else
178				ar->ar_gai_errno = 0;
179		} else {
180			if (strlcpy(as->as.ni.hostname,
181			    ar->ar_hostent->h_name,
182			    as->as.ni.hostnamelen) >= as->as.ni.hostnamelen)
183				ar->ar_gai_errno = EAI_OVERFLOW;
184			else
185				ar->ar_gai_errno = 0;
186			free(ar->ar_hostent);
187		}
188
189		async_set_state(as, ASR_STATE_HALT);
190		break;
191
192	case ASR_STATE_HALT:
193		return (ASYNC_DONE);
194
195	default:
196		ar->ar_errno = EOPNOTSUPP;
197		ar->ar_gai_errno = EAI_SYSTEM;
198		async_set_state(as, ASR_STATE_HALT);
199		break;
200	}
201	goto next;
202}
203
204
205/*
206 * Set the service name on the result buffer is not NULL.
207 * return (-1) if the buffer is too small.
208 */
209static int
210_servname(struct asr_query *as)
211{
212	struct servent		 s;
213	struct servent_data	 sd;
214	int			 port, r;
215	char			*buf = as->as.ni.servname;
216	size_t			 n, buflen = as->as.ni.servnamelen;
217
218	if (as->as.ni.servname == NULL || as->as.ni.servnamelen == 0)
219		return (0);
220
221	if (as->as.ni.sa.sa.sa_family == AF_INET)
222		port = as->as.ni.sa.sain.sin_port;
223	else
224		port = as->as.ni.sa.sain6.sin6_port;
225
226	if (!(as->as.ni.flags & NI_NUMERICSERV)) {
227		memset(&sd, 0, sizeof (sd));
228		r = getservbyport_r(port, (as->as.ni.flags & NI_DGRAM) ?
229		    "udp" : "tcp", &s, &sd);
230		if (r == 0)
231			n = strlcpy(buf, s.s_name, buflen);
232		endservent_r(&sd);
233		if (r == 0) {
234			if (n >= buflen)
235				return (-1);
236			return (0);
237		}
238	}
239
240	r = snprintf(buf, buflen, "%u", ntohs(port));
241	if (r < 0 || r >= buflen)
242		return (-1);
243
244	return (0);
245}
246
247/*
248 * Write the numeric address
249 */
250static int
251_numerichost(struct asr_query *as)
252{
253	unsigned int	ifidx;
254	char		scope[IF_NAMESIZE + 1], *ifname;
255	void		*addr;
256	char		*buf = as->as.ni.hostname;
257	size_t		 buflen = as->as.ni.hostnamelen;
258
259	if (as->as.ni.sa.sa.sa_family == AF_INET)
260		addr = &as->as.ni.sa.sain.sin_addr;
261	else
262		addr = &as->as.ni.sa.sain6.sin6_addr;
263
264	if (inet_ntop(as->as.ni.sa.sa.sa_family, addr, buf, buflen) == NULL)
265		return (-1); /* errno set */
266
267	if (as->as.ni.sa.sa.sa_family == AF_INET6 &&
268	    as->as.ni.sa.sain6.sin6_scope_id) {
269
270		scope[0] = SCOPE_DELIMITER;
271		scope[1] = '\0';
272
273		ifidx = as->as.ni.sa.sain6.sin6_scope_id;
274		ifname = NULL;
275
276		if (IN6_IS_ADDR_LINKLOCAL(&as->as.ni.sa.sain6.sin6_addr) ||
277		    IN6_IS_ADDR_MC_LINKLOCAL(&as->as.ni.sa.sain6.sin6_addr) ||
278		    IN6_IS_ADDR_MC_INTFACELOCAL(&as->as.ni.sa.sain6.sin6_addr))
279			ifname = if_indextoname(ifidx, scope + 1);
280
281		if (ifname == NULL)
282			snprintf(scope + 1, sizeof(scope) - 1, "%u", ifidx);
283
284		strlcat(buf, scope, buflen);
285	}
286
287	return (0);
288}
289