/* * This file Copyright (C) 2007-2014 Mnemosyne LLC * * It may be used under the GNU GPL versions 2 or 3 * or any future license endorsed by Mnemosyne LLC. * */ #include #include #include /* strcmp (), strlen (), strncmp () */ #include #include #include "transmission.h" #include "clients.h" #include "crypto-utils.h" #include "handshake.h" #include "log.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, INCOMING_HANDSHAKE_LEN = 48, /* Encryption Constants */ PadA_MAXLEN = 512, PadB_MAXLEN = 512, PadC_MAXLEN = 512, PadD_MAXLEN = 512, VC_LENGTH = 8, 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) != 0) #define HANDSHAKE_SET_LTEP(bits) ((bits)[5] |= 0x10) #else #define HANDSHAKE_HAS_LTEP(bits) (false) #define HANDSHAKE_SET_LTEP(bits) ((void)0) #endif #ifdef ENABLE_FAST #define HANDSHAKE_HAS_FASTEXT(bits) (((bits)[7] & 0x04) != 0) #define HANDSHAKE_SET_FASTEXT(bits) ((bits)[7] |= 0x04) #else #define HANDSHAKE_HAS_FASTEXT(bits) (false) #define HANDSHAKE_SET_FASTEXT(bits) ((void)0) #endif #ifdef ENABLE_DHT #define HANDSHAKE_HAS_DHT(bits) (((bits)[7] & 0x01) != 0) #define HANDSHAKE_SET_DHT(bits) ((bits)[7] |= 0x01) #else #define HANDSHAKE_HAS_DHT(bits) (false) #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 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, /* */ N_STATES } handshake_state_t; struct tr_handshake { bool haveReadAnythingFromPeer; bool havePeerID; bool haveSentBitTorrentHandshake; tr_peerIo* io; tr_crypto* crypto; tr_session* session; 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; }; /** *** **/ #define dbgmsg(handshake, ...) \ do \ { \ if (tr_logGetDeepEnabled()) \ { \ tr_logAddDeep(__FILE__, __LINE__, tr_peerIoGetAddrStr(handshake->io), __VA_ARGS__); \ } \ } \ while (0) static const char* getStateName(const handshake_state_t state) { static const char* const state_strings[N_STATES] = { "awaiting handshake", /* AWAITING_HANDSHAKE */ "awaiting peer id", /* AWAITING_PEER_ID */ "awaiting ya", /* AWAITING_YA */ "awaiting pad a", /* AWAITING_PAD_A */ "awaiting crypto_provide", /* AWAITING_CRYPTO_PROVIDE */ "awaiting pad c", /* AWAITING_PAD_C */ "awaiting ia", /* AWAITING_IA */ "awaiting payload stream", /* AWAITING_PAYLOAD_STREAM */ "awaiting yb", /* AWAITING_YB */ "awaiting vc", /* AWAITING_VC */ "awaiting crypto select", /* AWAITING_CRYPTO_SELECT */ "awaiting pad d" /* AWAITING_PAD_D */ }; return state < N_STATES ? state_strings[state] : "unknown state"; } 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 bool buildHandshakeMessage(tr_handshake* handshake, uint8_t* buf) { const unsigned char* peer_id = NULL; const uint8_t* torrentHash; tr_torrent* tor; bool success; if ((torrentHash = tr_cryptoGetTorrentHash(handshake->crypto))) { if ((tor = tr_torrentFindFromHash(handshake->session, torrentHash))) { peer_id = tr_torrentGetPeerId(tor); } } if (peer_id == NULL) { success = false; } else { uint8_t* walk = buf; 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(walk - buf == HANDSHAKE_SIZE); success = true; } return success; } static ReadState tr_handshakeDone(tr_handshake* handshake, bool isConnected); typedef enum { HANDSHAKE_OK, HANDSHAKE_ENCRYPTION_WRONG, HANDSHAKE_BAD_TORRENT, HANDSHAKE_PEER_IS_SELF, } handshake_parse_err_t; static handshake_parse_err_t 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]; tr_torrent* tor; uint8_t peer_id[PEER_ID_LEN]; dbgmsg(handshake, "payload: need %d, got %zu", HANDSHAKE_SIZE, evbuffer_get_length(inbuf)); if (evbuffer_get_length(inbuf) < HANDSHAKE_SIZE) { return HANDSHAKE_ENCRYPTION_WRONG; } /* confirm the protocol */ tr_peerIoReadBytes(handshake->io, inbuf, name, HANDSHAKE_NAME_LEN); if (memcmp(name, HANDSHAKE_NAME, HANDSHAKE_NAME_LEN) != 0) { 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) != 0) { 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); if (memcmp(peer_id, tr_torrentGetPeerId(tor), PEER_ID_LEN) == 0) { dbgmsg(handshake, "streuth! we've connected to ourselves."); return HANDSHAKE_PEER_IS_SELF; } /** *** Extensions **/ tr_peerIoEnableDHT(handshake->io, HANDSHAKE_HAS_DHT(reserved)); tr_peerIoEnableLTEP(handshake->io, HANDSHAKE_HAS_LTEP(reserved)); tr_peerIoEnableFEXT(handshake->io, HANDSHAKE_HAS_FASTEXT(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]; char* 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_rand_int(PadA_MAXLEN); tr_rand_buffer(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; int 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 void computeRequestHash(const tr_handshake* handshake, const char* name, uint8_t* hash) { tr_cryptoSecretKeySha1(handshake->crypto, name, 4, NULL, 0, hash); } static ReadState readYb(tr_handshake* handshake, struct evbuffer* inbuf) { bool isEncrypted; 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) != 0; if (isEncrypted) { needlen = KEY_LEN; if (evbuffer_get_length(inbuf) < needlen) { return READ_LATER; } } dbgmsg(handshake, "got an %s handshake", (isEncrypted ? "encrypted" : "plain")); 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); if (!tr_cryptoComputeSecret(handshake->crypto, yb)) { return tr_handshakeDone(handshake, false); } /* 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]; computeRequestHash(handshake, "req1", req1); 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); computeRequestHash(handshake, "req3", req3); 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]; if (!buildHandshakeMessage(handshake, msg)) { return tr_handshakeDone(handshake, false); } evbuffer_add_uint16(outbuf, sizeof(msg)); evbuffer_add(outbuf, msg, sizeof(msg)); handshake->haveSentBitTorrentHandshake = true; } /* send it */ tr_cryptoDecryptInit(handshake->crypto); setReadState(handshake, AWAITING_VC); tr_peerIoWriteBuf(handshake->io, outbuf, false); /* cleanup */ evbuffer_free(outbuf); return READ_LATER; } static ReadState readVC(tr_handshake* handshake, struct evbuffer* inbuf) { uint8_t tmp[VC_LENGTH]; const int key_len = VC_LENGTH; const uint8_t key[VC_LENGTH] = { 0, 0, 0, 0, 0, 0, 0, 0 }; /* 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) == 0) { break; } evbuffer_drain(inbuf, 1); } dbgmsg(handshake, "got it!"); evbuffer_drain(inbuf, key_len); setState(handshake, AWAITING_CRYPTO_SELECT); return READ_NOW; } static ReadState readCryptoSelect(tr_handshake* handshake, struct evbuffer* inbuf) { uint16_t pad_d_len; uint32_t crypto_select; static 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 offer"); 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 ReadState readPadD(tr_handshake* handshake, struct evbuffer* inbuf) { const size_t needlen = handshake->pad_d_len; dbgmsg(handshake, "pad d: need %zu, got %zu", needlen, evbuffer_get_length(inbuf)); if (evbuffer_get_length(inbuf) < needlen) { return READ_LATER; } tr_peerIoDrain(handshake->io, inbuf, needlen); tr_peerIoSetEncryption(handshake->io, handshake->crypto_select); setState(handshake, AWAITING_HANDSHAKE); return READ_NOW; } /*** **** **** INCOMING CONNECTIONS **** ***/ static ReadState readHandshake(tr_handshake* handshake, struct evbuffer* inbuf) { uint8_t pstrlen; uint8_t pstr[20]; uint8_t reserved[HANDSHAKE_FLAGS_LEN]; uint8_t hash[SHA_DIGEST_LENGTH]; dbgmsg(handshake, "payload: need %d, got %zu", 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) */ assert(pstrlen == 19); tr_peerIoReadBytes(handshake->io, inbuf, pstr, pstrlen); pstr[pstrlen] = '\0'; if (strncmp((const char*)pstr, "BitTorrent protocol", 19) != 0) { return tr_handshakeDone(handshake, false); } /* reserved bytes */ tr_peerIoReadBytes(handshake->io, inbuf, reserved, sizeof(reserved)); /** *** Extensions **/ tr_peerIoEnableDHT(handshake->io, HANDSHAKE_HAS_DHT(reserved)); tr_peerIoEnableLTEP(handshake->io, HANDSHAKE_HAS_LTEP(reserved)); tr_peerIoEnableFEXT(handshake->io, HANDSHAKE_HAS_FASTEXT(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) != 0) { 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]; if (!buildHandshakeMessage(handshake, msg)) { return tr_handshakeDone(handshake, false); } tr_peerIoWriteBytes(handshake->io, msg, sizeof(msg), false); handshake->haveSentBitTorrentHandshake = true; } setReadState(handshake, AWAITING_PEER_ID); return READ_NOW; } static ReadState readPeerId(tr_handshake* handshake, struct evbuffer* inbuf) { bool connected_to_self; char client[128]; uint8_t peer_id[PEER_ID_LEN]; tr_torrent* tor; 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)); connected_to_self = (tor != NULL) && memcmp(peer_id, tr_torrentGetPeerId(tor), PEER_ID_LEN) == 0; return tr_handshakeDone(handshake, !connected_to_self); } static ReadState readYa(tr_handshake* handshake, struct evbuffer* inbuf) { uint8_t ya[KEY_LEN]; uint8_t* walk, outbuf[KEY_LEN + PadB_MAXLEN]; const uint8_t* myKey; int len; dbgmsg(handshake, "in readYa... need %d, have %zu", 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); if (!tr_cryptoComputeSecret(handshake->crypto, ya)) { return tr_handshakeDone(handshake, false); } computeRequestHash(handshake, "req1", handshake->myReq1); /* send our public key to the peer */ dbgmsg(handshake, "sending B->A: Diffie Hellman Yb, PadB"); walk = outbuf; myKey = tr_cryptoGetMyPublicKey(handshake->crypto, &len); memcpy(walk, myKey, len); walk += len; len = tr_rand_int(PadB_MAXLEN); tr_rand_buffer(walk, len); walk += len; setReadState(handshake, AWAITING_PAD_A); tr_peerIoWriteBytes(handshake->io, outbuf, walk - outbuf, false); return READ_NOW; } static ReadState 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 ReadState 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; tr_torrent* tor; 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); 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); computeRequestHash(handshake, "req3", req3); for (i = 0; i < SHA_DIGEST_LENGTH; ++i) { obfuscatedTorrentHash[i] = req2[i] ^ req3[i]; } if ((tor = tr_torrentFindFromObfuscatedHash(handshake->session, obfuscatedTorrentHash))) { const bool clientIsSeed = tr_torrentIsSeed(tor); const 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 ReadState readPadC(tr_handshake* handshake, struct evbuffer* inbuf) { char* padc; uint16_t ia_len; const size_t needlen = handshake->pad_c_len + sizeof(uint16_t); if (evbuffer_get_length(inbuf) < needlen) { return READ_LATER; } /* read the throwaway padc */ padc = tr_new(char, handshake->pad_c_len); tr_peerIoReadBytes(handshake->io, inbuf, padc, handshake->pad_c_len); tr_free(padc); /* read ia_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 ReadState 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(); { /* send VC */ uint8_t vc[VC_LENGTH]; memset(vc, 0, VC_LENGTH); evbuffer_add(outbuf, vc, VC_LENGTH); dbgmsg(handshake, "sending vc"); } /* 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]; if (!buildHandshakeMessage(handshake, msg)) { return tr_handshakeDone(handshake, false); } evbuffer_add(outbuf, msg, sizeof(msg)); handshake->haveSentBitTorrentHandshake = true; } /* 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 ReadState readPayloadStream(tr_handshake* handshake, struct evbuffer* inbuf) { handshake_parse_err_t 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) { ReadState ret; tr_handshake* handshake = arg; struct evbuffer* inbuf = tr_peerIoGetReadBuffer(io); 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 bool fireDoneFunc(tr_handshake* handshake, bool isConnected) { const uint8_t* peer_id = isConnected && handshake->havePeerID ? tr_peerIoGetPeersId(handshake->io) : NULL; const 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 ReadState tr_handshakeDone(tr_handshake* handshake, bool isOK) { 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, short what, void* vhandshake) { int errcode = errno; tr_handshake* handshake = vhandshake; if (io->utp_socket && !io->isIncoming && handshake->state == AWAITING_YB) { /* This peer probably doesn't speak uTP. */ tr_torrent* tor; if (tr_peerIoHasTorrentHash(io)) { tor = tr_torrentFindFromHash(handshake->session, tr_peerIoGetTorrentHash(io)); } else { tor = NULL; } /* Don't mark a peer as non-uTP unless it's really a connect failure. */ if ((errcode == ETIMEDOUT || errcode == ECONNREFUSED) && tr_isTorrent(tor)) { tr_peerMgrSetUtpFailed(tor, tr_peerIoGetAddress(io, NULL), true); } if (!tr_peerIoReconnect(handshake->io)) { uint8_t msg[HANDSHAKE_SIZE]; buildHandshakeMessage(handshake, msg); handshake->haveSentBitTorrentHandshake = true; setReadState(handshake, AWAITING_HANDSHAKE); tr_peerIoWriteBytes(handshake->io, msg, sizeof(msg), false); } } /* 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 = true; 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(evutil_socket_t 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 = true; setReadState(handshake, AWAITING_HANDSHAKE); tr_peerIoWriteBytes(handshake->io, msg, sizeof(msg), false); } return handshake; } struct tr_peerIo* tr_handshakeGetIO(tr_handshake* handshake) { assert(handshake != NULL); assert(handshake->io != NULL); return handshake->io; } struct tr_peerIo* tr_handshakeStealIO(tr_handshake* handshake) { struct tr_peerIo* io; assert(handshake != NULL); assert(handshake->io != NULL); io = handshake->io; handshake->io = NULL; return io; } const tr_address* tr_handshakeGetAddr(const struct tr_handshake* handshake, tr_port* port) { assert(handshake != NULL); assert(handshake->io != NULL); return tr_peerIoGetAddress(handshake->io, port); }