Example #1
0
/**
 * 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');
    }
}
Example #2
0
File: zot.php Project: 23n/hubzilla
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;
}
Example #3
0
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
}
Example #4
0
/**
 * 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')));
    }
}
Example #5
0
/**
 * @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;
}
Example #6
0
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;
}
Example #7
0
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 */
}
Example #8
0
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;
}
Example #9
0
File: post.php Project: Mauru/red
/**
 * @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);
}
Example #10
0
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);
}
Example #11
0
/**
 * @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;
}