function new_contact($uid, $url, $channel, $interactive = false, $confirm = false) { $result = array('success' => false, 'message' => ''); $is_red = false; $is_http = strpos($url, '://') !== false ? true : false; if ($is_http && substr($url, -1, 1) === '/') { $url = substr($url, 0, -1); } if (!allowed_url($url)) { $result['message'] = t('Channel is blocked on this site.'); return $result; } if (!$url) { $result['message'] = t('Channel location missing.'); return $result; } // check service class limits $r = q("select count(*) as total from abook where abook_channel = %d and abook_self = 0 ", intval($uid)); if ($r) { $total_channels = $r[0]['total']; } if (!service_class_allows($uid, 'total_channels', $total_channels)) { $result['message'] = upgrade_message(); return $result; } $arr = array('url' => $url, 'channel' => array()); call_hooks('follow', $arr); if ($arr['channel']['success']) { $ret = $arr['channel']; } elseif (!$is_http) { $ret = Zotlabs\Zot\Finger::run($url, $channel); } if ($ret && is_array($ret) && $ret['success']) { $is_red = true; $j = $ret; } $my_perms = get_channel_default_perms($uid); $role = get_pconfig($uid, 'system', 'permissions_role'); if ($role) { $x = \Zotlabs\Access\PermissionRoles::role_perms($role); if ($x['perms_connect']) { $my_perms = $x['perms_connect']; } } if ($is_red && $j) { logger('follow: ' . $url . ' ' . print_r($j, true), LOGGER_DEBUG); if (!($j['success'] && $j['guid'])) { $result['message'] = t('Response from remote channel was incomplete.'); logger('mod_follow: ' . $result['message']); return $result; } // Premium channel, set confirm before callback to avoid recursion if (array_key_exists('connect_url', $j) && $interactive && !$confirm) { goaway(zid($j['connect_url'])); } // do we have an xchan and hubloc? // If not, create them. $x = import_xchan($j); if (array_key_exists('deleted', $j) && intval($j['deleted'])) { $result['message'] = t('Channel was deleted and no longer exists.'); return $result; } if (!$x['success']) { return $x; } $xchan_hash = $x['hash']; if (array_key_exists('permissions', $j) && array_key_exists('data', $j['permissions'])) { $permissions = crypto_unencapsulate(array('data' => $j['permissions']['data'], 'key' => $j['permissions']['key'], 'iv' => $j['permissions']['iv']), $channel['channel_prvkey']); if ($permissions) { $permissions = json_decode($permissions, true); } logger('decrypted permissions: ' . print_r($permissions, true), LOGGER_DATA); } else { $permissions = $j['permissions']; } if (is_array($permissions) && $permissions) { foreach ($permissions as $k => $v) { set_abconfig($channel['channel_uid'], $xchan_hash, 'their_perms', $k, intval($v)); } } } else { $xchan_hash = ''; $r = q("select * from xchan where xchan_hash = '%s' or xchan_url = '%s' limit 1", dbesc($url), dbesc($url)); if (!$r) { // attempt network auto-discovery $d = discover_by_webbie($url); if (!$d && $is_http) { // try RSS discovery if (get_config('system', 'feed_contacts')) { $d = discover_by_url($url); } else { $result['message'] = t('Protocol disabled.'); return $result; } } if ($d) { $r = q("select * from xchan where xchan_hash = '%s' or xchan_url = '%s' limit 1", dbesc($url), dbesc($url)); } } // if discovery was a success we should have an xchan record in $r if ($r) { $xchan = $r[0]; $xchan_hash = $r[0]['xchan_hash']; $their_perms = 0; } } if (!$xchan_hash) { $result['message'] = t('Channel discovery failed.'); logger('follow: ' . $result['message']); return $result; } $allowed = $is_red || $r[0]['xchan_network'] === 'rss' ? 1 : 0; $x = array('channel_id' => $uid, 'follow_address' => $url, 'xchan' => $r[0], 'allowed' => $allowed, 'singleton' => 0); call_hooks('follow_allow', $x); if (!$x['allowed']) { $result['message'] = t('Protocol disabled.'); return $result; } $singleton = intval($x['singleton']); $aid = $channel['channel_account_id']; $hash = get_observer_hash(); $default_group = $channel['channel_default_group']; if ($xchan['xchan_network'] === 'rss') { // check service class feed limits $r = q("select count(*) as total from abook where abook_account = %d and abook_feed = 1 ", intval($aid)); if ($r) { $total_feeds = $r[0]['total']; } if (!service_class_allows($uid, 'total_feeds', $total_feeds)) { $result['message'] = upgrade_message(); return $result; } } if ($hash == $xchan_hash) { $result['message'] = t('Cannot connect to yourself.'); return $result; } $r = q("select abook_xchan, abook_instance from abook where abook_xchan = '%s' and abook_channel = %d limit 1", dbesc($xchan_hash), intval($uid)); if ($is_http) { // Always set these "remote" permissions for feeds since we cannot interact with them // to negotiate a suitable permission response set_abconfig($uid, $xchan_hash, 'their_perms', 'view_stream', 1); set_abconfig($uid, $xchan_hash, 'their_perms', 'republish', 1); } if ($r) { $abook_instance = $r[0]['abook_instance']; if ($singleton && strpos($abook_instance, z_root()) === false) { if ($abook_instance) { $abook_instance .= ','; } $abook_instance .= z_root(); } $x = q("update abook set abook_instance = '%s' where abook_id = %d", dbesc($abook_instance), intval($r[0]['abook_id'])); } else { $closeness = get_pconfig($uid, 'system', 'new_abook_closeness'); if ($closeness === false) { $closeness = 80; } $r = q("insert into abook ( abook_account, abook_channel, abook_closeness, abook_xchan, abook_feed, abook_created, abook_updated, abook_instance )\n\t\t\tvalues( %d, %d, %d, '%s', %d, '%s', '%s', '%s' ) ", intval($aid), intval($uid), intval($closeness), dbesc($xchan_hash), intval($is_http ? 1 : 0), dbesc(datetime_convert()), dbesc(datetime_convert()), dbesc($singleton ? z_root() : '')); } if (!$r) { logger('mod_follow: abook creation failed'); } $all_perms = \Zotlabs\Access\Permissions::Perms(); if ($all_perms) { foreach ($all_perms as $k => $v) { if (in_array($k, $my_perms)) { set_abconfig($uid, $xchan_hash, 'my_perms', $k, 1); } else { set_abconfig($uid, $xchan_hash, 'my_perms', $k, 0); } } } $r = q("select abook.*, xchan.* from abook left join xchan on abook_xchan = xchan_hash \n\t\twhere abook_xchan = '%s' and abook_channel = %d limit 1", dbesc($xchan_hash), intval($uid)); if ($r) { $result['abook'] = $r[0]; Zotlabs\Daemon\Master::Summon(array('Notifier', 'permission_create', $result['abook']['abook_id'])); } $arr = array('channel_id' => $uid, 'channel' => $channel, 'abook' => $result['abook']); call_hooks('follow', $arr); /** If there is a default group for this channel, add this connection to it */ if ($default_group) { require_once 'include/group.php'; $g = group_rec_byhash($uid, $default_group); if ($g) { group_add_member($uid, '', $xchan_hash, $g['id']); } } $result['success'] = true; return $result; }
function new_contact($uid, $url, $interactive = false) { $result = array('cid' => -1, 'success' => false, 'message' => ''); $a = get_app(); // remove ajax junk, e.g. Twitter $url = str_replace('/#!/', '/', $url); if (!allowed_url($url)) { $result['message'] = t('Disallowed profile URL.'); return $result; } if (!$url) { $result['message'] = t('Connect URL missing.'); return $result; } $arr = array('url' => $url, 'contact' => array()); call_hooks('follow', $arr); if (x($arr['contact'], 'name')) { $ret = $arr['contact']; } else { $ret = probe_url($url); } if ($ret['network'] === NETWORK_DFRN) { if ($interactive) { if (strlen($a->path)) { $myaddr = bin2hex($a->get_baseurl() . '/profile/' . $a->user['nickname']); } else { $myaddr = bin2hex($a->user['nickname'] . '@' . $a->get_hostname()); } goaway($ret['request'] . "&addr={$myaddr}"); // NOTREACHED } } else { if (get_config('system', 'dfrn_only')) { $result['message'] = t('This site is not configured to allow communications with other networks.') . EOL; $result['message'] != t('No compatible communication protocols or feeds were discovered.') . EOL; return $result; } } // This extra param just confuses things, remove it if ($ret['network'] === NETWORK_DIASPORA) { $ret['url'] = str_replace('?absolute=true', '', $ret['url']); } // do we have enough information? if (!(x($ret, 'name') && x($ret, 'poll') && (x($ret, 'url') || x($ret, 'addr')))) { $result['message'] .= t('The profile address specified does not provide adequate information.') . EOL; if (!x($ret, 'poll')) { $result['message'] .= t('No compatible communication protocols or feeds were discovered.') . EOL; } if (!x($ret, 'name')) { $result['message'] .= t('An author or name was not found.') . EOL; } if (!x($ret, 'url')) { $result['message'] .= t('No browser URL could be matched to this address.') . EOL; } if (strpos($url, '@') !== false) { $result['message'] .= t('Unable to match @-style Identity Address with a known protocol or email contact.') . EOL; $result['message'] .= t('Use mailto: in front of address to force email check.') . EOL; } return $result; } if ($ret['network'] === NETWORK_OSTATUS && get_config('system', 'ostatus_disabled')) { $result['message'] .= t('The profile address specified belongs to a network which has been disabled on this site.') . EOL; $ret['notify'] = ''; } if (!$ret['notify']) { $result['message'] .= t('Limited profile. This person will be unable to receive direct/personal notifications from you.') . EOL; } $writeable = $ret['network'] === NETWORK_OSTATUS && $ret['notify'] ? 1 : 0; $subhub = $ret['network'] === NETWORK_OSTATUS ? true : false; $hidden = $ret['network'] === NETWORK_MAIL ? 1 : 0; if ($ret['network'] === NETWORK_MAIL) { $writeable = 1; } if ($ret['network'] === NETWORK_DIASPORA) { $writeable = 1; } // check if we already have a contact // the poll url is more reliable than the profile url, as we may have // indirect links or webfinger links $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `poll` IN ('%s', '%s') AND `network` = '%s' LIMIT 1", intval($uid), dbesc($ret['poll']), dbesc(normalise_link($ret['poll'])), dbesc($ret['network'])); if (!count($r)) { $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `nurl` = '%s' AND `network` = '%s' LIMIT 1", intval($uid), dbesc(normalise_link($url)), dbesc($ret['network'])); } if (count($r)) { // update contact if ($r[0]['rel'] == CONTACT_IS_FOLLOWER || $network === NETWORK_DIASPORA && $r[0]['rel'] == CONTACT_IS_SHARING) { q("UPDATE `contact` SET `rel` = %d , `subhub` = %d, `readonly` = 0 WHERE `id` = %d AND `uid` = %d", intval(CONTACT_IS_FRIEND), intval($subhub), intval($r[0]['id']), intval($uid)); } } else { // check service class limits $r = q("select count(*) as total from contact where uid = %d and pending = 0 and self = 0", intval($uid)); if (count($r)) { $total_contacts = $r[0]['total']; } if (!service_class_allows($uid, 'total_contacts', $total_contacts)) { $result['message'] .= upgrade_message(); return $result; } $r = q("select count(network) as total from contact where uid = %d and network = '%s' and pending = 0 and self = 0", intval($uid), dbesc($network)); if (count($r)) { $total_network = $r[0]['total']; } if (!service_class_allows($uid, 'total_contacts_' . $network, $total_network)) { $result['message'] .= upgrade_message(); return $result; } $new_relation = $ret['network'] === NETWORK_MAIL ? CONTACT_IS_FRIEND : CONTACT_IS_SHARING; if ($ret['network'] === NETWORK_DIASPORA) { $new_relation = CONTACT_IS_FOLLOWER; } // create contact record $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `addr`, `alias`, `batch`, `notify`, `poll`, `poco`, `name`, `nick`, `network`, `pubkey`, `rel`, `priority`,\n\t\t\t`writable`, `hidden`, `blocked`, `readonly`, `pending`, `subhub` )\n\t\t\tVALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, %d, %d, 0, 0, 0, %d ) ", intval($uid), dbesc(datetime_convert()), dbesc($ret['url']), dbesc(normalise_link($ret['url'])), dbesc($ret['addr']), dbesc($ret['alias']), dbesc($ret['batch']), dbesc($ret['notify']), dbesc($ret['poll']), dbesc($ret['poco']), dbesc($ret['name']), dbesc($ret['nick']), dbesc($ret['network']), dbesc($ret['pubkey']), intval($new_relation), intval($ret['priority']), intval($writeable), intval($hidden), intval($subhub)); } $r = q("SELECT * FROM `contact` WHERE `url` = '%s' AND `network` = '%s' AND `uid` = %d LIMIT 1", dbesc($ret['url']), dbesc($ret['network']), intval($uid)); if (!count($r)) { $result['message'] .= t('Unable to retrieve contact information.') . EOL; return $result; } $contact = $r[0]; $contact_id = $r[0]['id']; $result['cid'] = $contact_id; $g = q("select def_gid from user where uid = %d limit 1", intval($uid)); if ($g && intval($g[0]['def_gid'])) { require_once 'include/group.php'; group_add_member($uid, '', $contact_id, $g[0]['def_gid']); } require_once "include/Photo.php"; $photos = import_profile_photo($ret['photo'], $uid, $contact_id); $r = q("UPDATE `contact` SET `photo` = '%s',\n\t\t\t`thumb` = '%s',\n\t\t\t`micro` = '%s',\n\t\t\t`name-date` = '%s',\n\t\t\t`uri-date` = '%s',\n\t\t\t`avatar-date` = '%s'\n\t\t\tWHERE `id` = %d", dbesc($photos[0]), dbesc($photos[1]), dbesc($photos[2]), dbesc(datetime_convert()), dbesc(datetime_convert()), dbesc(datetime_convert()), intval($contact_id)); // pull feed and consume it, which should subscribe to the hub. proc_run('php', "include/onepoll.php", "{$contact_id}", "force"); // create a follow slap $tpl = get_markup_template('follow_slap.tpl'); $slap = replace_macros($tpl, array('$name' => $a->user['username'], '$profile_page' => $a->get_baseurl() . '/profile/' . $a->user['nickname'], '$photo' => $a->contact['photo'], '$thumb' => $a->contact['thumb'], '$published' => datetime_convert('UTC', 'UTC', 'now', ATOM_TIME), '$item_id' => 'urn:X-dfrn:' . $a->get_hostname() . ':follow:' . get_guid(32), '$title' => '', '$type' => 'text', '$content' => t('following'), '$nick' => $a->user['nickname'], '$verb' => ACTIVITY_FOLLOW, '$ostat_follow' => '')); $r = q("SELECT `contact`.*, `user`.* FROM `contact` INNER JOIN `user` ON `contact`.`uid` = `user`.`uid`\n\t\t\tWHERE `user`.`uid` = %d AND `contact`.`self` = 1 LIMIT 1", intval($uid)); if (count($r)) { if ($contact['network'] == NETWORK_OSTATUS && strlen($contact['notify'])) { require_once 'include/salmon.php'; slapper($r[0], $contact['notify'], $slap); } if ($contact['network'] == NETWORK_DIASPORA) { require_once 'include/diaspora.php'; $ret = diaspora_share($a->user, $contact); logger('mod_follow: diaspora_share returns: ' . $ret); } } $result['success'] = true; return $result; }
function new_contact($uid, $url, $channel, $interactive = false, $confirm = false) { $result = array('success' => false, 'message' => ''); $a = get_app(); $is_red = false; $is_http = strpos($url, '://') !== false ? true : false; if ($is_http && substr($url, -1, 1) === '/') { $url = substr($url, 0, -1); } if (!allowed_url($url)) { $result['message'] = t('Channel is blocked on this site.'); return $result; } if (!$url) { $result['message'] = t('Channel location missing.'); return $result; } // check service class limits $r = q("select count(*) as total from abook where abook_channel = %d and abook_self = 0 ", intval($uid)); if ($r) { $total_channels = $r[0]['total']; } if (!service_class_allows($uid, 'total_channels', $total_channels)) { $result['message'] = upgrade_message(); return $result; } $arr = array('url' => $url, 'channel' => array()); call_hooks('follow', $arr); if ($arr['channel']['success']) { $ret = $arr['channel']; } elseif (!$is_http) { $ret = zot_finger($url, $channel); } if ($ret && $ret['success']) { $is_red = true; $j = json_decode($ret['body'], true); } $my_perms = get_channel_default_perms($uid); $role = get_pconfig($uid, 'system', 'permissions_role'); if ($role) { $x = get_role_perms($role); if ($x['perms_follow']) { $my_perms = $x['perms_follow']; } } if ($is_red && $j) { logger('follow: ' . $url . ' ' . print_r($j, true), LOGGER_DEBUG); if (!($j['success'] && $j['guid'])) { $result['message'] = t('Response from remote channel was incomplete.'); logger('mod_follow: ' . $result['message']); return $result; } // Premium channel, set confirm before callback to avoid recursion if (array_key_exists('connect_url', $j) && $interactive && !$confirm) { goaway(zid($j['connect_url'])); } // do we have an xchan and hubloc? // If not, create them. $x = import_xchan($j); if (array_key_exists('deleted', $j) && intval($j['deleted'])) { $result['message'] = t('Channel was deleted and no longer exists.'); return $result; } if (!$x['success']) { return $x; } $xchan_hash = $x['hash']; $their_perms = 0; $global_perms = get_perms(); if (array_key_exists('permissions', $j) && array_key_exists('data', $j['permissions'])) { $permissions = crypto_unencapsulate(array('data' => $j['permissions']['data'], 'key' => $j['permissions']['key'], 'iv' => $j['permissions']['iv']), $channel['channel_prvkey']); if ($permissions) { $permissions = json_decode($permissions, true); } logger('decrypted permissions: ' . print_r($permissions, true), LOGGER_DATA); } else { $permissions = $j['permissions']; } foreach ($permissions as $k => $v) { if ($v) { $their_perms = $their_perms | intval($global_perms[$k][1]); } } } else { $their_perms = 0; $xchan_hash = ''; $r = q("select * from xchan where xchan_hash = '%s' or xchan_url = '%s' limit 1", dbesc($url), dbesc($url)); if (!$r) { // attempt network auto-discovery if (strpos($url, '@') && !$is_http) { $r = discover_by_webbie($url); } elseif ($is_http) { $r = discover_by_url($url); $r['allowed'] = intval(get_config('system', 'feed_contacts')); } if ($r) { $r['channel_id'] = $uid; call_hooks('follow_allow', $r); if (!$r['allowed']) { $result['message'] = t('Protocol disabled.'); return $result; } $r = q("select * from xchan where xchan_hash = '%s' or xchan_url = '%s' limit 1", dbesc($url), dbesc($url)); } } if ($r) { $xchan_hash = $r[0]['xchan_hash']; $their_perms = 0; } } if (!$xchan_hash) { $result['message'] = t('Channel discovery failed.'); logger('follow: ' . $result['message']); return $result; } if (local_channel() && $uid == local_channel()) { $aid = get_account_id(); $hash = get_observer_hash(); $ch = $a->get_channel(); $default_group = $ch['channel_default_group']; } else { $r = q("select * from channel where channel_id = %d limit 1", intval($uid)); if (!$r) { $result['message'] = t('local account not found.'); return $result; } $aid = $r[0]['channel_account_id']; $hash = $r[0]['channel_hash']; $default_group = $r[0]['channel_default_group']; } if ($is_http) { $r = q("select count(*) as total from abook where abook_account = %d and abook_feed = 1 ", intval($aid)); if ($r) { $total_feeds = $r[0]['total']; } if (!service_class_allows($uid, 'total_feeds', $total_feeds)) { $result['message'] = upgrade_message(); return $result; } } if ($hash == $xchan_hash) { $result['message'] = t('Cannot connect to yourself.'); return $result; } $r = q("select abook_xchan from abook where abook_xchan = '%s' and abook_channel = %d limit 1", dbesc($xchan_hash), intval($uid)); if ($r) { $x = q("update abook set abook_their_perms = %d where abook_id = %d", intval($their_perms), intval($r[0]['abook_id'])); } else { $closeness = get_pconfig($uid, 'system', 'new_abook_closeness'); if ($closeness === false) { $closeness = 80; } $r = q("insert into abook ( abook_account, abook_channel, abook_closeness, abook_xchan, abook_feed, abook_their_perms, abook_my_perms, abook_created, abook_updated )\n\t\t\tvalues( %d, %d, %d, '%s', %d, %d, %d, '%s', '%s' ) ", intval($aid), intval($uid), intval($closeness), dbesc($xchan_hash), intval($is_http ? 1 : 0), intval($is_http ? $their_perms | PERMS_R_STREAM | PERMS_A_REPUBLISH : $their_perms), intval($my_perms), dbesc(datetime_convert()), dbesc(datetime_convert())); } if (!$r) { logger('mod_follow: abook creation failed'); } $r = q("select abook.*, xchan.* from abook left join xchan on abook_xchan = xchan_hash \n\t\twhere abook_xchan = '%s' and abook_channel = %d limit 1", dbesc($xchan_hash), intval($uid)); if ($r) { $result['abook'] = $r[0]; proc_run('php', 'include/notifier.php', 'permission_update', $result['abook']['abook_id']); } $arr = array('channel_id' => $uid, 'abook' => $result['abook']); call_hooks('follow', $arr); /** If there is a default group for this channel, add this member to it */ if ($default_group) { require_once 'include/group.php'; $g = group_rec_byhash($uid, $default_group); if ($g) { group_add_member($uid, '', $xchan_hash, $g['id']); } } $result['success'] = true; return $result; }
function dfrn_request_post(&$a) { if ($a->argc != 2 || !count($a->profile)) { return; } if ($_POST['cancel']) { goaway(z_root()); } /** * * Scenario 2: We've introduced ourself to another cell, then have been returned to our own cell * to confirm the request, and then we've clicked submit (perhaps after logging in). * That brings us here: * */ if (x($_POST, 'localconfirm') && $_POST['localconfirm'] == 1) { /** * Ensure this is a valid request */ if (local_user() && $a->user['nickname'] == $a->argv[1] && x($_POST, 'dfrn_url')) { $dfrn_url = notags(trim($_POST['dfrn_url'])); $aes_allow = x($_POST, 'aes_allow') && $_POST['aes_allow'] == 1 ? 1 : 0; $confirm_key = x($_POST, 'confirm_key') ? $_POST['confirm_key'] : ""; $contact_record = null; if (x($dfrn_url)) { /** * Lookup the contact based on their URL (which is the only unique thing we have at the moment) */ $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `self` = 0 LIMIT 1", intval(local_user()), dbesc($dfrn_url)); if (count($r)) { if (strlen($r[0]['dfrn-id'])) { /** * We don't need to be here. It has already happened. */ notice(t("This introduction has already been accepted.") . EOL); return; } else { $contact_record = $r[0]; } } if (is_array($contact_record)) { $r = q("UPDATE `contact` SET `ret-aes` = %d WHERE `id` = %d LIMIT 1", intval($aes_allow), intval($contact_record['id'])); } else { /** * Scrape the other site's profile page to pick up the dfrn links, key, fn, and photo */ require_once 'Scrape.php'; $parms = scrape_dfrn($dfrn_url); if (!count($parms)) { notice(t('Profile location is not valid or does not contain profile information.') . EOL); return; } else { if (!x($parms, 'fn')) { notice(t('Warning: profile location has no identifiable owner name.') . EOL); } if (!x($parms, 'photo')) { notice(t('Warning: profile location has no profile photo.') . EOL); } $invalid = validate_dfrn($parms); if ($invalid) { notice(sprintf(tt("%d required parameter was not found at the given location", "%d required parameters were not found at the given location", $invalid), $invalid) . EOL); return; } } $dfrn_request = $parms['dfrn-request']; /********* Escape the entire array ********/ dbesc_array($parms); /******************************************/ /** * Create a contact record on our site for the other person */ $r = q("INSERT INTO `contact` ( `uid`, `created`,`url`, `nurl`, `name`, `nick`, `photo`, `site-pubkey`,\n\t\t\t\t\t\t`request`, `confirm`, `notify`, `poll`, `poco`, `network`, `aes_allow`) \n\t\t\t\t\t\tVALUES ( %d, '%s', '%s', '%s', '%s' , '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d)", intval(local_user()), datetime_convert(), dbesc($dfrn_url), dbesc(normalise_link($dfrn_url)), $parms['fn'], $parms['nick'], $parms['photo'], $parms['key'], $parms['dfrn-request'], $parms['dfrn-confirm'], $parms['dfrn-notify'], $parms['dfrn-poll'], $parms['dfrn-poco'], dbesc(NETWORK_DFRN), intval($aes_allow)); } if ($r) { info(t("Introduction complete.") . EOL); } /** * Allow the blocked remote notification to complete */ if (is_array($contact_record)) { $dfrn_request = $contact_record['request']; } if (strlen($dfrn_request) && strlen($confirm_key)) { $s = fetch_url($dfrn_request . '?confirm_key=' . $confirm_key); } // (ignore reply, nothing we can do it failed) goaway($dfrn_url); return; // NOTREACHED } } // invalid/bogus request notice(t('Unrecoverable protocol error.') . EOL); goaway(z_root()); return; // NOTREACHED } /** * Otherwise: * * Scenario 1: * We are the requestee. A person from a remote cell has made an introduction * on our profile web page and clicked submit. We will use their DFRN-URL to * figure out how to contact their cell. * * Scrape the originating DFRN-URL for everything we need. Create a contact record * and an introduction to show our user next time he/she logs in. * Finally redirect back to the requestor so that their site can record the request. * If our user (the requestee) later confirms this request, a record of it will need * to exist on the requestor's cell in order for the confirmation process to complete.. * * It's possible that neither the requestor or the requestee are logged in at the moment, * and the requestor does not yet have any credentials to the requestee profile. * * Who is the requestee? We've already loaded their profile which means their nickname should be * in $a->argv[1] and we should have their complete info in $a->profile. * */ if (!(is_array($a->profile) && count($a->profile))) { notice(t('Profile unavailable.') . EOL); return; } $nickname = $a->profile['nickname']; $notify_flags = $a->profile['notify-flags']; $uid = $a->profile['uid']; $maxreq = intval($a->profile['maxreq']); $contact_record = null; $failed = false; $parms = null; if (x($_POST, 'dfrn_url')) { /** * Block friend request spam */ if ($maxreq) { $r = q("SELECT * FROM `intro` WHERE `datetime` > '%s' AND `uid` = %d", dbesc(datetime_convert('UTC', 'UTC', 'now - 24 hours')), intval($uid)); if (count($r) > $maxreq) { notice(sprintf(t('%s has received too many connection requests today.'), $a->profile['name']) . EOL); notice(t('Spam protection measures have been invoked.') . EOL); notice(t('Friends are advised to please try again in 24 hours.') . EOL); return; } } /** * * Cleanup old introductions that remain blocked. * Also remove the contact record, but only if there is no existing relationship * Do not remove email contacts as these may be awaiting email verification */ $r = q("SELECT `intro`.*, `intro`.`id` AS `iid`, `contact`.`id` AS `cid`, `contact`.`rel` \n\t\t\tFROM `intro` LEFT JOIN `contact` on `intro`.`contact-id` = `contact`.`id`\n\t\t\tWHERE `intro`.`blocked` = 1 AND `contact`.`self` = 0 \n\t\t\tAND `contact`.`network` != '%s'\n\t\t\tAND `intro`.`datetime` < UTC_TIMESTAMP() - INTERVAL 30 MINUTE ", dbesc(NETWORK_MAIL)); if (count($r)) { foreach ($r as $rr) { if (!$rr['rel']) { q("DELETE FROM `contact` WHERE `id` = %d LIMIT 1", intval($rr['cid'])); } q("DELETE FROM `intro` WHERE `id` = %d LIMIT 1", intval($rr['iid'])); } } /** * * Cleanup any old email intros - which will have a greater lifetime */ $r = q("SELECT `intro`.*, `intro`.`id` AS `iid`, `contact`.`id` AS `cid`, `contact`.`rel` \n\t\t\tFROM `intro` LEFT JOIN `contact` on `intro`.`contact-id` = `contact`.`id`\n\t\t\tWHERE `intro`.`blocked` = 1 AND `contact`.`self` = 0 \n\t\t\tAND `contact`.`network` = '%s'\n\t\t\tAND `intro`.`datetime` < UTC_TIMESTAMP() - INTERVAL 3 DAY ", dbesc(NETWORK_MAIL)); if (count($r)) { foreach ($r as $rr) { if (!$rr['rel']) { q("DELETE FROM `contact` WHERE `id` = %d LIMIT 1", intval($rr['cid'])); } q("DELETE FROM `intro` WHERE `id` = %d LIMIT 1", intval($rr['iid'])); } } $url = trim($_POST['dfrn_url']); if (!strlen($url)) { notice(t("Invalid locator") . EOL); return; } // Canonicalise email-style profile locator $hcard = ''; $url = webfinger_dfrn($url, $hcard); if (substr($url, 0, 5) === 'stat:') { $network = NETWORK_OSTATUS; $url = substr($url, 5); } else { $network = NETWORK_DFRN; } logger('dfrn_request: url: ' . $url); if (!strlen($url)) { notice(t("Unable to resolve your name at the provided location.") . EOL); return; } if ($network === NETWORK_DFRN) { $ret = q("SELECT * FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `self` = 0 LIMIT 1", intval($uid), dbesc($url)); if (count($ret)) { if (strlen($ret[0]['issued-id'])) { notice(t('You have already introduced yourself here.') . EOL); return; } elseif ($ret[0]['rel'] == CONTACT_IS_FRIEND) { notice(sprintf(t('Apparently you are already friends with %s.'), $a->profile['name']) . EOL); return; } else { $contact_record = $ret[0]; $parms = array('dfrn-request' => $ret[0]['request']); } } $issued_id = random_string(); if (is_array($contact_record)) { // There is a contact record but no issued-id, so this // is a reciprocal introduction from a known contact $r = q("UPDATE `contact` SET `issued-id` = '%s' WHERE `id` = %d LIMIT 1", dbesc($issued_id), intval($contact_record['id'])); } else { if (!validate_url($url)) { notice(t('Invalid profile URL.') . EOL); goaway($a->get_baseurl() . '/' . $a->cmd); return; // NOTREACHED } if (!allowed_url($url)) { notice(t('Disallowed profile URL.') . EOL); goaway($a->get_baseurl() . '/' . $a->cmd); return; // NOTREACHED } require_once 'Scrape.php'; $parms = scrape_dfrn($hcard ? $hcard : $url); if (!count($parms)) { notice(t('Profile location is not valid or does not contain profile information.') . EOL); goaway($a->get_baseurl() . '/' . $a->cmd); } else { if (!x($parms, 'fn')) { notice(t('Warning: profile location has no identifiable owner name.') . EOL); } if (!x($parms, 'photo')) { notice(t('Warning: profile location has no profile photo.') . EOL); } $invalid = validate_dfrn($parms); if ($invalid) { notice(sprintf(tt("%d required parameter was not found at the given location", "%d required parameters were not found at the given location", $invalid), $invalid) . EOL); return; } } $parms['url'] = $url; $parms['issued-id'] = $issued_id; dbesc_array($parms); $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`,`name`, `nick`, `issued-id`, `photo`, `site-pubkey`,\n\t\t\t\t\t`request`, `confirm`, `notify`, `poll`, `poco`, `network` )\n\t\t\t\t\tVALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s' )", intval($uid), dbesc(datetime_convert()), $parms['url'], dbesc(normalise_link($parms['url'])), $parms['fn'], $parms['nick'], $parms['issued-id'], $parms['photo'], $parms['key'], $parms['dfrn-request'], $parms['dfrn-confirm'], $parms['dfrn-notify'], $parms['dfrn-poll'], $parms['dfrn-poco'], dbesc(NETWORK_DFRN)); // find the contact record we just created if ($r) { $r = q("SELECT `id` FROM `contact` \n\t\t\t\t\t\tWHERE `uid` = %d AND `url` = '%s' AND `issued-id` = '%s' LIMIT 1", intval($uid), $parms['url'], $parms['issued-id']); if (count($r)) { $contact_record = $r[0]; } } } if ($r === false) { notice(t('Failed to update contact record.') . EOL); return; } $hash = random_string() . (string) time(); // Generate a confirm_key if (is_array($contact_record)) { $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `note`, `hash`, `datetime`)\n\t\t\t\t\tVALUES ( %d, %d, 1, %d, '%s', '%s', '%s' )", intval($uid), intval($contact_record['id']), x($_POST, 'knowyou') && $_POST['knowyou'] == 1 ? 1 : 0, dbesc(notags(trim($_POST['dfrn-request-message']))), dbesc($hash), dbesc(datetime_convert())); } // This notice will only be seen by the requestor if the requestor and requestee are on the same server. if (!$failed) { info(t('Your introduction has been sent.') . EOL); } // "Homecoming" - send the requestor back to their site to record the introduction. $dfrn_url = bin2hex($a->get_baseurl() . '/profile/' . $nickname); $aes_allow = function_exists('openssl_encrypt') ? 1 : 0; goaway($parms['dfrn-request'] . "?dfrn_url={$dfrn_url}" . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . '&confirm_key=' . $hash . ($aes_allow ? "&aes_allow=1" : "")); // NOTREACHED // END $network === NETWORK_DFRN } elseif ($network === NETWORK_OSTATUS) { /** * * OStatus network * Check contact existence * Try and scrape together enough information to create a contact record, * with us as CONTACT_IS_FOLLOWER * Substitute our user's feed URL into $url template * Send the subscriber home to subscribe * */ $url = str_replace('{uri}', $a->get_baseurl() . '/dfrn_poll/' . $nickname, $url); goaway($url); // NOTREACHED // END $network === NETWORK_OSTATUS } } return; }
function follow_init(&$a) { if (!local_user()) { notice(t('Permission denied.') . EOL); goaway($_SESSION['return_url']); // NOTREACHED } $url = $orig_url = notags(trim($_REQUEST['url'])); // remove ajax junk, e.g. Twitter $url = str_replace('/#!/', '/', $url); if (!allowed_url($url)) { notice(t('Disallowed profile URL.') . EOL); goaway($_SESSION['return_url']); // NOTREACHED } if (!$url) { notice(t('Connect URL missing.') . EOL); goaway($_SESSION['return_url']); // NOTREACHED } $ret = probe_url($url); if ($ret['network'] === NETWORK_DFRN) { if (strlen($a->path)) { $myaddr = bin2hex($a->get_baseurl() . '/profile/' . $a->user['nickname']); } else { $myaddr = bin2hex($a->user['nickname'] . '@' . $a->get_hostname()); } goaway($ret['request'] . "&addr={$myaddr}"); // NOTREACHED } else { if (get_config('system', 'dfrn_only')) { notice(t('This site is not configured to allow communications with other networks.') . EOL); notice(t('No compatible communication protocols or feeds were discovered.') . EOL); goaway($_SESSION['return_url']); } } // do we have enough information? if (!(x($ret, 'name') && x($ret, 'poll') && (x($ret, 'url') || x($ret, 'addr')))) { notice(t('The profile address specified does not provide adequate information.') . EOL); if (!x($ret, 'poll')) { notice(t('No compatible communication protocols or feeds were discovered.') . EOL); } if (!x($ret, 'name')) { notice(t('An author or name was not found.') . EOL); } if (!x($ret, 'url')) { notice(t('No browser URL could be matched to this address.') . EOL); } if (strpos($url, '@') !== false) { notice('Unable to match @-style Identity Address with a known protocol or email contact'); } goaway($_SESSION['return_url']); } if ($ret['network'] === NETWORK_OSTATUS && get_config('system', 'ostatus_disabled')) { notice(t('The profile address specified belongs to a network which has been disabled on this site.') . EOL); $ret['notify'] = ''; } if (!$ret['notify']) { notice(t('Limited profile. This person will be unable to receive direct/personal notifications from you.') . EOL); } $writeable = $ret['network'] === NETWORK_OSTATUS && $ret['notify'] ? 1 : 0; $hidden = $ret['network'] === NETWORK_MAIL ? 1 : 0; if ($ret['network'] === NETWORK_MAIL) { $writeable = 1; } if ($ret['network'] === NETWORK_DIASPORA) { $writeable = 1; } // check if we already have a contact // the poll url is more reliable than the profile url, as we may have // indirect links or webfinger links $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `poll` = '%s' LIMIT 1", intval(local_user()), dbesc($ret['poll'])); if (count($r)) { // update contact if ($r[0]['rel'] == CONTACT_IS_FOLLOWER || $network === NETWORK_DIASPORA && $r[0]['rel'] == CONTACT_IS_SHARING) { q("UPDATE `contact` SET `rel` = %d , `readonly` = 0 WHERE `id` = %d AND `uid` = %d LIMIT 1", intval(CONTACT_IS_FRIEND), intval($r[0]['id']), intval(local_user())); } } else { $new_relation = $ret['network'] === NETWORK_MAIL ? CONTACT_IS_FRIEND : CONTACT_IS_SHARING; if ($ret['network'] === NETWORK_DIASPORA) { $new_relation = CONTACT_IS_FOLLOWER; } // create contact record $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `addr`, `alias`, `batch`, `notify`, `poll`, `poco`, `name`, `nick`, `photo`, `network`, `pubkey`, `rel`, `priority`,\n\t\t\t`writable`, `hidden`, `blocked`, `readonly`, `pending` )\n\t\t\tVALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, %d, %d, 0, 0, 0 ) ", intval(local_user()), dbesc(datetime_convert()), dbesc($ret['url']), dbesc(normalise_link($ret['url'])), dbesc($ret['addr']), dbesc($ret['alias']), dbesc($ret['batch']), dbesc($ret['notify']), dbesc($ret['poll']), dbesc($ret['poco']), dbesc($ret['name']), dbesc($ret['nick']), dbesc($ret['photo']), dbesc($ret['network']), dbesc($ret['pubkey']), intval($new_relation), intval($ret['priority']), intval($writeable), intval($hidden)); } $r = q("SELECT * FROM `contact` WHERE `url` = '%s' AND `uid` = %d LIMIT 1", dbesc($ret['url']), intval(local_user())); if (!count($r)) { notice(t('Unable to retrieve contact information.') . EOL); goaway($_SESSION['return_url']); // NOTREACHED } $contact = $r[0]; $contact_id = $r[0]['id']; require_once "Photo.php"; $photos = import_profile_photo($ret['photo'], local_user(), $contact_id); $r = q("UPDATE `contact` SET `photo` = '%s', \n\t\t\t`thumb` = '%s',\n\t\t\t`micro` = '%s', \n\t\t\t`name-date` = '%s', \n\t\t\t`uri-date` = '%s', \n\t\t\t`avatar-date` = '%s'\n\t\t\tWHERE `id` = %d LIMIT 1\n\t\t", dbesc($photos[0]), dbesc($photos[1]), dbesc($photos[2]), dbesc(datetime_convert()), dbesc(datetime_convert()), dbesc(datetime_convert()), intval($contact_id)); // pull feed and consume it, which should subscribe to the hub. proc_run('php', "include/poller.php", "{$contact_id}"); // create a follow slap $tpl = get_markup_template('follow_slap.tpl'); $slap = replace_macros($tpl, array('$name' => $a->user['username'], '$profile_page' => $a->get_baseurl() . '/profile/' . $a->user['nickname'], '$photo' => $a->contact['photo'], '$thumb' => $a->contact['thumb'], '$published' => datetime_convert('UTC', 'UTC', 'now', ATOM_TIME), '$item_id' => 'urn:X-dfrn:' . $a->get_hostname() . ':follow:' . random_string(), '$title' => '', '$type' => 'text', '$content' => t('following'), '$nick' => $a->user['nickname'], '$verb' => ACTIVITY_FOLLOW, '$ostat_follow' => '')); $r = q("SELECT `contact`.*, `user`.* FROM `contact` LEFT JOIN `user` ON `contact`.`uid` = `user`.`uid` \n\t\t\tWHERE `user`.`uid` = %d AND `contact`.`self` = 1 LIMIT 1", intval(local_user())); if (count($r)) { if ($contact['network'] == NETWORK_OSTATUS && strlen($contact['notify'])) { require_once 'include/salmon.php'; slapper($r[0], $contact['notify'], $slap); } if ($contact['network'] == NETWORK_DIASPORA) { require_once 'include/diaspora.php'; $ret = diaspora_share($a->user, $contact); logger('mod_follow: diaspora_share returns: ' . $ret); } } if (strstr($_SESSION['return_url'], 'contacts')) { goaway($a->get_baseurl() . '/contacts/' . $contact_id); } goaway($_SESSION['return_url']); // NOTREACHED }