From 7828cf31e06ed0a90b1ee5af83ac3b0ee526df33 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 1 Nov 2019 02:38:50 -0400 Subject: xmpp: Convert unencrypted connections to pure GIO. --- libpurple/network.c | 61 +++++++++++ libpurple/network.h | 22 ++++ libpurple/protocols/jabber/jabber.c | 197 ++++++++++++++++++------------------ libpurple/protocols/jabber/jabber.h | 5 +- libpurple/protocols/jabber/si.c | 6 +- 5 files changed, 188 insertions(+), 103 deletions(-) diff --git a/libpurple/network.c b/libpurple/network.c index b4b661ed15..3ca6cdba4a 100644 --- a/libpurple/network.c +++ b/libpurple/network.c @@ -150,6 +150,31 @@ purple_network_get_local_system_ip(int fd) return "0.0.0.0"; } +static const char * +purple_network_get_local_system_ip_from_gio(GSocketConnection *sockconn) +{ + GSocketAddress *addr; + GInetSocketAddress *inetsockaddr; + static char ip[16]; + + strcpy(ip, "0.0.0.0"); + + 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)) { + gchar *tmp = g_inet_address_to_string(inetaddr); + g_snprintf(ip, 16, "%s", tmp); + g_free(tmp); + } + } + g_object_unref(addr); + + return ip; +} + GList * purple_network_get_all_local_system_ips(void) { @@ -286,6 +311,42 @@ purple_network_get_my_ip(int fd) return purple_network_get_local_system_ip(fd); } +const char * +purple_network_get_my_ip_from_gio(GSocketConnection *sockconn) +{ + const char *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 ip; + } + } else { + /* Check if STUN discovery was already done */ + stun = purple_stun_discover(NULL); + if ((stun != NULL) && (stun->status == PURPLE_STUN_STATUS_DISCOVERED)) { + return stun->publicip; + } + + /* Attempt to get the IP from a NAT device using UPnP */ + ip = purple_upnp_get_public_ip(); + if (ip != NULL) { + return ip; + } + + /* Attempt to get the IP from a NAT device using NAT-PMP */ + ip = purple_pmp_get_public_ip(); + if (ip != NULL) { + return 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) diff --git a/libpurple/network.h b/libpurple/network.h index 6c93ab71ce..48f526615c 100644 --- a/libpurple/network.h +++ b/libpurple/network.h @@ -29,6 +29,7 @@ */ #include +#include G_BEGIN_DECLS @@ -112,6 +113,27 @@ GList *purple_network_get_all_local_system_ips(void); */ 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. + * + * Note: The returned string is a pointer to a static buffer. If this + * function is called twice, it may be important to make a copy + * of the returned string. + * + * Returns: The local IP address to be used. + */ +const char *purple_network_get_my_ip_from_gio(GSocketConnection *sockconn); + /** * purple_network_listen: * @port: The port number to bind to. Must be greater than 0. diff --git a/libpurple/protocols/jabber/jabber.c b/libpurple/protocols/jabber/jabber.c index b445ad453a..3f65f77b0b 100644 --- a/libpurple/protocols/jabber/jabber.c +++ b/libpurple/protocols/jabber/jabber.c @@ -379,18 +379,6 @@ void jabber_process_packet(JabberStream *js, PurpleXmlNode **packet) } } -static int jabber_do_send(JabberStream *js, const char *data, int len) -{ - 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) { JabberStream *js = data; @@ -406,7 +394,7 @@ static void jabber_send_cb(gpointer data, gint source, PurpleInputCondition cond return; } - ret = jabber_do_send(js, output, writelen); + ret = purple_ssl_write(js->gsc, output, writelen); if (ret < 0 && errno == EAGAIN) return; @@ -422,6 +410,24 @@ static void jabber_send_cb(gpointer data, gint source, PurpleInputCondition cond purple_circular_buffer_mark_read(js->write_buffer, ret); } +static void +jabber_push_bytes_cb(GObject *source, GAsyncResult *res, gpointer data) +{ + PurpleQueuedOutputStream *stream = PURPLE_QUEUED_OUTPUT_STREAM(source); + JabberStream *js = data; + 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); + } +} + static gboolean do_jabber_send_raw(JabberStream *js, const char *data, int len) { int ret; @@ -432,9 +438,18 @@ static gboolean do_jabber_send_raw(JabberStream *js, const char *data, int len) if (js->state == JABBER_STREAM_CONNECTED) jabber_stream_restart_inactivity_timer(js); - if (js->writeh == 0) - ret = jabber_do_send(js, data, len); - else { + if (js->gsc == NULL) { + GBytes *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; + } + + if (js->writeh == 0) { + ret = purple_ssl_write(js->gsc, data, len); + } else { ret = -1; errno = EAGAIN; } @@ -458,10 +473,10 @@ static gboolean do_jabber_send_raw(JabberStream *js, const char *data, int len) } 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); + if (js->writeh == 0) { + js->writeh = purple_input_add(js->gsc->fd, PURPLE_INPUT_WRITE, + jabber_send_cb, js); + } purple_circular_buffer_append(js->write_buffer, data + ret, len - ret); } @@ -532,8 +547,9 @@ void jabber_send_raw(JabberStream *js, const char *data, int len) if (js->sasl_maxbuf>0) { int pos = 0; - if (!js->gsc && js->fd<0) + if (!js->gsc && js->input == NULL) { g_return_if_reached(); + } while (pos < len) { int towrite; @@ -683,16 +699,21 @@ jabber_recv_cb_ssl(gpointer data, PurpleSslConnection *gsc, } static void -jabber_recv_cb(gpointer data, gint source, PurpleInputCondition condition) +jabber_recv_cb(GObject *stream, gpointer data) { PurpleConnection *gc = data; JabberStream *js = purple_connection_get_protocol_data(gc); - int len; + gssize len; static char buf[4096]; + GError *error = NULL; 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) { @@ -721,23 +742,20 @@ jabber_recv_cb(gpointer data, gint source, PurpleInputCondition condition) } #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); } + g_clear_error(&error); } static void @@ -823,34 +841,13 @@ txt_resolved_cb(GObject *sender, GAsyncResult *result, gpointer data) } } -/* Grabbed duplicate_fd() from GLib's testcases (gio/tests/socket.c). - * Can be dropped once this prpl has been fully converted to Gio. - */ -static int -duplicate_fd(int fd) -{ -#ifdef G_OS_WIN32 - HANDLE newfd; - - if (!DuplicateHandle(GetCurrentProcess(), (HANDLE)fd, GetCurrentProcess(), - &newfd, 0, FALSE, DUPLICATE_SAME_ACCESS)) { - return -1; - } - - return (int)newfd; -#else - return dup(fd); -#endif -} -/* End function grabbed from GLib */ - static void jabber_login_callback(GObject *source_object, GAsyncResult *res, gpointer data) { GSocketClient *client = G_SOCKET_CLIENT(source_object); JabberStream *js = data; GSocketConnection *conn; - GSocket *socket; + GSource *source; GError *error = NULL; conn = g_socket_client_connect_to_host_finish(client, res, &error); @@ -879,26 +876,20 @@ jabber_login_callback(GObject *source_object, GAsyncResult *res, gpointer data) return; } - socket = g_socket_connection_get_socket(conn); - g_assert(socket != NULL); - - /* Duplicate the file descriptor, and then free the connection. - * libpurple's proxy code doesn't keep an object around for the - * lifetime of the connection. Therefore, in order to not leak - * memory, the GSocketConnection must be freed here. In order - * to avoid the double close/free of the file descriptor, the - * file descriptor is duplicated. - */ - js->fd = duplicate_fd(g_socket_get_fd(socket)); - g_object_unref(conn); + js->stream = G_IO_STREAM(conn); + 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, "", -1); } jabber_stream_set_state(js, JABBER_STREAM_INITIALIZING); - js->inpa = - purple_input_add(js->fd, PURPLE_INPUT_READ, jabber_recv_cb, js->gc); + 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 @@ -918,22 +909,30 @@ jabber_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error, static void tls_init(JabberStream *js) { + GSocket *socket; + gint fd; + + socket = g_socket_connection_get_socket(G_SOCKET_CONNECTION(js->stream)); + g_assert(socket != NULL); + + fd = g_socket_get_fd(socket); + purple_input_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; + js->gsc = purple_ssl_connect_with_host_fd( + purple_connection_get_account(js->gc), fd, + jabber_login_callback_ssl, jabber_ssl_connect_failure, + js->certificate_CN, js->gc); } static void srv_resolved_cb(GObject *source_object, GAsyncResult *result, gpointer data) { GSocketClient *client = G_SOCKET_CLIENT(source_object); - GError *error = NULL; - GSocketConnection *conn; - GSocket *socket; JabberStream *js = data; + GSocketConnection *conn; + GSource *source; + GError *error = NULL; conn = g_socket_client_connect_to_service_finish(client, result, &error); if (error) { @@ -966,26 +965,20 @@ srv_resolved_cb(GObject *source_object, GAsyncResult *result, gpointer data) return; } - socket = g_socket_connection_get_socket(conn); - g_assert(socket != NULL); - - /* Duplicate the file descriptor, and then free the connection. - * libpurple's proxy code doesn't keep an object around for the - * lifetime of the connection. Therefore, in order to not leak - * memory, the GSocketConnection must be freed here. In order - * to avoid the double close/free of the file descriptor, the - * file descriptor is duplicated. - */ - js->fd = duplicate_fd(g_socket_get_fd(socket)); - g_object_unref(conn); + js->stream = G_IO_STREAM(conn); + 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, "", -1); } jabber_stream_set_state(js, JABBER_STREAM_INITIALIZING); - js->inpa = - purple_input_add(js->fd, PURPLE_INPUT_READ, jabber_recv_cb, js->gc); + 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 JabberStream * @@ -1010,7 +1003,6 @@ jabber_stream_new(PurpleAccount *account) 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); @@ -1691,19 +1683,24 @@ void jabber_close(PurpleConnection *gc) 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->gsc && js->gsc->fd > 0) || js->output != NULL) jabber_send_raw(js, "", -1); if(js->gsc) { purple_ssl_close(js->gsc); - } else if (js->fd > 0) { + } else if (js->output != NULL) { 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); @@ -1742,7 +1739,7 @@ void jabber_close(PurpleConnection *gc) if (js->write_buffer) g_object_unref(G_OBJECT(js->write_buffer)); if(js->writeh) - purple_input_remove(js->writeh); + g_source_remove(js->writeh); if (js->auth_mech && js->auth_mech->dispose) js->auth_mech->dispose(js); #ifdef HAVE_CYRUS_SASL diff --git a/libpurple/protocols/jabber/jabber.h b/libpurple/protocols/jabber/jabber.h index 543f7524bf..b4bda985c4 100644 --- a/libpurple/protocols/jabber/jabber.h +++ b/libpurple/protocols/jabber/jabber.h @@ -67,6 +67,7 @@ typedef struct _JabberStream JabberStream; #include "media.h" #include "mediamanager.h" #include "protocol.h" +#include "queuedoutputstream.h" #include "roomlist.h" #include "sslconn.h" @@ -119,7 +120,6 @@ typedef struct struct _JabberStream { - int fd; guint inpa; GCancellable *cancellable; @@ -197,6 +197,9 @@ struct _JabberStream PurpleConnection *gc; GSocketClient *client; + GIOStream *stream; + GInputStream *input; + PurpleQueuedOutputStream *output; PurpleSslConnection *gsc; gboolean registration; diff --git a/libpurple/protocols/jabber/si.c b/libpurple/protocols/jabber/si.c index 9b045d0c5c..1a3debaaff 100644 --- a/libpurple/protocols/jabber/si.c +++ b/libpurple/protocols/jabber/si.c @@ -508,7 +508,8 @@ jabber_si_xfer_bytestreams_send_read_again_cb(gpointer data, gint source, 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); @@ -867,7 +868,8 @@ jabber_si_xfer_bytestreams_listen_cb(int sock, gpointer data) 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) { -- cgit v1.2.1