function diaspora_like($importer, $xml, $msg) { $a = get_app(); $guid = notags(unxmlify($xml->guid)); $parent_guid = notags(unxmlify($xml->parent_guid)); $diaspora_handle = notags(unxmlify($xml->diaspora_handle)); $target_type = notags(unxmlify($xml->target_type)); $positive = notags(unxmlify($xml->positive)); $author_signature = notags(unxmlify($xml->author_signature)); $parent_author_signature = $xml->parent_author_signature ? notags(unxmlify($xml->parent_author_signature)) : ''; // likes on comments not supported here and likes on photos not supported by Diaspora // if($target_type !== 'Post') // return; $contact = diaspora_get_contact_by_handle($importer['uid'], $msg['author']); if (!$contact) { logger('diaspora_like: cannot find contact: ' . $msg['author']); return; } if (!diaspora_post_allow($importer, $contact, false)) { logger('diaspora_like: Ignoring this author.'); return 202; } $r = q("SELECT * FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", intval($importer['uid']), dbesc($parent_guid)); if (!count($r)) { $result = diaspora_store_by_guid($parent_guid, $contact['url'], $importer['uid']); if (!$result) { $person = find_diaspora_person_by_handle($diaspora_handle); $result = diaspora_store_by_guid($parent_guid, $person['url'], $importer['uid']); } if ($result) { logger("Fetched missing item " . $parent_guid . " - result: " . $result, LOGGER_DEBUG); $r = q("SELECT * FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", intval($importer['uid']), dbesc($parent_guid)); } } if (!count($r)) { logger('diaspora_like: parent item not found: ' . $guid); return; } $parent_item = $r[0]; $r = q("SELECT * FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", intval($importer['uid']), dbesc($guid)); if (count($r)) { if ($positive === 'true') { logger('diaspora_like: duplicate like: ' . $guid); return; } // Note: I don't think "Like" objects with positive = "false" are ever actually used // It looks like "RelayableRetractions" are used for "unlike" instead if ($positive === 'false') { logger('diaspora_like: received a like with positive set to "false"...ignoring'); /* q("UPDATE `item` SET `deleted` = 1 WHERE `id` = %d AND `uid` = %d", intval($r[0]['id']), intval($importer['uid']) );*/ // FIXME--actually don't unless it turns out that Diaspora does indeed send out "false" likes // send notification via proc_run() return; } } // Note: I don't think "Like" objects with positive = "false" are ever actually used // It looks like "RelayableRetractions" are used for "unlike" instead if ($positive === 'false') { logger('diaspora_like: received a like with positive set to "false"'); logger('diaspora_like: unlike received with no corresponding like...ignoring'); return; } /* How Diaspora performs "like" signature checking: - If an item has been sent by the like author to the top-level post owner to relay on to the rest of the contacts on the top-level post, the top-level post owner should check the author_signature, then create a parent_author_signature before relaying the like on - If an item has been relayed on by the top-level post owner, the contacts who receive it check only the parent_author_signature. Basically, they trust that the top-level post owner has already verified the authenticity of anything he/she sends out - In either case, the signature that get checked is the signature created by the person who sent the salmon */ // Diaspora has changed the way they are signing the likes. // Just to make sure that we don't miss any likes we will check the old and the current way. $old_signed_data = $guid . ';' . $target_type . ';' . $parent_guid . ';' . $positive . ';' . $diaspora_handle; $signed_data = $positive . ';' . $guid . ';' . $target_type . ';' . $parent_guid . ';' . $diaspora_handle; $key = $msg['key']; if ($parent_author_signature) { // If a parent_author_signature exists, then we've received the like // relayed from the top-level post owner. There's no need to check the // author_signature if the parent_author_signature is valid $parent_author_signature = base64_decode($parent_author_signature); if (!rsa_verify($signed_data, $parent_author_signature, $key, 'sha256') and !rsa_verify($old_signed_data, $parent_author_signature, $key, 'sha256')) { logger('diaspora_like: top-level owner verification failed.'); return; } } else { // If there's no parent_author_signature, then we've received the like // from the like creator. In that case, the person is "like"ing // our post, so he/she must be a contact of ours and his/her public key // should be in $msg['key'] $author_signature = base64_decode($author_signature); if (!rsa_verify($signed_data, $author_signature, $key, 'sha256') and !rsa_verify($old_signed_data, $author_signature, $key, 'sha256')) { logger('diaspora_like: like creator verification failed.'); return; } } // Phew! Everything checks out. Now create an item. // Find the original comment author information. // We need this to make sure we display the comment author // information (name and avatar) correctly. if (strcasecmp($diaspora_handle, $msg['author']) == 0) { $person = $contact; } else { $person = find_diaspora_person_by_handle($diaspora_handle); if (!is_array($person)) { logger('diaspora_like: unable to find author details'); return; } } $uri = $diaspora_handle . ':' . $guid; $activity = ACTIVITY_LIKE; $post_type = $parent_item['resource-id'] ? t('photo') : t('status'); $objtype = $parent_item['resource-id'] ? ACTIVITY_OBJ_PHOTO : ACTIVITY_OBJ_NOTE; $link = xmlify('<link rel="alternate" type="text/html" href="' . $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $parent_item['id'] . '" />' . "\n"); $body = $parent_item['body']; $obj = <<<EOT \t<object> \t\t<type>{$objtype}</type> \t\t<local>1</local> \t\t<id>{$parent_item['uri']}</id> \t\t<link>{$link}</link> \t\t<title></title> \t\t<content>{$body}</content> \t</object> EOT; $bodyverb = t('%1$s likes %2$s\'s %3$s'); $arr = array(); $arr['uri'] = $uri; $arr['uid'] = $importer['uid']; $arr['guid'] = $guid; $arr['network'] = NETWORK_DIASPORA; $arr['contact-id'] = $contact['id']; $arr['type'] = 'activity'; $arr['wall'] = $parent_item['wall']; $arr['gravity'] = GRAVITY_LIKE; $arr['parent'] = $parent_item['id']; $arr['parent-uri'] = $parent_item['uri']; $arr['owner-name'] = $parent_item['name']; $arr['owner-link'] = $parent_item['url']; //$arr['owner-avatar'] = $parent_item['thumb']; $arr['owner-avatar'] = x($parent_item, 'thumb') ? $parent_item['thumb'] : $parent_item['photo']; $arr['author-name'] = $person['name']; $arr['author-link'] = $person['url']; $arr['author-avatar'] = x($person, 'thumb') ? $person['thumb'] : $person['photo']; $ulink = '[url=' . $contact['url'] . ']' . $contact['name'] . '[/url]'; $alink = '[url=' . $parent_item['author-link'] . ']' . $parent_item['author-name'] . '[/url]'; //$plink = '[url=' . $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $parent_item['id'] . ']' . $post_type . '[/url]'; $plink = '[url=' . $a->get_baseurl() . '/display/' . urlencode($guid) . ']' . $post_type . '[/url]'; $arr['body'] = sprintf($bodyverb, $ulink, $alink, $plink); $arr['app'] = 'Diaspora'; $arr['private'] = $parent_item['private']; $arr['verb'] = $activity; $arr['object-type'] = $objtype; $arr['object'] = $obj; $arr['visible'] = 1; $arr['unseen'] = 1; $arr['last-child'] = 0; $message_id = item_store($arr); //if($message_id) { // q("update item set plink = '%s' where id = %d", // //dbesc($a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $message_id), // dbesc($a->get_baseurl().'/display/'.$guid), // intval($message_id) // ); //} if (!$parent_author_signature) { q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ", intval($message_id), dbesc($signed_data), dbesc(base64_encode($author_signature)), dbesc($diaspora_handle)); } // if the message isn't already being relayed, notify others // the existence of parent_author_signature means the parent_author or owner // is already relaying. The parent_item['origin'] indicates the message was created on our system if ($parent_item['origin'] && !$parent_author_signature) { proc_run('php', 'include/notifier.php', 'comment-import', $message_id); } return; }
function diaspora_like($importer, $xml, $msg) { $a = get_app(); $guid = notags(unxmlify($xml->guid)); $parent_guid = notags(unxmlify($xml->parent_guid)); $diaspora_handle = notags(unxmlify($xml->diaspora_handle)); $target_type = notags(unxmlify($xml->target_type)); $positive = notags(unxmlify($xml->positive)); $author_signature = notags(unxmlify($xml->author_signature)); $parent_author_signature = $xml->parent_author_signature ? notags(unxmlify($xml->parent_author_signature)) : ''; // likes on comments not supported here and likes on photos not supported by Diaspora // if($target_type !== 'Post') // return; $contact = diaspora_get_contact_by_handle($importer['channel_id'], $msg['author']); if (!$contact) { logger('diaspora_like: cannot find contact: ' . $msg['author']); return; } if (!perm_is_allowed($importer['channel_id'], $contact['xchan_hash'], 'post_comments')) { logger('diaspora_like: Ignoring this author.'); return 202; } $r = q("SELECT * FROM `item` WHERE `uid` = %d AND `mid` = '%s' LIMIT 1", intval($importer['channel_id']), dbesc($parent_guid)); if (!count($r)) { logger('diaspora_like: parent item not found: ' . $guid); return; } $parent_item = $r[0]; $r = q("SELECT * FROM `item` WHERE `uid` = %d AND `mid` = '%s' LIMIT 1", intval($importer['channel_id']), dbesc($guid)); if (count($r)) { if ($positive === 'true') { logger('diaspora_like: duplicate like: ' . $guid); return; } // Note: I don't think "Like" objects with positive = "false" are ever actually used // It looks like "RelayableRetractions" are used for "unlike" instead if ($positive === 'false') { logger('diaspora_like: received a like with positive set to "false"...ignoring'); // perhaps call drop_item() // FIXME--actually don't unless it turns out that Diaspora does indeed send out "false" likes // send notification via proc_run() return; } } $i = q("select * from xchan where xchan_hash = '%s' limit 1", dbesc($parent_item['author_xchan'])); if ($i) { $item_author = $i[0]; } // Note: I don't think "Like" objects with positive = "false" are ever actually used // It looks like "RelayableRetractions" are used for "unlike" instead if ($positive === 'false') { logger('diaspora_like: received a like with positive set to "false"'); logger('diaspora_like: unlike received with no corresponding like...ignoring'); return; } /* How Diaspora performs "like" signature checking: - If an item has been sent by the like author to the top-level post owner to relay on to the rest of the contacts on the top-level post, the top-level post owner should check the author_signature, then create a parent_author_signature before relaying the like on - If an item has been relayed on by the top-level post owner, the contacts who receive it check only the parent_author_signature. Basically, they trust that the top-level post owner has already verified the authenticity of anything he/she sends out - In either case, the signature that get checked is the signature created by the person who sent the salmon */ // 2014-09-10 let's try this: signatures are failing. I'll try and make a signable string from // the parameters in the order they were presented in the post. This is how D* creates the signable string. $signed_data = $positive . ';' . $guid . ';' . $target_type . ';' . $parent_guid . ';' . $diaspora_handle; $key = $msg['key']; if ($parent_author_signature) { // If a parent_author_signature exists, then we've received the like // relayed from the top-level post owner. There's no need to check the // author_signature if the parent_author_signature is valid $parent_author_signature = base64_decode($parent_author_signature); if (!rsa_verify($signed_data, $parent_author_signature, $key, 'sha256')) { if (intval(get_config('system', 'ignore_diaspora_like_signature'))) { logger('diaspora_like: top-level owner verification failed. Proceeding anyway.'); } else { logger('diaspora_like: top-level owner verification failed.'); return; } } } else { // If there's no parent_author_signature, then we've received the like // from the like creator. In that case, the person is "like"ing // our post, so he/she must be a contact of ours and his/her public key // should be in $msg['key'] $author_signature = base64_decode($author_signature); if (!rsa_verify($signed_data, $author_signature, $key, 'sha256')) { if (intval(get_config('system', 'ignore_diaspora_like_signature'))) { logger('diaspora_like: like creator verification failed. Proceeding anyway'); } else { logger('diaspora_like: like creator verification failed.'); return; } } } logger('diaspora_like: signature check complete.', LOGGER_DEBUG); // Phew! Everything checks out. Now create an item. // Find the original comment author information. // We need this to make sure we display the comment author // information (name and avatar) correctly. if (strcasecmp($diaspora_handle, $msg['author']) == 0) { $person = $contact; } else { $person = find_diaspora_person_by_handle($diaspora_handle); if (!is_array($person)) { logger('diaspora_like: unable to find author details'); return; } } $uri = $diaspora_handle . ':' . $guid; $activity = ACTIVITY_LIKE; $post_type = $parent_item['resource_type'] === 'photo' ? t('photo') : t('status'); $links = array(array('rel' => 'alternate', 'type' => 'text/html', 'href' => $parent_item['plink'])); $objtype = $parent_item['resource_type'] === 'photo' ? ACTIVITY_OBJ_PHOTO : ACTIVITY_OBJ_NOTE; $body = $parent_item['body']; $object = json_encode(array('type' => $post_type, 'id' => $parent_item['mid'], 'parent' => $parent_item['thr_parent'] ? $parent_item['thr_parent'] : $parent_item['parent_mid'], 'link' => $links, 'title' => $parent_item['title'], 'content' => $parent_item['body'], 'created' => $parent_item['created'], 'edited' => $parent_item['edited'], 'author' => array('name' => $item_author['xchan_name'], 'address' => $item_author['xchan_addr'], 'guid' => $item_author['xchan_guid'], 'guid_sig' => $item_author['xchan_guid_sig'], 'link' => array(array('rel' => 'alternate', 'type' => 'text/html', 'href' => $item_author['xchan_url']), array('rel' => 'photo', 'type' => $item_author['xchan_photo_mimetype'], 'href' => $item_author['xchan_photo_m']))))); $bodyverb = t('%1$s likes %2$s\'s %3$s'); $arr = array(); $arr['uid'] = $importer['channel_id']; $arr['aid'] = $importer['channel_account_id']; $arr['mid'] = $guid; $arr['parent_mid'] = $parent_item['mid']; $arr['owner_xchan'] = $parent_item['owner_xchan']; $arr['author_xchan'] = $person['xchan_hash']; $ulink = '[url=' . $contact['url'] . ']' . $contact['name'] . '[/url]'; $alink = '[url=' . $parent_item['author-link'] . ']' . $parent_item['author-name'] . '[/url]'; $plink = '[url=' . z_root() . '/display/' . $guid . ']' . $post_type . '[/url]'; $arr['body'] = sprintf($bodyverb, $ulink, $alink, $plink); $arr['app'] = 'Diaspora'; $arr['item_private'] = $parent_item['item_private']; $arr['verb'] = $activity; $arr['object-type'] = $objtype; $arr['object'] = $object; if (!$parent_author_signature) { $datarray['diaspora_meta'] = array('signer' => $diaspora_handle, 'body' => $text, 'signed_text' => $signed_data, 'signature' => base64_encode($author_signature)); } $x = item_store($arr); if ($x) { $message_id = $x['item_id']; } // if the message isn't already being relayed, notify others // the existence of parent_author_signature means the parent_author or owner // is already relaying. The parent_item['origin'] indicates the message was created on our system if ($parent_item['item_flags'] & ITEM_ORIGIN && !$parent_author_signature) { proc_run('php', 'include/notifier.php', 'comment-import', $message_id); } return; }
function diaspora_like($importer, $xml, $msg) { $a = get_app(); $guid = notags(unxmlify($xml->guid)); $parent_guid = notags(unxmlify($xml->parent_guid)); $diaspora_handle = notags(unxmlify($xml->diaspora_handle)); $target_type = notags(unxmlify($xml->target_type)); $positive = notags(unxmlify($xml->positive)); $author_signature = notags(unxmlify($xml->author_signature)); $parent_author_signature = $xml->parent_author_signature ? notags(unxmlify($xml->parent_author_signature)) : ''; // likes on comments not supported here and likes on photos not supported by Diaspora if ($target_type !== 'Post') { return; } $contact = diaspora_get_contact_by_handle($importer['uid'], $msg['author']); if (!$contact) { logger('diaspora_like: cannot find contact: ' . $msg['author']); return; } if ($contact['rel'] == CONTACT_IS_FOLLOWER || $contact['blocked'] || $contact['readonly']) { logger('diaspora_like: Ignoring this author.'); return 202; } $r = q("SELECT * FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", intval($importer['uid']), dbesc($parent_guid)); if (!count($r)) { logger('diaspora_like: parent item not found: ' . $guid); return; } $parent_item = $r[0]; $r = q("SELECT * FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", intval($importer['uid']), dbesc($guid)); if (count($r)) { if ($positive === 'true') { logger('diaspora_like: duplicate like: ' . $guid); return; } if ($positive === 'false') { q("UPDATE `item` SET `deleted` = 1 WHERE `id` = %d AND `uid` = %d LIMIT 1", intval($r[0]['id']), intval($importer['uid'])); // FIXME // send notification via proc_run() return; } } if ($positive === 'false') { logger('diaspora_like: unlike received with no corresponding like'); return; } $author_signed_data = $guid . ';' . $target_type . ';' . $parent_guid . ';' . $positive . ';' . $diaspora_handle; $author_signature = base64_decode($author_signature); if (strcasecmp($diaspora_handle, $msg['author']) == 0) { $person = $contact; $key = $msg['key']; } else { $person = find_diaspora_person_by_handle($diaspora_handle); if (is_array($person) && x($person, 'pubkey')) { $key = $person['pubkey']; } else { logger('diaspora_like: unable to find author details'); return; } } if (!rsa_verify($author_signed_data, $author_signature, $key, 'sha256')) { logger('diaspora_like: verification failed.'); return; } if ($parent_author_signature) { $owner_signed_data = $guid . ';' . $target_type . ';' . $parent_guid . ';' . $positive . ';' . $diaspora_handle; $parent_author_signature = base64_decode($parent_author_signature); $key = $msg['key']; if (!rsa_verify($owner_signed_data, $parent_author_signature, $key, 'sha256')) { logger('diaspora_like: owner verification failed.'); return; } } // Phew! Everything checks out. Now create an item. $uri = $diaspora_handle . ':' . $guid; $activity = ACTIVITY_LIKE; $post_type = $parent_item['resource-id'] ? t('photo') : t('status'); $objtype = $parent_item['resource-id'] ? ACTIVITY_OBJ_PHOTO : ACTIVITY_OBJ_NOTE; $link = xmlify('<link rel="alternate" type="text/html" href="' . $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $parent_item['id'] . '" />' . "\n"); $body = $parent_item['body']; $obj = <<<EOT \t<object> \t\t<type>{$objtype}</type> \t\t<local>1</local> \t\t<id>{$parent_item['uri']}</id> \t\t<link>{$link}</link> \t\t<title></title> \t\t<content>{$body}</content> \t</object> EOT; $bodyverb = t('%1$s likes %2$s\'s %3$s'); $arr = array(); $arr['uri'] = $uri; $arr['uid'] = $importer['uid']; $arr['guid'] = $guid; $arr['contact-id'] = $contact['id']; $arr['type'] = 'activity'; $arr['wall'] = $parent_item['wall']; $arr['gravity'] = GRAVITY_LIKE; $arr['parent'] = $parent_item['id']; $arr['parent-uri'] = $parent_item['uri']; $arr['owner-name'] = $parent_item['name']; $arr['owner-link'] = $parent_item['url']; $arr['owner-avatar'] = $parent_item['thumb']; $arr['author-name'] = $person['name']; $arr['author-link'] = $person['url']; $arr['author-avatar'] = x($person, 'thumb') ? $person['thumb'] : $person['photo']; $ulink = '[url=' . $contact['url'] . ']' . $contact['name'] . '[/url]'; $alink = '[url=' . $parent_item['author-link'] . ']' . $parent_item['author-name'] . '[/url]'; $plink = '[url=' . $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $parent_item['id'] . ']' . $post_type . '[/url]'; $arr['body'] = sprintf($bodyverb, $ulink, $alink, $plink); $arr['app'] = 'Diaspora'; $arr['private'] = $parent_item['private']; $arr['verb'] = $activity; $arr['object-type'] = $objtype; $arr['object'] = $obj; $arr['visible'] = 1; $arr['unseen'] = 1; $arr['last-child'] = 0; $message_id = item_store($arr); if ($message_id) { q("update item set plink = '%s' where id = %d limit 1", dbesc($a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $message_id), intval($message_id)); } if (!$parent_author_signature) { q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ", intval($message_id), dbesc($author_signed_data), dbesc(base64_encode($author_signature)), dbesc($diaspora_handle)); } // if the message isn't already being relayed, notify others // the existence of parent_author_signature means the parent_author or owner // is already relaying. The parent_item['origin'] indicates the message was created on our system if ($parent_item['origin'] && !$parent_author_signature) { proc_run('php', 'include/notifier.php', 'comment', $message_id); } return; }
function get_diaspora_key($handle) { logger('Fetching diaspora key for: ' . $handle, LOGGER_DEBUG); $r = find_diaspora_person_by_handle($handle); return $r ? $r['xchan_pubkey'] : ''; }
function diaspora_signed_retraction($importer, $xml, $msg) { $guid = notags(unxmlify($xml->target_guid)); $diaspora_handle = notags(unxmlify($xml->sender_handle)); $type = notags(unxmlify($xml->target_type)); $sig = notags(unxmlify($xml->target_author_signature)); $parent_author_signature = $xml->parent_author_signature ? notags(unxmlify($xml->parent_author_signature)) : ''; $contact = diaspora_get_contact_by_handle($importer['uid'], $diaspora_handle); if (!$contact) { logger('diaspora_signed_retraction: no contact'); return; } $signed_data = $guid . ';' . $type; $sig_decode = base64_decode($sig); if (strcasecmp($diaspora_handle, $msg['author']) == 0) { $person = $contact; $key = $msg['key']; } else { $person = find_diaspora_person_by_handle($diaspora_handle); if (is_array($person) && x($person, 'pubkey')) { $key = $person['pubkey']; } else { logger('diaspora_signed_retraction: unable to find author details'); return; } } if (!rsa_verify($signed_data, $sig_decode, $key, 'sha256')) { logger('diaspora_signed_retraction: retraction-owner verification failed.' . print_r($msg, true)); return; } if ($parent_author_signature) { $parent_author_signature = base64_decode($parent_author_signature); $key = $msg['key']; if (!rsa_verify($signed_data, $parent_author_signature, $key, 'sha256')) { logger('diaspora_signed_retraction: failed to verify person relaying the retraction (e.g. owner of a post relaying a retracted comment'); return; } } if ($type === 'StatusMessage' || $type === 'Comment' || $type === 'Like') { $r = q("select * from item where guid = '%s' and uid = %d and not file like '%%[%%' limit 1", dbesc($guid), intval($importer['uid'])); if (count($r)) { if (link_compare($r[0]['author-link'], $contact['url'])) { q("update item set `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = '' where `id` = %d limit 1", dbesc(datetime_convert()), dbesc(datetime_convert()), intval($r[0]['id'])); // Now check if the retraction needs to be relayed by us // // The first item in the `item` table with the parent id is the parent. However, MySQL doesn't always // return the items ordered by `item`.`id`, in which case the wrong item is chosen as the parent. // The only item with `parent` and `id` as the parent id is the parent item. $p = q("select origin from item where parent = %d and id = %d limit 1", $r[0]['parent'], $r[0]['parent']); if (count($p)) { if ($p[0]['origin'] && !$parent_author_signature) { q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ", $r[0]['id'], dbesc($signed_data), dbesc($sig), dbesc($diaspora_handle)); // the existence of parent_author_signature would have meant the parent_author or owner // is already relaying. logger('diaspora_signed_retraction: relaying relayable_retraction'); proc_run('php', 'include/notifier.php', 'relayable_retraction', $r[0]['id']); } } } } } else { logger('diaspora_signed_retraction: unknown type: ' . $type); } return 202; // NOTREACHED }