Some more bots and false positives
[friendica-addons.git/.git] / jappixmini / jappixmini.php
1 <?php
2
3 /**
4  * Name: jappixmini
5  * Description: Provides a Facebook-like chat using Jappix Mini
6  * Version: 1.0.1
7  * Author: leberwurscht <leberwurscht@hoegners.de>
8  *
9  */
10 //
11 // Copyright 2012 "Leberwurscht" <leberwurscht@hoegners.de>
12 //
13 // This file is dual-licensed under the MIT license (see MIT.txt) and the AGPL license (see jappix/COPYING).
14 //
15
16 /*
17
18   Problem:
19  * jabber password should not be stored on server
20  * jabber password should not be sent between server and browser as soon as the user is logged in
21  * jabber password should not be reconstructible from communication between server and browser as soon as the user is logged in
22
23   Solution:
24   Only store an encrypted version of the jabber password on the server. The encryption key is only available to the browser
25   and not to the server (at least as soon as the user is logged in). It can be stored using the jappix setDB function.
26
27   This encryption key could be the friendica password, but then this password would be stored in the browser in cleartext.
28   It is better to use a hash of the password.
29   The server should not be able to reconstruct the password, so we can't take the same hash the server stores. But we can
30   use hash("some_prefix"+password). This will however not work with OpenID logins, for this type of login the password must
31   be queried manually.
32
33   Problem:
34   How to discover the jabber addresses of the friendica contacts?
35
36   Solution:
37   Each Friendica site with this addon provides a /jappixmini/ module page. We go through our contacts and retrieve
38   this information every week using a cron hook.
39
40   Problem:
41   We do not want to make the jabber address public.
42
43   Solution:
44   When two friendica users connect using DFRN, the relation gets a DFRN ID and a keypair is generated.
45   Using this keypair, we can provide the jabber address only to contacts:
46
47   Alice:
48   signed_address = openssl_*_encrypt(alice_jabber_address)
49   send signed_address to Bob, who does
50   trusted_address = openssl_*_decrypt(signed_address)
51   save trusted_address
52   encrypted_address = openssl_*_encrypt(bob_jabber_address)
53   reply with encrypted_address to Alice, who does
54   decrypted_address = openssl_*_decrypt(encrypted_address)
55   save decrypted_address
56
57   Interface for this:
58   GET /jappixmini/?role=%s&signed_address=%s&dfrn_id=%s
59
60   Response:
61   json({"status":"ok", "encrypted_address":"%s"})
62
63  */
64
65 use Friendica\App;
66 use Friendica\Core\Config;
67 use Friendica\Core\Hook;
68 use Friendica\Core\L10n;
69 use Friendica\Core\Logger;
70 use Friendica\Core\PConfig;
71 use Friendica\Core\Protocol;
72 use Friendica\Database\DBA;
73 use Friendica\Model\User;
74 use Friendica\Util\Network;
75
76 function jappixmini_install()
77 {
78         Hook::register('addon_settings', 'addon/jappixmini/jappixmini.php', 'jappixmini_settings');
79         Hook::register('addon_settings_post', 'addon/jappixmini/jappixmini.php', 'jappixmini_settings_post');
80
81         Hook::register('page_end', 'addon/jappixmini/jappixmini.php', 'jappixmini_script');
82         Hook::register('authenticate', 'addon/jappixmini/jappixmini.php', 'jappixmini_login');
83
84         Hook::register('cron', 'addon/jappixmini/jappixmini.php', 'jappixmini_cron');
85
86         // Jappix source download as required by AGPL
87         Hook::register('about_hook', 'addon/jappixmini/jappixmini.php', 'jappixmini_download_source');
88
89         // set standard configuration
90         $info_text = Config::get("jappixmini", "infotext");
91         if (!$info_text)
92                 Config::set("jappixmini", "infotext", "To get the chat working, you need to know a BOSH host which works with your Jabber account. " .
93                         "An example of a BOSH server that works for all accounts is https://bind.jappix.com/, but keep " .
94                         "in mind that the BOSH server can read along all chat messages. If you know that your Jabber " .
95                         "server also provides an own BOSH server, it is much better to use this one!"
96                 );
97
98         $bosh_proxy = Config::get("jappixmini", "bosh_proxy");
99         if ($bosh_proxy === "") {
100                 Config::set("jappixmini", "bosh_proxy", "1");
101         }
102
103         // set addon version so that safe updates are possible later
104         $addon_version = Config::get("jappixmini", "version");
105         if ($addon_version === "") {
106                 Config::set("jappixmini", "version", "1");
107         }
108 }
109
110 function jappixmini_uninstall()
111 {
112         Hook::unregister('addon_settings', 'addon/jappixmini/jappixmini.php', 'jappixmini_settings');
113         Hook::unregister('addon_settings_post', 'addon/jappixmini/jappixmini.php', 'jappixmini_settings_post');
114
115         Hook::unregister('page_end', 'addon/jappixmini/jappixmini.php', 'jappixmini_script');
116         Hook::unregister('authenticate', 'addon/jappixmini/jappixmini.php', 'jappixmini_login');
117
118         Hook::unregister('cron', 'addon/jappixmini/jappixmini.php', 'jappixmini_cron');
119
120         Hook::unregister('about_hook', 'addon/jappixmini/jappixmini.php', 'jappixmini_download_source');
121 }
122
123 function jappixmini_addon_admin(App $a, &$o)
124 {
125         // display instructions and warnings on addon settings page for admin
126         if (!file_exists("addon/jappixmini.tgz")) {
127                 $o .= '<p><strong style="color:#fff;background-color:#f00">The source archive jappixmini.tgz does not exist. This is probably a violation of the Jappix License (AGPL).</strong></p>';
128         }
129
130         // warn if cron job has not yet been executed
131         $cron_run = Config::get("jappixmini", "last_cron_execution");
132         if (!$cron_run) {
133                 $o .= "<p><strong>Warning: The cron job has not yet been executed. If this message is still there after some time (usually 10 minutes), this means that autosubscribe and autoaccept will not work.</strong></p>";
134         }
135
136         // bosh proxy
137         $bosh_proxy = intval(Config::get("jappixmini", "bosh_proxy"));
138         $bosh_proxy = intval($bosh_proxy) ? ' checked="checked"' : '';
139         $o .= '<label for="jappixmini-proxy">Activate BOSH proxy</label>';
140         $o .= ' <input id="jappixmini-proxy" type="checkbox" name="jappixmini-proxy" value="1"' . $bosh_proxy . ' /><br />';
141
142         // bosh address
143         $bosh_address = Config::get("jappixmini", "bosh_address");
144         $o .= '<p><label for="jappixmini-address">Adress of the default BOSH proxy. If enabled it overrides the user settings:</label><br />';
145         $o .= '<input id="jappixmini-address" type="text" name="jappixmini-address" value="' . $bosh_address . '" /></p>';
146
147         // default server address
148         $default_server = Config::get("jappixmini", "default_server");
149         $o .= '<p><label for="jappixmini-server">Adress of the default jabber server:</label><br />';
150         $o .= '<input id="jappixmini-server" type="text" name="jappixmini-server" value="' . $default_server . '" /></p>';
151
152         // default user name to friendica nickname
153         $default_user = intval(Config::get("jappixmini", "default_user"));
154         $default_user = intval($default_user) ? ' checked="checked"' : '';
155         $o .= '<label for="jappixmini-user">Set the default username to the nickname:</label>';
156         $o .= ' <input id="jappixmini-user" type="checkbox" name="jappixmini-defaultuser" value="1"' . $default_user . ' /><br />';
157
158         // info text field
159         $info_text = Config::get("jappixmini", "infotext");
160         $o .= '<p><label for="jappixmini-infotext">Info text to help users with configuration (important if you want to provide your own BOSH host!):</label><br />';
161         $o .= '<textarea id="jappixmini-infotext" name="jappixmini-infotext" rows="5" cols="50">' . htmlentities($info_text) . '</textarea></p>';
162
163         // submit button
164         $o .= '<input type="submit" name="jappixmini-admin-settings" value="OK" />';
165 }
166
167 function jappixmini_addon_admin_post(App $a)
168 {
169         // set info text
170         $submit = $_REQUEST['jappixmini-admin-settings'];
171         if ($submit) {
172                 $info_text = $_REQUEST['jappixmini-infotext'];
173                 $bosh_proxy = intval($_REQUEST['jappixmini-proxy']);
174                 $default_user = intval($_REQUEST['jappixmini-defaultuser']);
175                 $bosh_address = $_REQUEST['jappixmini-address'];
176                 $default_server = $_REQUEST['jappixmini-server'];
177                 Config::set("jappixmini", "infotext", $info_text);
178                 Config::set("jappixmini", "bosh_proxy", $bosh_proxy);
179                 Config::set("jappixmini", "bosh_address", $bosh_address);
180                 Config::set("jappixmini", "default_server", $default_server);
181                 Config::set("jappixmini", "default_user", $default_user);
182         }
183 }
184
185 function jappixmini_module()
186 {
187
188 }
189
190 function jappixmini_init()
191 {
192         // module page where other Friendica sites can submit Jabber addresses to and also can query Jabber addresses
193         // of local users
194         $dfrn_id = $_REQUEST["dfrn_id"];
195         if (!$dfrn_id) {
196                 exit();
197         }
198
199         $role = $_REQUEST["role"];
200         if ($role == "pub") {
201                 $r = q("SELECT * FROM `contact` WHERE LENGTH(`pubkey`) AND `dfrn-id`='%s' LIMIT 1", DBA::escape($dfrn_id));
202                 if (!count($r)) {
203                         exit();
204                 }
205
206                 $encrypt_func = 'openssl_public_encrypt';
207                 $decrypt_func = 'openssl_public_decrypt';
208                 $key = $r[0]["pubkey"];
209         } else if ($role == "prv") {
210                 $r = q("SELECT * FROM `contact` WHERE LENGTH(`prvkey`) AND `issued-id`='%s' LIMIT 1", DBA::escape($dfrn_id));
211                 if (!count($r)) {
212                         exit();
213                 }
214
215                 $encrypt_func = 'openssl_private_encrypt';
216                 $decrypt_func = 'openssl_private_decrypt';
217                 $key = $r[0]["prvkey"];
218         } else {
219                 exit();
220         }
221
222         $uid = $r[0]["uid"];
223
224         // save the Jabber address we received
225         try {
226                 $signed_address_hex = $_REQUEST["signed_address"];
227                 $signed_address = hex2bin($signed_address_hex);
228
229                 $trusted_address = "";
230                 $decrypt_func($signed_address, $trusted_address, $key);
231
232                 $now = intval(time());
233                 PConfig::set($uid, "jappixmini", "id:$dfrn_id", "$now:$trusted_address");
234         } catch (Exception $e) {
235
236         }
237
238         // do not return an address if user deactivated addon
239         $activated = PConfig::get($uid, 'jappixmini', 'activate');
240         if (!$activated) {
241                 exit();
242         }
243
244         // return the requested Jabber address
245         try {
246                 $username = PConfig::get($uid, 'jappixmini', 'username');
247                 $server = PConfig::get($uid, 'jappixmini', 'server');
248                 $address = "$username@$server";
249
250                 $encrypted_address = "";
251                 $encrypt_func($address, $encrypted_address, $key);
252
253                 $encrypted_address_hex = bin2hex($encrypted_address);
254
255                 $answer = [
256                         "status" => "ok",
257                         "encrypted_address" => $encrypted_address_hex
258                 ];
259
260                 $answer_json = json_encode($answer);
261                 echo $answer_json;
262                 exit();
263         } catch (Exception $e) {
264                 exit();
265         }
266 }
267
268 function jappixmini_settings(App $a, &$s)
269 {
270         // addon settings for a user
271         $activate = PConfig::get(local_user(), 'jappixmini', 'activate');
272         $activate = intval($activate) ? ' checked="checked"' : '';
273         $dontinsertchat = PConfig::get(local_user(), 'jappixmini', 'dontinsertchat');
274         $insertchat = !(intval($dontinsertchat) ? ' checked="checked"' : '');
275
276         $defaultbosh = Config::get("jappixmini", "bosh_address");
277
278         if ($defaultbosh != "") {
279                 PConfig::set(local_user(), 'jappixmini', 'bosh', $defaultbosh);
280         }
281
282         $username = PConfig::get(local_user(), 'jappixmini', 'username');
283         $username = htmlentities($username);
284         $server = PConfig::get(local_user(), 'jappixmini', 'server');
285         $server = htmlentities($server);
286         $bosh = PConfig::get(local_user(), 'jappixmini', 'bosh');
287         $bosh = htmlentities($bosh);
288         $password = PConfig::get(local_user(), 'jappixmini', 'password');
289         $autosubscribe = PConfig::get(local_user(), 'jappixmini', 'autosubscribe');
290         $autosubscribe = intval($autosubscribe) ? ' checked="checked"' : '';
291         $autoapprove = PConfig::get(local_user(), 'jappixmini', 'autoapprove');
292         $autoapprove = intval($autoapprove) ? ' checked="checked"' : '';
293         $encrypt = intval(PConfig::get(local_user(), 'jappixmini', 'encrypt'));
294         $encrypt_checked = $encrypt ? ' checked="checked"' : '';
295         $encrypt_disabled = $encrypt ? '' : ' disabled="disabled"';
296
297         if ($server == "") {
298                 $server = Config::get("jappixmini", "default_server");
299         }
300
301         if (($username == "") && Config::get("jappixmini", "default_user")) {
302                 $username = $a->user["nickname"];
303         }
304
305         $info_text = Config::get("jappixmini", "infotext");
306         $info_text = htmlentities($info_text);
307         $info_text = str_replace("\n", "<br />", $info_text);
308
309         // count contacts
310         $r = q("SELECT COUNT(1) as `cnt` FROM `pconfig` WHERE `uid`=%d AND `cat`='jappixmini' AND `k` LIKE 'id:%%'", local_user());
311         if (count($r)) {
312                 $contact_cnt = $r[0]["cnt"];
313         } else {
314                 $contact_cnt = 0;
315         }
316
317         // count jabber addresses
318         $r = q("SELECT COUNT(1) as `cnt` FROM `pconfig` WHERE `uid`=%d AND `cat`='jappixmini' AND `k` LIKE 'id:%%' AND `v` LIKE '%%@%%'", local_user());
319         if (count($r)) {
320                 $address_cnt = $r[0]["cnt"];
321         } else {
322                 $address_cnt = 0;
323         }
324
325         if (!$activate) {
326                 // load scripts if not yet activated so that password can be saved
327                 $a->page['htmlhead'] .= '<script type="text/javascript" src="' . $a->getBaseURL() . '/addon/jappixmini/jappix/php/get.php?t=js&amp;g=mini.xml"></script>' . "\r\n";
328                 $a->page['htmlhead'] .= '<script type="text/javascript" src="' . $a->getBaseURL() . '/addon/jappixmini/jappix/php/get.php?t=js&amp;f=presence.js~caps.js~name.js~roster.js"></script>' . "\r\n";
329
330                 $a->page['htmlhead'] .= '<script type="text/javascript" src="' . $a->getBaseURL() . '/addon/jappixmini/lib.js"></script>' . "\r\n";
331         }
332
333         $s .= '<span id="settings_jappixmini_inflated" class="settings-block fakelink" style="display: block;" onclick="openClose(\'settings_jappixmini_expanded\'); openClose(\'settings_jappixmini_inflated\');">';
334         $s .= '<h3>' . L10n::t('Jappix Mini') . '</h3>';
335         $s .= '</span>';
336         $s .= '<div id="settings_jappixmini_expanded" class="settings-block" style="display: none;">';
337         $s .= '<span class="fakelink" onclick="openClose(\'settings_jappixmini_expanded\'); openClose(\'settings_jappixmini_inflated\');">';
338         $s .= '<h3>' . L10n::t('Jappix Mini') . '</h3>';
339         $s .= '</span>';
340
341         $s .= '<label for="jappixmini-activate">' . L10n::t('Activate addon') . '</label>';
342         $s .= ' <input id="jappixmini-activate" type="checkbox" name="jappixmini-activate" value="1"' . $activate . ' />';
343         $s .= '<br />';
344         $s .= '<label for"jappixmini-dont-insertchat">' . L10n::t('Do <em>not</em> insert the Jappixmini Chat-Widget into the webinterface') . '</label>';
345         $s .= '<input id="jappixmini-dont-insertchat" type="checkbox" name="jappixmini-dont-insertchat" value="1"' . $insertchat . ' />';
346         $s .= '<br />';
347         $s .= '<label for="jappixmini-username">' . L10n::t('Jabber username') . '</label>';
348         $s .= ' <input id="jappixmini-username" type="text" name="jappixmini-username" value="' . $username . '" />';
349         $s .= '<br />';
350         $s .= '<label for="jappixmini-server">' . L10n::t('Jabber server') . '</label>';
351         $s .= ' <input id="jappixmini-server" type="text" name="jappixmini-server" value="' . $server . '" />';
352         $s .= '<br />';
353
354         if ($defaultbosh == "") {
355                 $s .= '<label for="jappixmini-bosh">' . L10n::t('Jabber BOSH host') . '</label>';
356                 $s .= ' <input id="jappixmini-bosh" type="text" name="jappixmini-bosh" value="' . $bosh . '" />';
357                 $s .= '<br />';
358         }
359
360         $s .= '<label for="jappixmini-password">' . L10n::t('Jabber password') . '</label>';
361         $s .= ' <input type="hidden" id="jappixmini-password" name="jappixmini-encrypted-password" value="' . $password . '" />';
362         $s .= ' <input id="jappixmini-clear-password" type="password" value="" onchange="jappixmini_set_password();" />';
363         $s .= '<br />';
364         $onchange = "document.getElementById('jappixmini-friendica-password').disabled = !this.checked;jappixmini_set_password();";
365         $s .= '<label for="jappixmini-encrypt">' . L10n::t('Encrypt Jabber password with Friendica password (recommended)') . '</label>';
366         $s .= ' <input id="jappixmini-encrypt" type="checkbox" name="jappixmini-encrypt" onchange="' . $onchange . '" value="1"' . $encrypt_checked . ' />';
367         $s .= '<br />';
368         $s .= '<label for="jappixmini-friendica-password">' . L10n::t('Friendica password') . '</label>';
369         $s .= ' <input id="jappixmini-friendica-password" name="jappixmini-friendica-password" type="password" onchange="jappixmini_set_password();" value=""' . $encrypt_disabled . ' />';
370         $s .= '<br />';
371         $s .= '<label for="jappixmini-autoapprove">' . L10n::t('Approve subscription requests from Friendica contacts automatically') . '</label>';
372         $s .= ' <input id="jappixmini-autoapprove" type="checkbox" name="jappixmini-autoapprove" value="1"' . $autoapprove . ' />';
373         $s .= '<br />';
374         $s .= '<label for="jappixmini-autosubscribe">' . L10n::t('Subscribe to Friendica contacts automatically') . '</label>';
375         $s .= ' <input id="jappixmini-autosubscribe" type="checkbox" name="jappixmini-autosubscribe" value="1"' . $autosubscribe . ' />';
376         $s .= '<br />';
377         $s .= '<label for="jappixmini-purge">' . L10n::t('Purge internal list of jabber addresses of contacts') . '</label>';
378         $s .= ' <input id="jappixmini-purge" type="checkbox" name="jappixmini-purge" value="1" />';
379         $s .= '<br />';
380         if ($info_text) {
381                 $s .= '<br />Configuration help:<p style="margin-left:2em;">' . $info_text . '</p>';
382         }
383         $s .= '<br />Status:<p style="margin-left:2em;">Addon knows ' . $address_cnt . ' Jabber addresses of ' . $contact_cnt . ' Friendica contacts (takes some time, usually 10 minutes, to update).</p>';
384         $s .= '<input type="submit" name="jappixmini-submit" value="' . L10n::t('Save Settings') . '" />';
385         $s .= ' <input type="button" value="' . L10n::t('Add contact') . '" onclick="jappixmini_addon_subscribe();" />';
386
387         $s .= '</div>';
388
389         $a->page['htmlhead'] .= "<script type=\"text/javascript\">
390         function jappixmini_set_password() {
391             encrypt = document.getElementById('jappixmini-encrypt').checked;
392             password = document.getElementById('jappixmini-password');
393             clear_password = document.getElementById('jappixmini-clear-password');
394             if (encrypt) {
395                 friendica_password = document.getElementById('jappixmini-friendica-password');
396
397                 if (friendica_password) {
398                     jappixmini_addon_set_client_secret(friendica_password.value);
399                     jappixmini_addon_encrypt_password(clear_password.value, function(encrypted_password){
400                         password.value = encrypted_password;
401                     });
402                 }
403             }
404             else {
405                 password.value = clear_password.value;
406             }
407         }
408
409         jQuery(document).ready(function() {
410             encrypt = document.getElementById('jappixmini-encrypt').checked;
411             password = document.getElementById('jappixmini-password');
412             clear_password = document.getElementById('jappixmini-clear-password');
413             if (encrypt) {
414                 jappixmini_addon_decrypt_password(password.value, function(decrypted_password){
415                     clear_password.value = decrypted_password;
416                 });
417             }
418             else {
419                 clear_password.value = password.value;
420             }
421         });
422     </script>";
423 }
424
425 function jappixmini_settings_post(App $a, &$b)
426 {
427         // save addon settings for a user
428         if (!local_user()) {
429                 return;
430         }
431         $uid = local_user();
432
433         if ($_POST['jappixmini-submit']) {
434                 $encrypt = intval($b['jappixmini-encrypt']);
435                 if ($encrypt) {
436                         // check that Jabber password was encrypted with correct Friendica password
437                         $friendica_password = trim($b['jappixmini-friendica-password']);
438                         if (!User::authenticate((int) $uid, $friendica_password)) {
439                                 info("Wrong friendica password!");
440                                 return;
441                         }
442                 }
443
444                 $purge = intval($b['jappixmini-purge']);
445
446                 $username = trim($b['jappixmini-username']);
447                 $old_username = PConfig::get($uid, 'jappixmini', 'username');
448                 if ($username != $old_username) {
449                         $purge = 1;
450                 }
451
452                 $server = trim($b['jappixmini-server']);
453                 $old_server = PConfig::get($uid, 'jappixmini', 'server');
454                 if ($server != $old_server) {
455                         $purge = 1;
456                 }
457
458                 PConfig::set($uid, 'jappixmini', 'username'      , $username);
459                 PConfig::set($uid, 'jappixmini', 'server'        , $server);
460                 PConfig::set($uid, 'jappixmini', 'bosh'          , trim($b['jappixmini-bosh']));
461                 PConfig::set($uid, 'jappixmini', 'password'      , trim($b['jappixmini-encrypted-password']));
462                 PConfig::set($uid, 'jappixmini', 'autosubscribe' , intval($b['jappixmini-autosubscribe']));
463                 PConfig::set($uid, 'jappixmini', 'autoapprove'   , intval($b['jappixmini-autoapprove']));
464                 PConfig::set($uid, 'jappixmini', 'activate'      , intval($b['jappixmini-activate']));
465                 PConfig::set($uid, 'jappixmini', 'dontinsertchat', intval($b['jappixmini-dont-insertchat']));
466                 PConfig::set($uid, 'jappixmini', 'encrypt'       , $encrypt);
467                 info('Jappix Mini settings saved.');
468
469                 if ($purge) {
470                         q("DELETE FROM `pconfig` WHERE `uid`=$uid AND `cat`='jappixmini' AND `k` LIKE 'id:%%'");
471                         info('List of addresses purged.');
472                 }
473         }
474 }
475
476 function jappixmini_script(App $a)
477 {
478         // adds the script to the page header which starts Jappix Mini
479         if (!local_user()) {
480                 return;
481         }
482
483         if (defaults($_GET, "mode", '') == "minimal") {
484                 return;
485         }
486
487         $activate = PConfig::get(local_user(), 'jappixmini', 'activate');
488         $dontinsertchat = PConfig::get(local_user(), 'jappixmini', 'dontinsertchat');
489         if (!$activate || $dontinsertchat) {
490                 return;
491         }
492
493         $a->page['htmlhead'] .= '<script type="text/javascript" src="' . $a->getBaseURL() . '/addon/jappixmini/jappix/php/get.php?t=js&amp;g=mini.xml"></script>' . "\r\n";
494         $a->page['htmlhead'] .= '<script type="text/javascript" src="' . $a->getBaseURL() . '/addon/jappixmini/jappix/php/get.php?t=js&amp;f=presence.js~caps.js~name.js~roster.js"></script>' . "\r\n";
495
496         $a->page['htmlhead'] .= '<script type="text/javascript" src="' . $a->getBaseURL() . '/addon/jappixmini/lib.js"></script>' . "\r\n";
497
498         $username = PConfig::get(local_user(), 'jappixmini', 'username');
499         $username = str_replace("'", "\\'", $username);
500         $server = PConfig::get(local_user(), 'jappixmini', 'server');
501         $server = str_replace("'", "\\'", $server);
502         $bosh = PConfig::get(local_user(), 'jappixmini', 'bosh');
503         $bosh = str_replace("'", "\\'", $bosh);
504         $encrypt = PConfig::get(local_user(), 'jappixmini', 'encrypt');
505         $encrypt = intval($encrypt);
506         $password = PConfig::get(local_user(), 'jappixmini', 'password');
507         $password = str_replace("'", "\\'", $password);
508
509         $autoapprove = PConfig::get(local_user(), 'jappixmini', 'autoapprove');
510         $autoapprove = intval($autoapprove);
511         $autosubscribe = PConfig::get(local_user(), 'jappixmini', 'autosubscribe');
512         $autosubscribe = intval($autosubscribe);
513
514         // set proxy if necessary
515         $use_proxy = Config::get('jappixmini', 'bosh_proxy');
516         if ($use_proxy) {
517                 $proxy = $a->getBaseURL() . '/addon/jappixmini/proxy.php';
518         } else {
519                 $proxy = "";
520         }
521
522         // get a list of jabber accounts of the contacts
523         $contacts = [];
524         $uid = local_user();
525         $rows = q("SELECT * FROM `pconfig` WHERE `uid`=$uid AND `cat`='jappixmini' AND `k` LIKE 'id:%%'");
526         foreach ($rows as $row) {
527                 $key = $row['k'];
528                 $pos = strpos($key, ":");
529                 $dfrn_id = substr($key, $pos + 1);
530                 $r = q("SELECT `name` FROM `contact` WHERE `uid`=$uid AND (`dfrn-id`='%s' OR `issued-id`='%s')", DBA::escape($dfrn_id), DBA::escape($dfrn_id));
531                 if (count($r))
532                         $name = $r[0]["name"];
533
534                 $value = $row['v'];
535                 $pos = strpos($value, ":");
536                 $address = substr($value, $pos + 1);
537                 if (!$address) {
538                         continue;
539                 }
540                 if (!$name) {
541                         $name = $address;
542                 }
543
544                 $contacts[$address] = $name;
545         }
546         $contacts_json = json_encode($contacts);
547         $contacts_hash = sha1($contacts_json);
548
549         // get nickname
550         $r = q("SELECT `username` FROM `user` WHERE `uid`=$uid");
551         $nickname = json_encode($r[0]["username"]);
552         $groupchats = Config::get('jappixmini', 'groupchats');
553         //if $groupchats has no value jappix_addon_start will produce a syntax error
554         if (empty($groupchats)) {
555                 $groupchats = "{}";
556         }
557
558         // add javascript to start Jappix Mini
559         $a->page['htmlhead'] .= "<script type=\"text/javascript\">
560         jQuery(document).ready(function() {
561            jappixmini_addon_start('$server', '$username', '$proxy', '$bosh', $encrypt, '$password', $nickname, $contacts_json, '$contacts_hash', $autoapprove, $autosubscribe, $groupchats);
562         });
563     </script>";
564
565         return;
566 }
567
568 function jappixmini_login(App $a, &$o)
569 {
570         // create client secret on login to be able to encrypt jabber passwords
571         // for setDB and str_sha1, needed by jappixmini_addon_set_client_secret
572         $a->page['htmlhead'] .= '<script type="text/javascript" src="' . $a->getBaseURL() . '/addon/jappixmini/jappix/php/get.php?t=js&amp;f=datastore.js~jsjac.js"></script>' . "\r\n";
573
574         // for jappixmini_addon_set_client_secret
575         $a->page['htmlhead'] .= '<script type="text/javascript" src="' . $a->getBaseURL() . '/addon/jappixmini/lib.js"></script>' . "\r\n";
576
577         // save hash of password
578         $o = str_replace("<form ", "<form onsubmit=\"jappixmini_addon_set_client_secret(this.elements['id_password'].value);return true;\" ", $o);
579 }
580
581 function jappixmini_cron(App $a, $d)
582 {
583         // For autosubscribe/autoapprove, we need to maintain a list of jabber addresses of our contacts.
584         Config::set("jappixmini", "last_cron_execution", $d);
585
586         // go through list of users with jabber enabled
587         $users = q("SELECT `uid` FROM `pconfig` WHERE `cat`='jappixmini' AND (`k`='autosubscribe' OR `k`='autoapprove') AND `v`='1'");
588         Logger::log("jappixmini: Update list of contacts' jabber accounts for " . count($users) . " users.");
589
590         if (!count($users)) {
591                 return;
592         }
593
594         foreach ($users as $row) {
595                 $uid = $row["uid"];
596
597                 // for each user, go through list of contacts
598                 $contacts = q("SELECT * FROM `contact` WHERE `uid`=%d AND ((LENGTH(`dfrn-id`) AND LENGTH(`pubkey`)) OR (LENGTH(`issued-id`) AND LENGTH(`prvkey`))) AND `network` = '%s'",
599                         intval($uid), DBA::escape(Protocol::DFRN));
600                 foreach ($contacts as $contact_row) {
601                         $request = $contact_row["request"];
602                         if (!$request) {
603                                 continue;
604                         }
605
606                         $dfrn_id = $contact_row["dfrn-id"];
607                         if ($dfrn_id) {
608                                 $key = $contact_row["pubkey"];
609                                 $encrypt_func = 'openssl_public_encrypt';
610                                 $decrypt_func = 'openssl_public_decrypt';
611                                 $role = "prv";
612                         } else {
613                                 $dfrn_id = $contact_row["issued-id"];
614                                 $key = $contact_row["prvkey"];
615                                 $encrypt_func = 'openssl_private_encrypt';
616                                 $decrypt_func = 'openssl_private_decrypt';
617                                 $role = "pub";
618                         }
619
620                         // check if jabber address already present
621                         $present = PConfig::get($uid, "jappixmini", "id:" . $dfrn_id);
622                         $now = intval(time());
623                         if ($present) {
624                                 // $present has format "timestamp:jabber_address"
625                                 $p = strpos($present, ":");
626                                 $timestamp = intval(substr($present, 0, $p));
627
628                                 // do not re-retrieve jabber address if last retrieval
629                                 // is not older than a week
630                                 if ($now - $timestamp < 3600 * 24 * 7) {
631                                         continue;
632                                 }
633                         }
634
635                         // construct base retrieval address
636                         $pos = strpos($request, "/dfrn_request/");
637                         if ($pos === false) {
638                                 continue;
639                         }
640
641                         $base = substr($request, 0, $pos) . "/jappixmini?role=$role";
642
643                         // construct own address
644                         $username = PConfig::get($uid, 'jappixmini', 'username');
645                         if (!$username) {
646                                 continue;
647                         }
648                         $server = PConfig::get($uid, 'jappixmini', 'server');
649                         if (!$server) {
650                                 continue;
651                         }
652
653                         $address = $username . "@" . $server;
654
655                         // sign address
656                         $signed_address = "";
657                         $encrypt_func($address, $signed_address, $key);
658
659                         // construct request url
660                         $signed_address_hex = bin2hex($signed_address);
661                         $url = $base . "&signed_address=$signed_address_hex&dfrn_id=" . urlencode($dfrn_id);
662
663                         try {
664                                 // send request
665                                 $answer_json = Network::fetchUrl($url);
666
667                                 // parse answer
668                                 $answer = json_decode($answer_json);
669                                 if (empty($answer->status) || ($answer->status != "ok")) {
670                                         throw new Exception();
671                                 }
672
673                                 $encrypted_address_hex = $answer->encrypted_address;
674                                 if (!$encrypted_address_hex) {
675                                         throw new Exception();
676                                 }
677
678                                 $encrypted_address = hex2bin($encrypted_address_hex);
679                                 if (!$encrypted_address) {
680                                         throw new Exception();
681                                 }
682
683                                 // decrypt address
684                                 $decrypted_address = "";
685                                 $decrypt_func($encrypted_address, $decrypted_address, $key);
686                                 if (!$decrypted_address) {
687                                         throw new Exception();
688                                 }
689                         } catch (Exception $e) {
690                                 $decrypted_address = "";
691                         }
692
693                         // save address
694                         PConfig::set($uid, "jappixmini", "id:$dfrn_id", "$now:$decrypted_address");
695                 }
696         }
697 }
698
699 function jappixmini_download_source(App $a, &$b)
700 {
701         // Jappix Mini source download link on About page
702         $b .= '<h1>Jappix Mini</h1>';
703         $b .= '<p>This site uses the jappixmini addon, which includes Jappix Mini by the <a href="' . $a->getBaseURL() . '/addon/jappixmini/jappix/AUTHORS">Jappix authors</a> and is distributed under the terms of the <a href="' . $a->getBaseURL() . '/addon/jappixmini/jappix/COPYING">GNU Affero General Public License</a>.</p>';
704         $b .= '<p>You can download the <a href="' . $a->getBaseURL() . '/addon/jappixmini.tgz">source code of the addon</a>. The rest of Friendica is distributed under compatible licenses and can be retrieved from <a href="https://github.com/friendica/friendica">https://github.com/friendica/friendica</a> and <a href="https://github.com/friendica/friendica-addons">https://github.com/friendica/friendica-addons</a></p>';
705 }