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