authuser.php 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. <?php if (!defined('PmWiki')) exit();
  2. /* Copyright 2005-2006 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'] = crypt('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. */
  25. # let Site.AuthForm know that we're doing user-based authorization
  26. $EnableAuthUser = 1;
  27. $pagename = ResolvePageName($pagename);
  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;
  35. foreach((array)$AuthUser as $k=>$v) $auth[$k] = (array)$v;
  36. $authid = '';
  37. # load information from Site.AuthUser (or page in $AuthUserPageFmt)
  38. SDV($AuthUserPageFmt, '$SiteGroup.AuthUser');
  39. SDVA($AuthUserFunctions, array(
  40. 'htpasswd' => 'AuthUserHtPasswd',
  41. 'ldap' => 'AuthUserLDAP',
  42. # 'mysql' => 'AuthUserMySQL',
  43. $id => 'AuthUserConfig'));
  44. $pn = FmtPageName($AuthUserPageFmt, $pagename);
  45. $apage = ReadPage($pn, READPAGE_CURRENT);
  46. if ($apage && preg_match_all("/^\\s*([@\\w][^\\s:]*):(.*)/m",
  47. $apage['text'], $matches, PREG_SET_ORDER)) {
  48. foreach($matches as $m) {
  49. if (!preg_match_all('/\\bldaps?:\\S+|[^\\s,]+/', $m[2], $v))
  50. continue;
  51. if ($m[1]{0} == '@')
  52. foreach($v[0] as $g) $auth[$g][] = $m[1];
  53. else $auth[$m[1]] = array_merge((array)@$auth[$m[1]], $v[0]);
  54. }
  55. }
  56. if (is_null($pw)) $authid = $id;
  57. else
  58. foreach($AuthUserFunctions as $k => $fn)
  59. if ($auth[$k] && $fn($pagename, $id, $pw, $auth[$k]))
  60. { $authid = $id; break; }
  61. if (!$authid) { $GLOBALS['InvalidLogin'] = 1; return; }
  62. if (!isset($AuthId)) $AuthId = $authid;
  63. $authlist["id:$authid"] = 1;
  64. $authlist["id:-$authid"] = -1;
  65. foreach(preg_grep('/^@/', (array)@$auth[$authid]) as $g)
  66. $authlist[$g] = 1;
  67. foreach(preg_grep('/^@/', (array)@$auth['*']) as $g)
  68. $authlist[$g] = 1;
  69. foreach(preg_grep('/^@/', array_keys($auth)) as $g)
  70. if (in_array($authid, $auth[$g])) $authlist[$g] = 1;
  71. if ($auth['htgroup']) {
  72. foreach(AuthUserHtGroup($pagename, $id, $pw, $auth['htgroup']) as $g)
  73. $authlist["@$g"] = 1;
  74. }
  75. SessionAuth($pagename, array('authid' => $authid, 'authlist' => $authlist));
  76. }
  77. function AuthUserConfig($pagename, $id, $pw, $pwlist) {
  78. foreach ((array)$pwlist as $chal)
  79. if (_crypt($pw, $chal) == $chal) return true;
  80. return false;
  81. }
  82. function AuthUserHtPasswd($pagename, $id, $pw, $pwlist) {
  83. foreach ((array)$pwlist as $f) {
  84. $fp = fopen($f, "r"); if (!$fp) continue;
  85. while ($x = fgets($fp, 1024)) {
  86. $x = rtrim($x);
  87. list($i, $c, $r) = explode(':', $x, 3);
  88. if ($i == $id && _crypt($pw, $c) == $c) { fclose($fp); return true; }
  89. }
  90. fclose($fp);
  91. }
  92. return false;
  93. }
  94. function AuthUserHtGroup($pagename, $id, $pw, $pwlist) {
  95. $groups = array();
  96. foreach ((array)$pwlist as $f) {
  97. $fp = fopen($f, 'r'); if (!$fp) continue;
  98. while ($x = fgets($fp, 4096)) {
  99. if (preg_match('/^(\\w[^\\s:]+)\\s*:(.*)$/', trim($x), $match)) {
  100. $glist = preg_split('/[\\s,]+/', $match[2], -1, PREG_SPLIT_NO_EMPTY);
  101. if (in_array($id, $glist)) $groups[$match[1]] = 1;
  102. }
  103. }
  104. fclose($fp);
  105. }
  106. return array_keys($groups);
  107. }
  108. function AuthUserLDAP($pagename, $id, $pw, $pwlist) {
  109. global $AuthLDAPBindDN, $AuthLDAPBindPassword;
  110. if (!$pw) return false;
  111. if (!function_exists('ldap_connect')) return false;
  112. foreach ((array)$pwlist as $ldap) {
  113. if (!preg_match('!(ldaps?://[^/]+)/(.+)$!', $ldap, $match))
  114. continue;
  115. list($z, $url, $path) = $match;
  116. list($basedn, $attr, $sub, $filter) = explode('?', $path);
  117. if (!$attr) $attr = 'uid';
  118. if (!$sub) $sub = 'one';
  119. if (!$filter) $filter = '(objectClass=*)';
  120. $binddn = @$AuthLDAPBindDN;
  121. $bindpw = @$AuthLDAPBindPassword;
  122. $ds = ldap_connect($url);
  123. ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 3);
  124. if (ldap_bind($ds, $binddn, $bindpw)) {
  125. $fn = ($sub == 'sub') ? 'ldap_search' : 'ldap_list';
  126. $sr = $fn($ds, $basedn, "(&$filter($attr=$id))", array($attr));
  127. $x = ldap_get_entries($ds, $sr);
  128. if ($x['count'] == 1) {
  129. $dn = $x[0]['dn'];
  130. if (@ldap_bind($ds, $dn, $pw)) { ldap_close($ds); return true; }
  131. }
  132. }
  133. ldap_close($ds);
  134. }
  135. return false;
  136. }
  137. # The _crypt function provides support for SHA1 encrypted passwords
  138. # (keyed by '{SHA}') and Apache MD5 encrypted passwords (keyed by
  139. # '$apr1$'); otherwise it just calls PHP's crypt() for the rest.
  140. # The APR MD5 encryption code was contributed by D. Faure.
  141. function _crypt($plain, $salt=null) {
  142. if (strncmp($salt, '{SHA}', 5) == 0)
  143. return '{SHA}'.base64_encode(pack('H*', sha1($plain)));
  144. if (strncmp($salt, '$apr1$', 6) == 0) {
  145. preg_match('/^\\$apr1\\$([^$]+)/', $salt, $match);
  146. $salt = $match[1];
  147. $length = strlen($plain);
  148. $context = $plain . '$apr1$' . $salt;
  149. $binary = pack('H32', md5($plain . $salt . $plain));
  150. for($i = $length; $i > 0; $i -= 16)
  151. $context .= substr($binary, 0, min(16, $i));
  152. for($i = $length; $i > 0; $i >>= 1)
  153. $context .= ($i & 1) ? chr(0) : $plain{0};
  154. $binary = pack('H32', md5($context));
  155. for($i = 0; $i < 1000; $i++) {
  156. $new = ($i & 1) ? $plain : $binary;
  157. if ($i % 3) $new .= $salt;
  158. if ($i % 7) $new .= $plain;
  159. $new .= ($i & 1) ? $binary : $plain;
  160. $binary = pack('H32', md5($new));
  161. }
  162. $q = '';
  163. for ($i = 0; $i < 5; $i++) {
  164. $k = $i + 6;
  165. $j = $i + 12;
  166. if ($j == 16) $j = 5;
  167. $q = $binary{$i}.$binary{$k}.$binary{$j} . $q;
  168. }
  169. $q = chr(0).chr(0).$binary{11} . $q;
  170. $q = strtr(strrev(substr(base64_encode($q), 2)),
  171. 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/',
  172. './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz');
  173. return "\$apr1\$$salt\$$q";
  174. }
  175. if (md5($plain) == $salt) return $salt;
  176. return crypt($plain, $salt);
  177. }