phpldapadmin/lib/import_functions.php

619 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();
}
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();
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 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;
}
}