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 listing if($message_info["type"] == 1) { error_log("Get listing!!!!"); $ppcl = get_ppcl(); verify_collection_message($handle, $ppcl); $data = unpack("vflags/vcols/Vskip/Ctake", fread($handle, 9)); $flag_tag = ($data["flags"] & (1 << 0)) != 0; $flag_search = ($data["flags"] & (1 << 1)) != 0; $flag_since = ($data["flags"] & (1 << 2)) != 0; $flag_hidden = ($data["flags"] & (1 << 3)) != 0; $col_title = ($data["cols"] & (1 << 0)) != 0; $col_author = ($data["cols"] & (1 << 1)) != 0; $col_description = ($data["cols"] & (1 << 2)) != 0; $col_timestamp = ($data["cols"] & (1 << 3)) != 0; $col_tags = ($data["cols"] & (1 << 4)) != 0; $col_poster = ($data["cols"] & (1 << 5)) != 0; $col_copyright = ($data["cols"] & (1 << 6)) != 0; $col_checksum = ($data["cols"] & (1 << 7)) != 0; $tag = null; $search = null; $since = null; if($flag_tag && $flag_search) { send_failure(1, "Listing by tag and search at the same time is not supported"); } if($flag_tag) { $size = unpack("v", fread($handle, 2))[1]; $tag = fread($handle, $size); } if($flag_search) { $size = unpack("v", fread($handle, 2))[1]; $search = fread($handle, $size); } if($flag_since) { $size = unpack("C", fread($handle, 2))[1]; $since = new DateTime(fread($handle, $size)); } $results = array(); if(USE_PPIX) { include_once("ppix.php"); $ppix = new Ppix(fopen(PUBLICATION_DIR . "/lib.ppix", 'rb')); if($flag_search) { $ids = $ppix->do_search(strtolower($search)); $list = array(); for ($i=0; $i < count($ids); $i++) { $list[$i] = $ppix->get_publication_by_id($ids[$i]); } $results = $list; } else if($flag_tag) { $tags = $ppix->get_tags(); $col = $tags[$tag]; if($col != null) { $ids = $ppix->get_collection_by_id($col); $list = array(); for ($i=0; $i < count($ids); $i++) { $list[$i] = $ppix->get_publication_by_id($ids[$i]); } $results = $list; } } else { $count = $ppix->get_publication_count(); $list = array(); for ($i=0; $i < $count; $i++) { $list[$i] = $ppix->get_publication_by_id($i); } $results = $list; } } else { if($flag_tag || $flag_search) { send_failure(1, "Indexed listings are not enabled on this server"); } $count = count($ppcl->publications); $list = array(); for ($i=0; $i < $count; $i++) { $list[$i] = $ppcl->publications[$i]->name; } $results = $list; } $publications = array(); foreach($results as $result) { foreach($ppcl->publications as $pub) { if($pub->name == $result && (!$flag_since || $pub->timestamp > $since)) { array_push($publications, $pub); } } } $publications = array_slice($publications, $data["skip"], $data["take"]); $res_cols = 0; if($col_title) { $res_cols |= (1 << 0); } if($col_author) { $res_cols |= (1 << 1); } if($col_description) { $res_cols |= (1 << 2); } if($col_timestamp) { $res_cols |= (1 << 3); } if($col_tags) { $res_cols |= (1 << 4); } if($col_poster) { $res_cols |= (1 << 5); } if($col_copyright) { $res_cols |= (1 << 6); } if($col_checksum) { $res_cols |= (1 << 7); } $message = pack("vC", $res_cols, count($publications)); include_once("ppub.php"); foreach($publications as $pub) { $message .= pack("v", strlen($pub->name)); $message .= $pub->name; $ppub = new Ppub(); $ppub->read_file(PUBLICATION_DIR . "/".$pub->name); if($col_title) { if(!isset($ppub->metadata["title"])) { $message .= "\x00\x00"; } else { $message .= pack("v", strlen($ppub->metadata["title"])); $message .= $ppub->metadata["title"]; } } if($col_author) { if(!isset($ppub->metadata["author"])) { $message .= "\x00\x00"; } else { $message .= pack("v", strlen($ppub->metadata["author"])); $message .= $ppub->metadata["author"]; } } if($col_description) { if(!isset($ppub->metadata["description"])) { $message .= "\x00\x00"; } else { $message .= pack("v", strlen($ppub->metadata["description"])); $message .= $ppub->metadata["description"]; } } if($col_timestamp) { if(!isset($ppub->metadata["date"])) { $message .= "\x00\x00"; } else { $message .= pack("C", strlen($ppub->metadata["date"])); $message .= $ppub->metadata["date"]; } } if($col_tags) { if(!isset($ppub->metadata["tags"])) { $message .= "\x00\x00"; } else { $message .= pack("v", strlen($ppub->metadata["tags"])); $message .= $ppub->metadata["tags"]; } } if($col_poster) { if(!isset($ppub->metadata["poster"])) { $message .= "\x00\x00"; } else { $message .= pack("v", strlen($ppub->metadata["poster"])); $message .= $ppub->metadata["poster"]; } } if($col_copyright) { if(!isset($ppub->metadata["copyright"])) { $message .= "\x00\x00"; } else { $message .= pack("v", strlen($ppub->metadata["copyright"])); $message .= $ppub->metadata["copyright"]; } } if($col_checksum) { $message .= $pub->checksum; } } send_message(130, $message); } // Get asset if($message_info["type"] == 3) { include_once("ppub.php"); $ppcl = get_ppcl(); verify_collection_message($handle, $ppcl); $flags = unpack("v", fread($handle, 2))[1]; $name_len = unpack("C", fread($handle, 1))[1]; $name = fread($handle, $name_len); $asset_len = unpack("v", fread($handle, 2))[1]; $asset_name = ""; if($asset_len > 0) { $asset_name = fread($handle, $asset_len); } $skip = unpack("P", fread($handle, 8))[1]; $take = unpack("P", fread($handle, 8))[1]; error_log("Get asset " . $asset_name . " from publication " . $name); $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"); } $ppub = new Ppub(); $ppub->read_file(PUBLICATION_DIR . "/" . $name); $asset = null; if($asset_name == "") { $asset = $ppub->asset_list[1]; } else { $asset = $ppub->asset_index[$asset_name]; } if($asset == null) { send_failure(17, "Asset \"" . $asset_name . "\" not found"); } // Reserved flags $message = "\x00\x00"; $message .= pack("C", strlen($asset->mimetype)); $message .= $asset->mimetype; if($skip == 0 && $take == 0) { $data = $ppub->read_asset($asset); $message .= pack("P", strlen($data)); $message .= $data; send_message(132, $message); } else if(!$ppub->can_stream_asset($asset)) { send_failure(18, "Asset is not streamable"); } $file_size = $ppub->get_asset_size($asset); if($skip > $file_size) { $message .= pack("P", 0); send_message(132, $message); } if($take + $skip > $file_size) { $take = $file_size - $skip; } $message .= pack("P", strlen($take)); send_partial_message(132, $message); $ppub->stream_asset($asset, $skip, $take); exit(); } // Get publication if($message_info["type"] == 4) { $ppcl = get_ppcl(); verify_collection_message($handle, $ppcl); $flags = unpack("v", fread($handle, 2))[1]; $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"); } $path = PUBLICATION_DIR . "/" . $name; $size = filesize($path); $ppub = fopen($path, 'rb'); $message = "\x00\x00"; $message .= pack("P", $size); send_partial_message(133, $message); $pos = 0; while($pos < $size) { $chunksize = min(1024 * 1024, $size - $pos); echo(fread($ppub, $chunksize)); flush(); $pos += $chunksize; } exit(); } // 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()); } } // Rebuild index message if($message_info["type"] == 39) { $ppcl = get_ppcl(); verify_collection_message($handle, $ppcl); $auth = read_authenticated_message($handle, $ppcl); $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 = "PPIX\xFF" . $ppcl->current_state_token; if($expected_auth != $signature) { send_failure(5, "Invalid authorisation token"); } try { include_once("ppix-gen.php"); generate_ppix_from_ppcl(); } catch(Exception $e) { send_failure(12, $e->getMessage()); } send_confirmation(); } // // 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"); ?>