/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) * * This library is free software: you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation. * * This library 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 library. If not, see . * * Authors: Michael Zucchi * Jeffrey Stedfast * Chris Toshok */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include "camel-msgport.h" #include "camel-net-utils.h" #ifdef G_OS_WIN32 #include #include #ifdef HAVE_WSPIAPI_H #include #endif #endif #include "camel-object.h" #include "camel-operation.h" #include "camel-service.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 gshort 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 */ g_strlcpy (buf, res->ai_canonname, buflen); 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 *errmsg, GCancellable *cancellable, GError **error) { CamelMsgPort *reply_port; GThread *thread; gint cancel_fd, cancel = 0, fd; cancel_fd = g_cancellable_get_fd (cancellable); 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_new (NULL, worker, msg)) != 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) g_set_error ( error, G_IO_ERROR, g_io_error_from_errno (errno), "%s: %s", errmsg, #ifndef G_OS_WIN32 g_strerror (errno) #else g_win32_error_message (WSAGetLastError ()) #endif ); else g_set_error ( error, G_IO_ERROR, G_IO_ERROR_CANCELLED, _("Cancelled")); /* 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); } } camel_msgport_destroy (reply_port); g_cancellable_release_fd (cancellable); 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, GCancellable *cancellable, GError **error) { struct _addrinfo_msg *msg; struct addrinfo *res = NULL; #ifndef ENABLE_IPv6 struct addrinfo myhints; #endif gchar *ascii_name; g_return_val_if_fail (name != NULL, NULL); if (g_cancellable_set_error_if_cancelled (cancellable, error)) return NULL; camel_operation_push_message ( cancellable, _("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 ascii_name = camel_host_idna_to_ascii (name); msg = g_malloc0 (sizeof (*msg)); msg->name = ascii_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"), cancellable, error) == 0) { if (msg->result == EAI_NONAME || msg->result == EAI_FAIL) { g_set_error ( error, CAMEL_SERVICE_ERROR, CAMEL_SERVICE_ERROR_URL_INVALID, _("Host lookup '%s' failed. Check your host name for spelling errors."), name); } else if (msg->result != 0) { g_set_error ( error, CAMEL_SERVICE_ERROR, CAMEL_SERVICE_ERROR_URL_INVALID, _("Host lookup '%s' failed: %s"), name, gai_strerror (msg->result)); } } else res = NULL; cs_freeinfo (msg); g_free (ascii_name); camel_operation_pop_message (cancellable); 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 } /** * camel_host_idna_to_ascii: * @host: Host name, with or without non-ascii letters in utf8 * * Converts IDN (Internationalized Domain Name) into ASCII representation. * If there's a failure or the @host has only ASCII letters, then a copy * of @host is returned. * * Returns: Newly allocated string with only ASCII letters describing the @host. * Free it with g_free() when done with it. * * Since: 3.16 **/ gchar * camel_host_idna_to_ascii (const gchar *host) { UErrorCode uerror = U_ZERO_ERROR; int32_t uhost_len = 0; const gchar *ptr; gchar *ascii = NULL; g_return_val_if_fail (host != NULL, NULL); ptr = host; while (*ptr > 0) ptr++; if (!*ptr) { /* Did read whole buffer, it should be ASCII string already */ return g_strdup (host); } u_strFromUTF8 (NULL, 0, &uhost_len, host, -1, &uerror); if (uhost_len > 0) { UChar *uhost = g_new0 (UChar, uhost_len + 2); uerror = U_ZERO_ERROR; u_strFromUTF8 (uhost, uhost_len + 1, &uhost_len, host, -1, &uerror); if (uerror == U_ZERO_ERROR && uhost_len > 0) { int32_t buffer_len = uhost_len * 6 + 6, nconverted; UChar *buffer = g_new0 (UChar, buffer_len); nconverted = uidna_IDNToASCII (uhost, uhost_len, buffer, buffer_len, UIDNA_ALLOW_UNASSIGNED, 0, &uerror); if (uerror == U_ZERO_ERROR && nconverted > 0) { int32_t ascii_len = 0; u_strToUTF8 (NULL, 0, &ascii_len, buffer, nconverted, &uerror); if (ascii_len > 0) { uerror = U_ZERO_ERROR; ascii = g_new0 (gchar, ascii_len + 2); u_strToUTF8 (ascii, ascii_len + 1, &ascii_len, buffer, nconverted, &uerror); if (uerror == U_ZERO_ERROR && ascii_len > 0) { ascii[ascii_len] = '\0'; } else { g_free (ascii); ascii = NULL; } } } g_free (buffer); } g_free (uhost); } if (!ascii) ascii = g_strdup (host); return ascii; }