RELEASE-NOTES: two more bug fixes
[platform/upstream/c-ares.git] / ares_getnameinfo.c
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 char *ares_striendstr(const char *s1, const char *s2);
81
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 = 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
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       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       free(niquery);
269       return;
270     }
271   niquery->callback(niquery->arg, status, niquery->timeouts, NULL, NULL);
272   free(niquery);
273 }
274
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
285   if (port)
286     {
287       if (flags & ARES_NI_NUMERICSERV)
288         sep = NULL;
289       else
290         {
291           if (flags & ARES_NI_UDP)
292             proto = "udp";
293           else if (flags & ARES_NI_SCTP)
294             proto = "sctp";
295           else if (flags & ARES_NI_DCCP)
296             proto = "dccp";
297           else
298             proto = "tcp";
299 #ifdef HAVE_GETSERVBYPORT_R
300           sep = &se;
301           memset(tmpbuf, 0, sizeof(tmpbuf));
302 #if GETSERVBYPORT_R_ARGS == 6
303           if (getservbyport_r(port, proto, &se, (void *)tmpbuf,
304                               sizeof(tmpbuf), &sep) != 0)
305             sep = NULL;
306 #elif GETSERVBYPORT_R_ARGS == 5
307           sep = getservbyport_r(port, proto, &se, (void *)tmpbuf,
308                                 sizeof(tmpbuf));
309 #elif GETSERVBYPORT_R_ARGS == 4
310           if (getservbyport_r(port, proto, &se, (void *)tmpbuf) != 0)
311             sep = NULL;
312 #else
313           /* Lets just hope the OS uses TLS! */
314           sep = getservbyport(port, proto);
315 #endif
316 #else
317           /* Lets just hope the OS uses TLS! */
318 #if (defined(NETWARE) && !defined(__NOVELL_LIBC__))
319           sep = getservbyport(port, (char*)proto);
320 #else
321           sep = getservbyport(port, proto);
322 #endif
323 #endif
324         }
325       if (sep && sep->s_name)
326         /* get service name */
327         strcpy(tmpbuf, sep->s_name);
328       else
329         /* get port as a string */
330         sprintf(tmpbuf, "%u", (unsigned int)ntohs(port));
331       if (strlen(tmpbuf) < buflen)
332         /* return it if buffer big enough */
333         strcpy(buf, tmpbuf);
334       else
335         /* avoid reusing previous one */
336         buf[0] = '\0';
337       return buf;
338     }
339   buf[0] = '\0';
340   return NULL;
341 }
342
343 #ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID
344 static void append_scopeid(struct sockaddr_in6 *addr6, unsigned int flags,
345                            char *buf, size_t buflen)
346 {
347 #ifdef HAVE_IF_INDEXTONAME
348   int is_ll, is_mcll;
349 #endif
350   static const char fmt_u[] = "%u";
351   static const char fmt_lu[] = "%lu";
352   char tmpbuf[IF_NAMESIZE + 2];
353   size_t bufl;
354   const char *fmt = (sizeof(addr6->sin6_scope_id) > sizeof(unsigned int))?
355     fmt_lu:fmt_u;
356
357   tmpbuf[0] = '%';
358
359 #ifdef HAVE_IF_INDEXTONAME
360   is_ll = IN6_IS_ADDR_LINKLOCAL(&addr6->sin6_addr);
361   is_mcll = IN6_IS_ADDR_MC_LINKLOCAL(&addr6->sin6_addr);
362   if ((flags & ARES_NI_NUMERICSCOPE) ||
363       (!is_ll && !is_mcll))
364     {
365        sprintf(&tmpbuf[1], fmt, addr6->sin6_scope_id);
366     }
367   else
368     {
369       if (if_indextoname(addr6->sin6_scope_id, &tmpbuf[1]) == NULL)
370         sprintf(&tmpbuf[1], fmt, addr6->sin6_scope_id);
371     }
372 #else
373   sprintf(&tmpbuf[1], fmt, addr6->sin6_scope_id);
374   (void) flags;
375 #endif
376   tmpbuf[IF_NAMESIZE + 1] = '\0';
377   bufl = strlen(buf);
378
379   if(bufl + strlen(tmpbuf) < buflen)
380     /* only append the scopeid string if it fits in the target buffer */
381     strcpy(&buf[bufl], tmpbuf);
382 }
383 #endif
384
385 /* Determines if s1 ends with the string in s2 (case-insensitive) */
386 static char *ares_striendstr(const char *s1, const char *s2)
387 {
388   const char *c1, *c2, *c1_begin;
389   int lo1, lo2;
390   size_t s1_len = strlen(s1), s2_len = strlen(s2);
391
392   /* If the substr is longer than the full str, it can't match */
393   if (s2_len > s1_len)
394     return NULL;
395
396   /* Jump to the end of s1 minus the length of s2 */
397   c1_begin = s1+s1_len-s2_len;
398   c1 = (const char *)c1_begin;
399   c2 = s2;
400   while (c2 < s2+s2_len)
401     {
402       lo1 = TOLOWER(*c1);
403       lo2 = TOLOWER(*c2);
404       if (lo1 != lo2)
405         return NULL;
406       else
407         {
408           c1++;
409           c2++;
410         }
411     }
412   if (c2 == c1 && c2 == NULL)
413     return (char *)c1_begin;
414   return NULL;
415 }