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