OSDN Git Service

dab193ae4f44751bf9481916208a00cc253d35b5
[pukiwiki/pukiwiki.git] / lib / auth.php
1 <?php
2 // PukiWiki - Yet another WikiWikiWeb clone
3 // $Id: auth.php,v 1.22 2011/01/25 15:01:01 henoheno Exp $
4 // Copyright (C) 2003-2005, 2007 PukiWiki Developers Team
5 // License: GPL v2 or (at your option) any later version
6 //
7 // Authentication related functions
8
9 define('PKWK_PASSPHRASE_LIMIT_LENGTH', 512);
10
11 /////////////////////////////////////////////////
12 // Auth type
13
14 define('AUTH_TYPE_NONE', 0);
15 define('AUTH_TYPE_BASIC', 1);
16 define('AUTH_TYPE_EXTERNAL', 2);
17 define('AUTH_TYPE_FORM', 3);
18
19 define('AUTH_TYPE_EXTERNAL_REMOTE_USER', 4);
20 define('AUTH_TYPE_EXTERNAL_X_FORWARDED_USER', 5);
21
22
23 // Passwd-auth related ----
24
25 function pkwk_login($pass = '')
26 {
27         global $adminpass;
28
29         if (! PKWK_READONLY && isset($adminpass) &&
30                 pkwk_hash_compute($pass, $adminpass) === $adminpass) {
31                 return TRUE;
32         } else {
33                 sleep(2);       // Blocking brute force attack
34                 return FALSE;
35         }
36 }
37
38 // Compute RFC2307 'userPassword' value, like slappasswd (OpenLDAP)
39 // $phrase : Pass-phrase
40 // $scheme : Specify '{scheme}' or '{scheme}salt'
41 // $prefix : Output with a scheme-prefix or not
42 // $canonical : Correct or Preserve $scheme prefix
43 function pkwk_hash_compute($phrase = '', $scheme = '{x-php-md5}', $prefix = TRUE, $canonical = FALSE)
44 {
45         if (! is_string($phrase) || ! is_string($scheme)) return FALSE;
46
47         if (strlen($phrase) > PKWK_PASSPHRASE_LIMIT_LENGTH)
48                 die('pkwk_hash_compute(): malicious message length');
49
50         // With a {scheme}salt or not
51         $matches = array();
52         if (preg_match('/^(\{.+\})(.*)$/', $scheme, $matches)) {
53                 $scheme = & $matches[1];
54                 $salt   = & $matches[2];
55         } else if ($scheme != '') {
56                 $scheme  = ''; // Cleartext
57                 $salt    = '';
58         }
59
60         // Compute and add a scheme-prefix
61         switch (strtolower($scheme)) {
62
63         // PHP crypt()
64         case '{x-php-crypt}' :
65                 $hash = ($prefix ? ($canonical ? '{x-php-crypt}' : $scheme) : '') .
66                         ($salt != '' ? crypt($phrase, $salt) : crypt($phrase));
67                 break;
68
69         // PHP md5()
70         case '{x-php-md5}'   :
71                 $hash = ($prefix ? ($canonical ? '{x-php-md5}' : $scheme) : '') .
72                         md5($phrase);
73                 break;
74
75         // PHP sha1()
76         case '{x-php-sha1}'  :
77                 $hash = ($prefix ? ($canonical ? '{x-php-sha1}' : $scheme) : '') .
78                         sha1($phrase);
79                 break;
80
81         // LDAP CRYPT
82         case '{crypt}'       :
83                 $hash = ($prefix ? ($canonical ? '{CRYPT}' : $scheme) : '') .
84                         ($salt != '' ? crypt($phrase, $salt) : crypt($phrase));
85                 break;
86
87         // LDAP MD5
88         case '{md5}'         :
89                 $hash = ($prefix ? ($canonical ? '{MD5}' : $scheme) : '') .
90                         base64_encode(pkwk_hex2bin(md5($phrase)));
91                 break;
92
93         // LDAP SMD5
94         case '{smd5}'        :
95                 // MD5 Key length = 128bits = 16bytes
96                 $salt = ($salt != '' ? substr(base64_decode($salt), 16) : substr(crypt(''), -8));
97                 $hash = ($prefix ? ($canonical ? '{SMD5}' : $scheme) : '') .
98                         base64_encode(pkwk_hex2bin(md5($phrase . $salt)) . $salt);
99                 break;
100
101         // LDAP SHA
102         case '{sha}'         :
103                 $hash = ($prefix ? ($canonical ? '{SHA}' : $scheme) : '') .
104                         base64_encode(pkwk_hex2bin(sha1($phrase)));
105                 break;
106
107         // LDAP SSHA
108         case '{ssha}'        :
109                 // SHA-1 Key length = 160bits = 20bytes
110                 $salt = ($salt != '' ? substr(base64_decode($salt), 20) : substr(crypt(''), -8));
111                 $hash = ($prefix ? ($canonical ? '{SSHA}' : $scheme) : '') .
112                         base64_encode(pkwk_hex2bin(sha1($phrase . $salt)) . $salt);
113                 break;
114
115         // LDAP CLEARTEXT and just cleartext
116         case '{cleartext}'   : /* FALLTHROUGH */
117         case ''              :
118                 $hash = ($prefix ? ($canonical ? '{CLEARTEXT}' : $scheme) : '') .
119                         $phrase;
120                 break;
121
122         // Invalid scheme
123         default:
124                 $hash = FALSE;
125                 break;
126         }
127
128         return $hash;
129 }
130
131 // LDAP related functions
132
133 function _pkwk_ldap_escape_callback($matches) {
134         return sprintf('\\%02x', ord($matches[0]));
135 }
136
137 function pkwk_ldap_escape_filter($value) {
138         if (function_exists('ldap_escape')) {
139                 return ldap_escape($value, false, LDAP_ESCAPE_FILTER);
140         }
141         return preg_replace_callback('/[\\\\*()\0]/',
142                 '_pkwk_ldap_escape_callback', $value);
143 }
144
145 function pkwk_ldap_escape_dn($value) {
146         if (function_exists('ldap_escape')) {
147                 return ldap_escape($value, false, LDAP_ESCAPE_DN);
148         }
149         return preg_replace_callback('/[\\\\,=+<>;"#]/',
150                 '_pkwk_ldap_escape_callback', $value);
151 }
152
153
154 // Basic-auth related ----
155
156 // Check edit-permission
157 function check_editable($page, $auth_flag = TRUE, $exit_flag = TRUE)
158 {
159         global $script, $_title_cannotedit, $_msg_unfreeze;
160
161         if (edit_auth($page, $auth_flag, $exit_flag) && is_editable($page)) {
162                 // Editable
163                 return TRUE;
164         } else {
165                 // Not editable
166                 if ($exit_flag === FALSE) {
167                         return FALSE; // Without exit
168                 } else {
169                         // With exit
170                         $body = $title = str_replace('$1',
171                                 htmlsc(strip_bracket($page)), $_title_cannotedit);
172                         if (is_freeze($page))
173                                 $body .= '(<a href="' . $script . '?cmd=unfreeze&amp;page=' .
174                                         rawurlencode($page) . '">' . $_msg_unfreeze . '</a>)';
175                         $page = str_replace('$1', make_search($page), $_title_cannotedit);
176                         catbody($title, $page, $body);
177                         exit;
178                 }
179         }
180 }
181
182 // Check read-permission
183 function check_readable($page, $auth_flag = TRUE, $exit_flag = TRUE)
184 {
185         return read_auth($page, $auth_flag, $exit_flag);
186 }
187
188 function edit_auth($page, $auth_flag = TRUE, $exit_flag = TRUE)
189 {
190         global $edit_auth, $edit_auth_pages, $_title_cannotedit;
191         return $edit_auth ?  basic_auth($page, $auth_flag, $exit_flag,
192                 $edit_auth_pages, $_title_cannotedit) : TRUE;
193 }
194
195 function read_auth($page, $auth_flag = TRUE, $exit_flag = TRUE)
196 {
197         global $read_auth, $read_auth_pages, $_title_cannotread;
198         return $read_auth ?  basic_auth($page, $auth_flag, $exit_flag,
199                 $read_auth_pages, $_title_cannotread) : TRUE;
200 }
201
202 // Basic authentication
203 function basic_auth($page, $auth_flag, $exit_flag, $auth_pages, $title_cannot)
204 {
205         global $auth_method_type, $auth_users, $_msg_auth, $auth_user, $auth_groups;
206         global $auth_user_groups, $auth_type, $g_query_string;
207         // Checked by:
208         $target_str = '';
209         if ($auth_method_type == 'pagename') {
210                 $target_str = $page; // Page name
211         } else if ($auth_method_type == 'contents') {
212                 $target_str = join('', get_source($page)); // Its contents
213         }
214
215         $user_list = array();
216         foreach($auth_pages as $key=>$val)
217                 if (preg_match($key, $target_str))
218                         $user_list = array_merge($user_list, explode(',', $val));
219
220         if (empty($user_list)) return TRUE; // No limit
221
222         $matches = array();
223         if (PKWK_READONLY ||
224                 ! $auth_user ||
225                 count(array_intersect($auth_user_groups, $user_list)) === 0)
226         {
227                 // Auth failed
228                 pkwk_common_headers();
229                 if ($auth_flag && !$auth_user) {
230                         if (AUTH_TYPE_BASIC === $auth_type) {
231                                 header('WWW-Authenticate: Basic realm="' . $_msg_auth . '"');
232                                 header('HTTP/1.0 401 Unauthorized');
233                         } elseif (AUTH_TYPE_FORM === $auth_type) {
234                                 $url_after_login = get_script_uri() . '?' . $g_query_string;
235                                 $loginurl = get_script_uri() . '?plugin=loginform'
236                                         . '&page=' . rawurlencode($page)
237                                         . '&url_after_login=' . rawurlencode($url_after_login);
238                                 header('HTTP/1.0 302 Found');
239                                 header('Location: ' . $loginurl);
240                         } elseif (AUTH_TYPE_EXTERNAL === $auth_type) {
241                                 $url_after_login = get_script_uri() . '?' . $g_query_string;
242                                 $loginurl = get_auth_external_login_url($page, $url_after_login);
243                                 header('HTTP/1.0 302 Found');
244                                 header('Location: ' . $loginurl);
245                         }
246                 }
247                 if ($exit_flag) {
248                         $body = $title = str_replace('$1',
249                                 htmlsc(strip_bracket($page)), $title_cannot);
250                         $page = str_replace('$1', make_search($page), $title_cannot);
251                         catbody($title, $page, $body);
252                         exit;
253                 }
254                 return FALSE;
255         } else {
256                 return TRUE;
257         }
258 }
259
260 /**
261  * Send 401 if client send a invalid credentials
262  *
263  * @return true if valid, false if invalid credentials
264  */
265 function ensure_valid_auth_user()
266 {
267         global $auth_type, $auth_users, $_msg_auth, $auth_user, $auth_groups;
268         global $auth_user_groups, $auth_user_fullname;
269         global $ldap_user_account;
270         global $read_auth, $edit_auth;
271         if ($read_auth || $edit_auth) {
272                 switch ($auth_type) {
273                         case AUTH_TYPE_BASIC:
274                         case AUTH_TYPE_FORM:
275                         case AUTH_TYPE_EXTERNAL:
276                         case AUTH_TYPE_EXTERNAL_REMOTE_USER:
277                         case AUTH_TYPE_EXTERNAL_X_FORWARDED_USER:
278                                 break;
279                         default:
280                                 // $auth_type is not valid, Set form auth as default
281                                 $auth_type = AUTH_TYPE_FORM;
282                 }
283         }
284         switch ($auth_type) {
285                 case AUTH_TYPE_BASIC:
286                 {
287                         if (isset($_SERVER['PHP_AUTH_USER'])) {
288                                 $user = $_SERVER['PHP_AUTH_USER'];
289                                 if (in_array($user, array_keys($auth_users))) {
290                                         if (pkwk_hash_compute(
291                                                 $_SERVER['PHP_AUTH_PW'],
292                                                 $auth_users[$user]) === $auth_users[$user]) {
293                                                 $auth_user = $user;
294                                                 $auth_user_fullname = $auth_user;
295                                                 $auth_user_groups = get_groups_from_username($user);
296                                                 return true;
297                                         }
298                                 }
299                                 header('WWW-Authenticate: Basic realm="' . $_msg_auth . '"');
300                                 header('HTTP/1.0 401 Unauthorized');
301                         }
302                         $auth_user = '';
303                         $auth_user_groups = get_groups_from_username($user);
304                         return true; // no auth input
305                 }
306                 case AUTH_TYPE_FORM:
307                 case AUTH_TYPE_EXTERNAL:
308                 {
309                         session_start();
310                         $user = '';
311                         $fullname = '';
312                         if (isset($_SESSION['authenticated_user'])) {
313                                 $user = $_SESSION['authenticated_user'];
314                                 if (isset($_SESSION['authenticated_user_fullname'])) {
315                                         $fullname = $_SESSION['authenticated_user_fullname'];
316                                 } else {
317                                         $fullname = $user;
318                                         if ($auth_type === AUTH_TYPE_EXTERNAL && $ldap_user_account) {
319                                                 $ldap_user_info = ldap_get_simple_user_info($user);
320                                                 if ($ldap_user_info) {
321                                                         $fullname = $ldap_user_info['fullname'];
322                                                 }
323                                         }
324                                         $_SESSION['authenticated_user_fullname'] = $fullname;
325                                 }
326                         }
327                         $auth_user = $user;
328                         $auth_user_fullname = $fullname;
329                         break;
330                 }
331                 case AUTH_TYPE_EXTERNAL_REMOTE_USER:
332                         $auth_user = $_SERVER['REMOTE_USER'];
333                         $auth_user_fullname = $auth_user;
334                         break;
335                 case AUTH_TYPE_EXTERNAL_X_FORWARDED_USER:
336                         $auth_user =  $_SERVER['HTTP_X_FORWARDED_USER'];
337                         $auth_user_fullname = $auth_user;
338                         break;
339                 default: // AUTH_TYPE_NONE
340                         $auth_user = '';
341                         $auth_user_fullname = '';
342                         break;
343         }
344         $auth_user_groups = get_groups_from_username($auth_user);
345         return true; // is not basic auth
346 }
347
348 /**
349  * Return group name array whose group contains the user
350  *
351  * Result array contains reserved 'valid-user' group for all authenticated user
352  * @global array $auth_groups
353  * @param string $user
354  * @return array
355  */
356 function get_groups_from_username($user)
357 {
358         global $auth_groups;
359         if ($user !== '') {
360                 $groups = array();
361                 foreach ($auth_groups as $group=>$users) {
362                         $sp = explode(',', $users);
363                         if (in_array($user, $sp)) {
364                                 $groups[] = $group;
365                         }
366                 }
367                 // Implicit group that has same name as user itself
368                 $groups[] = $user;
369                 // 'valid-user' group for
370                 $valid_user = 'valid-user';
371                 if (!in_array($valid_user, $groups)) {
372                         $groups[] = $valid_user;
373                 }
374                 return $groups;
375         }
376         return array();
377 }
378
379 /**
380  * Get authenticated user name.
381  *
382  * @global type $auth_user
383  * @return type
384  */
385 function get_auth_user()
386 {
387         global $auth_user;
388         return $auth_user;
389 }
390
391 /**
392  * Sign in with username and password
393  *
394  * @param String username
395  * @param String password
396  * @return true is sign in is OK
397  */
398 function form_auth($username, $password)
399 {
400         global $ldap_user_account, $auth_users;
401         $user = $username;
402         if ($ldap_user_account) {
403                 // LDAP account
404                 return ldap_auth($username, $password);
405         } else {
406                 // Defined users in pukiwiki.ini.php
407                 if (in_array($user, array_keys($auth_users))) {
408                         if (pkwk_hash_compute(
409                                 $password,
410                                 $auth_users[$user]) === $auth_users[$user]) {
411                                 session_start();
412                                 session_regenerate_id(true); // require: PHP5.1+
413                                 $_SESSION['authenticated_user'] = $user;
414                                 $_SESSION['authenticated_user_fullname'] = $user;
415                                 return true;
416                         }
417                 }
418         }
419         return false;
420 }
421
422 function ldap_auth($username, $password)
423 {
424         global $ldap_server, $ldap_base_dn, $ldap_bind_dn, $ldap_bind_password;
425         $ldapconn = ldap_connect($ldap_server);
426         if ($ldapconn) {
427                 ldap_set_option($ldapconn, LDAP_OPT_PROTOCOL_VERSION, 3);
428                 ldap_set_option($ldapconn, LDAP_OPT_REFERRALS, 0);
429                 if (preg_match('#\$login\b#', $ldap_bind_dn)) {
430                         // Bind by user credential
431                         $username_esc = pkwk_ldap_escape_dn($username);
432                         $bind_dn_user = preg_replace('#\$login\b#', $username_esc, $ldap_bind_dn);
433                         $ldap_bind_user = ldap_bind($ldapconn, $bind_dn_user, $password);
434                         if ($ldap_bind_user) {
435                                 $user_info = get_ldap_user_info($ldapconn, $username, $ldap_base_dn);
436                                 if ($user_info) {
437                                         session_regenerate_id(true); // require: PHP5.1+
438                                         $_SESSION['authenticated_user'] = $user_info['uid'];
439                                         $_SESSION['authenticated_user_fullname'] = $user_info['fullname'];
440                                         return true;
441                                 }
442                         }
443                 } else {
444                         // Bind by bind dn
445                         $ldap_bind = ldap_bind($ldapconn, $ldap_bind_dn, $ldap_bind_password);
446                         if ($ldap_bind) {
447                                 $user_info = get_ldap_user_info($ldapconn, $username, $ldap_base_dn);
448                                 if ($user_info) {
449                                         $ldap_bind_user2 = ldap_bind($ldapconn, $user_info['dn'], $password);
450                                         if ($ldap_bind_user2) {
451                                                 session_regenerate_id(true); // require: PHP5.1+
452                                                 $_SESSION['authenticated_user'] = $user_info['uid'];
453                                                 $_SESSION['authenticated_user_fullname'] = $user_info['fullname'];
454                                                 return true;
455                                         }
456                                 }
457                         }
458                 }
459         }
460         return false;
461 }
462
463 // Get LDAP user info via bind DN
464 function ldap_get_simple_user_info($username)
465 {
466         global $ldap_server, $ldap_base_dn, $ldap_bind_dn, $ldap_bind_password;
467         $ldapconn = ldap_connect($ldap_server);
468         if ($ldapconn) {
469                 ldap_set_option($ldapconn, LDAP_OPT_PROTOCOL_VERSION, 3);
470                 ldap_set_option($ldapconn, LDAP_OPT_REFERRALS, 0);
471                 // Bind by bind dn
472                 $ldap_bind = ldap_bind($ldapconn, $ldap_bind_dn, $ldap_bind_password);
473                 if ($ldap_bind) {
474                         $user_info = get_ldap_user_info($ldapconn, $username, $ldap_base_dn);
475                         if ($user_info) {
476                                 return $user_info;
477                         }
478                 }
479         }
480         return false;
481 }
482
483 /**
484  * Search user and get 'dn', 'uid', 'fullname' and 'mail'
485  * @param type $ldapconn
486  * @param type $username
487  * @param type $base_dn
488  * @return boolean
489  */
490 function get_ldap_user_info($ldapconn, $username, $base_dn) {
491         $username_esc = pkwk_ldap_escape_filter($username);
492         $filter = "(|(uid=$username_esc)(sAMAccountName=$username_esc))";
493         $result1 = ldap_search($ldapconn, $base_dn, $filter, array('dn', 'uid', 'cn', 'samaccountname', 'displayname', 'mail'));
494         $entries = ldap_get_entries($ldapconn, $result1);
495         if (!isset($entries[0])) {
496                 return false;
497         }
498         $info = $entries[0];
499         if (isset($info['dn'])) {
500                 $user_dn = $info['dn'];
501                 $cano_username = $username;
502                 if (isset($info['uid'][0])) {
503                         $cano_username = $info['uid'][0];
504                 } elseif (isset($info['samaccountname'][0])) {
505                         $cano_username = $info['samaccountname'][0];
506                 }
507                 $cano_fullname = $username;
508                 if (isset($info['displayname'][0])) {
509                         $cano_fullname = $info['displayname'][0];
510                 } elseif (isset($info['cn'][0])) {
511                         $cano_fullname = $info['cn'][0];
512                 }
513                 return array(
514                         'dn' => $user_dn,
515                         'uid' => $cano_username,
516                         'fullname' => $cano_fullname,
517                         'mail' => $info['mail'][0]
518                 );
519         }
520         return false;
521 }
522
523 /**
524  * Redirect after login. Need to assing location or page
525  *
526  * @param type $location
527  * @param type $page
528  */
529 function form_auth_redirect($location, $page)
530 {
531         header('HTTP/1.0 302 Found');
532         if ($location) {
533                 header('Location: ' . $location);
534         } else {
535                 $url = get_script_uri() . '?' . $page;
536                 header('Location: ' . $url);
537         }
538 }
539
540 /**
541  * Get External Auth log-in URL
542  */
543 function get_auth_external_login_url($page, $url_after_login) {
544         global $auth_external_login_url_base;
545         $sep = '&';
546         if (strpos($auth_external_login_url_base, '?') === FALSE) {
547                 $sep = '?';
548         }
549         $url = $auth_external_login_url_base . $sep
550                 . 'page=' . rawurlencode($page)
551                 . '&url_after_login=' . rawurlencode($url_after_login);
552         return $url;
553 }