]> git.mxchange.org Git - friendica-addons.git/blob - jappixmini/jappixmini.php
08ba248d1e7515525e6f15e828ba794eeb59aa5d
[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 }
97
98 function jappixmini_plugin_admin_post(&$a) {
99 }
100
101 function jappixmini_module() {}
102 function jappixmini_init(&$a) {
103         // module page where other Friendica sites can submit Jabber addresses to and also can query Jabber addresses
104         // of local users
105
106         $dfrn_id = $_REQUEST["dfrn_id"];
107         if (!$dfrn_id) killme();
108
109         $role = $_REQUEST["role"];
110         if ($role=="pub") {
111                 $r = q("SELECT * FROM `contact` WHERE LENGTH(`pubkey`) AND `dfrn-id`='%s' LIMIT 1",
112                         dbesc($dfrn_id)
113                 );
114                 if (!count($r)) killme();
115
116                 $encrypt_func = openssl_public_encrypt;
117                 $decrypt_func = openssl_public_decrypt;
118                 $key = $r[0]["pubkey"];
119         } else if ($role=="prv") {
120                 $r = q("SELECT * FROM `contact` WHERE LENGTH(`prvkey`) AND `issued-id`='%s' LIMIT 1",
121                         dbesc($dfrn_id)
122                 );
123                 if (!count($r)) killme();
124
125                 $encrypt_func = openssl_private_encrypt;
126                 $decrypt_func = openssl_private_decrypt;
127                 $key = $r[0]["prvkey"];
128         } else {
129                 killme();
130         }
131
132         $uid = $r[0]["uid"];
133
134         // save the Jabber address we received
135         try {
136                 $signed_address_hex = $_REQUEST["signed_address"];
137                 $signed_address = hex2bin($signed_address_hex);
138
139                 $trusted_address = "";
140                 $decrypt_func($signed_address, $trusted_address, $key);
141
142                 $now = intval(time());
143                 set_pconfig($uid, "jappixmini", "id:$dfrn_id", "$now:$trusted_address");
144         } catch (Exception $e) {
145         }
146
147         // return the requested Jabber address
148         try {
149                 $username = get_pconfig($uid, 'jappixmini', 'username');
150                 $server = get_pconfig($uid, 'jappixmini', 'server');
151                 $address = "$username@$server";
152
153                 $encrypted_address = "";
154                 $encrypt_func($address, $encrypted_address, $key);
155
156                 $encrypted_address_hex = bin2hex($encrypted_address);
157
158                 $answer = Array(
159                         "status"=>"ok",
160                         "encrypted_address"=>$encrypted_address_hex
161                 );
162
163                 $answer_json = json_encode($answer);
164                 echo $answer_json;
165                 killme();
166         } catch (Exception $e) {
167                 killme();
168         }
169 }
170
171 function jappixmini_settings(&$a, &$s) {
172     // addon settings for a user
173
174     $activate = get_pconfig(local_user(),'jappixmini','activate');
175     $activate = intval($activate) ? ' checked="checked"' : '';
176
177     $username = get_pconfig(local_user(),'jappixmini','username');
178     $username = htmlentities($username);
179     $server = get_pconfig(local_user(),'jappixmini','server');
180     $server = htmlentities($server);
181     $bosh = get_pconfig(local_user(),'jappixmini','bosh');
182     $bosh = htmlentities($bosh);
183     $encrypted_password = get_pconfig(local_user(),'jappixmini','encrypted-password');
184     $autosubscribe = get_pconfig(local_user(),'jappixmini','autosubscribe');
185     $autosubscribe = intval($autosubscribe) ? ' checked="checked"' : '';
186     $autoapprove = get_pconfig(local_user(),'jappixmini','autoapprove');
187     $autoapprove = intval($autoapprove) ? ' checked="checked"' : '';
188
189     $s .= '<div class="settings-block">';
190     $s .= '<h3>Jappix Mini addon settings</h3>';
191     $s .= '<div>';
192     $s .= '<label for="jappixmini-activate">Activate addon</label>';
193     $s .= ' <input id="jappixmini-activate" type="checkbox" name="jappixmini-activate" value="1"'.$activate.' />';
194     $s .= '<br />';
195     $s .= '<label for="jappixmini-username">Jabber username</label>';
196     $s .= ' <input id="jappixmini-username" type="text" name="jappixmini-username" value="'.$username.'" />';
197     $s .= '<br />';
198     $s .= '<label for="jappixmini-server">Jabber server</label>';
199     $s .= ' <input id="jappixmini-server" type="text" name="jappixmini-server" value="'.$server.'" />';
200     $s .= '<br />';
201     $s .= '<label for="jappixmini-bosh">Jabber BOSH host</label>';
202     $s .= ' <input id="jappixmini-bosh" type="text" name="jappixmini-bosh" value="'.$bosh.'" />';
203     $s .= '<br />';
204     $s .= '<label for="jappixmini-password">Jabber password</label>';
205     $s .= ' <input type="hidden" id="jappixmini-encrypted-password" name="jappixmini-encrypted-password" value="'.$encrypted_password.'" />';
206     $onchange = "document.getElementById('jappixmini-encrypted-password').value = jappixmini_addon_encrypt_password(document.getElementById('jappixmini-password').value);";
207     $s .= ' <input id="jappixmini-password" type="password" value="" onchange="'.$onchange.'" />';
208     $s .= '<br />';
209     $s .= '<label for="jappixmini-autoapprove">Approve subscription requests from Friendica contacts automatically</label>';
210     $s .= ' <input id="jappixmini-autoapprove" type="checkbox" name="jappixmini-autoapprove" value="1"'.$autoapprove.' />';
211     $s .= '<br />';
212     $s .= '<label for="jappixmini-autosubscribe">Subscribe to Friendica contacts automatically</label>';
213     $s .= ' <input id="jappixmini-autosubscribe" type="checkbox" name="jappixmini-autosubscribe" value="1"'.$autosubscribe.' />';
214     $s .= '<br />';
215     $s .= '<label for="jappixmini-purge">Purge internal list of jabber addresses of contacts</label>';
216     $s .= ' <input id="jappixmini-purge" type="checkbox" name="jappixmini-purge" value="1" />';
217     $s .= '<br />';
218     $s .= '<input type="submit" name="jappixmini-submit" value="' . t('Submit') . '" />';
219     $s .= '</div>';
220
221     $a->page['htmlhead'] .= "<script type=\"text/javascript\">
222         jQuery(document).ready(function() {
223             document.getElementById('jappixmini-password').value = jappixmini_addon_decrypt_password('$encrypted_password');
224         });
225     </script>";
226 }
227
228 function jappixmini_settings_post(&$a,&$b) {
229         // save addon settings for a user
230
231         if(! local_user()) return;
232         $uid = local_user();
233
234         if($_POST['jappixmini-submit']) {
235                 $purge = intval($b['jappixmini-purge']);
236
237                 $username = trim($b['jappixmini-username']);
238                 $old_username = get_pconfig($uid,'jappixmini','username');
239                 if ($username!=$old_username) $purge = 1;
240
241                 $server = trim($b['jappixmini-server']);
242                 $old_server = get_pconfig($uid,'jappixmini','server');
243                 if ($server!=$old_server) $purge = 1;
244
245                 set_pconfig($uid,'jappixmini','username',$username);
246                 set_pconfig($uid,'jappixmini','server',$server);
247                 set_pconfig($uid,'jappixmini','bosh',trim($b['jappixmini-bosh']));
248                 set_pconfig($uid,'jappixmini','encrypted-password',trim($b['jappixmini-encrypted-password']));
249                 set_pconfig($uid,'jappixmini','autosubscribe',intval($b['jappixmini-autosubscribe']));
250                 set_pconfig($uid,'jappixmini','autoapprove',intval($b['jappixmini-autoapprove']));
251                 set_pconfig($uid,'jappixmini','activate',intval($b['jappixmini-activate']));
252                 info( 'Jappix Mini settings saved.' );
253
254                 if ($purge) {
255                         q("DELETE FROM `pconfig` WHERE `uid`=$uid AND `cat`='jappixmini' and `k` LIKE 'id%%'");
256                         info( 'List of addresses purged.' );
257                 }
258         }
259 }
260
261 function jappixmini_script(&$a,&$s) {
262     // adds the script to the page header which starts Jappix Mini
263
264     if(! local_user()) return;
265
266     $activate = get_pconfig(local_user(),'jappixmini','activate');
267     if (!$activate) return;
268
269     $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";
270     $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";
271
272     $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";
273     $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";
274     $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";
275
276     $a->page['htmlhead'] .= '<script type="text/javascript" src="' . $a->get_baseurl() . '/addon/jappixmini/lib.js"></script>'."\r\n";
277
278     $username = get_pconfig(local_user(),'jappixmini','username');
279     $username = str_replace("'", "\\'", $username);
280     $server = get_pconfig(local_user(),'jappixmini','server');
281     $server = str_replace("'", "\\'", $server);
282     $bosh = get_pconfig(local_user(),'jappixmini','bosh');
283     $bosh = str_replace("'", "\\'", $bosh);
284     $encrypted_password = get_pconfig(local_user(),'jappixmini','encrypted-password');
285     $encrypted_password = str_replace("'", "\\'", $encrypted_password);
286
287     $autoapprove = get_pconfig(local_user(),'jappixmini','autoapprove');
288     $autoapprove = intval($autoapprove);
289     $autosubscribe = get_pconfig(local_user(),'jappixmini','autosubscribe');
290     $autosubscribe = intval($autosubscribe);
291
292     // get a list of jabber accounts of the contacts
293     $contacts = Array();
294     $uid = local_user();
295     $rows = q("SELECT * FROM `pconfig` WHERE `uid`=$uid AND `cat`='jappixmini' and `k` LIKE 'id%%'");
296     foreach ($rows as $row) {
297         $key = $row['k'];
298         $pos = strpos($key, ":");
299         $dfrn_id = substr($key, $pos+1);
300         $r = q("SELECT `name` FROM `contact` WHERE `uid`=$uid AND `dfrn-id`='%s' OR `issued-id`='%s'",
301                 dbesc($dfrn_id),
302                 dbesc($dfrn_id)
303         );
304         $name = $r[0]["name"];
305
306         $value = $row['v'];
307         $pos = strpos($value, ":");
308         $address = substr($value, $pos+1);
309         if (!$address) continue;
310         if (!$name) $name = $address;
311
312         $contacts[$address] = $name;
313     }
314     $contacts_json = json_encode($contacts);
315
316     // get nickname
317     $r = q("SELECT `username` FROM `user` WHERE `uid`=$uid");
318     $nickname = json_encode($r[0]["username"]);
319
320     // add javascript to start Jappix Mini
321     $a->page['htmlhead'] .= "<script type=\"text/javascript\">
322         jQuery(document).ready(function() {
323            jappixmini_addon_start('$server', '$username', '$bosh', '$encrypted_password', $nickname);
324            jappixmini_manage_roster($contacts_json, $autoapprove, $autosubscribe);
325         });
326     </script>";
327
328     return;
329 }
330
331 function jappixmini_login(&$a, &$o) {
332     // for setDB, needed by jappixmini_addon_set_client_secret
333     $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";
334
335     // for str_sha1, needed by jappixmini_addon_set_client_secret
336     $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";
337
338     // for jappixmini_addon_set_client_secret
339     $a->page['htmlhead'] .= '<script type="text/javascript" src="' . $a->get_baseurl() . '/addon/jappixmini/lib.js"></script>'."\r\n";
340
341     // save hash of password
342     $o = str_replace("<form ", "<form onsubmit=\"jappixmini_addon_set_client_secret(this.elements['id_password'].value);return true;\" ", $o);
343 }
344
345 function jappixmini_cron(&$a, $d) {
346         // For autosubscribe/autoapprove, we need to maintain a list of jabber addresses of our contacts.
347
348         // go through list of users with jabber enabled
349         $users = q("SELECT `uid` FROM `pconfig` WHERE `cat`='jappixmini' AND (`k`='autosubscribe' OR `k`='autoapprove') AND `v`='1'");
350
351         foreach ($users as $row) {
352                 $uid = $row["uid"];
353
354                 // for each user, go through list of contacts
355                 $contacts = q("SELECT * FROM `contact` WHERE `uid`=%d AND ((LENGTH(`dfrn-id`) AND LENGTH(`pubkey`)) OR (LENGTH(`issued-id`) AND LENGTH(`prvkey`)))", intval($uid));
356                 foreach ($contacts as $contact_row) {
357                         $request = $contact_row["request"];
358                         if (!$request) continue;
359
360                         $dfrn_id = $contact_row["dfrn-id"];
361                         if ($dfrn_id) {
362                                 $key = $contact_row["pubkey"];
363                                 $encrypt_func = openssl_public_encrypt;
364                                 $decrypt_func = openssl_public_decrypt;
365                                 $role = "prv";
366                         } else {
367                                 $dfrn_id = $contact_row["issued-id"];
368                                 $key = $contact_row["prvkey"];
369                                 $encrypt_func = openssl_private_encrypt;
370                                 $decrypt_func = openssl_private_decrypt;
371                                 $role = "pub";
372                         }
373
374                         // check if jabber address already present
375                         $present = get_pconfig($uid, "jappixmini", "id:".$dfrn_id);
376                         $now = intval(time());
377                         if ($present) {
378                                 // $present has format "timestamp:jabber_address"
379                                 $p = strpos($present, ":");
380                                 $timestamp = intval(substr($present, 0, $p));
381
382                                 // do not re-retrieve jabber address if last retrieval
383                                 // is not older than a week
384                                 if ($now-$timestamp<3600*24*7) continue;
385                         }
386
387                         // construct base retrieval address
388                         $pos = strpos($request, "/dfrn_request/");
389                         if ($pos===false) continue;
390
391                         $base = substr($request, 0, $pos)."/jappixmini?role=$role";
392
393                         // construct own address
394                         $username = get_pconfig($uid, 'jappixmini', 'username');
395                         if (!$username) continue;
396                         $server = get_pconfig($uid, 'jappixmini', 'server');
397                         if (!$server) continue;
398
399                         $address = $username."@".$server;
400
401                         // sign address
402                         $signed_address = "";
403                         $encrypt_func($address, $signed_address, $key);
404
405                         // construct request url
406                         $signed_address_hex = bin2hex($signed_address);
407                         $url = $base."&signed_address=$signed_address_hex&dfrn_id=".urlencode($dfrn_id);
408
409                         try {
410                                 // send request
411                                 $answer_json = fetch_url($url);
412
413                                 // parse answer
414                                 $answer = json_decode($answer_json);
415                                 if ($answer->status != "ok") throw new Exception();
416
417                                 $encrypted_address_hex = $answer->encrypted_address;
418                                 if (!$encrypted_address_hex) throw new Exception();
419
420                                 $encrypted_address = hex2bin($encrypted_address_hex);
421                                 if (!$encrypted_address) throw new Exception();
422
423                                 // decrypt address
424                                 $decrypted_address = "";
425                                 $decrypt_func($encrypted_address, $decrypted_address, $key);
426                                 if (!$decrypted_address) throw new Exception();
427                         } catch (Exception $e) {
428                                 $decrypted_address = "";
429                         }
430
431                         // save address
432                         set_pconfig($uid, "jappixmini", "id:$dfrn_id", "$now:$decrypted_address");
433                 }
434         }
435 }
436
437 function jappixmini_download_source(&$a,&$b) {
438         // Jappix Mini source download link on About page
439
440         $b .= '<h1>Jappix Mini</h1>';
441         $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>';
442         $b .= '<p>You can download the <a href="'.$a->get_baseurl().'/addon/jappixmini/jappix.zip">source code</a>.</p>';
443 }