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