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