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
7 // Authentication related functions
9 define('PKWK_PASSPHRASE_LIMIT_LENGTH', 512);
11 /////////////////////////////////////////////////
14 define('AUTH_TYPE_NONE', 0);
15 define('AUTH_TYPE_BASIC', 1);
16 define('AUTH_TYPE_EXTERNAL', 2);
17 define('AUTH_TYPE_FORM', 3);
19 define('AUTH_TYPE_EXTERNAL_REMOTE_USER', 4);
20 define('AUTH_TYPE_EXTERNAL_X_FORWARDED_USER', 5);
23 // Passwd-auth related ----
25 function pkwk_login($pass = '')
29 if (! PKWK_READONLY && isset($adminpass) &&
30 pkwk_hash_compute($pass, $adminpass) === $adminpass) {
33 sleep(2); // Blocking brute force attack
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)
45 if (! is_string($phrase) || ! is_string($scheme)) return FALSE;
47 if (strlen($phrase) > PKWK_PASSPHRASE_LIMIT_LENGTH)
48 die('pkwk_hash_compute(): malicious message length');
50 // With a {scheme}salt or not
52 if (preg_match('/^(\{.+\})(.*)$/', $scheme, $matches)) {
53 $scheme = & $matches[1];
54 $salt = & $matches[2];
55 } else if ($scheme != '') {
56 $scheme = ''; // Cleartext
60 // Compute and add a scheme-prefix
61 switch (strtolower($scheme)) {
64 case '{x-php-crypt}' :
65 $hash = ($prefix ? ($canonical ? '{x-php-crypt}' : $scheme) : '') .
66 ($salt != '' ? crypt($phrase, $salt) : crypt($phrase));
71 $hash = ($prefix ? ($canonical ? '{x-php-md5}' : $scheme) : '') .
77 $hash = ($prefix ? ($canonical ? '{x-php-sha1}' : $scheme) : '') .
83 $hash = ($prefix ? ($canonical ? '{CRYPT}' : $scheme) : '') .
84 ($salt != '' ? crypt($phrase, $salt) : crypt($phrase));
89 $hash = ($prefix ? ($canonical ? '{MD5}' : $scheme) : '') .
90 base64_encode(pkwk_hex2bin(md5($phrase)));
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);
103 $hash = ($prefix ? ($canonical ? '{SHA}' : $scheme) : '') .
104 base64_encode(pkwk_hex2bin(sha1($phrase)));
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);
115 // LDAP CLEARTEXT and just cleartext
116 case '{cleartext}' : /* FALLTHROUGH */
118 $hash = ($prefix ? ($canonical ? '{CLEARTEXT}' : $scheme) : '') .
131 // LDAP related functions
133 function _pkwk_ldap_escape_callback($matches) {
134 return sprintf('\\%02x', ord($matches[0]));
137 function pkwk_ldap_escape_filter($value) {
138 if (function_exists('ldap_escape')) {
139 return ldap_escape($value, false, LDAP_ESCAPE_FILTER);
141 return preg_replace_callback('/[\\\\*()\0]/',
142 '_pkwk_ldap_escape_callback', $value);
145 function pkwk_ldap_escape_dn($value) {
146 if (function_exists('ldap_escape')) {
147 return ldap_escape($value, false, LDAP_ESCAPE_DN);
149 return preg_replace_callback('/[\\\\,=+<>;"#]/',
150 '_pkwk_ldap_escape_callback', $value);
154 // Basic-auth related ----
156 // Check edit-permission
157 function check_editable($page, $auth_flag = TRUE, $exit_flag = TRUE)
159 global $script, $_title_cannotedit, $_msg_unfreeze;
161 if (edit_auth($page, $auth_flag, $exit_flag) && is_editable($page)) {
166 if ($exit_flag === FALSE) {
167 return FALSE; // Without 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&page=' .
174 rawurlencode($page) . '">' . $_msg_unfreeze . '</a>)';
175 $page = str_replace('$1', make_search($page), $_title_cannotedit);
176 catbody($title, $page, $body);
182 // Check read-permission
183 function check_readable($page, $auth_flag = TRUE, $exit_flag = TRUE)
185 return read_auth($page, $auth_flag, $exit_flag);
188 function edit_auth($page, $auth_flag = TRUE, $exit_flag = TRUE)
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;
195 function read_auth($page, $auth_flag = TRUE, $exit_flag = TRUE)
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;
202 // Basic authentication
203 function basic_auth($page, $auth_flag, $exit_flag, $auth_pages, $title_cannot)
205 global $auth_method_type, $auth_users, $_msg_auth, $auth_user, $auth_groups;
206 global $auth_user_groups, $auth_type, $g_query_string;
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
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));
220 if (empty($user_list)) return TRUE; // No limit
225 count(array_intersect($auth_user_groups, $user_list)) === 0)
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);
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);
261 * Send 401 if client send a invalid credentials
263 * @return true if valid, false if invalid credentials
265 function ensure_valid_auth_user()
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:
275 case AUTH_TYPE_EXTERNAL:
276 case AUTH_TYPE_EXTERNAL_REMOTE_USER:
277 case AUTH_TYPE_EXTERNAL_X_FORWARDED_USER:
280 // $auth_type is not valid, Set form auth as default
281 $auth_type = AUTH_TYPE_FORM;
284 $auth_dynamic_groups = null;
285 switch ($auth_type) {
286 case AUTH_TYPE_BASIC:
288 if (isset($_SERVER['PHP_AUTH_USER'])) {
289 $user = $_SERVER['PHP_AUTH_USER'];
290 if (in_array($user, array_keys($auth_users))) {
291 if (pkwk_hash_compute(
292 $_SERVER['PHP_AUTH_PW'],
293 $auth_users[$user]) === $auth_users[$user]) {
295 $auth_user_fullname = $auth_user;
296 $auth_user_groups = get_groups_from_username($user);
300 header('WWW-Authenticate: Basic realm="' . $_msg_auth . '"');
301 header('HTTP/1.0 401 Unauthorized');
304 $auth_user_groups = array();
305 return true; // no auth input
308 case AUTH_TYPE_EXTERNAL:
313 $dynamic_groups = array();
314 if (isset($_SESSION['authenticated_user'])) {
315 $user = $_SESSION['authenticated_user'];
316 if (isset($_SESSION['authenticated_user_fullname'])) {
317 $fullname = $_SESSION['authenticated_user_fullname'];
318 $dynamic_groups = $_SESSION['dynamic_member_groups'];
321 if ($auth_type === AUTH_TYPE_EXTERNAL && $ldap_user_account) {
322 $ldap_user_info = ldap_get_simple_user_info($user);
323 if ($ldap_user_info) {
324 $fullname = $ldap_user_info['fullname'];
325 $dynamic_groups = $ldap_user_info['dynamic_member_groups'];
328 $_SESSION['authenticated_user_fullname'] = $fullname;
329 $_SESSION['dynamic_member_groups'] = $dynamic_groups;
333 $auth_user_fullname = $fullname;
334 $auth_dynamic_groups = $dynamic_groups;
337 case AUTH_TYPE_EXTERNAL_REMOTE_USER:
338 $auth_user = $_SERVER['REMOTE_USER'];
339 $auth_user_fullname = $auth_user;
341 case AUTH_TYPE_EXTERNAL_X_FORWARDED_USER:
342 $auth_user = $_SERVER['HTTP_X_FORWARDED_USER'];
343 $auth_user_fullname = $auth_user;
345 default: // AUTH_TYPE_NONE
347 $auth_user_fullname = '';
350 $auth_user_groups = get_groups_from_username($auth_user);
351 if ($auth_dynamic_groups && is_array($auth_dynamic_groups)) {
352 $auth_user_groups = array_values(array_merge($auth_user_groups, $auth_dynamic_groups));
354 return true; // is not basic auth
358 * Return group name array whose group contains the user
360 * Result array contains reserved 'valid-user' group for all authenticated user
361 * @global array $auth_groups
362 * @param string $user
365 function get_groups_from_username($user)
370 foreach ($auth_groups as $group=>$users) {
371 $sp = explode(',', $users);
372 if (in_array($user, $sp)) {
376 // Implicit group that has same name as user itself
378 // 'valid-user' group for
379 $valid_user = 'valid-user';
380 if (!in_array($valid_user, $groups)) {
381 $groups[] = $valid_user;
389 * Get authenticated user name.
391 * @global type $auth_user
394 function get_auth_user()
401 * Sign in with username and password
403 * @param String username
404 * @param String password
405 * @return true is sign in is OK
407 function form_auth($username, $password)
409 global $ldap_user_account, $auth_users;
411 if ($ldap_user_account) {
413 return ldap_auth($username, $password);
415 // Defined users in pukiwiki.ini.php
416 if (in_array($user, array_keys($auth_users))) {
417 if (pkwk_hash_compute(
419 $auth_users[$user]) === $auth_users[$user]) {
421 session_regenerate_id(true); // require: PHP5.1+
422 $_SESSION['authenticated_user'] = $user;
423 $_SESSION['authenticated_user_fullname'] = $user;
431 function ldap_auth($username, $password)
433 global $ldap_server, $ldap_base_dn, $ldap_bind_dn, $ldap_bind_password;
434 $ldapconn = ldap_connect($ldap_server);
436 ldap_set_option($ldapconn, LDAP_OPT_PROTOCOL_VERSION, 3);
437 ldap_set_option($ldapconn, LDAP_OPT_REFERRALS, 0);
438 if (preg_match('#\$login\b#', $ldap_bind_dn)) {
439 // Bind by user credential
440 $username_esc = pkwk_ldap_escape_dn($username);
441 $bind_dn_user = preg_replace('#\$login\b#', $username_esc, $ldap_bind_dn);
442 $ldap_bind_user = ldap_bind($ldapconn, $bind_dn_user, $password);
443 if ($ldap_bind_user) {
444 $user_info = get_ldap_user_info($ldapconn, $username, $ldap_base_dn);
446 $ldap_groups = get_ldap_groups_with_user($ldapconn, $username, $user_info['is_ad']);
447 session_regenerate_id(true); // require: PHP5.1+
448 $_SESSION['authenticated_user'] = $user_info['uid'];
449 $_SESSION['authenticated_user_fullname'] = $user_info['fullname'];
450 $_SESSION['dynamic_member_groups'] = $ldap_groups;
456 $ldap_bind = ldap_bind($ldapconn, $ldap_bind_dn, $ldap_bind_password);
458 $user_info = get_ldap_user_info($ldapconn, $username, $ldap_base_dn);
460 $ldap_bind_user2 = ldap_bind($ldapconn, $user_info['dn'], $password);
461 if ($ldap_bind_user2) {
462 $ldap_groups = get_ldap_groups_with_user($ldapconn, $username, $user_info['is_ad']);
463 session_regenerate_id(true); // require: PHP5.1+
464 $_SESSION['authenticated_user'] = $user_info['uid'];
465 $_SESSION['authenticated_user_fullname'] = $user_info['fullname'];
466 $_SESSION['dynamic_member_groups'] = $ldap_groups;
476 // Get LDAP user info via bind DN
477 function ldap_get_simple_user_info($username)
479 global $ldap_server, $ldap_base_dn, $ldap_bind_dn, $ldap_bind_password;
480 $ldapconn = ldap_connect($ldap_server);
482 ldap_set_option($ldapconn, LDAP_OPT_PROTOCOL_VERSION, 3);
483 ldap_set_option($ldapconn, LDAP_OPT_REFERRALS, 0);
485 $ldap_bind = ldap_bind($ldapconn, $ldap_bind_dn, $ldap_bind_password);
487 $user_info = get_ldap_user_info($ldapconn, $username, $ldap_base_dn);
489 $ldap_groups = get_ldap_groups_with_user($ldapconn,
490 $username, $user_info['is_ad']);
491 $user_info['dynamic_member_groups'] = $ldap_groups;
500 * Search user and get 'dn', 'uid', 'fullname' and 'mail'
501 * @param type $ldapconn
502 * @param type $username
503 * @param type $base_dn
506 function get_ldap_user_info($ldapconn, $username, $base_dn) {
507 $username_esc = pkwk_ldap_escape_filter($username);
508 $filter = "(|(uid=$username_esc)(sAMAccountName=$username_esc))";
509 $result1 = ldap_search($ldapconn, $base_dn, $filter, array('dn', 'uid', 'cn', 'samaccountname', 'displayname', 'mail'));
510 $entries = ldap_get_entries($ldapconn, $result1);
511 if (!isset($entries[0])) {
515 if (isset($info['dn'])) {
516 $user_dn = $info['dn'];
517 $cano_username = $username;
518 $is_active_directory = false;
519 if (isset($info['uid'][0])) {
520 $cano_username = $info['uid'][0];
521 } elseif (isset($info['samaccountname'][0])) {
522 $cano_username = $info['samaccountname'][0];
523 $is_active_directory = true;
525 $cano_fullname = $username;
526 if (isset($info['displayname'][0])) {
527 $cano_fullname = $info['displayname'][0];
528 } elseif (isset($info['cn'][0])) {
529 $cano_fullname = $info['cn'][0];
533 'uid' => $cano_username,
534 'fullname' => $cano_fullname,
535 'mail' => $info['mail'][0],
536 'is_ad' => $is_active_directory,
543 * Redirect after login. Need to assing location or page
545 * @param type $location
548 function form_auth_redirect($location, $page)
550 header('HTTP/1.0 302 Found');
552 header('Location: ' . $location);
554 $url = get_script_uri() . '?' . $page;
555 header('Location: ' . $url);
560 * Get External Auth log-in URL
562 function get_auth_external_login_url($page, $url_after_login) {
563 global $auth_external_login_url_base;
565 if (strpos($auth_external_login_url_base, '?') === FALSE) {
568 $url = $auth_external_login_url_base . $sep
569 . 'page=' . rawurlencode($page)
570 . '&url_after_login=' . rawurlencode($url_after_login);
574 function get_auth_user_prefix() {
575 global $ldap_user_account, $auth_type;
576 global $auth_provider_user_prefix_default;
577 global $auth_provider_user_prefix_ldap;
578 global $auth_provider_user_prefix_external;
580 switch ($auth_type) {
581 case AUTH_TYPE_BASIC:
582 $user_prefix = $auth_provider_user_prefix_default;
584 case AUTH_TYPE_EXTERNAL:
585 case AUTH_TYPE_EXTERNAL_REMOTE_USER:
586 case AUTH_TYPE_EXTERNAL_X_FORWARDED_USER:
587 $user_prefix = $auth_provider_user_prefix_external;
590 if ($ldap_user_account) {
591 $user_prefix = $auth_provider_user_prefix_ldap;
593 $user_prefix = $auth_provider_user_prefix_default;
600 function get_ldap_related_groups() {
601 global $read_auth_pages, $edit_auth_pages;
602 global $auth_provider_user_prefix_ldap;
603 $ldap_groups = array();
604 foreach ($read_auth_pages as $pattern=>$groups) {
605 $sp_groups = explode(',', $groups);
606 foreach ($sp_groups as $group) {
607 if (strpos($group, $auth_provider_user_prefix_ldap) === 0) {
608 $ldap_groups[] = $group;
612 foreach ($edit_auth_pages as $pattern=>$groups) {
613 $sp_groups = explode(',', $groups);
614 foreach ($sp_groups as $group) {
615 if (strpos($group, $auth_provider_user_prefix_ldap) === 0) {
616 $ldap_groups[] = $group;
620 $ldap_groups_unique = array_values(array_unique($ldap_groups));
621 return $ldap_groups_unique;
625 * Get LDAP groups user belongs to
627 * @param Resource $ldapconn
628 * @param String $user
632 function get_ldap_groups_with_user($ldapconn, $user, $is_ad) {
633 global $auth_provider_user_prefix_ldap;
634 global $ldap_base_dn;
635 $related_groups = get_ldap_related_groups();
636 if (count($related_groups) == 0) {
640 foreach ($related_groups as $group_full) {
641 $g = substr($group_full, strlen($auth_provider_user_prefix_ldap));
642 $gfilter .= sprintf('(cn=%s)', pkwk_ldap_escape_filter($g));
644 $gfilter .= sprintf('(sAMAccountName=%s)', pkwk_ldap_escape_filter($g));
648 $result_g = ldap_search($ldapconn, $ldap_base_dn, $gfilter,
649 array('dn', 'uid', 'cn', 'samaccountname'));
650 $entries = ldap_get_entries($ldapconn, $result_g);
651 if (!isset($entries[0])) {
657 $entry_count = $entries['count'];
658 $group_list = array();
659 for ($i = 0; $i < $entry_count; $i++) {
660 $group_name = $entries[$i]['cn'][0];
662 $group_name = $entries[$i]['samaccountname'][0];
664 $group_list[] = array(
665 'name' => $group_name,
666 'dn' => $entries[$i]['dn']
669 $groups_member = array();
670 $groups_nonmember = array();
671 foreach ($group_list as $gp) {
672 $fmt = '(&(uid=%s)(memberOf=%s))';
674 // LDAP_MATCHING_RULE_IN_CHAIN: Active Directory specific rule
675 $fmt = '(&(sAMAccountName=%s)(memberOf:1.2.840.113556.1.4.1941:=%s))';
677 $user_gfilter = sprintf($fmt,
678 pkwk_ldap_escape_filter($user),
679 pkwk_ldap_escape_filter($gp['dn']));
680 $result_e = ldap_search($ldapconn, $ldap_base_dn, $user_gfilter,
682 $user_e = ldap_get_entries($ldapconn, $result_e);
683 if (isset($user_e['count']) && $user_e['count'] > 0) {
684 $groups_member[] = $gp['name'];
686 $groups_nonmember[] = $gp['name'];
689 $groups_full = array();
690 foreach ($groups_member as $g) {
691 $groups_full[] = $auth_provider_user_prefix_ldap . $g;