/src/c-ares/src/lib/ares_getnameinfo.c
Line | Count | Source (jump to first uncovered line) |
1 | | |
2 | | /* Copyright 2005, 2023 by Dominick Meglio |
3 | | * |
4 | | * Permission to use, copy, modify, and distribute this |
5 | | * software and its documentation for any purpose and without |
6 | | * fee is hereby granted, provided that the above copyright |
7 | | * notice appear in all copies and that both that copyright |
8 | | * notice and this permission notice appear in supporting |
9 | | * documentation, and that the name of M.I.T. not be used in |
10 | | * advertising or publicity pertaining to distribution of the |
11 | | * software without specific, written prior permission. |
12 | | * M.I.T. makes no representations about the suitability of |
13 | | * this software for any purpose. It is provided "as is" |
14 | | * without express or implied warranty. |
15 | | * |
16 | | * SPDX-License-Identifier: MIT |
17 | | */ |
18 | | #include "ares_setup.h" |
19 | | |
20 | | #ifdef HAVE_GETSERVBYPORT_R |
21 | | # if !defined(GETSERVBYPORT_R_ARGS) || \ |
22 | | (GETSERVBYPORT_R_ARGS < 4) || (GETSERVBYPORT_R_ARGS > 6) |
23 | | # error "you MUST specifiy a valid number of arguments for getservbyport_r" |
24 | | # endif |
25 | | #endif |
26 | | |
27 | | #ifdef HAVE_NETINET_IN_H |
28 | | # include <netinet/in.h> |
29 | | #endif |
30 | | #ifdef HAVE_NETDB_H |
31 | | # include <netdb.h> |
32 | | #endif |
33 | | #ifdef HAVE_ARPA_INET_H |
34 | | # include <arpa/inet.h> |
35 | | #endif |
36 | | |
37 | | #include "ares_nameser.h" |
38 | | |
39 | | #ifdef HAVE_NET_IF_H |
40 | | #include <net/if.h> |
41 | | #endif |
42 | | |
43 | | #include "ares.h" |
44 | | #include "ares_ipv6.h" |
45 | | #include "ares_nowarn.h" |
46 | | #include "ares_private.h" |
47 | | |
48 | | struct nameinfo_query { |
49 | | ares_nameinfo_callback callback; |
50 | | void *arg; |
51 | | union { |
52 | | struct sockaddr_in addr4; |
53 | | struct sockaddr_in6 addr6; |
54 | | } addr; |
55 | | int family; |
56 | | int flags; |
57 | | int timeouts; |
58 | | }; |
59 | | |
60 | | #ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID |
61 | | #define IPBUFSIZ \ |
62 | 0 | (sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255") + IF_NAMESIZE) |
63 | | #else |
64 | | #define IPBUFSIZ \ |
65 | | (sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")) |
66 | | #endif |
67 | | |
68 | | static void nameinfo_callback(void *arg, int status, int timeouts, |
69 | | struct hostent *host); |
70 | | static char *lookup_service(unsigned short port, int flags, |
71 | | char *buf, size_t buflen); |
72 | | #ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID |
73 | | static void append_scopeid(struct sockaddr_in6 *addr6, unsigned int scopeid, |
74 | | char *buf, size_t buflen); |
75 | | #endif |
76 | | STATIC_TESTABLE char *ares_striendstr(const char *s1, const char *s2); |
77 | | |
78 | | void ares_getnameinfo(ares_channel channel, const struct sockaddr *sa, |
79 | | ares_socklen_t salen, |
80 | | int flags, ares_nameinfo_callback callback, void *arg) |
81 | 0 | { |
82 | 0 | struct sockaddr_in *addr = NULL; |
83 | 0 | struct sockaddr_in6 *addr6 = NULL; |
84 | 0 | struct nameinfo_query *niquery; |
85 | 0 | unsigned int port = 0; |
86 | | |
87 | | /* Validate socket address family and length */ |
88 | 0 | if ((sa->sa_family == AF_INET) && |
89 | 0 | (salen == sizeof(struct sockaddr_in))) |
90 | 0 | { |
91 | 0 | addr = CARES_INADDR_CAST(struct sockaddr_in *, sa); |
92 | 0 | port = addr->sin_port; |
93 | 0 | } |
94 | 0 | else if ((sa->sa_family == AF_INET6) && |
95 | 0 | (salen == sizeof(struct sockaddr_in6))) |
96 | 0 | { |
97 | 0 | addr6 = CARES_INADDR_CAST(struct sockaddr_in6 *, sa); |
98 | 0 | port = addr6->sin6_port; |
99 | 0 | } |
100 | 0 | else |
101 | 0 | { |
102 | 0 | callback(arg, ARES_ENOTIMP, 0, NULL, NULL); |
103 | 0 | return; |
104 | 0 | } |
105 | | |
106 | | /* If neither, assume they want a host */ |
107 | 0 | if (!(flags & ARES_NI_LOOKUPSERVICE) && !(flags & ARES_NI_LOOKUPHOST)) |
108 | 0 | flags |= ARES_NI_LOOKUPHOST; |
109 | | |
110 | | /* All they want is a service, no need for DNS */ |
111 | 0 | if ((flags & ARES_NI_LOOKUPSERVICE) && !(flags & ARES_NI_LOOKUPHOST)) |
112 | 0 | { |
113 | 0 | char buf[33], *service; |
114 | |
|
115 | 0 | service = lookup_service((unsigned short)(port & 0xffff), |
116 | 0 | flags, buf, sizeof(buf)); |
117 | 0 | callback(arg, ARES_SUCCESS, 0, NULL, service); |
118 | 0 | return; |
119 | 0 | } |
120 | | |
121 | | /* They want a host lookup */ |
122 | 0 | if ((flags & ARES_NI_LOOKUPHOST)) |
123 | 0 | { |
124 | | /* A numeric host can be handled without DNS */ |
125 | 0 | if ((flags & ARES_NI_NUMERICHOST)) |
126 | 0 | { |
127 | 0 | char ipbuf[IPBUFSIZ]; |
128 | 0 | char srvbuf[33]; |
129 | 0 | char *service = NULL; |
130 | 0 | ipbuf[0] = 0; |
131 | | |
132 | | /* Specifying not to lookup a host, but then saying a host |
133 | | * is required has to be illegal. |
134 | | */ |
135 | 0 | if (flags & ARES_NI_NAMEREQD) |
136 | 0 | { |
137 | 0 | callback(arg, ARES_EBADFLAGS, 0, NULL, NULL); |
138 | 0 | return; |
139 | 0 | } |
140 | 0 | if (salen == sizeof(struct sockaddr_in6)) |
141 | 0 | { |
142 | 0 | ares_inet_ntop(AF_INET6, &addr6->sin6_addr, ipbuf, IPBUFSIZ); |
143 | | /* If the system supports scope IDs, use it */ |
144 | 0 | #ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID |
145 | 0 | append_scopeid(addr6, flags, ipbuf, sizeof(ipbuf)); |
146 | 0 | #endif |
147 | 0 | } |
148 | 0 | else |
149 | 0 | { |
150 | 0 | ares_inet_ntop(AF_INET, &addr->sin_addr, ipbuf, IPBUFSIZ); |
151 | 0 | } |
152 | | /* They also want a service */ |
153 | 0 | if (flags & ARES_NI_LOOKUPSERVICE) |
154 | 0 | service = lookup_service((unsigned short)(port & 0xffff), |
155 | 0 | flags, srvbuf, sizeof(srvbuf)); |
156 | 0 | callback(arg, ARES_SUCCESS, 0, ipbuf, service); |
157 | 0 | return; |
158 | 0 | } |
159 | | /* This is where a DNS lookup becomes necessary */ |
160 | 0 | else |
161 | 0 | { |
162 | 0 | niquery = ares_malloc(sizeof(struct nameinfo_query)); |
163 | 0 | if (!niquery) |
164 | 0 | { |
165 | 0 | callback(arg, ARES_ENOMEM, 0, NULL, NULL); |
166 | 0 | return; |
167 | 0 | } |
168 | 0 | niquery->callback = callback; |
169 | 0 | niquery->arg = arg; |
170 | 0 | niquery->flags = flags; |
171 | 0 | niquery->timeouts = 0; |
172 | 0 | if (sa->sa_family == AF_INET) |
173 | 0 | { |
174 | 0 | niquery->family = AF_INET; |
175 | 0 | memcpy(&niquery->addr.addr4, addr, sizeof(niquery->addr.addr4)); |
176 | 0 | ares_gethostbyaddr(channel, &addr->sin_addr, |
177 | 0 | sizeof(struct in_addr), AF_INET, |
178 | 0 | nameinfo_callback, niquery); |
179 | 0 | } |
180 | 0 | else |
181 | 0 | { |
182 | 0 | niquery->family = AF_INET6; |
183 | 0 | memcpy(&niquery->addr.addr6, addr6, sizeof(niquery->addr.addr6)); |
184 | 0 | ares_gethostbyaddr(channel, &addr6->sin6_addr, |
185 | 0 | sizeof(struct ares_in6_addr), AF_INET6, |
186 | 0 | nameinfo_callback, niquery); |
187 | 0 | } |
188 | 0 | } |
189 | 0 | } |
190 | 0 | } |
191 | | |
192 | | static void nameinfo_callback(void *arg, int status, int timeouts, |
193 | | struct hostent *host) |
194 | 0 | { |
195 | 0 | struct nameinfo_query *niquery = (struct nameinfo_query *) arg; |
196 | 0 | char srvbuf[33]; |
197 | 0 | char *service = NULL; |
198 | |
|
199 | 0 | niquery->timeouts += timeouts; |
200 | 0 | if (status == ARES_SUCCESS) |
201 | 0 | { |
202 | | /* They want a service too */ |
203 | 0 | if (niquery->flags & ARES_NI_LOOKUPSERVICE) |
204 | 0 | { |
205 | 0 | if (niquery->family == AF_INET) |
206 | 0 | service = lookup_service(niquery->addr.addr4.sin_port, |
207 | 0 | niquery->flags, srvbuf, sizeof(srvbuf)); |
208 | 0 | else |
209 | 0 | service = lookup_service(niquery->addr.addr6.sin6_port, |
210 | 0 | niquery->flags, srvbuf, sizeof(srvbuf)); |
211 | 0 | } |
212 | | /* NOFQDN means we have to strip off the domain name portion. We do |
213 | | this by determining our own domain name, then searching the string |
214 | | for this domain name and removing it. |
215 | | */ |
216 | 0 | #ifdef HAVE_GETHOSTNAME |
217 | 0 | if (niquery->flags & ARES_NI_NOFQDN) |
218 | 0 | { |
219 | 0 | char buf[255]; |
220 | 0 | char *domain; |
221 | 0 | gethostname(buf, 255); |
222 | 0 | if ((domain = strchr(buf, '.')) != NULL) |
223 | 0 | { |
224 | 0 | char *end = ares_striendstr(host->h_name, domain); |
225 | 0 | if (end) |
226 | 0 | *end = 0; |
227 | 0 | } |
228 | 0 | } |
229 | 0 | #endif |
230 | 0 | niquery->callback(niquery->arg, ARES_SUCCESS, niquery->timeouts, |
231 | 0 | (char *)(host->h_name), |
232 | 0 | service); |
233 | 0 | ares_free(niquery); |
234 | 0 | return; |
235 | 0 | } |
236 | | /* We couldn't find the host, but it's OK, we can use the IP */ |
237 | 0 | else if (status == ARES_ENOTFOUND && !(niquery->flags & ARES_NI_NAMEREQD)) |
238 | 0 | { |
239 | 0 | char ipbuf[IPBUFSIZ]; |
240 | 0 | if (niquery->family == AF_INET) |
241 | 0 | ares_inet_ntop(AF_INET, &niquery->addr.addr4.sin_addr, ipbuf, |
242 | 0 | IPBUFSIZ); |
243 | 0 | else |
244 | 0 | { |
245 | 0 | ares_inet_ntop(AF_INET6, &niquery->addr.addr6.sin6_addr, ipbuf, |
246 | 0 | IPBUFSIZ); |
247 | 0 | #ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID |
248 | 0 | append_scopeid(&niquery->addr.addr6, niquery->flags, ipbuf, |
249 | 0 | sizeof(ipbuf)); |
250 | 0 | #endif |
251 | 0 | } |
252 | | /* They want a service too */ |
253 | 0 | if (niquery->flags & ARES_NI_LOOKUPSERVICE) |
254 | 0 | { |
255 | 0 | if (niquery->family == AF_INET) |
256 | 0 | service = lookup_service(niquery->addr.addr4.sin_port, |
257 | 0 | niquery->flags, srvbuf, sizeof(srvbuf)); |
258 | 0 | else |
259 | 0 | service = lookup_service(niquery->addr.addr6.sin6_port, |
260 | 0 | niquery->flags, srvbuf, sizeof(srvbuf)); |
261 | 0 | } |
262 | 0 | niquery->callback(niquery->arg, ARES_SUCCESS, niquery->timeouts, ipbuf, |
263 | 0 | service); |
264 | 0 | ares_free(niquery); |
265 | 0 | return; |
266 | 0 | } |
267 | 0 | niquery->callback(niquery->arg, status, niquery->timeouts, NULL, NULL); |
268 | 0 | ares_free(niquery); |
269 | 0 | } |
270 | | |
271 | | static char *lookup_service(unsigned short port, int flags, |
272 | | char *buf, size_t buflen) |
273 | 0 | { |
274 | 0 | const char *proto; |
275 | 0 | struct servent *sep; |
276 | 0 | #ifdef HAVE_GETSERVBYPORT_R |
277 | 0 | struct servent se; |
278 | 0 | #endif |
279 | 0 | char tmpbuf[4096]; |
280 | 0 | char *name; |
281 | 0 | size_t name_len; |
282 | |
|
283 | 0 | if (port) |
284 | 0 | { |
285 | 0 | if (flags & ARES_NI_NUMERICSERV) |
286 | 0 | sep = NULL; |
287 | 0 | else |
288 | 0 | { |
289 | 0 | if (flags & ARES_NI_UDP) |
290 | 0 | proto = "udp"; |
291 | 0 | else if (flags & ARES_NI_SCTP) |
292 | 0 | proto = "sctp"; |
293 | 0 | else if (flags & ARES_NI_DCCP) |
294 | 0 | proto = "dccp"; |
295 | 0 | else |
296 | 0 | proto = "tcp"; |
297 | 0 | #ifdef HAVE_GETSERVBYPORT_R |
298 | 0 | memset(&se, 0, sizeof(se)); |
299 | 0 | sep = &se; |
300 | 0 | memset(tmpbuf, 0, sizeof(tmpbuf)); |
301 | 0 | #if GETSERVBYPORT_R_ARGS == 6 |
302 | 0 | if (getservbyport_r(port, proto, &se, (void *)tmpbuf, |
303 | 0 | sizeof(tmpbuf), &sep) != 0) |
304 | 0 | sep = NULL; /* LCOV_EXCL_LINE: buffer large so this never fails */ |
305 | | #elif GETSERVBYPORT_R_ARGS == 5 |
306 | | sep = getservbyport_r(port, proto, &se, (void *)tmpbuf, |
307 | | sizeof(tmpbuf)); |
308 | | #elif GETSERVBYPORT_R_ARGS == 4 |
309 | | if (getservbyport_r(port, proto, &se, (void *)tmpbuf) != 0) |
310 | | sep = NULL; |
311 | | #else |
312 | | /* Lets just hope the OS uses TLS! */ |
313 | | sep = getservbyport(port, proto); |
314 | | #endif |
315 | | #else |
316 | | /* Lets just hope the OS uses TLS! */ |
317 | | #if (defined(NETWARE) && !defined(__NOVELL_LIBC__)) |
318 | | sep = getservbyport(port, (char*)proto); |
319 | | #else |
320 | | sep = getservbyport(port, proto); |
321 | | #endif |
322 | | #endif |
323 | 0 | } |
324 | 0 | if (sep && sep->s_name) |
325 | 0 | { |
326 | | /* get service name */ |
327 | 0 | name = sep->s_name; |
328 | 0 | } |
329 | 0 | else |
330 | 0 | { |
331 | | /* get port as a string */ |
332 | 0 | snprintf(tmpbuf, sizeof(tmpbuf), "%u", (unsigned int)ntohs(port)); |
333 | 0 | name = tmpbuf; |
334 | 0 | } |
335 | 0 | name_len = strlen(name); |
336 | 0 | if (name_len < buflen) |
337 | | /* return it if buffer big enough */ |
338 | 0 | memcpy(buf, name, name_len + 1); |
339 | 0 | else |
340 | | /* avoid reusing previous one */ |
341 | 0 | buf[0] = '\0'; /* LCOV_EXCL_LINE: no real service names are too big */ |
342 | 0 | return buf; |
343 | 0 | } |
344 | 0 | buf[0] = '\0'; |
345 | 0 | return NULL; |
346 | 0 | } |
347 | | |
348 | | #ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID |
349 | | static void append_scopeid(struct sockaddr_in6 *addr6, unsigned int flags, |
350 | | char *buf, size_t buflen) |
351 | 0 | { |
352 | 0 | #ifdef HAVE_IF_INDEXTONAME |
353 | 0 | int is_ll, is_mcll; |
354 | 0 | #endif |
355 | 0 | char tmpbuf[IF_NAMESIZE + 2]; |
356 | 0 | size_t bufl; |
357 | 0 | int is_scope_long = sizeof(addr6->sin6_scope_id) > sizeof(unsigned int); |
358 | |
|
359 | 0 | tmpbuf[0] = '%'; |
360 | |
|
361 | 0 | #ifdef HAVE_IF_INDEXTONAME |
362 | 0 | is_ll = IN6_IS_ADDR_LINKLOCAL(&addr6->sin6_addr); |
363 | 0 | is_mcll = IN6_IS_ADDR_MC_LINKLOCAL(&addr6->sin6_addr); |
364 | 0 | if ((flags & ARES_NI_NUMERICSCOPE) || |
365 | 0 | (!is_ll && !is_mcll)) |
366 | 0 | { |
367 | 0 | if (is_scope_long) |
368 | 0 | { |
369 | 0 | snprintf(&tmpbuf[1], sizeof(tmpbuf)-1, "%lu", (unsigned long)addr6->sin6_scope_id); |
370 | 0 | } |
371 | 0 | else |
372 | 0 | { |
373 | 0 | snprintf(&tmpbuf[1], sizeof(tmpbuf)-1, "%u", (unsigned int)addr6->sin6_scope_id); |
374 | 0 | } |
375 | 0 | } |
376 | 0 | else |
377 | 0 | { |
378 | 0 | if (if_indextoname(addr6->sin6_scope_id, &tmpbuf[1]) == NULL) |
379 | 0 | { |
380 | 0 | if (is_scope_long) |
381 | 0 | { |
382 | 0 | snprintf(&tmpbuf[1], sizeof(tmpbuf)-1, "%lu", (unsigned long)addr6->sin6_scope_id); |
383 | 0 | } |
384 | 0 | else |
385 | 0 | { |
386 | 0 | snprintf(&tmpbuf[1], sizeof(tmpbuf)-1, "%u", (unsigned int)addr6->sin6_scope_id); |
387 | 0 | } |
388 | 0 | } |
389 | 0 | } |
390 | | #else |
391 | | if (is_scope_long) |
392 | | { |
393 | | snprintf(&tmpbuf[1], sizeof(tmpbuf)-1, "%lu", (unsigned long)addr6->sin6_scope_id); |
394 | | } |
395 | | else |
396 | | { |
397 | | snprintf(&tmpbuf[1], sizeof(tmpbuf)-1, "%u", (unsigned int)addr6->sin6_scope_id); |
398 | | } |
399 | | (void) flags; |
400 | | #endif |
401 | 0 | tmpbuf[IF_NAMESIZE + 1] = '\0'; |
402 | 0 | bufl = strlen(buf); |
403 | |
|
404 | 0 | if(bufl + strlen(tmpbuf) < buflen) |
405 | | /* only append the scopeid string if it fits in the target buffer */ |
406 | 0 | strcpy(&buf[bufl], tmpbuf); |
407 | 0 | } |
408 | | #endif |
409 | | |
410 | | /* Determines if s1 ends with the string in s2 (case-insensitive) */ |
411 | | STATIC_TESTABLE char *ares_striendstr(const char *s1, const char *s2) |
412 | 371 | { |
413 | 371 | const char *c1, *c2, *c1_begin; |
414 | 371 | int lo1, lo2; |
415 | 371 | size_t s1_len = strlen(s1), s2_len = strlen(s2); |
416 | | |
417 | | /* If the substr is longer than the full str, it can't match */ |
418 | 371 | if (s2_len > s1_len) |
419 | 90 | return NULL; |
420 | | |
421 | | /* Jump to the end of s1 minus the length of s2 */ |
422 | 281 | c1_begin = s1+s1_len-s2_len; |
423 | 281 | c1 = (const char *)c1_begin; |
424 | 281 | c2 = s2; |
425 | 356 | while (c2 < s2+s2_len) |
426 | 354 | { |
427 | 354 | lo1 = TOLOWER(*c1); |
428 | 354 | lo2 = TOLOWER(*c2); |
429 | 354 | if (lo1 != lo2) |
430 | 279 | return NULL; |
431 | 75 | else |
432 | 75 | { |
433 | 75 | c1++; |
434 | 75 | c2++; |
435 | 75 | } |
436 | 354 | } |
437 | 2 | return (char *)c1_begin; |
438 | 281 | } |
439 | | |
440 | | int ares__is_onion_domain(const char *name) |
441 | 186 | { |
442 | 186 | if (ares_striendstr(name, ".onion")) |
443 | 1 | return 1; |
444 | | |
445 | 185 | if (ares_striendstr(name, ".onion.")) |
446 | 1 | return 1; |
447 | | |
448 | 184 | return 0; |
449 | 185 | } |