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
33 #include <glib/gi18n-lib.h>
35 #include "camel-exception.h"
36 #include "camel-msgport.h"
37 #include "camel-net-utils.h"
38 #include "camel-operation.h"
42 /* These are GNU extensions */
44 #define NI_MAXHOST 1025
52 typedef short in_port_t;
55 #define gai_strerror my_gai_strerror
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().
64 gai_strerror (int error_code)
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);
77 /* gethostbyname emulation code for emulating getaddrinfo code ...
79 This should probably go away */
83 #if !defined (HAVE_GETHOSTBYNAME_R) || !defined (HAVE_GETHOSTBYADDR_R)
84 G_LOCK_DEFINE_STATIC (gethost_mutex);
87 #define ALIGN(x) (((x) + (sizeof (char *) - 1)) & ~(sizeof (char *) - 1))
89 #define GETHOST_PROCESS(h, host, buf, buflen, herr) G_STMT_START { \
90 int num_aliases = 0, num_addrs = 0; \
95 /* check to make sure we have enough room in our buffer */ \
98 for (i = 0; h->h_aliases[i]; i++) \
99 req_length += strlen (h->h_aliases[i]) + 1; \
103 if (h->h_addr_list) { \
104 for (i = 0; h->h_addr_list[i]; i++) \
105 req_length += h->h_length; \
109 req_length += sizeof (char *) * (num_aliases + 1); \
110 req_length += sizeof (char *) * (num_addrs + 1); \
111 req_length += strlen (h->h_name) + 1; \
113 if (buflen < req_length) { \
115 G_UNLOCK (gethost_mutex); \
119 /* we store the alias/addr pointers in the buffer */ \
120 /* their addresses here. */ \
123 host->h_aliases = (char **) p; \
124 p += sizeof (char *) * (num_aliases + 1); \
126 host->h_aliases = NULL; \
129 host->h_addr_list = (char **) p; \
130 p += sizeof (char *) * (num_addrs + 1); \
132 host->h_addr_list = NULL; \
134 /* copy the host name into the buffer */ \
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; \
141 /* copy the aliases/addresses into the buffer */ \
142 /* and assign pointers into the hostent */ \
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]); \
150 host->h_aliases[num_aliases] = NULL; \
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; \
159 host->h_addr_list[num_addrs] = NULL; \
165 /* some helpful utils for IPv6 lookups */
166 #define IPv6_BUFLEN_MIN (sizeof (char *) * 3)
169 ai_to_herr (int error)
174 return HOST_NOT_FOUND;
200 #endif /* ENABLE_IPv6 */
203 camel_gethostbyname_r (const char *name, struct hostent *host,
204 char *buf, size_t buflen, int *herr)
207 struct addrinfo hints, *res;
211 memset (&hints, 0, sizeof (struct addrinfo));
212 #ifdef HAVE_AI_ADDRCONFIG
213 hints.ai_flags = AI_CANONNAME | AI_ADDRCONFIG;
215 hints.ai_flags = AI_CANONNAME;
217 hints.ai_family = PF_UNSPEC;
218 hints.ai_socktype = SOCK_STREAM;
219 hints.ai_protocol = IPPROTO_TCP;
221 if ((retval = getaddrinfo (name, NULL, &hints, &res)) != 0) {
222 *herr = ai_to_herr (retval);
226 len = ALIGN (strlen (res->ai_canonname) + 1);
227 if (buflen < IPv6_BUFLEN_MIN + len + res->ai_addrlen + sizeof (char *))
231 strcpy (buf, res->ai_canonname);
236 ((char **) buf)[0] = NULL;
237 host->h_aliases = (char **) buf;
238 buf += sizeof (char *);
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;
245 addr = (char *) &((struct sockaddr_in6 *) res->ai_addr)->sin6_addr;
247 host->h_addrtype = AF_INET;
249 addr = (char *) &((struct sockaddr_in *) res->ai_addr)->sin_addr;
252 memcpy (buf, addr, host->h_length);
254 buf += ALIGN (host->h_length);
257 ((char **) buf)[0] = addr;
258 ((char **) buf)[1] = NULL;
259 host->h_addr_list = (char **) buf;
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))
275 retval = gethostbyname_r (name, host, buf, buflen, &hp, herr);
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
291 #else /* No support for gethostbyname_r */
294 G_LOCK (gethost_mutex);
296 h = gethostbyname (name);
300 G_UNLOCK (gethost_mutex);
304 GETHOST_PROCESS (h, host, buf, buflen, herr);
306 G_UNLOCK (gethost_mutex);
309 #endif /* HAVE_GETHOSTBYNAME_R */
310 #endif /* ENABLE_IPv6 */
314 camel_gethostbyaddr_r (const char *addr, int addrlen, int type, struct hostent *host,
315 char *buf, size_t buflen, int *herr)
320 if ((retval = getnameinfo (addr, addrlen, buf, buflen, NULL, 0, NI_NAMEREQD)) != 0) {
321 *herr = ai_to_herr (retval);
325 len = ALIGN (strlen (buf) + 1);
326 if (buflen < IPv6_BUFLEN_MIN + len + addrlen + sizeof (char *))
334 ((char **) buf)[0] = NULL;
335 host->h_aliases = (char **) buf;
336 buf += sizeof (char *);
338 /* h_addrtype and h_length */
339 host->h_length = addrlen;
340 host->h_addrtype = type;
342 memcpy (buf, addr, host->h_length);
344 buf += ALIGN (host->h_length);
347 ((char **) buf)[0] = addr;
348 ((char **) buf)[1] = NULL;
349 host->h_addr_list = (char **) buf;
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))
363 retval = gethostbyaddr_r (addr, addrlen, type, host, buf, buflen, &hp, herr);
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
380 #else /* No support for gethostbyaddr_r */
383 G_LOCK (gethost_mutex);
385 h = gethostbyaddr (addr, addrlen, type);
389 G_UNLOCK (gethost_mutex);
393 GETHOST_PROCESS (h, host, buf, buflen, herr);
395 G_UNLOCK (gethost_mutex);
398 #endif /* HAVE_GETHOSTBYADDR_R */
399 #endif /* ENABLE_IPv6 */
401 #endif /* NEED_ADDRINFO */
403 /* ********************************************************************** */
404 struct _addrinfo_msg {
406 unsigned int cancelled:1;
408 /* for host lookup */
412 const struct addrinfo *hints;
413 struct addrinfo **res;
415 /* for host lookup emulation */
417 struct hostent hostbuf;
422 /* for name lookup */
423 const struct sockaddr *addr;
433 cs_freeinfo(struct _addrinfo_msg *msg)
438 g_free(msg->hostbufmem);
443 /* returns -1 if we didn't wait for reply from thread */
445 cs_waitinfo(void *(worker)(void *), struct _addrinfo_msg *msg, const char *error, CamelException *ex)
447 CamelMsgPort *reply_port;
449 int err, cancel_fd, cancel = 0, fd;
451 cancel_fd = camel_operation_cancel_fd(NULL);
452 if (cancel_fd == -1) {
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) {
465 polls[0].events = G_IO_IN;
466 polls[1].fd = cancel_fd;
467 polls[1].events = G_IO_IN;
469 d(printf("waiting for name return/cancellation in main process\n"));
471 polls[0].revents = 0;
472 polls[1].revents = 0;
473 status = g_poll(polls, 2, -1);
474 } while (status == -1 && errno == EINTR);
479 FD_SET(fd, &read_set);
480 FD_SET(cancel_fd, &read_set);
482 status = select(MAX(fd, cancel_fd) + 1, &read_set, NULL, NULL, NULL);
487 (polls[1].revents & G_IO_IN)
489 FD_ISSET (cancel_fd, &read_set)
493 camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, "%s: %s", error,
497 g_win32_error_message (WSAGetLastError ())
501 camel_exception_setv(ex, CAMEL_EXCEPTION_USER_CANCEL, _("Canceled"));
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"));
512 struct _addrinfo_msg *reply = (struct _addrinfo_msg *)camel_msgport_try_pop(reply_port);
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"));
520 camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, "%s: %s: %s", error, _("cannot create thread"), g_strerror(err));
522 camel_msgport_destroy(reply_port);
529 cs_getaddrinfo(void *data)
531 struct _addrinfo_msg *msg = data;
534 struct addrinfo *res, *last = NULL;
535 struct sockaddr_in *sin;
539 /* This is a pretty simplistic emulation of getaddrinfo */
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);
547 /* If we got cancelled, dont reply, just free it */
551 /* FIXME: map error numbers across */
552 if (msg->result != 0)
555 /* check hints matched */
556 if (msg->hints && msg->hints->ai_family && msg->hints->ai_family != h.h_addrtype) {
557 msg->result = EAI_FAMILY;
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;
567 /* check service mapping */
569 const char *p = msg->service;
572 if (*p < '0' || *p > '9')
578 const char *socktype = NULL;
579 struct servent *serv;
581 if (msg->hints && msg->hints->ai_socktype) {
582 if (msg->hints->ai_socktype == SOCK_STREAM)
584 else if (msg->hints->ai_socktype == SOCK_DGRAM)
588 serv = getservbyname(msg->service, socktype);
590 msg->result = EAI_NONAME;
595 port = htons(strtoul(msg->service, NULL, 10));
599 for (i=0;h.h_addr_list[i];i++) {
600 res = g_malloc0(sizeof(*res));
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;
609 res->ai_socktype = SOCK_STREAM; /* fudge */
610 res->ai_protocol = 0; /* fudge */
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));
621 *msg->res = last = res;
628 camel_msgport_reply((CamelMsg *)msg);
636 cs_getaddrinfo(void *data)
638 struct _addrinfo_msg *info = data;
640 info->result = getaddrinfo(info->name, info->service, info->hints, info->res);
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);
651 if (info->cancelled) {
654 camel_msgport_reply((CamelMsg *)info);
659 #endif /* NEED_ADDRINFO */
662 camel_getaddrinfo(const char *name, const char *service, const struct addrinfo *hints, CamelException *ex)
664 struct _addrinfo_msg *msg;
665 struct addrinfo *res = NULL;
667 struct addrinfo myhints;
669 g_return_val_if_fail(name != NULL, NULL);
671 if (camel_operation_cancel_check(NULL)) {
672 camel_exception_set(ex, CAMEL_EXCEPTION_USER_CANCEL, _("Canceled"));
676 camel_operation_start_transient(NULL, _("Resolving: %s"), name);
678 /* force ipv4 addresses only */
681 memset(&myhints, 0, sizeof(myhints));
683 memcpy (&myhints, hints, sizeof (myhints));
685 myhints.ai_family = AF_INET;
689 msg = g_malloc0(sizeof(*msg));
691 msg->service = service;
695 msg->hostbuflen = 1024;
696 msg->hostbufmem = g_malloc(msg->hostbuflen);
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));
708 camel_operation_end(NULL);
714 camel_freeaddrinfo(struct addrinfo *host)
718 struct addrinfo *next = host->ai_next;
720 g_free(host->ai_canonname);
721 g_free(host->ai_addr);
732 cs_getnameinfo(void *data)
734 struct _addrinfo_msg *msg = data;
737 struct sockaddr_in *sin = (struct sockaddr_in *)msg->addr;
739 /* FIXME: error code */
740 if (msg->addr->sa_family != AF_INET) {
745 /* FIXME: honour getnameinfo flags: do we care, not really */
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);
759 if (msg->result == 0 && h.h_name && h.h_name[0]) {
760 msg->host = g_strdup(h.h_name);
762 unsigned char *in = (unsigned char *)&sin->sin_addr;
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]);
769 /* we never actually use this anyway */
771 sprintf(msg->serv, "%d", sin->sin_port);
773 camel_msgport_reply((CamelMsg *)msg);
781 cs_getnameinfo(void *data)
783 struct _addrinfo_msg *msg = data;
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);
791 camel_msgport_reply((CamelMsg *)msg);
798 camel_getnameinfo(const struct sockaddr *sa, socklen_t salen, char **host, char **serv, int flags, CamelException *ex)
800 struct _addrinfo_msg *msg;
803 if (camel_operation_cancel_check(NULL)) {
804 camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL, _("Canceled"));
808 camel_operation_start_transient(NULL, _("Resolving address"));
810 msg = g_malloc0(sizeof(*msg));
812 msg->addrlen = salen;
814 msg->hostlen = NI_MAXHOST;
815 msg->host = g_malloc(msg->hostlen);
819 msg->servlen = NI_MAXSERV;
820 msg->serv = g_malloc(msg->servlen);
825 msg->hostbuflen = 1024;
826 msg->hostbufmem = g_malloc(msg->hostbuflen);
828 cs_waitinfo(cs_getnameinfo, msg, _("Name lookup failed"), ex);
830 if ((result = msg->result) != 0)
831 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, _("Name lookup failed: %s"),
832 gai_strerror (result));
835 *host = g_strdup(msg->host);
837 *serv = g_strdup(msg->serv);
844 camel_operation_end(NULL);