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, "Requested 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 auth"); } $verified = sodium_crypto_sign_open($decrypted, $member->signing_public_key); if($verified == null) { send_failure(5, "Could not verify upload auth"); } if($verified != $checksum) { send_failure(5, "Invalid checksum on upload auth"); } } function cleanup_upload_session($token) { unlink(PPRF_DATA_DIR . "/upload-session-info-" . bin2hex($token)); unlink(PPRF_DATA_DIR . "/upload-session-file-" . bin2hex($token)); } function read_authenticated_message($handle, $ppcl) { $data = array(); $name_len = unpack("C", fread($handle, 1))[1]; $data["member_name"] = fread($handle, $name_len); $auth_len = unpack("v", fread($handle, 2))[1]; $data["authentication"] = fread($handle, $auth_len); $data["member"] = get_member($ppcl, $data["member_name"]); return $data; } 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)); // Get collection if($message_info["type"] == 6) { $ppcl = get_ppcl(); verify_collection_message($handle, $ppcl); $data = file_get_contents(PUBLICATION_DIR . "/collection.ppcl"); $message = pack("V", strlen($data)); $message .= $data; send_message(135, $message); } // Register name if($message_info["type"] == 32) { $ppcl = get_ppcl(); verify_collection_message($handle, $ppcl); $auth = read_authenticated_message($handle, $ppcl); $member = $auth["member"]; $name_size = unpack("C", fread($handle, 1))[1]; $name = fread($handle, $name_size); $name_auth = sodium_crypto_sign_open($auth["authentication"], $member->signing_public_key); if($name_auth == null) { send_failure(11, "Could not authenticate register name request"); } if($name_auth != hash("sha512", $name, true)) { send_failure(11, "Invalid checksum"); } 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]; $chunk_size = unpack("P", fread($handle, 8))[1]; $chunk_path = PPRF_DATA_DIR . "/upload-session-file-chunk-offset-" . $offset . "-" . bin2hex($session_header["token"]); $member = get_member($ppcl, $session["member_name"]); if($offset != filesize($path)) { $expected = filesize($path); cleanup_upload_session($session_header["token"]); send_failure(6, "Invalid offset, got " . $offset . " expected " . $expected); } $read = 0; while($read < $chunk_size) { $data = fread($handle, $chunk_size); file_put_contents($chunk_path, $data, FILE_APPEND); file_put_contents($path, $data, FILE_APPEND); $read += strlen($data); } // Verify data $checksum = hash_file("sha512", $chunk_path, true); unlink($chunk_path); error_log("Received " . strlen($data) . " bytes from " . $message_info["size"] . " byte message"); verify_upload_auth($session_header, $session, $member, $checksum); 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; $flag_cancel = ($flags & 1 << 0) != 0; $flag_replace = ($flags & 1 << 1) != 0; $flag_vcdiff = ($flags & 1 << 2) != 0; // If cancel flag set if($flag_cancel) { cleanup_upload_session($session_header["token"]); send_confirmation(); } 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(!$flag_replace && filesize($dest_path) != 0) { cleanup_upload_session($session_header["token"]); send_failure(10, "The destination \"" . $dest_name . "\" is not empty"); } // If destination is already published foreach($ppcl->publications as $pub) { if($pub->name == $dest_name) { send_failure(15, "The destination \"" . $dest_name . "\" cannot be overwritten as it is currently published"); } } // Verify data $checksum = hash_file("sha512", $upload_path, true); verify_upload_auth($session_header, $session, $member, $checksum); // Copy to destination if(!$flag_vcdiff) { rename($upload_path, $dest_path); } else if(!ENABLE_PPRF_VCDIFF) { send_failure(1, "VCDIFF is not supported by this server"); } else { $old_hash = hash_file("sha512", $dest_path, true); $patched_path = PPRF_DATA_DIR . "/upload-session-patch-" . bin2hex($session_header["token"]); $output=null; $retval=null; exec(XDELTA3_PATH . " -d -s " . escapeshellarg($dest_path) . " " . escapeshellarg($upload_path) . " " . escapeshellarg($patched_path), $output, $retval); if($retval != 0) { cleanup_upload_session($session_header["token"]); send_failure(13, "xdelta3 failed with exit code " . $retval); } rename($patched_path, $dest_path); } cleanup_upload_session($session_header["token"]); send_confirmation(); } // Publish message if($message_info["type"] == 36) { $ppcl = get_ppcl(); verify_collection_message($handle, $ppcl); $auth = read_authenticated_message($handle, $ppcl); $str_len = unpack("v", fread($handle, 2))[1]; $pub_str = fread($handle, $str_len); try { $pub_entry = new CollectionPublication($pub_str, $ppcl->members); $signature = sodium_crypto_sign_open($auth["authentication"], $auth["member"]->signing_public_key); if($signature == null) { send_failure(5, "Could not verify member signature"); } $expected_auth = hash("sha512", $pub_str, true); $expected_auth .= $ppcl->current_state_token; if($expected_auth != $signature) { send_failure(5, "Invalid authorisation token"); } array_push($ppcl->publications, $pub_entry); file_put_contents(PUBLICATION_DIR . "/collection.ppcl", $ppcl->to_string()); send_confirmation(); } catch(Exception $e) { send_failure(14, $e->getMessage()); } } // Unpublish message if($message_info["type"] == 37) { $ppcl = get_ppcl(); verify_collection_message($handle, $ppcl); $auth = read_authenticated_message($handle, $ppcl); $name_len = unpack("C", fread($handle, 1))[1]; $name = fread($handle, $name_len); $pub_entry = null; foreach($ppcl->publications as $pub) { if($pub->name == $name) { $pub_entry = $pub; break; } } if($pub_entry == null) { send_failure(16, "Publication \"" . $name . "\" not found"); } $signature = sodium_crypto_sign_open($auth["authentication"], $auth["member"]->signing_public_key); if($signature == null) { send_failure(5, "Could not verify member signature"); } $expected_auth = hash("sha512", $name, true); $expected_auth .= $ppcl->current_state_token; if($expected_auth != $signature) { send_failure(5, "Invalid authorisation token"); } try { $i = array_search($pub_entry, $ppcl->publications); array_splice($ppcl->publications, $i, 1); file_put_contents(PUBLICATION_DIR . "/collection.ppcl", $ppcl->to_string()); send_confirmation(); } catch(Exception $e) { send_failure(14, $e->getMessage()); } } // // Get identity message // if($message_info["type"] == 41) { // $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); // $message = pack("v", strlen($ppcl->shared_signature_key)); // $message .= $ppcl->shared_signature_key; // $message = pack("v", strlen($ppcl->current_state_token)); // $message .= $ppcl->current_state_token; // $message .= pack("v", strlen($member->collection_secret)); // $message .= $member->collection_secret; // send_message(161, $message); // } send_failure(1, "Messages of type " . $message_info["type"] . " are not supported by this server"); ?>