* purple - Handling of XEP-0047: In-Band Bytestreams. * 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 #define JABBER_IBB_SESSION_DEFAULT_BLOCK_SIZE 4096 static GHashTable *jabber_ibb_sessions = NULL; static GList *open_handlers = NULL; jabber_ibb_session_create(JabberStream *js, const gchar *sid, const gchar *who, JabberIBBSession *sess = g_new0(JabberIBBSession, 1); sess->sid = g_strdup(sid); sess->sid = jabber_get_next_id(js); sess->who = g_strdup(who); sess->block_size = JABBER_IBB_SESSION_DEFAULT_BLOCK_SIZE; sess->state = JABBER_IBB_SESSION_NOT_OPENED; sess->user_data = user_data; g_hash_table_insert(jabber_ibb_sessions, sess->sid, sess); jabber_ibb_session_create_from_xmlnode(JabberStream *js, const char *from, const char *id, PurpleXmlNode *open, gpointer user_data) JabberIBBSession *sess = NULL; const gchar *sid = purple_xmlnode_get_attrib(open, "sid"); const gchar *block_size = purple_xmlnode_get_attrib(open, "block-size"); if (!sid || !block_size) { purple_debug_error("jabber", "IBB session open tag requires sid and block-size attributes\n"); sess = jabber_ibb_session_create(js, sid, from, user_data); sess->block_size = atoi(block_size); /* if we create a session from an incoming <open/> request, it means the session is immediatly open... */ sess->state = JABBER_IBB_SESSION_OPENED; jabber_ibb_session_destroy(JabberIBBSession *sess) purple_debug_info("jabber", "IBB: destroying session %p %s\n", sess, if (jabber_ibb_session_get_state(sess) == JABBER_IBB_SESSION_OPENED) { jabber_ibb_session_close(sess); purple_debug_info("jabber", "IBB: removing callback for <iq/> %s\n", jabber_iq_remove_callback_by_id(jabber_ibb_session_get_js(sess), g_free(sess->last_iq_id); g_hash_table_remove(jabber_ibb_sessions, sess->sid); jabber_ibb_session_get_sid(const JabberIBBSession *sess) jabber_ibb_session_get_js(JabberIBBSession *sess) jabber_ibb_session_get_who(const JabberIBBSession *sess) jabber_ibb_session_get_send_seq(const JabberIBBSession *sess) jabber_ibb_session_get_recv_seq(const JabberIBBSession *sess) jabber_ibb_session_get_state(const JabberIBBSession *sess) jabber_ibb_session_get_block_size(const JabberIBBSession *sess) jabber_ibb_session_set_block_size(JabberIBBSession *sess, gsize size) if (jabber_ibb_session_get_state(sess) == JABBER_IBB_SESSION_NOT_OPENED) { purple_debug_error("jabber", "Can't set block size on an open IBB session\n"); jabber_ibb_session_get_max_data_size(const JabberIBBSession *sess) return (gsize) floor((sess->block_size - 2) * (float) 3 / 4); jabber_ibb_session_get_user_data(JabberIBBSession *sess) jabber_ibb_session_set_opened_callback(JabberIBBSession *sess, JabberIBBOpenedCallback *cb) jabber_ibb_session_set_data_sent_callback(JabberIBBSession *sess, JabberIBBSentCallback *cb) jabber_ibb_session_set_closed_callback(JabberIBBSession *sess, JabberIBBClosedCallback *cb) jabber_ibb_session_set_data_received_callback(JabberIBBSession *sess, JabberIBBDataCallback *cb) sess->data_received_cb = cb; jabber_ibb_session_set_error_callback(JabberIBBSession *sess, JabberIBBErrorCallback *cb) jabber_ibb_session_opened_cb(JabberStream *js, const char *from, JabberIqType type, const char *id, PurpleXmlNode *packet, gpointer data) JabberIBBSession *sess = (JabberIBBSession *) data; if (type == JABBER_IQ_ERROR) { sess->state = JABBER_IBB_SESSION_ERROR; sess->state = JABBER_IBB_SESSION_OPENED; jabber_ibb_session_open(JabberIBBSession *sess) if (jabber_ibb_session_get_state(sess) != JABBER_IBB_SESSION_NOT_OPENED) { purple_debug_error("jabber", "jabber_ibb_session called on an already open stream\n"); JabberIq *set = jabber_iq_new(sess->js, JABBER_IQ_SET); PurpleXmlNode *open = purple_xmlnode_new("open"); purple_xmlnode_set_attrib(set->node, "to", jabber_ibb_session_get_who(sess)); purple_xmlnode_set_namespace(open, NS_IBB); purple_xmlnode_set_attrib(open, "sid", jabber_ibb_session_get_sid(sess)); g_snprintf(block_size, sizeof(block_size), "%" G_GSIZE_FORMAT, jabber_ibb_session_get_block_size(sess)); purple_xmlnode_set_attrib(open, "block-size", block_size); purple_xmlnode_insert_child(set->node, open); jabber_iq_set_callback(set, jabber_ibb_session_opened_cb, sess); jabber_ibb_session_close(JabberIBBSession *sess) JabberIBBSessionState state = jabber_ibb_session_get_state(sess); if (state != JABBER_IBB_SESSION_OPENED && state != JABBER_IBB_SESSION_ERROR) { purple_debug_error("jabber", "jabber_ibb_session_close called on a session that has not been" JabberIq *set = jabber_iq_new(jabber_ibb_session_get_js(sess), PurpleXmlNode *close = purple_xmlnode_new("close"); purple_xmlnode_set_attrib(set->node, "to", jabber_ibb_session_get_who(sess)); purple_xmlnode_set_namespace(close, NS_IBB); purple_xmlnode_set_attrib(close, "sid", jabber_ibb_session_get_sid(sess)); purple_xmlnode_insert_child(set->node, close); sess->state = JABBER_IBB_SESSION_CLOSED; jabber_ibb_session_accept(JabberIBBSession *sess) JabberIq *result = jabber_iq_new(jabber_ibb_session_get_js(sess), purple_xmlnode_set_attrib(result->node, "to", jabber_ibb_session_get_who(sess)); jabber_iq_set_id(result, sess->id); sess->state = JABBER_IBB_SESSION_OPENED; jabber_ibb_session_send_acknowledge_cb(JabberStream *js, const char *from, JabberIqType type, const char *id, PurpleXmlNode *packet, gpointer data) JabberIBBSession *sess = (JabberIBBSession *) data; g_free(sess->last_iq_id); if (type == JABBER_IQ_ERROR) { jabber_ibb_session_close(sess); sess->state = JABBER_IBB_SESSION_ERROR; if (sess->data_sent_cb) { sess->data_sent_cb(sess); /* the session has gone away, it was probably cancelled */ purple_debug_info("jabber", "got response from send data, but IBB session is no longer active\n"); jabber_ibb_session_send_data(JabberIBBSession *sess, gconstpointer data, JabberIBBSessionState state = jabber_ibb_session_get_state(sess); purple_debug_info("jabber", "sending data block of %" G_GSIZE_FORMAT " bytes on IBB stream\n", if (state != JABBER_IBB_SESSION_OPENED) { purple_debug_error("jabber", "trying to send data on a non-open IBB session\n"); } else if (size > jabber_ibb_session_get_max_data_size(sess)) { purple_debug_error("jabber", "trying to send a too large packet in the IBB session\n"); JabberIq *set = jabber_iq_new(jabber_ibb_session_get_js(sess), PurpleXmlNode *data_element = purple_xmlnode_new("data"); char *base64 = g_base64_encode(data, size); g_snprintf(seq, sizeof(seq), "%u", jabber_ibb_session_get_send_seq(sess)); purple_xmlnode_set_attrib(set->node, "to", jabber_ibb_session_get_who(sess)); purple_xmlnode_set_namespace(data_element, NS_IBB); purple_xmlnode_set_attrib(data_element, "sid", jabber_ibb_session_get_sid(sess)); purple_xmlnode_set_attrib(data_element, "seq", seq); purple_xmlnode_insert_data(data_element, base64, -1); purple_xmlnode_insert_child(set->node, data_element); purple_debug_info("jabber", "IBB: setting send <iq/> callback for session %p %s\n", sess, jabber_iq_set_callback(set, jabber_ibb_session_send_acknowledge_cb, sess); sess->last_iq_id = g_strdup(purple_xmlnode_get_attrib(set->node, "id")); purple_debug_info("jabber", "IBB: set sess->last_iq_id: %s\n", jabber_ibb_send_error_response(JabberStream *js, const char *to, const char *id) JabberIq *result = jabber_iq_new(js, JABBER_IQ_ERROR); PurpleXmlNode *error = purple_xmlnode_new("error"); PurpleXmlNode *item_not_found = purple_xmlnode_new("item-not-found"); purple_xmlnode_set_namespace(item_not_found, NS_XMPP_STANZAS); purple_xmlnode_set_attrib(error, "code", "440"); purple_xmlnode_set_attrib(error, "type", "cancel"); jabber_iq_set_id(result, id); purple_xmlnode_set_attrib(result->node, "to", to); purple_xmlnode_insert_child(error, item_not_found); purple_xmlnode_insert_child(result->node, error); jabber_ibb_parse(JabberStream *js, const char *who, JabberIqType type, const char *id, PurpleXmlNode *child) const char *name = child->name; gboolean data = purple_strequal(name, "data"); gboolean close = purple_strequal(name, "close"); gboolean open = purple_strequal(name, "open"); const gchar *sid = (data || close) ? purple_xmlnode_get_attrib(child, "sid") : NULL; sid ? g_hash_table_lookup(jabber_ibb_sessions, sid) : NULL; if (!purple_strequal(who, jabber_ibb_session_get_who(sess))) { /* the iq comes from a different JID than the remote JID of the purple_debug_error("jabber", "Got IBB iq from wrong JID, ignoring\n"); const gchar *seq_attr = purple_xmlnode_get_attrib(child, "seq"); guint16 seq = (seq_attr ? atoi(seq_attr) : 0); /* reject the data, and set the session in error if we get an if (seq_attr && seq == jabber_ibb_session_get_recv_seq(sess)) { /* sequence # is the expected... */ JabberIq *result = jabber_iq_new(js, JABBER_IQ_RESULT); jabber_iq_set_id(result, id); purple_xmlnode_set_attrib(result->node, "to", who); if (sess->data_received_cb) { gchar *base64 = purple_xmlnode_get_data(child); gpointer rawdata = g_base64_decode(base64, &size); purple_debug_info("jabber", "got %" G_GSIZE_FORMAT " bytes of data on IBB stream\n", /* we accept other clients to send up to block-size of _unencoded_ data, since there's been some confusions regarding the interpretation of this attribute (including previous versions of libpurple) */ if (size > jabber_ibb_session_get_block_size(sess)) { purple_debug_error("jabber", "IBB: received a too large packet\n"); purple_debug_info("jabber", "calling IBB callback for received data\n"); sess->data_received_cb(sess, rawdata, size); purple_debug_error("jabber", "IBB: invalid BASE64 data received\n"); purple_debug_error("jabber", "Received an out-of-order/invalid IBB packet\n"); sess->state = JABBER_IBB_SESSION_ERROR; sess->state = JABBER_IBB_SESSION_CLOSED; purple_debug_info("jabber", "IBB: received close\n"); purple_debug_info("jabber", "IBB: calling closed handler\n"); /* run all open handlers registered until one returns true */ for (iterator = open_handlers ; iterator ; iterator = g_list_next(iterator)) { JabberIBBOpenHandler *handler = iterator->data; if (handler(js, who, id, child)) { result = jabber_iq_new(js, JABBER_IQ_RESULT); purple_xmlnode_set_attrib(result->node, "to", who); jabber_iq_set_id(result, id); /* no open callback returned success, reject */ jabber_ibb_send_error_response(js, who, id); jabber_ibb_send_error_response(js, who, id); jabber_ibb_register_open_handler(JabberIBBOpenHandler *cb) open_handlers = g_list_append(open_handlers, cb); jabber_ibb_unregister_open_handler(JabberIBBOpenHandler *cb) open_handlers = g_list_remove(open_handlers, cb); jabber_ibb_sessions = g_hash_table_new(g_str_hash, g_str_equal); jabber_add_feature(NS_IBB, NULL); jabber_iq_register_handler("close", NS_IBB, jabber_ibb_parse); jabber_iq_register_handler("data", NS_IBB, jabber_ibb_parse); jabber_iq_register_handler("open", NS_IBB, jabber_ibb_parse); g_hash_table_destroy(jabber_ibb_sessions); g_list_free(open_handlers); jabber_ibb_sessions = NULL;