From e1dc0d3c99217504de2ac8467156274786efc0bd Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Thu, 9 Oct 2014 16:01:11 +0200 Subject: Big load of changes - Added callback functionality for taskmanager tasks. You can launch a task and define a callback function to be run when the task finished. This requires activating the cronjob - Added cron functionality: Add cronjob that calls the cron api every 5 minutes to use it. (See cron.inc.php) - Added eventlog - Added missing translations - Merged main-menu-login and main-menu-logout --- inc/configmodule.inc.php | 24 ++++++++++- inc/database.inc.php | 2 +- inc/defaultdata.inc.php | 21 +++++++-- inc/event.inc.php | 96 +++++++++++++++++++++++++++++++++++++++++ inc/eventlog.inc.php | 23 +++++----- inc/permission.inc.php | 7 +-- inc/property.inc.php | 27 +++++++++--- inc/taskmanager.inc.php | 23 ++++++++-- inc/taskmanagercallback.inc.php | 43 ++++++++++++++++++ inc/trigger.inc.php | 93 ++++++++++++++++++++++++--------------- inc/user.inc.php | 41 ++++++++++++++---- inc/validator.inc.php | 50 ++++++++++++++------- 12 files changed, 362 insertions(+), 88 deletions(-) create mode 100644 inc/event.inc.php create mode 100644 inc/taskmanagercallback.inc.php (limited to 'inc') diff --git a/inc/configmodule.inc.php b/inc/configmodule.inc.php index c0838b5c..1788a53a 100644 --- a/inc/configmodule.inc.php +++ b/inc/configmodule.inc.php @@ -5,13 +5,13 @@ class ConfigModule public static function insertAdConfig($title, $server, $searchbase, $binddn, $bindpw, $home) { - // TODO: Lock table, race condition if about 500 admins insert a config at the same time + Database::exec("LOCK TABLE configtgz_module WRITE"); Database::exec("INSERT INTO configtgz_module (title, moduletype, filepath, contents) " . " VALUES (:title, 'AD_AUTH', '', '')", array('title' => $title)); $id = Database::lastInsertId(); if (!is_numeric($id)) Util::traceError('Inserting new AD config to DB did not yield a numeric insert id'); // Entry created, now try to get a free port for the proxy - $res = Database::simpleQuery("SELECT moduleid, contents FROM configtgz_module"); + $res = Database::simpleQuery("SELECT moduleid, contents FROM configtgz_module WHERE moduletype = 'AD_AUTH'"); $ports = array(); while ($row = $res->fetch(PDO::FETCH_ASSOC)) { if ($row['moduleid'] == $id) { @@ -42,12 +42,32 @@ class ConfigModule 'filename' => $moduleTgz, 'contents' => $data )); + Database::exec("UNLOCK TABLES"); // Add archive file name to array before returning it $ownEntry['moduleid'] = $id; $ownEntry['filename'] = $moduleTgz; return $ownEntry; } + /** + * Get all existing AD proxy configs. + * + * @return array array of ad configs in DB with fields: + * moduleid, filename, server, searchbase, binddn, bindpw, home, proxyport + */ + public static function getAdConfigs() + { + $res = Database::simpleQuery("SELECT moduleid, filepath, contents FROM configtgz_module WHERE moduletype = 'AD_AUTH'"); + $mods = array(); + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + $data = json_decode($row['contents'], true); + $data['moduleid'] = $row['moduleid']; + $data['filename'] = $row['filepath']; + $mods[] = $data; + } + return $mods; + } + public static function insertBrandingModule($title, $archive) { Database::exec("INSERT INTO configtgz_module (title, moduletype, filepath, contents) " diff --git a/inc/database.inc.php b/inc/database.inc.php index 0d48c23c..72b7d0d7 100644 --- a/inc/database.inc.php +++ b/inc/database.inc.php @@ -16,7 +16,7 @@ class Database */ public static function getExpectedSchemaVersion() { - return 5; + return 6; } public static function needSchemaUpdate() diff --git a/inc/defaultdata.inc.php b/inc/defaultdata.inc.php index 12a304f6..abc857bb 100644 --- a/inc/defaultdata.inc.php +++ b/inc/defaultdata.inc.php @@ -25,6 +25,7 @@ class DefaultData 2 => 20, // Internet access 3 => 100, // Timesync 4 => 10, // System config + 5 => 15, // Public Shared folder ); foreach ($cats as $cat => $sort) { Database::exec("INSERT IGNORE INTO cat_setting (catid, sortval) VALUES (:catid, :sortval)", array( @@ -94,7 +95,7 @@ class DefaultData 'catid' => '2', 'defaultvalue' => 'off', 'permissions' => '2', - 'validator' => 'list:off|on|auto|wpad' + 'validator' => 'list:off|on|auto' ), array( 'setting' => 'SLX_PROXY_PORT', @@ -137,11 +138,25 @@ class DefaultData 'defaultvalue' => '1200', 'permissions' => '2', 'validator' => 'regex:/^\d*$/' - ) + ), + array( + 'setting' => 'SLX_COMMON_SHARE_PATH', + 'catid' => '5', + 'defaultvalue' => '', + 'permissions' => '2', + 'validator' => 'function:networkShare' + ), + array( + 'setting' => 'SLX_COMMON_SHARE_AUTH', + 'catid' => '5', + 'defaultvalue' => 'guest', + 'permissions' => '2', + 'validator' => 'list:guest|user' + ), ); foreach ($data as $entry) { Database::exec("INSERT IGNORE INTO setting (setting, catid, defaultvalue, permissions, validator)" - . "VALUES (:setting, :catid, :defaultvalue, :permissions, :validator)"); + . "VALUES (:setting, :catid, :defaultvalue, :permissions, :validator)", $entry); } } diff --git a/inc/event.inc.php b/inc/event.inc.php new file mode 100644 index 00000000..6689e12f --- /dev/null +++ b/inc/event.inc.php @@ -0,0 +1,96 @@ + $type, - 'message' => $message + 'message' => $message, + 'details' => $details )); } - public static function failure($message) + public static function failure($message, $details = '') { - self::log('failure', $message); + self::log('failure', $message, $details); } - public static function warning($message) + public static function warning($message, $details = '') { - self::log('warning', $message); + self::log('warning', $message, $details); + Property::setLastWarningId(Database::lastInsertId()); } - public static function info($message) + public static function info($message, $details = '') { - self::log('info', $message); + self::log('info', $message, $details); + Property::setLastWarningId(Database::lastInsertId()); } } diff --git a/inc/permission.inc.php b/inc/permission.inc.php index f90319a4..d04e3c3b 100644 --- a/inc/permission.inc.php +++ b/inc/permission.inc.php @@ -3,9 +3,10 @@ class Permission { private static $permissions = array( - 'superadmin' => 1, - 'baseconfig_global' => 2, - 'baseconfig_local' => 4, + 'superadmin' => 1, // Can do everything + 'baseconfig_global' => 2, // Change configuration globally + 'baseconfig_local' => 4, // Change configuration for specifig groups/rooms + 'translation' => 8, // Can edit translations ); public static function get($permission) diff --git a/inc/property.inc.php b/inc/property.inc.php index 605d901d..e4b4340b 100644 --- a/inc/property.inc.php +++ b/inc/property.inc.php @@ -19,11 +19,11 @@ class Property private static function get($key, $default = false) { if (self::$cache === false) { - if (mt_rand(1, 20) === 10) { - Database::exec("DELETE FROM property WHERE dateline <> 0 AND dateline < UNIX_TIMESTAMP()"); - } - $res = Database::simpleQuery("SELECT name, value FROM property"); + $NOW = time(); + $res = Database::simpleQuery("SELECT name, dateline, value FROM property"); while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + if ($row['dateline'] != 0 && $row['dateline'] < $NOW) + continue; self::$cache[$row['name']] = $row['value']; } } @@ -37,7 +37,7 @@ class Property * * @param string $key key of value to set * @param type $value the value to store for $key - * @param int minage how long to keep this entry around at least, in minutes. 0 for infinite + * @param int $minage how long to keep this entry around at least, in minutes. 0 for infinite */ private static function set($key, $value, $minage = 0) { @@ -59,6 +59,8 @@ class Property public static function setServerIp($value) { + if ($value !== self::getServerIp()) + Event::serverIpChanged(); self::set('server-ip', $value); } @@ -113,6 +115,7 @@ class Property { return json_decode(self::get('vmstore-config'), true); } + public static function getVmStoreUrl() { $store = self::getVmStoreConfig(); @@ -136,15 +139,25 @@ class Property { return self::get('dl-' . $name); } - + public static function setDownloadTask($name, $taskId) { self::set('dl-' . $name, $taskId, 5); } - + public static function getCurrentSchemaVersion() { return self::get('webif-version'); } + public static function setLastWarningId($id) + { + self::set('last-warn-event-id', $id); + } + + public static function getLastWarningId() + { + return self::get('last-warn-event-id', 0); + } + } diff --git a/inc/taskmanager.inc.php b/inc/taskmanager.inc.php index 5813164a..528b3f78 100644 --- a/inc/taskmanager.inc.php +++ b/inc/taskmanager.inc.php @@ -28,7 +28,7 @@ class Taskmanager * @param string $task name of task to start * @param array $data data to pass to the task. the structure depends on the task. * @param boolean $async if true, the function will not wait for the reply of the taskmanager, which means - * the return value is just true (and you won't know if the task could acutally be started) + * the return value is just true (and you won't know if the task could acutally be started) * @return array struct representing the task status, or result of submit, false on communication error */ public static function submit($task, $data = false, $async = false) @@ -49,8 +49,7 @@ class Taskmanager if ($async) return true; $reply = self::readReply($seq); - if ($reply === false || !is_array($reply) || !isset($reply['id']) - || (isset($reply['statusCode']) && $reply['statusCode'] === NO_SUCH_TASK)) { + if ($reply === false || !is_array($reply) || !isset($reply['id']) || (isset($reply['statusCode']) && $reply['statusCode'] === NO_SUCH_TASK)) { self::addErrorMessage($reply); return false; } @@ -117,7 +116,7 @@ class Taskmanager /** * Check whether the given task can be considered failed. * - * @param mixed $task task id or struct representing task + * @param array $task struct representing task, obtained by ::status * @return boolean true if task failed, false if finished successfully or still waiting/running */ public static function isFailed($task) @@ -129,6 +128,22 @@ class Taskmanager return false; } + /** + * Check whether the given task is finished, i.e. either failed or succeeded, + * but is not running, still waiting for execution or simply unknown. + * + * @param array $task struct representing task, obtained by ::status + * @return boolean true if task failed or finished, false if waiting for execution or currently executing, no valid task, etc. + */ + public static function isFinished($task) + { + if (!is_array($task) || !isset($task['statusCode']) || !isset($task['id'])) + return false; + if ($task['statusCode'] === TASK_ERROR || $task['statusCode'] === TASK_FINISHED) + return true; + return false; + } + public static function addErrorMessage($task) { static $failure = false; diff --git a/inc/taskmanagercallback.inc.php b/inc/taskmanagercallback.inc.php new file mode 100644 index 00000000..3ef4745c --- /dev/null +++ b/inc/taskmanagercallback.inc.php @@ -0,0 +1,43 @@ + $task, + 'callback' => $callback + )); + } + + /** + * Result of trying to (re)launch ldadp. + */ + public static function ldadpStartup($task) + { + if (Taskmanager::isFailed($task)) + EventLog::warning("Could not start/stop LDAP-AD-Proxy instances", $task['data']['messages']); + } + +} diff --git a/inc/trigger.inc.php b/inc/trigger.inc.php index 0b31c7b3..73ad6ce8 100644 --- a/inc/trigger.inc.php +++ b/inc/trigger.inc.php @@ -10,7 +10,7 @@ */ class Trigger { - + /** * Compile iPXE pxelinux menu. Needs to be done whenever the server's IP * address changes. @@ -26,7 +26,7 @@ class Trigger return false; return $task['id']; } - + /** * Try to automatically determine the primary IP address of the server. * This only works if the server has either one public IPv4 address (and potentially @@ -84,53 +84,53 @@ class Trigger public static function ldadp($parent = NULL) { $res = Database::simpleQuery("SELECT moduleid, configtgz.filepath FROM configtgz_module" - . " INNER JOIN configtgz_x_module USING (moduleid)" - . " INNER JOIN configtgz USING (configid)" - . " WHERE moduletype = 'AD_AUTH'"); + . " INNER JOIN configtgz_x_module USING (moduleid)" + . " INNER JOIN configtgz USING (configid)" + . " WHERE moduletype = 'AD_AUTH'"); // TODO: Multiconfig support $id = array(); while ($row = $res->fetch(PDO::FETCH_ASSOC)) { if (readlink('/srv/openslx/www/boot/default/config.tgz') === $row['filepath']) { - $id[] = (int)$row['moduleid']; + $id[] = (int) $row['moduleid']; break; } } $task = Taskmanager::submit('LdadpLauncher', array( - 'ids' => $id, - 'parentTask' => $parent, - 'failOnParentFail' => false + 'ids' => $id, + 'parentTask' => $parent, + 'failOnParentFail' => false )); if (!isset($task['id'])) return false; return $task['id']; } - + /** * To be called if the server ip changes, as it's embedded in the AD module configs. * This will then recreate all AD tgz modules. */ public static function rebuildAdModules() { - $res = Database::simpleQuery("SELECT moduleid, filepath, content FROM configtgz_module" - . " WHERE moduletype = 'AD_AUTH'"); - if ($res->rowCount() === 0) - return; - $task = Taskmanager::submit('LdadpLauncher', array('ids' => array())); // Stop all running instances + $ads = ConfigModule::getAdConfigs(); + if (empty($ads)) + return; + $parent = isset($task['id']) ? $task['id'] : NULL; - while ($row = $res->fetch(PDO::FETCH_ASSOC)) { - $config = json_decode($row['contents']); - $config['proxyip'] = Property::getServerIp(); - $config['moduleid'] = $row['moduleid']; - $config['filename'] = $row['filepath']; - $config['parentTask'] = $parent; - $config['failOnParentFail'] = false; - $task = Taskmanager::submit('CreateAdConfig', $config); - $parent = isset($task['id']) ? $task['id'] : NULL; + foreach ($ads as $ad) { + $ad['parentTask'] = $parent; + $ad['failOnParentFail'] = false; + $task = Taskmanager::submit('CreateAdConfig', $ad); + if (isset($task['id'])) + $parent = $task['id']; + } + if (Taskmanager::waitComplete($parent, 2000) === false) { + EventLog::warning('Rebuilding LDAP-AD-Proxy config failed. AD Auth might not work properly.'); + sleep(1); } - + Trigger::ldadp(); } - + /** * Mount the VM store into the server. * @@ -139,17 +139,42 @@ class Trigger public static function mount() { $vmstore = Property::getVmStoreConfig(); - if (!is_array($vmstore)) return false; + if (!is_array($vmstore)) + return false; $storetype = $vmstore['storetype']; - if ($storetype === 'nfs') $addr = $vmstore['nfsaddr']; - if ($storetype === 'cifs') $addr = $vmstore['cifsaddr']; - if ($storetype === 'internal') $addr = 'null'; + if ($storetype === 'nfs') + $addr = $vmstore['nfsaddr']; + if ($storetype === 'cifs') + $addr = $vmstore['cifsaddr']; + if ($storetype === 'internal') + $addr = 'null'; return Taskmanager::submit('MountVmStore', array( - 'address' => $addr, - 'type' => 'images', - 'username' => $vmstore['cifsuser'], - 'password' => $vmstore['cifspasswd'] + 'address' => $addr, + 'type' => 'images', + 'username' => $vmstore['cifsuser'], + 'password' => $vmstore['cifspasswd'] )); } + + /** + * Check and process all callbacks + */ + public static function checkCallbacks() + { + $res = Database::simpleQuery("SELECT taskid, cbfunction FROM callback"); + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + $status = Taskmanager::status($row['taskid']); + if (Taskmanager::isFailed($status) || Taskmanager::isFinished($status)) + Database::exec("DELETE FROM callback WHERE taskid = :task AND cbfunction = :cb LIMIT 1", array('task' => $row['taskid'], 'cb' => $row['cbfunction'])); + if (Taskmanager::isFinished($status)) { + $func = array('TaskmanagerCallback', preg_replace('/\W/', '', $row['cbfunction'])); + if (!call_user_func_array('method_exists', $func)) { + Eventlog::warning("Callback {$row['cbfunction']} doesn't exist."); + } else { + call_user_func($func, $status); + } + } + } + } } diff --git a/inc/user.inc.php b/inc/user.inc.php index 19d17753..111849fe 100644 --- a/inc/user.inc.php +++ b/inc/user.inc.php @@ -4,6 +4,7 @@ require_once('inc/session.inc.php'); class User { + private static $user = false; public static function isLoggedIn() @@ -13,26 +14,29 @@ class User public static function getName() { - if (self::$user === false) return false; + if (!self::isLoggedIn()) + return false; return self::$user['fullname']; } public static function hasPermission($permission) { - if (self::$user === false) return false; + if (!self::isLoggedIn()) + return false; return (self::$user['permissions'] & Permission::get($permission)) != 0; } public static function load() { - if (self::isLoggedIn()) { + if (self::isLoggedIn()) return true; - } if (Session::load()) { $uid = Session::get('uid'); - if ($uid === false || $uid < 1) self::logout(); + if ($uid === false || $uid < 1) + self::logout(); self::$user = Database::queryFirst('SELECT * FROM user WHERE userid = :uid LIMIT 1', array(':uid' => $uid)); - if (self::$user === false) self::logout(); + if (self::$user === false) + self::logout(); return true; } return false; @@ -41,8 +45,10 @@ class User public static function login($user, $pass) { $ret = Database::queryFirst('SELECT userid, passwd FROM user WHERE login = :user LIMIT 1', array(':user' => $user)); - if ($ret === false) return false; - if (!Crypto::verify($pass, $ret['passwd'])) return false; + if ($ret === false) + return false; + if (!Crypto::verify($pass, $ret['passwd'])) + return false; Session::create(); Session::set('uid', $ret['userid']); Session::set('token', md5(rand() . time() . rand() . $_SERVER['REMOTE_ADDR'] . rand() . $_SERVER['REMOTE_PORT'] . rand() . $_SERVER['HTTP_USER_AGENT'])); @@ -57,5 +63,22 @@ class User exit(0); } -} + public static function setLastSeenEvent($eventid) + { + if (!self::isLoggedIn()) + return; + Database::exec("UPDATE user SET lasteventid = :eventid WHERE userid = :userid LIMIT 1", array( + 'eventid' => $eventid, + 'userid' => self::$user['userid'] + )); + self::$user['lasteventid'] = $eventid; + } + public static function getLastSeenEvent() + { + if (!self::isLoggedIn()) + return false; + return self::$user['lasteventid']; + } + +} diff --git a/inc/validator.inc.php b/inc/validator.inc.php index 88be14f2..944ac2ef 100644 --- a/inc/validator.inc.php +++ b/inc/validator.inc.php @@ -12,18 +12,20 @@ class Validator public static function validate($condition, $value) { - if (empty($condition)) return $value; + if (empty($condition)) + return $value; $data = explode(':', $condition, 2); switch ($data[0]) { - case 'regex': - if (preg_match($data[1], $value)) return $value; - return false; - case 'list': - return self::validateList($data[1], $value); - case 'function': - return self::$data[1]($value); - default: - Util::traceError('Unknown validation method: ' . $data[0]); + case 'regex': + if (preg_match($data[1], $value)) + return $value; + return false; + case 'list': + return self::validateList($data[1], $value); + case 'function': + return self::$data[1]($value); + default: + Util::traceError('Unknown validation method: ' . $data[0]); } } @@ -36,11 +38,29 @@ class Validator */ private static function linuxPassword($value) { - if (empty($value)) return ''; - if (preg_match('/^\$6\$.+\$./', $value)) return $value; + if (empty($value)) + return ''; + if (preg_match('/^\$6\$.+\$./', $value)) + return $value; return Crypto::hash6($value); } - + + /** + * "Fix" network share path for SMB shares where a backslash + * is used instead of a slash. + * @param string $value network path + * @return string cleaned up path + */ + private static function networkShare($value) + { + $value = trim($value); + if (substr($value, 0, 2) === '\\\\') + $value = str_replace('\\', '/', $value); + if (substr($value, 0, 2) === '//') + $value = str_replace(' ', '\\040', $value); + return $value; + } + /** * Validate value against list. * @param string $list The list as a string of items, separated by "|" @@ -50,9 +70,9 @@ class Validator private static function validateList($list, $value) { $list = explode('|', $list); - if (in_array($value, $list)) return $value; + if (in_array($value, $list)) + return $value; return false; } } - -- cgit v1.2.3-55-g7522