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