OSDN Git Service

83aaafe7c051564c3ce5cf026569fa279d91dc1e
[pukiwiki/pukiwiki.git] / lib / auth.php
1 <?php
2 // PukiWiki - Yet another WikiWikiWeb clone
3 // auth.php
4 // Copyright 2003-2018 PukiWiki Development 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 define('AUTH_TYPE_SAML', 6);
22
23
24 // Passwd-auth related ----
25
26 function pkwk_login($pass = '')
27 {
28         global $adminpass;
29
30         if (! PKWK_READONLY && isset($adminpass) &&
31                 pkwk_hash_compute($pass, $adminpass) === $adminpass) {
32                 return TRUE;
33         } else {
34                 sleep(2);       // Blocking brute force attack
35                 return FALSE;
36         }
37 }
38
39 // Compute RFC2307 'userPassword' value, like slappasswd (OpenLDAP)
40 // $phrase : Pass-phrase
41 // $scheme : Specify '{scheme}' or '{scheme}salt'
42 // $prefix : Output with a scheme-prefix or not
43 // $canonical : Correct or Preserve $scheme prefix
44 function pkwk_hash_compute($phrase = '', $scheme = '{x-php-md5}', $prefix = TRUE, $canonical = FALSE)
45 {
46         if (! is_string($phrase) || ! is_string($scheme)) return FALSE;
47
48         if (strlen($phrase) > PKWK_PASSPHRASE_LIMIT_LENGTH)
49                 die('pkwk_hash_compute(): malicious message length');
50
51         // With a {scheme}salt or not
52         $matches = array();
53         if (preg_match('/^(\{.+\})(.*)$/', $scheme, $matches)) {
54                 $scheme = $matches[1];
55                 $salt   = $matches[2];
56         } else if ($scheme != '') {
57                 $scheme  = ''; // Cleartext
58                 $salt    = '';
59         }
60
61         // Compute and add a scheme-prefix
62         switch (strtolower($scheme)) {
63
64         // PHP crypt()
65         case '{x-php-crypt}' :
66                 $hash = ($prefix ? ($canonical ? '{x-php-crypt}' : $scheme) : '') .
67                         ($salt != '' ? crypt($phrase, $salt) : crypt($phrase));
68                 break;
69
70         // PHP md5()
71         case '{x-php-md5}'   :
72                 $hash = ($prefix ? ($canonical ? '{x-php-md5}' : $scheme) : '') .
73                         md5($phrase);
74                 break;
75
76         // PHP sha1()
77         case '{x-php-sha1}'  :
78                 $hash = ($prefix ? ($canonical ? '{x-php-sha1}' : $scheme) : '') .
79                         sha1($phrase);
80                 break;
81
82         // PHP sha256
83         case '{x-php-sha256}'  :
84                 $hash = ($prefix ? ($canonical ? '{x-php-sha256}' : $scheme) : '') .
85                         hash('sha256', $phrase);
86                 break;
87
88         // PHP sha384
89         case '{x-php-sha384}'  :
90                 $hash = ($prefix ? ($canonical ? '{x-php-sha384}' : $scheme) : '') .
91                         hash('sha384', $phrase);
92                 break;
93
94         // PHP sha512
95         case '{x-php-sha512}'  :
96                 $hash = ($prefix ? ($canonical ? '{x-php-sha512}' : $scheme) : '') .
97                         hash('sha512', $phrase);
98                 break;
99
100         // LDAP CRYPT
101         case '{crypt}'       :
102                 $hash = ($prefix ? ($canonical ? '{CRYPT}' : $scheme) : '') .
103                         ($salt != '' ? crypt($phrase, $salt) : crypt($phrase));
104                 break;
105
106         // LDAP MD5
107         case '{md5}'         :
108                 $hash = ($prefix ? ($canonical ? '{MD5}' : $scheme) : '') .
109                         base64_encode(pkwk_hex2bin(md5($phrase)));
110                 break;
111
112         // LDAP SMD5
113         case '{smd5}'        :
114                 // MD5 Key length = 128bits = 16bytes
115                 $salt = ($salt != '' ? substr(base64_decode($salt), 16) : substr(crypt(''), -8));
116                 $hash = ($prefix ? ($canonical ? '{SMD5}' : $scheme) : '') .
117                         base64_encode(pkwk_hex2bin(md5($phrase . $salt)) . $salt);
118                 break;
119
120         // LDAP SHA
121         case '{sha}'         :
122                 $hash = ($prefix ? ($canonical ? '{SHA}' : $scheme) : '') .
123                         base64_encode(pkwk_hex2bin(sha1($phrase)));
124                 break;
125
126         // LDAP SSHA
127         case '{ssha}'        :
128                 // SHA-1 Key length = 160bits = 20bytes
129                 $salt = ($salt != '' ? substr(base64_decode($salt), 20) : substr(crypt(''), -8));
130                 $hash = ($prefix ? ($canonical ? '{SSHA}' : $scheme) : '') .
131                         base64_encode(pkwk_hex2bin(sha1($phrase . $salt)) . $salt);
132                 break;
133
134         // LDAP SHA256
135         case '{sha256}'        :
136                 $hash = ($prefix ? ($canonical ? '{SHA256}' : $scheme) : '') .
137                         base64_encode(hash('sha256', $phrase, TRUE));
138                 break;
139
140         // LDAP SSHA256
141         case '{ssha256}'        :
142                 // SHA-2 SHA-256 Key length = 256bits = 32bytes
143                 $salt = ($salt != '' ? substr(base64_decode($salt), 32) : substr(crypt(''), -8));
144                 $hash = ($prefix ? ($canonical ? '{SSHA256}' : $scheme) : '') .
145                         base64_encode(hash('sha256', $phrase . $salt, TRUE) . $salt);
146                 break;
147
148         // LDAP SHA384
149         case '{sha384}'        :
150                 $hash = ($prefix ? ($canonical ? '{SHA384}' : $scheme) : '') .
151                         base64_encode(hash('sha384', $phrase, TRUE));
152                 break;
153
154         // LDAP SSHA384
155         case '{ssha384}'        :
156                 // SHA-2 SHA-384 Key length = 384bits = 48bytes
157                 $salt = ($salt != '' ? substr(base64_decode($salt), 48) : substr(crypt(''), -8));
158                 $hash = ($prefix ? ($canonical ? '{SSHA384}' : $scheme) : '') .
159                         base64_encode(hash('sha384', $phrase . $salt, TRUE) . $salt);
160                 break;
161
162         // LDAP SHA512
163         case '{sha512}'        :
164                 $hash = ($prefix ? ($canonical ? '{SHA512}' : $scheme) : '') .
165                         base64_encode(hash('sha512', $phrase, TRUE));
166                 break;
167
168         // LDAP SSHA512
169         case '{ssha512}'        :
170                 // SHA-2 SHA-512 Key length = 512bits = 64bytes
171                 $salt = ($salt != '' ? substr(base64_decode($salt), 64) : substr(crypt(''), -8));
172                 $hash = ($prefix ? ($canonical ? '{SSHA512}' : $scheme) : '') .
173                         base64_encode(hash('sha512', $phrase . $salt, TRUE) . $salt);
174                 break;
175
176         // LDAP CLEARTEXT and just cleartext
177         case '{cleartext}'   : /* FALLTHROUGH */
178         case ''              :
179                 $hash = ($prefix ? ($canonical ? '{CLEARTEXT}' : $scheme) : '') .
180                         $phrase;
181                 break;
182
183         // Invalid scheme
184         default:
185                 $hash = FALSE;
186                 break;
187         }
188
189         return $hash;
190 }
191
192 // LDAP related functions
193
194 function _pkwk_ldap_escape_callback($matches) {
195         return sprintf('\\%02x', ord($matches[0]));
196 }
197
198 function pkwk_ldap_escape_filter($value) {
199         if (function_exists('ldap_escape')) {
200                 return ldap_escape($value, false, LDAP_ESCAPE_FILTER);
201         }
202         return preg_replace_callback('/[\\\\*()\0]/',
203                 '_pkwk_ldap_escape_callback', $value);
204 }
205
206 function pkwk_ldap_escape_dn($value) {
207         if (function_exists('ldap_escape')) {
208                 return ldap_escape($value, false, LDAP_ESCAPE_DN);
209         }
210         return preg_replace_callback('/[\\\\,=+<>;"#]/',
211                 '_pkwk_ldap_escape_callback', $value);
212 }
213
214
215 // Basic-auth related ----
216
217 // Check edit-permission
218 function check_editable($page, $auth_enabled = TRUE, $exit_on_fail = TRUE)
219 {
220         global $_title_cannotedit, $_msg_unfreeze;
221
222         if (edit_auth($page, $auth_enabled, $exit_on_fail) && is_editable($page)) {
223                 // Editable
224                 return TRUE;
225         } else {
226                 // Not editable
227                 if ($exit_on_fail === FALSE) {
228                         return FALSE; // Without exit
229                 } else {
230                         // With exit
231                         $body = $title = str_replace('$1',
232                                 htmlsc(strip_bracket($page)), $_title_cannotedit);
233                         if (is_freeze($page))
234                                 $body .= '(<a href="' . get_base_uri() . '?cmd=unfreeze&amp;page=' .
235                                         rawurlencode($page) . '">' . $_msg_unfreeze . '</a>)';
236                         $page = str_replace('$1', make_search($page), $_title_cannotedit);
237                         catbody($title, $page, $body);
238                         exit;
239                 }
240         }
241 }
242
243 /**
244  * Whether the page is readable from current user or not.
245  */
246 function is_page_readable($page) {
247         global $read_auth_pages;
248         return _is_page_accessible($page, $read_auth_pages);
249 }
250
251 /**
252  * Whether the page is writable from current user or not.
253  */
254 function is_page_writable($page) {
255         global $edit_auth_pages;
256         return _is_page_accessible($page, $edit_auth_pages);
257 }
258
259 /**
260  * Get whether a current auth user can access the page
261  *
262  * @param $page page name
263  * @param $auth_pages pagepattern -> groups map
264  * @return true if a current user can access the page
265  */
266 function _is_page_accessible($page, $auth_pages) {
267         global $auth_method_type, $auth_user_groups, $auth_user;
268
269         // Checked by:
270         $target_str = '';
271         if ($auth_method_type == 'pagename') {
272                 $target_str = $page; // Page name
273         } else if ($auth_method_type == 'contents') {
274                 $target_str = join('', get_source($page)); // Its contents
275         }
276         $user_list = array();
277         foreach($auth_pages as $key=>$val) {
278                 if (preg_match($key, $target_str)) {
279                         $user_list = array_merge($user_list, explode(',', $val));
280                 }
281         }
282         if (empty($user_list)) return TRUE; // No limit
283         if (!$auth_user) {
284                 // Current user doesen't yet log in.
285                 return FALSE;
286         }
287         if (count(array_intersect($auth_user_groups, $user_list)) === 0) {
288                 return FALSE;
289         }
290         return TRUE;
291 }
292
293 /**
294  * Ensure the page is readable, or show Login UI.
295  * @param $page page
296  */
297 function ensure_page_readable($page) {
298         global $read_auth, $read_auth_pages, $_title_cannotread;
299         if (!$read_auth) {
300                 return true;
301         }
302         return basic_auth($page, true, true,
303                 $read_auth_pages, $_title_cannotread);
304 }
305
306 /**
307  * Ensure the page is writable, or show Login UI.
308  * @param $page page
309  */
310 function ensure_page_writable($page) {
311         global $edit_auth, $edit_auth_pages, $_title_cannotedit;
312         if (!$edit_auth) {
313                 return true;
314         }
315         return basic_auth($page, true, true,
316                 $edit_auth_pages, $_title_cannotedit);
317 }
318
319 /**
320  * Check a page is readable or not, show Auth UI in some cases.
321  *
322  * @param $page page name
323  * @param $auth_enabled true if auth is available (Normally true)
324  * @param $exit_on_fail  (Normally true)
325  * @return true if the page is readable
326  */
327 function check_readable($page, $auth_enabled = TRUE, $exit_on_fail = TRUE)
328 {
329         return read_auth($page, $auth_enabled, $exit_on_fail);
330 }
331
332 function edit_auth($page, $auth_enabled = TRUE, $exit_on_fail = TRUE)
333 {
334         global $edit_auth, $edit_auth_pages, $_title_cannotedit;
335         return $edit_auth ?  basic_auth($page, $auth_enabled, $exit_on_fail,
336                 $edit_auth_pages, $_title_cannotedit) : TRUE;
337 }
338
339 function read_auth($page, $auth_enabled = TRUE, $exit_on_fail = TRUE)
340 {
341         global $read_auth, $read_auth_pages, $_title_cannotread;
342         return $read_auth ?  basic_auth($page, $auth_enabled, $exit_on_fail,
343                 $read_auth_pages, $_title_cannotread) : TRUE;
344 }
345
346 /**
347  * Authentication
348  *
349  * @param $page page name
350  * @param $auth_enabled true if auth is available
351  * @param $exit_on_fail Show forbidden message and stop all following processes
352  * @param $auth_pages accessible users -> pages pattern map
353  * @param $title_cannot forbidden message
354  */
355 function basic_auth($page, $auth_enabled, $exit_on_fail, $auth_pages, $title_cannot)
356 {
357         global $auth_users, $_msg_auth, $auth_user;
358         global $auth_type, $g_query_string;
359         $is_accessible = _is_page_accessible($page, $auth_pages);
360         if ($is_accessible) {
361                 return TRUE;
362         } else {
363                 // Auth failed
364                 pkwk_common_headers();
365                 if ($auth_enabled && !$auth_user) {
366                         if (AUTH_TYPE_BASIC === $auth_type) {
367                                 header('WWW-Authenticate: Basic realm="' . $_msg_auth . '"');
368                                 header('HTTP/1.0 401 Unauthorized');
369                         } elseif (AUTH_TYPE_FORM === $auth_type) {
370                                 if (is_null($g_query_string)) {
371                                         $url_after_login = get_base_uri();
372                                 } else {
373                                         $url_after_login = get_base_uri() . '?' . $g_query_string;
374                                 }
375                                 $loginurl = get_base_uri() . '?plugin=loginform'
376                                         . '&page=' . rawurlencode($page)
377                                         . '&url_after_login=' . rawurlencode($url_after_login);
378                                 header('HTTP/1.0 302 Found');
379                                 header('Location: ' . $loginurl);
380                         } elseif (AUTH_TYPE_EXTERNAL === $auth_type ||
381                                 AUTH_TYPE_SAML === $auth_type) {
382                                 if (is_null($g_query_string)) {
383                                         $url_after_login = get_base_uri(PKWK_URI_ABSOLUTE);
384                                 } else {
385                                         $url_after_login = get_base_uri(PKWK_URI_ABSOLUTE) . '?' . $g_query_string;
386                                 }
387                                 $loginurl = get_auth_external_login_url($page, $url_after_login);
388                                 header('HTTP/1.0 302 Found');
389                                 header('Location: ' . $loginurl);
390                         }
391                 }
392                 if ($exit_on_fail) {
393                         $body = $title = str_replace('$1',
394                                 htmlsc(strip_bracket($page)), $title_cannot);
395                         $page = str_replace('$1', make_search($page), $title_cannot);
396                         catbody($title, $page, $body);
397                         exit;
398                 }
399                 return FALSE;
400         }
401 }
402
403 /**
404  * Send 401 if client send a invalid credentials
405  *
406  * @return true if valid, false if invalid credentials
407  */
408 function ensure_valid_auth_user()
409 {
410         global $auth_type, $auth_users, $_msg_auth, $auth_user, $auth_groups;
411         global $auth_user_groups, $auth_user_fullname;
412         global $ldap_user_account;
413         global $read_auth, $edit_auth;
414         if ($read_auth || $edit_auth) {
415                 switch ($auth_type) {
416                         case AUTH_TYPE_BASIC:
417                         case AUTH_TYPE_FORM:
418                         case AUTH_TYPE_EXTERNAL:
419                         case AUTH_TYPE_EXTERNAL_REMOTE_USER:
420                         case AUTH_TYPE_EXTERNAL_X_FORWARDED_USER:
421                         case AUTH_TYPE_SAML:
422                                 break;
423                         default:
424                                 // $auth_type is not valid, Set form auth as default
425                                 $auth_type = AUTH_TYPE_FORM;
426                 }
427         }
428         $auth_dynamic_groups = null;
429         switch ($auth_type) {
430                 case AUTH_TYPE_BASIC:
431                 {
432                         if (isset($_SERVER['PHP_AUTH_USER'])) {
433                                 $user = $_SERVER['PHP_AUTH_USER'];
434                                 if (in_array($user, array_keys($auth_users))) {
435                                         if (pkwk_hash_compute(
436                                                 $_SERVER['PHP_AUTH_PW'],
437                                                 $auth_users[$user]) === $auth_users[$user]) {
438                                                 $auth_user = $user;
439                                                 $auth_user_fullname = $auth_user;
440                                                 $auth_user_groups = get_groups_from_username($user);
441                                                 return true;
442                                         }
443                                 }
444                                 header('WWW-Authenticate: Basic realm="' . $_msg_auth . '"');
445                                 header('HTTP/1.0 401 Unauthorized');
446                         }
447                         $auth_user = '';
448                         $auth_user_groups = array();
449                         return true; // no auth input
450                 }
451                 case AUTH_TYPE_FORM:
452                 case AUTH_TYPE_EXTERNAL:
453                 case AUTH_TYPE_SAML:
454                 {
455                         session_start();
456                         $user = '';
457                         $fullname = '';
458                         $dynamic_groups = array();
459                         if (isset($_SESSION['authenticated_user'])) {
460                                 $user = $_SESSION['authenticated_user'];
461                                 if (isset($_SESSION['authenticated_user_fullname'])) {
462                                         $fullname = $_SESSION['authenticated_user_fullname'];
463                                         $dynamic_groups = $_SESSION['dynamic_member_groups'];
464                                 } else {
465                                         $fullname = $user;
466                                         if (($auth_type === AUTH_TYPE_EXTERNAL || $auth_type === AUTH_TYPE_SAML) &&
467                                                 $ldap_user_account) {
468                                                 $ldap_user_info = ldap_get_simple_user_info($user);
469                                                 if ($ldap_user_info) {
470                                                         $fullname = $ldap_user_info['fullname'];
471                                                         $dynamic_groups = $ldap_user_info['dynamic_member_groups'];
472                                                 }
473                                         }
474                                         $_SESSION['authenticated_user_fullname'] = $fullname;
475                                         $_SESSION['dynamic_member_groups'] = $dynamic_groups;
476                                 }
477                         }
478                         $auth_user = $user;
479                         $auth_user_fullname = $fullname;
480                         $auth_dynamic_groups = $dynamic_groups;
481                         break;
482                 }
483                 case AUTH_TYPE_EXTERNAL_REMOTE_USER:
484                         $auth_user = $_SERVER['REMOTE_USER'];
485                         $auth_user_fullname = $auth_user;
486                         break;
487                 case AUTH_TYPE_EXTERNAL_X_FORWARDED_USER:
488                         $auth_user =  $_SERVER['HTTP_X_FORWARDED_USER'];
489                         $auth_user_fullname = $auth_user;
490                         break;
491                 default: // AUTH_TYPE_NONE
492                         $auth_user = '';
493                         $auth_user_fullname = '';
494                         break;
495         }
496         $auth_user_groups = get_groups_from_username($auth_user);
497         if ($auth_dynamic_groups && is_array($auth_dynamic_groups)) {
498                 $auth_user_groups = array_values(array_merge($auth_user_groups, $auth_dynamic_groups));
499         }
500         return true; // is not basic auth
501 }
502
503 /**
504  * Return group name array whose group contains the user
505  *
506  * Result array contains reserved 'valid-user' group for all authenticated user
507  * @global array $auth_groups
508  * @param string $user
509  * @return array
510  */
511 function get_groups_from_username($user)
512 {
513         global $auth_groups;
514         if ($user !== '') {
515                 $groups = array();
516                 foreach ($auth_groups as $group=>$users) {
517                         $sp = explode(',', $users);
518                         if (in_array($user, $sp)) {
519                                 $groups[] = $group;
520                         }
521                 }
522                 // Implicit group that has same name as user itself
523                 $groups[] = $user;
524                 // 'valid-user' group for
525                 $valid_user = 'valid-user';
526                 if (!in_array($valid_user, $groups)) {
527                         $groups[] = $valid_user;
528                 }
529                 return $groups;
530         }
531         return array();
532 }
533
534 /**
535  * Get authenticated user name.
536  *
537  * @global type $auth_user
538  * @return type
539  */
540 function get_auth_user()
541 {
542         global $auth_user;
543         return $auth_user;
544 }
545
546 /**
547  * Sign in with username and password
548  *
549  * @param String username
550  * @param String password
551  * @return true is sign in is OK
552  */
553 function form_auth($username, $password)
554 {
555         global $ldap_user_account, $auth_users;
556         $user = $username;
557         if ($ldap_user_account) {
558                 // LDAP account
559                 return ldap_auth($username, $password);
560         } else {
561                 // Defined users in pukiwiki.ini.php
562                 if (in_array($user, array_keys($auth_users))) {
563                         if (pkwk_hash_compute(
564                                 $password,
565                                 $auth_users[$user]) === $auth_users[$user]) {
566                                 session_start();
567                                 session_regenerate_id(true); // require: PHP5.1+
568                                 $_SESSION['authenticated_user'] = $user;
569                                 $_SESSION['authenticated_user_fullname'] = $user;
570                                 return true;
571                         }
572                 }
573         }
574         return false;
575 }
576
577 function ldap_auth($username, $password)
578 {
579         global $ldap_server, $ldap_base_dn, $ldap_bind_dn, $ldap_bind_password;
580         $ldapconn = ldap_connect($ldap_server);
581         if ($ldapconn) {
582                 ldap_set_option($ldapconn, LDAP_OPT_PROTOCOL_VERSION, 3);
583                 ldap_set_option($ldapconn, LDAP_OPT_REFERRALS, 0);
584                 if (preg_match('#\$login\b#', $ldap_bind_dn)) {
585                         // Bind by user credential
586                         $username_esc = pkwk_ldap_escape_dn($username);
587                         $bind_dn_user = preg_replace('#\$login\b#', $username_esc, $ldap_bind_dn);
588                         $ldap_bind_user = ldap_bind($ldapconn, $bind_dn_user, $password);
589                         if ($ldap_bind_user) {
590                                 $user_info = get_ldap_user_info($ldapconn, $username, $ldap_base_dn);
591                                 if ($user_info) {
592                                         $ldap_groups = get_ldap_groups_with_user($ldapconn, $username, $user_info['is_ad']);
593                                         session_regenerate_id(true); // require: PHP5.1+
594                                         $_SESSION['authenticated_user'] = $user_info['uid'];
595                                         $_SESSION['authenticated_user_fullname'] = $user_info['fullname'];
596                                         $_SESSION['dynamic_member_groups'] = $ldap_groups;
597                                         return true;
598                                 }
599                         }
600                 } else {
601                         // Bind by bind dn
602                         $ldap_bind = ldap_bind($ldapconn, $ldap_bind_dn, $ldap_bind_password);
603                         if ($ldap_bind) {
604                                 $user_info = get_ldap_user_info($ldapconn, $username, $ldap_base_dn);
605                                 if ($user_info) {
606                                         $ldap_bind_user2 = ldap_bind($ldapconn, $user_info['dn'], $password);
607                                         if ($ldap_bind_user2) {
608                                                 $ldap_groups = get_ldap_groups_with_user($ldapconn, $username, $user_info['is_ad']);
609                                                 session_regenerate_id(true); // require: PHP5.1+
610                                                 $_SESSION['authenticated_user'] = $user_info['uid'];
611                                                 $_SESSION['authenticated_user_fullname'] = $user_info['fullname'];
612                                                 $_SESSION['dynamic_member_groups'] = $ldap_groups;
613                                                 return true;
614                                         }
615                                 }
616                         }
617                 }
618         }
619         return false;
620 }
621
622 // Get LDAP user info via bind DN
623 function ldap_get_simple_user_info($username)
624 {
625         global $ldap_server, $ldap_base_dn, $ldap_bind_dn, $ldap_bind_password;
626         $ldapconn = ldap_connect($ldap_server);
627         if ($ldapconn) {
628                 ldap_set_option($ldapconn, LDAP_OPT_PROTOCOL_VERSION, 3);
629                 ldap_set_option($ldapconn, LDAP_OPT_REFERRALS, 0);
630                 // Bind by bind dn
631                 $ldap_bind = ldap_bind($ldapconn, $ldap_bind_dn, $ldap_bind_password);
632                 if ($ldap_bind) {
633                         $user_info = get_ldap_user_info($ldapconn, $username, $ldap_base_dn);
634                         if ($user_info) {
635                                 $ldap_groups = get_ldap_groups_with_user($ldapconn,
636                                         $username, $user_info['is_ad']);
637                                 $user_info['dynamic_member_groups'] = $ldap_groups;
638                                 return $user_info;
639                         }
640                 }
641         }
642         return false;
643 }
644
645 /**
646  * Search user and get 'dn', 'uid', 'fullname' and 'mail'
647  * @param type $ldapconn
648  * @param type $username
649  * @param type $base_dn
650  * @return boolean
651  */
652 function get_ldap_user_info($ldapconn, $username, $base_dn) {
653         $username_esc = pkwk_ldap_escape_filter($username);
654         $filter = "(|(uid=$username_esc)(sAMAccountName=$username_esc))";
655         $result1 = ldap_search($ldapconn, $base_dn, $filter, array('dn', 'uid', 'cn', 'samaccountname', 'displayname', 'mail'));
656         $entries = ldap_get_entries($ldapconn, $result1);
657         if (!isset($entries[0])) {
658                 return false;
659         }
660         $info = $entries[0];
661         if (isset($info['dn'])) {
662                 $user_dn = $info['dn'];
663                 $cano_username = $username;
664                 $is_active_directory = false;
665                 if (isset($info['uid'][0])) {
666                         $cano_username = $info['uid'][0];
667                 } elseif (isset($info['samaccountname'][0])) {
668                         $cano_username = $info['samaccountname'][0];
669                         $is_active_directory = true;
670                 }
671                 $cano_fullname = $username;
672                 if (isset($info['displayname'][0])) {
673                         $cano_fullname = $info['displayname'][0];
674                 } elseif (isset($info['cn'][0])) {
675                         $cano_fullname = $info['cn'][0];
676                 }
677                 return array(
678                         'dn' => $user_dn,
679                         'uid' => $cano_username,
680                         'fullname' => $cano_fullname,
681                         'mail' => $info['mail'][0],
682                         'is_ad' => $is_active_directory,
683                 );
684         }
685         return false;
686 }
687
688 /**
689  * Redirect after login. Need to assing location or page
690  *
691  * @param type $location
692  * @param type $page
693  */
694 function form_auth_redirect($location, $page)
695 {
696         header('HTTP/1.0 302 Found');
697         if ($location) {
698                 header('Location: ' . $location);
699         } else {
700                 $url = get_page_uri($page, PKWK_URI_ROOT);
701                 header('Location: ' . $url);
702         }
703 }
704
705 /**
706  * Get External Auth log-in URL
707  */
708 function get_auth_external_login_url($page, $url_after_login) {
709         global $auth_external_login_url_base;
710         $sep = '&';
711         if (strpos($auth_external_login_url_base, '?') === FALSE) {
712                 $sep = '?';
713         }
714         $url = $auth_external_login_url_base . $sep
715                 . 'page=' . rawurlencode($page)
716                 . '&url_after_login=' . rawurlencode($url_after_login);
717         return $url;
718 }
719
720 function get_auth_user_prefix() {
721         global $ldap_user_account, $auth_type;
722         global $auth_provider_user_prefix_default;
723         global $auth_provider_user_prefix_ldap;
724         global $auth_provider_user_prefix_external;
725         global $auth_provider_user_prefix_saml;
726         $user_prefix = '';
727         switch ($auth_type) {
728                 case AUTH_TYPE_BASIC:
729                         $user_prefix = $auth_provider_user_prefix_default;
730                         break;
731                 case AUTH_TYPE_EXTERNAL:
732                 case AUTH_TYPE_EXTERNAL_REMOTE_USER:
733                 case AUTH_TYPE_EXTERNAL_X_FORWARDED_USER:
734                         $user_prefix = $auth_provider_user_prefix_external;
735                         break;
736                 case AUTH_TYPE_SAML:
737                         $user_prefix = $auth_provider_user_prefix_saml;
738                         break;
739                 case AUTH_TYPE_FORM:
740                         if ($ldap_user_account) {
741                                 $user_prefix = $auth_provider_user_prefix_ldap;
742                         } else {
743                                 $user_prefix = $auth_provider_user_prefix_default;
744                         }
745                         break;
746         }
747         return $user_prefix;
748 }
749
750 function get_ldap_related_groups() {
751         global $read_auth_pages, $edit_auth_pages;
752         global $auth_provider_user_prefix_ldap;
753         $ldap_groups = array();
754         foreach ($read_auth_pages as $pattern=>$groups) {
755                 $sp_groups = explode(',', $groups);
756                 foreach ($sp_groups as $group) {
757                         if (strpos($group, $auth_provider_user_prefix_ldap) === 0) {
758                                 $ldap_groups[] = $group;
759                         }
760                 }
761         }
762         foreach ($edit_auth_pages as $pattern=>$groups) {
763                 $sp_groups = explode(',', $groups);
764                 foreach ($sp_groups as $group) {
765                         if (strpos($group, $auth_provider_user_prefix_ldap) === 0) {
766                                 $ldap_groups[] = $group;
767                         }
768                 }
769         }
770         $ldap_groups_unique = array_values(array_unique($ldap_groups));
771         return $ldap_groups_unique;
772 }
773
774 /**
775  * Get LDAP groups user belongs to
776  *
777  * @param Resource $ldapconn
778  * @param String $user
779  * @param bool $is_ad
780  * @return Array
781  */
782 function get_ldap_groups_with_user($ldapconn, $user, $is_ad) {
783         global $auth_provider_user_prefix_ldap;
784         global $ldap_base_dn;
785         $related_groups = get_ldap_related_groups();
786         if (count($related_groups) == 0) {
787                 return array();
788         }
789         $gfilter = '(|';
790         foreach ($related_groups as $group_full) {
791                 $g = substr($group_full, strlen($auth_provider_user_prefix_ldap));
792                 $gfilter .= sprintf('(cn=%s)', pkwk_ldap_escape_filter($g));
793                 if ($is_ad) {
794                         $gfilter .= sprintf('(sAMAccountName=%s)', pkwk_ldap_escape_filter($g));
795                 }
796         }
797         $gfilter .= ')';
798         $result_g = ldap_search($ldapconn, $ldap_base_dn, $gfilter,
799                 array('dn', 'uid', 'cn', 'samaccountname'));
800         $entries = ldap_get_entries($ldapconn, $result_g);
801         if (!isset($entries[0])) {
802                 return false;
803         }
804         if (!$entries) {
805                 return array();
806         }
807         $entry_count = $entries['count'];
808         $group_list = array();
809         for ($i = 0; $i < $entry_count; $i++) {
810                 $group_name = $entries[$i]['cn'][0];
811                 if ($is_ad) {
812                         $group_name = $entries[$i]['samaccountname'][0];
813                 }
814                 $group_list[] = array(
815                         'name' => $group_name,
816                         'dn' => $entries[$i]['dn']
817                 );
818         }
819         $groups_member = array();
820         $groups_nonmember = array();
821         foreach ($group_list as $gp) {
822                 $fmt = '(&(uid=%s)(memberOf=%s))';
823                 if ($is_ad) {
824                         // LDAP_MATCHING_RULE_IN_CHAIN: Active Directory specific rule
825                         $fmt = '(&(sAMAccountName=%s)(memberOf:1.2.840.113556.1.4.1941:=%s))';
826                 }
827                 $user_gfilter = sprintf($fmt,
828                         pkwk_ldap_escape_filter($user),
829                         pkwk_ldap_escape_filter($gp['dn']));
830                 $result_e = ldap_search($ldapconn, $ldap_base_dn, $user_gfilter,
831                         array('dn'), 0, 1);
832                 $user_e = ldap_get_entries($ldapconn, $result_e);
833                 if (isset($user_e['count']) && $user_e['count'] > 0) {
834                         $groups_member[] = $gp['name'];
835                 } else {
836                         $groups_nonmember[] = $gp['name'];
837                 }
838         }
839         $groups_full = array();
840         foreach ($groups_member as $g) {
841                 $groups_full[] = $auth_provider_user_prefix_ldap . $g;
842         }
843         return $groups_full;
844 }