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) 2004 Ximian Inc.
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
37 #include <glib/gi18n-lib.h>
39 #include <libedataserver/e-msgport.h>
41 #include "camel-exception.h"
42 #include "camel-net-utils.h"
43 #include "camel-operation.h"
49 typedef short in_port_t;
52 #define gai_strerror my_gai_strerror
54 /* gai_strerror() is implemented as an inline function in Microsoft's
55 * SDK, but mingw lacks that. So implement here. The EAI_* errors can
56 * be handled with the normal FormatMessage() API,
57 * i.e. g_win32_error_message().
61 gai_strerror (int error_code)
63 gchar *msg = g_win32_error_message (error_code);
64 GQuark quark = g_quark_from_string (msg);
65 const gchar *retval = g_quark_to_string (quark);
74 /* gethostbyname emulation code for emulating getaddrinfo code ...
76 This should probably go away */
80 #if !defined (HAVE_GETHOSTBYNAME_R) || !defined (HAVE_GETHOSTBYADDR_R)
81 G_LOCK_DEFINE_STATIC (gethost_mutex);
84 #define ALIGN(x) (((x) + (sizeof (char *) - 1)) & ~(sizeof (char *) - 1))
86 #define GETHOST_PROCESS(h, host, buf, buflen, herr) G_STMT_START { \
87 int num_aliases = 0, num_addrs = 0; \
92 /* check to make sure we have enough room in our buffer */ \
95 for (i = 0; h->h_aliases[i]; i++) \
96 req_length += strlen (h->h_aliases[i]) + 1; \
100 if (h->h_addr_list) { \
101 for (i = 0; h->h_addr_list[i]; i++) \
102 req_length += h->h_length; \
106 req_length += sizeof (char *) * (num_aliases + 1); \
107 req_length += sizeof (char *) * (num_addrs + 1); \
108 req_length += strlen (h->h_name) + 1; \
110 if (buflen < req_length) { \
112 G_UNLOCK (gethost_mutex); \
116 /* we store the alias/addr pointers in the buffer */ \
117 /* their addresses here. */ \
120 host->h_aliases = (char **) p; \
121 p += sizeof (char *) * (num_aliases + 1); \
123 host->h_aliases = NULL; \
126 host->h_addr_list = (char **) p; \
127 p += sizeof (char *) * (num_addrs + 1); \
129 host->h_addr_list = NULL; \
131 /* copy the host name into the buffer */ \
133 strcpy (p, h->h_name); \
134 p += strlen (h->h_name) + 1; \
135 host->h_addrtype = h->h_addrtype; \
136 host->h_length = h->h_length; \
138 /* copy the aliases/addresses into the buffer */ \
139 /* and assign pointers into the hostent */ \
142 for (i = 0; i < num_aliases; i++) { \
143 strcpy (p, h->h_aliases[i]); \
144 host->h_aliases[i] = p; \
145 p += strlen (h->h_aliases[i]); \
147 host->h_aliases[num_aliases] = NULL; \
151 for (i = 0; i < num_addrs; i++) { \
152 memcpy (p, h->h_addr_list[i], h->h_length); \
153 host->h_addr_list[i] = p; \
156 host->h_addr_list[num_addrs] = NULL; \
162 /* some helpful utils for IPv6 lookups */
163 #define IPv6_BUFLEN_MIN (sizeof (char *) * 3)
166 ai_to_herr (int error)
171 return HOST_NOT_FOUND;
197 #endif /* ENABLE_IPv6 */
200 camel_gethostbyname_r (const char *name, struct hostent *host,
201 char *buf, size_t buflen, int *herr)
204 struct addrinfo hints, *res;
208 memset (&hints, 0, sizeof (struct addrinfo));
209 #ifdef HAVE_AI_ADDRCONFIG
210 hints.ai_flags = AI_CANONNAME | AI_ADDRCONFIG;
212 hints.ai_flags = AI_CANONNAME;
214 hints.ai_family = PF_UNSPEC;
215 hints.ai_socktype = SOCK_STREAM;
216 hints.ai_protocol = IPPROTO_TCP;
218 if ((retval = getaddrinfo (name, NULL, &hints, &res)) != 0) {
219 *herr = ai_to_herr (retval);
223 len = ALIGN (strlen (res->ai_canonname) + 1);
224 if (buflen < IPv6_BUFLEN_MIN + len + res->ai_addrlen + sizeof (char *))
228 strcpy (buf, res->ai_canonname);
233 ((char **) buf)[0] = NULL;
234 host->h_aliases = (char **) buf;
235 buf += sizeof (char *);
237 /* h_addrtype and h_length */
238 host->h_length = res->ai_addrlen;
239 if (res->ai_family == PF_INET6) {
240 host->h_addrtype = AF_INET6;
242 addr = (char *) &((struct sockaddr_in6 *) res->ai_addr)->sin6_addr;
244 host->h_addrtype = AF_INET;
246 addr = (char *) &((struct sockaddr_in *) res->ai_addr)->sin_addr;
249 memcpy (buf, addr, host->h_length);
251 buf += ALIGN (host->h_length);
254 ((char **) buf)[0] = addr;
255 ((char **) buf)[1] = NULL;
256 host->h_addr_list = (char **) buf;
261 #else /* No support for IPv6 addresses */
262 #ifdef HAVE_GETHOSTBYNAME_R
263 #ifdef GETHOSTBYNAME_R_FIVE_ARGS
264 if (gethostbyname_r (name, host, buf, buflen, herr))
272 retval = gethostbyname_r (name, host, buf, buflen, &hp, herr);
275 } else if (retval == 0) {
276 /* glibc 2.3.2 workaround - it seems that
277 * gethostbyname_r will sometimes return 0 on fail and
278 * not set the hostent values (hence the crash in bug
279 * #56337). Hopefully we can depend on @hp being NULL
280 * in this error case like we do with
288 #else /* No support for gethostbyname_r */
291 G_LOCK (gethost_mutex);
293 h = gethostbyname (name);
297 G_UNLOCK (gethost_mutex);
301 GETHOST_PROCESS (h, host, buf, buflen, herr);
303 G_UNLOCK (gethost_mutex);
306 #endif /* HAVE_GETHOSTBYNAME_R */
307 #endif /* ENABLE_IPv6 */
311 camel_gethostbyaddr_r (const char *addr, int addrlen, int type, struct hostent *host,
312 char *buf, size_t buflen, int *herr)
317 if ((retval = getnameinfo (addr, addrlen, buf, buflen, NULL, 0, NI_NAMEREQD)) != 0) {
318 *herr = ai_to_herr (retval);
322 len = ALIGN (strlen (buf) + 1);
323 if (buflen < IPv6_BUFLEN_MIN + len + addrlen + sizeof (char *))
331 ((char **) buf)[0] = NULL;
332 host->h_aliases = (char **) buf;
333 buf += sizeof (char *);
335 /* h_addrtype and h_length */
336 host->h_length = addrlen;
337 host->h_addrtype = type;
339 memcpy (buf, addr, host->h_length);
341 buf += ALIGN (host->h_length);
344 ((char **) buf)[0] = addr;
345 ((char **) buf)[1] = NULL;
346 host->h_addr_list = (char **) buf;
349 #else /* No support for IPv6 addresses */
350 #ifdef HAVE_GETHOSTBYADDR_R
351 #ifdef GETHOSTBYADDR_R_SEVEN_ARGS
352 if (gethostbyaddr_r (addr, addrlen, type, host, buf, buflen, herr))
360 retval = gethostbyaddr_r (addr, addrlen, type, host, buf, buflen, &hp, herr);
364 } else if (retval == 0) {
365 /* glibc 2.3.2 workaround - it seems that
366 * gethostbyaddr_r will sometimes return 0 on fail and
367 * fill @host with garbage strings from /etc/hosts
368 * (failure to parse the file? who knows). Luckily, it
369 * seems that we can rely on @hp being NULL on
377 #else /* No support for gethostbyaddr_r */
380 G_LOCK (gethost_mutex);
382 h = gethostbyaddr (addr, addrlen, type);
386 G_UNLOCK (gethost_mutex);
390 GETHOST_PROCESS (h, host, buf, buflen, herr);
392 G_UNLOCK (gethost_mutex);
395 #endif /* HAVE_GETHOSTBYADDR_R */
396 #endif /* ENABLE_IPv6 */
398 #endif /* NEED_ADDRINFO */
400 /* ********************************************************************** */
401 struct _addrinfo_msg {
403 unsigned int cancelled:1;
405 /* for host lookup */
409 const struct addrinfo *hints;
410 struct addrinfo **res;
412 /* for host lookup emulation */
414 struct hostent hostbuf;
419 /* for name lookup */
420 const struct sockaddr *addr;
430 cs_freeinfo(struct _addrinfo_msg *msg)
435 g_free(msg->hostbufmem);
440 /* returns -1 if we didn't wait for reply from thread */
442 cs_waitinfo(void *(worker)(void *), struct _addrinfo_msg *msg, const char *error, CamelException *ex)
444 EMsgPort *reply_port;
446 int err, cancel_fd, cancel = 0, fd;
448 cancel_fd = camel_operation_cancel_fd(NULL);
449 if (cancel_fd == -1) {
454 reply_port = msg->msg.reply_port = e_msgport_new();
455 fd = e_msgport_fd(msg->msg.reply_port);
456 if ((err = pthread_create(&id, NULL, worker, msg)) == 0) {
459 struct pollfd polls[2];
462 polls[0].events = POLLIN;
463 polls[1].fd = cancel_fd;
464 polls[1].events = POLLIN;
466 d(printf("waiting for name return/cancellation in main process\n"));
468 polls[0].revents = 0;
469 polls[1].revents = 0;
470 status = poll(polls, 2, -1);
471 } while (status == -1 && errno == EINTR);
476 FD_SET(fd, &read_set);
477 FD_SET(cancel_fd, &read_set);
479 status = select(MAX(fd, cancel_fd) + 1, &read_set, NULL, NULL, NULL);
484 (polls[1].revents & POLLIN)
486 FD_ISSET (cancel_fd, &read_set)
490 camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, "%s: %s", error,
494 g_win32_error_message (WSAGetLastError ())
498 camel_exception_setv(ex, CAMEL_EXCEPTION_USER_CANCEL, _("Canceled"));
500 /* We cancel so if the thread impl is decent it causes immediate exit.
501 We detach so we dont need to wait for it to exit if it isn't.
502 We check the reply port incase we had a reply in the mean time, which we free later */
503 d(printf("Canceling lookup thread and leaving it\n"));
509 struct _addrinfo_msg *reply = (struct _addrinfo_msg *)e_msgport_get(reply_port);
511 g_assert(reply == msg);
512 d(printf("waiting for child to exit\n"));
513 pthread_join(id, NULL);
514 d(printf("child done\n"));
517 camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, "%s: %s: %s", error, _("cannot create thread"), g_strerror(err));
519 e_msgport_destroy(reply_port);
526 cs_getaddrinfo(void *data)
528 struct _addrinfo_msg *msg = data;
531 struct addrinfo *res, *last = NULL;
532 struct sockaddr_in *sin;
536 /* This is a pretty simplistic emulation of getaddrinfo */
538 while ((msg->result = camel_gethostbyname_r(msg->name, &h, msg->hostbufmem, msg->hostbuflen, &herr)) == ERANGE) {
539 pthread_testcancel();
540 msg->hostbuflen *= 2;
541 msg->hostbufmem = g_realloc(msg->hostbufmem, msg->hostbuflen);
544 /* If we got cancelled, dont reply, just free it */
548 /* FIXME: map error numbers across */
549 if (msg->result != 0)
552 /* check hints matched */
553 if (msg->hints && msg->hints->ai_family && msg->hints->ai_family != h.h_addrtype) {
554 msg->result = EAI_FAMILY;
558 /* we only support ipv4 for this interface, even if it could supply ipv6 */
559 if (h.h_addrtype != AF_INET) {
560 msg->result = EAI_FAMILY;
564 /* check service mapping */
566 const char *p = msg->service;
569 if (*p < '0' || *p > '9')
575 const char *socktype = NULL;
576 struct servent *serv;
578 if (msg->hints && msg->hints->ai_socktype) {
579 if (msg->hints->ai_socktype == SOCK_STREAM)
581 else if (msg->hints->ai_socktype == SOCK_DGRAM)
585 serv = getservbyname(msg->service, socktype);
587 msg->result = EAI_NONAME;
592 port = htons(strtoul(msg->service, NULL, 10));
596 for (i=0;h.h_addr_list[i];i++) {
597 res = g_malloc0(sizeof(*res));
599 res->ai_flags = msg->hints->ai_flags;
600 if (msg->hints->ai_flags & AI_CANONNAME)
601 res->ai_canonname = g_strdup(h.h_name);
602 res->ai_socktype = msg->hints->ai_socktype;
603 res->ai_protocol = msg->hints->ai_protocol;
606 res->ai_socktype = SOCK_STREAM; /* fudge */
607 res->ai_protocol = 0; /* fudge */
609 res->ai_family = AF_INET;
610 res->ai_addrlen = sizeof(*sin);
611 res->ai_addr = g_malloc(sizeof(*sin));
612 sin = (struct sockaddr_in *)res->ai_addr;
613 sin->sin_family = AF_INET;
614 sin->sin_port = port;
615 memcpy(&sin->sin_addr, h.h_addr_list[i], sizeof(sin->sin_addr));
618 *msg->res = last = res;
625 e_msgport_reply((EMsg *)msg);
633 cs_getaddrinfo(void *data)
635 struct _addrinfo_msg *info = data;
637 info->result = getaddrinfo(info->name, info->service, info->hints, info->res);
639 if (info->cancelled) {
642 e_msgport_reply((EMsg *)info);
647 #endif /* NEED_ADDRINFO */
650 camel_getaddrinfo(const char *name, const char *service, const struct addrinfo *hints, CamelException *ex)
652 struct _addrinfo_msg *msg;
653 struct addrinfo *res = NULL;
655 struct addrinfo myhints;
657 g_return_val_if_fail(name != NULL, NULL);
659 if (camel_operation_cancel_check(NULL)) {
660 camel_exception_set(ex, CAMEL_EXCEPTION_USER_CANCEL, _("Canceled"));
664 camel_operation_start_transient(NULL, _("Resolving: %s"), name);
666 /* force ipv4 addresses only */
669 memset(&myhints, 0, sizeof(myhints));
671 memcpy (&myhints, hints, sizeof (myhints));
673 myhints.ai_family = AF_INET;
677 msg = g_malloc0(sizeof(*msg));
679 msg->service = service;
683 msg->hostbuflen = 1024;
684 msg->hostbufmem = g_malloc(msg->hostbuflen);
686 if (cs_waitinfo(cs_getaddrinfo, msg, _("Host lookup failed"), ex) == 0) {
687 if (msg->result != 0)
688 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, _("Host lookup failed: %s: %s"),
689 name, gai_strerror (msg->result));
695 camel_operation_end(NULL);
701 camel_freeaddrinfo(struct addrinfo *host)
705 struct addrinfo *next = host->ai_next;
707 g_free(host->ai_canonname);
708 g_free(host->ai_addr);
719 cs_getnameinfo(void *data)
721 struct _addrinfo_msg *msg = data;
724 struct sockaddr_in *sin = (struct sockaddr_in *)msg->addr;
726 /* FIXME: error code */
727 if (msg->addr->sa_family != AF_INET) {
732 /* FIXME: honour getnameinfo flags: do we care, not really */
734 while ((msg->result = camel_gethostbyaddr_r((const char *)&sin->sin_addr, sizeof(sin->sin_addr), AF_INET, &h,
735 msg->hostbufmem, msg->hostbuflen, &herr)) == ERANGE) {
736 pthread_testcancel ();
737 msg->hostbuflen *= 2;
738 msg->hostbufmem = g_realloc(msg->hostbufmem, msg->hostbuflen);
746 if (msg->result == 0 && h.h_name && h.h_name[0]) {
747 msg->host = g_strdup(h.h_name);
749 unsigned char *in = (unsigned char *)&sin->sin_addr;
751 /* sin_addr is always network order which is big-endian */
752 msg->host = g_strdup_printf("%u.%u.%u.%u", in[0], in[1], in[2], in[3]);
756 /* we never actually use this anyway */
758 sprintf(msg->serv, "%d", sin->sin_port);
760 e_msgport_reply((EMsg *)msg);
768 cs_getnameinfo(void *data)
770 struct _addrinfo_msg *msg = data;
772 /* there doens't appear to be a return code which says host or serv buffers are too short, lengthen them */
773 msg->result = getnameinfo(msg->addr, msg->addrlen, msg->host, msg->hostlen, msg->serv, msg->servlen, msg->flags);
778 e_msgport_reply((EMsg *)msg);
785 camel_getnameinfo(const struct sockaddr *sa, socklen_t salen, char **host, char **serv, int flags, CamelException *ex)
787 struct _addrinfo_msg *msg;
790 if (camel_operation_cancel_check(NULL)) {
791 camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL, _("Canceled"));
795 camel_operation_start_transient(NULL, _("Resolving address"));
797 msg = g_malloc0(sizeof(*msg));
799 msg->addrlen = salen;
801 msg->hostlen = NI_MAXHOST;
802 msg->host = g_malloc(msg->hostlen);
806 msg->servlen = NI_MAXSERV;
807 msg->serv = g_malloc(msg->servlen);
812 msg->hostbuflen = 1024;
813 msg->hostbufmem = g_malloc(msg->hostbuflen);
815 cs_waitinfo(cs_getnameinfo, msg, _("Name lookup failed"), ex);
817 if ((result = msg->result) != 0)
818 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, _("Name lookup failed: %s"),
819 gai_strerror (result));
822 *host = g_strdup(msg->host);
824 *serv = g_strdup(msg->serv);
831 camel_operation_end(NULL);