Moving to GitHub.
[platal.git] / modules / platal.php
1 <?php
2 /***************************************************************************
3 * Copyright (C) 2003-2014 Polytechnique.org *
4 * http://opensource.polytechnique.org/ *
5 * *
6 * This program is free software; you can redistribute it and/or modify *
7 * it under the terms of the GNU General Public License as published by *
8 * the Free Software Foundation; either version 2 of the License, or *
9 * (at your option) any later version. *
10 * *
11 * This program is distributed in the hope that it will be useful, *
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14 * GNU General Public License for more details. *
15 * *
16 * You should have received a copy of the GNU General Public License *
17 * along with this program; if not, write to the Free Software *
18 * Foundation, Inc., *
19 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *
20 ***************************************************************************/
21
22 function bugize($list)
23 {
24 $list = preg_split('/,/', $list, -1, PREG_SPLIT_NO_EMPTY);
25 $ans = array();
26
27 foreach ($list as $bug) {
28 $clean = str_replace('#', '', $bug);
29 $ans[] = "<a href='http://trackers.polytechnique.org/task/$clean'>$bug</a>";
30 }
31
32 return join(',', $ans);
33 }
34
35
36 class PlatalModule extends PLModule
37 {
38 function handlers()
39 {
40 return array(
41 'index' => $this->make_hook('index', AUTH_PUBLIC),
42 'cacert.pem' => $this->make_hook('cacert', AUTH_PUBLIC),
43 'changelog' => $this->make_hook('changelog', AUTH_PUBLIC),
44
45 // Preferences thingies
46 'prefs' => $this->make_hook('prefs', AUTH_COOKIE, 'user,groups'),
47 'prefs/rss' => $this->make_hook('prefs_rss', AUTH_COOKIE, 'user'),
48 'prefs/webredirect' => $this->make_hook('webredir', AUTH_PASSWD, 'mail'),
49 'prefs/skin' => $this->make_hook('skin', AUTH_COOKIE, 'user'),
50 'prefs/email' => $this->make_hook('prefs_email', AUTH_COOKIE, 'mail'),
51
52 // password related thingies
53 'password' => $this->make_hook('password', AUTH_PASSWD, 'user,groups'),
54 'password/smtp' => $this->make_hook('smtppass', AUTH_PASSWD, 'mail'),
55 'tmpPWD' => $this->make_hook('tmpPWD', AUTH_PUBLIC),
56 'recovery' => $this->make_hook('recovery', AUTH_PUBLIC),
57 'recovery/ext' => $this->make_hook('recovery_ext', AUTH_PUBLIC),
58 'register/ext' => $this->make_hook('register_ext', AUTH_PUBLIC),
59 'exit' => $this->make_hook('exit', AUTH_PUBLIC),
60 'review' => $this->make_hook('review', AUTH_PUBLIC),
61 'deconnexion.php' => $this->make_hook('exit', AUTH_PUBLIC),
62
63 'error' => $this->make_hook('test_error', AUTH_COOKIE),
64 );
65 }
66
67 function handler_index($page)
68 {
69 // Include X-XRDS-Location response-header for Yadis discovery
70 global $globals;
71 header('X-XRDS-Location: ' . $globals->baseurl . '/openid/xrds');
72
73 // Redirect to the suitable page
74 if (S::logged()) {
75 pl_redirect('events');
76 } else if (!@$GLOBALS['IS_XNET_SITE']) {
77 $this->handler_review($page);
78 }
79 }
80
81 function handler_cacert($page)
82 {
83 pl_cached_content_headers("application/x-x509-ca-cert");
84 readfile("/etc/ssl/xorgCA/cacert.pem");
85 exit;
86 }
87
88 function handler_changelog($page, $core = null)
89 {
90 $page->changeTpl('platal/changeLog.tpl');
91
92 function formatChangeLog($file) {
93 $clog = pl_entities(file_get_contents($file));
94 $clog = preg_replace('/===+\s*/', '</pre><hr /><pre>', $clog);
95 // url catch only (not all wiki syntax)
96 $clog = preg_replace(array(
97 '/((?:https?|ftp):\/\/(?:\.*,*[\w@~%$£µ&i#\-+=_\/\?;])*)/ui',
98 '/(\s|^)www\.((?:\.*,*[\w@~%$£µ&i#\-+=_\/\?;])*)/iu',
99 '/(?:mailto:)?([a-z0-9.\-+_]+@([\-.+_]?[a-z0-9])+)/i'),
100 array(
101 '<a href="\\0">\\0</a>',
102 '\\1<a href="http://www.\\2">www.\\2</a>',
103 '<a href="mailto:\\0">\\0</a>'),
104 $clog);
105 $clog = preg_replace('!(#[0-9]+(,[0-9]+)*)!e', 'bugize("\1")', $clog);
106 $clog = preg_replace('!vim:.*$!', '', $clog);
107 return preg_replace("!(<hr />(\\s|\n)*)?<pre>(\s|\n)*</pre>((\\s|\n)*<hr />)?!m", "", "<pre>$clog</pre>");
108 }
109 if ($core != 'core') {
110 $page->assign('core', false);
111 $page->assign('ChangeLog', formatChangeLog(dirname(__FILE__).'/../ChangeLog'));
112 } else {
113 $page->assign('core', true);
114 $page->assign('ChangeLog', formatChangeLog(dirname(__FILE__).'/../core/ChangeLog'));
115 }
116 }
117
118 function __set_rss_state($state)
119 {
120 if ($state) {
121 if (!S::user()->token) {
122 S::user()->token = rand_url_id(16);
123 S::set('token', S::user()->token);
124 XDB::execute('UPDATE accounts
125 SET token = {?}
126 WHERE uid = {?}', S::user()->token, S::i('uid'));
127 }
128 } else {
129 S::kill('token');
130 S::user()->token = null;
131 XDB::execute('UPDATE accounts
132 SET token = NULL
133 WHERE uid = {?}', S::i('uid'));
134 }
135 }
136
137 function handler_prefs($page)
138 {
139 $page->changeTpl('platal/preferences.tpl');
140 $page->setTitle('Mes préférences');
141
142 if (Post::has('email_format')) {
143 S::assert_xsrf_token();
144 $fmt = Post::s('email_format');
145 S::user()->setEmailFormat($fmt);
146 }
147
148 if (Post::has('rss')) {
149 S::assert_xsrf_token();
150 $this->__set_rss_state(Post::s('rss') == 'on');
151 }
152 }
153
154 function handler_webredir($page)
155 {
156 $page->changeTpl('platal/webredirect.tpl');
157 $page->setTitle('Redirection de page WEB');
158
159 if (Env::v('submit') == 'Valider' && !Env::blank('url')) {
160 if (Env::blank('url')) {
161 $page->trigError('URL invalide');
162 } else {
163 $url = Env::t('url');
164 XDB::execute('INSERT INTO carvas (uid, url)
165 VALUES ({?}, {?})
166 ON DUPLICATE KEY UPDATE url = VALUES(url)',
167 S::i('uid'), $url);
168 S::logger()->log('carva_add', 'http://' . $url);
169 $page->trigSuccess("Redirection activée vers <a href='http://$url'>$url</a>");
170 }
171 } elseif (Env::v('submit') == 'Supprimer') {
172 XDB::execute('DELETE FROM carvas
173 WHERE uid = {?}', S::i('uid'));
174 Post::kill('url');
175 S::logger()->log('carva_del');
176 $page->trigSuccess('Redirection supprimée');
177 }
178
179 $url = XDB::fetchOneCell('SELECT url
180 FROM carvas
181 WHERE uid = {?}', S::i('uid'));
182 $page->assign('carva', $url);
183
184 # FIXME: this code is not multi-domain compatible. We should decide how
185 # carva will extend to users not in the main domain.
186 $best = XDB::fetchOneCell('SELECT email
187 FROM email_source_account
188 WHERE uid = {?} AND FIND_IN_SET(\'bestalias\', flags)',
189 S::user()->id());
190 $page->assign('bestalias', $best);
191 }
192
193 function handler_prefs_rss($page)
194 {
195 $page->changeTpl('platal/filrss.tpl');
196
197 $page->assign('goback', Env::v('referer', 'login'));
198
199 if (Env::v('act_rss') == 'Activer') {
200 $this->__set_rss_state(true);
201 $page->trigSuccess("Ton Fil RSS est activé.");
202 }
203 }
204
205 function handler_prefs_email($page)
206 {
207 $page->changeTpl('platal/email_preferences.tpl');
208
209 if (Post::has('submit')) {
210 S::assert_xsrf_token();
211
212 $from_email = Post::t('from_email');
213 $from_format = Post::v('from_format');
214
215 // Checks email.
216 $email_regex = '/^[a-z0-9.\-+_\$]+@([\-.+_]?[a-z0-9])+$/i';
217 if (!preg_match($email_regex, $from_email)) {
218 $full_regex = '/^[^<]*<[a-z0-9.\-+_\$]+@([\-.+_]?[a-z0-9])+>$/i';
219 if (!preg_match($full_regex, $from_email)) {
220 $page->trigError("L'adresse email est erronée.");
221 $error = true;
222 $page->assign('from_email', $from_email);
223 $page->assign('from_format', $from_format);
224 $page->assign('error', true);
225 return;
226 }
227 }
228
229 // Saves data.
230 XDB::execute('UPDATE accounts
231 SET from_email = {?}, from_format = {?}
232 WHERE uid = {?}',
233 $from_email, ($from_format == 'html' ? 'html' : 'text'), S::user()->id());
234 $page->trigSuccess('Données enregistrées.');
235 }
236
237 $data = XDB::fetchOneAssoc('SELECT from_email, from_format
238 FROM accounts
239 WHERE uid = {?}',
240 S::user()->id());
241 $page->assign('from_email', $data['from_email']);
242 $page->assign('from_format', $data['from_format']);
243 $page->assign('error', false);
244 }
245
246 function handler_password($page)
247 {
248 global $globals;
249
250 if (Post::has('pwhash') && Post::t('pwhash')) {
251 S::assert_xsrf_token();
252
253 S::set('password', $password = Post::t('pwhash'));
254 XDB::execute('UPDATE accounts
255 SET password = {?}
256 WHERE uid={?}', $password,
257 S::i('uid'));
258
259 // If GoogleApps is enabled, and the user did choose to use synchronized passwords,
260 // updates the Google Apps password as well.
261 if ($globals->mailstorage->googleapps_domain) {
262 require_once 'googleapps.inc.php';
263 $account = new GoogleAppsAccount(S::user());
264 if ($account->active() && $account->sync_password) {
265 $account->set_password($password);
266 }
267 }
268
269 S::logger()->log('passwd');
270 Platal::session()->setAccessCookie(true);
271
272 $page->changeTpl('platal/password.success.tpl');
273 $page->run();
274 }
275
276 $page->changeTpl('platal/password.tpl');
277 $page->setTitle('Mon mot de passe');
278 $page->assign('do_auth', 0);
279 }
280
281 function handler_smtppass($page)
282 {
283 $page->changeTpl('platal/acces_smtp.tpl');
284 $page->setTitle('Acces SMTP/NNTP');
285
286 $wp = new PlWikiPage('Xorg.SMTPSécurisé');
287 $wp->buildCache();
288 $wp = new PlWikiPage('Xorg.NNTPSécurisé');
289 $wp->buildCache();
290
291 $uid = S::i('uid');
292 $pass = Env::v('smtppass1');
293
294 if (Env::v('op') == "Valider" && strlen($pass) >= 6
295 && Env::v('smtppass1') == Env::v('smtppass2')) {
296 XDB::execute('UPDATE accounts
297 SET weak_password = {?}
298 WHERE uid = {?}', $pass, $uid);
299 $page->trigSuccess('Mot de passe enregistré');
300 S::logger()->log("passwd_ssl");
301 } elseif (Env::v('op') == "Supprimer") {
302 XDB::execute('UPDATE accounts
303 SET weak_password = NULL
304 WHERE uid = {?}', $uid);
305 $page->trigSuccess('Compte SMTP et NNTP supprimé');
306 S::logger()->log("passwd_del");
307 }
308
309 $res = XDB::query("SELECT weak_password IS NOT NULL
310 FROM accounts
311 WHERE uid = {?}", $uid);
312 $page->assign('actif', $res->fetchOneCell());
313 }
314
315 function handler_recovery($page)
316 {
317 global $globals;
318
319 $page->changeTpl('platal/recovery.tpl');
320
321 if (!Env::has('login') || !Env::has('birth')) {
322 return;
323 }
324
325 if (!preg_match('/^[0-3][0-9][0-1][0-9][1][9]([0-9]{2})$/', Env::v('birth'))) {
326 $page->trigError('Date de naissance incorrecte ou incohérente');
327 return;
328 }
329
330 $birth = sprintf('%s-%s-%s',
331 substr(Env::v('birth'), 4, 4),
332 substr(Env::v('birth'), 2, 2),
333 substr(Env::v('birth'), 0, 2));
334
335 $mailorg = strtok(Env::v('login'), '@');
336
337 $profile = Profile::get(Env::t('login'));
338 if (is_null($profile) || $profile->birthdate != $birth) {
339 $page->trigError('Les informations que tu as rentrées ne permettent pas de récupérer ton mot de passe.<br />'.
340 'Si tu as un homonyme, utilise prenom.nom.promo comme login');
341 return;
342 }
343
344 $user = $profile->owner();
345 if ($user->state != 'active') {
346 $page->trigError('Ton compte n\'est pas activé.');
347 return;
348 }
349
350 if ($user->lost) {
351 $page->assign('no_addr', true);
352 return;
353 }
354
355 $page->assign('ok', true);
356
357 $url = rand_url_id();
358 XDB::execute('INSERT INTO account_lost_passwords (certificat,uid,created)
359 VALUES ({?},{?},NOW())', $url, $user->id());
360 $to = XDB::fetchOneCell('SELECT redirect
361 FROM email_redirect_account
362 WHERE uid = {?} AND redirect = {?}',
363 $user->id(), Post::t('email'));
364 if (is_null($to)) {
365 $emails = XDB::fetchColumn('SELECT redirect
366 FROM email_redirect_account
367 WHERE uid = {?} AND flags = \'inactive\' AND type = \'smtp\'',
368 $user->id());
369 $inactives_to = implode(', ', $emails);
370 }
371 $mymail = new PlMailer();
372 $mymail->setFrom('"Gestion des mots de passe" <support+password@' . $globals->mail->domain . '>');
373 if (is_null($to)) {
374 $mymail->addTo($user);
375 $log_to = $user->bestEmail();
376 if (!is_null($inactives_to)) {
377 $log_to = $inactives_to . ', ' . $log_to;
378 $mymail->addTo($inactives_to);
379 }
380 } else {
381 $mymail->addTo($to);
382 $log_to = $to;
383 }
384 $mymail->setSubject("Ton certificat d'authentification");
385 $mymail->setTxtBody("Visite la page suivante qui expire dans six heures :
386 {$globals->baseurl}/tmpPWD/$url
387
388 Si en cliquant dessus tu n'y arrives pas, copie intégralement l'adresse dans la barre de ton navigateur. Si tu n'as pas utilisé ce lien dans six heures, tu peux tout simplement recommencer cette procédure.
389
390 --
391 Polytechnique.org
392 \"Le portail des élèves & anciens élèves de l'École polytechnique\"
393
394 Email envoyé à ".Env::v('login') . (is_null($to) ? '' : '
395 Adresse de secours : ' . $to));
396 $mymail->send();
397
398 S::logger($user->id())->log('recovery', $log_to);
399 }
400
401 function handler_recovery_ext($page)
402 {
403 $page->changeTpl('xnet/recovery.tpl');
404
405 if (!Post::has('login')) {
406 return;
407 }
408
409 $user = User::getSilent(Post::t('login'));
410 if (is_null($user)) {
411 $page->trigError('Le compte n\'existe pas.');
412 return;
413 }
414 if ($user->state != 'active') {
415 $page->trigError('Ton compte n\'est pas activé.');
416 return;
417 }
418
419 $page->assign('ok', true);
420
421 $hash = rand_url_id();
422 XDB::execute('INSERT INTO account_lost_passwords (uid, created, certificat)
423 VALUES ({?}, NOW(), {?})',
424 $user->id(), $hash);
425
426 $mymail = new PlMailer('platal/password_recovery_xnet.mail.tpl');
427 $mymail->setTo($user);
428 $mymail->assign('hash', $hash);
429 $mymail->assign('email', Post::t('login'));
430 $mymail->send();
431
432 S::logger($user->id())->log('recovery', $user->bestEmail());
433 }
434
435 function handler_tmpPWD($page, $certif = null)
436 {
437 global $globals;
438 XDB::execute('DELETE FROM account_lost_passwords
439 WHERE DATE_SUB(NOW(), INTERVAL 380 MINUTE) > created');
440
441 if (Post::has('pwhash') && Post::t('pwhash')) {
442 $uid = XDB::fetchOneCell('SELECT uid
443 FROM accounts
444 WHERE hruid = {?}',
445 Post::t('username'));
446 $password = Post::t('pwhash');
447 XDB::query('UPDATE accounts
448 SET password = {?}
449 WHERE uid = {?} AND state = \'active\'',
450 $password, $uid);
451 XDB::query('DELETE FROM account_lost_passwords
452 WHERE certificat = {?}', $certif);
453
454 // If GoogleApps is enabled, and the user did choose to use synchronized passwords,
455 // updates the Google Apps password as well.
456 if ($globals->mailstorage->googleapps_domain) {
457 require_once 'googleapps.inc.php';
458 $account = new GoogleAppsAccount(User::getSilent($uid));
459 if ($account->active() && $account->sync_password) {
460 $account->set_password($password);
461 }
462 }
463
464 S::logger($uid)->log("passwd", "");
465
466 // Try to start a session (so the user don't have to log in); we will use
467 // the password available in Post:: to authenticate the user.
468 Platal::session()->start(AUTH_PASSWD);
469
470 $page->changeTpl('platal/tmpPWD.success.tpl');
471 } else {
472 $res = XDB::query('SELECT uid
473 FROM account_lost_passwords
474 WHERE certificat = {?}', $certif);
475 $ligne = $res->fetchOneAssoc();
476 if (!$ligne) {
477 $page->changeTpl('platal/index.tpl');
478 $page->kill("Cette adresse n'existe pas ou n'existe plus sur le serveur.");
479 }
480
481 $hruid = XDB::fetchOneCell('SELECT hruid
482 FROM accounts
483 WHERE uid = {?}',
484 $ligne['uid']);
485 $page->changeTpl('platal/password.tpl');
486 $page->assign('hruid', $hruid);
487 $page->assign('do_auth', 1);
488 }
489 }
490
491 function handler_register_ext($page, $hash = null)
492 {
493 XDB::execute('DELETE FROM register_pending_xnet
494 WHERE DATE_SUB(NOW(), INTERVAL 1 MONTH) > date');
495 $res = XDB::fetchOneAssoc('SELECT uid, hruid, email
496 FROM register_pending_xnet
497 WHERE hash = {?}',
498 $hash);
499
500 if (is_null($hash) || is_null($res)) {
501 $page->trigErrorRedirect('Cette adresse n\'existe pas ou n\'existe plus sur le serveur.', '');
502 }
503
504 if (Post::has('pwhash') && Post::t('pwhash')) {
505 XDB::startTransaction();
506 XDB::query('UPDATE accounts
507 SET password = {?}, state = \'active\', registration_date = NOW()
508 WHERE uid = {?} AND state = \'pending\' AND type = \'xnet\'',
509 Post::t('pwhash'), $res['uid']);
510 XDB::query('DELETE FROM register_pending_xnet
511 WHERE uid = {?}',
512 $res['uid']);
513 XDB::commit();
514
515 S::logger($res['uid'])->log('passwd', '');
516
517 // Try to start a session (so the user don't have to log in); we will use
518 // the password available in Post:: to authenticate the user.
519 Post::kill('wait');
520 Platal::session()->startAvailableAuth();
521
522 $page->changeTpl('xnet/register.success.tpl');
523 $page->assign('email', $res['email']);
524 } else {
525 $page->changeTpl('platal/password.tpl');
526 $page->assign('xnet', true);
527 $page->assign('hruid', $res['hruid']);
528 $page->assign('do_auth', 1);
529 }
530 }
531
532 function handler_skin($page)
533 {
534 global $globals;
535
536 $page->changeTpl('platal/skins.tpl');
537 $page->setTitle('Skins');
538
539 if (Env::has('newskin')) { // formulaire soumis, traitons les données envoyées
540 XDB::execute('UPDATE accounts
541 SET skin = {?}
542 WHERE uid = {?}',
543 Env::i('newskin'), S::i('uid'));
544 S::kill('skin');
545 Platal::session()->setSkin();
546 }
547
548 $res = XDB::query('SELECT id
549 FROM skins
550 WHERE skin_tpl = {?}', S::v('skin'));
551 $page->assign('skin_id', $res->fetchOneCell());
552
553 $sql = 'SELECT s.*, auteur, COUNT(*) AS nb
554 FROM skins AS s
555 LEFT JOIN accounts AS a ON (a.skin = s.id)
556 WHERE skin_tpl != \'\' AND ext != \'\'
557 GROUP BY id ORDER BY s.date DESC';
558 $page->assign('skins', XDB::iterator($sql));
559 }
560
561 function handler_exit($page, $level = null)
562 {
563 if (S::suid()) {
564 $old = S::user()->login();
565 S::logger()->log('suid_stop', $old . " by " . S::suid('hruid'));
566 Platal::session()->stopSUID();
567 $target = S::s('suid_startpage');
568 S::kill('suid_startpage');
569 if (!empty($target)) {
570 http_redirect($target);
571 }
572 pl_redirect('admin/user/' . $old);
573 }
574
575 if ($level == 'forget' || $level == 'forgetall') {
576 Platal::session()->killAccessCookie();
577 }
578
579 if ($level == 'forgetuid' || $level == 'forgetall') {
580 Platal::session()->killLoginFormCookies();
581 }
582
583 if (S::logged()) {
584 S::logger()->log('deconnexion', @$_SERVER['HTTP_REFERER']);
585 Platal::session()->destroy();
586 }
587
588 if (Get::has('redirect')) {
589 http_redirect(rawurldecode(Get::v('redirect')));
590 } else {
591 $page->changeTpl('platal/exit.tpl');
592 }
593 }
594
595 function handler_review($page, $action = null, $mode = null)
596 {
597 // Include X-XRDS-Location response-header for Yadis discovery
598 global $globals;
599 header('X-XRDS-Location: ' . $globals->baseurl . '/openid/xrds');
600
601 $this->load('review.inc.php');
602 $dom = 'Review';
603 if (@$GLOBALS['IS_XNET_SITE']) {
604 $dom .= 'Xnet';
605 }
606 $wp = new PlWikiPage($dom . '.Admin');
607 $conf = explode('%0a', $wp->getField('text'));
608 $wiz = new PlWizard('Tour d\'horizon', PlPage::getCoreTpl('plwizard.tpl'), true);
609 foreach ($conf as $line) {
610 $list = preg_split('/\s*[*|]\s*/', $line, -1, PREG_SPLIT_NO_EMPTY);
611 $wiz->addPage('ReviewPage', $list[0], $list[1]);
612 }
613 $wiz->apply($page, 'review', $action, $mode);
614 }
615
616 function handler_test_error($page, $mode = null)
617 {
618 if ($mode == 'js') {
619 $page->changeTpl('platal/error.tpl');
620 } else {
621 throw new Exception("Blih");
622 }
623 }
624 }
625
626 // vim:set et sw=4 sts=4 sws=4 foldmethod=marker fenc=utf-8:
627 ?>