Trust the database (i.e. don't encode special characters twice)
[infodrom.org/www.zeitungsliste.de] / lib / login.inc
1 <?php
2
3 include_once('extern/rfc822.php');
4
5 function account_exists($login)
6 {
7   global $cfg;
8   global $_SESSION;
9
10   $query = sprintf("SELECT id FROM users WHERE lower(nickname) = lower('%s')",
11                    pg_escape_string($login));
12
13   if (isset($_SESSION['uid']))
14     $query .= sprintf(' AND id <> %d', $_SESSION['uid']);
15
16   $sth = db_query($query);
17
18   # Return true in case of database error
19   if ($sth === false)
20     return true;
21
22   return pg_num_rows ($sth) > 0;
23 }
24
25 function is_valid_passwd ($nickname, $passwd)
26 {
27   if (strtolower($passwd) == strtolower($nickname))
28     return 'Das Passwort darf nicht dem Usernamen entsprechen.';
29
30   if (strlen($passwd) < 4)
31     return 'Das Passwort ist zu kurz.';
32
33   if (strlen($passwd) > 15)
34     return 'Das Passwort ist zu lang.';
35
36   if (!preg_match ('/^[a-zA-Z0-9%!@#^&\*\$\\\+\.\_\-]{4,15}$/', $passwd, $matches))
37     return 'Das Passwort enthält ungültige Zeichen.';
38
39   return true;
40 }
41
42 function is_valid_username($nickname)
43 {
44   $blacklist = array('hitler','stalin');
45
46   if (!preg_match ('/^[a-zA-Z0-9\.\_\-]{4,15}$/', $nickname, $matches))
47     return false;
48
49
50   foreach ($blacklist as $badnick) {
51     if (soundex($nickname) == soundex($badnick))
52       return false;
53   }
54
55   return true;
56 }
57
58 function is_valid_realname($name)
59 {
60   if (preg_match ('/[\\\\<>"\(\)\[\]]/', $name, $matches))
61     return false;
62
63   return true;
64 }
65
66 function check_account_data()
67 {
68   global $_POST;
69   global $zlist;
70
71   $zlist['replace'] = array('nickname' => $_POST['nickname'],
72                             'realname' => $_POST['realname'],
73                             'email' => $_POST['email'],
74                             'url' => $_POST['url']);
75
76   if (!strlen($_POST['nickname']) ||
77       !strlen($_POST['email']))
78     return 'Sie müssen alle Pflichtfelder ausfüllen! Siehe Beschreibung unten.';
79
80   if (account_exists($_POST['nickname']))
81     return 'Dieser Username ist in der Zeitungsliste bereits vergeben.';
82
83   if (!is_valid_email_address($_POST['email']))
84     return 'Die angegebene Mail-Adresse ist ungültig.';
85
86   if (!empty($_POST['url'])) {
87     $_POST['url'] = fix_url($_POST['url']);
88     if (!is_valid_url($_POST['url']))
89       return 'Die angegebene Homepage ist ungültig!';
90   }
91
92   if (!empty($_POST['realname']) && !is_valid_realname($_POST['realname']))
93     return 'Der angegebene Name enthält unerlaubte Zeichen.';
94
95   return is_valid_username($_POST['nickname']);
96 }
97
98 function check_passwd()
99 {
100   global $_POST;
101
102   if (!strlen($_POST['passwd']) || !strlen($_POST['pwcopy']))
103     return 'Sie müssen alle Pflichtfelder ausfüllen! Siehe Beschreibung unten.';
104
105   if (strlen($_POST['passwd']) < 5)
106     return 'Ihr Passwort ist zu kurz, bitte verwenden Sie mindestens 5 Zeichen.';
107
108   if ($_POST['passwd'] != $_POST['pwcopy'])
109     return 'Sie haben sich beim Passwort vertippt.';
110
111   return true;
112 }
113
114 function passwd ($nickname, $passwd)
115 {
116   return md5(md5($passwd). $nickname);
117 }
118
119 function pwgen ($num)
120 {
121   $chars = "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz";
122   $count = strlen($chars);
123   $pass = '';
124   srand((double)microtime()*1000000);
125
126   for ($i = 0;$i < $num; $i++) {
127     $pos = rand() % $count;
128     $pass .= substr ($chars, $pos, 1);
129   }
130
131   return $pass;
132 }
133
134 function account_activate($code)
135 {
136   global $cfg;
137
138   if (!strlen($code))
139     return 'Kein Aktivierungscode angegeben.';
140
141   $query = sprintf("SELECT uid FROM activation WHERE code = '%s'", pg_escape_string($code));
142
143   $sth = db_query($query);
144
145   if ($sth === false)
146     return 'Es ist ein Datenbankfehler aufgetreten.';
147
148   if (pg_num_rows($sth) != 1)
149     return 'Der angegebene Aktivierungscode ist ungültig!';
150
151   db_query('BEGIN TRANSACTION');
152
153   $row = pg_fetch_array($sth, 0);
154
155   $query = sprintf('UPDATE users SET status = 1 WHERE id = %d', $row['uid']);
156
157   $sth = db_query($query);
158
159   if ($sth === false) {
160     db_query('ROLLBACK');
161     return 'Es ist ein Datenbankfehler aufgetreten.';
162   }
163
164   $query = sprintf("DELETE FROM activation WHERE code = '%s'", pg_escape_string($code));
165
166   $sth = db_query($query);
167
168   if ($sth === false) {
169     db_query('ROLLBACK');
170     return 'Es ist ein Datenbankfehler aufgetreten.';
171   }
172
173   db_query('COMMIT');
174
175   return true;
176 }
177
178 function send_activation($email, $user, $code)
179 {
180   global $cfg;
181   global $_SERVER;
182
183   $url = sprintf('%sactivate.html?code=%s', $cfg['home'], urlencode($code));
184   $subject = 'Aktivierung Account Zeitungsliste';
185   $header = array('Bcc: ' . $cfg['mailto']);
186
187   $body = sprintf('Willkommen %s!
188
189 Sie haben einen neuen Account in der Zeitungsliste beantragt.
190
191 Um diesen zu aktivieren, geben Sie bitte folgende Adresse in
192 Ihren Browser ein:
193
194 %s
195
196 ', $user, $url);
197
198   sendmail($email, '', $subject, $body, $header);
199
200   return true;
201 }
202
203 function process_activate()
204 {
205   global $_GET;
206
207   if (($try = account_activate($_GET['code'])) !== true) {
208     $ret = warning($try);
209     } else {
210       $ret = information('Ihr Account ist aktiviert.');
211       $ret .= '<h3>Willkommen!</h3><p class="info">'.
212         'Sie werden automatisch zur <a href="login.html">Login-Seite</a> weitergeleitet. '.
213         'Bitte melden Sie sich dort an.</p>';
214       $zlist['redirect'] = 'login.html';
215     }
216   return $ret;
217 }
218
219 function process_account_new()
220 {
221   global $cfg;
222   global $_POST;
223
224   if (($try = check_account_data()) !== true)
225     return $try;
226
227   if (($try = check_passwd()) !== true)
228     return $try;
229
230   db_query('BEGIN TRANSACTION');
231
232   $query = sprintf('INSERT INTO users (nickname,email,status,passwd,register_date%s%s)' .
233                    "VALUES ('%s','%s',0,'%s',now()%s%s)",
234                    strlen($_POST['realname'])?',realname':'',
235                    strlen($_POST['url'])?',url':'',
236                    $_POST['nickname'],
237                    $_POST['email'],
238                    passwd($_POST['nickname'], $_POST['passwd']),
239                    strlen($_POST['realname'])?",'".pg_escape_string($_POST['realname'])."'":'',
240                    strlen($_POST['url'])?",'".pg_escape_string($_POST['url'])."'":'');
241
242   $sth = db_query($query);
243
244   if ($sth === false) {
245     db_query('ROLLBACK');
246     return 'Es ist ein Datenbankfehler aufgetreten.';
247   }
248
249   $uid = db_last_id('users', 'id');
250
251   $code = md5(pwgen(8));
252
253   $query = sprintf('INSERT INTO activation (code,uid,register_date) '.
254                    "VALUES ('%s',%d,now())", $code, $uid);
255
256   $sth = db_query($query);
257
258   if ($sth === false) {
259     db_query('ROLLBACK');
260     return 'Es ist ein Datenbankfehler aufgetreten.';
261   }
262
263   if (send_activation($_POST['email'],
264                       strlen($_POST['realname'])?$_POST['realname']:$_POST['nickname'],
265                       $code) === false) {
266     db_query('ROLLBACK');
267     return 'Fehler beim Versenden der Aktivierungsmail.';
268   }
269
270   db_query('COMMIT');
271
272   return true;
273 }
274
275 function update_account()
276 {
277   global $_POST;
278   global $_SESSION;
279   global $zlist;
280
281   $query = sprintf("UPDATE users SET nickname='%s',realname='%s',email='%s',url='%s' ".
282                    'WHERE id = %d',
283                    $_POST['nickname'],
284                    pg_escape_string($_POST['realname']),
285                    $_POST['email'],
286                    pg_escape_string($_POST['url']),
287                    $_SESSION['uid']);
288
289   db_query($query);
290
291   if ($_SESSION['nickname'] != $_POST['nickname'])
292     $zlist['newpass'] = true;
293
294   $_SESSION['nickname'] = $_POST['nickname'];
295   $_SESSION['email'] = $_POST['email'];
296   $_SESSION['homepage'] = $_POST['url'];
297   $_SESSION['realname'] = $_POST['realname'];
298 }
299
300 function update_passwd()
301 {
302   global $_POST;
303   global $_SESSION;
304
305   $hash = passwd($_SESSION['nickname'], $_POST['passwd']);
306
307   $query = sprintf("UPDATE users SET passwd='%s' WHERE id = %d",
308                    $hash, $_SESSION['uid']);
309
310   db_query($query);
311 }
312
313 function checkpass($nickname, $passwd)
314 {
315   global $cfg;
316
317   if (empty($nickname) || empty($passwd))
318     return false;
319
320   $query = sprintf("SELECT passwd,nickname FROM users ".
321                    "WHERE lower(nickname) = lower('%s') AND status = 1",
322                    pg_escape_string($nickname));
323
324   $sth = db_query($query);
325
326   if ($sth === false)
327     return false;
328
329   if (pg_num_rows($sth) == 0)
330     return false;
331
332   $row = pg_fetch_array($sth, 0);
333
334   $hash = passwd($row['nickname'], $passwd);
335
336   if (strcmp($row[0], $hash) != 0)
337     return false;
338
339   return true;
340 }
341
342 function login_user($nickname, $passwd)
343 {
344   global $cfg;
345   global $_SERVER;
346   global $_POST;
347   global $_SESSION;
348
349   $query = sprintf("SELECT id,nickname,realname,email,url,passwd FROM users " .
350                    "WHERE lower(nickname) = lower('%s') AND status = 1",
351                    pg_escape_string($nickname));
352
353   $sth = db_query($query);
354
355   if ($sth === false)
356     return false;
357
358   if (pg_num_rows($sth) == 0)
359     return false;
360
361   $row = pg_fetch_array($sth, 0);
362
363   $hash = passwd($row['nickname'], $passwd);
364
365   if (strcmp($row['passwd'], $hash) != 0)
366     return false;
367
368   session_name($cfg['session']);
369   session_start();
370
371   $_SESSION['REMOTE_ADDR'] = $_SERVER["REMOTE_ADDR"];
372   $_SESSION['uid'] = $row['id'];
373   $_SESSION['nickname'] = $row['nickname'];
374   $_SESSION['javascript'] = $_POST['js']=='1'?true:false;
375   $_SESSION['email'] = $row['email'];
376   $_SESSION['homepage'] = $row['url'];
377   $_SESSION['realname'] = $row['realname'];
378
379   $query = sprintf('UPDATE users SET last_login = now() WHERE id = %d', $_SESSION['uid']);
380   db_query($query);
381
382   $query = sprintf('INSERT INTO online (uid,activity) VALUES (%d,now())', $_SESSION['uid']);
383   db_query($query);
384
385   session_update();
386   $_SESSION["lastupdate"] = time();
387
388   return true;
389 }
390
391 function login_sendnew($nickname)
392 {
393   global $cfg;
394
395   if (!strlen($nickname))
396     return 'Kein Username angegeben!';
397
398   $query = 'SELECT id,email,nickname,realname FROM users WHERE status = 1 AND ';
399   if (strstr($nickname, '@') === false)
400     $query .= sprintf("lower(nickname) = lower('%s')", pg_escape_string($nickname));
401   else
402     $query .= sprintf("lower(email) = lower('%s')", pg_escape_string($nickname));
403
404   $sth = db_query($query);
405
406   if ($sth === false)
407     return 'Es ist ein Datenbankfehler aufgetreten.';
408
409   if (pg_num_rows($sth) == 0)
410     return 'Der angegebene Username ist im System nicht bekannt.';
411
412   $row = pg_fetch_array($sth, 0);
413
414   $passwd = pwgen(8);
415
416   $query = sprintf("UPDATE users SET passwd = '%s' WHERE id = %d",
417                    passwd($row['nickname'], $passwd), $row['id']);
418
419   $sth = db_query($query);
420
421   if ($sth === false)
422     return 'Es ist ein Datenbankfehler aufgetreten.';
423
424   $header = array();
425   $header[] = 'From: ' . $cfg['from'];
426   $header[] = sprintf('To: %s <%s>',
427                       strlen($row['realname'])?$row['realname']:$row['nickname'],
428                       $row['email']);
429   $header[] = 'MIME-Version: 1.0';
430   $header[] = 'Content-type: text/plain; charset=utf-8';
431
432   $subject = 'Zeitungsliste: Ihr Neues Passwort';
433
434   $body = sprintf('Moin %s!
435
436 Sie oder jemand anderes hat ein neues Passwort für Ihren Account auf
437 <%s> angefordert.
438
439 Das alte Passwort verliert damit seine Gültigkeit.  Das System hat
440 ein neues Passwort für Sie berechnet und eingetragen.
441
442 Das neue Passwort lautet %s
443
444 Bitte ändern Sie es beim nächsten Einloggen.
445 ',
446                   strlen($row['realname'])?$row['realname']:$row['nickname'],
447                   $cfg['home'], $passwd);
448
449   $sig = load_template('signature', array());
450   if (strlen($sig))
451     $body .= $sig;
452
453   if (mail ($row['email'], $subject, $body, implode("\n", $header)) === false)
454     return 'Es ist ein Fehler beim Versand der Mail aufgetreten.';
455
456   return true;
457 }
458
459 function process_login_request()
460 {
461   global $_POST;
462
463   if (isset($_POST['new'])) {
464     $try = process_account_new();
465     if ($try === true)
466       return information('Ihr neuer Account wurde angelegt.  Die Aktivierungsmail ist unterwegs.');
467     else {
468       $body = warning($try);
469       $replace = array('nickname' => $_POST['nickname'],
470                        'realname' => $_POST['realname'],
471                        'email' => $_POST['email'],
472                        'url' => $_POST['url']);
473       $body .= load_template('login_new.html', $replace);
474       return $body;
475     }
476   } elseif (isset($_POST['sendnew'])) {
477     $try = login_sendnew($_POST['nickname']);
478     if ($try === true) {
479       $ret = information('Ein neues Passwort wird Ihnen per Mail zugeschickt');
480       $ret .= '<p>Bitte melden Sie sich mit diesem an und ändern Sie das Passwort '.
481         'über den Menüpunkt "Einstellungen".</p>';
482       return $ret;
483     } else {
484       $body = warning($try);
485       $body .= load_template('login.html');
486       return $body;
487     }
488   } else {
489     if (empty($_POST['nickname'])) {
490       $body = warning('Sie haben keinen Usernamen angegeben!');
491     } elseif (empty($_POST['passwd'])) {
492       $body = warning('Sie haben kein Passwort angegeben!');
493     } else {
494       $try = login_user($_POST['nickname'], $_POST['passwd']);
495       if ($try === true)
496         return true;
497       else
498         $body = warning('Das angegebene Passwort ist ungültig!');
499     }
500
501     $replace = array('nickname' => $_POST['nickname']);
502     $body .= load_template('login.html', $replace);
503     return $body;
504   }
505 }
506
507 function last_activity()
508 {
509   global $cfg;
510   global $_SESSION;
511
512   $query = sprintf("SELECT DISTINCT topics.id,topics.topic FROM article " .
513                    "JOIN topics ON article.topic=topics.id " .
514                    "WHERE uid = %d AND article.created > now() - interval'7 days'",
515                    $_SESSION['uid']);
516
517   $sth = db_query($query);
518
519   if ($sth === false || pg_num_rows($sth) == 0)
520     return false;
521
522   $ret = '<h3>Sie haben an folgenden Diskussionen teilgenommen</h3>';
523   $ret .= '<p><ul class="gold">';
524
525   for ($i=0; $i < pg_num_rows($sth); $i++) {
526     $row = pg_fetch_array($sth, $i);
527
528     $ret .= sprintf('<li><a href="%stopic/%d.html">%s</a></li>',
529                     $cfg['basedir'],
530                     $row['id'], $row['topic']);
531   }
532   $ret .= '</ul></p>';
533
534   return $ret;
535 }
536
537 function process_login()
538 {
539   global $_GET;
540   global $_SERVER;
541   global $_SESSION;
542
543   if (isset($_GET['from'])) {
544     if ($_GET['from'] == 'article')
545       $ret .= warning('Um an einer Diskussion teilzunehmen, müssen Sie angemeldet sein.');
546     elseif ($_GET['from'] == 'zeitung')
547       $ret .= warning('Um eine neue Diskussion zu beginnen, müssen Sie angemeldet sein.');
548     elseif ($_GET['from'] == 'new')
549       $ret .= warning('Um eine Zeitung hinzuzufügen, müssen Sie angemeldet sein.');
550     elseif ($_GET['from'] == 'tags')
551       $ret .= warning('Um Tags zu einer Zeitung zu verwalten, müssen Sie angemeldet sein.');
552     elseif ($_GET['from'] == 'edit')
553       $ret .= warning('Um eine Zeitung zu bearbeiten, müssen Sie angemeldet sein.');
554     elseif ($_GET['from'] == 'session')
555       $ret .= warning('Ihre Session ist abgelaufen.  Bitte melden Sie sich erneut an.');
556     $ret .= load_template('login.html');
557   } elseif (isset($_GET['new'])) {
558     $ret = load_template('login_new.html');
559   } elseif ($_SERVER['REQUEST_METHOD'] == 'GET') {
560     $ret = load_template('login.html');
561   } else {
562     $try = process_login_request();
563     if ($try === true) {
564       $ret = information('Hallo '.$_SESSION['nickname'].'! Sie sind jetzt angemeldet.');
565       $try = last_activity();
566       if ($try !== false)
567         $ret .= $try;
568       else
569         $ret .= load_template('main.html');
570     } else
571       $ret = $try;
572   }
573
574   return $ret;
575 }
576
577 function process_passwd()
578 {
579   global $_SESSION;
580   global $_SERVER;
581   global $zlist;
582   global $cfg;
583
584   if (!isset($_SESSION['uid'])) {
585     $ret = warning('Sie sind nicht angemeldet.  Sie werden gleich zur Anmeldung weitergeleitet.');
586     $zlist['redirect'] = 'login.html';
587   } else {
588     if ($_SERVER['REQUEST_METHOD'] == 'GET') {
589       $ret .= load_javascript('passwd.js') . load_template('passwd.html', array('nickname' => $_SESSION['nickname']));
590     } elseif ($_SERVER['REQUEST_METHOD'] == 'POST') {
591       if (($try = check_passwd()) !== true) {
592         $ret = warning($try);
593         $ret .= load_javascript('passwd.js') . load_template('passwd.html', array('nickname' => $_SESSION['nickname']));
594       } else {
595         update_passwd();
596         $ret .= information('Ihr neues Passwort ist eingetragen.  Sie werden zur Hauptseite weitergeleitet.');
597         $zlist['redirect'] = '';
598       }
599     }
600   }
601
602   return $ret;
603 }
604
605 function logout()
606 {
607   session_invalidate();
608 }
609
610 function process_options()
611 {
612   global $_SESSION;
613   global $_SERVER;
614   global $_POST;
615   global $zlist;
616   global $cfg;
617
618   $ok = false;
619   $ret = '';
620   if (!isset($_SESSION['uid'])) {
621     $ret .= warning('Sie sind nicht angemeldet.  Sie werden gleich zur Anmeldung weitergeleitet.');
622     $zlist['redirect'] = 'login.html';
623   } else {
624     if ($_SERVER['REQUEST_METHOD'] == 'POST') {
625       if (($try = check_account_data()) !== true)
626         $ret .= warning($try);
627       else {
628         update_account();
629         $ret .= information('Ihre persönlichen Daten wurden geändert.  Sie werden zur Hauptseite weitergeleitet.');
630         $ok = true;
631         $zlist['redirect'] = '';
632         if ($zlist['newpass']) {
633           $try = login_sendnew($_POST['nickname']);
634           if ($try === true) {
635             $ret .= '<p>Aus technischen Gründen ist es erforderlich Ihnen ein neues Passwort zu geben. '.
636               'Dieses wird Ihnen per Mail an die im System gespeicherte Mail-Adresse geschickt. '.
637               'Diese Session ist von dieser Änderung nicht betroffen, Sie können daher Ihr Passwort '.
638               'direkt über den Menüpunkt "Passwort" ändern und die Mail ignorieren. '.
639               'Ansonsten melden Sie sich bitte beim nächsten Mal mit dem neuen Passwort an und ändern Sie es.</p>';
640           }
641         }
642       }
643     } else
644       $zlist['replace'] = array('nickname' => $_SESSION['nickname'],
645                                 'realname' => $_SESSION['realname'],
646                                 'email' => $_SESSION['email'],
647                                 'url' => $_SESSION['url']);
648     if (!$ok)
649       $ret .= load_javascript('options.js') . load_template('options.html', $zlist['replace']);
650   }
651
652   return $ret;
653 }
654
655 function ajax_passwd_check()
656 {
657   global $_POST;
658
659   return checkpass($_POST['nickname'], $_POST['passwd']);
660 }
661
662 function ajax_nickname_check()
663 {
664   global $_POST;
665
666   return account_exists($_POST['nickname']);
667 }
668
669 ?>