Mercurial > pidgin
comparison libpurple/protocols/jabber/si.c @ 15373:5fe8042783c1
Rename gtk/ and libgaim/ to pidgin/ and libpurple/
author | Sean Egan <seanegan@gmail.com> |
---|---|
date | Sat, 20 Jan 2007 02:32:10 +0000 |
parents | |
children | e926951e61fe |
comparison
equal
deleted
inserted
replaced
15372:f79e0f4df793 | 15373:5fe8042783c1 |
---|---|
1 /* | |
2 * gaim - Jabber Protocol Plugin | |
3 * | |
4 * Copyright (C) 2003, Nathan Walp <faceprint@faceprint.com> | |
5 * | |
6 * This program is free software; you can redistribute it and/or modify | |
7 * it under the terms of the GNU General Public License as published by | |
8 * the Free Software Foundation; either version 2 of the License, or | |
9 * (at your option) any later version. | |
10 * | |
11 * This program is distributed in the hope that it will be useful, | |
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 * | |
15 * GNU General Public License for more details. | |
16 * | |
17 * You should have received a copy of the GNU General Public License | |
18 * along with this program; if not, write to the Free Software | |
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
20 * | |
21 */ | |
22 | |
23 #include "blist.h" | |
24 | |
25 #include "internal.h" | |
26 #include "cipher.h" | |
27 #include "debug.h" | |
28 #include "ft.h" | |
29 #include "network.h" | |
30 #include "notify.h" | |
31 | |
32 #include "buddy.h" | |
33 #include "disco.h" | |
34 #include "jabber.h" | |
35 #include "iq.h" | |
36 #include "si.h" | |
37 | |
38 #include "si.h" | |
39 | |
40 struct bytestreams_streamhost { | |
41 char *jid; | |
42 char *host; | |
43 int port; | |
44 }; | |
45 | |
46 typedef struct _JabberSIXfer { | |
47 JabberStream *js; | |
48 | |
49 GaimProxyConnectData *connect_data; | |
50 GaimNetworkListenData *listen_data; | |
51 | |
52 gboolean accepted; | |
53 | |
54 char *stream_id; | |
55 char *iq_id; | |
56 | |
57 enum { | |
58 STREAM_METHOD_UNKNOWN = 0, | |
59 STREAM_METHOD_BYTESTREAMS = 2 << 1, | |
60 STREAM_METHOD_IBB = 2 << 2, | |
61 STREAM_METHOD_UNSUPPORTED = 2 << 31 | |
62 } stream_method; | |
63 | |
64 GList *streamhosts; | |
65 GaimProxyInfo *gpi; | |
66 | |
67 char *rxqueue; | |
68 size_t rxlen; | |
69 gsize rxmaxlen; | |
70 } JabberSIXfer; | |
71 | |
72 static GaimXfer* | |
73 jabber_si_xfer_find(JabberStream *js, const char *sid, const char *from) | |
74 { | |
75 GList *xfers; | |
76 | |
77 if(!sid || !from) | |
78 return NULL; | |
79 | |
80 for(xfers = js->file_transfers; xfers; xfers = xfers->next) { | |
81 GaimXfer *xfer = xfers->data; | |
82 JabberSIXfer *jsx = xfer->data; | |
83 if(jsx->stream_id && xfer->who && | |
84 !strcmp(jsx->stream_id, sid) && !strcmp(xfer->who, from)) | |
85 return xfer; | |
86 } | |
87 | |
88 return NULL; | |
89 } | |
90 | |
91 | |
92 static void jabber_si_bytestreams_attempt_connect(GaimXfer *xfer); | |
93 | |
94 static void | |
95 jabber_si_bytestreams_connect_cb(gpointer data, gint source, const gchar *error_message) | |
96 { | |
97 GaimXfer *xfer = data; | |
98 JabberSIXfer *jsx = xfer->data; | |
99 JabberIq *iq; | |
100 xmlnode *query, *su; | |
101 struct bytestreams_streamhost *streamhost = jsx->streamhosts->data; | |
102 | |
103 gaim_proxy_info_destroy(jsx->gpi); | |
104 jsx->connect_data = NULL; | |
105 | |
106 if(source < 0) { | |
107 jsx->streamhosts = g_list_remove(jsx->streamhosts, streamhost); | |
108 g_free(streamhost->jid); | |
109 g_free(streamhost->host); | |
110 g_free(streamhost); | |
111 jabber_si_bytestreams_attempt_connect(xfer); | |
112 return; | |
113 } | |
114 | |
115 iq = jabber_iq_new_query(jsx->js, JABBER_IQ_RESULT, "http://jabber.org/protocol/bytestreams"); | |
116 xmlnode_set_attrib(iq->node, "to", xfer->who); | |
117 jabber_iq_set_id(iq, jsx->iq_id); | |
118 query = xmlnode_get_child(iq->node, "query"); | |
119 su = xmlnode_new_child(query, "streamhost-used"); | |
120 xmlnode_set_attrib(su, "jid", streamhost->jid); | |
121 | |
122 jabber_iq_send(iq); | |
123 | |
124 gaim_xfer_start(xfer, source, NULL, -1); | |
125 } | |
126 | |
127 static void jabber_si_bytestreams_attempt_connect(GaimXfer *xfer) | |
128 { | |
129 JabberSIXfer *jsx = xfer->data; | |
130 struct bytestreams_streamhost *streamhost; | |
131 char *dstaddr, *p; | |
132 int i; | |
133 unsigned char hashval[20]; | |
134 JabberID *dstjid; | |
135 | |
136 if(!jsx->streamhosts) { | |
137 JabberIq *iq = jabber_iq_new(jsx->js, JABBER_IQ_ERROR); | |
138 xmlnode *error, *inf; | |
139 | |
140 if(jsx->iq_id) | |
141 jabber_iq_set_id(iq, jsx->iq_id); | |
142 | |
143 xmlnode_set_attrib(iq->node, "to", xfer->who); | |
144 error = xmlnode_new_child(iq->node, "error"); | |
145 xmlnode_set_attrib(error, "code", "404"); | |
146 xmlnode_set_attrib(error, "type", "cancel"); | |
147 inf = xmlnode_new_child(error, "item-not-found"); | |
148 xmlnode_set_namespace(inf, "urn:ietf:params:xml:ns:xmpp-stanzas"); | |
149 | |
150 jabber_iq_send(iq); | |
151 | |
152 gaim_xfer_cancel_local(xfer); | |
153 | |
154 return; | |
155 } | |
156 | |
157 streamhost = jsx->streamhosts->data; | |
158 | |
159 dstjid = jabber_id_new(xfer->who); | |
160 | |
161 if(dstjid != NULL) { | |
162 jsx->gpi = gaim_proxy_info_new(); | |
163 gaim_proxy_info_set_type(jsx->gpi, GAIM_PROXY_SOCKS5); | |
164 gaim_proxy_info_set_host(jsx->gpi, streamhost->host); | |
165 gaim_proxy_info_set_port(jsx->gpi, streamhost->port); | |
166 | |
167 | |
168 | |
169 dstaddr = g_strdup_printf("%s%s@%s/%s%s@%s/%s", jsx->stream_id, dstjid->node, dstjid->domain, dstjid->resource, jsx->js->user->node, | |
170 jsx->js->user->domain, jsx->js->user->resource); | |
171 | |
172 gaim_cipher_digest_region("sha1", (guchar *)dstaddr, strlen(dstaddr), | |
173 sizeof(hashval), hashval, NULL); | |
174 g_free(dstaddr); | |
175 dstaddr = g_malloc(41); | |
176 p = dstaddr; | |
177 for(i=0; i<20; i++, p+=2) | |
178 snprintf(p, 3, "%02x", hashval[i]); | |
179 | |
180 jsx->connect_data = gaim_proxy_connect_socks5(NULL, jsx->gpi, | |
181 dstaddr, 0, | |
182 jabber_si_bytestreams_connect_cb, xfer); | |
183 g_free(dstaddr); | |
184 | |
185 jabber_id_free(dstjid); | |
186 } | |
187 | |
188 if (jsx->connect_data == NULL) | |
189 { | |
190 jsx->streamhosts = g_list_remove(jsx->streamhosts, streamhost); | |
191 g_free(streamhost->jid); | |
192 g_free(streamhost->host); | |
193 g_free(streamhost); | |
194 jabber_si_bytestreams_attempt_connect(xfer); | |
195 } | |
196 } | |
197 | |
198 void jabber_bytestreams_parse(JabberStream *js, xmlnode *packet) | |
199 { | |
200 GaimXfer *xfer; | |
201 JabberSIXfer *jsx; | |
202 xmlnode *query, *streamhost; | |
203 const char *sid, *from, *type; | |
204 | |
205 if(!(type = xmlnode_get_attrib(packet, "type")) || strcmp(type, "set")) | |
206 return; | |
207 | |
208 if(!(from = xmlnode_get_attrib(packet, "from"))) | |
209 return; | |
210 | |
211 if(!(query = xmlnode_get_child(packet, "query"))) | |
212 return; | |
213 | |
214 if(!(sid = xmlnode_get_attrib(query, "sid"))) | |
215 return; | |
216 | |
217 if(!(xfer = jabber_si_xfer_find(js, sid, from))) | |
218 return; | |
219 | |
220 jsx = xfer->data; | |
221 | |
222 if(!jsx->accepted) | |
223 return; | |
224 | |
225 if(jsx->iq_id) | |
226 g_free(jsx->iq_id); | |
227 jsx->iq_id = g_strdup(xmlnode_get_attrib(packet, "id")); | |
228 | |
229 for(streamhost = xmlnode_get_child(query, "streamhost"); streamhost; | |
230 streamhost = xmlnode_get_next_twin(streamhost)) { | |
231 const char *jid, *host, *port; | |
232 int portnum; | |
233 | |
234 if((jid = xmlnode_get_attrib(streamhost, "jid")) && | |
235 (host = xmlnode_get_attrib(streamhost, "host")) && | |
236 (port = xmlnode_get_attrib(streamhost, "port")) && | |
237 (portnum = atoi(port))) { | |
238 struct bytestreams_streamhost *sh = g_new0(struct bytestreams_streamhost, 1); | |
239 sh->jid = g_strdup(jid); | |
240 sh->host = g_strdup(host); | |
241 sh->port = portnum; | |
242 jsx->streamhosts = g_list_append(jsx->streamhosts, sh); | |
243 } | |
244 } | |
245 | |
246 jabber_si_bytestreams_attempt_connect(xfer); | |
247 } | |
248 | |
249 | |
250 static void | |
251 jabber_si_xfer_bytestreams_send_read_again_resp_cb(gpointer data, gint source, | |
252 GaimInputCondition cond) | |
253 { | |
254 GaimXfer *xfer = data; | |
255 JabberSIXfer *jsx = xfer->data; | |
256 int len; | |
257 | |
258 len = write(source, jsx->rxqueue + jsx->rxlen, jsx->rxmaxlen - jsx->rxlen); | |
259 if (len < 0 && errno == EAGAIN) | |
260 return; | |
261 else if (len < 0) { | |
262 gaim_input_remove(xfer->watcher); | |
263 xfer->watcher = 0; | |
264 g_free(jsx->rxqueue); | |
265 jsx->rxqueue = NULL; | |
266 close(source); | |
267 gaim_xfer_cancel_remote(xfer); | |
268 return; | |
269 } | |
270 jsx->rxlen += len; | |
271 | |
272 if (jsx->rxlen < jsx->rxmaxlen) | |
273 return; | |
274 | |
275 gaim_input_remove(xfer->watcher); | |
276 xfer->watcher = 0; | |
277 g_free(jsx->rxqueue); | |
278 jsx->rxqueue = NULL; | |
279 | |
280 gaim_xfer_start(xfer, source, NULL, -1); | |
281 } | |
282 | |
283 static void | |
284 jabber_si_xfer_bytestreams_send_read_again_cb(gpointer data, gint source, | |
285 GaimInputCondition cond) | |
286 { | |
287 GaimXfer *xfer = data; | |
288 JabberSIXfer *jsx = xfer->data; | |
289 int i; | |
290 char buffer[256]; | |
291 int len; | |
292 char *dstaddr, *p; | |
293 unsigned char hashval[20]; | |
294 const char *host; | |
295 | |
296 gaim_debug_info("jabber", "in jabber_si_xfer_bytestreams_send_read_again_cb\n"); | |
297 | |
298 if(jsx->rxlen < 5) { | |
299 gaim_debug_info("jabber", "reading the first 5 bytes\n"); | |
300 len = read(source, buffer, 5 - jsx->rxlen); | |
301 if(len < 0 && errno == EAGAIN) | |
302 return; | |
303 else if(len <= 0) { | |
304 gaim_input_remove(xfer->watcher); | |
305 xfer->watcher = 0; | |
306 close(source); | |
307 gaim_xfer_cancel_remote(xfer); | |
308 return; | |
309 } | |
310 jsx->rxqueue = g_realloc(jsx->rxqueue, len + jsx->rxlen); | |
311 memcpy(jsx->rxqueue + jsx->rxlen, buffer, len); | |
312 jsx->rxlen += len; | |
313 return; | |
314 } else if(jsx->rxqueue[0] != 0x05 || jsx->rxqueue[1] != 0x01 || | |
315 jsx->rxqueue[3] != 0x03) { | |
316 gaim_debug_info("jabber", "invalid socks5 stuff\n"); | |
317 gaim_input_remove(xfer->watcher); | |
318 xfer->watcher = 0; | |
319 close(source); | |
320 gaim_xfer_cancel_remote(xfer); | |
321 return; | |
322 } else if(jsx->rxlen - 5 < jsx->rxqueue[4] + 2) { | |
323 gaim_debug_info("jabber", "reading umpteen more bytes\n"); | |
324 len = read(source, buffer, jsx->rxqueue[4] + 5 + 2 - jsx->rxlen); | |
325 if(len < 0 && errno == EAGAIN) | |
326 return; | |
327 else if(len <= 0) { | |
328 gaim_input_remove(xfer->watcher); | |
329 xfer->watcher = 0; | |
330 close(source); | |
331 gaim_xfer_cancel_remote(xfer); | |
332 return; | |
333 } | |
334 jsx->rxqueue = g_realloc(jsx->rxqueue, len + jsx->rxlen); | |
335 memcpy(jsx->rxqueue + jsx->rxlen, buffer, len); | |
336 jsx->rxlen += len; | |
337 } | |
338 | |
339 if(jsx->rxlen - 5 < jsx->rxqueue[4] + 2) | |
340 return; | |
341 | |
342 gaim_input_remove(xfer->watcher); | |
343 xfer->watcher = 0; | |
344 | |
345 dstaddr = g_strdup_printf("%s%s@%s/%s%s", jsx->stream_id, | |
346 jsx->js->user->node, jsx->js->user->domain, | |
347 jsx->js->user->resource, xfer->who); | |
348 | |
349 gaim_cipher_digest_region("sha1", (guchar *)dstaddr, strlen(dstaddr), | |
350 sizeof(hashval), hashval, NULL); | |
351 g_free(dstaddr); | |
352 dstaddr = g_malloc(41); | |
353 p = dstaddr; | |
354 for(i=0; i<20; i++, p+=2) | |
355 snprintf(p, 3, "%02x", hashval[i]); | |
356 | |
357 if(jsx->rxqueue[4] != 40 || strncmp(dstaddr, jsx->rxqueue+5, 40) || | |
358 jsx->rxqueue[45] != 0x00 || jsx->rxqueue[46] != 0x00) { | |
359 gaim_debug_error("jabber", "someone connected with the wrong info!\n"); | |
360 close(source); | |
361 gaim_xfer_cancel_remote(xfer); | |
362 return; | |
363 } | |
364 | |
365 g_free(jsx->rxqueue); | |
366 host = gaim_network_get_my_ip(jsx->js->fd); | |
367 | |
368 jsx->rxmaxlen = 5 + strlen(host) + 2; | |
369 jsx->rxqueue = g_malloc(jsx->rxmaxlen); | |
370 jsx->rxlen = 0; | |
371 | |
372 jsx->rxqueue[0] = 0x05; | |
373 jsx->rxqueue[1] = 0x00; | |
374 jsx->rxqueue[2] = 0x00; | |
375 jsx->rxqueue[3] = 0x03; | |
376 jsx->rxqueue[4] = strlen(host); | |
377 memcpy(jsx->rxqueue + 5, host, strlen(host)); | |
378 jsx->rxqueue[5+strlen(host)] = 0x00; | |
379 jsx->rxqueue[6+strlen(host)] = 0x00; | |
380 | |
381 xfer->watcher = gaim_input_add(source, GAIM_INPUT_WRITE, | |
382 jabber_si_xfer_bytestreams_send_read_again_resp_cb, xfer); | |
383 jabber_si_xfer_bytestreams_send_read_again_resp_cb(xfer, source, | |
384 GAIM_INPUT_WRITE); | |
385 } | |
386 | |
387 static void | |
388 jabber_si_xfer_bytestreams_send_read_response_cb(gpointer data, gint source, | |
389 GaimInputCondition cond) | |
390 { | |
391 GaimXfer *xfer = data; | |
392 JabberSIXfer *jsx = xfer->data; | |
393 int len; | |
394 | |
395 len = write(source, jsx->rxqueue + jsx->rxlen, jsx->rxmaxlen - jsx->rxlen); | |
396 if (len < 0 && errno == EAGAIN) | |
397 return; | |
398 else if (len < 0) { | |
399 gaim_input_remove(xfer->watcher); | |
400 xfer->watcher = 0; | |
401 g_free(jsx->rxqueue); | |
402 jsx->rxqueue = NULL; | |
403 close(source); | |
404 gaim_xfer_cancel_remote(xfer); | |
405 return; | |
406 } | |
407 jsx->rxlen += len; | |
408 | |
409 if (jsx->rxlen < jsx->rxmaxlen) | |
410 return; | |
411 | |
412 gaim_input_remove(xfer->watcher); | |
413 xfer->watcher = 0; | |
414 | |
415 if (jsx->rxqueue[1] == 0x00) { | |
416 xfer->watcher = gaim_input_add(source, GAIM_INPUT_READ, | |
417 jabber_si_xfer_bytestreams_send_read_again_cb, xfer); | |
418 g_free(jsx->rxqueue); | |
419 jsx->rxqueue = NULL; | |
420 } else { | |
421 close(source); | |
422 gaim_xfer_cancel_remote(xfer); | |
423 } | |
424 } | |
425 | |
426 static void | |
427 jabber_si_xfer_bytestreams_send_read_cb(gpointer data, gint source, | |
428 GaimInputCondition cond) | |
429 { | |
430 GaimXfer *xfer = data; | |
431 JabberSIXfer *jsx = xfer->data; | |
432 int i; | |
433 int len; | |
434 char buffer[256]; | |
435 | |
436 gaim_debug_info("jabber", "in jabber_si_xfer_bytestreams_send_read_cb\n"); | |
437 | |
438 xfer->fd = source; | |
439 | |
440 if(jsx->rxlen < 2) { | |
441 gaim_debug_info("jabber", "reading those first two bytes\n"); | |
442 len = read(source, buffer, 2 - jsx->rxlen); | |
443 if(len < 0 && errno == EAGAIN) | |
444 return; | |
445 else if(len <= 0) { | |
446 gaim_input_remove(xfer->watcher); | |
447 xfer->watcher = 0; | |
448 close(source); | |
449 gaim_xfer_cancel_remote(xfer); | |
450 return; | |
451 } | |
452 jsx->rxqueue = g_realloc(jsx->rxqueue, len + jsx->rxlen); | |
453 memcpy(jsx->rxqueue + jsx->rxlen, buffer, len); | |
454 jsx->rxlen += len; | |
455 return; | |
456 } else if(jsx->rxlen - 2 < jsx->rxqueue[1]) { | |
457 gaim_debug_info("jabber", "reading the next umpteen bytes\n"); | |
458 len = read(source, buffer, jsx->rxqueue[1] + 2 - jsx->rxlen); | |
459 if(len < 0 && errno == EAGAIN) | |
460 return; | |
461 else if(len <= 0) { | |
462 gaim_input_remove(xfer->watcher); | |
463 xfer->watcher = 0; | |
464 close(source); | |
465 gaim_xfer_cancel_remote(xfer); | |
466 return; | |
467 } | |
468 jsx->rxqueue = g_realloc(jsx->rxqueue, len + jsx->rxlen); | |
469 memcpy(jsx->rxqueue + jsx->rxlen, buffer, len); | |
470 jsx->rxlen += len; | |
471 } | |
472 | |
473 if(jsx->rxlen -2 < jsx->rxqueue[1]) | |
474 return; | |
475 | |
476 gaim_input_remove(xfer->watcher); | |
477 xfer->watcher = 0; | |
478 | |
479 gaim_debug_info("jabber", "checking to make sure we're socks FIVE\n"); | |
480 | |
481 if(jsx->rxqueue[0] != 0x05) { | |
482 close(source); | |
483 gaim_xfer_cancel_remote(xfer); | |
484 return; | |
485 } | |
486 | |
487 gaim_debug_info("jabber", "going to test %hhu different methods\n", jsx->rxqueue[1]); | |
488 | |
489 for(i=0; i<jsx->rxqueue[1]; i++) { | |
490 | |
491 gaim_debug_info("jabber", "testing %hhu\n", jsx->rxqueue[i+2]); | |
492 if(jsx->rxqueue[i+2] == 0x00) { | |
493 g_free(jsx->rxqueue); | |
494 jsx->rxlen = 0; | |
495 jsx->rxmaxlen = 2; | |
496 jsx->rxqueue = g_malloc(jsx->rxmaxlen); | |
497 jsx->rxqueue[0] = 0x05; | |
498 jsx->rxqueue[1] = 0x00; | |
499 xfer->watcher = gaim_input_add(source, GAIM_INPUT_WRITE, | |
500 jabber_si_xfer_bytestreams_send_read_response_cb, | |
501 xfer); | |
502 jabber_si_xfer_bytestreams_send_read_response_cb(xfer, | |
503 source, GAIM_INPUT_WRITE); | |
504 jsx->rxqueue = NULL; | |
505 jsx->rxlen = 0; | |
506 return; | |
507 } | |
508 } | |
509 | |
510 g_free(jsx->rxqueue); | |
511 jsx->rxlen = 0; | |
512 jsx->rxmaxlen = 2; | |
513 jsx->rxqueue = g_malloc(jsx->rxmaxlen); | |
514 jsx->rxqueue[0] = 0x05; | |
515 jsx->rxqueue[1] = 0xFF; | |
516 xfer->watcher = gaim_input_add(source, GAIM_INPUT_WRITE, | |
517 jabber_si_xfer_bytestreams_send_read_response_cb, xfer); | |
518 jabber_si_xfer_bytestreams_send_read_response_cb(xfer, | |
519 source, GAIM_INPUT_WRITE); | |
520 } | |
521 | |
522 static void | |
523 jabber_si_xfer_bytestreams_send_connected_cb(gpointer data, gint source, | |
524 GaimInputCondition cond) | |
525 { | |
526 GaimXfer *xfer = data; | |
527 int acceptfd; | |
528 | |
529 gaim_debug_info("jabber", "in jabber_si_xfer_bytestreams_send_connected_cb\n"); | |
530 | |
531 acceptfd = accept(source, NULL, 0); | |
532 if(acceptfd == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) | |
533 return; | |
534 else if(acceptfd == -1) { | |
535 gaim_debug_warning("jabber", "accept: %s\n", strerror(errno)); | |
536 return; | |
537 } | |
538 | |
539 gaim_input_remove(xfer->watcher); | |
540 close(source); | |
541 | |
542 xfer->watcher = gaim_input_add(acceptfd, GAIM_INPUT_READ, | |
543 jabber_si_xfer_bytestreams_send_read_cb, xfer); | |
544 } | |
545 | |
546 static void | |
547 jabber_si_xfer_bytestreams_listen_cb(int sock, gpointer data) | |
548 { | |
549 GaimXfer *xfer = data; | |
550 JabberSIXfer *jsx; | |
551 JabberIq *iq; | |
552 xmlnode *query, *streamhost; | |
553 char *jid, *port; | |
554 | |
555 jsx = xfer->data; | |
556 jsx->listen_data = NULL; | |
557 | |
558 if (gaim_xfer_get_status(xfer) == GAIM_XFER_STATUS_CANCEL_LOCAL) { | |
559 gaim_xfer_unref(xfer); | |
560 return; | |
561 } | |
562 | |
563 gaim_xfer_unref(xfer); | |
564 | |
565 if (sock < 0) { | |
566 gaim_xfer_cancel_local(xfer); | |
567 return; | |
568 } | |
569 | |
570 iq = jabber_iq_new_query(jsx->js, JABBER_IQ_SET, | |
571 "http://jabber.org/protocol/bytestreams"); | |
572 xmlnode_set_attrib(iq->node, "to", xfer->who); | |
573 query = xmlnode_get_child(iq->node, "query"); | |
574 | |
575 xmlnode_set_attrib(query, "sid", jsx->stream_id); | |
576 | |
577 streamhost = xmlnode_new_child(query, "streamhost"); | |
578 jid = g_strdup_printf("%s@%s/%s", jsx->js->user->node, | |
579 jsx->js->user->domain, jsx->js->user->resource); | |
580 xmlnode_set_attrib(streamhost, "jid", jid); | |
581 g_free(jid); | |
582 | |
583 /* XXX: shouldn't we use the public IP or something? here */ | |
584 xmlnode_set_attrib(streamhost, "host", | |
585 gaim_network_get_my_ip(jsx->js->fd)); | |
586 xfer->local_port = gaim_network_get_port_from_fd(sock); | |
587 port = g_strdup_printf("%hu", xfer->local_port); | |
588 xmlnode_set_attrib(streamhost, "port", port); | |
589 g_free(port); | |
590 | |
591 xfer->watcher = gaim_input_add(sock, GAIM_INPUT_READ, | |
592 jabber_si_xfer_bytestreams_send_connected_cb, xfer); | |
593 | |
594 /* XXX: insert proxies here */ | |
595 | |
596 /* XXX: callback to find out which streamhost they used, or see if they | |
597 * screwed it up */ | |
598 jabber_iq_send(iq); | |
599 | |
600 } | |
601 | |
602 static void | |
603 jabber_si_xfer_bytestreams_send_init(GaimXfer *xfer) | |
604 { | |
605 JabberSIXfer *jsx; | |
606 | |
607 gaim_xfer_ref(xfer); | |
608 | |
609 jsx = xfer->data; | |
610 jsx->listen_data = gaim_network_listen_range(0, 0, SOCK_STREAM, | |
611 jabber_si_xfer_bytestreams_listen_cb, xfer); | |
612 if (jsx->listen_data == NULL) { | |
613 gaim_xfer_unref(xfer); | |
614 /* XXX: couldn't open a port, we're fscked */ | |
615 gaim_xfer_cancel_local(xfer); | |
616 return; | |
617 } | |
618 | |
619 } | |
620 | |
621 static void jabber_si_xfer_send_method_cb(JabberStream *js, xmlnode *packet, | |
622 gpointer data) | |
623 { | |
624 GaimXfer *xfer = data; | |
625 xmlnode *si, *feature, *x, *field, *value; | |
626 | |
627 if(!(si = xmlnode_get_child_with_namespace(packet, "si", "http://jabber.org/protocol/si"))) { | |
628 gaim_xfer_cancel_remote(xfer); | |
629 return; | |
630 } | |
631 | |
632 if(!(feature = xmlnode_get_child_with_namespace(si, "feature", "http://jabber.org/protocol/feature-neg"))) { | |
633 gaim_xfer_cancel_remote(xfer); | |
634 return; | |
635 } | |
636 | |
637 if(!(x = xmlnode_get_child_with_namespace(feature, "x", "jabber:x:data"))) { | |
638 gaim_xfer_cancel_remote(xfer); | |
639 return; | |
640 } | |
641 | |
642 for(field = xmlnode_get_child(x, "field"); field; field = xmlnode_get_next_twin(field)) { | |
643 const char *var = xmlnode_get_attrib(field, "var"); | |
644 | |
645 if(var && !strcmp(var, "stream-method")) { | |
646 if((value = xmlnode_get_child(field, "value"))) { | |
647 char *val = xmlnode_get_data(value); | |
648 if(val && !strcmp(val, "http://jabber.org/protocol/bytestreams")) { | |
649 jabber_si_xfer_bytestreams_send_init(xfer); | |
650 g_free(val); | |
651 return; | |
652 } | |
653 g_free(val); | |
654 } | |
655 } | |
656 } | |
657 gaim_xfer_cancel_remote(xfer); | |
658 } | |
659 | |
660 static void jabber_si_xfer_send_request(GaimXfer *xfer) | |
661 { | |
662 JabberSIXfer *jsx = xfer->data; | |
663 JabberIq *iq; | |
664 xmlnode *si, *file, *feature, *x, *field, *option, *value; | |
665 char buf[32]; | |
666 | |
667 xfer->filename = g_path_get_basename(xfer->local_filename); | |
668 | |
669 iq = jabber_iq_new(jsx->js, JABBER_IQ_SET); | |
670 xmlnode_set_attrib(iq->node, "to", xfer->who); | |
671 si = xmlnode_new_child(iq->node, "si"); | |
672 xmlnode_set_namespace(si, "http://jabber.org/protocol/si"); | |
673 jsx->stream_id = jabber_get_next_id(jsx->js); | |
674 xmlnode_set_attrib(si, "id", jsx->stream_id); | |
675 xmlnode_set_attrib(si, "profile", | |
676 "http://jabber.org/protocol/si/profile/file-transfer"); | |
677 | |
678 file = xmlnode_new_child(si, "file"); | |
679 xmlnode_set_namespace(file, | |
680 "http://jabber.org/protocol/si/profile/file-transfer"); | |
681 xmlnode_set_attrib(file, "name", xfer->filename); | |
682 g_snprintf(buf, sizeof(buf), "%" G_GSIZE_FORMAT, xfer->size); | |
683 xmlnode_set_attrib(file, "size", buf); | |
684 /* maybe later we'll do hash and date attribs */ | |
685 | |
686 feature = xmlnode_new_child(si, "feature"); | |
687 xmlnode_set_namespace(feature, | |
688 "http://jabber.org/protocol/feature-neg"); | |
689 x = xmlnode_new_child(feature, "x"); | |
690 xmlnode_set_namespace(x, "jabber:x:data"); | |
691 xmlnode_set_attrib(x, "type", "form"); | |
692 field = xmlnode_new_child(x, "field"); | |
693 xmlnode_set_attrib(field, "var", "stream-method"); | |
694 xmlnode_set_attrib(field, "type", "list-single"); | |
695 option = xmlnode_new_child(field, "option"); | |
696 value = xmlnode_new_child(option, "value"); | |
697 xmlnode_insert_data(value, "http://jabber.org/protocol/bytestreams", | |
698 -1); | |
699 /* | |
700 option = xmlnode_new_child(field, "option"); | |
701 value = xmlnode_new_child(option, "value"); | |
702 xmlnode_insert_data(value, "http://jabber.org/protocol/ibb", -1); | |
703 */ | |
704 | |
705 jabber_iq_set_callback(iq, jabber_si_xfer_send_method_cb, xfer); | |
706 | |
707 jabber_iq_send(iq); | |
708 } | |
709 | |
710 static void jabber_si_xfer_free(GaimXfer *xfer) | |
711 { | |
712 JabberSIXfer *jsx = xfer->data; | |
713 JabberStream *js = jsx->js; | |
714 | |
715 js->file_transfers = g_list_remove(js->file_transfers, xfer); | |
716 | |
717 if (jsx->connect_data != NULL) | |
718 gaim_proxy_connect_cancel(jsx->connect_data); | |
719 if (jsx->listen_data != NULL) | |
720 gaim_network_listen_cancel(jsx->listen_data); | |
721 | |
722 g_free(jsx->stream_id); | |
723 g_free(jsx->iq_id); | |
724 /* XXX: free other stuff */ | |
725 g_free(jsx->rxqueue); | |
726 g_free(jsx); | |
727 xfer->data = NULL; | |
728 } | |
729 | |
730 static void jabber_si_xfer_cancel_send(GaimXfer *xfer) | |
731 { | |
732 jabber_si_xfer_free(xfer); | |
733 gaim_debug(GAIM_DEBUG_INFO, "jabber", "in jabber_si_xfer_cancel_send\n"); | |
734 } | |
735 | |
736 | |
737 static void jabber_si_xfer_request_denied(GaimXfer *xfer) | |
738 { | |
739 jabber_si_xfer_free(xfer); | |
740 gaim_debug(GAIM_DEBUG_INFO, "jabber", "in jabber_si_xfer_request_denied\n"); | |
741 } | |
742 | |
743 | |
744 static void jabber_si_xfer_cancel_recv(GaimXfer *xfer) | |
745 { | |
746 jabber_si_xfer_free(xfer); | |
747 gaim_debug(GAIM_DEBUG_INFO, "jabber", "in jabber_si_xfer_cancel_recv\n"); | |
748 } | |
749 | |
750 | |
751 static void jabber_si_xfer_end(GaimXfer *xfer) | |
752 { | |
753 jabber_si_xfer_free(xfer); | |
754 } | |
755 | |
756 | |
757 static void jabber_si_xfer_send_disco_cb(JabberStream *js, const char *who, | |
758 JabberCapabilities capabilities, gpointer data) | |
759 { | |
760 GaimXfer *xfer = data; | |
761 | |
762 if(capabilities & JABBER_CAP_SI_FILE_XFER) { | |
763 jabber_si_xfer_send_request(xfer); | |
764 } else { | |
765 char *msg = g_strdup_printf(_("Unable to send file to %s, user does not support file transfers"), who); | |
766 gaim_notify_error(js->gc, _("File Send Failed"), | |
767 _("File Send Failed"), msg); | |
768 g_free(msg); | |
769 } | |
770 } | |
771 | |
772 static void jabber_si_xfer_init(GaimXfer *xfer) | |
773 { | |
774 JabberSIXfer *jsx = xfer->data; | |
775 JabberIq *iq; | |
776 if(gaim_xfer_get_type(xfer) == GAIM_XFER_SEND) { | |
777 JabberBuddy *jb; | |
778 JabberBuddyResource *jbr = NULL; | |
779 | |
780 jb = jabber_buddy_find(jsx->js, xfer->who, TRUE); | |
781 /* XXX */ | |
782 if(!jb) | |
783 return; | |
784 | |
785 /* XXX: for now, send to the first resource available */ | |
786 if(g_list_length(jb->resources) >= 1) { | |
787 char **who_v = g_strsplit(xfer->who, "/", 2); | |
788 char *who; | |
789 | |
790 jbr = jabber_buddy_find_resource(jb, NULL); | |
791 who = g_strdup_printf("%s/%s", who_v[0], jbr->name); | |
792 g_strfreev(who_v); | |
793 g_free(xfer->who); | |
794 xfer->who = who; | |
795 jabber_disco_info_do(jsx->js, who, | |
796 jabber_si_xfer_send_disco_cb, xfer); | |
797 } else { | |
798 return; /* XXX: ick */ | |
799 } | |
800 } else { | |
801 xmlnode *si, *feature, *x, *field, *value; | |
802 | |
803 iq = jabber_iq_new(jsx->js, JABBER_IQ_RESULT); | |
804 xmlnode_set_attrib(iq->node, "to", xfer->who); | |
805 if(jsx->iq_id) | |
806 jabber_iq_set_id(iq, jsx->iq_id); | |
807 | |
808 jsx->accepted = TRUE; | |
809 | |
810 si = xmlnode_new_child(iq->node, "si"); | |
811 xmlnode_set_namespace(si, "http://jabber.org/protocol/si"); | |
812 | |
813 feature = xmlnode_new_child(si, "feature"); | |
814 xmlnode_set_namespace(feature, "http://jabber.org/protocol/feature-neg"); | |
815 | |
816 x = xmlnode_new_child(feature, "x"); | |
817 xmlnode_set_namespace(x, "jabber:x:data"); | |
818 xmlnode_set_attrib(x, "type", "submit"); | |
819 | |
820 field = xmlnode_new_child(x, "field"); | |
821 xmlnode_set_attrib(field, "var", "stream-method"); | |
822 | |
823 value = xmlnode_new_child(field, "value"); | |
824 if(jsx->stream_method & STREAM_METHOD_BYTESTREAMS) | |
825 xmlnode_insert_data(value, "http://jabber.org/protocol/bytestreams", -1); | |
826 /* | |
827 else if(jsx->stream_method & STREAM_METHOD_IBB) | |
828 xmlnode_insert_data(value, "http://jabber.org/protocol/ibb", -1); | |
829 */ | |
830 | |
831 jabber_iq_send(iq); | |
832 } | |
833 } | |
834 | |
835 GaimXfer *jabber_si_new_xfer(GaimConnection *gc, const char *who) | |
836 { | |
837 JabberStream *js; | |
838 | |
839 GaimXfer *xfer; | |
840 JabberSIXfer *jsx; | |
841 | |
842 js = gc->proto_data; | |
843 | |
844 xfer = gaim_xfer_new(gc->account, GAIM_XFER_SEND, who); | |
845 if (xfer) | |
846 { | |
847 xfer->data = jsx = g_new0(JabberSIXfer, 1); | |
848 jsx->js = js; | |
849 | |
850 gaim_xfer_set_init_fnc(xfer, jabber_si_xfer_init); | |
851 gaim_xfer_set_cancel_send_fnc(xfer, jabber_si_xfer_cancel_send); | |
852 gaim_xfer_set_end_fnc(xfer, jabber_si_xfer_end); | |
853 | |
854 js->file_transfers = g_list_append(js->file_transfers, xfer); | |
855 } | |
856 | |
857 return xfer; | |
858 } | |
859 | |
860 void jabber_si_xfer_send(GaimConnection *gc, const char *who, const char *file) | |
861 { | |
862 JabberStream *js; | |
863 | |
864 GaimXfer *xfer; | |
865 | |
866 js = gc->proto_data; | |
867 | |
868 if(!gaim_find_buddy(gc->account, who) || !jabber_buddy_find(js, who, FALSE)) | |
869 return; | |
870 | |
871 xfer = jabber_si_new_xfer(gc, who); | |
872 | |
873 if (file) | |
874 gaim_xfer_request_accepted(xfer, file); | |
875 else | |
876 gaim_xfer_request(xfer); | |
877 } | |
878 | |
879 void jabber_si_parse(JabberStream *js, xmlnode *packet) | |
880 { | |
881 JabberSIXfer *jsx; | |
882 GaimXfer *xfer; | |
883 xmlnode *si, *file, *feature, *x, *field, *option, *value; | |
884 const char *stream_id, *filename, *filesize_c, *profile, *from; | |
885 size_t filesize = 0; | |
886 | |
887 if(!(si = xmlnode_get_child(packet, "si"))) | |
888 return; | |
889 | |
890 if(!(profile = xmlnode_get_attrib(si, "profile")) || | |
891 strcmp(profile, "http://jabber.org/protocol/si/profile/file-transfer")) | |
892 return; | |
893 | |
894 if(!(stream_id = xmlnode_get_attrib(si, "id"))) | |
895 return; | |
896 | |
897 if(!(file = xmlnode_get_child(si, "file"))) | |
898 return; | |
899 | |
900 if(!(filename = xmlnode_get_attrib(file, "name"))) | |
901 return; | |
902 | |
903 if((filesize_c = xmlnode_get_attrib(file, "size"))) | |
904 filesize = atoi(filesize_c); | |
905 | |
906 if(!(feature = xmlnode_get_child(si, "feature"))) | |
907 return; | |
908 | |
909 if(!(x = xmlnode_get_child_with_namespace(feature, "x", "jabber:x:data"))) | |
910 return; | |
911 | |
912 if(!(from = xmlnode_get_attrib(packet, "from"))) | |
913 return; | |
914 | |
915 /* if they've already sent us this file transfer with the same damn id | |
916 * then we're gonna ignore it, until I think of something better to do | |
917 * with it */ | |
918 if((xfer = jabber_si_xfer_find(js, stream_id, from))) | |
919 return; | |
920 | |
921 jsx = g_new0(JabberSIXfer, 1); | |
922 | |
923 for(field = xmlnode_get_child(x, "field"); field; field = xmlnode_get_next_twin(field)) { | |
924 const char *var = xmlnode_get_attrib(field, "var"); | |
925 if(var && !strcmp(var, "stream-method")) { | |
926 for(option = xmlnode_get_child(field, "option"); option; | |
927 option = xmlnode_get_next_twin(option)) { | |
928 if((value = xmlnode_get_child(option, "value"))) { | |
929 char *val; | |
930 if((val = xmlnode_get_data(value))) { | |
931 if(!strcmp(val, "http://jabber.org/protocol/bytestreams")) { | |
932 jsx->stream_method |= STREAM_METHOD_BYTESTREAMS; | |
933 /* | |
934 } else if(!strcmp(val, "http://jabber.org/protocol/ibb")) { | |
935 jsx->stream_method |= STREAM_METHOD_IBB; | |
936 */ | |
937 } | |
938 g_free(val); | |
939 } | |
940 } | |
941 } | |
942 } | |
943 } | |
944 | |
945 if(jsx->stream_method == STREAM_METHOD_UNKNOWN) { | |
946 g_free(jsx); | |
947 return; | |
948 } | |
949 | |
950 jsx->js = js; | |
951 jsx->stream_id = g_strdup(stream_id); | |
952 jsx->iq_id = g_strdup(xmlnode_get_attrib(packet, "id")); | |
953 | |
954 xfer = gaim_xfer_new(js->gc->account, GAIM_XFER_RECEIVE, from); | |
955 if (xfer) | |
956 { | |
957 xfer->data = jsx; | |
958 | |
959 gaim_xfer_set_filename(xfer, filename); | |
960 if(filesize > 0) | |
961 gaim_xfer_set_size(xfer, filesize); | |
962 | |
963 gaim_xfer_set_init_fnc(xfer, jabber_si_xfer_init); | |
964 gaim_xfer_set_request_denied_fnc(xfer, jabber_si_xfer_request_denied); | |
965 gaim_xfer_set_cancel_recv_fnc(xfer, jabber_si_xfer_cancel_recv); | |
966 gaim_xfer_set_end_fnc(xfer, jabber_si_xfer_end); | |
967 | |
968 js->file_transfers = g_list_append(js->file_transfers, xfer); | |
969 | |
970 gaim_xfer_request(xfer); | |
971 } | |
972 } | |
973 | |
974 |