common.php 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. <?php declare(strict_types=1);
  2. function output(int $code, string $msg = '', array $logs = [''], array $data = []): never {
  3. http_response_code($code);
  4. $shortCode = intval($code / 100);
  5. if ($shortCode === 5)
  6. error_log('Internal error: ' . strip_tags($msg) . implode(LF, $logs));
  7. $final_message = match ($shortCode) {
  8. 2 => ($msg === '') ? '' : '<p><output>' . _('<strong>Success</strong>: ') . '<em>' . $msg . '</em></output></p>' . LF,
  9. 4 => '<p><output>' . _('<strong>User error</strong>: ') . '<em>' . $msg . '</em></output></p>' . LF,
  10. 5 => '<p><output>' . _('<strong>Server error</strong>: ') . '<em>' . $msg . '</em></output></p>' . LF,
  11. };
  12. if (is_callable('displayPage'))
  13. displayPage(array_merge(['final_message' => $final_message], $data));
  14. echo $final_message;
  15. exit();
  16. }
  17. function exescape(array $args, array &$output = NULL, int &$result_code = NULL): int {
  18. exec('2>&1 ' . implode(' ', array_map('escapeshellarg', $args)), $output, $result_code);
  19. return $result_code;
  20. }
  21. function kdig(string $name, string $type, string $server = NULL): array {
  22. exescape([
  23. CONF['dns']['kdig_path'],
  24. '+json',
  25. '+timeout=5',
  26. '+retry=0',
  27. '+noidn',
  28. '-q',
  29. $name,
  30. '-t',
  31. $type,
  32. ...(($server === CONF['reg']['address']) ? ['+tcp'] : []),
  33. ...(isset($server) ? ['@' . $server] : []),
  34. ], $output, $code);
  35. if ($code !== 0)
  36. throw new KdigException($name . ' ' . $type . ' resolution failed.');
  37. foreach ($output as &$line)
  38. if (str_starts_with($line, ';'))
  39. $line = '';
  40. return json_decode(implode(LF, $output), true, flags: JSON_THROW_ON_ERROR);
  41. }
  42. function insert(string $table, array $values): void {
  43. $query = 'INSERT INTO "' . $table . '"(';
  44. foreach ($values as $key => $val) {
  45. if ($key === array_key_last($values))
  46. $query .= "$key";
  47. else
  48. $query .= "$key, ";
  49. }
  50. $query .= ') VALUES(';
  51. foreach ($values as $key => $val) {
  52. if ($key === array_key_last($values))
  53. $query .= ":$key";
  54. else
  55. $query .= ":$key, ";
  56. }
  57. $query .= ')';
  58. DB->prepare($query)
  59. ->execute($values);
  60. }
  61. function query(string $action, string $table, array $conditions = [], array $columns = NULL): array {
  62. $query = match ($action) {
  63. 'select' => 'SELECT ' . implode(',', $columns ?? ['*']),
  64. 'delete' => 'DELETE',
  65. };
  66. $query .= ' FROM "' . $table . '"';
  67. foreach ($conditions as $key => $val) {
  68. if ($key === array_key_first($conditions))
  69. $query .= " WHERE $key = :$key";
  70. else
  71. $query .= " AND $key = :$key";
  72. }
  73. $stmt = DB->prepare($query);
  74. $stmt->execute($conditions);
  75. if (count($columns ?? []) === 1)
  76. return array_column($stmt->fetchAll(PDO::FETCH_ASSOC), $columns[0]);
  77. return $stmt->fetchAll(PDO::FETCH_ASSOC);
  78. }
  79. function displayIndex(): void { ?>
  80. <nav>
  81. <dl>
  82. <?php foreach (PAGES[SERVICE] as $pageId => $page) {
  83. if ($pageId === 'index') continue;
  84. ?>
  85. <dt><a href="<?= $pageId ?>"><?= $page['title'] ?></a></dt>
  86. <dd>
  87. <?= $page['description'] ?>
  88. </dd>
  89. <?php } ?>
  90. </dl>
  91. </nav>
  92. <?php
  93. }
  94. function redirUrl(string $pageId): string {
  95. return CONF['common']['prefix'] . '/' . $pageId . '?redir=' . PAGE_URL;
  96. }
  97. function redir(string $redir_to = NULL): never {
  98. $redir_to ??= $_GET['redir'] ?? NULL;
  99. if ($redir_to === NULL) {
  100. header('Location: ' . CONF['common']['prefix'] . '/');
  101. exit();
  102. }
  103. if (preg_match('/^[0-9a-z\/-]{0,128}$/D', $redir_to) !== 1)
  104. output(403, 'Wrong character in <code>redir</code>.');
  105. header('Location: ' . CONF['common']['prefix'] . '/' . $redir_to);
  106. exit();
  107. }
  108. // PHP rmdir() only works on empty directories
  109. function removeDirectory(string $dir): void {
  110. $dirObj = new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS);
  111. $files = new RecursiveIteratorIterator($dirObj, RecursiveIteratorIterator::CHILD_FIRST);
  112. foreach ($files as $file)
  113. $file->isDir() && !$file->isLink() ? rmdir($file->getPathname()) : unlink($file->getPathname());
  114. if (rmdir($dir) !== true)
  115. output(500, 'Unable to remove directory.');
  116. }
  117. function equalArrays(array $a, array $b): bool {
  118. return array_diff($a, $b) === [] AND array_diff($b, $a) === [];
  119. }
  120. /*
  121. This token authenticates the user to the server through a public communication (the DNS).
  122. It is therefore also designed to keep private:
  123. - the user's id
  124. - that a same user used a token multiple times (by using a unique salt for each token)
  125. */
  126. if (time() - query('select', 'params', ['name' => 'secret_key_last_change'], ['value'])[0] >= 86400 * 20) {
  127. DB->prepare("UPDATE params SET value = :secret_key WHERE name = 'secret_key';")
  128. ->execute([':secret_key' => bin2hex(random_bytes(32))]);
  129. DB->prepare("UPDATE params SET value = :last_change WHERE name = 'secret_key_last_change';")
  130. ->execute([':last_change' => time()]);
  131. }
  132. define('SECRET_KEY', hex2bin(query('select', 'params', ['name' => 'secret_key'], ['value'])[0]));
  133. function getAuthToken(): string {
  134. $salt = bin2hex(random_bytes(4));
  135. $hash = hash_hmac('sha256', $salt . ($_SESSION['id'] ?? ''), SECRET_KEY);
  136. return $salt . '-' . substr($hash, 0, 32);
  137. }
  138. function checkAuthToken(string $salt, string $hash): void {
  139. $correctProof = substr(hash_hmac('sha256', $salt . $_SESSION['id'], SECRET_KEY), 0, 32);
  140. if (hash_equals($correctProof, $hash) !== true)
  141. output(403, _('Wrong proof.'));
  142. }