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) : '') .
132 // Basic-auth related ----
134 // Check edit-permission
135 function check_editable($page, $auth_flag = TRUE, $exit_flag = TRUE)
137 global $script, $_title_cannotedit, $_msg_unfreeze;
139 if (edit_auth($page, $auth_flag, $exit_flag) && is_editable($page)) {
144 if ($exit_flag === FALSE) {
145 return FALSE; // Without exit
148 $body = $title = str_replace('$1',
149 htmlsc(strip_bracket($page)), $_title_cannotedit);
150 if (is_freeze($page))
151 $body .= '(<a href="' . $script . '?cmd=unfreeze&page=' .
152 rawurlencode($page) . '">' . $_msg_unfreeze . '</a>)';
153 $page = str_replace('$1', make_search($page), $_title_cannotedit);
154 catbody($title, $page, $body);
160 // Check read-permission
161 function check_readable($page, $auth_flag = TRUE, $exit_flag = TRUE)
163 return read_auth($page, $auth_flag, $exit_flag);
166 function edit_auth($page, $auth_flag = TRUE, $exit_flag = TRUE)
168 global $edit_auth, $edit_auth_pages, $_title_cannotedit;
169 return $edit_auth ? basic_auth($page, $auth_flag, $exit_flag,
170 $edit_auth_pages, $_title_cannotedit) : TRUE;
173 function read_auth($page, $auth_flag = TRUE, $exit_flag = TRUE)
175 global $read_auth, $read_auth_pages, $_title_cannotread;
176 return $read_auth ? basic_auth($page, $auth_flag, $exit_flag,
177 $read_auth_pages, $_title_cannotread) : TRUE;
180 // Basic authentication
181 function basic_auth($page, $auth_flag, $exit_flag, $auth_pages, $title_cannot)
183 global $auth_method_type, $auth_users, $_msg_auth, $auth_user, $auth_groups;
184 global $auth_user_groups, $auth_type, $g_query_string;
187 if ($auth_method_type == 'pagename') {
188 $target_str = $page; // Page name
189 } else if ($auth_method_type == 'contents') {
190 $target_str = join('', get_source($page)); // Its contents
193 $user_list = array();
194 foreach($auth_pages as $key=>$val)
195 if (preg_match($key, $target_str))
196 $user_list = array_merge($user_list, explode(',', $val));
198 if (empty($user_list)) return TRUE; // No limit
203 count(array_intersect($auth_user_groups, $user_list)) === 0)
206 pkwk_common_headers();
207 if ($auth_flag && !$auth_user) {
208 if (AUTH_TYPE_BASIC === $auth_type) {
209 header('WWW-Authenticate: Basic realm="' . $_msg_auth . '"');
210 header('HTTP/1.0 401 Unauthorized');
211 } elseif (AUTH_TYPE_FORM === $auth_type) {
212 $url_after_login = get_script_uri() . '?' . $g_query_string;
213 $loginurl = get_script_uri() . '?plugin=loginform'
214 . '&page=' . rawurlencode($page)
215 . '&url_after_login=' . rawurlencode($url_after_login);
216 header('HTTP/1.0 302 Found');
217 header('Location: ' . $loginurl);
218 } elseif (AUTH_TYPE_EXTERNAL === $auth_type) {
219 $url_after_login = get_script_uri() . '?' . $g_query_string;
220 $loginurl = get_auth_external_login_url($page, $url_after_login);
221 header('HTTP/1.0 302 Found');
222 header('Location: ' . $loginurl);
226 $body = $title = str_replace('$1',
227 htmlsc(strip_bracket($page)), $title_cannot);
228 $page = str_replace('$1', make_search($page), $title_cannot);
229 catbody($title, $page, $body);
239 * Send 401 if client send a invalid credentials
241 * @return true if valid, false if invalid credentials
243 function ensure_valid_auth_user()
245 global $auth_type, $auth_users, $_msg_auth, $auth_user, $auth_groups;
246 global $auth_user_groups, $auth_user_fullname;
247 global $ldap_user_account;
248 global $read_auth, $edit_auth;
249 if ($read_auth || $edit_auth) {
250 switch ($auth_type) {
251 case AUTH_TYPE_BASIC:
253 case AUTH_TYPE_EXTERNAL:
254 case AUTH_TYPE_EXTERNAL_REMOTE_USER:
255 case AUTH_TYPE_EXTERNAL_X_FORWARDED_USER:
258 // $auth_type is not valid, Set form auth as default
259 $auth_type = AUTH_TYPE_FORM;
262 switch ($auth_type) {
263 case AUTH_TYPE_BASIC:
265 if (isset($_SERVER['PHP_AUTH_USER'])) {
266 $user = $_SERVER['PHP_AUTH_USER'];
267 if (in_array($user, array_keys($auth_users))) {
268 if (pkwk_hash_compute(
269 $_SERVER['PHP_AUTH_PW'],
270 $auth_users[$user]) === $auth_users[$user]) {
272 $auth_user_fullname = $auth_user;
273 $auth_user_groups = get_groups_from_username($user);
277 header('WWW-Authenticate: Basic realm="' . $_msg_auth . '"');
278 header('HTTP/1.0 401 Unauthorized');
281 $auth_user_groups = get_groups_from_username($user);
282 return true; // no auth input
285 case AUTH_TYPE_EXTERNAL:
290 if (isset($_SESSION['authenticated_user'])) {
291 $user = $_SESSION['authenticated_user'];
292 if (isset($_SESSION['authenticated_user_fullname'])) {
293 $fullname = $_SESSION['authenticated_user_fullname'];
296 if ($auth_type === AUTH_TYPE_EXTERNAL && $ldap_user_account) {
297 $ldap_user_info = ldap_get_simple_user_info($user);
298 if ($ldap_user_info) {
299 $fullname = $ldap_user_info['fullname'];
302 $_SESSION['authenticated_user_fullname'] = $fullname;
306 $auth_user_fullname = $fullname;
309 case AUTH_TYPE_EXTERNAL_REMOTE_USER:
310 $auth_user = $_SERVER['REMOTE_USER'];
311 $auth_user_fullname = $auth_user;
313 case AUTH_TYPE_EXTERNAL_X_FORWARDED_USER:
314 $auth_user = $_SERVER['HTTP_X_FORWARDED_USER'];
315 $auth_user_fullname = $auth_user;
317 default: // AUTH_TYPE_NONE
319 $auth_user_fullname = '';
322 $auth_user_groups = get_groups_from_username($auth_user);
323 return true; // is not basic auth
327 * Return group name array whose group contains the user
329 * Result array contains reserved 'valid-user' group for all authenticated user
330 * @global array $auth_groups
331 * @param string $user
334 function get_groups_from_username($user)
339 foreach ($auth_groups as $group=>$users) {
340 $sp = explode(',', $users);
341 if (in_array($user, $sp)) {
345 // Implecit group that has same name as user itself
347 // 'valid-user' group for
348 $valid_user = 'valid-user';
349 if (!in_array($valid_user, $groups)) {
350 $groups[] = $valid_user;
358 * Get authenticated user name.
360 * @global type $auth_user
363 function get_auth_user()
370 * Sign in with username and password
372 * @param String username
373 * @param String password
374 * @return true is sign in is OK
376 function form_auth($username, $password)
378 global $ldap_user_account, $auth_users;
380 if ($ldap_user_account) {
382 return ldap_auth($username, $password);
384 // Defined users in pukiwiki.ini.php
385 if (in_array($user, array_keys($auth_users))) {
386 if (pkwk_hash_compute(
388 $auth_users[$user]) === $auth_users[$user]) {
390 session_regenerate_id(true); // require: PHP5.1+
391 $_SESSION['authenticated_user'] = $user;
392 $_SESSION['authenticated_user_fullname'] = $user;
400 function ldap_auth($username, $password)
402 global $ldap_server, $ldap_base_dn, $ldap_bind_dn, $ldap_bind_password;
403 $ldapconn = ldap_connect($ldap_server);
405 ldap_set_option($ldapconn, LDAP_OPT_PROTOCOL_VERSION, 3);
406 ldap_set_option($ldapconn, LDAP_OPT_REFERRALS, 0);
407 if (preg_match('#\$login\b#', $ldap_bind_dn)) {
408 // Bind by user credential
409 $bind_dn_user = preg_replace('#\$login#', $username, $ldap_bind_dn);
410 $ldap_bind_user = ldap_bind($ldapconn, $bind_dn_user, $password);
411 if ($ldap_bind_user) {
412 $user_info = get_ldap_user_info($ldapconn, $username, $ldap_base_dn);
414 session_regenerate_id(true); // require: PHP5.1+
415 $_SESSION['authenticated_user'] = $user_info['uid'];
416 $_SESSION['authenticated_user_fullname'] = $user_info['fullname'];
422 $ldap_bind = ldap_bind($ldapconn, $ldap_bind_dn, $ldap_bind_password);
424 $user_info = get_ldap_user_info($ldapconn, $username, $ldap_base_dn);
426 $ldap_bind_user2 = ldap_bind($ldapconn, $user_info['dn'], $password);
427 if ($ldap_bind_user2) {
428 session_regenerate_id(true); // require: PHP5.1+
429 $_SESSION['authenticated_user'] = $user_info['uid'];
430 $_SESSION['authenticated_user_fullname'] = $user_info['fullname'];
440 // Get LDAP user info via bind DN
441 function ldap_get_simple_user_info($username)
443 global $ldap_server, $ldap_base_dn, $ldap_bind_dn, $ldap_bind_password;
444 $ldapconn = ldap_connect($ldap_server);
446 ldap_set_option($ldapconn, LDAP_OPT_PROTOCOL_VERSION, 3);
447 ldap_set_option($ldapconn, LDAP_OPT_REFERRALS, 0);
449 $ldap_bind = ldap_bind($ldapconn, $ldap_bind_dn, $ldap_bind_password);
451 $user_info = get_ldap_user_info($ldapconn, $username, $ldap_base_dn);
461 * Search user and get 'dn', 'uid', 'fullname' and 'mail'
462 * @param type $ldapconn
463 * @param type $username
464 * @param type $base_dn
467 function get_ldap_user_info($ldapconn, $username, $base_dn) {
468 $filter = "(|(uid=$username)(sAMAccountName=$username))";
469 $result1 = ldap_search($ldapconn, $base_dn, $filter, array('dn', 'uid', 'cn', 'samaccountname', 'displayname', 'mail'));
470 $entries = ldap_get_entries($ldapconn, $result1);
472 if (isset($info['dn'])) {
473 $user_dn = $info['dn'];
474 $cano_username = $username;
475 if (isset($info['uid'][0])) {
476 $cano_username = $info['uid'][0];
477 } elseif (isset($info['samaccountname'][0])) {
478 $cano_username = $info['samaccountname'][0];
480 $cano_fullname = $username;
481 if (isset($info['displayname'][0])) {
482 $cano_fullname = $info['displayname'][0];
483 } elseif (isset($info['cn'][0])) {
484 $cano_fullname = $info['cn'][0];
488 'uid' => $cano_username,
489 'fullname' => $cano_fullname,
490 'mail' => $info['mail'][0]
497 * Redirect after login. Need to assing location or page
499 * @param type $location
502 function form_auth_redirect($location, $page)
504 header('HTTP/1.0 302 Found');
506 header('Location: ' . $location);
508 $url = get_script_uri() . '?' . $page;
509 header('Location: ' . $url);
514 * Get External Auth log-in URL
516 function get_auth_external_login_url($page, $url_after_login) {
517 global $auth_external_login_url_base;
519 if (strpos($auth_external_login_url_base, '?') === FALSE) {
522 $url = $auth_external_login_url_base . $sep
523 . 'page=' . rawurlencode($page)
524 . '&url_after_login=' . rawurlencode($url_after_login);