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
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
27 #include "buddylist.h"
42 #define STREAMHOST_CONNECT_TIMEOUT 15
43 #define ENABLE_FT_THUMBNAILS 0
45 struct _JabberSIXfer
{
50 PurpleProxyConnectData
*connect_data
;
51 PurpleNetworkListenData
*listen_data
;
52 guint connect_timeout
;
60 STREAM_METHOD_UNKNOWN
= 0,
61 STREAM_METHOD_BYTESTREAMS
= 2 << 1,
62 STREAM_METHOD_IBB
= 2 << 2,
63 STREAM_METHOD_UNSUPPORTED
= 2 << 30
72 int local_streamhost_fd
;
74 JabberIBBSession
*ibb_session
;
75 guint ibb_timeout_handle
;
76 PurpleCircularBuffer
*ibb_buffer
;
79 G_DEFINE_DYNAMIC_TYPE(JabberSIXfer
, jabber_si_xfer
, PURPLE_TYPE_XFER
);
81 /* some forward declarations */
82 static void jabber_si_xfer_ibb_send_init(JabberStream
*js
, PurpleXfer
*xfer
);
85 jabber_si_xfer_find(JabberStream
*js
, const char *sid
, const char *from
)
92 for(xfers
= js
->file_transfers
; xfers
; xfers
= xfers
->next
) {
93 PurpleXfer
*xfer
= xfers
->data
;
94 JabberSIXfer
*jsx
= JABBER_SI_XFER(xfer
);
95 if(jsx
->stream_id
&& purple_xfer_get_remote_user(xfer
) &&
96 purple_strequal(jsx
->stream_id
, sid
) && purple_strequal(purple_xfer_get_remote_user(xfer
), from
))
104 jabber_si_free_streamhost(gpointer data
) {
105 JabberBytestreamsStreamhost
*sh
= data
;
112 g_free(sh
->zeroconf
);
118 static void jabber_si_bytestreams_attempt_connect(PurpleXfer
*xfer
);
121 jabber_si_bytestreams_connect_cb(gpointer data
, gint source
, const gchar
*error_message
)
123 PurpleXfer
*xfer
= data
;
124 JabberSIXfer
*jsx
= JABBER_SI_XFER(xfer
);
126 PurpleXmlNode
*query
, *su
;
127 JabberBytestreamsStreamhost
*streamhost
= jsx
->streamhosts
->data
;
129 purple_proxy_info_destroy(jsx
->gpi
);
131 jsx
->connect_data
= NULL
;
133 if (jsx
->connect_timeout
> 0)
134 g_source_remove(jsx
->connect_timeout
);
135 jsx
->connect_timeout
= 0;
138 purple_debug_warning("jabber",
139 "si connection failed, jid was %s, host was %s, error was %s\n",
140 streamhost
->jid
, streamhost
->host
,
141 error_message
? error_message
: "(null)");
142 jsx
->streamhosts
= g_list_remove(jsx
->streamhosts
, streamhost
);
143 jabber_si_free_streamhost(streamhost
);
144 jabber_si_bytestreams_attempt_connect(xfer
);
148 /* unknown file transfer type is assumed to be RECEIVE */
149 if(purple_xfer_get_xfer_type(xfer
) == PURPLE_XFER_TYPE_SEND
)
151 PurpleXmlNode
*activate
;
152 iq
= jabber_iq_new_query(jsx
->js
, JABBER_IQ_SET
, NS_BYTESTREAMS
);
153 purple_xmlnode_set_attrib(iq
->node
, "to", streamhost
->jid
);
154 query
= purple_xmlnode_get_child(iq
->node
, "query");
155 purple_xmlnode_set_attrib(query
, "sid", jsx
->stream_id
);
156 activate
= purple_xmlnode_new_child(query
, "activate");
157 purple_xmlnode_insert_data(activate
, purple_xfer_get_remote_user(xfer
), -1);
159 /* TODO: We need to wait for an activation result before starting */
163 iq
= jabber_iq_new_query(jsx
->js
, JABBER_IQ_RESULT
, NS_BYTESTREAMS
);
164 purple_xmlnode_set_attrib(iq
->node
, "to", purple_xfer_get_remote_user(xfer
));
165 jabber_iq_set_id(iq
, jsx
->iq_id
);
166 query
= purple_xmlnode_get_child(iq
->node
, "query");
167 su
= purple_xmlnode_new_child(query
, "streamhost-used");
168 purple_xmlnode_set_attrib(su
, "jid", streamhost
->jid
);
173 purple_xfer_start(xfer
, source
, NULL
, -1);
177 connect_timeout_cb(gpointer data
)
179 PurpleXfer
*xfer
= data
;
180 JabberSIXfer
*jsx
= JABBER_SI_XFER(xfer
);
182 purple_debug_info("jabber", "Streamhost connection timeout of %d seconds exceeded.\n", STREAMHOST_CONNECT_TIMEOUT
);
184 jsx
->connect_timeout
= 0;
186 if (jsx
->connect_data
!= NULL
)
187 purple_proxy_connect_cancel(jsx
->connect_data
);
188 jsx
->connect_data
= NULL
;
190 /* Trigger the connect error manually */
191 jabber_si_bytestreams_connect_cb(xfer
, -1, "Timeout Exceeded.");
197 jabber_si_bytestreams_ibb_timeout_remove(JabberSIXfer
*jsx
)
199 if (jsx
->ibb_timeout_handle
) {
200 g_source_remove(jsx
->ibb_timeout_handle
);
201 jsx
->ibb_timeout_handle
= 0;
206 jabber_si_bytestreams_ibb_timeout_cb(gpointer data
)
208 PurpleXfer
*xfer
= (PurpleXfer
*) data
;
209 JabberSIXfer
*jsx
= JABBER_SI_XFER(xfer
);
211 if (jsx
&& !jsx
->ibb_session
) {
212 purple_debug_info("jabber",
213 "jabber_si_bytestreams_ibb_timeout called and IBB session not set "
214 " up yet, cancel transfer");
215 jabber_si_bytestreams_ibb_timeout_remove(jsx
);
216 purple_xfer_cancel_local(xfer
);
222 static void jabber_si_bytestreams_attempt_connect(PurpleXfer
*xfer
)
224 JabberSIXfer
*jsx
= JABBER_SI_XFER(xfer
);
225 JabberBytestreamsStreamhost
*streamhost
;
228 if(!jsx
->streamhosts
) {
229 JabberIq
*iq
= jabber_iq_new(jsx
->js
, JABBER_IQ_ERROR
);
230 PurpleXmlNode
*error
, *inf
;
233 jabber_iq_set_id(iq
, jsx
->iq_id
);
235 purple_xmlnode_set_attrib(iq
->node
, "to", purple_xfer_get_remote_user(xfer
));
236 error
= purple_xmlnode_new_child(iq
->node
, "error");
237 purple_xmlnode_set_attrib(error
, "code", "404");
238 purple_xmlnode_set_attrib(error
, "type", "cancel");
239 inf
= purple_xmlnode_new_child(error
, "item-not-found");
240 purple_xmlnode_set_namespace(inf
, NS_XMPP_STANZAS
);
244 /* if IBB is available, revert to that before giving up... */
245 if (jsx
->stream_method
& STREAM_METHOD_IBB
) {
246 /* if we are the initializer, init IBB */
247 purple_debug_info("jabber",
248 "jabber_si_bytestreams_attempt_connect: "
249 "no streamhosts found, trying IBB\n");
250 if(jsx
->stream_method
& STREAM_METHOD_BYTESTREAMS
) {
251 jsx
->stream_method
&= ~STREAM_METHOD_BYTESTREAMS
;
253 /* if we are the sender, open an IBB session, but not if we already
254 did it, since we could have received the error <iq/> from the
255 receiver already... */
256 if (purple_xfer_get_xfer_type(xfer
) == PURPLE_XFER_TYPE_SEND
257 && !jsx
->ibb_session
) {
258 jabber_si_xfer_ibb_send_init(jsx
->js
, xfer
);
260 /* setup a timeout to cancel waiting for IBB open */
261 jsx
->ibb_timeout_handle
= g_timeout_add_seconds(30,
262 jabber_si_bytestreams_ibb_timeout_cb
, xfer
);
264 /* if we are the receiver, just wait for IBB open, callback is
267 purple_xfer_cancel_local(xfer
);
273 streamhost
= jsx
->streamhosts
->data
;
275 if (jsx
->connect_data
) {
276 purple_debug_info("jabber",
277 "jabber_si_bytestreams_attempt_connect: "
278 "cancelling existing connection attempt and restarting\n");
279 purple_proxy_connect_cancel(jsx
->connect_data
);
280 jsx
->connect_data
= NULL
;
281 if (jsx
->connect_timeout
> 0)
282 g_source_remove(jsx
->connect_timeout
);
283 jsx
->connect_timeout
= 0;
285 if (jsx
->gpi
!= NULL
)
286 purple_proxy_info_destroy(jsx
->gpi
);
289 dstjid
= jabber_id_new(purple_xfer_get_remote_user(xfer
));
291 /* TODO: Deal with zeroconf */
293 if(dstjid
!= NULL
&& streamhost
->host
&& streamhost
->port
> 0) {
294 char *dstaddr
, *hash
;
295 PurpleAccount
*account
;
296 jsx
->gpi
= purple_proxy_info_new();
297 purple_proxy_info_set_proxy_type(jsx
->gpi
, PURPLE_PROXY_SOCKS5
);
298 purple_proxy_info_set_host(jsx
->gpi
, streamhost
->host
);
299 purple_proxy_info_set_port(jsx
->gpi
, streamhost
->port
);
301 /* unknown file transfer type is assumed to be RECEIVE */
302 if(purple_xfer_get_xfer_type(xfer
) == PURPLE_XFER_TYPE_SEND
)
303 dstaddr
= g_strdup_printf("%s%s@%s/%s%s@%s/%s", jsx
->stream_id
, jsx
->js
->user
->node
, jsx
->js
->user
->domain
,
304 jsx
->js
->user
->resource
, dstjid
->node
, dstjid
->domain
, dstjid
->resource
);
306 dstaddr
= g_strdup_printf("%s%s@%s/%s%s@%s/%s", jsx
->stream_id
, dstjid
->node
, dstjid
->domain
, dstjid
->resource
,
307 jsx
->js
->user
->node
, jsx
->js
->user
->domain
, jsx
->js
->user
->resource
);
309 /* Per XEP-0065, the 'host' must be SHA1(SID + from JID + to JID) */
310 hash
= g_compute_checksum_for_string(G_CHECKSUM_SHA1
,
313 account
= purple_connection_get_account(jsx
->js
->gc
);
314 jsx
->connect_data
= purple_proxy_connect_socks5_account(NULL
, account
,
316 jabber_si_bytestreams_connect_cb
, xfer
);
320 /* When selecting a streamhost, timeout after STREAMHOST_CONNECT_TIMEOUT seconds, otherwise it takes forever */
321 if (purple_xfer_get_xfer_type(xfer
) != PURPLE_XFER_TYPE_SEND
&& jsx
->connect_data
!= NULL
)
322 jsx
->connect_timeout
= g_timeout_add_seconds(
323 STREAMHOST_CONNECT_TIMEOUT
, connect_timeout_cb
, xfer
);
325 jabber_id_free(dstjid
);
328 if (jsx
->connect_data
== NULL
)
330 jsx
->streamhosts
= g_list_remove(jsx
->streamhosts
, streamhost
);
331 jabber_si_free_streamhost(streamhost
);
332 jabber_si_bytestreams_attempt_connect(xfer
);
336 void jabber_bytestreams_parse(JabberStream
*js
, const char *from
,
337 JabberIqType type
, const char *id
, PurpleXmlNode
*query
)
341 PurpleXmlNode
*streamhost
;
344 if(type
!= JABBER_IQ_SET
)
350 if(!(sid
= purple_xmlnode_get_attrib(query
, "sid")))
353 if(!(xfer
= jabber_si_xfer_find(js
, sid
, from
)))
356 jsx
= JABBER_SI_XFER(xfer
);
362 jsx
->iq_id
= g_strdup(id
);
364 for(streamhost
= purple_xmlnode_get_child(query
, "streamhost"); streamhost
;
365 streamhost
= purple_xmlnode_get_next_twin(streamhost
)) {
366 const char *jid
, *host
= NULL
, *port
, *zeroconf
;
369 if((jid
= purple_xmlnode_get_attrib(streamhost
, "jid")) &&
370 ((zeroconf
= purple_xmlnode_get_attrib(streamhost
, "zeroconf")) ||
371 ((host
= purple_xmlnode_get_attrib(streamhost
, "host")) &&
372 (port
= purple_xmlnode_get_attrib(streamhost
, "port")) &&
373 (portnum
= atoi(port
))))) {
374 JabberBytestreamsStreamhost
*sh
= g_new0(JabberBytestreamsStreamhost
, 1);
375 sh
->jid
= g_strdup(jid
);
376 sh
->host
= g_strdup(host
);
378 sh
->zeroconf
= g_strdup(zeroconf
);
379 /* If there were a lot of these, it'd be worthwhile to prepend and reverse. */
380 jsx
->streamhosts
= g_list_append(jsx
->streamhosts
, sh
);
384 jabber_si_bytestreams_attempt_connect(xfer
);
389 jabber_si_xfer_bytestreams_send_read_again_resp_cb(gpointer data
, gint source
,
390 PurpleInputCondition cond
)
392 PurpleXfer
*xfer
= data
;
393 JabberSIXfer
*jsx
= JABBER_SI_XFER(xfer
);
396 len
= write(source
, jsx
->rxqueue
+ jsx
->rxlen
, jsx
->rxmaxlen
- jsx
->rxlen
);
397 if (len
< 0 && errno
== EAGAIN
)
400 g_free(jsx
->rxqueue
);
403 purple_xfer_cancel_remote(xfer
);
408 if (jsx
->rxlen
< jsx
->rxmaxlen
)
411 purple_input_remove(purple_xfer_get_watcher(xfer
));
412 purple_xfer_set_watcher(xfer
, 0);
413 g_free(jsx
->rxqueue
);
416 /* Before actually starting sending the file, we need to wait until the
417 * recipient sends the IQ result with <streamhost-used/>
419 purple_debug_info("jabber", "SOCKS5 connection negotiation completed. "
420 "Waiting for IQ result to start file transfer.\n");
424 jabber_si_xfer_bytestreams_send_read_again_cb(gpointer data
, gint source
,
425 PurpleInputCondition cond
)
427 PurpleXfer
*xfer
= data
;
428 JabberSIXfer
*jsx
= JABBER_SI_XFER(xfer
);
429 char buffer
[42]; /* 40 for DST.ADDR + 2 bytes for port number*/
431 char *dstaddr
, *hash
;
434 purple_debug_info("jabber", "in jabber_si_xfer_bytestreams_send_read_again_cb\n");
437 purple_debug_info("jabber", "reading the first 5 bytes\n");
438 len
= read(source
, buffer
, 5 - jsx
->rxlen
);
439 if(len
< 0 && errno
== EAGAIN
)
443 purple_xfer_cancel_remote(xfer
);
446 jsx
->rxqueue
= g_realloc(jsx
->rxqueue
, len
+ jsx
->rxlen
);
447 memcpy(jsx
->rxqueue
+ jsx
->rxlen
, buffer
, len
);
450 } else if(jsx
->rxqueue
[0] != 0x05 || jsx
->rxqueue
[1] != 0x01 ||
451 jsx
->rxqueue
[3] != 0x03 || jsx
->rxqueue
[4] != 40) {
452 purple_debug_info("jabber", "Invalid socks5 conn req. header[0x%x,0x%x,0x%x,0x%x,0x%x]\n",
453 jsx
->rxqueue
[0], jsx
->rxqueue
[1], jsx
->rxqueue
[2],
454 jsx
->rxqueue
[3], jsx
->rxqueue
[4]);
456 purple_xfer_cancel_remote(xfer
);
458 } else if(jsx
->rxlen
- 5 < (size_t)jsx
->rxqueue
[4] + 2) {
459 /* Upper-bound of 257 (jsx->rxlen = 5, jsx->rxqueue[4] = 0xFF) */
460 unsigned short to_read
= jsx
->rxqueue
[4] + 2 - (jsx
->rxlen
- 5);
461 purple_debug_info("jabber", "reading %u bytes for DST.ADDR + port num (trying to read %hu now)\n",
462 jsx
->rxqueue
[4] + 2, to_read
);
463 len
= read(source
, buffer
, to_read
);
464 if(len
< 0 && errno
== EAGAIN
)
468 purple_xfer_cancel_remote(xfer
);
471 jsx
->rxqueue
= g_realloc(jsx
->rxqueue
, len
+ jsx
->rxlen
);
472 memcpy(jsx
->rxqueue
+ jsx
->rxlen
, buffer
, len
);
476 /* Have we not read all of DST.ADDR and the following 2-byte port number? */
477 if(jsx
->rxlen
- 5 < (size_t)jsx
->rxqueue
[4] + 2)
480 purple_input_remove(purple_xfer_get_watcher(xfer
));
481 purple_xfer_set_watcher(xfer
, 0);
483 dstaddr
= g_strdup_printf("%s%s@%s/%s%s", jsx
->stream_id
,
484 jsx
->js
->user
->node
, jsx
->js
->user
->domain
,
485 jsx
->js
->user
->resource
, purple_xfer_get_remote_user(xfer
));
487 /* Per XEP-0065, the 'host' must be SHA1(SID + from JID + to JID) */
488 hash
= g_compute_checksum_for_string(G_CHECKSUM_SHA1
, dstaddr
, -1);
490 if(strncmp(hash
, jsx
->rxqueue
+ 5, 40) ||
491 jsx
->rxqueue
[45] != 0x00 || jsx
->rxqueue
[46] != 0x00) {
492 if (jsx
->rxqueue
[45] != 0x00 || jsx
->rxqueue
[46] != 0x00)
493 purple_debug_error("jabber", "Got SOCKS5 BS conn with the wrong DST.PORT"
494 " (must be 0 - got[0x%x,0x%x]).\n",
495 jsx
->rxqueue
[45], jsx
->rxqueue
[46]);
497 purple_debug_error("jabber", "Got SOCKS5 BS conn with the wrong DST.ADDR"
498 " (expected '%s' - got '%.40s').\n",
499 hash
, jsx
->rxqueue
+ 5);
501 purple_xfer_cancel_remote(xfer
);
510 g_free(jsx
->rxqueue
);
511 host
= purple_network_get_my_ip(jsx
->js
->fd
);
513 jsx
->rxmaxlen
= 5 + strlen(host
) + 2;
514 jsx
->rxqueue
= g_malloc(jsx
->rxmaxlen
);
517 jsx
->rxqueue
[0] = 0x05;
518 jsx
->rxqueue
[1] = 0x00;
519 jsx
->rxqueue
[2] = 0x00;
520 jsx
->rxqueue
[3] = 0x03;
521 jsx
->rxqueue
[4] = strlen(host
);
522 memcpy(jsx
->rxqueue
+ 5, host
, strlen(host
));
523 jsx
->rxqueue
[5+strlen(host
)] = 0x00;
524 jsx
->rxqueue
[6+strlen(host
)] = 0x00;
526 purple_xfer_set_watcher(xfer
, purple_input_add(source
, PURPLE_INPUT_WRITE
,
527 jabber_si_xfer_bytestreams_send_read_again_resp_cb
, xfer
));
528 jabber_si_xfer_bytestreams_send_read_again_resp_cb(xfer
, source
,
533 jabber_si_xfer_bytestreams_send_read_response_cb(gpointer data
, gint source
,
534 PurpleInputCondition cond
)
536 PurpleXfer
*xfer
= data
;
537 JabberSIXfer
*jsx
= JABBER_SI_XFER(xfer
);
540 len
= write(source
, jsx
->rxqueue
+ jsx
->rxlen
, jsx
->rxmaxlen
- jsx
->rxlen
);
541 if (len
< 0 && errno
== EAGAIN
)
544 g_free(jsx
->rxqueue
);
547 purple_xfer_cancel_remote(xfer
);
552 if (jsx
->rxlen
< jsx
->rxmaxlen
)
555 /* If we sent a "Success", wait for a response, otherwise give up and cancel */
556 if (jsx
->rxqueue
[1] == 0x00) {
557 purple_input_remove(purple_xfer_get_watcher(xfer
));
558 purple_xfer_set_watcher(xfer
, purple_input_add(source
, PURPLE_INPUT_READ
,
559 jabber_si_xfer_bytestreams_send_read_again_cb
, xfer
));
560 g_free(jsx
->rxqueue
);
565 purple_xfer_cancel_remote(xfer
);
570 jabber_si_xfer_bytestreams_send_read_cb(gpointer data
, gint source
,
571 PurpleInputCondition cond
)
573 PurpleXfer
*xfer
= data
;
574 JabberSIXfer
*jsx
= JABBER_SI_XFER(xfer
);
579 purple_debug_info("jabber", "in jabber_si_xfer_bytestreams_send_read_cb\n");
581 purple_xfer_set_fd(xfer
, source
);
583 /** Try to read the SOCKS5 header */
585 purple_debug_info("jabber", "reading those first two bytes\n");
586 len
= read(source
, buffer
, 2 - jsx
->rxlen
);
587 if(len
< 0 && errno
== EAGAIN
)
590 purple_xfer_cancel_remote(xfer
);
593 jsx
->rxqueue
= g_realloc(jsx
->rxqueue
, len
+ jsx
->rxlen
);
594 memcpy(jsx
->rxqueue
+ jsx
->rxlen
, buffer
, len
);
597 } else if(jsx
->rxlen
- 2 < (size_t)jsx
->rxqueue
[1]) {
598 /* Has a maximum value of 255 (jsx->rxlen = 2, jsx->rxqueue[1] = 0xFF) */
599 unsigned short to_read
= jsx
->rxqueue
[1] - (jsx
->rxlen
- 2);
600 purple_debug_info("jabber", "reading %u bytes for auth methods (trying to read %hu now)\n",
601 jsx
->rxqueue
[1], to_read
);
602 len
= read(source
, buffer
, to_read
);
603 if(len
< 0 && errno
== EAGAIN
)
606 purple_xfer_cancel_remote(xfer
);
609 jsx
->rxqueue
= g_realloc(jsx
->rxqueue
, len
+ jsx
->rxlen
);
610 memcpy(jsx
->rxqueue
+ jsx
->rxlen
, buffer
, len
);
614 /* Have we not read all the auth. method bytes? */
615 if(jsx
->rxlen
-2 < (size_t)jsx
->rxqueue
[1])
618 purple_debug_info("jabber", "checking to make sure we're socks FIVE\n");
620 if(jsx
->rxqueue
[0] != 0x05) {
621 purple_xfer_cancel_remote(xfer
);
625 purple_debug_info("jabber", "going to test %u different methods\n", (guint
)jsx
->rxqueue
[1]);
627 for(i
=0; i
<jsx
->rxqueue
[1]; i
++) {
629 purple_debug_info("jabber", "testing %u\n", (guint
)jsx
->rxqueue
[i
+2]);
630 if(jsx
->rxqueue
[i
+2] == 0x00) {
631 g_free(jsx
->rxqueue
);
634 jsx
->rxqueue
= g_malloc(jsx
->rxmaxlen
);
635 jsx
->rxqueue
[0] = 0x05;
636 jsx
->rxqueue
[1] = 0x00;
637 purple_input_remove(purple_xfer_get_watcher(xfer
));
638 purple_xfer_set_watcher(xfer
, purple_input_add(source
, PURPLE_INPUT_WRITE
,
639 jabber_si_xfer_bytestreams_send_read_response_cb
,
641 jabber_si_xfer_bytestreams_send_read_response_cb(xfer
,
642 source
, PURPLE_INPUT_WRITE
);
649 g_free(jsx
->rxqueue
);
652 jsx
->rxqueue
= g_malloc(jsx
->rxmaxlen
);
653 jsx
->rxqueue
[0] = 0x05;
654 jsx
->rxqueue
[1] = 0xFF;
655 purple_input_remove(purple_xfer_get_watcher(xfer
));
656 purple_xfer_set_watcher(xfer
, purple_input_add(source
, PURPLE_INPUT_WRITE
,
657 jabber_si_xfer_bytestreams_send_read_response_cb
, xfer
));
658 jabber_si_xfer_bytestreams_send_read_response_cb(xfer
,
659 source
, PURPLE_INPUT_WRITE
);
663 jabber_si_compare_jid(gconstpointer a
, gconstpointer b
)
665 const JabberBytestreamsStreamhost
*sh
= a
;
670 return strcmp(sh
->jid
, (char *)b
);
674 jabber_si_xfer_bytestreams_send_connected_cb(gpointer data
, gint source
,
675 PurpleInputCondition cond
)
677 PurpleXfer
*xfer
= data
;
678 JabberSIXfer
*jsx
= JABBER_SI_XFER(xfer
);
681 purple_debug_info("jabber", "in jabber_si_xfer_bytestreams_send_connected_cb\n");
683 acceptfd
= accept(source
, NULL
, 0);
684 if(acceptfd
== -1 && (errno
== EAGAIN
|| errno
== EWOULDBLOCK
))
686 else if(acceptfd
== -1) {
687 purple_debug_warning("jabber", "accept: %s\n", g_strerror(errno
));
688 /* Don't cancel the ft - allow it to fall to the next streamhost.*/
692 purple_input_remove(purple_xfer_get_watcher(xfer
));
694 jsx
->local_streamhost_fd
= -1;
696 _purple_network_set_common_socket_flags(acceptfd
);
698 purple_xfer_set_watcher(xfer
, purple_input_add(acceptfd
, PURPLE_INPUT_READ
,
699 jabber_si_xfer_bytestreams_send_read_cb
, xfer
));
703 jabber_si_connect_proxy_cb(JabberStream
*js
, const char *from
,
704 JabberIqType type
, const char *id
,
705 PurpleXmlNode
*packet
, gpointer data
)
707 PurpleXfer
*xfer
= data
;
709 PurpleXmlNode
*query
, *streamhost_used
;
713 /* TODO: This need to send errors if we don't see what we're looking for */
715 /* Make sure that the xfer is actually still valid and we're not just receiving an old iq response */
716 if (!g_list_find(js
->file_transfers
, xfer
)) {
717 purple_debug_error("jabber", "Got bytestreams response for no longer existing xfer (%p)\n", xfer
);
721 jsx
= JABBER_SI_XFER(xfer
);
723 /* In the case of a direct file transfer, this is expected to return */
727 if(type
!= JABBER_IQ_RESULT
) {
728 purple_debug_info("jabber",
729 "jabber_si_xfer_connect_proxy_cb: type = error\n");
730 /* if IBB is available, open IBB session */
731 purple_debug_info("jabber",
732 "jabber_si_xfer_connect_proxy_cb: got error, method: %d\n",
734 if (jsx
->stream_method
& STREAM_METHOD_IBB
) {
735 /* if we previously tried bytestreams, we need to disble it. */
736 if(jsx
->stream_method
& STREAM_METHOD_BYTESTREAMS
) {
737 jsx
->stream_method
&= ~STREAM_METHOD_BYTESTREAMS
;
740 purple_debug_info("jabber", "IBB is possible, try it\n");
741 /* if we are the sender and haven't already opened an IBB
742 session, do so now (we might already have failed to open
743 the bytestream proxy ourselves when receiving this <iq/> */
744 if (purple_xfer_get_xfer_type(xfer
) == PURPLE_XFER_TYPE_SEND
745 && !jsx
->ibb_session
) {
746 jabber_si_xfer_ibb_send_init(js
, xfer
);
748 jsx
->ibb_timeout_handle
= g_timeout_add_seconds(30,
749 jabber_si_bytestreams_ibb_timeout_cb
, xfer
);
751 /* if we are receiver, just wait for IBB open stanza, callback
754 purple_xfer_cancel_remote(xfer
);
762 if(!(query
= purple_xmlnode_get_child(packet
, "query")))
765 if(!(streamhost_used
= purple_xmlnode_get_child(query
, "streamhost-used")))
768 if(!(jid
= purple_xmlnode_get_attrib(streamhost_used
, "jid")))
771 purple_debug_info("jabber", "jabber_si_connect_proxy_cb() will be looking at jsx %p: jsx->streamhosts is %p and jid is %s\n",
772 jsx
, jsx
->streamhosts
, jid
);
774 if(!(matched
= g_list_find_custom(jsx
->streamhosts
, jid
, jabber_si_compare_jid
)))
776 gchar
*my_jid
= g_strdup_printf("%s@%s/%s", jsx
->js
->user
->node
,
777 jsx
->js
->user
->domain
, jsx
->js
->user
->resource
);
778 if (purple_strequal(jid
, my_jid
)) {
779 purple_debug_info("jabber", "Got local SOCKS5 streamhost-used.\n");
780 purple_xfer_start(xfer
, purple_xfer_get_fd(xfer
), NULL
, -1);
782 /* if available, try to revert to IBB... */
783 if (jsx
->stream_method
& STREAM_METHOD_IBB
) {
784 if(jsx
->stream_method
& STREAM_METHOD_BYTESTREAMS
) {
785 jsx
->stream_method
&= ~STREAM_METHOD_BYTESTREAMS
;
788 purple_debug_info("jabber",
789 "jabber_si_connect_proxy_cb: trying to revert to IBB\n");
790 if (purple_xfer_get_xfer_type(xfer
) == PURPLE_XFER_TYPE_SEND
) {
791 jabber_si_xfer_ibb_send_init(jsx
->js
, xfer
);
793 jsx
->ibb_timeout_handle
= g_timeout_add_seconds(30,
794 jabber_si_bytestreams_ibb_timeout_cb
, xfer
);
796 /* if we are the receiver, we are already set up...*/
798 purple_debug_info("jabber",
799 "streamhost-used does not match any proxy that was offered to target\n");
800 purple_xfer_cancel_local(xfer
);
807 /* Clean up the local streamhost - it isn't going to be used.*/
808 if (purple_xfer_get_watcher(xfer
) > 0) {
809 purple_input_remove(purple_xfer_get_watcher(xfer
));
810 purple_xfer_set_watcher(xfer
, 0);
812 if (jsx
->local_streamhost_fd
>= 0) {
813 close(jsx
->local_streamhost_fd
);
814 jsx
->local_streamhost_fd
= -1;
817 jsx
->streamhosts
= g_list_remove_link(jsx
->streamhosts
, matched
);
818 g_list_free_full(jsx
->streamhosts
, jabber_si_free_streamhost
);
820 jsx
->streamhosts
= matched
;
822 jabber_si_bytestreams_attempt_connect(xfer
);
826 jabber_si_xfer_bytestreams_listen_cb(int sock
, gpointer data
)
828 PurpleXfer
*xfer
= data
;
831 PurpleXmlNode
*query
, *streamhost
;
834 JabberBytestreamsStreamhost
*sh
, *sh2
;
835 int streamhost_count
= 0;
837 jsx
= JABBER_SI_XFER(xfer
);
838 jsx
->listen_data
= NULL
;
840 /* I'm not sure under which conditions this can happen
841 * (it seems like it shouldn't be possible */
842 if (purple_xfer_get_status(xfer
) == PURPLE_XFER_STATUS_CANCEL_LOCAL
) {
843 g_object_unref(xfer
);
847 g_object_unref(xfer
);
849 iq
= jabber_iq_new_query(jsx
->js
, JABBER_IQ_SET
, NS_BYTESTREAMS
);
850 purple_xmlnode_set_attrib(iq
->node
, "to", purple_xfer_get_remote_user(xfer
));
851 query
= purple_xmlnode_get_child(iq
->node
, "query");
853 purple_xmlnode_set_attrib(query
, "sid", jsx
->stream_id
);
855 /* If we successfully started listening locally */
859 purple_network_get_all_local_system_ips();
860 const char *public_ip
;
861 gboolean has_public_ip
= FALSE
;
863 jsx
->local_streamhost_fd
= sock
;
865 jid
= g_strdup_printf("%s@%s/%s", jsx
->js
->user
->node
,
866 jsx
->js
->user
->domain
, jsx
->js
->user
->resource
);
867 purple_xfer_set_local_port(xfer
, purple_network_get_port_from_fd(sock
));
868 g_snprintf(port
, sizeof(port
), "%hu", purple_xfer_get_local_port(xfer
));
870 public_ip
= purple_network_get_my_ip(jsx
->js
->fd
);
872 /* Include the localhost's IPs (for in-network transfers) */
874 gchar
*local_ip
= local_ips
->data
;
876 streamhost
= purple_xmlnode_new_child(query
, "streamhost");
877 purple_xmlnode_set_attrib(streamhost
, "jid", jid
);
878 purple_xmlnode_set_attrib(streamhost
, "host", local_ip
);
879 purple_xmlnode_set_attrib(streamhost
, "port", port
);
880 if (purple_strequal(local_ip
, public_ip
))
881 has_public_ip
= TRUE
;
883 local_ips
= g_list_delete_link(local_ips
, local_ips
);
886 /* Include the public IP (assuming that there is a port mapped somehow) */
887 if (!has_public_ip
&& !purple_strequal(public_ip
, "0.0.0.0")) {
889 streamhost
= purple_xmlnode_new_child(query
, "streamhost");
890 purple_xmlnode_set_attrib(streamhost
, "jid", jid
);
891 purple_xmlnode_set_attrib(streamhost
, "host", public_ip
);
892 purple_xmlnode_set_attrib(streamhost
, "port", port
);
897 /* The listener for the local proxy */
898 purple_xfer_set_watcher(xfer
, purple_input_add(sock
, PURPLE_INPUT_READ
,
899 jabber_si_xfer_bytestreams_send_connected_cb
, xfer
));
902 for (tmp
= jsx
->js
->bs_proxies
; tmp
; tmp
= tmp
->next
) {
905 /* TODO: deal with zeroconf proxies */
907 if (!(sh
->jid
&& sh
->host
&& sh
->port
> 0))
910 purple_debug_info("jabber", "jabber_si_xfer_bytestreams_listen_cb() will be looking at jsx %p: jsx->streamhosts %p and sh->jid %p\n",
911 jsx
, jsx
->streamhosts
, sh
->jid
);
912 if(g_list_find_custom(jsx
->streamhosts
, sh
->jid
, jabber_si_compare_jid
) != NULL
)
916 streamhost
= purple_xmlnode_new_child(query
, "streamhost");
917 purple_xmlnode_set_attrib(streamhost
, "jid", sh
->jid
);
918 purple_xmlnode_set_attrib(streamhost
, "host", sh
->host
);
919 g_snprintf(port
, sizeof(port
), "%hu", sh
->port
);
920 purple_xmlnode_set_attrib(streamhost
, "port", port
);
922 sh2
= g_new0(JabberBytestreamsStreamhost
, 1);
923 sh2
->jid
= g_strdup(sh
->jid
);
924 sh2
->host
= g_strdup(sh
->host
);
925 /*sh2->zeroconf = g_strdup(sh->zeroconf);*/
926 sh2
->port
= sh
->port
;
928 jsx
->streamhosts
= g_list_prepend(jsx
->streamhosts
, sh2
);
931 /* We have no way of transferring, cancel the transfer */
932 if (streamhost_count
== 0) {
935 /* if available, revert to IBB */
936 if (jsx
->stream_method
& STREAM_METHOD_IBB
) {
937 if(jsx
->stream_method
& STREAM_METHOD_BYTESTREAMS
) {
938 jsx
->stream_method
&= ~STREAM_METHOD_BYTESTREAMS
;
940 purple_debug_info("jabber",
941 "jabber_si_xfer_bytestreams_listen_cb: trying to revert to IBB\n");
942 if (purple_xfer_get_xfer_type(xfer
) == PURPLE_XFER_TYPE_SEND
) {
943 /* if we are the sender, init the IBB session... */
944 jabber_si_xfer_ibb_send_init(jsx
->js
, xfer
);
946 jsx
->ibb_timeout_handle
= g_timeout_add_seconds(30,
947 jabber_si_bytestreams_ibb_timeout_cb
, xfer
);
949 /* if we are the receiver, we should just wait... the IBB open
950 handler has already been set up... */
952 /* We should probably notify the target,
953 but this really shouldn't ever happen */
954 purple_xfer_cancel_local(xfer
);
960 jabber_iq_set_callback(iq
, jabber_si_connect_proxy_cb
, xfer
);
967 jabber_si_xfer_bytestreams_send_init(PurpleXfer
*xfer
)
970 PurpleProxyType proxy_type
;
974 jsx
= JABBER_SI_XFER(xfer
);
976 /* TODO: This should probably be done with an account option instead of
977 * piggy-backing on the TOR proxy type. */
978 proxy_type
= purple_proxy_info_get_proxy_type(
979 purple_proxy_get_setup(purple_connection_get_account(jsx
->js
->gc
)));
980 if (proxy_type
== PURPLE_PROXY_TOR
) {
981 purple_debug_info("jabber", "Skipping attempting local streamhost.\n");
982 jsx
->listen_data
= NULL
;
984 jsx
->listen_data
= purple_network_listen_range(0, 0, AF_UNSPEC
, SOCK_STREAM
, TRUE
,
985 jabber_si_xfer_bytestreams_listen_cb
, xfer
);
987 if (jsx
->listen_data
== NULL
) {
988 /* We couldn't open a local port. Perhaps we can use a proxy. */
989 jabber_si_xfer_bytestreams_listen_cb(-1, xfer
);
995 jabber_si_xfer_ibb_error_cb(JabberIBBSession
*sess
)
997 PurpleXfer
*xfer
= (PurpleXfer
*) jabber_ibb_session_get_user_data(sess
);
999 purple_debug_error("jabber", "an error occurred during IBB file transfer\n");
1000 purple_xfer_cancel_remote(xfer
);
1004 jabber_si_xfer_ibb_closed_cb(JabberIBBSession
*sess
)
1006 PurpleXfer
*xfer
= (PurpleXfer
*) jabber_ibb_session_get_user_data(sess
);
1008 purple_debug_info("jabber", "the remote user closed the transfer\n");
1009 if (purple_xfer_get_bytes_remaining(xfer
) > 0) {
1010 purple_xfer_cancel_remote(xfer
);
1012 purple_xfer_set_completed(xfer
, TRUE
);
1013 purple_xfer_end(xfer
);
1018 jabber_si_xfer_ibb_recv_data_cb(JabberIBBSession
*sess
, gpointer data
,
1021 PurpleXfer
*xfer
= (PurpleXfer
*) jabber_ibb_session_get_user_data(sess
);
1022 JabberSIXfer
*jsx
= JABBER_SI_XFER(xfer
);
1024 if ((goffset
)size
<= purple_xfer_get_bytes_remaining(xfer
)) {
1025 purple_debug_info("jabber", "about to write %" G_GSIZE_FORMAT
" bytes from IBB stream\n",
1027 purple_circular_buffer_append(jsx
->ibb_buffer
, data
, size
);
1028 purple_xfer_protocol_ready(xfer
);
1030 /* trying to write past size of file transfers negotiated size,
1031 reject transfer to protect against malicious behaviour */
1032 purple_debug_error("jabber",
1033 "IBB file transfer send more data than expected\n");
1034 purple_xfer_cancel_remote(xfer
);
1040 jabber_si_xfer_ibb_read(PurpleXfer
*xfer
, guchar
**out_buffer
, size_t buf_size
)
1042 JabberSIXfer
*jsx
= JABBER_SI_XFER(xfer
);
1047 if(jsx
->stream_method
& STREAM_METHOD_BYTESTREAMS
) {
1048 return PURPLE_XFER_CLASS(jabber_si_xfer_parent_class
)->read(xfer
, out_buffer
, buf_size
);
1051 size
= purple_circular_buffer_get_used(jsx
->ibb_buffer
);
1053 *out_buffer
= buffer
= g_malloc(size
);
1054 while ((tmp
= purple_circular_buffer_get_max_read(jsx
->ibb_buffer
))) {
1055 const gchar
*output
= purple_circular_buffer_get_output(jsx
->ibb_buffer
);
1056 memcpy(buffer
, output
, tmp
);
1058 purple_circular_buffer_mark_read(jsx
->ibb_buffer
, tmp
);
1065 jabber_si_xfer_ibb_open_cb(JabberStream
*js
, const char *who
, const char *id
,
1066 PurpleXmlNode
*open
)
1068 const gchar
*sid
= purple_xmlnode_get_attrib(open
, "sid");
1069 PurpleXfer
*xfer
= jabber_si_xfer_find(js
, sid
, who
);
1072 JabberSIXfer
*jsx
= JABBER_SI_XFER(xfer
);
1073 JabberIBBSession
*sess
=
1074 jabber_ibb_session_create_from_xmlnode(js
, who
, id
, open
, xfer
);
1076 jabber_si_bytestreams_ibb_timeout_remove(jsx
);
1079 /* setup callbacks here...*/
1080 jabber_ibb_session_set_data_received_callback(sess
,
1081 jabber_si_xfer_ibb_recv_data_cb
);
1082 jabber_ibb_session_set_closed_callback(sess
,
1083 jabber_si_xfer_ibb_closed_cb
);
1084 jabber_ibb_session_set_error_callback(sess
,
1085 jabber_si_xfer_ibb_error_cb
);
1087 jsx
->ibb_session
= sess
;
1088 /* we handle up to block-size bytes of decoded data, to handle
1089 clients interpreting the block-size attribute as that
1090 (see also remark in ibb.c) */
1092 purple_circular_buffer_new(jabber_ibb_session_get_block_size(sess
));
1094 /* start the transfer */
1095 purple_xfer_start(xfer
, -1, NULL
, 0);
1098 /* failed to create IBB session */
1099 purple_debug_error("jabber", "failed to create IBB session\n");
1100 purple_xfer_cancel_remote(xfer
);
1104 /* we got an IBB <open/> for an unknown file transfer, pass along... */
1105 purple_debug_info("jabber",
1106 "IBB open did not match any SI file transfer\n");
1112 jabber_si_xfer_ibb_write(PurpleXfer
*xfer
, const guchar
*buffer
, size_t len
)
1114 JabberSIXfer
*jsx
= JABBER_SI_XFER(xfer
);
1115 JabberIBBSession
*sess
= jsx
->ibb_session
;
1118 if(jsx
->stream_method
& STREAM_METHOD_BYTESTREAMS
) {
1119 purple_debug_error("jabber", "falling back to raw socket\n");
1120 return PURPLE_XFER_CLASS(jabber_si_xfer_parent_class
)->write(xfer
, buffer
, len
);
1123 packet_size
= MIN(len
, jabber_ibb_session_get_max_data_size(sess
));
1125 jabber_ibb_session_send_data(sess
, buffer
, packet_size
);
1131 jabber_si_xfer_ibb_sent_cb(JabberIBBSession
*sess
)
1133 PurpleXfer
*xfer
= (PurpleXfer
*) jabber_ibb_session_get_user_data(sess
);
1134 goffset remaining
= purple_xfer_get_bytes_remaining(xfer
);
1136 if (remaining
== 0) {
1137 /* close the session */
1138 jabber_ibb_session_close(sess
);
1139 purple_xfer_set_completed(xfer
, TRUE
);
1140 purple_xfer_end(xfer
);
1143 purple_xfer_protocol_ready(xfer
);
1148 jabber_si_xfer_ibb_opened_cb(JabberIBBSession
*sess
)
1150 PurpleXfer
*xfer
= (PurpleXfer
*) jabber_ibb_session_get_user_data(sess
);
1152 if (jabber_ibb_session_get_state(sess
) == JABBER_IBB_SESSION_OPENED
) {
1153 purple_xfer_start(xfer
, -1, NULL
, 0);
1154 purple_xfer_protocol_ready(xfer
);
1157 purple_xfer_end(xfer
);
1162 jabber_si_xfer_ibb_send_init(JabberStream
*js
, PurpleXfer
*xfer
)
1164 JabberSIXfer
*jsx
= JABBER_SI_XFER(xfer
);
1166 jsx
->ibb_session
= jabber_ibb_session_create(js
, jsx
->stream_id
,
1167 purple_xfer_get_remote_user(xfer
), xfer
);
1169 if (jsx
->ibb_session
) {
1170 /* should set callbacks here... */
1171 jabber_ibb_session_set_opened_callback(jsx
->ibb_session
,
1172 jabber_si_xfer_ibb_opened_cb
);
1173 jabber_ibb_session_set_data_sent_callback(jsx
->ibb_session
,
1174 jabber_si_xfer_ibb_sent_cb
);
1175 jabber_ibb_session_set_closed_callback(jsx
->ibb_session
,
1176 jabber_si_xfer_ibb_closed_cb
);
1177 jabber_ibb_session_set_error_callback(jsx
->ibb_session
,
1178 jabber_si_xfer_ibb_error_cb
);
1181 purple_circular_buffer_new(jabber_ibb_session_get_max_data_size(jsx
->ibb_session
));
1183 /* open the IBB session */
1184 jabber_ibb_session_open(jsx
->ibb_session
);
1187 /* failed to create IBB session */
1188 purple_debug_error("jabber",
1189 "failed to initiate IBB session for file transfer\n");
1190 purple_xfer_cancel_local(xfer
);
1194 static void jabber_si_xfer_send_method_cb(JabberStream
*js
, const char *from
,
1195 JabberIqType type
, const char *id
,
1196 PurpleXmlNode
*packet
, gpointer data
)
1198 PurpleXfer
*xfer
= data
;
1199 PurpleXmlNode
*si
, *feature
, *x
, *field
, *value
;
1200 gboolean found_method
= FALSE
;
1202 if(!(si
= purple_xmlnode_get_child_with_namespace(packet
, "si", "http://jabber.org/protocol/si"))) {
1203 purple_xfer_cancel_remote(xfer
);
1207 if(!(feature
= purple_xmlnode_get_child_with_namespace(si
, "feature", "http://jabber.org/protocol/feature-neg"))) {
1208 purple_xfer_cancel_remote(xfer
);
1212 if(!(x
= purple_xmlnode_get_child_with_namespace(feature
, "x", "jabber:x:data"))) {
1213 purple_xfer_cancel_remote(xfer
);
1217 for(field
= purple_xmlnode_get_child(x
, "field"); field
; field
= purple_xmlnode_get_next_twin(field
)) {
1218 const char *var
= purple_xmlnode_get_attrib(field
, "var");
1219 JabberSIXfer
*jsx
= JABBER_SI_XFER(xfer
);
1221 if(purple_strequal(var
, "stream-method")) {
1222 if((value
= purple_xmlnode_get_child(field
, "value"))) {
1223 char *val
= purple_xmlnode_get_data(value
);
1224 if(purple_strequal(val
, NS_BYTESTREAMS
)) {
1225 jabber_si_xfer_bytestreams_send_init(xfer
);
1226 jsx
->stream_method
|= STREAM_METHOD_BYTESTREAMS
;
1227 found_method
= TRUE
;
1228 } else if (purple_strequal(val
, NS_IBB
)) {
1229 jsx
->stream_method
|= STREAM_METHOD_IBB
;
1230 if (!found_method
) {
1231 /* we haven't tried to init a bytestream session, yet
1232 start IBB right away... */
1233 jabber_si_xfer_ibb_send_init(js
, xfer
);
1234 found_method
= TRUE
;
1242 if (!found_method
) {
1243 purple_xfer_cancel_remote(xfer
);
1248 static void jabber_si_xfer_send_request(PurpleXfer
*xfer
)
1250 JabberSIXfer
*jsx
= JABBER_SI_XFER(xfer
);
1252 PurpleXmlNode
*si
, *file
, *feature
, *x
, *field
, *option
, *value
;
1254 #if ENABLE_FT_THUMBNAILS
1255 gconstpointer thumb
;
1258 purple_xfer_prepare_thumbnail(xfer
, "jpeg,png");
1260 purple_xfer_set_filename(xfer
, g_path_get_basename(purple_xfer_get_local_filename(xfer
)));
1262 iq
= jabber_iq_new(jsx
->js
, JABBER_IQ_SET
);
1263 purple_xmlnode_set_attrib(iq
->node
, "to", purple_xfer_get_remote_user(xfer
));
1264 si
= purple_xmlnode_new_child(iq
->node
, "si");
1265 purple_xmlnode_set_namespace(si
, "http://jabber.org/protocol/si");
1266 jsx
->stream_id
= jabber_get_next_id(jsx
->js
);
1267 purple_xmlnode_set_attrib(si
, "id", jsx
->stream_id
);
1268 purple_xmlnode_set_attrib(si
, "profile", NS_SI_FILE_TRANSFER
);
1270 file
= purple_xmlnode_new_child(si
, "file");
1271 purple_xmlnode_set_namespace(file
, NS_SI_FILE_TRANSFER
);
1272 purple_xmlnode_set_attrib(file
, "name", purple_xfer_get_filename(xfer
));
1273 g_snprintf(buf
, sizeof(buf
), "%" G_GOFFSET_FORMAT
, purple_xfer_get_size(xfer
));
1274 purple_xmlnode_set_attrib(file
, "size", buf
);
1275 /* maybe later we'll do hash and date attribs */
1277 #if ENABLE_FT_THUMBNAILS
1278 /* add thumbnail, if appropriate */
1279 if ((thumb
= purple_xfer_get_thumbnail(xfer
, &thumb_size
))) {
1280 const gchar
*mimetype
= purple_xfer_get_thumbnail_mimetype(xfer
);
1281 JabberData
*thumbnail_data
=
1282 jabber_data_create_from_data(thumb
, thumb_size
,
1283 mimetype
, TRUE
, jsx
->js
);
1284 PurpleXmlNode
*thumbnail
= purple_xmlnode_new_child(file
, "thumbnail");
1285 purple_xmlnode_set_namespace(thumbnail
, NS_THUMBS
);
1286 purple_xmlnode_set_attrib(thumbnail
, "cid",
1287 jabber_data_get_cid(thumbnail_data
));
1288 purple_xmlnode_set_attrib(thumbnail
, "mime-type", mimetype
);
1290 jabber_data_associate_local(thumbnail_data
, NULL
);
1294 feature
= purple_xmlnode_new_child(si
, "feature");
1295 purple_xmlnode_set_namespace(feature
, "http://jabber.org/protocol/feature-neg");
1296 x
= purple_xmlnode_new_child(feature
, "x");
1297 purple_xmlnode_set_namespace(x
, "jabber:x:data");
1298 purple_xmlnode_set_attrib(x
, "type", "form");
1299 field
= purple_xmlnode_new_child(x
, "field");
1300 purple_xmlnode_set_attrib(field
, "var", "stream-method");
1301 purple_xmlnode_set_attrib(field
, "type", "list-single");
1302 /* maybe we should add an option to always skip bytestreams for people
1303 behind troublesome firewalls */
1304 option
= purple_xmlnode_new_child(field
, "option");
1305 value
= purple_xmlnode_new_child(option
, "value");
1306 purple_xmlnode_insert_data(value
, NS_BYTESTREAMS
, -1);
1307 option
= purple_xmlnode_new_child(field
, "option");
1308 value
= purple_xmlnode_new_child(option
, "value");
1309 purple_xmlnode_insert_data(value
, NS_IBB
, -1);
1311 jabber_iq_set_callback(iq
, jabber_si_xfer_send_method_cb
, xfer
);
1313 /* Store the IQ id so that we can cancel the callback */
1315 jsx
->iq_id
= g_strdup(iq
->id
);
1321 * These four functions should only be called from the PurpleXfer functions
1322 * (typically purple_xfer_cancel_(remote|local), purple_xfer_end, or
1323 * purple_xfer_request_denied.
1325 static void jabber_si_xfer_cancel_send(PurpleXfer
*xfer
)
1327 JabberSIXfer
*jsx
= JABBER_SI_XFER(xfer
);
1329 /* if there is an IBB session active, send close on that */
1330 if (jsx
->ibb_session
) {
1331 jabber_ibb_session_close(jsx
->ibb_session
);
1333 purple_debug_info("jabber", "in jabber_si_xfer_cancel_send\n");
1337 static void jabber_si_xfer_request_denied(PurpleXfer
*xfer
)
1339 JabberSIXfer
*jsx
= JABBER_SI_XFER(xfer
);
1340 JabberStream
*js
= jsx
->js
;
1343 * TODO: It's probably an error if jsx->iq_id == NULL. g_return_if_fail
1344 * might be warranted.
1346 if (jsx
->iq_id
&& !jsx
->accepted
) {
1348 PurpleXmlNode
*error
, *child
;
1349 iq
= jabber_iq_new(js
, JABBER_IQ_ERROR
);
1350 purple_xmlnode_set_attrib(iq
->node
, "to", purple_xfer_get_remote_user(xfer
));
1351 jabber_iq_set_id(iq
, jsx
->iq_id
);
1353 error
= purple_xmlnode_new_child(iq
->node
, "error");
1354 purple_xmlnode_set_attrib(error
, "type", "cancel");
1355 child
= purple_xmlnode_new_child(error
, "forbidden");
1356 purple_xmlnode_set_namespace(child
, NS_XMPP_STANZAS
);
1357 child
= purple_xmlnode_new_child(error
, "text");
1358 purple_xmlnode_set_namespace(child
, NS_XMPP_STANZAS
);
1359 purple_xmlnode_insert_data(child
, "Offer Declined", -1);
1364 purple_debug_info("jabber", "in jabber_si_xfer_request_denied\n");
1368 static void jabber_si_xfer_cancel_recv(PurpleXfer
*xfer
)
1370 JabberSIXfer
*jsx
= JABBER_SI_XFER(xfer
);
1371 /* if there is an IBB session active, send close */
1372 if (jsx
->ibb_session
) {
1373 jabber_ibb_session_close(jsx
->ibb_session
);
1375 purple_debug_info("jabber", "in jabber_si_xfer_cancel_recv\n");
1379 static void jabber_si_xfer_send_disco_cb(JabberStream
*js
, const char *who
,
1380 JabberCapabilities capabilities
, gpointer data
)
1382 PurpleXfer
*xfer
= PURPLE_XFER(data
);
1383 JabberSIXfer
*jsx
= JABBER_SI_XFER(xfer
);
1385 if (capabilities
& JABBER_CAP_IBB
) {
1386 purple_debug_info("jabber",
1387 "jabber_si_xfer_send_disco_cb: remote JID supports IBB\n");
1388 jsx
->stream_method
|= STREAM_METHOD_IBB
;
1391 if (capabilities
& JABBER_CAP_SI_FILE_XFER
) {
1392 jabber_si_xfer_send_request(xfer
);
1394 char *msg
= g_strdup_printf(_("Unable to send file to %s, user does not support file transfers"), who
);
1395 purple_notify_error(js
->gc
, _("File Send Failed"),
1396 _("File Send Failed"), msg
,
1397 purple_request_cpar_from_connection(js
->gc
));
1399 purple_xfer_cancel_local(xfer
);
1403 static void resource_select_cancel_cb(PurpleXfer
*xfer
, PurpleRequestFields
*fields
)
1405 purple_xfer_cancel_local(xfer
);
1408 static void do_transfer_send(PurpleXfer
*xfer
, const char *resource
)
1410 JabberSIXfer
*jsx
= JABBER_SI_XFER(xfer
);
1411 char **who_v
= g_strsplit(purple_xfer_get_remote_user(xfer
), "/", 2);
1414 JabberBuddyResource
*jbr
= NULL
;
1416 jb
= jabber_buddy_find(jsx
->js
, who_v
[0], FALSE
);
1418 jbr
= jabber_buddy_find_resource(jb
, resource
);
1421 who
= g_strdup_printf("%s/%s", who_v
[0], resource
);
1423 purple_xfer_set_remote_user(xfer
, who
);
1425 if (jbr
&& jabber_resource_know_capabilities(jbr
)) {
1428 if (jabber_resource_has_capability(jbr
, NS_IBB
))
1429 jsx
->stream_method
|= STREAM_METHOD_IBB
;
1430 if (jabber_resource_has_capability(jbr
, NS_SI_FILE_TRANSFER
)) {
1431 jabber_si_xfer_send_request(xfer
);
1436 msg
= g_strdup_printf(_("Unable to send file to %s, user does not support file transfers"), who
);
1437 purple_notify_error(jsx
->js
->gc
, _("File Send Failed"),
1438 _("File Send Failed"), msg
,
1439 purple_request_cpar_from_connection(jsx
->js
->gc
));
1441 purple_xfer_cancel_local(xfer
);
1443 jabber_disco_info_do(jsx
->js
, who
,
1444 jabber_si_xfer_send_disco_cb
, xfer
);
1450 static void resource_select_ok_cb(PurpleXfer
*xfer
, PurpleRequestFields
*fields
)
1452 PurpleRequestField
*field
= purple_request_fields_get_field(fields
, "resource");
1453 const char *selected_label
= purple_request_field_choice_get_value(field
);
1455 do_transfer_send(xfer
, selected_label
);
1458 static void jabber_si_xfer_xfer_init(PurpleXfer
*xfer
)
1460 JabberSIXfer
*jsx
= JABBER_SI_XFER(xfer
);
1462 if(purple_xfer_get_xfer_type(xfer
) == PURPLE_XFER_TYPE_SEND
) {
1464 JabberBuddyResource
*jbr
= NULL
;
1466 GList
*resources
= NULL
;
1468 if(NULL
!= (resource
= jabber_get_resource(purple_xfer_get_remote_user(xfer
)))) {
1469 /* they've specified a resource, no need to ask or
1470 * default or anything, just do it */
1472 do_transfer_send(xfer
, resource
);
1477 jb
= jabber_buddy_find(jsx
->js
, purple_xfer_get_remote_user(xfer
), TRUE
);
1482 for (l
= jb
->resources
; l
; l
= g_list_next(l
)) {
1485 if (!jabber_resource_know_capabilities(jbr
) ||
1486 (jabber_resource_has_capability(jbr
, NS_SI_FILE_TRANSFER
)
1487 && (jabber_resource_has_capability(jbr
, NS_BYTESTREAMS
)
1488 || jabber_resource_has_capability(jbr
, NS_IBB
)))) {
1489 resources
= g_list_append(resources
, jbr
);
1495 /* no resources online, we're trying to send to someone
1496 * whose presence we're not subscribed to, or
1497 * someone who is offline. Let's inform the user */
1501 msg
= g_strdup_printf(_("Unable to send file to %s, invalid JID"), purple_xfer_get_remote_user(xfer
));
1502 } else if(jb
->subscription
& JABBER_SUB_TO
) {
1503 msg
= g_strdup_printf(_("Unable to send file to %s, user is not online"), purple_xfer_get_remote_user(xfer
));
1505 msg
= g_strdup_printf(_("Unable to send file to %s, not subscribed to user presence"), purple_xfer_get_remote_user(xfer
));
1508 purple_notify_error(jsx
->js
->gc
, _("File Send Failed"),
1509 _("File Send Failed"), msg
,
1510 purple_request_cpar_from_connection(jsx
->js
->gc
));
1512 } else if (g_list_length(resources
) == 1) {
1513 /* only 1 resource online (probably our most common case)
1514 * so no need to ask who to send to */
1515 jbr
= resources
->data
;
1516 do_transfer_send(xfer
, jbr
->name
);
1518 /* we've got multiple resources, we need to pick one to send to */
1520 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
));
1521 PurpleRequestFields
*fields
= purple_request_fields_new();
1522 PurpleRequestField
*field
= purple_request_field_choice_new("resource", _("Resource"), 0);
1523 PurpleRequestFieldGroup
*group
= purple_request_field_group_new(NULL
);
1525 purple_request_field_choice_set_data_destructor(field
, g_free
);
1527 for(l
= resources
; l
; l
= l
->next
) {
1529 purple_request_field_choice_add(field
, jbr
->name
, g_strdup(jbr
->name
));
1532 purple_request_field_group_add_field(group
, field
);
1534 purple_request_fields_add_group(fields
, group
);
1536 purple_request_fields(jsx
->js
->gc
, _("Select a Resource"), msg
, NULL
, fields
,
1537 _("Send File"), G_CALLBACK(resource_select_ok_cb
), _("Cancel"), G_CALLBACK(resource_select_cancel_cb
),
1538 purple_request_cpar_from_connection(jsx
->js
->gc
), xfer
);
1543 g_list_free(resources
);
1545 PurpleXmlNode
*si
, *feature
, *x
, *field
, *value
;
1547 iq
= jabber_iq_new(jsx
->js
, JABBER_IQ_RESULT
);
1548 purple_xmlnode_set_attrib(iq
->node
, "to", purple_xfer_get_remote_user(xfer
));
1550 jabber_iq_set_id(iq
, jsx
->iq_id
);
1552 purple_debug_error("jabber", "Sending SI result with new IQ id.\n");
1554 jsx
->accepted
= TRUE
;
1556 si
= purple_xmlnode_new_child(iq
->node
, "si");
1557 purple_xmlnode_set_namespace(si
, "http://jabber.org/protocol/si");
1559 feature
= purple_xmlnode_new_child(si
, "feature");
1560 purple_xmlnode_set_namespace(feature
, "http://jabber.org/protocol/feature-neg");
1562 x
= purple_xmlnode_new_child(feature
, "x");
1563 purple_xmlnode_set_namespace(x
, "jabber:x:data");
1564 purple_xmlnode_set_attrib(x
, "type", "submit");
1565 field
= purple_xmlnode_new_child(x
, "field");
1566 purple_xmlnode_set_attrib(field
, "var", "stream-method");
1568 /* we should maybe "remember" if bytestreams has failed before (in the
1569 same session) with this JID, and only present IBB as an option to
1570 avoid unnessesary timeout */
1571 /* maybe we should have an account option to always just try IBB
1572 for people who know their firewalls are very restrictive */
1573 if (jsx
->stream_method
& STREAM_METHOD_BYTESTREAMS
) {
1574 value
= purple_xmlnode_new_child(field
, "value");
1575 purple_xmlnode_insert_data(value
, NS_BYTESTREAMS
, -1);
1576 } else if(jsx
->stream_method
& STREAM_METHOD_IBB
) {
1577 value
= purple_xmlnode_new_child(field
, "value");
1578 purple_xmlnode_insert_data(value
, NS_IBB
, -1);
1585 PurpleXfer
*jabber_si_new_xfer(PurpleProtocolXfer
*prplxfer
, PurpleConnection
*gc
, const char *who
)
1590 js
= purple_connection_get_protocol_data(gc
);
1593 JABBER_TYPE_SI_XFER
,
1594 "account", purple_connection_get_account(gc
),
1595 "type", PURPLE_XFER_TYPE_SEND
,
1601 js
->file_transfers
= g_list_append(js
->file_transfers
, jsx
);
1603 return PURPLE_XFER(jsx
);
1606 void jabber_si_xfer_send(PurpleProtocolXfer
*prplxfer
, PurpleConnection
*gc
, const char *who
, const char *file
)
1610 xfer
= jabber_si_new_xfer(prplxfer
, gc
, who
);
1613 purple_xfer_request_accepted(xfer
, file
);
1615 purple_xfer_request(xfer
);
1618 #if ENABLE_FT_THUMBNAILS
1620 jabber_si_thumbnail_cb(JabberData
*data
, gchar
*alt
, gpointer userdata
)
1622 PurpleXfer
*xfer
= (PurpleXfer
*) userdata
;
1625 purple_xfer_set_thumbnail(xfer
, jabber_data_get_data(data
),
1626 jabber_data_get_size(data
), jabber_data_get_type(data
));
1627 /* data is ephemeral, get rid of now (the xfer re-owned the thumbnail */
1628 jabber_data_destroy(data
);
1631 purple_xfer_request(xfer
);
1635 void jabber_si_parse(JabberStream
*js
, const char *from
, JabberIqType type
,
1636 const char *id
, PurpleXmlNode
*si
)
1639 PurpleXmlNode
*file
, *feature
, *x
, *field
, *option
, *value
;
1640 #if ENABLE_FT_THUMBNAILS
1641 PurpleXmlNode
*thumbnail
;
1643 const char *stream_id
, *filename
, *filesize_c
, *profile
;
1644 goffset filesize
= 0;
1646 if(!(profile
= purple_xmlnode_get_attrib(si
, "profile")) ||
1647 !purple_strequal(profile
, NS_SI_FILE_TRANSFER
))
1650 if(!(stream_id
= purple_xmlnode_get_attrib(si
, "id")))
1653 if(!(file
= purple_xmlnode_get_child(si
, "file")))
1656 if(!(filename
= purple_xmlnode_get_attrib(file
, "name")))
1659 if((filesize_c
= purple_xmlnode_get_attrib(file
, "size")))
1660 filesize
= g_ascii_strtoull(filesize_c
, NULL
, 10);
1662 if(!(feature
= purple_xmlnode_get_child(si
, "feature")))
1665 if(!(x
= purple_xmlnode_get_child_with_namespace(feature
, "x", "jabber:x:data")))
1671 /* if they've already sent us this file transfer with the same damn id
1672 * then we're gonna ignore it, until I think of something better to do
1674 if(jabber_si_xfer_find(js
, stream_id
, from
) != NULL
)
1678 JABBER_TYPE_SI_XFER
,
1679 "account", purple_connection_get_account(js
->gc
),
1680 "type", PURPLE_XFER_TYPE_RECEIVE
,
1681 "remote-user", from
,
1685 for(field
= purple_xmlnode_get_child(x
, "field"); field
; field
= purple_xmlnode_get_next_twin(field
)) {
1686 const char *var
= purple_xmlnode_get_attrib(field
, "var");
1687 if(purple_strequal(var
, "stream-method")) {
1688 for(option
= purple_xmlnode_get_child(field
, "option"); option
;
1689 option
= purple_xmlnode_get_next_twin(option
)) {
1690 if((value
= purple_xmlnode_get_child(option
, "value"))) {
1692 if((val
= purple_xmlnode_get_data(value
))) {
1693 if(purple_strequal(val
, NS_BYTESTREAMS
)) {
1694 jsx
->stream_method
|= STREAM_METHOD_BYTESTREAMS
;
1695 } else if(purple_strequal(val
, NS_IBB
)) {
1696 jsx
->stream_method
|= STREAM_METHOD_IBB
;
1705 if(jsx
->stream_method
== STREAM_METHOD_UNKNOWN
) {
1706 g_object_unref(G_OBJECT(jsx
));
1711 jsx
->stream_id
= g_strdup(stream_id
);
1712 jsx
->iq_id
= g_strdup(id
);
1714 purple_xfer_set_filename(PURPLE_XFER(jsx
), filename
);
1716 purple_xfer_set_size(PURPLE_XFER(jsx
), filesize
);
1719 js
->file_transfers
= g_list_append(js
->file_transfers
, jsx
);
1721 #if ENABLE_FT_THUMBNAILS
1722 /* if there is a thumbnail, we should request it... */
1723 if ((thumbnail
= purple_xmlnode_get_child_with_namespace(file
, "thumbnail",
1725 const char *cid
= purple_xmlnode_get_attrib(thumbnail
, "cid");
1727 jabber_data_request(js
, cid
, purple_xfer_get_remote_user(PURPLE_XFER(jsx
)),
1728 NULL
, TRUE
, jabber_si_thumbnail_cb
, jsx
);
1734 purple_xfer_request(PURPLE_XFER(jsx
));
1737 /******************************************************************************
1738 * GObject Implementation
1739 *****************************************************************************/
1741 jabber_si_xfer_init(JabberSIXfer
*xfer
) {
1742 xfer
->local_streamhost_fd
= -1;
1743 xfer
->ibb_session
= NULL
;
1747 jabber_si_xfer_finalize(GObject
*obj
) {
1748 JabberSIXfer
*jsx
= JABBER_SI_XFER(obj
);
1749 JabberStream
*js
= jsx
->js
;
1751 js
->file_transfers
= g_list_remove(js
->file_transfers
, jsx
);
1753 if (jsx
->connect_data
!= NULL
) {
1754 purple_proxy_connect_cancel(jsx
->connect_data
);
1757 if (jsx
->listen_data
!= NULL
) {
1758 purple_network_listen_cancel(jsx
->listen_data
);
1761 if (jsx
->iq_id
!= NULL
) {
1762 jabber_iq_remove_callback_by_id(js
, jsx
->iq_id
);
1765 if (jsx
->local_streamhost_fd
>= 0) {
1766 close(jsx
->local_streamhost_fd
);
1769 if (purple_xfer_get_xfer_type(PURPLE_XFER(jsx
)) == PURPLE_XFER_TYPE_SEND
&& purple_xfer_get_fd(PURPLE_XFER(jsx
)) >= 0) {
1770 purple_debug_info("jabber", "remove port mapping\n");
1771 purple_network_remove_port_mapping(purple_xfer_get_fd(PURPLE_XFER(jsx
)));
1774 if (jsx
->connect_timeout
> 0) {
1775 g_source_remove(jsx
->connect_timeout
);
1778 if (jsx
->ibb_timeout_handle
> 0) {
1779 g_source_remove(jsx
->ibb_timeout_handle
);
1782 if (jsx
->streamhosts
) {
1783 g_list_free_full(jsx
->streamhosts
, jabber_si_free_streamhost
);
1786 if (jsx
->ibb_session
) {
1787 purple_debug_info("jabber",
1788 "jabber_si_xfer_free: destroying IBB session\n");
1789 jabber_ibb_session_destroy(jsx
->ibb_session
);
1792 if (jsx
->ibb_buffer
) {
1793 g_object_unref(G_OBJECT(jsx
->ibb_buffer
));
1796 purple_debug_info("jabber", "jabber_si_xfer_free(): freeing jsx %p\n", jsx
);
1798 g_free(jsx
->stream_id
);
1800 g_free(jsx
->rxqueue
);
1802 G_OBJECT_CLASS(jabber_si_xfer_parent_class
)->finalize(obj
);
1806 jabber_si_xfer_class_finalize(JabberSIXferClass
*klass
) {
1810 jabber_si_xfer_class_init(JabberSIXferClass
*klass
) {
1811 GObjectClass
*obj_class
= G_OBJECT_CLASS(klass
);
1812 PurpleXferClass
*xfer_class
= PURPLE_XFER_CLASS(klass
);
1814 obj_class
->finalize
= jabber_si_xfer_finalize
;
1816 xfer_class
->init
= jabber_si_xfer_xfer_init
;
1817 xfer_class
->request_denied
= jabber_si_xfer_request_denied
;
1818 xfer_class
->cancel_send
= jabber_si_xfer_cancel_send
;
1819 xfer_class
->cancel_recv
= jabber_si_xfer_cancel_recv
;
1820 xfer_class
->read
= jabber_si_xfer_ibb_read
;
1821 xfer_class
->write
= jabber_si_xfer_ibb_write
;
1824 /******************************************************************************
1826 *****************************************************************************/
1828 jabber_si_xfer_register(GTypeModule
*module
) {
1829 jabber_si_xfer_register_type(module
);
1833 jabber_si_init(void) {
1834 jabber_iq_register_handler("si", "http://jabber.org/protocol/si", jabber_si_parse
);
1836 jabber_ibb_register_open_handler(jabber_si_xfer_ibb_open_cb
);
1840 jabber_si_uninit(void) {
1841 jabber_ibb_unregister_open_handler(jabber_si_xfer_ibb_open_cb
);