diff options
Diffstat (limited to 'modules-available/usblockoff/page.inc.php')
-rw-r--r-- | modules-available/usblockoff/page.inc.php | 704 |
1 files changed, 704 insertions, 0 deletions
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; + } +} |