1
0

administration 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. #!/usr/bin/env php
  2. <?php declare(strict_types=1);
  3. /**
  4. * PrivateBin
  5. *
  6. * a zero-knowledge paste bin
  7. *
  8. * @link https://github.com/PrivateBin/PrivateBin
  9. * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
  10. * @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
  11. */
  12. namespace PrivateBin;
  13. use Exception;
  14. use PrivateBin\Configuration;
  15. use PrivateBin\Data\AbstractData;
  16. use PrivateBin\Model\Paste;
  17. define('PATH', dirname(__FILE__) . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR);
  18. require PATH . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php';
  19. /**
  20. * Administration
  21. *
  22. * Command line utility for administrative tasks.
  23. */
  24. class Administration
  25. {
  26. /**
  27. * configuration
  28. *
  29. * @access private
  30. * @var Configuration
  31. */
  32. private $_conf;
  33. /**
  34. * options, parsed from the command line arguments
  35. *
  36. * @access private
  37. * @var array
  38. */
  39. private $_opts = array();
  40. /**
  41. * data storage model
  42. *
  43. * @access private
  44. * @var AbstractData
  45. */
  46. private $_store;
  47. /**
  48. * deletes the requested document ID, if a valid ID and it exists
  49. *
  50. * @access private
  51. * @param string $pasteId
  52. */
  53. private function _delete($pasteId)
  54. {
  55. if (!Paste::isValidId($pasteId)) {
  56. self::_error('given ID is not a valid document ID (16 hexadecimal digits)', 5);
  57. }
  58. if (!$this->_store->exists($pasteId)) {
  59. self::_error('given ID does not exist, has expired or was already deleted', 6);
  60. }
  61. $this->_store->delete($pasteId);
  62. if ($this->_store->exists($pasteId)) {
  63. self::_error('document ID exists after deletion, permission problem?', 7);
  64. }
  65. exit("document $pasteId successfully deleted" . PHP_EOL);
  66. }
  67. /**
  68. * deletes all stored documents (regardless of expiration)
  69. *
  70. * @access private
  71. */
  72. private function _delete_all()
  73. {
  74. $ids = $this->_store->getAllPastes();
  75. foreach ($ids as $pasteid) {
  76. echo "Deleting document ID: $pasteid" . PHP_EOL;
  77. $this->_store->delete($pasteid);
  78. }
  79. exit("All documents successfully deleted" . PHP_EOL);
  80. }
  81. /**
  82. * deletes all unsupported v1 documents (regardless of expiration)
  83. *
  84. * @access private
  85. */
  86. private function _delete_v1()
  87. {
  88. $ids = $this->_store->getAllPastes();
  89. foreach ($ids as $pasteid) {
  90. try {
  91. $paste = $this->_store->read($pasteid);
  92. } catch (Exception $e) {
  93. echo "Error reading document {$pasteid}: ", $e->getMessage(), PHP_EOL;
  94. }
  95. if (array_key_exists('adata', $paste)) {
  96. continue;
  97. }
  98. echo "Deleting v1 document ID: $pasteid" . PHP_EOL;
  99. $this->_store->delete($pasteid);
  100. }
  101. exit("All unsupported legacy v1 documents successfully deleted" . PHP_EOL);
  102. }
  103. /**
  104. * removes empty directories, if current storage model uses Filesystem
  105. *
  106. * @access private
  107. */
  108. private function _empty_dirs()
  109. {
  110. if ($this->_conf->getKey('class', 'model') !== 'Filesystem') {
  111. self::_error('instance not using Filesystem storage, no directories to empty', 4);
  112. }
  113. $dir = $this->_conf->getKey('dir', 'model_options');
  114. passthru("find $dir -type d -empty -delete", $code);
  115. exit($code);
  116. }
  117. /**
  118. * display a message on STDERR and exits
  119. *
  120. * @access private
  121. * @static
  122. * @param string $message
  123. * @param int $code optional, defaults to 1
  124. */
  125. private static function _error($message, $code = 1)
  126. {
  127. self::_error_echo($message);
  128. exit($code);
  129. }
  130. /**
  131. * display a message on STDERR
  132. *
  133. * @access private
  134. * @static
  135. * @param string $message
  136. */
  137. private static function _error_echo($message)
  138. {
  139. fwrite(STDERR, 'Error: ' . $message . PHP_EOL);
  140. }
  141. /**
  142. * display usage help on STDOUT and exits
  143. *
  144. * @access private
  145. * @static
  146. * @param int $code optional, defaults to 0
  147. */
  148. private static function _help($code = 0)
  149. {
  150. echo <<<'EOT'
  151. Usage:
  152. administration [--delete <document id> | --delete-all | --delete-v1 |
  153. --empty-dirs | --help | --list-ids | --purge | --statistics]
  154. Options:
  155. -d, --delete deletes the requested document ID
  156. --delete-all deletes all documents
  157. --delete-v1 deletes all unsupported v1 documents
  158. -e, --empty-dirs removes empty directories (only if Filesystem storage is
  159. configured)
  160. -h, --help displays this help message
  161. -l, --list-ids lists all document IDs
  162. -p, --purge purge all expired documents
  163. -s, --statistics reads all stored documents and reports statistics
  164. EOT, PHP_EOL;
  165. exit($code);
  166. }
  167. /**
  168. * lists all stored document IDs
  169. *
  170. * @access private
  171. */
  172. private function _list_ids()
  173. {
  174. $ids = $this->_store->getAllPastes();
  175. foreach ($ids as $pasteid) {
  176. echo $pasteid, PHP_EOL;
  177. }
  178. exit;
  179. }
  180. /**
  181. * return option for given short or long keyname, if it got set
  182. *
  183. * @access private
  184. * @static
  185. * @param string $short
  186. * @param string $long
  187. * @return string|null
  188. */
  189. private function _option($short, $long)
  190. {
  191. foreach (array($short, $long) as $key) {
  192. if (array_key_exists($key, $this->_opts)) {
  193. return $this->_opts[$key];
  194. }
  195. }
  196. return null;
  197. }
  198. /**
  199. * initialize options from given argument array
  200. *
  201. * @access private
  202. * @static
  203. * @param array $arguments
  204. */
  205. private function _options_initialize($arguments)
  206. {
  207. if ($arguments > 3) {
  208. self::_error_echo('too many arguments given');
  209. echo PHP_EOL;
  210. self::_help(1);
  211. }
  212. if ($arguments < 2) {
  213. self::_error_echo('missing arguments');
  214. echo PHP_EOL;
  215. self::_help(2);
  216. }
  217. $this->_opts = getopt('hd:elps', array('help', 'delete:', 'delete-all', 'delete-v1', 'empty-dirs', 'list-ids', 'purge', 'statistics'));
  218. if (!$this->_opts) {
  219. self::_error_echo('unsupported arguments given');
  220. echo PHP_EOL;
  221. self::_help(3);
  222. }
  223. }
  224. /**
  225. * reads all stored documents and reports statistics
  226. *
  227. * @access public
  228. */
  229. private function _statistics()
  230. {
  231. $counters = array(
  232. 'burn' => 0,
  233. 'damaged' => 0,
  234. 'discussion' => 0,
  235. 'expired' => 0,
  236. 'legacy' => 0,
  237. 'md' => 0,
  238. 'percent' => 1,
  239. 'plain' => 0,
  240. 'progress' => 0,
  241. 'syntax' => 0,
  242. 'total' => 0,
  243. 'unknown' => 0,
  244. );
  245. $time = time();
  246. $ids = $this->_store->getAllPastes();
  247. $counters['total'] = count($ids);
  248. $dots = $counters['total'] < 100 ? 10 : (
  249. $counters['total'] < 1000 ? 50 : 100
  250. );
  251. $percentages = $counters['total'] < 100 ? 0 : (
  252. $counters['total'] < 1000 ? 4 : 10
  253. );
  254. echo "Total:\t\t\t{$counters['total']}", PHP_EOL;
  255. foreach ($ids as $pasteid) {
  256. try {
  257. $paste = $this->_store->read($pasteid);
  258. } catch (Exception $e) {
  259. echo "Error reading document {$pasteid}: ", $e->getMessage(), PHP_EOL;
  260. ++$counters['damaged'];
  261. }
  262. ++$counters['progress'];
  263. if (
  264. array_key_exists('expire_date', $paste['meta']) &&
  265. $paste['meta']['expire_date'] < $time
  266. ) {
  267. ++$counters['expired'];
  268. }
  269. if (array_key_exists('adata', $paste)) {
  270. $format = $paste['adata'][Paste::ADATA_FORMATTER];
  271. $discussion = $paste['adata'][Paste::ADATA_OPEN_DISCUSSION];
  272. $burn = $paste['adata'][Paste::ADATA_BURN_AFTER_READING];
  273. } else {
  274. echo "Unsupported v1 document ", $pasteid, PHP_EOL;
  275. ++$counters['legacy'];
  276. }
  277. if ($format === 'plaintext') {
  278. ++$counters['plain'];
  279. } elseif ($format === 'syntaxhighlighting') {
  280. ++$counters['syntax'];
  281. } elseif ($format === 'markdown') {
  282. ++$counters['md'];
  283. } else {
  284. ++$counters['unknown'];
  285. }
  286. $counters['discussion'] += (int) $discussion;
  287. $counters['burn'] += (int) $burn;
  288. // display progress
  289. if ($counters['progress'] % $dots === 0) {
  290. echo '.';
  291. if ($percentages) {
  292. $progress = $percentages / $counters['total'] * $counters['progress'];
  293. if ($progress >= $counters['percent']) {
  294. printf(' %d%% ', 100 / $percentages * $progress);
  295. ++$counters['percent'];
  296. }
  297. }
  298. }
  299. }
  300. echo PHP_EOL, <<<EOT
  301. Expired:\t\t{$counters['expired']}
  302. Burn after reading:\t{$counters['burn']}
  303. Discussions:\t\t{$counters['discussion']}
  304. Plain Text:\t\t{$counters['plain']}
  305. Source Code:\t\t{$counters['syntax']}
  306. Markdown:\t\t{$counters['md']}
  307. EOT, PHP_EOL;
  308. if ($counters['legacy'] > 0) {
  309. echo "Legacy v1:\t\t{$counters['legacy']}", PHP_EOL;
  310. }
  311. if ($counters['damaged'] > 0) {
  312. echo "Damaged:\t\t{$counters['damaged']}", PHP_EOL;
  313. }
  314. if ($counters['unknown'] > 0) {
  315. echo "Unknown format:\t\t{$counters['unknown']}", PHP_EOL;
  316. }
  317. }
  318. /**
  319. * constructor
  320. *
  321. * initializes and runs administrative tasks
  322. *
  323. * @access public
  324. */
  325. public function __construct()
  326. {
  327. $this->_options_initialize($_SERVER['argc']);
  328. if ($this->_option('h', 'help') !== null) {
  329. self::_help();
  330. }
  331. $this->_conf = new Configuration;
  332. if ($this->_option('e', 'empty-dirs') !== null) {
  333. $this->_empty_dirs();
  334. }
  335. $class = 'PrivateBin\\Data\\' . $this->_conf->getKey('class', 'model');
  336. $this->_store = new $class($this->_conf->getSection('model_options'));
  337. if (($pasteId = $this->_option('d', 'delete')) !== null) {
  338. $this->_delete($pasteId);
  339. }
  340. if ($this->_option(null, 'delete-all') !== null) {
  341. $this->_delete_all();
  342. }
  343. if ($this->_option(null, 'delete-v1') !== null) {
  344. $this->_delete_v1();
  345. }
  346. if ($this->_option('l', 'list-ids') !== null) {
  347. $this->_list_ids();
  348. }
  349. if ($this->_option('p', 'purge') !== null) {
  350. try {
  351. $this->_store->purge(PHP_INT_MAX);
  352. } catch (Exception $e) {
  353. echo 'Error purging documents: ', $e->getMessage(), PHP_EOL,
  354. 'Run the statistics to find damaged document IDs and either delete them or restore them from backup.', PHP_EOL;
  355. }
  356. exit('purging of expired documents concluded' . PHP_EOL);
  357. }
  358. if ($this->_option('s', 'statistics') !== null) {
  359. $this->_statistics();
  360. }
  361. }
  362. }
  363. new Administration();