Annotation of ChivanetAimPidgin/oscarprpl/src/c/peer.c, revision 1.1
1.1 ! snw 1: /*
! 2: * Purple'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., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 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 Purple */
! 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 *bn, guint64 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) && !oscar_util_name_compare(conn->bn, bn))
! 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 *bn, 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) && !oscar_util_name_compare(conn->bn, bn))
! 100: return conn;
! 101: }
! 102:
! 103: return NULL;
! 104: }
! 105:
! 106: PeerConnection *
! 107: peer_connection_new(OscarData *od, guint64 type, const char *bn)
! 108: {
! 109: PeerConnection *conn;
! 110: PurpleAccount *account;
! 111:
! 112: account = purple_connection_get_account(od->gc);
! 113:
! 114: conn = g_new0(PeerConnection, 1);
! 115: conn->od = od;
! 116: conn->type = type;
! 117: conn->bn = g_strdup(bn);
! 118: conn->buffer_outgoing = purple_circ_buffer_new(0);
! 119: conn->listenerfd = -1;
! 120: conn->fd = -1;
! 121: conn->lastactivity = time(NULL);
! 122: conn->use_proxy |= purple_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: purple_proxy_connect_cancel(conn->verified_connect_data);
! 145: conn->verified_connect_data = NULL;
! 146: }
! 147:
! 148: if (conn->client_connect_data != NULL)
! 149: {
! 150: purple_proxy_connect_cancel(conn->client_connect_data);
! 151: conn->client_connect_data = NULL;
! 152: }
! 153:
! 154: if (conn->listen_data != NULL)
! 155: {
! 156: purple_network_listen_cancel(conn->listen_data);
! 157: conn->listen_data = NULL;
! 158: }
! 159:
! 160: if (conn->connect_timeout_timer != 0)
! 161: {
! 162: purple_timeout_remove(conn->connect_timeout_timer);
! 163: conn->connect_timeout_timer = 0;
! 164: }
! 165:
! 166: if (conn->watcher_incoming != 0)
! 167: {
! 168: purple_input_remove(conn->watcher_incoming);
! 169: conn->watcher_incoming = 0;
! 170: }
! 171: if (conn->watcher_outgoing != 0)
! 172: {
! 173: purple_input_remove(conn->watcher_outgoing);
! 174: conn->watcher_outgoing = 0;
! 175: }
! 176: if (conn->listenerfd >= 0)
! 177: {
! 178: close(conn->listenerfd);
! 179: conn->listenerfd = -1;
! 180: }
! 181: if (conn->fd >= 0)
! 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: purple_circ_buffer_destroy(conn->buffer_outgoing);
! 193: conn->buffer_outgoing = purple_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: purple_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: PurpleXferStatusType status;
! 215: conn->xfer->data = NULL;
! 216: status = purple_xfer_get_status(conn->xfer);
! 217: if ((status != PURPLE_XFER_STATUS_DONE) &&
! 218: (status != PURPLE_XFER_STATUS_CANCEL_LOCAL) &&
! 219: (status != PURPLE_XFER_STATUS_CANCEL_REMOTE))
! 220: {
! 221: if ((conn->disconnect_reason == OSCAR_DISCONNECT_REMOTE_CLOSED) ||
! 222: (conn->disconnect_reason == OSCAR_DISCONNECT_REMOTE_REFUSED))
! 223: purple_xfer_cancel_remote(conn->xfer);
! 224: else
! 225: purple_xfer_cancel_local(conn->xfer);
! 226: }
! 227: purple_xfer_unref(conn->xfer);
! 228: conn->xfer = NULL;
! 229: }
! 230:
! 231: g_free(conn->bn);
! 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: purple_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: purple_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: purple_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 = purple_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, PurpleInputCondition cond)
! 292: {
! 293: PeerConnection *conn;
! 294: gssize 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 < 0)
! 314: {
! 315: if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
! 316: /* No worries */
! 317: return;
! 318:
! 319: peer_connection_destroy(conn,
! 320: OSCAR_DISCONNECT_LOST_CONNECTION, g_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: purple_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 < 0)
! 364: {
! 365: if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
! 366: /* No worries */
! 367: return;
! 368:
! 369: peer_connection_destroy(conn,
! 370: OSCAR_DISCONNECT_LOST_CONNECTION, g_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, PurpleInputCondition cond)
! 407: {
! 408: PeerConnection *conn;
! 409: gsize writelen;
! 410: gssize wrotelen;
! 411:
! 412: conn = data;
! 413: writelen = purple_circ_buffer_get_max_read(conn->buffer_outgoing);
! 414:
! 415: if (writelen == 0)
! 416: {
! 417: purple_input_remove(conn->watcher_outgoing);
! 418: conn->watcher_outgoing = 0;
! 419: /*
! 420: * The buffer is currently empty, so reset the current input
! 421: * and output positions to the start of the buffer. We do
! 422: * this so that the next chunk of data that we put into the
! 423: * buffer can be read back out of the buffer in one fell swoop.
! 424: * Otherwise it gets fragmented and we have to read from the
! 425: * second half of the buffer than go back and read the rest of
! 426: * the chunk from the first half.
! 427: *
! 428: * We're using TCP, which is a stream based protocol, so this
! 429: * isn't supposed to matter. However, experience has shown
! 430: * that at least the proxy file transfer code in AIM 6.1.41.2
! 431: * requires that the entire OFT frame arrive all at once. If
! 432: * the frame is fragmented then AIM freaks out and aborts the
! 433: * file transfer. Somebody should teach those guys how to
! 434: * write good TCP code.
! 435: */
! 436: conn->buffer_outgoing->inptr = conn->buffer_outgoing->buffer;
! 437: conn->buffer_outgoing->outptr = conn->buffer_outgoing->buffer;
! 438: return;
! 439: }
! 440:
! 441: wrotelen = send(conn->fd, conn->buffer_outgoing->outptr, writelen, 0);
! 442: if (wrotelen <= 0)
! 443: {
! 444: if (wrotelen < 0 && ((errno == EAGAIN) || (errno == EWOULDBLOCK)))
! 445: /* No worries */
! 446: return;
! 447:
! 448: if (conn->ready)
! 449: {
! 450: purple_input_remove(conn->watcher_outgoing);
! 451: conn->watcher_outgoing = 0;
! 452: close(conn->fd);
! 453: conn->fd = -1;
! 454: peer_connection_schedule_destroy(conn,
! 455: OSCAR_DISCONNECT_LOST_CONNECTION, NULL);
! 456: }
! 457: else
! 458: {
! 459: /*
! 460: * This could happen when unable to send a negotiation
! 461: * frame to a peer proxy server.
! 462: */
! 463: peer_connection_trynext(conn);
! 464: }
! 465: return;
! 466: }
! 467:
! 468: purple_circ_buffer_mark_read(conn->buffer_outgoing, wrotelen);
! 469: conn->lastactivity = time(NULL);
! 470: }
! 471:
! 472: /**
! 473: * This should be called by OFT/ODC code to send a standard OFT or ODC
! 474: * frame across the peer connection along with some payload data. Or
! 475: * maybe a file. Anything, really.
! 476: */
! 477: void
! 478: peer_connection_send(PeerConnection *conn, ByteStream *bs)
! 479: {
! 480: /* Add everything to our outgoing buffer */
! 481: purple_circ_buffer_append(conn->buffer_outgoing, bs->data, bs->len);
! 482:
! 483: /* If we haven't already started writing stuff, then start the cycle */
! 484: if ((conn->watcher_outgoing == 0) && (conn->fd >= 0))
! 485: {
! 486: conn->watcher_outgoing = purple_input_add(conn->fd,
! 487: PURPLE_INPUT_WRITE, send_cb, conn);
! 488: send_cb(conn, conn->fd, 0);
! 489: }
! 490: }
! 491:
! 492: /*******************************************************************/
! 493: /* End code for sending data on a peer connection */
! 494: /*******************************************************************/
! 495:
! 496: /*******************************************************************/
! 497: /* Begin code for establishing a peer connection */
! 498: /*******************************************************************/
! 499:
! 500: void
! 501: peer_connection_finalize_connection(PeerConnection *conn)
! 502: {
! 503: conn->watcher_incoming = purple_input_add(conn->fd,
! 504: PURPLE_INPUT_READ, peer_connection_recv_cb, conn);
! 505:
! 506: if (conn->type == OSCAR_CAPABILITY_DIRECTIM)
! 507: {
! 508: /*
! 509: * If we are connecting to them then send our cookie so they
! 510: * can verify who we are. Note: This doesn't seem to be
! 511: * necessary, but it also doesn't seem to hurt.
! 512: */
! 513: if (!(conn->flags & PEER_CONNECTION_FLAG_IS_INCOMING))
! 514: peer_odc_send_cookie(conn);
! 515: }
! 516: else if (conn->type == OSCAR_CAPABILITY_SENDFILE)
! 517: {
! 518: if (purple_xfer_get_type(conn->xfer) == PURPLE_XFER_SEND)
! 519: {
! 520: peer_oft_send_prompt(conn);
! 521: }
! 522: }
! 523:
! 524: /*
! 525: * Tell the remote user that we're connected (which may also imply
! 526: * that we've accepted their request).
! 527: */
! 528: if (!(conn->flags & PEER_CONNECTION_FLAG_IS_INCOMING))
! 529: aim_im_sendch2_connected(conn);
! 530: }
! 531:
! 532: /**
! 533: * We tried to make an outgoing connection to a remote user. It
! 534: * either connected or failed to connect.
! 535: */
! 536: static void
! 537: peer_connection_common_established_cb(gpointer data, gint source, const gchar *error_message, gboolean verified)
! 538: {
! 539: PeerConnection *conn;
! 540:
! 541: conn = data;
! 542:
! 543: if (verified)
! 544: conn->verified_connect_data = NULL;
! 545: else
! 546: conn->client_connect_data = NULL;
! 547:
! 548: if (source < 0)
! 549: {
! 550: if ((conn->verified_connect_data == NULL) &&
! 551: (conn->client_connect_data == NULL))
! 552: {
! 553: /* Our parallel connection attemps have both failed. */
! 554: peer_connection_trynext(conn);
! 555: }
! 556: return;
! 557: }
! 558:
! 559: purple_timeout_remove(conn->connect_timeout_timer);
! 560: conn->connect_timeout_timer = 0;
! 561:
! 562: if (conn->client_connect_data != NULL)
! 563: {
! 564: purple_proxy_connect_cancel(conn->client_connect_data);
! 565: conn->client_connect_data = NULL;
! 566: }
! 567:
! 568: if (conn->verified_connect_data != NULL)
! 569: {
! 570: purple_proxy_connect_cancel(conn->verified_connect_data);
! 571: conn->verified_connect_data = NULL;
! 572: }
! 573:
! 574: conn->fd = source;
! 575:
! 576: peer_connection_finalize_connection(conn);
! 577: }
! 578:
! 579: static void
! 580: peer_connection_verified_established_cb(gpointer data, gint source, const gchar *error_message)
! 581: {
! 582: peer_connection_common_established_cb(data, source, error_message, TRUE);
! 583: }
! 584:
! 585: static void
! 586: peer_connection_client_established_cb(gpointer data, gint source, const gchar *error_message)
! 587: {
! 588: peer_connection_common_established_cb(data, source, error_message, FALSE);
! 589: }
! 590:
! 591: /**
! 592: * This is the watcher callback for any listening socket that is
! 593: * waiting for a peer to connect. When a peer connects we set the
! 594: * input watcher to start reading data from the peer.
! 595: *
! 596: * To make sure that the connection is with the intended person and
! 597: * not with a malicious middle man, we don't send anything until we've
! 598: * received a peer frame from the remote user and have verified that
! 599: * the cookie in the peer frame matches the cookie that was exchanged
! 600: * in the channel 2 ICBM.
! 601: */
! 602: void
! 603: peer_connection_listen_cb(gpointer data, gint source, PurpleInputCondition cond)
! 604: {
! 605: PeerConnection *conn;
! 606: struct sockaddr addr;
! 607: socklen_t addrlen = sizeof(addr);
! 608:
! 609: conn = data;
! 610:
! 611: purple_debug_info("oscar", "Accepting connection on listener socket.\n");
! 612:
! 613: conn->fd = accept(conn->listenerfd, &addr, &addrlen);
! 614: if (conn->fd < 0)
! 615: {
! 616: if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
! 617: /* No connection yet--no worries */
! 618: /* TODO: Hmm, but they SHOULD be connected if we're here, right? */
! 619: return;
! 620:
! 621: peer_connection_trynext(conn);
! 622: return;
! 623: }
! 624:
! 625: if ((addr.sa_family != PF_INET) && (addr.sa_family != PF_INET6))
! 626: {
! 627: /* Invalid connection type?! Continue waiting. */
! 628: close(conn->fd);
! 629: return;
! 630: }
! 631:
! 632: //_purple_network_set_common_socket_flags(conn->fd); // TODO: Not public? Check
! 633:
! 634: purple_input_remove(conn->watcher_incoming);
! 635:
! 636: peer_connection_finalize_connection(conn);
! 637: }
! 638:
! 639: /**
! 640: * We've just opened a listener socket, so we send the remote
! 641: * user an ICBM and ask them to connect to us.
! 642: */
! 643: static void
! 644: peer_connection_establish_listener_cb(int listenerfd, gpointer data)
! 645: {
! 646: PeerConnection *conn;
! 647: OscarData *od;
! 648: PurpleConnection *gc;
! 649: PurpleAccount *account;
! 650: PurpleConversation *conv;
! 651: char *tmp;
! 652: FlapConnection *bos_conn;
! 653: const char *listener_ip;
! 654: const guchar *ip_atoi;
! 655: unsigned short listener_port;
! 656:
! 657: conn = data;
! 658: conn->listen_data = NULL;
! 659:
! 660: if (listenerfd < 0)
! 661: {
! 662: /* Could not open listener socket */
! 663: peer_connection_trynext(conn);
! 664: return;
! 665: }
! 666:
! 667: od = conn->od;
! 668: gc = od->gc;
! 669: account = purple_connection_get_account(gc);
! 670: conn->listenerfd = listenerfd;
! 671:
! 672: /* Watch for new connections on our listener socket */
! 673: conn->watcher_incoming = purple_input_add(conn->listenerfd,
! 674: PURPLE_INPUT_READ, peer_connection_listen_cb, conn);
! 675:
! 676: /* Send the "please connect to me!" ICBM */
! 677: bos_conn = flap_connection_findbygroup(od, SNAC_FAMILY_ICBM);
! 678: if (bos_conn == NULL)
! 679: {
! 680: /* Not good */
! 681: peer_connection_trynext(conn);
! 682: return;
! 683: }
! 684:
! 685: if (bos_conn->gsc)
! 686: listener_ip = purple_network_get_my_ip(bos_conn->gsc->fd);
! 687: else
! 688: listener_ip = purple_network_get_my_ip(bos_conn->fd);
! 689:
! 690: ip_atoi = purple_network_ip_atoi(listener_ip);
! 691: if (ip_atoi == NULL) {
! 692: /* Could not convert IP to 4 byte array--weird, but this does
! 693: happen for some users (#4829, Adium #15839). Maybe they're
! 694: connecting with IPv6...? Maybe through a proxy? */
! 695: purple_debug_error("oscar", "Can't ask peer to connect to us "
! 696: "because purple_network_ip_atoi(%s) returned NULL. "
! 697: "fd=%d. is_ssl=%d\n",
! 698: listener_ip ? listener_ip : "(null)",
! 699: bos_conn->gsc ? bos_conn->gsc->fd : bos_conn->fd,
! 700: bos_conn->gsc ? 1 : 0);
! 701: peer_connection_trynext(conn);
! 702: return;
! 703: }
! 704:
! 705: listener_port = purple_network_get_port_from_fd(conn->listenerfd);
! 706:
! 707: if (conn->type == OSCAR_CAPABILITY_DIRECTIM)
! 708: {
! 709: aim_im_sendch2_odc_requestdirect(od,
! 710: conn->cookie, conn->bn, ip_atoi,
! 711: listener_port, ++conn->lastrequestnumber);
! 712:
! 713: /* Print a message to a local conversation window */
! 714: conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, conn->bn);
! 715: tmp = g_strdup_printf(_("Asking %s to connect to us at %s:%hu for "
! 716: "Direct IM."), conn->bn, listener_ip, listener_port);
! 717: purple_conversation_write(conv, NULL, tmp, PURPLE_MESSAGE_SYSTEM, time(NULL));
! 718: g_free(tmp);
! 719: }
! 720: else if (conn->type == OSCAR_CAPABILITY_SENDFILE)
! 721: {
! 722: aim_im_sendch2_sendfile_requestdirect(od,
! 723: conn->cookie, conn->bn,
! 724: ip_atoi,
! 725: listener_port, ++conn->lastrequestnumber,
! 726: (const gchar *)conn->xferdata.name,
! 727: conn->xferdata.size, conn->xferdata.totfiles);
! 728: }
! 729: }
! 730:
! 731: /**
! 732: * This is a callback function used when we're connecting to a peer
! 733: * using either the client IP or the verified IP and the connection
! 734: * took longer than 5 seconds to complete. We do this because
! 735: * waiting for the OS to time out the connection attempt is not
! 736: * practical--the default timeout on many OSes can be 3 minutes or
! 737: * more, and users are impatient.
! 738: *
! 739: * Worst case scenario: the user is connected to the Internet using
! 740: * a modem with severe lag. The peer connections fail and Purple falls
! 741: * back to using a proxied connection. The lower bandwidth
! 742: * limitations imposed by the proxied connection won't matter because
! 743: * the user is using a modem.
! 744: *
! 745: * I suppose this line of thinking is discriminatory against people
! 746: * with very high lag but decent throughput who are transferring
! 747: * large files. But we don't care about those people.
! 748: *
! 749: * I (Sean) changed the timeout from 15 to 5 seconds, as 60 seconds is
! 750: * too long for a user to wait to send a file. I'm also parallelizing
! 751: * requests when possible. The longest we should have to wait now is 10
! 752: * seconds. We shouldn't make it shorter than this.
! 753: */
! 754: static gboolean
! 755: peer_connection_tooktoolong(gpointer data)
! 756: {
! 757: PeerConnection *conn;
! 758:
! 759: conn = data;
! 760:
! 761: purple_debug_info("oscar", "Peer connection timed out after 5 seconds. "
! 762: "Trying next method...\n");
! 763:
! 764: peer_connection_trynext(conn);
! 765:
! 766: /* Cancel this timer. It'll be added again, if needed. */
! 767: return FALSE;
! 768: }
! 769:
! 770: /**
! 771: * Try to establish the given PeerConnection using a defined
! 772: * sequence of steps.
! 773: */
! 774: void
! 775: peer_connection_trynext(PeerConnection *conn)
! 776: {
! 777: PurpleAccount *account;
! 778:
! 779: account = purple_connection_get_account(conn->od->gc);
! 780:
! 781: /*
! 782: * Close any remnants of a previous failed connection attempt.
! 783: */
! 784: peer_connection_close(conn);
! 785:
! 786: /*
! 787: * 1. Attempt to connect to the remote user using their verifiedip and clientip.
! 788: * We try these at the same time and use whichever succeeds first, so we don't
! 789: * have to wait for a timeout.
! 790: */
! 791: if (!(conn->flags & PEER_CONNECTION_FLAG_TRIED_DIRECT) &&
! 792: (conn->verifiedip != NULL) && (conn->port != 0) && (!conn->use_proxy))
! 793: {
! 794: conn->flags |= PEER_CONNECTION_FLAG_TRIED_DIRECT;
! 795:
! 796: if (conn->type == OSCAR_CAPABILITY_DIRECTIM)
! 797: {
! 798: gchar *tmp;
! 799: PurpleConversation *conv;
! 800: tmp = g_strdup_printf(_("Attempting to connect to %s:%hu."),
! 801: conn->verifiedip, conn->port);
! 802: conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, conn->bn);
! 803: purple_conversation_write(conv, NULL, tmp,
! 804: PURPLE_MESSAGE_SYSTEM, time(NULL));
! 805: g_free(tmp);
! 806: }
! 807:
! 808: conn->verified_connect_data = purple_proxy_connect(NULL, account,
! 809: conn->verifiedip, conn->port,
! 810: peer_connection_verified_established_cb, conn);
! 811:
! 812: if ((conn->verifiedip == NULL) ||
! 813: !purple_strequal(conn->verifiedip, conn->clientip))
! 814: {
! 815: conn->client_connect_data = purple_proxy_connect(NULL, account,
! 816: conn->clientip, conn->port,
! 817: peer_connection_client_established_cb, conn);
! 818: }
! 819:
! 820: if ((conn->verified_connect_data != NULL) ||
! 821: (conn->client_connect_data != NULL))
! 822: {
! 823: /* Connecting... */
! 824: conn->connect_timeout_timer = purple_timeout_add_seconds(5,
! 825: peer_connection_tooktoolong, conn);
! 826: return;
! 827: }
! 828: }
! 829:
! 830: /*
! 831: * 2. Attempt to have the remote user connect to us (using both
! 832: * our verifiedip and our clientip).
! 833: */
! 834: if (!(conn->flags & PEER_CONNECTION_FLAG_TRIED_INCOMING) &&
! 835: (!conn->use_proxy))
! 836: {
! 837: conn->flags |= PEER_CONNECTION_FLAG_TRIED_INCOMING;
! 838:
! 839: /*
! 840: * Remote user is connecting to us, so we'll need to verify
! 841: * that the user who connected is our friend.
! 842: */
! 843: conn->flags |= PEER_CONNECTION_FLAG_IS_INCOMING;
! 844:
! 845: conn->listen_data = purple_network_listen_range(5190, 5290, SOCK_STREAM,
! 846: peer_connection_establish_listener_cb, conn);
! 847: if (conn->listen_data != NULL)
! 848: {
! 849: /* Opening listener socket... */
! 850: return;
! 851: }
! 852: }
! 853:
! 854: /*
! 855: * 3. Attempt to have both users connect to an intermediate proxy
! 856: * server.
! 857: */
! 858: if (!(conn->flags & PEER_CONNECTION_FLAG_TRIED_PROXY))
! 859: {
! 860: conn->flags |= PEER_CONNECTION_FLAG_TRIED_PROXY;
! 861:
! 862: /*
! 863: * If we initiate the proxy connection, then the remote user
! 864: * could be anyone, so we need to verify that the user who
! 865: * connected is our friend.
! 866: */
! 867: if (!conn->use_proxy)
! 868: conn->flags |= PEER_CONNECTION_FLAG_IS_INCOMING;
! 869:
! 870: if (conn->type == OSCAR_CAPABILITY_DIRECTIM)
! 871: {
! 872: gchar *tmp;
! 873: PurpleConversation *conv;
! 874: tmp = g_strdup(_("Attempting to connect via proxy server."));
! 875: conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, conn->bn);
! 876: purple_conversation_write(conv, NULL, tmp,
! 877: PURPLE_MESSAGE_SYSTEM, time(NULL));
! 878: g_free(tmp);
! 879: }
! 880:
! 881: conn->verified_connect_data = purple_proxy_connect(NULL, account,
! 882: (conn->proxyip != NULL)
! 883: ? conn->proxyip
! 884: : (conn->od->icq ? ICQ_PEER_PROXY_SERVER : AIM_PEER_PROXY_SERVER),
! 885: PEER_PROXY_PORT,
! 886: peer_proxy_connection_established_cb, conn);
! 887: if (conn->verified_connect_data != NULL)
! 888: {
! 889: /* Connecting... */
! 890: return;
! 891: }
! 892: }
! 893:
! 894: /* Give up! */
! 895: peer_connection_destroy(conn, OSCAR_DISCONNECT_COULD_NOT_CONNECT, NULL);
! 896: }
! 897:
! 898: /**
! 899: * Initiate a peer connection with someone.
! 900: */
! 901: void
! 902: peer_connection_propose(OscarData *od, guint64 type, const char *bn)
! 903: {
! 904: PeerConnection *conn;
! 905:
! 906: if (type == OSCAR_CAPABILITY_DIRECTIM)
! 907: {
! 908: conn = peer_connection_find_by_type(od, bn, type);
! 909: if (conn != NULL)
! 910: {
! 911: if (conn->ready)
! 912: {
! 913: PurpleAccount *account;
! 914: PurpleConversation *conv;
! 915:
! 916: purple_debug_info("oscar", "Already have a direct IM "
! 917: "session with %s.\n", bn);
! 918: account = purple_connection_get_account(od->gc);
! 919: conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM,
! 920: bn, account);
! 921: if (conv != NULL)
! 922: purple_conversation_present(conv);
! 923: return;
! 924: }
! 925:
! 926: /* Cancel the old connection and try again */
! 927: peer_connection_destroy(conn, OSCAR_DISCONNECT_RETRYING, NULL);
! 928: }
! 929: }
! 930:
! 931: conn = peer_connection_new(od, type, bn);
! 932: conn->flags |= PEER_CONNECTION_FLAG_INITIATED_BY_ME;
! 933: conn->flags |= PEER_CONNECTION_FLAG_APPROVED;
! 934: aim_icbm_makecookie(conn->cookie);
! 935:
! 936: peer_connection_trynext(conn);
! 937: }
! 938:
! 939: /**
! 940: * Someone else wants to establish a peer connection with us,
! 941: * and we said yes.
! 942: */
! 943: static void
! 944: peer_connection_got_proposition_yes_cb(gpointer data, gint id)
! 945: {
! 946: PeerConnection *conn;
! 947:
! 948: conn = data;
! 949:
! 950: conn->flags |= PEER_CONNECTION_FLAG_APPROVED;
! 951: peer_connection_trynext(conn);
! 952: }
! 953:
! 954: /**
! 955: * Someone else wants to establish a peer connection with us,
! 956: * and we said no.
! 957: *
! 958: * "Well, one time my friend asked me if I wanted to play the
! 959: * piccolo. But I said no."
! 960: */
! 961: static void
! 962: peer_connection_got_proposition_no_cb(gpointer data, gint id)
! 963: {
! 964: PeerConnection *conn;
! 965:
! 966: conn = data;
! 967:
! 968: aim_im_denytransfer(conn->od, conn->bn, conn->cookie,
! 969: AIM_TRANSFER_DENY_DECLINE);
! 970: peer_connection_destroy(conn, OSCAR_DISCONNECT_LOCAL_CLOSED, NULL);
! 971: }
! 972:
! 973: /**
! 974: * Someone else wants to establish a peer connection with us.
! 975: */
! 976: void
! 977: peer_connection_got_proposition(OscarData *od, const gchar *bn, const gchar *message, IcbmArgsCh2 *args)
! 978: {
! 979: PurpleConnection *gc;
! 980: PurpleAccount *account;
! 981: PeerConnection *conn;
! 982: gchar *buf;
! 983:
! 984: gc = od->gc;
! 985: account = purple_connection_get_account(gc);
! 986:
! 987: /*
! 988: * If we have a connection with this same cookie then they are
! 989: * probably just telling us they weren't able to connect to us
! 990: * and we should try connecting to them, instead. Or they want
! 991: * to go through a proxy.
! 992: */
! 993: conn = peer_connection_find_by_cookie(od, bn, args->cookie);
! 994: if ((conn != NULL) && (conn->type == args->type))
! 995: {
! 996: purple_debug_info("oscar", "Remote user wants to try a "
! 997: "different connection method\n");
! 998: g_free(conn->proxyip);
! 999: g_free(conn->clientip);
! 1000: g_free(conn->verifiedip);
! 1001: if (args->use_proxy)
! 1002: conn->proxyip = g_strdup(args->proxyip);
! 1003: else
! 1004: conn->proxyip = NULL;
! 1005: conn->verifiedip = g_strdup(args->verifiedip);
! 1006: conn->clientip = g_strdup(args->clientip);
! 1007: conn->port = args->port;
! 1008: conn->use_proxy |= args->use_proxy;
! 1009: conn->lastrequestnumber++;
! 1010: peer_connection_trynext(conn);
! 1011: return;
! 1012: }
! 1013:
! 1014: /* If this is a direct IM, then close any existing session */
! 1015: if (args->type == OSCAR_CAPABILITY_DIRECTIM)
! 1016: {
! 1017: conn = peer_connection_find_by_type(od, bn, args->type);
! 1018: if (conn != NULL)
! 1019: {
! 1020: /* Close the old direct IM and start a new one */
! 1021: purple_debug_info("oscar", "Received new direct IM request "
! 1022: "from %s. Destroying old connection.\n", bn);
! 1023: peer_connection_destroy(conn, OSCAR_DISCONNECT_REMOTE_CLOSED, NULL);
! 1024: }
! 1025: }
! 1026:
! 1027: /* Check for proper arguments */
! 1028: if (args->type == OSCAR_CAPABILITY_SENDFILE)
! 1029: {
! 1030: if ((args->info.sendfile.filename == NULL) ||
! 1031: (args->info.sendfile.totsize == 0) ||
! 1032: (args->info.sendfile.totfiles == 0))
! 1033: {
! 1034: purple_debug_warning("oscar",
! 1035: "%s tried to send you a file with incomplete "
! 1036: "information.\n", bn);
! 1037: return;
! 1038: }
! 1039: }
! 1040:
! 1041: conn = peer_connection_new(od, args->type, bn);
! 1042: memcpy(conn->cookie, args->cookie, 8);
! 1043: if (args->use_proxy)
! 1044: conn->proxyip = g_strdup(args->proxyip);
! 1045: conn->clientip = g_strdup(args->clientip);
! 1046: conn->verifiedip = g_strdup(args->verifiedip);
! 1047: conn->port = args->port;
! 1048: conn->use_proxy |= args->use_proxy;
! 1049: conn->lastrequestnumber++;
! 1050:
! 1051: if (args->type == OSCAR_CAPABILITY_DIRECTIM)
! 1052: {
! 1053: buf = g_strdup_printf(_("%s has just asked to directly connect to %s"),
! 1054: bn, purple_account_get_username(account));
! 1055:
! 1056: purple_request_action(conn, NULL, buf,
! 1057: _("This requires a direct connection between "
! 1058: "the two computers and is necessary for IM "
! 1059: "Images. Because your IP address will be "
! 1060: "revealed, this may be considered a privacy "
! 1061: "risk."),
! 1062: PURPLE_DEFAULT_ACTION_NONE,
! 1063: account, bn, NULL,
! 1064: conn, 2,
! 1065: _("C_onnect"), G_CALLBACK(peer_connection_got_proposition_yes_cb),
! 1066: _("Cancel"), G_CALLBACK(peer_connection_got_proposition_no_cb));
! 1067: }
! 1068: else if (args->type == OSCAR_CAPABILITY_SENDFILE)
! 1069: {
! 1070: gchar *filename;
! 1071:
! 1072: conn->xfer = purple_xfer_new(account, PURPLE_XFER_RECEIVE, bn);
! 1073: if (conn->xfer)
! 1074: {
! 1075: conn->xfer->data = conn;
! 1076: purple_xfer_ref(conn->xfer);
! 1077: purple_xfer_set_size(conn->xfer, args->info.sendfile.totsize);
! 1078:
! 1079: /* Set the file name */
! 1080: if (g_utf8_validate(args->info.sendfile.filename, -1, NULL))
! 1081: filename = g_strdup(args->info.sendfile.filename);
! 1082: else
! 1083: filename = purple_utf8_salvage(args->info.sendfile.filename);
! 1084:
! 1085: if (args->info.sendfile.subtype == AIM_OFT_SUBTYPE_SEND_DIR)
! 1086: {
! 1087: /*
! 1088: * If they are sending us a directory then the last character
! 1089: * of the file name will be an asterisk. We don't want to
! 1090: * save stuff to a directory named "*" so we remove the
! 1091: * asterisk from the file name.
! 1092: */
! 1093: char *tmp = strrchr(filename, '\\');
! 1094: if ((tmp != NULL) && (tmp[1] == '*'))
! 1095: tmp[0] = '\0';
! 1096: }
! 1097: purple_xfer_set_filename(conn->xfer, filename);
! 1098: g_free(filename);
! 1099:
! 1100: /*
! 1101: * Set the message, unless this is the dummy message from an
! 1102: * ICQ client or an empty message from an AIM client.
! 1103: * TODO: Maybe we should strip HTML and then see if strlen>0?
! 1104: */
! 1105: if ((message != NULL) &&
! 1106: (g_ascii_strncasecmp(message, "<ICQ_COOL_FT>", 13) != 0) &&
! 1107: (g_ascii_strcasecmp(message, "<HTML>") != 0))
! 1108: {
! 1109: purple_xfer_set_message(conn->xfer, message);
! 1110: }
! 1111:
! 1112: /* Setup our I/O op functions */
! 1113: purple_xfer_set_init_fnc(conn->xfer, peer_oft_recvcb_init);
! 1114: purple_xfer_set_end_fnc(conn->xfer, peer_oft_recvcb_end);
! 1115: purple_xfer_set_request_denied_fnc(conn->xfer, peer_oft_cb_generic_cancel);
! 1116: purple_xfer_set_cancel_recv_fnc(conn->xfer, peer_oft_cb_generic_cancel);
! 1117: purple_xfer_set_ack_fnc(conn->xfer, peer_oft_recvcb_ack_recv);
! 1118:
! 1119: /* Now perform the request */
! 1120: purple_xfer_request(conn->xfer);
! 1121: }
! 1122: }
! 1123: }
! 1124:
! 1125: /*******************************************************************/
! 1126: /* End code for establishing a peer connection */
! 1127: /*******************************************************************/
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>