server_id = $server_id; $this->template_id = $template_id; $this->accept(); } public 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: system_message(array( 'title'=>sprintf('%s %s',_('Unknown Import Type'),$this->template_id), 'body'=>_('phpLDAPadmin has not been configured for that import type'), 'type'=>'warn'),'index.php'); 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(); public static 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 suppports 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(false,true); 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 * @param $line * @return array */ 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 array 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 * @param $value * @return bool|string */ 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 for'), $value); } @fclose($fh); } else { return $this->error(_('Unable to open file for'), $value); } } else { return $this->error(_('The url attribute value should begin with file:// for'), $value); } # It's a string } else { $return = $value; } return trim($return); } /** * Build the attributes array when the change type is add. * @param $lines */ 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 * @param $lines * @return bool */ 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 existant 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())) !== false) { $attribute->delValue($key); } else { return $this->error(sprintf('%s %s', _('Delete value doesnt 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('%s %s', _('The attribute to modify doesnt match the one specified by'), $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 * @param $lines * @return bool */ public 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; } }