518 lines
14 KiB
C
518 lines
14 KiB
C
/******************************************************************************
|
|
* $Id$
|
|
*
|
|
* Copyright (c) 2006-2007 Transmission authors and contributors
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the "Software"),
|
|
* to deal in the Software without restriction, including without limitation
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
* DEALINGS IN THE SOFTWARE.
|
|
*****************************************************************************/
|
|
|
|
#define AZ_EXT_VERSION 1
|
|
|
|
#define AZ_MSG_BT_HANDSHAKE -1
|
|
#define AZ_MSG_BT_KEEP_ALIVE -2
|
|
#define AZ_MSG_AZ_HANDSHAKE -3
|
|
#define AZ_MSG_AZ_PEER_EXCHANGE -4
|
|
#define AZ_MSG_INVALID -5
|
|
|
|
#define AZ_MSG_IS_OPTIONAL( id ) \
|
|
( AZ_MSG_AZ_PEER_EXCHANGE == (id) || AZ_MSG_IS_UNUSED( id ) )
|
|
#define AZ_MSG_IS_UNUSED( id ) \
|
|
( AZ_MSG_AZ_HANDSHAKE == (id) || AZ_MSG_BT_HANDSHAKE == (id) )
|
|
|
|
static const struct
|
|
{
|
|
char * label;
|
|
const int len;
|
|
const int id;
|
|
}
|
|
az_msgs[] = {
|
|
{ "AZ_PEER_EXCHANGE", 16, AZ_MSG_AZ_PEER_EXCHANGE },
|
|
{ "AZ_HANDSHAKE", 12, AZ_MSG_AZ_HANDSHAKE },
|
|
{ "BT_HANDSHAKE", 12, AZ_MSG_BT_HANDSHAKE },
|
|
{ "BT_KEEP_ALIVE", 13, AZ_MSG_BT_KEEP_ALIVE },
|
|
{ "BT_CHOKE", 8, PEER_MSG_CHOKE },
|
|
{ "BT_UNCHOKE", 10, PEER_MSG_UNCHOKE },
|
|
{ "BT_INTERESTED", 13, PEER_MSG_INTERESTED },
|
|
{ "BT_UNINTERESTED", 15, PEER_MSG_UNINTERESTED },
|
|
{ "BT_HAVE", 7, PEER_MSG_HAVE },
|
|
{ "BT_BITFIELD", 11, PEER_MSG_BITFIELD },
|
|
{ "BT_REQUEST", 10, PEER_MSG_REQUEST },
|
|
{ "BT_PIECE", 8, PEER_MSG_PIECE },
|
|
{ "BT_CANCEL", 9, PEER_MSG_CANCEL },
|
|
};
|
|
#define azmsgStr( idx ) ( az_msgs[(idx)].label )
|
|
#define azmsgLen( idx ) ( az_msgs[(idx)].len )
|
|
#define azmsgId( idx ) ( az_msgs[(idx)].id )
|
|
#define azmsgCount() ( (int)(sizeof( az_msgs ) / sizeof( az_msgs[0] ) ) )
|
|
|
|
static int
|
|
azmsgIdIndex( int id )
|
|
{
|
|
int ii;
|
|
|
|
for( ii = 0; azmsgCount() > ii; ii++ )
|
|
{
|
|
if( id == azmsgId( ii ) )
|
|
{
|
|
return ii;
|
|
}
|
|
}
|
|
|
|
assert( 0 );
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
azmsgNameIndex( const char * name, int len )
|
|
{
|
|
int ii;
|
|
|
|
for( ii = 0; azmsgCount() > ii; ii++ )
|
|
{
|
|
if( azmsgLen( ii ) == len &&
|
|
0 == memcmp( azmsgStr( ii ), name, len ) )
|
|
{
|
|
return ii;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
static uint8_t *
|
|
makeAZHandshake( tr_torrent_t * tor, tr_peer_t * peer, int * buflen )
|
|
{
|
|
char * buf;
|
|
benc_val_t val, * msgsval, * msgdictval;
|
|
int len, max, idx;
|
|
uint8_t vers;
|
|
|
|
*buflen = 0;
|
|
idx = azmsgIdIndex( AZ_MSG_AZ_HANDSHAKE );
|
|
len = 4 + 4 + azmsgLen( idx ) + 1;
|
|
buf = malloc( len );
|
|
if( NULL == buf )
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
/* set length to zero for now, we won't know it until after bencoding */
|
|
tr_htonl( 0, buf );
|
|
/* set name length, name, and version */
|
|
tr_htonl( azmsgLen( idx ), buf + 4 );
|
|
memcpy( buf + 8, azmsgStr( idx ), azmsgLen( idx ) );
|
|
buf[8 + azmsgLen( idx )] = AZ_EXT_VERSION;
|
|
|
|
/* start building a dictionary for handshake data */
|
|
tr_bencInit( &val, TYPE_DICT );
|
|
if( tr_bencDictReserve( &val, 5 ) )
|
|
{
|
|
free( buf );
|
|
tr_bencFree( &val );
|
|
return NULL;
|
|
}
|
|
|
|
/* fill in the dictionary values */
|
|
tr_bencInitStr( tr_bencDictAdd( &val, "identity" ),
|
|
tor->azId, TR_AZ_ID_LEN, 1 );
|
|
tr_bencInitStr( tr_bencDictAdd( &val, "client" ), TR_NAME, 0, 1 );
|
|
tr_bencInitStr( tr_bencDictAdd( &val, "version" ), SHORT_VERSION_STRING, 0, 1 );
|
|
if( 0 < tor->publicPort )
|
|
{
|
|
tr_bencInitInt( tr_bencDictAdd( &val, "tcp_port" ), tor->publicPort );
|
|
}
|
|
|
|
/* initialize supported message list */
|
|
msgsval = tr_bencDictAdd( &val, "messages" );
|
|
tr_bencInit( msgsval, TYPE_LIST );
|
|
if( tr_bencListReserve( msgsval, azmsgCount() ) )
|
|
{
|
|
tr_bencFree( &val );
|
|
free( buf );
|
|
return NULL;
|
|
}
|
|
|
|
/* fill in the message list */
|
|
vers = AZ_EXT_VERSION;
|
|
for( idx = 0; azmsgCount() > idx; idx++ )
|
|
{
|
|
if( AZ_MSG_IS_UNUSED( azmsgId( idx ) ) )
|
|
{
|
|
continue;
|
|
}
|
|
if( AZ_MSG_AZ_PEER_EXCHANGE == azmsgId( idx ) && peer->private )
|
|
{
|
|
/* no point in saying we can do pex if the torrent is private */
|
|
continue;
|
|
}
|
|
/* each item in the list is a dict with id and ver keys */
|
|
msgdictval = tr_bencListAdd( msgsval );
|
|
tr_bencInit( msgdictval, TYPE_DICT );
|
|
if( tr_bencDictReserve( msgdictval, 2 ) )
|
|
{
|
|
tr_bencFree( &val );
|
|
free( buf );
|
|
return NULL;
|
|
}
|
|
tr_bencInitStr( tr_bencDictAdd( msgdictval, "id" ),
|
|
azmsgStr( idx ), azmsgLen( idx ), 1 );
|
|
tr_bencInitStr( tr_bencDictAdd( msgdictval, "ver" ), &vers, 1, 1 );
|
|
}
|
|
|
|
/* bencode the dictionary and append it to the buffer */
|
|
max = len;
|
|
if( tr_bencSave( &val, &buf, &len, &max ) )
|
|
{
|
|
tr_bencFree( &val );
|
|
free( buf );
|
|
return NULL;
|
|
}
|
|
|
|
tr_bencFree( &val );
|
|
/* we know the length now, fill it in */
|
|
tr_htonl( len - 4, buf );
|
|
|
|
/* XXX is there a way to tell azureus that the public port has changed? */
|
|
peer->advertisedPort = tor->publicPort;
|
|
|
|
*buflen = len;
|
|
return ( uint8_t * )buf;
|
|
}
|
|
|
|
static int
|
|
peertreeToBencAZ( tr_peertree_t * tree, benc_val_t * val )
|
|
{
|
|
int count;
|
|
tr_peertree_entry_t * ii;
|
|
|
|
tr_bencInit( val, TYPE_LIST );
|
|
count = peertreeCount( tree );
|
|
if( 0 == count )
|
|
{
|
|
return 0;
|
|
}
|
|
if( tr_bencListReserve( val, count ) )
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
ii = peertreeFirst( tree );
|
|
while( NULL != ii )
|
|
{
|
|
tr_bencInitStr( tr_bencListAdd( val ), ii->peer, 6, 1 );
|
|
ii = peertreeNext( tree, ii );
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
makeAZPex( tr_torrent_t * tor, tr_peer_t * peer, char ** buf, int * len )
|
|
{
|
|
benc_val_t val;
|
|
|
|
assert( !peer->private );
|
|
tr_bencInitStr( &val, tor->info.hash, sizeof( tor->info.hash ), 1 );
|
|
return makeCommonPex( tor, peer, peertreeToBencAZ, "infohash", &val,
|
|
buf, len);
|
|
}
|
|
|
|
static int
|
|
sendAZHandshake( tr_torrent_t * tor, tr_peer_t * peer )
|
|
{
|
|
uint8_t * buf;
|
|
int len;
|
|
|
|
/* XXX this is kind of evil to use this buffer like this */
|
|
if( NULL == peer->outMessages )
|
|
{
|
|
buf = makeAZHandshake( tor, peer, &len );
|
|
if( NULL == buf )
|
|
{
|
|
return TR_NET_CLOSE;
|
|
}
|
|
peer->outMessages = buf;
|
|
peer->outMessagesSize = len;
|
|
peer->outMessagesPos = 0;
|
|
}
|
|
|
|
len = tr_netSend( peer->socket, peer->outMessages + peer->outMessagesPos,
|
|
peer->outMessagesSize - peer->outMessagesPos );
|
|
if( peer->outMessagesPos + len < peer->outMessagesSize )
|
|
{
|
|
peer->outMessagesPos += len;
|
|
return TR_NET_BLOCK;
|
|
}
|
|
|
|
peer_dbg( "SEND azureus-handshake" );
|
|
|
|
len = peer->outMessagesSize;
|
|
free( peer->outMessages );
|
|
peer->outMessages = NULL;
|
|
peer->outMessagesSize = 0;
|
|
peer->outMessagesPos = 0;
|
|
|
|
return len;
|
|
}
|
|
|
|
static int
|
|
parseAZMessageHeader( tr_peer_t * peer, uint8_t * buf, int len,
|
|
int * msgidret, int * msglenret )
|
|
{
|
|
uint8_t * name, vers;
|
|
int off, namelen, msglen, index, msgid;
|
|
|
|
if( 8 > len )
|
|
{
|
|
return TR_NET_BLOCK;
|
|
}
|
|
/* message length */
|
|
msglen = tr_ntohl( buf );
|
|
msglen += 4;
|
|
off = 4;
|
|
if( msglen > len )
|
|
{
|
|
return TR_NET_BLOCK;
|
|
}
|
|
if( 9 > msglen )
|
|
{
|
|
peer_dbg( "azureus peer message is too short to make sense" );
|
|
return TR_NET_CLOSE;
|
|
}
|
|
/* name length */
|
|
namelen = tr_ntohl( buf + off );
|
|
off += 4;
|
|
if( off + namelen + 1 > msglen )
|
|
{
|
|
peer_dbg( "azureus peer message name is too long to make sense" );
|
|
return TR_NET_CLOSE;
|
|
}
|
|
/* message name */
|
|
name = buf + off;
|
|
off += namelen;
|
|
/* message version */
|
|
vers = buf[off];
|
|
off++;
|
|
/* get payload length from message length */
|
|
msglen -= off;
|
|
|
|
index = azmsgNameIndex( ( char * )name, namelen );
|
|
if( AZ_EXT_VERSION != vers )
|
|
{
|
|
/* XXX should we close the connection here? */
|
|
peer_dbg( "GET unsupported azureus message version %hhu", vers );
|
|
msgid = AZ_MSG_INVALID;
|
|
}
|
|
else if( 0 > index )
|
|
{
|
|
name[namelen] = '\0';
|
|
peer_dbg( "GET unknown azureus message: \"%s\"", name );
|
|
name[namelen] = vers;
|
|
msgid = AZ_MSG_INVALID;
|
|
}
|
|
else
|
|
{
|
|
msgid = azmsgId( index );
|
|
}
|
|
|
|
*msgidret = msgid;
|
|
*msglenret = msglen;
|
|
|
|
return off;
|
|
}
|
|
|
|
static int
|
|
parseAZHandshake( tr_peer_t * peer, uint8_t * buf, int len )
|
|
{
|
|
benc_val_t val, * sub, * dict, * subsub;
|
|
tr_bitfield_t * msgs;
|
|
int ii, idx;
|
|
|
|
if( tr_bencLoad( buf, len, &val, NULL ) )
|
|
{
|
|
peer_dbg( "GET azureus-handshake, invalid bencoding" );
|
|
return TR_ERROR;
|
|
}
|
|
|
|
if( TYPE_DICT != val.type )
|
|
{
|
|
peer_dbg( "GET azureus-handshake, data not a dictionary" );
|
|
tr_bencFree( &val );
|
|
return TR_ERROR;
|
|
}
|
|
|
|
#if 0 /* ugh, we have to deal with encoding if we do this */
|
|
/* get peer's client name */
|
|
sub = tr_bencDictFind( &val, "client" );
|
|
sub2 = tr_bencDictFind( &val, "version" );
|
|
if( NULL != sub && TYPE_STR == sub->type &&
|
|
NULL != sub2 && TYPE_STR == sub->type )
|
|
{
|
|
if( NULL == peer->client ||
|
|
( 0 != strncmp( peer->client, sub->val.s.s, sub->val.s.i ) ||
|
|
' ' != peer->client[sub->val.s.i] ||
|
|
0 != strcmp( peer->client + sub->val.s.i + 1, sub2->val.s.s ) ) )
|
|
{
|
|
client = NULL;
|
|
asprintf( &client, "%s %s", sub->val.s.s, sub2->val.s.s );
|
|
if( NULL != client )
|
|
{
|
|
free( peer->client );
|
|
peer->client = client;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* get the peer's listening port */
|
|
sub = tr_bencDictFind( &val, "tcp_port" );
|
|
if( NULL != sub )
|
|
{
|
|
if( TYPE_INT == sub->type && 0x0 < sub->val.i && 0xffff >= sub->val.i )
|
|
{
|
|
peer->port = htons( sub->val.i );
|
|
}
|
|
}
|
|
|
|
/* find the supported message list */
|
|
sub = tr_bencDictFind( &val, "messages" );
|
|
if( !sub || sub->type != TYPE_LIST )
|
|
{
|
|
tr_bencFree( &val );
|
|
peer_dbg( "GET azureus-handshake, missing 'messages'" );
|
|
return TR_ERROR;
|
|
}
|
|
|
|
peer_dbg( "GET azureus-handshake, ok" );
|
|
|
|
/* fill bitmask with supported message info */
|
|
msgs = tr_bitfieldNew( azmsgCount() );
|
|
for( ii = 0; ii < sub->val.l.count; ii++ )
|
|
{
|
|
dict = &sub->val.l.vals[ii];
|
|
if( TYPE_DICT != dict->type )
|
|
{
|
|
continue;
|
|
}
|
|
subsub = tr_bencDictFind( dict, "id" );
|
|
if( NULL == subsub || TYPE_STR != subsub->type )
|
|
{
|
|
continue;
|
|
}
|
|
idx = azmsgNameIndex( subsub->val.s.s, subsub->val.s.i );
|
|
if( 0 > idx )
|
|
{
|
|
continue;
|
|
}
|
|
subsub = tr_bencDictFind( dict, "ver" );
|
|
if( NULL == subsub || TYPE_STR != subsub->type ||
|
|
1 != subsub->val.s.i || AZ_EXT_VERSION > subsub->val.s.s[0] )
|
|
{
|
|
continue;
|
|
}
|
|
tr_bitfieldAdd( msgs, idx );
|
|
}
|
|
|
|
tr_bencFree( &val );
|
|
|
|
/* check bitmask to see if we're missing any messages */
|
|
for( ii = 0; azmsgCount() > ii; ii++ )
|
|
{
|
|
if( AZ_MSG_AZ_PEER_EXCHANGE == azmsgId( ii ) )
|
|
{
|
|
peer->pexStatus = tr_bitfieldHas( msgs, ii );
|
|
}
|
|
if( !AZ_MSG_IS_OPTIONAL( azmsgId( ii ) ) &&
|
|
!tr_bitfieldHas( msgs, ii ) )
|
|
{
|
|
peer_dbg( "azureus message %s not supported by peer",
|
|
azmsgStr( ii ) );
|
|
tr_bitfieldFree( msgs );
|
|
return TR_ERROR;
|
|
}
|
|
}
|
|
tr_bitfieldFree( msgs );
|
|
|
|
return TR_OK;
|
|
}
|
|
|
|
static int
|
|
parseAZPex( tr_torrent_t * tor, tr_peer_t * peer, uint8_t * buf, int len )
|
|
{
|
|
tr_info_t * info = &tor->info;
|
|
benc_val_t val, * list, * pair;
|
|
int ii, used;
|
|
|
|
if( peer->private || PEX_PEER_CUTOFF <= tor->peerCount )
|
|
{
|
|
peer_dbg( "GET azureus-pex, ignoring p=%i c=(%i<=%i)",
|
|
peer->private, PEX_PEER_CUTOFF, tor->peerCount );
|
|
return TR_OK;
|
|
}
|
|
|
|
if( tr_bencLoad( buf, len, &val, NULL ) )
|
|
{
|
|
peer_dbg( "GET azureus-pex, invalid bencoding" );
|
|
return TR_ERROR;
|
|
}
|
|
if( TYPE_DICT != val.type )
|
|
{
|
|
tr_bencFree( &val );
|
|
peer_dbg( "GET azureus-pex, data not a dictionary" );
|
|
return TR_ERROR;
|
|
}
|
|
|
|
list = tr_bencDictFind( &val, "infohash" );
|
|
if( NULL == list || TYPE_STR != list->type ||
|
|
sizeof( info->hash ) != list->val.s.i ||
|
|
0 != memcmp( info->hash, list->val.s.s, sizeof( info->hash ) ) )
|
|
{
|
|
tr_bencFree( &val );
|
|
peer_dbg( "GET azureus-pex, bad infohash" );
|
|
return TR_ERROR;
|
|
}
|
|
|
|
list = tr_bencDictFind( &val, "added" );
|
|
if( NULL == list || TYPE_LIST != list->type )
|
|
{
|
|
peer_dbg( "GET azureus-pex, no peers" );
|
|
tr_bencFree( &val );
|
|
return TR_OK;
|
|
}
|
|
|
|
used = 0;
|
|
for( ii = 0; ii < list->val.l.count; ii++ )
|
|
{
|
|
pair = &list->val.l.vals[ii];
|
|
if( TYPE_STR == pair->type && 6 == pair->val.s.i )
|
|
{
|
|
used += tr_torrentAddCompact( tor, TR_PEER_FROM_PEX,
|
|
( uint8_t * )pair->val.s.s, 1 );
|
|
}
|
|
}
|
|
|
|
peer_dbg( "GET azureus-pex, found %i peers, using %i",
|
|
list->val.l.count, used );
|
|
|
|
tr_bencFree( &val );
|
|
|
|
return TR_OK;
|
|
}
|