commit 3a10305dc7edc339784dc4453091c80fb4b5f3e0 Author: Nicolas Dufresne Date: Thu Aug 26 11:29:12 2010 -0400 Added support for HTTP Connect Proxy This patch registers an HTTP Connect extension to GLib proxy facility. This extension is triggered (whenever an HTTPS proxy is set) when legacy_ssl is being by providing a https:// base URI to the GSocketClient connect method. diff --git a/configure.ac b/configure.ac index e0e8e69..8dc5368 100644 --- a/configure.ac +++ b/configure.ac @@ -116,6 +116,14 @@ PKG_CHECK_MODULES(GLIB, [glib-2.0 >= 2.16, gobject-2.0 >= 2.16, gthread-2.0 >= AC_SUBST(GLIB_CFLAGS) AC_SUBST(GLIB_LIBS) +dnl Check GIO proxy support +PKG_CHECK_EXISTS([gio-2.0 >= 2.25.15], + [HAVE_GIO_PROXY=yes + AC_DEFINE(HAVE_GIO_PROXY, [1], [Defined if GIO supports proxy])], + [HAVE_GIO_PROXY=no]) +AM_CONDITIONAL(HAVE_GIO_PROXY, [test "x${HAVE_GIO_PROXY}" = "xyes"]) + + dnl Choose an SSL/TLS backend (default gnutls) AC_ARG_WITH([tls], AC_HELP_STRING([--with-tls=BACKEND], diff --git a/wocky/Makefile.am b/wocky/Makefile.am index dfbd106..de71e7c 100644 --- a/wocky/Makefile.am +++ b/wocky/Makefile.am @@ -40,6 +40,10 @@ OPENSSL_SRC = \ GNUTLS_SRC = wocky-tls.c +PROXY_SRC = \ + wocky-http-proxy.h \ + wocky-http-proxy.c + handwritten_headers = \ wocky.h \ wocky-auth-handler.h \ @@ -132,6 +136,10 @@ else EXTRA_DIST += $(OPENSSL_SRC) endif +if HAVE_GIO_PROXY + handwritten_sources += $(PROXY_SRC) +endif + libwocky_la_SOURCES = $(handwritten_sources) $(built_sources) \ $(handwritten_headers) $(built_headers) diff --git a/wocky/wocky-connector.c b/wocky/wocky-connector.c index e0a1016..7d55e47 100644 --- a/wocky/wocky-connector.c +++ b/wocky/wocky-connector.c @@ -101,6 +101,7 @@ #define DEBUG_FLAG DEBUG_CONNECTOR #include "wocky-debug.h" +#include "wocky-http-proxy.h" #include "wocky-sasl-auth.h" #include "wocky-tls-handler.h" #include "wocky-tls-connector.h" @@ -557,6 +558,11 @@ wocky_connector_class_init (WockyConnectorClass *klass) oclass->dispose = wocky_connector_dispose; oclass->finalize = wocky_connector_finalize; +#if HAVE_GIO_PROXY + /* Ensure that HTTP Proxy extension is registered */ + _wocky_http_proxy_get_type (); +#endif + /** * WockyConnector:plaintext-auth-allowed: * @@ -782,6 +788,27 @@ wocky_connector_finalize (GObject *object) } static void +connect_to_host_async (WockyConnector *connector, + const gchar *host, + guint port) +{ + WockyConnectorPrivate *priv = connector->priv; + +#if HAVE_GIO_PROXY + /* Legacy SSL mode is just like doing HTTPS, so let's trigger HTTPS + * proxy setting if any */ + gchar *uri = g_strdup_printf ("%s://%s:%i", + priv->legacy_ssl ? "https" : "xmpp-client", host, port); + g_socket_client_connect_to_uri_async (priv->client, + uri, port, NULL, tcp_host_connected, connector); + g_free (uri); +#else + g_socket_client_connect_to_host_async (priv->client, + host, port, NULL, tcp_host_connected, connector); +#endif +} + +static void tcp_srv_connected (GObject *source, GAsyncResult *result, gpointer connector) @@ -836,8 +863,7 @@ tcp_srv_connected (GObject *source, wocky_decode_jid (priv->jid, &node, &host, NULL); if ((host != NULL) && (*host != '\0')) - g_socket_client_connect_to_host_async (priv->client, - host, port, NULL, tcp_host_connected, connector); + connect_to_host_async (connector, host, port); else abort_connect_code (self, WOCKY_CONNECTOR_ERROR_BAD_JID, "JID contains no domain: %s", priv->jid); @@ -2221,8 +2247,8 @@ connector_connect_async (WockyConnector *self, const gchar *srv = (priv->xmpp_host == NULL) ? host : priv->xmpp_host; DEBUG ("host: %s; port: %d", priv->xmpp_host, priv->xmpp_port); - g_socket_client_connect_to_host_async (priv->client, srv, port, NULL, - tcp_host_connected, self); + + connect_to_host_async (self, srv, port); } else { diff --git a/wocky/wocky-http-proxy.c b/wocky/wocky-http-proxy.c new file mode 100644 index 0000000..281a32a --- /dev/null +++ b/wocky/wocky-http-proxy.c @@ -0,0 +1,414 @@ + /* wocky-http-proxy.c: Source for WockyHttpProxy + * + * Copyright (C) 2010 Collabora, Ltd. + * @author Nicolas Dufresne + * + * 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; either + * version 2.1 of the License, or (at your option) any later version. + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" + +#include "wocky-http-proxy.h" + +#include +#include + + +struct _WockyHttpProxy +{ + GObject parent; +}; + +struct _WockyHttpProxyClass +{ + GObjectClass parent_class; +}; + +static void wocky_http_proxy_iface_init (GProxyInterface *proxy_iface); + +#define wocky_http_proxy_get_type _wocky_http_proxy_get_type +G_DEFINE_TYPE_WITH_CODE (WockyHttpProxy, wocky_http_proxy, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_PROXY, + wocky_http_proxy_iface_init) + g_io_extension_point_set_required_type ( + g_io_extension_point_register (G_PROXY_EXTENSION_POINT_NAME), + G_TYPE_PROXY); + g_io_extension_point_implement (G_PROXY_EXTENSION_POINT_NAME, + g_define_type_id, "http", 0)) + +static void +wocky_http_proxy_finalize (GObject *object) +{ + /* must chain up */ + G_OBJECT_CLASS (wocky_http_proxy_parent_class)->finalize (object); +} + +static void +wocky_http_proxy_init (WockyHttpProxy *proxy) +{ +} + +#define HTTP_END_MARKER "\r\n\r\n" + +static gchar * +create_request (gchar *host, gint port) +{ + return g_strdup_printf ("CONNECT %s:%i HTTP/1.0\r\n" + "Host: %s" + "Proxy-Connection: keep-alive\r\n" + "User-Agent: GLib/%i.%i\r\n" + "\r\n", + host, port, + host, + GLIB_MAJOR_VERSION, GLIB_MINOR_VERSION); +} + +static gboolean +check_reply (const gchar *buffer, GError **error) +{ + gint err_code; + const gchar *ptr = buffer + 7; + + if (strncmp (buffer, "HTTP/1.", 7) + || (*ptr != '0' && *ptr != '1')) + { + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_PROXY_FAILED, + "Bad HTTP proxy reply"); + return FALSE; + } + + ptr++; + while (*ptr == ' ') ptr++; + + err_code = atoi (ptr); + + if (err_code < 200 || err_code >= 300) + { + const gchar *msg_start; + gchar *msg; + + while (g_ascii_isdigit (*ptr)) + ptr++; + + while (*ptr == ' ') + ptr++; + + msg_start = ptr; + + ptr = strchr (msg_start, '\r'); + if (!ptr) + ptr = strchr (msg_start, '\0'); + + msg = g_strndup (msg_start, ptr - msg_start); + + if (err_code == 407) + g_set_error (error, G_IO_ERROR, G_IO_ERROR_PROXY_NEED_AUTH, + "HTTP proxy authentication not supported by GLib"); + else + g_set_error (error, G_IO_ERROR, G_IO_ERROR_PROXY_FAILED, + "HTTP proxy connection failed: %s", + msg); + + g_free (msg); + return FALSE; + } + + return TRUE; +} + +static GIOStream * +wocky_http_proxy_connect (GProxy *proxy, + GIOStream *io_stream, + GProxyAddress *proxy_address, + GCancellable *cancellable, + GError **error) +{ + GInputStream *in; + GOutputStream *out; + GDataInputStream *datain; + gchar *buffer; + gchar *host; + gint port; + gchar *username; + gchar *password; + + g_object_get (G_OBJECT (proxy_address), + "destination-hostname", &host, + "destination-port", &port, + "username", &username, + "password", &password, + NULL); + + in = g_io_stream_get_input_stream (io_stream); + out = g_io_stream_get_output_stream (io_stream); + + datain = g_data_input_stream_new (in); + g_filter_input_stream_set_close_base_stream (G_FILTER_INPUT_STREAM (datain), + FALSE); + + buffer = create_request (host, port); + + if (!g_output_stream_write_all (out, buffer, strlen (buffer), NULL, + cancellable, error)) + goto error; + + g_free (host); + host = NULL; + g_free (username); + username = NULL; + g_free (password); + password = NULL; + + g_free (buffer); + buffer = g_data_input_stream_read_until (datain, HTTP_END_MARKER, NULL, + cancellable, error); + g_object_unref (datain); + datain = NULL; + + if (!buffer) + goto error; + + if (!check_reply (buffer, error)) + goto error; + + g_free (buffer); + + return g_object_ref (io_stream); + +error: + if (datain) + g_object_unref (datain); + g_free (buffer); + g_free (host); + g_free (username); + g_free (password); + return NULL; +} + + +typedef struct +{ + GSimpleAsyncResult *simple; + GIOStream *io_stream; + gchar *hostname; + guint16 port; + gchar *username; + gchar *password; + gchar *buffer; + gssize length; + gssize offset; + GDataInputStream *data; + GCancellable *cancellable; +} ConnectAsyncData; + +static void request_write_cb (GObject *source, + GAsyncResult *res, + gpointer user_data); +static void reply_read_cb (GObject *source, + GAsyncResult *res, + gpointer user_data); + +static void +free_connect_data (ConnectAsyncData *data) +{ + if (data->io_stream) + g_object_unref (data->io_stream); + + g_free (data->hostname); + g_free (data->username); + g_free (data->password); + g_free (data->buffer); + + if (data->data) + g_object_unref (data->data); + + if (data->cancellable) + g_object_unref (data->cancellable); + + g_slice_free (ConnectAsyncData, data); +} + +static void +complete_async_from_error (ConnectAsyncData *data, GError *error) +{ + GSimpleAsyncResult *simple = data->simple; + g_simple_async_result_set_from_error (data->simple, + error); + g_error_free (error); + g_simple_async_result_set_op_res_gpointer (simple, NULL, NULL); + g_simple_async_result_complete (simple); + g_object_unref (simple); +} + +static void +do_write (GAsyncReadyCallback callback, ConnectAsyncData *data) +{ + GOutputStream *out; + out = g_io_stream_get_output_stream (data->io_stream); + g_output_stream_write_async (out, + data->buffer + data->offset, + data->length - data->offset, + G_PRIORITY_DEFAULT, data->cancellable, + callback, data); +} + +static void +wocky_http_proxy_connect_async (GProxy *proxy, + GIOStream *io_stream, + GProxyAddress *proxy_address, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *simple; + ConnectAsyncData *data; + GInputStream *in; + + simple = g_simple_async_result_new (G_OBJECT (proxy), + callback, user_data, + wocky_http_proxy_connect_async); + + data = g_slice_new0 (ConnectAsyncData); + + data->simple = simple; + data->io_stream = g_object_ref (io_stream); + + if (cancellable) + data->cancellable = g_object_ref (cancellable); + + g_object_get (G_OBJECT (proxy_address), + "destination-hostname", &data->hostname, + "destination-port", &data->port, + "username", &data->username, + "password", &data->password, + NULL); + + in = g_io_stream_get_input_stream (io_stream); + + data->data = g_data_input_stream_new (in); + g_filter_input_stream_set_close_base_stream (G_FILTER_INPUT_STREAM (data->data), + FALSE); + + g_simple_async_result_set_op_res_gpointer (simple, data, + (GDestroyNotify) free_connect_data); + + data->buffer = create_request (data->hostname, data->port); + data->length = strlen (data->buffer); + data->offset = 0; + + do_write (request_write_cb, data); +} + +static void +request_write_cb (GObject *source, + GAsyncResult *res, + gpointer user_data) +{ + GError *error = NULL; + ConnectAsyncData *data = user_data; + gssize written; + + written = g_output_stream_write_finish (G_OUTPUT_STREAM (source), + res, &error); + if (written < 0) + { + complete_async_from_error (data, error); + return; + } + + data->offset += written; + + if (data->offset == data->length) + { + g_free (data->buffer); + data->buffer = NULL; + + g_data_input_stream_read_until_async (data->data, + HTTP_END_MARKER, + G_PRIORITY_DEFAULT, + data->cancellable, + reply_read_cb, data); + + } + else + { + do_write (request_write_cb, data); + } +} + +static void +reply_read_cb (GObject *source, + GAsyncResult *res, + gpointer user_data) +{ + GError *error = NULL; + ConnectAsyncData *data = user_data; + + data->buffer = g_data_input_stream_read_until_finish (data->data, + res, NULL, &error); + + if (!data->buffer) + { + complete_async_from_error (data, error); + return; + } + + if (!check_reply (data->buffer, &error)) + { + complete_async_from_error (data, error); + return; + } + + g_simple_async_result_complete (data->simple); + g_object_unref (data->simple); +} + +static GIOStream * +wocky_http_proxy_connect_finish (GProxy *proxy, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result); + ConnectAsyncData *data = g_simple_async_result_get_op_res_gpointer (simple); + + if (g_simple_async_result_propagate_error (simple, error)) + return NULL; + + return g_object_ref (data->io_stream); +} + +static gboolean +wocky_http_proxy_supports_hostname (GProxy *proxy) +{ + return TRUE; +} + +static void +wocky_http_proxy_class_init (WockyHttpProxyClass *class) +{ + GObjectClass *object_class; + + object_class = (GObjectClass *) class; + object_class->finalize = wocky_http_proxy_finalize; +} + +static void +wocky_http_proxy_iface_init (GProxyInterface *proxy_iface) +{ + proxy_iface->connect = wocky_http_proxy_connect; + proxy_iface->connect_async = wocky_http_proxy_connect_async; + proxy_iface->connect_finish = wocky_http_proxy_connect_finish; + proxy_iface->supports_hostname = wocky_http_proxy_supports_hostname; +} diff --git a/wocky/wocky-http-proxy.h b/wocky/wocky-http-proxy.h new file mode 100644 index 0000000..0c27a70 --- /dev/null +++ b/wocky/wocky-http-proxy.h @@ -0,0 +1,43 @@ + /* wocky-http-proxy.h: Header for WockyHttpProxy + * + * Copyright (C) 2010 Collabora, Ltd. + * @author Nicolas Dufresne + * + * + * 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; either + * version 2.1 of the License, or (at your option) any later version. + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __WOCKY_HTTP_PROXY_H__ +#define __WOCKY_HTTP_PROXY_H__ + +#include + +G_BEGIN_DECLS + +#define WOCKY_TYPE_HTTP_PROXY (_wocky_http_proxy_get_type ()) +#define WOCKY_HTTP_PROXY(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), WOCKY_TYPE_HTTP_PROXY, WockyHttpProxy)) +#define WOCKY_HTTP_PROXY_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), WOCKY_TYPE_HTTP_PROXY, WockyHttpProxyClass)) +#define WOCKY_IS_HTTP_PROXY(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), WOCKY_TYPE_HTTP_PROXY)) +#define WOCKY_IS_HTTP_PROXY_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), WOCKY_TYPE_HTTP_PROXY)) +#define WOCKY_HTTP_PROXY_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), WOCKY_TYPE_HTTP_PROXY, WockyHttpProxyClass)) + +typedef struct _WockyHttpProxy WockyHttpProxy; +typedef struct _WockyHttpProxyClass WockyHttpProxyClass; + +GType _wocky_http_proxy_get_type (void); + +G_END_DECLS + +#endif /* __SOCKS5_PROXY_H__ */