/* * This file Copyright (C) 2008-2009 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 /* isspace */ #include #include #include #include #include /* strcmp */ #ifdef WIN32 #include /* getcwd */ #else #include /* getcwd */ #endif #include #define CURL_DISABLE_TYPECHECK /* otherwise -Wunreachable-code goes insane */ #include #include #include #include #include #include #include #include #define MY_NAME "transmission-remote" #define DEFAULT_HOST "localhost" #define DEFAULT_PORT atoi(TR_DEFAULT_RPC_PORT_STR) enum { TAG_SESSION, TAG_LIST, TAG_DETAILS, TAG_FILES, TAG_PEERS }; static const char* getUsage( void ) { return "Transmission " LONG_VERSION_STRING " http://www.transmissionbt.com/\n" "A fast and easy BitTorrent client\n" "\n" "Usage: " MY_NAME " [host] [options]\n" " " MY_NAME " [port] [options]\n" " " MY_NAME " [host:port] [options]\n" "\n" "See the man page for detailed explanations and many examples."; } static tr_option opts[] = { { 'a', "add", "Add torrent files by filename or URL", "a", 0, NULL }, { 'b', "debug", "Print debugging information", "b", 0, NULL }, { 'd', "downlimit", "Set the maximum global download speed in KB/s", "d", 1, "" }, { 'D', "no-downlimit", "Don't limit the global download speed", "D", 0, NULL }, { 910, "encryption-required", "Encrypt all peer connections", "er", 0, NULL }, { 911, "encryption-preferred", "Prefer encrypted peer connections", "ep", 0, NULL }, { 912, "encryption-tolerated", "Prefer unencrypted peer connections", "et", 0, NULL }, { 'f', "files", "List the current torrent(s)' files", "f", 0, NULL }, { 'g', "get", "Mark files for download", "g", 1, "" }, { 'G', "no-get", "Mark files for not downloading", "G", 1, "" }, { 'i', "info", "Show the current torrent(s)' details", "i", 0, NULL }, { 920, "session-info", "Show the session's details", "si", 0, NULL }, { 'l', "list", "List all torrents", "l", 0, NULL }, { 960, "move", "Move current torrent's data to a new folder", NULL, 1, "" }, { 961, "find", "Tell Transmission where to find a torrent's data", NULL, 1, "" }, { 'm', "portmap", "Enable portmapping via NAT-PMP or UPnP", "m", 0, NULL }, { 'M', "no-portmap", "Disable portmapping", "M", 0, NULL }, { 'n', "auth", "Set authentication info", "n", 1, "" }, { 'N', "netrc", "Set authentication info from a .netrc file", "N", 1, "" }, { 'o', "dht", "Enable distributed hash tables (DHT)", "o", 0, NULL }, { 'O', "no-dht", "Disable distributed hash tables (DHT)", "O", 0, NULL }, { 'p', "port", "Port for incoming peers (Default: " TR_DEFAULT_PEER_PORT_STR ")", "p", 1, "" }, { 'P', "random-port", "Random port for incomping peers", "P", 0, NULL }, { 900, "priority-high", "Set the files' priorities as high", "ph", 1, "" }, { 901, "priority-normal", "Set the files' priorities as normal", "pn", 1, "" }, { 902, "priority-low", "Set the files' priorities as low", "pl", 1, "" }, { 'r', "remove", "Remove the current torrent(s)", "r", 0, NULL }, { 930, "peers", "Set the current torrent(s)' maximum number of peers each", "pr", 1, "" }, { 931, "global-peers", "Set the global maximum number of peers", "gpr", 1, "" }, { 'R', "remove-and-delete", "Remove the current torrent(s) and delete local data", NULL, 0, NULL }, { 950, "seedratio", "Let the current torrent(s) seed until a specific ratio", "sr", 1, "ratio" }, { 951, "seedratio-default", "Let the current torrent(s) use the global seedratio settings", "srd", 0, NULL }, { 952, "no-seedratio", "Let the current torrent(s) seed regardless of ratio", "SR", 0, NULL }, { 953, "global-seedratio", "All torrents, unless overridden by a per-torrent setting, should seed until a specific ratio", "gsr", 1, "ratio" }, { 954, "no-global-seedratio", "All torrents, unless overridden by a per-torrent setting, should seed regardless of ratio", "GSR", 0, NULL }, { 's', "start", "Start the current torrent(s)", "s", 0, NULL }, { 'S', "stop", "Stop the current torrent(s)", "S", 0, NULL }, { 't', "torrent", "Set the current torrent(s)", "t", 1, "" }, { 'u', "uplimit", "Set the maximum global upload speed in KB/s", "u", 1, "" }, { 'U', "no-uplimit", "Don't limit the global upload speed", "U", 0, NULL }, { 'v', "verify", "Verify the current torrent(s)", "v", 0, NULL }, { 'V', "version", "Show version number and exit", "V", 0, NULL }, { 'w', "download-dir", "Set the default download folder", "w", 1, "" }, { 'x', "pex", "Enable peer exchange (PEX)", "x", 0, NULL }, { 'X', "no-pex", "Disable peer exchange (PEX)", "X", 0, NULL }, { 940, "peer-info", "List the current torrent(s)' peers", "pi", 0, NULL }, { 0, NULL, NULL, NULL, 0, NULL } }; static void showUsage( void ) { tr_getopt_usage( MY_NAME, getUsage( ), opts ); } static int numarg( const char * arg ) { char * end = NULL; const long num = strtol( arg, &end, 10 ); if( *end ) { fprintf( stderr, "Not a number: \"%s\"\n", arg ); showUsage( ); exit( EXIT_FAILURE ); } return num; } static char * reqs[256]; /* arbitrary max */ static int reqCount = 0; static int debug = 0; static char * auth = NULL; static char * netrc = NULL; static char * sessionId = NULL; static char* tr_getcwd( void ) { char buf[2048]; *buf = '\0'; #ifdef WIN32 _getcwd( buf, sizeof( buf ) ); #else getcwd( buf, sizeof( buf ) ); #endif return tr_strdup( buf ); } static char* absolutify( const char * path ) { char * buf; if( *path == '/' ) buf = tr_strdup( path ); else { char * cwd = tr_getcwd( ); buf = tr_buildPath( cwd, path, NULL ); tr_free( cwd ); } return buf; } static char* getEncodedMetainfo( const char * filename ) { size_t len = 0; char * b64 = NULL; uint8_t * buf = tr_loadFile( filename, &len ); if( buf ) { b64 = tr_base64_encode( buf, len, NULL ); tr_free( buf ); } return b64; } static void addIdArg( tr_benc * args, const char * id ) { if( !*id ) { fprintf( stderr, "No torrent specified! Please use the -t option first.\n" ); id = "-1"; /* no torrent will have this ID, so should be a no-op */ } if( strcmp( id, "all" ) ) { const char * pch; tr_bool isList = strchr(id,',') || strchr(id,'-'); tr_bool isNum = TRUE; for( pch=id; isNum && *pch; ++pch ) if( !isdigit( *pch ) ) isNum = FALSE; if( isNum || isList ) tr_rpc_parse_list_str( tr_bencDictAdd( args, "ids" ), id, strlen( id ) ); else tr_bencDictAddStr( args, "ids", id ); /* it's a torrent sha hash */ } } static void addFiles( tr_benc * args, const char * key, const char * arg ) { tr_benc * files = tr_bencDictAddList( args, key, 100 ); if( !*arg ) { fprintf( stderr, "No files specified!\n" ); arg = "-1"; /* no file will have this index, so should be a no-op */ } if( strcmp( arg, "all" ) ) { int i; int valueCount; int * values = tr_parseNumberRange( arg, -1, &valueCount ); for( i=0; i 0 ) tr_strlcpy( buf, "Uploading", buflen ); else tr_strlcpy( buf, "Seeding", buflen ); } else { tr_strlcpy( buf, "Idle", buflen ); } break; } } return buf; } static const char* getTrackerDateStr( const time_t t, tr_bool isStopped ) { const char * str; switch( t ) { case 0: str = isStopped ? "None (Stopped)\n" : "None\n"; break; case 1: str = "In Progress\n"; break; default: str = ctime( &t ); break; } return str; } static void printSession( tr_benc * top ) { tr_benc *args; if( ( tr_bencDictFindDict( top, "arguments", &args ) ) ) { const char * str; int64_t i; tr_bool boolVal; printf( "VERSION\n" ); if( tr_bencDictFindStr( args, "version", &str ) ) printf( " Daemon version: %s\n", str ); if( tr_bencDictFindInt( args, "rpc-version", &i ) ) printf( " RPC version: %" PRId64 "\n", i ); if( tr_bencDictFindInt( args, "rpc-version-minimum", &i ) ) printf( " RPC minimum version: %" PRId64 "\n", i ); printf( "\n" ); printf( "TRANSFER\n" ); if( tr_bencDictFindStr( args, TR_PREFS_KEY_DOWNLOAD_DIR, &str ) ) printf( " Download directory: %s\n", str ); if( tr_bencDictFindInt( args, TR_PREFS_KEY_PEER_PORT, &i ) ) printf( " Listenport: %" PRId64 "\n", i ); if( tr_bencDictFindBool( args, TR_PREFS_KEY_PORT_FORWARDING, &boolVal ) ) printf( " Portforwarding enabled: %s\n", ( boolVal ? "Yes" : "No" ) ); if( tr_bencDictFindBool( args, TR_PREFS_KEY_PEX_ENABLED, &boolVal ) ) printf( " Peer exchange allowed: %s\n", ( boolVal ? "Yes" : "No" ) ); if( tr_bencDictFindStr( args, TR_PREFS_KEY_ENCRYPTION, &str ) ) printf( " Encryption: %s\n", str ); printf( "\n" ); { tr_bool altEnabled, altTimeEnabled, upEnabled, downEnabled; int64_t altDown, altUp, altBegin, altEnd, altDay, upLimit, downLimit, peerLimit; if( tr_bencDictFindInt ( args, TR_PREFS_KEY_ALT_SPEED_DOWN, &altDown ) && tr_bencDictFindBool( args, TR_PREFS_KEY_ALT_SPEED_ENABLED, &altEnabled ) && tr_bencDictFindInt ( args, TR_PREFS_KEY_ALT_SPEED_TIME_BEGIN, &altBegin ) && tr_bencDictFindBool( args, TR_PREFS_KEY_ALT_SPEED_TIME_ENABLED, &altTimeEnabled ) && tr_bencDictFindInt ( args, TR_PREFS_KEY_ALT_SPEED_TIME_END, &altEnd ) && tr_bencDictFindInt ( args, TR_PREFS_KEY_ALT_SPEED_TIME_DAY, &altDay ) && tr_bencDictFindInt ( args, TR_PREFS_KEY_ALT_SPEED_UP, &altUp ) && tr_bencDictFindInt ( args, TR_PREFS_KEY_PEER_LIMIT_GLOBAL, &peerLimit ) && tr_bencDictFindInt ( args, TR_PREFS_KEY_DSPEED, &downLimit ) && tr_bencDictFindBool( args, TR_PREFS_KEY_DSPEED_ENABLED, &downEnabled ) && tr_bencDictFindInt ( args, TR_PREFS_KEY_USPEED, &upLimit ) && tr_bencDictFindBool( args, TR_PREFS_KEY_USPEED_ENABLED, &upEnabled ) ) { char buf[128]; printf( "LIMITS\n" ); printf( " Peer limit: %" PRId64 "\n", peerLimit ); if( altEnabled ) tr_snprintf( buf, sizeof( buf ), "%"PRId64" KB/s", altUp ); else if( upEnabled ) tr_snprintf( buf, sizeof( buf ), "%"PRId64" KB/s", upLimit ); else tr_strlcpy( buf, "Unlimited", sizeof( buf ) ); printf( " Upload speed limit: %s (%s limit: %"PRId64" KB/s; %s turtle limit: %"PRId64" KB/s)\n", buf, (upEnabled?"Enabled":"Disabled"), upLimit, (altEnabled?"Enabled":"Disabled"), altUp ); if( altEnabled ) tr_snprintf( buf, sizeof( buf ), "%"PRId64" KB/s", altDown ); else if( downEnabled ) tr_snprintf( buf, sizeof( buf ), "%"PRId64" KB/s", downLimit ); else tr_strlcpy( buf, "Unlimited", sizeof( buf ) ); printf( " Download speed limit: %s (%s limit: %"PRId64" KB/s; %s turtle limit: %"PRId64" KB/s)\n", buf, (downEnabled?"Enabled":"Disabled"), downLimit, (altEnabled?"Enabled":"Disabled"), altDown ); if( altTimeEnabled ) { printf( " Turtle schedule: %02d:%02d - %02d:%02d ", (int)(altBegin/60), (int)(altBegin%60), (int)(altEnd/60), (int)(altEnd%60) ); if( altDay & TR_SCHED_SUN ) printf( "Sun " ); if( altDay & TR_SCHED_MON ) printf( "Mon " ); if( altDay & TR_SCHED_TUES ) printf( "Tue " ); if( altDay & TR_SCHED_WED ) printf( "Wed " ); if( altDay & TR_SCHED_THURS ) printf( "Thu " ); if( altDay & TR_SCHED_FRI ) printf( "Fri " ); if( altDay & TR_SCHED_SAT ) printf( "Sat " ); printf( "\n" ); } } } } } static void printDetails( tr_benc * top ) { tr_benc *args, *torrents; if( ( tr_bencDictFindDict( top, "arguments", &args ) ) && ( tr_bencDictFindList( args, "torrents", &torrents ) ) ) { int ti, tCount; for( ti = 0, tCount = tr_bencListSize( torrents ); ti < tCount; ++ti ) { tr_benc * t = tr_bencListChild( torrents, ti ); tr_benc * l; const uint8_t * raw; size_t rawlen; const char * str; char buf[512]; char buf2[512]; int64_t i, j, k; tr_bool isStopped; tr_bool boolVal; isStopped = tr_bencDictFindInt( t, "status", &i ) && (i==TR_STATUS_STOPPED); printf( "NAME\n" ); if( tr_bencDictFindInt( t, "id", &i ) ) printf( " Id: %" PRId64 "\n", i ); if( tr_bencDictFindStr( t, "name", &str ) ) printf( " Name: %s\n", str ); if( tr_bencDictFindStr( t, "hashString", &str ) ) printf( " Hash: %s\n", str ); printf( "\n" ); printf( "TRANSFER\n" ); getStatusString( t, buf, sizeof( buf ) ); printf( " State: %s\n", buf ); if( tr_bencDictFindStr( t, "downloadDir", &str ) ) printf( " Location: %s\n", str ); if( tr_bencDictFindInt( t, "sizeWhenDone", &i ) && tr_bencDictFindInt( t, "leftUntilDone", &j ) ) { strlratio( buf, 100.0 * ( i - j ), i, sizeof( buf ) ); printf( " Percent Done: %s%%\n", buf ); } if( tr_bencDictFindInt( t, "eta", &i ) ) { etaToString( buf, sizeof( buf ), i ); printf( " ETA: %s\n", buf ); } if( tr_bencDictFindInt( t, "rateDownload", &i ) ) printf( " Download Speed: %.1f KB/s\n", i / 1024.0 ); if( tr_bencDictFindInt( t, "rateUpload", &i ) ) printf( " Upload Speed: %.1f KB/s\n", i / 1024.0 ); if( tr_bencDictFindInt( t, "haveUnchecked", &i ) && tr_bencDictFindInt( t, "haveValid", &j ) ) { strlsize( buf, i + j, sizeof( buf ) ); strlsize( buf2, j, sizeof( buf2 ) ); printf( " Have: %s (%s verified)\n", buf, buf2 ); } if( tr_bencDictFindInt( t, "sizeWhenDone", &i ) && tr_bencDictFindInt( t, "totalSize", &j ) ) { strlsize( buf, j, sizeof( buf ) ); strlsize( buf2, i, sizeof( buf2 ) ); printf( " Total size: %s (%s wanted)\n", buf, buf2 ); } if( tr_bencDictFindInt( t, "downloadedEver", &i ) && tr_bencDictFindInt( t, "uploadedEver", &j ) ) { strlsize( buf, i, sizeof( buf ) ); printf( " Downloaded: %s\n", buf ); strlsize( buf, j, sizeof( buf ) ); printf( " Uploaded: %s\n", buf ); strlratio( buf, j, i, sizeof( buf ) ); printf( " Ratio: %s\n", buf ); } if( tr_bencDictFindInt( t, "corruptEver", &i ) ) { strlsize( buf, i, sizeof( buf ) ); printf( " Corrupt DL: %s\n", buf ); } if( tr_bencDictFindStr( t, "errorString", &str ) && str && *str && tr_bencDictFindInt( t, "error", &i ) && i ) { switch( i ) { case TR_STAT_TRACKER_WARNING: printf( " Tracker gave a warning: %s\n", str ); break; case TR_STAT_TRACKER_ERROR: printf( " Tracker gave an error: %s\n", str ); break; case TR_STAT_LOCAL_ERROR: printf( " Error: %s\n", str ); break; default: break; /* no error */ } } if( tr_bencDictFindInt( t, "peersConnected", &i ) && tr_bencDictFindInt( t, "peersGettingFromUs", &j ) && tr_bencDictFindInt( t, "peersSendingToUs", &k ) ) { printf( " Peers: " "connected to %" PRId64 ", " "uploading to %" PRId64 ", " "downloading from %" PRId64 "\n", i, j, k ); } if( tr_bencDictFindList( t, "webseeds", &l ) && tr_bencDictFindInt( t, "webseedsSendingToUs", &i ) ) { const int64_t n = tr_bencListSize( l ); if( n > 0 ) printf( " Web Seeds: downloading from %" PRId64 " of %" PRId64 " web seeds\n", i, n ); } printf( "\n" ); printf( "HISTORY\n" ); if( tr_bencDictFindInt( t, "addedDate", &i ) && i ) { const time_t tt = i; printf( " Date added: %s", ctime( &tt ) ); } if( tr_bencDictFindInt( t, "doneDate", &i ) && i ) { const time_t tt = i; printf( " Date finished: %s", ctime( &tt ) ); } if( tr_bencDictFindInt( t, "startDate", &i ) && i ) { const time_t tt = i; printf( " Date started: %s", ctime( &tt ) ); } if( tr_bencDictFindInt( t, "activityDate", &i ) && i ) { const time_t tt = i; printf( " Latest activity: %s", ctime( &tt ) ); } printf( "\n" ); printf( "TRACKER\n" ); if( tr_bencDictFindInt( t, "lastAnnounceTime", &i ) ) printf( " Latest announce: %s", getTrackerDateStr( (time_t)i, isStopped ) ); if( tr_bencDictFindStr( t, "announceURL", &str ) ) printf( " Announce URL: %s\n", str ); if( tr_bencDictFindStr( t, "announceResponse", &str ) && str && *str ) printf( " Announce response: %s\n", str ); if( tr_bencDictFindInt( t, "nextAnnounceTime", &i ) ) printf( " Next announce: %s", getTrackerDateStr( (time_t)i, isStopped ) ); if( tr_bencDictFindInt( t, "lastScrapeTime", &i ) ) printf( " Latest scrape: %s", getTrackerDateStr( (time_t)i, isStopped ) ); if( tr_bencDictFindStr( t, "scrapeResponse", &str ) ) printf( " Scrape response: %s\n", str ); if( tr_bencDictFindInt( t, "nextScrapeTime", &i ) ) printf( " Next scrape: %s", getTrackerDateStr( (time_t)i, isStopped ) ); if( tr_bencDictFindInt( t, "seeders", &i ) && tr_bencDictFindInt( t, "leechers", &j ) ) printf( " Tracker knows of %" PRId64 " seeders and %" PRId64 " leechers\n", i, j ); if( tr_bencDictFindInt( t, "timesCompleted", &i ) ) printf( " Tracker has seen %" PRId64 " clients complete this torrent\n", i ); printf( "\n" ); printf( "ORIGINS\n" ); if( tr_bencDictFindInt( t, "dateCreated", &i ) && i ) { const time_t tt = i; printf( " Date created: %s", ctime( &tt ) ); } if( tr_bencDictFindBool( t, "isPrivate", &boolVal ) ) printf( " Public torrent: %s\n", ( boolVal ? "No" : "Yes" ) ); if( tr_bencDictFindStr( t, "comment", &str ) && str && *str ) printf( " Comment: %s\n", str ); if( tr_bencDictFindStr( t, "creator", &str ) && str && *str ) printf( " Creator: %s\n", str ); if( tr_bencDictFindInt( t, "pieceCount", &i ) ) printf( " Piece Count: %" PRId64 "\n", i ); if( tr_bencDictFindInt( t, "pieceSize", &i ) ) printf( " Piece Size: %" PRId64 "\n", i ); printf( "\n" ); printf("PIECES\n "); if( tr_bencDictFindRaw( t, "pieces", &raw, &rawlen ) && tr_bencDictFindInt( t, "pieceCount", &j ) ) { int len; char * str = tr_base64_decode( raw, rawlen, &len ); for( i=k=0; k= key_len ) && !memcmp( line, key, key_len ) ) { const char * begin = line + key_len; const char * end = begin; while( !isspace( *end ) ) ++end; tr_free( sessionId ); sessionId = tr_strndup( begin, end-begin ); } return line_len; } static CURL* tr_curl_easy_init( struct evbuffer * writebuf ) { CURL * curl = curl_easy_init( ); curl_easy_setopt( curl, CURLOPT_USERAGENT, MY_NAME "/" LONG_VERSION_STRING ); curl_easy_setopt( curl, CURLOPT_WRITEFUNCTION, writeFunc ); curl_easy_setopt( curl, CURLOPT_WRITEDATA, writebuf ); curl_easy_setopt( curl, CURLOPT_HEADERFUNCTION, parseResponseHeader ); curl_easy_setopt( curl, CURLOPT_POST, 1 ); curl_easy_setopt( curl, CURLOPT_NETRC, CURL_NETRC_OPTIONAL ); curl_easy_setopt( curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY ); curl_easy_setopt( curl, CURLOPT_TIMEOUT, 60L ); curl_easy_setopt( curl, CURLOPT_VERBOSE, debug ); curl_easy_setopt( curl, CURLOPT_ENCODING, "" ); /* "" tells curl to fill in the blanks with what it was compiled to support */ if( netrc ) curl_easy_setopt( curl, CURLOPT_NETRC_FILE, netrc ); if( auth ) curl_easy_setopt( curl, CURLOPT_USERPWD, auth ); if( sessionId ) { char * h = tr_strdup_printf( "%s: %s", TR_RPC_SESSION_ID_HEADER, sessionId ); struct curl_slist * custom_headers = curl_slist_append( NULL, h ); curl_easy_setopt( curl, CURLOPT_HTTPHEADER, custom_headers ); /* fixme: leaks */ } return curl; } static int processRequests( const char * host, int port, const char ** reqs, int reqCount ) { int i; CURL * curl = NULL; struct evbuffer * buf = evbuffer_new( ); char * url = tr_strdup_printf( "http://%s:%d/transmission/rpc", host, port ); int status = EXIT_SUCCESS; for( i=0; i