]> git.mxchange.org Git - friendica-addons.git/blob - jappixmini/jappixmini.php
889594486061455d47e837b71aeabfe860b9303c
[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.
27
28 Problem:
29 How to discover the jabber addresses of the friendica contacts?
30
31 Solution:
32 Each Friendica site with this addon provides a /jappixmini/ module page. We go through our contacts and retrieve
33 this information every week using a cron hook.
34
35 Problem:
36 We do not want to make the jabber address public.
37
38 Solution:
39 When two friendica users connect using DFRN, the relation gets a DFRN ID and a keypair is generated.
40 Using this keypair, we can provide the jabber address only to contacs:
41
42 Case 1: Alice has prvkey, Bob has pubkey.
43   Alice encrypts request
44   Bob decrypts the request, send jabber address unencrypted
45   Alice reads address
46
47 Case 2: Alice has prvkey, Bob has pubkey
48   Alice send request
49   Bob encrypts jabber address
50   Alice decrypts jabber address
51
52 */
53
54 function jappixmini_install() {
55 register_hook('plugin_settings', 'addon/jappixmini/jappixmini.php', 'jappixmini_settings');
56 register_hook('plugin_settings_post', 'addon/jappixmini/jappixmini.php', 'jappixmini_settings_post');
57
58 register_hook('page_end', 'addon/jappixmini/jappixmini.php', 'jappixmini_script');
59 register_hook('authenticate', 'addon/jappixmini/jappixmini.php', 'jappixmini_login');
60
61 register_hook('cron', 'addon/jappixmini/jappixmini.php', 'jappixmini_cron');
62
63 // Jappix source download as required by AGPL
64 register_hook('about_hook', 'addon/jappixmini/jappixmini.php', 'jappixmini_download_source');
65 }
66
67
68 function jappixmini_uninstall() {
69 unregister_hook('plugin_settings', 'addon/jappixmini/jappixmini.php', 'jappixmini_settings');
70 unregister_hook('plugin_settings_post', 'addon/jappixmini/jappixmini.php', 'jappixmini_settings_post');
71
72 unregister_hook('page_end', 'addon/jappixmini/jappixmini.php', 'jappixmini_script');
73 unregister_hook('authenticate', 'addon/jappixmini/jappixmini.php', 'jappixmini_login');
74
75 unregister_hook('cron', 'addon/jappixmini/jappixmini.php', 'jappixmini_cron');
76
77 unregister_hook('about_hook', 'addon/jappixmini/jappixmini.php', 'jappixmini_download_source');
78 }
79
80 function jappixmini_plugin_admin(&$a, &$o) {
81         if (!file_exists("addon/jappixmini/jappix")) {
82                 $o .= '<p><strong>You need to install the Jappix application, adapted for Friendica (see README).</strong></p>';
83         }
84         else if (!file_exists("addon/jappixmini/jappix.zip")) {
85                 $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>';
86         }
87 }
88
89 function jappixmini_plugin_admin_post(&$a) {
90 }
91
92 function jappixmini_module() {}
93 function jappixmini_init(&$a) {
94         // Here, other friendica sites can fetch the jabber address of local users.
95         // Because we do not want to publish the addresses publicly, they are encrypted so
96         // that only contacts can read it.
97         $encrypt_for = $_REQUEST["encrypt_for"];
98         if ($encrypt_for) {
99                 $r = q("SELECT * FROM `contact` WHERE LENGTH(`pubkey`) AND `dfrn-id` = '%s' LIMIT 1",
100                         dbesc($encrypt_for)
101                 );
102                 if (!count($r)) killme();
103
104                 // get public key to encrypt address
105                 $pubkey = $r[0]['pubkey'];
106
107                 // get jabber address
108                 $uid = $r[0]['uid'];
109                 $username = get_pconfig($uid, 'jappixmini', 'username');
110                 if (!$username) killme();
111                 $server = get_pconfig($uid, 'jappixmini', 'server');
112                 if (!$server) killme();
113
114                 $address = $username."@".$server;
115
116                 // encrypt address
117                 $encrypted = "";
118                 openssl_public_encrypt($address,$encrypted,$pubkey);
119
120                 // calculate hex representation of encrypted address
121                 $hex = bin2hex($encrypted);
122
123                 // construct answer
124                 $answer = Array("status"=>"ok", "encrypted_address"=>$hex);
125
126                 // return answer as json
127                 echo json_encode($answer);
128                 killme();
129         }
130
131         // If we have only a private key, other site sends encrypted request, we answer unencrypted.
132         $encrypted_for = $_REQUEST["encrypted_for"];
133         if (!$encrypted_for) killme();
134
135         $encrypted_request_hex = $_REQUEST["encrypted_request"];
136         if (!$encrypted_request_hex) killme();
137         $encrypted_request = hex2bin($encrypted_request_hex);
138
139         $r = q("SELECT * FROM `contact` WHERE LENGTH(`prvkey`) AND `issued-id` = '%s' LIMIT 1",
140                 dbesc($encrypted_for)
141         );
142         if (!count($r)) killme();
143
144         // decrypt request, validate it
145         $prvkey = $r[0]['prvkey'];
146         $decrypted_request = "";
147         openssl_private_decrypt($encrypted_request, $decrypted_request, $prvkey);
148
149         if ($decrypted_request!=$encrypted_for) killme();
150
151         // get jabber address
152         $uid = $r[0]['uid'];
153         $username = get_pconfig($uid, 'jappixmini', 'username');
154         if (!$username) killme();
155         $server = get_pconfig($uid, 'jappixmini', 'server');
156         if (!$server) killme();
157
158         $address = $username."@".$server;
159
160         // construct answer
161         $answer = Array("status"=>"ok", "address"=>$address);
162
163         // return answer as json
164         echo json_encode($answer);
165         killme();
166 }
167
168 function jappixmini_settings(&$a, &$s) {
169     $username = get_pconfig(local_user(),'jappixmini','username');
170     $username = htmlentities($username);
171     $server = get_pconfig(local_user(),'jappixmini','server');
172     $server = htmlentities($server);
173     $bosh = get_pconfig(local_user(),'jappixmini','bosh');
174     $bosh = htmlentities($bosh);
175     $encrypted_password = get_pconfig(local_user(),'jappixmini','encrypted-password');
176     $autosubscribe = get_pconfig(local_user(),'jappixmini','autosubscribe');
177     $autosubscribe = intval($autosubscribe) ? ' checked="checked"' : '';
178     $autoapprove = get_pconfig(local_user(),'jappixmini','autoapprove');
179     $autoapprove = intval($autoapprove) ? ' checked="checked"' : '';
180     $activate = get_pconfig(local_user(),'jappixmini','activate');
181     $activate = intval($activate) ? ' checked="checked"' : '';
182
183     $s .= '<div class="settings-block">';
184     $s .= '<h3>Jappix Mini addon settings</h3>';
185     $s .= '<div>';
186     $s .= '<label for="jappixmini-activate">Activate addon</label>';
187     $s .= ' <input id="jappixmini-activate" type="checkbox" name="jappixmini-activate" value="1"'.$activate.' />';
188     $s .= '<br />';
189     $s .= '<label for="jappixmini-username">Jabber username</label>';
190     $s .= ' <input id="jappixmini-username" type="text" name="jappixmini-username" value="'.$username.'" />';
191     $s .= '<br />';
192     $s .= '<label for="jappixmini-server">Jabber server</label>';
193     $s .= ' <input id="jappixmini-server" type="text" name="jappixmini-server" value="'.$server.'" />';
194     $s .= '<br />';
195     $s .= '<label for="jappixmini-bosh">Jabber BOSH host</label>';
196     $s .= ' <input id="jappixmini-bosh" type="text" name="jappixmini-bosh" value="'.$bosh.'" />';
197     $s .= '<br />';
198     $s .= '<label for="jappixmini-password">Jabber password</label>';
199     $s .= ' <input type="hidden" id="jappixmini-encrypted-password" name="jappixmini-encrypted-password" value="'.$encrypted_password.'" />';
200     $onchange = "document.getElementById('jappixmini-encrypted-password').value = jappixmini_addon_encrypt_password(document.getElementById('jappixmini-password').value);";
201     $s .= ' <input id="jappixmini-password" type="password" value="" onchange="'.$onchange.'" />';
202     $s .= '<br />';
203     $s .= '<label for="jappixmini-autoapprove">Approve subscription requests from Friendica contacts automatically</label>';
204     $s .= ' <input id="jappixmini-autoapprove" type="checkbox" name="jappixmini-autoapprove" value="1"'.$autoapprove.' />';
205     $s .= '<br />';
206     $s .= '<label for="jappixmini-autosubscribe">Subscribe to Friendica contacts automatically</label>';
207     $s .= ' <input id="jappixmini-autosubscribe" type="checkbox" name="jappixmini-autosubscribe" value="1"'.$autosubscribe.' />';
208     $s .= '<br />';
209     $s .= '<label for="jappixmini-purge">Purge list of jabber addresses of contacts</label>';
210     $s .= ' <input id="jappixmini-purge" type="checkbox" name="jappixmini-purge" value="1" />';
211     $s .= '<br />';
212     $s .= '<input type="submit" name="jappixmini-submit" value="' . t('Submit') . '" />';
213     $s .= '</div>';
214
215     $a->page['htmlhead'] .= "<script type=\"text/javascript\">
216         jQuery(document).ready(function() {
217             document.getElementById('jappixmini-password').value = jappixmini_addon_decrypt_password('$encrypted_password');
218         });
219     </script>";
220 }
221
222 function jappixmini_settings_post(&$a,&$b) {
223         if(! local_user()) return;
224
225         if($_POST['jappixmini-submit']) {
226                 set_pconfig(local_user(),'jappixmini','username',trim($b['jappixmini-username']));
227                 set_pconfig(local_user(),'jappixmini','server',trim($b['jappixmini-server']));
228                 set_pconfig(local_user(),'jappixmini','bosh',trim($b['jappixmini-bosh']));
229                 set_pconfig(local_user(),'jappixmini','encrypted-password',trim($b['jappixmini-encrypted-password']));
230                 set_pconfig(local_user(),'jappixmini','autosubscribe',intval($b['jappixmini-autosubscribe']));
231                 set_pconfig(local_user(),'jappixmini','autoapprove',intval($b['jappixmini-autoapprove']));
232                 set_pconfig(local_user(),'jappixmini','activate',intval($b['jappixmini-activate']));
233                 info( 'Jappix Mini settings saved.' );
234
235                 if (intval($b['jappixmini-purge'])) {
236                         $uid = local_user();
237                         q("DELETE FROM `pconfig` WHERE `uid`=$uid AND `cat`='jappixmini' and `k` LIKE 'id%%'");
238                         info( 'List of addresses purged.' );
239                 }
240         }
241 }
242
243 function jappixmini_script(&$a,&$s) {
244     if(! local_user()) return;
245
246     $activate = get_pconfig(local_user(),'jappixmini','activate');
247     if (!$activate) return;
248
249     $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";
250     $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";
251
252     $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";
253     $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";
254
255     $a->page['htmlhead'] .= '<script type="text/javascript" src="' . $a->get_baseurl() . '/addon/jappixmini/lib.js"></script>'."\r\n";
256
257     $username = get_pconfig(local_user(),'jappixmini','username');
258     $username = str_replace("'", "\\'", $username);
259     $server = get_pconfig(local_user(),'jappixmini','server');
260     $server = str_replace("'", "\\'", $server);
261     $bosh = get_pconfig(local_user(),'jappixmini','bosh');
262     $bosh = str_replace("'", "\\'", $bosh);
263     $encrypted_password = get_pconfig(local_user(),'jappixmini','encrypted-password');
264     $encrypted_password = str_replace("'", "\\'", $encrypted_password);
265
266     $autoapprove = get_pconfig(local_user(),'jappixmini','autoapprove');
267     $autoapprove = intval($autoapprove);
268     $autosubscribe = get_pconfig(local_user(),'jappixmini','autosubscribe');
269     $autosubscribe = intval($autosubscribe);
270
271     // get a list of jabber accounts of the contacts
272     $contacts = Array();
273     $uid = local_user();
274     $rows = q("SELECT `v` FROM `pconfig` WHERE `uid`=$uid AND `cat`='jappixmini' and `k` LIKE 'id%%'");
275     foreach ($rows as $row) {
276         $value = $row['v'];
277         $pos = strpos($value, ":");
278         $address = substr($value, $pos+1);
279         $contacts[] = $address;
280     }
281     $contacts_json = json_encode($contacts);
282
283     $a->page['htmlhead'] .= "<script type=\"text/javascript\">
284         jQuery(document).ready(function() {
285            jappixmini_addon_start('$server', '$username', '$bosh', '$encrypted_password');
286            jappixmini_manage_roster($contacts_json, $autoapprove, $autosubscribe);
287         });
288     </script>";
289
290     return;
291 }
292
293 function jappixmini_login(&$a, &$o) {
294     // save hash of password using setDB
295     $o = str_replace("<form ", "<form onsubmit=\"jappixmini_addon_set_client_secret(this.elements['id_password'].value);return true;\" ", $o);
296 }
297
298 function jappixmini_cron(&$a, $d) {
299         // For autosubscribe/autoapprove, we need to maintain a list of jabber addresses of our contacts.
300
301         // go through list of users with jabber enabled
302         $users = q("SELECT `uid` FROM `pconfig` WHERE `cat`='jappixmini' AND (`k`='autosubscribe' OR `k`='autoapprove') AND `v`='1'");
303
304         foreach ($users as $row) {
305                 $uid = $row["uid"];
306
307                 // for each user, go through list of contacts
308                 $contacts = q("SELECT * FROM `contact` WHERE `uid`=%d AND ((LENGTH(`dfrn-id`) AND LENGTH(`pubkey`)) OR (LENGTH(`issued-id`) AND LENGTH(`prvkey`)))", intval($uid));
309                 foreach ($contacts as $contact_row) {
310                         $request = $contact_row["request"];
311                         if (!$request) continue;
312
313                         $dfrn_id = $contact_row["dfrn-id"];
314                         $pubkey = $contact_row["pubkey"];
315                         if (!$dfrn_id) {
316                                 $dfrn_id = $contact_row["issued-id"];
317                                 $prvkey = $contact_row["prvkey"];
318                         }
319
320                         // check if jabber address already present
321                         $present = get_pconfig($uid, "jappixmini", "id:".$dfrn_id);
322                         $now = intval(time());
323                         if ($present) {
324                                 // $present has format "timestamp:jabber_address"
325                                 $p = strpos($present, ":");
326                                 $timestamp = intval(substr($present, 0, $p));
327
328                                 // do not re-retrieve jabber address if last retrieval
329                                 // is not older than a week
330                                 if ($now-$timestamp<3600*24*7) continue;
331                         }
332
333                         // construct base retrieval address
334                         $pos = strpos($request, "/dfrn_request/");
335                         if ($pos===false) continue;
336
337                         $base = substr($request, 0, $pos)."/jappixmini";
338
339                         // retrieve address
340                         if ($prvkey) {
341                                 $retrieval_address = $base."?encrypt_for=".urlencode($dfrn_id);
342
343                                 $answer_json = fetch_url($retrieval_address);
344                                 $answer = json_decode($answer_json);
345                                 if ($answer->status != "ok") continue;
346
347                                 $encrypted_address_hex = $answer->encrypted_address;
348                                 if (!$encrypted_address_hex) continue;
349                                 $encrypted_address = hex2bin($encrypted_address_hex);
350
351                                 $decrypted_address = "";
352                                 openssl_private_decrypt($encrypted_address, $decrypted_address, $prvkey);
353                                 if (!$decrypted_address) continue;
354
355                                 $address = $decrypted_address;
356                         } else if ($pubkey) {
357                                 $encrypted_request = "";
358                                 openssl_public_encrypt($dfrn_id, $encrypted_request, $pubkey);
359                                 if (!$encrypted_request) continue;
360                                 $encrypted_request_hex = bin2hex($encrypted_request);
361
362                                 $retrieval_address = $base."?encrypted_for=".urlencode($dfrn_id)."&encrypted_request=".urlencode($encrypted_request_hex);
363
364                                 $answer_json = fetch_url($retrieval_address);
365                                 $answer = json_decode($answer_json);
366                                 if ($answer->status != "ok") continue;
367
368                                 $address = $answer->address;
369                                 if (!$address) continue;
370                         }
371
372                         // save address
373                         set_pconfig($uid, "jappixmini", "id:$dfrn_id", "$now:$address");
374                 }
375         }
376 }
377
378 function jappixmini_download_source(&$a,&$b) {
379         $b .= '<h1>Jappix Mini</h1>';
380         $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>';
381         $b .= '<p>You can download the <a href="'.$a->get_baseurl().'/addon/jappixmini/jappix.zip">source code</a>.</p>';
382 }