Merged in default (pull request #617)

Mon, 04 Nov 2019 01:18:24 +0000

author
Gary Kramlich <grim@reaperworld.com>
date
Mon, 04 Nov 2019 01:18:24 +0000
changeset 40112
52c9656e40ed
parent 40102
df0db1029678 (current diff)
parent 40111
3acd6bb854c4 (diff)
child 40117
bc6bb63ae605
child 40118
621987e8e765

Merged in default (pull request #617)

Convert XMPP stuff to GIO

Approved-by: Gary Kramlich

libpurple/protocols/jabber/bosh.c file | annotate | diff | comparison | revisions
--- a/libpurple/network.c	Sun Nov 03 09:22:21 2019 +0000
+++ b/libpurple/network.c	Mon Nov 04 01:18:24 2019 +0000
@@ -150,6 +150,29 @@
 	return "0.0.0.0";
 }
 
+static gchar *
+purple_network_get_local_system_ip_from_gio(GSocketConnection *sockconn)
+{
+	GSocketAddress *addr;
+	GInetSocketAddress *inetsockaddr;
+	gchar *ip;
+
+	addr = g_socket_connection_get_local_address(sockconn, NULL);
+	if ((inetsockaddr = G_INET_SOCKET_ADDRESS(addr)) != NULL) {
+		GInetAddress *inetaddr =
+		        g_inet_socket_address_get_address(inetsockaddr);
+		if (g_inet_address_get_family(inetaddr) == G_SOCKET_FAMILY_IPV4 &&
+		    !g_inet_address_get_is_loopback(inetaddr)) {
+			ip = g_inet_address_to_string(inetaddr);
+			g_object_unref(addr);
+			return ip;
+		}
+	}
+	g_object_unref(addr);
+
+	return g_strdup("0.0.0.0");
+}
+
 GList *
 purple_network_get_all_local_system_ips(void)
 {
@@ -286,6 +309,42 @@
 	return purple_network_get_local_system_ip(fd);
 }
 
+gchar *
+purple_network_get_my_ip_from_gio(GSocketConnection *sockconn)
+{
+	const gchar *ip = NULL;
+	PurpleStunNatDiscovery *stun;
+
+	/* Check if the user specified an IP manually */
+	if (!purple_prefs_get_bool("/purple/network/auto_ip")) {
+		ip = purple_network_get_public_ip();
+		/* Make sure the IP address entered by the user is valid */
+		if ((ip != NULL) && (purple_network_is_ipv4(ip))) {
+			return g_strdup(ip);
+		}
+	} else {
+		/* Check if STUN discovery was already done */
+		stun = purple_stun_discover(NULL);
+		if ((stun != NULL) && (stun->status == PURPLE_STUN_STATUS_DISCOVERED)) {
+			return g_strdup(stun->publicip);
+		}
+
+		/* Attempt to get the IP from a NAT device using UPnP */
+		ip = purple_upnp_get_public_ip();
+		if (ip != NULL) {
+			return g_strdup(ip);
+		}
+
+		/* Attempt to get the IP from a NAT device using NAT-PMP */
+		ip = purple_pmp_get_public_ip();
+		if (ip != NULL) {
+			return g_strdup(ip);
+		}
+	}
+
+	/* Just fetch the IP of the local system */
+	return purple_network_get_local_system_ip_from_gio(sockconn);
+}
 
 static void
 purple_network_set_upnp_port_mapping_cb(gboolean success, gpointer data)
--- a/libpurple/network.h	Sun Nov 03 09:22:21 2019 +0000
+++ b/libpurple/network.h	Mon Nov 04 01:18:24 2019 +0000
@@ -29,6 +29,7 @@
  */
 
 #include <glib.h>
+#include <gio/gio.h>
 
 G_BEGIN_DECLS
 
@@ -113,6 +114,23 @@
 const char *purple_network_get_my_ip(int fd);
 
 /**
+ * purple_network_get_my_ip_from_gio:
+ * @sockconn: The socket connection to use to help figure out the IP, or %NULL.
+ *
+ * Returns the IP address that should be used anywhere a
+ * public IP addresses is needed (listening for an incoming
+ * file transfer, etc).
+ *
+ * If the user has manually specified an IP address via
+ * preferences, then this IP is returned.  Otherwise the
+ * IP address returned by purple_network_get_local_system_ip_from_gio()
+ * is returned.
+ *
+ * Returns: The local IP address to be used.
+ */
+gchar *purple_network_get_my_ip_from_gio(GSocketConnection *sockconn);
+
+/**
  * purple_network_listen:
  * @port: The port number to bind to.  Must be greater than 0.
  * @socket_family: The protocol family of the socket.  This should be
--- a/libpurple/protocols/jabber/auth_cyrus.c	Sun Nov 03 09:22:21 2019 +0000
+++ b/libpurple/protocols/jabber/auth_cyrus.c	Mon Nov 04 01:18:24 2019 +0000
@@ -402,11 +402,22 @@
 jabber_cyrus_start(JabberStream *js, PurpleXmlNode *mechanisms,
                    PurpleXmlNode **reply, char **error)
 {
-	PurpleXmlNode *mechnode;
+	PurpleXmlNode *mechnode, *hostname;
 	JabberSaslState ret;
 
 	js->sasl_mechs = g_string_new("");
 	js->sasl_password = g_strdup(purple_connection_get_password(js->gc));
+	/* XEP-0233 says we should grab the hostname for Kerberos v5, but there
+	 * is no claim about other SASL mechanisms. Fortunately, most don't
+	 * care what we use, so just use the domainpart. */
+	hostname = purple_xmlnode_get_child_with_namespace(
+	        mechanisms, "hostname", NS_XMPP_SERVER_REGISTRATION);
+	if (hostname) {
+		js->serverFQDN = purple_xmlnode_get_data(hostname);
+	}
+	if (js->serverFQDN == NULL) {
+		js->serverFQDN = g_strdup(js->user->domain);
+	}
 
 	for(mechnode = purple_xmlnode_get_child(mechanisms, "mechanism"); mechnode;
 			mechnode = purple_xmlnode_get_next_twin(mechnode))
--- a/libpurple/protocols/jabber/bosh.c	Sun Nov 03 09:22:21 2019 +0000
+++ b/libpurple/protocols/jabber/bosh.c	Mon Nov 04 01:18:24 2019 +0000
@@ -134,12 +134,6 @@
 	conn->rid = (((guint64)g_random_int() << 32) | g_random_int());
 	conn->rid &= 0xFFFFFFFFFFFFFLL;
 
-	if (g_hostname_is_ip_address(url_p->host)) {
-		js->serverFQDN = g_strdup(js->user->domain);
-	} else {
-		js->serverFQDN = g_strdup(url_p->host);
-	}
-
 	soup_uri_free(url_p);
 	g_object_unref(resolver);
 
--- a/libpurple/protocols/jabber/jabber.c	Sun Nov 03 09:22:21 2019 +0000
+++ b/libpurple/protocols/jabber/jabber.c	Mon Nov 04 01:18:24 2019 +0000
@@ -35,6 +35,7 @@
 #include "proxy.h"
 #include "protocol.h"
 #include "purpleaccountoption.h"
+#include "purple-gio.h"
 #include "request.h"
 #include "server.h"
 #include "status.h"
@@ -222,12 +223,13 @@
 	PurpleAccount *account = NULL;
 	PurpleXmlNode *starttls = NULL;
 
-	/* It's a secure BOSH connection, just return FALSE and skip, without doing anything extra.
-	 * XEP-0206 (XMPP Over BOSH): The client SHOULD ignore any Transport Layer Security (TLS)
-	 * feature since BOSH channel encryption SHOULD be negotiated at the HTTP layer.
+	/* It's a secure BOSH connection, just return FALSE and skip, without doing
+	 * anything extra. XEP-0206 (XMPP Over BOSH): The client SHOULD ignore any
+	 * Transport Layer Security (TLS) feature since BOSH channel encryption
+	 * SHOULD be negotiated at the HTTP layer.
 	 *
-	 * Note: we are already receiving STARTTLS at this point from a SSL/TLS BOSH connection,
-	 * so it is not necessary to check if purple_ssl_is_supported().
+	 * Note: we are already receiving STARTTLS at this point from a SSL/TLS BOSH
+	 * connection, so it is not necessary to check if SSL is supported.
 	 */
 	if (js->bosh && jabber_bosh_connection_is_ssl(js->bosh)) {
 		return FALSE;
@@ -366,9 +368,10 @@
 				jabber_auth_handle_failure(js, *packet);
 		}
 	} else if (purple_strequal(xmlns, NS_XMPP_TLS)) {
-		if (js->state != JABBER_STREAM_INITIALIZING_ENCRYPTION || js->gsc)
+		if (js->state != JABBER_STREAM_INITIALIZING_ENCRYPTION ||
+		    G_IS_TLS_CONNECTION(js->stream)) {
 			purple_debug_warning("jabber", "Ignoring spurious %s\n", name);
-		else {
+		} else {
 			if (purple_strequal(name, "proceed"))
 				tls_init(js);
 			/* TODO: Handle <failure/>, I guess? */
@@ -378,52 +381,27 @@
 	}
 }
 
-static int jabber_do_send(JabberStream *js, const char *data, int len)
+static void
+jabber_push_bytes_cb(GObject *source, GAsyncResult *res, gpointer data)
 {
-	int ret;
-
-	if (js->gsc)
-		ret = purple_ssl_write(js->gsc, data, len);
-	else
-		ret = write(js->fd, data, len);
-
-	return ret;
-}
-
-static void jabber_send_cb(gpointer data, gint source, PurpleInputCondition cond)
-{
+	PurpleQueuedOutputStream *stream = PURPLE_QUEUED_OUTPUT_STREAM(source);
 	JabberStream *js = data;
-	const gchar *output = NULL;
-	int ret, writelen;
-
-	writelen = purple_circular_buffer_get_max_read(js->write_buffer);
-	output = purple_circular_buffer_get_output(js->write_buffer);
-
-	if (writelen == 0) {
-		purple_input_remove(js->writeh);
-		js->writeh = 0;
-		return;
+	gboolean result;
+	GError *error = NULL;
+
+	result = purple_queued_output_stream_push_bytes_finish(stream, res, &error);
+
+	if (!result) {
+		purple_queued_output_stream_clear_queue(stream);
+
+		g_prefix_error(&error, "%s", _("Lost connection with server: "));
+		purple_connection_take_error(js->gc, error);
 	}
-
-	ret = jabber_do_send(js, output, writelen);
-
-	if (ret < 0 && errno == EAGAIN)
-		return;
-	else if (ret <= 0) {
-		gchar *tmp = g_strdup_printf(_("Lost connection with server: %s"),
-				g_strerror(errno));
-		purple_connection_error(js->gc,
-			PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
-		g_free(tmp);
-		return;
-	}
-
-	purple_circular_buffer_mark_read(js->write_buffer, ret);
 }
 
 static gboolean do_jabber_send_raw(JabberStream *js, const char *data, int len)
 {
-	int ret;
+	GBytes *output;
 	gboolean success = TRUE;
 
 	g_return_val_if_fail(len > 0, FALSE);
@@ -431,39 +409,11 @@
 	if (js->state == JABBER_STREAM_CONNECTED)
 		jabber_stream_restart_inactivity_timer(js);
 
-	if (js->writeh == 0)
-		ret = jabber_do_send(js, data, len);
-	else {
-		ret = -1;
-		errno = EAGAIN;
-	}
-
-	if (ret < 0 && errno != EAGAIN) {
-		PurpleAccount *account = purple_connection_get_account(js->gc);
-		/*
-		 * The server may have closed the socket (on a stream error), so if
-		 * we're disconnecting, don't generate (possibly another) error that
-		 * (for some UIs) would mask the first.
-		 */
-		if (!purple_account_is_disconnecting(account)) {
-			gchar *tmp = g_strdup_printf(_("Lost connection with server: %s"),
-					g_strerror(errno));
-			purple_connection_error(js->gc,
-				PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
-			g_free(tmp);
-		}
-
-		success = FALSE;
-	} else if (ret < len) {
-		if (ret < 0)
-			ret = 0;
-		if (js->writeh == 0)
-			js->writeh = purple_input_add(
-				js->gsc ? js->gsc->fd : js->fd,
-				PURPLE_INPUT_WRITE, jabber_send_cb, js);
-		purple_circular_buffer_append(js->write_buffer,
-			data + ret, len - ret);
-	}
+	output = g_bytes_new(data, len);
+	purple_queued_output_stream_push_bytes_async(
+	        js->output, output, G_PRIORITY_DEFAULT, js->cancellable,
+	        jabber_push_bytes_cb, js);
+	g_bytes_unref(output);
 
 	return success;
 }
@@ -531,8 +481,7 @@
 	if (js->sasl_maxbuf>0) {
 		int pos = 0;
 
-		if (!js->gsc && js->fd<0)
-			g_return_if_reached();
+		g_return_if_fail(js->input != NULL);
 
 		while (pos < len) {
 			int towrite;
@@ -647,51 +596,21 @@
 }
 
 static void
-jabber_recv_cb_ssl(gpointer data, PurpleSslConnection *gsc,
-		PurpleInputCondition cond)
+jabber_recv_cb(GObject *stream, gpointer data)
 {
 	PurpleConnection *gc = data;
 	JabberStream *js = purple_connection_get_protocol_data(gc);
-	int len;
-	static char buf[4096];
+	gssize len;
+	gchar buf[4096];
+	GError *error = NULL;
 
 	PURPLE_ASSERT_CONNECTION_IS_VALID(gc);
 
-	while((len = purple_ssl_read(gsc, buf, sizeof(buf) - 1)) > 0) {
-		purple_connection_update_last_received(gc);
-		buf[len] = '\0';
-		purple_debug_misc("jabber", "Recv (ssl)(%d): %s", len, buf);
-		jabber_parser_process(js, buf, len);
-		if(js->reinit)
-			jabber_stream_init(js);
-	}
-
-	if(len < 0 && errno == EAGAIN)
-		return;
-	else {
-		gchar *tmp;
-		if (len == 0)
-			tmp = g_strdup(_("Server closed the connection"));
-		else
-			tmp = g_strdup_printf(_("Lost connection with server: %s"),
-					g_strerror(errno));
-		purple_connection_error(js->gc,
-			PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
-		g_free(tmp);
-	}
-}
-
-static void
-jabber_recv_cb(gpointer data, gint source, PurpleInputCondition condition)
-{
-	PurpleConnection *gc = data;
-	JabberStream *js = purple_connection_get_protocol_data(gc);
-	int len;
-	static char buf[4096];
-
-	PURPLE_ASSERT_CONNECTION_IS_VALID(gc);
-
-	if((len = read(js->fd, buf, sizeof(buf) - 1)) > 0) {
+	len = g_pollable_input_stream_read_nonblocking(
+	        G_POLLABLE_INPUT_STREAM(stream), buf, sizeof(buf) - 1,
+	        js->cancellable, &error);
+
+	if (len > 0) {
 		purple_connection_update_last_received(gc);
 #ifdef HAVE_CYRUS_SASL
 		if (js->sasl_maxbuf > 0) {
@@ -720,43 +639,20 @@
 		}
 #endif
 		buf[len] = '\0';
-		purple_debug_misc("jabber", "Recv (%d): %s", len, buf);
+		purple_debug_misc("jabber", "Recv (%" G_GSSIZE_FORMAT "): %s", len,
+		                  buf);
 		jabber_parser_process(js, buf, len);
 		if(js->reinit)
 			jabber_stream_init(js);
-	} else if(len < 0 && errno == EAGAIN) {
-		return;
-	} else {
-		gchar *tmp;
-		if (len == 0)
-			tmp = g_strdup(_("Server closed the connection"));
-		else
-			tmp = g_strdup_printf(_("Lost connection with server: %s"),
-					g_strerror(errno));
-		purple_connection_error(js->gc,
-			PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
-		g_free(tmp);
+	} else if (len == 0) {
+		purple_connection_error(js->gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+		                        _("Server closed the connection"));
+	} else if (error->code != G_IO_ERROR_WOULD_BLOCK &&
+	           error->code != G_IO_ERROR_CANCELLED) {
+		g_prefix_error(&error, "%s", _("Lost connection with server: "));
+		purple_connection_g_error(js->gc, error);
 	}
-}
-
-static void
-jabber_login_callback_ssl(gpointer data, PurpleSslConnection *gsc,
-		PurpleInputCondition cond)
-{
-	PurpleConnection *gc = data;
-	JabberStream *js;
-
-	PURPLE_ASSERT_CONNECTION_IS_VALID(gc);
-
-	js = purple_connection_get_protocol_data(gc);
-
-	if(js->state == JABBER_STREAM_CONNECTING)
-		jabber_send_raw(js, "<?xml version='1.0' ?>", -1);
-	jabber_stream_set_state(js, JABBER_STREAM_INITIALIZING);
-	purple_ssl_input_add(gsc, jabber_recv_cb_ssl, gc);
-
-	/* Tell the app that we're doing encryption */
-	jabber_stream_set_state(js, JABBER_STREAM_INITIALIZING_ENCRYPTION);
+	g_clear_error(&error);
 }
 
 static void
@@ -823,130 +719,184 @@
 }
 
 static void
-jabber_login_callback(gpointer data, gint source, const gchar *error)
+jabber_stream_connect_finish(JabberStream *js, GIOStream *stream)
+{
+	GSource *source;
+
+	js->stream = stream;
+	js->input = g_io_stream_get_input_stream(js->stream);
+	js->output = purple_queued_output_stream_new(
+	        g_io_stream_get_output_stream(js->stream));
+
+	if (js->state == JABBER_STREAM_CONNECTING) {
+		jabber_send_raw(js, "<?xml version='1.0' ?>", -1);
+	}
+
+	jabber_stream_set_state(js, JABBER_STREAM_INITIALIZING);
+	source = g_pollable_input_stream_create_source(
+	        G_POLLABLE_INPUT_STREAM(js->input), js->cancellable);
+	g_source_set_callback(source, (GSourceFunc)jabber_recv_cb, js->gc, NULL);
+	js->inpa = g_source_attach(source, NULL);
+}
+
+static void
+jabber_login_callback(GObject *source_object, GAsyncResult *res, gpointer data)
 {
-	PurpleConnection *gc = data;
-	JabberStream *js = purple_connection_get_protocol_data(gc);
-
-	if (source < 0) {
-		GResolver *resolver = g_resolver_get_default();
-		gchar *name = g_strdup_printf("_xmppconnect.%s", js->user->domain);
-
-		purple_debug_info("jabber", "Couldn't connect directly to %s.  Trying to find alternative connection methods, like BOSH.\n", js->user->domain);
-
-		g_resolver_lookup_records_async(resolver,
-		                                name,
-		                                G_RESOLVER_RECORD_TXT,
-		                                js->cancellable,
-		                                txt_resolved_cb,
-		                                js);
+	GSocketClient *client = G_SOCKET_CLIENT(source_object);
+	JabberStream *js = data;
+	GSocketConnection *conn;
+	GIOStream *stream;
+	gboolean is_old_ssl = g_socket_client_get_tls(client);
+	GError *error = NULL;
+
+	conn = g_socket_client_connect_to_host_finish(client, res, &error);
+	if (conn == NULL) {
+		GResolver *resolver;
+		gchar *name;
+
+		if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+			g_error_free(error);
+			return;
+		} else if (is_old_ssl) {
+			/* Old-style SSL only makes a direct connection, or fails. */
+			purple_connection_take_error(js->gc, error);
+			return;
+		}
+		g_error_free(error);
+
+		name = g_strdup_printf("_xmppconnect.%s", js->user->domain);
+		purple_debug_info("jabber",
+		                  "Couldn't connect directly to %s.  Trying to find "
+		                  "alternative connection methods, like BOSH.\n",
+		                  js->user->domain);
+
+		resolver = g_resolver_get_default();
+		g_resolver_lookup_records_async(resolver, name, G_RESOLVER_RECORD_TXT,
+		                                js->cancellable, txt_resolved_cb, js);
 		g_free(name);
 		g_object_unref(resolver);
 
 		return;
 	}
 
-	js->fd = source;
-
-	if(js->state == JABBER_STREAM_CONNECTING)
-		jabber_send_raw(js, "<?xml version='1.0' ?>", -1);
-
-	jabber_stream_set_state(js, JABBER_STREAM_INITIALIZING);
-	js->inpa = purple_input_add(js->fd, PURPLE_INPUT_READ, jabber_recv_cb, gc);
+	if (is_old_ssl) {
+		stream = G_IO_STREAM(g_tcp_wrapper_connection_get_base_io_stream(
+		        G_TCP_WRAPPER_CONNECTION(conn)));
+	} else {
+		stream = G_IO_STREAM(conn);
+	}
+
+	jabber_stream_connect_finish(js, stream);
+
+	if (is_old_ssl) {
+		/* Tell the app that we're doing encryption */
+		jabber_stream_set_state(js, JABBER_STREAM_INITIALIZING_ENCRYPTION);
+	}
 }
 
 static void
-jabber_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
-		gpointer data)
+tls_handshake_cb(GObject *source_object, GAsyncResult *res, gpointer data)
 {
-	PurpleConnection *gc = data;
-	JabberStream *js;
-
-	PURPLE_ASSERT_CONNECTION_IS_VALID(gc);
-
-	js = purple_connection_get_protocol_data(gc);
-	js->gsc = NULL;
-
-	purple_connection_ssl_error (gc, error);
+	JabberStream *js = data;
+	GError *error = NULL;
+
+	if (!g_tls_connection_handshake_finish(G_TLS_CONNECTION(source_object), res,
+	                                       &error)) {
+		if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+			/* Connection already closed/freed. Escape. */
+		} else if (g_error_matches(error, G_TLS_ERROR, G_TLS_ERROR_HANDSHAKE)) {
+			/* In Gio, a handshake error is because of the cert */
+			purple_connection_ssl_error(js->gc, PURPLE_SSL_CERTIFICATE_INVALID);
+		} else {
+			/* Report any other errors as handshake failing */
+			purple_connection_ssl_error(js->gc, PURPLE_SSL_HANDSHAKE_FAILED);
+		}
+
+		g_error_free(error);
+		return;
+	}
+
+	jabber_stream_connect_finish(js, js->stream);
+
+	/* Tell the app that we're doing encryption */
+	jabber_stream_set_state(js, JABBER_STREAM_INITIALIZING_ENCRYPTION);
 }
 
 static void tls_init(JabberStream *js)
 {
-	purple_input_remove(js->inpa);
+	GSocketConnectable *identity;
+	GIOStream *tls_conn;
+	GError *error = NULL;
+
+	g_source_remove(js->inpa);
 	js->inpa = 0;
-	js->gsc = purple_ssl_connect_with_host_fd(purple_connection_get_account(js->gc), js->fd,
-			jabber_login_callback_ssl, jabber_ssl_connect_failure, js->certificate_CN, js->gc);
-	/* The fd is no longer our concern */
-	js->fd = -1;
-}
-
-static gboolean jabber_login_connect(JabberStream *js, const char *domain, const char *host, int port,
-				 gboolean fatal_failure)
-{
-	/* host should be used in preference to domain to
-	 * allow SASL authentication to work with FQDN of the server,
-	 * but we use domain as fallback for when users enter IP address
-	 * in connect server */
-	g_free(js->serverFQDN);
-	if (g_hostname_is_ip_address(host)) {
-		js->serverFQDN = g_strdup(domain);
-	} else {
-		js->serverFQDN = g_strdup(host);
+	js->input = NULL;
+	g_filter_output_stream_set_close_base_stream(
+	        G_FILTER_OUTPUT_STREAM(js->output), FALSE);
+	g_output_stream_close(G_OUTPUT_STREAM(js->output), js->cancellable, NULL);
+	js->output = NULL;
+
+	identity = g_network_address_new(js->certificate_CN, 0);
+	tls_conn = g_tls_client_connection_new(js->stream, identity, &error);
+	g_object_unref(identity);
+
+	if (tls_conn == NULL) {
+		purple_debug_warning("jabber",
+		                     "Error creating TLS client connection: %s",
+		                     error->message);
+		g_clear_error(&error);
+		purple_connection_ssl_error(js->gc, PURPLE_SSL_CONNECT_FAILED);
+		return;
 	}
 
-	if (purple_proxy_connect(js->gc, purple_connection_get_account(js->gc),
-			host, port, jabber_login_callback, js->gc) == NULL) {
-		if (fatal_failure) {
-			purple_connection_error(js->gc,
-				PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
-				_("Unable to connect"));
-		}
-
-		return FALSE;
-	}
-
-	return TRUE;
+	g_clear_object(&js->stream);
+	js->stream = G_IO_STREAM(tls_conn);
+
+	g_tls_connection_handshake_async(G_TLS_CONNECTION(tls_conn),
+	                                 G_PRIORITY_DEFAULT, js->cancellable,
+	                                 tls_handshake_cb, js);
 }
 
 static void
-srv_resolved_cb(GObject *sender, GAsyncResult *result, gpointer data)
+srv_resolved_cb(GObject *source_object, GAsyncResult *result, gpointer data)
 {
-	GError *error = NULL;
-	GList *targets = NULL, *l = NULL;
+	GSocketClient *client = G_SOCKET_CLIENT(source_object);
 	JabberStream *js = data;
-
-	targets = g_resolver_lookup_service_finish(G_RESOLVER(sender),
-			result, &error);
-	if(error) {
-		purple_debug_warning("jabber",
-		                     "SRV lookup failed, proceeding with normal connection : %s",
-		                     error->message);
+	GSocketConnection *conn;
+	GError *error = NULL;
+
+	conn = g_socket_client_connect_to_service_finish(client, result, &error);
+	if (error) {
+		if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+			/* Do nothing; cancelled. */
+
+		} else if (g_error_matches(error, G_RESOLVER_ERROR,
+		                           G_RESOLVER_ERROR_NOT_FOUND)) {
+			/* If there was no response, then attempt fallback behaviour of XMPP
+			 * Core 3.2.2. */
+			purple_debug_warning(
+			        "jabber",
+			        "SRV lookup failed, proceeding with normal connection : %s",
+			        error->message);
+
+			g_socket_client_connect_to_host_async(
+			        js->client, js->user->domain,
+			        purple_account_get_int(
+			                purple_connection_get_account(js->gc), "port",
+			                5222),
+			        js->cancellable, jabber_login_callback, js);
+
+		} else {
+			/* If resolving failed or connecting failed, then just error out, as
+			 * in XMPP Core 3.2.1 step 8. */
+			purple_connection_g_error(js->gc, error);
+		}
 
 		g_error_free(error);
-
-		jabber_login_connect(js, js->user->domain, js->user->domain,
-				purple_account_get_int(purple_connection_get_account(js->gc), "port", 5222),
-				TRUE);
-
-	} else {
-		for(l = targets; l; l = l->next) {
-			GSrvTarget *target = (GSrvTarget *)l->data;
-			const gchar *hostname = g_srv_target_get_hostname(target);
-			guint port = g_srv_target_get_port(target);
-
-			if(jabber_login_connect(js, hostname, hostname, port, FALSE)) {
-				g_resolver_free_targets(targets);
-
-				return;
-			}
-		}
-
-		g_resolver_free_targets(targets);
-
-		jabber_login_connect(js, js->user->domain, js->user->domain,
-				purple_account_get_int(purple_connection_get_account(js->gc), "port", 5222),
-				TRUE);		
+		return;
 	}
+
+	jabber_stream_connect_finish(js, G_IO_STREAM(conn));
 }
 
 static JabberStream *
@@ -971,7 +921,6 @@
 	js = g_new0(JabberStream, 1);
 	purple_connection_set_protocol_data(gc, js);
 	js->gc = gc;
-	js->fd = -1;
 	js->http_conns = soup_session_new_with_options(SOUP_SESSION_PROXY_RESOLVER,
 	                                               resolver, NULL);
 	g_object_unref(resolver);
@@ -1039,7 +988,6 @@
 	js->chats = g_hash_table_new_full(g_str_hash, g_str_equal,
 			g_free, (GDestroyNotify)jabber_chat_free);
 	js->next_id = g_random_int();
-	js->write_buffer = purple_circular_buffer_new(512);
 	js->old_length = 0;
 	js->keepalive_timeout = 0;
 	js->max_inactivity = DEFAULT_INACTIVITY_TIME;
@@ -1071,6 +1019,7 @@
 			"connect_server", "");
 	const char *bosh_url = purple_account_get_string(account,
 			"bosh_url", "");
+	GError *error = NULL;
 
 	jabber_stream_set_state(js, JABBER_STREAM_CONNECTING);
 
@@ -1088,37 +1037,35 @@
 		return;
 	}
 
+	js->client = purple_gio_socket_client_new(account, &error);
+	if (js->client == NULL) {
+		purple_connection_take_error(gc, error);
+		return;
+	}
+
 	js->certificate_CN = g_strdup(connect_server[0] ? connect_server : js->user->domain);
 
 	/* if they've got old-ssl mode going, we probably want to ignore SRV lookups */
 	if (purple_strequal("old_ssl", purple_account_get_string(account, "connection_security", JABBER_DEFAULT_REQUIRE_TLS))) {
-		js->gsc = purple_ssl_connect(account, js->certificate_CN,
-				purple_account_get_int(account, "port", 5223),
-				jabber_login_callback_ssl, jabber_ssl_connect_failure, gc);
-		if (!js->gsc) {
-			purple_connection_error(gc,
-				PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT,
-				_("Unable to establish SSL connection"));
-		}
-
+		g_socket_client_set_tls(js->client, TRUE);
+		g_socket_client_connect_to_host_async(
+		        js->client, js->certificate_CN,
+		        purple_account_get_int(account, "port", 5223), js->cancellable,
+		        jabber_login_callback, js);
 		return;
 	}
 
 	/* no old-ssl, so if they've specified a connect server, we'll use that, otherwise we'll
 	 * invoke the magic of SRV lookups, to figure out host and port */
 	if(connect_server[0]) {
-		jabber_login_connect(js, js->user->domain, connect_server,
-				purple_account_get_int(account, "port", 5222), TRUE);
+		g_socket_client_connect_to_host_async(
+		        js->client, connect_server,
+		        purple_account_get_int(account, "port", 5222), js->cancellable,
+		        jabber_login_callback, js);
 	} else {
-		GResolver *resolver = g_resolver_get_default();
-		g_resolver_lookup_service_async(resolver,
-		                                "xmpp-client",
-		                                "tcp",
-		                                js->user->domain,
-		                                js->cancellable,
-		                                srv_resolved_cb,
-		                                js);
-		g_object_unref(resolver);
+		g_socket_client_connect_to_service_async(js->client, js->user->domain,
+		                                         "xmpp-client", js->cancellable,
+		                                         srv_resolved_cb, js);
 	}
 }
 
@@ -1649,19 +1596,21 @@
 	if (js->bosh) {
 		jabber_bosh_connection_destroy(js->bosh);
 		js->bosh = NULL;
-	} else if ((js->gsc && js->gsc->fd > 0) || js->fd > 0)
+	} else if (js->output != NULL) {
 		jabber_send_raw(js, "</stream:stream>", -1);
 
-	if(js->gsc) {
-		purple_ssl_close(js->gsc);
-	} else if (js->fd > 0) {
 		if(js->inpa) {
-			purple_input_remove(js->inpa);
+			g_source_remove(js->inpa);
 			js->inpa = 0;
 		}
-		close(js->fd);
+		purple_gio_graceful_close(js->stream, js->input,
+		                          G_OUTPUT_STREAM(js->output));
 	}
 
+	g_clear_object(&js->output);
+	g_clear_object(&js->input);
+	g_clear_object(&js->stream);
+
 	jabber_buddy_remove_all_pending_buddy_info_requests(js);
 
 	jabber_parser_free(js);
@@ -1697,10 +1646,6 @@
 	g_free(js->avatar_hash);
 	g_free(js->caps_hash);
 
-	if (js->write_buffer)
-		g_object_unref(G_OBJECT(js->write_buffer));
-	if(js->writeh)
-		purple_input_remove(js->writeh);
 	if (js->auth_mech && js->auth_mech->dispose)
 		js->auth_mech->dispose(js);
 #ifdef HAVE_CYRUS_SASL
@@ -1760,7 +1705,11 @@
 
 void jabber_stream_set_state(JabberStream *js, JabberStreamState state)
 {
-#define JABBER_CONNECT_STEPS ((js->gsc || js->state == JABBER_STREAM_INITIALIZING_ENCRYPTION) ? 9 : 5)
+#define JABBER_CONNECT_STEPS \
+	((G_IS_TLS_CONNECTION(js->stream) || \
+	  js->state == JABBER_STREAM_INITIALIZING_ENCRYPTION) \
+	         ? 9 \
+	         : 5)
 
 	js->state = state;
 	switch(state) {
@@ -1771,8 +1720,10 @@
 					JABBER_CONNECT_STEPS);
 			break;
 		case JABBER_STREAM_INITIALIZING:
-			purple_connection_update_progress(js->gc, _("Initializing Stream"),
-					js->gsc ? 5 : 2, JABBER_CONNECT_STEPS);
+			purple_connection_update_progress(
+			        js->gc, _("Initializing Stream"),
+			        G_IS_TLS_CONNECTION(js->stream) ? 5 : 2,
+			        JABBER_CONNECT_STEPS);
 			jabber_stream_init(js);
 			break;
 		case JABBER_STREAM_INITIALIZING_ENCRYPTION:
@@ -1780,12 +1731,16 @@
 											  6, JABBER_CONNECT_STEPS);
 			break;
 		case JABBER_STREAM_AUTHENTICATING:
-			purple_connection_update_progress(js->gc, _("Authenticating"),
-					js->gsc ? 7 : 3, JABBER_CONNECT_STEPS);
+			purple_connection_update_progress(
+			        js->gc, _("Authenticating"),
+			        G_IS_TLS_CONNECTION(js->stream) ? 7 : 3,
+			        JABBER_CONNECT_STEPS);
 			break;
 		case JABBER_STREAM_POST_AUTH:
-			purple_connection_update_progress(js->gc, _("Re-initializing Stream"),
-					(js->gsc ? 8 : 4), JABBER_CONNECT_STEPS);
+			purple_connection_update_progress(
+			        js->gc, _("Re-initializing Stream"),
+			        (G_IS_TLS_CONNECTION(js->stream) ? 8 : 4),
+			        JABBER_CONNECT_STEPS);
 
 			break;
 		case JABBER_STREAM_CONNECTED:
@@ -2109,7 +2064,7 @@
 gboolean jabber_stream_is_ssl(JabberStream *js)
 {
 	return (js->bosh && jabber_bosh_connection_is_ssl(js->bosh)) ||
-	       (!js->bosh && js->gsc);
+	       (!js->bosh && G_IS_TLS_CONNECTION(js->stream));
 }
 
 static gboolean
--- a/libpurple/protocols/jabber/jabber.h	Sun Nov 03 09:22:21 2019 +0000
+++ b/libpurple/protocols/jabber/jabber.h	Mon Nov 04 01:18:24 2019 +0000
@@ -67,6 +67,7 @@
 #include "media.h"
 #include "mediamanager.h"
 #include "protocol.h"
+#include "queuedoutputstream.h"
 #include "roomlist.h"
 #include "sslconn.h"
 
@@ -119,7 +120,6 @@
 
 struct _JabberStream
 {
-	int fd;
 	guint inpa;
 
 	GCancellable *cancellable;
@@ -196,7 +196,10 @@
 	JabberBuddy *user_jb;
 
 	PurpleConnection *gc;
-	PurpleSslConnection *gsc;
+	GSocketClient *client;
+	GIOStream *stream;
+	GInputStream *input;
+	PurpleQueuedOutputStream *output;
 
 	gboolean registration;
 
@@ -206,9 +209,6 @@
 
 	GSList *pending_buddy_info_requests;
 
-	PurpleCircularBuffer *write_buffer;
-	guint writeh;
-
 	gboolean reinit;
 
 	JabberCapabilities server_caps;
--- a/libpurple/protocols/jabber/namespaces.h	Sun Nov 03 09:22:21 2019 +0000
+++ b/libpurple/protocols/jabber/namespaces.h	Mon Nov 04 01:18:24 2019 +0000
@@ -89,6 +89,9 @@
 /* XEP-0231 BoB (Bits of Binary) */
 #define NS_BOB "urn:xmpp:bob"
 
+/* XEP-0233 XMPP Server Registration for use with Kerberos V5 */
+#define NS_XMPP_SERVER_REGISTRATION "urn:xmpp:domain-based-name:1"
+
 /* XEP-0237 Roster Versioning */
 #define NS_ROSTER_VERSIONING "urn:xmpp:features:rosterver"
 
--- a/libpurple/protocols/jabber/si.c	Sun Nov 03 09:22:21 2019 +0000
+++ b/libpurple/protocols/jabber/si.c	Mon Nov 04 01:18:24 2019 +0000
@@ -429,7 +429,7 @@
 	char buffer[42]; /* 40 for DST.ADDR + 2 bytes for port number*/
 	int len;
 	char *dstaddr, *hash;
-	const char *host;
+	gchar *host;
 
 	purple_debug_info("jabber", "in jabber_si_xfer_bytestreams_send_read_again_cb\n");
 
@@ -508,7 +508,8 @@
 	g_free(dstaddr);
 
 	g_free(jsx->rxqueue);
-	host = purple_network_get_my_ip(jsx->js->fd);
+	host = purple_network_get_my_ip_from_gio(
+	        G_SOCKET_CONNECTION(jsx->js->stream));
 
 	jsx->rxmaxlen = 5 + strlen(host) + 2;
 	jsx->rxqueue = g_malloc(jsx->rxmaxlen);
@@ -527,6 +528,8 @@
 		jabber_si_xfer_bytestreams_send_read_again_resp_cb, xfer));
 	jabber_si_xfer_bytestreams_send_read_again_resp_cb(xfer, source,
 		PURPLE_INPUT_WRITE);
+
+	g_free(host);
 }
 
 static void
@@ -857,7 +860,7 @@
 		gchar *jid;
 		GList *local_ips =
 			purple_network_get_all_local_system_ips();
-		const char *public_ip;
+		gchar *public_ip;
 		gboolean has_public_ip = FALSE;
 
 		jsx->local_streamhost_fd = sock;
@@ -867,7 +870,8 @@
 		purple_xfer_set_local_port(xfer, purple_network_get_port_from_fd(sock));
 		g_snprintf(port, sizeof(port), "%hu", purple_xfer_get_local_port(xfer));
 
-		public_ip = purple_network_get_my_ip(jsx->js->fd);
+		public_ip = purple_network_get_my_ip_from_gio(
+		        G_SOCKET_CONNECTION(jsx->js->stream));
 
 		/* Include the localhost's IPs (for in-network transfers) */
 		while (local_ips) {
@@ -893,6 +897,7 @@
 		}
 
 		g_free(jid);
+		g_free(public_ip);
 
 		/* The listener for the local proxy */
 		purple_xfer_set_watcher(xfer, purple_input_add(sock, PURPLE_INPUT_READ,

mercurial