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