summaryrefslogtreecommitdiffstats
path: root/modules-available/sysconfig/inc
diff options
context:
space:
mode:
authorSimon Rettberg2016-05-11 19:00:30 +0200
committerSimon Rettberg2016-05-11 19:00:30 +0200
commit1cc1c2ed092c46eb35893c1d85accb24cf43d7f9 (patch)
tree95c1302f4a1ae441e174a1dca64133e2873f8297 /modules-available/sysconfig/inc
parentAdd PhpStorm prefs (diff)
downloadslx-admin-1cc1c2ed092c46eb35893c1d85accb24cf43d7f9.tar.gz
slx-admin-1cc1c2ed092c46eb35893c1d85accb24cf43d7f9.tar.xz
slx-admin-1cc1c2ed092c46eb35893c1d85accb24cf43d7f9.zip
Still working in modularization cleanup and refinement
Diffstat (limited to 'modules-available/sysconfig/inc')
-rw-r--r--modules-available/sysconfig/inc/configmodule.inc.php515
-rw-r--r--modules-available/sysconfig/inc/configmodule/adauth.inc.php75
-rw-r--r--modules-available/sysconfig/inc/configmodule/branding.inc.php56
-rw-r--r--modules-available/sysconfig/inc/configmodule/customodule.inc.php56
-rw-r--r--modules-available/sysconfig/inc/configmodule/ldapauth.inc.php77
-rw-r--r--modules-available/sysconfig/inc/configmodule/sshconfig.inc.php63
-rw-r--r--modules-available/sysconfig/inc/configtgz.inc.php317
7 files changed, 1159 insertions, 0 deletions
diff --git a/modules-available/sysconfig/inc/configmodule.inc.php b/modules-available/sysconfig/inc/configmodule.inc.php
new file mode 100644
index 00000000..7b92ff89
--- /dev/null
+++ b/modules-available/sysconfig/inc/configmodule.inc.php
@@ -0,0 +1,515 @@
+<?php
+
+/**
+ * Base class for config modules
+ */
+abstract class ConfigModule
+{
+
+ /**
+ * @var array list of known module types
+ */
+ private static $moduleTypes = false;
+
+ private $moduleId = 0;
+ private $moduleArchive = false;
+ private $moduleTitle = false;
+ private $currentVersion = 0;
+ protected $moduleData = false;
+
+ /**
+ * Load all known config module types. This is done
+ * by including *.inc.php from inc/configmodule/. The
+ * files there should in turn call ConfigModule::registerModule()
+ * to register themselves.
+ */
+ public static function loadDb()
+ {
+ if (self::$moduleTypes !== false)
+ return;
+ self::$moduleTypes = array();
+ foreach (glob(dirname(__FILE__) . '/configmodule/*.inc.php', GLOB_NOSORT) as $file) {
+ require_once $file;
+ }
+ }
+
+ /**
+ * Get all known config module types.
+ *
+ * @return array list of modules
+ */
+ public static function getList()
+ {
+ self::loadDb();
+ return self::$moduleTypes;
+ }
+
+ /**
+ * Add a known configuration module. Every inc/configmodule/*.inc.php should call this.
+ *
+ * @param string $id Identifier for the module.
+ * The module class must be called ConfigModule_{$id}, the wizard start class {$id}_Start.
+ * The wizard's classes should be located in modules/sysconfig/addmodule_{$id_lowercase}.inc.php
+ * @param string $title Title of this module type
+ * @param string $description Description for this module type
+ * @param string $group Title for group this module type belongs to
+ * @param bool $unique Can only one such module be added to a config?
+ * @param int $sortOrder Lower comes first, alphabetical ordering otherwiese
+ */
+ public static function registerModule($id, $title, $description, $group, $unique, $sortOrder = 0)
+ {
+ if (isset(self::$moduleTypes[$id]))
+ Util::traceError("Config Module $id already registered!");
+ $moduleClass = 'ConfigModule_' . $id;
+ $wizardClass = $id . '_Start';
+ if (!class_exists($moduleClass))
+ Util::traceError("Class $moduleClass does not exist!");
+ if (get_parent_class($moduleClass) !== 'ConfigModule')
+ Util::traceError("$moduleClass does not have ConfigModule as its parent!");
+ self::$moduleTypes[$id] = array(
+ 'title' => $title,
+ 'description' => $description,
+ 'group' => $group,
+ 'unique' => $unique,
+ 'sortOrder' => $sortOrder,
+ 'moduleClass' => $moduleClass,
+ 'wizardClass' => $wizardClass
+ );
+ }
+
+ /**
+ * Get fresh instance of ConfigModule subclass for given module type.
+ *
+ * @param string $moduleType name of module type
+ * @return \ConfigModule module instance
+ */
+ public static function getInstance($moduleType)
+ {
+ self::loadDb();
+ if (!isset(self::$moduleTypes[$moduleType]))
+ return false;
+ return new self::$moduleTypes[$moduleType]['moduleClass'];
+ }
+
+ /**
+ * Get module instance from id.
+ *
+ * @param int $moduleId module id to get
+ * @return ConfigModule The requested module from DB, or false on error
+ */
+ public static function get($moduleId)
+ {
+ $ret = Database::queryFirst("SELECT title, moduletype, filepath, contents, version FROM configtgz_module "
+ . " WHERE moduleid = :moduleid LIMIT 1", array('moduleid' => $moduleId));
+ if ($ret === false)
+ return false;
+ $instance = self::getInstance($ret['moduletype']);
+ if ($instance === false)
+ return false;
+ $instance->currentVersion = $ret['version'];
+ $instance->moduleArchive = $ret['filepath'];
+ $instance->moduleData = json_decode($ret['contents'], true);
+ $instance->moduleId = $moduleId;
+ $instance->moduleTitle = $ret['title'];
+ return $instance;
+ }
+
+ /**
+ * Get module instances from module type.
+ *
+ * @param int $moduleType module type to get
+ * @return array The requested modules from DB, or false on error
+ */
+ public static function getAll($moduleType = false)
+ {
+ if ($moduleType === false) {
+ $ret = Database::simpleQuery("SELECT moduleid, title, moduletype, filepath, contents, version FROM configtgz_module");
+ } else {
+ $ret = Database::simpleQuery("SELECT moduleid, title, moduletype, filepath, contents, version FROM configtgz_module "
+ . " WHERE moduletype = :moduletype", array('moduletype' => $moduleType));
+ }
+ if ($ret === false)
+ return false;
+ $list = array();
+ while ($row = $ret->fetch(PDO::FETCH_ASSOC)) {
+ $instance = self::getInstance($row['moduletype']);
+ if ($instance === false)
+ return false;
+ $instance->currentVersion = $row['version'];
+ $instance->moduleArchive = $row['filepath'];
+ $instance->moduleData = json_decode($row['contents'], true);
+ $instance->moduleId = $row['moduleid'];
+ $instance->moduleTitle = $row['title'];
+ $list[] = $instance;
+ }
+ return $list;
+ }
+
+ /**
+ * Get the module version.
+ *
+ * @return int module version
+ */
+ protected abstract function moduleVersion();
+
+ /**
+ * Validate the module's configuration.
+ *
+ * @return boolean ok or not
+ */
+ protected abstract function validateConfig();
+
+ /**
+ * Set module specific data.
+ *
+ * @param string $key key, name or id of data being set
+ * @param mixed $value Module specific data
+ * @return boolean true if data was successfully set, false otherwise (i.e. invalid data being set)
+ */
+ public abstract function setData($key, $value);
+
+ /**
+ * Get module specific data.
+ * Can be overridden by modules.
+ *
+ * @param string $key key, name or id of data to get, or false to get the raw moduleData array
+ * @return mixed Module specific data
+ */
+ public function getData($key)
+ {
+ if ($key === false)
+ return $this->moduleData;
+ if (!is_array($this->moduleData) || !isset($this->moduleData[$key]))
+ return false;
+ return $this->moduleData[$key];
+ }
+
+ /**
+ * Module specific version of generate.
+ *
+ * @param string $tgz File name of tgz module to write final output to
+ * @param string $parent Parent task of this task
+ * @return array|boolean true if generation is completed immediately,
+ * a task struct if some task needs to be run for generation,
+ * false on error
+ */
+ protected abstract function generateInternal($tgz, $parent);
+
+ private final function createFileName()
+ {
+ return CONFIG_TGZ_LIST_DIR . '/modules/'
+ . $this->moduleType() . '_id-' . $this->moduleId . '__' . mt_rand() . '-' . time() . '.tgz';
+ }
+
+ /**
+ * Get module id (in db)
+ *
+ * @return int id
+ */
+ public final function id()
+ {
+ return $this->moduleId;
+ }
+
+ /**
+ * Get module title.
+ *
+ * @return string
+ */
+ public final function title()
+ {
+ return $this->moduleTitle;
+ }
+
+ /**
+ * Get module archive file name.
+ *
+ * @return string tgz file absolute path
+ */
+ public final function archive()
+ {
+ return $this->moduleArchive;
+ }
+
+ /**
+ * Get the module type.
+ *
+ * @return string module type
+ */
+ public final function moduleType()
+ {
+ $name = get_class($this);
+ if ($name === false)
+ Util::traceError('ConfigModule::moduleType: get_class($this) returned false!');
+ // ConfigModule_*
+ if (!preg_match('/^ConfigModule_(\w+)$/', $name, $out))
+ Util::traceError('ConfigModule::moduleType: get_class($this) returned "' . $name . '"');
+ return $out[1];
+ }
+
+ /**
+ * Insert this config module into DB. Only
+ * valid if the object was created using the creating constructor,
+ * not if the instance was created using a database entry (static get method).
+ *
+ * @param string $title display name of the module
+ * @return boolean true if inserted successfully, false if module config is invalid
+ */
+ public final function insert($title)
+ {
+ if ($this->moduleId !== 0)
+ Util::traceError('ConfigModule::insert called when moduleId != 0');
+ if (!$this->validateConfig())
+ return false;
+ $this->moduleTitle = $title;
+ // Insert
+ Database::exec("INSERT INTO configtgz_module (title, moduletype, filepath, contents, version, status) "
+ . " VALUES (:title, :type, '', :contents, :version, :status)", array(
+ 'title' => $title,
+ 'type' => $this->moduleType(),
+ 'contents' => json_encode($this->moduleData),
+ 'version' => 0,
+ 'status' => 'MISSING'
+ ));
+ $this->moduleId = Database::lastInsertId();
+ if (!is_numeric($this->moduleId))
+ Util::traceError('Inserting new config module into DB did not yield a numeric insert id');
+ $this->moduleArchive = $this->createFileName();
+ Database::exec("UPDATE configtgz_module SET filepath = :path WHERE moduleid = :moduleid LIMIT 1", array(
+ 'path' => $this->moduleArchive,
+ 'moduleid' => $this->moduleId
+ ));
+ return true;
+ }
+
+ /**
+ * Update the given module in database. This will not regenerate
+ * the module's tgz.
+ *
+ * @return boolean true on success, false otherwise
+ */
+ public final function update($title)
+ {
+ if ($this->moduleId === 0)
+ Util::traceError('ConfigModule::update called when moduleId == 0');
+ if (empty($title))
+ $title = $this->moduleTitle;
+ if (!$this->validateConfig())
+ return false;
+ // Update
+ Database::exec("UPDATE configtgz_module SET title = :title, contents = :contents, status = :status "
+ . " WHERE moduleid = :moduleid LIMIT 1", array(
+ 'moduleid' => $this->moduleId,
+ 'title' => $title,
+ 'contents' => json_encode($this->moduleData),
+ 'status' => 'OUTDATED'
+ ));
+ return true;
+ }
+
+ /**
+ * Generate the module's tgz, don't wait for completion.
+ * Updating the database etc. will happen later through a callback.
+ *
+ * @param boolean $deleteOnError if true, the db entry will be deleted if generation failed
+ * @param string $parent Parent task of this task
+ * @param int $timeoutMs maximum time in milliseconds we wait for completion
+ * @return string|boolean task id if deferred generation was started,
+ * true if generation succeeded (without using a task or within $timeoutMs)
+ * false on error
+ */
+ public final function generate($deleteOnError, $parent = NULL, $timeoutMs = 0)
+ {
+ if ($this->moduleId === 0 || $this->moduleTitle === false)
+ Util::traceError('ConfigModule::generateAsync called on uninitialized/uninserted module!');
+ $tmpTgz = '/tmp/bwlp-id-' . $this->moduleId . '_' . mt_rand() . '_' . time() . '.tgz';
+ $ret = $this->generateInternal($tmpTgz, $parent);
+ // Wait for generation if requested
+ if ($timeoutMs > 0 && isset($ret['id']) && !Taskmanager::isFinished($ret))
+ $ret = Taskmanager::waitComplete($ret, $timeoutMs);
+ if ($ret === true || (isset($ret['statusCode']) && $ret['statusCode'] === TASK_FINISHED)) {
+ // Already Finished
+ if (file_exists($this->moduleArchive) && !file_exists($tmpTgz))
+ $tmpTgz = false; // If generateInternal succeeded and there's no tmpTgz, it means the file didn't have to be updated
+ return $this->markUpdated($tmpTgz);
+ }
+ if (!is_array($ret) || !isset($ret['id']) || Taskmanager::isFailed($ret)) {
+ if (is_array($ret)) // Failed
+ Taskmanager::addErrorMessage($ret);
+ if ($deleteOnError)
+ $this->delete();
+ else
+ $this->markFailed();
+ return false;
+ }
+ // Still running, add callback
+ TaskmanagerCallback::addCallback($ret, 'cbConfModCreated', array(
+ 'moduleid' => $this->moduleId,
+ 'deleteOnError' => $deleteOnError,
+ 'tmpTgz' => $tmpTgz
+ ));
+ return $ret['id'];
+ }
+
+ /**
+ * Delete the module.
+ */
+ public final function delete()
+ {
+ if ($this->moduleId === 0)
+ Util::traceError('ConfigModule::delete called with invalid module id!');
+ $ret = Database::exec("DELETE FROM configtgz_module WHERE moduleid = :moduleid LIMIT 1", array(
+ 'moduleid' => $this->moduleId
+ ), true) !== false;
+ if ($ret !== false) {
+ if ($this->moduleArchive)
+ Taskmanager::submit('DeleteFile', array('file' => $this->moduleArchive), true);
+ $this->moduleId = 0;
+ $this->moduleData = false;
+ $this->moduleTitle = false;
+ $this->moduleArchive = false;
+ }
+ return $ret;
+ }
+
+ private final function markUpdated($tmpTgz)
+ {
+ if ($this->moduleId === 0)
+ Util::traceError('ConfigModule::markUpdated called with invalid module id!');
+ if ($this->moduleArchive === false)
+ $this->moduleArchive = $this->createFileName();
+ // Move file
+ if ($tmpTgz === false) {
+ if (!file_exists($this->moduleArchive)) {
+ EventLog::failure('ConfigModule::markUpdated for "' . $this->moduleTitle . '" called with no tmpTgz and no existing tgz!');
+ $this->markFailed();
+ return false;
+ }
+ } else {
+ $task = Taskmanager::submit('MoveFile', array(
+ 'source' => $tmpTgz,
+ 'destination' => $this->moduleArchive
+ ));
+ $task = Taskmanager::waitComplete($task, 5000);
+ if (Taskmanager::isFailed($task) || !Taskmanager::isFinished($task)) {
+ if (!API && !AJAX)
+ Taskmanager::addErrorMessage($task);
+ else
+ EventLog::failure('Could not move ' . $tmpTgz . ' to ' . $this->moduleArchive . ' while generating "' . $this->moduleTitle . '"');
+ $this->markFailed();
+ return false;
+ }
+ }
+ // Update DB entry
+ $retval = Database::exec("UPDATE configtgz_module SET filepath = :filename, version = :version, status = 'OK' WHERE moduleid = :id LIMIT 1", array(
+ 'id' => $this->moduleId,
+ 'filename' => $this->moduleArchive,
+ 'version' => $this->moduleVersion()
+ )) !== false;
+ // Update related config.tgzs
+ $configs = ConfigTgz::getAllForModule($this->moduleId);
+ foreach ($configs as $config) {
+ $config->markOutdated();
+ $config->generate();
+ }
+ return $retval;
+ }
+
+ private final function markFailed()
+ {
+ if ($this->moduleId === 0)
+ Util::traceError('ConfigModule::markFailed called with invalid module id!');
+ if ($this->moduleArchive === false)
+ $this->moduleArchive = $this->createFileName();
+ if (!file_exists($this->moduleArchive))
+ $status = 'MISSING';
+ else
+ $status = 'OUTDATED';
+ return Database::exec("UPDATE configtgz_module SET filepath = :filename, status = :status WHERE moduleid = :id LIMIT 1", array(
+ 'id' => $this->moduleId,
+ 'filename' => $this->moduleArchive,
+ 'status' => $status
+ )) !== false;
+ }
+
+ ################# Callbacks ##############
+
+ /**
+ * Event callback for when the server ip changed.
+ * Override this if you need to handle this, otherwise
+ * the base implementation does nothing.
+ */
+ public function event_serverIpChanged()
+ {
+ // Do::Nothing()
+ }
+
+ ##################### STATIC CALLBACKS #####################
+
+ /**
+ * Will be called if the server's IP address changes. The event will be propagated
+ * to all config module classes so action can be taken if appropriate.
+ */
+ public static function serverIpChanged()
+ {
+ self::loadDb();
+ $list = self::getAll();
+ foreach ($list as $mod) {
+ $mod->event_serverIpChanged();
+ }
+ }
+
+ /**
+ * Called when (re)generating a config module failed, so we can
+ * update the status in the DB and add a server log entry.
+ *
+ * @param array $task
+ * @param array $args contains 'moduleid' and optionally 'deleteOnError' and 'tmpTgz'
+ */
+ public static function generateFailed($task, $args)
+ {
+ if (!isset($args['moduleid']) || !is_numeric($args['moduleid'])) {
+ EventLog::warning('Ignoring generateFailed event as it has no moduleid assigned.');
+ return;
+ }
+ $module = self::get($args['moduleid']);
+ if ($module === false) {
+ EventLog::warning('generateFailed callback for module id ' . $args['moduleid'] . ', but no instance could be generated.');
+ return;
+ }
+ if (isset($task['data']['error']))
+ $error = $task['data']['error'];
+ elseif (isset($task['data']['messages']))
+ $error = $task['data']['messages'];
+ else
+ $error = '';
+ EventLog::failure("Generating module '" . $module->moduleTitle . "' failed.", $error);
+ if ($args['deleteOnError'])
+ $module->delete();
+ else
+ $module->markFailed();
+ }
+
+ /**
+ * (Re)generating a config module succeeded. Update db entry.
+ *
+ * @param array $args contains 'moduleid' and optionally 'deleteOnError' and 'tmpTgz'
+ */
+ public static function generateSucceeded($args)
+ {
+ if (!isset($args['moduleid']) || !is_numeric($args['moduleid'])) {
+ EventLog::warning('Ignoring generateSucceeded event as it has no moduleid assigned.');
+ return;
+ }
+ $module = self::get($args['moduleid']);
+ if ($module === false) {
+ EventLog::warning('generateSucceeded callback for module id ' . $args['moduleid'] . ', but no instance could be generated.');
+ return;
+ }
+ if (isset($args['tmpTgz']))
+ $module->markUpdated($args['tmpTgz']);
+ else
+ $module->markUpdated(false);
+ }
+
+}
diff --git a/modules-available/sysconfig/inc/configmodule/adauth.inc.php b/modules-available/sysconfig/inc/configmodule/adauth.inc.php
new file mode 100644
index 00000000..a03be43c
--- /dev/null
+++ b/modules-available/sysconfig/inc/configmodule/adauth.inc.php
@@ -0,0 +1,75 @@
+<?php
+
+ConfigModule::registerModule(
+ ConfigModule_AdAuth::MODID, // ID
+ Dictionary::translateFile('config-module', 'adAuth_title'), // Title
+ Dictionary::translateFile('config-module', 'adAuth_description'), // Description
+ Dictionary::translateFile('config-module', 'group_authentication'), // Group
+ true // Only one per config?
+);
+
+class ConfigModule_AdAuth extends ConfigModule
+{
+
+ const MODID = 'AdAuth';
+ const VERSION = 1;
+
+ private static $REQUIRED_FIELDS = array('server', 'searchbase', 'binddn');
+ private static $OPTIONAL_FIELDS = array('bindpw', 'home', 'ssl', 'fingerprint', 'certificate', 'homeattr');
+
+ protected function generateInternal($tgz, $parent)
+ {
+ Trigger::ldadp($this->id(), $parent);
+ $config = $this->moduleData;
+ if (isset($config['certificate']) && !is_string($config['certificate'])) {
+ unset($config['certificate']);
+ }
+ if (preg_match('/^([^\:]+)\:(\d+)$/', $config['server'], $out)) {
+ $config['server'] = $out[1];
+ $config['adport'] = $out[2];
+ } else {
+ if (isset($config['certificate'])) {
+ $config['adport'] = 636;
+ } else {
+ $config['adport'] = 389;
+ }
+ }
+ $config['parentTask'] = $parent;
+ $config['failOnParentFail'] = false;
+ $config['proxyip'] = Property::getServerIp();
+ $config['proxyport'] = 3100 + $this->id();
+ $config['filename'] = $tgz;
+ $config['moduleid'] = $this->id();
+ return Taskmanager::submit('CreateLdapConfig', $config);
+ }
+
+ protected function moduleVersion()
+ {
+ return self::VERSION;
+ }
+
+ protected function validateConfig()
+ {
+ // Check if required fields are filled
+ return Util::hasAllKeys($this->moduleData, self::$REQUIRED_FIELDS);
+ }
+
+ public function setData($key, $value)
+ {
+ if (!in_array($key, self::$REQUIRED_FIELDS) && !in_array($key, self::$OPTIONAL_FIELDS))
+ return false;
+ $this->moduleData[$key] = $value;
+ return true;
+ }
+
+ // ############## Callbacks #############################
+
+ /**
+ * Server IP changed - rebuild all AD modules.
+ */
+ public function event_serverIpChanged()
+ {
+ $this->generate(false);
+ }
+
+}
diff --git a/modules-available/sysconfig/inc/configmodule/branding.inc.php b/modules-available/sysconfig/inc/configmodule/branding.inc.php
new file mode 100644
index 00000000..479b406c
--- /dev/null
+++ b/modules-available/sysconfig/inc/configmodule/branding.inc.php
@@ -0,0 +1,56 @@
+<?php
+
+ConfigModule::registerModule(
+ ConfigModule_Branding::MODID, // ID
+ Dictionary::translateFile('config-module', 'branding_title'), // Title
+ Dictionary::translateFile('config-module', 'branding_description'), // Description
+ Dictionary::translateFile('config-module', 'group_branding'), // Group
+ true // Only one per config?
+);
+
+class ConfigModule_Branding extends ConfigModule
+{
+
+ const MODID = 'Branding';
+ const VERSION = 1;
+
+ private $tmpFile = false;
+
+ protected function generateInternal($tgz, $parent)
+ {
+ if (!$this->validateConfig()) {
+ return $this->archive() !== false && file_exists($this->archive()); // No new temp file given, old archive still exists, pretend it worked...
+ }
+ $task = Taskmanager::submit('MoveFile', array(
+ 'source' => $this->tmpFile,
+ 'destination' => $tgz,
+ 'parentTask' => $parent,
+ 'failOnParentFail' => false
+ ));
+ return $task;
+ }
+
+ protected function moduleVersion()
+ {
+ return self::VERSION;
+ }
+
+ protected function validateConfig()
+ {
+ return $this->tmpFile !== false && file_exists($this->tmpFile);
+ }
+
+ public function setData($key, $value)
+ {
+ if ($key !== 'tmpFile' || !is_string($value) || !file_exists($value))
+ return false;
+ $this->tmpFile = $value;
+ return true;
+ }
+
+ public function getData($key)
+ {
+ return false;
+ }
+
+}
diff --git a/modules-available/sysconfig/inc/configmodule/customodule.inc.php b/modules-available/sysconfig/inc/configmodule/customodule.inc.php
new file mode 100644
index 00000000..09b621cc
--- /dev/null
+++ b/modules-available/sysconfig/inc/configmodule/customodule.inc.php
@@ -0,0 +1,56 @@
+<?php
+
+ConfigModule::registerModule(
+ ConfigModule_CustomModule::MODID, // ID
+ Dictionary::translateFile('config-module', 'custom_title'), // Title
+ Dictionary::translateFile('config-module', 'custom_description'), // Description
+ Dictionary::translateFile('config-module', 'group_generic'), // Group
+ false, // Only one per config?
+ 100 // Sort order
+);
+
+class ConfigModule_CustomModule extends ConfigModule
+{
+ const MODID = 'CustomModule';
+ const VERSION = 1;
+
+ private $tmpFile = false;
+
+ protected function generateInternal($tgz, $parent)
+ {
+ if (!$this->validateConfig()) {
+ return $this->archive() !== false && file_exists($this->archive()); // No new temp file given, old archive still exists, pretend it worked...
+ }
+ $task = Taskmanager::submit('MoveFile', array(
+ 'source' => $this->tmpFile,
+ 'destination' => $tgz,
+ 'parentTask' => $parent,
+ 'failOnParentFail' => false
+ ));
+ return $task;
+ }
+
+ protected function moduleVersion()
+ {
+ return self::VERSION;
+ }
+
+ protected function validateConfig()
+ {
+ return $this->tmpFile !== false && file_exists($this->tmpFile);
+ }
+
+ public function setData($key, $value)
+ {
+ if ($key !== 'tmpFile' || !file_exists($value))
+ return false;
+ $this->tmpFile = $value;
+ return true;
+ }
+
+ public function getData($key)
+ {
+ return false;
+ }
+
+}
diff --git a/modules-available/sysconfig/inc/configmodule/ldapauth.inc.php b/modules-available/sysconfig/inc/configmodule/ldapauth.inc.php
new file mode 100644
index 00000000..0f386033
--- /dev/null
+++ b/modules-available/sysconfig/inc/configmodule/ldapauth.inc.php
@@ -0,0 +1,77 @@
+<?php
+
+ConfigModule::registerModule(
+ ConfigModule_LdapAuth::MODID, // ID
+ Dictionary::translateFile('config-module', 'ldapAuth_title'), // Title
+ Dictionary::translateFile('config-module', 'ldapAuth_description'), // Description
+ Dictionary::translateFile('config-module', 'group_authentication'), // Group
+ true // Only one per config?
+);
+
+class ConfigModule_LdapAuth extends ConfigModule
+{
+
+ const MODID = 'LdapAuth';
+ const VERSION = 1;
+
+ private static $REQUIRED_FIELDS = array('server', 'searchbase');
+ private static $OPTIONAL_FIELDS = array('binddn', 'bindpw', 'home', 'ssl', 'fingerprint', 'certificate');
+
+ protected function generateInternal($tgz, $parent)
+ {
+ Trigger::ldadp($this->id(), $parent);
+ $config = $this->moduleData;
+ if (isset($config['certificate']) && !is_string($config['certificate'])) {
+ unset($config['certificate']);
+ }
+ if (preg_match('/^([^\:]+)\:(\d+)$/', $config['server'], $out)) {
+ $config['server'] = $out[1];
+ $config['adport'] = $out[2]; // sic!
+ } else {
+ if (isset($config['certificate'])) {
+ $config['adport'] = 636;
+ } else {
+ $config['adport'] = 389;
+ }
+ }
+ $config['parentTask'] = $parent;
+ $config['failOnParentFail'] = false;
+ $config['proxyip'] = Property::getServerIp();
+ $config['proxyport'] = 3100 + $this->id();
+ $config['filename'] = $tgz;
+ $config['moduleid'] = $this->id();
+ $config['plainldap'] = true;
+ return Taskmanager::submit('CreateLdapConfig', $config);
+ }
+
+ protected function moduleVersion()
+ {
+ return self::VERSION;
+ }
+
+ protected function validateConfig()
+ {
+ // Check if required fields are filled
+ return Util::hasAllKeys($this->moduleData, self::$REQUIRED_FIELDS);
+ }
+
+ public function setData($key, $value)
+ {
+ if (!in_array($key, self::$REQUIRED_FIELDS) && !in_array($key, self::$OPTIONAL_FIELDS))
+ return false;
+ $this->moduleData[$key] = $value;
+ return true;
+ }
+
+ // ############## Callbacks #############################
+
+ /**
+ * Server IP changed - rebuild all LDAP modules.
+ */
+ public function event_serverIpChanged()
+ {
+ error_log('Calling generate on ' . $this->title());
+ $this->generate(false);
+ }
+
+}
diff --git a/modules-available/sysconfig/inc/configmodule/sshconfig.inc.php b/modules-available/sysconfig/inc/configmodule/sshconfig.inc.php
new file mode 100644
index 00000000..b1d58153
--- /dev/null
+++ b/modules-available/sysconfig/inc/configmodule/sshconfig.inc.php
@@ -0,0 +1,63 @@
+<?php
+
+ConfigModule::registerModule(
+ ConfigModule_SshConfig::MODID, // ID
+ Dictionary::translateFile('config-module', 'sshconfig_title'), // Title
+ Dictionary::translateFile('config-module', 'sshconfig_description'), // Description
+ Dictionary::translateFile('config-module', 'group_sshconfig'), // Group
+ true // Only one per config?
+);
+
+class ConfigModule_SshConfig extends ConfigModule
+{
+ const MODID = 'SshConfig';
+ const VERSION = 1;
+
+ protected function generateInternal($tgz, $parent)
+ {
+ if (!$this->validateConfig())
+ return false;
+ $config = $this->moduleData + array(
+ 'filename' => $tgz,
+ 'failOnParentFail' => false,
+ 'parent' => $parent
+ );
+ // Create config module, which will also check if the pubkey is valid
+ return Taskmanager::submit('SshdConfigGenerator', $config);
+ }
+
+ protected function moduleVersion()
+ {
+ return self::VERSION;
+ }
+
+ protected function validateConfig()
+ {
+ return isset($this->moduleData['publicKey']) && isset($this->moduleData['allowPasswordLogin']) && isset($this->moduleData['listenPort']);
+ }
+
+ public function setData($key, $value)
+ {
+ switch ($key) {
+ case 'publicKey':
+ break;
+ case 'allowPasswordLogin':
+ if ($value === true || $value === 'yes')
+ $value = 'yes';
+ elseif ($value === false || $value === 'no')
+ $value = 'no';
+ else
+ return false;
+ break;
+ case 'listenPort':
+ if (!is_numeric($value) || $value < 1 || $value > 65535)
+ return false;
+ break;
+ default:
+ return false;
+ }
+ $this->moduleData[$key] = $value;
+ return true;
+ }
+
+}
diff --git a/modules-available/sysconfig/inc/configtgz.inc.php b/modules-available/sysconfig/inc/configtgz.inc.php
new file mode 100644
index 00000000..b51d2787
--- /dev/null
+++ b/modules-available/sysconfig/inc/configtgz.inc.php
@@ -0,0 +1,317 @@
+<?php
+
+class ConfigTgz
+{
+
+ private $configId = 0;
+ private $configTitle = false;
+ private $file = false;
+ private $modules = array();
+
+ private function __construct()
+ {
+ ;
+ }
+
+ public function id()
+ {
+ return $this->configId;
+ }
+
+ public function title()
+ {
+ return $this->configTitle;
+ }
+
+ public function areAllModulesUpToDate()
+ {
+ if (!$this->configId > 0)
+ Util::traceError('ConfigTgz::areAllModulesUpToDate called on un-inserted config.tgz!');
+ foreach ($this->modules as $module) {
+ if (!empty($module['filepath']) && file_exists($module['filepath'])) {
+ if ($module['status'] !== 'OK')
+ return false;
+ } else {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public function isActive()
+ {
+ return readlink(CONFIG_HTTP_DIR . '/default/config.tgz') === $this->file;
+ }
+
+ public function getModuleIds()
+ {
+ $ret = array();
+ foreach ($this->modules as $module) {
+ $ret[] = $module['moduleid'];
+ }
+ return $ret;
+ }
+
+ public function update($title, $moduleIds)
+ {
+ if (!is_array($moduleIds))
+ return false;
+ $this->configTitle = $title;
+ $this->modules = array();
+ // Get all modules to put in config
+ $idstr = '0'; // Passed directly in query. Make sure no SQL injection is possible
+ foreach ($moduleIds as $module) {
+ $idstr .= ',' . (int)$module; // Casting to int should make it safe
+ }
+ $res = Database::simpleQuery("SELECT moduleid, filepath, status FROM configtgz_module WHERE moduleid IN ($idstr)");
+ // Delete old connections
+ Database::exec("DELETE FROM configtgz_x_module WHERE configid = :configid", array('configid' => $this->configId));
+ // Make connection
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ Database::exec("INSERT INTO configtgz_x_module (configid, moduleid) VALUES (:configid, :moduleid)", array(
+ 'configid' => $this->configId,
+ 'moduleid' => $row['moduleid']
+ ));
+ $this->modules[] = $row;
+ }
+ // Update name
+ Database::exec("UPDATE configtgz SET title = :title, status = :status WHERE configid = :configid LIMIT 1", array(
+ 'configid' => $this->configId,
+ 'title' => $title,
+ 'status' => 'OUTDATED'
+ ));
+ return true;
+ }
+
+ public static function insert($title, $moduleIds)
+ {
+ if (!is_array($moduleIds))
+ return false;
+ $instance = new ConfigTgz;
+ $instance->configTitle = $title;
+ // Create output file name (config.tgz)
+ do {
+ $instance->file = CONFIG_TGZ_LIST_DIR . '/config-' . Util::sanitizeFilename($instance->configTitle) . '-' . mt_rand() . '-' . time() . '.tgz';
+ } while (file_exists($instance->file));
+ Database::exec("INSERT INTO configtgz (title, filepath, status) VALUES (:title, :filepath, :status)", array(
+ 'title' => $instance->configTitle,
+ 'filepath' => $instance->file,
+ 'status' => 'MISSING'
+ ));
+ $instance->configId = Database::lastInsertId();
+ $instance->modules = array();
+ // Get all modules to put in config
+ $idstr = '0'; // Passed directly in query. Make sure no SQL injection is possible
+ foreach ($moduleIds as $module) {
+ $idstr .= ',' . (int)$module; // Casting to int should make it safe
+ }
+ $res = Database::simpleQuery("SELECT moduleid, filepath, status FROM configtgz_module WHERE moduleid IN ($idstr)");
+ // Make connection
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ Database::exec("INSERT INTO configtgz_x_module (configid, moduleid) VALUES (:configid, :moduleid)", array(
+ 'configid' => $instance->configId,
+ 'moduleid' => $row['moduleid']
+ ));
+ $instance->modules[] = $row;
+ }
+ return $instance;
+ }
+
+ public static function get($configId)
+ {
+ $ret = Database::queryFirst("SELECT configid, title, filepath FROM configtgz WHERE configid = :configid", array(
+ 'configid' => $configId
+ ));
+ if ($ret === false)
+ return false;
+ $instance = new ConfigTgz;
+ $instance->configId = $ret['configid'];
+ $instance->configTitle = $ret['title'];
+ $instance->file = $ret['filepath'];
+ $ret = Database::simpleQuery("SELECT moduleid, filepath, status FROM configtgz_x_module "
+ . " INNER JOIN configtgz_module USING (moduleid) "
+ . " WHERE configid = :configid", array('configid' => $instance->configId));
+ $instance->modules = array();
+ while ($row = $ret->fetch(PDO::FETCH_ASSOC)) {
+ $instance->modules[] = $row;
+ }
+ return $instance;
+ }
+
+ public static function getAllForModule($moduleId)
+ {
+ $res = Database::simpleQuery("SELECT configid, title, filepath FROM configtgz_x_module "
+ . " INNER JOIN configtgz USING (configid) "
+ . " WHERE moduleid = :moduleid", array(
+ 'moduleid' => $moduleId
+ ));
+ if ($res === false)
+ return false;
+ $list = array();
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $instance = new ConfigTgz;
+ $instance->configId = $row['configid'];
+ $instance->configTitle = $row['title'];
+ $instance->file = $row['filepath'];
+ $innerRes = Database::simpleQuery("SELECT moduleid, filepath, status FROM configtgz_x_module "
+ . " INNER JOIN configtgz_module USING (moduleid) "
+ . " WHERE configid = :configid", array('configid' => $instance->configId));
+ $instance->modules = array();
+ while ($innerRow = $innerRes->fetch(PDO::FETCH_ASSOC)) {
+ $instance->modules[] = $innerRow;
+ }
+ $list[] = $instance;
+ }
+ return $list;
+ }
+
+ /**
+ * Called when (re)generating a config tgz failed, so we can
+ * update the status in the DB and add a server log entry.
+ *
+ * @param array $task
+ * @param array $args contains 'configid' and optionally 'deleteOnError'
+ */
+ public static function generateFailed($task, $args)
+ {
+ if (!isset($args['configid']) || !is_numeric($args['configid'])) {
+ EventLog::warning('Ignoring generateFailed event as it has no configid assigned.');
+ return;
+ }
+ $config = self::get($args['configid']);
+ if ($config === false) {
+ EventLog::warning('generateFailed callback for config id ' . $args['configid'] . ', but no instance could be generated.');
+ return;
+ }
+ if (isset($task['data']['error']))
+ $error = $task['data']['error'];
+ elseif (isset($task['data']['messages']))
+ $error = $task['data']['messages'];
+ else
+ $error = '';
+ EventLog::failure("Generating config.tgz '" . $config->configTitle . "' failed.", $error);
+ if ($args['deleteOnError'])
+ $config->delete();
+ else
+ $config->markFailed();
+ }
+
+ /**
+ * (Re)generating a config tgz succeeded. Update db entry.
+ *
+ * @param array $args contains 'configid' and optionally 'deleteOnError'
+ */
+ public static function generateSucceeded($args)
+ {
+ if (!isset($args['configid']) || !is_numeric($args['configid'])) {
+ EventLog::warning('Ignoring generateSucceeded event as it has no configid assigned.');
+ return;
+ }
+ $config = self::get($args['configid']);
+ if ($config === false) {
+ EventLog::warning('generateSucceeded callback for config id ' . $args['configid'] . ', but no instance could be generated.');
+ return;
+ }
+ $config->markUpdated();
+ }
+
+ /**
+ *
+ * @param type $deleteOnError
+ * @param type $timeoutMs
+ * @return string - OK (success)
+ * - OUTDATED (updating failed, but old version still exists)
+ * - MISSING (failed and no old version available)
+ */
+ public function generate($deleteOnError = false, $timeoutMs = 0)
+ {
+ if (!($this->configId > 0) || !is_array($this->modules) || $this->file === false)
+ Util::traceError ('configId <= 0 or modules not array in ConfigTgz::rebuild()');
+ $files = array();
+ foreach ($this->modules as $module) {
+ if (!empty($module['filepath']) && file_exists($module['filepath']))
+ $files[] = $module['filepath'];
+ }
+ // Hand over to tm
+ $task = Taskmanager::submit('RecompressArchive', array(
+ 'inputFiles' => $files,
+ 'outputFile' => $this->file
+ ));
+ // Wait for completion
+ if ($timeoutMs > 0 && !Taskmanager::isFailed($task) && !Taskmanager::isFinished($task))
+ $task = Taskmanager::waitComplete($task, $timeoutMs);
+ if ($task === true || (isset($task['statusCode']) && $task['statusCode'] === TASK_FINISHED)) {
+ // Success!
+ $this->markUpdated();
+ return true;
+ }
+ if (!is_array($task) || !isset($task['id']) || Taskmanager::isFailed($task)) {
+ // Failed...
+ Taskmanager::addErrorMessage($task);
+ if (!$deleteOnError)
+ $this->markFailed();
+ else
+ $this->delete();
+ return false;
+ }
+ // Still running, add callback
+ TaskmanagerCallback::addCallback($task, 'cbConfTgzCreated', array(
+ 'configid' => $this->configId,
+ 'deleteOnError' => $deleteOnError
+ ));
+ return $task['id'];
+ }
+
+ public function delete()
+ {
+ if ($this->configId === 0)
+ Util::traceError('ConfigTgz::delete called with invalid config id!');
+ $ret = Database::exec("DELETE FROM configtgz WHERE configid = :configid LIMIT 1", array(
+ 'configid' => $this->configId
+ ), true) !== false;
+ if ($ret !== false) {
+ if ($this->file !== false)
+ Taskmanager::submit('DeleteFile', array('file' => $this->file), true);
+ $this->configId = 0;
+ $this->modules = false;
+ $this->file = false;
+ }
+ return $ret;
+ }
+
+ public function markOutdated()
+ {
+ if ($this->configId === 0)
+ Util::traceError('ConfigTgz::markOutdated called with invalid config id!');
+ return $this->mark('OUTDATED');
+ }
+
+ private function markUpdated()
+ {
+ if ($this->configId === 0)
+ Util::traceError('ConfigTgz::markUpdated called with invalid config id!');
+ Event::activeConfigChanged();
+ if ($this->areAllModulesUpToDate())
+ return $this->mark('OK');
+ return $this->mark('OUTDATED');
+ }
+
+ private function markFailed()
+ {
+ if ($this->configId === 0)
+ Util::traceError('ConfigTgz::markFailed called with invalid config id!');
+ if ($this->file === false || !file_exists($this->file))
+ return $this->mark('MISSING');
+ return $this->mark('OUTDATED');
+ }
+
+ private function mark($status)
+ {
+ Database::exec("UPDATE configtgz SET status = :status WHERE configid = :configid LIMIT 1", array(
+ 'configid' => $this->configId,
+ 'status' => $status
+ ));
+ return $status;
+ }
+
+}