/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- * * Authors: Michael Zucchi * Jeffrey Stedfast * Chris Toshok * * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) * * This program is free software; you can redistribute it and/or * modify it under the terms of version 2 of the GNU Lesser General Public * License as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 * USA */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include "camel-exception.h" #include "camel-msgport.h" #include "camel-net-utils.h" #ifdef G_OS_WIN32 #include #include #ifdef HAVE_WSPIAPI_H #include #endif #endif #include "camel-operation.h" #define d(x) /* These are GNU extensions */ #ifndef NI_MAXHOST #define NI_MAXHOST 1025 #endif #ifndef NI_MAXSERV #define NI_MAXSERV 32 #endif #ifdef G_OS_WIN32 typedef short in_port_t; #undef gai_strerror #define gai_strerror my_gai_strerror /* gai_strerror() is implemented as an inline function in Microsoft's * SDK, but mingw lacks that. So implement here. The EAI_* errors can * be handled with the normal FormatMessage() API, * i.e. g_win32_error_message(). */ static const gchar * gai_strerror (gint error_code) { gchar *msg = g_win32_error_message (error_code); GQuark quark = g_quark_from_string (msg); const gchar *retval = g_quark_to_string (quark); g_free (msg); return retval; } #endif /* gethostbyname emulation code for emulating getaddrinfo code ... This should probably go away */ #ifdef NEED_ADDRINFO #if !defined (HAVE_GETHOSTBYNAME_R) || !defined (HAVE_GETHOSTBYADDR_R) G_LOCK_DEFINE_STATIC (gethost_mutex); #endif #define ALIGN(x) (((x) + (sizeof (gchar *) - 1)) & ~(sizeof (gchar *) - 1)) #define GETHOST_PROCESS(h, host, buf, buflen, herr) G_STMT_START { \ gint num_aliases = 0, num_addrs = 0; \ gint req_length; \ gchar *p; \ gint i; \ \ /* check to make sure we have enough room in our buffer */ \ req_length = 0; \ if (h->h_aliases) { \ for (i = 0; h->h_aliases[i]; i++) \ req_length += strlen (h->h_aliases[i]) + 1; \ num_aliases = i; \ } \ \ if (h->h_addr_list) { \ for (i = 0; h->h_addr_list[i]; i++) \ req_length += h->h_length; \ num_addrs = i; \ } \ \ req_length += sizeof (gchar *) * (num_aliases + 1); \ req_length += sizeof (gchar *) * (num_addrs + 1); \ req_length += strlen (h->h_name) + 1; \ \ if (buflen < req_length) { \ *herr = ERANGE; \ G_UNLOCK (gethost_mutex); \ return ERANGE; \ } \ \ /* we store the alias/addr pointers in the buffer */ \ /* their addresses here. */ \ p = buf; \ if (num_aliases) { \ host->h_aliases = (gchar **) p; \ p += sizeof (gchar *) * (num_aliases + 1); \ } else \ host->h_aliases = NULL; \ \ if (num_addrs) { \ host->h_addr_list = (gchar **) p; \ p += sizeof (gchar *) * (num_addrs + 1); \ } else \ host->h_addr_list = NULL; \ \ /* copy the host name into the buffer */ \ host->h_name = p; \ strcpy (p, h->h_name); \ p += strlen (h->h_name) + 1; \ host->h_addrtype = h->h_addrtype; \ host->h_length = h->h_length; \ \ /* copy the aliases/addresses into the buffer */ \ /* and assign pointers into the hostent */ \ *p = 0; \ if (num_aliases) { \ for (i = 0; i < num_aliases; i++) { \ strcpy (p, h->h_aliases[i]); \ host->h_aliases[i] = p; \ p += strlen (h->h_aliases[i]); \ } \ host->h_aliases[num_aliases] = NULL; \ } \ \ if (num_addrs) { \ for (i = 0; i < num_addrs; i++) { \ memcpy (p, h->h_addr_list[i], h->h_length); \ host->h_addr_list[i] = p; \ p += h->h_length; \ } \ host->h_addr_list[num_addrs] = NULL; \ } \ } G_STMT_END #ifdef ENABLE_IPv6 /* some helpful utils for IPv6 lookups */ #define IPv6_BUFLEN_MIN (sizeof (gchar *) * 3) static gint ai_to_herr (gint error) { switch (error) { case EAI_NONAME: case EAI_FAIL: return HOST_NOT_FOUND; break; case EAI_SERVICE: return NO_DATA; break; case EAI_ADDRFAMILY: return NO_ADDRESS; break; case EAI_NODATA: return NO_DATA; break; case EAI_MEMORY: return ENOMEM; break; case EAI_AGAIN: return TRY_AGAIN; break; case EAI_SYSTEM: return errno; break; default: return NO_RECOVERY; break; } } #endif /* ENABLE_IPv6 */ static gint camel_gethostbyname_r (const gchar *name, struct hostent *host, gchar *buf, gsize buflen, gint *herr) { #ifdef ENABLE_IPv6 struct addrinfo hints, *res; gint retval, len; gchar *addr; memset (&hints, 0, sizeof (struct addrinfo)); #ifdef HAVE_AI_ADDRCONFIG hints.ai_flags = AI_CANONNAME | AI_ADDRCONFIG; #else hints.ai_flags = AI_CANONNAME; #endif hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; if ((retval = getaddrinfo (name, NULL, &hints, &res)) != 0) { *herr = ai_to_herr (retval); return -1; } len = ALIGN (strlen (res->ai_canonname) + 1); if (buflen < IPv6_BUFLEN_MIN + len + res->ai_addrlen + sizeof (gchar *)) return ERANGE; /* h_name */ strcpy (buf, res->ai_canonname); host->h_name = buf; buf += len; /* h_aliases */ ((gchar **) buf)[0] = NULL; host->h_aliases = (gchar **) buf; buf += sizeof (gchar *); /* h_addrtype and h_length */ host->h_length = res->ai_addrlen; if (res->ai_family == PF_INET6) { host->h_addrtype = AF_INET6; addr = (gchar *) &((struct sockaddr_in6 *) res->ai_addr)->sin6_addr; } else { host->h_addrtype = AF_INET; addr = (gchar *) &((struct sockaddr_in *) res->ai_addr)->sin_addr; } memcpy (buf, addr, host->h_length); addr = buf; buf += ALIGN (host->h_length); /* h_addr_list */ ((gchar **) buf)[0] = addr; ((gchar **) buf)[1] = NULL; host->h_addr_list = (gchar **) buf; freeaddrinfo (res); return 0; #else /* No support for IPv6 addresses */ #ifdef HAVE_GETHOSTBYNAME_R #ifdef GETHOSTBYNAME_R_FIVE_ARGS if (gethostbyname_r (name, host, buf, buflen, herr)) return 0; else return errno; #else struct hostent *hp; gint retval; retval = gethostbyname_r (name, host, buf, buflen, &hp, herr); if (hp != NULL) { *herr = 0; } else if (retval == 0) { /* glibc 2.3.2 workaround - it seems that * gethostbyname_r will sometimes return 0 on fail and * not set the hostent values (hence the crash in bug * #56337). Hopefully we can depend on @hp being NULL * in this error case like we do with * gethostbyaddr_r(). */ retval = -1; } return retval; #endif #else /* No support for gethostbyname_r */ struct hostent *h; G_LOCK (gethost_mutex); h = gethostbyname (name); if (!h) { *herr = h_errno; G_UNLOCK (gethost_mutex); return -1; } GETHOST_PROCESS (h, host, buf, buflen, herr); G_UNLOCK (gethost_mutex); return 0; #endif /* HAVE_GETHOSTBYNAME_R */ #endif /* ENABLE_IPv6 */ } static gint camel_gethostbyaddr_r (const gchar *addr, gint addrlen, gint type, struct hostent *host, gchar *buf, gsize buflen, gint *herr) { #ifdef ENABLE_IPv6 gint retval, len; if ((retval = getnameinfo (addr, addrlen, buf, buflen, NULL, 0, NI_NAMEREQD)) != 0) { *herr = ai_to_herr (retval); return -1; } len = ALIGN (strlen (buf) + 1); if (buflen < IPv6_BUFLEN_MIN + len + addrlen + sizeof (gchar *)) return ERANGE; /* h_name */ host->h_name = buf; buf += len; /* h_aliases */ ((gchar **) buf)[0] = NULL; host->h_aliases = (gchar **) buf; buf += sizeof (gchar *); /* h_addrtype and h_length */ host->h_length = addrlen; host->h_addrtype = type; memcpy (buf, addr, host->h_length); addr = buf; buf += ALIGN (host->h_length); /* h_addr_list */ ((gchar **) buf)[0] = addr; ((gchar **) buf)[1] = NULL; host->h_addr_list = (gchar **) buf; return 0; #else /* No support for IPv6 addresses */ #ifdef HAVE_GETHOSTBYADDR_R #ifdef GETHOSTBYADDR_R_SEVEN_ARGS if (gethostbyaddr_r (addr, addrlen, type, host, buf, buflen, herr)) return 0; else return errno; #else struct hostent *hp; gint retval; retval = gethostbyaddr_r (addr, addrlen, type, host, buf, buflen, &hp, herr); if (hp != NULL) { *herr = 0; retval = 0; } else if (retval == 0) { /* glibc 2.3.2 workaround - it seems that * gethostbyaddr_r will sometimes return 0 on fail and * fill @host with garbage strings from /etc/hosts * (failure to parse the file? who knows). Luckily, it * seems that we can rely on @hp being NULL on * fail. */ retval = -1; } return retval; #endif #else /* No support for gethostbyaddr_r */ struct hostent *h; G_LOCK (gethost_mutex); h = gethostbyaddr (addr, addrlen, type); if (!h) { *herr = h_errno; G_UNLOCK (gethost_mutex); return -1; } GETHOST_PROCESS (h, host, buf, buflen, herr); G_UNLOCK (gethost_mutex); return 0; #endif /* HAVE_GETHOSTBYADDR_R */ #endif /* ENABLE_IPv6 */ } #endif /* NEED_ADDRINFO */ /* ********************************************************************** */ struct _addrinfo_msg { CamelMsg msg; guint cancelled:1; /* for host lookup */ const gchar *name; const gchar *service; gint result; const struct addrinfo *hints; struct addrinfo **res; /* for host lookup emulation */ #ifdef NEED_ADDRINFO struct hostent hostbuf; gint hostbuflen; gchar *hostbufmem; #endif /* for name lookup */ const struct sockaddr *addr; socklen_t addrlen; gchar *host; gint hostlen; gchar *serv; gint servlen; gint flags; }; static void cs_freeinfo(struct _addrinfo_msg *msg) { g_free(msg->host); g_free(msg->serv); #ifdef NEED_ADDRINFO g_free(msg->hostbufmem); #endif g_free(msg); } /* returns -1 if we didn't wait for reply from thread */ static gint cs_waitinfo(gpointer (worker)(gpointer), struct _addrinfo_msg *msg, const gchar *error, CamelException *ex) { CamelMsgPort *reply_port; GThread *thread; GError *err = NULL; gint cancel_fd, cancel = 0, fd; cancel_fd = camel_operation_cancel_fd(NULL); if (cancel_fd == -1) { worker(msg); return 0; } reply_port = msg->msg.reply_port = camel_msgport_new(); fd = camel_msgport_fd(msg->msg.reply_port); if ((thread = g_thread_create (worker, msg, TRUE, &err)) != NULL) { gint status; #ifndef G_OS_WIN32 GPollFD polls[2]; polls[0].fd = fd; polls[0].events = G_IO_IN; polls[1].fd = cancel_fd; polls[1].events = G_IO_IN; d(printf("waiting for name return/cancellation in main process\n")); do { polls[0].revents = 0; polls[1].revents = 0; status = g_poll(polls, 2, -1); } while (status == -1 && errno == EINTR); #else fd_set read_set; FD_ZERO(&read_set); FD_SET(fd, &read_set); FD_SET(cancel_fd, &read_set); status = select(MAX(fd, cancel_fd) + 1, &read_set, NULL, NULL, NULL); #endif if (status == -1 || #ifndef G_OS_WIN32 (polls[1].revents & G_IO_IN) #else FD_ISSET (cancel_fd, &read_set) #endif ) { if (status == -1) camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, "%s: %s", error, #ifndef G_OS_WIN32 g_strerror(errno) #else g_win32_error_message (WSAGetLastError ()) #endif ); else camel_exception_setv(ex, CAMEL_EXCEPTION_USER_CANCEL, _("Canceled")); /* We cancel so if the thread impl is decent it causes immediate exit. We check the reply port incase we had a reply in the mean time, which we free later */ d(printf("Canceling lookup thread and leaving it\n")); msg->cancelled = 1; g_thread_join (thread); cancel = 1; } else { struct _addrinfo_msg *reply; d(printf("waiting for child to exit\n")); g_thread_join (thread); d(printf("child done\n")); reply = (struct _addrinfo_msg *)camel_msgport_try_pop(reply_port); if (reply != msg) g_warning ("%s: Received msg reply %p doesn't match msg %p", G_STRFUNC, reply, msg); } } else { camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, "%s: %s: %s", error, _("cannot create thread"), err ? err->message : _("Unknown error")); if (err) g_error_free (err); } camel_msgport_destroy(reply_port); return cancel; } #ifdef NEED_ADDRINFO static gpointer cs_getaddrinfo(gpointer data) { struct _addrinfo_msg *msg = data; gint herr; struct hostent h; struct addrinfo *res, *last = NULL; struct sockaddr_in *sin; in_port_t port = 0; gint i; /* This is a pretty simplistic emulation of getaddrinfo */ while ((msg->result = camel_gethostbyname_r(msg->name, &h, msg->hostbufmem, msg->hostbuflen, &herr)) == ERANGE) { if (msg->cancelled) break; msg->hostbuflen *= 2; msg->hostbufmem = g_realloc(msg->hostbufmem, msg->hostbuflen); } /* If we got cancelled, dont reply, just free it */ if (msg->cancelled) goto cancel; /* FIXME: map error numbers across */ if (msg->result != 0) goto reply; /* check hints matched */ if (msg->hints && msg->hints->ai_family && msg->hints->ai_family != h.h_addrtype) { msg->result = EAI_FAMILY; goto reply; } /* we only support ipv4 for this interface, even if it could supply ipv6 */ if (h.h_addrtype != AF_INET) { msg->result = EAI_FAMILY; goto reply; } /* check service mapping */ if (msg->service) { const gchar *p = msg->service; while (*p) { if (*p < '0' || *p > '9') break; p++; } if (*p) { const gchar *socktype = NULL; struct servent *serv; if (msg->hints && msg->hints->ai_socktype) { if (msg->hints->ai_socktype == SOCK_STREAM) socktype = "tcp"; else if (msg->hints->ai_socktype == SOCK_DGRAM) socktype = "udp"; } serv = getservbyname(msg->service, socktype); if (serv == NULL) { msg->result = EAI_NONAME; goto reply; } port = serv->s_port; } else { port = htons(strtoul(msg->service, NULL, 10)); } } for (i = 0; h.h_addr_list[i] && !msg->cancelled; i++) { res = g_malloc0(sizeof(*res)); if (msg->hints) { res->ai_flags = msg->hints->ai_flags; if (msg->hints->ai_flags & AI_CANONNAME) res->ai_canonname = g_strdup(h.h_name); res->ai_socktype = msg->hints->ai_socktype; res->ai_protocol = msg->hints->ai_protocol; } else { res->ai_flags = 0; res->ai_socktype = SOCK_STREAM; /* fudge */ res->ai_protocol = 0; /* fudge */ } res->ai_family = AF_INET; res->ai_addrlen = sizeof(*sin); res->ai_addr = g_malloc(sizeof(*sin)); sin = (struct sockaddr_in *)res->ai_addr; sin->sin_family = AF_INET; sin->sin_port = port; memcpy(&sin->sin_addr, h.h_addr_list[i], sizeof(sin->sin_addr)); if (last == NULL) { *msg->res = last = res; } else { last->ai_next = res; last = res; } } reply: camel_msgport_reply((CamelMsg *)msg); cancel: return NULL; } #else static gpointer cs_getaddrinfo(gpointer data) { struct _addrinfo_msg *info = data; info->result = getaddrinfo(info->name, info->service, info->hints, info->res); /* On Solaris, the service name 'http' or 'https' is not defined. Use the port as the service name directly. */ if (info->result && info->service) { if (strcmp (info->service, "http") == 0) info->result = getaddrinfo(info->name, "80", info->hints, info->res); else if (strcmp (info->service, "https") == 0) info->result = getaddrinfo(info->name, "443", info->hints, info->res); } if (!info->cancelled) camel_msgport_reply((CamelMsg *)info); return NULL; } #endif /* NEED_ADDRINFO */ /** * camel_getaddrinfo: * * Since: 2.22 **/ struct addrinfo * camel_getaddrinfo(const gchar *name, const gchar *service, const struct addrinfo *hints, CamelException *ex) { struct _addrinfo_msg *msg; struct addrinfo *res = NULL; #ifndef ENABLE_IPv6 struct addrinfo myhints; #endif g_return_val_if_fail(name != NULL, NULL); if (camel_operation_cancel_check(NULL)) { camel_exception_set(ex, CAMEL_EXCEPTION_USER_CANCEL, _("Canceled")); return NULL; } camel_operation_start_transient(NULL, _("Resolving: %s"), name); /* force ipv4 addresses only */ #ifndef ENABLE_IPv6 if (hints == NULL) memset(&myhints, 0, sizeof(myhints)); else memcpy (&myhints, hints, sizeof (myhints)); myhints.ai_family = AF_INET; hints = &myhints; #endif msg = g_malloc0(sizeof(*msg)); msg->name = name; msg->service = service; msg->hints = hints; msg->res = &res; #ifdef NEED_ADDRINFO msg->hostbuflen = 1024; msg->hostbufmem = g_malloc(msg->hostbuflen); #endif if (cs_waitinfo(cs_getaddrinfo, msg, _("Host lookup failed"), ex) == 0) { if (msg->result != 0) { camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, _("Host lookup failed: %s: %s"), name, gai_strerror (msg->result)); } } else res = NULL; cs_freeinfo (msg); camel_operation_end(NULL); return res; } /** * camel_freeaddrinfo: * * Since: 2.22 **/ void camel_freeaddrinfo(struct addrinfo *host) { #ifdef NEED_ADDRINFO while (host) { struct addrinfo *next = host->ai_next; g_free(host->ai_canonname); g_free(host->ai_addr); g_free(host); host = next; } #else freeaddrinfo(host); #endif } #ifdef NEED_ADDRINFO static gpointer cs_getnameinfo(gpointer data) { struct _addrinfo_msg *msg = data; gint herr; struct hostent h; struct sockaddr_in *sin = (struct sockaddr_in *)msg->addr; /* FIXME: error code */ if (msg->addr->sa_family != AF_INET) { msg->result = -1; return NULL; } /* FIXME: honour getnameinfo flags: do we care, not really */ while ((msg->result = camel_gethostbyaddr_r((const gchar *)&sin->sin_addr, sizeof(sin->sin_addr), AF_INET, &h, msg->hostbufmem, msg->hostbuflen, &herr)) == ERANGE) { if (msg->cancelled) break; msg->hostbuflen *= 2; msg->hostbufmem = g_realloc(msg->hostbufmem, msg->hostbuflen); } if (msg->cancelled) goto cancel; if (msg->host) { g_free(msg->host); if (msg->result == 0 && h.h_name && h.h_name[0]) { msg->host = g_strdup(h.h_name); } else { guchar *in = (guchar *)&sin->sin_addr; /* sin_addr is always network order which is big-endian */ msg->host = g_strdup_printf("%u.%u.%u.%u", in[0], in[1], in[2], in[3]); } } /* we never actually use this anyway */ if (msg->serv) sprintf(msg->serv, "%d", sin->sin_port); if (!msg->cancelled) camel_msgport_reply ((CamelMsg *)msg); cancel: return NULL; } #else static gpointer cs_getnameinfo(gpointer data) { struct _addrinfo_msg *msg = data; /* there doens't appear to be a return code which says host or serv buffers are too short, lengthen them */ msg->result = getnameinfo(msg->addr, msg->addrlen, msg->host, msg->hostlen, msg->serv, msg->servlen, msg->flags); if (!msg->cancelled) camel_msgport_reply((CamelMsg *)msg); return NULL; } #endif /** * camel_getnameinfo: * * Since: 2.22 **/ gint camel_getnameinfo(const struct sockaddr *sa, socklen_t salen, gchar **host, gchar **serv, gint flags, CamelException *ex) { struct _addrinfo_msg *msg; gint result; if (camel_operation_cancel_check(NULL)) { camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL, _("Canceled")); return -1; } camel_operation_start_transient(NULL, _("Resolving address")); msg = g_malloc0(sizeof(*msg)); msg->addr = sa; msg->addrlen = salen; if (host) { msg->hostlen = NI_MAXHOST; msg->host = g_malloc(msg->hostlen); msg->host[0] = 0; } if (serv) { msg->servlen = NI_MAXSERV; msg->serv = g_malloc(msg->servlen); msg->serv[0] = 0; } msg->flags = flags; #ifdef NEED_ADDRINFO msg->hostbuflen = 1024; msg->hostbufmem = g_malloc(msg->hostbuflen); #endif cs_waitinfo(cs_getnameinfo, msg, _("Name lookup failed"), ex); if ((result = msg->result) != 0) camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, _("Name lookup failed: %s"), gai_strerror (result)); else { if (host) *host = g_strdup(msg->host); if (serv) *serv = g_strdup(msg->serv); } cs_freeinfo (msg); camel_operation_end(NULL); return result; }