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; }
/** * Some utility functions for processing the Diaspora comment virus. * */ function diaspora_sign_fields($fields, $prvkey) { if (!$fields) { return ''; } $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 base64_encode(rsa_sign($s, $prvkey)); }
function get() { if (!is_site_admin()) { return; } $o = ''; $r = q("select * from channel where channel_removed = 0"); $sitekey = get_config('system', 'pubkey'); if ($r) { foreach ($r as $rr) { $found = false; $primary_address = ''; $x = zot_get_hublocs($rr['channel_hash']); if ($x) { foreach ($x as $xx) { if ($xx['hubloc_url'] === z_root() && $xx['hubloc_sitekey'] === $sitekey) { $found = true; break; } } if ($found) { $o .= 'Hubloc exists for ' . $rr['channel_name'] . EOL; continue; } } $y = q("select xchan_addr from xchan where xchan_hash = '%s' limit 1", dbesc($rr['channel_hash'])); if ($y) { $primary_address = $y[0]['xchan_addr']; } $hub_address = $rr['channel']['channel_address'] . '@' . \App::get_hostname(); $primary = $hub_address === $primary_address ? 1 : 0; if (!$y) { $primary = 1; } $m = q("delete from hubloc where hubloc_hash = '%s' and hubloc_url = '%s' ", dbesc($rr['channel_hash']), dbesc(z_root())); // Create a verified hub location pointing to this site. $h = q("insert into hubloc ( hubloc_guid, hubloc_guid_sig, hubloc_hash, hubloc_addr, hubloc_primary, hubloc_url, hubloc_url_sig, hubloc_host, hubloc_callback, hubloc_sitekey, hubloc_network )\n\t\t\t\t\tvalues ( '%s', '%s', '%s', '%s', %d, '%s', '%s', '%s', '%s', '%s', '%s' )", dbesc($rr['channel_guid']), dbesc($rr['channel_guid_sig']), dbesc($rr['channel_hash']), dbesc($rr['channel_address'] . '@' . \App::get_hostname()), intval($primary), dbesc(z_root()), dbesc(base64url_encode(rsa_sign(z_root(), $rr['channel_prvkey']))), dbesc(\App::get_hostname()), dbesc(z_root() . '/post'), dbesc($sitekey), dbesc('zot')); if ($h) { $o . 'local hubloc created for ' . $rr['channel_name'] . EOL; } else { $o .= 'DB update failed for ' . $rr['channel_name'] . EOL; } } return $o; } }
function prate_post(&$a) { if (!local_channel()) { return; } $channel = App::get_channel(); $target = trim($_REQUEST['target']); if (!$target) { return; } if ($target === $channel['channel_hash']) { return; } $rating = intval($_POST['rating']); if ($rating < -10) { $rating = -10; } if ($rating > 10) { $rating = 10; } $rating_text = trim(escape_tags($_REQUEST['rating_text'])); $signed = $target . '.' . $rating . '.' . $rating_text; $sig = base64url_encode(rsa_sign($signed, $channel['channel_prvkey'])); $z = q("select * from xlink where xlink_xchan = '%s' and xlink_link = '%s' and xlink_static = 1 limit 1", dbesc($channel['channel_hash']), dbesc($target)); if ($z) { $record = $z[0]['xlink_id']; $w = q("update xlink set xlink_rating = '%d', xlink_rating_text = '%s', xlink_sig = '%s', xlink_updated = '%s'\n\t\t\twhere xlink_id = %d", intval($rating), dbesc($rating_text), dbesc($sig), dbesc(datetime_convert()), intval($record)); } else { $w = 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($channel['channel_hash']), dbesc($target), intval($rating), dbesc($rating_text), dbesc($sig), dbesc(datetime_convert())); $z = q("select * from xlink where xlink_xchan = '%s' and xlink_link = '%s' and xlink_static = 1 limit 1", dbesc($channel['channel_hash']), dbesc($orig_record[0]['abook_xchan'])); if ($z) { $record = $z[0]['xlink_id']; } } if ($record) { proc_run('php', 'include/ratenotif.php', 'rating', $record); } json_return_and_die(array('result' => true)); }
function post() { if (!local_channel()) { return; } if (!\App::$data['target']) { return; } if (!$_REQUEST['execute']) { return; } $channel = \App::get_channel(); $rating = intval($_POST['rating']); if ($rating < -10) { $rating = -10; } if ($rating > 10) { $rating = 10; } $rating_text = trim(escape_tags($_REQUEST['rating_text'])); $signed = \App::$data['target'] . '.' . $rating . '.' . $rating_text; $sig = base64url_encode(rsa_sign($signed, $channel['channel_prvkey'])); $z = q("select * from xlink where xlink_xchan = '%s' and xlink_link = '%s' and xlink_static = 1 limit 1", dbesc($channel['channel_hash']), dbesc(\App::$data['target'])); if ($z) { $record = $z[0]['xlink_id']; $w = q("update xlink set xlink_rating = '%d', xlink_rating_text = '%s', xlink_sig = '%s', xlink_updated = '%s'\n\t\t\t\twhere xlink_id = %d", intval($rating), dbesc($rating_text), dbesc($sig), dbesc(datetime_convert()), intval($record)); } else { $w = 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($channel['channel_hash']), dbesc(\App::$data['target']), intval($rating), dbesc($rating_text), dbesc($sig), dbesc(datetime_convert())); $z = q("select * from xlink where xlink_xchan = '%s' and xlink_link = '%s' and xlink_static = 1 limit 1", dbesc($channel['channel_hash']), dbesc(\App::$data['target'])); if ($z) { $record = $z[0]['xlink_id']; } } if ($record) { \Zotlabs\Daemon\Master::Summon(array('Ratenotif', 'rating', $record)); } }
function item_post(&$a) { // This will change. Figure out who the observer is and whether or not // they have permission to post here. Else ignore the post. if (!local_channel() && !remote_channel() && !x($_REQUEST, 'commenter')) { return; } require_once 'include/security.php'; $uid = local_channel(); $channel = null; $observer = null; /** * Is this a reply to something? */ $parent = x($_REQUEST, 'parent') ? intval($_REQUEST['parent']) : 0; $parent_mid = x($_REQUEST, 'parent_mid') ? trim($_REQUEST['parent_mid']) : ''; $remote_xchan = x($_REQUEST, 'remote_xchan') ? trim($_REQUEST['remote_xchan']) : false; $r = q("select * from xchan where xchan_hash = '%s' limit 1", dbesc($remote_xchan)); if ($r) { $remote_observer = $r[0]; } else { $remote_xchan = $remote_observer = false; } $profile_uid = x($_REQUEST, 'profile_uid') ? intval($_REQUEST['profile_uid']) : 0; require_once 'include/identity.php'; $sys = get_sys_channel(); if ($sys && $profile_uid && $sys['channel_id'] == $profile_uid && is_site_admin()) { $uid = intval($sys['channel_id']); $channel = $sys; $observer = $sys; } if (x($_REQUEST, 'dropitems')) { require_once 'include/items.php'; $arr_drop = explode(',', $_REQUEST['dropitems']); drop_items($arr_drop); $json = array('success' => 1); echo json_encode($json); killme(); } call_hooks('post_local_start', $_REQUEST); // logger('postvars ' . print_r($_REQUEST,true), LOGGER_DATA); $api_source = x($_REQUEST, 'api_source') && $_REQUEST['api_source'] ? true : false; $consensus = intval($_REQUEST['consensus']); // 'origin' (if non-zero) indicates that this network is where the message originated, // for the purpose of relaying comments to other conversation members. // If using the API from a device (leaf node) you must set origin to 1 (default) or leave unset. // If the API is used from another network with its own distribution // and deliveries, you may wish to set origin to 0 or false and allow the other // network to relay comments. // If you are unsure, it is prudent (and important) to leave it unset. $origin = $api_source && array_key_exists('origin', $_REQUEST) ? intval($_REQUEST['origin']) : 1; // To represent message-ids on other networks - this will create an item_id record $namespace = $api_source && array_key_exists('namespace', $_REQUEST) ? strip_tags($_REQUEST['namespace']) : ''; $remote_id = $api_source && array_key_exists('remote_id', $_REQUEST) ? strip_tags($_REQUEST['remote_id']) : ''; $owner_hash = null; $message_id = x($_REQUEST, 'message_id') && $api_source ? strip_tags($_REQUEST['message_id']) : ''; $created = x($_REQUEST, 'created') ? datetime_convert('UTC', 'UTC', $_REQUEST['created']) : datetime_convert(); $post_id = x($_REQUEST, 'post_id') ? intval($_REQUEST['post_id']) : 0; $app = x($_REQUEST, 'source') ? strip_tags($_REQUEST['source']) : ''; $return_path = x($_REQUEST, 'return') ? $_REQUEST['return'] : ''; $preview = x($_REQUEST, 'preview') ? intval($_REQUEST['preview']) : 0; $categories = x($_REQUEST, 'category') ? escape_tags($_REQUEST['category']) : ''; $webpage = x($_REQUEST, 'webpage') ? intval($_REQUEST['webpage']) : 0; $pagetitle = x($_REQUEST, 'pagetitle') ? escape_tags(urlencode($_REQUEST['pagetitle'])) : ''; $layout_mid = x($_REQUEST, 'layout_mid') ? escape_tags($_REQUEST['layout_mid']) : ''; $plink = x($_REQUEST, 'permalink') ? escape_tags($_REQUEST['permalink']) : ''; $obj_type = x($_REQUEST, 'obj_type') ? escape_tags($_REQUEST['obj_type']) : ACTIVITY_OBJ_NOTE; // allow API to bulk load a bunch of imported items with sending out a bunch of posts. $nopush = x($_REQUEST, 'nopush') ? intval($_REQUEST['nopush']) : 0; /* * Check service class limits */ if ($uid && !x($_REQUEST, 'parent') && !x($_REQUEST, 'post_id')) { $ret = item_check_service_class($uid, $_REQUEST['webpage'] == ITEM_WEBPAGE ? true : false); if (!$ret['success']) { notice(t($ret['message']) . EOL); if (x($_REQUEST, 'return')) { goaway($a->get_baseurl() . "/" . $return_path); } killme(); } } if ($pagetitle) { require_once 'library/urlify/URLify.php'; $pagetitle = strtolower(URLify::transliterate($pagetitle)); } $item_flags = $item_restrict = 0; $route = ''; $parent_item = null; $parent_contact = null; $thr_parent = ''; $parid = 0; $r = false; if ($parent || $parent_mid) { if (!x($_REQUEST, 'type')) { $_REQUEST['type'] = 'net-comment'; } if ($obj_type == ACTIVITY_OBJ_POST) { $obj_type = ACTIVITY_OBJ_COMMENT; } if ($parent) { $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1", intval($parent)); } elseif ($parent_mid && $uid) { // This is coming from an API source, and we are logged in $r = q("SELECT * FROM `item` WHERE `mid` = '%s' AND `uid` = %d LIMIT 1", dbesc($parent_mid), intval($uid)); } // if this isn't the real parent of the conversation, find it if ($r !== false && count($r)) { $parid = $r[0]['parent']; $parent_mid = $r[0]['mid']; if ($r[0]['id'] != $r[0]['parent']) { $r = q("SELECT * FROM `item` WHERE `id` = `parent` AND `parent` = %d LIMIT 1", intval($parid)); } } if ($r === false || !count($r)) { notice(t('Unable to locate original post.') . EOL); if (x($_REQUEST, 'return')) { goaway($a->get_baseurl() . "/" . $return_path); } killme(); } // can_comment_on_post() needs info from the following xchan_query xchan_query($r); $parent_item = $r[0]; $parent = $r[0]['id']; // multi-level threading - preserve the info but re-parent to our single level threading $thr_parent = $parent_mid; $route = $parent_item['route']; } if (!$observer) { $observer = $a->get_observer(); } if ($parent) { logger('mod_item: item_post parent=' . $parent); $can_comment = false; if (array_key_exists('owner', $parent_item) && $parent_item['owner']['abook_flags'] & ABOOK_FLAG_SELF) { $can_comment = perm_is_allowed($profile_uid, $observer['xchan_hash'], 'post_comments'); } else { $can_comment = can_comment_on_post($observer['xchan_hash'], $parent_item); } if (!$can_comment) { notice(t('Permission denied.') . EOL); if (x($_REQUEST, 'return')) { goaway($a->get_baseurl() . "/" . $return_path); } killme(); } } else { if (!perm_is_allowed($profile_uid, $observer['xchan_hash'], 'post_wall')) { notice(t('Permission denied.') . EOL); if (x($_REQUEST, 'return')) { goaway($a->get_baseurl() . "/" . $return_path); } killme(); } } // is this an edited post? $orig_post = null; if ($namespace && $remote_id) { // It wasn't an internally generated post - see if we've got an item matching this remote service id $i = q("select iid from item_id where service = '%s' and sid = '%s' limit 1", dbesc($namespace), dbesc($remote_id)); if ($i) { $post_id = $i[0]['iid']; } } if ($post_id) { $i = q("SELECT * FROM `item` WHERE `uid` = %d AND `id` = %d LIMIT 1", intval($profile_uid), intval($post_id)); if (!count($i)) { killme(); } $orig_post = $i[0]; } if (!$channel) { if ($uid && $uid == $profile_uid) { $channel = $a->get_channel(); } else { // posting as yourself but not necessarily to a channel you control $r = q("select * from channel left join account on channel_account_id = account_id where channel_id = %d LIMIT 1", intval($profile_uid)); if ($r) { $channel = $r[0]; } } } if (!$channel) { logger("mod_item: no channel."); if (x($_REQUEST, 'return')) { goaway($a->get_baseurl() . "/" . $return_path); } killme(); } $owner_xchan = null; $r = q("select * from xchan where xchan_hash = '%s' limit 1", dbesc($channel['channel_hash'])); if ($r && count($r)) { $owner_xchan = $r[0]; } else { logger("mod_item: no owner."); if (x($_REQUEST, 'return')) { goaway($a->get_baseurl() . "/" . $return_path); } killme(); } $walltowall = false; $walltowall_comment = false; if ($remote_xchan) { $observer = $remote_observer; } if ($observer) { logger('mod_item: post accepted from ' . $observer['xchan_name'] . ' for ' . $owner_xchan['xchan_name'], LOGGER_DEBUG); // wall-to-wall detection. // For top-level posts, if the author and owner are different it's a wall-to-wall // For comments, We need to additionally look at the parent and see if it's a wall post that originated locally. if ($observer['xchan_name'] != $owner_xchan['xchan_name']) { if ($parent_item && ($parent_item['item_flags'] & (ITEM_WALL | ITEM_ORIGIN)) == (ITEM_WALL | ITEM_ORIGIN)) { $walltowall_comment = true; $walltowall = true; } if (!$parent) { $walltowall = true; } } } $public_policy = x($_REQUEST, 'public_policy') ? escape_tags($_REQUEST['public_policy']) : map_scope($channel['channel_r_stream'], true); if ($webpage) { $public_policy = ''; } if ($public_policy) { $private = 1; } if ($orig_post) { $private = 0; // webpages are allowed to change ACLs after the fact. Normal conversation items aren't. if ($webpage) { $str_group_allow = perms2str($_REQUEST['group_allow']); $str_contact_allow = perms2str($_REQUEST['contact_allow']); $str_group_deny = perms2str($_REQUEST['group_deny']); $str_contact_deny = perms2str($_REQUEST['contact_deny']); } else { $str_group_allow = $orig_post['allow_gid']; $str_contact_allow = $orig_post['allow_cid']; $str_group_deny = $orig_post['deny_gid']; $str_contact_deny = $orig_post['deny_cid']; $public_policy = $orig_post['public_policy']; $private = $orig_post['item_private']; } if (strlen($str_group_allow) || strlen($str_contact_allow) || strlen($str_group_deny) || strlen($str_contact_deny) || strlen($public_policy) || $private) { $private = 1; } $location = $orig_post['location']; $coord = $orig_post['coord']; $verb = $orig_post['verb']; $app = $orig_post['app']; $title = escape_tags(trim($_REQUEST['title'])); $body = trim($_REQUEST['body']); $item_flags = $orig_post['item_flags']; // force us to recalculate if we need to obscure this post if ($item_flags & ITEM_OBSCURED) { $item_flags = $item_flags ^ ITEM_OBSCURED; } $item_restrict = $orig_post['item_restrict']; $postopts = $orig_post['postopts']; $created = $orig_post['created']; $mid = $orig_post['mid']; $parent_mid = $orig_post['parent_mid']; $plink = $orig_post['plink']; } else { // if coming from the API and no privacy settings are set, // use the user default permissions - as they won't have // been supplied via a form. if ($api_source && !array_key_exists('contact_allow', $_REQUEST) && !array_key_exists('group_allow', $_REQUEST) && !array_key_exists('contact_deny', $_REQUEST) && !array_key_exists('group_deny', $_REQUEST)) { $str_group_allow = $channel['channel_allow_gid']; $str_contact_allow = $channel['channel_allow_cid']; $str_group_deny = $channel['channel_deny_gid']; $str_contact_deny = $channel['channel_deny_cid']; } elseif ($walltowall) { // use the channel owner's default permissions $str_group_allow = $channel['channel_allow_gid']; $str_contact_allow = $channel['channel_allow_cid']; $str_group_deny = $channel['channel_deny_gid']; $str_contact_deny = $channel['channel_deny_cid']; } else { // use the posted permissions $str_group_allow = perms2str($_REQUEST['group_allow']); $str_contact_allow = perms2str($_REQUEST['contact_allow']); $str_group_deny = perms2str($_REQUEST['group_deny']); $str_contact_deny = perms2str($_REQUEST['contact_deny']); } $location = notags(trim($_REQUEST['location'])); $coord = notags(trim($_REQUEST['coord'])); $verb = notags(trim($_REQUEST['verb'])); $title = escape_tags(trim($_REQUEST['title'])); $body = trim($_REQUEST['body']); $body .= trim($_REQUEST['attachment']); $postopts = ''; $private = strlen($str_group_allow) || strlen($str_contact_allow) || strlen($str_group_deny) || strlen($str_contact_deny) || strlen($public_policy) ? 1 : 0; // If this is a comment, set the permissions from the parent. if ($parent_item) { $private = 0; if ($parent_item['item_private'] || strlen($parent_item['allow_cid']) || strlen($parent_item['allow_gid']) || strlen($parent_item['deny_cid']) || strlen($parent_item['deny_gid']) || strlen($parent_item['public_policy'])) { $private = $parent_item['item_private'] ? $parent_item['item_private'] : 1; } $public_policy = $parent_item['public_policy']; $str_contact_allow = $parent_item['allow_cid']; $str_group_allow = $parent_item['allow_gid']; $str_contact_deny = $parent_item['deny_cid']; $str_group_deny = $parent_item['deny_gid']; $owner_hash = $parent_item['owner_xchan']; } if (!strlen($body)) { if ($preview) { killme(); } info(t('Empty post discarded.') . EOL); if (x($_REQUEST, 'return')) { goaway($a->get_baseurl() . "/" . $return_path); } killme(); } } $expires = NULL_DATE; if (feature_enabled($profile_uid, 'content_expire')) { if (x($_REQUEST, 'expire')) { $expires = datetime_convert(date_default_timezone_get(), 'UTC', $_REQUEST['expire']); if ($expires <= datetime_convert()) { $expires = NULL_DATE; } } } $mimetype = notags(trim($_REQUEST['mimetype'])); if (!$mimetype) { $mimetype = 'text/bbcode'; } if ($preview) { $body = z_input_filter($profile_uid, $body, $mimetype); } // Verify ability to use html or php!!! $execflag = false; if ($mimetype === 'application/x-php') { $z = q("select account_id, account_roles, channel_pageflags from account left join channel on channel_account_id = account_id where channel_id = %d limit 1", intval($profile_uid)); if ($z && ($z[0]['account_roles'] & ACCOUNT_ROLE_ALLOWCODE || $z[0]['channel_pageflags'] & PAGE_ALLOWCODE)) { if ($uid && get_account_id() == $z[0]['account_id']) { $execflag = true; } else { notice(t('Executable content type not permitted to this channel.') . EOL); if (x($_REQUEST, 'return')) { goaway($a->get_baseurl() . "/" . $return_path); } killme(); } } } if ($mimetype === 'text/bbcode') { require_once 'include/text.php'; if ($uid && $uid == $profile_uid && feature_enabled($uid, 'markdown')) { require_once 'include/bb2diaspora.php'; $body = escape_tags($body); $body = preg_replace_callback('/\\[share(.*?)\\]/ism', 'share_shield', $body); $body = diaspora2bb($body, true); $body = preg_replace_callback('/\\[share(.*?)\\]/ism', 'share_unshield', $body); } // BBCODE alert: the following functions assume bbcode input // and will require alternatives for alternative content-types (text/html, text/markdown, text/plain, etc.) // we may need virtual or template classes to implement the possible alternatives // Work around doubled linefeeds in Tinymce 3.5b2 // First figure out if it's a status post that would've been // created using tinymce. Otherwise leave it alone. $plaintext = true; // $plaintext = ((feature_enabled($profile_uid,'richtext')) ? false : true); // if((! $parent) && (! $api_source) && (! $plaintext)) { // $body = fix_mce_lf($body); // } // If we're sending a private top-level message with a single @-taggable channel as a recipient, @-tag it, if our pconfig is set. if (!$parent && get_pconfig($profile_uid, 'system', 'tagifonlyrecip') && substr_count($str_contact_allow, '<') == 1 && $str_group_allow == '' && $str_contact_deny == '' && $str_group_deny == '') { $x = q("select abook_id, abook_their_perms from abook where abook_xchan = '%s' and abook_channel = %d limit 1", dbesc(str_replace(array('<', '>'), array('', ''), $str_contact_allow)), intval($profile_uid)); if ($x && $x[0]['abook_their_perms'] & PERMS_W_TAGWALL) { $body .= "\n\n@group+" . $x[0]['abook_id'] . "\n"; } } /** * fix naked links by passing through a callback to see if this is a red site * (already known to us) which will get a zrl, otherwise link with url, add bookmark tag to both. * First protect any url inside certain bbcode tags so we don't double link it. */ $body = preg_replace_callback('/\\[code(.*?)\\[\\/(code)\\]/ism', 'red_escape_codeblock', $body); $body = preg_replace_callback('/\\[url(.*?)\\[\\/(url)\\]/ism', 'red_escape_codeblock', $body); $body = preg_replace_callback('/\\[zrl(.*?)\\[\\/(zrl)\\]/ism', 'red_escape_codeblock', $body); $body = preg_replace_callback("/([^\\]\\='" . '"' . "\\/]|^|\\#\\^)(https?\\:\\/\\/[a-zA-Z0-9\\:\\/\\-\\?\\&\\;\\.\\=\\@\\_\\~\\#\\%\$\\!\\+\\,]+)/ism", 'red_zrl_callback', $body); $body = preg_replace_callback('/\\[\\$b64zrl(.*?)\\[\\/(zrl)\\]/ism', 'red_unescape_codeblock', $body); $body = preg_replace_callback('/\\[\\$b64url(.*?)\\[\\/(url)\\]/ism', 'red_unescape_codeblock', $body); $body = preg_replace_callback('/\\[\\$b64code(.*?)\\[\\/(code)\\]/ism', 'red_unescape_codeblock', $body); // fix any img tags that should be zmg $body = preg_replace_callback('/\\[img(.*?)\\](.*?)\\[\\/img\\]/ism', 'red_zrlify_img_callback', $body); $body = bb_translate_video($body); /** * Fold multi-line [code] sequences */ $body = preg_replace('/\\[\\/code\\]\\s*\\[code\\]/ism', "\n", $body); $body = scale_external_images($body, false); // Look for tags and linkify them $results = linkify_tags($a, $body, $uid ? $uid : $profile_uid); if ($results) { // Set permissions based on tag replacements set_linkified_perms($results, $str_contact_allow, $str_group_allow, $profile_uid, $parent_item, $private); $post_tags = array(); foreach ($results as $result) { $success = $result['success']; if ($success['replaced']) { $post_tags[] = array('uid' => $profile_uid, 'type' => $success['termtype'], 'otype' => TERM_OBJ_POST, 'term' => $success['term'], 'url' => $success['url']); } } } /** * * When a photo was uploaded into the message using the (profile wall) ajax * uploader, The permissions are initially set to disallow anybody but the * owner from seeing it. This is because the permissions may not yet have been * set for the post. If it's private, the photo permissions should be set * appropriately. But we didn't know the final permissions on the post until * now. So now we'll look for links of uploaded photos and attachments that are in the * post and set them to the same permissions as the post itself. * * If the post was end-to-end encrypted we can't find images and attachments in the body, * use our media_str input instead which only contains these elements - but only do this * when encrypted content exists because the photo/attachment may have been removed from * the post and we should keep it private. If it's encrypted we have no way of knowing * so we'll set the permissions regardless and realise that the media may not be * referenced in the post. * * What is preventing us from being able to upload photos into comments is dealing with * the photo and attachment permissions, since we don't always know who was in the * distribution for the top level post. * * We might be able to provide this functionality with a lot of fiddling: * - if the top level post is public (make the photo public) * - if the top level post was written by us or a wall post that belongs to us (match the top level post) * - if the top level post has privacy mentions, add those to the permissions. * - otherwise disallow the photo *or* make the photo public. This is the part that gets messy. */ if (!$preview) { fix_attached_photo_permissions($profile_uid, $owner_xchan['xchan_hash'], strpos($body, '[/crypt]') ? $_POST['media_str'] : $body, $str_contact_allow, $str_group_allow, $str_contact_deny, $str_group_deny); fix_attached_file_permissions($channel, $observer['xchan_hash'], strpos($body, '[/crypt]') ? $_POST['media_str'] : $body, $str_contact_allow, $str_group_allow, $str_contact_deny, $str_group_deny); } $attachments = ''; $match = false; if (preg_match_all('/(\\[attachment\\](.*?)\\[\\/attachment\\])/', $body, $match)) { $attachments = array(); foreach ($match[2] as $mtch) { $hash = substr($mtch, 0, strpos($mtch, ',')); $rev = intval(substr($mtch, strpos($mtch, ','))); $r = attach_by_hash_nodata($hash, $rev); if ($r['success']) { $attachments[] = array('href' => $a->get_baseurl() . '/attach/' . $r['data']['hash'], 'length' => $r['data']['filesize'], 'type' => $r['data']['filetype'], 'title' => urlencode($r['data']['filename']), 'revision' => $r['data']['revision']); } $body = str_replace($match[1], '', $body); } } } // BBCODE end alert if (strlen($categories)) { $cats = explode(',', $categories); foreach ($cats as $cat) { $post_tags[] = array('uid' => $profile_uid, 'type' => TERM_CATEGORY, 'otype' => TERM_OBJ_POST, 'term' => trim($cat), 'url' => $owner_xchan['xchan_url'] . '?f=&cat=' . urlencode(trim($cat))); } } $item_unseen = 1; // determine if this is a wall post if ($parent) { if ($parent_item['item_flags'] & ITEM_WALL) { $item_flags = $item_flags | ITEM_WALL; } } else { if (!$webpage) { $item_flags = $item_flags | ITEM_WALL; } } if ($origin) { $item_flags = $item_flags | ITEM_ORIGIN; } if ($moderated) { $item_restrict = $item_restrict | ITEM_MODERATED; } if ($webpage) { $item_restrict = $item_restrict | $webpage; } if (!strlen($verb)) { $verb = ACTIVITY_POST; } $notify_type = $parent ? 'comment-new' : 'wall-new'; if (!$mid) { $mid = $message_id ? $message_id : item_message_id(); } if (!$parent_mid) { $parent_mid = $mid; } if ($parent_item) { $parent_mid = $parent_item['mid']; } // Fallback so that we alway have a thr_parent if (!$thr_parent) { $thr_parent = $mid; } $datarray = array(); if (!$parent) { $item_flags = $item_flags | ITEM_THREAD_TOP; } if ($consensus) { $item_flags |= ITEM_CONSENSUS; } if (!$plink && $item_flags & ITEM_THREAD_TOP) { $plink = z_root() . '/channel/' . $channel['channel_address'] . '/?f=&mid=' . $mid; } $datarray['aid'] = $channel['channel_account_id']; $datarray['uid'] = $profile_uid; $datarray['owner_xchan'] = $owner_hash ? $owner_hash : $owner_xchan['xchan_hash']; $datarray['author_xchan'] = $observer['xchan_hash']; $datarray['created'] = $created; $datarray['edited'] = $orig_post ? datetime_convert() : $created; $datarray['expires'] = $expires; $datarray['commented'] = $orig_post ? datetime_convert() : $created; $datarray['received'] = $orig_post ? datetime_convert() : $created; $datarray['changed'] = $orig_post ? datetime_convert() : $created; $datarray['mid'] = $mid; $datarray['parent_mid'] = $parent_mid; $datarray['mimetype'] = $mimetype; $datarray['title'] = $title; $datarray['body'] = $body; $datarray['app'] = $app; $datarray['location'] = $location; $datarray['coord'] = $coord; $datarray['verb'] = $verb; $datarray['obj_type'] = $obj_type; $datarray['allow_cid'] = $str_contact_allow; $datarray['allow_gid'] = $str_group_allow; $datarray['deny_cid'] = $str_contact_deny; $datarray['deny_gid'] = $str_group_deny; $datarray['item_private'] = $private; $datarray['attach'] = $attachments; $datarray['thr_parent'] = $thr_parent; $datarray['postopts'] = $postopts; $datarray['item_restrict'] = $item_restrict; $datarray['item_flags'] = $item_flags; $datarray['layout_mid'] = $layout_mid; $datarray['public_policy'] = $public_policy; $datarray['comment_policy'] = map_scope($channel['channel_w_comment']); $datarray['term'] = $post_tags; $datarray['plink'] = $plink; $datarray['route'] = $route; $datarray['item_unseen'] = $item_unseen; // preview mode - prepare the body for display and send it via json if ($preview) { require_once 'include/conversation.php'; $datarray['owner'] = $owner_xchan; $datarray['author'] = $observer; $datarray['attach'] = json_encode($datarray['attach']); $o = conversation($a, array($datarray), 'search', false, 'preview'); // logger('preview: ' . $o, LOGGER_DEBUG); echo json_encode(array('preview' => $o)); killme(); } if ($orig_post) { $datarray['edit'] = true; } call_hooks('post_local', $datarray); if (x($datarray, 'cancel')) { logger('mod_item: post cancelled by plugin.'); if ($return_path) { goaway($a->get_baseurl() . "/" . $return_path); } $json = array('cancel' => 1); if (x($_REQUEST, 'jsreload') && strlen($_REQUEST['jsreload'])) { $json['reload'] = $a->get_baseurl() . '/' . $_REQUEST['jsreload']; } echo json_encode($json); killme(); } if (mb_strlen($datarray['title']) > 255) { $datarray['title'] = mb_substr($datarray['title'], 0, 255); } if (array_key_exists('item_private', $datarray) && $datarray['item_private']) { $datarray['body'] = trim(z_input_filter($datarray['uid'], $datarray['body'], $datarray['mimetype'])); if ($uid) { if ($channel['channel_hash'] === $datarray['author_xchan']) { $datarray['sig'] = base64url_encode(rsa_sign($datarray['body'], $channel['channel_prvkey'])); $datarray['item_flags'] = $datarray['item_flags'] | ITEM_VERIFIED; } } logger('Encrypting local storage'); $key = get_config('system', 'pubkey'); $datarray['item_flags'] = $datarray['item_flags'] | ITEM_OBSCURED; if ($datarray['title']) { $datarray['title'] = json_encode(crypto_encapsulate($datarray['title'], $key)); } if ($datarray['body']) { $datarray['body'] = json_encode(crypto_encapsulate($datarray['body'], $key)); } } if ($orig_post) { $datarray['id'] = $post_id; item_store_update($datarray, $execflag); update_remote_id($channel, $post_id, $webpage, $pagetitle, $namespace, $remote_id, $mid); if (!$nopush) { proc_run('php', "include/notifier.php", 'edit_post', $post_id); } if (x($_REQUEST, 'return') && strlen($return_path)) { logger('return: ' . $return_path); goaway($a->get_baseurl() . "/" . $return_path); } killme(); } else { $post_id = 0; } $post = item_store($datarray, $execflag); $post_id = $post['item_id']; if ($post_id) { logger('mod_item: saved item ' . $post_id); if ($parent) { // only send comment notification if this is a wall-to-wall comment, // otherwise it will happen during delivery if ($datarray['owner_xchan'] != $datarray['author_xchan'] && $parent_item['item_flags'] & ITEM_WALL) { notification(array('type' => NOTIFY_COMMENT, 'from_xchan' => $datarray['author_xchan'], 'to_xchan' => $datarray['owner_xchan'], 'item' => $datarray, 'link' => $a->get_baseurl() . '/display/' . $datarray['mid'], 'verb' => ACTIVITY_POST, 'otype' => 'item', 'parent' => $parent, 'parent_mid' => $parent_item['mid'])); } } else { $parent = $post_id; if ($datarray['owner_xchan'] != $datarray['author_xchan']) { notification(array('type' => NOTIFY_WALL, 'from_xchan' => $datarray['author_xchan'], 'to_xchan' => $datarray['owner_xchan'], 'item' => $datarray, 'link' => $a->get_baseurl() . '/display/' . $datarray['mid'], 'verb' => ACTIVITY_POST, 'otype' => 'item')); } if ($uid && $uid == $profile_uid && !$datarray['item_restrict']) { q("update channel set channel_lastpost = '%s' where channel_id = %d", dbesc(datetime_convert()), intval($uid)); } } // photo comments turn the corresponding item visible to the profile wall // This way we don't see every picture in your new photo album posted to your wall at once. // They will show up as people comment on them. if ($parent_item['item_restrict'] & ITEM_HIDDEN) { $r = q("UPDATE `item` SET `item_restrict` = %d WHERE `id` = %d", intval($parent_item['item_restrict'] - ITEM_HIDDEN), intval($parent_item['id'])); } } else { logger('mod_item: unable to retrieve post that was just stored.'); notice(t('System error. Post not saved.') . EOL); goaway($a->get_baseurl() . "/" . $return_path); // NOTREACHED } if ($parent) { // Store the comment signature information in case we need to relay to Diaspora $ditem = $datarray; $ditem['author'] = $observer; store_diaspora_comment_sig($ditem, $channel, $parent_item, $post_id, $walltowall_comment ? 1 : 0); } update_remote_id($channel, $post_id, $webpage, $pagetitle, $namespace, $remote_id, $mid); $datarray['id'] = $post_id; $datarray['llink'] = $a->get_baseurl() . '/display/' . $channel['channel_address'] . '/' . $post_id; call_hooks('post_local_end', $datarray); if (!$nopush) { proc_run('php', 'include/notifier.php', $notify_type, $post_id); } logger('post_complete'); // figure out how to return, depending on from whence we came if ($api_source) { return $post; } if ($return_path) { goaway($a->get_baseurl() . "/" . $return_path); } $json = array('success' => 1); if (x($_REQUEST, 'jsreload') && strlen($_REQUEST['jsreload'])) { $json['reload'] = $a->get_baseurl() . '/' . $_REQUEST['jsreload']; } logger('post_json: ' . print_r($json, true), LOGGER_DEBUG); echo json_encode($json); killme(); // NOTREACHED }
function item_url_replace($channel, &$item, $old, $new) { if ($item['attach']) { json_url_replace($old, $new, $item['attach']); } if ($item['object']) { json_url_replace($old, $new, $item['object']); } if ($item['target']) { json_url_replace($old, $new, $item['target']); } if (string_replace($old, $new, $item['body'])) { $item['sig'] = base64url_encode(rsa_sign($item['body'], $channel['channel_prvkey'])); $item['item_verified'] = 1; } }
function import_post(&$a) { if (!get_account_id()) { return; } $data = null; $seize = x($_REQUEST, 'make_primary') ? intval($_REQUEST['make_primary']) : 0; $src = $_FILES['filename']['tmp_name']; $filename = basename($_FILES['filename']['name']); $filesize = intval($_FILES['filename']['size']); $filetype = $_FILES['filename']['type']; if ($src) { if ($filesize) { $data = @file_get_contents($src); } unlink($src); } if (!$src) { $old_address = x($_REQUEST, 'old_address') ? $_REQUEST['old_address'] : ''; if (!$old_address) { logger('mod_import: nothing to import.'); notice(t('Nothing to import.') . EOL); return; } $email = x($_REQUEST, 'email') ? $_REQUEST['email'] : ''; $password = x($_REQUEST, 'password') ? $_REQUEST['password'] : ''; $channelname = substr($old_address, 0, strpos($old_address, '@')); $servername = substr($old_address, strpos($old_address, '@') + 1); $scheme = 'https://'; $api_path = '/api/red/channel/export/basic?f=&channel=' . $channelname; $binary = false; $redirects = 0; $opts = array('http_auth' => $email . ':' . $password); $url = $scheme . $servername . $api_path; $ret = z_fetch_url($url, $binary, $redirects, $opts); if (!$ret['success']) { $ret = z_fetch_url('http://' . $servername . $api_path, $binary, $redirects, $opts); } if ($ret['success']) { $data = $ret['body']; } else { notice(t('Unable to download data from old server') . EOL); } } if (!$data) { logger('mod_import: empty file.'); notice(t('Imported file is empty.') . EOL); return; } $data = json_decode($data, true); // logger('import: data: ' . print_r($data,true)); // print_r($data); // import channel $channel = $data['channel']; $r = q("select * from channel where (channel_guid = '%s' or channel_hash = '%s' or channel_address = '%s' ) limit 1", dbesc($channel['channel_guid']), dbesc($channel['channel_hash']), dbesc($channel['channel_address'])); // We should probably also verify the hash if ($r) { logger('mod_import: duplicate channel. ', print_r($channel, true)); notice(t('Cannot create a duplicate channel identifier on this system. Import failed.') . EOL); return; } unset($channel['channel_id']); $channel['channel_account_id'] = get_account_id(); $channel['channel_primary'] = $seize ? 1 : 0; dbesc_array($channel); $r = dbq("INSERT INTO channel (`" . implode("`, `", array_keys($channel)) . "`) VALUES ('" . implode("', '", array_values($channel)) . "')"); if (!$r) { logger('mod_import: channel clone failed. ', print_r($channel, true)); notice(t('Channel clone failed. Import failed.') . EOL); return; } $r = q("select * from channel where channel_account_id = %d and channel_guid = '%s' limit 1", intval(get_account_id()), $channel['channel_guid']); if (!$r) { logger('mod_import: channel not found. ', print_r($channel, true)); notice(t('Cloned channel not found. Import failed.') . EOL); return; } // reset $channel = $r[0]; set_default_login_identity(get_account_id(), $channel['channel_id'], false); if ($data['photo']) { require_once 'include/photo/photo_driver.php'; import_channel_photo(base64url_decode($data['photo']['data']), $data['photo']['type'], get_account_id(), $channel['channel_id']); } $profiles = $data['profile']; if ($profiles) { foreach ($profiles as $profile) { unset($profile['id']); $profile['aid'] = get_account_id(); $profile['uid'] = $channel['channel_id']; // we are going to reset all profile photos to the original // somebody will have to fix this later and put all the applicable photos into the export $profile['photo'] = z_root() . '/photo/profile/l/' . $channel['channel_id']; $profile['thumb'] = z_root() . '/photo/profile/m/' . $channel['channel_id']; dbesc_array($profile); $r = dbq("INSERT INTO profile (`" . implode("`, `", array_keys($profile)) . "`) VALUES ('" . implode("', '", array_values($profile)) . "')"); } } $hublocs = $data['hubloc']; if ($hublocs) { foreach ($hublocs as $hubloc) { $arr = array('guid' => $hubloc['hubloc_guid'], 'guid_sig' => $hubloc['guid_sig'], 'url' => $hubloc['hubloc_url'], 'url_sig' => $hubloc['hubloc_url_sig']); if ($hubloc['hubloc_hash'] === $channel['channel_hash'] && $hubloc['hubloc_flags'] & HUBLOC_FLAGS_PRIMARY && $seize) { $hubloc['hubloc_flags'] = $hubloc['hubloc_flags'] ^ HUBLOC_FLAGS_PRIMARY; } if (!zot_gethub($arr)) { unset($hubloc['hubloc_id']); dbesc_array($hubloc); $r = dbq("INSERT INTO hubloc (`" . implode("`, `", array_keys($hubloc)) . "`) VALUES ('" . implode("', '", array_values($hubloc)) . "')"); } } } // create new hubloc for the new channel at this site $r = q("insert into hubloc ( hubloc_guid, hubloc_guid_sig, hubloc_hash, hubloc_addr, hubloc_network, hubloc_flags, \n\t\thubloc_url, hubloc_url_sig, hubloc_host, hubloc_callback, hubloc_sitekey )\n\t\tvalues ( '%s', '%s', '%s', '%s', '%s', %d, '%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()), dbesc('zot'), intval($seize ? HUBLOC_FLAGS_PRIMARY : 0), 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'))); // reset the original primary hubloc if it is being seized if ($seize) { $r = q("update hubloc set hubloc_flags = (hubloc_flags ^ %d) where (hubloc_flags & %d) and hubloc_hash = '%s' and hubloc_url != '%s' ", intval(HUBLOC_FLAGS_PRIMARY), intval(HUBLOC_FLAGS_PRIMARY), dbesc($channel['channel_hash']), dbesc(z_root())); } // import xchans and contact photos if ($seize) { // replace our existing xchan if we're seizing control $r = q("delete from xchan where xchan_hash = '%s' limit 1", dbesc($channel['channel_hash'])); $r = q("insert into xchan ( xchan_hash, xchan_guid, xchan_guid_sig, xchan_pubkey, xchan_photo_l, xchan_photo_m, xchan_photo_s, xchan_addr, xchan_url, xchan_follow, xchan_connurl, xchan_name, xchan_network, xchan_photo_date, xchan_name_date ) values ('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s')", dbesc($channel['channel_hash']), dbesc($channel['channel_guid']), dbesc($channel['channel_guid_sig']), dbesc($channel['channel_pubkey']), dbesc($a->get_baseurl() . "/photo/profile/l/" . $channel['channel_id']), dbesc($a->get_baseurl() . "/photo/profile/m/" . $channel['channel_id']), dbesc($a->get_baseurl() . "/photo/profile/s/" . $channel['channel_id']), dbesc($channel['channel_address'] . '@' . get_app()->get_hostname()), dbesc(z_root() . '/channel/' . $channel['channel_address']), dbesc(z_root() . '/follow?f=&url=%s'), dbesc(z_root() . '/poco/' . $channel['channel_address']), dbesc($channel['channel_name']), dbesc('zot'), dbesc(datetime_convert()), dbesc(datetime_convert())); } $xchans = $data['xchan']; if ($xchans) { foreach ($xchans as $xchan) { $r = q("select xchan_hash from xchan where xchan_hash = '%s' limit 1", dbesc($xchan['xchan_hash'])); if ($r) { continue; } dbesc_array($xchan); $r = dbq("INSERT INTO xchan (`" . implode("`, `", array_keys($xchan)) . "`) VALUES ('" . implode("', '", array_values($xchan)) . "')"); require_once 'include/photo/photo_driver.php'; $photos = import_profile_photo($xchan['xchan_photo_l'], $xchan['xchan_hash']); if ($photos[4]) { $photodate = NULL_DATE; } else { $photodate = $xchan['xchan_photo_date']; } $r = q("update xchan set xchan_photo_l = '%s', xchan_photo_m = '%s', xchan_photo_s = '%s', xchan_photo_mimetype = '%s', xchan_photo_date = '%s'\n\t\t\t\twhere xchan_hash = '%s' limit 1", dbesc($photos[0]), dbesc($photos[1]), dbesc($photos[2]), dbesc($photos[3]), dbesc($photodate), dbesc($xchan_hash)); } } // FIXME - ensure we have an xchan if somebody is trying to pull a fast one // import contacts $abooks = $data['abook']; if ($abooks) { foreach ($abooks as $abook) { unset($abook['abook_id']); $abook['abook_account'] = get_account_id(); $abook['abook_channel'] = $channel['channel_id']; dbesc_array($abook); $r = dbq("INSERT INTO abook (`" . implode("`, `", array_keys($abook)) . "`) VALUES ('" . implode("', '", array_values($abook)) . "')"); } } $configs = $data['config']; if ($configs) { foreach ($configs as $config) { unset($config['id']); $config['uid'] = $channel['channel_id']; dbesc_array($config); $r = dbq("INSERT INTO pconfig (`" . implode("`, `", array_keys($config)) . "`) VALUES ('" . implode("', '", array_values($config)) . "')"); } } $groups = $data['group']; if ($groups) { $saved = array(); foreach ($groups as $group) { $saved[$group['hash']] = array('old' => $group['id']); unset($group['id']); $group['uid'] = $channel['channel_id']; dbesc_array($group); $r = dbq("INSERT INTO group (`" . implode("`, `", array_keys($group)) . "`) VALUES ('" . implode("', '", array_values($group)) . "')"); } $r = q("select * from `groups` where uid = %d", intval($channel['channel_id'])); if ($r) { foreach ($r as $rr) { $saved[$rr['hash']]['new'] = $rr['id']; } } } $group_members = $data['group_member']; if ($groups_members) { foreach ($group_members as $group_member) { unset($group_member['id']); $group_member['uid'] = $channel['channel_id']; foreach ($saved as $x) { if ($x['old'] == $group_member['gid']) { $group_member['gid'] = $x['new']; } } dbesc_array($group_member); $r = dbq("INSERT INTO group_member (`" . implode("`, `", array_keys($group_member)) . "`) VALUES ('" . implode("', '", array_values($group_member)) . "')"); } } // FIXME - ensure we have a self entry if somebody is trying to pull a fast one if ($seize) { // notify old server that it is no longer primary. } // This will indirectly perform a refresh_all *and* update the directory proc_run('php', 'include/directory.php', $channel['channel_id']); // send out refresh requests notice(t('Import completed.') . EOL); change_channel($channel['channel_id']); goaway(z_root() . '/network'); }
function store_diaspora_retract_sig($item, $user, $baseurl) { // Note that we can't add a target_author_signature // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting // the comment, that means we're the home of the post, and Diaspora will only // check the parent_author_signature of retractions that it doesn't have to relay further // // I don't think this function gets called for an "unlike," but I'll check anyway $enabled = intval(get_config('system', 'diaspora_enabled')); if (!$enabled) { logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG); return; } logger('drop_item: storing diaspora retraction signature'); $signed_text = $item['guid'] . ';' . ($item['verb'] === ACTIVITY_LIKE ? 'Like' : 'Comment'); if (local_user() == $item['uid']) { $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl, '://') + 3); $authorsig = base64_encode(rsa_sign($signed_text, $user['prvkey'], 'sha256')); } else { $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1", $item['contact-id']); if (count($r)) { // The below handle only works for NETWORK_DFRN. I think that's ok, because this function // only handles DFRN deletes $handle_baseurl_start = strpos($r['url'], '://') + 3; $handle_baseurl_length = strpos($r['url'], '/profile') - $handle_baseurl_start; $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length); $authorsig = ''; } } if (isset($handle)) { q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ", intval($item['id']), dbesc($signed_text), dbesc($authorsig), dbesc($handle)); } return; }
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 store_diaspora_comment_sig($datarray, $channel, $parent_item, $post_id, $walltowall = false) { // We won't be able to sign Diaspora comments for authenticated visitors // - we don't have their private key // since Diaspora doesn't handle edits we can only do this for the original text and not update it. require_once 'include/bb2diaspora.php'; $signed_body = bb2diaspora_itembody($datarray, $walltowall); if ($walltowall) { logger('wall to wall comment', LOGGER_DEBUG); // post will come across with the owner's identity. Throw a preamble onto the post to indicate the true author. $signed_body = "\n\n" . '![' . $datarray['author']['xchan_name'] . '](' . $datarray['author']['xchan_photo_m'] . ')' . '[' . $datarray['author']['xchan_name'] . '](' . $datarray['author']['xchan_url'] . ')' . "\n\n" . $signed_body; } logger('storing diaspora comment signature', LOGGER_DEBUG); $diaspora_handle = $channel['channel_address'] . '@' . get_app()->get_hostname(); $signed_text = $datarray['mid'] . ';' . $parent_item['mid'] . ';' . $signed_body . ';' . $diaspora_handle; /** @FIXME $uprvkey is undefined, do we still need this if-statement? */ if ($uprvkey !== false) { $authorsig = base64_encode(rsa_sign($signed_text, $channel['channel_prvkey'], 'sha256')); } else { $authorsig = ''; } $x = array('signer' => $diaspora_handle, 'body' => $signed_body, 'signed_text' => $signed_text, 'signature' => base64_encode($authorsig)); $key = get_config('system', 'pubkey'); $y = crypto_encapsulate(json_encode($x), $key); $r = q("update item set diaspora_meta = '%s' where id = %d", dbesc(json_encode($y)), intval($post_id)); if (!$r) { logger('store_diaspora_comment_sig: DB write failed'); } return; }
/** * @brief Create a new channel. * * Also creates the related xchan, hubloc, profile, and "self" abook records, * and an empty "Friends" group/collection for the new channel. * * @param array $arr assoziative array with: * * \e string \b name full name of channel * * \e string \b nickname "email/url-compliant" nickname * * \e int \b account_id to attach with this channel * * [other identity fields as desired] * * @returns array * 'success' => boolean true or false * 'message' => optional error text if success is false * 'channel' => if successful the created channel array */ function create_identity($arr) { $a = get_app(); $ret = array('success' => false); if (!$arr['account_id']) { $ret['message'] = t('No account identifier'); return $ret; } $ret = identity_check_service_class($arr['account_id']); if (!$ret['success']) { return $ret; } // save this for auto_friending $total_identities = $ret['total_identities']; $nick = mb_strtolower(trim($arr['nickname'])); if (!$nick) { $ret['message'] = t('Nickname is required.'); return $ret; } $name = escape_tags($arr['name']); $pageflags = x($arr, 'pageflags') ? intval($arr['pageflags']) : PAGE_NORMAL; $system = x($arr, 'system') ? intval($arr['system']) : 0; $name_error = validate_channelname($arr['name']); if ($name_error) { $ret['message'] = $name_error; return $ret; } if ($nick === 'sys' && !$system) { $ret['message'] = t('Reserved nickname. Please choose another.'); return $ret; } if (check_webbie(array($nick)) !== $nick) { $ret['message'] = t('Nickname has unsupported characters or is already being used on this site.'); return $ret; } $guid = zot_new_uid($nick); $key = new_keypair(4096); $sig = base64url_encode(rsa_sign($guid, $key['prvkey'])); $hash = make_xchan_hash($guid, $sig); // Force a few things on the short term until we can provide a theme or app with choice $publish = 1; if (array_key_exists('publish', $arr)) { $publish = intval($arr['publish']); } $primary = true; if (array_key_exists('primary', $arr)) { $primary = intval($arr['primary']); } $role_permissions = null; $global_perms = get_perms(); if (array_key_exists('permissions_role', $arr) && $arr['permissions_role']) { $role_permissions = get_role_perms($arr['permissions_role']); if ($role_permissions) { foreach ($role_permissions as $p => $v) { if (strpos($p, 'channel_') !== false) { $perms_keys .= ', ' . $p; $perms_vals .= ', ' . intval($v); } if ($p === 'directory_publish') { $publish = intval($v); } } } } else { $defperms = site_default_perms(); foreach ($defperms as $p => $v) { $perms_keys .= ', ' . $global_perms[$p][0]; $perms_vals .= ', ' . intval($v); } } $expire = 0; $r = q("insert into channel ( channel_account_id, channel_primary, \n\t\tchannel_name, channel_address, channel_guid, channel_guid_sig,\n\t\tchannel_hash, channel_prvkey, channel_pubkey, channel_pageflags, channel_system, channel_expire_days, channel_timezone {$perms_keys} )\n\t\tvalues ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, %d, '%s' {$perms_vals} ) ", intval($arr['account_id']), intval($primary), dbesc($name), dbesc($nick), dbesc($guid), dbesc($sig), dbesc($hash), dbesc($key['prvkey']), dbesc($key['pubkey']), intval($pageflags), intval($system), intval($expire), dbesc($a->timezone)); $r = q("select * from channel where channel_account_id = %d \n\t\tand channel_guid = '%s' limit 1", intval($arr['account_id']), dbesc($guid)); if (!$r) { $ret['message'] = t('Unable to retrieve created identity'); return $ret; } $ret['channel'] = $r[0]; if (intval($arr['account_id'])) { set_default_login_identity($arr['account_id'], $ret['channel']['channel_id'], false); } // Create a verified hub location pointing to this site. $r = q("insert into hubloc ( hubloc_guid, hubloc_guid_sig, hubloc_hash, hubloc_addr, hubloc_primary, \n\t\thubloc_url, hubloc_url_sig, hubloc_host, hubloc_callback, hubloc_sitekey, hubloc_network )\n\t\tvalues ( '%s', '%s', '%s', '%s', %d, '%s', '%s', '%s', '%s', '%s', '%s' )", dbesc($guid), dbesc($sig), dbesc($hash), dbesc($ret['channel']['channel_address'] . '@' . get_app()->get_hostname()), intval($primary), dbesc(z_root()), dbesc(base64url_encode(rsa_sign(z_root(), $ret['channel']['channel_prvkey']))), dbesc(get_app()->get_hostname()), dbesc(z_root() . '/post'), dbesc(get_config('system', 'pubkey')), dbesc('zot')); if (!$r) { logger('create_identity: Unable to store hub location'); } $newuid = $ret['channel']['channel_id']; $r = q("insert into xchan ( xchan_hash, xchan_guid, xchan_guid_sig, xchan_pubkey, xchan_photo_l, xchan_photo_m, xchan_photo_s, xchan_addr, xchan_url, xchan_follow, xchan_connurl, xchan_name, xchan_network, xchan_photo_date, xchan_name_date, xchan_system ) values ('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d)", dbesc($hash), dbesc($guid), dbesc($sig), dbesc($key['pubkey']), dbesc($a->get_baseurl() . "/photo/profile/l/{$newuid}"), dbesc($a->get_baseurl() . "/photo/profile/m/{$newuid}"), dbesc($a->get_baseurl() . "/photo/profile/s/{$newuid}"), dbesc($ret['channel']['channel_address'] . '@' . get_app()->get_hostname()), dbesc(z_root() . '/channel/' . $ret['channel']['channel_address']), dbesc(z_root() . '/follow?f=&url=%s'), dbesc(z_root() . '/poco/' . $ret['channel']['channel_address']), dbesc($ret['channel']['channel_name']), dbesc('zot'), dbesc(datetime_convert()), dbesc(datetime_convert()), intval($system)); // Not checking return value. // It's ok for this to fail if it's an imported channel, and therefore the hash is a duplicate $r = q("INSERT INTO profile ( aid, uid, profile_guid, profile_name, is_default, publish, name, photo, thumb)\n\t\tVALUES ( %d, %d, '%s', '%s', %d, %d, '%s', '%s', '%s') ", intval($ret['channel']['channel_account_id']), intval($newuid), dbesc(random_string()), t('Default Profile'), 1, $publish, dbesc($ret['channel']['channel_name']), dbesc($a->get_baseurl() . "/photo/profile/l/{$newuid}"), dbesc($a->get_baseurl() . "/photo/profile/m/{$newuid}")); if ($role_permissions) { $myperms = array_key_exists('perms_auto', $role_permissions) && $role_permissions['perms_auto'] ? intval($role_permissions['perms_accept']) : 0; } else { $myperms = PERMS_R_STREAM | PERMS_R_PROFILE | PERMS_R_PHOTOS | PERMS_R_ABOOK | PERMS_W_STREAM | PERMS_W_WALL | PERMS_W_COMMENT | PERMS_W_MAIL | PERMS_W_CHAT | PERMS_R_STORAGE | PERMS_R_PAGES | PERMS_W_LIKE; } $r = q("insert into abook ( abook_account, abook_channel, abook_xchan, abook_closeness, abook_created, abook_updated, abook_self, abook_my_perms )\n\t\tvalues ( %d, %d, '%s', %d, '%s', '%s', %d, %d ) ", intval($ret['channel']['channel_account_id']), intval($newuid), dbesc($hash), intval(0), dbesc(datetime_convert()), dbesc(datetime_convert()), intval(1), intval($myperms)); if (intval($ret['channel']['channel_account_id'])) { // Save our permissions role so we can perhaps call it up and modify it later. if ($role_permissions) { set_pconfig($newuid, 'system', 'permissions_role', $arr['permissions_role']); if (array_key_exists('online', $role_permissions)) { set_pconfig($newuid, 'system', 'hide_presence', 1 - intval($role_permissions['online'])); } if (array_key_exists('perms_auto', $role_permissions)) { set_pconfig($newuid, 'system', 'autoperms', $role_permissions['perms_auto'] ? $role_permissions['perms_accept'] : 0); } } // Create a group with yourself as a member. This allows somebody to use it // right away as a default group for new contacts. require_once 'include/group.php'; group_add($newuid, t('Friends')); group_add_member($newuid, t('Friends'), $ret['channel']['channel_hash']); // if our role_permissions indicate that we're using a default collection ACL, add it. if (is_array($role_permissions) && $role_permissions['default_collection']) { $r = q("select hash from groups where uid = %d and name = '%s' limit 1", intval($newuid), dbesc(t('Friends'))); if ($r) { q("update channel set channel_default_group = '%s', channel_allow_gid = '%s' where channel_id = %d", dbesc($r[0]['hash']), dbesc('<' . $r[0]['hash'] . '>'), intval($newuid)); } } if (!$system) { set_pconfig($ret['channel']['channel_id'], 'system', 'photo_path', '%Y-%m'); set_pconfig($ret['channel']['channel_id'], 'system', 'attach_path', '%Y-%m'); } // auto-follow any of the hub's pre-configured channel choices. // Only do this if it's the first channel for this account; // otherwise it could get annoying. Don't make this list too big // or it will impact registration time. $accts = get_config('system', 'auto_follow'); if ($accts && !$total_identities) { require_once 'include/follow.php'; if (!is_array($accts)) { $accts = array($accts); } foreach ($accts as $acct) { if (trim($acct)) { new_contact($newuid, trim($acct), $ret['channel'], false); } } } call_hooks('create_identity', $newuid); proc_run('php', 'include/directory.php', $ret['channel']['channel_id']); } $ret['success'] = true; return $ret; }
function diaspora_send_mail($item, $owner, $contact) { $a = get_app(); $myaddr = $owner['channel_address'] . '@' . App::get_hostname(); $r = q("select * from conv where guid = '%s' and uid = %d limit 1", dbesc($item['conv_guid']), intval($item['channel_id'])); if (!count($r)) { logger('diaspora_send_mail: conversation not found.'); return; } $z = q("select from_xchan from mail where conv_guid = '%s' and channel_id = %d and mid = parent_mid limit 1", dbesc($item['conv_guid']), intval($item['channel_id'])); $conv_owner = $z && $z[0]['from_xchan'] === $owner['channel_hash'] ? true : false; $cnv = $r[0]; $cnv['subject'] = base64url_decode(str_rot47($cnv['subject'])); $conv = array('guid' => xmlify($cnv['guid']), 'subject' => xmlify($cnv['subject']), 'created_at' => xmlify(datetime_convert('UTC', 'UTC', $cnv['created'], 'Y-m-d H:i:s \\U\\T\\C')), 'diaspora_handle' => xmlify($cnv['creator']), 'participant_handles' => xmlify($cnv['recips'])); if (array_key_exists('mail_obscured', $item) && intval($item['mail_obscured'])) { if ($item['title']) { $item['title'] = base64url_decode(str_rot47($item['title'])); } if ($item['body']) { $item['body'] = base64url_decode(str_rot47($item['body'])); } } // the parent_guid needs to be the conversation guid $parent_ptr = $cnv['guid']; $body = bb2diaspora($item['body']); $created = datetime_convert('UTC', 'UTC', $item['created'], 'Y-m-d H:i:s \\U\\T\\C'); $signed_text = $item['mid'] . ';' . $parent_ptr . ';' . $body . ';' . $created . ';' . $myaddr . ';' . $cnv['guid']; $sig = base64_encode(rsa_sign($signed_text, $owner['channel_prvkey'], 'sha256')); $msg = array('guid' => xmlify($item['mid']), 'parent_guid' => xmlify($parent_ptr), 'parent_author_signature' => $conv_owner ? xmlify($sig) : null, 'author_signature' => xmlify($sig), 'text' => xmlify($body), 'created_at' => xmlify($created), 'diaspora_handle' => xmlify($myaddr), 'conversation_guid' => xmlify($cnv['guid'])); if ($item['mail_isreply']) { $tpl = get_markup_template('diaspora_message.tpl', 'addon/diaspora'); $xmsg = replace_macros($tpl, array('$msg' => $msg)); } else { $conv['messages'] = array($msg); $tpl = get_markup_template('diaspora_conversation.tpl', 'addon/diaspora'); $xmsg = replace_macros($tpl, array('$conv' => $conv)); } logger('diaspora_conversation: ' . print_r($xmsg, true), LOGGER_DATA); $slap = 'xml=' . urlencode(urlencode(diaspora_msg_build($xmsg, $owner, $contact, $owner['channel_prvkey'], $contact['xchan_pubkey'], false))); return diaspora_queue($owner, $contact, $slap, false, $item['mid']); }
function item_url_replace($channel, &$item, $old, $new, $oldnick = '') { if ($item['attach']) { json_url_replace($old, $new, $item['attach']); if ($oldnick) { json_url_replace('/' . $oldnick . '/', '/' . $channel['channel_address'] . '/', $item['attach']); } } if ($item['object']) { json_url_replace($old, $new, $item['object']); if ($oldnick) { json_url_replace('/' . $oldnick . '/', '/' . $channel['channel_address'] . '/', $item['object']); } } if ($item['target']) { json_url_replace($old, $new, $item['target']); if ($oldnick) { json_url_replace('/' . $oldnick . '/', '/' . $channel['channel_address'] . '/', $item['target']); } } if (string_replace($old, $new, $item['body'])) { $item['sig'] = base64url_encode(rsa_sign($item['body'], $channel['channel_prvkey'])); $item['item_verified'] = 1; } $item['plink'] = str_replace($old, $new, $item['plink']); if ($oldnick) { $item['plink'] = str_replace('/' . $oldnick . '/', '/' . $channel['channel_address'] . '/', $item['plink']); } $item['llink'] = str_replace($old, $new, $item['llink']); if ($oldnick) { $item['llink'] = str_replace('/' . $oldnick . '/', '/' . $channel['channel_address'] . '/', $item['llink']); } }
/** * @brief * * We received a notification packet (in mod/post.php) that a message is waiting for us, and we've verified the sender. * Now send back a pickup message, using our message tracking ID ($arr['secret']), which we will sign with our site private key. * The entire pickup message is encrypted with the remote site's public key. * If everything checks out on the remote end, we will receive back a packet containing one or more messages, * which will be processed and delivered before this function ultimately returns. * * @see zot_import() * * @param array $arr * decrypted and json decoded notify packet from remote site * @return array from zot_import() */ function zot_fetch($arr) { logger('zot_fetch: ' . print_r($arr, true), LOGGER_DATA); $url = $arr['sender']['url'] . $arr['callback']; // set $multiple param on zot_gethub() to return all matching hubs // This allows us to recover from re-installs when a redundant (but invalid) hubloc for // this identity is widely dispersed throughout the network. $ret_hubs = zot_gethub($arr['sender'], true); if (!$ret_hubs) { logger('zot_fetch: no hub: ' . print_r($arr['sender'], true)); return; } foreach ($ret_hubs as $ret_hub) { $data = array('type' => 'pickup', 'url' => z_root(), 'callback_sig' => base64url_encode(rsa_sign(z_root() . '/post', get_config('system', 'prvkey'))), 'callback' => z_root() . '/post', 'secret' => $arr['secret'], 'secret_sig' => base64url_encode(rsa_sign($arr['secret'], get_config('system', 'prvkey')))); $datatosend = json_encode(crypto_encapsulate(json_encode($data), $ret_hub['hubloc_sitekey'])); $fetch = zot_zot($url, $datatosend); $result = zot_import($fetch, $arr['sender']['url']); if ($result) { return $result; } } return; }
function diaspora_magic_env($channel, $msg) { $data = preg_replace('/s+/', '', base64url_encode($msg)); $keyhash = base64url_encode(channel_reddress($channel)); $type = 'application/xml'; $encoding = 'base64url'; $algorithm = 'RSA-SHA256'; $precomputed = '.YXBwbGljYXRpb24vYXRvbSt4bWw=.YmFzZTY0dXJs.UlNBLVNIQTI1Ng=='; $signature = base64url_encode(rsa_sign($data . $precomputed, $channel['channel_prvkey'])); return replace_macros(get_markup_template('magicsig.tpl', 'addon/diaspora'), ['$data' => $data, '$encoding' => $encoding, '$algorithm' => $algorithm, '$keyhash' => $keyhash, '$signature' => $signature]); }
function slapper($owner, $url, $slap) { logger('slapper called for ' . $url . '. Data: ' . $slap); // does contact have a salmon endpoint? if (!strlen($url)) { return; } if (!$owner['sprvkey']) { logger(sprintf("user '%s' (%d) does not have a salmon private key. Send failed.", $owner['username'], $owner['uid'])); return; } // add all namespaces to item $namespaces = <<<EOT <entry xmlns="http://www.w3.org/2005/Atom" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:at="http://purl.org/atompub/tombstones/1.0" xmlns:media="http://purl.org/syndication/atommedia" xmlns:dfrn="http://purl.org/macgirvin/dfrn/1.0" xmlns:as="http://activitystrea.ms/spec/1.0/" xmlns:georss="http://www.georss.org/georss" xmlns:poco="http://portablecontacts.net/spec/1.0" xmlns:ostatus="http://ostatus.org/schema/1.0" \t xmlns:statusnet="http://status.net/schema/api/1/" >\t\t\t\t\t\t\t\t\t\t\t\t\t> EOT; $slap = str_replace('<entry>', $namespaces, $slap); // 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['spubkey'])), 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['sprvkey'])); $signature2 = base64url_encode(rsa_sign($data . $precomputed, $owner['sprvkey'])); $signature3 = base64url_encode(rsa_sign($data, $owner['sprvkey'])); $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 post_url($url, $salmon, array('Content-type: application/magic-envelope+xml', 'Content-length: ' . strlen($salmon))); $a = get_app(); $return_code = $a->get_curl_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)); // slap them post_url($url, $salmon, array('Content-type: application/magic-envelope+xml', 'Content-length: ' . strlen($salmon))); $return_code = $a->get_curl_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)); // slap them post_url($url, $salmon, array('Content-type: application/magic-envelope+xml', 'Content-length: ' . strlen($salmon))); $return_code = $a->get_curl_code(); } } logger('slapper for ' . $url . ' returned ' . $return_code); if (!$return_code) { return -1; } if ($return_code == 503 && stristr($a->get_curl_headers(), 'retry-after')) { return -1; } return $return_code >= 200 && $return_code < 300 ? 0 : 1; }
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 init() { $ret = array('success' => false, 'url' => '', 'message' => ''); logger('mod_magic: invoked', LOGGER_DEBUG); logger('mod_magic: args: ' . print_r($_REQUEST, true), LOGGER_DATA); $addr = x($_REQUEST, 'addr') ? $_REQUEST['addr'] : ''; $dest = x($_REQUEST, 'dest') ? $_REQUEST['dest'] : ''; $test = x($_REQUEST, 'test') ? intval($_REQUEST['test']) : 0; $rev = x($_REQUEST, 'rev') ? intval($_REQUEST['rev']) : 0; $delegate = x($_REQUEST, 'delegate') ? $_REQUEST['delegate'] : ''; $parsed = parse_url($dest); if (!$parsed) { if ($test) { $ret['message'] .= 'could not parse ' . $dest . EOL; return $ret; } goaway($dest); } $basepath = $parsed['scheme'] . '://' . $parsed['host'] . ($parsed['port'] ? ':' . $parsed['port'] : ''); $x = q("select * from hubloc where hubloc_url = '%s' order by hubloc_connected desc limit 1", dbesc($basepath)); if (!$x) { /* * We have no records for, or prior communications with this hub. * If an address was supplied, let's finger them to create a hub record. * Otherwise we'll use the special address '[system]' which will return * either a system channel or the first available normal channel. We don't * really care about what channel is returned - we need the hub information * from that response so that we can create signed auth packets destined * for that hub. * */ $ret = zot_finger($addr ? $addr : '[system]@' . $parsed['host'], null); if ($ret['success']) { $j = json_decode($ret['body'], true); if ($j) { import_xchan($j); } // Now try again $x = q("select * from hubloc where hubloc_url = '%s' order by hubloc_connected desc limit 1", dbesc($basepath)); } } if (!$x) { if ($rev) { goaway($dest); } else { logger('mod_magic: no channels found for requested hub.' . print_r($_REQUEST, true)); if ($test) { $ret['message'] .= 'This site has no previous connections with ' . $basepath . EOL; return $ret; } notice(t('Hub not found.') . EOL); return; } } // This is ready-made for a plugin that provides a blacklist or "ask me" before blindly authenticating. // By default, we'll proceed without asking. $arr = array('channel_id' => local_channel(), 'xchan' => $x[0], 'destination' => $dest, 'proceed' => true); call_hooks('magic_auth', $arr); $dest = $arr['destination']; if (!$arr['proceed']) { if ($test) { $ret['message'] .= 'cancelled by plugin.' . EOL; return $ret; } goaway($dest); } if (get_observer_hash() && $x[0]['hubloc_url'] === z_root()) { // We are already authenticated on this site and a registered observer. // Just redirect. if ($test) { $ret['success'] = true; $ret['message'] .= 'Local site - you are already authenticated.' . EOL; return $ret; } $delegation_success = false; if ($delegate) { $r = q("select * from channel left join hubloc on channel_hash = hubloc_hash where hubloc_addr = '%s' limit 1", dbesc($delegate)); if ($r && intval($r[0]['channel_id'])) { $allowed = perm_is_allowed($r[0]['channel_id'], get_observer_hash(), 'delegate'); if ($allowed) { $_SESSION['delegate_channel'] = $r[0]['channel_id']; $_SESSION['delegate'] = get_observer_hash(); $_SESSION['account_id'] = intval($r[0]['channel_account_id']); change_channel($r[0]['channel_id']); $delegation_success = true; } } } // FIXME: check and honour local delegation goaway($dest); } if (local_channel()) { $channel = \App::get_channel(); $token = random_string(); $token_sig = base64url_encode(rsa_sign($token, $channel['channel_prvkey'])); $channel['token'] = $token; $channel['token_sig'] = $token_sig; \Zotlabs\Zot\Verify::create('auth', $channel['channel_id'], $token, $x[0]['hubloc_url']); $target_url = $x[0]['hubloc_callback'] . '/?f=&auth=' . urlencode($channel['channel_address'] . '@' . \App::get_hostname()) . '&sec=' . $token . '&dest=' . urlencode($dest) . '&version=' . ZOT_REVISION; if ($delegate) { $target_url .= '&delegate=' . urlencode($delegate); } logger('mod_magic: redirecting to: ' . $target_url, LOGGER_DEBUG); if ($test) { $ret['success'] = true; $ret['url'] = $target_url; $ret['message'] = 'token ' . $token . ' created for channel ' . $channel['channel_id'] . ' for url ' . $x[0]['hubloc_url'] . EOL; return $ret; } goaway($target_url); } if ($test) { $ret['message'] = 'Not authenticated or invalid arguments to mod_magic' . EOL; return $ret; } goaway($dest); }
function store_diaspora_like_sig($activity, $post_type, $contact, $post_id) { // Note that we can only create a signature for a user of the local server. We don't have // a key for remote users. That is ok, because if a remote user is "unlike"ing a post, it // means we are the relay, and for relayable_retractions, Diaspora // only checks the parent_author_signature if it doesn't have to relay further $enabled = intval(get_config('system', 'diaspora_enabled')); if (!$enabled) { logger('mod_like: diaspora support disabled, not storing like signature', LOGGER_DEBUG); return; } logger('mod_like: storing diaspora like signature'); if ($activity === ACTIVITY_LIKE && $post_type === t('status')) { // Only works for NETWORK_DFRN $contact_baseurl_start = strpos($contact['url'], '://') + 3; $contact_baseurl_length = strpos($contact['url'], '/profile') - $contact_baseurl_start; $contact_baseurl = substr($contact['url'], $contact_baseurl_start, $contact_baseurl_length); $diaspora_handle = $contact['nick'] . '@' . $contact_baseurl; // Get contact's private key if he's a user of the local Friendica server $r = q("SELECT `contact`.`uid` FROM `contact` WHERE `url` = '%s' AND `self` = 1 LIMIT 1", dbesc($contact['url'])); if ($r) { $contact_uid = $r['uid']; $r = q("SELECT prvkey FROM user WHERE uid = %d LIMIT 1", intval($contact_uid)); if ($r) { $contact_uprvkey = $r['prvkey']; } } $r = q("SELECT guid, parent FROM `item` WHERE id = %d LIMIT 1", intval($post_id)); if ($r) { $p = q("SELECT guid FROM `item` WHERE id = %d AND parent = %d LIMIT 1", intval($r[0]['parent']), intval($r[0]['parent'])); if ($p) { $signed_text = $r[0]['guid'] . ';Post;' . $p[0]['guid'] . ';true;' . $diaspora_handle; if (isset($contact_uprvkey)) { $authorsig = base64_encode(rsa_sign($signed_text, $contact_uprvkey, 'sha256')); } else { $authorsig = ''; } q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ", intval($post_id), dbesc($signed_text), dbesc($authorsig), dbesc($diaspora_handle)); } } } return; }
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); }
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 import_account($account_id) { if (!$account_id) { logger("import_account: No account ID supplied"); return; } $max_identities = account_service_class_fetch($account_id, 'total_identities'); $max_friends = account_service_class_fetch($account_id, 'total_channels'); $max_feeds = account_service_class_fetch($account_id, 'total_feeds'); if ($max_identities !== false) { $r = q("select channel_id from channel where channel_account_id = %d", intval($account_id)); if ($r && count($r) > $max_identities) { notice(sprintf(t('Your service plan only allows %d channels.'), $max_identities) . EOL); return; } } $data = null; $seize = x($_REQUEST, 'make_primary') ? intval($_REQUEST['make_primary']) : 0; $import_posts = x($_REQUEST, 'import_posts') ? intval($_REQUEST['import_posts']) : 0; $src = $_FILES['filename']['tmp_name']; $filename = basename($_FILES['filename']['name']); $filesize = intval($_FILES['filename']['size']); $filetype = $_FILES['filename']['type']; $completed = array_key_exists('import_step', $_SESSION) ? intval($_SESSION['import_step']) : 0; if ($completed) { logger('saved import step: ' . $_SESSION['import_step']); } if ($src) { // This is OS specific and could also fail if your tmpdir isn't very large // mostly used for Diaspora which exports gzipped files. if (strpos($filename, '.gz')) { @rename($src, $src . '.gz'); @system('gunzip ' . escapeshellarg($src . '.gz')); } if ($filesize) { $data = @file_get_contents($src); } unlink($src); } if (!$src) { $old_address = x($_REQUEST, 'old_address') ? $_REQUEST['old_address'] : ''; if (!$old_address) { logger('mod_import: nothing to import.'); notice(t('Nothing to import.') . EOL); return; } $email = x($_REQUEST, 'email') ? $_REQUEST['email'] : ''; $password = x($_REQUEST, 'password') ? $_REQUEST['password'] : ''; $channelname = substr($old_address, 0, strpos($old_address, '@')); $servername = substr($old_address, strpos($old_address, '@') + 1); $scheme = 'https://'; $api_path = '/api/red/channel/export/basic?f=&channel=' . $channelname; if ($import_posts) { $api_path .= '&posts=1'; } $binary = false; $redirects = 0; $opts = array('http_auth' => $email . ':' . $password); $url = $scheme . $servername . $api_path; $ret = z_fetch_url($url, $binary, $redirects, $opts); if (!$ret['success']) { $ret = z_fetch_url('http://' . $servername . $api_path, $binary, $redirects, $opts); } if ($ret['success']) { $data = $ret['body']; } else { notice(t('Unable to download data from old server') . EOL); } } if (!$data) { logger('mod_import: empty file.'); notice(t('Imported file is empty.') . EOL); return; } $data = json_decode($data, true); // logger('import: data: ' . print_r($data,true)); // print_r($data); if (array_key_exists('user', $data) && array_key_exists('version', $data)) { require_once 'include/Import/import_diaspora.php'; import_diaspora($data); return; } $moving = false; if (array_key_exists('compatibility', $data) && array_key_exists('database', $data['compatibility'])) { $v1 = substr($data['compatibility']['database'], -4); $v2 = substr(DB_UPDATE_VERSION, -4); if ($v2 > $v1) { $t = sprintf(t('Warning: Database versions differ by %1$d updates.'), $v2 - $v1); notice($t); } if (array_key_exists('server_role', $data['compatibility']) && $data['compatibility']['server_role'] == 'basic') { $moving = true; } } if ($moving) { $seize = 1; } // import channel $relocate = array_key_exists('relocate', $data) ? $data['relocate'] : null; if (array_key_exists('channel', $data)) { if ($completed < 1) { $channel = import_channel($data['channel'], $account_id, $seize); } else { $r = q("select * from channel where channel_account_id = %d and channel_guid = '%s' limit 1", intval($account_id), dbesc($channel['channel_guid'])); if ($r) { $channel = $r[0]; } } if (!$channel) { logger('mod_import: channel not found. ', print_r($channel, true)); notice(t('Cloned channel not found. Import failed.') . EOL); return; } } if (!$channel) { $channel = \App::get_channel(); } if (!$channel) { logger('mod_import: channel not found. ', print_r($channel, true)); notice(t('No channel. Import failed.') . EOL); return; } if ($completed < 2) { if (is_array($data['config'])) { import_config($channel, $data['config']); } logger('import step 2'); $_SESSION['import_step'] = 2; } if ($completed < 3) { if ($data['photo']) { require_once 'include/photo/photo_driver.php'; import_channel_photo(base64url_decode($data['photo']['data']), $data['photo']['type'], $account_id, $channel['channel_id']); } if (is_array($data['profile'])) { import_profiles($channel, $data['profile']); } logger('import step 3'); $_SESSION['import_step'] = 3; } if ($completed < 4) { if (is_array($data['hubloc']) && !$moving) { import_hublocs($channel, $data['hubloc'], $seize); } logger('import step 4'); $_SESSION['import_step'] = 4; } if ($completed < 5) { // create new hubloc for the new channel at this site $r = q("insert into hubloc ( hubloc_guid, hubloc_guid_sig, hubloc_hash, hubloc_addr, hubloc_network, hubloc_primary, \n\t\t\t\thubloc_url, hubloc_url_sig, hubloc_host, hubloc_callback, hubloc_sitekey )\n\t\t\t\tvalues ( '%s', '%s', '%s', '%s', '%s', %d, '%s', '%s', '%s', '%s', '%s' )", dbesc($channel['channel_guid']), dbesc($channel['channel_guid_sig']), dbesc($channel['channel_hash']), dbesc(channel_reddress($channel)), dbesc('zot'), intval($seize ? 1 : 0), dbesc(z_root()), dbesc(base64url_encode(rsa_sign(z_root(), $channel['channel_prvkey']))), dbesc(\App::get_hostname()), dbesc(z_root() . '/post'), dbesc(get_config('system', 'pubkey'))); // reset the original primary hubloc if it is being seized if ($seize) { $r = q("update hubloc set hubloc_primary = 0 where hubloc_primary = 1 and hubloc_hash = '%s' and hubloc_url != '%s' ", dbesc($channel['channel_hash']), dbesc(z_root())); } logger('import step 5'); $_SESSION['import_step'] = 5; } if ($completed < 6) { // import xchans and contact photos if ($seize) { // replace any existing xchan we may have on this site if we're seizing control $r = q("delete from xchan where xchan_hash = '%s'", dbesc($channel['channel_hash'])); $r = q("insert into xchan ( xchan_hash, xchan_guid, xchan_guid_sig, xchan_pubkey, xchan_photo_l, xchan_photo_m, xchan_photo_s, xchan_addr, xchan_url, xchan_follow, xchan_connurl, xchan_name, xchan_network, xchan_photo_date, xchan_name_date, xchan_hidden, xchan_orphan, xchan_censored, xchan_selfcensored, xchan_system, xchan_pubforum, xchan_deleted ) values ('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, %d, %d, %d, %d, %d )", dbesc($channel['channel_hash']), dbesc($channel['channel_guid']), dbesc($channel['channel_guid_sig']), dbesc($channel['channel_pubkey']), dbesc(z_root() . "/photo/profile/l/" . $channel['channel_id']), dbesc(z_root() . "/photo/profile/m/" . $channel['channel_id']), dbesc(z_root() . "/photo/profile/s/" . $channel['channel_id']), dbesc(channel_reddress($channel)), dbesc(z_root() . '/channel/' . $channel['channel_address']), dbesc(z_root() . '/follow?f=&url=%s'), dbesc(z_root() . '/poco/' . $channel['channel_address']), dbesc($channel['channel_name']), dbesc('zot'), dbesc(datetime_convert()), dbesc(datetime_convert()), 0, 0, 0, 0, 0, 0, 0); } logger('import step 6'); $_SESSION['import_step'] = 6; } if ($completed < 7) { $xchans = $data['xchan']; if ($xchans) { foreach ($xchans as $xchan) { $hash = make_xchan_hash($xchan['xchan_guid'], $xchan['xchan_guid_sig']); if ($xchan['xchan_network'] === 'zot' && $hash !== $xchan['xchan_hash']) { logger('forged xchan: ' . print_r($xchan, true)); continue; } if (!array_key_exists('xchan_hidden', $xchan)) { $xchan['xchan_hidden'] = $xchan['xchan_flags'] & 0x1 ? 1 : 0; $xchan['xchan_orphan'] = $xchan['xchan_flags'] & 0x2 ? 1 : 0; $xchan['xchan_censored'] = $xchan['xchan_flags'] & 0x4 ? 1 : 0; $xchan['xchan_selfcensored'] = $xchan['xchan_flags'] & 0x8 ? 1 : 0; $xchan['xchan_system'] = $xchan['xchan_flags'] & 0x10 ? 1 : 0; $xchan['xchan_pubforum'] = $xchan['xchan_flags'] & 0x20 ? 1 : 0; $xchan['xchan_deleted'] = $xchan['xchan_flags'] & 0x1000 ? 1 : 0; } $r = q("select xchan_hash from xchan where xchan_hash = '%s' limit 1", dbesc($xchan['xchan_hash'])); if ($r) { continue; } dbesc_array($xchan); $r = dbq("INSERT INTO xchan (`" . implode("`, `", array_keys($xchan)) . "`) VALUES ('" . implode("', '", array_values($xchan)) . "')"); require_once 'include/photo/photo_driver.php'; $photos = import_xchan_photo($xchan['xchan_photo_l'], $xchan['xchan_hash']); if ($photos[4]) { $photodate = NULL_DATE; } else { $photodate = $xchan['xchan_photo_date']; } $r = q("update xchan set xchan_photo_l = '%s', xchan_photo_m = '%s', xchan_photo_s = '%s', xchan_photo_mimetype = '%s', xchan_photo_date = '%s'\n\t\t\t\t\t\twhere xchan_hash = '%s'", dbesc($photos[0]), dbesc($photos[1]), dbesc($photos[2]), dbesc($photos[3]), dbesc($photodate), dbesc($xchan['xchan_hash'])); } } logger('import step 7'); $_SESSION['import_step'] = 7; } // FIXME - ensure we have an xchan if somebody is trying to pull a fast one if ($completed < 8) { $friends = 0; $feeds = 0; // import contacts $abooks = $data['abook']; if ($abooks) { foreach ($abooks as $abook) { $abook_copy = $abook; $abconfig = null; if (array_key_exists('abconfig', $abook) && is_array($abook['abconfig']) && count($abook['abconfig'])) { $abconfig = $abook['abconfig']; } unset($abook['abook_id']); unset($abook['abook_rating']); unset($abook['abook_rating_text']); unset($abook['abconfig']); unset($abook['abook_their_perms']); unset($abook['abook_my_perms']); $abook['abook_account'] = $account_id; $abook['abook_channel'] = $channel['channel_id']; if (!array_key_exists('abook_blocked', $abook)) { $abook['abook_blocked'] = $abook['abook_flags'] & 0x1 ? 1 : 0; $abook['abook_ignored'] = $abook['abook_flags'] & 0x2 ? 1 : 0; $abook['abook_hidden'] = $abook['abook_flags'] & 0x4 ? 1 : 0; $abook['abook_archived'] = $abook['abook_flags'] & 0x8 ? 1 : 0; $abook['abook_pending'] = $abook['abook_flags'] & 0x10 ? 1 : 0; $abook['abook_unconnected'] = $abook['abook_flags'] & 0x20 ? 1 : 0; $abook['abook_self'] = $abook['abook_flags'] & 0x80 ? 1 : 0; $abook['abook_feed'] = $abook['abook_flags'] & 0x100 ? 1 : 0; } if ($abook['abook_self']) { $role = get_pconfig($channel['channel_id'], 'system', 'permissions_role'); if ($role === 'forum' || $abook['abook_my_perms'] & PERMS_W_TAGWALL) { q("update xchan set xchan_pubforum = 1 where xchan_hash = '%s' ", dbesc($abook['abook_xchan'])); } } else { if ($max_friends !== false && $friends > $max_friends) { continue; } if ($max_feeds !== false && intval($abook['abook_feed']) && $feeds > $max_feeds) { continue; } } dbesc_array($abook); $r = dbq("INSERT INTO abook (`" . implode("`, `", array_keys($abook)) . "`) VALUES ('" . implode("', '", array_values($abook)) . "')"); $friends++; if (intval($abook['abook_feed'])) { $feeds++; } translate_abook_perms_inbound($channel, $abook_copy); if ($abconfig) { // @fixme does not handle sync of del_abconfig foreach ($abconfig as $abc) { set_abconfig($channel['channel_id'], $abc['xchan'], $abc['cat'], $abc['k'], $abc['v']); } } } } logger('import step 8'); $_SESSION['import_step'] = 8; } if ($completed < 9) { $groups = $data['group']; if ($groups) { $saved = array(); foreach ($groups as $group) { $saved[$group['hash']] = array('old' => $group['id']); if (array_key_exists('name', $group)) { $group['gname'] = $group['name']; unset($group['name']); } unset($group['id']); $group['uid'] = $channel['channel_id']; dbesc_array($group); $r = dbq("INSERT INTO groups (`" . implode("`, `", array_keys($group)) . "`) VALUES ('" . implode("', '", array_values($group)) . "')"); } $r = q("select * from `groups` where uid = %d", intval($channel['channel_id'])); if ($r) { foreach ($r as $rr) { $saved[$rr['hash']]['new'] = $rr['id']; } } } $group_members = $data['group_member']; if ($group_members) { foreach ($group_members as $group_member) { unset($group_member['id']); $group_member['uid'] = $channel['channel_id']; foreach ($saved as $x) { if ($x['old'] == $group_member['gid']) { $group_member['gid'] = $x['new']; } } dbesc_array($group_member); $r = dbq("INSERT INTO group_member (`" . implode("`, `", array_keys($group_member)) . "`) VALUES ('" . implode("', '", array_values($group_member)) . "')"); } } logger('import step 9'); $_SESSION['import_step'] = 9; } if (is_array($data['obj'])) { import_objs($channel, $data['obj']); } if (is_array($data['likes'])) { import_likes($channel, $data['likes']); } if (is_array($data['app'])) { import_apps($channel, $data['app']); } if (is_array($data['chatroom'])) { import_chatrooms($channel, $data['chatroom']); } if (is_array($data['conv'])) { import_conv($channel, $data['conv']); } if (is_array($data['mail'])) { import_mail($channel, $data['mail']); } if (is_array($data['event'])) { import_events($channel, $data['event']); } if (is_array($data['event_item'])) { import_items($channel, $data['event_item'], false, $relocate); } if (is_array($data['menu'])) { import_menus($channel, $data['menu']); } $addon = array('channel' => $channel, 'data' => $data); call_hooks('import_channel', $addon); $saved_notification_flags = notifications_off($channel['channel_id']); if ($import_posts && array_key_exists('item', $data) && $data['item']) { import_items($channel, $data['item'], false, $relocate); } notifications_on($channel['channel_id'], $saved_notification_flags); if (array_key_exists('item_id', $data) && $data['item_id']) { import_item_ids($channel, $data['item_id']); } // FIXME - ensure we have a self entry if somebody is trying to pull a fast one // send out refresh requests // notify old server that it may no longer be primary. \Zotlabs\Daemon\Master::Summon(array('Notifier', 'location', $channel['channel_id'])); // This will indirectly perform a refresh_all *and* update the directory \Zotlabs\Daemon\Master::Summon(array('Directory', $channel['channel_id'])); notice(t('Import completed.') . EOL); change_channel($channel['channel_id']); unset($_SESSION['import_step']); goaway(z_root() . '/network'); }
function randpost_fetch(&$a, &$b) { $fort_server = get_config('fortunate', 'server'); if (!$fort_server) { return; } $r = q("select * from pconfig where cat = 'randpost' and k = 'enable'"); if ($r) { foreach ($r as $rr) { if (!$rr['v']) { continue; } // logger('randpost'); // cronhooks run every 10-15 minutes typically // try to keep from posting frequently. $test = mt_rand(0, 100); if ($test == 25) { $c = q("select * from channel where channel_id = %d limit 1", intval($rr['uid'])); if (!$c) { continue; } $mention = ''; require_once 'include/html2bbcode.php'; $s = z_fetch_url('http://' . $fort_server . '/cookie.php?numlines=2&equal=1&rand=' . mt_rand()); if (!$s['success']) { continue; } $x = array(); $x['uid'] = $c[0]['channel_id']; $x['aid'] = $c[0]['channel_account_id']; $x['mid'] = $x['parent_mid'] = item_message_id(); $x['author_xchan'] = $x['owner_xchan'] = $c[0]['channel_hash']; $x['item_thread_top'] = 1; $x['item_origin'] = 1; $x['item_verified'] = 1; $x['item_wall'] = 1; // if it might be a quote make it a quote if (strpos($s['body'], '--')) { $x['body'] = $mention . '[quote]' . html2bbcode($s['body']) . '[/quote]'; } else { $x['body'] = $mention . html2bbcode($s['body']); } $x['sig'] = base64url_encode(rsa_sign($x['body'], $c[0]['channel_prvkey'])); $post = item_store($x); $post_id = $post['item_id']; $x['id'] = $post_id; call_hooks('post_local_end', $x); Zotlabs\Daemon\Master::Summon(array('Notifier', 'wall-new', $post_id)); } } } }
function store_diaspora_comment_sig($datarray, $author, $uprvkey, $parent_item, $post_id) { // We won't be able to sign Diaspora comments for authenticated visitors - we don't have their private key $enabled = intval(get_config('system', 'diaspora_enabled')); if (!$enabled) { logger('mod_item: diaspora support disabled, not storing comment signature', LOGGER_DEBUG); return; } logger('mod_item: storing diaspora comment signature'); require_once 'include/bb2diaspora.php'; $signed_body = html_entity_decode(bb2diaspora($datarray['body'])); // Only works for NETWORK_DFRN $contact_baseurl_start = strpos($author['url'], '://') + 3; $contact_baseurl_length = strpos($author['url'], '/profile') - $contact_baseurl_start; $contact_baseurl = substr($author['url'], $contact_baseurl_start, $contact_baseurl_length); $diaspora_handle = $author['nick'] . '@' . $contact_baseurl; $signed_text = $datarray['guid'] . ';' . $parent_item['guid'] . ';' . $signed_body . ';' . $diaspora_handle; if ($uprvkey !== false) { $authorsig = base64_encode(rsa_sign($signed_text, $uprvkey, 'sha256')); } else { $authorsig = ''; } q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ", intval($post_id), dbesc($signed_text), dbesc(base64_encode($authorsig)), dbesc($diaspora_handle)); return; }
function diaspora_send_mail($item, $owner, $contact) { $a = get_app(); $myaddr = $owner['channel_address'] . '@' . get_app()->get_hostname(); $r = q("select * from conv where id = %d and uid = %d limit 1", intval($item['convid']), intval($item['channel_id'])); if (!count($r)) { logger('diaspora_send_mail: conversation not found.'); return; } $cnv = $r[0]; $conv = array('guid' => xmlify($cnv['guid']), 'subject' => xmlify($cnv['subject']), 'created_at' => xmlify(datetime_convert('UTC', 'UTC', $cnv['created'], 'Y-m-d H:i:s \\U\\T\\C')), 'diaspora_handle' => xmlify($cnv['creator']), 'participant_handles' => xmlify($cnv['recips'])); if (array_key_exists('mail_flags', $item) && $item['mail_flags'] & MAIL_OBSCURED) { $key = get_config('system', 'prvkey'); // if($item['title']) // $item['title'] = crypto_unencapsulate(json_decode_plus($item['title']),$key); if ($item['body']) { $item['body'] = crypto_unencapsulate(json_decode_plus($item['body']), $key); } } $body = bb2diaspora($item['body']); $created = datetime_convert('UTC', 'UTC', $item['created'], 'Y-m-d H:i:s \\U\\T\\C'); $signed_text = $item['mid'] . ';' . $cnv['guid'] . ';' . $body . ';' . $created . ';' . $myaddr . ';' . $cnv['guid']; $sig = base64_encode(rsa_sign($signed_text, $owner['channel_prvkey'], 'sha256')); $msg = array('guid' => xmlify($item['mid']), 'parent_guid' => xmlify($cnv['guid']), 'parent_author_signature' => $item['reply'] ? null : xmlify($sig), 'author_signature' => xmlify($sig), 'text' => xmlify($body), 'created_at' => xmlify($created), 'diaspora_handle' => xmlify($myaddr), 'conversation_guid' => xmlify($cnv['guid'])); if ($item['reply']) { $tpl = get_markup_template('diaspora_message.tpl'); $xmsg = replace_macros($tpl, array('$msg' => $msg)); } else { $conv['messages'] = array($msg); $tpl = get_markup_template('diaspora_conversation.tpl'); $xmsg = replace_macros($tpl, array('$conv' => $conv)); } logger('diaspora_conversation: ' . print_r($xmsg, true), LOGGER_DATA); $slap = 'xml=' . urlencode(urlencode(diaspora_msg_build($xmsg, $owner, $contact, $owner['channel_prvkey'], $contact['xchan_pubkey'], false))); return diaspora_transmit($owner, $contact, $slap, false); }
function diaspora_send_mail($item, $owner, $contact) { $a = get_app(); $myaddr = $owner['nickname'] . '@' . substr($a->get_baseurl(), strpos($a->get_baseurl(), '://') + 3); $r = q("select * from conv where id = %d and uid = %d limit 1", intval($item['convid']), intval($item['uid'])); if (!count($r)) { logger('diaspora_send_mail: conversation not found.'); return; } $cnv = $r[0]; $conv = array('guid' => xmlify($cnv['guid']), 'subject' => xmlify($cnv['subject']), 'created_at' => xmlify(datetime_convert('UTC', 'UTC', $cnv['created'], 'Y-m-d H:i:s \\U\\T\\C')), 'diaspora_handle' => xmlify($cnv['creator']), 'participant_handles' => xmlify($cnv['recips'])); $body = bb2diaspora($item['body']); $created = datetime_convert('UTC', 'UTC', $item['created'], 'Y-m-d H:i:s \\U\\T\\C'); $signed_text = $item['guid'] . ';' . $cnv['guid'] . ';' . $body . ';' . $created . ';' . $myaddr . ';' . $cnv['guid']; $sig = base64_encode(rsa_sign($signed_text, $owner['uprvkey'], 'sha256')); $msg = array('guid' => xmlify($item['guid']), 'parent_guid' => xmlify($cnv['guid']), 'parent_author_signature' => xmlify($sig), 'author_signature' => xmlify($sig), 'text' => xmlify($body), 'created_at' => xmlify($created), 'diaspora_handle' => xmlify($myaddr), 'conversation_guid' => xmlify($cnv['guid'])); if ($item['reply']) { $tpl = get_markup_template('diaspora_message.tpl'); $xmsg = replace_macros($tpl, array('$msg' => $msg)); } else { $conv['messages'] = array($msg); $tpl = get_markup_template('diaspora_conversation.tpl'); $xmsg = replace_macros($tpl, array('$conv' => $conv)); } logger('diaspora_conversation: ' . print_r($xmsg, true), LOGGER_DATA); $slap = 'xml=' . urlencode(urlencode(diaspora_msg_build($xmsg, $owner, $contact, $owner['uprvkey'], $contact['pubkey'], false))); //$slap = 'xml=' . urlencode(diaspora_msg_build($xmsg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],false)); return diaspora_transmit($owner, $contact, $slap, false); }
function store_diaspora_comment_sig($datarray, $channel, $parent_item, $post_id) { // We won't be able to sign Diaspora comments for authenticated visitors // - we don't have their private key // since Diaspora doesn't handle edits we can only do this for the original text and not update it. $enabled = intval(get_config('system', 'diaspora_enabled')); if (!$enabled) { logger('mod_item: diaspora support disabled, not storing comment signature', LOGGER_DEBUG); return; } $body = $datarray['body']; if (array_key_exists('item_flags', $datarray) && $datarray['item_flags'] & ITEM_OBSCURED) { $key = get_config('system', 'prvkey'); if ($datarray['body']) { $body = crypto_unencapsulate(json_decode($datarray['body'], true), $key); } } logger('mod_item: storing diaspora comment signature', LOGGER_DEBUG); require_once 'include/bb2diaspora.php'; $signed_body = html_entity_decode(bb2diaspora($body)); $diaspora_handle = $channel['channel_address'] . '@' . get_app()->get_hostname(); $signed_text = $datarray['mid'] . ';' . $parent_item['mid'] . ';' . $signed_body . ';' . $diaspora_handle; if ($uprvkey !== false) { $authorsig = base64_encode(rsa_sign($signed_text, $channel['channel_prvkey'], 'sha256')); } else { $authorsig = ''; } $x = array('signer' => $diaspora_handle, 'body' => $signed_body, 'signed_text' => $signed_text, 'signature' => base64_encode($authorsig)); $r = q("update item set diaspora_meta = '%s' where id = %d limit 1", dbesc(json_encode($x)), intval($post_id)); $r = q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ", intval($post_id), dbesc($signed_text), dbesc(base64_encode($authorsig)), dbesc($diaspora_handle)); if (!$r) { logger('store_diaspora_comment_sig: DB write failed'); } return; }
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, false); // do not strip padding $data_type = 'application/atom+xml'; $encoding = 'base64url'; $algorithm = 'RSA-SHA256'; $keyhash = base64url_encode(hash('sha256', salmon_key($owner['channel_pubkey'])), true); $data = str_replace(array(" ", "\t", "\r", "\n"), array("", "", "", ""), $data); // precomputed base64url encoding of data_type, encoding, algorithm concatenated with periods $precomputed = '.YXBwbGljYXRpb24vYXRvbSt4bWw=.YmFzZTY0dXJs.UlNBLVNIQTI1Ng=='; $signature = base64url_encode(rsa_sign($data . $precomputed, $owner['channel_prvkey'])); $salmon_tpl = get_markup_template('magicsig.tpl', 'addon/gnusoc/'); $salmon = replace_macros($salmon_tpl, array('$data' => $data, '$encoding' => $encoding, '$algorithm' => $algorithm, '$keyhash' => $keyhash, '$signature' => $signature)); logger('salmon: ' . $salmon, LOGGER_DATA); $hash = random_string(); queue_insert(array('hash' => $hash, 'account_id' => $owner['channel_account_id'], 'channel_id' => $owner['channel_id'], 'driver' => 'slap', 'posturl' => $url, 'notify' => '', 'msg' => $salmon)); return $hash; }
function fix_system_urls($oldurl, $newurl) { require_once 'include/crypto.php'; logger('fix_system_urls: renaming ' . $oldurl . ' to ' . $newurl); // Basically a site rename, but this can happen if you change from http to https for instance - even if the site name didn't change // This should fix URL changes on our site, but other sites will end up with orphan hublocs which they will try to contact and will // cause wasted communications. // What we need to do after fixing this up is to send a revocation of the old URL to every other site that we communicate with so // that they can clean up their hubloc tables (this includes directories). // It's a very expensive operation so you don't want to have to do it often or after your site gets to be large. $r = q("select xchan.*, hubloc.* from xchan left join hubloc on xchan_hash = hubloc_hash where hubloc_url like '%s'", dbesc($oldurl . '%')); if ($r) { foreach ($r as $rr) { $channel_address = substr($rr['hubloc_addr'], 0, strpos($rr['hubloc_addr'], '@')); // get the associated channel. If we don't have a local channel, do nothing for this entry. $c = q("select * from channel where channel_hash = '%s' limit 1", dbesc($rr['hubloc_hash'])); if (!$c) { continue; } $parsed = @parse_url($newurl); if (!$parsed) { continue; } $newhost = $parsed['host']; // sometimes parse_url returns unexpected results. if (strpos($newhost, '/') !== false) { $newhost = substr($newhost, 0, strpos($newhost, '/')); } $rhs = $newhost . ($parsed['port'] ? ':' . $parsed['port'] : ''); // paths aren't going to work. You have to be at the (sub)domain root // . (($parsed['path']) ? $parsed['path'] : ''); // The xchan_url might point to another nomadic identity clone $replace_xchan_url = strpos($rr['xchan_url'], $oldurl) !== false ? true : false; $x = q("update xchan set xchan_addr = '%s', xchan_url = '%s', xchan_connurl = '%s', xchan_follow = '%s', xchan_connpage = '%s', xchan_photo_l = '%s', xchan_photo_m = '%s', xchan_photo_s = '%s', xchan_photo_date = '%s' where xchan_hash = '%s'", dbesc($channel_address . '@' . $rhs), dbesc($replace_xchan_url ? str_replace($oldurl, $newurl, $rr['xchan_url']) : $rr['xchan_url']), dbesc(str_replace($oldurl, $newurl, $rr['xchan_connurl'])), dbesc(str_replace($oldurl, $newurl, $rr['xchan_follow'])), dbesc(str_replace($oldurl, $newurl, $rr['xchan_connpage'])), dbesc(str_replace($oldurl, $newurl, $rr['xchan_photo_l'])), dbesc(str_replace($oldurl, $newurl, $rr['xchan_photo_m'])), dbesc(str_replace($oldurl, $newurl, $rr['xchan_photo_s'])), dbesc(datetime_convert()), dbesc($rr['xchan_hash'])); $y = q("update hubloc set hubloc_addr = '%s', hubloc_url = '%s', hubloc_url_sig = '%s', hubloc_host = '%s', hubloc_callback = '%s' where hubloc_hash = '%s' and hubloc_url = '%s'", dbesc($channel_address . '@' . $rhs), dbesc($newurl), dbesc(base64url_encode(rsa_sign($newurl, $c[0]['channel_prvkey']))), dbesc($newhost), dbesc($newurl . '/post'), dbesc($rr['xchan_hash']), dbesc($oldurl)); $z = q("update profile set photo = '%s', thumb = '%s' where uid = %d", dbesc(str_replace($oldurl, $newurl, $rr['xchan_photo_l'])), dbesc(str_replace($oldurl, $newurl, $rr['xchan_photo_m'])), intval($c[0]['channel_id'])); proc_run('php', 'include/notifier.php', 'refresh_all', $c[0]['channel_id']); } } }