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