/** * Sourced and tag-delivered posts are re-targetted for delivery to the connections of the channel * receiving the post. This starts the second delivery chain, by resetting permissions and ensuring * that ITEM_UPLINK is set on the parent post, and storing the current owner_xchan as the source_xchan. * We'll become the new owner. If called without $parent, this *is* the parent post. * * @param array $channel * @param array $item * @param int $item_id * @param boolean $parent */ function start_delivery_chain($channel, $item, $item_id, $parent) { // Change this copy of the post to a forum head message and deliver to all the tgroup members // also reset all the privacy bits to the forum default permissions $private = $channel['channel_allow_cid'] || $channel['channel_allow_gid'] || $channel['channel_deny_cid'] || $channel['channel_deny_gid'] ? 1 : 0; $new_public_policy = map_scope($channel['channel_r_stream'], true); if (!$private && $new_public_policy) { $private = 1; } $flag_bits = $item['item_flags'] | ITEM_WALL; // The message didn't necessarily originate on this site, (we'll honour it if it did), // but the parent post of this thread will be reset as a local post, as it is the top of // this delivery chain and is coming from this site, regardless of where the original // originated. if (!$parent) { $flag_bits = $flag_bits | ITEM_ORIGIN; } // unset the nocomment bit if it's there. if ($flag_bits & ITEM_NOCOMMENT) { $flag_bits = $flag_bits ^ ITEM_NOCOMMENT; } // maintain the original source, which will be the original item owner and was stored in source_xchan // when we created the delivery fork if ($parent) { $r = q("update item set source_xchan = '%s' where id = %d", dbesc($parent['source_xchan']), intval($item_id)); } else { $flag_bits = $flag_bits | ITEM_UPLINK; $r = q("update item set source_xchan = owner_xchan where id = %d", intval($item_id)); } $title = $item['title']; $body = $item['body']; if ($private) { if (!($flag_bits & ITEM_OBSCURED)) { $key = get_config('system', 'pubkey'); $flag_bits = $flag_bits | ITEM_OBSCURED; if ($title) { $title = json_encode(crypto_encapsulate($title, $key)); } if ($body) { $body = json_encode(crypto_encapsulate($body, $key)); } } } else { if ($flag_bits & ITEM_OBSCURED) { $key = get_config('system', 'prvkey'); $flag_bits = $flag_bits ^ ITEM_OBSCURED; if ($title) { $title = crypto_unencapsulate(json_decode($title, true), $key); } if ($body) { $body = crypto_unencapsulate(json_decode($body, true), $key); } } } $r = q("update item set item_flags = %d, owner_xchan = '%s', allow_cid = '%s', allow_gid = '%s',\n\t\tdeny_cid = '%s', deny_gid = '%s', item_private = %d, public_policy = '%s', comment_policy = '%s', title = '%s', body = '%s' where id = %d", intval($flag_bits), dbesc($channel['channel_hash']), dbesc($channel['channel_allow_cid']), dbesc($channel['channel_allow_gid']), dbesc($channel['channel_deny_cid']), dbesc($channel['channel_deny_gid']), intval($private), dbesc($new_public_policy), dbesc(map_scope($channel['channel_w_comment'])), dbesc($title), dbesc($body), intval($item_id)); if ($r) { proc_run('php', 'include/notifier.php', 'tgroup', $item_id); } else { logger('start_delivery_chain: failed to update item'); } }
function zotinfo($arr) { $ret = array('success' => false); $zhash = x($arr, 'guid_hash') ? $arr['guid_hash'] : ''; $zguid = x($arr, 'guid') ? $arr['guid'] : ''; $zguid_sig = x($arr, 'guid_sig') ? $arr['guid_sig'] : ''; $zaddr = x($arr, 'address') ? $arr['address'] : ''; $ztarget = x($arr, 'target') ? $arr['target'] : ''; $zsig = x($arr, 'target_sig') ? $arr['target_sig'] : ''; $zkey = x($arr, 'key') ? $arr['key'] : ''; $mindate = x($arr, 'mindate') ? $arr['mindate'] : ''; $feed = x($arr, 'feed') ? intval($arr['feed']) : 0; if ($ztarget) { if (!$zkey || !$zsig || !rsa_verify($ztarget, base64url_decode($zsig), $zkey)) { logger('zfinger: invalid target signature'); $ret['message'] = t("invalid target signature"); return $ret; } } $r = null; if (strlen($zhash)) { $r = q("select channel.*, xchan.* from channel left join xchan on channel_hash = xchan_hash \n\t\t\twhere channel_hash = '%s' limit 1", dbesc($zhash)); } elseif (strlen($zguid) && strlen($zguid_sig)) { $r = q("select channel.*, xchan.* from channel left join xchan on channel_hash = xchan_hash \n\t\t\twhere channel_guid = '%s' and channel_guid_sig = '%s' limit 1", dbesc($zguid), dbesc($zguid_sig)); } elseif (strlen($zaddr)) { if (strpos($zaddr, '[system]') === false) { /* normal address lookup */ $r = q("select channel.*, xchan.* from channel left join xchan on channel_hash = xchan_hash\n\t\t\t\twhere ( channel_address = '%s' or xchan_addr = '%s' ) limit 1", dbesc($zaddr), dbesc($zaddr)); } else { /** * The special address '[system]' will return a system channel if one has been defined, * Or the first valid channel we find if there are no system channels. * * This is used by magic-auth if we have no prior communications with this site - and * returns an identity on this site which we can use to create a valid hub record so that * we can exchange signed messages. The precise identity is irrelevant. It's the hub * information that we really need at the other end - and this will return it. * */ $r = q("select channel.*, xchan.* from channel left join xchan on channel_hash = xchan_hash\n\t\t\t\twhere channel_system = 1 order by channel_id limit 1"); if (!$r) { $r = q("select channel.*, xchan.* from channel left join xchan on channel_hash = xchan_hash\n\t\t\t\t\twhere channel_removed = 0 order by channel_id limit 1"); } } } else { $ret['message'] = 'Invalid request'; return $ret; } if (!$r) { $ret['message'] = 'Item not found.'; return $ret; } $e = $r[0]; $id = $e['channel_id']; $sys_channel = intval($e['channel_system']) ? true : false; $special_channel = $e['channel_pageflags'] & PAGE_PREMIUM ? true : false; $adult_channel = $e['channel_pageflags'] & PAGE_ADULT ? true : false; $censored = $e['channel_pageflags'] & PAGE_CENSORED ? true : false; $searchable = $e['channel_pageflags'] & PAGE_HIDDEN ? false : true; $deleted = intval($e['xchan_deleted']) ? true : false; if ($deleted || $censored || $sys_channel) { $searchable = false; } $public_forum = false; $role = get_pconfig($e['channel_id'], 'system', 'permissions_role'); if ($role === 'forum' || $role === 'repository') { $public_forum = true; } else { // check if it has characteristics of a public forum based on custom permissions. $t = q("select abook_my_perms from abook where abook_channel = %d and abook_self = 1 limit 1", intval($e['channel_id'])); if ($t && ($t[0]['abook_my_perms'] & PERMS_W_TAGWALL && !($t[0]['abook_my_perms'] & PERMS_W_STREAM))) { $public_forum = true; } } // This is for birthdays and keywords, but must check access permissions $p = q("select * from profile where uid = %d and is_default = 1", intval($e['channel_id'])); $profile = array(); if ($p) { if (!intval($p[0]['publish'])) { $searchable = false; } $profile['description'] = $p[0]['pdesc']; $profile['birthday'] = $p[0]['dob']; if ($profile['birthday'] != '0000-00-00' && ($bd = z_birthday($p[0]['dob'], $e['channel_timezone'])) !== '') { $profile['next_birthday'] = $bd; } if ($age = age($p[0]['dob'], $e['channel_timezone'], '')) { $profile['age'] = $age; } $profile['gender'] = $p[0]['gender']; $profile['marital'] = $p[0]['marital']; $profile['sexual'] = $p[0]['sexual']; $profile['locale'] = $p[0]['locality']; $profile['region'] = $p[0]['region']; $profile['postcode'] = $p[0]['postal_code']; $profile['country'] = $p[0]['country_name']; $profile['about'] = $p[0]['about']; $profile['homepage'] = $p[0]['homepage']; $profile['hometown'] = $p[0]['hometown']; if ($p[0]['keywords']) { $tags = array(); $k = explode(' ', $p[0]['keywords']); if ($k) { foreach ($k as $kk) { if (trim($kk, " \t\n\r\v,")) { $tags[] = trim($kk, " \t\n\r\v,"); } } } if ($tags) { $profile['keywords'] = $tags; } } } $ret['success'] = true; // Communication details $ret['guid'] = $e['xchan_guid']; $ret['guid_sig'] = $e['xchan_guid_sig']; $ret['key'] = $e['xchan_pubkey']; $ret['name'] = $e['xchan_name']; $ret['name_updated'] = $e['xchan_name_date']; $ret['address'] = $e['xchan_addr']; $ret['photo_mimetype'] = $e['xchan_photo_mimetype']; $ret['photo'] = $e['xchan_photo_l']; $ret['photo_updated'] = $e['xchan_photo_date']; $ret['url'] = $e['xchan_url']; $ret['connections_url'] = $e['xchan_connurl'] ? $e['xchan_connurl'] : z_root() . '/poco/' . $e['channel_address']; $ret['target'] = $ztarget; $ret['target_sig'] = $zsig; $ret['searchable'] = $searchable; $ret['adult_content'] = $adult_channel; $ret['public_forum'] = $public_forum; if ($deleted) { $ret['deleted'] = $deleted; } if (intval($e['channel_removed'])) { $ret['deleted_locally'] = true; } // premium or other channel desiring some contact with potential followers before connecting. // This is a template - %s will be replaced with the follow_url we discover for the return channel. if ($special_channel) { $ret['connect_url'] = z_root() . '/connect/' . $e['channel_address']; } // This is a template for our follow url, %s will be replaced with a webbie $ret['follow_url'] = z_root() . '/follow?f=&url=%s'; $ztarget_hash = $ztarget && $zsig ? make_xchan_hash($ztarget, $zsig) : ''; $permissions = get_all_perms($e['channel_id'], $ztarget_hash, false); if ($ztarget_hash) { $permissions['connected'] = false; $b = q("select * from abook where abook_xchan = '%s' and abook_channel = %d limit 1", dbesc($ztarget_hash), intval($e['channel_id'])); if ($b) { $permissions['connected'] = true; } } $ret['permissions'] = $ztarget && $zkey ? crypto_encapsulate(json_encode($permissions), $zkey) : $permissions; if ($permissions['view_profile']) { $ret['profile'] = $profile; } // array of (verified) hubs this channel uses $x = zot_encode_locations($e); if ($x) { $ret['locations'] = $x; } $ret['site'] = array(); $ret['site']['url'] = z_root(); $ret['site']['url_sig'] = base64url_encode(rsa_sign(z_root(), $e['channel_prvkey'])); $dirmode = get_config('system', 'directory_mode'); if ($dirmode === false || $dirmode == DIRECTORY_MODE_NORMAL) { $ret['site']['directory_mode'] = 'normal'; } if ($dirmode == DIRECTORY_MODE_PRIMARY) { $ret['site']['directory_mode'] = 'primary'; } elseif ($dirmode == DIRECTORY_MODE_SECONDARY) { $ret['site']['directory_mode'] = 'secondary'; } elseif ($dirmode == DIRECTORY_MODE_STANDALONE) { $ret['site']['directory_mode'] = 'standalone'; } if ($dirmode != DIRECTORY_MODE_NORMAL) { $ret['site']['directory_url'] = z_root() . '/dirsearch'; } // hide detailed site information if you're off the grid if ($dirmode != DIRECTORY_MODE_STANDALONE) { $register_policy = intval(get_config('system', 'register_policy')); if ($register_policy == REGISTER_CLOSED) { $ret['site']['register_policy'] = 'closed'; } if ($register_policy == REGISTER_APPROVE) { $ret['site']['register_policy'] = 'approve'; } if ($register_policy == REGISTER_OPEN) { $ret['site']['register_policy'] = 'open'; } $access_policy = intval(get_config('system', 'access_policy')); if ($access_policy == ACCESS_PRIVATE) { $ret['site']['access_policy'] = 'private'; } if ($access_policy == ACCESS_PAID) { $ret['site']['access_policy'] = 'paid'; } if ($access_policy == ACCESS_FREE) { $ret['site']['access_policy'] = 'free'; } if ($access_policy == ACCESS_TIERED) { $ret['site']['access_policy'] = 'tiered'; } $ret['site']['accounts'] = account_total(); require_once 'include/identity.php'; $ret['site']['channels'] = channel_total(); $ret['site']['version'] = PLATFORM_NAME . ' ' . RED_VERSION . '[' . DB_UPDATE_VERSION . ']'; $ret['site']['admin'] = get_config('system', 'admin_email'); $a = get_app(); $visible_plugins = array(); if (is_array($a->plugins) && count($a->plugins)) { $r = q("select * from addon where hidden = 0"); if ($r) { foreach ($r as $rr) { $visible_plugins[] = $rr['name']; } } } $ret['site']['plugins'] = $visible_plugins; $ret['site']['sitehash'] = get_config('system', 'location_hash'); $ret['site']['sitename'] = get_config('system', 'sitename'); $ret['site']['sellpage'] = get_config('system', 'sellpage'); $ret['site']['location'] = get_config('system', 'site_location'); $ret['site']['realm'] = get_directory_realm(); $ret['site']['project'] = PLATFORM_NAME; } check_zotinfo($e, $x, $ret); call_hooks('zot_finger', $ret); return $ret; }
function item_post(&$a) { // This will change. Figure out who the observer is and whether or not // they have permission to post here. Else ignore the post. if (!local_channel() && !remote_channel() && !x($_REQUEST, 'commenter')) { return; } require_once 'include/security.php'; $uid = local_channel(); $channel = null; $observer = null; /** * Is this a reply to something? */ $parent = x($_REQUEST, 'parent') ? intval($_REQUEST['parent']) : 0; $parent_mid = x($_REQUEST, 'parent_mid') ? trim($_REQUEST['parent_mid']) : ''; $remote_xchan = x($_REQUEST, 'remote_xchan') ? trim($_REQUEST['remote_xchan']) : false; $r = q("select * from xchan where xchan_hash = '%s' limit 1", dbesc($remote_xchan)); if ($r) { $remote_observer = $r[0]; } else { $remote_xchan = $remote_observer = false; } $profile_uid = x($_REQUEST, 'profile_uid') ? intval($_REQUEST['profile_uid']) : 0; require_once 'include/identity.php'; $sys = get_sys_channel(); if ($sys && $profile_uid && $sys['channel_id'] == $profile_uid && is_site_admin()) { $uid = intval($sys['channel_id']); $channel = $sys; $observer = $sys; } if (x($_REQUEST, 'dropitems')) { require_once 'include/items.php'; $arr_drop = explode(',', $_REQUEST['dropitems']); drop_items($arr_drop); $json = array('success' => 1); echo json_encode($json); killme(); } call_hooks('post_local_start', $_REQUEST); // logger('postvars ' . print_r($_REQUEST,true), LOGGER_DATA); $api_source = x($_REQUEST, 'api_source') && $_REQUEST['api_source'] ? true : false; $consensus = intval($_REQUEST['consensus']); // 'origin' (if non-zero) indicates that this network is where the message originated, // for the purpose of relaying comments to other conversation members. // If using the API from a device (leaf node) you must set origin to 1 (default) or leave unset. // If the API is used from another network with its own distribution // and deliveries, you may wish to set origin to 0 or false and allow the other // network to relay comments. // If you are unsure, it is prudent (and important) to leave it unset. $origin = $api_source && array_key_exists('origin', $_REQUEST) ? intval($_REQUEST['origin']) : 1; // To represent message-ids on other networks - this will create an item_id record $namespace = $api_source && array_key_exists('namespace', $_REQUEST) ? strip_tags($_REQUEST['namespace']) : ''; $remote_id = $api_source && array_key_exists('remote_id', $_REQUEST) ? strip_tags($_REQUEST['remote_id']) : ''; $owner_hash = null; $message_id = x($_REQUEST, 'message_id') && $api_source ? strip_tags($_REQUEST['message_id']) : ''; $created = x($_REQUEST, 'created') ? datetime_convert('UTC', 'UTC', $_REQUEST['created']) : datetime_convert(); $post_id = x($_REQUEST, 'post_id') ? intval($_REQUEST['post_id']) : 0; $app = x($_REQUEST, 'source') ? strip_tags($_REQUEST['source']) : ''; $return_path = x($_REQUEST, 'return') ? $_REQUEST['return'] : ''; $preview = x($_REQUEST, 'preview') ? intval($_REQUEST['preview']) : 0; $categories = x($_REQUEST, 'category') ? escape_tags($_REQUEST['category']) : ''; $webpage = x($_REQUEST, 'webpage') ? intval($_REQUEST['webpage']) : 0; $pagetitle = x($_REQUEST, 'pagetitle') ? escape_tags(urlencode($_REQUEST['pagetitle'])) : ''; $layout_mid = x($_REQUEST, 'layout_mid') ? escape_tags($_REQUEST['layout_mid']) : ''; $plink = x($_REQUEST, 'permalink') ? escape_tags($_REQUEST['permalink']) : ''; $obj_type = x($_REQUEST, 'obj_type') ? escape_tags($_REQUEST['obj_type']) : ACTIVITY_OBJ_NOTE; // allow API to bulk load a bunch of imported items with sending out a bunch of posts. $nopush = x($_REQUEST, 'nopush') ? intval($_REQUEST['nopush']) : 0; /* * Check service class limits */ if ($uid && !x($_REQUEST, 'parent') && !x($_REQUEST, 'post_id')) { $ret = item_check_service_class($uid, $_REQUEST['webpage'] == ITEM_WEBPAGE ? true : false); if (!$ret['success']) { notice(t($ret['message']) . EOL); if (x($_REQUEST, 'return')) { goaway($a->get_baseurl() . "/" . $return_path); } killme(); } } if ($pagetitle) { require_once 'library/urlify/URLify.php'; $pagetitle = strtolower(URLify::transliterate($pagetitle)); } $item_flags = $item_restrict = 0; $route = ''; $parent_item = null; $parent_contact = null; $thr_parent = ''; $parid = 0; $r = false; if ($parent || $parent_mid) { if (!x($_REQUEST, 'type')) { $_REQUEST['type'] = 'net-comment'; } if ($obj_type == ACTIVITY_OBJ_POST) { $obj_type = ACTIVITY_OBJ_COMMENT; } if ($parent) { $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1", intval($parent)); } elseif ($parent_mid && $uid) { // This is coming from an API source, and we are logged in $r = q("SELECT * FROM `item` WHERE `mid` = '%s' AND `uid` = %d LIMIT 1", dbesc($parent_mid), intval($uid)); } // if this isn't the real parent of the conversation, find it if ($r !== false && count($r)) { $parid = $r[0]['parent']; $parent_mid = $r[0]['mid']; if ($r[0]['id'] != $r[0]['parent']) { $r = q("SELECT * FROM `item` WHERE `id` = `parent` AND `parent` = %d LIMIT 1", intval($parid)); } } if ($r === false || !count($r)) { notice(t('Unable to locate original post.') . EOL); if (x($_REQUEST, 'return')) { goaway($a->get_baseurl() . "/" . $return_path); } killme(); } // can_comment_on_post() needs info from the following xchan_query xchan_query($r); $parent_item = $r[0]; $parent = $r[0]['id']; // multi-level threading - preserve the info but re-parent to our single level threading $thr_parent = $parent_mid; $route = $parent_item['route']; } if (!$observer) { $observer = $a->get_observer(); } if ($parent) { logger('mod_item: item_post parent=' . $parent); $can_comment = false; if (array_key_exists('owner', $parent_item) && $parent_item['owner']['abook_flags'] & ABOOK_FLAG_SELF) { $can_comment = perm_is_allowed($profile_uid, $observer['xchan_hash'], 'post_comments'); } else { $can_comment = can_comment_on_post($observer['xchan_hash'], $parent_item); } if (!$can_comment) { notice(t('Permission denied.') . EOL); if (x($_REQUEST, 'return')) { goaway($a->get_baseurl() . "/" . $return_path); } killme(); } } else { if (!perm_is_allowed($profile_uid, $observer['xchan_hash'], 'post_wall')) { notice(t('Permission denied.') . EOL); if (x($_REQUEST, 'return')) { goaway($a->get_baseurl() . "/" . $return_path); } killme(); } } // is this an edited post? $orig_post = null; if ($namespace && $remote_id) { // It wasn't an internally generated post - see if we've got an item matching this remote service id $i = q("select iid from item_id where service = '%s' and sid = '%s' limit 1", dbesc($namespace), dbesc($remote_id)); if ($i) { $post_id = $i[0]['iid']; } } if ($post_id) { $i = q("SELECT * FROM `item` WHERE `uid` = %d AND `id` = %d LIMIT 1", intval($profile_uid), intval($post_id)); if (!count($i)) { killme(); } $orig_post = $i[0]; } if (!$channel) { if ($uid && $uid == $profile_uid) { $channel = $a->get_channel(); } else { // posting as yourself but not necessarily to a channel you control $r = q("select * from channel left join account on channel_account_id = account_id where channel_id = %d LIMIT 1", intval($profile_uid)); if ($r) { $channel = $r[0]; } } } if (!$channel) { logger("mod_item: no channel."); if (x($_REQUEST, 'return')) { goaway($a->get_baseurl() . "/" . $return_path); } killme(); } $owner_xchan = null; $r = q("select * from xchan where xchan_hash = '%s' limit 1", dbesc($channel['channel_hash'])); if ($r && count($r)) { $owner_xchan = $r[0]; } else { logger("mod_item: no owner."); if (x($_REQUEST, 'return')) { goaway($a->get_baseurl() . "/" . $return_path); } killme(); } $walltowall = false; $walltowall_comment = false; if ($remote_xchan) { $observer = $remote_observer; } if ($observer) { logger('mod_item: post accepted from ' . $observer['xchan_name'] . ' for ' . $owner_xchan['xchan_name'], LOGGER_DEBUG); // wall-to-wall detection. // For top-level posts, if the author and owner are different it's a wall-to-wall // For comments, We need to additionally look at the parent and see if it's a wall post that originated locally. if ($observer['xchan_name'] != $owner_xchan['xchan_name']) { if ($parent_item && ($parent_item['item_flags'] & (ITEM_WALL | ITEM_ORIGIN)) == (ITEM_WALL | ITEM_ORIGIN)) { $walltowall_comment = true; $walltowall = true; } if (!$parent) { $walltowall = true; } } } $public_policy = x($_REQUEST, 'public_policy') ? escape_tags($_REQUEST['public_policy']) : map_scope($channel['channel_r_stream'], true); if ($webpage) { $public_policy = ''; } if ($public_policy) { $private = 1; } if ($orig_post) { $private = 0; // webpages are allowed to change ACLs after the fact. Normal conversation items aren't. if ($webpage) { $str_group_allow = perms2str($_REQUEST['group_allow']); $str_contact_allow = perms2str($_REQUEST['contact_allow']); $str_group_deny = perms2str($_REQUEST['group_deny']); $str_contact_deny = perms2str($_REQUEST['contact_deny']); } else { $str_group_allow = $orig_post['allow_gid']; $str_contact_allow = $orig_post['allow_cid']; $str_group_deny = $orig_post['deny_gid']; $str_contact_deny = $orig_post['deny_cid']; $public_policy = $orig_post['public_policy']; $private = $orig_post['item_private']; } if (strlen($str_group_allow) || strlen($str_contact_allow) || strlen($str_group_deny) || strlen($str_contact_deny) || strlen($public_policy) || $private) { $private = 1; } $location = $orig_post['location']; $coord = $orig_post['coord']; $verb = $orig_post['verb']; $app = $orig_post['app']; $title = escape_tags(trim($_REQUEST['title'])); $body = trim($_REQUEST['body']); $item_flags = $orig_post['item_flags']; // force us to recalculate if we need to obscure this post if ($item_flags & ITEM_OBSCURED) { $item_flags = $item_flags ^ ITEM_OBSCURED; } $item_restrict = $orig_post['item_restrict']; $postopts = $orig_post['postopts']; $created = $orig_post['created']; $mid = $orig_post['mid']; $parent_mid = $orig_post['parent_mid']; $plink = $orig_post['plink']; } else { // if coming from the API and no privacy settings are set, // use the user default permissions - as they won't have // been supplied via a form. if ($api_source && !array_key_exists('contact_allow', $_REQUEST) && !array_key_exists('group_allow', $_REQUEST) && !array_key_exists('contact_deny', $_REQUEST) && !array_key_exists('group_deny', $_REQUEST)) { $str_group_allow = $channel['channel_allow_gid']; $str_contact_allow = $channel['channel_allow_cid']; $str_group_deny = $channel['channel_deny_gid']; $str_contact_deny = $channel['channel_deny_cid']; } elseif ($walltowall) { // use the channel owner's default permissions $str_group_allow = $channel['channel_allow_gid']; $str_contact_allow = $channel['channel_allow_cid']; $str_group_deny = $channel['channel_deny_gid']; $str_contact_deny = $channel['channel_deny_cid']; } else { // use the posted permissions $str_group_allow = perms2str($_REQUEST['group_allow']); $str_contact_allow = perms2str($_REQUEST['contact_allow']); $str_group_deny = perms2str($_REQUEST['group_deny']); $str_contact_deny = perms2str($_REQUEST['contact_deny']); } $location = notags(trim($_REQUEST['location'])); $coord = notags(trim($_REQUEST['coord'])); $verb = notags(trim($_REQUEST['verb'])); $title = escape_tags(trim($_REQUEST['title'])); $body = trim($_REQUEST['body']); $body .= trim($_REQUEST['attachment']); $postopts = ''; $private = strlen($str_group_allow) || strlen($str_contact_allow) || strlen($str_group_deny) || strlen($str_contact_deny) || strlen($public_policy) ? 1 : 0; // If this is a comment, set the permissions from the parent. if ($parent_item) { $private = 0; if ($parent_item['item_private'] || strlen($parent_item['allow_cid']) || strlen($parent_item['allow_gid']) || strlen($parent_item['deny_cid']) || strlen($parent_item['deny_gid']) || strlen($parent_item['public_policy'])) { $private = $parent_item['item_private'] ? $parent_item['item_private'] : 1; } $public_policy = $parent_item['public_policy']; $str_contact_allow = $parent_item['allow_cid']; $str_group_allow = $parent_item['allow_gid']; $str_contact_deny = $parent_item['deny_cid']; $str_group_deny = $parent_item['deny_gid']; $owner_hash = $parent_item['owner_xchan']; } if (!strlen($body)) { if ($preview) { killme(); } info(t('Empty post discarded.') . EOL); if (x($_REQUEST, 'return')) { goaway($a->get_baseurl() . "/" . $return_path); } killme(); } } $expires = NULL_DATE; if (feature_enabled($profile_uid, 'content_expire')) { if (x($_REQUEST, 'expire')) { $expires = datetime_convert(date_default_timezone_get(), 'UTC', $_REQUEST['expire']); if ($expires <= datetime_convert()) { $expires = NULL_DATE; } } } $mimetype = notags(trim($_REQUEST['mimetype'])); if (!$mimetype) { $mimetype = 'text/bbcode'; } if ($preview) { $body = z_input_filter($profile_uid, $body, $mimetype); } // Verify ability to use html or php!!! $execflag = false; if ($mimetype === 'application/x-php') { $z = q("select account_id, account_roles, channel_pageflags from account left join channel on channel_account_id = account_id where channel_id = %d limit 1", intval($profile_uid)); if ($z && ($z[0]['account_roles'] & ACCOUNT_ROLE_ALLOWCODE || $z[0]['channel_pageflags'] & PAGE_ALLOWCODE)) { if ($uid && get_account_id() == $z[0]['account_id']) { $execflag = true; } else { notice(t('Executable content type not permitted to this channel.') . EOL); if (x($_REQUEST, 'return')) { goaway($a->get_baseurl() . "/" . $return_path); } killme(); } } } if ($mimetype === 'text/bbcode') { require_once 'include/text.php'; if ($uid && $uid == $profile_uid && feature_enabled($uid, 'markdown')) { require_once 'include/bb2diaspora.php'; $body = escape_tags($body); $body = preg_replace_callback('/\\[share(.*?)\\]/ism', 'share_shield', $body); $body = diaspora2bb($body, true); $body = preg_replace_callback('/\\[share(.*?)\\]/ism', 'share_unshield', $body); } // BBCODE alert: the following functions assume bbcode input // and will require alternatives for alternative content-types (text/html, text/markdown, text/plain, etc.) // we may need virtual or template classes to implement the possible alternatives // Work around doubled linefeeds in Tinymce 3.5b2 // First figure out if it's a status post that would've been // created using tinymce. Otherwise leave it alone. $plaintext = true; // $plaintext = ((feature_enabled($profile_uid,'richtext')) ? false : true); // if((! $parent) && (! $api_source) && (! $plaintext)) { // $body = fix_mce_lf($body); // } // If we're sending a private top-level message with a single @-taggable channel as a recipient, @-tag it, if our pconfig is set. if (!$parent && get_pconfig($profile_uid, 'system', 'tagifonlyrecip') && substr_count($str_contact_allow, '<') == 1 && $str_group_allow == '' && $str_contact_deny == '' && $str_group_deny == '') { $x = q("select abook_id, abook_their_perms from abook where abook_xchan = '%s' and abook_channel = %d limit 1", dbesc(str_replace(array('<', '>'), array('', ''), $str_contact_allow)), intval($profile_uid)); if ($x && $x[0]['abook_their_perms'] & PERMS_W_TAGWALL) { $body .= "\n\n@group+" . $x[0]['abook_id'] . "\n"; } } /** * fix naked links by passing through a callback to see if this is a red site * (already known to us) which will get a zrl, otherwise link with url, add bookmark tag to both. * First protect any url inside certain bbcode tags so we don't double link it. */ $body = preg_replace_callback('/\\[code(.*?)\\[\\/(code)\\]/ism', 'red_escape_codeblock', $body); $body = preg_replace_callback('/\\[url(.*?)\\[\\/(url)\\]/ism', 'red_escape_codeblock', $body); $body = preg_replace_callback('/\\[zrl(.*?)\\[\\/(zrl)\\]/ism', 'red_escape_codeblock', $body); $body = preg_replace_callback("/([^\\]\\='" . '"' . "\\/]|^|\\#\\^)(https?\\:\\/\\/[a-zA-Z0-9\\:\\/\\-\\?\\&\\;\\.\\=\\@\\_\\~\\#\\%\$\\!\\+\\,]+)/ism", 'red_zrl_callback', $body); $body = preg_replace_callback('/\\[\\$b64zrl(.*?)\\[\\/(zrl)\\]/ism', 'red_unescape_codeblock', $body); $body = preg_replace_callback('/\\[\\$b64url(.*?)\\[\\/(url)\\]/ism', 'red_unescape_codeblock', $body); $body = preg_replace_callback('/\\[\\$b64code(.*?)\\[\\/(code)\\]/ism', 'red_unescape_codeblock', $body); // fix any img tags that should be zmg $body = preg_replace_callback('/\\[img(.*?)\\](.*?)\\[\\/img\\]/ism', 'red_zrlify_img_callback', $body); $body = bb_translate_video($body); /** * Fold multi-line [code] sequences */ $body = preg_replace('/\\[\\/code\\]\\s*\\[code\\]/ism', "\n", $body); $body = scale_external_images($body, false); // Look for tags and linkify them $results = linkify_tags($a, $body, $uid ? $uid : $profile_uid); if ($results) { // Set permissions based on tag replacements set_linkified_perms($results, $str_contact_allow, $str_group_allow, $profile_uid, $parent_item, $private); $post_tags = array(); foreach ($results as $result) { $success = $result['success']; if ($success['replaced']) { $post_tags[] = array('uid' => $profile_uid, 'type' => $success['termtype'], 'otype' => TERM_OBJ_POST, 'term' => $success['term'], 'url' => $success['url']); } } } /** * * When a photo was uploaded into the message using the (profile wall) ajax * uploader, The permissions are initially set to disallow anybody but the * owner from seeing it. This is because the permissions may not yet have been * set for the post. If it's private, the photo permissions should be set * appropriately. But we didn't know the final permissions on the post until * now. So now we'll look for links of uploaded photos and attachments that are in the * post and set them to the same permissions as the post itself. * * If the post was end-to-end encrypted we can't find images and attachments in the body, * use our media_str input instead which only contains these elements - but only do this * when encrypted content exists because the photo/attachment may have been removed from * the post and we should keep it private. If it's encrypted we have no way of knowing * so we'll set the permissions regardless and realise that the media may not be * referenced in the post. * * What is preventing us from being able to upload photos into comments is dealing with * the photo and attachment permissions, since we don't always know who was in the * distribution for the top level post. * * We might be able to provide this functionality with a lot of fiddling: * - if the top level post is public (make the photo public) * - if the top level post was written by us or a wall post that belongs to us (match the top level post) * - if the top level post has privacy mentions, add those to the permissions. * - otherwise disallow the photo *or* make the photo public. This is the part that gets messy. */ if (!$preview) { fix_attached_photo_permissions($profile_uid, $owner_xchan['xchan_hash'], strpos($body, '[/crypt]') ? $_POST['media_str'] : $body, $str_contact_allow, $str_group_allow, $str_contact_deny, $str_group_deny); fix_attached_file_permissions($channel, $observer['xchan_hash'], strpos($body, '[/crypt]') ? $_POST['media_str'] : $body, $str_contact_allow, $str_group_allow, $str_contact_deny, $str_group_deny); } $attachments = ''; $match = false; if (preg_match_all('/(\\[attachment\\](.*?)\\[\\/attachment\\])/', $body, $match)) { $attachments = array(); foreach ($match[2] as $mtch) { $hash = substr($mtch, 0, strpos($mtch, ',')); $rev = intval(substr($mtch, strpos($mtch, ','))); $r = attach_by_hash_nodata($hash, $rev); if ($r['success']) { $attachments[] = array('href' => $a->get_baseurl() . '/attach/' . $r['data']['hash'], 'length' => $r['data']['filesize'], 'type' => $r['data']['filetype'], 'title' => urlencode($r['data']['filename']), 'revision' => $r['data']['revision']); } $body = str_replace($match[1], '', $body); } } } // BBCODE end alert if (strlen($categories)) { $cats = explode(',', $categories); foreach ($cats as $cat) { $post_tags[] = array('uid' => $profile_uid, 'type' => TERM_CATEGORY, 'otype' => TERM_OBJ_POST, 'term' => trim($cat), 'url' => $owner_xchan['xchan_url'] . '?f=&cat=' . urlencode(trim($cat))); } } $item_unseen = 1; // determine if this is a wall post if ($parent) { if ($parent_item['item_flags'] & ITEM_WALL) { $item_flags = $item_flags | ITEM_WALL; } } else { if (!$webpage) { $item_flags = $item_flags | ITEM_WALL; } } if ($origin) { $item_flags = $item_flags | ITEM_ORIGIN; } if ($moderated) { $item_restrict = $item_restrict | ITEM_MODERATED; } if ($webpage) { $item_restrict = $item_restrict | $webpage; } if (!strlen($verb)) { $verb = ACTIVITY_POST; } $notify_type = $parent ? 'comment-new' : 'wall-new'; if (!$mid) { $mid = $message_id ? $message_id : item_message_id(); } if (!$parent_mid) { $parent_mid = $mid; } if ($parent_item) { $parent_mid = $parent_item['mid']; } // Fallback so that we alway have a thr_parent if (!$thr_parent) { $thr_parent = $mid; } $datarray = array(); if (!$parent) { $item_flags = $item_flags | ITEM_THREAD_TOP; } if ($consensus) { $item_flags |= ITEM_CONSENSUS; } if (!$plink && $item_flags & ITEM_THREAD_TOP) { $plink = z_root() . '/channel/' . $channel['channel_address'] . '/?f=&mid=' . $mid; } $datarray['aid'] = $channel['channel_account_id']; $datarray['uid'] = $profile_uid; $datarray['owner_xchan'] = $owner_hash ? $owner_hash : $owner_xchan['xchan_hash']; $datarray['author_xchan'] = $observer['xchan_hash']; $datarray['created'] = $created; $datarray['edited'] = $orig_post ? datetime_convert() : $created; $datarray['expires'] = $expires; $datarray['commented'] = $orig_post ? datetime_convert() : $created; $datarray['received'] = $orig_post ? datetime_convert() : $created; $datarray['changed'] = $orig_post ? datetime_convert() : $created; $datarray['mid'] = $mid; $datarray['parent_mid'] = $parent_mid; $datarray['mimetype'] = $mimetype; $datarray['title'] = $title; $datarray['body'] = $body; $datarray['app'] = $app; $datarray['location'] = $location; $datarray['coord'] = $coord; $datarray['verb'] = $verb; $datarray['obj_type'] = $obj_type; $datarray['allow_cid'] = $str_contact_allow; $datarray['allow_gid'] = $str_group_allow; $datarray['deny_cid'] = $str_contact_deny; $datarray['deny_gid'] = $str_group_deny; $datarray['item_private'] = $private; $datarray['attach'] = $attachments; $datarray['thr_parent'] = $thr_parent; $datarray['postopts'] = $postopts; $datarray['item_restrict'] = $item_restrict; $datarray['item_flags'] = $item_flags; $datarray['layout_mid'] = $layout_mid; $datarray['public_policy'] = $public_policy; $datarray['comment_policy'] = map_scope($channel['channel_w_comment']); $datarray['term'] = $post_tags; $datarray['plink'] = $plink; $datarray['route'] = $route; $datarray['item_unseen'] = $item_unseen; // preview mode - prepare the body for display and send it via json if ($preview) { require_once 'include/conversation.php'; $datarray['owner'] = $owner_xchan; $datarray['author'] = $observer; $datarray['attach'] = json_encode($datarray['attach']); $o = conversation($a, array($datarray), 'search', false, 'preview'); // logger('preview: ' . $o, LOGGER_DEBUG); echo json_encode(array('preview' => $o)); killme(); } if ($orig_post) { $datarray['edit'] = true; } call_hooks('post_local', $datarray); if (x($datarray, 'cancel')) { logger('mod_item: post cancelled by plugin.'); if ($return_path) { goaway($a->get_baseurl() . "/" . $return_path); } $json = array('cancel' => 1); if (x($_REQUEST, 'jsreload') && strlen($_REQUEST['jsreload'])) { $json['reload'] = $a->get_baseurl() . '/' . $_REQUEST['jsreload']; } echo json_encode($json); killme(); } if (mb_strlen($datarray['title']) > 255) { $datarray['title'] = mb_substr($datarray['title'], 0, 255); } if (array_key_exists('item_private', $datarray) && $datarray['item_private']) { $datarray['body'] = trim(z_input_filter($datarray['uid'], $datarray['body'], $datarray['mimetype'])); if ($uid) { if ($channel['channel_hash'] === $datarray['author_xchan']) { $datarray['sig'] = base64url_encode(rsa_sign($datarray['body'], $channel['channel_prvkey'])); $datarray['item_flags'] = $datarray['item_flags'] | ITEM_VERIFIED; } } logger('Encrypting local storage'); $key = get_config('system', 'pubkey'); $datarray['item_flags'] = $datarray['item_flags'] | ITEM_OBSCURED; if ($datarray['title']) { $datarray['title'] = json_encode(crypto_encapsulate($datarray['title'], $key)); } if ($datarray['body']) { $datarray['body'] = json_encode(crypto_encapsulate($datarray['body'], $key)); } } if ($orig_post) { $datarray['id'] = $post_id; item_store_update($datarray, $execflag); update_remote_id($channel, $post_id, $webpage, $pagetitle, $namespace, $remote_id, $mid); if (!$nopush) { proc_run('php', "include/notifier.php", 'edit_post', $post_id); } if (x($_REQUEST, 'return') && strlen($return_path)) { logger('return: ' . $return_path); goaway($a->get_baseurl() . "/" . $return_path); } killme(); } else { $post_id = 0; } $post = item_store($datarray, $execflag); $post_id = $post['item_id']; if ($post_id) { logger('mod_item: saved item ' . $post_id); if ($parent) { // only send comment notification if this is a wall-to-wall comment, // otherwise it will happen during delivery if ($datarray['owner_xchan'] != $datarray['author_xchan'] && $parent_item['item_flags'] & ITEM_WALL) { notification(array('type' => NOTIFY_COMMENT, 'from_xchan' => $datarray['author_xchan'], 'to_xchan' => $datarray['owner_xchan'], 'item' => $datarray, 'link' => $a->get_baseurl() . '/display/' . $datarray['mid'], 'verb' => ACTIVITY_POST, 'otype' => 'item', 'parent' => $parent, 'parent_mid' => $parent_item['mid'])); } } else { $parent = $post_id; if ($datarray['owner_xchan'] != $datarray['author_xchan']) { notification(array('type' => NOTIFY_WALL, 'from_xchan' => $datarray['author_xchan'], 'to_xchan' => $datarray['owner_xchan'], 'item' => $datarray, 'link' => $a->get_baseurl() . '/display/' . $datarray['mid'], 'verb' => ACTIVITY_POST, 'otype' => 'item')); } if ($uid && $uid == $profile_uid && !$datarray['item_restrict']) { q("update channel set channel_lastpost = '%s' where channel_id = %d", dbesc(datetime_convert()), intval($uid)); } } // photo comments turn the corresponding item visible to the profile wall // This way we don't see every picture in your new photo album posted to your wall at once. // They will show up as people comment on them. if ($parent_item['item_restrict'] & ITEM_HIDDEN) { $r = q("UPDATE `item` SET `item_restrict` = %d WHERE `id` = %d", intval($parent_item['item_restrict'] - ITEM_HIDDEN), intval($parent_item['id'])); } } else { logger('mod_item: unable to retrieve post that was just stored.'); notice(t('System error. Post not saved.') . EOL); goaway($a->get_baseurl() . "/" . $return_path); // NOTREACHED } if ($parent) { // Store the comment signature information in case we need to relay to Diaspora $ditem = $datarray; $ditem['author'] = $observer; store_diaspora_comment_sig($ditem, $channel, $parent_item, $post_id, $walltowall_comment ? 1 : 0); } update_remote_id($channel, $post_id, $webpage, $pagetitle, $namespace, $remote_id, $mid); $datarray['id'] = $post_id; $datarray['llink'] = $a->get_baseurl() . '/display/' . $channel['channel_address'] . '/' . $post_id; call_hooks('post_local_end', $datarray); if (!$nopush) { proc_run('php', 'include/notifier.php', $notify_type, $post_id); } logger('post_complete'); // figure out how to return, depending on from whence we came if ($api_source) { return $post; } if ($return_path) { goaway($a->get_baseurl() . "/" . $return_path); } $json = array('success' => 1); if (x($_REQUEST, 'jsreload') && strlen($_REQUEST['jsreload'])) { $json['reload'] = $a->get_baseurl() . '/' . $_REQUEST['jsreload']; } logger('post_json: ' . print_r($json, true), LOGGER_DEBUG); echo json_encode($json); killme(); // NOTREACHED }
/** * Render actions localized */ function localize_item(&$item) { if (activity_match($item['verb'], ACTIVITY_LIKE) || activity_match($item['verb'], ACTIVITY_DISLIKE)) { if (!$item['object']) { return; } if ($item['item_flags'] & ITEM_THREAD_TOP) { return; } $obj = json_decode_plus($item['object']); if (!$obj && $item['object']) { logger('localize_item: failed to decode object: ' . print_r($item['object'], true)); } if ($obj['author'] && $obj['author']['link']) { $author_link = get_rel_link($obj['author']['link'], 'alternate'); } else { $author_link = ''; } $author_name = $obj['author'] && $obj['author']['name'] ? $obj['author']['name'] : ''; $item_url = get_rel_link($obj['link'], 'alternate'); $Bphoto = ''; switch ($obj['type']) { case ACTIVITY_OBJ_PHOTO: $post_type = t('photo'); break; case ACTIVITY_OBJ_EVENT: $post_type = t('event'); break; case ACTIVITY_OBJ_PERSON: $post_type = t('channel'); $author_name = $obj['title']; if ($obj['link']) { $author_link = get_rel_link($obj['link'], 'alternate'); $Bphoto = get_rel_link($obj['link'], 'photo'); } break; case ACTIVITY_OBJ_THING: $post_type = $obj['title']; if ($obj['owner']) { if (array_key_exists('name', $obj['owner'])) { $obj['owner']['name']; } if (array_key_exists('link', $obj['owner'])) { $author_link = get_rel_link($obj['owner']['link'], 'alternate'); } } if ($obj['link']) { $Bphoto = get_rel_link($obj['link'], 'photo'); } break; case ACTIVITY_OBJ_NOTE: default: $post_type = t('status'); if ($obj['mid'] != $obj['parent_mid']) { $post_type = t('comment'); } break; } // If we couldn't parse something useful, don't bother translating. // We need something better than zid here, probably magic_link(), but it needs writing if ($author_link && $author_name && $item_url) { $author = '[zrl=' . chanlink_url($item['author']['xchan_url']) . ']' . $item['author']['xchan_name'] . '[/zrl]'; $objauthor = '[zrl=' . chanlink_url($author_link) . ']' . $author_name . '[/zrl]'; $plink = '[zrl=' . zid($item_url) . ']' . $post_type . '[/zrl]'; if (activity_match($item['verb'], ACTIVITY_LIKE)) { $bodyverb = t('%1$s likes %2$s\'s %3$s'); } elseif (activity_match($item['verb'], ACTIVITY_DISLIKE)) { $bodyverb = t('%1$s doesn\'t like %2$s\'s %3$s'); } $item['body'] = $item['localize'] = sprintf($bodyverb, $author, $objauthor, $plink); if ($Bphoto != "") { $item['body'] .= "\n\n\n" . '[zrl=' . chanlink_url($author_link) . '][zmg=80x80]' . $Bphoto . '[/zmg][/zrl]'; } } else { logger('localize_item like failed: link ' . $author_link . ' name ' . $author_name . ' url ' . $item_url); } } if (activity_match($item['verb'], ACTIVITY_FRIEND)) { if ($item['obj_type'] == "" || $item['obj_type'] !== ACTIVITY_OBJ_PERSON) { return; } $Aname = $item['author']['xchan_name']; $Alink = $item['author']['xchan_url']; $obj = json_decode_plus($item['object']); $Blink = $Bphoto = ''; if ($obj['link']) { $Blink = get_rel_link($obj['link'], 'alternate'); $Bphoto = get_rel_link($obj['link'], 'photo'); } $Bname = $obj['title']; $A = '[zrl=' . chanlink_url($Alink) . ']' . $Aname . '[/zrl]'; $B = '[zrl=' . chanlink_url($Blink) . ']' . $Bname . '[/zrl]'; if ($Bphoto != "") { $Bphoto = '[zrl=' . chanlink_url($Blink) . '][zmg=80x80]' . $Bphoto . '[/zmg][/zrl]'; } $item['body'] = $item['localize'] = sprintf(t('%1$s is now connected with %2$s'), $A, $B); $item['body'] .= "\n\n\n" . $Bphoto; } if (stristr($item['verb'], ACTIVITY_POKE)) { /** @FIXME for obscured private posts, until then leave untranslated */ return; $verb = urldecode(substr($item['verb'], strpos($item['verb'], '#') + 1)); if (!$verb) { return; } if ($item['obj_type'] == "" || $item['obj_type'] !== ACTIVITY_OBJ_PERSON) { return; } $Aname = $item['author']['xchan_name']; $Alink = $item['author']['xchan_url']; $obj = json_decode_plus($item['object']); $Blink = $Bphoto = ''; if ($obj['link']) { $Blink = get_rel_link($obj['link'], 'alternate'); $Bphoto = get_rel_link($obj['link'], 'photo'); } $Bname = $obj['title']; $A = '[zrl=' . chanlink_url($Alink) . ']' . $Aname . '[/zrl]'; $B = '[zrl=' . chanlink_url($Blink) . ']' . $Bname . '[/zrl]'; if ($Bphoto != "") { $Bphoto = '[zrl=' . chanlink_url($Blink) . '][zmg=80x80]' . $Bphoto . '[/zmg][/zrl]'; } // we can't have a translation string with three positions but no distinguishable text // So here is the translate string. $txt = t('%1$s poked %2$s'); // now translate the verb $txt = str_replace(t('poked'), t($verb), $txt); // then do the sprintf on the translation string $item['body'] = $item['localize'] = sprintf($txt, $A, $B); $item['body'] .= "\n\n\n" . $Bphoto; } if (stristr($item['verb'], ACTIVITY_MOOD)) { $verb = urldecode(substr($item['verb'], strpos($item['verb'], '#') + 1)); if (!$verb) { return; } $Aname = $item['author']['xchan_name']; $Alink = $item['author']['xchan_url']; $A = '[zrl=' . chanlink_url($Alink) . ']' . $Aname . '[/zrl]'; $txt = t('%1$s is %2$s', 'mood'); $item['body'] = sprintf($txt, $A, t($verb)); } /* // FIXME store parent item as object or target // (and update to json storage) if (activity_match($item['verb'],ACTIVITY_TAG)) { $r = q("SELECT * from `item`,`contact` WHERE `item`.`contact-id`=`contact`.`id` AND `item`.`mid`='%s';", dbesc($item['parent_mid'])); if(count($r)==0) return; $obj=$r[0]; $author = '[zrl=' . zid($item['author-link']) . ']' . $item['author-name'] . '[/zrl]'; $objauthor = '[zrl=' . zid($obj['author-link']) . ']' . $obj['author-name'] . '[/zrl]'; switch($obj['verb']){ case ACTIVITY_POST: switch ($obj['obj_type']){ case ACTIVITY_OBJ_EVENT: $post_type = t('event'); break; default: $post_type = t('status'); } break; default: if($obj['resource_id']){ $post_type = t('photo'); $m=array(); preg_match("/\[[zu]rl=([^]]*)\]/", $obj['body'], $m); $rr['plink'] = $m[1]; } else { $post_type = t('status'); } } $plink = '[zrl=' . $obj['plink'] . ']' . $post_type . '[/zrl]'; $parsedobj = parse_xml_string($xmlhead.$item['object']); $tag = sprintf('#[zrl=%s]%s[/zrl]', $parsedobj->id, $parsedobj->content); $item['body'] = sprintf( t('%1$s tagged %2$s\'s %3$s with %4$s'), $author, $objauthor, $plink, $tag ); } if (activity_match($item['verb'],ACTIVITY_FAVORITE)){ if ($item['obj_type']== "") return; $Aname = $item['author']['xchan_name']; $Alink = $item['author']['xchan_url']; $xmlhead="<"."?xml version='1.0' encoding='UTF-8' ?".">"; $obj = parse_xml_string($xmlhead.$item['object']); if(strlen($obj->id)) { $r = q("select * from item where mid = '%s' and uid = %d limit 1", dbesc($obj->id), intval($item['uid']) ); if(count($r) && $r[0]['plink']) { $target = $r[0]; $Bname = $target['author-name']; $Blink = $target['author-link']; $A = '[zrl=' . zid($Alink) . ']' . $Aname . '[/zrl]'; $B = '[zrl=' . zid($Blink) . ']' . $Bname . '[/zrl]'; $P = '[zrl=' . $target['plink'] . ']' . t('post/item') . '[/zrl]'; $item['body'] = sprintf( t('%1$s marked %2$s\'s %3$s as favorite'), $A, $B, $P)."\n"; } } } */ /* $matches = null; if(strpos($item['body'],'[zrl') !== false) { if(preg_match_all('/@\[zrl=(.*?)\]/is',$item['body'],$matches,PREG_SET_ORDER)) { foreach($matches as $mtch) { if(! strpos($mtch[1],'zid=')) $item['body'] = str_replace($mtch[0],'@[zrl=' . zid($mtch[1]). ']',$item['body']); } } } if(strpos($item['body'],'[zmg') !== false) { // add zid's to public images if(preg_match_all('/\[zrl=(.*?)\/photos\/(.*?)\/image\/(.*?)\]\[zmg(.*?)\]h(.*?)\[\/zmg\]\[\/zrl\]/is',$item['body'],$matches,PREG_SET_ORDER)) { foreach($matches as $mtch) { $item['body'] = str_replace($mtch[0],'[zrl=' . zid( $mtch[1] . '/photos/' . $mtch[2] . '/image/' . $mtch[3]) . '][zmg' . $mtch[4] . ']h' . $mtch[5] . '[/zmg][/zrl]',$item['body']); } } } */ // add sparkle links to appropriate permalinks // $x = stristr($item['plink'],'/display/'); // if($x) { // $sparkle = false; // $y = best_link_url($item,$sparkle,true); // if($sparkle) // $item['plink'] = $y . '?f=&url=' . $item['plink']; // } // if item body was obscured and we changed it, re-obscure it // FIXME - we need a better filter than just the string 'data'; try and // match the fact that it's json encoded if ($item['item_flags'] & ITEM_OBSCURED && strlen($item['body']) && !strpos($item['body'], 'data')) { $item['body'] = json_encode(crypto_encapsulate($item['body'], get_config('system', 'pubkey'))); } }
/** * @brief * * We received a notification packet (in mod/post.php) that a message is waiting for us, and we've verified the sender. * Now send back a pickup message, using our message tracking ID ($arr['secret']), which we will sign with our site private key. * The entire pickup message is encrypted with the remote site's public key. * If everything checks out on the remote end, we will receive back a packet containing one or more messages, * which will be processed and delivered before this function ultimately returns. * * @see zot_import() * * @param array $arr * decrypted and json decoded notify packet from remote site * @return array from zot_import() */ function zot_fetch($arr) { logger('zot_fetch: ' . print_r($arr, true), LOGGER_DATA); $url = $arr['sender']['url'] . $arr['callback']; // set $multiple param on zot_gethub() to return all matching hubs // This allows us to recover from re-installs when a redundant (but invalid) hubloc for // this identity is widely dispersed throughout the network. $ret_hubs = zot_gethub($arr['sender'], true); if (!$ret_hubs) { logger('zot_fetch: no hub: ' . print_r($arr['sender'], true)); return; } foreach ($ret_hubs as $ret_hub) { $data = array('type' => 'pickup', 'url' => z_root(), 'callback_sig' => base64url_encode(rsa_sign(z_root() . '/post', get_config('system', 'prvkey'))), 'callback' => z_root() . '/post', 'secret' => $arr['secret'], 'secret_sig' => base64url_encode(rsa_sign($arr['secret'], get_config('system', 'prvkey')))); $datatosend = json_encode(crypto_encapsulate(json_encode($data), $ret_hub['hubloc_sitekey'])); $fetch = zot_zot($url, $datatosend); $result = zot_import($fetch, $arr['sender']['url']); if ($result) { return $result; } } return; }
function send_message($uid = 0, $recipient = '', $body = '', $subject = '', $replyto = '', $expires = '') { $ret = array('success' => false); $a = get_app(); if (!$recipient) { $ret['message'] = t('No recipient provided.'); return $ret; } if (!strlen($subject)) { $subject = t('[no subject]'); } // if(! $expires) // $expires = NULL_DATE; // else // $expires = datetime_convert(date_default_timezone_get(),'UTC',$expires); if ($uid) { $r = q("select * from channel where channel_id = %d limit 1", intval($uid)); if ($r) { $channel = $r[0]; } } else { $channel = get_app()->get_channel(); } if (!$channel) { $ret['message'] = t('Unable to determine sender.'); return $ret; } // generate a unique message_id do { $dups = false; $hash = random_string(); $mid = $hash . '@' . get_app()->get_hostname(); $r = q("SELECT id FROM mail WHERE mid = '%s' LIMIT 1", dbesc($mid)); if (count($r)) { $dups = true; } } while ($dups == true); if (!strlen($replyto)) { $replyto = $mid; } /** * * When a photo was uploaded into the message using the (profile wall) ajax * uploader, The permissions are initially set to disallow anybody but the * owner from seeing it. This is because the permissions may not yet have been * set for the post. If it's private, the photo permissions should be set * appropriately. But we didn't know the final permissions on the post until * now. So now we'll look for links of uploaded messages that are in the * post and set them to the same permissions as the post itself. * */ $match = null; $images = null; if (preg_match_all("/\\[zmg\\](.*?)\\[\\/zmg\\]/", strpos($body, '[/crypt]') ? $_POST['media_str'] : $body, $match)) { $images = $match[1]; } $match = false; if (preg_match_all("/\\[attachment\\](.*?)\\[\\/attachment\\]/", strpos($body, '[/crypt]') ? $_POST['media_str'] : $body, $match)) { $attaches = $match[1]; } $attachments = ''; if (preg_match_all('/(\\[attachment\\](.*?)\\[\\/attachment\\])/', $body, $match)) { $attachments = array(); foreach ($match[2] as $mtch) { $hash = substr($mtch, 0, strpos($mtch, ',')); $rev = intval(substr($mtch, strpos($mtch, ','))); $r = attach_by_hash_nodata($hash, $rev); if ($r['success']) { $attachments[] = array('href' => $a->get_baseurl() . '/attach/' . $r['data']['hash'], 'length' => $r['data']['filesize'], 'type' => $r['data']['filetype'], 'title' => urlencode($r['data']['filename']), 'revision' => $r['data']['revision']); } $body = str_replace($match[1], '', $body); } } $jattach = $attachments ? json_encode($attachments) : ''; $key = get_config('system', 'pubkey'); if ($subject) { $subject = json_encode(crypto_encapsulate($subject, $key)); } if ($body) { $body = json_encode(crypto_encapsulate($body, $key)); } $r = q("INSERT INTO mail ( account_id, mail_flags, channel_id, from_xchan, to_xchan, title, body, attach, mid, parent_mid, created, expires )\n\t\tVALUES ( %d, %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s' )", intval($channel['channel_account_id']), intval(MAIL_OBSCURED), intval($channel['channel_id']), dbesc($channel['channel_hash']), dbesc($recipient), dbesc($subject), dbesc($body), dbesc($jattach), dbesc($mid), dbesc($replyto), dbesc(datetime_convert()), dbesc($expires)); // verify the save $r = q("SELECT * FROM mail WHERE mid = '%s' and channel_id = %d LIMIT 1", dbesc($mid), intval($channel['channel_id'])); if ($r) { $post_id = $r[0]['id']; } else { $ret['message'] = t('Stored post could not be verified.'); return $ret; } if (count($images)) { foreach ($images as $image) { if (!stristr($image, $a->get_baseurl() . '/photo/')) { continue; } $image_uri = substr($image, strrpos($image, '/') + 1); $image_uri = substr($image_uri, 0, strpos($image_uri, '-')); $r = q("UPDATE photo SET allow_cid = '%s' WHERE resource_id = '%s' AND uid = %d and allow_cid = '%s'", dbesc('<' . $recipient . '>'), dbesc($image_uri), intval($channel['channel_id']), dbesc('<' . $channel['channel_hash'] . '>')); } } if ($attaches) { foreach ($attaches as $attach) { $hash = substr($attach, 0, strpos($attach, ',')); $rev = intval(substr($attach, strpos($attach, ','))); attach_store($channel, $observer_hash, $options = 'update', array('hash' => $hash, 'revision' => $rev, 'allow_cid' => '<' . $recipient . '>')); } } proc_run('php', 'include/notifier.php', 'mail', $post_id); $ret['success'] = true; $ret['message_item'] = intval($post_id); return $ret; }
function zot_reply_pickup($data) { $ret = array('success' => false); /* * The 'pickup' message arrives with a tracking ID which is associated with a particular outq_hash * First verify that that the returned signatures verify, then check that we have an outbound queue item * with the correct hash. * If everything verifies, find any/all outbound messages in the queue for this hubloc and send them back */ if (!$data['secret'] || !$data['secret_sig']) { $ret['message'] = 'no verification signature'; logger('mod_zot: pickup: ' . $ret['message'], LOGGER_DEBUG); json_return_and_die($ret); } $r = q("select distinct hubloc_sitekey from hubloc where hubloc_url = '%s' and hubloc_callback = '%s' and hubloc_sitekey != '' group by hubloc_sitekey ", dbesc($data['url']), dbesc($data['callback'])); if (!$r) { $ret['message'] = 'site not found'; logger('mod_zot: pickup: ' . $ret['message']); json_return_and_die($ret); } foreach ($r as $hubsite) { // verify the url_sig // If the server was re-installed at some point, there could be multiple hubs with the same url and callback. // Only one will have a valid key. $forgery = true; $secret_fail = true; $sitekey = $hubsite['hubloc_sitekey']; logger('mod_zot: Checking sitekey: ' . $sitekey, LOGGER_DATA, LOG_DEBUG); if (rsa_verify($data['callback'], base64url_decode($data['callback_sig']), $sitekey)) { $forgery = false; } if (rsa_verify($data['secret'], base64url_decode($data['secret_sig']), $sitekey)) { $secret_fail = false; } if (!$forgery && !$secret_fail) { break; } } if ($forgery) { $ret['message'] = 'possible site forgery'; logger('mod_zot: pickup: ' . $ret['message']); json_return_and_die($ret); } if ($secret_fail) { $ret['message'] = 'secret validation failed'; logger('mod_zot: pickup: ' . $ret['message']); json_return_and_die($ret); } /* * If we made it to here, the signatures verify, but we still don't know if the tracking ID is valid. * It wouldn't be an error if the tracking ID isn't found, because we may have sent this particular * queue item with another pickup (after the tracking ID for the other pickup was verified). */ $r = q("select outq_posturl from outq where outq_hash = '%s' and outq_posturl = '%s' limit 1", dbesc($data['secret']), dbesc($data['callback'])); if (!$r) { $ret['message'] = 'nothing to pick up'; logger('mod_zot: pickup: ' . $ret['message']); json_return_and_die($ret); } /* * Everything is good if we made it here, so find all messages that are going to this location * and send them all. */ $r = q("select * from outq where outq_posturl = '%s'", dbesc($data['callback'])); if ($r) { logger('mod_zot: successful pickup message received from ' . $data['callback'] . ' ' . count($r) . ' message(s) picked up', LOGGER_DEBUG); $ret['success'] = true; $ret['pickup'] = array(); foreach ($r as $rr) { if ($rr['outq_msg']) { $x = json_decode($rr['outq_msg'], true); if (!$x) { continue; } if (is_array($x) && array_key_exists('message_list', $x)) { foreach ($x['message_list'] as $xx) { $ret['pickup'][] = array('notify' => json_decode($rr['outq_notify'], true), 'message' => $xx); } } else { $ret['pickup'][] = array('notify' => json_decode($rr['outq_notify'], true), 'message' => $x); } remove_queue_item($rr['outq_hash']); } } } $encrypted = crypto_encapsulate(json_encode($ret), $sitekey); json_return_and_die($encrypted); /* pickup: end */ }
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'] . ' for channel ' . $importer['channel_name']); return; } if (!$importer['system'] && !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'; // set the route to that of the parent so downstream hubs won't reject it. $arr['route'] = $parent_item['route']; $arr['item_private'] = $parent_item['item_private']; $arr['verb'] = $activity; $arr['obj_type'] = $objtype; $arr['object'] = $object; if (!$parent_author_signature) { $key = get_config('system', 'pubkey'); $x = array('signer' => $diaspora_handle, 'body' => $text, 'signed_text' => $signed_data, 'signature' => base64_encode($author_signature)); $arr['diaspora_meta'] = json_encode(crypto_encapsulate(json_encode($x), $key)); } $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 post_post(&$a) * zot communications and messaging * * Sender HTTP posts to this endpoint ($site/post typically) with 'data' parameter set to json zot message packet. * This packet is optionally encrypted, which we will discover if the json has an 'iv' element. * $contents => array( 'alg' => 'aes256cbc', 'iv' => initialisation vector, 'key' => decryption key, 'data' => encrypted data); * $contents->iv and $contents->key are random strings encrypted with this site's RSA public key and then base64url encoded. * Currently only 'aes256cbc' is used, but this is extensible should that algorithm prove inadequate. * * Once decrypted, one will find the normal json_encoded zot message packet. * * Defined packet types are: notify, purge, refresh, force_refresh, auth_check, ping, and pickup * * Standard packet: (used by notify, purge, refresh, force_refresh, and auth_check) * * { * "type": "notify", * "sender":{ * "guid":"kgVFf_1...", * "guid_sig":"PT9-TApzp...", * "url":"http:\/\/podunk.edu", * "url_sig":"T8Bp7j5...", * }, * "recipients": { optional recipient array }, * "callback":"\/post", * "version":1, * "secret":"1eaa...", * "secret_sig": "df89025470fac8..." * } * * Signature fields are all signed with the sender channel private key and base64url encoded. * Recipients are arrays of guid and guid_sig, which were previously signed with the recipients private * key and base64url encoded and later obtained via channel discovery. Absence of recipients indicates * a public message or visible to all potential listeners on this site. * * "pickup" packet: * The pickup packet is sent in response to a notify packet from another site * * { * "type":"pickup", * "url":"http:\/\/example.com", * "callback":"http:\/\/example.com\/post", * "callback_sig":"teE1_fLI...", * "secret":"1eaa...", * "secret_sig":"O7nB4_..." * } * * In the pickup packet, the sig fields correspond to the respective data element signed with this site's system * private key and then base64url encoded. * The "secret" is the same as the original secret from the notify packet. * * If verification is successful, a json structure is returned * containing a success indicator and an array of type 'pickup'. * Each pickup element contains the original notify request and a message field whose contents are * dependent on the message type * * This JSON array is AES encapsulated using the site public key of the site that sent the initial zot pickup packet. * Using the above example, this would be example.com. * * * { * "success":1, * "pickup":{ * "notify":{ * "type":"notify", * "sender":{ * "guid":"kgVFf_...", * "guid_sig":"PT9-TApz...", * "url":"http:\/\/z.podunk.edu", * "url_sig":"T8Bp7j5D..." * }, * "callback":"\/post", * "version":1, * "secret":"1eaa661..." * }, * "message":{ * "type":"activity", * "message_id":"*****@*****.**", * "message_top":"*****@*****.**", * "message_parent":"*****@*****.**", * "created":"2012-11-20 04:04:16", * "edited":"2012-11-20 04:04:16", * "title":"", * "body":"Hi Nickordo", * "app":"", * "verb":"post", * "object_type":"", * "target_type":"", * "permalink":"", * "location":"", * "longlat":"", * "owner":{ * "name":"Indigo", * "address":"*****@*****.**", * "url":"http:\/\/podunk.edu", * "photo":{ * "mimetype":"image\/jpeg", * "src":"http:\/\/podunk.edu\/photo\/profile\/m\/5" * }, * "guid":"kgVFf_...", * "guid_sig":"PT9-TAp...", * }, * "author":{ * "name":"Indigo", * "address":"*****@*****.**", * "url":"http:\/\/podunk.edu", * "photo":{ * "mimetype":"image\/jpeg", * "src":"http:\/\/podunk.edu\/photo\/profile\/m\/5" * }, * "guid":"kgVFf_...", * "guid_sig":"PT9-TAp..." * } * } * } *} * * Currently defined message types are 'activity', 'mail', 'profile' and 'channel_sync', which each have * different content schemas. * * Ping packet: * A ping packet does not require any parameters except the type. It may or may not be encrypted. * * { * "type": "ping" * } * * On receipt of a ping packet a ping response will be returned: * * { * "success" : 1, * "site" { * "url":"http:\/\/podunk.edu", * "url_sig":"T8Bp7j5...", * "sitekey": "-----BEGIN PUBLIC KEY----- * MIICIjANBgkqhkiG9w0BAQE..." * } * } * * The ping packet can be used to verify that a site has not been re-installed, and to * initiate corrective action if it has. The url_sig is signed with the site private key * and base64url encoded - and this should verify with the enclosed sitekey. Failure to * verify indicates the site is corrupt or otherwise unable to communicate using zot. * This return packet is not otherwise verified, so should be compared with other * results obtained from this site which were verified prior to taking action. For instance * if you have one verified result with this signature and key, and other records for this * url which have different signatures and keys, it indicates that the site was re-installed * and corrective action may commence (remove or mark invalid any entries with different * signatures). * If you have no records which match this url_sig and key - no corrective action should * be taken as this packet may have been returned by an imposter. * */ function post_post(&$a) { $encrypted_packet = false; $ret = array('success' => false); $data = json_decode($_REQUEST['data'], true); /** * Many message packets will arrive encrypted. The existence of an 'iv' element * tells us we need to unencapsulate the AES-256-CBC content using the site private key */ if (array_key_exists('iv', $data)) { $encrypted_packet = true; $data = crypto_unencapsulate($data, get_config('system', 'prvkey')); logger('mod_zot: decrypt1: ' . $data, LOGGER_DATA); $data = json_decode($data, true); } if (!$data) { // possible Bleichenbacher's attack, just treat it as a // message we have no handler for. It should fail a bit // further along with "no hub". Our public key is public // knowledge. There's no reason why anybody should get the // encryption wrong unless they're fishing or hacking. If // they're developing and made a goof, this can be discovered // in the logs of the destination site. If they're fishing or // hacking, the bottom line is we can't verify their hub. // That's all we're going to tell them. $data = array('type' => 'bogus'); } $msgtype = array_key_exists('type', $data) ? $data['type'] : ''; if ($msgtype === 'ping') { // Useful to get a health check on a remote site. // This will let us know if any important communication details // that we may have stored are no longer valid, regardless of xchan details. logger('POST: got ping send pong now back: ' . z_root(), LOGGER_DEBUG); $ret['success'] = true; $ret['site'] = array(); $ret['site']['url'] = z_root(); $ret['site']['url_sig'] = base64url_encode(rsa_sign(z_root(), get_config('system', 'prvkey'))); $ret['site']['sitekey'] = get_config('system', 'pubkey'); json_return_and_die($ret); } if ($msgtype === 'pickup') { /** * The 'pickup' message arrives with a tracking ID which is associated with a particular outq_hash * First verify that that the returned signatures verify, then check that we have an outbound queue item * with the correct hash. * If everything verifies, find any/all outbound messages in the queue for this hubloc and send them back * */ if (!$data['secret'] || !$data['secret_sig']) { $ret['message'] = 'no verification signature'; logger('mod_zot: pickup: ' . $ret['message'], LOGGER_DEBUG); json_return_and_die($ret); } $r = q("select distinct hubloc_sitekey from hubloc where hubloc_url = '%s' and hubloc_callback = '%s' and hubloc_sitekey != '' group by hubloc_sitekey ", dbesc($data['url']), dbesc($data['callback'])); if (!$r) { $ret['message'] = 'site not found'; logger('mod_zot: pickup: ' . $ret['message']); json_return_and_die($ret); } foreach ($r as $hubsite) { // verify the url_sig // If the server was re-installed at some point, there could be multiple hubs with the same url and callback. // Only one will have a valid key. $forgery = true; $secret_fail = true; $sitekey = $hubsite['hubloc_sitekey']; logger('mod_zot: Checking sitekey: ' . $sitekey, LOGGER_DATA); if (rsa_verify($data['callback'], base64url_decode($data['callback_sig']), $sitekey)) { $forgery = false; } if (rsa_verify($data['secret'], base64url_decode($data['secret_sig']), $sitekey)) { $secret_fail = false; } if (!$forgery && !$secret_fail) { break; } } if ($forgery) { $ret['message'] = 'possible site forgery'; logger('mod_zot: pickup: ' . $ret['message']); json_return_and_die($ret); } if ($secret_fail) { $ret['message'] = 'secret validation failed'; logger('mod_zot: pickup: ' . $ret['message']); json_return_and_die($ret); } /** * If we made it to here, the signatures verify, but we still don't know if the tracking ID is valid. * It wouldn't be an error if the tracking ID isn't found, because we may have sent this particular * queue item with another pickup (after the tracking ID for the other pickup was verified). */ $r = q("select outq_posturl from outq where outq_hash = '%s' and outq_posturl = '%s' limit 1", dbesc($data['secret']), dbesc($data['callback'])); if (!$r) { $ret['message'] = 'nothing to pick up'; logger('mod_zot: pickup: ' . $ret['message']); json_return_and_die($ret); } /** * Everything is good if we made it here, so find all messages that are going to this location * and send them all. */ $r = q("select * from outq where outq_posturl = '%s'", dbesc($data['callback'])); if ($r) { logger('mod_zot: succesful pickup message received from ' . $data['callback'] . ' ' . count($r) . ' message(s) picked up', LOGGER_DEBUG); $ret['success'] = true; $ret['pickup'] = array(); foreach ($r as $rr) { $ret['pickup'][] = array('notify' => json_decode($rr['outq_notify'], true), 'message' => json_decode($rr['outq_msg'], true)); $x = q("delete from outq where outq_hash = '%s' limit 1", dbesc($rr['outq_hash'])); } } $encrypted = crypto_encapsulate(json_encode($ret), $sitekey); json_return_and_die($encrypted); /** pickup: end */ } /** * All other message types require us to verify the sender. This is a generic check, so we * will do it once here and bail if anything goes wrong. */ if (array_key_exists('sender', $data)) { $sender = $data['sender']; } /** Check if the sender is already verified here */ $hub = zot_gethub($sender); if (!$hub) { /** Have never seen this guid or this guid coming from this location. Check it and register it. */ // (!!) this will validate the sender $result = zot_register_hub($sender); if (!$result['success'] || !($hub = zot_gethub($sender))) { $ret['message'] = 'Hub not available.'; logger('mod_zot: no hub'); json_return_and_die($ret); } } // Update our DB to show when we last communicated successfully with this hub // This will allow us to prune dead hubs from using up resources $r = q("update hubloc set hubloc_connected = '%s' where hubloc_id = %d limit 1", dbesc(datetime_convert()), intval($hub['hubloc_id'])); // a dead hub came back to life - reset any tombstones we might have if ($hub['hubloc_status'] & HUBLOC_OFFLINE) { q("update hubloc set hubloc_status = (hubloc_status ^ %d) where hubloc_id = %d limit 1", intval(HUBLOC_OFFLINE), intval($hub['hubloc_id'])); if ($r[0]['hubloc_flags'] & HUBLOC_FLAGS_ORPHANCHECK) { q("update hubloc set hubloc_flags = (hubloc_flags ^ %d) where hubloc_id = %d limit 1", intval(HUBLOC_FLAGS_ORPHANCHECK), intval($hub['hubloc_id'])); } q("update xchan set xchan_flags = (xchan_flags ^ %d) where (xchan_flags & %d) and xchan_hash = '%s' limit 1", intval(XCHAN_FLAGS_ORPHAN), intval(XCHAN_FLAGS_ORPHAN), dbesc($hub['hubloc_hash'])); } /** * This hub has now been proven to be valid. * Any hub with the same URL and a different sitekey cannot be valid. * Get rid of them (mark them deleted). There's a good chance they were re-installs. * */ q("update hubloc set hubloc_flags = ( hubloc_flags | %d ) where hubloc_url = '%s' and hubloc_sitekey != '%s' ", intval(HUBLOC_FLAGS_DELETED), dbesc($hub['hubloc_url']), dbesc($hub['hubloc_sitekey'])); // TODO: check which hub is primary and take action if mismatched if (array_key_exists('recipients', $data)) { $recipients = $data['recipients']; } if ($msgtype === 'auth_check') { /** * Requestor visits /magic/?dest=somewhere on their own site with a browser * magic redirects them to $destsite/post [with auth args....] * $destsite sends an auth_check packet to originator site * The auth_check packet is handled here by the originator's site * - the browser session is still waiting * inside $destsite/post for everything to verify * If everything checks out we'll return a token to $destsite * and then $destsite will verify the token, authenticate the browser * session and then redirect to the original destination. * If authentication fails, the redirection to the original destination * will still take place but without authentication. */ logger('mod_zot: auth_check', LOGGER_DEBUG); if (!$encrypted_packet) { logger('mod_zot: auth_check packet was not encrypted.'); $ret['message'] .= 'no packet encryption' . EOL; json_return_and_die($ret); } $arr = $data['sender']; $sender_hash = make_xchan_hash($arr['guid'], $arr['guid_sig']); // garbage collect any old unused notifications q("delete from verify where type = 'auth' and created < UTC_TIMESTAMP() - INTERVAL 10 MINUTE"); $y = q("select xchan_pubkey from xchan where xchan_hash = '%s' limit 1", dbesc($sender_hash)); // We created a unique hash in mod/magic.php when we invoked remote auth, and stored it in // the verify table. It is now coming back to us as 'secret' and is signed by a channel at the other end. // First verify their signature. We will have obtained a zot-info packet from them as part of the sender // verification. if (!$y || !rsa_verify($data['secret'], base64url_decode($data['secret_sig']), $y[0]['xchan_pubkey'])) { logger('mod_zot: auth_check: sender not found or secret_sig invalid.'); $ret['message'] .= 'sender not found or sig invalid ' . print_r($y, true) . EOL; json_return_and_die($ret); } // There should be exactly one recipient, the original auth requestor $ret['message'] .= 'recipients ' . print_r($recipients, true) . EOL; if ($data['recipients']) { $arr = $data['recipients'][0]; $recip_hash = make_xchan_hash($arr['guid'], $arr['guid_sig']); $c = q("select channel_id, channel_account_id, channel_prvkey from channel where channel_hash = '%s' limit 1", dbesc($recip_hash)); if (!$c) { logger('mod_zot: auth_check: recipient channel not found.'); $ret['message'] .= 'recipient not found.' . EOL; json_return_and_die($ret); } $confirm = base64url_encode(rsa_sign($data['secret'] . $recip_hash, $c[0]['channel_prvkey'])); // This additionally checks for forged sites since we already stored the expected result in meta // and we've already verified that this is them via zot_gethub() and that their key signed our token $z = q("select id from verify where channel = %d and type = 'auth' and token = '%s' and meta = '%s' limit 1", intval($c[0]['channel_id']), dbesc($data['secret']), dbesc($data['sender']['url'])); if (!$z) { logger('mod_zot: auth_check: verification key not found.'); $ret['message'] .= 'verification key not found' . EOL; json_return_and_die($ret); } $r = q("delete from verify where id = %d limit 1", intval($z[0]['id'])); $u = q("select account_service_class from account where account_id = %d limit 1", intval($c[0]['channel_account_id'])); logger('mod_zot: auth_check: success', LOGGER_DEBUG); $ret['success'] = true; $ret['confirm'] = $confirm; if ($u && $u[0]['account_service_class']) { $ret['service_class'] = $u[0]['account_service_class']; } // Set "do not track" flag if this site or this channel's profile is restricted if (intval(get_config('system', 'block_public'))) { $ret['DNT'] = true; } if (!perm_is_allowed($c[0]['channel_id'], '', 'view_profile')) { $ret['DNT'] = true; } if (get_pconfig($c[0]['channel_id'], 'system', 'do_not_track')) { $ret['DNT'] = true; } json_return_and_die($ret); } json_return_and_die($ret); } if ($msgtype === 'purge') { if ($recipients) { // basically this means "unfriend" foreach ($recipients as $recip) { $r = q("select channel.*,xchan.* from channel \n\t\t\t\t\tleft join xchan on channel_hash = xchan_hash\n\t\t\t\t\twhere channel_guid = '%s' and channel_guid_sig = '%s' limit 1", dbesc($recip['guid']), dbesc($recip['guid_sig'])); if ($r) { $r = q("select abook_id from abook where uid = %d and abook_xchan = '%s' limit 1", intval($r[0]['channel_id']), dbesc(make_xchan_hash($sender['guid'], $sender['guid_sig']))); if ($r) { contact_remove($r[0]['channel_id'], $r[0]['abook_id']); } } } } else { // Unfriend everybody - basically this means the channel has committed suicide $arr = $data['sender']; $sender_hash = make_xchan_hash($arr['guid'], $arr['guid_sig']); require_once 'include/Contact.php'; remove_all_xchan_resources($sender_hash); $ret['success'] = true; json_return_and_die($ret); } } if ($msgtype === 'refresh' || $msgtype === 'force_refresh') { // remote channel info (such as permissions or photo or something) // has been updated. Grab a fresh copy and sync it. // The difference between refresh and force_refresh is that // force_refresh unconditionally creates a directory update record, // even if no changes were detected upon processing. if ($recipients) { // This would be a permissions update, typically for one connection foreach ($recipients as $recip) { $r = q("select channel.*,xchan.* from channel \n\t\t\t\t\tleft join xchan on channel_hash = xchan_hash\n\t\t\t\t\twhere channel_guid = '%s' and channel_guid_sig = '%s' limit 1", dbesc($recip['guid']), dbesc($recip['guid_sig'])); $x = zot_refresh(array('xchan_guid' => $sender['guid'], 'xchan_guid_sig' => $sender['guid_sig'], 'hubloc_url' => $sender['url']), $r[0], $msgtype === 'force_refresh' ? true : false); } } else { // system wide refresh $x = zot_refresh(array('xchan_guid' => $sender['guid'], 'xchan_guid_sig' => $sender['guid_sig'], 'hubloc_url' => $sender['url']), null, $msgtype === 'force_refresh' ? true : false); } $ret['success'] = true; json_return_and_die($ret); } if ($msgtype === 'notify') { $async = get_config('system', 'queued_fetch'); if ($async) { // add to receive queue // qreceive_add($data); } else { $x = zot_fetch($data); $ret['delivery_report'] = $x; } $ret['success'] = true; json_return_and_die($ret); } // catchall json_return_and_die($ret); }
function zfinger_init(&$a) { require_once 'include/zot.php'; require_once 'include/crypto.php'; $ret = array('success' => false); $zhash = x($_REQUEST, 'guid_hash') ? $_REQUEST['guid_hash'] : ''; $zguid = x($_REQUEST, 'guid') ? $_REQUEST['guid'] : ''; $zguid_sig = x($_REQUEST, 'guid_sig') ? $_REQUEST['guid_sig'] : ''; $zaddr = x($_REQUEST, 'address') ? $_REQUEST['address'] : ''; $ztarget = x($_REQUEST, 'target') ? $_REQUEST['target'] : ''; $zsig = x($_REQUEST, 'target_sig') ? $_REQUEST['target_sig'] : ''; $zkey = x($_REQUEST, 'key') ? $_REQUEST['key'] : ''; $mindate = x($_REQUEST, 'mindate') ? $_REQUEST['mindate'] : ''; $feed = x($_REQUEST, 'feed') ? intval($_REQUEST['feed']) : 0; if ($ztarget) { if (!$zkey || !$zsig || !rsa_verify($ztarget, base64url_decode($zsig), $zkey)) { logger('zfinger: invalid target signature'); $ret['message'] = t("invalid target signature"); json_return_and_die($ret); } } // allow re-written domains so bob@foo.example.com can provide an address of bob@example.com // The top-level domain also needs to redirect .well-known/zot-info to the sub-domain with a 301 or 308 // TODO: Make 308 work in include/network.php for zot_fetch_url and zot_post_url if ($zaddr && ($s = get_config('system', 'zotinfo_domainrewrite'))) { $arr = explode('^', $s); if (count($arr) == 2) { $zaddr = str_replace($arr[0], $arr[1], $zaddr); } } $r = null; if (strlen($zhash)) { $r = q("select channel.*, xchan.* from channel left join xchan on channel_hash = xchan_hash \n\t\t\twhere channel_hash = '%s' limit 1", dbesc($zhash)); } elseif (strlen($zguid) && strlen($zguid_sig)) { $r = q("select channel.*, xchan.* from channel left join xchan on channel_hash = xchan_hash \n\t\t\twhere channel_guid = '%s' and channel_guid_sig = '%s' limit 1", dbesc($zguid), dbesc($zguid_sig)); } elseif (strlen($zaddr)) { if (strpos($zaddr, '[system]') === false) { /* normal address lookup */ $r = q("select channel.*, xchan.* from channel left join xchan on channel_hash = xchan_hash\n\t\t\t\twhere ( channel_address = '%s' or xchan_addr = '%s' ) limit 1", dbesc($zaddr), dbesc($zaddr)); } else { /** * The special address '[system]' will return a system channel if one has been defined, * Or the first valid channel we find if there are no system channels. * * This is used by magic-auth if we have no prior communications with this site - and * returns an identity on this site which we can use to create a valid hub record so that * we can exchange signed messages. The precise identity is irrelevant. It's the hub * information that we really need at the other end - and this will return it. * */ $r = q("select channel.*, xchan.* from channel left join xchan on channel_hash = xchan_hash\n\t\t\t\twhere ( channel_pageflags & %d ) order by channel_id limit 1", intval(PAGE_SYSTEM)); if (!$r) { $r = q("select channel.*, xchan.* from channel left join xchan on channel_hash = xchan_hash\n\t\t\t\t\twhere not ( channel_pageflags & %d ) order by channel_id limit 1", intval(PAGE_REMOVED)); } } } else { $ret['message'] = 'Invalid request'; json_return_and_die($ret); } if (!$r) { $ret['message'] = 'Item not found.'; json_return_and_die($ret); } $e = $r[0]; $id = $e['channel_id']; $special_channel = $e['channel_pageflags'] & PAGE_PREMIUM ? true : false; $adult_channel = $e['channel_pageflags'] & PAGE_ADULT ? true : false; $censored = $e['channel_pageflags'] & PAGE_CENSORED ? true : false; $searchable = $e['channel_pageflags'] & PAGE_HIDDEN ? false : true; $deleted = $e['xchan_flags'] & XCHAN_FLAGS_DELETED ? true : false; if ($deleted || $censored) { $searchable = false; } // This is for birthdays and keywords, but must check access permissions $p = q("select * from profile where uid = %d and is_default = 1", intval($e['channel_id'])); $profile = array(); if ($p) { if (!intval($p[0]['publish'])) { $searchable = false; } $profile['description'] = $p[0]['pdesc']; $profile['birthday'] = $p[0]['dob']; if ($profile['birthday'] != '0000-00-00' && ($bd = z_birthday($p[0]['dob'], $e['channel_timezone'])) !== '') { $profile['next_birthday'] = $bd; } if ($age = age($p[0]['dob'], $e['channel_timezone'], '')) { $profile['age'] = $age; } $profile['gender'] = $p[0]['gender']; $profile['marital'] = $p[0]['marital']; $profile['sexual'] = $p[0]['sexual']; $profile['locale'] = $p[0]['locality']; $profile['region'] = $p[0]['region']; $profile['postcode'] = $p[0]['postal_code']; $profile['country'] = $p[0]['country_name']; $profile['about'] = $p[0]['about']; $profile['homepage'] = $p[0]['homepage']; $profile['hometown'] = $p[0]['hometown']; if ($p[0]['keywords']) { $tags = array(); $k = explode(' ', $p[0]['keywords']); if ($k) { foreach ($k as $kk) { if (trim($kk, " \t\n\r\v,")) { $tags[] = trim($kk, " \t\n\r\v,"); } } } if ($tags) { $profile['keywords'] = $tags; } } } $ret['success'] = true; // Communication details $ret['guid'] = $e['xchan_guid']; $ret['guid_sig'] = $e['xchan_guid_sig']; $ret['key'] = $e['xchan_pubkey']; $ret['name'] = $e['xchan_name']; $ret['name_updated'] = $e['xchan_name_date']; $ret['address'] = $e['xchan_addr']; $ret['photo_mimetype'] = $e['xchan_photo_mimetype']; $ret['photo'] = $e['xchan_photo_l']; $ret['photo_updated'] = $e['xchan_photo_date']; $ret['url'] = $e['xchan_url']; $ret['connections_url'] = $e['xchan_connurl'] ? $e['xchan_connurl'] : z_root() . '/poco/' . $e['channel_address']; $ret['target'] = $ztarget; $ret['target_sig'] = $zsig; $ret['searchable'] = $searchable; $ret['adult_content'] = $adult_channel; if ($deleted) { $ret['deleted'] = $deleted; } // premium or other channel desiring some contact with potential followers before connecting. // This is a template - %s will be replaced with the follow_url we discover for the return channel. if ($special_channel) { $ret['connect_url'] = z_root() . '/connect/' . $e['channel_address']; } // This is a template for our follow url, %s will be replaced with a webbie $ret['follow_url'] = z_root() . '/follow?f=&url=%s'; $ztarget_hash = $ztarget && $zsig ? make_xchan_hash($ztarget, $zsig) : ''; $permissions = get_all_perms($e['channel_id'], $ztarget_hash, false); if ($ztarget_hash) { $permissions['connected'] = false; $b = q("select * from abook where abook_xchan = '%s' and abook_channel = %d limit 1", dbesc($ztarget_hash), intval($e['channel_id'])); if ($b) { $permissions['connected'] = true; } } $ret['permissions'] = $ztarget && $zkey ? crypto_encapsulate(json_encode($permissions), $zkey) : $permissions; if ($permissions['view_profile']) { $ret['profile'] = $profile; } // array of (verified) hubs this channel uses $ret['locations'] = array(); $x = zot_get_hublocs($e['channel_hash']); if ($x && count($x)) { foreach ($x as $hub) { if (!($hub['hubloc_flags'] & HUBLOC_FLAGS_UNVERIFIED)) { $ret['locations'][] = array('host' => $hub['hubloc_host'], 'address' => $hub['hubloc_addr'], 'primary' => $hub['hubloc_flags'] & HUBLOC_FLAGS_PRIMARY ? true : false, 'url' => $hub['hubloc_url'], 'url_sig' => $hub['hubloc_url_sig'], 'callback' => $hub['hubloc_callback'], 'sitekey' => $hub['hubloc_sitekey'], 'deleted' => $hub['hubloc_flags'] & HUBLOC_FLAGS_DELETED ? true : false); } } } $ret['site'] = array(); $ret['site']['url'] = z_root(); $ret['site']['url_sig'] = base64url_encode(rsa_sign(z_root(), $e['channel_prvkey'])); $dirmode = get_config('system', 'directory_mode'); if ($dirmode === false || $dirmode == DIRECTORY_MODE_NORMAL) { $ret['site']['directory_mode'] = 'normal'; } if ($dirmode == DIRECTORY_MODE_PRIMARY) { $ret['site']['directory_mode'] = 'primary'; } elseif ($dirmode == DIRECTORY_MODE_SECONDARY) { $ret['site']['directory_mode'] = 'secondary'; } elseif ($dirmode == DIRECTORY_MODE_STANDALONE) { $ret['site']['directory_mode'] = 'standalone'; } if ($dirmode != DIRECTORY_MODE_NORMAL) { $ret['site']['directory_url'] = z_root() . '/dirsearch'; } // hide detailed site information if you're off the grid if ($dirmode != DIRECTORY_MODE_STANDALONE) { $register_policy = intval(get_config('system', 'register_policy')); if ($register_policy == REGISTER_CLOSED) { $ret['site']['register_policy'] = 'closed'; } if ($register_policy == REGISTER_APPROVE) { $ret['site']['register_policy'] = 'approve'; } if ($register_policy == REGISTER_OPEN) { $ret['site']['register_policy'] = 'open'; } $access_policy = intval(get_config('system', 'access_policy')); if ($access_policy == ACCESS_PRIVATE) { $ret['site']['access_policy'] = 'private'; } if ($access_policy == ACCESS_PAID) { $ret['site']['access_policy'] = 'paid'; } if ($access_policy == ACCESS_FREE) { $ret['site']['access_policy'] = 'free'; } if ($access_policy == ACCESS_TIERED) { $ret['site']['access_policy'] = 'tiered'; } $ret['site']['accounts'] = account_total(); require_once 'include/identity.php'; $ret['site']['channels'] = channel_total(); $ret['site']['version'] = RED_PLATFORM . ' ' . RED_VERSION . '[' . DB_UPDATE_VERSION . ']'; $ret['site']['admin'] = get_config('system', 'admin_email'); $visible_plugins = array(); if (is_array($a->plugins) && count($a->plugins)) { $r = q("select * from addon where hidden = 0"); if ($r) { foreach ($r as $rr) { $visible_plugins[] = $rr['name']; } } } $ret['site']['plugins'] = $visible_plugins; $ret['site']['sitehash'] = get_config('system', 'location_hash'); $ret['site']['sitename'] = get_config('system', 'sitename'); $ret['site']['sellpage'] = get_config('system', 'sellpage'); $ret['site']['location'] = get_config('system', 'site_location'); $ret['site']['realm'] = get_directory_realm(); } call_hooks('zot_finger', $ret); json_return_and_die($ret); }
/** * @brief * * We received a notification packet (in mod/post.php) that a message is waiting for us, and we've verified the sender. * Now send back a pickup message, using our message tracking ID ($arr['secret']), which we will sign with our site private key. * The entire pickup message is encrypted with the remote site's public key. * If everything checks out on the remote end, we will receive back a packet containing one or more messages, * which will be processed and delivered before this function ultimately returns. * * @see zot_import() * * @param array $arr * decrypted and json decoded notify packet from remote site * @return array from zot_import() */ function zot_fetch($arr) { logger('zot_fetch: ' . print_r($arr, true), LOGGER_DATA); $url = $arr['sender']['url'] . $arr['callback']; $ret_hub = zot_gethub($arr['sender']); if (!$ret_hub) { logger('zot_fetch: no hub: ' . print_r($arr['sender'], true)); return; } $data = array('type' => 'pickup', 'url' => z_root(), 'callback_sig' => base64url_encode(rsa_sign(z_root() . '/post', get_config('system', 'prvkey'))), 'callback' => z_root() . '/post', 'secret' => $arr['secret'], 'secret_sig' => base64url_encode(rsa_sign($arr['secret'], get_config('system', 'prvkey')))); $datatosend = json_encode(crypto_encapsulate(json_encode($data), $ret_hub['hubloc_sitekey'])); $fetch = zot_zot($url, $datatosend); $result = zot_import($fetch, $arr['sender']['url']); return $result; }