diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..723ef36 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.idea \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index e293d51..2456c7b 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,68 @@ -# torrent-tracker -Easy, simple and fast torrent tracker +# Easy Torrent Tracker + +Easy, simple and yet fast torrent tracker created in PHP. + +Based on Nsuan's incredible work, source: +https://gist.github.com/nsuan/1967006 + +## Pre-note + +Trackers are putting on an extremely hard job in maintaining their trackers and dealing with their trackers being taken down or hosts forcing them to shut down, +even though the service is perfectly legal. And most of the people administering trackers don't make any money, as there's no way to advertise using a tracker. +That's why we need to show them support and help them out by creating as many small trackers as possible. + +As you can see in the source file, trackers doesn't store any information about the content that is being shared - it only connects peers to each other. + +## Installation + +- Copy `announce.php` to your web-server +- Create a new text-file that will act as a database for enabled peers. +- Change the `__LOCATION_PEERS` option with the location of your database file. +- Change the `__REDIR_BROWSER` option to redirect to a url describing your tracker. + +**Tip:** You can enable debugging by changing `DEBUG_ENABLE` option and adding the `?debug` param to your tracker. + +## Options + +### `__DEBUGGING_ENABLED` `bool` + +Enable or disable debugging. +This allows anyone to see the entire peer database by appending ?debug to the announce URL + +### `__INTERVAL` `int` + +How often should clients pull server for new clients? (Seconds) + +### `__INTERVAL_MIN` `int` + +What's the minimum interval a client may pull the server? (Seconds) +Some bittorrent clients does not obey this + +### `__CLIENT_TIMEOUT` `int` + +How long should we wait for a client to re-announce after the last announce expires? (Seconds) + +### `__NO_PEER_ID` `bool` + +Skip sending the peer id if client does not want it? +Hint: Should be set to true + +### `__NO_SEED_P2P` `bool` + +Should seeders not see each others? +Hint: Should be set to true + +### `__LOCATION_PEERS` `string` + +Where should we save the peer database +On Linux, you should use `/dev/shm` as it is very fast. +On Windows, you will need to change this value to some other valid path such as `C:/Peers.txt` + +### `__ENABLE_SHORT_ANNOUNCE` `bool` + +Should we enable short announces? +This allows NATed clients to get updates much faster, but it also takes more load on the server. (This is just an experimental feature which may be turned off) + +### `__REDIR_BROWSER` `string` + +In case someone tries to access the tracker using a browser, redirect to this URL or file diff --git a/announce.php b/announce.php new file mode 100644 index 0000000..23f4e2e --- /dev/null +++ b/announce.php @@ -0,0 +1,317 @@ +. +*/ + +/************************* + ** Configuration start ** + *************************/ + +/* + * Enable debugging? + * This allows anyone to see the entire peer database by appending ?debug to the announce URL + */ +define('__DEBUGGING_ENABLED', true); + +/** + * Version + */ +define('__VERSION', 1.4); + +/** + * How often should clients pull server for new clients? (Seconds) + */ +define('__INTERVAL', 1800); + +/** + * What's the minimum interval a client may pull the server? (Seconds) + * Some bittorrent clients does not obey this + */ +define('__INTERVAL_MIN', 300); + +/** + * How long should we wait for a client to re-announce after the last announce expires? (Seconds) + */ +define('__CLIENT_TIMEOUT', 60); + +/** + * Skip sending the peer id if client does not want it? + * Hint: Should be set to true + */ +define('__NO_PEER_ID', true); + +/** + * Should seeders not see each others? + * Hint: Should be set to true + */ +define('__NO_SEED_P2P', true); + +/** + * Where should we save the peer database + * On Linux, you should use /dev/shm as it is very fast. + * On Windows, you will need to change this value to some other valid path such as C:/Peers.txt + */ +define('__LOCATION_PEERS', ''); + +/** + * Should we enable short announces? + * This allows NATed clients to get updates much faster, but it also + * takes more load on the server. (This is just an experimental feature which may be turned off) + */ +define('__ENABLE_SHORT_ANNOUNCE', true); + +/** + * In case someone tries to access the tracker using a browser, redirect to this URL or file + */ +define('__REDIR_BROWSER', ''); + +/*********************** + ** Configuration end ** + ***********************/ + +//Send response as text +header('Content-type: Text/Plain'); +header('X-Tracker-Version: Bitstorm '.__VERSION.' by ck3r.org'); //Please give me some credit +//If you *really* dont want to, comment this line out +//Bencoding function, returns a bencoded dictionary +//You may go ahead and enter custom keys in the dictionary in +//this function if you'd like. +function track($list, $interval=60, $min_ival=0) { + if (is_string($list)) { //Did we get a string? Return an error to the client + return 'd14:failure reason'.strlen($list).':'.$list.'e'; + } + $p = ''; //Peer directory + $c = $i = 0; //Complete and Incomplete clients + foreach($list as $d) { //Runs for each client + if ($d[7]) { //Are we seeding? + $c++; //Seeding, add to complete list + if (__NO_SEED_P2P && is_seed()) { //Seeds should not see each others + continue; + } + } else { + $i++; //Not seeding, add to incomplete list + } + //Do some bencoding + + $pid = ''; + + if (!isset($_GET['no_peer_id']) && __NO_PEER_ID) { //Shall we include the peer id + $pid = '7:peer id'.strlen($d[1]).':'.$d[1]; + } + + $p .= 'd2:ip'.strlen($d[0]).':'.$d[0].$pid.'4:porti'.$d[2].'ee'; + } + //Add some other paramters in the dictionary and merge with peer list + $r = 'd8:intervali'.$interval.'e12:min intervali'.$min_ival.'e8:completei'.$c.'e10:incompletei'.$i.'e5:peersl'.$p.'ee'; + return $r; +} + +//Find out if we are seeding or not. Assume not if unknown. +function is_seed() { + if (!isset($_GET['left'])) { + return false; + } + if ($_GET['left'] == 0) { + return true; + } + return false; +} + +/* +* Yeah, this is the database engine. It's pretty bad, uses files to store peers. +* Should be easy to rewrite to use SQL instead. +* +* Yes, sometimes collisions may occur and screw the DB over. It might or might not +* recover by itself. +*/ + +//Save database to file +function db_save($data) { + $b = serialize($data); + $h = @fopen(__LOCATION_PEERS, 'w'); + if (!$h) { return false; } + if (!@flock($h, LOCK_EX)) { return false; } + @fwrite($h, $b); + @fclose($h); + return true; +} + +//Load database from file +function db_open() { + $p = ''; + $h = @fopen(__LOCATION_PEERS, 'r'); + if (!$h) { return false; } + if (!@flock($h, LOCK_EX)) { return false; } + while (!@feof($h)) { + $p .= @fread($h, 512); + } + @fclose($h); + return (strlen($p)) ? unserialize($p) : true; +} + +//Check if DB file exists, otherwise create it +function db_exists($create_empty=false) { + if (file_exists(__LOCATION_PEERS)) { + return true; + } + if ($create_empty) { + if (!db_save(array())) { + return false; + } + return true; + } + return false; +} + +//Default announce time +$interval = __INTERVAL; + +//Minimal announce time (does not apply to short announces) +$interval_min = __INTERVAL_MIN; + +/* +* This is a pretty smart feature not present in other tracker software. +* If you expect to have many NATed clients, add short as a GET parameter, +* and clients will pull much more often. +* +* This can be done automatically, simply try to open a TCP connection to +* the client and assume it is NATed if not successful. +*/ +if (isset($_GET['short']) && __ENABLE_SHORT_ANNOUNCE) { + $interval = 120; + $interval_min = 30; +} + +//Did we get any parameters at all? +//Client is probably a web browser, do a redirect +if (empty($_GET)) { + header('Location: '.__REDIR_BROWSER); + die(); +} + +//Create database if it does not exist +db_exists(true) or die(track('Unable to create database')); +$d = db_open(); + +//Do we want to debug? (Should not be used by default) +if (isset($_GET['debug']) && __DEBUGGING_ENABLED) { + echo 'Connected peers:'.count($d)."\n\n"; + die(); +} + +//Did we get a failure from the database? +if ($d === false) { + die(track('Database failure')); +} + +//Do some input validation +function valdata($g, $must_be_20_chars=false) { + if (!isset($_GET[$g])) { + die(track('Missing one or more arguments')); + } + if (!is_string($_GET[$g])) { + die(track('Invalid types on one or more arguments')); + } + if ($must_be_20_chars && strlen($_GET[$g]) != 20) { + die(track('Invalid length on '.$g.' argument')); + } + if (strlen($_GET[$g]) > 128) { //128 chars should really be enough + die(track('Argument '.$g.' is too large to handle')); + } +} + +//Inputs that are needed, do not continue without these +valdata('peer_id', true); +valdata('port'); +valdata('info_hash', true); + +//Use the tracker key extension. Makes it much harder to steal a session. +if (!isset($_GET['key'])) { + $_GET['key'] = ''; +} +valdata('key'); + +//Do we have a valid client port? +if (!ctype_digit($_GET['port']) || $_GET['port'] < 1 || $_GET['port'] > 65535) { + die(track('Invalid client port')); +} + +//Array key, unique for each client and torrent +$sum = sha1($_GET['peer_id'].$_GET['info_hash']); + +//Make sure we've got a user agent to avoid errors +//Used for debugging +if (!isset($_SERVER['HTTP_USER_AGENT'])) { + $_SERVER['HTTP_USER_AGENT'] = ''; //Must always be set +} + +//When should we remove the client? +$expire = time()+$interval; + +//Have this client registered itself before? Check that it uses the same key +if (isset($d[$sum])) { + if ($d[$sum][6] !== $_GET['key']) { + sleep(3); //Anti brute force + die(track('Access denied, authentication failed')); + } +} + +//Add/update the client in our global list of clients, with some information +$d[$sum] = array($_SERVER['REMOTE_ADDR'], $_GET['peer_id'], $_GET['port'], $expire, $_GET['info_hash'], $_SERVER['HTTP_USER_AGENT'], $_GET['key'], is_seed()); + +//No point in saving the user agent, unless we are debugging +if (!__DEBUGGING_ENABLED) { + unset($d[$sum][5]); +} elseif (!empty($_GET)) { //We are debugging, add GET parameters to database + $d[$sum]['get_parm'] = $_GET; +} + +//Did the client stop the torrent? +//We dont care about other events +if (isset($_GET['event']) && $_GET['event'] === 'stopped') { + unset($d[$sum]); + db_save($d); + die(track(array())); //The RFC says its OK to return whatever we want when the client stops downloading, + //however, some clients will complain about the tracker not working, hence we return + //an empty bencoded peer list +} + +//Check if any client timed out +foreach($d as $k => $data) { + if (time() > $data[3]+__CLIENT_TIMEOUT) { //Give the client some extra time before timeout + unset($d[$k]); //Client has gone away, remove it + } +} + +//Save the client list +db_save($d); + +//Compare info_hash to the rest of our clients and remove anyone who does not have the correct torrent +foreach($d as $id => $info) { + if ($info[4] !== $_GET['info_hash']) { + unset($d[$id]); + } +} + +//Remove self from list, no point in having ourselfes in the client dictionary +unset($d[$sum]); + +//Add a few more seconds on the timeout to balance the load +$interval += rand(0, 10); + +//Bencode the dictionary and send it back +die(track($d, $interval, $interval_min)); \ No newline at end of file