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