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; }
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; }
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; }
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; }
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; }
/** * @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'])); } } }
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'); }
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; }
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); }
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; }
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'); }
/** * 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); }
/** * 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')); }
/** * @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); } }
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; }