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.

343 lines
11 KiB

11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
10 years ago
10 years ago
11 years ago
  1. <?php
  2. require_once('library/simplepie/simplepie.inc');
  3. require_once('include/items.php');
  4. require_once('include/event.php');
  5. require_once('library/defuse/php-encryption-1.2.1/Crypto.php');
  6. function dfrn_notify_post(&$a) {
  7. logger(__function__, LOGGER_TRACE);
  8. $dfrn_id = ((x($_POST,'dfrn_id')) ? notags(trim($_POST['dfrn_id'])) : '');
  9. $dfrn_version = ((x($_POST,'dfrn_version')) ? (float) $_POST['dfrn_version'] : 2.0);
  10. $challenge = ((x($_POST,'challenge')) ? notags(trim($_POST['challenge'])) : '');
  11. $data = ((x($_POST,'data')) ? $_POST['data'] : '');
  12. $key = ((x($_POST,'key')) ? $_POST['key'] : '');
  13. $rino_remote = ((x($_POST,'rino')) ? intval($_POST['rino']) : 0);
  14. $dissolve = ((x($_POST,'dissolve')) ? intval($_POST['dissolve']) : 0);
  15. $perm = ((x($_POST,'perm')) ? notags(trim($_POST['perm'])) : 'r');
  16. $ssl_policy = ((x($_POST,'ssl_policy')) ? notags(trim($_POST['ssl_policy'])): 'none');
  17. $page = ((x($_POST,'page')) ? intval($_POST['page']) : 0);
  18. $forum = (($page == 1) ? 1 : 0);
  19. $prv = (($page == 2) ? 1 : 0);
  20. $writable = (-1);
  21. if($dfrn_version >= 2.21) {
  22. $writable = (($perm === 'rw') ? 1 : 0);
  23. }
  24. $direction = (-1);
  25. if(strpos($dfrn_id,':') == 1) {
  26. $direction = intval(substr($dfrn_id,0,1));
  27. $dfrn_id = substr($dfrn_id,2);
  28. }
  29. $r = q("SELECT * FROM `challenge` WHERE `dfrn-id` = '%s' AND `challenge` = '%s' LIMIT 1",
  30. dbesc($dfrn_id),
  31. dbesc($challenge)
  32. );
  33. if(! count($r)) {
  34. logger('dfrn_notify: could not match challenge to dfrn_id ' . $dfrn_id . ' challenge=' . $challenge);
  35. xml_status(3);
  36. }
  37. $r = q("DELETE FROM `challenge` WHERE `dfrn-id` = '%s' AND `challenge` = '%s'",
  38. dbesc($dfrn_id),
  39. dbesc($challenge)
  40. );
  41. // find the local user who owns this relationship.
  42. $sql_extra = '';
  43. switch($direction) {
  44. case (-1):
  45. $sql_extra = sprintf(" AND ( `issued-id` = '%s' OR `dfrn-id` = '%s' ) ", dbesc($dfrn_id), dbesc($dfrn_id));
  46. break;
  47. case 0:
  48. $sql_extra = sprintf(" AND `issued-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
  49. break;
  50. case 1:
  51. $sql_extra = sprintf(" AND `dfrn-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
  52. break;
  53. default:
  54. xml_status(3);
  55. break; // NOTREACHED
  56. }
  57. // be careful - $importer will contain both the contact information for the contact
  58. // sending us the post, and also the user information for the person receiving it.
  59. // since they are mixed together, it is easy to get them confused.
  60. $r = q("SELECT `contact`.*, `contact`.`uid` AS `importer_uid`,
  61. `contact`.`pubkey` AS `cpubkey`,
  62. `contact`.`prvkey` AS `cprvkey`,
  63. `contact`.`thumb` AS `thumb`,
  64. `contact`.`url` as `url`,
  65. `contact`.`name` as `senderName`,
  66. `user`.*
  67. FROM `contact`
  68. LEFT JOIN `user` ON `contact`.`uid` = `user`.`uid`
  69. WHERE `contact`.`blocked` = 0 AND `contact`.`pending` = 0
  70. AND `user`.`nickname` = '%s' AND `user`.`account_expired` = 0 AND `user`.`account_removed` = 0 $sql_extra LIMIT 1",
  71. dbesc($a->argv[1])
  72. );
  73. if(! count($r)) {
  74. logger('dfrn_notify: contact not found for dfrn_id ' . $dfrn_id);
  75. xml_status(3);
  76. //NOTREACHED
  77. }
  78. // $importer in this case contains the contact record for the remote contact joined with the user record of our user.
  79. $importer = $r[0];
  80. logger("Remote rino version: ".$rino_remote." for ".$importer["url"], LOGGER_DEBUG);
  81. if((($writable != (-1)) && ($writable != $importer['writable'])) || ($importer['forum'] != $forum) || ($importer['prv'] != $prv)) {
  82. q("UPDATE `contact` SET `writable` = %d, forum = %d, prv = %d WHERE `id` = %d",
  83. intval(($writable == (-1)) ? $importer['writable'] : $writable),
  84. intval($forum),
  85. intval($prv),
  86. intval($importer['id'])
  87. );
  88. if($writable != (-1))
  89. $importer['writable'] = $writable;
  90. $importer['forum'] = $page;
  91. }
  92. // if contact's ssl policy changed, update our links
  93. fix_contact_ssl_policy($importer,$ssl_policy);
  94. logger('dfrn_notify: received notify from ' . $importer['name'] . ' for ' . $importer['username']);
  95. logger('dfrn_notify: data: ' . $data, LOGGER_DATA);
  96. if($dissolve == 1) {
  97. /**
  98. * Relationship is dissolved permanently
  99. */
  100. require_once('include/Contact.php');
  101. contact_remove($importer['id']);
  102. logger('relationship dissolved : ' . $importer['name'] . ' dissolved ' . $importer['username']);
  103. xml_status(0);
  104. }
  105. // If we are setup as a soapbox we aren't accepting input from this person
  106. // This behaviour is deactivated since it really doesn't make sense to even disallow comments
  107. // The check if someone is a friend or simply a follower is done in a later place so it needn't to be done here
  108. //if($importer['page-flags'] == PAGE_SOAPBOX)
  109. // xml_status(0);
  110. $rino = get_config('system','rino_encrypt');
  111. $rino = intval($rino);
  112. // use RINO1 if mcrypt isn't installed and RINO2 was selected
  113. if ($rino==2 and !function_exists('mcrypt_create_iv')) $rino=1;
  114. logger("Local rino version: ". $rino, LOGGER_DEBUG);
  115. if(strlen($key)) {
  116. // if local rino is lower than remote rino, abort: should not happen!
  117. // but only for $remote_rino > 1, because old code did't send rino version
  118. if ($rino_remote_version > 1 && $rino < $rino_remote) {
  119. logger("rino version '$rino_remote' is lower than supported '$rino'");
  120. xml_status(0,"rino version '$rino_remote' is lower than supported '$rino'");
  121. }
  122. $rawkey = hex2bin(trim($key));
  123. logger('rino: md5 raw key: ' . md5($rawkey));
  124. $final_key = '';
  125. if($dfrn_version >= 2.1) {
  126. if((($importer['duplex']) && strlen($importer['cprvkey'])) || (! strlen($importer['cpubkey']))) {
  127. openssl_private_decrypt($rawkey,$final_key,$importer['cprvkey']);
  128. }
  129. else {
  130. openssl_public_decrypt($rawkey,$final_key,$importer['cpubkey']);
  131. }
  132. }
  133. else {
  134. if((($importer['duplex']) && strlen($importer['cpubkey'])) || (! strlen($importer['cprvkey']))) {
  135. openssl_public_decrypt($rawkey,$final_key,$importer['cpubkey']);
  136. }
  137. else {
  138. openssl_private_decrypt($rawkey,$final_key,$importer['cprvkey']);
  139. }
  140. }
  141. #logger('rino: received key : ' . $final_key);
  142. switch($rino_remote) {
  143. case 0:
  144. case 1:
  145. // we got a key. old code send only the key, without RINO version.
  146. // we assume RINO 1 if key and no RINO version
  147. $data = aes_decrypt(hex2bin($data),$final_key);
  148. break;
  149. case 2:
  150. try {
  151. $data = Crypto::decrypt(hex2bin($data),$final_key);
  152. } catch (InvalidCiphertext $ex) { // VERY IMPORTANT
  153. // Either:
  154. // 1. The ciphertext was modified by the attacker,
  155. // 2. The key is wrong, or
  156. // 3. $ciphertext is not a valid ciphertext or was corrupted.
  157. // Assume the worst.
  158. logger('The ciphertext has been tampered with!');
  159. xml_status(0,'The ciphertext has been tampered with!');
  160. } catch (Ex\CryptoTestFailed $ex) {
  161. logger('Cannot safely perform dencryption');
  162. xml_status(0,'CryptoTestFailed');
  163. } catch (Ex\CannotPerformOperation $ex) {
  164. logger('Cannot safely perform decryption');
  165. xml_status(0,'Cannot safely perform decryption');
  166. }
  167. break;
  168. default:
  169. logger("rino: invalid sent verision '$rino_remote'");
  170. xml_status(0);
  171. }
  172. logger('rino: decrypted data: ' . $data, LOGGER_DATA);
  173. }
  174. $ret = local_delivery($importer,$data);
  175. xml_status($ret);
  176. // NOTREACHED
  177. }
  178. function dfrn_notify_content(&$a) {
  179. if(x($_GET,'dfrn_id')) {
  180. // initial communication from external contact, $direction is their direction.
  181. // If this is a duplex communication, ours will be the opposite.
  182. $dfrn_id = notags(trim($_GET['dfrn_id']));
  183. $dfrn_version = (float) $_GET['dfrn_version'];
  184. $rino_remote = ((x($_GET,'rino')) ? intval($_GET['rino']) : 0);
  185. $type = "";
  186. $last_update = "";
  187. logger('dfrn_notify: new notification dfrn_id=' . $dfrn_id);
  188. $direction = (-1);
  189. if(strpos($dfrn_id,':') == 1) {
  190. $direction = intval(substr($dfrn_id,0,1));
  191. $dfrn_id = substr($dfrn_id,2);
  192. }
  193. $hash = random_string();
  194. $status = 0;
  195. $r = q("DELETE FROM `challenge` WHERE `expire` < " . intval(time()));
  196. $r = q("INSERT INTO `challenge` ( `challenge`, `dfrn-id`, `expire` , `type`, `last_update` )
  197. VALUES( '%s', '%s', %d, '%s', '%s' ) ",
  198. dbesc($hash),
  199. dbesc($dfrn_id),
  200. intval(time() + 90 ),
  201. dbesc($type),
  202. dbesc($last_update)
  203. );
  204. logger('dfrn_notify: challenge=' . $hash, LOGGER_DEBUG);
  205. $sql_extra = '';
  206. switch($direction) {
  207. case (-1):
  208. $sql_extra = sprintf(" AND ( `issued-id` = '%s' OR `dfrn-id` = '%s' ) ", dbesc($dfrn_id), dbesc($dfrn_id));
  209. $my_id = $dfrn_id;
  210. break;
  211. case 0:
  212. $sql_extra = sprintf(" AND `issued-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
  213. $my_id = '1:' . $dfrn_id;
  214. break;
  215. case 1:
  216. $sql_extra = sprintf(" AND `dfrn-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
  217. $my_id = '0:' . $dfrn_id;
  218. break;
  219. default:
  220. $status = 1;
  221. break; // NOTREACHED
  222. }
  223. $r = q("SELECT `contact`.*, `user`.`nickname`, `user`.`page-flags` FROM `contact` LEFT JOIN `user` ON `user`.`uid` = `contact`.`uid`
  224. WHERE `contact`.`blocked` = 0 AND `contact`.`pending` = 0 AND `user`.`nickname` = '%s'
  225. AND `user`.`account_expired` = 0 AND `user`.`account_removed` = 0 $sql_extra LIMIT 1",
  226. dbesc($a->argv[1])
  227. );
  228. if(! count($r))
  229. $status = 1;
  230. logger("Remote rino version: ".$rino_remote." for ".$r[0]["url"], LOGGER_DEBUG);
  231. $challenge = '';
  232. $encrypted_id = '';
  233. $id_str = $my_id . '.' . mt_rand(1000,9999);
  234. $prv_key = trim($r[0]['prvkey']);
  235. $pub_key = trim($r[0]['pubkey']);
  236. $dplx = intval($r[0]['duplex']);
  237. if((($dplx) && (strlen($prv_key))) || ((strlen($prv_key)) && (!(strlen($pub_key))))) {
  238. openssl_private_encrypt($hash,$challenge,$prv_key);
  239. openssl_private_encrypt($id_str,$encrypted_id,$prv_key);
  240. }
  241. elseif(strlen($pub_key)) {
  242. openssl_public_encrypt($hash,$challenge,$pub_key);
  243. openssl_public_encrypt($id_str,$encrypted_id,$pub_key);
  244. }
  245. else
  246. $status = 1;
  247. $challenge = bin2hex($challenge);
  248. $encrypted_id = bin2hex($encrypted_id);
  249. $rino = get_config('system','rino_encrypt');
  250. $rino = intval($rino);
  251. // use RINO1 if mcrypt isn't installed and RINO2 was selected
  252. if ($rino==2 and !function_exists('mcrypt_create_iv')) $rino=1;
  253. logger("Local rino version: ". $rino, LOGGER_DEBUG);
  254. // if requested rino is lower than enabled local rino, lower local rino version
  255. // if requested rino is higher than enabled local rino, reply with local rino
  256. if ($rino_remote < $rino) $rino = $rino_remote;
  257. if((($r[0]['rel']) && ($r[0]['rel'] != CONTACT_IS_SHARING)) || ($r[0]['page-flags'] == PAGE_COMMUNITY)) {
  258. $perm = 'rw';
  259. }
  260. else {
  261. $perm = 'r';
  262. }
  263. header("Content-type: text/xml");
  264. echo '<?xml version="1.0" encoding="UTF-8"?>' . "\r\n"
  265. . '<dfrn_notify>' . "\r\n"
  266. . "\t" . '<status>' . $status . '</status>' . "\r\n"
  267. . "\t" . '<dfrn_version>' . DFRN_PROTOCOL_VERSION . '</dfrn_version>' . "\r\n"
  268. . "\t" . '<rino>' . $rino . '</rino>' . "\r\n"
  269. . "\t" . '<perm>' . $perm . '</perm>' . "\r\n"
  270. . "\t" . '<dfrn_id>' . $encrypted_id . '</dfrn_id>' . "\r\n"
  271. . "\t" . '<challenge>' . $challenge . '</challenge>' . "\r\n"
  272. . '</dfrn_notify>' . "\r\n" ;
  273. killme();
  274. }
  275. }