From 2faaf8383c9c8a1a557518caa9f2284158df523b Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Fri, 15 May 2020 17:24:05 +0200 Subject: [remoteaccess] New module --- modules-available/remoteaccess/api.inc.php | 77 ++++++++++++++++ .../remoteaccess/baseconfig/getconfig.inc.php | 18 ++++ modules-available/remoteaccess/config.json | 7 ++ .../remoteaccess/hooks/client-update.inc.php | 9 ++ modules-available/remoteaccess/hooks/cron.inc.php | 6 ++ .../remoteaccess/inc/remoteaccess.inc.php | 78 ++++++++++++++++ modules-available/remoteaccess/install.inc.php | 60 +++++++++++++ .../remoteaccess/lang/de/messages.json | 6 ++ modules-available/remoteaccess/lang/de/module.json | 4 + .../remoteaccess/lang/de/template-tags.json | 16 ++++ modules-available/remoteaccess/page.inc.php | 100 +++++++++++++++++++++ .../remoteaccess/templates/edit-group.html | 43 +++++++++ .../remoteaccess/templates/edit-settings.html | 72 +++++++++++++++ 13 files changed, 496 insertions(+) create mode 100644 modules-available/remoteaccess/api.inc.php create mode 100644 modules-available/remoteaccess/baseconfig/getconfig.inc.php create mode 100644 modules-available/remoteaccess/config.json create mode 100644 modules-available/remoteaccess/hooks/client-update.inc.php create mode 100644 modules-available/remoteaccess/hooks/cron.inc.php create mode 100644 modules-available/remoteaccess/inc/remoteaccess.inc.php create mode 100644 modules-available/remoteaccess/install.inc.php create mode 100644 modules-available/remoteaccess/lang/de/messages.json create mode 100644 modules-available/remoteaccess/lang/de/module.json create mode 100644 modules-available/remoteaccess/lang/de/template-tags.json create mode 100644 modules-available/remoteaccess/page.inc.php create mode 100644 modules-available/remoteaccess/templates/edit-group.html create mode 100644 modules-available/remoteaccess/templates/edit-settings.html diff --git a/modules-available/remoteaccess/api.inc.php b/modules-available/remoteaccess/api.inc.php new file mode 100644 index 00000000..2e1e4bf9 --- /dev/null +++ b/modules-available/remoteaccess/api.inc.php @@ -0,0 +1,77 @@ + $ip]); + if ($c !== false) { + Database::exec("INSERT INTO remoteaccess_machine (machineuuid, password) + VALUES (:uuid, :passwd) + ON DUPLICATE KEY UPDATE password = VALUES(password)", ['uuid' => $c['machineuuid'], 'passwd' => $password]); + } + exit; +} + +$range = IpUtil::parseCidr(Property::get(RemoteAccess::PROP_ALLOWED_VNC_NET)); +if ($range === false) { + die('No allowed IP defined'); +} +$iplong = ip2long($ip); +if (PHP_INT_SIZE === 4) { + $iplong = sprintf('%u', $iplong); +} +if ($iplong < $range['start'] || $iplong > $range['end']) { + die('Access denied'); +} + +Header('Content-Type: application/json'); + +$remoteLocations = RemoteAccess::getEnabledLocations(); + +if (empty($remoteLocations)) { + $rows = []; +} else { +// TODO fail-counter for WOL, so we can ignore machines that apparently can't be woken up +// -> Reset counter in our ~poweron hook, but only if the time roughly matches a WOL attempt (within ~5 minutes) + $rows = Database::queryAll("SELECT m.clientip, m.locationid, m.state, ram.password, ram.woltime FROM machine m + LEFT JOIN remoteaccess_machine ram ON (ram.machineuuid = m.machineuuid AND (ram.password IS NOT NULL OR m.state <> 'IDLE')) + LEFT JOIN runmode r ON (r.machineuuid = m.machineuuid) + WHERE m.locationid IN (:locs) + AND r.machineuuid IS NULL", + ['locs' => $remoteLocations]); + + $wolCut = time() - 90; + foreach ($rows as &$row) { + if (($row['state'] === 'OFFLINE' || $row['state'] === 'STANDBY') && $row['woltime'] > $wolCut) { + $row['wol_in_progress'] = true; + } + settype($row['locationid'], 'int'); + unset($row['woltime']); + } +} + +$groups = Database::queryAll("SELECT g.groupid AS id, g.groupname AS name, + GROUP_CONCAT(l.locationid) AS locationids, g.passwd AS password + FROM remoteaccess_group g INNER JOIN remoteaccess_x_location l USING (groupid) + WHERE g.active = 1 + GROUP BY g.groupid"); +foreach ($groups as &$group) { + $group['locationids'] = explode(',', $group['locationids']); + if (empty($group['password'])) { + unset($group['password']); + } + settype($group['id'], 'int'); + foreach ($group['locationids'] as &$lid) { + settype($lid, 'int'); + } +} + +$fakeid = 100000; +echo json_encode(['clients' => $rows, 'locations' => $groups]); + +// WTF, this makes the server return a 500 -.- +//fastcgi_finish_request(); + +RemoteAccess::ensureMachinesRunning(); diff --git a/modules-available/remoteaccess/baseconfig/getconfig.inc.php b/modules-available/remoteaccess/baseconfig/getconfig.inc.php new file mode 100644 index 00000000..2ebc97cb --- /dev/null +++ b/modules-available/remoteaccess/baseconfig/getconfig.inc.php @@ -0,0 +1,18 @@ + $locationId], true); // TODO Remove true after next point release (2020-05-12) + if ($ret !== false) { + // TODO Properly merge + if (Property::get(RemoteAccess::PROP_TRY_VIRT_HANDOVER)) { + ConfigHolder::add("SLX_REMOTE_VNC", 'vmware virtualbox'); + } else { + ConfigHolder::add("SLX_REMOTE_VNC", 'x11vnc'); + } + ConfigHolder::add("SLX_REMOTE_HOST_ACCESS", Property::get(RemoteAccess::PROP_ALLOWED_VNC_NET)); + } +} diff --git a/modules-available/remoteaccess/config.json b/modules-available/remoteaccess/config.json new file mode 100644 index 00000000..1530df87 --- /dev/null +++ b/modules-available/remoteaccess/config.json @@ -0,0 +1,7 @@ +{ + "category": "main.settings-client", + "dependencies" : [ + "locations", + "rebootcontrol" + ] +} \ No newline at end of file diff --git a/modules-available/remoteaccess/hooks/client-update.inc.php b/modules-available/remoteaccess/hooks/client-update.inc.php new file mode 100644 index 00000000..ecf5d91c --- /dev/null +++ b/modules-available/remoteaccess/hooks/client-update.inc.php @@ -0,0 +1,9 @@ + $uuid]); +} elseif ($type === '~poweroff') { + Database::exec("UPDATE remoteaccess_machine SET woltime = 0 WHERE machineuuid = :uuid", + ['uuid' => $uuid]); +} diff --git a/modules-available/remoteaccess/hooks/cron.inc.php b/modules-available/remoteaccess/hooks/cron.inc.php new file mode 100644 index 00000000..3e0e130b --- /dev/null +++ b/modules-available/remoteaccess/hooks/cron.inc.php @@ -0,0 +1,6 @@ + $group]); + } + + public static function ensureMachinesRunning() + { + $res = Database::simpleQuery("SELECT rg.groupid, rg.wolcount, GROUP_CONCAT(rxl.locationid) AS locs FROM remoteaccess_group rg + INNER JOIN remoteaccess_x_location rxl USING (groupid) + WHERE rg.active = 1 + GROUP BY groupid"); + + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + if ($row['wolcount'] <= 0) + continue; + // This can't really be anything but a CSV list, but better be safe + $locs = preg_replace('/[^0-9,]/', '', $row['locs']); + if (empty($locs)) + continue; + $active = Database::queryFirst("SELECT Count(*) AS cnt FROM machine m + INNER JOIN remoteaccess_machine rm USING (machineuuid) + WHERE m.locationid IN ($locs) AND m.state = 'IDLE'"); + $wantNum = $row['wolcount'] - (isset($active['cnt']) ? $active['cnt'] : 0); + if ($wantNum <= 0) + continue; + self::tryWakeMachines($locs, $wantNum); + } + } + + private static function tryWakeMachines($locs, $num) + { + $res = Database::simpleQuery("SELECT m.machineuuid, m.macaddr, m.clientip FROM machine m + LEFT JOIN remoteaccess_machine rm USING (machineuuid) + WHERE m.locationid IN ($locs) AND m.state IN ('OFFLINE', 'STANDBY') + ORDER BY rm.woltime ASC"); + $NOW = time(); + while ($num > 0) { + $list = []; + for ($i = 0; $i < $num && $row = $res->fetch(PDO::FETCH_ASSOC); ++$i) { + $list[] = $row; + Database::exec("INSERT INTO remoteaccess_machine (machineuuid, password, woltime) + VALUES (:uuid, NULL, :now) + ON DUPLICATE KEY UPDATE woltime = VALUES(woltime)", + ['uuid' => $row['machineuuid'], 'now' => $NOW]); + } + if (empty($list)) + break; // No more clients in this location + error_log('Trying to wake ' . count($list) . " in $locs"); + RebootControl::wakeMachines($list, $fails); + $num -= count($list) - count($fails); + if (!empty($fails)) { + error_log(count($fails) . ' failed'); + $failIds = ArrayUtil::flattenByKey($fails, 'machineuuid'); + // Reduce time so they won't be marked as wol_in_progress + Database::exec('UPDATE remoteaccess_machine SET woltime = :faketime WHERE machineuuid IN (:fails)', + ['faketime' => $NOW - 95, 'fails' => $failIds]); + } + } + if ($num > 0) { + error_log("Could not wake $num clients..."); + } + } + +} diff --git a/modules-available/remoteaccess/install.inc.php b/modules-available/remoteaccess/install.inc.php new file mode 100644 index 00000000..11656218 --- /dev/null +++ b/modules-available/remoteaccess/install.inc.php @@ -0,0 +1,60 @@ + $max['groupid'] + 1]); + } + $ret = Database::exec("INSERT IGNORE INTO remoteaccess_x_location (groupid, locationid) + SELECT locationid, locationid FROM remoteaccess_location"); + if ($ret === false) { + finalResponse(UPDATE_FAILED, Database::lastError()); + } + Database::exec("DROP TABLE remoteaccess_location"); +} + +responseFromArray($dbret); diff --git a/modules-available/remoteaccess/lang/de/messages.json b/modules-available/remoteaccess/lang/de/messages.json new file mode 100644 index 00000000..fbdefd8f --- /dev/null +++ b/modules-available/remoteaccess/lang/de/messages.json @@ -0,0 +1,6 @@ +{ + "group-added": "Gruppe hinzugef\u00fcgt", + "group-not-found": "Gruppe {{0}} existiert nicht", + "group-updated": "Gruppe {{0}} wurde aktualisiert", + "settings-saved": "Einstellungen gespeichert" +} \ No newline at end of file diff --git a/modules-available/remoteaccess/lang/de/module.json b/modules-available/remoteaccess/lang/de/module.json new file mode 100644 index 00000000..21d8ca69 --- /dev/null +++ b/modules-available/remoteaccess/lang/de/module.json @@ -0,0 +1,4 @@ +{ + "module_name": "Fernzugriff", + "page_title": "Fernzugriff" +} \ No newline at end of file diff --git a/modules-available/remoteaccess/lang/de/template-tags.json b/modules-available/remoteaccess/lang/de/template-tags.json new file mode 100644 index 00000000..b44849d6 --- /dev/null +++ b/modules-available/remoteaccess/lang/de/template-tags.json @@ -0,0 +1,16 @@ +{ + "lang_add": "Hinzuf\u00fcgen", + "lang_allowAccessText": "IP-Adresse oder Netz in CIDR Notation, welches auf den VNC-Port des Clients zugreifen darf. (I.d.R. nur der Guacamole-Server)", + "lang_allowedAccessToVncPort": "Erlaubte Quelle f\u00fcr VNC-Zugriff", + "lang_assignLocations": "R\u00e4ume zuweisen", + "lang_group": "Gruppe", + "lang_groupListText": "Liste verf\u00fcgbarer Gruppen (\"virtuelle R\u00e4ume\")", + "lang_keepAvailableWol": "WoL#", + "lang_locationSelectionText": "Ausgew\u00e4hlte Orte werden in den Remote-Modus geschaltet (beim n\u00e4chsten Boot des Clients) und sind damit im Pool f\u00fcr den Fernzugriff.", + "lang_numLocs": "R\u00e4ume", + "lang_numberOfAvailableClients": "Anzahl bereit zu haltender Rechner", + "lang_numberOfAvailableText": "Wir hier eine Zahl > 0 angegeben, wird versucht mittels WOL mindestens diese Anzahl an Rechnern am Loginbildschirm bereit zu halten, um sofortigen Zugriff zu gew\u00e4hrleisten. Diese Einstellung deaktiviert keine eventuell gesetzten Reboot\/Shutdown Timeouts oder Zeitpl\u00e4ne, diese sollten also ggf. f\u00fcr die unten ausgew\u00e4hlten R\u00e4ume angepasst werden.", + "lang_remoteAccessSettings": "Einstellungen f\u00fcr den Fernzugriff", + "lang_tryVirtualizerHandover": "Versuche, VNC-Server des Virtualisierers zu verwenden", + "lang_tryVirtualizerText": "Wenn aktiviert wird versucht, nach dem Start einer VM die Verbindung auf den VNC-Server des Virtualisierers umzubuchen. Zumindest f\u00fcr VMware haben wir hier allerdings eher eine Verschlechterung der Performance beobachten k\u00f6nnen; au\u00dferdem bricht die Verbindung beim Handover manchmal ab -> Nur experimentell!" +} \ No newline at end of file diff --git a/modules-available/remoteaccess/page.inc.php b/modules-available/remoteaccess/page.inc.php new file mode 100644 index 00000000..edbe0ff8 --- /dev/null +++ b/modules-available/remoteaccess/page.inc.php @@ -0,0 +1,100 @@ + $group) { + Database::exec("UPDATE remoteaccess_group SET groupname = :name, wolcount = :wol, + passwd = :passwd, active = :active WHERE groupid = :id", [ + 'id' => $id, + 'name' => isset($group['groupname']) ? $group['groupname'] : $id, + 'wol' => isset($group['wolcount']) ? $group['wolcount'] : 0, + 'passwd' => isset($group['passwd']) ? $group['passwd'] : 0, + 'active' => isset($group['active']) && $group['active'] ? 1 : 0, + ]); + } + Property::set(RemoteAccess::PROP_ALLOWED_VNC_NET, Request::post('allowed-source', '', 'string')); + Property::set(RemoteAccess::PROP_TRY_VIRT_HANDOVER, Request::post('virt-handover', false, 'int')); + Message::addSuccess('settings-saved'); + } elseif ($action === 'set-locations') { + $groupid = Request::post('groupid', Request::REQUIRED, 'int'); + $group = Database::queryFirst("SELECT groupname FROM remoteaccess_group WHERE groupid = :groupid"); + if ($group === false) { + Message::addError('group-not-found', $groupid); + Util::redirect('?do=remoteaccess'); + } + $locations = array_values(Request::post('location', [], 'array')); + if (empty($locations)) { + Database::exec("DELETE FROM remoteaccess_x_location WHERE groupid = :id", ['id' => $groupid]); + } else { + Database::exec("INSERT IGNORE INTO remoteaccess_x_location (groupid, locationid) + VALUES :values", ['values' => array_map(function($item) use ($groupid) { return [$groupid, $item]; }, $locations)]); + Database::exec("DELETE FROM remoteaccess_x_location WHERE groupid = :id AND locationid NOT IN (:locations)", + ['id' => $groupid, 'locations' => $locations]); + } + Message::addSuccess('group-updated', $group['groupname']); + } + if (Request::isPost()) { + Util::redirect('?do=remoteaccess'); + } + } + + protected function doRender() + { + $groupid = Request::get('groupid', false, 'int'); + if ($groupid === false) { + // Edit list of groups and their settings + $groups = Database::queryAll("SELECT g.groupid, g.groupname, g.wolcount, g.passwd, + Count(l.locationid) AS locs, If(g.active, 'checked', '') AS checked + FROM remoteaccess_group g LEFT JOIN remoteaccess_x_location l USING (groupid) + GROUP BY g.groupid, g.groupname + ORDER BY g.groupname ASC"); + $data = [ + 'allowed-source' => Property::get(RemoteAccess::PROP_ALLOWED_VNC_NET), + 'virt-handover_checked' => Property::get(RemoteAccess::PROP_TRY_VIRT_HANDOVER) ? 'checked' : '', + 'groups' => $groups, + ]; + Render::addTemplate('edit-settings', $data); + } else { + // Edit locations for group + $group = Database::queryFirst("SELECT groupid, groupname FROM remoteaccess_group WHERE groupid = :id", + ['id' => $groupid]); + if ($group === false) { + Message::addError('group-not-found', $groupid); + return; + } + $locationList = Location::getLocationsAssoc(); + $enabled = RemoteAccess::getEnabledLocations($groupid); + foreach ($enabled as $lid) { + if (isset($locationList[$lid])) { + $locationList[$lid]['checked'] = 'checked'; + } + } + Render::addTemplate('edit-group', $group + ['locations' => array_values($locationList)]); + } + } + +} diff --git a/modules-available/remoteaccess/templates/edit-group.html b/modules-available/remoteaccess/templates/edit-group.html new file mode 100644 index 00000000..2c207ca5 --- /dev/null +++ b/modules-available/remoteaccess/templates/edit-group.html @@ -0,0 +1,43 @@ +

{{lang_assignLocations}}

+

{{groupname}}

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

{{lang_locationSelectionText}}

+ + {{#locations}} + + + + + {{/locations}} +
+
+ + +
+
+
+ +
+
+
+ +
+
+
diff --git a/modules-available/remoteaccess/templates/edit-settings.html b/modules-available/remoteaccess/templates/edit-settings.html new file mode 100644 index 00000000..2712cf04 --- /dev/null +++ b/modules-available/remoteaccess/templates/edit-settings.html @@ -0,0 +1,72 @@ +

{{lang_remoteAccessSettings}}

+ +
+ +
+ +

{{lang_allowAccessText}}

+
+
+
+ + +
+

{{lang_tryVirtualizerText}}

+
+ +
+

{{lang_groupListText}}

+ + + + + + + + + + + {{#groups}} + + + + + + + + {{/groups}} +
{{lang_group}}{{lang_numLocs}}{{lang_keepAvailableWol}}{{lang_password}}
+
+ + +
+
+ + + {{locs}} + + + + + + + +
+
+
+ + +
+
+
\ No newline at end of file -- cgit v1.2.3-55-g7522