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