1<?php
2//
3// Pear DB LDAP2 - Database independent query interface definition
4// for PHP's LDAP extension with protocol version 2.
5//
6// Copyright (C) 2002-2003 Piotr Roszatycki <dexter@debian.org>
7//
8// Based on ldap.php
9// Copyright (C) 2002 Ludovico Magnocavallo <ludo@sumatrasolutions.com>
10//
11//  This library is free software; you can redistribute it and/or
12//  modify it under the terms of the GNU Lesser General Public
13//  License as published by the Free Software Foundation; either
14//  version 2.1 of the License, or (at your option) any later version.
15//
16//  This library is distributed in the hope that it will be useful,
17//  but WITHOUT ANY WARRANTY; without even the implied warranty of
18//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
19//  Lesser General Public License for more details.
20//
21//  You should have received a copy of the GNU Lesser General Public
22//  License along with this library; if not, write to the Free Software
23//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
24//
25// $Id: ldap2.php 302688 2010-08-23 11:10:32Z clockwerx $
26//
27
28require_once 'DB/common.php';
29
30/**
31 * LDAP2 DB interface class
32 *
33 * DB_ldap2 extends DB_common to provide DB compliant
34 * access to LDAP servers with protocol version 2.
35 *
36 * @author Piotr Roszatycki <dexter@debian.org>
37 * @version $Revision: 302688 $
38 * @package DB_ldap2
39 */
40
41class DB_ldap2 extends DB_common
42{
43    // {{{ properties
44
45    /**
46     * LDAP connection handler
47     * @access private
48     */
49    var $connection;
50
51    /**
52     * list of actions which manipulate data
53     * @access private
54     */
55    var $action_manip = array(
56        'add', 'compare', 'delete', 'modify', 'mod_add', 'mod_del',
57        'mod_replace', 'rename');
58
59    /**
60     * list of parameters for search actions
61     * @access private
62     */
63    var	$param_search = array(
64        'action', 'base_dn', 'attributes', 'attrsonly', 'sizelimit',
65        'timelimit', 'deref','sort');
66
67    /**
68     * list of parameters for modify actions
69     * @access private
70     */
71    var	$param_modify = array(
72        'action', 'attribute', 'value', 'newrdn', 'newparent',
73        'deleteoldrdn');
74
75    /**
76     * default parameters for query
77     * @access private
78     */
79    var $param = array();
80
81    /**
82     * parameters for last query
83     * @access private
84     */
85    var $last_param = array();
86
87    /**
88     * array contained row counters for last query
89     * @access private
90     */
91    var $row = array();
92
93    /**
94     * array contained number of rows for last query
95     * @access private
96     */
97    var $num_rows = array();
98
99    /**
100     * array contained entry handlers for last query
101     * @access private
102     */
103    var $entry = array();
104
105    /**
106     * array contained number of rows affected by last query
107     * @access private
108     */
109    var $affected = 0;
110
111    // }}}
112    // {{{ constructor
113
114    /**
115     * Constructor, calls DB_common constructor
116     *
117     * @see DB_common::DB_common()
118     */
119    function DB_ldap2()
120    {
121        $this->DB_common();
122        $this->phptype = 'ldap2';
123        $this->dbsyntax = 'ldap2';
124        $this->features = array(
125            'prepare'       => false,
126            'pconnect'      => false,
127            'transactions'  => false,
128            'limit'         => false
129        );
130        $this->errorcode_map = array(
131            0x10 => DB_ERROR_NOSUCHFIELD,               // LDAP_NO_SUCH_ATTRIBUTE
132            0x11 => DB_ERROR_NOSUCHFIELD,               // LDAP_UNDEFINED_TYPE
133            0x12 => DB_ERROR_CONSTRAINT,                // LDAP_INAPPROPRIATE_MATCHING
134            0x13 => DB_ERROR_CONSTRAINT,                // LDAP_CONSTRAINT_VIOLATION
135            0x14 => DB_ERROR_ALREADY_EXISTS,            // LDAP_TYPE_OR_VALUE_EXISTS
136            0x15 => DB_ERROR_INVALID,                   // LDAP_INVALID_SYNTAX
137            0x20 => DB_ERROR_NOSUCHTABLE,               // LDAP_NO_SUCH_OBJECT
138            0x21 => DB_ERROR_NOSUCHTABLE,               // LDAP_ALIAS_PROBLEM
139            0x22 => DB_ERROR_INVALID,                   // LDAP_INVALID_DN_SYNTAX
140            0x23 => DB_ERROR_INVALID,                   // LDAP_IS_LEAF
141            0x24 => DB_ERROR_INVALID,                   // LDAP_ALIAS_DEREF_PROBLEM
142            0x30 => DB_ERROR_ACCESS_VIOLATION,          // LDAP_INAPPROPRIATE_AUTH
143            0x31 => DB_ERROR_ACCESS_VIOLATION,          // LDAP_INVALID_CREDENTIALS
144            0x32 => DB_ERROR_ACCESS_VIOLATION,          // LDAP_INSUFFICIENT_ACCESS
145            0x40 => DB_ERROR_MISMATCH,                  // LDAP_NAMING_VIOLATION
146            0x41 => DB_ERROR_CONSTRAINT,                // LDAP_OBJECT_CLASS_VIOLATION
147            0x44 => DB_ERROR_ALREADY_EXISTS,            // LDAP_ALREADY_EXISTS
148            0x51 => DB_ERROR_CONNECT_FAILED,            // LDAP_SERVER_DOWN
149            0x57 => DB_ERROR_SYNTAX                     // LDAP_FILTER_ERROR
150        );
151    }
152
153    // }}}
154    // {{{ connect()
155
156    /**
157     * Connect and bind to LDAPv2 server with either anonymous
158     * or authenticated bind depending on dsn info
159     *
160     * The format of the supplied DSN:
161     *
162     *  ldap2://binddn:bindpw@host:port/basedn
163     *
164     * I.e.:
165     *
166     *  ldap2://uid=dexter,ou=People,dc=example,dc=net:secret@127.0.0.1/dc=example,dc=net
167     *
168     * @param $dsn the data source name (see DB::parseDSN for syntax)
169     * @param boolean $persistent kept for interface compatibility
170     * @return int DB_OK if successfully connected.
171     * A DB error code is returned on failure.
172     */
173    function connect($dsninfo, $persistent = false)
174    {
175        if (!PEAR::loadExtension('ldap')) {
176            return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
177        }
178
179        $this->dsn = $dsninfo;
180        $type   = $dsninfo['phptype'];
181        $user   = $dsninfo['username'];
182        $pw     = $dsninfo['password'];
183        $host   = $dsninfo['hostspec'];
184        $port   = empty($dsninfo['port']) ? 389 : $dsninfo['port'];
185
186        $this->param = array(
187            'action' =>     'search',
188            'base_dn' =>    $this->base_dn = $dsninfo['database'],
189            'attributes' => array(),
190            'attrsonly' =>  0,
191            'sizelimit' =>  0,
192            'timelimit' =>  0,
193            'deref' =>      LDAP_DEREF_NEVER,
194            'attribute' =>  '',
195            'value' =>      '',
196            'newrdn' =>     '',
197            'newparent' =>  '',
198            'deleteoldrdn'=>false,
199            'sort' =>       ''
200        );
201        $this->last_param = $this->param;
202        $this->setOption("seqname_format", "sn=%s," . $dsninfo['database']);
203        $this->fetchmode = DB_FETCHMODE_ASSOC;
204
205        if ($host) {
206            $conn = @ldap_connect($host, $port);
207        } else {
208            return $this->raiseError("unknown host $host");
209        }
210        if (!$conn) {
211            return $this->raiseError(DB_ERROR_CONNECT_FAILED);
212        }
213        if ($user && $pw) {
214            $bind = @ldap_bind($conn, $user, $pw);
215        } else {
216            $bind = @ldap_bind($conn);
217        }
218        if (!$bind) {
219            return $this->raiseError(DB_ERROR_CONNECT_FAILED);
220        }
221        $this->connection = $conn;
222        return DB_OK;
223    }
224
225    // }}}
226    // {{{ disconnect()
227
228    /**
229     * Unbinds from LDAP server
230     *
231     * @return int ldap_unbind() return value
232     */
233    function disconnect()
234    {
235        $ret = @ldap_unbind($this->connection);
236        $this->connection = null;
237        return $ret;
238    }
239
240    // }}}
241    // {{{ simpleQuery()
242
243    /**
244     * Performs a request against the LDAP server
245     *
246     * The type of request depend on $query parameter.  If $query is string,
247     * perform simple searching query with filter in $query parameter.
248     * If $query is array, the first element of array is filter string
249     * (for reading operations) or data array (for writing operations).
250     * Another elements of $query array are query parameters which overrides
251     * the default parameters.
252     *
253     * The following parameters can be passed for search queries:<br />
254     * <li />base_dn
255     * <li />attributes - array, the attributes that shall be returned
256     * <li />attrsonly
257     * <li />sizelimit - integer, the max number of results to be returned
258     * <li />timelimit - integer, the timelimit after which to stop searching
259     * <li />deref -
260     * <li/>sort - string, which tells the attribute name by which to sort
261     *
262     *
263     * I.e.:
264     * <code>
265     * // search queries
266     * // 'base_dn' is not given, so the one passed to connect() will be used
267     * $db->simpleQuery("uid=dexter");
268     *
269     * // base_dn is given
270     * // the 'attributes' key defines the attributes that shall be returned
271     * // 'sort' defines the sort order of the data
272     * $db->simpleQuery(array(
273     *      'uid=dexter',
274     *      'base_dn' => 'ou=People,dc=example,dc=net',
275     *      'attributes'=>array('dn','o','l'),
276     *      'sort'=>'o'
277     * ));
278     *
279     * // use this kind of query for adding data
280     * $db->simpleQuery(
281     *      array(
282     *          array(
283     *              'dn' => 'cn=Piotr Roszatycki,dc=example,dc=com',
284     *              'objectClass' => array('top', 'person'),
285     *              'cn' => 'Piotr Roszatycki',
286     *              'sn' => 'Roszatycki'),
287     *          'action' => 'add'
288     * ));
289     *
290     * @param mixed $query the ldap query
291     * @return int result from LDAP function for failure queries,
292     * DB_OK for successful queries or DB Error object if wrong syntax
293     */
294    function simpleQuery( $query)
295    {
296        if (is_array($query)) {
297            $last_param = $query;
298            $query = (isset($query[0]) ? $query[0] : 'objectClass=*');
299            unset($last_param[0]);
300        } else {
301            $last_param = array();
302        }
303        $action = (isset($last_param['action']) ? $last_param['action'] : $this->param['action']);
304        // check if the given action is a valid modifier action, i.e. 'search'
305        if (!$this->isManip($action)) {
306            $this->last_param = $this->param;
307            foreach($this->param_search as $k) {
308                if (isset($last_param[$k])) {
309                    $this->last_param[$k] = $last_param[$k];
310                }
311            }
312            extract($this->last_param);
313            // double escape char for filter: '(o=Przedsi\C4\99biorstwo)' => '(o=Przedsi\\C4\\99biorstwo)'
314            $this->last_query = $query;
315            $filter = str_replace('\\', '\\\\', $query);
316            switch ($action) {
317                // ldap_search, *list, *read have the same arguments
318                case 'search':
319                case 'list':
320                case 'read':
321                    $ldap_action = "ldap_$action";
322                    $result = @$ldap_action($this->connection, $base_dn, $filter, $attributes, $attrsonly, $sizelimit, $timelimit, $deref);
323                    break;
324                default:
325                    return $this->ldapRaiseError(DB_ERROR_SYNTAX);
326            }
327            if (!$result) {
328                return $this->ldapRaiseError();
329            }
330            $this->row[$result] = 0; // reset the row counter.
331            $numrows = $this->numrows($result);
332            if (is_object($numrows)) {
333                return $numrows;
334            }
335            $this->num_rows[$result] = $numrows;
336            $this->affected = 0;
337            if ($sort) {
338                ldap_sort($this->connection,$result,$sort);
339            }
340            return $result;
341        } else {
342            // If first argument is an array, it contains the entry with DN.
343            if (is_array($query)) {
344                $entry = $query;
345                $dn = $entry['dn'];
346                unset($entry['dn']);
347            } else {
348                $entry = array();
349                $dn = $query;
350            }
351            $this->last_param = $this->param;
352            foreach($this->param_modify as $k) {
353                if (isset($last_param[$k])) {
354                    $this->last_param[$k] = $last_param[$k];
355                }
356            }
357            extract($this->last_param);
358            $this->last_query = $query;
359            switch ($action) {
360                case 'add':
361                case 'modify':
362                case 'mod_add':
363                case 'mod_del':
364                case 'mod_replace':
365                    $ldap_action = "ldap_$action";
366                    $result = @$ldap_action($this->connection, $dn, $entry);
367                    break;
368                case 'compare':
369                    $result = @ldap_compare($this->connection, $dn, $attribute, $value);
370                    break;
371                case 'delete':
372                    $result = @ldap_delete($this->connection, $dn);
373                    break;
374                case 'rename':
375                    $result = @ldap_rename($this->connection, $dn, $newrdn, $newparent, $deleteoldrdn);
376                    break;
377                default:
378                    return $this->ldapRaiseError(DB_ERROR_SYNTAX);
379            }
380            if (!$result) {
381                return $this->ldapRaiseError();
382            }
383            $this->affected = 1;
384            return DB_OK;
385        }
386    }
387
388    // }}}
389    // {{{ nextResult()
390
391    /**
392     * Move the internal ldap result pointer to the next available result
393     *
394     * @param a valid ldap result resource
395     *
396     * @access public
397     *
398     * @return true if a result is available otherwise return false
399     */
400    function nextResult($result)
401    {
402        return @ldap_next_entry($result);
403    }
404
405    // }}}
406    // {{{ fetchRow()
407
408    /**
409     * Fetch and return a row of data (it uses fetchInto for that)
410     * @param $result LDAP result identifier
411     * @param   $fetchmode  format of fetched row array
412     * @param   $rownum     the absolute row number to fetch
413     *
414     * @return  array   a row of data, or false on error
415     */
416    function fetchRow($result, $fetchmode = DB_FETCHMODE_DEFAULT, $rownum=null)
417    {
418        if ($fetchmode == DB_FETCHMODE_DEFAULT) {
419            $fetchmode = $this->fetchmode;
420        }
421        $res = $this->fetchInto($result, $arr, $fetchmode, $rownum);
422        if ($res !== DB_OK) {
423            return $res;
424        }
425        return $arr;
426    }
427
428    // }}}
429    // {{{ fetchInto()
430
431    /**
432     * Fetch a row and insert the data into an existing array.
433     *
434     * DB_FETCHMODE_ORDERED returns a flat array of values
435     * ("value", "val1", "val2").
436     *
437     * DB_FETCHMODE_ASSOC returns an array of structuralized data
438     * ("field_name1" => "value", "field_name2" => array("val1", "val2")).
439     *
440     * @param $result PostgreSQL result identifier
441     * @param $arr (reference) array where data from the row is stored
442     * @param $fetchmode how the array data should be indexed
443     * @param $rownum the row number to fetch
444     *
445     * @return int DB_OK on success, a DB error code on failure
446     */
447    function fetchInto($result, &$arr, $fetchmode, $rownum=null)
448    {
449	if ($rownum !== null) {
450	    // $rownum is unimplemented, yet
451	    return null;
452	}
453        $rownum = $this->row[$result];
454        if ($rownum >= $this->num_rows[$result]) {
455            return null;
456        }
457	if ($rownum == 0) {
458	    $entry = @ldap_first_entry($this->connection, $result);
459	} else {
460	    $entry = @ldap_next_entry($this->connection, $this->entry[$result]);
461	}
462	$this->entry[$result] = $entry;
463        if (!$entry) {
464            $errno = ldap_errno($this->connection);
465            if (!$err) {
466                return null;
467            }
468            return $this->ldapRaiseError();
469        }
470
471	switch ($fetchmode) {
472	case DB_FETCHMODE_ORDERED:
473    	    $arr = array();
474	    if (!($attr = @ldap_get_attributes($this->connection, $entry))) {
475        	$errno = ldap_errno($this->connection);
476        	if (!$err) {
477            	    return null;
478        	}
479        	return $this->ldapRaiseError();
480    	    }
481	    if ($attr["count"] == 0) {
482		if (!($arr[] = @ldap_get_dn($this->connection, $entry))) {
483        	    $errno = ldap_errno($this->connection);
484        	    if (!$err) {
485            		return null;
486        	    }
487        	    return $this->ldapRaiseError();
488		}
489    	    } else {
490		while (list($attr_name, $attr_val) = each($attr)) {
491	    	    if ($attr_val["count"] == 1) {
492	    	        $arr[] = $attr_val[0];
493		    } elseif ($attr_val["count"] > 1) {
494			for ($i=0; $i<$attr_val["count"]; $i++) {
495		    	    $arr[] = $attr_val[$i];
496			}
497		    }
498		}
499	    }
500	    break;
501	case DB_FETCHMODE_ASSOC:
502    	    $arr = array();
503	    if (!($arr["dn"] = @ldap_get_dn($this->connection, $entry))) {
504        	$errno = ldap_errno($this->connection);
505        	if (!$err) {
506            	    return null;
507        	}
508        	return $this->ldapRaiseError();
509    	    }
510	    if (!($attr = @ldap_get_attributes($this->connection, $entry))) {
511        	$errno = ldap_errno($this->connection);
512        	if (!$err) {
513            	    return null;
514        	}
515        	return $this->ldapRaiseError();
516    	    }
517	    while (list($attr_name, $attr_val) = each($attr)) {
518	        if ($attr_val["count"] == 1) {
519	    	    $arr[strtolower($attr_name)] = $attr_val[0];
520		} elseif ($attr_val["count"] > 1) {
521		    for ($i=0; $i<$attr_val["count"]; $i++) {
522		        $arr[strtolower($attr_name)][$i] = $attr_val[$i];
523		    }
524		}
525	    }
526	    break;
527	}
528
529        $this->row[$result] = ++$rownum;
530        return DB_OK;
531    }
532
533    // }}}
534    // {{{ freeResult()
535
536    /**
537     * Free the internal resources associated with $result.
538     *
539     * @param $result int LDAP result identifier or DB statement identifier
540     *
541     * @return bool TRUE on success, FALSE if $result is invalid
542     */
543    function freeResult($result)
544    {
545        if (is_resource($result)) {
546            return @ldap_free_result($result);
547        }
548        if (!isset($this->prepare_tokens[(int)$result])) {
549            return false;
550        }
551        unset($this->prepare_tokens[(int)$result]);
552        unset($this->prepare_types[(int)$result]);
553        unset($this->prepared_queries[(int)$result]);
554        unset($this->row[(int)$result]);
555        unset($this->num_rows[(int)$result]);
556        unset($this->entry[(int)$result]);
557        $this->affected = 0;
558        $this->last_param = $this->param;
559        $this->attributes = null;
560        $this->sorting = '';
561        return true;
562    }
563
564    // }}}
565    // {{{ quote()
566
567    /**
568    * Quote the given string so it can be safely used within string delimiters
569    * in a query.
570    *
571    * @param $string mixed Data to be quoted
572    *
573    * @return mixed "NULL" string, quoted string or original data
574    */
575    function quote($str = null)
576    {
577        $str = str_replace(array('\\', '"'), array('\\\\', '\\"'), $str);
578        return $str;
579    }
580
581    // }}}
582    // {{{ numCols()
583
584    /**
585     * Get the number of columns in a result set. This function
586     * is used only for compatibility reasons.
587     *
588     * @param $result resource LDAP result identifier
589     *
590     * @return int DB_ERROR_NOT_CAPABLE error code
591     */
592    function numCols($result)
593    {
594        return $this->ldapRaiseError(DB_ERROR_NOT_CAPABLE);
595    }
596
597    // }}}
598    // {{{ numRows()
599
600    /**
601     * Get the number of rows in a result set.
602     *
603     * @param $result resource LDAP result identifier
604     *
605     * @return int the number of rows in $result
606     */
607    function numRows($result)
608    {
609        $rows = @ldap_count_entries($this->connection, $result);
610        if ($rows === null) {
611            return $this->ldapRaiseError();
612        }
613        return $rows;
614    }
615
616    // }}}
617    // {{{ errorNative()
618
619    /**
620     * Get the native error code of the last error (if any) that
621     * occured on the current connection.
622     *
623     * @return int native LDAP error code
624     */
625    function errorNative()
626    {
627        return ldap_error($this->connection);
628    }
629
630    // }}}
631    // {{{ affectedRows()
632
633    /**
634     * Gets the number of rows affected by the last query.
635     * if the last query was a select, returns 0.
636     *
637     * @return int number of rows affected by the last query or DB_ERROR
638     */
639    function affectedRows()
640    {
641        return $this->affected;
642    }
643
644    // }}}
645    // {{{ getTables()
646
647    /**
648     * @deprecated
649     */
650    function getTables()
651    {
652        return $this->ldapRaiseError(DB_ERROR_NOT_CAPABLE);
653    }
654
655    // }}}
656    // {{{ getListOf()
657
658    /**
659     * Returns the query needed to get some backend info. This function is
660     * used only for compatibility reasons.
661     *
662     * @return int DB_ERROR_NOT_CAPABLE error code
663     */
664    function getListOf($type)
665    {
666        return $this->ldapRaiseError(DB_ERROR_NOT_CAPABLE);
667    }
668
669    // }}}
670    // {{{ isManip()
671
672    /**
673     * Tell whether an action is a data manipulation action (add, compare,
674     * delete, modify, mod_add, mod_del, mod_replace, rename)
675     *
676     * @param string $action the query
677     *
678     * @return boolean whether $query is a data manipulation action
679     */
680    function isManip($action)
681    {
682        return(in_array($action, $this->action_manip));
683    }
684
685    // }}}
686    // {{{ base()
687
688    /**
689     * @deprecated
690     */
691    function base($base_dn = null)
692    {
693        $this->q_base_dn = ($base_dn !== null) ? $base_dn : null;
694        return true;
695    }
696
697    // }}}
698    // {{{ ldapSetBaseDN()
699
700    /**
701     * @deprecated
702     */
703    function ldapSetBaseDN($base_dn = null)
704    {
705        $this->base_dn = ($base_dn !== null) ? $base_dn : $this->d_base_dn;
706        $this->q_base_dn = '';
707        return true;
708    }
709
710    // }}}
711    // {{{ ldapSetAction()
712
713    /**
714     * @deprecated
715     */
716    function ldapSetAction($action = 'search')
717    {
718        $this->action = $action;
719        $this->q_action = '';
720        return true;
721    }
722
723    // }}}
724    // {{{ nextId()
725
726    /**
727     * Get the next value in a sequence.
728     *
729     * LDAP provides transactions for only one entry and we need to
730     * prevent race condition. If unique value before and after modify
731     * aren't equal then wait and try again.
732     *
733     * @param string $seq_name the sequence name
734     * @param bool $ondemand whether to create the sequence on demand
735     *
736     * @return a sequence integer, or a DB error
737     */
738    function nextId($seq_name, $ondemand = true)
739    {
740	$seq_dn = $this->getSequenceName($seq_name);
741        $repeat = 0;
742        do {
743            // Get the sequence entry
744	    $this->expectError(DB_ERROR_NOSUCHTABLE);
745            $data = $this->getRow(array('objectClass=*', 'action'=>'read', 'base_dn'=>$seq_dn));
746	    $this->popExpect();
747
748            if (DB::isError($data)) {
749                if ($ondemand && $repeat == 0
750                && $data->getCode() == DB_ERROR_NOSUCHTABLE) {
751                // Try to create sequence and repeat
752                    $repeat = 1;
753                    $data = $this->createSequence($seq_name);
754                    if (DB::isError($data)) {
755                        return $this->ldapRaiseError($data);
756                    }
757                } else {
758                    // Other error
759                    return $this->ldapRaiseError($data);
760                }
761            } else {
762                // Increment sequence value
763                $data['cn']++;
764                // Unique identificator of transaction
765                $seq_unique = mt_rand();
766                $data['uid'] = $seq_unique;
767                // Modify the LDAP entry
768                $data = $this->simpleQuery(array($data, 'action'=>'modify'));
769                if (DB::isError($data)) {
770                    return $this->ldapRaiseError($data);
771                }
772                // Get the entry and check if it contains our unique value
773        	$data = $this->getRow(array('objectClass=*', 'action'=>'read', 'base_dn'=>$seq_dn));
774                if (DB::isError($data)) {
775                    return $this->ldapRaiseError($data);
776                }
777                if ($data['uid'] != $seq_unique) {
778                    // It is not our entry. Wait a little time and repeat
779                    sleep(1);
780                    $repeat = 1;
781                } else {
782                    $repeat = 0;
783                }
784            }
785        } while ($repeat);
786
787        if (DB::isError($data)) {
788            return $data;
789        }
790        return $data['cn'];
791    }
792
793    // }}}
794    // {{{ createSequence()
795
796    /**
797     * Create the sequence
798     *
799     * The sequence entry is based on core schema with extensibleObject,
800     * so it should work with any LDAP server which doesn't check schema
801     * or supports extensibleObject object class.
802     *
803     * Format of the entry:
804     *
805     *  dn: $seq_dn
806     *  objectClass: top
807     *  objectClass: extensibleObject
808     *  sn: $seq_id
809     *  cn: $seq_value
810     *  uid: $seq_uniq
811     *
812     * @param string $seq_name the sequence name
813     *
814     * @return mixed DB_OK on success or DB error on error
815     */
816    function createSequence($seq_name)
817    {
818	$seq_dn = $this->getSequenceName($seq_name);
819
820        // Create the sequence entry
821        $data = array(
822            'dn' => $seq_dn,
823            'objectclass' => array('top', 'extensibleObject'),
824            'sn' => $seq_name,
825            'cn' => 0,
826            'uid' => 0
827        );
828
829        // Add the LDAP entry
830        $data = $this->simpleQuery(array($data, 'action'=>'add'));
831        return $data;
832    }
833
834    // }}}
835    // {{{ dropSequence()
836
837    /**
838     * Drop a sequence
839     *
840     * @param string $seq_name the sequence name
841     *
842     * @return mixed DB_OK on success or DB error on error
843     */
844    function dropSequence($seq_name)
845    {
846        $seq_dn = $this->getSequenceName($seq_name);
847
848        // Delete the sequence entry
849        $data = array(
850            'dn' => $seq_dn,
851        );
852        $data = $this->simpleQuery(array($data, 'action'=>'delete'));
853        return $data;
854    }
855
856    // }}}
857    // {{{ ldapRaiseError()
858
859    /**
860     * Generate error message for LDAP errors.
861     *
862     * @param int $errno error number
863     *
864     * @return mixed DB_OK on success or DB error on error
865     */
866    function ldapRaiseError($errno = null)
867    {
868        if ($errno === null) {
869            $errno = $this->errorCode(ldap_errno($this->connection));
870        }
871        if ($this->last_param['action'] !== null) {
872            return $this->raiseError($errno, null, null,
873                        sprintf('%s base="%s" filter="%s"',
874                            $this->last_param['action'] ? $this->last_param['action'] : $this->param['action'],
875                            $this->last_param['base_dn'] ? $this->last_param['base_dn'] : $this->param['base_dn'],
876                            is_array($this->last_query) ? "" : $this->last_query
877                        ),
878                        $errno == @ldap_error($this->connection)
879                    );
880        } else {
881            return $this->raiseError($errno, null, null, "???",
882                        @ldap_error($this->connection));
883        }
884    }
885
886    // }}}
887    // {{{ prepare()
888
889
890    /**
891     * Prepares a query for multiple execution with execute().
892     * This behaviour is emulated for LDAP backend.
893     * prepare() requires a generic query as an array with special
894     * characters (wildcards) as values.
895     *
896     * Types of wildcards:
897     *   ? - a quoted scalar value, i.e. strings, integers
898     *   & - requires a file name, the content of the file
899     *       insert into the query (i.e. saving binary data
900     *       in a db)
901     *   ! - value is inserted 'as is'
902     *
903     * Example:
904     *
905     *  $sth = $dbh->prepare(
906     *      array(
907     *  	       array(
908     *  	           'dn' => '?',
909     *  	           'objectClass' => '?',
910     *  	           'cn' => '?',
911     *  	           'sn' => '?',
912     *  	           'description' => '&'
913     *  	       ),
914     *          'action' => 'add'
915     *      );
916     *  );
917     *
918     *  $sigfile = "/home/dexter/.signature";
919     *  $res = $dbh->execute($sth, array(
920     *      'cn=Piotr Roszatycki,dc=example,dc=com',
921     *      array('top', 'person'),
922     *      'Piotr Roszatycki', 'Roszatycki', $sigfile
923     *  ));
924     *
925     * @param mixed the query to prepare
926     *
927     * @return resource handle for the query
928     *
929     * @see execute
930     */
931    function prepare($query)
932    {
933	if (!is_array($query)) {
934	    return parent::prepare($query);
935	} elseif (is_array($query) && isset($query[0]) &&
936	    !$this->isManip(isset($query['action']) ? $query['action'] : $this->param['action'])
937	) {
938	    $filter = $query[0];
939            $tokens = preg_split("/[\&\?\!]/", $filter);
940            $token = 0;
941            $types = array();
942
943            for ($i = 0; $i < strlen($filter); $i++) {
944                switch ($filter[$i]) {
945                    case '?':
946                        $types[$token++] = DB_PARAM_SCALAR;
947                        break;
948                    case '&':
949                        $types[$token++] = DB_PARAM_OPAQUE;
950                        break;
951                    case '!':
952                        $types[$token++] = DB_PARAM_MISC;
953                        break;
954                }
955            }
956
957            $this->prepare_tokens[] = &$tokens;
958            end($this->prepare_tokens);
959
960            $k = key($this->prepare_tokens);
961            $this->prepare_types[$k] = $types;
962            $this->prepared_queries[$k] = &$query;
963
964            return $k;
965        } elseif(is_array($query) && isset($query[0]) && is_array($query[0])) {
966            $tokens = array();
967            $types = array();
968
969	    foreach ($query[0] as $k=>$v) {
970		$tokens[$k] = $v;
971                switch ($v) {
972                    case '?':
973                        $types[$k] = DB_PARAM_SCALAR;
974                        break;
975                    case '&':
976                        $types[$k] = DB_PARAM_OPAQUE;
977                        break;
978                    case '!':
979                        $types[$k] = DB_PARAM_MISC;
980                        break;
981		    default:
982			$types[$k] = null;
983                }
984	    }
985
986            $this->prepare_tokens[] = &$tokens;
987            end($this->prepare_tokens);
988
989            $k = key($this->prepare_tokens);
990            $this->prepare_types[$k] = $types;
991            $this->prepared_queries[$k] = &$query;
992
993            return $k;
994	} else {
995	    return parent::prepare($query);
996	}
997    }
998
999    // }}}
1000    // {{{ executeEmulateQuery()
1001
1002    /**
1003     * Emulates the execute statement.
1004     *
1005     * @param resource $stmt query handle from prepare()
1006     * @param array    $data numeric array containing the
1007     *                       data to insert into the query
1008     *
1009     * @return mixed an array containing the real query run when emulating
1010     * prepare/execute.  A DB error code is returned on failure.
1011     *
1012     * @see execute()
1013     */
1014    function executeEmulateQuery($stmt, $data = false)
1015    {
1016	$query = &$this->prepared_queries[$stmt];
1017
1018	if (!is_array($query)) {
1019	    return parent::executeEmulateQuery($stmt, $data);
1020	} elseif (is_array($query) && isset($query[0]) &&
1021	    !$this->isManip(isset($query['action']) ? $query['action'] : $this->param['action'])
1022	) {
1023	    $p = &$this->prepare_tokens;
1024
1025    	    if (!isset($this->prepare_tokens[$stmt]) ||
1026        	!is_array($this->prepare_tokens[$stmt]) ||
1027        	!sizeof($this->prepare_tokens[$stmt]))
1028    	    {
1029        	return $this->raiseError(DB_ERROR_INVALID);
1030    	    }
1031
1032            $qq = &$this->prepare_tokens[$stmt];
1033            $qp = sizeof($qq) - 1;
1034
1035            if ((!$data && $qp > 0) ||
1036                (!is_array($data) && $qp > 1) ||
1037                (is_array($data) && $qp > sizeof($data)))
1038            {
1039                $this->last_query = $this->prepared_queries[$stmt];
1040                return $this->raiseError(DB_ERROR_NEED_MORE_DATA);
1041            }
1042
1043	    $realquery = $query;
1044            $realquery[0] = $qq[0];
1045            for ($i = 0; $i < $qp; $i++) {
1046                $type = $this->prepare_types[$stmt][$i];
1047                if ($type == DB_PARAM_OPAQUE) {
1048                    if (is_array($data)) {
1049                        $fp = fopen($data[$i], 'r');
1050                    } else {
1051                        $fp = fopen($data, 'r');
1052                    }
1053
1054                    $pdata = '';
1055
1056                    if ($fp) {
1057                        while (($buf = fread($fp, 4096)) != false) {
1058                            $pdata .= $buf;
1059                        }
1060                    }
1061                } else {
1062                    if (is_array($data)) {
1063                        $pdata = &$data[$i];
1064                    } else {
1065                        $pdata = &$data;
1066                    }
1067                }
1068
1069                $realquery[0] .= ($type != DB_PARAM_MISC) ? $this->quote($pdata) : $pdata;
1070                $realquery[0] .= $qq[$i + 1];
1071            }
1072
1073            return $realquery;
1074
1075        } elseif(is_array($query) && isset($query[0]) && is_array($query[0])) {
1076
1077	    $p = &$this->prepare_tokens;
1078
1079    	    if (!isset($this->prepare_tokens[$stmt]) ||
1080        	!is_array($this->prepare_tokens[$stmt]) ||
1081        	!sizeof($this->prepare_tokens[$stmt]))
1082    	    {
1083        	return $this->raiseError(DB_ERROR_INVALID);
1084    	    }
1085
1086            $qq = &$this->prepare_tokens[$stmt];
1087	    $realquery = $query;
1088
1089	    $i = 0;
1090	    foreach ($qq as $k=>$v) {
1091                $type = $this->prepare_types[$stmt][$k];
1092
1093                if ($type !== null) {
1094
1095        	    if (!isset($data) ||
1096            		(is_array($data) && !isset($data[$i]))
1097		    ) {
1098            		$this->last_query = $this->prepared_queries[$stmt];
1099            		return $this->raiseError(DB_ERROR_NEED_MORE_DATA);
1100        	    }
1101
1102        	    if ($type == DB_PARAM_OPAQUE) {
1103                	if (is_array($data)) {
1104                    	    $fp = fopen($data[$i++], 'r');
1105                	} else {
1106                    	    $fp = fopen($data, 'r');
1107                	}
1108
1109                	$pdata = '';
1110
1111                	if ($fp) {
1112                    	    while (($buf = fread($fp, 4096)) != false) {
1113                        	$pdata .= $buf;
1114                    	    }
1115                	}
1116            	    } elseif ($type !== null) {
1117                	if (is_array($data)) {
1118                    	    $pdata = &$data[$i++];
1119                	} else {
1120                    	    $pdata = &$data;
1121                	}
1122            	    }
1123
1124		    $realquery[0][$k] = $pdata;
1125		}
1126	    }
1127
1128            return $realquery;
1129
1130	} else {
1131	    return parent::executeEmulateQuery($stmt, $data);
1132	}
1133    }
1134
1135    // }}}
1136    // {{{ ldapSetParam()
1137
1138    /**
1139     * Sets the default parameters for query.
1140     *
1141     * @param string $param the name of parameter for search actions (action,
1142     * base_dn, attributes, attrsonly, sizelimit, timelimit, deref) or
1143     * modify actions (action, attribute, value, newrdn, newparent,
1144     * deleteoldrdn).
1145     * @param string $value the value of parameter
1146     *
1147     * @return mixed DB_OK on success or DB error on error
1148     *
1149     * @see ldapGetParam()
1150     */
1151    function ldapSetParam($param, $value)
1152    {
1153        if (isset($this->param[$param])) {
1154            $this->param[$param] = $value;
1155            return DB_OK;
1156        }
1157        return $this->raiseError("unknown LDAP parameter $param");
1158    }
1159
1160    // }}}
1161    // {{{ ldapGetParam()
1162
1163    /**
1164     * Gets the default parameters for query.
1165     *
1166     * @param string $param the name of parameter for search or modify
1167     * actions.
1168     *
1169     * @return mixed value of parameter on success or DB error on error
1170     *
1171     * @see ldapSetParam()
1172     */
1173    function ldapGetParam($param)
1174    {
1175        if (isset($this->param[$param])) {
1176            return $this->param[$param];
1177        }
1178        return $this->raiseError("unknown LDAP parameter $param");
1179    }
1180
1181    // }}}
1182    // {{{ ldapSetOption()
1183
1184    /**
1185     * Sets the value of the given option.
1186     *
1187     * @param int $option the specified option
1188     * @param mixed $newval the value of specified option
1189     *
1190     * @return bool DB_OK on success or DB error on error
1191     *
1192     * @see ldapGetOption()
1193     */
1194    function ldapSetOption($option, $newval)
1195    {
1196        if (@ldap_set_option($this->connection, $option, $newval)) {
1197            return DB_OK;
1198        }
1199        return $this->raiseError("failed to set LDAP option");
1200    }
1201
1202    // }}}
1203    // {{{ ldapGetOption()
1204
1205    /**
1206     * Gets the current value for given option.
1207     *
1208     * @param int $option the specified option
1209     * @param mixed $retval (reference) the new value of specified option
1210     *
1211     * @return bool DB_OK on success or DB error on error
1212     *
1213     * @see ldapSetOption()
1214     */
1215    function ldapGetOption($option, &$retval)
1216    {
1217        if (@ldap_get_option($this->connection, $option, $retval)) {
1218            return DB_OK;
1219        }
1220        return $this->raiseError("failed to get LDAP option");
1221    }
1222
1223    // }}}
1224    // {{{ ldapExplodeDN()
1225
1226    /**
1227     * Splits the DN and breaks it up into its component parts.
1228     * Each part is known as Relative Distinguished Name, or RDN.
1229     *
1230     * @param string $dn the DN to split
1231     * @param int $with_attrib 0 to get RDNs with the attributes
1232     * or 1 to get only values.
1233     *
1234     * @return array an array of all those components
1235     */
1236    function ldapExplodeDN($dn, $with_attrib = 0)
1237    {
1238        $arr = ldap_explode_dn($dn, $with_attrib ? 1 : 0);
1239        unset($arr['count']);
1240        return $arr;
1241    }
1242
1243}
1244
1245?>
1246