Mercurial > pidgin
view libpurple/protocols/jabber/si.c @ 29299:7925bb7f2aa7
propagate from branch 'im.pidgin.pidgin' (head 92e101e3698042e7600729bc09dec9e28f81de8f)
to branch 'im.pidgin.cpw.attention_ui' (head 563c35d20efcc234a74567ebb88c059d10ce1e09)
author | Marcus Lundblad <ml@update.uu.se> |
---|---|
date | Mon, 24 Aug 2009 19:23:56 +0000 |
parents | c585572e80dd |
children | fa7f3426ed13 02f6f49da454 7763697a85c8 |
line wrap: on
line source
/* * 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 * source distribution. * * 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 "internal.h" #include "blist.h" #include "debug.h" #include "ft.h" #include "request.h" #include "network.h" #include "notify.h" #include "buddy.h" #include "disco.h" #include "jabber.h" #include "ibb.h" #include "iq.h" #include "si.h" #define STREAMHOST_CONNECT_TIMEOUT 15 typedef struct _JabberSIXfer { JabberStream *js; PurpleProxyConnectData *connect_data; PurpleNetworkListenData *listen_data; guint connect_timeout; gboolean accepted; char *stream_id; char *iq_id; enum { STREAM_METHOD_UNKNOWN = 0, STREAM_METHOD_BYTESTREAMS = 2 << 1, STREAM_METHOD_IBB = 2 << 2, STREAM_METHOD_UNSUPPORTED = 2 << 31 } stream_method; GList *streamhosts; PurpleProxyInfo *gpi; char *rxqueue; size_t rxlen; gsize rxmaxlen; int local_streamhost_fd; JabberIBBSession *ibb_session; guint ibb_timeout_handle; PurpleCircBuffer *ibb_buffer; } JabberSIXfer; /* some forward declarations */ static void jabber_si_xfer_ibb_send_init(JabberStream *js, PurpleXfer *xfer); static PurpleXfer* jabber_si_xfer_find(JabberStream *js, const char *sid, const char *from) { GList *xfers; if(!sid || !from) return NULL; for(xfers = js->file_transfers; xfers; xfers = xfers->next) { PurpleXfer *xfer = xfers->data; JabberSIXfer *jsx = xfer->data; if(jsx->stream_id && xfer->who && !strcmp(jsx->stream_id, sid) && !strcmp(xfer->who, from)) return xfer; } return NULL; } static void jabber_si_free_streamhost(gpointer data, gpointer user_data) { JabberBytestreamsStreamhost *sh = data; if(!data) return; g_free(sh->jid); g_free(sh->host); g_free(sh->zeroconf); g_free(sh); } static void jabber_si_bytestreams_attempt_connect(PurpleXfer *xfer); static void jabber_si_bytestreams_connect_cb(gpointer data, gint source, const gchar *error_message) { PurpleXfer *xfer = data; JabberSIXfer *jsx = xfer->data; JabberIq *iq; xmlnode *query, *su; JabberBytestreamsStreamhost *streamhost = jsx->streamhosts->data; purple_proxy_info_destroy(jsx->gpi); jsx->gpi = NULL; jsx->connect_data = NULL; if (jsx->connect_timeout > 0) purple_timeout_remove(jsx->connect_timeout); jsx->connect_timeout = 0; if(source < 0) { purple_debug_warning("jabber", "si connection failed, jid was %s, host was %s, error was %s\n", streamhost->jid, streamhost->host, error_message ? error_message : "(null)"); jsx->streamhosts = g_list_remove(jsx->streamhosts, streamhost); jabber_si_free_streamhost(streamhost, NULL); jabber_si_bytestreams_attempt_connect(xfer); return; } /* unknown file transfer type is assumed to be RECEIVE */ if(xfer->type == PURPLE_XFER_SEND) { xmlnode *activate; iq = jabber_iq_new_query(jsx->js, JABBER_IQ_SET, "http://jabber.org/protocol/bytestreams"); xmlnode_set_attrib(iq->node, "to", streamhost->jid); query = xmlnode_get_child(iq->node, "query"); xmlnode_set_attrib(query, "sid", jsx->stream_id); activate = xmlnode_new_child(query, "activate"); xmlnode_insert_data(activate, xfer->who, -1); /* TODO: We need to wait for an activation result before starting */ } else { iq = jabber_iq_new_query(jsx->js, JABBER_IQ_RESULT, "http://jabber.org/protocol/bytestreams"); xmlnode_set_attrib(iq->node, "to", xfer->who); jabber_iq_set_id(iq, jsx->iq_id); query = xmlnode_get_child(iq->node, "query"); su = xmlnode_new_child(query, "streamhost-used"); xmlnode_set_attrib(su, "jid", streamhost->jid); } jabber_iq_send(iq); purple_xfer_start(xfer, source, NULL, -1); } static gboolean connect_timeout_cb(gpointer data) { PurpleXfer *xfer = data; JabberSIXfer *jsx = xfer->data; purple_debug_info("jabber", "Streamhost connection timeout of %d seconds exceeded.\n", STREAMHOST_CONNECT_TIMEOUT); jsx->connect_timeout = 0; if (jsx->connect_data != NULL) purple_proxy_connect_cancel(jsx->connect_data); jsx->connect_data = NULL; /* Trigger the connect error manually */ jabber_si_bytestreams_connect_cb(xfer, -1, "Timeout Exceeded."); return FALSE; } static void jabber_si_bytestreams_ibb_timeout_remove(JabberSIXfer *jsx) { if (jsx->ibb_timeout_handle) { purple_timeout_remove(jsx->ibb_timeout_handle); jsx->ibb_timeout_handle = 0; } } static gboolean jabber_si_bytestreams_ibb_timeout_cb(gpointer data) { PurpleXfer *xfer = (PurpleXfer *) data; JabberSIXfer *jsx = xfer->data; if (jsx && !jsx->ibb_session) { purple_debug_info("jabber", "jabber_si_bytestreams_ibb_timeout called and IBB session not set " " up yet, cancel transfer"); jabber_si_bytestreams_ibb_timeout_remove(jsx); purple_xfer_cancel_local(xfer); } return FALSE; } static void jabber_si_bytestreams_attempt_connect(PurpleXfer *xfer) { JabberSIXfer *jsx = xfer->data; JabberBytestreamsStreamhost *streamhost; JabberID *dstjid; if(!jsx->streamhosts) { JabberIq *iq = jabber_iq_new(jsx->js, JABBER_IQ_ERROR); xmlnode *error, *inf; if(jsx->iq_id) jabber_iq_set_id(iq, jsx->iq_id); xmlnode_set_attrib(iq->node, "to", xfer->who); error = xmlnode_new_child(iq->node, "error"); xmlnode_set_attrib(error, "code", "404"); xmlnode_set_attrib(error, "type", "cancel"); inf = xmlnode_new_child(error, "item-not-found"); xmlnode_set_namespace(inf, "urn:ietf:params:xml:ns:xmpp-stanzas"); jabber_iq_send(iq); /* if IBB is available, revert to that before giving up... */ if (jsx->stream_method & STREAM_METHOD_IBB) { /* if we are the initializer, init IBB */ purple_debug_info("jabber", "jabber_si_bytestreams_attempt_connect: " "no streamhosts found, trying IBB\n"); /* if we are the sender, open an IBB session, but not if we already did it, since we could have received the error <iq/> from the receiver already... */ if (purple_xfer_get_type(xfer) == PURPLE_XFER_SEND && !jsx->ibb_session) { jabber_si_xfer_ibb_send_init(jsx->js, xfer); } else { /* setup a timeout to cancel waiting for IBB open */ jsx->ibb_timeout_handle = purple_timeout_add_seconds(30, jabber_si_bytestreams_ibb_timeout_cb, xfer); } /* if we are the receiver, just wait for IBB open, callback is already set up... */ } else { purple_xfer_cancel_local(xfer); } return; } streamhost = jsx->streamhosts->data; jsx->connect_data = NULL; if (jsx->gpi != NULL) purple_proxy_info_destroy(jsx->gpi); jsx->gpi = NULL; dstjid = jabber_id_new(xfer->who); /* TODO: Deal with zeroconf */ if(dstjid != NULL && streamhost->host && streamhost->port > 0) { char *dstaddr, *hash; jsx->gpi = purple_proxy_info_new(); purple_proxy_info_set_type(jsx->gpi, PURPLE_PROXY_SOCKS5); purple_proxy_info_set_host(jsx->gpi, streamhost->host); purple_proxy_info_set_port(jsx->gpi, streamhost->port); /* unknown file transfer type is assumed to be RECEIVE */ if(xfer->type == PURPLE_XFER_SEND) dstaddr = g_strdup_printf("%s%s@%s/%s%s@%s/%s", jsx->stream_id, jsx->js->user->node, jsx->js->user->domain, jsx->js->user->resource, dstjid->node, dstjid->domain, dstjid->resource); else dstaddr = g_strdup_printf("%s%s@%s/%s%s@%s/%s", jsx->stream_id, dstjid->node, dstjid->domain, dstjid->resource, jsx->js->user->node, jsx->js->user->domain, jsx->js->user->resource); /* Per XEP-0065, the 'host' must be SHA1(SID + from JID + to JID) */ hash = jabber_calculate_data_sha1sum(dstaddr, strlen(dstaddr)); jsx->connect_data = purple_proxy_connect_socks5(NULL, jsx->gpi, hash, 0, jabber_si_bytestreams_connect_cb, xfer); g_free(hash); g_free(dstaddr); /* When selecting a streamhost, timeout after STREAMHOST_CONNECT_TIMEOUT seconds, otherwise it takes forever */ if (xfer->type != PURPLE_XFER_SEND && jsx->connect_data != NULL) jsx->connect_timeout = purple_timeout_add_seconds( STREAMHOST_CONNECT_TIMEOUT, connect_timeout_cb, xfer); jabber_id_free(dstjid); } if (jsx->connect_data == NULL) { jsx->streamhosts = g_list_remove(jsx->streamhosts, streamhost); jabber_si_free_streamhost(streamhost, NULL); jabber_si_bytestreams_attempt_connect(xfer); } } void jabber_bytestreams_parse(JabberStream *js, const char *from, JabberIqType type, const char *id, xmlnode *query) { PurpleXfer *xfer; JabberSIXfer *jsx; xmlnode *streamhost; const char *sid; if(type != JABBER_IQ_SET) return; if(!from) return; if(!(sid = xmlnode_get_attrib(query, "sid"))) return; if(!(xfer = jabber_si_xfer_find(js, sid, from))) return; jsx = xfer->data; if(!jsx->accepted) return; if(jsx->iq_id) g_free(jsx->iq_id); jsx->iq_id = g_strdup(id); for(streamhost = xmlnode_get_child(query, "streamhost"); streamhost; streamhost = xmlnode_get_next_twin(streamhost)) { const char *jid, *host = NULL, *port, *zeroconf; int portnum = 0; if((jid = xmlnode_get_attrib(streamhost, "jid")) && ((zeroconf = xmlnode_get_attrib(streamhost, "zeroconf")) || ((host = xmlnode_get_attrib(streamhost, "host")) && (port = xmlnode_get_attrib(streamhost, "port")) && (portnum = atoi(port))))) { JabberBytestreamsStreamhost *sh = g_new0(JabberBytestreamsStreamhost, 1); sh->jid = g_strdup(jid); sh->host = g_strdup(host); sh->port = portnum; sh->zeroconf = g_strdup(zeroconf); /* If there were a lot of these, it'd be worthwhile to prepend and reverse. */ jsx->streamhosts = g_list_append(jsx->streamhosts, sh); } } jabber_si_bytestreams_attempt_connect(xfer); } static void jabber_si_xfer_bytestreams_send_read_again_resp_cb(gpointer data, gint source, PurpleInputCondition cond) { PurpleXfer *xfer = data; JabberSIXfer *jsx = xfer->data; int len; len = write(source, jsx->rxqueue + jsx->rxlen, jsx->rxmaxlen - jsx->rxlen); if (len < 0 && errno == EAGAIN) return; else if (len < 0) { purple_input_remove(xfer->watcher); xfer->watcher = 0; g_free(jsx->rxqueue); jsx->rxqueue = NULL; close(source); purple_xfer_cancel_remote(xfer); return; } jsx->rxlen += len; if (jsx->rxlen < jsx->rxmaxlen) return; purple_input_remove(xfer->watcher); xfer->watcher = 0; g_free(jsx->rxqueue); jsx->rxqueue = NULL; /* Before actually starting sending the file, we need to wait until the * recipient sends the IQ result with <streamhost-used/> */ purple_debug_info("jabber", "SOCKS5 connection negotiation completed. " "Waiting for IQ result to start file transfer.\n"); } static void jabber_si_xfer_bytestreams_send_read_again_cb(gpointer data, gint source, PurpleInputCondition cond) { PurpleXfer *xfer = data; JabberSIXfer *jsx = xfer->data; char buffer[42]; /* 40 for DST.ADDR + 2 bytes for port number*/ int len; char *dstaddr, *hash; const char *host; purple_debug_info("jabber", "in jabber_si_xfer_bytestreams_send_read_again_cb\n"); if(jsx->rxlen < 5) { purple_debug_info("jabber", "reading the first 5 bytes\n"); len = read(source, buffer, 5 - jsx->rxlen); if(len < 0 && errno == EAGAIN) return; else if(len <= 0) { purple_input_remove(xfer->watcher); xfer->watcher = 0; close(source); purple_xfer_cancel_remote(xfer); return; } jsx->rxqueue = g_realloc(jsx->rxqueue, len + jsx->rxlen); memcpy(jsx->rxqueue + jsx->rxlen, buffer, len); jsx->rxlen += len; return; } else if(jsx->rxqueue[0] != 0x05 || jsx->rxqueue[1] != 0x01 || jsx->rxqueue[3] != 0x03 || jsx->rxqueue[4] != 40) { purple_debug_info("jabber", "Invalid socks5 conn req. header[0x%x,0x%x,0x%x,0x%x,0x%x]\n", jsx->rxqueue[0], jsx->rxqueue[1], jsx->rxqueue[2], jsx->rxqueue[3], jsx->rxqueue[4]); purple_input_remove(xfer->watcher); xfer->watcher = 0; close(source); purple_xfer_cancel_remote(xfer); return; } else if(jsx->rxlen - 5 < jsx->rxqueue[4] + 2) { /* Upper-bound of 257 (jsx->rxlen = 5, jsx->rxqueue[4] = 0xFF) */ unsigned short to_read = jsx->rxqueue[4] + 2 - (jsx->rxlen - 5); purple_debug_info("jabber", "reading %u bytes for DST.ADDR + port num (trying to read %hu now)\n", jsx->rxqueue[4] + 2, to_read); len = read(source, buffer, to_read); if(len < 0 && errno == EAGAIN) return; else if(len <= 0) { purple_input_remove(xfer->watcher); xfer->watcher = 0; close(source); purple_xfer_cancel_remote(xfer); return; } jsx->rxqueue = g_realloc(jsx->rxqueue, len + jsx->rxlen); memcpy(jsx->rxqueue + jsx->rxlen, buffer, len); jsx->rxlen += len; } /* Have we not read all of DST.ADDR and the following 2-byte port number? */ if(jsx->rxlen - 5 < jsx->rxqueue[4] + 2) return; purple_input_remove(xfer->watcher); xfer->watcher = 0; dstaddr = g_strdup_printf("%s%s@%s/%s%s", jsx->stream_id, jsx->js->user->node, jsx->js->user->domain, jsx->js->user->resource, xfer->who); /* Per XEP-0065, the 'host' must be SHA1(SID + from JID + to JID) */ hash = jabber_calculate_data_sha1sum(dstaddr, strlen(dstaddr)); if(strncmp(hash, jsx->rxqueue + 5, 40) || jsx->rxqueue[45] != 0x00 || jsx->rxqueue[46] != 0x00) { if (jsx->rxqueue[45] != 0x00 || jsx->rxqueue[46] != 0x00) purple_debug_error("jabber", "Got SOCKS5 BS conn with the wrong DST.PORT" " (must be 0 - got[0x%x,0x%x]).\n", jsx->rxqueue[45], jsx->rxqueue[46]); else purple_debug_error("jabber", "Got SOCKS5 BS conn with the wrong DST.ADDR" " (expected '%s' - got '%.40s').\n", hash, jsx->rxqueue + 5); close(source); purple_xfer_cancel_remote(xfer); g_free(hash); g_free(dstaddr); return; } g_free(hash); g_free(dstaddr); g_free(jsx->rxqueue); host = purple_network_get_my_ip(jsx->js->fd); jsx->rxmaxlen = 5 + strlen(host) + 2; jsx->rxqueue = g_malloc(jsx->rxmaxlen); jsx->rxlen = 0; jsx->rxqueue[0] = 0x05; jsx->rxqueue[1] = 0x00; jsx->rxqueue[2] = 0x00; jsx->rxqueue[3] = 0x03; jsx->rxqueue[4] = strlen(host); memcpy(jsx->rxqueue + 5, host, strlen(host)); jsx->rxqueue[5+strlen(host)] = 0x00; jsx->rxqueue[6+strlen(host)] = 0x00; xfer->watcher = purple_input_add(source, PURPLE_INPUT_WRITE, jabber_si_xfer_bytestreams_send_read_again_resp_cb, xfer); jabber_si_xfer_bytestreams_send_read_again_resp_cb(xfer, source, PURPLE_INPUT_WRITE); } static void jabber_si_xfer_bytestreams_send_read_response_cb(gpointer data, gint source, PurpleInputCondition cond) { PurpleXfer *xfer = data; JabberSIXfer *jsx = xfer->data; int len; len = write(source, jsx->rxqueue + jsx->rxlen, jsx->rxmaxlen - jsx->rxlen); if (len < 0 && errno == EAGAIN) return; else if (len < 0) { purple_input_remove(xfer->watcher); xfer->watcher = 0; g_free(jsx->rxqueue); jsx->rxqueue = NULL; close(source); purple_xfer_cancel_remote(xfer); return; } jsx->rxlen += len; if (jsx->rxlen < jsx->rxmaxlen) return; purple_input_remove(xfer->watcher); xfer->watcher = 0; /* If we sent a "Success", wait for a response, otherwise give up and cancel */ if (jsx->rxqueue[1] == 0x00) { xfer->watcher = purple_input_add(source, PURPLE_INPUT_READ, jabber_si_xfer_bytestreams_send_read_again_cb, xfer); g_free(jsx->rxqueue); jsx->rxqueue = NULL; jsx->rxlen = 0; } else { close(source); purple_xfer_cancel_remote(xfer); } } static void jabber_si_xfer_bytestreams_send_read_cb(gpointer data, gint source, PurpleInputCondition cond) { PurpleXfer *xfer = data; JabberSIXfer *jsx = xfer->data; int i; int len; char buffer[256]; purple_debug_info("jabber", "in jabber_si_xfer_bytestreams_send_read_cb\n"); xfer->fd = source; /** Try to read the SOCKS5 header */ if(jsx->rxlen < 2) { purple_debug_info("jabber", "reading those first two bytes\n"); len = read(source, buffer, 2 - jsx->rxlen); if(len < 0 && errno == EAGAIN) return; else if(len <= 0) { purple_input_remove(xfer->watcher); xfer->watcher = 0; close(source); purple_xfer_cancel_remote(xfer); return; } jsx->rxqueue = g_realloc(jsx->rxqueue, len + jsx->rxlen); memcpy(jsx->rxqueue + jsx->rxlen, buffer, len); jsx->rxlen += len; return; } else if(jsx->rxlen - 2 < jsx->rxqueue[1]) { /* Has a maximum value of 255 (jsx->rxlen = 2, jsx->rxqueue[1] = 0xFF) */ unsigned short to_read = jsx->rxqueue[1] - (jsx->rxlen - 2); purple_debug_info("jabber", "reading %u bytes for auth methods (trying to read %hu now)\n", jsx->rxqueue[1], to_read); len = read(source, buffer, to_read); if(len < 0 && errno == EAGAIN) return; else if(len <= 0) { purple_input_remove(xfer->watcher); xfer->watcher = 0; close(source); purple_xfer_cancel_remote(xfer); return; } jsx->rxqueue = g_realloc(jsx->rxqueue, len + jsx->rxlen); memcpy(jsx->rxqueue + jsx->rxlen, buffer, len); jsx->rxlen += len; } /* Have we not read all the auth. method bytes? */ if(jsx->rxlen -2 < jsx->rxqueue[1]) return; purple_input_remove(xfer->watcher); xfer->watcher = 0; purple_debug_info("jabber", "checking to make sure we're socks FIVE\n"); if(jsx->rxqueue[0] != 0x05) { close(source); purple_xfer_cancel_remote(xfer); return; } purple_debug_info("jabber", "going to test %hhu different methods\n", jsx->rxqueue[1]); for(i=0; i<jsx->rxqueue[1]; i++) { purple_debug_info("jabber", "testing %hhu\n", jsx->rxqueue[i+2]); if(jsx->rxqueue[i+2] == 0x00) { g_free(jsx->rxqueue); jsx->rxlen = 0; jsx->rxmaxlen = 2; jsx->rxqueue = g_malloc(jsx->rxmaxlen); jsx->rxqueue[0] = 0x05; jsx->rxqueue[1] = 0x00; xfer->watcher = purple_input_add(source, PURPLE_INPUT_WRITE, jabber_si_xfer_bytestreams_send_read_response_cb, xfer); jabber_si_xfer_bytestreams_send_read_response_cb(xfer, source, PURPLE_INPUT_WRITE); jsx->rxqueue = NULL; jsx->rxlen = 0; return; } } g_free(jsx->rxqueue); jsx->rxlen = 0; jsx->rxmaxlen = 2; jsx->rxqueue = g_malloc(jsx->rxmaxlen); jsx->rxqueue[0] = 0x05; jsx->rxqueue[1] = 0xFF; xfer->watcher = purple_input_add(source, PURPLE_INPUT_WRITE, jabber_si_xfer_bytestreams_send_read_response_cb, xfer); jabber_si_xfer_bytestreams_send_read_response_cb(xfer, source, PURPLE_INPUT_WRITE); } static gint jabber_si_compare_jid(gconstpointer a, gconstpointer b) { const JabberBytestreamsStreamhost *sh = a; if(!a) return -1; return strcmp(sh->jid, (char *)b); } static void jabber_si_xfer_bytestreams_send_connected_cb(gpointer data, gint source, PurpleInputCondition cond) { PurpleXfer *xfer = data; JabberSIXfer *jsx = xfer->data; int acceptfd, flags; purple_debug_info("jabber", "in jabber_si_xfer_bytestreams_send_connected_cb\n"); acceptfd = accept(source, NULL, 0); if(acceptfd == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) return; else if(acceptfd == -1) { purple_debug_warning("jabber", "accept: %s\n", g_strerror(errno)); /* Don't cancel the ft - allow it to fall to the next streamhost.*/ return; } purple_input_remove(xfer->watcher); close(source); jsx->local_streamhost_fd = -1; flags = fcntl(acceptfd, F_GETFL); fcntl(acceptfd, F_SETFL, flags | O_NONBLOCK); #ifndef _WIN32 fcntl(acceptfd, F_SETFD, FD_CLOEXEC); #endif xfer->watcher = purple_input_add(acceptfd, PURPLE_INPUT_READ, jabber_si_xfer_bytestreams_send_read_cb, xfer); } static void jabber_si_connect_proxy_cb(JabberStream *js, const char *from, JabberIqType type, const char *id, xmlnode *packet, gpointer data) { PurpleXfer *xfer = data; JabberSIXfer *jsx; xmlnode *query, *streamhost_used; const char *jid; GList *matched; /* TODO: This need to send errors if we don't see what we're looking for */ /* Make sure that the xfer is actually still valid and we're not just receiving an old iq response */ if (!g_list_find(js->file_transfers, xfer)) { purple_debug_error("jabber", "Got bytestreams response for no longer existing xfer (%p)\n", xfer); return; } /* In the case of a direct file transfer, this is expected to return */ if(!xfer->data) return; jsx = xfer->data; if(type != JABBER_IQ_RESULT) { purple_debug_info("jabber", "jabber_si_xfer_connect_proxy_cb: type = error\n"); /* if IBB is available, open IBB session */ purple_debug_info("jabber", "jabber_si_xfer_connect_proxy_cb: got error, method: %d\n", jsx->stream_method); if (jsx->stream_method & STREAM_METHOD_IBB) { purple_debug_info("jabber", "IBB is possible, try it\n"); /* if we are the sender and haven't already opened an IBB session, do so now (we might already have failed to open the bytestream proxy ourselves when receiving this <iq/> */ if (purple_xfer_get_type(xfer) == PURPLE_XFER_SEND && !jsx->ibb_session) { jabber_si_xfer_ibb_send_init(js, xfer); } else { jsx->ibb_timeout_handle = purple_timeout_add_seconds(30, jabber_si_bytestreams_ibb_timeout_cb, xfer); } /* if we are receiver, just wait for IBB open stanza, callback is already set up */ } else { purple_xfer_cancel_remote(xfer); } return; } if (!from) return; if(!(query = xmlnode_get_child(packet, "query"))) return; if(!(streamhost_used = xmlnode_get_child(query, "streamhost-used"))) return; if(!(jid = xmlnode_get_attrib(streamhost_used, "jid"))) return; purple_debug_info("jabber", "jabber_si_connect_proxy_cb() will be looking at jsx %p: jsx->streamhosts is %p and jid is %s\n", jsx, jsx->streamhosts, jid); if(!(matched = g_list_find_custom(jsx->streamhosts, jid, jabber_si_compare_jid))) { gchar *my_jid = g_strdup_printf("%s@%s/%s", jsx->js->user->node, jsx->js->user->domain, jsx->js->user->resource); if (!strcmp(jid, my_jid)) { purple_debug_info("jabber", "Got local SOCKS5 streamhost-used.\n"); purple_xfer_start(xfer, xfer->fd, NULL, -1); } else { /* if available, try to revert to IBB... */ if (jsx->stream_method & STREAM_METHOD_IBB) { purple_debug_info("jabber", "jabber_si_connect_proxy_cb: trying to revert to IBB\n"); if (purple_xfer_get_type(xfer) == PURPLE_XFER_SEND) { jabber_si_xfer_ibb_send_init(jsx->js, xfer); } else { jsx->ibb_timeout_handle = purple_timeout_add_seconds(30, jabber_si_bytestreams_ibb_timeout_cb, xfer); } /* if we are the receiver, we are already set up...*/ } else { purple_debug_info("jabber", "streamhost-used does not match any proxy that was offered to target\n"); purple_xfer_cancel_local(xfer); } } g_free(my_jid); return; } /* Clean up the local streamhost - it isn't going to be used.*/ if (xfer->watcher > 0) { purple_input_remove(xfer->watcher); xfer->watcher = 0; } if (jsx->local_streamhost_fd >= 0) { close(jsx->local_streamhost_fd); jsx->local_streamhost_fd = -1; } jsx->streamhosts = g_list_remove_link(jsx->streamhosts, matched); g_list_foreach(jsx->streamhosts, jabber_si_free_streamhost, NULL); g_list_free(jsx->streamhosts); jsx->streamhosts = matched; jabber_si_bytestreams_attempt_connect(xfer); } static void jabber_si_xfer_bytestreams_listen_cb(int sock, gpointer data) { PurpleXfer *xfer = data; JabberSIXfer *jsx; JabberIq *iq; xmlnode *query, *streamhost; char port[6]; GList *tmp; JabberBytestreamsStreamhost *sh, *sh2; int streamhost_count = 0; jsx = xfer->data; jsx->listen_data = NULL; /* I'm not sure under which conditions this can happen * (it seems like it shouldn't be possible */ if (purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_CANCEL_LOCAL) { purple_xfer_unref(xfer); return; } purple_xfer_unref(xfer); iq = jabber_iq_new_query(jsx->js, JABBER_IQ_SET, "http://jabber.org/protocol/bytestreams"); xmlnode_set_attrib(iq->node, "to", xfer->who); query = xmlnode_get_child(iq->node, "query"); xmlnode_set_attrib(query, "sid", jsx->stream_id); /* If we successfully started listening locally */ if (sock >= 0) { gchar *jid; const char *local_ip, *public_ip; jsx->local_streamhost_fd = sock; jid = g_strdup_printf("%s@%s/%s", jsx->js->user->node, jsx->js->user->domain, jsx->js->user->resource); xfer->local_port = purple_network_get_port_from_fd(sock); g_snprintf(port, sizeof(port), "%hu", xfer->local_port); /* Include the localhost's IP (for in-network transfers) */ local_ip = purple_network_get_local_system_ip(jsx->js->fd); if (strcmp(local_ip, "0.0.0.0") != 0) { streamhost_count++; streamhost = xmlnode_new_child(query, "streamhost"); xmlnode_set_attrib(streamhost, "jid", jid); xmlnode_set_attrib(streamhost, "host", local_ip); xmlnode_set_attrib(streamhost, "port", port); } /* Include the public IP (assuming that there is a port mapped somehow) */ public_ip = purple_network_get_my_ip(jsx->js->fd); if (strcmp(public_ip, local_ip) != 0 && strcmp(public_ip, "0.0.0.0") != 0) { streamhost_count++; streamhost = xmlnode_new_child(query, "streamhost"); xmlnode_set_attrib(streamhost, "jid", jid); xmlnode_set_attrib(streamhost, "host", public_ip); xmlnode_set_attrib(streamhost, "port", port); } g_free(jid); /* The listener for the local proxy */ xfer->watcher = purple_input_add(sock, PURPLE_INPUT_READ, jabber_si_xfer_bytestreams_send_connected_cb, xfer); } for (tmp = jsx->js->bs_proxies; tmp; tmp = tmp->next) { sh = tmp->data; /* TODO: deal with zeroconf proxies */ if (!(sh->jid && sh->host && sh->port > 0)) continue; purple_debug_info("jabber", "jabber_si_xfer_bytestreams_listen_cb() will be looking at jsx %p: jsx->streamhosts %p and sh->jid %p\n", jsx, jsx->streamhosts, sh->jid); if(g_list_find_custom(jsx->streamhosts, sh->jid, jabber_si_compare_jid) != NULL) continue; streamhost_count++; streamhost = xmlnode_new_child(query, "streamhost"); xmlnode_set_attrib(streamhost, "jid", sh->jid); xmlnode_set_attrib(streamhost, "host", sh->host); g_snprintf(port, sizeof(port), "%hu", sh->port); xmlnode_set_attrib(streamhost, "port", port); sh2 = g_new0(JabberBytestreamsStreamhost, 1); sh2->jid = g_strdup(sh->jid); sh2->host = g_strdup(sh->host); /*sh2->zeroconf = g_strdup(sh->zeroconf);*/ sh2->port = sh->port; jsx->streamhosts = g_list_prepend(jsx->streamhosts, sh2); } /* We have no way of transferring, cancel the transfer */ if (streamhost_count == 0) { jabber_iq_free(iq); /* if available, revert to IBB */ if (jsx->stream_method & STREAM_METHOD_IBB) { purple_debug_info("jabber", "jabber_si_xfer_bytestreams_listen_cb: trying to revert to IBB\n"); if (purple_xfer_get_type(xfer) == PURPLE_XFER_SEND) { /* if we are the sender, init the IBB session... */ jabber_si_xfer_ibb_send_init(jsx->js, xfer); } else { jsx->ibb_timeout_handle = purple_timeout_add_seconds(30, jabber_si_bytestreams_ibb_timeout_cb, xfer); } /* if we are the receiver, we should just wait... the IBB open handler has already been set up... */ } else { /* We should probably notify the target, but this really shouldn't ever happen */ purple_xfer_cancel_local(xfer); } return; } jabber_iq_set_callback(iq, jabber_si_connect_proxy_cb, xfer); jabber_iq_send(iq); } static void jabber_si_xfer_bytestreams_send_init(PurpleXfer *xfer) { JabberSIXfer *jsx; purple_xfer_ref(xfer); jsx = xfer->data; /* TODO: Should there be an option to not use the local host as a ft proxy? * (to prevent revealing IP address, etc.) */ jsx->listen_data = purple_network_listen_range(0, 0, SOCK_STREAM, jabber_si_xfer_bytestreams_listen_cb, xfer); if (jsx->listen_data == NULL) { /* We couldn't open a local port. Perhaps we can use a proxy. */ jabber_si_xfer_bytestreams_listen_cb(-1, xfer); } } static void jabber_si_xfer_ibb_error_cb(JabberIBBSession *sess) { PurpleXfer *xfer = (PurpleXfer *) jabber_ibb_session_get_user_data(sess); JabberStream *js = jabber_ibb_session_get_js(sess); PurpleConnection *gc = js->gc; PurpleAccount *account = purple_connection_get_account(gc); purple_debug_error("jabber", "an error occurred during IBB file transfer\n"); purple_xfer_error(purple_xfer_get_type(xfer), account, jabber_ibb_session_get_who(sess), _("An error occurred on the in-band bytestream transfer\n")); purple_xfer_cancel_remote(xfer); } static void jabber_si_xfer_ibb_closed_cb(JabberIBBSession *sess) { PurpleXfer *xfer = (PurpleXfer *) jabber_ibb_session_get_user_data(sess); JabberStream *js = jabber_ibb_session_get_js(sess); PurpleConnection *gc = js->gc; PurpleAccount *account = purple_connection_get_account(gc); purple_debug_info("jabber", "the remote user closed the transfer\n"); if (purple_xfer_get_bytes_remaining(xfer) > 0) { purple_xfer_error(purple_xfer_get_type(xfer), account, jabber_ibb_session_get_who(sess), _("Transfer was closed.")); purple_xfer_cancel_remote(xfer); } else { purple_xfer_set_completed(xfer, TRUE); purple_xfer_end(xfer); } } static void jabber_si_xfer_ibb_recv_data_cb(JabberIBBSession *sess, gpointer data, gsize size) { PurpleXfer *xfer = (PurpleXfer *) jabber_ibb_session_get_user_data(sess); JabberSIXfer *jsx = (JabberSIXfer *) xfer->data; if (size <= purple_xfer_get_bytes_remaining(xfer)) { purple_debug_info("jabber", "about to write %" G_GSIZE_FORMAT " bytes from IBB stream\n", size); purple_circ_buffer_append(jsx->ibb_buffer, data, size); purple_xfer_prpl_ready(xfer); } else { /* trying to write past size of file transfers negotiated size, reject transfer to protect against malicious behaviour */ purple_debug_error("jabber", "IBB file transfer send more data than expected\n"); purple_xfer_cancel_remote(xfer); } } static gssize jabber_si_xfer_ibb_read(guchar **out_buffer, PurpleXfer *xfer) { JabberSIXfer *jsx = xfer->data; guchar *buffer; gsize size; gsize tmp; size = jsx->ibb_buffer->bufused; *out_buffer = buffer = g_malloc(size); while ((tmp = purple_circ_buffer_get_max_read(jsx->ibb_buffer))) { memcpy(buffer, jsx->ibb_buffer->outptr, tmp); buffer += tmp; purple_circ_buffer_mark_read(jsx->ibb_buffer, tmp); } return size; } static gboolean jabber_si_xfer_ibb_open_cb(JabberStream *js, const char *who, const char *id, xmlnode *open) { const gchar *sid = xmlnode_get_attrib(open, "sid"); PurpleXfer *xfer = jabber_si_xfer_find(js, sid, who); if (xfer) { JabberSIXfer *jsx = (JabberSIXfer *) xfer->data; JabberIBBSession *sess = jabber_ibb_session_create_from_xmlnode(js, who, id, open, xfer); jabber_si_bytestreams_ibb_timeout_remove(jsx); if (sess) { /* setup callbacks here...*/ jabber_ibb_session_set_data_received_callback(sess, jabber_si_xfer_ibb_recv_data_cb); jabber_ibb_session_set_closed_callback(sess, jabber_si_xfer_ibb_closed_cb); jabber_ibb_session_set_error_callback(sess, jabber_si_xfer_ibb_error_cb); jsx->ibb_session = sess; jsx->ibb_buffer = purple_circ_buffer_new(jabber_ibb_session_get_block_size(sess)); /* set up read function */ purple_xfer_set_read_fnc(xfer, jabber_si_xfer_ibb_read); /* start the transfer */ purple_xfer_start(xfer, -1, NULL, 0); return TRUE; } else { /* failed to create IBB session */ purple_debug_error("jabber", "failed to create IBB session\n"); purple_xfer_cancel_remote(xfer); return FALSE; } } else { /* we got an IBB <open/> for an unknown file transfer, pass along... */ purple_debug_info("jabber", "IBB open did not match any SI file transfer\n"); return FALSE; } } static gssize jabber_si_xfer_ibb_write(const guchar *buffer, size_t len, PurpleXfer *xfer) { JabberSIXfer *jsx = (JabberSIXfer *) xfer->data; JabberIBBSession *sess = jsx->ibb_session; gsize packet_size = len < jabber_ibb_session_get_block_size(sess) ? len : jabber_ibb_session_get_block_size(sess); jabber_ibb_session_send_data(sess, buffer, packet_size); return packet_size; } static void jabber_si_xfer_ibb_sent_cb(JabberIBBSession *sess) { PurpleXfer *xfer = (PurpleXfer *) jabber_ibb_session_get_user_data(sess); gsize remaining = purple_xfer_get_bytes_remaining(xfer); if (remaining == 0) { /* close the session */ jabber_ibb_session_close(sess); purple_xfer_set_completed(xfer, TRUE); purple_xfer_end(xfer); } else { /* send more... */ purple_xfer_prpl_ready(xfer); } } static void jabber_si_xfer_ibb_opened_cb(JabberIBBSession *sess) { PurpleXfer *xfer = (PurpleXfer *) jabber_ibb_session_get_user_data(sess); JabberStream *js = jabber_ibb_session_get_js(sess); PurpleConnection *gc = js->gc; PurpleAccount *account = purple_connection_get_account(gc); if (jabber_ibb_session_get_state(sess) == JABBER_IBB_SESSION_OPENED) { purple_xfer_start(xfer, -1, NULL, 0); purple_xfer_prpl_ready(xfer); } else { /* error */ purple_xfer_error(purple_xfer_get_type(xfer), account, jabber_ibb_session_get_who(sess), _("Failed to open in-band bytestream")); purple_xfer_end(xfer); } } static void jabber_si_xfer_ibb_send_init(JabberStream *js, PurpleXfer *xfer) { JabberSIXfer *jsx = (JabberSIXfer *) xfer->data; jsx->ibb_session = jabber_ibb_session_create(js, jsx->stream_id, purple_xfer_get_remote_user(xfer), xfer); if (jsx->ibb_session) { /* should set callbacks here... */ jabber_ibb_session_set_opened_callback(jsx->ibb_session, jabber_si_xfer_ibb_opened_cb); jabber_ibb_session_set_data_sent_callback(jsx->ibb_session, jabber_si_xfer_ibb_sent_cb); jabber_ibb_session_set_closed_callback(jsx->ibb_session, jabber_si_xfer_ibb_closed_cb); jabber_ibb_session_set_error_callback(jsx->ibb_session, jabber_si_xfer_ibb_error_cb); purple_xfer_set_write_fnc(xfer, jabber_si_xfer_ibb_write); jsx->ibb_buffer = purple_circ_buffer_new(jabber_ibb_session_get_block_size(jsx->ibb_session)); /* open the IBB session */ jabber_ibb_session_open(jsx->ibb_session); } else { /* failed to create IBB session */ purple_debug_error("jabber", "failed to initiate IBB session for file transfer\n"); purple_xfer_cancel_local(xfer); } } static void jabber_si_xfer_send_method_cb(JabberStream *js, const char *from, JabberIqType type, const char *id, xmlnode *packet, gpointer data) { PurpleXfer *xfer = data; xmlnode *si, *feature, *x, *field, *value; gboolean found_method = FALSE; if(!(si = xmlnode_get_child_with_namespace(packet, "si", "http://jabber.org/protocol/si"))) { purple_xfer_cancel_remote(xfer); return; } if(!(feature = xmlnode_get_child_with_namespace(si, "feature", "http://jabber.org/protocol/feature-neg"))) { purple_xfer_cancel_remote(xfer); return; } if(!(x = xmlnode_get_child_with_namespace(feature, "x", "jabber:x:data"))) { purple_xfer_cancel_remote(xfer); return; } for(field = xmlnode_get_child(x, "field"); field; field = xmlnode_get_next_twin(field)) { const char *var = xmlnode_get_attrib(field, "var"); JabberSIXfer *jsx = (JabberSIXfer *) xfer->data; if(var && !strcmp(var, "stream-method")) { if((value = xmlnode_get_child(field, "value"))) { char *val = xmlnode_get_data(value); if(val && !strcmp(val, "http://jabber.org/protocol/bytestreams")) { jabber_si_xfer_bytestreams_send_init(xfer); jsx->stream_method |= STREAM_METHOD_BYTESTREAMS; found_method = TRUE; } else if (val && !strcmp(val, XEP_0047_NAMESPACE)) { jsx->stream_method |= STREAM_METHOD_IBB; if (!found_method) { /* we haven't tried to init a bytestream session, yet start IBB right away... */ jabber_si_xfer_ibb_send_init(js, xfer); found_method = TRUE; } } g_free(val); } } } if (!found_method) { purple_xfer_cancel_remote(xfer); } } static void jabber_si_xfer_send_request(PurpleXfer *xfer) { JabberSIXfer *jsx = xfer->data; JabberIq *iq; xmlnode *si, *file, *feature, *x, *field, *option, *value; char buf[32]; xfer->filename = g_path_get_basename(xfer->local_filename); iq = jabber_iq_new(jsx->js, JABBER_IQ_SET); xmlnode_set_attrib(iq->node, "to", xfer->who); si = xmlnode_new_child(iq->node, "si"); xmlnode_set_namespace(si, "http://jabber.org/protocol/si"); jsx->stream_id = jabber_get_next_id(jsx->js); xmlnode_set_attrib(si, "id", jsx->stream_id); xmlnode_set_attrib(si, "profile", "http://jabber.org/protocol/si/profile/file-transfer"); file = xmlnode_new_child(si, "file"); xmlnode_set_namespace(file, "http://jabber.org/protocol/si/profile/file-transfer"); xmlnode_set_attrib(file, "name", xfer->filename); g_snprintf(buf, sizeof(buf), "%" G_GSIZE_FORMAT, xfer->size); xmlnode_set_attrib(file, "size", buf); /* maybe later we'll do hash and date attribs */ feature = xmlnode_new_child(si, "feature"); xmlnode_set_namespace(feature, "http://jabber.org/protocol/feature-neg"); x = xmlnode_new_child(feature, "x"); xmlnode_set_namespace(x, "jabber:x:data"); xmlnode_set_attrib(x, "type", "form"); field = xmlnode_new_child(x, "field"); xmlnode_set_attrib(field, "var", "stream-method"); xmlnode_set_attrib(field, "type", "list-single"); /* maybe we should add an option to always skip bytestreams for people behind troublesome firewalls */ option = xmlnode_new_child(field, "option"); value = xmlnode_new_child(option, "value"); xmlnode_insert_data(value, "http://jabber.org/protocol/bytestreams", -1); option = xmlnode_new_child(field, "option"); value = xmlnode_new_child(option, "value"); xmlnode_insert_data(value, "http://jabber.org/protocol/ibb", -1); jabber_iq_set_callback(iq, jabber_si_xfer_send_method_cb, xfer); /* Store the IQ id so that we can cancel the callback */ g_free(jsx->iq_id); jsx->iq_id = g_strdup(iq->id); jabber_iq_send(iq); } static void jabber_si_xfer_free(PurpleXfer *xfer) { JabberSIXfer *jsx = xfer->data; if (jsx) { JabberStream *js = jsx->js; js->file_transfers = g_list_remove(js->file_transfers, xfer); if (jsx->connect_data != NULL) purple_proxy_connect_cancel(jsx->connect_data); if (jsx->listen_data != NULL) purple_network_listen_cancel(jsx->listen_data); if (jsx->iq_id != NULL) jabber_iq_remove_callback_by_id(js, jsx->iq_id); if (jsx->local_streamhost_fd >= 0) close(jsx->local_streamhost_fd); if (purple_xfer_get_type(xfer) == PURPLE_XFER_SEND && xfer->fd >= 0) { purple_debug_info("jabber", "remove port mapping\n"); purple_network_remove_port_mapping(xfer->fd); } if (jsx->connect_timeout > 0) purple_timeout_remove(jsx->connect_timeout); if (jsx->ibb_timeout_handle > 0) purple_timeout_remove(jsx->ibb_timeout_handle); if (jsx->streamhosts) { g_list_foreach(jsx->streamhosts, jabber_si_free_streamhost, NULL); g_list_free(jsx->streamhosts); } if (jsx->ibb_session) { purple_debug_info("jabber", "jabber_si_xfer_free: destroying IBB session\n"); jabber_ibb_session_destroy(jsx->ibb_session); } if (jsx->ibb_buffer) { purple_circ_buffer_destroy(jsx->ibb_buffer); } purple_debug_info("jabber", "jabber_si_xfer_free(): freeing jsx %p\n", jsx); g_free(jsx->stream_id); g_free(jsx->iq_id); /* XXX: free other stuff */ g_free(jsx->rxqueue); g_free(jsx); xfer->data = NULL; } } /* * These four functions should only be called from the PurpleXfer functions * (typically purple_xfer_cancel_(remote|local), purple_xfer_end, or * purple_xfer_request_denied. */ static void jabber_si_xfer_cancel_send(PurpleXfer *xfer) { JabberSIXfer *jsx = (JabberSIXfer *) xfer->data; /* if there is an IBB session active, send close on that */ if (jsx->ibb_session) { jabber_ibb_session_close(jsx->ibb_session); } jabber_si_xfer_free(xfer); purple_debug(PURPLE_DEBUG_INFO, "jabber", "in jabber_si_xfer_cancel_send\n"); } static void jabber_si_xfer_request_denied(PurpleXfer *xfer) { JabberSIXfer *jsx = (JabberSIXfer *) xfer->data; JabberStream *js = jsx->js; /* * TODO: It's probably an error if jsx->iq_id == NULL. g_return_if_fail * might be warranted. */ if (jsx->iq_id && !jsx->accepted) { JabberIq *iq; xmlnode *error, *child; iq = jabber_iq_new(js, JABBER_IQ_ERROR); xmlnode_set_attrib(iq->node, "to", xfer->who); jabber_iq_set_id(iq, jsx->iq_id); error = xmlnode_new_child(iq->node, "error"); xmlnode_set_attrib(error, "type", "cancel"); child = xmlnode_new_child(error, "forbidden"); xmlnode_set_namespace(child, "urn:ietf:params:xml:ns:xmpp-stanzas"); child = xmlnode_new_child(error, "text"); xmlnode_set_namespace(child, "urn:ietf:params:xml:ns:xmpp-stanzas"); xmlnode_insert_data(child, "Offer Declined", -1); jabber_iq_send(iq); } jabber_si_xfer_free(xfer); purple_debug(PURPLE_DEBUG_INFO, "jabber", "in jabber_si_xfer_request_denied\n"); } static void jabber_si_xfer_cancel_recv(PurpleXfer *xfer) { JabberSIXfer *jsx = (JabberSIXfer *) xfer->data; /* if there is an IBB session active, send close */ if (jsx->ibb_session) { jabber_ibb_session_close(jsx->ibb_session); } jabber_si_xfer_free(xfer); purple_debug(PURPLE_DEBUG_INFO, "jabber", "in jabber_si_xfer_cancel_recv\n"); } static void jabber_si_xfer_end(PurpleXfer *xfer) { jabber_si_xfer_free(xfer); } static void jabber_si_xfer_send_disco_cb(JabberStream *js, const char *who, JabberCapabilities capabilities, gpointer data) { PurpleXfer *xfer = (PurpleXfer *) data; JabberSIXfer *jsx = (JabberSIXfer *) xfer->data; if (capabilities & JABBER_CAP_IBB) { purple_debug_info("jabber", "jabber_si_xfer_send_disco_cb: remote JID supports IBB\n"); jsx->stream_method |= STREAM_METHOD_IBB; } if (capabilities & JABBER_CAP_SI_FILE_XFER) { jabber_si_xfer_send_request(xfer); } else { char *msg = g_strdup_printf(_("Unable to send file to %s, user does not support file transfers"), who); purple_notify_error(js->gc, _("File Send Failed"), _("File Send Failed"), msg); g_free(msg); purple_xfer_cancel_local(xfer); } } static void resource_select_cancel_cb(PurpleXfer *xfer, PurpleRequestFields *fields) { purple_xfer_cancel_local(xfer); } static void do_transfer_send(PurpleXfer *xfer, const char *resource) { JabberSIXfer *jsx = xfer->data; char **who_v = g_strsplit(xfer->who, "/", 2); char *who; JabberBuddy *jb; JabberBuddyResource *jbr = NULL; jb = jabber_buddy_find(jsx->js, who_v[0], FALSE); if (jb) { jbr = jabber_buddy_find_resource(jb, resource); } who = g_strdup_printf("%s/%s", who_v[0], resource); g_strfreev(who_v); g_free(xfer->who); xfer->who = who; if (jbr) { char *msg; if (jabber_resource_has_capability(jbr, XEP_0047_NAMESPACE)) jsx->stream_method |= STREAM_METHOD_IBB; if (jabber_resource_has_capability(jbr, "http://jabber.org/protocol/si/profile/file-transfer")) { jabber_si_xfer_send_request(xfer); return; } msg = g_strdup_printf(_("Unable to send file to %s, user does not support file transfers"), who); purple_notify_error(jsx->js->gc, _("File Send Failed"), _("File Send Failed"), msg); g_free(msg); purple_xfer_cancel_local(xfer); } else { jabber_disco_info_do(jsx->js, who, jabber_si_xfer_send_disco_cb, xfer); } } static void resource_select_ok_cb(PurpleXfer *xfer, PurpleRequestFields *fields) { PurpleRequestField *field = purple_request_fields_get_field(fields, "resource"); int selected_id = purple_request_field_choice_get_value(field); GList *labels = purple_request_field_choice_get_labels(field); const char *selected_label = g_list_nth_data(labels, selected_id); do_transfer_send(xfer, selected_label); } static void jabber_si_xfer_init(PurpleXfer *xfer) { JabberSIXfer *jsx = xfer->data; JabberIq *iq; if(purple_xfer_get_type(xfer) == PURPLE_XFER_SEND) { JabberBuddy *jb; JabberBuddyResource *jbr = NULL; char *resource; if(NULL != (resource = jabber_get_resource(xfer->who))) { /* they've specified a resource, no need to ask or * default or anything, just do it */ do_transfer_send(xfer, resource); g_free(resource); return; } jb = jabber_buddy_find(jsx->js, xfer->who, TRUE); if(!jb || !jb->resources) { /* no resources online, we're trying to send to someone * whose presence we're not subscribed to, or * someone who is offline. Let's inform the user */ char *msg; if(!jb) { msg = g_strdup_printf(_("Unable to send file to %s, invalid JID"), xfer->who); } else if(jb->subscription & JABBER_SUB_TO) { msg = g_strdup_printf(_("Unable to send file to %s, user is not online"), xfer->who); } else { msg = g_strdup_printf(_("Unable to send file to %s, not subscribed to user presence"), xfer->who); } purple_notify_error(jsx->js->gc, _("File Send Failed"), _("File Send Failed"), msg); g_free(msg); } else if(!jb->resources->next) { /* only 1 resource online (probably our most common case) * so no need to ask who to send to */ jbr = jb->resources->data; do_transfer_send(xfer, jbr->name); } else { /* we've got multiple resources, we need to pick one to send to */ GList *l; char *msg = g_strdup_printf(_("Please select the resource of %s to which you would like to send a file"), xfer->who); PurpleRequestFields *fields = purple_request_fields_new(); PurpleRequestField *field = purple_request_field_choice_new("resource", _("Resource"), 0); PurpleRequestFieldGroup *group = purple_request_field_group_new(NULL); for(l = jb->resources; l; l = l->next) { jbr = l->data; purple_request_field_choice_add(field, jbr->name); } purple_request_field_group_add_field(group, field); purple_request_fields_add_group(fields, group); purple_request_fields(jsx->js->gc, _("Select a Resource"), msg, NULL, fields, _("Send File"), G_CALLBACK(resource_select_ok_cb), _("Cancel"), G_CALLBACK(resource_select_cancel_cb), jsx->js->gc->account, xfer->who, NULL, xfer); g_free(msg); } } else { xmlnode *si, *feature, *x, *field, *value; iq = jabber_iq_new(jsx->js, JABBER_IQ_RESULT); xmlnode_set_attrib(iq->node, "to", xfer->who); if(jsx->iq_id) jabber_iq_set_id(iq, jsx->iq_id); else purple_debug_error("jabber", "Sending SI result with new IQ id.\n"); jsx->accepted = TRUE; si = xmlnode_new_child(iq->node, "si"); xmlnode_set_namespace(si, "http://jabber.org/protocol/si"); feature = xmlnode_new_child(si, "feature"); xmlnode_set_namespace(feature, "http://jabber.org/protocol/feature-neg"); x = xmlnode_new_child(feature, "x"); xmlnode_set_namespace(x, "jabber:x:data"); xmlnode_set_attrib(x, "type", "submit"); field = xmlnode_new_child(x, "field"); xmlnode_set_attrib(field, "var", "stream-method"); /* we should maybe "remember" if bytestreams has failed before (in the same session) with this JID, and only present IBB as an option to avoid unnessesary timeout */ /* maybe we should have an account option to always just try IBB for people who know their firewalls are very restrictive */ if (jsx->stream_method & STREAM_METHOD_BYTESTREAMS) { value = xmlnode_new_child(field, "value"); xmlnode_insert_data(value, "http://jabber.org/protocol/bytestreams", -1); } else if(jsx->stream_method & STREAM_METHOD_IBB) { value = xmlnode_new_child(field, "value"); xmlnode_insert_data(value, "http://jabber.org/protocol/ibb", -1); } jabber_iq_send(iq); } } PurpleXfer *jabber_si_new_xfer(PurpleConnection *gc, const char *who) { JabberStream *js; PurpleXfer *xfer; JabberSIXfer *jsx; js = gc->proto_data; xfer = purple_xfer_new(gc->account, PURPLE_XFER_SEND, who); if (xfer) { xfer->data = jsx = g_new0(JabberSIXfer, 1); jsx->js = js; jsx->local_streamhost_fd = -1; jsx->ibb_session = NULL; purple_xfer_set_init_fnc(xfer, jabber_si_xfer_init); purple_xfer_set_cancel_send_fnc(xfer, jabber_si_xfer_cancel_send); purple_xfer_set_end_fnc(xfer, jabber_si_xfer_end); js->file_transfers = g_list_append(js->file_transfers, xfer); } return xfer; } void jabber_si_xfer_send(PurpleConnection *gc, const char *who, const char *file) { JabberStream *js; PurpleXfer *xfer; js = gc->proto_data; xfer = jabber_si_new_xfer(gc, who); if (file) purple_xfer_request_accepted(xfer, file); else purple_xfer_request(xfer); } void jabber_si_parse(JabberStream *js, const char *from, JabberIqType type, const char *id, xmlnode *si) { JabberSIXfer *jsx; PurpleXfer *xfer; xmlnode *file, *feature, *x, *field, *option, *value; const char *stream_id, *filename, *filesize_c, *profile; size_t filesize = 0; if(!(profile = xmlnode_get_attrib(si, "profile")) || strcmp(profile, "http://jabber.org/protocol/si/profile/file-transfer")) return; if(!(stream_id = xmlnode_get_attrib(si, "id"))) return; if(!(file = xmlnode_get_child(si, "file"))) return; if(!(filename = xmlnode_get_attrib(file, "name"))) return; if((filesize_c = xmlnode_get_attrib(file, "size"))) filesize = atoi(filesize_c); if(!(feature = xmlnode_get_child(si, "feature"))) return; if(!(x = xmlnode_get_child_with_namespace(feature, "x", "jabber:x:data"))) return; if(!from) return; /* if they've already sent us this file transfer with the same damn id * then we're gonna ignore it, until I think of something better to do * with it */ if((xfer = jabber_si_xfer_find(js, stream_id, from))) return; jsx = g_new0(JabberSIXfer, 1); jsx->local_streamhost_fd = -1; jsx->ibb_session = NULL; for(field = xmlnode_get_child(x, "field"); field; field = xmlnode_get_next_twin(field)) { const char *var = xmlnode_get_attrib(field, "var"); if(var && !strcmp(var, "stream-method")) { for(option = xmlnode_get_child(field, "option"); option; option = xmlnode_get_next_twin(option)) { if((value = xmlnode_get_child(option, "value"))) { char *val; if((val = xmlnode_get_data(value))) { if(!strcmp(val, "http://jabber.org/protocol/bytestreams")) { jsx->stream_method |= STREAM_METHOD_BYTESTREAMS; } else if(!strcmp(val, "http://jabber.org/protocol/ibb")) { jsx->stream_method |= STREAM_METHOD_IBB; } g_free(val); } } } } } if(jsx->stream_method == STREAM_METHOD_UNKNOWN) { g_free(jsx); return; } jsx->js = js; jsx->stream_id = g_strdup(stream_id); jsx->iq_id = g_strdup(id); xfer = purple_xfer_new(js->gc->account, PURPLE_XFER_RECEIVE, from); g_return_if_fail(xfer != NULL); xfer->data = jsx; purple_xfer_set_filename(xfer, filename); if(filesize > 0) purple_xfer_set_size(xfer, filesize); purple_xfer_set_init_fnc(xfer, jabber_si_xfer_init); purple_xfer_set_request_denied_fnc(xfer, jabber_si_xfer_request_denied); purple_xfer_set_cancel_recv_fnc(xfer, jabber_si_xfer_cancel_recv); purple_xfer_set_end_fnc(xfer, jabber_si_xfer_end); js->file_transfers = g_list_append(js->file_transfers, xfer); purple_xfer_request(xfer); } void jabber_si_init(void) { jabber_iq_register_handler("si", "http://jabber.org/protocol/si", jabber_si_parse); jabber_ibb_register_open_handler(jabber_si_xfer_ibb_open_cb); } void jabber_si_uninit(void) { jabber_ibb_unregister_open_handler(jabber_si_xfer_ibb_open_cb); }