%PDF- %PDF-
Direktori : /backups/router/usr/local/opnsense/mvc/app/controllers/OPNsense/Firewall/Api/ |
Current File : //backups/router/usr/local/opnsense/mvc/app/controllers/OPNsense/Firewall/Api/AliasController.php |
<?php /** * Copyright (C) 2018 Deciso B.V. * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * */ namespace OPNsense\Firewall\Api; use OPNsense\Base\ApiMutableModelControllerBase; use OPNsense\Base\UserException; use OPNsense\Core\Backend; use OPNsense\Core\Config; use OPNsense\Firewall\Category; /** * @package OPNsense\Firewall */ class AliasController extends ApiMutableModelControllerBase { protected static $internalModelName = 'alias'; protected static $internalModelClass = 'OPNsense\Firewall\Alias'; /** * search aliases * @return array search results * @throws \ReflectionException */ public function searchItemAction() { $type = $this->request->get('type'); $category = $this->request->get('category'); $filter_funct = function ($record) use ($type, $category) { $match_type = empty($type) || in_array($record->type, $type); $match_cat = empty($category) || array_intersect(explode(',', $record->categories), $category); return $match_type && $match_cat; }; $result = $this->searchBase( "aliases.alias", ['enabled', 'name', 'description', 'type', 'content', 'current_items', 'last_updated'], "name", $filter_funct ); /** * remap some source data from the model as searchBase() is not able to distinct this. * - category uuid's * - unix group id's to names in content fields */ $categories = []; $types = []; foreach ($this->getModel()->aliases->alias->iterateItems() as $key => $alias) { $categories[$key] = !empty((string)$alias->categories) ? explode(',', (string)$alias->categories) : []; $types[$key] = (string)$alias->type; } $group_mapping = null; foreach ($result['rows'] as &$record) { $record['categories_uuid'] = $categories[$record['uuid']]; if ($types[$record['uuid']] == 'authgroup') { if ($group_mapping === null) { $group_mapping = $this->listUserGroupsAction(); } $groups = []; foreach (explode(',', $record['content']) as $grp) { if (isset($group_mapping[$grp])) { $groups[] = $group_mapping[$grp]['name']; } } $record['content'] = implode(',', $groups); } } return $result; } /** * list categories and usage * @return array */ public function listCategoriesAction() { $response = ['rows' => []]; $catcount = []; foreach ($this->getModel()->aliases->alias->iterateItems() as $alias) { if (!empty((string)$alias->categories)) { foreach (explode(',', (string)$alias->categories) as $cat) { if (!isset($catcount[$cat])) { $catcount[$cat] = 0; } $catcount[$cat] += 1; } } } $mdlcat = new Category(); foreach ($mdlcat->categories->category->iterateItems() as $key => $category) { $response['rows'][] = [ "uuid" => $key, "name" => (string)$category->name, "color" => (string)$category->color, "used" => isset($catcount[$key]) ? $catcount[$key] : 0 ]; } array_multisort(array_column($response['rows'], "name"), SORT_ASC, SORT_NATURAL, $response['rows']); return $response; } /** * Update alias with given properties * @param string $uuid internal id * @return array save result + validation output * @throws \OPNsense\Base\ValidationException when field validations fail * @throws \ReflectionException when not bound to model */ public function setItemAction($uuid) { $node = $this->getModel()->getNodeByReference('aliases.alias.' . $uuid); $old_name = $node != null ? (string)$node->name : null; if ( $old_name !== null && $this->request->isPost() && $this->request->hasPost("alias") && !empty($this->request->getPost("alias")['name']) ) { $new_name = $this->request->getPost("alias")['name']; if ($new_name != $old_name) { // replace aliases, setBase() will synchronise the changes to disk $this->getModel()->refactor($old_name, $new_name); } } return $this->setBase("alias", "aliases.alias", $uuid); } /** * Add new alias and set with attributes from post * @return array save result + validation output * @throws \OPNsense\Base\ModelException when not bound to model * @throws \OPNsense\Base\ValidationException when field validations fail * @throws \ReflectionException when not bound to model */ public function addItemAction() { return $this->addBase("alias", "aliases.alias"); } /** * Retrieve alias settings or return defaults for new one * @param $uuid item unique id * @return array alias content * @throws \ReflectionException when not bound to model */ public function getItemAction($uuid = null) { $response = $this->getBase("alias", "aliases.alias", $uuid); $selected_aliases = array_keys($response['alias']['content']); foreach ($this->getModel()->aliasIterator() as $alias) { if (!in_array($alias['name'], $selected_aliases)) { $response['alias']['content'][$alias['name']] = [ "selected" => 0, "value" => $alias['name'] ]; } // append descriptions $response['alias']['content'][$alias['name']]['description'] = $alias['description']; } return $response; } /** * find the alias uuid by name * @param $name alias name * @return array uuid * @throws \ReflectionException */ public function getAliasUUIDAction($name) { $node = $this->getModel(); foreach ($node->aliases->alias->iterateItems() as $key => $alias) { if ((string)$alias->name == $name) { return array('uuid' => $key); } } return array(); } /** * Delete alias by uuid, save contents to tmp for removal on apply * @param string $uuid internal id * @return array save status * @throws \OPNsense\Base\ValidationException when field validations fail * @throws \ReflectionException when not bound to model * @throws \OPNsense\Base\UserException when unable to delete */ public function delItemAction($uuid) { Config::getInstance()->lock(); $node = $this->getModel()->getNodeByReference('aliases.alias.' . $uuid); if ($node != null) { $uses = $this->getModel()->whereUsed((string)$node->name); if (!empty($uses)) { $message = ""; foreach ($uses as $key => $value) { $message .= htmlspecialchars(sprintf("\n[%s] %s", $key, $value), ENT_NOQUOTES | ENT_HTML401); } $message = sprintf(gettext("Cannot delete alias. Currently in use by %s"), $message); throw new \OPNsense\Base\UserException($message, gettext("Alias in use")); } } return $this->delBase("aliases.alias", $uuid); } /** * toggle status * @param string $uuid id to toggled * @param string|null $enabled set enabled by default * @return array status * @throws \OPNsense\Base\ValidationException when field validations fail * @throws \ReflectionException when not bound to model */ public function toggleItemAction($uuid, $enabled = null) { return $this->toggleBase("aliases.alias", $uuid, $enabled); } /** * list countries and regions * @return array indexed by country code */ public function listCountriesAction() { $result = [ 'EU' => [ 'name' => gettext('Unclassified'), 'region' => 'Europe' ] ]; foreach (explode("\n", file_get_contents('/usr/local/opnsense/contrib/tzdata/iso3166.tab')) as $line) { $line = trim($line); if (strlen($line) > 3 && substr($line, 0, 1) != '#') { $result[substr($line, 0, 2)] = array( "name" => trim(substr($line, 2, 9999)), "region" => null ); } } foreach (explode("\n", file_get_contents('/usr/local/opnsense/contrib/tzdata/zone.tab')) as $line) { if (strlen($line) > 0 && substr($line, 0, 1) == '#') { continue; } $line = explode("\t", $line); if (empty($line[0]) || strlen($line[0]) != 2) { continue; } if (empty($line[2]) || strpos($line[2], '/') === false) { continue; } if (!empty($result[$line[0]]) && empty($result[$line[0]]['region'])) { $result[$line[0]]['region'] = explode('/', $line[2])[0]; } } return $result; } /** * list user groups * @return array user groups */ public function listUserGroupsAction() { $result = []; $cnf = Config::getInstance()->object(); if (isset($cnf->system->group)) { foreach ($cnf->system->group as $group) { $name = (string)$group->name; if ($name != 'all') { $result[(string)$group->gid] = [ "name" => $name, "gid" => (string)$group->gid ]; } } ksort($result); } return $result; } /** * list network alias types * @return array indexed by country alias name */ public function listNetworkAliasesAction() { $result = []; foreach ($this->getModel()->aliases->alias->iterateItems() as $alias) { if (!in_array((string)$alias->type, ['external', 'port'])) { $result[(string)$alias->name] = [ "name" => (string)$alias->name, "description" => (string)$alias->description ]; } } ksort($result); return $result; } /** * reconfigure aliases */ public function reconfigureAction() { if ($this->request->isPost()) { $backend = new Backend(); $backend->configdRun('template reload OPNsense/Filter'); $backend->configdRun("filter reload skip_alias"); $bckresult = json_decode($backend->configdRun("filter refresh_aliases"), true); if (!empty($bckresult['messages'])) { throw new UserException(implode("\n", $bckresult['messages']), gettext("Alias")); } return array("status" => "ok"); } else { return array("status" => "failed"); } } /** * get aliases load stats and table-entries limit */ public function getTableSizeAction() { return json_decode((new Backend())->configdRun('filter diag table_size'), true); } /** * variant on model.getNodes() returning actual field values * @param $parent_node BaseField node to reverse */ private function getRawNodes($parent_node) { $result = array(); foreach ($parent_node->iterateItems() as $key => $node) { if ($node->isContainer()) { $result[$key] = $this->getRawNodes($node); } else { $result[$key] = (string)$node; } } return $result; } /** * export configured aliases */ public function exportAction() { if ($this->request->isGet()) { // return raw, unescaped since this content is intended for direct download $this->response->setContentType('application/json', 'UTF-8'); $this->response->setContent(json_encode($this->getRawNodes($this->getModel()))); } else { throw new UserException("Unsupported request type"); } } /** * import delivered aliases in post variable "data", validate all only commit when fully valid. */ public function importAction() { if ($this->request->isPost()) { $result = array("existing" => 0, "new" => 0, "status" => "failed"); $data = $this->request->getPost("data"); if ( is_array($data) && !empty($data['aliases']) && !empty($data['aliases']['alias']) && is_array($data['aliases']['alias']) ) { Config::getInstance()->lock(); // save into model $uuid_mapping = array(); foreach ($data['aliases']['alias'] as $uuid => $content) { $type = !empty($content['type']) ? $content['type'] : ""; if (is_array($content) && !empty($content['name']) && $type != 'internal') { $node = $this->getModel()->getByName($content['name'], true); if ($node == null) { $node = $this->getModel()->aliases->alias->Add(); $result['new'] += 1; } else { $result['existing'] += 1; } foreach ($content as $prop => $value) { $node->$prop = $value; } $uuid_mapping[$node->getAttribute('uuid')] = $uuid; } } // attach this alias to util class, to avoid recursion issues (aliases used in aliases). \OPNsense\Firewall\Util::attachAliasObject($this->getModel()); // perform validation, record details. foreach ($this->getModel()->performValidation() as $msg) { if (empty($result['validations'])) { $result['validations'] = array(); } $parts = explode('.', $msg->getField()); $uuid = $parts[count($parts) - 2]; $fieldname = $parts[count($parts) - 1]; $result['validations'][$uuid_mapping[$uuid] . "." . $fieldname] = $msg->getMessage(); } // only persist when valid import if (empty($result['validations'])) { $result['status'] = "ok"; $this->save(); } else { $result['status'] = "failed"; Config::getInstance()->unlock(); } } } else { throw new UserException("Unsupported request type"); } return $result; } /** * get geoip settings (and stats) */ public function getGeoIPAction() { $result = array(); if ($this->request->isGet()) { $cnf = Config::getInstance()->object(); $result[static::$internalModelName] = ['geoip' => array()]; $node = $this->getModel()->getNodeByReference('geoip'); if ($node != null) { $result[static::$internalModelName]['geoip'] = $node->getNodes(); } // count aliases that depend on GeoIP data $result[static::$internalModelName]['geoip']['usages'] = 0; foreach ($this->getModel()->aliasIterator() as $alias) { if ($alias['type'] == "geoip") { $result[static::$internalModelName]['geoip']['usages']++; } } if (isset($cnf->system->firmware) && !empty($cnf->system->firmware->mirror)) { // XXX: we might add some attribute in firmware to store subscription status, since we now only store uri $result[static::$internalModelName]['geoip']['subscription'] = strpos($cnf->system->firmware->mirror, 'opnsense-update.deciso.com') !== false; } $result[static::$internalModelName]['geoip']['address_count'] = 0; if (file_exists('/usr/local/share/GeoIP/alias.stats')) { $stats = json_decode(file_get_contents('/usr/local/share/GeoIP/alias.stats'), true); $result[static::$internalModelName]['geoip'] = array_merge( $result[static::$internalModelName]['geoip'], $stats ); } } return $result; } }