summaryrefslogtreecommitdiff
path: root/includes/restore.php
diff options
context:
space:
mode:
Diffstat (limited to 'includes/restore.php')
-rw-r--r--includes/restore.php130
1 files changed, 130 insertions, 0 deletions
diff --git a/includes/restore.php b/includes/restore.php
new file mode 100644
index 0000000..72748ab
--- /dev/null
+++ b/includes/restore.php
@@ -0,0 +1,130 @@
+<?php
+
+function isJson($string): bool {
+ json_decode($string);
+ return (json_last_error() == JSON_ERROR_NONE);
+}
+
+function pkcs7_unpad($data) {
+ return substr($data, 0, -ord($data[strlen($data) - 1]));
+}
+
+function timeAgo($time): string {
+ if (!is_numeric($time)) {
+ $time = strtotime($time);
+ }
+
+ $periods = ["second", "minute", "hour", "day", "week", "month", "year", "age"];
+ $lengths = array("60", "60", "24", "7", "4.35", "12", "100");
+
+ $now = time();
+
+ $difference = $now - $time;
+ if ($difference <= 10 && $difference >= 0) {
+ return $tense = "now";
+ } elseif ($difference > 0) {
+ $tense = "ago";
+ } else {
+ $tense = "later";
+ }
+
+ for ($j = 0; $difference >= $lengths[$j] && $j < count($lengths)-1; $j++) {
+ $difference /= $lengths[$j];
+ }
+
+ $difference = round($difference);
+
+ $period = $periods[$j] . ($difference >1 ? "s" :'');
+ return "{$difference} {$period} {$tense}";
+}
+
+if (!isset($_SERVER['argv'][1]) || !isset($_SERVER['argv'][2])) {
+ echo("Usage: php " . $_SERVER['argv'][0] . " <file> <key>\n");
+ die();
+} else {
+ $file = @file_get_contents($_SERVER['argv'][1]);
+ $raw = @file_get_contents($_SERVER['argv'][2]);
+
+ if ($file === false) {
+ echo("Unable to open backup file\n");
+ die();
+ }
+
+ if ($raw === false) {
+ echo("Unable to open key file\n");
+ die();
+ }
+
+ $raw2 = base64_decode($raw);
+
+ if (!isJson($raw2)) {
+ echo("Key file is corrupt\n");
+ die();
+ }
+
+ $keydata = json_decode($raw2, true);
+
+ if (!is_array($keydata) || !isset($keydata["iv"]) || !isset($keydata["key"])) {
+ echo("Key file is invalid\n");
+ die();
+ }
+
+ $iv = hex2bin($keydata["iv"]);
+ $key = hex2bin($keydata["key"]);
+
+ $decrypted = openssl_decrypt($file, 'AES-256-CBC', $key, OPENSSL_RAW_DATA, $iv);
+
+ if ($decrypted === false) {
+ echo("Unable to decrypt backup\n");
+ die();
+ }
+
+ $unpadded = pkcs7_unpad($decrypted);
+
+ if (!is_string($unpadded)) {
+ echo("Unable to decrypt backup\n");
+ die();
+ }
+
+ if (!isJson($unpadded)) {
+ echo("Backup is corrupt\n");
+ die();
+ }
+
+ $data = json_decode($unpadded, true);
+
+ if (!is_array($data) || !isset($data["date"]) || !isset($data["files"])) {
+ echo("Backup is invalid\n");
+ die();
+ }
+
+ echo(realpath($_SERVER['argv'][1]) . "\n Key: " . $_SERVER['argv'][2] . "\n Date: " . date('r', strtotime($data["date"])) . " (" . timeAgo($data["date"]) . ")" . "\n Contents: " . count($data["files"]) . " files\n");
+
+ @mkdir("./_restored");
+
+ $index = 0;
+ foreach ($data["files"] as $file) {
+ if ($file["dir"] === "") {
+ print("[$index] /" . $file["file"] . "\n");
+ } else {
+ print("[$index] /" . $file["dir"] . "/" . $file["file"] . "\n");
+ }
+
+ $content = base64_decode($file["content"]);
+ if (sha1($content) !== $file["checksum"][0]) {
+ print(" Backed up file is corrupted (SHA1 mismatch)\n Expected: " . $file["checksum"][0] . "\n Got: " . sha1($content) . "\n");
+ die("Backup aborted.\n");
+ }
+ if (md5($content) !== $file["checksum"][1]) {
+ print(" Backed up file is corrupted (MD5 mismatch)\n Expected: " . $file["checksum"][1] . "\n Got: " . md5($content) . "\n");
+ die("Backup aborted.\n");
+ }
+
+ @mkdir("./_restored/" . $file["dir"], 0777, true);
+ file_put_contents("./_restored/" . $file["dir"] . "/" . $file["file"], $content);
+
+ $index++;
+ }
+
+ print("Restored backup to ./_restored; review files before restoring to production\n");
+} \ No newline at end of file