first commit. dokuwiki.
[h2N7SspZmY.git] / inc / auth / ldap.class.php
blobc519241357c711cd869470669525fa479d2b1721
1 <?php
2 /**
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>
8 */
10 class auth_ldap extends auth_basic {
11 var $cnf = null;
12 var $con = null;
13 var $bound = 0; // 0: anonymous, 1: user, 2: superuser
15 /**
16 * Constructor
18 function auth_ldap(){
19 global $conf;
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;
27 return;
30 if(empty($this->cnf['groupkey'])) $this->cnf['groupkey'] = 'cn';
32 // auth_ldap currently just handles authentication, so no
33 // capabilities are set
36 /**
37 * Check user+password
39 * Checks if the given user exists and the given
40 * plaintext password is correct by trying to bind
41 * to the LDAP server
43 * @author Andreas Gohr <andi@splitbrain.org>
44 * @return bool
46 function checkPass($user,$pass){
47 // reject empty password
48 if(empty($pass)) return false;
49 if(!$this->_openLDAP()) return false;
51 // indirect user bind
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__);
57 return false;
59 $this->bound = 2;
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}')) {
68 // direct user bind
69 $dn = $this->_makeFilter($this->cnf['usertree'],
70 array('user'=>$user,'server'=>$this->cnf['server']));
72 }else{
73 // Anonymous bind
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__);
78 return false;
82 // Try to bind to with the dn if we have one.
83 if(!empty($dn)) {
84 // User/Password bind
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);
90 return false;
92 $this->bound = 1;
93 return true;
94 }else{
95 // See if we can find the user
96 $info = $this->getUserData($user,true);
97 if(empty($info['dn'])) {
98 return false;
99 } else {
100 $dn = $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);
109 return false;
111 $this->bound = 1;
112 return true;
115 return false;
119 * Return user info
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
129 * addional fields:
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>
136 * @author Trouble
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) {
143 global $conf;
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__);
152 return false;
154 $this->bound = 2;
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);
170 } else {
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);
189 // general user info
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) {
199 if(is_array($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];
206 } else {
207 $info[$localkey] = $match[1];
211 } else {
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']));
223 if(!$sr){
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__);
229 return false;
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'];
247 return $info;
251 * Most values in LDAP are case-insensitive
253 function isCaseSensitive(){
254 return false;
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>
267 * @return string
269 function _makeFilter($filter, $placeholders) {
270 preg_match_all("/%{([^}]+)/", $filter, $matches, PREG_PATTERN_ORDER);
271 //replace each match
272 foreach ($matches[1] as $match) {
273 //take first element if array
274 if(is_array($placeholders[$match])) {
275 $value = $placeholders[$match][0];
276 } else {
277 $value = $placeholders[$match];
279 $value = $this->_filterEscape($value);
280 $filter = str_replace('%{'.$match.'}', $value, $filter);
282 return $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"))',
295 $string);
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
307 $this->bound = 0;
309 $port = ($this->cnf['port']) ? $this->cnf['port'] : 389;
310 $this->con = @ldap_connect($this->cnf['server'],$port);
311 if(!$this->con){
312 msg("LDAP: couldn't connect to LDAP server",-1);
313 return false;
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__);
323 }else{
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__);
332 // needs version 3
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__);
344 //set deref mode
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__);
353 return true;
357 //Setup VIM: ex: et ts=4 enc=utf-8 :