function p_init(&$a) { if (argc() < 2) { http_status_exit(401); } $mid = str_replace('.xml', '', argv(1)); $r = q("select * from item where mid = '%s' and item_wall = 1 and item_private = 0 limit 1", dbesc($mid)); if (!$r || !perm_is_allowed($r[0]['uid'], '', 'view_stream')) { http_status_exit(404); } $c = q("select * from channel where channel_id = %d limit 1", intval($r[0]['uid'])); if (!$c) { http_status_exit(404); } $myaddr = $c[0]['channel_address'] . '@' . App::get_hostname(); $item = $r[0]; $title = $item['title']; $body = bb2diaspora_itembody($item); $created = datetime_convert('UTC', 'UTC', $item['created'], 'Y-m-d H:i:s \\U\\T\\C'); $tpl = get_markup_template('diaspora_post.tpl', 'addon/diaspora'); $msg = replace_macros($tpl, array('$body' => xmlify($body), '$guid' => $item['mid'], '$handle' => xmlify($myaddr), '$public' => 'true', '$created' => $created, '$provider' => $item['app'] ? $item['app'] : t('$projectname'))); header('Content-type: text/xml'); echo $msg; killme(); }
function store_diaspora_comment_sig($datarray, $channel, $parent_item, $post_id, $walltowall = false) { // We won't be able to sign Diaspora comments for authenticated visitors // - we don't have their private key // since Diaspora doesn't handle edits we can only do this for the original text and not update it. require_once 'include/bb2diaspora.php'; $signed_body = bb2diaspora_itembody($datarray, $walltowall); if ($walltowall) { logger('wall to wall comment', LOGGER_DEBUG); // post will come across with the owner's identity. Throw a preamble onto the post to indicate the true author. $signed_body = "\n\n" . '![' . $datarray['author']['xchan_name'] . '](' . $datarray['author']['xchan_photo_m'] . ')' . '[' . $datarray['author']['xchan_name'] . '](' . $datarray['author']['xchan_url'] . ')' . "\n\n" . $signed_body; } logger('storing diaspora comment signature', LOGGER_DEBUG); $diaspora_handle = $channel['channel_address'] . '@' . get_app()->get_hostname(); $signed_text = $datarray['mid'] . ';' . $parent_item['mid'] . ';' . $signed_body . ';' . $diaspora_handle; /** @FIXME $uprvkey is undefined, do we still need this if-statement? */ if ($uprvkey !== false) { $authorsig = base64_encode(rsa_sign($signed_text, $channel['channel_prvkey'], 'sha256')); } else { $authorsig = ''; } $x = array('signer' => $diaspora_handle, 'body' => $signed_body, 'signed_text' => $signed_text, 'signature' => base64_encode($authorsig)); $key = get_config('system', 'pubkey'); $y = crypto_encapsulate(json_encode($x), $key); $r = q("update item set diaspora_meta = '%s' where id = %d", dbesc(json_encode($y)), intval($post_id)); if (!$r) { logger('store_diaspora_comment_sig: DB write failed'); } return; }
function diaspora_post_local(&$a, &$item) { /** * If all the conditions are met, generate an instance of the Diaspora Comment Virus * * Previously all comments from any Hubzilla source (including those who have not opted in to * Diaspora federation), were required to locally generate a Diaspora comment signature. * The only exception was wall-to-wall posts which have no local signing authority. * * Going forward, if we are asked to propagate the virus and it is not present (due to the post author * not opting in to Diaspora federation); we will generate a "wall-to-wall" comment and not require * a source signature. This allows hubs and communities to opt-out of Diaspora federation and not be * forced to generate the comment virus regardless. This is necessary because Diaspora now requires * the virus not just to provide a stored signature and Diaspora formatted text body, but must also * include all XML fields presented by the Diaspora protocol when transmitting the comment, while * maintaining their source order. This is fine for federated communities using UNO, but it makes * no sense to require this low-level baggage in channels and communities that have chosen not to use * the Diaspora protocol and services. * */ require_once 'include/bb2diaspora.php'; if ($item['mid'] === $item['parent_mid']) { return; } if ($item['created'] != $item['edited']) { return; } $meta = null; $author = channelx_by_hash($item['author_xchan']); if ($author) { // The author has a local channel, If they have this connector installed, // sign the comment and create a Diaspora Comment Virus. $dspr_allowed = get_pconfig($author['channel_id'], 'system', 'diaspora_allowed'); if (!$dspr_allowed) { return; } $handle = channel_reddress($author); if ($item['verb'] === ACTIVITY_LIKE) { if ($item['thr_parent'] == $item['parent_mid'] && $item['obj_type'] == ACTIVITY_OBJ_NOTE) { $meta = ['positive' => 'true', 'guid' => $item['mid'], 'target_type' => 'Post', 'parent_guid' => $item['parent_mid'], 'diaspora_handle' => $handle]; } } else { $body = bb2diaspora_itembody($item, true, true); $meta = ['guid' => $item['mid'], 'parent_guid' => $item['parent_mid'], 'text' => $body, 'diaspora_handle' => $handle]; } $meta['author_signature'] = diaspora_sign_fields($meta, $author['channel_prvkey']); if ($item['author_xchan'] === $item['owner_xchan']) { $meta['parent_author_signature'] = diaspora_sign_fields($meta, $author['channel_prvkey']); } } if (!$meta && $item['author_xchan'] !== $item['owner_xchan']) { // A local comment arrived but the commenter does not have a local channel // or the commenter doesn't have the Diaspora plugin enabled. // The owner *should* have a local channel // Find the owner and if the owner has this addon installed, turn the comment into // a 'wall-to-wall' message containing the author attribution, // with the comment signed by the owner. $owner = channelx_by_hash($item['owner_xchan']); if (!$owner) { return; } $dspr_allowed = get_pconfig($owner['channel_id'], 'system', 'diaspora_allowed'); if (!$dspr_allowed) { return; } $handle = channel_reddress($owner); if ($item['verb'] === ACTIVITY_LIKE) { if ($item['thr_parent'] == $item['parent_mid'] && $item['obj_type'] == ACTIVITY_OBJ_NOTE) { $meta = ['positive' => 'true', 'guid' => $item['mid'], 'target_type' => 'Post', 'parent_guid' => $item['parent_mid'], 'diaspora_handle' => $handle]; } } else { $body = bb2diaspora_itembody($item, true, false); $meta = ['guid' => $item['mid'], 'parent_guid' => $item['parent_mid'], 'text' => $body, 'diaspora_handle' => $handle]; } $meta['author_signature'] = diaspora_sign_fields($meta, $owner['channel_prvkey']); $meta['parent_author_signature'] = diaspora_sign_fields($meta, $owner['channel_prvkey']); } if ($meta) { set_iconfig($item, 'diaspora', 'fields', $meta, true); } // otherwise, neither the author or owner have this plugin installed. Do nothing. // logger('ditem: ' . print_r($item,true)); }
function diaspora_send_relay($item, $owner, $contact, $public_batch = false) { $a = get_app(); $myaddr = $owner['channel_address'] . '@' . substr($a->get_baseurl(), strpos($a->get_baseurl(), '://') + 3); $text = bb2diaspora_itembody($item); $body = $text; // Diaspora doesn't support threaded comments, but some // versions of Diaspora (i.e. Diaspora-pistos) support // likes on comments if ($item['verb'] === ACTIVITY_LIKE && $item['thr_parent']) { $p = q("select * from item where mid = '%s' limit 1", dbesc($item['thr_parent'])); } else { // 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 * from item where parent = %d and id = %d limit 1", intval($item['parent']), intval($item['parent'])); } if ($p) { $parent = $p[0]; } else { logger('diaspora_send_relay: no parent'); return; } $like = false; $relay_retract = false; $sql_sign_id = 'iid'; if ($item['item_restrict'] & ITEM_DELETED) { $relay_retract = true; $target_type = $item['verb'] === ACTIVITY_LIKE ? 'Like' : 'Comment'; $sql_sign_id = 'retract_iid'; $tpl = get_markup_template('diaspora_relayable_retraction.tpl'); } elseif ($item['verb'] === ACTIVITY_LIKE) { $like = true; $target_type = $parent['mid'] === $parent['parent_mid'] ? 'Post' : 'Comment'; // $positive = (($item['item_restrict'] & ITEM_DELETED) ? 'false' : 'true'); $positive = 'true'; $tpl = get_markup_template('diaspora_like_relay.tpl'); } else { // item is a comment $tpl = get_markup_template('diaspora_comment_relay.tpl'); } $diaspora_meta = $item['diaspora_meta'] ? json_decode($item['diaspora_meta'], true) : ''; if ($diaspora_meta) { $sender_signed_text = $diaspora_meta['signed_text']; $authorsig = $diaspora_meta['signature']; $handle = $diaspora_meta['signer']; $text = $diaspora_meta['body']; } else { logger('diaspora_send_relay: original author signature not found'); } /* Since the author signature is only checked by the parent, not by the relay recipients, * I think it may not be necessary for us to do so much work to preserve all the original * signatures. The important thing that Diaspora DOES need is the original creator's handle. * Let's just generate that and forget about all the original author signature stuff. * * Note: this might be more of an problem if we want to support likes on comments for older * versions of Diaspora (diaspora-pistos), but since there are a number of problems with * doing that, let's ignore it for now. * * */ // bug - nomadic identity may/will affect diaspora_handle_from_contact if (!$handle) { if ($item['author_xchan'] === $owner['channel_hash']) { $handle = $owner['channel_address'] . '@' . substr($a->get_baseurl(), strpos($a->get_baseurl(), '://') + 3); } else { $handle = diaspora_handle_from_contact($item['author_xchan']); } } if (!$handle) { logger('diaspora_send_relay: no handle'); return; } if (!$sender_signed_text) { if ($relay_retract) { $sender_signed_text = $item['mid'] . ';' . $target_type; } elseif ($like) { $sender_signed_text = $positive . ';' . $item['mid'] . ';' . $target_type . ';' . $parent['mid'] . ';' . $handle; } else { $sender_signed_text = $item['mid'] . ';' . $parent['mid'] . ';' . $text . ';' . $handle; } } // Sign the relayable with the top-level owner's signature // // We'll use the $sender_signed_text that we just created, instead of the $signed_text // stored in the database, because that provides the best chance that Diaspora will // be able to reconstruct the signed text the same way we did. This is particularly a // concern for the comment, whose signed text includes the text of the comment. The // smallest change in the text of the comment, including removing whitespace, will // make the signature verification fail. Since we translate from BB code to Diaspora's // markup at the top of this function, which is AFTER we placed the original $signed_text // in the database, it's hazardous to trust the original $signed_text. $parentauthorsig = base64_encode(rsa_sign($sender_signed_text, $owner['channel_prvkey'], 'sha256')); $msg = replace_macros($tpl, array('$guid' => xmlify($item['mid']), '$parent_guid' => xmlify($parent['mid']), '$target_type' => xmlify($target_type), '$authorsig' => xmlify($authorsig), '$parentsig' => xmlify($parentauthorsig), '$body' => xmlify($text), '$positive' => xmlify($positive), '$handle' => xmlify($handle))); logger('diaspora_send_relay: base message: ' . $msg, LOGGER_DATA); $slap = 'xml=' . urlencode(urlencode(diaspora_msg_build($msg, $owner, $contact, $owner['channel_prvkey'], $contact['xchan_pubkey'], $public_batch))); return diaspora_transmit($owner, $contact, $slap, $public_batch); }
function diaspora_build_status($item, $owner) { $myaddr = channel_reddress($owner); if (intval($item['id']) != intval($item['parent'])) { logger('attempted to send a comment as a top-level post'); return; } $images = array(); $title = $item['title']; $body = bb2diaspora_itembody($item, true); $poll = ''; $public = $item['item_private'] ? 'false' : 'true'; $created = datetime_convert('UTC', 'UTC', $item['created'], 'Y-m-d H:i:s \\U\\T\\C'); // Detect a share element and do a reshare if (!$item['item_private'] && ($ret = diaspora_is_reshare($item['body']))) { $msg = replace_macros(get_markup_template('diaspora_reshare.tpl', 'addon/diaspora'), ['$root_handle' => xmlify($ret['root_handle']), '$root_guid' => $ret['root_guid'], '$guid' => $item['mid'], '$handle' => xmlify($myaddr), '$public' => $public, '$created' => $created, '$provider' => $item['app'] ? $item['app'] : t('$projectname')]); } else { $msg = replace_macros(get_markup_template('diaspora_post.tpl', 'addon/diaspora'), ['$body' => xmlify($body), '$guid' => $item['mid'], '$poll' => $poll, '$handle' => xmlify($myaddr), '$public' => $public, '$created' => $created, '$provider' => $item['app'] ? $item['app'] : t('$projectname')]); } return $msg; }
function diaspora_send_downstream($item, $owner, $contact, $public_batch = false) { $a = get_app(); $myaddr = $owner['channel_address'] . '@' . App::get_hostname(); $text = bb2diaspora_itembody($item); $body = $text; // Diaspora doesn't support threaded comments, but some // versions of Diaspora (i.e. Diaspora-pistos) support // likes on comments // That version is now dead so detect a "sublike" and // just send it as an activity. $sublike = false; if ($item['verb'] === ACTIVITY_LIKE) { if ($item['thr_parent'] && $item['thr_parent'] !== $item['parent_mid']) { $sublike = true; } } // 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 * from item where parent = %d and id = %d limit 1", intval($item['parent']), intval($item['parent'])); if ($p) { $parent = $p[0]; } else { logger('diaspora_send_downstream: no parent'); return; } $xmlout = diaspora_fields_to_xml(get_iconfig($item, 'diaspora', 'fields')); $like = false; $relay_retract = false; $sql_sign_id = 'iid'; if (intval($item['item_deleted'])) { $relay_retract = true; $target_type = $item['verb'] === ACTIVITY_LIKE && !$sublike ? 'Like' : 'Comment'; $sql_sign_id = 'retract_iid'; $tpl = get_markup_template('diaspora_relayable_retraction.tpl', 'addon/diaspora'); } elseif ($item['verb'] === ACTIVITY_LIKE && !$sublike && $xmlout) { $like = true; $target_type = $parent['mid'] === $parent['parent_mid'] ? 'Post' : 'Comment'; // $positive = (intval($item['item_deleted']) ? 'false' : 'true'); $positive = 'true'; $tpl = get_markup_template('diaspora_like_relay.tpl', 'addon/diaspora'); } else { // item is a comment $tpl = get_markup_template('diaspora_comment_relay.tpl', 'addon/diaspora'); } $diaspora_meta = $item['diaspora_meta'] ? json_decode($item['diaspora_meta'], true) : ''; if ($diaspora_meta) { if (array_key_exists('iv', $diaspora_meta)) { $key = get_config('system', 'prvkey'); $meta = json_decode(crypto_unencapsulate($diaspora_meta, $key), true); } else { $meta = $diaspora_meta; } $sender_signed_text = $meta['signed_text']; $authorsig = $meta['signature']; $handle = $meta['signer']; $text = $meta['body']; } else { logger('diaspora_send_downstream: original author signature not found'); } /* Since the author signature is only checked by the parent, not by the relay recipients, * I think it may not be necessary for us to do so much work to preserve all the original * signatures. The important thing that Diaspora DOES need is the original creator's handle. * Let's just generate that and forget about all the original author signature stuff. * * Note: this might be more of an problem if we want to support likes on comments for older * versions of Diaspora (diaspora-pistos), but since there are a number of problems with * doing that, let's ignore it for now. * * */ // bug - nomadic identity may/will affect diaspora_handle_from_contact if (!$handle) { $handle = $owner['channel_address'] . '@' . App::get_hostname(); } if (!$sender_signed_text) { if ($relay_retract) { $sender_signed_text = $item['mid'] . ';' . $target_type; } elseif ($like) { $sender_signed_text = $positive . ';' . $item['mid'] . ';' . $target_type . ';' . $parent['mid'] . ';' . $handle; } else { $sender_signed_text = $item['mid'] . ';' . $parent['mid'] . ';' . $text . ';' . $handle; } } // The relayable may have arrived from somebody who provided no Diaspora Comment Virus. // We check for this above in bb2diaspora_itembody. In that case we will have generated // the body as a "wall-to-wall" post, and the author_signature will now be our own. if (!$xmlout && !$authorsig) { $authorsig = base64_encode(rsa_sign($sender_signed_text, $owner['channel_prvkey'], 'sha256')); } // Sign the relayable with the top-level owner's signature $parentauthorsig = base64_encode(rsa_sign($sender_signed_text, $owner['channel_prvkey'], 'sha256')); if (!$text) { logger('diaspora_send_downstream: no text'); } $msg = replace_macros($tpl, array('$xml' => $xmlout, '$guid' => xmlify($item['mid']), '$parent_guid' => xmlify($parent['mid']), '$target_type' => xmlify($target_type), '$authorsig' => xmlify($authorsig), '$parentsig' => xmlify($parentauthorsig), '$body' => xmlify($text), '$positive' => xmlify($positive), '$handle' => xmlify($handle))); logger('diaspora_send_downstream: base message: ' . $msg, LOGGER_DATA); $slap = 'xml=' . urlencode(urlencode(diaspora_msg_build($msg, $owner, $contact, $owner['channel_prvkey'], $contact['xchan_pubkey'], $public_batch))); return diaspora_queue($owner, $contact, $slap, $public_batch, $item['mid']); }