function post() { $hash = $_POST['hash']; $time = $_POST['time']; $sig = $_POST['signature']; $resource = $_POST['resource']; $revision = intval($_POST['revision']); if (!$hash) { killme(); } $channel = channelx_by_hash($hash); if (!$channel || !$time || !$sig) { killme(); } $slop = intval(get_pconfig($channel['channel_id'], 'system', 'getfile_time_slop')); if ($slop < 1) { $slop = 3; } $d1 = datetime_convert('UTC', 'UTC', "now + {$slop} minutes"); $d2 = datetime_convert('UTC', 'UTC', "now - {$slop} minutes"); if ($time > $d1 || $time < $d2) { logger('time outside allowable range'); killme(); } if (!rsa_verify($hash . '.' . $time, base64url_decode($sig), $channel['channel_pubkey'])) { logger('verify failed.'); killme(); } $r = attach_by_hash($resource, $revision); if (!$r['success']) { notice($r['message'] . EOL); return; } $unsafe_types = array('text/html', 'text/css', 'application/javascript'); if (in_array($r['data']['filetype'], $unsafe_types)) { header('Content-type: text/plain'); } else { header('Content-type: ' . $r['data']['filetype']); } header('Content-disposition: attachment; filename="' . $r['data']['filename'] . '"'); if (intval($r['data']['os_storage'])) { $fname = dbunescbin($r['data']['data']); if (strpos($fname, 'store') !== false) { $istream = fopen($fname, 'rb'); } else { $istream = fopen('store/' . $channel['channel_address'] . '/' . $fname, 'rb'); } $ostream = fopen('php://output', 'wb'); if ($istream && $ostream) { pipe_streams($istream, $ostream); fclose($istream); fclose($ostream); } } else { echo dbunescbin($r['data']['data']); } killme(); }
function Verify($channel, $hubloc) { logger('auth request received from ' . $hubloc['hubloc_addr']); $this->remote = remote_channel(); $this->remote_service_class = ''; $this->remote_level = 0; $this->remote_hub = $hubloc['hubloc_url']; $this->dnt = 0; // check credentials and access // If they are already authenticated and haven't changed credentials, // we can save an expensive network round trip and improve performance. // Also check that they are coming from the same site as they authenticated with originally. $already_authed = remote_channel() && $hubloc['hubloc_hash'] == remote_channel() && $hubloc['hubloc_url'] === $_SESSION['remote_hub'] ? true : false; if ($this->delegate && $this->delegate !== $_SESSION['delegate_channel']) { $already_authed = false; } if ($already_authed) { return true; } if (local_channel()) { // tell them to logout if they're logged in locally as anything but the target remote account // in which case just shut up because they don't need to be doing this at all. if (\App::$channel['channel_hash'] == $hubloc['xchan_hash']) { return true; } else { logger('already authenticated locally as somebody else.'); notice(t('Remote authentication blocked. You are logged into this site locally. Please logout and retry.') . EOL); if ($this->test) { $this->Debug('already logged in locally with a conflicting identity.'); return false; } } return false; } // Auth packets MUST use ultra top-secret hush-hush mode - e.g. the entire packet is encrypted using the // site private key // The actual channel sending the packet ($c[0]) is not important, but this provides a // generic zot packet with a sender which can be verified $p = zot_build_packet($channel, $type = 'auth_check', array(array('guid' => $hubloc['hubloc_guid'], 'guid_sig' => $hubloc['hubloc_guid_sig'])), $hubloc['hubloc_sitekey'], $this->sec); $this->Debug('auth check packet created using sitekey ' . $hubloc['hubloc_sitekey']); $this->Debug('packet contents: ' . $p); $result = zot_zot($hubloc['hubloc_callback'], $p); if (!$result['success']) { logger('auth_check callback failed.'); if ($this->test) { $this->Debug('auth check request to your site returned .' . print_r($result, true)); } return false; } $j = json_decode($result['body'], true); if (!$j) { logger('auth_check json data malformed.'); if ($this->test) { $this->Debug('json malformed: ' . $result['body']); } return false; } $this->Debug('auth check request returned .' . print_r($j, true)); if (!$j['success']) { return false; } // legit response, but we do need to check that this wasn't answered by a man-in-middle if (!rsa_verify($this->sec . $hubloc['xchan_hash'], base64url_decode($j['confirm']), $hubloc['xchan_pubkey'])) { logger('final confirmation failed.'); if ($this->test) { $this->Debug('final confirmation failed. ' . $sec . print_r($j, true) . print_r($hubloc, true)); } return false; } if (array_key_exists('service_class', $j)) { $this->remote_service_class = $j['service_class']; } if (array_key_exists('level', $j)) { $this->remote_level = $j['level']; } if (array_key_exists('DNT', $j)) { $this->dnt = $j['DNT']; } // log them in if ($this->test) { // testing only - return the success result $this->test_results['success'] = true; $this->Debug('Authentication Success!'); $this->Finalise(); } $_SESSION['authenticated'] = 1; // check for delegation and if all is well, log them in locally with delegation restrictions $this->delegate_success = false; if ($this->delegate) { $r = q("select * from channel left join xchan on channel_hash = xchan_hash where xchan_addr = '%s' limit 1", dbesc($this->delegate)); if ($r && intval($r[0]['channel_id'])) { $allowed = perm_is_allowed($r[0]['channel_id'], $hubloc['xchan_hash'], 'delegate'); if ($allowed) { $_SESSION['delegate_channel'] = $r[0]['channel_id']; $_SESSION['delegate'] = $hubloc['xchan_hash']; $_SESSION['account_id'] = intval($r[0]['channel_account_id']); require_once 'include/security.php'; // this will set the local_channel authentication in the session change_channel($r[0]['channel_id']); $this->delegate_success = true; } } } if (!$this->delegate_success) { // normal visitor (remote_channel) login session credentials $_SESSION['visitor_id'] = $hubloc['xchan_hash']; $_SESSION['my_url'] = $hubloc['xchan_url']; $_SESSION['my_address'] = $this->address; $_SESSION['remote_service_class'] = $this->remote_service_class; $_SESSION['remote_level'] = $this->remote_level; $_SESSION['remote_hub'] = $this->remote_hub; $_SESSION['DNT'] = $this->dnt; } $arr = array('xchan' => $hubloc, 'url' => $this->desturl, 'session' => $_SESSION); call_hooks('magic_auth_success', $arr); \App::set_observer($hubloc); require_once 'include/security.php'; \App::set_groups(init_groups_visitor($_SESSION['visitor_id'])); info(sprintf(t('Welcome %s. Remote authentication successful.'), $hubloc['xchan_name'])); logger('mod_zot: auth success from ' . $hubloc['xchan_addr']); $this->success = true; return true; }
include_once "log.php"; include_once "recharge.php"; include_once "ssl.php"; $config = (include "config.php"); header("Content-type: text/html; charset=utf-8"); log::init('./log', 'itools_log'); $uri = $_SERVER['REQUEST_URI']; $body = file_get_contents('php://input'); log::instance()->debug("new con: {$uri} {$body}"); $AppID = "533"; $pf_info = $config["itools"][$AppID]; // RSA verify $notify_data = base64_decode(stripslashes($_POST["notify_data"])); $notify_data = publickey_decodeing_sectionalized($notify_data, 128, $pf_info["PubKey"]); $sign = base64_decode(stripslashes($_POST["sign"])); if (!rsa_verify($notify_data, $sign, $pf_info["PubKey"])) { log::instance()->error("ret: 签名无效"); echo "fail"; exit; } // end verify echo "success"; $notify_data = json_decode($notify_data, true); if ($notify_data["result"] != "success") { log::instance()->error("ret: 支付失败"); exit; } $note = json_decode(base64_decode($notify_data["order_id_com"]), true); $ret = recharge($pf_info["PF"], $note["sid"], $note["odr"], $note["uid"], $note["item"], $notify_data["amount"], $notify_data["order_id"], 0); log::instance()->debug("ret: " . $ret);
private function getSign($type, $data) { $md5str = ""; switch ($type) { case "gfb": $signarray = array("version", "tranCode", "merchantID", "merOrderNum", "tranAmt", "feeAmt", "tranDateTime", "frontMerUrl", "backgroundMerUrl", "orderId", "gopayOutOrderId", "tranIP", "respCode", "gopayServerTime"); foreach ($signarray as $v) { if (!isset($data[$v])) { $md5str .= "{$v}=[]"; } else { $md5str .= "{$v}=[{$data[$v]}]"; } } $md5str .= "VerficationCode=[" . $this->payConfig['guofubao']['VerficationCode'] . "]"; $md5str = md5($md5str); return $md5str; break; case "ips": $md5str = "billno" . $data['Billno'] . "currencytype" . $data['Currency_Type'] . "amount" . $data['Amount'] . "date" . $data['Date'] . "orderencodetype" . $data['OrderEncodeType']; $md5str .= $this->payConfig['ips']['MerKey']; $md5str = md5($md5str); return $md5str; break; case "ips_return": $md5str = "billno" . $data['billno'] . "currencytype" . $data['Currency_type'] . "amount" . $data['amount'] . "date" . $data['date'] . "succ" . $data['succ'] . "ipsbillno" . $data['ipsbillno'] . "retencodetype" . $data['retencodetype']; $md5str .= $this->payConfig['ips']['MerKey']; $md5str = md5($md5str); return $md5str; break; case "chinabank": $signarray = array("v_amount", "v_moneytype", "v_oid", "v_mid", "v_url"); foreach ($signarray as $v) { if (!isset($data[$v])) { $md5str .= ""; } else { $md5str .= "{$data[$v]}"; } } $md5str .= $this->payConfig['chinabank']['mkey']; $md5str = md5($md5str); return $md5str; break; case "chinabank_return": $signarray = array("v_oid", "v_pstatus", "v_amount", "v_moneytype"); foreach ($signarray as $v) { if (!isset($data[$v])) { $md5str .= ""; } else { $md5str .= "{$data[$v]}"; } } $md5str .= $this->payConfig['chinabank']['mkey']; $md5str = md5($md5str); return $md5str; break; /*case "baofoo"://老宝付支付接口 $signarray = array( "MerchantID", "PayID", "TradeDate", "TransID", "OrderMoney", "Merchant_url", "Return_url", "NoticeType" ); foreach ( $signarray as $v ) { $md5str .= $data[$v]; } $md5str .= $this->payConfig['baofoo']['pkey']; $md5str = md5( $md5str ); return $md5str; break; case "baofoo_return": $signarray = array( "MerchantID", "TransID", "Result", "resultDesc", "factMoney", "additionalInfo", "SuccTime" ); foreach ( $signarray as $v ) { $md5str .= $data[$v]; } $md5str .= $this->payConfig['baofoo']['pkey']; $md5str = md5( $md5str ); return $md5str; break;*/ /*case "baofoo"://老宝付支付接口 $signarray = array( "MerchantID", "PayID", "TradeDate", "TransID", "OrderMoney", "Merchant_url", "Return_url", "NoticeType" ); foreach ( $signarray as $v ) { $md5str .= $data[$v]; } $md5str .= $this->payConfig['baofoo']['pkey']; $md5str = md5( $md5str ); return $md5str; break; case "baofoo_return": $signarray = array( "MerchantID", "TransID", "Result", "resultDesc", "factMoney", "additionalInfo", "SuccTime" ); foreach ( $signarray as $v ) { $md5str .= $data[$v]; } $md5str .= $this->payConfig['baofoo']['pkey']; $md5str = md5( $md5str ); return $md5str; break;*/ case "baofoo": $signarray = array("MemberID", "PayID", "TradeDate", "TransID", "OrderMoney", "PageUrl", "ReturnUrl", "NoticeType"); foreach ($signarray as $v) { $md5str .= $data[$v] . '|'; } $md5str .= $this->payConfig['baofoo']['pkey']; $md5str = md5($md5str); return $md5str; break; case "baofoo_return": $signarray = array("MemberID", "TerminalID", "TransID", "Result", "ResultDesc", "FactMoney", "AdditionalInfo", 'SuccTime'); foreach ($signarray as $v) { $md5str .= "{$v}" . '=' . $data[$v] . '~|~'; } //dump($md5str); $md5str .= 'Md5Sign=' . $this->payConfig['baofoo']['pkey']; $md5str = md5($md5str); return $md5str; break; case "shengpay": $signarray = array('Name', 'Version', 'Charset', 'MsgSender', 'SendTime', 'OrderNo', 'OrderAmount', 'OrderTime', 'PayType', 'PageUrl', 'NotifyUrl', 'ProductName', 'BuyerContact', 'BuyerIp', 'Ext1', 'Ext2', 'SignType'); foreach ($signarray as $v) { if (!isset($data[$v])) { $md5str .= ""; } else { $md5str .= "{$data[$v]}"; } } $md5str .= $this->payConfig['shengpay']['pkey']; //MD5密钥 $md5str = strtoupper(md5($md5str)); return $md5str; break; case "shengpay_return": $signarray = array('Name', 'Version', 'Charset', 'TraceNo', 'MsgSender', 'SendTime', 'InstCode', 'OrderNo', 'OrderAmount', 'TransNo', 'TransAmount', 'TransStatus', 'TransType', 'TransTime', 'MerchantNo', 'ErrorCode', 'ErrorMsg', 'Ext1', 'Ext2', 'SignType'); foreach ($signarray as $v) { if (!isset($data[$v])) { $md5str .= ""; } else { $md5str .= "{$data[$v]}"; } } $md5str .= $this->payConfig['shengpay']['mkey']; $md5str = strtoupper(md5($md5str)); return $md5str; break; case "tenpay": $signPars = ""; ksort($data); foreach ($data as $k => $v) { if ("" != $v && "sign" != $k) { $signPars .= $k . "=" . $v . "&"; } } $signPars .= "key=" . $this->payConfig['tenpay']['key']; $md5str = strtoupper(md5($signPars)); return $md5str; break; case "ecpss": $signarray = array('MerNo', 'BillNo', 'Amount', 'ReturnURL'); //校验源字符串 foreach ($signarray as $v) { if (!isset($data[$v])) { $md5str .= ""; } else { $md5str .= $data[$v]; } } $md5str .= $this->payConfig['ecpss']['MD5key']; //MD5密钥 $md5str = strtoupper(md5($md5str)); return $md5str; break; case "ecpss_return": $signarray = array("BillNo", "Amount", "Succeed"); //校验源字符串 foreach ($signarray as $v) { $md5str .= $data[$v] . "&"; } $md5str .= $this->payConfig['ecpss']['MD5key']; $md5str = strtoupper(md5($md5str)); return $md5str; break; case "easypay": //易生支付 $para = array(); while (list($key, $val) = each($data)) { if ($key == "sign" || $key == "sign_type" || $val == "") { continue; } else { $para[$key] = $data[$key]; } } ksort($para); reset($para); $signPars = ""; while (list($key, $val) = each($para)) { $signPars .= $key . "=" . $val . "&"; } $signPars = substr($signPars, 0, count($signPars) - 2); //去掉最后一个&字符 $signPars .= $this->payConfig['easypay']['key']; $md5str = md5($signPars); return $md5str; break; case "cmpay": //中国移动 $signarray = array('merchantId', 'payNo', 'returnCode', 'message', 'signType', 'type', 'version', 'amount', 'amtItem', 'bankAbbr', 'mobile', 'orderId', 'payDate', 'accountDate', 'reserved1', 'reserved2', 'status', 'orderDate', 'fee'); foreach ($signarray as $v) { $mac .= $data[$v]; } $signKey = $this->payConfig['cmpay']['serverCert']; $mac = MD5sign($signKey, $mac); return $mac; break; case "cmpay_return": //中国移动 foreach ($data as $v) { $mac .= $v; } $signKey = $this->payConfig['cmpay']['serverCert']; //MD5方式签名 $hmac = MD5sign($signKey, $mac); return $hmac; break; case "allinpay": $signarray = array("inputCharset", "pickupUrl", "receiveUrl", "version", "language", "signType", "merchantId", "payerName", "payerEmail", "payerTelephone", "payerIDCard", "pid", "orderNo", "orderAmount", "orderCurrency", "orderDatetime", "orderExpireDatetime", "productName", "productPrice", "productNum", "productId", "productDescription", "ext1", "ext2", "payType", "issuerId", "pan"); $i = 0; foreach ($signarray as $v) { if (0 < $i) { if ($data[$v] !== "") { $md5str .= "&{$v}=" . $data[$v]; } } else { if ($data[$v] !== "") { $md5str .= "{$v}=" . $data[$v]; } } ++$i; } $md5str .= "&key=" . $this->payConfig['allinpay']['key']; $md5str = strtoupper(md5($md5str)); return $md5str; case "allinpay_return": $signarray = array("merchantId", "version", "language", "signType", "payType", "issuerId", "paymentOrderId", "orderNo", "orderDatetime", "orderAmount", "payDatetime", "payAmount", "ext1", "ext2", "payResult", "errorCode", "returnDatetime"); $i = 0; foreach ($signarray as $v) { if (0 < $i) { if ($data[$v] !== "") { $md5str .= "&{$v}=" . $data[$v]; } } else { if ($data[$v] !== "") { $md5str .= "{$v}=" . $data[$v]; } } ++$i; } //解析publickey.txt文本获取公钥信息 require_once C("APP_ROOT") . "Lib/Pay/allinpay/php_rsa.php"; $publickeyfile = C("APP_ROOT") . "Lib/Pay/allinpay/publickey.txt"; $publickeycontent = file_get_contents($publickeyfile); //echo "<br>".$content; $publickeyarray = explode(PHP_EOL, $publickeycontent); $publickey = explode('=', $publickeyarray[0]); $modulus = explode('=', $publickeyarray[1]); $keylength = 1024; $verify_result = rsa_verify($md5str, $data['signMsg'], $publickey[1], $modulus[1], $keylength, "sha1"); return $verify_result; } }
function check_zotinfo($channel, $locations, &$ret) { // logger('locations: ' . print_r($locations,true),LOGGER_DATA); // This function will likely expand as we find more things to detect and fix. // 1. Because magic-auth is reliant on it, ensure that the system channel has a valid hubloc // Force this to be the case if anything is found to be wrong with it. // @FIXME ensure that the system channel exists in the first place and has an xchan if ($channel['channel_system']) { // the sys channel must have a location (hubloc) $valid_location = false; if (count($locations) === 1 && $locations[0]['primary'] && !$locations[0]['deleted']) { if (rsa_verify($locations[0]['url'], base64url_decode($locations[0]['url_sig']), $channel['channel_pubkey']) && $locations[0]['sitekey'] === get_config('system', 'pubkey') && $locations[0]['url'] === z_root()) { $valid_location = true; } else { logger('sys channel: invalid url signature'); } } if (!$locations || !$valid_location) { logger('System channel locations are not valid. Attempting repair.'); // Don't trust any existing records. Just get rid of them, but only do this // for the sys channel as normal channels will be trickier. q("delete from hubloc where hubloc_hash = '%s'", dbesc($channel['channel_hash'])); $r = q("insert into hubloc ( hubloc_guid, hubloc_guid_sig, hubloc_hash, hubloc_addr, hubloc_primary,\n\t\t\t\thubloc_url, hubloc_url_sig, hubloc_host, hubloc_callback, hubloc_sitekey, hubloc_network )\n\t\t\t\tvalues ( '%s', '%s', '%s', '%s', %d, '%s', '%s', '%s', '%s', '%s', '%s' )", dbesc($channel['channel_guid']), dbesc($channel['channel_guid_sig']), dbesc($channel['channel_hash']), dbesc($channel['channel_address'] . '@' . get_app()->get_hostname()), intval(1), dbesc(z_root()), dbesc(base64url_encode(rsa_sign(z_root(), $channel['channel_prvkey']))), dbesc(get_app()->get_hostname()), dbesc(z_root() . '/post'), dbesc(get_config('system', 'pubkey')), dbesc('zot')); if ($r) { $x = zot_encode_locations($channel); if ($x) { $ret['locations'] = $x; } } else { logger('Unable to store sys hub location'); } } } }
function zfinger_init(&$a) { require_once 'include/zot.php'; require_once 'include/crypto.php'; $ret = array('success' => false); $zhash = x($_REQUEST, 'guid_hash') ? $_REQUEST['guid_hash'] : ''; $zguid = x($_REQUEST, 'guid') ? $_REQUEST['guid'] : ''; $zguid_sig = x($_REQUEST, 'guid_sig') ? $_REQUEST['guid_sig'] : ''; $zaddr = x($_REQUEST, 'address') ? $_REQUEST['address'] : ''; $ztarget = x($_REQUEST, 'target') ? $_REQUEST['target'] : ''; $zsig = x($_REQUEST, 'target_sig') ? $_REQUEST['target_sig'] : ''; $zkey = x($_REQUEST, 'key') ? $_REQUEST['key'] : ''; $mindate = x($_REQUEST, 'mindate') ? $_REQUEST['mindate'] : ''; $feed = x($_REQUEST, 'feed') ? intval($_REQUEST['feed']) : 0; if ($ztarget) { if (!$zkey || !$zsig || !rsa_verify($ztarget, base64url_decode($zsig), $zkey)) { logger('zfinger: invalid target signature'); $ret['message'] = t("invalid target signature"); json_return_and_die($ret); } } // allow re-written domains so bob@foo.example.com can provide an address of bob@example.com // The top-level domain also needs to redirect .well-known/zot-info to the sub-domain with a 301 or 308 // TODO: Make 308 work in include/network.php for zot_fetch_url and zot_post_url if ($zaddr && ($s = get_config('system', 'zotinfo_domainrewrite'))) { $arr = explode('^', $s); if (count($arr) == 2) { $zaddr = str_replace($arr[0], $arr[1], $zaddr); } } $r = null; if (strlen($zhash)) { $r = q("select channel.*, xchan.* from channel left join xchan on channel_hash = xchan_hash \n\t\t\twhere channel_hash = '%s' limit 1", dbesc($zhash)); } elseif (strlen($zguid) && strlen($zguid_sig)) { $r = q("select channel.*, xchan.* from channel left join xchan on channel_hash = xchan_hash \n\t\t\twhere channel_guid = '%s' and channel_guid_sig = '%s' limit 1", dbesc($zguid), dbesc($zguid_sig)); } elseif (strlen($zaddr)) { if (strpos($zaddr, '[system]') === false) { /* normal address lookup */ $r = q("select channel.*, xchan.* from channel left join xchan on channel_hash = xchan_hash\n\t\t\t\twhere ( channel_address = '%s' or xchan_addr = '%s' ) limit 1", dbesc($zaddr), dbesc($zaddr)); } else { /** * The special address '[system]' will return a system channel if one has been defined, * Or the first valid channel we find if there are no system channels. * * This is used by magic-auth if we have no prior communications with this site - and * returns an identity on this site which we can use to create a valid hub record so that * we can exchange signed messages. The precise identity is irrelevant. It's the hub * information that we really need at the other end - and this will return it. * */ $r = q("select channel.*, xchan.* from channel left join xchan on channel_hash = xchan_hash\n\t\t\t\twhere ( channel_pageflags & %d )>0 order by channel_id limit 1", intval(PAGE_SYSTEM)); if (!$r) { $r = q("select channel.*, xchan.* from channel left join xchan on channel_hash = xchan_hash\n\t\t\t\t\twhere not ( channel_pageflags & %d )>0 order by channel_id limit 1", intval(PAGE_REMOVED)); } } } else { $ret['message'] = 'Invalid request'; json_return_and_die($ret); } if (!$r) { $ret['message'] = 'Item not found.'; json_return_and_die($ret); } $e = $r[0]; $id = $e['channel_id']; $sys_channel = $e['channel_pageflags'] & PAGE_SYSTEM ? true : false; $special_channel = $e['channel_pageflags'] & PAGE_PREMIUM ? true : false; $adult_channel = $e['channel_pageflags'] & PAGE_ADULT ? true : false; $censored = $e['channel_pageflags'] & PAGE_CENSORED ? true : false; $searchable = $e['channel_pageflags'] & PAGE_HIDDEN ? false : true; $deleted = $e['xchan_flags'] & XCHAN_FLAGS_DELETED ? true : false; if ($deleted || $censored || $sys_channel) { $searchable = false; } $public_forum = false; $role = get_pconfig($e['channel_id'], 'system', 'permissions_role'); if ($role === 'forum') { $public_forum = true; } else { // check if it has characteristics of a public forum based on custom permissions. $t = q("select abook_my_perms from abook where abook_channel = %d and (abook_flags & %d)>0 limit 1", intval($e['channel_id']), intval(ABOOK_FLAG_SELF)); if ($t && $t[0]['abook_my_perms'] & PERMS_W_TAGWALL) { $public_forum = true; } } // This is for birthdays and keywords, but must check access permissions $p = q("select * from profile where uid = %d and is_default = 1", intval($e['channel_id'])); $profile = array(); if ($p) { if (!intval($p[0]['publish'])) { $searchable = false; } $profile['description'] = $p[0]['pdesc']; $profile['birthday'] = $p[0]['dob']; if ($profile['birthday'] != '0000-00-00' && ($bd = z_birthday($p[0]['dob'], $e['channel_timezone'])) !== '') { $profile['next_birthday'] = $bd; } if ($age = age($p[0]['dob'], $e['channel_timezone'], '')) { $profile['age'] = $age; } $profile['gender'] = $p[0]['gender']; $profile['marital'] = $p[0]['marital']; $profile['sexual'] = $p[0]['sexual']; $profile['locale'] = $p[0]['locality']; $profile['region'] = $p[0]['region']; $profile['postcode'] = $p[0]['postal_code']; $profile['country'] = $p[0]['country_name']; $profile['about'] = $p[0]['about']; $profile['homepage'] = $p[0]['homepage']; $profile['hometown'] = $p[0]['hometown']; if ($p[0]['keywords']) { $tags = array(); $k = explode(' ', $p[0]['keywords']); if ($k) { foreach ($k as $kk) { if (trim($kk, " \t\n\r\v,")) { $tags[] = trim($kk, " \t\n\r\v,"); } } } if ($tags) { $profile['keywords'] = $tags; } } } $ret['success'] = true; // Communication details $ret['guid'] = $e['xchan_guid']; $ret['guid_sig'] = $e['xchan_guid_sig']; $ret['key'] = $e['xchan_pubkey']; $ret['name'] = $e['xchan_name']; $ret['name_updated'] = $e['xchan_name_date']; $ret['address'] = $e['xchan_addr']; $ret['photo_mimetype'] = $e['xchan_photo_mimetype']; $ret['photo'] = $e['xchan_photo_l']; $ret['photo_updated'] = $e['xchan_photo_date']; $ret['url'] = $e['xchan_url']; $ret['connections_url'] = $e['xchan_connurl'] ? $e['xchan_connurl'] : z_root() . '/poco/' . $e['channel_address']; $ret['target'] = $ztarget; $ret['target_sig'] = $zsig; $ret['searchable'] = $searchable; $ret['adult_content'] = $adult_channel; $ret['public_forum'] = $public_forum; if ($deleted) { $ret['deleted'] = $deleted; } // premium or other channel desiring some contact with potential followers before connecting. // This is a template - %s will be replaced with the follow_url we discover for the return channel. if ($special_channel) { $ret['connect_url'] = z_root() . '/connect/' . $e['channel_address']; } // This is a template for our follow url, %s will be replaced with a webbie $ret['follow_url'] = z_root() . '/follow?f=&url=%s'; $ztarget_hash = $ztarget && $zsig ? make_xchan_hash($ztarget, $zsig) : ''; $permissions = get_all_perms($e['channel_id'], $ztarget_hash, false); if ($ztarget_hash) { $permissions['connected'] = false; $b = q("select * from abook where abook_xchan = '%s' and abook_channel = %d limit 1", dbesc($ztarget_hash), intval($e['channel_id'])); if ($b) { $permissions['connected'] = true; } } $ret['permissions'] = $ztarget && $zkey ? crypto_encapsulate(json_encode($permissions), $zkey) : $permissions; if ($permissions['view_profile']) { $ret['profile'] = $profile; } // array of (verified) hubs this channel uses $x = zot_encode_locations($e); if ($x) { $ret['locations'] = $x; } $ret['site'] = array(); $ret['site']['url'] = z_root(); $ret['site']['url_sig'] = base64url_encode(rsa_sign(z_root(), $e['channel_prvkey'])); $dirmode = get_config('system', 'directory_mode'); if ($dirmode === false || $dirmode == DIRECTORY_MODE_NORMAL) { $ret['site']['directory_mode'] = 'normal'; } if ($dirmode == DIRECTORY_MODE_PRIMARY) { $ret['site']['directory_mode'] = 'primary'; } elseif ($dirmode == DIRECTORY_MODE_SECONDARY) { $ret['site']['directory_mode'] = 'secondary'; } elseif ($dirmode == DIRECTORY_MODE_STANDALONE) { $ret['site']['directory_mode'] = 'standalone'; } if ($dirmode != DIRECTORY_MODE_NORMAL) { $ret['site']['directory_url'] = z_root() . '/dirsearch'; } // hide detailed site information if you're off the grid if ($dirmode != DIRECTORY_MODE_STANDALONE) { $register_policy = intval(get_config('system', 'register_policy')); if ($register_policy == REGISTER_CLOSED) { $ret['site']['register_policy'] = 'closed'; } if ($register_policy == REGISTER_APPROVE) { $ret['site']['register_policy'] = 'approve'; } if ($register_policy == REGISTER_OPEN) { $ret['site']['register_policy'] = 'open'; } $access_policy = intval(get_config('system', 'access_policy')); if ($access_policy == ACCESS_PRIVATE) { $ret['site']['access_policy'] = 'private'; } if ($access_policy == ACCESS_PAID) { $ret['site']['access_policy'] = 'paid'; } if ($access_policy == ACCESS_FREE) { $ret['site']['access_policy'] = 'free'; } if ($access_policy == ACCESS_TIERED) { $ret['site']['access_policy'] = 'tiered'; } $ret['site']['accounts'] = account_total(); require_once 'include/identity.php'; $ret['site']['channels'] = channel_total(); $ret['site']['version'] = PLATFORM_NAME . ' ' . RED_VERSION . '[' . DB_UPDATE_VERSION . ']'; $ret['site']['admin'] = get_config('system', 'admin_email'); $visible_plugins = array(); if (is_array($a->plugins) && count($a->plugins)) { $r = q("select * from addon where hidden = 0"); if ($r) { foreach ($r as $rr) { $visible_plugins[] = $rr['name']; } } } $ret['site']['plugins'] = $visible_plugins; $ret['site']['sitehash'] = get_config('system', 'location_hash'); $ret['site']['sitename'] = get_config('system', 'sitename'); $ret['site']['sellpage'] = get_config('system', 'sellpage'); $ret['site']['location'] = get_config('system', 'site_location'); $ret['site']['realm'] = get_directory_realm(); } call_hooks('zot_finger', $ret); json_return_and_die($ret); }
/** * @brief Checks the directory mode of this hub. * * Checks the directory mode of this hub to see if it is some form of directory server. If it is, * get the directory realm of this hub. Fetch a list of all other directory servers in this realm and request * a directory sync packet. This will contain both directory updates and new ratings. Store these all in the DB. * In the case of updates, we will query each of them asynchronously from a poller task. Ratings are stored * directly if the rater's signature matches. * * @param int $dirmode; */ function sync_directories($dirmode) { if ($dirmode == DIRECTORY_MODE_STANDALONE || $dirmode == DIRECTORY_MODE_NORMAL) { return; } $realm = get_directory_realm(); if ($realm == DIRECTORY_REALM) { $r = q("select * from site where (site_flags & %d) > 0 and site_url != '%s' and site_type = %d and ( site_realm = '%s' or site_realm = '') ", intval(DIRECTORY_MODE_PRIMARY | DIRECTORY_MODE_SECONDARY), dbesc(z_root()), intval(SITE_TYPE_ZOT), dbesc($realm)); } else { $r = q("select * from site where (site_flags & %d) > 0 and site_url != '%s' and site_realm like '%s' and site_type = %d ", intval(DIRECTORY_MODE_PRIMARY | DIRECTORY_MODE_SECONDARY), dbesc(z_root()), dbesc(protect_sprintf('%' . $realm . '%')), intval(SITE_TYPE_ZOT)); } // If there are no directory servers, setup the fallback master /** @FIXME What to do if we're in a different realm? */ if (!$r && z_root() != DIRECTORY_FALLBACK_MASTER) { $r = array(); $r[] = array('site_url' => DIRECTORY_FALLBACK_MASTER, 'site_flags' => DIRECTORY_MODE_PRIMARY, 'site_update' => NULL_DATE, 'site_directory' => DIRECTORY_FALLBACK_MASTER . '/dirsearch', 'site_realm' => DIRECTORY_REALM, 'site_valid' => 1); $x = q("insert into site ( site_url, site_flags, site_update, site_directory, site_realm, site_valid )\n\t\t\tvalues ( '%s', %d, '%s', '%s', '%s', %d ) ", dbesc($r[0]['site_url']), intval($r[0]['site_flags']), dbesc($r[0]['site_update']), dbesc($r[0]['site_directory']), dbesc($r[0]['site_realm']), intval($r[0]['site_valid'])); $r = q("select * from site where site_flags in (%d, %d) and site_url != '%s' and site_type = %d ", intval(DIRECTORY_MODE_PRIMARY), intval(DIRECTORY_MODE_SECONDARY), dbesc(z_root()), intval(SITE_TYPE_ZOT)); } if (!$r) { return; } foreach ($r as $rr) { if (!$rr['site_directory']) { continue; } logger('sync directories: ' . $rr['site_directory']); // for brand new directory servers, only load the last couple of days. // It will take about a month for a new directory to obtain the full current repertoire of channels. /** @FIXME Go back and pick up earlier ratings if this is a new directory server. These do not get refreshed. */ $token = get_config('system', 'realm_token'); $syncdate = $rr['site_sync'] === NULL_DATE ? datetime_convert('UTC', 'UTC', 'now - 2 days') : $rr['site_sync']; $x = z_fetch_url($rr['site_directory'] . '?f=&sync=' . urlencode($syncdate) . ($token ? '&t=' . $token : '')); if (!$x['success']) { continue; } $j = json_decode($x['body'], true); if (!$j['transactions'] || $j['ratings']) { continue; } q("update site set site_sync = '%s' where site_url = '%s'", dbesc(datetime_convert()), dbesc($rr['site_url'])); logger('sync_directories: ' . $rr['site_url'] . ': ' . print_r($j, true), LOGGER_DATA); if (is_array($j['transactions']) && count($j['transactions'])) { foreach ($j['transactions'] as $t) { $r = q("select * from updates where ud_guid = '%s' limit 1", dbesc($t['transaction_id'])); if ($r) { continue; } $ud_flags = 0; if (is_array($t['flags']) && in_array('deleted', $t['flags'])) { $ud_flags |= UPDATE_FLAGS_DELETED; } if (is_array($t['flags']) && in_array('forced', $t['flags'])) { $ud_flags |= UPDATE_FLAGS_FORCED; } $z = q("insert into updates ( ud_hash, ud_guid, ud_date, ud_flags, ud_addr )\n\t\t\t\t\tvalues ( '%s', '%s', '%s', %d, '%s' ) ", dbesc($t['hash']), dbesc($t['transaction_id']), dbesc($t['timestamp']), intval($ud_flags), dbesc($t['address'])); } } if (is_array($j['ratings']) && count($j['ratings'])) { foreach ($j['ratings'] as $rr) { $x = q("select * from xlink where xlink_xchan = '%s' and xlink_link = '%s' and xlink_static = 1", dbesc($rr['channel']), dbesc($rr['target'])); if ($x && $x[0]['xlink_updated'] >= $rr['edited']) { continue; } // Ratings are signed by the rater. We need to verify before we can accept it. /** @TODO Queue or defer if the xchan is not yet present on our site */ $y = q("select xchan_pubkey from xchan where xchan_hash = '%s' limit 1", dbesc($rr['channel'])); if (!$y) { logger('key unavailable on this site for ' . $rr['channel']); continue; } if (!rsa_verify($rr['target'] . '.' . $rr['rating'] . '.' . $rr['rating_text'], base64url_decode($rr['signature']), $y[0]['xchan_pubkey'])) { logger('failed to verify rating'); continue; } if ($x) { $z = q("update xlink set xlink_rating = %d, xlink_rating_text = '%s', xlink_sig = '%s', xlink_updated = '%s' where xlink_id = %d", intval($rr['rating']), dbesc($rr['rating_text']), dbesc($rr['signature']), dbesc(datetime_convert()), intval($x[0]['xlink_id'])); logger('rating updated'); } else { $z = q("insert into xlink ( xlink_xchan, xlink_link, xlink_rating, xlink_rating_text, xlink_sig, xlink_updated, xlink_static ) values( '%s', '%s', %d, '%s', '%s', '%s', 1 ) ", dbesc($rr['channel']), dbesc($rr['target']), intval($rr['rating']), dbesc($rr['rating_text']), dbesc($rr['signature']), dbesc(datetime_convert())); logger('rating created'); } } } } }
function diaspora_verify_fields($fields, $sig, $pubkey) { if (!$fields) { return false; } $n = array(); foreach ($fields as $k => $v) { if ($k !== 'author_signature' && $k !== 'parent_author_signature') { $n[$k] = $v; } } $s = implode($n, ';'); logger('signing_string: ' . $s); return rsa_verify($s, base64_decode($sig), $pubkey); }
function diaspora_signed_retraction($importer, $xml, $msg) { // obsolete - see https://github.com/SuperTux88/diaspora_federation/issues/27 $guid = notags(diaspora_get_target_guid($xml)); $diaspora_handle = notags(diaspora_get_author($xml)); $type = notags(diaspora_get_type($xml)); $sig = notags(unxmlify($xml['target_author_signature'])); $parent_author_signature = $xml['parent_author_signature'] ? notags(unxmlify($xml['parent_author_signature'])) : ''; $contact = diaspora_get_contact_by_handle($importer['channel_id'], $diaspora_handle); if (!$contact) { logger('diaspora_signed_retraction: no contact ' . $diaspora_handle . ' for ' . $importer['channel_id']); return; } $signed_data = $guid . ';' . $type; $key = $msg['key']; /* How Diaspora performs relayable_retraction signature checking: - If an item has been sent by the item author to the top-level post owner to relay on to the rest of the contacts on the top-level post, the top-level post owner checks the author_signature, then creates a parent_author_signature before relaying the item on - If an item has been relayed on by the top-level post owner, the contacts who receive it check only the parent_author_signature. Basically, they trust that the top-level post owner has already verified the authenticity of anything he/she sends out - In either case, the signature that get checked is the signature created by the person who sent the salmon */ if ($parent_author_signature) { $parent_author_signature = base64_decode($parent_author_signature); if (!rsa_verify($signed_data, $parent_author_signature, $key, 'sha256')) { logger('diaspora_signed_retraction: top-level post owner verification failed'); return; } } else { $sig_decode = base64_decode($sig); if (!rsa_verify($signed_data, $sig_decode, $key, 'sha256')) { logger('diaspora_signed_retraction: retraction owner verification failed.' . print_r($msg, true)); return; } } if ($type === 'StatusMessage' || $type === 'Comment' || $type === 'Like') { $r = q("select * from item where mid = '%s' and uid = %d limit 1", dbesc($guid), intval($importer['channel_id'])); if ($r) { if ($r[0]['author_xchan'] == $contact['xchan_hash']) { drop_item($r[0]['id'], false, DROPITEM_PHASE1); // Now check if the retraction needs to be relayed by us // // The first item in the `item` table with the parent id is the parent. However, MySQL doesn't always // return the items ordered by `item`.`id`, in which case the wrong item is chosen as the parent. // The only item with `parent` and `id` as the parent id is the parent item. $p = q("select item_flags from item where parent = %d and id = %d limit 1", $r[0]['parent'], $r[0]['parent']); if ($p) { if (intval($p[0]['item_origin']) && !$parent_author_signature) { // the existence of parent_author_signature would have meant the parent_author or owner // is already relaying. logger('diaspora_signed_retraction: relaying relayable_retraction'); Zotlabs\Daemon\Master::Summon(array('Notifier', 'drop', $r[0]['id'])); } } } } } else { logger('diaspora_signed_retraction: unknown type: ' . $type); } return 202; // NOTREACHED }
/** * @function post_post(&$a) * zot communications and messaging * * Sender HTTP posts to this endpoint ($site/post typically) with 'data' parameter set to json zot message packet. * This packet is optionally encrypted, which we will discover if the json has an 'iv' element. * $contents => array( 'alg' => 'aes256cbc', 'iv' => initialisation vector, 'key' => decryption key, 'data' => encrypted data); * $contents->iv and $contents->key are random strings encrypted with this site's RSA public key and then base64url encoded. * Currently only 'aes256cbc' is used, but this is extensible should that algorithm prove inadequate. * * Once decrypted, one will find the normal json_encoded zot message packet. * * Defined packet types are: notify, purge, refresh, force_refresh, auth_check, ping, and pickup * * Standard packet: (used by notify, purge, refresh, force_refresh, and auth_check) * * { * "type": "notify", * "sender":{ * "guid":"kgVFf_1...", * "guid_sig":"PT9-TApzp...", * "url":"http:\/\/podunk.edu", * "url_sig":"T8Bp7j5...", * }, * "recipients": { optional recipient array }, * "callback":"\/post", * "version":1, * "secret":"1eaa...", * "secret_sig": "df89025470fac8..." * } * * Signature fields are all signed with the sender channel private key and base64url encoded. * Recipients are arrays of guid and guid_sig, which were previously signed with the recipients private * key and base64url encoded and later obtained via channel discovery. Absence of recipients indicates * a public message or visible to all potential listeners on this site. * * "pickup" packet: * The pickup packet is sent in response to a notify packet from another site * * { * "type":"pickup", * "url":"http:\/\/example.com", * "callback":"http:\/\/example.com\/post", * "callback_sig":"teE1_fLI...", * "secret":"1eaa...", * "secret_sig":"O7nB4_..." * } * * In the pickup packet, the sig fields correspond to the respective data element signed with this site's system * private key and then base64url encoded. * The "secret" is the same as the original secret from the notify packet. * * If verification is successful, a json structure is returned * containing a success indicator and an array of type 'pickup'. * Each pickup element contains the original notify request and a message field whose contents are * dependent on the message type * * This JSON array is AES encapsulated using the site public key of the site that sent the initial zot pickup packet. * Using the above example, this would be example.com. * * * { * "success":1, * "pickup":{ * "notify":{ * "type":"notify", * "sender":{ * "guid":"kgVFf_...", * "guid_sig":"PT9-TApz...", * "url":"http:\/\/z.podunk.edu", * "url_sig":"T8Bp7j5D..." * }, * "callback":"\/post", * "version":1, * "secret":"1eaa661..." * }, * "message":{ * "type":"activity", * "message_id":"*****@*****.**", * "message_top":"*****@*****.**", * "message_parent":"*****@*****.**", * "created":"2012-11-20 04:04:16", * "edited":"2012-11-20 04:04:16", * "title":"", * "body":"Hi Nickordo", * "app":"", * "verb":"post", * "object_type":"", * "target_type":"", * "permalink":"", * "location":"", * "longlat":"", * "owner":{ * "name":"Indigo", * "address":"*****@*****.**", * "url":"http:\/\/podunk.edu", * "photo":{ * "mimetype":"image\/jpeg", * "src":"http:\/\/podunk.edu\/photo\/profile\/m\/5" * }, * "guid":"kgVFf_...", * "guid_sig":"PT9-TAp...", * }, * "author":{ * "name":"Indigo", * "address":"*****@*****.**", * "url":"http:\/\/podunk.edu", * "photo":{ * "mimetype":"image\/jpeg", * "src":"http:\/\/podunk.edu\/photo\/profile\/m\/5" * }, * "guid":"kgVFf_...", * "guid_sig":"PT9-TAp..." * } * } * } *} * * Currently defined message types are 'activity', 'mail', 'profile' and 'channel_sync', which each have * different content schemas. * * Ping packet: * A ping packet does not require any parameters except the type. It may or may not be encrypted. * * { * "type": "ping" * } * * On receipt of a ping packet a ping response will be returned: * * { * "success" : 1, * "site" { * "url":"http:\/\/podunk.edu", * "url_sig":"T8Bp7j5...", * "sitekey": "-----BEGIN PUBLIC KEY----- * MIICIjANBgkqhkiG9w0BAQE..." * } * } * * The ping packet can be used to verify that a site has not been re-installed, and to * initiate corrective action if it has. The url_sig is signed with the site private key * and base64url encoded - and this should verify with the enclosed sitekey. Failure to * verify indicates the site is corrupt or otherwise unable to communicate using zot. * This return packet is not otherwise verified, so should be compared with other * results obtained from this site which were verified prior to taking action. For instance * if you have one verified result with this signature and key, and other records for this * url which have different signatures and keys, it indicates that the site was re-installed * and corrective action may commence (remove or mark invalid any entries with different * signatures). * If you have no records which match this url_sig and key - no corrective action should * be taken as this packet may have been returned by an imposter. * */ function post_post(&$a) { $encrypted_packet = false; $ret = array('success' => false); $data = json_decode($_REQUEST['data'], true); /** * Many message packets will arrive encrypted. The existence of an 'iv' element * tells us we need to unencapsulate the AES-256-CBC content using the site private key */ if (array_key_exists('iv', $data)) { $encrypted_packet = true; $data = crypto_unencapsulate($data, get_config('system', 'prvkey')); logger('mod_zot: decrypt1: ' . $data, LOGGER_DATA); $data = json_decode($data, true); } if (!$data) { // possible Bleichenbacher's attack, just treat it as a // message we have no handler for. It should fail a bit // further along with "no hub". Our public key is public // knowledge. There's no reason why anybody should get the // encryption wrong unless they're fishing or hacking. If // they're developing and made a goof, this can be discovered // in the logs of the destination site. If they're fishing or // hacking, the bottom line is we can't verify their hub. // That's all we're going to tell them. $data = array('type' => 'bogus'); } $msgtype = array_key_exists('type', $data) ? $data['type'] : ''; if ($msgtype === 'ping') { // Useful to get a health check on a remote site. // This will let us know if any important communication details // that we may have stored are no longer valid, regardless of xchan details. logger('POST: got ping send pong now back: ' . z_root(), LOGGER_DEBUG); $ret['success'] = true; $ret['site'] = array(); $ret['site']['url'] = z_root(); $ret['site']['url_sig'] = base64url_encode(rsa_sign(z_root(), get_config('system', 'prvkey'))); $ret['site']['sitekey'] = get_config('system', 'pubkey'); json_return_and_die($ret); } if ($msgtype === 'pickup') { /** * The 'pickup' message arrives with a tracking ID which is associated with a particular outq_hash * First verify that that the returned signatures verify, then check that we have an outbound queue item * with the correct hash. * If everything verifies, find any/all outbound messages in the queue for this hubloc and send them back * */ if (!$data['secret'] || !$data['secret_sig']) { $ret['message'] = 'no verification signature'; logger('mod_zot: pickup: ' . $ret['message'], LOGGER_DEBUG); json_return_and_die($ret); } $r = q("select distinct hubloc_sitekey from hubloc where hubloc_url = '%s' and hubloc_callback = '%s' and hubloc_sitekey != '' group by hubloc_sitekey ", dbesc($data['url']), dbesc($data['callback'])); if (!$r) { $ret['message'] = 'site not found'; logger('mod_zot: pickup: ' . $ret['message']); json_return_and_die($ret); } foreach ($r as $hubsite) { // verify the url_sig // If the server was re-installed at some point, there could be multiple hubs with the same url and callback. // Only one will have a valid key. $forgery = true; $secret_fail = true; $sitekey = $hubsite['hubloc_sitekey']; logger('mod_zot: Checking sitekey: ' . $sitekey, LOGGER_DATA); if (rsa_verify($data['callback'], base64url_decode($data['callback_sig']), $sitekey)) { $forgery = false; } if (rsa_verify($data['secret'], base64url_decode($data['secret_sig']), $sitekey)) { $secret_fail = false; } if (!$forgery && !$secret_fail) { break; } } if ($forgery) { $ret['message'] = 'possible site forgery'; logger('mod_zot: pickup: ' . $ret['message']); json_return_and_die($ret); } if ($secret_fail) { $ret['message'] = 'secret validation failed'; logger('mod_zot: pickup: ' . $ret['message']); json_return_and_die($ret); } /** * If we made it to here, the signatures verify, but we still don't know if the tracking ID is valid. * It wouldn't be an error if the tracking ID isn't found, because we may have sent this particular * queue item with another pickup (after the tracking ID for the other pickup was verified). */ $r = q("select outq_posturl from outq where outq_hash = '%s' and outq_posturl = '%s' limit 1", dbesc($data['secret']), dbesc($data['callback'])); if (!$r) { $ret['message'] = 'nothing to pick up'; logger('mod_zot: pickup: ' . $ret['message']); json_return_and_die($ret); } /** * Everything is good if we made it here, so find all messages that are going to this location * and send them all. */ $r = q("select * from outq where outq_posturl = '%s'", dbesc($data['callback'])); if ($r) { logger('mod_zot: succesful pickup message received from ' . $data['callback'] . ' ' . count($r) . ' message(s) picked up', LOGGER_DEBUG); $ret['success'] = true; $ret['pickup'] = array(); foreach ($r as $rr) { $ret['pickup'][] = array('notify' => json_decode($rr['outq_notify'], true), 'message' => json_decode($rr['outq_msg'], true)); $x = q("delete from outq where outq_hash = '%s' limit 1", dbesc($rr['outq_hash'])); } } $encrypted = crypto_encapsulate(json_encode($ret), $sitekey); json_return_and_die($encrypted); /** pickup: end */ } /** * All other message types require us to verify the sender. This is a generic check, so we * will do it once here and bail if anything goes wrong. */ if (array_key_exists('sender', $data)) { $sender = $data['sender']; } /** Check if the sender is already verified here */ $hub = zot_gethub($sender); if (!$hub) { /** Have never seen this guid or this guid coming from this location. Check it and register it. */ // (!!) this will validate the sender $result = zot_register_hub($sender); if (!$result['success'] || !($hub = zot_gethub($sender))) { $ret['message'] = 'Hub not available.'; logger('mod_zot: no hub'); json_return_and_die($ret); } } // Update our DB to show when we last communicated successfully with this hub // This will allow us to prune dead hubs from using up resources $r = q("update hubloc set hubloc_connected = '%s' where hubloc_id = %d limit 1", dbesc(datetime_convert()), intval($hub['hubloc_id'])); // a dead hub came back to life - reset any tombstones we might have if ($hub['hubloc_status'] & HUBLOC_OFFLINE) { q("update hubloc set hubloc_status = (hubloc_status ^ %d) where hubloc_id = %d limit 1", intval(HUBLOC_OFFLINE), intval($hub['hubloc_id'])); if ($r[0]['hubloc_flags'] & HUBLOC_FLAGS_ORPHANCHECK) { q("update hubloc set hubloc_flags = (hubloc_flags ^ %d) where hubloc_id = %d limit 1", intval(HUBLOC_FLAGS_ORPHANCHECK), intval($hub['hubloc_id'])); } q("update xchan set xchan_flags = (xchan_flags ^ %d) where (xchan_flags & %d) and xchan_hash = '%s' limit 1", intval(XCHAN_FLAGS_ORPHAN), intval(XCHAN_FLAGS_ORPHAN), dbesc($hub['hubloc_hash'])); } /** * This hub has now been proven to be valid. * Any hub with the same URL and a different sitekey cannot be valid. * Get rid of them (mark them deleted). There's a good chance they were re-installs. * */ q("update hubloc set hubloc_flags = ( hubloc_flags | %d ) where hubloc_url = '%s' and hubloc_sitekey != '%s' ", intval(HUBLOC_FLAGS_DELETED), dbesc($hub['hubloc_url']), dbesc($hub['hubloc_sitekey'])); // TODO: check which hub is primary and take action if mismatched if (array_key_exists('recipients', $data)) { $recipients = $data['recipients']; } if ($msgtype === 'auth_check') { /** * Requestor visits /magic/?dest=somewhere on their own site with a browser * magic redirects them to $destsite/post [with auth args....] * $destsite sends an auth_check packet to originator site * The auth_check packet is handled here by the originator's site * - the browser session is still waiting * inside $destsite/post for everything to verify * If everything checks out we'll return a token to $destsite * and then $destsite will verify the token, authenticate the browser * session and then redirect to the original destination. * If authentication fails, the redirection to the original destination * will still take place but without authentication. */ logger('mod_zot: auth_check', LOGGER_DEBUG); if (!$encrypted_packet) { logger('mod_zot: auth_check packet was not encrypted.'); $ret['message'] .= 'no packet encryption' . EOL; json_return_and_die($ret); } $arr = $data['sender']; $sender_hash = make_xchan_hash($arr['guid'], $arr['guid_sig']); // garbage collect any old unused notifications q("delete from verify where type = 'auth' and created < UTC_TIMESTAMP() - INTERVAL 10 MINUTE"); $y = q("select xchan_pubkey from xchan where xchan_hash = '%s' limit 1", dbesc($sender_hash)); // We created a unique hash in mod/magic.php when we invoked remote auth, and stored it in // the verify table. It is now coming back to us as 'secret' and is signed by a channel at the other end. // First verify their signature. We will have obtained a zot-info packet from them as part of the sender // verification. if (!$y || !rsa_verify($data['secret'], base64url_decode($data['secret_sig']), $y[0]['xchan_pubkey'])) { logger('mod_zot: auth_check: sender not found or secret_sig invalid.'); $ret['message'] .= 'sender not found or sig invalid ' . print_r($y, true) . EOL; json_return_and_die($ret); } // There should be exactly one recipient, the original auth requestor $ret['message'] .= 'recipients ' . print_r($recipients, true) . EOL; if ($data['recipients']) { $arr = $data['recipients'][0]; $recip_hash = make_xchan_hash($arr['guid'], $arr['guid_sig']); $c = q("select channel_id, channel_account_id, channel_prvkey from channel where channel_hash = '%s' limit 1", dbesc($recip_hash)); if (!$c) { logger('mod_zot: auth_check: recipient channel not found.'); $ret['message'] .= 'recipient not found.' . EOL; json_return_and_die($ret); } $confirm = base64url_encode(rsa_sign($data['secret'] . $recip_hash, $c[0]['channel_prvkey'])); // This additionally checks for forged sites since we already stored the expected result in meta // and we've already verified that this is them via zot_gethub() and that their key signed our token $z = q("select id from verify where channel = %d and type = 'auth' and token = '%s' and meta = '%s' limit 1", intval($c[0]['channel_id']), dbesc($data['secret']), dbesc($data['sender']['url'])); if (!$z) { logger('mod_zot: auth_check: verification key not found.'); $ret['message'] .= 'verification key not found' . EOL; json_return_and_die($ret); } $r = q("delete from verify where id = %d limit 1", intval($z[0]['id'])); $u = q("select account_service_class from account where account_id = %d limit 1", intval($c[0]['channel_account_id'])); logger('mod_zot: auth_check: success', LOGGER_DEBUG); $ret['success'] = true; $ret['confirm'] = $confirm; if ($u && $u[0]['account_service_class']) { $ret['service_class'] = $u[0]['account_service_class']; } // Set "do not track" flag if this site or this channel's profile is restricted if (intval(get_config('system', 'block_public'))) { $ret['DNT'] = true; } if (!perm_is_allowed($c[0]['channel_id'], '', 'view_profile')) { $ret['DNT'] = true; } if (get_pconfig($c[0]['channel_id'], 'system', 'do_not_track')) { $ret['DNT'] = true; } json_return_and_die($ret); } json_return_and_die($ret); } if ($msgtype === 'purge') { if ($recipients) { // basically this means "unfriend" foreach ($recipients as $recip) { $r = q("select channel.*,xchan.* from channel \n\t\t\t\t\tleft join xchan on channel_hash = xchan_hash\n\t\t\t\t\twhere channel_guid = '%s' and channel_guid_sig = '%s' limit 1", dbesc($recip['guid']), dbesc($recip['guid_sig'])); if ($r) { $r = q("select abook_id from abook where uid = %d and abook_xchan = '%s' limit 1", intval($r[0]['channel_id']), dbesc(make_xchan_hash($sender['guid'], $sender['guid_sig']))); if ($r) { contact_remove($r[0]['channel_id'], $r[0]['abook_id']); } } } } else { // Unfriend everybody - basically this means the channel has committed suicide $arr = $data['sender']; $sender_hash = make_xchan_hash($arr['guid'], $arr['guid_sig']); require_once 'include/Contact.php'; remove_all_xchan_resources($sender_hash); $ret['success'] = true; json_return_and_die($ret); } } if ($msgtype === 'refresh' || $msgtype === 'force_refresh') { // remote channel info (such as permissions or photo or something) // has been updated. Grab a fresh copy and sync it. // The difference between refresh and force_refresh is that // force_refresh unconditionally creates a directory update record, // even if no changes were detected upon processing. if ($recipients) { // This would be a permissions update, typically for one connection foreach ($recipients as $recip) { $r = q("select channel.*,xchan.* from channel \n\t\t\t\t\tleft join xchan on channel_hash = xchan_hash\n\t\t\t\t\twhere channel_guid = '%s' and channel_guid_sig = '%s' limit 1", dbesc($recip['guid']), dbesc($recip['guid_sig'])); $x = zot_refresh(array('xchan_guid' => $sender['guid'], 'xchan_guid_sig' => $sender['guid_sig'], 'hubloc_url' => $sender['url']), $r[0], $msgtype === 'force_refresh' ? true : false); } } else { // system wide refresh $x = zot_refresh(array('xchan_guid' => $sender['guid'], 'xchan_guid_sig' => $sender['guid_sig'], 'hubloc_url' => $sender['url']), null, $msgtype === 'force_refresh' ? true : false); } $ret['success'] = true; json_return_and_die($ret); } if ($msgtype === 'notify') { $async = get_config('system', 'queued_fetch'); if ($async) { // add to receive queue // qreceive_add($data); } else { $x = zot_fetch($data); $ret['delivery_report'] = $x; } $ret['success'] = true; json_return_and_die($ret); } // catchall json_return_and_die($ret); }
private function getAllinpyOrderResult() { require_once C('APP_ROOT') . "Lib/Pay/allinpay/php_rsa.php"; $result = array(); $merchantId = $_REQUEST["merchantId"]; $version = $_REQUEST['version']; $language = $_REQUEST['language']; $signType = $_REQUEST['signType']; $payType = $_REQUEST['payType']; $issuerId = $_REQUEST['issuerId']; $paymentOrderId = $_REQUEST['paymentOrderId']; $orderNo = $_REQUEST['orderNo']; $orderDatetime = $_REQUEST['orderDatetime']; $orderAmount = $_REQUEST['orderAmount']; $payDatetime = $_REQUEST['payDatetime']; $payAmount = $_REQUEST['payAmount']; $ext1 = $_REQUEST['ext1']; $ext2 = $_REQUEST['ext2']; $payResult = $_REQUEST['payResult']; $errorCode = $_REQUEST['errorCode']; $returnDatetime = $_REQUEST['returnDatetime']; $signMsg = $_REQUEST["signMsg"]; $bufSignSrc = ""; if ($merchantId != "") { $bufSignSrc = $bufSignSrc . "merchantId=" . $merchantId . "&"; } if ($version != "") { $bufSignSrc = $bufSignSrc . "version=" . $version . "&"; } if ($language != "") { $bufSignSrc = $bufSignSrc . "language=" . $language . "&"; } if ($signType != "") { $bufSignSrc = $bufSignSrc . "signType=" . $signType . "&"; } if ($payType != "") { $bufSignSrc = $bufSignSrc . "payType=" . $payType . "&"; } if ($issuerId != "") { $bufSignSrc = $bufSignSrc . "issuerId=" . $issuerId . "&"; } if ($paymentOrderId != "") { $bufSignSrc = $bufSignSrc . "paymentOrderId=" . $paymentOrderId . "&"; } if ($orderNo != "") { $bufSignSrc = $bufSignSrc . "orderNo=" . $orderNo . "&"; } if ($orderDatetime != "") { $bufSignSrc = $bufSignSrc . "orderDatetime=" . $orderDatetime . "&"; } if ($orderAmount != "") { $bufSignSrc = $bufSignSrc . "orderAmount=" . $orderAmount . "&"; } if ($payDatetime != "") { $bufSignSrc = $bufSignSrc . "payDatetime=" . $payDatetime . "&"; } if ($payAmount != "") { $bufSignSrc = $bufSignSrc . "payAmount=" . $payAmount . "&"; } if ($ext1 != "") { $bufSignSrc = $bufSignSrc . "ext1=" . $ext1 . "&"; } if ($ext2 != "") { $bufSignSrc = $bufSignSrc . "ext2=" . $ext2 . "&"; } if ($payResult != "") { $bufSignSrc = $bufSignSrc . "payResult=" . $payResult . "&"; } if ($errorCode != "") { $bufSignSrc = $bufSignSrc . "errorCode=" . $errorCode . "&"; } if ($returnDatetime != "") { $bufSignSrc = $bufSignSrc . "returnDatetime=" . $returnDatetime; } $allinpay_params = C('ALLINPAY_PARAMS'); //验签 //解析publickey.txt文本获取公钥信息 $publickeyfile = C('APP_ROOT') . $allinpay_params[$allinpay_params["MODE"] . '_KEY']; $publickeycontent = file_get_contents($publickeyfile); //echo "<br>".$content; $publickeyarray = explode(PHP_EOL, $publickeycontent); $publickey = explode('=', $publickeyarray[0]); $modulus = explode('=', $publickeyarray[1]); //echo "<br>publickey=".$publickey[1]; //echo "<br>modulus=".$modulus[1]; $keylength = 1024; //验签结果 $verify_result = rsa_verify($bufSignSrc, $signMsg, $publickey[1], $modulus[1], $keylength, "sha1"); $result['verify_result'] = $verify_result; $result['merchantId'] = $merchantId; $result['version'] = $version; $result['language'] = $language; $result['signType'] = $signType; $result['payType'] = $payType; $result['issuerId'] = $issuerId; $result['paymentOrderId'] = $paymentOrderId; $result['orderNo'] = $orderNo; $result['orderDatetime'] = $orderDatetime; $result['orderAmount'] = $orderAmount; $result['payDatetime'] = $payDatetime; $result['payAmount'] = $payAmount; $result['ext1'] = $ext1; $result['ext2'] = $ext2; $result['payResult'] = $payResult; $result['errorCode'] = $errorCode; $result['returnDatetime'] = $returnDatetime; return $result; }
$params = $_POST; ksort($params); $str = ""; foreach ($params as $key => $value) { if ($key == "sign") { continue; } if (strlen($str) == 0) { $str = $key . "=" . stripslashes($value); } else { $str = $str . "&" . $key . "=" . stripslashes($value); } } $sign = base64_decode(stripslashes($_POST["sign"])); $pubkey = "-----BEGIN PUBLIC KEY-----\r\n" . chunk_split($pf_info["PubKey"], 64, "\r\n") . "-----END PUBLIC KEY-----"; if (!rsa_verify($str, $sign, $pubkey)) { log::instance()->error("ret: 签名无效"); echo "{\"result\":1}"; exit; } // end verify if (stripslashes($_POST["result"]) != '0') { log::instance()->error("ret: 支付失败"); echo "{\"result\":0}"; exit; } $note = json_decode(stripslashes($_POST["extReserved"]), true); $ret = recharge($pf_info["PF"], $note["sid"], stripslashes($_POST["requestId"]), $note["uid"], $note["item"], stripslashes($_POST["amount"]), stripslashes($_POST["orderId"]), 0); log::instance()->debug("ret: " . $ret); if ($ret == "SUCCESS" || $ret == "TRADE_NO NOT EXIST") { echo "{\"result\":0}";
public function notify($request) { $return_res = array('info' => '', 'status' => false); //file_put_contents("./system/payment/log/notify_".strftime("%Y%m%d%H%M%S",time()).".txt",print_r($request,true)); //$payment_id = $GLOBALS['db']->getOne("select payment_id from ".DB_PREFIX."payment_log where id=".intval($ext1)); $payment = $GLOBALS['db']->getRow("select id,config from " . DB_PREFIX . "payment where class_name='Allinpay'"); $payment['config'] = unserialize($payment['config']); //print_r($payment['config']);exit; $merchant_acctid = trim($payment['config']['merchant_id']); //人民币账号 不可空 $key = trim($payment['config']['md5_key']); $merchantId = $request["merchantId"]; $version = $request['version']; $language = $request['language']; $signType = $request['signType']; $payType = $request['payType']; $issuerId = $request['issuerId']; $paymentOrderId = $request['paymentOrderId']; $orderNo = $request['orderNo']; $orderDatetime = $request['orderDatetime']; $orderAmount = $request['orderAmount']; $payDatetime = $request['payDatetime']; $payAmount = $request['payAmount']; $ext1 = $request['ext1']; $ext2 = $request['ext2']; $payResult = $request['payResult']; $errorCode = $request['errorCode']; $returnDatetime = $request['returnDatetime']; $signMsg = $request["signMsg"]; $bufSignSrc = ""; if ($merchantId != "") { $bufSignSrc = $bufSignSrc . "merchantId=" . $merchantId . "&"; } if ($version != "") { $bufSignSrc = $bufSignSrc . "version=" . $version . "&"; } if ($language != "") { $bufSignSrc = $bufSignSrc . "language=" . $language . "&"; } if ($signType != "") { $bufSignSrc = $bufSignSrc . "signType=" . $signType . "&"; } if ($payType != "") { $bufSignSrc = $bufSignSrc . "payType=" . $payType . "&"; } if ($issuerId != "") { $bufSignSrc = $bufSignSrc . "issuerId=" . $issuerId . "&"; } if ($paymentOrderId != "") { $bufSignSrc = $bufSignSrc . "paymentOrderId=" . $paymentOrderId . "&"; } if ($orderNo != "") { $bufSignSrc = $bufSignSrc . "orderNo=" . $orderNo . "&"; } if ($orderDatetime != "") { $bufSignSrc = $bufSignSrc . "orderDatetime=" . $orderDatetime . "&"; } if ($orderAmount != "") { $bufSignSrc = $bufSignSrc . "orderAmount=" . $orderAmount . "&"; } if ($payDatetime != "") { $bufSignSrc = $bufSignSrc . "payDatetime=" . $payDatetime . "&"; } if ($payAmount != "") { $bufSignSrc = $bufSignSrc . "payAmount=" . $payAmount . "&"; } if ($ext1 != "") { $bufSignSrc = $bufSignSrc . "ext1=" . $ext1 . "&"; } if ($ext2 != "") { $bufSignSrc = $bufSignSrc . "ext2=" . $ext2 . "&"; } if ($payResult != "") { $bufSignSrc = $bufSignSrc . "payResult=" . $payResult . "&"; } if ($errorCode != "") { $bufSignSrc = $bufSignSrc . "errorCode=" . $errorCode . "&"; } if ($returnDatetime != "") { $bufSignSrc = $bufSignSrc . "returnDatetime=" . $returnDatetime; } /* //验签 //解析publickey.txt文本获取公钥信息 $publickeycontent = trim($payment['config']['public_key']); //echo "<br>".$content; $publickeyarray = explode(PHP_EOL, $publickeycontent); $publickey = explode('=',$publickeyarray[0]); $modulus = explode('=',$publickeyarray[1]); //echo "<br>publickey=".$publickey[1]; //echo "<br>modulus=".$modulus[1]; */ $publickey = trim($payment['config']['public_exponent']); $modulus = trim($payment['config']['public_modulus']); require_once APP_ROOT_PATH . "system/payment/Allinpay/php_rsa.php"; $keylength = 1024; //验签结果 //$verifyResult = rsa_verify($bufSignSrc,$signMsg, $publickey[1], $modulus[1], $keylength,"sha1"); $verifyResult = rsa_verify($bufSignSrc, $signMsg, $publickey, $modulus, $keylength, "sha1"); /* echo 'bufSignSrc:'.$bufSignSrc."<br>"; echo 'signMsg:'.$signMsg."<br>"; echo 'publickey:'.$publickey."<br>"; echo 'modulus:'.$modulus."<br>"; if($verifyResult){ echo "报文验签成功!"; }else{ echo "报文验签失败!"; } exit; */ if ($verifyResult) { $payment_notice = $GLOBALS['db']->getRow("select * from " . DB_PREFIX . "payment_notice where notice_sn = '" . $orderNo . "'"); require_once APP_ROOT_PATH . "system/libs/cart.php"; $rs = payment_paid($payment_notice['id'], $paymentOrderId); $is_paid = intval($GLOBALS['db']->getOne("select is_paid from " . DB_PREFIX . "payment_notice where id = '" . intval($payment_notice['id']) . "'")); if ($is_paid == 1) { echo '1'; } else { echo '0'; } } else { echo '0'; } }
/** * @brief Look up information about channel. * * @param string $webbie * does not have to be host qualified e.g. 'foo' is treated as 'foo\@thishub' * @param array $channel * (optional), if supplied permissions will be enumerated specifically for $channel * @param boolean $autofallback * fallback/failover to http if https connection cannot be established. Default is true. * * @return zotinfo array (with 'success' => true) or array('success' => false); */ public static function run($webbie, $channel = null, $autofallback = true) { $ret = array('success' => false); self::$token = random_string(); if (strpos($webbie, '@') === false) { $address = $webbie; $host = App::get_hostname(); } else { $address = substr($webbie, 0, strpos($webbie, '@')); $host = substr($webbie, strpos($webbie, '@') + 1); } $xchan_addr = $address . '@' . $host; if (!$address || !$xchan_addr) { logger('zot_finger: no address :' . $webbie); return $ret; } logger('using xchan_addr: ' . $xchan_addr, LOGGER_DATA, LOG_DEBUG); // potential issue here; the xchan_addr points to the primary hub. // The webbie we were called with may not, so it might not be found // unless we query for hubloc_addr instead of xchan_addr $r = q("select xchan.*, hubloc.* from xchan\n\t\t\tleft join hubloc on xchan_hash = hubloc_hash\n\t\t\twhere xchan_addr = '%s' and hubloc_primary = 1 limit 1", dbesc($xchan_addr)); if ($r) { $url = $r[0]['hubloc_url']; if ($r[0]['hubloc_network'] && $r[0]['hubloc_network'] !== 'zot') { logger('zot_finger: alternate network: ' . $webbie); logger('url: ' . $url . ', net: ' . var_export($r[0]['hubloc_network'], true), LOGGER_DATA, LOG_DEBUG); return $ret; } } else { $url = 'https://' . $host; } $rhs = '/.well-known/zot-info'; $https = strpos($url, 'https://') === 0 ? true : false; logger('zot_finger: ' . $address . ' at ' . $url, LOGGER_DEBUG); if ($channel) { $postvars = array('address' => $address, 'target' => $channel['channel_guid'], 'target_sig' => $channel['channel_guid_sig'], 'key' => $channel['channel_pubkey'], 'token' => self::$token); $result = z_post_url($url . $rhs, $postvars); if (!$result['success'] && $autofallback) { if ($https) { logger('zot_finger: https failed. falling back to http'); $result = z_post_url('http://' . $host . $rhs, $postvars); } } } else { $rhs .= '?f=&address=' . urlencode($address) . '&token=' . self::$token; $result = z_fetch_url($url . $rhs); if (!$result['success'] && $autofallback) { if ($https) { logger('zot_finger: https failed. falling back to http'); $result = z_fetch_url('http://' . $host . $rhs); } } } if (!$result['success']) { logger('zot_finger: no results'); return $ret; } $x = json_decode($result['body'], true); if ($x) { $signed_token = is_array($x) && array_key_exists('signed_token', $x) ? $x['signed_token'] : null; if ($signed_token) { $valid = rsa_verify('token.' . self::$token, base64url_decode($signed_token), $x['key']); if (!$valid) { logger('invalid signed token: ' . $url . $rhs, LOGGER_NORMAL, LOG_ERR); return $ret; } } else { logger('No signed token from ' . $url . $rhs, LOGGER_NORMAL, LOG_WARNING); // after 2017-01-01 this will be a hard error unless you over-ride it. if (time() > 1483228800 && !get_config('system', 'allow_unsigned_zotfinger')) { return $ret; } } } return $x; }
function diaspora_signed_retraction($importer, $xml, $msg) { $guid = notags(unxmlify($xml->target_guid)); $diaspora_handle = notags(unxmlify($xml->sender_handle)); $type = notags(unxmlify($xml->target_type)); $sig = notags(unxmlify($xml->target_author_signature)); $contact = diaspora_get_contact_by_handle($importer['uid'], $diaspora_handle); if (!$contact) { logger('diaspora_signed_retraction: no contact'); return; } // this may not yet work for comments. Need to see how the relaying works // and figure out who signs it. $signed_data = $guid . ';' . $type; $sig = base64_decode($sig); $key = $msg['key']; if (!rsa_verify($signed_data, $sig, $key, 'sha256')) { logger('diaspora_signed_retraction: owner verification failed.' . print_r($msg, true)); return; } if ($type === 'StatusMessage') { $r = q("select * from item where guid = '%s' and uid = %d limit 1", dbesc($guid), intval($importer['uid'])); if (count($r)) { if (link_compare($r[0]['author-link'], $contact['url'])) { q("update item set `deleted` = 1, `changed` = '%s' where `id` = %d limit 1", dbesc(datetime_convert()), intval($r[0]['id'])); } } } else { logger('diaspora_signed_retraction: unknown type: ' . $type); } return 202; // NOTREACHED }
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 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); }
/** * @brief * * @param array $arr * @param string $pubkey * @return boolean true if updated or inserted */ function import_site($arr, $pubkey) { if (!is_array($arr) || !$arr['url'] || !$arr['url_sig']) { return false; } if (!rsa_verify($arr['url'], base64url_decode($arr['url_sig']), $pubkey)) { logger('import_site: bad url_sig'); return false; } $update = false; $exists = false; $r = q("select * from site where site_url = '%s' limit 1", dbesc($arr['url'])); if ($r) { $exists = true; $siterecord = $r[0]; } $site_directory = 0; if ($arr['directory_mode'] == 'normal') { $site_directory = DIRECTORY_MODE_NORMAL; } if ($arr['directory_mode'] == 'primary') { $site_directory = DIRECTORY_MODE_PRIMARY; } if ($arr['directory_mode'] == 'secondary') { $site_directory = DIRECTORY_MODE_SECONDARY; } if ($arr['directory_mode'] == 'standalone') { $site_directory = DIRECTORY_MODE_STANDALONE; } $register_policy = 0; if ($arr['register_policy'] == 'closed') { $register_policy = REGISTER_CLOSED; } if ($arr['register_policy'] == 'open') { $register_policy = REGISTER_OPEN; } if ($arr['register_policy'] == 'approve') { $register_policy = REGISTER_APPROVE; } $access_policy = 0; if (array_key_exists('access_policy', $arr)) { if ($arr['access_policy'] === 'private') { $access_policy = ACCESS_PRIVATE; } if ($arr['access_policy'] === 'paid') { $access_policy = ACCESS_PAID; } if ($arr['access_policy'] === 'free') { $access_policy = ACCESS_FREE; } if ($arr['access_policy'] === 'tiered') { $access_policy = ACCESS_TIERED; } } // don't let insecure sites register as public hubs if (strpos($arr['url'], 'https://') === false) { $access_policy = ACCESS_PRIVATE; } if ($access_policy != ACCESS_PRIVATE) { $x = z_fetch_url($arr['url'] . '/siteinfo/json'); if (!$x['success']) { $access_policy = ACCESS_PRIVATE; } } $directory_url = htmlspecialchars($arr['directory_url'], ENT_COMPAT, 'UTF-8', false); $url = htmlspecialchars(strtolower($arr['url']), ENT_COMPAT, 'UTF-8', false); $sellpage = htmlspecialchars($arr['sellpage'], ENT_COMPAT, 'UTF-8', false); $site_location = htmlspecialchars($arr['location'], ENT_COMPAT, 'UTF-8', false); $site_realm = htmlspecialchars($arr['realm'], ENT_COMPAT, 'UTF-8', false); // You can have one and only one primary directory per realm. // Downgrade any others claiming to be primary. As they have // flubbed up this badly already, don't let them be directory servers at all. if ($site_directory === DIRECTORY_MODE_PRIMARY && $site_realm === get_directory_realm() && $arr['url'] != get_directory_primary()) { $site_directory = DIRECTORY_MODE_NORMAL; } if ($exists) { if ($siterecord['site_flags'] != $site_directory || $siterecord['site_access'] != $access_policy || $siterecord['site_directory'] != $directory_url || $siterecord['site_sellpage'] != $sellpage || $siterecord['site_location'] != $site_location || $siterecord['site_register'] != $register_policy || $siterecord['site_realm'] != $site_realm) { $update = true; // logger('import_site: input: ' . print_r($arr,true)); // logger('import_site: stored: ' . print_r($siterecord,true)); $r = q("update site set site_location = '%s', site_flags = %d, site_access = %d, site_directory = '%s', site_register = %d, site_update = '%s', site_sellpage = '%s', site_realm = '%s'\n\t\t\t\twhere site_url = '%s'", dbesc($site_location), intval($site_directory), intval($access_policy), dbesc($directory_url), intval($register_policy), dbesc(datetime_convert()), dbesc($sellpage), dbesc($site_realm), dbesc($url)); if (!$r) { logger('import_site: update failed. ' . print_r($arr, true)); } } else { // update the timestamp to indicate we communicated with this site q("update site set site_update = '%s' where site_url = '%s'", dbesc(datetime_convert()), dbesc($url)); } } else { $update = true; $r = q("insert into site ( site_location, site_url, site_access, site_flags, site_update, site_directory, site_register, site_sellpage, site_realm )\n\t\t\tvalues ( '%s', '%s', %d, %d, '%s', '%s', %d, '%s', '%s' )", dbesc($site_location), dbesc($url), intval($access_policy), intval($site_directory), dbesc(datetime_convert()), dbesc($directory_url), intval($register_policy), dbesc($sellpage), dbesc($site_realm)); if (!$r) { logger('import_site: record create failed. ' . print_r($arr, true)); } } return $update; }
function zot_reply_auth_check($data, $encrypted_packet) { $ret = array('success' => false); /* * Requestor visits /magic/?dest=somewhere on their own site with a browser * magic redirects them to $destsite/post [with auth args....] * $destsite sends an auth_check packet to originator site * The auth_check packet is handled here by the originator's site * - the browser session is still waiting * inside $destsite/post for everything to verify * If everything checks out we'll return a token to $destsite * and then $destsite will verify the token, authenticate the browser * session and then redirect to the original destination. * If authentication fails, the redirection to the original destination * will still take place but without authentication. */ logger('mod_zot: auth_check', LOGGER_DEBUG); if (!$encrypted_packet) { logger('mod_zot: auth_check packet was not encrypted.'); $ret['message'] .= 'no packet encryption' . EOL; json_return_and_die($ret); } $arr = $data['sender']; $sender_hash = make_xchan_hash($arr['guid'], $arr['guid_sig']); // garbage collect any old unused notifications // This was and should be 10 minutes but my hosting provider has time lag between the DB and // the web server. We should probably convert this to webserver time rather than DB time so // that the different clocks won't affect it and allow us to keep the time short. q("delete from verify where type = 'auth' and created < %s - INTERVAL %s", db_utcnow(), db_quoteinterval('30 MINUTE')); $y = q("select xchan_pubkey from xchan where xchan_hash = '%s' limit 1", dbesc($sender_hash)); // We created a unique hash in mod/magic.php when we invoked remote auth, and stored it in // the verify table. It is now coming back to us as 'secret' and is signed by a channel at the other end. // First verify their signature. We will have obtained a zot-info packet from them as part of the sender // verification. if (!$y || !rsa_verify($data['secret'], base64url_decode($data['secret_sig']), $y[0]['xchan_pubkey'])) { logger('mod_zot: auth_check: sender not found or secret_sig invalid.'); $ret['message'] .= 'sender not found or sig invalid ' . print_r($y, true) . EOL; json_return_and_die($ret); } // There should be exactly one recipient, the original auth requestor $ret['message'] .= 'recipients ' . print_r($recipients, true) . EOL; if ($data['recipients']) { $arr = $data['recipients'][0]; $recip_hash = make_xchan_hash($arr['guid'], $arr['guid_sig']); $c = q("select channel_id, channel_account_id, channel_prvkey from channel where channel_hash = '%s' limit 1", dbesc($recip_hash)); if (!$c) { logger('mod_zot: auth_check: recipient channel not found.'); $ret['message'] .= 'recipient not found.' . EOL; json_return_and_die($ret); } $confirm = base64url_encode(rsa_sign($data['secret'] . $recip_hash, $c[0]['channel_prvkey'])); // This additionally checks for forged sites since we already stored the expected result in meta // and we've already verified that this is them via zot_gethub() and that their key signed our token $z = q("select id from verify where channel = %d and type = 'auth' and token = '%s' and meta = '%s' limit 1", intval($c[0]['channel_id']), dbesc($data['secret']), dbesc($data['sender']['url'])); if (!$z) { logger('mod_zot: auth_check: verification key not found.'); $ret['message'] .= 'verification key not found' . EOL; json_return_and_die($ret); } $r = q("delete from verify where id = %d", intval($z[0]['id'])); $u = q("select account_service_class from account where account_id = %d limit 1", intval($c[0]['channel_account_id'])); logger('mod_zot: auth_check: success', LOGGER_DEBUG); $ret['success'] = true; $ret['confirm'] = $confirm; if ($u && $u[0]['account_service_class']) { $ret['service_class'] = $u[0]['account_service_class']; } // Set "do not track" flag if this site or this channel's profile is restricted // in some way if (intval(get_config('system', 'block_public'))) { $ret['DNT'] = true; } if (!perm_is_allowed($c[0]['channel_id'], '', 'view_profile')) { $ret['DNT'] = true; } if (get_pconfig($c[0]['channel_id'], 'system', 'do_not_track')) { $ret['DNT'] = true; } if (get_pconfig($c[0]['channel_id'], 'system', 'hide_online_status')) { $ret['DNT'] = true; } json_return_and_die($ret); } json_return_and_die($ret); }
function get_item_elements($x, $allow_code = false) { $arr = array(); if ($allow_code) { $arr['body'] = $x['body']; } else { $arr['body'] = $x['body'] ? htmlspecialchars($x['body'], ENT_COMPAT, 'UTF-8', false) : ''; } $key = get_config('system', 'pubkey'); $maxlen = get_max_import_size(); if ($maxlen && mb_strlen($arr['body']) > $maxlen) { $arr['body'] = mb_substr($arr['body'], 0, $maxlen, 'UTF-8'); logger('get_item_elements: message length exceeds max_import_size: truncated'); } $arr['created'] = datetime_convert('UTC', 'UTC', $x['created']); $arr['edited'] = datetime_convert('UTC', 'UTC', $x['edited']); if ($arr['created'] > datetime_convert()) { $arr['created'] = datetime_convert(); } if ($arr['edited'] > datetime_convert()) { $arr['edited'] = datetime_convert(); } $arr['expires'] = x($x, 'expires') && $x['expires'] ? datetime_convert('UTC', 'UTC', $x['expires']) : NULL_DATE; $arr['commented'] = x($x, 'commented') && $x['commented'] ? datetime_convert('UTC', 'UTC', $x['commented']) : $arr['created']; $arr['comments_closed'] = x($x, 'comments_closed') && $x['comments_closed'] ? datetime_convert('UTC', 'UTC', $x['comments_closed']) : NULL_DATE; $arr['title'] = $x['title'] ? htmlspecialchars($x['title'], ENT_COMPAT, 'UTF-8', false) : ''; if (mb_strlen($arr['title']) > 255) { $arr['title'] = mb_substr($arr['title'], 0, 255); } $arr['app'] = $x['app'] ? htmlspecialchars($x['app'], ENT_COMPAT, 'UTF-8', false) : ''; $arr['route'] = $x['route'] ? htmlspecialchars($x['route'], ENT_COMPAT, 'UTF-8', false) : ''; $arr['mid'] = $x['message_id'] ? htmlspecialchars($x['message_id'], ENT_COMPAT, 'UTF-8', false) : ''; $arr['parent_mid'] = $x['message_top'] ? htmlspecialchars($x['message_top'], ENT_COMPAT, 'UTF-8', false) : ''; $arr['thr_parent'] = $x['message_parent'] ? htmlspecialchars($x['message_parent'], ENT_COMPAT, 'UTF-8', false) : ''; $arr['plink'] = $x['permalink'] ? htmlspecialchars($x['permalink'], ENT_COMPAT, 'UTF-8', false) : ''; $arr['location'] = $x['location'] ? htmlspecialchars($x['location'], ENT_COMPAT, 'UTF-8', false) : ''; $arr['coord'] = $x['longlat'] ? htmlspecialchars($x['longlat'], ENT_COMPAT, 'UTF-8', false) : ''; $arr['verb'] = $x['verb'] ? htmlspecialchars($x['verb'], ENT_COMPAT, 'UTF-8', false) : ''; $arr['mimetype'] = $x['mimetype'] ? htmlspecialchars($x['mimetype'], ENT_COMPAT, 'UTF-8', false) : ''; $arr['obj_type'] = $x['object_type'] ? htmlspecialchars($x['object_type'], ENT_COMPAT, 'UTF-8', false) : ''; $arr['tgt_type'] = $x['target_type'] ? htmlspecialchars($x['target_type'], ENT_COMPAT, 'UTF-8', false) : ''; $arr['public_policy'] = $x['public_scope'] ? htmlspecialchars($x['public_scope'], ENT_COMPAT, 'UTF-8', false) : ''; if ($arr['public_policy'] === 'public') { $arr['public_policy'] = ''; } $arr['comment_policy'] = $x['comment_scope'] ? htmlspecialchars($x['comment_scope'], ENT_COMPAT, 'UTF-8', false) : 'contacts'; $arr['sig'] = $x['signature'] ? htmlspecialchars($x['signature'], ENT_COMPAT, 'UTF-8', false) : ''; if (array_key_exists('diaspora_signature', $x) && is_array($x['diaspora_signature'])) { $x['diaspora_signature'] = json_encode($x['diaspora_signature']); } $arr['diaspora_meta'] = $x['diaspora_signature'] ? $x['diaspora_signature'] : ''; $arr['object'] = activity_sanitise($x['object']); $arr['target'] = activity_sanitise($x['target']); $arr['attach'] = activity_sanitise($x['attach']); $arr['term'] = decode_tags($x['tags']); $arr['item_private'] = array_key_exists('flags', $x) && is_array($x['flags']) && in_array('private', $x['flags']) ? 1 : 0; $arr['item_flags'] = 0; if (array_key_exists('flags', $x) && in_array('consensus', $x['flags'])) { $arr['item_consensus'] = 1; } if (array_key_exists('flags', $x) && in_array('deleted', $x['flags'])) { $arr['item_deleted'] = 1; } if (array_key_exists('flags', $x) && in_array('hidden', $x['flags'])) { $arr['item_hidden'] = 1; } // Here's the deal - the site might be down or whatever but if there's a new person you've never // seen before sending stuff to your stream, we MUST be able to look them up and import their data from their // hub and verify that they are legit - or else we're going to toss the post. We only need to do this // once, and after that your hub knows them. Sure some info is in the post, but it's only a transit identifier // and not enough info to be able to look you up from your hash - which is the only thing stored with the post. if (($xchan_hash = import_author_xchan($x['author'])) !== false) { $arr['author_xchan'] = $xchan_hash; } else { return array(); } // save a potentially expensive lookup if author == owner if ($arr['author_xchan'] === make_xchan_hash($x['owner']['guid'], $x['owner']['guid_sig'])) { $arr['owner_xchan'] = $arr['author_xchan']; } else { if (($xchan_hash = import_author_xchan($x['owner'])) !== false) { $arr['owner_xchan'] = $xchan_hash; } else { return array(); } } if ($arr['sig']) { $r = q("select xchan_pubkey from xchan where xchan_hash = '%s' limit 1", dbesc($arr['author_xchan'])); if ($r && rsa_verify($x['body'], base64url_decode($arr['sig']), $r[0]['xchan_pubkey'])) { $arr['item_verified'] = 1; } else { logger('get_item_elements: message verification failed.'); } } if (array_key_exists('revision', $x)) { // extended export encoding $arr['revision'] = $x['revision']; $arr['allow_cid'] = $x['allow_cid']; $arr['allow_gid'] = $x['allow_gid']; $arr['deny_cid'] = $x['deny_cid']; $arr['deny_gid'] = $x['deny_gid']; $arr['layout_mid'] = $x['layout_mid']; $arr['postopts'] = $x['postopts']; $arr['resource_id'] = $x['resource_id']; $arr['resource_type'] = $x['resource_type']; $arr['attach'] = $x['attach']; $arr['item_origin'] = $x['item_origin']; $arr['item_unseen'] = $x['item_unseen']; $arr['item_starred'] = $x['item_starred']; $arr['item_uplink'] = $x['item_uplink']; $arr['item_consensus'] = $x['item_consensus']; $arr['item_wall'] = $x['item_wall']; $arr['item_thread_top'] = $x['item_thread_top']; $arr['item_notshown'] = $x['item_notshown']; $arr['item_nsfw'] = $x['item_nsfw']; // local only $arr['item_relay'] = $x['item_relay']; $arr['item_mentionsme'] = $x['item_mentionsme']; $arr['item_nocomment'] = $x['item_nocomment']; // local only $arr['item_obscured'] = $x['item_obscured']; // local only $arr['item_verified'] = $x['item_verified']; $arr['item_retained'] = $x['item_retained']; $arr['item_rss'] = $x['item_rss']; $arr['item_deleted'] = $x['item_deleted']; $arr['item_type'] = $x['item_type']; $arr['item_hidden'] = $x['item_hidden']; $arr['item_unpublished'] = $x['item_unpublished']; $arr['item_delayed'] = $x['item_delayed']; $arr['item_pending_remove'] = $x['item_pending_remove']; $arr['item_blocked'] = $x['item_blocked']; if (array_key_exists('item_flags', $x)) { if ($x['item_flags'] & 0x4) { $arr['item_starred'] = 1; } if ($x['item_flags'] & 0x8) { $arr['item_uplink'] = 1; } if ($x['item_flags'] & 0x10) { $arr['item_consensus'] = 1; } if ($x['item_flags'] & 0x20) { $arr['item_wall'] = 1; } if ($x['item_flags'] & 0x40) { $arr['item_thread_top'] = 1; } if ($x['item_flags'] & 0x80) { $arr['item_notshown'] = 1; } if ($x['item_flags'] & 0x100) { $arr['item_nsfw'] = 1; } if ($x['item_flags'] & 0x400) { $arr['item_mentionsme'] = 1; } if ($x['item_flags'] & 0x800) { $arr['item_nocomment'] = 1; } if ($x['item_flags'] & 0x4000) { $arr['item_retained'] = 1; } if ($x['item_flags'] & 0x8000) { $arr['item_rss'] = 1; } } if (array_key_exists('item_restrict', $x)) { if ($x['item_restrict'] & 0x1) { $arr['item_hidden'] = 1; } if ($x['item_restrict'] & 0x2) { $arr['item_blocked'] = 1; } if ($x['item_restrict'] & 0x10) { $arr['item_deleted'] = 1; } if ($x['item_restrict'] & 0x20) { $arr['item_unpublished'] = 1; } if ($x['item_restrict'] & 0x40) { $arr['item_type'] = ITEM_TYPE_WEBPAGE; } if ($x['item_restrict'] & 0x80) { $arr['item_delayed'] = 1; } if ($x['item_restrict'] & 0x100) { $arr['item_type'] = ITEM_TYPE_BLOCK; } if ($x['item_restrict'] & 0x200) { $arr['item_type'] = ITEM_TYPE_PDL; } if ($x['item_restrict'] & 0x400) { $arr['item_type'] = ITEM_TYPE_BUG; } if ($x['item_restrict'] & 0x800) { $arr['item_pending_remove'] = 1; } if ($x['item_restrict'] & 0x1000) { $arr['item_type'] = ITEM_TYPE_DOC; } } } return $arr; }
include_once "log.php"; include_once "recharge.php"; include_once "ssl.php"; $config = (include "config.php"); header("Content-type: text/html; charset=utf-8"); log::init('./log', 'wdj_log'); $uri = $_SERVER['REQUEST_URI']; $body = file_get_contents('php://input'); log::instance()->debug("new con: {$uri} {$body}"); $AppID = "100013257"; $pf_info = $config["wdj"][$AppID]; // RSA verify $content = stripslashes($_POST["content"]); $sign = base64_decode(stripslashes($_POST["sign"])); $pem = chunk_split($pf_info["PubKey"], 64, "\n"); $pubkey = "-----BEGIN PUBLIC KEY-----\n" . $pem . "-----END PUBLIC KEY-----"; if (!rsa_verify($content, $sign, $pubkey)) { log::instance()->error("ret: 签名无效"); echo "fail"; exit; } // end verify $content = json_decode($content, true); $note = json_decode($content["out_trade_no"], true); $ret = recharge($pf_info["PF"], $note["sid"], $note["odr"], $note["uid"], $note["item"], $content["money"] / 100, $content["orderId"], 0); log::instance()->debug("ret: " . $ret); if ($ret == "SUCCESS" || $ret == "TRADE_NO NOT EXIST") { echo "success"; } else { echo "fail"; }
function get_item_elements($x) { $arr = array(); $arr['body'] = $x['body'] ? htmlspecialchars($x['body'], ENT_COMPAT, 'UTF-8', false) : ''; $key = get_config('system', 'pubkey'); $maxlen = get_max_import_size(); if ($maxlen && mb_strlen($arr['body']) > $maxlen) { $arr['body'] = mb_substr($arr['body'], 0, $maxlen, 'UTF-8'); logger('get_item_elements: message length exceeds max_import_size: truncated'); } $arr['created'] = datetime_convert('UTC', 'UTC', $x['created']); $arr['edited'] = datetime_convert('UTC', 'UTC', $x['edited']); if ($arr['created'] > datetime_convert()) { $arr['created'] = datetime_convert(); } if ($arr['edited'] > datetime_convert()) { $arr['edited'] = datetime_convert(); } $arr['expires'] = x($x, 'expires') && $x['expires'] ? datetime_convert('UTC', 'UTC', $x['expires']) : NULL_DATE; $arr['commented'] = x($x, 'commented') && $x['commented'] ? datetime_convert('UTC', 'UTC', $x['commented']) : $arr['created']; $arr['comments_closed'] = x($x, 'comments_closed') && $x['comments_closed'] ? datetime_convert('UTC', 'UTC', $x['comments_closed']) : NULL_DATE; $arr['title'] = $x['title'] ? htmlspecialchars($x['title'], ENT_COMPAT, 'UTF-8', false) : ''; if (mb_strlen($arr['title']) > 255) { $arr['title'] = mb_substr($arr['title'], 0, 255); } $arr['app'] = $x['app'] ? htmlspecialchars($x['app'], ENT_COMPAT, 'UTF-8', false) : ''; $arr['route'] = $x['route'] ? htmlspecialchars($x['route'], ENT_COMPAT, 'UTF-8', false) : ''; $arr['mid'] = $x['message_id'] ? htmlspecialchars($x['message_id'], ENT_COMPAT, 'UTF-8', false) : ''; $arr['parent_mid'] = $x['message_top'] ? htmlspecialchars($x['message_top'], ENT_COMPAT, 'UTF-8', false) : ''; $arr['thr_parent'] = $x['message_parent'] ? htmlspecialchars($x['message_parent'], ENT_COMPAT, 'UTF-8', false) : ''; $arr['plink'] = $x['permalink'] ? htmlspecialchars($x['permalink'], ENT_COMPAT, 'UTF-8', false) : ''; $arr['location'] = $x['location'] ? htmlspecialchars($x['location'], ENT_COMPAT, 'UTF-8', false) : ''; $arr['coord'] = $x['longlat'] ? htmlspecialchars($x['longlat'], ENT_COMPAT, 'UTF-8', false) : ''; $arr['verb'] = $x['verb'] ? htmlspecialchars($x['verb'], ENT_COMPAT, 'UTF-8', false) : ''; $arr['mimetype'] = $x['mimetype'] ? htmlspecialchars($x['mimetype'], ENT_COMPAT, 'UTF-8', false) : ''; $arr['obj_type'] = $x['object_type'] ? htmlspecialchars($x['object_type'], ENT_COMPAT, 'UTF-8', false) : ''; $arr['tgt_type'] = $x['target_type'] ? htmlspecialchars($x['target_type'], ENT_COMPAT, 'UTF-8', false) : ''; $arr['public_policy'] = $x['public_scope'] ? htmlspecialchars($x['public_scope'], ENT_COMPAT, 'UTF-8', false) : ''; if ($arr['public_policy'] === 'public') { $arr['public_policy'] = ''; } $arr['comment_policy'] = $x['comment_scope'] ? htmlspecialchars($x['comment_scope'], ENT_COMPAT, 'UTF-8', false) : 'contacts'; $arr['sig'] = $x['signature'] ? htmlspecialchars($x['signature'], ENT_COMPAT, 'UTF-8', false) : ''; $arr['diaspora_meta'] = $x['diaspora_signature'] ? json_encode(crypto_encapsulate($x['diaspora_signature'], $key)) : ''; $arr['object'] = activity_sanitise($x['object']); $arr['target'] = activity_sanitise($x['target']); $arr['attach'] = activity_sanitise($x['attach']); $arr['term'] = decode_tags($x['tags']); $arr['item_private'] = array_key_exists('flags', $x) && is_array($x['flags']) && in_array('private', $x['flags']) ? 1 : 0; $arr['item_flags'] = 0; if (array_key_exists('flags', $x) && in_array('consensus', $x['flags'])) { $arr['item_flags'] |= ITEM_CONSENSUS; } if (array_key_exists('flags', $x) && in_array('deleted', $x['flags'])) { $arr['item_restrict'] |= ITEM_DELETED; } if (array_key_exists('flags', $x) && in_array('hidden', $x['flags'])) { $arr['item_restrict'] |= ITEM_HIDDEN; } // Here's the deal - the site might be down or whatever but if there's a new person you've never // seen before sending stuff to your stream, we MUST be able to look them up and import their data from their // hub and verify that they are legit - or else we're going to toss the post. We only need to do this // once, and after that your hub knows them. Sure some info is in the post, but it's only a transit identifier // and not enough info to be able to look you up from your hash - which is the only thing stored with the post. if (($xchan_hash = import_author_xchan($x['author'])) !== false) { $arr['author_xchan'] = $xchan_hash; } else { return array(); } // save a potentially expensive lookup if author == owner if ($arr['author_xchan'] === make_xchan_hash($x['owner']['guid'], $x['owner']['guid_sig'])) { $arr['owner_xchan'] = $arr['author_xchan']; } else { if (($xchan_hash = import_author_xchan($x['owner'])) !== false) { $arr['owner_xchan'] = $xchan_hash; } else { return array(); } } if ($arr['sig']) { $r = q("select xchan_pubkey from xchan where xchan_hash = '%s' limit 1", dbesc($arr['author_xchan'])); if ($r && rsa_verify($x['body'], base64url_decode($arr['sig']), $r[0]['xchan_pubkey'])) { $arr['item_flags'] |= ITEM_VERIFIED; } else { logger('get_item_elements: message verification failed.'); } } // if it's a private post, encrypt it in the DB. // We have to do that here because we need to cleanse the input and prevent bad stuff from getting in, // and we need plaintext to do that. if (intval($arr['item_private'])) { $arr['item_flags'] = $arr['item_flags'] | ITEM_OBSCURED; if ($arr['title']) { $arr['title'] = json_encode(crypto_encapsulate($arr['title'], $key)); } if ($arr['body']) { $arr['body'] = json_encode(crypto_encapsulate($arr['body'], $key)); } } if (array_key_exists('revision', $x)) { // extended export encoding $arr['revision'] = $x['revision']; $arr['allow_cid'] = $x['allow_cid']; $arr['allow_gid'] = $x['allow_gid']; $arr['deny_cid'] = $x['deny_cid']; $arr['deny_gid'] = $x['deny_gid']; $arr['layout_mid'] = $x['layout_mid']; $arr['postopts'] = $x['postopts']; $arr['resource_id'] = $x['resource_id']; $arr['resource_type'] = $x['resource_type']; $arr['item_restrict'] = $x['item_restrict']; $arr['item_flags'] = $x['item_flags']; $arr['attach'] = $x['attach']; } return $arr; }
function diaspora_signed_retraction($importer, $xml, $msg) { $guid = notags(unxmlify($xml->target_guid)); $diaspora_handle = notags(unxmlify($xml->sender_handle)); $type = notags(unxmlify($xml->target_type)); $sig = notags(unxmlify($xml->target_author_signature)); $parent_author_signature = $xml->parent_author_signature ? notags(unxmlify($xml->parent_author_signature)) : ''; $contact = diaspora_get_contact_by_handle($importer['uid'], $diaspora_handle); if (!$contact) { logger('diaspora_signed_retraction: no contact'); return; } $signed_data = $guid . ';' . $type; $sig_decode = base64_decode($sig); if (strcasecmp($diaspora_handle, $msg['author']) == 0) { $person = $contact; $key = $msg['key']; } else { $person = find_diaspora_person_by_handle($diaspora_handle); if (is_array($person) && x($person, 'pubkey')) { $key = $person['pubkey']; } else { logger('diaspora_signed_retraction: unable to find author details'); return; } } if (!rsa_verify($signed_data, $sig_decode, $key, 'sha256')) { logger('diaspora_signed_retraction: retraction-owner verification failed.' . print_r($msg, true)); return; } if ($parent_author_signature) { $parent_author_signature = base64_decode($parent_author_signature); $key = $msg['key']; if (!rsa_verify($signed_data, $parent_author_signature, $key, 'sha256')) { logger('diaspora_signed_retraction: failed to verify person relaying the retraction (e.g. owner of a post relaying a retracted comment'); return; } } if ($type === 'StatusMessage' || $type === 'Comment' || $type === 'Like') { $r = q("select * from item where guid = '%s' and uid = %d and not file like '%%[%%' limit 1", dbesc($guid), intval($importer['uid'])); if (count($r)) { if (link_compare($r[0]['author-link'], $contact['url'])) { q("update item set `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = '' where `id` = %d limit 1", dbesc(datetime_convert()), dbesc(datetime_convert()), intval($r[0]['id'])); // Now check if the retraction needs to be relayed by us // // The first item in the `item` table with the parent id is the parent. However, MySQL doesn't always // return the items ordered by `item`.`id`, in which case the wrong item is chosen as the parent. // The only item with `parent` and `id` as the parent id is the parent item. $p = q("select origin from item where parent = %d and id = %d limit 1", $r[0]['parent'], $r[0]['parent']); if (count($p)) { if ($p[0]['origin'] && !$parent_author_signature) { q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ", $r[0]['id'], dbesc($signed_data), dbesc($sig), dbesc($diaspora_handle)); // the existence of parent_author_signature would have meant the parent_author or owner // is already relaying. logger('diaspora_signed_retraction: relaying relayable_retraction'); proc_run('php', 'include/notifier.php', 'relayable_retraction', $r[0]['id']); } } } } } else { logger('diaspora_signed_retraction: unknown type: ' . $type); } return 202; // NOTREACHED }
function diaspora_signed_retraction($importer, $xml, $msg) { $guid = notags(unxmlify($xml->target_guid)); $diaspora_handle = notags(unxmlify($xml->sender_handle)); $type = notags(unxmlify($xml->target_type)); $sig = notags(unxmlify($xml->target_author_signature)); $parent_author_signature = $xml->parent_author_signature ? notags(unxmlify($xml->parent_author_signature)) : ''; $contact = diaspora_get_contact_by_handle($importer['uid'], $diaspora_handle); if (!$contact) { logger('diaspora_signed_retraction: no contact ' . $diaspora_handle . ' for ' . $importer['uid']); return; } $signed_data = $guid . ';' . $type; $key = $msg['key']; /* How Diaspora performs relayable_retraction signature checking: - If an item has been sent by the item author to the top-level post owner to relay on to the rest of the contacts on the top-level post, the top-level post owner checks the author_signature, then creates a parent_author_signature before relaying the item on - If an item has been relayed on by the top-level post owner, the contacts who receive it check only the parent_author_signature. Basically, they trust that the top-level post owner has already verified the authenticity of anything he/she sends out - In either case, the signature that get checked is the signature created by the person who sent the salmon */ if ($parent_author_signature) { $parent_author_signature = base64_decode($parent_author_signature); if (!rsa_verify($signed_data, $parent_author_signature, $key, 'sha256')) { logger('diaspora_signed_retraction: top-level post owner verification failed'); return; } } else { $sig_decode = base64_decode($sig); if (!rsa_verify($signed_data, $sig_decode, $key, 'sha256')) { logger('diaspora_signed_retraction: retraction owner verification failed.' . print_r($msg, true)); return; } } if ($type === 'StatusMessage' || $type === 'Comment' || $type === 'Like') { $r = q("select * from item where guid = '%s' and uid = %d and not file like '%%[%%' limit 1", dbesc($guid), intval($importer['uid'])); if (count($r)) { if (link_compare($r[0]['author-link'], $contact['url'])) { q("update item set `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = '' where `id` = %d", dbesc(datetime_convert()), dbesc(datetime_convert()), intval($r[0]['id'])); delete_thread($r[0]['id'], $r[0]['parent-uri']); // Now check if the retraction needs to be relayed by us // // The first item in the `item` table with the parent id is the parent. However, MySQL doesn't always // return the items ordered by `item`.`id`, in which case the wrong item is chosen as the parent. // The only item with `parent` and `id` as the parent id is the parent item. $p = q("select origin from item where parent = %d and id = %d limit 1", $r[0]['parent'], $r[0]['parent']); if (count($p)) { if ($p[0]['origin'] && !$parent_author_signature) { q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ", $r[0]['id'], dbesc($signed_data), dbesc($sig), dbesc($diaspora_handle)); // the existence of parent_author_signature would have meant the parent_author or owner // is already relaying. logger('diaspora_signed_retraction: relaying relayable_retraction'); proc_run('php', 'include/notifier.php', 'drop', $r[0]['id']); } } } } } else { logger('diaspora_signed_retraction: unknown type: ' . $type); } return 202; // NOTREACHED }
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); }