rename accountopt.[ch] to purpleaccountoption.[ch]
[pidgin-git.git] / libpurple / protocols / jabber / si.c
blob9b045d0c5c2434f2cf9dd1e2fc0d554a6a716ecc
1 /*
2 * purple - Jabber Protocol Plugin
4 * Purple is the legal property of its developers, whose names are too numerous
5 * to list here. Please refer to the COPYRIGHT file distributed with this
6 * source distribution.
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
25 #include "internal.h"
27 #include "buddylist.h"
28 #include "debug.h"
29 #include "xfer.h"
30 #include "request.h"
31 #include "network.h"
32 #include "notify.h"
34 #include "buddy.h"
35 #include "data.h"
36 #include "disco.h"
37 #include "jabber.h"
38 #include "ibb.h"
39 #include "iq.h"
40 #include "si.h"
42 #define STREAMHOST_CONNECT_TIMEOUT 15
43 #define ENABLE_FT_THUMBNAILS 0
45 struct _JabberSIXfer {
46 PurpleXfer parent;
48 JabberStream *js;
50 PurpleProxyConnectData *connect_data;
51 PurpleNetworkListenData *listen_data;
52 guint connect_timeout;
54 gboolean accepted;
56 char *stream_id;
57 char *iq_id;
59 enum {
60 STREAM_METHOD_UNKNOWN = 0,
61 STREAM_METHOD_BYTESTREAMS = 2 << 1,
62 STREAM_METHOD_IBB = 2 << 2,
63 STREAM_METHOD_UNSUPPORTED = 2 << 30
64 } stream_method;
66 GList *streamhosts;
67 PurpleProxyInfo *gpi;
69 char *rxqueue;
70 size_t rxlen;
71 gsize rxmaxlen;
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);
84 static PurpleXfer*
85 jabber_si_xfer_find(JabberStream *js, const char *sid, const char *from)
87 GList *xfers;
89 if(!sid || !from)
90 return NULL;
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))
97 return xfer;
100 return NULL;
103 static void
104 jabber_si_free_streamhost(gpointer data) {
105 JabberBytestreamsStreamhost *sh = data;
107 if(!data)
108 return;
110 g_free(sh->jid);
111 g_free(sh->host);
112 g_free(sh->zeroconf);
113 g_free(sh);
118 static void jabber_si_bytestreams_attempt_connect(PurpleXfer *xfer);
120 static void
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);
125 JabberIq *iq;
126 PurpleXmlNode *query, *su;
127 JabberBytestreamsStreamhost *streamhost = jsx->streamhosts->data;
129 purple_proxy_info_destroy(jsx->gpi);
130 jsx->gpi = NULL;
131 jsx->connect_data = NULL;
133 if (jsx->connect_timeout > 0)
134 g_source_remove(jsx->connect_timeout);
135 jsx->connect_timeout = 0;
137 if(source < 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);
145 return;
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 */
161 else
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);
171 jabber_iq_send(iq);
173 purple_xfer_start(xfer, source, NULL, -1);
176 static gboolean
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.");
193 return FALSE;
196 static void
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;
205 static gboolean
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);
219 return FALSE;
222 static void jabber_si_bytestreams_attempt_connect(PurpleXfer *xfer)
224 JabberSIXfer *jsx = JABBER_SI_XFER(xfer);
225 JabberBytestreamsStreamhost *streamhost;
226 JabberID *dstjid;
228 if(!jsx->streamhosts) {
229 JabberIq *iq = jabber_iq_new(jsx->js, JABBER_IQ_ERROR);
230 PurpleXmlNode *error, *inf;
232 if(jsx->iq_id)
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);
242 jabber_iq_send(iq);
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);
259 } else {
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
265 already set up... */
266 } else {
267 purple_xfer_cancel_local(xfer);
270 return;
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);
287 jsx->gpi = NULL;
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);
305 else
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,
311 dstaddr, -1);
313 account = purple_connection_get_account(jsx->js->gc);
314 jsx->connect_data = purple_proxy_connect_socks5_account(NULL, account,
315 jsx->gpi, hash, 0,
316 jabber_si_bytestreams_connect_cb, xfer);
317 g_free(hash);
318 g_free(dstaddr);
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)
339 PurpleXfer *xfer;
340 JabberSIXfer *jsx;
341 PurpleXmlNode *streamhost;
342 const char *sid;
344 if(type != JABBER_IQ_SET)
345 return;
347 if(!from)
348 return;
350 if(!(sid = purple_xmlnode_get_attrib(query, "sid")))
351 return;
353 if(!(xfer = jabber_si_xfer_find(js, sid, from)))
354 return;
356 jsx = JABBER_SI_XFER(xfer);
358 if(!jsx->accepted)
359 return;
361 g_free(jsx->iq_id);
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;
367 int portnum = 0;
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);
377 sh->port = portnum;
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);
388 static void
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);
394 int len;
396 len = write(source, jsx->rxqueue + jsx->rxlen, jsx->rxmaxlen - jsx->rxlen);
397 if (len < 0 && errno == EAGAIN)
398 return;
399 else if (len < 0) {
400 g_free(jsx->rxqueue);
401 jsx->rxqueue = NULL;
402 close(source);
403 purple_xfer_cancel_remote(xfer);
404 return;
406 jsx->rxlen += len;
408 if (jsx->rxlen < jsx->rxmaxlen)
409 return;
411 purple_input_remove(purple_xfer_get_watcher(xfer));
412 purple_xfer_set_watcher(xfer, 0);
413 g_free(jsx->rxqueue);
414 jsx->rxqueue = NULL;
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");
423 static void
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*/
430 int len;
431 char *dstaddr, *hash;
432 const char *host;
434 purple_debug_info("jabber", "in jabber_si_xfer_bytestreams_send_read_again_cb\n");
436 if(jsx->rxlen < 5) {
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)
440 return;
441 else if(len <= 0) {
442 close(source);
443 purple_xfer_cancel_remote(xfer);
444 return;
446 jsx->rxqueue = g_realloc(jsx->rxqueue, len + jsx->rxlen);
447 memcpy(jsx->rxqueue + jsx->rxlen, buffer, len);
448 jsx->rxlen += len;
449 return;
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]);
455 close(source);
456 purple_xfer_cancel_remote(xfer);
457 return;
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)
465 return;
466 else if(len <= 0) {
467 close(source);
468 purple_xfer_cancel_remote(xfer);
469 return;
471 jsx->rxqueue = g_realloc(jsx->rxqueue, len + jsx->rxlen);
472 memcpy(jsx->rxqueue + jsx->rxlen, buffer, len);
473 jsx->rxlen += 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)
478 return;
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]);
496 else
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);
500 close(source);
501 purple_xfer_cancel_remote(xfer);
502 g_free(hash);
503 g_free(dstaddr);
504 return;
507 g_free(hash);
508 g_free(dstaddr);
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);
515 jsx->rxlen = 0;
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,
529 PURPLE_INPUT_WRITE);
532 static void
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);
538 int len;
540 len = write(source, jsx->rxqueue + jsx->rxlen, jsx->rxmaxlen - jsx->rxlen);
541 if (len < 0 && errno == EAGAIN)
542 return;
543 else if (len < 0) {
544 g_free(jsx->rxqueue);
545 jsx->rxqueue = NULL;
546 close(source);
547 purple_xfer_cancel_remote(xfer);
548 return;
550 jsx->rxlen += len;
552 if (jsx->rxlen < jsx->rxmaxlen)
553 return;
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);
561 jsx->rxqueue = NULL;
562 jsx->rxlen = 0;
563 } else {
564 close(source);
565 purple_xfer_cancel_remote(xfer);
569 static void
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);
575 int i;
576 int len;
577 char buffer[256];
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 */
584 if(jsx->rxlen < 2) {
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)
588 return;
589 else if(len <= 0) {
590 purple_xfer_cancel_remote(xfer);
591 return;
593 jsx->rxqueue = g_realloc(jsx->rxqueue, len + jsx->rxlen);
594 memcpy(jsx->rxqueue + jsx->rxlen, buffer, len);
595 jsx->rxlen += len;
596 return;
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)
604 return;
605 else if(len <= 0) {
606 purple_xfer_cancel_remote(xfer);
607 return;
609 jsx->rxqueue = g_realloc(jsx->rxqueue, len + jsx->rxlen);
610 memcpy(jsx->rxqueue + jsx->rxlen, buffer, len);
611 jsx->rxlen += len;
614 /* Have we not read all the auth. method bytes? */
615 if(jsx->rxlen -2 < (size_t)jsx->rxqueue[1])
616 return;
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);
622 return;
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);
632 jsx->rxlen = 0;
633 jsx->rxmaxlen = 2;
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,
640 xfer));
641 jabber_si_xfer_bytestreams_send_read_response_cb(xfer,
642 source, PURPLE_INPUT_WRITE);
643 jsx->rxqueue = NULL;
644 jsx->rxlen = 0;
645 return;
649 g_free(jsx->rxqueue);
650 jsx->rxlen = 0;
651 jsx->rxmaxlen = 2;
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);
662 static gint
663 jabber_si_compare_jid(gconstpointer a, gconstpointer b)
665 const JabberBytestreamsStreamhost *sh = a;
667 if(!a)
668 return -1;
670 return strcmp(sh->jid, (char *)b);
673 static void
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);
679 int acceptfd;
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))
685 return;
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.*/
689 return;
692 purple_input_remove(purple_xfer_get_watcher(xfer));
693 close(source);
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));
702 static void
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;
708 JabberSIXfer *jsx;
709 PurpleXmlNode *query, *streamhost_used;
710 const char *jid;
711 GList *matched;
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);
718 return;
721 jsx = JABBER_SI_XFER(xfer);
723 /* In the case of a direct file transfer, this is expected to return */
724 if(!jsx)
725 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",
733 jsx->stream_method);
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);
747 } else {
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
752 is already set up */
753 } else {
754 purple_xfer_cancel_remote(xfer);
756 return;
759 if (!from)
760 return;
762 if(!(query = purple_xmlnode_get_child(packet, "query")))
763 return;
765 if(!(streamhost_used = purple_xmlnode_get_child(query, "streamhost-used")))
766 return;
768 if(!(jid = purple_xmlnode_get_attrib(streamhost_used, "jid")))
769 return;
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);
781 } else {
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);
792 } else {
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...*/
797 } else {
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);
803 g_free(my_jid);
804 return;
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);
825 static void
826 jabber_si_xfer_bytestreams_listen_cb(int sock, gpointer data)
828 PurpleXfer *xfer = data;
829 JabberSIXfer *jsx;
830 JabberIq *iq;
831 PurpleXmlNode *query, *streamhost;
832 char port[6];
833 GList *tmp;
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);
844 return;
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 */
856 if (sock >= 0) {
857 gchar *jid;
858 GList *local_ips =
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) */
873 while (local_ips) {
874 gchar *local_ip = local_ips->data;
875 streamhost_count++;
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;
882 g_free(local_ip);
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")) {
888 streamhost_count++;
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);
895 g_free(jid);
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) {
903 sh = tmp->data;
905 /* TODO: deal with zeroconf proxies */
907 if (!(sh->jid && sh->host && sh->port > 0))
908 continue;
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)
913 continue;
915 streamhost_count++;
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) {
933 jabber_iq_free(iq);
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);
945 } else {
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... */
951 } else {
952 /* We should probably notify the target,
953 but this really shouldn't ever happen */
954 purple_xfer_cancel_local(xfer);
957 return;
960 jabber_iq_set_callback(iq, jabber_si_connect_proxy_cb, xfer);
962 jabber_iq_send(iq);
966 static void
967 jabber_si_xfer_bytestreams_send_init(PurpleXfer *xfer)
969 JabberSIXfer *jsx;
970 PurpleProxyType proxy_type;
972 g_object_ref(xfer);
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;
983 } else
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);
994 static void
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);
1003 static void
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);
1011 } else {
1012 purple_xfer_set_completed(xfer, TRUE);
1013 purple_xfer_end(xfer);
1017 static void
1018 jabber_si_xfer_ibb_recv_data_cb(JabberIBBSession *sess, gpointer data,
1019 gsize size)
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",
1026 size);
1027 purple_circular_buffer_append(jsx->ibb_buffer, data, size);
1028 purple_xfer_protocol_ready(xfer);
1029 } else {
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);
1039 static gssize
1040 jabber_si_xfer_ibb_read(PurpleXfer *xfer, guchar **out_buffer, size_t buf_size)
1042 JabberSIXfer *jsx = JABBER_SI_XFER(xfer);
1043 guchar *buffer;
1044 gsize size;
1045 gsize tmp;
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);
1057 buffer += tmp;
1058 purple_circular_buffer_mark_read(jsx->ibb_buffer, tmp);
1061 return size;
1064 static gboolean
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);
1071 if (xfer) {
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);
1078 if (sess) {
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) */
1091 jsx->ibb_buffer =
1092 purple_circular_buffer_new(jabber_ibb_session_get_block_size(sess));
1094 /* start the transfer */
1095 purple_xfer_start(xfer, -1, NULL, 0);
1096 return TRUE;
1097 } else {
1098 /* failed to create IBB session */
1099 purple_debug_error("jabber", "failed to create IBB session\n");
1100 purple_xfer_cancel_remote(xfer);
1101 return FALSE;
1103 } else {
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");
1107 return FALSE;
1111 static gssize
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;
1116 gsize packet_size;
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);
1127 return packet_size;
1130 static void
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);
1141 } else {
1142 /* send more... */
1143 purple_xfer_protocol_ready(xfer);
1147 static void
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);
1155 } else {
1156 /* error */
1157 purple_xfer_end(xfer);
1161 static void
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);
1180 jsx->ibb_buffer =
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);
1186 } else {
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);
1204 return;
1207 if(!(feature = purple_xmlnode_get_child_with_namespace(si, "feature", "http://jabber.org/protocol/feature-neg"))) {
1208 purple_xfer_cancel_remote(xfer);
1209 return;
1212 if(!(x = purple_xmlnode_get_child_with_namespace(feature, "x", "jabber:x:data"))) {
1213 purple_xfer_cancel_remote(xfer);
1214 return;
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;
1237 g_free(val);
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);
1251 JabberIq *iq;
1252 PurpleXmlNode *si, *file, *feature, *x, *field, *option, *value;
1253 char buf[32];
1254 #if ENABLE_FT_THUMBNAILS
1255 gconstpointer thumb;
1256 gsize thumb_size;
1258 purple_xfer_prepare_thumbnail(xfer, "jpeg,png");
1259 #endif
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);
1289 /* cache data */
1290 jabber_data_associate_local(thumbnail_data, NULL);
1292 #endif
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 */
1314 g_free(jsx->iq_id);
1315 jsx->iq_id = g_strdup(iq->id);
1317 jabber_iq_send(iq);
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) {
1347 JabberIq *iq;
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);
1361 jabber_iq_send(iq);
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);
1393 } else {
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));
1398 g_free(msg);
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);
1412 char *who;
1413 JabberBuddy *jb;
1414 JabberBuddyResource *jbr = NULL;
1416 jb = jabber_buddy_find(jsx->js, who_v[0], FALSE);
1417 if (jb) {
1418 jbr = jabber_buddy_find_resource(jb, resource);
1421 who = g_strdup_printf("%s/%s", who_v[0], resource);
1422 g_strfreev(who_v);
1423 purple_xfer_set_remote_user(xfer, who);
1425 if (jbr && jabber_resource_know_capabilities(jbr)) {
1426 char *msg;
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);
1432 g_free(who);
1433 return;
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));
1440 g_free(msg);
1441 purple_xfer_cancel_local(xfer);
1442 } else {
1443 jabber_disco_info_do(jsx->js, who,
1444 jabber_si_xfer_send_disco_cb, xfer);
1447 g_free(who);
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);
1461 JabberIq *iq;
1462 if(purple_xfer_get_xfer_type(xfer) == PURPLE_XFER_TYPE_SEND) {
1463 JabberBuddy *jb;
1464 JabberBuddyResource *jbr = NULL;
1465 char *resource;
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);
1473 g_free(resource);
1474 return;
1477 jb = jabber_buddy_find(jsx->js, purple_xfer_get_remote_user(xfer), TRUE);
1479 if (jb) {
1480 GList *l;
1482 for (l = jb->resources ; l ; l = g_list_next(l)) {
1483 jbr = l->data;
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);
1494 if (!resources) {
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 */
1498 char *msg;
1500 if(!jb) {
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));
1504 } else {
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));
1511 g_free(msg);
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);
1517 } else {
1518 /* we've got multiple resources, we need to pick one to send to */
1519 GList *l;
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) {
1528 jbr = l->data;
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);
1540 g_free(msg);
1543 g_list_free(resources);
1544 } else {
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));
1549 if(jsx->iq_id)
1550 jabber_iq_set_id(iq, jsx->iq_id);
1551 else
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);
1581 jabber_iq_send(iq);
1585 PurpleXfer *jabber_si_new_xfer(PurpleProtocolXfer *prplxfer, PurpleConnection *gc, const char *who)
1587 JabberStream *js;
1588 JabberSIXfer *jsx;
1590 js = purple_connection_get_protocol_data(gc);
1592 jsx = g_object_new(
1593 JABBER_TYPE_SI_XFER,
1594 "account", purple_connection_get_account(gc),
1595 "type", PURPLE_XFER_TYPE_SEND,
1596 "remote-user", who,
1597 NULL
1600 jsx->js = js;
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)
1608 PurpleXfer *xfer;
1610 xfer = jabber_si_new_xfer(prplxfer, gc, who);
1612 if (file)
1613 purple_xfer_request_accepted(xfer, file);
1614 else
1615 purple_xfer_request(xfer);
1618 #if ENABLE_FT_THUMBNAILS
1619 static void
1620 jabber_si_thumbnail_cb(JabberData *data, gchar *alt, gpointer userdata)
1622 PurpleXfer *xfer = (PurpleXfer *) userdata;
1624 if (data) {
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);
1633 #endif
1635 void jabber_si_parse(JabberStream *js, const char *from, JabberIqType type,
1636 const char *id, PurpleXmlNode *si)
1638 JabberSIXfer *jsx;
1639 PurpleXmlNode *file, *feature, *x, *field, *option, *value;
1640 #if ENABLE_FT_THUMBNAILS
1641 PurpleXmlNode *thumbnail;
1642 #endif
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))
1648 return;
1650 if(!(stream_id = purple_xmlnode_get_attrib(si, "id")))
1651 return;
1653 if(!(file = purple_xmlnode_get_child(si, "file")))
1654 return;
1656 if(!(filename = purple_xmlnode_get_attrib(file, "name")))
1657 return;
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")))
1663 return;
1665 if(!(x = purple_xmlnode_get_child_with_namespace(feature, "x", "jabber:x:data")))
1666 return;
1668 if(!from)
1669 return;
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
1673 * with it */
1674 if(jabber_si_xfer_find(js, stream_id, from) != NULL)
1675 return;
1677 jsx = g_object_new(
1678 JABBER_TYPE_SI_XFER,
1679 "account", purple_connection_get_account(js->gc),
1680 "type", PURPLE_XFER_TYPE_RECEIVE,
1681 "remote-user", from,
1682 NULL
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"))) {
1691 char *val;
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;
1698 g_free(val);
1705 if(jsx->stream_method == STREAM_METHOD_UNKNOWN) {
1706 g_object_unref(G_OBJECT(jsx));
1707 return;
1710 jsx->js = js;
1711 jsx->stream_id = g_strdup(stream_id);
1712 jsx->iq_id = g_strdup(id);
1714 purple_xfer_set_filename(PURPLE_XFER(jsx), filename);
1715 if(filesize > 0) {
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",
1724 NS_THUMBS))) {
1725 const char *cid = purple_xmlnode_get_attrib(thumbnail, "cid");
1726 if (cid) {
1727 jabber_data_request(js, cid, purple_xfer_get_remote_user(PURPLE_XFER(jsx)),
1728 NULL, TRUE, jabber_si_thumbnail_cb, jsx);
1729 return;
1732 #endif
1734 purple_xfer_request(PURPLE_XFER(jsx));
1737 /******************************************************************************
1738 * GObject Implementation
1739 *****************************************************************************/
1740 static void
1741 jabber_si_xfer_init(JabberSIXfer *xfer) {
1742 xfer->local_streamhost_fd = -1;
1743 xfer->ibb_session = NULL;
1746 static void
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);
1799 g_free(jsx->iq_id);
1800 g_free(jsx->rxqueue);
1802 G_OBJECT_CLASS(jabber_si_xfer_parent_class)->finalize(obj);
1805 static void
1806 jabber_si_xfer_class_finalize(JabberSIXferClass *klass) {
1809 static void
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 /******************************************************************************
1825 * Public API
1826 *****************************************************************************/
1827 void
1828 jabber_si_xfer_register(GTypeModule *module) {
1829 jabber_si_xfer_register_type(module);
1832 void
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);
1839 void
1840 jabber_si_uninit(void) {
1841 jabber_ibb_unregister_open_handler(jabber_si_xfer_ibb_open_cb);