mirror of
https://github.com/transmission/transmission
synced 2024-12-26 01:27:28 +00:00
489 lines
14 KiB
C
489 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 inline int
|
|
azmsgIdIndex( int id )
|
|
{
|
|
int ii;
|
|
|
|
for( ii = 0; azmsgCount() > ii; ii++ )
|
|
{
|
|
if( id == azmsgId( ii ) )
|
|
{
|
|
return ii;
|
|
}
|
|
}
|
|
|
|
assert( 0 );
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline 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, * idval, * clientval, * versval;
|
|
benc_val_t * tcpval, * msgsval, * msgdictval, * msgidval, * msgversval;
|
|
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_bencDictAppendNofree( &val,
|
|
"identity", &idval,
|
|
"client", &clientval,
|
|
"version", &versval,
|
|
"tcp_port", &tcpval,
|
|
"messages", &msgsval,
|
|
NULL ) )
|
|
{
|
|
free( buf );
|
|
tr_bencFree( &val );
|
|
return NULL;
|
|
}
|
|
|
|
/* fill in the dictionary values */
|
|
tr_bencInitStr( idval, tor->azId, TR_AZ_ID_LEN, 1 );
|
|
tr_bencInitStr( clientval, TR_NAME, 0, 1 );
|
|
tr_bencInitStr( versval, VERSION_STRING, 0, 1 );
|
|
tr_bencInitInt( tcpval, tor->publicPort );
|
|
tr_bencInit( msgsval, TYPE_LIST );
|
|
|
|
/* fill in the supported 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;
|
|
}
|
|
if( tr_bencListAppend( msgsval, &msgdictval, NULL ) )
|
|
{
|
|
tr_bencFree( &val );
|
|
free( buf );
|
|
return NULL;
|
|
}
|
|
/* each item in the list is a dict with id and ver keys */
|
|
tr_bencInit( msgdictval, TYPE_DICT );
|
|
if( tr_bencDictAppendNofree( msgdictval,
|
|
"id", &msgidval,
|
|
"ver", &msgversval,
|
|
NULL ) )
|
|
{
|
|
tr_bencFree( &val );
|
|
free( buf );
|
|
return NULL;
|
|
}
|
|
tr_bencInitStr( msgidval, azmsgStr( idx ), azmsgLen( idx ), 1 );
|
|
tr_bencInitStr( msgversval, &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, jj;
|
|
tr_peertree_entry_t * ii;
|
|
|
|
tr_bencInit( val, TYPE_LIST );
|
|
count = peertreeCount( tree );
|
|
if( 0 == count )
|
|
{
|
|
return 0;
|
|
}
|
|
if( tr_bencListExtend( val, count ) )
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
ii = peertreeFirst( tree );
|
|
jj = val->val.l.count;
|
|
while( NULL != ii )
|
|
{
|
|
assert( jj < val->val.l.alloc );
|
|
tr_bencInitStr( &val->val.l.vals[jj], ii->peer, 6, 1 );
|
|
val->val.l.count++;
|
|
ii = peertreeNext( tree, ii );
|
|
jj++;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static char *
|
|
makeAZPex( tr_torrent_t * tor, tr_peer_t * peer, int * len )
|
|
{
|
|
benc_val_t val;
|
|
char * buf;
|
|
|
|
assert( !peer->private );
|
|
tr_bencInitStr( &val, tor->info.hash, sizeof( tor->info.hash ), 1 );
|
|
buf = makeCommonPex( tor, peer, len, peertreeToBencAZ, "infohash", &val );
|
|
|
|
return buf;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
len = peer->outMessagesSize;
|
|
free( peer->outMessages );
|
|
peer->outMessages = NULL;
|
|
peer->outMessagesSize = 0;
|
|
peer->outMessagesPos = 0;
|
|
|
|
return len;
|
|
}
|
|
|
|
static inline 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 */
|
|
TR_NTOHL( buf, msglen );
|
|
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 */
|
|
TR_NTOHL( buf + off, namelen );
|
|
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( "unsupported Azureus message version %hhu", vers );
|
|
msgid = AZ_MSG_INVALID;
|
|
}
|
|
else if( 0 > index )
|
|
{
|
|
name[namelen] = '\0';
|
|
peer_dbg( "got unknown Azureus message: \"%s\"", name );
|
|
name[namelen] = vers;
|
|
msgid = AZ_MSG_INVALID;
|
|
}
|
|
else
|
|
{
|
|
msgid = azmsgId( index );
|
|
}
|
|
|
|
*msgidret = msgid;
|
|
*msglenret = msglen;
|
|
|
|
return off;
|
|
}
|
|
|
|
static inline 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( "invalid bencoding in Azureus handshake" );
|
|
return TR_ERROR;
|
|
}
|
|
|
|
if( TYPE_DICT != val.type )
|
|
{
|
|
peer_dbg( "Azureus handshake is not a dictionary" );
|
|
tr_bencFree( &val );
|
|
return TR_ERROR;
|
|
}
|
|
|
|
/* 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( NULL == sub && TYPE_LIST != sub->type )
|
|
{
|
|
tr_bencFree( &val );
|
|
peer_dbg( "Azureus handshake is missing 'messages'" );
|
|
return TR_ERROR;
|
|
}
|
|
|
|
/* 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", azmsgStr( ii ) );
|
|
tr_bitfieldFree( msgs );
|
|
return TR_ERROR;
|
|
}
|
|
}
|
|
tr_bitfieldFree( msgs );
|
|
|
|
return TR_OK;
|
|
}
|
|
|
|
static inline 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;
|
|
|
|
if( peer->private || PEX_PEER_CUTOFF <= tor->peerCount )
|
|
{
|
|
return TR_OK;
|
|
}
|
|
|
|
if( tr_bencLoad( buf, len, &val, NULL ) )
|
|
{
|
|
peer_dbg( "invalid bencoding in Azureus peer exchange" );
|
|
return TR_ERROR;
|
|
}
|
|
if( TYPE_DICT != val.type )
|
|
{
|
|
tr_bencFree( &val );
|
|
peer_dbg( "Azureus peer exchange is 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( "Azureus peer exchange has missing or invalid 'infohash'" );
|
|
return TR_ERROR;
|
|
}
|
|
|
|
list = tr_bencDictFind( &val, "added" );
|
|
if( NULL == list || TYPE_LIST != list->type )
|
|
{
|
|
tr_bencFree( &val );
|
|
return TR_OK;
|
|
}
|
|
|
|
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 )
|
|
{
|
|
tr_torrentAddCompact( tor, TR_PEER_FROM_PEX,
|
|
( uint8_t * )pair->val.s.s, 1 );
|
|
}
|
|
}
|
|
|
|
tr_bencFree( &val );
|
|
|
|
return TR_OK;
|
|
}
|