6 * Description: Inserts a jabber chat
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
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.
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.
29 How to discover the jabber addresses of the friendica contacts?
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.
36 We do not want to make the jabber address public.
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:
42 Case 1: Alice has prvkey, Bob has pubkey.
43 Alice encrypts request
44 Bob decrypts the request, send jabber address unencrypted
47 Case 2: Alice has prvkey, Bob has pubkey
49 Bob encrypts jabber address
50 Alice decrypts jabber address
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');
58 register_hook('page_end', 'addon/jappixmini/jappixmini.php', 'jappixmini_script');
59 register_hook('authenticate', 'addon/jappixmini/jappixmini.php', 'jappixmini_login');
61 register_hook('cron', 'addon/jappixmini/jappixmini.php', 'jappixmini_cron');
63 // Jappix source download as required by AGPL
64 register_hook('about_hook', 'addon/jappixmini/jappixmini.php', 'jappixmini_download_source');
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');
72 unregister_hook('page_end', 'addon/jappixmini/jappixmini.php', 'jappixmini_script');
73 unregister_hook('authenticate', 'addon/jappixmini/jappixmini.php', 'jappixmini_login');
75 unregister_hook('cron', 'addon/jappixmini/jappixmini.php', 'jappixmini_cron');
77 unregister_hook('about_hook', 'addon/jappixmini/jappixmini.php', 'jappixmini_download_source');
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>';
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>';
89 function jappixmini_plugin_admin_post(&$a) {
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"];
99 $r = q("SELECT * FROM `contact` WHERE LENGTH(`pubkey`) AND `dfrn-id` = '%s' LIMIT 1",
102 if (!count($r)) killme();
104 // get public key to encrypt address
105 $pubkey = $r[0]['pubkey'];
107 // get jabber address
109 $username = get_pconfig($uid, 'jappixmini', 'username');
110 if (!$username) killme();
111 $server = get_pconfig($uid, 'jappixmini', 'server');
112 if (!$server) killme();
114 $address = $username."@".$server;
118 openssl_public_encrypt($address,$encrypted,$pubkey);
120 // calculate hex representation of encrypted address
121 $hex = bin2hex($encrypted);
124 $answer = Array("status"=>"ok", "encrypted_address"=>$hex);
126 // return answer as json
127 echo json_encode($answer);
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();
135 $encrypted_request_hex = $_REQUEST["encrypted_request"];
136 if (!$encrypted_request_hex) killme();
137 $encrypted_request = hex2bin($encrypted_request_hex);
139 $r = q("SELECT * FROM `contact` WHERE LENGTH(`prvkey`) AND `issued-id` = '%s' LIMIT 1",
140 dbesc($encrypted_for)
142 if (!count($r)) killme();
144 // decrypt request, validate it
145 $prvkey = $r[0]['prvkey'];
146 $decrypted_request = "";
147 openssl_private_decrypt($encrypted_request, $decrypted_request, $prvkey);
149 if ($decrypted_request!=$encrypted_for) killme();
151 // get jabber address
153 $username = get_pconfig($uid, 'jappixmini', 'username');
154 if (!$username) killme();
155 $server = get_pconfig($uid, 'jappixmini', 'server');
156 if (!$server) killme();
158 $address = $username."@".$server;
161 $answer = Array("status"=>"ok", "address"=>$address);
163 // return answer as json
164 echo json_encode($answer);
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"' : '';
183 $s .= '<div class="settings-block">';
184 $s .= '<h3>Jappix Mini addon settings</h3>';
186 $s .= '<label for="jappixmini-activate">Activate addon</label>';
187 $s .= ' <input id="jappixmini-activate" type="checkbox" name="jappixmini-activate" value="1"'.$activate.' />';
189 $s .= '<label for="jappixmini-username">Jabber username</label>';
190 $s .= ' <input id="jappixmini-username" type="text" name="jappixmini-username" value="'.$username.'" />';
192 $s .= '<label for="jappixmini-server">Jabber server</label>';
193 $s .= ' <input id="jappixmini-server" type="text" name="jappixmini-server" value="'.$server.'" />';
195 $s .= '<label for="jappixmini-bosh">Jabber BOSH host</label>';
196 $s .= ' <input id="jappixmini-bosh" type="text" name="jappixmini-bosh" value="'.$bosh.'" />';
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.'" />';
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.' />';
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.' />';
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" />';
212 $s .= '<input type="submit" name="jappixmini-submit" value="' . t('Submit') . '" />';
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');
222 function jappixmini_settings_post(&$a,&$b) {
223 if(! local_user()) return;
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.' );
235 if (intval($b['jappixmini-purge'])) {
237 q("DELETE FROM `pconfig` WHERE `uid`=$uid AND `cat`='jappixmini' and `k` LIKE 'id%%'");
238 info( 'List of addresses purged.' );
243 function jappixmini_script(&$a,&$s) {
244 if(! local_user()) return;
246 $activate = get_pconfig(local_user(),'jappixmini','activate');
247 if (!$activate) return;
249 $a->page['htmlhead'] .= '<script type="text/javascript" src="' . $a->get_baseurl() . '/addon/jappixmini/jappix/php/get.php?t=js&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&f=presence.js"></script>'."\r\n";
252 $a->page['htmlhead'] .= '<script type="text/javascript" src="' . $a->get_baseurl() . '/addon/jappixmini/jappix/php/get.php?t=js&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&f=name.js"></script>'."\r\n";
255 $a->page['htmlhead'] .= '<script type="text/javascript" src="' . $a->get_baseurl() . '/addon/jappixmini/lib.js"></script>'."\r\n";
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);
266 $autoapprove = get_pconfig(local_user(),'jappixmini','autoapprove');
267 $autoapprove = intval($autoapprove);
268 $autosubscribe = get_pconfig(local_user(),'jappixmini','autosubscribe');
269 $autosubscribe = intval($autosubscribe);
271 // get a list of jabber accounts of the contacts
274 $rows = q("SELECT `v` FROM `pconfig` WHERE `uid`=$uid AND `cat`='jappixmini' and `k` LIKE 'id%%'");
275 foreach ($rows as $row) {
277 $pos = strpos($value, ":");
278 $address = substr($value, $pos+1);
279 $contacts[] = $address;
281 $contacts_json = json_encode($contacts);
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);
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);
298 function jappixmini_cron(&$a, $d) {
299 // For autosubscribe/autoapprove, we need to maintain a list of jabber addresses of our contacts.
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'");
304 foreach ($users as $row) {
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;
313 $dfrn_id = $contact_row["dfrn-id"];
314 $pubkey = $contact_row["pubkey"];
316 $dfrn_id = $contact_row["issued-id"];
317 $prvkey = $contact_row["prvkey"];
320 // check if jabber address already present
321 $present = get_pconfig($uid, "jappixmini", "id:".$dfrn_id);
322 $now = intval(time());
324 // $present has format "timestamp:jabber_address"
325 $p = strpos($present, ":");
326 $timestamp = intval(substr($present, 0, $p));
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;
333 // construct base retrieval address
334 $pos = strpos($request, "/dfrn_request/");
335 if ($pos===false) continue;
337 $base = substr($request, 0, $pos)."/jappixmini";
341 $retrieval_address = $base."?encrypt_for=".urlencode($dfrn_id);
343 $answer_json = fetch_url($retrieval_address);
344 $answer = json_decode($answer_json);
345 if ($answer->status != "ok") continue;
347 $encrypted_address_hex = $answer->encrypted_address;
348 if (!$encrypted_address_hex) continue;
349 $encrypted_address = hex2bin($encrypted_address_hex);
351 $decrypted_address = "";
352 openssl_private_decrypt($encrypted_address, $decrypted_address, $prvkey);
353 if (!$decrypted_address) continue;
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);
362 $retrieval_address = $base."?encrypted_for=".urlencode($dfrn_id)."&encrypted_request=".urlencode($encrypted_request_hex);
364 $answer_json = fetch_url($retrieval_address);
365 $answer = json_decode($answer_json);
366 if ($answer->status != "ok") continue;
368 $address = $answer->address;
369 if (!$address) continue;
373 set_pconfig($uid, "jappixmini", "id:$dfrn_id", "$now:$address");
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>';