Migrate certificates, icons, logs to XDG dirs
[pidgin-git.git] / libpurple / protocols / jabber / si.c
blob8ae46e8958e1902655975a4bf57202a7416d7509
1 /*
2 * purple - Jabber Protocol Plugin
4 * Purple is the legal property of its developers, whose names are too numerous
5 * to list here. Please refer to the COPYRIGHT file distributed with this
6 * source distribution.
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
25 #include "internal.h"
27 #include "buddylist.h"
28 #include "debug.h"
29 #include "xfer.h"
30 #include "request.h"
31 #include "network.h"
32 #include "notify.h"
34 #include "buddy.h"
35 #include "data.h"
36 #include "disco.h"
37 #include "jabber.h"
38 #include "ibb.h"
39 #include "iq.h"
40 #include "si.h"
42 #define STREAMHOST_CONNECT_TIMEOUT 15
43 #define ENABLE_FT_THUMBNAILS 0
45 typedef struct _JabberSIXfer {
46 JabberStream *js;
48 PurpleProxyConnectData *connect_data;
49 PurpleNetworkListenData *listen_data;
50 guint connect_timeout;
52 gboolean accepted;
54 char *stream_id;
55 char *iq_id;
57 enum {
58 STREAM_METHOD_UNKNOWN = 0,
59 STREAM_METHOD_BYTESTREAMS = 2 << 1,
60 STREAM_METHOD_IBB = 2 << 2,
61 STREAM_METHOD_UNSUPPORTED = 2 << 30
62 } stream_method;
64 GList *streamhosts;
65 PurpleProxyInfo *gpi;
67 char *rxqueue;
68 size_t rxlen;
69 gsize rxmaxlen;
70 int local_streamhost_fd;
72 JabberIBBSession *ibb_session;
73 guint ibb_timeout_handle;
74 PurpleCircularBuffer *ibb_buffer;
75 } JabberSIXfer;
77 /* some forward declarations */
78 static void jabber_si_xfer_ibb_send_init(JabberStream *js, PurpleXfer *xfer);
80 static PurpleXfer*
81 jabber_si_xfer_find(JabberStream *js, const char *sid, const char *from)
83 GList *xfers;
85 if(!sid || !from)
86 return NULL;
88 for(xfers = js->file_transfers; xfers; xfers = xfers->next) {
89 PurpleXfer *xfer = xfers->data;
90 JabberSIXfer *jsx = purple_xfer_get_protocol_data(xfer);
91 if(jsx->stream_id && purple_xfer_get_remote_user(xfer) &&
92 !strcmp(jsx->stream_id, sid) && !strcmp(purple_xfer_get_remote_user(xfer), from))
93 return xfer;
96 return NULL;
99 static void
100 jabber_si_free_streamhost(gpointer data, gpointer user_data)
102 JabberBytestreamsStreamhost *sh = data;
104 if(!data)
105 return;
107 g_free(sh->jid);
108 g_free(sh->host);
109 g_free(sh->zeroconf);
110 g_free(sh);
115 static void jabber_si_bytestreams_attempt_connect(PurpleXfer *xfer);
117 static void
118 jabber_si_bytestreams_connect_cb(gpointer data, gint source, const gchar *error_message)
120 PurpleXfer *xfer = data;
121 JabberSIXfer *jsx = purple_xfer_get_protocol_data(xfer);
122 JabberIq *iq;
123 PurpleXmlNode *query, *su;
124 JabberBytestreamsStreamhost *streamhost = jsx->streamhosts->data;
126 purple_proxy_info_destroy(jsx->gpi);
127 jsx->gpi = NULL;
128 jsx->connect_data = NULL;
130 if (jsx->connect_timeout > 0)
131 purple_timeout_remove(jsx->connect_timeout);
132 jsx->connect_timeout = 0;
134 if(source < 0) {
135 purple_debug_warning("jabber",
136 "si connection failed, jid was %s, host was %s, error was %s\n",
137 streamhost->jid, streamhost->host,
138 error_message ? error_message : "(null)");
139 jsx->streamhosts = g_list_remove(jsx->streamhosts, streamhost);
140 jabber_si_free_streamhost(streamhost, NULL);
141 jabber_si_bytestreams_attempt_connect(xfer);
142 return;
145 /* unknown file transfer type is assumed to be RECEIVE */
146 if(purple_xfer_get_xfer_type(xfer) == PURPLE_XFER_TYPE_SEND)
148 PurpleXmlNode *activate;
149 iq = jabber_iq_new_query(jsx->js, JABBER_IQ_SET, NS_BYTESTREAMS);
150 purple_xmlnode_set_attrib(iq->node, "to", streamhost->jid);
151 query = purple_xmlnode_get_child(iq->node, "query");
152 purple_xmlnode_set_attrib(query, "sid", jsx->stream_id);
153 activate = purple_xmlnode_new_child(query, "activate");
154 purple_xmlnode_insert_data(activate, purple_xfer_get_remote_user(xfer), -1);
156 /* TODO: We need to wait for an activation result before starting */
158 else
160 iq = jabber_iq_new_query(jsx->js, JABBER_IQ_RESULT, NS_BYTESTREAMS);
161 purple_xmlnode_set_attrib(iq->node, "to", purple_xfer_get_remote_user(xfer));
162 jabber_iq_set_id(iq, jsx->iq_id);
163 query = purple_xmlnode_get_child(iq->node, "query");
164 su = purple_xmlnode_new_child(query, "streamhost-used");
165 purple_xmlnode_set_attrib(su, "jid", streamhost->jid);
168 jabber_iq_send(iq);
170 purple_xfer_start(xfer, source, NULL, -1);
173 static gboolean
174 connect_timeout_cb(gpointer data)
176 PurpleXfer *xfer = data;
177 JabberSIXfer *jsx = purple_xfer_get_protocol_data(xfer);
179 purple_debug_info("jabber", "Streamhost connection timeout of %d seconds exceeded.\n", STREAMHOST_CONNECT_TIMEOUT);
181 jsx->connect_timeout = 0;
183 if (jsx->connect_data != NULL)
184 purple_proxy_connect_cancel(jsx->connect_data);
185 jsx->connect_data = NULL;
187 /* Trigger the connect error manually */
188 jabber_si_bytestreams_connect_cb(xfer, -1, "Timeout Exceeded.");
190 return FALSE;
193 static void
194 jabber_si_bytestreams_ibb_timeout_remove(JabberSIXfer *jsx)
196 if (jsx->ibb_timeout_handle) {
197 purple_timeout_remove(jsx->ibb_timeout_handle);
198 jsx->ibb_timeout_handle = 0;
202 static gboolean
203 jabber_si_bytestreams_ibb_timeout_cb(gpointer data)
205 PurpleXfer *xfer = (PurpleXfer *) data;
206 JabberSIXfer *jsx = purple_xfer_get_protocol_data(xfer);
208 if (jsx && !jsx->ibb_session) {
209 purple_debug_info("jabber",
210 "jabber_si_bytestreams_ibb_timeout called and IBB session not set "
211 " up yet, cancel transfer");
212 jabber_si_bytestreams_ibb_timeout_remove(jsx);
213 purple_xfer_cancel_local(xfer);
216 return FALSE;
219 static void jabber_si_bytestreams_attempt_connect(PurpleXfer *xfer)
221 JabberSIXfer *jsx = purple_xfer_get_protocol_data(xfer);
222 JabberBytestreamsStreamhost *streamhost;
223 JabberID *dstjid;
225 if(!jsx->streamhosts) {
226 JabberIq *iq = jabber_iq_new(jsx->js, JABBER_IQ_ERROR);
227 PurpleXmlNode *error, *inf;
229 if(jsx->iq_id)
230 jabber_iq_set_id(iq, jsx->iq_id);
232 purple_xmlnode_set_attrib(iq->node, "to", purple_xfer_get_remote_user(xfer));
233 error = purple_xmlnode_new_child(iq->node, "error");
234 purple_xmlnode_set_attrib(error, "code", "404");
235 purple_xmlnode_set_attrib(error, "type", "cancel");
236 inf = purple_xmlnode_new_child(error, "item-not-found");
237 purple_xmlnode_set_namespace(inf, NS_XMPP_STANZAS);
239 jabber_iq_send(iq);
241 /* if IBB is available, revert to that before giving up... */
242 if (jsx->stream_method & STREAM_METHOD_IBB) {
243 /* if we are the initializer, init IBB */
244 purple_debug_info("jabber",
245 "jabber_si_bytestreams_attempt_connect: "
246 "no streamhosts found, trying IBB\n");
247 /* if we are the sender, open an IBB session, but not if we already
248 did it, since we could have received the error <iq/> from the
249 receiver already... */
250 if (purple_xfer_get_xfer_type(xfer) == PURPLE_XFER_TYPE_SEND
251 && !jsx->ibb_session) {
252 jabber_si_xfer_ibb_send_init(jsx->js, xfer);
253 } else {
254 /* setup a timeout to cancel waiting for IBB open */
255 jsx->ibb_timeout_handle = purple_timeout_add_seconds(30,
256 jabber_si_bytestreams_ibb_timeout_cb, xfer);
258 /* if we are the receiver, just wait for IBB open, callback is
259 already set up... */
260 } else {
261 purple_xfer_cancel_local(xfer);
264 return;
267 streamhost = jsx->streamhosts->data;
269 if (jsx->connect_data) {
270 purple_debug_info("jabber",
271 "jabber_si_bytestreams_attempt_connect: "
272 "cancelling existing connection attempt and restarting\n");
273 purple_proxy_connect_cancel(jsx->connect_data);
274 jsx->connect_data = NULL;
275 if (jsx->connect_timeout > 0)
276 purple_timeout_remove(jsx->connect_timeout);
277 jsx->connect_timeout = 0;
279 if (jsx->gpi != NULL)
280 purple_proxy_info_destroy(jsx->gpi);
281 jsx->gpi = NULL;
283 dstjid = jabber_id_new(purple_xfer_get_remote_user(xfer));
285 /* TODO: Deal with zeroconf */
287 if(dstjid != NULL && streamhost->host && streamhost->port > 0) {
288 char *dstaddr, *hash;
289 PurpleAccount *account;
290 jsx->gpi = purple_proxy_info_new();
291 purple_proxy_info_set_proxy_type(jsx->gpi, PURPLE_PROXY_SOCKS5);
292 purple_proxy_info_set_host(jsx->gpi, streamhost->host);
293 purple_proxy_info_set_port(jsx->gpi, streamhost->port);
295 /* unknown file transfer type is assumed to be RECEIVE */
296 if(purple_xfer_get_xfer_type(xfer) == PURPLE_XFER_TYPE_SEND)
297 dstaddr = g_strdup_printf("%s%s@%s/%s%s@%s/%s", jsx->stream_id, jsx->js->user->node, jsx->js->user->domain,
298 jsx->js->user->resource, dstjid->node, dstjid->domain, dstjid->resource);
299 else
300 dstaddr = g_strdup_printf("%s%s@%s/%s%s@%s/%s", jsx->stream_id, dstjid->node, dstjid->domain, dstjid->resource,
301 jsx->js->user->node, jsx->js->user->domain, jsx->js->user->resource);
303 /* Per XEP-0065, the 'host' must be SHA1(SID + from JID + to JID) */
304 hash = jabber_calculate_data_hash(dstaddr, strlen(dstaddr), "sha1");
306 account = purple_connection_get_account(jsx->js->gc);
307 jsx->connect_data = purple_proxy_connect_socks5_account(NULL, account,
308 jsx->gpi, hash, 0,
309 jabber_si_bytestreams_connect_cb, xfer);
310 g_free(hash);
311 g_free(dstaddr);
313 /* When selecting a streamhost, timeout after STREAMHOST_CONNECT_TIMEOUT seconds, otherwise it takes forever */
314 if (purple_xfer_get_xfer_type(xfer) != PURPLE_XFER_TYPE_SEND && jsx->connect_data != NULL)
315 jsx->connect_timeout = purple_timeout_add_seconds(
316 STREAMHOST_CONNECT_TIMEOUT, connect_timeout_cb, xfer);
318 jabber_id_free(dstjid);
321 if (jsx->connect_data == NULL)
323 jsx->streamhosts = g_list_remove(jsx->streamhosts, streamhost);
324 jabber_si_free_streamhost(streamhost, NULL);
325 jabber_si_bytestreams_attempt_connect(xfer);
329 void jabber_bytestreams_parse(JabberStream *js, const char *from,
330 JabberIqType type, const char *id, PurpleXmlNode *query)
332 PurpleXfer *xfer;
333 JabberSIXfer *jsx;
334 PurpleXmlNode *streamhost;
335 const char *sid;
337 if(type != JABBER_IQ_SET)
338 return;
340 if(!from)
341 return;
343 if(!(sid = purple_xmlnode_get_attrib(query, "sid")))
344 return;
346 if(!(xfer = jabber_si_xfer_find(js, sid, from)))
347 return;
349 jsx = purple_xfer_get_protocol_data(xfer);
351 if(!jsx->accepted)
352 return;
354 g_free(jsx->iq_id);
355 jsx->iq_id = g_strdup(id);
357 for(streamhost = purple_xmlnode_get_child(query, "streamhost"); streamhost;
358 streamhost = purple_xmlnode_get_next_twin(streamhost)) {
359 const char *jid, *host = NULL, *port, *zeroconf;
360 int portnum = 0;
362 if((jid = purple_xmlnode_get_attrib(streamhost, "jid")) &&
363 ((zeroconf = purple_xmlnode_get_attrib(streamhost, "zeroconf")) ||
364 ((host = purple_xmlnode_get_attrib(streamhost, "host")) &&
365 (port = purple_xmlnode_get_attrib(streamhost, "port")) &&
366 (portnum = atoi(port))))) {
367 JabberBytestreamsStreamhost *sh = g_new0(JabberBytestreamsStreamhost, 1);
368 sh->jid = g_strdup(jid);
369 sh->host = g_strdup(host);
370 sh->port = portnum;
371 sh->zeroconf = g_strdup(zeroconf);
372 /* If there were a lot of these, it'd be worthwhile to prepend and reverse. */
373 jsx->streamhosts = g_list_append(jsx->streamhosts, sh);
377 jabber_si_bytestreams_attempt_connect(xfer);
381 static void
382 jabber_si_xfer_bytestreams_send_read_again_resp_cb(gpointer data, gint source,
383 PurpleInputCondition cond)
385 PurpleXfer *xfer = data;
386 JabberSIXfer *jsx = purple_xfer_get_protocol_data(xfer);
387 int len;
389 len = write(source, jsx->rxqueue + jsx->rxlen, jsx->rxmaxlen - jsx->rxlen);
390 if (len < 0 && errno == EAGAIN)
391 return;
392 else if (len < 0) {
393 g_free(jsx->rxqueue);
394 jsx->rxqueue = NULL;
395 close(source);
396 purple_xfer_cancel_remote(xfer);
397 return;
399 jsx->rxlen += len;
401 if (jsx->rxlen < jsx->rxmaxlen)
402 return;
404 purple_input_remove(purple_xfer_get_watcher(xfer));
405 purple_xfer_set_watcher(xfer, 0);
406 g_free(jsx->rxqueue);
407 jsx->rxqueue = NULL;
409 /* Before actually starting sending the file, we need to wait until the
410 * recipient sends the IQ result with <streamhost-used/>
412 purple_debug_info("jabber", "SOCKS5 connection negotiation completed. "
413 "Waiting for IQ result to start file transfer.\n");
416 static void
417 jabber_si_xfer_bytestreams_send_read_again_cb(gpointer data, gint source,
418 PurpleInputCondition cond)
420 PurpleXfer *xfer = data;
421 JabberSIXfer *jsx = purple_xfer_get_protocol_data(xfer);
422 char buffer[42]; /* 40 for DST.ADDR + 2 bytes for port number*/
423 int len;
424 char *dstaddr, *hash;
425 const char *host;
427 purple_debug_info("jabber", "in jabber_si_xfer_bytestreams_send_read_again_cb\n");
429 if(jsx->rxlen < 5) {
430 purple_debug_info("jabber", "reading the first 5 bytes\n");
431 len = read(source, buffer, 5 - jsx->rxlen);
432 if(len < 0 && errno == EAGAIN)
433 return;
434 else if(len <= 0) {
435 close(source);
436 purple_xfer_cancel_remote(xfer);
437 return;
439 jsx->rxqueue = g_realloc(jsx->rxqueue, len + jsx->rxlen);
440 memcpy(jsx->rxqueue + jsx->rxlen, buffer, len);
441 jsx->rxlen += len;
442 return;
443 } else if(jsx->rxqueue[0] != 0x05 || jsx->rxqueue[1] != 0x01 ||
444 jsx->rxqueue[3] != 0x03 || jsx->rxqueue[4] != 40) {
445 purple_debug_info("jabber", "Invalid socks5 conn req. header[0x%x,0x%x,0x%x,0x%x,0x%x]\n",
446 jsx->rxqueue[0], jsx->rxqueue[1], jsx->rxqueue[2],
447 jsx->rxqueue[3], jsx->rxqueue[4]);
448 close(source);
449 purple_xfer_cancel_remote(xfer);
450 return;
451 } else if(jsx->rxlen - 5 < (size_t)jsx->rxqueue[4] + 2) {
452 /* Upper-bound of 257 (jsx->rxlen = 5, jsx->rxqueue[4] = 0xFF) */
453 unsigned short to_read = jsx->rxqueue[4] + 2 - (jsx->rxlen - 5);
454 purple_debug_info("jabber", "reading %u bytes for DST.ADDR + port num (trying to read %hu now)\n",
455 jsx->rxqueue[4] + 2, to_read);
456 len = read(source, buffer, to_read);
457 if(len < 0 && errno == EAGAIN)
458 return;
459 else if(len <= 0) {
460 close(source);
461 purple_xfer_cancel_remote(xfer);
462 return;
464 jsx->rxqueue = g_realloc(jsx->rxqueue, len + jsx->rxlen);
465 memcpy(jsx->rxqueue + jsx->rxlen, buffer, len);
466 jsx->rxlen += len;
469 /* Have we not read all of DST.ADDR and the following 2-byte port number? */
470 if(jsx->rxlen - 5 < (size_t)jsx->rxqueue[4] + 2)
471 return;
473 purple_input_remove(purple_xfer_get_watcher(xfer));
474 purple_xfer_set_watcher(xfer, 0);
476 dstaddr = g_strdup_printf("%s%s@%s/%s%s", jsx->stream_id,
477 jsx->js->user->node, jsx->js->user->domain,
478 jsx->js->user->resource, purple_xfer_get_remote_user(xfer));
480 /* Per XEP-0065, the 'host' must be SHA1(SID + from JID + to JID) */
481 hash = jabber_calculate_data_hash(dstaddr, strlen(dstaddr), "sha1");
483 if(strncmp(hash, jsx->rxqueue + 5, 40) ||
484 jsx->rxqueue[45] != 0x00 || jsx->rxqueue[46] != 0x00) {
485 if (jsx->rxqueue[45] != 0x00 || jsx->rxqueue[46] != 0x00)
486 purple_debug_error("jabber", "Got SOCKS5 BS conn with the wrong DST.PORT"
487 " (must be 0 - got[0x%x,0x%x]).\n",
488 jsx->rxqueue[45], jsx->rxqueue[46]);
489 else
490 purple_debug_error("jabber", "Got SOCKS5 BS conn with the wrong DST.ADDR"
491 " (expected '%s' - got '%.40s').\n",
492 hash, jsx->rxqueue + 5);
493 close(source);
494 purple_xfer_cancel_remote(xfer);
495 g_free(hash);
496 g_free(dstaddr);
497 return;
500 g_free(hash);
501 g_free(dstaddr);
503 g_free(jsx->rxqueue);
504 host = purple_network_get_my_ip(jsx->js->fd);
506 jsx->rxmaxlen = 5 + strlen(host) + 2;
507 jsx->rxqueue = g_malloc(jsx->rxmaxlen);
508 jsx->rxlen = 0;
510 jsx->rxqueue[0] = 0x05;
511 jsx->rxqueue[1] = 0x00;
512 jsx->rxqueue[2] = 0x00;
513 jsx->rxqueue[3] = 0x03;
514 jsx->rxqueue[4] = strlen(host);
515 memcpy(jsx->rxqueue + 5, host, strlen(host));
516 jsx->rxqueue[5+strlen(host)] = 0x00;
517 jsx->rxqueue[6+strlen(host)] = 0x00;
519 purple_xfer_set_watcher(xfer, purple_input_add(source, PURPLE_INPUT_WRITE,
520 jabber_si_xfer_bytestreams_send_read_again_resp_cb, xfer));
521 jabber_si_xfer_bytestreams_send_read_again_resp_cb(xfer, source,
522 PURPLE_INPUT_WRITE);
525 static void
526 jabber_si_xfer_bytestreams_send_read_response_cb(gpointer data, gint source,
527 PurpleInputCondition cond)
529 PurpleXfer *xfer = data;
530 JabberSIXfer *jsx = purple_xfer_get_protocol_data(xfer);
531 int len;
533 len = write(source, jsx->rxqueue + jsx->rxlen, jsx->rxmaxlen - jsx->rxlen);
534 if (len < 0 && errno == EAGAIN)
535 return;
536 else if (len < 0) {
537 g_free(jsx->rxqueue);
538 jsx->rxqueue = NULL;
539 close(source);
540 purple_xfer_cancel_remote(xfer);
541 return;
543 jsx->rxlen += len;
545 if (jsx->rxlen < jsx->rxmaxlen)
546 return;
548 /* If we sent a "Success", wait for a response, otherwise give up and cancel */
549 if (jsx->rxqueue[1] == 0x00) {
550 purple_input_remove(purple_xfer_get_watcher(xfer));
551 purple_xfer_set_watcher(xfer, purple_input_add(source, PURPLE_INPUT_READ,
552 jabber_si_xfer_bytestreams_send_read_again_cb, xfer));
553 g_free(jsx->rxqueue);
554 jsx->rxqueue = NULL;
555 jsx->rxlen = 0;
556 } else {
557 close(source);
558 purple_xfer_cancel_remote(xfer);
562 static void
563 jabber_si_xfer_bytestreams_send_read_cb(gpointer data, gint source,
564 PurpleInputCondition cond)
566 PurpleXfer *xfer = data;
567 JabberSIXfer *jsx = purple_xfer_get_protocol_data(xfer);
568 int i;
569 int len;
570 char buffer[256];
572 purple_debug_info("jabber", "in jabber_si_xfer_bytestreams_send_read_cb\n");
574 purple_xfer_set_fd(xfer, source);
576 /** Try to read the SOCKS5 header */
577 if(jsx->rxlen < 2) {
578 purple_debug_info("jabber", "reading those first two bytes\n");
579 len = read(source, buffer, 2 - jsx->rxlen);
580 if(len < 0 && errno == EAGAIN)
581 return;
582 else if(len <= 0) {
583 purple_xfer_cancel_remote(xfer);
584 return;
586 jsx->rxqueue = g_realloc(jsx->rxqueue, len + jsx->rxlen);
587 memcpy(jsx->rxqueue + jsx->rxlen, buffer, len);
588 jsx->rxlen += len;
589 return;
590 } else if(jsx->rxlen - 2 < (size_t)jsx->rxqueue[1]) {
591 /* Has a maximum value of 255 (jsx->rxlen = 2, jsx->rxqueue[1] = 0xFF) */
592 unsigned short to_read = jsx->rxqueue[1] - (jsx->rxlen - 2);
593 purple_debug_info("jabber", "reading %u bytes for auth methods (trying to read %hu now)\n",
594 jsx->rxqueue[1], to_read);
595 len = read(source, buffer, to_read);
596 if(len < 0 && errno == EAGAIN)
597 return;
598 else if(len <= 0) {
599 purple_xfer_cancel_remote(xfer);
600 return;
602 jsx->rxqueue = g_realloc(jsx->rxqueue, len + jsx->rxlen);
603 memcpy(jsx->rxqueue + jsx->rxlen, buffer, len);
604 jsx->rxlen += len;
607 /* Have we not read all the auth. method bytes? */
608 if(jsx->rxlen -2 < (size_t)jsx->rxqueue[1])
609 return;
611 purple_debug_info("jabber", "checking to make sure we're socks FIVE\n");
613 if(jsx->rxqueue[0] != 0x05) {
614 purple_xfer_cancel_remote(xfer);
615 return;
618 purple_debug_info("jabber", "going to test %u different methods\n", (guint)jsx->rxqueue[1]);
620 for(i=0; i<jsx->rxqueue[1]; i++) {
622 purple_debug_info("jabber", "testing %u\n", (guint)jsx->rxqueue[i+2]);
623 if(jsx->rxqueue[i+2] == 0x00) {
624 g_free(jsx->rxqueue);
625 jsx->rxlen = 0;
626 jsx->rxmaxlen = 2;
627 jsx->rxqueue = g_malloc(jsx->rxmaxlen);
628 jsx->rxqueue[0] = 0x05;
629 jsx->rxqueue[1] = 0x00;
630 purple_input_remove(purple_xfer_get_watcher(xfer));
631 purple_xfer_set_watcher(xfer, purple_input_add(source, PURPLE_INPUT_WRITE,
632 jabber_si_xfer_bytestreams_send_read_response_cb,
633 xfer));
634 jabber_si_xfer_bytestreams_send_read_response_cb(xfer,
635 source, PURPLE_INPUT_WRITE);
636 jsx->rxqueue = NULL;
637 jsx->rxlen = 0;
638 return;
642 g_free(jsx->rxqueue);
643 jsx->rxlen = 0;
644 jsx->rxmaxlen = 2;
645 jsx->rxqueue = g_malloc(jsx->rxmaxlen);
646 jsx->rxqueue[0] = 0x05;
647 jsx->rxqueue[1] = 0xFF;
648 purple_input_remove(purple_xfer_get_watcher(xfer));
649 purple_xfer_set_watcher(xfer, purple_input_add(source, PURPLE_INPUT_WRITE,
650 jabber_si_xfer_bytestreams_send_read_response_cb, xfer));
651 jabber_si_xfer_bytestreams_send_read_response_cb(xfer,
652 source, PURPLE_INPUT_WRITE);
655 static gint
656 jabber_si_compare_jid(gconstpointer a, gconstpointer b)
658 const JabberBytestreamsStreamhost *sh = a;
660 if(!a)
661 return -1;
663 return strcmp(sh->jid, (char *)b);
666 static void
667 jabber_si_xfer_bytestreams_send_connected_cb(gpointer data, gint source,
668 PurpleInputCondition cond)
670 PurpleXfer *xfer = data;
671 JabberSIXfer *jsx = purple_xfer_get_protocol_data(xfer);
672 int acceptfd;
674 purple_debug_info("jabber", "in jabber_si_xfer_bytestreams_send_connected_cb\n");
676 acceptfd = accept(source, NULL, 0);
677 if(acceptfd == -1 && (errno == EAGAIN || errno == EWOULDBLOCK))
678 return;
679 else if(acceptfd == -1) {
680 purple_debug_warning("jabber", "accept: %s\n", g_strerror(errno));
681 /* Don't cancel the ft - allow it to fall to the next streamhost.*/
682 return;
685 purple_input_remove(purple_xfer_get_watcher(xfer));
686 close(source);
687 jsx->local_streamhost_fd = -1;
689 _purple_network_set_common_socket_flags(acceptfd);
691 purple_xfer_set_watcher(xfer, purple_input_add(acceptfd, PURPLE_INPUT_READ,
692 jabber_si_xfer_bytestreams_send_read_cb, xfer));
695 static void
696 jabber_si_connect_proxy_cb(JabberStream *js, const char *from,
697 JabberIqType type, const char *id,
698 PurpleXmlNode *packet, gpointer data)
700 PurpleXfer *xfer = data;
701 JabberSIXfer *jsx;
702 PurpleXmlNode *query, *streamhost_used;
703 const char *jid;
704 GList *matched;
706 /* TODO: This need to send errors if we don't see what we're looking for */
708 /* Make sure that the xfer is actually still valid and we're not just receiving an old iq response */
709 if (!g_list_find(js->file_transfers, xfer)) {
710 purple_debug_error("jabber", "Got bytestreams response for no longer existing xfer (%p)\n", xfer);
711 return;
714 jsx = purple_xfer_get_protocol_data(xfer);
716 /* In the case of a direct file transfer, this is expected to return */
717 if(!jsx)
718 return;
720 if(type != JABBER_IQ_RESULT) {
721 purple_debug_info("jabber",
722 "jabber_si_xfer_connect_proxy_cb: type = error\n");
723 /* if IBB is available, open IBB session */
724 purple_debug_info("jabber",
725 "jabber_si_xfer_connect_proxy_cb: got error, method: %d\n",
726 jsx->stream_method);
727 if (jsx->stream_method & STREAM_METHOD_IBB) {
728 purple_debug_info("jabber", "IBB is possible, try it\n");
729 /* if we are the sender and haven't already opened an IBB
730 session, do so now (we might already have failed to open
731 the bytestream proxy ourselves when receiving this <iq/> */
732 if (purple_xfer_get_xfer_type(xfer) == PURPLE_XFER_TYPE_SEND
733 && !jsx->ibb_session) {
734 jabber_si_xfer_ibb_send_init(js, xfer);
735 } else {
736 jsx->ibb_timeout_handle = purple_timeout_add_seconds(30,
737 jabber_si_bytestreams_ibb_timeout_cb, xfer);
739 /* if we are receiver, just wait for IBB open stanza, callback
740 is already set up */
741 } else {
742 purple_xfer_cancel_remote(xfer);
744 return;
747 if (!from)
748 return;
750 if(!(query = purple_xmlnode_get_child(packet, "query")))
751 return;
753 if(!(streamhost_used = purple_xmlnode_get_child(query, "streamhost-used")))
754 return;
756 if(!(jid = purple_xmlnode_get_attrib(streamhost_used, "jid")))
757 return;
759 purple_debug_info("jabber", "jabber_si_connect_proxy_cb() will be looking at jsx %p: jsx->streamhosts is %p and jid is %s\n",
760 jsx, jsx->streamhosts, jid);
762 if(!(matched = g_list_find_custom(jsx->streamhosts, jid, jabber_si_compare_jid)))
764 gchar *my_jid = g_strdup_printf("%s@%s/%s", jsx->js->user->node,
765 jsx->js->user->domain, jsx->js->user->resource);
766 if (!strcmp(jid, my_jid)) {
767 purple_debug_info("jabber", "Got local SOCKS5 streamhost-used.\n");
768 purple_xfer_start(xfer, purple_xfer_get_fd(xfer), NULL, -1);
769 } else {
770 /* if available, try to revert to IBB... */
771 if (jsx->stream_method & STREAM_METHOD_IBB) {
772 purple_debug_info("jabber",
773 "jabber_si_connect_proxy_cb: trying to revert to IBB\n");
774 if (purple_xfer_get_xfer_type(xfer) == PURPLE_XFER_TYPE_SEND) {
775 jabber_si_xfer_ibb_send_init(jsx->js, xfer);
776 } else {
777 jsx->ibb_timeout_handle = purple_timeout_add_seconds(30,
778 jabber_si_bytestreams_ibb_timeout_cb, xfer);
780 /* if we are the receiver, we are already set up...*/
781 } else {
782 purple_debug_info("jabber",
783 "streamhost-used does not match any proxy that was offered to target\n");
784 purple_xfer_cancel_local(xfer);
787 g_free(my_jid);
788 return;
791 /* Clean up the local streamhost - it isn't going to be used.*/
792 if (purple_xfer_get_watcher(xfer) > 0) {
793 purple_input_remove(purple_xfer_get_watcher(xfer));
794 purple_xfer_set_watcher(xfer, 0);
796 if (jsx->local_streamhost_fd >= 0) {
797 close(jsx->local_streamhost_fd);
798 jsx->local_streamhost_fd = -1;
801 jsx->streamhosts = g_list_remove_link(jsx->streamhosts, matched);
802 g_list_foreach(jsx->streamhosts, jabber_si_free_streamhost, NULL);
803 g_list_free(jsx->streamhosts);
805 jsx->streamhosts = matched;
807 jabber_si_bytestreams_attempt_connect(xfer);
810 static void
811 jabber_si_xfer_bytestreams_listen_cb(int sock, gpointer data)
813 PurpleXfer *xfer = data;
814 JabberSIXfer *jsx;
815 JabberIq *iq;
816 PurpleXmlNode *query, *streamhost;
817 char port[6];
818 GList *tmp;
819 JabberBytestreamsStreamhost *sh, *sh2;
820 int streamhost_count = 0;
822 jsx = purple_xfer_get_protocol_data(xfer);
823 jsx->listen_data = NULL;
825 /* I'm not sure under which conditions this can happen
826 * (it seems like it shouldn't be possible */
827 if (purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_CANCEL_LOCAL) {
828 g_object_unref(xfer);
829 return;
832 g_object_unref(xfer);
834 iq = jabber_iq_new_query(jsx->js, JABBER_IQ_SET, NS_BYTESTREAMS);
835 purple_xmlnode_set_attrib(iq->node, "to", purple_xfer_get_remote_user(xfer));
836 query = purple_xmlnode_get_child(iq->node, "query");
838 purple_xmlnode_set_attrib(query, "sid", jsx->stream_id);
840 /* If we successfully started listening locally */
841 if (sock >= 0) {
842 gchar *jid;
843 GList *local_ips =
844 purple_network_get_all_local_system_ips();
845 const char *public_ip;
846 gboolean has_public_ip = FALSE;
848 jsx->local_streamhost_fd = sock;
850 jid = g_strdup_printf("%s@%s/%s", jsx->js->user->node,
851 jsx->js->user->domain, jsx->js->user->resource);
852 purple_xfer_set_local_port(xfer, purple_network_get_port_from_fd(sock));
853 g_snprintf(port, sizeof(port), "%hu", purple_xfer_get_local_port(xfer));
855 public_ip = purple_network_get_my_ip(jsx->js->fd);
857 /* Include the localhost's IPs (for in-network transfers) */
858 while (local_ips) {
859 gchar *local_ip = local_ips->data;
860 streamhost_count++;
861 streamhost = purple_xmlnode_new_child(query, "streamhost");
862 purple_xmlnode_set_attrib(streamhost, "jid", jid);
863 purple_xmlnode_set_attrib(streamhost, "host", local_ip);
864 purple_xmlnode_set_attrib(streamhost, "port", port);
865 if (purple_strequal(local_ip, public_ip))
866 has_public_ip = TRUE;
867 g_free(local_ip);
868 local_ips = g_list_delete_link(local_ips, local_ips);
871 /* Include the public IP (assuming that there is a port mapped somehow) */
872 if (!has_public_ip && strcmp(public_ip, "0.0.0.0") != 0) {
873 streamhost_count++;
874 streamhost = purple_xmlnode_new_child(query, "streamhost");
875 purple_xmlnode_set_attrib(streamhost, "jid", jid);
876 purple_xmlnode_set_attrib(streamhost, "host", public_ip);
877 purple_xmlnode_set_attrib(streamhost, "port", port);
880 g_free(jid);
882 /* The listener for the local proxy */
883 purple_xfer_set_watcher(xfer, purple_input_add(sock, PURPLE_INPUT_READ,
884 jabber_si_xfer_bytestreams_send_connected_cb, xfer));
887 for (tmp = jsx->js->bs_proxies; tmp; tmp = tmp->next) {
888 sh = tmp->data;
890 /* TODO: deal with zeroconf proxies */
892 if (!(sh->jid && sh->host && sh->port > 0))
893 continue;
895 purple_debug_info("jabber", "jabber_si_xfer_bytestreams_listen_cb() will be looking at jsx %p: jsx->streamhosts %p and sh->jid %p\n",
896 jsx, jsx->streamhosts, sh->jid);
897 if(g_list_find_custom(jsx->streamhosts, sh->jid, jabber_si_compare_jid) != NULL)
898 continue;
900 streamhost_count++;
901 streamhost = purple_xmlnode_new_child(query, "streamhost");
902 purple_xmlnode_set_attrib(streamhost, "jid", sh->jid);
903 purple_xmlnode_set_attrib(streamhost, "host", sh->host);
904 g_snprintf(port, sizeof(port), "%hu", sh->port);
905 purple_xmlnode_set_attrib(streamhost, "port", port);
907 sh2 = g_new0(JabberBytestreamsStreamhost, 1);
908 sh2->jid = g_strdup(sh->jid);
909 sh2->host = g_strdup(sh->host);
910 /*sh2->zeroconf = g_strdup(sh->zeroconf);*/
911 sh2->port = sh->port;
913 jsx->streamhosts = g_list_prepend(jsx->streamhosts, sh2);
916 /* We have no way of transferring, cancel the transfer */
917 if (streamhost_count == 0) {
918 jabber_iq_free(iq);
920 /* if available, revert to IBB */
921 if (jsx->stream_method & STREAM_METHOD_IBB) {
922 purple_debug_info("jabber",
923 "jabber_si_xfer_bytestreams_listen_cb: trying to revert to IBB\n");
924 if (purple_xfer_get_xfer_type(xfer) == PURPLE_XFER_TYPE_SEND) {
925 /* if we are the sender, init the IBB session... */
926 jabber_si_xfer_ibb_send_init(jsx->js, xfer);
927 } else {
928 jsx->ibb_timeout_handle = purple_timeout_add_seconds(30,
929 jabber_si_bytestreams_ibb_timeout_cb, xfer);
931 /* if we are the receiver, we should just wait... the IBB open
932 handler has already been set up... */
933 } else {
934 /* We should probably notify the target,
935 but this really shouldn't ever happen */
936 purple_xfer_cancel_local(xfer);
939 return;
942 jabber_iq_set_callback(iq, jabber_si_connect_proxy_cb, xfer);
944 jabber_iq_send(iq);
948 static void
949 jabber_si_xfer_bytestreams_send_init(PurpleXfer *xfer)
951 JabberSIXfer *jsx;
952 PurpleProxyType proxy_type;
954 g_object_ref(xfer);
956 jsx = purple_xfer_get_protocol_data(xfer);
958 /* TODO: This should probably be done with an account option instead of
959 * piggy-backing on the TOR proxy type. */
960 proxy_type = purple_proxy_info_get_proxy_type(
961 purple_proxy_get_setup(purple_connection_get_account(jsx->js->gc)));
962 if (proxy_type == PURPLE_PROXY_TOR) {
963 purple_debug_info("jabber", "Skipping attempting local streamhost.\n");
964 jsx->listen_data = NULL;
965 } else
966 jsx->listen_data = purple_network_listen_range(0, 0, AF_UNSPEC, SOCK_STREAM, TRUE,
967 jabber_si_xfer_bytestreams_listen_cb, xfer);
969 if (jsx->listen_data == NULL) {
970 /* We couldn't open a local port. Perhaps we can use a proxy. */
971 jabber_si_xfer_bytestreams_listen_cb(-1, xfer);
976 static void
977 jabber_si_xfer_ibb_error_cb(JabberIBBSession *sess)
979 PurpleXfer *xfer = (PurpleXfer *) jabber_ibb_session_get_user_data(sess);
981 purple_debug_error("jabber", "an error occurred during IBB file transfer\n");
982 purple_xfer_cancel_remote(xfer);
985 static void
986 jabber_si_xfer_ibb_closed_cb(JabberIBBSession *sess)
988 PurpleXfer *xfer = (PurpleXfer *) jabber_ibb_session_get_user_data(sess);
990 purple_debug_info("jabber", "the remote user closed the transfer\n");
991 if (purple_xfer_get_bytes_remaining(xfer) > 0) {
992 purple_xfer_cancel_remote(xfer);
993 } else {
994 purple_xfer_set_completed(xfer, TRUE);
995 purple_xfer_end(xfer);
999 static void
1000 jabber_si_xfer_ibb_recv_data_cb(JabberIBBSession *sess, gpointer data,
1001 gsize size)
1003 PurpleXfer *xfer = (PurpleXfer *) jabber_ibb_session_get_user_data(sess);
1004 JabberSIXfer *jsx = purple_xfer_get_protocol_data(xfer);
1006 if ((goffset)size <= purple_xfer_get_bytes_remaining(xfer)) {
1007 purple_debug_info("jabber", "about to write %" G_GSIZE_FORMAT " bytes from IBB stream\n",
1008 size);
1009 purple_circular_buffer_append(jsx->ibb_buffer, data, size);
1010 purple_xfer_protocol_ready(xfer);
1011 } else {
1012 /* trying to write past size of file transfers negotiated size,
1013 reject transfer to protect against malicious behaviour */
1014 purple_debug_error("jabber",
1015 "IBB file transfer send more data than expected\n");
1016 purple_xfer_cancel_remote(xfer);
1021 static gssize
1022 jabber_si_xfer_ibb_read(guchar **out_buffer, size_t buf_size, PurpleXfer *xfer)
1024 JabberSIXfer *jsx = purple_xfer_get_protocol_data(xfer);
1025 guchar *buffer;
1026 gsize size = purple_circular_buffer_get_used(jsx->ibb_buffer);
1027 gsize tmp;
1029 *out_buffer = buffer = g_malloc(size);
1030 while ((tmp = purple_circular_buffer_get_max_read(jsx->ibb_buffer))) {
1031 const gchar *output = purple_circular_buffer_get_output(jsx->ibb_buffer);
1032 memcpy(buffer, output, tmp);
1033 buffer += tmp;
1034 purple_circular_buffer_mark_read(jsx->ibb_buffer, tmp);
1037 return size;
1040 static gboolean
1041 jabber_si_xfer_ibb_open_cb(JabberStream *js, const char *who, const char *id,
1042 PurpleXmlNode *open)
1044 const gchar *sid = purple_xmlnode_get_attrib(open, "sid");
1045 PurpleXfer *xfer = jabber_si_xfer_find(js, sid, who);
1046 if (xfer) {
1047 JabberSIXfer *jsx = purple_xfer_get_protocol_data(xfer);
1048 JabberIBBSession *sess =
1049 jabber_ibb_session_create_from_xmlnode(js, who, id, open, xfer);
1051 jabber_si_bytestreams_ibb_timeout_remove(jsx);
1053 if (sess) {
1054 /* setup callbacks here...*/
1055 jabber_ibb_session_set_data_received_callback(sess,
1056 jabber_si_xfer_ibb_recv_data_cb);
1057 jabber_ibb_session_set_closed_callback(sess,
1058 jabber_si_xfer_ibb_closed_cb);
1059 jabber_ibb_session_set_error_callback(sess,
1060 jabber_si_xfer_ibb_error_cb);
1062 jsx->ibb_session = sess;
1063 /* we handle up to block-size bytes of decoded data, to handle
1064 clients interpreting the block-size attribute as that
1065 (see also remark in ibb.c) */
1066 jsx->ibb_buffer =
1067 purple_circular_buffer_new(jabber_ibb_session_get_block_size(sess));
1069 /* set up read function */
1070 purple_xfer_set_read_fnc(xfer, jabber_si_xfer_ibb_read);
1072 /* start the transfer */
1073 purple_xfer_start(xfer, -1, NULL, 0);
1074 return TRUE;
1075 } else {
1076 /* failed to create IBB session */
1077 purple_debug_error("jabber", "failed to create IBB session\n");
1078 purple_xfer_cancel_remote(xfer);
1079 return FALSE;
1081 } else {
1082 /* we got an IBB <open/> for an unknown file transfer, pass along... */
1083 purple_debug_info("jabber",
1084 "IBB open did not match any SI file transfer\n");
1085 return FALSE;
1089 static gssize
1090 jabber_si_xfer_ibb_write(const guchar *buffer, size_t len, PurpleXfer *xfer)
1092 JabberSIXfer *jsx = purple_xfer_get_protocol_data(xfer);
1093 JabberIBBSession *sess = jsx->ibb_session;
1094 gsize packet_size = len < jabber_ibb_session_get_max_data_size(sess) ?
1095 len : jabber_ibb_session_get_max_data_size(sess);
1097 jabber_ibb_session_send_data(sess, buffer, packet_size);
1099 return packet_size;
1102 static void
1103 jabber_si_xfer_ibb_sent_cb(JabberIBBSession *sess)
1105 PurpleXfer *xfer = (PurpleXfer *) jabber_ibb_session_get_user_data(sess);
1106 goffset remaining = purple_xfer_get_bytes_remaining(xfer);
1108 if (remaining == 0) {
1109 /* close the session */
1110 jabber_ibb_session_close(sess);
1111 purple_xfer_set_completed(xfer, TRUE);
1112 purple_xfer_end(xfer);
1113 } else {
1114 /* send more... */
1115 purple_xfer_protocol_ready(xfer);
1119 static void
1120 jabber_si_xfer_ibb_opened_cb(JabberIBBSession *sess)
1122 PurpleXfer *xfer = (PurpleXfer *) jabber_ibb_session_get_user_data(sess);
1124 if (jabber_ibb_session_get_state(sess) == JABBER_IBB_SESSION_OPENED) {
1125 purple_xfer_start(xfer, -1, NULL, 0);
1126 purple_xfer_protocol_ready(xfer);
1127 } else {
1128 /* error */
1129 purple_xfer_end(xfer);
1133 static void
1134 jabber_si_xfer_ibb_send_init(JabberStream *js, PurpleXfer *xfer)
1136 JabberSIXfer *jsx = purple_xfer_get_protocol_data(xfer);
1138 jsx->ibb_session = jabber_ibb_session_create(js, jsx->stream_id,
1139 purple_xfer_get_remote_user(xfer), xfer);
1141 if (jsx->ibb_session) {
1142 /* should set callbacks here... */
1143 jabber_ibb_session_set_opened_callback(jsx->ibb_session,
1144 jabber_si_xfer_ibb_opened_cb);
1145 jabber_ibb_session_set_data_sent_callback(jsx->ibb_session,
1146 jabber_si_xfer_ibb_sent_cb);
1147 jabber_ibb_session_set_closed_callback(jsx->ibb_session,
1148 jabber_si_xfer_ibb_closed_cb);
1149 jabber_ibb_session_set_error_callback(jsx->ibb_session,
1150 jabber_si_xfer_ibb_error_cb);
1152 purple_xfer_set_write_fnc(xfer, jabber_si_xfer_ibb_write);
1154 jsx->ibb_buffer =
1155 purple_circular_buffer_new(jabber_ibb_session_get_max_data_size(jsx->ibb_session));
1157 /* open the IBB session */
1158 jabber_ibb_session_open(jsx->ibb_session);
1160 } else {
1161 /* failed to create IBB session */
1162 purple_debug_error("jabber",
1163 "failed to initiate IBB session for file transfer\n");
1164 purple_xfer_cancel_local(xfer);
1168 static void jabber_si_xfer_send_method_cb(JabberStream *js, const char *from,
1169 JabberIqType type, const char *id,
1170 PurpleXmlNode *packet, gpointer data)
1172 PurpleXfer *xfer = data;
1173 PurpleXmlNode *si, *feature, *x, *field, *value;
1174 gboolean found_method = FALSE;
1176 if(!(si = purple_xmlnode_get_child_with_namespace(packet, "si", "http://jabber.org/protocol/si"))) {
1177 purple_xfer_cancel_remote(xfer);
1178 return;
1181 if(!(feature = purple_xmlnode_get_child_with_namespace(si, "feature", "http://jabber.org/protocol/feature-neg"))) {
1182 purple_xfer_cancel_remote(xfer);
1183 return;
1186 if(!(x = purple_xmlnode_get_child_with_namespace(feature, "x", "jabber:x:data"))) {
1187 purple_xfer_cancel_remote(xfer);
1188 return;
1191 for(field = purple_xmlnode_get_child(x, "field"); field; field = purple_xmlnode_get_next_twin(field)) {
1192 const char *var = purple_xmlnode_get_attrib(field, "var");
1193 JabberSIXfer *jsx = purple_xfer_get_protocol_data(xfer);
1195 if(var && !strcmp(var, "stream-method")) {
1196 if((value = purple_xmlnode_get_child(field, "value"))) {
1197 char *val = purple_xmlnode_get_data(value);
1198 if(val && !strcmp(val, NS_BYTESTREAMS)) {
1199 jabber_si_xfer_bytestreams_send_init(xfer);
1200 jsx->stream_method |= STREAM_METHOD_BYTESTREAMS;
1201 found_method = TRUE;
1202 } else if (val && !strcmp(val, NS_IBB)) {
1203 jsx->stream_method |= STREAM_METHOD_IBB;
1204 if (!found_method) {
1205 /* we haven't tried to init a bytestream session, yet
1206 start IBB right away... */
1207 jabber_si_xfer_ibb_send_init(js, xfer);
1208 found_method = TRUE;
1211 g_free(val);
1216 if (!found_method) {
1217 purple_xfer_cancel_remote(xfer);
1222 static void jabber_si_xfer_send_request(PurpleXfer *xfer)
1224 JabberSIXfer *jsx = purple_xfer_get_protocol_data(xfer);
1225 JabberIq *iq;
1226 PurpleXmlNode *si, *file, *feature, *x, *field, *option, *value;
1227 char buf[32];
1228 #if ENABLE_FT_THUMBNAILS
1229 gconstpointer thumb;
1230 gsize thumb_size;
1232 purple_xfer_prepare_thumbnail(xfer, "jpeg,png");
1233 #endif
1234 purple_xfer_set_filename(xfer, g_path_get_basename(purple_xfer_get_local_filename(xfer)));
1236 iq = jabber_iq_new(jsx->js, JABBER_IQ_SET);
1237 purple_xmlnode_set_attrib(iq->node, "to", purple_xfer_get_remote_user(xfer));
1238 si = purple_xmlnode_new_child(iq->node, "si");
1239 purple_xmlnode_set_namespace(si, "http://jabber.org/protocol/si");
1240 jsx->stream_id = jabber_get_next_id(jsx->js);
1241 purple_xmlnode_set_attrib(si, "id", jsx->stream_id);
1242 purple_xmlnode_set_attrib(si, "profile", NS_SI_FILE_TRANSFER);
1244 file = purple_xmlnode_new_child(si, "file");
1245 purple_xmlnode_set_namespace(file, NS_SI_FILE_TRANSFER);
1246 purple_xmlnode_set_attrib(file, "name", purple_xfer_get_filename(xfer));
1247 g_snprintf(buf, sizeof(buf), "%" G_GOFFSET_FORMAT, purple_xfer_get_size(xfer));
1248 purple_xmlnode_set_attrib(file, "size", buf);
1249 /* maybe later we'll do hash and date attribs */
1251 #if ENABLE_FT_THUMBNAILS
1252 /* add thumbnail, if appropriate */
1253 if ((thumb = purple_xfer_get_thumbnail(xfer, &thumb_size))) {
1254 const gchar *mimetype = purple_xfer_get_thumbnail_mimetype(xfer);
1255 JabberData *thumbnail_data =
1256 jabber_data_create_from_data(thumb, thumb_size,
1257 mimetype, TRUE, jsx->js);
1258 PurpleXmlNode *thumbnail = purple_xmlnode_new_child(file, "thumbnail");
1259 purple_xmlnode_set_namespace(thumbnail, NS_THUMBS);
1260 purple_xmlnode_set_attrib(thumbnail, "cid",
1261 jabber_data_get_cid(thumbnail_data));
1262 purple_xmlnode_set_attrib(thumbnail, "mime-type", mimetype);
1263 /* cache data */
1264 jabber_data_associate_local(thumbnail_data, NULL);
1266 #endif
1268 feature = purple_xmlnode_new_child(si, "feature");
1269 purple_xmlnode_set_namespace(feature, "http://jabber.org/protocol/feature-neg");
1270 x = purple_xmlnode_new_child(feature, "x");
1271 purple_xmlnode_set_namespace(x, "jabber:x:data");
1272 purple_xmlnode_set_attrib(x, "type", "form");
1273 field = purple_xmlnode_new_child(x, "field");
1274 purple_xmlnode_set_attrib(field, "var", "stream-method");
1275 purple_xmlnode_set_attrib(field, "type", "list-single");
1276 /* maybe we should add an option to always skip bytestreams for people
1277 behind troublesome firewalls */
1278 option = purple_xmlnode_new_child(field, "option");
1279 value = purple_xmlnode_new_child(option, "value");
1280 purple_xmlnode_insert_data(value, NS_BYTESTREAMS, -1);
1281 option = purple_xmlnode_new_child(field, "option");
1282 value = purple_xmlnode_new_child(option, "value");
1283 purple_xmlnode_insert_data(value, NS_IBB, -1);
1285 jabber_iq_set_callback(iq, jabber_si_xfer_send_method_cb, xfer);
1287 /* Store the IQ id so that we can cancel the callback */
1288 g_free(jsx->iq_id);
1289 jsx->iq_id = g_strdup(iq->id);
1291 jabber_iq_send(iq);
1294 static void jabber_si_xfer_free(PurpleXfer *xfer)
1296 JabberSIXfer *jsx = purple_xfer_get_protocol_data(xfer);
1298 if (jsx) {
1299 JabberStream *js = jsx->js;
1301 js->file_transfers = g_list_remove(js->file_transfers, xfer);
1303 if (jsx->connect_data != NULL)
1304 purple_proxy_connect_cancel(jsx->connect_data);
1305 if (jsx->listen_data != NULL)
1306 purple_network_listen_cancel(jsx->listen_data);
1307 if (jsx->iq_id != NULL)
1308 jabber_iq_remove_callback_by_id(js, jsx->iq_id);
1309 if (jsx->local_streamhost_fd >= 0)
1310 close(jsx->local_streamhost_fd);
1311 if (purple_xfer_get_xfer_type(xfer) == PURPLE_XFER_TYPE_SEND && purple_xfer_get_fd(xfer) >= 0) {
1312 purple_debug_info("jabber", "remove port mapping\n");
1313 purple_network_remove_port_mapping(purple_xfer_get_fd(xfer));
1315 if (jsx->connect_timeout > 0)
1316 purple_timeout_remove(jsx->connect_timeout);
1317 if (jsx->ibb_timeout_handle > 0)
1318 purple_timeout_remove(jsx->ibb_timeout_handle);
1320 if (jsx->streamhosts) {
1321 g_list_foreach(jsx->streamhosts, jabber_si_free_streamhost, NULL);
1322 g_list_free(jsx->streamhosts);
1325 if (jsx->ibb_session) {
1326 purple_debug_info("jabber",
1327 "jabber_si_xfer_free: destroying IBB session\n");
1328 jabber_ibb_session_destroy(jsx->ibb_session);
1331 if (jsx->ibb_buffer) {
1332 g_object_unref(G_OBJECT(jsx->ibb_buffer));
1335 purple_debug_info("jabber", "jabber_si_xfer_free(): freeing jsx %p\n", jsx);
1337 g_free(jsx->stream_id);
1338 g_free(jsx->iq_id);
1339 /* XXX: free other stuff */
1340 g_free(jsx->rxqueue);
1341 g_free(jsx);
1342 purple_xfer_set_protocol_data(xfer, NULL);
1347 * These four functions should only be called from the PurpleXfer functions
1348 * (typically purple_xfer_cancel_(remote|local), purple_xfer_end, or
1349 * purple_xfer_request_denied.
1351 static void jabber_si_xfer_cancel_send(PurpleXfer *xfer)
1353 JabberSIXfer *jsx = purple_xfer_get_protocol_data(xfer);
1355 /* if there is an IBB session active, send close on that */
1356 if (jsx->ibb_session) {
1357 jabber_ibb_session_close(jsx->ibb_session);
1359 jabber_si_xfer_free(xfer);
1360 purple_debug_info("jabber", "in jabber_si_xfer_cancel_send\n");
1364 static void jabber_si_xfer_request_denied(PurpleXfer *xfer)
1366 JabberSIXfer *jsx = purple_xfer_get_protocol_data(xfer);
1367 JabberStream *js = jsx->js;
1370 * TODO: It's probably an error if jsx->iq_id == NULL. g_return_if_fail
1371 * might be warranted.
1373 if (jsx->iq_id && !jsx->accepted) {
1374 JabberIq *iq;
1375 PurpleXmlNode *error, *child;
1376 iq = jabber_iq_new(js, JABBER_IQ_ERROR);
1377 purple_xmlnode_set_attrib(iq->node, "to", purple_xfer_get_remote_user(xfer));
1378 jabber_iq_set_id(iq, jsx->iq_id);
1380 error = purple_xmlnode_new_child(iq->node, "error");
1381 purple_xmlnode_set_attrib(error, "type", "cancel");
1382 child = purple_xmlnode_new_child(error, "forbidden");
1383 purple_xmlnode_set_namespace(child, NS_XMPP_STANZAS);
1384 child = purple_xmlnode_new_child(error, "text");
1385 purple_xmlnode_set_namespace(child, NS_XMPP_STANZAS);
1386 purple_xmlnode_insert_data(child, "Offer Declined", -1);
1388 jabber_iq_send(iq);
1391 jabber_si_xfer_free(xfer);
1392 purple_debug_info("jabber", "in jabber_si_xfer_request_denied\n");
1396 static void jabber_si_xfer_cancel_recv(PurpleXfer *xfer)
1398 JabberSIXfer *jsx = purple_xfer_get_protocol_data(xfer);
1399 /* if there is an IBB session active, send close */
1400 if (jsx->ibb_session) {
1401 jabber_ibb_session_close(jsx->ibb_session);
1403 jabber_si_xfer_free(xfer);
1404 purple_debug_info("jabber", "in jabber_si_xfer_cancel_recv\n");
1408 static void jabber_si_xfer_end(PurpleXfer *xfer)
1410 jabber_si_xfer_free(xfer);
1414 static void jabber_si_xfer_send_disco_cb(JabberStream *js, const char *who,
1415 JabberCapabilities capabilities, gpointer data)
1417 PurpleXfer *xfer = (PurpleXfer *) data;
1418 JabberSIXfer *jsx = purple_xfer_get_protocol_data(xfer);
1420 if (capabilities & JABBER_CAP_IBB) {
1421 purple_debug_info("jabber",
1422 "jabber_si_xfer_send_disco_cb: remote JID supports IBB\n");
1423 jsx->stream_method |= STREAM_METHOD_IBB;
1426 if (capabilities & JABBER_CAP_SI_FILE_XFER) {
1427 jabber_si_xfer_send_request(xfer);
1428 } else {
1429 char *msg = g_strdup_printf(_("Unable to send file to %s, user does not support file transfers"), who);
1430 purple_notify_error(js->gc, _("File Send Failed"),
1431 _("File Send Failed"), msg,
1432 purple_request_cpar_from_connection(js->gc));
1433 g_free(msg);
1434 purple_xfer_cancel_local(xfer);
1438 static void resource_select_cancel_cb(PurpleXfer *xfer, PurpleRequestFields *fields)
1440 purple_xfer_cancel_local(xfer);
1443 static void do_transfer_send(PurpleXfer *xfer, const char *resource)
1445 JabberSIXfer *jsx = purple_xfer_get_protocol_data(xfer);
1446 char **who_v = g_strsplit(purple_xfer_get_remote_user(xfer), "/", 2);
1447 char *who;
1448 JabberBuddy *jb;
1449 JabberBuddyResource *jbr = NULL;
1451 jb = jabber_buddy_find(jsx->js, who_v[0], FALSE);
1452 if (jb) {
1453 jbr = jabber_buddy_find_resource(jb, resource);
1456 who = g_strdup_printf("%s/%s", who_v[0], resource);
1457 g_strfreev(who_v);
1458 purple_xfer_set_remote_user(xfer, who);
1460 if (jbr && jabber_resource_know_capabilities(jbr)) {
1461 char *msg;
1463 if (jabber_resource_has_capability(jbr, NS_IBB))
1464 jsx->stream_method |= STREAM_METHOD_IBB;
1465 if (jabber_resource_has_capability(jbr, NS_SI_FILE_TRANSFER)) {
1466 jabber_si_xfer_send_request(xfer);
1467 g_free(who);
1468 return;
1471 msg = g_strdup_printf(_("Unable to send file to %s, user does not support file transfers"), who);
1472 purple_notify_error(jsx->js->gc, _("File Send Failed"),
1473 _("File Send Failed"), msg,
1474 purple_request_cpar_from_connection(jsx->js->gc));
1475 g_free(msg);
1476 purple_xfer_cancel_local(xfer);
1477 } else {
1478 jabber_disco_info_do(jsx->js, who,
1479 jabber_si_xfer_send_disco_cb, xfer);
1482 g_free(who);
1485 static void resource_select_ok_cb(PurpleXfer *xfer, PurpleRequestFields *fields)
1487 PurpleRequestField *field = purple_request_fields_get_field(fields, "resource");
1488 const char *selected_label = purple_request_field_choice_get_value(field);
1490 do_transfer_send(xfer, selected_label);
1493 static void jabber_si_xfer_init(PurpleXfer *xfer)
1495 JabberSIXfer *jsx = purple_xfer_get_protocol_data(xfer);
1496 JabberIq *iq;
1497 if(purple_xfer_get_xfer_type(xfer) == PURPLE_XFER_TYPE_SEND) {
1498 JabberBuddy *jb;
1499 JabberBuddyResource *jbr = NULL;
1500 char *resource;
1501 GList *resources = NULL;
1503 if(NULL != (resource = jabber_get_resource(purple_xfer_get_remote_user(xfer)))) {
1504 /* they've specified a resource, no need to ask or
1505 * default or anything, just do it */
1507 do_transfer_send(xfer, resource);
1508 g_free(resource);
1509 return;
1512 jb = jabber_buddy_find(jsx->js, purple_xfer_get_remote_user(xfer), TRUE);
1514 if (jb) {
1515 GList *l;
1517 for (l = jb->resources ; l ; l = g_list_next(l)) {
1518 jbr = l->data;
1520 if (!jabber_resource_know_capabilities(jbr) ||
1521 (jabber_resource_has_capability(jbr, NS_SI_FILE_TRANSFER)
1522 && (jabber_resource_has_capability(jbr, NS_BYTESTREAMS)
1523 || jabber_resource_has_capability(jbr, NS_IBB)))) {
1524 resources = g_list_append(resources, jbr);
1529 if (!resources) {
1530 /* no resources online, we're trying to send to someone
1531 * whose presence we're not subscribed to, or
1532 * someone who is offline. Let's inform the user */
1533 char *msg;
1535 if(!jb) {
1536 msg = g_strdup_printf(_("Unable to send file to %s, invalid JID"), purple_xfer_get_remote_user(xfer));
1537 } else if(jb->subscription & JABBER_SUB_TO) {
1538 msg = g_strdup_printf(_("Unable to send file to %s, user is not online"), purple_xfer_get_remote_user(xfer));
1539 } else {
1540 msg = g_strdup_printf(_("Unable to send file to %s, not subscribed to user presence"), purple_xfer_get_remote_user(xfer));
1543 purple_notify_error(jsx->js->gc, _("File Send Failed"),
1544 _("File Send Failed"), msg,
1545 purple_request_cpar_from_connection(jsx->js->gc));
1546 g_free(msg);
1547 } else if (g_list_length(resources) == 1) {
1548 /* only 1 resource online (probably our most common case)
1549 * so no need to ask who to send to */
1550 jbr = resources->data;
1551 do_transfer_send(xfer, jbr->name);
1552 } else {
1553 /* we've got multiple resources, we need to pick one to send to */
1554 GList *l;
1555 char *msg = g_strdup_printf(_("Please select the resource of %s to which you would like to send a file"), purple_xfer_get_remote_user(xfer));
1556 PurpleRequestFields *fields = purple_request_fields_new();
1557 PurpleRequestField *field = purple_request_field_choice_new("resource", _("Resource"), 0);
1558 PurpleRequestFieldGroup *group = purple_request_field_group_new(NULL);
1560 purple_request_field_choice_set_data_destructor(field, g_free);
1562 for(l = resources; l; l = l->next) {
1563 jbr = l->data;
1564 purple_request_field_choice_add(field, jbr->name, g_strdup(jbr->name));
1567 purple_request_field_group_add_field(group, field);
1569 purple_request_fields_add_group(fields, group);
1571 purple_request_fields(jsx->js->gc, _("Select a Resource"), msg, NULL, fields,
1572 _("Send File"), G_CALLBACK(resource_select_ok_cb), _("Cancel"), G_CALLBACK(resource_select_cancel_cb),
1573 purple_request_cpar_from_connection(jsx->js->gc), xfer);
1575 g_free(msg);
1578 g_list_free(resources);
1579 } else {
1580 PurpleXmlNode *si, *feature, *x, *field, *value;
1582 iq = jabber_iq_new(jsx->js, JABBER_IQ_RESULT);
1583 purple_xmlnode_set_attrib(iq->node, "to", purple_xfer_get_remote_user(xfer));
1584 if(jsx->iq_id)
1585 jabber_iq_set_id(iq, jsx->iq_id);
1586 else
1587 purple_debug_error("jabber", "Sending SI result with new IQ id.\n");
1589 jsx->accepted = TRUE;
1591 si = purple_xmlnode_new_child(iq->node, "si");
1592 purple_xmlnode_set_namespace(si, "http://jabber.org/protocol/si");
1594 feature = purple_xmlnode_new_child(si, "feature");
1595 purple_xmlnode_set_namespace(feature, "http://jabber.org/protocol/feature-neg");
1597 x = purple_xmlnode_new_child(feature, "x");
1598 purple_xmlnode_set_namespace(x, "jabber:x:data");
1599 purple_xmlnode_set_attrib(x, "type", "submit");
1600 field = purple_xmlnode_new_child(x, "field");
1601 purple_xmlnode_set_attrib(field, "var", "stream-method");
1603 /* we should maybe "remember" if bytestreams has failed before (in the
1604 same session) with this JID, and only present IBB as an option to
1605 avoid unnessesary timeout */
1606 /* maybe we should have an account option to always just try IBB
1607 for people who know their firewalls are very restrictive */
1608 if (jsx->stream_method & STREAM_METHOD_BYTESTREAMS) {
1609 value = purple_xmlnode_new_child(field, "value");
1610 purple_xmlnode_insert_data(value, NS_BYTESTREAMS, -1);
1611 } else if(jsx->stream_method & STREAM_METHOD_IBB) {
1612 value = purple_xmlnode_new_child(field, "value");
1613 purple_xmlnode_insert_data(value, NS_IBB, -1);
1616 jabber_iq_send(iq);
1620 PurpleXfer *jabber_si_new_xfer(PurpleConnection *gc, const char *who)
1622 JabberStream *js;
1624 PurpleXfer *xfer;
1625 JabberSIXfer *jsx;
1627 js = purple_connection_get_protocol_data(gc);
1629 xfer = purple_xfer_new(purple_connection_get_account(gc), PURPLE_XFER_TYPE_SEND, who);
1630 if (xfer)
1632 jsx = g_new0(JabberSIXfer, 1);
1633 purple_xfer_set_protocol_data(xfer, jsx);
1634 jsx->js = js;
1635 jsx->local_streamhost_fd = -1;
1637 jsx->ibb_session = NULL;
1639 purple_xfer_set_init_fnc(xfer, jabber_si_xfer_init);
1640 purple_xfer_set_cancel_send_fnc(xfer, jabber_si_xfer_cancel_send);
1641 purple_xfer_set_end_fnc(xfer, jabber_si_xfer_end);
1643 js->file_transfers = g_list_append(js->file_transfers, xfer);
1646 return xfer;
1649 void jabber_si_xfer_send(PurpleConnection *gc, const char *who, const char *file)
1651 PurpleXfer *xfer;
1653 xfer = jabber_si_new_xfer(gc, who);
1655 if (file)
1656 purple_xfer_request_accepted(xfer, file);
1657 else
1658 purple_xfer_request(xfer);
1661 #if ENABLE_FT_THUMBNAILS
1662 static void
1663 jabber_si_thumbnail_cb(JabberData *data, gchar *alt, gpointer userdata)
1665 PurpleXfer *xfer = (PurpleXfer *) userdata;
1667 if (data) {
1668 purple_xfer_set_thumbnail(xfer, jabber_data_get_data(data),
1669 jabber_data_get_size(data), jabber_data_get_type(data));
1670 /* data is ephemeral, get rid of now (the xfer re-owned the thumbnail */
1671 jabber_data_destroy(data);
1674 purple_xfer_request(xfer);
1676 #endif
1678 void jabber_si_parse(JabberStream *js, const char *from, JabberIqType type,
1679 const char *id, PurpleXmlNode *si)
1681 JabberSIXfer *jsx;
1682 PurpleXfer *xfer;
1683 PurpleXmlNode *file, *feature, *x, *field, *option, *value;
1684 #if ENABLE_FT_THUMBNAILS
1685 PurpleXmlNode *thumbnail;
1686 #endif
1687 const char *stream_id, *filename, *filesize_c, *profile;
1688 goffset filesize = 0;
1690 if(!(profile = purple_xmlnode_get_attrib(si, "profile")) ||
1691 strcmp(profile, NS_SI_FILE_TRANSFER))
1692 return;
1694 if(!(stream_id = purple_xmlnode_get_attrib(si, "id")))
1695 return;
1697 if(!(file = purple_xmlnode_get_child(si, "file")))
1698 return;
1700 if(!(filename = purple_xmlnode_get_attrib(file, "name")))
1701 return;
1703 if((filesize_c = purple_xmlnode_get_attrib(file, "size")))
1704 filesize = g_ascii_strtoull(filesize_c, NULL, 10);
1706 if(!(feature = purple_xmlnode_get_child(si, "feature")))
1707 return;
1709 if(!(x = purple_xmlnode_get_child_with_namespace(feature, "x", "jabber:x:data")))
1710 return;
1712 if(!from)
1713 return;
1715 /* if they've already sent us this file transfer with the same damn id
1716 * then we're gonna ignore it, until I think of something better to do
1717 * with it */
1718 if(jabber_si_xfer_find(js, stream_id, from) != NULL)
1719 return;
1721 jsx = g_new0(JabberSIXfer, 1);
1722 jsx->local_streamhost_fd = -1;
1724 jsx->ibb_session = NULL;
1726 for(field = purple_xmlnode_get_child(x, "field"); field; field = purple_xmlnode_get_next_twin(field)) {
1727 const char *var = purple_xmlnode_get_attrib(field, "var");
1728 if(var && !strcmp(var, "stream-method")) {
1729 for(option = purple_xmlnode_get_child(field, "option"); option;
1730 option = purple_xmlnode_get_next_twin(option)) {
1731 if((value = purple_xmlnode_get_child(option, "value"))) {
1732 char *val;
1733 if((val = purple_xmlnode_get_data(value))) {
1734 if(!strcmp(val, NS_BYTESTREAMS)) {
1735 jsx->stream_method |= STREAM_METHOD_BYTESTREAMS;
1736 } else if(!strcmp(val, NS_IBB)) {
1737 jsx->stream_method |= STREAM_METHOD_IBB;
1739 g_free(val);
1746 if(jsx->stream_method == STREAM_METHOD_UNKNOWN) {
1747 g_free(jsx);
1748 return;
1751 jsx->js = js;
1752 jsx->stream_id = g_strdup(stream_id);
1753 jsx->iq_id = g_strdup(id);
1755 xfer = purple_xfer_new(purple_connection_get_account(js->gc), PURPLE_XFER_TYPE_RECEIVE, from);
1756 g_return_if_fail(xfer != NULL);
1758 purple_xfer_set_protocol_data(xfer, jsx);
1760 purple_xfer_set_filename(xfer, filename);
1761 if(filesize > 0)
1762 purple_xfer_set_size(xfer, filesize);
1764 purple_xfer_set_init_fnc(xfer, jabber_si_xfer_init);
1765 purple_xfer_set_request_denied_fnc(xfer, jabber_si_xfer_request_denied);
1766 purple_xfer_set_cancel_recv_fnc(xfer, jabber_si_xfer_cancel_recv);
1767 purple_xfer_set_end_fnc(xfer, jabber_si_xfer_end);
1769 js->file_transfers = g_list_append(js->file_transfers, xfer);
1771 #if ENABLE_FT_THUMBNAILS
1772 /* if there is a thumbnail, we should request it... */
1773 if ((thumbnail = purple_xmlnode_get_child_with_namespace(file, "thumbnail",
1774 NS_THUMBS))) {
1775 const char *cid = purple_xmlnode_get_attrib(thumbnail, "cid");
1776 if (cid) {
1777 jabber_data_request(js, cid, purple_xfer_get_remote_user(xfer),
1778 NULL, TRUE, jabber_si_thumbnail_cb, xfer);
1779 return;
1782 #endif
1784 purple_xfer_request(xfer);
1787 void
1788 jabber_si_init(void)
1790 jabber_iq_register_handler("si", "http://jabber.org/protocol/si", jabber_si_parse);
1792 jabber_ibb_register_open_handler(jabber_si_xfer_ibb_open_cb);
1795 void
1796 jabber_si_uninit(void)
1798 jabber_ibb_unregister_open_handler(jabber_si_xfer_ibb_open_cb);