Location: PHPKode > projects > LDAPted > ldapted/includes/LDAPinterface.php
<?php
/***************************************************************************
 *
 *                                  LDAPinterface.php
 *                              -------------------
 *
 *   begin                : Friday, Jul 5, 2002
 *   copyright            : (C) 2002 The Kabramps Team
 *   email                : hide@address.com,
 *                          hide@address.com
 *
 *
 *
 ***************************************************************************/

/***************************************************************************
 *
 *   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.
 *   (http://www.gnu.org/licenses/gpl.html)
 *
 ***************************************************************************/


// This class depends on the LDAP extension (>= 1.116.2.1).
if (!extension_loaded('ldap')) {
  die(gettext("LDAP extension is not available in your PHP version - so we quit here!"));
}

include('includes/Schema.php');

class LDAPinterface {

  var $connection;
  var $binddn;
  var $errormessage;
  var $protocol_version = 3; // no need to support version 2
  //var $sort = LDAP_SORT_ASCII;
  var $attriberrors = array(); // represent all attribs and corresponding objectclasses which can't be deleted because of one of its value or emptiness

	function LDAPinterface ($host, $bind_dn, $pw)
	{
		$this->binddn     = $bind_dn;
		$this->connection = ldap_connect( $host );

		if ($this->connection)
		{
	      //if ( !ldap_start_tls($this->connection) ) {
	      //$this->errormessage .= "Couldn't create secure connection<br>";
	      //return false;
	      //}
			if ( ! ldap_set_option($this->connection,LDAP_OPT_PROTOCOL_VERSION, $this->protocol_version) )
			{
				echo 'WARNING: LDAP_OPT_PROTOCOL_VERSION '.$this->protocol_version.' not availible!<br>';
			}
	
			//$pw = str_replace('\\', "" , $pw); // ??
			$bind = @ldap_bind( $this->connection, $bind_dn, $pw );
	
			if ($bind)
			{
				return true;
			}
			else
			{
				$this->binddn = gettext('anonymous');
				$this->errormessage .= "No valid credential for $bind_dn";
				return false;
			}
		}
		else
		{
			$this->binddn =gettext('anonymous');
			$this->errormessage .= "Could not connect to $host";
			return false;
		}
	}

  function destroy () {
    ldap_close($this->connection);
  }

  function search ($base_dn, $attributes, $filter='objectClass=*', $recursive='0', $sortattribute = false ) {
    global $options;
    if ( ! $filter ) {
      $filter='objectClass=*';
    }

/** I'm not sure if modifytimestamp is really needed
    if ($recursive=="1" && count($attributes) == 0) { 
    } else {
      $attributes[] = "modifytimestamp";
	}
**/
    
    if ($recursive) {
      $search = @ldap_search($this->connection, $base_dn, $filter, $attributes, 0, $options["sizelimit"] );
    } else {
      $search = @ldap_list($this->connection, $base_dn, $filter, $attributes, 0, $options["sizelimit"] );
    }
    
    if ($search) {
      $return = array();
      if (ldap_count_entries($this->connection, $search) > 0) {
	if ( $sortattribute ) {
	  ldap_sort($this->connection, $search, $sortattribute); //sort the result list
	}
	$return = ldap_get_entries($this->connection, $search);
      }
      return $return;
    } else {
      $this->LDAPErrorHandler('search', array('attributes'    => $attributes,
                                              'base_dn'       => $base_dn,
                                              'recursive'     => $recursive,
                                              'sortattribute' => $sortattribute,
                                              'filter'        => $filter
                                              ));
      return false;
    }
  }
  
  
	function modify ($dn, $entries) // entries is an array
	{
#    global $performance;
#    $performance->start("check");
    //$entries = $this->checkForm2($dn, $entries); //modifies the entries eventually
#    $performance->stop("check");
		if (is_array( $entries ) )
		{
#      $performance->start("modify");
      $modify = ldap_modify ($this->connection, $dn, $entries);
#      $performance->stop("modify");

      if (!$modify) {
      	$this->errormessage .= "Modification failed! DN: $dn<br>";
      	$this->LDAPErrorHandler('modify', $entries);
      }
      return $modify;
    } else {
      return false;
    }
  }
  
	function add ($dn, $entries, $check = true ) // entries is an array
	{
#		if ( $check )
#			$entries = $this->checkForm2($dn, $entries,$new=true); //modifies the entries eventually
    
		if (is_array( $entries ) )
		{
			// clean up entries
			foreach( $entries as $key => $entry )
			{
				if( is_array( $entry ) && !isset( $entry[0] ) )
				{
					unset( $entries[$key] );
				}
			}
			
			$add = @ldap_add ($this->connection, $dn, $entries);
			if (!$add)
			{
				$this->errormessage .= "Could not add the DN: $dn<br>";
				$this->LDAPErrorHandler('add', $entries);
			}
			return $add;
		}
		else
		{
			return false;
		}
	}
  
  function delete_entries($dn) {
    $deleted=0;
    for ($i=0; $i<count($dn); $i++ ) {
      if ( $this->delete($dn[$i]) ) {
	$deleted++;
      }
    }
    return $deleted;
  }

  function delete ($dn) {
    $del = ldap_delete ($this->connection, $dn);
    if (!$del)
      $this->errormessage .= "Deletion failed: DN: $dn";
    return $del;
  }

  function link ( $entries, $dn ) {
    for ( $i=0; $i<count($entries); $i++ ) {
      
    }
  } 

/**
 * $entries are the selected entries which are to copy to another location.
 * $dn is the target location for the new entries.
 **/
 
  function copy( $entries, $dn ) {
    $count = 0;
    for ( $i=0; $i < count($entries);  $i++ ) {
      $result = $this->search($entries[$i], array(), $filter='', $recursive='1');
      $entry = $result[0]; // we do not support recusrive copy yet ;-)
      if( count($entry) > 0 ) {
        $name = split(',',$entry['dn']);
        $dname = $name[0].",".$dn;   // destination dn        
        //unset($entry['dn']); //why should "dn" be unset ??
        $attributes = array();
        for ( $k=0; $k<count($entry); $k++ ) {
          if ( $entry[$k] != "" ) {
            for ( $l=0; $l<count($entry[$entry[$k]]); $l++ ) {
              if ( $entry[$entry[$k]][$l] != "" ) {
                //echo $entry[$entry[$k]][$l]."<br>";
                $attributes[$entry[$k]][]=$entry[$entry[$k]][$l];
              }
            }
          }
        }
        if ( $this->add($dname, $attributes, false ) ) {
          $count++;
        }
      }
    }
    // clipboard should be empty at this point.
    return $count;
  }

  function move( $entries, $dn ) {
    ldap_get_option($this->connection, LDAP_OPT_PROTOCOL_VERSION, $version);
    if ( $version < 3 ) {
      if ( $this->copy( $entries, $dn ) == count($entries) ) {
	if ( $this->delete_entries($entries) == count($entries) ) {
	  return count($entries);
	} else {
	  return 0;
	}
      } else {
	return 0;
      }
    } else {
      $count = 0;
      for ( $i=0; $i<count($entries); $i++ ) {
	$rdnInterpreter = new dnInterpreter($entries[$i]);
	$rdn = $rdnInterpreter->get_leaf();
	if ( ldap_rename($this->connection, $entries[$i], $rdn, $dn, true) ) {
	  $count++;
	}
      }
      return $count;
    }
  }

  function get_error_message($ldaperror=false) {
    if ( $ldaperror ) {
      $this->errormessage .= "Error: ".ldap_err2str(ldap_errno($this->connection))."<br>";
    }
    return $this->errormessage;
  }

  function get_unresolvable_attrib_errors ($list) {
    $return = '';
    for ( reset($this->attriberrors); $key=key($this->attriberrors); next($this->attriberrors) ) {
      if (!in_array($key,$list)) {
	$return .= gettext("objectclass violation: unresolved problem for:"." ".$key."\n");
      }
    }
    return $return;
  }

  function checkForm2 ($dn, $entries, $new = false) {
    global $options;
#    global $performance;

    // clear the entries, resolve entry aliases
#    $performance->start("schema");
    $schema = new Schema($this->connection);
    $tmpentries = array();
    for ( reset($entries); $key=key($entries); next($entries) ) {
      $name = $schema->getAttributeName($key);
      if ( !$name ) {
	$this->errormessage .= "Error: unknown Attribute: '".$key."'<br>";
        return false;
      }
      $tmpentries[$name] = $entries[$key];
      if ( is_array($tmpentries[$name])) {
	$tmpvalue = array();
	for ($i = 0; $i < count($tmpentries[$name]); $i++) {
	  if (chop($tmpentries[$name][$i]) != '') {
	    $tmpvalue[] = $tmpentries[$name][$i];
	  }
	}
	$tmpentries[$name] = $tmpvalue;
      } else {
	if ($options['debug']) die ("The data type of $name is not a Array.");
	$this->errormessage .= "The data type of $name is not a Array.";
	return false;
      }
    }
    $formentries = $tmpentries;
#    $performance->stop("schema");
   
    // formentries are the entries without the 'objectclasses'
    $formobjectClasses = $formentries['objectclass'];
    unset($formentries['objectclass']);

    
    // let's read what we can get from LDAP
    if (!$new) {
      $ldapentries = $this->search($dn,array('*'),null,1);
      $ldapentries = $ldapentries[0]; // [0] - one element only in result list of the search
      if ($ldapentries['modifytimestamp'][0] != $entries['modifytimestamp'][0] ) {
	$this->errormessage .= gettext("Entry already modified by another user");
	return false;
      } else {
	unset($entries['modifytimestamp']);
      }
      // some empty fields should be filtered
      //for ($i=0; $i < count($ldapentries['objectclass']); $i++) {
      //if ($ldapentries['objectclass'][$i] != "") {
      //$tmp[] = $ldapentries['objectclass'][$i];
      //}
      //}
      $ldapobjectClasses = $ldapentries['objectclass'];
      unset($ldapentries['objectclass']);
    } else {
      $ldapentries = array();
      $ldapobjectClasses = array();
    }
    
    // saveEntries are only these entries which have been changed
    if ($options['debug']) echo "<br>Start building the saveEntries array<br>";
    if ($options['debug']) echo "Count of SaveEntries before check: ".count($formentries)."<br>";
    for ( reset($formentries); $key=key($formentries); next($formentries)) {
      $saveEntries[$key] = $formentries[$key];
      if ($options['debug']) echo "$key";
      if (is_array($ldapentries[$key])) {
	// some empty fields should be filtered
	for ($i=0; $i < count($ldapentries["$key"]); $i++) {
	  if ($ldapentries["$key"][$i] != "") {
	    $tmp[] = $ldapentries["$key"][$i];
	  }
	}
	$ldapentries["$key"] = $tmp;
	unset($tmp);
	
	//$ldapentries[$key] = array_diff($ldapentries[$key],array(''));
	$a = array_diff($formentries[$key],$ldapentries[$key]);
	$b = array_diff($ldapentries[$key],$formentries[$key]);
	if ((count($a) == 0) && (count($b) == 0 )) {
	  unset($saveEntries[$key]);
	  if ($options['debug']) echo " didn't change (1)";
	} else {
	  if ($options['debug']) echo " is different to LDAP-value";
	}
      } else {
	if ( count($formentries[$key]) == '0') {
	  unset($saveEntries[$key]);
          if ($options['debug']) echo " is empty in form (2)";
	} else {
	  if ($options['debug']) echo " has new value in form";
	}
      }
      if ($options['debug']) echo "<br>";
    }
    
    if ($options['debug']) echo "Count of SaveEntries after check: ".count($saveEntries)."<br>";
    if (count($saveEntries) == 0) { //nothing to do
      if (!$options['debug']) return array();
      die("Nothing more to do");
    }
    
    // let's construct $objectClasses with all possible objectClasses for the DN
    //for ($i = 0; $i < count($formobjectClasses); $i++) {
    //  if (($formobjectClasses[$i] != "") && ( !in_array($formobjectClasses[$i],$ldapobjectClasses))) {
    //$ldapobjectClasses[] = $formobjectClasses[$i];
    //}
    //}
    $objectClasses = array_merge($formobjectClasses,$ldapobjectClasses);
    
    //build writeVector and saveMatrix
    $writeVector = array(); // writeVector indicates if a attrib empty (0), not empty (1) and (-1) if not readable
    $saveMatrix = array();
    for ($i = 0; $i < count($objectClasses); $i++) {
      $allows = $schema->getAllows($objectClasses[$i]);
      for ($j = 0; $j < count($allows); $j++) {
	$noalias = $schema->getAttributeName($allows[$j]);
	if (!isset($saveMatrix["$objectClasses[$i]"]["$noalias"])) {
	  $saveMatrix["$objectClasses[$i]"]["$noalias"] = '0';
	  if ($new) {
	    $writeVector["$noalias"] = '0';
	  } else {
	    $writeVector["$noalias"] = '-1';
	  }
	  //echo "$objectClasses[$i] $allows[$j]<br>";
	}
      }
      $require =  $schema->getRequires($objectClasses[$i]);
      for ($k = 0; $k < count($require); $k++) {
	$noalias =  $schema->getAttributeName($require[$k]);
	if (!isset($saveMatrix["$objectClasses[$i]"]["$noalias"])) {
	  $saveMatrix["$objectClasses[$i]"]["$noalias"] = '1';
	  if ($new) {
	    $writeVector["$noalias"] = '0';
	  } else{
	    $writeVector["$noalias"]= '-1';
	  }
	  //echo "$objectClasses[$i] $require[$k]<br>";
	}
      }
    }

    //first of all we write the ldap entrie in the vector
    for ($i = 0; $i < count($ldapentries); $i++) {
      if (($ldapentries[$i] != "") && ($ldapentries[$i] != "objectclass")) {
	$writeVector["$ldapentries[$i]"] = 1;
      }
    }

    // lets override the attributes of the vector from the http form fields
    //    for ($i = 0; $i < count($formentries); $i++) {
    if ($options['debug']) echo "<br>Overrides the writeVector with the informations of the HTML form...<br>";
    for ( reset($formentries); $key=key($formentries); next($formentries) ) {
      if ($options['debug']) echo " $key is ";
      if ( $formentries[$key][0] != "" ) {
	$writeVector["$key"] = 1;
	if ($options['debug']) echo "not empty - so change value to 1<br>";
      } else {
	$writeVector["$key"] = 0;
	if ($options['debug']) echo "empty - so change value to 0<br>";
      }
    }
    
    $toBeDelete = array();
    if ($options['debug']) echo "<br>Lets see all objectclasses in combination with the attribs, which have to be written...<br>";
    if ($options['debug']) echo "and which have to be deleted<br>";
    for ( reset($saveMatrix); $xkey=key($saveMatrix); next($saveMatrix) ) {
      if ($options['debug']) echo "<br>$xkey<br>";
      if ($options['debug']) echo count($saveMatrix[$xkey])."::<br>";
      for ( reset($saveMatrix[$xkey]); $ykey=key($saveMatrix[$xkey]); next($saveMatrix[$xkey]) ) {
	if ($options['debug']) echo "$ykey: ".($writeVector[$ykey] - $saveMatrix[$xkey][$ykey])."<br>";
	if ((($writeVector[$ykey] - $saveMatrix[$xkey][$ykey]) == '-1' ) && ($writeVector[$ykey] != '-1')) {
	  if (!in_array($xkey, $toBeDelete)) {
	    $toBeDelete[] = $xkey;
	  }
	}
	// a required field is not in the form:
	if (($writeVector[$ykey] - $saveMatrix[$xkey][$ykey]) == '-2' ) {
	  if (!in_array($xkey, $toBeDelete)) {
            $toBeDelete[] = $xkey;
          }
	}
      }
    }

    // now we check if a objectclass can be deleted without loosing information
    for ($i = 0; $i < count($toBeDelete); $i++) {
      $problems = array();
      if ($options['debug']) echo "<br><b>To delete: $toBeDelete[$i]</b><br>";
      for ( reset($saveMatrix["$toBeDelete[$i]"]); $key=key($saveMatrix["$toBeDelete[$i]"]); next($saveMatrix["$toBeDelete[$i]"])) {
	if ($writeVector["$key"] != '0') {
	  if ($options['debug']) echo "Problem for $key";
	  $problems["$key"] = $writeVector["$key"];
	  for ( reset($saveMatrix); $akey=key($saveMatrix); next($saveMatrix)) {
	    if (!in_array($akey, $toBeDelete)) {
	      $attriblist = array_keys($saveMatrix["$akey"]);
	      if (in_array($key,$attriblist)) {
		$problems["$key"] = 0;
		if ($options['debug']) echo ", but found in ".$akey;
		break;
	      }
	    }
	  }
	  if ($options['debug']) echo "<br>";
	}
      }
      if ($options['debug']) echo "<br>".count($problems)." (un)resolved problems here:<br>";
      for ( reset($problems); $pkey=key($problems); next($problems)) {
	if ($options['debug']) echo "$pkey: ".$problems["$pkey"]."<br>"; 
	if ($problems["$pkey"] != '0') {
	  if ($options['debug']) echo "objectclass violation: cant delete $toBeDelete[$i] because of $pkey and its value: ".$problems["$pkey"]."<br>";
	  // not a errormessage: $this->errormessage .= "objectclass violation: cant delete $toBeDelete[$i] because of $pkey and its value: ".$problems["$pkey"]."<br>";
	  $this->attriberrors["$pkey"]["$toBeDelete[$i]"] = $problems[$pkey];
								    }
      }
      // lets remove the objectclass from the saveMatrix
      unset( $saveMatrix["$toBeDelete[$i]"]);
    }
    
    if ( $this->attriberrors ) {
      if ($options['debug']) {
	die("Unresolved problems found and die because of debug mode");
      } else {
	return false;
      }
    }

    // lets see if we can remove some redundant objectclasses
    if ($options['debug']) echo "<b><br><br>Check for redundant objectclasses<br></b>";
    if ($options['debug']) echo "remove empty attribs (we don't need them to have in another class)<br>";
    for ( reset($saveMatrix); $xkey=key($saveMatrix); next($saveMatrix) ) {
      if ($options['debug']) echo "<br><b>$xkey</b><br>";
      for ( reset($saveMatrix[$xkey]); $ykey=key($saveMatrix[$xkey]); next($saveMatrix[$xkey]) ) {
	if ($options['debug']) echo "$ykey: ".($writeVector[$ykey] * ($saveMatrix[$xkey][$ykey]+1))."<br>";
	if ($writeVector[$ykey] * ($saveMatrix[$xkey][$ykey]+1) == 0 ) {
	  unset($saveMatrix[$xkey][$ykey]);
	}
      }
    }
    
    $copyOfSaveMatrix = $saveMatrix;
    $redundantClasses = array();
    for ( reset($saveMatrix); $xkey=key($saveMatrix); next($saveMatrix) ) {
      //$redundantClasses["$xkey"] = '0';
      if ($options['debug']) echo "<br><b>lets check if $xkey is redundant</b><br>";
      $a = array_keys($saveMatrix["$xkey"]);
      for ($i = 0; $i <= count ($a); $i++) {
        if (($i == (count($a)-'1')) && ($redundantClasses["$xkey"]["$a[$i]"] == '1')) {
          // even the last attrib is redundant - so the hole objectclass is redundant
          $redundantClasses["$xkey"] = '1';
	  break;
        }
	$iMinusOne = $i - '1';
	if ( ($i > 0) && ($redundantClasses["$xkey"]["$a[$iMinusOne]"] != '1')) { // when one attrib wasn't found in another class, we can stop here
	  $redundantClasses["$xkey"] = '0';
	  if ($options['debug']) echo "stop here<br>";
	  break;
	}
	for ( reset($copyOfSaveMatrix); $xxkey=key($copyOfSaveMatrix); next($copyOfSaveMatrix) ) {
	  if ( ($xkey != $xxkey) && ($redundantClasses["$xxkey"] != '1')) {
	    if ($options['debug']) echo "try to find $a[$i] in $xxkey<br>";
	    $b = array_keys($saveMatrix["$xxkey"]);
	    if (in_array($a[$i],$b)) {
	      if ($options['debug']) echo "found $a[$i] in $xxkey :-)<br>";
	      $redundantClasses["$xkey"]["$a[$i]"] = '1';
	      break;
	    }
	  }
	}
      }
    }
    
    //removes the objectclasses from the saveMatrix
    if ($options['debug']) echo "<b><br>Remove redundant objectclasses<br></b>";
    for ( reset($redundantClasses); $key=key($redundantClasses); next($redundantClasses) ) {
      if ($options['debug']) echo "$key: ".$redundantClasses[$key]."<br>";
      if ($redundantClasses[$key]) {
	unset($saveMatrix[$key]);
      }
    }
    
    // lets complete the saveEntries with the objectclasses
    $saveEntries['objectclass'] = array_keys($saveMatrix);
    
    if ($options['debug']) {
      echo "<b><br>Here what would be added to the LDAP-Server...<br></b>";
      for ( reset($saveEntries); $xkey=key($saveEntries); next($saveEntries) ) {
	echo "<br>$xkey (".count($saveEntries[$xkey])."):<br>";
	for ($i = 0; $i < count($saveEntries[$xkey]); $i++) {	
	  echo "|-".$i." : ".$saveEntries[$xkey][$i]."<br>";
	}
      }
    }

    if ($options['debug']) die("We die here cause of the debug mode");
    
    return $saveEntries;
  }
  
  function get_error_attribs( $attribute ) {
    $return = array();
    if (is_array($this->attriberrors[$attribute])) {
      for ( reset($this->attriberrors[$attribute]); $key=key($this->attriberrors[$attribute]); next($this->attriberrors[$attribute]) ) {
	switch ($this->attriberrors[$attribute][$key]) {
	case "1":
	  $return[] = gettext("Field should be empty to delete")." ".$key;
	  break;
	default:
	}
      }
    }
    return $return;
  }
  
  function get_binddn() {
    return $this->binddn;
  }

  function LDAPErrorHandler ($source, $options) {
    // timestamp for the error entry
    
    echo("Error caught by LDAPErrorHandler :<br>");
    echo(ldap_error($this->connection)."(".ldap_errno($this->connection).")<br>");
    echo "<br>";
    echo $this->errormessage;
    echo "<br>";
    echo "<br>";
    echo "Error in <b>".$source."</b> with the options :<br>";
	echo "<pre>";
	
	print_r( $options );

	echo 'Debug back trace:'."\n";
	
	print_r( debug_backtrace() );

	echo "</pre>";
    die();
    $dt = date("Y-m-d H:i:s (T)");

    // define an assoc array of error string
    // in reality the only entries we should
    // consider are 2,8,256,512 and 1024
    $errortype = array (
			1   =>  "Error",
			2   =>  "Warning",
			4   =>  "Parsing Error",
			8   =>  "Notice",
			16  =>  "Core Error",
			32  =>  "Core Warning",
			64  =>  "Compile Error",
			128 =>  "Compile Warning",
			256 =>  "User Error",
			512 =>  "User Warning",
			1024=>  "User Notice"
			);
    // set of errors for which a var trace will be saved
    $user_errors = array(E_USER_ERROR, E_USER_WARNING, E_USER_NOTICE);
    
    $err = "<errorentry>\n";
    $err .= "\t<datetime>".$dt."</datetime>\n";
    $err .= "\t<errornum>".$errno."</errornum>\n";
    $err .= "\t<errortype>".$errortype[$errno]."</errortype>\n";
    $err .= "\t<errormsg>".$errmsg."</errormsg>\n";
    $err .= "\t<scriptname>".$filename."</scriptname>\n";
    $err .= "\t<scriptlinenum>".$linenum."</scriptlinenum>\n";
    
    if (in_array($errno, $user_errors))
      $err .= "\t<vartrace>".wddx_serialize_value($vars,"Variables")."</vartrace>\n";
    $err .= "</errorentry>\n\n";
    
    // for testing
    echo $err;
    
    // save to the error log, and e-mail me if there is a critical user error
    //error_log($err, 3, "/usr/local/php4/error.log");
    //if ($errno == E_USER_ERROR)
    //  mail("hide@address.com","Critical User Error",$err);
  }
}
?>
Return current item: LDAPted