Anonymous 3D Imageboard http://cyberia.digital/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

358 line
9.9KB

  1. <?php
  2. /*
  3. * Bitstorm - A small and fast Bittorrent tracker
  4. * Copyright 2008 Peter Caprioli & Simon Sessingø
  5. *
  6. * This program is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. */
  19. /*************************
  20. ** Configuration start **
  21. *************************/
  22. /*
  23. * Enable debugging?
  24. * This allows anyone to see the entire peer database by appending ?debug to the announce URL.
  25. * It will also create debugging file used to report php errors.
  26. */
  27. define('__DEBUGGING_ENABLED', true);
  28. /**
  29. * Version
  30. */
  31. define('__VERSION', 1.5);
  32. /**
  33. * How often should clients pull server for new clients? (Seconds)
  34. */
  35. define('__INTERVAL', 1800);
  36. /**
  37. * What's the minimum interval a client may pull the server? (Seconds)
  38. * Some bittorrent clients does not obey this
  39. */
  40. define('__INTERVAL_MIN', 300);
  41. /**
  42. * How long should we wait for a client to re-announce after the last announce expires? (Seconds)
  43. */
  44. define('__CLIENT_TIMEOUT', 60);
  45. /**
  46. * Skip sending the peer id if client does not want it?
  47. * Hint: Should be set to true
  48. */
  49. define('__NO_PEER_ID', true);
  50. /**
  51. * Should seeders not see each others?
  52. * Hint: Should be set to true
  53. */
  54. define('__NO_SEED_P2P', true);
  55. /**
  56. * Where should we save the peer database
  57. * On Linux, you should use /dev/shm as it is very fast.
  58. * On Windows, you will need to change this value to some other valid path such as C:/Peers.txt
  59. */
  60. define('__LOCATION_PEERS', 'peers.txt');
  61. /**
  62. * Should we enable short announces?
  63. * This allows NATed clients to get updates much faster, but it also
  64. * takes more load on the server. (This is just an experimental feature which may be turned off)
  65. */
  66. define('__ENABLE_SHORT_ANNOUNCE', false);
  67. /**
  68. * In case someone tries to access the tracker using a browser, redirect to this URL or file
  69. */
  70. define('__REDIR_BROWSER', '');
  71. define('__LOG_FILE', __DIR__ . '/error.log');
  72. /***********************
  73. ** Configuration end **
  74. ***********************/
  75. if(__DEBUGGING_ENABLED === true) {
  76. set_error_handler(function ($errno, $errstr, $errfile, $errline) {
  77. if (file_exists(__LOG_FILE) === false) {
  78. $handle = fopen(__LOG_FILE, 'w+b');
  79. fclose($handle);
  80. }
  81. file_put_contents(__LOG_FILE, sprintf('Line: %s - Error: %s', $errline, $errstr));
  82. }, E_ALL);
  83. }
  84. //Send response as text
  85. header('Content-type: Text/Plain');
  86. header('X-Tracker-Version: Bitstorm ' . __VERSION); //Please give me some credit
  87. /**
  88. * If you *really* dont want to, comment this line out.
  89. * Bencoding function, returns a bencoded dictionary.
  90. * You may go ahead and enter custom keys in the dictionary in this function if you'd like.
  91. */
  92. function track($list, $interval = 60, $min_ival = 0)
  93. {
  94. if (is_string($list)) { //Did we get a string? Return an error to the client
  95. return 'd14:failure reason' . strlen($list) . ':' . $list . 'e';
  96. }
  97. $p = ''; //Peer directory
  98. $c = $i = 0; //Complete and Incomplete clients
  99. foreach ($list as $d) { //Runs for each client
  100. if ($d[7]) { //Are we seeding?
  101. $c++; //Seeding, add to complete list
  102. if (__NO_SEED_P2P && is_seed()) { //Seeds should not see each others
  103. continue;
  104. }
  105. } else {
  106. $i++; //Not seeding, add to incomplete list
  107. }
  108. //Do some bencoding
  109. $pid = '';
  110. if (isset($_GET['no_peer_id']) === false && __NO_PEER_ID) { //Shall we include the peer id
  111. $pid = '7:peer id' . strlen($d[1]) . ':' . $d[1];
  112. }
  113. $p .= 'd2:ip' . strlen($d[0]) . ':' . $d[0] . $pid . '4:porti' . $d[2] . 'ee';
  114. }
  115. //Add some other paramters in the dictionary and merge with peer list
  116. $r = 'd8:intervali' . $interval . 'e12:min intervali' . $min_ival . 'e8:completei' . $c . 'e10:incompletei' . $i . 'e5:peersl' . $p . 'ee';
  117. return $r;
  118. }
  119. //Find out if we are seeding or not. Assume not if unknown.
  120. function is_seed()
  121. {
  122. return (isset($_GET['left']) && (int)$_GET['left'] === 0);
  123. }
  124. /*
  125. * Yeah, this is the database engine. It's pretty bad, uses files to store peers.
  126. * Should be easy to rewrite to use SQL instead.
  127. *
  128. * Yes, sometimes collisions may occur and screw the DB over. It might or might not
  129. * recover by itself.
  130. */
  131. //Save database to file
  132. function db_save($data)
  133. {
  134. $b = serialize($data);
  135. $handle = fopen(__LOCATION_PEERS, 'wb');
  136. if ($handle === false) {
  137. return false;
  138. }
  139. if (flock($handle, LOCK_EX) === false) {
  140. return false;
  141. }
  142. fwrite($handle, $b);
  143. fclose($handle);
  144. return true;
  145. }
  146. //Load database from file
  147. function db_open()
  148. {
  149. $p = '';
  150. $handle = fopen(__LOCATION_PEERS, 'rb');
  151. if ($handle === false) {
  152. return false;
  153. }
  154. if (flock($handle, LOCK_EX) === false) {
  155. return false;
  156. }
  157. while (feof($handle) === false) {
  158. $p .= fread($handle, 512);
  159. }
  160. fclose($handle);
  161. return ((string)$p !== '') ? unserialize($p) : true;
  162. }
  163. //Check if DB file exists, otherwise create it
  164. function db_exists($createEmpty = false)
  165. {
  166. if (file_exists(__LOCATION_PEERS) === true) {
  167. return true;
  168. }
  169. if ($createEmpty === true) {
  170. return db_save([]);
  171. }
  172. return false;
  173. }
  174. //Default announce time
  175. $interval = __INTERVAL;
  176. //Minimal announce time (does not apply to short announces)
  177. $interval_min = __INTERVAL_MIN;
  178. /*
  179. * This is a pretty smart feature not present in other tracker software.
  180. * If you expect to have many NATed clients, add short as a GET parameter,
  181. * and clients will pull much more often.
  182. *
  183. * This can be done automatically, simply try to open a TCP connection to
  184. * the client and assume it is NATed if not successful.
  185. */
  186. if (isset($_GET['short']) && __ENABLE_SHORT_ANNOUNCE) {
  187. $interval = 120;
  188. $interval_min = 30;
  189. }
  190. //Did we get any parameters at all?
  191. //Client is probably a web browser, do a redirect
  192. if (empty($_GET)) {
  193. header('Location: ' . __REDIR_BROWSER);
  194. die();
  195. }
  196. //Create database if it does not exist
  197. db_exists(true) or die(track('Unable to create database'));
  198. $d = db_open();
  199. //Do we want to debug? (Should not be used by default)
  200. if (isset($_GET['debug']) && __DEBUGGING_ENABLED) {
  201. echo 'Connected peers:' . count($d) . "\n\n";
  202. die();
  203. }
  204. //Did we get a failure from the database?
  205. if ($d === false) {
  206. die(track('Database failure'));
  207. }
  208. //Do some input validation
  209. function valdata($g, $must_be_20_chars = false)
  210. {
  211. if (!isset($_GET[$g])) {
  212. die(track('Missing one or more arguments'));
  213. }
  214. if (!is_string($_GET[$g])) {
  215. die(track('Invalid types on one or more arguments'));
  216. }
  217. if ($must_be_20_chars && strlen($_GET[$g]) != 20) {
  218. die(track('Invalid length on ' . $g . ' argument'));
  219. }
  220. if (strlen($_GET[$g]) > 128) { //128 chars should really be enough
  221. die(track('Argument ' . $g . ' is too large to handle'));
  222. }
  223. }
  224. //Inputs that are needed, do not continue without these
  225. valdata('peer_id', true);
  226. valdata('port');
  227. valdata('info_hash', true);
  228. //Use the tracker key extension. Makes it much harder to steal a session.
  229. if (!isset($_GET['key'])) {
  230. $_GET['key'] = '';
  231. }
  232. valdata('key');
  233. //Do we have a valid client port?
  234. if (!ctype_digit($_GET['port']) || $_GET['port'] < 1 || $_GET['port'] > 65535) {
  235. die(track('Invalid client port'));
  236. }
  237. //Array key, unique for each client and torrent
  238. $sum = sha1($_GET['peer_id'] . $_GET['info_hash']);
  239. //Make sure we've got a user agent to avoid errors
  240. //Used for debugging
  241. if (!isset($_SERVER['HTTP_USER_AGENT'])) {
  242. $_SERVER['HTTP_USER_AGENT'] = ''; //Must always be set
  243. }
  244. //When should we remove the client?
  245. $expire = time() + $interval;
  246. //Have this client registered itself before? Check that it uses the same key
  247. if (isset($d[$sum])) {
  248. if ((string)$d[$sum][6] !== (string)$_GET['key']) {
  249. sleep(3); //Anti brute force
  250. die(track('Access denied, authentication failed'));
  251. }
  252. }
  253. //Add/update the client in our global list of clients, with some information
  254. $d[$sum] = [$_SERVER['REMOTE_ADDR'], $_GET['peer_id'], $_GET['port'], $expire, $_GET['info_hash'], $_SERVER['HTTP_USER_AGENT'], $_GET['key'], is_seed()];
  255. //No point in saving the user agent, unless we are debugging
  256. if (!__DEBUGGING_ENABLED) {
  257. unset($d[$sum][5]);
  258. } elseif (!empty($_GET)) { //We are debugging, add GET parameters to database
  259. $d[$sum]['get_parm'] = $_GET;
  260. }
  261. //Did the client stop the torrent?
  262. //We dont care about other events
  263. if (isset($_GET['event']) && (string)$_GET['event'] === 'stopped') {
  264. unset($d[$sum]);
  265. db_save($d);
  266. die(track([])); //The RFC says its OK to return whatever we want when the client stops downloading,
  267. //however, some clients will complain about the tracker not working, hence we return
  268. //an empty bencoded peer list
  269. }
  270. //Check if any client timed out
  271. foreach ($d as $k => $data) {
  272. if (time() > $data[3] + __CLIENT_TIMEOUT) { //Give the client some extra time before timeout
  273. unset($d[$k]); //Client has gone away, remove it
  274. }
  275. }
  276. //Save the client list
  277. db_save($d);
  278. //Compare info_hash to the rest of our clients and remove anyone who does not have the correct torrent
  279. foreach ($d as $id => $info) {
  280. if ((string)$info[4] !== (string)$_GET['info_hash']) {
  281. unset($d[$id]);
  282. }
  283. }
  284. // Remove self from list, no point in having ourselfes in the client dictionary
  285. unset($d[$sum]);
  286. // Add a few more seconds on the timeout to balance the load
  287. $interval += mt_rand(0, 10);
  288. // Bencode the dictionary and send it back
  289. echo track($d, $interval, $interval_min);
  290. exit(0);