/* * This file Copyright (C) 2007-2008 Charles Kerr * * 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 #include #include #include /* UCHAR_MAX */ #include #include #include #include "transmission.h" #include "bencode.h" #include "clients.h" #include "crypto.h" #include "handshake.h" #include "peer-io.h" #include "peer-mgr.h" #include "torrent.h" #include "trevent.h" #include "utils.h" /* enable LibTransmission extension protocol */ #define ENABLE_LTEP */ /* enable fast peers extension protocol */ /* #define ENABLE_FASTPEER */ /*** **** ***/ #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 }; #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_FASTPEER #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 /* 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) ) struct tr_handshake { unsigned int havePeerID : 1; unsigned int haveSentBitTorrentHandshake : 1; tr_peerIo * io; tr_crypto * crypto; struct tr_handle * handle; uint8_t myPublicKey[KEY_LEN]; uint8_t mySecret[KEY_LEN]; uint8_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]; uint8_t peer_id[PEER_ID_LEN]; handshakeDoneCB doneCB; void * doneUserData; }; /** *** **/ enum { /* incoming */ AWAITING_HANDSHAKE, AWAITING_PEER_ID, AWAITING_YA, AWAITING_PAD_A, AWAITING_CRYPTO_PROVIDE, AWAITING_PAD_C, AWAITING_IA, /* outgoing */ AWAITING_YB, AWAITING_VC, AWAITING_CRYPTO_SELECT, AWAITING_PAD_D, }; /** *** **/ #define dbgmsg(handshake, fmt...) tr_deepLog( __FILE__, __LINE__, tr_peerIoGetAddrStr( handshake->io ), ##fmt ) static const char* getStateName( short 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, short state ) { dbgmsg( handshake, "setting to state [%s]", getStateName(state) ); handshake->state = state; } static void setReadState( tr_handshake * handshake, int state ) { setState( handshake, state ); } static uint8_t * buildHandshakeMessage( tr_handshake * handshake, int * setme_len ) { uint8_t * buf = tr_new0( uint8_t, HANDSHAKE_SIZE ); uint8_t * walk = buf; const uint8_t * torrentHash = tr_cryptoGetTorrentHash( handshake->crypto ); const uint8_t * peerId = 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 ); walk += HANDSHAKE_FLAGS_LEN; memcpy( walk, torrentHash, SHA_DIGEST_LENGTH ); walk += SHA_DIGEST_LENGTH; memcpy( walk, peerId, PEER_ID_LEN ); walk += PEER_ID_LEN; assert( strlen( ( const char* )peerId ) == PEER_ID_LEN ); assert( walk-buf == HANDSHAKE_SIZE ); *setme_len = walk - buf; return buf; } static void tr_handshakeDone( tr_handshake * handshake, int 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]; dbgmsg( handshake, "payload: need %d, got %d", (int)HANDSHAKE_SIZE, (int)EVBUFFER_LENGTH(inbuf) ); if( EVBUFFER_LENGTH(inbuf) < HANDSHAKE_SIZE ) return READ_MORE; /* 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_torrentExists( handshake->handle, hash ) ); assert( tr_peerIoHasTorrentHash( handshake->io ) ); if( 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, handshake->peer_id, sizeof(handshake->peer_id) ); tr_peerIoSetPeersId( handshake->io, handshake->peer_id ); /* peer id */ handshake->havePeerID = TRUE; dbgmsg( handshake, "peer-id is [%*.*s]", PEER_ID_LEN, PEER_ID_LEN, handshake->peer_id ); if( !memcmp( handshake->peer_id, tr_getPeerId(), PEER_ID_LEN ) ) { dbgmsg( handshake, "streuth! we've connected to ourselves." ); return HANDSHAKE_PEER_IS_SELF; } /** *** Extensions **/ if( HANDSHAKE_HAS_LTEP( reserved ) ) { tr_peerIoEnableLTEP( handshake->io, 1 ); dbgmsg(handshake,"using ltep" ); } if( HANDSHAKE_HAS_FASTEXT( reserved ) ) { tr_peerIoEnableFEXT( handshake->io, 1 ); dbgmsg(handshake,"using fext" ); } return HANDSHAKE_OK; } /*** **** **** OUTGOING CONNECTIONS **** ***/ /* 1 A->B: Diffie Hellman Ya, PadA */ static void sendYa( tr_handshake * handshake ) { int i; int len; const uint8_t * public_key; struct evbuffer * outbuf = evbuffer_new( ); uint8_t pad_a[PadA_MAXLEN]; /* add our public key (Ya) */ public_key = tr_cryptoGetMyPublicKey( handshake->crypto, &len ); assert( len == KEY_LEN ); assert( public_key != NULL ); evbuffer_add( outbuf, public_key, len ); /* add some bullshit padding */ len = tr_rand( PadA_MAXLEN ); for( i=0; iio, outbuf ); /* cleanup */ evbuffer_free( outbuf ); } 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_PLAINTEXT_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[4]; 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_PLAINTEXT_PREFERRED: choices[nChoices++] = CRYPTO_PROVIDE_PLAINTEXT; choices[nChoices++] = CRYPTO_PROVIDE_CRYPTO; break; } for( i=0; iio, isEncrypted ? PEER_ENCRYPTION_RC4 : PEER_ENCRYPTION_NONE ); if( !isEncrypted ) { setState( handshake, AWAITING_HANDSHAKE ); return READ_AGAIN; } /* 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; icrypto ); tr_peerIoSetEncryption( handshake->io, PEER_ENCRYPTION_RC4 ); tr_peerIoWriteBytes( handshake->io, outbuf, vc, VC_LENGTH ); tr_peerIoWriteUint32( handshake->io, outbuf, getCryptoProvide( handshake ) ); tr_peerIoWriteUint16( handshake->io, outbuf, 0 ); } /* ENCRYPT len(IA)), ENCRYPT(IA) */ { int msglen; uint8_t * msg = buildHandshakeMessage( handshake, &msglen ); tr_peerIoWriteUint16( handshake->io, outbuf, msglen ); tr_peerIoWriteBytes( handshake->io, outbuf, msg, msglen ); handshake->haveSentBitTorrentHandshake = 1; tr_free( msg ); } /* send it */ tr_cryptoDecryptInit( handshake->crypto ); setReadState( handshake, AWAITING_VC ); tr_peerIoWriteBuf( handshake->io, outbuf ); /* cleanup */ evbuffer_free( outbuf ); return READ_DONE; } 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_LENGTH(inbuf) < VC_LENGTH ) { dbgmsg( handshake, "not enough bytes... returning read_more" ); return READ_MORE; } memcpy( tmp, EVBUFFER_DATA(inbuf), 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_AGAIN; } 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_LENGTH(inbuf) < needlen ) return READ_MORE; 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" ); tr_handshakeDone( handshake, FALSE ); return READ_DONE; } 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" ); tr_handshakeDone( handshake, FALSE ); return READ_DONE; } handshake->pad_d_len = pad_d_len; setState( handshake, AWAITING_PAD_D ); return READ_AGAIN; } 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 %d, got %d", (int)needlen, (int)EVBUFFER_LENGTH(inbuf) ); if( EVBUFFER_LENGTH(inbuf) < needlen ) return READ_MORE; 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_AGAIN; } /*** **** **** 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 %d", (int)INCOMING_HANDSHAKE_LEN, (int)EVBUFFER_LENGTH(inbuf) ); if( EVBUFFER_LENGTH(inbuf) < INCOMING_HANDSHAKE_LEN ) return READ_MORE; pstrlen = EVBUFFER_DATA(inbuf)[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" ); tr_handshakeDone( handshake, FALSE ); return READ_DONE; } } 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_AGAIN; } tr_cryptoDecrypt( handshake->crypto, 1, &pstrlen, &pstrlen ); if( pstrlen != 19 ) { dbgmsg( handshake, "I think peer has sent us a corrupt handshake..." ); tr_handshakeDone( handshake, FALSE ); return READ_DONE; } } 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 ); tr_handshakeDone( handshake, FALSE ); return READ_DONE; } tr_free( pstr ); /* reserved bytes */ tr_peerIoReadBytes( handshake->io, inbuf, reserved, sizeof(reserved) ); /** *** Extension negotiation **/ if( HANDSHAKE_HAS_LTEP( reserved ) ) { tr_peerIoEnableLTEP( handshake->io, 1 ); dbgmsg( handshake,"using ltep" ); } if( HANDSHAKE_HAS_FASTEXT( reserved ) ) { tr_peerIoEnableFEXT( handshake->io, 1 ); dbgmsg( handshake,"using fext" ); } /* torrent hash */ tr_peerIoReadBytes( handshake->io, inbuf, hash, sizeof(hash) ); if( tr_peerIoIsIncoming( handshake->io ) ) { if( !tr_torrentExists( handshake->handle, hash ) ) { dbgmsg( handshake, "peer is trying to connect to us for a torrent we don't have." ); tr_handshakeDone( handshake, FALSE ); return READ_DONE; } 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?" ); tr_handshakeDone( handshake, FALSE ); return READ_DONE; } } /** *** If this is an incoming message, then we need to send a response handshake **/ if( !handshake->haveSentBitTorrentHandshake ) { int msgSize; uint8_t * msg = buildHandshakeMessage( handshake, &msgSize ); tr_peerIoWrite( handshake->io, msg, msgSize ); tr_free( msg ); handshake->haveSentBitTorrentHandshake = 1; } setReadState( handshake, AWAITING_PEER_ID ); return READ_AGAIN; } static int readPeerId( tr_handshake * handshake, struct evbuffer * inbuf ) { int peerIsGood; char client[128]; if( EVBUFFER_LENGTH(inbuf) < PEER_ID_LEN ) return READ_MORE; /* peer id */ tr_peerIoReadBytes( handshake->io, inbuf, handshake->peer_id, PEER_ID_LEN ); tr_peerIoSetPeersId( handshake->io, handshake->peer_id ); handshake->havePeerID = TRUE; tr_clientForId( client, sizeof( client ), handshake->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 */ peerIsGood = memcmp( handshake->peer_id, tr_getPeerId(), PEER_ID_LEN ) ? 1 : 0; dbgmsg( handshake, "isPeerGood == %d", peerIsGood ); tr_handshakeDone( handshake, peerIsGood ); return READ_DONE; } 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 %d", (int)KEY_LEN, (int)EVBUFFER_LENGTH( inbuf ) ); if( EVBUFFER_LENGTH( inbuf ) < KEY_LEN ) return READ_MORE; /* 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_rand( PadB_MAXLEN ); while( len-- ) *walk++ = tr_rand( UCHAR_MAX ); setReadState( handshake, AWAITING_PAD_A ); tr_peerIoWrite( handshake->io, outbuf, walk-outbuf ); return READ_AGAIN; } static int readPadA( tr_handshake * handshake, struct evbuffer * inbuf ) { uint8_t * pch; dbgmsg( handshake, "looking to get past pad a... & resync on hash('req',S) ... have %d bytes", (int)EVBUFFER_LENGTH(inbuf) ); /** *** Resynchronizing on HASH('req1',S) **/ pch = memchr( EVBUFFER_DATA(inbuf), handshake->myReq1[0], EVBUFFER_LENGTH(inbuf) ); if( pch == NULL ) { dbgmsg( handshake, "no luck so far.. draining %d bytes", (int)EVBUFFER_LENGTH(inbuf) ); evbuffer_drain( inbuf, EVBUFFER_LENGTH(inbuf) ); return READ_MORE; } dbgmsg( handshake, "looking for hash('req',S) ... draining %d bytes", (int)(pch-EVBUFFER_DATA(inbuf)) ); evbuffer_drain( inbuf, pch-EVBUFFER_DATA(inbuf) ); if( EVBUFFER_LENGTH(inbuf) < SHA_DIGEST_LENGTH ) return READ_MORE; if( memcmp( EVBUFFER_DATA(inbuf), handshake->myReq1, SHA_DIGEST_LENGTH ) ) { dbgmsg( handshake, "draining one more byte" ); evbuffer_drain( inbuf, 1 ); return READ_AGAIN; } dbgmsg( handshake, "found it... looking setting to awaiting_crypto_provide" ); setState( handshake, AWAITING_CRYPTO_PROVIDE ); return READ_AGAIN; } 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_LENGTH(inbuf) < needlen ) return READ_MORE; /* 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; ihandle, obfuscatedTorrentHash ))) { dbgmsg( handshake, "found the torrent; it's [%s]", tor->info.name ); tr_peerIoSetTorrentHash( handshake->io, tor->info.hash ); if( !tr_torrentAllowsPex( tor ) && tr_peerMgrPeerIsSeed( handshake->handle->peerMgr, tor->info.hash, tr_peerIoGetAddress( handshake->io, NULL ))) { dbgmsg( handshake, "a peer has tried to reconnect to us!" ); tr_handshakeDone( handshake, FALSE ); return READ_DONE; } } else { dbgmsg( handshake, "can't find that torrent..." ); tr_handshakeDone( handshake, FALSE ); return READ_DONE; } /* 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_AGAIN; } 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_LENGTH(inbuf) < needlen ) return READ_MORE; 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_AGAIN; } static int readIA( tr_handshake * handshake, struct evbuffer * inbuf ) { int i; const size_t needlen = handshake->ia_len; struct evbuffer * outbuf; uint32_t crypto_select; dbgmsg( handshake, "reading IA... have %d, need %d", (int)EVBUFFER_LENGTH(inbuf), (int)needlen ); if( EVBUFFER_LENGTH(inbuf) < needlen ) return READ_MORE; dbgmsg( handshake, "reading IA..." ); /* parse the handshake ... */ i = parseHandshake( handshake, inbuf ); dbgmsg( handshake, "parseHandshake returned %d", i ); if( i != HANDSHAKE_OK ) { tr_handshakeDone( handshake, FALSE ); return READ_DONE; } /** *** 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 ); tr_peerIoWriteBytes( handshake->io, 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 ); tr_peerIoWriteUint32( handshake->io, outbuf, crypto_select ); } else { dbgmsg( handshake, "peer didn't offer an encryption mode we like." ); evbuffer_free( outbuf ); tr_handshakeDone( handshake, FALSE ); return READ_DONE; } dbgmsg( handshake, "sending pad d" ); /* ENCRYPT(VC, crypto_provide, len(PadC), PadC * PadD is reserved for future extensions to the handshake... * standard practice at this time is for it to be zero-length */ { const int len = 0; tr_peerIoWriteUint16( handshake->io, outbuf, len ); } /* maybe de-encrypt our connection */ if( crypto_select == CRYPTO_PROVIDE_PLAINTEXT ) tr_peerIoSetEncryption( handshake->io, PEER_ENCRYPTION_NONE ); dbgmsg( handshake, "sending handshake" ); /* send our handshake */ { int msgSize; uint8_t * msg = buildHandshakeMessage( handshake, &msgSize ); tr_peerIoWriteBytes( handshake->io, outbuf, msg, msgSize ); handshake->haveSentBitTorrentHandshake = 1; tr_free( msg ); } /* send it out */ tr_peerIoWriteBuf( handshake->io, outbuf ); evbuffer_free( outbuf ); /* we've completed the BT handshake... pass the work on to peer-msgs */ tr_handshakeDone( handshake, TRUE ); return READ_DONE; } /*** **** **** **** ***/ static ReadState canRead( struct bufferevent * evin, void * arg ) { tr_handshake * handshake = (tr_handshake *) arg; struct evbuffer * inbuf = EVBUFFER_INPUT ( evin ); ReadState ret; dbgmsg( handshake, "handling canRead; state is [%s]", getStateName(handshake->state) ); 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_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 ); } return ret; } static void fireDoneFunc( tr_handshake * handshake, int isConnected ) { const uint8_t * peer_id = isConnected && handshake->havePeerID ? handshake->peer_id : NULL; (*handshake->doneCB)( handshake, handshake->io, isConnected, peer_id, handshake->doneUserData ); } void tr_handshakeDone( tr_handshake * handshake, int isOK ) { dbgmsg( handshake, "handshakeDone: %s", isOK ? "connected" : "aborting" ); tr_peerIoSetIOFuncs( handshake->io, NULL, NULL, NULL, NULL ); fireDoneFunc( handshake, isOK ); tr_free( handshake ); } void tr_handshakeAbort( tr_handshake * handshake ) { tr_handshakeDone( handshake, FALSE ); } static void gotError( struct bufferevent * evbuf UNUSED, short what, void * arg ) { tr_handshake * handshake = (tr_handshake *) arg; /* 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 ) ) ) { int msgSize; uint8_t * msg; dbgmsg( handshake, "handshake failed, trying plaintext..." ); msg = buildHandshakeMessage( handshake, &msgSize ); handshake->haveSentBitTorrentHandshake = 1; setReadState( handshake, AWAITING_HANDSHAKE ); tr_peerIoWrite( handshake->io, msg, msgSize ); tr_free( msg ); } else { dbgmsg( handshake, "libevent got an error what==%d, errno=%d (%s)", (int)what, errno, tr_strerror(errno) ); tr_handshakeDone( handshake, FALSE ); } } /** *** **/ tr_handshake* tr_handshakeNew( tr_peerIo * io, tr_encryption_mode encryptionMode, handshakeDoneCB doneCB, void * doneUserData ) { tr_handshake * handshake; handshake = tr_new0( tr_handshake, 1 ); handshake->io = io; handshake->crypto = tr_peerIoGetCrypto( io ); handshake->encryptionMode = encryptionMode; handshake->doneCB = doneCB; handshake->doneUserData = doneUserData; handshake->handle = tr_peerIoGetHandle( io ); tr_peerIoSetTimeoutSecs( io, 15 ); tr_peerIoSetIOFuncs( handshake->io, canRead, NULL, gotError, handshake ); if( tr_peerIoIsIncoming( handshake->io ) ) setReadState( handshake, AWAITING_HANDSHAKE ); else if( encryptionMode != TR_PLAINTEXT_PREFERRED ) sendYa( handshake ); else { int msgSize; uint8_t * msg = buildHandshakeMessage( handshake, &msgSize ); handshake->haveSentBitTorrentHandshake = 1; setReadState( handshake, AWAITING_HANDSHAKE ); tr_peerIoWrite( handshake->io, msg, msgSize ); tr_free( msg ); } return handshake; } const struct in_addr * tr_handshakeGetAddr( const struct tr_handshake * handshake, uint16_t * port ) { assert( handshake != NULL ); assert( handshake->io != NULL ); return tr_peerIoGetAddress( handshake->io, port ); }