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 (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); }
/** * @brief Look up if channel is known and previously verified. * * A guid and a url, both signed by the sender, distinguish a known sender at a * known location. * This function looks these up to see if the channel is known and therefore * previously verified. If not, we will need to verify it. * * @param array $arr an associative array which must contain: * * \e string \b guid => guid of conversant * * \e string \b guid_sig => guid signed with conversant's private key * * \e string \b url => URL of the origination hub of this communication * * \e string \b url_sig => URL signed with conversant's private key * * @returns array|null null if site is blacklisted or not found, otherwise an * array with an hubloc record */ function zot_gethub($arr, $multiple = false) { if ($arr['guid'] && $arr['guid_sig'] && $arr['url'] && $arr['url_sig']) { if (!check_siteallowed($arr['url'])) { logger('blacklisted site: ' . $arr['url']); return null; } $limit = $multiple ? '' : ' limit 1 '; $sitekey = array_key_exists('sitekey', $arr) && $arr['sitekey'] ? " and hubloc_sitekey = '" . protect_sprintf($arr['sitekey']) . "' " : ''; $r = q("select * from hubloc\n\t\t\t\twhere hubloc_guid = '%s' and hubloc_guid_sig = '%s'\n\t\t\t\tand hubloc_url = '%s' and hubloc_url_sig = '%s'\n\t\t\t\t{$sitekey} {$limit}", dbesc($arr['guid']), dbesc($arr['guid_sig']), dbesc($arr['url']), dbesc($arr['url_sig'])); if ($r) { logger('zot_gethub: found', LOGGER_DEBUG); return $multiple ? $r : $r[0]; } } logger('zot_gethub: not found: ' . print_r($arr, true), LOGGER_DEBUG); return null; }
function diaspora_is_blacklisted($s) { if (!check_siteallowed($s)) { logger('blacklisted site: ' . $s); return true; } return false; }
function externals_run($argv, $argc) { cli_startup(); $a = get_app(); $total = 0; $attempts = 0; logger('externals: startup', LOGGER_DEBUG); // pull in some public posts while ($total == 0 && $attempts < 3) { $arr = array('url' => ''); call_hooks('externals_url_select', $arr); if ($arr['url']) { $url = $arr['url']; } else { $randfunc = db_getfunc('RAND'); // fixme this query does not deal with directory realms. $r = q("select site_url, site_pull from site where site_url != '%s' and site_flags != %d and site_type = %d and site_dead = 0 order by {$randfunc} limit 1", dbesc(z_root()), intval(DIRECTORY_MODE_STANDALONE), intval(SITE_TYPE_ZOT)); if ($r) { $url = $r[0]['site_url']; } } $blacklisted = false; if (!check_siteallowed($url)) { logger('blacklisted site: ' . $url); $blacklisted = true; } $attempts++; // make sure we can eventually break out if somebody blacklists all known sites if ($blacklisted) { if ($attempts > 20) { break; } $attempts--; continue; } if ($url) { if ($r[0]['site_pull'] !== NULL_DATE) { $mindate = urlencode(datetime_convert('', '', $r[0]['site_pull'] . ' - 1 day')); } else { $days = get_config('externals', 'since_days'); if ($days === false) { $days = 15; } $mindate = urlencode(datetime_convert('', '', 'now - ' . intval($days) . ' days')); } $feedurl = $url . '/zotfeed?f=&mindate=' . $mindate; logger('externals: pulling public content from ' . $feedurl, LOGGER_DEBUG); $x = z_fetch_url($feedurl); if ($x && $x['success']) { q("update site set site_pull = '%s' where site_url = '%s'", dbesc(datetime_convert()), dbesc($url)); $j = json_decode($x['body'], true); if ($j['success'] && $j['messages']) { $sys = get_sys_channel(); foreach ($j['messages'] as $message) { // on these posts, clear any route info. $message['route'] = ''; $results = process_delivery(array('hash' => 'undefined'), get_item_elements($message), array(array('hash' => $sys['xchan_hash'])), false, true); $total++; } logger('externals: import_public_posts: ' . $total . ' messages imported', LOGGER_DEBUG); } } } } }
function salmon_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 (App::$data['salmon_test']) { $xml = file_get_contents('test.xml'); App::$argv[1] = 'gnusoc'; } else { $xml = file_get_contents('php://input'); } logger('mod-salmon: new salmon ' . $xml, LOGGER_DATA); $nick = argc() > 1 ? trim(argv(1)) : ''; // $mentions = ((App::$argc > 2 && App::$argv[2] === 'mention') ? true : false); $importer = channelx_by_nick($nick); if (!$importer) { http_status_exit(500); } // @fixme check that this channel has the GNU-Social protocol enabled // 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); } logger('data: ' . $xml, LOGGER_DATA); // Stash the signature away for now. We have to find their key or it won't be good for anything. logger('sig: ' . $base->sig); $signature = base64url_decode($base->sig); logger('sig: ' . $base->sig . ' decoded length: ' . strlen($signature)); // 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, false) . '.' . base64url_encode($encoding, false) . '.' . base64url_encode($alg, false); $compliant_format = str_replace('=', '', $signed_data); // decode the data $data = base64url_decode($data); logger('decoded: ' . $data, LOGGER_DATA); // GNU-Social doesn't send a legal Atom feed over salmon, only an Atom entry. Unfortunately // our parser is a bit strict about compliance so we'll insert just enough of a feed // tag to trick it into believing it's a compliant feed. if (!strstr($data, '<feed')) { $data = str_replace('<entry ', '<feed xmlns="http://www.w3.org/2005/Atom"><entry ', $data); $data .= '</feed>'; } $datarray = process_salmon_feed($data, $importer); $author_link = $datarray['author']['author_link']; $item = $datarray['item']; if (!$author_link) { logger('mod-salmon: Could not retrieve author URI.'); http_status_exit(400); } $r = q("select xchan_pubkey from xchan where xchan_guid = '%s' limit 1", dbesc($author_link)); if ($r) { $pubkey = $r[0]['xchan_pubkey']; } else { // 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); $pubkey = get_salmon_key($author_link, $keyhash); if (!$pubkey) { logger('mod-salmon: Could not retrieve author key.'); http_status_exit(400); } logger('mod-salmon: key details: ' . print_r($pubkey, true), LOGGER_DEBUG); } $pubkey = rtrim($pubkey); // We should have everything we need now. Let's see if it verifies. $verify = rsa_verify($signed_data, $signature, $pubkey); if (!$verify) { logger('mod-salmon: message did not verify using protocol. Trying padding hack.'); $verify = rsa_verify($compliant_format, $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.'); /* lookup the author */ if (!$datarray['author']['author_link']) { logger('unable to probe - no author identifier'); http_status_exit(400); } $r = q("select * from xchan where xchan_guid = '%s' limit 1", dbesc($datarray['author']['author_link'])); if (!$r) { if (discover_by_webbie($datarray['author']['author_link'])) { $r = q("select * from xchan where xchan_guid = '%s' limit 1", dbesc($datarray['author']['author_link'])); if (!$r) { logger('discovery failed'); http_status_exit(400); } } } $xchan = $r[0]; /* * * If we reached this point, the message is good. Now let's figure out if the author is allowed to send us stuff. * */ // First check for and process follow activity if (activity_match($item['verb'], ACTIVITY_FOLLOW) && $item['obj_type'] === ACTIVITY_OBJ_PERSON) { $cb = array('item' => $item, 'channel' => $importer, 'xchan' => $xchan, 'author' => $datarray['author'], 'caught' => false); call_hooks('follow_from_feed', $cb); if ($cb['caught']) { http_status_exit(200); } } $m = parse_url($xchan['xchan_url']); if ($m) { $host = $m['scheme'] . '://' . $m['host']; q("update site set site_dead = 0, site_update = '%s' where site_type = %d and site_url = '%s'", dbesc(datetime_convert()), intval(SITE_TYPE_NOTZOT), dbesc($url)); if (!check_siteallowed($host)) { logger('blacklisted site: ' . $host); http_status_exit(403, 'permission denied.'); } } $importer_arr = array($importer); if (!$sys_disabled) { $sys['system'] = true; $importer_arr[] = $sys; } unset($datarray['author']); // we will only set and return the status code for operations // on an importer channel and not for the sys channel $status = 200; foreach ($importer_arr as $importer) { if (!$importer['system']) { $allowed = get_pconfig($importer['channel_id'], 'system', 'gnusoc_allowed'); if (!intval($allowed)) { logger('mod-salmon: disallowed for channel ' . $importer['channel_name']); $status = 202; continue; } } // Otherwise check general permissions if (!perm_is_allowed($importer['channel_id'], $xchan['xchan_hash'], 'send_stream') && !$importer['system']) { // check for and process ostatus autofriend // ... fixme // otherwise logger('mod-salmon: Ignoring this author.'); $status = 202; continue; } $parent_item = null; if ($item['parent_mid']) { $r = q("select * from item where mid = '%s' and uid = %d limit 1", dbesc($item['parent_mid']), intval($importer['channel_id'])); if (!$r) { logger('mod-salmon: parent item not found.'); if (!$importer['system']) { $status = 202; } continue; } $parent_item = $r[0]; } if (!$item['author_xchan']) { $item['author_xchan'] = $xchan['xchan_hash']; } $item['owner_xchan'] = $parent_item ? $parent_item['owner_xchan'] : $xchan['xchan_hash']; $r = q("SELECT edited FROM item WHERE mid = '%s' AND uid = %d LIMIT 1", dbesc($item['mid']), intval($importer['channel_id'])); // Update content if 'updated' changes // currently a no-op @fixme if ($r) { if (x($item, 'edited') !== false && datetime_convert('UTC', 'UTC', $item['edited']) !== $r[0]['edited']) { // do not accept (ignore) an earlier edit than one we currently have. if (datetime_convert('UTC', 'UTC', $item['edited']) > $r[0]['edited']) { update_feed_item($importer['channel_id'], $item); } } if (!$importer['system']) { $status = 200; } continue; } if (!$item['parent_mid']) { $item['parent_mid'] = $item['mid']; } $item['aid'] = $importer['channel_account_id']; $item['uid'] = $importer['channel_id']; logger('consume_feed: ' . print_r($item, true), LOGGER_DATA); $xx = item_store($item); $r = $xx['item_id']; if (!$importer['system']) { $status = 200; } continue; } http_status_exit($status); }