Esempio n. 1
0
function probe_content(&$a)
{
    $o .= '<h3>Probe Diagnostic</h3>';
    $o .= '<form action="probe" method="get">';
    $o .= 'Lookup address: <input type="text" style="width: 250px;" name="addr" value="' . $_GET['addr'] . '" />';
    $o .= '<input type="submit" name="submit" value="Submit" /></form>';
    $o .= '<br /><br />';
    if (x($_GET, 'addr')) {
        $channel = $a->get_channel();
        $addr = trim($_GET['addr']);
        $res = zot_finger($addr, $channel, false);
        $o .= '<pre>';
        if ($res['success']) {
            $j = json_decode($res['body'], true);
        } else {
            $o .= sprintf(t('Fetching URL returns error: %1$s'), $res['error'] . "\r\n\r\n");
            $o .= "<strong>https connection failed. Trying again with auto failover to http.</strong>\r\n\r\n";
            $res = zot_finger($addr, $channel, true);
            if ($res['success']) {
                $j = json_decode($res['body'], true);
            } else {
                $o .= sprintf(t('Fetching URL returns error: %1$s'), $res['error'] . "\r\n\r\n");
            }
        }
        if ($j && $j['permissions'] && $j['permissions']['iv']) {
            $j['permissions'] = json_decode(crypto_unencapsulate($j['permissions'], $channel['channel_prvkey']), true);
        }
        $o .= str_replace("\n", '<br />', print_r($j, true));
        $o .= '</pre>';
    }
    return $o;
}
Esempio n. 2
0
 function GetHublocs($address)
 {
     // Try and find a hubloc for the person attempting to auth.
     // Since we're matching by address, we have to return all entries
     // some of which may be from re-installed hubs; and we'll need to
     // try each sequentially to see if one can pass the test
     $x = q("select * from hubloc left join xchan on xchan_hash = hubloc_hash \n\t\t\twhere hubloc_addr = '%s' order by hubloc_id desc", dbesc($address));
     if (!$x) {
         // finger them if they can't be found.
         $ret = zot_finger($address, null);
         if ($ret['success']) {
             $j = json_decode($ret['body'], true);
             if ($j) {
                 import_xchan($j);
             }
             $x = q("select * from hubloc left join xchan on xchan_hash = hubloc_hash \n\t\t\t\t\twhere hubloc_addr = '%s' order by hubloc_id desc", dbesc($address));
         }
     }
     if (!$x) {
         logger('mod_zot: auth: unable to finger ' . $address);
         $this->Debug('no hubloc found for ' . $address . ' and probing failed.');
         $this->Finalise();
     }
     return $x;
 }
Esempio n. 3
0
function gprobe_run($argv, $argc)
{
    cli_startup();
    $a = get_app();
    if ($argc != 2) {
        return;
    }
    $url = hex2bin($argv[1]);
    $r = q("select * from xchan where xchan_addr = '%s' limit 1", dbesc($url));
    if (!$r) {
        $x = zot_finger($url, null);
        if ($x['success']) {
            $j = json_decode($x['body'], true);
            $y = import_xchan($j);
        }
    }
    return;
}
Esempio n. 4
0
 function GetHublocs($address)
 {
     // Try and find a hubloc for the person attempting to auth
     $x = q("select * from hubloc left join xchan on xchan_hash = hubloc_hash \n\t\t\twhere hubloc_addr = '%s' order by hubloc_id desc", dbesc($address));
     if (!$x) {
         // finger them if they can't be found.
         $ret = zot_finger($address, null);
         if ($ret['success']) {
             $j = json_decode($ret['body'], true);
             if ($j) {
                 import_xchan($j);
             }
             $x = q("select * from hubloc left join xchan on xchan_hash = hubloc_hash \n\t\t\t\t\twhere hubloc_addr = '%s' order by hubloc_id desc", dbesc($address));
         }
     }
     if (!$x) {
         logger('mod_zot: auth: unable to finger ' . $address);
         $this->reply_die('no hubloc found for ' . $address . ' and probing failed.');
     }
     return $x;
 }
Esempio n. 5
0
function chanview_content(&$a)
{
    $observer = $a->get_observer();
    $xchan = null;
    $r = null;
    if ($_REQUEST['hash']) {
        $r = q("select * from xchan where xchan_hash = '%s' limit 1", dbesc($_REQUEST['hash']));
    }
    if ($_REQUEST['address']) {
        $r = q("select * from xchan where xchan_addr = '%s' limit 1", dbesc($_REQUEST['address']));
    } elseif (local_channel() && intval($_REQUEST['cid'])) {
        $r = q("SELECT abook.*, xchan.* \n\t\t\tFROM abook left join xchan on abook_xchan = xchan_hash\n\t\t\tWHERE abook_channel = %d and abook_id = %d LIMIT 1", intval(local_channel()), intval($_REQUEST['cid']));
    } elseif ($_REQUEST['url']) {
        // if somebody re-installed they will have more than one xchan, use the most recent name date as this is
        // the most useful consistently ascending table item we have.
        $r = q("select * from xchan where xchan_url = '%s' order by xchan_name_date desc limit 1", dbesc($_REQUEST['url']));
    }
    if ($r) {
        $a->poi = $r[0];
    }
    // Here, let's see if we have an xchan. If we don't, how we proceed is determined by what
    // info we do have. If it's a URL, we can offer to visit it directly. If it's a webbie or
    // address, we can and should try to import it. If it's just a hash, we can't continue, but we
    // probably wouldn't have a hash if we don't already have an xchan for this channel.
    if (!$a->poi) {
        logger('mod_chanview: fallback');
        // This is hackish - construct a zot address from the url
        if ($_REQUEST['url']) {
            if (preg_match('/https?\\:\\/\\/(.*?)(\\/channel\\/|\\/profile\\/)(.*?)$/ism', $_REQUEST['url'], $matches)) {
                $_REQUEST['address'] = $matches[3] . '@' . $matches[1];
            }
            logger('mod_chanview: constructed address ' . print_r($matches, true));
        }
        if ($_REQUEST['address']) {
            $ret = zot_finger($_REQUEST['address'], null);
            if ($ret['success']) {
                $j = json_decode($ret['body'], true);
                if ($j) {
                    import_xchan($j);
                }
                $r = q("select * from xchan where xchan_addr = '%s' limit 1", dbesc($_REQUEST['address']));
                if ($r) {
                    $a->poi = $r[0];
                }
            }
        }
    }
    if (!$a->poi) {
        //		We don't know who this is, and we can't figure it out from the URL
        //		On the plus side, there's a good chance we know somebody else at that
        //		hub so sending them there with a Zid will probably work anyway.
        $url = $_REQUEST['url'];
        if ($observer) {
            $url = zid($url);
        }
    }
    if ($a->poi) {
        $url = $a->poi['xchan_url'];
        if ($observer) {
            $url = zid($url);
        }
    }
    // let somebody over-ride the iframed viewport presentation
    // or let's just declare this a failed experiment.
    //	if((! local_channel()) || (get_pconfig(local_channel(),'system','chanview_full')))
    goaway($url);
    //	$o = replace_macros(get_markup_template('chanview.tpl'),array(
    //		'$url' => $url,
    //		'$full' => t('toggle full screen mode')
    //	));
    //	return $o;
}
Esempio n. 6
0
/**
 * @brief
 *
 * Given an update record, probe the channel, grab a zot-info packet and refresh/sync the data.
 *
 * Ignore updating records marked as deleted.
 *
 * If successful, sets ud_last in the DB to the current datetime for this
 * reddress/webbie.
 *
 * @param array $ud Entry from update table
 */
function update_directory_entry($ud)
{
    logger('update_directory_entry: ' . print_r($ud, true), LOGGER_DATA);
    if ($ud['ud_addr'] && !($ud['ud_flags'] & UPDATE_FLAGS_DELETED)) {
        $success = false;
        $x = zot_finger($ud['ud_addr'], '');
        if ($x['success']) {
            $j = json_decode($x['body'], true);
            if ($j) {
                $success = true;
            }
            $y = import_xchan($j, 0, $ud);
        }
        if (!$success) {
            q("update updates set ud_last = '%s' where ud_addr = '%s'", dbesc(datetime_convert()), dbesc($ud['ud_addr']));
        }
    }
}
Esempio n. 7
0
File: mail.php Progetto: Mauru/red
function mail_post(&$a)
{
    if (!local_user()) {
        return;
    }
    $replyto = x($_REQUEST, 'replyto') ? notags(trim($_REQUEST['replyto'])) : '';
    $subject = x($_REQUEST, 'subject') ? notags(trim($_REQUEST['subject'])) : '';
    $body = x($_REQUEST, 'body') ? escape_tags(trim($_REQUEST['body'])) : '';
    $recipient = x($_REQUEST, 'messageto') ? notags(trim($_REQUEST['messageto'])) : '';
    $rstr = x($_REQUEST, 'messagerecip') ? notags(trim($_REQUEST['messagerecip'])) : '';
    $expires = x($_REQUEST, 'expires') ? datetime_convert(date_default_timezone_get(), 'UTC', $_REQUEST['expires']) : NULL_DATE;
    // If we have a raw string for a recipient which hasn't been auto-filled,
    // it means they probably aren't in our address book, hence we don't know
    // if we have permission to send them private messages.
    // finger them and find out before we try and send it.
    if (!$recipient) {
        $channel = $a->get_channel();
        $ret = zot_finger($rstr, $channel);
        if (!$ret['success']) {
            notice(t('Unable to lookup recipient.') . EOL);
            return;
        }
        $j = json_decode($ret['body'], true);
        logger('message_post: lookup: ' . $url . ' ' . print_r($j, true));
        if (!($j['success'] && $j['guid'])) {
            notice(t('Unable to communicate with requested channel.'));
            return;
        }
        $x = import_xchan($j);
        if (!$x['success']) {
            notice(t('Cannot verify requested channel.'));
            return;
        }
        $recipient = $x['hash'];
        $their_perms = 0;
        $global_perms = get_perms();
        if ($j['permissions']['data']) {
            $permissions = crypto_unencapsulate($j['permissions'], $channel['channel_prvkey']);
            if ($permissions) {
                $permissions = json_decode($permissions);
            }
            logger('decrypted permissions: ' . print_r($permissions, true), LOGGER_DATA);
        } else {
            $permissions = $j['permissions'];
        }
        foreach ($permissions as $k => $v) {
            if ($v) {
                $their_perms = $their_perms | intval($global_perms[$k][1]);
            }
        }
        if (!($their_perms & PERMS_W_MAIL)) {
            notice(t('Selected channel has private message restrictions. Send failed.'));
            return;
        }
    }
    //	if(feature_enabled(local_user(),'richtext')) {
    //		$body = fix_mce_lf($body);
    //	}
    if (!$recipient) {
        notice('No recipient found.');
        $a->argc = 2;
        $a->argv[1] = 'new';
        return;
    }
    // We have a local_user, let send_message use the session channel and save a lookup
    $ret = send_message(0, $recipient, $body, $subject, $replyto, $expires);
    if (!$ret['success']) {
        notice($ret['message']);
    }
    goaway(z_root() . '/message');
}
Esempio n. 8
0
function new_contact($uid, $url, $channel, $interactive = false, $confirm = false)
{
    $result = array('success' => false, 'message' => '');
    $a = get_app();
    $is_red = false;
    $is_http = strpos($url, '://') !== false ? true : false;
    if ($is_http && substr($url, -1, 1) === '/') {
        $url = substr($url, 0, -1);
    }
    if (!allowed_url($url)) {
        $result['message'] = t('Channel is blocked on this site.');
        return $result;
    }
    if (!$url) {
        $result['message'] = t('Channel location missing.');
        return $result;
    }
    // check service class limits
    $r = q("select count(*) as total from abook where abook_channel = %d and abook_self = 0 ", intval($uid));
    if ($r) {
        $total_channels = $r[0]['total'];
    }
    if (!service_class_allows($uid, 'total_channels', $total_channels)) {
        $result['message'] = upgrade_message();
        return $result;
    }
    $arr = array('url' => $url, 'channel' => array());
    call_hooks('follow', $arr);
    if ($arr['channel']['success']) {
        $ret = $arr['channel'];
    } elseif (!$is_http) {
        $ret = zot_finger($url, $channel);
    }
    if ($ret && $ret['success']) {
        $is_red = true;
        $j = json_decode($ret['body'], true);
    }
    $my_perms = get_channel_default_perms($uid);
    $role = get_pconfig($uid, 'system', 'permissions_role');
    if ($role) {
        $x = get_role_perms($role);
        if ($x['perms_follow']) {
            $my_perms = $x['perms_follow'];
        }
    }
    if ($is_red && $j) {
        logger('follow: ' . $url . ' ' . print_r($j, true), LOGGER_DEBUG);
        if (!($j['success'] && $j['guid'])) {
            $result['message'] = t('Response from remote channel was incomplete.');
            logger('mod_follow: ' . $result['message']);
            return $result;
        }
        // Premium channel, set confirm before callback to avoid recursion
        if (array_key_exists('connect_url', $j) && $interactive && !$confirm) {
            goaway(zid($j['connect_url']));
        }
        // do we have an xchan and hubloc?
        // If not, create them.
        $x = import_xchan($j);
        if (array_key_exists('deleted', $j) && intval($j['deleted'])) {
            $result['message'] = t('Channel was deleted and no longer exists.');
            return $result;
        }
        if (!$x['success']) {
            return $x;
        }
        $xchan_hash = $x['hash'];
        $their_perms = 0;
        $global_perms = get_perms();
        if (array_key_exists('permissions', $j) && array_key_exists('data', $j['permissions'])) {
            $permissions = crypto_unencapsulate(array('data' => $j['permissions']['data'], 'key' => $j['permissions']['key'], 'iv' => $j['permissions']['iv']), $channel['channel_prvkey']);
            if ($permissions) {
                $permissions = json_decode($permissions, true);
            }
            logger('decrypted permissions: ' . print_r($permissions, true), LOGGER_DATA);
        } else {
            $permissions = $j['permissions'];
        }
        foreach ($permissions as $k => $v) {
            if ($v) {
                $their_perms = $their_perms | intval($global_perms[$k][1]);
            }
        }
    } else {
        $their_perms = 0;
        $xchan_hash = '';
        $r = q("select * from xchan where xchan_hash = '%s' or xchan_url = '%s' limit 1", dbesc($url), dbesc($url));
        if (!$r) {
            // attempt network auto-discovery
            if (strpos($url, '@') && !$is_http) {
                $r = discover_by_webbie($url);
            } elseif ($is_http) {
                $r = discover_by_url($url);
                $r['allowed'] = intval(get_config('system', 'feed_contacts'));
            }
            if ($r) {
                $r['channel_id'] = $uid;
                call_hooks('follow_allow', $r);
                if (!$r['allowed']) {
                    $result['message'] = t('Protocol disabled.');
                    return $result;
                }
                $r = q("select * from xchan where xchan_hash = '%s' or xchan_url = '%s' limit 1", dbesc($url), dbesc($url));
            }
        }
        if ($r) {
            $xchan_hash = $r[0]['xchan_hash'];
            $their_perms = 0;
        }
    }
    if (!$xchan_hash) {
        $result['message'] = t('Channel discovery failed.');
        logger('follow: ' . $result['message']);
        return $result;
    }
    if (local_channel() && $uid == local_channel()) {
        $aid = get_account_id();
        $hash = get_observer_hash();
        $ch = $a->get_channel();
        $default_group = $ch['channel_default_group'];
    } else {
        $r = q("select * from channel where channel_id = %d limit 1", intval($uid));
        if (!$r) {
            $result['message'] = t('local account not found.');
            return $result;
        }
        $aid = $r[0]['channel_account_id'];
        $hash = $r[0]['channel_hash'];
        $default_group = $r[0]['channel_default_group'];
    }
    if ($is_http) {
        $r = q("select count(*) as total from abook where abook_account = %d and abook_feed = 1 ", intval($aid));
        if ($r) {
            $total_feeds = $r[0]['total'];
        }
        if (!service_class_allows($uid, 'total_feeds', $total_feeds)) {
            $result['message'] = upgrade_message();
            return $result;
        }
    }
    if ($hash == $xchan_hash) {
        $result['message'] = t('Cannot connect to yourself.');
        return $result;
    }
    $r = q("select abook_xchan from abook where abook_xchan = '%s' and abook_channel = %d limit 1", dbesc($xchan_hash), intval($uid));
    if ($r) {
        $x = q("update abook set abook_their_perms = %d where abook_id = %d", intval($their_perms), intval($r[0]['abook_id']));
    } else {
        $closeness = get_pconfig($uid, 'system', 'new_abook_closeness');
        if ($closeness === false) {
            $closeness = 80;
        }
        $r = q("insert into abook ( abook_account, abook_channel, abook_closeness, abook_xchan, abook_feed, abook_their_perms, abook_my_perms, abook_created, abook_updated )\n\t\t\tvalues( %d, %d, %d, '%s', %d, %d, %d, '%s', '%s' ) ", intval($aid), intval($uid), intval($closeness), dbesc($xchan_hash), intval($is_http ? 1 : 0), intval($is_http ? $their_perms | PERMS_R_STREAM | PERMS_A_REPUBLISH : $their_perms), intval($my_perms), dbesc(datetime_convert()), dbesc(datetime_convert()));
    }
    if (!$r) {
        logger('mod_follow: abook creation failed');
    }
    $r = q("select abook.*, xchan.* from abook left join xchan on abook_xchan = xchan_hash \n\t\twhere abook_xchan = '%s' and abook_channel = %d limit 1", dbesc($xchan_hash), intval($uid));
    if ($r) {
        $result['abook'] = $r[0];
        proc_run('php', 'include/notifier.php', 'permission_update', $result['abook']['abook_id']);
    }
    $arr = array('channel_id' => $uid, 'abook' => $result['abook']);
    call_hooks('follow', $arr);
    /** If there is a default group for this channel, add this member to it */
    if ($default_group) {
        require_once 'include/group.php';
        $g = group_rec_byhash($uid, $default_group);
        if ($g) {
            group_add_member($uid, '', $xchan_hash, $g['id']);
        }
    }
    $result['success'] = true;
    return $result;
}
Esempio n. 9
0
 function init()
 {
     $ret = array('success' => false, 'url' => '', 'message' => '');
     logger('mod_magic: invoked', LOGGER_DEBUG);
     logger('mod_magic: args: ' . print_r($_REQUEST, true), LOGGER_DATA);
     $addr = x($_REQUEST, 'addr') ? $_REQUEST['addr'] : '';
     $dest = x($_REQUEST, 'dest') ? $_REQUEST['dest'] : '';
     $test = x($_REQUEST, 'test') ? intval($_REQUEST['test']) : 0;
     $rev = x($_REQUEST, 'rev') ? intval($_REQUEST['rev']) : 0;
     $delegate = x($_REQUEST, 'delegate') ? $_REQUEST['delegate'] : '';
     $parsed = parse_url($dest);
     if (!$parsed) {
         if ($test) {
             $ret['message'] .= 'could not parse ' . $dest . EOL;
             return $ret;
         }
         goaway($dest);
     }
     $basepath = $parsed['scheme'] . '://' . $parsed['host'] . ($parsed['port'] ? ':' . $parsed['port'] : '');
     $x = q("select * from hubloc where hubloc_url = '%s' order by hubloc_connected desc limit 1", dbesc($basepath));
     if (!$x) {
         /*
          * We have no records for, or prior communications with this hub. 
          * If an address was supplied, let's finger them to create a hub record. 
          * Otherwise we'll use the special address '[system]' which will return
          * either a system channel or the first available normal channel. We don't
          * really care about what channel is returned - we need the hub information 
          * from that response so that we can create signed auth packets destined 
          * for that hub.
          *
          */
         $ret = zot_finger($addr ? $addr : '[system]@' . $parsed['host'], null);
         if ($ret['success']) {
             $j = json_decode($ret['body'], true);
             if ($j) {
                 import_xchan($j);
             }
             // Now try again
             $x = q("select * from hubloc where hubloc_url = '%s' order by hubloc_connected desc limit 1", dbesc($basepath));
         }
     }
     if (!$x) {
         if ($rev) {
             goaway($dest);
         } else {
             logger('mod_magic: no channels found for requested hub.' . print_r($_REQUEST, true));
             if ($test) {
                 $ret['message'] .= 'This site has no previous connections with ' . $basepath . EOL;
                 return $ret;
             }
             notice(t('Hub not found.') . EOL);
             return;
         }
     }
     // This is ready-made for a plugin that provides a blacklist or "ask me" before blindly authenticating.
     // By default, we'll proceed without asking.
     $arr = array('channel_id' => local_channel(), 'xchan' => $x[0], 'destination' => $dest, 'proceed' => true);
     call_hooks('magic_auth', $arr);
     $dest = $arr['destination'];
     if (!$arr['proceed']) {
         if ($test) {
             $ret['message'] .= 'cancelled by plugin.' . EOL;
             return $ret;
         }
         goaway($dest);
     }
     if (get_observer_hash() && $x[0]['hubloc_url'] === z_root()) {
         // We are already authenticated on this site and a registered observer.
         // Just redirect.
         if ($test) {
             $ret['success'] = true;
             $ret['message'] .= 'Local site - you are already authenticated.' . EOL;
             return $ret;
         }
         $delegation_success = false;
         if ($delegate) {
             $r = q("select * from channel left join hubloc on channel_hash = hubloc_hash where hubloc_addr = '%s' limit 1", dbesc($delegate));
             if ($r && intval($r[0]['channel_id'])) {
                 $allowed = perm_is_allowed($r[0]['channel_id'], get_observer_hash(), 'delegate');
                 if ($allowed) {
                     $_SESSION['delegate_channel'] = $r[0]['channel_id'];
                     $_SESSION['delegate'] = get_observer_hash();
                     $_SESSION['account_id'] = intval($r[0]['channel_account_id']);
                     change_channel($r[0]['channel_id']);
                     $delegation_success = true;
                 }
             }
         }
         // FIXME: check and honour local delegation
         goaway($dest);
     }
     if (local_channel()) {
         $channel = \App::get_channel();
         $token = random_string();
         $token_sig = base64url_encode(rsa_sign($token, $channel['channel_prvkey']));
         $channel['token'] = $token;
         $channel['token_sig'] = $token_sig;
         \Zotlabs\Zot\Verify::create('auth', $channel['channel_id'], $token, $x[0]['hubloc_url']);
         $target_url = $x[0]['hubloc_callback'] . '/?f=&auth=' . urlencode($channel['channel_address'] . '@' . \App::get_hostname()) . '&sec=' . $token . '&dest=' . urlencode($dest) . '&version=' . ZOT_REVISION;
         if ($delegate) {
             $target_url .= '&delegate=' . urlencode($delegate);
         }
         logger('mod_magic: redirecting to: ' . $target_url, LOGGER_DEBUG);
         if ($test) {
             $ret['success'] = true;
             $ret['url'] = $target_url;
             $ret['message'] = 'token ' . $token . ' created for channel ' . $channel['channel_id'] . ' for url ' . $x[0]['hubloc_url'] . EOL;
             return $ret;
         }
         goaway($target_url);
     }
     if ($test) {
         $ret['message'] = 'Not authenticated or invalid arguments to mod_magic' . EOL;
         return $ret;
     }
     goaway($dest);
 }
Esempio n. 10
0
File: post.php Progetto: Mauru/red
function post_init(&$a)
{
    // Most access to this endpoint is via the post method.
    // Here we will pick out the magic auth params which arrive
    // as a get request, and the only communications to arrive this way.
    /**
     * Magic Auth
     * ==========
     *
     * So-called "magic auth" takes place by a special exchange. On the site where the "channel to be authenticated" lives (e.g. $mysite), 
     * a redirection is made via $mysite/magic to the zot endpoint of the remote site ($remotesite) with special GET parameters.
     *
     * The endpoint is typically  https://$remotesite/post - or whatever was specified as the callback url in prior communications
     * (we will bootstrap an address and fetch a zot info packet if possible where no prior communications exist)
     *
     * Four GET parameters are supplied:
     *
     ** auth => the urlencoded webbie (channel@host.domain) of the channel requesting access
     ** dest => the desired destination URL (urlencoded)
     ** sec  => a random string which is also stored on $mysite for use during the verification phase. 
     ** version => the zot revision
     *
     * When this packet is received, an "auth-check" zot message is sent to $mysite.
     * (e.g. if $_GET['auth'] is foobar@podunk.edu, a zot packet is sent to the podunk.edu zot endpoint, which is typically /post)
     * If no information has been recorded about the requesting identity a zot information packet will be retrieved before
     * continuing.
     * 
     * The sender of this packet is an arbitrary/random site channel. The recipients will be a single recipient corresponding
     * to the guid and guid_sig we have associated with the requesting auth identity
     *
     *
     *    {
     *      "type":"auth_check",
     *      "sender":{
     *        "guid":"kgVFf_...",
     *        "guid_sig":"PT9-TApz...",
     *        "url":"http:\/\/podunk.edu",
     *        "url_sig":"T8Bp7j..."
     *      },
     *      "recipients":{
     *        {
     *        "guid":"ZHSqb...",
     *        "guid_sig":"JsAAXi..."
     *        }
     *      }
     *      "callback":"\/post",
     *      "version":1,
     *      "secret":"1eaa661",
     *      "secret_sig":"eKV968b1..."
     *    }
     *
     *
     * auth_check messages MUST use encapsulated encryption. This message is sent to the origination site, which checks the 'secret' to see 
     * if it is the same as the 'sec' which it passed originally. It also checks the secret_sig which is the secret signed by the 
     * destination channel's private key and base64url encoded. If everything checks out, a json packet is returned:
     *
     *    { 
     *      "success":1, 
     *      "confirm":"q0Ysovd1u..."
     *      "service_class":(optional)
     *      "level":(optional)
     *    }
     *
     * 'confirm' in this case is the base64url encoded RSA signature of the concatenation of 'secret' with the
     * base64url encoded whirlpool hash of the requestor's guid and guid_sig; signed with the source channel private key. 
     * This prevents a man-in-the-middle from inserting a rogue success packet. Upon receipt and successful 
     * verification of this packet, the destination site will redirect to the original destination URL and indicate a successful remote login. 
     * Service_class can be used by cooperating sites to provide different access rights based on account rights and subscription plans. It is 
     * a string whose contents are not defined by protocol. Example: "basic" or "gold". 
     *
     *
     *
     */
    if (array_key_exists('auth', $_REQUEST)) {
        $ret = array('success' => false, 'message' => '');
        logger('mod_zot: auth request received.');
        $address = $_REQUEST['auth'];
        $desturl = $_REQUEST['dest'];
        $sec = $_REQUEST['sec'];
        $version = $_REQUEST['version'];
        $test = x($_REQUEST, 'test') ? intval($_REQUEST['test']) : 0;
        // They are authenticating ultimately to the site and not to a particular channel.
        // Any channel will do, providing it's currently active. We just need to have an
        // identity to attach to the packet we send back. So find one.
        $c = q("select * from channel where not ( channel_pageflags & %d ) limit 1", intval(PAGE_REMOVED));
        if (!$c) {
            // nobody here
            logger('mod_zot: auth: unable to find a response channel');
            if ($test) {
                $ret['message'] .= 'no local channels found.' . EOL;
                json_return_and_die($ret);
            }
            goaway($desturl);
        }
        // Try and find a hubloc for the person attempting to auth
        $x = q("select * from hubloc left join xchan on xchan_hash = hubloc_hash where hubloc_addr = '%s' order by hubloc_id desc limit 1", dbesc($address));
        if (!$x) {
            // finger them if they can't be found.
            $ret = zot_finger($address, null);
            if ($ret['success']) {
                $j = json_decode($ret['body'], true);
                if ($j) {
                    import_xchan($j);
                }
                $x = q("select * from hubloc left join xchan on xchan_hash = hubloc_hash where hubloc_addr = '%s' order by hubloc_id desc limit 1", dbesc($address));
            }
        }
        if (!$x) {
            logger('mod_zot: auth: unable to finger ' . $address);
            if ($test) {
                $ret['message'] .= 'no hubloc found for ' . $address . ' and probing failed.' . EOL;
                json_return_and_die($ret);
            }
            goaway($desturl);
        }
        logger('mod_zot: auth request received from ' . $x[0]['hubloc_addr']);
        // check credentials and access
        // If they are already authenticated and haven't changed credentials,
        // we can save an expensive network round trip and improve performance.
        $remote = remote_user();
        $result = null;
        $remote_service_class = '';
        $remote_level = 0;
        $remote_hub = $x[0]['hubloc_url'];
        $DNT = 0;
        // Also check that they are coming from the same site as they authenticated with originally.
        $already_authed = $remote && $x[0]['hubloc_hash'] == $remote && $x[0]['hubloc_url'] === $_SESSION['remote_hub'] ? true : false;
        $j = array();
        if (!$already_authed) {
            // Auth packets MUST use ultra top-secret hush-hush mode - e.g. the entire packet is encrypted using the site private key
            // The actual channel sending the packet ($c[0]) is not important, but this provides a generic zot packet with a sender
            // which can be verified
            $p = zot_build_packet($c[0], $type = 'auth_check', array(array('guid' => $x[0]['hubloc_guid'], 'guid_sig' => $x[0]['hubloc_guid_sig'])), $x[0]['hubloc_sitekey'], $sec);
            if ($test) {
                $ret['message'] .= 'auth check packet created using sitekey ' . $x[0]['hubloc_sitekey'] . EOL;
                $ret['message'] .= 'packet contents: ' . $p . EOL;
            }
            $result = zot_zot($x[0]['hubloc_callback'], $p);
            if (!$result['success']) {
                logger('mod_zot: auth_check callback failed.');
                if ($test) {
                    $ret['message'] .= 'auth check request to your site returned .' . print_r($result, true) . EOL;
                    json_return_and_die($ret);
                }
                goaway($desturl);
            }
            $j = json_decode($result['body'], true);
            if (!$j) {
                logger('mod_zot: auth_check json data malformed.');
                if ($test) {
                    $ret['message'] .= 'json malformed: ' . $result['body'] . EOL;
                    json_return_and_die($ret);
                }
            }
        }
        if ($test) {
            $ret['message'] .= 'auth check request returned .' . print_r($j, true) . EOL;
        }
        if ($already_authed || $j['success']) {
            if ($j['success']) {
                // legit response, but we do need to check that this wasn't answered by a man-in-middle
                if (!rsa_verify($sec . $x[0]['xchan_hash'], base64url_decode($j['confirm']), $x[0]['xchan_pubkey'])) {
                    logger('mod_zot: auth: final confirmation failed.');
                    if ($test) {
                        $ret['message'] .= 'final confirmation failed. ' . $sec . print_r($j, true) . print_r($x[0], true);
                        json_return_and_die($ret);
                    }
                    goaway($desturl);
                }
                if (array_key_exists('service_class', $j)) {
                    $remote_service_class = $j['service_class'];
                }
                if (array_key_exists('level', $j)) {
                    $remote_level = $j['level'];
                }
                if (array_key_exists('DNT', $j)) {
                    $DNT = $j['DNT'];
                }
            }
            // everything is good... maybe
            if (local_user()) {
                // tell them to logout if they're logged in locally as anything but the target remote account
                // in which case just shut up because they don't need to be doing this at all.
                if ($a->channel['channel_hash'] != $x[0]['xchan_hash']) {
                    logger('mod_zot: auth: already authenticated locally as somebody else.');
                    notice(t('Remote authentication blocked. You are logged into this site locally. Please logout and retry.') . EOL);
                    if ($test) {
                        $ret['message'] .= 'already logged in locally with a conflicting identity.' . EOL;
                        json_return_and_die($ret);
                    }
                }
                goaway($desturl);
            }
            // log them in
            if ($test) {
                $ret['success'] = true;
                $ret['message'] .= 'Authentication Success!' . EOL;
                json_return_and_die($ret);
            }
            $_SESSION['authenticated'] = 1;
            $_SESSION['visitor_id'] = $x[0]['xchan_hash'];
            $_SESSION['my_url'] = $x[0]['xchan_url'];
            $_SESSION['my_address'] = $address;
            $_SESSION['remote_service_class'] = $remote_service_class;
            $_SESSION['remote_level'] = $remote_level;
            $_SESSION['remote_hub'] = $remote_hub;
            $_SESSION['DNT'] = $DNT;
            $arr = array('xchan' => $x[0], 'url' => $desturl, 'session' => $_SESSION);
            call_hooks('magic_auth_success', $arr);
            $a->set_observer($x[0]);
            require_once 'include/security.php';
            $a->set_groups(init_groups_visitor($_SESSION['visitor_id']));
            info(sprintf(t('Welcome %s. Remote authentication successful.'), $x[0]['xchan_name']));
            logger('mod_zot: auth success from ' . $x[0]['xchan_addr']);
            q("update hubloc set hubloc_status =  (hubloc_status | %d ) where hubloc_id = %d ", intval(HUBLOC_WORKS), intval($x[0]['hubloc_id']));
        } else {
            if ($test) {
                $ret['message'] .= 'auth failure. ' . print_r($_REQUEST, true) . print_r($j, true) . EOL;
                json_return_and_die($ret);
            }
            logger('mod_zot: magic-auth failure - not authenticated: ' . $x[0]['xchan_addr']);
            q("update hubloc set hubloc_status =  (hubloc_status | %d ) where hubloc_id = %d ", intval(HUBLOC_RECEIVE_ERROR), intval($x[0]['hubloc_id']));
        }
        // FIXME - we really want to save the return_url in the session before we visit rmagic.
        // This does however prevent a recursion if you visit rmagic directly, as it would otherwise send you back here again.
        // But z_root() probably isn't where you really want to go.
        if ($test) {
            $ret['message'] .= 'auth failure fallthrough ' . print_r($_REQUEST, true) . print_r($j, true) . EOL;
            json_return_and_die($ret);
        }
        if (strstr($desturl, z_root() . '/rmagic')) {
            goaway(z_root());
        }
        goaway($desturl);
    }
    return;
}
Esempio n. 11
0
function mail_post(&$a)
{
    if (!local_channel()) {
        return;
    }
    $replyto = x($_REQUEST, 'replyto') ? notags(trim($_REQUEST['replyto'])) : '';
    $subject = x($_REQUEST, 'subject') ? notags(trim($_REQUEST['subject'])) : '';
    $body = x($_REQUEST, 'body') ? escape_tags(trim($_REQUEST['body'])) : '';
    $recipient = x($_REQUEST, 'messageto') ? notags(trim($_REQUEST['messageto'])) : '';
    $rstr = x($_REQUEST, 'messagerecip') ? notags(trim($_REQUEST['messagerecip'])) : '';
    $preview = x($_REQUEST, 'preview') ? intval($_REQUEST['preview']) : 0;
    $expires = x($_REQUEST, 'expires') ? datetime_convert(date_default_timezone_get(), 'UTC', $_REQUEST['expires']) : NULL_DATE;
    // If we have a raw string for a recipient which hasn't been auto-filled,
    // it means they probably aren't in our address book, hence we don't know
    // if we have permission to send them private messages.
    // finger them and find out before we try and send it.
    if (!$recipient) {
        $channel = App::get_channel();
        $ret = zot_finger($rstr, $channel);
        if (!$ret['success']) {
            notice(t('Unable to lookup recipient.') . EOL);
            return;
        }
        $j = json_decode($ret['body'], true);
        logger('message_post: lookup: ' . $url . ' ' . print_r($j, true));
        if (!($j['success'] && $j['guid'])) {
            notice(t('Unable to communicate with requested channel.'));
            return;
        }
        $x = import_xchan($j);
        if (!$x['success']) {
            notice(t('Cannot verify requested channel.'));
            return;
        }
        $recipient = $x['hash'];
        $their_perms = 0;
        $global_perms = get_perms();
        if ($j['permissions']['data']) {
            $permissions = crypto_unencapsulate($j['permissions'], $channel['channel_prvkey']);
            if ($permissions) {
                $permissions = json_decode($permissions);
            }
            logger('decrypted permissions: ' . print_r($permissions, true), LOGGER_DATA);
        } else {
            $permissions = $j['permissions'];
        }
        foreach ($permissions as $k => $v) {
            if ($v) {
                $their_perms = $their_perms | intval($global_perms[$k][1]);
            }
        }
        if (!($their_perms & PERMS_W_MAIL)) {
            notice(t('Selected channel has private message restrictions. Send failed.'));
            // reported issue: let's still save the message and continue. We'll just tell them
            // that nothing useful is likely to happen. They might have spent hours on it.
            //			return;
        }
    }
    //	if(feature_enabled(local_channel(),'richtext')) {
    //		$body = fix_mce_lf($body);
    //	}
    require_once 'include/text.php';
    linkify_tags($a, $body, local_channel());
    if ($preview) {
    }
    if (!$recipient) {
        notice('No recipient found.');
        App::$argc = 2;
        App::$argv[1] = 'new';
        return;
    }
    // We have a local_channel, let send_message use the session channel and save a lookup
    $ret = send_message(0, $recipient, $body, $subject, $replyto, $expires);
    if ($ret['success']) {
        xchan_mail_query($ret['mail']);
        build_sync_packet(0, array('conv' => array($ret['conv']), 'mail' => array(encode_mail($ret['mail'], true))));
    } else {
        notice($ret['message']);
    }
    goaway(z_root() . '/mail/combined');
}
Esempio n. 12
0
/**
 * With args, register a directory server for this realm.
 * With no args, return a JSON array of directory servers for this realm.
 *
 * @FIXME Not yet implemented: Some realms may require authentication to join their realm.
 * The RED_GLOBAL realm does not require authentication.
 * We would then need a flag in the site table to indicate that they've been
 * validated by the PRIMARY directory for that realm. Sites claiming to be PRIMARY
 * but are not the realm PRIMARY will be marked invalid.
 * 
 * @param App &$a
 */
function regdir_init(&$a)
{
    $result = array('success' => false);
    $url = $_REQUEST['url'];
    $access_token = $_REQUEST['t'];
    $valid = 0;
    // we probably don't need the realm as we will find out in the probe.
    // What we may want to die is throw an error if you're trying to register in a different realm
    // so this configuration issue can be discovered.
    $realm = $_REQUEST['realm'];
    if (!$realm) {
        $realm = DIRECTORY_REALM;
    }
    if ($realm === DIRECTORY_REALM) {
        $valid = 1;
    } else {
        $token = get_config('system', 'realm_token');
        if ($token && $access_token != $token) {
            $result['message'] = 'This realm requires an access token';
            return;
        }
        $valid = 1;
    }
    $dirmode = intval(get_config('system', 'directory_mode'));
    if ($dirmode == DIRECTORY_MODE_NORMAL) {
        $ret['message'] = t('This site is not a directory server');
        json_return_and_die($ret);
    }
    $m = null;
    if ($url) {
        $m = parse_url($url);
        if (!$m || !@dns_get_record($m['host'], DNS_A + DNS_CNAME + DNS_PTR) && !filter_var($m['host'], FILTER_VALIDATE_IP)) {
            $result['message'] = 'unparseable url';
            json_return_and_die($result);
        }
        $f = zot_finger('[system]@' . $m['host']);
        if ($f['success']) {
            $j = json_decode($f['body'], true);
            if ($j['success'] && $j['guid']) {
                $x = import_xchan($j);
                if ($x['success']) {
                    $result['success'] = true;
                }
            }
        }
        if (!$result['success']) {
            $valid = 0;
        }
        q("update site set site_valid = %d where site_url = '%s' limit 1", intval($valid), strtolower($url));
        json_return_and_die($result);
    } else {
        // We can put this in the sql without the condition after 31 august 2015 assuming
        // most directory servers will have updated by then
        // This just makes sure it happens if I forget
        $sql_extra = datetime_convert() > datetime_convert('UTC', 'UTC', '2015-08-31') ? ' and site_valid = 1 ' : '';
        if ($dirmode == DIRECTORY_MODE_STANDALONE) {
            $r = array(array('site_url' => z_root()));
        } else {
            $r = q("select site_url from site where site_flags in ( 1, 2 ) and site_realm = '%s' {$sql_extra} ", dbesc(get_directory_realm()));
        }
        if ($r) {
            $result['success'] = true;
            $result['directories'] = array();
            foreach ($r as $rr) {
                $result['directories'][] = $rr['site_url'];
            }
            json_return_and_die($result);
        }
    }
    json_return_and_die($result);
}
Esempio n. 13
0
/**
 * poco_load
 *
 * xchan is your connection
 * We will load their friend list, and store in xlink_xchan your connection hash and xlink_link the hash for each connection
 * If xchan isn't provided we will load the list of people from url who have indicated they are willing to be friends with
 * new folks and add them to xlink with no xlink_xchan.
 *
 * Old behaviour: (documentation only):
 * Given a contact-id (minimum), load the PortableContacts friend list for that contact,
 * and add the entries to the gcontact (Global Contact) table, or update existing entries
 * if anything (name or photo) has changed.
 * We use normalised urls for comparison which ignore http vs https and www.domain vs domain
 *
 * Once the global contact is stored add (if necessary) the contact linkage which associates
 * the given uid, cid to the global contact entry. There can be many uid/cid combinations
 * pointing to the same global contact id. 
 *
 * @param string $xchan
 * @param string $url
 */
function poco_load($xchan = '', $url = null)
{
    if ($xchan && !$url) {
        $r = q("select xchan_connurl from xchan where xchan_hash = '%s' limit 1", dbesc($xchan));
        if ($r) {
            $url = $r[0]['xchan_connurl'];
        }
    }
    if (!$url) {
        logger('poco_load: no url');
        return;
    }
    $url = $url . '?f=&fields=displayName,hash,urls,photos,rating';
    logger('poco_load: ' . $url, LOGGER_DEBUG);
    $s = z_fetch_url($url);
    if (!$s['success']) {
        if ($s['return_code'] == 401) {
            logger('poco_load: protected');
        } elseif ($s['return_code'] == 404) {
            logger('poco_load: nothing found');
        } else {
            logger('poco_load: returns ' . print_r($s, true));
        }
        return;
    }
    $j = json_decode($s['body'], true);
    if (!$j) {
        logger('poco_load: unable to json_decode returned data.');
        return;
    }
    logger('poco_load: ' . print_r($j, true), LOGGER_DATA);
    if ($xchan) {
        if (array_key_exists('chatrooms', $j) && is_array($j['chatrooms'])) {
            foreach ($j['chatrooms'] as $room) {
                if (!$room['url'] || !$room['desc']) {
                    continue;
                }
                $r = q("select * from xchat where xchat_url = '%s' and xchat_xchan = '%s' limit 1", dbesc($room['url']), dbesc($xchan));
                if ($r) {
                    q("update xchat set xchat_edited = '%s' where xchat_id = %d", dbesc(datetime_convert()), intval($r[0]['xchat_id']));
                } else {
                    $x = q("insert into xchat ( xchat_url, xchat_desc, xchat_xchan, xchat_edited )\n\t\t\t\t\t\tvalues ( '%s', '%s', '%s', '%s' ) ", dbesc(escape_tags($room['url'])), dbesc(escape_tags($room['desc'])), dbesc($xchan), dbesc(datetime_convert()));
                }
            }
        }
        q("delete from xchat where xchat_edited < %s - INTERVAL %s and xchat_xchan = '%s' ", db_utcnow(), db_quoteinterval('7 DAY'), dbesc($xchan));
    }
    if (!(x($j, 'entry') && is_array($j['entry']))) {
        logger('poco_load: no entries');
        return;
    }
    $total = 0;
    foreach ($j['entry'] as $entry) {
        $profile_url = '';
        $profile_photo = '';
        $address = '';
        $name = '';
        $hash = '';
        $rating = 0;
        $name = $entry['displayName'];
        $hash = $entry['hash'];
        $rating = array_key_exists('rating', $entry) && !is_array($entry['rating']) ? intval($entry['rating']) : 0;
        $rating_text = array_key_exists('rating_text', $entry) ? escape_tags($entry['rating_text']) : '';
        if (x($entry, 'urls') && is_array($entry['urls'])) {
            foreach ($entry['urls'] as $url) {
                if ($url['type'] == 'profile') {
                    $profile_url = $url['value'];
                    continue;
                }
                if ($url['type'] == 'zot' || $url['type'] == 'diaspora' || $url['type'] == 'friendica') {
                    $network = $url['type'];
                    $address = str_replace('acct:', '', $url['value']);
                    continue;
                }
            }
        }
        if (x($entry, 'photos') && is_array($entry['photos'])) {
            foreach ($entry['photos'] as $photo) {
                if ($photo['type'] == 'profile') {
                    $profile_photo = $photo['value'];
                    continue;
                }
            }
        }
        if (!$name || !$profile_url || !$profile_photo || !$hash || !$address) {
            logger('poco_load: missing data');
            continue;
        }
        $x = q("select xchan_hash from xchan where xchan_hash = '%s' limit 1", dbesc($hash));
        // We've never seen this person before. Import them.
        if ($x !== false && !count($x)) {
            if ($address) {
                if ($network === 'zot') {
                    $z = zot_finger($address, null);
                    if ($z['success']) {
                        $j = json_decode($z['body'], true);
                        if ($j) {
                            import_xchan($j);
                        }
                    }
                    $x = q("select xchan_hash from xchan where xchan_hash = '%s' limit 1", dbesc($hash));
                    if (!$x) {
                        continue;
                    }
                } else {
                    $x = import_author_diaspora(array('address' => $address));
                    if (!$x) {
                        continue;
                    }
                }
            } else {
                continue;
            }
        }
        $total++;
        $r = q("select * from xlink where xlink_xchan = '%s' and xlink_link = '%s' and xlink_static = 0 limit 1", dbesc($xchan), dbesc($hash));
        if (!$r) {
            q("insert into xlink ( xlink_xchan, xlink_link, xlink_updated, xlink_static ) values ( '%s', '%s', '%s', 0 ) ", dbesc($xchan), dbesc($hash), dbesc(datetime_convert()));
        } else {
            q("update xlink set xlink_updated = '%s' where xlink_id = %d", dbesc(datetime_convert()), intval($r[0]['xlink_id']));
        }
    }
    logger("poco_load: loaded {$total} entries", LOGGER_DEBUG);
    q("delete from xlink where xlink_xchan = '%s' and xlink_updated < %s - INTERVAL %s and xlink_static = 0", dbesc($xchan), db_utcnow(), db_quoteinterval('2 DAY'));
}
Esempio n. 14
0
/**
 * @brief HTTP POST entry point for Zot.
 *
 * Most access to this endpoint is via the post method.
 * Here we will pick out the magic auth params which arrive as a get request,
 * and the only communications to arrive this way.
 *
 * Magic Auth
 * ==========
 *
 * So-called "magic auth" takes place by a special exchange. On the site where the "channel to be authenticated" lives (e.g. $mysite), 
 * a redirection is made via $mysite/magic to the zot endpoint of the remote site ($remotesite) with special GET parameters.
 *
 * The endpoint is typically  https://$remotesite/post - or whatever was specified as the callback url in prior communications
 * (we will bootstrap an address and fetch a zot info packet if possible where no prior communications exist)
 *
 * Five GET parameters are supplied:
 * * auth => the urlencoded webbie (channel@host.domain) of the channel requesting access
 * * dest => the desired destination URL (urlencoded)
 * * sec  => a random string which is also stored on $mysite for use during the verification phase. 
 * * version => the zot revision
 * * delegate => optional urlencoded webbie of a local channel to invoke delegation rights for
 *
 * When this packet is received, an "auth-check" zot message is sent to $mysite.
 * (e.g. if $_GET['auth'] is foobar@podunk.edu, a zot packet is sent to the podunk.edu zot endpoint, which is typically /post)
 * If no information has been recorded about the requesting identity a zot information packet will be retrieved before
 * continuing.
 *
 * The sender of this packet is an arbitrary/random site channel. The recipients will be a single recipient corresponding
 * to the guid and guid_sig we have associated with the requesting auth identity
 *
 * \code{.json}
 * {
 *   "type":"auth_check",
 *   "sender":{
 *     "guid":"kgVFf_...",
 *     "guid_sig":"PT9-TApz...",
 *     "url":"http:\/\/podunk.edu",
 *     "url_sig":"T8Bp7j..."
 *   },
 *   "recipients":{
 *     {
 *       "guid":"ZHSqb...",
 *       "guid_sig":"JsAAXi..."
 *     }
 *   }
 *   "callback":"\/post",
 *   "version":1,
 *   "secret":"1eaa661",
 *   "secret_sig":"eKV968b1..."
 * }
 * \endcode
 *
 * auth_check messages MUST use encapsulated encryption. This message is sent to the origination site, which checks the 'secret' to see 
 * if it is the same as the 'sec' which it passed originally. It also checks the secret_sig which is the secret signed by the 
 * destination channel's private key and base64url encoded. If everything checks out, a json packet is returned:
 *
 * \code{.json}
 * {
 *   "success":1,
 *   "confirm":"q0Ysovd1u...",
 *   "service_class":(optional)
 *   "level":(optional)
 * }
 * \endcode
 *
 * 'confirm' in this case is the base64url encoded RSA signature of the concatenation of 'secret' with the
 * base64url encoded whirlpool hash of the requestor's guid and guid_sig; signed with the source channel private key. 
 * This prevents a man-in-the-middle from inserting a rogue success packet. Upon receipt and successful 
 * verification of this packet, the destination site will redirect to the original destination URL and indicate a successful remote login. 
 * Service_class can be used by cooperating sites to provide different access rights based on account rights and subscription plans. It is 
 * a string whose contents are not defined by protocol. Example: "basic" or "gold".
 *
 * @param[in,out] App &$a
 */
function post_init(&$a)
{
    if (array_key_exists('auth', $_REQUEST)) {
        $ret = array('success' => false, 'message' => '');
        logger('mod_zot: auth request received.');
        $address = $_REQUEST['auth'];
        $desturl = $_REQUEST['dest'];
        $sec = $_REQUEST['sec'];
        $version = $_REQUEST['version'];
        $delegate = $_REQUEST['delegate'];
        $test = x($_REQUEST, 'test') ? intval($_REQUEST['test']) : 0;
        // They are authenticating ultimately to the site and not to a particular channel.
        // Any channel will do, providing it's currently active. We just need to have an
        // identity to attach to the packet we send back. So find one.
        $c = q("select * from channel where channel_removed = 0 limit 1");
        if (!$c) {
            // nobody here
            logger('mod_zot: auth: unable to find a response channel');
            if ($test) {
                $ret['message'] .= 'no local channels found.' . EOL;
                json_return_and_die($ret);
            }
            goaway($desturl);
        }
        // Try and find a hubloc for the person attempting to auth
        $x = q("select * from hubloc left join xchan on xchan_hash = hubloc_hash where hubloc_addr = '%s' order by hubloc_id desc", dbesc($address));
        if (!$x) {
            // finger them if they can't be found.
            $ret = zot_finger($address, null);
            if ($ret['success']) {
                $j = json_decode($ret['body'], true);
                if ($j) {
                    import_xchan($j);
                }
                $x = q("select * from hubloc left join xchan on xchan_hash = hubloc_hash where hubloc_addr = '%s' order by hubloc_id desc", dbesc($address));
            }
        }
        if (!$x) {
            logger('mod_zot: auth: unable to finger ' . $address);
            if ($test) {
                $ret['message'] .= 'no hubloc found for ' . $address . ' and probing failed.' . EOL;
                json_return_and_die($ret);
            }
            goaway($desturl);
        }
        foreach ($x as $xx) {
            logger('mod_zot: auth request received from ' . $xx['hubloc_addr']);
            // check credentials and access
            // If they are already authenticated and haven't changed credentials,
            // we can save an expensive network round trip and improve performance.
            $remote = remote_channel();
            $result = null;
            $remote_service_class = '';
            $remote_level = 0;
            $remote_hub = $xx['hubloc_url'];
            $DNT = 0;
            // Also check that they are coming from the same site as they authenticated with originally.
            $already_authed = $remote && $xx['hubloc_hash'] == $remote && $xx['hubloc_url'] === $_SESSION['remote_hub'] ? true : false;
            if ($delegate && $delegate !== $_SESSION['delegate_channel']) {
                $already_authed = false;
            }
            $j = array();
            if (!$already_authed) {
                // Auth packets MUST use ultra top-secret hush-hush mode - e.g. the entire packet is encrypted using the site private key
                // The actual channel sending the packet ($c[0]) is not important, but this provides a generic zot packet with a sender
                // which can be verified
                $p = zot_build_packet($c[0], $type = 'auth_check', array(array('guid' => $xx['hubloc_guid'], 'guid_sig' => $xx['hubloc_guid_sig'])), $xx['hubloc_sitekey'], $sec);
                if ($test) {
                    $ret['message'] .= 'auth check packet created using sitekey ' . $xx['hubloc_sitekey'] . EOL;
                    $ret['message'] .= 'packet contents: ' . $p . EOL;
                }
                $result = zot_zot($xx['hubloc_callback'], $p);
                if (!$result['success']) {
                    logger('mod_zot: auth_check callback failed.');
                    if ($test) {
                        $ret['message'] .= 'auth check request to your site returned .' . print_r($result, true) . EOL;
                        continue;
                    }
                    continue;
                }
                $j = json_decode($result['body'], true);
                if (!$j) {
                    logger('mod_zot: auth_check json data malformed.');
                    if ($test) {
                        $ret['message'] .= 'json malformed: ' . $result['body'] . EOL;
                        continue;
                    }
                }
            }
            if ($test) {
                $ret['message'] .= 'auth check request returned .' . print_r($j, true) . EOL;
            }
            if ($already_authed || $j['success']) {
                if ($j['success']) {
                    // legit response, but we do need to check that this wasn't answered by a man-in-middle
                    if (!rsa_verify($sec . $xx['xchan_hash'], base64url_decode($j['confirm']), $xx['xchan_pubkey'])) {
                        logger('mod_zot: auth: final confirmation failed.');
                        if ($test) {
                            $ret['message'] .= 'final confirmation failed. ' . $sec . print_r($j, true) . print_r($xx, true);
                            continue;
                        }
                        continue;
                    }
                    if (array_key_exists('service_class', $j)) {
                        $remote_service_class = $j['service_class'];
                    }
                    if (array_key_exists('level', $j)) {
                        $remote_level = $j['level'];
                    }
                    if (array_key_exists('DNT', $j)) {
                        $DNT = $j['DNT'];
                    }
                }
                // everything is good... maybe
                if (local_channel()) {
                    // tell them to logout if they're logged in locally as anything but the target remote account
                    // in which case just shut up because they don't need to be doing this at all.
                    if ($a->channel['channel_hash'] != $xx['xchan_hash']) {
                        logger('mod_zot: auth: already authenticated locally as somebody else.');
                        notice(t('Remote authentication blocked. You are logged into this site locally. Please logout and retry.') . EOL);
                        if ($test) {
                            $ret['message'] .= 'already logged in locally with a conflicting identity.' . EOL;
                            continue;
                        }
                    }
                    continue;
                }
                // log them in
                if ($test) {
                    $ret['success'] = true;
                    $ret['message'] .= 'Authentication Success!' . EOL;
                    json_return_and_die($ret);
                }
                $delegation_success = false;
                if ($delegate) {
                    $r = q("select * from channel left join xchan on channel_hash = xchan_hash where xchan_addr = '%s' limit 1", dbesc($delegate));
                    if ($r && intval($r[0]['channel_id'])) {
                        $allowed = perm_is_allowed($r[0]['channel_id'], $xx['xchan_hash'], 'delegate');
                        if ($allowed) {
                            $_SESSION['delegate_channel'] = $r[0]['channel_id'];
                            $_SESSION['delegate'] = $xx['xchan_hash'];
                            $_SESSION['account_id'] = intval($r[0]['channel_account_id']);
                            require_once 'include/security.php';
                            change_channel($r[0]['channel_id']);
                            $delegation_success = true;
                        }
                    }
                }
                $_SESSION['authenticated'] = 1;
                if (!$delegation_success) {
                    $_SESSION['visitor_id'] = $xx['xchan_hash'];
                    $_SESSION['my_url'] = $xx['xchan_url'];
                    $_SESSION['my_address'] = $address;
                    $_SESSION['remote_service_class'] = $remote_service_class;
                    $_SESSION['remote_level'] = $remote_level;
                    $_SESSION['remote_hub'] = $remote_hub;
                    $_SESSION['DNT'] = $DNT;
                }
                $arr = array('xchan' => $xx, 'url' => $desturl, 'session' => $_SESSION);
                call_hooks('magic_auth_success', $arr);
                $a->set_observer($xx);
                require_once 'include/security.php';
                $a->set_groups(init_groups_visitor($_SESSION['visitor_id']));
                info(sprintf(t('Welcome %s. Remote authentication successful.'), $xx['xchan_name']));
                logger('mod_zot: auth success from ' . $xx['xchan_addr']);
            } else {
                if ($test) {
                    $ret['message'] .= 'auth failure. ' . print_r($_REQUEST, true) . print_r($j, true) . EOL;
                    continue;
                }
                logger('mod_zot: magic-auth failure - not authenticated: ' . $xx['xchan_addr']);
            }
            if ($test) {
                $ret['message'] .= 'auth failure fallthrough ' . print_r($_REQUEST, true) . print_r($j, true) . EOL;
                continue;
            }
        }
        /**
         * @FIXME we really want to save the return_url in the session before we
         * visit rmagic. This does however prevent a recursion if you visit
         * rmagic directly, as it would otherwise send you back here again.
         * But z_root() probably isn't where you really want to go.
         */
        if (strstr($desturl, z_root() . '/rmagic')) {
            goaway(z_root());
        }
        if ($test) {
            json_return_and_die($ret);
        }
        goaway($desturl);
    }
}
Esempio n. 15
0
function process_channel_sync_delivery($sender, $arr, $deliveries)
{
    /** @FIXME this will sync red structures (channel, pconfig and abook). Eventually we need to make this application agnostic. */
    $result = array();
    foreach ($deliveries as $d) {
        $r = q("select * from channel where channel_hash = '%s' limit 1", dbesc($d['hash']));
        if (!$r) {
            $result[] = array($d['hash'], 'not found');
            continue;
        }
        $channel = $r[0];
        $max_friends = service_class_fetch($channel['channel_id'], 'total_channels');
        $max_feeds = account_service_class_fetch($channel['channel_account_id'], 'total_feeds');
        if ($channel['channel_hash'] != $sender['hash']) {
            logger('process_channel_sync_delivery: possible forgery. Sender ' . $sender['hash'] . ' is not ' . $channel['channel_hash']);
            $result[] = array($d['hash'], 'channel mismatch', $channel['channel_name'], '');
            continue;
        }
        if (array_key_exists('config', $arr) && is_array($arr['config']) && count($arr['config'])) {
            foreach ($arr['config'] as $cat => $k) {
                foreach ($arr['config'][$cat] as $k => $v) {
                    set_pconfig($channel['channel_id'], $cat, $k, $v);
                }
            }
        }
        if (array_key_exists('channel', $arr) && is_array($arr['channel']) && count($arr['channel'])) {
            $disallowed = array('channel_id', 'channel_account_id', 'channel_primary', 'channel_prvkey', 'channel_address', 'channel_notifyflags');
            $clean = array();
            foreach ($arr['channel'] as $k => $v) {
                if (in_array($k, $disallowed)) {
                    continue;
                }
                $clean[$k] = $v;
            }
            if (count($clean)) {
                foreach ($clean as $k => $v) {
                    $r = dbq("UPDATE channel set " . dbesc($k) . " = '" . dbesc($v) . "' where channel_id = " . intval($channel['channel_id']));
                }
            }
        }
        if (array_key_exists('abook', $arr) && is_array($arr['abook']) && count($arr['abook'])) {
            $total_friends = 0;
            $total_feeds = 0;
            $r = q("select abook_id, abook_flags from abook where abook_channel = %d", intval($channel['channel_id']));
            if ($r) {
                // don't count yourself
                $total_friends = count($r) > 0 ? count($r) - 1 : 0;
                foreach ($r as $rr) {
                    if ($rr['abook_flags'] & ABOOK_FLAG_FEED) {
                        $total_feeds++;
                    }
                }
            }
            $disallowed = array('abook_id', 'abook_account', 'abook_channel');
            foreach ($arr['abook'] as $abook) {
                $clean = array();
                if ($abook['abook_xchan'] && $abook['entry_deleted']) {
                    logger('process_channel_sync_delivery: removing abook entry for ' . $abook['abook_xchan']);
                    require_once 'include/Contact.php';
                    $r = q("select abook_id, abook_flags from abook where abook_xchan = '%s' and abook_channel = %d and not ( abook_flags & %d )>0 limit 1", dbesc($abook['abook_xchan']), intval($channel['channel_id']), intval(ABOOK_FLAG_SELF));
                    if ($r) {
                        contact_remove($channel['channel_id'], $r[0]['abook_id']);
                        if ($total_friends) {
                            $total_friends--;
                        }
                        if ($r[0]['abook_flags'] & ABOOK_FLAG_FEED) {
                            $total_feeds--;
                        }
                    }
                    continue;
                }
                // Perform discovery if the referenced xchan hasn't ever been seen on this hub.
                // This relies on the undocumented behaviour that red sites send xchan info with the abook
                if ($abook['abook_xchan'] && $abook['xchan_address']) {
                    $h = zot_get_hublocs($abook['abook_xchan']);
                    if (!$h) {
                        $f = zot_finger($abook['xchan_address'], $channel);
                        if (!$f['success']) {
                            logger('process_channel_sync_delivery: abook not probe-able' . $abook['xchan_address']);
                            continue;
                        }
                        $j = json_decode($f['body'], true);
                        if (!($j['success'] && $j['guid'])) {
                            logger('process_channel_sync_delivery: probe failed.');
                            continue;
                        }
                        $x = import_xchan($j);
                        if (!$x['success']) {
                            logger('process_channel_sync_delivery: import failed.');
                            continue;
                        }
                    }
                }
                foreach ($abook as $k => $v) {
                    if (in_array($k, $disallowed) || strpos($k, 'abook') !== 0) {
                        continue;
                    }
                    $clean[$k] = $v;
                }
                if (!array_key_exists('abook_xchan', $clean)) {
                    continue;
                }
                $r = q("select * from abook where abook_xchan = '%s' and abook_channel = %d limit 1", dbesc($clean['abook_xchan']), intval($channel['channel_id']));
                // make sure we have an abook entry for this xchan on this system
                if (!$r) {
                    if ($max_friends !== false && $total_friends > $max_friends) {
                        logger('process_channel_sync_delivery: total_channels service class limit exceeded');
                        continue;
                    }
                    if ($max_feeds !== false && $clean['abook_flags'] & ABOOK_FLAG_FEED && $total_feeds > $max_feeds) {
                        logger('process_channel_sync_delivery: total_feeds service class limit exceeded');
                        continue;
                    }
                    q("insert into abook ( abook_xchan, abook_channel ) values ('%s', %d ) ", dbesc($clean['abook_xchan']), intval($channel['channel_id']));
                    $total_friends++;
                    if ($clean['abook_flags'] & ABOOK_FLAG_FEED) {
                        $total_feeds++;
                    }
                }
                if (count($clean)) {
                    foreach ($clean as $k => $v) {
                        if ($k == 'abook_dob') {
                            $v = dbescdate($v);
                        }
                        $r = dbq("UPDATE abook set " . dbesc($k) . " = '" . dbesc($v) . "' where abook_xchan = '" . dbesc($clean['abook_xchan']) . "' and abook_channel = " . intval($channel['channel_id']));
                    }
                }
            }
        }
        // sync collections (privacy groups) oh joy...
        if (array_key_exists('collections', $arr) && is_array($arr['collections']) && count($arr['collections'])) {
            $x = q("select * from groups where uid = %d", intval($channel['channel_id']));
            foreach ($arr['collections'] as $cl) {
                $found = false;
                if ($x) {
                    foreach ($x as $y) {
                        if ($cl['collection'] == $y['hash']) {
                            $found = true;
                            break;
                        }
                    }
                    if ($found) {
                        if ($y['name'] != $cl['name'] || $y['visible'] != $cl['visible'] || $y['deleted'] != $cl['deleted']) {
                            q("update groups set name = '%s', visible = %d, deleted = %d where hash = '%s' and uid = %d", dbesc($cl['name']), intval($cl['visible']), intval($cl['deleted']), dbesc($cl['hash']), intval($channel['channel_id']));
                        }
                        if (intval($cl['deleted']) && !intval($y['deleted'])) {
                            q("delete from group_member where gid = %d", intval($y['id']));
                        }
                    }
                }
                if (!$found) {
                    $r = q("INSERT INTO `groups` ( hash, uid, visible, deleted, name )\n\t\t\t\t\t\tVALUES( '%s', %d, %d, %d, '%s' ) ", dbesc($cl['collection']), intval($channel['channel_id']), intval($cl['visible']), intval($cl['deleted']), dbesc($cl['name']));
                }
                // now look for any collections locally which weren't in the list we just received.
                // They need to be removed by marking deleted and removing the members.
                // This shouldn't happen except for clones created before this function was written.
                if ($x) {
                    $found_local = false;
                    foreach ($x as $y) {
                        foreach ($arr['collections'] as $cl) {
                            if ($cl['collection'] == $y['hash']) {
                                $found_local = true;
                                break;
                            }
                        }
                        if (!$found_local) {
                            q("delete from group_member where gid = %d", intval($y['id']));
                            q("update groups set deleted = 1 where id = %d and uid = %d", intval($y['id']), intval($channel['channel_id']));
                        }
                    }
                }
            }
            // reload the group list with any updates
            $x = q("select * from groups where uid = %d", intval($channel['channel_id']));
            // now sync the members
            if (array_key_exists('collection_members', $arr) && is_array($arr['collection_members']) && count($arr['collection_members'])) {
                // first sort into groups keyed by the group hash
                $members = array();
                foreach ($arr['collection_members'] as $cm) {
                    if (!array_key_exists($cm['collection'], $members)) {
                        $members[$cm['collection']] = array();
                    }
                    $members[$cm['collection']][] = $cm['member'];
                }
                // our group list is already synchronised
                if ($x) {
                    foreach ($x as $y) {
                        // for each group, loop on members list we just received
                        foreach ($members[$y['hash']] as $member) {
                            $found = false;
                            $z = q("select xchan from group_member where gid = %d and uid = %d and xchan = '%s' limit 1", intval($y['id']), intval($channel['channel_id']), dbesc($member));
                            if ($z) {
                                $found = true;
                            }
                            // if somebody is in the group that wasn't before - add them
                            if (!$found) {
                                q("INSERT INTO `group_member` (`uid`, `gid`, `xchan`)\n\t\t\t\t\t\t\t\t\tVALUES( %d, %d, '%s' ) ", intval($channel['channel_id']), intval($y['id']), dbesc($member));
                            }
                        }
                        // now retrieve a list of members we have on this site
                        $m = q("select xchan from group_member where gid = %d and uid = %d", intval($y['id']), intval($channel['channel_id']));
                        if ($m) {
                            foreach ($m as $mm) {
                                // if the local existing member isn't in the list we just received - remove them
                                if (!in_array($mm['xchan'], $members[$y['hash']])) {
                                    q("delete from group_member where xchan = '%s' and gid = %d and uid = %d", dbesc($mm['xchan']), intval($y['id']), intval($channel['channel_id']));
                                }
                            }
                        }
                    }
                }
            }
        }
        if (array_key_exists('profile', $arr) && is_array($arr['profile']) && count($arr['profile'])) {
            $disallowed = array('id', 'aid', 'uid');
            foreach ($arr['profile'] as $profile) {
                $x = q("select * from profile where profile_guid = '%s' and uid = %d limit 1", dbesc($profile['profile_guid']), intval($channel['channel_id']));
                if (!$x) {
                    q("insert into profile ( profile_guid, aid, uid ) values ('%s', %d, %d)", dbesc($profile['profile_guid']), intval($channel['channel_account_id']), intval($channel['channel_id']));
                    $x = q("select * from profile where profile_guid = '%s' and uid = %d limit 1", dbesc($profile['profile_guid']), intval($channel['channel_id']));
                    if (!$x) {
                        continue;
                    }
                }
                $clean = array();
                foreach ($profile as $k => $v) {
                    if (in_array($k, $disallowed)) {
                        continue;
                    }
                    $clean[$k] = $v;
                    /**
                     * @TODO check if these are allowed, otherwise we'll error
                     * We also need to import local photos if a custom photo is selected
                     */
                }
                if (count($clean)) {
                    foreach ($clean as $k => $v) {
                        $r = dbq("UPDATE profile set " . dbesc($k) . " = '" . dbesc($v) . "' where profile_guid = '" . dbesc($profile['profile_guid']) . "' and uid = " . intval($channel['channel_id']));
                    }
                }
            }
        }
        $result[] = array($d['hash'], 'channel sync updated', $channel['channel_name'], '');
    }
    return $result;
}