]> git.mxchange.org Git - friendica.git/blobdiff - include/diaspora2.php
Retraction and reshares work
[friendica.git] / include / diaspora2.php
index da772d68bd67f0451c19495a220c25b1c9a733a9..f6b8b9a704165e976f9d05a8a97c22ca06ec0f0c 100644 (file)
@@ -64,6 +64,168 @@ class diaspora {
                return $relay;
        }
 
+       function repair_signature($signature, $handle = "", $level = 1) {
+
+               if ($signature == "")
+                       return ($signature);
+
+               if (base64_encode(base64_decode(base64_decode($signature))) == base64_decode($signature)) {
+                       $signature = base64_decode($signature);
+                       logger("Repaired double encoded signature from Diaspora/Hubzilla handle ".$handle." - level ".$level, LOGGER_DEBUG);
+
+                       // Do a recursive call to be able to fix even multiple levels
+                       if ($level < 10)
+                               $signature = self::repair_signature($signature, $handle, ++$level);
+               }
+
+               return($signature);
+       }
+
+       /**
+        * @brief: Decodes incoming Diaspora message
+        *
+        * @param array $importer from user table
+        * @param string $xml urldecoded Diaspora salmon
+        *
+        * @return array
+        * 'message' -> decoded Diaspora XML message
+        * 'author' -> author diaspora handle
+        * 'key' -> author public key (converted to pkcs#8)
+        */
+       function decode($importer, $xml) {
+
+               $public = false;
+               $basedom = parse_xml_string($xml);
+
+               if (!is_object($basedom))
+                       return false;
+
+               $children = $basedom->children('https://joindiaspora.com/protocol');
+
+               if($children->header) {
+                       $public = true;
+                       $author_link = str_replace('acct:','',$children->header->author_id);
+               } else {
+
+                       $encrypted_header = json_decode(base64_decode($children->encrypted_header));
+
+                       $encrypted_aes_key_bundle = base64_decode($encrypted_header->aes_key);
+                       $ciphertext = base64_decode($encrypted_header->ciphertext);
+
+                       $outer_key_bundle = '';
+                       openssl_private_decrypt($encrypted_aes_key_bundle,$outer_key_bundle,$importer['prvkey']);
+
+                       $j_outer_key_bundle = json_decode($outer_key_bundle);
+
+                       $outer_iv = base64_decode($j_outer_key_bundle->iv);
+                       $outer_key = base64_decode($j_outer_key_bundle->key);
+
+                       $decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $outer_key, $ciphertext, MCRYPT_MODE_CBC, $outer_iv);
+
+
+                       $decrypted = pkcs5_unpad($decrypted);
+
+                       /**
+                        * $decrypted now contains something like
+                        *
+                        *  <decrypted_header>
+                        *     <iv>8e+G2+ET8l5BPuW0sVTnQw==</iv>
+                        *     <aes_key>UvSMb4puPeB14STkcDWq+4QE302Edu15oaprAQSkLKU=</aes_key>
+                        *     <author_id>galaxor@diaspora.priateship.org</author_id>
+                        *  </decrypted_header>
+                        */
+
+                       logger('decrypted: '.$decrypted, LOGGER_DEBUG);
+                       $idom = parse_xml_string($decrypted,false);
+
+                       $inner_iv = base64_decode($idom->iv);
+                       $inner_aes_key = base64_decode($idom->aes_key);
+
+                       $author_link = str_replace('acct:','',$idom->author_id);
+               }
+
+               $dom = $basedom->children(NAMESPACE_SALMON_ME);
+
+               // figure out where in the DOM tree our data is hiding
+
+               if($dom->provenance->data)
+                       $base = $dom->provenance;
+               elseif($dom->env->data)
+                       $base = $dom->env;
+               elseif($dom->data)
+                       $base = $dom;
+
+               if (!$base) {
+                       logger('unable to locate salmon data in xml');
+                       http_status_exit(400);
+               }
+
+
+               // Stash the signature away for now. We have to find their key or it won't be good for anything.
+               $signature = base64url_decode($base->sig);
+
+               // unpack the  data
+
+               // strip whitespace so our data element will return to one big base64 blob
+               $data = str_replace(array(" ","\t","\r","\n"),array("","","",""),$base->data);
+
+
+               // stash away some other stuff for later
+
+               $type = $base->data[0]->attributes()->type[0];
+               $keyhash = $base->sig[0]->attributes()->keyhash[0];
+               $encoding = $base->encoding;
+               $alg = $base->alg;
+
+
+               $signed_data = $data.'.'.base64url_encode($type).'.'.base64url_encode($encoding).'.'.base64url_encode($alg);
+
+
+               // decode the data
+               $data = base64url_decode($data);
+
+
+               if($public)
+                       $inner_decrypted = $data;
+               else {
+
+                       // Decode the encrypted blob
+
+                       $inner_encrypted = base64_decode($data);
+                       $inner_decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $inner_aes_key, $inner_encrypted, MCRYPT_MODE_CBC, $inner_iv);
+                       $inner_decrypted = pkcs5_unpad($inner_decrypted);
+               }
+
+               if (!$author_link) {
+                       logger('Could not retrieve author URI.');
+                       http_status_exit(400);
+               }
+               // Once we have the author URI, go to the web and try to find their public key
+               // (first this will look it up locally if it is in the fcontact cache)
+               // This will also convert diaspora public key from pkcs#1 to pkcs#8
+
+               logger('Fetching key for '.$author_link);
+               $key = self::key($author_link);
+
+               if (!$key) {
+                       logger('Could not retrieve author key.');
+                       http_status_exit(400);
+               }
+
+               $verify = rsa_verify($signed_data,$signature,$key);
+
+               if (!$verify) {
+                       logger('Message did not verify. Discarding.');
+                       http_status_exit(400);
+               }
+
+               logger('Message verified.');
+
+               return array('message' => $inner_decrypted, 'author' => $author_link, 'key' => $key);
+
+       }
+
+
        /**
         * @brief Dispatches public messages and find the fitting receivers
         *
@@ -122,6 +284,8 @@ class diaspora {
 
                $type = $fields->getName();
 
+               logger("Received message type ".$type." from ".$sender." for user ".$importer["uid"], LOGGER_DEBUG);
+
                switch ($type) {
                        case "account_deletion":
                                return self::receive_account_deletion($importer, $fields);
@@ -492,7 +656,7 @@ class diaspora {
                        return false;
                }
 
-               if (!self::post_allow($importer, $contact, false)) {
+               if (!self::post_allow($importer, $contact, $is_comment)) {
                        logger("The handle: ".$handle." is not allowed to post to user ".$importer["uid"]);
                        return false;
                }
@@ -507,10 +671,10 @@ class diaspora {
 
                if($r) {
                        logger("message ".$guid." already exists for user ".$uid);
-                       return false;
+                       return true;
                }
 
-               return true;
+               return false;
        }
 
        private function fetch_guid($item) {
@@ -612,10 +776,12 @@ class diaspora {
                }
 
                if (!$r) {
-                       logger("parent item not found: parent: ".$guid." item: ".$guid);
+                       logger("parent item not found: parent: ".$guid." - user: ".$uid);
                        return false;
-               } else
+               } else {
+                       logger("parent item found: parent: ".$guid." - user: ".$uid);
                        return $r[0];
+               }
        }
 
        private function author_contact_by_url($contact, $person, $uid) {
@@ -730,6 +896,9 @@ class diaspora {
 
                $message_id = item_store($datarray);
 
+               if ($message_id)
+                       logger("Stored comment ".$datarray["guid"]." with message id ".$message_id, LOGGER_DEBUG);
+
                // If we are the origin of the parent we store the original data and notify our followers
                if($message_id AND $parent_item["origin"]) {
 
@@ -1013,6 +1182,9 @@ class diaspora {
 
                $message_id = item_store($datarray);
 
+               if ($message_id)
+                       logger("Stored like ".$datarray["guid"]." with message id ".$message_id, LOGGER_DEBUG);
+
                // If we are the origin of the parent we store the original data and notify our followers
                if($message_id AND $parent_item["origin"]) {
 
@@ -1196,6 +1368,8 @@ class diaspora {
 
                update_gcontact($gcontact);
 
+               logger("Profile of contact ".$contact["id"]." stored for user ".$importer["uid"], LOGGER_DEBUG);
+
                return true;
        }
 
@@ -1287,7 +1461,7 @@ class diaspora {
                $ret = self::person_by_handle($author);
 
                if (!$ret || ($ret["network"] != NETWORK_DIASPORA)) {
-                       logger("Cannot resolve diaspora handle ".$author ." for ".$recipient);
+                       logger("Cannot resolve diaspora handle ".$author." for ".$recipient);
                        return false;
                }
 
@@ -1454,6 +1628,8 @@ class diaspora {
                if (!$original_item)
                        return false;
 
+               $orig_url = App::get_baseurl()."/display/".$original_item["guid"];
+
                $datarray = array();
 
                $datarray["uid"] = $importer["uid"];
@@ -1477,7 +1653,7 @@ class diaspora {
                $datarray["object"] = json_encode($data);
 
                $prefix = share_header($original_item["author-name"], $original_item["author-link"], $original_item["author-avatar"],
-                                       $original_item["guid"], $original_item["created"], $original_item["uri"]);
+                                       $original_item["guid"], $original_item["created"], $orig_url);
                $datarray["body"] = $prefix.$original_item["body"]."[/share]";
 
                $datarray["tag"] = $original_item["tag"];
@@ -1492,6 +1668,9 @@ class diaspora {
                self::fetch_guid($datarray);
                $message_id = item_store($datarray);
 
+               if ($message_id)
+                       logger("Stored reshare ".$datarray["guid"]." with message id ".$message_id, LOGGER_DEBUG);
+
                return $message_id;
        }
 
@@ -1514,16 +1693,20 @@ class diaspora {
                        return false;
 
                // Only delete it if the author really fits
-               if (!link_compare($r[0]["author-link"],$person["url"]))
+               if (!link_compare($r[0]["author-link"], $person["url"])) {
+                       logger("Item author ".$r[0]["author-link"]." doesn't fit to expected contact ".$person["url"], LOGGER_DEBUG);
                        return false;
+               }
 
                // Check if the sender is the thread owner
                $p = q("SELECT `author-link`, `origin` FROM `item` WHERE `id` = %d",
                        intval($r[0]["parent"]));
 
                // Only delete it if the parent author really fits
-               if (!link_compare($p[0]["author-link"], $contact["url"]))
+               if (!link_compare($p[0]["author-link"], $contact["url"]) AND !link_compare($r[0]["author-link"], $contact["url"])) {
+                       logger("Thread author ".$p[0]["author-link"]." and item author ".$r[0]["author-link"]." don't fit to expected contact ".$contact["url"], LOGGER_DEBUG);
                        return false;
+               }
 
                // Currently we don't have a central deletion function that we could use in this case. The function "item_drop" doesn't work for that case
                q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = '' WHERE `id` = %d",
@@ -1533,6 +1716,8 @@ class diaspora {
                );
                delete_thread($r[0]["id"], $r[0]["parent-uri"]);
 
+               logger("Deleted target ".$target_guid." from user ".$importer["uid"], LOGGER_DEBUG);
+
                // Now check if the retraction needs to be relayed by us
                if($p[0]["origin"]) {
 
@@ -1557,6 +1742,8 @@ class diaspora {
                        return false;
                }
 
+               logger("Got retraction for ".$target_type.", sender ".$sender." and user ".$importer["uid"], LOGGER_DEBUG);
+
                switch ($target_type) {
                        case "Comment":
                        case "Like":
@@ -1660,7 +1847,8 @@ class diaspora {
                self::fetch_guid($datarray);
                $message_id = item_store($datarray);
 
-               logger("Stored item with message id ".$message_id, LOGGER_DEBUG);
+               if ($message_id)
+                       logger("Stored item ".$datarray["guid"]." with message id ".$message_id, LOGGER_DEBUG);
 
                return $message_id;
        }
@@ -1854,7 +2042,7 @@ class diaspora {
                                dbesc($slap),
                                intval($public_batch)
                        );
-                       if(count($r)) {
+                       if($r) {
                                logger("add_to_queue ignored - identical item already in queue");
                        } else {
                                // queue message for redelivery
@@ -2167,8 +2355,21 @@ class diaspora {
                /// @todo Change all signatur storing functions to the new format
                if ($signature['signed_text'] AND $signature['signature'] AND $signature['signer'])
                        $message = self::message_from_signatur($item, $signature);
-               else // New way
-                       $message = json_decode($signature['signed_text']);
+               else {// New way
+                       $msg = json_decode($signature['signed_text'], true);
+
+                       $message = array();
+                       foreach ($msg AS $field => $data) {
+                               if (!$item["deleted"]) {
+                                       if ($field == "author")
+                                               $field = "diaspora_handle";
+                                       if ($field == "parent_type")
+                                               $field = "target_type";
+                               }
+
+                               $message[$field] = $data;
+                       }
+               }
 
                if ($item["deleted"]) {
                        $signed_text = $message["target_guid"].';'.$message["target_type"];
@@ -2176,6 +2377,8 @@ class diaspora {
                } else
                        $message["parent_author_signature"] = self::signature($owner, $message);
 
+               logger("Relayed data ".print_r($message, true), LOGGER_DEBUG);
+
                return self::build_and_transmit($owner, $contact, $type, $message, $public_batch, $item["guid"]);
        }
 
@@ -2211,7 +2414,7 @@ class diaspora {
                        intval($item["uid"])
                );
 
-               if (!count($r)) {
+               if (!$r) {
                        logger("conversation not found.");
                        return;
                }