function pumpio_registerclient($a, $host) { $url = 'https://' . $host . '/api/client/register'; $params = array(); $application_name = get_config('pumpio', 'application_name'); if (!$application_name) { $application_name = $a->get_hostname(); } $params['type'] = 'client_associate'; $params['contacts'] = get_config('system', 'admin_email'); $params['application_type'] = 'native'; $params['application_name'] = $application_name; $params['logo_uri'] = z_root() . '/images/rhash-32.png'; $params['redirect_uris'] = z_root() . '/pumpio/connect'; $res = z_post_url($url, $params); if ($res['success']) { logger('pumpio: registerclient: ' . $res['body'], LOGGER_DATA); $values = json_decode($res['body'], true); $pumpio = array(); $pumpio["client_id"] = $values['client_id']; $pumpio["client_secret"] = $values['client_secret']; //print_r($values); return $values; } return false; }
function share_init(&$a) { $post_id = argc() > 1 ? intval(argv(1)) : 0; if (!$post_id) { killme(); } if (!(local_channel() || remote_channel())) { killme(); } $r = q("SELECT * from item left join xchan on author_xchan = xchan_hash WHERE id = %d LIMIT 1", intval($post_id)); if (!$r) { killme(); } if ($r[0]['item_private'] && $r[0]['xchan_network'] !== 'rss') { killme(); } $sql_extra = item_permissions_sql($r[0]['uid']); $r = q("select * from item where id = %d {$sql_extra}", intval($post_id)); if (!$r) { killme(); } /** @FIXME we only share bbcode */ if ($r[0]['mimetype'] !== 'text/bbcode') { killme(); } /** @FIXME eventually we want to post remotely via rpost on your home site */ // When that works remove this next bit: if (!local_channel()) { killme(); } xchan_query($r); if (strpos($r[0]['body'], "[/share]") !== false) { $pos = strpos($r[0]['body'], "[share"); $o = substr($r[0]['body'], $pos); } else { $o = "[share author='" . urlencode($r[0]['author']['xchan_name']) . "' profile='" . $r[0]['author']['xchan_url'] . "' avatar='" . $r[0]['author']['xchan_photo_s'] . "' link='" . $r[0]['plink'] . "' posted='" . $r[0]['created'] . "' message_id='" . $r[0]['mid'] . "']"; if ($r[0]['title']) { $o .= '[b]' . $r[0]['title'] . '[/b]' . "\n"; } $o .= $r[0]['body']; $o .= "[/share]"; } if (local_channel()) { echo $o; killme(); } $observer = $a->get_observer(); $parsed = $observer['xchan_url']; if ($parsed) { $post_url = $parsed['scheme'] . ':' . $parsed['host'] . ($parsed['port'] ? ':' . $parsed['port'] : '') . '/rpost'; /** * @FIXME we were probably called from JS so we don't know the return page. * In fact we won't be able to load the remote page. * we might need an iframe */ $x = z_post_url($post_url, array('f' => '', 'body' => $o)); killme(); } }
function queue_run($argv, $argc) { cli_startup(); global $a; require_once 'include/items.php'; require_once 'include/bbcode.php'; if (argc() > 1) { $queue_id = argv(1); } else { $queue_id = 0; } $deadguys = array(); logger('queue: start'); $r = q("DELETE FROM outq WHERE outq_created < UTC_TIMESTAMP() - INTERVAL 3 DAY"); if ($queue_id) { $r = q("SELECT * FROM outq WHERE outq_hash = '%s' LIMIT 1", dbesc($queue_id)); } else { // For the first 12 hours we'll try to deliver every 15 minutes // After that, we'll only attempt delivery once per hour. // This currently only handles the default queue drivers ('zot' or '') which we will group by posturl // so that we don't start off a thousand deliveries for a couple of dead hubs. // The zot driver will deliver everything destined for a single hub once contact is made (*if* contact is made). // Other drivers will have to do something different here and may need their own query. $r = q("SELECT * FROM outq WHERE outq_delivered = 0 and (( outq_created > UTC_TIMESTAMP() - INTERVAL 12 HOUR and outq_updated < UTC_TIMESTAMP() - INTERVAL 15 MINUTE ) OR ( outq_updated < UTC_TIMESTAMP() - INTERVAL 1 HOUR )) group by outq_posturl"); } if (!$r) { return; } foreach ($r as $rr) { if (in_array($rr['outq_posturl'], $deadguys)) { continue; } if ($rr['outq_driver'] === 'post') { $result = z_post_url($rr['outq_posturl'], $rr['outq_msg']); if ($result['success'] && $result['return_code'] < 300) { logger('queue: queue post success to ' . $rr['outq_posturl'], LOGGER_DEBUG); $y = q("delete from outq where outq_hash = '%s' limit 1", dbesc($rr['ouq_hash'])); } else { logger('queue: queue post returned ' . $result['return_code'] . ' from ' . $rr['outq_posturl'], LOGGER_DEBUG); $y = q("update outq set outq_updated = '%s' where outq_hash = '%s' limit 1", dbesc(datetime_convert()), dbesc($rr['outq_hash'])); } continue; } $result = zot_zot($rr['outq_posturl'], $rr['outq_notify']); if ($result['success']) { zot_process_response($rr['outq_posturl'], $result, $rr); } else { $deadguys[] = $rr['outq_posturl']; $y = q("update outq set outq_updated = '%s' where outq_hash = '%s' limit 1", dbesc(datetime_convert()), dbesc($rr['outq_hash'])); } } }
function deliver_run($argv, $argc) { cli_startup(); $a = get_app(); if ($argc < 2) { return; } logger('deliver: invoked: ' . print_r($argv, true), LOGGER_DATA); for ($x = 1; $x < $argc; $x++) { $r = q("select * from outq where outq_hash = '%s' limit 1", dbesc($argv[$x])); if ($r) { if ($r[0]['outq_driver'] === 'post') { $result = z_post_url($r[0]['outq_posturl'], $r[0]['outq_msg']); if ($result['success'] && $result['return_code'] < 300) { logger('deliver: queue post success to ' . $r[0]['outq_posturl'], LOGGER_DEBUG); $y = q("delete from outq where outq_hash = '%s' limit 1", dbesc($argv[$x])); } else { logger('deliver: queue post returned ' . $result['return_code'] . ' from ' . $r[0]['outq_posturl'], LOGGER_DEBUG); $y = q("update outq set outq_updated = '%s' where outq_hash = '%s' limit 1", dbesc(datetime_convert()), dbesc($argv[$x])); } continue; } if ($r[0]['outq_posturl'] === z_root() . '/post') { logger('deliver: local delivery', LOGGER_DEBUG); // local delivery // we should probably batch these and save a few delivery processes // If there is no outq_msg, this is a refresh_all message which does not require local handling if ($r[0]['outq_msg']) { $msg = array('body' => json_encode(array('pickup' => array(array('notify' => json_decode($r[0]['outq_notify'], true), 'message' => json_decode($r[0]['outq_msg'], true)))))); zot_import($msg, z_root()); $r = q("delete from outq where outq_hash = '%s' limit 1", dbesc($argv[$x])); } } else { logger('deliver: dest: ' . $r[0]['outq_posturl'], LOGGER_DEBUG); $result = zot_zot($r[0]['outq_posturl'], $r[0]['outq_notify']); if ($result['success']) { zot_process_response($r[0]['outq_posturl'], $result, $r[0]); } else { $y = q("update outq set outq_updated = '%s' where outq_hash = '%s' limit 1", dbesc(datetime_convert()), dbesc($argv[$x])); } } } } }
function oexchange_content(&$a) { if (!local_channel()) { if (remote_channel()) { $observer = $a->get_observer(); if ($observer && $observer['xchan_url']) { $parsed = @parse_url($observer['xchan_url']); if (!$parsed) { notice(t('Unable to find your hub.') . EOL); return; } $url = $parsed['scheme'] . '://' . $parsed['host'] . ($parsed['port'] ? ':' . $parsed['port'] : ''); $url .= '/oexchange'; $result = z_post_url($url, $_REQUEST); json_return_and_die($result); } } return login(false); } if (argc() > 1 && argv(1) === 'done') { info(t('Post successful.') . EOL); return; } $url = x($_REQUEST, 'url') && strlen($_REQUEST['url']) ? urlencode(notags(trim($_REQUEST['url']))) : ''; $title = x($_REQUEST, 'title') && strlen($_REQUEST['title']) ? '&title=' . urlencode(notags(trim($_REQUEST['title']))) : ''; $description = x($_REQUEST, 'description') && strlen($_REQUEST['description']) ? '&description=' . urlencode(notags(trim($_REQUEST['description']))) : ''; $tags = x($_REQUEST, 'tags') && strlen($_REQUEST['tags']) ? '&tags=' . urlencode(notags(trim($_REQUEST['tags']))) : ''; $ret = z_fetch_url($a->get_baseurl() . '/urlinfo?f=&url=' . $url . $title . $description . $tags); if ($ret['success']) { $s = $ret['body']; } if (!strlen($s)) { return; } $post = array(); $post['profile_uid'] = local_channel(); $post['return'] = '/oexchange/done'; $post['body'] = $s; $post['type'] = 'wall'; $_REQUEST = $post; require_once 'mod/item.php'; item_post($a); }
function sync_files($channel, $files) { require_once 'include/attach.php'; if ($channel && $files) { foreach ($files as $f) { if (!$f) { continue; } $fetch_url = $f['fetch_url']; $oldbase = dirname($fetch_url); $original_channel = $f['original_channel']; if (!($fetch_url && $original_channel)) { continue; } if ($f['attach']) { $attachment_stored = false; foreach ($f['attach'] as $att) { convert_oldfields($att, 'data', 'content'); if ($att['deleted']) { attach_delete($channel, $att['hash']); continue; } $attach_exists = false; $x = attach_by_hash($att['hash']); logger('sync_files duplicate check: attach_exists=' . $attach_exists, LOGGER_DEBUG); logger('sync_files duplicate check: att=' . print_r($att, true), LOGGER_DEBUG); logger('sync_files duplicate check: attach_by_hash() returned ' . print_r($x, true), LOGGER_DEBUG); if ($x['success']) { $attach_exists = true; $attach_id = $x[0]['id']; } $newfname = 'store/' . $channel['channel_address'] . '/' . get_attach_binname($att['content']); unset($att['id']); $att['aid'] = $channel['channel_account_id']; $att['uid'] = $channel['channel_id']; // check for duplicate folder names with the same parent. // If we have a duplicate that doesn't match this hash value // change the name so that the contents won't be "covered over" // by the existing directory. Use the same logic we use for // duplicate files. if (strpos($att['filename'], '.') !== false) { $basename = substr($att['filename'], 0, strrpos($att['filename'], '.')); $ext = substr($att['filename'], strrpos($att['filename'], '.')); } else { $basename = $att['filename']; $ext = ''; } $r = q("select filename from attach where ( filename = '%s' OR filename like '%s' ) and folder = '%s' and hash != '%s' ", dbesc($basename . $ext), dbesc($basename . '(%)' . $ext), dbesc($att['folder']), dbesc($att['hash'])); if ($r) { $x = 1; do { $found = false; foreach ($r as $rr) { if ($rr['filename'] === $basename . '(' . $x . ')' . $ext) { $found = true; break; } } if ($found) { $x++; } } while ($found); $att['filename'] = $basename . '(' . $x . ')' . $ext; } else { $att['filename'] = $basename . $ext; } // end duplicate detection // @fixme - update attachment structures if they are modified rather than created $att['content'] = $newfname; // Note: we use $att['hash'] below after it has been escaped to // fetch the file contents. // If the hash ever contains any escapable chars this could cause // problems. Currently it does not. dbesc_array($att); if ($attach_exists) { logger('sync_files attach exists: ' . print_r($att, true), LOGGER_DEBUG); $str = ''; foreach ($att as $k => $v) { if ($str) { $str .= ","; } $str .= " `" . $k . "` = '" . $v . "' "; } $r = dbq("update `attach` set " . $str . " where id = " . intval($attach_id)); } else { logger('sync_files attach does not exists: ' . print_r($att, true), LOGGER_DEBUG); $r = dbq("INSERT INTO attach (`" . implode("`, `", array_keys($att)) . "`) VALUES ('" . implode("', '", array_values($att)) . "')"); } // is this a directory? if ($att['filetype'] === 'multipart/mixed' && $att['is_dir']) { os_mkdir($newfname, STORAGE_DEFAULT_PERMISSIONS, true); $attachment_stored = true; continue; } else { // it's a file // for the sync version of this algorithm (as opposed to 'offline import') // we will fetch the actual file from the source server so it can be // streamed directly to disk and avoid consuming PHP memory if it's a huge // audio/video file or something. $time = datetime_convert(); $parr = array('hash' => $channel['channel_hash'], 'time' => $time, 'resource' => $att['hash'], 'revision' => 0, 'signature' => base64url_encode(rsa_sign($channel['channel_hash'] . '.' . $time, $channel['channel_prvkey']))); $store_path = $newfname; $fp = fopen($newfname, 'w'); if (!$fp) { logger('failed to open storage file.', LOGGER_NORMAL, LOG_ERR); continue; } $redirects = 0; $x = z_post_url($fetch_url, $parr, $redirects, array('filep' => $fp)); fclose($fp); if ($x['success']) { $attachment_stored = true; } continue; } } } if (!$attachment_stored) { // @TODO should we queue this and retry or delete everything or what? logger('attachment store failed', LOGGER_NORMAL, LOG_ERR); } if ($f['photo']) { foreach ($f['photo'] as $p) { unset($p['id']); $p['aid'] = $channel['channel_account_id']; $p['uid'] = $channel['channel_id']; convert_oldfields($p, 'data', 'content'); convert_oldfields($p, 'scale', 'imgscale'); convert_oldfields($p, 'size', 'filesize'); convert_oldfields($p, 'type', 'mimetype'); // if this is a profile photo, undo the profile photo bit // for any other photo which previously held it. if ($p['photo_usage'] == PHOTO_PROFILE) { $e = q("update photo set photo_usage = %d where photo_usage = %d\n\t\t\t\t\t\t\tand resource_id != '%s' and uid = %d ", intval(PHOTO_NORMAL), intval(PHOTO_PROFILE), dbesc($p['resource_id']), intval($channel['channel_id'])); } // same for cover photos if ($p['photo_usage'] == PHOTO_COVER) { $e = q("update photo set photo_usage = %d where photo_usage = %d\n\t\t\t\t\t\t\tand resource_id != '%s' and uid = %d ", intval(PHOTO_NORMAL), intval(PHOTO_COVER), dbesc($p['resource_id']), intval($channel['channel_id'])); } if ($p['imgscale'] === 0 && $p['os_storage']) { $p['content'] = $store_path; } else { $p['content'] = base64_decode($p['content']); } $exists = q("select * from photo where resource_id = '%s' and imgscale = %d and uid = %d limit 1", dbesc($p['resource_id']), intval($p['imgscale']), intval($channel['channel_id'])); dbesc_array($p); if ($exists) { $str = ''; foreach ($p as $k => $v) { if ($str) { $str .= ","; } $str .= " `" . $k . "` = '" . $v . "' "; } $r = dbq("update `photo` set " . $str . " where id = " . intval($exists[0]['id'])); } else { $r = dbq("INSERT INTO photo (`" . implode("`, `", array_keys($p)) . "`) VALUES ('" . implode("', '", array_values($p)) . "')"); } } } if ($f['item']) { sync_items($channel, $f['item'], ['channel_address' => $original_channel, 'url' => $oldbase]); } } } }
function pubsubhubbub_subscribe($url, $channel, $xchan, $feed, $hubmode = 'subscribe') { $push_url = z_root() . '/pubsub/' . $channel['channel_address'] . '/' . $xchan['abook_id']; $verify = get_abconfig($channel['channel_id'], $xchan['xchan_hash'], 'pubsubhubbub', 'verify_token'); if (!$verify) { $verify = set_abconfig($channel['channel_id'], $xchan['xchan_hash'], 'pubsubhubbub', 'verify_token', random_string(16)); } if ($feed) { set_xconfig($xchan['xchan_hash'], 'system', 'feed_url', $feed); } else { $feed = get_xconfig($xchan['xchan_hash'], 'system', 'feed_url'); } $params = 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($feed) . '&hub.verify=async&hub.verify_token=' . $verify; logger('subscribe_to_hub: ' . $hubmode . ' ' . $xchan['xchan_name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify); $x = z_post_url($url, $params); logger('subscribe_to_hub: returns: ' . $x['return_code'], LOGGER_DEBUG); return; }
/** * @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; }
/** * @brief Refreshes after permission changed or friending, etc. * * zot_refresh is typically invoked when somebody has changed permissions of a channel and they are notified * to fetch new permissions via a finger/discovery operation. This may result in a new connection * (abook entry) being added to a local channel and it may result in auto-permissions being granted. * * Friending in zot is accomplished by sending a refresh packet to a specific channel which indicates a * permission change has been made by the sender which affects the target channel. The hub controlling * the target channel does targetted discovery (a zot-finger request requesting permissions for the local * channel). These are decoded here, and if necessary and abook structure (addressbook) is created to store * the permissions assigned to this channel. * * Initially these abook structures are created with a 'pending' flag, so that no reverse permissions are * implied until this is approved by the owner channel. A channel can also auto-populate permissions in * return and send back a refresh packet of its own. This is used by forum and group communication channels * so that friending and membership in the channel's "club" is automatic. * * @param array $them => xchan structure of sender * @param array $channel => local channel structure of target recipient, required for "friending" operations * @param array $force default false * * @returns boolean true if successful, else false */ function zot_refresh($them, $channel = null, $force = false) { if (array_key_exists('xchan_network', $them) && $them['xchan_network'] !== 'zot') { logger('zot_refresh: not got zot. ' . $them['xchan_name']); return true; } logger('zot_refresh: them: ' . print_r($them, true), LOGGER_DATA); if ($channel) { logger('zot_refresh: channel: ' . print_r($channel, true), LOGGER_DATA); } $url = null; if ($them['hubloc_url']) { $url = $them['hubloc_url']; } else { $r = null; // if they re-installed the server we could end up with the wrong record - pointing to the old install. // We'll order by reverse id to try and pick off the newest one first and hopefully end up with the // correct hubloc. If this doesn't work we may have to re-write this section to try them all. if (array_key_exists('xchan_addr', $them) && $them['xchan_addr']) { $r = q("select hubloc_url, hubloc_primary from hubloc where hubloc_addr = '%s' order by hubloc_id desc", dbesc($them['xchan_addr'])); } if (!$r) { $r = q("select hubloc_url, hubloc_primary from hubloc where hubloc_hash = '%s' order by hubloc_id desc", dbesc($them['xchan_hash'])); } if ($r) { foreach ($r as $rr) { if (intval($rr['hubloc_primary'])) { $url = $rr['hubloc_url']; break; } } if (!$url) { $url = $r[0]['hubloc_url']; } } } if (!$url) { logger('zot_refresh: no url'); return false; } $postvars = array(); if ($channel) { $postvars['target'] = $channel['channel_guid']; $postvars['target_sig'] = $channel['channel_guid_sig']; $postvars['key'] = $channel['channel_pubkey']; } if (array_key_exists('xchan_addr', $them) && $them['xchan_addr']) { $postvars['address'] = $them['xchan_addr']; } if (array_key_exists('xchan_hash', $them) && $them['xchan_hash']) { $postvars['guid_hash'] = $them['xchan_hash']; } if (array_key_exists('xchan_guid', $them) && $them['xchan_guid'] && array_key_exists('xchan_guid_sig', $them) && $them['xchan_guid_sig']) { $postvars['guid'] = $them['xchan_guid']; $postvars['guid_sig'] = $them['xchan_guid_sig']; } $rhs = '/.well-known/zot-info'; $result = z_post_url($url . $rhs, $postvars); logger('zot_refresh: zot-info: ' . print_r($result, true), LOGGER_DATA); if ($result['success']) { $j = json_decode($result['body'], true); if (!($j && $j['success'])) { logger('zot_refresh: result not decodable'); return false; } $x = import_xchan($j, $force ? UPDATE_FLAGS_FORCED : UPDATE_FLAGS_UPDATED); if (!$x['success']) { return false; } $their_perms = 0; if ($channel) { $global_perms = get_perms(); if ($j['permissions']['data']) { $permissions = crypto_unencapsulate(array('data' => $j['permissions']['data'], 'key' => $j['permissions']['key'], 'iv' => $j['permissions']['iv']), $channel['channel_prvkey']); if ($permissions) { $permissions = json_decode($permissions, true); } logger('decrypted permissions: ' . print_r($permissions, true), LOGGER_DATA); } else { $permissions = $j['permissions']; } $connected_set = false; if ($permissions && is_array($permissions)) { foreach ($permissions as $k => $v) { // The connected permission means you are in their address book if ($k === 'connected') { $connected_set = intval($v); continue; } if ($v && array_key_exists($k, $global_perms)) { $their_perms = $their_perms | intval($global_perms[$k][1]); } } } if (array_key_exists('profile', $j) && array_key_exists('next_birthday', $j['profile'])) { $next_birthday = datetime_convert('UTC', 'UTC', $j['profile']['next_birthday']); } else { $next_birthday = NULL_DATE; } $r = q("select * from abook where abook_xchan = '%s' and abook_channel = %d and abook_self = 0 limit 1", dbesc($x['hash']), intval($channel['channel_id'])); if ($r) { // connection exists // if the dob is the same as what we have stored (disregarding the year), keep the one // we have as we may have updated the year after sending a notification; and resetting // to the one we just received would cause us to create duplicated events. if (substr($r[0]['abook_dob'], 5) == substr($next_birthday, 5)) { $next_birthday = $r[0]['abook_dob']; } $current_abook_connected = intval($r[0]['abook_unconnected']) ? 0 : 1; $y = q("update abook set abook_their_perms = %d, abook_dob = '%s'\n\t\t\t\t\twhere abook_xchan = '%s' and abook_channel = %d\n\t\t\t\t\tand abook_self = 0 ", intval($their_perms), dbescdate($next_birthday), dbesc($x['hash']), intval($channel['channel_id'])); // if(($connected_set === 0 || $connected_set === 1) && ($connected_set !== $current_abook_unconnected)) { // if they are in your address book but you aren't in theirs, and/or this does not // match your current connected state setting, toggle it. /** @FIXME uncoverted to postgres */ /** @FIXME when this was enabled, all contacts became unconnected. Currently disabled intentionally */ // $y1 = q("update abook set abook_unconnected = 1 // where abook_xchan = '%s' and abook_channel = %d // and abook_self = 0 limit 1", // dbesc($x['hash']), // intval($channel['channel_id']) // ); // } if (!$y) { logger('abook update failed'); } else { // if we were just granted read stream permission and didn't have it before, try to pull in some posts if (!($r[0]['abook_their_perms'] & PERMS_R_STREAM) && $their_perms & PERMS_R_STREAM) { proc_run('php', 'include/onepoll.php', $r[0]['abook_id']); } } } else { // new connection $role = get_pconfig($channel['channel_id'], 'system', 'permissions_role'); if ($role) { $xx = get_role_perms($role); if ($xx['perms_auto']) { $default_perms = $xx['perms_accept']; } } if (!$default_perms) { $default_perms = intval(get_pconfig($channel['channel_id'], 'system', 'autoperms')); } // Keep original perms to check if we need to notify them $previous_perms = get_all_perms($channel['channel_id'], $x['hash']); $closeness = get_pconfig($channel['channel_id'], 'system', 'new_abook_closeness'); if ($closeness === false) { $closeness = 80; } $y = q("insert into abook ( abook_account, abook_channel, abook_closeness, abook_xchan, abook_their_perms, abook_my_perms, abook_created, abook_updated, abook_dob, abook_pending ) values ( %d, %d, %d, '%s', %d, %d, '%s', '%s', '%s', %d )", intval($channel['channel_account_id']), intval($channel['channel_id']), intval($closeness), dbesc($x['hash']), intval($their_perms), intval($default_perms), dbesc(datetime_convert()), dbesc(datetime_convert()), dbesc($next_birthday), intval($default_perms ? 0 : 1)); if ($y) { logger("New introduction received for {$channel['channel_name']}"); $new_perms = get_all_perms($channel['channel_id'], $x['hash']); // Send a clone sync packet and a permissions update if permissions have changed $new_connection = q("select * from abook left join xchan on abook_xchan = xchan_hash where abook_xchan = '%s' and abook_channel = %d and abook_self = 0 order by abook_created desc limit 1", dbesc($x['hash']), intval($channel['channel_id'])); if ($new_connection) { if ($new_perms != $previous_perms) { proc_run('php', 'include/notifier.php', 'permission_create', $new_connection[0]['abook_id']); } require_once 'include/enotify.php'; notification(array('type' => NOTIFY_INTRO, 'from_xchan' => $x['hash'], 'to_xchan' => $channel['channel_hash'], 'link' => z_root() . '/connedit/' . $new_connection[0]['abook_id'])); if ($their_perms & PERMS_R_STREAM) { if ($channel['channel_w_stream'] & PERMS_PENDING || !intval($new_connection[0]['abook_pending'])) { proc_run('php', 'include/onepoll.php', $new_connection[0]['abook_id']); } } unset($new_connection[0]['abook_id']); unset($new_connection[0]['abook_account']); unset($new_connection[0]['abook_channel']); build_sync_packet($channel['channel_id'], array('abook' => $new_connection)); } } } } return true; } return false; }
/** * @brief Like z_post_url() but with an application/json HTTP header. * * Add a "Content-Type: application/json" HTTP-header to $opts and call z_post_url(). * * @see z_post_url() * * @param string $url * @param array $params * @param number $redirects default 0 * @param array $opts (optional) curl options * @return z_post_url() */ function z_post_url_json($url, $params, $redirects = 0, $opts = array()) { $opts = array_merge($opts, array('headers' => array('Content-Type: application/json'))); return z_post_url($url, json_encode($params), $redirects, $opts); }
function deliver_run($argv, $argc) { cli_startup(); $a = get_app(); if ($argc < 2) { return; } logger('deliver: invoked: ' . print_r($argv, true), LOGGER_DATA); for ($x = 1; $x < $argc; $x++) { $r = q("select * from outq where outq_hash = '%s' limit 1", dbesc($argv[$x])); if ($r) { /** * Check to see if we have any recent communications with this hub (in the last month). * If not, reduce the outq_priority. */ $h = parse_url($r[0]['outq_posturl']); if ($h) { $base = $h['scheme'] . '://' . $h['host'] . ($h['port'] ? ':' . $h['port'] : ''); if ($base !== z_root()) { $y = q("select site_update, site_dead from site where site_url = '%s' ", dbesc($base)); if ($y) { if (intval($y[0]['site_dead'])) { q("delete from outq where outq_posturl = '%s'", dbesc($r[0]['outq_posturl'])); logger('dead site ignored ' . $base); continue; } if ($y[0]['site_update'] < datetime_convert('UTC', 'UTC', 'now - 1 month')) { q("update outq set outq_priority = %d where outq_hash = '%s'", intval($r[0]['outq_priority'] + 10), dbesc($r[0]['outq_hash'])); logger('immediate delivery deferred for site ' . $base); continue; } } } } // "post" queue driver - used for diaspora and friendica-over-diaspora communications. if ($r[0]['outq_driver'] === 'post') { $result = z_post_url($r[0]['outq_posturl'], $r[0]['outq_msg']); if ($result['success'] && $result['return_code'] < 300) { logger('deliver: queue post success to ' . $r[0]['outq_posturl'], LOGGER_DEBUG); $y = q("delete from outq where outq_hash = '%s'", dbesc($argv[$x])); } else { logger('deliver: queue post returned ' . $result['return_code'] . ' from ' . $r[0]['outq_posturl'], LOGGER_DEBUG); $y = q("update outq set outq_updated = '%s' where outq_hash = '%s'", dbesc(datetime_convert()), dbesc($argv[$x])); } continue; } $notify = json_decode($r[0]['outq_notify'], true); // Check if this is a conversation request packet. It won't have outq_msg // but will be an encrypted packet - so will need to be handed off to // web delivery rather than processed inline. $sendtoweb = false; if (array_key_exists('iv', $notify) && !$r[0]['outq_msg']) { $sendtoweb = true; } if ($r[0]['outq_posturl'] === z_root() . '/post' && !$sendtoweb) { logger('deliver: local delivery', LOGGER_DEBUG); // local delivery // we should probably batch these and save a few delivery processes if ($r[0]['outq_msg']) { $m = json_decode($r[0]['outq_msg'], true); if (array_key_exists('message_list', $m)) { foreach ($m['message_list'] as $mm) { $msg = array('body' => json_encode(array('success' => true, 'pickup' => array(array('notify' => $notify, 'message' => $mm))))); zot_import($msg, z_root()); } } else { $msg = array('body' => json_encode(array('success' => true, 'pickup' => array(array('notify' => $notify, 'message' => $m))))); zot_import($msg, z_root()); } $r = q("delete from outq where outq_hash = '%s'", dbesc($argv[$x])); } } else { logger('deliver: dest: ' . $r[0]['outq_posturl'], LOGGER_DEBUG); $result = zot_zot($r[0]['outq_posturl'], $r[0]['outq_notify']); if ($result['success']) { logger('deliver: remote zot delivery succeeded to ' . $r[0]['outq_posturl']); zot_process_response($r[0]['outq_posturl'], $result, $r[0]); } else { logger('deliver: remote zot delivery failed to ' . $r[0]['outq_posturl']); $y = q("update outq set outq_updated = '%s' where outq_hash = '%s'", dbesc(datetime_convert()), dbesc($argv[$x])); } } } } }
/** * @brief Refreshes after permission changed or friending, etc. * * zot_refresh is typically invoked when somebody has changed permissions of a channel and they are notified * to fetch new permissions via a finger/discovery operation. This may result in a new connection * (abook entry) being added to a local channel and it may result in auto-permissions being granted. * * Friending in zot is accomplished by sending a refresh packet to a specific channel which indicates a * permission change has been made by the sender which affects the target channel. The hub controlling * the target channel does targetted discovery (a zot-finger request requesting permissions for the local * channel). These are decoded here, and if necessary and abook structure (addressbook) is created to store * the permissions assigned to this channel. * * Initially these abook structures are created with a 'pending' flag, so that no reverse permissions are * implied until this is approved by the owner channel. A channel can also auto-populate permissions in * return and send back a refresh packet of its own. This is used by forum and group communication channels * so that friending and membership in the channel's "club" is automatic. * * @param array $them => xchan structure of sender * @param array $channel => local channel structure of target recipient, required for "friending" operations * @param array $force default false * * @returns boolean true if successful, else false */ function zot_refresh($them, $channel = null, $force = false) { if (array_key_exists('xchan_network', $them) && $them['xchan_network'] !== 'zot') { logger('zot_refresh: not got zot. ' . $them['xchan_name']); return true; } logger('zot_refresh: them: ' . print_r($them, true), LOGGER_DATA, LOG_DEBUG); if ($channel) { logger('zot_refresh: channel: ' . print_r($channel, true), LOGGER_DATA, LOG_DEBUG); } $url = null; if ($them['hubloc_url']) { $url = $them['hubloc_url']; } else { $r = null; // if they re-installed the server we could end up with the wrong record - pointing to the old install. // We'll order by reverse id to try and pick off the newest one first and hopefully end up with the // correct hubloc. If this doesn't work we may have to re-write this section to try them all. if (array_key_exists('xchan_addr', $them) && $them['xchan_addr']) { $r = q("select hubloc_url, hubloc_primary from hubloc where hubloc_addr = '%s' order by hubloc_id desc", dbesc($them['xchan_addr'])); } if (!$r) { $r = q("select hubloc_url, hubloc_primary from hubloc where hubloc_hash = '%s' order by hubloc_id desc", dbesc($them['xchan_hash'])); } if ($r) { foreach ($r as $rr) { if (intval($rr['hubloc_primary'])) { $url = $rr['hubloc_url']; break; } } if (!$url) { $url = $r[0]['hubloc_url']; } } } if (!$url) { logger('zot_refresh: no url'); return false; } $token = random_string(); $postvars = array(); $postvars['token'] = $token; if ($channel) { $postvars['target'] = $channel['channel_guid']; $postvars['target_sig'] = $channel['channel_guid_sig']; $postvars['key'] = $channel['channel_pubkey']; } if (array_key_exists('xchan_addr', $them) && $them['xchan_addr']) { $postvars['address'] = $them['xchan_addr']; } if (array_key_exists('xchan_hash', $them) && $them['xchan_hash']) { $postvars['guid_hash'] = $them['xchan_hash']; } if (array_key_exists('xchan_guid', $them) && $them['xchan_guid'] && array_key_exists('xchan_guid_sig', $them) && $them['xchan_guid_sig']) { $postvars['guid'] = $them['xchan_guid']; $postvars['guid_sig'] = $them['xchan_guid_sig']; } $rhs = '/.well-known/zot-info'; $result = z_post_url($url . $rhs, $postvars); logger('zot_refresh: zot-info: ' . print_r($result, true), LOGGER_DATA, LOG_DEBUG); if ($result['success']) { $j = json_decode($result['body'], true); if (!($j && $j['success'])) { logger('zot_refresh: result not decodable'); return false; } $signed_token = is_array($j) && array_key_exists('signed_token', $j) ? $j['signed_token'] : null; if ($signed_token) { $valid = rsa_verify('token.' . $token, base64url_decode($signed_token), $j['key']); if (!$valid) { logger('invalid signed token: ' . $url . $rhs, LOGGER_NORMAL, LOG_ERR); return false; } } 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 false; } } $x = import_xchan($j, $force ? UPDATE_FLAGS_FORCED : UPDATE_FLAGS_UPDATED); if (!$x['success']) { return false; } if ($channel) { if ($j['permissions']['data']) { $permissions = crypto_unencapsulate(array('data' => $j['permissions']['data'], 'key' => $j['permissions']['key'], 'iv' => $j['permissions']['iv']), $channel['channel_prvkey']); if ($permissions) { $permissions = json_decode($permissions, true); } logger('decrypted permissions: ' . print_r($permissions, true), LOGGER_DATA, LOG_DEBUG); } else { $permissions = $j['permissions']; } $connected_set = false; if ($permissions && is_array($permissions)) { $old_read_stream_perm = get_abconfig($channel['channel_id'], $x['hash'], 'their_perms', 'view_stream'); foreach ($permissions as $k => $v) { set_abconfig($channel['channel_id'], $x['hash'], 'their_perms', $k, $v); } } if (array_key_exists('profile', $j) && array_key_exists('next_birthday', $j['profile'])) { $next_birthday = datetime_convert('UTC', 'UTC', $j['profile']['next_birthday']); } else { $next_birthday = NULL_DATE; } $r = q("select * from abook where abook_xchan = '%s' and abook_channel = %d and abook_self = 0 limit 1", dbesc($x['hash']), intval($channel['channel_id'])); if ($r) { // connection exists // if the dob is the same as what we have stored (disregarding the year), keep the one // we have as we may have updated the year after sending a notification; and resetting // to the one we just received would cause us to create duplicated events. if (substr($r[0]['abook_dob'], 5) == substr($next_birthday, 5)) { $next_birthday = $r[0]['abook_dob']; } $y = q("update abook set abook_dob = '%s'\n\t\t\t\t\twhere abook_xchan = '%s' and abook_channel = %d\n\t\t\t\t\tand abook_self = 0 ", dbescdate($next_birthday), dbesc($x['hash']), intval($channel['channel_id'])); if (!$y) { logger('abook update failed'); } else { // if we were just granted read stream permission and didn't have it before, try to pull in some posts if (!$old_read_stream_perm && intval($permissions['view_stream'])) { Zotlabs\Daemon\Master::Summon(array('Onepoll', $r[0]['abook_id'])); } } } else { // new connection $my_perms = null; $automatic = false; $role = get_pconfig($channel['channel_id'], 'system', 'permissions_role'); if ($role) { $xx = \Zotlabs\Access\PermissionRoles::role_perms($role); if ($xx['perms_auto']) { $automatic = true; $default_perms = $xx['perms_connect']; $my_perms = \Zotlabs\Access\Permissions::FilledPerms($default_perms); } } if (!$my_perms) { $m = \Zotlabs\Access\Permissions::FilledAutoperms($channel['channel_id']); if ($m) { $automatic = true; $my_perms = $m; } } if ($my_perms) { foreach ($my_perms as $k => $v) { set_abconfig($channel['channel_id'], $x['hash'], 'my_perms', $k, $v); } } // Keep original perms to check if we need to notify them $previous_perms = get_all_perms($channel['channel_id'], $x['hash']); $closeness = get_pconfig($channel['channel_id'], 'system', 'new_abook_closeness'); if ($closeness === false) { $closeness = 80; } $y = q("insert into abook ( abook_account, abook_channel, abook_closeness, abook_xchan, abook_created, abook_updated, abook_dob, abook_pending ) values ( %d, %d, %d, '%s', '%s', '%s', '%s', %d )", intval($channel['channel_account_id']), intval($channel['channel_id']), intval($closeness), dbesc($x['hash']), dbesc(datetime_convert()), dbesc(datetime_convert()), dbesc($next_birthday), intval($automatic ? 0 : 1)); if ($y) { logger("New introduction received for {$channel['channel_name']}"); $new_perms = get_all_perms($channel['channel_id'], $x['hash']); // Send a clone sync packet and a permissions update if permissions have changed $new_connection = q("select * from abook left join xchan on abook_xchan = xchan_hash where abook_xchan = '%s' and abook_channel = %d and abook_self = 0 order by abook_created desc limit 1", dbesc($x['hash']), intval($channel['channel_id'])); if ($new_connection) { if (!\Zotlabs\Access\Permissions::PermsCompare($new_perms, $previous_perms)) { Zotlabs\Daemon\Master::Summon(array('Notifier', 'permission_create', $new_connection[0]['abook_id'])); } Zotlabs\Lib\Enotify::submit(array('type' => NOTIFY_INTRO, 'from_xchan' => $x['hash'], 'to_xchan' => $channel['channel_hash'], 'link' => z_root() . '/connedit/' . $new_connection[0]['abook_id'])); if (intval($permissions['view_stream'])) { if (intval(get_pconfig($channel['channel_id'], 'perm_limits', 'send_stream') & PERMS_PENDING) || !intval($new_connection[0]['abook_pending'])) { Zotlabs\Daemon\Master::Summon(array('Onepoll', $new_connection[0]['abook_id'])); } } /** If there is a default group for this channel, add this connection to it */ $default_group = $channel['channel_default_group']; if ($default_group) { require_once 'include/group.php'; $g = group_rec_byhash($channel['channel_id'], $default_group); if ($g) { group_add_member($channel['channel_id'], '', $x['hash'], $g['id']); } } unset($new_connection[0]['abook_id']); unset($new_connection[0]['abook_account']); unset($new_connection[0]['abook_channel']); $abconfig = load_abconfig($channel['channel_id'], $new_connection['abook_xchan']); if ($abconfig) { $new_connection['abconfig'] = $abconfig; } build_sync_packet($channel['channel_id'], array('abook' => $new_connection)); } } } } return true; } return false; }
function deliver_run($argv, $argc) { cli_startup(); $a = get_app(); if ($argc < 2) { return; } logger('deliver: invoked: ' . print_r($argv, true), LOGGER_DATA); for ($x = 1; $x < $argc; $x++) { $dresult = null; $r = q("select * from outq where outq_hash = '%s' limit 1", dbesc($argv[$x])); if ($r) { /** * Check to see if we have any recent communications with this hub (in the last month). * If not, reduce the outq_priority. */ $h = parse_url($r[0]['outq_posturl']); if ($h) { $base = $h['scheme'] . '://' . $h['host'] . ($h['port'] ? ':' . $h['port'] : ''); if ($base !== z_root()) { $y = q("select site_update, site_dead from site where site_url = '%s' ", dbesc($base)); if ($y) { if (intval($y[0]['site_dead'])) { q("delete from outq where outq_posturl = '%s'", dbesc($r[0]['outq_posturl'])); logger('dead site ignored ' . $base); continue; } if ($y[0]['site_update'] < datetime_convert('UTC', 'UTC', 'now - 1 month')) { q("update outq set outq_priority = %d where outq_hash = '%s'", intval($r[0]['outq_priority'] + 10), dbesc($r[0]['outq_hash'])); logger('immediate delivery deferred for site ' . $base); continue; } } else { // zot sites should all have a site record, unless they've been dead for as long as // your site has existed. Since we don't know for sure what these sites are, // call them unknown q("insert into site (site_url, site_update, site_dead, site_type) values ('%s','%s',0,%d) ", dbesc($base), dbesc(datetime_convert()), intval($r[0]['outq_driver'] === 'post' ? SITE_TYPE_NOTZOT : SITE_TYPE_UNKNOWN)); } } } // "post" queue driver - used for diaspora and friendica-over-diaspora communications. if ($r[0]['outq_driver'] === 'post') { $result = z_post_url($r[0]['outq_posturl'], $r[0]['outq_msg']); if ($result['success'] && $result['return_code'] < 300) { logger('deliver: queue post success to ' . $r[0]['outq_posturl'], LOGGER_DEBUG); q("update site set site_update = '%s', site_dead = 0 where site_url = '%s' ", dbesc(datetime_convert()), dbesc($site_url)); q("update dreport set status = '%s', dreport_time = '%s' where dreport_queue = '%s' limit 1", dbesc('accepted for delivery'), dbesc(datetime_convert()), dbesc($argv[$x])); $y = q("delete from outq where outq_hash = '%s'", dbesc($argv[$x])); } else { logger('deliver: queue post returned ' . $result['return_code'] . ' from ' . $r[0]['outq_posturl'], LOGGER_DEBUG); $y = q("update outq set outq_updated = '%s' where outq_hash = '%s'", dbesc(datetime_convert()), dbesc($argv[$x])); } continue; } $notify = json_decode($r[0]['outq_notify'], true); // Check if this is a conversation request packet. It won't have outq_msg // but will be an encrypted packet - so will need to be handed off to // web delivery rather than processed inline. $sendtoweb = false; if (array_key_exists('iv', $notify) && !$r[0]['outq_msg']) { $sendtoweb = true; } if ($r[0]['outq_posturl'] === z_root() . '/post' && !$sendtoweb) { logger('deliver: local delivery', LOGGER_DEBUG); // local delivery // we should probably batch these and save a few delivery processes if ($r[0]['outq_msg']) { $m = json_decode($r[0]['outq_msg'], true); if (array_key_exists('message_list', $m)) { foreach ($m['message_list'] as $mm) { $msg = array('body' => json_encode(array('success' => true, 'pickup' => array(array('notify' => $notify, 'message' => $mm))))); zot_import($msg, z_root()); } } else { $msg = array('body' => json_encode(array('success' => true, 'pickup' => array(array('notify' => $notify, 'message' => $m))))); $dresult = zot_import($msg, z_root()); } $r = q("delete from outq where outq_hash = '%s'", dbesc($argv[$x])); if ($dresult && is_array($dresult)) { foreach ($dresult as $xx) { if (is_array($xx) && array_key_exists('message_id', $xx)) { q("insert into dreport ( dreport_mid, dreport_site, dreport_recip, dreport_result, dreport_time, dreport_xchan ) values ( '%s', '%s','%s','%s','%s','%s' ) ", dbesc($xx['message_id']), dbesc($xx['location']), dbesc($xx['recipient']), dbesc($xx['status']), dbesc(datetime_convert($xx['date'])), dbesc($xx['sender'])); } } } q("delete from dreport where dreport_queue = '%s' limit 1", dbesc($argv[$x])); } } else { logger('deliver: dest: ' . $r[0]['outq_posturl'], LOGGER_DEBUG); $result = zot_zot($r[0]['outq_posturl'], $r[0]['outq_notify']); if ($result['success']) { logger('deliver: remote zot delivery succeeded to ' . $r[0]['outq_posturl']); zot_process_response($r[0]['outq_posturl'], $result, $r[0]); } else { logger('deliver: remote zot delivery failed to ' . $r[0]['outq_posturl']); $y = q("update outq set outq_updated = '%s' where outq_hash = '%s'", dbesc(datetime_convert()), dbesc($argv[$x])); } } } } }
/** * @function z_post_url * @param string $url * URL to post * @param mixed $params * The full data to post in a HTTP "POST" operation. This parameter can * either be passed as a urlencoded string like 'para1=val1¶2=val2&...' * or as an array with the field name as key and field data as value. If value * is an array, the Content-Type header will be set to multipart/form-data. * @param int $redirects = 0 * internal use, recursion counter * @param array $opts (optional parameters) * 'accept_content' => supply Accept: header with 'accept_content' as the value * 'timeout' => int seconds, default system config value or 60 seconds * 'http_auth' => username:password * 'novalidate' => do not validate SSL certs, default is to validate using our CA list * * @returns array * 'return_code' => HTTP return code or 0 if timeout or failure * 'success' => boolean true (if HTTP 2xx result) or false * 'header' => HTTP headers * 'body' => fetched content */ function z_post_url($url, $params, $redirects = 0, $opts = array()) { $ret = array('return_code' => 0, 'success' => false, 'header' => "", 'body' => ""); $ch = curl_init($url); if ($redirects > 8 || !$ch) { return ret; } @curl_setopt($ch, CURLOPT_HEADER, true); @curl_setopt($ch, CURLOPT_CAINFO, get_capath()); @curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); @curl_setopt($ch, CURLOPT_POST, 1); @curl_setopt($ch, CURLOPT_POSTFIELDS, $params); @curl_setopt($ch, CURLOPT_USERAGENT, "Red"); $ciphers = @get_config('system', 'curl_ssl_ciphers'); if ($ciphers) { @curl_setopt($ch, CURLOPT_SSL_CIPHER_LIST, $ciphers); } if (x($opts, 'accept_content')) { @curl_setopt($ch, CURLOPT_HTTPHEADER, array("Accept: " . $opts['accept_content'])); } if (x($opts, 'headers')) { @curl_setopt($ch, CURLOPT_HTTPHEADER, $opts['headers']); } if (x($opts, 'timeout') && intval($opts['timeout'])) { @curl_setopt($ch, CURLOPT_TIMEOUT, $opts['timeout']); } else { $curl_time = intval(get_config('system', 'curl_timeout')); @curl_setopt($ch, CURLOPT_TIMEOUT, $curl_time !== false ? $curl_time : 60); } if (x($opts, 'http_auth')) { // "username" . ':' . "password" @curl_setopt($ch, CURLOPT_USERPWD, $opts['http_auth']); } @curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, x($opts, 'novalidate') && intval($opts['novalidate']) ? false : true); $prx = get_config('system', 'proxy'); if (strlen($prx)) { @curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1); @curl_setopt($ch, CURLOPT_PROXY, $prx); $prxusr = get_config('system', 'proxyuser'); if (strlen($prxusr)) { @curl_setopt($ch, CURLOPT_PROXYUSERPWD, $prxusr); } } // don't let curl abort the entire application // if it throws any errors. $s = @curl_exec($ch); $base = $s; $curl_info = @curl_getinfo($ch); $http_code = $curl_info['http_code']; $header = ''; // Pull out multiple headers, e.g. proxy and continuation headers // allow for HTTP/2.x without fixing code while (preg_match('/^HTTP\\/[1-2].+? [1-5][0-9][0-9]/', $base)) { $chunk = substr($base, 0, strpos($base, "\r\n\r\n") + 4); $header .= $chunk; $base = substr($base, strlen($chunk)); } if ($http_code == 301 || $http_code == 302 || $http_code == 303 || $http_code == 307 || $http_code == 308) { $matches = array(); preg_match('/(Location:|URI:)(.*?)\\n/', $header, $matches); $newurl = trim(array_pop($matches)); if (strpos($newurl, '/') === 0) { $newurl = $url . $newurl; } $url_parsed = @parse_url($newurl); if (isset($url_parsed)) { curl_close($ch); if ($http_code == 303) { return z_fetch_url($newurl, false, $redirects++, $opts); } else { return z_post_url($newurl, $params, $redirects++, $opts); } } } $rc = intval($http_code); $ret['return_code'] = $rc; $ret['success'] = $rc >= 200 && $rc <= 299 ? true : false; if (!$ret['success']) { $ret['error'] = curl_error($ch); $ret['debug'] = $curl_info; logger('z_post_url: error: ' . $url . ': ' . $ret['error'], LOGGER_DEBUG); logger('z_post_url: debug: ' . print_r($curl_info, true), LOGGER_DATA); } $ret['body'] = substr($s, strlen($header)); $ret['header'] = $header; curl_close($ch); return $ret; }
function jappixmini_cron(&$a, $d) { require_once 'include/Contact.php'; // For autosubscribe/autoapprove, we need to maintain a list of jabber addresses of our contacts. set_config("jappixmini", "last_cron_execution", $d); // go through list of users with jabber enabled $users = q("SELECT uid FROM pconfig WHERE cat = 'jappixmini' AND ( k = 'autosubscribe' OR k = 'autoapprove') AND v = '1' group by uid "); logger("jappixmini: Update list of contacts' jabber accounts for " . count($users) . " users."); if (!count($users)) { return; } foreach ($users as $row) { $uid = $row["uid"]; // for each user, go through list of contacts $rand = db_getfunc('rand'); $contacts = q("SELECT * FROM `abook` left join xchan on abook_xchan = xchan_hash WHERE `abook_channel`=%d AND not (abook_flags & %d) > 0 order by {$rand}", intval($uid), intval(ABOOK_FLAG_SELF)); $channel = channelx_by_n($uid); if (!$channel || !$contacts) { continue; } foreach ($contacts as $contact_row) { $xchan_hash = $contact_row["abook_xchan"]; $pubkey = $contact_row["xchan_pubkey"]; // check if jabber address already present $present = get_pconfig($uid, "jappixmini", "id:" . $xchan_hash); $now = intval(time()); if ($present) { // $present has format "timestamp:jabber_address" $p = strpos($present, ":"); $timestamp = intval(substr($present, 0, $p)); // do not re-retrieve jabber address if last retrieval // is not older than a week if ($now - $timestamp < 3600 * 24 * 7) { continue; } } logger('jappixmini: checking ' . $contact_row['xchan_name'] . ' for channel ' . $channel['channel_name']); // construct base retrieval address $pos = strpos($contact_row['xchan_connurl'], "/poco/"); if ($pos === false) { continue; } $url = substr($contact_row['xchan_connurl'], 0, $pos) . "/jappixmini?f="; // construct own address $username = get_pconfig($uid, 'jappixmini', 'username'); if (!$username) { continue; } $server = get_pconfig($uid, 'jappixmini', 'server'); if (!$server) { continue; } $address = $username . "@" . $server; // sign address $signed_address = ""; openssl_private_encrypt($address, $signed_address, $channel['channel_prvkey']); // construct request url $signed_address_hex = base64url_encode($signed_address); $postvars = array('address' => $signed_address, 'requestor' => $channel['xchan_hash'], 'requestee' => $contact_row['xchan_hash']); try { // send request $answer_json = z_post_url($url, $postvars); logger('jappixmini: url response: ' . print_r($answer_json, true)); if (!$answer_json['success']) { logger('jappixmini: failed z_post_url ' . $url); throw new Exception(); } if ($answer_json['return_code'] == 404) { logger('jappixmini: failed z_post_url (404)' . $url); throw new Exception(); } // parse answer $answer = json_decode($answer_json['body'], true); if ($answer['status'] != "ok") { throw new Exception(); } $address = base64url_decode($answer['address']); if (!$address) { throw new Exception(); } // decrypt address $decrypted_address = ""; openssl_public_decrypt($address, $decrypted_address, $pubkey); if (!$decrypted_address) { throw new Exception(); } } catch (Exception $e) { $decrypted_address = ""; } // save address set_pconfig($uid, "jappixmini", "id:" . $xchan_hash, "{$now}:{$decrypted_address}"); } } }
function slapper($owner, $url, $slap) { // does contact have a salmon endpoint? if (!strlen($url)) { return; } if (!$owner['channel_prvkey']) { logger(sprintf("channel '%s' (%d) does not have a salmon private key. Send failed.", $owner['channel_address'], $owner['channel_id'])); return; } logger('slapper called for ' . $url . '. Data: ' . $slap, LOGGER_DATA, LOG_DEBUG); // create a magic envelope $data = base64url_encode($slap); $data_type = 'application/atom+xml'; $encoding = 'base64url'; $algorithm = 'RSA-SHA256'; $keyhash = base64url_encode(hash('sha256', salmon_key($owner['channel_pubkey'])), true); // precomputed base64url encoding of data_type, encoding, algorithm concatenated with periods $precomputed = '.YXBwbGljYXRpb24vYXRvbSt4bWw=.YmFzZTY0dXJs.UlNBLVNIQTI1Ng=='; $signature = base64url_encode(rsa_sign(str_replace('=', '', $data . $precomputed), $owner['channel_prvkey'])); $signature2 = base64url_encode(rsa_sign($data . $precomputed, $owner['channel_prvkey'])); $signature3 = base64url_encode(rsa_sign($data, $owner['channel_prvkey'])); $salmon_tpl = get_markup_template('magicsig.tpl'); $salmon = replace_macros($salmon_tpl, array('$data' => $data, '$encoding' => $encoding, '$algorithm' => $algorithm, '$keyhash' => $keyhash, '$signature' => $signature)); // slap them $redirects = 0; $ret = z_post_url($url, $salmon, $redirects, array('headers' => array('Content-type: application/magic-envelope+xml', 'Content-length: ' . strlen($salmon)))); $return_code = $ret['return_code']; // check for success, e.g. 2xx if ($return_code > 299) { logger('compliant salmon failed. Falling back to status.net hack2'); // Entirely likely that their salmon implementation is // non-compliant. Let's try once more, this time only signing // the data, without stripping '=' chars $salmon = replace_macros($salmon_tpl, array('$data' => $data, '$encoding' => $encoding, '$algorithm' => $algorithm, '$keyhash' => $keyhash, '$signature' => $signature2)); $redirects = 0; $ret = z_post_url($url, $salmon, $redirects, array('headers' => array('Content-type: application/magic-envelope+xml', 'Content-length: ' . strlen($salmon)))); $return_code = $ret['return_code']; if ($return_code > 299) { logger('compliant salmon failed. Falling back to status.net hack3'); // Entirely likely that their salmon implementation is // non-compliant. Let's try once more, this time only signing // the data, without the precomputed blob $salmon = replace_macros($salmon_tpl, array('$data' => $data, '$encoding' => $encoding, '$algorithm' => $algorithm, '$keyhash' => $keyhash, '$signature' => $signature3)); $redirects = 0; $ret = z_post_url($url, $salmon, $redirects, array('headers' => array('Content-type: application/magic-envelope+xml', 'Content-length: ' . strlen($salmon)))); $return_code = $ret['return_code']; } } logger('slapper for ' . $url . ' returned ' . $return_code); if (!$return_code) { return -1; } if ($return_code == 503 && stristr($ret['header'], 'retry-after')) { return -1; } return $return_code >= 200 && $return_code < 300 ? 0 : 1; }
function libertree_send(&$a, &$b) { if (!is_item_normal($b) || $b['item_private'] || $b['created'] !== $b['edited']) { return; } if (!perm_is_allowed($b['uid'], '', 'view_stream')) { return; } if (!strstr($b['postopts'], 'libertree')) { return; } if ($b['parent'] != $b['id']) { return; } logger('libertree xpost invoked'); $ltree_api_token = get_pconfig($b['uid'], 'libertree', 'libertree_api_token'); $ltree_url = get_pconfig($b['uid'], 'libertree', 'libertree_url'); $ltree_blog = "{$ltree_url}/api/v1/posts/create/?token={$ltree_api_token}"; $ltree_source = "[" . $a->config['system']['sitename'] . "](" . $a->get_baseurl() . ")"; // $ltree_source = "RedMatrix"; logger('sitename: ' . print_r($ltree_source, true)); if ($ltree_url && $ltree_api_token && $ltree_blog && $ltree_source) { require_once 'include/bb2diaspora.php'; $tag_arr = array(); $tags = ''; $x = preg_match_all('/\\#\\[(.*?)\\](.*?)\\[/', $b['tag'], $matches, PREG_SET_ORDER); if ($x) { foreach ($matches as $mtch) { $tag_arr[] = $mtch[2]; } } if (count($tag_arr)) { $tags = implode(',', $tag_arr); } $title = $b['title']; $body = $b['body']; // Insert a newline before and after a quote $body = str_ireplace("[quote", "\n\n[quote", $body); $body = str_ireplace("[/quote]", "[/quote]\n\n", $body); // Removal of tags and mentions // #-tags $body = preg_replace('/#\\[url\\=(\\w+.*?)\\](\\w+.*?)\\[\\/url\\]/i', '#$2', $body); // @-mentions $body = preg_replace('/@\\[url\\=(\\w+.*?)\\](\\w+.*?)\\[\\/url\\]/i', '@$2', $body); // remove multiple newlines do { $oldbody = $body; $body = str_replace("\n\n\n", "\n\n", $body); } while ($oldbody != $body); // convert to markdown $body = bb2diaspora($body, false, false); // Adding the title if (strlen($title)) { $body = "## " . html_entity_decode($title) . "\n\n" . $body; } $params = array('text' => $body, 'source' => $ltree_source); $level = 0; $result = z_post_url($ltree_blog, $params, $level, array('novalidate' => true)); logger('libertree: ' . print_r($result, true)); } }
function rtof_post_hook(&$a, &$b) { /** * Post to Friendica */ // for now, just top level posts. if ($b['mid'] != $b['parent_mid']) { return; } if (!is_item_normal($b) || $b['item_private'] || $b['created'] !== $b['edited']) { return; } if (!perm_is_allowed($b['uid'], '', 'view_stream')) { return; } if (!strstr($b['postopts'], 'rtof')) { return; } logger('Red-to-Friendica post invoked'); load_pconfig($b['uid'], 'rtof'); $api = get_pconfig($b['uid'], 'rtof', 'baseapi'); if (substr($api, -1, 1) != '/') { $api .= '/'; } $username = get_pconfig($b['uid'], 'rtof', 'username'); $password = z_unobscure(get_pconfig($b['uid'], 'rtof', 'password')); $msg = $b['body']; $postdata = array('status' => $b['body'], 'title' => $b['title'], 'message_id' => $b['mid'], 'source' => 'Red Matrix'); if (strlen($b['body'])) { $ret = z_post_url($api . 'statuses/update', $postdata, 0, array('http_auth' => $username . ':' . $password, 'novalidate' => 1)); if ($ret['success']) { logger('rtof: returns: ' . print_r($ret['body'], true)); } else { logger('rtof: z_post_url failed: ' . print_r($ret['debug'], true)); } } }
function queue_run($argv, $argc) { cli_startup(); global $a; require_once 'include/items.php'; require_once 'include/bbcode.php'; if (argc() > 1) { $queue_id = argv(1); } else { $queue_id = 0; } $deadguys = array(); logger('queue: start'); $r = q("select outq_posturl from outq where outq_created < %s - INTERVAL %s", db_utcnow(), db_quoteinterval('3 DAY')); if ($r) { foreach ($r as $rr) { $site_url = ''; $h = parse_url($rr['outq_posturl']); $desturl = $h['scheme'] . '://' . $h['host'] . ($h['port'] ? ':' . $h['port'] : ''); q("update site set site_dead = 1 where site_dead = 0 and site_url = '%s' and site_update < %s - INTERVAL %s", dbesc($desturl), db_utcnow(), db_quoteinterval('1 MONTH')); } } $r = q("DELETE FROM outq WHERE outq_created < %s - INTERVAL %s", db_utcnow(), db_quoteinterval('3 DAY')); if ($queue_id) { $r = q("SELECT * FROM outq WHERE outq_hash = '%s' LIMIT 1", dbesc($queue_id)); } else { // For the first 12 hours we'll try to deliver every 15 minutes // After that, we'll only attempt delivery once per hour. // This currently only handles the default queue drivers ('zot' or '') which we will group by posturl // so that we don't start off a thousand deliveries for a couple of dead hubs. // The zot driver will deliver everything destined for a single hub once contact is made (*if* contact is made). // Other drivers will have to do something different here and may need their own query. // Note: this requires some tweaking as new posts to long dead hubs once a day will keep them in the // "every 15 minutes" category. We probably need to prioritise them when inserted into the queue // or just prior to this query based on recent and long-term delivery history. If we have good reason to believe // the site is permanently down, there's no reason to attempt delivery at all, or at most not more than once // or twice a day. // FIXME: can we sort postgres on outq_priority and maintain the 'distinct' ? // The order by max(outq_priority) might be a dodgy query because of the group by. // The desired result is to return a sequence in the order most likely to be delivered in this run. // If a hub has already been sitting in the queue for a few days, they should be delivered last; // hence every failure should drop them further down the priority list. if (ACTIVE_DBTYPE == DBTYPE_POSTGRES) { $prefix = 'DISTINCT ON (outq_posturl)'; $suffix = 'ORDER BY outq_posturl'; } else { $prefix = ''; $suffix = 'GROUP BY outq_posturl ORDER BY max(outq_priority)'; } $r = q("SELECT {$prefix} * FROM outq WHERE outq_delivered = 0 and (( outq_created > %s - INTERVAL %s and outq_updated < %s - INTERVAL %s ) OR ( outq_updated < %s - INTERVAL %s )) {$suffix}", db_utcnow(), db_quoteinterval('12 HOUR'), db_utcnow(), db_quoteinterval('15 MINUTE'), db_utcnow(), db_quoteinterval('1 HOUR')); } if (!$r) { return; } foreach ($r as $rr) { if (in_array($rr['outq_posturl'], $deadguys)) { continue; } if ($rr['outq_driver'] === 'post') { $result = z_post_url($rr['outq_posturl'], $rr['outq_msg']); if ($result['success'] && $result['return_code'] < 300) { logger('queue: queue post success to ' . $rr['outq_posturl'], LOGGER_DEBUG); $y = q("delete from outq where outq_hash = '%s'", dbesc($rr['ouq_hash'])); } else { logger('queue: queue post returned ' . $result['return_code'] . ' from ' . $rr['outq_posturl'], LOGGER_DEBUG); $y = q("update outq set outq_updated = '%s', outq_priority = outq_priority + 10 where outq_hash = '%s'", dbesc(datetime_convert()), dbesc($rr['outq_hash'])); } continue; } $result = zot_zot($rr['outq_posturl'], $rr['outq_notify']); if ($result['success']) { logger('queue: deliver zot success to ' . $rr['outq_posturl'], LOGGER_DEBUG); zot_process_response($rr['outq_posturl'], $result, $rr); } else { $deadguys[] = $rr['outq_posturl']; logger('queue: deliver zot returned ' . $result['return_code'] . ' from ' . $rr['outq_posturl'], LOGGER_DEBUG); $y = q("update outq set outq_updated = '%s', outq_priority = outq_priority + 10 where outq_hash = '%s'", dbesc(datetime_convert()), dbesc($rr['outq_hash'])); } } }
function dwpost_send(&$a, &$b) { if (!is_item_normal($b) || $b['item_private'] || $b['created'] !== $b['edited']) { return; } if (!perm_is_allowed($b['uid'], '', 'view_stream')) { return; } if (!strstr($b['postopts'], 'dwpost')) { return; } if ($b['parent'] != $b['id']) { return; } // dreamwidth post in the LJ user's timezone. // Hopefully the person's Friendica account // will be set to the same thing. $tz = 'UTC'; $x = q("select channel_timezone from channel where channel_id = %d limit 1", intval($b['uid'])); if ($x && strlen($x[0]['channel_timezone'])) { $tz = $x[0]['channel_timezone']; } $dw_username = get_pconfig($b['uid'], 'dwpost', 'dw_username'); $dw_password = z_unobscure(get_pconfig($b['uid'], 'dwpost', 'dw_password')); $dw_blog = 'http://www.dreamwidth.org/interface/xmlrpc'; if ($dw_username && $dw_password && $dw_blog) { require_once 'include/bbcode.php'; require_once 'include/datetime.php'; $title = $b['title']; $post = bbcode($b['body']); $post = xmlify($post); $tags = dwpost_get_tags($b['tag']); $date = datetime_convert('UTC', $tz, $b['created'], 'Y-m-d H:i:s'); $year = intval(substr($date, 0, 4)); $mon = intval(substr($date, 5, 2)); $day = intval(substr($date, 8, 2)); $hour = intval(substr($date, 11, 2)); $min = intval(substr($date, 14, 2)); $xml = <<<EOT <?xml version="1.0" encoding="utf-8"?> <methodCall><methodName>LJ.XMLRPC.postevent</methodName> <params><param> <value><struct> <member><name>year</name><value><int>{$year}</int></value></member> <member><name>mon</name><value><int>{$mon}</int></value></member> <member><name>day</name><value><int>{$day}</int></value></member> <member><name>hour</name><value><int>{$hour}</int></value></member> <member><name>min</name><value><int>{$min}</int></value></member> <member><name>event</name><value><string>{$post}</string></value></member> <member><name>username</name><value><string>{$dw_username}</string></value></member> <member><name>password</name><value><string>{$dw_password}</string></value></member> <member><name>subject</name><value><string>{$title}</string></value></member> <member><name>lineendings</name><value><string>unix</string></value></member> <member><name>ver</name><value><int>1</int></value></member> <member><name>props</name> <value><struct> <member><name>useragent</name><value><string>Friendica</string></value></member> <member><name>taglist</name><value><string>{$tags}</string></value></member> </struct></value></member> </struct></value> </param></params> </methodCall> EOT; logger('dwpost: data: ' . $xml, LOGGER_DATA); if ($dw_blog !== 'test') { $x = z_post_url($dw_blog, $xml, array('headers' => array("Content-Type: text/xml"))); } logger('posted to dreamwidth: ' . print_r($x, true), LOGGER_DEBUG); } }
function queue_deliver($outq, $immediate = false) { $base = null; $h = parse_url($outq['outq_posturl']); if ($h) { $base = $h['scheme'] . '://' . $h['host'] . ($h['port'] ? ':' . $h['port'] : ''); } if ($base && $base !== z_root() && $immediate) { $y = q("select site_update, site_dead from site where site_url = '%s' ", dbesc($base)); if ($y) { if (intval($y[0]['site_dead'])) { remove_queue_by_posturl($outq['outq_posturl']); logger('dead site ignored ' . $base); return; } if ($y[0]['site_update'] < datetime_convert('UTC', 'UTC', 'now - 1 month')) { update_queue_item($outq['outq_hash'], 10); logger('immediate delivery deferred for site ' . $base); return; } } else { // zot sites should all have a site record, unless they've been dead for as long as // your site has existed. Since we don't know for sure what these sites are, // call them unknown q("insert into site (site_url, site_update, site_dead, site_type) values ('%s','%s',0,%d) ", dbesc($base), dbesc(datetime_convert()), intval($outq['outq_driver'] === 'post' ? SITE_TYPE_NOTZOT : SITE_TYPE_UNKNOWN)); } } $arr = array('outq' => $outq, 'base' => $base, 'handled' => false, 'immediate' => $immediate); call_hooks('queue_deliver', $arr); if ($arr['handled']) { return; } // "post" queue driver - used for diaspora and friendica-over-diaspora communications. if ($outq['outq_driver'] === 'post') { $result = z_post_url($outq['outq_posturl'], $outq['outq_msg']); if ($result['success'] && $result['return_code'] < 300) { logger('deliver: queue post success to ' . $outq['outq_posturl'], LOGGER_DEBUG); if ($base) { q("update site set site_update = '%s', site_dead = 0 where site_url = '%s' ", dbesc(datetime_convert()), dbesc($base)); } q("update dreport set dreport_result = '%s', dreport_time = '%s' where dreport_queue = '%s' limit 1", dbesc('accepted for delivery'), dbesc(datetime_convert()), dbesc($outq['outq_hash'])); remove_queue_item($outq['outq_hash']); // server is responding - see if anything else is going to this destination and is piled up // and try to send some more. We're relying on the fact that delivery_loop() results in an // immediate delivery otherwise we could get into a queue loop. if (!$immediate) { $x = q("select outq_hash from outq where outq_posturl = '%s' and outq_delivered = 0", dbesc($outq['outq_posturl'])); $piled_up = array(); if ($x) { foreach ($x as $xx) { $piled_up[] = $xx['outq_hash']; } } if ($piled_up) { delivery_loop($piled_up); } } } else { logger('deliver: queue post returned ' . $result['return_code'] . ' from ' . $outq['outq_posturl'], LOGGER_DEBUG); update_queue_item($outq['outq_posturl']); } return; } // normal zot delivery logger('deliver: dest: ' . $outq['outq_posturl'], LOGGER_DEBUG); $result = zot_zot($outq['outq_posturl'], $outq['outq_notify']); if ($result['success']) { logger('deliver: remote zot delivery succeeded to ' . $outq['outq_posturl']); zot_process_response($outq['outq_posturl'], $result, $outq); } else { logger('deliver: remote zot delivery failed to ' . $outq['outq_posturl']); logger('deliver: remote zot delivery fail data: ' . print_r($result, true), LOGGER_DATA); update_queue_item($outq['outq_hash'], 10); } return; }
function gnusoc_queue_deliver(&$a, &$b) { $outq = $b['outq']; if ($outq['outq_driver'] !== 'slap') { return; } $b['handled'] = true; $headers = array('Content-type: application/magic-envelope+xml', 'Content-length: ' . strlen($outq['outq_msg'])); $counter = 0; $result = z_post_url($outq['outq_posturl'], $outq['outq_msg'], $counter, array('headers' => $headers, 'novalidate' => true)); if ($result['success'] && $result['return_code'] < 300) { logger('slap_deliver: queue post success to ' . $outq['outq_posturl'], LOGGER_DEBUG); if ($b['base']) { q("update site set site_update = '%s', site_dead = 0 where site_url = '%s' ", dbesc(datetime_convert()), dbesc($b['base'])); } q("update dreport set dreport_result = '%s', dreport_time = '%s' where dreport_queue = '%s' limit 1", dbesc('accepted for delivery'), dbesc(datetime_convert()), dbesc($outq['outq_hash'])); remove_queue_item($outq['outq_hash']); } else { logger('slap_deliver: queue post returned ' . $result['return_code'] . ' from ' . $outq['outq_posturl'], LOGGER_DEBUG); update_queue_item($outq['outq_hash']); } return; }
/** * @function: zot_refresh($them, $channel = null, $force = false) * * zot_refresh is typically invoked when somebody has changed permissions of a channel and they are notified * to fetch new permissions via a finger/discovery operation. This may result in a new connection * (abook entry) being added to a local channel and it may result in auto-permissions being granted. * * Friending in zot is accomplished by sending a refresh packet to a specific channel which indicates a * permission change has been made by the sender which affects the target channel. The hub controlling * the target channel does targetted discovery (a zot-finger request requesting permissions for the local * channel). These are decoded here, and if necessary and abook structure (addressbook) is created to store * the permissions assigned to this channel. * * Initially these abook structures are created with a 'pending' flag, so that no reverse permissions are * implied until this is approved by the owner channel. A channel can also auto-populate permissions in * return and send back a refresh packet of its own. This is used by forum and group communication channels * so that friending and membership in the channel's "club" is automatic. * * @param array $them => xchan structure of sender * @param array $channel => local channel structure of target recipient, required for "friending" operations * * @returns boolean true if successful, else false */ function zot_refresh($them, $channel = null, $force = false) { if (array_key_exists('xchan_network', $them) && $them['xchan_network'] !== 'zot') { logger('zot_refresh: not got zot. ' . $them['xchan_name']); return true; } logger('zot_refresh: them: ' . print_r($them, true), LOGGER_DATA); if ($channel) { logger('zot_refresh: channel: ' . print_r($channel, true), LOGGER_DATA); } if ($them['hubloc_url']) { $url = $them['hubloc_url']; } else { $r = q("select hubloc_url from hubloc where hubloc_hash = '%s' and ( hubloc_flags & %d ) limit 1", dbesc($them['xchan_hash']), intval(HUBLOC_FLAGS_PRIMARY)); if ($r) { $url = $r[0]['hubloc_url']; } } if (!$url) { logger('zot_refresh: no url'); return false; } $postvars = array(); if ($channel) { $postvars['target'] = $channel['channel_guid']; $postvars['target_sig'] = $channel['channel_guid_sig']; $postvars['key'] = $channel['channel_pubkey']; } if (array_key_exists('xchan_addr', $them) && $them['xchan_addr']) { $postvars['address'] = $them['xchan_addr']; } if (array_key_exists('xchan_hash', $them) && $them['xchan_hash']) { $postvars['guid_hash'] = $them['xchan_hash']; } if (array_key_exists('xchan_guid', $them) && $them['xchan_guid'] && array_key_exists('xchan_guid_sig', $them) && $them['xchan_guid_sig']) { $postvars['guid'] = $them['xchan_guid']; $postvars['guid_sig'] = $them['xchan_guid_sig']; } $rhs = '/.well-known/zot-info'; $result = z_post_url($url . $rhs, $postvars); logger('zot_refresh: zot-info: ' . print_r($result, true), LOGGER_DATA); if ($result['success']) { $j = json_decode($result['body'], true); if (!($j && $j['success'])) { logger('zot_refresh: result not decodable'); return false; } $x = import_xchan($j, $force ? UPDATE_FLAGS_FORCED : UPDATE_FLAGS_UPDATED); if (!$x['success']) { return false; } $their_perms = 0; if ($channel) { $global_perms = get_perms(); if ($j['permissions']['data']) { $permissions = crypto_unencapsulate(array('data' => $j['permissions']['data'], 'key' => $j['permissions']['key'], 'iv' => $j['permissions']['iv']), $channel['channel_prvkey']); if ($permissions) { $permissions = json_decode($permissions, true); } logger('decrypted permissions: ' . print_r($permissions, true), LOGGER_DATA); } else { $permissions = $j['permissions']; } $connected_set = false; if ($permissions && is_array($permissions)) { foreach ($permissions as $k => $v) { // The connected permission means you are in their address book if ($k === 'connected') { $connected_set = intval($v); continue; } if ($v && array_key_exists($k, $global_perms)) { $their_perms = $their_perms | intval($global_perms[$k][1]); } } } $r = q("select * from abook where abook_xchan = '%s' and abook_channel = %d and not (abook_flags & %d) limit 1", dbesc($x['hash']), intval($channel['channel_id']), intval(ABOOK_FLAG_SELF)); if (array_key_exists('profile', $j) && array_key_exists('next_birthday', $j['profile'])) { $next_birthday = datetime_convert('UTC', 'UTC', $j['profile']['next_birthday']); } else { $next_birthday = NULL_DATE; } if ($r) { // if the dob is the same as what we have stored (disregarding the year), keep the one // we have as we may have updated the year after sending a notification; and resetting // to the one we just received would cause us to create duplicated events. if (substr($r[0]['abook_dob'], 5) == substr($next_birthday, 5)) { $next_birthday = $r[0]['abook_dob']; } $current_abook_connected = $r[0]['abook_flags'] & ABOOK_FLAG_UNCONNECTED ? 0 : 1; $y = q("update abook set abook_their_perms = %d, abook_dob = '%s'\n\t\t\t\t\twhere abook_xchan = '%s' and abook_channel = %d \n\t\t\t\t\tand not (abook_flags & %d) limit 1", intval($their_perms), dbesc($next_birthday), dbesc($x['hash']), intval($channel['channel_id']), intval(ABOOK_FLAG_SELF)); // if(($connected_set === 0 || $connected_set === 1) && ($connected_set !== $current_abook_unconnected)) { // if they are in your address book but you aren't in theirs, and/or this does not // match your current connected state setting, toggle it. // $y1 = q("update abook set abook_flags = (abook_flags ^ %d) // where abook_xchan = '%s' and abook_channel = %d // and not (abook_flags & %d) limit 1", // intval(ABOOK_FLAG_UNCONNECTED), // dbesc($x['hash']), // intval($channel['channel_id']), // intval(ABOOK_FLAG_SELF) // ); // } if (!$y) { logger('abook update failed'); } else { // if we were just granted read stream permission and didn't have it before, try to pull in some posts if (!($r[0]['abook_their_perms'] & PERMS_R_STREAM) && $their_perms & PERMS_R_STREAM) { proc_run('php', 'include/onepoll.php', $r[0]['abook_id']); } } } else { $default_perms = 0; // look for default permissions to apply in return - e.g. auto-friend $z = q("select * from abook where abook_channel = %d and (abook_flags & %d) limit 1", intval($channel['channel_id']), intval(ABOOK_FLAG_SELF)); if ($z) { $default_perms = intval($z[0]['abook_my_perms']); } // Keep original perms to check if we need to notify them $previous_perms = get_all_perms($channel['channel_id'], $x['hash']); $y = q("insert into abook ( abook_account, abook_channel, abook_xchan, abook_their_perms, abook_my_perms, abook_created, abook_updated, abook_dob, abook_flags ) values ( %d, %d, '%s', %d, %d, '%s', '%s', '%s', %d )", intval($channel['channel_account_id']), intval($channel['channel_id']), dbesc($x['hash']), intval($their_perms), intval($default_perms), dbesc(datetime_convert()), dbesc(datetime_convert()), dbesc($next_birthday), intval($default_perms ? 0 : ABOOK_FLAG_PENDING)); if ($y) { logger("New introduction received for {$channel['channel_name']}"); $new_perms = get_all_perms($channel['channel_id'], $x['hash']); if ($new_perms != $previous_perms) { // Send back a permissions update if permissions have changed $z = q("select * from abook where abook_xchan = '%s' and abook_channel = %d and not (abook_flags & %d) limit 1", dbesc($x['hash']), intval($channel['channel_id']), intval(ABOOK_FLAG_SELF)); if ($z) { proc_run('php', 'include/notifier.php', 'permission_update', $z[0]['abook_id']); } } $new_connection = q("select abook_id, abook_flags from abook where abook_channel = %d and abook_xchan = '%s' order by abook_created desc limit 1", intval($channel['channel_id']), dbesc($x['hash'])); if ($new_connection) { require_once 'include/enotify.php'; notification(array('type' => NOTIFY_INTRO, 'from_xchan' => $x['hash'], 'to_xchan' => $channel['channel_hash'], 'link' => z_root() . '/connedit/' . $new_connection[0]['abook_id'])); } if ($new_connection && $their_perms & PERMS_R_STREAM) { if ($channel['channel_w_stream'] & PERMS_PENDING || !($new_connection[0]['abook_flags'] & ABOOK_FLAG_PENDING)) { proc_run('php', 'include/onepoll.php', $new_connection[0]['abook_id']); } } } } } return true; } return false; }