605 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			PHP
		
	
	
	
			
		
		
	
	
			605 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			PHP
		
	
	
	
| <?php
 | |
| /**
 | |
|  * Classes and functions for importing data to LDAP
 | |
|  *
 | |
|  * These classes provide differnet import formats.
 | |
|  *
 | |
|  * @author The phpLDAPadmin development team
 | |
|  * @package phpLDAPadmin
 | |
|  * @see import.php and import_form.php
 | |
|  */
 | |
| 
 | |
| /**
 | |
|  * Importer Class
 | |
|  *
 | |
|  * This class serves as a top level importer class, which will return
 | |
|  * the correct Import class.
 | |
|  *
 | |
|  * @package phpLDAPadmin
 | |
|  * @subpackage Import
 | |
|  */
 | |
| class Importer {
 | |
| 	# Server ID that the export is linked to
 | |
| 	private $server_id;
 | |
| 	# Import Type
 | |
| 	private $template_id;
 | |
| 	private $template;
 | |
| 
 | |
| 	public function __construct($server_id,$template_id) {
 | |
| 		$this->server_id = $server_id;
 | |
| 		$this->template_id = $template_id;
 | |
| 
 | |
| 		$this->accept();
 | |
| 	}
 | |
| 
 | |
| 	static function types() {
 | |
| 		$type = array();
 | |
| 
 | |
| 		$details = ImportLDIF::getType();
 | |
| 		$type[$details['type']] = $details;
 | |
| 
 | |
| 		return $type;
 | |
| 	}
 | |
| 
 | |
| 	private function accept() {
 | |
| 		switch($this->template_id) {
 | |
| 			case 'LDIF':
 | |
| 				$this->template = new ImportLDIF($this->server_id);
 | |
| 				break;
 | |
| 
 | |
| 			default:
 | |
| 				die();
 | |
| 		}
 | |
| 
 | |
| 		$this->template->accept();
 | |
| 	}
 | |
| 
 | |
| 	public function getTemplate() {
 | |
| 		return $this->template;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Import Class
 | |
|  *
 | |
|  * This abstract classes provides all the common methods and variables for the
 | |
|  * custom import classes.
 | |
|  *
 | |
|  * @package phpLDAPadmin
 | |
|  * @subpackage Import
 | |
|  */
 | |
| abstract class Import {
 | |
| 	protected $server_id = null;
 | |
| 	protected $input = null;
 | |
| 	protected $source = array();
 | |
| 
 | |
| 	public function __construct($server_id) {
 | |
| 		$this->server_id = $server_id;
 | |
| 	}
 | |
| 
 | |
| 	public function accept() {
 | |
| 		if (get_request('ldif','REQUEST')) {
 | |
| 			$this->input = explode("\n",get_request('ldif','REQUEST'));
 | |
| 			$this->source['name'] = 'STDIN';
 | |
| 			$this->source['size'] = strlen(get_request('ldif','REQUEST'));
 | |
| 
 | |
| 		} elseif (isset($_FILES['ldif_file']) && is_array($_FILES['ldif_file']) && ! $_FILES['ldif_file']['error']) {
 | |
| 			$input = file_get_contents($_FILES['ldif_file']['tmp_name']);
 | |
| 			$this->input = preg_split("/\n|\r\n|\r/",$input);
 | |
| 			$this->source['name'] = $_FILES['ldif_file']['name'];
 | |
| 			$this->source['size'] = $_FILES['ldif_file']['size'];
 | |
| 
 | |
| 		} else {
 | |
| 			system_message(array(
 | |
| 				'title'=>_('No import input'),
 | |
| 				'body'=>_('You must either upload a file or provide an import in the text box.'),
 | |
| 				'type'=>'error'),sprintf('cmd.php?cmd=import_form&server_id=%s',get_request('server_id','REQUEST')));
 | |
| 
 | |
| 			die();
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	public function getSource($attr) {
 | |
| 		if (isset($this->source[$attr]))
 | |
| 			return $this->source[$attr];
 | |
| 		else
 | |
| 			return null;
 | |
| 	}
 | |
| 
 | |
| 	# @todo integrate hooks
 | |
| 	public function LDAPimport() {
 | |
| 		$template = $this->getTemplate();
 | |
| 		$server = $this->getServer();
 | |
| 
 | |
| 		switch ($template->getType()) {
 | |
| 			case 'add': 
 | |
| 				return $server->add($template->getDN(),$template->getLDAPadd());
 | |
| 
 | |
| 			case 'modify':
 | |
| 				return $server->modify($template->getDN(),$template->getLDAPmodify());
 | |
| 
 | |
| 			case 'moddn':
 | |
| 			case 'modrdn':
 | |
| 				return $server->rename($template->getDN(),$template->modrdn['newrdn'],$template->modrdn['newsuperior'],$template->modrdn['deleteoldrdn']);
 | |
| 
 | |
| 			default:
 | |
| 				debug_dump_backtrace(sprintf('Unknown template type %s',$template->getType()),1);
 | |
| 		}
 | |
| 
 | |
| 		return true;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Import entries from LDIF
 | |
|  *
 | |
|  * The LDIF spec is described by RFC2849
 | |
|  * http://www.ietf.org/rfc/rfc2849.txt
 | |
|  *
 | |
|  * @package phpLDAPadmin
 | |
|  * @subpackage Import
 | |
|  */
 | |
| class ImportLDIF extends Import {
 | |
| 	private $_currentLineNumber = 0;
 | |
| 	private $_currentLine = '';
 | |
| 	private $template;
 | |
| 	public $error = array();
 | |
| 
 | |
| 	static public function getType() {
 | |
| 		return array('type'=>'LDIF','description' => _('LDIF import'),'extension'=>'ldif');
 | |
| 	}
 | |
| 
 | |
| 	protected function getTemplate() {
 | |
| 		return $this->template;
 | |
| 	}
 | |
| 
 | |
| 	protected function getServer() {
 | |
| 		return $_SESSION[APPCONFIG]->getServer($this->server_id);
 | |
| 	}
 | |
| 
 | |
| 	public function readEntry() {
 | |
| 		static $haveVersion = false;
 | |
| 
 | |
| 		if ($lines = $this->nextLines()) {
 | |
| 
 | |
| 			# If we have a version line.
 | |
| 			if (! $haveVersion && preg_match('/^version:/',$lines[0])) {
 | |
| 				list($text,$version) = $this->getAttrValue(array_shift($lines));
 | |
| 
 | |
| 				if ($version != 1)
 | |
| 					return $this->error(sprintf('%s %s',_('LDIF import only supports version 1'),$version),$lines);
 | |
| 
 | |
| 				$haveVersion = true;
 | |
| 				$lines = $this->nextLines();
 | |
| 			}
 | |
| 
 | |
| 			$server = $this->getServer();
 | |
| 
 | |
| 			# The first line should be the DN
 | |
| 			if (preg_match('/^dn:/',$lines[0])) {
 | |
| 				list($text,$dn) = $this->getAttrValue(array_shift($lines));
 | |
| 
 | |
| 				# The second line should be our changetype
 | |
| 				if (preg_match('/^changetype:[ ]*(delete|add|modrdn|moddn|modify)/i',$lines[0])) {
 | |
| 					$attrvalue = $this->getAttrValue($lines[0]);
 | |
| 					$changetype = $attrvalue[1];
 | |
| 					array_shift($lines);
 | |
| 
 | |
| 				} else
 | |
| 					$changetype = 'add';
 | |
| 
 | |
| 				$this->template = new Template($this->server_id,null,null,$changetype);
 | |
| 
 | |
| 				switch ($changetype) {
 | |
| 					case 'add':
 | |
| 						$rdn = get_rdn($dn);
 | |
| 						$container = $server->getContainer($dn);
 | |
| 
 | |
| 						$this->template->setContainer($container);
 | |
| 						$this->template->accept();
 | |
| 
 | |
| 						$this->getAddDetails($lines);
 | |
| 						$this->template->setRDNAttributes($rdn);
 | |
| 
 | |
| 						return $this->template;
 | |
| 
 | |
| 						break;
 | |
| 
 | |
| 					case 'modify':
 | |
| 						if (! $server->dnExists($dn))
 | |
| 							return $this->error(sprintf('%s %s',_('DN does not exist'),$dn),$lines);
 | |
| 
 | |
| 						$this->template->setDN($dn);
 | |
| 						$this->template->accept();
 | |
| 
 | |
| 						return $this->getModifyDetails($lines);
 | |
| 
 | |
| 						break;
 | |
| 
 | |
| 					case 'moddn':
 | |
| 					case 'modrdn':
 | |
| 						if (! $server->dnExists($dn))
 | |
| 							return $this->error(sprintf('%s %s',_('DN does not exist'),$dn),$lines);
 | |
| 
 | |
| 						$this->template->setDN($dn);
 | |
| 						$this->template->accept();
 | |
| 
 | |
| 						return $this->getModRDNAttributes($lines);
 | |
| 
 | |
| 						break;
 | |
| 
 | |
| 					default:
 | |
| 						if (! $server->dnExists($dn))
 | |
| 							return $this->error(_('Unkown change type'),$lines);
 | |
| 				}
 | |
| 
 | |
| 			} else
 | |
| 				return $this->error(_('A valid dn line is required'),$lines);
 | |
| 
 | |
| 		} else
 | |
| 			return false;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Get the Attribute and Decoded Value
 | |
| 	 */
 | |
| 	private function getAttrValue($line) {
 | |
| 		list($attr,$value) = explode(':',$line,2);
 | |
| 
 | |
| 		# Get the DN
 | |
| 		if (substr($value,0,1) == ':')
 | |
| 			$value = base64_decode(trim(substr($value,1)));
 | |
| 		else
 | |
| 			$value = trim($value);
 | |
| 
 | |
| 		return array($attr,$value);
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Get the lines of the next entry
 | |
| 	 *
 | |
| 	 * @return The lines (unfolded) of the next entry
 | |
| 	 */
 | |
| 	private function nextLines() {
 | |
| 		$current = array();
 | |
| 		$endEntryFound = false;
 | |
| 
 | |
| 		if ($this->hasMoreEntries() && ! $this->eof()) {
 | |
| 			# The first line is the DN one
 | |
| 			$current[0]= trim($this->_currentLine);
 | |
| 
 | |
| 			# While we end on a blank line, fetch the attribute lines
 | |
| 			$count = 0;
 | |
| 			while (! $this->eof() && ! $endEntryFound) {
 | |
| 				# Fetch the next line
 | |
| 				$this->nextLine();
 | |
| 
 | |
| 				/* If the next line begin with a space, we append it to the current row
 | |
| 				 * else we push it into the array (unwrap)*/
 | |
| 				if ($this->isWrappedLine())
 | |
| 					$current[$count] .= trim($this->_currentLine);
 | |
| 				elseif ($this->isCommentLine()) {}
 | |
| 				# Do nothing
 | |
| 				elseif (! $this->isBlankLine())
 | |
| 					$current[++$count] = trim($this->_currentLine);
 | |
| 				else
 | |
| 					$endEntryFound = true;
 | |
| 			}
 | |
| 
 | |
| 			# Return the LDIF entry array
 | |
| 			return $current;
 | |
| 
 | |
| 		} else
 | |
| 			return array();
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Private method to check if there is more entries in the input.
 | |
| 	 *
 | |
| 	 * @return boolean true if an entry was found, false otherwise.
 | |
| 	 */
 | |
| 	private function hasMoreEntries() {
 | |
| 		$entry_found = false;
 | |
| 
 | |
| 		while (! $this->eof() && ! $entry_found) {
 | |
| 			# If it's a comment or blank line, switch to the next line
 | |
| 			if ($this->isCommentLine() || $this->isBlankLine()) {
 | |
| 				# Do nothing
 | |
| 				$this->nextLine();
 | |
| 
 | |
| 			} else {
 | |
| 				$this->_currentDnLine = $this->_currentLine;
 | |
| 				$this->dnLineNumber = $this->_currentLineNumber;
 | |
| 				$entry_found = true;
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		return $entry_found;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Helper method to switch to the next line
 | |
| 	 */
 | |
| 	private function nextLine() {
 | |
| 		$this->_currentLineNumber++;
 | |
| 		$this->_currentLine = array_shift($this->input);
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Check if it's a comment line.
 | |
| 	 *
 | |
| 	 * @return boolean true if it's a comment line,false otherwise
 | |
| 	 */
 | |
| 	private function isCommentLine() {
 | |
| 		return substr(trim($this->_currentLine),0,1) == '#' ? true : false;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Check if it's a wrapped line.
 | |
| 	 *
 | |
| 	 * @return boolean true if it's a wrapped line,false otherwise
 | |
| 	 */
 | |
| 	private function isWrappedLine() {
 | |
| 		return substr($this->_currentLine,0,1) == ' ' ? true : false;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Check if is the current line is a blank line.
 | |
| 	 *
 | |
| 	 * @return boolean if it is a blank line,false otherwise.
 | |
| 	 */
 | |
| 	private function isBlankLine() {
 | |
| 		return(trim($this->_currentLine) == '') ? true : false;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Returns true if we reached the end of the input.
 | |
| 	 *
 | |
| 	 * @return boolean true if it's the end of file, false otherwise.
 | |
| 	 */
 | |
| 	public function eof() {
 | |
| 		return count($this->input) > 0 ? false : true;
 | |
| 	}
 | |
| 
 | |
| 	private function error($msg,$data) {
 | |
| 		$this->error['message'] = sprintf('%s [%s]',$msg,$this->template ? $this->template->getDN() : '');
 | |
| 		$this->error['line'] = $this->_currentLineNumber;
 | |
| 		$this->error['data'] = $data;
 | |
| 		$this->error['changetype'] = $this->template ? $this->template->getType() : 'Not set';
 | |
| 
 | |
| 		return false;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Method to retrieve the attribute value of a ldif line,
 | |
| 	 * and get the base 64 decoded value if it is encoded
 | |
| 	 */
 | |
| 	private function getAttributeValue($value) {
 | |
| 		$return = '';
 | |
| 
 | |
| 		if (substr($value,0,1) == '<') {
 | |
| 			$url = trim(substr($value,1));
 | |
| 
 | |
| 			if (preg_match('^file://',$url)) {
 | |
| 				$filename = substr(trim($url),7);
 | |
| 
 | |
| 				if ($fh = @fopen($filename,'rb')) {
 | |
| 					if (! $return = @fread($fh,filesize($filename)))
 | |
| 						return $this->error(_('Unable to read file.'),$value);
 | |
| 
 | |
| 					@fclose($fh);
 | |
| 
 | |
| 				} else
 | |
| 					return $this->error(_('Unable to read file.'),$value);
 | |
| 
 | |
| 			} else
 | |
| 				return $this->error(_('The url attribute value should begin with file://.'),$value);
 | |
| 
 | |
| 		# It's a string
 | |
| 		} else
 | |
| 			$return = $value;
 | |
| 
 | |
| 		return trim($return);
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Build the attributes array when the change type is add.
 | |
| 	 */
 | |
| 	private function getAddDetails($lines) {
 | |
| 		foreach ($lines as $line) {
 | |
| 			list($attr,$value) = $this->getAttrValue($line);
 | |
| 
 | |
| 			if (is_null($attribute = $this->template->getAttribute($attr))) {
 | |
| 				$attribute = $this->template->addAttribute($attr,array('values'=>array($value)));
 | |
| 				$attribute->justModified();
 | |
| 
 | |
| 			} else
 | |
| 				if ($attribute->hasBeenModified())
 | |
| 					$attribute->addValue($value);
 | |
| 				else
 | |
| 					$attribute->setValue(array($value));
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Build the attributes array for the entry when the change type is modify
 | |
| 	 */
 | |
| 	private function getModifyDetails($lines) {
 | |
| 		if (! count($lines))
 | |
| 			return $this->error(_('Missing attributes for'),$lines);
 | |
| 
 | |
| 		# While the array is not empty
 | |
| 		while (count($lines)) {
 | |
| 			$processline = false;
 | |
| 			$deleteattr = false;
 | |
| 
 | |
| 			# Get the current line with the action
 | |
| 			$currentLine = array_shift($lines);
 | |
| 			$attrvalue = $this->getAttrValue($currentLine);
 | |
| 			$action_attribute = $attrvalue[0];
 | |
| 			$action_attribute_value = $attrvalue[1];
 | |
| 
 | |
| 			if (! in_array($action_attribute,array('add','delete','replace')))
 | |
| 				return $this->error(_('Missing modify command add, delete or replace'),array_merge(array($currentLine),$lines));
 | |
| 
 | |
| 			$processline = true;
 | |
| 			switch ($action_attribute) {
 | |
| 				case 'add':
 | |
| 
 | |
| 					break;
 | |
| 
 | |
| 				case 'delete':
 | |
| 					$attribute = $this->template->getAttribute($action_attribute_value);
 | |
| 
 | |
| 					if (is_null($attribute))
 | |
| 						return $this->error(sprintf('%s %s',_('Attempting to delete a non existent attribute'),$action_attribute_value),
 | |
| 							array_merge(array($currentLine),$lines));
 | |
| 
 | |
| 					$deleteattr = true;
 | |
| 
 | |
| 					break;
 | |
| 
 | |
| 				case 'replace':
 | |
| 					$attribute = $this->template->getAttribute($action_attribute_value);
 | |
| 
 | |
| 					if (is_null($attribute))
 | |
| 						return $this->error(sprintf('%s %s',_('Attempting to replace a non existant attribute'),$action_attribute_value),
 | |
| 							array_merge(array($currentLine),$lines));
 | |
| 
 | |
| 					break;
 | |
| 
 | |
| 				default:
 | |
| 					debug_dump_backtrace(sprintf('Unknown action %s',$action_attribute),1);
 | |
| 			}
 | |
| 
 | |
| 			# Fetch the attribute for the following line
 | |
| 			$currentLine = array_shift($lines);
 | |
| 
 | |
| 			while ($processline && trim($currentLine) && (trim($currentLine) != '-')) {
 | |
| 				$processline = false;
 | |
| 
 | |
| 				# If there is a valid line
 | |
| 				if (preg_match('/:/',$currentLine)) {
 | |
| 					$attrvalue = $this->getAttrValue($currentLine);
 | |
| 					$attr = $attrvalue[0];
 | |
| 					$attribute_value_part = $attrvalue[1];
 | |
| 
 | |
| 					# Check that it correspond to the one specified before
 | |
| 					if ($attr == $action_attribute_value) {
 | |
| 						# Get the value part of the attribute
 | |
| 						$attribute_value = $this->getAttributeValue($attribute_value_part);
 | |
| 
 | |
| 						$attribute = $this->template->getAttribute($attr);
 | |
| 
 | |
| 						# This should be a add/replace operation
 | |
| 						switch ($action_attribute) {
 | |
| 							case 'add':
 | |
| 								if (is_null($attribute))
 | |
| 									$attribute = $this->template->addAttribute($attr,array('values'=>array($attribute_value_part)));
 | |
| 								else
 | |
| 									$attribute->addValue($attribute_value_part,-1);
 | |
| 
 | |
| 								$attribute->justModified();
 | |
| 
 | |
| 								break;
 | |
| 
 | |
| 							case 'delete':
 | |
| 								$deleteattr = false;
 | |
| 
 | |
| 								if ($key = array_search($attribute_value_part,$attribute->getValues()))
 | |
| 									$attribute->delValue($key);
 | |
| 								else
 | |
| 									return $this->error(sprintf('%s %s',_('Value to delete does not exist in DN'),$attribute_value_part),
 | |
| 										array_merge(array($currentLine),$lines));
 | |
| 
 | |
| 
 | |
| 								break;
 | |
| 
 | |
| 							case 'replace':
 | |
| 								if ($attribute->hasBeenModified())
 | |
| 									$attribute->addValue($attribute_value_part,-1);
 | |
| 								else
 | |
| 									$attribute->setValue(array($attribute_value_part));
 | |
| 
 | |
| 								break;
 | |
| 
 | |
| 							default:
 | |
| 								debug_dump_backtrace(sprintf('Unexpected operation %s',$action_attribute));
 | |
| 						}
 | |
| 
 | |
| 					} else
 | |
| 						return $this->error(sprintf(_('The attribute to modify doesn\'t match the one specified by %s.'),$action_attribute),
 | |
| 							array_merge(array($currentLine),$lines));
 | |
| 
 | |
| 				} else
 | |
| 					return $this->error(sprintf('%s %s',_('Attribute not valid'),$currentLine),
 | |
| 						array_merge(array($currentLine),$lines));
 | |
| 
 | |
| 				$currentLine = array_shift($lines);
 | |
| 				if (trim($currentLine))
 | |
| 					$processline = true;
 | |
| 			}
 | |
| 
 | |
| 			if ($action_attribute == 'delete' && $deleteattr)
 | |
| 				$attribute->setValue(array());
 | |
| 
 | |
| 		}
 | |
| 
 | |
| 		return $this->template;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Build the attributes for the entry when the change type is modrdn
 | |
| 	 */
 | |
| 	function getModRDNAttributes($lines) {
 | |
| 		$server = $this->getServer();
 | |
| 		$attrs = array();
 | |
| 
 | |
| 		# MODRDN MODDN should only be 2 or 3 lines.
 | |
| 		if (count($lines) != 2 && count($lines) !=3)
 | |
| 			return $this->error(_('Invalid entry'),$lines);
 | |
| 
 | |
| 		else {
 | |
| 			$currentLine = array_shift($lines);
 | |
| 
 | |
| 			# First we need to check if there is an new rdn specified
 | |
| 			if (preg_match('/^newrdn:(:?)/',$currentLine)) {
 | |
| 
 | |
| 				$attrvalue = $this->getAttrValue($currentLine);
 | |
| 				$attrs['newrdn'] = $attrvalue[1];
 | |
| 
 | |
| 				$currentLine = array_shift($lines);
 | |
| 
 | |
| 				if (preg_match('/^deleteoldrdn:[ ]*(0|1)/',$currentLine)) {
 | |
| 					$attrvalue = $this->getAttrValue($currentLine);
 | |
| 					$attrs['deleteoldrdn'] = $attrvalue[1];
 | |
| 
 | |
| 					# Switch to the possible new superior attribute
 | |
| 					if (count($lines)) {
 | |
| 						$currentLine = array_shift($lines);
 | |
| 
 | |
| 						# then the possible new superior attribute
 | |
| 						if (preg_match('/^newsuperior:/',$currentLine)) {
 | |
| 							$attrvalue = $this->getAttrValue($currentLine);
 | |
| 							$attrs['newsuperior'] = $attrvalue[1];
 | |
| 
 | |
| 						} else
 | |
| 							return $this->error(_('A valid newsuperior attribute should be specified'),$lines);
 | |
| 
 | |
| 					} else
 | |
| 						$attrs['newsuperior'] = $server->getContainer($this->template->getDN());
 | |
| 
 | |
| 				} else
 | |
| 					return $this->error(_('A valid deleteoldrdn attribute should be specified'),$lines);
 | |
| 
 | |
| 			} else
 | |
| 				return $this->error(_('A valid newrdn attribute should be specified'),$lines);
 | |
| 		}
 | |
| 
 | |
| 		# Well do something out of the ordinary here, since our template doesnt handle mod[r]dn yet.
 | |
| 		$this->template->modrdn = $attrs;
 | |
| 		return $this->template;
 | |
| 	}
 | |
| }
 | |
| ?>
 |