1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
3 * Authors: Michael Zucchi <notzed@ximian.com>
4 * Jeffrey Stedfast <fejj@ximian.com>
5 * Chris Toshok <toshok@ximian.com>
7 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
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.
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.
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
31 #include <glib/gi18n-lib.h>
33 #include "camel-msgport.h"
34 #include "camel-net-utils.h"
42 #include "camel-object.h"
43 #include "camel-operation.h"
47 /* These are GNU extensions */
49 #define NI_MAXHOST 1025
57 typedef gshort in_port_t;
60 #define gai_strerror my_gai_strerror
62 /* gai_strerror() is implemented as an inline function in Microsoft's
63 * SDK, but mingw lacks that. So implement here. The EAI_* errors can
64 * be handled with the normal FormatMessage() API,
65 * i.e. g_win32_error_message().
69 gai_strerror (gint error_code)
71 gchar *msg = g_win32_error_message (error_code);
72 GQuark quark = g_quark_from_string (msg);
73 const gchar *retval = g_quark_to_string (quark);
82 /* gethostbyname emulation code for emulating getaddrinfo code ...
84 * This should probably go away */
88 #if !defined (HAVE_GETHOSTBYNAME_R) || !defined (HAVE_GETHOSTBYADDR_R)
89 G_LOCK_DEFINE_STATIC (gethost_mutex);
92 #define ALIGN(x) (((x) + (sizeof (gchar *) - 1)) & ~(sizeof (gchar *) - 1))
94 #define GETHOST_PROCESS(h, host, buf, buflen, herr) G_STMT_START { \
95 gint num_aliases = 0, num_addrs = 0; \
100 /* check to make sure we have enough room in our buffer */ \
102 if (h->h_aliases) { \
103 for (i = 0; h->h_aliases[i]; i++) \
104 req_length += strlen (h->h_aliases[i]) + 1; \
108 if (h->h_addr_list) { \
109 for (i = 0; h->h_addr_list[i]; i++) \
110 req_length += h->h_length; \
114 req_length += sizeof (gchar *) * (num_aliases + 1); \
115 req_length += sizeof (gchar *) * (num_addrs + 1); \
116 req_length += strlen (h->h_name) + 1; \
118 if (buflen < req_length) { \
120 G_UNLOCK (gethost_mutex); \
124 /* we store the alias/addr pointers in the buffer */ \
125 /* their addresses here. */ \
128 host->h_aliases = (gchar **) p; \
129 p += sizeof (gchar *) * (num_aliases + 1); \
131 host->h_aliases = NULL; \
134 host->h_addr_list = (gchar **) p; \
135 p += sizeof (gchar *) * (num_addrs + 1); \
137 host->h_addr_list = NULL; \
139 /* copy the host name into the buffer */ \
141 strcpy (p, h->h_name); \
142 p += strlen (h->h_name) + 1; \
143 host->h_addrtype = h->h_addrtype; \
144 host->h_length = h->h_length; \
146 /* copy the aliases/addresses into the buffer */ \
147 /* and assign pointers into the hostent */ \
150 for (i = 0; i < num_aliases; i++) { \
151 strcpy (p, h->h_aliases[i]); \
152 host->h_aliases[i] = p; \
153 p += strlen (h->h_aliases[i]); \
155 host->h_aliases[num_aliases] = NULL; \
159 for (i = 0; i < num_addrs; i++) { \
160 memcpy (p, h->h_addr_list[i], h->h_length); \
161 host->h_addr_list[i] = p; \
164 host->h_addr_list[num_addrs] = NULL; \
169 /* some helpful utils for IPv6 lookups */
170 #define IPv6_BUFLEN_MIN (sizeof (gchar *) * 3)
173 ai_to_herr (gint error)
178 return HOST_NOT_FOUND;
204 #endif /* ENABLE_IPv6 */
207 camel_gethostbyname_r (const gchar *name,
208 struct hostent *host,
214 struct addrinfo hints, *res;
218 memset (&hints, 0, sizeof (struct addrinfo));
219 #ifdef HAVE_AI_ADDRCONFIG
220 hints.ai_flags = AI_CANONNAME | AI_ADDRCONFIG;
222 hints.ai_flags = AI_CANONNAME;
224 hints.ai_family = PF_UNSPEC;
225 hints.ai_socktype = SOCK_STREAM;
226 hints.ai_protocol = IPPROTO_TCP;
228 if ((retval = getaddrinfo (name, NULL, &hints, &res)) != 0) {
229 *herr = ai_to_herr (retval);
233 len = ALIGN (strlen (res->ai_canonname) + 1);
234 if (buflen < IPv6_BUFLEN_MIN + len + res->ai_addrlen + sizeof (gchar *))
238 strcpy (buf, res->ai_canonname);
243 ((gchar **) buf)[0] = NULL;
244 host->h_aliases = (gchar **) buf;
245 buf += sizeof (gchar *);
247 /* h_addrtype and h_length */
248 host->h_length = res->ai_addrlen;
249 if (res->ai_family == PF_INET6) {
250 host->h_addrtype = AF_INET6;
252 addr = (gchar *) &((struct sockaddr_in6 *) res->ai_addr)->sin6_addr;
254 host->h_addrtype = AF_INET;
256 addr = (gchar *) &((struct sockaddr_in *) res->ai_addr)->sin_addr;
259 memcpy (buf, addr, host->h_length);
261 buf += ALIGN (host->h_length);
264 ((gchar **) buf)[0] = addr;
265 ((gchar **) buf)[1] = NULL;
266 host->h_addr_list = (gchar **) buf;
271 #else /* No support for IPv6 addresses */
272 #ifdef HAVE_GETHOSTBYNAME_R
273 #ifdef GETHOSTBYNAME_R_FIVE_ARGS
274 if (gethostbyname_r (name, host, buf, buflen, herr))
282 retval = gethostbyname_r (name, host, buf, buflen, &hp, herr);
285 } else if (retval == 0) {
286 /* glibc 2.3.2 workaround - it seems that
287 * gethostbyname_r will sometimes return 0 on fail and
288 * not set the hostent values (hence the crash in bug
289 * #56337). Hopefully we can depend on @hp being NULL
290 * in this error case like we do with
298 #else /* No support for gethostbyname_r */
301 G_LOCK (gethost_mutex);
303 h = gethostbyname (name);
307 G_UNLOCK (gethost_mutex);
311 GETHOST_PROCESS (h, host, buf, buflen, herr);
313 G_UNLOCK (gethost_mutex);
316 #endif /* HAVE_GETHOSTBYNAME_R */
317 #endif /* ENABLE_IPv6 */
321 camel_gethostbyaddr_r (const gchar *addr,
324 struct hostent *host,
332 if ((retval = getnameinfo (addr, addrlen, buf, buflen, NULL, 0, NI_NAMEREQD)) != 0) {
333 *herr = ai_to_herr (retval);
337 len = ALIGN (strlen (buf) + 1);
338 if (buflen < IPv6_BUFLEN_MIN + len + addrlen + sizeof (gchar *))
346 ((gchar **) buf)[0] = NULL;
347 host->h_aliases = (gchar **) buf;
348 buf += sizeof (gchar *);
350 /* h_addrtype and h_length */
351 host->h_length = addrlen;
352 host->h_addrtype = type;
354 memcpy (buf, addr, host->h_length);
356 buf += ALIGN (host->h_length);
359 ((gchar **) buf)[0] = addr;
360 ((gchar **) buf)[1] = NULL;
361 host->h_addr_list = (gchar **) buf;
364 #else /* No support for IPv6 addresses */
365 #ifdef HAVE_GETHOSTBYADDR_R
366 #ifdef GETHOSTBYADDR_R_SEVEN_ARGS
367 if (gethostbyaddr_r (addr, addrlen, type, host, buf, buflen, herr))
375 retval = gethostbyaddr_r (addr, addrlen, type, host, buf, buflen, &hp, herr);
379 } else if (retval == 0) {
380 /* glibc 2.3.2 workaround - it seems that
381 * gethostbyaddr_r will sometimes return 0 on fail and
382 * fill @host with garbage strings from /etc/hosts
383 * (failure to parse the file? who knows). Luckily, it
384 * seems that we can rely on @hp being NULL on
392 #else /* No support for gethostbyaddr_r */
395 G_LOCK (gethost_mutex);
397 h = gethostbyaddr (addr, addrlen, type);
401 G_UNLOCK (gethost_mutex);
405 GETHOST_PROCESS (h, host, buf, buflen, herr);
407 G_UNLOCK (gethost_mutex);
410 #endif /* HAVE_GETHOSTBYADDR_R */
411 #endif /* ENABLE_IPv6 */
413 #endif /* NEED_ADDRINFO */
415 /* ********************************************************************** */
416 struct _addrinfo_msg {
420 /* for host lookup */
422 const gchar *service;
424 const struct addrinfo *hints;
425 struct addrinfo **res;
427 /* for host lookup emulation */
429 struct hostent hostbuf;
434 /* for name lookup */
435 const struct sockaddr *addr;
445 cs_freeinfo (struct _addrinfo_msg *msg)
450 g_free (msg->hostbufmem);
455 /* returns -1 if we didn't wait for reply from thread */
457 cs_waitinfo (gpointer (worker)(gpointer),
458 struct _addrinfo_msg *msg,
460 GCancellable *cancellable,
463 CamelMsgPort *reply_port;
465 gint cancel_fd, cancel = 0, fd;
467 cancel_fd = g_cancellable_get_fd (cancellable);
468 if (cancel_fd == -1) {
473 reply_port = msg->msg.reply_port = camel_msgport_new ();
474 fd = camel_msgport_fd (msg->msg.reply_port);
475 if ((thread = g_thread_new (NULL, worker, msg)) != NULL) {
481 polls[0].events = G_IO_IN;
482 polls[1].fd = cancel_fd;
483 polls[1].events = G_IO_IN;
485 d (printf ("waiting for name return/cancellation in main process\n"));
487 polls[0].revents = 0;
488 polls[1].revents = 0;
489 status = g_poll (polls, 2, -1);
490 } while (status == -1 && errno == EINTR);
495 FD_SET (fd, &read_set);
496 FD_SET (cancel_fd, &read_set);
498 status = select (MAX (fd, cancel_fd) + 1, &read_set, NULL, NULL, NULL);
503 (polls[1].revents & G_IO_IN)
505 FD_ISSET (cancel_fd, &read_set)
511 g_io_error_from_errno (errno),
516 g_win32_error_message (WSAGetLastError ())
522 G_IO_ERROR_CANCELLED,
525 /* We cancel so if the thread impl is decent it causes immediate exit.
526 * We check the reply port incase we had a reply in the mean time, which we free later */
527 d (printf ("Canceling lookup thread and leaving it\n"));
529 g_thread_join (thread);
532 struct _addrinfo_msg *reply;
534 d (printf ("waiting for child to exit\n"));
535 g_thread_join (thread);
536 d (printf ("child done\n"));
538 reply = (struct _addrinfo_msg *) camel_msgport_try_pop (reply_port);
540 g_warning ("%s: Received msg reply %p doesn't match msg %p", G_STRFUNC, reply, msg);
543 camel_msgport_destroy (reply_port);
545 g_cancellable_release_fd (cancellable);
552 cs_getaddrinfo (gpointer data)
554 struct _addrinfo_msg *msg = data;
557 struct addrinfo *res, *last = NULL;
558 struct sockaddr_in *sin;
562 /* This is a pretty simplistic emulation of getaddrinfo */
564 while ((msg->result = camel_gethostbyname_r (msg->name, &h, msg->hostbufmem, msg->hostbuflen, &herr)) == ERANGE) {
567 msg->hostbuflen *= 2;
568 msg->hostbufmem = g_realloc (msg->hostbufmem, msg->hostbuflen);
571 /* If we got cancelled, dont reply, just free it */
575 /* FIXME: map error numbers across */
576 if (msg->result != 0)
579 /* check hints matched */
580 if (msg->hints && msg->hints->ai_family && msg->hints->ai_family != h.h_addrtype) {
581 msg->result = EAI_FAMILY;
585 /* we only support ipv4 for this interface, even if it could supply ipv6 */
586 if (h.h_addrtype != AF_INET) {
587 msg->result = EAI_FAMILY;
591 /* check service mapping */
593 const gchar *p = msg->service;
596 if (*p < '0' || *p > '9')
602 const gchar *socktype = NULL;
603 struct servent *serv;
605 if (msg->hints && msg->hints->ai_socktype) {
606 if (msg->hints->ai_socktype == SOCK_STREAM)
608 else if (msg->hints->ai_socktype == SOCK_DGRAM)
612 serv = getservbyname (msg->service, socktype);
614 msg->result = EAI_NONAME;
619 port = htons (strtoul (msg->service, NULL, 10));
623 for (i = 0; h.h_addr_list[i] && !msg->cancelled; i++) {
624 res = g_malloc0 (sizeof (*res));
626 res->ai_flags = msg->hints->ai_flags;
627 if (msg->hints->ai_flags & AI_CANONNAME)
628 res->ai_canonname = g_strdup (h.h_name);
629 res->ai_socktype = msg->hints->ai_socktype;
630 res->ai_protocol = msg->hints->ai_protocol;
633 res->ai_socktype = SOCK_STREAM; /* fudge */
634 res->ai_protocol = 0; /* fudge */
636 res->ai_family = AF_INET;
637 res->ai_addrlen = sizeof (*sin);
638 res->ai_addr = g_malloc (sizeof (*sin));
639 sin = (struct sockaddr_in *) res->ai_addr;
640 sin->sin_family = AF_INET;
641 sin->sin_port = port;
642 memcpy (&sin->sin_addr, h.h_addr_list[i], sizeof (sin->sin_addr));
645 *msg->res = last = res;
652 camel_msgport_reply ((CamelMsg *) msg);
658 cs_getaddrinfo (gpointer data)
660 struct _addrinfo_msg *info = data;
662 info->result = getaddrinfo (info->name, info->service, info->hints, info->res);
664 /* On Solaris, the service name 'http' or 'https' is not defined.
665 * Use the port as the service name directly. */
666 if (info->result && info->service) {
667 if (strcmp (info->service, "http") == 0)
668 info->result = getaddrinfo (info->name, "80", info->hints, info->res);
669 else if (strcmp (info->service, "https") == 0)
670 info->result = getaddrinfo (info->name, "443", info->hints, info->res);
673 if (!info->cancelled)
674 camel_msgport_reply ((CamelMsg *) info);
678 #endif /* NEED_ADDRINFO */
686 camel_getaddrinfo (const gchar *name,
687 const gchar *service,
688 const struct addrinfo *hints,
689 GCancellable *cancellable,
692 struct _addrinfo_msg *msg;
693 struct addrinfo *res = NULL;
695 struct addrinfo myhints;
697 g_return_val_if_fail (name != NULL, NULL);
699 if (g_cancellable_set_error_if_cancelled (cancellable, error))
702 camel_operation_push_message (
703 cancellable, _("Resolving: %s"), name);
705 /* force ipv4 addresses only */
708 memset (&myhints, 0, sizeof (myhints));
710 memcpy (&myhints, hints, sizeof (myhints));
712 myhints.ai_family = AF_INET;
716 msg = g_malloc0 (sizeof (*msg));
718 msg->service = service;
722 msg->hostbuflen = 1024;
723 msg->hostbufmem = g_malloc (msg->hostbuflen);
726 cs_getaddrinfo, msg, _("Host lookup failed"),
727 cancellable, error) == 0) {
729 if (msg->result != 0) {
731 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
732 _("Host lookup failed: %s: %s"),
733 name, gai_strerror (msg->result));
740 camel_operation_pop_message (cancellable);
746 * camel_freeaddrinfo:
751 camel_freeaddrinfo (struct addrinfo *host)
755 struct addrinfo *next = host->ai_next;
757 g_free (host->ai_canonname);
758 g_free (host->ai_addr);
769 cs_getnameinfo (gpointer data)
771 struct _addrinfo_msg *msg = data;
774 struct sockaddr_in *sin = (struct sockaddr_in *) msg->addr;
776 /* FIXME: error code */
777 if (msg->addr->sa_family != AF_INET) {
782 /* FIXME: honour getnameinfo flags: do we care, not really */
784 while ((msg->result = camel_gethostbyaddr_r ((const gchar *) &sin->sin_addr, sizeof (sin->sin_addr), AF_INET, &h,
785 msg->hostbufmem, msg->hostbuflen, &herr)) == ERANGE) {
788 msg->hostbuflen *= 2;
789 msg->hostbufmem = g_realloc (msg->hostbufmem, msg->hostbuflen);
797 if (msg->result == 0 && h.h_name && h.h_name[0]) {
798 msg->host = g_strdup (h.h_name);
800 guchar *in = (guchar *) &sin->sin_addr;
802 /* sin_addr is always network order which is big-endian */
803 msg->host = g_strdup_printf ("%u.%u.%u.%u", in[0], in[1], in[2], in[3]);
807 /* we never actually use this anyway */
809 sprintf (msg->serv, "%d", sin->sin_port);
812 camel_msgport_reply ((CamelMsg *) msg);
818 cs_getnameinfo (gpointer data)
820 struct _addrinfo_msg *msg = data;
822 /* there doens't appear to be a return code which says host or serv buffers are too short, lengthen them */
823 msg->result = getnameinfo (msg->addr, msg->addrlen, msg->host, msg->hostlen, msg->serv, msg->servlen, msg->flags);
826 camel_msgport_reply ((CamelMsg *) msg);
838 camel_getnameinfo (const struct sockaddr *sa,
843 GCancellable *cancellable,
846 struct _addrinfo_msg *msg;
849 if (g_cancellable_set_error_if_cancelled (cancellable, error))
852 camel_operation_push_message (
853 cancellable, _("Resolving address"));
855 msg = g_malloc0 (sizeof (*msg));
857 msg->addrlen = salen;
859 msg->hostlen = NI_MAXHOST;
860 msg->host = g_malloc (msg->hostlen);
864 msg->servlen = NI_MAXSERV;
865 msg->serv = g_malloc (msg->servlen);
870 msg->hostbuflen = 1024;
871 msg->hostbufmem = g_malloc (msg->hostbuflen);
874 cs_getnameinfo, msg, _("Name lookup failed"),
877 if ((result = msg->result) != 0)
879 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
880 _("Name lookup failed: %s"), gai_strerror (result));
883 *host = g_strdup(msg->host);
885 *serv = g_strdup(msg->serv);
890 camel_operation_pop_message (cancellable);