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