| |
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 |