From a8b0095b335780ae0bb950bc44021215d43a6b2d Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Mon, 12 Feb 2018 14:17:07 +0100 Subject: [permissionmanager] Introduce "location-aware" flag for permissions This flag tells wether the permission can be restricted to certain locations in a meaningful way. This flag has to be set in the permissions.json of the according module. For example, the permission to reboot the server cannot be limited to certain locations in a meaningful way, while the view of the client log can be filtered to only show log entries for clients in specific locations. --- .../serversetup-bwlp/permissions/permissions.json | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) (limited to 'modules-available/serversetup-bwlp/permissions/permissions.json') diff --git a/modules-available/serversetup-bwlp/permissions/permissions.json b/modules-available/serversetup-bwlp/permissions/permissions.json index 2166cf8e..6bae5422 100644 --- a/modules-available/serversetup-bwlp/permissions/permissions.json +++ b/modules-available/serversetup-bwlp/permissions/permissions.json @@ -1,5 +1,11 @@ -[ - "edit.address", - "edit.menu", - "download" -] \ No newline at end of file +{ + "download": { + "location-aware": false + }, + "edit.address": { + "location-aware": false + }, + "edit.menu": { + "location-aware": false + } +} \ No newline at end of file -- cgit v1.2.3-55-g7522 From 24815e16087b4b1b64e9f380d45d411af32daf42 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Mon, 9 Apr 2018 16:56:04 +0200 Subject: Permissions: Consistency: Make all pages require at least one permission to be accessible Closes #3340 --- modules-available/backup/page.inc.php | 1 + modules-available/exams/page.inc.php | 9 +-------- modules-available/locations/page.inc.php | 19 +++++++++++++++---- modules-available/news/page.inc.php | 10 ++-------- modules-available/news/permissions/permissions.json | 3 +++ modules-available/rebootcontrol/page.inc.php | 9 ++++++--- modules-available/serversetup-bwlp/page.inc.php | 6 ++++++ .../serversetup-bwlp/permissions/permissions.json | 3 +++ modules-available/statistics_reporting/page.inc.php | 1 + modules-available/sysconfig/page.inc.php | 2 +- modules-available/syslog/page.inc.php | 1 + modules-available/systemstatus/page.inc.php | 1 + modules-available/webinterface/page.inc.php | 4 ++++ .../webinterface/permissions/permissions.json | 3 +++ 14 files changed, 48 insertions(+), 24 deletions(-) (limited to 'modules-available/serversetup-bwlp/permissions/permissions.json') diff --git a/modules-available/backup/page.inc.php b/modules-available/backup/page.inc.php index 14522734..985f39ee 100644 --- a/modules-available/backup/page.inc.php +++ b/modules-available/backup/page.inc.php @@ -23,6 +23,7 @@ class Page_Backup extends Page User::assertPermission("restore"); $this->restore(); } + User::assertPermission('*'); } protected function doRender() diff --git a/modules-available/exams/page.inc.php b/modules-available/exams/page.inc.php index 51975052..15640a73 100644 --- a/modules-available/exams/page.inc.php +++ b/modules-available/exams/page.inc.php @@ -441,16 +441,9 @@ class Page_Exams extends Page protected function doRender() { - if (Request::isPost()) { - $examid = Request::post('examid', 0, 'int'); - } else if (Request::isGet()) { - $examid = Request::get('examid', 0, 'int'); - } else { - die('Neither Post nor Get Request send.'); - } - if ($this->action === "show") { + User::assertPermission('exams.view'); // General title and description Render::addTemplate('page-main-heading'); // List of defined exam periods diff --git a/modules-available/locations/page.inc.php b/modules-available/locations/page.inc.php index 80a8076b..4d5c6628 100644 --- a/modules-available/locations/page.inc.php +++ b/modules-available/locations/page.inc.php @@ -24,6 +24,9 @@ class Page_Locations extends Page } elseif ($this->action === 'updatesubnets') { $this->updateSubnets(); } + if (Request::isPost()) { + Util::redirect('?do=locations'); + } } private function updateSubnets() @@ -306,10 +309,16 @@ class Page_Locations extends Page protected function doRender() { - $getAction = Request::get('action'); - if (empty($getAction)) { - // Until we have a main landing page? - Util::redirect('?do=Locations&action=showlocations'); + $getAction = Request::get('action', false, 'string'); + if ($getAction === false) { + if (User::hasPermission('location.view')) { + Util::redirect('?do=locations&action=showlocations'); + } elseif (User::hasPermission('subnets.edit')) { + Util::redirect('?do=locations&action=showsubnets'); + } else { + // Trigger permission denied by asserting non-existent permission + User::assertPermission('location.view'); + } } if ($getAction === 'showsubnets') { User::assertPermission('subnets.edit', NULL, '?do=locations'); @@ -324,6 +333,8 @@ class Page_Locations extends Page Render::addTemplate('subnets', array('list' => $rows)); } elseif ($getAction === 'showlocations') { $this->showLocationList(); + } else { + Util::redirect('?do=locations'); } } diff --git a/modules-available/news/page.inc.php b/modules-available/news/page.inc.php index e7b70c0f..1e2e3eef 100644 --- a/modules-available/news/page.inc.php +++ b/modules-available/news/page.inc.php @@ -46,14 +46,8 @@ class Page_News extends Page // check which action we need to do $action = Request::any('action', 'show'); - if ($action === 'clear') { - // clear news input fields - // TODO: is this the right way? - $this->newsId = false; - $this->newsTitle = false; - $this->newsContent = false; - $this->newsDate = false; - } elseif ($action === 'show') { + if ($action === 'show') { + User::assertPermission('access-page'); /* load latest things */ $this->loadLatest('help'); $this->loadLatest('news'); diff --git a/modules-available/news/permissions/permissions.json b/modules-available/news/permissions/permissions.json index 0d9435d7..953599df 100644 --- a/modules-available/news/permissions/permissions.json +++ b/modules-available/news/permissions/permissions.json @@ -1,4 +1,7 @@ { + "access-page": { + "location-aware": false + }, "help.delete": { "location-aware": false }, diff --git a/modules-available/rebootcontrol/page.inc.php b/modules-available/rebootcontrol/page.inc.php index abbdb2c3..041ae74f 100644 --- a/modules-available/rebootcontrol/page.inc.php +++ b/modules-available/rebootcontrol/page.inc.php @@ -79,11 +79,14 @@ class Page_RebootControl extends Page //location you want to see, default are "not assigned" clients $requestedLocation = Request::get('location', false, 'int'); $allowedLocs = User::getAllowedLocations("action.*"); + if (empty($allowedLocs)) { + User::assertPermission('action.*'); + } if ($requestedLocation === false) { if (in_array(0, $allowedLocs)) { $requestedLocation = 0; - } elseif (!empty($allowedLocs)) { + } else { $requestedLocation = reset($allowedLocs); } } @@ -105,8 +108,8 @@ class Page_RebootControl extends Page Render::addTemplate('header', $data); // only fill table if user has at least one permission for the location - if ($requestedLocation === false) { - Message::addError('main.no-permission'); + if (!in_array($requestedLocation, $allowedLocs)) { + Message::addError('locations.no-permission-location', $requestedLocation); } else { $data['data'] = RebootQueries::getMachineTable($requestedLocation); Render::addTemplate('_page', $data); diff --git a/modules-available/serversetup-bwlp/page.inc.php b/modules-available/serversetup-bwlp/page.inc.php index ae709da7..78096d7b 100644 --- a/modules-available/serversetup-bwlp/page.inc.php +++ b/modules-available/serversetup-bwlp/page.inc.php @@ -43,6 +43,12 @@ class Page_ServerSetup extends Page // iPXE stuff changes $this->updatePxeMenu(); } + + if (Request::isPost()) { + Util::redirect('?do=serversetup'); + } + + User::assertPermission('access-page'); } protected function doRender() diff --git a/modules-available/serversetup-bwlp/permissions/permissions.json b/modules-available/serversetup-bwlp/permissions/permissions.json index 6bae5422..44927506 100644 --- a/modules-available/serversetup-bwlp/permissions/permissions.json +++ b/modules-available/serversetup-bwlp/permissions/permissions.json @@ -1,4 +1,7 @@ { + "access-page": { + "location-aware": false + }, "download": { "location-aware": false }, diff --git a/modules-available/statistics_reporting/page.inc.php b/modules-available/statistics_reporting/page.inc.php index af4b2b12..cc03e4d8 100644 --- a/modules-available/statistics_reporting/page.inc.php +++ b/modules-available/statistics_reporting/page.inc.php @@ -84,6 +84,7 @@ class Page_Statistics_Reporting extends Page die(json_encode($report)); } } + User::assertPermission('*'); } /** diff --git a/modules-available/sysconfig/page.inc.php b/modules-available/sysconfig/page.inc.php index 7bb3e599..8d1799af 100644 --- a/modules-available/sysconfig/page.inc.php +++ b/modules-available/sysconfig/page.inc.php @@ -160,7 +160,7 @@ class Page_SysConfig extends Page $pMods = User::hasPermission('module.view-list'); $pConfs = User::hasPermission('config.view-list'); if (!($pMods || $pConfs)) { - Message::addError('main.no-permission'); + User::assertPermission('config.view-list'); } Render::openTag('div', array('class' => 'row')); if ($pConfs) { diff --git a/modules-available/syslog/page.inc.php b/modules-available/syslog/page.inc.php index 3a7513b5..00c55a3f 100644 --- a/modules-available/syslog/page.inc.php +++ b/modules-available/syslog/page.inc.php @@ -25,6 +25,7 @@ class Page_SysLog extends Page } Util::redirect('?do=syslog'); } + User::assertPermission('*'); } protected function doRender() diff --git a/modules-available/systemstatus/page.inc.php b/modules-available/systemstatus/page.inc.php index 816caa05..66b30bcf 100644 --- a/modules-available/systemstatus/page.inc.php +++ b/modules-available/systemstatus/page.inc.php @@ -18,6 +18,7 @@ class Page_SystemStatus extends Page User::assertPermission("serverreboot"); $this->rebootTask = Taskmanager::submit('Reboot'); } + User::assertPermission('*'); } protected function doRender() diff --git a/modules-available/webinterface/page.inc.php b/modules-available/webinterface/page.inc.php index 806ffd59..ca52c2ab 100644 --- a/modules-available/webinterface/page.inc.php +++ b/modules-available/webinterface/page.inc.php @@ -28,6 +28,10 @@ class Page_WebInterface extends Page $this->actionCustomization(); break; } + if (Request::isPost()) { + Util::redirect('?do=webinterface'); + } + User::assertPermission('access-page'); } private function actionConfigureHttps() diff --git a/modules-available/webinterface/permissions/permissions.json b/modules-available/webinterface/permissions/permissions.json index fa6f493f..ed81602a 100644 --- a/modules-available/webinterface/permissions/permissions.json +++ b/modules-available/webinterface/permissions/permissions.json @@ -1,4 +1,7 @@ { + "access-page": { + "location-aware": false + }, "edit.design": { "location-aware": false }, -- cgit v1.2.3-55-g7522 From 2e78eec281815d6ba42ff2cf7c3a937abe6d83c5 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Tue, 12 Jun 2018 17:15:44 +0200 Subject: [serversetup-bwlp] Start rewrite as purely iPXE-based --- modules-available/serversetup-bwlp/api.inc.php | 242 +++++++++ modules-available/serversetup-bwlp/config.json | 5 +- .../serversetup-bwlp/inc/bootentry.inc.php | 195 +++++++ .../serversetup-bwlp/inc/ipxe.inc.php | 560 ++++++++++++++------- .../serversetup-bwlp/inc/ipxemenu.inc.php | 142 ++++++ .../serversetup-bwlp/inc/menuentry.inc.php | 170 +++++++ .../serversetup-bwlp/inc/pxelinux.inc.php | 262 ++++++++++ modules-available/serversetup-bwlp/install.inc.php | 74 +++ modules-available/serversetup-bwlp/page.inc.php | 300 ++++++++++- .../serversetup-bwlp/permissions/permissions.json | 14 +- .../serversetup-bwlp/templates/download.html | 38 ++ .../serversetup-bwlp/templates/heading.html | 4 +- .../templates/ipxe-new-boot-entry.html | 108 ++++ .../serversetup-bwlp/templates/ipxe.html | 117 ----- .../serversetup-bwlp/templates/menu-edit.html | 114 +++++ .../serversetup-bwlp/templates/menu-list.html | 36 ++ 16 files changed, 2049 insertions(+), 332 deletions(-) create mode 100644 modules-available/serversetup-bwlp/api.inc.php create mode 100644 modules-available/serversetup-bwlp/inc/bootentry.inc.php create mode 100644 modules-available/serversetup-bwlp/inc/ipxemenu.inc.php create mode 100644 modules-available/serversetup-bwlp/inc/menuentry.inc.php create mode 100644 modules-available/serversetup-bwlp/inc/pxelinux.inc.php create mode 100644 modules-available/serversetup-bwlp/install.inc.php create mode 100644 modules-available/serversetup-bwlp/templates/download.html create mode 100644 modules-available/serversetup-bwlp/templates/ipxe-new-boot-entry.html delete mode 100644 modules-available/serversetup-bwlp/templates/ipxe.html create mode 100644 modules-available/serversetup-bwlp/templates/menu-edit.html create mode 100644 modules-available/serversetup-bwlp/templates/menu-list.html (limited to 'modules-available/serversetup-bwlp/permissions/permissions.json') diff --git a/modules-available/serversetup-bwlp/api.inc.php b/modules-available/serversetup-bwlp/api.inc.php new file mode 100644 index 00000000..36f9063c --- /dev/null +++ b/modules-available/serversetup-bwlp/api.inc.php @@ -0,0 +1,242 @@ + 'exit 1', + 'COMBOOT' => 'chain /tftp/chain.c32 hd0', + 'SANBOOT' => 'sanboot --no-describe', +]; + +$serverIp = Property::getServerIp(); + +$ip = $_SERVER['REMOTE_ADDR']; +if (substr($ip, 0, 7) === '::ffff:') { + $ip = substr($ip, 7); +} +$uuid = Request::any('uuid', false, 'string'); +$menu = IPxeMenu::forClient($ip, $uuid); + +// Get platform - EFI or PCBIOS +$platform = strtoupper(Request::any('platform', 'PCBIOS', 'string')); + +// Get preferred localboot method, depending on system model +$localboot = false; +$model = false; +if ($uuid !== false && Module::get('statistics') !== false) { + $row = Database::queryFirst('SELECT systemmodel FROM machine WHERE machineuuid = :uuid', ['uuid' => $uuid]); + if ($row !== false && !empty($row['systemmodel'])) { + $model = $row['systemmodel']; + } +} +if ($model === false) { + function modfilt($str) + { + if (empty($str) || preg_match('/product\s+name|be\s+filled|unknown|default\s+string/i', $str)) + return false; + return trim(preg_replace('/\s+/', ' ', $str)); + } + $manuf = modfilt(Request::any('manuf', false, 'string')); + $product = modfilt(Request::any('product', false, 'string')); + if (!empty($product)) { + $model = $product; + if (!empty($manuf)) { + $model .= " ($manuf)"; + } + } +} +// Query +if ($model !== false) { + $row = Database::queryFirst("SELECT bootmethod FROM serversetup_localboot WHERE systemmodel = :model LIMIT 1", + ['model' => $model]); + if ($row !== false) { + $localboot = $row['bootmethod']; + } +} +if ($localboot === false || !isset($BOOT_METHODS[$localboot])) { + $localboot = Property::get('serversetup.localboot', false); + if ($localboot === false) { + if ($platform === 'EFI') { + // It seems most (all) EFI platforms won't enumerate any drives in ipxe. + // No idea if this can be fixed in ipxe code in the future. + $localboot = 'EXIT'; + } else { + $localboot = 'SANBOOT'; + } + } +} +if (isset($BOOT_METHODS[$localboot])) { + // Move preferred method first + $BOOT_METHODS[] = $BOOT_METHODS[$localboot]; + unset($BOOT_METHODS[$localboot]); + $BOOT_METHODS = array_reverse($BOOT_METHODS); +} + +$output = <<getMenuDefinition('target'); + +$output .= <<getItemsCode(); + +/* +:i1 +#console || +echo Welcome to Shell || +shell +goto slx_menu + +:i2 +imgfree || +kernel /boot/default/kernel slxbase=boot/default slxsrv=$serverIp splash BOOTIF=01-\${net\${nic}/mac:hexhyp} || echo Could not download kernel +initrd /boot/default/initramfs-stage31 || echo Could not download initrd +boot -ar || goto fail + +:i3 +chain -ar \${self} || +chain -ar /tftp/undionly.kpxe || goto fail + +:i4 +imgfree || +sanboot --no-describe --drive 0x80 || goto fail + +:i5 +chain -a /tftp/memtest.0 passes=1 onepass || goto membad +prompt Memory OK. Press a key. +goto init + +:i6 +console --left 60 --top 130 --right 67 --bottom 96 --quick --picture bg-load --keep || +echo Welcome to Shell || +shell +goto slx_menu + +:i7 +chain -ar tftp://132.230.4.6/ipxelinux.0 || prompt FAILED PRESS A KEY +goto slx_menu + +:i8 +set x:int32 0 +:again +console --left 60 --top 130 --right 67 --bottom 96 --picture bg-load --keep --quick || +console --left 55 --top 88 --right 63 --bottom 64 --picture bg-menu --keep --quick || +inc x +iseq \${x} 20 || goto again +prompt DONE. Press dein Knie. +goto slx_menu + +:i9 +reboot || +prompt Reboot failed. Press a key. +goto slx_menu + +:i10 +poweroff || +prompt Poweroff failed. Press a key. +goto slx_menu + +:membad +iseq \${errno} 0x1 || goto memaborted +params +param scrot \${vram} +imgfetch -a http://132.230.8.113/screen.php##params || +prompt Memory is bad. Press a key. +goto init + +:memaborted +params +param scrot \${vram} +imgfetch -a http://132.230.8.113/screen.php##params || +prompt Memory test aborted. Press a key. +goto init + +*/ + +$output .= << $value) { + if (property_exists($this, $key)) { + $this->{$key} = $value; + } + } + } + } + + public abstract function toScript($failLabel); + + public abstract function toArray(); + + public abstract function addFormFields(&$array); + + /* + * + */ + + /** + * Return a BootEntry instance from the serialized data. + * + * @param string $jsonString serialized entry data + * @return BootEntry|null instance representing boot entry, null on error + */ + public static function fromJson($data) + { + if (is_string($data)) { + $data = json_decode($data, true); + } + if (isset($data['script'])) { + return new CustomBootEntry($data); + } + if (isset($data['executable'])) { + return new StandardBootEntry($data); + } + return null; + } + + public static function newStandardBootEntry($initData) + { + if (empty($initData['executable'])) + return null; + return new StandardBootEntry($initData); + } + + public static function newCustomBootEntry($initData) + { + if (empty($initData['script'])) + return null; + return new CustomBootEntry($initData); + } + + /** + * Return a BootEntry instance from database with the given id. + * + * @param string $id + * @return BootEntry|null|false false == unknown id, null = unknown entry type, BootEntry instance on success + */ + public static function fromDatabaseId($id) + { + $row = Database::queryFirst("SELECT data FROM serversetup_bootentry + WHERE entryid = :id LIMIT 1", ['id' => $id]); + if ($row === false) + return false; + return self::fromJson($row['data']); + } + +} + +class StandardBootEntry extends BootEntry +{ + protected $executable; + protected $initRd; + protected $commandLine; + protected $replace; + protected $autoUnload; + protected $resetConsole; + + public function __construct($data = false) + { + if ($data instanceof PxeSection) { + $this->executable = $data->kernel; + $this->initRd = $data->initrd; + $this->commandLine = ' ' . str_replace('vga=current', '', $data->append) . ' '; + $this->resetConsole = true; + $this->replace = true; + $this->autoUnload = true; + if (strpos($this->commandLine, ' quiet ') !== false) { + $this->commandLine .= ' loglevel=5 rd.systemd.show_status=auto'; + } + if ($data->ipAppend & 1) { + $this->commandLine .= ' ${ipappend1}'; + } + if ($data->ipAppend & 2) { + $this->commandLine .= ' ${ipappend2}'; + } + if ($data->ipAppend & 4) { + $this->commandLine .= ' SYSUUID=${uuid}'; + } + $this->commandLine = trim(preg_replace('/\s+/', ' ', $this->commandLine)); + } else { + parent::__construct($data); + } + } + + public function toScript($failLabel) + { + $script = ''; + if ($this->resetConsole) { + $script .= "console ||\n"; + } + if (!empty($this->initRd)) { + $script .= "imgfree ||\n"; + if (!is_array($this->initRd)) { + $script .= "initrd {$this->initRd} || goto $failLabel\n"; + } else { + foreach ($this->initRd as $initrd) { + $script .= "initrd $initrd || goto $failLabel\n"; + } + } + } + $script .= "boot "; + if ($this->autoUnload) { + $script .= "-a "; + } + if ($this->replace) { + $script .= "-r "; + } + $script .= "{$this->executable}"; + if (!empty($this->commandLine)) { + $script .= " {$this->commandLine}"; + } + $script .= " || goto $failLabel\n"; + if ($this->resetConsole) { + $script .= "goto start ||\n"; + } + return $script; + } + + public function addFormFields(&$array) + { + $array['entry'] = [ + 'executable' => $this->executable, + 'initRd' => $this->initRd, + 'commandLine' => $this->commandLine, + 'replace_checked' => $this->replace ? 'checked' : '', + 'autoUnload_checked' => $this->autoUnload ? 'checked' : '', + 'resetConsole_checked' => $this->resetConsole ? 'checked' : '', + ]; + $array['exec_checked'] = 'checked'; + } + + public function toArray() + { + return [ + 'executable' => $this->executable, + 'initRd' => $this->initRd, + 'commandLine' => $this->commandLine, + 'replace' => $this->replace, + 'autoUnload' => $this->autoUnload, + 'resetConsole' => $this->resetConsole, + ]; + } +} + +class CustomBootEntry extends BootEntry +{ + protected $script; + + public function toScript($failLabel) + { + return str_replace('%fail%', $failLabel, $this->script) . "\n"; + } + + public function addFormFields(&$array) + { + $array['entry'] = [ + 'script' => $this->script, + ]; + $array['script_checked'] = 'checked'; + } + + public function toArray() + { + return ['script' => $this->script]; + } +} diff --git a/modules-available/serversetup-bwlp/inc/ipxe.inc.php b/modules-available/serversetup-bwlp/inc/ipxe.inc.php index c42de80b..d5bbb4b2 100644 --- a/modules-available/serversetup-bwlp/inc/ipxe.inc.php +++ b/modules-available/serversetup-bwlp/inc/ipxe.inc.php @@ -1,224 +1,398 @@ ['string', 'title'], - 'menu default' => ['true', 'isDefault'], - 'menu hide' => ['true', 'isHidden'], - 'menu disabled' => ['true', 'isDisabled'], - 'menu indent' => ['int', 'indent'], - 'kernel' => ['string', 'kernel'], - 'initrd' => ['string', 'initrd'], - 'append' => ['string', 'append'], - 'ipappend' => ['int', 'ipAppend'], - 'localboot' => ['int', 'localBoot'], - ]; - $globalPropMap = [ - 'timeout' => ['int', 'timeoutMs', 100], - 'totaltimeout' => ['int', 'totalTimeoutMs', 100], - 'menu title' => ['string', 'title'], - 'menu clear' => ['true', 'menuClear'], - 'menu immediate' => ['true', 'immediateHotkeys'], - 'ontimeout' => ['string', 'timeoutLabel'], - ]; - $lines = preg_split('/[\r\n]+/', $input); - $section = null; - $count = count($lines); - for ($li = 0; $li < $count; ++$li) { - $line =& $lines[$li]; - if (!preg_match('/^\s*([^m]\S*|menu\s+\S+)(\s+.*?|)\s*$/i', $line, $out)) + foreach (glob($configPath . '/*', GLOB_NOSORT) as $file) { + if (!is_file($file) || !preg_match('~/[A-F0-9]{1,8}$~', $file)) continue; - $key = trim($out[1]); - $key = strtolower($key); - $key = preg_replace('/\s+/', ' ', $key); - if ($key === 'label') { - if ($section !== null) { - $menu->sections[] = $section; - } - $section = new PxeSection($out[2]); - } elseif ($key === 'menu separator') { - if ($section !== null) { - $menu->sections[] = $section; - $section = null; - } - $menu->sections[] = new PxeSection(null); - } elseif (self::handleKeyword($key, $out[2], $globalPropMap, $menu)) { + $content = file_get_contents($file); + if ($content === false) continue; - } elseif ($section === null) { - continue; - } elseif ($key === 'text' && strtolower($out[2]) === 'help') { - $text = ''; - while (++$li < $count) { - $line =& $lines[$li]; - if (strtolower(trim($line)) === 'endtext') - break; - $text .= $line . "\n"; + $file = basename($file); + $start = hexdec(str_pad($file,8, '0')); + $end = hexdec(str_pad($file,8, 'f')); // TODO ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^PREFIX + error_log('From ' . long2ip($start) . ' to ' . long2ip($end)); + $res = Database::simpleQuery("SELECT locationid, startaddr, endaddr FROM subnet + WHERE startaddr >= :start AND endaddr <= :end", compact('start', 'end')); + $locations = []; + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + foreach ($locations as &$loc) { + if ($row['startaddr'] <= $loc['startaddr'] && $row['endaddr'] >= $loc['endaddr']) { + $loc = false; + } elseif ($row['startaddr'] >= $loc['startaddr'] && $row['endaddr'] <= $loc['endaddr']) { + continue 2; + } } - $section->helpText = $text; - } elseif (self::handleKeyword($key, $out[2], $sectionPropMap, $section)) { + unset($loc); + $locations[] = $row; + } + $menuId = self::insertMenu($content, 'Imported', false, 0, [], []); + if ($menuId === false) continue; + foreach ($locations as $loc) { + Database::exec('INSERT IGNORE INTO serversetup_menu_x_location (menuid, locationid) + VALUES (:menuid, :locationid)', ['menuid' => $menuId, 'locationid' => $loc['locationid']]); } } - if ($section !== null) { - $menu->sections[] = $section; + } + + public static function importLegacyMenu($force = false) + { + if (!$force && false !== Database::queryFirst("SELECT entryid FROM serversetup_bootentry WHERE entryid = 'bwlp-default'")) + return false; // Already exists + // Now create the default entry + self::createDefaultEntries(); + $prepend = ['bwlp-default' => false, 'localboot' => false]; + $defaultLabel = 'bwlp-default'; + $menuTitle = 'bwLehrpool Bootauswahl'; + $pxeConfig = ''; + $timeoutSecs = 60; + // Try to import any customization + $oldMenu = Property::getBootMenu(); + if (is_array($oldMenu)) { + // + if (isset($oldMenu['timeout'])) { + $timeoutSecs = (int)$oldMenu['timeout']; + } + if (isset($oldMenu['defaultentry'])) { + if ($oldMenu['defaultentry'] === 'net') { + $defaultLabel = 'bwlp-default'; + } elseif ($oldMenu['defaultentry'] === 'hdd') { + $defaultLabel = 'localboot'; + } elseif ($oldMenu['defaultentry'] === 'custom') { + $defaultLabel = 'custom'; + } + } + if (!empty($oldMenu['custom'])) { + $pxeConfig = $oldMenu['custom']; + } } - return $menu; + $append = [ + '', + 'bwlp-default-dbg' => false, + '', + 'poweroff' => false, + ]; + return self::insertMenu($pxeConfig, $menuTitle, $defaultLabel, $timeoutSecs, $prepend, $append); } - /** - * Check if keyword is valid and if so, add its interpreted value - * to the given object. The map to look up the keyword has to be passed - * as well as the object to set the value in. Map and object should - * obviously match. - * @param string $key keyword of parsed line - * @param string $val raw value of currently parsed line (empty if not present) - * @param array $map Map in which $key is looked up as key - * @param PxeMenu|PxeSection The object to set the parsed and sanitized value in - * @return bool true if the value was found in the map (and set in the object), false otherwise - */ - private static function handleKeyword($key, $val, $map, $object) + private static function insertMenu($pxeConfig, $menuTitle, $defaultLabel, $defaultTimeoutSeconds, $prepend, $append) { - if (!isset($map[$key])) + $timeoutMs = []; + $menuEntries = $prepend; + settype($menuEntries, 'array'); + if (!empty($pxeConfig)) { + $pxe = PxeLinux::parsePxeLinux($pxeConfig); + if (!empty($pxe->title)) { + $menuTitle = $pxe->title; + } + if ($pxe->timeoutLabel !== null) { + $defaultLabel = $pxe->timeoutLabel; + } + $timeoutMs[] = $pxe->timeoutMs; + $timeoutMs[] = $pxe->totalTimeoutMs; + foreach ($pxe->sections as $section) { + if ($section->localBoot || preg_match('/chain.c32$/i', $section->kernel)) { + $menuEntries['localboot'] = 'localboot'; + continue; + } + $section->mangle(); + if ($section->label === null) { + if (!$section->isHidden && !empty($section->title)) { + $menuEntries[] = $section->title; + } + continue; + } + if (empty($section->kernel)) { + if (!$section->isHidden && !empty($section->title)) { + $menuEntries[] = $section->title; + } + continue; + } + $entry = self::pxe2BootEntry($section); + if ($entry === null) + continue; + $label = self::cleanLabelFixLocal($section); + if ($defaultLabel === $section->label) { + $defaultLabel = $label; + } + $hotkey = MenuEntry::filterKeyName($section->hotkey); + // Create boot entry + $data = $entry->toArray(); + Database::exec('INSERT IGNORE INTO serversetup_bootentry (entryid, hotkey, title, builtin, data) + VALUES (:label, :hotkey, :title, 0, :data)', [ + 'label' => $label, + 'hotkey' => $hotkey, + 'title' => self::sanitizeIpxeString($section->title), + 'data' => json_encode($data), + ]); + $menuEntries[$label] = $section; + } + } + if (is_array($append)) { + $menuEntries += $append; + } + if (empty($menuEntries)) return false; - $opt = $map[$key]; - // opt[0] is the type the value should be cast to; special case "true" means - // this is a bool option that will be set as soon as the keyword is present, - // as it doesn't have any parameters - if ($opt[0] === 'true') { - $val = true; + // Make menu + $timeoutMs = array_filter($timeoutMs, 'is_int'); + if (empty($timeoutMs)) { + $timeoutMs = (int)($defaultTimeoutSeconds * 1000); } else { - settype($val, $opt[0]); + $timeoutMs = min($timeoutMs); } - // If opt[2] is present it's a multiplier for the value - if (isset($opt[2])) { - $val *= $opt[2]; + $isDefault = (int)(Database::queryFirst('SELECT menuid FROM serversetup_menu WHERE isdefault = 1') === false); + Database::exec("INSERT INTO serversetup_menu (timeoutms, title, defaultentryid, isdefault) + VALUES (:timeoutms, :title, NULL, :isdefault)", [ + 'title' => self::sanitizeIpxeString($menuTitle), + 'timeoutms' => $timeoutMs, + 'isdefault' => $isDefault, + ]); + $menuId = Database::lastInsertId(); + if (!array_key_exists($defaultLabel, $menuEntries) && $timeoutMs > 0) { + $defaultLabel = array_keys($menuEntries)[0]; } - $object->{$opt[1]} = $val; - return true; + // Link boot entries to menu + $defaultEntryId = null; + $order = 1000; + foreach ($menuEntries as $label => $entry) { + if (is_string($entry)) { + // Gap entry + Database::exec("INSERT INTO serversetup_menuentry + (menuid, entryid, hotkey, title, hidden, sortval, plainpass, md5pass) + VALUES (:menuid, :entryid, :hotkey, :title, :hidden, :sortval, '', '')", [ + 'menuid' => $menuId, + 'entryid' => null, + 'hotkey' => '', + 'title' => self::sanitizeIpxeString($entry), + 'hidden' => 0, + 'sortval' => $order += 100, + ]); + continue; + } + $data = Database::queryFirst("SELECT entryid, hotkey, title FROM serversetup_bootentry WHERE entryid = :entryid", ['entryid' => $label]); + if ($data === false) + continue; + $data['pass'] = ''; + $data['hidden'] = 0; + if ($entry instanceof PxeSection) { + $data['hidden'] = (int)$entry->isHidden; + // Prefer explicit data from this imported menu over the defaults + $data['title'] = self::sanitizeIpxeString($entry->title); + if (MenuEntry::getKeyCode($entry->hotkey) !== false) { + $data['hotkey'] = $entry->hotkey; + } + if (!empty($entry->passwd)) { + // Most likely it's a hash so we cannot recover; ask people to reset + $data['pass'] ='please_reset'; + } + } + $data['menuid'] = $menuId; + $data['sortval'] = $order += 100; + $res = Database::exec("INSERT INTO serversetup_menuentry + (menuid, entryid, hotkey, title, hidden, sortval, plainpass, md5pass) + VALUES (:menuid, :entryid, :hotkey, :title, :hidden, :sortval, :pass, :pass)", $data); + if ($res !== false && $label === $defaultLabel) { + $defaultEntryId = Database::lastInsertId(); + } + } + // Now we can set default entry + if (!empty($defaultEntryId)) { + Database::exec("UPDATE serversetup_menu SET defaultentryid = :entryid WHERE menuid = :menuid", + ['menuid' => $menuId, 'entryid' => $defaultEntryId]); + } + // TODO: masterpw? rather pointless.... + //$oldMenu['masterpasswordclear']; + return $menuId; } -} + private static function createDefaultEntries() + { + $query = 'INSERT IGNORE INTO serversetup_bootentry (entryid, hotkey, title, builtin, data) + VALUES (:entryid, :hotkey, :title, 1, :data)'; + Database::exec($query, + [ + 'entryid' => 'bwlp-default', + 'hotkey' => 'B', + 'title' => 'bwLehrpool-Umgebung starten', + 'data' => json_encode([ + 'executable' => '/boot/default/kernel', + 'initRd' => '/boot/default/initramfs-stage31', + 'commandLine' => 'slxbase=boot/default quiet splash loglevel=5 rd.systemd.show_status=auto ${ipappend1} ${ipappend2}', + 'replace' => true, + 'autoUnload' => true, + 'resetConsole' => true, + ]), + ]); + Database::exec($query, + [ + 'entryid' => 'bwlp-default-dbg', + 'hotkey' => '', + 'title' => 'bwLehrpool-Umgebung starten (nosplash, debug)', + 'data' => json_encode([ + 'executable' => '/boot/default/kernel', + 'initRd' => '/boot/default/initramfs-stage31', + 'commandLine' => 'slxbase=boot/default loglevel=7 ${ipappend1} ${ipappend2}', + 'replace' => true, + 'autoUnload' => true, + 'resetConsole' => true, + ]), + ]); + Database::exec($query, + [ + 'entryid' => 'localboot', + 'hotkey' => 'L', + 'title' => 'Lokales System starten', + 'data' => json_encode([ + 'script' => 'goto slx_localboot || goto %fail% ||', + ]), + ]); + Database::exec($query, + [ + 'entryid' => 'poweroff', + 'hotkey' => 'P', + 'title' => 'Power off', + 'data' => json_encode([ + 'script' => 'poweroff || goto %fail% ||', + ]), + ]); + Database::exec($query, + [ + 'entryid' => 'reboot', + 'hotkey' => 'R', + 'title' => 'Reboot', + 'data' => json_encode([ + 'script' => 'reboot || goto %fail% ||', + ]), + ]); + } -/** - * Class representing a parsed pxelinux menu. Members - * will be set to their annotated type if present or - * be null otherwise, except for present-only boolean - * options, which will default to false. - */ -class PxeMenu -{ - /** - * @var string menu title, shown at the top of the menu - */ - public $title; - /** - * @var int initial timeout after which $timeoutLabel would be executed - */ - public $timeoutMs; - /** - * @var int if the user canceled the timeout by pressing a key, this timeout would still eventually - * trigger and launch the $timeoutLabel section - */ - public $totalTimeoutMs; - /** - * @var string label of section which will execute if the timeout expires - */ - public $timeoutLabel; - /** - * @var bool hide menu and just show background after triggering an entry - */ - public $menuClear = false; /** - * @var bool boot the associated entry directly if its corresponding hotkey is presed instead of just highlighting + * Create unique label for a boot entry. It will try to figure out whether + * this is one of our default entries and if not, create a unique label + * representing the menu entry contents. + * Also it patches the entry if it's referencing the local bwlp install + * because side effects. + * + * @param PxeSection $section + * @return string */ - public $immediateHotkeys = false; - /** - * @var PxeSection[] list of sections the menu contains - */ - public $sections = []; -} + private static function cleanLabelFixLocal($section) + { + $myip = Property::getServerIp(); + // Detect our "old" entry types + if (count($section->initrd) === 1 && preg_match(",$myip/boot/default/kernel\$,", $section->kernel) + && preg_match(",$myip/boot/default/initramfs-stage31\$,", $section->initrd[0])) { + // Kernel and initrd match, examine KCL + if ($section->append === 'slxbase=boot/default vga=current quiet splash') { + // Normal + return 'bwlp-default'; + } elseif ($section->append === 'slxbase=boot/default') { + // Debug output + return 'bwlp-default-dbg'; + } else { + // Transform to relative URL, leave KCL, fall through to generic label gen + $section->kernel = '/boot/default/kernel'; + $section->initrd = ['/boot/default/initramfs-stage31']; + } + } + // Generic -- "smart" hash of kernel, initrd and command line + $str = $section->kernel . ' ' . implode(',', $section->initrd); + $array = preg_split('/\s+/', $section->append, -1, PREG_SPLIT_NO_EMPTY); + sort($array); + $str .= ' ' . implode(' ', $array); + + return 'i-' . substr(md5($str), 0, 12); + } -/** - * Class representing a parsed pxelinux menu entry. Members - * will be set to their annotated type if present or - * be null otherwise, except for present-only boolean - * options, which will default to false. - */ -class PxeSection -{ - /** - * @var string label used internally in PXEMENU definition to address this entry - */ - public $label; - /** - * @var string MENU LABEL of PXEMENU - title of entry displayed to the user - */ - public $title; - /** - * @var int Number of spaces to prefix the title with - */ - public $indent; - /** - * @var string help text to display when the entry is highlighted - */ - public $helpText; - /** - * @var string Kernel to load - */ - public $kernel; - /** - * @var string initrd to load for the kernel - */ - public $initrd; - /** - * @var string command line options to pass to the kernel - */ - public $append; - /** - * @var int IPAPPEND from PXEMENU. Bitmask of valid options 1 and 2. - */ - public $ipAppend; - /** - * @var string Password protecting the entry. This is most likely in crypted form. - */ - public $passwd; - /** - * @var bool whether this section is marked as default (booted after timeout) - */ - public $isDefault = false; - /** - * @var bool Menu entry is not visible (can only be triggered by timeout) - */ - public $isHidden = false; /** - * @var bool Disable this entry, making it unselectable + * @param PxeSection $section + * @return BootEntry|null The according boot entry, null if it's unparsable */ - public $isDisabled = false; + private static function pxe2BootEntry($section) + { + if (preg_match('/(pxechain.com|pxechn.c32)$/i', $section->kernel)) { + // Chaining -- create script + $args = preg_split('/\s+/', $section->append); + $script = ''; + $file = false; + for ($i = 0; $i < count($args); ++$i) { + $arg = $args[$i]; + if ($arg === '-c') { // PXELINUX config file option + ++$i; + $script .= "set 209:string {$args[$i]} || goto %fail%\n"; + } elseif ($arg === '-p') { // PXELINUX prefix path option + ++$i; + $script .= "set 210:string {$args[$i]} || goto %fail%\n"; + } elseif ($arg === '-t') { // PXELINUX timeout option + ++$i; + $script .= "set 211:int32 {$args[$i]} || goto %fail%\n"; + } elseif ($arg === '-o') { // Overriding various DHCP options + ++$i; + if (preg_match('/^((?:0x)?[a-f0-9]{1,4})\.([bwlsh])=(.*)$/i', $args[$i], $out)) { + // TODO: 'q' (8byte) unsupported for now + $opt = intval($out[1], 0); + if ($opt > 0 && $opt < 255) { + static $optType = ['b' => 'uint8', 'w' => 'uint16', 'l' => 'int32', 's' => 'string', 'h' => 'hex']; + $type = $optType[$out[2]]; + $script .= "set {$opt}:{$type} {$args[$i]} || goto %fail%\n"; + } + } + } elseif ($arg{0} === '-') { + continue; + } elseif ($file === false) { + $file = self::parseFile($arg); + } + } + if ($file !== false) { + $url = parse_url($file); + if (isset($url['host'])) { + $script .= "set next-server {$url['host']} || goto %fail%\n"; + } + if (isset($url['path'])) { + $script .= "set filename {$url['path']} || goto %fail%\n"; + } + $script .= "chain -ar {$file} || goto %fail%\n"; + return new CustomBootEntry(['script' => $script]); + } + return null; + } + // "Normal" entry that should be convertible into a StandardBootEntry + $section->kernel = self::parseFile($section->kernel); + foreach ($section->initrd as &$initrd) { + $initrd = self::parseFile($initrd); + } + return BootEntry::newStandardBootEntry($section); + } + /** - * @var int Value of the LOCALBOOT field + * Parse PXELINUX file notion. Basically, turn + * server::file into tftp://server/file. + * + * @param string $file + * @return string */ - public $localBoot; + private static function parseFile($file) + { + if (preg_match(',^([^:/]+)::(.*)$,', $file, $out)) { + return 'tftp://' . $out[1] . '/' . $out[2]; + } + return $file; + } - public function __construct($label) { $this->label = $label; } -} + public static function sanitizeIpxeString($string) + { + return str_replace(['&', '|', ';', '$', "\r", "\n"], ['+', '/', ':', 'S', ' ', ' '], $string); + } + + public static function makeMd5Pass($plainpass, $salt) + { + if (empty($plainpass)) + return ''; + return md5(md5($plainpass) . '-' . $salt); + } +} diff --git a/modules-available/serversetup-bwlp/inc/ipxemenu.inc.php b/modules-available/serversetup-bwlp/inc/ipxemenu.inc.php new file mode 100644 index 00000000..ed9f0986 --- /dev/null +++ b/modules-available/serversetup-bwlp/inc/ipxemenu.inc.php @@ -0,0 +1,142 @@ + $menu]); + if (!is_array($menu)) { + $menu = ['menuid' => 'foo', 'title' => 'Invalid Menu ID: ' . (int)$menu]; + } + } + $this->menuid = (int)$menu['menuid']; + $this->timeoutMs = (int)$menu['timeoutms']; + $this->title = $menu['title']; + $this->defaultEntryId = $menu['defaultentryid']; + $res = Database::simpleQuery("SELECT e.menuentryid, e.entryid, e.hotkey, e.title, e.hidden, e.sortval, e.md5pass, + b.data AS bootentry + FROM serversetup_menuentry e + LEFT JOIN serversetup_bootentry b USING (entryid) + WHERE e.menuid = :menuid + ORDER BY e.sortval ASC, e.title ASC", ['menuid' => $menu['menuid']]); + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + $this->items[] = new MenuEntry($row); + } + } + + public function getMenuDefinition($targetVar) + { + $str = "menu {$this->title}\n"; + foreach ($this->items as $item) { + $str .= $item->getMenuItemScript("m_{$this->menuid}", $this->defaultEntryId); + } + if ($this->defaultEntryId === null) { + $defaultLabel = "mx_{$this->menuid}_poweroff"; + } else { + $defaultLabel = "m_{$this->menuid}_{$this->defaultEntryId}"; + } + $str .= "choose"; + if ($this->timeoutMs > 0) { + $str .= " --timeout {$this->timeoutMs}"; + } + $str .= " $targetVar || goto $defaultLabel || goto fail\n"; + if ($this->defaultEntryId === null) { + $str .= "goto skip_{$defaultLabel}\n" + . ":{$defaultLabel}\n" + . "poweroff || goto fail\n" + . ":skip_{$defaultLabel}\n"; + } + return $str; + } + + public function getItemsCode() + { + $str = ''; + foreach ($this->items as $item) { + $str .= $item->getBootEntryScript("m_{$this->menuid}", 'fail'); + $str .= "goto slx_menu\n"; + } + return $str; + } + + /* + * + */ + + public static function forLocation($locationId) + { + $chain = null; + if (Module::isAvailable('location')) { + $chain = Location::getLocationRootChain($locationId); + } + if (!empty($chain)) { + $res = Database::simpleQuery("SELECT m.menuid, m.timeoutms, m.title, m.defaultentryid, ml.locationid + FROM serversetup_menu m + INNER JOIN serversetup_menu_location ml USING (menuid) + WHERE ml.locationid IN (:chain)", ['chain' => $chain]); + if ($res->rowCount() > 0) { + // Make the location id key, preserving order (closest location is first) + $chain = array_flip($chain); + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + // Overwrite the value (numeric ascending values, useless) with menu array of according location + $chain[(int)$row['locationid']] = $row; + } + // Use first one that was found + foreach ($chain as $menu) { + if (is_array($menu)) { + return new IPxeMenu($menu); + } + } + // Should never end up here, but we'd just fall through and use the default + } + } + // We're here, no specific menu, use default + $menu = Database::queryFirst("SELECT menuid, timeoutms, title, defaultentryid + FROM serversetup_menu + ORDER BY isdefault DESC LIMIT 1"); + if ($menu === false) { + return new EmptyIPxeMenu; + } + return new IPxeMenu($menu); + } + + public static function forClient($ip, $uuid) + { + $locationId = 0; + if (Module::isAvailable('location')) { + $locationId = Location::getFromIpAndUuid($ip, $uuid); + } + return self::forLocation($locationId); + } + +} + +class EmptyIPxeMenu extends IPxeMenu +{ + + /** @noinspection PhpMissingParentConstructorInspection */ + public function __construct() + { + $this->title = 'No menu defined'; + $this->menuid = -1; + $this->items[] = new MenuEntry([ + 'title' => 'Please create a menu in Server-Setup first' + ]); + $this->items[] = new MenuEntry([ + 'title' => 'Bitte erstellen Sie zunächst ein Menü' + ]); + } + +} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp/inc/menuentry.inc.php b/modules-available/serversetup-bwlp/inc/menuentry.inc.php new file mode 100644 index 00000000..9736b7bb --- /dev/null +++ b/modules-available/serversetup-bwlp/inc/menuentry.inc.php @@ -0,0 +1,170 @@ + $value) { + if (property_exists($this, $key)) { + $this->{$key} = $value; + } + } + $this->hotkey = self::getKeyCode($row['hotkey']); + if (!empty($row['bootentry'])) { + $this->bootEntry = BootEntry::fromJson($row['bootentry']); + } + $this->gap = (array_key_exists('entryid', $row) && $row['entryid'] === null); + } + settype($this->hidden, 'bool'); + settype($this->gap, 'bool'); + settype($this->sortval, 'int'); + settype($this->menuentryid, 'int'); + } + + public function getMenuItemScript($lblPrefix, $requestedDefaultId) + { + $str = 'item '; + if ($this->gap) { + $str .= '--gap '; + } else { + if ($this->hidden) { + if ($this->hotkey === false) + return ''; // Hidden entries without hotkey are illegal + $str .= '--hidden '; + } + if ($this->hotkey !== false) { + $str .= '--key ' . $this->hotkey . ' '; + } + if ($this->menuentryid == $requestedDefaultId) { + $str .= '--default '; + } + $str .= "{$lblPrefix}_{$this->menuentryid} "; + } + $str .= $this->title; + return $str . " || prompt Could not create menu item for {$lblPrefix}_{$this->menuentryid}\n"; + } + + public function getBootEntryScript($lblPrefix, $failLabel) + { + if ($this->bootEntry === null) + return ''; + $str = ":{$lblPrefix}_{$this->menuentryid}\n"; + if (!empty($this->md5pass)) { + $str .= "set slx_hash {$this->md5pass} || goto $failLabel\n" + . "set slx_salt {$this->menuentryid} || goto $failLabel\n" + . "set slx_pw_ok {$lblPrefix}_ok || goto $failLabel\n" + . "set slx_pw_fail slx_menu || goto $failLabel\n" + . "goto slx_pass_check || goto $failLabel\n" + . ":{$lblPrefix}_ok\n"; + } + return $str . $this->bootEntry->toScript($failLabel); + } + + /* + * + */ + + private static function getKeyArray() + { + static $data = false; + if ($data === false) { + $data = [ + 'F5' => 0x107e, + 'F6' => 0x127e, + 'F7' => 0x137e, + 'F8' => 0x147e, + 'F9' => 0x157e, + 'F10' => 0x167e, + 'F11' => 0x187e, + 'F12' => 0x197e, + ]; + for ($i = 1; $i <= 26; ++$i) { + $letter = chr(0x40 + $i); + $data['SHIFT_' . $letter] = 0x40 + $i; + if ($letter !== 'C') { + $data['CTRL_' . $letter] = $i; + } + $data[$letter] = 0x60 + $i; + } + for ($i = 0; $i <= 9; ++$i) { + $data[chr(0x30 + $i)] = 0x30 + $i; + } + asort($data, SORT_NUMERIC); + } + return $data; + } + + /** + * Get all the known/supported keys, usable for menu items. + * + * @return string[] list of known key names + */ + public static function getKeyList() + { + return array_keys(self::getKeyArray()); + } + + /** + * Get the key code ipxe expects for the given named + * key. Returns false if the key name is unknown. + * + * @param string $keyName + * @return false|string Key code as hex string, or false if not found + */ + public static function getKeyCode($keyName) + { + $data = self::getKeyArray(); + if (isset($data[$keyName])) + return '0x' . dechex($data[$keyName]); + return false; + } + + /** + * @param string $keyName desired key name + * @return string $keyName if it's known, empty string otherwise + */ + public static function filterKeyName($keyName) + { + if (isset($data[$keyName])) + return $keyName; + return ''; + } + +} diff --git a/modules-available/serversetup-bwlp/inc/pxelinux.inc.php b/modules-available/serversetup-bwlp/inc/pxelinux.inc.php new file mode 100644 index 00000000..db3dac4b --- /dev/null +++ b/modules-available/serversetup-bwlp/inc/pxelinux.inc.php @@ -0,0 +1,262 @@ + ['string', 'title'], + 'menu default' => ['true', 'isDefault'], + 'menu hide' => ['true', 'isHidden'], + 'menu disabled' => ['true', 'isDisabled'], + 'menu indent' => ['int', 'indent'], + 'kernel' => ['string', 'kernel'], + 'com32' => ['string', 'kernel'], + 'pxe' => ['string', 'kernel'], + 'initrd' => ['string', 'initrd'], + 'append' => ['string', 'append'], + 'ipappend' => ['int', 'ipAppend'], + 'sysappend' => ['int', 'ipAppend'], + 'localboot' => ['int', 'localBoot'], + ]; + $globalPropMap = [ + 'timeout' => ['int', 'timeoutMs', 100], + 'totaltimeout' => ['int', 'totalTimeoutMs', 100], + 'menu title' => ['string', 'title'], + 'menu clear' => ['true', 'menuClear'], + 'menu immediate' => ['true', 'immediateHotkeys'], + 'ontimeout' => ['string', 'timeoutLabel'], + ]; + $lines = preg_split('/[\r\n]+/', $input); + $section = null; + $count = count($lines); + for ($li = 0; $li < $count; ++$li) { + $line =& $lines[$li]; + if (!preg_match('/^\s*([^m]\S*|menu\s+\S+)(\s+.*?|)\s*$/i', $line, $out)) + continue; + $val = trim($out[2]); + $key = trim($out[1]); + $key = strtolower($key); + $key = preg_replace('/\s+/', ' ', $key); + if ($key === 'label') { + if ($section !== null) { + $menu->sections[] = $section; + } + $section = new PxeSection($val); + } elseif ($key === 'menu separator') { + if ($section !== null) { + $menu->sections[] = $section; + $section = null; + } + $menu->sections[] = new PxeSection(null); + } elseif (self::handleKeyword($key, $val, $globalPropMap, $menu)) { + continue; + } elseif ($section === null) { + continue; + } elseif ($key === 'text' && strtolower($val) === 'help') { + $text = ''; + while (++$li < $count) { + $line =& $lines[$li]; + if (strtolower(trim($line)) === 'endtext') + break; + $text .= $line . "\n"; + } + $section->helpText = $text; + } elseif (self::handleKeyword($key, $val, $sectionPropMap, $section)) { + continue; + } + } + if ($section !== null) { + $menu->sections[] = $section; + } + return $menu; + } + + /** + * Check if keyword is valid and if so, add its interpreted value + * to the given object. The map to look up the keyword has to be passed + * as well as the object to set the value in. Map and object should + * obviously match. + * @param string $key keyword of parsed line + * @param string $val raw value of currently parsed line (empty if not present) + * @param array $map Map in which $key is looked up as key + * @param PxeMenu|PxeSection The object to set the parsed and sanitized value in + * @return bool true if the value was found in the map (and set in the object), false otherwise + */ + private static function handleKeyword($key, $val, $map, $object) + { + if (!isset($map[$key])) + return false; + $opt = $map[$key]; + // opt[0] is the type the value should be cast to; special case "true" means + // this is a bool option that will be set as soon as the keyword is present, + // as it doesn't have any parameters + if ($opt[0] === 'true') { + $val = true; + } else { + settype($val, $opt[0]); + } + // If opt[2] is present it's a multiplier for the value + if (isset($opt[2])) { + $val *= $opt[2]; + } + $object->{$opt[1]} = $val; + return true; + } + +} + +/** + * Class representing a parsed pxelinux menu. Members + * will be set to their annotated type if present or + * be null otherwise, except for present-only boolean + * options, which will default to false. + */ +class PxeMenu +{ + /** + * @var string menu title, shown at the top of the menu + */ + public $title; + /** + * @var int initial timeout after which $timeoutLabel would be executed + */ + public $timeoutMs; + /** + * @var int if the user canceled the timeout by pressing a key, this timeout would still eventually + * trigger and launch the $timeoutLabel section + */ + public $totalTimeoutMs; + /** + * @var string label of section which will execute if the timeout expires + */ + public $timeoutLabel; + /** + * @var bool hide menu and just show background after triggering an entry + */ + public $menuClear = false; + /** + * @var bool boot the associated entry directly if its corresponding hotkey is pressed instead of just highlighting + */ + public $immediateHotkeys = false; + /** + * @var PxeSection[] list of sections the menu contains + */ + public $sections = []; +} + +/** + * Class representing a parsed pxelinux menu entry. Members + * will be set to their annotated type if present or + * be null otherwise, except for present-only boolean + * options, which will default to false. + */ +class PxeSection +{ + /** + * @var string label used internally in PXEMENU definition to address this entry + */ + public $label; + /** + * @var string MENU LABEL of PXEMENU - title of entry displayed to the user + */ + public $title; + /** + * @var int Number of spaces to prefix the title with + */ + public $indent; + /** + * @var string help text to display when the entry is highlighted + */ + public $helpText; + /** + * @var string Kernel to load + */ + public $kernel; + /** + * @var string|string[] initrd to load for the kernel. + * If mangle() has been called this will be an array, + * otherwise it's a comma separated list. + */ + public $initrd; + /** + * @var string command line options to pass to the kernel + */ + public $append; + /** + * @var int IPAPPEND from PXEMENU. Bitmask of valid options 1 and 2. + */ + public $ipAppend; + /** + * @var string Password protecting the entry. This is most likely in crypted form. + */ + public $passwd; + /** + * @var bool whether this section is marked as default (booted after timeout) + */ + public $isDefault = false; + /** + * @var bool Menu entry is not visible (can only be triggered by timeout) + */ + public $isHidden = false; + /** + * @var bool Disable this entry, making it unselectable + */ + public $isDisabled = false; + /** + * @var int Value of the LOCALBOOT field + */ + public $localBoot; + /** + * @var string hotkey to trigger item. Only valid after calling mangle() + */ + public $hotkey; + + public function __construct($label) { $this->label = $label; } + + public function mangle() + { + if (($i = strpos($this->title, '^')) !== false) { + $this->hotkey = strtoupper($this->title{$i+1}); + $this->title = substr($this->title, 0, $i) . substr($this->title, $i + 1); + } + if (strpos($this->append, 'initrd=') !== false) { + $parts = preg_split('/\s+/', $this->append); + $this->append = ''; + for ($i = 0; $i < count($parts); ++$i) { + if (preg_match('/^initrd=(.*)$/', $parts[$i], $out)) { + if (!empty($this->initrd)) { + $this->initrd .= ','; + } + $this->initrd .= $out[1]; + } else { + $this->append .= ' ' . $parts[$i]; + } + } + $this->append = trim($this->append); + } + if (is_string($this->initrd)) { + $this->initrd = explode(',', $this->initrd); + } elseif (!is_array($this->initrd)) { + $this->initrd = []; + } + } +} + diff --git a/modules-available/serversetup-bwlp/install.inc.php b/modules-available/serversetup-bwlp/install.inc.php new file mode 100644 index 00000000..8814bb7c --- /dev/null +++ b/modules-available/serversetup-bwlp/install.inc.php @@ -0,0 +1,74 @@ +handleGetImage(); @@ -44,16 +50,45 @@ class Page_ServerSetup extends Page $this->updatePxeMenu(); } + if ($action === 'savebootentry') { + User::assertPermission('ipxe.bootentry.edit'); + $this->saveBootEntry(); + } + + if ($action === 'savemenu') { + User::assertPermission('ipxe.menu.edit'); + $this->saveMenu(); + } + if (Request::isPost()) { Util::redirect('?do=serversetup'); } User::assertPermission('access-page'); + + if (User::hasPermission('ipxe.*')) { + Dashboard::addSubmenu('?do=serversetup&show=menu', Dictionary::translate('submenu_menu', true)); + } + if (User::hasPermission('edit.address')) { + Dashboard::addSubmenu('?do=serversetup&show=address', Dictionary::translate('submenu_address', true)); + } + if (User::hasPermission('download')) { + Dashboard::addSubmenu('?do=serversetup&show=download', Dictionary::translate('submenu_download', true)); + } + if (Request::get('show') === false) { + $subs = Dashboard::getSubmenus(); + if (empty($subs)) { + User::assertPermission('download'); + } else { + Util::redirect($subs[0]['url']); + } + } } protected function doRender() { Render::addTemplate("heading"); + $task = Property::get('ipxe-task-id'); if ($task !== false) { $task = Taskmanager::status($task); @@ -65,32 +100,143 @@ class Page_ServerSetup extends Page Render::addTemplate('ipxe_update', array('taskid' => $task['id'])); } - Permission::addGlobalTags($perms, null, ['edit.menu', 'edit.address', 'download']); + switch (Request::get('show')) { + case 'editbootentry': + User::assertPermission('ipxe.bootentry.edit'); + $this->showEditBootEntry(); + break; + case 'editmenu': + User::assertPermission('ipxe.menu.view'); + $this->showEditMenu(); + break; + case 'download': + User::assertPermission('download'); + $this->showDownload(); + break; + case 'menu': + User::assertPermission('ipxe.menu.view'); + $this->showMenuList(); + break; + default: + Util::redirect('?do=serversetup'); + break; + } + } + + private function showDownload() + { + // TODO: Make nicer, support more variants (taskmanager-plugin) + Render::addTemplate('download'); + } + + private function showMenuList() + { + $allowedEdit = User::getAllowedLocations('ipxe.menu.edit'); + + // TODO Permission::addGlobalTags($perms, null, ['edit.menu', 'edit.address', 'download']); + + $res = Database::simpleQuery("SELECT m.menuid, m.title, m.isdefault, GROUP_CONCAT(l.locationid) AS locations + FROM serversetup_menu m LEFT JOIN serversetup_menu_location l USING (menuid) GROUP BY menuid ORDER BY title"); + $table = []; + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + if (empty($row['locations'])) { + $locations = []; + $row['allowEdit'] = in_array(0, $allowedEdit); + } else { + $locations = explode(',', $row['locations']); + $row['allowEdit'] = empty(array_diff($locations, $allowedEdit)); + } + $row['locationCount'] = empty($locations) ? '' : count($locations); + $table[] = $row; + } - Render::addTemplate('ipaddress', array( - 'ips' => $this->taskStatus['data']['addresses'], - 'chooseHintClass' => $this->hasIpSet ? '' : 'alert alert-danger', - 'editAllowed' => User::hasPermission("edit.address"), - 'perms' => $perms, + Render::addTemplate('menu-list', array( + 'table' => $table, )); - $data = $this->currentMenu; - if (!User::hasPermission('edit.menu')) { - unset($data['masterpasswordclear']); + } + + private function hasMenuPermission($menuid, $permission) + { + $allowedEditLocations = User::getAllowedLocations($permission); + $allowEdit = in_array(0, $allowedEditLocations); + if (!$allowEdit) { + // Get locations + $locations = Database::queryColumnArray('SELECT locationid FROM serversetup_menu_location + WHERE menuid = :menuid', compact('menuid')); + if (!empty($locations)) { + $allowEdit = count(array_diff($locations, $allowedEditLocations)) === 0; + } } - if (!isset($data['defaultentry'])) { - $data['defaultentry'] = 'net'; + return $allowEdit; + } + + private function showEditMenu() + { + $id = Request::get('id', false, 'int'); + $menu = Database::queryFirst("SELECT menuid, timeoutms, title, defaultentryid, isdefault + FROM serversetup_menu WHERE menuid = :id", compact('id')); + if ($menu === false) { + Message::addError('invalid-menu-id', $id); + Util::redirect('?do=serversetup&show=menu'); } - if ($data['defaultentry'] === 'net') { - $data['active-net'] = 'checked'; + if (!$this->hasMenuPermission($id, 'ipxe.menu.edit')) { + $menu['readonly'] = 'readonly'; + $menu['disabled'] = 'disabled'; + $menu['plainpass'] = ''; } - if ($data['defaultentry'] === 'hdd') { - $data['active-hdd'] = 'checked'; + $menu['timeout'] = round($menu['timeoutms'] / 1000); + $menu['entries'] = Database::queryAll("SELECT menuentryid, entryid, hotkey, title, hidden, sortval, plainpass FROM + serversetup_menuentry WHERE menuid = :id", compact('id')); + $keyList = array_map(function ($item) { return ['key' => $item]; }, MenuEntry::getKeyList()); + $entryList = Database::queryAll("SELECT entryid, title, hotkey FROM serversetup_bootentry ORDER BY title ASC"); + foreach ($menu['entries'] as &$entry) { + $entry['isdefault'] = ($entry['menuentryid'] == $menu['defaultentryid']); + $entry['keys'] = $keyList; + foreach ($entry['keys'] as &$key) { + if ($key['key'] === $entry['hotkey']) { + $key['selected'] = 'selected'; // TODO: plainpass only when permissions + } + } + $entry['entrylist'] = $entryList; + foreach ($entry['entrylist'] as &$item) { + if ($item['entryid'] == $entry['entryid']) { + $item['selected'] = 'selected'; + } + if (empty($item['title'])) { + $item['title'] = $item['entryid']; + } + } } - if ($data['defaultentry'] === 'custom') { - $data['active-custom'] = 'checked'; + // TODO: Make assigned locations editable + Permission::addGlobalTags($menu['perms'], 0, ['ipxe.menu.edit']); + Render::addTemplate('menu-edit', $menu); + } + + private function showEditBootEntry() + { + $params = []; + $id = Request::get('id', false, 'string'); + if ($id === false) { + $params['exec_checked'] = 'checked'; + $params['entryid'] = 'u-' . dechex(mt_rand(0x1000, 0xffff)) . '-' . dechex(time()); + } else { + // Query existing entry + $row = Database::queryFirst('SELECT entryid, title, builtin, data FROM serversetup_bootentry + WHERE entryid = :id LIMIT 1', ['id' => $id]); + if ($row === false) { + Message::addError('invalid-boot-entry', $id); + Util::redirect('?do=serversetup'); + } + $entry = BootEntry::fromJson($row['data']); + if ($entry === null) { + Message::addError('unknown-boot-entry-type', $id); + Util::redirect('?do=serversetup'); + } + $entry->addFormFields($params); + $params['title'] = $row['title']; + $params['oldentryid'] = $params['entryid'] = $row['entryid']; } - $data['perms'] = $perms; - Render::addTemplate('ipxe', $data); + Render::addTemplate('ipxe-new-boot-entry', $params); } // ----------------------------------------------------------------------------------------------- @@ -131,6 +277,76 @@ class Page_ServerSetup extends Page return true; } + private function saveMenu() + { + $id = Request::post('menuid', false, 'int'); + if ($id === false) { + Message::addError('main.parameter-missing', 'menuid'); + return; + } + $menu = Database::queryFirst("SELECT m.menuid, GROUP_CONCAT(l.locationid) AS locations + FROM serversetup_menu m + LEFT JOIN serversetup_menu_location l USING (menuid) + WHERE menuid = :id", compact('id')); + if ($menu === false) { + Message::addError('no-such-menu', $id); + return; + } + if (!$this->hasMenuPermission($id, 'ipxe.menu.edit')) { + Message::addError('locations.no-permission-location', 'TODO'); + return; + } + // TODO: Validate new locations to be saved (and actually save them) + + Database::exec('UPDATE serversetup_menu SET title = :title, timeoutms = :timeoutms + WHERE menuid = :menuid', [ + 'menuid' => $id, + 'title' => IPxe::sanitizeIpxeString(Request::post('title', '', 'string')), + 'timeoutms' => abs(Request::post('timeoutms', 0, 'int') * 1000), + ]); + if (User::hasPermission('ipxe.menu.edit', 0)) { + Database::exec('UPDATE serversetup_menu SET isdefault = (menuid = :menuid)', ['menuid' => $id]); + } + + $keepIds = []; + $entries = Request::post('entry', false, 'array'); + foreach ($entries as $key => $entry) { + $params = [ + 'entryid' => $entry['entryid'], // TODO validate + 'hotkey' => MenuEntry::filterKeyName($entry['hotkey']), + 'title' => IPxe::sanitizeIpxeString($entry['title']), + 'hidden' => (int)$entry['hidden'], + 'sortval' => (int)$entry['sortval'], + 'plainpass' => $entry['plainpass'], + 'menuid' => $menu['menuid'], + ]; + if (is_numeric($key)) { + $keepIds[] = $key; + $params['menuentryid'] = $key; + $params['md5pass'] = IPxe::makeMd5Pass($entry['plainpass'], $key); + $ret = Database::exec('UPDATE serversetup_menuentry + SET entryid = :entryid, hotkey = :hotkey, title = :title, hidden = :hidden, sortval = :sortval, + plainpass = :plainpass, md5pass = :md5pass + WHERE menuid = :menuid AND menuentryid = :menuentryid', $params, true); + } else { + $ret = Database::exec("INSERT INTO serversetup_menuentry + (menuid, entryid, hotkey, title, hidden, sortval, plainpass, md5pass) + VALUES (:menuid, :entryid, :hotkey, :title, :hidden, :sortval, :plainpass, '')", $params, true); + if ($ret && !empty($entry['plainpass'])) { + $key = Database::lastInsertId(); + Database::exec('UPDATE serversetup_menuentry SET md5pass = :md5pass WHERE menuentryid = :id', [ + 'md5pass' => IPxe::makeMd5Pass($entry['plainpass'], $key), + 'key' => $id, + ]); + } + } + if ($ret === false) { + Message::addWarning('error-saving-entry', $entry['title'], Database::lastError()); + } + } + Message::addSuccess('menu-saved'); + } + private function updateLocalAddress() { $newAddress = Request::post('ip', 'none'); @@ -184,4 +400,50 @@ class Page_ServerSetup extends Page exit; } + private function saveBootEntry() + { + $oldEntryId = Request::post('entryid', false, 'string'); + $newId = Request::post('newid', false, 'string'); + if (!preg_match('/^[a-z0-9\-_]{1,16}$/', $newId)) { + Message::addError('main.parameter-empty', 'newid'); + return; + } + $data = Request::post('entry', false); + if (!is_array($data)) { + Message::addError('missing-entry-data'); + return; + } + $type = Request::post('type', false, 'string'); + if ($type === 'exec') { + $entry = BootEntry::newStandardBootEntry($data); + } elseif ($type === 'script') { + $entry = BootEntry::newCustomBootEntry($data); + } else { + Message::addError('unknown-entry-type', $type); + return; + } + if ($entry === null) { + Message::addError('main.empty-field'); + return; + } + $params = [ + 'entryid' => $newId, + 'title' => Request::post('title', '', 'string'), + 'data' => json_encode($entry->toArray()), + ]; + // New or update? + if (empty($oldEntryId)) { + // New entry + Database::exec('INSERT INTO serversetup_bootentry (entryid, title, builtin, data) + VALUES (:entryid, :title, 0, :data)', $params); + Message::addSuccess('boot-entry-created', $newId); + } else { + // Edit existing entry + $params['oldid'] = $oldEntryId; + Database::exec('UPDATE serversetup_bootentry SET entryid = :entryid, title = :title, data = :data + WHERE entryid = :oldid AND builtin = 0', $params); + Message::addSuccess('boot-entry-updated', $newId); + } + } + } diff --git a/modules-available/serversetup-bwlp/permissions/permissions.json b/modules-available/serversetup-bwlp/permissions/permissions.json index 44927506..aa2aa001 100644 --- a/modules-available/serversetup-bwlp/permissions/permissions.json +++ b/modules-available/serversetup-bwlp/permissions/permissions.json @@ -8,7 +8,19 @@ "edit.address": { "location-aware": false }, - "edit.menu": { + "ipxe.bootentry.view": { + "location-aware": false + }, + "ipxe.bootentry.edit": { + "location-aware": false + }, + "ipxe.menu.view": { + "location-aware": false + }, + "ipxe.menu.edit": { + "location-aware": true + }, + "ipxe.localboot.edit": { "location-aware": false } } \ No newline at end of file diff --git a/modules-available/serversetup-bwlp/templates/download.html b/modules-available/serversetup-bwlp/templates/download.html new file mode 100644 index 00000000..6752f7fc --- /dev/null +++ b/modules-available/serversetup-bwlp/templates/download.html @@ -0,0 +1,38 @@ + + + \ No newline at end of file diff --git a/modules-available/serversetup-bwlp/templates/heading.html b/modules-available/serversetup-bwlp/templates/heading.html index d68360f1..e2aa0bff 100644 --- a/modules-available/serversetup-bwlp/templates/heading.html +++ b/modules-available/serversetup-bwlp/templates/heading.html @@ -1 +1,3 @@ -

{{lang_moduleHeading}}

\ No newline at end of file + \ No newline at end of file diff --git a/modules-available/serversetup-bwlp/templates/ipxe-new-boot-entry.html b/modules-available/serversetup-bwlp/templates/ipxe-new-boot-entry.html new file mode 100644 index 00000000..fd9e1d72 --- /dev/null +++ b/modules-available/serversetup-bwlp/templates/ipxe-new-boot-entry.html @@ -0,0 +1,108 @@ +

{{lang_newBootEntryHead}}

+ +
+
+ {{lang_bootEntryData}} +
+
+
+ + + + +
+
+ + +
+
+ + +
+
+ +
+ + +
+
+ + +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+ +
+
+ + +
+
+ +
+ +
+
+
+
+ + \ No newline at end of file diff --git a/modules-available/serversetup-bwlp/templates/ipxe.html b/modules-available/serversetup-bwlp/templates/ipxe.html deleted file mode 100644 index f4b0b4d3..00000000 --- a/modules-available/serversetup-bwlp/templates/ipxe.html +++ /dev/null @@ -1,117 +0,0 @@ -
- - - - -
-
- {{lang_bootMenu}} -
-
-

- {{lang_bootInfo}} -

-
- -
- {{lang_bootBehavior}} -
- - -
-
- - -
-
- - -
-
- -
- {{lang_menuDisplayTime}} -
- - {{lang_seconds}} -
-
- -
- {{lang_masterPassword}} -
- -
- {{lang_masterPasswordHelp}} -
- -
- {{lang_menuCustom}} - -
-
- - -
-
- - - - \ No newline at end of file diff --git a/modules-available/serversetup-bwlp/templates/menu-edit.html b/modules-available/serversetup-bwlp/templates/menu-edit.html new file mode 100644 index 00000000..cf10296e --- /dev/null +++ b/modules-available/serversetup-bwlp/templates/menu-edit.html @@ -0,0 +1,114 @@ +

{{lang_editMenuHead}}

+ +
+
+ {{title}} + {{^title}} + {{lang_newMenu}} + {{/title}} +
+
+
+ + + +
+
+ +
+
+ +
+
+
+
+ +
+
+
+ + {{lang_seconds}} +
+
+
+
+
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + {{#entries}} + + + + + {{#entryid}} + + {{/entryid}} + + + + {{/entries}} + +
{{lang_entryId}}{{lang_title}}{{lang_hotkey}}{{lang_sortOrder}}{{lang_password}}
+
+ + +
+
+ {{#entryid}} + + {{/entryid}} + {{^entryid}} + {{lang_spacer}} + {{/entryid}} + + + + + + + + +
+
+
+ +
+
+
+
\ No newline at end of file diff --git a/modules-available/serversetup-bwlp/templates/menu-list.html b/modules-available/serversetup-bwlp/templates/menu-list.html new file mode 100644 index 00000000..a862cff2 --- /dev/null +++ b/modules-available/serversetup-bwlp/templates/menu-list.html @@ -0,0 +1,36 @@ +

{{lang_listOfMenus}}

+ + + + + + + + + + + + {{#table}} + + + + + + + {{/table}} + +
{{lang_menuTitle}}{{lang_locationCount}}{{lang_isDefault}}{{lang_edit}}
+ {{title}} + + {{locationCount}} + + {{#isdefault}} + + {{/isdefault}} + + {{#allowEdit}} + + + + {{/allowEdit}} +
\ No newline at end of file -- cgit v1.2.3-55-g7522 From 43c02c0eb26a5253663db244346ec1f2a5b62eb2 Mon Sep 17 00:00:00 2001 From: Christian Hofmaier Date: Tue, 24 Jul 2018 12:41:51 +0200 Subject: [ipxe] added add-menu-button, added delete-menu-buttons, fixed bug that timeoutms didn't save, fixed bug that last edited menu gets the isDefault value, some table styling + language tags --- .../serversetup-bwlp/lang/de/template-tags.json | 6 ++ .../serversetup-bwlp/lang/en/template-tags.json | 6 ++ modules-available/serversetup-bwlp/page.inc.php | 75 ++++++++++++++++++---- .../serversetup-bwlp/permissions/permissions.json | 6 ++ .../serversetup-bwlp/templates/menu-list.html | 54 ++++++++++++++-- 5 files changed, 131 insertions(+), 16 deletions(-) (limited to 'modules-available/serversetup-bwlp/permissions/permissions.json') diff --git a/modules-available/serversetup-bwlp/lang/de/template-tags.json b/modules-available/serversetup-bwlp/lang/de/template-tags.json index 8d612ab0..14f1c134 100644 --- a/modules-available/serversetup-bwlp/lang/de/template-tags.json +++ b/modules-available/serversetup-bwlp/lang/de/template-tags.json @@ -1,5 +1,6 @@ { "lang_active": "Aktiv", + "lang_addMenu": "Menü hinzufügen", "lang_bootAddress": "Boot-Adresse des Servers", "lang_bootBehavior": "Standard-Bootverhalten", "lang_bootHint": "Das Bootmen\u00fc muss nach einer \u00c4nderung der IP-Adresse neu generiert werden. In der Regel geschieht dies automatisch, der Vorgang kann in der Sektion Bootmen\u00fc allerdings auch manuell ausgel\u00f6st werden.", @@ -8,11 +9,15 @@ "lang_bootMenuCreate": "Bootmen\u00fc erzeugen", "lang_chooseIP": "Bitte w\u00e4hlen Sie die IP-Adresse, \u00fcber die der Server von den Clients zum Booten angesprochen werden soll.", "lang_customEntry": "Eigener Eintrag", + "lang_menuDeleteConfirm": "Sind Sie sicher, dass Sie dieses Menü löschen wollen?", "lang_downloadImage": "USB-Image herunterladen", "lang_downloadRufus": "Rufus herunterladen", "lang_example": "Beispiel", "lang_generationFailed": "Erzeugen des Bootmen\u00fcs fehlgeschlagen. Der Netzwerkboot von bwLehrpool wird wahrscheinlich nicht funktionieren. Wenn Sie den Fehler nicht selbst beheben k\u00f6nnen, melden Sie bitte die Logausgabe an das bwLehrpool-Projekt.", + "lang_isDefault": "Standard", + "lang_listOfMenus": "Menüliste", "lang_localHDD": "Lokale HDD", + "lang_locationCount": "Anzahl Orte", "lang_masterPassword": "Master-Passwort", "lang_masterPasswordHelp": "Das Master-Passwort wird ben\u00f6tigt, um einen Booteintrag direkt am Client tempor\u00e4r durch Dr\u00fccken der Tab-Taste zu editieren. Da dies f\u00fcr Manipulation am Client genutzt werden kann, sollte diese Funktion unbedingt mit einem Passwort gesch\u00fctzt werden.", "lang_menuCustom": "Benutzerdefinierter Men\u00fczusatz", @@ -21,6 +26,7 @@ "lang_menuCustomHint3": "und w\u00e4hlen Sie als Standard-Bootverhalten ebenfalls custom.", "lang_menuDisplayTime": "Anzeigedauer des Men\u00fcs", "lang_menuGeneration": "Erzeugen des Bootmen\u00fcs", + "lang_menuTitle": "Menü", "lang_moduleHeading": "iPXE \/ Boot Menu", "lang_pxeBuilt": "PXE-Binary gebaut", "lang_seconds": "Sekunden", diff --git a/modules-available/serversetup-bwlp/lang/en/template-tags.json b/modules-available/serversetup-bwlp/lang/en/template-tags.json index 9bb55f93..d70159e2 100644 --- a/modules-available/serversetup-bwlp/lang/en/template-tags.json +++ b/modules-available/serversetup-bwlp/lang/en/template-tags.json @@ -1,5 +1,6 @@ { "lang_active": "Active", + "lang_addMenu": "Add Menu", "lang_bootAddress": "Boot Address of the Server", "lang_bootBehavior": "Default Boot Behavior", "lang_bootHint": "The Boot menu must be recreated after changing the IP address. Usually this is done automatically, but the process can also be triggered manually in the section of the boot menu.", @@ -8,11 +9,15 @@ "lang_bootMenuCreate": "Create Boot Menu", "lang_chooseIP": "Please select the IP address that the client server will use to boot.", "lang_customEntry": "Custom entry", + "lang_menuDeleteConfirm": "Are you sure you want to delete this menu?", "lang_downloadImage": "Download USB Image", "lang_downloadRufus": "Download Rufus", "lang_example": "Example", "lang_generationFailed": "Could not generate boot menu. The bwLehrpool-System might not work properly. If you can't fix the problem, please report the error log below to the bwLehrpool project.", + "lang_isDefault": "Default", + "lang_listOfMenus": "Menulist", "lang_localHDD": "Local HDD", + "lang_locationCount": "Number of Locations", "lang_masterPassword": "Master Password", "lang_masterPasswordHelp": "The master password is required to edit a boot menu entry. This should be set for security reasons.", "lang_menuCustom": "Custom Extra Menu", @@ -21,6 +26,7 @@ "lang_menuCustomHint3": "and select as the default boot behavior custom as well.", "lang_menuDisplayTime": "Menu Display Time", "lang_menuGeneration": "Generating boot menu...", + "lang_menuTitle": "Menu", "lang_moduleHeading": "iPXE \/ Boot Menu", "lang_pxeBuilt": "Built PXE binary", "lang_seconds": "Seconds", diff --git a/modules-available/serversetup-bwlp/page.inc.php b/modules-available/serversetup-bwlp/page.inc.php index dc53070b..6df4c49c 100644 --- a/modules-available/serversetup-bwlp/page.inc.php +++ b/modules-available/serversetup-bwlp/page.inc.php @@ -60,6 +60,11 @@ class Page_ServerSetup extends Page $this->saveMenu(); } + if ($action === 'deleteMenu') { + User::assertPermission('ipxe.menu.delete'); + $this->deleteMenu(); + } + if (Request::isPost()) { Util::redirect('?do=serversetup'); } @@ -132,26 +137,35 @@ class Page_ServerSetup extends Page private function showMenuList() { $allowedEdit = User::getAllowedLocations('ipxe.menu.edit'); + $allowedDelete = User::getAllowedLocations('ipxe.menu.delete'); // TODO Permission::addGlobalTags($perms, null, ['edit.menu', 'edit.address', 'download']); $res = Database::simpleQuery("SELECT m.menuid, m.title, m.isdefault, GROUP_CONCAT(l.locationid) AS locations FROM serversetup_menu m LEFT JOIN serversetup_menu_location l USING (menuid) GROUP BY menuid ORDER BY title"); - $table = []; + $menuTable = []; while ($row = $res->fetch(PDO::FETCH_ASSOC)) { if (empty($row['locations'])) { $locations = []; $row['allowEdit'] = in_array(0, $allowedEdit); + $row['allowDelete'] = in_array(0, $allowedDelete); } else { $locations = explode(',', $row['locations']); $row['allowEdit'] = empty(array_diff($locations, $allowedEdit)); + $row['allowDelete'] = empty(array_diff($locations, $allowedDelete)); } $row['locationCount'] = empty($locations) ? '' : count($locations); - $table[] = $row; + $menuTable[] = $row; + } + + $allowAddMenu = 'disabled'; + if (User::hasPermission('ipxe.menu.add')) { + $allowAddMenu = ''; } Render::addTemplate('menu-list', array( - 'table' => $table, + 'menuTable' => $menuTable, + 'allowAddMenu' => $allowAddMenu )); } @@ -173,8 +187,19 @@ class Page_ServerSetup extends Page private function showEditMenu() { $id = Request::get('id', false, 'int'); - $menu = Database::queryFirst("SELECT menuid, timeoutms, title, defaultentryid, isdefault + // if = edit, else = add new + if ($id != 0) { + $menu = Database::queryFirst("SELECT menuid, timeoutms, title, defaultentryid, isdefault FROM serversetup_menu WHERE menuid = :id", compact('id')); + } else { + $menu = []; + $menu['menuid'] = 0; + $menu['timeoutms'] = 0; + $menu['title'] = ''; + $menu['defaultentryid'] = null; + $menu['isdefault'] = false; + } + if ($menu === false) { Message::addError('invalid-menu-id', $id); Util::redirect('?do=serversetup&show=menu'); @@ -263,6 +288,22 @@ class Page_ServerSetup extends Page return true; } + private function deleteMenu() + { + $id = Request::post('deleteid', false, 'int'); + if ($id === false) { + Message::addError('main.parameter-missing', 'menuid'); + return; + } + if (!$this->hasMenuPermission($id, 'ipxe.menu.delete')) { + Message::addError('locations.no-permission-location', 'TODO'); + return; + } + Database::exec("DELETE FROM serversetup_menu WHERE menuid = :menuid", array("menuid" => $id)); + Message::addSuccess('menu-deleted'); + + } + private function saveMenu() { $id = Request::post('menuid', false, 'int'); @@ -284,15 +325,27 @@ class Page_ServerSetup extends Page } // TODO: Validate new locations to be saved (and actually save them) - Database::exec('UPDATE serversetup_menu SET title = :title, timeoutms = :timeoutms, defaultentryid = :defaultentryid + if ($id == 0) { + Database::exec("INSERT IGNORE INTO serversetup_menu (title, timeoutms, defaultentryid) VALUES (:title, :timeoutms, :defaultentryid)", [ + 'title' => IPxe::sanitizeIpxeString(Request::post('title', '', 'string')), + 'timeoutms' => abs(Request::post('timeout', 0, 'int') * 1000), + 'defaultentryid' => Request::post('defaultentry', null, 'int') + ]); + } else { + Database::exec('UPDATE serversetup_menu SET title = :title, timeoutms = :timeoutms, defaultentryid = :defaultentryid WHERE menuid = :menuid', [ - 'menuid' => $id, - 'title' => IPxe::sanitizeIpxeString(Request::post('title', '', 'string')), - 'timeoutms' => abs(Request::post('timeoutms', 0, 'int') * 1000), - 'defaultentryid' => Request::post('defaultentry', null, 'int'), - ]); + 'menuid' => $id, + 'title' => IPxe::sanitizeIpxeString(Request::post('title', '', 'string')), + 'timeoutms' => abs(Request::post('timeout', 0, 'int') * 1000), + 'defaultentryid' => Request::post('defaultentry', null, 'int'), + ]); + } + + $defmenu = Request::post('defmenu', false, 'boolean'); if (User::hasPermission('ipxe.menu.edit', 0)) { - Database::exec('UPDATE serversetup_menu SET isdefault = (menuid = :menuid)', ['menuid' => $id]); + if ($defmenu) { + Database::exec('UPDATE serversetup_menu SET isdefault = (menuid = :menuid)', ['menuid' => $id]); + } } $keepIds = []; diff --git a/modules-available/serversetup-bwlp/permissions/permissions.json b/modules-available/serversetup-bwlp/permissions/permissions.json index aa2aa001..e05b9f6c 100644 --- a/modules-available/serversetup-bwlp/permissions/permissions.json +++ b/modules-available/serversetup-bwlp/permissions/permissions.json @@ -20,6 +20,12 @@ "ipxe.menu.edit": { "location-aware": true }, + "ipxe.menu.add": { + "location-aware": false + }, + "ipxe.menu.delete": { + "location-aware": true + }, "ipxe.localboot.edit": { "location-aware": false } diff --git a/modules-available/serversetup-bwlp/templates/menu-list.html b/modules-available/serversetup-bwlp/templates/menu-list.html index a862cff2..1f190bb7 100644 --- a/modules-available/serversetup-bwlp/templates/menu-list.html +++ b/modules-available/serversetup-bwlp/templates/menu-list.html @@ -7,10 +7,11 @@ {{lang_locationCount}} {{lang_isDefault}} {{lang_edit}} + {{lang_delete}} - {{#table}} + {{#menuTable}} {{title}} @@ -18,19 +19,62 @@ {{locationCount}} - + {{#isdefault}} {{/isdefault}} - + {{#allowEdit}} {{/allowEdit}} + + {{#allowDelete}} + + {{/allowDelete}} + - {{/table}} + {{/menuTable}} - \ No newline at end of file + + + + + +
+ + +
+ + \ No newline at end of file -- cgit v1.2.3-55-g7522 From 60b1fc7329725612fb5e6289295c5efd0f31f36d Mon Sep 17 00:00:00 2001 From: Christian Hofmaier Date: Tue, 24 Jul 2018 15:36:48 +0200 Subject: [ipxe] Add Bootentry list Bootentry list with add/edit/delete functionality --- .../serversetup-bwlp/lang/de/template-tags.json | 3 + .../serversetup-bwlp/lang/en/template-tags.json | 3 + modules-available/serversetup-bwlp/page.inc.php | 47 +++++++++++++- .../serversetup-bwlp/permissions/permissions.json | 6 ++ .../serversetup-bwlp/templates/bootentry-list.html | 71 ++++++++++++++++++++++ 5 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 modules-available/serversetup-bwlp/templates/bootentry-list.html (limited to 'modules-available/serversetup-bwlp/permissions/permissions.json') diff --git a/modules-available/serversetup-bwlp/lang/de/template-tags.json b/modules-available/serversetup-bwlp/lang/de/template-tags.json index 14f1c134..bda890c1 100644 --- a/modules-available/serversetup-bwlp/lang/de/template-tags.json +++ b/modules-available/serversetup-bwlp/lang/de/template-tags.json @@ -1,14 +1,17 @@ { "lang_active": "Aktiv", + "lang_addBootentry": "Booteintrag hinzufügen", "lang_addMenu": "Menü hinzufügen", "lang_bootAddress": "Boot-Adresse des Servers", "lang_bootBehavior": "Standard-Bootverhalten", + "lang_bootentryTitle": "Booteintrag", "lang_bootHint": "Das Bootmen\u00fc muss nach einer \u00c4nderung der IP-Adresse neu generiert werden. In der Regel geschieht dies automatisch, der Vorgang kann in der Sektion Bootmen\u00fc allerdings auch manuell ausgel\u00f6st werden.", "lang_bootInfo": "Hier k\u00f6nnen Anpassungen am Erscheinungsbild des Bootmen\u00fcs vorgenommen werden.", "lang_bootMenu": "Bootmen\u00fc", "lang_bootMenuCreate": "Bootmen\u00fc erzeugen", "lang_chooseIP": "Bitte w\u00e4hlen Sie die IP-Adresse, \u00fcber die der Server von den Clients zum Booten angesprochen werden soll.", "lang_customEntry": "Eigener Eintrag", + "lang_bootentryDeleteConfirm": "Sind Sie sicher, dass Sie diesen Booteintrag löschen wollen?", "lang_menuDeleteConfirm": "Sind Sie sicher, dass Sie dieses Menü löschen wollen?", "lang_downloadImage": "USB-Image herunterladen", "lang_downloadRufus": "Rufus herunterladen", diff --git a/modules-available/serversetup-bwlp/lang/en/template-tags.json b/modules-available/serversetup-bwlp/lang/en/template-tags.json index d70159e2..121ed3e7 100644 --- a/modules-available/serversetup-bwlp/lang/en/template-tags.json +++ b/modules-available/serversetup-bwlp/lang/en/template-tags.json @@ -1,14 +1,17 @@ { "lang_active": "Active", + "lang_addBootentry": "Add Bootentry", "lang_addMenu": "Add Menu", "lang_bootAddress": "Boot Address of the Server", "lang_bootBehavior": "Default Boot Behavior", + "lang_bootentryTitle": "Bootentry", "lang_bootHint": "The Boot menu must be recreated after changing the IP address. Usually this is done automatically, but the process can also be triggered manually in the section of the boot menu.", "lang_bootInfo": "Here adjustments can be made to the appearance of the boot menu.", "lang_bootMenu": "Boot Menu", "lang_bootMenuCreate": "Create Boot Menu", "lang_chooseIP": "Please select the IP address that the client server will use to boot.", "lang_customEntry": "Custom entry", + "lang_bootentryDeleteConfirm": "Are you sure you want to delete this bootentry?", "lang_menuDeleteConfirm": "Are you sure you want to delete this menu?", "lang_downloadImage": "Download USB Image", "lang_downloadRufus": "Download Rufus", diff --git a/modules-available/serversetup-bwlp/page.inc.php b/modules-available/serversetup-bwlp/page.inc.php index 6df4c49c..3ef4371f 100644 --- a/modules-available/serversetup-bwlp/page.inc.php +++ b/modules-available/serversetup-bwlp/page.inc.php @@ -55,6 +55,11 @@ class Page_ServerSetup extends Page $this->saveBootEntry(); } + if ($action === 'deleteBootentry') { + User::assertPermission('ipxe.bootentry.delete'); + $this->deleteBootEntry(); + } + if ($action === 'savemenu') { User::assertPermission('ipxe.menu.edit'); $this->saveMenu(); @@ -73,6 +78,7 @@ class Page_ServerSetup extends Page if (User::hasPermission('ipxe.*')) { Dashboard::addSubmenu('?do=serversetup&show=menu', Dictionary::translate('submenu_menu', true)); + Dashboard::addSubmenu('?do=serversetup&show=bootentry', Dictionary::translate('submenu_bootentry', true)); } if (User::hasPermission('edit.address')) { Dashboard::addSubmenu('?do=serversetup&show=address', Dictionary::translate('submenu_address', true)); @@ -122,6 +128,10 @@ class Page_ServerSetup extends Page User::assertPermission('ipxe.menu.view'); $this->showMenuList(); break; + case 'bootentry': + User::assertPermission('ipxe.bootentry.view'); + $this->showBootentryList(); + break; default: Util::redirect('?do=serversetup'); break; @@ -134,6 +144,29 @@ class Page_ServerSetup extends Page Render::addTemplate('download'); } + private function showBootentryList() + { + $allowEdit = User::hasPermission('ipxe.bootentry.edit'); + $allowDelete = User::hasPermission('ipxe.bootentry.delete'); + $allowAdd = 'disabled'; + if (User::hasPermission('ipxe.bootentry.add')) { + $allowAdd = ''; + } + + $res = Database::simpleQuery("SELECT entryid, hotkey, title FROM serversetup_bootentry"); + $bootentryTable = []; + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + $bootentryTable[] = $row; + } + + Render::addTemplate('bootentry-list', array( + 'bootentryTable' => $bootentryTable, + 'allowAdd' => $allowAdd, + 'allowEdit' => $allowEdit, + 'allowDelete' => $allowDelete + )); + } + private function showMenuList() { $allowedEdit = User::getAllowedLocations('ipxe.menu.edit'); @@ -288,11 +321,22 @@ class Page_ServerSetup extends Page return true; } + private function deleteBootEntry() { + $id = Request::post('deleteid', false, 'string'); + if ($id === false) { + Message::addError('main.parameter-missing', 'deleteid'); + return; + } + Database::exec("DELETE FROM serversetup_bootentry WHERE entryid = :entryid", array("entryid" => $id)); + // TODO: Redirect to &show=bootentry + Message::addSuccess('bootentry-deleted'); + } + private function deleteMenu() { $id = Request::post('deleteid', false, 'int'); if ($id === false) { - Message::addError('main.parameter-missing', 'menuid'); + Message::addError('main.parameter-missing', 'deleteid'); return; } if (!$this->hasMenuPermission($id, 'ipxe.menu.delete')) { @@ -512,6 +556,7 @@ class Page_ServerSetup extends Page $params['oldid'] = $oldEntryId; Database::exec('UPDATE serversetup_bootentry SET entryid = :entryid, title = :title, data = :data WHERE entryid = :oldid AND builtin = 0', $params); + // TODO: Redirect to &show=bootentry Message::addSuccess('boot-entry-updated', $newId); } } diff --git a/modules-available/serversetup-bwlp/permissions/permissions.json b/modules-available/serversetup-bwlp/permissions/permissions.json index e05b9f6c..5b97e5c2 100644 --- a/modules-available/serversetup-bwlp/permissions/permissions.json +++ b/modules-available/serversetup-bwlp/permissions/permissions.json @@ -14,6 +14,12 @@ "ipxe.bootentry.edit": { "location-aware": false }, + "ipxe.bootentry.add": { + "location-aware": false + }, + "ipxe.bootentry.delete": { + "location-aware": false + }, "ipxe.menu.view": { "location-aware": false }, diff --git a/modules-available/serversetup-bwlp/templates/bootentry-list.html b/modules-available/serversetup-bwlp/templates/bootentry-list.html new file mode 100644 index 00000000..f9e881b2 --- /dev/null +++ b/modules-available/serversetup-bwlp/templates/bootentry-list.html @@ -0,0 +1,71 @@ + + + + + + + + + + + {{#bootentryTable}} + + + + + + + {{/bootentryTable}} + +
{{lang_bootentryTitle}}Hotkey{{lang_edit}}{{lang_delete}}
+ {{title}} + + {{hotkey}} + + {{#allowEdit}} + + + + {{/allowEdit}} + + {{#allowDelete}} + + {{/allowDelete}} +
+ + + +
+ + +
+ + \ No newline at end of file -- cgit v1.2.3-55-g7522 From e32cc22b6efa1bebe057eba42a596bc367b6c197 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Tue, 11 Dec 2018 17:47:35 +0100 Subject: [serversetup-bwlp] Simplify permissions --- .../serversetup-bwlp/lang/de/permissions.json | 7 +++++- modules-available/serversetup-bwlp/page.inc.php | 26 +++++----------------- .../serversetup-bwlp/permissions/permissions.json | 12 ---------- .../serversetup-bwlp/templates/bootentry-list.html | 16 +++++++------ 4 files changed, 20 insertions(+), 41 deletions(-) (limited to 'modules-available/serversetup-bwlp/permissions/permissions.json') diff --git a/modules-available/serversetup-bwlp/lang/de/permissions.json b/modules-available/serversetup-bwlp/lang/de/permissions.json index 98baec3c..a6cdbce2 100644 --- a/modules-available/serversetup-bwlp/lang/de/permissions.json +++ b/modules-available/serversetup-bwlp/lang/de/permissions.json @@ -2,5 +2,10 @@ "access-page": "Seite sehen.", "download": "USB-Image herunterladen.", "edit.address": "Boot-Adresse des Servers ausw\u00e4hlen.", - "edit.menu": "Bootmen\u00fc anpassen." + "edit.menu": "Bootmen\u00fc anpassen.", + "ipxe.bootentry.edit": "Einen Boot-Eintrag bearbeiten.", + "ipxe.bootentry.view": "Liste aller Boot-Eintr\u00e4ge sehen.", + "ipxe.localboot.edit": "Ausnahmeliste f\u00fcr Localboot-Modus bearbeiten.", + "ipxe.menu.edit": "Men\u00fc editieren.", + "ipxe.menu.view": "Liste der Men\u00fcs sehen." } \ No newline at end of file diff --git a/modules-available/serversetup-bwlp/page.inc.php b/modules-available/serversetup-bwlp/page.inc.php index 8cd20c75..f8a21227 100644 --- a/modules-available/serversetup-bwlp/page.inc.php +++ b/modules-available/serversetup-bwlp/page.inc.php @@ -73,7 +73,7 @@ class Page_ServerSetup extends Page } if ($action === 'deleteBootentry') { - User::assertPermission('ipxe.bootentry.delete'); + User::assertPermission('ipxe.bootentry.edit'); $this->deleteBootEntry(); } @@ -83,7 +83,7 @@ class Page_ServerSetup extends Page } if ($action === 'deleteMenu') { - User::assertPermission('ipxe.menu.delete'); + // Permcheck in function $this->deleteMenu(); } @@ -173,13 +173,8 @@ class Page_ServerSetup extends Page private function showBootentryList() { $allowEdit = User::hasPermission('ipxe.bootentry.edit'); - $allowDelete = User::hasPermission('ipxe.bootentry.delete'); - $allowAdd = 'disabled'; - if (User::hasPermission('ipxe.bootentry.add')) { - $allowAdd = ''; - } - $res = Database::simpleQuery("SELECT entryid, hotkey, title FROM serversetup_bootentry"); + $res = Database::simpleQuery("SELECT entryid, hotkey, title, builtin FROM serversetup_bootentry"); $bootentryTable = []; while ($row = $res->fetch(PDO::FETCH_ASSOC)) { $bootentryTable[] = $row; @@ -187,16 +182,13 @@ class Page_ServerSetup extends Page Render::addTemplate('bootentry-list', array( 'bootentryTable' => $bootentryTable, - 'allowAdd' => $allowAdd, 'allowEdit' => $allowEdit, - 'allowDelete' => $allowDelete )); } private function showMenuList() { $allowedEdit = User::getAllowedLocations('ipxe.menu.edit'); - $allowedDelete = User::getAllowedLocations('ipxe.menu.delete'); // TODO Permission::addGlobalTags($perms, null, ['edit.menu', 'edit.address', 'download']); @@ -207,24 +199,16 @@ class Page_ServerSetup extends Page if (empty($row['locations'])) { $locations = []; $row['allowEdit'] = in_array(0, $allowedEdit); - $row['allowDelete'] = in_array(0, $allowedDelete); } else { $locations = explode(',', $row['locations']); $row['allowEdit'] = empty(array_diff($locations, $allowedEdit)); - $row['allowDelete'] = empty(array_diff($locations, $allowedDelete)); } $row['locationCount'] = empty($locations) ? '' : count($locations); $menuTable[] = $row; } - $allowAddMenu = 'disabled'; - if (User::hasPermission('ipxe.menu.add')) { - $allowAddMenu = ''; - } - Render::addTemplate('menu-list', array( 'menuTable' => $menuTable, - 'allowAddMenu' => $allowAddMenu, 'showSetDefault' => User::hasPermission('ipxe.menu.edit', 0) )); } @@ -437,8 +421,8 @@ class Page_ServerSetup extends Page Message::addError('main.parameter-missing', 'deleteid'); return; } - if (!$this->hasMenuPermission($id, 'ipxe.menu.delete')) { - Message::addError('locations.no-permission-location', 'TODO'); + if (!$this->hasMenuPermission($id, 'ipxe.menu.edit')) { + Message::addError('locations.no-permission-location', $id); return; } Database::exec("DELETE FROM serversetup_menu WHERE menuid = :menuid", array("menuid" => $id)); diff --git a/modules-available/serversetup-bwlp/permissions/permissions.json b/modules-available/serversetup-bwlp/permissions/permissions.json index 5b97e5c2..aa2aa001 100644 --- a/modules-available/serversetup-bwlp/permissions/permissions.json +++ b/modules-available/serversetup-bwlp/permissions/permissions.json @@ -14,24 +14,12 @@ "ipxe.bootentry.edit": { "location-aware": false }, - "ipxe.bootentry.add": { - "location-aware": false - }, - "ipxe.bootentry.delete": { - "location-aware": false - }, "ipxe.menu.view": { "location-aware": false }, "ipxe.menu.edit": { "location-aware": true }, - "ipxe.menu.add": { - "location-aware": false - }, - "ipxe.menu.delete": { - "location-aware": true - }, "ipxe.localboot.edit": { "location-aware": false } diff --git a/modules-available/serversetup-bwlp/templates/bootentry-list.html b/modules-available/serversetup-bwlp/templates/bootentry-list.html index f9e881b2..929b8c47 100644 --- a/modules-available/serversetup-bwlp/templates/bootentry-list.html +++ b/modules-available/serversetup-bwlp/templates/bootentry-list.html @@ -24,21 +24,23 @@ {{/allowEdit}} - {{#allowDelete}} - - {{/allowDelete}} + {{/allowEdit}} {{/bootentryTable}} -- cgit v1.2.3-55-g7522 From 856ff2fa8e9b103ee4033c8ceec3e80af87009bb Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Fri, 11 Jan 2019 16:48:54 +0100 Subject: [serversetup-bwlp] Decouple location assigning from menu editing --- modules-available/serversetup-bwlp/install.inc.php | 3 +- modules-available/serversetup-bwlp/page.inc.php | 133 ++++++++++++++------- .../serversetup-bwlp/permissions/permissions.json | 3 + .../templates/menu-assign-location.html | 69 +++++++++++ .../serversetup-bwlp/templates/menu-edit.html | 51 ++------ 5 files changed, 169 insertions(+), 90 deletions(-) create mode 100644 modules-available/serversetup-bwlp/templates/menu-assign-location.html (limited to 'modules-available/serversetup-bwlp/permissions/permissions.json') diff --git a/modules-available/serversetup-bwlp/install.inc.php b/modules-available/serversetup-bwlp/install.inc.php index 67d6693f..25579c13 100644 --- a/modules-available/serversetup-bwlp/install.inc.php +++ b/modules-available/serversetup-bwlp/install.inc.php @@ -54,7 +54,8 @@ $res[] = tableCreate('serversetup_localboot', " // Add defaultentry override column if (!tableHasColumn('serversetup_menu_location', 'defaultentryid')) { - if (Database::exec('ALTER TABLE serversetup_menu_location ADD COLUMN `defaultentryid` int(11) DEFAULT NULL')) { + if (Database::exec('ALTER TABLE serversetup_menu_location ADD COLUMN `defaultentryid` int(11) DEFAULT NULL, + ADD KEY `defaultentryid` (`defaultentryid`)') !== false) { $res[] = UPDATE_DONE; } else { $res[] = UPDATE_FAILED; diff --git a/modules-available/serversetup-bwlp/page.inc.php b/modules-available/serversetup-bwlp/page.inc.php index f8a21227..004077dc 100644 --- a/modules-available/serversetup-bwlp/page.inc.php +++ b/modules-available/serversetup-bwlp/page.inc.php @@ -82,6 +82,12 @@ class Page_ServerSetup extends Page $this->saveMenu(); } + if ($action === 'savelocation') { + // Permcheck in function + $this->saveLocationMenu(); + Util::redirect('?do=locations'); + } + if ($action === 'deleteMenu') { // Permcheck in function $this->deleteMenu(); @@ -158,6 +164,10 @@ class Page_ServerSetup extends Page User::assertPermission('edit.address'); $this->showEditAddress(); break; + case 'assignlocation': + // Permcheck in function + $this->showEditLocation(); + break; default: Util::redirect('?do=serversetup'); break; @@ -294,19 +304,6 @@ class Page_ServerSetup extends Page $entry['isdefault'] = ($entry['menuentryid'] == $menu['defaultentryid']); // TODO: plainpass only when permissions } - // TODO: Make assigned locations editable - - $currentLocations = Database::queryColumnArray('SELECT locationid FROM serversetup_menu_location - WHERE menuid = :menuid', array('menuid' => $id)); - $menu['locations'] = Location::getLocations($currentLocations); - - // if user has no permission to edit for this location, disable the location in the select - $allowedEditLocations = User::getAllowedLocations('ipxe.menu.edit'); - foreach ($menu['locations'] as &$loc) { - if (!in_array($loc["locationid"], $allowedEditLocations)) { - $loc["disabled"] = "disabled"; - } - } Permission::addGlobalTags($menu['perms'], 0, ['ipxe.menu.edit']); Render::addTemplate('menu-edit', $menu); @@ -437,23 +434,6 @@ class Page_ServerSetup extends Page return; } - $locationids = Request::post('locations', [], "ARRAY"); - // check if the user is allowed to edit the menu on the affected locations - $allowedEditLocations = User::getAllowedLocations('ipxe.menu.edit'); - $currentLocations = Database::queryColumnArray('SELECT locationid FROM serversetup_menu_location - WHERE menuid = :menuid', array('menuid' => $id)); - // permission denied if the user tries to assign or remove a menu to/from locations he has no edit rights for - // or if the user tries to save a menu without locations but does not have the permission for the root location (0) - if (!in_array(0, $allowedEditLocations) - && ( - (!empty(array_diff($locationids, $allowedEditLocations)) && !empty(array_diff($currentLocations, $allowedEditLocations))) - || empty($locationids) - ) - ) { - Message::addError('main.no-permission'); - Util::redirect('?do=serversetup'); - } - $insertParams = [ 'title' => IPxe::sanitizeIpxeString(Request::post('title', '', 'string')), 'timeoutms' => abs(Request::post('timeout', 0, 'int') * 1000), @@ -462,18 +442,13 @@ class Page_ServerSetup extends Page Database::exec("INSERT INTO serversetup_menu (title, timeoutms, isdefault) VALUES (:title, :timeoutms, 0)", $insertParams); $menu['menuid'] = $id = Database::lastInsertId(); } else { - $menu = Database::queryFirst("SELECT m.menuid, GROUP_CONCAT(l.locationid) AS locations + $menu = Database::queryFirst("SELECT m.menuid FROM serversetup_menu m - LEFT JOIN serversetup_menu_location l USING (menuid) WHERE menuid = :id", compact('id')); if ($menu === false) { Message::addError('no-such-menu', $id); return; } - if (!$this->hasMenuPermission($id, 'ipxe.menu.edit')) { - Message::addError('locations.no-permission-location', 'TODO'); - return; - } $insertParams['menuid'] = $id; Database::exec('UPDATE serversetup_menu SET title = :title, timeoutms = :timeoutms WHERE menuid = :menuid', $insertParams); @@ -562,15 +537,6 @@ class Page_ServerSetup extends Page Database::exec('UPDATE serversetup_menu SET defaultentryid = NULL WHERE menuid = :menuid', ['menuid' => $menu['menuid']]); } - Database::exec('DELETE FROM serversetup_menu_location WHERE menuid = :menuid', ['menuid' => $menu['menuid']]); - if (!empty($locationids)) { - Database::exec('DELETE FROM serversetup_menu_location WHERE locationid IN (:locationids)', ['locationids' => $locationids]); - foreach ($locationids as $locationid) { - Database::exec('INSERT INTO serversetup_menu_location (menuid, locationid) VALUES (:menuid, :locationid)', - ['menuid' => $menu['menuid'], 'locationid' => $locationid]); - } - } - Message::addSuccess('menu-saved'); } @@ -653,4 +619,81 @@ class Page_ServerSetup extends Page Util::redirect('?do=serversetup&show=bootentry'); } + private function showEditLocation() + { + $locationId = Request::get('locationid', false, 'int'); + $loc = Location::get($locationId); + if ($loc === false) { + Message::addError('locations.invalid-location-id', $locationId); + return; + } + User::assertPermission('ipxe.menu.assign', $locationId); + // List of menu entries + $res = Database::simpleQuery('SELECT menuentryid, title FROM serversetup_menuentry'); + $menuEntries = $res->fetchAll(PDO::FETCH_KEY_PAIR); + // List of menus + $data = [ + 'locationid' => $locationId, + 'locationName' => $loc['locationname'], + ]; + $res = Database::simpleQuery('SELECT m.menuid, m.title, ml.locationid, ml.defaultentryid, GROUP_CONCAT(me.menuentryid) AS entries FROM serversetup_menu m + LEFT JOIN serversetup_menu_location ml ON (m.menuid = ml.menuid AND ml.locationid = :locationid) + INNER JOIN serversetup_menuentry me ON (m.menuid = me.menuid AND me.entryid IS NOT NULL) + GROUP BY menuid + ORDER BY m.title ASC', ['locationid' => $locationId]); + $menus = []; + $hasDefault = false; + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + $eids = explode(',', $row['entries']); + $row['entries'] = []; + foreach ($eids as $eid) { + $row['entries'][] = [ + 'id' => $eid, + 'title' => $menuEntries[$eid], + 'selected' => ($eid == $row['defaultentryid'] ? 'selected' : ''), + ]; + } + if ($row['locationid'] !== null) { + $hasDefault = true; + $row['menu_selected'] = 'checked'; + } + $menus[] = $row; + } + if (!$hasDefault) { + $data['default_selected'] = 'checked'; + } + $data['list'] = $menus; + Render::addTemplate('menu-assign-location', $data); + } + + private function saveLocationMenu() + { + $locationId = Request::post('locationid', false, 'int'); + $loc = Location::get($locationId); + if ($loc === false) { + Message::addError('locations.invalid-location-id', $locationId); + return; + } + User::assertPermission('ipxe.menu.assign', $locationId); + $menuId = Request::post('menuid', false, 'int'); + if ($menuId === 0) { + Database::exec('DELETE FROM serversetup_menu_location WHERE locationid = :locationid', + ['locationid' => $locationId]); + Message::addSuccess('location-use-default', $loc['locationname']); + return; + } + $defaultEntryId = Request::post('defaultentryid-' . $menuId, 0, 'int'); + if ($defaultEntryId === 0) { + $defaultEntryId = null; + } + Database::exec('INSERT INTO serversetup_menu_location (menuid, locationid, defaultentryid) + VALUES (:menuid, :locationid, :defaultentryid) + ON DUPLICATE KEY UPDATE menuid = :menuid, defaultentryid = :defaultentryid', [ + 'menuid' => $menuId, + 'locationid' => $locationId, + 'defaultentryid' => $defaultEntryId + ]); + Message::addSuccess('location-menu-assigned', $loc['locationname']); + } + } diff --git a/modules-available/serversetup-bwlp/permissions/permissions.json b/modules-available/serversetup-bwlp/permissions/permissions.json index aa2aa001..33cc9cea 100644 --- a/modules-available/serversetup-bwlp/permissions/permissions.json +++ b/modules-available/serversetup-bwlp/permissions/permissions.json @@ -18,6 +18,9 @@ "location-aware": false }, "ipxe.menu.edit": { + "location-aware": false + }, + "ipxe.menu.assign": { "location-aware": true }, "ipxe.localboot.edit": { diff --git a/modules-available/serversetup-bwlp/templates/menu-assign-location.html b/modules-available/serversetup-bwlp/templates/menu-assign-location.html new file mode 100644 index 00000000..077d137e --- /dev/null +++ b/modules-available/serversetup-bwlp/templates/menu-assign-location.html @@ -0,0 +1,69 @@ +

{{lang_assignMenuToLocation}}

+

{{locationName}}

+ +
+ + + + + + + + + + + + + + + + + + + {{#list}} + + + + + + {{/list}} + +
{{lang_menuTitle}}{{lang_menuEntryOverride}}
+
+ + +
+
+ {{lang_useDefaultMenu}} +
+
+ + +
+
+ {{title}} + + +
+ +
+ +
+ +
+ +
+ + \ No newline at end of file diff --git a/modules-available/serversetup-bwlp/templates/menu-edit.html b/modules-available/serversetup-bwlp/templates/menu-edit.html index 2141103f..21c6a30e 100644 --- a/modules-available/serversetup-bwlp/templates/menu-edit.html +++ b/modules-available/serversetup-bwlp/templates/menu-edit.html @@ -36,33 +36,18 @@ -
-
- -
-
- - {{#globalMenuWarning}} - - {{/globalMenuWarning}} -
-
- - - + + + - - - - + + + + @@ -289,28 +274,6 @@ var spacerText = "{{lang_spacer}}"; document.addEventListener("DOMContentLoaded", function() { - var locationSelect = $('#panel-locations'); - locationSelect.multiselect({numberDisplayed: 1}); - var globalMenuWarning = $('#global-menu-warning'); - if (globalMenuWarning.length) { - var saveButton = $('#save-button'); - if (locationSelect.val() !== null) { - saveButton.prop('disabled', false); - globalMenuWarning.hide(); - } else { - saveButton.prop('disabled', true); - globalMenuWarning.show(); - } - locationSelect.change(function () { - if ($(this).val() !== null) { - saveButton.prop('disabled', false); - globalMenuWarning.hide(); - } else { - saveButton.prop('disabled', true); - globalMenuWarning.show(); - } - }); - } function reassignSortValues() { var startValue = 1; -- cgit v1.2.3-55-g7522 From c2a6dfb649107ce3ad64162e2bb2c3c7275650fb Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Tue, 12 Feb 2019 14:55:12 +0100 Subject: [serversetup*] PXELinux and iPXE side-by-side --- .../serversetup-bwlp-ipxe/api.inc.php | 254 +++++++ .../serversetup-bwlp-ipxe/config.json | 8 + .../serversetup-bwlp-ipxe/hooks/bootup.inc.php | 11 + .../hooks/ipxe-update.inc.php | 10 + .../hooks/main-warning.inc.php | 6 + .../serversetup-bwlp-ipxe/inc/bootentry.inc.php | 258 +++++++ .../serversetup-bwlp-ipxe/inc/ipxe.inc.php | 453 +++++++++++ .../serversetup-bwlp-ipxe/inc/ipxemenu.inc.php | 142 ++++ .../serversetup-bwlp-ipxe/inc/localboot.inc.php | 15 + .../serversetup-bwlp-ipxe/inc/menuentry.inc.php | 177 +++++ .../serversetup-bwlp-ipxe/inc/pxelinux.inc.php | 302 ++++++++ .../serversetup-bwlp-ipxe/install.inc.php | 89 +++ .../serversetup-bwlp-ipxe/lang/de/messages.json | 21 + .../serversetup-bwlp-ipxe/lang/de/module.json | 19 + .../serversetup-bwlp-ipxe/lang/de/permissions.json | 12 + .../lang/de/template-tags.json | 94 +++ .../serversetup-bwlp-ipxe/lang/en/messages.json | 5 + .../serversetup-bwlp-ipxe/lang/en/module.json | 3 + .../serversetup-bwlp-ipxe/lang/en/permissions.json | 6 + .../lang/en/template-tags.json | 42 ++ .../serversetup-bwlp-ipxe/lang/pt/messages.json | 3 + .../serversetup-bwlp-ipxe/lang/pt/module.json | 3 + .../lang/pt/template-tags.json | 38 + .../serversetup-bwlp-ipxe/page.inc.php | 829 +++++++++++++++++++++ .../permissions/permissions.json | 29 + .../templates/bootentry-list.html | 83 +++ .../serversetup-bwlp-ipxe/templates/download.html | 53 ++ .../serversetup-bwlp-ipxe/templates/heading.html | 3 + .../serversetup-bwlp-ipxe/templates/ipaddress.html | 44 ++ .../templates/ipxe-new-boot-entry.html | 165 ++++ .../templates/ipxe_update.html | 54 ++ .../serversetup-bwlp-ipxe/templates/localboot.html | 59 ++ .../templates/menu-assign-location.html | 69 ++ .../serversetup-bwlp-ipxe/templates/menu-edit.html | 368 +++++++++ .../serversetup-bwlp-ipxe/templates/menu-list.html | 100 +++ .../serversetup-bwlp-pxelinux/config.json | 3 + .../hooks/ipxe-update.inc.php | 9 + .../hooks/main-warning.inc.php | 6 + .../serversetup-bwlp-pxelinux/inc/ipxe.inc.php | 224 ++++++ .../lang/de/messages.json | 5 + .../serversetup-bwlp-pxelinux/lang/de/module.json | 4 + .../lang/de/permissions.json | 6 + .../lang/de/template-tags.json | 33 + .../lang/en/messages.json | 5 + .../serversetup-bwlp-pxelinux/lang/en/module.json | 3 + .../lang/en/permissions.json | 6 + .../lang/en/template-tags.json | 33 + .../lang/pt/messages.json | 3 + .../serversetup-bwlp-pxelinux/lang/pt/module.json | 3 + .../lang/pt/template-tags.json | 38 + .../serversetup-bwlp-pxelinux/page.inc.php | 187 +++++ .../permissions/permissions.json | 14 + .../templates/heading.html | 1 + .../templates/ipaddress.html | 37 + .../serversetup-bwlp-pxelinux/templates/ipxe.html | 117 +++ .../templates/ipxe_update.html | 38 + modules-available/serversetup-bwlp/api.inc.php | 254 ------- modules-available/serversetup-bwlp/config.json | 8 - .../serversetup-bwlp/hooks/bootup.inc.php | 11 - .../serversetup-bwlp/hooks/ipxe-update.inc.php | 10 - .../serversetup-bwlp/hooks/main-warning.inc.php | 6 - .../serversetup-bwlp/inc/bootentry.inc.php | 258 ------- .../serversetup-bwlp/inc/ipxe.inc.php | 453 ----------- .../serversetup-bwlp/inc/ipxemenu.inc.php | 142 ---- .../serversetup-bwlp/inc/localboot.inc.php | 15 - .../serversetup-bwlp/inc/menuentry.inc.php | 177 ----- .../serversetup-bwlp/inc/pxelinux.inc.php | 302 -------- modules-available/serversetup-bwlp/install.inc.php | 89 --- .../serversetup-bwlp/lang/de/messages.json | 21 - .../serversetup-bwlp/lang/de/module.json | 19 - .../serversetup-bwlp/lang/de/permissions.json | 12 - .../serversetup-bwlp/lang/de/template-tags.json | 94 --- .../serversetup-bwlp/lang/en/messages.json | 5 - .../serversetup-bwlp/lang/en/module.json | 3 - .../serversetup-bwlp/lang/en/permissions.json | 6 - .../serversetup-bwlp/lang/en/template-tags.json | 42 -- .../serversetup-bwlp/lang/pt/messages.json | 3 - .../serversetup-bwlp/lang/pt/module.json | 3 - .../serversetup-bwlp/lang/pt/template-tags.json | 38 - modules-available/serversetup-bwlp/page.inc.php | 829 --------------------- .../serversetup-bwlp/permissions/permissions.json | 29 - .../serversetup-bwlp/templates/bootentry-list.html | 83 --- .../serversetup-bwlp/templates/download.html | 53 -- .../serversetup-bwlp/templates/heading.html | 3 - .../serversetup-bwlp/templates/ipaddress.html | 44 -- .../templates/ipxe-new-boot-entry.html | 165 ---- .../serversetup-bwlp/templates/ipxe_update.html | 54 -- .../serversetup-bwlp/templates/localboot.html | 59 -- .../templates/menu-assign-location.html | 69 -- .../serversetup-bwlp/templates/menu-edit.html | 368 --------- .../serversetup-bwlp/templates/menu-list.html | 100 --- 91 files changed, 4602 insertions(+), 3827 deletions(-) create mode 100644 modules-available/serversetup-bwlp-ipxe/api.inc.php create mode 100644 modules-available/serversetup-bwlp-ipxe/config.json create mode 100644 modules-available/serversetup-bwlp-ipxe/hooks/bootup.inc.php create mode 100644 modules-available/serversetup-bwlp-ipxe/hooks/ipxe-update.inc.php create mode 100644 modules-available/serversetup-bwlp-ipxe/hooks/main-warning.inc.php create mode 100644 modules-available/serversetup-bwlp-ipxe/inc/bootentry.inc.php create mode 100644 modules-available/serversetup-bwlp-ipxe/inc/ipxe.inc.php create mode 100644 modules-available/serversetup-bwlp-ipxe/inc/ipxemenu.inc.php create mode 100644 modules-available/serversetup-bwlp-ipxe/inc/localboot.inc.php create mode 100644 modules-available/serversetup-bwlp-ipxe/inc/menuentry.inc.php create mode 100644 modules-available/serversetup-bwlp-ipxe/inc/pxelinux.inc.php create mode 100644 modules-available/serversetup-bwlp-ipxe/install.inc.php create mode 100644 modules-available/serversetup-bwlp-ipxe/lang/de/messages.json create mode 100644 modules-available/serversetup-bwlp-ipxe/lang/de/module.json create mode 100644 modules-available/serversetup-bwlp-ipxe/lang/de/permissions.json create mode 100644 modules-available/serversetup-bwlp-ipxe/lang/de/template-tags.json create mode 100644 modules-available/serversetup-bwlp-ipxe/lang/en/messages.json create mode 100644 modules-available/serversetup-bwlp-ipxe/lang/en/module.json create mode 100644 modules-available/serversetup-bwlp-ipxe/lang/en/permissions.json create mode 100644 modules-available/serversetup-bwlp-ipxe/lang/en/template-tags.json create mode 100644 modules-available/serversetup-bwlp-ipxe/lang/pt/messages.json create mode 100644 modules-available/serversetup-bwlp-ipxe/lang/pt/module.json create mode 100644 modules-available/serversetup-bwlp-ipxe/lang/pt/template-tags.json create mode 100644 modules-available/serversetup-bwlp-ipxe/page.inc.php create mode 100644 modules-available/serversetup-bwlp-ipxe/permissions/permissions.json create mode 100644 modules-available/serversetup-bwlp-ipxe/templates/bootentry-list.html create mode 100644 modules-available/serversetup-bwlp-ipxe/templates/download.html create mode 100644 modules-available/serversetup-bwlp-ipxe/templates/heading.html create mode 100644 modules-available/serversetup-bwlp-ipxe/templates/ipaddress.html create mode 100644 modules-available/serversetup-bwlp-ipxe/templates/ipxe-new-boot-entry.html create mode 100644 modules-available/serversetup-bwlp-ipxe/templates/ipxe_update.html create mode 100644 modules-available/serversetup-bwlp-ipxe/templates/localboot.html create mode 100644 modules-available/serversetup-bwlp-ipxe/templates/menu-assign-location.html create mode 100644 modules-available/serversetup-bwlp-ipxe/templates/menu-edit.html create mode 100644 modules-available/serversetup-bwlp-ipxe/templates/menu-list.html create mode 100644 modules-available/serversetup-bwlp-pxelinux/config.json create mode 100644 modules-available/serversetup-bwlp-pxelinux/hooks/ipxe-update.inc.php create mode 100644 modules-available/serversetup-bwlp-pxelinux/hooks/main-warning.inc.php create mode 100644 modules-available/serversetup-bwlp-pxelinux/inc/ipxe.inc.php create mode 100644 modules-available/serversetup-bwlp-pxelinux/lang/de/messages.json create mode 100644 modules-available/serversetup-bwlp-pxelinux/lang/de/module.json create mode 100644 modules-available/serversetup-bwlp-pxelinux/lang/de/permissions.json create mode 100644 modules-available/serversetup-bwlp-pxelinux/lang/de/template-tags.json create mode 100644 modules-available/serversetup-bwlp-pxelinux/lang/en/messages.json create mode 100644 modules-available/serversetup-bwlp-pxelinux/lang/en/module.json create mode 100644 modules-available/serversetup-bwlp-pxelinux/lang/en/permissions.json create mode 100644 modules-available/serversetup-bwlp-pxelinux/lang/en/template-tags.json create mode 100644 modules-available/serversetup-bwlp-pxelinux/lang/pt/messages.json create mode 100644 modules-available/serversetup-bwlp-pxelinux/lang/pt/module.json create mode 100644 modules-available/serversetup-bwlp-pxelinux/lang/pt/template-tags.json create mode 100644 modules-available/serversetup-bwlp-pxelinux/page.inc.php create mode 100644 modules-available/serversetup-bwlp-pxelinux/permissions/permissions.json create mode 100644 modules-available/serversetup-bwlp-pxelinux/templates/heading.html create mode 100644 modules-available/serversetup-bwlp-pxelinux/templates/ipaddress.html create mode 100644 modules-available/serversetup-bwlp-pxelinux/templates/ipxe.html create mode 100644 modules-available/serversetup-bwlp-pxelinux/templates/ipxe_update.html delete mode 100644 modules-available/serversetup-bwlp/api.inc.php delete mode 100644 modules-available/serversetup-bwlp/config.json delete mode 100644 modules-available/serversetup-bwlp/hooks/bootup.inc.php delete mode 100644 modules-available/serversetup-bwlp/hooks/ipxe-update.inc.php delete mode 100644 modules-available/serversetup-bwlp/hooks/main-warning.inc.php delete mode 100644 modules-available/serversetup-bwlp/inc/bootentry.inc.php delete mode 100644 modules-available/serversetup-bwlp/inc/ipxe.inc.php delete mode 100644 modules-available/serversetup-bwlp/inc/ipxemenu.inc.php delete mode 100644 modules-available/serversetup-bwlp/inc/localboot.inc.php delete mode 100644 modules-available/serversetup-bwlp/inc/menuentry.inc.php delete mode 100644 modules-available/serversetup-bwlp/inc/pxelinux.inc.php delete mode 100644 modules-available/serversetup-bwlp/install.inc.php delete mode 100644 modules-available/serversetup-bwlp/lang/de/messages.json delete mode 100644 modules-available/serversetup-bwlp/lang/de/module.json delete mode 100644 modules-available/serversetup-bwlp/lang/de/permissions.json delete mode 100644 modules-available/serversetup-bwlp/lang/de/template-tags.json delete mode 100644 modules-available/serversetup-bwlp/lang/en/messages.json delete mode 100644 modules-available/serversetup-bwlp/lang/en/module.json delete mode 100644 modules-available/serversetup-bwlp/lang/en/permissions.json delete mode 100644 modules-available/serversetup-bwlp/lang/en/template-tags.json delete mode 100644 modules-available/serversetup-bwlp/lang/pt/messages.json delete mode 100644 modules-available/serversetup-bwlp/lang/pt/module.json delete mode 100644 modules-available/serversetup-bwlp/lang/pt/template-tags.json delete mode 100644 modules-available/serversetup-bwlp/page.inc.php delete mode 100644 modules-available/serversetup-bwlp/permissions/permissions.json delete mode 100644 modules-available/serversetup-bwlp/templates/bootentry-list.html delete mode 100644 modules-available/serversetup-bwlp/templates/download.html delete mode 100644 modules-available/serversetup-bwlp/templates/heading.html delete mode 100644 modules-available/serversetup-bwlp/templates/ipaddress.html delete mode 100644 modules-available/serversetup-bwlp/templates/ipxe-new-boot-entry.html delete mode 100644 modules-available/serversetup-bwlp/templates/ipxe_update.html delete mode 100644 modules-available/serversetup-bwlp/templates/localboot.html delete mode 100644 modules-available/serversetup-bwlp/templates/menu-assign-location.html delete mode 100644 modules-available/serversetup-bwlp/templates/menu-edit.html delete mode 100644 modules-available/serversetup-bwlp/templates/menu-list.html (limited to 'modules-available/serversetup-bwlp/permissions/permissions.json') diff --git a/modules-available/serversetup-bwlp-ipxe/api.inc.php b/modules-available/serversetup-bwlp-ipxe/api.inc.php new file mode 100644 index 00000000..1df0e6e7 --- /dev/null +++ b/modules-available/serversetup-bwlp-ipxe/api.inc.php @@ -0,0 +1,254 @@ + $v) { + $query .= $k . '=' . $v . '&'; + } + //$query = substr($query, 0, -1); + echo << $uuid]); + if ($row !== false && !empty($row['systemmodel'])) { + $model = $row['systemmodel']; + } +} +if ($model === false) { + // Otherwise use what iPXE sent us + function modfilt($str) + { + if (empty($str) || preg_match('/product\s+name|be\s+filled|unknown|default\s+string/i', $str)) + return false; + return trim(preg_replace('/\s+/', ' ', $str)); + } + $manuf = modfilt($manuf); + $product = modfilt($product); + if (!empty($product)) { + $model = $product; + if (!empty($manuf)) { + $model .= " ($manuf)"; + } + } +} +// Query +if ($model !== false) { + $row = Database::queryFirst("SELECT bootmethod FROM serversetup_localboot WHERE systemmodel = :model LIMIT 1", + ['model' => $model]); + if ($row !== false) { + $localboot = $row['bootmethod']; + } +} +if ($localboot === false || !isset($BOOT_METHODS[$localboot])) { + $localboot = Property::get(Localboot::PROPERTY_KEY, 'AUTO'); + if (!isset($BOOT_METHODS[$localboot])) { + $localboot = 'AUTO'; + } +} +if (isset($BOOT_METHODS[$localboot])) { + // Move preferred method first + $BOOT_METHODS[] = $BOOT_METHODS[$localboot]; + unset($BOOT_METHODS[$localboot]); + $BOOT_METHODS = array_reverse($BOOT_METHODS); +} + +if ($slxExtensions) { + $slxConsoleUpdate = '--update'; +} else { + $slxConsoleUpdate = ''; +} + +$output = <<getMenuDefinition('target', $platform, $slxExtensions); + +$output .= <<getItemsCode($platform); + +/* + +:i5 +chain -a /tftp/memtest.0 passes=1 onepass || goto membad +prompt Memory OK. Press a key. +goto init + +:i8 +set x:int32 0 +:again +console --left 60 --top 130 --right 67 --bottom 96 --picture bg-load --keep || +console --left 55 --top 88 --right 63 --bottom 64 --picture bg-menu --keep || +inc x +iseq \${x} 20 || goto again +prompt DONE. Press dein Knie. +goto slx_menu + +:membad +iseq \${errno} 0x1 || goto memaborted +params +param scrot \${vram} +imgfetch -a http://132.230.8.113/screen.php##params || +prompt Memory is bad. Press a key. +goto init + +:memaborted +params +param scrot \${vram} +imgfetch -a http://132.230.8.113/screen.php##params || +prompt Memory test aborted. Press a key. +goto init + +*/ + +$output .= << 0) { + EventLog::info('Imported old PXELinux menu, with ' . $num . ' additional IP-range based menus.'); + } else { + EventLog::info('Imported old PXELinux menu.'); + } +} diff --git a/modules-available/serversetup-bwlp-ipxe/hooks/ipxe-update.inc.php b/modules-available/serversetup-bwlp-ipxe/hooks/ipxe-update.inc.php new file mode 100644 index 00000000..583c5a2b --- /dev/null +++ b/modules-available/serversetup-bwlp-ipxe/hooks/ipxe-update.inc.php @@ -0,0 +1,10 @@ + Property::getServerIp() +]; +$task = Taskmanager::submit('CompileIPxeNew', $data); +if (!isset($task['id'])) + return false; +Property::set('ipxe-task-id', $task['id'], 15); +return $task['id']; \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-ipxe/hooks/main-warning.inc.php b/modules-available/serversetup-bwlp-ipxe/hooks/main-warning.inc.php new file mode 100644 index 00000000..a2eba6ff --- /dev/null +++ b/modules-available/serversetup-bwlp-ipxe/hooks/main-warning.inc.php @@ -0,0 +1,6 @@ + $value) { + if (property_exists($this, $key)) { + $this->{$key} = $value; + } + } + } + } + + public abstract function supportsMode($mode); + + public abstract function toScript($failLabel, $mode); + + public abstract function toArray(); + + public abstract function addFormFields(&$array); + + /* + * + */ + + /** + * Return a BootEntry instance from the serialized data. + * + * @param string $jsonString serialized entry data + * @return BootEntry|null instance representing boot entry, null on error + */ + public static function fromJson($data) + { + if (is_string($data)) { + $data = json_decode($data, true); + } + if (isset($data['script'])) { + return new CustomBootEntry($data); + } + if (isset($data['executable'])) { + return new StandardBootEntry($data); + } + return null; + } + + public static function newStandardBootEntry($initData) + { + $ret = new StandardBootEntry($initData); + $list = []; + if ($ret->arch() !== StandardBootEntry::EFI) { + $list[] = StandardBootEntry::BIOS; + } + if ($ret->arch() === StandardBootEntry::EFI || $ret->arch() === StandardBootEntry::BOTH) { + $list[] = StandardBootEntry::EFI; + } + foreach ($list as $mode) { + if (empty($initData['executable'][$mode])) + return null; + } + return $ret; + } + + public static function newCustomBootEntry($initData) + { + if (empty($initData['script'])) + return null; + return new CustomBootEntry($initData); + } + + /** + * Return a BootEntry instance from database with the given id. + * + * @param string $id + * @return BootEntry|null|false false == unknown id, null = unknown entry type, BootEntry instance on success + */ + public static function fromDatabaseId($id) + { + $row = Database::queryFirst("SELECT data FROM serversetup_bootentry + WHERE entryid = :id LIMIT 1", ['id' => $id]); + if ($row === false) + return false; + return self::fromJson($row['data']); + } + +} + +class StandardBootEntry extends BootEntry +{ + protected $executable; + protected $initRd; + protected $commandLine; + protected $replace; + protected $autoUnload; + protected $resetConsole; + protected $arch; // Constants below + + const BIOS = 'PCBIOS'; // Only valid for legacy BIOS boot + const EFI = 'EFI'; // Only valid for EFI boot + const BOTH = 'PCBIOS-EFI'; // Supports both via distinct entry + const AGNOSTIC = 'agnostic'; // Supports both via same entry (PCBIOS entry) + + public function __construct($data = false) + { + if ($data instanceof PxeSection) { + // Gets arrayfied below + $this->executable = $data->kernel; + $this->initRd = $data->initrd; + $this->commandLine = ' ' . str_replace('vga=current', '', $data->append) . ' '; + $this->resetConsole = true; + $this->replace = true; + $this->autoUnload = true; + if (strpos($this->commandLine, ' quiet ') !== false) { + $this->commandLine .= ' loglevel=5 rd.systemd.show_status=auto'; + } + if ($data->ipAppend & 1) { + $this->commandLine .= ' ${ipappend1}'; + } + if ($data->ipAppend & 2) { + $this->commandLine .= ' ${ipappend2}'; + } + if ($data->ipAppend & 4) { + $this->commandLine .= ' SYSUUID=${uuid}'; + } + $this->commandLine = trim(preg_replace('/\s+/', ' ', $this->commandLine)); + } else { + parent::__construct($data); + } + // Convert legacy DB format + foreach (['executable', 'initRd', 'commandLine', 'replace', 'autoUnload', 'resetConsole'] as $key) { + if (!is_array($this->{$key})) { + $this->{$key} = [ 'PCBIOS' => $this->{$key}, 'EFI' => '' ]; + } + } + if ($this->arch === null) { + $this->arch = self::AGNOSTIC; + } + } + + public function arch() + { + return $this->arch; + } + + public function supportsMode($mode) + { + if ($mode === $this->arch || $this->arch === self::AGNOSTIC) + return true; + if ($mode === self::BIOS || $mode === self::EFI) { + return $this->arch === self::BOTH; + } + error_log('Unknown iPXE platform: ' . $mode); + return false; + } + + public function toScript($failLabel, $mode) + { + if (!$this->supportsMode($mode)) { + return "prompt Entry doesn't have an executable for mode $mode\n"; + } + if ($this->arch === self::AGNOSTIC) { + $mode = self::BIOS; + } + + $script = ''; + if ($this->resetConsole[$mode]) { + $script .= "console ||\n"; + } + if (!empty($this->initRd[$mode])) { + $script .= "imgfree ||\n"; + if (!is_array($this->initRd[$mode])) { + $script .= "initrd {$this->initRd[$mode]} || goto $failLabel\n"; + } else { + foreach ($this->initRd[$mode] as $initrd) { + $script .= "initrd $initrd || goto $failLabel\n"; + } + } + } + $script .= "boot "; + if ($this->autoUnload[$mode]) { + $script .= "-a "; + } + if ($this->replace[$mode]) { + $script .= "-r "; + } + $script .= $this->executable[$mode]; + $rdBase = basename($this->initRd[$mode]); + if (!empty($this->commandLine[$mode])) { + $script .= " initrd=$rdBase {$this->commandLine[$mode]}"; + } + $script .= " || goto $failLabel\n"; + if ($this->resetConsole[$mode]) { + $script .= "goto start ||\n"; + } + return $script; + } + + public function addFormFields(&$array) + { + $array[$this->arch . '_selected'] = 'selected'; + foreach ([self::BIOS, self::EFI] as $mode) { + $array['entries'][] = [ + 'is' . $mode => true, + 'mode' => $mode, + 'executable' => $this->executable[$mode], + 'initRd' => $this->initRd[$mode], + 'commandLine' => $this->commandLine[$mode], + 'replace_checked' => $this->replace[$mode] ? 'checked' : '', + 'autoUnload_checked' => $this->autoUnload[$mode] ? 'checked' : '', + 'resetConsole_checked' => $this->resetConsole[$mode] ? 'checked' : '', + ]; + } + $array['exec_checked'] = 'checked'; + } + + public function toArray() + { + return [ + 'executable' => $this->executable, + 'initRd' => $this->initRd, + 'commandLine' => $this->commandLine, + 'replace' => $this->replace, + 'autoUnload' => $this->autoUnload, + 'resetConsole' => $this->resetConsole, + 'arch' => $this->arch, + ]; + } +} + +class CustomBootEntry extends BootEntry +{ + protected $script; + + public function supportsMode($mode) + { + return true; + } + + public function toScript($failLabel, $mode) + { + return str_replace('%fail%', $failLabel, $this->script) . "\n"; + } + + public function addFormFields(&$array) + { + $array['entry'] = [ + 'script' => $this->script, + ]; + $array['script_checked'] = 'checked'; + } + + public function toArray() + { + return ['script' => $this->script]; + } +} diff --git a/modules-available/serversetup-bwlp-ipxe/inc/ipxe.inc.php b/modules-available/serversetup-bwlp-ipxe/inc/ipxe.inc.php new file mode 100644 index 00000000..d34839f0 --- /dev/null +++ b/modules-available/serversetup-bwlp-ipxe/inc/ipxe.inc.php @@ -0,0 +1,453 @@ += :start AND endaddr <= :end", compact('start', 'end')); + $locations = []; + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + foreach ($locations as &$loc) { + if ($row['startaddr'] <= $loc['startaddr'] && $row['endaddr'] >= $loc['endaddr']) { + $loc = false; + } elseif ($row['startaddr'] >= $loc['startaddr'] && $row['endaddr'] <= $loc['endaddr']) { + continue 2; + } + } + unset($loc); + $locations[] = $row; + } + $menu = PxeLinux::parsePxeLinux($content); + $key = $menu->hash(true); + if (isset($menus[$key])) { + $menuId = $menus[$key]; + $defId = null; + // Figure out the default label, get it's label name + foreach ($menu->sections as $section) { + if ($section->isDefault) { + $defId = $section; + } elseif ($defId === null && $section->label === $menu->timeoutLabel) { + $defId = $section; + } + } + if ($defId !== null) { + $defId = self::cleanLabelFixLocal($defId); + // Confirm it actually exists (it should since the menu seems identical) and get menuEntryId + $me = Database::queryFirst('SELECT m.defaultentryid, me.menuentryid FROM serversetup_bootentry be + INNER JOIN serversetup_menuentry me ON (be.entryid = me.entryid) + INNER JOIN serversetup_menu m ON (m.menuid = me.menuid) + WHERE be.entryid = :id AND me.menuid = :menuid', + ['id' => $defId, 'menuid' => $menuId]); + if ($me === false || $me['defaultentryid'] == $me['menuentryid']) { + $defId = null; // Not found, or is already default - don't override if it's the same + } else { + $defId = $me['menuentryid']; + } + } + } else { + $menuId = self::insertMenu($menu, 'Imported', false, 0, [], []); + $menus[$key] = $menuId; + $defId = null; + $importCount++; + } + if ($menuId === false) + continue; + foreach ($locations as $loc) { + if ($loc === false) + continue; + Database::exec('INSERT IGNORE INTO serversetup_menu_location (menuid, locationid, defaultentryid) + VALUES (:menuid, :locationid, :def)', [ + 'menuid' => $menuId, + 'locationid' => $loc['locationid'], + 'def' => $defId, + ]); + } + } + return $importCount; + } + + public static function importLegacyMenu($force = false) + { + if (!$force && false !== Database::queryFirst("SELECT entryid FROM serversetup_bootentry WHERE entryid = 'bwlp-default'")) + return false; // Already exists + // Now create the default entry + self::createDefaultEntries(); + $prepend = ['bwlp-default' => false, 'localboot' => false]; + $defaultLabel = 'bwlp-default'; + $menuTitle = 'bwLehrpool Bootauswahl'; + $pxeConfig = ''; + $timeoutSecs = 60; + // Try to import any customization + $oldMenu = Property::getBootMenu(); + if (is_array($oldMenu)) { + // + if (isset($oldMenu['timeout'])) { + $timeoutSecs = (int)$oldMenu['timeout']; + } + if (isset($oldMenu['defaultentry'])) { + if ($oldMenu['defaultentry'] === 'net') { + $defaultLabel = 'bwlp-default'; + } elseif ($oldMenu['defaultentry'] === 'hdd') { + $defaultLabel = 'localboot'; + } elseif ($oldMenu['defaultentry'] === 'custom') { + $defaultLabel = 'custom'; + } + } + if (!empty($oldMenu['custom'])) { + $pxeConfig = $oldMenu['custom']; + } + } + $append = [ + '', + 'bwlp-default-dbg' => false, + '', + 'poweroff' => false, + ]; + return self::insertMenu(PxeLinux::parsePxeLinux($pxeConfig), $menuTitle, $defaultLabel, $timeoutSecs, $prepend, $append); + } + + /** + * @param PxeMenu $pxeMenu + * @param string $menuTitle + * @param string|false $defaultLabel + * @param $defaultTimeoutSeconds + * @param $prepend + * @param $append + * @return bool|int + */ + private static function insertMenu($pxeMenu, $menuTitle, $defaultLabel, $defaultTimeoutSeconds, $prepend, $append) + { + $timeoutMs = []; + $menuEntries = $prepend; + settype($menuEntries, 'array'); + if (!empty($pxeMenu)) { + $pxe =& $pxeMenu; + if (!empty($pxe->title)) { + $menuTitle = $pxe->title; + } + if ($pxe->timeoutLabel !== null) { + $defaultLabel = $pxe->timeoutLabel; + } + $timeoutMs[] = $pxe->timeoutMs; + $timeoutMs[] = $pxe->totalTimeoutMs; + foreach ($pxe->sections as $section) { + if ($section->localBoot || preg_match('/chain\.c32$/i', $section->kernel)) { + $menuEntries['localboot'] = 'localboot'; + continue; + } + if ($section->label === null) { + if (!$section->isHidden && !empty($section->title)) { + $menuEntries[] = $section->title; + } + continue; + } + if (empty($section->kernel)) { + if (!$section->isHidden && !empty($section->title)) { + $menuEntries[] = $section->title; + } + continue; + } + $entry = self::pxe2BootEntry($section); + if ($entry === null) + continue; + $label = self::cleanLabelFixLocal($section); + if ($defaultLabel === $section->label) { + $defaultLabel = $label; + } + $hotkey = MenuEntry::filterKeyName($section->hotkey); + // Create boot entry + $data = $entry->toArray(); + Database::exec('INSERT IGNORE INTO serversetup_bootentry (entryid, hotkey, title, builtin, data) + VALUES (:label, :hotkey, :title, 0, :data)', [ + 'label' => $label, + 'hotkey' => $hotkey, + 'title' => self::sanitizeIpxeString($section->title), + 'data' => json_encode($data), + ]); + $menuEntries[$label] = $section; + } + } + if (is_array($append)) { + $menuEntries += $append; + } + if (empty($menuEntries)) + return false; + // Make menu + $timeoutMs = array_filter($timeoutMs, 'is_int'); + if (empty($timeoutMs)) { + $timeoutMs = (int)($defaultTimeoutSeconds * 1000); + } else { + $timeoutMs = min($timeoutMs); + } + $isDefault = (int)(Database::queryFirst('SELECT menuid FROM serversetup_menu WHERE isdefault = 1') === false); + Database::exec("INSERT INTO serversetup_menu (timeoutms, title, defaultentryid, isdefault) + VALUES (:timeoutms, :title, NULL, :isdefault)", [ + 'title' => self::sanitizeIpxeString($menuTitle), + 'timeoutms' => $timeoutMs, + 'isdefault' => $isDefault, + ]); + $menuId = Database::lastInsertId(); + if (!array_key_exists($defaultLabel, $menuEntries) && $timeoutMs > 0) { + $defaultLabel = array_keys($menuEntries)[0]; + } + // Link boot entries to menu + $defaultEntryId = null; + $order = 1000; + foreach ($menuEntries as $label => $entry) { + if (is_string($entry)) { + // Gap entry + Database::exec("INSERT INTO serversetup_menuentry + (menuid, entryid, hotkey, title, hidden, sortval, plainpass, md5pass) + VALUES (:menuid, :entryid, :hotkey, :title, :hidden, :sortval, '', '')", [ + 'menuid' => $menuId, + 'entryid' => null, + 'hotkey' => '', + 'title' => self::sanitizeIpxeString($entry), + 'hidden' => 0, + 'sortval' => $order += 100, + ]); + continue; + } + $data = Database::queryFirst("SELECT entryid, hotkey, title FROM serversetup_bootentry WHERE entryid = :entryid", ['entryid' => $label]); + if ($data === false) + continue; + $data['pass'] = ''; + $data['hidden'] = 0; + if ($entry instanceof PxeSection) { + $data['hidden'] = (int)$entry->isHidden; + // Prefer explicit data from this imported menu over the defaults + $data['title'] = self::sanitizeIpxeString($entry->title); + if (MenuEntry::getKeyCode($entry->hotkey) !== false) { + $data['hotkey'] = $entry->hotkey; + } + if (!empty($entry->passwd)) { + // Most likely it's a hash so we cannot recover; ask people to reset + $data['pass'] ='please_reset'; + } + } + $data['menuid'] = $menuId; + $data['sortval'] = $order += 100; + $res = Database::exec("INSERT INTO serversetup_menuentry + (menuid, entryid, hotkey, title, hidden, sortval, plainpass, md5pass) + VALUES (:menuid, :entryid, :hotkey, :title, :hidden, :sortval, :pass, :pass)", $data); + if ($res !== false && $label === $defaultLabel) { + $defaultEntryId = Database::lastInsertId(); + } + } + // Now we can set default entry + if (!empty($defaultEntryId)) { + Database::exec("UPDATE serversetup_menu SET defaultentryid = :entryid WHERE menuid = :menuid", + ['menuid' => $menuId, 'entryid' => $defaultEntryId]); + } + // TODO: masterpw? rather pointless.... + //$oldMenu['masterpasswordclear']; + return $menuId; + } + + private static function createDefaultEntries() + { + $query = 'INSERT IGNORE INTO serversetup_bootentry (entryid, hotkey, title, builtin, data) + VALUES (:entryid, :hotkey, :title, 1, :data)'; + Database::exec($query, + [ + 'entryid' => 'bwlp-default', + 'hotkey' => 'B', + 'title' => 'bwLehrpool-Umgebung starten', + 'data' => json_encode([ + 'executable' => '/boot/default/kernel', + 'initRd' => '/boot/default/initramfs-stage31', + 'commandLine' => 'slxbase=boot/default quiet splash loglevel=5 rd.systemd.show_status=auto intel_iommu=igfx_off ${ipappend1} ${ipappend2}', + 'replace' => true, + 'autoUnload' => true, + 'resetConsole' => true, + ]), + ]); + Database::exec($query, + [ + 'entryid' => 'bwlp-default-dbg', + 'hotkey' => '', + 'title' => 'bwLehrpool-Umgebung starten (nosplash, debug)', + 'data' => json_encode([ + 'executable' => '/boot/default/kernel', + 'initRd' => '/boot/default/initramfs-stage31', + 'commandLine' => 'slxbase=boot/default loglevel=7 intel_iommu=igfx_off ${ipappend1} ${ipappend2}', + 'replace' => true, + 'autoUnload' => true, + 'resetConsole' => true, + ]), + ]); + Database::exec($query, + [ + 'entryid' => 'localboot', + 'hotkey' => 'L', + 'title' => 'Lokales System starten', + 'data' => json_encode([ + 'script' => 'goto slx_localboot || goto %fail% ||', + ]), + ]); + Database::exec($query, + [ + 'entryid' => 'poweroff', + 'hotkey' => 'P', + 'title' => 'Power off', + 'data' => json_encode([ + 'script' => 'poweroff || goto %fail% ||', + ]), + ]); + Database::exec($query, + [ + 'entryid' => 'reboot', + 'hotkey' => 'R', + 'title' => 'Reboot', + 'data' => json_encode([ + 'script' => 'reboot || goto %fail% ||', + ]), + ]); + } + + /** + * Create unique label for a boot entry. It will try to figure out whether + * this is one of our default entries and if not, create a unique label + * representing the menu entry contents. + * Also it patches the entry if it's referencing the local bwlp install + * because side effects. + * + * @param PxeSection $section + * @return string + */ + private static function cleanLabelFixLocal($section) + { + $myip = Property::getServerIp(); + // Detect our "old" entry types + if (count($section->initrd) === 1 && preg_match(",$myip/boot/default/kernel\$,", $section->kernel) + && preg_match(",$myip/boot/default/initramfs-stage31\$,", $section->initrd[0])) { + // Kernel and initrd match, examine KCL + if ($section->append === 'slxbase=boot/default vga=current quiet splash') { + // Normal + return 'bwlp-default'; + } elseif ($section->append === 'slxbase=boot/default') { + // Debug output + return 'bwlp-default-dbg'; + } else { + // Transform to relative URL, leave KCL, fall through to generic label gen + $section->kernel = '/boot/default/kernel'; + $section->initrd = ['/boot/default/initramfs-stage31']; + } + } + // Generic -- "smart" hash of kernel, initrd and command line + $str = $section->kernel . ' ' . implode(',', $section->initrd); + $array = preg_split('/\s+/', $section->append, -1, PREG_SPLIT_NO_EMPTY); + sort($array); + $str .= ' ' . implode(' ', $array); + + return 'i-' . substr(md5($str), 0, 12); + } + + /** + * @param PxeSection $section + * @return BootEntry|null The according boot entry, null if it's unparsable + */ + private static function pxe2BootEntry($section) + { + if (preg_match('/(pxechain\.com|pxechn\.c32)$/i', $section->kernel)) { + // Chaining -- create script + $args = preg_split('/\s+/', $section->append); + $script = ''; + $file = false; + for ($i = 0; $i < count($args); ++$i) { + $arg = $args[$i]; + if ($arg === '-c') { // PXELINUX config file option + ++$i; + $script .= "set 209:string {$args[$i]} || goto %fail%\n"; + } elseif ($arg === '-p') { // PXELINUX prefix path option + ++$i; + $script .= "set 210:string {$args[$i]} || goto %fail%\n"; + } elseif ($arg === '-t') { // PXELINUX timeout option + ++$i; + $script .= "set 211:int32 {$args[$i]} || goto %fail%\n"; + } elseif ($arg === '-o') { // Overriding various DHCP options + ++$i; + if (preg_match('/^((?:0x)?[a-f0-9]{1,4})\.([bwlsh])=(.*)$/i', $args[$i], $out)) { + // TODO: 'q' (8byte) unsupported for now + $opt = intval($out[1], 0); + if ($opt > 0 && $opt < 255) { + static $optType = ['b' => 'uint8', 'w' => 'uint16', 'l' => 'int32', 's' => 'string', 'h' => 'hex']; + $type = $optType[$out[2]]; + $script .= "set {$opt}:{$type} {$args[$i]} || goto %fail%\n"; + } + } + } elseif ($arg{0} === '-') { + continue; + } elseif ($file === false) { + $file = self::parseFile($arg); + } + } + if ($file !== false) { + $url = parse_url($file); + if (isset($url['host'])) { + $script .= "set next-server {$url['host']} || goto %fail%\n"; + } + if (isset($url['path'])) { + $script .= "set filename {$url['path']} || goto %fail%\n"; + } + $script .= "chain -ar {$file} || goto %fail%\n"; + return new CustomBootEntry(['script' => $script]); + } + return null; + } + // "Normal" entry that should be convertible into a StandardBootEntry + $section->kernel = self::parseFile($section->kernel); + foreach ($section->initrd as &$initrd) { + $initrd = self::parseFile($initrd); + } + return BootEntry::newStandardBootEntry($section); + } + + /** + * Parse PXELINUX file notion. Basically, turn + * server::file into tftp://server/file. + * + * @param string $file + * @return string + */ + private static function parseFile($file) + { + if (preg_match(',^([^:/]+)::(.*)$,', $file, $out)) { + return 'tftp://' . $out[1] . '/' . $out[2]; + } + return $file; + } + + public static function sanitizeIpxeString($string) + { + return str_replace(['&', '|', ';', '$', "\r", "\n"], ['+', '/', ':', 'S', ' ', ' '], $string); + } + + public static function makeMd5Pass($plainpass, $salt) + { + if (empty($plainpass)) + return ''; + return md5(md5($plainpass) . '-' . $salt); + } + +} diff --git a/modules-available/serversetup-bwlp-ipxe/inc/ipxemenu.inc.php b/modules-available/serversetup-bwlp-ipxe/inc/ipxemenu.inc.php new file mode 100644 index 00000000..5c1a87d5 --- /dev/null +++ b/modules-available/serversetup-bwlp-ipxe/inc/ipxemenu.inc.php @@ -0,0 +1,142 @@ + $menu]); + if (!is_array($menu)) { + $menu = ['menuid' => 'foo', 'title' => 'Invalid Menu ID: ' . (int)$menu]; + } + } + $this->menuid = (int)$menu['menuid']; + $this->timeoutMs = (int)$menu['timeoutms']; + $this->title = $menu['title']; + $this->defaultEntryId = $menu['defaultentryid']; + $res = Database::simpleQuery("SELECT e.menuentryid, e.entryid, e.hotkey, e.title, e.hidden, e.sortval, e.md5pass, + b.data AS bootentry + FROM serversetup_menuentry e + LEFT JOIN serversetup_bootentry b USING (entryid) + WHERE e.menuid = :menuid + ORDER BY e.sortval ASC, e.title ASC", ['menuid' => $menu['menuid']]); + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + $this->items[] = new MenuEntry($row); + } + } + + public function getMenuDefinition($targetVar, $mode, $slxExtensions) + { + $str = "menu -- {$this->title}\n"; + foreach ($this->items as $item) { + $str .= $item->getMenuItemScript("m_{$this->menuid}", $this->defaultEntryId, $mode, $slxExtensions); + } + if ($this->defaultEntryId === null) { + $defaultLabel = "mx_{$this->menuid}_poweroff"; + } else { + $defaultLabel = "m_{$this->menuid}_{$this->defaultEntryId}"; + } + $str .= "choose"; + if ($this->timeoutMs > 0) { + $str .= " --timeout {$this->timeoutMs}"; + } + $str .= " $targetVar || goto $defaultLabel || goto fail\n"; + if ($this->defaultEntryId === null) { + $str .= "goto skip_{$defaultLabel}\n" + . ":{$defaultLabel}\n" + . "poweroff || goto fail\n" + . ":skip_{$defaultLabel}\n"; + } + return $str; + } + + public function getItemsCode($mode) + { + $str = ''; + foreach ($this->items as $item) { + $str .= $item->getBootEntryScript("m_{$this->menuid}", 'fail', $mode); + $str .= "goto slx_menu\n"; + } + return $str; + } + + /* + * + */ + + public static function forLocation($locationId) + { + $chain = null; + if (Module::isAvailable('location')) { + $chain = Location::getLocationRootChain($locationId); + } + if (!empty($chain)) { + $res = Database::simpleQuery("SELECT m.menuid, m.timeoutms, m.title, IFNULL(ml.defaultentryid, m.defaultentryid) AS defaultentryid, ml.locationid + FROM serversetup_menu m + INNER JOIN serversetup_menu_location ml USING (menuid) + WHERE ml.locationid IN (:chain)", ['chain' => $chain]); + if ($res->rowCount() > 0) { + // Make the location id key, preserving order (closest location is first) + $chain = array_flip($chain); + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + // Overwrite the value (numeric ascending values, useless) with menu array of according location + $chain[(int)$row['locationid']] = $row; + } + // Use first one that was found + foreach ($chain as $menu) { + if (is_array($menu)) { + return new IPxeMenu($menu); + } + } + // Should never end up here, but we'd just fall through and use the default + } + } + // We're here, no specific menu, use default + $menu = Database::queryFirst("SELECT menuid, timeoutms, title, defaultentryid + FROM serversetup_menu + ORDER BY isdefault DESC LIMIT 1"); + if ($menu === false) { + return new EmptyIPxeMenu; + } + return new IPxeMenu($menu); + } + + public static function forClient($ip, $uuid) + { + $locationId = 0; + if (Module::isAvailable('location')) { + $locationId = Location::getFromIpAndUuid($ip, $uuid); + } + return self::forLocation($locationId); + } + +} + +class EmptyIPxeMenu extends IPxeMenu +{ + + /** @noinspection PhpMissingParentConstructorInspection */ + public function __construct() + { + $this->title = 'No menu defined'; + $this->menuid = -1; + $this->items[] = new MenuEntry([ + 'title' => 'Please create a menu in Server-Setup first' + ]); + $this->items[] = new MenuEntry([ + 'title' => 'Bitte erstellen Sie zunächst ein Menü' + ]); + } + +} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-ipxe/inc/localboot.inc.php b/modules-available/serversetup-bwlp-ipxe/inc/localboot.inc.php new file mode 100644 index 00000000..3ab81862 --- /dev/null +++ b/modules-available/serversetup-bwlp-ipxe/inc/localboot.inc.php @@ -0,0 +1,15 @@ + 'iseq efi ${platform} && exit 1 || sanboot --no-describe', + 'EXIT' => 'exit 1', + 'COMBOOT' => 'chain /tftp/chain.c32 hd0', + 'SANBOOT' => 'sanboot --no-describe', + ]; + +} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-ipxe/inc/menuentry.inc.php b/modules-available/serversetup-bwlp-ipxe/inc/menuentry.inc.php new file mode 100644 index 00000000..d243fd23 --- /dev/null +++ b/modules-available/serversetup-bwlp-ipxe/inc/menuentry.inc.php @@ -0,0 +1,177 @@ + $value) { + if (property_exists($this, $key)) { + $this->{$key} = $value; + } + } + $this->hotkey = self::getKeyCode($row['hotkey']); + if (!empty($row['bootentry'])) { + $this->bootEntry = BootEntry::fromJson($row['bootentry']); + } + $this->gap = (array_key_exists('entryid', $row) && $row['entryid'] === null); + } + settype($this->hidden, 'bool'); + settype($this->gap, 'bool'); + settype($this->sortval, 'int'); + settype($this->menuentryid, 'int'); + } + + public function getMenuItemScript($lblPrefix, $requestedDefaultId, $mode, $slxExtensions) + { + if ($this->bootEntry !== null && !$this->bootEntry->supportsMode($mode)) + return ''; + $str = 'item '; + if ($this->gap) { + $str .= '--gap '; + } else { + if ($this->hidden && $slxExtensions) { + if ($this->hotkey === false) + return ''; // Hidden entries without hotkey are illegal + $str .= '--hidden '; + } + if ($this->hotkey !== false) { + $str .= '--key ' . $this->hotkey . ' '; + } + if ($this->menuentryid == $requestedDefaultId) { + $str .= '--default '; + } + $str .= "{$lblPrefix}_{$this->menuentryid} "; + } + if (empty($this->title)) { + $str .= '${}'; + } else { + $str .= $this->title; + } + return $str . " || prompt Could not create menu item for {$lblPrefix}_{$this->menuentryid}\n"; + } + + public function getBootEntryScript($lblPrefix, $failLabel, $mode) + { + if ($this->bootEntry === null || !$this->bootEntry->supportsMode($mode)) + return ''; + $str = ":{$lblPrefix}_{$this->menuentryid}\n"; + if (!empty($this->md5pass)) { + $str .= "set slx_hash {$this->md5pass} || goto $failLabel\n" + . "set slx_salt {$this->menuentryid} || goto $failLabel\n" + . "set slx_pw_ok {$lblPrefix}_ok || goto $failLabel\n" + . "set slx_pw_fail slx_menu || goto $failLabel\n" + . "goto slx_pass_check || goto $failLabel\n" + . ":{$lblPrefix}_ok\n"; + } + return $str . $this->bootEntry->toScript($failLabel, $mode); + } + + /* + * + */ + + private static function getKeyArray() + { + static $data = false; + if ($data === false) { + $data = [ + 'F5' => 0x107e, + 'F6' => 0x127e, + 'F7' => 0x137e, + 'F8' => 0x147e, + 'F9' => 0x157e, + 'F10' => 0x167e, + 'F11' => 0x187e, + 'F12' => 0x197e, + ]; + for ($i = 1; $i <= 26; ++$i) { + $letter = chr(0x40 + $i); + $data['SHIFT_' . $letter] = 0x40 + $i; + if ($letter !== 'C') { + $data['CTRL_' . $letter] = $i; + } + $data[$letter] = 0x60 + $i; + } + for ($i = 0; $i <= 9; ++$i) { + $data[chr(0x30 + $i)] = 0x30 + $i; + } + asort($data, SORT_NUMERIC); + } + return $data; + } + + /** + * Get all the known/supported keys, usable for menu items. + * + * @return string[] list of known key names + */ + public static function getKeyList() + { + return array_keys(self::getKeyArray()); + } + + /** + * Get the key code ipxe expects for the given named + * key. Returns false if the key name is unknown. + * + * @param string $keyName + * @return false|string Key code as hex string, or false if not found + */ + public static function getKeyCode($keyName) + { + $data = self::getKeyArray(); + if (isset($data[$keyName])) + return '0x' . dechex($data[$keyName]); + return false; + } + + /** + * @param string $keyName desired key name + * @return string $keyName if it's known, empty string otherwise + */ + public static function filterKeyName($keyName) + { + $data = self::getKeyArray(); + if (isset($data[$keyName])) + return $keyName; + return ''; + } + +} diff --git a/modules-available/serversetup-bwlp-ipxe/inc/pxelinux.inc.php b/modules-available/serversetup-bwlp-ipxe/inc/pxelinux.inc.php new file mode 100644 index 00000000..1d022fef --- /dev/null +++ b/modules-available/serversetup-bwlp-ipxe/inc/pxelinux.inc.php @@ -0,0 +1,302 @@ + ['string', 'title'], + 'menu default' => ['true', 'isDefault'], + 'menu hide' => ['true', 'isHidden'], + 'menu disabled' => ['true', 'isDisabled'], + 'menu indent' => ['int', 'indent'], + 'kernel' => ['string', 'kernel'], + 'com32' => ['string', 'kernel'], + 'pxe' => ['string', 'kernel'], + 'initrd' => ['string', 'initrd'], + 'append' => ['string', 'append'], + 'ipappend' => ['int', 'ipAppend'], + 'sysappend' => ['int', 'ipAppend'], + 'localboot' => ['int', 'localBoot'], + ]; + $globalPropMap = [ + 'timeout' => ['int', 'timeoutMs', 100], + 'totaltimeout' => ['int', 'totalTimeoutMs', 100], + 'menu title' => ['string', 'title'], + 'menu clear' => ['true', 'menuClear'], + 'menu immediate' => ['true', 'immediateHotkeys'], + 'ontimeout' => ['string', 'timeoutLabel'], + ]; + $lines = preg_split('/[\r\n]+/', $input); + $section = null; + $count = count($lines); + for ($li = 0; $li < $count; ++$li) { + $line =& $lines[$li]; + if (!preg_match('/^\s*([^m]\S*|menu\s+\S+)(\s+.*?|)\s*$/i', $line, $out)) + continue; + $val = trim($out[2]); + $key = trim($out[1]); + $key = strtolower($key); + $key = preg_replace('/\s+/', ' ', $key); + if ($key === 'label') { + if ($section !== null) { + $menu->sections[] = $section; + } + $section = new PxeSection($val); + } elseif ($key === 'menu separator') { + if ($section !== null) { + $menu->sections[] = $section; + $section = null; + } + $menu->sections[] = new PxeSection(null); + } elseif (self::handleKeyword($key, $val, $globalPropMap, $menu)) { + continue; + } elseif ($section === null) { + continue; + } elseif ($key === 'text' && strtolower($val) === 'help') { + $text = ''; + while (++$li < $count) { + $line =& $lines[$li]; + if (strtolower(trim($line)) === 'endtext') + break; + $text .= $line . "\n"; + } + $section->helpText = $text; + } elseif (self::handleKeyword($key, $val, $sectionPropMap, $section)) { + continue; + } + } + if ($section !== null) { + $menu->sections[] = $section; + } + foreach ($menu->sections as $section) { + $section->mangle(); + } + return $menu; + } + + /** + * Check if keyword is valid and if so, add its interpreted value + * to the given object. The map to look up the keyword has to be passed + * as well as the object to set the value in. Map and object should + * obviously match. + * @param string $key keyword of parsed line + * @param string $val raw value of currently parsed line (empty if not present) + * @param array $map Map in which $key is looked up as key + * @param PxeMenu|PxeSection The object to set the parsed and sanitized value in + * @return bool true if the value was found in the map (and set in the object), false otherwise + */ + private static function handleKeyword($key, $val, $map, $object) + { + if (!isset($map[$key])) + return false; + $opt = $map[$key]; + // opt[0] is the type the value should be cast to; special case "true" means + // this is a bool option that will be set as soon as the keyword is present, + // as it doesn't have any parameters + if ($opt[0] === 'true') { + $val = true; + } else { + settype($val, $opt[0]); + } + // If opt[2] is present it's a multiplier for the value + if (isset($opt[2])) { + $val *= $opt[2]; + } + $object->{$opt[1]} = $val; + return true; + } + +} + +/** + * Class representing a parsed pxelinux menu. Members + * will be set to their annotated type if present or + * be null otherwise, except for present-only boolean + * options, which will default to false. + */ +class PxeMenu +{ + + /** + * @var string menu title, shown at the top of the menu + */ + public $title; + /** + * @var int initial timeout after which $timeoutLabel would be executed + */ + public $timeoutMs; + /** + * @var int if the user canceled the timeout by pressing a key, this timeout would still eventually + * trigger and launch the $timeoutLabel section + */ + public $totalTimeoutMs; + /** + * @var string label of section which will execute if the timeout expires + */ + public $timeoutLabel; + /** + * @var bool hide menu and just show background after triggering an entry + */ + public $menuClear = false; + /** + * @var bool boot the associated entry directly if its corresponding hotkey is pressed instead of just highlighting + */ + public $immediateHotkeys = false; + /** + * @var PxeSection[] list of sections the menu contains + */ + public $sections = []; + + public function hash($fuzzy) + { + $ctx = hash_init('md5'); + if (!$fuzzy) { + hash_update($ctx, $this->title); + hash_update($ctx, $this->timeoutLabel); + } + hash_update($ctx, $this->timeoutMs); + foreach ($this->sections as $section) { + if ($fuzzy) { + hash_update($ctx, mb_strtolower(preg_replace('/[^a-zA-Z0-9]/', '', $section->title))); + } else { + hash_update($ctx, $section->label); + hash_update($ctx, $section->title); + hash_update($ctx, $section->indent); + hash_update($ctx, $section->helpText); + hash_update($ctx, $section->isDefault); + hash_update($ctx, $section->hotkey); + } + hash_update($ctx, $section->kernel); + hash_update($ctx, $section->append); + hash_update($ctx, $section->ipAppend); + hash_update($ctx, $section->passwd); + hash_update($ctx, $section->isHidden); + hash_update($ctx, $section->isDisabled); + hash_update($ctx, $section->localBoot); + foreach ($section->initrd as $initrd) { + hash_update($ctx, $initrd); + } + } + return hash_final($ctx, false); + } + +} + +/** + * Class representing a parsed pxelinux menu entry. Members + * will be set to their annotated type if present or + * be null otherwise, except for present-only boolean + * options, which will default to false. + */ +class PxeSection +{ + + /** + * @var string label used internally in PXEMENU definition to address this entry + */ + public $label; + /** + * @var string MENU LABEL of PXEMENU - title of entry displayed to the user + */ + public $title; + /** + * @var int Number of spaces to prefix the title with + */ + public $indent; + /** + * @var string help text to display when the entry is highlighted + */ + public $helpText; + /** + * @var string Kernel to load + */ + public $kernel; + /** + * @var string|string[] initrd to load for the kernel. + * If mangle() has been called this will be an array, + * otherwise it's a comma separated list. + */ + public $initrd; + /** + * @var string command line options to pass to the kernel + */ + public $append; + /** + * @var int IPAPPEND from PXEMENU. Bitmask of valid options 1 and 2. + */ + public $ipAppend; + /** + * @var string Password protecting the entry. This is most likely in crypted form. + */ + public $passwd; + /** + * @var bool whether this section is marked as default (booted after timeout) + */ + public $isDefault = false; + /** + * @var bool Menu entry is not visible (can only be triggered by timeout) + */ + public $isHidden = false; + /** + * @var bool Disable this entry, making it unselectable + */ + public $isDisabled = false; + /** + * @var int Value of the LOCALBOOT field + */ + public $localBoot; + /** + * @var string hotkey to trigger item. Only valid after calling mangle() + */ + public $hotkey; + + public function __construct($label) { $this->label = $label; } + + public function mangle() + { + if (($i = strpos($this->title, '^')) !== false) { + $this->hotkey = strtoupper($this->title{$i+1}); + $this->title = substr($this->title, 0, $i) . substr($this->title, $i + 1); + } + if (strpos($this->append, 'initrd=') !== false) { + $parts = preg_split('/\s+/', $this->append); + $this->append = ''; + for ($i = 0; $i < count($parts); ++$i) { + if (preg_match('/^initrd=(.*)$/', $parts[$i], $out)) { + if (!empty($this->initrd)) { + $this->initrd .= ','; + } + $this->initrd .= $out[1]; + } else { + $this->append .= ' ' . $parts[$i]; + } + } + $this->append = trim($this->append); + } + if (is_string($this->initrd)) { + $this->initrd = explode(',', $this->initrd); + } elseif (!is_array($this->initrd)) { + $this->initrd = []; + } + } + +} + diff --git a/modules-available/serversetup-bwlp-ipxe/install.inc.php b/modules-available/serversetup-bwlp-ipxe/install.inc.php new file mode 100644 index 00000000..25579c13 --- /dev/null +++ b/modules-available/serversetup-bwlp-ipxe/install.inc.php @@ -0,0 +1,89 @@ +compileTask !== null) + return $this->compileTask; + $this->compileTask = Property::get('ipxe-task-id'); + if ($this->compileTask !== false) { + $this->compileTask = Taskmanager::status($this->compileTask); + if (!Taskmanager::isTask($this->compileTask) || Taskmanager::isFinished($this->compileTask)) { + $this->compileTask = false; + } + } + return $this->compileTask; + } + + protected function doPreprocess() + { + User::load(); + + if (!User::isLoggedIn()) { + Message::addError('main.no-permission'); + Util::redirect('?do=Main'); + } + + if (Request::any('action') === 'getimage') { + User::assertPermission("download"); + $this->handleGetImage(); + } + + $this->currentMenu = Property::getBootMenu(); + + $action = Request::post('action'); + + if ($action === false) { + $this->currentAddress = Property::getServerIp(); + $this->getLocalAddresses(); + } + + if ($action === 'compile') { + User::assertPermission("edit.address"); + if ($this->getCompileTask() === false) { + Trigger::ipxe(); + } + Util::redirect('?do=serversetup'); + } + + if ($action === 'ip') { + User::assertPermission("edit.address"); + // New address is to be set + $this->getLocalAddresses(); + $this->updateLocalAddress(); + } + + if ($action === 'savebootentry') { + User::assertPermission('ipxe.bootentry.edit'); + $this->saveBootEntry(); + } + + if ($action === 'deleteBootentry') { + User::assertPermission('ipxe.bootentry.edit'); + $this->deleteBootEntry(); + } + + if ($action === 'savemenu') { + User::assertPermission('ipxe.menu.edit'); + $this->saveMenu(); + } + + if ($action === 'savelocation') { + // Permcheck in function + $this->saveLocationMenu(); + Util::redirect('?do=locations'); + } + + if ($action === 'savelocalboot') { + User::assertPermission('ipxe.localboot.edit'); + $this->saveLocalboot(); + } + + if ($action === 'deleteMenu') { + // Permcheck in function + $this->deleteMenu(); + } + + if ($action === 'setDefaultMenu') { + User::assertPermission('ipxe.menu.edit', 0); + $this->setDefaultMenu(); + } + + if (Request::isPost()) { + Util::redirect('?do=serversetup'); + } + + User::assertPermission('access-page'); + + if (User::hasPermission('ipxe.*')) { + Dashboard::addSubmenu('?do=serversetup&show=menu', Dictionary::translate('submenu_menu', true)); + Dashboard::addSubmenu('?do=serversetup&show=bootentry', Dictionary::translate('submenu_bootentry', true)); + } + if (User::hasPermission('edit.address')) { + Dashboard::addSubmenu('?do=serversetup&show=address', Dictionary::translate('submenu_address', true)); + } + if (User::hasPermission('download')) { + Dashboard::addSubmenu('?do=serversetup&show=download', Dictionary::translate('submenu_download', true)); + } + if (User::hasPermission('ipxe.localboot.*')) { + Dashboard::addSubmenu('?do=serversetup&show=localboot', Dictionary::translate('submenu_localboot', true)); + } + if (Request::get('show') === false) { + $subs = Dashboard::getSubmenus(); + if (empty($subs)) { + User::assertPermission('download'); + } else { + Util::redirect($subs[0]['url']); + } + } + } + + protected function doRender() + { + Render::addTemplate("heading"); + + $task = $this->getCompileTask(); + if ($task !== false) { + $files = []; + if ($task['data'] && $task['data']['files']) { + foreach ($task['data']['files'] as $k => $v) { + $files[] = ['name' => $k, 'namehyphen' => str_replace(['/', '.'], '-', $k)]; + } + } + Render::addTemplate('ipxe_update', array('taskid' => $task['id'], 'files' => $files)); + } + + switch (Request::get('show')) { + case 'editbootentry': + User::assertPermission('ipxe.bootentry.edit'); + $this->showEditBootEntry(); + break; + case 'editmenu': + User::assertPermission('ipxe.menu.view'); + $this->showEditMenu(); + break; + case 'download': + User::assertPermission('download'); + $this->showDownload(); + break; + case 'menu': + User::assertPermission('ipxe.menu.view'); + $this->showMenuList(); + break; + case 'bootentry': + User::assertPermission('ipxe.bootentry.view'); + $this->showBootentryList(); + break; + case 'address': + User::assertPermission('edit.address'); + $this->showEditAddress(); + break; + case 'assignlocation': + // Permcheck in function + $this->showEditLocation(); + break; + case 'localboot': + User::assertPermission('ipxe.localboot.*'); + $this->showLocalbootConfig(); + break; + default: + Util::redirect('?do=serversetup'); + break; + } + } + + private function showDownload() + { + $list = glob('/srv/openslx/www/boot/download/*', GLOB_NOSORT); + usort($list, function ($a, $b) { + return strcmp(substr($a, -4), substr($b, -4)) * 100 + strcmp($a, $b); + }); + $files = []; + $strings = [ + 'efi' => [Dictionary::translate('dl-efi', true) => 50], + 'pcbios' => [Dictionary::translate('dl-pcbios', true) => 51], + 'usb' => [Dictionary::translate('dl-usb', true) => 80], + 'hd' => [Dictionary::translate('dl-hd', true) => 81], + 'lkrn' => [Dictionary::translate('dl-lkrn', true) => 82], + 'i386' => [Dictionary::translate('dl-i386', true) => 10], + 'x86_64' => [Dictionary::translate('dl-x86_64', true) => 11], + 'ecm' => [Dictionary::translate('dl-usbnic', true) => 60], + 'ncm' => [Dictionary::translate('dl-usbnic', true) => 61], + 'ipxe' => [Dictionary::translate('dl-pcinic', true) => 62], + 'snp' => [Dictionary::translate('dl-snp', true) => 63], + ]; + foreach ($list as $file) { + if ($file{0} === '.') + continue; + if (is_file($file)) { + $base = basename($file); + $features = []; + foreach (preg_split('/[\-\.\/]+/', $base, -1, PREG_SPLIT_NO_EMPTY) as $p) { + if (array_key_exists($p, $strings)) { + $features += $strings[$p]; + } + } + asort($features); + $files[] = [ + 'name' => $base, + 'size' => Util::readableFileSize(filesize($file)), + 'modified' => Util::prettyTime(filemtime($file)), + 'class' => substr($base, -4) === '.usb' ? 'slx-bold' : '', + 'features' => implode(', ', array_keys($features)), + ]; + } + } + Render::addTemplate('download', ['files' => $files]); + } + + private function makeSelectArray($list, $default) + { + $ret = []; + foreach (array_keys($list) as $k) { + $ret[] = [ + 'key' => $k, + 'selected' => ($k === $default ? 'selected' : ''), + ]; + } + return $ret; + } + + private function showLocalbootConfig() + { + // Default setting + $default = Property::get(Localboot::PROPERTY_KEY, 'AUTO'); + if (!array_key_exists($default, Localboot::BOOT_METHODS)) { + $default = 'AUTO'; + } + $optionList = $this->makeSelectArray(Localboot::BOOT_METHODS, $default); + // Exceptions + $cutoff = strtotime('-90 days'); + $models = []; + $res = Database::simpleQuery('SELECT m.systemmodel, cnt, sl.bootmethod FROM ( + SELECT m2.systemmodel, Count(*) AS cnt FROM machine m2 + WHERE m2.lastseen > :cutoff + GROUP BY systemmodel + ) m + LEFT JOIN serversetup_localboot sl USING (systemmodel) + ORDER BY systemmodel', ['cutoff' => $cutoff]); + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + $row['options'] = $this->makeSelectArray(Localboot::BOOT_METHODS, $row['bootmethod']); + $models[] = $row; + } + // Output + $data = [ + 'default' => $default, + 'options' => $optionList, + 'exceptions' => $models, + ]; + Render::addTemplate('localboot', $data); + } + + private function showBootentryList() + { + $allowEdit = User::hasPermission('ipxe.bootentry.edit'); + + $res = Database::simpleQuery("SELECT be.entryid, be.hotkey, be.title, be.builtin, Count(*) AS refs FROM serversetup_bootentry be + INNER JOIN serversetup_menuentry sm USING (entryid) + GROUP BY be.entryid + ORDER BY be.title ASC"); + $bootentryTable = []; + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + $bootentryTable[] = $row; + } + + Render::addTemplate('bootentry-list', array( + 'bootentryTable' => $bootentryTable, + 'allowEdit' => $allowEdit, + )); + } + + private function showMenuList() + { + $allowedEdit = User::getAllowedLocations('ipxe.menu.edit'); + + // TODO Permission::addGlobalTags($perms, null, ['edit.menu', 'edit.address', 'download']); + + $res = Database::simpleQuery("SELECT m.menuid, m.title, m.isdefault, GROUP_CONCAT(l.locationid) AS locations, + GROUP_CONCAT(ll.locationname SEPARATOR ', ') AS locnames + FROM serversetup_menu m + LEFT JOIN serversetup_menu_location l USING (menuid) + LEFT JOIN location ll USING (locationid) + GROUP BY menuid + ORDER BY title"); + $menuTable = []; + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + if (empty($row['locations'])) { + $locations = []; + $row['allowEdit'] = in_array(0, $allowedEdit); + } else { + $locations = explode(',', $row['locations']); + $row['allowEdit'] = empty(array_diff($locations, $allowedEdit)); + } + $row['locationCount'] = empty($locations) ? '' : count($locations); + $menuTable[] = $row; + } + + Render::addTemplate('menu-list', array( + 'menuTable' => $menuTable, + 'showSetDefault' => User::hasPermission('ipxe.menu.edit', 0) + )); + } + + private function hasMenuPermission($menuid, $permission) + { + $allowedEditLocations = User::getAllowedLocations($permission); + $allowEdit = in_array(0, $allowedEditLocations); + if (!$allowEdit) { + // Get locations + $locations = Database::queryColumnArray('SELECT locationid FROM serversetup_menu_location + WHERE menuid = :menuid', compact('menuid')); + if (!empty($locations)) { + $allowEdit = count(array_diff($locations, $allowedEditLocations)) === 0; + } + } + return $allowEdit; + } + + private function showEditMenu() + { + $id = Request::get('id', false, 'int'); + // if = edit, else = add new + if ($id !== 0) { + $menu = Database::queryFirst("SELECT menuid, timeoutms, title, defaultentryid, isdefault + FROM serversetup_menu WHERE menuid = :id", compact('id')); + } else { + $menu = []; + $menu['menuid'] = 0; + $menu['timeoutms'] = 0; + $menu['title'] = ''; + $menu['defaultentryid'] = null; + $menu['isdefault'] = false; + } + + if ($menu === false) { + Message::addError('invalid-menu-id', $id); + Util::redirect('?do=serversetup&show=menu'); + } + $highlight = Request::get('highlight', false, 'string'); + if ($id !== 0 && !$this->hasMenuPermission($id, 'ipxe.menu.edit')) { + $menu['readonly'] = 'readonly'; + $menu['disabled'] = 'disabled'; + $menu['plainpass'] = ''; + } + if (!User::hasPermission('ipxe.menu.edit', 0)) { + $menu['globalMenuWarning'] = true; + } + + $menu['timeout'] = round($menu['timeoutms'] / 1000); + $menu['entries'] = []; + $res = Database::simpleQuery("SELECT menuentryid, entryid, hotkey, title, hidden, sortval, plainpass FROM + serversetup_menuentry WHERE menuid = :id ORDER BY sortval ASC", compact('id')); + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + if ($row['entryid'] == $highlight) { + $row['highlight'] = 'active'; + } + $menu['entries'][] = $row; + } + $menu['keys'] = array_map(function ($item) { return ['key' => $item]; }, MenuEntry::getKeyList()); + $menu['entrylist'] = Database::queryAll("SELECT entryid, title, hotkey, data FROM serversetup_bootentry ORDER BY title ASC"); + foreach ($menu['entrylist'] as &$bootentry) { + //$bootentry['json'] = $bootentry['data']; + $bootentry['data'] = json_decode($bootentry['data'], true); + if (array_key_exists('arch', $bootentry['data'])) { + $bootentry['data']['PCBIOS'] = array('executable' => $bootentry['data']['executable']['PCBIOS'], + 'initRd' => $bootentry['data']['initRd']['PCBIOS'], + 'commandLine' => $bootentry['data']['commandLine']['PCBIOS']); + $bootentry['data']['EFI'] = array('executable' => $bootentry['data']['executable']['EFI'], + 'initRd' => $bootentry['data']['initRd']['EFI'], + 'commandLine' => $bootentry['data']['commandLine']['EFI']); + + if ($bootentry['data']['arch'] === 'PCBIOS') { + $bootentry['data']['arch'] = Dictionary::translateFile('template-tags','lang_biosOnly', true); + unset($bootentry['data']['EFI']); + } else if ($bootentry['data']['arch'] === 'EFI') { + $bootentry['data']['arch'] = Dictionary::translateFile('template-tags','lang_efiOnly', true); + unset($bootentry['data']['PCBIOS']); + } else { + $bootentry['data']['arch'] = Dictionary::translateFile('template-tags','lang_archBoth', true); + } + + } elseif (!array_key_exists('script', $bootentry['data'])) { + $bootentry['data']['arch'] = Dictionary::translateFile('template-tags','lang_archAgnostic', true); + $bootentry['data']['archAgnostic'] = array('executable' => $bootentry['data']['executable'], + 'initRd' => $bootentry['data']['initRd'], + 'commandLine' => $bootentry['data']['commandLine']); + } + } + foreach ($menu['entries'] as &$entry) { + $entry['isdefault'] = ($entry['menuentryid'] == $menu['defaultentryid']); + // TODO: plainpass only when permissions + } + + Permission::addGlobalTags($menu['perms'], 0, ['ipxe.menu.edit']); + Render::addTemplate('menu-edit', $menu); + } + + private function showEditBootEntry() + { + $params = []; + $id = Request::get('id', false, 'string'); + if ($id === false) { + $params['exec_checked'] = 'checked'; + $params['entryid'] = 'u-' . dechex(mt_rand(0x1000, 0xffff)) . '-' . dechex(time()); + $params['entries'] = [ + ['mode' => 'PCBIOS'], + ['mode' => 'EFI'], + ]; + } else { + // Query existing entry + $row = Database::queryFirst('SELECT entryid, title, builtin, data FROM serversetup_bootentry + WHERE entryid = :id LIMIT 1', ['id' => $id]); + if ($row === false) { + Message::addError('invalid-boot-entry', $id); + Util::redirect('?do=serversetup'); + } + $entry = BootEntry::fromJson($row['data']); + if ($entry === null) { + Message::addError('unknown-bootentry-type', $id); + Util::redirect('?do=serversetup'); + } + $entry->addFormFields($params); + $params['title'] = $row['title']; + $params['entryid'] = $row['entryid']; + $params['builtin'] = $row['builtin']; + $params['menus'] = Database::queryAll('SELECT m.menuid, m.title FROM serversetup_menu m + INNER JOIN serversetup_menuentry me ON (me.menuid = m.menuid) + WHERE me.entryid = :entryid', ['entryid' => $row['entryid']]); + } + + Render::addTemplate('ipxe-new-boot-entry', $params); + } + + private function showEditAddress() + { + Render::addTemplate('ipaddress', array( + 'ips' => $this->addrListTask['data']['addresses'], + 'chooseHintClass' => $this->hasIpSet ? '' : 'alert alert-danger', + 'disabled' => ($this->getCompileTask() === false) ? '' : 'disabled', + )); + } + + // ----------------------------------------------------------------------------------------------- + + private function getLocalAddresses() + { + $this->addrListTask = Taskmanager::submit('LocalAddressesList', array()); + + if ($this->addrListTask === false) { + $this->addrListTask['data']['addresses'] = false; + return false; + } + + if (!Taskmanager::isFinished($this->addrListTask)) { // TODO: Async if just displaying + $this->addrListTask = Taskmanager::waitComplete($this->addrListTask['id'], 4000); + } + + if (Taskmanager::isFailed($this->addrListTask) || !isset($this->addrListTask['data']['addresses'])) { + $this->addrListTask['data']['addresses'] = false; + return false; + } + + $sortIp = array(); + foreach (array_keys($this->addrListTask['data']['addresses']) as $key) { + $item = & $this->addrListTask['data']['addresses'][$key]; + if (!isset($item['ip']) || !preg_match('/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/', $item['ip']) || substr($item['ip'], 0, 4) === '127.') { + unset($this->addrListTask['data']['addresses'][$key]); + continue; + } + if ($this->currentAddress === $item['ip']) { + $item['default'] = true; + $this->hasIpSet = true; + } + $sortIp[] = $item['ip']; + } + unset($item); + array_multisort($sortIp, SORT_STRING, $this->addrListTask['data']['addresses']); + return true; + } + + private function deleteBootEntry() { + $id = Request::post('deleteid', false, 'string'); + if ($id === false) { + Message::addError('main.parameter-missing', 'deleteid'); + return; + } + Database::exec("DELETE FROM serversetup_bootentry WHERE entryid = :entryid", array("entryid" => $id)); + // TODO: Redirect to &show=bootentry + Message::addSuccess('bootentry-deleted'); + } + + private function setDefaultMenu() + { + $id = Request::post('menuid', false, 'int'); + if ($id === false) { + Message::addError('main.parameter-missing', 'menuid'); + return; + } + Database::exec('UPDATE serversetup_menu SET isdefault = (menuid = :menuid)', ['menuid' => $id]); + Message::addSuccess('menu-set-default'); + } + + private function deleteMenu() + { + $id = Request::post('deleteid', false, 'int'); + if ($id === false) { + Message::addError('main.parameter-missing', 'deleteid'); + return; + } + if (!$this->hasMenuPermission($id, 'ipxe.menu.edit')) { + Message::addError('locations.no-permission-location', $id); + return; + } + Database::exec("DELETE FROM serversetup_menu WHERE menuid = :menuid", array("menuid" => $id)); + Message::addSuccess('menu-deleted'); + } + + private function saveMenu() + { + $id = Request::post('menuid', false, 'int'); + if ($id === false) { + Message::addError('main.parameter-missing', 'menuid'); + return; + } + + $insertParams = [ + 'title' => IPxe::sanitizeIpxeString(Request::post('title', '', 'string')), + 'timeoutms' => abs(Request::post('timeout', 0, 'int') * 1000), + ]; + if ($id === 0) { + Database::exec("INSERT INTO serversetup_menu (title, timeoutms, isdefault) VALUES (:title, :timeoutms, 0)", $insertParams); + $menu['menuid'] = $id = Database::lastInsertId(); + } else { + $menu = Database::queryFirst("SELECT m.menuid + FROM serversetup_menu m + WHERE menuid = :id", compact('id')); + if ($menu === false) { + Message::addError('no-such-menu', $id); + return; + } + $insertParams['menuid'] = $id; + Database::exec('UPDATE serversetup_menu SET title = :title, timeoutms = :timeoutms + WHERE menuid = :menuid', $insertParams); + } + + $keepIds = []; + $entries = Request::post('entry', false, 'array'); + $wantedDefaultEntryId = Request::post('defaultentry', null, 'string'); + $defaultEntryId = null; + + if ($entries) { + foreach ($entries as $key => $entry) { + if (!isset($entry['sortval'])) { + error_log(print_r($entry, true)); + continue; + } + // Fallback defaults + $entry += [ + 'entryid' => null, + 'title' => '', + 'hidden' => 0, + 'plainpass' => '', + ]; + $params = [ + 'title' => IPxe::sanitizeIpxeString($entry['title']), + 'sortval' => (int)$entry['sortval'], + 'menuid' => $menu['menuid'], + ]; + if (empty($entry['entryid'])) { + // Spacer + $params += [ + 'entryid' => null, + 'hotkey' => '', + 'hidden' => 0, // Doesn't make any sense + 'plainpass' => '', // Doesn't make any sense + ]; + } else { + $params += [ + 'entryid' => $entry['entryid'], // TODO validate? + 'hotkey' => MenuEntry::filterKeyName($entry['hotkey']), + 'hidden' => (int)$entry['hidden'], // TODO (needs hotkey to make sense) + 'plainpass' => $entry['plainpass'], + ]; + } + if (is_numeric($key)) { + if ((string)$key === $wantedDefaultEntryId) { // Check now that we have generated our key + $defaultEntryId = $key; + } + $keepIds[] = $key; + $params['menuentryid'] = $key; + $params['md5pass'] = IPxe::makeMd5Pass($entry['plainpass'], $key); + $ret = Database::exec('UPDATE serversetup_menuentry + SET entryid = :entryid, hotkey = :hotkey, title = :title, hidden = :hidden, sortval = :sortval, + plainpass = :plainpass, md5pass = :md5pass + WHERE menuid = :menuid AND menuentryid = :menuentryid', $params, true); + } else { + $ret = Database::exec("INSERT INTO serversetup_menuentry + (menuid, entryid, hotkey, title, hidden, sortval, plainpass, md5pass) + VALUES (:menuid, :entryid, :hotkey, :title, :hidden, :sortval, :plainpass, '')", $params, true); + if ($ret) { + $newKey = Database::lastInsertId(); + if ((string)$key === $wantedDefaultEntryId) { // Check now that we have generated our key + $defaultEntryId = $newKey; + } + $keepIds[] = (int)$newKey; + if (!empty($entry['plainpass'])) { + Database::exec('UPDATE serversetup_menuentry SET md5pass = :md5pass WHERE menuentryid = :id', [ + 'md5pass' => IPxe::makeMd5Pass($entry['plainpass'], $newKey), + 'id' => $newKey, + ]); + } + } + } + + if ($ret === false) { + Message::addWarning('error-saving-entry', $entry['title'], Database::lastError()); + } + } + Database::exec('DELETE FROM serversetup_menuentry WHERE menuid = :menuid AND menuentryid NOT IN (:keep)', + ['menuid' => $menu['menuid'], 'keep' => $keepIds]); + // Set default entry + Database::exec('UPDATE serversetup_menu SET defaultentryid = :default WHERE menuid = :menuid', + ['menuid' => $menu['menuid'], 'default' => $defaultEntryId]); + } else { + Database::exec('DELETE FROM serversetup_menuentry WHERE menuid = :menuid', ['menuid' => $menu['menuid']]); + Database::exec('UPDATE serversetup_menu SET defaultentryid = NULL WHERE menuid = :menuid', ['menuid' => $menu['menuid']]); + } + + Message::addSuccess('menu-saved'); + } + + private function updateLocalAddress() + { + $newAddress = Request::post('ip', 'none', 'string'); + $valid = false; + foreach ($this->addrListTask['data']['addresses'] as $item) { + if ($item['ip'] !== $newAddress) + continue; + $valid = true; + break; + } + if ($valid) { + Property::setServerIp($newAddress); + Util::redirect('?do=ServerSetup'); + } else { + Message::addError('invalid-ip', $newAddress); + } + Util::redirect(); + } + + private function handleGetImage() + { + $file = "/opt/openslx/ipxe/openslx-bootstick.raw"; + if (!is_readable($file)) { + Message::addError('image-not-found'); + return; + } + Header('Content-Type: application/octet-stream'); + Header('Content-Disposition: attachment; filename="openslx-bootstick-' . Property::getServerIp() . '-raw.img"'); + readfile($file); + exit; + } + + private function saveBootEntry() + { + $oldEntryId = Request::post('entryid', false, 'string'); + $newId = Request::post('newid', false, 'string'); + if (!preg_match('/^[a-z0-9\-_]{1,16}$/', $newId)) { + Message::addError('main.parameter-empty', 'newid'); + return; + } + $data = Request::post('entry', false); + if (!is_array($data)) { + Message::addError('missing-bootentry-data'); + return; + } + $type = Request::post('type', false, 'string'); + if ($type === 'exec') { + $entry = BootEntry::newStandardBootEntry($data); + } elseif ($type === 'script') { + $entry = BootEntry::newCustomBootEntry($data); + } else { + Message::addError('unknown-bootentry-type', $type); + return; + } + if ($entry === null) { + Message::addError('main.empty-field'); + Util::redirect('?do=serversetup&show=bootentry'); + } + $params = [ + 'entryid' => $newId, + 'title' => Request::post('title', '', 'string'), + 'data' => json_encode($entry->toArray()), + ]; + // New or update? + if (empty($oldEntryId)) { + // New entry + Database::exec('INSERT INTO serversetup_bootentry (entryid, title, builtin, data) + VALUES (:entryid, :title, 0, :data)', $params); + Message::addSuccess('boot-entry-created', $newId); + } else { + // Edit existing entry + $params['oldid'] = $oldEntryId; + Database::exec('UPDATE serversetup_bootentry SET entryid = :entryid, title = :title, data = :data + WHERE entryid = :oldid', $params); + Message::addSuccess('boot-entry-updated', $newId); + } + Util::redirect('?do=serversetup&show=bootentry'); + } + + private function showEditLocation() + { + $locationId = Request::get('locationid', false, 'int'); + $loc = Location::get($locationId); + if ($loc === false) { + Message::addError('locations.invalid-location-id', $locationId); + return; + } + User::assertPermission('ipxe.menu.assign', $locationId); + // List of menu entries + $res = Database::simpleQuery('SELECT menuentryid, title FROM serversetup_menuentry'); + $menuEntries = $res->fetchAll(PDO::FETCH_KEY_PAIR); + // List of menus + $data = [ + 'locationid' => $locationId, + 'locationName' => $loc['locationname'], + ]; + $res = Database::simpleQuery('SELECT m.menuid, m.title, ml.locationid, ml.defaultentryid, GROUP_CONCAT(me.menuentryid) AS entries FROM serversetup_menu m + LEFT JOIN serversetup_menu_location ml ON (m.menuid = ml.menuid AND ml.locationid = :locationid) + INNER JOIN serversetup_menuentry me ON (m.menuid = me.menuid AND me.entryid IS NOT NULL) + GROUP BY menuid + ORDER BY m.title ASC', ['locationid' => $locationId]); + $menus = []; + $hasDefault = false; + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + $eids = explode(',', $row['entries']); + $row['entries'] = []; + foreach ($eids as $eid) { + $row['entries'][] = [ + 'id' => $eid, + 'title' => $menuEntries[$eid], + 'selected' => ($eid == $row['defaultentryid'] ? 'selected' : ''), + ]; + } + if ($row['locationid'] !== null) { + $hasDefault = true; + $row['menu_selected'] = 'checked'; + } + $menus[] = $row; + } + if (!$hasDefault) { + $data['default_selected'] = 'checked'; + } + $data['list'] = $menus; + Render::addTemplate('menu-assign-location', $data); + } + + private function saveLocationMenu() + { + $locationId = Request::post('locationid', false, 'int'); + $loc = Location::get($locationId); + if ($loc === false) { + Message::addError('locations.invalid-location-id', $locationId); + return; + } + User::assertPermission('ipxe.menu.assign', $locationId); + $menuId = Request::post('menuid', false, 'int'); + if ($menuId === 0) { + Database::exec('DELETE FROM serversetup_menu_location WHERE locationid = :locationid', + ['locationid' => $locationId]); + Message::addSuccess('location-use-default', $loc['locationname']); + return; + } + $defaultEntryId = Request::post('defaultentryid-' . $menuId, 0, 'int'); + if ($defaultEntryId === 0) { + $defaultEntryId = null; + } + Database::exec('INSERT INTO serversetup_menu_location (menuid, locationid, defaultentryid) + VALUES (:menuid, :locationid, :defaultentryid) + ON DUPLICATE KEY UPDATE menuid = :menuid, defaultentryid = :defaultentryid', [ + 'menuid' => $menuId, + 'locationid' => $locationId, + 'defaultentryid' => $defaultEntryId + ]); + Message::addSuccess('location-menu-assigned', $loc['locationname']); + } + + private function saveLocalboot() + { + $default = Request::post('default', 'AUTO', 'string'); + if (!array_key_exists($default, Localboot::BOOT_METHODS)) { + Message::addError('localboot-invalid-method', $default); + return; + } + Property::set(Localboot::PROPERTY_KEY, $default); + $overrides = Request::post('override', [], 'array'); + Database::exec('TRUNCATE TABLE serversetup_localboot'); + foreach ($overrides as $model => $mode) { + if (empty($mode)) // No override + continue; + if (!array_key_exists($mode, Localboot::BOOT_METHODS)) { + Message::addWarning('localboot-invalid-method', $mode); + continue; + } + Database::exec('INSERT INTO serversetup_localboot (systemmodel, bootmethod) + VALUES (:model, :mode)', compact('model', 'mode')); + } + Message::addSuccess('localboot-saved'); + Util::redirect('?do=serversetup&show=localboot'); + } + +} diff --git a/modules-available/serversetup-bwlp-ipxe/permissions/permissions.json b/modules-available/serversetup-bwlp-ipxe/permissions/permissions.json new file mode 100644 index 00000000..33cc9cea --- /dev/null +++ b/modules-available/serversetup-bwlp-ipxe/permissions/permissions.json @@ -0,0 +1,29 @@ +{ + "access-page": { + "location-aware": false + }, + "download": { + "location-aware": false + }, + "edit.address": { + "location-aware": false + }, + "ipxe.bootentry.view": { + "location-aware": false + }, + "ipxe.bootentry.edit": { + "location-aware": false + }, + "ipxe.menu.view": { + "location-aware": false + }, + "ipxe.menu.edit": { + "location-aware": false + }, + "ipxe.menu.assign": { + "location-aware": true + }, + "ipxe.localboot.edit": { + "location-aware": false + } +} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-ipxe/templates/bootentry-list.html b/modules-available/serversetup-bwlp-ipxe/templates/bootentry-list.html new file mode 100644 index 00000000..0cf005c5 --- /dev/null +++ b/modules-available/serversetup-bwlp-ipxe/templates/bootentry-list.html @@ -0,0 +1,83 @@ +

{{lang_bootentryHead}}

+ +

+ {{lang_bootentryIntro}} +

+ +
{{lang_entryId}}{{lang_entryId}} {{lang_title}}{{lang_hotkey}}{{lang_password}}{{lang_hotkey}}{{lang_password}}
+ + + + + + + + + + + {{#bootentryTable}} + + + + + + + + {{/bootentryTable}} + +
{{lang_bootentryTitle}}{{lang_hotkey}}{{lang_refCount}}{{lang_edit}}{{lang_delete}}
+ {{title}} + + {{hotkey}} + + {{refs}} + + {{#allowEdit}} + + + + {{/allowEdit}} + + {{#allowEdit}} + + {{/allowEdit}} +
+
+ {{#allowEdit}} + + + {{lang_addBootentry}} + + {{/allowEdit}} +
+ + +
+ + +
+ + \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-ipxe/templates/download.html b/modules-available/serversetup-bwlp-ipxe/templates/download.html new file mode 100644 index 00000000..62064b66 --- /dev/null +++ b/modules-available/serversetup-bwlp-ipxe/templates/download.html @@ -0,0 +1,53 @@ +
+
+ {{lang_downloadBootImage}} +
+
+ + {{#files}} + + + + + + + {{/files}} +
{{name}}{{size}}{{modified}}({{features}})
+

+ + + {{lang_usbImgHelpBtn}} + +

+

+ {{lang_additionalInfoLink}} {{lang_ipxeWikiUrl}} +

+
+
+ + \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-ipxe/templates/heading.html b/modules-available/serversetup-bwlp-ipxe/templates/heading.html new file mode 100644 index 00000000..e2aa0bff --- /dev/null +++ b/modules-available/serversetup-bwlp-ipxe/templates/heading.html @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-ipxe/templates/ipaddress.html b/modules-available/serversetup-bwlp-ipxe/templates/ipaddress.html new file mode 100644 index 00000000..ea19c417 --- /dev/null +++ b/modules-available/serversetup-bwlp-ipxe/templates/ipaddress.html @@ -0,0 +1,44 @@ +
+
+ {{lang_bootAddress}} +
+
+
+ {{lang_chooseIP}} +
+
+ + + + {{#ips}} + + + {{#default}} + + {{/default}} + {{^default}} + + {{/default}} + + {{/ips}} +
{{ip}} + {{lang_active}} + + +
+

+ {{lang_recompileHint}} +

+
+
+ + +
+
+
\ No newline at end of file diff --git a/modules-available/serversetup-bwlp-ipxe/templates/ipxe-new-boot-entry.html b/modules-available/serversetup-bwlp-ipxe/templates/ipxe-new-boot-entry.html new file mode 100644 index 00000000..7e82b5cc --- /dev/null +++ b/modules-available/serversetup-bwlp-ipxe/templates/ipxe-new-boot-entry.html @@ -0,0 +1,165 @@ +

{{lang_newBootEntryHead}}

+ +{{#builtin}} +
+ {{lang_editBuiltinWarn}} +
+{{/builtin}} + +
+
+ {{lang_bootEntryData}} +
+
+
+ + + + +
+
+ + +
+
+ + +
+
+ +
+ + +
+
+ + +
+
+ + +
+ +
+
+ {{#entries}} +
+
+
+

{{mode}}

+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+
+ {{/entries}} +
+
+ +
+
+ + +
+
+ + {{#builtin}} +
+ {{lang_editBuiltinWarn}} +
+ {{/builtin}} + +

{{lang_referencingMenus}}:

+ + +
+ +
+
+
+
+ + \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-ipxe/templates/ipxe_update.html b/modules-available/serversetup-bwlp-ipxe/templates/ipxe_update.html new file mode 100644 index 00000000..344d3905 --- /dev/null +++ b/modules-available/serversetup-bwlp-ipxe/templates/ipxe_update.html @@ -0,0 +1,54 @@ +
+
{{lang_menuGeneration}}
+
+
+ {{#files}} +
+ + {{name}} +
+ {{/files}} +
+
+
+ {{lang_generationFailed}} +
+
+
{{lang_menuGeneration}}
+
+
+ + diff --git a/modules-available/serversetup-bwlp-ipxe/templates/localboot.html b/modules-available/serversetup-bwlp-ipxe/templates/localboot.html new file mode 100644 index 00000000..3037de2a --- /dev/null +++ b/modules-available/serversetup-bwlp-ipxe/templates/localboot.html @@ -0,0 +1,59 @@ +

{{lang_localBootHead}}

+ +

{{lang_localBootIntro}}

+ +
+ + + + +
+
+ + +
+ +
+

+ {{lang_localBootExceptions}} +

+ + + + + + + {{#exceptions}} + + + + + + {{/exceptions}} +
{{lang_systemmodel}}{{lang_count}}{{lang_override}}
{{systemmodel}}{{cnt}} + +
+ +
+ + +
+ +
diff --git a/modules-available/serversetup-bwlp-ipxe/templates/menu-assign-location.html b/modules-available/serversetup-bwlp-ipxe/templates/menu-assign-location.html new file mode 100644 index 00000000..077d137e --- /dev/null +++ b/modules-available/serversetup-bwlp-ipxe/templates/menu-assign-location.html @@ -0,0 +1,69 @@ +

{{lang_assignMenuToLocation}}

+

{{locationName}}

+ +
+ + + + + + + + + + + + + + + + + + + {{#list}} + + + + + + {{/list}} + +
{{lang_menuTitle}}{{lang_menuEntryOverride}}
+
+ + +
+
+ {{lang_useDefaultMenu}} +
+
+ + +
+
+ {{title}} + + +
+ +
+ +
+ +
+ +
+ + \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-ipxe/templates/menu-edit.html b/modules-available/serversetup-bwlp-ipxe/templates/menu-edit.html new file mode 100644 index 00000000..1598a2b7 --- /dev/null +++ b/modules-available/serversetup-bwlp-ipxe/templates/menu-edit.html @@ -0,0 +1,368 @@ +

{{lang_editMenuHead}}

+ + + + +
+
+ {{title}} + {{^title}} + {{lang_newMenu}} + {{/title}} +
+
+
+ + + + +
+
+ +
+
+ +
+
+
+
+ +
+
+
+ + {{lang_seconds}} +
+
+
+
+ + + + + + + + + + + + + + + {{#entries}} + + + + + + + + + + + + + + + + + {{/entries}} + +
{{lang_entryId}}{{lang_title}}{{lang_hotkey}}{{lang_password}}
+ + +
+ + +
+
+ + + + + + + + + +
+ + +
+
+ +
+
+
+ +
+
+ {{lang_cancel}} + +
+
+ + +
+
+ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-ipxe/templates/menu-list.html b/modules-available/serversetup-bwlp-ipxe/templates/menu-list.html new file mode 100644 index 00000000..545f22a9 --- /dev/null +++ b/modules-available/serversetup-bwlp-ipxe/templates/menu-list.html @@ -0,0 +1,100 @@ +

{{lang_listOfMenus}}

+ +

+ {{lang_menuListIntro}} +

+ + + + + + + + + + + + + {{#menuTable}} + + + + + + + + {{/menuTable}} + +
{{lang_menuTitle}}{{lang_locationCount}}{{lang_isDefault}}{{lang_edit}}{{lang_delete}}
+ {{title}} + + {{locationCount}} + + {{^isdefault}} + {{#showSetDefault}} +
+ + + +
+ {{/showSetDefault}} + {{/isdefault}} + {{#isdefault}} + + {{/isdefault}} +
+ {{#allowEdit}} + + + + {{/allowEdit}} + + {{#allowDelete}} + + {{/allowDelete}} +
+ + +
+ + + +
+ + +
+ + \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-pxelinux/config.json b/modules-available/serversetup-bwlp-pxelinux/config.json new file mode 100644 index 00000000..36268c6a --- /dev/null +++ b/modules-available/serversetup-bwlp-pxelinux/config.json @@ -0,0 +1,3 @@ +{ + "category": "main.settings-server" +} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-pxelinux/hooks/ipxe-update.inc.php b/modules-available/serversetup-bwlp-pxelinux/hooks/ipxe-update.inc.php new file mode 100644 index 00000000..baa7a1bf --- /dev/null +++ b/modules-available/serversetup-bwlp-pxelinux/hooks/ipxe-update.inc.php @@ -0,0 +1,9 @@ + ['string', 'title'], + 'menu default' => ['true', 'isDefault'], + 'menu hide' => ['true', 'isHidden'], + 'menu disabled' => ['true', 'isDisabled'], + 'menu indent' => ['int', 'indent'], + 'kernel' => ['string', 'kernel'], + 'initrd' => ['string', 'initrd'], + 'append' => ['string', 'append'], + 'ipappend' => ['int', 'ipAppend'], + 'localboot' => ['int', 'localBoot'], + ]; + $globalPropMap = [ + 'timeout' => ['int', 'timeoutMs', 100], + 'totaltimeout' => ['int', 'totalTimeoutMs', 100], + 'menu title' => ['string', 'title'], + 'menu clear' => ['true', 'menuClear'], + 'menu immediate' => ['true', 'immediateHotkeys'], + 'ontimeout' => ['string', 'timeoutLabel'], + ]; + $lines = preg_split('/[\r\n]+/', $input); + $section = null; + $count = count($lines); + for ($li = 0; $li < $count; ++$li) { + $line =& $lines[$li]; + if (!preg_match('/^\s*([^m]\S*|menu\s+\S+)(\s+.*?|)\s*$/i', $line, $out)) + continue; + $key = trim($out[1]); + $key = strtolower($key); + $key = preg_replace('/\s+/', ' ', $key); + if ($key === 'label') { + if ($section !== null) { + $menu->sections[] = $section; + } + $section = new PxeSection($out[2]); + } elseif ($key === 'menu separator') { + if ($section !== null) { + $menu->sections[] = $section; + $section = null; + } + $menu->sections[] = new PxeSection(null); + } elseif (self::handleKeyword($key, $out[2], $globalPropMap, $menu)) { + continue; + } elseif ($section === null) { + continue; + } elseif ($key === 'text' && strtolower($out[2]) === 'help') { + $text = ''; + while (++$li < $count) { + $line =& $lines[$li]; + if (strtolower(trim($line)) === 'endtext') + break; + $text .= $line . "\n"; + } + $section->helpText = $text; + } elseif (self::handleKeyword($key, $out[2], $sectionPropMap, $section)) { + continue; + } + } + if ($section !== null) { + $menu->sections[] = $section; + } + return $menu; + } + + /** + * Check if keyword is valid and if so, add its interpreted value + * to the given object. The map to look up the keyword has to be passed + * as well as the object to set the value in. Map and object should + * obviously match. + * @param string $key keyword of parsed line + * @param string $val raw value of currently parsed line (empty if not present) + * @param array $map Map in which $key is looked up as key + * @param PxeMenu|PxeSection The object to set the parsed and sanitized value in + * @return bool true if the value was found in the map (and set in the object), false otherwise + */ + private static function handleKeyword($key, $val, $map, $object) + { + if (!isset($map[$key])) + return false; + $opt = $map[$key]; + // opt[0] is the type the value should be cast to; special case "true" means + // this is a bool option that will be set as soon as the keyword is present, + // as it doesn't have any parameters + if ($opt[0] === 'true') { + $val = true; + } else { + settype($val, $opt[0]); + } + // If opt[2] is present it's a multiplier for the value + if (isset($opt[2])) { + $val *= $opt[2]; + } + $object->{$opt[1]} = $val; + return true; + } + +} + +/** + * Class representing a parsed pxelinux menu. Members + * will be set to their annotated type if present or + * be null otherwise, except for present-only boolean + * options, which will default to false. + */ +class PxeMenu +{ + /** + * @var string menu title, shown at the top of the menu + */ + public $title; + /** + * @var int initial timeout after which $timeoutLabel would be executed + */ + public $timeoutMs; + /** + * @var int if the user canceled the timeout by pressing a key, this timeout would still eventually + * trigger and launch the $timeoutLabel section + */ + public $totalTimeoutMs; + /** + * @var string label of section which will execute if the timeout expires + */ + public $timeoutLabel; + /** + * @var bool hide menu and just show background after triggering an entry + */ + public $menuClear = false; + /** + * @var bool boot the associated entry directly if its corresponding hotkey is presed instead of just highlighting + */ + public $immediateHotkeys = false; + /** + * @var PxeSection[] list of sections the menu contains + */ + public $sections = []; +} + +/** + * Class representing a parsed pxelinux menu entry. Members + * will be set to their annotated type if present or + * be null otherwise, except for present-only boolean + * options, which will default to false. + */ +class PxeSection +{ + /** + * @var string label used internally in PXEMENU definition to address this entry + */ + public $label; + /** + * @var string MENU LABEL of PXEMENU - title of entry displayed to the user + */ + public $title; + /** + * @var int Number of spaces to prefix the title with + */ + public $indent; + /** + * @var string help text to display when the entry is highlighted + */ + public $helpText; + /** + * @var string Kernel to load + */ + public $kernel; + /** + * @var string initrd to load for the kernel + */ + public $initrd; + /** + * @var string command line options to pass to the kernel + */ + public $append; + /** + * @var int IPAPPEND from PXEMENU. Bitmask of valid options 1 and 2. + */ + public $ipAppend; + /** + * @var string Password protecting the entry. This is most likely in crypted form. + */ + public $passwd; + /** + * @var bool whether this section is marked as default (booted after timeout) + */ + public $isDefault = false; + /** + * @var bool Menu entry is not visible (can only be triggered by timeout) + */ + public $isHidden = false; + /** + * @var bool Disable this entry, making it unselectable + */ + public $isDisabled = false; + /** + * @var int Value of the LOCALBOOT field + */ + public $localBoot; + + public function __construct($label) { $this->label = $label; } +} + diff --git a/modules-available/serversetup-bwlp-pxelinux/lang/de/messages.json b/modules-available/serversetup-bwlp-pxelinux/lang/de/messages.json new file mode 100644 index 00000000..3e2cc834 --- /dev/null +++ b/modules-available/serversetup-bwlp-pxelinux/lang/de/messages.json @@ -0,0 +1,5 @@ +{ + "image-not-found": "USB-Image nicht gefunden. Generieren Sie das Bootmen\u00fc neu.", + "invalid-ip": "Kein Interface ist auf die Adresse {{0}} konfiguriert", + "no-ip-addr-set": "Bitte w\u00e4hlen Sie die prim\u00e4re IP-Adresse des Servers" +} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-pxelinux/lang/de/module.json b/modules-available/serversetup-bwlp-pxelinux/lang/de/module.json new file mode 100644 index 00000000..da71d558 --- /dev/null +++ b/modules-available/serversetup-bwlp-pxelinux/lang/de/module.json @@ -0,0 +1,4 @@ +{ + "module_name": "iPXE \/ Boot Menu", + "page_title": "PXE- und Boot-Einstellungen" +} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-pxelinux/lang/de/permissions.json b/modules-available/serversetup-bwlp-pxelinux/lang/de/permissions.json new file mode 100644 index 00000000..98baec3c --- /dev/null +++ b/modules-available/serversetup-bwlp-pxelinux/lang/de/permissions.json @@ -0,0 +1,6 @@ +{ + "access-page": "Seite sehen.", + "download": "USB-Image herunterladen.", + "edit.address": "Boot-Adresse des Servers ausw\u00e4hlen.", + "edit.menu": "Bootmen\u00fc anpassen." +} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-pxelinux/lang/de/template-tags.json b/modules-available/serversetup-bwlp-pxelinux/lang/de/template-tags.json new file mode 100644 index 00000000..8d612ab0 --- /dev/null +++ b/modules-available/serversetup-bwlp-pxelinux/lang/de/template-tags.json @@ -0,0 +1,33 @@ +{ + "lang_active": "Aktiv", + "lang_bootAddress": "Boot-Adresse des Servers", + "lang_bootBehavior": "Standard-Bootverhalten", + "lang_bootHint": "Das Bootmen\u00fc muss nach einer \u00c4nderung der IP-Adresse neu generiert werden. In der Regel geschieht dies automatisch, der Vorgang kann in der Sektion Bootmen\u00fc allerdings auch manuell ausgel\u00f6st werden.", + "lang_bootInfo": "Hier k\u00f6nnen Anpassungen am Erscheinungsbild des Bootmen\u00fcs vorgenommen werden.", + "lang_bootMenu": "Bootmen\u00fc", + "lang_bootMenuCreate": "Bootmen\u00fc erzeugen", + "lang_chooseIP": "Bitte w\u00e4hlen Sie die IP-Adresse, \u00fcber die der Server von den Clients zum Booten angesprochen werden soll.", + "lang_customEntry": "Eigener Eintrag", + "lang_downloadImage": "USB-Image herunterladen", + "lang_downloadRufus": "Rufus herunterladen", + "lang_example": "Beispiel", + "lang_generationFailed": "Erzeugen des Bootmen\u00fcs fehlgeschlagen. Der Netzwerkboot von bwLehrpool wird wahrscheinlich nicht funktionieren. Wenn Sie den Fehler nicht selbst beheben k\u00f6nnen, melden Sie bitte die Logausgabe an das bwLehrpool-Projekt.", + "lang_localHDD": "Lokale HDD", + "lang_masterPassword": "Master-Passwort", + "lang_masterPasswordHelp": "Das Master-Passwort wird ben\u00f6tigt, um einen Booteintrag direkt am Client tempor\u00e4r durch Dr\u00fccken der Tab-Taste zu editieren. Da dies f\u00fcr Manipulation am Client genutzt werden kann, sollte diese Funktion unbedingt mit einem Passwort gesch\u00fctzt werden.", + "lang_menuCustom": "Benutzerdefinierter Men\u00fczusatz", + "lang_menuCustomHint1": "Hier haben Sie die M\u00f6glichkeit, eigenen Men\u00fc-Code zum angezeigten PXE-Men\u00fc hinzuzuf\u00fcgen, um z.B. auf weitere PXE-Server zu verweisen. Das Format entspricht dem syslinux Men\u00fcformat.", + "lang_menuCustomHint2": "Sie k\u00f6nnen ein oder mehrere Eintr\u00e4ge erzeugen. Wenn Sie einen Eintrag erzeugen m\u00f6chten, der automatisch gestartet wird, wenn der Benutzer keine Auswahl t\u00e4tigt, vergeben Sie als", + "lang_menuCustomHint3": "und w\u00e4hlen Sie als Standard-Bootverhalten ebenfalls custom.", + "lang_menuDisplayTime": "Anzeigedauer des Men\u00fcs", + "lang_menuGeneration": "Erzeugen des Bootmen\u00fcs", + "lang_moduleHeading": "iPXE \/ Boot Menu", + "lang_pxeBuilt": "PXE-Binary gebaut", + "lang_seconds": "Sekunden", + "lang_set": "Setzen", + "lang_usbBuilt": "USB-Image gebaut", + "lang_usbImage": "USB-Image", + "lang_usbImgHelp": "Mit dem USB-Image k\u00f6nnen Sie einen bootbaren USB-Stick erstellen, \u00fcber den sich bwLehrpool an Rechnern starten l\u00e4sst, die keinen Netzwerkboot unterst\u00fctzen, bzw. f\u00fcr die keine entsprechende DHCP-Konfiguration vorhanden ist. Dies erfordert dann lediglich, dass in der BIOS-Konfiguration des Rechners USB-Boot zugelassen ist. Der Stick dient dabei lediglich als Einstiegspunkt; es ist nach wie vor ein bwLehrpool-Satellitenserver f\u00fcr den eigentlichen Bootvorgang von N\u00f6ten.", + "lang_usbImgHelpLinux": "Nutzen Sie dd, um das Image auf einen USB-Stick zu schreiben. Das Image enth\u00e4lt bereits eine Partitionstabelle, achten Sie daher darauf, dass Sie das Image z.B. nach \/dev\/sdx schreiben, und nicht nach \/dev\/sdx1", + "lang_usbImgHelpWindows": "Unter Windows muss zun\u00e4chst ein Programm besorgt werden, mit dem sich Images direkt auf einen USB-Stick schreiben lassen. Es gibt gleich mehrere kostenlose und quelloffene Programme, eines davon ist Rufus. Rufus wurde mit dem bwLehrpool-Image gestetet. Nach dem Starten des Programms ist lediglich das heruntergeladene Image zu \u00f6ffnen, sowie in der Liste der Laufwerke der richtige USB-Stick auszuw\u00e4hlen (damit Sie nicht versehentlich Daten auf dem falschen Laufwerk \u00fcberschreiben!)" +} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-pxelinux/lang/en/messages.json b/modules-available/serversetup-bwlp-pxelinux/lang/en/messages.json new file mode 100644 index 00000000..d4ba6905 --- /dev/null +++ b/modules-available/serversetup-bwlp-pxelinux/lang/en/messages.json @@ -0,0 +1,5 @@ +{ + "image-not-found": "USB image not found. Try regenerating the boot menu first.", + "invalid-ip": "No interface is configured with the address {{0}}", + "no-ip-addr-set": "Please set the server's primary IP address" +} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-pxelinux/lang/en/module.json b/modules-available/serversetup-bwlp-pxelinux/lang/en/module.json new file mode 100644 index 00000000..aeea610c --- /dev/null +++ b/modules-available/serversetup-bwlp-pxelinux/lang/en/module.json @@ -0,0 +1,3 @@ +{ + "module_name": "iPXE \/ Boot Menu" +} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-pxelinux/lang/en/permissions.json b/modules-available/serversetup-bwlp-pxelinux/lang/en/permissions.json new file mode 100644 index 00000000..44d1c519 --- /dev/null +++ b/modules-available/serversetup-bwlp-pxelinux/lang/en/permissions.json @@ -0,0 +1,6 @@ +{ + "access-page": "View page.", + "download": "Download USB Image.", + "edit.address": "Choose boot address of the server.", + "edit.menu": "Customize boot menu." +} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-pxelinux/lang/en/template-tags.json b/modules-available/serversetup-bwlp-pxelinux/lang/en/template-tags.json new file mode 100644 index 00000000..9bb55f93 --- /dev/null +++ b/modules-available/serversetup-bwlp-pxelinux/lang/en/template-tags.json @@ -0,0 +1,33 @@ +{ + "lang_active": "Active", + "lang_bootAddress": "Boot Address of the Server", + "lang_bootBehavior": "Default Boot Behavior", + "lang_bootHint": "The Boot menu must be recreated after changing the IP address. Usually this is done automatically, but the process can also be triggered manually in the section of the boot menu.", + "lang_bootInfo": "Here adjustments can be made to the appearance of the boot menu.", + "lang_bootMenu": "Boot Menu", + "lang_bootMenuCreate": "Create Boot Menu", + "lang_chooseIP": "Please select the IP address that the client server will use to boot.", + "lang_customEntry": "Custom entry", + "lang_downloadImage": "Download USB Image", + "lang_downloadRufus": "Download Rufus", + "lang_example": "Example", + "lang_generationFailed": "Could not generate boot menu. The bwLehrpool-System might not work properly. If you can't fix the problem, please report the error log below to the bwLehrpool project.", + "lang_localHDD": "Local HDD", + "lang_masterPassword": "Master Password", + "lang_masterPasswordHelp": "The master password is required to edit a boot menu entry. This should be set for security reasons.", + "lang_menuCustom": "Custom Extra Menu", + "lang_menuCustomHint1": "Here you have the opportunity to add your own menu code to the displayed PXE menu, eg to refer to other PXE server. The format corresponds to the syslinux menu format.", + "lang_menuCustomHint2": "You can create one or more entries. If you want to create an entry that starts automatically when the user makes a selection, assign as", + "lang_menuCustomHint3": "and select as the default boot behavior custom as well.", + "lang_menuDisplayTime": "Menu Display Time", + "lang_menuGeneration": "Generating boot menu...", + "lang_moduleHeading": "iPXE \/ Boot Menu", + "lang_pxeBuilt": "Built PXE binary", + "lang_seconds": "Seconds", + "lang_set": "Set", + "lang_usbBuilt": "Built USB image", + "lang_usbImage": "USB image", + "lang_usbImgHelp": "The USB image can be used to create a bootable USB stick, which enables you to boot bwLehrpool without changing your DHCP settings or enabling network boot in the clients. The only requirement is that you enable USB boot in the client's BIOS. The USB stick is only used for bootstrapping, the actual bwLehrpool system is still loaded via network from your local bwLehrpool server.", + "lang_usbImgHelpLinux": "On Linux you can simply use dd to write the image to a usb stick. The image already contains a partition table, so make sure you write the image to the device itself and not to an already existing partition (e.g. to \/dev\/sdx not \/dev\/sdx1)", + "lang_usbImgHelpWindows": "On Windows you need to use a 3rd party tool that can directly write to usb sticks. There are several free and open source soltions, one of them being Rufus. Rufus has been tested with the bwLehrpool image and is very simple to use. After launching Rufus, just open the downloaded USB image, select the proper USB stick to write to (be careful not to overwrite the wrong drive!), and you're ready to go." +} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-pxelinux/lang/pt/messages.json b/modules-available/serversetup-bwlp-pxelinux/lang/pt/messages.json new file mode 100644 index 00000000..65745768 --- /dev/null +++ b/modules-available/serversetup-bwlp-pxelinux/lang/pt/messages.json @@ -0,0 +1,3 @@ +{ + "invalid-ip": "Nenhuma interface est\u00e1 configurada com o endere\u00e7o {{0}}" +} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-pxelinux/lang/pt/module.json b/modules-available/serversetup-bwlp-pxelinux/lang/pt/module.json new file mode 100644 index 00000000..aeea610c --- /dev/null +++ b/modules-available/serversetup-bwlp-pxelinux/lang/pt/module.json @@ -0,0 +1,3 @@ +{ + "module_name": "iPXE \/ Boot Menu" +} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-pxelinux/lang/pt/template-tags.json b/modules-available/serversetup-bwlp-pxelinux/lang/pt/template-tags.json new file mode 100644 index 00000000..14788767 --- /dev/null +++ b/modules-available/serversetup-bwlp-pxelinux/lang/pt/template-tags.json @@ -0,0 +1,38 @@ +{ + "lang_active": "Ativo", + "lang_bootAddress": "Endere\u00e7o Boot do Servidor", + "lang_bootBehavior": "Comportamento Padr\u00e3o de Boot", + "lang_bootHint": "O menu de boot deve ser recriado ap\u00f3s alterar o endere\u00e7o IP. Geralmente isso \u00e9 feito automaticamente, mas o processo tamb\u00e9m pode ser acionado manualmente na se\u00e7\u00e3o do menu de boot.", + "lang_bootInfo": "Aqui ajustes podem ser feitos na apar\u00eancia do menu de boot.", + "lang_bootMenu": "Menu de Boot", + "lang_bootMenuCreate": "Criar Menu de Boot", + "lang_chooseIP": "Por favor, selecione o endere\u00e7o IP que o servidor do cliente utilizar\u00e1 realizar o boot.", + "lang_close": "Fechar", + "lang_compile": "Compilar", + "lang_compileIso": "Compilar .iso", + "lang_compileKkpxe": "Compilar .kkpxe", + "lang_compileUsb": "Compilar .usb", + "lang_compilingIpxe": "Compilando iPXE", + "lang_customScript": "Script Customizado", + "lang_download": "Baixar", + "lang_example": "Exemplo", + "lang_extension": "Extens\u00e3o", + "lang_ipxeAdv": "Gerar iPXE no Modo Avan\u00e7ado", + "lang_ipxeInfo": "Aqui \u00e9 poss\u00edvel compilar e baixar o iPXE utilizando um script customiz\u00e1vel.", + "lang_ipxeSmp": "Gerar iPXE no Modo Simples", + "lang_ipxeSmpInfo": "Aqui voc\u00ea pode escolher gerar o iPXE escolhendo apenas uma das extens\u00f5es abaixo", + "lang_ipxeWarning": "Se esta for a primeira vez compilando, poder\u00e1 levar entre 1 e 4 minutos para que termine.", + "lang_loading": "Carregando", + "lang_localHDD": "HDD Local", + "lang_menuCustom": "Menu Adicional Customizado", + "lang_menuCustomHint1": "Aqui voc\u00ea tem a oportunidade de adicionar seu pr\u00f3prio c\u00f3digo de menu para o menu PXE exibido, por exemplo, para se referir a outro servidor PXE. O formato corresponde ao formato de menu syslinux.", + "lang_menuCustomHint2": "Voc\u00ea pode criar uma ou mais entradas. Se voc\u00ea quiser criar uma entrada que \u00e9 iniciada automaticamente quando o usu\u00e1rio faz uma sele\u00e7\u00e3o, atribua como", + "lang_menuCustomHint3": "e selecione como o comportamento de boot padr\u00e3o tamb\u00e9m my-entry.", + "lang_menuDisplayTime": "Tempo de Exibi\u00e7\u00e3o do Menu", + "lang_mountIpxe": "Montar iPXE", + "lang_restoreDefault": "Restaurar Padr\u00e3o", + "lang_saveScript": "Salvar Script", + "lang_seconds": "Segundos", + "lang_set": "Definir", + "lang_success": "Arquivo criado com sucesso:" +} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-pxelinux/page.inc.php b/modules-available/serversetup-bwlp-pxelinux/page.inc.php new file mode 100644 index 00000000..52b3afe4 --- /dev/null +++ b/modules-available/serversetup-bwlp-pxelinux/page.inc.php @@ -0,0 +1,187 @@ +handleGetImage(); + } + + $this->currentMenu = Property::getBootMenu(); + + $action = Request::post('action'); + + if ($action === false) { + $this->currentAddress = Property::getServerIp(); + $this->getLocalAddresses(); + } + + if ($action === 'ip') { + User::assertPermission("edit.address"); + // New address is to be set + $this->getLocalAddresses(); + $this->updateLocalAddress(); + } + + if ($action === 'ipxe') { + User::assertPermission("edit.menu"); + // iPXE stuff changes + $this->updatePxeMenu(); + } + + if (Request::isPost()) { + Util::redirect('?do=serversetup'); + } + + User::assertPermission('access-page'); + } + + protected function doRender() + { + Render::addTemplate("heading"); + $task = Property::get('ipxe-task-id'); + if ($task !== false) { + $task = Taskmanager::status($task); + if (!Taskmanager::isTask($task) || Taskmanager::isFinished($task)) { + $task = false; + } + } + if ($task !== false) { + Render::addTemplate('ipxe_update', array('taskid' => $task['id'])); + } + + Permission::addGlobalTags($perms, null, ['edit.menu', 'edit.address', 'download']); + + Render::addTemplate('ipaddress', array( + 'ips' => $this->taskStatus['data']['addresses'], + 'chooseHintClass' => $this->hasIpSet ? '' : 'alert alert-danger', + 'editAllowed' => User::hasPermission("edit.address"), + 'perms' => $perms, + )); + $data = $this->currentMenu; + if (!User::hasPermission('edit.menu')) { + unset($data['masterpasswordclear']); + } + if (!isset($data['defaultentry'])) { + $data['defaultentry'] = 'net'; + } + if ($data['defaultentry'] === 'net') { + $data['active-net'] = 'checked'; + } + if ($data['defaultentry'] === 'hdd') { + $data['active-hdd'] = 'checked'; + } + if ($data['defaultentry'] === 'custom') { + $data['active-custom'] = 'checked'; + } + $data['perms'] = $perms; + Render::addTemplate('ipxe', $data); + } + + // ----------------------------------------------------------------------------------------------- + + private function getLocalAddresses() + { + $this->taskStatus = Taskmanager::submit('LocalAddressesList', array()); + + if ($this->taskStatus === false) { + $this->taskStatus['data']['addresses'] = false; + return false; + } + + if (!Taskmanager::isFinished($this->taskStatus)) { // TODO: Async if just displaying + $this->taskStatus = Taskmanager::waitComplete($this->taskStatus['id'], 4000); + } + + if (Taskmanager::isFailed($this->taskStatus) || !isset($this->taskStatus['data']['addresses'])) { + $this->taskStatus['data']['addresses'] = false; + return false; + } + + $sortIp = array(); + foreach (array_keys($this->taskStatus['data']['addresses']) as $key) { + $item = & $this->taskStatus['data']['addresses'][$key]; + if (!isset($item['ip']) || !preg_match('/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/', $item['ip']) || substr($item['ip'], 0, 4) === '127.') { + unset($this->taskStatus['data']['addresses'][$key]); + continue; + } + if ($this->currentAddress === $item['ip']) { + $item['default'] = true; + $this->hasIpSet = true; + } + $sortIp[] = $item['ip']; + } + unset($item); + array_multisort($sortIp, SORT_STRING, $this->taskStatus['data']['addresses']); + return true; + } + + private function updateLocalAddress() + { + $newAddress = Request::post('ip', 'none'); + $valid = false; + foreach ($this->taskStatus['data']['addresses'] as $item) { + if ($item['ip'] !== $newAddress) + continue; + $valid = true; + break; + } + if ($valid) { + Property::setServerIp($newAddress); + Util::redirect('?do=ServerSetup'); + } else { + Message::addError('invalid-ip', $newAddress); + } + Util::redirect(); + } + + private function updatePxeMenu() + { + $timeout = Request::post('timeout', 10); + if ($timeout === '') + $timeout = 0; + if (!is_numeric($timeout) || $timeout < 0) { + Message::addError('main.value-invalid', 'timeout', $timeout); + } + $this->currentMenu['defaultentry'] = Request::post('defaultentry', 'net'); + $this->currentMenu['timeout'] = $timeout; + $this->currentMenu['custom'] = Request::post('custom', ''); + $this->currentMenu['masterpasswordclear'] = Request::post('masterpassword', ''); + if (empty($this->currentMenu['masterpasswordclear'])) + $this->currentMenu['masterpassword'] = 'invalid'; + else + $this->currentMenu['masterpassword'] = Crypto::hash6($this->currentMenu['masterpasswordclear']); + Property::setBootMenu($this->currentMenu); + Trigger::ipxe(); + Util::redirect('?do=ServerSetup'); + } + + private function handleGetImage() + { + $file = "/opt/openslx/ipxe/openslx-bootstick.raw"; + if (!is_readable($file)) { + Message::addError('image-not-found'); + return; + } + Header('Content-Type: application/octet-stream'); + Header('Content-Disposition: attachment; filename="openslx-bootstick-' . Property::getServerIp() . '-raw.img"'); + readfile($file); + exit; + } + +} diff --git a/modules-available/serversetup-bwlp-pxelinux/permissions/permissions.json b/modules-available/serversetup-bwlp-pxelinux/permissions/permissions.json new file mode 100644 index 00000000..44927506 --- /dev/null +++ b/modules-available/serversetup-bwlp-pxelinux/permissions/permissions.json @@ -0,0 +1,14 @@ +{ + "access-page": { + "location-aware": false + }, + "download": { + "location-aware": false + }, + "edit.address": { + "location-aware": false + }, + "edit.menu": { + "location-aware": false + } +} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-pxelinux/templates/heading.html b/modules-available/serversetup-bwlp-pxelinux/templates/heading.html new file mode 100644 index 00000000..d68360f1 --- /dev/null +++ b/modules-available/serversetup-bwlp-pxelinux/templates/heading.html @@ -0,0 +1 @@ +

{{lang_moduleHeading}}

\ No newline at end of file diff --git a/modules-available/serversetup-bwlp-pxelinux/templates/ipaddress.html b/modules-available/serversetup-bwlp-pxelinux/templates/ipaddress.html new file mode 100644 index 00000000..8d73dfac --- /dev/null +++ b/modules-available/serversetup-bwlp-pxelinux/templates/ipaddress.html @@ -0,0 +1,37 @@ +
+
+ {{lang_bootAddress}} +
+
+
+ {{lang_chooseIP}} +
+
+ + + + {{#ips}} + + + {{#default}} + + {{/default}} + {{^default}} + + {{/default}} + + {{/ips}} +
{{ip}} + {{lang_active}} + + +
+

+ {{lang_bootHint}} +

+
+
+
\ No newline at end of file diff --git a/modules-available/serversetup-bwlp-pxelinux/templates/ipxe.html b/modules-available/serversetup-bwlp-pxelinux/templates/ipxe.html new file mode 100644 index 00000000..f4b0b4d3 --- /dev/null +++ b/modules-available/serversetup-bwlp-pxelinux/templates/ipxe.html @@ -0,0 +1,117 @@ +
+ + + + +
+
+ {{lang_bootMenu}} +
+
+

+ {{lang_bootInfo}} +

+
+ +
+ {{lang_bootBehavior}} +
+ + +
+
+ + +
+
+ + +
+
+ +
+ {{lang_menuDisplayTime}} +
+ + {{lang_seconds}} +
+
+ +
+ {{lang_masterPassword}} +
+ +
+ {{lang_masterPasswordHelp}} +
+ +
+ {{lang_menuCustom}} + +
+
+ + +
+
+ + + + \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-pxelinux/templates/ipxe_update.html b/modules-available/serversetup-bwlp-pxelinux/templates/ipxe_update.html new file mode 100644 index 00000000..c5aafa1c --- /dev/null +++ b/modules-available/serversetup-bwlp-pxelinux/templates/ipxe_update.html @@ -0,0 +1,38 @@ +
+
{{lang_menuGeneration}}
+
+ + +
+
+ {{lang_generationFailed}} +
+
+
{{lang_menuGeneration}}
+
+
+ + diff --git a/modules-available/serversetup-bwlp/api.inc.php b/modules-available/serversetup-bwlp/api.inc.php deleted file mode 100644 index 1df0e6e7..00000000 --- a/modules-available/serversetup-bwlp/api.inc.php +++ /dev/null @@ -1,254 +0,0 @@ - $v) { - $query .= $k . '=' . $v . '&'; - } - //$query = substr($query, 0, -1); - echo << $uuid]); - if ($row !== false && !empty($row['systemmodel'])) { - $model = $row['systemmodel']; - } -} -if ($model === false) { - // Otherwise use what iPXE sent us - function modfilt($str) - { - if (empty($str) || preg_match('/product\s+name|be\s+filled|unknown|default\s+string/i', $str)) - return false; - return trim(preg_replace('/\s+/', ' ', $str)); - } - $manuf = modfilt($manuf); - $product = modfilt($product); - if (!empty($product)) { - $model = $product; - if (!empty($manuf)) { - $model .= " ($manuf)"; - } - } -} -// Query -if ($model !== false) { - $row = Database::queryFirst("SELECT bootmethod FROM serversetup_localboot WHERE systemmodel = :model LIMIT 1", - ['model' => $model]); - if ($row !== false) { - $localboot = $row['bootmethod']; - } -} -if ($localboot === false || !isset($BOOT_METHODS[$localboot])) { - $localboot = Property::get(Localboot::PROPERTY_KEY, 'AUTO'); - if (!isset($BOOT_METHODS[$localboot])) { - $localboot = 'AUTO'; - } -} -if (isset($BOOT_METHODS[$localboot])) { - // Move preferred method first - $BOOT_METHODS[] = $BOOT_METHODS[$localboot]; - unset($BOOT_METHODS[$localboot]); - $BOOT_METHODS = array_reverse($BOOT_METHODS); -} - -if ($slxExtensions) { - $slxConsoleUpdate = '--update'; -} else { - $slxConsoleUpdate = ''; -} - -$output = <<getMenuDefinition('target', $platform, $slxExtensions); - -$output .= <<getItemsCode($platform); - -/* - -:i5 -chain -a /tftp/memtest.0 passes=1 onepass || goto membad -prompt Memory OK. Press a key. -goto init - -:i8 -set x:int32 0 -:again -console --left 60 --top 130 --right 67 --bottom 96 --picture bg-load --keep || -console --left 55 --top 88 --right 63 --bottom 64 --picture bg-menu --keep || -inc x -iseq \${x} 20 || goto again -prompt DONE. Press dein Knie. -goto slx_menu - -:membad -iseq \${errno} 0x1 || goto memaborted -params -param scrot \${vram} -imgfetch -a http://132.230.8.113/screen.php##params || -prompt Memory is bad. Press a key. -goto init - -:memaborted -params -param scrot \${vram} -imgfetch -a http://132.230.8.113/screen.php##params || -prompt Memory test aborted. Press a key. -goto init - -*/ - -$output .= << 0) { - EventLog::info('Imported old PXELinux menu, with ' . $num . ' additional IP-range based menus.'); - } else { - EventLog::info('Imported old PXELinux menu.'); - } -} diff --git a/modules-available/serversetup-bwlp/hooks/ipxe-update.inc.php b/modules-available/serversetup-bwlp/hooks/ipxe-update.inc.php deleted file mode 100644 index 166e80a8..00000000 --- a/modules-available/serversetup-bwlp/hooks/ipxe-update.inc.php +++ /dev/null @@ -1,10 +0,0 @@ - Property::getServerIp() -]; -$task = Taskmanager::submit('CompileIPxeNew', $data); -if (!isset($task['id'])) -return false; -Property::set('ipxe-task-id', $task['id'], 15); -return $task['id']; \ No newline at end of file diff --git a/modules-available/serversetup-bwlp/hooks/main-warning.inc.php b/modules-available/serversetup-bwlp/hooks/main-warning.inc.php deleted file mode 100644 index a2eba6ff..00000000 --- a/modules-available/serversetup-bwlp/hooks/main-warning.inc.php +++ /dev/null @@ -1,6 +0,0 @@ - $value) { - if (property_exists($this, $key)) { - $this->{$key} = $value; - } - } - } - } - - public abstract function supportsMode($mode); - - public abstract function toScript($failLabel, $mode); - - public abstract function toArray(); - - public abstract function addFormFields(&$array); - - /* - * - */ - - /** - * Return a BootEntry instance from the serialized data. - * - * @param string $jsonString serialized entry data - * @return BootEntry|null instance representing boot entry, null on error - */ - public static function fromJson($data) - { - if (is_string($data)) { - $data = json_decode($data, true); - } - if (isset($data['script'])) { - return new CustomBootEntry($data); - } - if (isset($data['executable'])) { - return new StandardBootEntry($data); - } - return null; - } - - public static function newStandardBootEntry($initData) - { - $ret = new StandardBootEntry($initData); - $list = []; - if ($ret->arch() !== StandardBootEntry::EFI) { - $list[] = StandardBootEntry::BIOS; - } - if ($ret->arch() === StandardBootEntry::EFI || $ret->arch() === StandardBootEntry::BOTH) { - $list[] = StandardBootEntry::EFI; - } - foreach ($list as $mode) { - if (empty($initData['executable'][$mode])) - return null; - } - return $ret; - } - - public static function newCustomBootEntry($initData) - { - if (empty($initData['script'])) - return null; - return new CustomBootEntry($initData); - } - - /** - * Return a BootEntry instance from database with the given id. - * - * @param string $id - * @return BootEntry|null|false false == unknown id, null = unknown entry type, BootEntry instance on success - */ - public static function fromDatabaseId($id) - { - $row = Database::queryFirst("SELECT data FROM serversetup_bootentry - WHERE entryid = :id LIMIT 1", ['id' => $id]); - if ($row === false) - return false; - return self::fromJson($row['data']); - } - -} - -class StandardBootEntry extends BootEntry -{ - protected $executable; - protected $initRd; - protected $commandLine; - protected $replace; - protected $autoUnload; - protected $resetConsole; - protected $arch; // Constants below - - const BIOS = 'PCBIOS'; // Only valid for legacy BIOS boot - const EFI = 'EFI'; // Only valid for EFI boot - const BOTH = 'PCBIOS-EFI'; // Supports both via distinct entry - const AGNOSTIC = 'agnostic'; // Supports both via same entry (PCBIOS entry) - - public function __construct($data = false) - { - if ($data instanceof PxeSection) { - // Gets arrayfied below - $this->executable = $data->kernel; - $this->initRd = $data->initrd; - $this->commandLine = ' ' . str_replace('vga=current', '', $data->append) . ' '; - $this->resetConsole = true; - $this->replace = true; - $this->autoUnload = true; - if (strpos($this->commandLine, ' quiet ') !== false) { - $this->commandLine .= ' loglevel=5 rd.systemd.show_status=auto'; - } - if ($data->ipAppend & 1) { - $this->commandLine .= ' ${ipappend1}'; - } - if ($data->ipAppend & 2) { - $this->commandLine .= ' ${ipappend2}'; - } - if ($data->ipAppend & 4) { - $this->commandLine .= ' SYSUUID=${uuid}'; - } - $this->commandLine = trim(preg_replace('/\s+/', ' ', $this->commandLine)); - } else { - parent::__construct($data); - } - // Convert legacy DB format - foreach (['executable', 'initRd', 'commandLine', 'replace', 'autoUnload', 'resetConsole'] as $key) { - if (!is_array($this->{$key})) { - $this->{$key} = [ 'PCBIOS' => $this->{$key}, 'EFI' => '' ]; - } - } - if ($this->arch === null) { - $this->arch = self::AGNOSTIC; - } - } - - public function arch() - { - return $this->arch; - } - - public function supportsMode($mode) - { - if ($mode === $this->arch || $this->arch === self::AGNOSTIC) - return true; - if ($mode === self::BIOS || $mode === self::EFI) { - return $this->arch === self::BOTH; - } - error_log('Unknown iPXE platform: ' . $mode); - return false; - } - - public function toScript($failLabel, $mode) - { - if (!$this->supportsMode($mode)) { - return "prompt Entry doesn't have an executable for mode $mode\n"; - } - if ($this->arch === self::AGNOSTIC) { - $mode = self::BIOS; - } - - $script = ''; - if ($this->resetConsole[$mode]) { - $script .= "console ||\n"; - } - if (!empty($this->initRd[$mode])) { - $script .= "imgfree ||\n"; - if (!is_array($this->initRd[$mode])) { - $script .= "initrd {$this->initRd[$mode]} || goto $failLabel\n"; - } else { - foreach ($this->initRd[$mode] as $initrd) { - $script .= "initrd $initrd || goto $failLabel\n"; - } - } - } - $script .= "boot "; - if ($this->autoUnload[$mode]) { - $script .= "-a "; - } - if ($this->replace[$mode]) { - $script .= "-r "; - } - $script .= $this->executable[$mode]; - $rdBase = basename($this->initRd[$mode]); - if (!empty($this->commandLine[$mode])) { - $script .= " initrd=$rdBase {$this->commandLine[$mode]}"; - } - $script .= " || goto $failLabel\n"; - if ($this->resetConsole[$mode]) { - $script .= "goto start ||\n"; - } - return $script; - } - - public function addFormFields(&$array) - { - $array[$this->arch . '_selected'] = 'selected'; - foreach ([self::BIOS, self::EFI] as $mode) { - $array['entries'][] = [ - 'is' . $mode => true, - 'mode' => $mode, - 'executable' => $this->executable[$mode], - 'initRd' => $this->initRd[$mode], - 'commandLine' => $this->commandLine[$mode], - 'replace_checked' => $this->replace[$mode] ? 'checked' : '', - 'autoUnload_checked' => $this->autoUnload[$mode] ? 'checked' : '', - 'resetConsole_checked' => $this->resetConsole[$mode] ? 'checked' : '', - ]; - } - $array['exec_checked'] = 'checked'; - } - - public function toArray() - { - return [ - 'executable' => $this->executable, - 'initRd' => $this->initRd, - 'commandLine' => $this->commandLine, - 'replace' => $this->replace, - 'autoUnload' => $this->autoUnload, - 'resetConsole' => $this->resetConsole, - 'arch' => $this->arch, - ]; - } -} - -class CustomBootEntry extends BootEntry -{ - protected $script; - - public function supportsMode($mode) - { - return true; - } - - public function toScript($failLabel, $mode) - { - return str_replace('%fail%', $failLabel, $this->script) . "\n"; - } - - public function addFormFields(&$array) - { - $array['entry'] = [ - 'script' => $this->script, - ]; - $array['script_checked'] = 'checked'; - } - - public function toArray() - { - return ['script' => $this->script]; - } -} diff --git a/modules-available/serversetup-bwlp/inc/ipxe.inc.php b/modules-available/serversetup-bwlp/inc/ipxe.inc.php deleted file mode 100644 index d34839f0..00000000 --- a/modules-available/serversetup-bwlp/inc/ipxe.inc.php +++ /dev/null @@ -1,453 +0,0 @@ -= :start AND endaddr <= :end", compact('start', 'end')); - $locations = []; - while ($row = $res->fetch(PDO::FETCH_ASSOC)) { - foreach ($locations as &$loc) { - if ($row['startaddr'] <= $loc['startaddr'] && $row['endaddr'] >= $loc['endaddr']) { - $loc = false; - } elseif ($row['startaddr'] >= $loc['startaddr'] && $row['endaddr'] <= $loc['endaddr']) { - continue 2; - } - } - unset($loc); - $locations[] = $row; - } - $menu = PxeLinux::parsePxeLinux($content); - $key = $menu->hash(true); - if (isset($menus[$key])) { - $menuId = $menus[$key]; - $defId = null; - // Figure out the default label, get it's label name - foreach ($menu->sections as $section) { - if ($section->isDefault) { - $defId = $section; - } elseif ($defId === null && $section->label === $menu->timeoutLabel) { - $defId = $section; - } - } - if ($defId !== null) { - $defId = self::cleanLabelFixLocal($defId); - // Confirm it actually exists (it should since the menu seems identical) and get menuEntryId - $me = Database::queryFirst('SELECT m.defaultentryid, me.menuentryid FROM serversetup_bootentry be - INNER JOIN serversetup_menuentry me ON (be.entryid = me.entryid) - INNER JOIN serversetup_menu m ON (m.menuid = me.menuid) - WHERE be.entryid = :id AND me.menuid = :menuid', - ['id' => $defId, 'menuid' => $menuId]); - if ($me === false || $me['defaultentryid'] == $me['menuentryid']) { - $defId = null; // Not found, or is already default - don't override if it's the same - } else { - $defId = $me['menuentryid']; - } - } - } else { - $menuId = self::insertMenu($menu, 'Imported', false, 0, [], []); - $menus[$key] = $menuId; - $defId = null; - $importCount++; - } - if ($menuId === false) - continue; - foreach ($locations as $loc) { - if ($loc === false) - continue; - Database::exec('INSERT IGNORE INTO serversetup_menu_location (menuid, locationid, defaultentryid) - VALUES (:menuid, :locationid, :def)', [ - 'menuid' => $menuId, - 'locationid' => $loc['locationid'], - 'def' => $defId, - ]); - } - } - return $importCount; - } - - public static function importLegacyMenu($force = false) - { - if (!$force && false !== Database::queryFirst("SELECT entryid FROM serversetup_bootentry WHERE entryid = 'bwlp-default'")) - return false; // Already exists - // Now create the default entry - self::createDefaultEntries(); - $prepend = ['bwlp-default' => false, 'localboot' => false]; - $defaultLabel = 'bwlp-default'; - $menuTitle = 'bwLehrpool Bootauswahl'; - $pxeConfig = ''; - $timeoutSecs = 60; - // Try to import any customization - $oldMenu = Property::getBootMenu(); - if (is_array($oldMenu)) { - // - if (isset($oldMenu['timeout'])) { - $timeoutSecs = (int)$oldMenu['timeout']; - } - if (isset($oldMenu['defaultentry'])) { - if ($oldMenu['defaultentry'] === 'net') { - $defaultLabel = 'bwlp-default'; - } elseif ($oldMenu['defaultentry'] === 'hdd') { - $defaultLabel = 'localboot'; - } elseif ($oldMenu['defaultentry'] === 'custom') { - $defaultLabel = 'custom'; - } - } - if (!empty($oldMenu['custom'])) { - $pxeConfig = $oldMenu['custom']; - } - } - $append = [ - '', - 'bwlp-default-dbg' => false, - '', - 'poweroff' => false, - ]; - return self::insertMenu(PxeLinux::parsePxeLinux($pxeConfig), $menuTitle, $defaultLabel, $timeoutSecs, $prepend, $append); - } - - /** - * @param PxeMenu $pxeMenu - * @param string $menuTitle - * @param string|false $defaultLabel - * @param $defaultTimeoutSeconds - * @param $prepend - * @param $append - * @return bool|int - */ - private static function insertMenu($pxeMenu, $menuTitle, $defaultLabel, $defaultTimeoutSeconds, $prepend, $append) - { - $timeoutMs = []; - $menuEntries = $prepend; - settype($menuEntries, 'array'); - if (!empty($pxeMenu)) { - $pxe =& $pxeMenu; - if (!empty($pxe->title)) { - $menuTitle = $pxe->title; - } - if ($pxe->timeoutLabel !== null) { - $defaultLabel = $pxe->timeoutLabel; - } - $timeoutMs[] = $pxe->timeoutMs; - $timeoutMs[] = $pxe->totalTimeoutMs; - foreach ($pxe->sections as $section) { - if ($section->localBoot || preg_match('/chain\.c32$/i', $section->kernel)) { - $menuEntries['localboot'] = 'localboot'; - continue; - } - if ($section->label === null) { - if (!$section->isHidden && !empty($section->title)) { - $menuEntries[] = $section->title; - } - continue; - } - if (empty($section->kernel)) { - if (!$section->isHidden && !empty($section->title)) { - $menuEntries[] = $section->title; - } - continue; - } - $entry = self::pxe2BootEntry($section); - if ($entry === null) - continue; - $label = self::cleanLabelFixLocal($section); - if ($defaultLabel === $section->label) { - $defaultLabel = $label; - } - $hotkey = MenuEntry::filterKeyName($section->hotkey); - // Create boot entry - $data = $entry->toArray(); - Database::exec('INSERT IGNORE INTO serversetup_bootentry (entryid, hotkey, title, builtin, data) - VALUES (:label, :hotkey, :title, 0, :data)', [ - 'label' => $label, - 'hotkey' => $hotkey, - 'title' => self::sanitizeIpxeString($section->title), - 'data' => json_encode($data), - ]); - $menuEntries[$label] = $section; - } - } - if (is_array($append)) { - $menuEntries += $append; - } - if (empty($menuEntries)) - return false; - // Make menu - $timeoutMs = array_filter($timeoutMs, 'is_int'); - if (empty($timeoutMs)) { - $timeoutMs = (int)($defaultTimeoutSeconds * 1000); - } else { - $timeoutMs = min($timeoutMs); - } - $isDefault = (int)(Database::queryFirst('SELECT menuid FROM serversetup_menu WHERE isdefault = 1') === false); - Database::exec("INSERT INTO serversetup_menu (timeoutms, title, defaultentryid, isdefault) - VALUES (:timeoutms, :title, NULL, :isdefault)", [ - 'title' => self::sanitizeIpxeString($menuTitle), - 'timeoutms' => $timeoutMs, - 'isdefault' => $isDefault, - ]); - $menuId = Database::lastInsertId(); - if (!array_key_exists($defaultLabel, $menuEntries) && $timeoutMs > 0) { - $defaultLabel = array_keys($menuEntries)[0]; - } - // Link boot entries to menu - $defaultEntryId = null; - $order = 1000; - foreach ($menuEntries as $label => $entry) { - if (is_string($entry)) { - // Gap entry - Database::exec("INSERT INTO serversetup_menuentry - (menuid, entryid, hotkey, title, hidden, sortval, plainpass, md5pass) - VALUES (:menuid, :entryid, :hotkey, :title, :hidden, :sortval, '', '')", [ - 'menuid' => $menuId, - 'entryid' => null, - 'hotkey' => '', - 'title' => self::sanitizeIpxeString($entry), - 'hidden' => 0, - 'sortval' => $order += 100, - ]); - continue; - } - $data = Database::queryFirst("SELECT entryid, hotkey, title FROM serversetup_bootentry WHERE entryid = :entryid", ['entryid' => $label]); - if ($data === false) - continue; - $data['pass'] = ''; - $data['hidden'] = 0; - if ($entry instanceof PxeSection) { - $data['hidden'] = (int)$entry->isHidden; - // Prefer explicit data from this imported menu over the defaults - $data['title'] = self::sanitizeIpxeString($entry->title); - if (MenuEntry::getKeyCode($entry->hotkey) !== false) { - $data['hotkey'] = $entry->hotkey; - } - if (!empty($entry->passwd)) { - // Most likely it's a hash so we cannot recover; ask people to reset - $data['pass'] ='please_reset'; - } - } - $data['menuid'] = $menuId; - $data['sortval'] = $order += 100; - $res = Database::exec("INSERT INTO serversetup_menuentry - (menuid, entryid, hotkey, title, hidden, sortval, plainpass, md5pass) - VALUES (:menuid, :entryid, :hotkey, :title, :hidden, :sortval, :pass, :pass)", $data); - if ($res !== false && $label === $defaultLabel) { - $defaultEntryId = Database::lastInsertId(); - } - } - // Now we can set default entry - if (!empty($defaultEntryId)) { - Database::exec("UPDATE serversetup_menu SET defaultentryid = :entryid WHERE menuid = :menuid", - ['menuid' => $menuId, 'entryid' => $defaultEntryId]); - } - // TODO: masterpw? rather pointless.... - //$oldMenu['masterpasswordclear']; - return $menuId; - } - - private static function createDefaultEntries() - { - $query = 'INSERT IGNORE INTO serversetup_bootentry (entryid, hotkey, title, builtin, data) - VALUES (:entryid, :hotkey, :title, 1, :data)'; - Database::exec($query, - [ - 'entryid' => 'bwlp-default', - 'hotkey' => 'B', - 'title' => 'bwLehrpool-Umgebung starten', - 'data' => json_encode([ - 'executable' => '/boot/default/kernel', - 'initRd' => '/boot/default/initramfs-stage31', - 'commandLine' => 'slxbase=boot/default quiet splash loglevel=5 rd.systemd.show_status=auto intel_iommu=igfx_off ${ipappend1} ${ipappend2}', - 'replace' => true, - 'autoUnload' => true, - 'resetConsole' => true, - ]), - ]); - Database::exec($query, - [ - 'entryid' => 'bwlp-default-dbg', - 'hotkey' => '', - 'title' => 'bwLehrpool-Umgebung starten (nosplash, debug)', - 'data' => json_encode([ - 'executable' => '/boot/default/kernel', - 'initRd' => '/boot/default/initramfs-stage31', - 'commandLine' => 'slxbase=boot/default loglevel=7 intel_iommu=igfx_off ${ipappend1} ${ipappend2}', - 'replace' => true, - 'autoUnload' => true, - 'resetConsole' => true, - ]), - ]); - Database::exec($query, - [ - 'entryid' => 'localboot', - 'hotkey' => 'L', - 'title' => 'Lokales System starten', - 'data' => json_encode([ - 'script' => 'goto slx_localboot || goto %fail% ||', - ]), - ]); - Database::exec($query, - [ - 'entryid' => 'poweroff', - 'hotkey' => 'P', - 'title' => 'Power off', - 'data' => json_encode([ - 'script' => 'poweroff || goto %fail% ||', - ]), - ]); - Database::exec($query, - [ - 'entryid' => 'reboot', - 'hotkey' => 'R', - 'title' => 'Reboot', - 'data' => json_encode([ - 'script' => 'reboot || goto %fail% ||', - ]), - ]); - } - - /** - * Create unique label for a boot entry. It will try to figure out whether - * this is one of our default entries and if not, create a unique label - * representing the menu entry contents. - * Also it patches the entry if it's referencing the local bwlp install - * because side effects. - * - * @param PxeSection $section - * @return string - */ - private static function cleanLabelFixLocal($section) - { - $myip = Property::getServerIp(); - // Detect our "old" entry types - if (count($section->initrd) === 1 && preg_match(",$myip/boot/default/kernel\$,", $section->kernel) - && preg_match(",$myip/boot/default/initramfs-stage31\$,", $section->initrd[0])) { - // Kernel and initrd match, examine KCL - if ($section->append === 'slxbase=boot/default vga=current quiet splash') { - // Normal - return 'bwlp-default'; - } elseif ($section->append === 'slxbase=boot/default') { - // Debug output - return 'bwlp-default-dbg'; - } else { - // Transform to relative URL, leave KCL, fall through to generic label gen - $section->kernel = '/boot/default/kernel'; - $section->initrd = ['/boot/default/initramfs-stage31']; - } - } - // Generic -- "smart" hash of kernel, initrd and command line - $str = $section->kernel . ' ' . implode(',', $section->initrd); - $array = preg_split('/\s+/', $section->append, -1, PREG_SPLIT_NO_EMPTY); - sort($array); - $str .= ' ' . implode(' ', $array); - - return 'i-' . substr(md5($str), 0, 12); - } - - /** - * @param PxeSection $section - * @return BootEntry|null The according boot entry, null if it's unparsable - */ - private static function pxe2BootEntry($section) - { - if (preg_match('/(pxechain\.com|pxechn\.c32)$/i', $section->kernel)) { - // Chaining -- create script - $args = preg_split('/\s+/', $section->append); - $script = ''; - $file = false; - for ($i = 0; $i < count($args); ++$i) { - $arg = $args[$i]; - if ($arg === '-c') { // PXELINUX config file option - ++$i; - $script .= "set 209:string {$args[$i]} || goto %fail%\n"; - } elseif ($arg === '-p') { // PXELINUX prefix path option - ++$i; - $script .= "set 210:string {$args[$i]} || goto %fail%\n"; - } elseif ($arg === '-t') { // PXELINUX timeout option - ++$i; - $script .= "set 211:int32 {$args[$i]} || goto %fail%\n"; - } elseif ($arg === '-o') { // Overriding various DHCP options - ++$i; - if (preg_match('/^((?:0x)?[a-f0-9]{1,4})\.([bwlsh])=(.*)$/i', $args[$i], $out)) { - // TODO: 'q' (8byte) unsupported for now - $opt = intval($out[1], 0); - if ($opt > 0 && $opt < 255) { - static $optType = ['b' => 'uint8', 'w' => 'uint16', 'l' => 'int32', 's' => 'string', 'h' => 'hex']; - $type = $optType[$out[2]]; - $script .= "set {$opt}:{$type} {$args[$i]} || goto %fail%\n"; - } - } - } elseif ($arg{0} === '-') { - continue; - } elseif ($file === false) { - $file = self::parseFile($arg); - } - } - if ($file !== false) { - $url = parse_url($file); - if (isset($url['host'])) { - $script .= "set next-server {$url['host']} || goto %fail%\n"; - } - if (isset($url['path'])) { - $script .= "set filename {$url['path']} || goto %fail%\n"; - } - $script .= "chain -ar {$file} || goto %fail%\n"; - return new CustomBootEntry(['script' => $script]); - } - return null; - } - // "Normal" entry that should be convertible into a StandardBootEntry - $section->kernel = self::parseFile($section->kernel); - foreach ($section->initrd as &$initrd) { - $initrd = self::parseFile($initrd); - } - return BootEntry::newStandardBootEntry($section); - } - - /** - * Parse PXELINUX file notion. Basically, turn - * server::file into tftp://server/file. - * - * @param string $file - * @return string - */ - private static function parseFile($file) - { - if (preg_match(',^([^:/]+)::(.*)$,', $file, $out)) { - return 'tftp://' . $out[1] . '/' . $out[2]; - } - return $file; - } - - public static function sanitizeIpxeString($string) - { - return str_replace(['&', '|', ';', '$', "\r", "\n"], ['+', '/', ':', 'S', ' ', ' '], $string); - } - - public static function makeMd5Pass($plainpass, $salt) - { - if (empty($plainpass)) - return ''; - return md5(md5($plainpass) . '-' . $salt); - } - -} diff --git a/modules-available/serversetup-bwlp/inc/ipxemenu.inc.php b/modules-available/serversetup-bwlp/inc/ipxemenu.inc.php deleted file mode 100644 index 5c1a87d5..00000000 --- a/modules-available/serversetup-bwlp/inc/ipxemenu.inc.php +++ /dev/null @@ -1,142 +0,0 @@ - $menu]); - if (!is_array($menu)) { - $menu = ['menuid' => 'foo', 'title' => 'Invalid Menu ID: ' . (int)$menu]; - } - } - $this->menuid = (int)$menu['menuid']; - $this->timeoutMs = (int)$menu['timeoutms']; - $this->title = $menu['title']; - $this->defaultEntryId = $menu['defaultentryid']; - $res = Database::simpleQuery("SELECT e.menuentryid, e.entryid, e.hotkey, e.title, e.hidden, e.sortval, e.md5pass, - b.data AS bootentry - FROM serversetup_menuentry e - LEFT JOIN serversetup_bootentry b USING (entryid) - WHERE e.menuid = :menuid - ORDER BY e.sortval ASC, e.title ASC", ['menuid' => $menu['menuid']]); - while ($row = $res->fetch(PDO::FETCH_ASSOC)) { - $this->items[] = new MenuEntry($row); - } - } - - public function getMenuDefinition($targetVar, $mode, $slxExtensions) - { - $str = "menu -- {$this->title}\n"; - foreach ($this->items as $item) { - $str .= $item->getMenuItemScript("m_{$this->menuid}", $this->defaultEntryId, $mode, $slxExtensions); - } - if ($this->defaultEntryId === null) { - $defaultLabel = "mx_{$this->menuid}_poweroff"; - } else { - $defaultLabel = "m_{$this->menuid}_{$this->defaultEntryId}"; - } - $str .= "choose"; - if ($this->timeoutMs > 0) { - $str .= " --timeout {$this->timeoutMs}"; - } - $str .= " $targetVar || goto $defaultLabel || goto fail\n"; - if ($this->defaultEntryId === null) { - $str .= "goto skip_{$defaultLabel}\n" - . ":{$defaultLabel}\n" - . "poweroff || goto fail\n" - . ":skip_{$defaultLabel}\n"; - } - return $str; - } - - public function getItemsCode($mode) - { - $str = ''; - foreach ($this->items as $item) { - $str .= $item->getBootEntryScript("m_{$this->menuid}", 'fail', $mode); - $str .= "goto slx_menu\n"; - } - return $str; - } - - /* - * - */ - - public static function forLocation($locationId) - { - $chain = null; - if (Module::isAvailable('location')) { - $chain = Location::getLocationRootChain($locationId); - } - if (!empty($chain)) { - $res = Database::simpleQuery("SELECT m.menuid, m.timeoutms, m.title, IFNULL(ml.defaultentryid, m.defaultentryid) AS defaultentryid, ml.locationid - FROM serversetup_menu m - INNER JOIN serversetup_menu_location ml USING (menuid) - WHERE ml.locationid IN (:chain)", ['chain' => $chain]); - if ($res->rowCount() > 0) { - // Make the location id key, preserving order (closest location is first) - $chain = array_flip($chain); - while ($row = $res->fetch(PDO::FETCH_ASSOC)) { - // Overwrite the value (numeric ascending values, useless) with menu array of according location - $chain[(int)$row['locationid']] = $row; - } - // Use first one that was found - foreach ($chain as $menu) { - if (is_array($menu)) { - return new IPxeMenu($menu); - } - } - // Should never end up here, but we'd just fall through and use the default - } - } - // We're here, no specific menu, use default - $menu = Database::queryFirst("SELECT menuid, timeoutms, title, defaultentryid - FROM serversetup_menu - ORDER BY isdefault DESC LIMIT 1"); - if ($menu === false) { - return new EmptyIPxeMenu; - } - return new IPxeMenu($menu); - } - - public static function forClient($ip, $uuid) - { - $locationId = 0; - if (Module::isAvailable('location')) { - $locationId = Location::getFromIpAndUuid($ip, $uuid); - } - return self::forLocation($locationId); - } - -} - -class EmptyIPxeMenu extends IPxeMenu -{ - - /** @noinspection PhpMissingParentConstructorInspection */ - public function __construct() - { - $this->title = 'No menu defined'; - $this->menuid = -1; - $this->items[] = new MenuEntry([ - 'title' => 'Please create a menu in Server-Setup first' - ]); - $this->items[] = new MenuEntry([ - 'title' => 'Bitte erstellen Sie zunächst ein Menü' - ]); - } - -} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp/inc/localboot.inc.php b/modules-available/serversetup-bwlp/inc/localboot.inc.php deleted file mode 100644 index 3ab81862..00000000 --- a/modules-available/serversetup-bwlp/inc/localboot.inc.php +++ /dev/null @@ -1,15 +0,0 @@ - 'iseq efi ${platform} && exit 1 || sanboot --no-describe', - 'EXIT' => 'exit 1', - 'COMBOOT' => 'chain /tftp/chain.c32 hd0', - 'SANBOOT' => 'sanboot --no-describe', - ]; - -} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp/inc/menuentry.inc.php b/modules-available/serversetup-bwlp/inc/menuentry.inc.php deleted file mode 100644 index d243fd23..00000000 --- a/modules-available/serversetup-bwlp/inc/menuentry.inc.php +++ /dev/null @@ -1,177 +0,0 @@ - $value) { - if (property_exists($this, $key)) { - $this->{$key} = $value; - } - } - $this->hotkey = self::getKeyCode($row['hotkey']); - if (!empty($row['bootentry'])) { - $this->bootEntry = BootEntry::fromJson($row['bootentry']); - } - $this->gap = (array_key_exists('entryid', $row) && $row['entryid'] === null); - } - settype($this->hidden, 'bool'); - settype($this->gap, 'bool'); - settype($this->sortval, 'int'); - settype($this->menuentryid, 'int'); - } - - public function getMenuItemScript($lblPrefix, $requestedDefaultId, $mode, $slxExtensions) - { - if ($this->bootEntry !== null && !$this->bootEntry->supportsMode($mode)) - return ''; - $str = 'item '; - if ($this->gap) { - $str .= '--gap '; - } else { - if ($this->hidden && $slxExtensions) { - if ($this->hotkey === false) - return ''; // Hidden entries without hotkey are illegal - $str .= '--hidden '; - } - if ($this->hotkey !== false) { - $str .= '--key ' . $this->hotkey . ' '; - } - if ($this->menuentryid == $requestedDefaultId) { - $str .= '--default '; - } - $str .= "{$lblPrefix}_{$this->menuentryid} "; - } - if (empty($this->title)) { - $str .= '${}'; - } else { - $str .= $this->title; - } - return $str . " || prompt Could not create menu item for {$lblPrefix}_{$this->menuentryid}\n"; - } - - public function getBootEntryScript($lblPrefix, $failLabel, $mode) - { - if ($this->bootEntry === null || !$this->bootEntry->supportsMode($mode)) - return ''; - $str = ":{$lblPrefix}_{$this->menuentryid}\n"; - if (!empty($this->md5pass)) { - $str .= "set slx_hash {$this->md5pass} || goto $failLabel\n" - . "set slx_salt {$this->menuentryid} || goto $failLabel\n" - . "set slx_pw_ok {$lblPrefix}_ok || goto $failLabel\n" - . "set slx_pw_fail slx_menu || goto $failLabel\n" - . "goto slx_pass_check || goto $failLabel\n" - . ":{$lblPrefix}_ok\n"; - } - return $str . $this->bootEntry->toScript($failLabel, $mode); - } - - /* - * - */ - - private static function getKeyArray() - { - static $data = false; - if ($data === false) { - $data = [ - 'F5' => 0x107e, - 'F6' => 0x127e, - 'F7' => 0x137e, - 'F8' => 0x147e, - 'F9' => 0x157e, - 'F10' => 0x167e, - 'F11' => 0x187e, - 'F12' => 0x197e, - ]; - for ($i = 1; $i <= 26; ++$i) { - $letter = chr(0x40 + $i); - $data['SHIFT_' . $letter] = 0x40 + $i; - if ($letter !== 'C') { - $data['CTRL_' . $letter] = $i; - } - $data[$letter] = 0x60 + $i; - } - for ($i = 0; $i <= 9; ++$i) { - $data[chr(0x30 + $i)] = 0x30 + $i; - } - asort($data, SORT_NUMERIC); - } - return $data; - } - - /** - * Get all the known/supported keys, usable for menu items. - * - * @return string[] list of known key names - */ - public static function getKeyList() - { - return array_keys(self::getKeyArray()); - } - - /** - * Get the key code ipxe expects for the given named - * key. Returns false if the key name is unknown. - * - * @param string $keyName - * @return false|string Key code as hex string, or false if not found - */ - public static function getKeyCode($keyName) - { - $data = self::getKeyArray(); - if (isset($data[$keyName])) - return '0x' . dechex($data[$keyName]); - return false; - } - - /** - * @param string $keyName desired key name - * @return string $keyName if it's known, empty string otherwise - */ - public static function filterKeyName($keyName) - { - $data = self::getKeyArray(); - if (isset($data[$keyName])) - return $keyName; - return ''; - } - -} diff --git a/modules-available/serversetup-bwlp/inc/pxelinux.inc.php b/modules-available/serversetup-bwlp/inc/pxelinux.inc.php deleted file mode 100644 index 1d022fef..00000000 --- a/modules-available/serversetup-bwlp/inc/pxelinux.inc.php +++ /dev/null @@ -1,302 +0,0 @@ - ['string', 'title'], - 'menu default' => ['true', 'isDefault'], - 'menu hide' => ['true', 'isHidden'], - 'menu disabled' => ['true', 'isDisabled'], - 'menu indent' => ['int', 'indent'], - 'kernel' => ['string', 'kernel'], - 'com32' => ['string', 'kernel'], - 'pxe' => ['string', 'kernel'], - 'initrd' => ['string', 'initrd'], - 'append' => ['string', 'append'], - 'ipappend' => ['int', 'ipAppend'], - 'sysappend' => ['int', 'ipAppend'], - 'localboot' => ['int', 'localBoot'], - ]; - $globalPropMap = [ - 'timeout' => ['int', 'timeoutMs', 100], - 'totaltimeout' => ['int', 'totalTimeoutMs', 100], - 'menu title' => ['string', 'title'], - 'menu clear' => ['true', 'menuClear'], - 'menu immediate' => ['true', 'immediateHotkeys'], - 'ontimeout' => ['string', 'timeoutLabel'], - ]; - $lines = preg_split('/[\r\n]+/', $input); - $section = null; - $count = count($lines); - for ($li = 0; $li < $count; ++$li) { - $line =& $lines[$li]; - if (!preg_match('/^\s*([^m]\S*|menu\s+\S+)(\s+.*?|)\s*$/i', $line, $out)) - continue; - $val = trim($out[2]); - $key = trim($out[1]); - $key = strtolower($key); - $key = preg_replace('/\s+/', ' ', $key); - if ($key === 'label') { - if ($section !== null) { - $menu->sections[] = $section; - } - $section = new PxeSection($val); - } elseif ($key === 'menu separator') { - if ($section !== null) { - $menu->sections[] = $section; - $section = null; - } - $menu->sections[] = new PxeSection(null); - } elseif (self::handleKeyword($key, $val, $globalPropMap, $menu)) { - continue; - } elseif ($section === null) { - continue; - } elseif ($key === 'text' && strtolower($val) === 'help') { - $text = ''; - while (++$li < $count) { - $line =& $lines[$li]; - if (strtolower(trim($line)) === 'endtext') - break; - $text .= $line . "\n"; - } - $section->helpText = $text; - } elseif (self::handleKeyword($key, $val, $sectionPropMap, $section)) { - continue; - } - } - if ($section !== null) { - $menu->sections[] = $section; - } - foreach ($menu->sections as $section) { - $section->mangle(); - } - return $menu; - } - - /** - * Check if keyword is valid and if so, add its interpreted value - * to the given object. The map to look up the keyword has to be passed - * as well as the object to set the value in. Map and object should - * obviously match. - * @param string $key keyword of parsed line - * @param string $val raw value of currently parsed line (empty if not present) - * @param array $map Map in which $key is looked up as key - * @param PxeMenu|PxeSection The object to set the parsed and sanitized value in - * @return bool true if the value was found in the map (and set in the object), false otherwise - */ - private static function handleKeyword($key, $val, $map, $object) - { - if (!isset($map[$key])) - return false; - $opt = $map[$key]; - // opt[0] is the type the value should be cast to; special case "true" means - // this is a bool option that will be set as soon as the keyword is present, - // as it doesn't have any parameters - if ($opt[0] === 'true') { - $val = true; - } else { - settype($val, $opt[0]); - } - // If opt[2] is present it's a multiplier for the value - if (isset($opt[2])) { - $val *= $opt[2]; - } - $object->{$opt[1]} = $val; - return true; - } - -} - -/** - * Class representing a parsed pxelinux menu. Members - * will be set to their annotated type if present or - * be null otherwise, except for present-only boolean - * options, which will default to false. - */ -class PxeMenu -{ - - /** - * @var string menu title, shown at the top of the menu - */ - public $title; - /** - * @var int initial timeout after which $timeoutLabel would be executed - */ - public $timeoutMs; - /** - * @var int if the user canceled the timeout by pressing a key, this timeout would still eventually - * trigger and launch the $timeoutLabel section - */ - public $totalTimeoutMs; - /** - * @var string label of section which will execute if the timeout expires - */ - public $timeoutLabel; - /** - * @var bool hide menu and just show background after triggering an entry - */ - public $menuClear = false; - /** - * @var bool boot the associated entry directly if its corresponding hotkey is pressed instead of just highlighting - */ - public $immediateHotkeys = false; - /** - * @var PxeSection[] list of sections the menu contains - */ - public $sections = []; - - public function hash($fuzzy) - { - $ctx = hash_init('md5'); - if (!$fuzzy) { - hash_update($ctx, $this->title); - hash_update($ctx, $this->timeoutLabel); - } - hash_update($ctx, $this->timeoutMs); - foreach ($this->sections as $section) { - if ($fuzzy) { - hash_update($ctx, mb_strtolower(preg_replace('/[^a-zA-Z0-9]/', '', $section->title))); - } else { - hash_update($ctx, $section->label); - hash_update($ctx, $section->title); - hash_update($ctx, $section->indent); - hash_update($ctx, $section->helpText); - hash_update($ctx, $section->isDefault); - hash_update($ctx, $section->hotkey); - } - hash_update($ctx, $section->kernel); - hash_update($ctx, $section->append); - hash_update($ctx, $section->ipAppend); - hash_update($ctx, $section->passwd); - hash_update($ctx, $section->isHidden); - hash_update($ctx, $section->isDisabled); - hash_update($ctx, $section->localBoot); - foreach ($section->initrd as $initrd) { - hash_update($ctx, $initrd); - } - } - return hash_final($ctx, false); - } - -} - -/** - * Class representing a parsed pxelinux menu entry. Members - * will be set to their annotated type if present or - * be null otherwise, except for present-only boolean - * options, which will default to false. - */ -class PxeSection -{ - - /** - * @var string label used internally in PXEMENU definition to address this entry - */ - public $label; - /** - * @var string MENU LABEL of PXEMENU - title of entry displayed to the user - */ - public $title; - /** - * @var int Number of spaces to prefix the title with - */ - public $indent; - /** - * @var string help text to display when the entry is highlighted - */ - public $helpText; - /** - * @var string Kernel to load - */ - public $kernel; - /** - * @var string|string[] initrd to load for the kernel. - * If mangle() has been called this will be an array, - * otherwise it's a comma separated list. - */ - public $initrd; - /** - * @var string command line options to pass to the kernel - */ - public $append; - /** - * @var int IPAPPEND from PXEMENU. Bitmask of valid options 1 and 2. - */ - public $ipAppend; - /** - * @var string Password protecting the entry. This is most likely in crypted form. - */ - public $passwd; - /** - * @var bool whether this section is marked as default (booted after timeout) - */ - public $isDefault = false; - /** - * @var bool Menu entry is not visible (can only be triggered by timeout) - */ - public $isHidden = false; - /** - * @var bool Disable this entry, making it unselectable - */ - public $isDisabled = false; - /** - * @var int Value of the LOCALBOOT field - */ - public $localBoot; - /** - * @var string hotkey to trigger item. Only valid after calling mangle() - */ - public $hotkey; - - public function __construct($label) { $this->label = $label; } - - public function mangle() - { - if (($i = strpos($this->title, '^')) !== false) { - $this->hotkey = strtoupper($this->title{$i+1}); - $this->title = substr($this->title, 0, $i) . substr($this->title, $i + 1); - } - if (strpos($this->append, 'initrd=') !== false) { - $parts = preg_split('/\s+/', $this->append); - $this->append = ''; - for ($i = 0; $i < count($parts); ++$i) { - if (preg_match('/^initrd=(.*)$/', $parts[$i], $out)) { - if (!empty($this->initrd)) { - $this->initrd .= ','; - } - $this->initrd .= $out[1]; - } else { - $this->append .= ' ' . $parts[$i]; - } - } - $this->append = trim($this->append); - } - if (is_string($this->initrd)) { - $this->initrd = explode(',', $this->initrd); - } elseif (!is_array($this->initrd)) { - $this->initrd = []; - } - } - -} - diff --git a/modules-available/serversetup-bwlp/install.inc.php b/modules-available/serversetup-bwlp/install.inc.php deleted file mode 100644 index 25579c13..00000000 --- a/modules-available/serversetup-bwlp/install.inc.php +++ /dev/null @@ -1,89 +0,0 @@ -compileTask !== null) - return $this->compileTask; - $this->compileTask = Property::get('ipxe-task-id'); - if ($this->compileTask !== false) { - $this->compileTask = Taskmanager::status($this->compileTask); - if (!Taskmanager::isTask($this->compileTask) || Taskmanager::isFinished($this->compileTask)) { - $this->compileTask = false; - } - } - return $this->compileTask; - } - - protected function doPreprocess() - { - User::load(); - - if (!User::isLoggedIn()) { - Message::addError('main.no-permission'); - Util::redirect('?do=Main'); - } - - if (Request::any('action') === 'getimage') { - User::assertPermission("download"); - $this->handleGetImage(); - } - - $this->currentMenu = Property::getBootMenu(); - - $action = Request::post('action'); - - if ($action === false) { - $this->currentAddress = Property::getServerIp(); - $this->getLocalAddresses(); - } - - if ($action === 'compile') { - User::assertPermission("edit.address"); - if ($this->getCompileTask() === false) { - Trigger::ipxe(); - } - Util::redirect('?do=serversetup'); - } - - if ($action === 'ip') { - User::assertPermission("edit.address"); - // New address is to be set - $this->getLocalAddresses(); - $this->updateLocalAddress(); - } - - if ($action === 'savebootentry') { - User::assertPermission('ipxe.bootentry.edit'); - $this->saveBootEntry(); - } - - if ($action === 'deleteBootentry') { - User::assertPermission('ipxe.bootentry.edit'); - $this->deleteBootEntry(); - } - - if ($action === 'savemenu') { - User::assertPermission('ipxe.menu.edit'); - $this->saveMenu(); - } - - if ($action === 'savelocation') { - // Permcheck in function - $this->saveLocationMenu(); - Util::redirect('?do=locations'); - } - - if ($action === 'savelocalboot') { - User::assertPermission('ipxe.localboot.edit'); - $this->saveLocalboot(); - } - - if ($action === 'deleteMenu') { - // Permcheck in function - $this->deleteMenu(); - } - - if ($action === 'setDefaultMenu') { - User::assertPermission('ipxe.menu.edit', 0); - $this->setDefaultMenu(); - } - - if (Request::isPost()) { - Util::redirect('?do=serversetup'); - } - - User::assertPermission('access-page'); - - if (User::hasPermission('ipxe.*')) { - Dashboard::addSubmenu('?do=serversetup&show=menu', Dictionary::translate('submenu_menu', true)); - Dashboard::addSubmenu('?do=serversetup&show=bootentry', Dictionary::translate('submenu_bootentry', true)); - } - if (User::hasPermission('edit.address')) { - Dashboard::addSubmenu('?do=serversetup&show=address', Dictionary::translate('submenu_address', true)); - } - if (User::hasPermission('download')) { - Dashboard::addSubmenu('?do=serversetup&show=download', Dictionary::translate('submenu_download', true)); - } - if (User::hasPermission('ipxe.localboot.*')) { - Dashboard::addSubmenu('?do=serversetup&show=localboot', Dictionary::translate('submenu_localboot', true)); - } - if (Request::get('show') === false) { - $subs = Dashboard::getSubmenus(); - if (empty($subs)) { - User::assertPermission('download'); - } else { - Util::redirect($subs[0]['url']); - } - } - } - - protected function doRender() - { - Render::addTemplate("heading"); - - $task = $this->getCompileTask(); - if ($task !== false) { - $files = []; - if ($task['data'] && $task['data']['files']) { - foreach ($task['data']['files'] as $k => $v) { - $files[] = ['name' => $k, 'namehyphen' => str_replace(['/', '.'], '-', $k)]; - } - } - Render::addTemplate('ipxe_update', array('taskid' => $task['id'], 'files' => $files)); - } - - switch (Request::get('show')) { - case 'editbootentry': - User::assertPermission('ipxe.bootentry.edit'); - $this->showEditBootEntry(); - break; - case 'editmenu': - User::assertPermission('ipxe.menu.view'); - $this->showEditMenu(); - break; - case 'download': - User::assertPermission('download'); - $this->showDownload(); - break; - case 'menu': - User::assertPermission('ipxe.menu.view'); - $this->showMenuList(); - break; - case 'bootentry': - User::assertPermission('ipxe.bootentry.view'); - $this->showBootentryList(); - break; - case 'address': - User::assertPermission('edit.address'); - $this->showEditAddress(); - break; - case 'assignlocation': - // Permcheck in function - $this->showEditLocation(); - break; - case 'localboot': - User::assertPermission('ipxe.localboot.*'); - $this->showLocalbootConfig(); - break; - default: - Util::redirect('?do=serversetup'); - break; - } - } - - private function showDownload() - { - $list = glob('/srv/openslx/www/boot/download/*', GLOB_NOSORT); - usort($list, function ($a, $b) { - return strcmp(substr($a, -4), substr($b, -4)) * 100 + strcmp($a, $b); - }); - $files = []; - $strings = [ - 'efi' => [Dictionary::translate('dl-efi', true) => 50], - 'pcbios' => [Dictionary::translate('dl-pcbios', true) => 51], - 'usb' => [Dictionary::translate('dl-usb', true) => 80], - 'hd' => [Dictionary::translate('dl-hd', true) => 81], - 'lkrn' => [Dictionary::translate('dl-lkrn', true) => 82], - 'i386' => [Dictionary::translate('dl-i386', true) => 10], - 'x86_64' => [Dictionary::translate('dl-x86_64', true) => 11], - 'ecm' => [Dictionary::translate('dl-usbnic', true) => 60], - 'ncm' => [Dictionary::translate('dl-usbnic', true) => 61], - 'ipxe' => [Dictionary::translate('dl-pcinic', true) => 62], - 'snp' => [Dictionary::translate('dl-snp', true) => 63], - ]; - foreach ($list as $file) { - if ($file{0} === '.') - continue; - if (is_file($file)) { - $base = basename($file); - $features = []; - foreach (preg_split('/[\-\.\/]+/', $base, -1, PREG_SPLIT_NO_EMPTY) as $p) { - if (array_key_exists($p, $strings)) { - $features += $strings[$p]; - } - } - asort($features); - $files[] = [ - 'name' => $base, - 'size' => Util::readableFileSize(filesize($file)), - 'modified' => Util::prettyTime(filemtime($file)), - 'class' => substr($base, -4) === '.usb' ? 'slx-bold' : '', - 'features' => implode(', ', array_keys($features)), - ]; - } - } - Render::addTemplate('download', ['files' => $files]); - } - - private function makeSelectArray($list, $default) - { - $ret = []; - foreach (array_keys($list) as $k) { - $ret[] = [ - 'key' => $k, - 'selected' => ($k === $default ? 'selected' : ''), - ]; - } - return $ret; - } - - private function showLocalbootConfig() - { - // Default setting - $default = Property::get(Localboot::PROPERTY_KEY, 'AUTO'); - if (!array_key_exists($default, Localboot::BOOT_METHODS)) { - $default = 'AUTO'; - } - $optionList = $this->makeSelectArray(Localboot::BOOT_METHODS, $default); - // Exceptions - $cutoff = strtotime('-90 days'); - $models = []; - $res = Database::simpleQuery('SELECT m.systemmodel, cnt, sl.bootmethod FROM ( - SELECT m2.systemmodel, Count(*) AS cnt FROM machine m2 - WHERE m2.lastseen > :cutoff - GROUP BY systemmodel - ) m - LEFT JOIN serversetup_localboot sl USING (systemmodel) - ORDER BY systemmodel', ['cutoff' => $cutoff]); - while ($row = $res->fetch(PDO::FETCH_ASSOC)) { - $row['options'] = $this->makeSelectArray(Localboot::BOOT_METHODS, $row['bootmethod']); - $models[] = $row; - } - // Output - $data = [ - 'default' => $default, - 'options' => $optionList, - 'exceptions' => $models, - ]; - Render::addTemplate('localboot', $data); - } - - private function showBootentryList() - { - $allowEdit = User::hasPermission('ipxe.bootentry.edit'); - - $res = Database::simpleQuery("SELECT be.entryid, be.hotkey, be.title, be.builtin, Count(*) AS refs FROM serversetup_bootentry be - INNER JOIN serversetup_menuentry sm USING (entryid) - GROUP BY be.entryid - ORDER BY be.title ASC"); - $bootentryTable = []; - while ($row = $res->fetch(PDO::FETCH_ASSOC)) { - $bootentryTable[] = $row; - } - - Render::addTemplate('bootentry-list', array( - 'bootentryTable' => $bootentryTable, - 'allowEdit' => $allowEdit, - )); - } - - private function showMenuList() - { - $allowedEdit = User::getAllowedLocations('ipxe.menu.edit'); - - // TODO Permission::addGlobalTags($perms, null, ['edit.menu', 'edit.address', 'download']); - - $res = Database::simpleQuery("SELECT m.menuid, m.title, m.isdefault, GROUP_CONCAT(l.locationid) AS locations, - GROUP_CONCAT(ll.locationname SEPARATOR ', ') AS locnames - FROM serversetup_menu m - LEFT JOIN serversetup_menu_location l USING (menuid) - LEFT JOIN location ll USING (locationid) - GROUP BY menuid - ORDER BY title"); - $menuTable = []; - while ($row = $res->fetch(PDO::FETCH_ASSOC)) { - if (empty($row['locations'])) { - $locations = []; - $row['allowEdit'] = in_array(0, $allowedEdit); - } else { - $locations = explode(',', $row['locations']); - $row['allowEdit'] = empty(array_diff($locations, $allowedEdit)); - } - $row['locationCount'] = empty($locations) ? '' : count($locations); - $menuTable[] = $row; - } - - Render::addTemplate('menu-list', array( - 'menuTable' => $menuTable, - 'showSetDefault' => User::hasPermission('ipxe.menu.edit', 0) - )); - } - - private function hasMenuPermission($menuid, $permission) - { - $allowedEditLocations = User::getAllowedLocations($permission); - $allowEdit = in_array(0, $allowedEditLocations); - if (!$allowEdit) { - // Get locations - $locations = Database::queryColumnArray('SELECT locationid FROM serversetup_menu_location - WHERE menuid = :menuid', compact('menuid')); - if (!empty($locations)) { - $allowEdit = count(array_diff($locations, $allowedEditLocations)) === 0; - } - } - return $allowEdit; - } - - private function showEditMenu() - { - $id = Request::get('id', false, 'int'); - // if = edit, else = add new - if ($id !== 0) { - $menu = Database::queryFirst("SELECT menuid, timeoutms, title, defaultentryid, isdefault - FROM serversetup_menu WHERE menuid = :id", compact('id')); - } else { - $menu = []; - $menu['menuid'] = 0; - $menu['timeoutms'] = 0; - $menu['title'] = ''; - $menu['defaultentryid'] = null; - $menu['isdefault'] = false; - } - - if ($menu === false) { - Message::addError('invalid-menu-id', $id); - Util::redirect('?do=serversetup&show=menu'); - } - $highlight = Request::get('highlight', false, 'string'); - if ($id !== 0 && !$this->hasMenuPermission($id, 'ipxe.menu.edit')) { - $menu['readonly'] = 'readonly'; - $menu['disabled'] = 'disabled'; - $menu['plainpass'] = ''; - } - if (!User::hasPermission('ipxe.menu.edit', 0)) { - $menu['globalMenuWarning'] = true; - } - - $menu['timeout'] = round($menu['timeoutms'] / 1000); - $menu['entries'] = []; - $res = Database::simpleQuery("SELECT menuentryid, entryid, hotkey, title, hidden, sortval, plainpass FROM - serversetup_menuentry WHERE menuid = :id ORDER BY sortval ASC", compact('id')); - while ($row = $res->fetch(PDO::FETCH_ASSOC)) { - if ($row['entryid'] == $highlight) { - $row['highlight'] = 'active'; - } - $menu['entries'][] = $row; - } - $menu['keys'] = array_map(function ($item) { return ['key' => $item]; }, MenuEntry::getKeyList()); - $menu['entrylist'] = Database::queryAll("SELECT entryid, title, hotkey, data FROM serversetup_bootentry ORDER BY title ASC"); - foreach ($menu['entrylist'] as &$bootentry) { - //$bootentry['json'] = $bootentry['data']; - $bootentry['data'] = json_decode($bootentry['data'], true); - if (array_key_exists('arch', $bootentry['data'])) { - $bootentry['data']['PCBIOS'] = array('executable' => $bootentry['data']['executable']['PCBIOS'], - 'initRd' => $bootentry['data']['initRd']['PCBIOS'], - 'commandLine' => $bootentry['data']['commandLine']['PCBIOS']); - $bootentry['data']['EFI'] = array('executable' => $bootentry['data']['executable']['EFI'], - 'initRd' => $bootentry['data']['initRd']['EFI'], - 'commandLine' => $bootentry['data']['commandLine']['EFI']); - - if ($bootentry['data']['arch'] === 'PCBIOS') { - $bootentry['data']['arch'] = Dictionary::translateFile('template-tags','lang_biosOnly', true); - unset($bootentry['data']['EFI']); - } else if ($bootentry['data']['arch'] === 'EFI') { - $bootentry['data']['arch'] = Dictionary::translateFile('template-tags','lang_efiOnly', true); - unset($bootentry['data']['PCBIOS']); - } else { - $bootentry['data']['arch'] = Dictionary::translateFile('template-tags','lang_archBoth', true); - } - - } elseif (!array_key_exists('script', $bootentry['data'])) { - $bootentry['data']['arch'] = Dictionary::translateFile('template-tags','lang_archAgnostic', true); - $bootentry['data']['archAgnostic'] = array('executable' => $bootentry['data']['executable'], - 'initRd' => $bootentry['data']['initRd'], - 'commandLine' => $bootentry['data']['commandLine']); - } - } - foreach ($menu['entries'] as &$entry) { - $entry['isdefault'] = ($entry['menuentryid'] == $menu['defaultentryid']); - // TODO: plainpass only when permissions - } - - Permission::addGlobalTags($menu['perms'], 0, ['ipxe.menu.edit']); - Render::addTemplate('menu-edit', $menu); - } - - private function showEditBootEntry() - { - $params = []; - $id = Request::get('id', false, 'string'); - if ($id === false) { - $params['exec_checked'] = 'checked'; - $params['entryid'] = 'u-' . dechex(mt_rand(0x1000, 0xffff)) . '-' . dechex(time()); - $params['entries'] = [ - ['mode' => 'PCBIOS'], - ['mode' => 'EFI'], - ]; - } else { - // Query existing entry - $row = Database::queryFirst('SELECT entryid, title, builtin, data FROM serversetup_bootentry - WHERE entryid = :id LIMIT 1', ['id' => $id]); - if ($row === false) { - Message::addError('invalid-boot-entry', $id); - Util::redirect('?do=serversetup'); - } - $entry = BootEntry::fromJson($row['data']); - if ($entry === null) { - Message::addError('unknown-bootentry-type', $id); - Util::redirect('?do=serversetup'); - } - $entry->addFormFields($params); - $params['title'] = $row['title']; - $params['entryid'] = $row['entryid']; - $params['builtin'] = $row['builtin']; - $params['menus'] = Database::queryAll('SELECT m.menuid, m.title FROM serversetup_menu m - INNER JOIN serversetup_menuentry me ON (me.menuid = m.menuid) - WHERE me.entryid = :entryid', ['entryid' => $row['entryid']]); - } - - Render::addTemplate('ipxe-new-boot-entry', $params); - } - - private function showEditAddress() - { - Render::addTemplate('ipaddress', array( - 'ips' => $this->addrListTask['data']['addresses'], - 'chooseHintClass' => $this->hasIpSet ? '' : 'alert alert-danger', - 'disabled' => ($this->getCompileTask() === false) ? '' : 'disabled', - )); - } - - // ----------------------------------------------------------------------------------------------- - - private function getLocalAddresses() - { - $this->addrListTask = Taskmanager::submit('LocalAddressesList', array()); - - if ($this->addrListTask === false) { - $this->addrListTask['data']['addresses'] = false; - return false; - } - - if (!Taskmanager::isFinished($this->addrListTask)) { // TODO: Async if just displaying - $this->addrListTask = Taskmanager::waitComplete($this->addrListTask['id'], 4000); - } - - if (Taskmanager::isFailed($this->addrListTask) || !isset($this->addrListTask['data']['addresses'])) { - $this->addrListTask['data']['addresses'] = false; - return false; - } - - $sortIp = array(); - foreach (array_keys($this->addrListTask['data']['addresses']) as $key) { - $item = & $this->addrListTask['data']['addresses'][$key]; - if (!isset($item['ip']) || !preg_match('/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/', $item['ip']) || substr($item['ip'], 0, 4) === '127.') { - unset($this->addrListTask['data']['addresses'][$key]); - continue; - } - if ($this->currentAddress === $item['ip']) { - $item['default'] = true; - $this->hasIpSet = true; - } - $sortIp[] = $item['ip']; - } - unset($item); - array_multisort($sortIp, SORT_STRING, $this->addrListTask['data']['addresses']); - return true; - } - - private function deleteBootEntry() { - $id = Request::post('deleteid', false, 'string'); - if ($id === false) { - Message::addError('main.parameter-missing', 'deleteid'); - return; - } - Database::exec("DELETE FROM serversetup_bootentry WHERE entryid = :entryid", array("entryid" => $id)); - // TODO: Redirect to &show=bootentry - Message::addSuccess('bootentry-deleted'); - } - - private function setDefaultMenu() - { - $id = Request::post('menuid', false, 'int'); - if ($id === false) { - Message::addError('main.parameter-missing', 'menuid'); - return; - } - Database::exec('UPDATE serversetup_menu SET isdefault = (menuid = :menuid)', ['menuid' => $id]); - Message::addSuccess('menu-set-default'); - } - - private function deleteMenu() - { - $id = Request::post('deleteid', false, 'int'); - if ($id === false) { - Message::addError('main.parameter-missing', 'deleteid'); - return; - } - if (!$this->hasMenuPermission($id, 'ipxe.menu.edit')) { - Message::addError('locations.no-permission-location', $id); - return; - } - Database::exec("DELETE FROM serversetup_menu WHERE menuid = :menuid", array("menuid" => $id)); - Message::addSuccess('menu-deleted'); - } - - private function saveMenu() - { - $id = Request::post('menuid', false, 'int'); - if ($id === false) { - Message::addError('main.parameter-missing', 'menuid'); - return; - } - - $insertParams = [ - 'title' => IPxe::sanitizeIpxeString(Request::post('title', '', 'string')), - 'timeoutms' => abs(Request::post('timeout', 0, 'int') * 1000), - ]; - if ($id === 0) { - Database::exec("INSERT INTO serversetup_menu (title, timeoutms, isdefault) VALUES (:title, :timeoutms, 0)", $insertParams); - $menu['menuid'] = $id = Database::lastInsertId(); - } else { - $menu = Database::queryFirst("SELECT m.menuid - FROM serversetup_menu m - WHERE menuid = :id", compact('id')); - if ($menu === false) { - Message::addError('no-such-menu', $id); - return; - } - $insertParams['menuid'] = $id; - Database::exec('UPDATE serversetup_menu SET title = :title, timeoutms = :timeoutms - WHERE menuid = :menuid', $insertParams); - } - - $keepIds = []; - $entries = Request::post('entry', false, 'array'); - $wantedDefaultEntryId = Request::post('defaultentry', null, 'string'); - $defaultEntryId = null; - - if ($entries) { - foreach ($entries as $key => $entry) { - if (!isset($entry['sortval'])) { - error_log(print_r($entry, true)); - continue; - } - // Fallback defaults - $entry += [ - 'entryid' => null, - 'title' => '', - 'hidden' => 0, - 'plainpass' => '', - ]; - $params = [ - 'title' => IPxe::sanitizeIpxeString($entry['title']), - 'sortval' => (int)$entry['sortval'], - 'menuid' => $menu['menuid'], - ]; - if (empty($entry['entryid'])) { - // Spacer - $params += [ - 'entryid' => null, - 'hotkey' => '', - 'hidden' => 0, // Doesn't make any sense - 'plainpass' => '', // Doesn't make any sense - ]; - } else { - $params += [ - 'entryid' => $entry['entryid'], // TODO validate? - 'hotkey' => MenuEntry::filterKeyName($entry['hotkey']), - 'hidden' => (int)$entry['hidden'], // TODO (needs hotkey to make sense) - 'plainpass' => $entry['plainpass'], - ]; - } - if (is_numeric($key)) { - if ((string)$key === $wantedDefaultEntryId) { // Check now that we have generated our key - $defaultEntryId = $key; - } - $keepIds[] = $key; - $params['menuentryid'] = $key; - $params['md5pass'] = IPxe::makeMd5Pass($entry['plainpass'], $key); - $ret = Database::exec('UPDATE serversetup_menuentry - SET entryid = :entryid, hotkey = :hotkey, title = :title, hidden = :hidden, sortval = :sortval, - plainpass = :plainpass, md5pass = :md5pass - WHERE menuid = :menuid AND menuentryid = :menuentryid', $params, true); - } else { - $ret = Database::exec("INSERT INTO serversetup_menuentry - (menuid, entryid, hotkey, title, hidden, sortval, plainpass, md5pass) - VALUES (:menuid, :entryid, :hotkey, :title, :hidden, :sortval, :plainpass, '')", $params, true); - if ($ret) { - $newKey = Database::lastInsertId(); - if ((string)$key === $wantedDefaultEntryId) { // Check now that we have generated our key - $defaultEntryId = $newKey; - } - $keepIds[] = (int)$newKey; - if (!empty($entry['plainpass'])) { - Database::exec('UPDATE serversetup_menuentry SET md5pass = :md5pass WHERE menuentryid = :id', [ - 'md5pass' => IPxe::makeMd5Pass($entry['plainpass'], $newKey), - 'id' => $newKey, - ]); - } - } - } - - if ($ret === false) { - Message::addWarning('error-saving-entry', $entry['title'], Database::lastError()); - } - } - Database::exec('DELETE FROM serversetup_menuentry WHERE menuid = :menuid AND menuentryid NOT IN (:keep)', - ['menuid' => $menu['menuid'], 'keep' => $keepIds]); - // Set default entry - Database::exec('UPDATE serversetup_menu SET defaultentryid = :default WHERE menuid = :menuid', - ['menuid' => $menu['menuid'], 'default' => $defaultEntryId]); - } else { - Database::exec('DELETE FROM serversetup_menuentry WHERE menuid = :menuid', ['menuid' => $menu['menuid']]); - Database::exec('UPDATE serversetup_menu SET defaultentryid = NULL WHERE menuid = :menuid', ['menuid' => $menu['menuid']]); - } - - Message::addSuccess('menu-saved'); - } - - private function updateLocalAddress() - { - $newAddress = Request::post('ip', 'none', 'string'); - $valid = false; - foreach ($this->addrListTask['data']['addresses'] as $item) { - if ($item['ip'] !== $newAddress) - continue; - $valid = true; - break; - } - if ($valid) { - Property::setServerIp($newAddress); - Util::redirect('?do=ServerSetup'); - } else { - Message::addError('invalid-ip', $newAddress); - } - Util::redirect(); - } - - private function handleGetImage() - { - $file = "/opt/openslx/ipxe/openslx-bootstick.raw"; - if (!is_readable($file)) { - Message::addError('image-not-found'); - return; - } - Header('Content-Type: application/octet-stream'); - Header('Content-Disposition: attachment; filename="openslx-bootstick-' . Property::getServerIp() . '-raw.img"'); - readfile($file); - exit; - } - - private function saveBootEntry() - { - $oldEntryId = Request::post('entryid', false, 'string'); - $newId = Request::post('newid', false, 'string'); - if (!preg_match('/^[a-z0-9\-_]{1,16}$/', $newId)) { - Message::addError('main.parameter-empty', 'newid'); - return; - } - $data = Request::post('entry', false); - if (!is_array($data)) { - Message::addError('missing-bootentry-data'); - return; - } - $type = Request::post('type', false, 'string'); - if ($type === 'exec') { - $entry = BootEntry::newStandardBootEntry($data); - } elseif ($type === 'script') { - $entry = BootEntry::newCustomBootEntry($data); - } else { - Message::addError('unknown-bootentry-type', $type); - return; - } - if ($entry === null) { - Message::addError('main.empty-field'); - Util::redirect('?do=serversetup&show=bootentry'); - } - $params = [ - 'entryid' => $newId, - 'title' => Request::post('title', '', 'string'), - 'data' => json_encode($entry->toArray()), - ]; - // New or update? - if (empty($oldEntryId)) { - // New entry - Database::exec('INSERT INTO serversetup_bootentry (entryid, title, builtin, data) - VALUES (:entryid, :title, 0, :data)', $params); - Message::addSuccess('boot-entry-created', $newId); - } else { - // Edit existing entry - $params['oldid'] = $oldEntryId; - Database::exec('UPDATE serversetup_bootentry SET entryid = :entryid, title = :title, data = :data - WHERE entryid = :oldid', $params); - Message::addSuccess('boot-entry-updated', $newId); - } - Util::redirect('?do=serversetup&show=bootentry'); - } - - private function showEditLocation() - { - $locationId = Request::get('locationid', false, 'int'); - $loc = Location::get($locationId); - if ($loc === false) { - Message::addError('locations.invalid-location-id', $locationId); - return; - } - User::assertPermission('ipxe.menu.assign', $locationId); - // List of menu entries - $res = Database::simpleQuery('SELECT menuentryid, title FROM serversetup_menuentry'); - $menuEntries = $res->fetchAll(PDO::FETCH_KEY_PAIR); - // List of menus - $data = [ - 'locationid' => $locationId, - 'locationName' => $loc['locationname'], - ]; - $res = Database::simpleQuery('SELECT m.menuid, m.title, ml.locationid, ml.defaultentryid, GROUP_CONCAT(me.menuentryid) AS entries FROM serversetup_menu m - LEFT JOIN serversetup_menu_location ml ON (m.menuid = ml.menuid AND ml.locationid = :locationid) - INNER JOIN serversetup_menuentry me ON (m.menuid = me.menuid AND me.entryid IS NOT NULL) - GROUP BY menuid - ORDER BY m.title ASC', ['locationid' => $locationId]); - $menus = []; - $hasDefault = false; - while ($row = $res->fetch(PDO::FETCH_ASSOC)) { - $eids = explode(',', $row['entries']); - $row['entries'] = []; - foreach ($eids as $eid) { - $row['entries'][] = [ - 'id' => $eid, - 'title' => $menuEntries[$eid], - 'selected' => ($eid == $row['defaultentryid'] ? 'selected' : ''), - ]; - } - if ($row['locationid'] !== null) { - $hasDefault = true; - $row['menu_selected'] = 'checked'; - } - $menus[] = $row; - } - if (!$hasDefault) { - $data['default_selected'] = 'checked'; - } - $data['list'] = $menus; - Render::addTemplate('menu-assign-location', $data); - } - - private function saveLocationMenu() - { - $locationId = Request::post('locationid', false, 'int'); - $loc = Location::get($locationId); - if ($loc === false) { - Message::addError('locations.invalid-location-id', $locationId); - return; - } - User::assertPermission('ipxe.menu.assign', $locationId); - $menuId = Request::post('menuid', false, 'int'); - if ($menuId === 0) { - Database::exec('DELETE FROM serversetup_menu_location WHERE locationid = :locationid', - ['locationid' => $locationId]); - Message::addSuccess('location-use-default', $loc['locationname']); - return; - } - $defaultEntryId = Request::post('defaultentryid-' . $menuId, 0, 'int'); - if ($defaultEntryId === 0) { - $defaultEntryId = null; - } - Database::exec('INSERT INTO serversetup_menu_location (menuid, locationid, defaultentryid) - VALUES (:menuid, :locationid, :defaultentryid) - ON DUPLICATE KEY UPDATE menuid = :menuid, defaultentryid = :defaultentryid', [ - 'menuid' => $menuId, - 'locationid' => $locationId, - 'defaultentryid' => $defaultEntryId - ]); - Message::addSuccess('location-menu-assigned', $loc['locationname']); - } - - private function saveLocalboot() - { - $default = Request::post('default', 'AUTO', 'string'); - if (!array_key_exists($default, Localboot::BOOT_METHODS)) { - Message::addError('localboot-invalid-method', $default); - return; - } - Property::set(Localboot::PROPERTY_KEY, $default); - $overrides = Request::post('override', [], 'array'); - Database::exec('TRUNCATE TABLE serversetup_localboot'); - foreach ($overrides as $model => $mode) { - if (empty($mode)) // No override - continue; - if (!array_key_exists($mode, Localboot::BOOT_METHODS)) { - Message::addWarning('localboot-invalid-method', $mode); - continue; - } - Database::exec('INSERT INTO serversetup_localboot (systemmodel, bootmethod) - VALUES (:model, :mode)', compact('model', 'mode')); - } - Message::addSuccess('localboot-saved'); - Util::redirect('?do=serversetup&show=localboot'); - } - -} diff --git a/modules-available/serversetup-bwlp/permissions/permissions.json b/modules-available/serversetup-bwlp/permissions/permissions.json deleted file mode 100644 index 33cc9cea..00000000 --- a/modules-available/serversetup-bwlp/permissions/permissions.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "access-page": { - "location-aware": false - }, - "download": { - "location-aware": false - }, - "edit.address": { - "location-aware": false - }, - "ipxe.bootentry.view": { - "location-aware": false - }, - "ipxe.bootentry.edit": { - "location-aware": false - }, - "ipxe.menu.view": { - "location-aware": false - }, - "ipxe.menu.edit": { - "location-aware": false - }, - "ipxe.menu.assign": { - "location-aware": true - }, - "ipxe.localboot.edit": { - "location-aware": false - } -} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp/templates/bootentry-list.html b/modules-available/serversetup-bwlp/templates/bootentry-list.html deleted file mode 100644 index 0cf005c5..00000000 --- a/modules-available/serversetup-bwlp/templates/bootentry-list.html +++ /dev/null @@ -1,83 +0,0 @@ -

{{lang_bootentryHead}}

- -

- {{lang_bootentryIntro}} -

- - - - - - - - - - - - - {{#bootentryTable}} - - - - - - - - {{/bootentryTable}} - -
{{lang_bootentryTitle}}{{lang_hotkey}}{{lang_refCount}}{{lang_edit}}{{lang_delete}}
- {{title}} - - {{hotkey}} - - {{refs}} - - {{#allowEdit}} - - - - {{/allowEdit}} - - {{#allowEdit}} - - {{/allowEdit}} -
-
- {{#allowEdit}} - - - {{lang_addBootentry}} - - {{/allowEdit}} -
- - -
- - -
- - \ No newline at end of file diff --git a/modules-available/serversetup-bwlp/templates/download.html b/modules-available/serversetup-bwlp/templates/download.html deleted file mode 100644 index 62064b66..00000000 --- a/modules-available/serversetup-bwlp/templates/download.html +++ /dev/null @@ -1,53 +0,0 @@ -
-
- {{lang_downloadBootImage}} -
-
- - {{#files}} - - - - - - - {{/files}} -
{{name}}{{size}}{{modified}}({{features}})
-

- - - {{lang_usbImgHelpBtn}} - -

-

- {{lang_additionalInfoLink}} {{lang_ipxeWikiUrl}} -

-
-
- - \ No newline at end of file diff --git a/modules-available/serversetup-bwlp/templates/heading.html b/modules-available/serversetup-bwlp/templates/heading.html deleted file mode 100644 index e2aa0bff..00000000 --- a/modules-available/serversetup-bwlp/templates/heading.html +++ /dev/null @@ -1,3 +0,0 @@ - \ No newline at end of file diff --git a/modules-available/serversetup-bwlp/templates/ipaddress.html b/modules-available/serversetup-bwlp/templates/ipaddress.html deleted file mode 100644 index ea19c417..00000000 --- a/modules-available/serversetup-bwlp/templates/ipaddress.html +++ /dev/null @@ -1,44 +0,0 @@ -
-
- {{lang_bootAddress}} -
-
-
- {{lang_chooseIP}} -
-
- - - - {{#ips}} - - - {{#default}} - - {{/default}} - {{^default}} - - {{/default}} - - {{/ips}} -
{{ip}} - {{lang_active}} - - -
-

- {{lang_recompileHint}} -

-
-
- - -
-
-
\ No newline at end of file diff --git a/modules-available/serversetup-bwlp/templates/ipxe-new-boot-entry.html b/modules-available/serversetup-bwlp/templates/ipxe-new-boot-entry.html deleted file mode 100644 index 7e82b5cc..00000000 --- a/modules-available/serversetup-bwlp/templates/ipxe-new-boot-entry.html +++ /dev/null @@ -1,165 +0,0 @@ -

{{lang_newBootEntryHead}}

- -{{#builtin}} -
- {{lang_editBuiltinWarn}} -
-{{/builtin}} - -
-
- {{lang_bootEntryData}} -
-
-
- - - - -
-
- - -
-
- - -
-
- -
- - -
-
- - -
-
- - -
- -
-
- {{#entries}} -
-
-
-

{{mode}}

-
- - -
-
- - -
-
- - -
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
-
-
- {{/entries}} -
-
- -
-
- - -
-
- - {{#builtin}} -
- {{lang_editBuiltinWarn}} -
- {{/builtin}} - -

{{lang_referencingMenus}}:

- - -
- -
-
-
-
- - \ No newline at end of file diff --git a/modules-available/serversetup-bwlp/templates/ipxe_update.html b/modules-available/serversetup-bwlp/templates/ipxe_update.html deleted file mode 100644 index 344d3905..00000000 --- a/modules-available/serversetup-bwlp/templates/ipxe_update.html +++ /dev/null @@ -1,54 +0,0 @@ -
-
{{lang_menuGeneration}}
-
-
- {{#files}} -
- - {{name}} -
- {{/files}} -
-
-
- {{lang_generationFailed}} -
-
-
{{lang_menuGeneration}}
-
-
- - diff --git a/modules-available/serversetup-bwlp/templates/localboot.html b/modules-available/serversetup-bwlp/templates/localboot.html deleted file mode 100644 index 3037de2a..00000000 --- a/modules-available/serversetup-bwlp/templates/localboot.html +++ /dev/null @@ -1,59 +0,0 @@ -

{{lang_localBootHead}}

- -

{{lang_localBootIntro}}

- -
- - - - -
-
- - -
- -
-

- {{lang_localBootExceptions}} -

- - - - - - - {{#exceptions}} - - - - - - {{/exceptions}} -
{{lang_systemmodel}}{{lang_count}}{{lang_override}}
{{systemmodel}}{{cnt}} - -
- -
- - -
- -
diff --git a/modules-available/serversetup-bwlp/templates/menu-assign-location.html b/modules-available/serversetup-bwlp/templates/menu-assign-location.html deleted file mode 100644 index 077d137e..00000000 --- a/modules-available/serversetup-bwlp/templates/menu-assign-location.html +++ /dev/null @@ -1,69 +0,0 @@ -

{{lang_assignMenuToLocation}}

-

{{locationName}}

- -
- - - - - - - - - - - - - - - - - - - {{#list}} - - - - - - {{/list}} - -
{{lang_menuTitle}}{{lang_menuEntryOverride}}
-
- - -
-
- {{lang_useDefaultMenu}} -
-
- - -
-
- {{title}} - - -
- -
- -
- -
- -
- - \ No newline at end of file diff --git a/modules-available/serversetup-bwlp/templates/menu-edit.html b/modules-available/serversetup-bwlp/templates/menu-edit.html deleted file mode 100644 index 1598a2b7..00000000 --- a/modules-available/serversetup-bwlp/templates/menu-edit.html +++ /dev/null @@ -1,368 +0,0 @@ -

{{lang_editMenuHead}}

- - - - -
-
- {{title}} - {{^title}} - {{lang_newMenu}} - {{/title}} -
-
-
- - - - -
-
- -
-
- -
-
-
-
- -
-
-
- - {{lang_seconds}} -
-
-
-
- - - - - - - - - - - - - - - {{#entries}} - - - - - - - - - - - - - - - - - {{/entries}} - -
{{lang_entryId}}{{lang_title}}{{lang_hotkey}}{{lang_password}}
- - -
- - -
-
- - - - - - - - - -
- - -
-
- -
-
-
- -
-
- {{lang_cancel}} - -
-
- - -
-
- - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/modules-available/serversetup-bwlp/templates/menu-list.html b/modules-available/serversetup-bwlp/templates/menu-list.html deleted file mode 100644 index 545f22a9..00000000 --- a/modules-available/serversetup-bwlp/templates/menu-list.html +++ /dev/null @@ -1,100 +0,0 @@ -

{{lang_listOfMenus}}

- -

- {{lang_menuListIntro}} -

- - - - - - - - - - - - - {{#menuTable}} - - - - - - - - {{/menuTable}} - -
{{lang_menuTitle}}{{lang_locationCount}}{{lang_isDefault}}{{lang_edit}}{{lang_delete}}
- {{title}} - - {{locationCount}} - - {{^isdefault}} - {{#showSetDefault}} -
- - - -
- {{/showSetDefault}} - {{/isdefault}} - {{#isdefault}} - - {{/isdefault}} -
- {{#allowEdit}} - - - - {{/allowEdit}} - - {{#allowDelete}} - - {{/allowDelete}} -
- - -
- - - -
- - -
- - \ No newline at end of file -- cgit v1.2.3-55-g7522