mirror of
https://github.com/Dolibarr/dolibarr.git
synced 2025-12-06 17:48:25 +01:00
1247 lines
37 KiB
PHP
1247 lines
37 KiB
PHP
<?php
|
|
//
|
|
// Pear DB LDAP2 - Database independent query interface definition
|
|
// for PHP's LDAP extension with protocol version 2.
|
|
//
|
|
// Copyright (C) 2002-2003 Piotr Roszatycki <dexter@debian.org>
|
|
//
|
|
// Based on ldap.php
|
|
// Copyright (C) 2002 Ludovico Magnocavallo <ludo@sumatrasolutions.com>
|
|
//
|
|
// This library is free software; you can redistribute it and/or
|
|
// modify it under the terms of the GNU Lesser General Public
|
|
// License as published by the Free Software Foundation; either
|
|
// version 2.1 of the License, or (at your option) any later version.
|
|
//
|
|
// This library is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
// Lesser General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Lesser General Public
|
|
// License along with this library; if not, write to the Free Software
|
|
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
//
|
|
// $Id$
|
|
//
|
|
|
|
// require_once 'DB/common.php';
|
|
require_once PEAR_PATH."/DB/common.php";
|
|
|
|
/**
|
|
* LDAP2 DB interface class
|
|
*
|
|
* DB_ldap2 extends DB_common to provide DB compliant
|
|
* access to LDAP servers with protocol version 2.
|
|
*
|
|
* @author Piotr Roszatycki <dexter@debian.org>
|
|
* @version $Revision$
|
|
* @package DB_ldap2
|
|
*/
|
|
|
|
class DB_ldap2 extends DB_common
|
|
{
|
|
// {{{ properties
|
|
|
|
/**
|
|
* LDAP connection handler
|
|
* @access private
|
|
*/
|
|
var $connection;
|
|
|
|
/**
|
|
* list of actions which manipulate data
|
|
* @access private
|
|
*/
|
|
var $action_manip = array(
|
|
'add', 'compare', 'delete', 'modify', 'mod_add', 'mod_del',
|
|
'mod_replace', 'rename');
|
|
|
|
/**
|
|
* list of parameters for search actions
|
|
* @access private
|
|
*/
|
|
var $param_search = array(
|
|
'action', 'base_dn', 'attributes', 'attrsonly', 'sizelimit',
|
|
'timelimit', 'deref','sort');
|
|
|
|
/**
|
|
* list of parameters for modify actions
|
|
* @access private
|
|
*/
|
|
var $param_modify = array(
|
|
'action', 'attribute', 'value', 'newrdn', 'newparent',
|
|
'deleteoldrdn');
|
|
|
|
/**
|
|
* default parameters for query
|
|
* @access private
|
|
*/
|
|
var $param = array();
|
|
|
|
/**
|
|
* parameters for last query
|
|
* @access private
|
|
*/
|
|
var $last_param = array();
|
|
|
|
/**
|
|
* array contained row counters for last query
|
|
* @access private
|
|
*/
|
|
var $row = array();
|
|
|
|
/**
|
|
* array contained number of rows for last query
|
|
* @access private
|
|
*/
|
|
var $num_rows = array();
|
|
|
|
/**
|
|
* array contained entry handlers for last query
|
|
* @access private
|
|
*/
|
|
var $entry = array();
|
|
|
|
/**
|
|
* array contained number of rows affected by last query
|
|
* @access private
|
|
*/
|
|
var $affected = 0;
|
|
|
|
// }}}
|
|
// {{{ constructor
|
|
|
|
/**
|
|
* Constructor, calls DB_common constructor
|
|
*
|
|
* @see DB_common::DB_common()
|
|
*/
|
|
function DB_ldap2()
|
|
{
|
|
$this->DB_common();
|
|
$this->phptype = 'ldap2';
|
|
$this->dbsyntax = 'ldap2';
|
|
$this->features = array(
|
|
'prepare' => false,
|
|
'pconnect' => false,
|
|
'transactions' => false,
|
|
'limit' => false
|
|
);
|
|
$this->errorcode_map = array(
|
|
0x10 => DB_ERROR_NOSUCHFIELD, // LDAP_NO_SUCH_ATTRIBUTE
|
|
0x11 => DB_ERROR_NOSUCHFIELD, // LDAP_UNDEFINED_TYPE
|
|
0x12 => DB_ERROR_CONSTRAINT, // LDAP_INAPPROPRIATE_MATCHING
|
|
0x13 => DB_ERROR_CONSTRAINT, // LDAP_CONSTRAINT_VIOLATION
|
|
0x14 => DB_ERROR_ALREADY_EXISTS, // LDAP_TYPE_OR_VALUE_EXISTS
|
|
0x15 => DB_ERROR_INVALID, // LDAP_INVALID_SYNTAX
|
|
0x20 => DB_ERROR_NOSUCHTABLE, // LDAP_NO_SUCH_OBJECT
|
|
0x21 => DB_ERROR_NOSUCHTABLE, // LDAP_ALIAS_PROBLEM
|
|
0x22 => DB_ERROR_INVALID, // LDAP_INVALID_DN_SYNTAX
|
|
0x23 => DB_ERROR_INVALID, // LDAP_IS_LEAF
|
|
0x24 => DB_ERROR_INVALID, // LDAP_ALIAS_DEREF_PROBLEM
|
|
0x30 => DB_ERROR_ACCESS_VIOLATION, // LDAP_INAPPROPRIATE_AUTH
|
|
0x31 => DB_ERROR_ACCESS_VIOLATION, // LDAP_INVALID_CREDENTIALS
|
|
0x32 => DB_ERROR_ACCESS_VIOLATION, // LDAP_INSUFFICIENT_ACCESS
|
|
0x40 => DB_ERROR_MISMATCH, // LDAP_NAMING_VIOLATION
|
|
0x41 => DB_ERROR_CONSTRAINT, // LDAP_OBJECT_CLASS_VIOLATION
|
|
0x44 => DB_ERROR_ALREADY_EXISTS, // LDAP_ALREADY_EXISTS
|
|
0x51 => DB_ERROR_CONNECT_FAILED, // LDAP_SERVER_DOWN
|
|
0x57 => DB_ERROR_SYNTAX // LDAP_FILTER_ERROR
|
|
);
|
|
}
|
|
|
|
// }}}
|
|
// {{{ connect()
|
|
|
|
/**
|
|
* Connect and bind to LDAPv2 server with either anonymous
|
|
* or authenticated bind depending on dsn info
|
|
*
|
|
* The format of the supplied DSN:
|
|
*
|
|
* ldap2://binddn:bindpw@host:port/basedn
|
|
*
|
|
* I.e.:
|
|
*
|
|
* ldap2://uid=dexter,ou=People,dc=example,dc=net:secret@127.0.0.1/dc=example,dc=net
|
|
*
|
|
* @param $dsn the data source name (see DB::parseDSN for syntax)
|
|
* @param boolean $persistent kept for interface compatibility
|
|
* @return int DB_OK if successfully connected.
|
|
* A DB error code is returned on failure.
|
|
*/
|
|
function connect($dsninfo, $persistent = false)
|
|
{
|
|
if (!DB::assertExtension('ldap')) {
|
|
return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
|
|
}
|
|
|
|
$this->dsn = $dsninfo;
|
|
$type = $dsninfo['phptype'];
|
|
$user = $dsninfo['username'];
|
|
$pw = $dsninfo['password'];
|
|
$host = $dsninfo['hostspec'];
|
|
$port = empty($dsninfo['port']) ? 389 : $dsninfo['port'];
|
|
|
|
$this->param = array(
|
|
'action' => 'search',
|
|
'base_dn' => $this->base_dn = $dsninfo['database'],
|
|
'attributes' => array(),
|
|
'attrsonly' => 0,
|
|
'sizelimit' => 0,
|
|
'timelimit' => 0,
|
|
'deref' => LDAP_DEREF_NEVER,
|
|
'attribute' => '',
|
|
'value' => '',
|
|
'newrdn' => '',
|
|
'newparent' => '',
|
|
'deleteoldrdn'=>false,
|
|
'sort' => ''
|
|
);
|
|
$this->last_param = $this->param;
|
|
$this->setOption("seqname_format", "sn=%s," . $dsninfo['database']);
|
|
$this->fetchmode = DB_FETCHMODE_ASSOC;
|
|
|
|
if ($host) {
|
|
$conn = @ldap_connect($host, $port);
|
|
} else {
|
|
return $this->raiseError("unknown host $host");
|
|
}
|
|
if (!$conn) {
|
|
return $this->raiseError(DB_ERROR_CONNECT_FAILED);
|
|
}
|
|
if ($user && $pw) {
|
|
$bind = @ldap_bind($conn, $user, $pw);
|
|
} else {
|
|
$bind = @ldap_bind($conn);
|
|
}
|
|
if (!$bind) {
|
|
return $this->raiseError(DB_ERROR_CONNECT_FAILED);
|
|
}
|
|
$this->connection = $conn;
|
|
return DB_OK;
|
|
}
|
|
|
|
// }}}
|
|
// {{{ disconnect()
|
|
|
|
/**
|
|
* Unbinds from LDAP server
|
|
*
|
|
* @return int ldap_unbind() return value
|
|
*/
|
|
function disconnect()
|
|
{
|
|
$ret = @ldap_unbind($this->connection);
|
|
$this->connection = null;
|
|
return $ret;
|
|
}
|
|
|
|
// }}}
|
|
// {{{ simpleQuery()
|
|
|
|
/**
|
|
* Performs a request against the LDAP server
|
|
*
|
|
* The type of request depend on $query parameter. If $query is string,
|
|
* perform simple searching query with filter in $query parameter.
|
|
* If $query is array, the first element of array is filter string
|
|
* (for reading operations) or data array (for writing operations).
|
|
* Another elements of $query array are query parameters which overrides
|
|
* the default parameters.
|
|
*
|
|
* The following parameters can be passed for search queries:<br />
|
|
* <li />base_dn
|
|
* <li />attributes - array, the attributes that shall be returned
|
|
* <li />attrsonly
|
|
* <li />sizelimit - integer, the max number of results to be returned
|
|
* <li />timelimit - integer, the timelimit after which to stop searching
|
|
* <li />deref -
|
|
* <li/>sort - string, which tells the attribute name by which to sort
|
|
*
|
|
*
|
|
* I.e.:
|
|
* <code>
|
|
* // search queries
|
|
* // 'base_dn' is not given, so the one passed to connect() will be used
|
|
* $db->simpleQuery("uid=dexter");
|
|
*
|
|
* // base_dn is given
|
|
* // the 'attributes' key defines the attributes that shall be returned
|
|
* // 'sort' defines the sort order of the data
|
|
* $db->simpleQuery(array(
|
|
* 'uid=dexter',
|
|
* 'base_dn' => 'ou=People,dc=example,dc=net',
|
|
* 'attributes'=>array('dn','o','l'),
|
|
* 'sort'=>'o'
|
|
* ));
|
|
*
|
|
* // use this kind of query for adding data
|
|
* $db->simpleQuery(
|
|
* array(
|
|
* array(
|
|
* 'dn' => 'cn=Piotr Roszatycki,dc=example,dc=com',
|
|
* 'objectClass' => array('top', 'person'),
|
|
* 'cn' => 'Piotr Roszatycki',
|
|
* 'sn' => 'Roszatycki'),
|
|
* 'action' => 'add'
|
|
* ));
|
|
*
|
|
* @param mixed $query the ldap query
|
|
* @return int result from LDAP function for failure queries,
|
|
* DB_OK for successful queries or DB Error object if wrong syntax
|
|
*/
|
|
function simpleQuery( $query)
|
|
{
|
|
if (is_array($query)) {
|
|
$last_param = $query;
|
|
$query = (isset($query[0]) ? $query[0] : 'objectClass=*');
|
|
unset($last_param[0]);
|
|
} else {
|
|
$last_param = array();
|
|
}
|
|
$action = (isset($last_param['action']) ? $last_param['action'] : $this->param['action']);
|
|
// check if the given action is a valid modifier action, i.e. 'search'
|
|
if (!$this->isManip($action)) {
|
|
$this->last_param = $this->param;
|
|
foreach($this->param_search as $k) {
|
|
if (isset($last_param[$k])) {
|
|
$this->last_param[$k] = $last_param[$k];
|
|
}
|
|
}
|
|
extract($this->last_param);
|
|
// double escape char for filter: '(o=Przedsi\C4\99biorstwo)' => '(o=Przedsi\\C4\\99biorstwo)'
|
|
$this->last_query = $query;
|
|
$filter = str_replace('\\', '\\\\', $query);
|
|
switch ($action) {
|
|
// ldap_search, *list, *read have the same arguments
|
|
case 'search':
|
|
case 'list':
|
|
case 'read':
|
|
$ldap_action = "ldap_$action";
|
|
$result = @$ldap_action($this->connection, $base_dn, $filter, $attributes, $attrsonly, $sizelimit, $timelimit, $deref);
|
|
break;
|
|
default:
|
|
return $this->ldapRaiseError(DB_ERROR_SYNTAX);
|
|
}
|
|
if (!$result) {
|
|
return $this->ldapRaiseError();
|
|
}
|
|
$this->row[$result] = 0; // reset the row counter.
|
|
$numrows = $this->numrows($result);
|
|
if (is_object($numrows)) {
|
|
return $numrows;
|
|
}
|
|
$this->num_rows[$result] = $numrows;
|
|
$this->affected = 0;
|
|
if ($sort) {
|
|
ldap_sort($this->connection,$result,$sort);
|
|
}
|
|
return $result;
|
|
} else {
|
|
// If first argument is an array, it contains the entry with DN.
|
|
if (is_array($query)) {
|
|
$entry = $query;
|
|
$dn = $entry['dn'];
|
|
unset($entry['dn']);
|
|
} else {
|
|
$entry = array();
|
|
$dn = $query;
|
|
}
|
|
$this->last_param = $this->param;
|
|
foreach($this->param_modify as $k) {
|
|
if (isset($last_param[$k])) {
|
|
$this->last_param[$k] = $last_param[$k];
|
|
}
|
|
}
|
|
extract($this->last_param);
|
|
$this->last_query = $query;
|
|
switch ($action) {
|
|
case 'add':
|
|
case 'modify':
|
|
case 'mod_add':
|
|
case 'mod_del':
|
|
case 'mod_replace':
|
|
$ldap_action = "ldap_$action";
|
|
$result = @$ldap_action($this->connection, $dn, $entry);
|
|
break;
|
|
case 'compare':
|
|
$result = @ldap_compare($this->connection, $dn, $attribute, $value);
|
|
break;
|
|
case 'delete':
|
|
$result = @ldap_delete($this->connection, $dn);
|
|
break;
|
|
case 'rename':
|
|
$result = @ldap_rename($this->connection, $dn, $newrdn, $newparent, $deleteoldrdn);
|
|
break;
|
|
default:
|
|
return $this->ldapRaiseError(DB_ERROR_SYNTAX);
|
|
}
|
|
if (!$result) {
|
|
return $this->ldapRaiseError();
|
|
}
|
|
$this->affected = 1;
|
|
return DB_OK;
|
|
}
|
|
}
|
|
|
|
// }}}
|
|
// {{{ nextResult()
|
|
|
|
/**
|
|
* Move the internal ldap result pointer to the next available result
|
|
*
|
|
* @param a valid ldap result resource
|
|
*
|
|
* @access public
|
|
*
|
|
* @return true if a result is available otherwise return false
|
|
*/
|
|
function nextResult($result)
|
|
{
|
|
return @ldap_next_entry($result);
|
|
}
|
|
|
|
// }}}
|
|
// {{{ fetchRow()
|
|
|
|
/**
|
|
* Fetch and return a row of data (it uses fetchInto for that)
|
|
* @param $result LDAP result identifier
|
|
* @param $fetchmode format of fetched row array
|
|
* @param $rownum the absolute row number to fetch
|
|
*
|
|
* @return array a row of data, or false on error
|
|
*/
|
|
function fetchRow($result, $fetchmode = DB_FETCHMODE_DEFAULT, $rownum=null)
|
|
{
|
|
if ($fetchmode == DB_FETCHMODE_DEFAULT) {
|
|
$fetchmode = $this->fetchmode;
|
|
}
|
|
$res = $this->fetchInto($result, $arr, $fetchmode, $rownum);
|
|
if ($res !== DB_OK) {
|
|
return $res;
|
|
}
|
|
return $arr;
|
|
}
|
|
|
|
// }}}
|
|
// {{{ fetchInto()
|
|
|
|
/**
|
|
* Fetch a row and insert the data into an existing array.
|
|
*
|
|
* DB_FETCHMODE_ORDERED returns a flat array of values
|
|
* ("value", "val1", "val2").
|
|
*
|
|
* DB_FETCHMODE_ASSOC returns an array of structuralized data
|
|
* ("field_name1" => "value", "field_name2" => array("val1", "val2")).
|
|
*
|
|
* @param $result PostgreSQL result identifier
|
|
* @param $arr (reference) array where data from the row is stored
|
|
* @param $fetchmode how the array data should be indexed
|
|
* @param $rownum the row number to fetch
|
|
*
|
|
* @return int DB_OK on success, a DB error code on failure
|
|
*/
|
|
function fetchInto($result, &$arr, $fetchmode, $rownum=null)
|
|
{
|
|
if ($rownum !== null) {
|
|
// $rownum is unimplemented, yet
|
|
return null;
|
|
}
|
|
$rownum = $this->row[$result];
|
|
if ($rownum >= $this->num_rows[$result]) {
|
|
return null;
|
|
}
|
|
if ($rownum == 0) {
|
|
$entry = @ldap_first_entry($this->connection, $result);
|
|
} else {
|
|
$entry = @ldap_next_entry($this->connection, $this->entry[$result]);
|
|
}
|
|
$this->entry[$result] = $entry;
|
|
if (!$entry) {
|
|
$errno = ldap_errno($this->connection);
|
|
if (!$err) {
|
|
return null;
|
|
}
|
|
return $this->ldapRaiseError();
|
|
}
|
|
|
|
switch ($fetchmode) {
|
|
case DB_FETCHMODE_ORDERED:
|
|
$arr = array();
|
|
if (!($attr = @ldap_get_attributes($this->connection, $entry))) {
|
|
$errno = ldap_errno($this->connection);
|
|
if (!$err) {
|
|
return null;
|
|
}
|
|
return $this->ldapRaiseError();
|
|
}
|
|
if ($attr["count"] == 0) {
|
|
if (!($arr[] = @ldap_get_dn($this->connection, $entry))) {
|
|
$errno = ldap_errno($this->connection);
|
|
if (!$err) {
|
|
return null;
|
|
}
|
|
return $this->ldapRaiseError();
|
|
}
|
|
} else {
|
|
while (list($attr_name, $attr_val) = each($attr)) {
|
|
if ($attr_val["count"] == 1) {
|
|
$arr[] = $attr_val[0];
|
|
} elseif ($attr_val["count"] > 1) {
|
|
for ($i=0; $i<$attr_val["count"]; $i++) {
|
|
$arr[] = $attr_val[$i];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case DB_FETCHMODE_ASSOC:
|
|
$arr = array();
|
|
if (!($arr["dn"] = @ldap_get_dn($this->connection, $entry))) {
|
|
$errno = ldap_errno($this->connection);
|
|
if (!$err) {
|
|
return null;
|
|
}
|
|
return $this->ldapRaiseError();
|
|
}
|
|
if (!($attr = @ldap_get_attributes($this->connection, $entry))) {
|
|
$errno = ldap_errno($this->connection);
|
|
if (!$err) {
|
|
return null;
|
|
}
|
|
return $this->ldapRaiseError();
|
|
}
|
|
while (list($attr_name, $attr_val) = each($attr)) {
|
|
if ($attr_val["count"] == 1) {
|
|
$arr[strtolower($attr_name)] = $attr_val[0];
|
|
} elseif ($attr_val["count"] > 1) {
|
|
for ($i=0; $i<$attr_val["count"]; $i++) {
|
|
$arr[strtolower($attr_name)][$i] = $attr_val[$i];
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
$this->row[$result] = ++$rownum;
|
|
return DB_OK;
|
|
}
|
|
|
|
// }}}
|
|
// {{{ freeResult()
|
|
|
|
/**
|
|
* Free the internal resources associated with $result.
|
|
*
|
|
* @param $result int LDAP result identifier or DB statement identifier
|
|
*
|
|
* @return bool TRUE on success, FALSE if $result is invalid
|
|
*/
|
|
function freeResult($result)
|
|
{
|
|
if (is_resource($result)) {
|
|
return @ldap_free_result($result);
|
|
}
|
|
if (!isset($this->prepare_tokens[(int)$result])) {
|
|
return false;
|
|
}
|
|
unset($this->prepare_tokens[(int)$result]);
|
|
unset($this->prepare_types[(int)$result]);
|
|
unset($this->prepared_queries[(int)$result]);
|
|
unset($this->row[(int)$result]);
|
|
unset($this->num_rows[(int)$result]);
|
|
unset($this->entry[(int)$result]);
|
|
$this->affected = 0;
|
|
$this->last_param = $this->param;
|
|
$this->attributes = null;
|
|
$this->sorting = '';
|
|
return true;
|
|
}
|
|
|
|
// }}}
|
|
// {{{ quote()
|
|
|
|
/**
|
|
* Quote the given string so it can be safely used within string delimiters
|
|
* in a query.
|
|
*
|
|
* @param $string mixed Data to be quoted
|
|
*
|
|
* @return mixed "NULL" string, quoted string or original data
|
|
*/
|
|
function quote($str = null)
|
|
{
|
|
$str = str_replace(array('\\', '"'), array('\\\\', '\\"'), $str);
|
|
return $str;
|
|
}
|
|
|
|
// }}}
|
|
// {{{ numCols()
|
|
|
|
/**
|
|
* Get the number of columns in a result set. This function
|
|
* is used only for compatibility reasons.
|
|
*
|
|
* @param $result resource LDAP result identifier
|
|
*
|
|
* @return int DB_ERROR_NOT_CAPABLE error code
|
|
*/
|
|
function numCols($result)
|
|
{
|
|
return $this->ldapRaiseError(DB_ERROR_NOT_CAPABLE);
|
|
}
|
|
|
|
// }}}
|
|
// {{{ numRows()
|
|
|
|
/**
|
|
* Get the number of rows in a result set.
|
|
*
|
|
* @param $result resource LDAP result identifier
|
|
*
|
|
* @return int the number of rows in $result
|
|
*/
|
|
function numRows($result)
|
|
{
|
|
$rows = @ldap_count_entries($this->connection, $result);
|
|
if ($rows === null) {
|
|
return $this->ldapRaiseError();
|
|
}
|
|
return $rows;
|
|
}
|
|
|
|
// }}}
|
|
// {{{ errorNative()
|
|
|
|
/**
|
|
* Get the native error code of the last error (if any) that
|
|
* occured on the current connection.
|
|
*
|
|
* @return int native LDAP error code
|
|
*/
|
|
function errorNative()
|
|
{
|
|
return ldap_error($this->connection);
|
|
}
|
|
|
|
// }}}
|
|
// {{{ affectedRows()
|
|
|
|
/**
|
|
* Gets the number of rows affected by the last query.
|
|
* if the last query was a select, returns 0.
|
|
*
|
|
* @return int number of rows affected by the last query or DB_ERROR
|
|
*/
|
|
function affectedRows()
|
|
{
|
|
return $this->affected;
|
|
}
|
|
|
|
// }}}
|
|
// {{{ getTables()
|
|
|
|
/**
|
|
* @deprecated
|
|
*/
|
|
function getTables()
|
|
{
|
|
return $this->ldapRaiseError(DB_ERROR_NOT_CAPABLE);
|
|
}
|
|
|
|
// }}}
|
|
// {{{ getListOf()
|
|
|
|
/**
|
|
* Returns the query needed to get some backend info. This function is
|
|
* used only for compatibility reasons.
|
|
*
|
|
* @return int DB_ERROR_NOT_CAPABLE error code
|
|
*/
|
|
function getListOf($type)
|
|
{
|
|
return $this->ldapRaiseError(DB_ERROR_NOT_CAPABLE);
|
|
}
|
|
|
|
// }}}
|
|
// {{{ isManip()
|
|
|
|
/**
|
|
* Tell whether an action is a data manipulation action (add, compare,
|
|
* delete, modify, mod_add, mod_del, mod_replace, rename)
|
|
*
|
|
* @param string $action the query
|
|
*
|
|
* @return boolean whether $query is a data manipulation action
|
|
*/
|
|
function isManip($action)
|
|
{
|
|
return(in_array($action, $this->action_manip));
|
|
}
|
|
|
|
// }}}
|
|
// {{{ base()
|
|
|
|
/**
|
|
* @deprecated
|
|
*/
|
|
function base($base_dn = null)
|
|
{
|
|
$this->q_base_dn = ($base_dn !== null) ? $base_dn : null;
|
|
return true;
|
|
}
|
|
|
|
// }}}
|
|
// {{{ ldapSetBaseDN()
|
|
|
|
/**
|
|
* @deprecated
|
|
*/
|
|
function ldapSetBaseDN($base_dn = null)
|
|
{
|
|
$this->base_dn = ($base_dn !== null) ? $base_dn : $this->d_base_dn;
|
|
$this->q_base_dn = '';
|
|
return true;
|
|
}
|
|
|
|
// }}}
|
|
// {{{ ldapSetAction()
|
|
|
|
/**
|
|
* @deprecated
|
|
*/
|
|
function ldapSetAction($action = 'search')
|
|
{
|
|
$this->action = $action;
|
|
$this->q_action = '';
|
|
return true;
|
|
}
|
|
|
|
// }}}
|
|
// {{{ nextId()
|
|
|
|
/**
|
|
* Get the next value in a sequence.
|
|
*
|
|
* LDAP provides transactions for only one entry and we need to
|
|
* prevent race condition. If unique value before and after modify
|
|
* aren't equal then wait and try again.
|
|
*
|
|
* @param string $seq_name the sequence name
|
|
* @param bool $ondemand whether to create the sequence on demand
|
|
*
|
|
* @return a sequence integer, or a DB error
|
|
*/
|
|
function nextId($seq_name, $ondemand = true)
|
|
{
|
|
$seq_dn = $this->getSequenceName($seq_name);
|
|
$repeat = 0;
|
|
do {
|
|
// Get the sequence entry
|
|
$this->expectError(DB_ERROR_NOSUCHTABLE);
|
|
$data = $this->getRow(array('objectClass=*', 'action'=>'read', 'base_dn'=>$seq_dn));
|
|
$this->popExpect();
|
|
|
|
if (DB::isError($data)) {
|
|
if ($ondemand && $repeat == 0
|
|
&& $data->getCode() == DB_ERROR_NOSUCHTABLE) {
|
|
// Try to create sequence and repeat
|
|
$repeat = 1;
|
|
$data = $this->createSequence($seq_name);
|
|
if (DB::isError($data)) {
|
|
return $this->ldapRaiseError($data);
|
|
}
|
|
} else {
|
|
// Other error
|
|
return $this->ldapRaiseError($data);
|
|
}
|
|
} else {
|
|
// Increment sequence value
|
|
$data['cn']++;
|
|
// Unique identificator of transaction
|
|
$seq_unique = mt_rand();
|
|
$data['uid'] = $seq_unique;
|
|
// Modify the LDAP entry
|
|
$data = $this->simpleQuery(array($data, 'action'=>'modify'));
|
|
if (DB::isError($data)) {
|
|
return $this->ldapRaiseError($data);
|
|
}
|
|
// Get the entry and check if it contains our unique value
|
|
$data = $this->getRow(array('objectClass=*', 'action'=>'read', 'base_dn'=>$seq_dn));
|
|
if (DB::isError($data)) {
|
|
return $this->ldapRaiseError($data);
|
|
}
|
|
if ($data['uid'] != $seq_unique) {
|
|
// It is not our entry. Wait a little time and repeat
|
|
sleep(1);
|
|
$repeat = 1;
|
|
} else {
|
|
$repeat = 0;
|
|
}
|
|
}
|
|
} while ($repeat);
|
|
|
|
if (DB::isError($data)) {
|
|
return $data;
|
|
}
|
|
return $data['cn'];
|
|
}
|
|
|
|
// }}}
|
|
// {{{ createSequence()
|
|
|
|
/**
|
|
* Create the sequence
|
|
*
|
|
* The sequence entry is based on core schema with extensibleObject,
|
|
* so it should work with any LDAP server which doesn't check schema
|
|
* or supports extensibleObject object class.
|
|
*
|
|
* Format of the entry:
|
|
*
|
|
* dn: $seq_dn
|
|
* objectClass: top
|
|
* objectClass: extensibleObject
|
|
* sn: $seq_id
|
|
* cn: $seq_value
|
|
* uid: $seq_uniq
|
|
*
|
|
* @param string $seq_name the sequence name
|
|
*
|
|
* @return mixed DB_OK on success or DB error on error
|
|
*/
|
|
function createSequence($seq_name)
|
|
{
|
|
$seq_dn = $this->getSequenceName($seq_name);
|
|
|
|
// Create the sequence entry
|
|
$data = array(
|
|
'dn' => $seq_dn,
|
|
'objectclass' => array('top', 'extensibleObject'),
|
|
'sn' => $seq_name,
|
|
'cn' => 0,
|
|
'uid' => 0
|
|
);
|
|
|
|
// Add the LDAP entry
|
|
$data = $this->simpleQuery(array($data, 'action'=>'add'));
|
|
return $data;
|
|
}
|
|
|
|
// }}}
|
|
// {{{ dropSequence()
|
|
|
|
/**
|
|
* Drop a sequence
|
|
*
|
|
* @param string $seq_name the sequence name
|
|
*
|
|
* @return mixed DB_OK on success or DB error on error
|
|
*/
|
|
function dropSequence($seq_name)
|
|
{
|
|
$seq_dn = $this->getSequenceName($seq_name);
|
|
|
|
// Delete the sequence entry
|
|
$data = array(
|
|
'dn' => $seq_dn,
|
|
);
|
|
$data = $this->simpleQuery(array($data, 'action'=>'delete'));
|
|
return $data;
|
|
}
|
|
|
|
// }}}
|
|
// {{{ ldapRaiseError()
|
|
|
|
/**
|
|
* Generate error message for LDAP errors.
|
|
*
|
|
* @param int $errno error number
|
|
*
|
|
* @return mixed DB_OK on success or DB error on error
|
|
*/
|
|
function ldapRaiseError($errno = null)
|
|
{
|
|
if ($errno === null) {
|
|
$errno = $this->errorCode(ldap_errno($this->connection));
|
|
}
|
|
if ($this->last_param['action'] !== null) {
|
|
return $this->raiseError($errno, null, null,
|
|
sprintf('%s base="%s" filter="%s"',
|
|
$this->last_param['action'] ? $this->last_param['action'] : $this->param['action'],
|
|
$this->last_param['base_dn'] ? $this->last_param['base_dn'] : $this->param['base_dn'],
|
|
is_array($this->last_query) ? "" : $this->last_query
|
|
),
|
|
$errno == @ldap_error($this->connection)
|
|
);
|
|
} else {
|
|
return $this->raiseError($errno, null, null, "???",
|
|
@ldap_error($this->connection));
|
|
}
|
|
}
|
|
|
|
// }}}
|
|
// {{{ prepare()
|
|
|
|
|
|
/**
|
|
* Prepares a query for multiple execution with execute().
|
|
* This behaviour is emulated for LDAP backend.
|
|
* prepare() requires a generic query as an array with special
|
|
* characters (wildcards) as values.
|
|
*
|
|
* Types of wildcards:
|
|
* ? - a quoted scalar value, i.e. strings, integers
|
|
* & - requires a file name, the content of the file
|
|
* insert into the query (i.e. saving binary data
|
|
* in a db)
|
|
* ! - value is inserted 'as is'
|
|
*
|
|
* Example:
|
|
*
|
|
* $sth = $dbh->prepare(
|
|
* array(
|
|
* array(
|
|
* 'dn' => '?',
|
|
* 'objectClass' => '?',
|
|
* 'cn' => '?',
|
|
* 'sn' => '?',
|
|
* 'description' => '&'
|
|
* ),
|
|
* 'action' => 'add'
|
|
* );
|
|
* );
|
|
*
|
|
* $sigfile = "/home/dexter/.signature";
|
|
* $res = $dbh->execute($sth, array(
|
|
* 'cn=Piotr Roszatycki,dc=example,dc=com',
|
|
* array('top', 'person'),
|
|
* 'Piotr Roszatycki', 'Roszatycki', $sigfile
|
|
* ));
|
|
*
|
|
* @param mixed the query to prepare
|
|
*
|
|
* @return resource handle for the query
|
|
*
|
|
* @see execute
|
|
*/
|
|
function prepare($query)
|
|
{
|
|
if (!is_array($query)) {
|
|
return parent::prepare($query);
|
|
} elseif (is_array($query) && isset($query[0]) &&
|
|
!$this->isManip(isset($query['action']) ? $query['action'] : $this->param['action'])
|
|
) {
|
|
$filter = $query[0];
|
|
$tokens = split("[\&\?\!]", $filter);
|
|
$token = 0;
|
|
$types = array();
|
|
|
|
for ($i = 0; $i < strlen($filter); $i++) {
|
|
switch ($filter[$i]) {
|
|
case '?':
|
|
$types[$token++] = DB_PARAM_SCALAR;
|
|
break;
|
|
case '&':
|
|
$types[$token++] = DB_PARAM_OPAQUE;
|
|
break;
|
|
case '!':
|
|
$types[$token++] = DB_PARAM_MISC;
|
|
break;
|
|
}
|
|
}
|
|
|
|
$this->prepare_tokens[] = &$tokens;
|
|
end($this->prepare_tokens);
|
|
|
|
$k = key($this->prepare_tokens);
|
|
$this->prepare_types[$k] = $types;
|
|
$this->prepared_queries[$k] = &$query;
|
|
|
|
return $k;
|
|
} elseif(is_array($query) && isset($query[0]) && is_array($query[0])) {
|
|
$tokens = array();
|
|
$types = array();
|
|
|
|
foreach ($query[0] as $k=>$v) {
|
|
$tokens[$k] = $v;
|
|
switch ($v) {
|
|
case '?':
|
|
$types[$k] = DB_PARAM_SCALAR;
|
|
break;
|
|
case '&':
|
|
$types[$k] = DB_PARAM_OPAQUE;
|
|
break;
|
|
case '!':
|
|
$types[$k] = DB_PARAM_MISC;
|
|
break;
|
|
default:
|
|
$types[$k] = null;
|
|
}
|
|
}
|
|
|
|
$this->prepare_tokens[] = &$tokens;
|
|
end($this->prepare_tokens);
|
|
|
|
$k = key($this->prepare_tokens);
|
|
$this->prepare_types[$k] = $types;
|
|
$this->prepared_queries[$k] = &$query;
|
|
|
|
return $k;
|
|
} else {
|
|
return parent::prepare($query);
|
|
}
|
|
}
|
|
|
|
// }}}
|
|
// {{{ executeEmulateQuery()
|
|
|
|
/**
|
|
* Emulates the execute statement.
|
|
*
|
|
* @param resource $stmt query handle from prepare()
|
|
* @param array $data numeric array containing the
|
|
* data to insert into the query
|
|
*
|
|
* @return mixed an array containing the real query run when emulating
|
|
* prepare/execute. A DB error code is returned on failure.
|
|
*
|
|
* @see execute()
|
|
*/
|
|
function executeEmulateQuery($stmt, $data = false)
|
|
{
|
|
$query = &$this->prepared_queries[$stmt];
|
|
|
|
if (!is_array($query)) {
|
|
return parent::executeEmulateQuery($stmt, $data);
|
|
} elseif (is_array($query) && isset($query[0]) &&
|
|
!$this->isManip(isset($query['action']) ? $query['action'] : $this->param['action'])
|
|
) {
|
|
$p = &$this->prepare_tokens;
|
|
|
|
if (!isset($this->prepare_tokens[$stmt]) ||
|
|
!is_array($this->prepare_tokens[$stmt]) ||
|
|
!sizeof($this->prepare_tokens[$stmt]))
|
|
{
|
|
return $this->raiseError(DB_ERROR_INVALID);
|
|
}
|
|
|
|
$qq = &$this->prepare_tokens[$stmt];
|
|
$qp = sizeof($qq) - 1;
|
|
|
|
if ((!$data && $qp > 0) ||
|
|
(!is_array($data) && $qp > 1) ||
|
|
(is_array($data) && $qp > sizeof($data)))
|
|
{
|
|
$this->last_query = $this->prepared_queries[$stmt];
|
|
return $this->raiseError(DB_ERROR_NEED_MORE_DATA);
|
|
}
|
|
|
|
$realquery = $query;
|
|
$realquery[0] = $qq[0];
|
|
for ($i = 0; $i < $qp; $i++) {
|
|
$type = $this->prepare_types[$stmt][$i];
|
|
if ($type == DB_PARAM_OPAQUE) {
|
|
if (is_array($data)) {
|
|
$fp = fopen($data[$i], 'r');
|
|
} else {
|
|
$fp = fopen($data, 'r');
|
|
}
|
|
|
|
$pdata = '';
|
|
|
|
if ($fp) {
|
|
while (($buf = fread($fp, 4096)) != false) {
|
|
$pdata .= $buf;
|
|
}
|
|
}
|
|
} else {
|
|
if (is_array($data)) {
|
|
$pdata = &$data[$i];
|
|
} else {
|
|
$pdata = &$data;
|
|
}
|
|
}
|
|
|
|
$realquery[0] .= ($type != DB_PARAM_MISC) ? $this->quote($pdata) : $pdata;
|
|
$realquery[0] .= $qq[$i + 1];
|
|
}
|
|
|
|
return $realquery;
|
|
|
|
} elseif(is_array($query) && isset($query[0]) && is_array($query[0])) {
|
|
|
|
$p = &$this->prepare_tokens;
|
|
|
|
if (!isset($this->prepare_tokens[$stmt]) ||
|
|
!is_array($this->prepare_tokens[$stmt]) ||
|
|
!sizeof($this->prepare_tokens[$stmt]))
|
|
{
|
|
return $this->raiseError(DB_ERROR_INVALID);
|
|
}
|
|
|
|
$qq = &$this->prepare_tokens[$stmt];
|
|
$realquery = $query;
|
|
|
|
$i = 0;
|
|
foreach ($qq as $k=>$v) {
|
|
$type = $this->prepare_types[$stmt][$k];
|
|
|
|
if ($type !== null) {
|
|
|
|
if (!isset($data) ||
|
|
(is_array($data) && !isset($data[$i]))
|
|
) {
|
|
$this->last_query = $this->prepared_queries[$stmt];
|
|
return $this->raiseError(DB_ERROR_NEED_MORE_DATA);
|
|
}
|
|
|
|
if ($type == DB_PARAM_OPAQUE) {
|
|
if (is_array($data)) {
|
|
$fp = fopen($data[$i++], 'r');
|
|
} else {
|
|
$fp = fopen($data, 'r');
|
|
}
|
|
|
|
$pdata = '';
|
|
|
|
if ($fp) {
|
|
while (($buf = fread($fp, 4096)) != false) {
|
|
$pdata .= $buf;
|
|
}
|
|
}
|
|
} elseif ($type !== null) {
|
|
if (is_array($data)) {
|
|
$pdata = &$data[$i++];
|
|
} else {
|
|
$pdata = &$data;
|
|
}
|
|
}
|
|
|
|
$realquery[0][$k] = $pdata;
|
|
}
|
|
}
|
|
|
|
return $realquery;
|
|
|
|
} else {
|
|
return parent::executeEmulateQuery($stmt, $data);
|
|
}
|
|
}
|
|
|
|
// }}}
|
|
// {{{ ldapSetParam()
|
|
|
|
/**
|
|
* Sets the default parameters for query.
|
|
*
|
|
* @param string $param the name of parameter for search actions (action,
|
|
* base_dn, attributes, attrsonly, sizelimit, timelimit, deref) or
|
|
* modify actions (action, attribute, value, newrdn, newparent,
|
|
* deleteoldrdn).
|
|
* @param string $value the value of parameter
|
|
*
|
|
* @return mixed DB_OK on success or DB error on error
|
|
*
|
|
* @see ldapGetParam()
|
|
*/
|
|
function ldapSetParam($param, $value)
|
|
{
|
|
if (isset($this->param[$param])) {
|
|
$this->param[$param] = $value;
|
|
return DB_OK;
|
|
}
|
|
return $this->raiseError("unknown LDAP parameter $param");
|
|
}
|
|
|
|
// }}}
|
|
// {{{ ldapGetParam()
|
|
|
|
/**
|
|
* Gets the default parameters for query.
|
|
*
|
|
* @param string $param the name of parameter for search or modify
|
|
* actions.
|
|
*
|
|
* @return mixed value of parameter on success or DB error on error
|
|
*
|
|
* @see ldapSetParam()
|
|
*/
|
|
function ldapGetParam($param)
|
|
{
|
|
if (isset($this->param[$param])) {
|
|
return $this->param[$param];
|
|
}
|
|
return $this->raiseError("unknown LDAP parameter $param");
|
|
}
|
|
|
|
// }}}
|
|
// {{{ ldapSetOption()
|
|
|
|
/**
|
|
* Sets the value of the given option.
|
|
*
|
|
* @param int $option the specified option
|
|
* @param mixed $newval the value of specified option
|
|
*
|
|
* @return bool DB_OK on success or DB error on error
|
|
*
|
|
* @see ldapGetOption()
|
|
*/
|
|
function ldapSetOption($option, $newval)
|
|
{
|
|
if (@ldap_set_option($this->connection, $option, $newval)) {
|
|
return DB_OK;
|
|
}
|
|
return $this->raiseError("failed to set LDAP option");
|
|
}
|
|
|
|
// }}}
|
|
// {{{ ldapGetOption()
|
|
|
|
/**
|
|
* Gets the current value for given option.
|
|
*
|
|
* @param int $option the specified option
|
|
* @param mixed $retval (reference) the new value of specified option
|
|
*
|
|
* @return bool DB_OK on success or DB error on error
|
|
*
|
|
* @see ldapSetOption()
|
|
*/
|
|
function ldapGetOption($option, &$retval)
|
|
{
|
|
if (@ldap_get_option($this->connection, $option, $retval)) {
|
|
return DB_OK;
|
|
}
|
|
return $this->raiseError("failed to get LDAP option");
|
|
}
|
|
|
|
// }}}
|
|
// {{{ ldapExplodeDN()
|
|
|
|
/**
|
|
* Splits the DN and breaks it up into its component parts.
|
|
* Each part is known as Relative Distinguished Name, or RDN.
|
|
*
|
|
* @param string $dn the DN to split
|
|
* @param int $with_attrib 0 to get RDNs with the attributes
|
|
* or 1 to get only values.
|
|
*
|
|
* @return array an array of all those components
|
|
*/
|
|
function ldapExplodeDN($dn, $with_attrib = 0)
|
|
{
|
|
$arr = ldap_explode_dn($dn, $with_attrib ? 1 : 0);
|
|
unset($arr['count']);
|
|
return $arr;
|
|
}
|
|
|
|
}
|
|
|
|
?>
|