function post_post(&$a) { $bulk_delivery = false; if ($a->argc == 1) { $bulk_delivery = true; } else { $nickname = $a->argv[2]; $r = q("SELECT * FROM `user` WHERE `nickname` = '%s' \n\t\t\t\tAND `account_expired` = 0 AND `account_removed` = 0 LIMIT 1", dbesc($nickname)); if (!count($r)) { http_status_exit(500); } $importer = $r[0]; } $xml = file_get_contents('php://input'); logger('mod-post: new zot: ' . $xml, LOGGER_DATA); if (!$xml) { http_status_exit(500); } $msg = zot_decode($importer, $xml); logger('mod-post: decoded msg: ' . print_r($msg, true), LOGGER_DATA); if (!is_array($msg)) { http_status_exit(500); } $ret = 0; $ret = zot_incoming($bulk_delivery, $importer, $msg); http_status_exit($ret ? $ret : 200); // NOTREACHED }
function _well_known_init(&$a) { if (argc() > 1) { $arr = array('server' => $_SERVER, 'request' => $_REQUEST); call_hooks('well_known', $arr); switch (argv(1)) { case 'zot-info': $a->argc -= 1; array_shift($a->argv); $a->argv[0] = 'zfinger'; require_once 'mod/zfinger.php'; zfinger_init($a); break; case 'webfinger': $a->argc -= 1; array_shift($a->argv); $a->argv[0] = 'wfinger'; require_once 'mod/wfinger.php'; wfinger_init($a); break; case 'host-meta': $a->argc -= 1; array_shift($a->argv); $a->argv[0] = 'hostxrd'; require_once 'mod/hostxrd.php'; hostxrd_init($a); break; default: break; } } http_status_exit(404); }
function init() { if (argc() != 3 || !in_array(argv(1), ['post', 'status_message', 'reshare'])) { http_status_exit(404, 'Not found'); } $guid = argv(2); // Fetch the item $item = q("SELECT * from item where mid = '%s' and item_private = 0 and mid = parent_mid limit 1", dbesc($guid)); if (!$item) { http_status_exit(404, 'Not found'); } xchan_query($item); $item = fetch_post_tags($item, true); $channel = channelx_by_hash($item[0]['author_xchan']); if (!$channel) { $r = q("select * from xchan where xchan_hash = '%s' limit 1", dbesc($item[0]['author_xchan'])); if ($r) { $url = $r[0]['xchan_url']; if (strpos($url, z_root()) === false) { $m = parse_url($url); goaway($m['scheme'] . '://' . $m['host'] . ($m['port'] ? ':' . $m['port'] : '') . '/fetch/' . argv(1) . '/' . argv(2)); } } http_status_exit(404, 'Not found'); } $status = diaspora_build_status($item[0], $channel); header("Content-type: application/magic-envelope+xml; charset=utf-8"); echo diaspora_magic_env($channel, $status); killme(); }
function p_init(&$a) { if (argc() < 2) { http_status_exit(401); } $mid = str_replace('.xml', '', argv(1)); $r = q("select * from item where mid = '%s' and item_wall = 1 and item_private = 0 limit 1", dbesc($mid)); if (!$r || !perm_is_allowed($r[0]['uid'], '', 'view_stream')) { http_status_exit(404); } $c = q("select * from channel where channel_id = %d limit 1", intval($r[0]['uid'])); if (!$c) { http_status_exit(404); } $myaddr = $c[0]['channel_address'] . '@' . App::get_hostname(); $item = $r[0]; $title = $item['title']; $body = bb2diaspora_itembody($item); $created = datetime_convert('UTC', 'UTC', $item['created'], 'Y-m-d H:i:s \\U\\T\\C'); $tpl = get_markup_template('diaspora_post.tpl', 'addon/diaspora'); $msg = replace_macros($tpl, array('$body' => xmlify($body), '$guid' => $item['mid'], '$handle' => xmlify($myaddr), '$public' => 'true', '$created' => $created, '$provider' => $item['app'] ? $item['app'] : t('$projectname'))); header('Content-type: text/xml'); echo $msg; killme(); }
function _well_known_init(&$a) { if (argc() > 1) { $arr = array('server' => $_SERVER, 'request' => $_REQUEST); call_hooks('well_known', $arr); if (!check_siteallowed($_SERVER['REMOTE_ADDR'])) { logger('well_known: site not allowed. ' . $_SERVER['REMOTE_ADDR']); killme(); } // from php.net re: REMOTE_HOST: // Note: Your web server must be configured to create this variable. For example in Apache // you'll need HostnameLookups On inside httpd.conf for it to exist. See also gethostbyaddr(). if (get_config('system', 'siteallowed_remote_host') && !check_siteallowed($_SERVER['REMOTE_HOST'])) { logger('well_known: site not allowed. ' . $_SERVER['REMOTE_HOST']); killme(); } switch (argv(1)) { case 'zot-info': $a->argc -= 1; array_shift($a->argv); $a->argv[0] = 'zfinger'; require_once 'mod/zfinger.php'; zfinger_init($a); break; case 'webfinger': $a->argc -= 1; array_shift($a->argv); $a->argv[0] = 'wfinger'; require_once 'mod/wfinger.php'; wfinger_init($a); break; case 'host-meta': $a->argc -= 1; array_shift($a->argv); $a->argv[0] = 'hostxrd'; require_once 'mod/hostxrd.php'; hostxrd_init($a); break; default: // look in $WEBROOT/well_known for the requested file in case it is // something a site requires and for which we do not have a module // @fixme - we may need to determine the content-type and stick it in the header // for now this can be done with a php script masquerading as the requested file $wk_file = str_replace('.well-known', 'well_known', $a->cmd); if (file_exists($wk_file)) { echo file_get_contents($wk_file); killme(); } elseif (file_exists($wk_file . '.php')) { require_once $wk_file . '.php'; } break; } } http_status_exit(404); }
function _well_known_init(&$a) { if ($a->argc > 1) { switch ($a->argv[1]) { case "host-meta": hostxrd_init($a); break; } } http_status_exit(404); killme(); }
function _well_known_init(&$a) { if (argc() > 1) { $arr = array('server' => $_SERVER, 'request' => $_REQUEST); call_hooks('well_known', $arr); if (!check_siteallowed($_SERVER['REMOTE_ADDR'])) { logger('well_known: site not allowed. ' . $_SERVER['REMOTE_ADDR']); killme(); } // from php.net re: REMOTE_HOST: // Note: Your web server must be configured to create this variable. For example in Apache // you'll need HostnameLookups On inside httpd.conf for it to exist. See also gethostbyaddr(). if (get_config('system', 'siteallowed_remote_host') && !check_siteallowed($_SERVER['REMOTE_HOST'])) { logger('well_known: site not allowed. ' . $_SERVER['REMOTE_HOST']); killme(); } switch (argv(1)) { case 'zot-info': $a->argc -= 1; array_shift($a->argv); $a->argv[0] = 'zfinger'; require_once 'mod/zfinger.php'; zfinger_init($a); break; case 'webfinger': $a->argc -= 1; array_shift($a->argv); $a->argv[0] = 'wfinger'; require_once 'mod/wfinger.php'; wfinger_init($a); break; case 'host-meta': $a->argc -= 1; array_shift($a->argv); $a->argv[0] = 'hostxrd'; require_once 'mod/hostxrd.php'; hostxrd_init($a); break; default: if (file_exists($a->cmd)) { echo file_get_contents($a->cmd); killme(); } elseif (file_exists($a->cmd . '.php')) { require_once $a->cmd . '.php'; } break; } } http_status_exit(404); }
function init() { logger('oep: ' . print_r($_REQUEST, true), LOGGER_DEBUG, LOG_INFO); $html = argc() > 1 && argv(1) === 'html' ? true : false; if ($_REQUEST['url']) { $_REQUEST['url'] = strip_zids($_REQUEST['url']); $url = $_REQUEST['url']; } if (!$url) { http_status_exit(404, 'Not found'); } $maxwidth = $_REQUEST['maxwidth']; $maxheight = $_REQUEST['maxheight']; $format = $_REQUEST['format']; if ($format && $format !== 'json') { http_status_exit(501, 'Not implemented'); } if (fnmatch('*/photos/*/album/*', $url)) { $arr = $this->oep_album_reply($_REQUEST); } elseif (fnmatch('*/photos/*/image/*', $url)) { $arr = $this->oep_photo_reply($_REQUEST); } elseif (fnmatch('*/photos*', $url)) { $arr = $this->oep_phototop_reply($_REQUEST); } elseif (fnmatch('*/display/*', $url)) { $arr = $this->oep_display_reply($_REQUEST); } elseif (fnmatch('*/channel/*mid=*', $url)) { $arr = $this->oep_mid_reply($_REQUEST); } elseif (fnmatch('*/channel*', $url)) { $arr = $this->oep_profile_reply($_REQUEST); } elseif (fnmatch('*/profile/*', $url)) { $arr = $this->oep_profile_reply($_REQUEST); } if ($arr) { if ($html) { if ($arr['type'] === 'rich') { header('Content-Type: text/html'); echo $arr['html']; } } else { header('Content-Type: application/json+oembed'); echo json_encode($arr); } killme(); } http_status_exit(404, 'Not found'); }
function receive_post(&$a) { $public = false; logger('diaspora_receive: ' . print_r($a->argv, true), LOGGER_DEBUG); if (argc() == 2 && argv(1) === 'public') { $public = true; } else { if (argc() != 3 || argv(1) !== 'users') { http_status_exit(500); } $guid = argv(2); $hn = str_replace('.', '', $a->get_hostname()); if (($x = strpos($guid, $hn)) > 0) { $guid = substr($guid, 0, $x); } // Diaspora sites *may* provide a truncated guid. $r = q("SELECT * FROM channel left join xchan on channel_hash = xchan_hash WHERE channel_guid like '%s' AND channel_removed = 0 LIMIT 1", dbesc($guid . '%')); if (!$r) { http_status_exit(500); } $importer = $r[0]; } // It is an application/x-www-form-urlencoded that has been urlencoded twice. logger('mod-diaspora: receiving post', LOGGER_DEBUG); $xml = urldecode($_POST['xml']); logger('mod-diaspora: new salmon ' . $xml, LOGGER_DATA); if (!$xml) { http_status_exit(500); } logger('mod-diaspora: message is okay', LOGGER_DEBUG); $msg = diaspora_decode($importer, $xml); logger('mod-diaspora: decoded', LOGGER_DEBUG); logger('mod-diaspora: decoded msg: ' . print_r($msg, true), LOGGER_DATA); if (!is_array($msg)) { http_status_exit(500); } logger('mod-diaspora: dispatching', LOGGER_DEBUG); $ret = 0; if ($public) { diaspora_dispatch_public($msg); } else { $ret = diaspora_dispatch($importer, $msg); } http_status_exit($ret ? $ret : 200); // NOTREACHED }
function receive_post(&$a) { $enabled = intval(get_config('system', 'diaspora_enabled')); if (!$enabled) { logger('mod-diaspora: disabled'); http_status_exit(500); } $public = false; if (argc() == 2 && argv(1) === 'public') { $public = true; } else { if (argc() != 3 || argv(1) !== 'users') { http_status_exit(500); } $guid = argv(2); // Diaspora sites *may* provide a truncated guid. $r = q("SELECT * FROM channel left join xchan on channel_hash = xchan_hash WHERE channel_guid like '%s' AND NOT (channel_pageflags & %d )>0 LIMIT 1", dbesc($guid . '%'), intval(PAGE_REMOVED)); if (!$r) { http_status_exit(500); } $importer = $r[0]; } // It is an application/x-www-form-urlencoded that has been urlencoded twice. logger('mod-diaspora: receiving post', LOGGER_DEBUG); $xml = urldecode($_POST['xml']); logger('mod-diaspora: new salmon ' . $xml, LOGGER_DATA); if (!$xml) { http_status_exit(500); } logger('mod-diaspora: message is okay', LOGGER_DEBUG); $msg = diaspora_decode($importer, $xml); logger('mod-diaspora: decoded', LOGGER_DEBUG); logger('mod-diaspora: decoded msg: ' . print_r($msg, true), LOGGER_DATA); if (!is_array($msg)) { http_status_exit(500); } logger('mod-diaspora: dispatching', LOGGER_DEBUG); $ret = 0; if ($public) { diaspora_dispatch_public($msg); } else { $ret = diaspora_dispatch($importer, $msg); } http_status_exit($ret ? $ret : 200); // NOTREACHED }
function receive_post(&$a) { $enabled = intval(get_config('system', 'diaspora_enabled')); if (!$enabled) { logger('mod-diaspora: disabled'); http_status_exit(500); } $public = false; if ($a->argc == 2 && $a->argv[1] === 'public') { $public = true; } else { if ($a->argc != 3 || $a->argv[1] !== 'users') { http_status_exit(500); } $guid = $a->argv[2]; $r = q("SELECT * FROM `user` WHERE `guid` = '%s' AND `account_expired` = 0 AND `account_removed` = 0 LIMIT 1", dbesc($guid)); if (!count($r)) { http_status_exit(500); } $importer = $r[0]; } // It is an application/x-www-form-urlencoded logger('mod-diaspora: receiving post', LOGGER_DEBUG); $xml = urldecode($_POST['xml']); logger('mod-diaspora: new salmon ' . $xml, LOGGER_DATA); if (!$xml) { http_status_exit(500); } logger('mod-diaspora: message is okay', LOGGER_DEBUG); $msg = diaspora_decode($importer, $xml); logger('mod-diaspora: decoded', LOGGER_DEBUG); logger('mod-diaspora: decoded msg: ' . print_r($msg, true), LOGGER_DATA); if (!is_array($msg)) { http_status_exit(500); } logger('mod-diaspora: dispatching', LOGGER_DEBUG); $ret = 0; if ($public) { diaspora_dispatch_public($msg); } else { $ret = diaspora_dispatch($importer, $msg); } http_status_exit($ret ? $ret : 200); // NOTREACHED }
function _well_known_init(&$a) { if ($a->argc > 1) { switch ($a->argv[1]) { case "host-meta": hostxrd_init($a); break; case "x-social-relay": wk_social_relay($a); break; case "nodeinfo": nodeinfo_wellknown($a); break; } } http_status_exit(404); killme(); }
function statistics_json_init(&$a) { if (!get_config("system", "nodeinfo")) { http_status_exit(404); killme(); } $statistics = array("name" => $a->config["sitename"], "network" => FRIENDICA_PLATFORM, "version" => FRIENDICA_VERSION . "-" . DB_UPDATE_VERSION, "registrations_open" => $a->config['register_policy'] != 0, "total_users" => get_config('nodeinfo', 'total_users'), "active_users_halfyear" => get_config('nodeinfo', 'active_users_halfyear'), "active_users_monthly" => get_config('nodeinfo', 'active_users_monthly'), "local_posts" => get_config('nodeinfo', 'local_posts')); $statistics["services"] = array(); $statistics["services"]["appnet"] = plugin_enabled("appnet"); $statistics["services"]["blogger"] = plugin_enabled("blogger"); $statistics["services"]["buffer"] = plugin_enabled("buffer"); $statistics["services"]["dreamwidth"] = plugin_enabled("dwpost"); $statistics["services"]["facebook"] = plugin_enabled("fbpost"); $statistics["services"]["gnusocial"] = plugin_enabled("statusnet"); $statistics["services"]["googleplus"] = plugin_enabled("gpluspost"); $statistics["services"]["libertree"] = plugin_enabled("libertree"); $statistics["services"]["livejournal"] = plugin_enabled("ljpost"); $statistics["services"]["pumpio"] = plugin_enabled("pumpio"); $statistics["services"]["twitter"] = plugin_enabled("twitter"); $statistics["services"]["tumblr"] = plugin_enabled("tumblr"); $statistics["services"]["wordpress"] = plugin_enabled("wppost"); $statistics["appnet"] = $statistics["services"]["appnet"]; $statistics["blogger"] = $statistics["services"]["blogger"]; $statistics["buffer"] = $statistics["services"]["buffer"]; $statistics["dreamwidth"] = $statistics["services"]["dreamwidth"]; $statistics["facebook"] = $statistics["services"]["facebook"]; $statistics["gnusocial"] = $statistics["services"]["gnusocial"]; $statistics["googleplus"] = $statistics["services"]["googleplus"]; $statistics["libertree"] = $statistics["services"]["libertree"]; $statistics["livejournal"] = $statistics["services"]["livejournal"]; $statistics["pumpio"] = $statistics["services"]["pumpio"]; $statistics["twitter"] = $statistics["services"]["twitter"]; $statistics["tumblr"] = $statistics["services"]["tumblr"]; $statistics["wordpress"] = $statistics["services"]["wordpress"]; header("Content-Type: application/json"); echo json_encode($statistics, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); logger("statistics_init: printed " . print_r($statistics, true), LOGGER_DATA); killme(); }
function poco($a, $extended = false) { $system_mode = false; if (observer_prohibited()) { logger('mod_poco: block_public'); http_status_exit(401); } $observer = App::get_observer(); if (argc() > 1) { $user = notags(trim(argv(1))); } if (!x($user)) { $c = q("select * from pconfig where cat = 'system' and k = 'suggestme' and v = '1'"); if (!$c) { logger('mod_poco: system mode. No candidates.', LOGGER_DEBUG); http_status_exit(404); } $system_mode = true; } $format = $_REQUEST['format'] ? $_REQUEST['format'] : 'json'; $justme = false; if (argc() > 2 && argv(2) === '@me') { $justme = true; } if (argc() > 3) { if (argv(3) === '@all') { $justme = false; } elseif (argv(3) === '@self') { $justme = true; } } if (argc() > 4 && intval(argv(4)) && $justme == false) { $cid = intval(argv(4)); } if (!$system_mode) { $r = q("SELECT channel_id from channel where channel_address = '%s' limit 1", dbesc($user)); if (!$r) { logger('mod_poco: user mode. Account not found. ' . $user); http_status_exit(404); } $channel_id = $r[0]['channel_id']; $ohash = $observer ? $observer['xchan_hash'] : ''; if (!perm_is_allowed($channel_id, $ohash, 'view_contacts')) { logger('mod_poco: user mode. Permission denied for ' . $ohash . ' user: '******'system' and k = 'suggestme' and v = '1') "); } else { $r = q("SELECT count(*) as `total` from abook where abook_channel = %d \n\t\t\t{$sql_extra} ", intval($channel_id)); $rooms = q("select * from menu_item where ( mitem_flags & " . intval(MENU_ITEM_CHATROOM) . " )>0 and allow_cid = '' and allow_gid = '' and deny_cid = '' and deny_gid = '' and mitem_channel_id = %d", intval($channel_id)); } if ($r) { $totalResults = intval($r[0]['total']); } else { $totalResults = 0; } $startIndex = intval($_GET['startIndex']); if (!$startIndex) { $startIndex = 0; } $itemsPerPage = x($_GET, 'count') && intval($_GET['count']) ? intval($_GET['count']) : $totalResults; if ($system_mode) { $r = q("SELECT abook.*, xchan.* from abook left join xchan on abook_xchan = xchan_hash where abook_self = 1 \n\t\t\tand abook_channel in (select uid from pconfig where cat = 'system' and k = 'suggestme' and v = '1') \n\t\t\tlimit %d offset %d ", intval($itemsPerPage), intval($startIndex)); } else { $r = q("SELECT abook.*, xchan.* from abook left join xchan on abook_xchan = xchan_hash where abook_channel = %d \n\t\t\t{$sql_extra} LIMIT %d OFFSET %d", intval($channel_id), intval($itemsPerPage), intval($startIndex)); } $ret = array(); if (x($_GET, 'sorted')) { $ret['sorted'] = 'false'; } if (x($_GET, 'filtered')) { $ret['filtered'] = 'false'; } if (x($_GET, 'updatedSince')) { $ret['updateSince'] = 'false'; } $ret['startIndex'] = (string) $startIndex; $ret['itemsPerPage'] = (string) $itemsPerPage; $ret['totalResults'] = (string) $totalResults; if ($rooms) { $ret['chatrooms'] = array(); foreach ($rooms as $room) { $ret['chatrooms'][] = array('url' => $room['mitem_link'], 'desc' => $room['mitem_desc']); } } $ret['entry'] = array(); $fields_ret = array('id' => false, 'guid' => false, 'guid_sig' => false, 'hash' => false, 'displayName' => false, 'urls' => false, 'preferredUsername' => false, 'photos' => false, 'rating' => false); if (!x($_GET, 'fields') || $_GET['fields'] === '@all') { foreach ($fields_ret as $k => $v) { $fields_ret[$k] = true; } } else { $fields_req = explode(',', $_GET['fields']); foreach ($fields_req as $f) { $fields_ret[trim($f)] = true; } } if (is_array($r)) { if (count($r)) { foreach ($r as $rr) { $entry = array(); if ($fields_ret['id']) { $entry['id'] = $rr['abook_id']; } if ($fields_ret['guid']) { $entry['guid'] = $rr['xchan_guid']; } if ($fields_ret['guid_sig']) { $entry['guid_sig'] = $rr['xchan_guid_sig']; } if ($fields_ret['hash']) { $entry['hash'] = $rr['xchan_hash']; } if ($fields_ret['displayName']) { $entry['displayName'] = $rr['xchan_name']; } if ($fields_ret['urls']) { $entry['urls'] = array(array('value' => $rr['xchan_url'], 'type' => 'profile')); $network = $rr['xchan_network']; if (strpos($network, 'friendica') !== false) { $network = 'friendica'; } if ($rr['xchan_addr']) { $entry['urls'][] = array('value' => 'acct:' . $rr['xchan_addr'], 'type' => $network); } } if ($fields_ret['preferredUsername']) { $entry['preferredUsername'] = substr($rr['xchan_addr'], 0, strpos($rr['xchan_addr'], '@')); } if ($fields_ret['photos']) { $entry['photos'] = array(array('value' => $rr['xchan_photo_l'], 'mimetype' => $rr['xchan_photo_mimetype'], 'type' => 'profile')); } $ret['entry'][] = $entry; } } else { $ret['entry'][] = array(); } } else { http_status_exit(500); } if ($format === 'xml') { header('Content-type: text/xml'); echo replace_macros(get_markup_template('poco_xml.tpl'), array_xmlify(array('$response' => $ret))); http_status_exit(500); } if ($format === 'json') { header('Content-type: application/json'); echo json_encode($ret); killme(); } else { http_status_exit(500); } }
function salmon_post(&$a) { $xml = file_get_contents('php://input'); logger('mod-salmon: new salmon ' . $xml, LOGGER_DATA); $nick = $a->argc > 1 ? notags(trim($a->argv[1])) : ''; $mentions = $a->argc > 2 && $a->argv[2] === 'mention' ? true : false; $r = q("SELECT * FROM `user` WHERE `nickname` = '%s' AND `account_expired` = 0 AND `account_removed` = 0 LIMIT 1", dbesc($nick)); if (!count($r)) { http_status_exit(500); } $importer = $r[0]; // parse the xml $dom = simplexml_load_string($xml, 'SimpleXMLElement', 0, NAMESPACE_SALMON_ME); // figure out where in the DOM tree our data is hiding if ($dom->provenance->data) { $base = $dom->provenance; } elseif ($dom->env->data) { $base = $dom->env; } elseif ($dom->data) { $base = $dom; } if (!$base) { logger('mod-salmon: unable to locate salmon data in xml '); http_status_exit(400); } // Stash the signature away for now. We have to find their key or it won't be good for anything. $signature = base64url_decode($base->sig); // unpack the data // strip whitespace so our data element will return to one big base64 blob $data = str_replace(array(" ", "\t", "\r", "\n"), array("", "", "", ""), $base->data); // stash away some other stuff for later $type = $base->data[0]->attributes()->type[0]; $keyhash = $base->sig[0]->attributes()->keyhash[0]; $encoding = $base->encoding; $alg = $base->alg; // Salmon magic signatures have evolved and there is no way of knowing ahead of time which // flavour we have. We'll try and verify it regardless. $stnet_signed_data = $data; $signed_data = $data . '.' . base64url_encode($type) . '.' . base64url_encode($encoding) . '.' . base64url_encode($alg); $compliant_format = str_replace('=', '', $signed_data); // decode the data $data = base64url_decode($data); $author = ostatus_salmon_author($data, $importer); $author_link = $author["author-link"]; if (!$author_link) { logger('mod-salmon: Could not retrieve author URI.'); http_status_exit(400); } // Once we have the author URI, go to the web and try to find their public key logger('mod-salmon: Fetching key for ' . $author_link); $key = get_salmon_key($author_link, $keyhash); if (!$key) { logger('mod-salmon: Could not retrieve author key.'); http_status_exit(400); } $key_info = explode('.', $key); $m = base64url_decode($key_info[1]); $e = base64url_decode($key_info[2]); logger('mod-salmon: key details: ' . print_r($key_info, true), LOGGER_DEBUG); $pubkey = metopem($m, $e); // We should have everything we need now. Let's see if it verifies. $verify = rsa_verify($compliant_format, $signature, $pubkey); if (!$verify) { logger('mod-salmon: message did not verify using protocol. Trying padding hack.'); $verify = rsa_verify($signed_data, $signature, $pubkey); } if (!$verify) { logger('mod-salmon: message did not verify using padding. Trying old statusnet hack.'); $verify = rsa_verify($stnet_signed_data, $signature, $pubkey); } if (!$verify) { logger('mod-salmon: Message did not verify. Discarding.'); http_status_exit(400); } logger('mod-salmon: Message verified.'); /* * * If we reached this point, the message is good. Now let's figure out if the author is allowed to send us stuff. * */ $r = q("SELECT * FROM `contact` WHERE `network` IN ('%s', '%s')\n\t\t\t\t\t\tAND (`nurl` = '%s' OR `alias` = '%s' OR `alias` = '%s')\n\t\t\t\t\t\tAND `uid` = %d LIMIT 1", dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN), dbesc(normalise_link($author_link)), dbesc($author_link), dbesc(normalise_link($author_link)), intval($importer['uid'])); if (!count($r)) { logger('mod-salmon: Author unknown to us.'); if (get_pconfig($importer['uid'], 'system', 'ostatus_autofriend')) { $result = new_contact($importer['uid'], $author_link); if ($result['success']) { $r = q("SELECT * FROM `contact` WHERE `network` = '%s' AND ( `url` = '%s' OR `alias` = '%s') \n\t\t\t\t\tAND `uid` = %d LIMIT 1", dbesc(NETWORK_OSTATUS), dbesc($author_link), dbesc($author_link), intval($importer['uid'])); } } } // Have we ignored the person? // If so we can not accept this post. //if((count($r)) && (($r[0]['readonly']) || ($r[0]['rel'] == CONTACT_IS_FOLLOWER) || ($r[0]['blocked']))) { if (count($r) && $r[0]['blocked']) { logger('mod-salmon: Ignoring this author.'); http_status_exit(202); // NOTREACHED } // Placeholder for hub discovery. $hub = ''; $contact_rec = count($r) ? $r[0] : null; ostatus_import($data, $importer, $contact_rec, $hub); http_status_exit(200); }
function nodeinfo_init(&$a) { if (!get_config("system", "nodeinfo")) { http_status_exit(404); killme(); } if ($a->argc != 2 or $a->argv[1] != "1.0") { http_status_exit(404); killme(); } $smtp = (function_exists("imap_open") and !get_config("system", "imap_disabled") and !get_config("system", "dfrn_only")); $nodeinfo = array(); $nodeinfo["version"] = "1.0"; $nodeinfo["software"] = array("name" => "friendica", "version" => FRIENDICA_VERSION . "-" . DB_UPDATE_VERSION); $nodeinfo["protocols"] = array(); $nodeinfo["protocols"]["inbound"] = array(); $nodeinfo["protocols"]["outbound"] = array(); if (get_config("system", "diaspora_enabled")) { $nodeinfo["protocols"]["inbound"][] = "diaspora"; $nodeinfo["protocols"]["outbound"][] = "diaspora"; } $nodeinfo["protocols"]["inbound"][] = "friendica"; $nodeinfo["protocols"]["outbound"][] = "friendica"; if (!get_config("system", "ostatus_disabled")) { $nodeinfo["protocols"]["inbound"][] = "gnusocial"; $nodeinfo["protocols"]["outbound"][] = "gnusocial"; } $nodeinfo["services"] = array(); $nodeinfo["services"]["inbound"] = array(); $nodeinfo["services"]["outbound"] = array(); $nodeinfo["openRegistrations"] = $a->config['register_policy'] != 0; $nodeinfo["usage"] = array(); $nodeinfo["usage"]["users"] = array("total" => (int) get_config("nodeinfo", "total_users"), "activeHalfyear" => (int) get_config("nodeinfo", "active_users_halfyear"), "activeMonth" => (int) get_config("nodeinfo", "active_users_monthly")); $nodeinfo["usage"]["localPosts"] = (int) get_config("nodeinfo", "local_posts"); $nodeinfo["usage"]["localComments"] = (int) get_config("nodeinfo", "local_comments"); $nodeinfo["metadata"] = array("nodeName" => $a->config["sitename"]); if (nodeinfo_plugin_enabled("appnet")) { $nodeinfo["services"]["inbound"][] = "appnet"; } if (nodeinfo_plugin_enabled("appnet") or nodeinfo_plugin_enabled("buffer")) { $nodeinfo["services"]["outbound"][] = "appnet"; } if (nodeinfo_plugin_enabled("blogger")) { $nodeinfo["services"]["outbound"][] = "blogger"; } if (nodeinfo_plugin_enabled("dwpost")) { $nodeinfo["services"]["outbound"][] = "dreamwidth"; } if (nodeinfo_plugin_enabled("fbpost") or nodeinfo_plugin_enabled("buffer")) { $nodeinfo["services"]["outbound"][] = "facebook"; } if (nodeinfo_plugin_enabled("statusnet")) { $nodeinfo["services"]["inbound"][] = "gnusocial"; $nodeinfo["services"]["outbound"][] = "gnusocial"; } if (nodeinfo_plugin_enabled("gpluspost") or nodeinfo_plugin_enabled("buffer")) { $nodeinfo["services"]["outbound"][] = "google"; } if (nodeinfo_plugin_enabled("ijpost")) { $nodeinfo["services"]["outbound"][] = "insanejournal"; } if (nodeinfo_plugin_enabled("libertree")) { $nodeinfo["services"]["outbound"][] = "libertree"; } if (nodeinfo_plugin_enabled("buffer")) { $nodeinfo["services"]["outbound"][] = "linkedin"; } if (nodeinfo_plugin_enabled("ljpost")) { $nodeinfo["services"]["outbound"][] = "livejournal"; } if (nodeinfo_plugin_enabled("buffer")) { $nodeinfo["services"]["outbound"][] = "pinterest"; } if (nodeinfo_plugin_enabled("posterous")) { $nodeinfo["services"]["outbound"][] = "posterous"; } if (nodeinfo_plugin_enabled("pumpio")) { $nodeinfo["services"]["inbound"][] = "pumpio"; $nodeinfo["services"]["outbound"][] = "pumpio"; } // redmatrix if ($smtp) { $nodeinfo["services"]["outbound"][] = "smtp"; } if (nodeinfo_plugin_enabled("tumblr")) { $nodeinfo["services"]["outbound"][] = "tumblr"; } if (nodeinfo_plugin_enabled("twitter") or nodeinfo_plugin_enabled("buffer")) { $nodeinfo["services"]["outbound"][] = "twitter"; } if (nodeinfo_plugin_enabled("wppost")) { $nodeinfo["services"]["outbound"][] = "wordpress"; } $nodeinfo["metadata"]["protocols"] = $nodeinfo["protocols"]; $nodeinfo["metadata"]["protocols"]["outbound"][] = "atom1.0"; $nodeinfo["metadata"]["protocols"]["inbound"][] = "atom1.0"; $nodeinfo["metadata"]["protocols"]["inbound"][] = "rss2.0"; $nodeinfo["metadata"]["services"] = $nodeinfo["services"]; if (nodeinfo_plugin_enabled("twitter")) { $nodeinfo["metadata"]["services"]["inbound"][] = "twitter"; } header('Content-type: application/json; charset=utf-8'); echo json_encode($nodeinfo, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); exit; }
function pubsubhubbub_init(&$a) { // PuSH subscription must be considered "public" so just block it // if public access isn't enabled. if (get_config('system', 'block_public')) { http_status_exit(403); } // Subscription request from subscriber // https://pubsubhubbub.googlecode.com/git/pubsubhubbub-core-0.4.html#anchor4 // Example from GNU Social: // [hub_mode] => subscribe // [hub_callback] => http://status.local/main/push/callback/1 // [hub_verify] => sync // [hub_verify_token] => af11... // [hub_secret] => af11... // [hub_topic] => http://friendica.local/dfrn_poll/sazius if ($_SERVER['REQUEST_METHOD'] === 'POST') { $hub_mode = push_post_var('hub_mode'); $hub_callback = push_post_var('hub_callback'); $hub_verify = push_post_var('hub_verify'); $hub_verify_token = push_post_var('hub_verify_token'); $hub_secret = push_post_var('hub_secret'); $hub_topic = push_post_var('hub_topic'); // check for valid hub_mode if ($hub_mode === 'subscribe') { $subscribe = 1; } else { if ($hub_mode === 'unsubscribe') { $subscribe = 0; } else { logger("pubsubhubbub: invalid hub_mode={$hub_mode}, ignoring."); http_status_exit(404); } } logger("pubsubhubbub: {$hub_mode} request from " . $_SERVER['REMOTE_ADDR']); // get the nick name from the topic, a bit hacky but needed $nick = substr(strrchr($hub_topic, "/"), 1); if (!$nick) { logger('pubsubhubbub: bad hub_topic=$hub_topic, ignoring.'); http_status_exit(404); } // fetch user from database given the nickname $owner = channelx_by_nick($nick); if (!$owner) { logger('pubsubhubbub: local account not found: ' . $nick); http_status_exit(404); } if (!perm_is_allowed($owner['channel_id'], '', 'view_stream')) { logger('pubsubhubbub: local channel ' . $nick . 'has chosen to hide wall, ignoring.'); http_status_exit(403); } // sanity check that topic URLs are the same if (!link_compare($hub_topic, z_root() . '/feed/' . $nick)) { logger('pubsubhubbub: not a valid hub topic ' . $hub_topic); http_status_exit(404); } // do subscriber verification according to the PuSH protocol $hub_challenge = random_string(40); $params = 'hub.mode=' . ($subscribe == 1 ? 'subscribe' : 'unsubscribe') . '&hub.topic=' . urlencode($hub_topic) . '&hub.challenge=' . $hub_challenge . '&hub.lease_seconds=604800' . '&hub.verify_token=' . $hub_verify_token; // lease time is hard coded to one week (in seconds) // we don't actually enforce the lease time because GNU // Social/StatusNet doesn't honour it (yet) $x = z_fetch_url($hub_callback . "?" . $params); if (!$x['success']) { logger("pubsubhubbub: subscriber verification at {$hub_callback} " . "returned {$ret}, ignoring."); http_status_exit(404); } // check that the correct hub_challenge code was echoed back if (trim($x['body']) !== $hub_challenge) { logger("pubsubhubbub: subscriber did not echo back " . "hub.challenge, ignoring."); logger("\"{$hub_challenge}\" != \"" . trim($x['body']) . "\""); http_status_exit(404); } // fetch the old subscription if it exists $orig = q("SELECT * FROM `push_subscriber` WHERE `callback_url` = '%s'", dbesc($hub_callback)); // delete old subscription if it exists q("DELETE FROM push_subscriber WHERE callback_url = '%s' and topic = '%s'", dbesc($hub_callback), dbesc($hub_topic)); if ($subscribe) { $last_update = datetime_convert('UTC', 'UTC', 'now', 'Y-m-d H:i:s'); // if we are just updating an old subscription, keep the // old values for last_update if ($orig) { $last_update = $orig[0]['last_update']; } // subscribe means adding the row to the table q("INSERT INTO push_subscriber ( callback_url, topic, last_update, secret) values ('%s', '%s', '%s', '%s') ", dbesc($hub_callback), dbesc($hub_topic), dbesc($last_update), dbesc($hub_secret)); logger("pubsubhubbub: successfully subscribed [{$hub_callback}]."); } else { logger("pubsubhubbub: successfully unsubscribed [{$hub_callback}]."); // we do nothing here, since the row was already deleted } http_status_exit(202); } killme(); }
function dfrn_poll_init(&$a) { $dfrn_id = x($_GET, 'dfrn_id') ? $_GET['dfrn_id'] : ''; $type = x($_GET, 'type') ? $_GET['type'] : 'data'; $last_update = x($_GET, 'last_update') ? $_GET['last_update'] : ''; $destination_url = x($_GET, 'destination_url') ? $_GET['destination_url'] : ''; $challenge = x($_GET, 'challenge') ? $_GET['challenge'] : ''; $sec = x($_GET, 'sec') ? $_GET['sec'] : ''; $dfrn_version = x($_GET, 'dfrn_version') ? (double) $_GET['dfrn_version'] : 2.0; $perm = x($_GET, 'perm') ? $_GET['perm'] : 'r'; $quiet = x($_GET, 'quiet') ? true : false; $direction = -1; if (strpos($dfrn_id, ':') == 1) { $direction = intval(substr($dfrn_id, 0, 1)); $dfrn_id = substr($dfrn_id, 2); } if ($dfrn_id === '' && !x($_POST, 'dfrn_id')) { if (get_config('system', 'block_public') && !local_user() && !remote_user()) { http_status_exit(403); } $user = ''; if ($a->argc > 1) { $r = q("SELECT `hidewall`,`nickname` FROM `user` WHERE `user`.`nickname` = '%s' LIMIT 1", dbesc($a->argv[1])); if (!$r) { http_status_exit(404); } if ($r[0]['hidewall'] && !local_user()) { http_status_exit(403); } $user = $r[0]['nickname']; } logger('dfrn_poll: public feed request from ' . $_SERVER['REMOTE_ADDR'] . ' for ' . $user); header("Content-type: application/atom+xml"); echo get_feed_for($a, '', $user, $last_update); killme(); } if ($type === 'profile' && !strlen($sec)) { $sql_extra = ''; switch ($direction) { case -1: $sql_extra = sprintf(" AND ( `dfrn-id` = '%s' OR `issued-id` = '%s' ) ", dbesc($dfrn_id), dbesc($dfrn_id)); $my_id = $dfrn_id; break; case 0: $sql_extra = sprintf(" AND `issued-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id)); $my_id = '1:' . $dfrn_id; break; case 1: $sql_extra = sprintf(" AND `dfrn-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id)); $my_id = '0:' . $dfrn_id; break; default: goaway(z_root()); break; // NOTREACHED } $r = q("SELECT `contact`.*, `user`.`username`, `user`.`nickname`\n\t\t\tFROM `contact` LEFT JOIN `user` ON `contact`.`uid` = `user`.`uid`\n\t\t\tWHERE `contact`.`blocked` = 0 AND `contact`.`pending` = 0\n\t\t\tAND `user`.`nickname` = '%s' {$sql_extra} LIMIT 1", dbesc($a->argv[1])); if (count($r)) { $s = fetch_url($r[0]['poll'] . '?dfrn_id=' . $my_id . '&type=profile-check'); logger("dfrn_poll: old profile returns " . $s, LOGGER_DATA); if (strlen($s)) { $xml = parse_xml_string($s); if ((int) $xml->status == 1) { $_SESSION['authenticated'] = 1; if (!x($_SESSION, 'remote')) { $_SESSION['remote'] = array(); } $_SESSION['remote'][] = array('cid' => $r[0]['id'], 'uid' => $r[0]['uid'], 'url' => $r[0]['url']); $_SESSION['visitor_id'] = $r[0]['id']; $_SESSION['visitor_home'] = $r[0]['url']; $_SESSION['visitor_handle'] = $r[0]['addr']; $_SESSION['visitor_visiting'] = $r[0]['uid']; if (!$quiet) { info(sprintf(t('%1$s welcomes %2$s'), $r[0]['username'], $r[0]['name']) . EOL); } // Visitors get 1 day session. $session_id = session_id(); $expire = time() + 86400; q("UPDATE `session` SET `expire` = '%s' WHERE `sid` = '%s'", dbesc($expire), dbesc($session_id)); } } $profile = $r[0]['nickname']; goaway(strlen($destination_url) ? $destination_url : $a->get_baseurl() . '/profile/' . $profile); } goaway(z_root()); } if ($type === 'profile-check' && $dfrn_version < 2.2) { if (strlen($challenge) && strlen($sec)) { q("DELETE FROM `profile_check` WHERE `expire` < " . intval(time())); $r = q("SELECT * FROM `profile_check` WHERE `sec` = '%s' ORDER BY `expire` DESC LIMIT 1", dbesc($sec)); if (!count($r)) { xml_status(3, 'No ticket'); // NOTREACHED } $orig_id = $r[0]['dfrn_id']; if (strpos($orig_id, ':')) { $orig_id = substr($orig_id, 2); } $c = q("SELECT * FROM `contact` WHERE `id` = %d LIMIT 1", intval($r[0]['cid'])); if (!count($c)) { xml_status(3, 'No profile'); } $contact = $c[0]; $sent_dfrn_id = hex2bin($dfrn_id); $challenge = hex2bin($challenge); $final_dfrn_id = ''; if ($contact['duplex'] && strlen($contact['prvkey'])) { openssl_private_decrypt($sent_dfrn_id, $final_dfrn_id, $contact['prvkey']); openssl_private_decrypt($challenge, $decoded_challenge, $contact['prvkey']); } else { openssl_public_decrypt($sent_dfrn_id, $final_dfrn_id, $contact['pubkey']); openssl_public_decrypt($challenge, $decoded_challenge, $contact['pubkey']); } $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.')); if (strpos($final_dfrn_id, ':') == 1) { $final_dfrn_id = substr($final_dfrn_id, 2); } if ($final_dfrn_id != $orig_id) { logger('profile_check: ' . $final_dfrn_id . ' != ' . $orig_id, LOGGER_DEBUG); // did not decode properly - cannot trust this site xml_status(3, 'Bad decryption'); } header("Content-type: text/xml"); echo "<?xml version=\"1.0\" encoding=\"UTF-8\"?><dfrn_poll><status>0</status><challenge>{$decoded_challenge}</challenge><sec>{$sec}</sec></dfrn_poll>"; killme(); // NOTREACHED } else { // old protocol switch ($direction) { case 1: $dfrn_id = '0:' . $dfrn_id; break; case 0: $dfrn_id = '1:' . $dfrn_id; break; default: break; } q("DELETE FROM `profile_check` WHERE `expire` < " . intval(time())); $r = q("SELECT * FROM `profile_check` WHERE `dfrn_id` = '%s' ORDER BY `expire` DESC", dbesc($dfrn_id)); if (count($r)) { xml_status(1); return; // NOTREACHED } xml_status(0); return; // NOTREACHED } } }
/** * @brief * * @param array $channel * @param string $observer_hash * @param array $params * @return string */ function get_feed_for($channel, $observer_hash, $params) { if (!channel) { http_status_exit(401); } if ($params['pages']) { if (!perm_is_allowed($channel['channel_id'], $observer_hash, 'view_pages')) { http_status_exit(403); } } else { if (!perm_is_allowed($channel['channel_id'], $observer_hash, 'view_stream')) { http_status_exit(403); } } $items = items_fetch(array('wall' => '1', 'datequery' => $params['begin'], 'datequery2' => $params['end'], 'start' => $params['start'], 'records' => $params['records'], 'direction' => $params['direction'], 'pages' => $params['pages'], 'order' => 'post', 'top' => $params['top']), $channel, $observer_hash, CLIENT_MODE_NORMAL, get_app()->module); $feed_template = get_markup_template('atom_feed.tpl'); $atom = ''; $atom .= replace_macros($feed_template, array('$version' => xmlify(RED_VERSION), '$red' => xmlify(PLATFORM_NAME), '$feed_id' => xmlify($channel['xchan_url']), '$feed_title' => xmlify($channel['channel_name']), '$feed_updated' => xmlify(datetime_convert('UTC', 'UTC', 'now', ATOM_TIME)), '$hub' => '', '$salmon' => '', '$name' => xmlify($channel['channel_name']), '$profile_page' => xmlify($channel['xchan_url']), '$mimephoto' => xmlify($channel['xchan_photo_mimetype']), '$photo' => xmlify($channel['xchan_photo_l']), '$thumb' => xmlify($channel['xchan_photo_m']), '$picdate' => '', '$uridate' => '', '$namdate' => '', '$birthday' => '', '$community' => '')); call_hooks('atom_feed', $atom); if ($items) { $type = 'html'; foreach ($items as $item) { if ($item['item_private']) { continue; } /** @BUG $owner is undefined in this call */ $atom .= atom_entry($item, $type, null, $owner, true); } } call_hooks('atom_feed_end', $atom); $atom .= '</feed>' . "\r\n"; return $atom; }
function poco_init(&$a) { require_once "include/bbcode.php"; $system_mode = false; if (intval(get_config('system', 'block_public')) || get_config('system', 'block_local_dir')) { http_status_exit(401); } if ($a->argc > 1) { $user = notags(trim($a->argv[1])); } if (!x($user)) { $c = q("SELECT * FROM `pconfig` WHERE `cat` = 'system' AND `k` = 'suggestme' AND `v` = 1"); if (!count($c)) { http_status_exit(401); } $system_mode = true; } $format = $_GET['format'] ? $_GET['format'] : 'json'; $justme = false; $global = false; if ($a->argc > 1 && $a->argv[1] === '@global') { $global = true; $update_limit = date("Y-m-d H:i:s", time() - 30 * 86400); } if ($a->argc > 2 && $a->argv[2] === '@me') { $justme = true; } if ($a->argc > 3 && $a->argv[3] === '@all') { $justme = false; } if ($a->argc > 3 && $a->argv[3] === '@self') { $justme = true; } if ($a->argc > 4 && intval($a->argv[4]) && $justme == false) { $cid = intval($a->argv[4]); } if (!$system_mode and !$global) { $r = q("SELECT `user`.*,`profile`.`hide-friends` from user left join profile on `user`.`uid` = `profile`.`uid`\n\t\t\twhere `user`.`nickname` = '%s' and `profile`.`is-default` = 1 limit 1", dbesc($user)); if (!count($r) || $r[0]['hidewall'] || $r[0]['hide-friends']) { http_status_exit(404); } $user = $r[0]; } if ($justme) { $sql_extra = " AND `contact`.`self` = 1 "; } // else // $sql_extra = " AND `contact`.`self` = 0 "; if ($cid) { $sql_extra = sprintf(" AND `contact`.`id` = %d ", intval($cid)); } if (x($_GET, 'updatedSince')) { $update_limit = date("Y-m-d H:i:s", strtotime($_GET['updatedSince'])); } if ($global) { //$r = q("SELECT count(*) AS `total` FROM `gcontact` WHERE `updated` >= '%s' AND ((`last_contact` >= `last_failure`) OR (`updated` >= `last_failure`)) AND `network` IN ('%s')", $r = q("SELECT count(*) AS `total` FROM `gcontact` WHERE `updated` >= '%s' AND `updated` >= `last_failure` AND `network` IN ('%s', '%s', '%s')", dbesc($update_limit), dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS)); } elseif ($system_mode) { $r = q("SELECT count(*) AS `total` FROM `contact` WHERE `self` = 1 AND `network` IN ('%s', '%s', '%s', '%s')\n\t\t\tAND (`success_update` >= `failure_update` OR `last-item` >= `failure_update`)\n\t\t\tAND `uid` IN (SELECT `uid` FROM `pconfig` WHERE `cat` = 'system' AND `k` = 'suggestme' AND `v` = 1) ", dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS), dbesc(NETWORK_STATUSNET)); } else { $r = q("SELECT count(*) AS `total` FROM `contact` WHERE `uid` = %d AND `blocked` = 0 AND `pending` = 0 AND `hidden` = 0 AND `archive` = 0\n\t\t\tAND (`success_update` >= `failure_update` OR `last-item` >= `failure_update`)\n\t\t\tAND `network` IN ('%s', '%s', '%s', '%s') {$sql_extra}", intval($user['uid']), dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS), dbesc(NETWORK_STATUSNET)); } if (count($r)) { $totalResults = intval($r[0]['total']); } else { $totalResults = 0; } $startIndex = intval($_GET['startIndex']); if (!$startIndex) { $startIndex = 0; } $itemsPerPage = x($_GET, 'count') && intval($_GET['count']) ? intval($_GET['count']) : $totalResults; if ($global) { logger("Start global query", LOGGER_DEBUG); //$r = q("SELECT * FROM `gcontact` WHERE `updated` > '%s' AND `network` IN ('%s') AND ((`last_contact` >= `last_failure`) OR (`updated` > `last_failure`)) LIMIT %d, %d", $r = q("SELECT * FROM `gcontact` WHERE `updated` > '%s' AND `network` IN ('%s', '%s', '%s') AND `updated` > `last_failure`\n\t\t\tORDER BY `updated` DESC LIMIT %d, %d", dbesc($update_limit), dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS), intval($startIndex), intval($itemsPerPage)); } elseif ($system_mode) { logger("Start system mode query", LOGGER_DEBUG); $r = q("SELECT `contact`.*, `profile`.`about` AS `pabout`, `profile`.`locality` AS `plocation`, `profile`.`pub_keywords`, `profile`.`gender` AS `pgender`,\n\t\t\t`profile`.`address` AS `paddress`, `profile`.`region` AS `pregion`, `profile`.`postal-code` AS `ppostalcode`, `profile`.`country-name` AS `pcountry`\n\t\t\tFROM `contact` INNER JOIN `profile` ON `profile`.`uid` = `contact`.`uid`\n\t\t\tWHERE `self` = 1 AND `network` IN ('%s', '%s', '%s', '%s') AND `profile`.`is-default`\n\t\t\tAND ((`contact`.`success_update` >= `contact`.`failure_update`) OR (`contact`.`last-item` >= `contact`.`failure_update`))\n\t\t\tAND `contact`.`uid` IN (SELECT `uid` FROM `pconfig` WHERE `cat` = 'system' AND `k` = 'suggestme' AND `v` = 1) LIMIT %d, %d", dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS), dbesc(NETWORK_STATUSNET), intval($startIndex), intval($itemsPerPage)); } else { logger("Start query for user " . $user['nickname'], LOGGER_DEBUG); $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `blocked` = 0 AND `pending` = 0 AND `hidden` = 0 AND `archive` = 0\n\t\t\tAND (`success_update` >= `failure_update` OR `last-item` >= `failure_update`)\n\t\t\tAND `network` IN ('%s', '%s', '%s', '%s') {$sql_extra} LIMIT %d, %d", intval($user['uid']), dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS), dbesc(NETWORK_STATUSNET), intval($startIndex), intval($itemsPerPage)); } logger("Query done", LOGGER_DEBUG); $ret = array(); if (x($_GET, 'sorted')) { $ret['sorted'] = false; } if (x($_GET, 'filtered')) { $ret['filtered'] = false; } if (x($_GET, 'updatedSince') and !$global) { $ret['updatedSince'] = false; } $ret['startIndex'] = (int) $startIndex; $ret['itemsPerPage'] = (int) $itemsPerPage; $ret['totalResults'] = (int) $totalResults; $ret['entry'] = array(); $fields_ret = array('id' => false, 'displayName' => false, 'urls' => false, 'updated' => false, 'preferredUsername' => false, 'photos' => false, 'aboutMe' => false, 'currentLocation' => false, 'network' => false, 'gender' => false, 'tags' => false, 'address' => false, 'generation' => false); if (!x($_GET, 'fields') || $_GET['fields'] === '@all') { foreach ($fields_ret as $k => $v) { $fields_ret[$k] = true; } } else { $fields_req = explode(',', $_GET['fields']); foreach ($fields_req as $f) { $fields_ret[trim($f)] = true; } } if (is_array($r)) { if (count($r)) { foreach ($r as $rr) { if (!isset($rr['generation'])) { if ($global) { $rr['generation'] = 3; } elseif ($system_mode) { $rr['generation'] = 1; } else { $rr['generation'] = 2; } } if ($rr['about'] == "" and isset($rr['pabout'])) { $rr['about'] = $rr['pabout']; } if ($rr['location'] == "") { if (isset($rr['plocation'])) { $rr['location'] = $rr['plocation']; } if (isset($rr['pregion']) and $rr['pregion'] != "") { if ($rr['location'] != "") { $rr['location'] .= ", "; } $rr['location'] .= $rr['pregion']; } if (isset($rr['pcountry']) and $rr['pcountry'] != "") { if ($rr['location'] != "") { $rr['location'] .= ", "; } $rr['location'] .= $rr['pcountry']; } } if ($rr['gender'] == "" and isset($rr['pgender'])) { $rr['gender'] = $rr['pgender']; } if ($rr['keywords'] == "" and isset($rr['pub_keywords'])) { $rr['keywords'] = $rr['pub_keywords']; } $about = Cache::get("about:" . $rr['updated'] . ":" . $rr['nurl']); if (is_null($about)) { $about = bbcode($rr['about'], false, false); Cache::set("about:" . $rr['updated'] . ":" . $rr['nurl'], $about); } $entry = array(); if ($fields_ret['id']) { $entry['id'] = (int) $rr['id']; } if ($fields_ret['displayName']) { $entry['displayName'] = $rr['name']; } if ($fields_ret['aboutMe']) { $entry['aboutMe'] = $about; } if ($fields_ret['currentLocation']) { $entry['currentLocation'] = $rr['location']; } if ($fields_ret['gender']) { $entry['gender'] = $rr['gender']; } if ($fields_ret['generation']) { $entry['generation'] = (int) $rr['generation']; } if ($fields_ret['urls']) { $entry['urls'] = array(array('value' => $rr['url'], 'type' => 'profile')); if ($rr['addr'] && $rr['network'] !== NETWORK_MAIL) { $entry['urls'][] = array('value' => 'acct:' . $rr['addr'], 'type' => 'webfinger'); } } if ($fields_ret['preferredUsername']) { $entry['preferredUsername'] = $rr['nick']; } if ($fields_ret['updated']) { if (!$global) { $entry['updated'] = $rr['success_update']; if ($rr['name-date'] > $entry['updated']) { $entry['updated'] = $rr['name-date']; } if ($rr['uri-date'] > $entry['updated']) { $entry['updated'] = $rr['uri-date']; } if ($rr['avatar-date'] > $entry['updated']) { $entry['updated'] = $rr['avatar-date']; } } else { $entry['updated'] = $rr['updated']; } $entry['updated'] = date("c", strtotime($entry['updated'])); } if ($fields_ret['photos']) { $entry['photos'] = array(array('value' => $rr['photo'], 'type' => 'profile')); } if ($fields_ret['network']) { $entry['network'] = $rr['network']; if ($entry['network'] == NETWORK_STATUSNET) { $entry['network'] = NETWORK_OSTATUS; } if ($entry['network'] == "" and $rr['self']) { $entry['network'] = NETWORK_DFRN; } } if ($fields_ret['tags']) { $tags = str_replace(",", " ", $rr['keywords']); $tags = explode(" ", $tags); $cleaned = array(); foreach ($tags as $tag) { $tag = trim(strtolower($tag)); if ($tag != "") { $cleaned[] = $tag; } } $entry['tags'] = array($cleaned); } if ($fields_ret['address']) { $entry['address'] = array(); // Deactivated. It just reveals too much data. (Although its from the default profile) //if (isset($rr['paddress'])) // $entry['address']['streetAddress'] = $rr['paddress']; if (isset($rr['plocation'])) { $entry['address']['locality'] = $rr['plocation']; } if (isset($rr['pregion'])) { $entry['address']['region'] = $rr['pregion']; } // See above //if (isset($rr['ppostalcode'])) // $entry['address']['postalCode'] = $rr['ppostalcode']; if (isset($rr['pcountry'])) { $entry['address']['country'] = $rr['pcountry']; } } $ret['entry'][] = $entry; } } else { $ret['entry'][] = array(); } } else { http_status_exit(500); } logger("End of poco", LOGGER_DEBUG); if ($format === 'xml') { header('Content-type: text/xml'); echo replace_macros(get_markup_template('poco_xml.tpl'), array_xmlify(array('$response' => $ret))); killme(); } if ($format === 'json') { header('Content-type: application/json'); echo json_encode($ret); killme(); } else { http_status_exit(500); } }
/** * @brief Fires up the SabreDAV server. * * @param App &$a */ function cloud_init(&$a) { require_once 'include/reddav.php'; if (!is_dir('store')) { os_mkdir('store', STORAGE_DEFAULT_PERMISSIONS, false); } $which = null; if (argc() > 1) { $which = argv(1); } $profile = 0; $a->page['htmlhead'] .= '<link rel="alternate" type="application/atom+xml" href="' . $a->get_baseurl() . '/feed/' . $which . '" />' . "\r\n"; if ($which) { profile_load($a, $which, $profile); } $auth = new RedDAV\RedBasicAuth(); $ob_hash = get_observer_hash(); if ($ob_hash) { if (local_channel()) { $channel = $a->get_channel(); $auth->setCurrentUser($channel['channel_address']); $auth->channel_id = $channel['channel_id']; $auth->channel_hash = $channel['channel_hash']; $auth->channel_account_id = $channel['channel_account_id']; if ($channel['channel_timezone']) { $auth->setTimezone($channel['channel_timezone']); } } $auth->observer = $ob_hash; } if ($_GET['davguest']) { $_SESSION['davguest'] = true; } $_SERVER['QUERY_STRING'] = str_replace(array('?f=', '&f='), array('', ''), $_SERVER['QUERY_STRING']); $_SERVER['QUERY_STRING'] = strip_zids($_SERVER['QUERY_STRING']); $_SERVER['QUERY_STRING'] = preg_replace('/[\\?&]davguest=(.*?)([\\?&]|$)/ism', '', $_SERVER['QUERY_STRING']); $_SERVER['REQUEST_URI'] = str_replace(array('?f=', '&f='), array('', ''), $_SERVER['REQUEST_URI']); $_SERVER['REQUEST_URI'] = strip_zids($_SERVER['REQUEST_URI']); $_SERVER['REQUEST_URI'] = preg_replace('/[\\?&]davguest=(.*?)([\\?&]|$)/ism', '', $_SERVER['REQUEST_URI']); $rootDirectory = new RedDAV\RedDirectory('/', $auth); // A SabreDAV server-object $server = new DAV\Server($rootDirectory); // prevent overwriting changes each other with a lock backend $lockBackend = new DAV\Locks\Backend\File('store/[data]/locks'); $lockPlugin = new DAV\Locks\Plugin($lockBackend); $server->addPlugin($lockPlugin); $is_readable = false; if ($_SERVER['REQUEST_METHOD'] === 'GET') { try { $x = RedFileData('/' . $a->cmd, $auth); } catch (\Exception $e) { if ($e instanceof Sabre\DAV\Exception\Forbidden) { http_status_exit(401, 'Permission denied.'); } } } require_once 'include/RedDAV/RedBrowser.php'; // provide a directory view for the cloud in Hubzilla $browser = new RedDAV\RedBrowser($auth); $auth->setBrowserPlugin($browser); $server->addPlugin($browser); // Experimental QuotaPlugin // require_once('include/RedDAV/QuotaPlugin.php'); // $server->addPlugin(new RedDAV\QuotaPlugin($auth)); // All we need to do now, is to fire up the server $server->exec(); killme(); }
function poco_init(&$a) { $system_mode = false; if (intval(get_config('system', 'block_public'))) { http_status_exit(401); } if ($a->argc > 1) { $user = notags(trim($a->argv[1])); } if (!x($user)) { $c = q("select * from pconfig where cat = 'system' and k = 'suggestme' and v = 1"); if (!count($c)) { http_status_exit(401); } $system_mode = true; } $format = $_GET['format'] ? $_GET['format'] : 'json'; $justme = false; if ($a->argc > 2 && $a->argv[2] === '@me') { $justme = true; } if ($a->argc > 3 && $a->argv[3] === '@all') { $justme = false; } if ($a->argc > 3 && $a->argv[3] === '@self') { $justme = true; } if ($a->argc > 4 && intval($a->argv[4]) && $justme == false) { $cid = intval($a->argv[4]); } if (!$system_mode) { $r = q("SELECT `user`.*,`profile`.`hide-friends` from user left join profile on `user`.`uid` = `profile`.`uid`\n\t\t\twhere `user`.`nickname` = '%s' and `profile`.`is-default` = 1 limit 1", dbesc($user)); if (!count($r) || $r[0]['hidewall'] || $r[0]['hide-friends']) { http_status_exit(404); } $user = $r[0]; } if ($justme) { $sql_extra = " and `contact`.`self` = 1 "; } if ($cid) { $sql_extra = sprintf(" and `contact`.`id` = %d ", intval($cid)); } if ($system_mode) { $r = q("SELECT count(*) as `total` from `contact` where self = 1 \n\t\t\tand uid in (select uid from pconfig where cat = 'system' and k = 'suggestme' and v = 1) "); } else { $r = q("SELECT count(*) as `total` from `contact` where `uid` = %d and blocked = 0 and pending = 0 and hidden = 0\n\t\t\t{$sql_extra} ", intval($user['uid'])); } if (count($r)) { $totalResults = intval($r[0]['total']); } else { $totalResults = 0; } $startIndex = intval($_GET['startIndex']); if (!$startIndex) { $startIndex = 0; } $itemsPerPage = x($_GET, 'count') && intval($_GET['count']) ? intval($_GET['count']) : $totalResults; if ($system_mode) { $r = q("SELECT * from contact where self = 1 \n\t\t\tand uid in (select uid from pconfig where cat = 'system' and k = 'suggestme' and v = 1) limit %d, %d ", intval($startIndex), intval($itemsPerPage)); } else { $r = q("SELECT * from `contact` where `uid` = %d and blocked = 0 and pending = 0 and hidden = 0\n\t\t\t{$sql_extra} LIMIT %d, %d", intval($user['uid']), intval($startIndex), intval($itemsPerPage)); } $ret = array(); if (x($_GET, 'sorted')) { $ret['sorted'] = 'false'; } if (x($_GET, 'filtered')) { $ret['filtered'] = 'false'; } if (x($_GET, 'updatedSince')) { $ret['updateSince'] = 'false'; } $ret['startIndex'] = (string) $startIndex; $ret['itemsPerPage'] = (string) $itemsPerPage; $ret['totalResults'] = (string) $totalResults; $ret['entry'] = array(); $fields_ret = array('id' => false, 'displayName' => false, 'urls' => false, 'preferredUsername' => false, 'photos' => false); if (!x($_GET, 'fields') || $_GET['fields'] === '@all') { foreach ($fields_ret as $k => $v) { $fields_ret[$k] = true; } } else { $fields_req = explode(',', $_GET['fields']); foreach ($fields_req as $f) { $fields_ret[trim($f)] = true; } } if (is_array($r)) { if (count($r)) { foreach ($r as $rr) { $entry = array(); if ($fields_ret['id']) { $entry['id'] = $rr['id']; } if ($fields_ret['displayName']) { $entry['displayName'] = $rr['name']; } if ($fields_ret['urls']) { $entry['urls'] = array(array('value' => $rr['url'], 'type' => 'profile')); if ($rr['addr'] && $rr['network'] !== NETWORK_MAIL) { $entry['urls'][] = array('value' => 'acct:' . $rr['addr'], 'type' => 'webfinger'); } } if ($fields_ret['preferredUsername']) { $entry['preferredUsername'] = $rr['nick']; } if ($fields_ret['photos']) { $entry['photos'] = array(array('value' => $rr['photo'], 'type' => 'profile')); } $ret['entry'][] = $entry; } } else { $ret['entry'][] = array(); } } else { http_status_exit(500); } if ($format === 'xml') { header('Content-type: text/xml'); echo replace_macros(get_markup_template('poco_xml.tpl'), array_xmlify(array('$response' => $ret))); http_status_exit(500); } if ($format === 'json') { header('Content-type: application/json'); echo json_encode($ret); killme(); } else { http_status_exit(500); } }
function pubsub_post(&$a) { $sys_disabled = true; if (!get_config('system', 'disable_discover_tab')) { $sys_disabled = get_config('system', 'disable_diaspora_discover_tab'); } $sys = $sys_disabled ? null : get_sys_channel(); if ($sys) { $sys['system'] = true; } $xml = file_get_contents('php://input'); logger('pubsub: feed arrived from ' . $_SERVER['REMOTE_ADDR'] . ' for ' . App::$cmd); logger('pubsub: user-agent: ' . $_SERVER['HTTP_USER_AGENT']); logger('pubsub: data: ' . $xml, LOGGER_DATA); $nick = argc() > 1 ? escape_tags(trim(argv(1))) : ''; $contact_id = argc() > 2 ? intval(argv(2)) : 0; $channel = channelx_by_nick($nick); if (!$channel) { http_status_exit(200, 'OK'); } $importer_arr = array($channel); if ($sys) { $importer_arr[] = $sys; } foreach ($importer_arr as $channel) { if (!$channel['system']) { $connections = abook_connections($channel['channel_id'], ' and abook_id = ' . $contact_id); } else { $connections = q("select * from abook left join xchan on abook_xchan = xchan_hash where abook_id = %d", intval($contact_id)); } if ($connections) { $xchan = $connections[0]; } else { logger('connection ' . $contact_id . ' not found.'); continue; } if (!perm_is_allowed($channel['channel_id'], $xchan['xchan_hash'], 'send_stream') && !$channel['system']) { logger('permission denied.'); continue; } consume_feed($xml, $channel, $xchan, 1); consume_feed($xml, $channel, $xchan, 2); } http_status_exit(200, 'OK'); }
/** * @brief Fires up the SabreDAV server. * * @param App &$a */ function cloud_init(&$a) { // call ($currenttheme)_init since we're operating outside of index.php $theme_info_file = "view/theme/" . current_theme() . "/php/theme.php"; if (file_exists($theme_info_file)) { require_once $theme_info_file; if (function_exists(str_replace('-', '_', current_theme()) . '_init')) { $func = str_replace('-', '_', current_theme()) . '_init'; $func($a); } } require_once 'include/reddav.php'; if (!is_dir('store')) { os_mkdir('store', STORAGE_DEFAULT_PERMISSIONS, false); } $which = null; if (argc() > 1) { $which = argv(1); } $profile = 0; $channel = $a->get_channel(); $a->page['htmlhead'] .= '<link rel="alternate" type="application/atom+xml" href="' . $a->get_baseurl() . '/feed/' . $which . '" />' . "\r\n"; if ($which) { profile_load($a, $which, $profile); } $auth = new RedBasicAuth(); $ob_hash = get_observer_hash(); if ($ob_hash) { if (local_user()) { $channel = $a->get_channel(); $auth->setCurrentUser($channel['channel_address']); $auth->channel_name = $channel['channel_address']; $auth->channel_id = $channel['channel_id']; $auth->channel_hash = $channel['channel_hash']; $auth->channel_account_id = $channel['channel_account_id']; if ($channel['channel_timezone']) { $auth->timezone = $channel['channel_timezone']; } } $auth->observer = $ob_hash; } if ($_GET['davguest']) { $_SESSION['davguest'] = true; } $_SERVER['QUERY_STRING'] = str_replace(array('?f=', '&f='), array('', ''), $_SERVER['QUERY_STRING']); $_SERVER['QUERY_STRING'] = strip_zids($_SERVER['QUERY_STRING']); $_SERVER['QUERY_STRING'] = preg_replace('/[\\?&]davguest=(.*?)([\\?&]|$)/ism', '', $_SERVER['QUERY_STRING']); $_SERVER['REQUEST_URI'] = str_replace(array('?f=', '&f='), array('', ''), $_SERVER['REQUEST_URI']); $_SERVER['REQUEST_URI'] = strip_zids($_SERVER['REQUEST_URI']); $_SERVER['REQUEST_URI'] = preg_replace('/[\\?&]davguest=(.*?)([\\?&]|$)/ism', '', $_SERVER['REQUEST_URI']); $rootDirectory = new RedDirectory('/', $auth); // A SabreDAV server-object $server = new DAV\Server($rootDirectory); // prevent overwriting changes each other with a lock backend $lockBackend = new DAV\Locks\Backend\File('store/[data]/locks'); $lockPlugin = new DAV\Locks\Plugin($lockBackend); $server->addPlugin($lockPlugin); // The next section of code allows us to bypass prompting for http-auth if a FILE is being accessed anonymously and permissions // allow this. This way one can create hotlinks to public media files in their cloud and anonymous viewers won't get asked to login. // If a DIRECTORY is accessed or there are permission issues accessing the file and we aren't previously authenticated via zot, // prompt for HTTP-auth. This will be the default case for mounting a DAV directory. // In order to avoid prompting for passwords for viewing a DIRECTORY, add the URL query parameter 'davguest=1' $isapublic_file = false; $davguest = x($_SESSION, 'davguest') ? true : false; if (!$auth->observer && $_SERVER['REQUEST_METHOD'] === 'GET') { try { $x = RedFileData('/' . $a->cmd, $auth); if ($x instanceof RedFile) { $isapublic_file = true; } } catch (Exception $e) { $isapublic_file = false; } } if (!$auth->observer && !$isapublic_file && !$davguest) { try { $auth->Authenticate($server, t('Red Matrix - Guests: Username: {your email address}, Password: +++')); } catch (Exception $e) { logger('mod_cloud: auth exception' . $e->getMessage()); http_status_exit($e->getHTTPCode(), $e->getMessage()); } } // provide a directory view for the cloud in Red Matrix $browser = new RedBrowser($auth); $auth->setBrowserPlugin($browser); $server->addPlugin($browser); // All we need to do now, is to fire up the server $server->exec(); killme(); }
/** * @brief Process atom feed and update anything/everything we might need to update. * * @param array $xml * The (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds. * @param $importer * The contact_record (joined to user_record) of the local user who owns this * relationship. It is this person's stuff that is going to be updated. * @param $contact * The person who is sending us stuff. If not set, we MAY be processing a "follow" activity * from an external network and MAY create an appropriate contact record. Otherwise, we MUST * have a contact record. * @param int $pass by default ($pass = 0) we cannot guarantee that a parent item has been * imported prior to its children being seen in the stream unless we are certain * of how the feed is arranged/ordered. * * With $pass = 1, we only pull parent items out of the stream. * * With $pass = 2, we only pull children (comments/likes). * * So running this twice, first with pass 1 and then with pass 2 will do the right * thing regardless of feed ordering. This won't be adequate in a fully-threaded * model where comments can have sub-threads. That would require some massive sorting * to get all the feed items into a mostly linear ordering, and might still require * recursion. */ function consume_feed($xml, $importer, &$contact, $pass = 0) { require_once 'library/simplepie/simplepie.inc'; if (!strlen($xml)) { logger('consume_feed: empty input'); return; } $sys_expire = intval(get_config('system', 'default_expire_days')); $chn_expire = intval($importer['channel_expire_days']); $expire_days = $sys_expire; if ($chn_expire != 0 && $chn_expire < $sys_expire) { $expire_days = $chn_expire; } // logger('expire_days: ' . $expire_days); $feed = new SimplePie(); $feed->set_raw_data($xml); $feed->init(); if ($feed->error()) { logger('consume_feed: Error parsing XML: ' . $feed->error()); } $permalink = $feed->get_permalink(); // Check at the feed level for updated contact name and/or photo // process any deleted entries $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry'); if (is_array($del_entries) && count($del_entries) && $pass != 2) { foreach ($del_entries as $dentry) { $deleted = false; if (isset($dentry['attribs']['']['ref'])) { $mid = $dentry['attribs']['']['ref']; $deleted = true; if (isset($dentry['attribs']['']['when'])) { $when = $dentry['attribs']['']['when']; $when = datetime_convert('UTC', 'UTC', $when, 'Y-m-d H:i:s'); } else { $when = datetime_convert('UTC', 'UTC', 'now', 'Y-m-d H:i:s'); } } if ($deleted && is_array($contact)) { $r = q("SELECT * from item where mid = '%s' and author_xchan = '%s' and uid = %d limit 1", dbesc(base64url_encode($mid)), dbesc($contact['xchan_hash']), intval($importer['channel_id'])); if ($r) { $item = $r[0]; if (!intval($item['item_deleted'])) { logger('consume_feed: deleting item ' . $item['id'] . ' mid=' . base64url_decode($item['mid']), LOGGER_DEBUG); drop_item($item['id'], false); } } } } } // Now process the feed if ($feed->get_item_quantity()) { logger('consume_feed: feed item count = ' . $feed->get_item_quantity(), LOGGER_DEBUG); $items = $feed->get_items(); foreach ($items as $item) { $is_reply = false; $item_id = base64url_encode($item->get_id()); logger('consume_feed: processing ' . $item_id, LOGGER_DEBUG); $rawthread = $item->get_item_tags(NAMESPACE_THREAD, 'in-reply-to'); if (isset($rawthread[0]['attribs']['']['ref'])) { $is_reply = true; $parent_mid = base64url_encode($rawthread[0]['attribs']['']['ref']); } if ($is_reply) { if ($pass == 1) { continue; } // Have we seen it? If not, import it. $item_id = base64url_encode($item->get_id()); $author = array(); $datarray = get_atom_elements($feed, $item, $author); if ($contact['xchan_network'] === 'rss') { $datarray['public_policy'] = 'specific'; $datarray['comment_policy'] = 'none'; } if (!x($author, 'author_name') || $author['author_is_feed']) { $author['author_name'] = $contact['xchan_name']; } if (!x($author, 'author_link') || $author['author_is_feed']) { $author['author_link'] = $contact['xchan_url']; } if (!x($author, 'author_photo') || $author['author_is_feed']) { $author['author_photo'] = $contact['xchan_photo_m']; } $datarray['author_xchan'] = ''; if ($author['author_link'] != $contact['xchan_url']) { $x = import_author_unknown(array('name' => $author['author_name'], 'url' => $author['author_link'], 'photo' => array('src' => $author['author_photo']))); if ($x) { $datarray['author_xchan'] = $x; } } if (!$datarray['author_xchan']) { $datarray['author_xchan'] = $contact['xchan_hash']; } $datarray['owner_xchan'] = $contact['xchan_hash']; $r = q("SELECT edited FROM item WHERE mid = '%s' AND uid = %d LIMIT 1", dbesc($item_id), intval($importer['channel_id'])); // Update content if 'updated' changes if ($r) { if (x($datarray, 'edited') !== false && datetime_convert('UTC', 'UTC', $datarray['edited']) !== $r[0]['edited']) { // do not accept (ignore) an earlier edit than one we currently have. if (datetime_convert('UTC', 'UTC', $datarray['edited']) < $r[0]['edited']) { continue; } update_feed_item($importer['channel_id'], $datarray); } continue; } $datarray['parent_mid'] = $parent_mid; $datarray['aid'] = $importer['channel_account_id']; $datarray['uid'] = $importer['channel_id']; logger('consume_feed: ' . print_r($datarray, true), LOGGER_DATA); $xx = item_store($datarray); $r = $xx['item_id']; continue; } else { // Head post of a conversation. Have we seen it? If not, import it. $item_id = base64url_encode($item->get_id()); $author = array(); $datarray = get_atom_elements($feed, $item, $author); if ($contact['xchan_network'] === 'rss') { $datarray['public_policy'] = 'specific'; $datarray['comment_policy'] = 'none'; } if (is_array($contact)) { if (!x($author, 'author_name') || $author['author_is_feed']) { $author['author_name'] = $contact['xchan_name']; } if (!x($author, 'author_link') || $author['author_is_feed']) { $author['author_link'] = $contact['xchan_url']; } if (!x($author, 'author_photo') || $author['author_is_feed']) { $author['author_photo'] = $contact['xchan_photo_m']; } } if (!x($author, 'author_name') || !x($author, 'author_link')) { logger('consume_feed: no author information! ' . print_r($author, true)); continue; } $datarray['author_xchan'] = ''; if (activity_match($datarray['verb'], ACTIVITY_FOLLOW) && $datarray['obj_type'] === ACTIVITY_OBJ_PERSON) { $cb = array('item' => $datarray, 'channel' => $importer, 'xchan' => null, 'author' => $author, 'caught' => false); call_hooks('follow_from_feed', $cb); if ($cb['caught']) { if ($cb['return_code']) { http_status_exit($cb['return_code']); } continue; } } if ($author['author_link'] != $contact['xchan_url']) { $x = import_author_unknown(array('name' => $author['author_name'], 'url' => $author['author_link'], 'photo' => array('src' => $author['author_photo']))); if ($x) { $datarray['author_xchan'] = $x; } } if (!$datarray['author_xchan']) { $datarray['author_xchan'] = $contact['xchan_hash']; } $datarray['owner_xchan'] = $contact['xchan_hash']; if (array_key_exists('created', $datarray) && $datarray['created'] != NULL_DATE && $expire_days) { $t1 = $datarray['created']; $t2 = datetime_convert('UTC', 'UTC', 'now - ' . $expire_days . 'days'); if ($t1 < $t2) { logger('feed content older than expiration. Ignoring.', LOGGER_DEBUG, LOG_INFO); continue; } } $r = q("SELECT edited FROM item WHERE mid = '%s' AND uid = %d LIMIT 1", dbesc($item_id), intval($importer['channel_id'])); // Update content if 'updated' changes if ($r) { if (x($datarray, 'edited') !== false && datetime_convert('UTC', 'UTC', $datarray['edited']) !== $r[0]['edited']) { // do not accept (ignore) an earlier edit than one we currently have. if (datetime_convert('UTC', 'UTC', $datarray['edited']) < $r[0]['edited']) { continue; } update_feed_item($importer['channel_id'], $datarray); } continue; } $datarray['parent_mid'] = $item_id; $datarray['uid'] = $importer['channel_id']; $datarray['aid'] = $importer['channel_account_id']; if (!link_compare($author['owner_link'], $contact['xchan_url'])) { logger('consume_feed: Correcting item owner.', LOGGER_DEBUG); $author['owner_name'] = $contact['name']; $author['owner_link'] = $contact['url']; $author['owner_avatar'] = $contact['thumb']; } if (!post_is_importable($datarray, $contact)) { continue; } logger('consume_feed: author ' . print_r($author, true), LOGGER_DEBUG); logger('consume_feed: ' . print_r($datarray, true), LOGGER_DATA); $xx = item_store($datarray); $r = $xx['item_id']; continue; } } } }
function search_content(&$a) { if (get_config('system', 'block_public') && !local_user() && !remote_user()) { notice(t('Public access denied.') . EOL); return; } if (get_config('system', 'local_search') and !local_user()) { http_status_exit(403, array("title" => t("Public access denied."), "description" => t("Only logged in users are permitted to perform a search."))); killme(); //notice(t('Public access denied.').EOL); //return; } if (get_config('system', 'permit_crawling') and !local_user()) { // Default values: // 10 requests are "free", after the 11th only a call per minute is allowed $free_crawls = intval(get_config('system', 'free_crawls')); if ($free_crawls == 0) { $free_crawls = 10; } $crawl_permit_period = intval(get_config('system', 'crawl_permit_period')); if ($crawl_permit_period == 0) { $crawl_permit_period = 10; } $remote = $_SERVER["REMOTE_ADDR"]; $result = Cache::get("remote_search:" . $remote); if (!is_null($result)) { $resultdata = json_decode($result); if ($resultdata->time > time() - $crawl_permit_period and $resultdata->accesses > $free_crawls) { http_status_exit(429, array("title" => t("Too Many Requests"), "description" => t("Only one search per minute is permitted for not logged in users."))); killme(); } Cache::set("remote_search:" . $remote, json_encode(array("time" => time(), "accesses" => $resultdata->accesses + 1)), CACHE_HOUR); } else { Cache::set("remote_search:" . $remote, json_encode(array("time" => time(), "accesses" => 1)), CACHE_HOUR); } } nav_set_selected('search'); $o = '<h3>' . t('Search') . '</h3>'; if (x($a->data, 'search')) { $search = notags(trim($a->data['search'])); } else { $search = x($_GET, 'search') ? notags(trim(rawurldecode($_GET['search']))) : ''; } $tag = false; if (x($_GET, 'tag')) { $tag = true; $search = x($_GET, 'tag') ? notags(trim(rawurldecode($_GET['tag']))) : ''; } $o .= search($search, 'search-box', '/search', local_user() ? true : false, false); if (strpos($search, '#') === 0) { $tag = true; $search = substr($search, 1); } if (strpos($search, '@') === 0) { return dirfind_content($a); } if (strpos($search, '!') === 0) { return dirfind_content($a); } if (x($_GET, 'search-option')) { switch ($_GET['search-option']) { case 'fulltext': break; case 'tags': $tag = true; break; case 'contacts': return dirfind_content($a, "@"); break; case 'forums': return dirfind_content($a, "!"); break; } } if (!$search) { return $o; } if (get_config('system', 'only_tag_search')) { $tag = true; } // Here is the way permissions work in the search module... // Only public posts can be shown // OR your own posts if you are a logged in member // No items will be shown if the member has a blocked profile wall. if ($tag) { logger("Start tag search for '" . $search . "'", LOGGER_DEBUG); $r = q("SELECT STRAIGHT_JOIN `item`.`uri`, `item`.*, `item`.`id` AS `item_id`,\n\t\t\t\t`contact`.`name`, `contact`.`photo`, `contact`.`url`, `contact`.`alias`, `contact`.`rel`,\n\t\t\t\t`contact`.`network`, `contact`.`thumb`, `contact`.`self`, `contact`.`writable`,\n\t\t\t\t`contact`.`id` AS `cid`, `contact`.`uid` AS `contact-uid`\n\t\t\tFROM `term`\n\t\t\t\tINNER JOIN `item` ON `item`.`id`=`term`.`oid`\n\t\t\t\tINNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id` AND NOT `contact`.`blocked` AND NOT `contact`.`pending`\n\t\t\tWHERE `item`.`visible` AND NOT `item`.`deleted` AND NOT `item`.`moderated`\n\t\t\t\tAND (`term`.`uid` = 0 OR (`term`.`uid` = %d AND NOT `term`.`global`)) AND `term`.`otype` = %d AND `term`.`type` = %d AND `term`.`term` = '%s'\n\t\t\tORDER BY term.created DESC LIMIT %d , %d ", intval(local_user()), intval(TERM_OBJ_POST), intval(TERM_HASHTAG), dbesc(protect_sprintf($search)), intval($a->pager['start']), intval($a->pager['itemspage'])); } else { logger("Start fulltext search for '" . $search . "'", LOGGER_DEBUG); if (get_config('system', 'use_fulltext_engine')) { $sql_extra = sprintf(" AND MATCH (`item`.`body`, `item`.`title`) AGAINST ('%s' in boolean mode) ", dbesc(protect_sprintf($search))); } else { $sql_extra = sprintf(" AND `item`.`body` REGEXP '%s' ", dbesc(protect_sprintf(preg_quote($search)))); } $r = q("SELECT STRAIGHT_JOIN `item`.`uri`, `item`.*, `item`.`id` AS `item_id`,\n\t\t\t\t`contact`.`name`, `contact`.`photo`, `contact`.`url`, `contact`.`alias`, `contact`.`rel`,\n\t\t\t\t`contact`.`network`, `contact`.`thumb`, `contact`.`self`, `contact`.`writable`,\n\t\t\t\t`contact`.`id` AS `cid`, `contact`.`uid` AS `contact-uid`\n\t\t\tFROM `item`\n\t\t\t\tINNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id` AND NOT `contact`.`blocked` AND NOT `contact`.`pending`\n\t\t\tWHERE `item`.`visible` AND NOT `item`.`deleted` AND NOT `item`.`moderated`\n\t\t\t\tAND (`item`.`uid` = 0 OR (`item`.`uid` = %s AND (`item`.`private` OR NOT `item`.`network` IN ('%s', '%s', '%s'))))\n\t\t\t\t{$sql_extra}\n\t\t\tGROUP BY `item`.`uri` ORDER BY `item`.`id` DESC LIMIT %d , %d ", intval(local_user()), dbesc(NETWORK_DFRN), dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DIASPORA), intval($a->pager['start']), intval($a->pager['itemspage'])); } if (!count($r)) { info(t('No results.') . EOL); return $o; } if ($tag) { $title = sprintf(t('Items tagged with: %s'), $search); } else { $title = sprintf(t('Search results for: %s'), $search); } $o .= replace_macros(get_markup_template("section_title.tpl"), array('$title' => $title)); logger("Start Conversation for '" . $search . "'", LOGGER_DEBUG); $o .= conversation($a, $r, 'search', false); $o .= alt_pager($a, count($r)); logger("Done '" . $search . "'", LOGGER_DEBUG); return $o; }
function pubsubhubbub_init(&$a) { // PuSH subscription must be considered "public" so just block it // if public access isn't enabled. if (get_config('system', 'block_public')) { http_status_exit(403); } // Subscription request from subscriber // https://pubsubhubbub.googlecode.com/git/pubsubhubbub-core-0.4.html#anchor4 // Example from GNU Social: // [hub_mode] => subscribe // [hub_callback] => http://status.local/main/push/callback/1 // [hub_verify] => sync // [hub_verify_token] => af11... // [hub_secret] => af11... // [hub_topic] => http://friendica.local/dfrn_poll/sazius if ($_SERVER['REQUEST_METHOD'] === 'POST') { $hub_mode = post_var('hub_mode'); $hub_callback = post_var('hub_callback'); $hub_verify = post_var('hub_verify'); $hub_verify_token = post_var('hub_verify_token'); $hub_secret = post_var('hub_secret'); $hub_topic = post_var('hub_topic'); // check for valid hub_mode if ($hub_mode === 'subscribe') { $subscribe = 1; } else { if ($hub_mode === 'unsubscribe') { $subscribe = 0; } else { logger("pubsubhubbub: invalid hub_mode={$hub_mode}, ignoring."); http_status_exit(404); } } logger("pubsubhubbub: {$hub_mode} request from " . $_SERVER['REMOTE_ADDR']); // get the nick name from the topic, a bit hacky but needed $nick = substr(strrchr($hub_topic, "/"), 1); if (!$nick) { logger('pubsubhubbub: bad hub_topic=$hub_topic, ignoring.'); http_status_exit(404); } // fetch user from database given the nickname $r = q("SELECT * FROM `user` WHERE `nickname` = '%s'" . " AND `account_expired` = 0 AND `account_removed` = 0 LIMIT 1", dbesc($nick)); if (!count($r)) { logger('pubsubhubbub: local account not found: ' . $nick); http_status_exit(404); } $owner = $r[0]; // abort if user's wall is supposed to be private if ($r[0]['hidewall']) { logger('pubsubhubbub: local user ' . $nick . 'has chosen to hide wall, ignoring.'); http_status_exit(403); } // get corresponding row from contact table $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND NOT `blocked`" . " AND NOT `pending` AND `self` LIMIT 1", intval($owner['uid'])); if (!count($r)) { logger('pubsubhubbub: contact not found.'); http_status_exit(404); } $contact = $r[0]; // sanity check that topic URLs are the same if (!link_compare($hub_topic, $contact['poll'])) { logger('pubsubhubbub: hub topic ' . $hub_topic . ' != ' . $contact['poll']); http_status_exit(404); } // do subscriber verification according to the PuSH protocol $hub_challenge = random_string(40); $params = 'hub.mode=' . ($subscribe == 1 ? 'subscribe' : 'unsubscribe') . '&hub.topic=' . urlencode($hub_topic) . '&hub.challenge=' . $hub_challenge . '&hub.lease_seconds=604800' . '&hub.verify_token=' . $hub_verify_token; // lease time is hard coded to one week (in seconds) // we don't actually enforce the lease time because GNU // Social/StatusNet doesn't honour it (yet) $body = fetch_url($hub_callback . "?" . $params); $ret = $a->get_curl_code(); // give up if the HTTP return code wasn't a success (2xx) if ($ret < 200 || $ret > 299) { logger("pubsubhubbub: subscriber verification at {$hub_callback} " . "returned {$ret}, ignoring."); http_status_exit(404); } // check that the correct hub_challenge code was echoed back if (trim($body) !== $hub_challenge) { logger("pubsubhubbub: subscriber did not echo back " . "hub.challenge, ignoring."); logger("\"{$hub_challenge}\" != \"" . trim($body) . "\""); http_status_exit(404); } // fetch the old subscription if it exists $r = q("SELECT * FROM `push_subscriber` WHERE `callback_url` = '%s'", dbesc($hub_callback)); // delete old subscription if it exists q("DELETE FROM `push_subscriber` WHERE `callback_url` = '%s'", dbesc($hub_callback)); if ($subscribe) { $last_update = datetime_convert('UTC', 'UTC', 'now', 'Y-m-d H:i:s'); $push_flag = 0; // if we are just updating an old subscription, keep the // old values for push and last_update if (count($r)) { $last_update = $r[0]['last_update']; $push_flag = $r[0]['push']; } // subscribe means adding the row to the table q("INSERT INTO `push_subscriber` (`uid`, `callback_url`, " . "`topic`, `nickname`, `push`, `last_update`, `secret`) values " . "(%d, '%s', '%s', '%s', %d, '%s', '%s')", intval($owner['uid']), dbesc($hub_callback), dbesc($hub_topic), dbesc($nick), intval($push_flag), dbesc($last_update), dbesc($hub_secret)); logger("pubsubhubbub: successfully subscribed [{$hub_callback}]."); } else { logger("pubsubhubbub: successfully unsubscribed [{$hub_callback}]."); // we do nothing here, since the row was already deleted } http_status_exit(202); } killme(); }
/** * * diaspora_decode($importer,$xml,$format) * array $importer -> from user table * string $xml -> urldecoded Diaspora salmon * string $format 'legacy', 'salmon', or 'json' * * Returns array * 'message' -> decoded Diaspora XML message * 'author' -> author diaspora handle * 'key' -> author public key (converted to pkcs#8) * * Author and key are used elsewhere to save a lookup for verifying replies and likes */ function diaspora_decode($importer, $xml, $format) { $public = false; if ($format === 'json') { $json = json_decode($xml, true); if ($json['aes_key']) { $key_bundle = ''; $result = openssl_private_decrypt(base64_decode($json['aes_key']), $key_bundle, $importer['channel_prvkey']); if (!$result) { logger('decrypting key_bundle for ' . $importer['channel_address'] . ' failed: ' . $json['aes_key'], LOGGER_NORMAL, LOG_ERR); http_status_exit(400); } $jkey = json_decode($key_bundle, true); $xml = AES256CBC_decrypt(base64_decode($json['encrypted_magic_envelope']), base64_decode($jkey['key']), base64_decode($jkey['iv'])); if (!$xml) { logger('decrypting magic_envelope for ' . $importer['channel_address'] . ' failed: ' . $json['aes_key'], LOGGER_NORMAL, LOG_ERR); http_status_exit(400); } } } $basedom = parse_xml_string($xml); if ($format !== 'legacy') { $children = $basedom->children('http://salmon-protocol.org/ns/magic-env'); $public = true; $author_link = str_replace('acct:', '', base64url_decode($children->key_id)); /** SimpleXMLElement Object ( [encoding] => base64url [alg] => RSA-SHA256 [data] => ((base64url-encoded payload message)) [sig] => ((the RSA-SHA256 signature of the above data)) [key_id] => ((base64url-encoded Alice's diaspora ID)) ) **/ } else { $children = $basedom->children('https://joindiaspora.com/protocol'); if ($children->header) { $public = true; $author_link = str_replace('acct:', '', $children->header->author_id); } else { $encrypted_header = json_decode(base64_decode($children->encrypted_header)); $encrypted_aes_key_bundle = base64_decode($encrypted_header->aes_key); $ciphertext = base64_decode($encrypted_header->ciphertext); $outer_key_bundle = ''; openssl_private_decrypt($encrypted_aes_key_bundle, $outer_key_bundle, $importer['channel_prvkey']); $j_outer_key_bundle = json_decode($outer_key_bundle); $outer_iv = base64_decode($j_outer_key_bundle->iv); $outer_key = base64_decode($j_outer_key_bundle->key); $decrypted = AES256CBC_decrypt($ciphertext, $outer_key, $outer_iv); /** * $decrypted now contains something like * * <decrypted_header> * <iv>8e+G2+ET8l5BPuW0sVTnQw==</iv> * <aes_key>UvSMb4puPeB14STkcDWq+4QE302Edu15oaprAQSkLKU=</aes_key> ***** OBSOLETE * <author> * <name>Ryan Hughes</name> * <uri>acct:galaxor@diaspora.pirateship.org</uri> * </author> ***** CURRENT/LEGACY * <author_id>galaxor@diaspora.pirateship.org</author_id> ***** END DIFFS * </decrypted_header> */ logger('decrypted: ' . $decrypted, LOGGER_DATA); $idom = parse_xml_string($decrypted, false); $inner_iv = base64_decode($idom->iv); $inner_aes_key = base64_decode($idom->aes_key); $author_link = str_replace('acct:', '', $idom->author_id); } } $dom = $basedom->children(NAMESPACE_SALMON_ME); // figure out where in the DOM tree our data is hiding if ($dom->provenance->data) { $base = $dom->provenance; } elseif ($dom->env->data) { $base = $dom->env; } elseif ($dom->data) { $base = $dom; } if (!$base) { logger('mod-diaspora: unable to locate salmon data in xml ', LOGGER_NORMAL, LOG_ERR); http_status_exit(400); } // Stash the signature away for now. We have to find their key or it won't be good for anything. $signature = base64url_decode($base->sig); // unpack the data // strip whitespace so our data element will return to one big base64 blob $data = str_replace(array(" ", "\t", "\r", "\n"), array("", "", "", ""), $base->data); // stash away some other stuff for later $type = $base->data[0]->attributes()->type[0]; $keyhash = $base->sig[0]->attributes()->keyhash[0]; $encoding = $base->encoding; $alg = $base->alg; $signed_data = $data . '.' . base64url_encode($type, false) . '.' . base64url_encode($encoding, false) . '.' . base64url_encode($alg, false); // decode the data $data = base64url_decode($data); if ($format === 'legacy' && !$public) { // Decode the encrypted blob $final_msg = AES256CBC_decrypt(base64_decode($data), $inner_aes_key, $inner_iv); } else { $final_msg = $data; } if (!$author_link) { logger('mod-diaspora: Could not retrieve author URI.'); http_status_exit(400); } // Once we have the author URI, go to the web and try to find their public key // (first this will look it up locally if it is in the fcontact cache) // This will also convert diaspora public key from pkcs#1 to pkcs#8 logger('mod-diaspora: Fetching key for ' . $author_link); $key = get_diaspora_key($author_link); if (!$key) { logger('mod-diaspora: Could not retrieve author key.', LOGGER_NORMAL, LOG_WARNING); http_status_exit(400); } $verify = rsa_verify($signed_data, $signature, $key); if (!$verify) { logger('mod-diaspora: Message did not verify. Discarding.', LOGGER_NORMAL, LOG_ERR); http_status_exit(400); } logger('mod-diaspora: Message verified.'); return array('message' => $final_msg, 'author' => $author_link, 'key' => $key); }
/** * * diaspora_decode($importer,$xml) * array $importer -> from user table * string $xml -> urldecoded Diaspora salmon * * Returns array * 'message' -> decoded Diaspora XML message * 'author' -> author diaspora handle * 'key' -> author public key (converted to pkcs#8) * * Author and key are used elsewhere to save a lookup for verifying replies and likes */ function diaspora_decode($importer, $xml) { $public = false; $basedom = parse_xml_string($xml); $children = $basedom->children('https://joindiaspora.com/protocol'); if ($children->header) { $public = true; $author_link = str_replace('acct:', '', $children->header->author_id); } else { $encrypted_header = json_decode(base64_decode($children->encrypted_header)); $encrypted_aes_key_bundle = base64_decode($encrypted_header->aes_key); $ciphertext = base64_decode($encrypted_header->ciphertext); $outer_key_bundle = ''; openssl_private_decrypt($encrypted_aes_key_bundle, $outer_key_bundle, $importer['prvkey']); $j_outer_key_bundle = json_decode($outer_key_bundle); $outer_iv = base64_decode($j_outer_key_bundle->iv); $outer_key = base64_decode($j_outer_key_bundle->key); $decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $outer_key, $ciphertext, MCRYPT_MODE_CBC, $outer_iv); $decrypted = pkcs5_unpad($decrypted); /** * $decrypted now contains something like * * <decrypted_header> * <iv>8e+G2+ET8l5BPuW0sVTnQw==</iv> * <aes_key>UvSMb4puPeB14STkcDWq+4QE302Edu15oaprAQSkLKU=</aes_key> ***** OBSOLETE * <author> * <name>Ryan Hughes</name> * <uri>acct:galaxor@diaspora.pirateship.org</uri> * </author> ***** CURRENT * <author_id>galaxor@diaspora.priateship.org</author_id> ***** END DIFFS * </decrypted_header> */ logger('decrypted: ' . $decrypted, LOGGER_DEBUG); $idom = parse_xml_string($decrypted, false); $inner_iv = base64_decode($idom->iv); $inner_aes_key = base64_decode($idom->aes_key); $author_link = str_replace('acct:', '', $idom->author_id); } $dom = $basedom->children(NAMESPACE_SALMON_ME); // figure out where in the DOM tree our data is hiding if ($dom->provenance->data) { $base = $dom->provenance; } elseif ($dom->env->data) { $base = $dom->env; } elseif ($dom->data) { $base = $dom; } if (!$base) { logger('mod-diaspora: unable to locate salmon data in xml '); http_status_exit(400); } // Stash the signature away for now. We have to find their key or it won't be good for anything. $signature = base64url_decode($base->sig); // unpack the data // strip whitespace so our data element will return to one big base64 blob $data = str_replace(array(" ", "\t", "\r", "\n"), array("", "", "", ""), $base->data); // stash away some other stuff for later $type = $base->data[0]->attributes()->type[0]; $keyhash = $base->sig[0]->attributes()->keyhash[0]; $encoding = $base->encoding; $alg = $base->alg; $signed_data = $data . '.' . base64url_encode($type) . '.' . base64url_encode($encoding) . '.' . base64url_encode($alg); // decode the data $data = base64url_decode($data); if ($public) { $inner_decrypted = $data; } else { // Decode the encrypted blob $inner_encrypted = base64_decode($data); $inner_decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $inner_aes_key, $inner_encrypted, MCRYPT_MODE_CBC, $inner_iv); $inner_decrypted = pkcs5_unpad($inner_decrypted); } if (!$author_link) { logger('mod-diaspora: Could not retrieve author URI.'); http_status_exit(400); } // Once we have the author URI, go to the web and try to find their public key // (first this will look it up locally if it is in the fcontact cache) // This will also convert diaspora public key from pkcs#1 to pkcs#8 logger('mod-diaspora: Fetching key for ' . $author_link); $key = get_diaspora_key($author_link); if (!$key) { logger('mod-diaspora: Could not retrieve author key.'); http_status_exit(400); } $verify = rsa_verify($signed_data, $signature, $key); if (!$verify) { logger('mod-diaspora: Message did not verify. Discarding.'); http_status_exit(400); } logger('mod-diaspora: Message verified.'); return array('message' => $inner_decrypted, 'author' => $author_link, 'key' => $key); }
function salmon_post(&$a) { $xml = file_get_contents('php://input'); logger('mod-salmon: new salmon ' . $xml, LOGGER_DATA); $nick = $a->argc > 1 ? notags(trim($a->argv[1])) : ''; $mentions = $a->argc > 2 && $a->argv[2] === 'mention' ? true : false; $r = q("SELECT * FROM `user` WHERE `nickname` = '%s' AND `account_expired` = 0 AND `account_removed` = 0 LIMIT 1", dbesc($nick)); if (!count($r)) { http_status_exit(500); } $importer = $r[0]; // parse the xml $dom = simplexml_load_string($xml, 'SimpleXMLElement', 0, NAMESPACE_SALMON_ME); // figure out where in the DOM tree our data is hiding if ($dom->provenance->data) { $base = $dom->provenance; } elseif ($dom->env->data) { $base = $dom->env; } elseif ($dom->data) { $base = $dom; } if (!$base) { logger('mod-salmon: unable to locate salmon data in xml '); http_status_exit(400); } // Stash the signature away for now. We have to find their key or it won't be good for anything. $signature = base64url_decode($base->sig); // unpack the data // strip whitespace so our data element will return to one big base64 blob $data = str_replace(array(" ", "\t", "\r", "\n"), array("", "", "", ""), $base->data); // stash away some other stuff for later $type = $base->data[0]->attributes()->type[0]; $keyhash = $base->sig[0]->attributes()->keyhash[0]; $encoding = $base->encoding; $alg = $base->alg; // Salmon magic signatures have evolved and there is no way of knowing ahead of time which // flavour we have. We'll try and verify it regardless. $stnet_signed_data = $data; $signed_data = $data . '.' . base64url_encode($type) . '.' . base64url_encode($encoding) . '.' . base64url_encode($alg); $compliant_format = str_replace('=', '', $signed_data); // decode the data $data = base64url_decode($data); // Remove the xml declaration $data = preg_replace('/\\<\\?xml[^\\?].*\\?\\>/', '', $data); // Create a fake feed wrapper so simplepie doesn't choke $tpl = get_markup_template('fake_feed.tpl'); $base = substr($data, strpos($data, '<entry')); $feedxml = $tpl . $base . '</feed>'; logger('mod-salmon: Processed feed: ' . $feedxml); // Now parse it like a normal atom feed to scrape out the author URI $feed = new SimplePie(); $feed->set_raw_data($feedxml); $feed->enable_order_by_date(false); $feed->init(); logger('mod-salmon: Feed parsed.'); if ($feed->get_item_quantity()) { foreach ($feed->get_items() as $item) { $author = $item->get_author(); $author_link = unxmlify($author->get_link()); break; } } if (!$author_link) { logger('mod-salmon: Could not retrieve author URI.'); http_status_exit(400); } // Once we have the author URI, go to the web and try to find their public key logger('mod-salmon: Fetching key for ' . $author_link); $key = get_salmon_key($author_link, $keyhash); if (!$key) { logger('mod-salmon: Could not retrieve author key.'); http_status_exit(400); } $key_info = explode('.', $key); $m = base64url_decode($key_info[1]); $e = base64url_decode($key_info[2]); logger('mod-salmon: key details: ' . print_r($key_info, true), LOGGER_DEBUG); $pubkey = metopem($m, $e); // We should have everything we need now. Let's see if it verifies. $verify = rsa_verify($compliant_format, $signature, $pubkey); if (!$verify) { logger('mod-salmon: message did not verify using protocol. Trying padding hack.'); $verify = rsa_verify($signed_data, $signature, $pubkey); } if (!$verify) { logger('mod-salmon: message did not verify using padding. Trying old statusnet hack.'); $verify = rsa_verify($stnet_signed_data, $signature, $pubkey); } if (!$verify) { logger('mod-salmon: Message did not verify. Discarding.'); http_status_exit(400); } logger('mod-salmon: Message verified.'); /* * * If we reached this point, the message is good. Now let's figure out if the author is allowed to send us stuff. * */ $r = q("SELECT * FROM `contact` WHERE `network` = '%s' AND ( `url` = '%s' OR `alias` = '%s' ) \n\t\tAND `uid` = %d LIMIT 1", dbesc(NETWORK_OSTATUS), dbesc($author_link), dbesc($author_link), intval($importer['uid'])); if (!count($r)) { logger('mod-salmon: Author unknown to us.'); if (get_pconfig($importer['uid'], 'system', 'ostatus_autofriend')) { require_once 'include/follow.php'; $result = new_contact($importer['uid'], $author_link); if ($result['success']) { $r = q("SELECT * FROM `contact` WHERE `network` = '%s' AND ( `url` = '%s' OR `alias` = '%s' ) \n\t\t\t\t\tAND `uid` = %d LIMIT 1", dbesc(NETWORK_OSTATUS), dbesc($author_link), dbesc($author_link), intval($importer['uid'])); } } } // is this a follower? Or have we ignored the person? // If so we can not accept this post. if (count($r) && ($r[0]['readonly'] || $r[0]['rel'] == CONTACT_IS_FOLLOWER || $r[0]['blocked'])) { logger('mod-salmon: Ignoring this author.'); http_status_exit(202); // NOTREACHED } require_once 'include/items.php'; // Placeholder for hub discovery. We shouldn't find any hubs // since we supplied the fake feed header - and it doesn't have any. $hub = ''; /** * * anti-spam measure: consume_feed will accept a follow activity from * this person (and nothing else) if there is no existing contact record. * */ $contact_rec = count($r) ? $r[0] : null; consume_feed($feedxml, $importer, $contact_rec, $hub); http_status_exit(200); }