|
@@ -0,0 +1,260 @@
|
|
|
+<?php
|
|
|
+include("config.php");
|
|
|
+include_once("ppcl.php");
|
|
|
+
|
|
|
+function send_message($type, $message) {
|
|
|
+ header("Content-type: application/pprf");
|
|
|
+ echo("PPRF\x00");
|
|
|
+ $len = strlen($message) + 1;
|
|
|
+ echo(pack("PC", $len, $type));
|
|
|
+ echo($message);
|
|
|
+ exit();
|
|
|
+}
|
|
|
+
|
|
|
+function send_failure($code, $message) {
|
|
|
+ $len = strlen($message);
|
|
|
+ $msg = pack("vv", $code, $len);
|
|
|
+ $msg .= $message;
|
|
|
+ send_message(193, $msg);
|
|
|
+}
|
|
|
+
|
|
|
+function send_confirmation() {
|
|
|
+ send_message(192, "");
|
|
|
+}
|
|
|
+
|
|
|
+function get_ppcl() {
|
|
|
+ $ppcl = new Ppcl();
|
|
|
+ $ppcl->from_string(file_get_contents(PUBLICATION_DIR . "/collection.ppcl"));
|
|
|
+ return $ppcl;
|
|
|
+}
|
|
|
+
|
|
|
+function get_member($ppcl, $name) {
|
|
|
+ foreach($ppcl->members as $m) {
|
|
|
+ if($m->name == $name) {
|
|
|
+ return $m;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ send_failure(3, "No member \"" . $member_name . "\" in collection");
|
|
|
+}
|
|
|
+
|
|
|
+function verify_collection_message($handle, $ppcl) {
|
|
|
+ $id = fread($handle, SODIUM_CRYPTO_SIGN_PUBLICKEYBYTES);
|
|
|
+ if($ppcl->identifier != $id) {
|
|
|
+ send_failure(2, "Provided collection not hosted by this server");
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function read_upload_session_message($handle) {
|
|
|
+ $data = array();
|
|
|
+ $tsize = unpack("v", fread($handle, 2))[1];
|
|
|
+ $data["token"] = fread($handle, $tsize);
|
|
|
+
|
|
|
+ $asize = unpack("v", fread($handle, 2))[1];
|
|
|
+ $data["auth"] = fread($handle, $asize);
|
|
|
+ return $data;
|
|
|
+}
|
|
|
+
|
|
|
+function get_upload_session($session_header) {
|
|
|
+ $file = fopen(PPRF_DATA_DIR . "/upload-session-info-" . bin2hex($session_header["token"]), 'rb');
|
|
|
+ if($file == null) {
|
|
|
+ send_failure(4, "Invalid upload session token");
|
|
|
+ }
|
|
|
+ $session = array();
|
|
|
+ $session["file_size"] = unpack("P", fread($file, 8))[1];
|
|
|
+ $session["key"] = fread($file, SODIUM_CRYPTO_BOX_KEYPAIRBYTES);
|
|
|
+ $name_size = unpack("C", fread($file, 1))[1];
|
|
|
+ $session["member_name"] = fread($file, $name_size);
|
|
|
+ return $session;
|
|
|
+}
|
|
|
+
|
|
|
+function verify_upload_auth($session_header, $session, $member, $checksum) {
|
|
|
+ $decrypted = sodium_crypto_box_seal_open($session_header["auth"], $session["key"]);
|
|
|
+ if($decrypted == null) {
|
|
|
+ send_failure(5, "Could not decrypt upload chunk auth");
|
|
|
+ }
|
|
|
+ $verified = sodium_crypto_sign_open($decrypted, $member->signing_public_key);
|
|
|
+ if($verified == null) {
|
|
|
+ send_failure(5, "Could not verify upload chunk auth");
|
|
|
+ }
|
|
|
+
|
|
|
+ if($verified != $checksum) {
|
|
|
+ send_failure(5, "Invalid checksum on upload chunk auth");
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function cleanup_upload_session($token) {
|
|
|
+ unlink(PPRF_DATA_DIR . "/upload-session-info-" . bin2hex($token));
|
|
|
+ unlink(PPRF_DATA_DIR . "/upload-session-file-" . bin2hex($token));
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+if(!ENABLE_PPRF) {
|
|
|
+ send_failure(1, "PPRF has been disabled on this server");
|
|
|
+}
|
|
|
+
|
|
|
+$handle = fopen("php://input", "rb");
|
|
|
+if(fread($handle, 5) != "PPRF\x00") {
|
|
|
+ send_failure(0, "Stream did not begin with PPRF magic number.");
|
|
|
+}
|
|
|
+
|
|
|
+$message_info = unpack("Psize/Ctype", fread($handle, 9));
|
|
|
+
|
|
|
+// Register name
|
|
|
+if($message_info["type"] == 32) {
|
|
|
+ $ppcl = get_ppcl();
|
|
|
+ verify_collection_message($handle, $ppcl);
|
|
|
+ $member_name_size = unpack("C", fread($handle, 1))[1];
|
|
|
+ $member_name = fread($handle, $member_name_size);
|
|
|
+ $member = get_member($ppcl, $member_name);
|
|
|
+
|
|
|
+ $signed_name_size = unpack("v", fread($handle, 2))[1];
|
|
|
+ $signed_name = fread($handle, $signed_name_size);
|
|
|
+
|
|
|
+ $name = sodium_crypto_sign_open($signed_name, $member->signing_public_key);
|
|
|
+ if($name == null) {
|
|
|
+ send_failure(11, "Could not verify signed name");
|
|
|
+ }
|
|
|
+
|
|
|
+ if(strtolower(substr($name, strlen($name)-5, 5)) != ".ppub") {
|
|
|
+ send_failure(7, "Name must end in \".ppub\"");
|
|
|
+ }
|
|
|
+
|
|
|
+ $path = PUBLICATION_DIR . "/" . $name;
|
|
|
+ if(file_exists($path)) {
|
|
|
+ send_failure(8, "Name already exists");
|
|
|
+ }
|
|
|
+
|
|
|
+ file_put_contents($path, "");
|
|
|
+ send_confirmation();
|
|
|
+}
|
|
|
+
|
|
|
+// Begin upload
|
|
|
+if($message_info["type"] == 33) {
|
|
|
+ $ppcl = get_ppcl();
|
|
|
+ verify_collection_message($handle, $ppcl);
|
|
|
+ $details = unpack("Pfile_size/Cname_size", fread($handle, 9));
|
|
|
+ $member_name = fread($handle, $details["name_size"]);
|
|
|
+ $member = get_member($ppcl, $member_name);
|
|
|
+
|
|
|
+ // Create upload session
|
|
|
+ $token = random_bytes(16);
|
|
|
+ $key = sodium_crypto_box_keypair();
|
|
|
+
|
|
|
+ $session_data = pack("P", $details["file_size"]);
|
|
|
+ $session_data .= $key;
|
|
|
+ $session_data .= pack("C", $details["name_size"]);
|
|
|
+ $session_data .= $member_name;
|
|
|
+ file_put_contents(PPRF_DATA_DIR . "/upload-session-info-" . bin2hex($token), $session_data);
|
|
|
+ file_put_contents(PPRF_DATA_DIR . "/upload-session-file-" . bin2hex($token), "");
|
|
|
+
|
|
|
+ $session_auth = sodium_crypto_box_publickey($key);
|
|
|
+ $session_auth .= $token;
|
|
|
+
|
|
|
+ // Build and send reply
|
|
|
+ $challenge = sodium_crypto_box_seal($session_auth, $member->sealing_public_key);
|
|
|
+ $reply = pack("v", strlen($challenge));
|
|
|
+ $reply .= $challenge;
|
|
|
+ $reply .= pack("V", PPRF_MAX_UPLOAD_CHUNK_SIZE);
|
|
|
+ send_message(160, $reply);
|
|
|
+}
|
|
|
+
|
|
|
+// Upload message
|
|
|
+if($message_info["type"] == 35) {
|
|
|
+ $ppcl = get_ppcl();
|
|
|
+ verify_collection_message($handle, $ppcl);
|
|
|
+ $session_header = read_upload_session_message($handle);
|
|
|
+ $session = get_upload_session($session_header);
|
|
|
+ $path = PPRF_DATA_DIR . "/upload-session-file-" . bin2hex($session_header["token"]);
|
|
|
+ $offset = unpack("P", fread($handle, 8))[1];
|
|
|
+
|
|
|
+ $member = get_member($ppcl, $session["member_name"]);
|
|
|
+
|
|
|
+ if($offset != filesize($path)) {
|
|
|
+ send_failure(6, "Invalid offset, got " . $offset . " expected " . filesize($path));
|
|
|
+ }
|
|
|
+
|
|
|
+ $data_size = $message_info["size"] - 9; // Message size minus headers
|
|
|
+ $data = fread($handle, $data_size);
|
|
|
+
|
|
|
+ // Verify data
|
|
|
+ $checksum = hash("sha512", $data, true);
|
|
|
+ verify_upload_auth($session_header, $session, $member, $checksum);
|
|
|
+
|
|
|
+ file_put_contents($path, $data, FILE_APPEND);
|
|
|
+ send_confirmation();
|
|
|
+}
|
|
|
+
|
|
|
+// Finalise upload message
|
|
|
+if($message_info["type"] == 34) {
|
|
|
+ $ppcl = get_ppcl();
|
|
|
+ verify_collection_message($handle, $ppcl);
|
|
|
+ $session_header = read_upload_session_message($handle);
|
|
|
+ $session = get_upload_session($session_header);
|
|
|
+ $upload_path = PPRF_DATA_DIR . "/upload-session-file-" . bin2hex($session_header["token"]);
|
|
|
+ $member = get_member($ppcl, $session["member_name"]);
|
|
|
+
|
|
|
+ $flags = unpack('C', fread($handle, 1))[1];
|
|
|
+ $dest_len = unpack('v', fread($handle, 2))[1];
|
|
|
+ $dest_name = fread($handle, $dest_len);
|
|
|
+ $dest_path = PUBLICATION_DIR . "/" . $dest_name;
|
|
|
+ $bsdiff_old_checksum = null;
|
|
|
+
|
|
|
+ // If cancel flag set
|
|
|
+ if(($flags & 1 << 0) != 0) {
|
|
|
+ cleanup_upload_session($session_header["token"]);
|
|
|
+ send_confirmation();
|
|
|
+ }
|
|
|
+
|
|
|
+ // If BSDIFF flag set
|
|
|
+ if(($flags & 1 << 2) != 0) {
|
|
|
+ $bsdiff_old_checksum = fread($handle, 64);
|
|
|
+ }
|
|
|
+
|
|
|
+ if(!file_exists($dest_path)) {
|
|
|
+ cleanup_upload_session($session_header["token"]);
|
|
|
+ send_failure(9, "The name \"" . $dest_name . "\" is not registered");
|
|
|
+ }
|
|
|
+
|
|
|
+ // If overwrite flag NOT set, and file is not empty
|
|
|
+ if(($flags & 1 << 1) == 0 && filesize($dest_path) != 0) {
|
|
|
+ cleanup_upload_session($session_header["token"]);
|
|
|
+ send_failure(10, "The destination \"" . $dest_name . "\" is not empty");
|
|
|
+ }
|
|
|
+
|
|
|
+ // Verify data
|
|
|
+ $checksum = hash_file("sha512", $upload_path, true);
|
|
|
+ verify_upload_auth($session_header, $session, $member, $checksum);
|
|
|
+
|
|
|
+ // Copy to destination
|
|
|
+ if($bsdiff_old_checksum == null) {
|
|
|
+ rename($upload_path, $dest_path);
|
|
|
+ }
|
|
|
+ else if(!ENABLE_PPRF_BSPATCH) {
|
|
|
+ send_failure(1, "BSPATCH is not supported by this server");
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ $old_hash = hash_file("sha512", $dest_path, true);
|
|
|
+ if($bsdiff_old_checksum != $old_hash) {
|
|
|
+ cleanup_upload_session($session_header["token"]);
|
|
|
+ send_failure(12, "Destination file does not match provided checksum");
|
|
|
+ }
|
|
|
+ $patched_path = PPRF_DATA_DIR . "/upload-session-patch-" . bin2hex($session_header["token"]);
|
|
|
+ $output=null;
|
|
|
+ $retval=null;
|
|
|
+ exec(BSPATCH_PATH . " " . escapeshellarg($dest_path) . " " . escapeshellarg($patched_path) . " " . escapeshellarg($upload_path), $output, $retval);
|
|
|
+ if($retval != 0) {
|
|
|
+ cleanup_upload_session($session_header["token"]);
|
|
|
+ send_failure(13, "bspatch failed with exit code " . $retval);
|
|
|
+ }
|
|
|
+
|
|
|
+ rename($patched_path, $dest_path);
|
|
|
+ }
|
|
|
+
|
|
|
+ cleanup_upload_session($session_header["token"]);
|
|
|
+ send_confirmation();
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+send_failure(1, "Messages of type " . $message_info["type"] . " are not supported by this server");
|
|
|
+
|
|
|
+?>
|