Mercurial > pidgin
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libgaim/protocols/oscar/peer.c Sat Aug 19 01:50:10 2006 +0000 @@ -0,0 +1,1050 @@ +/* + * Gaim's oscar protocol plugin + * This file is the legal property of its developers. + * Please see the AUTHORS file distributed alongside this file. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/* + * Functions dealing with peer connections. This includes the code + * used to establish a peer connection for both Oscar File transfer + * (OFT) and Oscar Direct Connect (ODC). (ODC is also referred to + * as DirectIM and IM Image.) + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +/* From the oscar PRPL */ +#include "oscar.h" +#include "peer.h" + +/* From Gaim */ +#include "conversation.h" +#include "ft.h" +#include "network.h" +#include "notify.h" +#include "request.h" +#include "util.h" + +#ifndef _WIN32 +#include <stdio.h> +#include <netdb.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> /* for inet_ntoa */ +#include <limits.h> /* for UINT_MAX */ +#endif + +#ifdef _WIN32 +#include "win32dep.h" +#endif + +/* + * I really want to switch all our networking code to using IPv6 only, + * but that really isn't a good idea at all. Evan S. of Adium says + * OS X sets all connections as "AF_INET6/PF_INET6," even if there is + * nothing inherently IPv6 about them. And I feel like Linux kernel + * 2.6.5 is doing the same thing. So we REALLY should accept + * connections if they're showing up as IPv6. Old OSes (Solaris?) + * that might not have full IPv6 support yet will fail if we try + * to use PF_INET6 but it isn't defined. --Mark Doliner + */ +#ifndef PF_INET6 +#define PF_INET6 PF_INET +#endif + +PeerConnection * +peer_connection_find_by_type(OscarData *od, const char *sn, OscarCapability type) +{ + GList *cur; + PeerConnection *conn; + + for (cur = od->peer_connections; cur != NULL; cur = cur->next) + { + conn = cur->data; + if ((conn->type == type) && !aim_sncmp(conn->sn, sn)) + return conn; + } + + return NULL; +} + +/** + * @param cookie This must be exactly 8 characters. + */ +PeerConnection * +peer_connection_find_by_cookie(OscarData *od, const char *sn, const guchar *cookie) +{ + GList *cur; + PeerConnection *conn; + + for (cur = od->peer_connections; cur != NULL; cur = cur->next) + { + conn = cur->data; + if (!memcmp(conn->cookie, cookie, 8) && !aim_sncmp(conn->sn, sn)) + return conn; + } + + return NULL; +} + +PeerConnection * +peer_connection_new(OscarData *od, OscarCapability type, const char *sn) +{ + PeerConnection *conn; + GaimAccount *account; + + account = gaim_connection_get_account(od->gc); + + conn = g_new0(PeerConnection, 1); + conn->od = od; + conn->type = type; + conn->sn = g_strdup(sn); + conn->buffer_outgoing = gaim_circ_buffer_new(0); + conn->listenerfd = -1; + conn->fd = -1; + conn->lastactivity = time(NULL); + conn->use_proxy |= gaim_account_get_bool(account, "always_use_rv_proxy", FALSE); + + if (type == OSCAR_CAPABILITY_DIRECTIM) + memcpy(conn->magic, "ODC2", 4); + else if (type == OSCAR_CAPABILITY_SENDFILE) + memcpy(conn->magic, "OFT2", 4); + + od->peer_connections = g_list_prepend(od->peer_connections, conn); + + return conn; +} + +static void +peer_connection_close(PeerConnection *conn) +{ + if (conn->type == OSCAR_CAPABILITY_DIRECTIM) + peer_odc_close(conn); + else if (conn->type == OSCAR_CAPABILITY_SENDFILE) + peer_oft_close(conn); + + if (conn->connect_info != NULL) + { + gaim_proxy_connect_cancel(conn->connect_info); + conn->connect_info = NULL; + } + + if (conn->connect_timeout_timer != 0) + { + gaim_timeout_remove(conn->connect_timeout_timer); + conn->connect_timeout_timer = 0; + } + + if (conn->watcher_incoming != 0) + { + gaim_input_remove(conn->watcher_incoming); + conn->watcher_incoming = 0; + } + if (conn->watcher_outgoing != 0) + { + gaim_input_remove(conn->watcher_outgoing); + conn->watcher_outgoing = 0; + } + if (conn->listenerfd != -1) + { + close(conn->listenerfd); + conn->listenerfd = -1; + } + if (conn->fd != -1) + { + close(conn->fd); + conn->fd = -1; + } + + g_free(conn->buffer_incoming.data); + conn->buffer_incoming.data = NULL; + conn->buffer_incoming.len = 0; + conn->buffer_incoming.offset = 0; + + gaim_circ_buffer_destroy(conn->buffer_outgoing); + conn->buffer_outgoing = gaim_circ_buffer_new(0); + + conn->flags &= ~PEER_CONNECTION_FLAG_IS_INCOMING; +} + +static gboolean +peer_connection_destroy_cb(gpointer data) +{ + PeerConnection *conn; + + conn = data; + + gaim_request_close_with_handle(conn); + + peer_connection_close(conn); + + if (conn->xfer != NULL) + { + GaimXferStatusType status; + conn->xfer->data = NULL; + status = gaim_xfer_get_status(conn->xfer); + if ((status != GAIM_XFER_STATUS_DONE) && + (status != GAIM_XFER_STATUS_CANCEL_LOCAL) && + (status != GAIM_XFER_STATUS_CANCEL_REMOTE)) + { + if ((conn->disconnect_reason == OSCAR_DISCONNECT_REMOTE_CLOSED) || + (conn->disconnect_reason == OSCAR_DISCONNECT_REMOTE_REFUSED)) + gaim_xfer_cancel_remote(conn->xfer); + else + gaim_xfer_cancel_local(conn->xfer); + } + gaim_xfer_unref(conn->xfer); + conn->xfer = NULL; + } + + g_free(conn->proxyip); + g_free(conn->clientip); + g_free(conn->verifiedip); + gaim_circ_buffer_destroy(conn->buffer_outgoing); + + conn->od->peer_connections = g_list_remove(conn->od->peer_connections, conn); + + g_free(conn); + + return FALSE; +} + +void +peer_connection_destroy(PeerConnection *conn, OscarDisconnectReason reason) +{ + conn->disconnect_reason = reason; + if (conn->destroy_timeout != 0) + gaim_timeout_remove(conn->destroy_timeout); + peer_connection_destroy_cb(conn); +} + +void +peer_connection_schedule_destroy(PeerConnection *conn, OscarDisconnectReason reason) +{ + if (conn->destroy_timeout != 0) + /* Already taken care of */ + return; + + gaim_debug_info("oscar", "Scheduling destruction of peer connection\n"); + conn->disconnect_reason = reason; + conn->destroy_timeout = gaim_timeout_add(0, peer_connection_destroy_cb, conn); +} + +/*******************************************************************/ +/* Begin code for receiving data on a peer connection */ +/*******************************************************************/ + +/** + * This should be used to read ODC and OFT framing info. It should + * NOT be used to read the payload sent across the connection (IMs, + * file data, etc), and it should NOT be used to read proxy negotiation + * headers. + * + * Unlike flap_connection_recv_cb(), this only reads one frame at a + * time. This is done so that the watcher can be changed during the + * handling of the frame. If the watcher is changed then this + * function will not read in any more data. This happens when + * reading the payload of a direct IM frame, or when we're + * receiving a file from the remote user. Once the data has been + * read, the watcher will be switched back to this function to + * continue reading the next frame. + */ +void +peer_connection_recv_cb(gpointer data, gint source, GaimInputCondition cond) +{ + PeerConnection *conn; + ssize_t read; + guint8 header[6]; + + conn = data; + + /* Start reading a new ODC/OFT frame */ + if (conn->buffer_incoming.data == NULL) + { + /* Peek at the first 6 bytes to get the length */ + read = recv(conn->fd, &header, 6, MSG_PEEK); + + /* Check if the remote user closed the connection */ + if (read == 0) + { + peer_connection_destroy(conn, OSCAR_DISCONNECT_REMOTE_CLOSED); + return; + } + + /* If there was an error then close the connection */ + if (read == -1) + { + if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) + /* No worries */ + return; + + peer_connection_destroy(conn, OSCAR_DISCONNECT_LOST_CONNECTION); + return; + } + + conn->lastactivity = time(NULL); + + /* If we don't even have the first 6 bytes then do nothing */ + if (read < 6) + return; + + /* Read the first 6 bytes (magic string and frame length) */ + read = recv(conn->fd, &header, 6, 0); + + /* All ODC/OFT frames must start with a magic string */ + if (memcmp(conn->magic, header, 4)) + { + gaim_debug_warning("oscar", "Expecting magic string to " + "be %c%c%c%c but received magic string %c%c%c%c. " + "Closing connection.\n", + conn->magic[0], conn->magic[1], conn->magic[2], + conn->magic[3], header[0], header[1], header[2], header[3]); + peer_connection_destroy(conn, OSCAR_DISCONNECT_INVALID_DATA); + return; + } + + /* Initialize a new temporary ByteStream for incoming data */ + conn->buffer_incoming.len = aimutil_get16(&header[4]) - 6; + conn->buffer_incoming.data = g_new(guint8, conn->buffer_incoming.len); + conn->buffer_incoming.offset = 0; + } + + /* Read data into the temporary buffer until it is complete */ + read = recv(conn->fd, + &conn->buffer_incoming.data[conn->buffer_incoming.offset], + conn->buffer_incoming.len - conn->buffer_incoming.offset, + 0); + + /* Check if the remote user closed the connection */ + if (read == 0) + { + peer_connection_destroy(conn, OSCAR_DISCONNECT_REMOTE_CLOSED); + return; + } + + if (read == -1) + { + if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) + /* No worries */ + return; + + peer_connection_destroy(conn, OSCAR_DISCONNECT_LOST_CONNECTION); + return; + } + + conn->lastactivity = time(NULL); + conn->buffer_incoming.offset += read; + if (conn->buffer_incoming.offset < conn->buffer_incoming.len) + /* Waiting for more data to arrive */ + return; + + /* We have a complete ODC/OFT frame! Handle it and continue reading */ + byte_stream_rewind(&conn->buffer_incoming); + if (conn->type == OSCAR_CAPABILITY_DIRECTIM) + { + peer_odc_recv_frame(conn, &conn->buffer_incoming); + } + else if (conn->type == OSCAR_CAPABILITY_SENDFILE) + { + peer_oft_recv_frame(conn, &conn->buffer_incoming); + } + g_free(conn->buffer_incoming.data); + conn->buffer_incoming.data = NULL; +} + +/*******************************************************************/ +/* End code for receiving data on a peer connection */ +/*******************************************************************/ + +/*******************************************************************/ +/* Begin code for sending data on a peer connection */ +/*******************************************************************/ + +static void +send_cb(gpointer data, gint source, GaimInputCondition cond) +{ + PeerConnection *conn; + gsize writelen; + ssize_t wrotelen; + + conn = data; + writelen = gaim_circ_buffer_get_max_read(conn->buffer_outgoing); + + if (writelen == 0) + { + gaim_input_remove(conn->watcher_outgoing); + conn->watcher_outgoing = 0; + return; + } + + wrotelen = send(conn->fd, conn->buffer_outgoing->outptr, writelen, 0); + if (wrotelen <= 0) + { + if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) + /* No worries */ + return; + + if (conn->ready) + peer_connection_schedule_destroy(conn, OSCAR_DISCONNECT_LOST_CONNECTION); + else + { + /* + * This could happen when unable to send a negotiation + * frame to a peer proxy server. + */ + peer_connection_trynext(conn); + } + return; + } + + gaim_circ_buffer_mark_read(conn->buffer_outgoing, wrotelen); + conn->lastactivity = time(NULL); +} + +/** + * This should be called by OFT/ODC code to send a standard OFT or ODC + * frame across the peer connection along with some payload data. Or + * maybe a file. Anything, really. + */ +void +peer_connection_send(PeerConnection *conn, ByteStream *bs) +{ + /* Add everything to our outgoing buffer */ + gaim_circ_buffer_append(conn->buffer_outgoing, bs->data, bs->len); + + /* If we haven't already started writing stuff, then start the cycle */ + if (conn->watcher_outgoing == 0) + { + conn->watcher_outgoing = gaim_input_add(conn->fd, + GAIM_INPUT_WRITE, send_cb, conn); + send_cb(conn, conn->fd, 0); + } +} + +/*******************************************************************/ +/* End code for sending data on a peer connection */ +/*******************************************************************/ + +/*******************************************************************/ +/* Begin code for establishing a peer connection */ +/*******************************************************************/ + +void +peer_connection_finalize_connection(PeerConnection *conn) +{ + conn->watcher_incoming = gaim_input_add(conn->fd, + GAIM_INPUT_READ, peer_connection_recv_cb, conn); + + if (conn->type == OSCAR_CAPABILITY_DIRECTIM) + { + /* + * If we are connecting to them then send our cookie so they + * can verify who we are. Note: This doesn't seem to be + * necessary, but it also doesn't seem to hurt. + */ + if (!(conn->flags & PEER_CONNECTION_FLAG_IS_INCOMING)) + peer_odc_send_cookie(conn); + } + else if (conn->type == OSCAR_CAPABILITY_SENDFILE) + { + if (gaim_xfer_get_type(conn->xfer) == GAIM_XFER_SEND) + { + peer_oft_send_prompt(conn); + } + } + + /* + * Tell the remote user that we're connected (which may also imply + * that we've accepted their request). + */ + if (!(conn->flags & PEER_CONNECTION_FLAG_IS_INCOMING)) + aim_im_sendch2_connected(conn); +} + +/** + * We tried to make an outgoing connection to a remote user. It + * either connected or failed to connect. + */ +static void +peer_connection_established_cb(gpointer data, gint source, const gchar *error_message) +{ + PeerConnection *conn; + + conn = data; + + conn->connect_info = NULL; + gaim_timeout_remove(conn->connect_timeout_timer); + conn->connect_timeout_timer = 0; + + if (source < 0) + { + peer_connection_trynext(conn); + return; + } + + conn->fd = source; + + peer_connection_finalize_connection(conn); +} + +/** + * This is the watcher callback for any listening socket that is + * waiting for a peer to connect. When a peer connects we set the + * input watcher to start reading data from the peer. + * + * To make sure that the connection is with the intended person and + * not with a malicious middle man, we don't send anything until we've + * received a peer frame from the remote user and have verified that + * the cookie in the peer frame matches the cookie that was exchanged + * in the channel 2 ICBM. + */ +void +peer_connection_listen_cb(gpointer data, gint source, GaimInputCondition cond) +{ + PeerConnection *conn; + OscarData *od; + GaimConnection *gc; + struct sockaddr addr; + socklen_t addrlen = sizeof(addr); + + conn = data; + od = conn->od; + gc = od->gc; + + gaim_debug_info("oscar", "Accepting connection on listener socket.\n"); + + conn->fd = accept(conn->listenerfd, &addr, &addrlen); + if (conn->fd == -1) + { + if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) + /* No connection yet--no worries */ + /* TODO: Hmm, but they SHOULD be connected if we're here, right? */ + return; + + peer_connection_trynext(conn); + return; + } + + if ((addr.sa_family != PF_INET) && (addr.sa_family != PF_INET6)) + { + /* Invalid connection type?! Continue waiting. */ + close(conn->fd); + return; + } + + fcntl(conn->fd, F_SETFL, O_NONBLOCK); + gaim_input_remove(conn->watcher_incoming); + + peer_connection_finalize_connection(conn); +} + +/** + * We've just opened a listener socket, so we send the remote + * user an ICBM and ask them to connect to us. + */ +static void +peer_connection_establish_listener_cb(int listenerfd, gpointer data) +{ + NewPeerConnectionData *new_conn_data; + PeerConnection *conn; + OscarData *od; + GaimConnection *gc; + GaimAccount *account; + GaimConversation *conv; + char *tmp; + FlapConnection *bos_conn; + const char *listener_ip; + unsigned short listener_port; + + new_conn_data = data; + gc = new_conn_data->gc; + conn = new_conn_data->conn; + g_free(new_conn_data); + + if (!GAIM_CONNECTION_IS_VALID(gc)) + { + if (listenerfd != -1) + close(listenerfd); + return; + } + + if (listenerfd == -1) + { + /* Could not open listener socket */ + peer_connection_trynext(conn); + return; + } + + od = conn->od; + account = gaim_connection_get_account(gc); + conn->listenerfd = listenerfd; + + /* Send the "please connect to me!" ICBM */ + bos_conn = flap_connection_findbygroup(od, SNAC_FAMILY_ICBM); + if (bos_conn == NULL) + { + /* Not good */ + peer_connection_trynext(conn); + return; + } + + listener_ip = gaim_network_get_my_ip(bos_conn->fd); + listener_port = gaim_network_get_port_from_fd(conn->listenerfd); + if (conn->type == OSCAR_CAPABILITY_DIRECTIM) + { + aim_im_sendch2_odc_requestdirect(od, + conn->cookie, conn->sn, gaim_network_ip_atoi(listener_ip), + listener_port, ++conn->lastrequestnumber); + + /* Print a message to a local conversation window */ + conv = gaim_conversation_new(GAIM_CONV_TYPE_IM, account, conn->sn); + tmp = g_strdup_printf(_("Asking %s to connect to us at %s:%hu for " + "Direct IM."), conn->sn, listener_ip, listener_port); + gaim_conversation_write(conv, NULL, tmp, GAIM_MESSAGE_SYSTEM, time(NULL)); + g_free(tmp); + } + else if (conn->type == OSCAR_CAPABILITY_SENDFILE) + { + aim_im_sendch2_sendfile_requestdirect(od, + conn->cookie, conn->sn, + gaim_network_ip_atoi(listener_ip), + listener_port, ++conn->lastrequestnumber, + (const gchar *)conn->xferdata.name, + conn->xferdata.size, conn->xferdata.totfiles); + } +} + +/** + * This is a callback function used when we're connecting to a peer + * using either the client IP or the verified IP and the connection + * took longer than 15 seconds to complete. We do this because + * waiting for the OS to time out the connection attempt is not + * practical--the default timeout on many OSes can be 3 minutes or + * more, and users are impatient. + * + * Worst case scenario: the user is connected to the Internet using + * a modem with severe lag. The peer connections fail and Gaim falls + * back to using a proxied connection. The lower bandwidth + * limitations imposed by the proxied connection won't matter because + * the user is using a modem. + * + * I suppose this line of thinking is discriminatory against people + * with very high lag but decent throughput who are transferring + * large files. But we don't care about those people. + */ +static gboolean +peer_connection_tooktoolong(gpointer data) +{ + PeerConnection *conn; + + conn = data; + + gaim_debug_info("oscar", "Peer connection timed out after 15 seconds. " + "Trying next method...\n"); + + peer_connection_close(conn); + + peer_connection_trynext(conn); + + /* Cancel this timer. It'll be added again, if needed. */ + return FALSE; +} + +/** + * Try to establish the given PeerConnection using a defined + * sequence of steps. + */ +void +peer_connection_trynext(PeerConnection *conn) +{ + GaimAccount *account; + + account = gaim_connection_get_account(conn->od->gc); + + /* + * Close any remnants of a previous failed connection attempt. + */ + peer_connection_close(conn); + + /* + * 1. Attempt to connect to the remote user using their verifiedip. + */ + if (!(conn->flags & PEER_CONNECTION_FLAG_TRIED_VERIFIEDIP) && + (conn->verifiedip != NULL) && (conn->port != 0) && (!conn->use_proxy)) + { + conn->flags |= PEER_CONNECTION_FLAG_TRIED_VERIFIEDIP; + + if (conn->type == OSCAR_CAPABILITY_DIRECTIM) + { + gchar *tmp; + GaimConversation *conv; + tmp = g_strdup_printf(_("Attempting to connect to %s:%hu."), + conn->verifiedip, conn->port); + conv = gaim_conversation_new(GAIM_CONV_TYPE_IM, account, conn->sn); + gaim_conversation_write(conv, NULL, tmp, + GAIM_MESSAGE_SYSTEM, time(NULL)); + g_free(tmp); + } + + conn->connect_info = gaim_proxy_connect(account, + conn->verifiedip, conn->port, + peer_connection_established_cb, conn); + if (conn->connect_info != NULL) + { + /* Connecting... */ + conn->connect_timeout_timer = gaim_timeout_add(15000, + peer_connection_tooktoolong, conn); + return; + } + } + + /* + * 2. Attempt to connect to the remote user using their clientip. + */ + if (!(conn->flags & PEER_CONNECTION_FLAG_TRIED_CLIENTIP) && + (conn->clientip != NULL) && (conn->port != 0) && (!conn->use_proxy)) + { + conn->flags |= PEER_CONNECTION_FLAG_TRIED_CLIENTIP; + + if ((conn->verifiedip == NULL) || + strcmp(conn->verifiedip, conn->clientip)) + { + if (conn->type == OSCAR_CAPABILITY_DIRECTIM) + { + gchar *tmp; + GaimConversation *conv; + tmp = g_strdup_printf(_("Attempting to connect to %s:%hu."), + conn->clientip, conn->port); + conv = gaim_conversation_new(GAIM_CONV_TYPE_IM, account, conn->sn); + gaim_conversation_write(conv, NULL, tmp, + GAIM_MESSAGE_SYSTEM, time(NULL)); + g_free(tmp); + } + + conn->connect_info = gaim_proxy_connect(account, + conn->clientip, conn->port, + peer_connection_established_cb, conn); + if (conn->connect_info != NULL) + { + /* Connecting... */ + conn->connect_timeout_timer = gaim_timeout_add(15000, + peer_connection_tooktoolong, conn); + return; + } + } + } + + /* + * 3. Attempt to have the remote user connect to us (using both + * our verifiedip and our clientip). + */ + if (!(conn->flags & PEER_CONNECTION_FLAG_TRIED_INCOMING) && + (!conn->use_proxy)) + { + NewPeerConnectionData *new_conn_data; + + new_conn_data = g_new(NewPeerConnectionData, 1); + new_conn_data->gc = conn->od->gc; + new_conn_data->conn = conn; + + conn->flags |= PEER_CONNECTION_FLAG_TRIED_INCOMING; + + /* + * Remote user is connecting to us, so we'll need to verify + * that the user who connected is our friend. + */ + conn->flags |= PEER_CONNECTION_FLAG_IS_INCOMING; + + if (gaim_network_listen_range(5190, 5290, SOCK_STREAM, + peer_connection_establish_listener_cb, new_conn_data)) + { + /* Opening listener socket... */ + return; + } + + g_free(new_conn_data); + } + + /* + * 4. Attempt to have both users connect to an intermediate proxy + * server. + */ + if (!(conn->flags & PEER_CONNECTION_FLAG_TRIED_PROXY)) + { + conn->flags |= PEER_CONNECTION_FLAG_TRIED_PROXY; + + /* + * If we initiate the proxy connection, then the remote user + * could be anyone, so we need to verify that the user who + * connected is our friend. + */ + if (!conn->use_proxy) + conn->flags |= PEER_CONNECTION_FLAG_IS_INCOMING; + + if (conn->type == OSCAR_CAPABILITY_DIRECTIM) + { + gchar *tmp; + GaimConversation *conv; + tmp = g_strdup_printf(_("Attempting to connect via proxy server.")); + conv = gaim_conversation_new(GAIM_CONV_TYPE_IM, account, conn->sn); + gaim_conversation_write(conv, NULL, tmp, + GAIM_MESSAGE_SYSTEM, time(NULL)); + g_free(tmp); + } + + conn->connect_info = gaim_proxy_connect(account, + (conn->proxyip != NULL) ? conn->proxyip : PEER_PROXY_SERVER, + PEER_PROXY_PORT, + peer_proxy_connection_established_cb, conn); + if (conn->connect_info != NULL) + { + /* Connecting... */ + return; + } + } + + /* Give up! */ + peer_connection_destroy(conn, OSCAR_DISCONNECT_COULD_NOT_CONNECT); +} + +/** + * Initiate a peer connection with someone. + */ +void +peer_connection_propose(OscarData *od, OscarCapability type, const char *sn) +{ + PeerConnection *conn; + + if (type == OSCAR_CAPABILITY_DIRECTIM) + { + conn = peer_connection_find_by_type(od, sn, type); + if (conn != NULL) + { + if (conn->ready) + { + GaimAccount *account; + GaimConversation *conv; + + gaim_debug_info("oscar", "Already have a direct IM " + "session with %s.\n", sn); + account = gaim_connection_get_account(od->gc); + conv = gaim_find_conversation_with_account(GAIM_CONV_TYPE_IM, + sn, account); + if (conv != NULL) + gaim_conversation_present(conv); + return; + } + + /* Cancel the old connection and try again */ + peer_connection_destroy(conn, OSCAR_DISCONNECT_RETRYING); + } + } + + conn = peer_connection_new(od, type, sn); + conn->flags |= PEER_CONNECTION_FLAG_INITIATED_BY_ME; + conn->flags |= PEER_CONNECTION_FLAG_APPROVED; + aim_icbm_makecookie(conn->cookie); + + peer_connection_trynext(conn); +} + +/** + * Someone else wants to establish a peer connection with us, + * and we said yes. + */ +static void +peer_connection_got_proposition_yes_cb(gpointer data, gint id) +{ + PeerConnection *conn; + + conn = data; + + conn->flags |= PEER_CONNECTION_FLAG_APPROVED; + peer_connection_trynext(conn); +} + +/** + * Someone else wants to establish a peer connection with us, + * and we said no. + * + * "Well, one time my friend asked me if I wanted to play the + * piccolo. But I said no." + */ +static void +peer_connection_got_proposition_no_cb(gpointer data, gint id) +{ + PeerConnection *conn; + + conn = data; + + aim_im_denytransfer(conn->od, conn->sn, conn->cookie, + AIM_TRANSFER_DENY_DECLINE); + peer_connection_destroy(conn, OSCAR_DISCONNECT_LOCAL_CLOSED); +} + +/** + * Someone else wants to establish a peer connection with us. + */ +void +peer_connection_got_proposition(OscarData *od, const gchar *sn, const gchar *message, IcbmArgsCh2 *args) +{ + GaimConnection *gc; + GaimAccount *account; + PeerConnection *conn; + gchar *buf; + + gc = od->gc; + account = gaim_connection_get_account(gc); + + /* + * If we have a connection with this same cookie then they are + * probably just telling us they weren't able to connect to us + * and we should try connecting to them, instead. Or they want + * to go through a proxy. + */ + conn = peer_connection_find_by_cookie(od, sn, args->cookie); + if ((conn != NULL) && (conn->type == args->type)) + { + gaim_debug_info("oscar", "Remote user wants to try a " + "different connection method\n"); + g_free(conn->proxyip); + g_free(conn->clientip); + g_free(conn->verifiedip); + if (args->use_proxy) + conn->proxyip = g_strdup(args->proxyip); + else + conn->proxyip = NULL; + conn->verifiedip = g_strdup(args->verifiedip); + conn->clientip = g_strdup(args->clientip); + conn->port = args->port; + conn->use_proxy |= args->use_proxy; + conn->lastrequestnumber++; + peer_connection_trynext(conn); + return; + } + + /* If this is a direct IM, then close any existing session */ + if (args->type == OSCAR_CAPABILITY_DIRECTIM) + { + conn = peer_connection_find_by_type(od, sn, args->type); + if (conn != NULL) + { + /* Close the old direct IM and start a new one */ + gaim_debug_info("oscar", "Received new direct IM request " + "from %s. Destroying old connection.\n", sn); + peer_connection_destroy(conn, OSCAR_DISCONNECT_REMOTE_CLOSED); + } + } + + /* Check for proper arguments */ + if (args->type == OSCAR_CAPABILITY_SENDFILE) + { + if ((args->info.sendfile.filename == NULL) || + (args->info.sendfile.totsize == 0) || + (args->info.sendfile.totfiles == 0)) + { + gaim_debug_warning("oscar", + "%s tried to send you a file with incomplete " + "information.\n", sn); + return; + } + } + + conn = peer_connection_new(od, args->type, sn); + memcpy(conn->cookie, args->cookie, 8); + if (args->use_proxy) + conn->proxyip = g_strdup(args->proxyip); + conn->clientip = g_strdup(args->clientip); + conn->verifiedip = g_strdup(args->verifiedip); + conn->port = args->port; + conn->use_proxy |= args->use_proxy; + conn->lastrequestnumber++; + + if (args->type == OSCAR_CAPABILITY_DIRECTIM) + { + buf = g_strdup_printf(_("%s has just asked to directly connect to %s"), + sn, gaim_account_get_username(account)); + + gaim_request_action(conn, NULL, buf, + _("This requires a direct connection between " + "the two computers and is necessary for IM " + "Images. Because your IP address will be " + "revealed, this may be considered a privacy " + "risk."), + GAIM_DEFAULT_ACTION_NONE, conn, 2, + _("_Connect"), G_CALLBACK(peer_connection_got_proposition_yes_cb), + _("Cancel"), G_CALLBACK(peer_connection_got_proposition_no_cb)); + } + else if (args->type == OSCAR_CAPABILITY_SENDFILE) + { + gchar *filename; + + conn->xfer = gaim_xfer_new(account, GAIM_XFER_RECEIVE, sn); + conn->xfer->data = conn; + gaim_xfer_ref(conn->xfer); + gaim_xfer_set_size(conn->xfer, args->info.sendfile.totsize); + + /* Set the file name */ + if (g_utf8_validate(args->info.sendfile.filename, -1, NULL)) + filename = g_strdup(args->info.sendfile.filename); + else + filename = gaim_utf8_salvage(args->info.sendfile.filename); + + if (args->info.sendfile.subtype == AIM_OFT_SUBTYPE_SEND_DIR) + { + /* + * If they are sending us a directory then the last character + * of the file name will be an asterisk. We don't want to + * save stuff to a directory named "*" so we remove the + * asterisk from the file name. + */ + char *tmp = strrchr(filename, '\\'); + if ((tmp != NULL) && (tmp[1] == '*')) + tmp[0] = '\0'; + } + gaim_xfer_set_filename(conn->xfer, filename); + g_free(filename); + + /* + * Set the message (unless this is the dummy message from an + * ICQ client or an empty message from an AIM client. + * TODO: Maybe we should strip HTML and then see if strlen>0? + */ + if ((message != NULL) && + (g_ascii_strncasecmp(message, "<ICQ_COOL_FT>", 13) != 0) && + (g_ascii_strcasecmp(message, "<HTML>") != 0)) + { + gaim_xfer_set_message(conn->xfer, message); + } + + /* Setup our I/O op functions */ + gaim_xfer_set_init_fnc(conn->xfer, peer_oft_recvcb_init); + gaim_xfer_set_end_fnc(conn->xfer, peer_oft_recvcb_end); + gaim_xfer_set_request_denied_fnc(conn->xfer, peer_oft_cb_generic_cancel); + gaim_xfer_set_cancel_recv_fnc(conn->xfer, peer_oft_cb_generic_cancel); + gaim_xfer_set_ack_fnc(conn->xfer, peer_oft_recvcb_ack_recv); + + /* Now perform the request */ + gaim_xfer_request(conn->xfer); + } +} + +/*******************************************************************/ +/* End code for establishing a peer connection */ +/*******************************************************************/