/* * bytestream-factory.c - Source for GabbleBytestreamFactory * Copyright (C) 2007-2008 Collabora Ltd. * * 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 "bytestream-factory.h" #include #include #include #include #include #include #include #define DEBUG_FLAG GABBLE_DEBUG_BYTESTREAM #include "bytestream-ibb.h" #include "bytestream-iface.h" #include "bytestream-muc.h" #include "bytestream-multiple.h" #include "bytestream-socks5.h" #include "connection.h" #include "conn-util.h" #include "debug.h" #include "disco.h" #include "namespaces.h" #include "presence-cache.h" #include "private-tubes-factory.h" #include "util.h" G_DEFINE_TYPE (GabbleBytestreamFactory, gabble_bytestream_factory, G_TYPE_OBJECT); /* The number of proxy we'll try to have at a minimum in the cache. */ #define NB_MIN_SOCKS5_PROXIES 3 #define FALLBACK_PROXY_CACHE_SIZE 5 #define SOCKS5_PROXY_TIMEOUT 10 #define TELEPATHY_PROXIES_SERVICE "proxies.telepathy.im" /* The life time (in seconds) of the proxies list discovered using * TELEPATHY_PROXIES_SERVICE */ /* 6 hours */ #define PROXIES_LIST_LIFE_TIME 6 * 60 * 60 /* properties */ enum { PROP_CONNECTION = 1, LAST_PROPERTY }; typedef struct { gchar *jid; gchar *stream; } BytestreamIdentifier; typedef struct { const gchar *jid; const gchar *stream; } ConstBytestreamIdentifier; static gboolean bytestream_id_equal (gconstpointer v1, gconstpointer v2) { const ConstBytestreamIdentifier *left = v1; const ConstBytestreamIdentifier *right = v2; return (!tp_strdiff (left->jid, right->jid)) && (!tp_strdiff (left->stream, right->stream)); } static guint bytestream_id_hash (gconstpointer v) { const ConstBytestreamIdentifier *bsid = v; return g_str_hash (bsid->jid) ^ g_str_hash (bsid->stream); } static BytestreamIdentifier * bytestream_id_new (GabbleBytestreamIface *bytestream) { BytestreamIdentifier *bsid = g_slice_new (BytestreamIdentifier); g_object_get (bytestream, "stream-id", &(bsid->stream), "peer-jid", &(bsid->jid), NULL); return bsid; } static void bytestream_id_free (gpointer v) { BytestreamIdentifier *bsid = v; g_free (bsid->jid); g_free (bsid->stream); g_slice_free (BytestreamIdentifier, bsid); } static GabbleSocks5Proxy * gabble_socks5_proxy_new (const gchar *jid, const gchar *host, guint16 port) { GabbleSocks5Proxy *proxy; proxy = g_slice_new (GabbleSocks5Proxy); proxy->jid = g_strdup (jid); proxy->host = g_strdup (host); proxy->port = port; return proxy; } static void gabble_socks5_proxy_free (GabbleSocks5Proxy *proxy) { g_free (proxy->jid); g_free (proxy->host); g_slice_free (GabbleSocks5Proxy, proxy); } struct _GabbleBytestreamFactoryPrivate { GabbleConnection *conn; guint iq_si_cb; guint iq_ibb_cb; guint iq_socks5_cb; guint msg_data_cb; /* SI-initiated bytestreams - data sent by normal messages, IQs used to * open and close. * * BytestreamIdentifier -> GabbleBytestreamIBB */ GHashTable *ibb_bytestreams; /* BytestreamIdentifier -> GabbleBytestreamSocks5 */ GHashTable *socks5_bytestreams; /* MUC pseudo-IBB - data sent by groupchat messages, IQs not allowed. * * BytestreamIdentifier -> GabbleBytestreamMuc */ GHashTable *muc_bytestreams; /* SI-initiated bytestreams - real data sent through another bytestream. * * BytestreamIdentifier -> GabbleBytestreamMultiple */ GHashTable *multiple_bytestreams; /* List of GabbleSocks5Proxy discovered on the connection */ GSList *socks5_proxies; /* List of GabbleSocks5Proxy found using the fallback-socks5-proxies param */ GSList *socks5_fallback_proxies; /* List of SOCKS5's jids that have not been queried yet */ GSList *socks5_potential_proxies; /* Next proxy on socks5_potential_proxies that we'll query */ GSList *next_query; /* Time stamp of the proxies list received from TELEPATHY_PROXIES_SERVICE */ GTimeVal proxies_list_stamp; gboolean dispose_has_run; }; #define GABBLE_BYTESTREAM_FACTORY_GET_PRIVATE(obj) ((obj)->priv) static gboolean bytestream_factory_msg_data_cb ( WockyPorter *porter, WockyStanza *msg, gpointer user_data); static gboolean bytestream_factory_iq_si_cb ( WockyPorter *porter, WockyStanza *msg, gpointer user_data); static gboolean bytestream_factory_iq_ibb_cb ( WockyPorter *porter, WockyStanza *msg, gpointer user_data); static gboolean handle_socks5_query_iq ( WockyPorter *porter, WockyStanza *msg, gpointer user_data); static void query_proxies (GabbleBytestreamFactory *self, guint nb_proxies_needed); static GSList * randomize_g_slist (GSList *list); static void gabble_bytestream_factory_init (GabbleBytestreamFactory *self) { GabbleBytestreamFactoryPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GABBLE_TYPE_BYTESTREAM_FACTORY, GabbleBytestreamFactoryPrivate); self->priv = priv; priv->ibb_bytestreams = g_hash_table_new_full (bytestream_id_hash, bytestream_id_equal, bytestream_id_free, g_object_unref); priv->muc_bytestreams = g_hash_table_new_full (bytestream_id_hash, bytestream_id_equal, bytestream_id_free, g_object_unref); priv->socks5_bytestreams = g_hash_table_new_full (bytestream_id_hash, bytestream_id_equal, bytestream_id_free, g_object_unref); priv->multiple_bytestreams = g_hash_table_new_full (bytestream_id_hash, bytestream_id_equal, bytestream_id_free, g_object_unref); memset (&priv->proxies_list_stamp, 0, sizeof (GTimeVal)); } static gint cmp_proxy (gconstpointer a, gconstpointer b) { GabbleSocks5Proxy *proxy_a = (GabbleSocks5Proxy *) a; GabbleSocks5Proxy *proxy_b = (GabbleSocks5Proxy *) b; /* Streamhost are identified by their jid */ return strcmp (proxy_a->jid, proxy_b->jid); } static void add_proxy_to_list (GabbleBytestreamFactory *self, GabbleSocks5Proxy *proxy, gboolean fallback) { GabbleBytestreamFactoryPrivate *priv = GABBLE_BYTESTREAM_FACTORY_GET_PRIVATE ( self); GSList **list; GSList *found; if (fallback) { list = &priv->socks5_fallback_proxies; } else { list = &priv->socks5_proxies; } found = g_slist_find_custom (*list, proxy, cmp_proxy); if (found != NULL) { DEBUG ("%s SOCKS5 proxy (%s %s:%d) is already known; " "move it to the head of the list", fallback ? "Fallback": "Discovered", proxy->jid, proxy->host, proxy->port); *list = g_slist_delete_link (*list, found); } else { DEBUG ("Add %s SOCKS5 proxy: %s %s:%d", fallback ? "fallback": "discovered", proxy->jid, proxy->host, proxy->port); if (fallback && g_slist_length (*list) >= FALLBACK_PROXY_CACHE_SIZE) { GSList *last; GabbleSocks5Proxy *oldest; last = g_slist_last (*list); oldest = last->data; DEBUG ("Proxy cache is full, remove the oldest entry (%s)", oldest->jid); *list = g_slist_delete_link (*list, last); gabble_socks5_proxy_free (oldest); } } *list = g_slist_prepend (*list, proxy); } static void socks5_proxy_query_reply_cb (GabbleConnection *conn, WockyStanza *sent_msg, WockyStanza *reply_msg, GObject *obj, gpointer user_data) { GabbleBytestreamFactory *self = GABBLE_BYTESTREAM_FACTORY (obj); GabbleBytestreamFactoryPrivate *priv = GABBLE_BYTESTREAM_FACTORY_GET_PRIVATE ( self); WockyNode *query, *streamhost; const gchar *from; const gchar *jid, *host, *portstr; gint64 port; GabbleSocks5Proxy *proxy; gboolean fallback = GPOINTER_TO_INT (user_data); GSList *found = NULL; from = wocky_node_get_attribute ( wocky_stanza_get_top_node (reply_msg), "from"); if (from == NULL) goto fail; if (wocky_stanza_extract_errors (reply_msg, NULL, NULL, NULL, NULL)) goto fail; query = wocky_node_get_child_ns ( wocky_stanza_get_top_node (reply_msg), "query", NS_BYTESTREAMS); if (query == NULL) goto fail; streamhost = wocky_node_get_child (query, "streamhost"); if (streamhost == NULL) goto fail; jid = wocky_node_get_attribute (streamhost, "jid"); host = wocky_node_get_attribute (streamhost, "host"); portstr = wocky_node_get_attribute (streamhost, "port"); if (jid == NULL || host == NULL || portstr == NULL) goto fail; port = g_ascii_strtoll (portstr, NULL, 10); if (port <= 0 || port > G_MAXUINT16) goto fail; proxy = gabble_socks5_proxy_new (jid, host, port); add_proxy_to_list (self , proxy, fallback); return; fail: if (fallback && from != NULL) { /* Remove the buggy proxy so we won't query it anymore */ found = g_slist_find_custom (priv->socks5_potential_proxies, from, (GCompareFunc) strcmp); if (found != NULL) { DEBUG ("remove proxy %s", from); g_free (found->data); priv->socks5_potential_proxies = g_slist_delete_link ( priv->socks5_potential_proxies, found); } } /* Try to get another proxy as this one failed */ query_proxies (self, 1); } static void send_proxy_query (GabbleBytestreamFactory *self, const gchar *jid, gboolean fallback) { GabbleBytestreamFactoryPrivate *priv = GABBLE_BYTESTREAM_FACTORY_GET_PRIVATE ( self); WockyStanza *query; DEBUG ("send SOCKS5 query to %s", jid); query = wocky_stanza_build (WOCKY_STANZA_TYPE_IQ, WOCKY_STANZA_SUB_TYPE_GET, NULL, jid, '(', "query", ':', NS_BYTESTREAMS, ')', NULL); _gabble_connection_send_with_reply (priv->conn, query, socks5_proxy_query_reply_cb, G_OBJECT (self), GINT_TO_POINTER (fallback), NULL); g_object_unref (query); } static void disco_item_found_cb (GabbleDisco *disco, GabbleDiscoItem *item, GabbleBytestreamFactory *self) { if (tp_strdiff (item->category, "proxy") || tp_strdiff (item->type, "bytestreams")) return; send_proxy_query (self, item->jid, FALSE); } static void query_proxies (GabbleBytestreamFactory *self, guint nb_proxies_needed) { GabbleBytestreamFactoryPrivate *priv = GABBLE_BYTESTREAM_FACTORY_GET_PRIVATE ( self); guint i; /* We don't want to query more than once the same proxy */ nb_proxies_needed = MIN (nb_proxies_needed, g_slist_length (priv->socks5_potential_proxies)); for (i = 0; i < nb_proxies_needed; i++) { gchar *jid; if (priv->next_query == NULL) priv->next_query = priv->socks5_potential_proxies; jid = priv->next_query->data; send_proxy_query (self, jid, TRUE); priv->next_query = g_slist_next (priv->next_query); } } static void proxies_disco_cb (GabbleDisco *disco, GabbleDiscoRequest *request, const gchar *j, const gchar *n, WockyNode *query_result, GError *error, gpointer user_data) { GabbleBytestreamFactory *self = GABBLE_BYTESTREAM_FACTORY (user_data); GabbleBytestreamFactoryPrivate *priv = GABBLE_BYTESTREAM_FACTORY_GET_PRIVATE ( self); WockyNodeIter i; WockyNode *node; GSList *new_list = NULL; if (error != NULL) { DEBUG ("disco failed: %s", error->message); return; } wocky_node_iter_init (&i, query_result, "item", NULL); while (wocky_node_iter_next (&i, &node)) { const gchar *jid = wocky_node_get_attribute (node, "jid"); if (jid == NULL) continue; DEBUG ("Discovered proxy %s", jid); new_list = g_slist_prepend (new_list, g_strdup (jid)); } g_get_current_time (&priv->proxies_list_stamp); if (new_list == NULL) return; /* replace the old list by the new one */ g_slist_foreach (priv->socks5_potential_proxies, (GFunc) g_free, NULL); g_slist_free (priv->socks5_potential_proxies); /* randomize the list to not always use the same proxies */ priv->socks5_potential_proxies = randomize_g_slist (new_list); priv->next_query = priv->socks5_potential_proxies; gabble_bytestream_factory_query_socks5_proxies (self); } /* Query TELEPATHY_PROXIES_SERVICE to get a list of proxies */ static void get_proxies_list (GabbleBytestreamFactory *self) { GabbleBytestreamFactoryPrivate *priv = GABBLE_BYTESTREAM_FACTORY_GET_PRIVATE ( self); DEBUG ("Ask %s for proxies", TELEPATHY_PROXIES_SERVICE); gabble_disco_request (priv->conn->disco, GABBLE_DISCO_TYPE_ITEMS, TELEPATHY_PROXIES_SERVICE, NULL, proxies_disco_cb, self, G_OBJECT (self), NULL); } /* ask to the factory to try to find more proxies if needed */ void gabble_bytestream_factory_query_socks5_proxies (GabbleBytestreamFactory *self) { GabbleBytestreamFactoryPrivate *priv = GABBLE_BYTESTREAM_FACTORY_GET_PRIVATE ( self); guint nb_proxies_found; guint nb_proxies_needed; GTimeVal now; if (priv->socks5_potential_proxies == NULL) { DEBUG ("No proxies list; request one"); get_proxies_list (self); return; } g_get_current_time (&now); if (now.tv_sec - priv->proxies_list_stamp.tv_sec > PROXIES_LIST_LIFE_TIME) { DEBUG ("Proxies list has expired; request a new one"); get_proxies_list (self); } nb_proxies_found = g_slist_length (priv->socks5_proxies) + g_slist_length (priv->socks5_fallback_proxies); if (nb_proxies_found >= NB_MIN_SOCKS5_PROXIES) { DEBUG ("we already have discovered enough proxies (%u); " "request just one to refresh our cache", nb_proxies_found); nb_proxies_needed = 1; } else { nb_proxies_needed = NB_MIN_SOCKS5_PROXIES - nb_proxies_found; DEBUG ("Need %u more proxies", nb_proxies_needed); } query_proxies (self, nb_proxies_needed); } static GSList * randomize_g_slist (GSList *list) { guint len; guint i; GSList *new_head, *new_tail; len = g_slist_length (list); if (len <= 1) return list; i = g_random_int_range (0, len); if (i == 0) return list; /* Cut the list at the i th position and make it the new head of the * list */ new_tail = g_slist_nth (list, i - 1); g_assert (new_tail != NULL); new_head = new_tail->next; g_assert (new_head != NULL); new_tail->next = NULL; return g_slist_concat (new_head, list); } static void porter_available_cb ( GabbleConnection *conn, WockyPorter *porter, gpointer user_data) { GabbleBytestreamFactory *self = GABBLE_BYTESTREAM_FACTORY (user_data); GabbleBytestreamFactoryPrivate *priv = self->priv; priv->msg_data_cb = wocky_porter_register_handler_from_anyone (porter, WOCKY_STANZA_TYPE_MESSAGE, WOCKY_STANZA_SUB_TYPE_NONE, WOCKY_PORTER_HANDLER_PRIORITY_MAX, bytestream_factory_msg_data_cb, self, NULL); priv->iq_si_cb = wocky_porter_register_handler_from_anyone (porter, WOCKY_STANZA_TYPE_IQ, WOCKY_STANZA_SUB_TYPE_SET, WOCKY_PORTER_HANDLER_PRIORITY_MAX, bytestream_factory_iq_si_cb, self, '(', "si", ':', NS_SI, ')', NULL); priv->iq_ibb_cb = wocky_porter_register_handler_from_anyone (porter, WOCKY_STANZA_TYPE_IQ, WOCKY_STANZA_SUB_TYPE_NONE, WOCKY_PORTER_HANDLER_PRIORITY_MAX, bytestream_factory_iq_ibb_cb, self, NULL); priv->iq_socks5_cb = wocky_porter_register_handler_from_anyone (porter, WOCKY_STANZA_TYPE_IQ, WOCKY_STANZA_SUB_TYPE_SET, WOCKY_PORTER_HANDLER_PRIORITY_MAX, handle_socks5_query_iq, self, '(', "query", ':', NS_BYTESTREAMS, ')', NULL); } static void conn_status_changed_cb (GabbleConnection *conn, TpConnectionStatus status, TpConnectionStatusReason reason, gpointer user_data) { GabbleBytestreamFactory *self = GABBLE_BYTESTREAM_FACTORY (user_data); GabbleBytestreamFactoryPrivate *priv = GABBLE_BYTESTREAM_FACTORY_GET_PRIVATE ( self); if (status == TP_CONNECTION_STATUS_CONNECTED) { GStrv jids; guint i; /* we can't intialize socks5_potential_proxies in the constructor * because Connection's properties are not set yet at this point */ g_object_get (priv->conn, "fallback-socks5-proxies", &jids, NULL); for (i = 0; jids != NULL && jids[i] != NULL; i++) { priv->socks5_potential_proxies = g_slist_prepend ( priv->socks5_potential_proxies, g_strdup (jids[i])); } /* randomize the list to not always use the same proxies */ priv->socks5_potential_proxies = randomize_g_slist ( priv->socks5_potential_proxies); priv->next_query = priv->socks5_potential_proxies; g_strfreev (jids); } } static GObject * gabble_bytestream_factory_constructor (GType type, guint n_props, GObjectConstructParam *props) { GObject *obj; GabbleBytestreamFactory *self; GabbleBytestreamFactoryPrivate *priv; obj = G_OBJECT_CLASS (gabble_bytestream_factory_parent_class)-> constructor (type, n_props, props); self = GABBLE_BYTESTREAM_FACTORY (obj); priv = GABBLE_BYTESTREAM_FACTORY_GET_PRIVATE (self); /* Track SOCKS5 proxy available on the connection */ gabble_signal_connect_weak (priv->conn->disco, "item-found", G_CALLBACK (disco_item_found_cb), G_OBJECT (self)); gabble_signal_connect_weak (priv->conn, "status-changed", G_CALLBACK (conn_status_changed_cb), G_OBJECT (self)); tp_g_signal_connect_object (priv->conn, "porter-available", G_CALLBACK (porter_available_cb), self, 0); return obj; } static void gabble_bytestream_factory_dispose (GObject *object) { GabbleBytestreamFactory *self = GABBLE_BYTESTREAM_FACTORY (object); GabbleBytestreamFactoryPrivate *priv = GABBLE_BYTESTREAM_FACTORY_GET_PRIVATE (self); GSList *proxies, *l; if (priv->dispose_has_run) return; DEBUG ("dispose called"); priv->dispose_has_run = TRUE; if (priv->msg_data_cb != 0) { WockyPorter *porter = wocky_session_get_porter (priv->conn->session); wocky_porter_unregister_handler (porter, priv->msg_data_cb); wocky_porter_unregister_handler (porter, priv->iq_si_cb); wocky_porter_unregister_handler (porter, priv->iq_ibb_cb); wocky_porter_unregister_handler (porter, priv->iq_socks5_cb); } g_hash_table_unref (priv->ibb_bytestreams); priv->ibb_bytestreams = NULL; g_hash_table_unref (priv->muc_bytestreams); priv->muc_bytestreams = NULL; g_hash_table_unref (priv->socks5_bytestreams); priv->socks5_bytestreams = NULL; g_hash_table_unref (priv->multiple_bytestreams); priv->multiple_bytestreams = NULL; proxies = g_slist_concat (priv->socks5_proxies, priv->socks5_fallback_proxies); for (l = proxies; l != NULL; l = g_slist_next (l)) { GabbleSocks5Proxy *proxy = (GabbleSocks5Proxy *) l->data; gabble_socks5_proxy_free (proxy); } g_slist_free (proxies); priv->socks5_proxies = NULL; priv->socks5_fallback_proxies = NULL; g_slist_foreach (priv->socks5_potential_proxies, (GFunc) g_free, NULL); g_slist_free (priv->socks5_potential_proxies); priv->socks5_potential_proxies = NULL; if (G_OBJECT_CLASS (gabble_bytestream_factory_parent_class)->dispose) G_OBJECT_CLASS (gabble_bytestream_factory_parent_class)->dispose (object); } static void gabble_bytestream_factory_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { GabbleBytestreamFactory *self = GABBLE_BYTESTREAM_FACTORY (object); GabbleBytestreamFactoryPrivate *priv = GABBLE_BYTESTREAM_FACTORY_GET_PRIVATE (self); switch (property_id) { case PROP_CONNECTION: g_value_set_object (value, priv->conn); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void gabble_bytestream_factory_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { GabbleBytestreamFactory *self = GABBLE_BYTESTREAM_FACTORY (object); GabbleBytestreamFactoryPrivate *priv = GABBLE_BYTESTREAM_FACTORY_GET_PRIVATE (self); switch (property_id) { case PROP_CONNECTION: priv->conn = g_value_get_object (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void gabble_bytestream_factory_class_init ( GabbleBytestreamFactoryClass *gabble_bytestream_factory_class) { GObjectClass *object_class = G_OBJECT_CLASS (gabble_bytestream_factory_class); GParamSpec *param_spec; g_type_class_add_private (gabble_bytestream_factory_class, sizeof (GabbleBytestreamFactoryPrivate)); object_class->constructor = gabble_bytestream_factory_constructor; object_class->dispose = gabble_bytestream_factory_dispose; object_class->get_property = gabble_bytestream_factory_get_property; object_class->set_property = gabble_bytestream_factory_set_property; param_spec = g_param_spec_object ( "connection", "GabbleConnection object", "Gabble connection object that owns this bytestream factory object.", GABBLE_TYPE_CONNECTION, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_CONNECTION, param_spec); } static void remove_bytestream (GabbleBytestreamFactory *self, GabbleBytestreamIface *bytestream) { GabbleBytestreamFactoryPrivate *priv = GABBLE_BYTESTREAM_FACTORY_GET_PRIVATE (self); BytestreamIdentifier bsid = { NULL, NULL }; guint handle_type; GHashTable *table = NULL; g_object_get (bytestream, "stream-id", &(bsid.stream), "peer-jid", &(bsid.jid), "peer-handle-type", &handle_type, NULL); if (handle_type == TP_HANDLE_TYPE_ROOM) { table = priv->muc_bytestreams; } else { if (GABBLE_IS_BYTESTREAM_IBB (bytestream)) table = priv->ibb_bytestreams; else if (GABBLE_IS_BYTESTREAM_SOCKS5 (bytestream)) table = priv->socks5_bytestreams; else if (GABBLE_IS_BYTESTREAM_MULTIPLE (bytestream)) table = priv->multiple_bytestreams; } if (table == NULL) return; DEBUG ("removing bytestream: <%s> from <%s>", bsid.stream, bsid.jid); g_hash_table_remove (table, &bsid); g_free (bsid.stream); g_free (bsid.jid); } /** * streaminit_parse_request * * Parses a SI request, or returns FALSE if it can't be parsed. * * The items in the linked list of stream methods are borrowed * from the message; their lifetime is only as long as that of the * message. */ static gboolean streaminit_parse_request (WockyStanza *message, WockyNode *si, const gchar **profile, const gchar **from, const gchar **stream_id, const gchar **stream_init_id, const gchar **mime_type, GSList **stream_methods, gboolean *multiple) { WockyNode *iq = wocky_stanza_get_top_node (message); WockyNode *feature, *x, *si_multiple, *field; WockyNodeIter i, j; *stream_init_id = wocky_node_get_attribute (iq, "id"); *from = wocky_node_get_attribute (iq, "from"); if (*from == NULL) { STANZA_DEBUG (message, "got a message without a from field"); return FALSE; } /* Parse */ *stream_id = wocky_node_get_attribute (si, "id"); if (*stream_id == NULL) { STANZA_DEBUG (message, "got a SI request without a stream id field"); return FALSE; } *mime_type = wocky_node_get_attribute (si, "mime-type"); /* if no mime_type is defined, XEP-0095 says to assume "binary/octect-stream" * which is presumably a typo for "application/octet-stream" */ *profile = wocky_node_get_attribute (si, "profile"); if (*profile == NULL) { STANZA_DEBUG (message, "got a SI request without a profile field"); return FALSE; } /* Parse */ feature = wocky_node_get_child_ns (si, "feature", NS_FEATURENEG); if (feature == NULL) { STANZA_DEBUG (message, "got a SI request without a feature field"); return FALSE; } x = wocky_node_get_child_ns (feature, "x", NS_X_DATA); if (x == NULL) { STANZA_DEBUG (message, "got a SI request without a X data field"); return FALSE; } wocky_node_iter_init (&i, x, NULL, NULL); while (wocky_node_iter_next (&i, &field)) { WockyNode *stream_method; if (tp_strdiff (wocky_node_get_attribute (field, "var"), "stream-method")) /* some future field, ignore it */ continue; if (tp_strdiff (wocky_node_get_attribute (field, "type"), "list-single")) { STANZA_DEBUG (message, "SI request's stream-method field was " "not of type list-single"); return FALSE; } /* Get the stream methods offered */ *stream_methods = NULL; wocky_node_iter_init (&j, field, NULL, NULL); while (wocky_node_iter_next (&j, &stream_method)) { WockyNode *value; const gchar *stream_method_str; value = wocky_node_get_child (stream_method, "value"); if (value == NULL) continue; stream_method_str = value->content; if (!tp_strdiff (stream_method_str, "")) continue; DEBUG ("Got stream-method %s", stream_method_str); /* Append to the stream_methods list */ *stream_methods = g_slist_append (*stream_methods, (gchar *) stream_method_str); } /* no need to parse the rest of the fields, we've found the one we * wanted */ break; } if (*stream_methods == NULL) { STANZA_DEBUG (message, "got a SI request without stream method proposed"); return FALSE; } si_multiple = wocky_node_get_child_ns (si, "si-multiple", NS_SI_MULTIPLE); if (si_multiple == NULL) *multiple = FALSE; else *multiple = TRUE; return TRUE; } /** * gabble_bytestream_factory_make_stream_init_iq * * @full_jid: the full jid of the contact to whom we want to offer the stream * @stream_id: the stream ID of the new stream * @profile: the profile associated with the stream * * Create a SI request IQ as described in XEP-0095. * * The MIME type is not set - the receiving client will assume * application/octet-stream unless the caller sets a MIME type explicitly. */ WockyStanza * gabble_bytestream_factory_make_stream_init_iq (const gchar *full_jid, const gchar *stream_id, const gchar *profile) { return wocky_stanza_build (WOCKY_STANZA_TYPE_IQ, WOCKY_STANZA_SUB_TYPE_SET, NULL, full_jid, '(', "si", ':', NS_SI, '@', "id", stream_id, '@', "profile", profile, '(', "feature", ':', NS_FEATURENEG, '(', "x", ':', NS_X_DATA, '@', "type", "form", '(', "field", '@', "var", "stream-method", '@', "type", "list-single", '(', "option", '(', "value", '$', NS_BYTESTREAMS, ')', ')', '(', "option", '(', "value", '$', NS_IBB, ')', ')', ')', ')', ')', '(', "si-multiple", ':', NS_SI_MULTIPLE, ')', ')', NULL); } static gboolean stream_method_supported (const gchar *stream_method) { /* IBB */ if (!tp_strdiff (stream_method, NS_IBB)) return TRUE; /* Sock5 */ if (!tp_strdiff (stream_method, NS_BYTESTREAMS)) return TRUE; return FALSE; } static GabbleBytestreamMultiple *gabble_bytestream_factory_create_multiple ( GabbleBytestreamFactory *self, TpHandle peer_handle, const gchar *stream_id, const gchar *stream_init_id, const gchar *peer_resource, const gchar *self_jid, GabbleBytestreamState state); static GabbleBytestreamIBB *gabble_bytestream_factory_create_ibb ( GabbleBytestreamFactory *fac, TpHandle peer_handle, const gchar *stream_id, const gchar *stream_init_id, const gchar *peer_resource, GabbleBytestreamState state); static GabbleBytestreamSocks5 *gabble_bytestream_factory_create_socks5 ( GabbleBytestreamFactory *fac, TpHandle peer_handle, const gchar *stream_id, const gchar *stream_init_id, const gchar *peer_resource, const gchar *self_jid, GabbleBytestreamState state); static void si_tube_received (GabbleBytestreamFactory *self, WockyStanza *msg, WockyNode *si, GabbleBytestreamIface *bytestream, TpHandle peer_handle, TpHandle room_handle, const gchar *stream_id) { GabbleBytestreamFactoryPrivate *priv = GABBLE_BYTESTREAM_FACTORY_GET_PRIVATE (self); /* A Tubes SI request can be: * - a 1-1 new tube offer * - a 1-1 tube extra bytestream offer * - a muc tube extra bytestream offer */ if (wocky_node_get_child_ns (si, "tube", NS_TUBES) != NULL) { /* The SI request is a tube offer */ gabble_private_tubes_factory_handle_si_tube_request ( priv->conn->private_tubes_factory, bytestream, peer_handle, stream_id, msg); } else if (wocky_node_get_child_ns (si, "stream", NS_TUBES) != NULL) { /* The SI request is an extra bytestream for a 1-1 tube */ gabble_private_tubes_factory_handle_si_stream_request ( priv->conn->private_tubes_factory, bytestream, peer_handle, stream_id, msg); } else if (wocky_node_get_child_ns (si, "muc-stream", NS_TUBES) != NULL) { /* The SI request is an extra bytestream for a muc tube */ if (room_handle == 0) { GError e = { WOCKY_XMPP_ERROR, WOCKY_XMPP_ERROR_BAD_REQUEST, " is only valid in a MUC context" }; gabble_bytestream_iface_close (bytestream, &e); } else { gabble_muc_factory_handle_si_stream_request (priv->conn->muc_factory, bytestream, room_handle, stream_id, msg); } } else { GError e = { WOCKY_XMPP_ERROR, WOCKY_XMPP_ERROR_BAD_REQUEST, "Invalid tube SI request: expected , or " "" }; /* Invalid tube SI request */ STANZA_DEBUG (msg, e.message); gabble_bytestream_iface_close (bytestream, &e); } } /** * bytestream_factory_iq_si_cb: * * Called by Wocky when we get an incoming . This handler is concerned * with Stream Initiation requests (XEP-0095). * */ static gboolean bytestream_factory_iq_si_cb ( WockyPorter *porter, WockyStanza *msg, gpointer user_data) { GabbleBytestreamFactory *self = GABBLE_BYTESTREAM_FACTORY (user_data); GabbleBytestreamFactoryPrivate *priv = GABBLE_BYTESTREAM_FACTORY_GET_PRIVATE (self); TpHandleRepoIface *contact_repo = tp_base_connection_get_handles ( (TpBaseConnection *) priv->conn, TP_HANDLE_TYPE_CONTACT); TpHandleRepoIface *room_repo = tp_base_connection_get_handles ( (TpBaseConnection *) priv->conn, TP_HANDLE_TYPE_ROOM); WockyNode *si; TpHandle peer_handle = 0, room_handle; GabbleBytestreamIface *bytestream = NULL; GSList *l; const gchar *profile, *from, *stream_id, *stream_init_id, *mime_type; GSList *stream_methods = NULL; gboolean multiple; gchar *peer_resource = NULL; gchar *self_jid = NULL; si = wocky_node_get_child_ns ( wocky_stanza_get_top_node (msg), "si", NS_SI); g_return_val_if_fail (si != NULL, FALSE); /* after this point, the message is for us, so in all cases we either handle * it or send an error reply */ if (!streaminit_parse_request (msg, si, &profile, &from, &stream_id, &stream_init_id, &mime_type, &stream_methods, &multiple)) { wocky_porter_send_iq_error (porter, msg, WOCKY_XMPP_ERROR_BAD_REQUEST, "failed to parse SI request"); goto out; } DEBUG ("received a SI request"); room_handle = gabble_get_room_handle_from_jid (room_repo, from); if (room_handle == 0) { /* jid is not a muc jid so we need contact's resource */ if (!wocky_decode_jid (from, NULL, NULL, &peer_resource)) { DEBUG ("Got an SI IQ response from a bad JID. Ignoring."); goto out; } if (!peer_resource) { DEBUG ("Got an SI IQ response from a JID without a resource." "Ignoring."); goto out; } peer_handle = tp_handle_ensure (contact_repo, from, NULL, NULL); /* we are not in a muc so our own jid is the one in the 'to' attribute */ self_jid = g_strdup (wocky_node_get_attribute ( wocky_stanza_get_top_node (msg), "to")); } else { /* we are in a muc so need to get our muc jid */ GabbleMucChannel *muc; peer_handle = tp_handle_ensure (contact_repo, from, GUINT_TO_POINTER (GABBLE_JID_ROOM_MEMBER), NULL); muc = gabble_muc_factory_find_text_channel (priv->conn->muc_factory, room_handle); if (muc == NULL) { DEBUG ("Got an IQ from a muc in which we are not. Ignoring."); goto out; } g_object_get (muc, "self-jid", &self_jid, NULL); } if (peer_handle == 0) { wocky_porter_send_iq_error (porter, msg, WOCKY_XMPP_ERROR_JID_MALFORMED, NULL); goto out; } if (multiple) { DEBUG ("Receiver supports multi bytestreams"); bytestream = (GabbleBytestreamIface *) gabble_bytestream_factory_create_multiple (self, peer_handle, stream_id, stream_init_id, peer_resource, self_jid, GABBLE_BYTESTREAM_STATE_LOCAL_PENDING); } /* check stream method */ for (l = stream_methods; l != NULL; l = l->next) { if (multiple) { if (stream_method_supported (l->data)) { DEBUG ("add %s to multi-bytestream methods", (const gchar *) l->data); gabble_bytestream_multiple_add_stream_method ( GABBLE_BYTESTREAM_MULTIPLE (bytestream), l->data); } else { DEBUG ("skip unsupported stream method: %s", (const gchar *) l->data); } } else { /* We create the stream according the stream method chosen. * User has to accept it */ DEBUG ("Receiver doesn't support multi bytestreams. He chose %s", (const gchar *) l->data); bytestream = gabble_bytestream_factory_create_from_method (self, l->data, peer_handle, stream_id, stream_init_id, peer_resource, self_jid, GABBLE_BYTESTREAM_STATE_LOCAL_PENDING); if (bytestream != NULL) break; } } if (bytestream == NULL) { GError error = { WOCKY_SI_ERROR, WOCKY_SI_ERROR_NO_VALID_STREAMS, "" }; DEBUG ("SI request doesn't contain any supported stream methods."); wocky_porter_send_iq_gerror (porter, msg, &error); goto out; } if (multiple) { /* Is there at least one stream method? */ if (!gabble_bytestream_multiple_has_stream_method ( GABBLE_BYTESTREAM_MULTIPLE (bytestream))) { GError e = { WOCKY_SI_ERROR, WOCKY_SI_ERROR_NO_VALID_STREAMS, "" }; DEBUG ("No valid stream method in the multi bytestream. Closing"); gabble_bytestream_iface_close (bytestream, &e); goto out; } } /* Now that we have a bytestream, it's responsible for declining the IQ * if needed. */ /* We inform the right factory we received a SI request */ if (!tp_strdiff (profile, NS_TUBES)) { si_tube_received (self, msg, si, bytestream, peer_handle, room_handle, stream_id); } #ifdef ENABLE_FILE_TRANSFER else if (!tp_strdiff (profile, NS_FILE_TRANSFER)) { gabble_ft_manager_handle_si_request (priv->conn->ft_manager, bytestream, peer_handle, stream_id, msg); } #endif else { GError e = { WOCKY_SI_ERROR, WOCKY_SI_ERROR_BAD_PROFILE, "" }; DEBUG ("SI profile unsupported: %s", profile); gabble_bytestream_iface_close (bytestream, &e); } out: g_slist_free (stream_methods); g_free (peer_resource); g_free (self_jid); return TRUE; } static gboolean handle_ibb_open_iq (GabbleBytestreamFactory *self, WockyStanza *msg) { GabbleBytestreamFactoryPrivate *priv = GABBLE_BYTESTREAM_FACTORY_GET_PRIVATE (self); WockyPorter *porter = wocky_session_get_porter (priv->conn->session); GabbleBytestreamIBB *bytestream; WockyNode *open_node; ConstBytestreamIdentifier bsid = { NULL, NULL }; const gchar *tmp; guint state; WockyStanzaSubType sub_type; wocky_stanza_get_type_info (msg, NULL, &sub_type); if (sub_type != WOCKY_STANZA_SUB_TYPE_SET) return FALSE; open_node = wocky_node_get_child_ns ( wocky_stanza_get_top_node (msg), "open", NS_IBB); if (open_node == NULL) return FALSE; bsid.jid = wocky_node_get_attribute ( wocky_stanza_get_top_node (msg), "from"); if (bsid.jid == NULL) { DEBUG ("got a message without a from field"); wocky_porter_send_iq_error (porter, msg, WOCKY_XMPP_ERROR_BAD_REQUEST, NULL); return TRUE; } bsid.stream = wocky_node_get_attribute (open_node, "sid"); if (bsid.stream == NULL) { DEBUG ("IBB open stanza doesn't contain stream id"); wocky_porter_send_iq_error (porter, msg, WOCKY_XMPP_ERROR_BAD_REQUEST, NULL); return TRUE; } bytestream = g_hash_table_lookup (priv->ibb_bytestreams, &bsid); if (bytestream == NULL) { /* We don't accept streams not previously announced using SI */ DEBUG ("unknown stream: <%s> from <%s>", bsid.stream, bsid.jid); wocky_porter_send_iq_error (porter, msg, WOCKY_XMPP_ERROR_BAD_REQUEST, NULL); return TRUE; } g_object_get (bytestream, "state", &state, NULL); if (state != GABBLE_BYTESTREAM_STATE_ACCEPTED) { /* We don't accept streams not previously accepted using SI */ DEBUG ("unaccepted stream: <%s> from <%s>", bsid.stream, bsid.jid); wocky_porter_send_iq_error (porter, msg, WOCKY_XMPP_ERROR_BAD_REQUEST, NULL); return TRUE; } tmp = wocky_node_get_attribute (open_node, "block-size"); if (tmp != NULL) { guint block_size = strtoul (tmp, NULL, 10); if (block_size > 0) g_object_set (bytestream, "block-size", block_size, NULL); } g_object_set (bytestream, "state", GABBLE_BYTESTREAM_STATE_OPEN, NULL); wocky_porter_acknowledge_iq (porter, msg, NULL); return TRUE; } static gboolean handle_ibb_close_iq (GabbleBytestreamFactory *self, WockyStanza *msg) { GabbleBytestreamFactoryPrivate *priv = GABBLE_BYTESTREAM_FACTORY_GET_PRIVATE (self); WockyPorter *porter = wocky_session_get_porter (priv->conn->session); ConstBytestreamIdentifier bsid = { NULL, NULL }; GabbleBytestreamIBB *bytestream; WockyNode *close_node; WockyStanzaSubType sub_type; wocky_stanza_get_type_info (msg, NULL, &sub_type); if (sub_type != WOCKY_STANZA_SUB_TYPE_SET) return FALSE; close_node = wocky_node_get_child_ns ( wocky_stanza_get_top_node (msg), "close", NS_IBB); if (close_node == NULL) return FALSE; bsid.jid = wocky_node_get_attribute (wocky_stanza_get_top_node (msg), "from"); if (bsid.jid == NULL) { DEBUG ("got a message without a from field"); wocky_porter_send_iq_error (porter, msg, WOCKY_XMPP_ERROR_BAD_REQUEST, "IBB has no 'from' attribute"); return TRUE; } bsid.stream = wocky_node_get_attribute (close_node, "sid"); if (bsid.stream == NULL) { DEBUG ("IBB close stanza doesn't contain stream id"); wocky_porter_send_iq_error (porter, msg, WOCKY_XMPP_ERROR_BAD_REQUEST, "IBB has no stream ID"); return TRUE; } bytestream = g_hash_table_lookup (priv->ibb_bytestreams, &bsid); if (bytestream == NULL) { DEBUG ("unknown stream: <%s> from <%s>", bsid.stream, bsid.jid); wocky_porter_send_iq_error (porter, msg, WOCKY_XMPP_ERROR_ITEM_NOT_FOUND, NULL); } else { gabble_bytestream_ibb_close_received (bytestream, msg); } return TRUE; } /* IBB can be transported over either IQs or messages, so msg can either be * an or a . If it's an we need to reply to it. * * Return TRUE if we take responsibility for this message. */ static gboolean handle_ibb_data (GabbleBytestreamFactory *self, WockyStanza *msg, gboolean is_iq) { GabbleBytestreamFactoryPrivate *priv = GABBLE_BYTESTREAM_FACTORY_GET_PRIVATE (self); WockyPorter *porter = wocky_session_get_porter (priv->conn->session); GabbleBytestreamIBB *bytestream = NULL; WockyNode *data; ConstBytestreamIdentifier bsid = { NULL, NULL }; WockyStanzaSubType sub_type; priv = GABBLE_BYTESTREAM_FACTORY_GET_PRIVATE (self); wocky_stanza_get_type_info (msg, NULL, &sub_type); if (is_iq && sub_type != WOCKY_STANZA_SUB_TYPE_SET) return FALSE; data = wocky_node_get_child_ns ( wocky_stanza_get_top_node (msg), "data", NS_IBB); if (data == NULL) return FALSE; bsid.jid = wocky_node_get_attribute (wocky_stanza_get_top_node (msg), "from"); if (bsid.jid == NULL) { DEBUG ("got a message without a from field"); if (is_iq) wocky_porter_send_iq_error (porter, msg, WOCKY_XMPP_ERROR_BAD_REQUEST, "IBB has no 'from' attribute"); return TRUE; } bsid.stream = wocky_node_get_attribute (data, "sid"); if (bsid.stream == NULL) { DEBUG ("got a IBB message data without a stream id field"); if (is_iq) wocky_porter_send_iq_error (porter, msg, WOCKY_XMPP_ERROR_BAD_REQUEST, "IBB needs a stream ID"); return TRUE; } bytestream = g_hash_table_lookup (priv->ibb_bytestreams, &bsid); if (bytestream == NULL) { DEBUG ("unknown stream: <%s> from <%s>", bsid.stream, bsid.jid); if (is_iq) wocky_porter_send_iq_error (porter, msg, WOCKY_XMPP_ERROR_BAD_REQUEST, "IBB has unknown stream ID"); return TRUE; } gabble_bytestream_ibb_receive (bytestream, msg, is_iq); return TRUE; } static gboolean handle_muc_data (GabbleBytestreamFactory *self, WockyStanza *msg) { GabbleBytestreamFactoryPrivate *priv = GABBLE_BYTESTREAM_FACTORY_GET_PRIVATE (self); GabbleBytestreamMuc *bytestream = NULL; WockyNode *data; ConstBytestreamIdentifier bsid = { NULL, NULL }; gchar *room_name; const gchar *from; priv = GABBLE_BYTESTREAM_FACTORY_GET_PRIVATE (self); data = wocky_node_get_child_ns ( wocky_stanza_get_top_node (msg), "data", NS_MUC_BYTESTREAM); if (data == NULL) return FALSE; from = wocky_node_get_attribute ( wocky_stanza_get_top_node (msg), "from"); if (from == NULL) { DEBUG ("got a message without a from field"); return TRUE; } bsid.stream = wocky_node_get_attribute (data, "sid"); if (bsid.stream == NULL) { DEBUG ("got a pseudo IBB muc message data without a stream id field"); return TRUE; } room_name = gabble_remove_resource (from); bsid.jid = room_name; bytestream = g_hash_table_lookup (priv->muc_bytestreams, &bsid); if (bytestream == NULL) { DEBUG ("unknown muc stream: <%s> from <%s>", bsid.stream, bsid.jid); g_free (room_name); return TRUE; } g_assert (GABBLE_IS_BYTESTREAM_MUC (bytestream)); gabble_bytestream_muc_receive (bytestream, msg); g_free (room_name); return TRUE; } /** * bytestream_factory_iq_ibb_cb: * * Called by Wocky when we get an incoming . * This handler is concerned with IBB iq's. * */ static gboolean bytestream_factory_iq_ibb_cb ( WockyPorter *porter, WockyStanza *msg, gpointer user_data) { GabbleBytestreamFactory *self = GABBLE_BYTESTREAM_FACTORY (user_data); if (handle_ibb_open_iq (self, msg)) return TRUE; if (handle_ibb_close_iq (self, msg)) return TRUE; if (handle_ibb_data (self, msg, TRUE)) return TRUE; return FALSE; } /** * bytestream_factory_msg_data_cb * * Called by Wocky when we get an incoming . * This handler handles IBB data and pseudo IBB Muc data. */ static gboolean bytestream_factory_msg_data_cb ( WockyPorter *porter, WockyStanza *msg, gpointer user_data) { GabbleBytestreamFactory *self = user_data; if (handle_ibb_data (self, msg, FALSE)) return TRUE; if (handle_muc_data (self, msg)) return TRUE; return FALSE; } static gboolean handle_socks5_query_iq ( WockyPorter *porter, WockyStanza *msg, gpointer user_data) { GabbleBytestreamFactory *self = GABBLE_BYTESTREAM_FACTORY (user_data); GabbleBytestreamFactoryPrivate *priv = self->priv; GabbleBytestreamSocks5 *bytestream; WockyNode *query_node, *child_node; ConstBytestreamIdentifier bsid = { NULL, NULL }; const gchar *tmp; WockyNodeIter i; query_node = wocky_node_get_child_ns ( wocky_stanza_get_top_node (msg), "query", NS_BYTESTREAMS); g_return_val_if_fail (query_node != NULL, FALSE); bsid.jid = wocky_node_get_attribute ( wocky_stanza_get_top_node (msg), "from"); if (bsid.jid == NULL) { DEBUG ("got a message without a from field"); wocky_porter_send_iq_error (porter, msg, WOCKY_XMPP_ERROR_BAD_REQUEST, "SOCKS5 has no 'from' attribute"); return TRUE; } bsid.stream = wocky_node_get_attribute (query_node, "sid"); if (bsid.stream == NULL) { DEBUG ("SOCKS5 query stanza doesn't contain stream id"); wocky_porter_send_iq_error (porter, msg, WOCKY_XMPP_ERROR_BAD_REQUEST, "SOCKS5 has no stream ID"); return TRUE; } bytestream = g_hash_table_lookup (priv->socks5_bytestreams, &bsid); if (bytestream == NULL) { /* We don't accept streams not previously announced using SI */ DEBUG ("unknown stream: <%s> from <%s>", bsid.stream, bsid.jid); wocky_porter_send_iq_error (porter, msg, WOCKY_XMPP_ERROR_ITEM_NOT_FOUND, "SOCKS5 has an unknown stream ID"); return TRUE; } tmp = wocky_node_get_attribute (query_node, "mode"); /* If this attribute is missing, the default value of "tcp" MUST be assumed */ if (tmp != NULL && tp_strdiff (tmp, "tcp")) { DEBUG ("non-TCP SOCKS5 bytestreams are not supported"); wocky_porter_send_iq_error (porter, msg, WOCKY_XMPP_ERROR_BAD_REQUEST, "SOCKS5 non-TCP bytestreams are not supported"); return TRUE; } wocky_node_iter_init (&i, query_node, "streamhost", NULL); while (wocky_node_iter_next (&i, &child_node)) { gabble_bytestream_socks5_add_streamhost (bytestream, child_node); } gabble_bytestream_socks5_connect_to_streamhost (bytestream, msg); return TRUE; } GabbleBytestreamFactory * gabble_bytestream_factory_new (GabbleConnection *conn) { GabbleBytestreamFactory *factory; g_return_val_if_fail (GABBLE_IS_CONNECTION (conn), NULL); factory = GABBLE_BYTESTREAM_FACTORY ( g_object_new (GABBLE_TYPE_BYTESTREAM_FACTORY, "connection", conn, NULL)); return factory; } static void bytestream_state_changed_cb (GabbleBytestreamIface *bytestream, GabbleBytestreamState state, gpointer user_data) { GabbleBytestreamFactory *self = GABBLE_BYTESTREAM_FACTORY (user_data); GabbleBytestreamFactoryPrivate *priv = GABBLE_BYTESTREAM_FACTORY_GET_PRIVATE ( self); if (priv->dispose_has_run) return; if (state == GABBLE_BYTESTREAM_STATE_CLOSED) { remove_bytestream (self, bytestream); } } gchar * gabble_bytestream_factory_generate_stream_id (void) { gchar *stream_id; stream_id = g_strdup_printf ("%lu-%u", (unsigned long) time (NULL), g_random_int ()); return stream_id; } GabbleBytestreamIface * gabble_bytestream_factory_create_from_method (GabbleBytestreamFactory *self, const gchar *stream_method, TpHandle peer_handle, const gchar *stream_id, const gchar *stream_init_id, const gchar *peer_resource, const gchar *self_jid, GabbleBytestreamState state) { GabbleBytestreamIface *bytestream = NULL; if (!tp_strdiff (stream_method, NS_IBB)) { bytestream = GABBLE_BYTESTREAM_IFACE ( gabble_bytestream_factory_create_ibb (self, peer_handle, stream_id, stream_init_id, peer_resource, state)); } else if (!tp_strdiff (stream_method, NS_BYTESTREAMS)) { bytestream = GABBLE_BYTESTREAM_IFACE ( gabble_bytestream_factory_create_socks5 (self, peer_handle, stream_id, stream_init_id, peer_resource, self_jid, state)); } return bytestream; } static GabbleBytestreamIBB * gabble_bytestream_factory_create_ibb (GabbleBytestreamFactory *self, TpHandle peer_handle, const gchar *stream_id, const gchar *stream_init_id, const gchar *peer_resource, GabbleBytestreamState state) { GabbleBytestreamFactoryPrivate *priv; GabbleBytestreamIBB *ibb; BytestreamIdentifier *id; g_return_val_if_fail (GABBLE_IS_BYTESTREAM_FACTORY (self), NULL); priv = GABBLE_BYTESTREAM_FACTORY_GET_PRIVATE (self); ibb = g_object_new (GABBLE_TYPE_BYTESTREAM_IBB, "connection", priv->conn, "peer-handle", peer_handle, "stream-id", stream_id, "state", state, "stream-init-id", stream_init_id, "peer-resource", peer_resource, NULL); gabble_signal_connect_weak (ibb, "state-changed", G_CALLBACK (bytestream_state_changed_cb), G_OBJECT (self)); id = bytestream_id_new (GABBLE_BYTESTREAM_IFACE (ibb)); DEBUG ("add IBB bytestream <%s> from <%s>", id->stream, id->jid); g_hash_table_insert (priv->ibb_bytestreams, id, ibb); return ibb; } GabbleBytestreamMuc * gabble_bytestream_factory_create_muc (GabbleBytestreamFactory *self, TpHandle peer_handle, const gchar *stream_id, GabbleBytestreamState state) { GabbleBytestreamFactoryPrivate *priv; GabbleBytestreamMuc *bytestream; BytestreamIdentifier *id; g_return_val_if_fail (GABBLE_IS_BYTESTREAM_FACTORY (self), NULL); priv = GABBLE_BYTESTREAM_FACTORY_GET_PRIVATE (self); bytestream = g_object_new (GABBLE_TYPE_BYTESTREAM_MUC, "connection", priv->conn, "peer-handle", peer_handle, "stream-id", stream_id, "state", state, NULL); gabble_signal_connect_weak (bytestream, "state-changed", G_CALLBACK (bytestream_state_changed_cb), G_OBJECT (self)); id = bytestream_id_new (GABBLE_BYTESTREAM_IFACE (bytestream)); DEBUG ("add muc bytestream <%s> from <%s>", id->stream, id->jid); g_hash_table_insert (priv->muc_bytestreams, id, bytestream); return bytestream; } static GabbleBytestreamSocks5 * gabble_bytestream_factory_create_socks5 (GabbleBytestreamFactory *self, TpHandle peer_handle, const gchar *stream_id, const gchar *stream_init_id, const gchar *peer_resource, const gchar *self_jid, GabbleBytestreamState state) { GabbleBytestreamFactoryPrivate *priv; GabbleBytestreamSocks5 *socks5; BytestreamIdentifier *id; g_return_val_if_fail (GABBLE_IS_BYTESTREAM_FACTORY (self), NULL); priv = GABBLE_BYTESTREAM_FACTORY_GET_PRIVATE (self); socks5 = g_object_new (GABBLE_TYPE_BYTESTREAM_SOCKS5, "connection", priv->conn, "peer-handle", peer_handle, "stream-id", stream_id, "state", state, "stream-init-id", stream_init_id, "peer-resource", peer_resource, "self-jid", self_jid, NULL); gabble_signal_connect_weak (socks5, "state-changed", G_CALLBACK (bytestream_state_changed_cb), G_OBJECT (self)); id = bytestream_id_new (GABBLE_BYTESTREAM_IFACE (socks5)); DEBUG ("add SOCKS5 bytestream <%s> from <%s>", id->stream, id->jid); g_hash_table_insert (priv->socks5_bytestreams, id, socks5); return socks5; } static GabbleBytestreamMultiple * gabble_bytestream_factory_create_multiple (GabbleBytestreamFactory *self, TpHandle peer_handle, const gchar *stream_id, const gchar *stream_init_id, const gchar *peer_resource, const gchar *self_jid, GabbleBytestreamState state) { GabbleBytestreamFactoryPrivate *priv; GabbleBytestreamMultiple *multiple; BytestreamIdentifier *id; g_return_val_if_fail (GABBLE_IS_BYTESTREAM_FACTORY (self), NULL); priv = GABBLE_BYTESTREAM_FACTORY_GET_PRIVATE (self); multiple = g_object_new (GABBLE_TYPE_BYTESTREAM_MULTIPLE, "connection", priv->conn, "peer-handle", peer_handle, "stream-id", stream_id, "state", state, "stream-init-id", stream_init_id, "peer-resource", peer_resource, "factory", self, "self-jid", self_jid, NULL); gabble_signal_connect_weak (multiple, "state-changed", G_CALLBACK (bytestream_state_changed_cb), G_OBJECT (self)); id = bytestream_id_new (GABBLE_BYTESTREAM_IFACE (multiple)); DEBUG ("add multi bytestream <%s> from <%s>", id->stream, id->jid); g_hash_table_insert (priv->multiple_bytestreams, id, multiple); return multiple; } static GabbleBytestreamIface * streaminit_get_multiple_bytestream (GabbleBytestreamFactory *self, WockyStanza *reply_msg, WockyNode *si, const gchar *stream_id, TpHandle peer_handle, const gchar *peer_resource, const gchar *self_jid) { /* If the other client supports si-multiple we have directly a list of * supported methods inside tags */ WockyNode *si_multi; const gchar *stream_method; GabbleBytestreamMultiple *bytestream = NULL; WockyNode *value; WockyNodeIter i; si_multi = wocky_node_get_child_ns (si, "si-multiple", NS_SI_MULTIPLE); if (si_multi == NULL) return NULL; bytestream = gabble_bytestream_factory_create_multiple (self, peer_handle, stream_id, NULL, peer_resource, self_jid, GABBLE_BYTESTREAM_STATE_INITIATING); wocky_node_iter_init (&i, si_multi, "value", NULL); while (wocky_node_iter_next (&i, &value)) { stream_method = value->content; if (!stream_method_supported (stream_method)) { DEBUG ("got a si-multiple reply with an unsupported " "stream method: %s", stream_method); continue; } gabble_bytestream_multiple_add_stream_method (bytestream, stream_method); } return GABBLE_BYTESTREAM_IFACE (bytestream); } static GabbleBytestreamIface * streaminit_get_bytestream (GabbleBytestreamFactory *self, WockyStanza *reply_msg, WockyNode *si, const gchar *stream_id, TpHandle peer_handle, const gchar *peer_resource, const gchar *self_jid) { WockyNode *feature, *x, *value, *field; GabbleBytestreamIface *bytestream = NULL; const gchar *stream_method; WockyNodeIter i; feature = wocky_node_get_child_ns (si, "feature", NS_FEATURENEG); if (feature == NULL) { STANZA_DEBUG (reply_msg, "got a SI reply without a feature field"); return NULL; } x = wocky_node_get_child_ns (feature, "x", NS_X_DATA); if (x == NULL) { STANZA_DEBUG (reply_msg, "got a SI reply without a x field"); return NULL; } wocky_node_iter_init (&i, x, NULL, NULL); while (wocky_node_iter_next (&i, &field)) { if (tp_strdiff (wocky_node_get_attribute (field, "var"), "stream-method")) /* some future field, ignore it */ continue; value = wocky_node_get_child (field, "value"); if (value == NULL) { STANZA_DEBUG (reply_msg, "SI reply's stream-method field " "doesn't contain stream-method value"); return NULL; } stream_method = value->content; bytestream = gabble_bytestream_factory_create_from_method (self, stream_method, peer_handle, stream_id, NULL, peer_resource, self_jid, GABBLE_BYTESTREAM_STATE_INITIATING); /* no need to parse the rest of the fields, we've found the one we * wanted */ break; } return bytestream; } struct _streaminit_reply_cb_data { GabbleBytestreamFactory *self; gchar *stream_id; GabbleBytestreamFactoryNegotiateReplyFunc func; TpWeakRef *weak_object; }; /* Called when we receive the reply of a SI request */ static void streaminit_reply_cb ( GObject *source, GAsyncResult *result, gpointer user_data) { GabbleConnection *conn = GABBLE_CONNECTION (source); struct _streaminit_reply_cb_data *data = user_data; GabbleBytestreamFactory *self = data->self; GabbleBytestreamFactoryPrivate *priv = self->priv; GabbleBytestreamIface *bytestream = NULL; gchar *peer_resource = NULL; WockyNode *si; const gchar *from; TpHandleRepoIface *contact_repo = tp_base_connection_get_handles ( (TpBaseConnection *) conn, TP_HANDLE_TYPE_CONTACT); TpHandleRepoIface *room_repo = tp_base_connection_get_handles ( (TpBaseConnection *) conn, TP_HANDLE_TYPE_ROOM); TpHandle peer_handle = 0; TpHandle room_handle; gboolean success = FALSE; gchar *self_jid = NULL; GObject *object = tp_weak_ref_dup_object (data->weak_object); WockyStanza *reply_msg = NULL; if (object == NULL) { DEBUG ("Object which requested the bytestream was disposed. Ignoring"); goto END; } if (!conn_util_send_iq_finish (conn, result, &reply_msg, NULL)) { DEBUG ("stream %s declined", data->stream_id); goto END; } /* stream accepted */ from = wocky_node_get_attribute ( wocky_stanza_get_top_node (reply_msg), "from"); if (from == NULL) { STANZA_DEBUG (reply_msg, "got a message without a from field"); goto END; } peer_handle = tp_handle_ensure (contact_repo, from, NULL, NULL); room_handle = gabble_get_room_handle_from_jid (room_repo, from); if (room_handle == 0) { /* jid is not a muc jid so we need contact's resource */ if (!wocky_decode_jid (from, NULL, NULL, &peer_resource)) { DEBUG ("Got an SI request with a bad JID"); goto END; } if (peer_resource == NULL) { DEBUG ("Got an SI request from a JID without a resource; ignoring"); goto END; } /* we are not in a muc so our own jid is the one in the 'to' attribute */ self_jid = g_strdup (wocky_node_get_attribute ( wocky_stanza_get_top_node (reply_msg), "to")); } else { /* we are in a muc so need to get our muc jid */ GabbleMucChannel *muc; muc = gabble_muc_factory_find_text_channel (priv->conn->muc_factory, room_handle); if (muc == NULL) { DEBUG ("Got an IQ from a muc in which we are not. Ignoring"); goto END; } g_object_get (muc, "self-jid", &self_jid, NULL); } si = wocky_node_get_child_ns ( wocky_stanza_get_top_node (reply_msg), "si", NS_SI); if (si == NULL) { STANZA_DEBUG (reply_msg, "got a SI reply without a si field"); goto END; } /* Try to build a multiple bytestream with fallback methods */ bytestream = streaminit_get_multiple_bytestream (self, reply_msg, si, data->stream_id, peer_handle, peer_resource, self_jid); /* FIXME: check if there is at least one stream method */ if (bytestream == NULL) /* The other client doesn't suppport si-multiple, use the normal XEP-095 * method */ bytestream = streaminit_get_bytestream (self, reply_msg, si, data->stream_id, peer_handle, peer_resource, self_jid); if (bytestream == NULL) goto END; DEBUG ("stream %s accepted", data->stream_id); /* Let's start the initiation of the stream */ if (gabble_bytestream_iface_initiate (bytestream)) { /* FIXME: we should really only "succeed" when our succeeds. * It only really matters from the point of view of the data->func */ success = TRUE; } END: if (!success && bytestream != NULL) { remove_bytestream (self, bytestream); bytestream = NULL; } /* user callback */ if (object != NULL) { data->func (bytestream, reply_msg, object, tp_weak_ref_get_user_data (data->weak_object)); g_clear_object (&object); } if (peer_resource != NULL) g_free (peer_resource); g_clear_object (&reply_msg); g_clear_object (&data->self); g_free (self_jid); g_free (data->stream_id); tp_weak_ref_destroy (data->weak_object); g_slice_free (struct _streaminit_reply_cb_data, data); } /* * gabble_bytestream_factory_negotiate_stream: * * @msg: the SI negotiation IQ (created using * gabble_bytestream_factory_make_stream_init_iq) * @stream_id: the stream identifier * @func: the callback to call when we receive the answser of the request * @user_data: user data to pass to the callback * @object: the handler will follow the lifetime of this object, * which means that if the object is destroyed the callback will not be invoked. * * Send a Stream Initiation (XEP-0095) request. */ void gabble_bytestream_factory_negotiate_stream (GabbleBytestreamFactory *self, WockyStanza *msg, const gchar *stream_id, GabbleBytestreamFactoryNegotiateReplyFunc func, gpointer user_data, GObject *object) { GabbleBytestreamFactoryPrivate *priv; struct _streaminit_reply_cb_data *data; g_assert (GABBLE_IS_BYTESTREAM_FACTORY (self)); g_assert (stream_id != NULL); g_assert (func != NULL); g_assert (object != NULL); priv = GABBLE_BYTESTREAM_FACTORY_GET_PRIVATE (self); data = g_slice_new (struct _streaminit_reply_cb_data); data->self = g_object_ref (self); data->stream_id = g_strdup (stream_id); data->func = func; data->weak_object = tp_weak_ref_new (object, user_data, NULL); conn_util_send_iq_async (priv->conn, msg, NULL, streaminit_reply_cb, data); } /* * gabble_bytestream_factory_make_accept_iq * * @full_jid: the full jid of the stream initiator * @stream_init_id: the id of the SI request * @stream_method: the stream method chosen (one of them proposed * in the SI request) * * Create an IQ stanza accepting a stream in response to * a SI request (XEP-0095). * */ WockyStanza * gabble_bytestream_factory_make_accept_iq (const gchar *full_jid, const gchar *stream_init_id, const gchar *stream_method) { return wocky_stanza_build (WOCKY_STANZA_TYPE_IQ, WOCKY_STANZA_SUB_TYPE_RESULT, NULL, full_jid, '@', "id", stream_init_id, '(', "si", ':', NS_SI, '(', "feature", ':', NS_FEATURENEG, '(', "x", ':', NS_X_DATA, '@', "type", "submit", '(', "field", '@', "var", "stream-method", '(', "value", '$', stream_method, ')', ')', ')', ')', ')', NULL); } /* * gabble_bytestream_factory_make_multi_accept_iq * * @full_jid: the full jid of the stream initiator * @stream_init_id: the id of the SI request * @stream_methods: a list of the accepted string methods * * Create an IQ stanza accepting a stream in response to * a si-multiple SI request. * */ WockyStanza * gabble_bytestream_factory_make_multi_accept_iq (const gchar *full_jid, const gchar *stream_init_id, GList *stream_methods) { WockyStanza *msg; WockyNode *multi_node; GList *l; msg = wocky_stanza_build (WOCKY_STANZA_TYPE_IQ, WOCKY_STANZA_SUB_TYPE_RESULT, NULL, full_jid, '@', "id", stream_init_id, '(', "si", ':', NS_SI, '(', "si-multiple", ':', NS_SI_MULTIPLE, '*', &multi_node, ')', ')', NULL); for (l = stream_methods; l != NULL; l = l->next) { wocky_node_add_child_with_content (multi_node, "value", l->data); } return msg; } GSList * gabble_bytestream_factory_get_socks5_proxies (GabbleBytestreamFactory *self) { GabbleBytestreamFactoryPrivate *priv = GABBLE_BYTESTREAM_FACTORY_GET_PRIVATE ( self); return g_slist_concat (g_slist_copy (priv->socks5_proxies), g_slist_copy (priv->socks5_fallback_proxies)); }