xref: /dokuwiki/inc/auth.php (revision 44f669e9d81543bb16f1017c005d7739766c6d7d)
1<?
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  require_once("inc/common.php");
13  require_once("inc/io.php");
14  require_once("inc/blowfish.php");
15  require_once("inc/mail.php");
16  // load the the auth functions
17  require_once('inc/auth_'.$conf['authtype'].'.php');
18
19  // some ACL level defines
20  define('AUTH_NONE',0);
21  define('AUTH_READ',1);
22  define('AUTH_EDIT',2);
23  define('AUTH_CREATE',4);
24  define('AUTH_UPLOAD',8);
25  define('AUTH_GRANT',255);
26
27  if($conf['useacl']){
28    auth_login($_REQUEST['u'],$_REQUEST['p'],$_REQUEST['r']);
29    // load ACL into a global array
30    $AUTH_ACL = file('conf/acl.auth');
31  }
32
33/**
34 * This tries to login the user based on the sent auth credentials
35 *
36 * The authentication works like this: if a username was given
37 * a new login is assumed and user/password are checked. If they
38 * are correct the password is encrypted with blowfish and stored
39 * together with the username in a cookie - the same info is stored
40 * in the session, too. Additonally a browserID is stored in the
41 * session.
42 *
43 * If no username was given the cookie is checked: if the username,
44 * crypted password and browserID match between session and cookie
45 * no further testing is done and the user is accepted
46 *
47 * If a cookie was found but no session info was availabe the
48 * blowish encrypted password from the cookie is decrypted and
49 * together with username rechecked by calling this function again.
50 *
51 * On a successful login $_SERVER[REMOTE_USER] and $USERINFO
52 * are set.
53 *
54 * @author  Andreas Gohr <andi@splitbrain.org>
55 *
56 * @param   string  $user    Username
57 * @param   string  $pass    Cleartext Password
58 * @param   bool    $sticky  Cookie should not expire
59 * @return  bool             true on successful auth
60*/
61function auth_login($user,$pass,$sticky=false){
62  global $USERINFO;
63  global $conf;
64  global $lang;
65  $sticky ? $sticky = true : $sticky = false; //sanity check
66
67  if(isset($user)){
68    //usual login
69    if (auth_checkPass($user,$pass)){
70      // make logininfo globally available
71      $_SERVER['REMOTE_USER'] = $user;
72      $USERINFO = auth_getUserData($user); //FIXME move all references to session
73
74      // set cookie
75      $pass   = PMA_blowfish_encrypt($pass,auth_cookiesalt());
76      $cookie = base64_encode("$user|$sticky|$pass");
77      if($sticky) $time = time()+60*60*24*365; //one year
78      setcookie('DokuWikiAUTH',$cookie,$time);
79
80      // set session
81      $_SESSION[$conf['title']]['auth']['user'] = $user;
82      $_SESSION[$conf['title']]['auth']['pass'] = $pass;
83      $_SESSION[$conf['title']]['auth']['buid'] = auth_browseruid();
84      $_SESSION[$conf['title']]['auth']['info'] = $USERINFO;
85      return true;
86    }else{
87      //invalid credentials - log off
88      msg($lang['badlogin'],-1);
89      auth_logoff();
90      return false;
91    }
92  }else{
93    // read cookie information
94    $cookie = base64_decode($_COOKIE['DokuWikiAUTH']);
95    list($user,$sticky,$pass) = split('\|',$cookie,3);
96    // get session info
97    $session = $_SESSION[$conf['title']]['auth'];
98
99    if($user && $pass){
100      // we got a cookie - see if we can trust it
101      if(isset($session) &&
102        ($session['user'] == $user) &&
103        ($session['pass'] == $pass) &&  //still crypted
104        ($session['buid'] == auth_browseruid()) ){
105        // he has session, cookie and browser right - let him in
106        $_SERVER['REMOTE_USER'] = $user;
107        $USERINFO = $session['info']; //FIXME move all references to session
108        return true;
109      }
110      // no we don't trust it yet - recheck pass
111      $pass = PMA_blowfish_decrypt($pass,auth_cookiesalt());
112      return auth_login($user,$pass,$sticky);
113    }
114  }
115  //just to be sure
116  auth_logoff();
117  return false;
118}
119
120/**
121 * Builds a pseudo UID from browserdata
122 *
123 * This is neither unique nor unfakable - still it adds some
124 * security
125 *
126 * @author  Andreas Gohr <andi@splitbrain.org>
127 *
128 * @return  string  a MD5 sum of various browser headers
129 */
130function auth_browseruid(){
131  $uid  = '';
132  $uid .= $_SERVER['HTTP_USER_AGENT'];
133  $uid .= $_SERVER['HTTP_ACCEPT_ENCODING'];
134  $uid .= $_SERVER['HTTP_ACCEPT_LANGUAGE'];
135  $uid .= $_SERVER['HTTP_ACCEPT_CHARSET'];
136  return md5($uid);
137}
138
139/**
140 * Creates a random key to encrypt the password in cookies
141 *
142 * This function tries to read the password for encrypting
143 * cookies from $conf['datadir'].'/.cache/cookiesalt'
144 * if no such file is found a random key is created and
145 * and stored in this file.
146 *
147 * @author  Andreas Gohr <andi@splitbrain.org>
148 *
149 * @return  string
150 */
151function auth_cookiesalt(){
152  global $conf;
153  $file = $conf['datadir'].'/.cache/cookiesalt';
154  $salt = io_readFile($file);
155  if(empty($salt)){
156    $salt = uniqid(rand(),true);
157    io_saveFile($file,$salt);
158  }
159  return $salt;
160}
161
162/**
163 * This clears all authenticationdata and thus log the user
164 * off
165 *
166 * @author  Andreas Gohr <andi@splitbrain.org>
167 */
168function auth_logoff(){
169  global $conf;
170  global $USERINFO;
171  unset($_SESSION[$conf['title']]['auth']['user']);
172  unset($_SESSION[$conf['title']]['auth']['pass']);
173  unset($_SESSION[$conf['title']]['auth']['info']);
174  unset($_SERVER['REMOTE_USER']);
175  $USERINFO=null; //FIXME
176  setcookie('DokuWikiAUTH','',time()-3600);
177}
178
179/**
180 * Convinience function for auth_aclcheck()
181 *
182 * This checks the permissions for the current user
183 *
184 * @author  Andreas Gohr <andi@splitbrain.org>
185 *
186 * @param  string  $id  page ID
187 * @return int          permission level
188 */
189function auth_quickaclcheck($id){
190  global $conf;
191  global $USERINFO;
192  # if no ACL is used always return upload rights
193  if(!$conf['useacl']) return AUTH_UPLOAD;
194  return auth_aclcheck($id,$_SERVER['REMOTE_USER'],$USERINFO['grps']);
195}
196
197/**
198 * Returns the maximum rights a user has for
199 * the given ID or its namespace
200 *
201 * @author  Andreas Gohr <andi@splitbrain.org>
202 *
203 * @param  string  $id     page ID
204 * @param  string  $user   Username
205 * @param  array   $groups Array of groups the user is in
206 * @return int             permission level
207 */
208function auth_aclcheck($id,$user,$groups){
209  global $conf;
210  global $AUTH_ACL;
211
212  # if no ACL is used always return upload rights
213  if(!$conf['useacl']) return AUTH_UPLOAD;
214
215  $ns    = getNS($id);
216  $perm  = -1;
217
218  if($user){
219    //prepend groups with @
220    for($i=0; $i<count($groups); $i++){
221      $groups[$i] = '@'.$groups[$i];
222    }
223    //add ALL group
224    $groups[] = '@ALL';
225    //add User
226    $groups[] = $user;
227    //build regexp
228    $regexp   = join('|',$groups);
229  }else{
230    $regexp = '@ALL';
231  }
232
233  //check exact match first
234  $matches = preg_grep('/^'.$id.'\s+('.$regexp.')\s+/',$AUTH_ACL);
235  if(count($matches)){
236    foreach($matches as $match){
237      $match = preg_replace('/#.*$/','',$match); //ignore comments
238      $acl   = preg_split('/\s+/',$match);
239      if($acl[2] > $perm){
240        $perm = $acl[2];
241      }
242    }
243    if($perm > -1){
244      //we had a match - return it
245      return $perm;
246    }
247  }
248
249  //still here? do the namespace checks
250  if($ns){
251    $path = $ns.':\*';
252  }else{
253    $path = '\*'; //root document
254  }
255
256  do{
257    $matches = preg_grep('/^'.$path.'\s+('.$regexp.')\s+/',$AUTH_ACL);
258    if(count($matches)){
259      foreach($matches as $match){
260        $match = preg_replace('/#.*$/','',$match); //ignore comments
261        $acl   = preg_split('/\s+/',$match);
262        if($acl[2] > $perm){
263          $perm = $acl[2];
264        }
265      }
266      //we had a match - return it
267      return $perm;
268    }
269
270    //get next higher namespace
271    $ns   = getNS($ns);
272
273    if($path != '\*'){
274      $path = $ns.':\*';
275      if($path == ':\*') $path = '\*';
276    }else{
277      //we did this already
278      //looks like there is something wrong with the ACL
279      //break here
280      return $perm;
281    }
282  }while(1); //this should never loop endless
283}
284
285/**
286 * Create a pronouncable password
287 *
288 * @author  Andreas Gohr <andi@splitbrain.org>
289 * @link    http://www.phpbuilder.com/annotate/message.php3?id=1014451
290 *
291 * @return string  pronouncable password
292 */
293function auth_pwgen(){
294  $pw = '';
295  $c  = 'bcdfghjklmnprstvwz'; //consonants except hard to speak ones
296  $v  = 'aeiou';              //vowels
297  $a  = $c.$v;                //both
298
299  //use two syllables...
300  for($i=0;$i < 2; $i++){
301    $pw .= $c[rand(0, strlen($c)-1)];
302    $pw .= $v[rand(0, strlen($v)-1)];
303    $pw .= $a[rand(0, strlen($a)-1)];
304  }
305  //... and add a nice number
306  $pw .= rand(10,99);
307
308  return $pw;
309}
310
311/**
312 * Sends a password to the given user
313 *
314 * @author  Andreas Gohr <andi@splitbrain.org>
315 *
316 * @return bool  true on success
317 */
318function auth_sendPassword($user,$password){
319  global $conf;
320  global $lang;
321  $hdrs  = '';
322  $userinfo = auth_getUserData($user);
323
324  if(!$userinfo['mail']) return false;
325
326  $text = rawLocale('password');
327  $text = str_replace('@DOKUWIKIURL@',getBaseURL(true),$text);
328  $text = str_replace('@FULLNAME@',$userinfo['name'],$text);
329  $text = str_replace('@LOGIN@',$user,$text);
330  $text = str_replace('@PASSWORD@',$password,$text);
331  $text = str_replace('@TITLE@',$conf['title'],$text);
332
333  return mail_send($userinfo['name'].' <'.$userinfo['mail'].'>',
334                   $lang['regpwmail'],
335                   $text,
336                   $conf['mailfrom']);
337}
338
339/**
340 * Register a new user
341 *
342 * This registers a new user - Data is read directly from $_POST
343 *
344 * @author  Andreas Gohr <andi@splitbrain.org>
345 *
346 * @return bool  true on success, false on any error
347 */
348function register(){
349  global $lang;
350  global $conf;
351
352  if(!$_POST['save']) return false;
353  if(!$conf['openregister']) return false;
354
355  //clean username
356  $_POST['login'] = preg_replace('/.*:/','',$_POST['login']);
357  $_POST['login'] = cleanID($_POST['login']);
358  //clean fullname and email
359  $_POST['fullname'] = trim(str_replace(':','',$_POST['fullname']));
360  $_POST['email']    = trim(str_replace(':','',$_POST['email']));
361
362  if( empty($_POST['login']) ||
363      empty($_POST['fullname']) ||
364      empty($_POST['email']) ){
365    msg($lang['regmissing'],-1);
366    return false;
367  }
368
369  //check mail
370  if(!mail_isvalid($_POST['email'])){
371    msg($lang['regbadmail'],-1);
372    return false;
373  }
374
375  //okay try to create the user
376  $pass = auth_createUser($_POST['login'],$_POST['fullname'],$_POST['email']);
377  if(empty($pass)){
378    msg($lang['reguexists'],-1);
379    return false;
380  }
381
382  //send him the password
383  if (auth_sendPassword($_POST['login'],$pass)){
384    msg($lang['regsuccess'],1);
385    return true;
386  }else{
387    msg($lang['regmailfail'],-1);
388    return false;
389  }
390}
391
392?>
393