summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--modules-available/statistics/inc/devicetype.inc.php1
-rw-r--r--modules-available/usblockoff/api.inc.php230
-rw-r--r--modules-available/usblockoff/config.json4
-rw-r--r--modules-available/usblockoff/inc/default-configs/rules.conf0
-rw-r--r--modules-available/usblockoff/inc/default-configs/usbguard-daemon.conf160
-rw-r--r--modules-available/usblockoff/install.inc.php65
-rw-r--r--modules-available/usblockoff/lang/de/messages.json7
-rw-r--r--modules-available/usblockoff/lang/de/module.json4
-rw-r--r--modules-available/usblockoff/lang/de/rule.json26
-rw-r--r--modules-available/usblockoff/lang/de/template-tags.json61
-rw-r--r--modules-available/usblockoff/lang/en/messages.json7
-rw-r--r--modules-available/usblockoff/lang/en/module.json4
-rw-r--r--modules-available/usblockoff/lang/en/rule.json26
-rw-r--r--modules-available/usblockoff/lang/en/template-tags.json61
-rw-r--r--modules-available/usblockoff/page.inc.php704
-rw-r--r--modules-available/usblockoff/style.css13
-rw-r--r--modules-available/usblockoff/templates/server-prop-bool.html16
-rw-r--r--modules-available/usblockoff/templates/server-prop-dropdown.html25
-rw-r--r--modules-available/usblockoff/templates/server-prop-generic.html16
-rw-r--r--modules-available/usblockoff/templates/usb-add-generic-rule.html203
-rw-r--r--modules-available/usblockoff/templates/usb-configuration-table.html147
-rw-r--r--modules-available/usblockoff/templates/usb-daemon-config.html26
-rw-r--r--modules-available/usblockoff/templates/usb-device-list.html180
-rw-r--r--modules-available/usblockoff/templates/usb-edit-config.html87
-rw-r--r--modules-available/usblockoff/templates/usb-edit-rule.html170
-rw-r--r--modules-available/usblockoff/templates/usb-rules-config.html122
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&amp;show=edit-config&amp;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&amp;show=edit-config&amp;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&amp;show=edit-config&amp;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&amp;show=edit-config&amp;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