]> git.mxchange.org Git - friendica-addons.git/blob - jappixmini/jappixmini.php
jappixmini: move jappixmini_manage_roster into callback
[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
74
75 function jappixmini_uninstall() {
76 unregister_hook('plugin_settings', 'addon/jappixmini/jappixmini.php', 'jappixmini_settings');
77 unregister_hook('plugin_settings_post', 'addon/jappixmini/jappixmini.php', 'jappixmini_settings_post');
78
79 unregister_hook('page_end', 'addon/jappixmini/jappixmini.php', 'jappixmini_script');
80 unregister_hook('authenticate', 'addon/jappixmini/jappixmini.php', 'jappixmini_login');
81
82 unregister_hook('cron', 'addon/jappixmini/jappixmini.php', 'jappixmini_cron');
83
84 unregister_hook('about_hook', 'addon/jappixmini/jappixmini.php', 'jappixmini_download_source');
85 }
86
87 function jappixmini_plugin_admin(&$a, &$o) {
88         // display instructions and warnings on addon settings page for admin
89
90         if (!file_exists("addon/jappixmini/jappix")) {
91                 $o .= '<p><strong>You need to install the Jappix application, adapted for Friendica (see README).</strong></p>';
92         }
93         else if (!file_exists("addon/jappixmini/jappix.zip")) {
94                 $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>';
95         }
96         else {
97                 $o .= '<p>Jappix is installed.</p>';
98         }
99 }
100
101 function jappixmini_plugin_admin_post(&$a) {
102 }
103
104 function jappixmini_module() {}
105 function jappixmini_init(&$a) {
106         // module page where other Friendica sites can submit Jabber addresses to and also can query Jabber addresses
107         // of local users
108
109         if (!file_exists("addon/jappixmini/jappix")) killme();
110
111         $dfrn_id = $_REQUEST["dfrn_id"];
112         if (!$dfrn_id) killme();
113
114         $role = $_REQUEST["role"];
115         if ($role=="pub") {
116                 $r = q("SELECT * FROM `contact` WHERE LENGTH(`pubkey`) AND `dfrn-id`='%s' LIMIT 1",
117                         dbesc($dfrn_id)
118                 );
119                 if (!count($r)) killme();
120
121                 $encrypt_func = openssl_public_encrypt;
122                 $decrypt_func = openssl_public_decrypt;
123                 $key = $r[0]["pubkey"];
124         } else if ($role=="prv") {
125                 $r = q("SELECT * FROM `contact` WHERE LENGTH(`prvkey`) AND `issued-id`='%s' LIMIT 1",
126                         dbesc($dfrn_id)
127                 );
128                 if (!count($r)) killme();
129
130                 $encrypt_func = openssl_private_encrypt;
131                 $decrypt_func = openssl_private_decrypt;
132                 $key = $r[0]["prvkey"];
133         } else {
134                 killme();
135         }
136
137         $uid = $r[0]["uid"];
138
139         // save the Jabber address we received
140         try {
141                 $signed_address_hex = $_REQUEST["signed_address"];
142                 $signed_address = hex2bin($signed_address_hex);
143
144                 $trusted_address = "";
145                 $decrypt_func($signed_address, $trusted_address, $key);
146
147                 $now = intval(time());
148                 set_pconfig($uid, "jappixmini", "id:$dfrn_id", "$now:$trusted_address");
149         } catch (Exception $e) {
150         }
151
152         // do not return an address if user deactivated plugin
153         $activated = get_pconfig($uid, 'jappixmini', 'activate');
154         if (!$activated) killme();
155
156         // return the requested Jabber address
157         try {
158                 $username = get_pconfig($uid, 'jappixmini', 'username');
159                 $server = get_pconfig($uid, 'jappixmini', 'server');
160                 $address = "$username@$server";
161
162                 $encrypted_address = "";
163                 $encrypt_func($address, $encrypted_address, $key);
164
165                 $encrypted_address_hex = bin2hex($encrypted_address);
166
167                 $answer = Array(
168                         "status"=>"ok",
169                         "encrypted_address"=>$encrypted_address_hex
170                 );
171
172                 $answer_json = json_encode($answer);
173                 echo $answer_json;
174                 killme();
175         } catch (Exception $e) {
176                 killme();
177         }
178 }
179
180 function jappixmini_settings(&$a, &$s) {
181     // addon settings for a user
182
183     if (!file_exists("addon/jappixmini/jappix")) return;
184
185     $activate = get_pconfig(local_user(),'jappixmini','activate');
186     $activate = intval($activate) ? ' checked="checked"' : '';
187
188     $username = get_pconfig(local_user(),'jappixmini','username');
189     $username = htmlentities($username);
190     $server = get_pconfig(local_user(),'jappixmini','server');
191     $server = htmlentities($server);
192     $bosh = get_pconfig(local_user(),'jappixmini','bosh');
193     $bosh = htmlentities($bosh);
194     $password = get_pconfig(local_user(),'jappixmini','password');
195     $autosubscribe = get_pconfig(local_user(),'jappixmini','autosubscribe');
196     $autosubscribe = intval($autosubscribe) ? ' checked="checked"' : '';
197     $autoapprove = get_pconfig(local_user(),'jappixmini','autoapprove');
198     $autoapprove = intval($autoapprove) ? ' checked="checked"' : '';
199     $encrypt = intval(get_pconfig(local_user(),'jappixmini','encrypt'));
200     $encrypt_checked = $encrypt ? ' checked="checked"' : '';
201     $encrypt_disabled = $encrypt ? '' : ' disabled="disabled"';
202
203     if (!$activate) {
204         // load scripts if not yet activated so that password can be saved
205         $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";
206         $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";
207
208         $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";
209         $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";
210         $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";
211
212         $a->page['htmlhead'] .= '<script type="text/javascript" src="' . $a->get_baseurl() . '/addon/jappixmini/lib.js"></script>'."\r\n";
213     }
214
215     $s .= '<div class="settings-block">';
216
217     $s .= '<h3>Jappix Mini addon settings</h3>';
218     $s .= '<div>';
219     $s .= '<label for="jappixmini-activate">Activate addon</label>';
220     $s .= ' <input id="jappixmini-activate" type="checkbox" name="jappixmini-activate" value="1"'.$activate.' />';
221     $s .= '<br />';
222     $s .= '<label for="jappixmini-username">Jabber username</label>';
223     $s .= ' <input id="jappixmini-username" type="text" name="jappixmini-username" value="'.$username.'" />';
224     $s .= '<br />';
225     $s .= '<label for="jappixmini-server">Jabber server</label>';
226     $s .= ' <input id="jappixmini-server" type="text" name="jappixmini-server" value="'.$server.'" />';
227     $s .= '<br />';
228
229     $conf = file_get_contents("addon/jappixmini/jappix/store/conf/main.xml");
230     preg_match("/<bosh_proxy>(.*)<\/bosh_proxy>/", $conf, $matches);
231     if ($matches[1]=="on") {
232         $s .= '<label for="jappixmini-bosh">Jabber BOSH host</label>';
233         $s .= ' <input id="jappixmini-bosh" type="text" name="jappixmini-bosh" value="'.$bosh.'" />';
234         $s .= '<br />';
235     }
236
237     $s .= '<label for="jappixmini-password">Jabber password</label>';
238     $s .= ' <input type="hidden" id="jappixmini-password" name="jappixmini-encrypted-password" value="'.$password.'" />';
239     $s .= ' <input id="jappixmini-clear-password" type="password" value="" onchange="jappixmini_set_password();" />';
240     $s .= '<br />';
241     $onchange = "document.getElementById('jappixmini-friendica-password').disabled = !this.checked;jappixmini_set_password();";
242     $s .= '<label for="jappixmini-encrypt">Encrypt Jabber password with Friendica password (recommended)</label>';
243     $s .= ' <input id="jappixmini-encrypt" type="checkbox" name="jappixmini-encrypt" onchange="'.$onchange.'" value="1"'.$encrypt_checked.' />';
244     $s .= '<br />';
245     $s .= '<label for="jappixmini-friendica-password">Friendica password</label>';
246     $s .= ' <input id="jappixmini-friendica-password" name="jappixmini-friendica-password" type="password" onchange="jappixmini_set_password();" value=""'.$encrypt_disabled.' />';
247     $s .= '<br />';
248     $s .= '<label for="jappixmini-autoapprove">Approve subscription requests from Friendica contacts automatically</label>';
249     $s .= ' <input id="jappixmini-autoapprove" type="checkbox" name="jappixmini-autoapprove" value="1"'.$autoapprove.' />';
250     $s .= '<br />';
251     $s .= '<label for="jappixmini-autosubscribe">Subscribe to Friendica contacts automatically</label>';
252     $s .= ' <input id="jappixmini-autosubscribe" type="checkbox" name="jappixmini-autosubscribe" value="1"'.$autosubscribe.' />';
253     $s .= '<br />';
254     $s .= '<label for="jappixmini-purge">Purge internal list of jabber addresses of contacts</label>';
255     $s .= ' <input id="jappixmini-purge" type="checkbox" name="jappixmini-purge" value="1" />';
256     $s .= '<br />';
257     $s .= '<input type="submit" name="jappixmini-submit" value="' . t('Submit') . '" />';
258     $s .= ' <input type="button" value="Add contact" onclick="jappixmini_addon_subscribe();" />';
259     $s .= '</div>';
260
261     $s .= '</div>';
262
263     $a->page['htmlhead'] .= "<script type=\"text/javascript\">
264         function jappixmini_set_password() {
265             encrypt = document.getElementById('jappixmini-encrypt').checked;
266             password = document.getElementById('jappixmini-password');
267             clear_password = document.getElementById('jappixmini-clear-password');
268             if (encrypt) {
269                 friendica_password = document.getElementById('jappixmini-friendica-password');
270
271                 if (friendica_password) {
272                     jappixmini_addon_set_client_secret(friendica_password.value);
273                     jappixmini_addon_encrypt_password(clear_password.value, function(encrypted_password){
274                         password.value = encrypted_password;
275                     });
276                 }
277             }
278             else {
279                 password.value = clear_password.value;
280             }
281         }
282
283         jQuery(document).ready(function() {
284             encrypt = document.getElementById('jappixmini-encrypt').checked;
285             password = document.getElementById('jappixmini-password');
286             clear_password = document.getElementById('jappixmini-clear-password');
287             if (encrypt) {
288                 jappixmini_addon_decrypt_password(password.value, function(decrypted_password){
289                     clear_password.value = decrypted_password;
290                 });
291             }
292             else {
293                 clear_password.value = password.value;
294             }
295         });
296     </script>";
297 }
298
299 function jappixmini_settings_post(&$a,&$b) {
300         // save addon settings for a user
301
302         if (!file_exists("addon/jappixmini/jappix")) return;
303
304         if(! local_user()) return;
305         $uid = local_user();
306
307         if($_POST['jappixmini-submit']) {
308                 $encrypt = intval($b['jappixmini-encrypt']);
309                 if ($encrypt) {
310                         // check that Jabber password was encrypted with correct Friendica password
311                         $friendica_password = trim($b['jappixmini-friendica-password']);
312                         $encrypted = hash('whirlpool',$friendica_password);
313                         $r = q("SELECT * FROM `user` WHERE `uid`=$uid AND `password`='%s'",
314                                 dbesc($encrypted)
315                         );
316                         if (!count($r)) {
317                                 info("Wrong friendica password!");
318                                 return;
319                         }
320                 }
321
322                 $purge = intval($b['jappixmini-purge']);
323
324                 $username = trim($b['jappixmini-username']);
325                 $old_username = get_pconfig($uid,'jappixmini','username');
326                 if ($username!=$old_username) $purge = 1;
327
328                 $server = trim($b['jappixmini-server']);
329                 $old_server = get_pconfig($uid,'jappixmini','server');
330                 if ($server!=$old_server) $purge = 1;
331
332                 set_pconfig($uid,'jappixmini','username',$username);
333                 set_pconfig($uid,'jappixmini','server',$server);
334                 set_pconfig($uid,'jappixmini','bosh',trim($b['jappixmini-bosh']));
335                 set_pconfig($uid,'jappixmini','password',trim($b['jappixmini-encrypted-password']));
336                 set_pconfig($uid,'jappixmini','autosubscribe',intval($b['jappixmini-autosubscribe']));
337                 set_pconfig($uid,'jappixmini','autoapprove',intval($b['jappixmini-autoapprove']));
338                 set_pconfig($uid,'jappixmini','activate',intval($b['jappixmini-activate']));
339                 set_pconfig($uid,'jappixmini','encrypt',$encrypt);
340                 info( 'Jappix Mini settings saved.' );
341
342                 if ($purge) {
343                         q("DELETE FROM `pconfig` WHERE `uid`=$uid AND `cat`='jappixmini' and `k` LIKE 'id%%'");
344                         info( 'List of addresses purged.' );
345                 }
346         }
347 }
348
349 function jappixmini_script(&$a,&$s) {
350     // adds the script to the page header which starts Jappix Mini
351
352     if (!file_exists("addon/jappixmini/jappix")) return;
353     if(! local_user()) return;
354
355     $activate = get_pconfig(local_user(),'jappixmini','activate');
356     if (!$activate) return;
357
358     $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";
359     $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";
360
361     $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";
362     $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";
363     $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";
364
365     $a->page['htmlhead'] .= '<script type="text/javascript" src="' . $a->get_baseurl() . '/addon/jappixmini/lib.js"></script>'."\r\n";
366
367     $username = get_pconfig(local_user(),'jappixmini','username');
368     $username = str_replace("'", "\\'", $username);
369     $server = get_pconfig(local_user(),'jappixmini','server');
370     $server = str_replace("'", "\\'", $server);
371     $bosh = get_pconfig(local_user(),'jappixmini','bosh');
372     $bosh = str_replace("'", "\\'", $bosh);
373     $encrypt = get_pconfig(local_user(),'jappixmini','encrypt');
374     $encrypt = intval($encrypt);
375     $password = get_pconfig(local_user(),'jappixmini','password');
376     $password = str_replace("'", "\\'", $password);
377
378     $autoapprove = get_pconfig(local_user(),'jappixmini','autoapprove');
379     $autoapprove = intval($autoapprove);
380     $autosubscribe = get_pconfig(local_user(),'jappixmini','autosubscribe');
381     $autosubscribe = intval($autosubscribe);
382
383     // deactivate bosh host if proxy is off
384     $conf = file_get_contents("addon/jappixmini/jappix/store/conf/main.xml");
385     preg_match("/<bosh_proxy>(.*)<\/bosh_proxy>/", $conf, $matches);
386     if ($matches[1]!="on") {
387         $bosh = '';
388     }
389
390     // get a list of jabber accounts of the contacts
391     $contacts = Array();
392     $uid = local_user();
393     $rows = q("SELECT * FROM `pconfig` WHERE `uid`=$uid AND `cat`='jappixmini' and `k` LIKE 'id%%'");
394     foreach ($rows as $row) {
395         $key = $row['k'];
396         $pos = strpos($key, ":");
397         $dfrn_id = substr($key, $pos+1);
398         $r = q("SELECT `name` FROM `contact` WHERE `uid`=$uid AND `dfrn-id`='%s' OR `issued-id`='%s'",
399                 dbesc($dfrn_id),
400                 dbesc($dfrn_id)
401         );
402         $name = $r[0]["name"];
403
404         $value = $row['v'];
405         $pos = strpos($value, ":");
406         $address = substr($value, $pos+1);
407         if (!$address) continue;
408         if (!$name) $name = $address;
409
410         $contacts[$address] = $name;
411     }
412     $contacts_json = json_encode($contacts);
413
414     // get nickname
415     $r = q("SELECT `username` FROM `user` WHERE `uid`=$uid");
416     $nickname = json_encode($r[0]["username"]);
417
418     // add javascript to start Jappix Mini
419     $a->page['htmlhead'] .= "<script type=\"text/javascript\">
420         jQuery(document).ready(function() {
421            jappixmini_addon_start('$server', '$username', '$bosh', $encrypt, '$password', $nickname, $contacts_json, $autoapprove, $autosubscribe);
422         });
423     </script>";
424
425     return;
426 }
427
428 function jappixmini_login(&$a, &$o) {
429     // create client secret on login to be able to encrypt jabber passwords
430
431     if (!file_exists("addon/jappixmini/jappix")) return;
432
433     // for setDB, needed by jappixmini_addon_set_client_secret
434     $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";
435
436     // for str_sha1, needed by jappixmini_addon_set_client_secret
437     $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";
438
439     // for jappixmini_addon_set_client_secret
440     $a->page['htmlhead'] .= '<script type="text/javascript" src="' . $a->get_baseurl() . '/addon/jappixmini/lib.js"></script>'."\r\n";
441
442     // save hash of password
443     $o = str_replace("<form ", "<form onsubmit=\"jappixmini_addon_set_client_secret(this.elements['id_password'].value);return true;\" ", $o);
444 }
445
446 function jappixmini_cron(&$a, $d) {
447         // For autosubscribe/autoapprove, we need to maintain a list of jabber addresses of our contacts.
448
449         if (!file_exists("addon/jappixmini/jappix")) return;
450
451         // go through list of users with jabber enabled
452         $users = q("SELECT `uid` FROM `pconfig` WHERE `cat`='jappixmini' AND (`k`='autosubscribe' OR `k`='autoapprove') AND `v`='1'");
453
454         foreach ($users as $row) {
455                 $uid = $row["uid"];
456
457                 // for each user, go through list of contacts
458                 $contacts = q("SELECT * FROM `contact` WHERE `uid`=%d AND ((LENGTH(`dfrn-id`) AND LENGTH(`pubkey`)) OR (LENGTH(`issued-id`) AND LENGTH(`prvkey`)))", intval($uid));
459                 foreach ($contacts as $contact_row) {
460                         $request = $contact_row["request"];
461                         if (!$request) continue;
462
463                         $dfrn_id = $contact_row["dfrn-id"];
464                         if ($dfrn_id) {
465                                 $key = $contact_row["pubkey"];
466                                 $encrypt_func = openssl_public_encrypt;
467                                 $decrypt_func = openssl_public_decrypt;
468                                 $role = "prv";
469                         } else {
470                                 $dfrn_id = $contact_row["issued-id"];
471                                 $key = $contact_row["prvkey"];
472                                 $encrypt_func = openssl_private_encrypt;
473                                 $decrypt_func = openssl_private_decrypt;
474                                 $role = "pub";
475                         }
476
477                         // check if jabber address already present
478                         $present = get_pconfig($uid, "jappixmini", "id:".$dfrn_id);
479                         $now = intval(time());
480                         if ($present) {
481                                 // $present has format "timestamp:jabber_address"
482                                 $p = strpos($present, ":");
483                                 $timestamp = intval(substr($present, 0, $p));
484
485                                 // do not re-retrieve jabber address if last retrieval
486                                 // is not older than a week
487                                 if ($now-$timestamp<3600*24*7) continue;
488                         }
489
490                         // construct base retrieval address
491                         $pos = strpos($request, "/dfrn_request/");
492                         if ($pos===false) continue;
493
494                         $base = substr($request, 0, $pos)."/jappixmini?role=$role";
495
496                         // construct own address
497                         $username = get_pconfig($uid, 'jappixmini', 'username');
498                         if (!$username) continue;
499                         $server = get_pconfig($uid, 'jappixmini', 'server');
500                         if (!$server) continue;
501
502                         $address = $username."@".$server;
503
504                         // sign address
505                         $signed_address = "";
506                         $encrypt_func($address, $signed_address, $key);
507
508                         // construct request url
509                         $signed_address_hex = bin2hex($signed_address);
510                         $url = $base."&signed_address=$signed_address_hex&dfrn_id=".urlencode($dfrn_id);
511
512                         try {
513                                 // send request
514                                 $answer_json = fetch_url($url);
515
516                                 // parse answer
517                                 $answer = json_decode($answer_json);
518                                 if ($answer->status != "ok") throw new Exception();
519
520                                 $encrypted_address_hex = $answer->encrypted_address;
521                                 if (!$encrypted_address_hex) throw new Exception();
522
523                                 $encrypted_address = hex2bin($encrypted_address_hex);
524                                 if (!$encrypted_address) throw new Exception();
525
526                                 // decrypt address
527                                 $decrypted_address = "";
528                                 $decrypt_func($encrypted_address, $decrypted_address, $key);
529                                 if (!$decrypted_address) throw new Exception();
530                         } catch (Exception $e) {
531                                 $decrypted_address = "";
532                         }
533
534                         // save address
535                         set_pconfig($uid, "jappixmini", "id:$dfrn_id", "$now:$decrypted_address");
536                 }
537         }
538 }
539
540 function jappixmini_download_source(&$a,&$b) {
541         // Jappix Mini source download link on About page
542
543         if (!file_exists("addon/jappixmini/jappix")) return;
544
545         $b .= '<h1>Jappix Mini</h1>';
546         $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>';
547         $b .= '<p>You can download the <a href="'.$a->get_baseurl().'/addon/jappixmini/jappix.zip">source code</a>.</p>';
548 }