Mercurial > pidgin.yaz
comparison libpurple/protocols/oscar/peer.c @ 15374: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 | 32c366eeeb99 |
comparison
equal
deleted
inserted
replaced
15373:f79e0f4df793 | 15374:5fe8042783c1 |
---|---|
1 /* | |
2 * Gaim's oscar protocol plugin | |
3 * This file is the legal property of its developers. | |
4 * Please see the AUTHORS file distributed alongside this file. | |
5 * | |
6 * This library is free software; you can redistribute it and/or | |
7 * modify it under the terms of the GNU Lesser General Public | |
8 * License as published by the Free Software Foundation; either | |
9 * version 2 of the License, or (at your option) any later version. | |
10 * | |
11 * This library 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 GNU | |
14 * Lesser General Public License for more details. | |
15 * | |
16 * You should have received a copy of the GNU Lesser General Public | |
17 * License along with this library; if not, write to the Free Software | |
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
19 */ | |
20 | |
21 /* | |
22 * Functions dealing with peer connections. This includes the code | |
23 * used to establish a peer connection for both Oscar File transfer | |
24 * (OFT) and Oscar Direct Connect (ODC). (ODC is also referred to | |
25 * as DirectIM and IM Image.) | |
26 */ | |
27 | |
28 #ifdef HAVE_CONFIG_H | |
29 #include <config.h> | |
30 #endif | |
31 | |
32 /* From the oscar PRPL */ | |
33 #include "oscar.h" | |
34 #include "peer.h" | |
35 | |
36 /* From Gaim */ | |
37 #include "conversation.h" | |
38 #include "ft.h" | |
39 #include "network.h" | |
40 #include "notify.h" | |
41 #include "request.h" | |
42 #include "util.h" | |
43 | |
44 #ifndef _WIN32 | |
45 #include <stdio.h> | |
46 #include <netdb.h> | |
47 #include <sys/socket.h> | |
48 #include <netinet/in.h> | |
49 #include <arpa/inet.h> /* for inet_ntoa */ | |
50 #include <limits.h> /* for UINT_MAX */ | |
51 #endif | |
52 | |
53 #ifdef _WIN32 | |
54 #include "win32dep.h" | |
55 #endif | |
56 | |
57 /* | |
58 * I really want to switch all our networking code to using IPv6 only, | |
59 * but that really isn't a good idea at all. Evan S. of Adium says | |
60 * OS X sets all connections as "AF_INET6/PF_INET6," even if there is | |
61 * nothing inherently IPv6 about them. And I feel like Linux kernel | |
62 * 2.6.5 is doing the same thing. So we REALLY should accept | |
63 * connections if they're showing up as IPv6. Old OSes (Solaris?) | |
64 * that might not have full IPv6 support yet will fail if we try | |
65 * to use PF_INET6 but it isn't defined. --Mark Doliner | |
66 */ | |
67 #ifndef PF_INET6 | |
68 #define PF_INET6 PF_INET | |
69 #endif | |
70 | |
71 PeerConnection * | |
72 peer_connection_find_by_type(OscarData *od, const char *sn, OscarCapability type) | |
73 { | |
74 GSList *cur; | |
75 PeerConnection *conn; | |
76 | |
77 for (cur = od->peer_connections; cur != NULL; cur = cur->next) | |
78 { | |
79 conn = cur->data; | |
80 if ((conn->type == type) && !aim_sncmp(conn->sn, sn)) | |
81 return conn; | |
82 } | |
83 | |
84 return NULL; | |
85 } | |
86 | |
87 /** | |
88 * @param cookie This must be exactly 8 characters. | |
89 */ | |
90 PeerConnection * | |
91 peer_connection_find_by_cookie(OscarData *od, const char *sn, const guchar *cookie) | |
92 { | |
93 GSList *cur; | |
94 PeerConnection *conn; | |
95 | |
96 for (cur = od->peer_connections; cur != NULL; cur = cur->next) | |
97 { | |
98 conn = cur->data; | |
99 if (!memcmp(conn->cookie, cookie, 8) && !aim_sncmp(conn->sn, sn)) | |
100 return conn; | |
101 } | |
102 | |
103 return NULL; | |
104 } | |
105 | |
106 PeerConnection * | |
107 peer_connection_new(OscarData *od, OscarCapability type, const char *sn) | |
108 { | |
109 PeerConnection *conn; | |
110 GaimAccount *account; | |
111 | |
112 account = gaim_connection_get_account(od->gc); | |
113 | |
114 conn = g_new0(PeerConnection, 1); | |
115 conn->od = od; | |
116 conn->type = type; | |
117 conn->sn = g_strdup(sn); | |
118 conn->buffer_outgoing = gaim_circ_buffer_new(0); | |
119 conn->listenerfd = -1; | |
120 conn->fd = -1; | |
121 conn->lastactivity = time(NULL); | |
122 conn->use_proxy |= gaim_account_get_bool(account, "always_use_rv_proxy", FALSE); | |
123 | |
124 if (type == OSCAR_CAPABILITY_DIRECTIM) | |
125 memcpy(conn->magic, "ODC2", 4); | |
126 else if (type == OSCAR_CAPABILITY_SENDFILE) | |
127 memcpy(conn->magic, "OFT2", 4); | |
128 | |
129 od->peer_connections = g_slist_prepend(od->peer_connections, conn); | |
130 | |
131 return conn; | |
132 } | |
133 | |
134 static void | |
135 peer_connection_close(PeerConnection *conn) | |
136 { | |
137 if (conn->type == OSCAR_CAPABILITY_DIRECTIM) | |
138 peer_odc_close(conn); | |
139 else if (conn->type == OSCAR_CAPABILITY_SENDFILE) | |
140 peer_oft_close(conn); | |
141 | |
142 if (conn->verified_connect_data != NULL) | |
143 { | |
144 gaim_proxy_connect_cancel(conn->verified_connect_data); | |
145 conn->verified_connect_data = NULL; | |
146 } | |
147 | |
148 if (conn->client_connect_data != NULL) | |
149 { | |
150 gaim_proxy_connect_cancel(conn->client_connect_data); | |
151 conn->client_connect_data = NULL; | |
152 } | |
153 | |
154 if (conn->listen_data != NULL) | |
155 { | |
156 gaim_network_listen_cancel(conn->listen_data); | |
157 conn->listen_data = NULL; | |
158 } | |
159 | |
160 if (conn->connect_timeout_timer != 0) | |
161 { | |
162 gaim_timeout_remove(conn->connect_timeout_timer); | |
163 conn->connect_timeout_timer = 0; | |
164 } | |
165 | |
166 if (conn->watcher_incoming != 0) | |
167 { | |
168 gaim_input_remove(conn->watcher_incoming); | |
169 conn->watcher_incoming = 0; | |
170 } | |
171 if (conn->watcher_outgoing != 0) | |
172 { | |
173 gaim_input_remove(conn->watcher_outgoing); | |
174 conn->watcher_outgoing = 0; | |
175 } | |
176 if (conn->listenerfd != -1) | |
177 { | |
178 close(conn->listenerfd); | |
179 conn->listenerfd = -1; | |
180 } | |
181 if (conn->fd != -1) | |
182 { | |
183 close(conn->fd); | |
184 conn->fd = -1; | |
185 } | |
186 | |
187 g_free(conn->buffer_incoming.data); | |
188 conn->buffer_incoming.data = NULL; | |
189 conn->buffer_incoming.len = 0; | |
190 conn->buffer_incoming.offset = 0; | |
191 | |
192 gaim_circ_buffer_destroy(conn->buffer_outgoing); | |
193 conn->buffer_outgoing = gaim_circ_buffer_new(0); | |
194 | |
195 conn->flags &= ~PEER_CONNECTION_FLAG_IS_INCOMING; | |
196 } | |
197 | |
198 static gboolean | |
199 peer_connection_destroy_cb(gpointer data) | |
200 { | |
201 PeerConnection *conn; | |
202 | |
203 conn = data; | |
204 | |
205 gaim_request_close_with_handle(conn); | |
206 | |
207 peer_connection_close(conn); | |
208 | |
209 if (conn->checksum_data != NULL) | |
210 peer_oft_checksum_destroy(conn->checksum_data); | |
211 | |
212 if (conn->xfer != NULL) | |
213 { | |
214 GaimXferStatusType status; | |
215 conn->xfer->data = NULL; | |
216 status = gaim_xfer_get_status(conn->xfer); | |
217 if ((status != GAIM_XFER_STATUS_DONE) && | |
218 (status != GAIM_XFER_STATUS_CANCEL_LOCAL) && | |
219 (status != GAIM_XFER_STATUS_CANCEL_REMOTE)) | |
220 { | |
221 if ((conn->disconnect_reason == OSCAR_DISCONNECT_REMOTE_CLOSED) || | |
222 (conn->disconnect_reason == OSCAR_DISCONNECT_REMOTE_REFUSED)) | |
223 gaim_xfer_cancel_remote(conn->xfer); | |
224 else | |
225 gaim_xfer_cancel_local(conn->xfer); | |
226 } | |
227 gaim_xfer_unref(conn->xfer); | |
228 conn->xfer = NULL; | |
229 } | |
230 | |
231 g_free(conn->sn); | |
232 g_free(conn->error_message); | |
233 g_free(conn->proxyip); | |
234 g_free(conn->clientip); | |
235 g_free(conn->verifiedip); | |
236 g_free(conn->xferdata.name); | |
237 gaim_circ_buffer_destroy(conn->buffer_outgoing); | |
238 | |
239 conn->od->peer_connections = g_slist_remove(conn->od->peer_connections, conn); | |
240 | |
241 g_free(conn); | |
242 | |
243 return FALSE; | |
244 } | |
245 | |
246 void | |
247 peer_connection_destroy(PeerConnection *conn, OscarDisconnectReason reason, const gchar *error_message) | |
248 { | |
249 if (conn->destroy_timeout != 0) | |
250 gaim_timeout_remove(conn->destroy_timeout); | |
251 conn->disconnect_reason = reason; | |
252 g_free(conn->error_message); | |
253 conn->error_message = g_strdup(error_message); | |
254 peer_connection_destroy_cb(conn); | |
255 } | |
256 | |
257 void | |
258 peer_connection_schedule_destroy(PeerConnection *conn, OscarDisconnectReason reason, const gchar *error_message) | |
259 { | |
260 if (conn->destroy_timeout != 0) | |
261 /* Already taken care of */ | |
262 return; | |
263 | |
264 gaim_debug_info("oscar", "Scheduling destruction of peer connection\n"); | |
265 conn->disconnect_reason = reason; | |
266 g_free(conn->error_message); | |
267 conn->error_message = g_strdup(error_message); | |
268 conn->destroy_timeout = gaim_timeout_add(0, peer_connection_destroy_cb, conn); | |
269 } | |
270 | |
271 /*******************************************************************/ | |
272 /* Begin code for receiving data on a peer connection */ | |
273 /*******************************************************************/ | |
274 | |
275 /** | |
276 * This should be used to read ODC and OFT framing info. It should | |
277 * NOT be used to read the payload sent across the connection (IMs, | |
278 * file data, etc), and it should NOT be used to read proxy negotiation | |
279 * headers. | |
280 * | |
281 * Unlike flap_connection_recv_cb(), this only reads one frame at a | |
282 * time. This is done so that the watcher can be changed during the | |
283 * handling of the frame. If the watcher is changed then this | |
284 * function will not read in any more data. This happens when | |
285 * reading the payload of a direct IM frame, or when we're | |
286 * receiving a file from the remote user. Once the data has been | |
287 * read, the watcher will be switched back to this function to | |
288 * continue reading the next frame. | |
289 */ | |
290 void | |
291 peer_connection_recv_cb(gpointer data, gint source, GaimInputCondition cond) | |
292 { | |
293 PeerConnection *conn; | |
294 ssize_t read; | |
295 | |
296 conn = data; | |
297 | |
298 /* Start reading a new ODC/OFT frame */ | |
299 if (conn->buffer_incoming.data == NULL) | |
300 { | |
301 /* Read the first 6 bytes (magic string and frame length) */ | |
302 read = recv(conn->fd, conn->header + conn->header_received, | |
303 6 - conn->header_received, 0); | |
304 | |
305 /* Check if the remote user closed the connection */ | |
306 if (read == 0) | |
307 { | |
308 peer_connection_destroy(conn, OSCAR_DISCONNECT_REMOTE_CLOSED, NULL); | |
309 return; | |
310 } | |
311 | |
312 /* If there was an error then close the connection */ | |
313 if (read == -1) | |
314 { | |
315 if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) | |
316 /* No worries */ | |
317 return; | |
318 | |
319 peer_connection_destroy(conn, | |
320 OSCAR_DISCONNECT_LOST_CONNECTION, strerror(errno)); | |
321 return; | |
322 } | |
323 | |
324 conn->lastactivity = time(NULL); | |
325 | |
326 /* If we don't even have the first 6 bytes then do nothing */ | |
327 conn->header_received += read; | |
328 if (conn->header_received < 6) | |
329 return; | |
330 | |
331 /* All ODC/OFT frames must start with a magic string */ | |
332 if (memcmp(conn->magic, conn->header, 4)) | |
333 { | |
334 gaim_debug_warning("oscar", "Expecting magic string to " | |
335 "be %c%c%c%c but received magic string %c%c%c%c. " | |
336 "Closing connection.\n", | |
337 conn->magic[0], conn->magic[1], conn->magic[2], | |
338 conn->magic[3], conn->header[0], conn->header[1], | |
339 conn->header[2], conn->header[3]); | |
340 peer_connection_destroy(conn, OSCAR_DISCONNECT_INVALID_DATA, NULL); | |
341 return; | |
342 } | |
343 | |
344 /* Initialize a new temporary ByteStream for incoming data */ | |
345 conn->buffer_incoming.len = aimutil_get16(&conn->header[4]) - 6; | |
346 conn->buffer_incoming.data = g_new(guint8, conn->buffer_incoming.len); | |
347 conn->buffer_incoming.offset = 0; | |
348 } | |
349 | |
350 /* Read data into the temporary buffer until it is complete */ | |
351 read = recv(conn->fd, | |
352 &conn->buffer_incoming.data[conn->buffer_incoming.offset], | |
353 conn->buffer_incoming.len - conn->buffer_incoming.offset, | |
354 0); | |
355 | |
356 /* Check if the remote user closed the connection */ | |
357 if (read == 0) | |
358 { | |
359 peer_connection_destroy(conn, OSCAR_DISCONNECT_REMOTE_CLOSED, NULL); | |
360 return; | |
361 } | |
362 | |
363 if (read == -1) | |
364 { | |
365 if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) | |
366 /* No worries */ | |
367 return; | |
368 | |
369 peer_connection_destroy(conn, | |
370 OSCAR_DISCONNECT_LOST_CONNECTION, strerror(errno)); | |
371 return; | |
372 } | |
373 | |
374 conn->lastactivity = time(NULL); | |
375 conn->buffer_incoming.offset += read; | |
376 if (conn->buffer_incoming.offset < conn->buffer_incoming.len) | |
377 /* Waiting for more data to arrive */ | |
378 return; | |
379 | |
380 /* We have a complete ODC/OFT frame! Handle it and continue reading */ | |
381 byte_stream_rewind(&conn->buffer_incoming); | |
382 if (conn->type == OSCAR_CAPABILITY_DIRECTIM) | |
383 { | |
384 peer_odc_recv_frame(conn, &conn->buffer_incoming); | |
385 } | |
386 else if (conn->type == OSCAR_CAPABILITY_SENDFILE) | |
387 { | |
388 peer_oft_recv_frame(conn, &conn->buffer_incoming); | |
389 } | |
390 | |
391 g_free(conn->buffer_incoming.data); | |
392 conn->buffer_incoming.data = NULL; | |
393 | |
394 conn->header_received = 0; | |
395 } | |
396 | |
397 /*******************************************************************/ | |
398 /* End code for receiving data on a peer connection */ | |
399 /*******************************************************************/ | |
400 | |
401 /*******************************************************************/ | |
402 /* Begin code for sending data on a peer connection */ | |
403 /*******************************************************************/ | |
404 | |
405 static void | |
406 send_cb(gpointer data, gint source, GaimInputCondition cond) | |
407 { | |
408 PeerConnection *conn; | |
409 gsize writelen; | |
410 ssize_t wrotelen; | |
411 | |
412 conn = data; | |
413 writelen = gaim_circ_buffer_get_max_read(conn->buffer_outgoing); | |
414 | |
415 if (writelen == 0) | |
416 { | |
417 gaim_input_remove(conn->watcher_outgoing); | |
418 conn->watcher_outgoing = 0; | |
419 return; | |
420 } | |
421 | |
422 wrotelen = send(conn->fd, conn->buffer_outgoing->outptr, writelen, 0); | |
423 if (wrotelen <= 0) | |
424 { | |
425 if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) | |
426 /* No worries */ | |
427 return; | |
428 | |
429 if (conn->ready) | |
430 { | |
431 gaim_input_remove(conn->watcher_outgoing); | |
432 conn->watcher_outgoing = 0; | |
433 close(conn->fd); | |
434 conn->fd = -1; | |
435 peer_connection_schedule_destroy(conn, | |
436 OSCAR_DISCONNECT_LOST_CONNECTION, NULL); | |
437 } | |
438 else | |
439 { | |
440 /* | |
441 * This could happen when unable to send a negotiation | |
442 * frame to a peer proxy server. | |
443 */ | |
444 peer_connection_trynext(conn); | |
445 } | |
446 return; | |
447 } | |
448 | |
449 gaim_circ_buffer_mark_read(conn->buffer_outgoing, wrotelen); | |
450 conn->lastactivity = time(NULL); | |
451 } | |
452 | |
453 /** | |
454 * This should be called by OFT/ODC code to send a standard OFT or ODC | |
455 * frame across the peer connection along with some payload data. Or | |
456 * maybe a file. Anything, really. | |
457 */ | |
458 void | |
459 peer_connection_send(PeerConnection *conn, ByteStream *bs) | |
460 { | |
461 /* Add everything to our outgoing buffer */ | |
462 gaim_circ_buffer_append(conn->buffer_outgoing, bs->data, bs->len); | |
463 | |
464 /* If we haven't already started writing stuff, then start the cycle */ | |
465 if ((conn->watcher_outgoing == 0) && (conn->fd != -1)) | |
466 { | |
467 conn->watcher_outgoing = gaim_input_add(conn->fd, | |
468 GAIM_INPUT_WRITE, send_cb, conn); | |
469 send_cb(conn, conn->fd, 0); | |
470 } | |
471 } | |
472 | |
473 /*******************************************************************/ | |
474 /* End code for sending data on a peer connection */ | |
475 /*******************************************************************/ | |
476 | |
477 /*******************************************************************/ | |
478 /* Begin code for establishing a peer connection */ | |
479 /*******************************************************************/ | |
480 | |
481 void | |
482 peer_connection_finalize_connection(PeerConnection *conn) | |
483 { | |
484 conn->watcher_incoming = gaim_input_add(conn->fd, | |
485 GAIM_INPUT_READ, peer_connection_recv_cb, conn); | |
486 | |
487 if (conn->type == OSCAR_CAPABILITY_DIRECTIM) | |
488 { | |
489 /* | |
490 * If we are connecting to them then send our cookie so they | |
491 * can verify who we are. Note: This doesn't seem to be | |
492 * necessary, but it also doesn't seem to hurt. | |
493 */ | |
494 if (!(conn->flags & PEER_CONNECTION_FLAG_IS_INCOMING)) | |
495 peer_odc_send_cookie(conn); | |
496 } | |
497 else if (conn->type == OSCAR_CAPABILITY_SENDFILE) | |
498 { | |
499 if (gaim_xfer_get_type(conn->xfer) == GAIM_XFER_SEND) | |
500 { | |
501 peer_oft_send_prompt(conn); | |
502 } | |
503 } | |
504 | |
505 /* | |
506 * Tell the remote user that we're connected (which may also imply | |
507 * that we've accepted their request). | |
508 */ | |
509 if (!(conn->flags & PEER_CONNECTION_FLAG_IS_INCOMING)) | |
510 aim_im_sendch2_connected(conn); | |
511 } | |
512 | |
513 /** | |
514 * We tried to make an outgoing connection to a remote user. It | |
515 * either connected or failed to connect. | |
516 */ | |
517 static void | |
518 peer_connection_common_established_cb(gpointer data, gint source, const gchar *error_message, gboolean verified) | |
519 { | |
520 PeerConnection *conn; | |
521 | |
522 conn = data; | |
523 | |
524 if (verified) | |
525 conn->verified_connect_data = NULL; | |
526 else | |
527 conn->client_connect_data = NULL; | |
528 | |
529 if (source < 0) | |
530 { | |
531 if ((conn->verified_connect_data == NULL) && | |
532 (conn->client_connect_data == NULL)) | |
533 { | |
534 /* Our parallel connection attemps have both failed. */ | |
535 peer_connection_trynext(conn); | |
536 } | |
537 return; | |
538 } | |
539 | |
540 gaim_timeout_remove(conn->connect_timeout_timer); | |
541 conn->connect_timeout_timer = 0; | |
542 | |
543 if (conn->client_connect_data != NULL) | |
544 { | |
545 gaim_proxy_connect_cancel(conn->client_connect_data); | |
546 conn->client_connect_data = NULL; | |
547 } | |
548 | |
549 if (conn->verified_connect_data != NULL) | |
550 { | |
551 gaim_proxy_connect_cancel(conn->verified_connect_data); | |
552 conn->verified_connect_data = NULL; | |
553 } | |
554 | |
555 conn->fd = source; | |
556 | |
557 peer_connection_finalize_connection(conn); | |
558 } | |
559 | |
560 static void | |
561 peer_connection_verified_established_cb(gpointer data, gint source, const gchar *error_message) | |
562 { | |
563 peer_connection_common_established_cb(data, source, error_message, TRUE); | |
564 } | |
565 | |
566 static void | |
567 peer_connection_client_established_cb(gpointer data, gint source, const gchar *error_message) | |
568 { | |
569 peer_connection_common_established_cb(data, source, error_message, FALSE); | |
570 } | |
571 | |
572 /** | |
573 * This is the watcher callback for any listening socket that is | |
574 * waiting for a peer to connect. When a peer connects we set the | |
575 * input watcher to start reading data from the peer. | |
576 * | |
577 * To make sure that the connection is with the intended person and | |
578 * not with a malicious middle man, we don't send anything until we've | |
579 * received a peer frame from the remote user and have verified that | |
580 * the cookie in the peer frame matches the cookie that was exchanged | |
581 * in the channel 2 ICBM. | |
582 */ | |
583 void | |
584 peer_connection_listen_cb(gpointer data, gint source, GaimInputCondition cond) | |
585 { | |
586 PeerConnection *conn; | |
587 OscarData *od; | |
588 GaimConnection *gc; | |
589 struct sockaddr addr; | |
590 socklen_t addrlen = sizeof(addr); | |
591 | |
592 conn = data; | |
593 od = conn->od; | |
594 gc = od->gc; | |
595 | |
596 gaim_debug_info("oscar", "Accepting connection on listener socket.\n"); | |
597 | |
598 conn->fd = accept(conn->listenerfd, &addr, &addrlen); | |
599 if (conn->fd == -1) | |
600 { | |
601 if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) | |
602 /* No connection yet--no worries */ | |
603 /* TODO: Hmm, but they SHOULD be connected if we're here, right? */ | |
604 return; | |
605 | |
606 peer_connection_trynext(conn); | |
607 return; | |
608 } | |
609 | |
610 if ((addr.sa_family != PF_INET) && (addr.sa_family != PF_INET6)) | |
611 { | |
612 /* Invalid connection type?! Continue waiting. */ | |
613 close(conn->fd); | |
614 return; | |
615 } | |
616 | |
617 fcntl(conn->fd, F_SETFL, O_NONBLOCK); | |
618 gaim_input_remove(conn->watcher_incoming); | |
619 | |
620 peer_connection_finalize_connection(conn); | |
621 } | |
622 | |
623 /** | |
624 * We've just opened a listener socket, so we send the remote | |
625 * user an ICBM and ask them to connect to us. | |
626 */ | |
627 static void | |
628 peer_connection_establish_listener_cb(int listenerfd, gpointer data) | |
629 { | |
630 PeerConnection *conn; | |
631 OscarData *od; | |
632 GaimConnection *gc; | |
633 GaimAccount *account; | |
634 GaimConversation *conv; | |
635 char *tmp; | |
636 FlapConnection *bos_conn; | |
637 const char *listener_ip; | |
638 unsigned short listener_port; | |
639 | |
640 conn = data; | |
641 conn->listen_data = NULL; | |
642 | |
643 if (listenerfd == -1) | |
644 { | |
645 /* Could not open listener socket */ | |
646 peer_connection_trynext(conn); | |
647 return; | |
648 } | |
649 | |
650 od = conn->od; | |
651 gc = od->gc; | |
652 account = gaim_connection_get_account(gc); | |
653 conn->listenerfd = listenerfd; | |
654 | |
655 /* Send the "please connect to me!" ICBM */ | |
656 bos_conn = flap_connection_findbygroup(od, SNAC_FAMILY_ICBM); | |
657 if (bos_conn == NULL) | |
658 { | |
659 /* Not good */ | |
660 peer_connection_trynext(conn); | |
661 return; | |
662 } | |
663 | |
664 listener_ip = gaim_network_get_my_ip(bos_conn->fd); | |
665 listener_port = gaim_network_get_port_from_fd(conn->listenerfd); | |
666 if (conn->type == OSCAR_CAPABILITY_DIRECTIM) | |
667 { | |
668 aim_im_sendch2_odc_requestdirect(od, | |
669 conn->cookie, conn->sn, gaim_network_ip_atoi(listener_ip), | |
670 listener_port, ++conn->lastrequestnumber); | |
671 | |
672 /* Print a message to a local conversation window */ | |
673 conv = gaim_conversation_new(GAIM_CONV_TYPE_IM, account, conn->sn); | |
674 tmp = g_strdup_printf(_("Asking %s to connect to us at %s:%hu for " | |
675 "Direct IM."), conn->sn, listener_ip, listener_port); | |
676 gaim_conversation_write(conv, NULL, tmp, GAIM_MESSAGE_SYSTEM, time(NULL)); | |
677 g_free(tmp); | |
678 } | |
679 else if (conn->type == OSCAR_CAPABILITY_SENDFILE) | |
680 { | |
681 aim_im_sendch2_sendfile_requestdirect(od, | |
682 conn->cookie, conn->sn, | |
683 gaim_network_ip_atoi(listener_ip), | |
684 listener_port, ++conn->lastrequestnumber, | |
685 (const gchar *)conn->xferdata.name, | |
686 conn->xferdata.size, conn->xferdata.totfiles); | |
687 } | |
688 } | |
689 | |
690 /** | |
691 * This is a callback function used when we're connecting to a peer | |
692 * using either the client IP or the verified IP and the connection | |
693 * took longer than 5 seconds to complete. We do this because | |
694 * waiting for the OS to time out the connection attempt is not | |
695 * practical--the default timeout on many OSes can be 3 minutes or | |
696 * more, and users are impatient. | |
697 * | |
698 * Worst case scenario: the user is connected to the Internet using | |
699 * a modem with severe lag. The peer connections fail and Gaim falls | |
700 * back to using a proxied connection. The lower bandwidth | |
701 * limitations imposed by the proxied connection won't matter because | |
702 * the user is using a modem. | |
703 * | |
704 * I suppose this line of thinking is discriminatory against people | |
705 * with very high lag but decent throughput who are transferring | |
706 * large files. But we don't care about those people. | |
707 * | |
708 * I (Sean) changed the timeout from 15 to 5 seconds, as 60 seconds is | |
709 * too long for a user to wait to send a file. I'm also parallelizing | |
710 * requests when possible. The longest we should have to wait now is 10 | |
711 * seconds. We shouldn't make it shorter than this. | |
712 */ | |
713 static gboolean | |
714 peer_connection_tooktoolong(gpointer data) | |
715 { | |
716 PeerConnection *conn; | |
717 | |
718 conn = data; | |
719 | |
720 gaim_debug_info("oscar", "Peer connection timed out after 5 seconds. " | |
721 "Trying next method...\n"); | |
722 | |
723 peer_connection_trynext(conn); | |
724 | |
725 /* Cancel this timer. It'll be added again, if needed. */ | |
726 return FALSE; | |
727 } | |
728 | |
729 /** | |
730 * Try to establish the given PeerConnection using a defined | |
731 * sequence of steps. | |
732 */ | |
733 void | |
734 peer_connection_trynext(PeerConnection *conn) | |
735 { | |
736 GaimAccount *account; | |
737 | |
738 account = gaim_connection_get_account(conn->od->gc); | |
739 | |
740 /* | |
741 * Close any remnants of a previous failed connection attempt. | |
742 */ | |
743 peer_connection_close(conn); | |
744 | |
745 /* | |
746 * 1. Attempt to connect to the remote user using their verifiedip and clientip. | |
747 * We try these at the same time and use whichever succeeds first, so we don't | |
748 * have to wait for a timeout. | |
749 */ | |
750 if (!(conn->flags & PEER_CONNECTION_FLAG_TRIED_DIRECT) && | |
751 (conn->verifiedip != NULL) && (conn->port != 0) && (!conn->use_proxy)) | |
752 { | |
753 conn->flags |= PEER_CONNECTION_FLAG_TRIED_DIRECT; | |
754 | |
755 if (conn->type == OSCAR_CAPABILITY_DIRECTIM) | |
756 { | |
757 gchar *tmp; | |
758 GaimConversation *conv; | |
759 tmp = g_strdup_printf(_("Attempting to connect to %s:%hu."), | |
760 conn->verifiedip, conn->port); | |
761 conv = gaim_conversation_new(GAIM_CONV_TYPE_IM, account, conn->sn); | |
762 gaim_conversation_write(conv, NULL, tmp, | |
763 GAIM_MESSAGE_SYSTEM, time(NULL)); | |
764 g_free(tmp); | |
765 } | |
766 | |
767 conn->verified_connect_data = gaim_proxy_connect(NULL, account, | |
768 conn->verifiedip, conn->port, | |
769 peer_connection_verified_established_cb, conn); | |
770 | |
771 if ((conn->verifiedip == NULL) || | |
772 strcmp(conn->verifiedip, conn->clientip)) | |
773 { | |
774 conn->client_connect_data = gaim_proxy_connect(NULL, account, | |
775 conn->clientip, conn->port, | |
776 peer_connection_client_established_cb, conn); | |
777 } | |
778 | |
779 if ((conn->verified_connect_data != NULL) || | |
780 (conn->client_connect_data != NULL)) | |
781 { | |
782 /* Connecting... */ | |
783 conn->connect_timeout_timer = gaim_timeout_add(5000, | |
784 peer_connection_tooktoolong, conn); | |
785 return; | |
786 } | |
787 } | |
788 | |
789 /* | |
790 * 2. Attempt to have the remote user connect to us (using both | |
791 * our verifiedip and our clientip). | |
792 */ | |
793 if (!(conn->flags & PEER_CONNECTION_FLAG_TRIED_INCOMING) && | |
794 (!conn->use_proxy)) | |
795 { | |
796 conn->flags |= PEER_CONNECTION_FLAG_TRIED_INCOMING; | |
797 | |
798 /* | |
799 * Remote user is connecting to us, so we'll need to verify | |
800 * that the user who connected is our friend. | |
801 */ | |
802 conn->flags |= PEER_CONNECTION_FLAG_IS_INCOMING; | |
803 | |
804 conn->listen_data = gaim_network_listen_range(5190, 5290, SOCK_STREAM, | |
805 peer_connection_establish_listener_cb, conn); | |
806 if (conn->listen_data != NULL) | |
807 { | |
808 /* Opening listener socket... */ | |
809 return; | |
810 } | |
811 } | |
812 | |
813 /* | |
814 * 3. Attempt to have both users connect to an intermediate proxy | |
815 * server. | |
816 */ | |
817 if (!(conn->flags & PEER_CONNECTION_FLAG_TRIED_PROXY)) | |
818 { | |
819 conn->flags |= PEER_CONNECTION_FLAG_TRIED_PROXY; | |
820 | |
821 /* | |
822 * If we initiate the proxy connection, then the remote user | |
823 * could be anyone, so we need to verify that the user who | |
824 * connected is our friend. | |
825 */ | |
826 if (!conn->use_proxy) | |
827 conn->flags |= PEER_CONNECTION_FLAG_IS_INCOMING; | |
828 | |
829 if (conn->type == OSCAR_CAPABILITY_DIRECTIM) | |
830 { | |
831 gchar *tmp; | |
832 GaimConversation *conv; | |
833 tmp = g_strdup_printf(_("Attempting to connect via proxy server.")); | |
834 conv = gaim_conversation_new(GAIM_CONV_TYPE_IM, account, conn->sn); | |
835 gaim_conversation_write(conv, NULL, tmp, | |
836 GAIM_MESSAGE_SYSTEM, time(NULL)); | |
837 g_free(tmp); | |
838 } | |
839 | |
840 conn->verified_connect_data = gaim_proxy_connect(NULL, account, | |
841 (conn->proxyip != NULL) ? conn->proxyip : PEER_PROXY_SERVER, | |
842 PEER_PROXY_PORT, | |
843 peer_proxy_connection_established_cb, conn); | |
844 if (conn->verified_connect_data != NULL) | |
845 { | |
846 /* Connecting... */ | |
847 return; | |
848 } | |
849 } | |
850 | |
851 /* Give up! */ | |
852 peer_connection_destroy(conn, OSCAR_DISCONNECT_COULD_NOT_CONNECT, NULL); | |
853 } | |
854 | |
855 /** | |
856 * Initiate a peer connection with someone. | |
857 */ | |
858 void | |
859 peer_connection_propose(OscarData *od, OscarCapability type, const char *sn) | |
860 { | |
861 PeerConnection *conn; | |
862 | |
863 if (type == OSCAR_CAPABILITY_DIRECTIM) | |
864 { | |
865 conn = peer_connection_find_by_type(od, sn, type); | |
866 if (conn != NULL) | |
867 { | |
868 if (conn->ready) | |
869 { | |
870 GaimAccount *account; | |
871 GaimConversation *conv; | |
872 | |
873 gaim_debug_info("oscar", "Already have a direct IM " | |
874 "session with %s.\n", sn); | |
875 account = gaim_connection_get_account(od->gc); | |
876 conv = gaim_find_conversation_with_account(GAIM_CONV_TYPE_IM, | |
877 sn, account); | |
878 if (conv != NULL) | |
879 gaim_conversation_present(conv); | |
880 return; | |
881 } | |
882 | |
883 /* Cancel the old connection and try again */ | |
884 peer_connection_destroy(conn, OSCAR_DISCONNECT_RETRYING, NULL); | |
885 } | |
886 } | |
887 | |
888 conn = peer_connection_new(od, type, sn); | |
889 conn->flags |= PEER_CONNECTION_FLAG_INITIATED_BY_ME; | |
890 conn->flags |= PEER_CONNECTION_FLAG_APPROVED; | |
891 aim_icbm_makecookie(conn->cookie); | |
892 | |
893 peer_connection_trynext(conn); | |
894 } | |
895 | |
896 /** | |
897 * Someone else wants to establish a peer connection with us, | |
898 * and we said yes. | |
899 */ | |
900 static void | |
901 peer_connection_got_proposition_yes_cb(gpointer data, gint id) | |
902 { | |
903 PeerConnection *conn; | |
904 | |
905 conn = data; | |
906 | |
907 conn->flags |= PEER_CONNECTION_FLAG_APPROVED; | |
908 peer_connection_trynext(conn); | |
909 } | |
910 | |
911 /** | |
912 * Someone else wants to establish a peer connection with us, | |
913 * and we said no. | |
914 * | |
915 * "Well, one time my friend asked me if I wanted to play the | |
916 * piccolo. But I said no." | |
917 */ | |
918 static void | |
919 peer_connection_got_proposition_no_cb(gpointer data, gint id) | |
920 { | |
921 PeerConnection *conn; | |
922 | |
923 conn = data; | |
924 | |
925 aim_im_denytransfer(conn->od, conn->sn, conn->cookie, | |
926 AIM_TRANSFER_DENY_DECLINE); | |
927 peer_connection_destroy(conn, OSCAR_DISCONNECT_LOCAL_CLOSED, NULL); | |
928 } | |
929 | |
930 /** | |
931 * Someone else wants to establish a peer connection with us. | |
932 */ | |
933 void | |
934 peer_connection_got_proposition(OscarData *od, const gchar *sn, const gchar *message, IcbmArgsCh2 *args) | |
935 { | |
936 GaimConnection *gc; | |
937 GaimAccount *account; | |
938 PeerConnection *conn; | |
939 gchar *buf; | |
940 | |
941 gc = od->gc; | |
942 account = gaim_connection_get_account(gc); | |
943 | |
944 /* | |
945 * If we have a connection with this same cookie then they are | |
946 * probably just telling us they weren't able to connect to us | |
947 * and we should try connecting to them, instead. Or they want | |
948 * to go through a proxy. | |
949 */ | |
950 conn = peer_connection_find_by_cookie(od, sn, args->cookie); | |
951 if ((conn != NULL) && (conn->type == args->type)) | |
952 { | |
953 gaim_debug_info("oscar", "Remote user wants to try a " | |
954 "different connection method\n"); | |
955 g_free(conn->proxyip); | |
956 g_free(conn->clientip); | |
957 g_free(conn->verifiedip); | |
958 if (args->use_proxy) | |
959 conn->proxyip = g_strdup(args->proxyip); | |
960 else | |
961 conn->proxyip = NULL; | |
962 conn->verifiedip = g_strdup(args->verifiedip); | |
963 conn->clientip = g_strdup(args->clientip); | |
964 conn->port = args->port; | |
965 conn->use_proxy |= args->use_proxy; | |
966 conn->lastrequestnumber++; | |
967 peer_connection_trynext(conn); | |
968 return; | |
969 } | |
970 | |
971 /* If this is a direct IM, then close any existing session */ | |
972 if (args->type == OSCAR_CAPABILITY_DIRECTIM) | |
973 { | |
974 conn = peer_connection_find_by_type(od, sn, args->type); | |
975 if (conn != NULL) | |
976 { | |
977 /* Close the old direct IM and start a new one */ | |
978 gaim_debug_info("oscar", "Received new direct IM request " | |
979 "from %s. Destroying old connection.\n", sn); | |
980 peer_connection_destroy(conn, OSCAR_DISCONNECT_REMOTE_CLOSED, NULL); | |
981 } | |
982 } | |
983 | |
984 /* Check for proper arguments */ | |
985 if (args->type == OSCAR_CAPABILITY_SENDFILE) | |
986 { | |
987 if ((args->info.sendfile.filename == NULL) || | |
988 (args->info.sendfile.totsize == 0) || | |
989 (args->info.sendfile.totfiles == 0)) | |
990 { | |
991 gaim_debug_warning("oscar", | |
992 "%s tried to send you a file with incomplete " | |
993 "information.\n", sn); | |
994 return; | |
995 } | |
996 } | |
997 | |
998 conn = peer_connection_new(od, args->type, sn); | |
999 memcpy(conn->cookie, args->cookie, 8); | |
1000 if (args->use_proxy) | |
1001 conn->proxyip = g_strdup(args->proxyip); | |
1002 conn->clientip = g_strdup(args->clientip); | |
1003 conn->verifiedip = g_strdup(args->verifiedip); | |
1004 conn->port = args->port; | |
1005 conn->use_proxy |= args->use_proxy; | |
1006 conn->lastrequestnumber++; | |
1007 | |
1008 if (args->type == OSCAR_CAPABILITY_DIRECTIM) | |
1009 { | |
1010 buf = g_strdup_printf(_("%s has just asked to directly connect to %s"), | |
1011 sn, gaim_account_get_username(account)); | |
1012 | |
1013 gaim_request_action(conn, NULL, buf, | |
1014 _("This requires a direct connection between " | |
1015 "the two computers and is necessary for IM " | |
1016 "Images. Because your IP address will be " | |
1017 "revealed, this may be considered a privacy " | |
1018 "risk."), | |
1019 GAIM_DEFAULT_ACTION_NONE, conn, 2, | |
1020 _("_Connect"), G_CALLBACK(peer_connection_got_proposition_yes_cb), | |
1021 _("Cancel"), G_CALLBACK(peer_connection_got_proposition_no_cb)); | |
1022 } | |
1023 else if (args->type == OSCAR_CAPABILITY_SENDFILE) | |
1024 { | |
1025 gchar *filename; | |
1026 | |
1027 conn->xfer = gaim_xfer_new(account, GAIM_XFER_RECEIVE, sn); | |
1028 if (conn->xfer) | |
1029 { | |
1030 conn->xfer->data = conn; | |
1031 gaim_xfer_ref(conn->xfer); | |
1032 gaim_xfer_set_size(conn->xfer, args->info.sendfile.totsize); | |
1033 | |
1034 /* Set the file name */ | |
1035 if (g_utf8_validate(args->info.sendfile.filename, -1, NULL)) | |
1036 filename = g_strdup(args->info.sendfile.filename); | |
1037 else | |
1038 filename = gaim_utf8_salvage(args->info.sendfile.filename); | |
1039 | |
1040 if (args->info.sendfile.subtype == AIM_OFT_SUBTYPE_SEND_DIR) | |
1041 { | |
1042 /* | |
1043 * If they are sending us a directory then the last character | |
1044 * of the file name will be an asterisk. We don't want to | |
1045 * save stuff to a directory named "*" so we remove the | |
1046 * asterisk from the file name. | |
1047 */ | |
1048 char *tmp = strrchr(filename, '\\'); | |
1049 if ((tmp != NULL) && (tmp[1] == '*')) | |
1050 tmp[0] = '\0'; | |
1051 } | |
1052 gaim_xfer_set_filename(conn->xfer, filename); | |
1053 g_free(filename); | |
1054 | |
1055 /* | |
1056 * Set the message, unless this is the dummy message from an | |
1057 * ICQ client or an empty message from an AIM client. | |
1058 * TODO: Maybe we should strip HTML and then see if strlen>0? | |
1059 */ | |
1060 if ((message != NULL) && | |
1061 (g_ascii_strncasecmp(message, "<ICQ_COOL_FT>", 13) != 0) && | |
1062 (g_ascii_strcasecmp(message, "<HTML>") != 0)) | |
1063 { | |
1064 gaim_xfer_set_message(conn->xfer, message); | |
1065 } | |
1066 | |
1067 /* Setup our I/O op functions */ | |
1068 gaim_xfer_set_init_fnc(conn->xfer, peer_oft_recvcb_init); | |
1069 gaim_xfer_set_end_fnc(conn->xfer, peer_oft_recvcb_end); | |
1070 gaim_xfer_set_request_denied_fnc(conn->xfer, peer_oft_cb_generic_cancel); | |
1071 gaim_xfer_set_cancel_recv_fnc(conn->xfer, peer_oft_cb_generic_cancel); | |
1072 gaim_xfer_set_ack_fnc(conn->xfer, peer_oft_recvcb_ack_recv); | |
1073 | |
1074 /* Now perform the request */ | |
1075 gaim_xfer_request(conn->xfer); | |
1076 } | |
1077 } | |
1078 } | |
1079 | |
1080 /*******************************************************************/ | |
1081 /* End code for establishing a peer connection */ | |
1082 /*******************************************************************/ |