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