function create_user($arr) { // Required: { username, nickname, email } or { openid_url } $a = get_app(); $result = array('success' => false, 'user' => null, 'password' => '', 'message' => ''); $using_invites = get_config('system', 'invitation_only'); $num_invites = get_config('system', 'number_invites'); $invite_id = x($arr, 'invite_id') ? notags(trim($arr['invite_id'])) : ''; $username = x($arr, 'username') ? notags(trim($arr['username'])) : ''; $nickname = x($arr, 'nickname') ? notags(trim($arr['nickname'])) : ''; $email = x($arr, 'email') ? notags(trim($arr['email'])) : ''; $openid_url = x($arr, 'openid_url') ? notags(trim($arr['openid_url'])) : ''; $photo = x($arr, 'photo') ? notags(trim($arr['photo'])) : ''; $password = x($arr, 'password') ? trim($arr['password']) : ''; $blocked = x($arr, 'blocked') ? intval($arr['blocked']) : 0; $verified = x($arr, 'verified') ? intval($arr['verified']) : 0; $publish = x($arr, 'profile_publish_reg') && intval($arr['profile_publish_reg']) ? 1 : 0; $netpublish = strlen(get_config('system', 'directory_submit_url')) ? $publish : 0; $tmp_str = $openid_url; if ($using_invites) { if (!$invite_id) { $result['message'] .= t('An invitation is required.') . EOL; return $result; } $r = q("select * from register where `hash` = '%s' limit 1", dbesc($invite_id)); if (!results($r)) { $result['message'] .= t('Invitation could not be verified.') . EOL; return $result; } } if (!x($username) || !x($email) || !x($nickname)) { if ($openid_url) { if (!validate_url($tmp_str)) { $result['message'] .= t('Invalid OpenID url') . EOL; return $result; } $_SESSION['register'] = 1; $_SESSION['openid'] = $openid_url; require_once 'library/openid.php'; $openid = new LightOpenID(); $openid->identity = $openid_url; $openid->returnUrl = $a->get_baseurl() . '/openid'; $openid->required = array('namePerson/friendly', 'contact/email', 'namePerson'); $openid->optional = array('namePerson/first', 'media/image/aspect11', 'media/image/default'); try { $authurl = $openid->authUrl(); } catch (Exception $e) { $result['message'] .= t("We encountered a problem while logging in with the OpenID you provided. Please check the correct spelling of the ID.") . EOL . EOL . t("The error message was:") . $e->getMessage() . EOL; return $result; } goaway($authurl); // NOTREACHED } notice(t('Please enter the required information.') . EOL); return; } if (!validate_url($tmp_str)) { $openid_url = ''; } $err = ''; // collapse multiple spaces in name $username = preg_replace('/ +/', ' ', $username); if (mb_strlen($username) > 48) { $result['message'] .= t('Please use a shorter name.') . EOL; } if (mb_strlen($username) < 3) { $result['message'] .= t('Name too short.') . EOL; } // I don't really like having this rule, but it cuts down // on the number of auto-registrations by Russian spammers // Using preg_match was completely unreliable, due to mixed UTF-8 regex support // $no_utf = get_config('system','no_utf'); // $pat = (($no_utf) ? '/^[a-zA-Z]* [a-zA-Z]*$/' : '/^\p{L}* \p{L}*$/u' ); // So now we are just looking for a space in the full name. $loose_reg = get_config('system', 'no_regfullname'); if (!$loose_reg) { $username = mb_convert_case($username, MB_CASE_TITLE, 'UTF-8'); if (!strpos($username, ' ')) { $result['message'] .= t("That doesn't appear to be your full (First Last) name.") . EOL; } } if (!allowed_email($email)) { $result['message'] .= t('Your email domain is not among those allowed on this site.') . EOL; } if (!valid_email($email) || !validate_email($email)) { $result['message'] .= t('Not a valid email address.') . EOL; } // Disallow somebody creating an account using openid that uses the admin email address, // since openid bypasses email verification. We'll allow it if there is not yet an admin account. $adminlist = explode(",", str_replace(" ", "", strtolower($a->config['admin_email']))); //if((x($a->config,'admin_email')) && (strcasecmp($email,$a->config['admin_email']) == 0) && strlen($openid_url)) { if (x($a->config, 'admin_email') && in_array(strtolower($email), $adminlist) && strlen($openid_url)) { $r = q("SELECT * FROM `user` WHERE `email` = '%s' LIMIT 1", dbesc($email)); if (count($r)) { $result['message'] .= t('Cannot use that email.') . EOL; } } $nickname = $arr['nickname'] = strtolower($nickname); if (!preg_match("/^[a-z][a-z0-9\\-\\_]*\$/", $nickname)) { $result['message'] .= t('Your "nickname" can only contain "a-z", "0-9", "-", and "_", and must also begin with a letter.') . EOL; } $r = q("SELECT `uid` FROM `user`\n \tWHERE `nickname` = '%s' LIMIT 1", dbesc($nickname)); if (count($r)) { $result['message'] .= t('Nickname is already registered. Please choose another.') . EOL; } // Check deleted accounts that had this nickname. Doesn't matter to us, // but could be a security issue for federated platforms. $r = q("SELECT * FROM `userd`\n \tWHERE `username` = '%s' LIMIT 1", dbesc($nickname)); if (count($r)) { $result['message'] .= t('Nickname was once registered here and may not be re-used. Please choose another.') . EOL; } if (strlen($result['message'])) { return $result; } $new_password = strlen($password) ? $password : autoname(6) . mt_rand(100, 9999); $new_password_encoded = hash('whirlpool', $new_password); $result['password'] = $new_password; require_once 'include/crypto.php'; $keys = new_keypair(4096); if ($keys === false) { $result['message'] .= t('SERIOUS ERROR: Generation of security keys failed.') . EOL; return $result; } $default_service_class = get_config('system', 'default_service_class'); if (!$default_service_class) { $default_service_class = ''; } $prvkey = $keys['prvkey']; $pubkey = $keys['pubkey']; /** * * Create another keypair for signing/verifying * salmon protocol messages. We have to use a slightly * less robust key because this won't be using openssl * but the phpseclib. Since it is PHP interpreted code * it is not nearly as efficient, and the larger keys * will take several minutes each to process. * */ $sres = new_keypair(512); $sprvkey = $sres['prvkey']; $spubkey = $sres['pubkey']; $r = q("INSERT INTO `user` ( `guid`, `username`, `password`, `email`, `openid`, `nickname`,\n\t\t`pubkey`, `prvkey`, `spubkey`, `sprvkey`, `register_date`, `verified`, `blocked`, `timezone`, `service_class`, `default-location` )\n\t\tVALUES ( '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, 'UTC', '%s', '' )", dbesc(generate_user_guid()), dbesc($username), dbesc($new_password_encoded), dbesc($email), dbesc($openid_url), dbesc($nickname), dbesc($pubkey), dbesc($prvkey), dbesc($spubkey), dbesc($sprvkey), dbesc(datetime_convert()), intval($verified), intval($blocked), dbesc($default_service_class)); if ($r) { $r = q("SELECT * FROM `user`\n\t\t\tWHERE `username` = '%s' AND `password` = '%s' LIMIT 1", dbesc($username), dbesc($new_password_encoded)); if ($r !== false && count($r)) { $u = $r[0]; $newuid = intval($r[0]['uid']); } } else { $result['message'] .= t('An error occurred during registration. Please try again.') . EOL; return $result; } /** * if somebody clicked submit twice very quickly, they could end up with two accounts * due to race condition. Remove this one. */ $r = q("SELECT `uid` FROM `user`\n \tWHERE `nickname` = '%s' ", dbesc($nickname)); if (count($r) > 1 && $newuid) { $result['message'] .= t('Nickname is already registered. Please choose another.') . EOL; q("DELETE FROM `user` WHERE `uid` = %d", intval($newuid)); return $result; } if (x($newuid) !== false) { $r = q("INSERT INTO `profile` ( `uid`, `profile-name`, `is-default`, `name`, `photo`, `thumb`, `publish`, `net-publish` )\n\t\t\tVALUES ( %d, '%s', %d, '%s', '%s', '%s', %d, %d ) ", intval($newuid), t('default'), 1, dbesc($username), dbesc($a->get_baseurl() . "/photo/profile/{$newuid}.jpg"), dbesc($a->get_baseurl() . "/photo/avatar/{$newuid}.jpg"), intval($publish), intval($netpublish)); if ($r === false) { $result['message'] .= t('An error occurred creating your default profile. Please try again.') . EOL; // Start fresh next time. $r = q("DELETE FROM `user` WHERE `uid` = %d", intval($newuid)); return $result; } $r = q("INSERT INTO `contact` ( `uid`, `created`, `self`, `name`, `nick`, `photo`, `thumb`, `micro`, `blocked`, `pending`, `url`, `nurl`,\n\t\t\t`request`, `notify`, `poll`, `confirm`, `poco`, `name-date`, `uri-date`, `avatar-date`, `closeness` )\n\t\t\tVALUES ( %d, '%s', 1, '%s', '%s', '%s', '%s', '%s', 0, 0, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', 0 ) ", intval($newuid), datetime_convert(), dbesc($username), dbesc($nickname), dbesc($a->get_baseurl() . "/photo/profile/{$newuid}.jpg"), dbesc($a->get_baseurl() . "/photo/avatar/{$newuid}.jpg"), dbesc($a->get_baseurl() . "/photo/micro/{$newuid}.jpg"), dbesc($a->get_baseurl() . "/profile/{$nickname}"), dbesc(normalise_link($a->get_baseurl() . "/profile/{$nickname}")), dbesc($a->get_baseurl() . "/dfrn_request/{$nickname}"), dbesc($a->get_baseurl() . "/dfrn_notify/{$nickname}"), dbesc($a->get_baseurl() . "/dfrn_poll/{$nickname}"), dbesc($a->get_baseurl() . "/dfrn_confirm/{$nickname}"), dbesc($a->get_baseurl() . "/poco/{$nickname}"), dbesc(datetime_convert()), dbesc(datetime_convert()), dbesc(datetime_convert())); // Create a group with no members. 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')); $r = q("SELECT id FROM `group` WHERE uid = %d AND name = '%s'", intval($newuid), dbesc(t('Friends'))); if ($r && count($r)) { $def_gid = $r[0]['id']; q("UPDATE user SET def_gid = %d WHERE uid = %d", intval($r[0]['id']), intval($newuid)); } if (get_config('system', 'newuser_private') && $def_gid) { q("UPDATE user SET allow_gid = '%s' WHERE uid = %d", dbesc("<" . $def_gid . ">"), intval($newuid)); } } // if we have no OpenID photo try to look up an avatar if (!strlen($photo)) { $photo = avatar_img($email); } // unless there is no avatar-plugin loaded if (strlen($photo)) { require_once 'include/Photo.php'; $photo_failure = false; $filename = basename($photo); $img_str = fetch_url($photo, true); // guess mimetype from headers or filename $type = guess_image_type($photo, true); $img = new Photo($img_str, $type); if ($img->is_valid()) { $img->scaleImageSquare(175); $hash = photo_new_resource(); $r = $img->store($newuid, 0, $hash, $filename, t('Profile Photos'), 4); if ($r === false) { $photo_failure = true; } $img->scaleImage(80); $r = $img->store($newuid, 0, $hash, $filename, t('Profile Photos'), 5); if ($r === false) { $photo_failure = true; } $img->scaleImage(48); $r = $img->store($newuid, 0, $hash, $filename, t('Profile Photos'), 6); if ($r === false) { $photo_failure = true; } if (!$photo_failure) { q("UPDATE `photo` SET `profile` = 1 WHERE `resource-id` = '%s' ", dbesc($hash)); } } } call_hooks('register_account', $newuid); $result['success'] = true; $result['user'] = $u; return $result; }
function probe_url($url, $mode = PROBE_NORMAL) { require_once 'include/email.php'; $result = array(); if (!$url) { return $result; } $network = null; $diaspora = false; $diaspora_base = ''; $diaspora_guid = ''; $diaspora_key = ''; $has_lrdd = false; $email_conversant = false; $twitter = strpos($url, 'twitter.com') !== false ? true : false; $at_addr = strpos($url, '@') !== false ? true : false; if (!$twitter) { if (strpos($url, 'mailto:') !== false && $at_addr) { $url = str_replace('mailto:', '', $url); $links = array(); } else { $links = lrdd($url); } if (count($links)) { $has_lrdd = true; logger('probe_url: found lrdd links: ' . print_r($links, true), LOGGER_DATA); foreach ($links as $link) { if ($link['@attributes']['rel'] === NAMESPACE_ZOT) { $zot = unamp($link['@attributes']['href']); } if ($link['@attributes']['rel'] === NAMESPACE_DFRN) { $dfrn = unamp($link['@attributes']['href']); } if ($link['@attributes']['rel'] === 'salmon') { $notify = unamp($link['@attributes']['href']); } if ($link['@attributes']['rel'] === NAMESPACE_FEED) { $poll = unamp($link['@attributes']['href']); } if ($link['@attributes']['rel'] === 'http://microformats.org/profile/hcard') { $hcard = unamp($link['@attributes']['href']); } if ($link['@attributes']['rel'] === 'http://webfinger.net/rel/profile-page') { $profile = unamp($link['@attributes']['href']); } if ($link['@attributes']['rel'] === 'http://portablecontacts.net/spec/1.0') { $poco = unamp($link['@attributes']['href']); } if ($link['@attributes']['rel'] === 'http://joindiaspora.com/seed_location') { $diaspora_base = unamp($link['@attributes']['href']); $diaspora = true; } if ($link['@attributes']['rel'] === 'http://joindiaspora.com/guid') { $diaspora_guid = unamp($link['@attributes']['href']); $diaspora = true; } if ($link['@attributes']['rel'] === 'diaspora-public-key') { $diaspora_key = base64_decode(unamp($link['@attributes']['href'])); $pubkey = rsatopem($diaspora_key); $diaspora = true; } } // Status.Net can have more than one profile URL. We need to match the profile URL // to a contact on incoming messages to prevent spam, and we won't know which one // to match. So in case of two, one of them is stored as an alias. Only store URL's // and not webfinger user@host aliases. If they've got more than two non-email style // aliases, let's hope we're lucky and get one that matches the feed author-uri because // otherwise we're screwed. foreach ($links as $link) { if ($link['@attributes']['rel'] === 'alias') { if (strpos($link['@attributes']['href'], '@') === false) { if (isset($profile)) { if ($link['@attributes']['href'] !== $profile) { $alias = unamp($link['@attributes']['href']); } } else { $profile = unamp($link['@attributes']['href']); } } } } } elseif ($mode == PROBE_NORMAL) { // Check email $orig_url = $url; if (strpos($orig_url, '@') && validate_email($orig_url)) { $x = q("SELECT `prvkey` FROM `user` WHERE `uid` = %d LIMIT 1", intval(local_user())); $r = q("SELECT * FROM `mailacct` WHERE `uid` = %d AND `server` != '' LIMIT 1", intval(local_user())); if (count($x) && count($r)) { $mailbox = construct_mailbox_name($r[0]); $password = ''; openssl_private_decrypt(hex2bin($r[0]['pass']), $password, $x[0]['prvkey']); $mbox = email_connect($mailbox, $r[0]['user'], $password); if (!$mbox) { logger('probe_url: email_connect failed.'); } unset($password); } if ($mbox) { $msgs = email_poll($mbox, $orig_url); logger('probe_url: searching ' . $orig_url . ', ' . count($msgs) . ' messages found.', LOGGER_DEBUG); if (count($msgs)) { $addr = $orig_url; $network = NETWORK_MAIL; $name = substr($url, 0, strpos($url, '@')); $phost = substr($url, strpos($url, '@') + 1); $profile = 'http://' . $phost; // fix nick character range $vcard = array('fn' => $name, 'nick' => $name, 'photo' => avatar_img($url)); $notify = 'smtp ' . random_string(); $poll = 'email ' . random_string(); $priority = 0; $x = email_msg_meta($mbox, $msgs[0]); if (stristr($x->from, $orig_url)) { $adr = imap_rfc822_parse_adrlist($x->from, ''); } elseif (stristr($x->to, $orig_url)) { $adr = imap_rfc822_parse_adrlist($x->to, ''); } if (isset($adr)) { foreach ($adr as $feadr) { if (strcasecmp($feadr->mailbox, $name) == 0 && strcasecmp($feadr->host, $phost) == 0 && strlen($feadr->personal)) { $personal = imap_mime_header_decode($feadr->personal); $vcard['fn'] = ""; foreach ($personal as $perspart) { if ($perspart->charset != "default") { $vcard['fn'] .= iconv($perspart->charset, 'UTF-8//IGNORE', $perspart->text); } else { $vcard['fn'] .= $perspart->text; } } $vcard['fn'] = notags($vcard['fn']); } } } } imap_close($mbox); } } } } if ($mode == PROBE_NORMAL) { if (strlen($zot)) { $s = fetch_url($zot); if ($s) { $j = json_decode($s); if ($j) { $network = NETWORK_ZOT; $vcard = array('fn' => $j->fullname, 'nick' => $j->nickname, 'photo' => $j->photo); $profile = $j->url; $notify = $j->post; $pubkey = $j->pubkey; $poll = 'N/A'; } } } if (strlen($dfrn)) { $ret = scrape_dfrn($hcard ? $hcard : $dfrn); if (is_array($ret) && x($ret, 'dfrn-request')) { $network = NETWORK_DFRN; $request = $ret['dfrn-request']; $confirm = $ret['dfrn-confirm']; $notify = $ret['dfrn-notify']; $poll = $ret['dfrn-poll']; $vcard = array(); $vcard['fn'] = $ret['fn']; $vcard['nick'] = $ret['nick']; $vcard['photo'] = $ret['photo']; } } } if ($diaspora && $diaspora_base && $diaspora_guid) { if ($mode == PROBE_DIASPORA || !$notify) { $notify = $diaspora_base . 'receive/users/' . $diaspora_guid; $batch = $diaspora_base . 'receive/public'; } if (strpos($url, '@')) { $addr = str_replace('acct:', '', $url); } } if ($network !== NETWORK_ZOT && $network !== NETWORK_DFRN && $network !== NETWORK_MAIL) { if ($diaspora) { $network = NETWORK_DIASPORA; } elseif ($has_lrdd) { $network = NETWORK_OSTATUS; } $priority = 0; if ($hcard && !$vcard) { $vcard = scrape_vcard($hcard); // Google doesn't use absolute url in profile photos if (x($vcard, 'photo') && substr($vcard['photo'], 0, 1) == '/') { $h = @parse_url($hcard); if ($h) { $vcard['photo'] = $h['scheme'] . '://' . $h['host'] . $vcard['photo']; } } logger('probe_url: scrape_vcard: ' . print_r($vcard, true), LOGGER_DATA); } if ($twitter) { logger('twitter: setup'); $tid = basename($url); $tapi = 'https://api.twitter.com/1/statuses/user_timeline.rss'; if (intval($tid)) { $poll = $tapi . '?user_id=' . $tid; } else { $poll = $tapi . '?screen_name=' . $tid; } $profile = 'http://twitter.com/#!/' . $tid; $vcard['photo'] = 'https://api.twitter.com/1/users/profile_image/' . $tid; $vcard['nick'] = $tid; $vcard['fn'] = $tid . '@twitter'; } if (!x($vcard, 'fn')) { if (x($vcard, 'nick')) { $vcard['fn'] = $vcard['nick']; } } $check_feed = false; if ($twitter || !$poll) { $check_feed = true; } if (!isset($vcard) || !x($vcard, 'fn') || !$profile) { $check_feed = true; } if ($at_addr && !count($links)) { $check_feed = false; } if ($check_feed) { $feedret = scrape_feed($poll ? $poll : $url); logger('probe_url: scrape_feed ' . ($poll ? $poll : $url) . ' returns: ' . print_r($feedret, true), LOGGER_DATA); if (count($feedret) && ($feedret['feed_atom'] || $feedret['feed_rss'])) { $poll = x($feedret, 'feed_atom') ? unamp($feedret['feed_atom']) : unamp($feedret['feed_rss']); if (!x($vcard)) { $vcard = array(); } } if (x($feedret, 'photo') && !x($vcard, 'photo')) { $vcard['photo'] = $feedret['photo']; } require_once 'library/simplepie/simplepie.inc'; $feed = new SimplePie(); $xml = fetch_url($poll); logger('probe_url: fetch feed: ' . $poll . ' returns: ' . $xml, LOGGER_DATA); $a = get_app(); logger('probe_url: scrape_feed: headers: ' . $a->get_curl_headers(), LOGGER_DATA); $feed->set_raw_data($xml); $feed->init(); if ($feed->error()) { logger('probe_url: scrape_feed: Error parsing XML: ' . $feed->error()); } if (!x($vcard, 'photo')) { $vcard['photo'] = $feed->get_image_url(); } $author = $feed->get_author(); if ($author) { $vcard['fn'] = unxmlify(trim($author->get_name())); if (!$vcard['fn']) { $vcard['fn'] = trim(unxmlify($author->get_email())); } if (strpos($vcard['fn'], '@') !== false) { $vcard['fn'] = substr($vcard['fn'], 0, strpos($vcard['fn'], '@')); } $email = unxmlify($author->get_email()); if (!$profile && $author->get_link()) { $profile = trim(unxmlify($author->get_link())); } if (!$vcard['photo']) { $rawtags = $feed->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'author'); if ($rawtags) { $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]; if (x($elems, 'link') && $elems['link'][0]['attribs']['']['rel'] === 'photo') { $vcard['photo'] = $elems['link'][0]['attribs']['']['href']; } } } } else { $item = $feed->get_item(0); if ($item) { $author = $item->get_author(); if ($author) { $vcard['fn'] = trim(unxmlify($author->get_name())); if (!$vcard['fn']) { $vcard['fn'] = trim(unxmlify($author->get_email())); } if (strpos($vcard['fn'], '@') !== false) { $vcard['fn'] = substr($vcard['fn'], 0, strpos($vcard['fn'], '@')); } $email = unxmlify($author->get_email()); if (!$profile && $author->get_link()) { $profile = trim(unxmlify($author->get_link())); } } if (!$vcard['photo']) { $rawmedia = $item->get_item_tags('http://search.yahoo.com/mrss/', 'thumbnail'); if ($rawmedia && $rawmedia[0]['attribs']['']['url']) { $vcard['photo'] = unxmlify($rawmedia[0]['attribs']['']['url']); } } if (!$vcard['photo']) { $rawtags = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'author'); if ($rawtags) { $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]; if (x($elems, 'link') && $elems['link'][0]['attribs']['']['rel'] === 'photo') { $vcard['photo'] = $elems['link'][0]['attribs']['']['href']; } } } } } if (!$vcard['photo'] && strlen($email)) { $vcard['photo'] = avatar_img($email); } if ($poll === $profile) { $lnk = $feed->get_permalink(); } if (isset($lnk) && strlen($lnk)) { $profile = $lnk; } if (!x($vcard, 'fn')) { $vcard['fn'] = notags($feed->get_title()); } if (!x($vcard, 'fn')) { $vcard['fn'] = notags($feed->get_description()); } if (strpos($vcard['fn'], 'Twitter / ') !== false) { $vcard['fn'] = substr($vcard['fn'], strpos($vcard['fn'], '/') + 1); $vcard['fn'] = trim($vcard['fn']); } if (!x($vcard, 'nick')) { $vcard['nick'] = strtolower(notags(unxmlify($vcard['fn']))); if (strpos($vcard['nick'], ' ')) { $vcard['nick'] = trim(substr($vcard['nick'], 0, strpos($vcard['nick'], ' '))); } } if (!$network) { $network = NETWORK_FEED; } if (!$priority) { $priority = 2; } } } if (!x($vcard, 'photo')) { $a = get_app(); $vcard['photo'] = $a->get_baseurl() . '/images/person-175.jpg'; } if (!$profile) { $profile = $url; } // No human could be associated with this link, use the URL as the contact name if ($network === NETWORK_FEED && $poll && !x($vcard, 'fn')) { $vcard['fn'] = $url; } $vcard['fn'] = notags($vcard['fn']); $vcard['nick'] = str_replace(' ', '', notags($vcard['nick'])); $result['name'] = $vcard['fn']; $result['nick'] = $vcard['nick']; $result['url'] = $profile; $result['addr'] = $addr; $result['batch'] = $batch; $result['notify'] = $notify; $result['poll'] = $poll; $result['request'] = $request; $result['confirm'] = $confirm; $result['poco'] = $poco; $result['photo'] = $vcard['photo']; $result['priority'] = $priority; $result['network'] = $network; $result['alias'] = $alias; $result['pubkey'] = $pubkey; logger('probe_url: ' . print_r($result, true), LOGGER_DEBUG); return $result; }
function probe_url($url, $mode = PROBE_NORMAL, $level = 1) { require_once 'include/email.php'; $result = array(); if (!$url) { return $result; } $result = Cache::get("probe_url:" . $mode . ":" . $url); if (!is_null($result)) { $result = unserialize($result); return $result; } $network = null; $diaspora = false; $diaspora_base = ''; $diaspora_guid = ''; $diaspora_key = ''; $has_lrdd = false; $email_conversant = false; $connectornetworks = false; $appnet = false; if (strpos($url, 'twitter.com')) { $connectornetworks = true; $network = NETWORK_TWITTER; } // Twitter is deactivated since twitter closed its old API //$twitter = ((strpos($url,'twitter.com') !== false) ? true : false); $lastfm = strpos($url, 'last.fm/user') !== false ? true : false; $at_addr = strpos($url, '@') !== false ? true : false; if (!$appnet && !$lastfm && !$connectornetworks) { if (strpos($url, 'mailto:') !== false && $at_addr) { $url = str_replace('mailto:', '', $url); $links = array(); } else { $links = lrdd($url); } if (count($links)) { $has_lrdd = true; logger('probe_url: found lrdd links: ' . print_r($links, true), LOGGER_DATA); foreach ($links as $link) { if ($link['@attributes']['rel'] === NAMESPACE_ZOT) { $zot = unamp($link['@attributes']['href']); } if ($link['@attributes']['rel'] === NAMESPACE_DFRN) { $dfrn = unamp($link['@attributes']['href']); } if ($link['@attributes']['rel'] === 'salmon') { $notify = unamp($link['@attributes']['href']); } if ($link['@attributes']['rel'] === NAMESPACE_FEED) { $poll = unamp($link['@attributes']['href']); } if ($link['@attributes']['rel'] === 'http://microformats.org/profile/hcard') { $hcard = unamp($link['@attributes']['href']); } if ($link['@attributes']['rel'] === 'http://webfinger.net/rel/profile-page') { $profile = unamp($link['@attributes']['href']); } if ($link['@attributes']['rel'] === 'http://portablecontacts.net/spec/1.0') { $poco = unamp($link['@attributes']['href']); } if ($link['@attributes']['rel'] === 'http://joindiaspora.com/seed_location') { $diaspora_base = unamp($link['@attributes']['href']); $diaspora = true; } if ($link['@attributes']['rel'] === 'http://joindiaspora.com/guid') { $diaspora_guid = unamp($link['@attributes']['href']); $diaspora = true; } if ($link['@attributes']['rel'] === 'diaspora-public-key') { $diaspora_key = base64_decode(unamp($link['@attributes']['href'])); if (strstr($diaspora_key, 'RSA ')) { $pubkey = rsatopem($diaspora_key); } else { $pubkey = $diaspora_key; } $diaspora = true; } if ($link['@attributes']['rel'] === 'http://ostatus.org/schema/1.0/subscribe' and $mode == PROBE_NORMAL) { $diaspora = false; } } // Status.Net can have more than one profile URL. We need to match the profile URL // to a contact on incoming messages to prevent spam, and we won't know which one // to match. So in case of two, one of them is stored as an alias. Only store URL's // and not webfinger user@host aliases. If they've got more than two non-email style // aliases, let's hope we're lucky and get one that matches the feed author-uri because // otherwise we're screwed. foreach ($links as $link) { if ($link['@attributes']['rel'] === 'alias') { if (strpos($link['@attributes']['href'], '@') === false) { if (isset($profile)) { if ($link['@attributes']['href'] !== $profile) { $alias = unamp($link['@attributes']['href']); } } else { $profile = unamp($link['@attributes']['href']); } } } } // If the profile is different from the url then the url is abviously an alias if ($alias == "" and $profile != "" and !$at_addr and normalise_link($profile) != normalise_link($url)) { $alias = $url; } } elseif ($mode == PROBE_NORMAL) { // Check email $orig_url = $url; if (strpos($orig_url, '@') && validate_email($orig_url)) { $x = q("SELECT `prvkey` FROM `user` WHERE `uid` = %d LIMIT 1", intval(local_user())); $r = q("SELECT * FROM `mailacct` WHERE `uid` = %d AND `server` != '' LIMIT 1", intval(local_user())); if (count($x) && count($r)) { $mailbox = construct_mailbox_name($r[0]); $password = ''; openssl_private_decrypt(hex2bin($r[0]['pass']), $password, $x[0]['prvkey']); $mbox = email_connect($mailbox, $r[0]['user'], $password); if (!$mbox) { logger('probe_url: email_connect failed.'); } unset($password); } if ($mbox) { $msgs = email_poll($mbox, $orig_url); logger('probe_url: searching ' . $orig_url . ', ' . count($msgs) . ' messages found.', LOGGER_DEBUG); if (count($msgs)) { $addr = $orig_url; $network = NETWORK_MAIL; $name = substr($url, 0, strpos($url, '@')); $phost = substr($url, strpos($url, '@') + 1); $profile = 'http://' . $phost; // fix nick character range $vcard = array('fn' => $name, 'nick' => $name, 'photo' => avatar_img($url)); $notify = 'smtp ' . random_string(); $poll = 'email ' . random_string(); $priority = 0; $x = email_msg_meta($mbox, $msgs[0]); if (stristr($x[0]->from, $orig_url)) { $adr = imap_rfc822_parse_adrlist($x[0]->from, ''); } elseif (stristr($x[0]->to, $orig_url)) { $adr = imap_rfc822_parse_adrlist($x[0]->to, ''); } if (isset($adr)) { foreach ($adr as $feadr) { if (strcasecmp($feadr->mailbox, $name) == 0 && strcasecmp($feadr->host, $phost) == 0 && strlen($feadr->personal)) { $personal = imap_mime_header_decode($feadr->personal); $vcard['fn'] = ""; foreach ($personal as $perspart) { if ($perspart->charset != "default") { $vcard['fn'] .= iconv($perspart->charset, 'UTF-8//IGNORE', $perspart->text); } else { $vcard['fn'] .= $perspart->text; } } $vcard['fn'] = notags($vcard['fn']); } } } } imap_close($mbox); } } } } if ($mode == PROBE_NORMAL) { if (strlen($zot)) { $s = fetch_url($zot); if ($s) { $j = json_decode($s); if ($j) { $network = NETWORK_ZOT; $vcard = array('fn' => $j->fullname, 'nick' => $j->nickname, 'photo' => $j->photo); $profile = $j->url; $notify = $j->post; $pubkey = $j->pubkey; $poll = 'N/A'; } } } if (strlen($dfrn)) { $ret = scrape_dfrn($hcard ? $hcard : $dfrn, true); if (is_array($ret) && x($ret, 'dfrn-request')) { $network = NETWORK_DFRN; $request = $ret['dfrn-request']; $confirm = $ret['dfrn-confirm']; $notify = $ret['dfrn-notify']; $poll = $ret['dfrn-poll']; $vcard = array(); $vcard['fn'] = $ret['fn']; $vcard['nick'] = $ret['nick']; $vcard['photo'] = $ret['photo']; } } } if ($diaspora && $diaspora_base && $diaspora_guid) { if ($mode == PROBE_DIASPORA || !$notify) { $notify = $diaspora_base . 'receive/users/' . $diaspora_guid; $batch = $diaspora_base . 'receive/public'; } if (strpos($url, '@')) { $addr = str_replace('acct:', '', $url); } } if ($network !== NETWORK_ZOT && $network !== NETWORK_DFRN && $network !== NETWORK_MAIL) { if ($diaspora) { $network = NETWORK_DIASPORA; } elseif ($has_lrdd and $notify) { $network = NETWORK_OSTATUS; } if (strpos($url, '@')) { $addr = str_replace('acct:', '', $url); } $priority = 0; if ($hcard && !$vcard) { $vcard = scrape_vcard($hcard); // Google doesn't use absolute url in profile photos if (x($vcard, 'photo') && substr($vcard['photo'], 0, 1) == '/') { $h = @parse_url($hcard); if ($h) { $vcard['photo'] = $h['scheme'] . '://' . $h['host'] . $vcard['photo']; } } logger('probe_url: scrape_vcard: ' . print_r($vcard, true), LOGGER_DATA); } if ($diaspora && $addr) { // Diaspora returns the name as the nick. As the nick will never be updated, // let's use the Diaspora nickname (the first part of the handle) as the nick instead $addr_parts = explode('@', $addr); $vcard['nick'] = $addr_parts[0]; } /* if($twitter) { logger('twitter: setup'); $tid = basename($url); $tapi = 'https://api.twitter.com/1/statuses/user_timeline.rss'; if(intval($tid)) $poll = $tapi . '?user_id=' . $tid; else $poll = $tapi . '?screen_name=' . $tid; $profile = 'http://twitter.com/#!/' . $tid; //$vcard['photo'] = 'https://api.twitter.com/1/users/profile_image/' . $tid; $vcard['photo'] = 'https://api.twitter.com/1/users/profile_image?screen_name=' . $tid . '&size=bigger'; $vcard['nick'] = $tid; $vcard['fn'] = $tid; } */ if ($lastfm) { $profile = $url; $poll = str_replace(array('www.', 'last.fm/'), array('', 'ws.audioscrobbler.com/1.0/'), $url) . '/recenttracks.rss'; $vcard['nick'] = basename($url); $vcard['fn'] = $vcard['nick'] . t(' on Last.fm'); $network = NETWORK_FEED; } if (!x($vcard, 'fn')) { if (x($vcard, 'nick')) { $vcard['fn'] = $vcard['nick']; } } $check_feed = false; if (stristr($url, 'tumblr.com') && !stristr($url, '/rss')) { $poll = $url . '/rss'; $check_feed = true; // Will leave it to others to figure out how to grab the avatar, which is on the $url page in the open graph meta links } if ($appnet || !$poll) { $check_feed = true; } if (!isset($vcard) || !x($vcard, 'fn') || !$profile) { $check_feed = true; } if ($at_addr && !count($links)) { $check_feed = false; } if ($connectornetworks) { $check_feed = false; } if ($check_feed) { $feedret = scrape_feed($poll ? $poll : $url); logger('probe_url: scrape_feed ' . ($poll ? $poll : $url) . ' returns: ' . print_r($feedret, true), LOGGER_DATA); if (count($feedret) && ($feedret['feed_atom'] || $feedret['feed_rss'])) { $poll = x($feedret, 'feed_atom') ? unamp($feedret['feed_atom']) : unamp($feedret['feed_rss']); if (!x($vcard)) { $vcard = array(); } } if (x($feedret, 'photo') && !x($vcard, 'photo')) { $vcard['photo'] = $feedret['photo']; } require_once 'library/simplepie/simplepie.inc'; $feed = new SimplePie(); $xml = fetch_url($poll); logger('probe_url: fetch feed: ' . $poll . ' returns: ' . $xml, LOGGER_DATA); $a = get_app(); logger('probe_url: scrape_feed: headers: ' . $a->get_curl_headers(), LOGGER_DATA); // Don't try and parse an empty string $feed->set_raw_data($xml ? $xml : '<?xml version="1.0" encoding="utf-8" ?><xml></xml>'); $feed->init(); if ($feed->error()) { logger('probe_url: scrape_feed: Error parsing XML: ' . $feed->error()); $network = NETWORK_PHANTOM; } if (!x($vcard, 'photo')) { $vcard['photo'] = $feed->get_image_url(); } $author = $feed->get_author(); if ($author) { $vcard['fn'] = unxmlify(trim($author->get_name())); if (!$vcard['fn']) { $vcard['fn'] = trim(unxmlify($author->get_email())); } if (strpos($vcard['fn'], '@') !== false) { $vcard['fn'] = substr($vcard['fn'], 0, strpos($vcard['fn'], '@')); } $email = unxmlify($author->get_email()); if (!$profile && $author->get_link()) { $profile = trim(unxmlify($author->get_link())); } if (!$vcard['photo']) { $rawtags = $feed->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'author'); if ($rawtags) { $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]; if (x($elems, 'link') && $elems['link'][0]['attribs']['']['rel'] === 'photo') { $vcard['photo'] = $elems['link'][0]['attribs']['']['href']; } } } // Fetch fullname via poco:displayName $pocotags = $feed->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'author'); if ($pocotags) { $elems = $pocotags[0]['child']['http://portablecontacts.net/spec/1.0']; if (isset($elems["displayName"])) { $vcard['fn'] = $elems["displayName"][0]["data"]; } if (isset($elems["preferredUsername"])) { $vcard['nick'] = $elems["preferredUsername"][0]["data"]; } } } else { $item = $feed->get_item(0); if ($item) { $author = $item->get_author(); if ($author) { $vcard['fn'] = trim(unxmlify($author->get_name())); if (!$vcard['fn']) { $vcard['fn'] = trim(unxmlify($author->get_email())); } if (strpos($vcard['fn'], '@') !== false) { $vcard['fn'] = substr($vcard['fn'], 0, strpos($vcard['fn'], '@')); } $email = unxmlify($author->get_email()); if (!$profile && $author->get_link()) { $profile = trim(unxmlify($author->get_link())); } } if (!$vcard['photo']) { $rawmedia = $item->get_item_tags('http://search.yahoo.com/mrss/', 'thumbnail'); if ($rawmedia && $rawmedia[0]['attribs']['']['url']) { $vcard['photo'] = unxmlify($rawmedia[0]['attribs']['']['url']); } } if (!$vcard['photo']) { $rawtags = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'author'); if ($rawtags) { $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]; if (x($elems, 'link') && $elems['link'][0]['attribs']['']['rel'] === 'photo') { $vcard['photo'] = $elems['link'][0]['attribs']['']['href']; } } } } } // Workaround for misconfigured Friendica servers if ($network == "" and strstr($url, "/profile/")) { $noscrape = str_replace("/profile/", "/noscrape/", $url); $noscrapejson = fetch_url($noscrape); if ($noscrapejson) { $network = NETWORK_DFRN; $poco = str_replace("/profile/", "/poco/", $url); $noscrapedata = json_decode($noscrapejson, true); if (isset($noscrapedata["addr"])) { $addr = $noscrapedata["addr"]; } if (isset($noscrapedata["fn"])) { $vcard["fn"] = $noscrapedata["fn"]; } if (isset($noscrapedata["key"])) { $pubkey = $noscrapedata["key"]; } if (isset($noscrapedata["photo"])) { $vcard["photo"] = $noscrapedata["photo"]; } if (isset($noscrapedata["dfrn-request"])) { $request = $noscrapedata["dfrn-request"]; } if (isset($noscrapedata["dfrn-confirm"])) { $confirm = $noscrapedata["dfrn-confirm"]; } if (isset($noscrapedata["dfrn-notify"])) { $notify = $noscrapedata["dfrn-notify"]; } if (isset($noscrapedata["dfrn-poll"])) { $poll = $noscrapedata["dfrn-poll"]; } } } if (!$vcard['photo'] && strlen($email)) { $vcard['photo'] = avatar_img($email); } if ($poll === $profile) { $lnk = $feed->get_permalink(); } if (isset($lnk) && strlen($lnk)) { $profile = $lnk; } if (!$network) { $network = NETWORK_FEED; // If it is a feed, don't take the author name as feed name unset($vcard['fn']); } if (!x($vcard, 'fn')) { $vcard['fn'] = notags($feed->get_title()); } if (!x($vcard, 'fn')) { $vcard['fn'] = notags($feed->get_description()); } if (strpos($vcard['fn'], 'Twitter / ') !== false) { $vcard['fn'] = substr($vcard['fn'], strpos($vcard['fn'], '/') + 1); $vcard['fn'] = trim($vcard['fn']); } if (!x($vcard, 'nick')) { $vcard['nick'] = strtolower(notags(unxmlify($vcard['fn']))); if (strpos($vcard['nick'], ' ')) { $vcard['nick'] = trim(substr($vcard['nick'], 0, strpos($vcard['nick'], ' '))); } } if (!$priority) { $priority = 2; } } } if (!x($vcard, 'photo')) { $a = get_app(); $vcard['photo'] = $a->get_baseurl() . '/images/person-175.jpg'; } if (!$profile) { $profile = $url; } // No human could be associated with this link, use the URL as the contact name if ($network === NETWORK_FEED && $poll && !x($vcard, 'fn')) { $vcard['fn'] = $url; } if ($notify != "" and $poll != "") { $baseurl = matching(normalise_link($notify), normalise_link($poll)); $baseurl2 = matching($baseurl, normalise_link($profile)); if ($baseurl2 != "") { $baseurl = $baseurl2; } } if ($baseurl == "" and $notify != "") { $baseurl = matching(normalise_link($profile), normalise_link($notify)); } if ($baseurl == "" and $poll != "") { $baseurl = matching(normalise_link($profile), normalise_link($poll)); } $baseurl = rtrim($baseurl, "/"); if (strpos($url, '@') and $addr == "" and $network == NETWORK_DFRN) { $addr = str_replace('acct:', '', $url); } $vcard['fn'] = notags($vcard['fn']); $vcard['nick'] = str_replace(' ', '', notags($vcard['nick'])); $result['name'] = $vcard['fn']; $result['nick'] = $vcard['nick']; $result['url'] = $profile; $result['addr'] = $addr; $result['batch'] = $batch; $result['notify'] = $notify; $result['poll'] = $poll; $result['request'] = $request; $result['confirm'] = $confirm; $result['poco'] = $poco; $result['photo'] = $vcard['photo']; $result['priority'] = $priority; $result['network'] = $network; $result['alias'] = $alias; $result['pubkey'] = $pubkey; $result['baseurl'] = $baseurl; logger('probe_url: ' . print_r($result, true), LOGGER_DEBUG); if ($level == 1) { // Trying if it maybe a diaspora account if ($result['network'] == NETWORK_FEED or $result['addr'] == "") { require_once 'include/bbcode.php'; $address = GetProfileUsername($url, "", true); $result2 = probe_url($address, $mode, ++$level); if ($result2['network'] != "") { $result = $result2; } } // Maybe it's some non standard GNU Social installation (Single user, subfolder or no uri rewrite) if ($result['network'] == NETWORK_FEED and $result['baseurl'] != "" and $result['nick'] != "") { $addr = $result['nick'] . '@' . str_replace("http://", "", $result['baseurl']); $result2 = probe_url($addr, $mode, ++$level); if ($result2['network'] != "" and $result2['network'] != NETWORK_FEED) { $result = $result2; } } } // Only store into the cache if the value seems to be valid if ($result['network'] != NETWORK_PHANTOM) { Cache::set("probe_url:" . $mode . ":" . $url, serialize($result), CACHE_DAY); } return $result; }
function dfrn_request_post(&$a) { if ($a->argc != 2 || !count($a->profile)) { return; } if (x($_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'] : ""; $hidden = x($_POST, 'hidden-contact') ? intval($_POST['hidden-contact']) : 0; $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' OR `nurl` = '%s') AND `self` = 0 LIMIT 1", intval(local_user()), dbesc($dfrn_url), dbesc(normalise_link($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, hidden = %d WHERE `id` = %d", intval($aes_allow), intval($hidden), intval($contact_record['id'])); } else { /** * Scrape the other site's profile page to pick up the dfrn links, key, fn, and photo */ require_once 'include/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`, `hidden`)\n\t\t\t\t\t\tVALUES ( %d, '%s', '%s', '%s', '%s' , '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %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), intval($hidden)); } if ($r) { info(t("Introduction complete.") . EOL); } $r = q("select id from contact where uid = %d and url = '%s' and `site-pubkey` = '%s' limit 1", intval(local_user()), dbesc($dfrn_url), $parms['key']); if (count($r)) { $g = q("select def_gid from user where uid = %d limit 1", intval(local_user())); if ($g && intval($g[0]['def_gid'])) { require_once 'include/group.php'; group_add_member(local_user(), '', $r[0]['id'], $g[0]['def_gid']); } $forwardurl = $a->get_baseurl() . "/contacts/" . $r[0]['id']; } else { $forwardurl = $a->get_baseurl() . "/contacts"; } /** * 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) // Old: goaway(zrl($dfrn_url)); goaway($forwardurl); 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_MAIL2)); if (count($r)) { foreach ($r as $rr) { if (!$rr['rel']) { q("DELETE FROM `contact` WHERE `id` = %d", intval($rr['cid'])); } q("DELETE FROM `intro` WHERE `id` = %d", 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_MAIL2)); if (count($r)) { foreach ($r as $rr) { if (!$rr['rel']) { q("DELETE FROM `contact` WHERE `id` = %d", intval($rr['cid'])); } q("DELETE FROM `intro` WHERE `id` = %d", intval($rr['iid'])); } } $email_follow = x($_POST, 'email_follow') ? intval($_POST['email_follow']) : 0; $real_name = x($_POST, 'realname') ? notags(trim($_POST['realname'])) : ''; $url = trim($_POST['dfrn_url']); if (!strlen($url)) { notice(t("Invalid locator") . EOL); return; } $hcard = ''; if ($email_follow) { if (!validate_email($url)) { notice(t('Invalid email address.') . EOL); return; } $addr = $url; $name = $realname ? $realname : $addr; $nick = substr($addr, 0, strpos($addr, '@')); $url = 'http://' . substr($addr, strpos($addr, '@') + 1); $nurl = normalise_url($host); $poll = 'email ' . random_string(); $notify = 'smtp ' . random_string(); $blocked = 1; $pending = 1; $network = NETWORK_MAIL2; $rel = CONTACT_IS_FOLLOWER; $mail_disabled = function_exists('imap_open') && !get_config('system', 'imap_disabled') ? 0 : 1; if (get_config('system', 'dfrn_only')) { $mail_disabled = 1; } if (!$mail_disabled) { $failed = false; $r = q("SELECT * FROM `mailacct` WHERE `uid` = %d LIMIT 1", intval($uid)); if (!count($r)) { notice(t('This account has not been configured for email. Request failed.') . EOL); return; } } $r = q("insert into contact ( uid, created, addr, name, nick, url, nurl, poll, notify, blocked, pending, network, rel )\n\t\t\t\tvalues( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, '%s', %d ) ", intval($uid), dbesc(datetime_convert()), dbesc($addr), dbesc($name), dbesc($nick), dbesc($url), dbesc($nurl), dbesc($poll), dbesc($notify), intval($blocked), intval($pending), dbesc($network), intval($rel)); $r = q("select id from contact where poll = '%s' and uid = %d limit 1", dbesc($poll), intval($uid)); if (count($r)) { $contact_id = $r[0]['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']); } $photo = avatar_img($addr); $r = q("UPDATE `contact` SET\n\t\t\t\t\t`photo` = '%s',\n\t\t\t\t\t`thumb` = '%s',\n\t\t\t\t\t`micro` = '%s',\n\t\t\t\t\t`name-date` = '%s',\n\t\t\t\t\t`uri-date` = '%s',\n\t\t\t\t\t`avatar-date` = '%s',\n\t\t\t\t\t`hidden` = 0,\n\t\t\t\t\tWHERE `id` = %d\n\t\t\t\t", dbesc($photos[0]), dbesc($photos[1]), dbesc($photos[2]), dbesc(datetime_convert()), dbesc(datetime_convert()), dbesc(datetime_convert()), intval($contact_id)); } // contact is created. Now create an introduction $hash = random_string(); $r = q("insert into intro ( uid, `contact-id`, knowyou, note, hash, datetime, blocked )\n\t\t\t\tvalues( %d , %d, %d, '%s', '%s', '%s', %d ) ", intval($uid), intval($contact_id), x($_POST, 'knowyou') && $_POST['knowyou'] == 1 ? 1 : 0, dbesc(notags(trim($_POST['dfrn-request-message']))), dbesc($hash), dbesc(datetime_convert()), 1); // Next send an email verify form to the requestor. } else { // Canonicalise email-style profile locator $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", 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 'include/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() . '/profile/' . $nickname, $url); goaway($url); // NOTREACHED // END $network === NETWORK_OSTATUS } } return; }