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"); ?>