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 $log_to = $user->bestEmail();
374 if (!is_null($inactives_to)) {
375 $log_to = $inactives_to . ', ' . $log_to;
376 $mymail->addTo($inactives_to);
377 }
378 } else {
379 $mymail->addTo($to);
380 $log_to = $to;
381 }
382 $mymail->setSubject("Ton certificat d'authentification");
383 $mymail->setTxtBody("Visite la page suivante qui expire dans six heures :
384 {$globals->baseurl}/tmpPWD/$url
385
386 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.
387
388 --
389 Polytechnique.org
390 \"Le portail des élèves & anciens élèves de l'École polytechnique\"
391
392 Email envoyé à ".Env::v('login') . (is_null($to) ? '' : '
393 Adresse de secours : ' . $to));
394 $mymail->send();
395
396 S::logger($user->id())->log('recovery', $log_to);
397 }
398
399 function handler_recovery_ext($page)
400 {
401 $page->changeTpl('xnet/recovery.tpl');
402
403 if (!Post::has('login')) {
404 return;
405 }
406
407 $user = User::getSilent(Post::t('login'));
408 if (is_null($user)) {
409 $page->trigError('Le compte n\'existe pas.');
410 return;
411 }
412 if ($user->state != 'active') {
413 $page->trigError('Ton compte n\'est pas activé.');
414 return;
415 }
416
417 $page->assign('ok', true);
418
419 $hash = rand_url_id();
420 XDB::execute('INSERT INTO account_lost_passwords (uid, created, certificat)
421 VALUES ({?}, NOW(), {?})',
422 $user->id(), $hash);
423
424 $mymail = new PlMailer('platal/password_recovery_xnet.mail.tpl');
425 $mymail->setTo($user);
426 $mymail->assign('hash', $hash);
427 $mymail->assign('email', Post::t('login'));
428 $mymail->send();
429
430 S::logger($user->id())->log('recovery', $user->bestEmail());
431 }
432
433 function handler_tmpPWD($page, $certif = null)
434 {
435 global $globals;
436 XDB::execute('DELETE FROM account_lost_passwords
437 WHERE DATE_SUB(NOW(), INTERVAL 380 MINUTE) > created');
438
439 if (Post::has('pwhash') && Post::t('pwhash')) {
440 $uid = XDB::fetchOneCell('SELECT uid
441 FROM accounts
442 WHERE hruid = {?}',
443 Post::t('username'));
444 $password = Post::t('pwhash');
445 XDB::query('UPDATE accounts
446 SET password = {?}
447 WHERE uid = {?} AND state = \'active\'',
448 $password, $uid);
449 XDB::query('DELETE FROM account_lost_passwords
450 WHERE certificat = {?}', $certif);
451
452 // If GoogleApps is enabled, and the user did choose to use synchronized passwords,
453 // updates the Google Apps password as well.
454 if ($globals->mailstorage->googleapps_domain) {
455 require_once 'googleapps.inc.php';
456 $account = new GoogleAppsAccount(User::getSilent($uid));
457 if ($account->active() && $account->sync_password) {
458 $account->set_password($password);
459 }
460 }
461
462 S::logger($uid)->log("passwd", "");
463
464 // Try to start a session (so the user don't have to log in); we will use
465 // the password available in Post:: to authenticate the user.
466 Platal::session()->start(AUTH_PASSWD);
467
468 $page->changeTpl('platal/tmpPWD.success.tpl');
469 } else {
470 $res = XDB::query('SELECT uid
471 FROM account_lost_passwords
472 WHERE certificat = {?}', $certif);
473 $ligne = $res->fetchOneAssoc();
474 if (!$ligne) {
475 $page->changeTpl('platal/index.tpl');
476 $page->kill("Cette adresse n'existe pas ou n'existe plus sur le serveur.");
477 }
478
479 $hruid = XDB::fetchOneCell('SELECT hruid
480 FROM accounts
481 WHERE uid = {?}',
482 $ligne['uid']);
483 $page->changeTpl('platal/password.tpl');
484 $page->assign('hruid', $hruid);
485 $page->assign('do_auth', 1);
486 }
487 }
488
489 function handler_register_ext($page, $hash = null)
490 {
491 XDB::execute('DELETE FROM register_pending_xnet
492 WHERE DATE_SUB(NOW(), INTERVAL 1 MONTH) > date');
493 $res = XDB::fetchOneAssoc('SELECT uid, hruid, email
494 FROM register_pending_xnet
495 WHERE hash = {?}',
496 $hash);
497
498 if (is_null($hash) || is_null($res)) {
499 $page->trigErrorRedirect('Cette adresse n\'existe pas ou n\'existe plus sur le serveur.', '');
500 }
501
502 if (Post::has('pwhash') && Post::t('pwhash')) {
503 XDB::startTransaction();
504 XDB::query('UPDATE accounts
505 SET password = {?}, state = \'active\', registration_date = NOW()
506 WHERE uid = {?} AND state = \'pending\' AND type = \'xnet\'',
507 Post::t('pwhash'), $res['uid']);
508 XDB::query('DELETE FROM register_pending_xnet
509 WHERE uid = {?}',
510 $res['uid']);
511 XDB::commit();
512
513 S::logger($res['uid'])->log('passwd', '');
514
515 // Try to start a session (so the user don't have to log in); we will use
516 // the password available in Post:: to authenticate the user.
517 Post::kill('wait');
518 Platal::session()->startAvailableAuth();
519
520 $page->changeTpl('xnet/register.success.tpl');
521 $page->assign('email', $res['email']);
522 } else {
523 $page->changeTpl('platal/password.tpl');
524 $page->assign('xnet', true);
525 $page->assign('hruid', $res['hruid']);
526 $page->assign('do_auth', 1);
527 }
528 }
529
530 function handler_skin($page)
531 {
532 global $globals;
533
534 $page->changeTpl('platal/skins.tpl');
535 $page->setTitle('Skins');
536
537 if (Env::has('newskin')) { // formulaire soumis, traitons les données envoyées
538 XDB::execute('UPDATE accounts
539 SET skin = {?}
540 WHERE uid = {?}',
541 Env::i('newskin'), S::i('uid'));
542 S::kill('skin');
543 Platal::session()->setSkin();
544 }
545
546 $res = XDB::query('SELECT id
547 FROM skins
548 WHERE skin_tpl = {?}', S::v('skin'));
549 $page->assign('skin_id', $res->fetchOneCell());
550
551 $sql = 'SELECT s.*, auteur, COUNT(*) AS nb
552 FROM skins AS s
553 LEFT JOIN accounts AS a ON (a.skin = s.id)
554 WHERE skin_tpl != \'\' AND ext != \'\'
555 GROUP BY id ORDER BY s.date DESC';
556 $page->assign('skins', XDB::iterator($sql));
557 }
558
559 function handler_exit($page, $level = null)
560 {
561 if (S::suid()) {
562 $old = S::user()->login();
563 S::logger()->log('suid_stop', $old . " by " . S::suid('hruid'));
564 Platal::session()->stopSUID();
565 $target = S::s('suid_startpage');
566 S::kill('suid_startpage');
567 if (!empty($target)) {
568 http_redirect($target);
569 }
570 pl_redirect('admin/user/' . $old);
571 }
572
573 if ($level == 'forget' || $level == 'forgetall') {
574 Platal::session()->killAccessCookie();
575 }
576
577 if ($level == 'forgetuid' || $level == 'forgetall') {
578 Platal::session()->killLoginFormCookies();
579 }
580
581 if (S::logged()) {
582 S::logger()->log('deconnexion', @$_SERVER['HTTP_REFERER']);
583 Platal::session()->destroy();
584 }
585
586 if (Get::has('redirect')) {
587 http_redirect(rawurldecode(Get::v('redirect')));
588 } else {
589 $page->changeTpl('platal/exit.tpl');
590 }
591 }
592
593 function handler_review($page, $action = null, $mode = null)
594 {
595 // Include X-XRDS-Location response-header for Yadis discovery
596 global $globals;
597 header('X-XRDS-Location: ' . $globals->baseurl . '/openid/xrds');
598
599 $this->load('review.inc.php');
600 $dom = 'Review';
601 if (@$GLOBALS['IS_XNET_SITE']) {
602 $dom .= 'Xnet';
603 }
604 $wp = new PlWikiPage($dom . '.Admin');
605 $conf = explode('%0a', $wp->getField('text'));
606 $wiz = new PlWizard('Tour d\'horizon', PlPage::getCoreTpl('plwizard.tpl'), true);
607 foreach ($conf as $line) {
608 $list = preg_split('/\s*[*|]\s*/', $line, -1, PREG_SPLIT_NO_EMPTY);
609 $wiz->addPage('ReviewPage', $list[0], $list[1]);
610 }
611 $wiz->apply($page, 'review', $action, $mode);
612 }
613 }
614
615 // vim:set et sw=4 sts=4 sws=4 foldmethod=marker enc=utf-8:
616 ?>