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.

567 lines
16KB

  1. <?php
  2. require_once('include/items.php');
  3. require_once('include/auth.php');
  4. require_once('include/dfrn.php');
  5. function dfrn_poll_init(App $a) {
  6. $dfrn_id = ((x($_GET,'dfrn_id')) ? $_GET['dfrn_id'] : '');
  7. $type = ((x($_GET,'type')) ? $_GET['type'] : 'data');
  8. $last_update = ((x($_GET,'last_update')) ? $_GET['last_update'] : '');
  9. $destination_url = ((x($_GET,'destination_url')) ? $_GET['destination_url'] : '');
  10. $challenge = ((x($_GET,'challenge')) ? $_GET['challenge'] : '');
  11. $sec = ((x($_GET,'sec')) ? $_GET['sec'] : '');
  12. $dfrn_version = ((x($_GET,'dfrn_version')) ? (float) $_GET['dfrn_version'] : 2.0);
  13. $perm = ((x($_GET,'perm')) ? $_GET['perm'] : 'r');
  14. $quiet = ((x($_GET,'quiet')) ? true : false);
  15. $direction = (-1);
  16. if(strpos($dfrn_id,':') == 1) {
  17. $direction = intval(substr($dfrn_id,0,1));
  18. $dfrn_id = substr($dfrn_id,2);
  19. }
  20. $hidewall = false;
  21. if(($dfrn_id === '') && (! x($_POST,'dfrn_id'))) {
  22. if((get_config('system','block_public')) && (! local_user()) && (! remote_user())) {
  23. http_status_exit(403);
  24. }
  25. $user = '';
  26. if($a->argc > 1) {
  27. $r = q("SELECT `hidewall`,`nickname` FROM `user` WHERE `user`.`nickname` = '%s' LIMIT 1",
  28. dbesc($a->argv[1])
  29. );
  30. if (!$r)
  31. http_status_exit(404);
  32. $hidewall = ($r[0]['hidewall'] && !local_user());
  33. $user = $r[0]['nickname'];
  34. }
  35. logger('dfrn_poll: public feed request from ' . $_SERVER['REMOTE_ADDR'] . ' for ' . $user);
  36. header("Content-type: application/atom+xml");
  37. echo dfrn::feed('', $user,$last_update, 0, $hidewall);
  38. killme();
  39. }
  40. if(($type === 'profile') && (! strlen($sec))) {
  41. $sql_extra = '';
  42. switch($direction) {
  43. case (-1):
  44. $sql_extra = sprintf(" AND ( `dfrn-id` = '%s' OR `issued-id` = '%s' ) ", dbesc($dfrn_id),dbesc($dfrn_id));
  45. $my_id = $dfrn_id;
  46. break;
  47. case 0:
  48. $sql_extra = sprintf(" AND `issued-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
  49. $my_id = '1:' . $dfrn_id;
  50. break;
  51. case 1:
  52. $sql_extra = sprintf(" AND `dfrn-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
  53. $my_id = '0:' . $dfrn_id;
  54. break;
  55. default:
  56. goaway(z_root());
  57. break; // NOTREACHED
  58. }
  59. $r = q("SELECT `contact`.*, `user`.`username`, `user`.`nickname`
  60. FROM `contact` LEFT JOIN `user` ON `contact`.`uid` = `user`.`uid`
  61. WHERE `contact`.`blocked` = 0 AND `contact`.`pending` = 0
  62. AND `user`.`nickname` = '%s' $sql_extra LIMIT 1",
  63. dbesc($a->argv[1])
  64. );
  65. if (dbm::is_result($r)) {
  66. $s = fetch_url($r[0]['poll'] . '?dfrn_id=' . $my_id . '&type=profile-check');
  67. logger("dfrn_poll: old profile returns " . $s, LOGGER_DATA);
  68. if(strlen($s)) {
  69. $xml = parse_xml_string($s);
  70. if((int) $xml->status == 1) {
  71. $_SESSION['authenticated'] = 1;
  72. if(! x($_SESSION,'remote'))
  73. $_SESSION['remote'] = array();
  74. $_SESSION['remote'][] = array('cid' => $r[0]['id'],'uid' => $r[0]['uid'],'url' => $r[0]['url']);
  75. $_SESSION['visitor_id'] = $r[0]['id'];
  76. $_SESSION['visitor_home'] = $r[0]['url'];
  77. $_SESSION['visitor_handle'] = $r[0]['addr'];
  78. $_SESSION['visitor_visiting'] = $r[0]['uid'];
  79. if(!$quiet)
  80. info( sprintf(t('%1$s welcomes %2$s'), $r[0]['username'] , $r[0]['name']) . EOL);
  81. // Visitors get 1 day session.
  82. $session_id = session_id();
  83. $expire = time() + 86400;
  84. q("UPDATE `session` SET `expire` = '%s' WHERE `sid` = '%s'",
  85. dbesc($expire),
  86. dbesc($session_id)
  87. );
  88. }
  89. }
  90. $profile = $r[0]['nickname'];
  91. goaway((strlen($destination_url)) ? $destination_url : App::get_baseurl() . '/profile/' . $profile);
  92. }
  93. goaway(z_root());
  94. }
  95. if($type === 'profile-check' && $dfrn_version < 2.2 ) {
  96. if((strlen($challenge)) && (strlen($sec))) {
  97. q("DELETE FROM `profile_check` WHERE `expire` < " . intval(time()));
  98. $r = q("SELECT * FROM `profile_check` WHERE `sec` = '%s' ORDER BY `expire` DESC LIMIT 1",
  99. dbesc($sec)
  100. );
  101. if (! dbm::is_result($r)) {
  102. xml_status(3, 'No ticket');
  103. // NOTREACHED
  104. }
  105. $orig_id = $r[0]['dfrn_id'];
  106. if(strpos($orig_id, ':'))
  107. $orig_id = substr($orig_id,2);
  108. $c = q("SELECT * FROM `contact` WHERE `id` = %d LIMIT 1",
  109. intval($r[0]['cid'])
  110. );
  111. if (! dbm::is_result($c)) {
  112. xml_status(3, 'No profile');
  113. }
  114. $contact = $c[0];
  115. $sent_dfrn_id = hex2bin($dfrn_id);
  116. $challenge = hex2bin($challenge);
  117. $final_dfrn_id = '';
  118. if(($contact['duplex']) && strlen($contact['prvkey'])) {
  119. openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
  120. openssl_private_decrypt($challenge,$decoded_challenge,$contact['prvkey']);
  121. }
  122. else {
  123. openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
  124. openssl_public_decrypt($challenge,$decoded_challenge,$contact['pubkey']);
  125. }
  126. $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
  127. if(strpos($final_dfrn_id,':') == 1)
  128. $final_dfrn_id = substr($final_dfrn_id,2);
  129. if($final_dfrn_id != $orig_id) {
  130. logger('profile_check: ' . $final_dfrn_id . ' != ' . $orig_id, LOGGER_DEBUG);
  131. // did not decode properly - cannot trust this site
  132. xml_status(3, 'Bad decryption');
  133. }
  134. header("Content-type: text/xml");
  135. echo "<?xml version=\"1.0\" encoding=\"UTF-8\"?><dfrn_poll><status>0</status><challenge>$decoded_challenge</challenge><sec>$sec</sec></dfrn_poll>";
  136. killme();
  137. // NOTREACHED
  138. }
  139. else {
  140. // old protocol
  141. switch($direction) {
  142. case 1:
  143. $dfrn_id = '0:' . $dfrn_id;
  144. break;
  145. case 0:
  146. $dfrn_id = '1:' . $dfrn_id;
  147. break;
  148. default:
  149. break;
  150. }
  151. q("DELETE FROM `profile_check` WHERE `expire` < " . intval(time()));
  152. $r = q("SELECT * FROM `profile_check` WHERE `dfrn_id` = '%s' ORDER BY `expire` DESC",
  153. dbesc($dfrn_id));
  154. if (dbm::is_result($r)) {
  155. xml_status(1);
  156. return; // NOTREACHED
  157. }
  158. xml_status(0);
  159. return; // NOTREACHED
  160. }
  161. }
  162. }
  163. function dfrn_poll_post(App $a) {
  164. $dfrn_id = ((x($_POST,'dfrn_id')) ? $_POST['dfrn_id'] : '');
  165. $challenge = ((x($_POST,'challenge')) ? $_POST['challenge'] : '');
  166. $url = ((x($_POST,'url')) ? $_POST['url'] : '');
  167. $sec = ((x($_POST,'sec')) ? $_POST['sec'] : '');
  168. $ptype = ((x($_POST,'type')) ? $_POST['type'] : '');
  169. $dfrn_version = ((x($_POST,'dfrn_version')) ? (float) $_POST['dfrn_version'] : 2.0);
  170. $perm = ((x($_POST,'perm')) ? $_POST['perm'] : 'r');
  171. if($ptype === 'profile-check') {
  172. if((strlen($challenge)) && (strlen($sec))) {
  173. logger('dfrn_poll: POST: profile-check');
  174. q("DELETE FROM `profile_check` WHERE `expire` < " . intval(time()));
  175. $r = q("SELECT * FROM `profile_check` WHERE `sec` = '%s' ORDER BY `expire` DESC LIMIT 1",
  176. dbesc($sec)
  177. );
  178. if (! dbm::is_result($r)) {
  179. xml_status(3, 'No ticket');
  180. // NOTREACHED
  181. }
  182. $orig_id = $r[0]['dfrn_id'];
  183. if(strpos($orig_id, ':'))
  184. $orig_id = substr($orig_id,2);
  185. $c = q("SELECT * FROM `contact` WHERE `id` = %d LIMIT 1",
  186. intval($r[0]['cid'])
  187. );
  188. if (! dbm::is_result($c)) {
  189. xml_status(3, 'No profile');
  190. }
  191. $contact = $c[0];
  192. $sent_dfrn_id = hex2bin($dfrn_id);
  193. $challenge = hex2bin($challenge);
  194. $final_dfrn_id = '';
  195. if(($contact['duplex']) && strlen($contact['prvkey'])) {
  196. openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
  197. openssl_private_decrypt($challenge,$decoded_challenge,$contact['prvkey']);
  198. }
  199. else {
  200. openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
  201. openssl_public_decrypt($challenge,$decoded_challenge,$contact['pubkey']);
  202. }
  203. $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
  204. if(strpos($final_dfrn_id,':') == 1)
  205. $final_dfrn_id = substr($final_dfrn_id,2);
  206. if($final_dfrn_id != $orig_id) {
  207. logger('profile_check: ' . $final_dfrn_id . ' != ' . $orig_id, LOGGER_DEBUG);
  208. // did not decode properly - cannot trust this site
  209. xml_status(3, 'Bad decryption');
  210. }
  211. header("Content-type: text/xml");
  212. echo "<?xml version=\"1.0\" encoding=\"UTF-8\"?><dfrn_poll><status>0</status><challenge>$decoded_challenge</challenge><sec>$sec</sec></dfrn_poll>";
  213. killme();
  214. // NOTREACHED
  215. }
  216. }
  217. $direction = (-1);
  218. if(strpos($dfrn_id,':') == 1) {
  219. $direction = intval(substr($dfrn_id,0,1));
  220. $dfrn_id = substr($dfrn_id,2);
  221. }
  222. $r = q("SELECT * FROM `challenge` WHERE `dfrn-id` = '%s' AND `challenge` = '%s' LIMIT 1",
  223. dbesc($dfrn_id),
  224. dbesc($challenge)
  225. );
  226. if (! dbm::is_result($r)) {
  227. killme();
  228. }
  229. $type = $r[0]['type'];
  230. $last_update = $r[0]['last_update'];
  231. $r = q("DELETE FROM `challenge` WHERE `dfrn-id` = '%s' AND `challenge` = '%s'",
  232. dbesc($dfrn_id),
  233. dbesc($challenge)
  234. );
  235. $sql_extra = '';
  236. switch($direction) {
  237. case (-1):
  238. $sql_extra = sprintf(" AND `issued-id` = '%s' ", dbesc($dfrn_id));
  239. $my_id = $dfrn_id;
  240. break;
  241. case 0:
  242. $sql_extra = sprintf(" AND `issued-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
  243. $my_id = '1:' . $dfrn_id;
  244. break;
  245. case 1:
  246. $sql_extra = sprintf(" AND `dfrn-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
  247. $my_id = '0:' . $dfrn_id;
  248. break;
  249. default:
  250. goaway(z_root());
  251. break; // NOTREACHED
  252. }
  253. $r = q("SELECT * FROM `contact` WHERE `blocked` = 0 AND `pending` = 0 $sql_extra LIMIT 1");
  254. if (! dbm::is_result($r)) {
  255. killme();
  256. }
  257. $contact = $r[0];
  258. $owner_uid = $r[0]['uid'];
  259. $contact_id = $r[0]['id'];
  260. if($type === 'reputation' && strlen($url)) {
  261. $r = q("SELECT * FROM `contact` WHERE `url` = '%s' AND `uid` = %d LIMIT 1",
  262. dbesc($url),
  263. intval($owner_uid)
  264. );
  265. $reputation = 0;
  266. $text = '';
  267. if (dbm::is_result($r)) {
  268. $reputation = $r[0]['rating'];
  269. $text = $r[0]['reason'];
  270. if($r[0]['id'] == $contact_id) { // inquiring about own reputation not allowed
  271. $reputation = 0;
  272. $text = '';
  273. }
  274. }
  275. echo "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
  276. <reputation>
  277. <url>$url</url>
  278. <rating>$reputation</rating>
  279. <description>$text</description>
  280. </reputation>
  281. ";
  282. killme();
  283. // NOTREACHED
  284. }
  285. else {
  286. // Update the writable flag if it changed
  287. logger('dfrn_poll: post request feed: ' . print_r($_POST,true),LOGGER_DATA);
  288. if($dfrn_version >= 2.21) {
  289. if($perm === 'rw')
  290. $writable = 1;
  291. else
  292. $writable = 0;
  293. if($writable != $contact['writable']) {
  294. q("UPDATE `contact` SET `writable` = %d WHERE `id` = %d",
  295. intval($writable),
  296. intval($contact_id)
  297. );
  298. }
  299. }
  300. header("Content-type: application/atom+xml");
  301. $o = dfrn::feed($dfrn_id, $a->argv[1], $last_update, $direction);
  302. echo $o;
  303. killme();
  304. }
  305. }
  306. function dfrn_poll_content(App $a) {
  307. $dfrn_id = ((x($_GET,'dfrn_id')) ? $_GET['dfrn_id'] : '');
  308. $type = ((x($_GET,'type')) ? $_GET['type'] : 'data');
  309. $last_update = ((x($_GET,'last_update')) ? $_GET['last_update'] : '');
  310. $destination_url = ((x($_GET,'destination_url')) ? $_GET['destination_url'] : '');
  311. $sec = ((x($_GET,'sec')) ? $_GET['sec'] : '');
  312. $dfrn_version = ((x($_GET,'dfrn_version')) ? (float) $_GET['dfrn_version'] : 2.0);
  313. $perm = ((x($_GET,'perm')) ? $_GET['perm'] : 'r');
  314. $quiet = ((x($_GET,'quiet')) ? true : false);
  315. $direction = (-1);
  316. if(strpos($dfrn_id,':') == 1) {
  317. $direction = intval(substr($dfrn_id,0,1));
  318. $dfrn_id = substr($dfrn_id,2);
  319. }
  320. if($dfrn_id != '') {
  321. // initial communication from external contact
  322. $hash = random_string();
  323. $status = 0;
  324. $r = q("DELETE FROM `challenge` WHERE `expire` < " . intval(time()));
  325. if($type !== 'profile') {
  326. $r = q("INSERT INTO `challenge` ( `challenge`, `dfrn-id`, `expire` , `type`, `last_update` )
  327. VALUES( '%s', '%s', '%s', '%s', '%s' ) ",
  328. dbesc($hash),
  329. dbesc($dfrn_id),
  330. intval(time() + 60 ),
  331. dbesc($type),
  332. dbesc($last_update)
  333. );
  334. }
  335. $sql_extra = '';
  336. switch($direction) {
  337. case (-1):
  338. if($type === 'profile')
  339. $sql_extra = sprintf(" AND ( `dfrn-id` = '%s' OR `issued-id` = '%s' ) ", dbesc($dfrn_id),dbesc($dfrn_id));
  340. else
  341. $sql_extra = sprintf(" AND `issued-id` = '%s' ", dbesc($dfrn_id));
  342. $my_id = $dfrn_id;
  343. break;
  344. case 0:
  345. $sql_extra = sprintf(" AND `issued-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
  346. $my_id = '1:' . $dfrn_id;
  347. break;
  348. case 1:
  349. $sql_extra = sprintf(" AND `dfrn-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
  350. $my_id = '0:' . $dfrn_id;
  351. break;
  352. default:
  353. goaway(z_root());
  354. break; // NOTREACHED
  355. }
  356. $nickname = $a->argv[1];
  357. $r = q("SELECT `contact`.*, `user`.`username`, `user`.`nickname`
  358. FROM `contact` LEFT JOIN `user` ON `contact`.`uid` = `user`.`uid`
  359. WHERE `contact`.`blocked` = 0 AND `contact`.`pending` = 0
  360. AND `user`.`nickname` = '%s' $sql_extra LIMIT 1",
  361. dbesc($nickname)
  362. );
  363. if (dbm::is_result($r)) {
  364. $challenge = '';
  365. $encrypted_id = '';
  366. $id_str = $my_id . '.' . mt_rand(1000,9999);
  367. if(($r[0]['duplex'] && strlen($r[0]['pubkey'])) || (! strlen($r[0]['prvkey']))) {
  368. openssl_public_encrypt($hash,$challenge,$r[0]['pubkey']);
  369. openssl_public_encrypt($id_str,$encrypted_id,$r[0]['pubkey']);
  370. }
  371. else {
  372. openssl_private_encrypt($hash,$challenge,$r[0]['prvkey']);
  373. openssl_private_encrypt($id_str,$encrypted_id,$r[0]['prvkey']);
  374. }
  375. $challenge = bin2hex($challenge);
  376. $encrypted_id = bin2hex($encrypted_id);
  377. }
  378. else {
  379. $status = 1;
  380. $challenge = '';
  381. $encrypted_id = '';
  382. }
  383. if(($type === 'profile') && (strlen($sec))) {
  384. // URL reply
  385. if($dfrn_version < 2.2) {
  386. $s = fetch_url($r[0]['poll']
  387. . '?dfrn_id=' . $encrypted_id
  388. . '&type=profile-check'
  389. . '&dfrn_version=' . DFRN_PROTOCOL_VERSION
  390. . '&challenge=' . $challenge
  391. . '&sec=' . $sec
  392. );
  393. }
  394. else {
  395. $s = post_url($r[0]['poll'], array(
  396. 'dfrn_id' => $encrypted_id,
  397. 'type' => 'profile-check',
  398. 'dfrn_version' => DFRN_PROTOCOL_VERSION,
  399. 'challenge' => $challenge,
  400. 'sec' => $sec
  401. ));
  402. }
  403. $profile = ((dbm::is_result($r) && $r[0]['nickname']) ? $r[0]['nickname'] : $nickname);
  404. switch($destination_url) {
  405. case 'profile':
  406. $dest = App::get_baseurl() . '/profile/' . $profile . '?f=&tab=profile';
  407. break;
  408. case 'photos':
  409. $dest = App::get_baseurl() . '/photos/' . $profile;
  410. break;
  411. case 'status':
  412. case '':
  413. $dest = App::get_baseurl() . '/profile/' . $profile;
  414. break;
  415. default:
  416. $dest = $destination_url . '?f=&redir=1';
  417. break;
  418. }
  419. logger("dfrn_poll: sec profile: " . $s, LOGGER_DATA);
  420. if(strlen($s) && strstr($s,'<?xml')) {
  421. $xml = parse_xml_string($s);
  422. logger('dfrn_poll: profile: parsed xml: ' . print_r($xml,true), LOGGER_DATA);
  423. logger('dfrn_poll: secure profile: challenge: ' . $xml->challenge . ' expecting ' . $hash);
  424. logger('dfrn_poll: secure profile: sec: ' . $xml->sec . ' expecting ' . $sec);
  425. if(((int) $xml->status == 0) && ($xml->challenge == $hash) && ($xml->sec == $sec)) {
  426. $_SESSION['authenticated'] = 1;
  427. if(! x($_SESSION,'remote'))
  428. $_SESSION['remote'] = array();
  429. $_SESSION['remote'][] = array('cid' => $r[0]['id'],'uid' => $r[0]['uid'],'url' => $r[0]['url']);
  430. $_SESSION['visitor_id'] = $r[0]['id'];
  431. $_SESSION['visitor_home'] = $r[0]['url'];
  432. $_SESSION['visitor_visiting'] = $r[0]['uid'];
  433. if(!$quiet)
  434. info( sprintf(t('%1$s welcomes %2$s'), $r[0]['username'] , $r[0]['name']) . EOL);
  435. // Visitors get 1 day session.
  436. $session_id = session_id();
  437. $expire = time() + 86400;
  438. q("UPDATE `session` SET `expire` = '%s' WHERE `sid` = '%s'",
  439. dbesc($expire),
  440. dbesc($session_id)
  441. );
  442. }
  443. goaway($dest);
  444. }
  445. goaway($dest);
  446. // NOTREACHED
  447. }
  448. else {
  449. // XML reply
  450. header("Content-type: text/xml");
  451. echo '<?xml version="1.0" encoding="UTF-8"?>' . "\r\n"
  452. . '<dfrn_poll>' . "\r\n"
  453. . "\t" . '<status>' . $status . '</status>' . "\r\n"
  454. . "\t" . '<dfrn_version>' . DFRN_PROTOCOL_VERSION . '</dfrn_version>' . "\r\n"
  455. . "\t" . '<dfrn_id>' . $encrypted_id . '</dfrn_id>' . "\r\n"
  456. . "\t" . '<challenge>' . $challenge . '</challenge>' . "\r\n"
  457. . '</dfrn_poll>' . "\r\n" ;
  458. killme();
  459. // NOTREACHED
  460. }
  461. }
  462. }