pidgin/pidgin

Fix an error caused by misreading purple_strequal()
release-2.x.y
2019-11-05, John Bailey
60fbcaffa69a
Fix an error caused by misreading purple_strequal()
/*
* Purple'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., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 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 Purple */
#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 *bn, guint64 type)
{
GSList *cur;
PeerConnection *conn;
for (cur = od->peer_connections; cur != NULL; cur = cur->next)
{
conn = cur->data;
if ((conn->type == type) && !oscar_util_name_compare(conn->bn, bn))
return conn;
}
return NULL;
}
/**
* @param cookie This must be exactly 8 characters.
*/
PeerConnection *
peer_connection_find_by_cookie(OscarData *od, const char *bn, const guchar *cookie)
{
GSList *cur;
PeerConnection *conn;
for (cur = od->peer_connections; cur != NULL; cur = cur->next)
{
conn = cur->data;
if (!memcmp(conn->cookie, cookie, 8) && !oscar_util_name_compare(conn->bn, bn))
return conn;
}
return NULL;
}
PeerConnection *
peer_connection_new(OscarData *od, guint64 type, const char *bn)
{
PeerConnection *conn;
PurpleAccount *account;
account = purple_connection_get_account(od->gc);
conn = g_new0(PeerConnection, 1);
conn->od = od;
conn->type = type;
conn->bn = g_strdup(bn);
conn->buffer_outgoing = purple_circ_buffer_new(0);
conn->listenerfd = -1;
conn->fd = -1;
conn->lastactivity = time(NULL);
conn->use_proxy |= purple_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_slist_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->verified_connect_data != NULL)
{
purple_proxy_connect_cancel(conn->verified_connect_data);
conn->verified_connect_data = NULL;
}
if (conn->client_connect_data != NULL)
{
purple_proxy_connect_cancel(conn->client_connect_data);
conn->client_connect_data = NULL;
}
if (conn->listen_data != NULL)
{
purple_network_listen_cancel(conn->listen_data);
conn->listen_data = NULL;
}
if (conn->connect_timeout_timer != 0)
{
purple_timeout_remove(conn->connect_timeout_timer);
conn->connect_timeout_timer = 0;
}
if (conn->watcher_incoming != 0)
{
purple_input_remove(conn->watcher_incoming);
conn->watcher_incoming = 0;
}
if (conn->watcher_outgoing != 0)
{
purple_input_remove(conn->watcher_outgoing);
conn->watcher_outgoing = 0;
}
if (conn->listenerfd >= 0)
{
close(conn->listenerfd);
conn->listenerfd = -1;
}
if (conn->fd >= 0)
{
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;
purple_circ_buffer_destroy(conn->buffer_outgoing);
conn->buffer_outgoing = purple_circ_buffer_new(0);
conn->flags &= ~PEER_CONNECTION_FLAG_IS_INCOMING;
}
static gboolean
peer_connection_destroy_cb(gpointer data)
{
PeerConnection *conn;
conn = data;
purple_request_close_with_handle(conn);
peer_connection_close(conn);
if (conn->checksum_data != NULL)
peer_oft_checksum_destroy(conn->checksum_data);
if (conn->xfer != NULL)
{
PurpleXferStatusType status;
conn->xfer->data = NULL;
status = purple_xfer_get_status(conn->xfer);
if ((status != PURPLE_XFER_STATUS_DONE) &&
(status != PURPLE_XFER_STATUS_CANCEL_LOCAL) &&
(status != PURPLE_XFER_STATUS_CANCEL_REMOTE))
{
if ((conn->disconnect_reason == OSCAR_DISCONNECT_REMOTE_CLOSED) ||
(conn->disconnect_reason == OSCAR_DISCONNECT_REMOTE_REFUSED))
purple_xfer_cancel_remote(conn->xfer);
else
purple_xfer_cancel_local(conn->xfer);
}
purple_xfer_unref(conn->xfer);
conn->xfer = NULL;
}
g_free(conn->bn);
g_free(conn->error_message);
g_free(conn->proxyip);
g_free(conn->clientip);
g_free(conn->verifiedip);
g_free(conn->xferdata.name);
purple_circ_buffer_destroy(conn->buffer_outgoing);
conn->od->peer_connections = g_slist_remove(conn->od->peer_connections, conn);
g_free(conn);
return FALSE;
}
void
peer_connection_destroy(PeerConnection *conn, OscarDisconnectReason reason, const gchar *error_message)
{
if (conn->destroy_timeout != 0)
purple_timeout_remove(conn->destroy_timeout);
conn->disconnect_reason = reason;
g_free(conn->error_message);
conn->error_message = g_strdup(error_message);
peer_connection_destroy_cb(conn);
}
void
peer_connection_schedule_destroy(PeerConnection *conn, OscarDisconnectReason reason, const gchar *error_message)
{
if (conn->destroy_timeout != 0)
/* Already taken care of */
return;
purple_debug_info("oscar", "Scheduling destruction of peer connection\n");
conn->disconnect_reason = reason;
g_free(conn->error_message);
conn->error_message = g_strdup(error_message);
conn->destroy_timeout = purple_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, PurpleInputCondition cond)
{
PeerConnection *conn;
gssize read;
conn = data;
/* Start reading a new ODC/OFT frame */
if (conn->buffer_incoming.data == NULL)
{
/* Read the first 6 bytes (magic string and frame length) */
read = recv(conn->fd, conn->header + conn->header_received,
6 - conn->header_received, 0);
/* Check if the remote user closed the connection */
if (read == 0)
{
peer_connection_destroy(conn, OSCAR_DISCONNECT_REMOTE_CLOSED, NULL);
return;
}
/* If there was an error then close the connection */
if (read < 0)
{
if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
/* No worries */
return;
peer_connection_destroy(conn,
OSCAR_DISCONNECT_LOST_CONNECTION, g_strerror(errno));
return;
}
conn->lastactivity = time(NULL);
/* If we don't even have the first 6 bytes then do nothing */
conn->header_received += read;
if (conn->header_received < 6)
return;
/* All ODC/OFT frames must start with a magic string */
if (memcmp(conn->magic, conn->header, 4))
{
purple_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], conn->header[0], conn->header[1],
conn->header[2], conn->header[3]);
peer_connection_destroy(conn, OSCAR_DISCONNECT_INVALID_DATA, NULL);
return;
}
/* Initialize a new temporary ByteStream for incoming data */
conn->buffer_incoming.len = aimutil_get16(&conn->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, NULL);
return;
}
if (read < 0)
{
if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
/* No worries */
return;
peer_connection_destroy(conn,
OSCAR_DISCONNECT_LOST_CONNECTION, g_strerror(errno));
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;
conn->header_received = 0;
}
/*******************************************************************/
/* 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, PurpleInputCondition cond)
{
PeerConnection *conn;
gsize writelen;
gssize wrotelen;
conn = data;
writelen = purple_circ_buffer_get_max_read(conn->buffer_outgoing);
if (writelen == 0)
{
purple_input_remove(conn->watcher_outgoing);
conn->watcher_outgoing = 0;
/*
* The buffer is currently empty, so reset the current input
* and output positions to the start of the buffer. We do
* this so that the next chunk of data that we put into the
* buffer can be read back out of the buffer in one fell swoop.
* Otherwise it gets fragmented and we have to read from the
* second half of the buffer than go back and read the rest of
* the chunk from the first half.
*
* We're using TCP, which is a stream based protocol, so this
* isn't supposed to matter. However, experience has shown
* that at least the proxy file transfer code in AIM 6.1.41.2
* requires that the entire OFT frame arrive all at once. If
* the frame is fragmented then AIM freaks out and aborts the
* file transfer. Somebody should teach those guys how to
* write good TCP code.
*/
conn->buffer_outgoing->inptr = conn->buffer_outgoing->buffer;
conn->buffer_outgoing->outptr = conn->buffer_outgoing->buffer;
return;
}
wrotelen = send(conn->fd, conn->buffer_outgoing->outptr, writelen, 0);
if (wrotelen <= 0)
{
if (wrotelen < 0 && ((errno == EAGAIN) || (errno == EWOULDBLOCK)))
/* No worries */
return;
if (conn->ready)
{
purple_input_remove(conn->watcher_outgoing);
conn->watcher_outgoing = 0;
close(conn->fd);
conn->fd = -1;
peer_connection_schedule_destroy(conn,
OSCAR_DISCONNECT_LOST_CONNECTION, NULL);
}
else
{
/*
* This could happen when unable to send a negotiation
* frame to a peer proxy server.
*/
peer_connection_trynext(conn);
}
return;
}
purple_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 */
purple_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->fd >= 0))
{
conn->watcher_outgoing = purple_input_add(conn->fd,
PURPLE_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 = purple_input_add(conn->fd,
PURPLE_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 (purple_xfer_get_type(conn->xfer) == PURPLE_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_common_established_cb(gpointer data, gint source, const gchar *error_message, gboolean verified)
{
PeerConnection *conn;
conn = data;
if (verified)
conn->verified_connect_data = NULL;
else
conn->client_connect_data = NULL;
if (source < 0)
{
if ((conn->verified_connect_data == NULL) &&
(conn->client_connect_data == NULL))
{
/* Our parallel connection attemps have both failed. */
peer_connection_trynext(conn);
}
return;
}
purple_timeout_remove(conn->connect_timeout_timer);
conn->connect_timeout_timer = 0;
if (conn->client_connect_data != NULL)
{
purple_proxy_connect_cancel(conn->client_connect_data);
conn->client_connect_data = NULL;
}
if (conn->verified_connect_data != NULL)
{
purple_proxy_connect_cancel(conn->verified_connect_data);
conn->verified_connect_data = NULL;
}
conn->fd = source;
peer_connection_finalize_connection(conn);
}
static void
peer_connection_verified_established_cb(gpointer data, gint source, const gchar *error_message)
{
peer_connection_common_established_cb(data, source, error_message, TRUE);
}
static void
peer_connection_client_established_cb(gpointer data, gint source, const gchar *error_message)
{
peer_connection_common_established_cb(data, source, error_message, FALSE);
}
/**
* 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, PurpleInputCondition cond)
{
PeerConnection *conn;
struct sockaddr addr;
socklen_t addrlen = sizeof(addr);
conn = data;
purple_debug_info("oscar", "Accepting connection on listener socket.\n");
conn->fd = accept(conn->listenerfd, &addr, &addrlen);
if (conn->fd < 0)
{
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;
}
_purple_network_set_common_socket_flags(conn->fd);
purple_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)
{
PeerConnection *conn;
OscarData *od;
PurpleConnection *gc;
PurpleAccount *account;
PurpleConversation *conv;
char *tmp;
FlapConnection *bos_conn;
const char *listener_ip;
const guchar *ip_atoi;
unsigned short listener_port;
conn = data;
conn->listen_data = NULL;
if (listenerfd < 0)
{
/* Could not open listener socket */
peer_connection_trynext(conn);
return;
}
od = conn->od;
gc = od->gc;
account = purple_connection_get_account(gc);
conn->listenerfd = listenerfd;
/* Watch for new connections on our listener socket */
conn->watcher_incoming = purple_input_add(conn->listenerfd,
PURPLE_INPUT_READ, peer_connection_listen_cb, conn);
/* 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;
}
if (bos_conn->gsc)
listener_ip = purple_network_get_my_ip(bos_conn->gsc->fd);
else
listener_ip = purple_network_get_my_ip(bos_conn->fd);
ip_atoi = purple_network_ip_atoi(listener_ip);
if (ip_atoi == NULL) {
/* Could not convert IP to 4 byte array--weird, but this does
happen for some users (#4829, Adium #15839). Maybe they're
connecting with IPv6...? Maybe through a proxy? */
purple_debug_error("oscar", "Can't ask peer to connect to us "
"because purple_network_ip_atoi(%s) returned NULL. "
"fd=%d. is_ssl=%d\n",
listener_ip ? listener_ip : "(null)",
bos_conn->gsc ? bos_conn->gsc->fd : bos_conn->fd,
bos_conn->gsc ? 1 : 0);
peer_connection_trynext(conn);
return;
}
listener_port = purple_network_get_port_from_fd(conn->listenerfd);
if (conn->type == OSCAR_CAPABILITY_DIRECTIM)
{
aim_im_sendch2_odc_requestdirect(od,
conn->cookie, conn->bn, ip_atoi,
listener_port, ++conn->lastrequestnumber);
/* Print a message to a local conversation window */
conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, conn->bn);
tmp = g_strdup_printf(_("Asking %s to connect to us at %s:%hu for "
"Direct IM."), conn->bn, listener_ip, listener_port);
purple_conversation_write(conv, NULL, tmp, PURPLE_MESSAGE_SYSTEM, time(NULL));
g_free(tmp);
}
else if (conn->type == OSCAR_CAPABILITY_SENDFILE)
{
aim_im_sendch2_sendfile_requestdirect(od,
conn->cookie, conn->bn,
ip_atoi,
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 5 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 Purple 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.
*
* I (Sean) changed the timeout from 15 to 5 seconds, as 60 seconds is
* too long for a user to wait to send a file. I'm also parallelizing
* requests when possible. The longest we should have to wait now is 10
* seconds. We shouldn't make it shorter than this.
*/
static gboolean
peer_connection_tooktoolong(gpointer data)
{
PeerConnection *conn;
conn = data;
purple_debug_info("oscar", "Peer connection timed out after 5 seconds. "
"Trying next method...\n");
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)
{
PurpleAccount *account;
account = purple_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 and clientip.
* We try these at the same time and use whichever succeeds first, so we don't
* have to wait for a timeout.
*/
if (!(conn->flags & PEER_CONNECTION_FLAG_TRIED_DIRECT) &&
(conn->verifiedip != NULL) && (conn->port != 0) && (!conn->use_proxy))
{
conn->flags |= PEER_CONNECTION_FLAG_TRIED_DIRECT;
if (conn->type == OSCAR_CAPABILITY_DIRECTIM)
{
gchar *tmp;
PurpleConversation *conv;
tmp = g_strdup_printf(_("Attempting to connect to %s:%hu."),
conn->verifiedip, conn->port);
conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, conn->bn);
purple_conversation_write(conv, NULL, tmp,
PURPLE_MESSAGE_SYSTEM, time(NULL));
g_free(tmp);
}
conn->verified_connect_data = purple_proxy_connect(NULL, account,
conn->verifiedip, conn->port,
peer_connection_verified_established_cb, conn);
if ((conn->verifiedip == NULL) ||
!purple_strequal(conn->verifiedip, conn->clientip))
{
conn->client_connect_data = purple_proxy_connect(NULL, account,
conn->clientip, conn->port,
peer_connection_client_established_cb, conn);
}
if ((conn->verified_connect_data != NULL) ||
(conn->client_connect_data != NULL))
{
/* Connecting... */
conn->connect_timeout_timer = purple_timeout_add_seconds(5,
peer_connection_tooktoolong, conn);
return;
}
}
/*
* 2. 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))
{
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;
conn->listen_data = purple_network_listen_range(5190, 5290, SOCK_STREAM,
peer_connection_establish_listener_cb, conn);
if (conn->listen_data != NULL)
{
/* Opening listener socket... */
return;
}
}
/*
* 3. 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;
PurpleConversation *conv;
tmp = g_strdup(_("Attempting to connect via proxy server."));
conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, conn->bn);
purple_conversation_write(conv, NULL, tmp,
PURPLE_MESSAGE_SYSTEM, time(NULL));
g_free(tmp);
}
conn->verified_connect_data = purple_proxy_connect(NULL, account,
(conn->proxyip != NULL)
? conn->proxyip
: (conn->od->icq ? ICQ_PEER_PROXY_SERVER : AIM_PEER_PROXY_SERVER),
PEER_PROXY_PORT,
peer_proxy_connection_established_cb, conn);
if (conn->verified_connect_data != NULL)
{
/* Connecting... */
return;
}
}
/* Give up! */
peer_connection_destroy(conn, OSCAR_DISCONNECT_COULD_NOT_CONNECT, NULL);
}
/**
* Initiate a peer connection with someone.
*/
void
peer_connection_propose(OscarData *od, guint64 type, const char *bn)
{
PeerConnection *conn;
if (type == OSCAR_CAPABILITY_DIRECTIM)
{
conn = peer_connection_find_by_type(od, bn, type);
if (conn != NULL)
{
if (conn->ready)
{
PurpleAccount *account;
PurpleConversation *conv;
purple_debug_info("oscar", "Already have a direct IM "
"session with %s.\n", bn);
account = purple_connection_get_account(od->gc);
conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM,
bn, account);
if (conv != NULL)
purple_conversation_present(conv);
return;
}
/* Cancel the old connection and try again */
peer_connection_destroy(conn, OSCAR_DISCONNECT_RETRYING, NULL);
}
}
conn = peer_connection_new(od, type, bn);
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->bn, conn->cookie,
AIM_TRANSFER_DENY_DECLINE);
peer_connection_destroy(conn, OSCAR_DISCONNECT_LOCAL_CLOSED, NULL);
}
/**
* Someone else wants to establish a peer connection with us.
*/
void
peer_connection_got_proposition(OscarData *od, const gchar *bn, const gchar *message, IcbmArgsCh2 *args)
{
PurpleConnection *gc;
PurpleAccount *account;
PeerConnection *conn;
gchar *buf;
gc = od->gc;
account = purple_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, bn, args->cookie);
if ((conn != NULL) && (conn->type == args->type))
{
purple_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, bn, args->type);
if (conn != NULL)
{
/* Close the old direct IM and start a new one */
purple_debug_info("oscar", "Received new direct IM request "
"from %s. Destroying old connection.\n", bn);
peer_connection_destroy(conn, OSCAR_DISCONNECT_REMOTE_CLOSED, NULL);
}
}
/* 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))
{
purple_debug_warning("oscar",
"%s tried to send you a file with incomplete "
"information.\n", bn);
return;
}
}
conn = peer_connection_new(od, args->type, bn);
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"),
bn, purple_account_get_username(account));
purple_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."),
PURPLE_DEFAULT_ACTION_NONE,
account, bn, NULL,
conn, 2,
_("C_onnect"), 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 = purple_xfer_new(account, PURPLE_XFER_RECEIVE, bn);
if (conn->xfer)
{
conn->xfer->data = conn;
purple_xfer_ref(conn->xfer);
purple_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 = purple_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';
}
purple_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))
{
purple_xfer_set_message(conn->xfer, message);
}
/* Setup our I/O op functions */
purple_xfer_set_init_fnc(conn->xfer, peer_oft_recvcb_init);
purple_xfer_set_end_fnc(conn->xfer, peer_oft_recvcb_end);
purple_xfer_set_request_denied_fnc(conn->xfer, peer_oft_cb_generic_cancel);
purple_xfer_set_cancel_recv_fnc(conn->xfer, peer_oft_cb_generic_cancel);
purple_xfer_set_ack_fnc(conn->xfer, peer_oft_recvcb_ack_recv);
/* Now perform the request */
purple_xfer_request(conn->xfer);
}
}
}
/*******************************************************************/
/* End code for establishing a peer connection */
/*******************************************************************/