diff --git a/plist b/plist index c50e265ec8..c194d3225a 100644 --- a/plist +++ b/plist @@ -199,6 +199,7 @@ /usr/local/opnsense/mvc/app/controllers/OPNsense/CaptivePortal/forms/dialogZone.xml /usr/local/opnsense/mvc/app/controllers/OPNsense/Core/Api/BackupController.php /usr/local/opnsense/mvc/app/controllers/OPNsense/Core/Api/DashboardController.php +/usr/local/opnsense/mvc/app/controllers/OPNsense/Core/Api/DefaultsController.php /usr/local/opnsense/mvc/app/controllers/OPNsense/Core/Api/FirmwareController.php /usr/local/opnsense/mvc/app/controllers/OPNsense/Core/Api/HasyncController.php /usr/local/opnsense/mvc/app/controllers/OPNsense/Core/Api/HasyncStatusController.php @@ -210,6 +211,7 @@ /usr/local/opnsense/mvc/app/controllers/OPNsense/Core/Api/TunablesController.php /usr/local/opnsense/mvc/app/controllers/OPNsense/Core/BackupController.php /usr/local/opnsense/mvc/app/controllers/OPNsense/Core/DashboardController.php +/usr/local/opnsense/mvc/app/controllers/OPNsense/Core/DefaultsController.php /usr/local/opnsense/mvc/app/controllers/OPNsense/Core/FirmwareController.php /usr/local/opnsense/mvc/app/controllers/OPNsense/Core/HaltController.php /usr/local/opnsense/mvc/app/controllers/OPNsense/Core/HasyncController.php @@ -520,6 +522,7 @@ /usr/local/opnsense/mvc/app/library/OPNsense/Core/Backend.php /usr/local/opnsense/mvc/app/library/OPNsense/Core/Config.php /usr/local/opnsense/mvc/app/library/OPNsense/Core/ConfigException.php +/usr/local/opnsense/mvc/app/library/OPNsense/Core/ConfigMaintenance.php /usr/local/opnsense/mvc/app/library/OPNsense/Core/File.php /usr/local/opnsense/mvc/app/library/OPNsense/Core/FileObject.php /usr/local/opnsense/mvc/app/library/OPNsense/Core/SanitizeFilter.php @@ -888,6 +891,7 @@ /usr/local/opnsense/mvc/app/views/OPNsense/CaptivePortal/vouchers.volt /usr/local/opnsense/mvc/app/views/OPNsense/Core/backup_history.volt /usr/local/opnsense/mvc/app/views/OPNsense/Core/dashboard.volt +/usr/local/opnsense/mvc/app/views/OPNsense/Core/defaults.volt /usr/local/opnsense/mvc/app/views/OPNsense/Core/firmware.volt /usr/local/opnsense/mvc/app/views/OPNsense/Core/halt.volt /usr/local/opnsense/mvc/app/views/OPNsense/Core/hasync.volt @@ -1316,6 +1320,7 @@ /usr/local/opnsense/scripts/system/certctl.py /usr/local/opnsense/scripts/system/cpu.py /usr/local/opnsense/scripts/system/crl_fetch.py +/usr/local/opnsense/scripts/system/factory_defaults.php /usr/local/opnsense/scripts/system/flush_config_history /usr/local/opnsense/scripts/system/get_locales.php /usr/local/opnsense/scripts/system/get_timezones.php @@ -2438,7 +2443,6 @@ /usr/local/www/csrf.inc /usr/local/www/diag_authentication.php /usr/local/www/diag_backup.php -/usr/local/www/diag_defaults.php /usr/local/www/fbegin.inc /usr/local/www/firewall_nat.php /usr/local/www/firewall_nat_edit.php diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Core/Api/DefaultsController.php b/src/opnsense/mvc/app/controllers/OPNsense/Core/Api/DefaultsController.php new file mode 100644 index 0000000000..aee8fb21dd --- /dev/null +++ b/src/opnsense/mvc/app/controllers/OPNsense/Core/Api/DefaultsController.php @@ -0,0 +1,133 @@ +hasPrivilege($this->getUserName(), 'user-config-readonly')) { + throw new UserException( + sprintf("User %s denied for write access (user-config-readonly set)", $this->getUserName()) + ); + } + } + + /** + * return defaults + */ + public function getAction() + { + $default_ip = '192.168.1.1'; + if (is_file('/usr/local/etc/config.xml')) { + $cfg = Config::getInstance()->toArrayFromFile('/usr/local/etc/config.xml'); + if (is_array($cfg) && + !empty($cfg['interfaces']) && + !empty($cfg['interfaces']['lan']) && + !empty($cfg['interfaces']['lan']['ipaddr']) + ) { + $default_ip = $cfg['interfaces']['lan']['ipaddr']; + } + } + return ['default_ip' => $default_ip]; + } + + /** + * reset to defaults + */ + public function factoryDefaultsAction() + { + $this->throwReadOnly(); + if (!$this->request->isPost()) { + return ['status' => 'failed']; + } + + /* schedule factory defaults so we can safely respond to the client */ + (new Backend())->configdRun('system reset_factory_defaults', true); + return ['status' => 'ok']; + } + + /** + * return used configuration items + */ + public function getInstalledSectionsAction() + { + $result = ['items' => []]; + $cm = new ConfigMaintenance(); + foreach($cm->traverseConfig() as $item) { + $result['items'][] = $item; + } + return $result; + } + + /** + * reset a (list of) section(s) + */ + public function resetAction() + { + $this->throwReadOnly(); + if ( + !$this->request->isPost() || + !is_array($this->request->getPost('items')) || + empty($this->request->getPost('items')) + ) { + return ['status' => 'failed']; + } + $cm = new ConfigMaintenance(); + $modelmap = $cm->getMap(); + foreach ($this->request->getPost('items') as $section) { + if (isset($modelmap[$section])) { + /* installed model flush */ + $mdl = new $modelmap[$section]['class'](true); + $mdl->Default(); + $mdl->serializeToConfig(false, true); + } else { + $cm->delItem($section); + } + } + + Config::getInstance()->save(); + + return ['status' => 'ok']; + } + +} diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Core/DefaultsController.php b/src/opnsense/mvc/app/controllers/OPNsense/Core/DefaultsController.php new file mode 100644 index 0000000000..9a64af4ccc --- /dev/null +++ b/src/opnsense/mvc/app/controllers/OPNsense/Core/DefaultsController.php @@ -0,0 +1,41 @@ +view->pick('OPNsense/Core/defaults'); + } +} diff --git a/src/opnsense/mvc/app/library/OPNsense/Core/ConfigMaintenance.php b/src/opnsense/mvc/app/library/OPNsense/Core/ConfigMaintenance.php new file mode 100644 index 0000000000..2f700a75f1 --- /dev/null +++ b/src/opnsense/mvc/app/library/OPNsense/Core/ConfigMaintenance.php @@ -0,0 +1,145 @@ +modelmap = $this->loadModels(); + } + + /** + * collect all installed modules + */ + private function loadModels() + { + $modelfiles = []; + $model_dir = dirname((new \ReflectionClass("OPNsense\\Base\\BaseModel"))->getFileName()) . "/../../"; + foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($model_dir)) as $x) { + $pinfo = pathinfo(realpath($x->getPathname())); + $xmlname = sprintf("%s/%s.xml", $pinfo['dirname'], $pinfo['filename']); + $classname = str_replace('/', '\\', explode('.', str_replace($model_dir, '', $x->getPathname()))[0]); + if (file_exists($xmlname) && isset($pinfo['extension']) && $pinfo['extension'] == 'php') { + $parent = (new \ReflectionClass($classname))->getParentClass(); + if ($parent && $parent->name == 'OPNsense\Base\BaseModel') { + $modelfiles[$xmlname] = $classname; + } + } + } + $map = []; + foreach ($modelfiles as $filename => $classname) { + $model_xml = simplexml_load_file($filename); + if ($model_xml !== false && str_starts_with($model_xml->mount, '/')) { + $mount = str_replace('/', '.', ltrim(rtrim($model_xml->mount, '+'), '/')); + $map[$mount] = [ + 'filename' => $filename, + 'class' => $classname, + 'description' => trim((string)$model_xml->description ?? '') + ]; + } + } + return $map; + } + + + /** + * collect all "flushable" configuration items (models) + */ + public function traverseConfig($node=null, $path='') + { + if ($node === null) { + $node = (Config::getInstance())->object(); + $this->foundrefs = []; + } + foreach ($node->children() as $xmlNode) { + if ($xmlNode->count() > 0) { + $this_path = ltrim($path . '.' . $xmlNode->getName(), '.'); + if (in_array($this_path, $this->foundrefs)) { + continue; + } elseif (isset($this->modelmap[$this_path]) || !empty($xmlNode->attributes()['version'])) { + /* found a registered model or a container with a version tag (likely model, but not installed) */ + $this->foundrefs[] = $this_path; + if (isset($this->modelmap[$this_path])) { + yield [ + 'id' => $this_path, + 'description' => $this->modelmap[$this_path]['description'] + ]; + } else { + $tmp = explode('.', $this_path); + yield [ + 'id' => $this_path, + 'description' => end($tmp) + ]; + } + } else { + yield from $this->traverseConfig($xmlNode, $this_path); + } + } + } + } + + /** + * del model item, requires a version attribute to identify itself as a model. + */ + public function delItem($item, $node=null, $path='') + { + if ($node === null) { + $node = (Config::getInstance())->object(); + } + foreach ($node->children() as $xmlNode) { + if ($xmlNode->count() > 0) { + $this_path = ltrim($path . '.' . $xmlNode->getName(), '.'); + if (!empty($xmlNode->attributes()['version'])) { + if ($this_path == $item) { + unset($xmlNode[0]); + return true; + } + } else { + $this->delItem($item, $xmlNode, $this_path); + } + } + } + return false; + } + + /** + * retrieve model map + */ + public function getMap() + { + return $this->modelmap; + } +} \ No newline at end of file diff --git a/src/opnsense/mvc/app/models/OPNsense/Core/ACL/ACL.xml b/src/opnsense/mvc/app/models/OPNsense/Core/ACL/ACL.xml index 4008af2673..d9e566d5ab 100644 --- a/src/opnsense/mvc/app/models/OPNsense/Core/ACL/ACL.xml +++ b/src/opnsense/mvc/app/models/OPNsense/Core/ACL/ACL.xml @@ -60,7 +60,8 @@ Diagnostics: Factory defaults - diag_defaults.php* + ui/core/defaults + api/core/defaults/* diff --git a/src/opnsense/mvc/app/models/OPNsense/Core/Menu/Menu.xml b/src/opnsense/mvc/app/models/OPNsense/Core/Menu/Menu.xml index 1b4551d6f9..cf21b3cafa 100644 --- a/src/opnsense/mvc/app/models/OPNsense/Core/Menu/Menu.xml +++ b/src/opnsense/mvc/app/models/OPNsense/Core/Menu/Menu.xml @@ -28,7 +28,7 @@ - + diff --git a/src/opnsense/mvc/app/views/OPNsense/Core/defaults.volt b/src/opnsense/mvc/app/views/OPNsense/Core/defaults.volt new file mode 100644 index 0000000000..55e4d6e7c5 --- /dev/null +++ b/src/opnsense/mvc/app/views/OPNsense/Core/defaults.volt @@ -0,0 +1,151 @@ +{# + # Copyright (c) 2025 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. + #} + + + + +
+
+
+
+ +
+
+
+
+ +
+ + + + + + + + + + + + + + + +
+ {{ lang._('Danger zone, only reset configuration sections if you understand the impact.') }} +
{{ lang._('Sections')}}
+ + + +
+
+
diff --git a/src/opnsense/scripts/system/factory_defaults.php b/src/opnsense/scripts/system/factory_defaults.php new file mode 100755 index 0000000000..3bf252b98a --- /dev/null +++ b/src/opnsense/scripts/system/factory_defaults.php @@ -0,0 +1,41 @@ +#!/usr/local/bin/php + - * Copyright (C) 2003-2004 Manuel Kasper - * 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. - */ - -require_once("guiconfig.inc"); -require_once("system.inc"); - -$input_errors = []; - -if ($_SERVER['REQUEST_METHOD'] === 'POST') { - if (!empty($_POST['Submit'])) { - $user = getUserEntry($_SESSION['Username']); - if (userHasPrivilege($user, 'user-config-readonly')) { - $input_errors[] = gettext('You do not have the permission to perform this action.'); - } - } -} - -$default_config_ip = '192.168.1.1'; /* failsafe default */ -if (is_file('/usr/local/etc/config.xml')) { - try { - $restore_conf = load_config_from_file('/usr/local/etc/config.xml'); - if ( - is_array($restore_conf) && - !empty($restore_conf['interfaces']) && - !empty($restore_conf['interfaces']['lan']) && - !empty($restore_conf['interfaces']['lan']['ipaddr']) - ) { - $default_config_ip = $restore_conf['interfaces']['lan']['ipaddr']; - } - } catch (Exception $e) { } -} - -include("head.inc"); - -?> - - - - - - - -
-
-
- -
-
-

-
    -
  • -
  • -
  • -
  • -
  • -
  • -
-

-
- - -
-
-
-
-
-
-