1
0
Fork 0
mirror of https://github.com/transmission/transmission synced 2024-12-26 09:37:56 +00:00
transmission/libtransmission/handshake.c
Jordan Lee 879a2afcbd Update the copyright year in the source code comments.
The Berne Convention says that the copyright year is moot, so instead of adding another year to each file as in previous years, I've removed the year altogether from the source code comments in libtransmission, gtk, qt, utils, daemon, and cli.

Juliusz's copyright notice in tr-dht and Johannes' copyright notice in tr-lpd have been left alone; it didn't seem appropriate to modify them.
2011-01-19 13:48:47 +00:00

1238 lines
36 KiB
C

/*
* This file Copyright (C) Mnemosyne LLC
*
* This file is licensed by the GPL version 2. Works owned by the
* Transmission project are granted a special exemption to clause 2(b)
* so that the bulk of its code can remain under the MIT license.
* This exemption does not extend to derived works not owned by
* the Transmission project.
*
* $Id$
*/
#include <assert.h>
#include <errno.h>
#include <inttypes.h>
#include <limits.h> /* UCHAR_MAX */
#include <string.h>
#include <stdio.h>
#include <event2/event.h>
#include "transmission.h"
#include "bencode.h"
#include "clients.h"
#include "crypto.h"
#include "handshake.h"
#include "peer-io.h"
#include "peer-mgr.h"
#include "session.h"
#include "torrent.h"
#include "tr-dht.h"
#include "utils.h"
/* enable LibTransmission extension protocol */
#define ENABLE_LTEP * /
/* fast extensions */
#define ENABLE_FAST * /
/* DHT */
#define ENABLE_DHT * /
/***
****
***/
#define HANDSHAKE_NAME "\023BitTorrent protocol"
enum
{
/* BitTorrent Handshake Constants */
HANDSHAKE_NAME_LEN = 20,
HANDSHAKE_FLAGS_LEN = 8,
HANDSHAKE_SIZE = 68,
PEER_ID_LEN = 20,
INCOMING_HANDSHAKE_LEN = 48,
/* Encryption Constants */
PadA_MAXLEN = 512,
PadB_MAXLEN = 512,
PadC_MAXLEN = 512,
PadD_MAXLEN = 512,
VC_LENGTH = 8,
KEY_LEN = 96,
CRYPTO_PROVIDE_PLAINTEXT = 1,
CRYPTO_PROVIDE_CRYPTO = 2,
/* how long to wait before giving up on a handshake */
HANDSHAKE_TIMEOUT_SEC = 30
};
#ifdef ENABLE_LTEP
#define HANDSHAKE_HAS_LTEP( bits ) ( ( ( bits )[5] & 0x10 ) ? 1 : 0 )
#define HANDSHAKE_SET_LTEP( bits ) ( ( bits )[5] |= 0x10 )
#else
#define HANDSHAKE_HAS_LTEP( bits ) ( 0 )
#define HANDSHAKE_SET_LTEP( bits ) ( (void)0 )
#endif
#ifdef ENABLE_FAST
#define HANDSHAKE_HAS_FASTEXT( bits ) ( ( ( bits )[7] & 0x04 ) ? 1 : 0 )
#define HANDSHAKE_SET_FASTEXT( bits ) ( ( bits )[7] |= 0x04 )
#else
#define HANDSHAKE_HAS_FASTEXT( bits ) ( 0 )
#define HANDSHAKE_SET_FASTEXT( bits ) ( (void)0 )
#endif
#ifdef ENABLE_DHT
#define HANDSHAKE_HAS_DHT( bits ) ( ( ( bits )[7] & 0x01 ) ? 1 : 0 )
#define HANDSHAKE_SET_DHT( bits ) ( ( bits )[7] |= 0x01 )
#else
#define HANDSHAKE_HAS_DHT( bits ) ( 0 )
#define HANDSHAKE_SET_DHT( bits ) ( (void)0 )
#endif
/* http://www.azureuswiki.com/index.php/Extension_negotiation_protocol
these macros are to be used if both extended messaging and the
azureus protocol is supported, they indicate which protocol is preferred */
#define HANDSHAKE_GET_EXTPREF( reserved ) ( ( reserved )[5] & 0x03 )
#define HANDSHAKE_SET_EXTPREF( reserved, val ) ( ( reserved )[5] |= 0x03 &\
( val ) )
typedef uint8_t handshake_state_t;
struct tr_handshake
{
tr_bool haveReadAnythingFromPeer;
tr_bool havePeerID;
tr_bool haveSentBitTorrentHandshake;
tr_peerIo * io;
tr_crypto * crypto;
tr_session * session;
uint8_t mySecret[KEY_LEN];
handshake_state_t state;
tr_encryption_mode encryptionMode;
uint16_t pad_c_len;
uint16_t pad_d_len;
uint16_t ia_len;
uint32_t crypto_select;
uint32_t crypto_provide;
uint8_t myReq1[SHA_DIGEST_LENGTH];
handshakeDoneCB doneCB;
void * doneUserData;
struct event * timeout_timer;
};
/**
***
**/
enum
{
/* incoming */
AWAITING_HANDSHAKE,
AWAITING_PEER_ID,
AWAITING_YA,
AWAITING_PAD_A,
AWAITING_CRYPTO_PROVIDE,
AWAITING_PAD_C,
AWAITING_IA,
AWAITING_PAYLOAD_STREAM,
/* outgoing */
AWAITING_YB,
AWAITING_VC,
AWAITING_CRYPTO_SELECT,
AWAITING_PAD_D,
};
/**
***
**/
#define dbgmsg( handshake, ... ) \
do { \
if( tr_deepLoggingIsActive( ) ) \
tr_deepLog( __FILE__, __LINE__, tr_peerIoGetAddrStr( handshake->io ), __VA_ARGS__ ); \
} while( 0 )
static const char*
getStateName( const handshake_state_t state )
{
const char * str = "f00!";
switch( state )
{
case AWAITING_HANDSHAKE:
str = "awaiting handshake"; break;
case AWAITING_PEER_ID:
str = "awaiting peer id"; break;
case AWAITING_YA:
str = "awaiting ya"; break;
case AWAITING_PAD_A:
str = "awaiting pad a"; break;
case AWAITING_CRYPTO_PROVIDE:
str = "awaiting crypto_provide"; break;
case AWAITING_PAD_C:
str = "awaiting pad c"; break;
case AWAITING_IA:
str = "awaiting ia"; break;
case AWAITING_YB:
str = "awaiting yb"; break;
case AWAITING_VC:
str = "awaiting vc"; break;
case AWAITING_CRYPTO_SELECT:
str = "awaiting crypto select"; break;
case AWAITING_PAD_D:
str = "awaiting pad d"; break;
}
return str;
}
static void
setState( tr_handshake * handshake, handshake_state_t state )
{
dbgmsg( handshake, "setting to state [%s]", getStateName( state ) );
handshake->state = state;
}
static void
setReadState( tr_handshake * handshake, handshake_state_t state )
{
setState( handshake, state );
}
static void
buildHandshakeMessage( tr_handshake * handshake, uint8_t * buf )
{
uint8_t * walk = buf;
const uint8_t * torrentHash = tr_cryptoGetTorrentHash( handshake->crypto );
const tr_torrent * tor = tr_torrentFindFromHash( handshake->session, torrentHash );
const uint8_t * peer_id = tor && tor->peer_id ? tor->peer_id : tr_getPeerId( );
memcpy( walk, HANDSHAKE_NAME, HANDSHAKE_NAME_LEN );
walk += HANDSHAKE_NAME_LEN;
memset( walk, 0, HANDSHAKE_FLAGS_LEN );
HANDSHAKE_SET_LTEP( walk );
HANDSHAKE_SET_FASTEXT( walk );
/* Note that this doesn't depend on whether the torrent is private.
* We don't accept DHT peers for a private torrent,
* but we participate in the DHT regardless. */
if( tr_dhtEnabled( handshake->session ) )
HANDSHAKE_SET_DHT( walk );
walk += HANDSHAKE_FLAGS_LEN;
memcpy( walk, torrentHash, SHA_DIGEST_LENGTH );
walk += SHA_DIGEST_LENGTH;
memcpy( walk, peer_id, PEER_ID_LEN );
walk += PEER_ID_LEN;
assert( strlen( ( const char* )peer_id ) == PEER_ID_LEN );
assert( walk - buf == HANDSHAKE_SIZE );
}
static int tr_handshakeDone( tr_handshake * handshake,
tr_bool isConnected );
enum
{
HANDSHAKE_OK,
HANDSHAKE_ENCRYPTION_WRONG,
HANDSHAKE_BAD_TORRENT,
HANDSHAKE_PEER_IS_SELF,
};
static int
parseHandshake( tr_handshake * handshake,
struct evbuffer * inbuf )
{
uint8_t name[HANDSHAKE_NAME_LEN];
uint8_t reserved[HANDSHAKE_FLAGS_LEN];
uint8_t hash[SHA_DIGEST_LENGTH];
const tr_torrent * tor;
const uint8_t * tor_peer_id;
uint8_t peer_id[PEER_ID_LEN];
dbgmsg( handshake, "payload: need %d, got %zu",
(int)HANDSHAKE_SIZE, evbuffer_get_length( inbuf ) );
if( evbuffer_get_length( inbuf ) < HANDSHAKE_SIZE )
return READ_LATER;
/* confirm the protocol */
tr_peerIoReadBytes( handshake->io, inbuf, name, HANDSHAKE_NAME_LEN );
if( memcmp( name, HANDSHAKE_NAME, HANDSHAKE_NAME_LEN ) )
return HANDSHAKE_ENCRYPTION_WRONG;
/* read the reserved bytes */
tr_peerIoReadBytes( handshake->io, inbuf, reserved, HANDSHAKE_FLAGS_LEN );
/* torrent hash */
tr_peerIoReadBytes( handshake->io, inbuf, hash, sizeof( hash ) );
assert( tr_peerIoHasTorrentHash( handshake->io ) );
if( !tr_torrentExists( handshake->session, hash )
|| memcmp( hash, tr_peerIoGetTorrentHash( handshake->io ),
SHA_DIGEST_LENGTH ) )
{
dbgmsg( handshake, "peer returned the wrong hash. wtf?" );
return HANDSHAKE_BAD_TORRENT;
}
/* peer_id */
tr_peerIoReadBytes( handshake->io, inbuf, peer_id, sizeof( peer_id ) );
tr_peerIoSetPeersId( handshake->io, peer_id );
/* peer id */
handshake->havePeerID = TRUE;
dbgmsg( handshake, "peer-id is [%*.*s]", PEER_ID_LEN, PEER_ID_LEN, peer_id );
tor = tr_torrentFindFromHash( handshake->session, hash );
tor_peer_id = tor && tor->peer_id ? tor->peer_id : tr_getPeerId( );
if( !memcmp( peer_id, tor_peer_id, PEER_ID_LEN ) )
{
dbgmsg( handshake, "streuth! we've connected to ourselves." );
return HANDSHAKE_PEER_IS_SELF;
}
/**
*** Extensions
**/
tr_peerIoEnableLTEP( handshake->io, HANDSHAKE_HAS_LTEP( reserved ) );
tr_peerIoEnableFEXT( handshake->io, HANDSHAKE_HAS_FASTEXT( reserved ) );
tr_peerIoEnableDHT( handshake->io, HANDSHAKE_HAS_DHT( reserved ) );
return HANDSHAKE_OK;
}
/***
****
**** OUTGOING CONNECTIONS
****
***/
/* 1 A->B: Diffie Hellman Ya, PadA */
static void
sendYa( tr_handshake * handshake )
{
int len;
const uint8_t * public_key;
char outbuf[ KEY_LEN + PadA_MAXLEN ], *walk=outbuf;
/* add our public key (Ya) */
public_key = tr_cryptoGetMyPublicKey( handshake->crypto, &len );
assert( len == KEY_LEN );
assert( public_key );
memcpy( walk, public_key, len );
walk += len;
/* add some bullshit padding */
len = tr_cryptoRandInt( PadA_MAXLEN );
tr_cryptoRandBuf( walk, len );
walk += len;
/* send it */
setReadState( handshake, AWAITING_YB );
tr_peerIoWriteBytes( handshake->io, outbuf, walk - outbuf, FALSE );
}
static uint32_t
getCryptoProvide( const tr_handshake * handshake )
{
uint32_t provide = 0;
switch( handshake->encryptionMode )
{
case TR_ENCRYPTION_REQUIRED:
case TR_ENCRYPTION_PREFERRED:
provide |= CRYPTO_PROVIDE_CRYPTO;
break;
case TR_CLEAR_PREFERRED:
provide |= CRYPTO_PROVIDE_CRYPTO | CRYPTO_PROVIDE_PLAINTEXT;
break;
}
return provide;
}
static uint32_t
getCryptoSelect( const tr_handshake * handshake,
uint32_t crypto_provide )
{
uint32_t choices[2];
int i, nChoices = 0;
switch( handshake->encryptionMode )
{
case TR_ENCRYPTION_REQUIRED:
choices[nChoices++] = CRYPTO_PROVIDE_CRYPTO;
break;
case TR_ENCRYPTION_PREFERRED:
choices[nChoices++] = CRYPTO_PROVIDE_CRYPTO;
choices[nChoices++] = CRYPTO_PROVIDE_PLAINTEXT;
break;
case TR_CLEAR_PREFERRED:
choices[nChoices++] = CRYPTO_PROVIDE_PLAINTEXT;
choices[nChoices++] = CRYPTO_PROVIDE_CRYPTO;
break;
}
for( i = 0; i < nChoices; ++i )
if( crypto_provide & choices[i] )
return choices[i];
return 0;
}
static int
readYb( tr_handshake * handshake, struct evbuffer * inbuf )
{
int isEncrypted;
const uint8_t * secret;
uint8_t yb[KEY_LEN];
struct evbuffer * outbuf;
size_t needlen = HANDSHAKE_NAME_LEN;
if( evbuffer_get_length( inbuf ) < needlen )
return READ_LATER;
isEncrypted = memcmp( evbuffer_pullup( inbuf, HANDSHAKE_NAME_LEN ), HANDSHAKE_NAME, HANDSHAKE_NAME_LEN );
if( isEncrypted )
{
needlen = KEY_LEN;
if( evbuffer_get_length( inbuf ) < needlen )
return READ_LATER;
}
dbgmsg( handshake, "got a %s handshake",
( isEncrypted ? "encrypted" : "plaintext" ) );
tr_peerIoSetEncryption( handshake->io, isEncrypted ? PEER_ENCRYPTION_RC4
: PEER_ENCRYPTION_NONE );
if( !isEncrypted )
{
setState( handshake, AWAITING_HANDSHAKE );
return READ_NOW;
}
handshake->haveReadAnythingFromPeer = TRUE;
/* compute the secret */
evbuffer_remove( inbuf, yb, KEY_LEN );
secret = tr_cryptoComputeSecret( handshake->crypto, yb );
memcpy( handshake->mySecret, secret, KEY_LEN );
/* now send these: HASH('req1', S), HASH('req2', SKEY) xor HASH('req3', S),
* ENCRYPT(VC, crypto_provide, len(PadC), PadC, len(IA)), ENCRYPT(IA) */
outbuf = evbuffer_new( );
/* HASH('req1', S) */
{
uint8_t req1[SHA_DIGEST_LENGTH];
tr_sha1( req1, "req1", 4, secret, KEY_LEN, NULL );
evbuffer_add( outbuf, req1, SHA_DIGEST_LENGTH );
}
/* HASH('req2', SKEY) xor HASH('req3', S) */
{
int i;
uint8_t req2[SHA_DIGEST_LENGTH];
uint8_t req3[SHA_DIGEST_LENGTH];
uint8_t buf[SHA_DIGEST_LENGTH];
tr_sha1( req2, "req2", 4,
tr_cryptoGetTorrentHash( handshake->crypto ),
SHA_DIGEST_LENGTH, NULL );
tr_sha1( req3, "req3", 4, secret, KEY_LEN, NULL );
for( i = 0; i < SHA_DIGEST_LENGTH; ++i )
buf[i] = req2[i] ^ req3[i];
evbuffer_add( outbuf, buf, SHA_DIGEST_LENGTH );
}
/* ENCRYPT(VC, crypto_provide, len(PadC), PadC
* PadC is reserved for future extensions to the handshake...
* standard practice at this time is for it to be zero-length */
{
uint8_t vc[VC_LENGTH] = { 0, 0, 0, 0, 0, 0, 0, 0 };
tr_peerIoWriteBuf( handshake->io, outbuf, FALSE );
tr_cryptoEncryptInit( handshake->crypto );
tr_peerIoSetEncryption( handshake->io, PEER_ENCRYPTION_RC4 );
evbuffer_add ( outbuf, vc, VC_LENGTH );
evbuffer_add_uint32 ( outbuf, getCryptoProvide( handshake ) );
evbuffer_add_uint16 ( outbuf, 0 );
}
/* ENCRYPT len(IA)), ENCRYPT(IA) */
{
uint8_t msg[HANDSHAKE_SIZE];
buildHandshakeMessage( handshake, msg );
evbuffer_add_uint16 ( outbuf, sizeof( msg ) );
evbuffer_add ( outbuf, msg, sizeof( msg ) );
handshake->haveSentBitTorrentHandshake = 1;
}
/* send it */
tr_cryptoDecryptInit( handshake->crypto );
setReadState( handshake, AWAITING_VC );
tr_peerIoWriteBuf( handshake->io, outbuf, FALSE );
/* cleanup */
evbuffer_free( outbuf );
return READ_LATER;
}
static int
readVC( tr_handshake * handshake,
struct evbuffer * inbuf )
{
const uint8_t key[VC_LENGTH] = { 0, 0, 0, 0, 0, 0, 0, 0 };
const int key_len = VC_LENGTH;
uint8_t tmp[VC_LENGTH];
/* note: this works w/o having to `unwind' the buffer if
* we read too much, but it is pretty brute-force.
* it would be nice to make this cleaner. */
for( ; ; )
{
if( evbuffer_get_length( inbuf ) < VC_LENGTH )
{
dbgmsg( handshake, "not enough bytes... returning read_more" );
return READ_LATER;
}
memcpy( tmp, evbuffer_pullup( inbuf, key_len ), key_len );
tr_cryptoDecryptInit( handshake->crypto );
tr_cryptoDecrypt( handshake->crypto, key_len, tmp, tmp );
if( !memcmp( tmp, key, key_len ) )
break;
evbuffer_drain( inbuf, 1 );
}
dbgmsg( handshake, "got it!" );
evbuffer_drain( inbuf, key_len );
setState( handshake, AWAITING_CRYPTO_SELECT );
return READ_NOW;
}
static int
readCryptoSelect( tr_handshake * handshake,
struct evbuffer * inbuf )
{
uint32_t crypto_select;
uint16_t pad_d_len;
const size_t needlen = sizeof( uint32_t ) + sizeof( uint16_t );
if( evbuffer_get_length( inbuf ) < needlen )
return READ_LATER;
tr_peerIoReadUint32( handshake->io, inbuf, &crypto_select );
handshake->crypto_select = crypto_select;
dbgmsg( handshake, "crypto select is %d", (int)crypto_select );
if( !( crypto_select & getCryptoProvide( handshake ) ) )
{
dbgmsg( handshake,
"peer selected an encryption option we didn't provide" );
return tr_handshakeDone( handshake, FALSE );
}
tr_peerIoReadUint16( handshake->io, inbuf, &pad_d_len );
dbgmsg( handshake, "pad_d_len is %d", (int)pad_d_len );
if( pad_d_len > 512 )
{
dbgmsg( handshake, "encryption handshake: pad_d_len is too long" );
return tr_handshakeDone( handshake, FALSE );
}
handshake->pad_d_len = pad_d_len;
setState( handshake, AWAITING_PAD_D );
return READ_NOW;
}
static int
readPadD( tr_handshake * handshake,
struct evbuffer * inbuf )
{
const size_t needlen = handshake->pad_d_len;
uint8_t * tmp;
dbgmsg( handshake, "pad d: need %zu, got %zu",
needlen, evbuffer_get_length( inbuf ) );
if( evbuffer_get_length( inbuf ) < needlen )
return READ_LATER;
tmp = tr_new( uint8_t, needlen );
tr_peerIoReadBytes( handshake->io, inbuf, tmp, needlen );
tr_free( tmp );
tr_peerIoSetEncryption( handshake->io, handshake->crypto_select );
setState( handshake, AWAITING_HANDSHAKE );
return READ_NOW;
}
/***
****
**** INCOMING CONNECTIONS
****
***/
static int
readHandshake( tr_handshake * handshake,
struct evbuffer * inbuf )
{
uint8_t pstrlen;
uint8_t * pstr;
uint8_t reserved[HANDSHAKE_FLAGS_LEN];
uint8_t hash[SHA_DIGEST_LENGTH];
dbgmsg( handshake, "payload: need %d, got %zu",
(int)INCOMING_HANDSHAKE_LEN, evbuffer_get_length( inbuf ) );
if( evbuffer_get_length( inbuf ) < INCOMING_HANDSHAKE_LEN )
return READ_LATER;
handshake->haveReadAnythingFromPeer = TRUE;
pstrlen = evbuffer_pullup( inbuf, 1 )[0]; /* peek, don't read. We may be
handing inbuf to AWAITING_YA */
if( pstrlen == 19 ) /* unencrypted */
{
tr_peerIoSetEncryption( handshake->io, PEER_ENCRYPTION_NONE );
if( handshake->encryptionMode == TR_ENCRYPTION_REQUIRED )
{
dbgmsg( handshake,
"peer is unencrypted, and we're disallowing that" );
return tr_handshakeDone( handshake, FALSE );
}
}
else /* encrypted or corrupt */
{
tr_peerIoSetEncryption( handshake->io, PEER_ENCRYPTION_RC4 );
if( tr_peerIoIsIncoming( handshake->io ) )
{
dbgmsg( handshake,
"I think peer is sending us an encrypted handshake..." );
setState( handshake, AWAITING_YA );
return READ_NOW;
}
tr_cryptoDecrypt( handshake->crypto, 1, &pstrlen, &pstrlen );
if( pstrlen != 19 )
{
dbgmsg( handshake,
"I think peer has sent us a corrupt handshake..." );
return tr_handshakeDone( handshake, FALSE );
}
}
evbuffer_drain( inbuf, 1 );
/* pstr (BitTorrent) */
pstr = tr_new( uint8_t, pstrlen + 1 );
tr_peerIoReadBytes( handshake->io, inbuf, pstr, pstrlen );
pstr[pstrlen] = '\0';
if( strcmp( (char*)pstr, "BitTorrent protocol" ) )
{
tr_free( pstr );
return tr_handshakeDone( handshake, FALSE );
}
tr_free( pstr );
/* reserved bytes */
tr_peerIoReadBytes( handshake->io, inbuf, reserved, sizeof( reserved ) );
/**
*** Extensions
**/
tr_peerIoEnableLTEP( handshake->io, HANDSHAKE_HAS_LTEP( reserved ) );
tr_peerIoEnableFEXT( handshake->io, HANDSHAKE_HAS_FASTEXT( reserved ) );
tr_peerIoEnableDHT( handshake->io, HANDSHAKE_HAS_DHT( reserved ) );
/* torrent hash */
tr_peerIoReadBytes( handshake->io, inbuf, hash, sizeof( hash ) );
if( tr_peerIoIsIncoming( handshake->io ) )
{
if( !tr_torrentExists( handshake->session, hash ) )
{
dbgmsg( handshake, "peer is trying to connect to us for a torrent we don't have." );
return tr_handshakeDone( handshake, FALSE );
}
else
{
assert( !tr_peerIoHasTorrentHash( handshake->io ) );
tr_peerIoSetTorrentHash( handshake->io, hash );
}
}
else /* outgoing */
{
assert( tr_peerIoHasTorrentHash( handshake->io ) );
if( memcmp( hash, tr_peerIoGetTorrentHash( handshake->io ),
SHA_DIGEST_LENGTH ) )
{
dbgmsg( handshake, "peer returned the wrong hash. wtf?" );
return tr_handshakeDone( handshake, FALSE );
}
}
/**
*** If it's an incoming message, we need to send a response handshake
**/
if( !handshake->haveSentBitTorrentHandshake )
{
uint8_t msg[HANDSHAKE_SIZE];
buildHandshakeMessage( handshake, msg );
tr_peerIoWriteBytes( handshake->io, msg, sizeof( msg ), FALSE );
handshake->haveSentBitTorrentHandshake = 1;
}
setReadState( handshake, AWAITING_PEER_ID );
return READ_NOW;
}
static int
readPeerId( tr_handshake * handshake,
struct evbuffer * inbuf )
{
tr_bool peerIsGood;
char client[128];
tr_torrent * tor;
const uint8_t * tor_peer_id;
uint8_t peer_id[PEER_ID_LEN];
if( evbuffer_get_length( inbuf ) < PEER_ID_LEN )
return READ_LATER;
/* peer id */
tr_peerIoReadBytes( handshake->io, inbuf, peer_id, PEER_ID_LEN );
tr_peerIoSetPeersId( handshake->io, peer_id );
handshake->havePeerID = TRUE;
tr_clientForId( client, sizeof( client ), peer_id );
dbgmsg( handshake, "peer-id is [%s] ... isIncoming is %d", client,
tr_peerIoIsIncoming( handshake->io ) );
/* if we've somehow connected to ourselves, don't keep the connection */
tor = tr_torrentFindFromHash( handshake->session, tr_peerIoGetTorrentHash( handshake->io ) );
tor_peer_id = tor && tor->peer_id ? tor->peer_id : tr_getPeerId( );
peerIsGood = memcmp( peer_id, tor_peer_id, PEER_ID_LEN ) != 0;
dbgmsg( handshake, "isPeerGood == %d", (int)peerIsGood );
return tr_handshakeDone( handshake, peerIsGood );
}
static int
readYa( tr_handshake * handshake,
struct evbuffer * inbuf )
{
uint8_t ya[KEY_LEN];
uint8_t * walk, outbuf[KEY_LEN + PadB_MAXLEN];
const uint8_t *myKey, *secret;
int len;
dbgmsg( handshake, "in readYa... need %d, have %zu",
(int)KEY_LEN, evbuffer_get_length( inbuf ) );
if( evbuffer_get_length( inbuf ) < KEY_LEN )
return READ_LATER;
/* read the incoming peer's public key */
evbuffer_remove( inbuf, ya, KEY_LEN );
secret = tr_cryptoComputeSecret( handshake->crypto, ya );
memcpy( handshake->mySecret, secret, KEY_LEN );
tr_sha1( handshake->myReq1, "req1", 4, secret, KEY_LEN, NULL );
dbgmsg( handshake, "sending B->A: Diffie Hellman Yb, PadB" );
/* send our public key to the peer */
walk = outbuf;
myKey = tr_cryptoGetMyPublicKey( handshake->crypto, &len );
memcpy( walk, myKey, len );
walk += len;
len = tr_cryptoRandInt( PadB_MAXLEN );
tr_cryptoRandBuf( walk, len );
walk += len;
setReadState( handshake, AWAITING_PAD_A );
tr_peerIoWriteBytes( handshake->io, outbuf, walk - outbuf, FALSE );
return READ_NOW;
}
static int
readPadA( tr_handshake * handshake, struct evbuffer * inbuf )
{
/* resynchronizing on HASH('req1',S) */
struct evbuffer_ptr ptr = evbuffer_search( inbuf, (const char*)handshake->myReq1, SHA_DIGEST_LENGTH, NULL );
if( ptr.pos != -1 ) /* match */
{
evbuffer_drain( inbuf, ptr.pos );
dbgmsg( handshake, "found it... looking setting to awaiting_crypto_provide" );
setState( handshake, AWAITING_CRYPTO_PROVIDE );
return READ_NOW;
}
else
{
const size_t len = evbuffer_get_length( inbuf );
if( len > SHA_DIGEST_LENGTH )
evbuffer_drain( inbuf, len - SHA_DIGEST_LENGTH );
return READ_LATER;
}
}
static int
readCryptoProvide( tr_handshake * handshake,
struct evbuffer * inbuf )
{
/* HASH('req2', SKEY) xor HASH('req3', S), ENCRYPT(VC, crypto_provide,
len(PadC)) */
int i;
uint8_t vc_in[VC_LENGTH];
uint8_t req2[SHA_DIGEST_LENGTH];
uint8_t req3[SHA_DIGEST_LENGTH];
uint8_t obfuscatedTorrentHash[SHA_DIGEST_LENGTH];
uint16_t padc_len = 0;
uint32_t crypto_provide = 0;
const size_t needlen = SHA_DIGEST_LENGTH /* HASH('req1',s) */
+ SHA_DIGEST_LENGTH /* HASH('req2', SKEY) xor
HASH('req3', S) */
+ VC_LENGTH
+ sizeof( crypto_provide )
+ sizeof( padc_len );
tr_torrent * tor = NULL;
if( evbuffer_get_length( inbuf ) < needlen )
return READ_LATER;
/* TODO: confirm they sent HASH('req1',S) here? */
evbuffer_drain( inbuf, SHA_DIGEST_LENGTH );
/* This next piece is HASH('req2', SKEY) xor HASH('req3', S) ...
* we can get the first half of that (the obufscatedTorrentHash)
* by building the latter and xor'ing it with what the peer sent us */
dbgmsg( handshake, "reading obfuscated torrent hash..." );
evbuffer_remove( inbuf, req2, SHA_DIGEST_LENGTH );
tr_sha1( req3, "req3", 4, handshake->mySecret, KEY_LEN, NULL );
for( i = 0; i < SHA_DIGEST_LENGTH; ++i )
obfuscatedTorrentHash[i] = req2[i] ^ req3[i];
if(( tor = tr_torrentFindFromObfuscatedHash( handshake->session, obfuscatedTorrentHash )))
{
const tr_bool clientIsSeed = tr_torrentIsSeed( tor );
const tr_bool peerIsSeed = tr_peerMgrPeerIsSeed( tor, tr_peerIoGetAddress( handshake->io, NULL ) );
dbgmsg( handshake, "got INCOMING connection's encrypted handshake for torrent [%s]",
tr_torrentName( tor ) );
tr_peerIoSetTorrentHash( handshake->io, tor->info.hash );
if( clientIsSeed && peerIsSeed )
{
dbgmsg( handshake, "another seed tried to reconnect to us!" );
return tr_handshakeDone( handshake, FALSE );
}
}
else
{
dbgmsg( handshake, "can't find that torrent..." );
return tr_handshakeDone( handshake, FALSE );
}
/* next part: ENCRYPT(VC, crypto_provide, len(PadC), */
tr_cryptoDecryptInit( handshake->crypto );
tr_peerIoReadBytes( handshake->io, inbuf, vc_in, VC_LENGTH );
tr_peerIoReadUint32( handshake->io, inbuf, &crypto_provide );
handshake->crypto_provide = crypto_provide;
dbgmsg( handshake, "crypto_provide is %d", (int)crypto_provide );
tr_peerIoReadUint16( handshake->io, inbuf, &padc_len );
dbgmsg( handshake, "padc is %d", (int)padc_len );
handshake->pad_c_len = padc_len;
setState( handshake, AWAITING_PAD_C );
return READ_NOW;
}
static int
readPadC( tr_handshake * handshake,
struct evbuffer * inbuf )
{
uint16_t ia_len;
const size_t needlen = handshake->pad_c_len + sizeof( uint16_t );
if( evbuffer_get_length( inbuf ) < needlen )
return READ_LATER;
evbuffer_drain( inbuf, handshake->pad_c_len );
tr_peerIoReadUint16( handshake->io, inbuf, &ia_len );
dbgmsg( handshake, "ia_len is %d", (int)ia_len );
handshake->ia_len = ia_len;
setState( handshake, AWAITING_IA );
return READ_NOW;
}
static int
readIA( tr_handshake * handshake,
struct evbuffer * inbuf )
{
const size_t needlen = handshake->ia_len;
struct evbuffer * outbuf;
uint32_t crypto_select;
dbgmsg( handshake, "reading IA... have %zu, need %zu",
evbuffer_get_length( inbuf ), needlen );
if( evbuffer_get_length( inbuf ) < needlen )
return READ_LATER;
/**
*** B->A: ENCRYPT(VC, crypto_select, len(padD), padD), ENCRYPT2(Payload Stream)
**/
tr_cryptoEncryptInit( handshake->crypto );
outbuf = evbuffer_new( );
dbgmsg( handshake, "sending vc" );
/* send VC */
{
uint8_t vc[VC_LENGTH];
memset( vc, 0, VC_LENGTH );
evbuffer_add( outbuf, vc, VC_LENGTH );
}
/* send crypto_select */
crypto_select = getCryptoSelect( handshake, handshake->crypto_provide );
if( crypto_select )
{
dbgmsg( handshake, "selecting crypto mode '%d'", (int)crypto_select );
evbuffer_add_uint32( outbuf, crypto_select );
}
else
{
dbgmsg( handshake, "peer didn't offer an encryption mode we like." );
evbuffer_free( outbuf );
return tr_handshakeDone( handshake, FALSE );
}
dbgmsg( handshake, "sending pad d" );
/* ENCRYPT(VC, crypto_provide, len(PadD), PadD
* PadD is reserved for future extensions to the handshake...
* standard practice at this time is for it to be zero-length */
{
const uint16_t len = 0;
evbuffer_add_uint16( outbuf, len );
}
/* maybe de-encrypt our connection */
if( crypto_select == CRYPTO_PROVIDE_PLAINTEXT )
{
tr_peerIoWriteBuf( handshake->io, outbuf, FALSE );
tr_peerIoSetEncryption( handshake->io, PEER_ENCRYPTION_NONE );
}
dbgmsg( handshake, "sending handshake" );
/* send our handshake */
{
uint8_t msg[HANDSHAKE_SIZE];
buildHandshakeMessage( handshake, msg );
evbuffer_add( outbuf, msg, sizeof( msg ) );
handshake->haveSentBitTorrentHandshake = 1;
}
/* send it out */
tr_peerIoWriteBuf( handshake->io, outbuf, FALSE );
evbuffer_free( outbuf );
/* now await the handshake */
setState( handshake, AWAITING_PAYLOAD_STREAM );
return READ_NOW;
}
static int
readPayloadStream( tr_handshake * handshake,
struct evbuffer * inbuf )
{
int i;
const size_t needlen = HANDSHAKE_SIZE;
dbgmsg( handshake, "reading payload stream... have %zu, need %zu",
evbuffer_get_length( inbuf ), needlen );
if( evbuffer_get_length( inbuf ) < needlen )
return READ_LATER;
/* parse the handshake ... */
i = parseHandshake( handshake, inbuf );
dbgmsg( handshake, "parseHandshake returned %d", i );
if( i != HANDSHAKE_OK )
return tr_handshakeDone( handshake, FALSE );
/* we've completed the BT handshake... pass the work on to peer-msgs */
return tr_handshakeDone( handshake, TRUE );
}
/***
****
****
****
***/
static ReadState
canRead( struct tr_peerIo * io, void * arg, size_t * piece )
{
tr_handshake * handshake = arg;
struct evbuffer * inbuf = tr_peerIoGetReadBuffer( io );
ReadState ret;
tr_bool readyForMore = TRUE;
assert( tr_isPeerIo( io ) );
/* no piece data in handshake */
*piece = 0;
dbgmsg( handshake, "handling canRead; state is [%s]",
getStateName( handshake->state ) );
while( readyForMore )
{
switch( handshake->state )
{
case AWAITING_HANDSHAKE:
ret = readHandshake ( handshake, inbuf ); break;
case AWAITING_PEER_ID:
ret = readPeerId ( handshake, inbuf ); break;
case AWAITING_YA:
ret = readYa ( handshake, inbuf ); break;
case AWAITING_PAD_A:
ret = readPadA ( handshake, inbuf ); break;
case AWAITING_CRYPTO_PROVIDE:
ret = readCryptoProvide( handshake, inbuf ); break;
case AWAITING_PAD_C:
ret = readPadC ( handshake, inbuf ); break;
case AWAITING_IA:
ret = readIA ( handshake, inbuf ); break;
case AWAITING_PAYLOAD_STREAM:
ret = readPayloadStream( handshake, inbuf ); break;
case AWAITING_YB:
ret = readYb ( handshake, inbuf ); break;
case AWAITING_VC:
ret = readVC ( handshake, inbuf ); break;
case AWAITING_CRYPTO_SELECT:
ret = readCryptoSelect ( handshake, inbuf ); break;
case AWAITING_PAD_D:
ret = readPadD ( handshake, inbuf ); break;
default:
assert( 0 );
}
if( ret != READ_NOW )
readyForMore = FALSE;
else if( handshake->state == AWAITING_PAD_C )
readyForMore = evbuffer_get_length( inbuf ) >= handshake->pad_c_len;
else if( handshake->state == AWAITING_PAD_D )
readyForMore = evbuffer_get_length( inbuf ) >= handshake->pad_d_len;
else if( handshake->state == AWAITING_IA )
readyForMore = evbuffer_get_length( inbuf ) >= handshake->ia_len;
}
return ret;
}
static tr_bool
fireDoneFunc( tr_handshake * handshake, tr_bool isConnected )
{
const uint8_t * peer_id = isConnected && handshake->havePeerID
? tr_peerIoGetPeersId( handshake->io )
: NULL;
const tr_bool success = ( *handshake->doneCB )( handshake,
handshake->io,
handshake->haveReadAnythingFromPeer,
isConnected,
peer_id,
handshake->doneUserData );
return success;
}
static void
tr_handshakeFree( tr_handshake * handshake )
{
if( handshake->io )
tr_peerIoUnref( handshake->io ); /* balanced by the ref in tr_handshakeNew */
event_free( handshake->timeout_timer );
tr_free( handshake );
}
static int
tr_handshakeDone( tr_handshake * handshake,
tr_bool isOK )
{
tr_bool success;
dbgmsg( handshake, "handshakeDone: %s", isOK ? "connected" : "aborting" );
tr_peerIoSetIOFuncs( handshake->io, NULL, NULL, NULL, NULL );
success = fireDoneFunc( handshake, isOK );
tr_handshakeFree( handshake );
return success ? READ_LATER : READ_ERR;
}
void
tr_handshakeAbort( tr_handshake * handshake )
{
if( handshake != NULL )
tr_handshakeDone( handshake, FALSE );
}
static void
gotError( tr_peerIo * io UNUSED,
short what,
void * vhandshake )
{
tr_handshake * handshake = vhandshake;
/* if the error happened while we were sending a public key, we might
* have encountered a peer that doesn't do encryption... reconnect and
* try a plaintext handshake */
if( ( ( handshake->state == AWAITING_YB )
|| ( handshake->state == AWAITING_VC ) )
&& ( handshake->encryptionMode != TR_ENCRYPTION_REQUIRED )
&& ( !tr_peerIoReconnect( handshake->io ) ) )
{
uint8_t msg[HANDSHAKE_SIZE];
dbgmsg( handshake, "handshake failed, trying plaintext..." );
buildHandshakeMessage( handshake, msg );
handshake->haveSentBitTorrentHandshake = 1;
setReadState( handshake, AWAITING_HANDSHAKE );
tr_peerIoWriteBytes( handshake->io, msg, sizeof( msg ), FALSE );
}
else
{
dbgmsg( handshake, "libevent got an error what==%d, errno=%d (%s)",
(int)what, errno, tr_strerror( errno ) );
tr_handshakeDone( handshake, FALSE );
}
}
/**
***
**/
static void
handshakeTimeout( int foo UNUSED, short bar UNUSED, void * handshake )
{
tr_handshakeAbort( handshake );
}
tr_handshake*
tr_handshakeNew( tr_peerIo * io,
tr_encryption_mode encryptionMode,
handshakeDoneCB doneCB,
void * doneUserData )
{
tr_handshake * handshake;
tr_session * session = tr_peerIoGetSession( io );
handshake = tr_new0( tr_handshake, 1 );
handshake->io = io;
handshake->crypto = tr_peerIoGetCrypto( io );
handshake->encryptionMode = encryptionMode;
handshake->doneCB = doneCB;
handshake->doneUserData = doneUserData;
handshake->session = session;
handshake->timeout_timer = evtimer_new( session->event_base, handshakeTimeout, handshake );
tr_timerAdd( handshake->timeout_timer, HANDSHAKE_TIMEOUT_SEC, 0 );
tr_peerIoRef( io ); /* balanced by the unref in tr_handshakeFree */
tr_peerIoSetIOFuncs( handshake->io, canRead, NULL, gotError, handshake );
tr_peerIoSetEncryption( io, PEER_ENCRYPTION_NONE );
if( tr_peerIoIsIncoming( handshake->io ) )
setReadState( handshake, AWAITING_HANDSHAKE );
else if( encryptionMode != TR_CLEAR_PREFERRED )
sendYa( handshake );
else
{
uint8_t msg[HANDSHAKE_SIZE];
buildHandshakeMessage( handshake, msg );
handshake->haveSentBitTorrentHandshake = 1;
setReadState( handshake, AWAITING_HANDSHAKE );
tr_peerIoWriteBytes( handshake->io, msg, sizeof( msg ), FALSE );
}
return handshake;
}
struct tr_peerIo*
tr_handshakeGetIO( tr_handshake * handshake )
{
assert( handshake );
assert( handshake->io );
return handshake->io;
}
struct tr_peerIo*
tr_handshakeStealIO( tr_handshake * handshake )
{
struct tr_peerIo * io;
assert( handshake );
assert( handshake->io );
io = handshake->io;
handshake->io = NULL;
return io;
}
const tr_address *
tr_handshakeGetAddr( const struct tr_handshake * handshake,
tr_port * port )
{
assert( handshake );
assert( handshake->io );
return tr_peerIoGetAddress( handshake->io, port );
}