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 viewsrc_content(&$a) { $o = ''; $item_id = argc() > 1 ? intval(argv(1)) : 0; $json = argc() > 2 && argv(2) === 'json' ? true : false; if (!local_user()) { notice(t('Permission denied.') . EOL); } if (!$item_id) { $a->error = 404; notice(t('Item not found.') . EOL); } if (local_user() && $item_id) { $r = q("select item_flags, body from item where item_restrict = 0 and uid = %d and id = %d limit 1", intval(local_user()), intval($item_id)); if ($r) { if ($r[0]['item_flags'] & ITEM_OBSCURED) { $r[0]['body'] = crypto_unencapsulate(json_decode($r[0]['body'], true), get_config('system', 'prvkey')); } $o = $json ? json_encode($r[0]['body']) : str_replace("\n", '<br />', $r[0]['body']); } } if (is_ajax()) { echo $o; killme(); } return $o; }
function get() { $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 = \App::get_channel(); $addr = trim($_GET['addr']); $do_import = intval($_GET['import']) && is_site_admin() ? true : false; $j = \Zotlabs\Zot\Finger::run($addr, $channel, false); // $res = zot_finger($addr,$channel,false); $o .= '<pre>'; if (!$j['success']) { $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"; $j = \Zotlabs\Zot\Finger::run($addr, $channel, true); if (!$j['success']) { $o .= sprintf(t('Fetching URL returns error: %1$s'), $res['error'] . "\r\n\r\n"); } } if ($do_import && $j) { $x = import_xchan($j); } 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 __construct($data, $prvkey, $handler) { $this->error = false; $this->validated = false; $this->messagetype = ''; $this->response = array('success' => false); $this->handler = $handler; if (!is_array($data)) { $data = json_decode($data, true); } if ($data && is_array($data)) { $this->encrypted = array_key_exists('iv', $data) ? true : false; if ($this->encrypted) { $this->data = @json_decode(@crypto_unencapsulate($data, $prvkey), true); } if (!$this->data) { $this->data = $data; } if ($this->data && is_array($this->data) && array_key_exists('type', $this->data)) { $this->messagetype = $this->data['type']; } } if (!$this->messagetype) { $this->error = true; } $this->sender = array_key_exists('sender', $this->data) ? $this->data['sender'] : null; $this->recipients = array_key_exists('recipients', $this->data) ? $this->data['recipients'] : null; if ($this->sender) { $this->ValidateSender(); } $this->Dispatch(); }
function viewsrc_content(&$a) { $o = ''; $sys = get_sys_channel(); $item_id = argc() > 1 ? intval(argv(1)) : 0; $json = argc() > 2 && argv(2) === 'json' ? true : false; if (!local_channel()) { notice(t('Permission denied.') . EOL); } if (!$item_id) { App::$error = 404; notice(t('Item not found.') . EOL); } $item_normal = item_normal(); if (local_channel() && $item_id) { $r = q("select id, item_flags, item_obscured, body from item where uid in (%d , %d) and id = %d {$item_normal} limit 1", intval(local_channel()), intval($sys['channel_id']), intval($item_id)); if ($r) { if (intval($r[0]['item_obscured'])) { $r[0]['body'] = crypto_unencapsulate(json_decode($r[0]['body'], true), get_config('system', 'prvkey')); } $o = $json ? json_encode($r[0]['body']) : str_replace("\n", '<br />', $r[0]['body']); } } if (is_ajax()) { print '<div><i class="icon-pencil"> ' . t('Source of Item') . ' ' . $r[0]['id'] . '</i></div>'; echo $o; killme(); } return $o; }
function get() { $o = ''; if (!local_channel()) { notice(t('Permission denied.') . EOL); return; } $post_id = argc() > 1 ? intval(argv(1)) : 0; if (!$post_id) { notice(t('Item not found') . EOL); return; } $itm = q("SELECT * FROM `item` WHERE `id` = %d AND ( owner_xchan = '%s' OR author_xchan = '%s' ) LIMIT 1", intval($post_id), dbesc(get_observer_hash()), dbesc(get_observer_hash())); if (!count($itm)) { notice(t('Item is not editable') . EOL); return; } if ($itm[0]['resource_type'] === 'event' && $itm[0]['resource_id']) { goaway(z_root() . '/events/' . $itm[0]['resource_id'] . '?expandform=1'); } $owner_uid = $itm[0]['uid']; $channel = \App::get_channel(); if (intval($itm[0]['item_obscured'])) { $key = get_config('system', 'prvkey'); if ($itm[0]['title']) { $itm[0]['title'] = crypto_unencapsulate(json_decode_plus($itm[0]['title']), $key); } if ($itm[0]['body']) { $itm[0]['body'] = crypto_unencapsulate(json_decode_plus($itm[0]['body']), $key); } } $category = ''; $catsenabled = feature_enabled($owner_uid, 'categories') ? 'categories' : ''; if ($catsenabled) { $itm = fetch_post_tags($itm); $cats = get_terms_oftype($itm[0]['term'], TERM_CATEGORY); foreach ($cats as $cat) { if (strlen($category)) { $category .= ', '; } $category .= $cat['term']; } } if ($itm[0]['attach']) { $j = json_decode($itm[0]['attach'], true); if ($j) { foreach ($j as $jj) { $itm[0]['body'] .= "\n" . '[attachment]' . basename($jj['href']) . ',' . $jj['revision'] . '[/attachment]' . "\n"; } } } $x = array('nickname' => $channel['channel_address'], 'editor_autocomplete' => true, 'bbco_autocomplete' => 'bbcode', 'return_path' => $_SESSION['return_url'], 'button' => t('Edit'), 'hide_voting' => true, 'hide_future' => true, 'hide_location' => true, 'mimetype' => $itm[0]['mimetype'], 'ptyp' => $itm[0]['obj_type'], 'body' => undo_post_tagging($itm[0]['body']), 'post_id' => $post_id, 'defloc' => $channel['channel_location'], 'visitor' => true, 'title' => htmlspecialchars($itm[0]['title'], ENT_COMPAT, 'UTF-8'), 'category' => $category, 'showacl' => false, 'profile_uid' => $owner_uid, 'catsenabled' => $catsenabled, 'hide_expire' => true, 'bbcode' => true); $editor = status_editor($a, $x); $o .= replace_macros(get_markup_template('edpost_head.tpl'), array('$title' => t('Edit post'), '$editor' => $editor)); return $o; }
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 get() { if (!\App::$profile) { notice(t('Requested profile is not available.') . EOL); \App::$error = 404; return; } $which = argv(1); $uid = local_channel(); $owner = 0; $channel = null; $observer = \App::get_observer(); $channel = \App::get_channel(); if (\App::$is_sys && is_site_admin()) { $sys = get_sys_channel(); if ($sys && intval($sys['channel_id'])) { $uid = $owner = intval($sys['channel_id']); $channel = $sys; $observer = $sys; } } if (!$owner) { // Figure out who the page owner is. $r = q("select channel_id from channel where channel_address = '%s'", dbesc($which)); if ($r) { $owner = intval($r[0]['channel_id']); } } $ob_hash = $observer ? $observer['xchan_hash'] : ''; if (!perm_is_allowed($owner, $ob_hash, 'write_pages')) { notice(t('Permission denied.') . EOL); return; } $is_owner = $uid && $uid == $owner ? true : false; $o = ''; // Figure out which post we're editing $post_id = argc() > 2 ? intval(argv(2)) : 0; if (!$post_id) { notice(t('Item not found') . EOL); return; } $ob_hash = $observer ? $observer['xchan_hash'] : ''; $perms = get_all_perms($owner, $ob_hash); if (!$perms['write_pages']) { notice(t('Permission denied.') . EOL); return; } // We've already figured out which item we want and whose copy we need, // so we don't need anything fancy here $sql_extra = item_permissions_sql($owner); $itm = q("SELECT * FROM `item` WHERE `id` = %d and uid = %s {$sql_extra} LIMIT 1", intval($post_id), intval($owner)); if (!$itm) { notice(t('Permission denied.') . EOL); return; } if (intval($itm[0]['item_obscured'])) { $key = get_config('system', 'prvkey'); if ($itm[0]['title']) { $itm[0]['title'] = crypto_unencapsulate(json_decode_plus($itm[0]['title']), $key); } if ($itm[0]['body']) { $itm[0]['body'] = crypto_unencapsulate(json_decode_plus($itm[0]['body']), $key); } } $item_id = q("select * from item_id where service = 'WEBPAGE' and iid = %d limit 1", intval($itm[0]['id'])); if ($item_id) { $page_title = $item_id[0]['sid']; } $mimetype = $itm[0]['mimetype']; if ($mimetype === 'application/x-php') { if (!$uid || $uid != $itm[0]['uid']) { notice(t('Permission denied.') . EOL); return; } } $layout = $itm[0]['layout_mid']; $tpl = get_markup_template("jot.tpl"); $rp = 'webpages/' . $which; $x = array('nickname' => $channel['channel_address'], 'bbco_autocomplete' => $mimetype == 'text/bbcode' ? 'bbcode' : '', 'return_path' => $rp, 'webpage' => ITEM_TYPE_WEBPAGE, 'ptlabel' => t('Page link'), 'pagetitle' => $page_title, 'writefiles' => $mimetype == 'text/bbcode' ? perm_is_allowed($owner, get_observer_hash(), 'write_storage') : false, 'button' => t('Edit'), 'weblink' => $mimetype == 'text/bbcode' ? t('Insert web link') : false, 'hide_location' => true, 'hide_voting' => true, 'ptyp' => $itm[0]['type'], 'body' => undo_post_tagging($itm[0]['body']), 'post_id' => $post_id, 'visitor' => $is_owner ? true : false, 'acl' => populate_acl($itm[0], false, \PermissionDescription::fromGlobalPermission('view_pages')), 'showacl' => $is_owner ? true : false, 'mimetype' => $mimetype, 'mimeselect' => true, 'layout' => $layout, 'layoutselect' => true, 'title' => htmlspecialchars($itm[0]['title'], ENT_COMPAT, 'UTF-8'), 'lockstate' => strlen($itm[0]['allow_cid']) || strlen($itm[0]['allow_gid']) || strlen($itm[0]['deny_cid']) || strlen($itm[0]['deny_gid']) ? 'lock' : 'unlock', 'profile_uid' => intval($owner), 'bbcode' => $mimetype == 'text/bbcode' ? true : false); $editor = status_editor($a, $x); $o .= replace_macros(get_markup_template('edpost_head.tpl'), array('$title' => t('Edit Webpage'), '$delete' => $itm[0]['author_xchan'] === $ob_hash || $itm[0]['owner_xchan'] === $ob_hash ? t('Delete') : false, '$editor' => $editor, '$id' => $itm[0]['id'])); return $o; }
function editwebpage_content(&$a) { if (!App::$profile) { notice(t('Requested profile is not available.') . EOL); App::$error = 404; return; } $which = argv(1); $uid = local_channel(); $owner = 0; $channel = null; $observer = App::get_observer(); $channel = App::get_channel(); if (App::$is_sys && is_site_admin()) { $sys = get_sys_channel(); if ($sys && intval($sys['channel_id'])) { $uid = $owner = intval($sys['channel_id']); $channel = $sys; $observer = $sys; } } if (!$owner) { // Figure out who the page owner is. $r = q("select channel_id from channel where channel_address = '%s'", dbesc($which)); if ($r) { $owner = intval($r[0]['channel_id']); } } $ob_hash = $observer ? $observer['xchan_hash'] : ''; if (!perm_is_allowed($owner, $ob_hash, 'write_pages')) { notice(t('Permission denied.') . EOL); return; } $is_owner = $uid && $uid == $owner ? true : false; $o = ''; // Figure out which post we're editing $post_id = argc() > 2 ? intval(argv(2)) : 0; if (!$post_id) { notice(t('Item not found') . EOL); return; } $ob_hash = $observer ? $observer['xchan_hash'] : ''; $perms = get_all_perms($owner, $ob_hash); if (!$perms['write_pages']) { notice(t('Permission denied.') . EOL); return; } // We've already figured out which item we want and whose copy we need, // so we don't need anything fancy here $sql_extra = item_permissions_sql($owner); $itm = q("SELECT * FROM `item` WHERE `id` = %d and uid = %s {$sql_extra} LIMIT 1", intval($post_id), intval($owner)); if (!$itm) { notice(t('Permission denied.') . EOL); return; } if (intval($itm[0]['item_obscured'])) { $key = get_config('system', 'prvkey'); if ($itm[0]['title']) { $itm[0]['title'] = crypto_unencapsulate(json_decode_plus($itm[0]['title']), $key); } if ($itm[0]['body']) { $itm[0]['body'] = crypto_unencapsulate(json_decode_plus($itm[0]['body']), $key); } } $item_id = q("select * from item_id where service = 'WEBPAGE' and iid = %d limit 1", intval($itm[0]['id'])); if ($item_id) { $page_title = $item_id[0]['sid']; } $plaintext = true; $mimetype = $itm[0]['mimetype']; if ($mimetype === 'application/x-php') { if (!$uid || $uid != $itm[0]['uid']) { notice(t('Permission denied.') . EOL); return; } } $mimeselect = ''; if ($mimetype != 'text/bbcode') { $plaintext = true; } if (get_config('system', 'page_mimetype')) { $mimeselect = '<input type="hidden" name="mimetype" value="' . $mimetype . '" />'; } else { $mimeselect = mimetype_select($itm[0]['uid'], $mimetype); } $layout = get_config('system', 'page_layout'); if ($layout) { $layoutselect = '<input type="hidden" name="layout_mid" value="' . $layout . '" />'; } else { $layoutselect = layout_select($itm[0]['uid'], $itm[0]['layout_mid']); } App::$page['htmlhead'] .= replace_macros(get_markup_template('jot-header.tpl'), array('$baseurl' => z_root(), '$editselect' => $plaintext ? 'none' : '/(profile-jot-text|prvmail-text)/', '$pretext' => '', '$ispublic' => ' ', '$geotag' => $geotag, '$nickname' => $channel['channel_address'], '$confirmdelete' => t('Delete webpage?'), '$bbco_autocomplete' => $mimetype == 'text/bbcode' ? 'bbcode' : '')); $tpl = get_markup_template("jot.tpl"); $jotplugins = ''; $jotnets = ''; call_hooks('jot_tool', $jotplugins); call_hooks('jot_networks', $jotnets); // FIXME A return path with $_SESSION doesn't always work for observer - it may WSoD // instead of loading a sensible page. So, send folk to the webpage list. $rp = 'webpages/' . $which; $editor = replace_macros($tpl, array('$return_path' => $rp, '$webpage' => ITEM_TYPE_WEBPAGE, '$placeholdpagetitle' => t('Page link title'), '$pagetitle' => $page_title, '$writefiles' => perm_is_allowed($owner, get_observer_hash(), 'write_storage'), '$action' => 'item', '$share' => t('Edit'), '$bold' => t('Bold'), '$italic' => t('Italic'), '$underline' => t('Underline'), '$quote' => t('Quote'), '$code' => t('Code'), '$upload' => t('Upload photo'), '$attach' => t('Attach file'), '$weblink' => t('Insert web link'), '$youtube' => t('Insert YouTube video'), '$video' => t('Insert Vorbis [.ogg] video'), '$audio' => t('Insert Vorbis [.ogg] audio'), '$setloc' => t('Set your location'), '$noloc' => get_pconfig($uid, 'system', 'use_browser_location') ? t('Clear browser location') : '', '$wait' => t('Please wait'), '$permset' => t('Permission settings'), '$ptyp' => $itm[0]['type'], '$content' => undo_post_tagging($itm[0]['body']), '$post_id' => $post_id, '$baseurl' => z_root(), '$defloc' => $itm[0]['location'], '$visitor' => $is_owner ? true : false, '$acl' => populate_acl($itm[0], false), '$showacl' => $is_owner ? true : false, '$public' => t('Public post'), '$jotnets' => $jotnets, '$mimeselect' => $mimeselect, '$layoutselect' => $layoutselect, '$title' => htmlspecialchars($itm[0]['title'], ENT_COMPAT, 'UTF-8'), '$placeholdertitle' => t('Title (optional)'), '$category' => '', '$placeholdercategory' => t('Categories (optional, comma-separated list)'), '$emtitle' => t('Example: bob@example.com, mary@example.com'), 'lockstate' => strlen($itm[0]['allow_cid']) || strlen($itm[0]['allow_gid']) || strlen($itm[0]['deny_cid']) || strlen($itm[0]['deny_gid']) ? 'lock' : 'unlock', '$bang' => '', '$profile_uid' => intval($owner), '$preview' => t('Preview'), '$jotplugins' => $jotplugins, '$sourceapp' => App::$sourcename, '$defexpire' => '', '$feature_expire' => false, '$expires' => t('Set expiration date'), '$bbcode' => $mimetype == 'text/bbcode' ? true : false)); $o .= replace_macros(get_markup_template('edpost_head.tpl'), array('$title' => t('Edit Webpage'), '$delete' => $itm[0]['author_xchan'] === $ob_hash || $itm[0]['owner_xchan'] === $ob_hash ? t('Delete') : false, '$editor' => $editor, '$id' => $itm[0]['id'])); return $o; }
/** * Sourced and tag-delivered posts are re-targetted for delivery to the connections of the channel * receiving the post. This starts the second delivery chain, by resetting permissions and ensuring * that ITEM_UPLINK is set on the parent post, and storing the current owner_xchan as the source_xchan. * We'll become the new owner. If called without $parent, this *is* the parent post. * * @param array $channel * @param array $item * @param int $item_id * @param boolean $parent */ function start_delivery_chain($channel, $item, $item_id, $parent) { // Change this copy of the post to a forum head message and deliver to all the tgroup members // also reset all the privacy bits to the forum default permissions $private = $channel['channel_allow_cid'] || $channel['channel_allow_gid'] || $channel['channel_deny_cid'] || $channel['channel_deny_gid'] ? 1 : 0; $new_public_policy = map_scope($channel['channel_r_stream'], true); if (!$private && $new_public_policy) { $private = 1; } $flag_bits = $item['item_flags'] | ITEM_WALL; // The message didn't necessarily originate on this site, (we'll honour it if it did), // but the parent post of this thread will be reset as a local post, as it is the top of // this delivery chain and is coming from this site, regardless of where the original // originated. if (!$parent) { $flag_bits = $flag_bits | ITEM_ORIGIN; } // unset the nocomment bit if it's there. if ($flag_bits & ITEM_NOCOMMENT) { $flag_bits = $flag_bits ^ ITEM_NOCOMMENT; } // maintain the original source, which will be the original item owner and was stored in source_xchan // when we created the delivery fork if ($parent) { $r = q("update item set source_xchan = '%s' where id = %d", dbesc($parent['source_xchan']), intval($item_id)); } else { $flag_bits = $flag_bits | ITEM_UPLINK; $r = q("update item set source_xchan = owner_xchan where id = %d", intval($item_id)); } $title = $item['title']; $body = $item['body']; if ($private) { if (!($flag_bits & ITEM_OBSCURED)) { $key = get_config('system', 'pubkey'); $flag_bits = $flag_bits | ITEM_OBSCURED; if ($title) { $title = json_encode(crypto_encapsulate($title, $key)); } if ($body) { $body = json_encode(crypto_encapsulate($body, $key)); } } } else { if ($flag_bits & ITEM_OBSCURED) { $key = get_config('system', 'prvkey'); $flag_bits = $flag_bits ^ ITEM_OBSCURED; if ($title) { $title = crypto_unencapsulate(json_decode($title, true), $key); } if ($body) { $body = crypto_unencapsulate(json_decode($body, true), $key); } } } $r = q("update item set item_flags = %d, owner_xchan = '%s', allow_cid = '%s', allow_gid = '%s',\n\t\tdeny_cid = '%s', deny_gid = '%s', item_private = %d, public_policy = '%s', comment_policy = '%s', title = '%s', body = '%s' where id = %d", intval($flag_bits), dbesc($channel['channel_hash']), dbesc($channel['channel_allow_cid']), dbesc($channel['channel_allow_gid']), dbesc($channel['channel_deny_cid']), dbesc($channel['channel_deny_gid']), intval($private), dbesc($new_public_policy), dbesc(map_scope($channel['channel_w_comment'])), dbesc($title), dbesc($body), intval($item_id)); if ($r) { proc_run('php', 'include/notifier.php', 'tgroup', $item_id); } else { logger('start_delivery_chain: failed to update item'); } }
function 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 editwebpage_content(&$a) { // We first need to figure out who owns the webpage, grab it from an argument $which = argv(1); // $a->get_channel() and stuff don't work here, so we've got to find the owner for ourselves. $r = q("select channel_id from channel where channel_address = '%s'", dbesc($which)); if ($r) { $owner = intval($r[0]['channel_id']); //logger('owner: ' . print_r($owner,true)); } $is_owner = local_user() && local_user() == $owner ? true : false; $o = ''; // Figure out which post we're editing $post_id = argc() > 2 ? intval(argv(2)) : 0; if (!$post_id) { notice(t('Item not found') . EOL); return; } // Now we've got a post and an owner, let's find out if we're allowed to edit it $observer = $a->get_observer(); $ob_hash = $observer ? $observer['xchan_hash'] : ''; $perms = get_all_perms($owner, $ob_hash); if (!$perms['write_pages']) { notice(t('Permission denied.') . EOL); return; } // We've already figured out which item we want and whose copy we need, so we don't need anything fancy here $itm = q("SELECT * FROM `item` WHERE `id` = %d and uid = %s LIMIT 1", intval($post_id), intval($owner)); if ($itm[0]['item_flags'] & ITEM_OBSCURED) { $key = get_config('system', 'prvkey'); if ($itm[0]['title']) { $itm[0]['title'] = crypto_unencapsulate(json_decode_plus($itm[0]['title']), $key); } if ($itm[0]['body']) { $itm[0]['body'] = crypto_unencapsulate(json_decode_plus($itm[0]['body']), $key); } } $item_id = q("select * from item_id where service = 'WEBPAGE' and iid = %d limit 1", $itm[0]['id']); if ($item_id) { $page_title = $item_id[0]['sid']; } $plaintext = true; // if(feature_enabled($itm[0]['uid'],'richtext')) // $plaintext = false; $mimetype = $itm[0]['mimetype']; if ($mimetype === 'application/x-php') { if (!local_user() || local_user() != $itm[0]['uid']) { notice(t('Permission denied.') . EOL); return; } } $mimeselect = ''; if ($mimetype != 'text/bbcode') { $plaintext = true; } if (get_config('system', 'page_mimetype')) { $mimeselect = '<input type="hidden" name="mimetype" value="' . $mimetype . '" />'; } else { $mimeselect = mimetype_select($itm[0]['uid'], $mimetype); } $layout = get_config('system', 'page_layout'); if ($layout) { $layoutselect = '<input type="hidden" name="layout_mid" value="' . $layout . '" />'; } else { $layoutselect = layout_select($itm[0]['uid'], $itm[0]['layout_mid']); } $o .= replace_macros(get_markup_template('edpost_head.tpl'), array('$title' => t('Edit Webpage'))); $a->page['htmlhead'] .= replace_macros(get_markup_template('jot-header.tpl'), array('$baseurl' => $a->get_baseurl(), '$editselect' => $plaintext ? 'none' : '/(profile-jot-text|prvmail-text)/', '$ispublic' => ' ', '$geotag' => $geotag, '$nickname' => $a->user['nickname'], '$confirmdelete' => t('Delete webpage?'))); $tpl = get_markup_template("jot.tpl"); $jotplugins = ''; $jotnets = ''; call_hooks('jot_tool', $jotplugins); call_hooks('jot_networks', $jotnets); $channel = $a->get_channel(); //$tpl = replace_macros($tpl,array('$jotplugins' => $jotplugins)); //FIXME A return path with $_SESSION doesn't always work for observer - it may WSoD instead of loading a sensible page. So, send folk to the webpage list. $rp = 'webpages/' . $which; $o .= replace_macros($tpl, array('$return_path' => $rp, '$webpage' => ITEM_WEBPAGE, '$placeholdpagetitle' => t('Page link title'), '$pagetitle' => $page_title, '$action' => 'item', '$share' => t('Edit'), '$upload' => t('Upload photo'), '$attach' => t('Attach file'), '$weblink' => t('Insert web link'), '$youtube' => t('Insert YouTube video'), '$video' => t('Insert Vorbis [.ogg] video'), '$audio' => t('Insert Vorbis [.ogg] audio'), '$setloc' => t('Set your location'), '$noloc' => t('Clear browser location'), '$wait' => t('Please wait'), '$permset' => t('Permission settings'), '$ptyp' => $itm[0]['type'], '$content' => undo_post_tagging($itm[0]['body']), '$post_id' => $post_id, '$baseurl' => $a->get_baseurl(), '$defloc' => $itm[0]['location'], '$visitor' => $is_owner ? true : false, '$acl' => populate_acl($itm[0], false), '$showacl' => $is_owner ? true : false, '$public' => t('Public post'), '$jotnets' => $jotnets, '$mimeselect' => $mimeselect, '$layoutselect' => $layoutselect, '$title' => htmlspecialchars($itm[0]['title'], ENT_COMPAT, 'UTF-8'), '$placeholdertitle' => t('Set title'), '$category' => '', '$placeholdercategory' => t('Categories (comma-separated list)'), '$emtitle' => t('Example: bob@example.com, mary@example.com'), 'lockstate' => strlen($itm[0]['allow_cid']) || strlen($itm[0]['allow_gid']) || strlen($itm[0]['deny_cid']) || strlen($itm[0]['deny_gid']) ? 'lock' : 'unlock', '$bang' => '', '$profile_uid' => intval($owner), '$preview' => feature_enabled(local_user(), 'preview') ? t('Preview') : '', '$jotplugins' => $jotplugins, '$sourceapp' => t($a->sourcename), '$defexpire' => '', '$feature_expire' => false, '$expires' => t('Set expiration date'))); $ob = get_observer_hash(); if ($itm[0]['author_xchan'] === $ob || $itm[0]['owner_xchan'] === $ob) { $o .= '<br /><br /><a class="page-delete-link" href="item/drop/' . $itm[0]['id'] . '" >' . t('Delete Webpage') . '</a><br />'; } return $o; }
function editpost_content(&$a) { $o = ''; if (!local_channel()) { notice(t('Permission denied.') . EOL); return; } $post_id = argc() > 1 ? intval(argv(1)) : 0; if (!$post_id) { notice(t('Item not found') . EOL); return; } $itm = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d and author_xchan = '%s' LIMIT 1", intval($post_id), intval(local_channel()), dbesc(get_observer_hash())); if (!count($itm)) { notice(t('Item is not editable') . EOL); return; } $plaintext = true; // if(feature_enabled(local_channel(),'richtext')) // $plaintext = false; $channel = $a->get_channel(); $a->page['htmlhead'] .= replace_macros(get_markup_template('jot-header.tpl'), array('$baseurl' => $a->get_baseurl(), '$editselect' => $plaintext ? 'none' : '/(profile-jot-text|prvmail-text)/', '$ispublic' => ' ', '$geotag' => $geotag, '$nickname' => $channel['channel_address'], '$expireswhen' => t('Expires YYYY-MM-DD HH:MM'), '$confirmdelete' => t('Delete item?'))); if ($itm[0]['item_flags'] & ITEM_OBSCURED) { $key = get_config('system', 'prvkey'); if ($itm[0]['title']) { $itm[0]['title'] = crypto_unencapsulate(json_decode_plus($itm[0]['title']), $key); } if ($itm[0]['body']) { $itm[0]['body'] = crypto_unencapsulate(json_decode_plus($itm[0]['body']), $key); } } $tpl = get_markup_template("jot.tpl"); $jotplugins = ''; $jotnets = ''; call_hooks('jot_tool', $jotplugins); call_hooks('jot_networks', $jotnets); $channel = $a->get_channel(); //$tpl = replace_macros($tpl,array('$jotplugins' => $jotplugins)); $voting = feature_enabled(local_channel(), 'consensus_tools'); $category = ''; $catsenabled = feature_enabled(local_channel(), 'categories') ? 'categories' : ''; if ($catsenabled) { $itm = fetch_post_tags($itm); $cats = get_terms_oftype($itm[0]['term'], TERM_CATEGORY); foreach ($cats as $cat) { if (strlen($category)) { $category .= ', '; } $category .= $cat['term']; } } if ($itm[0]['attach']) { $j = json_decode($itm[0]['attach'], true); if ($j) { foreach ($j as $jj) { $itm[0]['body'] .= "\n" . '[attachment]' . basename($jj['href']) . ',' . $jj['revision'] . '[/attachment]' . "\n"; } } } $cipher = get_pconfig(get_app()->profile['profile_uid'], 'system', 'default_cipher'); if (!$cipher) { $cipher = 'aes256'; } $editor = replace_macros($tpl, array('$return_path' => $_SESSION['return_url'], '$action' => 'item', '$share' => t('Edit'), '$bold' => t('Bold'), '$italic' => t('Italic'), '$underline' => t('Underline'), '$quote' => t('Quote'), '$code' => t('Code'), '$upload' => t('Upload photo'), '$attach' => t('Attach file'), '$weblink' => t('Insert web link'), '$youtube' => t('Insert YouTube video'), '$video' => t('Insert Vorbis [.ogg] video'), '$audio' => t('Insert Vorbis [.ogg] audio'), '$setloc' => t('Set your location'), '$noloc' => t('Clear browser location'), '$voting' => t('Toggle voting'), '$feature_voting' => $voting, '$consensus' => $itm[0]['item_flags'] & ITEM_CONSENSUS ? 1 : 0, '$wait' => t('Please wait'), '$permset' => t('Permission settings'), '$ptyp' => $itm[0]['type'], '$content' => undo_post_tagging($itm[0]['body']), '$post_id' => $post_id, '$parent' => $itm[0]['parent'] != $itm[0]['id'] ? $itm[0]['parent'] : '', '$baseurl' => $a->get_baseurl(), '$defloc' => $channel['channel_location'], '$visitor' => false, '$public' => t('Public post'), '$jotnets' => $jotnets, '$title' => htmlspecialchars($itm[0]['title'], ENT_COMPAT, 'UTF-8'), '$placeholdertitle' => t('Title (optional)'), '$category' => $category, '$placeholdercategory' => t('Categories (optional, comma-separated list)'), '$emtitle' => t('Example: bob@example.com, mary@example.com'), '$lockstate' => $lockstate, '$acl' => '', '$bang' => '', '$profile_uid' => local_channel(), '$preview' => t('Preview'), '$jotplugins' => $jotplugins, '$sourceapp' => t($a->sourcename), '$catsenabled' => $catsenabled, '$defexpire' => datetime_convert('UTC', date_default_timezone_get(), $itm[0]['expires']), '$feature_expire' => feature_enabled(get_app()->profile['profile_uid'], 'content_expire') && !$webpage ? true : false, '$expires' => t('Set expiration date'), '$feature_encrypt' => feature_enabled(get_app()->profile['profile_uid'], 'content_encrypt') && !$webpage ? true : false, '$encrypt' => t('Encrypt text'), '$cipher' => $cipher, '$expiryModalOK' => t('OK'), '$expiryModalCANCEL' => t('Cancel'))); $o .= replace_macros(get_markup_template('edpost_head.tpl'), array('$title' => t('Edit post'), '$editor' => $editor)); return $o; }
/** * @function post_post(&$a) * zot communications and messaging * * Sender HTTP posts to this endpoint ($site/post typically) with 'data' parameter set to json zot message packet. * This packet is optionally encrypted, which we will discover if the json has an 'iv' element. * $contents => array( 'alg' => 'aes256cbc', 'iv' => initialisation vector, 'key' => decryption key, 'data' => encrypted data); * $contents->iv and $contents->key are random strings encrypted with this site's RSA public key and then base64url encoded. * Currently only 'aes256cbc' is used, but this is extensible should that algorithm prove inadequate. * * Once decrypted, one will find the normal json_encoded zot message packet. * * Defined packet types are: notify, purge, refresh, force_refresh, auth_check, ping, and pickup * * Standard packet: (used by notify, purge, refresh, force_refresh, and auth_check) * * { * "type": "notify", * "sender":{ * "guid":"kgVFf_1...", * "guid_sig":"PT9-TApzp...", * "url":"http:\/\/podunk.edu", * "url_sig":"T8Bp7j5...", * }, * "recipients": { optional recipient array }, * "callback":"\/post", * "version":1, * "secret":"1eaa...", * "secret_sig": "df89025470fac8..." * } * * Signature fields are all signed with the sender channel private key and base64url encoded. * Recipients are arrays of guid and guid_sig, which were previously signed with the recipients private * key and base64url encoded and later obtained via channel discovery. Absence of recipients indicates * a public message or visible to all potential listeners on this site. * * "pickup" packet: * The pickup packet is sent in response to a notify packet from another site * * { * "type":"pickup", * "url":"http:\/\/example.com", * "callback":"http:\/\/example.com\/post", * "callback_sig":"teE1_fLI...", * "secret":"1eaa...", * "secret_sig":"O7nB4_..." * } * * In the pickup packet, the sig fields correspond to the respective data element signed with this site's system * private key and then base64url encoded. * The "secret" is the same as the original secret from the notify packet. * * If verification is successful, a json structure is returned * containing a success indicator and an array of type 'pickup'. * Each pickup element contains the original notify request and a message field whose contents are * dependent on the message type * * This JSON array is AES encapsulated using the site public key of the site that sent the initial zot pickup packet. * Using the above example, this would be example.com. * * * { * "success":1, * "pickup":{ * "notify":{ * "type":"notify", * "sender":{ * "guid":"kgVFf_...", * "guid_sig":"PT9-TApz...", * "url":"http:\/\/z.podunk.edu", * "url_sig":"T8Bp7j5D..." * }, * "callback":"\/post", * "version":1, * "secret":"1eaa661..." * }, * "message":{ * "type":"activity", * "message_id":"*****@*****.**", * "message_top":"*****@*****.**", * "message_parent":"*****@*****.**", * "created":"2012-11-20 04:04:16", * "edited":"2012-11-20 04:04:16", * "title":"", * "body":"Hi Nickordo", * "app":"", * "verb":"post", * "object_type":"", * "target_type":"", * "permalink":"", * "location":"", * "longlat":"", * "owner":{ * "name":"Indigo", * "address":"*****@*****.**", * "url":"http:\/\/podunk.edu", * "photo":{ * "mimetype":"image\/jpeg", * "src":"http:\/\/podunk.edu\/photo\/profile\/m\/5" * }, * "guid":"kgVFf_...", * "guid_sig":"PT9-TAp...", * }, * "author":{ * "name":"Indigo", * "address":"*****@*****.**", * "url":"http:\/\/podunk.edu", * "photo":{ * "mimetype":"image\/jpeg", * "src":"http:\/\/podunk.edu\/photo\/profile\/m\/5" * }, * "guid":"kgVFf_...", * "guid_sig":"PT9-TAp..." * } * } * } *} * * Currently defined message types are 'activity', 'mail', 'profile' and 'channel_sync', which each have * different content schemas. * * Ping packet: * A ping packet does not require any parameters except the type. It may or may not be encrypted. * * { * "type": "ping" * } * * On receipt of a ping packet a ping response will be returned: * * { * "success" : 1, * "site" { * "url":"http:\/\/podunk.edu", * "url_sig":"T8Bp7j5...", * "sitekey": "-----BEGIN PUBLIC KEY----- * MIICIjANBgkqhkiG9w0BAQE..." * } * } * * The ping packet can be used to verify that a site has not been re-installed, and to * initiate corrective action if it has. The url_sig is signed with the site private key * and base64url encoded - and this should verify with the enclosed sitekey. Failure to * verify indicates the site is corrupt or otherwise unable to communicate using zot. * This return packet is not otherwise verified, so should be compared with other * results obtained from this site which were verified prior to taking action. For instance * if you have one verified result with this signature and key, and other records for this * url which have different signatures and keys, it indicates that the site was re-installed * and corrective action may commence (remove or mark invalid any entries with different * signatures). * If you have no records which match this url_sig and key - no corrective action should * be taken as this packet may have been returned by an imposter. * */ function post_post(&$a) { $encrypted_packet = false; $ret = array('success' => false); $data = json_decode($_REQUEST['data'], true); /** * Many message packets will arrive encrypted. The existence of an 'iv' element * tells us we need to unencapsulate the AES-256-CBC content using the site private key */ if (array_key_exists('iv', $data)) { $encrypted_packet = true; $data = crypto_unencapsulate($data, get_config('system', 'prvkey')); logger('mod_zot: decrypt1: ' . $data, LOGGER_DATA); $data = json_decode($data, true); } if (!$data) { // possible Bleichenbacher's attack, just treat it as a // message we have no handler for. It should fail a bit // further along with "no hub". Our public key is public // knowledge. There's no reason why anybody should get the // encryption wrong unless they're fishing or hacking. If // they're developing and made a goof, this can be discovered // in the logs of the destination site. If they're fishing or // hacking, the bottom line is we can't verify their hub. // That's all we're going to tell them. $data = array('type' => 'bogus'); } $msgtype = array_key_exists('type', $data) ? $data['type'] : ''; if ($msgtype === 'ping') { // Useful to get a health check on a remote site. // This will let us know if any important communication details // that we may have stored are no longer valid, regardless of xchan details. logger('POST: got ping send pong now back: ' . z_root(), LOGGER_DEBUG); $ret['success'] = true; $ret['site'] = array(); $ret['site']['url'] = z_root(); $ret['site']['url_sig'] = base64url_encode(rsa_sign(z_root(), get_config('system', 'prvkey'))); $ret['site']['sitekey'] = get_config('system', 'pubkey'); json_return_and_die($ret); } if ($msgtype === 'pickup') { /** * The 'pickup' message arrives with a tracking ID which is associated with a particular outq_hash * First verify that that the returned signatures verify, then check that we have an outbound queue item * with the correct hash. * If everything verifies, find any/all outbound messages in the queue for this hubloc and send them back * */ if (!$data['secret'] || !$data['secret_sig']) { $ret['message'] = 'no verification signature'; logger('mod_zot: pickup: ' . $ret['message'], LOGGER_DEBUG); json_return_and_die($ret); } $r = q("select distinct hubloc_sitekey from hubloc where hubloc_url = '%s' and hubloc_callback = '%s' and hubloc_sitekey != '' group by hubloc_sitekey ", dbesc($data['url']), dbesc($data['callback'])); if (!$r) { $ret['message'] = 'site not found'; logger('mod_zot: pickup: ' . $ret['message']); json_return_and_die($ret); } foreach ($r as $hubsite) { // verify the url_sig // If the server was re-installed at some point, there could be multiple hubs with the same url and callback. // Only one will have a valid key. $forgery = true; $secret_fail = true; $sitekey = $hubsite['hubloc_sitekey']; logger('mod_zot: Checking sitekey: ' . $sitekey, LOGGER_DATA); if (rsa_verify($data['callback'], base64url_decode($data['callback_sig']), $sitekey)) { $forgery = false; } if (rsa_verify($data['secret'], base64url_decode($data['secret_sig']), $sitekey)) { $secret_fail = false; } if (!$forgery && !$secret_fail) { break; } } if ($forgery) { $ret['message'] = 'possible site forgery'; logger('mod_zot: pickup: ' . $ret['message']); json_return_and_die($ret); } if ($secret_fail) { $ret['message'] = 'secret validation failed'; logger('mod_zot: pickup: ' . $ret['message']); json_return_and_die($ret); } /** * If we made it to here, the signatures verify, but we still don't know if the tracking ID is valid. * It wouldn't be an error if the tracking ID isn't found, because we may have sent this particular * queue item with another pickup (after the tracking ID for the other pickup was verified). */ $r = q("select outq_posturl from outq where outq_hash = '%s' and outq_posturl = '%s' limit 1", dbesc($data['secret']), dbesc($data['callback'])); if (!$r) { $ret['message'] = 'nothing to pick up'; logger('mod_zot: pickup: ' . $ret['message']); json_return_and_die($ret); } /** * Everything is good if we made it here, so find all messages that are going to this location * and send them all. */ $r = q("select * from outq where outq_posturl = '%s'", dbesc($data['callback'])); if ($r) { logger('mod_zot: succesful pickup message received from ' . $data['callback'] . ' ' . count($r) . ' message(s) picked up', LOGGER_DEBUG); $ret['success'] = true; $ret['pickup'] = array(); foreach ($r as $rr) { $ret['pickup'][] = array('notify' => json_decode($rr['outq_notify'], true), 'message' => json_decode($rr['outq_msg'], true)); $x = q("delete from outq where outq_hash = '%s' limit 1", dbesc($rr['outq_hash'])); } } $encrypted = crypto_encapsulate(json_encode($ret), $sitekey); json_return_and_die($encrypted); /** pickup: end */ } /** * All other message types require us to verify the sender. This is a generic check, so we * will do it once here and bail if anything goes wrong. */ if (array_key_exists('sender', $data)) { $sender = $data['sender']; } /** Check if the sender is already verified here */ $hub = zot_gethub($sender); if (!$hub) { /** Have never seen this guid or this guid coming from this location. Check it and register it. */ // (!!) this will validate the sender $result = zot_register_hub($sender); if (!$result['success'] || !($hub = zot_gethub($sender))) { $ret['message'] = 'Hub not available.'; logger('mod_zot: no hub'); json_return_and_die($ret); } } // Update our DB to show when we last communicated successfully with this hub // This will allow us to prune dead hubs from using up resources $r = q("update hubloc set hubloc_connected = '%s' where hubloc_id = %d limit 1", dbesc(datetime_convert()), intval($hub['hubloc_id'])); // a dead hub came back to life - reset any tombstones we might have if ($hub['hubloc_status'] & HUBLOC_OFFLINE) { q("update hubloc set hubloc_status = (hubloc_status ^ %d) where hubloc_id = %d limit 1", intval(HUBLOC_OFFLINE), intval($hub['hubloc_id'])); if ($r[0]['hubloc_flags'] & HUBLOC_FLAGS_ORPHANCHECK) { q("update hubloc set hubloc_flags = (hubloc_flags ^ %d) where hubloc_id = %d limit 1", intval(HUBLOC_FLAGS_ORPHANCHECK), intval($hub['hubloc_id'])); } q("update xchan set xchan_flags = (xchan_flags ^ %d) where (xchan_flags & %d) and xchan_hash = '%s' limit 1", intval(XCHAN_FLAGS_ORPHAN), intval(XCHAN_FLAGS_ORPHAN), dbesc($hub['hubloc_hash'])); } /** * This hub has now been proven to be valid. * Any hub with the same URL and a different sitekey cannot be valid. * Get rid of them (mark them deleted). There's a good chance they were re-installs. * */ q("update hubloc set hubloc_flags = ( hubloc_flags | %d ) where hubloc_url = '%s' and hubloc_sitekey != '%s' ", intval(HUBLOC_FLAGS_DELETED), dbesc($hub['hubloc_url']), dbesc($hub['hubloc_sitekey'])); // TODO: check which hub is primary and take action if mismatched if (array_key_exists('recipients', $data)) { $recipients = $data['recipients']; } if ($msgtype === 'auth_check') { /** * Requestor visits /magic/?dest=somewhere on their own site with a browser * magic redirects them to $destsite/post [with auth args....] * $destsite sends an auth_check packet to originator site * The auth_check packet is handled here by the originator's site * - the browser session is still waiting * inside $destsite/post for everything to verify * If everything checks out we'll return a token to $destsite * and then $destsite will verify the token, authenticate the browser * session and then redirect to the original destination. * If authentication fails, the redirection to the original destination * will still take place but without authentication. */ logger('mod_zot: auth_check', LOGGER_DEBUG); if (!$encrypted_packet) { logger('mod_zot: auth_check packet was not encrypted.'); $ret['message'] .= 'no packet encryption' . EOL; json_return_and_die($ret); } $arr = $data['sender']; $sender_hash = make_xchan_hash($arr['guid'], $arr['guid_sig']); // garbage collect any old unused notifications q("delete from verify where type = 'auth' and created < UTC_TIMESTAMP() - INTERVAL 10 MINUTE"); $y = q("select xchan_pubkey from xchan where xchan_hash = '%s' limit 1", dbesc($sender_hash)); // We created a unique hash in mod/magic.php when we invoked remote auth, and stored it in // the verify table. It is now coming back to us as 'secret' and is signed by a channel at the other end. // First verify their signature. We will have obtained a zot-info packet from them as part of the sender // verification. if (!$y || !rsa_verify($data['secret'], base64url_decode($data['secret_sig']), $y[0]['xchan_pubkey'])) { logger('mod_zot: auth_check: sender not found or secret_sig invalid.'); $ret['message'] .= 'sender not found or sig invalid ' . print_r($y, true) . EOL; json_return_and_die($ret); } // There should be exactly one recipient, the original auth requestor $ret['message'] .= 'recipients ' . print_r($recipients, true) . EOL; if ($data['recipients']) { $arr = $data['recipients'][0]; $recip_hash = make_xchan_hash($arr['guid'], $arr['guid_sig']); $c = q("select channel_id, channel_account_id, channel_prvkey from channel where channel_hash = '%s' limit 1", dbesc($recip_hash)); if (!$c) { logger('mod_zot: auth_check: recipient channel not found.'); $ret['message'] .= 'recipient not found.' . EOL; json_return_and_die($ret); } $confirm = base64url_encode(rsa_sign($data['secret'] . $recip_hash, $c[0]['channel_prvkey'])); // This additionally checks for forged sites since we already stored the expected result in meta // and we've already verified that this is them via zot_gethub() and that their key signed our token $z = q("select id from verify where channel = %d and type = 'auth' and token = '%s' and meta = '%s' limit 1", intval($c[0]['channel_id']), dbesc($data['secret']), dbesc($data['sender']['url'])); if (!$z) { logger('mod_zot: auth_check: verification key not found.'); $ret['message'] .= 'verification key not found' . EOL; json_return_and_die($ret); } $r = q("delete from verify where id = %d limit 1", intval($z[0]['id'])); $u = q("select account_service_class from account where account_id = %d limit 1", intval($c[0]['channel_account_id'])); logger('mod_zot: auth_check: success', LOGGER_DEBUG); $ret['success'] = true; $ret['confirm'] = $confirm; if ($u && $u[0]['account_service_class']) { $ret['service_class'] = $u[0]['account_service_class']; } // Set "do not track" flag if this site or this channel's profile is restricted if (intval(get_config('system', 'block_public'))) { $ret['DNT'] = true; } if (!perm_is_allowed($c[0]['channel_id'], '', 'view_profile')) { $ret['DNT'] = true; } if (get_pconfig($c[0]['channel_id'], 'system', 'do_not_track')) { $ret['DNT'] = true; } json_return_and_die($ret); } json_return_and_die($ret); } if ($msgtype === 'purge') { if ($recipients) { // basically this means "unfriend" foreach ($recipients as $recip) { $r = q("select channel.*,xchan.* from channel \n\t\t\t\t\tleft join xchan on channel_hash = xchan_hash\n\t\t\t\t\twhere channel_guid = '%s' and channel_guid_sig = '%s' limit 1", dbesc($recip['guid']), dbesc($recip['guid_sig'])); if ($r) { $r = q("select abook_id from abook where uid = %d and abook_xchan = '%s' limit 1", intval($r[0]['channel_id']), dbesc(make_xchan_hash($sender['guid'], $sender['guid_sig']))); if ($r) { contact_remove($r[0]['channel_id'], $r[0]['abook_id']); } } } } else { // Unfriend everybody - basically this means the channel has committed suicide $arr = $data['sender']; $sender_hash = make_xchan_hash($arr['guid'], $arr['guid_sig']); require_once 'include/Contact.php'; remove_all_xchan_resources($sender_hash); $ret['success'] = true; json_return_and_die($ret); } } if ($msgtype === 'refresh' || $msgtype === 'force_refresh') { // remote channel info (such as permissions or photo or something) // has been updated. Grab a fresh copy and sync it. // The difference between refresh and force_refresh is that // force_refresh unconditionally creates a directory update record, // even if no changes were detected upon processing. if ($recipients) { // This would be a permissions update, typically for one connection foreach ($recipients as $recip) { $r = q("select channel.*,xchan.* from channel \n\t\t\t\t\tleft join xchan on channel_hash = xchan_hash\n\t\t\t\t\twhere channel_guid = '%s' and channel_guid_sig = '%s' limit 1", dbesc($recip['guid']), dbesc($recip['guid_sig'])); $x = zot_refresh(array('xchan_guid' => $sender['guid'], 'xchan_guid_sig' => $sender['guid_sig'], 'hubloc_url' => $sender['url']), $r[0], $msgtype === 'force_refresh' ? true : false); } } else { // system wide refresh $x = zot_refresh(array('xchan_guid' => $sender['guid'], 'xchan_guid_sig' => $sender['guid_sig'], 'hubloc_url' => $sender['url']), null, $msgtype === 'force_refresh' ? true : false); } $ret['success'] = true; json_return_and_die($ret); } if ($msgtype === 'notify') { $async = get_config('system', 'queued_fetch'); if ($async) { // add to receive queue // qreceive_add($data); } else { $x = zot_fetch($data); $ret['delivery_report'] = $x; } $ret['success'] = true; json_return_and_die($ret); } // catchall json_return_and_die($ret); }
function 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'); }
function bb2diaspora_itembody($item, $force_update = false) { $matches = array(); if ($item['diaspora_meta'] && !$force_update) { $diaspora_meta = json_decode($item['diaspora_meta'], true); if ($diaspora_meta) { if (array_key_exists('iv', $diaspora_meta)) { $key = get_config('system', 'prvkey'); $meta = json_decode(crypto_unencapsulate($diaspora_meta, $key), true); } else { $meta = $diaspora_meta; } if ($meta) { logger('bb2diaspora_itembody: cached '); $newitem = $item; $newitem['body'] = $meta['body']; return $newitem['body']; } } } create_export_photo_body($item); $newitem = $item; if (array_key_exists('item_obscured', $item) && intval($item['item_obscured'])) { $key = get_config('system', 'prvkey'); $b = json_decode($item['body'], true); // if called from diaspora_process_outbound, this decoding has already been done. // Everything else that calls us will not yet be decoded. if ($b && is_array($b) && array_key_exists('iv', $b)) { $newitem['title'] = $item['title'] ? crypto_unencapsulate(json_decode($item['title'], true), $key) : ''; $newitem['body'] = $item['body'] ? crypto_unencapsulate(json_decode($item['body'], true), $key) : ''; } } bb2diaspora_itemwallwall($newitem); $title = $newitem['title']; $body = preg_replace('/\\#\\^http/i', 'http', $newitem['body']); // protect tags and mentions from hijacking if (intval(get_pconfig($item['uid'], 'system', 'prevent_tag_hijacking'))) { $new_tag = html_entity_decode('⋕', ENT_COMPAT, 'UTF-8'); $new_mention = html_entity_decode('@', ENT_COMPAT, 'UTF-8'); // #-tags $body = preg_replace('/\\#\\[url/i', $new_tag . '[url', $body); $body = preg_replace('/\\#\\[zrl/i', $new_tag . '[zrl', $body); // @-mentions $body = preg_replace('/\\@\\!?\\[url/i', $new_mention . '[url', $body); $body = preg_replace('/\\@\\!?\\[zrl/i', $new_mention . '[zrl', $body); } // remove multiple newlines do { $oldbody = $body; $body = str_replace("\n\n\n", "\n\n", $body); } while ($oldbody != $body); $body = bb2diaspora($body); if (strlen($title)) { $body = "## " . $title . "\n\n" . $body; } if ($item['attach']) { $cnt = preg_match_all('/href=\\"(.*?)\\"(.*?)title=\\"(.*?)\\"/ism', $item['attach'], $matches, PREG_SET_ORDER); if ($cnt) { $body .= "\n" . t('Attachments:') . "\n"; foreach ($matches as $mtch) { $body .= '[' . $mtch[3] . '](' . $mtch[1] . ')' . "\n"; } } } // logger('bb2diaspora_itembody : ' . $body, LOGGER_DATA); return html_entity_decode($body); }
function bb2diaspora_itembody($item) { if ($item['diaspora_meta']) { $j = json_decode($item['diaspora_meta'], true); if ($j && $j['body']) { logger('bb2diaspora_itembody: cached '); return $j['body']; } } $body = $item['body']; if (array_key_exists('item_flags', $item) && $item['item_flags'] & ITEM_OBSCURED) { $key = get_config('system', 'prvkey'); $title = $item['title'] ? crypto_unencapsulate(json_decode($item['title'], true), $key) : ''; $body = $item['body'] ? crypto_unencapsulate(json_decode($item['body'], true), $key) : ''; } $body = preg_replace('/\\#\\^http/i', 'http', $body); // protect tags and mentions from hijacking if (intval(get_pconfig($item['uid'], 'system', 'prevent_tag_hijacking'))) { $new_tag = html_entity_decode('⋕', ENT_COMPAT, 'UTF-8'); $new_mention = html_entity_decode('@', ENT_COMPAT, 'UTF-8'); // #-tags $body = preg_replace('/\\#\\[url/i', $new_tag . '[url', $body); $body = preg_replace('/\\#\\[zrl/i', $new_tag . '[zrl', $body); // @-mentions $body = preg_replace('/\\@\\!?\\[url/i', $new_mention . '[url', $body); $body = preg_replace('/\\@\\!?\\[zrl/i', $new_mention . '[zrl', $body); } // remove multiple newlines do { $oldbody = $body; $body = str_replace("\n\n\n", "\n\n", $body); } while ($oldbody != $body); $body = bb2diaspora($body); if (strlen($title)) { $body = "## " . $title . "\n\n" . $body; } if ($item['attach']) { $cnt = preg_match_all('/href=\\"(.*?)\\"(.*?)title=\\"(.*?)\\"/ism', $item['attach'], $matches, PREG_SET_ORDER); if (cnt) { $body .= "\n" . t('Attachments:') . "\n"; foreach ($matches as $mtch) { $body .= '[' . $mtch[3] . '](' . $mtch[1] . ')' . "\n"; } } } logger('bb2diaspora_itembody : ' . $body); return html_entity_decode($body); }
function unobscure(&$item) { if (array_key_exists('item_obscured', $item) && intval($item['item_obscured'])) { $key = get_config('system', 'prvkey'); if ($item['title']) { $item['title'] = crypto_unencapsulate(json_decode_plus($item['title']), $key); } if ($item['body']) { $item['body'] = crypto_unencapsulate(json_decode_plus($item['body']), $key); } if (get_config('system', 'item_cache')) { q("update item set title = '%s', body = '%s', item_obscured = 0 where id = %d", dbesc($item['title']), dbesc($item['body']), intval($item['id'])); } } }
function private_messages_fetch_conversation($channel_id, $messageitem_id, $updateseen = false) { // find the parent_mid of the message being requested $r = q("SELECT parent_mid from mail WHERE channel_id = %d and id = %d limit 1", intval($channel_id), intval($messageitem_id)); if (!$r) { return array(); } $messages = q("select * from mail where parent_mid = '%s' and channel_id = %d order by created asc", dbesc($r[0]['parent_mid']), intval($channel_id)); if (!$messages) { return array(); } $chans = array(); foreach ($messages as $rr) { $s = "'" . dbesc(trim($rr['from_xchan'])) . "'"; if (!in_array($s, $chans)) { $chans[] = $s; } $s = "'" . dbesc(trim($rr['to_xchan'])) . "'"; if (!in_array($s, $chans)) { $chans[] = $s; } } $c = q("select * from xchan where xchan_hash in (" . implode(',', $chans) . ")"); foreach ($messages as $k => $message) { $messages[$k]['from'] = find_xchan_in_array($message['from_xchan'], $c); $messages[$k]['to'] = find_xchan_in_array($message['to_xchan'], $c); if ($messages[$k]['mail_flags'] & MAIL_OBSCURED) { $key = get_config('system', 'prvkey'); if ($messages[$k]['title']) { $messages[$k]['title'] = crypto_unencapsulate(json_decode_plus($messages[$k]['title']), $key); } if ($messages[$k]['body']) { $messages[$k]['body'] = crypto_unencapsulate(json_decode_plus($messages[$k]['body']), $key); } } } if ($updateseen) { $r = q("UPDATE `mail` SET mail_flags = (mail_flags ^ %d) where not (mail_flags & %d) and parent_mid = '%s' AND channel_id = %d", intval(MAIL_SEEN), intval(MAIL_SEEN), dbesc($r[0]['parent_mid']), intval($channel_id)); } return $messages; }
function diaspora_process_outbound(&$a, &$arr) { /* We are passed the following array from the notifier, providing everything we need to make delivery decisions. $arr = array( 'channel' => $channel, 'env_recips' => $env_recips, 'packet_recips' => $packet_recips, 'recipients' => $recipients, 'item' => $item, 'target_item' => $target_item, 'hub' => $hub, 'top_level_post' => $top_level_post, 'private' => $private, 'relay_to_owner' => $relay_to_owner, 'uplink' => $uplink, 'cmd' => $cmd, 'mail' => $mail, 'location' => $location, 'normal_mode' => $normal_mode, 'packet_type' => $packet_type, 'walltowall' => $walltowall, 'queued' => pass these queued items (outq_hash) back to notifier.php for delivery ); */ // logger('notifier_array: ' . print_r($arr,true), LOGGER_ALL, LOG_INFO); // allow this to be set per message if (strpos($arr['target_item']['postopts'], 'nodspr') !== false) { return; } $allowed = get_pconfig($arr['channel']['channel_id'], 'system', 'diaspora_allowed'); if (!intval($allowed)) { logger('mod-diaspora: disallowed for channel ' . $arr['channel']['channel_name']); return; } if ($arr['location']) { return; } // send to public relay server - not ready for prime time if ($arr['top_level_post'] && !$arr['env_recips']) { // Add the relay server to the list of hubs. // = array('hubloc_callback' => 'https://relay.iliketoast.net/receive', 'xchan_pubkey' => 'bogus'); } $target_item = $arr['target_item']; if ($target_item && array_key_exists('item_obscured', $target_item) && intval($target_item['item_obscured'])) { $key = get_config('system', 'prvkey'); if ($target_item['title']) { $target_item['title'] = crypto_unencapsulate(json_decode($target_item['title'], true), $key); } if ($target_item['body']) { $target_item['body'] = crypto_unencapsulate(json_decode($target_item['body'], true), $key); } } $prv_recips = $arr['env_recips']; // The Diaspora profile message is unusual in that it is sent privately. if ($arr['cmd'] === 'refresh_all' && $arr['recipients']) { $prv_recips = array(); foreach ($arr['recipients'] as $r) { $prv_recips[] = array('hash' => trim($r, "'")); } } if ($prv_recips) { $hashes = array(); // re-explode the recipients, but only for this hub/pod foreach ($prv_recips as $recip) { $hashes[] = "'" . $recip['hash'] . "'"; } $r = q("select * from xchan left join hubloc on xchan_hash = hubloc_hash where hubloc_url = '%s' \n\t\t\tand xchan_hash in (" . implode(',', $hashes) . ") and xchan_network in ('diaspora', 'friendica-over-diaspora') ", dbesc($arr['hub']['hubloc_url'])); if (!$r) { logger('diaspora_process_outbound: no recipients'); return; } foreach ($r as $contact) { if (!deliverable_singleton($arr['channel']['channel_id'], $contact)) { logger('not deliverable from this hub'); continue; } if ($arr['packet_type'] == 'refresh') { $qi = diaspora_profile_change($arr['channel'], $contact); if ($qi) { $arr['queued'][] = $qi; } return; } if ($arr['mail']) { $qi = diaspora_send_mail($arr['item'], $arr['channel'], $contact); if ($qi) { $arr['queued'][] = $qi; } continue; } if (!$arr['normal_mode']) { continue; } // special handling for send_upstream to public post // all other public posts processed as public batches further below if (!$arr['private'] && $arr['relay_to_owner']) { $qi = diaspora_send_upstream($target_item, $arr['channel'], $contact, true); if ($qi) { $arr['queued'][] = $qi; } continue; } if (!$contact['xchan_pubkey']) { continue; } if (intval($target_item['item_deleted']) && ($target_item['mid'] === $target_item['parent_mid'] || $arr['relay_to_owner'])) { // send both top-level retractions and relayable retractions for owner to relay $qi = diaspora_send_retraction($target_item, $arr['channel'], $contact); if ($qi) { $arr['queued'][] = $qi; } continue; } elseif ($arr['relay_to_owner'] || $arr['uplink']) { // send comments and likes to owner to relay $qi = diaspora_send_upstream($target_item, $arr['channel'], $contact, false, $arr['uplink'] && !$arr['relay_to_owner'] ? true : false); if ($qi) { $arr['queued'][] = $qi; } continue; } elseif ($target_item['mid'] !== $target_item['parent_mid']) { // we are the relay - send comments, likes and relayable_retractions // (of comments and likes) to our conversants $qi = diaspora_send_downstream($target_item, $arr['channel'], $contact); if ($qi) { $arr['queued'][] = $qi; } continue; } elseif ($arr['top_level_post']) { $qi = diaspora_send_status($target_item, $arr['channel'], $contact); if ($qi) { foreach ($qi as $q) { $arr['queued'][] = $q; } } continue; } } } else { // public message $contact = $arr['hub']; if (intval($target_item['item_deleted']) && $target_item['mid'] === $target_item['parent_mid']) { // top-level retraction logger('delivery: diaspora retract: ' . $loc); $qi = diaspora_send_retraction($target_item, $arr['channel'], $contact, true); if ($qi) { $arr['queued'][] = $qi; } return; } elseif ($target_item['mid'] !== $target_item['parent_mid']) { // we are the relay - send comments, likes and relayable_retractions to our conversants logger('delivery: diaspora relay: ' . $loc); $qi = diaspora_send_downstream($target_item, $arr['channel'], $contact, true); if ($qi) { $arr['queued'][] = $qi; } return; } elseif ($arr['top_level_post']) { if (perm_is_allowed($arr['channel']['channel_id'], '', 'view_stream')) { logger('delivery: diaspora status: ' . $loc); $qi = diaspora_send_status($target_item, $arr['channel'], $contact, true); if ($qi) { foreach ($qi as $q) { $arr['queued'][] = $q; } } return; } } } }
/** * @brief This function is called pre-deliver to see if a post matches the criteria to be tag delivered. * * We don't actually do anything except check that it matches the criteria. * This is so that the channel with tag_delivery enabled can receive the post even if they turn off * permissions for the sender to send their stream. tag_deliver() can't be called until the post is actually stored. * By then it would be too late to reject it. */ function tgroup_check($uid, $item) { $mention = false; // check that the message originated elsewhere and is a top-level post // or is a followup and we have already accepted the top level post as an uplink if ($item['mid'] != $item['parent_mid']) { $r = q("select id from item where mid = '%s' and uid = %d and item_uplink = 1 limit 1", dbesc($item['parent_mid']), intval($uid)); if ($r) { return true; } return false; } if (!perm_is_allowed($uid, $item['author_xchan'], 'tag_deliver')) { return false; } $u = q("select * from channel left join xchan on channel_hash = xchan_hash where channel_id = %d limit 1", intval($uid)); if (!$u) { return false; } $terms = get_terms_oftype($item['term'], TERM_MENTION); if ($terms) { logger('tgroup_check: post mentions: ' . print_r($terms, true), LOGGER_DATA); } $link = normalise_link($u[0]['xchan_url']); if ($terms) { foreach ($terms as $term) { if (link_compare($term['url'], $link)) { $mention = true; break; } } } if ($mention) { logger('tgroup_check: mention found for ' . $u[0]['channel_name']); } else { return false; } // At this point we've determined that the person receiving this post was mentioned in it. // Now let's check if this mention was inside a reshare so we don't spam a forum // note: $term has been set to the matching term $body = $item['body']; if (array_key_exists('item_obscured', $item) && intval($item['item_obscured']) && $body) { $key = get_config('system', 'prvkey'); $body = crypto_unencapsulate(json_decode($body, true), $key); } $body = preg_replace('/\\[share(.*?)\\[\\/share\\]/', '', $body); // $pattern = '/@\!?\[zrl\=' . preg_quote($term['url'],'/') . '\]' . preg_quote($term['term'] . '+','/') . '\[\/zrl\]/'; $pattern = '/@\\!?\\[zrl\\=([^\\]]*?)\\]((?:.(?!\\[zrl\\=))*?)\\+\\[\\/zrl\\]/'; $found = false; $matches = array(); if (preg_match_all($pattern, $body, $matches, PREG_SET_ORDER)) { $max_forums = get_config('system', 'max_tagged_forums'); if (!$max_forums) { $max_forums = 2; } $matched_forums = 0; foreach ($matches as $match) { $matched_forums++; if ($term['url'] === $match[1] && $term['term'] === $match[2]) { if ($matched_forums <= $max_forums) { $found = true; break; } logger('forum ' . $term['term'] . ' exceeded max_tagged_forums - ignoring'); } } } if (!$found) { logger('tgroup_check: mention was in a reshare or exceeded max_tagged_forums - ignoring'); return false; } return true; }
function diaspora_send_relay($item, $owner, $contact, $public_batch = false) { $a = get_app(); $myaddr = $owner['channel_address'] . '@' . get_app()->get_hostname(); $text = bb2diaspora_itembody($item); $body = $text; // Diaspora doesn't support threaded comments, but some // versions of Diaspora (i.e. Diaspora-pistos) support // likes on comments // That version is now dead so detect a "sublike" and // just send it as an activity. $sublike = false; if ($item['verb'] === ACTIVITY_LIKE) { if ($item['thr_parent'] && $item['thr_parent'] !== $item['parent_mid']) { $sublike = true; } } // The first item in the `item` table with the parent id is the parent. However, MySQL doesn't always // return the items ordered by `item`.`id`, in which case the wrong item is chosen as the parent. // The only item with `parent` and `id` as the parent id is the parent item. $p = q("select * from item where parent = %d and id = %d limit 1", intval($item['parent']), intval($item['parent'])); if ($p) { $parent = $p[0]; } else { logger('diaspora_send_relay: no parent'); return; } $like = false; $relay_retract = false; $sql_sign_id = 'iid'; if (intval($item['item_deleted'])) { $relay_retract = true; $target_type = $item['verb'] === ACTIVITY_LIKE && !$sublike ? 'Like' : 'Comment'; $sql_sign_id = 'retract_iid'; $tpl = get_markup_template('diaspora_relayable_retraction.tpl', 'addon/diaspora'); } elseif ($item['verb'] === ACTIVITY_LIKE && !$sublike) { $like = true; $target_type = $parent['mid'] === $parent['parent_mid'] ? 'Post' : 'Comment'; // $positive = (intval($item['item_deleted']) ? 'false' : 'true'); $positive = 'true'; $tpl = get_markup_template('diaspora_like_relay.tpl', 'addon/diaspora'); } else { // item is a comment $tpl = get_markup_template('diaspora_comment_relay.tpl', 'addon/diaspora'); } $diaspora_meta = $item['diaspora_meta'] ? json_decode($item['diaspora_meta'], true) : ''; if ($diaspora_meta) { if (array_key_exists('iv', $diaspora_meta)) { $key = get_config('system', 'prvkey'); $meta = json_decode(crypto_unencapsulate($diaspora_meta, $key), true); } else { $meta = $diaspora_meta; } $sender_signed_text = $meta['signed_text']; $authorsig = $meta['signature']; $handle = $meta['signer']; $text = $meta['body']; } else { logger('diaspora_send_relay: original author signature not found'); } /* Since the author signature is only checked by the parent, not by the relay recipients, * I think it may not be necessary for us to do so much work to preserve all the original * signatures. The important thing that Diaspora DOES need is the original creator's handle. * Let's just generate that and forget about all the original author signature stuff. * * Note: this might be more of an problem if we want to support likes on comments for older * versions of Diaspora (diaspora-pistos), but since there are a number of problems with * doing that, let's ignore it for now. * * */ // bug - nomadic identity may/will affect diaspora_handle_from_contact if (!$handle) { if ($item['author_xchan'] === $owner['channel_hash']) { $handle = $owner['channel_address'] . '@' . substr($a->get_baseurl(), strpos($a->get_baseurl(), '://') + 3); } else { $handle = diaspora_handle_from_contact($item['author_xchan']); } } if (!$handle) { logger('diaspora_send_relay: no handle'); return; } if (!$sender_signed_text) { if ($relay_retract) { $sender_signed_text = $item['mid'] . ';' . $target_type; } elseif ($like) { $sender_signed_text = $positive . ';' . $item['mid'] . ';' . $target_type . ';' . $parent['mid'] . ';' . $handle; } else { $sender_signed_text = $item['mid'] . ';' . $parent['mid'] . ';' . $text . ';' . $handle; } } // Sign the relayable with the top-level owner's signature // // We'll use the $sender_signed_text that we just created, instead of the $signed_text // stored in the database, because that provides the best chance that Diaspora will // be able to reconstruct the signed text the same way we did. This is particularly a // concern for the comment, whose signed text includes the text of the comment. The // smallest change in the text of the comment, including removing whitespace, will // make the signature verification fail. Since we translate from BB code to Diaspora's // markup at the top of this function, which is AFTER we placed the original $signed_text // in the database, it's hazardous to trust the original $signed_text. $parentauthorsig = base64_encode(rsa_sign($sender_signed_text, $owner['channel_prvkey'], 'sha256')); if (!$text) { logger('diaspora_send_relay: no text'); } $msg = replace_macros($tpl, array('$guid' => xmlify($item['mid']), '$parent_guid' => xmlify($parent['mid']), '$target_type' => xmlify($target_type), '$authorsig' => xmlify($authorsig), '$parentsig' => xmlify($parentauthorsig), '$body' => xmlify($text), '$positive' => xmlify($positive), '$handle' => xmlify($handle))); logger('diaspora_send_relay: base message: ' . $msg, LOGGER_DATA); $slap = 'xml=' . urlencode(urlencode(diaspora_msg_build($msg, $owner, $contact, $owner['channel_prvkey'], $contact['xchan_pubkey'], $public_batch))); return diaspora_queue($owner, $contact, $slap, $public_batch, $item['mid']); }
function diaspora_process_outbound($arr) { /* We are passed the following array from the notifier, providing everything we need to make delivery decisions. diaspora_process_outbound(array( 'channel' => $channel, 'env_recips' => $env_recips, 'recipients' => $recipients, 'item' => $item, 'target_item' => $target_item, 'hub' => $hub, 'top_level_post' => $top_level_post, 'private' => $private, 'followup' => $followup, 'relay_to_owner' => $relay_to_owner, 'uplink' => $uplink, 'cmd' => $cmd, 'expire' => $expire, 'mail' => $mail, 'fsuggest' => $fsuggest, 'normal_mode' => $normal_mode, 'packet_type' => $packet_type, 'walltowall' => $walltowall, )); */ $target_item = $arr['target_item']; if ($target_item && array_key_exists('item_flags', $target_item) && $target_item['item_flags'] & ITEM_OBSCURED) { $key = get_config('system', 'prvkey'); if ($target_item['title']) { $target_item['title'] = crypto_unencapsulate(json_decode($target_item['title'], true), $key); } if ($target_item['body']) { $target_item['body'] = crypto_unencapsulate(json_decode($target_item['body'], true), $key); } } if ($arr['walltowall']) { return; } if ($arr['env_recips']) { $hashes = array(); // re-explode the recipients, but only for this hub/pod foreach ($arr['env_recips'] as $recip) { $hashes[] = "'" . $recip['hash'] . "'"; } $r = q("select * from xchan left join hubloc on xchan_hash = hubloc_hash where hubloc_url = '%s' \n\t\t\tand xchan_hash in (" . implode(',', $hashes) . ") and xchan_network in ('diaspora', 'friendica-over-diaspora') ", dbesc($arr['hub']['hubloc_url'])); if (!$r) { logger('diaspora_process_outbound: no recipients'); return; } foreach ($r as $contact) { if ($arr['mail']) { diaspora_send_mail($arr['item'], $arr['channel'], $contact); continue; } if (!$arr['normal_mode']) { continue; } // special handling for followup to public post // all other public posts processed as public batches further below if (!$arr['private'] && $arr['followup']) { diaspora_send_followup($target_item, $arr['channel'], $contact, true); continue; } if (!$contact['xchan_pubkey']) { continue; } if (activity_match($target_item['verb'], ACTIVITY_DISLIKE)) { continue; } elseif ($target_item['item_restrict'] & ITEM_DELETED && ($target_item['mid'] === $target_item['parent_mid'] || $arr['followup'])) { // send both top-level retractions and relayable retractions for owner to relay diaspora_send_retraction($target_item, $arr['channel'], $contact); continue; } elseif ($arr['followup']) { // send comments and likes to owner to relay diaspora_send_followup($target_item, $arr['channel'], $contact); continue; } elseif ($target_item['mid'] !== $target_item['parent_mid']) { // we are the relay - send comments, likes and relayable_retractions // (of comments and likes) to our conversants diaspora_send_relay($target_item, $arr['channel'], $contact); continue; } elseif ($arr['top_level_post']) { diaspora_send_status($target_item, $arr['channel'], $contact); continue; } } } else { // public message $contact = $arr['hub']; if ($target_item['verb'] === ACTIVITY_DISLIKE) { // unsupported return; } elseif ($target_item['deleted'] && $target_item['mid'] === $target_item['parent_mod']) { // top-level retraction logger('delivery: diaspora retract: ' . $loc); diaspora_send_retraction($target_item, $arr['channel'], $contact, true); return; } elseif ($target_item['mid'] !== $target_item['parent_mid']) { // we are the relay - send comments, likes and relayable_retractions to our conversants logger('delivery: diaspora relay: ' . $loc); diaspora_send_relay($target_item, $arr['channel'], $contact, true); return; } elseif ($arr['top_level_post']) { // currently no workable solution for sending walltowall logger('delivery: diaspora status: ' . $loc); diaspora_send_status($target_item, $arr['channel'], $contact, true); return; } } }
function diaspora_send_downstream($item, $owner, $contact, $public_batch = false) { $a = get_app(); $myaddr = $owner['channel_address'] . '@' . App::get_hostname(); $text = bb2diaspora_itembody($item); $body = $text; // Diaspora doesn't support threaded comments, but some // versions of Diaspora (i.e. Diaspora-pistos) support // likes on comments // That version is now dead so detect a "sublike" and // just send it as an activity. $sublike = false; if ($item['verb'] === ACTIVITY_LIKE) { if ($item['thr_parent'] && $item['thr_parent'] !== $item['parent_mid']) { $sublike = true; } } // The first item in the `item` table with the parent id is the parent. However, MySQL doesn't always // return the items ordered by `item`.`id`, in which case the wrong item is chosen as the parent. // The only item with `parent` and `id` as the parent id is the parent item. $p = q("select * from item where parent = %d and id = %d limit 1", intval($item['parent']), intval($item['parent'])); if ($p) { $parent = $p[0]; } else { logger('diaspora_send_downstream: no parent'); return; } $xmlout = diaspora_fields_to_xml(get_iconfig($item, 'diaspora', 'fields')); $like = false; $relay_retract = false; $sql_sign_id = 'iid'; if (intval($item['item_deleted'])) { $relay_retract = true; $target_type = $item['verb'] === ACTIVITY_LIKE && !$sublike ? 'Like' : 'Comment'; $sql_sign_id = 'retract_iid'; $tpl = get_markup_template('diaspora_relayable_retraction.tpl', 'addon/diaspora'); } elseif ($item['verb'] === ACTIVITY_LIKE && !$sublike && $xmlout) { $like = true; $target_type = $parent['mid'] === $parent['parent_mid'] ? 'Post' : 'Comment'; // $positive = (intval($item['item_deleted']) ? 'false' : 'true'); $positive = 'true'; $tpl = get_markup_template('diaspora_like_relay.tpl', 'addon/diaspora'); } else { // item is a comment $tpl = get_markup_template('diaspora_comment_relay.tpl', 'addon/diaspora'); } $diaspora_meta = $item['diaspora_meta'] ? json_decode($item['diaspora_meta'], true) : ''; if ($diaspora_meta) { if (array_key_exists('iv', $diaspora_meta)) { $key = get_config('system', 'prvkey'); $meta = json_decode(crypto_unencapsulate($diaspora_meta, $key), true); } else { $meta = $diaspora_meta; } $sender_signed_text = $meta['signed_text']; $authorsig = $meta['signature']; $handle = $meta['signer']; $text = $meta['body']; } else { logger('diaspora_send_downstream: original author signature not found'); } /* Since the author signature is only checked by the parent, not by the relay recipients, * I think it may not be necessary for us to do so much work to preserve all the original * signatures. The important thing that Diaspora DOES need is the original creator's handle. * Let's just generate that and forget about all the original author signature stuff. * * Note: this might be more of an problem if we want to support likes on comments for older * versions of Diaspora (diaspora-pistos), but since there are a number of problems with * doing that, let's ignore it for now. * * */ // bug - nomadic identity may/will affect diaspora_handle_from_contact if (!$handle) { $handle = $owner['channel_address'] . '@' . App::get_hostname(); } if (!$sender_signed_text) { if ($relay_retract) { $sender_signed_text = $item['mid'] . ';' . $target_type; } elseif ($like) { $sender_signed_text = $positive . ';' . $item['mid'] . ';' . $target_type . ';' . $parent['mid'] . ';' . $handle; } else { $sender_signed_text = $item['mid'] . ';' . $parent['mid'] . ';' . $text . ';' . $handle; } } // The relayable may have arrived from somebody who provided no Diaspora Comment Virus. // We check for this above in bb2diaspora_itembody. In that case we will have generated // the body as a "wall-to-wall" post, and the author_signature will now be our own. if (!$xmlout && !$authorsig) { $authorsig = base64_encode(rsa_sign($sender_signed_text, $owner['channel_prvkey'], 'sha256')); } // Sign the relayable with the top-level owner's signature $parentauthorsig = base64_encode(rsa_sign($sender_signed_text, $owner['channel_prvkey'], 'sha256')); if (!$text) { logger('diaspora_send_downstream: no text'); } $msg = replace_macros($tpl, array('$xml' => $xmlout, '$guid' => xmlify($item['mid']), '$parent_guid' => xmlify($parent['mid']), '$target_type' => xmlify($target_type), '$authorsig' => xmlify($authorsig), '$parentsig' => xmlify($parentauthorsig), '$body' => xmlify($text), '$positive' => xmlify($positive), '$handle' => xmlify($handle))); logger('diaspora_send_downstream: base message: ' . $msg, LOGGER_DATA); $slap = 'xml=' . urlencode(urlencode(diaspora_msg_build($msg, $owner, $contact, $owner['channel_prvkey'], $contact['xchan_pubkey'], $public_batch))); return diaspora_queue($owner, $contact, $slap, $public_batch, $item['mid']); }
function new_contact($uid, $url, $channel, $interactive = false, $confirm = false) { $result = array('success' => false, 'message' => ''); $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 = Zotlabs\Zot\Finger::run($url, $channel); } if ($ret && is_array($ret) && $ret['success']) { $is_red = true; $j = $ret; } $my_perms = get_channel_default_perms($uid); $role = get_pconfig($uid, 'system', 'permissions_role'); if ($role) { $x = \Zotlabs\Access\PermissionRoles::role_perms($role); if ($x['perms_connect']) { $my_perms = $x['perms_connect']; } } 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']; 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']; } if (is_array($permissions) && $permissions) { foreach ($permissions as $k => $v) { set_abconfig($channel['channel_uid'], $xchan_hash, 'their_perms', $k, intval($v)); } } } else { $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 $d = discover_by_webbie($url); if (!$d && $is_http) { // try RSS discovery if (get_config('system', 'feed_contacts')) { $d = discover_by_url($url); } else { $result['message'] = t('Protocol disabled.'); return $result; } } if ($d) { $r = q("select * from xchan where xchan_hash = '%s' or xchan_url = '%s' limit 1", dbesc($url), dbesc($url)); } } // if discovery was a success we should have an xchan record in $r if ($r) { $xchan = $r[0]; $xchan_hash = $r[0]['xchan_hash']; $their_perms = 0; } } if (!$xchan_hash) { $result['message'] = t('Channel discovery failed.'); logger('follow: ' . $result['message']); return $result; } $allowed = $is_red || $r[0]['xchan_network'] === 'rss' ? 1 : 0; $x = array('channel_id' => $uid, 'follow_address' => $url, 'xchan' => $r[0], 'allowed' => $allowed, 'singleton' => 0); call_hooks('follow_allow', $x); if (!$x['allowed']) { $result['message'] = t('Protocol disabled.'); return $result; } $singleton = intval($x['singleton']); $aid = $channel['channel_account_id']; $hash = get_observer_hash(); $default_group = $channel['channel_default_group']; if ($xchan['xchan_network'] === 'rss') { // check service class feed limits $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, abook_instance from abook where abook_xchan = '%s' and abook_channel = %d limit 1", dbesc($xchan_hash), intval($uid)); if ($is_http) { // Always set these "remote" permissions for feeds since we cannot interact with them // to negotiate a suitable permission response set_abconfig($uid, $xchan_hash, 'their_perms', 'view_stream', 1); set_abconfig($uid, $xchan_hash, 'their_perms', 'republish', 1); } if ($r) { $abook_instance = $r[0]['abook_instance']; if ($singleton && strpos($abook_instance, z_root()) === false) { if ($abook_instance) { $abook_instance .= ','; } $abook_instance .= z_root(); } $x = q("update abook set abook_instance = '%s' where abook_id = %d", dbesc($abook_instance), 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_created, abook_updated, abook_instance )\n\t\t\tvalues( %d, %d, %d, '%s', %d, '%s', '%s', '%s' ) ", intval($aid), intval($uid), intval($closeness), dbesc($xchan_hash), intval($is_http ? 1 : 0), dbesc(datetime_convert()), dbesc(datetime_convert()), dbesc($singleton ? z_root() : '')); } if (!$r) { logger('mod_follow: abook creation failed'); } $all_perms = \Zotlabs\Access\Permissions::Perms(); if ($all_perms) { foreach ($all_perms as $k => $v) { if (in_array($k, $my_perms)) { set_abconfig($uid, $xchan_hash, 'my_perms', $k, 1); } else { set_abconfig($uid, $xchan_hash, 'my_perms', $k, 0); } } } $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]; Zotlabs\Daemon\Master::Summon(array('Notifier', 'permission_create', $result['abook']['abook_id'])); } $arr = array('channel_id' => $uid, 'channel' => $channel, 'abook' => $result['abook']); call_hooks('follow', $arr); /** If there is a default group for this channel, add this connection 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 unobscure_mail(&$item) { if (array_key_exists('mail_flags', $item) && $item['mail_flags'] & MAIL_OBSCURED) { $key = get_config('system', 'prvkey'); if ($item['title']) { $item['title'] = crypto_unencapsulate(json_decode_plus($item['title']), $key); } if ($item['body']) { $item['body'] = crypto_unencapsulate(json_decode_plus($item['body']), $key); } } }
/** * @brief Process incoming array of messages. * * Process an incoming array of messages which were obtained via pickup, and * import, update, delete as directed. * * The message types handled here are 'activity' (e.g. posts), 'mail' , * 'profile', 'location' and 'channel_sync'. * * @param array $arr * 'pickup' structure returned from remote site * @param string $sender_url * the url specified by the sender in the initial communication. * We will verify the sender and url in each returned message structure and * also verify that all the messages returned match the site url that we are * currently processing. * * @returns array * suitable for logging remotely, enumerating the processing results of each message/recipient combination * * [0] => \e string $channel_hash * * [1] => \e string $delivery_status * * [2] => \e string $address */ function zot_import($arr, $sender_url) { $data = json_decode($arr['body'], true); if (!$data) { logger('zot_import: empty body'); return array(); } if (array_key_exists('iv', $data)) { $data = json_decode(crypto_unencapsulate($data, get_config('system', 'prvkey')), true); } if (!$data['success']) { if ($data['message']) { logger('remote pickup failed: ' . $data['message']); } return false; } $incoming = $data['pickup']; $return = array(); if (is_array($incoming)) { foreach ($incoming as $i) { if (!is_array($i)) { logger('incoming is not an array'); continue; } $result = null; if (array_key_exists('iv', $i['notify'])) { $i['notify'] = json_decode(crypto_unencapsulate($i['notify'], get_config('system', 'prvkey')), true); } logger('zot_import: notify: ' . print_r($i['notify'], true), LOGGER_DATA); $hub = zot_gethub($i['notify']['sender']); if (!$hub || $hub['hubloc_url'] != $sender_url) { logger('zot_import: potential forgery: wrong site for sender: ' . $sender_url . ' != ' . print_r($i['notify'], true)); continue; } $message_request = array_key_exists('message_id', $i['notify']) ? true : false; if ($message_request) { logger('processing message request'); } $i['notify']['sender']['hash'] = make_xchan_hash($i['notify']['sender']['guid'], $i['notify']['sender']['guid_sig']); $deliveries = null; if (array_key_exists('message', $i) && array_key_exists('type', $i['message']) && $i['message']['type'] === 'rating') { // rating messages are processed only by directory servers logger('Rating received: ' . print_r($arr, true), LOGGER_DATA); $result = process_rating_delivery($i['notify']['sender'], $i['message']); continue; } if (array_key_exists('recipients', $i['notify']) && count($i['notify']['recipients'])) { logger('specific recipients'); $recip_arr = array(); foreach ($i['notify']['recipients'] as $recip) { if (is_array($recip)) { $recip_arr[] = make_xchan_hash($recip['guid'], $recip['guid_sig']); } } $r = false; if ($recip_arr) { stringify_array_elms($recip_arr); $recips = implode(',', $recip_arr); $r = q("select channel_hash as hash from channel where channel_hash in ( " . $recips . " ) \n\t\t\t\t\t\tand channel_removed = 0 "); } if (!$r) { logger('recips: no recipients on this site'); continue; } // It's a specifically targetted post. If we were sent a public_scope hint (likely), // get rid of it so that it doesn't get stored and cause trouble. if ($i && is_array($i) && array_key_exists('message', $i) && is_array($i['message']) && $i['message']['type'] === 'activity' && array_key_exists('public_scope', $i['message'])) { unset($i['message']['public_scope']); } $deliveries = $r; // We found somebody on this site that's in the recipient list. } else { if ($i['message'] && array_key_exists('flags', $i['message']) && in_array('private', $i['message']['flags']) && $i['message']['type'] === 'activity') { if (array_key_exists('public_scope', $i['message']) && $i['message']['public_scope'] === 'public') { // This should not happen but until we can stop it... logger('private message was delivered with no recipients.'); continue; } } logger('public post'); // Public post. look for any site members who are or may be accepting posts from this sender // and who are allowed to see them based on the sender's permissions $deliveries = allowed_public_recips($i); if ($i['message'] && array_key_exists('type', $i['message']) && $i['message']['type'] === 'location') { $sys = get_sys_channel(); $deliveries = array(array('hash' => $sys['xchan_hash'])); } // if the scope is anything but 'public' we're going to store it as private regardless // of the private flag on the post. if ($i['message'] && array_key_exists('public_scope', $i['message']) && $i['message']['public_scope'] !== 'public') { if (!array_key_exists('flags', $i['message'])) { $i['message']['flags'] = array(); } if (!in_array('private', $i['message']['flags'])) { $i['message']['flags'][] = 'private'; } } } // Go through the hash array and remove duplicates. array_unique() won't do this because the array is more than one level. $no_dups = array(); if ($deliveries) { foreach ($deliveries as $d) { if (!in_array($d['hash'], $no_dups)) { $no_dups[] = $d['hash']; } } if ($no_dups) { $deliveries = array(); foreach ($no_dups as $n) { $deliveries[] = array('hash' => $n); } } } if (!$deliveries) { logger('zot_import: no deliveries on this site'); continue; } if ($i['message']) { if ($i['message']['type'] === 'activity') { $arr = get_item_elements($i['message']); $v = validate_item_elements($i['message'], $arr); if (!$v['success']) { logger('Activity rejected: ' . $v['message'] . ' ' . print_r($i['message'], true)); continue; } logger('Activity received: ' . print_r($arr, true), LOGGER_DATA); logger('Activity recipients: ' . print_r($deliveries, true), LOGGER_DATA); $relay = array_key_exists('flags', $i['message']) && in_array('relay', $i['message']['flags']) ? true : false; $result = process_delivery($i['notify']['sender'], $arr, $deliveries, $relay, false, $message_request); } elseif ($i['message']['type'] === 'mail') { $arr = get_mail_elements($i['message']); logger('Mail received: ' . print_r($arr, true), LOGGER_DATA); logger('Mail recipients: ' . print_r($deliveries, true), LOGGER_DATA); $result = process_mail_delivery($i['notify']['sender'], $arr, $deliveries); } elseif ($i['message']['type'] === 'profile') { $arr = get_profile_elements($i['message']); logger('Profile received: ' . print_r($arr, true), LOGGER_DATA); logger('Profile recipients: ' . print_r($deliveries, true), LOGGER_DATA); $result = process_profile_delivery($i['notify']['sender'], $arr, $deliveries); } elseif ($i['message']['type'] === 'channel_sync') { // $arr = get_channelsync_elements($i['message']); $arr = $i['message']; logger('Channel sync received: ' . print_r($arr, true), LOGGER_DATA); logger('Channel sync recipients: ' . print_r($deliveries, true), LOGGER_DATA); $result = process_channel_sync_delivery($i['notify']['sender'], $arr, $deliveries); } elseif ($i['message']['type'] === 'location') { $arr = $i['message']; logger('Location message received: ' . print_r($arr, true), LOGGER_DATA); logger('Location message recipients: ' . print_r($deliveries, true), LOGGER_DATA); $result = process_location_delivery($i['notify']['sender'], $arr, $deliveries); } } if ($result) { $return = array_merge($return, $result); } } } return $return; }
function diaspora_send_mail($item, $owner, $contact) { $a = get_app(); $myaddr = $owner['channel_address'] . '@' . get_app()->get_hostname(); $r = q("select * from conv where id = %d and uid = %d limit 1", intval($item['convid']), intval($item['channel_id'])); if (!count($r)) { logger('diaspora_send_mail: conversation not found.'); return; } $cnv = $r[0]; $conv = array('guid' => xmlify($cnv['guid']), 'subject' => xmlify($cnv['subject']), 'created_at' => xmlify(datetime_convert('UTC', 'UTC', $cnv['created'], 'Y-m-d H:i:s \\U\\T\\C')), 'diaspora_handle' => xmlify($cnv['creator']), 'participant_handles' => xmlify($cnv['recips'])); if (array_key_exists('mail_flags', $item) && $item['mail_flags'] & MAIL_OBSCURED) { $key = get_config('system', 'prvkey'); // if($item['title']) // $item['title'] = crypto_unencapsulate(json_decode_plus($item['title']),$key); if ($item['body']) { $item['body'] = crypto_unencapsulate(json_decode_plus($item['body']), $key); } } $body = bb2diaspora($item['body']); $created = datetime_convert('UTC', 'UTC', $item['created'], 'Y-m-d H:i:s \\U\\T\\C'); $signed_text = $item['mid'] . ';' . $cnv['guid'] . ';' . $body . ';' . $created . ';' . $myaddr . ';' . $cnv['guid']; $sig = base64_encode(rsa_sign($signed_text, $owner['channel_prvkey'], 'sha256')); $msg = array('guid' => xmlify($item['mid']), 'parent_guid' => xmlify($cnv['guid']), 'parent_author_signature' => $item['reply'] ? null : xmlify($sig), 'author_signature' => xmlify($sig), 'text' => xmlify($body), 'created_at' => xmlify($created), 'diaspora_handle' => xmlify($myaddr), 'conversation_guid' => xmlify($cnv['guid'])); if ($item['reply']) { $tpl = get_markup_template('diaspora_message.tpl'); $xmsg = replace_macros($tpl, array('$msg' => $msg)); } else { $conv['messages'] = array($msg); $tpl = get_markup_template('diaspora_conversation.tpl'); $xmsg = replace_macros($tpl, array('$conv' => $conv)); } logger('diaspora_conversation: ' . print_r($xmsg, true), LOGGER_DATA); $slap = 'xml=' . urlencode(urlencode(diaspora_msg_build($xmsg, $owner, $contact, $owner['channel_prvkey'], $contact['xchan_pubkey'], false))); return diaspora_transmit($owner, $contact, $slap, false); }