comparison libgaim/protocols/oscar/peer.c @ 14192:60b1bc8dbf37

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