Porównaj commity
7 Commity
37368caf72
...
7f2cd763d0
Autor | SHA1 | Data |
---|---|---|
Terence Eden | 7f2cd763d0 | |
Terence Eden | d829c201f2 | |
Terence Eden | d88addacb0 | |
Terence Eden | 3045406f76 | |
Terence Eden | 864315d5f7 | |
Terence Eden | 325f43fc70 | |
Terence Eden | ccdcca3dae |
316
index.php
316
index.php
|
@ -247,97 +247,120 @@
|
|||
// Get the message, type, and ID
|
||||
$inbox_message = $body;
|
||||
$inbox_type = $inbox_message["type"];
|
||||
$inbox_id = $inbox_message["id"];
|
||||
|
||||
// If this is an Undo, Delete, or Update message, try to process it
|
||||
if ( "Undo" == $inbox_type || "Delete" == $inbox_type || "Update" == $inbox_type ) {
|
||||
undo( $inbox_message );
|
||||
die();
|
||||
}
|
||||
|
||||
// Messages to ignore.
|
||||
// Some servers are very chatty. They send lots of irrelevant messages.
|
||||
// Before even bothering to validate them, we can delete them.
|
||||
|
||||
// Messages from accounts which aren't being followed.
|
||||
// Some servers send delete messages about users we don't follow.
|
||||
// Lemmy sends messages even after unfollowing or blocking a channel
|
||||
|
||||
// Get a list of every account we follow
|
||||
// Get all the files
|
||||
$following_files = glob( $directories["following"] . "/*.json");
|
||||
|
||||
// Create a list of all accounts being followed
|
||||
$following_ids = array();
|
||||
foreach ( $following_files as $following_file ) {
|
||||
$following = json_decode( file_get_contents( $following_file ), true );
|
||||
$following_ids[] = $following["id"];
|
||||
}
|
||||
|
||||
// Is this from someone we follow?
|
||||
in_array( $inbox_message["actor"], $following_ids ) ? $from_following = true: $from_following = false;
|
||||
|
||||
// Has the user has been specifically CC'd?
|
||||
if ( isset( $inbox_message["cc"] ) ) {
|
||||
$reply = in_array( "https://{$server}/{$username}", $inbox_message["cc"] );
|
||||
} else {
|
||||
$reply = false;
|
||||
}
|
||||
|
||||
if ( !$reply && !$from_following ) {
|
||||
// Don't bother processing it at all.
|
||||
die();
|
||||
}
|
||||
|
||||
// Validate HTTP Message Signature
|
||||
if ( !verifyHTTPSignature() ) { die(); }
|
||||
|
||||
// If the message is valid, save the message in `/data/inbox/`
|
||||
$uuid = uuid( $inbox_message );
|
||||
$inbox_filename = $uuid . "." . urlencode( $inbox_type ) . ".json";
|
||||
file_put_contents( $directories["inbox"] . "/{$inbox_filename}", json_encode( $inbox_message ) );
|
||||
|
||||
// This inbox only sends responses to follow requests.
|
||||
// A remote server sends the inbox a follow request which is a JSON file saying who they are.
|
||||
// The details of the remote user's server is saved to a file so that future messages can be delivered to the follower.
|
||||
// An accept request is cryptographically signed and POST'd back to the remote server.
|
||||
if ( "Follow" != $inbox_type ) { die(); }
|
||||
if ( "Follow" == $inbox_type ) {
|
||||
// Validate HTTP Message Signature
|
||||
if ( !verifyHTTPSignature() ) { die(); }
|
||||
|
||||
// Get the parameters
|
||||
$follower_id = $inbox_message["id"]; // E.g. https://mastodon.social/(unique id)
|
||||
$follower_actor = $inbox_message["actor"]; // E.g. https://mastodon.social/users/Edent
|
||||
|
||||
// Get the actor's profile as JSON
|
||||
$follower_actor_details = getDataFromURl( $follower_actor );
|
||||
// Get the parameters
|
||||
$follower_id = $inbox_message["id"]; // E.g. https://mastodon.social/(unique id)
|
||||
$follower_actor = $inbox_message["actor"]; // E.g. https://mastodon.social/users/Edent
|
||||
|
||||
// Get the actor's profile as JSON
|
||||
$follower_actor_details = getDataFromURl( $follower_actor );
|
||||
|
||||
// Save the actor's data in `/data/followers/`
|
||||
$follower_filename = urlencode( $follower_actor );
|
||||
file_put_contents( $directories["followers"] . "/{$follower_filename}.json", json_encode( $follower_actor_details ) );
|
||||
|
||||
// Get the new follower's Inbox
|
||||
$follower_inbox = $follower_actor_details["inbox"];
|
||||
// Save the actor's data in `/data/followers/`
|
||||
$follower_filename = urlencode( $follower_actor );
|
||||
file_put_contents( $directories["followers"] . "/{$follower_filename}.json", json_encode( $follower_actor_details ) );
|
||||
|
||||
// Get the new follower's Inbox
|
||||
$follower_inbox = $follower_actor_details["inbox"];
|
||||
|
||||
// Response Message ID
|
||||
// This isn't used for anything important so could just be a random number
|
||||
$guid = uuid();
|
||||
// Response Message ID
|
||||
// This isn't used for anything important so could just be a random number
|
||||
$guid = uuid();
|
||||
|
||||
// Create the Accept message to the new follower
|
||||
$message = [
|
||||
"@context" => "https://www.w3.org/ns/activitystreams",
|
||||
"id" => "https://{$server}/{$guid}",
|
||||
"type" => "Accept",
|
||||
"actor" => "https://{$server}/{$username}",
|
||||
"object" => [
|
||||
// Create the Accept message to the new follower
|
||||
$message = [
|
||||
"@context" => "https://www.w3.org/ns/activitystreams",
|
||||
"id" => $follower_id,
|
||||
"type" => $inbox_type,
|
||||
"actor" => $follower_actor,
|
||||
"object" => "https://{$server}/{$username}",
|
||||
]
|
||||
];
|
||||
"id" => "https://{$server}/{$guid}",
|
||||
"type" => "Accept",
|
||||
"actor" => "https://{$server}/{$username}",
|
||||
"object" => [
|
||||
"@context" => "https://www.w3.org/ns/activitystreams",
|
||||
"id" => $follower_id,
|
||||
"type" => $inbox_type,
|
||||
"actor" => $follower_actor,
|
||||
"object" => "https://{$server}/{$username}",
|
||||
]
|
||||
];
|
||||
|
||||
// The Accept is POSTed to the inbox on the server of the user who requested the follow
|
||||
sendMessageToSingle( $follower_inbox, $message );
|
||||
} else {
|
||||
// Messages to ignore.
|
||||
// Some servers are very chatty. They send lots of irrelevant messages.
|
||||
// Before even bothering to validate them, we can delete them.
|
||||
|
||||
// This server doesn't handle Add, Remove, or Reject
|
||||
// See https://www.w3.org/wiki/ActivityPub/Primer
|
||||
if ( "Add" == $inbox_type || "Remove" == $inbox_type || "Reject" == $inbox_type ) {
|
||||
die();
|
||||
}
|
||||
|
||||
// Messages from accounts which aren't being followed.
|
||||
// Some servers send delete messages about users we don't follow.
|
||||
// Lemmy sends messages even after unfollowing or blocking a channel
|
||||
|
||||
// Get a list of every account we follow
|
||||
// Get all the files
|
||||
$following_files = glob( $directories["following"] . "/*.json");
|
||||
|
||||
// Create a list of all accounts being followed
|
||||
$following_ids = array();
|
||||
foreach ( $following_files as $following_file ) {
|
||||
$following = json_decode( file_get_contents( $following_file ), true );
|
||||
$following_ids[] = $following["id"];
|
||||
}
|
||||
|
||||
// Is this from someone we follow?
|
||||
in_array( $inbox_message["actor"], $following_ids ) ? $from_following = true: $from_following = false;
|
||||
|
||||
// Get a list of every account following us
|
||||
// Get all the files
|
||||
$followers_files = glob( $directories["followers"] . "/*.json");
|
||||
|
||||
// Create a list of all accounts being followed
|
||||
$followers_ids = array();
|
||||
foreach ( $followers_files as $follower_file ) {
|
||||
$follower = json_decode( file_get_contents( $follower_file ), true );
|
||||
$followers_ids[] = $follower["id"];
|
||||
}
|
||||
|
||||
// Is this from someone following us?
|
||||
in_array( $inbox_message["actor"], $followers_ids ) ? $from_follower = true: $from_follower = false;
|
||||
|
||||
// Has the user has been specifically CC'd?
|
||||
if ( isset( $inbox_message["cc"] ) ) {
|
||||
$reply = in_array( "https://{$server}/{$username}", $inbox_message["cc"] );
|
||||
} else {
|
||||
$reply = false;
|
||||
}
|
||||
|
||||
// As long as one of these is true, the server will process it
|
||||
if ( !$reply && !$from_following && !$from_follower ) {
|
||||
// Don't bother processing it at all.
|
||||
die();
|
||||
}
|
||||
|
||||
// Validate HTTP Message Signature
|
||||
if ( !verifyHTTPSignature() ) { die(); }
|
||||
|
||||
// If this is an Undo, Delete, or Update message, try to process it
|
||||
if ( "Undo" == $inbox_type || "Delete" == $inbox_type || "Update" == $inbox_type ) {
|
||||
undo( $inbox_message );
|
||||
}
|
||||
}
|
||||
|
||||
// If the message is valid, save the message in `/data/inbox/`
|
||||
$uuid = uuid( $inbox_message );
|
||||
$inbox_filename = $uuid . "." . urlencode( $inbox_type ) . ".json";
|
||||
file_put_contents( $directories["inbox"] . "/{$inbox_filename}", json_encode( $inbox_message ) );
|
||||
|
||||
// The Accept is POSTed to the inbox on the server of the user who requested the follow
|
||||
sendMessageToSingle( $follower_inbox, $message );
|
||||
die();
|
||||
}
|
||||
|
||||
|
@ -768,8 +791,10 @@ die();
|
|||
}
|
||||
|
||||
// User Interface for Writing:
|
||||
// This creates a basic HTML form. Type in your message and your password. It then POSTs the data to the `/send` endpoint.
|
||||
// This creates a basic HTML form. Type in your message and your password. It then POSTs the data to the `/action/send` endpoint.
|
||||
function write() {
|
||||
$send = "/action/send";
|
||||
|
||||
if ( isset( $_GET["announce"] ) && filter_var( $_GET["announce"], FILTER_VALIDATE_URL ) ) {
|
||||
$announceURl = $_GET["announce"];
|
||||
} else {
|
||||
|
@ -800,7 +825,7 @@ echo <<< HTML
|
|||
<body>
|
||||
<fieldset>
|
||||
<legend>Send a message</legend>
|
||||
<form action="/action/send" method="post" enctype="multipart/form-data">
|
||||
<form action="{$send}" method="post" enctype="multipart/form-data">
|
||||
<input type="hidden" id="type" name="type" value="Create">
|
||||
<label for="content">Your message:</label><br>
|
||||
<textarea id="content" name="content" rows="5" cols="32"></textarea><br>
|
||||
|
@ -817,7 +842,7 @@ echo <<< HTML
|
|||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>Like a post</legend>
|
||||
<form action="/send" method="post" enctype="multipart/form-data">
|
||||
<form action="{$send}" method="post" enctype="multipart/form-data">
|
||||
<input type="hidden" id="type" name="type" value="Like">
|
||||
<label for="postURl">URl of post to like:</label>
|
||||
<input type="url" name="postURl" id="postURl" size="32" value="{$likeURl}"><br>
|
||||
|
@ -828,7 +853,7 @@ echo <<< HTML
|
|||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>Boost a post</legend>
|
||||
<form action="/send" method="post" enctype="multipart/form-data">
|
||||
<form action="{$send}" method="post" enctype="multipart/form-data">
|
||||
<input type="hidden" id="type" name="type" value="Announce">
|
||||
<label for="postURl">URl of post to boost:</label>
|
||||
<input type="url" name="postURl" id="postURl" size="32" value="{$announceURl}"><br>
|
||||
|
@ -852,7 +877,7 @@ HTML;
|
|||
global $password, $server, $username, $key_private, $directories;
|
||||
|
||||
// Does the posted password match the stored password?
|
||||
if( $password != $_POST["password"] ) { die(); }
|
||||
if( $password != $_POST["password"] ) { echo "Wrong password."; die(); }
|
||||
|
||||
// What sort of message is being sent?
|
||||
$type = $_POST["type"];
|
||||
|
@ -1459,23 +1484,60 @@ HTML;
|
|||
// Ensure the header keys match the format expected by the signature
|
||||
$headers = array_change_key_case( $headers, CASE_LOWER );
|
||||
|
||||
// Validate the timestamp is within ±30 seconds
|
||||
if ( !isset( $headers["date"] ) ) { return null; } // No date set
|
||||
// Validate the timestamp
|
||||
// 7.2.4 of https://datatracker.ietf.org/doc/rfc9421/
|
||||
if ( !isset( $headers["date"] ) ) {
|
||||
// No date set
|
||||
// Filename for the log
|
||||
$filename = "{$timestamp}.{$type}.Signature.Date_Failure.txt";
|
||||
|
||||
// Save headers and request data to the timestamped file in the logs directory
|
||||
file_put_contents( $directories["logs"] . "/{$filename}",
|
||||
"Original Body:\n" . print_r( $body, true ) . "\n\n" .
|
||||
"Original Headers:\n" . print_r( $headers, true ) . "\n\n"
|
||||
);
|
||||
return null;
|
||||
}
|
||||
$dateHeader = $headers["date"];
|
||||
$headerDatetime = DateTime::createFromFormat('D, d M Y H:i:s T', $dateHeader);
|
||||
$currentDatetime = new DateTime();
|
||||
|
||||
// First, check if the message was sent no more than ± 1 hour
|
||||
// https://github.com/mastodon/mastodon/blob/82c2af0356ff888e9665b5b08fda58c7722be637/app/controllers/concerns/signature_verification.rb#L11
|
||||
// Calculate the time difference in seconds
|
||||
$timeDifference = abs( $currentDatetime->getTimestamp() - $headerDatetime->getTimestamp() );
|
||||
if ( $timeDifference > 30 ) {
|
||||
if ( $timeDifference > 3600 ) {
|
||||
// Write a log detailing the error
|
||||
// Filename for the log
|
||||
$filename = "{$timestamp}.{$type}.Signature.Delay_Failure.txt";
|
||||
|
||||
// Save headers and request data to the timestamped file in the logs directory
|
||||
file_put_contents( $directories["logs"] . "/{$filename}",
|
||||
"Header Date:\n" . print_r( $dateHeader, true ) . "\n" .
|
||||
"Server Date:\n" . print_r( $currentDatetime->format('D, d M Y H:i:s T'), true ) ."\n" .
|
||||
"Original Body:\n" . print_r( $body, true ) . "\n\n" .
|
||||
"Original Headers:\n" . print_r( $headers, true ) . "\n\n"
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Is there a significant difference between the Date header and the published timestamp?
|
||||
// Two minutes chosen because Friendica is frequently more than a minute skewed
|
||||
$published = $body["published"];
|
||||
$publishedDatetime = new DateTime($published);
|
||||
// Calculate the time difference in seconds
|
||||
$timeDifference = abs( $publishedDatetime->getTimestamp() - $headerDatetime->getTimestamp() );
|
||||
if ( $timeDifference > 120 ) {
|
||||
// Write a log detailing the error
|
||||
// Filename for the log
|
||||
$filename = "{$timestamp}.{$type}.Signature.Time_Failure.txt";
|
||||
|
||||
// Save headers and request data to the timestamped file in the logs directory
|
||||
file_put_contents( $directories["logs"] . "/{$filename}",
|
||||
"Original Date:\n" . print_r( $dateHeader, true ) . "\n" .
|
||||
"Local Date:\n" . print_r( $currentDatetime->format('D, d M Y H:i:s T'), true ) . "\n"
|
||||
"Header Date:\n" . print_r( $dateHeader, true ) . "\n" .
|
||||
"Published Date:\n" . print_r( $publishedDatetime->format('D, d M Y H:i:s T'), true ) ."\n" .
|
||||
"Original Body:\n" . print_r( $body, true ) . "\n\n" .
|
||||
"Original Headers:\n" . print_r( $headers, true ) . "\n\n"
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
@ -1563,38 +1625,50 @@ HTML;
|
|||
$actorPublicKey = $actorData["publicKey"]["publicKeyPem"];
|
||||
|
||||
if ( $publicKey != $actorPublicKey ) {
|
||||
$verified = false;
|
||||
} else {
|
||||
// Get the remaining parts
|
||||
$signature = base64_decode( $signatureParts["signature"] );
|
||||
$algorithm = $signatureParts["algorithm"];
|
||||
// Filename for the log
|
||||
$filename = "{$timestamp}.{$type}.Signature.Mismatch_Failure.txt";
|
||||
|
||||
// There might be many different signing algorithms
|
||||
// TODO: Find a way to transform these automatically
|
||||
// See https://github.com/superseriousbusiness/gotosocial/issues/1186#issuecomment-1976166659 and https://github.com/snarfed/bridgy-fed/issues/430 for hs2019
|
||||
if ( "hs2019" == $algorithm ) {
|
||||
$algorithm = "sha256";
|
||||
}
|
||||
|
||||
// Finally! Calculate whether the signature is valid
|
||||
// Returns 1 if verified, 0 if not, false or -1 if an error occurred
|
||||
$verified = openssl_verify(
|
||||
$signatureString,
|
||||
$signature,
|
||||
$publicKey,
|
||||
$algorithm
|
||||
// Save headers and request data to the timestamped file in the logs directory
|
||||
file_put_contents( $directories["logs"] . "/{$filename}",
|
||||
"Original Body:\n" . print_r( $body, true ) . "\n\n" .
|
||||
"Original Headers:\n" . print_r( $headers, true ) . "\n\n" .
|
||||
"Signature Headers:\n" . print_r( $signatureHeaders, true ) . "\n\n" .
|
||||
"publicKeyURL:\n" . print_r( $publicKeyURL, true ) . "\n\n" .
|
||||
"publicKey:\n" . print_r( $publicKey, true ) . "\n\n" .
|
||||
"actorPublicKey:\n" . print_r( $actorPublicKey, true ) . "\n"
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the remaining parts
|
||||
$signature = base64_decode( $signatureParts["signature"] );
|
||||
$algorithm = $signatureParts["algorithm"];
|
||||
|
||||
// Convert to boolean
|
||||
if ( $verified === 1 ) {
|
||||
$verified = true;
|
||||
} elseif ( $verified === 0 ) {
|
||||
$verified = false;
|
||||
} else {
|
||||
$verified = null;
|
||||
}
|
||||
// There might be many different signing algorithms
|
||||
// TODO: Find a way to transform these automatically
|
||||
// See https://github.com/superseriousbusiness/gotosocial/issues/1186#issuecomment-1976166659 and https://github.com/snarfed/bridgy-fed/issues/430 for hs2019
|
||||
if ( "hs2019" == $algorithm ) {
|
||||
$algorithm = "sha256";
|
||||
}
|
||||
|
||||
// Finally! Calculate whether the signature is valid
|
||||
// Returns 1 if verified, 0 if not, false or -1 if an error occurred
|
||||
$verified = openssl_verify(
|
||||
$signatureString,
|
||||
$signature,
|
||||
$publicKey,
|
||||
$algorithm
|
||||
);
|
||||
|
||||
// Convert to boolean
|
||||
if ( $verified === 1 ) {
|
||||
$verified = true;
|
||||
} elseif ( $verified === 0 ) {
|
||||
$verified = false;
|
||||
} else {
|
||||
$verified = null;
|
||||
}
|
||||
|
||||
// Filename for the log
|
||||
$filename = "{$timestamp}.{$type}.Signature.". json_encode( $verified ) . ".txt";
|
||||
|
||||
|
@ -1677,9 +1751,6 @@ HTML;
|
|||
// Perform the Undo action requested
|
||||
function undo( $message ) {
|
||||
global $server, $directories;
|
||||
|
||||
// Validate HTTP Message Signature
|
||||
if ( !verifyHTTPSignature() ) { die(); }
|
||||
|
||||
// Get some basic data
|
||||
$type = $message["type"];
|
||||
|
@ -1734,11 +1805,6 @@ HTML;
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If the message is valid, save the message in `/data/inbox/`
|
||||
$uuid = uuid( $message );
|
||||
$filename = $uuid . "." . urlencode( $type ) . ".json";
|
||||
file_put_contents( $directories["inbox"] . "/{$filename}", json_encode( $message ) );
|
||||
}
|
||||
|
||||
// "One to stun, two to kill, three to make sure"
|
||||
|
|
Ładowanie…
Reference in New Issue