3 * LDAP authentication backend
5 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
6 * @author Andreas Gohr <andi@splitbrain.org>
7 * @author Chris Smith <chris@jalakaic.co.uk>
10 class auth_ldap
extends auth_basic
{
13 var $bound = 0; // 0: anonymous, 1: user, 2: superuser
20 $this->cnf
= $conf['auth']['ldap'];
22 // ldap extension is needed
23 if(!function_exists('ldap_connect')) {
24 if ($this->cnf
['debug'])
25 msg("LDAP err: PHP LDAP extension not found.",-1,__LINE__
,__FILE__
);
26 $this->success
= false;
30 if(empty($this->cnf
['groupkey'])) $this->cnf
['groupkey'] = 'cn';
32 // auth_ldap currently just handles authentication, so no
33 // capabilities are set
39 * Checks if the given user exists and the given
40 * plaintext password is correct by trying to bind
43 * @author Andreas Gohr <andi@splitbrain.org>
46 function checkPass($user,$pass){
47 // reject empty password
48 if(empty($pass)) return false;
49 if(!$this->_openLDAP()) return false;
52 if($this->cnf
['binddn'] && $this->cnf
['bindpw']){
53 // use superuser credentials
54 if(!@ldap_bind
($this->con
,$this->cnf
['binddn'],$this->cnf
['bindpw'])){
55 if($this->cnf
['debug'])
56 msg('LDAP bind as superuser: '.htmlspecialchars(ldap_error($this->con
)),0,__LINE__
,__FILE__
);
60 }else if($this->cnf
['binddn'] &&
61 $this->cnf
['usertree'] &&
62 $this->cnf
['userfilter']) {
63 // special bind string
64 $dn = $this->_makeFilter($this->cnf
['binddn'],
65 array('user'=>$user,'server'=>$this->cnf
['server']));
67 }else if(strpos($this->cnf
['usertree'], '%{user}')) {
69 $dn = $this->_makeFilter($this->cnf
['usertree'],
70 array('user'=>$user,'server'=>$this->cnf
['server']));
74 if(!@ldap_bind
($this->con
)){
75 msg("LDAP: can not bind anonymously",-1);
76 if($this->cnf
['debug'])
77 msg('LDAP anonymous bind: '.htmlspecialchars(ldap_error($this->con
)),0,__LINE__
,__FILE__
);
82 // Try to bind to with the dn if we have one.
85 if(!@ldap_bind
($this->con
,$dn,$pass)){
86 if($this->cnf
['debug']){
87 msg("LDAP: bind with $dn failed", -1,__LINE__
,__FILE__
);
88 msg('LDAP user dn bind: '.htmlspecialchars(ldap_error($this->con
)),0);
95 // See if we can find the user
96 $info = $this->getUserData($user,true);
97 if(empty($info['dn'])) {
103 // Try to bind with the dn provided
104 if(!@ldap_bind
($this->con
,$dn,$pass)){
105 if($this->cnf
['debug']){
106 msg("LDAP: bind with $dn failed", -1,__LINE__
,__FILE__
);
107 msg('LDAP user bind: '.htmlspecialchars(ldap_error($this->con
)),0);
121 * Returns info about the given user needs to contain
122 * at least these fields:
124 * name string full name of the user
125 * mail string email addres of the user
126 * grps array list of groups the user is in
128 * This LDAP specific function returns the following
131 * dn string distinguished name (DN)
132 * uid string Posix User ID
133 * inbind bool for internal use - avoid loop in binding
135 * @author Andreas Gohr <andi@splitbrain.org>
137 * @author Dan Allen <dan.j.allen@gmail.com>
138 * @author <evaldas.auryla@pheur.org>
139 * @author Stephane Chazelas <stephane.chazelas@emerson.com>
140 * @return array containing user data or false
142 function getUserData($user,$inbind=false) {
144 if(!$this->_openLDAP()) return false;
146 // force superuser bind if wanted and not bound as superuser yet
147 if($this->cnf
['binddn'] && $this->cnf
['bindpw'] && $this->bound
< 2){
148 // use superuser credentials
149 if(!@ldap_bind
($this->con
,$this->cnf
['binddn'],$this->cnf
['bindpw'])){
150 if($this->cnf
['debug'])
151 msg('LDAP bind as superuser: '.htmlspecialchars(ldap_error($this->con
)),0,__LINE__
,__FILE__
);
155 }elseif($this->bound
== 0 && !$inbind) {
156 // in some cases getUserData is called outside the authentication workflow
157 // eg. for sending email notification on subscribed pages. This data might not
158 // be accessible anonymously, so we try to rebind the current user here
159 $pass = PMA_blowfish_decrypt($_SESSION[DOKU_COOKIE
]['auth']['pass'],auth_cookiesalt());
160 $this->checkPass($_SESSION[DOKU_COOKIE
]['auth']['user'], $pass);
163 $info['user'] = $user;
164 $info['server'] = $this->cnf
['server'];
166 //get info for given user
167 $base = $this->_makeFilter($this->cnf
['usertree'], $info);
168 if(!empty($this->cnf
['userfilter'])) {
169 $filter = $this->_makeFilter($this->cnf
['userfilter'], $info);
171 $filter = "(ObjectClass=*)";
174 $sr = @ldap_search
($this->con
, $base, $filter);
175 $result = @ldap_get_entries
($this->con
, $sr);
176 if($this->cnf
['debug']){
177 msg('LDAP user search: '.htmlspecialchars(ldap_error($this->con
)),0,__LINE__
,__FILE__
);
178 msg('LDAP search at: '.htmlspecialchars($base.' '.$filter),0,__LINE__
,__FILE__
);
181 // Don't accept more or less than one response
182 if(!is_array($result) ||
$result['count'] != 1){
183 return false; //user not found
186 $user_result = $result[0];
187 ldap_free_result($sr);
190 $info['dn'] = $user_result['dn'];
191 $info['gid'] = $user_result['gidnumber'][0];
192 $info['mail'] = $user_result['mail'][0];
193 $info['name'] = $user_result['cn'][0];
194 $info['grps'] = array();
196 // overwrite if other attribs are specified.
197 if(is_array($this->cnf
['mapping'])){
198 foreach($this->cnf
['mapping'] as $localkey => $key) {
200 // use regexp to clean up user_result
201 list($key, $regexp) = each($key);
202 if($user_result[$key]) foreach($user_result[$key] as $grp){
203 if (preg_match($regexp,$grp,$match)) {
204 if($localkey == 'grps') {
205 $info[$localkey][] = $match[1];
207 $info[$localkey] = $match[1];
212 $info[$localkey] = $user_result[$key][0];
216 $user_result = array_merge($info,$user_result);
218 //get groups for given user if grouptree is given
219 if ($this->cnf
['grouptree'] && $this->cnf
['groupfilter']) {
220 $base = $this->_makeFilter($this->cnf
['grouptree'], $user_result);
221 $filter = $this->_makeFilter($this->cnf
['groupfilter'], $user_result);
222 $sr = @ldap_search
($this->con
, $base, $filter, array($this->cnf
['groupkey']));
224 msg("LDAP: Reading group memberships failed",-1);
225 if($this->cnf
['debug']){
226 msg('LDAP group search: '.htmlspecialchars(ldap_error($this->con
)),0,__LINE__
,__FILE__
);
227 msg('LDAP search at: '.htmlspecialchars($base.' '.$filter),0,__LINE__
,__FILE__
);
231 $result = ldap_get_entries($this->con
, $sr);
232 ldap_free_result($sr);
234 if(is_array($result)) foreach($result as $grp){
235 if(!empty($grp[$this->cnf
['groupkey']][0])){
236 if($this->cnf
['debug'])
237 msg('LDAP usergroup: '.htmlspecialchars($grp[$this->cnf
['groupkey']][0]),0,__LINE__
,__FILE__
);
238 $info['grps'][] = $grp[$this->cnf
['groupkey']][0];
243 // always add the default group to the list of groups
244 if(!in_array($conf['defaultgroup'],$info['grps'])){
245 $info['grps'][] = $conf['defaultgroup'];
251 * Most values in LDAP are case-insensitive
253 function isCaseSensitive(){
258 * Make LDAP filter strings.
260 * Used by auth_getUserData to make the filter
261 * strings for grouptree and groupfilter
263 * filter string ldap search filter with placeholders
264 * placeholders array array with the placeholders
266 * @author Troels Liebe Bentsen <tlb@rapanden.dk>
269 function _makeFilter($filter, $placeholders) {
270 preg_match_all("/%{([^}]+)/", $filter, $matches, PREG_PATTERN_ORDER
);
272 foreach ($matches[1] as $match) {
273 //take first element if array
274 if(is_array($placeholders[$match])) {
275 $value = $placeholders[$match][0];
277 $value = $placeholders[$match];
279 $value = $this->_filterEscape($value);
280 $filter = str_replace('%{'.$match.'}', $value, $filter);
286 * Escape a string to be used in a LDAP filter
288 * Ported from Perl's Net::LDAP::Util escape_filter_value
290 * @author Andreas Gohr
292 function _filterEscape($string){
293 return preg_replace('/([\x00-\x1F\*\(\)\\\\])/e',
294 '"\\\\\".join("",unpack("H2","$1"))',
299 * Opens a connection to the configured LDAP server and sets the wanted
300 * option on the connection
302 * @author Andreas Gohr <andi@splitbrain.org>
304 function _openLDAP(){
305 if($this->con
) return true; // connection already established
309 $port = ($this->cnf
['port']) ?
$this->cnf
['port'] : 389;
310 $this->con
= @ldap_connect
($this->cnf
['server'],$port);
312 msg("LDAP: couldn't connect to LDAP server",-1);
316 //set protocol version and dependend options
317 if($this->cnf
['version']){
318 if(!@ldap_set_option
($this->con
, LDAP_OPT_PROTOCOL_VERSION
,
319 $this->cnf
['version'])){
320 msg('Setting LDAP Protocol version '.$this->cnf
['version'].' failed',-1);
321 if($this->cnf
['debug'])
322 msg('LDAP version set: '.htmlspecialchars(ldap_error($this->con
)),0,__LINE__
,__FILE__
);
324 //use TLS (needs version 3)
325 if($this->cnf
['starttls']) {
326 if (!@ldap_start_tls
($this->con
)){
327 msg('Starting TLS failed',-1);
328 if($this->cnf
['debug'])
329 msg('LDAP TLS set: '.htmlspecialchars(ldap_error($this->con
)),0,__LINE__
,__FILE__
);
333 if(isset($this->cnf
['referrals'])) {
334 if(!@ldap_set_option
($this->con
, LDAP_OPT_REFERRALS
,
335 $this->cnf
['referrals'])){
336 msg('Setting LDAP referrals to off failed',-1);
337 if($this->cnf
['debug'])
338 msg('LDAP referal set: '.htmlspecialchars(ldap_error($this->con
)),0,__LINE__
,__FILE__
);
345 if($this->cnf
['deref']){
346 if(!@ldap_set_option
($this->con
, LDAP_OPT_DEREF
, $this->cnf
['deref'])){
347 msg('Setting LDAP Deref mode '.$this->cnf
['deref'].' failed',-1);
348 if($this->cnf
['debug'])
349 msg('LDAP deref set: '.htmlspecialchars(ldap_error($this->con
)),0,__LINE__
,__FILE__
);
357 //Setup VIM: ex: et ts=4 enc=utf-8 :