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 typedef struct _JabberSIXfer
{
48 PurpleProxyConnectData
*connect_data
;
49 PurpleNetworkListenData
*listen_data
;
50 guint connect_timeout
;
58 STREAM_METHOD_UNKNOWN
= 0,
59 STREAM_METHOD_BYTESTREAMS
= 2 << 1,
60 STREAM_METHOD_IBB
= 2 << 2,
61 STREAM_METHOD_UNSUPPORTED
= 2 << 30
70 int local_streamhost_fd
;
72 JabberIBBSession
*ibb_session
;
73 guint ibb_timeout_handle
;
74 PurpleCircularBuffer
*ibb_buffer
;
77 /* some forward declarations */
78 static void jabber_si_xfer_ibb_send_init(JabberStream
*js
, PurpleXfer
*xfer
);
81 jabber_si_xfer_find(JabberStream
*js
, const char *sid
, const char *from
)
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
))
100 jabber_si_free_streamhost(gpointer data
, gpointer user_data
)
102 JabberBytestreamsStreamhost
*sh
= data
;
109 g_free(sh
->zeroconf
);
115 static void jabber_si_bytestreams_attempt_connect(PurpleXfer
*xfer
);
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
);
123 PurpleXmlNode
*query
, *su
;
124 JabberBytestreamsStreamhost
*streamhost
= jsx
->streamhosts
->data
;
126 purple_proxy_info_destroy(jsx
->gpi
);
128 jsx
->connect_data
= NULL
;
130 if (jsx
->connect_timeout
> 0)
131 purple_timeout_remove(jsx
->connect_timeout
);
132 jsx
->connect_timeout
= 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
);
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 */
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
);
170 purple_xfer_start(xfer
, source
, NULL
, -1);
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.");
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;
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
);
219 static void jabber_si_bytestreams_attempt_connect(PurpleXfer
*xfer
)
221 JabberSIXfer
*jsx
= purple_xfer_get_protocol_data(xfer
);
222 JabberBytestreamsStreamhost
*streamhost
;
225 if(!jsx
->streamhosts
) {
226 JabberIq
*iq
= jabber_iq_new(jsx
->js
, JABBER_IQ_ERROR
);
227 PurpleXmlNode
*error
, *inf
;
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
);
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
);
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
261 purple_xfer_cancel_local(xfer
);
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
);
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
);
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
,
309 jabber_si_bytestreams_connect_cb
, xfer
);
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
)
334 PurpleXmlNode
*streamhost
;
337 if(type
!= JABBER_IQ_SET
)
343 if(!(sid
= purple_xmlnode_get_attrib(query
, "sid")))
346 if(!(xfer
= jabber_si_xfer_find(js
, sid
, from
)))
349 jsx
= purple_xfer_get_protocol_data(xfer
);
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
;
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
);
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
);
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
);
389 len
= write(source
, jsx
->rxqueue
+ jsx
->rxlen
, jsx
->rxmaxlen
- jsx
->rxlen
);
390 if (len
< 0 && errno
== EAGAIN
)
393 g_free(jsx
->rxqueue
);
396 purple_xfer_cancel_remote(xfer
);
401 if (jsx
->rxlen
< jsx
->rxmaxlen
)
404 purple_input_remove(purple_xfer_get_watcher(xfer
));
405 purple_xfer_set_watcher(xfer
, 0);
406 g_free(jsx
->rxqueue
);
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");
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*/
424 char *dstaddr
, *hash
;
427 purple_debug_info("jabber", "in jabber_si_xfer_bytestreams_send_read_again_cb\n");
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
)
436 purple_xfer_cancel_remote(xfer
);
439 jsx
->rxqueue
= g_realloc(jsx
->rxqueue
, len
+ jsx
->rxlen
);
440 memcpy(jsx
->rxqueue
+ jsx
->rxlen
, buffer
, len
);
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]);
449 purple_xfer_cancel_remote(xfer
);
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
)
461 purple_xfer_cancel_remote(xfer
);
464 jsx
->rxqueue
= g_realloc(jsx
->rxqueue
, len
+ jsx
->rxlen
);
465 memcpy(jsx
->rxqueue
+ jsx
->rxlen
, buffer
, 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)
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]);
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);
494 purple_xfer_cancel_remote(xfer
);
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
);
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
,
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
);
533 len
= write(source
, jsx
->rxqueue
+ jsx
->rxlen
, jsx
->rxmaxlen
- jsx
->rxlen
);
534 if (len
< 0 && errno
== EAGAIN
)
537 g_free(jsx
->rxqueue
);
540 purple_xfer_cancel_remote(xfer
);
545 if (jsx
->rxlen
< jsx
->rxmaxlen
)
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
);
558 purple_xfer_cancel_remote(xfer
);
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
);
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 */
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
)
583 purple_xfer_cancel_remote(xfer
);
586 jsx
->rxqueue
= g_realloc(jsx
->rxqueue
, len
+ jsx
->rxlen
);
587 memcpy(jsx
->rxqueue
+ jsx
->rxlen
, buffer
, len
);
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
)
599 purple_xfer_cancel_remote(xfer
);
602 jsx
->rxqueue
= g_realloc(jsx
->rxqueue
, len
+ jsx
->rxlen
);
603 memcpy(jsx
->rxqueue
+ jsx
->rxlen
, buffer
, len
);
607 /* Have we not read all the auth. method bytes? */
608 if(jsx
->rxlen
-2 < (size_t)jsx
->rxqueue
[1])
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
);
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
);
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
,
634 jabber_si_xfer_bytestreams_send_read_response_cb(xfer
,
635 source
, PURPLE_INPUT_WRITE
);
642 g_free(jsx
->rxqueue
);
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
);
656 jabber_si_compare_jid(gconstpointer a
, gconstpointer b
)
658 const JabberBytestreamsStreamhost
*sh
= a
;
663 return strcmp(sh
->jid
, (char *)b
);
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
);
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
))
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.*/
685 purple_input_remove(purple_xfer_get_watcher(xfer
));
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
));
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
;
702 PurpleXmlNode
*query
, *streamhost_used
;
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
);
714 jsx
= purple_xfer_get_protocol_data(xfer
);
716 /* In the case of a direct file transfer, this is expected to 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",
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
);
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
742 purple_xfer_cancel_remote(xfer
);
750 if(!(query
= purple_xmlnode_get_child(packet
, "query")))
753 if(!(streamhost_used
= purple_xmlnode_get_child(query
, "streamhost-used")))
756 if(!(jid
= purple_xmlnode_get_attrib(streamhost_used
, "jid")))
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);
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
);
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...*/
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
);
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
);
811 jabber_si_xfer_bytestreams_listen_cb(int sock
, gpointer data
)
813 PurpleXfer
*xfer
= data
;
816 PurpleXmlNode
*query
, *streamhost
;
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
);
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 */
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) */
859 gchar
*local_ip
= local_ips
->data
;
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
;
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) {
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
);
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
) {
890 /* TODO: deal with zeroconf proxies */
892 if (!(sh
->jid
&& sh
->host
&& sh
->port
> 0))
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
)
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) {
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
);
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... */
934 /* We should probably notify the target,
935 but this really shouldn't ever happen */
936 purple_xfer_cancel_local(xfer
);
942 jabber_iq_set_callback(iq
, jabber_si_connect_proxy_cb
, xfer
);
949 jabber_si_xfer_bytestreams_send_init(PurpleXfer
*xfer
)
952 PurpleProxyType proxy_type
;
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
;
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
);
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
);
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
);
994 purple_xfer_set_completed(xfer
, TRUE
);
995 purple_xfer_end(xfer
);
1000 jabber_si_xfer_ibb_recv_data_cb(JabberIBBSession
*sess
, gpointer data
,
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",
1009 purple_circular_buffer_append(jsx
->ibb_buffer
, data
, size
);
1010 purple_xfer_protocol_ready(xfer
);
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
);
1022 jabber_si_xfer_ibb_read(guchar
**out_buffer
, size_t buf_size
, PurpleXfer
*xfer
)
1024 JabberSIXfer
*jsx
= purple_xfer_get_protocol_data(xfer
);
1026 gsize size
= purple_circular_buffer_get_used(jsx
->ibb_buffer
);
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
);
1034 purple_circular_buffer_mark_read(jsx
->ibb_buffer
, tmp
);
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
);
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
);
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) */
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);
1076 /* failed to create IBB session */
1077 purple_debug_error("jabber", "failed to create IBB session\n");
1078 purple_xfer_cancel_remote(xfer
);
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");
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
);
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
);
1115 purple_xfer_protocol_ready(xfer
);
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
);
1129 purple_xfer_end(xfer
);
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
);
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
);
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
);
1181 if(!(feature
= purple_xmlnode_get_child_with_namespace(si
, "feature", "http://jabber.org/protocol/feature-neg"))) {
1182 purple_xfer_cancel_remote(xfer
);
1186 if(!(x
= purple_xmlnode_get_child_with_namespace(feature
, "x", "jabber:x:data"))) {
1187 purple_xfer_cancel_remote(xfer
);
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
;
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
);
1226 PurpleXmlNode
*si
, *file
, *feature
, *x
, *field
, *option
, *value
;
1228 #if ENABLE_FT_THUMBNAILS
1229 gconstpointer thumb
;
1232 purple_xfer_prepare_thumbnail(xfer
, "jpeg,png");
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
);
1264 jabber_data_associate_local(thumbnail_data
, NULL
);
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 */
1289 jsx
->iq_id
= g_strdup(iq
->id
);
1294 static void jabber_si_xfer_free(PurpleXfer
*xfer
)
1296 JabberSIXfer
*jsx
= purple_xfer_get_protocol_data(xfer
);
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
);
1339 /* XXX: free other stuff */
1340 g_free(jsx
->rxqueue
);
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
) {
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);
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
);
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
));
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);
1449 JabberBuddyResource
*jbr
= NULL
;
1451 jb
= jabber_buddy_find(jsx
->js
, who_v
[0], FALSE
);
1453 jbr
= jabber_buddy_find_resource(jb
, resource
);
1456 who
= g_strdup_printf("%s/%s", who_v
[0], resource
);
1458 purple_xfer_set_remote_user(xfer
, who
);
1460 if (jbr
&& jabber_resource_know_capabilities(jbr
)) {
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
);
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
));
1476 purple_xfer_cancel_local(xfer
);
1478 jabber_disco_info_do(jsx
->js
, who
,
1479 jabber_si_xfer_send_disco_cb
, xfer
);
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
);
1497 if(purple_xfer_get_xfer_type(xfer
) == PURPLE_XFER_TYPE_SEND
) {
1499 JabberBuddyResource
*jbr
= NULL
;
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
);
1512 jb
= jabber_buddy_find(jsx
->js
, purple_xfer_get_remote_user(xfer
), TRUE
);
1517 for (l
= jb
->resources
; l
; l
= g_list_next(l
)) {
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
);
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 */
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
));
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
));
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
);
1553 /* we've got multiple resources, we need to pick one to send to */
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
) {
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
);
1578 g_list_free(resources
);
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
));
1585 jabber_iq_set_id(iq
, jsx
->iq_id
);
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);
1620 PurpleXfer
*jabber_si_new_xfer(PurpleConnection
*gc
, const char *who
)
1627 js
= purple_connection_get_protocol_data(gc
);
1629 xfer
= purple_xfer_new(purple_connection_get_account(gc
), PURPLE_XFER_TYPE_SEND
, who
);
1632 jsx
= g_new0(JabberSIXfer
, 1);
1633 purple_xfer_set_protocol_data(xfer
, jsx
);
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
);
1649 void jabber_si_xfer_send(PurpleConnection
*gc
, const char *who
, const char *file
)
1653 xfer
= jabber_si_new_xfer(gc
, who
);
1656 purple_xfer_request_accepted(xfer
, file
);
1658 purple_xfer_request(xfer
);
1661 #if ENABLE_FT_THUMBNAILS
1663 jabber_si_thumbnail_cb(JabberData
*data
, gchar
*alt
, gpointer userdata
)
1665 PurpleXfer
*xfer
= (PurpleXfer
*) userdata
;
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
);
1678 void jabber_si_parse(JabberStream
*js
, const char *from
, JabberIqType type
,
1679 const char *id
, PurpleXmlNode
*si
)
1683 PurpleXmlNode
*file
, *feature
, *x
, *field
, *option
, *value
;
1684 #if ENABLE_FT_THUMBNAILS
1685 PurpleXmlNode
*thumbnail
;
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
))
1694 if(!(stream_id
= purple_xmlnode_get_attrib(si
, "id")))
1697 if(!(file
= purple_xmlnode_get_child(si
, "file")))
1700 if(!(filename
= purple_xmlnode_get_attrib(file
, "name")))
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")))
1709 if(!(x
= purple_xmlnode_get_child_with_namespace(feature
, "x", "jabber:x:data")))
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
1718 if(jabber_si_xfer_find(js
, stream_id
, from
) != NULL
)
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"))) {
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
;
1746 if(jsx
->stream_method
== STREAM_METHOD_UNKNOWN
) {
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
);
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",
1775 const char *cid
= purple_xmlnode_get_attrib(thumbnail
, "cid");
1777 jabber_data_request(js
, cid
, purple_xfer_get_remote_user(xfer
),
1778 NULL
, TRUE
, jabber_si_thumbnail_cb
, xfer
);
1784 purple_xfer_request(xfer
);
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
);
1796 jabber_si_uninit(void)
1798 jabber_ibb_unregister_open_handler(jabber_si_xfer_ibb_open_cb
);