/****************************************************************************** * $Id$ * * Copyright (c) 2007 Joshua Elsasser * * 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. *****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "bsdtree.h" #include "errors.h" #include "misc.h" #include "torrents.h" #define EXIT_TIMEOUT 10 /* how many seconds to wait on exit */ #define TIMER_SECS 1 /* timer interval seconds */ #define TIMER_USECS 0 /* timer interval microseconds */ struct tor { int id; uint8_t hash[SHA_DIGEST_LENGTH]; tr_torrent * tor; RB_ENTRY( tor ) idlinks; RB_ENTRY( tor ) hashlinks; }; RB_HEAD( tortree, tor ); RB_HEAD( hashtree, tor ); static struct tor * opentor ( const char *, const char *, uint8_t *, size_t, const char * ); static void closetor ( struct tor *, int ); static void starttimer ( int ); static void timerfunc ( int, short, void * ); static int loadstate ( void ); static int savestate ( void ); static struct event_base * gl_base = NULL; static tr_handle * gl_handle = NULL; static struct tortree gl_tree = RB_INITIALIZER( &gl_tree ); static struct hashtree gl_hashes = RB_INITIALIZER( &gl_hashes ); static int gl_lastid = 0; static struct event gl_event; static time_t gl_exiting = 0; static int gl_exitval = 0; static char gl_state[MAXPATHLEN]; static char gl_newstate[MAXPATHLEN]; static int gl_autostart = 1; static int gl_pex = 1; static int gl_port = TR_DEFAULT_PORT; static int gl_mapping = 0; static int gl_uplimit = -1; static int gl_downlimit = -1; static char gl_dir[MAXPATHLEN]; static tr_encryption_mode gl_crypto = TR_ENCRYPTION_PREFERRED; static int torhashcmp( struct tor * left, struct tor * right ) { return memcmp( left->hash, right->hash, sizeof left->hash ); } RB_GENERATE_STATIC( hashtree, tor, hashlinks, torhashcmp ) INTCMP_FUNC( toridcmp, tor, id ) RB_GENERATE_STATIC( tortree, tor, idlinks, toridcmp ) void torrent_init( struct event_base * base ) { assert( NULL == gl_handle && NULL == gl_base ); gl_base = base; gl_handle = tr_init( tr_getDefaultConfigDir(), "daemon" ); confpath( gl_state, sizeof gl_state, CONF_FILE_STATE, 0 ); strlcpy( gl_newstate, gl_state, sizeof gl_state ); strlcat( gl_newstate, ".new", sizeof gl_state ); absolutify( gl_dir, sizeof gl_dir, "." ); loadstate(); } int torrent_add_file( const char * path, const char * dir, int autostart ) { struct tor * tor; assert( NULL != gl_handle ); assert( !gl_exiting ); tor = opentor( path, NULL, NULL, 0, dir ); if( NULL == tor ) { return -1; } if( 0 > autostart ) { autostart = gl_autostart; } if( autostart ) { tr_torrentStart( tor->tor ); } savestate(); return tor->id; } int torrent_add_data( uint8_t * data, size_t size, const char * dir, int autostart ) { struct tor * tor; assert( NULL != gl_handle ); assert( !gl_exiting ); tor = opentor( NULL, NULL, data, size, dir ); if( NULL == tor ) { return -1; } if( 0 > autostart ) { autostart = gl_autostart; } if( autostart ) { tr_torrentStart( tor->tor ); } savestate(); return tor->id; } static struct tor * idlookup( int id ) { struct tor * found = NULL; if( gl_handle && !gl_exiting ) { struct tor key; memset( &key, 0, sizeof key ); key.id = id; found = RB_FIND( tortree, &gl_tree, &key ); } return found; } void torrent_start( int id ) { struct tor * tor = idlookup( id ); if( tor && !TR_STATUS_IS_ACTIVE( tr_torrentStat( tor->tor )->status ) ) { tr_torrentStart( tor->tor ); savestate(); } } void torrent_stop( int id ) { struct tor * tor = idlookup( id ); if( tor && TR_STATUS_IS_ACTIVE( tr_torrentStat( tor->tor )->status ) ) { tr_torrentStop( tor->tor ); savestate( ); } } void torrent_verify( int id ) { struct tor * tor = idlookup( id ); if( tor ) tr_torrentVerify( tor->tor ); } void torrent_remove( int id ) { struct tor * tor = idlookup( id ); if( tor ) { closetor( tor, 1 ); savestate(); } } tr_torrent * torrent_handle( int id ) { const struct tor * tor = idlookup( id ); return tor ? tor->tor : NULL; } const tr_info * torrent_info( int id ) { return tr_torrentInfo( torrent_handle( id ) ); } const tr_stat * torrent_stat( int id ) { return tr_torrentStat( torrent_handle( id ) ); } static struct tor * hashlookup( const uint8_t * hash ) { struct tor key, * found; memset( &key, 0, sizeof key ); memcpy( key.hash, hash, sizeof key.hash ); found = RB_FIND( hashtree, &gl_hashes, &key ); return found; } int torrent_lookup( const uint8_t * hashstr ) { uint8_t hash[SHA_DIGEST_LENGTH]; size_t ii; struct tor * tor; char buf[3]; assert( NULL != gl_handle ); assert( !gl_exiting ); memset( buf, 0, sizeof buf ); for( ii = 0; sizeof( hash ) > ii; ii++ ) { if( !isxdigit( hashstr[2*ii] ) || !isxdigit( hashstr[1+2*ii] ) ) { return -1; } memcpy( buf, &hashstr[2*ii], 2 ); hash[ii] = strtol( buf, NULL, 16 ); } tor = hashlookup( hash ); if( NULL == tor ) { return -1; } return tor->id; } static struct tor * iterate( struct tor * tor ) { struct tor * next = NULL; if( gl_handle && !gl_exiting ) next = tor ? RB_NEXT( tortree, &gl_tree, tor ) : RB_MIN( tortree, &gl_tree ); return next; } void * torrent_iter( void * cur, int * id ) { struct tor * next = iterate( cur ); if( next ) *id = next->id; return next; } void torrent_exit( int exitval ) { struct tor * tor; assert( NULL != gl_handle ); assert( !gl_exiting ); gl_exiting = time( NULL ); gl_exitval = exitval; RB_FOREACH( tor, tortree, &gl_tree ) { closetor( tor, 0 ); } tr_natTraversalEnable( gl_handle, 0 ); starttimer( 1 ); } void torrent_set_autostart( int autostart ) { assert( NULL != gl_handle ); assert( !gl_exiting ); gl_autostart = autostart; savestate(); } int torrent_get_autostart( void ) { return gl_autostart; } void torrent_set_port( int port ) { assert( NULL != gl_handle ); assert( !gl_exiting ); if( 0 < port && 0xffff > port ) { gl_port = port; tr_setBindPort( gl_handle, port ); savestate(); } } int torrent_get_port( void ) { return gl_port; } void torrent_set_pex( int pex ) { assert( NULL != gl_handle ); assert( !gl_exiting ); if( gl_pex != pex ) { gl_pex = pex; tr_setPexEnabled( gl_handle, gl_pex ); savestate( ); } } int torrent_get_pex( void ) { return gl_pex; } void torrent_enable_port_mapping( int automap ) { assert( NULL != gl_handle ); assert( !gl_exiting ); gl_mapping = ( automap ? 1 : 0 ); tr_natTraversalEnable( gl_handle, gl_mapping ); savestate(); } int torrent_get_port_mapping( void ) { return gl_mapping; } void torrent_set_uplimit( int uplimit ) { assert( NULL != gl_handle ); assert( !gl_exiting ); gl_uplimit = uplimit; tr_setGlobalSpeedLimit ( gl_handle, TR_UP, uplimit ); tr_setUseGlobalSpeedLimit( gl_handle, TR_UP, uplimit > 0 ); savestate(); } int torrent_get_uplimit( void ) { return gl_uplimit; } void torrent_set_downlimit( int downlimit ) { assert( NULL != gl_handle ); assert( !gl_exiting ); gl_downlimit = downlimit; tr_setGlobalSpeedLimit ( gl_handle, TR_DOWN, downlimit ); tr_setUseGlobalSpeedLimit( gl_handle, TR_DOWN, downlimit > 0 ); savestate(); } int torrent_get_downlimit( void ) { return gl_downlimit; } void torrent_set_directory( const char * path ) { assert( NULL != gl_handle ); assert( !gl_exiting ); absolutify( gl_dir, sizeof gl_dir, path ); savestate(); } const char * torrent_get_directory( void ) { return gl_dir; } void torrent_set_encryption(tr_encryption_mode mode) { tr_setEncryptionMode(gl_handle, mode); gl_crypto = mode; savestate(); } tr_encryption_mode torrent_get_encryption(void) { return tr_getEncryptionMode(gl_handle); } struct tor * opentor( const char * path, const char * hash, uint8_t * data, size_t size, const char * dir ) { struct tor * tor, * found; int errcode; const tr_info * inf; tr_ctor * ctor; assert( ( NULL != path && NULL == hash && NULL == data ) || ( NULL == path && NULL != hash && NULL == data ) || ( NULL == path && NULL == hash && NULL != data ) ); /* XXX should probably wrap around back to 1 and avoid duplicates */ if( INT_MAX == gl_lastid ) { errmsg( "Congratulations, you're the %ith torrent! Your prize the " "inability to load any more torrents, enjoy!", INT_MAX ); return NULL; } tor = calloc( 1, sizeof *tor ); if( NULL == tor ) { mallocmsg( sizeof *tor ); return NULL; } if( dir == NULL ) dir = gl_dir; ctor = tr_ctorNew( gl_handle ); tr_ctorSetPaused( ctor, TR_FORCE, 1 ); tr_ctorSetDestination( ctor, TR_FORCE, dir ); if( path != NULL ) tr_ctorSetMetainfoFromFile( ctor, path ); else if( hash != NULL ) tr_ctorSetMetainfoFromHash( ctor, hash ); else tr_ctorSetMetainfo( ctor, data, size ); tor->tor = tr_torrentNew( gl_handle, ctor, &errcode ); tr_ctorFree( ctor ); if( NULL == tor->tor ) { found = NULL; switch( errcode ) { case TR_EINVALID: if( NULL == path ) { errmsg( "invalid torrent file" ); } else { errmsg( "invalid torrent file: %s", path ); } break; case TR_EUNSUPPORTED: if( NULL == path ) { errmsg( "unsupported torrent file" ); } else { errmsg( "unsupported torrent file: %s", path ); } break; case TR_EDUPLICATE: /* XXX not yet found = hashlookup( tor->hash, 1 ); assert( NULL != found ); found->deleting = 0; */ errmsg( "XXX loaded duplicate torrent" ); break; default: if( NULL == path ) { errmsg( "torrent file failed to load" ); } else { errmsg( "torrent file failed to load: %s", path ); } break; } free( tor ); return found; } gl_lastid++; tor->id = gl_lastid; assert( sizeof( inf->hash ) == sizeof( tor->hash ) ); inf = tr_torrentInfo( tor->tor ); memcpy( tor->hash, inf->hash, sizeof tor->hash ); found = RB_INSERT( tortree, &gl_tree, tor ); assert( NULL == found ); found = RB_INSERT( hashtree, &gl_hashes, tor ); assert( NULL == found ); return tor; } static void freetor( struct tor * tor ) { tr_torrentClose( tor->tor ); RB_REMOVE( tortree, &gl_tree, tor ); RB_REMOVE( hashtree, &gl_hashes, tor ); free( tor ); } void closetor( struct tor * tor, int calltimer ) { if( NULL != tor ) { freetor( tor ); starttimer( calltimer ); } } void starttimer( int callnow ) { if( !evtimer_initialized( &gl_event ) ) { evtimer_set( &gl_event, timerfunc, NULL ); event_base_set( gl_base, &gl_event ); } if( callnow ) { timerfunc( -1, EV_TIMEOUT, NULL ); } } static void timerfunc( int fd UNUSED, short event UNUSED, void * arg UNUSED ) { struct tor * tor, * next; const tr_handle_status * hs; int stillmore; struct timeval tv; /* true if we've still got live torrents... */ stillmore = tr_torrentCount( gl_handle ) != 0; if( gl_exiting ) { if( !stillmore ) { hs = tr_handleStatus( gl_handle ); if( TR_NAT_TRAVERSAL_UNMAPPED != hs->natTraversalStatus ) { stillmore = 1; } } if( !stillmore || EXIT_TIMEOUT <= time( NULL ) - gl_exiting ) { if( stillmore ) { errmsg( "timing out trackers and/or port mapping on exit" ); } for( tor = RB_MIN( tortree, &gl_tree ); NULL != tor; tor = next ) { next = RB_NEXT( tortree, &gl_tree, tor ); freetor( tor ); } tr_close( gl_handle ); exit( gl_exitval ); } } if( stillmore ) { memset( &tv, 0, sizeof tv ); tv.tv_sec = TIMER_SECS; tv.tv_usec = TIMER_USECS; evtimer_add( &gl_event, &tv ); } } int loadstate( void ) { uint8_t * buf; size_t len; benc_val_t top, * num, * str, * list; int ii; struct tor * tor; const char * dir; buf = readfile( gl_state, &len ); if( NULL == buf ) { return -1; } if( tr_bencLoad( buf, len, &top, NULL ) ) { free( buf ); errmsg( "failed to load bencoded data from %s", gl_state ); return -1; } free( buf ); num = tr_bencDictFind( &top, "autostart" ); if( tr_bencIsInt( num ) ) gl_autostart = ( num->val.i ? 1 : 0 ); num = tr_bencDictFind( &top, "port" ); if( tr_bencIsInt( num ) && 0 < num->val.i && 0xffff > num->val.i ) { gl_port = num->val.i; } tr_setBindPort( gl_handle, gl_port ); num = tr_bencDictFind( &top, "default-pex" ); if( tr_bencIsInt( num ) ) gl_pex = ( num->val.i ? 1 : 0 ); num = tr_bencDictFind( &top, "port-mapping" ); if( tr_bencIsInt( num ) ) gl_mapping = ( num->val.i ? 1 : 0 ); tr_natTraversalEnable( gl_handle, gl_mapping ); num = tr_bencDictFind( &top, "upload-limit" ); if( tr_bencIsInt( num ) ) gl_uplimit = num->val.i; tr_setGlobalSpeedLimit( gl_handle, TR_UP, gl_uplimit ); tr_setUseGlobalSpeedLimit( gl_handle, TR_UP, gl_uplimit > 0 ); num = tr_bencDictFind( &top, "download-limit" ); if( tr_bencIsInt( num ) ) gl_downlimit = num->val.i; tr_setGlobalSpeedLimit( gl_handle, TR_DOWN, gl_downlimit ); tr_setUseGlobalSpeedLimit( gl_handle, TR_DOWN, gl_downlimit > 0 ); str = tr_bencDictFind( &top, "default-directory" ); if( tr_bencIsString( str ) ) strlcpy( gl_dir, str->val.s.s, sizeof gl_dir ); str = tr_bencDictFind( &top, "encryption-mode" ); if( tr_bencIsString( str ) ) { if(!strcasecmp(str->val.s.s, "preferred")) gl_crypto = TR_ENCRYPTION_PREFERRED; else if(!strcasecmp(str->val.s.s, "required")) gl_crypto = TR_ENCRYPTION_REQUIRED; } tr_setEncryptionMode(gl_handle, gl_crypto); list = tr_bencDictFind( &top, "torrents" ); if( !tr_bencIsList( list ) ) return 0; for( ii = 0; ii < list->val.l.count; ii++ ) { tr_benc * dict = &list->val.l.vals[ii]; if( !tr_bencIsDict( dict ) ) continue; str = tr_bencDictFind( dict, "directory" ); dir = tr_bencIsString( str ) ? str->val.s.s : NULL; str = tr_bencDictFind( dict, "hash" ); if( !tr_bencIsString( str ) || 2 * SHA_DIGEST_LENGTH != str->val.s.i ) continue; tor = opentor( NULL, str->val.s.s, NULL, 0, dir ); if( !tor ) continue; num = tr_bencDictFind( dict, "pex" ); if( tr_bencIsInt( num ) ) fprintf( stderr, "warning: obsolete command 'pex'\n" ); num = tr_bencDictFind( dict, "paused" ); if( tr_bencIsInt( num ) && !num->val.i ) tr_torrentStart( tor->tor ); } return 0; } int savestate( void ) { benc_val_t top, * list, * tor; struct tor * ii; uint8_t * buf; int len; tr_bencInit( &top, TYPE_DICT ); if( tr_bencDictReserve( &top, 9 ) ) { nomem: tr_bencFree( &top ); errmsg( "failed to save state: failed to allocate memory" ); return -1; } tr_bencInitInt( tr_bencDictAdd( &top, "autostart" ), gl_autostart ); tr_bencInitInt( tr_bencDictAdd( &top, "port" ), gl_port ); tr_bencInitInt( tr_bencDictAdd( &top, "default-pex" ), gl_pex ); tr_bencInitInt( tr_bencDictAdd( &top, "port-mapping" ), gl_mapping ); tr_bencInitInt( tr_bencDictAdd( &top, "upload-limit" ), gl_uplimit ); tr_bencInitInt( tr_bencDictAdd( &top, "download-limit" ), gl_downlimit ); tr_bencInitStr( tr_bencDictAdd( &top, "default-directory" ), gl_dir, -1, 1 ); if(TR_ENCRYPTION_REQUIRED == gl_crypto) tr_bencInitStr(tr_bencDictAdd(&top, "encryption-mode"), "required", -1, 1); else tr_bencInitStr(tr_bencDictAdd(&top, "encryption-mode"), "preferred", -1, 1); list = tr_bencDictAdd( &top, "torrents" ); tr_bencInit( list, TYPE_LIST ); len = 0; RB_FOREACH( ii, tortree, &gl_tree ) { len++; } if( tr_bencListReserve( list, len ) ) { goto nomem; } RB_FOREACH( ii, tortree, &gl_tree ) { const tr_info * inf; const tr_stat * st; tor = tr_bencListAdd( list ); assert( NULL != tor ); tr_bencInit( tor, TYPE_DICT ); inf = tr_torrentInfo( ii->tor ); st = tr_torrentStat( ii->tor ); if( tr_bencDictReserve( tor, 3 ) ) { goto nomem; } tr_bencInitStr( tr_bencDictAdd( tor, "hash" ), inf->hashString, 2 * SHA_DIGEST_LENGTH, 1 ); tr_bencInitInt( tr_bencDictAdd( tor, "paused" ), !TR_STATUS_IS_ACTIVE( st->status ) ); tr_bencInitStr( tr_bencDictAdd( tor, "directory" ), tr_torrentGetFolder( ii->tor ), -1, 1 ); } buf = ( uint8_t * )tr_bencSave( &top, &len ); SAFEBENCFREE( &top ); if( NULL == buf ) { errnomsg( "failed to save state: bencoding failed" ); return -1; } if( 0 > writefile( gl_newstate, buf, len ) ) { free( buf ); return -1; } free( buf ); if( 0 > rename( gl_newstate, gl_state ) ) { errnomsg( "failed to save state: failed to rename %s to %s", gl_newstate, CONF_FILE_STATE ); return -1; } return 0; }