-
Matthew Newton authored2943977f
<?php
/*
This code is part of FusionDirectory (http://www.fusiondirectory.org/)
Copyright (C) 2003-2010 Cajus Pollmeier
Copyright (C) 2003 Alejandro Escanero Blanco <aescanero@chaosdimension.org>
Copyright (C) 1998 Eric Kilfoil <eric@ipass.net>
Copyright (C) 2011-2016 FusionDirectory
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/*!
* \file class_ldap.inc
* Source code for Class LDAP
*/
/*!
* \brief This class contains all ldap function needed to make
* ldap operations easy
*/
class LDAP
{
var $hascon = FALSE;
var $reconnect = FALSE;
var $tls = FALSE;
/**
* Connection identifier
*
* @var resource|object|false
*/
var $cid = FALSE;
var $hasres = [];
var $sr = [];
var $re = [];
var $basedn = "";
/* 0 if we are fetching the first entry, otherwise 1 */
var $start = [];
/* Any error messages to be returned can be put here */
var $error = "";
var $srp = 0;
/* Information read from slapd.oc.conf */
var $objectClasses = [];
/* the dn for the bind */
var $binddn = "";
/* the dn's password for the bind */
var $bindpw = "";
var $hostname = "";
var $follow_referral = FALSE;
var $referrals = [];
/* 0, empty or negative values will disable this check */
7172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
var $max_ldap_query_time = 0;
/*!
* \brief Create a LDAP connection
*
* \param string $binddn Bind of the DN
*
* \param string $bindpw Bind
*
* \param string $hostname The hostname
*
* \param boolean $follow_referral FALSE
*
* \param boolean $tls FALSE
*/
function __construct ($binddn, $bindpw, $hostname, $follow_referral = FALSE, $tls = FALSE)
{
global $config;
$this->follow_referral = $follow_referral;
$this->tls = $tls;
$this->binddn = $binddn;
$this->bindpw = $bindpw;
$this->hostname = $hostname;
/* Check if MAX_LDAP_QUERY_TIME is defined */
if (is_object($config) && ($config->get_cfg_value("ldapMaxQueryTime") != "")) {
$str = $config->get_cfg_value("ldapMaxQueryTime");
$this->max_ldap_query_time = (float)($str);
}
$this->connect();
}
/*! \brief Remove bogus resources after unserialize
*/
public function __wakeup ()
{
$this->cid = FALSE;
$this->hascon = FALSE;
}
/*!
* \brief Initialize a LDAP connection
*
* Initializes a LDAP connection.
*
* \param string $server The server we are connecting to
*
* \param string $base The base of our ldap tree
*
* \param string $binddn Default: empty
*
* \param string $pass Default: empty
*
* \return LDAP object
*/
public static function init (string $server, string $base, string $binddn = '', string $pass = ''): LDAP
{
global $config;
$ldap = new LDAP($binddn, $pass, $server,
isset($config->current['LDAPFOLLOWREFERRALS']) && $config->current['LDAPFOLLOWREFERRALS'] == 'TRUE',
isset($config->current['LDAPTLS']) && $config->current['LDAPTLS'] == 'TRUE');
/* Sadly we've no proper return values here. Use the error message instead. */
if (!$ldap->success()) {
throw new FatalError(htmlescape(sprintf(_('FATAL: Error when connecting to LDAP. Server said "%s".'), $ldap->get_error())));
}
/* Preset connection base to $base and return to caller */
141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
$ldap->cd($base);
return $ldap;
}
/*!
* \brief Get the search ressource
*
* \return increase srp
*/
function getSearchResource ()
{
$this->sr[$this->srp] = NULL;
$this->start[$this->srp] = 0;
$this->hasres[$this->srp] = FALSE;
return $this->srp++;
}
/*!
* \brief Function to fix problematic characters in DN's that are used for search requests. I.e. member=....
*
* \param string $dn The DN
*/
static function prepare4filter ($dn)
{
trigger_error('deprecated, use ldap_escape_f instead');
return ldap_escape_f($dn);
}
/*!
* \brief Error text that must be returned for invalid user or password
*
* This is useful to make sure the same error text is shown whether a user exists or not, when the password is not correct.
*/
static function invalidCredentialsError (): string
{
return _(ldap_err2str(49));
}
/*!
* \brief Create a connection to LDAP server
*
* The string $error containts result of the connection
*/
function connect ()
{
$this->hascon = FALSE;
$this->reconnect = FALSE;
if ($this->cid = @ldap_connect($this->hostname)) {
@ldap_set_option($this->cid, LDAP_OPT_PROTOCOL_VERSION, 3);
if ($this->follow_referral) {
@ldap_set_option($this->cid, LDAP_OPT_REFERRALS, 1);
@ldap_set_rebind_proc($this->cid, [&$this, 'rebind']);
}
if ($this->tls) {
@ldap_start_tls($this->cid);
}
$this->error = 'No Error';
$serverctrls = [];
if (class_available('ppolicyAccount')) {
$serverctrls = [['oid' => LDAP_CONTROL_PASSWORDPOLICYREQUEST]];
}
$result = @ldap_bind_ext($this->cid, $this->binddn, $this->bindpw, $serverctrls);
if (@ldap_parse_result($this->cid, $result, $errcode, $matcheddn, $errmsg, $referrals, $ctrls)) {
if (isset($ctrls[LDAP_CONTROL_PASSWORDPOLICYRESPONSE]['value']['error'])) {
$this->hascon = FALSE;
switch ($ctrls[LDAP_CONTROL_PASSWORDPOLICYRESPONSE]['value']['error']) {
case 0:
/* passwordExpired - password has expired and must be reset */
$this->error = _('It seems your user password has expired. Please use <a href="recovery.php">password recovery</a> to change it.');
211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
break;
case 1:
/* accountLocked */
$this->error = _('Account locked. Please contact your system administrator!');
break;
case 2:
/* changeAfterReset - password must be changed before the user will be allowed to perform any other operation */
$this->error = 'changeAfterReset';
break;
case 3:
/* passwordModNotAllowed */
case 4:
/* mustSupplyOldPassword */
case 5:
/* insufficientPasswordQuality */
case 6:
/* passwordTooShort */
case 7:
/* passwordTooYoung */
case 8:
/* passwordInHistory */
default:
$this->error = sprintf(_('Unexpected ppolicy error "%s", please contact the administrator'), $ctrls[LDAP_CONTROL_PASSWORDPOLICYRESPONSE]['value']['error']);
break;
}
// Note: Also available: expire, grace
} else {
$this->hascon = ($errcode == 0);
if ($errcode == 49) {
$this->error = static::invalidCredentialsError();
} elseif (empty($errmsg)) {
$this->error = ldap_err2str($errcode);
} else {
$this->error = $errmsg;
}
}
} else {
$this->error = 'Parsing of LDAP result from bind failed';
$this->hascon = FALSE;
}
} else {
$this->error = 'Could not connect to LDAP server';
}
logging::debug(DEBUG_LDAP, __LINE__, __FUNCTION__, __FILE__, $this->error, 'connect');
}
/*!
* \brief Rebind
*/
function rebind ($ldap, $referral)
{
$credentials = $this->get_credentials($referral);
if (@ldap_bind($ldap, $credentials['ADMINDN'], $credentials['ADMINPASSWORD'])) {
$this->error = "Success";
$this->hascon = TRUE;
$this->reconnect = TRUE;
logging::debug(DEBUG_LDAP, __LINE__, __FUNCTION__, __FILE__, $this->error, 'rebind');
return 0;
} else {
$this->error = "Could not bind to " . $credentials['ADMINDN'];
logging::debug(DEBUG_LDAP, __LINE__, __FUNCTION__, __FILE__, $this->error, 'rebind');
return NULL;
}
}
/*!
* \brief Reconnect to LDAP server
*/
function reconnect ()
281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
{
if ($this->reconnect) {
$this->unbind();
}
}
/*!
* \brief Unbind to LDAP server
*/
function unbind ()
{
@ldap_unbind($this->cid);
$this->cid = FALSE;
logging::debug(DEBUG_LDAP, __LINE__, __FUNCTION__, __FILE__, '', 'unbind');
}
/*!
* \brief Disconnect to LDAP server
*/
function disconnect ()
{
if ($this->hascon) {
@ldap_close($this->cid);
$this->hascon = FALSE;
$this->cid = FALSE;
}
logging::debug(DEBUG_LDAP, __LINE__, __FUNCTION__, __FILE__, '', 'disconnect');
}
/*!
* \brief Change directory
*
* \param string $dir The new directory
*/
function cd ($dir)
{
if ($dir == '..') {
$this->basedn = $this->getParentDir();
} else {
$this->basedn = $dir;
}
}
/*!
* \brief Accessor of the parent directory of the basedn
*
* \param string $basedn The basedn which we want the parent directory
*
* \return String, the parent directory
*/
function getParentDir ($basedn = '')
{
if ($basedn == '') {
$basedn = $this->basedn;
}
return preg_replace("/[^,]*[,]*[ ]*(.*)/", "$1", $basedn);
}
/*!
* \brief Search about filter
*
* \param integer $srp srp
*
* \param string $filter The filter
*
* \param array $attrs
*
* \param string $scope Scope of the search: subtree/base/one
*/
function search ($srp, $filter, $attrs = [], $scope = 'subtree', array $controls = NULL)
351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
{
if ($this->hascon) {
if ($this->reconnect) {
$this->connect();
}
$startTime = microtime(TRUE);
$this->clearResult($srp);
switch (strtolower($scope)) {
case 'base':
if (isset($controls)) {
$this->sr[$srp] = @ldap_read($this->cid, $this->basedn, $filter, $attrs, 0, 0, 0, LDAP_DEREF_NEVER, $controls);
} else {
$this->sr[$srp] = @ldap_read($this->cid, $this->basedn, $filter, $attrs);
}
break;
case 'one':
if (isset($controls)) {
$this->sr[$srp] = @ldap_list($this->cid, $this->basedn, $filter, $attrs, 0, 0, 0, LDAP_DEREF_NEVER, $controls);
} else {
$this->sr[$srp] = @ldap_list($this->cid, $this->basedn, $filter, $attrs);
}
break;
case 'subtree':
default:
if (isset($controls)) {
$this->sr[$srp] = @ldap_search($this->cid, $this->basedn, $filter, $attrs, 0, 0, 0, LDAP_DEREF_NEVER, $controls);
} else {
$this->sr[$srp] = @ldap_search($this->cid, $this->basedn, $filter, $attrs);
}
break;
}
$this->error = @ldap_error($this->cid);
$this->resetResult($srp);
$this->hasres[$srp] = TRUE;
/* Check if query took longer as specified in max_ldap_query_time */
$diff = microtime(TRUE) - $startTime;
if ($this->max_ldap_query_time && ($diff > $this->max_ldap_query_time)) {
$warning = new FusionDirectoryWarning(htmlescape(sprintf(_('LDAP performance is poor: last query took about %.2fs!'), $diff)));
$warning->display();
}
$this->log("LDAP operation: time=".$diff." operation=search('".$this->basedn."', '$filter')");
logging::debug(DEBUG_LDAP, __LINE__, __FUNCTION__, __FILE__, $this->error, 'search(base="'.$this->basedn.'",scope="'.$scope.'",filter="'.$filter.'")');
return $this->sr[$srp];
} else {
$this->error = "Could not connect to LDAP server";
logging::debug(DEBUG_LDAP, __LINE__, __FUNCTION__, __FILE__, $this->error, 'search(base="'.$this->basedn.'",scope="'.$scope.'",filter="'.$filter.'")');
return "";
}
}
/*!
* \brief Parse last result
*
* \param integer $srp srp
*
*/
function parse_result ($srp): array
{
if ($this->hascon && $this->hasres[$srp]) {
if (ldap_parse_result($this->cid, $this->sr[$srp], $errcode, $matcheddn, $errmsg, $referrals, $controls)) {
return [$errcode, $matcheddn, $errmsg, $referrals, $controls];
}
throw new FusionDirectoryException(_('Parsing LDAP result failed'));
} else {
throw new FusionDirectoryException(_('No LDAP result to parse'));
}
}
421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
/*
* \brief List
*
* \param integer $srp
*
* \param string $filter Initialized at "(objectclass=*)"
*
* \param string $basedn Empty string
*
* \param array $attrs
*/
function ls ($srp, $filter = "(objectclass=*)", $basedn = "", $attrs = ["*"])
{
trigger_error('deprecated');
$this->cd($basedn);
return $this->search($srp, $filter, $attrs, 'one');
}
/*
* \brief Concatenate
*
* \param integer $srp
*
* \param string $dn The DN
*
* \param array $attrs
*
* \param string $filter Initialized at "(objectclass=*)"
*/
function cat ($srp, $dn, $attrs = ["*"], $filter = "(objectclass=*)")
{
if ($this->hascon) {
if ($this->reconnect) {
$this->connect();
}
$this->clearResult($srp);
$this->sr[$srp] = @ldap_read($this->cid, $dn, $filter, $attrs);
$this->error = @ldap_error($this->cid);
$this->resetResult($srp);
$this->hasres[$srp] = TRUE;
logging::debug(DEBUG_LDAP, __LINE__, __FUNCTION__, __FILE__, $this->error, 'cat(dn="'.$dn.'",filter="'.$filter.'")');
return $this->sr[$srp];
} else {
$this->error = "Could not connect to LDAP server";
logging::debug(DEBUG_LDAP, __LINE__, __FUNCTION__, __FILE__, $this->error, 'cat(dn="'.$dn.'",filter="'.$filter.'")');
return "";
}
}
/*!
* \brief Search object from a filter
*
* \param string $dn The DN
*
* \param string $filter The filter of the research
*/
function object_match_filter ($dn, $filter)
{
if ($this->hascon) {
if ($this->reconnect) {
$this->connect();
}
$res = @ldap_read($this->cid, $dn, $filter, ["objectClass"]);
logging::debug(DEBUG_LDAP, __LINE__, __FUNCTION__, __FILE__, $this->error, 'object_match_filter(dn="'.$dn.'",filter="'.$filter.'")');
return @ldap_count_entries($this->cid, $res);
} else {
$this->error = "Could not connect to LDAP server";
logging::debug(DEBUG_LDAP, __LINE__, __FUNCTION__, __FILE__, $this->error, 'object_match_filter(dn="'.$dn.'",filter="'.$filter.'")');
491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560
return FALSE;
}
}
/*!
* \brief Set a size limit
*
* \param $size The limit
*/
function set_size_limit ($size)
{
/* Ignore zero settings */
if ($size == 0) {
@ldap_set_option($this->cid, LDAP_OPT_SIZELIMIT, 10000000);
}
if ($this->hascon) {
@ldap_set_option($this->cid, LDAP_OPT_SIZELIMIT, $size);
} else {
$this->error = "Could not connect to LDAP server";
}
logging::debug(DEBUG_LDAP, __LINE__, __FUNCTION__, __FILE__, $size, 'set_size_limit');
}
/*!
* \brief Fetch
*
* \param integer $srp
* \param bool $cleanUpNumericIndices whether to remove numeric indices and "count" index at top level ("count" index in each attribute value is kept)
*/
function fetch ($srp, bool $cleanUpNumericIndices = FALSE)
{
if ($this->hascon) {
if ($this->hasres[$srp]) {
if ($this->start[$srp] == 0) {
if ($this->sr[$srp]) {
$this->start[$srp] = 1;
$this->re[$srp] = @ldap_first_entry($this->cid, $this->sr[$srp]);
} else {
return [];
}
} else {
$this->re[$srp] = @ldap_next_entry($this->cid, $this->re[$srp]);
}
$att = [];
if ($this->re[$srp]) {
$att = @ldap_get_attributes($this->cid, $this->re[$srp]);
$att['dn'] = trim(@ldap_get_dn($this->cid, $this->re[$srp]));
if ($cleanUpNumericIndices && isset($att['count'])) {
for ($i = 0; $i < $att['count']; ++$i) {
/* Remove numeric keys */
unset($att[$i]);
}
unset($att['count']);
}
}
$this->error = @ldap_error($this->cid);
logging::debug(DEBUG_LDAP, __LINE__, __FUNCTION__, __FILE__, $this->error, 'fetch()');
return $att;
} else {
$this->error = "Perform a fetch with no search";
logging::debug(DEBUG_LDAP, __LINE__, __FUNCTION__, __FILE__, $this->error, 'fetch()');
return "";
}
} else {
$this->error = "Could not connect to LDAP server";
logging::debug(DEBUG_LDAP, __LINE__, __FUNCTION__, __FILE__, $this->error, 'fetch()');
return "";
}
}
561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630
/*!
* \brief Reset the result
*
* \param integer $srp Value to be reset
*/
function resetResult ($srp)
{
$this->start[$srp] = 0;
}
/*!
* \brief Clear a result
*
* \param integer $srp The result to clear
*/
function clearResult ($srp)
{
if ($this->hasres[$srp]) {
$this->hasres[$srp] = FALSE;
@ldap_free_result($this->sr[$srp]);
}
}
/*!
* \brief Accessor of the DN
*
* \param $srp srp
*/
function getDN ($srp)
{
if ($this->hascon) {
if ($this->hasres[$srp]) {
if (!$this->re[$srp]) {
$this->error = "Perform a Fetch with no valid Result";
} else {
$rv = @ldap_get_dn($this->cid, $this->re[$srp]);
$this->error = @ldap_error($this->cid);
return trim($rv);
}
} else {
$this->error = "Perform a Fetch with no Search";
return "";
}
} else {
$this->error = "Could not connect to LDAP server";
return "";
}
}
/*!
* \brief Return the numbers of entries
*
* \param $srp srp
*/
function count ($srp)
{
if ($this->hascon) {
if ($this->hasres[$srp]) {
$rv = @ldap_count_entries($this->cid, $this->sr[$srp]);
$this->error = @ldap_error($this->cid);
logging::debug(DEBUG_LDAP, __LINE__, __FUNCTION__, __FILE__, $this->error, 'count()');
return $rv;
} else {
$this->error = "Perform a Fetch with no Search";
logging::debug(DEBUG_LDAP, __LINE__, __FUNCTION__, __FILE__, $this->error, 'count()');
return "";
}
} else {
$this->error = "Could not connect to LDAP server";
631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700
logging::debug(DEBUG_LDAP, __LINE__, __FUNCTION__, __FILE__, $this->error, 'count()');
return "";
}
}
/*!
* \brief Remove
*
* \param string $attrs Empty string
*
* \param string $dn Empty string
*/
function rm ($attrs = "", $dn = "")
{
if ($this->hascon) {
if ($this->reconnect) {
$this->connect();
}
if ($dn == '') {
$dn = $this->basedn;
}
$r = ldap_mod_del($this->cid, $dn, $attrs);
$this->error = @ldap_error($this->cid);
logging::debug(DEBUG_LDAP, __LINE__, __FUNCTION__, __FILE__, $this->error, 'rm('.$dn.')');
return $r;
} else {
$this->error = 'Could not connect to LDAP server';
logging::debug(DEBUG_LDAP, __LINE__, __FUNCTION__, __FILE__, $this->error, 'rm('.$dn.')');
return '';
}
}
function mod_add ($attrs = "", $dn = "")
{
if ($this->hascon) {
if ($this->reconnect) {
$this->connect();
}
if ($dn == "") {
$dn = $this->basedn;
}
$r = @ldap_mod_add($this->cid, $dn, $attrs);
$this->error = @ldap_error($this->cid);
logging::debug(DEBUG_LDAP, __LINE__, __FUNCTION__, __FILE__, $this->error, 'mod_add('.$dn.')');
return $r;
} else {
$this->error = "Could not connect to LDAP server";
logging::debug(DEBUG_LDAP, __LINE__, __FUNCTION__, __FILE__, $this->error, 'mod_add('.$dn.')');
return "";
}
}
/*!
* \brief Remove directory
*
* \param string $deletedn The DN to be deleted
*/
function rmdir ($deletedn)
{
if ($this->hascon) {
if ($this->reconnect) {
$this->connect();
}
$r = @ldap_delete($this->cid, $deletedn);
$this->error = @ldap_error($this->cid);
logging::debug(DEBUG_LDAP, __LINE__, __FUNCTION__, __FILE__, $this->error, 'rmdir('.$deletedn.')');
return ($r ? $r : 0);
701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770
} else {
$this->error = "Could not connect to LDAP server";
logging::debug(DEBUG_LDAP, __LINE__, __FUNCTION__, __FILE__, $this->error, 'rmdir('.$deletedn.')');
return "";
}
}
/*!
* \brief Move the given Ldap entry from $source to $dest
*
* \param String $source The source dn.
*
* \param String $dest The destination dn.
*
* \return Boolean TRUE on success else FALSE.
*/
function rename_dn ($source, $dest)
{
/* Check if source and destination are the same entry */
if (strtolower($source) == strtolower($dest)) {
trigger_error("Source and destination can't be the same entry.");
$this->error = "Source and destination can't be the same entry.";
return FALSE;
}
/* Check if destination entry exists */
if ($this->dn_exists($dest)) {
trigger_error("Destination '$dest' already exists.");
$this->error = "Destination '$dest' already exists.";
return FALSE;
}
/* Extract the name and the parent part out ouf source dn.
e.g. cn=herbert,ou=department,dc=...
parent => ou=department,dc=...
dest_rdn => cn=herbert
*/
$parent = preg_replace("/^[^,]+,/", "", $dest);
$dest_rdn = preg_replace("/,.*$/", "", $dest);
if ($this->hascon) {
if ($this->reconnect) {
$this->connect();
}
/* We have to pass TRUE as deleteoldrdn in case the attribute is single-valued */
$r = ldap_rename($this->cid, $source, $dest_rdn, $parent, TRUE);
$this->error = ldap_error($this->cid);
/* Check if destination dn exists, if not the server may not support this operation */
$r &= $this->dn_exists($dest);
logging::debug(DEBUG_LDAP, __LINE__, __FUNCTION__, __FILE__, $this->error, 'rename("'.$source.'","'.$dest.'")');
return $r;
} else {
$this->error = "Could not connect to LDAP server";
logging::debug(DEBUG_LDAP, __LINE__, __FUNCTION__, __FILE__, $this->error, 'rename("'.$source.'","'.$dest.'")');
return FALSE;
}
}
/*!
* \brief Function rmdir_recursive
*
* Based on recursive_remove, adding two thing: full subtree remove, and delete own node.
*
* \param $srp srp
*
* \param string $deletedn The dn to delete
*
771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840
* \return TRUE on sucessfull , 0 in error, and "" when we don't get a ldap conection
*/
function rmdir_recursive ($srp, $deletedn)
{
if ($this->hascon) {
if ($this->reconnect) {
$this->connect();
}
$delarray = [];
/* Get sorted list of dn's to delete */
$this->cd($deletedn);
$this->search($srp, '(objectClass=*)', ['dn']);
while ($attrs = $this->fetch($srp)) {
$delarray[$attrs['dn']] = strlen($attrs['dn']);
}
arsort($delarray);
reset($delarray);
/* Really Delete ALL dn's in subtree */
$r = TRUE;
foreach (array_keys($delarray) as $key) {
$r = @ldap_delete($this->cid, $key);
if ($r === FALSE) {
break;
}
}
$this->error = @ldap_error($this->cid);
logging::debug(DEBUG_LDAP, __LINE__, __FUNCTION__, __FILE__, $this->error, 'rmdir_recursive("'.$deletedn.'")');
return ($r ? $r : 0);
} else {
$this->error = "Could not connect to LDAP server";
logging::debug(DEBUG_LDAP, __LINE__, __FUNCTION__, __FILE__, $this->error, 'rmdir_recursive("'.$deletedn.'")');
return "";
}
}
function makeReadableErrors ($error, $attrs)
{
if ($this->success()) {
return "";
}
$str = "";
if (isset($attrs['objectClass'])
&& preg_match("/^objectClass: value #([0-9]*) invalid per syntax$/", $this->get_additional_error(), $m)) {
$ocs = $attrs['objectClass'];
if (!is_array($ocs)) {
$ocs = [$ocs];
}
if (isset($ocs[$m[1]])) {
$str .= " - <b>objectClass: ".$ocs[$m[1]]."</b>";
}
}
if ($error == "Undefined attribute type") {
$str = " - <b>attribute: ".preg_replace("/:.*$/", "", $this->get_additional_error())."</b>";
}
logging::debug(DEBUG_LDAP, __LINE__, __FUNCTION__, __FILE__, $attrs, "Erroneous data");
return $str;
}
/*!
* \brief Modify a entry of the directory LDAP
*
* \param array $attrs The new entry
*/
function modify (array $attrs)
{
841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910
if (count($attrs) == 0) {
return 0;
}
if ($this->hascon) {
if ($this->reconnect) {
$this->connect();
}
$r = @ldap_modify($this->cid, $this->basedn, $attrs);
$this->error = @ldap_error($this->cid);
if (!$this->success()) {
$this->error .= $this->makeReadableErrors($this->error, $attrs);
}
logging::debug(DEBUG_LDAP, __LINE__, __FUNCTION__, __FILE__, $this->error, 'modify('.$this->basedn.')');
return ($r ? $r : 0);
} else {
$this->error = "Could not connect to LDAP server";
logging::debug(DEBUG_LDAP, __LINE__, __FUNCTION__, __FILE__, $this->error, 'modify('.$this->basedn.')');
return "";
}
}
/*!
* \brief Modify a entry of the directory LDAP with fine control
*
* \param array $changes The changes
*/
function modify_batch (array $changes)
{
if (count($changes) == 0) {
return TRUE;
}
if ($this->hascon) {
if ($this->reconnect) {
$this->connect();
}
$r = @ldap_modify_batch($this->cid, $this->basedn, $changes);
$this->error = @ldap_error($this->cid);
logging::debug(DEBUG_LDAP, __LINE__, __FUNCTION__, __FILE__, $this->error, 'modify_batch('.$this->basedn.')');
return $r;
} else {
$this->error = 'Could not connect to LDAP server';
logging::debug(DEBUG_LDAP, __LINE__, __FUNCTION__, __FILE__, $this->error, 'modify_batch('.$this->basedn.')');
return FALSE;
}
}
/*!
* \brief Add entry in the LDAP directory
*
* \param string $attrs The entry to add
*/
function add ($attrs)
{
if ($this->hascon) {
if ($this->reconnect) {
$this->connect();
}
$r = @ldap_add($this->cid, $this->basedn, $attrs);
$this->error = @ldap_error($this->cid);
if (!$this->success()) {
$this->error .= $this->makeReadableErrors($this->error, $attrs);
}
logging::debug(DEBUG_LDAP, __LINE__, __FUNCTION__, __FILE__, $this->error, 'add('.$this->basedn.')');
return ($r ? $r : 0);
} else {
$this->error = "Could not connect to LDAP server";
logging::debug(DEBUG_LDAP, __LINE__, __FUNCTION__, __FILE__, $this->error, 'add('.$this->basedn.')');
return "";
}
}
911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980
/*
* $target is a dn, i.e. "ou=example,ou=orga,dc=base"
*
* Creates missing trees, in our example ou=orga,dc=base will get created if not existing, same thing for ou=example,ou=orga,dc=base
* */
function create_missing_trees ($srp, $target, $ignoreReferralBases = TRUE)
{
$real_path = substr($target, 0, strlen($target) - strlen($this->basedn) - 1);
if ($target == $this->basedn) {
$l = ["dummy"];
} else {
$l = array_reverse(ldap_explode_dn($real_path, 0));
}
unset($l['count']);
$cdn = $this->basedn;
/* Load schema if available... */
$classes = $this->get_objectclasses();
foreach ($l as $part) {
if ($part != "dummy") {
$cdn = "$part,$cdn";
}
/* Ignore referrals */
if ($ignoreReferralBases) {
$found = FALSE;
foreach ($this->referrals as $ref) {
if ($ref['BASE'] == $cdn) {
$found = TRUE;
break;
}
}
if ($found) {
continue;
}
}
/* Create missing entry? */
if (!$this->dn_exists($cdn)) {
$type = preg_replace('/^([^=]+)=.*$/', '\\1', $cdn);
$param = preg_replace('/^[^=]+=([^,]+).*$/', '\\1', $cdn);
$param = preg_replace(['/\\\\,/','/\\\\"/'], [',','"'], $param);
$na = [];
/* Automatic or traditional? */
if (count($classes)) {
if ($type == 'l') {
/* Locality has l as MAY so autodetection fails */
$ocname = 'locality';
} else {
/* Get name of first matching objectClass */
$ocname = '';
foreach ($classes as $class) {
if (isset($class['MUST']) && in_array($type, $class['MUST'])) {
/* Look for first classes that is structural... */
if (isset($class['STRUCTURAL'])) {
$ocname = $class['NAME'];
break;
}
/* Look for classes that are auxiliary... */
if (isset($class['AUXILIARY'])) {
$ocname = $class['NAME'];
}
}
}
981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050
}
/* Bail out, if we've nothing to do... */
if ($ocname == '') {
throw new FusionDirectoryError(htmlescape(sprintf(_('Cannot automatically create subtrees with RDN "%s": no object class found!'), $type)));
}
/* Assemble_entry */
$na['objectClass'] = [$ocname];
if (isset($classes[$ocname]['AUXILIARY'])) {
$na['objectClass'][] = $classes[$ocname]['SUP'];
}
if ($type == 'dc') {
/* This is bad actually, but - tell me a better way? */
$na['objectClass'][] = 'organization';
$na['o'] = $param;
}
$na[$type] = $param;
// Fill in MUST values - but do not overwrite existing ones.
$oc = $ocname;
do {
if (isset($classes[$oc]['MUST']) && is_array($classes[$oc]['MUST'])) {
foreach ($classes[$oc]['MUST'] as $attr) {
if (isset($na[$attr]) && !empty($na[$attr])) {
continue;
}
$na[$attr] = 'filled';
}
}
$oc = ($classes[$oc]['SUP'] ?? NULL);
} while ($oc);
} else {
/* Use alternative add... */
switch ($type) {
case 'ou':
$na['objectClass'] = 'organizationalUnit';
$na['ou'] = $param;
break;
case 'dc':
$na['objectClass'] = ['dcObject', 'top', 'organization'];
$na['dc'] = $param;
$na['o'] = $param;
break;
default:
throw new FusionDirectoryError(htmlescape(sprintf(_('Cannot automatically create subtrees with RDN "%s": not supported'), $type)));
}
}
$this->cd($cdn);
$this->add($na);
if (!$this->success()) {
logging::debug(DEBUG_LDAP, __LINE__, __FUNCTION__, __FILE__, $cdn, 'dn');
logging::debug(DEBUG_LDAP, __LINE__, __FUNCTION__, __FILE__, $na, 'Content');
logging::debug(DEBUG_LDAP, __LINE__, __FUNCTION__, __FILE__, $this->get_error(), 'LDAP error');
throw new FusionDirectoryLdapError($cdn, LDAP_ADD, $this->get_error(), $this->get_errno());
}
}
}
}
/*!
* \brief Get the LDAP additional error
*
* \return string containts LDAP_OPT_ERROR_STRING
*/
function get_additional_error ()
{
1051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120
$additional_error = '';
@ldap_get_option($this->cid, LDAP_OPT_ERROR_STRING, $additional_error);
return $additional_error;
}
/*!
* \brief Success
*
* \return boolean TRUE if Success is found in $error, else return FALSE
*/
function success (): bool
{
return (trim($this->error) === 'Success');
}
/*!
* \brief Get the error
*/
function get_error ($details = TRUE): string
{
if (($this->error == 'Success') || !$details) {
return $this->error;
} else {
$adderror = $this->get_additional_error();
if ($adderror != '') {
return sprintf(
_('%s (%s, while operating on "%s" using LDAP server "%s")'),
$this->error, $adderror, $this->basedn, $this->hostname
);
} else {
return sprintf(
_('%s (while operating on LDAP server "%s")'),
$this->error, $this->hostname
);
}
}
}
/*!
* \brief Get the errno
*
* Must be run right after the ldap request
*/
function get_errno (): int
{
if ($this->error == 'Success') {
return 0;
} else {
return @ldap_errno($this->cid) ?? -1;
}
}
/*!
* \brief Check if the search hit the size limit
*
* Must be run right after the search
*/
function hitSizeLimit (): bool
{
/* LDAP_SIZELIMIT_EXCEEDED 0x04 */
return ($this->get_errno() == 0x04);
}
function get_credentials ($url, $referrals = NULL)
{
$ret = [];
$url = preg_replace('!\?\?.*$!', '', $url);
$server = preg_replace('!^([^:]+://[^/]+)/.*$!', '\\1', $url);
if ($referrals === NULL) {
1121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190
$referrals = $this->referrals;
}
if (isset($referrals[$server])) {
return $referrals[$server];
} else {
$ret['ADMINDN'] = $this->binddn;
$ret['ADMINPASSWORD'] = $this->bindpw;
}
return $ret;
}
/*!
* \brief Generates an ldif for all entries matching the filter settings, scope and limit.
*
* \param string $dn The entry to export.
*
* \param string $filter Limit the exported object to those maching this filter.
*
* \param string $scope 'base', 'sub' .. see manpage for 'ldapmodify' for details.
*
* \param int $limit Limits the result.
*
* \param ?int $wrap Wraps line around this length (0 to disable).
*/
function generateLdif (string $dn, string $filter = '(objectClass=*)', string $scope = 'sub', int $limit = 0, int $wrap = NULL): string
{
$limit = (($limit == 0) ? '' : ' -z '.$limit);
if ($wrap === NULL) {
$wrap = '';
} else {
$wrap = ' -o ldif-wrap='.($wrap ? $wrap : 'no');
}
// Check scope values
$scope = trim($scope);
if (!empty($scope) && !in_array($scope, ['base', 'one', 'sub', 'children'])) {
throw new LDIFExportException(sprintf('Invalid parameter for scope "%s", please use "base", "one", "sub" or "children".', $scope));
}
$scope = (empty($scope) ? '' : ' -s '.$scope);
// Prepare parameters to be valid for shell execution
$dn = escapeshellarg($dn);
$pwd = escapeshellarg($this->bindpw);
$host = escapeshellarg($this->hostname);
$admin = escapeshellarg($this->binddn);
$filter = escapeshellarg($filter);
$cmd = 'ldapsearch'.($this->tls ? ' -ZZ' : '')." -x -LLLL -D {$admin} {$filter} {$limit} {$wrap} {$scope} -H {$host} -b {$dn} -w {$pwd} ";
// Create list of process pipes
$descriptorspec = [
0 => ["pipe", "r"], // stdin
1 => ["pipe", "w"], // stdout
2 => ["pipe", "w"] // stderr
];
// Try to open the process
$process = proc_open($cmd, $descriptorspec, $pipes);
if ($process !== FALSE) {
// Write the password to stdin
fclose($pipes[0]);
// Get results from stdout and stderr
$res = stream_get_contents($pipes[1]);
$err = stream_get_contents($pipes[2]);
fclose($pipes[1]);
1191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260
// Close the process and check its return value
if (proc_close($process) != 0) {
throw new LDIFExportException($err);
}
} else {
throw new LDIFExportException(_('proc_open failed to execute ldapsearch'));
}
return $res;
}
function dn_exists ($dn): bool
{
logging::debug(DEBUG_LDAP, __LINE__, __FUNCTION__, __FILE__, '', 'dn_exists('.$dn.')');
return (@ldap_read($this->cid, $dn, '(objectClass=*)', ['objectClass']) !== FALSE);
}
function parseLdif (string $str_attr): array
{
/* First we split the string into lines */
$fileLines = preg_split("/\n/", $str_attr);
if (end($fileLines) != '') {
$fileLines[] = '';
}
/* Joining lines */
$line = NULL;
$entry = [];
$entries = [];
$entryStart = -1;
foreach ($fileLines as $lineNumber => $fileLine) {
if (preg_match('/^ /', $fileLine)) {
if ($line === NULL) {
throw new LDIFImportException(sprintf(_('Error line %s, first line of an entry cannot start with a space'), $lineNumber));
}
/* Append to current line */
$line .= substr($fileLine, 1);
} else {
if ($line !== NULL) {
if (preg_match('/^#/', $line)
|| (preg_match('/^version:/', $line) && empty($entry))) {
/* Ignore comment */
/* Ignore version number */
} else {
/* Line has ended */
list ($key, $value) = explode(':', $line, 2);
$value = trim($value);
if (preg_match('/^:/', $value)) {
$value = base64_decode(trim(substr($value, 1)));
}
if (preg_match('/^</', $value)) {
throw new LDIFImportException(sprintf(_('Error line %s, references to an external file are not supported'), $lineNumber));
}
if ($value === '') {
throw new LDIFImportException(sprintf(_('Error line %s, attribute "%s" has no value'), $lineNumber, $key));
}
if ($key == 'dn') {
if (!empty($entry)) {
throw new LDIFImportException(sprintf(_('Error line %s, an entry bloc can only have one dn'), $lineNumber));
}
$entry['dn'] = $value;
$entryStart = $lineNumber;
} elseif (empty($entry)) {
throw new LDIFImportException(sprintf(_('Error line %s, an entry bloc should start with the dn'), $lineNumber));
} else {
if (!isset($entry[$key])) {
$entry[$key] = [];
}
$entry[$key][] = $value;
}
}
1261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330
}
/* Start new line */
$line = trim($fileLine);
if ($line == '') {
if (!empty($entry)) {
/* Entry is finished */
$entries[$entryStart] = $entry;
}
/* Start a new entry */
$entry = [];
$entryStart = -1;
$line = NULL;
}
}
}
return $entries;
}
/*!
* \brief Function to imports ldifs
*
* If DeleteOldEntries is TRUE, the destination entry will be deleted first.
* If JustModify is TRUE the destination entry will only be touched by the attributes specified in the ldif.
* if JustMofify is FALSE the destination dn will be overwritten by the new ldif.
*
* \param integer $srp
*
* \param string $str_attr
*
* \param boolean $JustModify
*
* \param boolean $DeleteOldEntries
*/
function import_complete_ldif ($srp, $str_attr, $JustModify, $DeleteOldEntries)
{
$entries = $this->parseLdif($str_attr);
if ($this->reconnect) {
$this->connect();
}
foreach ($entries as $startLine => $entry) {
/* Delete before insert */
$usermdir = ($this->dn_exists($entry['dn']) && $DeleteOldEntries);
/* Should we use Modify instead of Add */
$usemodify = ($this->dn_exists($entry['dn']) && $JustModify);
/* If we can't Import, return with a file error */
if (!$this->import_single_entry($srp, $entry, $usemodify, $usermdir)) {
throw new LDIFImportException(sprintf(_('Error while importing dn: "%s", please check your LDIF from line %s on!'), $entry['dn'][0], $startLine));
}
}
return count($entries);
}
/*! \brief Function to Imports a single entry
*
* If $delete is TRUE; The old entry will be deleted if it exists.
* if $modify is TRUE; All variables that are not touched by the new ldif will be kept.
* if $modify is FALSE; The new ldif overwrites the old entry, and all untouched attributes get lost.
*
* \param integer $srp
*
* \param array $data
*
* \param boolean $modify
*
* \param boolean $delete
1331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400
*/
protected function import_single_entry ($srp, $data, $modify, $delete)
{
global $config;
if (!$config) {
trigger_error("Can't import ldif, can't read config object.");
}
if ($this->reconnect) {
$this->connect();
}
$ret = FALSE;
$dn = NULL;
$operation = NULL;
/* If dn is an index of data, we should try to insert the data */
if (isset($data['dn'])) {
/* Fix dn */
$tmp = ldap_explode_dn($data['dn'], 0);
unset($tmp['count']);
$dn = '';
foreach ($tmp as $tm) {
$dn .= trim($tm).',';
}
$dn = preg_replace('/,$/', '', $dn);
unset($data['dn']);
/* Creating Entry */
$this->cd($dn);
/* Delete existing entry */
if ($delete) {
$this->rmdir_recursive($srp, $dn);
}
/* Create missing trees */
$this->cd($config->current['BASE']);
try {
$this->create_missing_trees($srp, preg_replace('/^[^,]+,/', '', $dn));
} catch (FusionDirectoryError $error) {
$error->display();
}
$this->cd($dn);
$operation = LDAP_MOD;
if (!$modify) {
$this->cat($srp, $dn);
if ($this->count($srp)) {
/* The destination entry exists, overwrite it with the new entry */
$attrs = $this->fetch($srp);
foreach (array_keys($attrs) as $name) {
if (!is_numeric($name)) {
if (in_array($name, ['dn','count'])) {
continue;
}
if (!isset($data[$name])) {
$data[$name] = [];
}
}
}
$ret = $this->modify($data);
} else {
/* The destination entry doesn't exists, create it */
$operation = LDAP_ADD;
$ret = $this->add($data);
}
} else {
/* Keep all vars that aren't touched by this ldif */
1401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470
$ret = $this->modify($data);
}
}
if (!$this->success()) {
$error = new FusionDirectoryLdapError($dn, $operation, $this->get_error(), $this->get_errno());
$error->display();
}
return $ret;
}
/*!
* \brief Get the object classes
*
* \param boolean $force_reload FALSE
*/
function get_objectclasses ($force_reload = FALSE)
{
/* Return the cached results. */
if (class_available('session') && session::is_set('LDAP_CACHE::get_objectclasses') && !$force_reload) {
return session::get('LDAP_CACHE::get_objectclasses');
}
// Get base to look for schema
$res = @ldap_read($this->cid, '', 'objectClass=*', ['subschemaSubentry']);
$attrs = @ldap_get_entries($this->cid, $res);
if (!isset($attrs[0]['subschemasubentry'][0])) {
return [];
}
/* Get list of objectclasses and fill array */
$nb = $attrs[0]['subschemasubentry'][0];
$objectclasses = [];
$res = ldap_read($this->cid, $nb, 'objectClass=*', ['objectclasses']);
$attrs = ldap_get_entries($this->cid, $res);
if (!isset($attrs[0])) {
return [];
}
foreach ($attrs[0]['objectclasses'] as $val) {
if (preg_match('/^[0-9]+$/', $val)) {
continue;
}
$name = 'OID';
$pattern = explode(' ', $val);
$ocname = preg_replace("/^.* NAME\s+\(*\s*'([^']+)'\s*\)*.*$/", '\\1', $val);
$objectclasses[$ocname] = [];
$value = '';
foreach ($pattern as $chunk) {
switch ($chunk) {
case '(':
$value = '';
break;
case ')':
if ($name != '') {
$v = $this->value2container($value);
if (in_array($name, ['MUST', 'MAY']) && !is_array($v)) {
$v = [$v];
}
$objectclasses[$ocname][$name] = $v;
}
$name = '';
$value = '';
break;
case 'NAME':
1471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540
case 'DESC':
case 'SUP':
case 'STRUCTURAL':
case 'ABSTRACT':
case 'AUXILIARY':
case 'MUST':
case 'MAY':
if ($name != '') {
$v = $this->value2container($value);
if (in_array($name, ['MUST','MAY']) && !is_array($v)) {
$v = [$v];
}
$objectclasses[$ocname][$name] = $v;
}
$name = $chunk;
$value = '';
break;
default: $value .= $chunk.' ';
}
}
}
if (class_available('session')) {
session::set('LDAP_CACHE::get_objectclasses', $objectclasses);
}
return $objectclasses;
}
function value2container ($value)
{
/* Set emtpy values to "TRUE" only */
if (preg_match('/^\s*$/', $value)) {
return TRUE;
}
/* Remove ' and " if needed */
$value = preg_replace('/^[\'"]/', '', $value);
$value = preg_replace('/[\'"] *$/', '', $value);
/* Convert to array if $ is inside... */
if (preg_match('/\$/', $value)) {
$container = preg_split('/\s*\$\s*/', $value);
} else {
$container = chop($value);
}
return $container;
}
/*!
* \brief Add a string in log file
*
* \param string $string
*/
function log ($string)
{
if (session::is_set('config')) {
$cfg = session::get('config');
if (isset($cfg->current['LDAPSTATS']) && preg_match('/true/i', $cfg->current['LDAPSTATS'])) {
syslog(LOG_INFO, $string);
}
}
}
/* added by Guido Serra aka Zeph <zeph@purotesto.it> */
/*!
* \brief Function to get cn
15411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575
*
* \param $dn The DN
*/
function getCn ($dn)
{
$simple = explode(",", $dn);
foreach ($simple as $piece) {
$partial = explode("=", $piece);
if ($partial[0] == "cn") {
return $partial[1];
}
}
}
public static function get_naming_contexts ($server, $admin = '', $password = '')
{
/* Build LDAP connection */
$ds = ldap_connect($server);
if (!$ds) {
die('Can\'t bind to LDAP. No check possible!');
}
ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 3);
ldap_bind($ds, $admin, $password);
/* Get base to look for naming contexts */
$res = @ldap_read($ds, '', 'objectClass=*', ['namingContexts']);
$attrs = @ldap_get_entries($ds, $res);
logging::debug(DEBUG_LDAP, __LINE__, __FUNCTION__, __FILE__, $attrs[0]['namingcontexts'], 'get_naming_contexts');
return $attrs[0]['namingcontexts'];
}
}