diff options
26 files changed, 2365 insertions, 0 deletions
diff --git a/modules-available/statistics/inc/devicetype.inc.php b/modules-available/statistics/inc/devicetype.inc.php index 41ee237d..571d6d2c 100644 --- a/modules-available/statistics/inc/devicetype.inc.php +++ b/modules-available/statistics/inc/devicetype.inc.php @@ -3,4 +3,5 @@ class DeviceType { const SCREEN = 'SCREEN'; + const USB = 'USB'; } diff --git a/modules-available/usblockoff/api.inc.php b/modules-available/usblockoff/api.inc.php new file mode 100644 index 00000000..52a98b67 --- /dev/null +++ b/modules-available/usblockoff/api.inc.php @@ -0,0 +1,230 @@ +<?php + +HandleParameters(); + +function HandleParameters() +{ + $getAction = Request::get('action', 0, 'string'); + if ($getAction == "newdevice") { + $id = Request::get('id', '', 'string'); + $serial = Request::get('serial', '', 'sting'); + $name = Request::get('name', '', 'string'); + $ip = Request::get('ip', 0, 'string'); + $client = Database::queryFirst("SELECT m.machineuuid AS 'muid', m.currentuser AS 'user' FROM machine AS m WHERE m.clientip=:ip", array('ip' => $ip)); + + // TODO: product and vendor id necessary? It's already in the hwname part. + list($vid, $pid) = explode(':', $id); + $hwProps = array( + 'vendorid' => $vid, + 'productid' => $pid, + 'name' => $name, + 'with-interface' => Request::get('with-interface', '', 'string') + ); + // TODO: WITH INTERFACE in the HW table?! Should be equal for every device but not guaranteed (ODROID). + $deviceProps = array( + 'hash' => Request::get('hash', '', 'string'), + 'parent-hash' => Request::get('parent-hash', '', 'string'), + 'via-port' => Request::get('via-port', '', 'string'), + 'interface-policy' => Request::get('interface-policy', '', 'string'), + 'machineuuid' => $client['muid'], + 'user' => $client['user'], + 'lastseen' => time() + ); + newDevice($id, $serial, $hwProps, $deviceProps); + } elseif ($getAction === "deletedevice") { + $id = Request::get('id', '', 'string'); + $serial = Request::get('serial', '', 'string'); + deleteDevice($id, $serial); + } elseif ($getAction === "getrule") { + $configid = Request::get('configid', '0', 'int'); + getRule($configid); + } elseif ($getAction === "getidlist") { + echo getIDList(); + } +} + +/** + * Adds a new USB-Device to the db. + * + * @param string $id USB-Device id. + * @param string $serial USB-Device serial number. + * @param string $name USB-Device name. + */ +function newDevice($id, $serial, $hwProps, $deviceProps) +{ + // Add or Update the usb device in the statistic_hw table. + $hwid = (int)Database::insertIgnore('statistic_hw', 'hwid', array( + 'hwtype' => DeviceType::USB, + 'hwname' => $id)); + // TODO: Is it okay to use the id (vendor:product) as hwname to identify a usb device? + + // Add all the global prop values to the statistics_hw_prop table. + // productid, vendorid, name, interfaces + // TODO: + addHwProps('statistic_hw_prop', $hwid, $hwProps); + + // Only when the device has a serial number add the specific hw props. + // TODO: !!! Are there data transfer devices without a serial number? !!! + if (!empty($serial)) { + // Add the hwid -> serial in the usblockoff_hw table if not already existent. + $dbquery2 = Database::queryFirst("Select * FROM `usblockoff_hw` WHERE hwid=:hwid AND serial=:serial", array( + 'hwid' => $hwid, + 'serial' => $serial + )); + + if (empty($dbquery2)) { + Database::exec("INSERT INTO `usblockoff_hw` (hwid, serial) VALUES (:hwid, :serial)", array( + 'hwid' => $hwid, + 'serial' => $serial + )); + } + + // Add all the prop values to the usblockoff_hw_prop table. + // PROP: serial, machineuuid, time, user, ruleInformation, Port, hash, interface-policy + addUSBHwProps('usblockoff_hw_prop', $hwid, $serial, $deviceProps); + echo "Successfully added"; + } else { + echo "No specific props were added. Device has no serial number"; + } +} + +function getRule($configid) { + // Get the config from the db. + $config = Database::queryFirst("SELECT * FROM `usb_configs` WHERE configid=:configid", array( + "configid" => $configid + )); + $idList = array(); + $rules = array(); + // For each $id get the rule information and build the rule. + foreach (json_decode($config['rulesconfig']) as $id) { + $idList[] = $id; + + // TODO: Make more efficient with one query instead of one per id. + + $dbquery = Database::simpleQuery("SELECT * FROM `usb_rule_prop` WHERE ruleid=:id", array( + "id" => $id + )); + $rule = ""; + while ($attribute = $dbquery->fetch(PDO::FETCH_ASSOC)) { + if ($attribute['prop'] == "target") { + $rule = $attribute['value'] . $rule; + } else { + if ($attribute['prop'] == "serial" || $attribute['prop'] == "name" || $attribute['prop'] == "hash" || + $attribute['prop'] == "parent-hash" || $attribute['prop'] == "via-port") { + $rule = $rule . " " . $attribute['prop'] . " \"" . $attribute['value'] . "\""; + } else { + $rule = $rule . " " . $attribute['prop'] . " " . $attribute['value']; + } + } + } + $rules[] = $rule; + } + + // Return the completed rules.conf. + echo implode("\n", $rules); +} + +function addHwProps($table, $hwid, $propArray) { + foreach ($propArray as $prop => $value) { + if (empty($value)) { + continue; + } + Database::exec("INSERT INTO " . $table . " (hwid, prop, value) VALUES (:hwid, :prop, :value) ON DUPLICATE KEY UPDATE value=:value", array( + 'hwid' => $hwid, + 'prop' => $prop, + 'value' => $value + )); + } +} + +function addUSBHwProps($table, $hwid, $serial, $propArray) { + foreach ($propArray as $prop => $value) { + if (empty($value)) { + continue; + } + Database::exec("INSERT INTO " . $table . " (hwid, serial, prop, value) VALUES (:hwid, :serial, :prop, :value) ON DUPLICATE KEY UPDATE value=:value", array( + 'hwid' => $hwid, + 'serial' => $serial, + 'prop' => $prop, + 'value' => $value + )); + } +} + +/** + * Deletes a device from the db given a serial number. + * + * @param string $serial USB-Device serial number. + */ +function deleteDevice($id, $serial) +{ + $hw = Database::queryFirst("SELECT * FROM `statistic_hw` WHERE hwname=:id", array('id' => $id)); + if($hw['hwtype'] === DeviceType::USB) { + Database::exec("DELETE FROM `usblockoff_hw` WHERE hwid=:hwid AND serial=:serial", array( + 'hwid' => $hw['hwid'], + 'serial' => $serial)); + echo "Successfully deleted."; + } else { + echo "Type is not a USB device"; + } +} + +/** + * Return a sorted list with all vendor / id information as JSON + */ +function getIDList() { + $usblist = array(); + + // TODO: Online version takes a bit longer to load but is more accurate. (2018 instead of 2015) + //$lines = file('http://www.linux-usb.org/usb.ids'); + $lines = file('/var/lib/usbutils/usb.ids'); + $currentVendor = ''; + $br = false; + foreach ($lines as $line) { + if ($line === "\n" && $br) { + // If its the first part (vid - name / pid - name) skip comments else break because the part we needed is finished. + break; + } else if ($line[0] === '#' || $line === "\n") { + continue; + } else if (!ctype_space($line[0])) { + $br = true; + // It's a vendor id. + $l = explode(' ', preg_replace('~[\r\n\t]+~', '', $line)); + $vendor = array(); + $vendor['name'] = $l[1]; + $vendor['products'] = array(); + $currentVendor = $l[0]; + $usblist[$l[0]] = $vendor; + } else if (!ctype_space($line[1])) { + // It's a product id. + $l = explode(' ', preg_replace('~[\r\n\t]+~', '', $line)); + $usblist[$currentVendor]['products'][$l[0]] = $l[1]; + } else { + // It's a interface + continue; + } + } + + $sortVendorName = []; + $sortVendorId = []; + foreach ($usblist as $key => $value) { + $sortVendorName[] = (string)$value['name']; + $sortVendorId[] = (string)$key; + + $sortProductName = []; + $sortProductId = []; + $tmp = $value['products']; + $keys2 = array_keys($tmp); + foreach ($tmp as $k => $v) { + $sortProductName[] = $v; + $sortProductId[] = $k; + } + + array_multisort($sortProductName, SORT_ASC, $sortProductId, SORT_ASC, $tmp, $keys2); + $usblist[$key]['products'] = array_combine($keys2, $tmp); + } + + $keys = array_keys($usblist); + array_multisort($sortVendorName, SORT_ASC, $sortVendorId, SORT_ASC, $usblist, $keys); + return json_encode(array_combine($keys, $usblist)); +} diff --git a/modules-available/usblockoff/config.json b/modules-available/usblockoff/config.json new file mode 100644 index 00000000..7655f131 --- /dev/null +++ b/modules-available/usblockoff/config.json @@ -0,0 +1,4 @@ +{ + "category":"main.beta", + "dependencies": ["bootstrap_switch", "js_jqueryui", "bootstrap_dialog", "statistics", "permissionmanager"] +} diff --git a/modules-available/usblockoff/inc/default-configs/rules.conf b/modules-available/usblockoff/inc/default-configs/rules.conf new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/modules-available/usblockoff/inc/default-configs/rules.conf diff --git a/modules-available/usblockoff/inc/default-configs/usbguard-daemon.conf b/modules-available/usblockoff/inc/default-configs/usbguard-daemon.conf new file mode 100644 index 00000000..44f2d66c --- /dev/null +++ b/modules-available/usblockoff/inc/default-configs/usbguard-daemon.conf @@ -0,0 +1,160 @@ +#
+# Rule set file path.
+#
+# The USBGuard daemon will use this file to load the policy
+# rule set from it and to write new rules received via the
+# IPC interface.
+#
+# RuleFile=/path/to/rules.conf
+#
+RuleFile=/usr/local/etc/usbguard/rules.conf
+
+#
+# Implicit policy target.
+#
+# How to treat devices that don't match any rule in the
+# policy. One of:
+#
+# * allow - authorize the device
+# * block - block the device
+# * reject - remove the device
+#
+ImplicitPolicyTarget=allow
+
+#
+# Present device policy.
+#
+# How to treat devices that are already connected when the
+# daemon starts. One of:
+#
+# * allow - authorize every present device
+# * block - deauthorize every present device
+# * reject - remove every present device
+# * keep - just sync the internal state and leave it
+# * apply-policy - evaluate the ruleset for every present
+# device
+#
+PresentDevicePolicy=apply-policy
+
+#
+# Present controller policy.
+#
+# How to treat USB controllers that are already connected
+# when the daemon starts. One of:
+#
+# * allow - authorize every present device
+# * block - deauthorize every present device
+# * reject - remove every present device
+# * keep - just sync the internal state and leave it
+# * apply-policy - evaluate the ruleset for every present
+# device
+#
+PresentControllerPolicy=keep
+
+#
+# Inserted device policy.
+#
+# How to treat USB devices that are already connected
+# *after* the daemon starts. One of:
+#
+# * block - deauthorize every present device
+# * reject - remove every present device
+# * apply-policy - evaluate the ruleset for every present
+# device
+#
+InsertedDevicePolicy=apply-policy
+
+#
+# Restore controller device state.
+#
+# The USBGuard daemon modifies some attributes of controller
+# devices like the default authorization state of new child device
+# instances. Using this setting, you can controll whether the
+# daemon will try to restore the attribute values to the state
+# before modificaton on shutdown.
+#
+# SECURITY CONSIDERATIONS: If set to true, the USB authorization
+# policy could be bypassed by performing some sort of attack on the
+# daemon (via a local exploit or via a USB device) to make it shutdown
+# and restore to the operating-system default state (known to be permissive).
+#
+RestoreControllerDeviceState=false
+
+#
+# Device manager backend
+#
+# Which device manager backend implementation to use. One of:
+#
+# * uevent - Netlink based implementation which uses sysfs to scan for present
+# devices and an uevent netlink socket for receiving USB device
+# related events.
+# * dummy - A dummy device manager which simulates several devices and device
+# events. Useful for testing.
+#
+DeviceManagerBackend=uevent
+
+#!!! WARNING: It's good practice to set at least one of the !!!
+#!!! two options bellow. If none of them are set, !!!
+#!!! the daemon will accept IPC connections from !!!
+#!!! anyone, thus allowing anyone to modify the !!!
+#!!! rule set and (de)authorize USB devices. !!!
+
+#
+# Users allowed to use the IPC interface.
+#
+# A space delimited list of usernames that the daemon will
+# accept IPC connections from.
+#
+# IPCAllowedUsers=username1 username2 ...
+#
+IPCAllowedUsers=root
+
+#
+# Groups allowed to use the IPC interface.
+#
+# A space delimited list of groupnames that the daemon will
+# accept IPC connections from.
+#
+# IPCAllowedGroups=groupname1 groupname2 ...
+#
+IPCAllowedGroups=
+
+#
+# IPC access control definition files path.
+#
+# The files at this location will be interpreted by the daemon
+# as access control definition files. The (base)name of a file
+# should be in the form:
+#
+# [user][:<group>]
+#
+# and should contain lines in the form:
+#
+# <section>=[privilege] ...
+#
+# This way each file defines who is able to connect to the IPC
+# bus and what privileges he has.
+#
+IPCAccessControlFiles=/usr/local/etc/usbguard/IPCAccessControl.d/
+
+#
+# Generate device specific rules including the "via-port"
+# attribute.
+#
+# This option modifies the behavior of the allowDevice
+# action. When instructed to generate a permanent rule,
+# the action can generate a port specific rule. Because
+# some systems have unstable port numbering, the generated
+# rule might not match the device after rebooting the system.
+#
+# If set to false, the generated rule will still contain
+# the "parent-hash" attribute which also defines an association
+# to the parent device. See usbguard-rules.conf(5) for more
+# details.
+#
+DeviceRulesWithPort=false
+
+#
+# USBGuard audit events log file path.
+#
+AuditFilePath=/usr/local/var/log/usbguard/usbguard-audit.log
diff --git a/modules-available/usblockoff/install.inc.php b/modules-available/usblockoff/install.inc.php new file mode 100644 index 00000000..dda0ac35 --- /dev/null +++ b/modules-available/usblockoff/install.inc.php @@ -0,0 +1,65 @@ +<?php + +$res = array(); + +$t1 = $res[] = tableCreate('usblockoff_hw', ' + `hwid` INT(10) UNSIGNED NOT NULL, + `serial` VARCHAR(128), + PRIMARY KEY (`hwid`, `serial`) +'); + +$t2 = $res[] = tableCreate('usblockoff_hw_prop', ' + `hwid` INT(10) UNSIGNED NOT NULL, + `serial` VARCHAR(128), + `prop` CHAR(16), + `value` VARCHAR(500), + PRIMARY KEY (`hwid`, `serial`, `prop`) +'); + +$t4 = $res[] = tableCreate('usb_configs', ' + `configid` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, + `configname` VARCHAR(200) NOT NULL, + `rulesconfig` VARCHAR(512), + `daemonconfig` BLOB, + PRIMARY KEY (`configid`) +'); + +$t3 = $res[] = tableCreate('usb_rule_prop', ' + `ruleid` INT(10) UNSIGNED NOT NULL, + `prop` CHAR(16), + `value` VARCHAR(500) +'); + +/* +$res[] = tableCreate('usb_configs', ' + `configid` int(10) UNSIGNED NOT NULL AUTO_INCREMENTloadAddDeviceModal, + `configname` VARCHAR(200) NOT NULL, + `rulesconfig` BLOB, + `daemonconfig` BLOB, + PRIMARY KEY (`configid`) +'); +*/ + +//$ret = Database::exec("DROP TABLE `usb_devices`"); +//$ret = Database::exec("DROP TABLE `usblockoff_hw`"); +//$ret = Database::exec("DROP TABLE `usblockoff_hw_prop`"); +//$ret = Database::exec("DROP TABLE `usb_configs`"); + + +if ($t4 === UPDATE_DONE) { + $ret = Database::exec("ALTER TABLE `usb_configs` ADD `configdesc` VARCHAR(200)"); + if ($ret === false) { + finalResponse(UPDATE_FAILED, 'Adding column configdesc to usb_configs failed.'); + } +} + +$res[] = tableAddConstraint("usblockoff_hw", "hwid", "statistic_hw", "hwid", "ON DELETE CASCADE"); + +// TODO: Works, as the one above already forces retry. How to write it in tableAddConstraint ? +if ($t2 === UPDATE_DONE) { + $ret = Database::exec('ALTER TABLE `usblockoff_hw_prop` + ADD CONSTRAINT `usblockoff_hw__prop_ibfk_1` FOREIGN KEY (`hwid`, `serial`) REFERENCES `usblockoff_hw` (`hwid`, `serial`) ON DELETE CASCADE'); +} + +responseFromArray($res); + diff --git a/modules-available/usblockoff/lang/de/messages.json b/modules-available/usblockoff/lang/de/messages.json new file mode 100644 index 00000000..319569fe --- /dev/null +++ b/modules-available/usblockoff/lang/de/messages.json @@ -0,0 +1,7 @@ +{ + "config-deleted": "Konfiguration erfolgreich gelöscht.", + "config-saved": "Konfiguration erfolgreich gespeichert.", + "rule-edited": "Regel erfolgreich geändert.", + "rule-deleted": "Regel erfolgreich gelöscht.", + "invalid-rule-id": "Ungültige Regel ID." +}
\ No newline at end of file diff --git a/modules-available/usblockoff/lang/de/module.json b/modules-available/usblockoff/lang/de/module.json new file mode 100644 index 00000000..bfde75fe --- /dev/null +++ b/modules-available/usblockoff/lang/de/module.json @@ -0,0 +1,4 @@ +{ + "module_name": "USB Lock-Off", + "page_title": "USB Lock-Off" +} diff --git a/modules-available/usblockoff/lang/de/rule.json b/modules-available/usblockoff/lang/de/rule.json new file mode 100644 index 00000000..bb6752ad --- /dev/null +++ b/modules-available/usblockoff/lang/de/rule.json @@ -0,0 +1,26 @@ +{ + "target": "Aktion", + "target_helptext": "allow: Autorisiert das Gerät.\u000Dblock: Blockiert das Gerät.\u000Dreject: Entfernt das Gerät aus dem System.", + "id": "ID", + "id_helptext": "ID des USB-Geräts.", + "vid": "Hersteller ID", + "vid_helptext": "Die eindeutige ID des Herstellers.", + "pid": "Produkt ID", + "pid_helptext": "Die Herstellerspezifische ID des Produktes.", + "vendor": "Hersteller", + "product": "Produkt", + "serial": "Seriennummer", + "serial_helptext": "Seriennummer des USB-Geräts.", + "name": "Name", + "name_helptext": "Name des USB-Geräts.", + "hash": "Hashwert", + "hash_helptext": "Hashwert des USB-Geräts. Von USBGuard mittels sodium oder gcrypt berechnet.", + "parent-hash": "Parent-Hashwert", + "parent-hash_helptext": "Hashwert des Clients an dem das USB-Gerät angeschlossen wurde.", + "via-port": "Erlaubte Ports", + "via-port_helptext": "USB-Port(s), an welche das USB-Gerät angeschlossen werden kann.", + "with-interface": "Interfaces", + "with-interface_helptext": "Die Interfaces, welches das USB-Gerät besitzt.", + "interface-policy": "Interface Police", + "interface-policy_helptext": "Per-Interface Authorisierung." +} diff --git a/modules-available/usblockoff/lang/de/template-tags.json b/modules-available/usblockoff/lang/de/template-tags.json new file mode 100644 index 00000000..1807ffd3 --- /dev/null +++ b/modules-available/usblockoff/lang/de/template-tags.json @@ -0,0 +1,61 @@ +{ + "lang_rules": "Regeln", + "lang_howToRuleLang": "Verwendung der Regel Sprache.", + "lang_device": "USB Gerät", + "lang_devices": "USB Geräte", + "lang_general": "Allgemein", + "lang_config": "Konfiguration", + "lang_config_helptext": "Erstelle eine neue Konfiguration oder wähle eine aus, um sie laden und bearbeiten zu können.", + "lang_configName": "Konfigurationsname", + "lang_configName_helptext": "Name der Konfiguration", + "lang_deleteConfig": "Konfiguration löschen", + "lang_deleteConfig_helptext": "Löscht die Konfiguration.", + "lang_createNewConfig": "<Neue Konfiguration erstellen>", + "lang_deleteConfigMessage": "Sind Sie sicher, dass Sie die Konfiguration löschen wollen?", + "lang_genericRule": "Generische Regel", + "lang_generalOptions": "Allgemeine Optionen", + "lang_modeOptions": "Modus Optionen", + "lang_deviceClasses": "Geräteklassen", + "lang_classes-helptext": "", + "lang_contains": "Beinhält Interface", + "lang_contains-helptext": "Erlaubt das Gerät selbst wenn es noch zusätzliche Interfaces hat.", + "lang_mass-storage": "Massenspeichergeräte", + "lang_hid": "Eingabegeräte", + "lang_hub": "USB Hubs", + "lang_printer": "Drucker", + "lang_audio": "Audiogeräte", + "lang_all-devices": "Alle USB Geräte", + "lang_addRule": "Regel hinzufügen", + "lang_operator": "Operator", + "lang_operator-helptext": "", + "lang_deviceClass": "Geräteklasse", + "lang_deviceClass-helptext": "", + "lang_deviceSubClass": "Gerätesubklasse", + "lang_deviceSubClass-helptext": "", + "lang_deviceProtocol": "Geräteprotokoll", + "lang_deviceProtocol-helptext": "", + "lang_all-of": "Alle von", + "lang_one-of": "Eins von", + "lang_none-of": "Keins von", + "lang_equals": "Gleicht", + "lang_equals-ordered": "Gleicht geordnet", + "lang_saveAsNewConfig": "Als neue Konfiguration speichern", + "lang_saveAsNewConfig-helptext": "Erstellt eine neue Konfiguration statt die alte zu Überschreiben.", + "lang_add-generic-rule": "Generische Regel hinzufügen", + "lang_device-list": "Geräte Liste", + "lang_rulesConfig": "Regel Konfiguration", + "lang_daemonConfig": "Daemon Konfiguration", + "lang_assignMenu": "Zuweisungsmenü", + "lang_serverName": "Servername", + "lang_configuration": "Konfiguration", + "lang_usb-lock-off": "USB Lock-Off", + "lang_disabledButtons_helptext": "Sie müssen zuerst die Konfiguration speichern, um Regeln hinzufügen zu können.", + "lang_firstMatchingRuleCounts": "Die erste zutreffende Regel (von Oben nach Unten) wird angewendet.", + "lang_description": "Beschreibung", + "lang_configurationTable": "Konfigurationstabelle", + "lang_editConfig": "Konfiguration bearbeiten", + "lang_configDesc_helptext": "Beschreibung der Konfiguration.", + "lang_attributes": "Attribute", + "lang_action": "Aktion", + "lang_editRule": "Regel bearbeiten" +} diff --git a/modules-available/usblockoff/lang/en/messages.json b/modules-available/usblockoff/lang/en/messages.json new file mode 100644 index 00000000..70627a36 --- /dev/null +++ b/modules-available/usblockoff/lang/en/messages.json @@ -0,0 +1,7 @@ +{ + "config-deleted": "Configuration successfully deleted.", + "config-saved": "Configuration successfully saved.", + "rule-edited": "Rule successfully edited.", + "rule-deleted": "Rule successfully deleted.", + "invalid-rule-id": "Invalid rule ID." +}
\ No newline at end of file diff --git a/modules-available/usblockoff/lang/en/module.json b/modules-available/usblockoff/lang/en/module.json new file mode 100644 index 00000000..bfde75fe --- /dev/null +++ b/modules-available/usblockoff/lang/en/module.json @@ -0,0 +1,4 @@ +{ + "module_name": "USB Lock-Off", + "page_title": "USB Lock-Off" +} diff --git a/modules-available/usblockoff/lang/en/rule.json b/modules-available/usblockoff/lang/en/rule.json new file mode 100644 index 00000000..53682563 --- /dev/null +++ b/modules-available/usblockoff/lang/en/rule.json @@ -0,0 +1,26 @@ +{ + "target": "Action", + "target_helptext": "allow: authorize the device.\u000Dblock: block the device.\u000DReject: remove the device from the system.", + "id": "ID", + "id_helptext": "ID of the USB-device.", + "vid": "Vendor ID", + "vid_helptext": "The unique ID of the vendor.", + "pid": "Product ID", + "pid_helptext": "The vendor specific ID of the product.", + "vendor": "Vendor", + "product": "Product", + "serial": "Serialnumber", + "serial_helptext": "Serialnumber of the USB-device.", + "name": "Name", + "name_helptext": "Name of the USB-device.", + "hash": "Hash value", + "hash_helptext": "Hash value of the USB-device. Calculated via USBGuard through sodium or gcrypt.", + "parent-hash": "Parent-hash value", + "parent-hash_helptext": "Hash value of the Client the USB-device was connected to.", + "via-port": "Via port", + "via-port_helptext": "Accepted USB-port(s) for the USB-device.", + "with-interface": "Interfaces", + "with-interface_helptext": "Interfaces of the USB-device.", + "interface-policy": "interface policy", + "interface-policy_helptext": "Per-interface authorisation." +} diff --git a/modules-available/usblockoff/lang/en/template-tags.json b/modules-available/usblockoff/lang/en/template-tags.json new file mode 100644 index 00000000..e8b9d380 --- /dev/null +++ b/modules-available/usblockoff/lang/en/template-tags.json @@ -0,0 +1,61 @@ +{ + "lang_rules": "Rules", + "lang_howToRuleLang": "Usage of the Rule Language.", + "lang_device": "USB device", + "lang_devices": "USB devices", + "lang_general": "General", + "lang_config": "Configuration", + "lang_config_helptext": "Create a new configuration or choose one to load and edit it", + "lang_configName": "Configuration name", + "lang_configName_helptext": "The name of the configuration", + "lang_deleteConfig": "Delete configuration", + "lang_deleteConfig_helptext": "Delets the configuration.", + "lang_createNewConfig": "<Create new configuration>", + "lang_deleteConfigMessage": "Are you sure you want to delete the configuration?", + "lang_genericRule": "Generic rule", + "lang_generalOptions": "General options", + "lang_modeOptions": "Mode options", + "lang_deviceClasses": "Device classes", + "lang_classes-helptext": "", + "lang_contains": "Contains interface", + "lang_contains-helptext": "If true the device is allowed even if it contains additional interfaces.", + "lang_mass-storage": "Mass storage devices", + "lang_hid": "Human interface devices", + "lang_hub": "USB Hubs", + "lang_printer": "Printer", + "lang_audio": "Audio devices", + "lang_all-devices": "All USB devices", + "lang_addRule": "Add rule", + "lang_operator": "Operator", + "lang_operator-helptext": "", + "lang_deviceClass": "Device class", + "lang_deviceClass-helptext": "", + "lang_deviceSubClass": "Device subclass", + "lang_deviceSubClass-helptext": "", + "lang_deviceProtocol": "Device protocol", + "lang_deviceProtocol-helptext": "", + "lang_all-of": "All of", + "lang_one-of": "One of", + "lang_none-of": "None of", + "lang_equals": "Equals", + "lang_equals-ordered": "Equals ordered", + "lang_saveAsNewConfig": "Save as new configuration", + "lang_saveAsNewConfig-helptext": "If true a new configuration is created instead of overriding the old one", + "lang_add-generic-rule": "Add generic rule", + "lang_device-list": "Device list", + "lang_rulesConfig": "Rules configuration", + "lang_daemonConfig": "Daemon configuration", + "lang_assignMenu": "Assign menu", + "lang_serverName": "Server name", + "lang_configuration": "Configuration", + "lang_usb-lock-off": "USB lock off", + "lang_disabledButtons_helptext": "You must first save the configuration to add rules.", + "lang_firstMatchingRuleCounts": "First matching rule (from top to bottom) will be applied.", + "lang_description": "Description", + "lang_configurationTable": "Configuration Table", + "lang_editConfig": "Edit Configuration", + "lang_configDesc_helptext": "Description of the configuration.", + "lang_attributes": "Attributes", + "lang_action": "Action", + "lang_editRule": "Edit rule" +} diff --git a/modules-available/usblockoff/page.inc.php b/modules-available/usblockoff/page.inc.php new file mode 100644 index 00000000..0f29ef91 --- /dev/null +++ b/modules-available/usblockoff/page.inc.php @@ -0,0 +1,704 @@ +<?php +class Page_usblockoff extends Page +{ + + /** + * Called before any page rendering happens - early hook to check parameters etc. + */ + protected function doPreprocess() + { + User::load(); + + if (!User::isLoggedIn()) { + Message::addError('main.no-permission'); + Util::redirect('?do=Main'); // does not return + } + + $this->action = Request::any('action'); + + if ($this->action === 'updateConfig') { + $this->updateConfig(); + } elseif ($this->action === 'addDevices') { + $this->addDevices(); + } elseif ($this->action === 'deleteConfig') { + $this->deleteConfig(); + } elseif ($this->action === 'deleteRule') { + $this->deleteRule(); + } elseif ($this->action === 'editRule') { + $this->editRule(); + } + } + + /** + * Menu etc. has already been generated, now it's time to generate page content. + */ + protected function doRender() + { + $show = Request::get("show", "config-table"); + if ($show === "config-table") { + $this->loadConfigChooser(); + } else if ($show === "edit-config") { + $configid = Request::get("configid", ""); + $dbquery = Database::queryFirst("SELECT configname, configdesc FROM `usb_configs` WHERE configid=:id", array( + 'id' => $configid + )); + + $rulesConfigHtml = $this->loadRulesConfig($configid); + $daemonConfigHtml = $this->loadDaemonConfig($configid); + + Render::addTemplate('usb-edit-config', array( + 'configid' => $configid, + 'configName' => $dbquery['configname'], + 'configDesc' => $dbquery['configdesc'], + 'rulesConfigHtml' => $rulesConfigHtml, + 'daemonConfigHtml' => $daemonConfigHtml + )); + } else if ($show === "add-devices") { + $this->deviceList(); + } else if ($show === "add-generic-rule") { + $this->addGenericRule(); + } else if ($show === "edit-rule") { + $ruleid = Request::get("ruleid", 0, "int"); + if ($ruleid === 0) { + Message::addError('invalid-rule-id'); + return; + } + $configid = Request::get("configid", ""); + $this->showEditRule($configid, $ruleid); + } + } + + private function editRule() { + $ruleid = Request::post('ruleid', 0, 'int'); + $configid = Request::post('configid', 0, 'int'); + + $attributes = json_decode(Request::post('attributes', '', 'string'), true); + + if ($ruleid != 0) { + Database::exec("DELETE FROM `usb_rule_prop` WHERE ruleid=:ruleid", array('ruleid' => $ruleid)); + } + + // Prepare array for the insert. prop- has to be cut and vid:pid = id + $a = array(); + foreach ($attributes as $att) { + // SPECIAL CASE: PID AND VID needs to put together to VID:PID = ID + if (substr($att['prop'], 5) === "vid") { + if (!array_key_exists('id', $a)) { + $a['id'] = ""; + } + $a['id'] = $att['value'] . ':' . $a['id']; + } else if (substr($att['prop'], 5) === "pid") { + if (!array_key_exists('id', $a)) { + $a['id'] = ""; + } + $a['id'] = $a['id'] . $att['value']; + } else { + $a[substr($att['prop'], 5)] = $att['value']; + } + } + + foreach ($a as $key => $value) { + // TODO: Better in one query? + Database::exec("INSERT INTO `usb_rule_prop` (ruleid, prop, value) VALUES (:ruleid, :prop, :val)", array( + 'ruleid' => $ruleid, + 'prop' => $key, + 'val' => $value + )); + } + + Message::addSuccess('rule-edited'); + Util::redirect('?do=usblockoff&show=edit-config&configid=' . $configid); + } + + private function addDevices() + { + $configid = Request::any('configid', 0, 'int'); + + $rules = json_decode(Request::post('rules', '', 'string'), true); + + foreach ($rules as $rule) { + $rid = (int)$rule['id']; + if($rid == 0) { + // New entry so insert only with new id. + $rid = Database::queryFirst("SELECT MAX(ruleid) AS ID FROM `usb_rule_prop`"); + $rid = $rid['ID']; + if ($rid == null) $rid = 1; + else $rid += 1; + } else { + // Old entry so delete all old ones and insert new ones. + Database::exec("DELETE FROM `usb_rule_prop` WHERE ruleid=:ruleid", array('ruleid' => $rid)); + } + + Database::exec("INSERT INTO `usb_rule_prop` (ruleid, prop, value) VALUES (:ruleid, :prop, :val)", array( + 'ruleid' => $rid, + 'prop' => 'target', + 'val' => $rule['target'] + )); + + foreach ($rule['attributes'] as $attribute) { + // TODO: Better in one query? + Database::exec("INSERT INTO `usb_rule_prop` (ruleid, prop, value) VALUES (:ruleid, :prop, :val)", array( + 'ruleid' => $rid, + 'prop' => $attribute['prop'], + 'val' => $attribute['value'] + )); + } + + // TODO: Add id at the end of the config entry. + $config = Database::queryFirst("SELECT rulesconfig FROM `usb_configs` WHERE configid=:configid", array( + 'configid' => $configid + )); + $rulesconfig = json_decode($config['rulesconfig'], true); + $rulesconfig[] = $rid; + Database::exec("UPDATE `usb_configs` SET rulesconfig = :rulesconfig WHERE configid=:configid", array( + 'configid' => $configid, + 'rulesconfig' => json_encode($rulesconfig) + )); + //$result['rules'][] = $rid; + } + + Util::redirect('?do=usblockoff&show=edit-config&configid=' . $configid); + } + + private function deviceList() + { + $configid = Request::get("configid", 0, 'int'); + $usbdevices = $this->getUsbDeviceList(); + + // TODO: Translate Operator Action etc.. + + $settings = array(); + $setting = array(); + $setting['title'] = "Action"; + $setting['select_list'] = array( + array( + 'option' => 'allow', + 'active' => true, + 'value' => 'allow', + ), + array( + 'option' => 'block', + 'active' => false, + 'value' => 'block', + ), + array( + 'option' => 'reject', + 'active' => false, + 'value' => 'reject', + )); + $setting['helptext'] = array('helptext' => Dictionary::translateFile('rule', 'target_helptext')); + $setting['property'] = 'action'; + $setting['settingHtml'] = Render::parse('server-prop-dropdown', (array)$setting); + $settings[] = $setting; + + $ruleValues = array('id' => true, + 'serial' => true, + 'name' => true, + //'hash' => false, + //'parent-hash' => false, + 'via-port' => false, + 'with-interface' => false); + foreach ($ruleValues as $key => $value) { + $settings[] = array( + 'settingHtml' => Render::parse('server-prop-bool', array( + 'title' => Dictionary::translateFile('rule', $key), + 'helptext' => array('helptext' => Dictionary::translateFile('rule', $key . "_helptext")), + 'property' => $key, + 'currentvalue' => $value)), + ); + } + Render::addTemplate('usb-device-list', array( + 'list' => array_values($usbdevices), + 'settings' => array_values($settings), + 'configid' => $configid + )); + } + + private function showEditRule($configid, $ruleid) { + $attributes = array(); + + $query = Database::simpleQuery("SELECT * FROM `usb_rule_prop` WHERE ruleid=:ruleid", array( + 'ruleid' => $ruleid + )); + $idList = json_decode($this->getIDList(), true); + + while ($attribute = $query->fetch(PDO::FETCH_ASSOC)) { + $attributesHtml = ''; + $attr = array( + 'title' => Dictionary::translateFile('rule', $attribute['prop']), + 'property' => $attribute['prop'], + 'currentvalue' => $attribute['value'], + 'helptext' => Dictionary::translateFile('rule', $attribute['prop'] . "_helptext"), + ); + + if ($attribute['prop'] === 'target') { + $attr['select_list'] = array( + array( + 'option' => 'allow', + 'active' => ($attribute['value'] === 'allow' ? true : false), + 'value' => 'allow', + ), + array( + 'option' => 'block', + 'active' => ($attribute['value'] === 'block' ? true : false), + 'value' => 'block', + ), + array( + 'option' => 'reject', + 'active' => ($attribute['value'] === 'reject' ? true : false), + 'value' => 'reject', + )); + + $attributesHtml = Render::parse('server-prop-dropdown', (array)$attr); + } else if ($attribute['prop'] === 'id') { + + $id = explode(':', $attribute['value']); + $vid = $id[0]; + $pid = $id[1]; + + // Vendor ID + $attr['title'] = Dictionary::translateFile('rule', 'vid'); + $attr['select_list'] = array(); + $attr['toTextButton'] = true; + $attr['property'] = 'vid'; + $attr['helptext'] = Dictionary::translateFile('rule', "vid_helptext"); + + if (array_key_exists($vid, $idList)) { + $attr['select_list'][] = array( + 'option' => $idList[$vid]['name'], + 'active' => true, + 'value' => $vid, + ); + } else { + $attr['inputtype'] = 'text'; + $attr['value'] = $vid; + $attr['select_list'][] = array( + 'option' => "unknown", + 'active' => true, + 'value' => $vid, + ); + } + $attributesHtml = Render::parse('server-prop-dropdown', (array)$attr); + $attributes[] = array( + 'attributesHtml' => $attributesHtml + ); + + // Product ID + $attr['title'] = Dictionary::translateFile('rule', 'pid'); + $attr['select_list'] = array(); + $attr['property'] = 'pid'; + $attr['helptext'] = Dictionary::translateFile('rule', "pid_helptext"); + + if (array_key_exists($pid, $idList)) { + $attr['select_list'][] = array( + 'option' => $idList[$vid]['products'][$pid], + 'active' => true, + 'value' => $pid, + ); + } else { + $attr['select_list'][] = array( + 'option' => "unknown", + 'active' => true, + 'value' => $pid, + ); + } + $attributesHtml = Render::parse('server-prop-dropdown', (array)$attr); + } else { + $attr['inputtype'] = 'text'; + $attributesHtml = Render::parse('server-prop-generic', (array)$attr); + } + $attributes[] = array( + 'attributesHtml' => $attributesHtml + ); + } + + Render::addTemplate('usb-edit-rule', array( + 'configid' => $configid, + 'attributes' => $attributes, + 'ruleid' => $ruleid, + 'usbJson' => json_encode($idList), + )); + } + + /** + * Return a sorted list with all vendor / id information as JSON + */ + function getIDList() { + $usblist = array(); + + // TODO: Online version takes a bit longer to load but is more accurate. (2018 instead of 2015) + //$lines = file('http://www.linux-usb.org/usb.ids'); + $lines = file('/var/lib/usbutils/usb.ids'); + $currentVendor = ''; + $br = false; + foreach ($lines as $line) { + if ($line === "\n" && $br) { + // If its the first part (vid - name / pid - name) skip comments else break because the part we needed is finished. + break; + } else if ($line[0] === '#' || $line === "\n") { + continue; + } else if (!ctype_space($line[0])) { + $br = true; + // It's a vendor id. + $l = explode(' ', preg_replace('~[\r\n\t]+~', '', $line)); + $vendor = array(); + $vendor['name'] = $l[1]; + $vendor['products'] = array(); + $currentVendor = $l[0]; + $usblist[$l[0]] = $vendor; + } else if (!ctype_space($line[1])) { + // It's a product id. + $l = explode(' ', preg_replace('~[\r\n\t]+~', '', $line)); + $usblist[$currentVendor]['products'][$l[0]] = $l[1]; + } else { + // It's a interface + continue; + } + } + + $sortVendorName = []; + $sortVendorId = []; + foreach ($usblist as $key => $value) { + $sortVendorName[] = (string)$value['name']; + $sortVendorId[] = (string)$key; + + $sortProductName = []; + $sortProductId = []; + $tmp = $value['products']; + $keys2 = array_keys($tmp); + foreach ($tmp as $k => $v) { + $sortProductName[] = $v; + $sortProductId[] = $k; + } + // TODO: SORT_FLAG_CASE + array_multisort($sortProductName, SORT_ASC, $sortProductId, SORT_ASC, $tmp, $keys2); + $usblist[$key]['products'] = array_combine($keys2, $tmp); + } + + $keys = array_keys($usblist); + // TODO: SORT_FLAG_CASE + array_multisort($sortVendorName, SORT_ASC, $sortVendorId, SORT_ASC, $usblist, $keys); + return json_encode(array_combine($keys, $usblist), JSON_HEX_APOS); + } + + private function addGenericRule($target = 'allow') { + $settings = array(); + $configid = Request::get("configid", ""); + + // TODO: Translate Operator Action etc.. + + $setting = array(); + $setting['title'] = "Action"; + $setting['select_list'] = array(array( + 'option' => 'allow', + 'active' => ($target == 'allow' ? true : false), + 'value' => 'allow', + ), + array( + 'option' => 'block', + 'active' => ($target == 'block' ? true : false), + 'value' => 'block', + ), + array( + 'option' => 'reject', + 'active' => ($target == 'reject' ? true : false), + 'value' => 'reject', + )); + $setting['helptext'] = array('helptext' => Dictionary::translateFile('rule', 'target_helptext')); + $setting['property'] = 'action'; + $setting['settingHtml'] = Render::parse('server-prop-dropdown', (array)$setting); + $settings[] = $setting; + + Render::addTemplate('usb-add-generic-rule', array( + 'settings' => array_values($settings), + 'configid' => $configid + )); + } + + protected function loadConfigChooser() + { + $dbquery = Database::simpleQuery("SELECT configid, configname, configdesc FROM `usb_configs`"); + $configs = array(); + while ($dbentry = $dbquery->fetch(PDO::FETCH_ASSOC)) { + $config['config_id'] = $dbentry['configid']; + $config['config_name'] = $dbentry['configname']; + $config['config_desc'] = $dbentry['configdesc']; + $configs[] = $config; + } + Render::addTemplate('usb-configuration-table', array('config_list' => array_values($configs))); + } + + protected function deleteConfig() + { + $configID = Request::any('id', 0, 'int'); + if ($configID != 0) { + Database::exec("DELETE FROM `usb_configs` WHERE configid=:configid", array('configid' => $configID)); + } + + Message::addSuccess('config-deleted'); + Util::redirect('?do=usblockoff'); + } + + protected function deleteRule() + { + $configid = Request::any('configid', 0, 'int'); + $ruleid = Request::any('id', 0, 'int'); + if ($ruleid != 0) { + Database::exec("DELETE FROM `usb_rule_prop` WHERE ruleid=:ruleid", array('ruleid' => $ruleid)); + } + + Message::addSuccess('rule-deleted'); + Util::redirect('?do=usblockoff&show=edit-config&configid=' . $configid); + } + + + protected function updateConfig() + { + $result['saveAsNewConfig'] = Request::post('saveAsNewConfig', false, 'bool'); + // Add new settings in usbguard-daemon.conf here: + $result['RuleFile'] = Request::post('RuleFile', '', 'string'); + $result['ImplicitPolicyTarget'] = Request::post('ImplicitPolicyTarget', '', 'string'); + $result['PresentDevicePolicy'] = Request::post('PresentDevicePolicy', '', 'string'); + $result['PresentControllerPolicy'] = Request::post('PresentControllerPolicy', '', 'string'); + $result['InsertedDevicePolicy'] = Request::post('InsertedDevicePolicy', '', 'string'); + $result['RestoreControllerDeviceState'] = Request::post('RestoreControllerDeviceState', '', 'string'); + $result['DeviceManagerBackend'] = Request::post('DeviceManagerBackend', '', 'string'); + $result['IPCAllowedUsers'] = Request::post('IPCAllowedUsers', '', 'string'); + $result['IPCAllowedGroups'] = Request::post('IPCAllowedGroups', '', 'string'); + $result['IPCAccessControlFiles'] = Request::post('IPCAccessControlFiles', '', 'string'); + $result['DeviceRulesWithPort'] = Request::post('DeviceRulesWithPort', '', 'string'); + $result['AuditFilePath'] = Request::post('AuditFilePath', '', 'string'); + $result['rules'] = json_decode(Request::post('rules', '', 'string'), true); + + $id = Request::post('id', 0, 'int'); + $configname = Request::post('configName', '', 'string'); + $configdesc = Request::post('configDesc', '', 'string'); + $dbquery = Database::queryFirst("SELECT * FROM `usb_configs` WHERE configid=:id", array('id' => $id)); + + // Load daemon.conf from db else load default + if ($dbquery !== false) { + $daemonConf = explode("\r\n", $dbquery['daemonconfig']); + } else { + $currentdir = getcwd(); + $file = $currentdir . '/modules/usblockoff/inc/default-configs/usbguard-daemon.conf'; + $daemonConf = file($file); + } + $newDaemonConf = array(); + + foreach ($daemonConf as $line) { + $t_line = trim($line, "\r\n"); + if ($t_line == '' || $t_line[0] == '#') { + $newDaemonConf[] = $line . "\r\n"; + continue; + } else { + $splitstr = explode('=', $line); + + $splitstr[1] = $result[$splitstr[0]]; + $newDaemonConf[] = implode('=', $splitstr) . "\r\n"; + } + } + + // INSERT IN DB + if ($id == '0' || $result['saveAsNewConfig']) { + $dbquery = Database::exec("INSERT INTO `usb_configs` (configname, rulesconfig, daemonconfig, configdesc) VALUES (:configname, :rulesconfig, :daemonconfig, :configdesc)", + array('configname' => $configname, + 'rulesconfig' => json_encode($result['rules']), + 'daemonconfig' => implode($newDaemonConf), + 'configdesc' => $configdesc)); + } else { + $dbquery = Database::exec("UPDATE `usb_configs` SET configname=:configname, rulesconfig=:rulesconfig, daemonconfig=:daemonconfig, configdesc=:configdesc WHERE configid=:configid", + array('configid' => $id, + 'configname' => $configname, + 'rulesconfig' => json_encode($result['rules']), + 'daemonconfig' => implode($newDaemonConf), + 'configdesc' => $configdesc)); + } + Message::addSuccess('config-saved'); + } + + private function loadRulesConfig($configid) { + $rulesConf = null; + + if ($configid == 0) { + $currentdir = getcwd(); + // TODO: No need for that with the new rule db structure. + $rulesConf = file_get_contents($currentdir . '/modules/usblockoff/inc/default-configs/rules.conf'); + } else { + $dbquery = Database::queryFirst("SELECT * FROM `usb_configs` WHERE configid=:id", array('id' => $configid)); + $ruleIds = json_decode($dbquery['rulesconfig'], true); + } + + + $rulesArray = []; + foreach ($ruleIds as $id) { + // TODO: Query rule and prepare array for the html file. + $dbq = Database::simpleQuery("SELECT * FROM `usb_rule_prop` WHERE ruleid=:id", array('id' => $id)); + $rule = []; + $rule['id'] = $id; + $rule['hasoverload'] = false; + $rule['num_overload'] = 0; + $rule['attributes'] = array(); + $rule['attributes_overload'] = ""; + while ($entry = $dbq->fetch(PDO::FETCH_ASSOC)) { + if ($entry['prop'] == "target") { + $rule['target'] = $entry['value']; + } else if ($entry['prop'] == "id") { + $idList = json_decode($this->getIDList(), true); + $id = explode(":", $entry['value']); + + if (array_key_exists($id[0], $idList)) { + $vendor = $idList[$id[0]]['name']; + $attributes = []; + $attributes['prop'] = Dictionary::translateFile('rule', 'vendor'); + $attributes['value'] = $vendor; + $rule['attributes'][] = $attributes; + + if (array_key_exists($id[1], $idList[$id[0]]['products'])) { + $product = $idList[$id[0]]['products'][$id[1]]; + $attributes = []; + $attributes['prop'] = Dictionary::translateFile('rule', 'product'); + $attributes['value'] = $product; + $rule['attributes'][] = $attributes; + } + } else { + $attributes = []; + $attributes['prop'] = $entry['prop']; + $attributes['value'] = $entry['value']; + $rule['attributes'][] = $attributes; + } + + } else { + $attributes = []; + $attributes['prop'] = $entry['prop']; + $attributes['value'] = $entry['value']; + + if(sizeof($rule['attributes']) >= 5) { + $rule['hasoverload'] = true; + $rule['num_overload'] += 1; + $rule['attributes_overload'] .= $attributes['prop'] . ': ' . $attributes['value'] . "<br>"; + } else { + $rule['attributes'][] = $attributes; + } + } + } + + if (!empty($rule['target'])) { + $rulesArray[] = $rule; + } + } + + if ($configid == "new-default") { + $newConfig = true; + } else { + $newConfig = false; + } + + return Render::parse('usb-rules-config', array( + 'rules' => (array)$rulesArray, + 'configid' => $configid, + 'newConfig' => $newConfig + )); + } + + private function loadDaemonConfig($id) + { + $form = array(); + $rulesConf = null; + + if ($id == 0) { + $currentdir = getcwd(); + + $daemonConf = file($currentdir . '/modules/usblockoff/inc/default-configs/usbguard-daemon.conf'); + } else { + $dbquery = Database::queryFirst("SELECT * FROM `usb_configs` WHERE configid=:id", array('id' => $id)); + $daemonConf = explode("\r\n", $dbquery['daemonconfig']); + } + $element = array(); + $hlptxt = ''; + + foreach ($daemonConf as $line) { + $t_line = trim($line, "\r\n"); + if ($t_line == '#' || $t_line == '' || strpos($t_line, '#!!!') !== false) { + continue; + } elseif ($t_line[0] == '#') { + $ttxt = trim($line, "#"); + $hlptxt .= $ttxt . '<br>'; + } else { + $splitstr = explode('=', $t_line); + $element['name'] = $splitstr[0]; + $element['value'] = $splitstr[1]; + $element['helptext'] = $hlptxt; + + $form[] = $element; + $hlptxt = ''; + } + } + + return Render::parse('usb-daemon-config', array( + 'list' => array_values($form), + )); + } + + /** + * AJAX + */ + protected function doAjax() + { + User::load(); + if (!User::isLoggedIn()) { + die('Unauthorized'); + } + $action = Request::any('action'); + + // TODO: Removed if not needed anymore. + if ($action === '') { + //$this->ajaxDeviceList(); + } + } + + private function getUsbDeviceList() { + $usbdevices = array(); + + // TODO: Per USB Device 3 querys are executed.. better build a more complex sql query? + $uid = 0; + $dbquery = Database::simpleQuery("SELECT * FROM `usblockoff_hw`"); + while ($entry = $dbquery->fetch(PDO::FETCH_ASSOC)) { + + $device = array(); + + // Get all props from the hw table. + $dbquery2 = Database::simpleQuery("SELECT * FROM `statistic_hw_prop` WHERE hwid=:hwid", array( + 'hwid' => $entry['hwid'] + )); + + while ($prop = $dbquery2->fetch(PDO::FETCH_ASSOC)) { + $device[$prop['prop']] = $prop['value']; + } + + // Get all props from the device table. + $dbquery3 = Database::simpleQuery("SELECT * FROM `usblockoff_hw_prop` WHERE hwid=:hwid AND serial=:serial", array( + 'hwid' => $entry['hwid'], + 'serial' => $entry['serial'] + )); + + while ($prop = $dbquery3->fetch(PDO::FETCH_ASSOC)) { + $device[$prop['prop']] = $prop['value']; + } + if (!empty($device['machineuuid'])) { + $locationquery = Database::queryFirst("SELECT l.locationname AS 'name', m.clientip AS 'ip' FROM machine AS m JOIN location AS l ON l.locationid=m.locationid + WHERE m.machineuuid=:machineuuid", array('machineuuid' => $entry['machineuuid'])); + $device['clientip'] = $locationquery['ip']; + $device['location'] = $locationquery['name']; + } + + $device['uid'] = ++$uid; + $device['id'] = $device['vendorid'] . ":" . $device['productid']; + $device['serial'] = $entry['serial']; + $device['date'] = date('d.m.Y', $device['lastseen']); + $device['time'] = date('G:i', $device['lastseen']); + $usbdevices[] = $device; + } + + return $usbdevices; + } +} diff --git a/modules-available/usblockoff/style.css b/modules-available/usblockoff/style.css new file mode 100644 index 00000000..266ca2ec --- /dev/null +++ b/modules-available/usblockoff/style.css @@ -0,0 +1,13 @@ +.fixedTableLayout { + table-layout: fixed; + width: 100%; +} + +.tableWrapBreakWord{ + word-wrap: break-word +} + +.missingInput { + border-color: rgba(255, 0, 0, 0.8); + box-shadow: 0 1px 1px rgba(255, 0, 0, 0.075) inset, 0 0 8px rgba(255, 0, 0, 0.6); +}
\ No newline at end of file diff --git a/modules-available/usblockoff/templates/server-prop-bool.html b/modules-available/usblockoff/templates/server-prop-bool.html new file mode 100644 index 00000000..de7c990a --- /dev/null +++ b/modules-available/usblockoff/templates/server-prop-bool.html @@ -0,0 +1,16 @@ +<div class="list-group-item"> + <div class="row"> + <div class="col-md-3"><label for="prop-{{property}}">{{title}}</label></div> + <div class="col-md-7"> + <input class="settings-bs-switch" id="prop-{{property}}" type="checkbox" name="prop-{{property}}" value="1" + {{#currentvalue}}checked{{/currentvalue}}> + </div> + <div class="col-md-2"> + {{#helptext}} + <a class="btn btn-default" title="{{helptext}}"> + <span class="glyphicon glyphicon-question-sign"></span> + </a> + {{/helptext}} + </div> + </div> +</div>
\ No newline at end of file diff --git a/modules-available/usblockoff/templates/server-prop-dropdown.html b/modules-available/usblockoff/templates/server-prop-dropdown.html new file mode 100644 index 00000000..73a55467 --- /dev/null +++ b/modules-available/usblockoff/templates/server-prop-dropdown.html @@ -0,0 +1,25 @@ +<div class="list-group-item"> + <div class="row"> + <div class="col-md-3"><label for="prop-{{property}}">{{title}}</label></div> + <div class="col-md-7 form-inline"> + <select class="form-control" id="prop-{{property}}" name="prop-{{property}}"> + {{#select_list}} + <option value="{{value}}" {{#active}}selected{{/active}}>{{option}}</option> + {{/select_list}} + </select> + {{#toTextButton}} + <button type="button" class="btn btn-default switch-input"> + <span class="glyphicon glyphicon-pencil"></span> + </button> + {{/toTextButton}} + + </div> + <div class="col-md-2"> + {{#helptext}} + <a class="btn btn-default" title="{{helptext}}"> + <span class="glyphicon glyphicon-question-sign"></span> + </a> + {{/helptext}} + </div> + </div> +</div>
\ No newline at end of file diff --git a/modules-available/usblockoff/templates/server-prop-generic.html b/modules-available/usblockoff/templates/server-prop-generic.html new file mode 100644 index 00000000..3c06585e --- /dev/null +++ b/modules-available/usblockoff/templates/server-prop-generic.html @@ -0,0 +1,16 @@ +<div class="list-group-item"> + <div class="row"> + <div class="col-md-3"><label for="prop-{{property}}">{{title}}</label></div> + <div class="col-md-7"> + <input class="form-control" id="prop-{{property}}" type="{{inputtype}}" name="prop-{{property}}" + value="{{currentvalue}}"> + </div> + <div class="col-md-2"> + {{#helptext}} + <a class="btn btn-default" title="{{helptext}}"> + <span class="glyphicon glyphicon-question-sign"></span> + </a> + {{/helptext}} + </div> + </div> +</div>
\ No newline at end of file diff --git a/modules-available/usblockoff/templates/usb-add-generic-rule.html b/modules-available/usblockoff/templates/usb-add-generic-rule.html new file mode 100644 index 00000000..61e38ebc --- /dev/null +++ b/modules-available/usblockoff/templates/usb-add-generic-rule.html @@ -0,0 +1,203 @@ +<form method="post" action="?do=usblockoff" id="addGenericRuleForm"> + <input type="hidden" name="token" value="{{token}}"> + <!--<input type="hidden" name="action" value="addGenericRule">--> + <input type="hidden" name="action" value="addDevices"> + <input type="hidden" name="rules" value="" id="rules"> + <input type="hidden" name="configid" value="{{configid}}" id="configid"> + + + <div class="panel panel-default"> + <div class="panel-heading">{{lang_generalOptions}}</div> + <div class="panel-body"> + <div class="list-group"> + + {{#settings}} + {{{settingHtml}}} + {{/settings}} + + </div> + </div> + </div> + + <div class="panel panel-default"> + <div class="panel-heading">{{lang_modeOptions}} + <!-- + <input class="settings-bs-switch" id="expert_Switch" type="checkbox" name="expert_Switch" + data-on-text="Expert" data-off-text="Casual" data-size="small"> + --> + </div> + <div class="panel-body"> + <div class="list-group"> + + <div id="casualMode"> + + <div class="list-group-item"> + <div class="row"> + <div class="col-md-3"><label for="casual_selected">{{lang_deviceClasses}}</label></div> + <div class="col-md-7"> + <select class="form-control" id="casual_selected"> + <option value="08:*:*" selected>{{lang_mass-storage}}</option> + <option value="03:*:*">{{lang_hid}}</option> + <option value="09:*:*">{{lang_hub}}</option> + <option value="07:*:*">{{lang_printer}}</option> + <option value="01:*:*">{{lang_audio}}</option> + <option value="*:*:*">{{lang_all-devices}}</option> + </select> + </div> + <div class="col-md-2"> + <a class="btn btn-default" title="{{lang_classes-helptext}}"> + <span class="glyphicon glyphicon-question-sign"></span> + </a> + </div> + </div> + </div> + + <div class="list-group-item"> + <div class="row"> + <div class="col-md-3"><label for="contains">{{lang_contains}}</label></div> + <div class="col-md-7"> + <input class="settings-bs-switch" id="contains" type="checkbox" value="1" checked + data-size="small"> + </div> + <div class="col-md-2"> + <a class="btn btn-default" title="{{lang_contains-helptext}}"> + <span class="glyphicon glyphicon-question-sign"></span> + </a> + </div> + </div> + </div> + + </div> + + <!-- + <div id="expertMode" style="display: none;"> + + <div class="list-group-item"> + <div class="row"> + <div class="col-md-3"><label>{{lang_operator}}</label></div> + <div class="col-md-7"> + <select class="form-control" id="expert_selected"> + <option value="all-of">{{lang_all-of}}</option> + <option value="one-of">{{lang_one-of}}</option> + <option value="none-of">{{lang_none-of}}</option> + <option value="equals" selected>{{lang_equals}}</option> + <option value="equals-ordered">{{lang_equals-ordered}}</option> + </select> + </div> + <div class="col-md-2"> + <a class="btn btn-default" title="{{lang_operator-helptext}}"> + <span class="glyphicon glyphicon-question-sign"></span> + </a> + </div> + </div> + </div> + + <div class="list-group-item"> + <div class="row"> + <div class="col-md-3"><label>{{lang_deviceClass}}</label></div> + <div class="col-md-7"> + <input class="form-control" type="input" id="input_deviceClass" + value=""> + </div> + <div class="col-md-2"> + <a class="btn btn-default" title="{{lang_deviceClass-helptext}}"> + <span class="glyphicon glyphicon-question-sign"></span> + </a> + </div> + </div> + </div> + + <div class="list-group-item"> + <div class="row"> + <div class="col-md-3"><label>{{lang_deviceSubClass}}</label></div> + <div class="col-md-7"> + <input class="form-control" type="input" id="input_deviceSubClass" + value=""> + </div> + <div class="col-md-2"> + <a class="btn btn-default" title="{{lang_deviceSubClass-helptext}}"> + <span class="glyphicon glyphicon-question-sign"></span> + </a> + </div> + </div> + </div> + + <div class="list-group-item"> + <div class="row"> + <div class="col-md-3"><label>{{lang_deviceProtocol}}</label></div> + <div class="col-md-7"> + <input class="form-control" type="input" id="input_deviceProtocol" + value=""> + </div> + <div class="col-md-2"> + <a class="btn btn-default" title="{{lang_deviceProtocol-helptext}}"> + <span class="glyphicon glyphicon-question-sign"></span> + </a> + </div> + </div> + </div> + + </div> +--> + </div> + </div> + </div> + + <div class="pull-right"> + <a href="?do=usblockoff&show=edit-config&configid={{configid}}" class="btn btn-default">Cancel</a> + + <button id="addButton" class="btn btn-primary" type="submit" onclick="addRule();"> + <span class="glyphicon glyphicon-floppy-disk"></span> {{lang_save}} + </button> + </div> + +</form> + +<script type="text/javascript"> + document.addEventListener("DOMContentLoaded", function() { + $('a.btn[title]').tooltip({placement: "auto", html: true}); + + var contains = true; + var c = $('#contains'); + c.bootstrapSwitch(); + c.on('switchChange.bootstrapSwitch', function(event, state) { + contains = state; + }); + }); + + /* + var s = $('#expert_Switch'); + var mode = "casual"; + s.bootstrapSwitch(); + s.parent().parent().addClass('pull-right'); + s.parent().parent().css("margin", "-5px"); + + s.on('switchChange.bootstrapSwitch', function(event, state) { + if (state) { + // Expert mode. + $('#casualMode').hide(); + $('#expertMode').show(); + mode = "expert"; + } else { + // Casual mode. + $('#expertMode').hide(); + $('#casualMode').show(); + mode = "casual"; + } + }); + */ + + function addRule() { + var rules = []; + var rule = {}; + rule['target'] = $('#prop-action').val(); + rule['id'] = 0; + var attribute = {}; + attribute['prop'] = 'with-interface'; + attribute['value'] = $('#casual_selected option:selected').val(); + rule['attributes'] = []; + rule['attributes'].push(attribute); + rules.push(rule); + $('#rules').val(JSON.stringify(rules)); + } +</script>
\ No newline at end of file diff --git a/modules-available/usblockoff/templates/usb-configuration-table.html b/modules-available/usblockoff/templates/usb-configuration-table.html new file mode 100644 index 00000000..dc8254df --- /dev/null +++ b/modules-available/usblockoff/templates/usb-configuration-table.html @@ -0,0 +1,147 @@ +<div class="container-fluid"> + <div class="row"> + <div class="col-md-12"> + <div class="page-header"> + <h1>{{lang_usb-lock-off}}</h1> + </div> + </div> + </div> +<!-- + <div class="row"> + <div class="col-md-12"> + <table id="configurationTable" class="table table-condensed table-hover stupidtable"> + <thead> + <tr> + <th data-sort="string">{{lang_serverName}}</th> + <th>{{lang_ruleInfoTODO}}</th> + <th>{{lang_edit}}</th> + <th>{{lang_delete}}</th> + </tr> + </thead> + <tbody> + {{#config_list}} + <tr> + <td data-sort-value="{{config_name}}">{{config_name}}</td> + <td>TODO: Show Rule information here</td> + <td> + <a class="btn btn-xs btn-info" href="?do=usblockoff&show=edit-config&configid={{config_id}}"> + <span class="glyphicon glyphicon-edit"></span> + </a> + </td> + <td> + <a class="btn btn-xs btn-danger" onclick="deleteConfig(event, {{config_id}});"> + <span class="glyphicon glyphicon-trash"></span> + </a> + </td> + </tr> + {{/config_list}} + </tbody> + </table> + <div class="buttonbar text-right"> + <a class="btn btn-success" href="?do=usblockoff&show=edit-config&configid=new-default"> + <span class="glyphicon glyphicon-plus"></span> + {{lang_configuration}} + </a> + </div> + </div> + </div> --> +</div> + + +<ul class="nav nav-tabs"> + <li class="active"><a data-toggle="tab" href="#configTableMenu">{{lang_editConfig}}</a></li> + <li><a data-toggle="tab" href="#assignMenu">{{lang_assingMenu}}</a></li> +</ul> + +<div class="tab-content"> + <div id="configTableMenu" class="tab-pane fade in active"> + <div class="panel panel-default"> + <div class="panel-heading">{{lang_configurationTable}}</div> + <div class="panel-body"> + <div class="list-group"> + + <div class="row"> + <div class="col-md-12"> + <table id="configurationTable" class="table table-condensed table-hover stupidtable fixedTableLayout"> + <thead> + <tr> + <th style="width: 5%" class="tableWrapBreakWord" data-sort="int">ID</th> + <th style="width: 35%" class="tableWrapBreakWord" data-sort="string">{{lang_configName}}</th> + <th style="width: 40%" class="tableWrapBreakWord">{{lang_description}}</th> + <th style="width: 10%" class="tableWrapBreakWord">{{lang_edit}}</th> + <th style="width: 10%" class="tableWrapBreakWord">{{lang_delete}}</th> + </tr> + </thead> + <tbody> + {{#config_list}} + <tr> + <td class="tableWrapBreakWord">{{config_id}}</td> + <td class="tableWrapBreakWord" data-sort-value="{{config_name}}">{{config_name}}</td> + <td class="tableWrapBreakWord">{{config_desc}}</td> + <td class="tableWrapBreakWord"> + <a class="btn btn-xs btn-info" href="?do=usblockoff&show=edit-config&configid={{config_id}}"> + <span class="glyphicon glyphicon-edit"></span> + </a> + </td> + <td class="tableWrapBreakWord"> + <a class="btn btn-xs btn-danger" onclick="deleteConfig(event, {{config_id}});"> + <span class="glyphicon glyphicon-trash"></span> + </a> + </td> + </tr> + {{/config_list}} + </tbody> + </table> + <div class="buttonbar text-right"> + <a class="btn btn-success" href="?do=usblockoff&show=edit-config&configid=new-default"> + <span class="glyphicon glyphicon-plus"></span> + {{lang_configuration}} + </a> + </div> + </div> + </div> + + </div> + </div> + </div> + </div> + <div id="assignMenu" class="tab-pane fade"> + <div class="panel panel-default"> + <div class="panel-heading">{{lang_configurationTable}}</div> + <div class="panel-body"> + <div class="list-group"> + + <h3>Work in progress ...</h3> + <p>Todo: Implement this.</p> + <p>Or not.</p> + <p>¯\_(ツ)_/¯</p> + + </div> + </div> + </div> + </div> +</div> + + +<script> + function deleteConfig(event, id) { + event.preventDefault(); + + BootstrapDialog.confirm({ + title: '{{lang_delete}}', + message: '{{lang_deleteConfigMessage}}', + type: BootstrapDialog.TYPE_DANGER, // <-- Default value is BootstrapDialog.TYPE_PRIMARY + closable: false, // <-- Default value is false + draggable: false, // <-- Default value is false + btnCancelLabel: '{{lang_cancel}}', // <-- Default value is 'Cancel', + btnOKLabel: '<span class="glyphicon glyphicon-trash"></span> {{lang_delete}}', // <-- Default value is 'OK', + btnOKClass: 'btn-danger', // <-- If you didn't specify it, dialog type will be used, + callback: function (result) { + if (result) { + url = "?do=usblockoff&action=deleteConfig&id=" + id; + window.location = url; + } + } + }); + } +</script>
\ No newline at end of file diff --git a/modules-available/usblockoff/templates/usb-daemon-config.html b/modules-available/usblockoff/templates/usb-daemon-config.html new file mode 100644 index 00000000..be8c903c --- /dev/null +++ b/modules-available/usblockoff/templates/usb-daemon-config.html @@ -0,0 +1,26 @@ +<div class="panel panel-default"> + <div class="panel-heading">usbugard-daemon.conf</div> + <div class="panel-body"> + <div class="list-group"> + + {{#list}} + <div class="list-group-item"> + <div class="row"> + <div class="col-sm-3"> + <label>{{name}}</label> + </div> + <div class="col-sm-7"> + <input class="form-control" name="{{name}}" id="{{name}}" value="{{value}}"> + </div> + <div class="col-sm-2"> + <a class="btn btn-default" title="{{helptext}}"> + <span class="glyphicon glyphicon-question-sign"></span> + </a> + </div> + </div> + </div> + {{/list}} + + </div> + </div> +</div>
\ No newline at end of file diff --git a/modules-available/usblockoff/templates/usb-device-list.html b/modules-available/usblockoff/templates/usb-device-list.html new file mode 100644 index 00000000..2c26996b --- /dev/null +++ b/modules-available/usblockoff/templates/usb-device-list.html @@ -0,0 +1,180 @@ +<form method="post" action="?do=usblockoff" id="addDevicesForm"> + <input type="hidden" name="token" value="{{token}}"> + <input type="hidden" name="action" value="addDevices"> + <input type="hidden" name="rules" value="" id="rules"> + <input type="hidden" name="configid" value="{{configid}}" id="configid"> + + <div class="input-group" id="search"> + <span class="input-group-addon"><i class="glyphicon glyphicon-search"></i></span> + <input type="text" id="myInput" class="form-control" onkeyup="search()" placeholder="Search for .." + style="font-size: 16px;"/> + <span class="input-group-addon" style="width:0px; padding-left:0px; padding-right:0px; border:none;"></span> + <select class="form-control" id="searchFor" style="font-size: 16px;" onchange="search()"> + <option value="0" select>Name</option> + <option value="1">Date / Time</option> + <option value="2">User Information</option> + <option value="3">USB Information</option> + <option value="4">Rules Information</option> + </select> + </div> + + <div style="max-height: 800px; overflow-x: auto;"> + <table class="table table-hover" id="myTable"> + <thead> + <tr> + <th width="1" style="text-align: center;">Name</th> + <th width="1" style="text-align: center;">Time</th> + <th width="1">User Info</th> + <th width="1">USB Info</th> + <th width="1">Rule Info</th> + </tr> + </thead> + {{#list}} + <input type="hidden" id="{{uid}}-prop-name" value="{{name}}"> + <input type="hidden" id="{{uid}}-prop-id" value="{{id}}"> + <input type="hidden" id="{{uid}}-prop-serial" value="{{serial}}"> + <input type="hidden" id="{{uid}}-prop-via-port" value="{{via-port}}"> + <input type="hidden" id="{{uid}}-prop-hash" value="{{hash}}"> + <input type="hidden" id="{{uid}}-prop-parent-hash" value="{{parent-hash}}"> + <input type="hidden" id="{{uid}}-prop-with-interface" value="{{with-interface}}"> + + <tbody onclick="clickRow(this, {{uid}});" id="{{uid}}"> + <tr> + <td nowrap align="center" style="vertical-align: middle;"><label>{{name}}</label></td> + <td nowrap align="center" style="vertical-align: middle;">{{time}}<br>{{date}}</td> + <td nowrap><font size="0">User: {{user}}<br>Location: {{location}}<br>Client: {{clientip}}</font></td> + <td nowrap><font size="0">id: {{id}}<br>Serial: {{serial}}<br>via-port: {{via-port}}</font></td> + <td nowrap><font size="0">hash: {{hash}}<br>parent-hash: {{parent-hash}}<br>with-interface: + {{with-interface}}</font></td> + </tr> + </tbody> + {{/list}} + </table> + </div> + + <div class="panel panel-default"> + <div class="panel-heading">{{lang_ruleOptions}}</div> + <div class="panel-body"> + <div class="list-group"> + <div id="settingsDIV"> + {{#settings}} + {{{settingHtml}}} + {{/settings}} + </div> + </div> + </div> + </div> + <div class="pull-right"> + <a href="?do=usblockoff&show=edit-config&configid={{configid}}" class="btn btn-default">Cancel</a> + <button id="addButton" class="btn btn-primary" type="submit" onclick="addDevices();"> + <span style="margin-right: 5px;" class="glyphicon glyphicon-floppy-disk"></span> + <span id="addButtonText"></span> + </button> + </div> + +</form> + +<script type="text/javascript"> + + document.addEventListener("DOMContentLoaded", function(event) { + $('a.btn[title]').tooltip(); + $('.settings-bs-switch').bootstrapSwitch({size: 'small'}); + countSelected(); + }); + + function clickRow(tbody, uid) { + $(tbody).toggleClass('selected'); + countSelected(); + } + + function countSelected() { + var numSelected = $('.selected').length; + if (numSelected == 0) { + $('#addButton').prop('disabled', true); + //$('#addButton').addClass('disabled'); + } else { + $('#addButton').prop('disabled', false); + //$('#addButton').removeClass('disabled'); + } + if (numSelected == 1) { + $('#addButtonText').text(' ' + numSelected + ' {{lang_device}}'); + } else { + $('#addButtonText').text(' ' + numSelected + ' {{lang_devices}}'); + } + } + + function search() { + var searchForIndex = $('#searchFor').val(); + // Declare variables + var input, filter, table, tr, td, i; + input = document.getElementById("myInput"); + filter = input.value.toUpperCase(); + table = document.getElementById("myTable"); + tr = table.getElementsByTagName("tr"); + + // Loop through all table rows, and hide those who don't match the search query + for (i = 0; i < tr.length; i++) { + td = tr[i].getElementsByTagName("td")[searchForIndex]; + if (td) { + if (td.innerHTML.toUpperCase().indexOf(filter) > -1) { + tr[i].style.display = ""; + } else { + tr[i].style.display = "none"; + } + } + } + } + + function addDevices() { + var rules = []; + $('.selected').each(function () { + var rule = {}; + rule['target'] = $('#prop-action').val(); + rule['id'] = 0; + rule['attributes'] = []; + + var selected = $(this); + $('#settingsDIV .settings-bs-switch').each(function () { + if ($(this).is(":checked")) { + var attr = {}; + attr['prop'] = $(this).attr('name').substring(5); + attr['value'] = $('#' + $(selected).attr('id') + '-' + $(this).attr('name')).val(); + + rule['attributes'].push(attr); + } + }); + rules.push(rule); + }); + $('#rules').val(JSON.stringify(rules)); + } +</script> + +<style type='text/css'> + .selected { + background-color: rgba(124, 252, 0, 0.152); + } + + .selected tr:hover { + background-color: rgba(124, 252, 0, 0.252) !important; + } + + #myTable { + border-collapse: collapse; /* Collapse borders */ + width: 100%; /* Full-width */ + border: 1px solid #ddd; /* Add a grey border */ + } + + #myTable th, #myTable td { + padding: 12px; /* Add padding */ + } + + #myTable tr { + /* Add a bottom border to all table rows */ + border-bottom: 1px solid #ddd; + } + + #myTable tr.header, #myTable tr:hover { + /* Add a grey background color to the table header and on hover */ + background-color: #f1f1f1; + } +</style> diff --git a/modules-available/usblockoff/templates/usb-edit-config.html b/modules-available/usblockoff/templates/usb-edit-config.html new file mode 100644 index 00000000..1cd24ce7 --- /dev/null +++ b/modules-available/usblockoff/templates/usb-edit-config.html @@ -0,0 +1,87 @@ +<form method="post" action="?do=usblockoff" id="configForm"> + <input type="hidden" name="token" value="{{token}}"> + <input type="hidden" name="action" id="formAction" value="updateConfig"> + <input type="hidden" name="id" value="{{configid}}" id="configID"> + + <div class="panel panel-default"> + <div class="panel-heading">{{lang_general}}</div> + <div class="panel-body"> + <div class="list-group"> + + <div class="list-group-item"> + <div class="row"> + <div class="col-sm-3"> + <label for="configName">{{lang_configName}}</label> + </div> + <div class="col-sm-7"> + <input type="text" class="form-control" name="configName" id="configName" value="{{configName}}"> + </div> + <div class="col-sm-2"> + <a class="btn btn-default" title="{{lang_configName_helptext}}"> + <span class="glyphicon glyphicon-question-sign"></span> + </a> + </div> + </div> + <br> + <div class="row"> + <div class="col-sm-3"> + <label for="configDesc">{{lang_description}}</label> + </div> + <div class="col-sm-7"> + <input class="form-control" name="configDesc" id="configDesc" value="{{configDesc}}"> + </div> + <div class="col-sm-2"> + <a class="btn btn-default" title="{{lang_configDesc_helptext}}"> + <span class="glyphicon glyphicon-question-sign"></span> + </a> + </div> + </div> + </div> + + </div> + </div> + </div> + + <ul class="nav nav-tabs"> + <li class="active"><a data-toggle="tab" href="#rulesConfigMenu">{{lang_rulesConfig}}</a></li> + <li><a data-toggle="tab" href="#deamonConfigMenu">{{lang_daemonConfig}}</a></li> + </ul> + + <div class="tab-content"> + <div id="rulesConfigMenu" class="tab-pane fade in active"> + <div id="rulesConfigDIV"> + {{{rulesConfigHtml}}} + </div> + </div> + <div id="deamonConfigMenu" class="tab-pane fade"> + <div id="daemonConfigDIV"> + {{{daemonConfigHtml}} + </div> + </div> + </div> + + <div class="pull-right"> + <a href="?do=usblockoff" class="btn btn-default">Cancel</a> + <button type="submit" id="configFormButton" class="btn btn-primary"> + <span class="glyphicon glyphicon-floppy-disk"></span> {{lang_save}} + </button> + </div> + +</form> + + + +<script type="application/javascript"> + document.addEventListener("DOMContentLoaded", function () { + $('form').submit(function () { + var input = $("#configName"); + var name = $.trim(input.val()); + if (!name) { + input.addClass("missingInput"); + return false; + } + }); + }); +</script> + + diff --git a/modules-available/usblockoff/templates/usb-edit-rule.html b/modules-available/usblockoff/templates/usb-edit-rule.html new file mode 100644 index 00000000..ef5d48d5 --- /dev/null +++ b/modules-available/usblockoff/templates/usb-edit-rule.html @@ -0,0 +1,170 @@ +<form method="post" action="?do=usblockoff" id="editRuleForm"> + <input type="hidden" name="token" value="{{token}}"> + <input type="hidden" name="action" value="editRule"> + <input type="hidden" name="configid" value="{{configid}}" id="configid"> + <input type="hidden" name="ruleid" value="{{ruleid}}" id="ruleid"> + <input type="hidden" name="attributes" value="" id="attributes"> + + <div class="panel panel-default"> + <div class="panel-heading">{{lang_editRule}}</div> + <div class="panel-body"> + <div class="list-group"> + + {{#attributes}} + {{{attributesHtml}}} + {{/attributes}} + + </div> + </div> + </div> + + <div class="pull-right"> + <a href="?do=usblockoff&show=edit-config&configid={{configid}}" class="btn btn-default">Cancel</a> + + <button id="addButton" class="btn btn-primary" type="submit"> + <span class="glyphicon glyphicon-floppy-disk"></span> {{lang_save}} + </button> + </div> + +</form> + +<script> + // JSON with the vendor / product lists. + var idArray = {{{usbJson}}}; + + // Page loaded event listener. + document.addEventListener("DOMContentLoaded", function() { + // Init the tooltips. + $('a.btn[title]').tooltip({placement: "auto", html: true}); + + // Add the click event to the switch input button. + $('.switch-input').click(function () { + var oldInput = $(this).parent().find(':input').not('button'); + var value = oldInput.val(); + + // Switch case: Either input type=text or select. + if (oldInput[0].localName == 'select') { + // Replace it with an input type=text field. + oldInput.replaceWith($('<input class="form-control" type="text">').attr('name', oldInput.attr('name')).attr('id', oldInput.attr('id')).val(value)); + + if (oldInput.attr('id') === 'prop-vid') { + // Adds the onchange event for the vendor input field. + createVendorOnChange(); + } else if (oldInput.attr('id') === 'prop-pid') { + // Adds the onchange event for the products input field. + createProductOnChange(); + } + + // Change the buttons glyphicon. + $(this).parent().find('button span').removeClass('glyphicon-pencil').addClass('glyphicon glyphicon-list-alt'); + } else if (oldInput[0].localName == 'input') { + // Replace it with a select input field. + oldInput.replaceWith($('<select class="form-control"><option id="option" selected></option></select>').attr('name', oldInput.attr('name')).attr('id', oldInput.attr('id'))); + $('#option').val(value).text("unknown"); + + if (oldInput.attr('id') === 'prop-vid') { + // Initializes (fills) the vendor select dropdown box. + fillVendors(); + + // Adds the onchange event for the vendor input dropdown. + createVendorOnChange(); + } else if (oldInput.attr('id') === 'prop-pid') { + // Initializes (fills) the product select dropdown box. + fillProducts(); + + // Adds the onchange event for the products input dropdown. + createProductOnChange(); + } + + // Change the buttons glyphicon. + $(this).parent().find('button span').removeClass('glyphicon-list-alt').addClass('glyphicon-pencil'); + } + }); + + // Create the change event for all props. + $('[id^="prop-"]').change(function () { + updateAttributes(); + }); + + // Initializes (fills) the dropdown boxes and creates the change events. + fillVendors(); + fillProducts(); + updateAttributes(); + createVendorOnChange(); + createProductOnChange(); + }); + + // Fills the vendor dropdown box with options. + function fillVendors() { + var vendorid = $('#prop-vid'); + var value = vendorid.val(); + vendorid.empty(); + + // Fill the select with options. + $.each(idArray, function(key, value) { + vendorid.append($('<option></option>').val(key).html(value['name'])); + }); + + // If the value exist in the array. + if (value in idArray) { + // Assign vendor value. + vendorid.val(value); + } + } + + // Creates the onChange event for the vendor dropdown. + function createVendorOnChange() { + var vendorid = $('#prop-vid'); + vendorid.change(function() { + fillProducts(); + updateAttributes(); + }); + } + + // Fills the product dropdown box with options. + function fillProducts() { + var vendorid = $('#prop-vid'); + var productid = $('#prop-pid'); + var value = productid.val(); + productid.empty(); + + // If the key exists load the option list. + if (vendorid.val() in idArray) { + $.each(idArray[vendorid.val()]['products'], function(key, value) { + productid.append($('<option></option>').val(key).html(value)); + }); + + // IF the value exist in the array. + if (value in idArray[vendorid.val()]['products']) { + // Assign product value. + productid.val(value); + } + } + } + + // Creates the onChange event for the products dropdown. + function createProductOnChange() { + var productid = $('#prop-pid'); + + productid.change(function() { + updateAttributes(); + }); + } + + // Updates the attributes variable which is send to the page.php on save. + function updateAttributes() { + var attributes = []; + + // Add all attributes in an array. + $.each($('[id^="prop-"]'), function () { + var attr = {}; + attr['prop'] = $(this).attr('id'); + attr['value'] = $(this).val(); + + attributes.push(attr); + }); + + // Save the array as JSON in the attributes hidden input. + $('#attributes').val(JSON.stringify(attributes)); + } +</script>
\ No newline at end of file diff --git a/modules-available/usblockoff/templates/usb-rules-config.html b/modules-available/usblockoff/templates/usb-rules-config.html new file mode 100644 index 00000000..eb6bea59 --- /dev/null +++ b/modules-available/usblockoff/templates/usb-rules-config.html @@ -0,0 +1,122 @@ +<div class="panel panel-default"> + <div class="panel-heading"> + {{lang_rules}} + + <div class="pull-right"> + <span style="color: red;font-weight: bold;opacity: 0.75;"> + <span class="glyphicon glyphicon-alert" style="margin-right: 5px;"></span> + {{lang_firstMatchingRuleCounts}} + </span> + </div> + <div class="clearfix"></div> + + </div> + + <div class="panel-body" id="casualRules"> + <div class="list-group"> + + <input type="hidden" name="rules" value="" id="rules"> + <table id="rulesTable" class="table table-condensed table-hover fixedTableLayout"> + <thead> + <tr> + <th style="width: 10%" data-sort="string"><span class="glyphicon glyphicon-th-list"></span></th> + <th style="width: 20%" class="tableWrapBreakWord" width="120">{{lang_action}}</th> + <th style="width: 50%" class="tableWrapBreakWord">{{lang_attributes}}</th> + <th style="width: 10%" class="tableWrapBreakWord">{{lang_edit}}</th> + <th style="width: 10%" class="tableWrapBreakWord">{{lang_delete}}</th> + </tr> + </thead> + <tbody id="tableBody" style="overflow: auto;"> + + + {{#rules}} + <tr id="{{id}}"> + <td class="drag-handler" style="cursor: pointer;text-align: center; vertical-align: middle;"> + <span class="glyphicon glyphicon-th-list"></span> + </td> + <td class="tableWrapBreakWord" style="vertical-align: middle; text-align: center;">{{target}}</td> + <td class="tableWrapBreakWord"> + {{#attributes}} + {{prop}}: {{value}}<br> + {{/attributes}} + {{#hasoverload}} + <a class="label label-default overload" style="background-color: #337ab7;" title="{{attributes_overload}}">+{{num_overload}}</a> + {{/hasoverload}} + </td> + <td class="tableWrapBreakWord"> + <a class="btn btn-xs btn-info" href="?do=usblockoff&show=edit-rule&ruleid={{id}}&configid={{configid}}"> + <span class="glyphicon glyphicon-edit"></span> + </a> + </td> + <td class="tableWrapBreakWord"> + <a class="btn btn-xs btn-danger" href="?do=usblockoff&action=deleteRule&id={{id}}&configid={{configid}}"> + <span class="glyphicon glyphicon-trash"></span> + </a> + </td> + </tr> + {{/rules}} + + </tbody> + </table> + <div class="pull-right"> + <a class="btn btn-success {{#newConfig}}disabled{{/newConfig}}" + {{^newConfig}}href="?do=usblockoff&show=add-generic-rule&configid={{configid}}"{{/newConfig}} + {{#newConfig}}title="{{lang_disabledButtons_helptext}}" style="pointer-events: auto;"{{/newConfig}}> + <span class="glyphicon glyphicon-plus"></span> + <span>{{lang_genericRule}}</span> + </a> + <a class="btn btn-success {{#newConfig}}disabled{{/newConfig}}" + {{^newConfig}}href="?do=usblockoff&show=add-devices&configid={{configid}}"{{/newConfig}} + {{#newConfig}}title="{{lang_disabledButtons_helptext}}" style="pointer-events: auto;"{{/newConfig}}> + <span class="glyphicon glyphicon-plus"></span> + <span>{{lang_devices}}</span> + </a> + </div> + </div> + + </div> +</div> +<script type="text/javascript"> + var rules = []; + var oldIndex = -1; + document.addEventListener("DOMContentLoaded", function(event) { + $('a.btn[title]').tooltip({placement: "auto", html: true}); + + $("#tableBody tr").each(function() { + rules.push(Number(this.id)); + }); + $('#rules').val(JSON.stringify(rules)); + + $('#tableBody').sortable({ + opacity: 0.8, + handle: '.drag-handler', + start: function(evt, ui) { + oldIndex = ui.item.index(); + ui.placeholder.css("visibility", "visible"); + ui.placeholder.css("opacity", "0.152"); + ui.placeholder.css("background-color", "LawnGreen"); + }, + stop: function(evt, ui) { + updateTable(ui.item.index()); + } + }); + + $('a.overload').tooltip({placement: "auto", html: true}); + }); + + + // Called after a drag & drop event is finished. + function updateTable(new_index) { + var old_i = -1; + if (oldIndex === -1) { + return; + } else { + old_i = oldIndex; + oldIndex = -1; + } + var rule = rules[old_i]; + rules.splice(old_i, 1); + rules.splice(new_index, 0, rule); + $('#rules').val(JSON.stringify(rules)); + } +</script>
\ No newline at end of file |