authuser.php 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. <?php if (!defined('PmWiki')) exit();
  2. /* Copyright 2005-2019 Patrick R. Michaud (pmichaud@pobox.com)
  3. This file is part of PmWiki; you can redistribute it and/or modify
  4. it under the terms of the GNU General Public License as published
  5. by the Free Software Foundation; either version 2 of the License, or
  6. (at your option) any later version. See pmwiki.php for full details.
  7. The APR compatible MD5 encryption algorithm in _crypt() below is
  8. based on code Copyright 2005 by D. Faure and the File::Passwd
  9. PEAR library module by Mike Wallner <mike@php.net>.
  10. This script enables simple authentication based on username and
  11. password combinations. At present this script can authenticate
  12. from passwords held in arrays or in .htpasswd-formatted files,
  13. but eventually it will support authentication via sources such
  14. as LDAP and Active Directory.
  15. To configure a .htpasswd-formatted file for authentication, do
  16. $AuthUser['htpasswd'] = '/path/to/.htpasswd';
  17. prior to including this script.
  18. Individual username/password combinations can also be placed
  19. directly in the $AuthUser array, such as:
  20. $AuthUser['pmichaud'] = pmcrypt('secret');
  21. To authenticate against an LDAP server, put the url for
  22. the server in $AuthUser['ldap'], as in:
  23. $AuthUser['ldap'] = 'ldap://ldap.example.com/ou=People,o=example?uid';
  24. Script maintained by Petko YOTOV www.pmwiki.org/petko
  25. */
  26. # let Site.AuthForm know that we're doing user-based authorization
  27. $EnableAuthUser = 1;
  28. if (@$_POST['authid'])
  29. AuthUserId($pagename, stripmagic(@$_POST['authid']),
  30. stripmagic(@$_POST['authpw']));
  31. else SessionAuth($pagename);
  32. function AuthUserId($pagename, $id, $pw=NULL) {
  33. global $AuthUser, $AuthUserPageFmt, $AuthUserFunctions,
  34. $AuthId, $MessagesFmt, $AuthUserPat;
  35. $auth = array();
  36. foreach((array)$AuthUser as $k=>$v) $auth[$k] = (array)$v;
  37. $authid = '';
  38. # load information from SiteAdmin.AuthUser (or page in $AuthUserPageFmt)
  39. SDV($AuthUserPageFmt, '$SiteAdminGroup.AuthUser');
  40. SDVA($AuthUserFunctions, array(
  41. 'htpasswd' => 'AuthUserHtPasswd',
  42. 'ldap' => 'AuthUserLDAP',
  43. # 'mysql' => 'AuthUserMySQL',
  44. $id => 'AuthUserConfig'));
  45. SDV($AuthUserPat, "/^\\s*([@\\w][^\\s:]*):(.*)/m");
  46. foreach ( (array)$AuthUserPageFmt as $aupn) {
  47. $pn = FmtPageName($aupn, $pagename);
  48. $apage = ReadPage($pn, READPAGE_CURRENT);
  49. if ($apage && preg_match_all($AuthUserPat,
  50. $apage['text'], $matches, PREG_SET_ORDER)) {
  51. foreach($matches as $m) {
  52. if (!preg_match_all('/\\bldaps?:\\S+|[^\\s,]+/', $m[2], $v))
  53. continue;
  54. if ($m[1][0] == '@')
  55. foreach($v[0] as $g) $auth[$g][] = $m[1];
  56. else $auth[$m[1]] = array_merge((array)@$auth[$m[1]], $v[0]);
  57. }
  58. }
  59. }
  60. if (func_num_args()==2) $authid = $id;
  61. else
  62. foreach($AuthUserFunctions as $k => $fn)
  63. if (@$auth[$k] && $fn($pagename, $id, $pw, $auth[$k], $authlist))
  64. { $authid = $id; break; }
  65. if (!$authid) { $GLOBALS['InvalidLogin'] = 1; return; }
  66. if (!isset($AuthId)) $AuthId = $authid;
  67. $authlist["id:$authid"] = 1;
  68. $authlist["id:-$authid"] = -1;
  69. foreach(preg_grep('/^@/', (array)@$auth[$authid]) as $g)
  70. $authlist[$g] = 1;
  71. foreach(preg_grep('/^@/', (array)@$auth['*']) as $g)
  72. $authlist[$g] = 1;
  73. foreach(preg_grep('/^@/', array_keys($auth)) as $g) # useless? PITS:01201
  74. if (in_array($authid, $auth[$g])) $authlist[$g] = 1;
  75. if ($auth['htgroup']) {
  76. foreach(AuthUserHtGroup($pagename, $id, $pw, $auth['htgroup']) as $g)
  77. $authlist["@$g"] = 1;
  78. }
  79. foreach(preg_grep('/^@/', (array)@$auth["-$authid"]) as $g)
  80. unset($authlist[$g]);
  81. SessionAuth($pagename, array('authid' => $authid, 'authlist' => $authlist));
  82. }
  83. function AuthUserConfig($pagename, $id, $pw, $pwlist) {
  84. foreach ((array)$pwlist as $chal)
  85. if (_crypt($pw, $chal) == $chal) return true;
  86. return false;
  87. }
  88. function AuthUserHtPasswd($pagename, $id, $pw, $pwlist) {
  89. foreach ((array)$pwlist as $f) {
  90. $fp = fopen($f, "r"); if (!$fp) continue;
  91. while ($x = fgets($fp, 1024)) {
  92. $x = rtrim($x);
  93. @list($i, $c, $r) = explode(':', $x, 3);
  94. if ($i == $id && _crypt($pw, $c) == $c) { fclose($fp); return true; }
  95. }
  96. fclose($fp);
  97. }
  98. return false;
  99. }
  100. function AuthUserHtGroup($pagename, $id, $pw, $pwlist) {
  101. $groups = array();
  102. foreach ((array)$pwlist as $f) {
  103. $fp = fopen($f, 'r'); if (!$fp) continue;
  104. while ($x = fgets($fp, 4096)) {
  105. if (preg_match('/^(\\w[^\\s:]+)\\s*:(.*)$/', trim($x), $match)) {
  106. $glist = preg_split('/[\\s,]+/', $match[2], -1, PREG_SPLIT_NO_EMPTY);
  107. if (in_array($id, $glist)) $groups[$match[1]] = 1;
  108. }
  109. }
  110. fclose($fp);
  111. }
  112. return array_keys($groups);
  113. }
  114. function AuthUserLDAP($pagename, $id, $pw, $pwlist) {
  115. global $AuthLDAPBindDN, $AuthLDAPBindPassword, $AuthLDAPReferrals;
  116. if (!$pw) return false;
  117. if (!function_exists('ldap_connect'))
  118. Abort('authuser: LDAP authentication requires PHP ldap functions','ldapfn');
  119. foreach ((array)$pwlist as $ldap) {
  120. if (!preg_match('!(ldaps?://[^/]+)/(.*)$!', $ldap, $match))
  121. continue;
  122. ## connect to the LDAP server
  123. list($z, $url, $path) = $match;
  124. $ds = ldap_connect($url);
  125. ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 3);
  126. if(isset($AuthLDAPReferrals)) # *NOT* IsEnabled
  127. ldap_set_option($ds, LDAP_OPT_REFERRALS, $AuthLDAPReferrals);
  128. ## For Active Directory, don't specify a path and we simply
  129. ## attempt to bind with the username and password directly
  130. if (!$path && @ldap_bind($ds, $id, $pw)) { ldap_close($ds); return true; }
  131. ## Otherwise, we use Apache-style urls for LDAP authentication
  132. ## Split the path into its search components
  133. list($basedn, $attr, $sub, $filter) = explode('?', $path);
  134. if (!$attr) $attr = 'uid';
  135. if (!$sub) $sub = 'one';
  136. if (!$filter) $filter = '(objectClass=*)';
  137. $binddn = @$AuthLDAPBindDN;
  138. $bindpw = @$AuthLDAPBindPassword;
  139. if (ldap_bind($ds, $binddn, $bindpw)) {
  140. ## Search for the appropriate uid
  141. $fn = ($sub == 'sub') ? 'ldap_search' : 'ldap_list';
  142. $sr = $fn($ds, $basedn, "(&$filter($attr=$id))", array($attr));
  143. $x = ldap_get_entries($ds, $sr);
  144. ## If we find a unique id, bind to it for success
  145. if ($x['count'] == 1) {
  146. $dn = $x[0]['dn'];
  147. if (@ldap_bind($ds, $dn, $pw)) { ldap_close($ds); return true; }
  148. }
  149. }
  150. ldap_close($ds);
  151. }
  152. return false;
  153. }
  154. # The _crypt function provides support for SHA1 encrypted passwords
  155. # (keyed by '{SHA}') and Apache MD5 encrypted passwords (keyed by
  156. # '$apr1$'); otherwise it just calls PHP's crypt() for the rest.
  157. # The APR MD5 encryption code was contributed by D. Faure.
  158. function _crypt($plain, $salt=null) {
  159. if (strncmp($salt, '{SHA}', 5) == 0)
  160. return '{SHA}'.base64_encode(pack('H*', sha1($plain)));
  161. if (strncmp($salt, '$apr1$', 6) == 0) {
  162. preg_match('/^\\$apr1\\$([^$]+)/', $salt, $match);
  163. $salt = $match[1];
  164. $length = strlen($plain);
  165. $context = $plain . '$apr1$' . $salt;
  166. $binary = pack('H32', md5($plain . $salt . $plain));
  167. for($i = $length; $i > 0; $i -= 16)
  168. $context .= substr($binary, 0, min(16, $i));
  169. for($i = $length; $i > 0; $i >>= 1)
  170. $context .= ($i & 1) ? chr(0) : $plain[0];
  171. $binary = pack('H32', md5($context));
  172. for($i = 0; $i < 1000; $i++) {
  173. $new = ($i & 1) ? $plain : $binary;
  174. if ($i % 3) $new .= $salt;
  175. if ($i % 7) $new .= $plain;
  176. $new .= ($i & 1) ? $binary : $plain;
  177. $binary = pack('H32', md5($new));
  178. }
  179. $q = '';
  180. for ($i = 0; $i < 5; $i++) {
  181. $k = $i + 6;
  182. $j = $i + 12;
  183. if ($j == 16) $j = 5;
  184. $q = $binary[$i].$binary[$k].$binary[$j] . $q;
  185. }
  186. $q = chr(0).chr(0).$binary[11] . $q;
  187. $q = strtr(strrev(substr(base64_encode($q), 2)),
  188. 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/',
  189. './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz');
  190. return "\$apr1\$$salt\$$q";
  191. }
  192. if (md5($plain) == $salt) return $salt;
  193. return pmcrypt($plain, $salt);
  194. }