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