mirror of
https://github.com/opnsense/core.git
synced 2026-02-05 04:02:56 -06:00
Compare commits
74 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cbc09e7c5a | ||
|
|
700f590383 | ||
|
|
4912a671be | ||
|
|
d4eb6235ae | ||
|
|
d43b14ef9b | ||
|
|
6dce1de829 | ||
|
|
7a8f6bee11 | ||
|
|
cf0341f139 | ||
|
|
40cb82128d | ||
|
|
5276f51dc0 | ||
|
|
7ae42d9584 | ||
|
|
c3dd6d56f1 | ||
|
|
e771a800d5 | ||
|
|
b95c81d08d | ||
|
|
e2d95ad672 | ||
|
|
45597a976c | ||
|
|
39fcbddb05 | ||
|
|
ce432fa769 | ||
|
|
d260467553 | ||
|
|
0f6d82af34 | ||
|
|
9aaf675694 | ||
|
|
7333fba07a | ||
|
|
3ce73ff043 | ||
|
|
b5cf3f7410 | ||
|
|
f7f0857ca9 | ||
|
|
4c559a63d4 | ||
|
|
a5fed616a5 | ||
|
|
3bcdae70f7 | ||
|
|
1727592311 | ||
|
|
c6540bf6fa | ||
|
|
d31faf7f7c | ||
|
|
70629923bb | ||
|
|
ca06d54676 | ||
|
|
311184daa8 | ||
|
|
0f6cc03c69 | ||
|
|
c264c90504 | ||
|
|
ec20be4dd4 | ||
|
|
7a11458ea2 | ||
|
|
ffe3b40872 | ||
|
|
5cc95f47a6 | ||
|
|
be4900b112 | ||
|
|
816fd574c9 | ||
|
|
9e70ee7508 | ||
|
|
cff4c085d3 | ||
|
|
c827a02ef6 | ||
|
|
5d571dcc89 | ||
|
|
476ad93d6f | ||
|
|
1ddc63e402 | ||
|
|
60695dd259 | ||
|
|
0642e17bc5 | ||
|
|
87445129bf | ||
|
|
1ddc661a49 | ||
|
|
35575f9446 | ||
|
|
43de1e0e42 | ||
|
|
1e1a6a37f6 | ||
|
|
8c1a820340 | ||
|
|
34d7d77426 | ||
|
|
f8560f063f | ||
|
|
12aab2a9e0 | ||
|
|
f7fac5a6f4 | ||
|
|
44dbcd103b | ||
|
|
83f9492087 | ||
|
|
fcab636a4c | ||
|
|
b6a59bb7e5 | ||
|
|
f456d15d76 | ||
|
|
fce3f7973a | ||
|
|
660fa8210b | ||
|
|
b2a376cece | ||
|
|
a35dce38e8 | ||
|
|
c030ca6507 | ||
|
|
163162a8ac | ||
|
|
6d3ca746d1 | ||
|
|
ca9d7a550a | ||
|
|
ee962f01db |
2
LICENSE
2
LICENSE
@ -28,7 +28,7 @@ Copyright (c) 2021 Kyle Evans <kevans@FreeBSD.org>
|
||||
Copyright (c) 2015 Manuel Faux <mfaux@conf.at>
|
||||
Copyright (c) 2003-2006 Manuel Kasper <mk@neon1.net>
|
||||
Copyright (c) 2012 Marcello Coutinho
|
||||
Copyright (c) 2018 Martin Wasley <mjwasley@gmail.com>
|
||||
Copyright (c) 2017-2020 Martin Wasley <mjwasley@gmail.com>
|
||||
Copyright (c) 2022 Maurice Walker <maurice@walker.earth>
|
||||
Copyright (c) 2010-2015 Michael Bostock
|
||||
Copyright (c) 2018-2021 Michael Muenz <m.muenz@gmail.com>
|
||||
|
||||
12
Makefile
12
Makefile
@ -205,7 +205,7 @@ CORE_CONFLICTS:= ${CORE_CONFLICTS:S/^/os-/g:O}
|
||||
|
||||
mount:
|
||||
@if [ ! -f ${WRKDIR}/.mount_done ]; then \
|
||||
echo -n "Enabling core live mount..."; \
|
||||
echo -n ">>> Enabling core live mount..."; \
|
||||
sed ${SED_REPLACE} ${.CURDIR}/src/${VERSIONFILE}.in > \
|
||||
${.CURDIR}/src/${VERSIONFILE}; \
|
||||
mount_unionfs ${.CURDIR}/src ${LOCALBASE}; \
|
||||
@ -216,7 +216,7 @@ mount:
|
||||
|
||||
umount:
|
||||
@if [ -f ${WRKDIR}/.mount_done ]; then \
|
||||
echo -n "Disabling core live mount..."; \
|
||||
echo -n ">>> Disabling core live mount..."; \
|
||||
umount -f "<above>:${.CURDIR}/src"; \
|
||||
rm ${WRKDIR}/.mount_done; \
|
||||
echo "done"; \
|
||||
@ -227,7 +227,7 @@ manifest-check:
|
||||
# check if all annotations are in the version file
|
||||
.for REPLACEMENT in ${REPLACEMENTS}
|
||||
@grep -q '\"${REPLACEMENT}\": \"%%${REPLACEMENT}%%\"' ${.CURDIR}/src/${VERSIONFILE}.in || \
|
||||
(echo "Could not find ${REPLACEMENT} in version file"; exit 1)
|
||||
(echo ">>> Could not find ${REPLACEMENT} in version file" >&2; exit 1)
|
||||
.endfor
|
||||
|
||||
manifest:
|
||||
@ -333,11 +333,13 @@ package: lint-plist manifest-check package-check clean-wrksrc
|
||||
upgrade-check:
|
||||
@if ! ${PKG} info ${CORE_NAME} > /dev/null; then \
|
||||
REAL_NAME=$$(pkg which -q /usr/local/opnsense/version/core); \
|
||||
echo ">>> Cannot find installed package. Use CORE_NAME=$${REAL_NAME%-*} instead." >&2; \
|
||||
echo ">>> Upgrade cannot continue without installed package" >&2; \
|
||||
echo ">>> To continue anyway set CORE_NAME=$${REAL_NAME%-*}" >&2; \
|
||||
exit 1; \
|
||||
fi
|
||||
@if [ "$$(${VERSIONBIN} -vH)" = "${CORE_PKGVERSION} ${CORE_HASH}" ]; then \
|
||||
echo "Installed version already matches ${CORE_PKGVERSION} ${CORE_HASH}" >&2; \
|
||||
echo ">>> Installed version already matches ${CORE_PKGVERSION} ${CORE_HASH}" >&2; \
|
||||
echo ">>> To continue anyway set CORE_HASH=<something>" >&2; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
|
||||
@ -108,6 +108,9 @@ lint-model:
|
||||
lint-acl:
|
||||
@${COREREFDIR}/Scripts/dashboard-acl.sh ${COREREFDIR}
|
||||
|
||||
lint-class:
|
||||
@${COREREFDIR}/Scripts/class-filename.sh ${COREREFDIR}
|
||||
|
||||
SCRIPTDIRS!= if [ -d ${.CURDIR}/src/opnsense/scripts ]; then find ${.CURDIR}/src/opnsense/scripts -type d -depth 1; fi
|
||||
|
||||
lint-exec:
|
||||
@ -151,4 +154,4 @@ lint-plist:
|
||||
@rm ${WRKDIR}/plist.*
|
||||
.endif
|
||||
|
||||
lint: lint-plist lint-desc lint-shell lint-xml lint-model lint-acl lint-exec lint-php
|
||||
lint: lint-plist lint-desc lint-shell lint-xml lint-model lint-acl lint-class lint-exec lint-php
|
||||
|
||||
52
Scripts/class-filename.sh
Executable file
52
Scripts/class-filename.sh
Executable file
@ -0,0 +1,52 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Copyright (c) 2026 Franco Fichtner <franco@opnsense.org>
|
||||
#
|
||||
# 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 BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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.
|
||||
|
||||
RET=0
|
||||
|
||||
for FILE in $(find src -name "*.php"); do
|
||||
CLASS=$(grep ^class ${FILE} | awk '{ print $2 }')
|
||||
if [ -z "${CLASS}" ]; then
|
||||
continue
|
||||
fi
|
||||
MULTI=
|
||||
OK=
|
||||
for _CLASS in ${CLASS}; do
|
||||
if [ "$(basename ${FILE})" == "${_CLASS}.php" ]; then
|
||||
OK=${_CLASS}
|
||||
else
|
||||
MULTI="${_CLASS} ${MULTI}"
|
||||
fi
|
||||
done
|
||||
if [ -z "${OK}" ]; then
|
||||
echo "${FILE}: error: does not match class name" ${CLASS}
|
||||
RET=1
|
||||
elif [ -n "${MULTI}" ]; then
|
||||
echo "${FILE}: warning: has additional classes" ${MULTI}
|
||||
fi
|
||||
done
|
||||
|
||||
exit ${RET}
|
||||
3
plist
3
plist
@ -79,7 +79,7 @@
|
||||
/usr/local/etc/pkg/fingerprints/OPNsense/revoked/pkg.opnsense.org.20240105
|
||||
/usr/local/etc/pkg/fingerprints/OPNsense/revoked/pkg.opnsense.org.20240611
|
||||
/usr/local/etc/pkg/fingerprints/OPNsense/revoked/pkg.opnsense.org.20241217
|
||||
/usr/local/etc/pkg/fingerprints/OPNsense/trusted/pkg.opnsense.org.20250710
|
||||
/usr/local/etc/pkg/fingerprints/OPNsense/revoked/pkg.opnsense.org.20250710
|
||||
/usr/local/etc/pkg/fingerprints/OPNsense/trusted/pkg.opnsense.org.20260120
|
||||
/usr/local/etc/rc
|
||||
/usr/local/etc/rc.bootup
|
||||
@ -1242,6 +1242,7 @@
|
||||
/usr/local/opnsense/scripts/interfaces/carp_global_status.php
|
||||
/usr/local/opnsense/scripts/interfaces/carp_set_status.php
|
||||
/usr/local/opnsense/scripts/interfaces/dhclient-script
|
||||
/usr/local/opnsense/scripts/interfaces/dhcp6c_script.sh
|
||||
/usr/local/opnsense/scripts/interfaces/gen_duid.php
|
||||
/usr/local/opnsense/scripts/interfaces/ifctl.sh
|
||||
/usr/local/opnsense/scripts/interfaces/lib/__init__.py
|
||||
|
||||
@ -33,6 +33,7 @@
|
||||
<disablenatreflection>yes</disablenatreflection>
|
||||
<usevirtualterminal>1</usevirtualterminal>
|
||||
<disableconsolemenu/>
|
||||
<ipv6allow>1</ipv6allow>
|
||||
<powerd_ac_mode>hadp</powerd_ac_mode>
|
||||
<powerd_battery_mode>hadp</powerd_battery_mode>
|
||||
<powerd_normal_mode>hadp</powerd_normal_mode>
|
||||
|
||||
@ -170,7 +170,7 @@ function convert_config($verbose = false)
|
||||
$function = $verbose ? 'pass_safe' : 'mwexecf';
|
||||
|
||||
/* chain the new migration into this function call */
|
||||
$function('/usr/local/sbin/pluginctl -m');
|
||||
$function('/usr/local/sbin/pluginctl %s', $verbose ? '-m' : '-M');
|
||||
|
||||
/* register pluggable interfaces */
|
||||
$function('/usr/local/sbin/pluginctl -i');
|
||||
|
||||
@ -392,7 +392,8 @@ EOD;
|
||||
}
|
||||
}
|
||||
$config['dnsmasq']['enable'] = '1';
|
||||
$config['dnsmasq']['dhcp'] = ['enable_ra' => '1'];
|
||||
config_read_array('dnsmasq', 'dhcp');
|
||||
$config['dnsmasq']['dhcp']['enable_ra'] = '1';
|
||||
$config['dnsmasq']['dhcp_ranges'][] = [
|
||||
'@attributes' => ['uuid' => generate_uuid()],
|
||||
'start_addr' => '192.168.1.100',
|
||||
|
||||
@ -187,8 +187,8 @@ function filter_configure_sync($verbose = false, $load_aliases = true)
|
||||
filter_core_bootstrap($fw);
|
||||
$cnfint = iterator_to_array($fw->getInterfaceMapping());
|
||||
plugins_firewall($fw);
|
||||
// register user rules, returns kill states for schedules
|
||||
$sched_kill_states = filter_core_rules_user($fw);
|
||||
// register legacy user rules
|
||||
filter_core_rules_user($fw);
|
||||
|
||||
// manual outbound nat rules
|
||||
if (
|
||||
@ -256,6 +256,33 @@ function filter_configure_sync($verbose = false, $load_aliases = true)
|
||||
foreach ((new OPNsense\Firewall\DNat())->rule->sortedBy(['sequence']) as $key => $rule) {
|
||||
$fw->registerForwardRule(600, $rule->getNodeContent());
|
||||
}
|
||||
/**
|
||||
* XXX: Apply schedules on all installed rules so we can use both legacy and MVC ones.
|
||||
* Eventually this needs to be replaced, but as this requires legacy code, we cannot do that easily now
|
||||
* without increasing impact.
|
||||
*/
|
||||
$sched_kill_states = [];
|
||||
foreach ($fw->iterateFilterRules() as $rule) {
|
||||
$rrule = $rule->getRawRule();
|
||||
if (!empty($rrule['sched'])) {
|
||||
$descr = $rrule['descr'] ?? '';
|
||||
$descr .= " ({$rrule['sched']})";
|
||||
$rule->updateDescription($descr);
|
||||
foreach ($config['schedules']['schedule'] as $sched) {
|
||||
if ($sched['name'] == $rrule['sched']) {
|
||||
if (!filter_get_time_based_rule_status($sched)) {
|
||||
if (!isset($config['system']['schedule_states'])) {
|
||||
$sched_kill_states[] = $rrule['label'];
|
||||
}
|
||||
/* disable rule, suffix label to mark end of schedule */
|
||||
$rule->disable();
|
||||
$rule->updateDescription("[FIN] " . $descr);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
openlog("firewall", LOG_DAEMON, LOG_LOCAL4);
|
||||
|
||||
|
||||
@ -658,7 +658,7 @@ function filter_core_rules_system($fw, $defaults)
|
||||
$parts = explode('-', $dnatrule['destination']['port']);
|
||||
if (count($parts) == 2) {
|
||||
$range_end = $parts[1] - $parts[0] + $dnatrule['local-port'];
|
||||
$dnatrule['local-port'] .= sprint("-%s", $range_end);
|
||||
$dnatrule['local-port'] .= sprintf('-%s', $range_end);
|
||||
}
|
||||
}
|
||||
$fw->registerFilterRule(500000, $tmprule); /* automatic at the end of our ruleset */
|
||||
@ -667,14 +667,12 @@ function filter_core_rules_system($fw, $defaults)
|
||||
}
|
||||
|
||||
/**
|
||||
* register user rules, returns kill states for schedules
|
||||
* register user rules
|
||||
*/
|
||||
function filter_core_rules_user($fw)
|
||||
{
|
||||
global $config;
|
||||
|
||||
$sched_kill_states = [];
|
||||
|
||||
foreach ((new \OPNsense\Firewall\Group())->ifgroupentry->iterateItems() as $node) {
|
||||
$ifgroups[(string)$node->ifname] = !empty((string)$node->sequence) ? (string)$node->sequence : 0;
|
||||
}
|
||||
@ -696,9 +694,6 @@ function filter_core_rules_user($fw)
|
||||
if (empty($rule['descr'])) {
|
||||
$rule['descr'] = '';
|
||||
}
|
||||
if (!empty($rule['sched'])) {
|
||||
$rule['descr'] .= " ({$rule['sched']})";
|
||||
}
|
||||
|
||||
if (isset($rule['floating'])) {
|
||||
$prio = 200000;
|
||||
@ -707,25 +702,7 @@ function filter_core_rules_user($fw)
|
||||
} else {
|
||||
$prio = 400000;
|
||||
}
|
||||
/* is a time based rule schedule attached? */
|
||||
if (!empty($rule['sched']) && !empty($config['schedules'])) {
|
||||
foreach ($config['schedules']['schedule'] as $sched) {
|
||||
if ($sched['name'] == $rule['sched']) {
|
||||
if (!filter_get_time_based_rule_status($sched)) {
|
||||
if (!isset($config['system']['schedule_states'])) {
|
||||
$sched_kill_states[] = $rule['label'];
|
||||
}
|
||||
/* disable rule, suffix label to mark end of schedule */
|
||||
$rule['disabled'] = true;
|
||||
$rule['descr'] = "[FIN] " . $rule['descr'];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
$fw->registerFilterRule($prio, $rule);
|
||||
}
|
||||
}
|
||||
|
||||
return $sched_kill_states;
|
||||
}
|
||||
|
||||
@ -1655,18 +1655,6 @@ function _interfaces_wlan_clone($device, $wlcfg)
|
||||
$wlcfg_mode = $wlcfg['mode'] ?? 'bss';
|
||||
}
|
||||
|
||||
switch ($wlcfg_mode) {
|
||||
case "hostap":
|
||||
$mode = 'wlanmode hostap';
|
||||
break;
|
||||
case "adhoc":
|
||||
$mode = 'wlanmode adhoc';
|
||||
break;
|
||||
default:
|
||||
$mode = '';
|
||||
break;
|
||||
}
|
||||
|
||||
if (does_interface_exist($device)) {
|
||||
$ifconfig_str = shell_safe('/sbin/ifconfig %s', $device);
|
||||
if (($wlcfg_mode == 'hostap') && !preg_match('/hostap/si', $ifconfig_str)) {
|
||||
@ -1690,7 +1678,18 @@ function _interfaces_wlan_clone($device, $wlcfg)
|
||||
|
||||
legacy_interface_destroy($device);
|
||||
|
||||
if (mwexecf('/sbin/ifconfig wlan create wlandev %s %s bssid name %s', [$baseif, $mode, $device])) {
|
||||
$wlan_frmt = ['/sbin/ifconfig wlan create wlandev %s'];
|
||||
$wlan_args = [$baseif];
|
||||
|
||||
if (in_array($wlcfg_mode, ['adhoc', 'hostap'])) {
|
||||
$wlan_frmt[] = 'wlanmode %s';
|
||||
$wlan_args[] = $wlcfg_mode;
|
||||
}
|
||||
|
||||
$wlan_frmt[] = 'bssid name %s';
|
||||
$wlan_args[] = $device;
|
||||
|
||||
if (mwexecf($wlan_frmt, $wlan_args)) {
|
||||
log_msg("Failed to clone interface {$baseif} to {$device}", LOG_ERR);
|
||||
return null;
|
||||
}
|
||||
@ -2980,71 +2979,6 @@ function interface_dhcpv6_prepare($interface, $wancfg, $cleanup = false)
|
||||
@unlink("/var/etc/dhcp6c.{$device}.conf");
|
||||
}
|
||||
|
||||
$dhcp6cscript = <<<EOF
|
||||
#!/bin/sh
|
||||
|
||||
FORCE=
|
||||
|
||||
case \${REASON} in
|
||||
INFOREQ|REBIND|RENEW|REQUEST|SOLICIT)
|
||||
/usr/bin/logger -t dhcp6c "dhcp6c_script: \${REASON} on {$device} executing"
|
||||
|
||||
ARGS=
|
||||
for NAMESERVER in \${new_domain_name_servers}; do
|
||||
ARGS="\${ARGS} -a \${NAMESERVER}"
|
||||
done
|
||||
/usr/local/sbin/ifctl -i {$device} -6nd \${ARGS}
|
||||
|
||||
ARGS=
|
||||
for DOMAIN in \${new_domain_name}; do
|
||||
ARGS="\${ARGS} -a \${DOMAIN}"
|
||||
done
|
||||
/usr/local/sbin/ifctl -i {$device} -6sd \${ARGS}
|
||||
|
||||
if [ \${REASON} = "REQUEST" -o \${REASON} = "SOLICIT" ]; then
|
||||
/usr/bin/logger -t dhcp6c "dhcp6c_script: \${REASON} on {$device} connected to server"
|
||||
FORCE=\${REASON}
|
||||
|
||||
# can safely clear the prefix during connect
|
||||
/usr/local/sbin/ifctl -i {$device} -6pd
|
||||
fi
|
||||
|
||||
if [ \${REASON} != "INFOREQ" -a -n "\${PDINFO}" ]; then
|
||||
ARGS=
|
||||
for PD in \${PDINFO}; do
|
||||
ARGS="\${ARGS} -a \${PD}"
|
||||
done
|
||||
|
||||
if /usr/local/sbin/ifctl -i {$device} -6pu \${ARGS}; then
|
||||
/usr/bin/logger -t dhcp6c "dhcp6c_script: \${REASON} on {$device} prefix now \${PDINFO}"
|
||||
|
||||
if [ -z "\${FORCE}" ]; then
|
||||
FORCE=\${REASON}
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
/usr/local/sbin/configctl -d interface newipv6 {$device} \${FORCE}
|
||||
;;
|
||||
EXIT|RELEASE)
|
||||
/usr/bin/logger -t dhcp6c "dhcp6c_script: \${REASON} on {$device} executing"
|
||||
|
||||
/usr/local/sbin/ifctl -i {$device} -6nd
|
||||
/usr/local/sbin/ifctl -i {$device} -6sd
|
||||
/usr/local/sbin/ifctl -i {$device} -6pd
|
||||
|
||||
/usr/local/sbin/configctl -d interface newipv6 {$device}
|
||||
;;
|
||||
*)
|
||||
/usr/bin/logger -t dhcp6c "dhcp6c_script: \${REASON} on {$device} ignored"
|
||||
;;
|
||||
esac
|
||||
|
||||
EOF;
|
||||
|
||||
@file_put_contents("/var/etc/dhcp6c_{$interface}_script.sh", $dhcp6cscript);
|
||||
@chmod("/var/etc/dhcp6c_{$interface}_script.sh", 0755);
|
||||
|
||||
$dhcp6cconf = '';
|
||||
|
||||
/* merge configs and prepare single instance of dhcp6c for startup */
|
||||
@ -3071,7 +3005,7 @@ function DHCP6_Config_File_Basic($interface, $wancfg, $wanif, $id = 0)
|
||||
$dhcp6cconf .= " information-only;\n";
|
||||
$dhcp6cconf .= " request domain-name-servers;\n";
|
||||
$dhcp6cconf .= " request domain-name;\n";
|
||||
$dhcp6cconf .= " script \"/var/etc/dhcp6c_{$interface}_script.sh\";\n";
|
||||
$dhcp6cconf .= " script \"/usr/local/opnsense/scripts/interfaces/dhcp6c_script.sh\";\n";
|
||||
$dhcp6cconf .= "};\n";
|
||||
} elseif ($wancfg['ipaddrv6'] == 'dhcp6') {
|
||||
$assoc_pd = [];
|
||||
@ -3136,7 +3070,7 @@ function DHCP6_Config_File_Basic($interface, $wancfg, $wanif, $id = 0)
|
||||
$dhcp6cconf .= " request domain-name;\n";
|
||||
}
|
||||
|
||||
$dhcp6cconf .= " script \"/var/etc/dhcp6c_{$interface}_script.sh\";\n";
|
||||
$dhcp6cconf .= " script \"/usr/local/opnsense/scripts/interfaces/dhcp6c_script.sh\";\n";
|
||||
$dhcp6cconf .= "};\n";
|
||||
|
||||
if (!isset($wancfg['dhcp6prefixonly'])) {
|
||||
@ -3181,7 +3115,7 @@ function DHCP6_Config_File_Advanced($interface, $wancfg, $wanif, $id = 0)
|
||||
$information_only = " information-only;\n";
|
||||
}
|
||||
|
||||
$script = " script \"/var/etc/dhcp6c_{$interface}_script.sh\";\n";
|
||||
$script = " script \"/usr/local/opnsense/scripts/interfaces/dhcp6c_script.sh\";\n";
|
||||
if ($wancfg['adv_dhcp6_interface_statement_script'] != '') {
|
||||
$script = " script \"{$wancfg['adv_dhcp6_interface_statement_script']}\";\n";
|
||||
}
|
||||
|
||||
@ -157,7 +157,7 @@ function openssh_configure_do($verbose = false, $interface_map = null)
|
||||
|
||||
$sshport = isset($sshcfg['port']) ? $sshcfg['port'] : 22;
|
||||
|
||||
$sshconf = "# This file was automatically generated by openssh_configure_do()\n";
|
||||
$sshconf = "# This file was automatically generated by openssh_configure_do()\n";
|
||||
$sshconf .= "# If you want to include custom options please use this directory:\n";
|
||||
$sshconf .= "Include /usr/local/etc/ssh/sshd_config.d/*.conf\n";
|
||||
$sshconf .= "Port {$sshport}\n";
|
||||
|
||||
@ -58,36 +58,31 @@ function pf_cron()
|
||||
{
|
||||
global $config;
|
||||
|
||||
$jobs = array();
|
||||
$jobs = [];
|
||||
|
||||
if (isset($config['filter']['rule'])) {
|
||||
foreach ($config['filter']['rule'] as $rule) {
|
||||
if (empty($rule['disabled']) && !empty($rule['sched'])) {
|
||||
$jobs[]['autocron'] = array('/usr/bin/logger "reload filter for configured schedules" ; /usr/local/etc/rc.filter_configure', '1,16,31,46');
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ((new OPNsense\Firewall\Filter(true))->hasSchedule()) {
|
||||
$jobs[]['autocron'] = ['/usr/bin/logger "reload filter for configured schedules" ; /usr/local/etc/rc.filter_configure', '1,16,31,46'];
|
||||
}
|
||||
|
||||
/* bogons fetch always set in default config.xml */
|
||||
switch ($config['system']['bogons']['interval']) {
|
||||
case 'daily':
|
||||
$jobs[]['autocron'] = array('/usr/local/sbin/configctl -d filter schedule bogons', '1', '3', '*', '*', '*');
|
||||
$jobs[]['autocron'] = ['/usr/local/sbin/configctl -d filter schedule bogons', '1', '3', '*', '*', '*'];
|
||||
break;
|
||||
case 'weekly':
|
||||
$jobs[]['autocron'] = array('/usr/local/sbin/configctl -d filter schedule bogons', '1', '3', '*', '*', '0');
|
||||
$jobs[]['autocron'] = ['/usr/local/sbin/configctl -d filter schedule bogons', '1', '3', '*', '*', '0'];
|
||||
break;
|
||||
case 'monthly':
|
||||
default:
|
||||
$jobs[]['autocron'] = array('/usr/local/sbin/configctl -d filter schedule bogons', '1', '3', '1', '*', '*');
|
||||
$jobs[]['autocron'] = ['/usr/local/sbin/configctl -d filter schedule bogons', '1', '3', '1', '*', '*'];
|
||||
break;
|
||||
}
|
||||
|
||||
$jobs[]['autocron'] = array(
|
||||
$jobs[]['autocron'] = [
|
||||
'/usr/local/bin/flock -n -E 0 -o /tmp/filter_update_tables.lock ' .
|
||||
'/usr/local/opnsense/scripts/filter/update_tables.py --quick',
|
||||
'*'
|
||||
);
|
||||
];
|
||||
|
||||
return $jobs;
|
||||
}
|
||||
|
||||
@ -149,8 +149,16 @@ function radvd_configure_do($verbose = false, $ignorelist = [])
|
||||
|
||||
$carp_mode = false;
|
||||
$src_addr = false;
|
||||
$ifcfgipv6 = null;
|
||||
|
||||
if (!$entry->AdvRASrcAddress->isEmpty()) {
|
||||
$ifcfgipv6 = $entry->AdvRASrcAddress->getValue();
|
||||
} elseif (!$entry->Base6Interface->isEmpty()) {
|
||||
$ifcfgipv6 = get_interface_ipv6($entry->Base6Interface->getValue());
|
||||
} else {
|
||||
$ifcfgipv6 = get_interface_ipv6($dhcpv6if);
|
||||
}
|
||||
|
||||
$ifcfgipv6 = !$entry->AdvRASrcAddress->isEmpty() ? $entry->AdvRASrcAddress->getValue() : get_interface_ipv6($dhcpv6if);
|
||||
if (!is_ipaddrv6($ifcfgipv6)) {
|
||||
$radvdconf .= "# Skipping addressless interface {$dhcpv6if}\n";
|
||||
continue;
|
||||
@ -181,24 +189,24 @@ function radvd_configure_do($verbose = false, $ignorelist = [])
|
||||
}
|
||||
$radvdconf .= "# Generated RADVD config for manual assignment on {$dhcpv6if}\n";
|
||||
$radvdconf .= "interface {$device} {\n";
|
||||
$radvdconf .= "\tAdvSendAdvert on;\n";
|
||||
$radvdconf .= "\tMinRtrAdvInterval {$entry->MinRtrAdvInterval->getValue()};\n";
|
||||
$radvdconf .= "\tMaxRtrAdvInterval {$entry->MaxRtrAdvInterval->getValue()};\n";
|
||||
$radvdconf .= " AdvSendAdvert on;\n";
|
||||
$radvdconf .= " MinRtrAdvInterval {$entry->MinRtrAdvInterval->getValue()};\n";
|
||||
$radvdconf .= " MaxRtrAdvInterval {$entry->MaxRtrAdvInterval->getValue()};\n";
|
||||
if ($entry->AdvDefaultLifetime->isSet()) {
|
||||
$radvdconf .= "\tAdvDefaultLifetime {$entry->AdvDefaultLifetime->getValue()};\n";
|
||||
$radvdconf .= " AdvDefaultLifetime {$entry->AdvDefaultLifetime->getValue()};\n";
|
||||
}
|
||||
$radvdconf .= sprintf("\tAdvLinkMTU %s;\n", !empty($mtu) ? $mtu : 0);
|
||||
$radvdconf .= "\tAdvDefaultPreference {$entry->AdvDefaultPreference->getValue()};\n";
|
||||
$radvdconf .= sprintf(" AdvLinkMTU %s;\n", !empty($mtu) ? $mtu : 0);
|
||||
$radvdconf .= " AdvDefaultPreference {$entry->AdvDefaultPreference->getValue()};\n";
|
||||
|
||||
switch ($entry->mode->getValue()) {
|
||||
case 'assist':
|
||||
case 'managed':
|
||||
$radvdconf .= "\tAdvManagedFlag on;\n";
|
||||
$radvdconf .= "\tAdvOtherConfigFlag on;\n";
|
||||
$radvdconf .= " AdvManagedFlag on;\n";
|
||||
$radvdconf .= " AdvOtherConfigFlag on;\n";
|
||||
break;
|
||||
case 'stateless':
|
||||
$radvdconf .= "\tAdvManagedFlag off;\n";
|
||||
$radvdconf .= "\tAdvOtherConfigFlag on;\n";
|
||||
$radvdconf .= " AdvManagedFlag off;\n";
|
||||
$radvdconf .= " AdvOtherConfigFlag on;\n";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@ -239,72 +247,72 @@ function radvd_configure_do($verbose = false, $ignorelist = [])
|
||||
|
||||
if ($src_addr) {
|
||||
/* inject configured link-local address into the RA message */
|
||||
$radvdconf .= "\tAdvRASrcAddress {\n";
|
||||
$radvdconf .= "\t\t{$ifcfgipv6};\n";
|
||||
$radvdconf .= "\t};\n";
|
||||
$radvdconf .= " AdvRASrcAddress {\n";
|
||||
$radvdconf .= " {$ifcfgipv6};\n";
|
||||
$radvdconf .= " };\n";
|
||||
}
|
||||
|
||||
if ($carp_mode) {
|
||||
/* to avoid wrong MAC being stuck during failover */
|
||||
$radvdconf .= "\tAdvSourceLLAddress off;\n";
|
||||
$radvdconf .= " AdvSourceLLAddress off;\n";
|
||||
}
|
||||
|
||||
/* to avoid final advertisement with zero router lifetime */
|
||||
$radvdconf .= "\tRemoveAdvOnExit " . (!$entry->RemoveAdvOnExit->isEmpty() ? $entry->RemoveAdvOnExit->getValue() : ($carp_mode ? 'off' : 'on')) . ";\n";
|
||||
$radvdconf .= " RemoveAdvOnExit " . (!$entry->RemoveAdvOnExit->isEmpty() ? $entry->RemoveAdvOnExit->getValue() : ($carp_mode ? 'off' : 'on')) . ";\n";
|
||||
|
||||
/* VIPs may duplicate readings from system */
|
||||
$stanzas = array_unique($stanzas);
|
||||
|
||||
foreach ($stanzas as $stanza) {
|
||||
if ($stanza === 'base6') {
|
||||
$radvdconf .= "\tprefix ::/64 {\n";
|
||||
$radvdconf .= " prefix ::/64 {\n";
|
||||
$baseif = get_real_interface($entry->Base6Interface->getValue(), 'inet6');
|
||||
$radvdconf .= "\t\tBase6Interface {$baseif};\n";
|
||||
$radvdconf .= " Base6Interface {$baseif};\n";
|
||||
} else {
|
||||
$radvdconf .= "\tprefix {$stanza} {\n";
|
||||
$radvdconf .= " prefix {$stanza} {\n";
|
||||
}
|
||||
$radvdconf .= "\t\tDeprecatePrefix " . (!$entry->DeprecatePrefix->isEmpty() ? $entry->DeprecatePrefix->getValue() : ($carp_mode ? 'off' : 'on')) . ";\n";
|
||||
$radvdconf .= " DeprecatePrefix " . (!$entry->DeprecatePrefix->isEmpty() ? $entry->DeprecatePrefix->getValue() : ($carp_mode ? 'off' : 'on')) . ";\n";
|
||||
switch ($entry->mode->getValue()) {
|
||||
case 'assist':
|
||||
case 'stateless':
|
||||
case 'unmanaged':
|
||||
$radvdconf .= "\t\tAdvOnLink on;\n";
|
||||
$radvdconf .= "\t\tAdvAutonomous on;\n";
|
||||
$radvdconf .= " AdvOnLink on;\n";
|
||||
$radvdconf .= " AdvAutonomous on;\n";
|
||||
break;
|
||||
case 'managed':
|
||||
$radvdconf .= "\t\tAdvOnLink on;\n";
|
||||
$radvdconf .= "\t\tAdvAutonomous off;\n";
|
||||
$radvdconf .= " AdvOnLink on;\n";
|
||||
$radvdconf .= " AdvAutonomous off;\n";
|
||||
break;
|
||||
case 'router':
|
||||
$radvdconf .= "\t\tAdvOnLink off;\n";
|
||||
$radvdconf .= "\t\tAdvAutonomous off;\n";
|
||||
$radvdconf .= " AdvOnLink off;\n";
|
||||
$radvdconf .= " AdvAutonomous off;\n";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (!$entry->AdvValidLifetime->isEmpty()) {
|
||||
$radvdconf .= "\t\tAdvValidLifetime {$entry->AdvValidLifetime->getValue()};\n";
|
||||
$radvdconf .= " AdvValidLifetime {$entry->AdvValidLifetime->getValue()};\n";
|
||||
}
|
||||
if (!$entry->AdvPreferredLifetime->isEmpty()) {
|
||||
$radvdconf .= "\t\tAdvPreferredLifetime {$entry->AdvPreferredLifetime->getValue()};\n";
|
||||
$radvdconf .= " AdvPreferredLifetime {$entry->AdvPreferredLifetime->getValue()};\n";
|
||||
}
|
||||
$radvdconf .= "\t};\n";
|
||||
$radvdconf .= " };\n";
|
||||
}
|
||||
|
||||
if (!$entry->routes->isEmpty()) {
|
||||
foreach ($entry->routes->getValues() as $raroute) {
|
||||
$radvdconf .= "\troute {$raroute} {\n";
|
||||
$radvdconf .= "\t\tRemoveRoute " . (!$entry->RemoveRoute->isEmpty() ? $entry->RemoveRoute->getValue() : ($carp_mode ? 'off' : 'on')) . ";\n";
|
||||
$radvdconf .= " route {$raroute} {\n";
|
||||
$radvdconf .= " RemoveRoute " . (!$entry->RemoveRoute->isEmpty() ? $entry->RemoveRoute->getValue() : ($carp_mode ? 'off' : 'on')) . ";\n";
|
||||
if (!$entry->AdvRouteLifetime->isEmpty()) {
|
||||
$radvdconf .= "\t\tAdvRouteLifetime {$entry->AdvRouteLifetime->getValue()};\n";
|
||||
$radvdconf .= " AdvRouteLifetime {$entry->AdvRouteLifetime->getValue()};\n";
|
||||
}
|
||||
$radvdconf .= "\t};\n";
|
||||
$radvdconf .= " };\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (!$entry->nat64prefix->isEmpty()) {
|
||||
$radvdconf .= "\tnat64prefix {$entry->nat64prefix->getValue()} {\n";
|
||||
$radvdconf .= "\t};\n";
|
||||
$radvdconf .= " nat64prefix {$entry->nat64prefix->getValue()} {\n";
|
||||
$radvdconf .= " };\n";
|
||||
}
|
||||
|
||||
$dnssl = [];
|
||||
@ -346,19 +354,19 @@ function radvd_configure_do($verbose = false, $ignorelist = [])
|
||||
}
|
||||
|
||||
if (count($rdnss)) {
|
||||
$radvdconf .= "\tRDNSS " . implode(" ", $rdnss) . " {\n";
|
||||
$radvdconf .= " RDNSS " . implode(" ", $rdnss) . " {\n";
|
||||
if (!$entry->AdvRDNSSLifetime->isEmpty()) {
|
||||
$radvdconf .= "\t\tAdvRDNSSLifetime {$entry->AdvRDNSSLifetime->getValue()};\n";
|
||||
$radvdconf .= " AdvRDNSSLifetime {$entry->AdvRDNSSLifetime->getValue()};\n";
|
||||
}
|
||||
$radvdconf .= "\t};\n";
|
||||
$radvdconf .= " };\n";
|
||||
}
|
||||
|
||||
if (count($dnssl)) {
|
||||
$radvdconf .= "\tDNSSL " . implode(" ", $dnssl) . " {\n";
|
||||
$radvdconf .= " DNSSL " . implode(" ", $dnssl) . " {\n";
|
||||
if (!$entry->AdvDNSSLLifetime->isEmpty()) {
|
||||
$radvdconf .= "\t\tAdvDNSSLLifetime {$entry->AdvDNSSLLifetime->getValue()};\n";
|
||||
$radvdconf .= " AdvDNSSLLifetime {$entry->AdvDNSSLLifetime->getValue()};\n";
|
||||
}
|
||||
$radvdconf .= "\t};\n";
|
||||
$radvdconf .= " };\n";
|
||||
}
|
||||
|
||||
$radvdconf .= "};\n";
|
||||
@ -432,17 +440,17 @@ function radvd_configure_do($verbose = false, $ignorelist = [])
|
||||
|
||||
$radvdconf .= "# Generated RADVD config for {$autotype} assignment from {$trackif} on {$if}\n";
|
||||
$radvdconf .= "interface {$device} {\n";
|
||||
$radvdconf .= "\tAdvSendAdvert on;\n";
|
||||
$radvdconf .= sprintf("\tAdvLinkMTU %s;\n", !empty($mtu) ? $mtu : 0);
|
||||
$radvdconf .= "\tAdvManagedFlag on;\n";
|
||||
$radvdconf .= "\tAdvOtherConfigFlag on;\n";
|
||||
$radvdconf .= " AdvSendAdvert on;\n";
|
||||
$radvdconf .= sprintf(" AdvLinkMTU %s;\n", !empty($mtu) ? $mtu : 0);
|
||||
$radvdconf .= " AdvManagedFlag on;\n";
|
||||
$radvdconf .= " AdvOtherConfigFlag on;\n";
|
||||
|
||||
if (!empty($networkv6)) {
|
||||
$radvdconf .= "\tprefix {$networkv6} {\n";
|
||||
$radvdconf .= "\t\tDeprecatePrefix on;\n";
|
||||
$radvdconf .= "\t\tAdvOnLink on;\n";
|
||||
$radvdconf .= "\t\tAdvAutonomous on;\n";
|
||||
$radvdconf .= "\t};\n";
|
||||
$radvdconf .= " prefix {$networkv6} {\n";
|
||||
$radvdconf .= " DeprecatePrefix on;\n";
|
||||
$radvdconf .= " AdvOnLink on;\n";
|
||||
$radvdconf .= " AdvAutonomous on;\n";
|
||||
$radvdconf .= " };\n";
|
||||
}
|
||||
|
||||
foreach (config_read_array('virtualip', 'vip') as $vip) {
|
||||
@ -462,18 +470,18 @@ function radvd_configure_do($verbose = false, $ignorelist = [])
|
||||
continue;
|
||||
}
|
||||
|
||||
$radvdconf .= "\tprefix {$vipnetv6} {\n";
|
||||
$radvdconf .= "\t\tDeprecatePrefix on;\n";
|
||||
$radvdconf .= "\t\tAdvOnLink on;\n";
|
||||
$radvdconf .= "\t\tAdvAutonomous on;\n";
|
||||
$radvdconf .= "\t};\n";
|
||||
$radvdconf .= " prefix {$vipnetv6} {\n";
|
||||
$radvdconf .= " DeprecatePrefix on;\n";
|
||||
$radvdconf .= " AdvOnLink on;\n";
|
||||
$radvdconf .= " AdvAutonomous on;\n";
|
||||
$radvdconf .= " };\n";
|
||||
}
|
||||
|
||||
if (count($dnslist) > 0) {
|
||||
$radvdconf .= "\tRDNSS " . implode(" ", $dnslist) . " { };\n";
|
||||
$radvdconf .= " RDNSS " . implode(" ", $dnslist) . " { };\n";
|
||||
}
|
||||
if (!empty($config['system']['domain'])) {
|
||||
$radvdconf .= "\tDNSSL {$config['system']['domain']} { };\n";
|
||||
$radvdconf .= " DNSSL {$config['system']['domain']} { };\n";
|
||||
}
|
||||
$radvdconf .= "};\n";
|
||||
}
|
||||
|
||||
@ -280,7 +280,6 @@ function get_locale_list()
|
||||
|
||||
if (!product::getInstance()->development()) {
|
||||
/* below 30% progress or currently unvetted */
|
||||
unset($locales['fa_IR']);
|
||||
unset($locales['vi_VN']);
|
||||
}
|
||||
|
||||
|
||||
@ -922,30 +922,6 @@ function url_safe($format, $args = [])
|
||||
return vsprintf($format, $args);
|
||||
}
|
||||
|
||||
function mwexec($command, $mute = false) /* XXX deprecated */
|
||||
{
|
||||
$oarr = [];
|
||||
$retval = 0;
|
||||
|
||||
$garbage = exec("{$command} 2>&1", $oarr, $retval); /* XXX to be removed */
|
||||
unset($garbage);
|
||||
|
||||
if ($retval != 0 && $mute !== true) {
|
||||
$output = implode(' ', $oarr);
|
||||
log_msg(sprintf("The command '%s'%s returned exit code '%d', the output was '%s'", $command, !empty($mute) ? "($mute) " : '', $retval, $output), LOG_ERR);
|
||||
unset($output);
|
||||
}
|
||||
|
||||
unset($oarr);
|
||||
|
||||
return $retval;
|
||||
}
|
||||
|
||||
function mwexec_bg($command, $mute = false) /* XXX deprecated */
|
||||
{
|
||||
mwexec("/usr/sbin/daemon -f {$command}", $mute);
|
||||
}
|
||||
|
||||
/* safe shell command formatter */
|
||||
function exec_safe($format, $args = [])
|
||||
{
|
||||
|
||||
@ -1,3 +1 @@
|
||||
# configuration for opnsense-update(8), do not edit
|
||||
|
||||
UPGRADE_RELEASE="26.1"
|
||||
|
||||
@ -172,7 +172,7 @@ class ApiControllerBase extends ControllerRoot
|
||||
$this->response->setHeader($parts[0], ltrim($parts[1]));
|
||||
}
|
||||
rewind($stream);
|
||||
$this->response->setContent($stream);
|
||||
$this->response->setContent($stream, true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -31,6 +31,13 @@
|
||||
Can be helpful to determine the maximum size a transport is able to send.
|
||||
</help>
|
||||
</field>
|
||||
<field>
|
||||
<id>ping.settings.interval</id>
|
||||
<label>Interval (seconds)</label>
|
||||
<type>text</type>
|
||||
<hint>1</hint>
|
||||
<help>Specify the number of seconds to wait between sending pings.</help>
|
||||
</field>
|
||||
<field>
|
||||
<id>ping.settings.description</id>
|
||||
<label>Description</label>
|
||||
|
||||
@ -76,22 +76,20 @@ class LeasesController extends ApiControllerBase
|
||||
$reservedKeys = [];
|
||||
|
||||
foreach ((new Dnsmasq())->hosts->iterateItems() as $host) {
|
||||
if (!empty($host->client_id)) {
|
||||
$reservedKeys[] = (string)$host->client_id;
|
||||
if (!$host->client_id->isEmpty()) {
|
||||
$reservedKeys[] = strtolower($host->client_id->getValue());
|
||||
}
|
||||
|
||||
if (!empty($host->hwaddr)) {
|
||||
foreach (explode(',', (string)$host->hwaddr) as $hwaddr) {
|
||||
if (!empty($hwaddr)) {
|
||||
$reservedKeys[] = $hwaddr;
|
||||
}
|
||||
if (!$host->hwaddr->isEmpty()) {
|
||||
foreach ($host->hwaddr->getValues() as $hwaddr) {
|
||||
$reservedKeys[] = strtolower($hwaddr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($records as &$record) {
|
||||
$is_ipv6 = Util::isIpv6Address($record['address'] ?? '');
|
||||
$key = $is_ipv6 ? ($record['client_id'] ?? '') : ($record['hwaddr'] ?? '');
|
||||
$key = strtolower($is_ipv6 ? ($record['client_id'] ?? '') : ($record['hwaddr'] ?? ''));
|
||||
$record['is_reserved'] = in_array($key, $reservedKeys, true) ? '1' : '0';
|
||||
}
|
||||
|
||||
|
||||
@ -114,7 +114,7 @@
|
||||
<id>dnsmasq.add_mac</id>
|
||||
<label>Add MAC</label>
|
||||
<type>dropdown</type>
|
||||
<help>Add the MAC address of the requestor to DNS queries which are forwarded upstream. The MAC address will only be added if the upstream DNS Server is in the same subnet as the requestor. Since this is not standardized, it should be considered experiemental. This is useful for selective DNS filtering on the upstream DNS server.</help>
|
||||
<help>Add the MAC address of the requestor to DNS queries which are forwarded upstream. The MAC address will only be added if the upstream DNS Server is in the same subnet as the requestor. Since this is not standardized, it should be considered experimental. This is useful for selective DNS filtering on the upstream DNS server.</help>
|
||||
<advanced>true</advanced>
|
||||
</field>
|
||||
<field>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* Copyright (C) 2020 Deciso B.V.
|
||||
* Copyright (C) 2020-2026 Deciso B.V.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -25,9 +25,12 @@
|
||||
* 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\FieldTypes\PortField;
|
||||
use OPNsense\Base\UserException;
|
||||
use OPNsense\Core\Backend;
|
||||
use OPNsense\Core\Config;
|
||||
use OPNsense\Firewall\Alias;
|
||||
@ -209,21 +212,37 @@ abstract class FilterBaseController extends ApiMutableModelControllerBase
|
||||
{
|
||||
$result = [
|
||||
'single' => [
|
||||
'label' => gettext("Single port or range"),
|
||||
'label' => gettext('Single port or range'),
|
||||
],
|
||||
'aliases' => [
|
||||
'label' => gettext("Aliases"),
|
||||
'label' => gettext('Aliases'),
|
||||
'items' => [],
|
||||
],
|
||||
// XXX: Well known ports could be gathered from /etc/services but there is a lot of noise
|
||||
'ports' => [
|
||||
'label' => gettext("Ports"),
|
||||
'label' => gettext('Ports'),
|
||||
'items' => [
|
||||
"" => gettext("any"),
|
||||
'' => gettext('any'),
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
/*
|
||||
* XXX Eventually it might make more sense to instantate the
|
||||
* actual protocol fields of the rules in order to get the full
|
||||
* list of options in one go (modifying the model XML to
|
||||
* automatically get the correct values).
|
||||
*
|
||||
* This works using e.g.
|
||||
* (new Filter())->rules->rule->getTemplateNode()->source_port
|
||||
* but loads all rules as a side effect if they exist which we
|
||||
* want to avoid and raises the question how we're going to deal
|
||||
* with every field's own setup as we can't simply derive one
|
||||
* field's accepted values for another.
|
||||
*/
|
||||
foreach (PortField::getWellKnown() as $port) {
|
||||
$result['ports']['items'][$port] = strtoupper($port);
|
||||
}
|
||||
|
||||
foreach ((new Alias())->aliases->alias->iterateItems() as $alias) {
|
||||
if ($alias->type == 'internal') {
|
||||
/* currently only used for legacy bindings, align with legacy_list_aliases() usage */
|
||||
@ -376,9 +395,7 @@ abstract class FilterBaseController extends ApiMutableModelControllerBase
|
||||
$node = $mdl->getNodeByReference($node_reference . '.' . $uuid);
|
||||
|
||||
if ($node === null) {
|
||||
throw new UserException(
|
||||
gettext("Rule not found"),
|
||||
);
|
||||
throw new UserException(gettext('Rule not found'));
|
||||
}
|
||||
|
||||
$node->log = $log;
|
||||
|
||||
@ -30,10 +30,11 @@ namespace OPNsense\Firewall\Api;
|
||||
use OPNsense\Base\UserException;
|
||||
use OPNsense\Core\Config;
|
||||
use OPNsense\Core\Backend;
|
||||
use OPNsense\Firewall\Alias;
|
||||
use OPNsense\Firewall\Category;
|
||||
use OPNsense\Firewall\Filter;
|
||||
use OPNsense\Firewall\Group;
|
||||
use OPNsense\Firewall\Util;
|
||||
use OPNsense\Firewall\Alias;
|
||||
|
||||
class FilterController extends FilterBaseController
|
||||
{
|
||||
@ -81,18 +82,21 @@ class FilterController extends FilterBaseController
|
||||
{
|
||||
$categories = $this->request->get('category');
|
||||
$show_all = !empty($this->request->get('show_all'));
|
||||
if (!empty($this->request->get('interface'))) {
|
||||
$interfaces = explode(',', $this->request->get('interface'));
|
||||
if ($show_all) {
|
||||
/* add groups which contain the selected interface when looking at full impact*/
|
||||
if (!$this->request->has('interface')) {
|
||||
// ALL rules
|
||||
$interfaces = null;
|
||||
} else {
|
||||
// interface param may be empty
|
||||
$interfaces = array_filter(explode(',', (string)$this->request->get('interface')), 'strlen');
|
||||
|
||||
if ($show_all && !empty($interfaces)) {
|
||||
/* add groups which contain the selected interface when looking at full impact */
|
||||
foreach ((new Group())->ifgroupentry->iterateItems() as $groupItem) {
|
||||
if (array_intersect($interfaces, $groupItem->members->getValues())) {
|
||||
$interfaces[] = $groupItem->ifname->getValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$interfaces = [];
|
||||
}
|
||||
|
||||
/* filter logic for mvc rules */
|
||||
@ -100,6 +104,11 @@ class FilterController extends FilterBaseController
|
||||
$is_cat = empty($categories) || array_intersect(explode(',', $record->categories), $categories);
|
||||
$rule_interfaces = $record->interface->getValues();
|
||||
|
||||
// ALL rules, skip interface logic entirely
|
||||
if ($interfaces === null) {
|
||||
return $is_cat;
|
||||
}
|
||||
|
||||
if (!$record->interfacenot->isEmpty()) {
|
||||
$if_intersects = !array_intersect($interfaces, $rule_interfaces); /* All but interface */
|
||||
} else {
|
||||
@ -151,11 +160,12 @@ class FilterController extends FilterBaseController
|
||||
$is_cat = empty($categories) || array_intersect($r_categories, $categories);
|
||||
|
||||
if (!empty($record['interfacenot'])) {
|
||||
$is_if = !array_intersect(explode(',', $record['interface'] ?? ''), $interfaces);
|
||||
$is_if = !array_intersect(explode(',', $record['interface'] ?? ''), $interfaces ?? []);
|
||||
} else {
|
||||
$is_if = array_intersect(explode(',', $record['interface'] ?? ''), $interfaces);
|
||||
$is_if = array_intersect(explode(',', $record['interface'] ?? ''), $interfaces ?? []);
|
||||
}
|
||||
$is_if = $is_if || empty($record['interface']);
|
||||
// ALL interfaces or floating always matches
|
||||
$is_if = $is_if || $interfaces === null || empty($record['interface']);
|
||||
|
||||
if ($is_cat && $is_if) {
|
||||
/* translate/convert legacy fields before returning, similar to mvc handling */
|
||||
@ -371,24 +381,29 @@ class FilterController extends FilterBaseController
|
||||
$result = [
|
||||
'floating' => [
|
||||
'label' => gettext('Floating'),
|
||||
'icon' => 'fa fa-layer-group text-primary',
|
||||
'items' => []
|
||||
'icon' => 'fa fa-layer-group fa-fw text-primary',
|
||||
'items' => [],
|
||||
],
|
||||
'groups' => [
|
||||
'label' => gettext('Groups'),
|
||||
'icon' => 'fa fa-sitemap text-warning',
|
||||
'items' => []
|
||||
'icon' => 'fa fa-sitemap fa-fw text-warning',
|
||||
'items' => [],
|
||||
],
|
||||
'interfaces' => [
|
||||
'label' => gettext('Interfaces'),
|
||||
'icon' => 'fa fa-ethernet text-info',
|
||||
'items' => []
|
||||
]
|
||||
'icon' => 'fa fa-ethernet fa-fw text-info',
|
||||
'items' => [],
|
||||
],
|
||||
'any' => [
|
||||
'label' => gettext('Any'),
|
||||
'icon' => 'fa fa-globe-europe fa-fw',
|
||||
'items' => [],
|
||||
],
|
||||
];
|
||||
|
||||
// Count rules per interface
|
||||
$ruleCounts = [];
|
||||
foreach ((new \OPNsense\Firewall\Filter())->rules->rule->iterateItems() as $rule) {
|
||||
foreach ((new Filter())->rules->rule->iterateItems() as $rule) {
|
||||
$interfaces = $rule->interface->getValues();
|
||||
|
||||
if (!$rule->interfacenot->isEmpty() || count($interfaces) !== 1) {
|
||||
@ -399,6 +414,7 @@ class FilterController extends FilterBaseController
|
||||
$ruleCounts[$interfaces[0]] = ($ruleCounts[$interfaces[0]] ?? 0) + 1;
|
||||
}
|
||||
}
|
||||
$totalRules = array_sum($ruleCounts);
|
||||
|
||||
// Helper to build item with label and count
|
||||
$makeItem = fn($value, $label, $count, $type) => [
|
||||
@ -409,23 +425,30 @@ class FilterController extends FilterBaseController
|
||||
];
|
||||
|
||||
// Floating
|
||||
$result['floating']['items'][] = $makeItem('', gettext('Any'), $ruleCounts['floating'] ?? 0, 'floating');
|
||||
$result['floating']['items'][] = $makeItem('__floating', gettext('Floating'), $ruleCounts['floating'] ?? 0, 'floating');
|
||||
|
||||
// Groups
|
||||
foreach ((new \OPNsense\Firewall\Group())->ifgroupentry->iterateItems() as $groupItem) {
|
||||
$name = (string)$groupItem->ifname;
|
||||
$result['groups']['items'][] = $makeItem($name, $name, $ruleCounts[$name] ?? 0, 'group');
|
||||
foreach ((new Group())->ifgroupentry->iterateItems() as $groupItem) {
|
||||
$name = $groupItem->ifname->getValue();
|
||||
$descr = $groupItem->descr->getValue();
|
||||
$descr = empty($descr) ? $name : "{$descr} ($name)";
|
||||
|
||||
$result['groups']['items'][] = $makeItem($name, $descr, $ruleCounts[$name] ?? 0, 'group');
|
||||
}
|
||||
|
||||
// Interfaces
|
||||
$groupKeys = array_column($result['groups']['items'], 'value');
|
||||
foreach (\OPNsense\Core\Config::getInstance()->object()->interfaces->children() as $key => $intf) {
|
||||
if (!in_array($key, $groupKeys)) {
|
||||
foreach (Config::getInstance()->object()->interfaces->children() as $key => $intf) {
|
||||
// XXX: Loopback excluded since no rules should be on there
|
||||
if (!in_array($key, array_merge($groupKeys, ['lo0']))) {
|
||||
$descr = !empty($intf->descr) ? (string)$intf->descr : strtoupper($key);
|
||||
$result['interfaces']['items'][] = $makeItem($key, $descr, $ruleCounts[$key] ?? 0, 'interface');
|
||||
}
|
||||
}
|
||||
|
||||
// ALL rules
|
||||
$result['any']['items'][] = $makeItem('__any', gettext('All rules'), $totalRules, 'any');
|
||||
|
||||
// Sort items by count and alphabetically
|
||||
foreach ($result as &$section) {
|
||||
usort($section['items'], fn($a, $b) =>
|
||||
@ -441,14 +464,18 @@ class FilterController extends FilterBaseController
|
||||
if ($this->request->isGet()) {
|
||||
/* categories have unique names, export names instead of ids so we can easily map them on other targets */
|
||||
$categories = [];
|
||||
$aliases = [];
|
||||
foreach ((new Category())->categories->category->iterateItems() as $key => $category) {
|
||||
$categories[$key] = $category->name->getValue();
|
||||
}
|
||||
foreach ((new Alias())->aliases->alias->iterateItems() as $key => $alias) {
|
||||
$aliases[$key] = $alias->name->getValue();
|
||||
}
|
||||
/* XXX: as shaper1/2 don't have functional keys, we can only export uuid's here*/
|
||||
$this->exportCsv($this->getModel()->rules->rule->asRecordSet(
|
||||
false,
|
||||
['sort_order', 'prio_group'],
|
||||
function ($node, $record) use ($categories) {
|
||||
function ($node, $record) use ($categories, $aliases) {
|
||||
if (!empty($record['categories'])) {
|
||||
$cats = [];
|
||||
foreach (explode(',', $record['categories']) as $key) {
|
||||
@ -458,6 +485,9 @@ class FilterController extends FilterBaseController
|
||||
}
|
||||
$record['categories'] = implode(',', $cats);
|
||||
}
|
||||
if (!empty($record['overload']) && isset($aliases[$record['overload']])) {
|
||||
$record['overload'] = $aliases[$record['overload']];
|
||||
}
|
||||
return array_merge(['@uuid' => $node->getAttribute('uuid')], $record);
|
||||
}
|
||||
));
|
||||
@ -469,15 +499,18 @@ class FilterController extends FilterBaseController
|
||||
if ($this->request->isPost() && $this->request->hasPost('payload')) {
|
||||
/* catgories have unique names, need to map them to uuids */
|
||||
$categories = [];
|
||||
$aliases = [];
|
||||
foreach ((new Category())->categories->category->iterateItems() as $key => $category) {
|
||||
$categories[$category->name->getValue()] = $key;
|
||||
}
|
||||
|
||||
foreach ((new Alias())->aliases->alias->iterateItems() as $key => $alias) {
|
||||
$aliases[$alias->name->getValue()] = $key;
|
||||
}
|
||||
return $this->importCsv(
|
||||
'rules.rule',
|
||||
$this->request->getPost('payload'),
|
||||
['@uuid'],
|
||||
function (&$record) use ($categories) {
|
||||
function (&$record) use ($categories, $aliases) {
|
||||
if (!empty($record['categories'])) {
|
||||
/* only map what we know, ignore the rest */
|
||||
$cats = [];
|
||||
@ -488,6 +521,9 @@ class FilterController extends FilterBaseController
|
||||
}
|
||||
$record['categories'] = implode(',', $cats);
|
||||
}
|
||||
if (!empty($record['overload']) && isset($aliases[$record['overload']])) {
|
||||
$record['overload'] = $aliases[$record['overload']];
|
||||
}
|
||||
}
|
||||
);
|
||||
} else {
|
||||
|
||||
@ -279,6 +279,16 @@
|
||||
<visible>false</visible>
|
||||
</grid_view>
|
||||
</field>
|
||||
<field>
|
||||
<id>rule.tcpflags_any</id>
|
||||
<label>TCP flags any</label>
|
||||
<type>checkbox</type>
|
||||
<help>Use this to choose TCP flags that must be cleared for this rule to match.</help>
|
||||
<advanced>true</advanced>
|
||||
<grid_view>
|
||||
<visible>false</visible>
|
||||
</grid_view>
|
||||
</field>
|
||||
<field>
|
||||
<id>rule.sched</id>
|
||||
<label>Schedule</label>
|
||||
@ -286,6 +296,7 @@
|
||||
<advanced>true</advanced>
|
||||
<grid_view>
|
||||
<visible>false</visible>
|
||||
<formatter>sched</formatter>
|
||||
</grid_view>
|
||||
</field>
|
||||
<!-- XXX: start as advanced, promote to non-advanced feature in a later version -->
|
||||
@ -652,7 +663,6 @@
|
||||
<grid_view>
|
||||
<formatter>statistics</formatter>
|
||||
<sequence>115</sequence>
|
||||
<min-width>200</min-width>
|
||||
</grid_view>
|
||||
</field>
|
||||
<field>
|
||||
|
||||
@ -30,6 +30,7 @@ namespace OPNsense\Hostdiscovery\Api;
|
||||
|
||||
use OPNsense\Base\ApiMutableServiceControllerBase;
|
||||
use OPNsense\Core\Backend;
|
||||
use OPNsense\Core\Config;
|
||||
|
||||
class ServiceController extends ApiMutableServiceControllerBase
|
||||
{
|
||||
@ -40,12 +41,21 @@ class ServiceController extends ApiMutableServiceControllerBase
|
||||
|
||||
public function searchAction()
|
||||
{
|
||||
$if_descr = [];
|
||||
|
||||
foreach ((Config::getInstance()->object())->interfaces->children() as $key => $node) {
|
||||
if (!empty((string)$node->if)) {
|
||||
$descr = (string)$node->descr;
|
||||
$if_descr[(string)$node->if] = strlen($descr) ? $descr : strtoupper($key);
|
||||
}
|
||||
}
|
||||
|
||||
$data = json_decode((new Backend())->configdRun('hostwatch dump_full'), true) ?? [];
|
||||
$records = [];
|
||||
foreach ((!empty($data) && !empty($data['rows'])) ? $data['rows'] : [] as $rec) {
|
||||
$records[] = [
|
||||
'source' => $data['source'],
|
||||
'interface_name' => $rec[0],
|
||||
'interface_name' => $if_descr[$rec[0]] ?? $rec[0],
|
||||
'ether_address' => $rec[1],
|
||||
'ip_address' => $rec[2],
|
||||
'organization_name' => $rec[3],
|
||||
|
||||
@ -31,10 +31,10 @@ namespace OPNsense\IPsec\Api;
|
||||
use OPNsense\Base\ApiMutableModelControllerBase;
|
||||
|
||||
/**
|
||||
* Class ManualSPD
|
||||
* Class ManualSpd
|
||||
* @package OPNsense\IPsec\Api
|
||||
*/
|
||||
class ManualSPDController extends ApiMutableModelControllerBase
|
||||
class ManualSpdController extends ApiMutableModelControllerBase
|
||||
{
|
||||
protected static $internalModelName = 'swanctl';
|
||||
protected static $internalModelClass = 'OPNsense\IPsec\Swanctl';
|
||||
|
||||
@ -28,7 +28,7 @@
|
||||
|
||||
namespace OPNsense\Interfaces;
|
||||
|
||||
class VxLanController extends \OPNsense\Base\IndexController
|
||||
class VxlanController extends \OPNsense\Base\IndexController
|
||||
{
|
||||
public function indexAction()
|
||||
{
|
||||
|
||||
@ -60,16 +60,21 @@ abstract class LeasesController extends ApiControllerBase
|
||||
];
|
||||
}
|
||||
|
||||
// Mark records as reserved based on hwaddr (IPv4) or duid (IPv6) match
|
||||
// Mark records as reserved based on hwaddr (IPv4) or duid/hwaddr (IPv6) match
|
||||
$resv4 = [];
|
||||
$resv6 = [];
|
||||
|
||||
foreach ((new KeaDhcpv4())->reservations->reservation->iterateItems() as $reservation) {
|
||||
$resv4[strtolower((string)$reservation->hw_address)] = true;
|
||||
$resv4[strtolower($reservation->hw_address->getValue())] = 'hwaddr';
|
||||
}
|
||||
|
||||
foreach ((new KeaDhcpv6())->reservations->reservation->iterateItems() as $reservation) {
|
||||
$resv6[strtolower((string)$reservation->duid)] = true;
|
||||
// At least one of these is required in the model
|
||||
if (!$reservation->duid->isEmpty()) {
|
||||
$resv6[strtolower($reservation->duid->getValue())] = 'duid';
|
||||
} elseif (!$reservation->hw_address->isEmpty()) {
|
||||
$resv6[strtolower($reservation->hw_address->getValue())] = 'hwaddr';
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($leases) && isset($leases['records'])) {
|
||||
@ -87,13 +92,21 @@ abstract class LeasesController extends ApiControllerBase
|
||||
$mac = strtoupper(substr(str_replace(':', '', $record['hwaddr']), 0, 6));
|
||||
$record['mac_info'] = isset($mac_db[$mac]) ? $mac_db[$mac] : '';
|
||||
// Reservation
|
||||
$record['is_reserved'] = '';
|
||||
$addr = $record['address'] ?? '';
|
||||
if (strpos($addr, ':') !== false) {
|
||||
$duid = strtolower($record['duid'] ?? '');
|
||||
$record['is_reserved'] = isset($resv6[$duid]) ? '1' : '0';
|
||||
$mac = strtolower($record['hwaddr'] ?? '');
|
||||
if (isset($resv6[$duid])) {
|
||||
$record['is_reserved'] = $resv6[$duid];
|
||||
} elseif (isset($resv6[$mac])) {
|
||||
$record['is_reserved'] = $resv6[$mac];
|
||||
}
|
||||
} else {
|
||||
$mac_lower = strtolower($record['hwaddr'] ?? '');
|
||||
$record['is_reserved'] = isset($resv4[$mac_lower]) ? '1' : '0';
|
||||
$mac = strtolower($record['hwaddr'] ?? '');
|
||||
if (isset($resv4[$mac])) {
|
||||
$record['is_reserved'] = $resv4[$mac];
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
@ -41,7 +41,9 @@ class ConfigMaintenance
|
||||
{
|
||||
$this->modelmap = $this->loadModels();
|
||||
$this->legacyPaths = [
|
||||
'filter.rule' => gettext("Firewall / Rules (legacy)")
|
||||
'dhcpd' => gettext('Services: ISC DHCPv4 [legacy]'),
|
||||
'dhcpdv6' => gettext('Services: ISC DHCPv6 [legacy]'),
|
||||
'filter.rule' => gettext('Firewall: Rules [legacy]'),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@ -40,6 +40,8 @@ class Shell
|
||||
*/
|
||||
public static function exec_safe($format, $args = [])
|
||||
{
|
||||
$command = '/usr/bin/true';
|
||||
|
||||
if (!is_array($format)) {
|
||||
$format = [$format];
|
||||
}
|
||||
@ -49,10 +51,17 @@ class Shell
|
||||
}
|
||||
|
||||
foreach ($args as $id => $arg) {
|
||||
/* XXX casts to string, formatter only really supports %% and %s */
|
||||
$args[$id] = escapeshellarg($arg ?? '');
|
||||
}
|
||||
|
||||
return vsprintf(implode(' ', $format), $args);
|
||||
try {
|
||||
$command = vsprintf(implode(' ', $format), $args);
|
||||
} catch (\Error $e) {
|
||||
error_log($e);
|
||||
}
|
||||
|
||||
return $command;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -36,6 +36,7 @@ class Syslog
|
||||
private $name = null;
|
||||
private $option = null;
|
||||
private $facility = null;
|
||||
private static $echo_stdout = false;
|
||||
|
||||
public function __construct($name, $option = null, $facility = null)
|
||||
{
|
||||
@ -48,6 +49,9 @@ class Syslog
|
||||
{
|
||||
openlog($this->name, $this->option, $this->facility);
|
||||
syslog($level, $message);
|
||||
if (self::$echo_stdout) {
|
||||
echo sprintf("%s\n", trim($message));
|
||||
}
|
||||
}
|
||||
|
||||
public function alert($message)
|
||||
@ -90,4 +94,20 @@ class Syslog
|
||||
{
|
||||
$this->send(LOG_WARNING, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable local (stdout) logging for all syslog instances for this application, for debug purposes
|
||||
*/
|
||||
public static function enableLocalEcho()
|
||||
{
|
||||
self::$echo_stdout = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable local (stdout) logging for all syslog instances for this application
|
||||
*/
|
||||
public static function disableLocalEcho()
|
||||
{
|
||||
self::$echo_stdout = false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -198,7 +198,7 @@ class FilterRule extends Rule
|
||||
}
|
||||
// restructure flags
|
||||
if (isset($rule['protocol']) && $rule['protocol'] == "tcp") {
|
||||
if (isset($rule['tcpflags_any'])) {
|
||||
if (!empty($rule['tcpflags_any'])) {
|
||||
$rule['flags'] = "any";
|
||||
} elseif (!empty($rule['tcpflags2'])) {
|
||||
$rule['flags'] = "";
|
||||
|
||||
@ -239,6 +239,8 @@ class Plugin
|
||||
foreach (explode(',', $types) as $type) {
|
||||
foreach ($this->anchors as $anchorKey => $anchor) {
|
||||
if (strpos($anchorKey, "{$type}.{$placement}") === 0) {
|
||||
$result .= $type == "fw" ? "" : "{$type}-";
|
||||
$result .= "anchor \"{$anchor['name']}\"";
|
||||
if ($anchor['quick']) {
|
||||
$result .= " quick";
|
||||
}
|
||||
|
||||
@ -476,6 +476,16 @@ abstract class Rule
|
||||
return 'internal2'; // late
|
||||
}
|
||||
|
||||
public function updateDescription($descr)
|
||||
{
|
||||
$this->rule['descr'] = $descr;
|
||||
}
|
||||
|
||||
public function disable()
|
||||
{
|
||||
$this->rule['disabled'] = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* return raw rule
|
||||
*/
|
||||
|
||||
@ -177,6 +177,38 @@ class Util
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* fetch alias by uuid from model
|
||||
* @param string $uuid
|
||||
* @return Alias|null
|
||||
*/
|
||||
private static function getAliasByUuid($uuid)
|
||||
{
|
||||
if (self::$aliasObject == null) {
|
||||
// Cache the alias object to avoid object creation overhead.
|
||||
self::$aliasObject = new Alias(true);
|
||||
self::$aliasObject->flushCache();
|
||||
}
|
||||
if (!empty($uuid)) {
|
||||
return self::$aliasObject->getNodeByReference('aliases.alias.' . $uuid);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* resolve alias uuid to alias name
|
||||
* @param string $uuid
|
||||
* @return string|null
|
||||
*/
|
||||
public static function aliasUuidToName($uuid)
|
||||
{
|
||||
$alias = self::getAliasByUuid($uuid);
|
||||
if ($alias !== null) {
|
||||
return $alias->name->getValue();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* fetch alias by name from model
|
||||
* @param string $name name
|
||||
|
||||
@ -752,7 +752,15 @@ abstract class BaseModel
|
||||
* e.g. /system/user should place new entries at /system
|
||||
**/
|
||||
$pxpath = implode("/", array_slice(explode("/", $xpath), 0, -1));
|
||||
$toDom = dom_import_simplexml($target_node->xpath($pxpath)[0]);
|
||||
$target = $target_node->xpath($pxpath);
|
||||
if (empty($target)) {
|
||||
/* target doesn't exist yet, create the root node by traversing the tree (starting inside /opnsense) */
|
||||
$target = $target_node;
|
||||
foreach (array_slice(explode("/", trim($xpath, "/")), 1, -1) as $p) {
|
||||
$target = count($target_node->xpath($p)) == 0 ? $target->addChild($p) : $target->xpath($p)[0];
|
||||
}
|
||||
}
|
||||
$toDom = dom_import_simplexml($target[0]);
|
||||
foreach ($newNodes as $node) {
|
||||
if ($node !== null) {
|
||||
$toDom->appendChild($toDom->ownerDocument->importNode($node, true));
|
||||
|
||||
@ -331,8 +331,29 @@ class ArrayField extends BaseField
|
||||
}
|
||||
|
||||
foreach ($records as $idx => $record) {
|
||||
$uuid = $record['@uuid'] ?? '';
|
||||
if (
|
||||
!empty($uuid) &&
|
||||
(
|
||||
!is_string($uuid) ||
|
||||
preg_match('/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/', $uuid) !== 1
|
||||
)
|
||||
) {
|
||||
$results['validations'][] = [
|
||||
'sequence' => $idx,
|
||||
'field' => '@uuid',
|
||||
'message' => sprintf("Invalid UUID offered (%s)", $uuid)
|
||||
];
|
||||
continue;
|
||||
}
|
||||
if (is_callable($data_callback)) {
|
||||
$data_callback($record);
|
||||
try {
|
||||
$data_callback($record);
|
||||
} catch (\Exception $e) {
|
||||
/* callbacks may trow errors to report validation issues */
|
||||
$results['validations'][] = ['sequence' => $idx, 'message' => $e->getMessage()];
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$keydata = [];
|
||||
foreach ($keyfields as $keyfield) {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* Copyright (C) 2015-2020 Deciso B.V.
|
||||
* Copyright (C) 2015-2026 Deciso B.V.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -39,7 +39,7 @@ class PortField extends BaseListField
|
||||
/**
|
||||
* @var array list of well known services
|
||||
*/
|
||||
private static $wellknownservices = [
|
||||
protected static $wellknownservices = [
|
||||
'cvsup',
|
||||
'domain',
|
||||
'ftp',
|
||||
@ -107,6 +107,15 @@ class PortField extends BaseListField
|
||||
*/
|
||||
private $enableAlias = false;
|
||||
|
||||
/**
|
||||
* get the list of well known services
|
||||
* @return array service names
|
||||
*/
|
||||
public static function getWellKnown()
|
||||
{
|
||||
return self::$wellknownservices;
|
||||
}
|
||||
|
||||
/**
|
||||
* generate validation data (list of port numbers and well know ports)
|
||||
*/
|
||||
|
||||
@ -25,6 +25,10 @@
|
||||
<MaximumValue>65535</MaximumValue>
|
||||
</packetsize>
|
||||
<disable_frag type="BooleanField"/>
|
||||
<interval type="IntegerField">
|
||||
<MinimumValue>1</MinimumValue>
|
||||
<MaximumValue>120</MaximumValue>
|
||||
</interval>
|
||||
<description type="DescriptionField"/>
|
||||
</settings>
|
||||
</items>
|
||||
|
||||
@ -49,7 +49,7 @@
|
||||
<EnableWellKnown>Y</EnableWellKnown>
|
||||
<EnableRanges>Y</EnableRanges>
|
||||
<EnableAlias>Y</EnableAlias>
|
||||
<ValidationMessage>Please specify a valid portnumber, name, alias or range.</ValidationMessage>
|
||||
<ValidationMessage>Please specify a valid port number, well-known name, alias or range.</ValidationMessage>
|
||||
</port>
|
||||
<not type="BooleanField"/>
|
||||
</source>
|
||||
@ -61,14 +61,14 @@
|
||||
<EnableWellKnown>Y</EnableWellKnown>
|
||||
<EnableRanges>Y</EnableRanges>
|
||||
<EnableAlias>Y</EnableAlias>
|
||||
<ValidationMessage>Please specify a valid portnumber, name, alias or range.</ValidationMessage>
|
||||
<ValidationMessage>Please specify a valid port number, well-known name, alias or range.</ValidationMessage>
|
||||
</port>
|
||||
<not type="BooleanField"/>
|
||||
</destination>
|
||||
<target type="NetworkAliasField"/>
|
||||
<local-port type="PortField">
|
||||
<EnableAlias>Y</EnableAlias>
|
||||
<ValidationMessage>Please specify a valid portnumber, name, alias or range.</ValidationMessage>
|
||||
<ValidationMessage>Please specify a valid port number or alias.</ValidationMessage>
|
||||
</local-port>
|
||||
<!-- -->
|
||||
<poolopts type="OptionField">
|
||||
|
||||
@ -29,6 +29,7 @@
|
||||
namespace OPNsense\Firewall\FieldTypes;
|
||||
|
||||
use OPNsense\Core\Config;
|
||||
use OPNsense\Firewall\Util;
|
||||
use OPNsense\Firewall\Group;
|
||||
use OPNsense\Base\FieldTypes\ArrayField;
|
||||
use OPNsense\Base\FieldTypes\ContainerField;
|
||||
@ -88,6 +89,15 @@ class FilterRuleContainerField extends ContainerField
|
||||
$result['to_port'] = (string)$this->destination_port;
|
||||
}
|
||||
}
|
||||
if (!$this->overload->isEmpty()) {
|
||||
$alias = Util::aliasUuidToName($this->overload->getValue());
|
||||
if ($alias !== null) {
|
||||
$result['overload'] = $alias;
|
||||
} else {
|
||||
// fall back to default virusprod table
|
||||
unset($result['overload']);
|
||||
}
|
||||
}
|
||||
// field mappings and differences
|
||||
$result['disabled'] = empty((string)$this->enabled);
|
||||
$result['descr'] = (string)$this->description;
|
||||
|
||||
@ -133,7 +133,7 @@ class Filter extends BaseModel
|
||||
if (!in_array($rule->protocol, ['TCP', 'TCP/UDP'])) {
|
||||
foreach (
|
||||
[
|
||||
'statetimeout', 'max-src-conn', 'tcpflags1', 'tcpflags2',
|
||||
'statetimeout', 'max-src-conn', 'tcpflags1', 'tcpflags2', 'tcpflags_any',
|
||||
'max-src-conn-rate', 'max-src-conn-rates', 'overload'
|
||||
] as $fieldname
|
||||
) {
|
||||
@ -230,6 +230,13 @@ class Filter extends BaseModel
|
||||
));
|
||||
}
|
||||
|
||||
if (!$rule->tcpflags1->isEmpty() && !$rule->tcpflags2->isEmpty() && !$rule->tcpflags_any->isEmpty()) {
|
||||
$messages->appendMessage(new Message(
|
||||
gettext("If you select any TCP flags, you cannot also select specific ones."),
|
||||
$rule->tcpflags_any->__reference
|
||||
));
|
||||
}
|
||||
|
||||
if ((string)$rule->{'set-prio'} == '' && (string)$rule->{'set-prio-low'} != '') {
|
||||
$messages->appendMessage(new Message(
|
||||
gettext("Set priority for low latency and acknowledgements " .
|
||||
@ -366,4 +373,22 @@ class Filter extends BaseModel
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function hasSchedule()
|
||||
{
|
||||
foreach ($this->rules->rule->iterateItems() as $rule) {
|
||||
if (!$rule->sched->isEmpty() && !$rule->enabled->isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
$cfg = (\OPNsense\Core\Config::getInstance())->object();
|
||||
if (isset($cfg->filter->rule)) {
|
||||
foreach ($cfg->filter->children() as $tag => $rule) {
|
||||
if ($tag === 'rule' && !empty($rule->sched) && empty((string)$rule->disabled)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -229,7 +229,7 @@
|
||||
</max>
|
||||
<max-src-conn-rate type="IntegerField">
|
||||
<MinimumValue>1</MinimumValue>
|
||||
<MaximumValue>2147483647</MaximumValue>
|
||||
<MaximumValue>4294967</MaximumValue>
|
||||
</max-src-conn-rate>
|
||||
<max-src-conn-rates type="IntegerField">
|
||||
<MinimumValue>1</MinimumValue>
|
||||
@ -328,6 +328,7 @@
|
||||
<cwr>cwr</cwr>
|
||||
</OptionValues>
|
||||
</tcpflags2>
|
||||
<tcpflags_any type="BooleanField"/>
|
||||
<categories type="ModelRelationField">
|
||||
<Model>
|
||||
<rulesets>
|
||||
|
||||
@ -61,7 +61,7 @@ class SET1_0_0 extends BaseModelMigration
|
||||
if (isset($config->system->$key)) {
|
||||
$nodes[$_key] = (string)$config->system->$key;
|
||||
}
|
||||
} elseif ($key == 'dhcp6_debug') {
|
||||
} elseif (in_array($key, ['dhcp6_debug', 'dhcp6_norelease'])) {
|
||||
$nodes[$key] = isset($config->system->$key) ? '1' : '0';
|
||||
} elseif (isset($config->system->$key)) {
|
||||
$nodes[$key] = (string)$config->system->$key;
|
||||
|
||||
@ -91,6 +91,31 @@ class KeaDhcpv6 extends BaseModel
|
||||
}
|
||||
}
|
||||
}
|
||||
// validate changed pd_pools
|
||||
foreach ($this->pd_pools->pd_pool->iterateItems() as $pool) {
|
||||
if (!$validateFullModel && !$pool->isFieldChanged()) {
|
||||
continue;
|
||||
}
|
||||
$key = $pool->__reference;
|
||||
if ($pool->prefix_len->getValue() >= $pool->delegated_len->getValue()) {
|
||||
$messages->appendMessage(new Message(gettext("Delegated length must be longer than or equal to prefix length"), $key . ".delegated_len"));
|
||||
}
|
||||
$subnet = $pool->prefix->getValue() . "/" . $pool->prefix_len->getValue();
|
||||
$trange = Util::cidrToRange($subnet);
|
||||
if (!Util::isSubnetStrict($subnet)) {
|
||||
$messages->appendMessage(new Message(gettext("Invalid Pool boundaries, offered address is not the first address in the prefix."), $key . ".prefix"));
|
||||
}
|
||||
foreach ($this->pd_pools->pd_pool->iterateItems() as $tmppool) {
|
||||
if ($key === $tmppool->__reference) {
|
||||
continue;
|
||||
}
|
||||
$osubnet = $tmppool->prefix->getValue() . "/" . $tmppool->prefix_len->getValue();
|
||||
$orange = Util::cidrToRange($osubnet);
|
||||
if (Util::isIPInCIDR($orange[0], $subnet) || Util::isIPInCIDR($trange[0], $osubnet)) {
|
||||
$messages->appendMessage(new Message(gettext("Pool overlaps with an existing one."), $key . ".prefix"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $messages;
|
||||
}
|
||||
|
||||
@ -954,7 +954,7 @@
|
||||
<td>
|
||||
<select id="alias.authtype" data-container="body" class="selectpicker" style="margin-bottom: 3px;"></select>
|
||||
<input type="text" placeholder="{{lang._('Username')}}" class="form-control" size="50" id="alias.username"/>
|
||||
<input type="password" class="form-control" size="50" id="alias.password"/>
|
||||
<input type="password" class="form-control" autocomplete="new-password" size="50" id="alias.password"/>
|
||||
<div class="hidden" data-for="help_for_alias.authtype">
|
||||
<small>
|
||||
{{lang._('If the remote server enforces authorization, you can specify the authorization type here.')}}
|
||||
|
||||
@ -159,7 +159,7 @@
|
||||
title="${row.log == '1'
|
||||
? '{{ lang._("Disable Logging") }}'
|
||||
: '{{ lang._("Enable Logging") }}'}">
|
||||
<i class="fa fa-exclamation-circle fa-fw ${row.log == '1' ? 'text-info' : 'text-muted'}"></i>
|
||||
<i class="fa fa-fw ${row.log == '1' ? 'fa-bell' : 'fa-bell-slash'}"></i>
|
||||
</button>
|
||||
|
||||
<button type="button" class="btn btn-xs btn-default command-edit
|
||||
@ -493,8 +493,16 @@
|
||||
|
||||
ajaxGet('/api/firewall/d_nat/list_port_select_options', [], function (data) {
|
||||
if (!data || !data.single) return;
|
||||
// local-port does not support port ranges, so we replace the label for clarity
|
||||
const singlePortOnly = $.extend(true, {}, data);
|
||||
singlePortOnly.single.label = "{{ lang._('Single port') }}";
|
||||
|
||||
$(".port_selector").each(function () {
|
||||
$(this).replaceInputWithSelector(data, false);
|
||||
const opts = $(this).is('#row_rule\\.local-port .port_selector')
|
||||
? singlePortOnly
|
||||
: data;
|
||||
|
||||
$(this).replaceInputWithSelector(opts, false);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@ -108,7 +108,8 @@
|
||||
/* hook import/export buttons */
|
||||
$("#upload_rules").SimpleFileUploadDlg({
|
||||
onAction: function(){
|
||||
$('#{{formGridFilterRule["edit_dialog_id"]}}').bootgrid('reload');
|
||||
$("#{{formGridFilterRule['table_id']}}").bootgrid('reload');
|
||||
$("#change_message_base_form").stop(true, false).slideDown(1000).delay(2000).slideUp(2000);
|
||||
}
|
||||
});
|
||||
|
||||
@ -162,11 +163,15 @@
|
||||
}
|
||||
// Add interface selectpicker, or fall back to hash for the first load
|
||||
let selectedInterface = $('#interface_select').val();
|
||||
if ((!selectedInterface || selectedInterface.length === 0) && pendingUrlInterface) {
|
||||
request['interface'] = pendingUrlInterface;
|
||||
if (selectedInterface == null && pendingUrlInterface != null) {
|
||||
selectedInterface = pendingUrlInterface;
|
||||
pendingUrlInterface = null; // consume the hash so it is not used again
|
||||
} else if (selectedInterface && selectedInterface.length > 0) {
|
||||
request['interface'] = selectedInterface;
|
||||
}
|
||||
if (selectedInterface === '__floating') {
|
||||
request.interface = '';
|
||||
} else if (selectedInterface !== null && selectedInterface !== '__any') {
|
||||
request.interface = selectedInterface;
|
||||
// '__any' omit parameter for all rules
|
||||
}
|
||||
if (inspectEnabled) {
|
||||
// Send as a comma separated string
|
||||
@ -267,7 +272,7 @@
|
||||
title="${row.log == '1'
|
||||
? '{{ lang._("Disable Logging") }}'
|
||||
: '{{ lang._("Enable Logging") }}'}">
|
||||
<i class="fa fa-exclamation-circle fa-fw ${row.log == '1' ? 'text-info' : 'text-muted'}"></i>
|
||||
<i class="fa fa-fw ${row.log == '1' ? 'fa-bell' : 'fa-bell-slash'}"></i>
|
||||
</button>
|
||||
|
||||
<button type="button" class="btn btn-xs btn-default command-edit
|
||||
@ -317,9 +322,8 @@
|
||||
}
|
||||
|
||||
if (
|
||||
row[column.id] !== '' &&
|
||||
row[column.id] !== 'any' &&
|
||||
row[column.id] !== 'None'
|
||||
row[column.id] !== undefined &&
|
||||
!['', 'any', 'None'].includes(row[column.id])
|
||||
) {
|
||||
return row[column.id];
|
||||
} else {
|
||||
@ -444,7 +448,7 @@
|
||||
const usedAdvancedFields = [];
|
||||
|
||||
advancedFieldIds.forEach(function (fieldId) {
|
||||
const value = row[fieldId];
|
||||
const value = row["%" + fieldId] ?? row[fieldId];
|
||||
if (value !== undefined) {
|
||||
const lowerValue = value.toString().toLowerCase().trim();
|
||||
// Check: if the value is empty OR starts with any default prefix, consider it default
|
||||
@ -522,8 +526,9 @@
|
||||
const states = row["states"] ?? "";
|
||||
const packets = row["packets"] ?? "";
|
||||
const bytes = row["bytes"] ?? "";
|
||||
const uuid = row["uuid"] ?? "";
|
||||
|
||||
function render(icon, title, value, is_number = false) {
|
||||
function render(icon, title, value, is_number = false, link = null) {
|
||||
if (!value || value === "0") {
|
||||
return "";
|
||||
}
|
||||
@ -533,14 +538,18 @@
|
||||
|
||||
return `
|
||||
<span data-toggle="tooltip" title="${title}: ${numValue.toLocaleString()}">
|
||||
<i class="fa fa-fw ${icon} text-muted"></i> ${formatted}
|
||||
${link
|
||||
? `<a href="${link}" target="_blank" rel="noopener noreferrer" id="${uuid}_states">
|
||||
<i class="fa fa-fw ${icon}"></i> ${formatted}
|
||||
</a>`
|
||||
: `<i class="fa fa-fw ${icon}"></i> ${formatted}`}
|
||||
</span>
|
||||
`;
|
||||
}
|
||||
|
||||
const parts = [
|
||||
render("fa-chart-line", "{{ lang._('States') }}", states, true, `/ui/diagnostics/firewall/states#${uuid}`),
|
||||
render("fa-bullseye", "{{ lang._('Evaluations') }}", evals, true),
|
||||
render("fa-chart-line", "{{ lang._('States') }}", states, true),
|
||||
render("fa-box", "{{ lang._('Packets') }}", packets, true),
|
||||
render("fa-database", "{{ lang._('Bytes') }}", bytes)
|
||||
].filter(Boolean);
|
||||
@ -549,17 +558,23 @@
|
||||
return "";
|
||||
}
|
||||
|
||||
// Split into two vertical rows
|
||||
const firstGroup = parts.slice(0, 2).join(" ");
|
||||
const secondGroup = parts.slice(2).join(" ");
|
||||
|
||||
return `
|
||||
<div class="stats-cell">
|
||||
<div>${firstGroup}</div>
|
||||
<div>${secondGroup}</div>
|
||||
${parts.join("")}
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
sched: function(column, row) {
|
||||
if (row.isGroup || typeof row[column.id] !== "string" || row[column.id] === "") {
|
||||
return "";
|
||||
}
|
||||
return `
|
||||
${row[column.id]}
|
||||
<a href="/firewall_schedule_edit.php?name=${row[column.id]}" data-toggle="tooltip" title="{{ lang._('Edit') }}">
|
||||
<i class="fa fa-calendar text-muted"></i>
|
||||
</a>
|
||||
`;
|
||||
},
|
||||
},
|
||||
},
|
||||
commands: {
|
||||
@ -716,7 +731,8 @@
|
||||
const bgClassMap = {
|
||||
floating: 'bg-primary',
|
||||
group: 'bg-warning',
|
||||
interface: 'bg-info'
|
||||
interface: 'bg-info',
|
||||
any: ''
|
||||
};
|
||||
const badgeClass = bgClassMap[item.type] || 'bg-info';
|
||||
|
||||
@ -727,7 +743,6 @@
|
||||
<span>
|
||||
${count > 0 ? `<span class="badge badge-sm ${badgeClass}">${count}</span>` : ''}
|
||||
${label}
|
||||
<small class="text-muted ms-2"><em>${subtext}</em></small>
|
||||
</span>
|
||||
`.trim()
|
||||
};
|
||||
@ -745,6 +760,9 @@
|
||||
if (allOptions.includes(ifaceFromHash)) {
|
||||
$('#interface_select').val(ifaceFromHash).selectpicker('refresh');
|
||||
}
|
||||
} else {
|
||||
// Default to ALL interfaces
|
||||
$('#interface_select').selectpicker('val', '__any');
|
||||
}
|
||||
interfaceInitialized = true;
|
||||
|
||||
@ -1044,11 +1062,20 @@
|
||||
|
||||
.stats-cell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px 10px;
|
||||
align-items: center;
|
||||
container-type: inline-size;
|
||||
}
|
||||
|
||||
.stats-cell div {
|
||||
gap: 6px;
|
||||
.stats-cell > span {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@container (max-width: 160px) {
|
||||
.stats-cell > span {
|
||||
flex: 1 1 50%;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@ -145,7 +145,7 @@
|
||||
title="${row.log == '1'
|
||||
? '{{ lang._("Disable Logging") }}'
|
||||
: '{{ lang._("Enable Logging") }}'}">
|
||||
<i class="fa fa-exclamation-circle fa-fw ${row.log == '1' ? 'text-info' : 'text-muted'}"></i>
|
||||
<i class="fa fa-fw ${row.log == '1' ? 'fa-bell' : 'fa-bell-slash'}"></i>
|
||||
</button>
|
||||
|
||||
<button type="button" class="btn btn-xs btn-default command-edit
|
||||
|
||||
@ -145,7 +145,7 @@
|
||||
title="${row.log == '1'
|
||||
? '{{ lang._("Disable Logging") }}'
|
||||
: '{{ lang._("Enable Logging") }}'}">
|
||||
<i class="fa fa-exclamation-circle fa-fw ${row.log == '1' ? 'text-info' : 'text-muted'}"></i>
|
||||
<i class="fa fa-fw ${row.log == '1' ? 'fa-bell' : 'fa-bell-slash'}"></i>
|
||||
</button>
|
||||
|
||||
<button type="button" class="btn btn-xs btn-default command-edit
|
||||
|
||||
@ -145,7 +145,7 @@
|
||||
title="${row.log == '1'
|
||||
? '{{ lang._("Disable Logging") }}'
|
||||
: '{{ lang._("Enable Logging") }}'}">
|
||||
<i class="fa fa-exclamation-circle fa-fw ${row.log == '1' ? 'text-info' : 'text-muted'}"></i>
|
||||
<i class="fa fa-fw ${row.log == '1' ? 'fa-bell' : 'fa-bell-slash'}"></i>
|
||||
</button>
|
||||
|
||||
<button type="button" class="btn btn-xs btn-default command-edit
|
||||
|
||||
@ -175,6 +175,7 @@
|
||||
if (params.has('hostname')) $('#reservation\\.hostname').val(params.get('hostname'));
|
||||
if (params.has('ip_address')) $('#reservation\\.ip_address').val(params.get('ip_address'));
|
||||
if (params.has('duid')) $('#reservation\\.duid').val(params.get('duid'));
|
||||
if (params.has('hw_address')) $('#reservation\\.hw_address').val(params.get('hw_address'));
|
||||
history.replaceState(null, null, window.location.pathname + '#reservations');
|
||||
});
|
||||
$(this).find('.command-add').trigger('click');
|
||||
|
||||
@ -84,6 +84,11 @@
|
||||
"timestamp": function (column, row) {
|
||||
return moment.unix(row[column.id]).local().format('YYYY-MM-DD HH:mm:ss');
|
||||
},
|
||||
"reservation": function (column, row) {
|
||||
return row.is_reserved !== ''
|
||||
? "{{ lang._('static') }}"
|
||||
: "{{ lang._('dynamic') }}";
|
||||
},
|
||||
"commands": function (column, row) {
|
||||
const baseUrl = `/ui/kea/dhcp/v4#reservations`;
|
||||
const searchUrl = `${baseUrl}&search=${encodeURIComponent(row.hwaddr || '')}`;
|
||||
@ -96,7 +101,7 @@
|
||||
|
||||
let btn;
|
||||
|
||||
if (row.is_reserved === '1') {
|
||||
if (row.is_reserved !== '') {
|
||||
btn = $(`
|
||||
<button type="button" class="btn btn-xs" data-toggle="tooltip"
|
||||
title="{{ lang._('Find Reservation') }}">
|
||||
@ -151,6 +156,7 @@
|
||||
<th data-column-id="valid_lifetime" data-type="integer">{{ lang._('Lifetime') }}</th>
|
||||
<th data-column-id="expire" data-type="string" data-formatter="timestamp">{{ lang._('Expire') }}</th>
|
||||
<th data-column-id="hostname" data-type="string" data-formatter="overflowformatter">{{ lang._('Hostname') }}</th>
|
||||
<th data-column-id="is_reserved" data-type="string" data-formatter="reservation" data-width="6em">{{ lang._('Lease Type') }}</th>
|
||||
<th data-column-id="commands" data-formatter="commands" data-sortable="false">{{ lang._('Commands') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
@ -84,19 +84,33 @@
|
||||
"timestamp": function (column, row) {
|
||||
return moment.unix(row[column.id]).local().format('YYYY-MM-DD HH:mm:ss');
|
||||
},
|
||||
"reservation": function (column, row) {
|
||||
return row.is_reserved !== ''
|
||||
? "{{ lang._('static') }}"
|
||||
: "{{ lang._('dynamic') }}";
|
||||
},
|
||||
"commands": function (column, row) {
|
||||
const baseUrl = `/ui/kea/dhcp/v6#reservations`;
|
||||
const searchUrl = `${baseUrl}&search=${encodeURIComponent(row.duid || '')}`;
|
||||
let searchValue = '';
|
||||
|
||||
if (row.is_reserved === 'duid') {
|
||||
searchValue = row.duid || '';
|
||||
} else if (row.is_reserved === 'hwaddr') {
|
||||
searchValue = row.hwaddr || '';
|
||||
}
|
||||
|
||||
const searchUrl = `${baseUrl}&search=${encodeURIComponent(searchValue)}`;
|
||||
const addUrlParams = {
|
||||
ip_address: row.address || '',
|
||||
duid: row.duid || '',
|
||||
hw_address: row.hwaddr || '',
|
||||
hostname: row.hostname || ''
|
||||
};
|
||||
const addUrl = `${baseUrl}?${new URLSearchParams(addUrlParams)}`;
|
||||
|
||||
let btn;
|
||||
|
||||
if (row.is_reserved === '1') {
|
||||
if (row.is_reserved !== '') {
|
||||
btn = $(`
|
||||
<button type="button" class="btn btn-xs" data-toggle="tooltip"
|
||||
title="{{ lang._('Find Reservation') }}">
|
||||
@ -152,6 +166,7 @@
|
||||
<th data-column-id="valid_lifetime" data-type="integer">{{ lang._('Lifetime') }}</th>
|
||||
<th data-column-id="expire" data-type="string" data-formatter="timestamp">{{ lang._('Expire') }}</th>
|
||||
<th data-column-id="hostname" data-type="string" data-formatter="overflowformatter">{{ lang._('Hostname') }}</th>
|
||||
<th data-column-id="is_reserved" data-type="string" data-formatter="reservation" data-width="6em">{{ lang._('Lease Type') }}</th>
|
||||
<th data-column-id="commands" data-formatter="commands" data-sortable="false">{{ lang._('Commands') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
@ -314,6 +314,8 @@
|
||||
$.extend(jQuery.fn.UIBootgrid.translations, {
|
||||
add: "{{ lang._('Add') }}",
|
||||
deleteSelected: "{{ lang._('Delete selected') }}",
|
||||
enableSelected: "{{ lang._('Enable selected') }}",
|
||||
disableSelected: "{{ lang._('Disable selected') }}",
|
||||
edit: "{{ lang._('Edit') }}",
|
||||
disable: "{{ lang._('Disable') }}",
|
||||
enable: "{{ lang._('Enable') }}",
|
||||
@ -327,7 +329,8 @@
|
||||
refresh: "{{ lang._('Refresh') }}",
|
||||
infosTotal: "{{ lang._('Showing %s to %s of %s entries') | format('{{ctx.start}}','{{ctx.end}}','{{ctx.totalRows}}') }}",
|
||||
infos: "{{ lang._('Showing %s to %s') | format('{{ctx.start}}','{{ctx.end}}') }}",
|
||||
resetGrid: "{{ lang._('Reset grid layout') }}"
|
||||
resetGrid: "{{ lang._('Reset grid layout') }}",
|
||||
searchColumns: "{{ lang._('Search columns') }}"
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@ -32,7 +32,22 @@ require_once('script/load_phalcon.php');
|
||||
|
||||
use OPNsense\Core\Config;
|
||||
|
||||
$classprefix = !empty($argv[1]) ? str_replace('/', '\\', $argv[1]) : '';
|
||||
$classprefix = '';
|
||||
foreach ($argv as $idx => $arg) {
|
||||
if ($idx > 0) {
|
||||
if (strpos($arg, '-') === 0) {
|
||||
if ($arg == '-v') {
|
||||
/* verbose mode, force logging to stdout as well */
|
||||
OPNsense\Core\Syslog::enableLocalEcho();
|
||||
} elseif ($arg == '-h') {
|
||||
echo sprintf("usage : %s [-v] [h] <classperefix>\n", $argv[0]);
|
||||
exit(0);
|
||||
}
|
||||
} else {
|
||||
$classprefix = str_replace('/', '\\', $arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$class_info = new \ReflectionClass("OPNsense\\Base\\BaseModel");
|
||||
$executed_migration = false;
|
||||
@ -79,6 +94,8 @@ foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($model_dir
|
||||
}
|
||||
}
|
||||
|
||||
OPNsense\Core\Syslog::disableLocalEcho();
|
||||
|
||||
if ($executed_migration) {
|
||||
// make changes persistent
|
||||
Config::getInstance()->save();
|
||||
|
||||
@ -38,6 +38,8 @@ if output_cmd opnsense-update -u; then
|
||||
elif output_cmd opnsense-update -K; then
|
||||
output_reboot keep-log
|
||||
fi
|
||||
# kernel is already applied so continue
|
||||
output_reboot keep-log
|
||||
fi
|
||||
|
||||
output_txt "The upgrade was aborted due to an error."
|
||||
|
||||
@ -151,7 +151,7 @@ abstract class Base
|
||||
return $this;
|
||||
}
|
||||
|
||||
$cmd_frmt = ['/usr/local/bin/rrdtool create %s --step %d'];
|
||||
$cmd_frmt = ['/usr/local/bin/rrdtool create %s --step %s'];
|
||||
$cmd_args = [$this->filename, 60];
|
||||
|
||||
foreach ($this->datasets as $dataset) {
|
||||
|
||||
96
src/opnsense/scripts/interfaces/dhcp6c_script.sh
Executable file
96
src/opnsense/scripts/interfaces/dhcp6c_script.sh
Executable file
@ -0,0 +1,96 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Copyright (c) 2017-2020 Martin Wasley <mjwasley@gmail.com>
|
||||
# Copyright (c) 2017-2026 Franco Fichtner <franco@opnsense.org>
|
||||
#
|
||||
# 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.
|
||||
|
||||
FORCE=
|
||||
|
||||
if [ -z "${REASON}" -o -z "${IFNAME}" ]; then
|
||||
/usr/bin/logger -t dhcp6c "dhcp6c_script: missing REASON or IFNAME"
|
||||
fi
|
||||
|
||||
case ${REASON} in
|
||||
INFOREQ|REBIND|RENEW|REQUEST|SOLICIT)
|
||||
/usr/bin/logger -t dhcp6c "dhcp6c_script: ${REASON} on ${IFNAME} executing"
|
||||
|
||||
IFCFILE="/tmp/dhcp6c.${IFNAME}.ifconfig"
|
||||
ENVFILE="/tmp/dhcp6c.${IFNAME}.env"
|
||||
|
||||
if [ "$(pluginctl -g OPNsense.Interfaces.settings.dhcp6_debug)" = "1" ]; then
|
||||
ifconfig -L > "${IFCFILE}"
|
||||
env | sort > "${ENVFILE}"
|
||||
else
|
||||
rm -f "${IFCFILE}" "${ENVFILE}"
|
||||
fi
|
||||
|
||||
ARGS=
|
||||
for NAMESERVER in ${new_domain_name_servers}; do
|
||||
ARGS="${ARGS} -a ${NAMESERVER}"
|
||||
done
|
||||
/usr/local/sbin/ifctl -i "${IFNAME}" -6nd ${ARGS}
|
||||
|
||||
ARGS=
|
||||
for DOMAIN in ${new_domain_name}; do
|
||||
ARGS="${ARGS} -a ${DOMAIN}"
|
||||
done
|
||||
/usr/local/sbin/ifctl -i "${IFNAME}" -6sd ${ARGS}
|
||||
|
||||
if [ ${REASON} = "REQUEST" -o ${REASON} = "SOLICIT" ]; then
|
||||
/usr/bin/logger -t dhcp6c "dhcp6c_script: ${REASON} on ${IFNAME} connected to server"
|
||||
FORCE=${REASON}
|
||||
|
||||
# can safely clear the prefix during connect
|
||||
/usr/local/sbin/ifctl -i "${IFNAME}" -6pd
|
||||
fi
|
||||
|
||||
if [ ${REASON} != "INFOREQ" -a -n "${PDINFO}" ]; then
|
||||
ARGS=
|
||||
for PD in ${PDINFO}; do
|
||||
ARGS="${ARGS} -a ${PD}"
|
||||
done
|
||||
|
||||
if /usr/local/sbin/ifctl -i "${IFNAME}" -6pu ${ARGS}; then
|
||||
/usr/bin/logger -t dhcp6c "dhcp6c_script: ${REASON} on ${IFNAME} prefix now ${PDINFO}"
|
||||
|
||||
if [ -z "${FORCE}" ]; then
|
||||
FORCE=${REASON}
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
/usr/local/sbin/configctl -d interface newipv6 "${IFNAME}" ${FORCE}
|
||||
;;
|
||||
EXIT|RELEASE)
|
||||
/usr/bin/logger -t dhcp6c "dhcp6c_script: ${REASON} on ${IFNAME} executing"
|
||||
|
||||
/usr/local/sbin/ifctl -i "${IFNAME}" -6nd
|
||||
/usr/local/sbin/ifctl -i "${IFNAME}" -6sd
|
||||
/usr/local/sbin/ifctl -i "${IFNAME}" -6pd
|
||||
|
||||
/usr/local/sbin/configctl -d interface newipv6 "${IFNAME}"
|
||||
;;
|
||||
*)
|
||||
/usr/bin/logger -t dhcp6c "dhcp6c_script: ${REASON} on ${IFNAME} ignored"
|
||||
;;
|
||||
esac
|
||||
@ -172,6 +172,9 @@ if __name__ == '__main__':
|
||||
args.append(settings['packetsize'])
|
||||
if settings.get('disable_frag', '0') == '1':
|
||||
args.append('-D')
|
||||
if settings.get('interval', '') != '':
|
||||
args.append('-i')
|
||||
args.append(settings['interval'])
|
||||
args.append(settings.get('hostname', ''))
|
||||
if os.path.isfile(log_target):
|
||||
os.remove(log_target)
|
||||
|
||||
@ -28,6 +28,7 @@
|
||||
import argparse
|
||||
import csv
|
||||
import ipaddress
|
||||
import ujson
|
||||
import os
|
||||
import time
|
||||
import subprocess
|
||||
@ -69,24 +70,27 @@ def yield_log_records(filename, poll_interval=5):
|
||||
else:
|
||||
time.sleep(poll_interval)
|
||||
|
||||
class NDP:
|
||||
""" simplistic NDP link local lookup
|
||||
class Hostwatch:
|
||||
"""
|
||||
Use hostwatch service to gather link-local IPv6 addresses.
|
||||
The endpoint falls back to NDP when hostwatch service is not running.
|
||||
"""
|
||||
def __init__(self):
|
||||
self._def_local_db = {}
|
||||
|
||||
def reload(self):
|
||||
ndpdata = subprocess.run(['/usr/sbin/ndp', '-an'], capture_output=True, text=True).stdout
|
||||
for idx, line in enumerate(ndpdata.split("\n")):
|
||||
parts = line.split()
|
||||
if idx > 0 and len(parts) > 1:
|
||||
try:
|
||||
addr = parts[0].split('%')[0]
|
||||
if ipaddress.ip_address(addr).is_link_local:
|
||||
self._def_local_db[parts[1]] = parts[0]
|
||||
except (ValueError, IndexError):
|
||||
pass
|
||||
self._def_local_db.clear()
|
||||
|
||||
out = subprocess.run(
|
||||
['/usr/local/opnsense/scripts/interfaces/list_hosts.py', '--proto', 'inet6', '--ndp'],
|
||||
capture_output=True,
|
||||
text=True
|
||||
).stdout
|
||||
|
||||
for row in ujson.loads(out).get("rows", []):
|
||||
# [ifname, mac, ip]
|
||||
if ipaddress.ip_address(row[2]).is_link_local:
|
||||
self._def_local_db[row[1]] = row[2]
|
||||
|
||||
def get(self, mac):
|
||||
if mac not in self._def_local_db:
|
||||
@ -110,7 +114,7 @@ if __name__ == '__main__':
|
||||
if not os.path.isfile(inputargs.filename):
|
||||
syslog.syslog(syslog.LOG_ERR, "lease file does not exist: %s" % inputargs.filename)
|
||||
sys.exit(1)
|
||||
ndp = NDP()
|
||||
hostwatch = Hostwatch()
|
||||
for record in yield_log_records(inputargs.filename):
|
||||
# IA_PD: type 2, prefix_len <= 64 - the delegated prefix
|
||||
if record.get('lease_type', 0) == 2 and record.get('prefix_len', 128) <= 64:
|
||||
@ -118,7 +122,7 @@ if __name__ == '__main__':
|
||||
if (prefix not in prefixes or prefixes[prefix].get('hwaddr') != record.get('hwaddr')) \
|
||||
and record.get('expire', 0) > time.time():
|
||||
prefixes[prefix] = record
|
||||
ll_addr = ndp.get(record.get('hwaddr'))
|
||||
ll_addr = hostwatch.get(record.get('hwaddr'))
|
||||
# lazy drop
|
||||
subprocess.run(['/sbin/route', 'delete', '-inet6', prefix], capture_output=True)
|
||||
if record.get('valid_lifetime', 0) > 0:
|
||||
|
||||
@ -539,7 +539,8 @@ function console_configure_dhcpd($version = 4)
|
||||
}
|
||||
|
||||
if ($version === 6) {
|
||||
$config['dnsmasq']['dhcp'] = ['enable_ra' => '1'];
|
||||
config_read_array('dnsmasq', 'dhcp');
|
||||
$config['dnsmasq']['dhcp']['enable_ra'] = '1';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -25,7 +25,7 @@ type:script_output
|
||||
message:get host discovery daemon status
|
||||
|
||||
[dump_full]
|
||||
command:/usr/local/opnsense/scripts/interfaces/list_hosts.py -v
|
||||
command:/usr/local/opnsense/scripts/interfaces/list_hosts.py -v -n
|
||||
parameters:
|
||||
errors:no
|
||||
type:script_output
|
||||
|
||||
@ -30,6 +30,15 @@
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.bootgrid-footer-commands {
|
||||
padding-left: 8px;
|
||||
padding-right: 15px;
|
||||
}
|
||||
|
||||
.dropdown-menu .dropdown-search {
|
||||
padding: 6px 10px;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.bootgrid-header .actionBar {
|
||||
flex-wrap: wrap;
|
||||
|
||||
@ -221,11 +221,6 @@
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
.bootgrid-footer-commands {
|
||||
width: 90px;
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
/* move border from footer to last row */
|
||||
.tabulator .tabulator-footer {
|
||||
border-top: none;
|
||||
|
||||
@ -192,6 +192,10 @@ function setFormData(parent,data) {
|
||||
targetNode.append($("<optgroup/>").attr('label', group).append(items));
|
||||
}
|
||||
}
|
||||
if (targetNode.parent().hasClass('bootstrap-select')) {
|
||||
/* if our node is a selectpicker type, refresh after re-populating */
|
||||
targetNode.selectpicker('refresh');
|
||||
}
|
||||
} else if (targetNode.prop("type") === "checkbox") {
|
||||
// checkbox type
|
||||
targetNode.prop("checked", node[keypart] != 0);
|
||||
|
||||
@ -164,6 +164,8 @@ class UIBootgrid {
|
||||
deleteSelectedButton: false,
|
||||
commands: {}, //additional registered commands
|
||||
virtualDOM: false,
|
||||
selection: true,
|
||||
multiSelect: true,
|
||||
stickySelect: false,
|
||||
rowSelect: false,
|
||||
triggerEditFor: null,
|
||||
@ -248,11 +250,15 @@ class UIBootgrid {
|
||||
this.compatOptions['selectableRows'] = 1;
|
||||
if (bootGridOptions?.multiSelect ?? true) {
|
||||
this.compatOptions['selectableRows'] = true;
|
||||
} else {
|
||||
this.options.multiSelect = false;
|
||||
}
|
||||
// TODO rowSelect toggle (currently not support by tabulator)
|
||||
|
||||
this.options.rowSelect = bootGridOptions?.rowSelect ?? false;
|
||||
} else {
|
||||
this.options.selection = false;
|
||||
this.options.multiSelect = false;
|
||||
// remove checkbox select column
|
||||
this.compatOptions['rowHeader'] = null;
|
||||
}
|
||||
@ -1018,6 +1024,17 @@ class UIBootgrid {
|
||||
|
||||
this.$element.before($(this._getHeader()));
|
||||
|
||||
// column selection dropdown search bar
|
||||
const $menu = $(`#${this.id}-columnselect-items`);
|
||||
$menu.on("input", ".columnsearch", function () {
|
||||
var q = $.trim($(this).val()).toLowerCase();
|
||||
|
||||
$menu.children("li").not(".columnsearch-li, .divider").each(function () {
|
||||
var text = $(this).text().trim().toLowerCase();
|
||||
$(this).toggle(text.indexOf(q) !== -1);
|
||||
});
|
||||
});
|
||||
|
||||
// search functionality
|
||||
$(`#${this.id}-search-field`).val(this.searchPhrase).on("keyup", (e) => {
|
||||
this._search($(`#${this.id}-search-field`).val(), e);
|
||||
@ -1124,8 +1141,8 @@ class UIBootgrid {
|
||||
}
|
||||
|
||||
const title = typeof command?.title === "function"
|
||||
? `title=${command?.title()}`
|
||||
: `title=${command?.title}` ?? '';
|
||||
? `title="${command?.title()}"`
|
||||
: `title="${command?.title}"` ?? '';
|
||||
|
||||
const $element = $(`
|
||||
<button type="button" class="btn btn-xs ${key === 'add' ? 'btn-primary' : 'btn-default'} command-${key} bootgrid-tooltip" ${title}>
|
||||
@ -1133,7 +1150,7 @@ class UIBootgrid {
|
||||
</button>
|
||||
`);
|
||||
|
||||
if (key === 'add' || key === 'delete-selected') {
|
||||
if (command?.primary) {
|
||||
$commandContainer.append($element);
|
||||
} else {
|
||||
$element.appendTo($footerSecondary);
|
||||
@ -1214,7 +1231,12 @@ class UIBootgrid {
|
||||
<span class="dropdown-text"><span class="icon fa-solid fa-list"></span></span>
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul id="${this.id}-columnselect-items" class="dropdown-menu pull-right" role="menu"></ul>
|
||||
<ul id="${this.id}-columnselect-items" class="dropdown-menu pull-right" role="menu">
|
||||
<li class="dropdown-search columnsearch-li">
|
||||
<input type="text" class="form-control input-sm columnsearch" placeholder="${this._translate('searchColumns')}"/>
|
||||
</li>
|
||||
<li class="divider"></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -1529,6 +1551,8 @@ class UIBootgrid {
|
||||
* - requires: an array of strings marking which this.crud properties are required
|
||||
* - sequence: order of commands rendering
|
||||
* - footer: true|false whether this command should be rendered in the table footer
|
||||
* - primary: true|false only if footer: true, whether this command should be rendered as part
|
||||
* of the primary button container (intended for primary CRUD actions)
|
||||
* - classname: required. icon class added to the span inside the button element
|
||||
* - filter: a function that returns true or false determining if the command should be rendered.
|
||||
* the cell object is passed in only if footer: false
|
||||
@ -1545,6 +1569,7 @@ class UIBootgrid {
|
||||
classname: 'fa fa-plus fa-fw',
|
||||
sequence: 100,
|
||||
footer: true,
|
||||
primary: true,
|
||||
title: this._translate('add')
|
||||
},
|
||||
"edit": {
|
||||
@ -1587,12 +1612,37 @@ class UIBootgrid {
|
||||
}
|
||||
}
|
||||
},
|
||||
"enable-selected": {
|
||||
method: this.command_toggle_selected.bind(this, true),
|
||||
requires: ['toggle'],
|
||||
classname: 'fa fa-fw fa-check-square-o',
|
||||
sequence: 200,
|
||||
filter: () => {
|
||||
return this.options.selection && this.options.multiSelect && !this.options.stickySelect;
|
||||
},
|
||||
footer: true,
|
||||
primary: true,
|
||||
title: this._translate('enableSelected'),
|
||||
},
|
||||
"disable-selected": {
|
||||
method: this.command_toggle_selected.bind(this, false),
|
||||
requires: ['toggle'],
|
||||
classname: 'fa fa-fw fa-square-o',
|
||||
sequence: 300,
|
||||
filter: () => {
|
||||
return this.options.selection && this.options.multiSelect && !this.options.stickySelect;
|
||||
},
|
||||
footer: true,
|
||||
primary: true,
|
||||
title: this._translate('disableSelected'),
|
||||
},
|
||||
"delete-selected": {
|
||||
method: this.command_delete_selected.bind(this),
|
||||
requires: ['del'],
|
||||
classname: 'fa fa-trash-o fa-fw',
|
||||
sequence: 100,
|
||||
sequence: 400,
|
||||
footer: true,
|
||||
primary: true,
|
||||
title: this._translate('deleteSelected')
|
||||
}
|
||||
};
|
||||
@ -1648,8 +1698,8 @@ class UIBootgrid {
|
||||
|
||||
if (has_option) {
|
||||
let title = typeof command?.title === "function"
|
||||
? `title=${command?.title(cell)}`
|
||||
: `title=${command?.title}` ?? '';
|
||||
? `title="${command?.title(cell)}"`
|
||||
: `title="${command?.title}"` ?? '';
|
||||
|
||||
html.push(`
|
||||
<button type="button"
|
||||
@ -1743,7 +1793,6 @@ class UIBootgrid {
|
||||
mapDataToFormUI(urlMap, server_params).done((payload) => {
|
||||
// update selectors
|
||||
formatTokenizersUI();
|
||||
$('.selectpicker').selectpicker('refresh');
|
||||
// clear validation errors (if any)
|
||||
clearFormValidation('frm_' + editDlg);
|
||||
let target = $('#' + editDlg);
|
||||
@ -2047,6 +2096,22 @@ class UIBootgrid {
|
||||
});
|
||||
}
|
||||
|
||||
command_toggle_selected(enable, event) {
|
||||
event.stopPropagation();
|
||||
const rows = this.table.getSelectedData();
|
||||
if (rows.length > 0) {
|
||||
const deferreds = [];
|
||||
rows.forEach((row) => {
|
||||
const uuid = row[this.options.datakey];
|
||||
deferreds.push(ajaxCall(`${this.crud['toggle']}${uuid}/${enable ? "1" : "0"}`, {}, null));
|
||||
})
|
||||
$.when.apply(null, deferreds).done(() => {
|
||||
this._reload(true);
|
||||
this.showSaveAlert(event);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_debounce(f, delay = 50, ensure = true) {
|
||||
// debounce to prevent a flood of calls in a short time
|
||||
let lastCall = Number.NEGATIVE_INFINITY;
|
||||
|
||||
@ -985,20 +985,21 @@ $.fn.replaceInputWithSelector = function (data, multiple=false) {
|
||||
});
|
||||
$this_input.attr('id', $(this).attr('id'));
|
||||
$this_input.change(function(){
|
||||
let selopt = multiple ? $(this).val().split(',') : [$(this).val()];
|
||||
let selopt = [];
|
||||
/* skim given options for valid ones in our list, so we can safely set them and identify manual input*/
|
||||
let givenopts = multiple ? $(this).val().split(',') : [$(this).val()];
|
||||
$this_select.find('option').each(function(){
|
||||
if (selopt.includes($(this).val())) {
|
||||
selopt.splice(selopt.indexOf($(this).val()), 1)
|
||||
$(this).attr('selected', 'selected');
|
||||
} else {
|
||||
$(this).prop('selected', false);
|
||||
if (givenopts.includes($(this).val())) {
|
||||
selopt.push($(this).val());
|
||||
}
|
||||
});
|
||||
if (selopt.length == 0) {
|
||||
$this_input.hide(); /* items not in selector, show input */
|
||||
} else {
|
||||
/* none of the options are selected, so we need to feed empty_select_token here and show manual input*/
|
||||
$this_select.val(multiple ? [empty_select_token]: empty_select_token);
|
||||
$this_input.show();
|
||||
$this_select.val(empty_select_token);
|
||||
} else {
|
||||
$this_select.val(multiple ? selopt : selopt[0]);
|
||||
$this_input.hide();
|
||||
}
|
||||
$this_select.selectpicker('refresh');
|
||||
});
|
||||
|
||||
@ -225,11 +225,6 @@
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
.bootgrid-footer-commands {
|
||||
width: 90px;
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
.tabulator-tableholder::after {
|
||||
content: '';
|
||||
display: block;
|
||||
|
||||
@ -225,11 +225,6 @@
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
.bootgrid-footer-commands {
|
||||
width: 90px;
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
.tabulator-tableholder::after {
|
||||
content: "";
|
||||
display: block;
|
||||
|
||||
@ -224,11 +224,6 @@
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
.bootgrid-footer-commands {
|
||||
width: 90px;
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
.tabulator-tableholder::after {
|
||||
content: '';
|
||||
display: block;
|
||||
|
||||
@ -239,11 +239,6 @@
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
.bootgrid-footer-commands {
|
||||
width: 90px;
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
.tabulator-tableholder::after {
|
||||
content: "";
|
||||
display: block;
|
||||
|
||||
@ -148,12 +148,12 @@ function get_all_services($name = '', $id = '')
|
||||
}
|
||||
|
||||
$jflags = JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_INVALID_UTF8_IGNORE;
|
||||
$opts = getopt('46cDdfghIimrqSsvXx', [], $optind);
|
||||
$opts = getopt('46cDdfghIiMmrqSsvXx', [], $optind);
|
||||
$args = array_slice($argv, $optind);
|
||||
$ret = 0;
|
||||
|
||||
if (isset($opts['h'])) {
|
||||
echo "Usage: pluginctl [-4|-6|-c|-D|-d|-f|-g|-h|-I|-i|-m|-r|-q|-S|-s|-v|-X|-x] ...\n\n";
|
||||
echo "Usage: pluginctl [-4|-6|-c|-D|-d|-f|-g|-h|-I|-i|-M|-m|-r|-q|-S|-s|-v|-X|-x] ...\n\n";
|
||||
echo "\t-4 IPv4 address mode, return primary address of interface\n";
|
||||
echo "\t-6 IPv4 address mode, return primary address of interface\n";
|
||||
echo "\t-c configure mode (default), executes plugin [_configure] hook\n";
|
||||
@ -164,7 +164,8 @@ if (isset($opts['h'])) {
|
||||
echo "\t-h show this help text and exit\n";
|
||||
echo "\t-I information mode, lists registered device statistics\n";
|
||||
echo "\t-i invoke dynamic interface registration\n";
|
||||
echo "\t-m invoke model migrations\n";
|
||||
echo "\t-M invoke model migrations in quiet mode\n";
|
||||
echo "\t-m invoke model migrations in verbose mode\n";
|
||||
echo "\t-q quiet switch for configure mode\n";
|
||||
echo "\t-r run mode (e.g. command)\n";
|
||||
echo "\t-S service metadata dump\n";
|
||||
@ -300,8 +301,10 @@ if (isset($opts['h'])) {
|
||||
$payload = new ArrayObject();
|
||||
}
|
||||
echo json_encode($payload, $jflags) . PHP_EOL;
|
||||
} elseif (isset($opts['m'])) {
|
||||
} elseif (isset($opts['M'])) {
|
||||
pass_safe('/usr/local/opnsense/mvc/script/run_migrations.php', [], $ret);
|
||||
} elseif (isset($opts['m'])) {
|
||||
pass_safe('/usr/local/opnsense/mvc/script/run_migrations.php -v', [], $ret);
|
||||
} elseif (isset($opts['v'])) {
|
||||
pass_safe('/usr/local/opnsense/mvc/script/run_validations.php', [], $ret);
|
||||
} elseif (empty($args[0])) {
|
||||
|
||||
@ -961,7 +961,7 @@ include("head.inc");
|
||||
<td><a id="help_for_interface" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext("Interface");?></td>
|
||||
<td>
|
||||
<?php if (!empty($pconfig['floating'])): ?>
|
||||
<select name="interface[]" id="interface" title="Select interfaces..." multiple="multiple" class="selectpicker" data-live-search="true" data-size="5" tabindex="2">
|
||||
<select name="interface[]" id="interface" title="<?= html_safe(gettext('Select interfaces...')) ?>" multiple="multiple" class="selectpicker" data-live-search="true" data-size="5" tabindex="2">
|
||||
<?php else: ?>
|
||||
<select name="interface" id="interface" class="selectpicker" data-live-search="true" data-size="5">
|
||||
<?php endif ?>
|
||||
|
||||
@ -291,7 +291,7 @@ include("head.inc");
|
||||
<tr>
|
||||
<td><a id="help_for_interface" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext("Interface");?></td>
|
||||
<td>
|
||||
<select name="interface[]" title="<?=gettext("Select interfaces...");?>" multiple="multiple" class="selectpicker" data-live-search="true" data-size="5" <?=!empty($pconfig['associated-rule-id']) ? "disabled" : "";?>>
|
||||
<select name="interface[]" title="<?= html_safe(gettext('Select interfaces...')) ?>" multiple="multiple" class="selectpicker" data-live-search="true" data-size="5">
|
||||
<?php
|
||||
foreach (legacy_config_get_interfaces(array("enable" => true)) as $iface => $ifaceInfo): ?>
|
||||
<option value="<?=$iface;?>"
|
||||
|
||||
@ -690,10 +690,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($config['dhcpd']) && isset($config['dhcpd'][$if]['enable']) && !preg_match('/^staticv4/', $pconfig['type'])) {
|
||||
if (isset($config['dhcpd'][$if]['enable']) && !preg_match('/^staticv4/', $pconfig['type'])) {
|
||||
$input_errors[] = gettext("The DHCP Server is active on this interface and it can be used only with a static IP configuration. Please disable the DHCP Server service on this interface first, then change the interface configuration.");
|
||||
}
|
||||
if (isset($config['dhcpdv6']) && isset($config['dhcpdv6'][$if]['enable']) && !preg_match('/^staticv6/', $pconfig['type6']) && !isset($pconfig['dhcpd6track6allowoverride'])) {
|
||||
if (isset($config['dhcpdv6'][$if]['enable']) && !preg_match('/^staticv6/', $pconfig['type6']) && !isset($pconfig['dhcpd6track6allowoverride']) && $config['dhcpdv6'][$if]['enable'] != '-1') {
|
||||
$input_errors[] = gettext("The DHCPv6 Server is active on this interface and it can be used only with a static IPv6 configuration. Please disable the DHCPv6 Server service on this interface first, then change the interface configuration.");
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user