Creating repository for dokuwiki modifications for sudaraka.org
[sudaraka-org:dokuwiki-mods.git] / inc / auth.php
1 <?php
2 /**
3  * Authentication library
4  *
5  * Including this file will automatically try to login
6  * a user by calling auth_login()
7  *
8  * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
9  * @author     Andreas Gohr <andi@splitbrain.org>
10  */
11
12 if(!defined('DOKU_INC')) die('meh.');
13
14 // some ACL level defines
15 define('AUTH_NONE',0);
16 define('AUTH_READ',1);
17 define('AUTH_EDIT',2);
18 define('AUTH_CREATE',4);
19 define('AUTH_UPLOAD',8);
20 define('AUTH_DELETE',16);
21 define('AUTH_ADMIN',255);
22
23 /**
24  * Initialize the auth system.
25  *
26  * This function is automatically called at the end of init.php
27  *
28  * This used to be the main() of the auth.php
29  *
30  * @todo backend loading maybe should be handled by the class autoloader
31  * @todo maybe split into multiple functions at the XXX marked positions
32  */
33 function auth_setup(){
34     global $conf;
35     global $auth;
36     global $AUTH_ACL;
37     global $lang;
38     global $config_cascade;
39     $AUTH_ACL = array();
40
41     if(!$conf['useacl']) return false;
42
43     // load the the backend auth functions and instantiate the auth object XXX
44     if (@file_exists(DOKU_INC.'inc/auth/'.$conf['authtype'].'.class.php')) {
45         require_once(DOKU_INC.'inc/auth/basic.class.php');
46         require_once(DOKU_INC.'inc/auth/'.$conf['authtype'].'.class.php');
47
48         $auth_class = "auth_".$conf['authtype'];
49         if (class_exists($auth_class)) {
50             $auth = new $auth_class();
51             if ($auth->success == false) {
52                 // degrade to unauthenticated user
53                 unset($auth);
54                 auth_logoff();
55                 msg($lang['authtempfail'], -1);
56             }
57         } else {
58             nice_die($lang['authmodfailed']);
59         }
60     } else {
61         nice_die($lang['authmodfailed']);
62     }
63
64     if(!$auth) return;
65
66     // do the login either by cookie or provided credentials XXX
67     if (!isset($_REQUEST['u'])) $_REQUEST['u'] = '';
68     if (!isset($_REQUEST['p'])) $_REQUEST['p'] = '';
69     if (!isset($_REQUEST['r'])) $_REQUEST['r'] = '';
70     $_REQUEST['http_credentials'] = false;
71     if (!$conf['rememberme']) $_REQUEST['r'] = false;
72
73     // handle renamed HTTP_AUTHORIZATION variable (can happen when a fix like
74     // the one presented at
75     // http://www.besthostratings.com/articles/http-auth-php-cgi.html is used
76     // for enabling HTTP authentication with CGI/SuExec)
77     if(isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION']))
78         $_SERVER['HTTP_AUTHORIZATION'] = $_SERVER['REDIRECT_HTTP_AUTHORIZATION'];
79     // streamline HTTP auth credentials (IIS/rewrite -> mod_php)
80     if(isset($_SERVER['HTTP_AUTHORIZATION'])){
81         list($_SERVER['PHP_AUTH_USER'],$_SERVER['PHP_AUTH_PW']) =
82             explode(':', base64_decode(substr($_SERVER['HTTP_AUTHORIZATION'], 6)));
83     }
84
85     // if no credentials were given try to use HTTP auth (for SSO)
86     if(empty($_REQUEST['u']) && empty($_COOKIE[DOKU_COOKIE]) && !empty($_SERVER['PHP_AUTH_USER'])){
87         $_REQUEST['u'] = $_SERVER['PHP_AUTH_USER'];
88         $_REQUEST['p'] = $_SERVER['PHP_AUTH_PW'];
89         $_REQUEST['http_credentials'] = true;
90     }
91
92     // apply cleaning
93     $_REQUEST['u'] = $auth->cleanUser($_REQUEST['u']);
94
95     if(isset($_REQUEST['authtok'])){
96         // when an authentication token is given, trust the session
97         auth_validateToken($_REQUEST['authtok']);
98     }elseif(!is_null($auth) && $auth->canDo('external')){
99         // external trust mechanism in place
100         $auth->trustExternal($_REQUEST['u'],$_REQUEST['p'],$_REQUEST['r']);
101     }else{
102         $evdata = array(
103                 'user'     => $_REQUEST['u'],
104                 'password' => $_REQUEST['p'],
105                 'sticky'   => $_REQUEST['r'],
106                 'silent'   => $_REQUEST['http_credentials'],
107                 );
108         trigger_event('AUTH_LOGIN_CHECK', $evdata, 'auth_login_wrapper');
109     }
110
111     //load ACL into a global array XXX
112     $AUTH_ACL = auth_loadACL();
113 }
114
115 /**
116  * Loads the ACL setup and handle user wildcards
117  *
118  * @author Andreas Gohr <andi@splitbrain.org>
119  * @returns array
120  */
121 function auth_loadACL(){
122     global $config_cascade;
123
124     if(!is_readable($config_cascade['acl']['default'])) return array();
125
126     $acl = file($config_cascade['acl']['default']);
127
128     //support user wildcard
129     if(isset($_SERVER['REMOTE_USER'])){
130         $len = count($acl);
131         for($i=0; $i<$len; $i++){
132             if($acl[$i]{0} == '#') continue;
133             list($id,$rest) = preg_split('/\s+/',$acl[$i],2);
134             $id   = str_replace('%USER%',cleanID($_SERVER['REMOTE_USER']),$id);
135             $rest = str_replace('%USER%',auth_nameencode($_SERVER['REMOTE_USER']),$rest);
136             $acl[$i] = "$id\t$rest";
137         }
138     }
139     return $acl;
140 }
141
142 function auth_login_wrapper($evdata) {
143     return auth_login($evdata['user'],
144                       $evdata['password'],
145                       $evdata['sticky'],
146                       $evdata['silent']);
147 }
148
149 /**
150  * This tries to login the user based on the sent auth credentials
151  *
152  * The authentication works like this: if a username was given
153  * a new login is assumed and user/password are checked. If they
154  * are correct the password is encrypted with blowfish and stored
155  * together with the username in a cookie - the same info is stored
156  * in the session, too. Additonally a browserID is stored in the
157  * session.
158  *
159  * If no username was given the cookie is checked: if the username,
160  * crypted password and browserID match between session and cookie
161  * no further testing is done and the user is accepted
162  *
163  * If a cookie was found but no session info was availabe the
164  * blowfish encrypted password from the cookie is decrypted and
165  * together with username rechecked by calling this function again.
166  *
167  * On a successful login $_SERVER[REMOTE_USER] and $USERINFO
168  * are set.
169  *
170  * @author  Andreas Gohr <andi@splitbrain.org>
171  *
172  * @param   string  $user    Username
173  * @param   string  $pass    Cleartext Password
174  * @param   bool    $sticky  Cookie should not expire
175  * @param   bool    $silent  Don't show error on bad auth
176  * @return  bool             true on successful auth
177  */
178 function auth_login($user,$pass,$sticky=false,$silent=false){
179     global $USERINFO;
180     global $conf;
181     global $lang;
182     global $auth;
183     $sticky ? $sticky = true : $sticky = false; //sanity check
184
185     if (!$auth) return false;
186
187     if(!empty($user)){
188         //usual login
189         if ($auth->checkPass($user,$pass)){
190             // make logininfo globally available
191             $_SERVER['REMOTE_USER'] = $user;
192             $secret = auth_cookiesalt(!$sticky); //bind non-sticky to session
193             auth_setCookie($user,PMA_blowfish_encrypt($pass,$secret),$sticky);
194             return true;
195         }else{
196             //invalid credentials - log off
197             if(!$silent) msg($lang['badlogin'],-1);
198             auth_logoff();
199             return false;
200         }
201     }else{
202         // read cookie information
203         list($user,$sticky,$pass) = auth_getCookie();
204         if($user && $pass){
205             // we got a cookie - see if we can trust it
206
207             // get session info
208             $session = $_SESSION[DOKU_COOKIE]['auth'];
209             if(isset($session) &&
210                     $auth->useSessionCache($user) &&
211                     ($session['time'] >= time()-$conf['auth_security_timeout']) &&
212                     ($session['user'] == $user) &&
213                     ($session['pass'] == sha1($pass)) &&  //still crypted
214                     ($session['buid'] == auth_browseruid()) ){
215
216                 // he has session, cookie and browser right - let him in
217                 $_SERVER['REMOTE_USER'] = $user;
218                 $USERINFO = $session['info']; //FIXME move all references to session
219                 return true;
220             }
221             // no we don't trust it yet - recheck pass but silent
222             $secret = auth_cookiesalt(!$sticky); //bind non-sticky to session
223             $pass = PMA_blowfish_decrypt($pass,$secret);
224             return auth_login($user,$pass,$sticky,true);
225         }
226     }
227     //just to be sure
228     auth_logoff(true);
229     return false;
230 }
231
232 /**
233  * Checks if a given authentication token was stored in the session
234  *
235  * Will setup authentication data using data from the session if the
236  * token is correct. Will exit with a 401 Status if not.
237  *
238  * @author Andreas Gohr <andi@splitbrain.org>
239  * @param  string $token The authentication token
240  * @return boolean true (or will exit on failure)
241  */
242 function auth_validateToken($token){
243     if(!$token || $token != $_SESSION[DOKU_COOKIE]['auth']['token']){
244         // bad token
245         header("HTTP/1.0 401 Unauthorized");
246         print 'Invalid auth token - maybe the session timed out';
247         unset($_SESSION[DOKU_COOKIE]['auth']['token']); // no second chance
248         exit;
249     }
250     // still here? trust the session data
251     global $USERINFO;
252     $_SERVER['REMOTE_USER'] = $_SESSION[DOKU_COOKIE]['auth']['user'];
253     $USERINFO = $_SESSION[DOKU_COOKIE]['auth']['info'];
254     return true;
255 }
256
257 /**
258  * Create an auth token and store it in the session
259  *
260  * NOTE: this is completely unrelated to the getSecurityToken() function
261  *
262  * @author Andreas Gohr <andi@splitbrain.org>
263  * @return string The auth token
264  */
265 function auth_createToken(){
266     $token = md5(mt_rand());
267     @session_start(); // reopen the session if needed
268     $_SESSION[DOKU_COOKIE]['auth']['token'] = $token;
269     session_write_close();
270     return $token;
271 }
272
273 /**
274  * Builds a pseudo UID from browser and IP data
275  *
276  * This is neither unique nor unfakable - still it adds some
277  * security. Using the first part of the IP makes sure
278  * proxy farms like AOLs are stil okay.
279  *
280  * @author  Andreas Gohr <andi@splitbrain.org>
281  *
282  * @return  string  a MD5 sum of various browser headers
283  */
284 function auth_browseruid(){
285     $ip   = clientIP(true);
286     $uid  = '';
287     $uid .= $_SERVER['HTTP_USER_AGENT'];
288     $uid .= $_SERVER['HTTP_ACCEPT_ENCODING'];
289     $uid .= $_SERVER['HTTP_ACCEPT_LANGUAGE'];
290     $uid .= $_SERVER['HTTP_ACCEPT_CHARSET'];
291     $uid .= substr($ip,0,strpos($ip,'.'));
292     return md5($uid);
293 }
294
295 /**
296  * Creates a random key to encrypt the password in cookies
297  *
298  * This function tries to read the password for encrypting
299  * cookies from $conf['metadir'].'/_htcookiesalt'
300  * if no such file is found a random key is created and
301  * and stored in this file.
302  *
303  * @author  Andreas Gohr <andi@splitbrain.org>
304  * @param   bool $addsession if true, the sessionid is added to the salt
305  * @return  string
306  */
307 function auth_cookiesalt($addsession=false){
308     global $conf;
309     $file = $conf['metadir'].'/_htcookiesalt';
310     $salt = io_readFile($file);
311     if(empty($salt)){
312         $salt = uniqid(rand(),true);
313         io_saveFile($file,$salt);
314     }
315     if($addsession){
316         $salt .= session_id();
317     }
318     return $salt;
319 }
320
321 /**
322  * Log out the current user
323  *
324  * This clears all authentication data and thus log the user
325  * off. It also clears session data.
326  *
327  * @author  Andreas Gohr <andi@splitbrain.org>
328  * @param bool $keepbc - when true, the breadcrumb data is not cleared
329  */
330 function auth_logoff($keepbc=false){
331     global $conf;
332     global $USERINFO;
333     global $INFO, $ID;
334     global $auth;
335
336     // make sure the session is writable (it usually is)
337     @session_start();
338
339     if(isset($_SESSION[DOKU_COOKIE]['auth']['user']))
340         unset($_SESSION[DOKU_COOKIE]['auth']['user']);
341     if(isset($_SESSION[DOKU_COOKIE]['auth']['pass']))
342         unset($_SESSION[DOKU_COOKIE]['auth']['pass']);
343     if(isset($_SESSION[DOKU_COOKIE]['auth']['info']))
344         unset($_SESSION[DOKU_COOKIE]['auth']['info']);
345     if(!$keepbc && isset($_SESSION[DOKU_COOKIE]['bc']))
346         unset($_SESSION[DOKU_COOKIE]['bc']);
347     if(isset($_SERVER['REMOTE_USER']))
348         unset($_SERVER['REMOTE_USER']);
349     $USERINFO=null; //FIXME
350
351     $cookieDir = empty($conf['cookiedir']) ? DOKU_REL : $conf['cookiedir'];
352     if (version_compare(PHP_VERSION, '5.2.0', '>')) {
353         setcookie(DOKU_COOKIE,'',time()-600000,$cookieDir,'',($conf['securecookie'] && is_ssl()),true);
354     }else{
355         setcookie(DOKU_COOKIE,'',time()-600000,$cookieDir,'',($conf['securecookie'] && is_ssl()));
356     }
357
358     if($auth) $auth->logOff();
359 }
360
361 /**
362  * Check if a user is a manager
363  *
364  * Should usually be called without any parameters to check the current
365  * user.
366  *
367  * The info is available through $INFO['ismanager'], too
368  *
369  * @author Andreas Gohr <andi@splitbrain.org>
370  * @see    auth_isadmin
371  * @param  string user      - Username
372  * @param  array  groups    - List of groups the user is in
373  * @param  bool   adminonly - when true checks if user is admin
374  */
375 function auth_ismanager($user=null,$groups=null,$adminonly=false){
376     global $conf;
377     global $USERINFO;
378     global $auth;
379
380     if (!$auth) return false;
381     if(is_null($user)) {
382         if (!isset($_SERVER['REMOTE_USER'])) {
383             return false;
384         } else {
385             $user = $_SERVER['REMOTE_USER'];
386         }
387     }
388     if(is_null($groups)){
389         $groups = (array) $USERINFO['grps'];
390     }
391
392     // check superuser match
393     if(auth_isMember($conf['superuser'],$user, $groups)) return true;
394     if($adminonly) return false;
395     // check managers
396     if(auth_isMember($conf['manager'],$user, $groups)) return true;
397
398     return false;
399 }
400
401 /**
402  * Check if a user is admin
403  *
404  * Alias to auth_ismanager with adminonly=true
405  *
406  * The info is available through $INFO['isadmin'], too
407  *
408  * @author Andreas Gohr <andi@splitbrain.org>
409  * @see auth_ismanager
410  */
411 function auth_isadmin($user=null,$groups=null){
412     return auth_ismanager($user,$groups,true);
413 }
414
415
416 /**
417  * Match a user and his groups against a comma separated list of
418  * users and groups to determine membership status
419  *
420  * Note: all input should NOT be nameencoded.
421  *
422  * @param $memberlist string commaseparated list of allowed users and groups
423  * @param $user       string user to match against
424  * @param $groups     array  groups the user is member of
425  * @returns bool      true for membership acknowledged
426  */
427 function auth_isMember($memberlist,$user,array $groups){
428     global $auth;
429     if (!$auth) return false;
430
431     // clean user and groups
432     if(!$auth->isCaseSensitive()){
433         $user = utf8_strtolower($user);
434         $groups = array_map('utf8_strtolower',$groups);
435     }
436     $user = $auth->cleanUser($user);
437     $groups = array_map(array($auth,'cleanGroup'),$groups);
438
439     // extract the memberlist
440     $members = explode(',',$memberlist);
441     $members = array_map('trim',$members);
442     $members = array_unique($members);
443     $members = array_filter($members);
444
445     // compare cleaned values
446     foreach($members as $member){
447         if(!$auth->isCaseSensitive()) $member = utf8_strtolower($member);
448         if($member[0] == '@'){
449             $member = $auth->cleanGroup(substr($member,1));
450             if(in_array($member, $groups)) return true;
451         }else{
452             $member = $auth->cleanUser($member);
453             if($member == $user) return true;
454         }
455     }
456
457     // still here? not a member!
458     return false;
459 }
460
461 /**
462  * Convinience function for auth_aclcheck()
463  *
464  * This checks the permissions for the current user
465  *
466  * @author  Andreas Gohr <andi@splitbrain.org>
467  *
468  * @param  string  $id  page ID (needs to be resolved and cleaned)
469  * @return int          permission level
470  */
471 function auth_quickaclcheck($id){
472     global $conf;
473     global $USERINFO;
474     # if no ACL is used always return upload rights
475     if(!$conf['useacl']) return AUTH_UPLOAD;
476     return auth_aclcheck($id,$_SERVER['REMOTE_USER'],$USERINFO['grps']);
477 }
478
479 /**
480  * Returns the maximum rights a user has for
481  * the given ID or its namespace
482  *
483  * @author  Andreas Gohr <andi@splitbrain.org>
484  *
485  * @param  string  $id     page ID (needs to be resolved and cleaned)
486  * @param  string  $user   Username
487  * @param  array   $groups Array of groups the user is in
488  * @return int             permission level
489  */
490 function auth_aclcheck($id,$user,$groups){
491     global $conf;
492     global $AUTH_ACL;
493     global $auth;
494
495     // if no ACL is used always return upload rights
496     if(!$conf['useacl']) return AUTH_UPLOAD;
497     if (!$auth) return AUTH_NONE;
498
499     //make sure groups is an array
500     if(!is_array($groups)) $groups = array();
501
502     //if user is superuser or in superusergroup return 255 (acl_admin)
503     if(auth_isadmin($user,$groups)) { return AUTH_ADMIN; }
504
505     $ci = '';
506     if(!$auth->isCaseSensitive()) $ci = 'ui';
507
508     $user = $auth->cleanUser($user);
509     $groups = array_map(array($auth,'cleanGroup'),(array)$groups);
510     $user = auth_nameencode($user);
511
512     //prepend groups with @ and nameencode
513     $cnt = count($groups);
514     for($i=0; $i<$cnt; $i++){
515         $groups[$i] = '@'.auth_nameencode($groups[$i]);
516     }
517
518     $ns    = getNS($id);
519     $perm  = -1;
520
521     if($user || count($groups)){
522         //add ALL group
523         $groups[] = '@ALL';
524         //add User
525         if($user) $groups[] = $user;
526         //build regexp
527         $regexp   = join('|',$groups);
528     }else{
529         $regexp = '@ALL';
530     }
531
532     //check exact match first
533     $matches = preg_grep('/^'.preg_quote($id,'/').'\s+('.$regexp.')\s+/'.$ci,$AUTH_ACL);
534     if(count($matches)){
535         foreach($matches as $match){
536             $match = preg_replace('/#.*$/','',$match); //ignore comments
537             $acl   = preg_split('/\s+/',$match);
538             if($acl[2] > AUTH_DELETE) $acl[2] = AUTH_DELETE; //no admins in the ACL!
539             if($acl[2] > $perm){
540                 $perm = $acl[2];
541             }
542         }
543         if($perm > -1){
544             //we had a match - return it
545             return $perm;
546         }
547     }
548
549     //still here? do the namespace checks
550     if($ns){
551         $path = $ns.':*';
552     }else{
553         $path = '*'; //root document
554     }
555
556     do{
557         $matches = preg_grep('/^'.preg_quote($path,'/').'\s+('.$regexp.')\s+/'.$ci,$AUTH_ACL);
558         if(count($matches)){
559             foreach($matches as $match){
560                 $match = preg_replace('/#.*$/','',$match); //ignore comments
561                 $acl   = preg_split('/\s+/',$match);
562                 if($acl[2] > AUTH_DELETE) $acl[2] = AUTH_DELETE; //no admins in the ACL!
563                 if($acl[2] > $perm){
564                     $perm = $acl[2];
565                 }
566             }
567             //we had a match - return it
568             return $perm;
569         }
570
571         //get next higher namespace
572         $ns   = getNS($ns);
573
574         if($path != '*'){
575             $path = $ns.':*';
576             if($path == ':*') $path = '*';
577         }else{
578             //we did this already
579             //looks like there is something wrong with the ACL
580             //break here
581             msg('No ACL setup yet! Denying access to everyone.');
582             return AUTH_NONE;
583         }
584     }while(1); //this should never loop endless
585
586     //still here? return no permissions
587     return AUTH_NONE;
588 }
589
590 /**
591  * Encode ASCII special chars
592  *
593  * Some auth backends allow special chars in their user and groupnames
594  * The special chars are encoded with this function. Only ASCII chars
595  * are encoded UTF-8 multibyte are left as is (different from usual
596  * urlencoding!).
597  *
598  * Decoding can be done with rawurldecode
599  *
600  * @author Andreas Gohr <gohr@cosmocode.de>
601  * @see rawurldecode()
602  */
603 function auth_nameencode($name,$skip_group=false){
604     global $cache_authname;
605     $cache =& $cache_authname;
606     $name  = (string) $name;
607
608     // never encode wildcard FS#1955
609     if($name == '%USER%') return $name;
610
611     if (!isset($cache[$name][$skip_group])) {
612         if($skip_group && $name{0} =='@'){
613             $cache[$name][$skip_group] = '@'.preg_replace('/([\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f])/e',
614                     "'%'.dechex(ord(substr('\\1',-1)))",substr($name,1));
615         }else{
616             $cache[$name][$skip_group] = preg_replace('/([\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f])/e',
617                     "'%'.dechex(ord(substr('\\1',-1)))",$name);
618         }
619     }
620
621     return $cache[$name][$skip_group];
622 }
623
624 /**
625  * Create a pronouncable password
626  *
627  * @author  Andreas Gohr <andi@splitbrain.org>
628  * @link    http://www.phpbuilder.com/annotate/message.php3?id=1014451
629  *
630  * @return string  pronouncable password
631  */
632 function auth_pwgen(){
633     $pw = '';
634     $c  = 'bcdfghjklmnprstvwz'; //consonants except hard to speak ones
635     $v  = 'aeiou';              //vowels
636     $a  = $c.$v;                //both
637
638     //use two syllables...
639     for($i=0;$i < 2; $i++){
640         $pw .= $c[rand(0, strlen($c)-1)];
641         $pw .= $v[rand(0, strlen($v)-1)];
642         $pw .= $a[rand(0, strlen($a)-1)];
643     }
644     //... and add a nice number
645     $pw .= rand(10,99);
646
647     return $pw;
648 }
649
650 /**
651  * Sends a password to the given user
652  *
653  * @author  Andreas Gohr <andi@splitbrain.org>
654  *
655  * @return bool  true on success
656  */
657 function auth_sendPassword($user,$password){
658     global $conf;
659     global $lang;
660     global $auth;
661     if (!$auth) return false;
662
663     $hdrs  = '';
664     $user     = $auth->cleanUser($user);
665     $userinfo = $auth->getUserData($user);
666
667     if(!$userinfo['mail']) return false;
668
669     $text = rawLocale('password');
670     $text = str_replace('@DOKUWIKIURL@',DOKU_URL,$text);
671     $text = str_replace('@FULLNAME@',$userinfo['name'],$text);
672     $text = str_replace('@LOGIN@',$user,$text);
673     $text = str_replace('@PASSWORD@',$password,$text);
674     $text = str_replace('@TITLE@',$conf['title'],$text);
675
676     if(empty($conf['mailprefix'])) {
677         $subject = $lang['regpwmail'];
678     } else {  
679         $subject = '['.$conf['mailprefix'].'] '.$lang['regpwmail'];
680     }
681
682     return mail_send($userinfo['name'].' <'.$userinfo['mail'].'>',
683             $subject,
684             $text,
685             $conf['mailfrom']);
686 }
687
688 /**
689  * Register a new user
690  *
691  * This registers a new user - Data is read directly from $_POST
692  *
693  * @author  Andreas Gohr <andi@splitbrain.org>
694  *
695  * @return bool  true on success, false on any error
696  */
697 function register(){
698     global $lang;
699     global $conf;
700     global $auth;
701
702     if(!$_POST['save']) return false;
703     if(!actionOK('register')) return false;
704
705     //clean username
706     $_POST['login'] = trim($auth->cleanUser($_POST['login']));
707
708     //clean fullname and email
709     $_POST['fullname'] = trim(preg_replace('/[\x00-\x1f:<>&%,;]+/','',$_POST['fullname']));
710     $_POST['email']    = trim(preg_replace('/[\x00-\x1f:<>&%,;]+/','',$_POST['email']));
711
712     if( empty($_POST['login']) ||
713         empty($_POST['fullname']) ||
714         empty($_POST['email']) ){
715         msg($lang['regmissing'],-1);
716         return false;
717     }
718
719     if ($conf['autopasswd']) {
720         $pass = auth_pwgen();                // automatically generate password
721     } elseif (empty($_POST['pass']) ||
722             empty($_POST['passchk'])) {
723         msg($lang['regmissing'], -1);        // complain about missing passwords
724         return false;
725     } elseif ($_POST['pass'] != $_POST['passchk']) {
726         msg($lang['regbadpass'], -1);      // complain about misspelled passwords
727         return false;
728     } else {
729         $pass = $_POST['pass'];              // accept checked and valid password
730     }
731
732     //check mail
733     if(!mail_isvalid($_POST['email'])){
734         msg($lang['regbadmail'],-1);
735         return false;
736     }
737
738     //okay try to create the user
739     if(!$auth->triggerUserMod('create', array($_POST['login'],$pass,$_POST['fullname'],$_POST['email']))){
740         msg($lang['reguexists'],-1);
741         return false;
742     }
743
744     // create substitutions for use in notification email
745     $substitutions = array(
746             'NEWUSER' => $_POST['login'],
747             'NEWNAME' => $_POST['fullname'],
748             'NEWEMAIL' => $_POST['email'],
749             );
750
751     if (!$conf['autopasswd']) {
752         msg($lang['regsuccess2'],1);
753         notify('', 'register', '', $_POST['login'], false, $substitutions);
754         return true;
755     }
756
757     // autogenerated password? then send him the password
758     if (auth_sendPassword($_POST['login'],$pass)){
759         msg($lang['regsuccess'],1);
760         notify('', 'register', '', $_POST['login'], false, $substitutions);
761         return true;
762     }else{
763         msg($lang['regmailfail'],-1);
764         return false;
765     }
766 }
767
768 /**
769  * Update user profile
770  *
771  * @author    Christopher Smith <chris@jalakai.co.uk>
772  */
773 function updateprofile() {
774     global $conf;
775     global $INFO;
776     global $lang;
777     global $auth;
778
779     if(empty($_POST['save'])) return false;
780     if(!checkSecurityToken()) return false;
781
782     if(!actionOK('profile')) {
783         msg($lang['profna'],-1);
784         return false;
785     }
786
787     if ($_POST['newpass'] != $_POST['passchk']) {
788         msg($lang['regbadpass'], -1);      // complain about misspelled passwords
789         return false;
790     }
791
792     //clean fullname and email
793     $_POST['fullname'] = trim(preg_replace('/[\x00-\x1f:<>&%,;]+/','',$_POST['fullname']));
794     $_POST['email']    = trim(preg_replace('/[\x00-\x1f:<>&%,;]+/','',$_POST['email']));
795
796     if ((empty($_POST['fullname']) && $auth->canDo('modName')) ||
797         (empty($_POST['email']) && $auth->canDo('modMail'))) {
798         msg($lang['profnoempty'],-1);
799         return false;
800     }
801
802     if (!mail_isvalid($_POST['email']) && $auth->canDo('modMail')){
803         msg($lang['regbadmail'],-1);
804         return false;
805     }
806
807     if ($_POST['fullname'] != $INFO['userinfo']['name'] && $auth->canDo('modName')) $changes['name'] = $_POST['fullname'];
808     if ($_POST['email'] != $INFO['userinfo']['mail'] && $auth->canDo('modMail')) $changes['mail'] = $_POST['email'];
809     if (!empty($_POST['newpass']) && $auth->canDo('modPass')) $changes['pass'] = $_POST['newpass'];
810
811     if (!count($changes)) {
812         msg($lang['profnochange'], -1);
813         return false;
814     }
815
816     if ($conf['profileconfirm']) {
817         if (!$auth->checkPass($_SERVER['REMOTE_USER'], $_POST['oldpass'])) {
818             msg($lang['badlogin'],-1);
819             return false;
820         }
821     }
822
823     if ($result = $auth->triggerUserMod('modify', array($_SERVER['REMOTE_USER'], $changes))) {
824         // update cookie and session with the changed data
825         if ($changes['pass']){
826             list($user,$sticky,$pass) = auth_getCookie();
827             $pass = PMA_blowfish_encrypt($changes['pass'],auth_cookiesalt(!$sticky));
828             auth_setCookie($_SERVER['REMOTE_USER'],$pass,(bool)$sticky);
829         }
830         return true;
831     }
832 }
833
834 /**
835  * Send a  new password
836  *
837  * This function handles both phases of the password reset:
838  *
839  *   - handling the first request of password reset
840  *   - validating the password reset auth token
841  *
842  * @author Benoit Chesneau <benoit@bchesneau.info>
843  * @author Chris Smith <chris@jalakai.co.uk>
844  * @author Andreas Gohr <andi@splitbrain.org>
845  *
846  * @return bool true on success, false on any error
847  */
848 function act_resendpwd(){
849     global $lang;
850     global $conf;
851     global $auth;
852
853     if(!actionOK('resendpwd')) {
854         msg($lang['resendna'],-1);
855         return false;
856     }
857
858     $token = preg_replace('/[^a-f0-9]+/','',$_REQUEST['pwauth']);
859
860     if($token){
861         // we're in token phase
862
863         $tfile = $conf['cachedir'].'/'.$token{0}.'/'.$token.'.pwauth';
864         if(!@file_exists($tfile)){
865             msg($lang['resendpwdbadauth'],-1);
866             return false;
867         }
868         $user = io_readfile($tfile);
869         @unlink($tfile);
870         $userinfo = $auth->getUserData($user);
871         if(!$userinfo['mail']) {
872             msg($lang['resendpwdnouser'], -1);
873             return false;
874         }
875
876         $pass = auth_pwgen();
877         if (!$auth->triggerUserMod('modify', array($user,array('pass' => $pass)))) {
878             msg('error modifying user data',-1);
879             return false;
880         }
881
882         if (auth_sendPassword($user,$pass)) {
883             msg($lang['resendpwdsuccess'],1);
884         } else {
885             msg($lang['regmailfail'],-1);
886         }
887         return true;
888
889     } else {
890         // we're in request phase
891
892         if(!$_POST['save']) return false;
893
894         if (empty($_POST['login'])) {
895             msg($lang['resendpwdmissing'], -1);
896             return false;
897         } else {
898             $user = trim($auth->cleanUser($_POST['login']));
899         }
900
901         $userinfo = $auth->getUserData($user);
902         if(!$userinfo['mail']) {
903             msg($lang['resendpwdnouser'], -1);
904             return false;
905         }
906
907         // generate auth token
908         $token = md5(auth_cookiesalt().$user); //secret but user based
909         $tfile = $conf['cachedir'].'/'.$token{0}.'/'.$token.'.pwauth';
910         $url = wl('',array('do'=>'resendpwd','pwauth'=>$token),true,'&');
911
912         io_saveFile($tfile,$user);
913
914         $text = rawLocale('pwconfirm');
915         $text = str_replace('@DOKUWIKIURL@',DOKU_URL,$text);
916         $text = str_replace('@FULLNAME@',$userinfo['name'],$text);
917         $text = str_replace('@LOGIN@',$user,$text);
918         $text = str_replace('@TITLE@',$conf['title'],$text);
919         $text = str_replace('@CONFIRM@',$url,$text);
920
921         if(empty($conf['mailprefix'])) {
922             $subject = $lang['regpwmail'];
923         } else {  
924             $subject = '['.$conf['mailprefix'].'] '.$lang['regpwmail'];
925         }
926         
927         if(mail_send($userinfo['name'].' <'.$userinfo['mail'].'>',
928                      $subject,
929                      $text,
930                      $conf['mailfrom'])){
931             msg($lang['resendpwdconfirm'],1);
932         }else{
933             msg($lang['regmailfail'],-1);
934         }
935         return true;
936     }
937
938     return false; // never reached
939 }
940
941 /**
942  * Encrypts a password using the given method and salt
943  *
944  * If the selected method needs a salt and none was given, a random one
945  * is chosen.
946  *
947  * @author  Andreas Gohr <andi@splitbrain.org>
948  * @return  string  The crypted password
949  */
950 function auth_cryptPassword($clear,$method='',$salt=null){
951     global $conf;
952     if(empty($method)) $method = $conf['passcrypt'];
953
954     $pass  = new PassHash();
955     $call  = 'hash_'.$method;
956
957     if(!method_exists($pass,$call)){
958         msg("Unsupported crypt method $method",-1);
959         return false;
960     }
961
962     return $pass->$call($clear,$salt);
963 }
964
965 /**
966  * Verifies a cleartext password against a crypted hash
967  *
968  * @author  Andreas Gohr <andi@splitbrain.org>
969  * @return  bool
970  */
971 function auth_verifyPassword($clear,$crypt){
972     $pass = new PassHash();
973     return $pass->verify_hash($clear,$crypt);
974 }
975
976 /**
977  * Set the authentication cookie and add user identification data to the session
978  *
979  * @param string  $user       username
980  * @param string  $pass       encrypted password
981  * @param bool    $sticky     whether or not the cookie will last beyond the session
982  */
983 function auth_setCookie($user,$pass,$sticky) {
984     global $conf;
985     global $auth;
986     global $USERINFO;
987
988     if (!$auth) return false;
989     $USERINFO = $auth->getUserData($user);
990
991     // set cookie
992     $cookie = base64_encode($user).'|'.((int) $sticky).'|'.base64_encode($pass);
993     $cookieDir = empty($conf['cookiedir']) ? DOKU_REL : $conf['cookiedir'];
994     $time = $sticky ? (time()+60*60*24*365) : 0; //one year
995     if (version_compare(PHP_VERSION, '5.2.0', '>')) {
996         setcookie(DOKU_COOKIE,$cookie,$time,$cookieDir,'',($conf['securecookie'] && is_ssl()),true);
997     }else{
998         setcookie(DOKU_COOKIE,$cookie,$time,$cookieDir,'',($conf['securecookie'] && is_ssl()));
999     }
1000     // set session
1001     $_SESSION[DOKU_COOKIE]['auth']['user'] = $user;
1002     $_SESSION[DOKU_COOKIE]['auth']['pass'] = sha1($pass);
1003     $_SESSION[DOKU_COOKIE]['auth']['buid'] = auth_browseruid();
1004     $_SESSION[DOKU_COOKIE]['auth']['info'] = $USERINFO;
1005     $_SESSION[DOKU_COOKIE]['auth']['time'] = time();
1006 }
1007
1008 /**
1009  * Returns the user, (encrypted) password and sticky bit from cookie
1010  *
1011  * @returns array
1012  */
1013 function auth_getCookie(){
1014     if (!isset($_COOKIE[DOKU_COOKIE])) {
1015         return array(null, null, null);
1016     }
1017     list($user,$sticky,$pass) = explode('|',$_COOKIE[DOKU_COOKIE],3);
1018     $sticky = (bool) $sticky;
1019     $pass   = base64_decode($pass);
1020     $user   = base64_decode($user);
1021     return array($user,$sticky,$pass);
1022 }
1023
1024 //Setup VIM: ex: et ts=2 :