|
|
|
<?php
|
|
|
|
/**
|
|
|
|
* @file include/diaspora.php
|
|
|
|
* @brief The implementation of the diaspora protocol
|
|
|
|
*/
|
|
|
|
|
|
|
|
require_once("include/items.php");
|
|
|
|
require_once("include/bb2diaspora.php");
|
|
|
|
require_once("include/Scrape.php");
|
|
|
|
require_once("include/Contact.php");
|
|
|
|
require_once("include/Photo.php");
|
|
|
|
require_once("include/socgraph.php");
|
|
|
|
require_once("include/group.php");
|
|
|
|
require_once("include/xml.php");
|
|
|
|
require_once("include/datetime.php");
|
|
|
|
require_once("include/queue_fn.php");
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief This class contain functions to create and send Diaspora XML files
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
class diaspora {
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief Return a list of relay servers
|
|
|
|
*
|
|
|
|
* This is an experimental Diaspora feature.
|
|
|
|
*
|
|
|
|
* @return array of relay servers
|
|
|
|
*/
|
|
|
|
public static function relay_list() {
|
|
|
|
|
|
|
|
$serverdata = get_config("system", "relay_server");
|
|
|
|
if ($serverdata == "")
|
|
|
|
return array();
|
|
|
|
|
|
|
|
$relay = array();
|
|
|
|
|
|
|
|
$servers = explode(",", $serverdata);
|
|
|
|
|
|
|
|
foreach($servers AS $server) {
|
|
|
|
$server = trim($server);
|
|
|
|
$batch = $server."/receive/public";
|
|
|
|
|
|
|
|
$relais = q("SELECT `batch`, `id`, `name`,`network` FROM `contact` WHERE `uid` = 0 AND `batch` = '%s' LIMIT 1", dbesc($batch));
|
|
|
|
|
|
|
|
if (!$relais) {
|
|
|
|
$addr = "relay@".str_replace("http://", "", normalise_link($server));
|
|
|
|
|
|
|
|
$r = q("INSERT INTO `contact` (`uid`, `created`, `name`, `nick`, `addr`, `url`, `nurl`, `batch`, `network`, `rel`, `blocked`, `pending`, `writable`, `name-date`, `uri-date`, `avatar-date`)
|
|
|
|
VALUES (0, '%s', '%s', 'relay', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, '%s', '%s', '%s')",
|
|
|
|
datetime_convert(),
|
|
|
|
dbesc($addr),
|
|
|
|
dbesc($addr),
|
|
|
|
dbesc($server),
|
|
|
|
dbesc(normalise_link($server)),
|
|
|
|
dbesc($batch),
|
|
|
|
dbesc(NETWORK_DIASPORA),
|
|
|
|
intval(CONTACT_IS_FOLLOWER),
|
|
|
|
dbesc(datetime_convert()),
|
|
|
|
dbesc(datetime_convert()),
|
|
|
|
dbesc(datetime_convert())
|
|
|
|
);
|
|
|
|
|
|
|
|
$relais = q("SELECT `batch`, `id`, `name`,`network` FROM `contact` WHERE `uid` = 0 AND `batch` = '%s' LIMIT 1", dbesc($batch));
|
|
|
|
if ($relais)
|
|
|
|
$relay[] = $relais[0];
|
|
|
|
} else
|
|
|
|
$relay[] = $relais[0];
|
|
|
|
}
|
|
|
|
|
|
|
|
return $relay;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief repairs a signature that was double encoded
|
|
|
|
*
|
|
|
|
* The function is unused at the moment. It was copied from the old implementation.
|
|
|
|
*
|
|
|
|
* @param string $signature The signature
|
|
|
|
* @param string $handle The handle of the signature owner
|
|
|
|
* @param integer $level This value is only set inside this function to avoid endless loops
|
|
|
|
*
|
|
|
|
* @return string the repaired signature
|
|
|
|
*/
|
|
|
|
private function repair_signature($signature, $handle = "", $level = 1) {
|
|
|
|
|
|
|
|
if ($signature == "")
|
|
|
|
return ($signature);
|
|
|
|
|
|
|
|
if (base64_encode(base64_decode(base64_decode($signature))) == base64_decode($signature)) {
|
|
|
|
$signature = base64_decode($signature);
|
|
|
|
logger("Repaired double encoded signature from Diaspora/Hubzilla handle ".$handle." - level ".$level, LOGGER_DEBUG);
|
|
|
|
|
|
|
|
// Do a recursive call to be able to fix even multiple levels
|
|
|
|
if ($level < 10)
|
|
|
|
$signature = self::repair_signature($signature, $handle, ++$level);
|
|
|
|
}
|
|
|
|
|
|
|
|
return($signature);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief verify the envelope and return the verified data
|
|
|
|
*
|
|
|
|
* @param string $envelope The magic envelope
|
|
|
|
*
|
|
|
|
* @return string verified data
|
|
|
|
*/
|
|
|
|
private function verify_magic_envelope($envelope) {
|
|
|
|
|
|
|
|
$basedom = parse_xml_string($envelope, false);
|
|
|
|
|
|
|
|
if (!is_object($basedom)) {
|
|
|
|
logger("Envelope is no XML file");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
$children = $basedom->children('http://salmon-protocol.org/ns/magic-env');
|
|
|
|
|
|
|
|
if (sizeof($children) == 0) {
|
|
|
|
logger("XML has no children");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
$handle = "";
|
|
|
|
|
|
|
|
$data = base64url_decode($children->data);
|
|
|
|
$type = $children->data->attributes()->type[0];
|
|
|
|
|
|
|
|
$encoding = $children->encoding;
|
|
|
|
|
|
|
|
$alg = $children->alg;
|
|
|
|
|
|
|
|
$sig = base64url_decode($children->sig);
|
|
|
|
$key_id = $children->sig->attributes()->key_id[0];
|
|
|
|
if ($key_id != "")
|
|
|
|
$handle = base64url_decode($key_id);
|
|
|
|
|
|
|
|
$b64url_data = base64url_encode($data);
|
|
|
|
$msg = str_replace(array("\n", "\r", " ", "\t"), array("", "", "", ""), $b64url_data);
|
|
|
|
|
|
|
|
$signable_data = $msg.".".base64url_encode($type).".".base64url_encode($encoding).".".base64url_encode($alg);
|
|
|
|
|
|
|
|
$key = self::key($handle);
|
|
|
|
|
|
|
|
$verify = rsa_verify($signable_data, $sig, $key);
|
|
|
|
if (!$verify) {
|
|
|
|
logger('Message did not verify. Discarding.');
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $data;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief: Decodes incoming Diaspora message
|
|
|
|
*
|
|
|
|
* @param array $importer Array of the importer user
|
|
|
|
* @param string $xml urldecoded Diaspora salmon
|
|
|
|
*
|
|
|
|
* @return array
|
|
|
|
* 'message' -> decoded Diaspora XML message
|
|
|
|
* 'author' -> author diaspora handle
|
|
|
|
* 'key' -> author public key (converted to pkcs#8)
|
|
|
|
*/
|
|
|
|
public static function decode($importer, $xml) {
|
|
|
|
|
|
|
|
$public = false;
|
|
|
|
$basedom = parse_xml_string($xml);
|
|
|
|
|
|
|
|
if (!is_object($basedom))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
$children = $basedom->children('https://joindiaspora.com/protocol');
|
|
|
|
|
|
|
|
if($children->header) {
|
|
|
|
$public = true;
|
|
|
|
$author_link = str_replace('acct:','',$children->header->author_id);
|
|
|
|
} else {
|
|
|
|
|
|
|
|
$encrypted_header = json_decode(base64_decode($children->encrypted_header));
|
|
|
|
|
|
|
|
$encrypted_aes_key_bundle = base64_decode($encrypted_header->aes_key);
|
|
|
|
$ciphertext = base64_decode($encrypted_header->ciphertext);
|
|
|
|
|
|
|
|
$outer_key_bundle = '';
|
|
|
|
openssl_private_decrypt($encrypted_aes_key_bundle,$outer_key_bundle,$importer['prvkey']);
|
|
|
|
|
|
|
|
$j_outer_key_bundle = json_decode($outer_key_bundle);
|
|
|
|
|
|
|
|
$outer_iv = base64_decode($j_outer_key_bundle->iv);
|
|
|
|
$outer_key = base64_decode($j_outer_key_bundle->key);
|
|
|
|
|
|
|
|
$decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $outer_key, $ciphertext, MCRYPT_MODE_CBC, $outer_iv);
|
|
|
|
|
|
|
|
|
|
|
|
$decrypted = pkcs5_unpad($decrypted);
|
|
|
|
|
|
|
|
logger('decrypted: '.$decrypted, LOGGER_DEBUG);
|
|
|
|
$idom = parse_xml_string($decrypted,false);
|
|
|
|
|
|
|
|
$inner_iv = base64_decode($idom->iv);
|
|
|
|
$inner_aes_key = base64_decode($idom->aes_key);
|
|
|
|
|
|
|
|
$author_link = str_replace('acct:','',$idom->author_id);
|
|
|
|
}
|
|
|
|
|
|
|
|
$dom = $basedom->children(NAMESPACE_SALMON_ME);
|
|
|
|
|
|
|
|
// figure out where in the DOM tree our data is hiding
|
|
|
|
|
|
|
|
if($dom->provenance->data)
|
|
|
|
$base = $dom->provenance;
|
|
|
|
elseif($dom->env->data)
|
|
|
|
$base = $dom->env;
|
|
|
|
elseif($dom->data)
|
|
|
|
$base = $dom;
|
|
|
|
|
|
|
|
if (!$base) {
|
|
|
|
logger('unable to locate salmon data in xml');
|
|
|
|
http_status_exit(400);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Stash the signature away for now. We have to find their key or it won't be good for anything.
|
|
|
|
$signature = base64url_decode($base->sig);
|
|
|
|
|
|
|
|
// unpack the data
|
|
|
|
|
|
|
|
// strip whitespace so our data element will return to one big base64 blob
|
|
|
|
$data = str_replace(array(" ","\t","\r","\n"),array("","","",""),$base->data);
|
|
|
|
|
|
|
|
|
|
|
|
// stash away some other stuff for later
|
|
|
|
|
|
|
|
$type = $base->data[0]->attributes()->type[0];
|
|
|
|
$keyhash = $base->sig[0]->attributes()->keyhash[0];
|
|
|
|
$encoding = $base->encoding;
|
|
|
|
$alg = $base->alg;
|
|
|
|
|
|
|
|
|
|
|
|
$signed_data = $data.'.'.base64url_encode($type).'.'.base64url_encode($encoding).'.'.base64url_encode($alg);
|
|
|
|
|
|
|
|
|
|
|
|
// decode the data
|
|
|
|
$data = base64url_decode($data);
|
|
|
|
|
|
|
|
|
|
|
|
if($public)
|
|
|
|
$inner_decrypted = $data;
|
|
|
|
else {
|
|
|
|
|
|
|
|
// Decode the encrypted blob
|
|
|
|
|
|
|
|
$inner_encrypted = base64_decode($data);
|
|
|
|
$inner_decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $inner_aes_key, $inner_encrypted, MCRYPT_MODE_CBC, $inner_iv);
|
|
|
|
$inner_decrypted = pkcs5_unpad($inner_decrypted);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$author_link) {
|
|
|
|
logger('Could not retrieve author URI.');
|
|
|
|
http_status_exit(400);
|
|
|
|
}
|
|
|
|
// Once we have the author URI, go to the web and try to find their public key
|
|
|
|
// (first this will look it up locally if it is in the fcontact cache)
|
|
|
|
// This will also convert diaspora public key from pkcs#1 to pkcs#8
|
|
|
|
|
|
|
|
logger('Fetching key for '.$author_link);
|
|
|
|
$key = self::key($author_link);
|
|
|
|
|
|
|
|
if (!$key) {
|
|
|
|
logger('Could not retrieve author key.');
|
|
|
|
http_status_exit(400);
|
|
|
|
}
|
|
|
|
|
|
|
|
$verify = rsa_verify($signed_data,$signature,$key);
|
|
|
|
|
|
|
|
if (!$verify) {
|
|
|
|
logger('Message did not verify. Discarding.');
|
|
|
|
http_status_exit(400);
|
|
|
|
}
|
|
|
|
|
|
|
|
logger('Message verified.');
|
|
|
|
|
|
|
|
return array('message' => (string)$inner_decrypted,
|
|
|
|
'author' => unxmlify($author_link),
|
|
|
|
'key' => (string)$key);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief Dispatches public messages and find the fitting receivers
|
|
|
|
*
|
|
|
|
* @param array $msg The post that will be dispatched
|
|
|
|
*
|
|
|
|
* @return int The message id of the generated message, "true" or "false" if there was an error
|
|
|
|
*/
|
|
|
|
public static function dispatch_public($msg) {
|
|
|
|
|
|
|
|
$enabled = intval(get_config("system", "diaspora_enabled"));
|
|
|
|
if (!$enabled) {
|
|
|
|
logger("diaspora is disabled");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Use a dummy importer to import the data for the public copy
|
|
|
|
$importer = array("uid" => 0, "page-flags" => PAGE_FREELOVE);
|
|
|
|
$message_id = self::dispatch($importer,$msg);
|
|
|
|
|
|
|
|
// Now distribute it to the followers
|
|
|
|
$r = q("SELECT `user`.* FROM `user` WHERE `user`.`uid` IN
|
|
|
|
(SELECT `contact`.`uid` FROM `contact` WHERE `contact`.`network` = '%s' AND `contact`.`addr` = '%s')
|
|
|
|
AND NOT `account_expired` AND NOT `account_removed`",
|
|
|
|
dbesc(NETWORK_DIASPORA),
|
|
|
|
dbesc($msg["author"])
|
|
|
|
);
|
|
|
|
if($r) {
|
|
|
|
foreach($r as $rr) {
|
|
|
|
logger("delivering to: ".$rr["username"]);
|
|
|
|
self::dispatch($rr,$msg);
|
|
|
|
}
|
|
|
|
} else
|
|
|
|
logger("No subscribers for ".$msg["author"]." ".print_r($msg, true));
|
|
|
|
|
|
|
|
return $message_id;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief Dispatches the different message types to the different functions
|
|
|
|
*
|
|
|
|
* @param array $importer Array of the importer user
|
|
|
|
* @param array $msg The post that will be dispatched
|
|
|
|
*
|
|
|
|
* @return int The message id of the generated message, "true" or "false" if there was an error
|
|
|
|
*/
|
|
|
|
public static function dispatch($importer, $msg) {
|
|
|
|
|
|
|
|
// The sender is the handle of the contact that sent the message.
|
|
|
|
// This will often be different with relayed messages (for example "like" and "comment")
|
|
|
|
$sender = $msg["author"];
|
|
|
|
|
|
|
|
if (!diaspora::valid_posting($msg, $fields)) {
|
|
|
|
logger("Invalid posting");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
$type = $fields->getName();
|
|
|
|
|
|
|
|
logger("Received message type ".$type." from ".$sender." for user ".$importer["uid"], LOGGER_DEBUG);
|
|
|
|
|
|
|
|
switch ($type) {
|
|
|
|
case "account_deletion":
|
|
|
|
return self::receive_account_deletion($importer, $fields);
|
|
|
|
|
|
|
|
case "comment":
|
|
|
|
return self::receive_comment($importer, $sender, $fields, $msg["message"]);
|
|
|
|
|
|
|
|
case "contact":
|
|
|
|
return self::receive_contact_request($importer, $fields);
|
|
|
|
|
|
|
|
case "conversation":
|
|
|
|
return self::receive_conversation($importer, $msg, $fields);
|
|
|
|
|
|
|
|
case "like":
|
|
|
|
return self::receive_like($importer, $sender, $fields);
|
|
|
|
|
|
|
|
case "message":
|
|
|
|
return self::receive_message($importer, $fields);
|
|
|
|
|
|
|
|
case "participation": // Not implemented
|
|
|
|
return self::receive_participation($importer, $fields);
|
|
|
|
|
|
|
|
case "photo": // Not implemented
|
|
|
|
return self::receive_photo($importer, $fields);
|
|
|
|
|
|
|
|
case "poll_participation": // Not implemented
|
|
|
|
return self::receive_poll_participation($importer, $fields);
|
|
|
|
|
|
|
|
case "profile":
|
|
|
|
return self::receive_profile($importer, $fields);
|
|
|
|
|
|
|
|
case "reshare":
|
|
|
|
return self::receive_reshare($importer, $fields, $msg["message"]);
|
|
|
|
|
|
|
|
case "retraction":
|
|
|
|
return self::receive_retraction($importer, $sender, $fields);
|
|
|
|
|
|
|
|
case "status_message":
|
|
|
|
return self::receive_status_message($importer, $fields, $msg["message"]);
|
|
|
|
|
|
|
|
default:
|
|
|
|
logger("Unknown message type ".$type);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief Checks if a posting is valid and fetches the data fields.
|
|
|
|
*
|
|
|
|
* This function does not only check the signature.
|
|
|
|
* It also does the conversion between the old and the new diaspora format.
|
|
|
|
*
|
|
|
|
* @param array $msg Array with the XML, the sender handle and the sender signature
|
|
|
|
* @param object $fields SimpleXML object that contains the posting when it is valid
|
|
|
|
*
|
|
|
|
* @return bool Is the posting valid?
|
|
|
|
*/
|
|
|
|
private function valid_posting($msg, &$fields) {
|
|
|
|
|
|
|
|
$data = parse_xml_string($msg["message"], false);
|
|
|
|
|
|
|
|
if (!is_object($data)) {
|
|
|
|
logger("No valid XML ".$msg["message"], LOGGER_DEBUG);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
$first_child = $data->getName();
|
|
|
|
|
|
|
|
// Is this the new or the old version?
|
|
|
|
if ($data->getName() == "XML") {
|
|
|
|
$oldXML = true;
|
|
|
|
foreach ($data->post->children() as $child)
|
|
|
|
$element = $child;
|
|
|
|
} else {
|
|
|
|
$oldXML = false;
|
|
|
|
$element = $data;
|
|
|
|
}
|
|
|
|
|
|
|
|
$type = $element->getName();
|
|
|
|
$orig_type = $type;
|
|
|
|
|
|
|
|
// All retractions are handled identically from now on.
|
|
|
|
// In the new version there will only be "retraction".
|
|
|
|
if (in_array($type, array("signed_retraction", "relayable_retraction")))
|
|
|
|
$type = "retraction";
|
|
|
|
|
|
|
|
if ($type == "request")
|
|
|
|
$type = "contact";
|
|
|
|
|
|
|
|
$fields = new SimpleXMLElement("<".$type."/>");
|
|
|
|
|
|
|
|
$signed_data = "";
|
|
|
|
|
|
|
|
foreach ($element->children() AS $fieldname => $entry) {
|
|
|
|
if ($oldXML) {
|
|
|
|
// Translation for the old XML structure
|
|
|
|
if ($fieldname == "diaspora_handle")
|
|
|
|
$fieldname = "author";
|
|
|
|
|
|
|
|
if ($fieldname == "participant_handles")
|
|
|
|
$fieldname = "participants";
|
|
|
|
|
|
|
|
if (in_array($type, array("like", "participation"))) {
|
|
|
|
if ($fieldname == "target_type")
|
|
|
|
$fieldname = "parent_type";
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($fieldname == "sender_handle")
|
|
|
|
$fieldname = "author";
|
|
|
|
|
|
|
|
if ($fieldname == "recipient_handle")
|
|
|
|
$fieldname = "recipient";
|
|
|
|
|
|
|
|
if ($fieldname == "root_diaspora_id")
|
|
|
|
$fieldname = "root_author";
|
|
|
|
|
|
|
|
if ($type == "retraction") {
|
|
|
|
if ($fieldname == "post_guid")
|
|
|
|
$fieldname = "target_guid";
|
|
|
|
|
|
|
|
if ($fieldname == "type")
|
|
|
|
$fieldname = "target_type";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (($fieldname == "author_signature") AND ($entry != ""))
|
|
|
|
$author_signature = base64_decode($entry);
|
|
|
|
elseif (($fieldname == "parent_author_signature") AND ($entry != ""))
|
|
|
|
$parent_author_signature = base64_decode($entry);
|
|
|
|
elseif (!in_array($fieldname, array("author_signature", "parent_author_signature", "target_author_signature"))) {
|
|
|
|
if ($signed_data != "") {
|
|
|
|
$signed_data .= ";";
|
|
|
|
$signed_data_parent .= ";";
|
|
|
|
}
|
|
|
|
|
|
|
|
$signed_data .= $entry;
|
|
|
|
}
|
|
|
|
if (!in_array($fieldname, array("parent_author_signature", "target_author_signature")) OR
|
|
|
|
($orig_type == "relayable_retraction"))
|
|
|
|
xml::copy($entry, $fields, $fieldname);
|
|
|
|
}
|
|
|
|
|
|
|
|
// This is something that shouldn't happen at all.
|
|
|
|
if (in_array($type, array("status_message", "reshare", "profile")))
|
|
|
|
if ($msg["author"] != $fields->author) {
|
|
|
|
logger("Message handle is not the same as envelope sender. Quitting this message.");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Only some message types have signatures. So we quit here for the other types.
|
|
|
|
if (!in_array($type, array("comment", "message", "like")))
|
|
|
|
return true;
|
|
|
|
|
|
|
|
// No author_signature? This is a must, so we quit.
|
|
|
|
if (!isset($author_signature)) {
|
|
|
|
logger("No author signature for type ".$type." - Message: ".$msg["message"], LOGGER_DEBUG);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isset($parent_author_signature)) {
|
|
|
|
$key = self::key($msg["author"]);
|
|
|
|
|
|
|
|
if (!rsa_verify($signed_data, $parent_author_signature, $key, "sha256")) {
|
|
|
|
logger("No valid parent author signature for author ".$msg["author"]. " in type ".$type." - signed data: ".$signed_data." - Message: ".$msg["message"]." - Signature ".$parent_author_signature, LOGGER_DEBUG);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$key = self::key($fields->author);
|
|
|
|
|
|
|
|
if (!rsa_verify($signed_data, $author_signature, $key, "sha256")) {
|
|
|
|
logger("No valid author signature for author ".$msg["author"]. " in type ".$type." - signed data: ".$signed_data." - Message: ".$msg["message"]." - Signature ".$author_signature, LOGGER_DEBUG);
|
|
|
|
return false;
|
|
|
|
} else
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief Fetches the public key for a given handle
|
|
|
|
*
|
|
|
|
* @param string $handle The handle
|
|
|
|
*
|
|
|
|
* @return string The public key
|
|
|
|
*/
|
|
|
|
private function key($handle) {
|
|
|
|
$handle = strval($handle);
|
|
|
|
|
|
|
|
logger("Fetching diaspora key for: ".$handle);
|
|
|
|
|
|
|
|
$r = self::person_by_handle($handle);
|
|
|
|
if($r)
|
|
|
|
return $r["pubkey"];
|
|
|
|
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief Fetches data for a given handle
|
|
|
|
*
|
|
|
|
* @param string $handle The handle
|
|
|
|
*
|
|
|
|
* @return array the queried data
|
|
|
|
*/
|
|
|
|
private function person_by_handle($handle) {
|
|
|
|
|
|
|
|
$r = q("SELECT * FROM `fcontact` WHERE `network` = '%s' AND `addr` = '%s' LIMIT 1",
|
|
|
|
dbesc(NETWORK_DIASPORA),
|
|
|
|
dbesc($handle)
|
|
|
|
);
|
|
|
|
if ($r) {
|
|
|
|
$person = $r[0];
|
|
|
|
logger("In cache ".print_r($r,true), LOGGER_DEBUG);
|
|
|
|
|
|
|
|
// update record occasionally so it doesn't get stale
|
|
|
|
$d = strtotime($person["updated"]." +00:00");
|
|
|
|
if ($d < strtotime("now - 14 days"))
|
|
|
|
$update = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$person OR $update) {
|
|
|
|
logger("create or refresh", LOGGER_DEBUG);
|
|
|
|
$r = probe_url($handle, PROBE_DIASPORA);
|
|
|
|
|
|
|
|
// Note that Friendica contacts will return a "Diaspora person"
|
|
|
|
// if Diaspora connectivity is enabled on their server
|
|
|
|
if ($r AND ($r["network"] === NETWORK_DIASPORA)) {
|
|
|
|
self::add_fcontact($r, $update);
|
|
|
|
$person = $r;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return $person;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief Updates the fcontact table
|
|
|
|
*
|
|
|
|
* @param array $arr The fcontact data
|
|
|
|
* @param bool $update Update or insert?
|
|
|
|
*
|
|
|
|
* @return string The id of the fcontact entry
|
|
|
|
*/
|
|
|
|
private function add_fcontact($arr, $update = false) {
|
|
|
|
|
|
|
|
if($update) {
|
|
|
|
$r = q("UPDATE `fcontact` SET
|
|
|
|
`name` = '%s',
|
|
|
|
`photo` = '%s',
|
|
|
|
`request` = '%s',
|
|
|
|
`nick` = '%s',
|
|
|
|
`addr` = '%s',
|
|
|
|
`batch` = '%s',
|
|
|
|
`notify` = '%s',
|
|
|
|
`poll` = '%s',
|
|
|
|
`confirm` = '%s',
|
|
|
|
`alias` = '%s',
|
|
|
|
`pubkey` = '%s',
|
|
|
|
`updated` = '%s'
|
|
|
|
WHERE `url` = '%s' AND `network` = '%s'",
|
|
|
|
dbesc($arr["name"]),
|
|
|
|
dbesc($arr["photo"]),
|
|
|
|
dbesc($arr["request"]),
|
|
|
|
dbesc($arr["nick"]),
|
|
|
|
dbesc($arr["addr"]),
|
|
|
|
dbesc($arr["batch"]),
|
|
|
|
dbesc($arr["notify"]),
|
|
|
|
dbesc($arr["poll"]),
|
|
|
|
dbesc($arr["confirm"]),
|
|
|
|
dbesc($arr["alias"]),
|
|
|
|
dbesc($arr["pubkey"]),
|
|
|
|
dbesc(datetime_convert()),
|
|
|
|
dbesc($arr["url"]),
|
|
|
|
dbesc($arr["network"])
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
$r = q("INSERT INTO `fcontact` (`url`,`name`,`photo`,`request`,`nick`,`addr`,
|
|
|
|
`batch`, `notify`,`poll`,`confirm`,`network`,`alias`,`pubkey`,`updated`)
|
|
|
|
VALUES ('%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s')",
|
|
|
|
dbesc($arr["url"]),
|
|
|
|
dbesc($arr["name"]),
|
|
|
|
dbesc($arr["photo"]),
|
|
|
|
dbesc($arr["request"]),
|
|
|
|
dbesc($arr["nick"]),
|
|
|
|
dbesc($arr["addr"]),
|
|
|
|
dbesc($arr["batch"]),
|
|
|
|
dbesc($arr["notify"]),
|
|
|
|
dbesc($arr["poll"]),
|
|
|
|
dbesc($arr["confirm"]),
|
|
|
|
dbesc($arr["network"]),
|
|
|
|
dbesc($arr["alias"]),
|
|
|
|
dbesc($arr["pubkey"]),
|
|
|
|
dbesc(datetime_convert())
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $r;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief get a handle (user@domain.tld) from a given contact id or gcontact id
|
|
|
|
*
|
|
|
|
* @param int $contact_id The id in the contact table
|
|
|
|
* @param int $gcontact_id The id in the gcontact table
|
|
|
|
*
|
|
|
|
* @return string the handle
|
|
|
|
*/
|
|
|
|
public static function handle_from_contact($contact_id, $gcontact_id = 0) {
|
|
|
|
$handle = False;
|
|
|
|
|
|
|
|
logger("contact id is ".$contact_id." - gcontact id is ".$gcontact_id, LOGGER_DEBUG);
|
|
|
|
|
|
|
|
if ($gcontact_id != 0) {
|
|
|
|
$r = q("SELECT `addr` FROM `gcontact` WHERE `id` = %d AND `addr` != ''",
|
|
|
|
intval($gcontact_id));
|
|
|
|
if ($r)
|
|
|
|
return $r[0]["addr"];
|
|
|
|
}
|
|
|
|
|
|
|
|
$r = q("SELECT `network`, `addr`, `self`, `url`, `nick` FROM `contact` WHERE `id` = %d",
|
|
|
|
intval($contact_id));
|
|
|
|
if ($r) {
|
|
|
|
$contact = $r[0];
|
|
|
|
|
|
|
|
logger("contact 'self' = ".$contact['self']." 'url' = ".$contact['url'], LOGGER_DEBUG);
|
|
|
|
|
|
|
|
if($contact['addr'] != "")
|
|
|
|
$handle = $contact['addr'];
|
|
|
|
else {
|
|
|
|
$baseurl_start = strpos($contact['url'],'://') + 3;
|
|
|
|
$baseurl_length = strpos($contact['url'],'/profile') - $baseurl_start; // allows installations in a subdirectory--not sure how Diaspora will handle
|
|
|
|
$baseurl = substr($contact['url'], $baseurl_start, $baseurl_length);
|
|
|
|
$handle = $contact['nick'].'@'.$baseurl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $handle;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief Get a contact id for a given handle
|
|
|
|
*
|
|
|
|
* @param int $uid The user id
|
|
|
|
* @param string $handle The handle in the format user@domain.tld
|
|
|
|
*
|
|
|
|
* @return The contact id
|
|
|
|
*/
|
|
|
|
private function contact_by_handle($uid, $handle) {
|
|
|
|
$r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `addr` = '%s' LIMIT 1",
|
|
|
|
intval($uid),
|
|
|
|
dbesc($handle)
|
|
|
|
);
|
|
|
|
|
|
|
|
if ($r)
|
|
|
|
return $r[0];
|
|
|
|
|
|
|
|
$handle_parts = explode("@", $handle);
|
|
|
|
$nurl_sql = "%%://".$handle_parts[1]."%%/profile/".$handle_parts[0];
|
|
|
|
$r = q("SELECT * FROM `contact` WHERE `network` = '%s' AND `uid` = %d AND `nurl` LIKE '%s' LIMIT 1",
|
|
|
|
dbesc(NETWORK_DFRN),
|
|
|
|
intval($uid),
|
|
|
|
dbesc($nurl_sql)
|
|
|
|
);
|
|
|
|
if($r)
|
|
|
|
return $r[0];
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief Check if posting is allowed for this contact
|
|
|
|
*
|
|
|
|
* @param array $importer Array of the importer user
|
|
|
|
* @param array $contact The contact that is checked
|
|
|
|
* @param bool $is_comment Is the check for a comment?
|
|
|
|
*
|
|
|
|
* @return bool is the contact allowed to post?
|
|
|
|
*/
|
|
|
|
private function post_allow($importer, $contact, $is_comment = false) {
|
|
|
|
|
|
|
|
// perhaps we were already sharing with this person. Now they're sharing with us.
|
|
|
|
// That makes us friends.
|
|
|
|
// Normally this should have handled by getting a request - but this could get lost
|
|
|
|
if($contact["rel"] == CONTACT_IS_FOLLOWER && in_array($importer["page-flags"], array(PAGE_FREELOVE))) {
|
|
|
|
q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
|
|
|
|
intval(CONTACT_IS_FRIEND),
|
|
|
|
intval($contact["id"]),
|
|
|
|
intval($importer["uid"])
|
|
|
|
);
|
|
|
|
$contact["rel"] = CONTACT_IS_FRIEND;
|
|
|
|
logger("defining user ".$contact["nick"]." as friend");
|
|
|
|
}
|
|
|
|
|
|
|
|
if(($contact["blocked"]) || ($contact["readonly"]) || ($contact["archive"]))
|
|
|
|
return false;
|
|
|
|
if($contact["rel"] == CONTACT_IS_SHARING || $contact["rel"] == CONTACT_IS_FRIEND)
|
|
|
|
return true;
|
|
|
|
if($contact["rel"] == CONTACT_IS_FOLLOWER)
|
|
|
|
if(($importer["page-flags"] == PAGE_COMMUNITY) OR $is_comment)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
// Messages for the global users are always accepted
|
|
|
|
if ($importer["uid"] == 0)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief Fetches the contact id for a handle and checks if posting is allowed
|
|
|
|
*
|
|