* purple - Jabber Protocol Plugin * Purple is the legal property of its developers, whose names are too numerous * to list here. Please refer to the COPYRIGHT file distributed with this * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * 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 General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA #include <libsoup/soup.h> TODO: test, what happens, if the http server (BOSH server) doesn't support keep-alive (sends connection: close). #define JABBER_BOSH_SEND_DELAY 250 #define JABBER_BOSH_TIMEOUT 10 static gchar *jabber_bosh_useragent = NULL; struct _PurpleJabberBOSHConnection { SoupSession *payload_reqs; guint64 rid; /* Must be big enough to hold 2^53 - 1 */ static SoupMessage *jabber_bosh_connection_http_request_new( PurpleJabberBOSHConnection *conn, const GString *data); jabber_bosh_connection_session_create(PurpleJabberBOSHConnection *conn); jabber_bosh_connection_send_now(PurpleJabberBOSHConnection *conn); GHashTable *ui_info = purple_core_get_ui_info(); const gchar *ui_name = NULL; const gchar *ui_version = NULL; ui_name = g_hash_table_lookup(ui_info, "name"); ui_version = g_hash_table_lookup(ui_info, "version"); jabber_bosh_useragent = g_strdup_printf( "%s%s%s (libpurple " VERSION ")", ui_name, ui_version ? " " : "", ui_version ? ui_version : ""); jabber_bosh_useragent = g_strdup("libpurple " VERSION); void jabber_bosh_uninit(void) g_free(jabber_bosh_useragent); jabber_bosh_useragent = NULL; PurpleJabberBOSHConnection* jabber_bosh_connection_new(JabberStream *js, const gchar *url) PurpleJabberBOSHConnection *conn; GProxyResolver *resolver; account = purple_connection_get_account(js->gc); resolver = purple_proxy_get_proxy_resolver(account, &error); purple_debug_error("jabber-bosh", "Unable to get account proxy resolver: %s", url_p = soup_uri_new(url); if (!SOUP_URI_VALID_FOR_HTTP(url_p)) { purple_debug_error("jabber-bosh", "Unable to parse given BOSH URL: %s", g_object_unref(resolver); conn = g_new0(PurpleJabberBOSHConnection, 1); conn->payload_reqs = soup_session_new_with_options( SOUP_SESSION_PROXY_RESOLVER, resolver, SOUP_SESSION_TIMEOUT, JABBER_BOSH_TIMEOUT + 2, SOUP_SESSION_USER_AGENT, jabber_bosh_useragent, NULL); conn->url = g_strdup(url); conn->is_ssl = (url_p->scheme == SOUP_URI_SCHEME_HTTPS); conn->send_buff = g_string_new(NULL); * Random 64-bit integer masked off by 2^52 - 1. * This should produce a random integer in the range [0, 2^52). It's * unlikely we'll send enough packets in one session to overflow the 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); js->serverFQDN = g_strdup(url_p->host); g_object_unref(resolver); jabber_bosh_connection_session_create(conn); jabber_bosh_connection_destroy(PurpleJabberBOSHConnection *conn) if (conn == NULL || conn->is_terminating) conn->is_terminating = TRUE; purple_debug_info("jabber-bosh", "Terminating a session for %p\n", conn); jabber_bosh_connection_send_now(conn); g_source_remove(conn->send_timer); soup_session_abort(conn->payload_reqs); g_clear_object(&conn->payload_reqs); g_string_free(conn->send_buff, TRUE); jabber_bosh_connection_is_ssl(const PurpleJabberBOSHConnection *conn) jabber_bosh_connection_parse(PurpleJabberBOSHConnection *conn, g_return_val_if_fail(conn != NULL, NULL); g_return_val_if_fail(response != NULL, NULL); if (conn->is_terminating || purple_account_is_disconnecting( purple_connection_get_account(conn->js->gc))) if (!SOUP_STATUS_IS_SUCCESSFUL(response->status_code)) { gchar *tmp = g_strdup_printf(_("Unable to connect: %s"), response->reason_phrase); purple_connection_error(conn->js->gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp); root = purple_xmlnode_from_str(response->response_body->data, response->response_body->length); type = purple_xmlnode_get_attrib(root, "type"); if (purple_strequal(type, "terminate")) { purple_connection_error(conn->js->gc, PURPLE_CONNECTION_ERROR_OTHER_ERROR, _("The BOSH " "connection manager terminated your session.")); purple_xmlnode_free(root); jabber_bosh_connection_recv(SoupSession *session, SoupMessage *msg, PurpleJabberBOSHConnection *bosh_conn = user_data; PurpleXmlNode *node, *child; if (purple_debug_is_verbose() && purple_debug_is_unsafe()) { purple_debug_misc("jabber-bosh", "received: %s\n", msg->response_body->data); node = jabber_bosh_connection_parse(bosh_conn, msg); /* jabber_process_packet might free child */ PurpleXmlNode *next = child->next; if (child->type != PURPLE_XMLNODE_TYPE_TAG) { /* Workaround for non-compliant servers that don't stamp * the right xmlns on these packets. See #11315. xmlns = purple_xmlnode_get_namespace(child); if ((xmlns == NULL || purple_strequal(xmlns, NS_BOSH)) && (purple_strequal(child->name, "iq") || purple_strequal(child->name, "message") || purple_strequal(child->name, "presence"))) purple_xmlnode_set_namespace(child, NS_XMPP_CLIENT); jabber_process_packet(bosh_conn->js, &child); jabber_bosh_connection_send(bosh_conn, NULL); jabber_bosh_connection_send_now(PurpleJabberBOSHConnection *conn) g_return_if_fail(conn != NULL); if (conn->send_timer != 0) { g_source_remove(conn->send_timer); data = g_string_new(NULL); /* missing parameters: route, from, ack */ g_string_printf(data, "<body " "rid='%" G_GUINT64_FORMAT "' " "xmlns:xmpp='" NS_XMPP_BOSH "' ", if (conn->js->reinit && !conn->is_terminating) { g_string_append(data, "xmpp:restart='true'/>"); conn->js->reinit = FALSE; if (conn->is_terminating) g_string_append(data, "type='terminate' "); g_string_append_c(data, '>'); g_string_append_len(data, conn->send_buff->str, g_string_append(data, "</body>"); g_string_set_size(conn->send_buff, 0); if (purple_debug_is_verbose() && purple_debug_is_unsafe()) purple_debug_misc("jabber-bosh", "sending: %s\n", data->str); req = jabber_bosh_connection_http_request_new(conn, data); g_string_free(data, FALSE); if (conn->is_terminating) { soup_session_send_async(conn->payload_reqs, req, NULL, NULL, NULL); soup_session_queue_message(conn->payload_reqs, req, jabber_bosh_connection_recv, conn); jabber_bosh_connection_send_delayed(gpointer _conn) PurpleJabberBOSHConnection *conn = _conn; jabber_bosh_connection_send_now(conn); jabber_bosh_connection_send(PurpleJabberBOSHConnection *conn, g_return_if_fail(conn != NULL); g_string_append(conn->send_buff, data); if (conn->send_timer == 0) { conn->send_timer = g_timeout_add( jabber_bosh_connection_send_delayed, conn); jabber_bosh_connection_send_keepalive(PurpleJabberBOSHConnection *conn) g_return_if_fail(conn != NULL); jabber_bosh_connection_send_now(conn); jabber_bosh_version_check(const gchar *version, int major_req, int minor_min) dot = strchr(version, '.'); jabber_bosh_connection_session_created(SoupSession *session, SoupMessage *msg, PurpleJabberBOSHConnection *bosh_conn = user_data; PurpleXmlNode *node, *features; const gchar *sid, *ver, *inactivity_str; if (purple_debug_is_verbose() && purple_debug_is_unsafe()) { purple_debug_misc("jabber-bosh", "received (session creation): %s\n", msg->response_body->data); node = jabber_bosh_connection_parse(bosh_conn, msg); sid = purple_xmlnode_get_attrib(node, "sid"); ver = purple_xmlnode_get_attrib(node, "ver"); inactivity_str = purple_xmlnode_get_attrib(node, "inactivity"); /* requests = purple_xmlnode_get_attrib(node, "requests"); */ purple_connection_error(bosh_conn->js->gc, PURPLE_CONNECTION_ERROR_OTHER_ERROR, _("No BOSH session ID given")); purple_xmlnode_free(node); purple_debug_info("jabber-bosh", "Missing version in BOSH initiation\n"); } else if (!jabber_bosh_version_check(ver, 1, 6)) { purple_debug_error("jabber-bosh", "Unsupported BOSH version: %s\n", ver); purple_connection_error(bosh_conn->js->gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Unsupported version of BOSH protocol")); purple_xmlnode_free(node); purple_debug_misc("jabber-bosh", "Session created for %p\n", bosh_conn); bosh_conn->sid = g_strdup(sid); inactivity = atoi(inactivity_str); if (inactivity < 0 || inactivity > 3600) { purple_debug_warning("jabber-bosh", "Ignoring invalid " "inactivity value: %s\n", inactivity_str); inactivity -= 5; /* rounding */ bosh_conn->js->max_inactivity = inactivity; if (bosh_conn->js->inactivity_timer == 0) { purple_debug_misc("jabber-bosh", "Starting inactivity " "timer for %d secs (compensating for " "rounding)\n", inactivity); jabber_stream_restart_inactivity_timer(bosh_conn->js); jabber_stream_set_state(bosh_conn->js, JABBER_STREAM_AUTHENTICATING); /* FIXME: Depending on receiving features might break with some hosts */ features = purple_xmlnode_get_child(node, "features"); jabber_stream_features_parse(bosh_conn->js, features); purple_xmlnode_free(node); jabber_bosh_connection_send(bosh_conn, NULL); jabber_bosh_connection_session_create(PurpleJabberBOSHConnection *conn) purple_debug_misc("jabber-bosh", "Requesting Session Create for %p\n", data = g_string_new(NULL); /* missing optional parameters: route, from, ack */ g_string_printf(data, "<body content='text/xml; charset=utf-8' " "rid='%" G_GUINT64_FORMAT "' " "xmlns:xmpp='urn:xmpp:xbosh' " ++conn->rid, conn->js->user->domain, JABBER_BOSH_TIMEOUT); req = jabber_bosh_connection_http_request_new(conn, data); g_string_free(data, FALSE); soup_session_queue_message(conn->payload_reqs, req, jabber_bosh_connection_session_created, conn); jabber_bosh_connection_http_request_new(PurpleJabberBOSHConnection *conn, jabber_stream_restart_inactivity_timer(conn->js); req = soup_message_new("POST", conn->url); soup_message_set_request(req, "text/xml; charset=utf-8", SOUP_MEMORY_TAKE,