Friendica Communications Platform (please note that this is a clone of the repository at github, issues are handled there) https://friendi.ca
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2150 lines
62 KiB

10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
  1. <?php
  2. require_once('include/crypto.php');
  3. require_once('include/items.php');
  4. require_once('include/bb2diaspora.php');
  5. require_once('include/contact_selectors.php');
  6. require_once('include/queue_fn.php');
  7. function diaspora_dispatch_public($msg) {
  8. $r = q("SELECT `user`.* FROM `user` WHERE `user`.`uid` IN ( SELECT `contact`.`uid` FROM `contact` WHERE `contact`.`network` = '%s' AND `contact`.`addr` = '%s' ) AND `account_expired` = 0 ",
  9. dbesc(NETWORK_DIASPORA),
  10. dbesc($msg['author'])
  11. );
  12. if(count($r)) {
  13. foreach($r as $rr) {
  14. logger('diaspora_public: delivering to: ' . $rr['username']);
  15. diaspora_dispatch($rr,$msg);
  16. }
  17. }
  18. else
  19. logger('diaspora_public: no subscribers');
  20. }
  21. function diaspora_dispatch($importer,$msg) {
  22. $ret = 0;
  23. // php doesn't like dashes in variable names
  24. $msg['message'] = str_replace(
  25. array('<activity_streams-photo>','</activity_streams-photo>'),
  26. array('<asphoto>','</asphoto>'),
  27. $msg['message']);
  28. $parsed_xml = parse_xml_string($msg['message'],false);
  29. $xmlbase = $parsed_xml->post;
  30. logger('diaspora_dispatch: ' . print_r($xmlbase,true), LOGGER_DEBUG);
  31. if($xmlbase->request) {
  32. $ret = diaspora_request($importer,$xmlbase->request);
  33. }
  34. elseif($xmlbase->status_message) {
  35. $ret = diaspora_post($importer,$xmlbase->status_message);
  36. }
  37. elseif($xmlbase->profile) {
  38. $ret = diaspora_profile($importer,$xmlbase->profile);
  39. }
  40. elseif($xmlbase->comment) {
  41. $ret = diaspora_comment($importer,$xmlbase->comment,$msg);
  42. }
  43. elseif($xmlbase->like) {
  44. $ret = diaspora_like($importer,$xmlbase->like,$msg);
  45. }
  46. elseif($xmlbase->asphoto) {
  47. $ret = diaspora_asphoto($importer,$xmlbase->asphoto);
  48. }
  49. elseif($xmlbase->reshare) {
  50. $ret = diaspora_reshare($importer,$xmlbase->reshare);
  51. }
  52. elseif($xmlbase->retraction) {
  53. $ret = diaspora_retraction($importer,$xmlbase->retraction,$msg);
  54. }
  55. elseif($xmlbase->signed_retraction) {
  56. $ret = diaspora_signed_retraction($importer,$xmlbase->retraction,$msg);
  57. }
  58. elseif($xmlbase->photo) {
  59. $ret = diaspora_photo($importer,$xmlbase->photo,$msg);
  60. }
  61. elseif($xmlbase->conversation) {
  62. $ret = diaspora_conversation($importer,$xmlbase->conversation,$msg);
  63. }
  64. elseif($xmlbase->message) {
  65. $ret = diaspora_message($importer,$xmlbase->message,$msg);
  66. }
  67. else {
  68. logger('diaspora_dispatch: unknown message type: ' . print_r($xmlbase,true));
  69. }
  70. return $ret;
  71. }
  72. function diaspora_get_contact_by_handle($uid,$handle) {
  73. $r = q("SELECT * FROM `contact` WHERE `network` = '%s' AND `uid` = %d AND `addr` = '%s' LIMIT 1",
  74. dbesc(NETWORK_DIASPORA),
  75. intval($uid),
  76. dbesc($handle)
  77. );
  78. if($r && count($r))
  79. return $r[0];
  80. return false;
  81. }
  82. function find_diaspora_person_by_handle($handle) {
  83. $update = false;
  84. $r = q("select * from fcontact where network = '%s' and addr = '%s' limit 1",
  85. dbesc(NETWORK_DIASPORA),
  86. dbesc($handle)
  87. );
  88. if(count($r)) {
  89. logger('find_diaspora_person_by handle: in cache ' . print_r($r,true), LOGGER_DEBUG);
  90. // update record occasionally so it doesn't get stale
  91. $d = strtotime($r[0]['updated'] . ' +00:00');
  92. if($d > strtotime('now - 14 days'))
  93. return $r[0];
  94. $update = true;
  95. }
  96. logger('find_diaspora_person_by_handle: refresh',LOGGER_DEBUG);
  97. require_once('include/Scrape.php');
  98. $r = probe_url($handle, PROBE_DIASPORA);
  99. if((count($r)) && ($r['network'] === NETWORK_DIASPORA)) {
  100. add_fcontact($r,$update);
  101. return ($r);
  102. }
  103. return false;
  104. }
  105. function get_diaspora_key($uri) {
  106. logger('Fetching diaspora key for: ' . $uri);
  107. $r = find_diaspora_person_by_handle($uri);
  108. if($r)
  109. return $r['pubkey'];
  110. return '';
  111. }
  112. function diaspora_pubmsg_build($msg,$user,$contact,$prvkey,$pubkey) {
  113. $a = get_app();
  114. logger('diaspora_pubmsg_build: ' . $msg, LOGGER_DATA);
  115. $handle = $user['nickname'] . '@' . substr($a->get_baseurl(), strpos($a->get_baseurl(),'://') + 3);
  116. // $b64_data = base64_encode($msg);
  117. // $b64url_data = base64url_encode($b64_data);
  118. $b64url_data = base64url_encode($msg);
  119. $data = str_replace(array("\n","\r"," ","\t"),array('','','',''),$b64url_data);
  120. $type = 'application/xml';
  121. $encoding = 'base64url';
  122. $alg = 'RSA-SHA256';
  123. $signable_data = $data . '.' . base64url_encode($type) . '.'
  124. . base64url_encode($encoding) . '.' . base64url_encode($alg) ;
  125. $signature = rsa_sign($signable_data,$prvkey);
  126. $sig = base64url_encode($signature);
  127. $magic_env = <<< EOT
  128. <?xml version='1.0' encoding='UTF-8'?>
  129. <diaspora xmlns="https://joindiaspora.com/protocol" xmlns:me="http://salmon-protocol.org/ns/magic-env" >
  130. <header>
  131. <author_id>$handle</author_id>
  132. </header>
  133. <me:env>
  134. <me:encoding>base64url</me:encoding>
  135. <me:alg>RSA-SHA256</me:alg>
  136. <me:data type="application/xml">$data</me:data>
  137. <me:sig>$sig</me:sig>
  138. </me:env>
  139. </diaspora>
  140. EOT;
  141. logger('diaspora_pubmsg_build: magic_env: ' . $magic_env, LOGGER_DATA);
  142. return $magic_env;
  143. }
  144. function diaspora_msg_build($msg,$user,$contact,$prvkey,$pubkey,$public = false) {
  145. $a = get_app();
  146. if($public)
  147. return diaspora_pubmsg_build($msg,$user,$contact,$prvkey,$pubkey);
  148. logger('diaspora_msg_build: ' . $msg, LOGGER_DATA);
  149. $inner_aes_key = random_string(32);
  150. $b_inner_aes_key = base64_encode($inner_aes_key);
  151. $inner_iv = random_string(16);
  152. $b_inner_iv = base64_encode($inner_iv);
  153. $outer_aes_key = random_string(32);
  154. $b_outer_aes_key = base64_encode($outer_aes_key);
  155. $outer_iv = random_string(16);
  156. $b_outer_iv = base64_encode($outer_iv);
  157. $handle = $user['nickname'] . '@' . substr($a->get_baseurl(), strpos($a->get_baseurl(),'://') + 3);
  158. $padded_data = pkcs5_pad($msg,16);
  159. $inner_encrypted = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $inner_aes_key, $padded_data, MCRYPT_MODE_CBC, $inner_iv);
  160. $b64_data = base64_encode($inner_encrypted);
  161. $b64url_data = base64url_encode($b64_data);
  162. $data = str_replace(array("\n","\r"," ","\t"),array('','','',''),$b64url_data);
  163. $type = 'application/xml';
  164. $encoding = 'base64url';
  165. $alg = 'RSA-SHA256';
  166. $signable_data = $data . '.' . base64url_encode($type) . '.'
  167. . base64url_encode($encoding) . '.' . base64url_encode($alg) ;
  168. $signature = rsa_sign($signable_data,$prvkey);
  169. $sig = base64url_encode($signature);
  170. $decrypted_header = <<< EOT
  171. <decrypted_header>
  172. <iv>$b_inner_iv</iv>
  173. <aes_key>$b_inner_aes_key</aes_key>
  174. <author_id>$handle</author_id>
  175. </decrypted_header>
  176. EOT;
  177. $decrypted_header = pkcs5_pad($decrypted_header,16);
  178. $ciphertext = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $outer_aes_key, $decrypted_header, MCRYPT_MODE_CBC, $outer_iv);
  179. $outer_json = json_encode(array('iv' => $b_outer_iv,'key' => $b_outer_aes_key));
  180. $encrypted_outer_key_bundle = '';
  181. openssl_public_encrypt($outer_json,$encrypted_outer_key_bundle,$pubkey);
  182. $b64_encrypted_outer_key_bundle = base64_encode($encrypted_outer_key_bundle);
  183. logger('outer_bundle: ' . $b64_encrypted_outer_key_bundle . ' key: ' . $pubkey, LOGGER_DATA);
  184. $encrypted_header_json_object = json_encode(array('aes_key' => base64_encode($encrypted_outer_key_bundle),
  185. 'ciphertext' => base64_encode($ciphertext)));
  186. $cipher_json = base64_encode($encrypted_header_json_object);
  187. $encrypted_header = '<encrypted_header>' . $cipher_json . '</encrypted_header>';
  188. $magic_env = <<< EOT
  189. <?xml version='1.0' encoding='UTF-8'?>
  190. <diaspora xmlns="https://joindiaspora.com/protocol" xmlns:me="http://salmon-protocol.org/ns/magic-env" >
  191. $encrypted_header
  192. <me:env>
  193. <me:encoding>base64url</me:encoding>
  194. <me:alg>RSA-SHA256</me:alg>
  195. <me:data type="application/xml">$data</me:data>
  196. <me:sig>$sig</me:sig>
  197. </me:env>
  198. </diaspora>
  199. EOT;
  200. logger('diaspora_msg_build: magic_env: ' . $magic_env, LOGGER_DATA);
  201. return $magic_env;
  202. }
  203. /**
  204. *
  205. * diaspora_decode($importer,$xml)
  206. * array $importer -> from user table
  207. * string $xml -> urldecoded Diaspora salmon
  208. *
  209. * Returns array
  210. * 'message' -> decoded Diaspora XML message
  211. * 'author' -> author diaspora handle
  212. * 'key' -> author public key (converted to pkcs#8)
  213. *
  214. * Author and key are used elsewhere to save a lookup for verifying replies and likes
  215. */
  216. function diaspora_decode($importer,$xml) {
  217. $public = false;
  218. $basedom = parse_xml_string($xml);
  219. $children = $basedom->children('https://joindiaspora.com/protocol');
  220. if($children->header) {
  221. $public = true;
  222. $author_link = str_replace('acct:','',$children->header->author_id);
  223. }
  224. else {
  225. $encrypted_header = json_decode(base64_decode($children->encrypted_header));
  226. $encrypted_aes_key_bundle = base64_decode($encrypted_header->aes_key);
  227. $ciphertext = base64_decode($encrypted_header->ciphertext);
  228. $outer_key_bundle = '';
  229. openssl_private_decrypt($encrypted_aes_key_bundle,$outer_key_bundle,$importer['prvkey']);
  230. $j_outer_key_bundle = json_decode($outer_key_bundle);
  231. $outer_iv = base64_decode($j_outer_key_bundle->iv);
  232. $outer_key = base64_decode($j_outer_key_bundle->key);
  233. $decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $outer_key, $ciphertext, MCRYPT_MODE_CBC, $outer_iv);
  234. $decrypted = pkcs5_unpad($decrypted);
  235. /**
  236. * $decrypted now contains something like
  237. *
  238. * <decrypted_header>
  239. * <iv>8e+G2+ET8l5BPuW0sVTnQw==</iv>
  240. * <aes_key>UvSMb4puPeB14STkcDWq+4QE302Edu15oaprAQSkLKU=</aes_key>
  241. ***** OBSOLETE
  242. * <author>
  243. * <name>Ryan Hughes</name>
  244. * <uri>acct:galaxor@diaspora.pirateship.org</uri>
  245. * </author>
  246. ***** CURRENT
  247. * <author_id>galaxor@diaspora.priateship.org</author_id>
  248. ***** END DIFFS
  249. * </decrypted_header>
  250. */
  251. logger('decrypted: ' . $decrypted, LOGGER_DEBUG);
  252. $idom = parse_xml_string($decrypted,false);
  253. $inner_iv = base64_decode($idom->iv);
  254. $inner_aes_key = base64_decode($idom->aes_key);
  255. $author_link = str_replace('acct:','',$idom->author_id);
  256. }
  257. $dom = $basedom->children(NAMESPACE_SALMON_ME);
  258. // figure out where in the DOM tree our data is hiding
  259. if($dom->provenance->data)
  260. $base = $dom->provenance;
  261. elseif($dom->env->data)
  262. $base = $dom->env;
  263. elseif($dom->data)
  264. $base = $dom;
  265. if(! $base) {
  266. logger('mod-diaspora: unable to locate salmon data in xml ');
  267. http_status_exit(400);
  268. }
  269. // Stash the signature away for now. We have to find their key or it won't be good for anything.
  270. $signature = base64url_decode($base->sig);
  271. // unpack the data
  272. // strip whitespace so our data element will return to one big base64 blob
  273. $data = str_replace(array(" ","\t","\r","\n"),array("","","",""),$base->data);
  274. // stash away some other stuff for later
  275. $type = $base->data[0]->attributes()->type[0];
  276. $keyhash = $base->sig[0]->attributes()->keyhash[0];
  277. $encoding = $base->encoding;
  278. $alg = $base->alg;
  279. $signed_data = $data . '.' . base64url_encode($type) . '.' . base64url_encode($encoding) . '.' . base64url_encode($alg);
  280. // decode the data
  281. $data = base64url_decode($data);
  282. if($public) {
  283. $inner_decrypted = $data;
  284. }
  285. else {
  286. // Decode the encrypted blob
  287. $inner_encrypted = base64_decode($data);
  288. $inner_decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $inner_aes_key, $inner_encrypted, MCRYPT_MODE_CBC, $inner_iv);
  289. $inner_decrypted = pkcs5_unpad($inner_decrypted);
  290. }
  291. if(! $author_link) {
  292. logger('mod-diaspora: Could not retrieve author URI.');
  293. http_status_exit(400);
  294. }
  295. // Once we have the author URI, go to the web and try to find their public key
  296. // (first this will look it up locally if it is in the fcontact cache)
  297. // This will also convert diaspora public key from pkcs#1 to pkcs#8
  298. logger('mod-diaspora: Fetching key for ' . $author_link );
  299. $key = get_diaspora_key($author_link);
  300. if(! $key) {
  301. logger('mod-diaspora: Could not retrieve author key.');
  302. http_status_exit(400);
  303. }
  304. $verify = rsa_verify($signed_data,$signature,$key);
  305. if(! $verify) {
  306. logger('mod-diaspora: Message did not verify. Discarding.');
  307. http_status_exit(400);
  308. }
  309. logger('mod-diaspora: Message verified.');
  310. return array('message' => $inner_decrypted, 'author' => $author_link, 'key' => $key);
  311. }
  312. function diaspora_request($importer,$xml) {
  313. $a = get_app();
  314. $sender_handle = unxmlify($xml->sender_handle);
  315. $recipient_handle = unxmlify($xml->recipient_handle);
  316. if(! $sender_handle || ! $recipient_handle)
  317. return;
  318. $contact = diaspora_get_contact_by_handle($importer['uid'],$sender_handle);
  319. if($contact) {
  320. // perhaps we were already sharing with this person. Now they're sharing with us.
  321. // That makes us friends.
  322. if($contact['rel'] == CONTACT_IS_FOLLOWER) {
  323. q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d LIMIT 1",
  324. intval(CONTACT_IS_FRIEND),
  325. intval($contact['id']),
  326. intval($importer['uid'])
  327. );
  328. }
  329. // send notification
  330. $r = q("SELECT `hide-friends` FROM `profile` WHERE `uid` = %d AND `is-default` = 1 LIMIT 1",
  331. intval($importer['uid'])
  332. );
  333. if((count($r)) && ($r[0]['hide-friends'] == 0)) {
  334. require_once('include/items.php');
  335. $self = q("SELECT * FROM `contact` WHERE `self` = 1 AND `uid` = %d LIMIT 1",
  336. intval($importer['uid'])
  337. );
  338. // they are not CONTACT_IS_FOLLOWER anymore but that's what we have in the array
  339. if(count($self) && $contact['rel'] == CONTACT_IS_FOLLOWER) {
  340. $arr = array();
  341. $arr['uri'] = $arr['parent-uri'] = item_new_uri($a->get_hostname(), $importer['uid']);
  342. $arr['uid'] = $importer['uid'];
  343. $arr['contact-id'] = $self[0]['id'];
  344. $arr['wall'] = 1;
  345. $arr['type'] = 'wall';
  346. $arr['gravity'] = 0;
  347. $arr['origin'] = 1;
  348. $arr['author-name'] = $arr['owner-name'] = $self[0]['name'];
  349. $arr['author-link'] = $arr['owner-link'] = $self[0]['url'];
  350. $arr['author-avatar'] = $arr['owner-avatar'] = $self[0]['thumb'];
  351. $arr['verb'] = ACTIVITY_FRIEND;
  352. $arr['object-type'] = ACTIVITY_OBJ_PERSON;
  353. $A = '[url=' . $self[0]['url'] . ']' . $self[0]['name'] . '[/url]';
  354. $B = '[url=' . $contact['url'] . ']' . $contact['name'] . '[/url]';
  355. $BPhoto = '[url=' . $contact['url'] . ']' . '[img]' . $contact['thumb'] . '[/img][/url]';
  356. $arr['body'] = sprintf( t('%1$s is now friends with %2$s'), $A, $B)."\n\n\n".$Bphoto;
  357. $arr['object'] = '<object><type>' . ACTIVITY_OBJ_PERSON . '</type><title>' . $contact['name'] . '</title>'
  358. . '<id>' . $contact['url'] . '/' . $contact['name'] . '</id>';
  359. $arr['object'] .= '<link>' . xmlify('<link rel="alternate" type="text/html" href="' . $contact['url'] . '" />' . "\n");
  360. $arr['object'] .= xmlify('<link rel="photo" type="image/jpeg" href="' . $contact['thumb'] . '" />' . "\n");
  361. $arr['object'] .= '</link></object>' . "\n";
  362. $arr['last-child'] = 1;
  363. $arr['allow_cid'] = $user[0]['allow_cid'];
  364. $arr['allow_gid'] = $user[0]['allow_gid'];
  365. $arr['deny_cid'] = $user[0]['deny_cid'];
  366. $arr['deny_gid'] = $user[0]['deny_gid'];
  367. $i = item_store($arr);
  368. if($i)
  369. proc_run('php',"include/notifier.php","activity","$i");
  370. }
  371. }
  372. return;
  373. }
  374. $ret = find_diaspora_person_by_handle($sender_handle);
  375. if((! count($ret)) || ($ret['network'] != NETWORK_DIASPORA)) {
  376. logger('diaspora_request: Cannot resolve diaspora handle ' . $sender_handle . ' for ' . $recipient_handle);
  377. return;
  378. }
  379. $batch = (($ret['batch']) ? $ret['batch'] : implode('/', array_slice(explode('/',$ret['url']),0,3)) . '/receive/public');
  380. $r = q("INSERT INTO `contact` (`uid`, `network`,`addr`,`created`,`url`,`nurl`,`batch`,`name`,`nick`,`photo`,`pubkey`,`notify`,`poll`,`blocked`,`priority`)
  381. VALUES ( %d, '%s', '%s', '%s', '%s','%s','%s','%s','%s','%s','%s','%s','%s',%d,%d) ",
  382. intval($importer['uid']),
  383. dbesc($ret['network']),
  384. dbesc($ret['addr']),
  385. datetime_convert(),
  386. dbesc($ret['url']),
  387. dbesc(normalise_link($ret['url'])),
  388. dbesc($batch),
  389. dbesc($ret['name']),
  390. dbesc($ret['nick']),
  391. dbesc($ret['photo']),
  392. dbesc($ret['pubkey']),
  393. dbesc($ret['notify']),
  394. dbesc($ret['poll']),
  395. 1,
  396. 2
  397. );
  398. // find the contact record we just created
  399. $contact_record = diaspora_get_contact_by_handle($importer['uid'],$sender_handle);
  400. $hash = random_string() . (string) time(); // Generate a confirm_key
  401. if($contact_record) {
  402. $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `note`, `hash`, `datetime` )
  403. VALUES ( %d, %d, %d, %d, '%s', '%s', '%s' )",
  404. intval($importer['uid']),
  405. intval($contact_record['id']),
  406. 0,
  407. 0,
  408. dbesc( t('Sharing notification from Diaspora network')),
  409. dbesc($hash),
  410. dbesc(datetime_convert())
  411. );
  412. }
  413. return;
  414. }
  415. function diaspora_post($importer,$xml) {
  416. $a = get_app();
  417. $guid = notags(unxmlify($xml->guid));
  418. $diaspora_handle = notags(unxmlify($xml->diaspora_handle));
  419. $contact = diaspora_get_contact_by_handle($importer['uid'],$diaspora_handle);
  420. if(! $contact)
  421. return;
  422. if(($contact['rel'] == CONTACT_IS_FOLLOWER) || ($contact['blocked']) || ($contact['readonly'])) {
  423. logger('diaspora_post: Ignoring this author.');
  424. return 202;
  425. }
  426. $message_id = $diaspora_handle . ':' . $guid;
  427. $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s' AND `guid` = '%s' LIMIT 1",
  428. intval($importer['uid']),
  429. dbesc($message_id),
  430. dbesc($guid)
  431. );
  432. if(count($r)) {
  433. logger('diaspora_post: message exists: ' . $guid);
  434. return;
  435. }
  436. // allocate a guid on our system - we aren't fixing any collisions.
  437. // we're ignoring them
  438. $g = q("select * from guid where guid = '%s' limit 1",
  439. dbesc($guid)
  440. );
  441. if(! count($g)) {
  442. q("insert into guid ( guid ) values ( '%s' )",
  443. dbesc($guid)
  444. );
  445. }
  446. $created = unxmlify($xml->created_at);
  447. $private = ((unxmlify($xml->public) == 'false') ? 1 : 0);
  448. $body = diaspora2bb($xml->raw_message);
  449. $datarray = array();
  450. $str_tags = '';
  451. $tags = get_tags($body);
  452. if(count($tags)) {
  453. foreach($tags as $tag) {
  454. if(strpos($tag,'#') === 0) {
  455. if(strpos($tag,'[url='))
  456. continue;
  457. $basetag = str_replace('_',' ',substr($tag,1));
  458. $body = str_replace($tag,'#[url=' . $a->get_baseurl() . '/search?search=' . rawurlencode($basetag) . ']' . $basetag . '[/url]',$body);
  459. if(strlen($str_tags))
  460. $str_tags .= ',';
  461. $str_tags .= '#[url=' . $a->get_baseurl() . '/search?search=' . rawurlencode($basetag) . ']' . $basetag . '[/url]';
  462. continue;
  463. }
  464. }
  465. }
  466. $cnt = preg_match_all('/@\[url=(.*?)\[\/url\]/ism',$body,$matches,PREG_SET_ORDER);
  467. if($cnt) {
  468. foreach($matches as $mtch) {
  469. if(strlen($str_tags))
  470. $str_tags .= ',';
  471. $str_tags .= '@[url=' . $mtch[1] . '[/url]';
  472. }
  473. }
  474. $datarray['uid'] = $importer['uid'];
  475. $datarray['contact-id'] = $contact['id'];
  476. $datarray['wall'] = 0;
  477. $datarray['guid'] = $guid;
  478. $datarray['uri'] = $datarray['parent-uri'] = $message_id;
  479. $datarray['created'] = $datarray['edited'] = datetime_convert('UTC','UTC',$created);
  480. $datarray['private'] = $private;
  481. $datarray['parent'] = 0;
  482. $datarray['owner-name'] = $contact['name'];
  483. $datarray['owner-link'] = $contact['url'];
  484. $datarray['owner-avatar'] = $contact['thumb'];
  485. $datarray['author-name'] = $contact['name'];
  486. $datarray['author-link'] = $contact['url'];
  487. $datarray['author-avatar'] = $contact['thumb'];
  488. $datarray['body'] = $body;
  489. $datarray['tag'] = $str_tags;
  490. $datarray['app'] = 'Diaspora';
  491. // if empty content it might be a photo that hasn't arrived yet. If a photo arrives, we'll make it visible.
  492. $datarray['visible'] = ((strlen($body)) ? 1 : 0);
  493. $message_id = item_store($datarray);
  494. if($message_id) {
  495. q("update item set plink = '%s' where id = %d limit 1",
  496. dbesc($a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $message_id),
  497. intval($message_id)
  498. );
  499. }
  500. return;
  501. }
  502. function diaspora_reshare($importer,$xml) {
  503. logger('diaspora_reshare: init: ' . print_r($xml,true));
  504. $a = get_app();
  505. $guid = notags(unxmlify($xml->guid));
  506. $diaspora_handle = notags(unxmlify($xml->diaspora_handle));
  507. $contact = diaspora_get_contact_by_handle($importer['uid'],$diaspora_handle);
  508. if(! $contact)
  509. return;
  510. if(($contact['rel'] == CONTACT_IS_FOLLOWER) || ($contact['blocked']) || ($contact['readonly'])) {
  511. logger('diaspora_reshare: Ignoring this author: ' . $diaspora_handle . ' ' . print_r($xml,true));
  512. return 202;
  513. }
  514. $message_id = $diaspora_handle . ':' . $guid;
  515. $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s' AND `guid` = '%s' LIMIT 1",
  516. intval($importer['uid']),
  517. dbesc($message_id),
  518. dbesc($guid)
  519. );
  520. if(count($r)) {
  521. logger('diaspora_reshare: message exists: ' . $guid);
  522. return;
  523. }
  524. $orig_author = notags(unxmlify($xml->root_diaspora_id));
  525. $orig_guid = notags(unxmlify($xml->root_guid));
  526. $source_url = 'https://' . substr($orig_author,strpos($orig_author,'@')+1) . '/p/' . $orig_guid . '.xml';
  527. $x = fetch_url($source_url);
  528. if(! $x)
  529. $x = fetch_url(str_replace('https://','http://',$source_url));
  530. if(! $x) {
  531. logger('diaspora_reshare: unable to fetch source url ' . $source_url);
  532. return;
  533. }
  534. logger('diaspora_reshare: source: ' . $x);
  535. $x = str_replace(array('<activity_streams-photo>','</activity_streams-photo>'),array('<asphoto>','</asphoto>'),$x);
  536. $source_xml = parse_xml_string($x,false);
  537. if(strlen($source_xml->post->asphoto->objectId) && ($source_xml->post->asphoto->objectId != 0) && ($source_xml->post->asphoto->image_url)) {
  538. $body = '[url=' . notags(unxmlify($source_xml->post->asphoto->image_url)) . '][img]' . notags(unxmlify($source_xml->post->asphoto->objectId)) . '[/img][/url]' . "\n";
  539. $body = scale_diaspora_images($body,false);
  540. }
  541. elseif($source_xml->post->asphoto->image_url) {
  542. $body = '[img]' . notags(unxmlify($source_xml->post->asphoto->image_url)) . '[/img]' . "\n";
  543. $body = scale_diaspora_images($body);
  544. }
  545. elseif($source_xml->post->status_message) {
  546. $body = diaspora2bb($source_xml->post->status_message->raw_message);
  547. $body = scale_diaspora_images($body);
  548. }
  549. else {
  550. logger('diaspora_reshare: no reshare content found: ' . print_r($source_xml,true));
  551. return;
  552. }
  553. if(! $body) {
  554. logger('diaspora_reshare: empty body: source= ' . $x);
  555. return;
  556. }
  557. $person = find_diaspora_person_by_handle($orig_author);
  558. if(is_array($person) && x($person,'name') && x($person,'url'))
  559. $details = '[url=' . $person['url'] . ']' . $person['name'] . '[/url]';
  560. else
  561. $details = $orig_author;
  562. $prefix = '&#x2672; ' . $details . "\n";
  563. // allocate a guid on our system - we aren't fixing any collisions.
  564. // we're ignoring them
  565. $g = q("select * from guid where guid = '%s' limit 1",
  566. dbesc($guid)
  567. );
  568. if(! count($g)) {
  569. q("insert into guid ( guid ) values ( '%s' )",
  570. dbesc($guid)
  571. );
  572. }
  573. $created = unxmlify($xml->created_at);
  574. $private = ((unxmlify($xml->public) == 'false') ? 1 : 0);
  575. $datarray = array();
  576. $str_tags = '';
  577. $tags = get_tags($body);
  578. if(count($tags)) {
  579. foreach($tags as $tag) {
  580. if(strpos($tag,'#') === 0) {
  581. if(strpos($tag,'[url='))
  582. continue;
  583. $basetag = str_replace('_',' ',substr($tag,1));
  584. $body = str_replace($tag,'#[url=' . $a->get_baseurl() . '/search?search=' . rawurlencode($basetag) . ']' . $basetag . '[/url]',$body);
  585. if(strlen($str_tags))
  586. $str_tags .= ',';
  587. $str_tags .= '#[url=' . $a->get_baseurl() . '/search?search=' . rawurlencode($basetag) . ']' . $basetag . '[/url]';
  588. continue;
  589. }
  590. }
  591. }
  592. $datarray['uid'] = $importer['uid'];
  593. $datarray['contact-id'] = $contact['id'];
  594. $datarray['wall'] = 0;
  595. $datarray['guid'] = $guid;
  596. $datarray['uri'] = $datarray['parent-uri'] = $message_id;
  597. $datarray['created'] = $datarray['edited'] = datetime_convert('UTC','UTC',$created);
  598. $datarray['private'] = $private;
  599. $datarray['parent'] = 0;
  600. $datarray['owner-name'] = $contact['name'];
  601. $datarray['owner-link'] = $contact['url'];
  602. $datarray['owner-avatar'] = $contact['thumb'];
  603. $datarray['author-name'] = $contact['name'];
  604. $datarray['author-link'] = $contact['url'];
  605. $datarray['author-avatar'] = $contact['thumb'];
  606. $datarray['body'] = $prefix . $body;
  607. $datarray['tag'] = $str_tags;
  608. $datarray['app'] = 'Diaspora';
  609. $message_id = item_store($datarray);
  610. if($message_id) {
  611. q("update item set plink = '%s' where id = %d limit 1",
  612. dbesc($a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $message_id),
  613. intval($message_id)
  614. );
  615. }
  616. return;
  617. }
  618. function diaspora_asphoto($importer,$xml) {
  619. logger('diaspora_asphoto called');
  620. $a = get_app();
  621. $guid = notags(unxmlify($xml->guid));
  622. $diaspora_handle = notags(unxmlify($xml->diaspora_handle));
  623. $contact = diaspora_get_contact_by_handle($importer['uid'],$diaspora_handle);
  624. if(! $contact)
  625. return;
  626. if(($contact['rel'] == CONTACT_IS_FOLLOWER) || ($contact['blocked']) || ($contact['readonly'])) {
  627. logger('diaspora_asphoto: Ignoring this author.');
  628. return 202;
  629. }
  630. $message_id = $diaspora_handle . ':' . $guid;
  631. $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s' AND `guid` = '%s' LIMIT 1",
  632. intval($importer['uid']),
  633. dbesc($message_id),
  634. dbesc($guid)
  635. );
  636. if(count($r)) {
  637. logger('diaspora_asphoto: message exists: ' . $guid);
  638. return;
  639. }
  640. // allocate a guid on our system - we aren't fixing any collisions.
  641. // we're ignoring them
  642. $g = q("select * from guid where guid = '%s' limit 1",
  643. dbesc($guid)
  644. );
  645. if(! count($g)) {
  646. q("insert into guid ( guid ) values ( '%s' )",
  647. dbesc($guid)
  648. );
  649. }
  650. $created = unxmlify($xml->created_at);
  651. $private = ((unxmlify($xml->public) == 'false') ? 1 : 0);
  652. if(strlen($xml->objectId) && ($xml->objectId != 0) && ($xml->image_url)) {
  653. $body = '[url=' . notags(unxmlify($xml->image_url)) . '][img]' . notags(unxmlify($xml->objectId)) . '[/img][/url]' . "\n";
  654. $body = scale_diaspora_images($body,false);
  655. }
  656. elseif($xml->image_url) {
  657. $body = '[img]' . notags(unxmlify($xml->image_url)) . '[/img]' . "\n";
  658. $body = scale_diaspora_images($body);
  659. }
  660. else {
  661. logger('diaspora_asphoto: no photo url found.');
  662. return;
  663. }
  664. $datarray = array();
  665. $datarray['uid'] = $importer['uid'];
  666. $datarray['contact-id'] = $contact['id'];
  667. $datarray['wall'] = 0;
  668. $datarray['guid'] = $guid;
  669. $datarray['uri'] = $datarray['parent-uri'] = $message_id;
  670. $datarray['created'] = $datarray['edited'] = datetime_convert('UTC','UTC',$created);
  671. $datarray['private'] = $private;
  672. $datarray['parent'] = 0;
  673. $datarray['owner-name'] = $contact['name'];
  674. $datarray['owner-link'] = $contact['url'];
  675. $datarray['owner-avatar'] = $contact['thumb'];
  676. $datarray['author-name'] = $contact['name'];
  677. $datarray['author-link'] = $contact['url'];
  678. $datarray['author-avatar'] = $contact['thumb'];
  679. $datarray['body'] = $body;
  680. $datarray['app'] = 'Diaspora/Cubbi.es';
  681. $message_id = item_store($datarray);
  682. if($message_id) {
  683. q("update item set plink = '%s' where id = %d limit 1",
  684. dbesc($a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $message_id),
  685. intval($message_id)
  686. );
  687. }
  688. return;
  689. }
  690. function diaspora_comment($importer,$xml,$msg) {
  691. $a = get_app();
  692. $guid = notags(unxmlify($xml->guid));
  693. $parent_guid = notags(unxmlify($xml->parent_guid));
  694. $diaspora_handle = notags(unxmlify($xml->diaspora_handle));
  695. $target_type = notags(unxmlify($xml->target_type));
  696. $text = unxmlify($xml->text);
  697. $author_signature = notags(unxmlify($xml->author_signature));
  698. $parent_author_signature = (($xml->parent_author_signature) ? notags(unxmlify($xml->parent_author_signature)) : '');
  699. $contact = diaspora_get_contact_by_handle($importer['uid'],$msg['author']);
  700. if(! $contact) {
  701. logger('diaspora_comment: cannot find contact: ' . $msg['author']);
  702. return;
  703. }
  704. if(($contact['rel'] == CONTACT_IS_FOLLOWER) || ($contact['blocked']) || ($contact['readonly'])) {
  705. logger('diaspora_comment: Ignoring this author.');
  706. return 202;
  707. }
  708. $r = q("SELECT * FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1",
  709. intval($importer['uid']),
  710. dbesc($guid)
  711. );
  712. if(count($r)) {
  713. logger('diaspora_comment: our comment just got relayed back to us (or there was a guid collision) : ' . $guid);
  714. return;
  715. }
  716. $r = q("SELECT * FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1",
  717. intval($importer['uid']),
  718. dbesc($parent_guid)
  719. );
  720. if(! count($r)) {
  721. logger('diaspora_comment: parent item not found: parent: ' . $parent_guid . ' item: ' . $guid);
  722. return;
  723. }
  724. $parent_item = $r[0];
  725. $author_signed_data = $guid . ';' . $parent_guid . ';' . $text . ';' . $diaspora_handle;
  726. $author_signature = base64_decode($author_signature);
  727. if(strcasecmp($diaspora_handle,$msg['author']) == 0) {
  728. $person = $contact;
  729. $key = $msg['key'];
  730. }
  731. else {
  732. $person = find_diaspora_person_by_handle($diaspora_handle);
  733. if(is_array($person) && x($person,'pubkey'))
  734. $key = $person['pubkey'];
  735. else {
  736. logger('diaspora_comment: unable to find author details');
  737. return;
  738. }
  739. }
  740. if(! rsa_verify($author_signed_data,$author_signature,$key,'sha256')) {
  741. logger('diaspora_comment: verification failed.');
  742. return;
  743. }
  744. if($parent_author_signature) {
  745. $owner_signed_data = $guid . ';' . $parent_guid . ';' . $text . ';' . $diaspora_handle;
  746. $parent_author_signature = base64_decode($parent_author_signature);
  747. $key = $msg['key'];
  748. if(! rsa_verify($owner_signed_data,$parent_author_signature,$key,'sha256')) {
  749. logger('diaspora_comment: owner verification failed.');
  750. return;
  751. }
  752. }
  753. // Phew! Everything checks out. Now create an item.
  754. $body = diaspora2bb($text);
  755. $message_id = $diaspora_handle . ':' . $guid;
  756. $datarray = array();
  757. $str_tags = '';
  758. $tags = get_tags($body);
  759. if(count($tags)) {
  760. foreach($tags as $tag) {
  761. if(strpos($tag,'#') === 0) {
  762. if(strpos($tag,'[url='))
  763. continue;
  764. $basetag = str_replace('_',' ',substr($tag,1));
  765. $body = str_replace($tag,'#[url=' . $a->get_baseurl() . '/search?search=' . rawurlencode($basetag) . ']' . $basetag . '[/url]',$body);
  766. if(strlen($str_tags))
  767. $str_tags .= ',';
  768. $str_tags .= '#[url=' . $a->get_baseurl() . '/search?search=' . rawurlencode($basetag) . ']' . $basetag . '[/url]';
  769. continue;
  770. }
  771. }
  772. }
  773. $datarray['uid'] = $importer['uid'];
  774. $datarray['contact-id'] = $contact['id'];
  775. $datarray['wall'] = $parent_item['wall'];
  776. $datarray['gravity'] = GRAVITY_COMMENT;
  777. $datarray['guid'] = $guid;
  778. $datarray['uri'] = $message_id;
  779. $datarray['parent-uri'] = $parent_item['uri'];
  780. // No timestamps for comments? OK, we'll the use current time.
  781. $datarray['created'] = $datarray['edited'] = datetime_convert();
  782. $datarray['private'] = $parent_item['private'];
  783. $datarray['owner-name'] = $parent_item['owner-name'];
  784. $datarray['owner-link'] = $parent_item['owner-link'];
  785. $datarray['owner-avatar'] = $parent_item['owner-avatar'];
  786. $datarray['author-name'] = $person['name'];
  787. $datarray['author-link'] = $person['url'];
  788. $datarray['author-avatar'] = ((x($person,'thumb')) ? $person['thumb'] : $person['photo']);
  789. $datarray['body'] = $body;
  790. $datarray['tag'] = $str_tags;
  791. // We can't be certain what the original app is if the message is relayed.
  792. if(($parent_item['origin']) && (! $parent_author_signature))
  793. $datarray['app'] = 'Diaspora';
  794. $message_id = item_store($datarray);
  795. if($message_id) {
  796. q("update item set plink = '%s' where id = %d limit 1",
  797. dbesc($a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $message_id),
  798. intval($message_id)
  799. );
  800. }
  801. if(($parent_item['origin']) && (! $parent_author_signature)) {
  802. q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
  803. intval($message_id),
  804. dbesc($author_signed_data),
  805. dbesc(base64_encode($author_signature)),
  806. dbesc($diaspora_handle)
  807. );
  808. // if the message isn't already being relayed, notify others
  809. // the existence of parent_author_signature means the parent_author or owner
  810. // is already relaying.
  811. proc_run('php','include/notifier.php','comment',$message_id);
  812. }
  813. return;
  814. }
  815. function diaspora_conversation($importer,$xml,$msg) {
  816. $a = get_app();
  817. $guid = notags(unxmlify($xml->guid));
  818. $subject = notags(unxmlify($xml->subject));
  819. $diaspora_handle = notags(unxmlify($xml->diaspora_handle));
  820. $participant_handles = notags(unxmlify($xml->participant_handles));
  821. $created_at = datetime_convert('UTC','UTC',notags(unxmlify($xml->created_at)));
  822. $parent_uri = $diaspora_handle . ':' . $guid;
  823. $messages = $xml->message;
  824. if(! count($messages)) {
  825. logger('diaspora_conversation: empty conversation');
  826. return;
  827. }
  828. $contact = diaspora_get_contact_by_handle($importer['uid'],$msg['author']);
  829. if(! $contact) {
  830. logger('diaspora_conversation: cannot find contact: ' . $msg['author']);
  831. return;
  832. }
  833. if(($contact['rel'] == CONTACT_IS_FOLLOWER) || ($contact['blocked']) || ($contact['readonly'])) {
  834. logger('diaspora_conversation: Ignoring this author.');
  835. return 202;
  836. }
  837. $conversation = null;
  838. $c = q("select * from conv where uid = %d and guid = '%s' limit 1",
  839. intval($importer['uid']),
  840. dbesc($guid)
  841. );
  842. if(count($c))
  843. $conversation = $c[0];
  844. else {
  845. $r = q("insert into conv (uid,guid,creator,created,updated,subject,recips) values(%d, '%s', '%s', '%s', '%s', '%s', '%s') ",
  846. intval($importer['uid']),
  847. dbesc($guid),
  848. dbesc($diaspora_handle),
  849. dbesc(datetime_convert('UTC','UTC',$created_at)),
  850. dbesc(datetime_convert()),
  851. dbesc($subject),
  852. dbesc($participant_handles)
  853. );
  854. if($r)
  855. $c = q("select * from conv where uid = %d and guid = '%s' limit 1",
  856. intval($importer['uid']),
  857. dbesc($guid)
  858. );
  859. if(count($c))
  860. $conversation = $c[0];
  861. }
  862. if(! $conversation) {
  863. logger('diaspora_conversation: unable to create conversation.');
  864. return;
  865. }
  866. foreach($messages as $mesg) {
  867. $reply = 0;
  868. $msg_guid = notags(unxmlify($mesg->guid));
  869. $msg_parent_guid = notags(unxmlify($mesg->parent_guid));
  870. $msg_parent_author_signature = notags(unxmlify($mesg->parent_author_signature));
  871. $msg_author_signature = notags(unxmlify($mesg->author_signature));
  872. $msg_text = unxmlify($mesg->text);
  873. $msg_created_at = datetime_convert('UTC','UTC',notags(unxmlify($mesg->created_at)));
  874. $msg_diaspora_handle = notags(unxmlify($mesg->diaspora_handle));
  875. $msg_conversation_guid = notags(unxmlify($mesg->conversation_guid));
  876. if($msg_conversation_guid != $guid) {
  877. logger('diaspora_conversation: message conversation guid does not belong to the current conversation. ' . $xml);
  878. continue;
  879. }
  880. $body = diaspora2bb($msg_text);
  881. $message_id = $msg_diaspora_handle . ':' . $msg_guid;
  882. $author_signed_data = $msg_guid . ';' . $msg_parent_guid . ';' . $msg_text . ';' . unxmlify($mesg->created_at) . ';' . $msg_diaspora_handle . ';' . $msg_conversation_guid;
  883. $author_signature = base64_decode($msg_author_signature);
  884. if(strcasecmp($msg_diaspora_handle,$msg['author']) == 0) {
  885. $person = $contact;
  886. $key = $msg['key'];
  887. }
  888. else {
  889. $person = find_diaspora_person_by_handle($msg_diaspora_handle);
  890. if(is_array($person) && x($person,'pubkey'))
  891. $key = $person['pubkey'];
  892. else {
  893. logger('diaspora_conversation: unable to find author details');
  894. continue;
  895. }
  896. }
  897. if(! rsa_verify($author_signed_data,$author_signature,$key,'sha256')) {
  898. logger('diaspora_conversation: verification failed.');
  899. continue;
  900. }
  901. if($msg_parent_author_signature) {
  902. $owner_signed_data = $msg_guid . ';' . $msg_parent_guid . ';' . $msg_text . ';' . unxmlify($mesg->created_at) . ';' . $msg_diaspora_handle . ';' . $msg_conversation_guid;
  903. $parent_author_signature = base64_decode($msg_parent_author_signature);
  904. $key = $msg['key'];
  905. if(! rsa_verify($owner_signed_data,$parent_author_signature,$key,'sha256')) {
  906. logger('diaspora_conversation: owner verification failed.');
  907. continue;
  908. }
  909. }
  910. $r = q("select id from mail where `uri` = '%s' limit 1",
  911. dbesc($message_id)
  912. );
  913. if(count($r)) {
  914. logger('diaspora_conversation: duplicate message already delivered.', LOGGER_DEBUG);
  915. continue;
  916. }
  917. q("insert into mail ( `uid`, `guid`, `convid`, `from-name`,`from-photo`,`from-url`,`contact-id`,`title`,`body`,`seen`,`reply`,`uri`,`parent-uri`,`created`) values ( %d, '%s', %d, '%s', '%s', '%s', %d, '%s', '%s', %d, %d, '%s','%s','%s')",
  918. intval($importer['uid']),
  919. dbesc($msg_guid),
  920. intval($conversation['id']),
  921. dbesc($person['name']),
  922. dbesc($person['photo']),
  923. dbesc($person['url']),
  924. intval($contact['id']),
  925. dbesc($subject),
  926. dbesc($body),
  927. 0,
  928. 0,
  929. dbesc($message_id),
  930. dbesc($parent_uri),
  931. dbesc($msg_created_at)
  932. );
  933. q("update conv set updated = '%s' where id = %d limit 1",
  934. dbesc(datetime_convert()),
  935. intval($conversation['id'])
  936. );
  937. }
  938. return;
  939. }
  940. function diaspora_message($importer,$xml,$msg) {
  941. $a = get_app();
  942. $msg_guid = notags(unxmlify($xml->guid));
  943. $msg_parent_guid = notags(unxmlify($xml->parent_guid));
  944. $msg_parent_author_signature = notags(unxmlify($xml->parent_author_signature));
  945. $msg_author_signature = notags(unxmlify($xml->author_signature));
  946. $msg_text = unxmlify($xml->text);
  947. $msg_created_at = datetime_convert('UTC','UTC',notags(unxmlify($xml->created_at)));
  948. $msg_diaspora_handle = notags(unxmlify($xml->diaspora_handle));
  949. $msg_conversation_guid = notags(unxmlify($xml->conversation_guid));
  950. $parent_uri = $diaspora_handle . ':' . $msg_parent_guid;
  951. $contact = diaspora_get_contact_by_handle($importer['uid'],$msg_diaspora_handle);
  952. if(! $contact) {
  953. logger('diaspora_message: cannot find contact: ' . $msg_diaspora_handle);
  954. return;
  955. }
  956. if(($contact['rel'] == CONTACT_IS_FOLLOWER) || ($contact['blocked']) || ($contact['readonly'])) {
  957. logger('diaspora_message: Ignoring this author.');
  958. return 202;
  959. }
  960. $conversation = null;
  961. $c = q("select * from conv where uid = %d and guid = '%s' limit 1",
  962. intval($importer['uid']),
  963. dbesc($msg_conversation_guid)
  964. );
  965. if(count($c))
  966. $conversation = $c[0];
  967. else {
  968. logger('diaspora_message: conversation not available.');
  969. return;
  970. }
  971. $reply = 0;
  972. $body = diaspora2bb($msg_text);
  973. $message_id = $msg_diaspora_handle . ':' . $msg_guid;
  974. $author_signed_data = $msg_guid . ';' . $msg_parent_guid . ';' . $msg_text . ';' . unxmlify($xml->created_at) . ';' . $msg_diaspora_handle . ';' . $msg_conversation_guid;
  975. $author_signature = base64_decode($msg_author_signature);
  976. $person = find_diaspora_person_by_handle($msg_diaspora_handle);
  977. if(is_array($person) && x($person,'pubkey'))
  978. $key = $person['pubkey'];
  979. else {
  980. logger('diaspora_message: unable to find author details');
  981. return;
  982. }
  983. if(! rsa_verify($author_signed_data,$author_signature,$key,'sha256')) {
  984. logger('diaspora_message: verification failed.');
  985. return;
  986. }
  987. $r = q("select id from mail where `uri` = '%s' and uid = %d limit 1",
  988. dbesc($message_id),
  989. intval($importer['uid'])
  990. );
  991. if(count($r)) {
  992. logger('diaspora_message: duplicate message already delivered.', LOGGER_DEBUG);
  993. return;
  994. }
  995. q("insert into mail ( `uid`, `guid`, `convid`, `from-name`,`from-photo`,`from-url`,`contact-id`,`title`,`body`,`seen`,`reply`,`uri`,`parent-uri`,`created`) values ( %d, '%s', %d, '%s', '%s', '%s', %d, '%s', '%s', %d, %d, '%s','%s','%s')",
  996. intval($importer['uid']),
  997. dbesc($msg_guid),
  998. intval($conversation['id']),
  999. dbesc($person['name']),
  1000. dbesc($person['photo']),
  1001. dbesc($person['url']),
  1002. intval($contact['id']),
  1003. dbesc($conversation['subject']),
  1004. dbesc($body),
  1005. 0,
  1006. 1,
  1007. dbesc($message_id),
  1008. dbesc($parent_uri),
  1009. dbesc($msg_created_at)
  1010. );
  1011. q("update conv set updated = '%s' where id = %d limit 1",
  1012. dbesc(datetime_convert()),
  1013. intval($conversation['id'])
  1014. );
  1015. return;
  1016. }
  1017. function diaspora_photo($importer,$xml,$msg) {
  1018. $a = get_app();
  1019. $remote_photo_path = notags(unxmlify($xml->remote_photo_path));
  1020. $remote_photo_name = notags(unxmlify($xml->remote_photo_name));
  1021. $status_message_guid = notags(unxmlify($xml->status_message_guid));
  1022. $guid = notags(unxmlify($xml->guid));
  1023. $diaspora_handle = notags(unxmlify($xml->diaspora_handle));
  1024. $public = notags(unxmlify($xml->public));
  1025. $created_at = notags(unxmlify($xml_created_at));
  1026. $contact = diaspora_get_contact_by_handle($importer['uid'],$msg['author']);
  1027. if(! $contact)
  1028. return;
  1029. if(($contact['rel'] == CONTACT_IS_FOLLOWER) || ($contact['blocked']) || ($contact['readonly'])) {
  1030. logger('diaspora_photo: Ignoring this author.');
  1031. return 202;
  1032. }
  1033. $r = q("SELECT * FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1",
  1034. intval($importer['uid']),
  1035. dbesc($status_message_guid)
  1036. );
  1037. if(! count($r)) {
  1038. logger('diaspora_photo: parent item not found: parent: ' . $parent_guid . ' item: ' . $guid);
  1039. return;
  1040. }
  1041. $parent_item = $r[0];
  1042. $link_text = '[img]' . $remote_photo_path . $remote_photo_name . '[/img]' . "\n";
  1043. $link_text = scale_diaspora_images($link_text);
  1044. if(strpos($parent_item['body'],$link_text) === false) {
  1045. $r = q("update item set `body` = '%s', `visible` = 1 where `id` = %d and `uid` = %d limit 1",
  1046. dbesc($link_text . $parent_item['body']),
  1047. intval($parent_item['id']),
  1048. intval($parent_item['uid'])
  1049. );
  1050. }
  1051. return;
  1052. }
  1053. function diaspora_like($importer,$xml,$msg) {
  1054. $a = get_app();
  1055. $guid = notags(unxmlify($xml->guid));
  1056. $parent_guid = notags(unxmlify($xml->parent_guid));
  1057. $diaspora_handle = notags(unxmlify($xml->diaspora_handle));
  1058. $target_type = notags(unxmlify($xml->target_type));
  1059. $positive = notags(unxmlify($xml->positive));
  1060. $author_signature = notags(unxmlify($xml->author_signature));
  1061. $parent_author_signature = (($xml->parent_author_signature) ? notags(unxmlify($xml->parent_author_signature)) : '');
  1062. // likes on comments not supported here and likes on photos not supported by Diaspora
  1063. if($target_type !== 'Post')
  1064. return;
  1065. $contact = diaspora_get_contact_by_handle($importer['uid'],$msg['author']);
  1066. if(! $contact) {
  1067. logger('diaspora_like: cannot find contact: ' . $msg['author']);
  1068. return;
  1069. }
  1070. if(($contact['rel'] == CONTACT_IS_FOLLOWER) || ($contact['blocked']) || ($contact['readonly'])) {
  1071. logger('diaspora_like: Ignoring this author.');
  1072. return 202;
  1073. }
  1074. $r = q("SELECT * FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1",
  1075. intval($importer['uid']),
  1076. dbesc($parent_guid)
  1077. );
  1078. if(! count($r)) {
  1079. logger('diaspora_like: parent item not found: ' . $guid);
  1080. return;
  1081. }
  1082. $parent_item = $r[0];
  1083. $r = q("SELECT * FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1",
  1084. intval($importer['uid']),
  1085. dbesc($guid)
  1086. );
  1087. if(count($r)) {
  1088. if($positive === 'true') {
  1089. logger('diaspora_like: duplicate like: ' . $guid);
  1090. return;
  1091. }
  1092. if($positive === 'false') {
  1093. q("UPDATE `item` SET `deleted` = 1 WHERE `id` = %d AND `uid` = %d LIMIT 1",
  1094. intval($r[0]['id']),
  1095. intval($importer['uid'])
  1096. );
  1097. // FIXME
  1098. // send notification via proc_run()
  1099. return;
  1100. }
  1101. }
  1102. if($positive === 'false') {
  1103. logger('diaspora_like: unlike received with no corresponding like');
  1104. return;
  1105. }
  1106. $author_signed_data = $guid . ';' . $target_type . ';' . $parent_guid . ';' . $positive . ';' . $diaspora_handle;
  1107. $author_signature = base64_decode($author_signature);
  1108. if(strcasecmp($diaspora_handle,$msg['author']) == 0) {
  1109. $person = $contact;
  1110. $key = $msg['key'];
  1111. }
  1112. else {
  1113. $person = find_diaspora_person_by_handle($diaspora_handle);
  1114. if(is_array($person) && x($person,'pubkey'))
  1115. $key = $person['pubkey'];
  1116. else {
  1117. logger('diaspora_like: unable to find author details');
  1118. return;
  1119. }
  1120. }
  1121. if(! rsa_verify($author_signed_data,$author_signature,$key,'sha256')) {
  1122. logger('diaspora_like: verification failed.');
  1123. return;
  1124. }
  1125. if($parent_author_signature) {
  1126. $owner_signed_data = $guid . ';' . $target_type . ';' . $parent_guid . ';' . $positive . ';' . $diaspora_handle;
  1127. $parent_author_signature = base64_decode($parent_author_signature);
  1128. $key = $msg['key'];
  1129. if(! rsa_verify($owner_signed_data,$parent_author_signature,$key,'sha256')) {
  1130. logger('diaspora_like: owner verification failed.');
  1131. return;
  1132. }
  1133. }
  1134. // Phew! Everything checks out. Now create an item.
  1135. $uri = $diaspora_handle . ':' . $guid;
  1136. $activity = ACTIVITY_LIKE;
  1137. $post_type = (($parent_item['resource-id']) ? t('photo') : t('status'));
  1138. $objtype = (($parent_item['resource-id']) ? ACTIVITY_OBJ_PHOTO : ACTIVITY_OBJ_NOTE );
  1139. $link = xmlify('<link rel="alternate" type="text/html" href="' . $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $parent_item['id'] . '" />' . "\n") ;
  1140. $body = $parent_item['body'];
  1141. $obj = <<< EOT
  1142. <object>
  1143. <type>$objtype</type>
  1144. <local>1</local>
  1145. <id>{$parent_item['uri']}</id>
  1146. <link>$link</link>
  1147. <title></title>
  1148. <content>$body</content>
  1149. </object>
  1150. EOT;
  1151. $bodyverb = t('%1$s likes %2$s\'s %3$s');
  1152. $arr = array();
  1153. $arr['uri'] = $uri;
  1154. $arr['uid'] = $importer['uid'];
  1155. $arr['guid'] = $guid;
  1156. $arr['contact-id'] = $contact['id'];
  1157. $arr['type'] = 'activity';
  1158. $arr['wall'] = $parent_item['wall'];
  1159. $arr['gravity'] = GRAVITY_LIKE;
  1160. $arr['parent'] = $parent_item['id'];
  1161. $arr['parent-uri'] = $parent_item['uri'];
  1162. $arr['owner-name'] = $parent_item['name'];
  1163. $arr['owner-link'] = $parent_item['url'];
  1164. $arr['owner-avatar'] = $parent_item['thumb'];
  1165. $arr['author-name'] = $person['name'];
  1166. $arr['author-link'] = $person['url'];
  1167. $arr['author-avatar'] = ((x($person,'thumb')) ? $person['thumb'] : $person['photo']);
  1168. $ulink = '[url=' . $contact['url'] . ']' . $contact['name'] . '[/url]';
  1169. $alink = '[url=' . $parent_item['author-link'] . ']' . $parent_item['author-name'] . '[/url]';
  1170. $plink = '[url=' . $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $parent_item['id'] . ']' . $post_type . '[/url]';
  1171. $arr['body'] = sprintf( $bodyverb, $ulink, $alink, $plink );
  1172. $arr['app'] = 'Diaspora';
  1173. $arr['private'] = $parent_item['private'];
  1174. $arr['verb'] = $activity;
  1175. $arr['object-type'] = $objtype;
  1176. $arr['object'] = $obj;
  1177. $arr['visible'] = 1;
  1178. $arr['unseen'] = 1;
  1179. $arr['last-child'] = 0;
  1180. $message_id = item_store($arr);
  1181. if($message_id) {
  1182. q("update item set plink = '%s' where id = %d limit 1",
  1183. dbesc($a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $message_id),
  1184. intval($message_id)
  1185. );
  1186. }
  1187. if(! $parent_author_signature) {
  1188. q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
  1189. intval($message_id),
  1190. dbesc($author_signed_data),
  1191. dbesc(base64_encode($author_signature)),
  1192. dbesc($diaspora_handle)
  1193. );
  1194. }
  1195. // if the message isn't already being relayed, notify others
  1196. // the existence of parent_author_signature means the parent_author or owner
  1197. // is already relaying. The parent_item['origin'] indicates the message was created on our system
  1198. if(($parent_item['origin']) && (! $parent_author_signature))
  1199. proc_run('php','include/notifier.php','comment',$message_id);
  1200. return;
  1201. }
  1202. function diaspora_retraction($importer,$xml) {
  1203. $guid = notags(unxmlify($xml->guid));
  1204. $diaspora_handle = notags(unxmlify($xml->diaspora_handle));
  1205. $type = notags(unxmlify($xml->type));
  1206. $contact = diaspora_get_contact_by_handle($importer['uid'],$diaspora_handle);
  1207. if(! $contact)
  1208. return;
  1209. if($type === 'Person') {
  1210. require_once('include/Contact.php');
  1211. contact_remove($contact['id']);
  1212. }
  1213. elseif($type === 'Post') {
  1214. $r = q("select * from item where guid = '%s' and uid = %d limit 1",
  1215. dbesc('guid'),
  1216. intval($importer['uid'])
  1217. );
  1218. if(count($r)) {
  1219. if(link_compare($r[0]['author-link'],$contact['url'])) {
  1220. q("update item set `deleted` = 1, `changed` = '%s' where `id` = %d limit 1",
  1221. dbesc(datetime_convert()),
  1222. intval($r[0]['id'])
  1223. );
  1224. }
  1225. }
  1226. }
  1227. return 202;
  1228. // NOTREACHED
  1229. }
  1230. function diaspora_signed_retraction($importer,$xml) {
  1231. $guid = notags(unxmlify($xml->target_guid));
  1232. $diaspora_handle = notags(unxmlify($xml->sender_handle));
  1233. $type = notags(unxmlify($xml->target_type));
  1234. $sig = notags(unxmlify($xml->target_author_signature));
  1235. $contact = diaspora_get_contact_by_handle($importer['uid'],$diaspora_handle);
  1236. if(! $contact)
  1237. return;
  1238. // this may not yet work for comments. Need to see how the relaying works
  1239. // and figure out who signs it.
  1240. $signed_data = $guid . ';' . $type ;
  1241. $sig = base64_decode($sig);
  1242. $key = $msg['key'];
  1243. if(! rsa_verify($signed_data,$sig,$key,'sha256')) {
  1244. logger('diaspora_signed_retraction: owner verification failed.' . print_r($msg,true));
  1245. return;
  1246. }
  1247. if($type === 'StatusMessage') {
  1248. $r = q("select * from item where guid = '%s' and uid = %d limit 1",
  1249. dbesc('guid'),
  1250. intval($importer['uid'])
  1251. );
  1252. if(count($r)) {
  1253. if(link_compare($r[0]['author-link'],$contact['url'])) {
  1254. q("update item set `deleted` = 1, `changed` = '%s' where `id` = %d limit 1",
  1255. dbesc(datetime_convert()),
  1256. intval($r[0]['id'])
  1257. );
  1258. }
  1259. }
  1260. }
  1261. return 202;
  1262. // NOTREACHED
  1263. }
  1264. function diaspora_profile($importer,$xml) {
  1265. $a = get_app();
  1266. $diaspora_handle = notags(unxmlify($xml->diaspora_handle));
  1267. $contact = diaspora_get_contact_by_handle($importer['uid'],$diaspora_handle);
  1268. if(! $contact)
  1269. return;
  1270. if($contact['blocked']) {
  1271. logger('diaspora_post: Ignoring this author.');
  1272. return 202;
  1273. }
  1274. $name = unxmlify($xml->first_name) . ((strlen($xml->last_name)) ? ' ' . unxmlify($xml->last_name) : '');
  1275. $image_url = unxmlify($xml->image_url);
  1276. $birthday = unxmlify($xml->birthday);
  1277. $r = q("SELECT DISTINCT ( `resource-id` ) FROM `photo` WHERE `uid` = %d AND `contact-id` = %d AND `album` = 'Contact Photos' ",
  1278. intval($importer['uid']),
  1279. intval($contact['id'])
  1280. );
  1281. $oldphotos = ((count($r)) ? $r : null);
  1282. require_once('include/Photo.php');
  1283. $images = import_profile_photo($image_url,$importer['uid'],$contact['id']);
  1284. // Generic birthday. We don't know the timezone. The year is irrelevant.
  1285. $birthday = str_replace('1000','1901',$birthday);
  1286. $birthday = datetime_convert('UTC','UTC',$birthday,'Y-m-d');
  1287. $r = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s', `avatar-date` = '%s' , `bd` = '%s' WHERE `id` = %d AND `uid` = %d LIMIT 1",
  1288. dbesc($name),
  1289. dbesc(datetime_convert()),
  1290. dbesc($images[0]),
  1291. dbesc($images[1]),
  1292. dbesc($images[2]),
  1293. dbesc(datetime_convert()),
  1294. dbesc($birthday),
  1295. intval($contact['id']),
  1296. intval($importer['uid'])
  1297. );
  1298. if($r) {
  1299. if($oldphotos) {
  1300. foreach($oldphotos as $ph) {
  1301. q("DELETE FROM `photo` WHERE `uid` = %d AND `contact-id` = %d AND `album` = 'Contact Photos' AND `resource-id` = '%s' ",
  1302. intval($importer['uid']),
  1303. intval($contact['id']),
  1304. dbesc($ph['resource-id'])
  1305. );
  1306. }
  1307. }
  1308. }
  1309. return;
  1310. }
  1311. function diaspora_share($me,$contact) {
  1312. $a = get_app();
  1313. $myaddr = $me['nickname'] . '@' . substr($a->get_baseurl(), strpos($a->get_baseurl(),'://') + 3);
  1314. $theiraddr = $contact['addr'];
  1315. $tpl = get_markup_template('diaspora_share.tpl');
  1316. $msg = replace_macros($tpl, array(
  1317. '$sender' => $myaddr,
  1318. '$recipient' => $theiraddr
  1319. ));
  1320. $slap = 'xml=' . urlencode(urlencode(diaspora_msg_build($msg,$me,$contact,$me['prvkey'],$contact['pubkey'])));
  1321. return(diaspora_transmit($owner,$contact,$slap, false));
  1322. }
  1323. function diaspora_unshare($me,$contact) {
  1324. $a = get_app();
  1325. $myaddr = $me['nickname'] . '@' . substr($a->get_baseurl(), strpos($a->get_baseurl(),'://') + 3);
  1326. $tpl = get_markup_template('diaspora_retract.tpl');
  1327. $msg = replace_macros($tpl, array(
  1328. '$guid' => $me['guid'],
  1329. '$type' => 'Person',
  1330. '$handle' => $myaddr
  1331. ));
  1332. $slap = 'xml=' . urlencode(urlencode(diaspora_msg_build($msg,$me,$contact,$me['prvkey'],$contact['pubkey'])));
  1333. return(diaspora_transmit($owner,$contact,$slap, false));
  1334. }
  1335. function diaspora_send_status($item,$owner,$contact,$public_batch = false) {
  1336. $a = get_app();
  1337. $myaddr = $owner['nickname'] . '@' . substr($a->get_baseurl(), strpos($a->get_baseurl(),'://') + 3);
  1338. $theiraddr = $contact['addr'];
  1339. $images = array();
  1340. $body = $item['body'];
  1341. /*
  1342. // We're trying to match Diaspora's split message/photo protocol but
  1343. // all the photos are displayed on D* as links and not img's - even
  1344. // though we're sending pretty much precisely what they send us when
  1345. // doing the same operation.
  1346. // Commented out for now, we'll use bb2diaspora to convert photos to markdown
  1347. // which seems to get through intact.
  1348. $cnt = preg_match_all('|\[img\](.*?)\[\/img\]|',$body,$matches,PREG_SET_ORDER);
  1349. if($cnt) {
  1350. foreach($matches as $mtch) {
  1351. $detail = array();
  1352. $detail['str'] = $mtch[0];
  1353. $detail['path'] = dirname($mtch[1]) . '/';
  1354. $detail['file'] = basename($mtch[1]);
  1355. $detail['guid'] = $item['guid'];
  1356. $detail['handle'] = $myaddr;
  1357. $images[] = $detail;
  1358. $body = str_replace($detail['str'],$mtch[1],$body);
  1359. }
  1360. }
  1361. */
  1362. $body = xmlify(html_entity_decode(bb2diaspora($body)));
  1363. if($item['attach']) {
  1364. $cnt = preg_match_all('/href=\"(.*?)\"(.*?)title=\"(.*?)\"/ism',$item['attach'],$matches,PREG_SET_ORDER);
  1365. if(cnt) {
  1366. $body .= "\n" . t('Attachments:') . "\n";
  1367. foreach($matches as $mtch) {
  1368. $body .= '[' . $mtch[3] . '](' . $mtch[1] . ')' . "\n";
  1369. }
  1370. }
  1371. }
  1372. $public = (($item['private']) ? 'false' : 'true');
  1373. require_once('include/datetime.php');
  1374. $created = datetime_convert('UTC','UTC',$item['created'],'Y-m-d H:i:s \U\T\C');
  1375. $tpl = get_markup_template('diaspora_post.tpl');
  1376. $msg = replace_macros($tpl, array(
  1377. '$body' => $body,
  1378. '$guid' => $item['guid'],
  1379. '$handle' => xmlify($myaddr),
  1380. '$public' => $public,
  1381. '$created' => $created
  1382. ));
  1383. logger('diaspora_send_status: ' . $owner['username'] . ' -> ' . $contact['name'] . ' base message: ' . $msg, LOGGER_DATA);
  1384. $slap = 'xml=' . urlencode(urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch)));
  1385. $return_code = diaspora_transmit($owner,$contact,$slap,$public_batch);
  1386. if(count($images)) {
  1387. diaspora_send_images($item,$owner,$contact,$images,$public_batch);
  1388. }
  1389. return $return_code;
  1390. }
  1391. function diaspora_send_images($item,$owner,$contact,$images,$public_batch = false) {
  1392. $a = get_app();
  1393. if(! count($images))
  1394. return;
  1395. $mysite = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://') + 3) . '/photo';
  1396. $tpl = get_markup_template('diaspora_photo.tpl');
  1397. foreach($images as $image) {
  1398. if(! stristr($image['path'],$mysite))
  1399. continue;
  1400. $resource = str_replace('.jpg','',$image['file']);
  1401. $resource = substr($resource,0,strpos($resource,'-'));
  1402. $r = q("select * from photo where `resource-id` = '%s' and `uid` = %d limit 1",
  1403. dbesc($resource),
  1404. intval($owner['uid'])
  1405. );
  1406. if(! count($r))
  1407. continue;
  1408. $public = (($r[0]['allow_cid'] || $r[0]['allow_gid'] || $r[0]['deny_cid'] || $r[0]['deny_gid']) ? 'false' : 'true' );
  1409. $msg = replace_macros($tpl,array(
  1410. '$path' => xmlify($image['path']),
  1411. '$filename' => xmlify($image['file']),
  1412. '$msg_guid' => xmlify($image['guid']),
  1413. '$guid' => xmlify($r[0]['guid']),
  1414. '$handle' => xmlify($image['handle']),
  1415. '$public' => xmlify($public),
  1416. '$created_at' => xmlify(datetime_convert('UTC','UTC',$r[0]['created'],'Y-m-d H:i:s \U\T\C'))
  1417. ));
  1418. logger('diaspora_send_photo: base message: ' . $msg, LOGGER_DATA);
  1419. $slap = 'xml=' . urlencode(urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch)));
  1420. diaspora_transmit($owner,$contact,$slap,$public_batch);
  1421. }
  1422. }
  1423. function diaspora_send_followup($item,$owner,$contact,$public_batch = false) {
  1424. $a = get_app();
  1425. $myaddr = $owner['nickname'] . '@' . substr($a->get_baseurl(), strpos($a->get_baseurl(),'://') + 3);
  1426. $theiraddr = $contact['addr'];
  1427. $p = q("select guid from item where parent = %d limit 1",
  1428. $item['parent']
  1429. );
  1430. if(count($p))
  1431. $parent_guid = $p[0]['guid'];
  1432. else
  1433. return;
  1434. if($item['verb'] === ACTIVITY_LIKE) {
  1435. $tpl = get_markup_template('diaspora_like.tpl');
  1436. $like = true;
  1437. $target_type = 'Post';
  1438. $positive = (($item['deleted']) ? 'false' : 'true');
  1439. }
  1440. else {
  1441. $tpl = get_markup_template('diaspora_comment.tpl');
  1442. $like = false;
  1443. }
  1444. $text = html_entity_decode(bb2diaspora($item['body']));
  1445. // sign it
  1446. if($like)
  1447. $signed_text = $item['guid'] . ';' . $target_type . ';' . $parent_guid . ';' . $positive . ';' . $myaddr;
  1448. else
  1449. $signed_text = $item['guid'] . ';' . $parent_guid . ';' . $text . ';' . $myaddr;
  1450. $authorsig = base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256'));
  1451. $msg = replace_macros($tpl,array(
  1452. '$guid' => xmlify($item['guid']),
  1453. '$parent_guid' => xmlify($parent_guid),
  1454. '$target_type' =>xmlify($target_type),
  1455. '$authorsig' => xmlify($authorsig),
  1456. '$body' => xmlify($text),
  1457. '$positive' => xmlify($positive),
  1458. '$handle' => xmlify($myaddr)
  1459. ));
  1460. logger('diaspora_followup: base message: ' . $msg, LOGGER_DATA);
  1461. $slap = 'xml=' . urlencode(urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch)));
  1462. return(diaspora_transmit($owner,$contact,$slap,$public_batch));
  1463. }
  1464. function diaspora_send_relay($item,$owner,$contact,$public_batch = false) {
  1465. $a = get_app();
  1466. $myaddr = $owner['nickname'] . '@' . substr($a->get_baseurl(), strpos($a->get_baseurl(),'://') + 3);
  1467. $theiraddr = $contact['addr'];
  1468. $p = q("select guid from item where parent = %d limit 1",
  1469. $item['parent']
  1470. );
  1471. if(count($p))
  1472. $parent_guid = $p[0]['guid'];
  1473. else
  1474. return;
  1475. if($item['verb'] === ACTIVITY_LIKE) {
  1476. $tpl = get_markup_template('diaspora_like_relay.tpl');
  1477. $like = true;
  1478. $target_type = 'Post';
  1479. $positive = (($item['deleted']) ? 'false' : 'true');
  1480. }
  1481. else {
  1482. $tpl = get_markup_template('diaspora_comment_relay.tpl');
  1483. $like = false;
  1484. }
  1485. $body = $item['body'];
  1486. $text = html_entity_decode(bb2diaspora($body));
  1487. // fetch the original signature if somebody sent the post to us to relay
  1488. // If we are relaying for a reply originating on our own account, there wasn't a 'send to relay'
  1489. // action. It wasn't needed. In that case create the original signature and the
  1490. // owner (parent author) signature
  1491. // comments from other networks will be relayed under our name, with a brief
  1492. // preamble to describe what's happening and noting the real author
  1493. $r = q("select * from sign where iid = %d limit 1",
  1494. intval($item['id'])
  1495. );
  1496. if(count($r)) {
  1497. $orig_sign = $r[0];
  1498. $signed_text = $orig_sign['signed_text'];
  1499. $authorsig = $orig_sign['signature'];
  1500. $handle = $orig_sign['signer'];
  1501. }
  1502. else {
  1503. $itemcontact = q("select * from contact where `id` = %d limit 1",
  1504. intval($item['contact-id'])
  1505. );
  1506. if(count($itemcontact)) {
  1507. if(! $itemcontact[0]['self']) {
  1508. $prefix = sprintf( t('[Relayed] Comment authored by %s from network %s'),
  1509. '['. $item['author-name'] . ']' . '(' . $item['author-link'] . ')',
  1510. network_to_name($itemcontact['network'])) . "\n";
  1511. $body = $prefix . $body;
  1512. }
  1513. }
  1514. else {
  1515. if($like)
  1516. $signed_text = $item['guid'] . ';' . $target_type . ';' . $parent_guid . ';' . $positive . ';' . $myaddr;
  1517. else
  1518. $signed_text = $item['guid'] . ';' . $parent_guid . ';' . $text . ';' . $myaddr;
  1519. $authorsig = base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256'));
  1520. q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
  1521. intval($item['id']),
  1522. dbesc($signed_text),
  1523. dbesc(base64_encode($authorsig)),
  1524. dbesc($myaddr)
  1525. );
  1526. $handle = $myaddr;
  1527. }
  1528. }
  1529. // sign it
  1530. $parentauthorsig = base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256'));
  1531. $msg = replace_macros($tpl,array(
  1532. '$guid' => xmlify($item['guid']),
  1533. '$parent_guid' => xmlify($parent_guid),
  1534. '$target_type' =>xmlify($target_type),
  1535. '$authorsig' => xmlify($orig_sign['signature']),
  1536. '$parentsig' => xmlify($parentauthorsig),
  1537. '$body' => xmlify($text),
  1538. '$positive' => xmlify($positive),
  1539. '$handle' => xmlify($handle)
  1540. ));
  1541. logger('diaspora_relay_comment: base message: ' . $msg, LOGGER_DATA);
  1542. $slap = 'xml=' . urlencode(urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch)));
  1543. return(diaspora_transmit($owner,$contact,$slap,$public_batch));
  1544. }
  1545. function diaspora_send_retraction($item,$owner,$contact,$public_batch = false) {
  1546. $a = get_app();
  1547. $myaddr = $owner['nickname'] . '@' . substr($a->get_baseurl(), strpos($a->get_baseurl(),'://') + 3);
  1548. $signed_text = $item['guid'] . ';' . 'StatusMessage';
  1549. $tpl = get_markup_template('diaspora_signed_retract.tpl');
  1550. $msg = replace_macros($tpl, array(
  1551. '$guid' => $item['guid'],
  1552. '$type' => 'StatusMessage',
  1553. '$handle' => $myaddr,
  1554. '$signature' => base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256'))
  1555. ));
  1556. $slap = 'xml=' . urlencode(urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch)));
  1557. return(diaspora_transmit($owner,$contact,$slap,$public_batch));
  1558. }
  1559. function diaspora_send_mail($item,$owner,$contact) {
  1560. $a = get_app();
  1561. $myaddr = $owner['nickname'] . '@' . substr($a->get_baseurl(), strpos($a->get_baseurl(),'://') + 3);
  1562. $r = q("select * from conv where id = %d and uid = %d limit 1",
  1563. intval($item['convid']),
  1564. intval($item['uid'])
  1565. );
  1566. if(! count($r)) {
  1567. logger('diaspora_send_mail: conversation not found.');
  1568. return;
  1569. }
  1570. $cnv = $r[0];
  1571. $conv = array(
  1572. 'guid' => xmlify($cnv['guid']),
  1573. 'subject' => xmlify($cnv['subject']),
  1574. 'created_at' => xmlify(datetime_convert('UTC','UTC',$cnv['created'],'Y-m-d H:i:s \U\T\C')),
  1575. 'diaspora_handle' => xmlify($cnv['creator']),
  1576. 'participant_handles' => xmlify($cnv['recips'])
  1577. );
  1578. $body = bb2diaspora($item['body']);
  1579. $created = datetime_convert('UTC','UTC',$item['created'],'Y-m-d H:i:s \U\T\C');
  1580. $signed_text = $item['guid'] . ';' . $cnv['guid'] . ';' . $body . ';'
  1581. . $created . ';' . $myaddr . ';' . $cnv['guid'];
  1582. $sig = base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256'));
  1583. $msg = array(
  1584. 'guid' => xmlify($item['guid']),
  1585. 'parent_guid' => xmlify($cnv['guid']),
  1586. 'parent_author_signature' => (($item['reply']) ? null : xmlify($sig)),
  1587. 'author_signature' => xmlify($sig),
  1588. 'text' => xmlify($body),
  1589. 'created_at' => xmlify($created),
  1590. 'diaspora_handle' => xmlify($myaddr),
  1591. 'conversation_guid' => xmlify($cnv['guid'])
  1592. );
  1593. if($item['reply']) {
  1594. $tpl = get_markup_template('diaspora_message.tpl');
  1595. $xmsg = replace_macros($tpl, array('$msg' => $msg));
  1596. }
  1597. else {
  1598. $conv['messages'] = array($msg);
  1599. $tpl = get_markup_template('diaspora_conversation.tpl');
  1600. $xmsg = replace_macros($tpl, array('$conv' => $conv));
  1601. }
  1602. logger('diaspora_conversation: ' . print_r($xmsg,true), LOGGER_DATA);
  1603. $slap = 'xml=' . urlencode(urlencode(diaspora_msg_build($xmsg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],false)));
  1604. return(diaspora_transmit($owner,$contact,$slap,false));
  1605. }
  1606. function diaspora_transmit($owner,$contact,$slap,$public_batch) {
  1607. $a = get_app();
  1608. $logid = random_string(4);
  1609. $dest_url = (($public_batch) ? $contact['batch'] : $contact['notify']);
  1610. if(! $dest_url) {
  1611. logger('diaspora_transmit: no url for contact: ' . $contact['id'] . ' batch mode =' . $public_batch);
  1612. return 0;
  1613. }
  1614. logger('diaspora_transmit: ' . $logid . ' ' . $dest_url);
  1615. post_url($dest_url . '/', $slap);
  1616. $return_code = $a->get_curl_code();
  1617. logger('diaspora_transmit: ' . $logid . ' returns: ' . $return_code);
  1618. if((! $return_code) || (($return_code == 503) && (stristr($a->get_curl_headers(),'retry-after')))) {
  1619. logger('diaspora_transmit: queue message');
  1620. $r = q("SELECT id from queue where cid = %d and network = '%s' and content = '%s' and batch = %d limit 1",
  1621. intval($contact['id']),
  1622. dbesc(NETWORK_DIASPORA),
  1623. dbesc($slap),
  1624. intval($public_batch)
  1625. );
  1626. if(count($r)) {
  1627. logger('diaspora_transmit: add_to_queue ignored - identical item already in queue');
  1628. }
  1629. else {
  1630. // queue message for redelivery
  1631. add_to_queue($contact['id'],NETWORK_DIASPORA,$slap,$public_batch);
  1632. }
  1633. }
  1634. return(($return_code) ? $return_code : (-1));
  1635. }