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