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.

3016 lines
96 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
7 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
8 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
9 years ago
9 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. require_once('include/lock.php');
  8. require_once('include/threads.php');
  9. require_once('mod/share.php');
  10. function diaspora_dispatch_public($msg) {
  11. $enabled = intval(get_config('system','diaspora_enabled'));
  12. if(! $enabled) {
  13. logger('mod-diaspora: disabled');
  14. return;
  15. }
  16. $r = q("SELECT `user`.* FROM `user` WHERE `user`.`uid` IN
  17. ( SELECT `contact`.`uid` FROM `contact` WHERE `contact`.`network` = '%s' AND `contact`.`addr` = '%s' )
  18. AND `account_expired` = 0 AND `account_removed` = 0 ",
  19. dbesc(NETWORK_DIASPORA),
  20. dbesc($msg['author'])
  21. );
  22. if(count($r)) {
  23. foreach($r as $rr) {
  24. logger('diaspora_public: delivering to: ' . $rr['username']);
  25. diaspora_dispatch($rr,$msg);
  26. }
  27. }
  28. else
  29. logger('diaspora_public: no subscribers');
  30. }
  31. function diaspora_dispatch($importer,$msg,$attempt=1) {
  32. $ret = 0;
  33. $enabled = intval(get_config('system','diaspora_enabled'));
  34. if(! $enabled) {
  35. logger('mod-diaspora: disabled');
  36. return;
  37. }
  38. // php doesn't like dashes in variable names
  39. $msg['message'] = str_replace(
  40. array('<activity_streams-photo>','</activity_streams-photo>'),
  41. array('<asphoto>','</asphoto>'),
  42. $msg['message']);
  43. $parsed_xml = parse_xml_string($msg['message'],false);
  44. $xmlbase = $parsed_xml->post;
  45. logger('diaspora_dispatch: ' . print_r($xmlbase,true), LOGGER_DEBUG);
  46. if($xmlbase->request) {
  47. $ret = diaspora_request($importer,$xmlbase->request);
  48. }
  49. elseif($xmlbase->status_message) {
  50. $ret = diaspora_post($importer,$xmlbase->status_message,$msg);
  51. }
  52. elseif($xmlbase->profile) {
  53. $ret = diaspora_profile($importer,$xmlbase->profile,$msg);
  54. }
  55. elseif($xmlbase->comment) {
  56. $ret = diaspora_comment($importer,$xmlbase->comment,$msg);
  57. }
  58. elseif($xmlbase->like) {
  59. $ret = diaspora_like($importer,$xmlbase->like,$msg);
  60. }
  61. elseif($xmlbase->asphoto) {
  62. $ret = diaspora_asphoto($importer,$xmlbase->asphoto,$msg);
  63. }
  64. elseif($xmlbase->reshare) {
  65. $ret = diaspora_reshare($importer,$xmlbase->reshare,$msg);
  66. }
  67. elseif($xmlbase->retraction) {
  68. $ret = diaspora_retraction($importer,$xmlbase->retraction,$msg);
  69. }
  70. elseif($xmlbase->signed_retraction) {
  71. $ret = diaspora_signed_retraction($importer,$xmlbase->signed_retraction,$msg);
  72. }
  73. elseif($xmlbase->relayable_retraction) {
  74. $ret = diaspora_signed_retraction($importer,$xmlbase->relayable_retraction,$msg);
  75. }
  76. elseif($xmlbase->photo) {
  77. $ret = diaspora_photo($importer,$xmlbase->photo,$msg,$attempt);
  78. }
  79. elseif($xmlbase->conversation) {
  80. $ret = diaspora_conversation($importer,$xmlbase->conversation,$msg);
  81. }
  82. elseif($xmlbase->message) {
  83. $ret = diaspora_message($importer,$xmlbase->message,$msg);
  84. }
  85. else {
  86. logger('diaspora_dispatch: unknown message type: ' . print_r($xmlbase,true));
  87. }
  88. return $ret;
  89. }
  90. function diaspora_handle_from_contact($contact_id) {
  91. $handle = False;
  92. logger("diaspora_handle_from_contact: contact id is " . $contact_id, LOGGER_DEBUG);
  93. $r = q("SELECT network, addr, self, url, nick FROM contact WHERE id = %d",
  94. intval($contact_id)
  95. );
  96. if($r) {
  97. $contact = $r[0];
  98. logger("diaspora_handle_from_contact: contact 'self' = " . $contact['self'] . " 'url' = " . $contact['url'], LOGGER_DEBUG);
  99. if($contact['network'] === NETWORK_DIASPORA) {
  100. $handle = $contact['addr'];
  101. // logger("diaspora_handle_from_contact: contact id is a Diaspora person, handle = " . $handle, LOGGER_DEBUG);
  102. }
  103. elseif(($contact['network'] === NETWORK_DFRN) || ($contact['self'] == 1)) {
  104. $baseurl_start = strpos($contact['url'],'://') + 3;
  105. $baseurl_length = strpos($contact['url'],'/profile') - $baseurl_start; // allows installations in a subdirectory--not sure how Diaspora will handle
  106. $baseurl = substr($contact['url'], $baseurl_start, $baseurl_length);
  107. $handle = $contact['nick'] . '@' . $baseurl;
  108. // logger("diaspora_handle_from_contact: contact id is a DFRN person, handle = " . $handle, LOGGER_DEBUG);
  109. }
  110. }
  111. return $handle;
  112. }
  113. function diaspora_get_contact_by_handle($uid,$handle) {
  114. $r = q("SELECT * FROM `contact` WHERE `network` = '%s' AND `uid` = %d AND `addr` = '%s' LIMIT 1",
  115. dbesc(NETWORK_DIASPORA),
  116. intval($uid),
  117. dbesc($handle)
  118. );
  119. if($r && count($r))
  120. return $r[0];
  121. $handle_parts = explode("@", $handle);
  122. $nurl_sql = '%%://' . $handle_parts[1] . '%%/profile/' . $handle_parts[0];
  123. $r = q("SELECT * FROM contact WHERE network = '%s' AND uid = %d AND nurl LIKE '%s' LIMIT 1",
  124. dbesc(NETWORK_DFRN),
  125. intval($uid),
  126. dbesc($nurl_sql)
  127. );
  128. if($r && count($r))
  129. return $r[0];
  130. return false;
  131. }
  132. function find_diaspora_person_by_handle($handle) {
  133. $person = false;
  134. $update = false;
  135. $got_lock = false;
  136. $endlessloop = 0;
  137. $maxloops = 10;
  138. do {
  139. $r = q("select * from fcontact where network = '%s' and addr = '%s' limit 1",
  140. dbesc(NETWORK_DIASPORA),
  141. dbesc($handle)
  142. );
  143. if(count($r)) {
  144. $person = $r[0];
  145. logger('find_diaspora_person_by handle: in cache ' . print_r($r,true), LOGGER_DEBUG);
  146. // update record occasionally so it doesn't get stale
  147. $d = strtotime($person['updated'] . ' +00:00');
  148. if($d < strtotime('now - 14 days'))
  149. $update = true;
  150. }
  151. // FETCHING PERSON INFORMATION FROM REMOTE SERVER
  152. //
  153. // If the person isn't in our 'fcontact' table, or if he/she is but
  154. // his/her information hasn't been updated for more than 14 days, then
  155. // we want to fetch the person's information from the remote server.
  156. //
  157. // Note that $person isn't changed by this block of code unless the
  158. // person's information has been successfully fetched from the remote
  159. // server. So if $person was 'false' to begin with (because he/she wasn't
  160. // in the local cache), it'll stay false, and if $person held the local
  161. // cache information to begin with, it'll keep that information. That way
  162. // if there's a problem with the remote fetch, we can at least use our
  163. // cached information--it's better than nothing.
  164. if((! $person) || ($update)) {
  165. // Lock the function to prevent race conditions if multiple items
  166. // come in at the same time from a person who doesn't exist in
  167. // fcontact
  168. //
  169. // Don't loop forever. On the last loop, try to create the contact
  170. // whether the function is locked or not. Maybe the locking thread
  171. // has died or something. At any rate, a duplicate in 'fcontact'
  172. // is a much smaller problem than a deadlocked thread
  173. $got_lock = lock_function('find_diaspora_person_by_handle', false);
  174. if(($endlessloop + 1) >= $maxloops)
  175. $got_lock = true;
  176. if($got_lock) {
  177. logger('find_diaspora_person_by_handle: create or refresh', LOGGER_DEBUG);
  178. require_once('include/Scrape.php');
  179. $r = probe_url($handle, PROBE_DIASPORA);
  180. // Note that Friendica contacts can return a "Diaspora person"
  181. // if Diaspora connectivity is enabled on their server
  182. if((count($r)) && ($r['network'] === NETWORK_DIASPORA)) {
  183. add_fcontact($r,$update);
  184. $person = ($r);
  185. }
  186. unlock_function('find_diaspora_person_by_handle');
  187. }
  188. else {
  189. logger('find_diaspora_person_by_handle: couldn\'t lock function', LOGGER_DEBUG);
  190. if(! $person)
  191. block_on_function_lock('find_diaspora_person_by_handle');
  192. }
  193. }
  194. } while((! $person) && (! $got_lock) && (++$endlessloop < $maxloops));
  195. // We need to try again if the person wasn't in 'fcontact' but the function was locked.
  196. // The fact that the function was locked may mean that another process was creating the
  197. // person's record. It could also mean another process was creating or updating an unrelated
  198. // person.
  199. //
  200. // At any rate, we need to keep trying until we've either got the person or had a chance to
  201. // try to fetch his/her remote information. But we don't want to block on locking the
  202. // function, because if the other process is creating the record, then when we acquire the lock
  203. // we'll dive right into creating another, duplicate record. We DO want to at least wait
  204. // until the lock is released, so we don't flood the database with requests.
  205. //
  206. // If the person was in the 'fcontact' table, don't try again. It's not worth the time, since
  207. // we do have some information for the person
  208. return $person;
  209. }
  210. function get_diaspora_key($uri) {
  211. logger('Fetching diaspora key for: ' . $uri);
  212. $r = find_diaspora_person_by_handle($uri);
  213. if($r)
  214. return $r['pubkey'];
  215. return '';
  216. }
  217. function diaspora_pubmsg_build($msg,$user,$contact,$prvkey,$pubkey) {
  218. $a = get_app();
  219. logger('diaspora_pubmsg_build: ' . $msg, LOGGER_DATA);
  220. $handle = $user['nickname'] . '@' . substr($a->get_baseurl(), strpos($a->get_baseurl(),'://') + 3);
  221. // $b64_data = base64_encode($msg);
  222. // $b64url_data = base64url_encode($b64_data);
  223. $b64url_data = base64url_encode($msg);
  224. $data = str_replace(array("\n","\r"," ","\t"),array('','','',''),$b64url_data);
  225. $type = 'application/xml';
  226. $encoding = 'base64url';
  227. $alg = 'RSA-SHA256';
  228. $signable_data = $data . '.' . base64url_encode($type) . '.'
  229. . base64url_encode($encoding) . '.' . base64url_encode($alg) ;
  230. $signature = rsa_sign($signable_data,$prvkey);
  231. $sig = base64url_encode($signature);
  232. $magic_env = <<< EOT
  233. <?xml version='1.0' encoding='UTF-8'?>
  234. <diaspora xmlns="https://joindiaspora.com/protocol" xmlns:me="http://salmon-protocol.org/ns/magic-env" >
  235. <header>
  236. <author_id>$handle</author_id>
  237. </header>
  238. <me:env>
  239. <me:encoding>base64url</me:encoding>
  240. <me:alg>RSA-SHA256</me:alg>
  241. <me:data type="application/xml">$data</me:data>
  242. <me:sig>$sig</me:sig>
  243. </me:env>
  244. </diaspora>
  245. EOT;
  246. logger('diaspora_pubmsg_build: magic_env: ' . $magic_env, LOGGER_DATA);
  247. return $magic_env;
  248. }
  249. function diaspora_msg_build($msg,$user,$contact,$prvkey,$pubkey,$public = false) {
  250. $a = get_app();
  251. if($public)
  252. return diaspora_pubmsg_build($msg,$user,$contact,$prvkey,$pubkey);
  253. logger('diaspora_msg_build: ' . $msg, LOGGER_DATA);
  254. // without a public key nothing will work
  255. if(! $pubkey) {
  256. logger('diaspora_msg_build: pubkey missing: contact id: ' . $contact['id']);
  257. return '';
  258. }
  259. $inner_aes_key = random_string(32);
  260. $b_inner_aes_key = base64_encode($inner_aes_key);
  261. $inner_iv = random_string(16);
  262. $b_inner_iv = base64_encode($inner_iv);
  263. $outer_aes_key = random_string(32);
  264. $b_outer_aes_key = base64_encode($outer_aes_key);
  265. $outer_iv = random_string(16);
  266. $b_outer_iv = base64_encode($outer_iv);
  267. $handle = $user['nickname'] . '@' . substr($a->get_baseurl(), strpos($a->get_baseurl(),'://') + 3);
  268. $padded_data = pkcs5_pad($msg,16);
  269. $inner_encrypted = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $inner_aes_key, $padded_data, MCRYPT_MODE_CBC, $inner_iv);
  270. $b64_data = base64_encode($inner_encrypted);
  271. $b64url_data = base64url_encode($b64_data);
  272. $data = str_replace(array("\n","\r"," ","\t"),array('','','',''),$b64url_data);
  273. $type = 'application/xml';
  274. $encoding = 'base64url';
  275. $alg = 'RSA-SHA256';
  276. $signable_data = $data . '.' . base64url_encode($type) . '.'
  277. . base64url_encode($encoding) . '.' . base64url_encode($alg) ;
  278. $signature = rsa_sign($signable_data,$prvkey);
  279. $sig = base64url_encode($signature);
  280. $decrypted_header = <<< EOT
  281. <decrypted_header>
  282. <iv>$b_inner_iv</iv>
  283. <aes_key>$b_inner_aes_key</aes_key>
  284. <author_id>$handle</author_id>
  285. </decrypted_header>
  286. EOT;
  287. $decrypted_header = pkcs5_pad($decrypted_header,16);
  288. $ciphertext = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $outer_aes_key, $decrypted_header, MCRYPT_MODE_CBC, $outer_iv);
  289. $outer_json = json_encode(array('iv' => $b_outer_iv,'key' => $b_outer_aes_key));
  290. $encrypted_outer_key_bundle = '';
  291. openssl_public_encrypt($outer_json,$encrypted_outer_key_bundle,$pubkey);
  292. $b64_encrypted_outer_key_bundle = base64_encode($encrypted_outer_key_bundle);
  293. logger('outer_bundle: ' . $b64_encrypted_outer_key_bundle . ' key: ' . $pubkey, LOGGER_DATA);
  294. $encrypted_header_json_object = json_encode(array('aes_key' => base64_encode($encrypted_outer_key_bundle),
  295. 'ciphertext' => base64_encode($ciphertext)));
  296. $cipher_json = base64_encode($encrypted_header_json_object);
  297. $encrypted_header = '<encrypted_header>' . $cipher_json . '</encrypted_header>';
  298. $magic_env = <<< EOT
  299. <?xml version='1.0' encoding='UTF-8'?>
  300. <diaspora xmlns="https://joindiaspora.com/protocol" xmlns:me="http://salmon-protocol.org/ns/magic-env" >
  301. $encrypted_header
  302. <me:env>
  303. <me:encoding>base64url</me:encoding>
  304. <me:alg>RSA-SHA256</me:alg>
  305. <me:data type="application/xml">$data</me:data>
  306. <me:sig>$sig</me:sig>
  307. </me:env>
  308. </diaspora>
  309. EOT;
  310. logger('diaspora_msg_build: magic_env: ' . $magic_env, LOGGER_DATA);
  311. return $magic_env;
  312. }
  313. /**
  314. *
  315. * diaspora_decode($importer,$xml)
  316. * array $importer -> from user table
  317. * string $xml -> urldecoded Diaspora salmon
  318. *
  319. * Returns array
  320. * 'message' -> decoded Diaspora XML message
  321. * 'author' -> author diaspora handle
  322. * 'key' -> author public key (converted to pkcs#8)
  323. *
  324. * Author and key are used elsewhere to save a lookup for verifying replies and likes
  325. */
  326. function diaspora_decode($importer,$xml) {
  327. $public = false;
  328. $basedom = parse_xml_string($xml);
  329. $children = $basedom->children('https://joindiaspora.com/protocol');
  330. if($children->header) {
  331. $public = true;
  332. $author_link = str_replace('acct:','',$children->header->author_id);
  333. }
  334. else {
  335. $encrypted_header = json_decode(base64_decode($children->encrypted_header));
  336. $encrypted_aes_key_bundle = base64_decode($encrypted_header->aes_key);
  337. $ciphertext = base64_decode($encrypted_header->ciphertext);
  338. $outer_key_bundle = '';
  339. openssl_private_decrypt($encrypted_aes_key_bundle,$outer_key_bundle,$importer['prvkey']);
  340. $j_outer_key_bundle = json_decode($outer_key_bundle);
  341. $outer_iv = base64_decode($j_outer_key_bundle->iv);
  342. $outer_key = base64_decode($j_outer_key_bundle->key);
  343. $decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $outer_key, $ciphertext, MCRYPT_MODE_CBC, $outer_iv);
  344. $decrypted = pkcs5_unpad($decrypted);
  345. /**
  346. * $decrypted now contains something like
  347. *
  348. * <decrypted_header>
  349. * <iv>8e+G2+ET8l5BPuW0sVTnQw==</iv>
  350. * <aes_key>UvSMb4puPeB14STkcDWq+4QE302Edu15oaprAQSkLKU=</aes_key>
  351. ***** OBSOLETE
  352. * <author>
  353. * <name>Ryan Hughes</name>
  354. * <uri>acct:galaxor@diaspora.pirateship.org</uri>
  355. * </author>
  356. ***** CURRENT
  357. * <author_id>galaxor@diaspora.priateship.org</author_id>
  358. ***** END DIFFS
  359. * </decrypted_header>
  360. */
  361. logger('decrypted: ' . $decrypted, LOGGER_DEBUG);
  362. $idom = parse_xml_string($decrypted,false);
  363. $inner_iv = base64_decode($idom->iv);
  364. $inner_aes_key = base64_decod