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