Example #1
0
File: zot.php Project: 23n/hubzilla
/**
 * @brief
 *
 * @param array $sender
 * @param array $arr
 * @param array $deliveries
 * @param boolean $relay
 * @param boolean $public (optional) default false
 * @param boolean $request (optional) default false
 * @return array
 */
function process_delivery($sender, $arr, $deliveries, $relay, $public = false, $request = false)
{
    $result = array();
    $result['site'] = z_root();
    // We've validated the sender. Now make sure that the sender is the owner or author
    if (!$public) {
        if ($sender['hash'] != $arr['owner_xchan'] && $sender['hash'] != $arr['author_xchan']) {
            logger("process_delivery: sender {$sender['hash']} is not owner {$arr['owner_xchan']} or author {$arr['author_xchan']} - mid {$arr['mid']}");
            return;
        }
    }
    foreach ($deliveries as $d) {
        $local_public = $public;
        $DR = new DReport(z_root(), $sender['hash'], $d['hash'], $arr['mid']);
        $r = q("select * from channel where channel_hash = '%s' limit 1", dbesc($d['hash']));
        if (!$r) {
            $DR->update('recipient not found');
            $result[] = $DR->get();
            continue;
        }
        $channel = $r[0];
        $DR->addto_recipient($channel['channel_name'] . ' <' . $channel['channel_address'] . '@' . get_app()->get_hostname() . '>');
        /**
         * @FIXME: Somehow we need to block normal message delivery from our clones, as the delivered
         * message doesn't have ACL information in it as the cloned copy does. That copy 
         * will normally arrive first via sync delivery, but this isn't guaranteed. 
         * There's a chance the current delivery could take place before the cloned copy arrives
         * hence the item could have the wrong ACL and *could* be used in subsequent deliveries or
         * access checks. So far all attempts at identifying this situation precisely
         * have caused issues with delivery of relayed comments. 
         */
        //		if(($d['hash'] === $sender['hash']) && ($sender['url'] !== z_root()) && (! $relay)) {
        //			$DR->update('self delivery ignored');
        //			$result[] = $DR->get();
        //			continue;
        //		}
        // allow public postings to the sys channel regardless of permissions, but not
        // for comments travelling upstream. Wait and catch them on the way down.
        // They may have been blocked by the owner.
        if (intval($channel['channel_system']) && !$arr['item_private'] && !$relay) {
            $local_public = true;
            $r = q("select xchan_selfcensored from xchan where xchan_hash = '%s' limit 1", dbesc($sender['hash']));
            // don't import sys channel posts from selfcensored authors
            if ($r && intval($r[0]['xchan_selfcensored'])) {
                $local_public = false;
                continue;
            }
        }
        $tag_delivery = tgroup_check($channel['channel_id'], $arr);
        $perm = 'send_stream';
        if ($arr['mid'] !== $arr['parent_mid'] && $relay) {
            $perm = 'post_comments';
        }
        // This is our own post, possibly coming from a channel clone
        if ($arr['owner_xchan'] == $d['hash']) {
            $arr['item_wall'] = 1;
        } else {
            $arr['item_wall'] = 0;
        }
        if (!perm_is_allowed($channel['channel_id'], $sender['hash'], $perm) && !$tag_delivery && !$local_public) {
            logger("permission denied for delivery to channel {$channel['channel_id']} {$channel['channel_address']}");
            $DR->update('permission denied');
            $result[] = $DR->get();
            continue;
        }
        if ($arr['mid'] != $arr['parent_mid']) {
            // check source route.
            // We are only going to accept comments from this sender if the comment has the same route as the top-level-post,
            // this is so that permissions mismatches between senders apply to the entire conversation
            // As a side effect we will also do a preliminary check that we have the top-level-post, otherwise
            // processing it is pointless.
            $r = q("select route, id from item where mid = '%s' and uid = %d limit 1", dbesc($arr['parent_mid']), intval($channel['channel_id']));
            if (!$r) {
                $DR->update('comment parent not found');
                $result[] = $DR->get();
                // We don't seem to have a copy of this conversation or at least the parent
                // - so request a copy of the entire conversation to date.
                // Don't do this if it's a relay post as we're the ones who are supposed to
                // have the copy and we don't want the request to loop.
                // Also don't do this if this comment came from a conversation request packet.
                // It's possible that comments are allowed but posting isn't and that could
                // cause a conversation fetch loop. We can detect these packets since they are
                // delivered via a 'notify' packet type that has a message_id element in the
                // initial zot packet (just like the corresponding 'request' packet type which
                // makes the request).
                // We'll also check the send_stream permission - because if it isn't allowed,
                // the top level post is unlikely to be imported and
                // this is just an exercise in futility.
                if (!$relay && !$request && !$local_public && perm_is_allowed($channel['channel_id'], $sender['hash'], 'send_stream')) {
                    proc_run('php', 'include/notifier.php', 'request', $channel['channel_id'], $sender['hash'], $arr['parent_mid']);
                }
                continue;
            }
            if ($relay) {
                // reset the route in case it travelled a great distance upstream
                // use our parent's route so when we go back downstream we'll match
                // with whatever route our parent has.
                $arr['route'] = $r[0]['route'];
            } else {
                // going downstream check that we have the same upstream provider that
                // sent it to us originally. Ignore it if it came from another source
                // (with potentially different permissions).
                // only compare the last hop since it could have arrived at the last location any number of ways.
                // Always accept empty routes and firehose items (route contains 'undefined') .
                $existing_route = explode(',', $r[0]['route']);
                $routes = count($existing_route);
                if ($routes) {
                    $last_hop = array_pop($existing_route);
                    $last_prior_route = implode(',', $existing_route);
                } else {
                    $last_hop = '';
                    $last_prior_route = '';
                }
                if (in_array('undefined', $existing_route) || $last_hop == 'undefined' || $sender['hash'] == 'undefined') {
                    $last_hop = '';
                }
                $current_route = ($arr['route'] ? $arr['route'] . ',' : '') . $sender['hash'];
                if ($last_hop && $last_hop != $sender['hash']) {
                    logger('comment route mismatch: parent route = ' . $r[0]['route'] . ' expected = ' . $current_route, LOGGER_DEBUG);
                    logger('comment route mismatch: parent msg = ' . $r[0]['id'], LOGGER_DEBUG);
                    $DR->update('comment route mismatch');
                    $result[] = $DR->get();
                    continue;
                }
                // we'll add sender['hash'] onto this when we deliver it. $last_prior_route now has the previously stored route
                // *except* for the sender['hash'] which would've been the last hop before it got to us.
                $arr['route'] = $last_prior_route;
            }
        }
        $ab = q("select * from abook where abook_channel = %d and abook_xchan = '%s'", intval($channel['channel_id']), dbesc($arr['owner_xchan']));
        $abook = $ab ? $ab[0] : null;
        if (intval($arr['item_deleted'])) {
            // remove_community_tag is a no-op if this isn't a community tag activity
            remove_community_tag($sender, $arr, $channel['channel_id']);
            // set these just in case we need to store a fresh copy of the deleted post.
            // This could happen if the delete got here before the original post did.
            $arr['aid'] = $channel['channel_account_id'];
            $arr['uid'] = $channel['channel_id'];
            $item_id = delete_imported_item($sender, $arr, $channel['channel_id'], $relay);
            $DR->update($item_id ? 'deleted' : 'delete_failed');
            $result[] = $DR->get();
            if ($relay && $item_id) {
                logger('process_delivery: invoking relay');
                proc_run('php', 'include/notifier.php', 'relay', intval($item_id));
                $DR->update('relayed');
                $result[] = $DR->get();
            }
            continue;
        }
        $r = q("select * from item where mid = '%s' and uid = %d limit 1", dbesc($arr['mid']), intval($channel['channel_id']));
        if ($r) {
            // We already have this post.
            $item_id = $r[0]['id'];
            if (intval($r[0]['item_deleted'])) {
                // It was deleted locally.
                $DR->update('update ignored');
                $result[] = $DR->get();
                continue;
            } elseif ($arr['edited'] > $r[0]['edited']) {
                $arr['id'] = $r[0]['id'];
                $arr['uid'] = $channel['channel_id'];
                if ($arr['mid'] == $arr['parent_mid'] && !post_is_importable($arr, $abook)) {
                    $DR->update('update ignored');
                    $result[] = $DR->get();
                } else {
                    update_imported_item($sender, $arr, $r[0], $channel['channel_id']);
                    $DR->update('updated');
                    $result[] = $DR->get();
                    if (!$relay) {
                        add_source_route($item_id, $sender['hash']);
                    }
                }
            } else {
                $DR->update('update ignored');
                $result[] = $DR->get();
                // We need this line to ensure wall-to-wall comments are relayed (by falling through to the relay bit),
                // and at the same time not relay any other relayable posts more than once, because to do so is very wasteful.
                if (!intval($r[0]['item_origin'])) {
                    continue;
                }
            }
        } else {
            $arr['aid'] = $channel['channel_account_id'];
            $arr['uid'] = $channel['channel_id'];
            // if it's a sourced post, call the post_local hooks as if it were
            // posted locally so that crosspost connectors will be triggered.
            if (check_item_source($arr['uid'], $arr)) {
                call_hooks('post_local', $arr);
            }
            $item_id = 0;
            if ($arr['mid'] == $arr['parent_mid'] && !post_is_importable($arr, $abook)) {
                $DR->update('post ignored');
                $result[] = $DR->get();
            } else {
                $item_result = item_store($arr);
                if ($item_result['success']) {
                    $item_id = $item_result['item_id'];
                    $parr = array('item_id' => $item_id, 'item' => $arr, 'sender' => $sender, 'channel' => $channel);
                    call_hooks('activity_received', $parr);
                    // don't add a source route if it's a relay or later recipients will get a route mismatch
                    if (!$relay) {
                        add_source_route($item_id, $sender['hash']);
                    }
                }
                $DR->update($item_id ? 'posted' : 'storage failed: ' . $item_result['message']);
                $result[] = $DR->get();
            }
        }
        if ($relay && $item_id) {
            logger('process_delivery: invoking relay');
            proc_run('php', 'include/notifier.php', 'relay', intval($item_id));
            $DR->addto_update('relayed');
            $result[] = $DR->get();
        }
    }
    if (!$deliveries) {
        $result[] = array('', 'no recipients', '', $arr['mid']);
    }
    logger('process_delivery: local results: ' . print_r($result, true), LOGGER_DEBUG);
    return $result;
}
Example #2
0
File: zot.php Project: Mauru/red
function process_delivery($sender, $arr, $deliveries, $relay, $public = false)
{
    $result = array();
    // We've validated the sender. Now make sure that the sender is the owner or author
    if (!$public) {
        if ($sender['hash'] != $arr['owner_xchan'] && $sender['hash'] != $arr['author_xchan']) {
            logger("process_delivery: sender {$sender['hash']} is not owner {$arr['owner_xchan']} or author {$arr['author_xchan']} - mid {$arr['mid']}");
            return;
        }
    }
    foreach ($deliveries as $d) {
        $r = q("select * from channel where channel_hash = '%s' limit 1", dbesc($d['hash']));
        if (!$r) {
            $result[] = array($d['hash'], 'recipients not found');
            continue;
        }
        $channel = $r[0];
        $tag_delivery = tgroup_check($channel['channel_id'], $arr);
        $perm = $arr['mid'] == $arr['parent_mid'] ? 'send_stream' : 'post_comments';
        // This is our own post, possibly coming from a channel clone
        if ($arr['owner_xchan'] == $d['hash']) {
            $arr['item_flags'] = $arr['item_flags'] | ITEM_WALL;
        } else {
            // clear the wall flag if it is set
            if ($arr['item_flags'] & ITEM_WALL) {
                $arr['item_flags'] = $arr['item_flags'] ^ ITEM_WALL;
            }
        }
        if (!perm_is_allowed($channel['channel_id'], $sender['hash'], $perm) && !$tag_delivery && !$public) {
            logger("permission denied for delivery to channel {$channel['channel_id']} {$channel['channel_address']}");
            $result[] = array($d['hash'], 'permission denied', $channel['channel_name'] . ' <' . $channel['channel_address'] . '@' . get_app()->get_hostname() . '>', $arr['mid']);
            continue;
        }
        if ($arr['item_restrict'] & ITEM_DELETED) {
            // remove_community_tag is a no-op if this isn't a community tag activity
            remove_community_tag($sender, $arr, $channel['channel_id']);
            $item_id = delete_imported_item($sender, $arr, $channel['channel_id']);
            $result[] = array($d['hash'], $item_id ? 'deleted' : 'delete_failed', $channel['channel_name'] . ' <' . $channel['channel_address'] . '@' . get_app()->get_hostname() . '>', $arr['mid']);
            if ($relay && $item_id) {
                logger('process_delivery: invoking relay');
                proc_run('php', 'include/notifier.php', 'relay', intval($item_id));
                $result[] = array($d['hash'], 'relayed', $channel['channel_name'] . ' <' . $channel['channel_address'] . '@' . get_app()->get_hostname() . '>', $arr['mid']);
            }
            continue;
        }
        $r = q("select id, edited from item where mid = '%s' and uid = %d limit 1", dbesc($arr['mid']), intval($channel['channel_id']));
        if ($r) {
            if ($arr['edited'] > $r[0]['edited']) {
                $arr['id'] = $r[0]['id'];
                $arr['uid'] = $channel['channel_id'];
                update_imported_item($sender, $arr, $channel['channel_id']);
            }
            $result[] = array($d['hash'], 'updated', $channel['channel_name'] . ' <' . $channel['channel_address'] . '@' . get_app()->get_hostname() . '>', $arr['mid']);
            $item_id = $r[0]['id'];
        } else {
            $arr['aid'] = $channel['channel_account_id'];
            $arr['uid'] = $channel['channel_id'];
            $item_result = item_store($arr);
            $item_id = 0;
            if ($item_result['success']) {
                $item_id = $item_result['item_id'];
                $parr = array('item_id' => $item_id, 'item' => $arr, 'sender' => $sender, 'channel' => $channel);
                call_hooks('activity_received', $parr);
                add_source_route($item_id, $sender['hash']);
            }
            $result[] = array($d['hash'], $item_id ? 'posted' : 'storage failed:' . $item_result['message'], $channel['channel_name'] . ' <' . $channel['channel_address'] . '@' . get_app()->get_hostname() . '>', $arr['mid']);
        }
        if ($relay && $item_id) {
            logger('process_delivery: invoking relay');
            proc_run('php', 'include/notifier.php', 'relay', intval($item_id));
            $result[] = array($d['hash'], 'relayed', $channel['channel_name'] . ' <' . $channel['channel_address'] . '@' . get_app()->get_hostname() . '>', $arr['mid']);
        }
    }
    if (!$deliveries) {
        $result[] = array('', 'no recipients', '', $arr['mid']);
    }
    logger('process_delivery: local results: ' . print_r($result, true), LOGGER_DEBUG);
    return $result;
}