2009-05-06 Jeff Cai <jeff.cai@sun.com>
[platform/upstream/evolution-data-server.git] / camel / camel-net-utils.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2  *
3  * Authors: Michael Zucchi <notzed@ximian.com>
4  *          Jeffrey Stedfast <fejj@ximian.com>
5  *          Chris Toshok <toshok@ximian.com>
6  *
7  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
8  *
9  * This program is free software; you can redistribute it and/or 
10  * modify it under the terms of version 2 of the GNU Lesser General Public 
11  * License as published by the Free Software Foundation.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
21  * USA
22  */
23
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27
28 #include <errno.h>
29 #include <pthread.h>
30 #include <stdio.h>
31
32 #include <glib.h>
33 #include <glib/gi18n-lib.h>
34
35 #include "camel-exception.h"
36 #include "camel-msgport.h"
37 #include "camel-net-utils.h"
38 #include "camel-operation.h"
39
40 #define d(x)
41
42 /* These are GNU extensions */
43 #ifndef NI_MAXHOST
44 #define NI_MAXHOST      1025
45 #endif
46 #ifndef NI_MAXSERV
47 #define NI_MAXSERV      32
48 #endif
49
50 #ifdef G_OS_WIN32
51
52 typedef short in_port_t;
53
54 #undef gai_strerror
55 #define gai_strerror my_gai_strerror
56
57 /* gai_strerror() is implemented as an inline function in Microsoft's
58  * SDK, but mingw lacks that. So implement here. The EAI_* errors can
59  * be handled with the normal FormatMessage() API,
60  * i.e. g_win32_error_message().
61  */
62
63 static const char *
64 gai_strerror (int error_code)
65 {
66         gchar *msg = g_win32_error_message (error_code);
67         GQuark quark = g_quark_from_string (msg);
68         const gchar *retval = g_quark_to_string (quark);
69
70         g_free (msg);
71
72         return retval;
73 }
74
75 #endif
76
77 /* gethostbyname emulation code for emulating getaddrinfo code ...
78
79 This should probably go away */
80
81 #ifdef NEED_ADDRINFO
82
83 #if !defined (HAVE_GETHOSTBYNAME_R) || !defined (HAVE_GETHOSTBYADDR_R)
84 G_LOCK_DEFINE_STATIC (gethost_mutex);
85 #endif
86
87 #define ALIGN(x) (((x) + (sizeof (char *) - 1)) & ~(sizeof (char *) - 1))
88
89 #define GETHOST_PROCESS(h, host, buf, buflen, herr) G_STMT_START {     \
90         int num_aliases = 0, num_addrs = 0;                            \
91         int req_length;                                                \
92         char *p;                                                       \
93         int i;                                                         \
94                                                                        \
95         /* check to make sure we have enough room in our buffer */     \
96         req_length = 0;                                                \
97         if (h->h_aliases) {                                            \
98                 for (i = 0; h->h_aliases[i]; i++)                      \
99                         req_length += strlen (h->h_aliases[i]) + 1;    \
100                 num_aliases = i;                                       \
101         }                                                              \
102                                                                        \
103         if (h->h_addr_list) {                                          \
104                 for (i = 0; h->h_addr_list[i]; i++)                    \
105                         req_length += h->h_length;                     \
106                 num_addrs = i;                                         \
107         }                                                              \
108                                                                        \
109         req_length += sizeof (char *) * (num_aliases + 1);             \
110         req_length += sizeof (char *) * (num_addrs + 1);               \
111         req_length += strlen (h->h_name) + 1;                          \
112                                                                        \
113         if (buflen < req_length) {                                     \
114                 *herr = ERANGE;                                        \
115                 G_UNLOCK (gethost_mutex);                              \
116                 return ERANGE;                                         \
117         }                                                              \
118                                                                        \
119         /* we store the alias/addr pointers in the buffer */           \
120         /* their addresses here. */                                    \
121         p = buf;                                                       \
122         if (num_aliases) {                                             \
123                 host->h_aliases = (char **) p;                         \
124                 p += sizeof (char *) * (num_aliases + 1);              \
125         } else                                                         \
126                 host->h_aliases = NULL;                                \
127                                                                        \
128         if (num_addrs) {                                               \
129                 host->h_addr_list = (char **) p;                       \
130                 p += sizeof (char *) * (num_addrs + 1);                \
131         } else                                                         \
132                 host->h_addr_list = NULL;                              \
133                                                                        \
134         /* copy the host name into the buffer */                       \
135         host->h_name = p;                                              \
136         strcpy (p, h->h_name);                                         \
137         p += strlen (h->h_name) + 1;                                   \
138         host->h_addrtype = h->h_addrtype;                              \
139         host->h_length = h->h_length;                                  \
140                                                                        \
141         /* copy the aliases/addresses into the buffer */               \
142         /* and assign pointers into the hostent */                     \
143         *p = 0;                                                        \
144         if (num_aliases) {                                             \
145                 for (i = 0; i < num_aliases; i++) {                    \
146                         strcpy (p, h->h_aliases[i]);                   \
147                         host->h_aliases[i] = p;                        \
148                         p += strlen (h->h_aliases[i]);                 \
149                 }                                                      \
150                 host->h_aliases[num_aliases] = NULL;                   \
151         }                                                              \
152                                                                        \
153         if (num_addrs) {                                               \
154                 for (i = 0; i < num_addrs; i++) {                      \
155                         memcpy (p, h->h_addr_list[i], h->h_length);    \
156                         host->h_addr_list[i] = p;                      \
157                         p += h->h_length;                              \
158                 }                                                      \
159                 host->h_addr_list[num_addrs] = NULL;                   \
160         }                                                              \
161 } G_STMT_END
162
163
164 #ifdef ENABLE_IPv6
165 /* some helpful utils for IPv6 lookups */
166 #define IPv6_BUFLEN_MIN  (sizeof (char *) * 3)
167
168 static int
169 ai_to_herr (int error)
170 {
171         switch (error) {
172         case EAI_NONAME:
173         case EAI_FAIL:
174                 return HOST_NOT_FOUND;
175                 break;
176         case EAI_SERVICE:
177                 return NO_DATA;
178                 break;
179         case EAI_ADDRFAMILY:
180                 return NO_ADDRESS;
181                 break;
182         case EAI_NODATA:
183                 return NO_DATA;
184                 break;
185         case EAI_MEMORY:
186                 return ENOMEM;
187                 break;
188         case EAI_AGAIN:
189                 return TRY_AGAIN;
190                 break;
191         case EAI_SYSTEM:
192                 return errno;
193                 break;
194         default:
195                 return NO_RECOVERY;
196                 break;
197         }
198 }
199
200 #endif /* ENABLE_IPv6 */
201
202 static int
203 camel_gethostbyname_r (const char *name, struct hostent *host,
204                        char *buf, size_t buflen, int *herr)
205 {
206 #ifdef ENABLE_IPv6
207         struct addrinfo hints, *res;
208         int retval, len;
209         char *addr;
210         
211         memset (&hints, 0, sizeof (struct addrinfo));
212 #ifdef HAVE_AI_ADDRCONFIG
213         hints.ai_flags = AI_CANONNAME | AI_ADDRCONFIG;
214 #else
215         hints.ai_flags = AI_CANONNAME;
216 #endif
217         hints.ai_family = PF_UNSPEC;
218         hints.ai_socktype = SOCK_STREAM;
219         hints.ai_protocol = IPPROTO_TCP;
220         
221         if ((retval = getaddrinfo (name, NULL, &hints, &res)) != 0) {
222                 *herr = ai_to_herr (retval);
223                 return -1;
224         }
225         
226         len = ALIGN (strlen (res->ai_canonname) + 1);
227         if (buflen < IPv6_BUFLEN_MIN + len + res->ai_addrlen + sizeof (char *))
228                 return ERANGE;
229         
230         /* h_name */
231         strcpy (buf, res->ai_canonname);
232         host->h_name = buf;
233         buf += len;
234         
235         /* h_aliases */
236         ((char **) buf)[0] = NULL;
237         host->h_aliases = (char **) buf;
238         buf += sizeof (char *);
239         
240         /* h_addrtype and h_length */
241         host->h_length = res->ai_addrlen;
242         if (res->ai_family == PF_INET6) {
243                 host->h_addrtype = AF_INET6;
244                 
245                 addr = (char *) &((struct sockaddr_in6 *) res->ai_addr)->sin6_addr;
246         } else {
247                 host->h_addrtype = AF_INET;
248                 
249                 addr = (char *) &((struct sockaddr_in *) res->ai_addr)->sin_addr;
250         }
251         
252         memcpy (buf, addr, host->h_length);
253         addr = buf;
254         buf += ALIGN (host->h_length);
255         
256         /* h_addr_list */
257         ((char **) buf)[0] = addr;
258         ((char **) buf)[1] = NULL;
259         host->h_addr_list = (char **) buf;
260         
261         freeaddrinfo (res);
262         
263         return 0;
264 #else /* No support for IPv6 addresses */
265 #ifdef HAVE_GETHOSTBYNAME_R
266 #ifdef GETHOSTBYNAME_R_FIVE_ARGS
267         if (gethostbyname_r (name, host, buf, buflen, herr))
268                 return 0;
269         else
270                 return errno;
271 #else
272         struct hostent *hp;
273         int retval;
274         
275         retval = gethostbyname_r (name, host, buf, buflen, &hp, herr);
276         if (hp != NULL) {
277                 *herr = 0;
278         } else if (retval == 0) {
279                 /* glibc 2.3.2 workaround - it seems that
280                  * gethostbyname_r will sometimes return 0 on fail and
281                  * not set the hostent values (hence the crash in bug
282                  * #56337).  Hopefully we can depend on @hp being NULL
283                  * in this error case like we do with
284                  * gethostbyaddr_r().
285                  */
286                 retval = -1;
287         }
288         
289         return retval;
290 #endif
291 #else /* No support for gethostbyname_r */
292         struct hostent *h;
293         
294         G_LOCK (gethost_mutex);
295         
296         h = gethostbyname (name);
297         
298         if (!h) {
299                 *herr = h_errno;
300                 G_UNLOCK (gethost_mutex);
301                 return -1;
302         }
303         
304         GETHOST_PROCESS (h, host, buf, buflen, herr);
305         
306         G_UNLOCK (gethost_mutex);
307         
308         return 0;
309 #endif /* HAVE_GETHOSTBYNAME_R */
310 #endif /* ENABLE_IPv6 */
311 }
312
313 static int
314 camel_gethostbyaddr_r (const char *addr, int addrlen, int type, struct hostent *host,
315                        char *buf, size_t buflen, int *herr)
316 {
317 #ifdef ENABLE_IPv6
318         int retval, len;
319         
320         if ((retval = getnameinfo (addr, addrlen, buf, buflen, NULL, 0, NI_NAMEREQD)) != 0) {
321                 *herr = ai_to_herr (retval);
322                 return -1;
323         }
324         
325         len = ALIGN (strlen (buf) + 1);
326         if (buflen < IPv6_BUFLEN_MIN + len + addrlen + sizeof (char *))
327                 return ERANGE;
328         
329         /* h_name */
330         host->h_name = buf;
331         buf += len;
332         
333         /* h_aliases */
334         ((char **) buf)[0] = NULL;
335         host->h_aliases = (char **) buf;
336         buf += sizeof (char *);
337         
338         /* h_addrtype and h_length */
339         host->h_length = addrlen;
340         host->h_addrtype = type;
341         
342         memcpy (buf, addr, host->h_length);
343         addr = buf;
344         buf += ALIGN (host->h_length);
345         
346         /* h_addr_list */
347         ((char **) buf)[0] = addr;
348         ((char **) buf)[1] = NULL;
349         host->h_addr_list = (char **) buf;
350         
351         return 0;
352 #else /* No support for IPv6 addresses */
353 #ifdef HAVE_GETHOSTBYADDR_R
354 #ifdef GETHOSTBYADDR_R_SEVEN_ARGS
355         if (gethostbyaddr_r (addr, addrlen, type, host, buf, buflen, herr))
356                 return 0;
357         else
358                 return errno;
359 #else
360         struct hostent *hp;
361         int retval;
362         
363         retval = gethostbyaddr_r (addr, addrlen, type, host, buf, buflen, &hp, herr);
364         if (hp != NULL) {
365                 *herr = 0;
366                 retval = 0;
367         } else if (retval == 0) {
368                 /* glibc 2.3.2 workaround - it seems that
369                  * gethostbyaddr_r will sometimes return 0 on fail and
370                  * fill @host with garbage strings from /etc/hosts
371                  * (failure to parse the file? who knows). Luckily, it
372                  * seems that we can rely on @hp being NULL on
373                  * fail.
374                  */
375                 retval = -1;
376         }
377         
378         return retval;
379 #endif
380 #else /* No support for gethostbyaddr_r */
381         struct hostent *h;
382         
383         G_LOCK (gethost_mutex);
384         
385         h = gethostbyaddr (addr, addrlen, type);
386         
387         if (!h) {
388                 *herr = h_errno;
389                 G_UNLOCK (gethost_mutex);
390                 return -1;
391         }
392         
393         GETHOST_PROCESS (h, host, buf, buflen, herr);
394         
395         G_UNLOCK (gethost_mutex);
396         
397         return 0;
398 #endif /* HAVE_GETHOSTBYADDR_R */
399 #endif /* ENABLE_IPv6 */
400 }
401 #endif /* NEED_ADDRINFO */
402
403 /* ********************************************************************** */
404 struct _addrinfo_msg {
405         CamelMsg msg;
406         unsigned int cancelled:1;
407
408         /* for host lookup */
409         const char *name;
410         const char *service;
411         int result;
412         const struct addrinfo *hints;
413         struct addrinfo **res;
414
415         /* for host lookup emulation */
416 #ifdef NEED_ADDRINFO
417         struct hostent hostbuf;
418         int hostbuflen;
419         char *hostbufmem;
420 #endif
421
422         /* for name lookup */
423         const struct sockaddr *addr;
424         socklen_t addrlen;
425         char *host;
426         int hostlen;
427         char *serv;
428         int servlen;
429         int flags;
430 };
431
432 static void
433 cs_freeinfo(struct _addrinfo_msg *msg)
434 {
435         g_free(msg->host);
436         g_free(msg->serv);
437 #ifdef NEED_ADDRINFO
438         g_free(msg->hostbufmem);
439 #endif
440         g_free(msg);
441 }
442
443 /* returns -1 if we didn't wait for reply from thread */
444 static int
445 cs_waitinfo(void *(worker)(void *), struct _addrinfo_msg *msg, const char *error, CamelException *ex)
446 {
447         CamelMsgPort *reply_port;
448         pthread_t id;
449         int err, cancel_fd, cancel = 0, fd;
450
451         cancel_fd = camel_operation_cancel_fd(NULL);
452         if (cancel_fd == -1) {
453                 worker(msg);
454                 return 0;
455         }
456         
457         reply_port = msg->msg.reply_port = camel_msgport_new();
458         fd = camel_msgport_fd(msg->msg.reply_port);
459         if ((err = pthread_create(&id, NULL, worker, msg)) == 0) {
460                 int status;
461 #ifndef G_OS_WIN32
462                 GPollFD polls[2];
463
464                 polls[0].fd = fd;
465                 polls[0].events = G_IO_IN;
466                 polls[1].fd = cancel_fd;
467                 polls[1].events = G_IO_IN;
468
469                 d(printf("waiting for name return/cancellation in main process\n"));
470                 do {
471                         polls[0].revents = 0;
472                         polls[1].revents = 0;
473                         status = g_poll(polls, 2, -1);
474                 } while (status == -1 && errno == EINTR);
475 #else
476                 fd_set read_set;
477
478                 FD_ZERO(&read_set);
479                 FD_SET(fd, &read_set);
480                 FD_SET(cancel_fd, &read_set);
481
482                 status = select(MAX(fd, cancel_fd) + 1, &read_set, NULL, NULL, NULL);
483 #endif
484
485                 if (status == -1 ||
486 #ifndef G_OS_WIN32
487                     (polls[1].revents & G_IO_IN)
488 #else
489                     FD_ISSET (cancel_fd, &read_set)
490 #endif
491                                                    ) {
492                         if (status == -1)
493                                 camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, "%s: %s", error,
494 #ifndef G_OS_WIN32
495                                                      g_strerror(errno)
496 #else
497                                                      g_win32_error_message (WSAGetLastError ())
498 #endif
499                                                      );
500                         else
501                                 camel_exception_setv(ex, CAMEL_EXCEPTION_USER_CANCEL, _("Canceled"));
502                         
503                         /* We cancel so if the thread impl is decent it causes immediate exit.
504                            We detach so we dont need to wait for it to exit if it isn't.
505                            We check the reply port incase we had a reply in the mean time, which we free later */
506                         d(printf("Canceling lookup thread and leaving it\n"));
507                         msg->cancelled = 1;
508                         pthread_detach(id);
509                         pthread_cancel(id);
510                         cancel = 1;
511                 } else {
512                         struct _addrinfo_msg *reply = (struct _addrinfo_msg *)camel_msgport_try_pop(reply_port);
513
514                         g_assert(reply == msg);
515                         d(printf("waiting for child to exit\n"));
516                         pthread_join(id, NULL);
517                         d(printf("child done\n"));
518                 }
519         } else {
520                 camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, "%s: %s: %s", error, _("cannot create thread"), g_strerror(err));
521         }
522         camel_msgport_destroy(reply_port);
523
524         return cancel;
525 }
526
527 #ifdef NEED_ADDRINFO
528 static void *
529 cs_getaddrinfo(void *data)
530 {
531         struct _addrinfo_msg *msg = data;
532         int herr;
533         struct hostent h;
534         struct addrinfo *res, *last = NULL;
535         struct sockaddr_in *sin;
536         in_port_t port = 0;
537         int i;
538
539         /* This is a pretty simplistic emulation of getaddrinfo */
540
541         while ((msg->result = camel_gethostbyname_r(msg->name, &h, msg->hostbufmem, msg->hostbuflen, &herr)) == ERANGE) {
542                 pthread_testcancel();
543                 msg->hostbuflen *= 2;
544                 msg->hostbufmem = g_realloc(msg->hostbufmem, msg->hostbuflen);
545         }
546         
547         /* If we got cancelled, dont reply, just free it */
548         if (msg->cancelled)
549                 goto cancel;
550
551         /* FIXME: map error numbers across */
552         if (msg->result != 0)
553                 goto reply;
554
555         /* check hints matched */
556         if (msg->hints && msg->hints->ai_family && msg->hints->ai_family != h.h_addrtype) {
557                 msg->result = EAI_FAMILY;
558                 goto reply;
559         }
560
561         /* we only support ipv4 for this interface, even if it could supply ipv6 */
562         if (h.h_addrtype != AF_INET) {
563                 msg->result = EAI_FAMILY;
564                 goto reply;
565         }
566
567         /* check service mapping */
568         if (msg->service) {
569                 const char *p = msg->service;
570
571                 while (*p) {
572                         if (*p < '0' || *p > '9')
573                                 break;
574                         p++;
575                 }
576
577                 if (*p) {
578                         const char *socktype = NULL;
579                         struct servent *serv;
580
581                         if (msg->hints && msg->hints->ai_socktype) {
582                                 if (msg->hints->ai_socktype == SOCK_STREAM)
583                                         socktype = "tcp";
584                                 else if (msg->hints->ai_socktype == SOCK_DGRAM)
585                                         socktype = "udp";
586                         }
587
588                         serv = getservbyname(msg->service, socktype);
589                         if (serv == NULL) {
590                                 msg->result = EAI_NONAME;
591                                 goto reply;
592                         }
593                         port = serv->s_port;
594                 } else {
595                         port = htons(strtoul(msg->service, NULL, 10));
596                 }
597         }
598
599         for (i=0;h.h_addr_list[i];i++) {
600                 res = g_malloc0(sizeof(*res));
601                 if (msg->hints) {
602                         res->ai_flags = msg->hints->ai_flags;
603                         if (msg->hints->ai_flags & AI_CANONNAME)
604                                 res->ai_canonname = g_strdup(h.h_name);
605                         res->ai_socktype = msg->hints->ai_socktype;
606                         res->ai_protocol = msg->hints->ai_protocol;
607                 } else {
608                         res->ai_flags = 0;
609                         res->ai_socktype = SOCK_STREAM; /* fudge */
610                         res->ai_protocol = 0;   /* fudge */
611                 }
612                 res->ai_family = AF_INET;
613                 res->ai_addrlen = sizeof(*sin);
614                 res->ai_addr = g_malloc(sizeof(*sin));
615                 sin = (struct sockaddr_in *)res->ai_addr;
616                 sin->sin_family = AF_INET;
617                 sin->sin_port = port;
618                 memcpy(&sin->sin_addr, h.h_addr_list[i], sizeof(sin->sin_addr));
619
620                 if (last == NULL) {
621                         *msg->res = last = res;
622                 } else {
623                         last->ai_next = res;
624                         last = res;
625                 }
626         }
627 reply:
628         camel_msgport_reply((CamelMsg *)msg);
629         return NULL;
630 cancel:
631         cs_freeinfo(msg);
632         return NULL;
633 }
634 #else
635 static void *
636 cs_getaddrinfo(void *data)
637 {
638         struct _addrinfo_msg *info = data;
639
640         info->result = getaddrinfo(info->name, info->service, info->hints, info->res);
641
642         /* On Solaris, the service name 'http' or 'https' is not defined. 
643            Use the port as the service name directly. */
644         if (info->result && info->service) {
645                 if (strcmp (info->service, "http") == 0)
646                         info->result = getaddrinfo(info->name, "80", info->hints, info->res);
647                 else if (strcmp (info->service, "https") == 0)
648                         info->result = getaddrinfo(info->name, "443", info->hints, info->res);
649         }
650         
651         if (info->cancelled) {
652                 cs_freeinfo(info);
653         } else {
654                 camel_msgport_reply((CamelMsg *)info);
655         }
656         
657         return NULL;
658 }
659 #endif /* NEED_ADDRINFO */
660
661 struct addrinfo *
662 camel_getaddrinfo(const char *name, const char *service, const struct addrinfo *hints, CamelException *ex)
663 {
664         struct _addrinfo_msg *msg;
665         struct addrinfo *res = NULL;
666 #ifndef ENABLE_IPv6
667         struct addrinfo myhints;
668 #endif
669         g_return_val_if_fail(name != NULL, NULL);
670         
671         if (camel_operation_cancel_check(NULL)) {
672                 camel_exception_set(ex, CAMEL_EXCEPTION_USER_CANCEL, _("Canceled"));
673                 return NULL;
674         }
675
676         camel_operation_start_transient(NULL, _("Resolving: %s"), name);
677
678         /* force ipv4 addresses only */
679 #ifndef ENABLE_IPv6
680         if (hints == NULL)
681                 memset(&myhints, 0, sizeof(myhints));
682         else
683                 memcpy (&myhints, hints, sizeof (myhints));
684         
685         myhints.ai_family = AF_INET;
686         hints = &myhints;
687 #endif
688
689         msg = g_malloc0(sizeof(*msg));
690         msg->name = name;
691         msg->service = service;
692         msg->hints = hints;
693         msg->res = &res;
694 #ifdef NEED_ADDRINFO
695         msg->hostbuflen = 1024;
696         msg->hostbufmem = g_malloc(msg->hostbuflen);
697 #endif
698         if (cs_waitinfo(cs_getaddrinfo, msg, _("Host lookup failed"), ex) == 0) {
699                 if (msg->result != 0) {
700                         camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, _("Host lookup failed: %s: %s"),
701                                               name, gai_strerror (msg->result));
702                 }
703
704                 cs_freeinfo(msg);
705         } else
706                 res = NULL;
707
708         camel_operation_end(NULL);
709
710         return res;
711 }
712
713 void
714 camel_freeaddrinfo(struct addrinfo *host)
715 {
716 #ifdef NEED_ADDRINFO
717         while (host) {
718                 struct addrinfo *next = host->ai_next;
719
720                 g_free(host->ai_canonname);
721                 g_free(host->ai_addr);
722                 g_free(host);
723                 host = next;
724         }
725 #else
726         freeaddrinfo(host);
727 #endif
728 }
729
730 #ifdef NEED_ADDRINFO
731 static void *
732 cs_getnameinfo(void *data)
733 {
734         struct _addrinfo_msg *msg = data;
735         int herr;
736         struct hostent h;
737         struct sockaddr_in *sin = (struct sockaddr_in *)msg->addr;
738
739         /* FIXME: error code */
740         if (msg->addr->sa_family != AF_INET) {
741                 msg->result = -1;
742                 return NULL;
743         }
744
745         /* FIXME: honour getnameinfo flags: do we care, not really */
746
747         while ((msg->result = camel_gethostbyaddr_r((const char *)&sin->sin_addr, sizeof(sin->sin_addr), AF_INET, &h,
748                                                     msg->hostbufmem, msg->hostbuflen, &herr)) == ERANGE) {
749                 pthread_testcancel ();
750                 msg->hostbuflen *= 2;
751                 msg->hostbufmem = g_realloc(msg->hostbufmem, msg->hostbuflen);
752         }
753         
754         if (msg->cancelled)
755                 goto cancel;
756
757         if (msg->host) {
758                 g_free(msg->host);
759                 if (msg->result == 0 && h.h_name && h.h_name[0]) {
760                         msg->host = g_strdup(h.h_name);
761                 } else {
762                         unsigned char *in = (unsigned char *)&sin->sin_addr;
763                         
764                         /* sin_addr is always network order which is big-endian */
765                         msg->host = g_strdup_printf("%u.%u.%u.%u", in[0], in[1], in[2], in[3]);
766                 }
767         }
768
769         /* we never actually use this anyway */
770         if (msg->serv)
771                 sprintf(msg->serv, "%d", sin->sin_port);
772
773         camel_msgport_reply((CamelMsg *)msg);
774         return NULL;
775 cancel:
776         cs_freeinfo(msg);
777         return NULL;
778 }
779 #else
780 static void *
781 cs_getnameinfo(void *data)
782 {
783         struct _addrinfo_msg *msg = data;
784
785         /* there doens't appear to be a return code which says host or serv buffers are too short, lengthen them */
786         msg->result = getnameinfo(msg->addr, msg->addrlen, msg->host, msg->hostlen, msg->serv, msg->servlen, msg->flags);
787         
788         if (msg->cancelled)
789                 cs_freeinfo(msg);
790         else
791                 camel_msgport_reply((CamelMsg *)msg);
792
793         return NULL;
794 }
795 #endif
796
797 int
798 camel_getnameinfo(const struct sockaddr *sa, socklen_t salen, char **host, char **serv, int flags, CamelException *ex)
799 {
800         struct _addrinfo_msg *msg;
801         int result;
802
803         if (camel_operation_cancel_check(NULL)) {
804                 camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL, _("Canceled"));
805                 return -1;
806         }
807
808         camel_operation_start_transient(NULL, _("Resolving address"));
809
810         msg = g_malloc0(sizeof(*msg));
811         msg->addr = sa;
812         msg->addrlen = salen;
813         if (host) {
814                 msg->hostlen = NI_MAXHOST;
815                 msg->host = g_malloc(msg->hostlen);
816                 msg->host[0] = 0;
817         }
818         if (serv) {
819                 msg->servlen = NI_MAXSERV;
820                 msg->serv = g_malloc(msg->servlen);
821                 msg->serv[0] = 0;
822         }
823         msg->flags = flags;
824 #ifdef NEED_ADDRINFO
825         msg->hostbuflen = 1024;
826         msg->hostbufmem = g_malloc(msg->hostbuflen);
827 #endif
828         cs_waitinfo(cs_getnameinfo, msg, _("Name lookup failed"), ex);
829
830         if ((result = msg->result) != 0)
831                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, _("Name lookup failed: %s"),
832                                       gai_strerror (result));
833         else {
834                 if (host)
835                         *host = g_strdup(msg->host);
836                 if (serv)
837                         *serv = g_strdup(msg->serv);
838         }
839
840         g_free(msg->host);
841         g_free(msg->serv);
842         g_free(msg);
843
844         camel_operation_end(NULL);
845
846         return result;
847 }