diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index f54090a6b9..b77b481693 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -10,11 +10,9 @@ assignees: '' **Important notices** Before you add a new report, we ask you kindly to acknowledge the following: -[-] I have read the contributing guide lines at https://github.com/opnsense/plugins/blob/master/CONTRIBUTING.md - -[-] I have searched the existing issues and I'm convinced that mine is new. - -[-] The title contains the plugin to which this issue belongs +- [ ] I have read the contributing guide lines at https://github.com/opnsense/plugins/blob/master/CONTRIBUTING.md +- [ ] I have searched the existing issues, open and closed, and I'm convinced that mine is new. +- [ ] The title contains the plugin to which this issue belongs **Describe the bug** A clear and concise description of what the bug is, including last known working version (if any). @@ -44,6 +42,6 @@ Add any other context about the problem here. Software version used and hardware type if relevant. e.g.: -OPNsense 19.1.1 (amd64, OpenSSL). +OPNsense 23.7.8 (amd64). Intel® Xeon™ E3-1225V5 3.3Ghz Quad Core Network Intel® I210-AT diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index c96c1c7467..fe3c8fca51 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -10,11 +10,9 @@ assignees: '' **Important notices** Before you add a new report, we ask you kindly to acknowledge the following: -[-] I have read the contributing guide lines at https://github.com/opnsense/plugins/blob/master/CONTRIBUTING.md - -[-] I have searched the existing issues and I'm convinced that mine is new. - -[-] When the request is meant for an existing plugin, I've added its name to the title. +- [ ] I have read the contributing guide lines at https://github.com/opnsense/plugins/blob/master/CONTRIBUTING.md +- [ ] I have searched the existing issues, open and closed, and I'm convinced that mine is new. +- [ ] When the request is meant for an existing plugin, I've added its name to the title. **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md index 6152184ac3..b4d4daf5e5 100644 --- a/.github/ISSUE_TEMPLATE/question.md +++ b/.github/ISSUE_TEMPLATE/question.md @@ -13,6 +13,5 @@ Our forum is located at https://forum.opnsense.org , please consider joining dis Before you ask a new question, we ask you kindly to acknowledge the following: -[-] I have read the contributing guide lines at https://github.com/opnsense/plugins/blob/master/CONTRIBUTING.md - -[-] I have searched the existing issues and I'm convinced that mine is new. +- [ ] I have read the contributing guide lines at https://github.com/opnsense/plugins/blob/master/CONTRIBUTING.md +- [ ] I have searched the existing issues, open and closed, and I'm convinced that mine is new. diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000000..4bd1d0a5d9 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,30 @@ +**Important notices** + +Before you submit a pull request, we ask you kindly to acknowledge the following: + +- [ ] I have read the contributing guidelines at https://github.com/opnsense/plugins/blob/master/CONTRIBUTING.md +- [ ] I opened an issue first for non-trivial changes and linked it below. +- [ ] AI tools were used to create at least part of the code submitted herewith. + +If AI was used, please disclose: + +- Model used: +- Extent of AI involvement: + +--- + +**Describe the problem** + +A clear and concise description of the problem this pull request addresses. + +--- + +**Describe the proposed solution** + +Explain what this pull request changes and why. + +--- + +**Related issue** + +If this pull request relates to an issue, link it here. diff --git a/.gitignore b/.gitignore index 547c60c39e..6213804b6b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ -/*/*/work *.pyc +.*DS_Store .idea +.sass-cache +venv +/*/*/work diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5bd849c95e..b35353f5e0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -47,3 +47,16 @@ When creating pull request, please heed the following: * Code review may ensue in order to help shape your proposal * Pull request must adhere to 2-Clause BSD licensing * Explain the problem and your proposed solution + +New plugins +----------- + +The pull request notes apply, but with the following additional points: + +* Open an issue first to explain what you want to work on and give it time for discussion +* If you are integrating a service binary it should at least be available in FreeBSD ports +* Precompiled binaries in the plugins are not allowed +* Plugins should almost always focus on integrating an existing service and providing MVC/API GUI pages for it +* It is not possible to review and integrate plugins with a large initial codebase +* If you use AI tools in your submission please disclose their use (name and model) +* Even though you are the maintainer you effectively force burden of maintainership to the community and OPNsense developers as soon as you open your first PR diff --git a/Keywords/sample.ucl b/Keywords/sample.ucl new file mode 100644 index 0000000000..ea5bf5db18 --- /dev/null +++ b/Keywords/sample.ucl @@ -0,0 +1,57 @@ +# MAINTAINER: portmgr@FreeBSD.org +# +# @sample etc/somefile.conf.sample +# or +# @sample file1 file2 +# +# Where file1 is considered as a sample file and file2 the target file +# +# This will install the somefile.conf.sample and automatically copy to +# somefile.conf if it doesn't exist. On deinstall it will remove the +# somefile.conf if it still matches the sample, otherwise it is +# kept. +# +# This replaces the old pattern: +# @unexec if cmp -s %D/etc/pkgtools.conf %D/etc/pkgtools.conf.sample; then rm -f %D/etc/pkgtools.conf; fi +# etc/pkgtools.conf.sample +# @exec [ -f %B/pkgtools.conf ] || cp %B/%f %B/pkgtools.conf + +actions: [file(1)] +arguments: true +post-install: < -Copyright (c) 2019 Cloudfence - Julio Camargo (JCC) +Copyright (c) 2023-2025 A. Kulikov +Copyright (c) 2015-2025 Ad Schellevis +Copyright (c) 2022 agh1467 +Copyright (c) 2024 Alex Smith +Copyright (c) 2021 Alexander Noack +Copyright (c) 2021 Andreas Stuerz +Copyright (c) 2025 Andy Binder +Copyright (c) 2024 AnShen +Copyright (c) 2025 Anton Avramov +Copyright (c) 2025 Arcan Consulting +Copyright (c) 2021 Axelrtgs +Copyright (c) 2026 Benno Kutschenreuter +Copyright (c) 2023 Bernhard Frenking +Copyright (c) 2023 Cannon Matthews +Copyright (c) 2026 Carsten Kallies +Copyright (c) 2023-2026 Cedrik Pischem +Copyright (c) 2025 Christopher Linn, BackendMedia IT-Services GmbH Copyright (c) 2005-2006 Colin Smith -Copyright (c) 2011 Dan Myers +Copyright (c) 2021 Dan Lundqvist +Copyright (c) 2021 David Berry Copyright (c) 2017-2018 David Harrigan -Copyright (c) 2014-2019 Deciso B.V. -Copyright (c) 2008 Donovan Schonknecht -Copyright (c) 2016-2019 EURO-LOG AG +Copyright (c) 2021 David Hughes +Copyright (c) 2014-2026 Deciso B.V. +Copyright (c) 2020 devNan0 +Copyright (c) 2023 Dmitry Shinkaruk +Copyright (c) 2024 DollarSign23 Copyright (c) 2006 Eric Friesen -Copyright (c) 2008-2010 Ermal Luçi -Copyright (c) 2017-2019 Fabian Franz +Copyright (c) 2008-2014 Ermal Luçi +Copyright (c) 2016-2019 EURO-LOG AG +Copyright (c) 2017-2020 Fabian Franz Copyright (c) 2019 Felix Matouschek -Copyright (c) 2014-2020 Franco Fichtner -Copyright (c) 2016-2019 Frank Wall +Copyright (c) 2025 Florian Latifi +Copyright (c) 2024 Francisco Dimattia +Copyright (c) 2014-2025 Franco Fichtner +Copyright (c) 2016-2026 Frank Wall +Copyright (c) 2021 Github-jjw +Copyright (c) 2023 Greg Glockner +Copyright (c) 2024 Hasan Ucak +Copyright (c) 2023 Ingo Lafrenz Copyright (c) 2016 IT-assistans Sverige AB +Copyright (c) 2021-2023 Jan Winkler +Copyright (c) 2023-2026 Jeremy Gutierrez Copyright (c) 2010 Jim Pingle -Copyright (c) 2015 Jos Schellevis +Copyright (c) 2015 Jos Schellevis +Copyright (c) 2025 Joseph Bauser Copyright (c) 2018 João Vilaça -Copyright (c) 2019 Juergen Kellerer -Copyright (c) 2003-2006 Manuel Kasper -Copyright (c) 2017-2020 Michael Muenz +Copyright (c) 2019-2022 Juergen Kellerer +Copyright (c) 2026 Konstantinos Spartalis +Copyright (c) 2024 laraveluser +Copyright (c) 2026 Leandro Scardua +Copyright (c) 2023 Liam Steckler +Copyright (c) 2020-2021 Manuel Faux +Copyright (c) 2021 Manuel Hofmann +Copyright (c) 2003-2005 Manuel Kasper +Copyright (c) 2023 Marc Bartelt +Copyright (c) 2021 Marcel Koepfli +Copyright (c) 2021 Markus Peter +Copyright (c) 2022 Markus Reiter +Copyright (c) 2020 Martin Wasley +Copyright (c) 2022 Marvo2011 +Copyright (c) 2025 Matthias Valvekens +Copyright (c) 2025 Maxime Thiebaut +Copyright (c) 2017-2025 Michael Muenz +Copyright (c) 2024 Michał Brzeziński +Copyright (c) 2024 Mike Shuey +Copyright (c) 2023-2024 Mikhail Kharisov +Copyright (c) 2023 mleinart +Copyright (c) 2024 MVZ Labor Ludwigsburg GbR +Copyright (c) 2025 Neil Merchant +Copyright (c) 2025 NetBird GmbH +Copyright (c) 2025 Nick Card +Copyright (c) 2021-2024 Nicola Pellegrini +Copyright (c) 2022 Nikolaj Brinch Jørgensen +Copyright (c) 2021 Nim G +Copyright (c) 2023 Oliver Hartl +Copyright (c) 2025 Oliver Traber +Copyright (c) 2024 Olly Baker +Copyright (c) 2019 Pascal Mathis +Copyright (c) 2025 Peter Vos +Copyright (c) 2025 Ralph Moser, PJ Monitoring GmbH +Copyright (c) 2024 realizelol +Copyright (c) 2025 Renat Gorbushin +Copyright (c) 2022 Robbert Rijkse +Copyright (c) 2023 sattamjh Copyright (c) 2004-2012 Scott Ullrich Copyright (c) 2010-2012 Seth Mos +Copyright (c) 2024 Sheridan Computers Copyright (c) 2008 Shrew Soft Inc. -Copyright (c) 2017-2019 Smart-Soft -Copyright (c) 2013 Stanley P. Miller \ stan-qaz -Copyright (c) 2010 Yehuda Katz +Copyright (c) 2017-2018 Smart-Soft +Copyright (c) 2025 sourceforge807 +Copyright (c) 2025 squared GmbH +Copyright (c) 2020 Starkstromkonsument +Copyright (c) 2023-2024 Thomas Cekal +Copyright (c) 2026 Thomas Moore +Copyright (c) 2020 Tobias Boehnert +Copyright (c) 2024 txr13 +Copyright (c) 2024 W516 +Copyright (c) 2022 Wouter Deurholt +Copyright (c) 2025 Yann Bayart +Copyright (c) 2025 Yann Demoulin Copyright (c) 2015 YoungJoo.Kim All rights reserved. diff --git a/Makefile b/Makefile index f5b95c2e5a..abd83c36f1 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -# Copyright (c) 2015-2020 Franco Fichtner +# Copyright (c) 2015-2025 Franco Fichtner # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -23,68 +23,43 @@ # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. -all: - @cat ${.CURDIR}/README.md | ${PAGER} - -.include "Mk/defaults.mk" +PLUGINSDIR?= ${.CURDIR} -CATEGORIES= benchmarks databases devel dns mail misc net-mgmt \ - net security sysutils vendor www +_CATEGORIES!= ls -1d [a-z0-9]* +CATEGORIES?= ${_CATEGORIES} .for CATEGORY in ${CATEGORIES} _${CATEGORY}!= ls -1d ${CATEGORY}/* PLUGIN_DIRS+= ${_${CATEGORY}} .endfor -list: -.for PLUGIN_DIR in ${PLUGIN_DIRS} - @echo ${PLUGIN_DIR} -- $$(${MAKE} -C ${PLUGIN_DIR} -V PLUGIN_COMMENT) -.endfor - -# shared targets that are sane to run from the root directory -TARGETS= clean lint style style-fix style-python sweep test - -.for TARGET in ${TARGETS} -${TARGET}: -. for PLUGIN_DIR in ${PLUGIN_DIRS} - @${MAKE} -C ${PLUGIN_DIR} ${TARGET} -. endfor -.endfor +all: + @cat ${.CURDIR}/README.md | ${PAGER} -ARGS= diff mfc +.include "Mk/defaults.mk" +.include "Mk/common.mk" +.include "Mk/git.mk" -# handle argument expansion for required targets -.for TARGET in ${.TARGETS} -_TARGET= ${TARGET:C/\-.*//} -.if ${_TARGET} != ${TARGET} -.for ARGUMENT in ${ARGS} -.if ${_TARGET} == ${ARGUMENT} -${_TARGET}_ARGS+= ${TARGET:C/^[^\-]*(\-|\$)//:S/,/ /g} -${TARGET}: ${_TARGET} -.endif -.endfor -${_TARGET}_ARG= ${${_TARGET}_ARGS:[0]} -.endif +list: +.for PLUGIN_DIR in ${PLUGIN_DIRS} + @echo ${PLUGIN_DIR} -- $$(${MAKE} -C ${PLUGIN_DIR} -v PLUGIN_COMMENT) \ + $$(if [ "$$(${MAKE} -C ${PLUGIN_DIR} -v PLUGIN_MAINTAINER)" = "N/A" ]; then echo "(not maintained)"; fi) \ + $$(if [ -n "$$(${MAKE} -C ${PLUGIN_DIR} -v PLUGIN_DEVEL _PLUGIN_DEVEL=)" ]; then echo "(development only)"; fi) \ + $$(if [ -n "$$(${MAKE} -C ${PLUGIN_DIR} -v PLUGIN_OBSOLETE)" ]; then echo "(pending removal)"; fi) .endfor -diff: - @git diff --stat -p stable/${PLUGIN_ABI} ${.CURDIR}/${diff_ARGS:[1]} +# known good targets (expanded below) +TARGETS= clean glint lint revision style sweep test -mfc: -.for MFC in ${mfc_ARGS} -.if exists(${MFC}) - @git diff --stat -p stable/${PLUGIN_ABI} ${.CURDIR}/${MFC} > /tmp/mfc.diff - @git checkout stable/${PLUGIN_ABI} - @git apply /tmp/mfc.diff - @git add ${.CURDIR} - @if ! git diff --quiet HEAD; then \ - git commit -m "${MFC}: sync with master"; \ - fi -.else - @git checkout stable/${PLUGIN_ABI} - @git cherry-pick -x ${MFC} -.endif - @git checkout master +.for _TARGET in ${.TARGETS} +__TARGET= ${TARGETS:M${_TARGET:C/-.*//}} +. if "${__TARGET}" != "" +${_TARGET}: +. for PLUGIN_DIR in ${PLUGIN_DIRS} + @echo ">>> Entering ${PLUGIN_DIR} with target '${_TARGET}'" + @${MAKE} -C ${PLUGIN_DIR} ${_TARGET} +. endfor +. endif .endfor license: diff --git a/Mk/common.mk b/Mk/common.mk new file mode 100644 index 0000000000..bfd8be9ffd --- /dev/null +++ b/Mk/common.mk @@ -0,0 +1,26 @@ +# Copyright (c) 2025 Franco Fichtner +# +# 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. + +.-include "${PLUGINSDIR}/../core/Mk/common.mk" diff --git a/Mk/contrib.mk b/Mk/contrib.mk new file mode 100644 index 0000000000..22f9df38d2 --- /dev/null +++ b/Mk/contrib.mk @@ -0,0 +1,46 @@ +# Copyright (c) 2025 Franco Fichtner +# +# 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. + +.for TARGET in ${PLUGIN_CONTRIB} +.if "${ROOT_${TARGET}}" == "" +.error "No ROOT directory set for target: ${TARGET}" +.endif + +# fixup root target dir +ROOT_${TARGET}:=${ROOT_${TARGET}:S/^\/$//} + +install-${TARGET}: + @mkdir -p ${DESTDIR}${ROOT_${TARGET}}/${TARGET} + @tar -C ${TARGET} -cf - . | tar -C ${DESTDIR}${ROOT_${TARGET}}/${TARGET} -xf - + +install: install-${TARGET} + +plist-${TARGET}: + @(cd ${TARGET}; find * -type f) | while read FILE; do \ + echo "${ROOT_${TARGET}}/${TARGET}/$${FILE}"; \ + done + +plist: plist-${TARGET} +.endfor diff --git a/Mk/defaults.mk b/Mk/defaults.mk index be747ef1b8..be52679327 100644 --- a/Mk/defaults.mk +++ b/Mk/defaults.mk @@ -1,4 +1,4 @@ -# Copyright (c) 2016-2020 Franco Fichtner +# Copyright (c) 2016-2025 Franco Fichtner # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -23,28 +23,81 @@ # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. +OSABIPREFIX= FreeBSD + LOCALBASE?= /usr/local PAGER?= less -OPENSSL?= ${LOCALBASE}/bin/openssl +PKG= ${LOCALBASE}/sbin/pkg +.if ! exists(${PKG}) +PKG= true +.endif +GIT!= which git || echo true + +SCRIPTSDIR= ${PLUGINSDIR}/Scripts +TEMPLATESDIR= ${PLUGINSDIR}/Templates -_FLAVOUR!= if [ -f ${OPENSSL} ]; then ${OPENSSL} version; fi -FLAVOUR?= ${_FLAVOUR:[1]} +GITVERSION= ${SCRIPTSDIR}/version.sh -PKG!= which pkg || echo true -GIT!= which git || echo true -ARCH!= uname -p +_PLUGIN_ARCH!= uname -p +PLUGIN_ARCH?= ${_PLUGIN_ARCH} + +VERSIONBIN= ${LOCALBASE}/sbin/opnsense-version + +.if exists(${VERSIONBIN}) +_PLUGIN_ABI!= ${VERSIONBIN} -a +PLUGIN_ABIS?= ${_PLUGIN_ABI} +.else +PLUGIN_ABIS?= 26.1 +.endif + +PLUGIN_ABI?= ${PLUGIN_ABIS:[1]} -PLUGIN_ABI?= 20.1 -PLUGIN_ARCH?= ${ARCH} -PLUGIN_FLAVOUR= ${FLAVOUR} +PLUGIN_MAINS= master main +PLUGIN_MAIN?= ${PLUGIN_MAINS:[1]} +PLUGIN_STABLE?= stable/${PLUGIN_ABI} +PLUGIN_UPSTREAM?=community + +PHPBIN= ${LOCALBASE}/bin/php + +.if exists(${PHPBIN}) +_PLUGIN_PHP!= ${PHPBIN} -v +PLUGIN_PHP?= ${_PLUGIN_PHP:[2]:S/./ /g:[1..2]:tW:S/ //} +.endif + +PYTHONLINK= ${LOCALBASE}/bin/python3 + +.if exists(${PYTHONLINK}) +_PLUGIN_PYTHON!=${PYTHONLINK} -V +PLUGIN_PYTHON?= ${_PLUGIN_PYTHON:[2]:S/./ /g:[1..2]:tW:S/ //} +.endif + +.for REPLACEMENT in ABI PHP PYTHON +. if empty(PLUGIN_${REPLACEMENT}) +. warning Cannot build without PLUGIN_${REPLACEMENT} set +. endif +.endfor REPLACEMENTS= PLUGIN_ABI \ PLUGIN_ARCH \ - PLUGIN_FLAVOUR + PLUGIN_CONFLICTS \ + PLUGIN_HASH \ + PLUGIN_MAINTAINER \ + PLUGIN_NAME \ + PLUGIN_PKGNAME \ + PLUGIN_PKGVERSION \ + PLUGIN_TIER \ + PLUGIN_VARIANT \ + PLUGIN_WWW SED_REPLACE= # empty .for REPLACEMENT in ${REPLACEMENTS} SED_REPLACE+= -e "s=%%${REPLACEMENT}%%=${${REPLACEMENT}}=g" .endfor + +WRKDIR?= ${.CURDIR}/work +MFCDIR?= /tmp/mfc.dir +PKGDIR?= ${WRKDIR}/pkg +WRKSRC?= ${WRKDIR}/src +TESTDIR?= ${.CURDIR}/src/opnsense/mvc/tests diff --git a/Mk/devel.mk b/Mk/devel.mk new file mode 100644 index 0000000000..f1a8bfd79a --- /dev/null +++ b/Mk/devel.mk @@ -0,0 +1,28 @@ +# Copyright (c) 2025 Franco Fichtner +# +# 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. + +# This file merely exists on the development +# branch to force plugins to development mode: +PLUGIN_DEVEL?= yes diff --git a/Mk/git.mk b/Mk/git.mk new file mode 100644 index 0000000000..5caf19bb1c --- /dev/null +++ b/Mk/git.mk @@ -0,0 +1,33 @@ +# Copyright (c) 2025 Franco Fichtner +# +# 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. + +CORE_ABI= ${PLUGIN_ABI} +CORE_ABIS= ${PLUGIN_ABIS} +CORE_MAIN= ${PLUGIN_MAIN} +CORE_MAINS= ${PLUGIN_MAINS} +CORE_STABLE= ${PLUGIN_STABLE} +CORE_UPSTREAM= ${PLUGIN_UPSTREAM} + +.-include "${PLUGINSDIR}/../core/Mk/git.mk" diff --git a/Mk/lint.mk b/Mk/lint.mk new file mode 100644 index 0000000000..ddc3c2ad73 --- /dev/null +++ b/Mk/lint.mk @@ -0,0 +1,26 @@ +# Copyright (c) 2025 Franco Fichtner +# +# 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. + +.-include "${PLUGINSDIR}/../core/Mk/lint.mk" diff --git a/Mk/plugins.mk b/Mk/plugins.mk index d95dba8ed2..2c89efc64e 100644 --- a/Mk/plugins.mk +++ b/Mk/plugins.mk @@ -1,4 +1,4 @@ -# Copyright (c) 2015-2020 Franco Fichtner +# Copyright (c) 2015-2025 Franco Fichtner # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -23,26 +23,38 @@ # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. +PLUGINSDIR?= ${.CURDIR}/../.. + all: check + @cat ${.CURDIR}/${PLUGIN_DESC} | ${PAGER} .include "defaults.mk" -PLUGIN_ARCH?= ${ARCH} -PLUGIN_PHP?= 72 -PLUGIN_PYTHON?= 37 +.if exists(${GIT}) && exists(${GITVERSION}) +PLUGIN_COMMIT!= ${GITVERSION} +.else +PLUGIN_COMMIT= unknown 0 undefined +.endif + +PLUGIN_HASH?= ${PLUGIN_COMMIT:[3]} PLUGIN_DESC= pkg-descr PLUGIN_SCRIPTS= +PRE_INSTALL +POST_INSTALL \ +PRE_DEINSTALL +POST_DEINSTALL -PLUGINSDIR= ${.CURDIR}/../.. -TEMPLATESDIR= ${PLUGINSDIR}/Templates - PLUGIN_WWW?= https://opnsense.org/ +PLUGIN_MAINTAINER?= N/A +PLUGIN_LICENSE?= BSD2CLAUSE +PLUGIN_TIER?= 3 PLUGIN_REVISION?= 0 -PLUGIN_REQUIRES= PLUGIN_NAME PLUGIN_VERSION PLUGIN_COMMENT \ - PLUGIN_MAINTAINER +PLUGIN_REQUIRES= PLUGIN_NAME PLUGIN_VERSION PLUGIN_COMMENT + +.include "common.mk" +.include "git.mk" +.include "lint.mk" +.include "style.mk" +.include "sweep.mk" check: .for PLUGIN_REQUIRE in ${PLUGIN_REQUIRES} @@ -51,50 +63,79 @@ check: . endif .endfor -PLUGIN_DEVEL?= yes +_PLUGIN_COMMENT:= ${PLUGIN_COMMENT} + +.if defined(_PLUGIN_DEVEL) +PLUGIN_DEVEL?:= ${_PLUGIN_DEVEL} +.else +.-include "devel.mk" +.endif PLUGIN_PREFIX?= os- PLUGIN_SUFFIX?= -devel -PLUGIN_PKGNAMES= ${PLUGIN_PREFIX}${PLUGIN_NAME}${PLUGIN_SUFFIX} \ - ${PLUGIN_PREFIX}${PLUGIN_NAME} .for CONFLICT in ${PLUGIN_CONFLICTS} -PLUGIN_PKGNAMES+= ${PLUGIN_PREFIX}${CONFLICT}${PLUGIN_SUFFIX} \ - ${PLUGIN_PREFIX}${CONFLICT} +PLUGIN_CONFLICTS+= ${CONFLICT}${PLUGIN_SUFFIX} .endfor +.if !empty(PLUGIN_VARIANTS) +PLUGIN_VARIANT?= ${PLUGIN_VARIANTS:[1]} +.if "${PLUGIN_VARIANT}" == "" +.error Plugin variant cannot be empty +.else +PLUGIN_NAME:= ${${PLUGIN_VARIANT}_NAME} +.if empty(PLUGIN_NAME) +.error Plugin variant '${PLUGIN_VARIANT}' does not exist +.endif +.for _PLUGIN_VARIANT in ${PLUGIN_VARIANTS:N${PLUGIN_VARIANT}} +PLUGIN_CONFLICTS+= ${${_PLUGIN_VARIANT}_NAME}${PLUGIN_SUFFIX} \ + ${${_PLUGIN_VARIANT}_NAME} +.endfor +PLUGIN_DEPENDS+= ${${PLUGIN_VARIANT}_DEPENDS} +.if !empty(${PLUGIN_VARIANT}_COMMENT) +_PLUGIN_COMMENT:= ${${PLUGIN_VARIANT}_COMMENT} +.endif +.endif +.endif + +.if !empty(PLUGIN_NAME) +PLUGIN_DIR?= ${.CURDIR:S/\// /g:[-2]}/${.CURDIR:S/\// /g:[-1]} +.endif + .if "${PLUGIN_DEVEL}" != "" -PLUGIN_PKGNAME= ${PLUGIN_PREFIX}${PLUGIN_NAME}${PLUGIN_SUFFIX} +PLUGIN_CONFLICTS+= ${PLUGIN_NAME} +PLUGIN_PKGSUFFIX= ${PLUGIN_SUFFIX} +PLUGIN_TIER= 4 .else -PLUGIN_PKGNAME= ${PLUGIN_PREFIX}${PLUGIN_NAME} +PLUGIN_CONFLICTS+= ${PLUGIN_NAME}${PLUGIN_SUFFIX} +PLUGIN_PKGSUFFIX= # empty .endif +PLUGIN_CONFLICTS:= ${PLUGIN_CONFLICTS:S/^/${PLUGIN_PREFIX}/g:O} + +PLUGIN_PKGNAME= ${PLUGIN_PREFIX}${PLUGIN_NAME}${PLUGIN_PKGSUFFIX} +PLUGIN_PKGNAMES= ${PLUGIN_CONFLICTS} ${PLUGIN_PKGNAME} + .if "${PLUGIN_REVISION}" != "" && "${PLUGIN_REVISION}" != "0" PLUGIN_PKGVERSION= ${PLUGIN_VERSION}_${PLUGIN_REVISION} .else PLUGIN_PKGVERSION= ${PLUGIN_VERSION} .endif -name: check - @echo ${PLUGIN_PKGNAME} - -depends: check - @echo ${PLUGIN_DEPENDS} - manifest: check @echo "name: ${PLUGIN_PKGNAME}" @echo "version: \"${PLUGIN_PKGVERSION}\"" @echo "origin: opnsense/${PLUGIN_PKGNAME}" - @echo "comment: \"${PLUGIN_COMMENT}\"" + @echo "comment: \"${_PLUGIN_COMMENT}\"" @echo "maintainer: \"${PLUGIN_MAINTAINER}\"" @echo "categories: [ \"${.CURDIR:S/\// /g:[-2]}\" ]" @echo "www: \"${PLUGIN_WWW}\"" @echo "prefix: \"${LOCALBASE}\"" @echo "licenselogic: \"single\"" - @echo "licenses: [ \"BSD2CLAUSE\" ]" + @echo "licenses: [ \"${PLUGIN_LICENSE}\" ]" .if defined(PLUGIN_NO_ABI) - @echo "arch: `pkg config abi | tr '[:upper:]' '[:lower:]' | cut -d: -f1`:*:*" - @echo "abi: `pkg config abi | cut -d: -f1`:*:*" + @echo "arch: \"${OSABIPREFIX:tl}:*:*\"" + @echo "abi: \"${OSABIPREFIX}:*:*\"" .endif .if defined(PLUGIN_DEPENDS) @echo "deps: {" @@ -107,14 +148,30 @@ manifest: check done @echo "}" .endif + @if [ -f ${WRKSRC}${LOCALBASE}/opnsense/version/${PLUGIN_NAME} ]; then \ + echo "annotations $$(cat ${WRKSRC}${LOCALBASE}/opnsense/version/${PLUGIN_NAME})"; \ + fi + +# Package scripts generation handling 101: +# +# "auto" generates automatic hooks that a plugin may need in order to +# reload on the fly. ".pre" and ".post" suffixed files can be used to +# extend the auto-generated content. +# +# "manual" overwrites the automatic script and also ignores ".pre" and +# ".post" files since they do not make sense in manual mode. +# +# Furthermore, variable replacement via %%PLUGIN_VAR%% takes place in +# "manual" as well as ".pre" and ".post" scripts provided. -scripts: check scripts-pre scripts-auto scripts-manual scripts-post +scripts: check scripts-pre scripts-auto scripts-post scripts-manual scripts-pre: @for SCRIPT in ${PLUGIN_SCRIPTS}; do \ rm -f ${DESTDIR}/$${SCRIPT}; \ if [ -f ${.CURDIR}/$${SCRIPT}.pre ]; then \ - cp ${.CURDIR}/$${SCRIPT}.pre ${DESTDIR}/$${SCRIPT}; \ + sed ${SED_REPLACE} -- ${.CURDIR}/$${SCRIPT}.pre > \ + ${DESTDIR}/$${SCRIPT}; \ fi; \ done @@ -158,40 +215,72 @@ scripts-auto: ${DESTDIR}/+POST_INSTALL; \ done; \ fi + @if [ -d ${.CURDIR}/src/opnsense/scripts/firmware/repos ]; then \ + for FILE in $$(cd ${.CURDIR}/src && find -s \ + opnsense/scripts/firmware/repos -type f); do \ + echo "${LOCALBASE}/$${FILE#.}" >> ${DESTDIR}/+POST_INSTALL; \ + done \ + fi -scripts-manual: +scripts-post: @for SCRIPT in ${PLUGIN_SCRIPTS}; do \ - if [ -f ${.CURDIR}/$${SCRIPT} ]; then \ - cp ${.CURDIR}/$${SCRIPT} ${DESTDIR}/$${SCRIPT}; \ + if [ -f ${.CURDIR}/$${SCRIPT}.post ]; then \ + sed ${SED_REPLACE} -- ${.CURDIR}/$${SCRIPT}.post >> \ + ${DESTDIR}/$${SCRIPT}; \ fi; \ done -scripts-post: +scripts-manual: @for SCRIPT in ${PLUGIN_SCRIPTS}; do \ - if [ -f ${.CURDIR}/$${SCRIPT}.post ]; then \ - cat ${.CURDIR}/$${SCRIPT}.post >> ${DESTDIR}/$${SCRIPT}; \ + if [ -f ${.CURDIR}/$${SCRIPT} ]; then \ + sed ${SED_REPLACE} -- ${.CURDIR}/$${SCRIPT} > \ + ${DESTDIR}/$${SCRIPT}; \ fi; \ done install: check @mkdir -p ${DESTDIR}${LOCALBASE}/opnsense/version - @(cd ${.CURDIR}/src; find * -type f) | while read FILE; do \ - tar -C ${.CURDIR}/src -cpf - $${FILE} | \ + @if [ -d ${.CURDIR}/contrib ]; then ${MAKE} DESTDIR=$$(readlink -f ${DESTDIR}) -C ${.CURDIR}/contrib install; fi + @(cd ${.CURDIR}/src 2> /dev/null && find * -type f) | while read FILE; do \ + tar -C ${.CURDIR}/src -cpf - "$${FILE}" | \ tar -C ${DESTDIR}${LOCALBASE} -xpf -; \ if [ "$${FILE%%.in}" != "$${FILE}" ]; then \ sed -i '' ${SED_REPLACE} "${DESTDIR}${LOCALBASE}/$${FILE}"; \ mv "${DESTDIR}${LOCALBASE}/$${FILE}" "${DESTDIR}${LOCALBASE}/$${FILE%%.in}"; \ + FILE="$${FILE%%.in}"; \ + fi; \ + if [ "$${FILE%%.shadow}" != "$${FILE}" ]; then \ + mv "${DESTDIR}${LOCALBASE}/$${FILE}" \ + "${DESTDIR}${LOCALBASE}/$${FILE%%.shadow}.sample"; \ + fi; \ + if [ "$${FILE%%/*}" == "man" ]; then \ + gzip -cn "${DESTDIR}${LOCALBASE}/$${FILE}" > \ + "${DESTDIR}${LOCALBASE}/$${FILE}.gz"; \ + rm "${DESTDIR}${LOCALBASE}/$${FILE}"; \ fi; \ done - @echo "${PLUGIN_PKGVERSION}" > "${DESTDIR}${LOCALBASE}/opnsense/version/${PLUGIN_NAME}" + @cat ${TEMPLATESDIR}/version | sed ${SED_REPLACE} > "${DESTDIR}${LOCALBASE}/opnsense/version/${PLUGIN_NAME}" plist: check - @(cd ${.CURDIR}/src; find * -type f) | while read FILE; do \ + @(if [ -d ${.CURDIR}/contrib ]; then \ + ${MAKE} -C ${.CURDIR}/contrib plist; \ + fi; \ + (cd ${.CURDIR}/src 2> /dev/null && find * -type f) | while read FILE; do \ if [ -f "$${FILE}.in" ]; then continue; fi; \ - FILE="$${FILE%%.in}"; \ - echo ${LOCALBASE}/$${FILE}; \ - done - @echo "${LOCALBASE}/opnsense/version/${PLUGIN_NAME}" + FILE="$${FILE%%.in}"; PREFIX=""; \ + if [ "$${FILE%%.sample}" != "$${FILE}" ]; then \ + PREFIX="@sample "; \ + elif [ "$${FILE%%.shadow}" != "$${FILE}" ]; then \ + FILE="$${FILE%%.shadow}.sample"; \ + PREFIX="@shadow "; \ + fi; \ + if [ "$${FILE%%/*}" == "man" ]; then \ + FILE="$${FILE}.gz"; \ + fi; \ + echo "$${PREFIX}${LOCALBASE}/$${FILE}"; \ + done; \ + echo "${LOCALBASE}/opnsense/version/${PLUGIN_NAME}" \ + ) | sort description: check @if [ -f ${.CURDIR}/${PLUGIN_DESC} ]; then \ @@ -206,39 +295,37 @@ metadata: check @${MAKE} DESTDIR=${DESTDIR} plist > ${DESTDIR}/plist collect: check - @(cd ${.CURDIR}/src; find * -type f) | while read FILE; do \ - tar -C ${DESTDIR}${LOCALBASE} -cpf - $${FILE} | \ + @(cd ${.CURDIR}/src 2> /dev/null && find * -type f) | while read FILE; do \ + tar -C ${DESTDIR}${LOCALBASE} -cpf - "$${FILE}" | \ tar -C ${.CURDIR}/src -xpf -; \ done remove: check - @(cd ${.CURDIR}/src; find * -type f) | while read FILE; do \ - rm -f ${DESTDIR}${LOCALBASE}/$${FILE}; \ + @(cd ${.CURDIR}/src 2> /dev/null && find * -type f) | while read FILE; do \ + rm -f "${DESTDIR}${LOCALBASE}/$${FILE}"; \ done - @(cd ${.CURDIR}/src; find * -type d -depth) | while read DIR; do \ - if [ -d ${DESTDIR}${LOCALBASE}/$${DIR} ]; then \ - rmdir ${DESTDIR}${LOCALBASE}/$${DIR} 2> /dev/null || true; \ + @(cd ${.CURDIR}/src 2> /dev/null && find * -type d -depth) | while read DIR; do \ + if [ -d "${DESTDIR}${LOCALBASE}/$${DIR}" ]; then \ + rmdir "${DESTDIR}${LOCALBASE}/$${DIR}" 2> /dev/null || true; \ fi; \ done -WRKDIR?=${.CURDIR}/work -WRKSRC?=${WRKDIR}/src -PKGDIR?=${WRKDIR}/pkg - package: check @rm -rf ${WRKSRC} @mkdir -p ${WRKSRC} ${PKGDIR} .for DEP in ${PLUGIN_DEPENDS} @if ! ${PKG} info ${DEP} > /dev/null; then ${PKG} install -yA ${DEP}; fi .endfor - @echo -n ">>> Generating metadata for ${PLUGIN_PKGNAME}-${PLUGIN_PKGVERSION}..." - @${MAKE} DESTDIR=${WRKSRC} FLAVOUR=${FLAVOUR} metadata - @echo " done" @echo -n ">>> Staging files for ${PLUGIN_PKGNAME}-${PLUGIN_PKGVERSION}..." - @${MAKE} DESTDIR=${WRKSRC} FLAVOUR=${FLAVOUR} install + @${MAKE} DESTDIR=${WRKSRC} install + @echo " done" + @echo ">>> Generated version info for ${PLUGIN_PKGNAME}-${PLUGIN_PKGVERSION}:" + @cat ${WRKSRC}${LOCALBASE}/opnsense/version/${PLUGIN_NAME} + @echo -n ">>> Generating metadata for ${PLUGIN_PKGNAME}-${PLUGIN_PKGVERSION}..." + @${MAKE} DESTDIR=${WRKSRC} metadata @echo " done" @echo ">>> Packaging files for ${PLUGIN_PKGNAME}-${PLUGIN_PKGVERSION}:" - @${PKG} create -v -m ${WRKSRC} -r ${WRKSRC} \ + @PORTSDIR=${PLUGINSDIR} ${PKG} create -v -m ${WRKSRC} -r ${WRKSRC} \ -p ${WRKSRC}/plist -o ${PKGDIR} upgrade-check: check @@ -250,7 +337,7 @@ upgrade: upgrade-check package ${PKG} delete -fy ${NAME}; \ fi .endfor - @${PKG} add ${PKGDIR}/*.txz + @${PKG} add ${PKGDIR}/*.pkg mount: check mount_unionfs ${.CURDIR}/src ${DESTDIR}${LOCALBASE} @@ -266,92 +353,20 @@ clean: check fi @rm -rf ${.CURDIR}/work -lint-desc: check - @if [ ! -f ${.CURDIR}/${PLUGIN_DESC} ]; then \ - echo ">>> Missing ${PLUGIN_DESC}"; exit 1; \ - fi - -lint-shell: - @for FILE in $$(find ${.CURDIR}/src -name "*.sh" -type f); do \ - if [ "$$(head $${FILE} | grep -c '^#!\/bin\/sh$$')" == "0" ]; then \ - echo "Missing shebang in $${FILE}"; exit 1; \ - fi; \ - sh -n $${FILE} || exit 1; \ - done +glint: sweep lint -lint-xml: - @find ${.CURDIR}/src \ - -name "*.xml" -type f -print0 | xargs -0 -n1 xmllint --noout +revision: + @MAKE=${MAKE} ${SCRIPTSDIR}/revbump.sh ${.CURDIR} -lint-exec: check -.for DIR in ${.CURDIR}/src/opnsense/scripts ${.CURDIR}/src/etc/rc.d ${.CURDIR}/src/etc/rc.syshook.d -.if exists(${DIR}) - @find ${DIR} -type f ! -name "*.xml" -print0 | \ - xargs -0 -t -n1 test -x || \ - (echo "Missing executable permission in ${DIR}"; exit 1) +test: check +.if exists(${TESTDIR}) + @cd ${TESTDIR} && phpunit || true; \ + rm -rf ${TESTDIR}/.phpunit.result.cache .endif -.endfor - -lint-php: check - @find ${.CURDIR}/src \ - ! -name "*.xml" ! -name "*.xml.sample" ! -name "*.eot" \ - ! -name "*.svg" ! -name "*.woff" ! -name "*.woff2" \ - ! -name "*.otf" ! -name "*.png" ! -name "*.js" \ - ! -name "*.scss" ! -name "*.py" ! -name "*.ttf" \ - ! -name "*.tgz" ! -name "*.xml.dist" ! -name "*.sh" \ - -type f -print0 | xargs -0 -n1 php -l - -lint: lint-desc lint-shell lint-xml lint-exec lint-php - -sweep: check - find ${.CURDIR}/src -type f -name "*.map" -print0 | \ - xargs -0 -n1 rm - if grep -nr sourceMappingURL= ${.CURDIR}/src; then \ - echo "Mentions of sourceMappingURL must be removed"; \ - exit 1; \ - fi - find ${.CURDIR}/src ! -name "*.min.*" ! -name "*.svg" \ - ! -name "*.ser" -type f -print0 | \ - xargs -0 -n1 ${.CURDIR}/../../Scripts/cleanfile - find ${.CURDIR} -type f -depth 1 -print0 | \ - xargs -0 -n1 ${.CURDIR}/../../Scripts/cleanfile - -STYLEDIRS?= src/etc/inc src/opnsense - -style: check - @: > ${.CURDIR}/.style.out -.for STYLEDIR in ${STYLEDIRS} - @if [ -d ${.CURDIR}/${STYLEDIR} ]; then \ - (phpcs --standard=${.CURDIR}/../../ruleset.xml \ - ${.CURDIR}/${STYLEDIR} || true) > \ - ${.CURDIR}/.style.out; \ - fi -.endfor - @echo -n "Total number of style warnings: " - @grep '| WARNING' ${.CURDIR}/.style.out | wc -l - @echo -n "Total number of style errors: " - @grep '| ERROR' ${.CURDIR}/.style.out | wc -l - @cat ${.CURDIR}/.style.out - @rm ${.CURDIR}/.style.out - -style-fix: check -.for STYLEDIR in ${STYLEDIRS} - @if [ -d ${.CURDIR}/${STYLEDIR} ]; then \ - phpcbf --standard=${.CURDIR}/../../ruleset.xml \ - ${.CURDIR}/${STYLEDIR} || true; \ - fi -.endfor - -style-python: check - @if [ -d ${.CURDIR}/src ]; then \ - pycodestyle --ignore=E501 ${.CURDIR}/src || true; \ - fi -test: check - @if [ -d ${.CURDIR}/src/opnsense/mvc/tests ]; then \ - cd /usr/local/opnsense/mvc/tests && \ - phpunit --configuration PHPunit.xml \ - ${.CURDIR}/src/opnsense/mvc/tests; \ - fi +commit: + @mkdir -p ${MFCDIR} + @/bin/echo -n "${.CURDIR:C/\// /g:[-2]}/${.CURDIR:C/\// /g:[-1]}: " > \ + ${MFCDIR}/.commitmsg && git commit -eF ${MFCDIR}/.commitmsg . .PHONY: check diff --git a/Mk/style.mk b/Mk/style.mk new file mode 100644 index 0000000000..e9dcc839fd --- /dev/null +++ b/Mk/style.mk @@ -0,0 +1,28 @@ +# Copyright (c) 2025 Franco Fichtner +# +# 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. + +CORE_PYTHON_DOT=${PLUGIN_PYTHON:C/./&./1} + +.-include "${PLUGINSDIR}/../core/Mk/style.mk" diff --git a/Mk/sweep.mk b/Mk/sweep.mk new file mode 100644 index 0000000000..ced1d0ea34 --- /dev/null +++ b/Mk/sweep.mk @@ -0,0 +1,26 @@ +# Copyright (c) 2025 Franco Fichtner +# +# 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. + +.-include "${PLUGINSDIR}/../core/Mk/sweep.mk" diff --git a/README.md b/README.md index 5464e9a951..fa5c613514 100644 --- a/README.md +++ b/README.md @@ -35,74 +35,99 @@ devel/debug -- Debugging Tools devel/grid_example -- A sample framework application devel/helloworld -- A sample framework application dns/bind -- BIND domain name service +dns/ddclient -- Dynamic DNS client dns/dnscrypt-proxy -- Flexible DNS proxy supporting DNSCrypt and DoH -dns/dyndns -- Dynamic DNS Support dns/rfc2136 -- RFC-2136 Support -dns/unbound-plus -- Unbound additions +emulators/qemu-guest-agent -- QEMU Guest Agent for OPNsense +ftp/tftp -- TFTP server mail/postfix -- SMTP mail relay mail/rspamd -- Protect your network from spam -misc/theme-cicada -- The cicada theme - dark grey +misc/theme-advanced -- Theme based on AdvancedTomato GUI +misc/theme-cicada -- The cicada theme - dark grey onyx +misc/theme-flexcolor -- Theme with 3 different color schemes: black as default, light and dark-light misc/theme-rebellion -- A suitably dark theme misc/theme-tukan -- The tukan theme - blue/white -misc/theme-vicuna -- The vicuna theme - dark anthrazit -net-mgmt/collectd -- Collect system and application performance metrics periodically -net-mgmt/lldpd -- LLDP allows you to know exactly on which port is a server -net-mgmt/net-snmp -- Net-SNMP is a daemon for the SNMP protocol -net-mgmt/netdata -- Real-time performance monitoring -net-mgmt/nrpe -- Execute nagios plugins -net-mgmt/telegraf -- Agent for collecting metrics and data -net-mgmt/zabbix-agent -- Zabbix monitoring agent -net-mgmt/zabbix4-proxy -- Zabbix Proxy enables decentralized monitoring +misc/theme-vicuna -- The vicuna theme - blue sapphire +net/chrony -- Chrony time synchronisation net/freeradius -- RADIUS Authentication, Authorization and Accounting Server net/frr -- The FRRouting Protocol Suite net/ftp-proxy -- Control ftp-proxy processes net/google-cloud-sdk -- Google Cloud SDK net/haproxy -- Reliable, high performance TCP/HTTP load balancer -net/igmp-proxy -- IGMP-Proxy Service -net/l2tp -- End of life, no replacement +net/igmp-proxy -- IGMP-Proxy Service (not maintained) +net/isc-dhcp -- ISC DHCPv4/v6 server net/mdns-repeater -- Proxy multicast DNS between networks +net/ndp-proxy-go -- IPv6 Neighbor Discovery Protocol (NDP) Proxy +net/ndproxy -- Neighbor Discovery Proxy net/ntopng -- Traffic Analysis and Flow Collection -net/pppoe -- End of life, no replacement -net/pptp -- End of life, no replacement +net/radsecproxy -- RADIUS proxy provides both RADIUS UDP and TCP/TLS (RadSec) transport +net/realtek-re -- Realtek re(4) vendor driver net/relayd -- Relayd Load Balancer net/shadowsocks -- Secure socks5 proxy net/siproxd -- Siproxd is a proxy daemon for the SIP protocol -net/tayga -- Tayga IPv6 64NAT -net/upnp -- Universal Plug and Play Service -net/vnstat -- vnStat is a console-based network traffic monitor -net/wireguard -- WireGuard VPN service -net/wol -- Wake on LAN Service -net/zerotier -- Virtual Networks That Just Work -security/acme-client -- Let's Encrypt client +net/sslh -- sslh configuration front-end +net/tayga -- Tayga NAT64 +net/turnserver -- The coturn STUN/TURN Server +net/udpbroadcastrelay -- Control udpbroadcastrelay processes +net/upnp -- UPnP IGD & PCP/NAT-PMP Service +net/vnstat -- Network traffic monitor +net/wol -- Wake on LAN Service (not maintained) +net/zerotier -- Virtual Networks That Just Work (not maintained) +net-mgmt/collectd -- Collect system and application performance metrics periodically +net-mgmt/lldpd -- LLDP allows you to know exactly on which port is a server +net-mgmt/net-snmp -- Net-SNMP is a daemon for the SNMP protocol +net-mgmt/netdata -- Real-time performance monitoring +net-mgmt/nrpe -- Execute nagios plugins +net-mgmt/telegraf -- Agent for collecting metrics and data +net-mgmt/zabbix-agent -- Zabbix monitoring agent +net-mgmt/zabbix-proxy -- Zabbix monitoring proxy +security/acme-client -- ACME Client security/clamav -- Antivirus engine for detecting malicious threats +security/crowdsec -- Lightweight and collaborative security engine security/etpro-telemetry -- ET Pro Telemetry Edition +security/intrusion-detection-content-et-open -- IDS Proofpoint full ET open ruleset complementary subset for ET Pro Telemetry edition security/intrusion-detection-content-et-pro -- IDS Proofpoint ET Pro ruleset (needs a valid subscription) -security/intrusion-detection-content-pt-open -- IDS PT Research ruleset (only for non-commercial use) +security/intrusion-detection-content-pt-open -- IDS Positive Technologies ESC ruleset security/intrusion-detection-content-snort-vrt -- IDS Snort VRT ruleset (needs registration or subscription) security/maltrail -- Malicious traffic detection system +security/netbird -- Peer-to-peer VPN that seamlessly connects your devices security/openconnect -- OpenConnect Client -security/softether -- Cross-platform Multi-protocol VPN Program +security/openvpn-legacy -- OpenVPN legacy support +security/q-feeds-connector -- Connector for Q-Feeds threat intel +security/strongswan-legacy -- IPsec legacy support +security/stunnel -- Stunnel TLS proxy +security/tailscale -- VPN mesh securely connecting clients using WireGuard security/tinc -- Tinc VPN security/tor -- The Onion Router -sysutils/api-backup -- Provide the functionality to download the config.xml -sysutils/apuled -- PC Engine APU LED control -sysutils/boot-delay -- Apply a persistent 10 second boot delay -sysutils/dmidecode -- Display hardware information on the dashboard -sysutils/lcdproc-sdeclcd -- LCDProc for SDEC LCD devices +security/wazuh-agent -- Agent for the open source security platform Wazuh +sysutils/apcupsd -- APCUPSD - APC UPS daemon +sysutils/beats -- Send logs, network, metrics and heartbeat to Elasticsearch +sysutils/cpu-microcode -- CPU microcode updates +sysutils/dec-hw -- Deciso hardware specific information +sysutils/dmidecode -- Display hardware information on the dashboard (not maintained) +sysutils/gdrive-backup -- Backup configurations using Google Drive +sysutils/git-backup -- Track config changes using git +sysutils/hw-probe -- Collect hardware diagnostics +sysutils/lcdproc-sdeclcd -- LCDProc for SDEC LCD devices (not maintained) sysutils/mail-backup -- Send configuration file backup by e-mail -sysutils/munin-node -- Munin monitorin agent +sysutils/munin-node -- Munin monitoring agent +sysutils/nextcloud-backup -- Track config changes using NextCloud (not maintained) sysutils/node_exporter -- Prometheus exporter for machine metrics sysutils/nut -- Network UPS Tools -sysutils/smart -- SMART tools +sysutils/puppet-agent -- Manage Puppet Agent +sysutils/sftp-backup -- Backup configurations using SFTP +sysutils/smart -- SMART tools (not maintained) sysutils/virtualbox -- VirtualBox guest additions sysutils/vmware -- VMware tools sysutils/xen -- Xen guest utilities -vendor/sunnyvalley -- Vendor repository for Sensei (Next Generation Firewall Extensions) +vendor/sunnyvalley -- Vendor Repository for Zenarmor - Enterprise SASE & SSE platform (NGFW, SWG, CASB, ZTNA, SD-WAN) +www/OPNProxy -- OPNsense proxy additions (not maintained) www/c-icap -- c-icap connects the web proxy with a virus scanner www/cache -- Webserver cache +www/caddy -- Modern Reverse Proxy with Automatic HTTPS, Dynamic DNS and Layer4 Routing www/nginx -- Nginx HTTP server and reverse proxy -www/web-proxy-sso -- Kerberos authentication module -www/web-proxy-useracl -- Group and user ACL for the web proxy +www/squid -- Squid is a caching proxy for the web (not maintained) +www/web-proxy-sso -- Kerberos authentication module (not maintained) ``` A brief description of how to use the plugins repository @@ -127,9 +152,8 @@ The make targets for the root directory: * clean: remove all changes and unknown files * lint: run syntax checks * list: print a list of all plugin directories with comments -* style-fix: apply style fixes * style: run style checks -* sweep: apply whitespace fixes +* sweep: apply style fixes The make targets for any plugin directory: @@ -140,6 +164,5 @@ The make targets for any plugin directory: * package: creates a package * upgrade: upgrades existing package * remove: remove known files from target directory -* style-fix: apply style fixes * style: run style checks -* sweep: apply whitespace fixes +* sweep: apply style fixes diff --git a/Scripts/cleanfile b/Scripts/cleanfile deleted file mode 100755 index 8abf76025b..0000000000 --- a/Scripts/cleanfile +++ /dev/null @@ -1,185 +0,0 @@ -#!/usr/bin/env perl -# -# Clean a text file -- or directory of text files -- of stealth whitespace. -# WARNING: this can be a highly destructive operation. Use with caution. -# - -use bytes; -use File::Basename; - -# Default options -$max_width = 0; - -# Clean up space-tab sequences, either by removing spaces or -# replacing them with tabs. -sub clean_space_tabs($) -{ - no bytes; # Tab alignment depends on characters - - my($li) = @_; - my($lo) = ''; - my $pos = 0; - my $nsp = 0; - my($i, $c); - - for ($i = 0; $i < length($li); $i++) { - $c = substr($li, $i, 1); - if ($c eq "\t") { - my $npos = ($pos+$nsp+8) & ~7; - my $ntab = ($npos >> 3) - ($pos >> 3); - $lo .= "\t" x $ntab; - $pos = $npos; - $nsp = 0; - } elsif ($c eq "\n" || $c eq "\r") { - $lo .= " " x $nsp; - $pos += $nsp; - $nsp = 0; - $lo .= $c; - $pos = 0; - } elsif ($c eq " ") { - $nsp++; - } else { - $lo .= " " x $nsp; - $pos += $nsp; - $nsp = 0; - $lo .= $c; - $pos++; - } - } - $lo .= " " x $nsp; - return $lo; -} - -# Compute the visual width of a string -sub strwidth($) { - no bytes; # Tab alignment depends on characters - - my($li) = @_; - my($c, $i); - my $pos = 0; - my $mlen = 0; - - for ($i = 0; $i < length($li); $i++) { - $c = substr($li,$i,1); - if ($c eq "\t") { - $pos = ($pos+8) & ~7; - } elsif ($c eq "\n") { - $mlen = $pos if ($pos > $mlen); - $pos = 0; - } else { - $pos++; - } - } - - $mlen = $pos if ($pos > $mlen); - return $mlen; -} - -$name = basename($0); - -@files = (); - -while (defined($a = shift(@ARGV))) { - if ($a =~ /^-/) { - if ($a eq '-width' || $a eq '-w') { - $max_width = shift(@ARGV)+0; - } else { - print STDERR "Usage: $name [-width #] files...\n"; - exit 1; - } - } else { - push(@files, $a); - } -} - -foreach $f ( @files ) { - print STDERR "$name: $f\n"; - - if (! -f $f) { - print STDERR "$f: not a file\n"; - next; - } - - if (!open(FILE, '+<', $f)) { - print STDERR "$name: Cannot open file: $f: $!\n"; - next; - } - - binmode FILE; - - # First, verify that it is not a binary file; consider any file - # with a zero byte to be a binary file. Is there any better, or - # additional, heuristic that should be applied? - $is_binary = 0; - - while (read(FILE, $data, 65536) > 0) { - if ($data =~ /\0/) { - $is_binary = 1; - last; - } - } - - if ($is_binary) { - print STDERR "$name: $f: binary file\n"; - next; - } - - seek(FILE, 0, 0); - - $in_bytes = 0; - $out_bytes = 0; - $blank_bytes = 0; - - @blanks = (); - @lines = (); - $last = "\n"; - $lineno = 0; - - while ( defined($line = ) ) { - $lineno++; - $in_bytes += length($line); - $line =~ s/[ \t\r]*$//; # Remove trailing spaces - $line = clean_space_tabs($line); - $last = $line; - - if ( $line eq "\n" ) { - push(@blanks, $line); - $blank_bytes += length($line); - } else { - push(@lines, @blanks); - $out_bytes += $blank_bytes; - push(@lines, $line); - $out_bytes += length($line); - @blanks = (); - $blank_bytes = 0; - } - - $l_width = strwidth($line); - if ($max_width && $l_width > $max_width) { - print STDERR - "$f:$lineno: line exceeds $max_width characters ($l_width)\n"; - } - } - - if ( chop($last) ne "\n" ) { - # fix missing newline at EOF - push(@lines, "\n"); - # increment input bytes to signal character append - $in_bytes += 1; - } - - # Any blanks at the end of the file are discarded - - if ($in_bytes != $out_bytes) { - # Only write to the file if changed - seek(FILE, 0, 0); - print FILE @lines; - - if ( !defined($where = tell(FILE)) || - !truncate(FILE, $where) ) { - die "$name: Failed to truncate modified file: $f: $!\n"; - } - } - - close(FILE); -} diff --git a/Scripts/license b/Scripts/license index 01d2d97d58..38d61f2602 100755 --- a/Scripts/license +++ b/Scripts/license @@ -67,6 +67,7 @@ sub process_file my $filename = $File::Find::name; return if not -f "$cwd/$filename"; + return if $filename =~ /\/Private\//; my @lines = read_file( "$cwd/$filename" ); my $possibly_bsd; @@ -84,7 +85,7 @@ sub process_file for my $line ( @lines ) { my $copy = $line; next if $line !~ s/copyright\s+\(c\)\s+//i; - $line =~ s/^[."\*\\#\s]+//g; + $line =~ s/^[."\*\\#\s-]+//g; $line =~ s/\s+$//g; chomp $copy; $copy =~ s/^[\*\\#\s]+//g; @@ -93,7 +94,7 @@ sub process_file $start = $1; $start =~ s/[,\s\-]+//g; } - if ( $line =~ s/(\d\d\d\d\s*,?\s+)// ) { + if ( $line =~ s/^(\d\d\d\d\s*,?\s+)// ) { $end = $1; $end =~ s/[,\s]+//g; } @@ -113,7 +114,7 @@ sub process_file find( \&process_file, $src ); -for ( sort keys %copyrights ) { +for ( sort { lc($a) cmp lc($b) } keys %copyrights ) { my $date = $copyrights{$_}[0]; next if $date == 0; $date = join '-', @{ $copyrights{$_} } if $copyrights{$_}[1] != $date; diff --git a/Scripts/revbump.sh b/Scripts/revbump.sh new file mode 100755 index 0000000000..674fd50c27 --- /dev/null +++ b/Scripts/revbump.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +set -e + +DIR=${1} + +if [ -z "${DIR}" ]; then + DIR=. +fi + +REV=$(${MAKE} -C ${DIR} -v PLUGIN_REVISION) +REV=$((REV + 1)) + +grep -v ^PLUGIN_REVISION ${DIR}/Makefile > ${DIR}/Makefile.tmp +sed -e "s/^\(PLUGIN_VERSION.*\)/\1%PLUGIN_REVISION= ${REV}/g" \ + ${DIR}/Makefile.tmp | tr '%' '\n' > ${DIR}/Makefile +rm -f ${DIR}/Makefile.tmp diff --git a/Scripts/update-list.sh b/Scripts/update-list.sh index 0fd2aa5e50..be82d5e54a 100755 --- a/Scripts/update-list.sh +++ b/Scripts/update-list.sh @@ -29,7 +29,7 @@ set -e PATTERN=XXXNEWLISTHEREXXX -(echo '```'; ${MAKE} list; echo '```') > README.list +(echo '```'; ${MAKE} list 2> /dev/null; echo '```') > README.list sed -e '/```/,/```/c\ '"${PATTERN}"' diff --git a/Scripts/version.sh b/Scripts/version.sh new file mode 100755 index 0000000000..402d840839 --- /dev/null +++ b/Scripts/version.sh @@ -0,0 +1,34 @@ +#!/bin/sh + +# Copyright (c) 2015 Franco Fichtner +# +# 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. + +set -e + +VERSION=$(git describe --abbrev=0 --always ${1}) +REVISION=$(git rev-list ${VERSION}.. --count) +HASH=$(git rev-list HEAD --max-count=1 | cut -c1-9) + +echo ${VERSION} ${REVISION} ${HASH} diff --git a/Templates/version b/Templates/version new file mode 100644 index 0000000000..82d753f288 --- /dev/null +++ b/Templates/version @@ -0,0 +1,12 @@ +{ + "product_abi": "%%PLUGIN_ABI%%", + "product_arch": "%%PLUGIN_ARCH%%", + "product_conflicts": "%%PLUGIN_CONFLICTS%%", + "product_email": "%%PLUGIN_MAINTAINER%%", + "product_hash": "%%PLUGIN_HASH%%", + "product_id": "%%PLUGIN_PKGNAME%%", + "product_name": "%%PLUGIN_NAME%%", + "product_tier": "%%PLUGIN_TIER%%", + "product_version": "%%PLUGIN_PKGVERSION%%", + "product_website": "%%PLUGIN_WWW%%" +} diff --git a/benchmarks/iperf/Makefile b/benchmarks/iperf/Makefile index e32555da33..c3e8860aec 100644 --- a/benchmarks/iperf/Makefile +++ b/benchmarks/iperf/Makefile @@ -1,7 +1,8 @@ PLUGIN_NAME= iperf PLUGIN_VERSION= 1.0 +PLUGIN_REVISION= 2 PLUGIN_COMMENT= Connection speed tester -PLUGIN_DEPENDS= iperf3 ruby +PLUGIN_DEPENDS= iperf3 ruby rubygem-rexml PLUGIN_MAINTAINER= franz.fabian.94@gmail.com .include "../../Mk/plugins.mk" diff --git a/benchmarks/iperf/src/opnsense/mvc/app/models/OPNsense/iperf/FakeInstance.xml b/benchmarks/iperf/src/opnsense/mvc/app/models/OPNsense/iperf/FakeInstance.xml index 939f4e21f0..f3254be98e 100644 --- a/benchmarks/iperf/src/opnsense/mvc/app/models/OPNsense/iperf/FakeInstance.xml +++ b/benchmarks/iperf/src/opnsense/mvc/app/models/OPNsense/iperf/FakeInstance.xml @@ -1,11 +1,11 @@ - //OPNsense/Iperf3 - Fake model for the API - will be never stored to config (only used for defaults, validation etc.). - - - lan - Y - N - - + //OPNsense/Iperf3 + Fake model for the API - will be never stored to config (only used for defaults, validation etc.). + + + lan + Y + N + + diff --git a/benchmarks/iperf/src/opnsense/mvc/app/views/OPNsense/iperf/index.volt b/benchmarks/iperf/src/opnsense/mvc/app/views/OPNsense/iperf/index.volt index 85ca1d448c..8cef80501f 100644 --- a/benchmarks/iperf/src/opnsense/mvc/app/views/OPNsense/iperf/index.volt +++ b/benchmarks/iperf/src/opnsense/mvc/app/views/OPNsense/iperf/index.volt @@ -51,47 +51,55 @@ function result_to_html(elements) { // only if test did already run if ('result' in element) { - var result = element.result, - start = result.start, - connection = start.connected[0], - intervals = result.intervals, - test_end = result.end, - cpu = test_end.cpu_utilization_percent; - // General - output += "

{{ lang._('General') }}

"; - output += ''; - output += table_tr_kv("{{ lang._('Time') }}", start.timestamp.time); - output += table_tr_kv("{{ lang._('Duration') }}", start.test_start.duration); - output += table_tr_kv("{{ lang._('Block Size') }}", start.test_start.blksize); - output += "
"; - // connection - output += "

{{ lang._('Connection') }}

"; - output += ''; - output += table_tr_kv("{{ lang._('Local Host') }}", connection.local_host); - output += table_tr_kv("{{ lang._('Local Port') }}", connection.local_port); - output += table_tr_kv("{{ lang._('Remote Host') }}", connection.remote_host); - output += table_tr_kv("{{ lang._('Remote Port') }}", connection.remote_port); - output += "
"; - // CPU Usage - output += "

{{ lang._('CPU Usage') }}

"; - output += ''; - output += table_tr_kv("{{ lang._('Host Total') }}", cpu.host_total.toFixed(2)); - output += table_tr_kv("{{ lang._('Host User') }}", cpu.host_user.toFixed(2)); - output += table_tr_kv("{{ lang._('Host System') }}", cpu.host_system.toFixed(2)); - output += table_tr_kv("{{ lang._('Remote Total') }}", cpu.remote_total.toFixed(2)); - output += table_tr_kv("{{ lang._('Remote User') }}", cpu.remote_user.toFixed(2)); - output += table_tr_kv("{{ lang._('Remote System') }}", cpu.remote_system.toFixed(2)); - output += "
"; - // performance data - output += "

{{ lang._('Performance Data') }}

"; - output += ''; - var fields = ['sum_sent', 'sum_received']; - output += table_tr_transpose("{{ lang._('Start') }}","start",fields, test_end); - output += table_tr_transpose("{{ lang._('End') }}","end",fields, test_end); - output += table_tr_transpose("{{ lang._('Seconds') }}","seconds",fields, test_end); - output += table_tr_transpose("{{ lang._('Bytes') }}","bytes",fields, test_end); - output += table_tr_transpose("{{ lang._('Bits Per Second') }}","bits_per_second",fields, test_end); - output += "
"; + var result = element.result; + if ('error' in result) { + // We can't assume that any other fields exist when there's an error + output += "

{{ lang._('Error') }}

"; + output += ''; + output += table_tr_kv("{{ lang._('Error message') }}", result.error); + output += "
"; + } else { + var start = result.start, + connection = start.connected[0], + intervals = result.intervals, + test_end = result.end, + cpu = test_end.cpu_utilization_percent; + // General + output += "

{{ lang._('General') }}

"; + output += ''; + output += table_tr_kv("{{ lang._('Time') }}", start.timestamp.time); + output += table_tr_kv("{{ lang._('Duration') }}", start.test_start.duration); + output += table_tr_kv("{{ lang._('Block Size') }}", start.test_start.blksize); + output += "
"; + // connection + output += "

{{ lang._('Connection') }}

"; + output += ''; + output += table_tr_kv("{{ lang._('Local Host') }}", connection.local_host); + output += table_tr_kv("{{ lang._('Local Port') }}", connection.local_port); + output += table_tr_kv("{{ lang._('Remote Host') }}", connection.remote_host); + output += table_tr_kv("{{ lang._('Remote Port') }}", connection.remote_port); + output += "
"; + // CPU Usage + output += "

{{ lang._('CPU Usage') }}

"; + output += ''; + output += table_tr_kv("{{ lang._('Host Total') }}", cpu.host_total.toFixed(2)); + output += table_tr_kv("{{ lang._('Host User') }}", cpu.host_user.toFixed(2)); + output += table_tr_kv("{{ lang._('Host System') }}", cpu.host_system.toFixed(2)); + output += table_tr_kv("{{ lang._('Remote Total') }}", cpu.remote_total.toFixed(2)); + output += table_tr_kv("{{ lang._('Remote User') }}", cpu.remote_user.toFixed(2)); + output += table_tr_kv("{{ lang._('Remote System') }}", cpu.remote_system.toFixed(2)); + output += "
"; + // performance data + output += "

{{ lang._('Performance Data') }}

"; + output += ''; + var fields = ['sum_sent', 'sum_received']; + output += table_tr_transpose("{{ lang._('Start') }}","start",fields, test_end); + output += table_tr_transpose("{{ lang._('End') }}","end",fields, test_end); + output += table_tr_transpose("{{ lang._('Seconds') }}","seconds",fields, test_end); + output += table_tr_transpose("{{ lang._('Bytes') }}","bytes",fields, test_end); + output += table_tr_transpose("{{ lang._('Bits Per Second') }}","bits_per_second",fields, test_end); + output += "
"; + } } } $('#resultcontainer').html(output); diff --git a/databases/redis/Makefile b/databases/redis/Makefile index 35faf0478c..95048dc98b 100644 --- a/databases/redis/Makefile +++ b/databases/redis/Makefile @@ -1,7 +1,8 @@ PLUGIN_NAME= redis PLUGIN_VERSION= 1.1 +PLUGIN_REVISION= 4 PLUGIN_COMMENT= Redis DB -PLUGIN_DEPENDS= redis +PLUGIN_DEPENDS= redis72 PLUGIN_MAINTAINER= franz.fabian.94@gmail.com .include "../../Mk/plugins.mk" diff --git a/databases/redis/pkg-descr b/databases/redis/pkg-descr index 3bc510cbca..399ab102cf 100644 --- a/databases/redis/pkg-descr +++ b/databases/redis/pkg-descr @@ -23,6 +23,7 @@ Plugin Changelog 1.1 * Add a button to reset all databases (contributed by Michael Muenz) +* Fix service widget behaviour (contributed by sevengiants) 1.0 @@ -31,6 +32,3 @@ Plugin Changelog * Allow password protection * Connection limits * Performance monitoring of slow connections - - -WWW: http://redis.io/ diff --git a/databases/redis/src/opnsense/mvc/app/models/OPNsense/Redis/Redis.xml b/databases/redis/src/opnsense/mvc/app/models/OPNsense/Redis/Redis.xml index 0750ed1d80..df895e9862 100644 --- a/databases/redis/src/opnsense/mvc/app/models/OPNsense/Redis/Redis.xml +++ b/databases/redis/src/opnsense/mvc/app/models/OPNsense/Redis/Redis.xml @@ -1,108 +1,108 @@ - //OPNsense/redis - Redis DB - - - - 0 - Y - - - N - Y - - - 1 - Y - - - 1 - 65536 - N - 6379 - This must be a valid port number. - - - Y - warning - - Debug - Verbose - Notice - Warning - - - - 0 - Y - - - Y - LOCAL0 - - USER - LOCAL0 - LOCAL1 - LOCAL2 - LOCAL3 - LOCAL4 - LOCAL5 - LOCAL6 - LOCAL7 - - - - 0 - Y - 16 - - - - - N - - + //OPNsense/redis + Redis DB + + + + 0 + Y + + + N + Y + + + 1 + Y + + + 1 + 65536 + N + 6379 + This must be a valid port number. + + + Y + warning + + Debug + Verbose + Notice + Warning + + + + 0 + Y + + + Y + LOCAL0 + + USER + LOCAL0 + LOCAL1 + LOCAL2 + LOCAL3 + LOCAL4 + LOCAL5 + LOCAL6 + LOCAL7 + + + + 0 + Y + 16 + + + + + N + + - - - - 0 - N - 10000 - - - 0 - N - - - Y - noeviction - - noeviction - volatile-ttl - allkeys-random - volatile-random - allkeys-lru - volatile-lru - - - - 0 - N - 5 - - - - - 0 - N - 10000 - - - 0 - N - 128 - - - + + + + 0 + N + 10000 + + + 0 + N + + + Y + noeviction + + noeviction + volatile-ttl + allkeys-random + volatile-random + allkeys-lru + volatile-lru + + + + 0 + N + 5 + + + + + 0 + N + 10000 + + + 0 + N + 128 + + + diff --git a/databases/redis/src/opnsense/mvc/app/views/OPNsense/Redis/index.volt b/databases/redis/src/opnsense/mvc/app/views/OPNsense/Redis/index.volt index bf0bc4d96f..e031f94867 100644 --- a/databases/redis/src/opnsense/mvc/app/views/OPNsense/Redis/index.volt +++ b/databases/redis/src/opnsense/mvc/app/views/OPNsense/Redis/index.volt @@ -34,12 +34,11 @@ $( document ).ready(function() { mapDataToFormUI(data_get_map).done(function(){ formatTokenizersUI(); $('.selectpicker').selectpicker('refresh'); - // request service status on load and update status box - ajaxCall(url="/api/redis/service/status", sendData={}, callback=function(data,status) { - updateServiceStatusUI(data['status']); - }); }); + // request service status on load and update status box + updateServiceControlUI('redis'); + // update history on tab state and implement navigation if(window.location.hash != "") { $('a[href="' + window.location.hash + '"]').click() @@ -73,9 +72,7 @@ $( document ).ready(function() { draggable: true }); } else { - ajaxCall(url="/api/redis/service/status", sendData={}, callback=function(data,status) { - updateServiceStatusUI(data['status']); - }); + updateServiceControlUI('redis'); } }); }); diff --git a/databases/redis/src/opnsense/service/conf/actions.d/actions_redis.conf b/databases/redis/src/opnsense/service/conf/actions.d/actions_redis.conf index f9183fec73..6c388a70f5 100644 --- a/databases/redis/src/opnsense/service/conf/actions.d/actions_redis.conf +++ b/databases/redis/src/opnsense/service/conf/actions.d/actions_redis.conf @@ -1,5 +1,5 @@ [start] -command:/usr/local/opnsense/scripts/redis/setup.sh;/usr/local/etc/rc.d/redis start +command:/usr/local/etc/rc.d/redis start parameters: type:script message:starting redis @@ -11,19 +11,19 @@ type:script message:stopping redis [restart] -command:/usr/local/opnsense/scripts/redis/setup.sh;/usr/local/etc/rc.d/redis restart +command:/usr/local/etc/rc.d/redis restart parameters: type:script message:restarting redis [status] -command:/usr/local/etc/rc.d/redis status;exit 0 +command:/usr/local/etc/rc.d/redis status; exit 0 parameters: type:script_output message:request redis status [resetdb] -command:/usr/local/etc/rc.d/redis stop;rm -rf /var/db/redis +command:/usr/local/etc/rc.d/redis stop; rm -rf /var/db/redis parameters: type:script message:remove all databases diff --git a/databases/redis/src/opnsense/service/templates/OPNsense/Redis/redis b/databases/redis/src/opnsense/service/templates/OPNsense/Redis/redis index 57fe4d2470..2ec3e47a26 100644 --- a/databases/redis/src/opnsense/service/templates/OPNsense/Redis/redis +++ b/databases/redis/src/opnsense/service/templates/OPNsense/Redis/redis @@ -1,5 +1,5 @@ {% if helpers.exists('OPNsense.redis.general.enabled') and OPNsense.redis.general.enabled == '1' %} -redis_var_script="/usr/local/opnsense/scripts/redis/setup.sh" +redis_setup="/usr/local/opnsense/scripts/redis/setup.sh" redis_enable="YES" {% else %} redis_enable="NO" diff --git a/databases/redis/src/opnsense/service/templates/OPNsense/Redis/redis.conf b/databases/redis/src/opnsense/service/templates/OPNsense/Redis/redis.conf index 6deeeda1ae..8882751ea7 100644 --- a/databases/redis/src/opnsense/service/templates/OPNsense/Redis/redis.conf +++ b/databases/redis/src/opnsense/service/templates/OPNsense/Redis/redis.conf @@ -54,12 +54,8 @@ {% endif %} {% if helpers.exists('virtualip') %} {% for intf_item in helpers.toList('virtualip.vip') %} -{% if intf_item.interface == listen_interface and intf_item.type == 'single' %} -{% if intf_item.subnet.find(':') > -1 %} -{% do listen_ip.append(interface_ip) %} -{% else %} -{% do listen_ip.append(interface_ip) %} -{% endif %} +{% if intf_item.interface == listen_interface and intf_item.mode in ['carp', 'ipalias'] %} +{% do listen_ip.append(intf_item.subnet) %} {% endif %} {% endfor %} {% endif %} diff --git a/devel/debug/Makefile b/devel/debug/Makefile index 1f821dd593..e60a1f1f5d 100644 --- a/devel/debug/Makefile +++ b/devel/debug/Makefile @@ -1,11 +1,14 @@ PLUGIN_NAME= debug -PLUGIN_VERSION= 1.3 +PLUGIN_VERSION= 1.7 PLUGIN_COMMENT= Debugging Tools PLUGIN_DEPENDS= php${PLUGIN_PHP}-pear-PHP_CodeSniffer \ php${PLUGIN_PHP}-pecl-xdebug \ - phpunit7-php${PLUGIN_PHP} \ + phpunit9-php${PLUGIN_PHP} \ py${PLUGIN_PYTHON}-pycodestyle \ - p5-File-Slurp vim-console git + py${PLUGIN_PYTHON}-pytest \ + py${PLUGIN_PYTHON}-scapy \ + p5-File-Slurp git jq PLUGIN_MAINTAINER= franco@opnsense.org +PLUGIN_TIER= 2 .include "../../Mk/plugins.mk" diff --git a/devel/debug/src/bin/qyua b/devel/debug/src/bin/qyua new file mode 100755 index 0000000000..5b3426878a --- /dev/null +++ b/devel/debug/src/bin/qyua @@ -0,0 +1,183 @@ +#!/bin/sh + +# Copyright (c) 2024 Franco Fichtner +# +# 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. + +set -e + +COMPONENT=sys/netpfil/pf + +TESTDIR=/usr/src/tests/${COMPONENT} +DESTDIR=/usr/tests/${COMPONENT} + +if [ "$(id -u)" != "0" ]; then + echo "Must be root." >&2 + exit 1 +fi + +DO_ALL= +DO_BOOTSTRAP= +DO_DEBUG= +DO_LIST= +DO_INJECT=yes + +while getopts abdlrV OPT; do + case ${OPT} in + a) + DO_ALL="-a" + ;; + b) + DO_BOOTSTRAP="-b" + ;; + d) + DO_DEBUG="-d" + ;; + l) + DO_LIST="-l" + ;; + r) + # referenece testing via /usr/tests + unset DO_INJECT + ;; + V) + DO_VERBOSE="-V" + ;; + *) + echo "Usage: man ${0##*/}" >&2 + exit 1 + ;; + esac +done + +shift $((OPTIND - 1)) + +if [ -n "${DO_VERBOSE}" ]; then + set -x +fi + +if [ -n "${DO_BOOTSTRAP}" ]; then + opnsense-update -Q + + # XXX needed for inject mode (default) but not sure if the best way + #opnsense-code src +fi + +if [ -n "${DO_INJECT}" -a ! -d "${TESTDIR}" ]; then + echo "Source directory not found: ${TESTDIR}" >&2 + exit 1 +fi + +if [ ! -d "${DESTDIR}" ]; then + echo "Target directory not found: ${DESTDIR}" >&2 + exit 1 +fi + +# clear from previous run +rm -rf ${DESTDIR}/_* + +list() +{ + if [ -z "${DO_INJECT}" ]; then + cat ${DESTDIR}/Kyuafile | tr '"' ' ' | while read PRE NAME MORE; do + echo ${NAME} + done + else + for TEST in $(find -s ${TESTDIR} -name "*.sh"); do + TEST=$(basename ${TEST}) + echo ${TEST%.sh} + done + fi +} + +if [ -n "${DO_LIST}" ]; then + list + exit 0 +fi + +TESTS=${@} +if [ -n "${DO_ALL}" ]; then + TESTS=$(list) +fi + +if [ -z "${TESTS}" ]; then + echo "Nothing to do." + exit 0 +fi + +# XXX kldload required things now as kyua is powerless (but complains) + +# set up a shadow config +cat > ${DESTDIR}/_Kyuafile << EOF +-- Automatically generated by bsd.test.mk. + +syntax(2) + +test_suite("FreeBSD") + +EOF + +FINAL= + +for TEST in ${TESTS}; do + if [ -n "${DO_INJECT}" ]; then + CASE= + + if [ -n "${DO_DEBUG}" ]; then + if [ -n "${TEST%%*:*}" ]; then + echo "No test case given in debug mode, use '${TEST}:case'" >&2 + exit 1 + fi + + CASE=:${TEST##*:} + TEST=${TEST%%:*} + fi + + if [ ! -f ${TESTDIR}/${TEST}.sh ]; then + echo "Source file not found: ${TESTDIR}/${TEST}.sh" >&2 + exit 1 + fi + + cat >> ${DESTDIR}/_Kyuafile << EOF +atf_test_program{name="_${TEST}", is_exclusive=true} +EOF + + cat > ${DESTDIR}/_${TEST} << EOF +#! /usr/libexec/atf-sh + +$(cat ${TESTDIR}/${TEST}.sh) +EOF + + chmod 555 ${DESTDIR}/_${TEST} + fi + + FINAL="${FINAL} ${DO_INJECT+_}${TEST}${CASE}" +done + +if [ -z "${DO_DEBUG}" ]; then + exec kyua test -k ${DESTDIR}/${DO_INJECT+_}Kyuafile ${FINAL} +else + for TEST in ${FINAL}; do + # only support first one + exec kyua debug -k ${DESTDIR}/${DO_INJECT+_}Kyuafile ${TEST} + done +fi diff --git a/devel/debug/src/etc/php/ext-20-xdebug-settings.ini b/devel/debug/src/etc/php/ext-20-xdebug-settings.ini index 74cba733b4..7821833984 100644 --- a/devel/debug/src/etc/php/ext-20-xdebug-settings.ini +++ b/devel/debug/src/etc/php/ext-20-xdebug-settings.ini @@ -1,2 +1,4 @@ -xdebug.profiler_enable_trigger = 1 +xdebug.mode = profile; +xdebug.output_dir = /tmp xdebug.profiler_output_name = cachegrind.out.%t.%p +xdebug.start_with_request = trigger; diff --git a/devel/debug/src/man/man1/qyua.1 b/devel/debug/src/man/man1/qyua.1 new file mode 100644 index 0000000000..ad9a6e3897 --- /dev/null +++ b/devel/debug/src/man/man1/qyua.1 @@ -0,0 +1,99 @@ +.\" +.\" Copyright (c) 2024 Franco Fichtner +.\" +.\" 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. +.\" +.Dd November 22, 2024 +.Dt QYUA 1 +.Os +.Sh NAME +.Nm qyua +.Nd Quick kyua wrapper +.Sh SYNOPSIS +.Nm +.Op Fl abdlrV +.Op Ar test ... +.Sh DESCRIPTION +The +.Nm +utility is a thin wrapper around +.Xr kyua 1 +in order to run test cases directly from the source tree at +.Pa /usr/src . +In order to do this, +the +.Pa /usr/tests +directory is required, +which is provided by the +.Li tests +set of +.Xr opnsense-update 8 . +The source tree can be installed using +.Xr opnsense-code 8 . +Internally, +.Xr kyua-test 1 +is used to run the given tests. +.Pp +Please note that +.Nm +is only concerned with a single test component, namely +.Pa sys/netpfil/pf . +The focus is the ability to assist with writing tests where +they are being committed/published in the first place without +the need to compile/install anything. +.Pp +The options are as follows: +.Bl -tag -width ".Fl a" -offset indent +.It Fl a +Select all available tests to run. +.It Fl b +Bootstrap mode installs the matching tests set. +.It Fl d +In debug mode, a single test case is selected from the specified +test file using +.Sq Ar file:name . +Internally, this invokes +.Xr kyua-debug 1 +instead. +.It Fl l +List all the tests that can be run. +.It Fl r +In reference mode, run the tests available in +.Pa /usr/tests . +This works without +.Pa /usr/src +being available, but is not the default mode. +.It Fl V +Set debug mode for shell script output. +.El +.Sh EXIT STATUS +.Ex -std +.Sh SEE ALSO +.Xr kyua 1 , +.Xr kyua-debug 1 , +.Xr kyua-test 1 , +.Xr opnsense-code 8 , +.Xr opnsense-update 8 +.Sh AUTHORS +.An Franco Fichtner Aq Mt franco@opnsense.org diff --git a/devel/grid_example/Makefile b/devel/grid_example/Makefile index dfc4cf776d..c274386418 100644 --- a/devel/grid_example/Makefile +++ b/devel/grid_example/Makefile @@ -1,5 +1,6 @@ PLUGIN_NAME= grid_example -PLUGIN_VERSION= 1.0 +PLUGIN_VERSION= 1.1 +PLUGIN_REVISION= 1 PLUGIN_COMMENT= A sample framework application PLUGIN_MAINTAINER= ad@opnsense.org diff --git a/devel/grid_example/src/opnsense/mvc/app/controllers/OPNsense/GridExample/Api/ServiceController.php b/devel/grid_example/src/opnsense/mvc/app/controllers/OPNsense/GridExample/Api/ServiceController.php new file mode 100644 index 0000000000..00ae072e08 --- /dev/null +++ b/devel/grid_example/src/opnsense/mvc/app/controllers/OPNsense/GridExample/Api/ServiceController.php @@ -0,0 +1,49 @@ + "ok"]; + } +} diff --git a/devel/grid_example/src/opnsense/mvc/app/controllers/OPNsense/GridExample/Api/SettingsController.php b/devel/grid_example/src/opnsense/mvc/app/controllers/OPNsense/GridExample/Api/SettingsController.php index 3cbe4725d4..a7f63c2ddf 100644 --- a/devel/grid_example/src/opnsense/mvc/app/controllers/OPNsense/GridExample/Api/SettingsController.php +++ b/devel/grid_example/src/opnsense/mvc/app/controllers/OPNsense/GridExample/Api/SettingsController.php @@ -1,7 +1,7 @@ searchBase("addresses.address", array('enabled', 'email'), "email"); + return $this->searchBase("addresses.address", null, "email"); } public function setItemAction($uuid) diff --git a/devel/grid_example/src/opnsense/mvc/app/controllers/OPNsense/GridExample/IndexController.php b/devel/grid_example/src/opnsense/mvc/app/controllers/OPNsense/GridExample/IndexController.php index e3e86e6474..79d674d0b3 100644 --- a/devel/grid_example/src/opnsense/mvc/app/controllers/OPNsense/GridExample/IndexController.php +++ b/devel/grid_example/src/opnsense/mvc/app/controllers/OPNsense/GridExample/IndexController.php @@ -1,7 +1,7 @@ view->pick('OPNsense/GridExample/index'); $this->view->formDialogAddress = $this->getForm("dialogAddress"); + // convert dialog for grid table + $this->view->formGridAddress = $this->getFormGrid("dialogAddress"); } } diff --git a/devel/grid_example/src/opnsense/mvc/app/controllers/OPNsense/GridExample/forms/dialogAddress.xml b/devel/grid_example/src/opnsense/mvc/app/controllers/OPNsense/GridExample/forms/dialogAddress.xml index 9a65a91a77..0d0152e0f0 100644 --- a/devel/grid_example/src/opnsense/mvc/app/controllers/OPNsense/GridExample/forms/dialogAddress.xml +++ b/devel/grid_example/src/opnsense/mvc/app/controllers/OPNsense/GridExample/forms/dialogAddress.xml @@ -1,13 +1,36 @@
address.enabled - + checkbox - Enable this address + Enable this address. + + + 6em + boolean + rowtoggle + address.email text + Enter the email address. + + + address.schedule + + dropdown + Select your preferred schedule. + + + address.description + + text + Enter an optional description. + + + false +
diff --git a/devel/grid_example/src/opnsense/mvc/app/models/OPNsense/GridExample/GridExample.xml b/devel/grid_example/src/opnsense/mvc/app/models/OPNsense/GridExample/GridExample.xml index 7208836b36..255d6689fb 100644 --- a/devel/grid_example/src/opnsense/mvc/app/models/OPNsense/GridExample/GridExample.xml +++ b/devel/grid_example/src/opnsense/mvc/app/models/OPNsense/GridExample/GridExample.xml @@ -1,18 +1,26 @@ //OPNsense/GridExample - - the OPNsense "GridExample" application - + OPNsense "GridExample" application
- 1 + 1 Y + + wly + Y + + Daily + Weekly + Monthly + + Y +
diff --git a/devel/grid_example/src/opnsense/mvc/app/views/OPNsense/GridExample/index.volt b/devel/grid_example/src/opnsense/mvc/app/views/OPNsense/GridExample/index.volt index 0061bcb4f4..4f0ed8fd52 100644 --- a/devel/grid_example/src/opnsense/mvc/app/views/OPNsense/GridExample/index.volt +++ b/devel/grid_example/src/opnsense/mvc/app/views/OPNsense/GridExample/index.volt @@ -1,5 +1,5 @@ {# - # Copyright (c) 2019 Deciso B.V. + # Copyright (c) 2019-2025 Deciso B.V. # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, @@ -25,43 +25,29 @@ #} - - - - - - - - - - - - - - - - - - -
{{ lang._('ID') }}{{ lang._('Enabled') }}{{ lang._('Email') }}{{ lang._('Commands') }}
- - -
- - -{{ partial("layout_partials/base_dialog",['fields':formDialogAddress,'id':'DialogAddress','label':lang._('Edit address')])}} +
+ + {{ partial('layout_partials/base_bootgrid_table', formGridAddress) }} +
+ +{{ partial('layout_partials/base_apply_button', {'data_endpoint': '/api/gridexample/service/reconfigure'}) }} + +{{ partial("layout_partials/base_dialog",['fields':formDialogAddress,'id':formGridAddress['edit_dialog_id'],'label':lang._('Edit address')])}} diff --git a/devel/helloworld/Makefile b/devel/helloworld/Makefile index de639c35d5..22c2e705cf 100644 --- a/devel/helloworld/Makefile +++ b/devel/helloworld/Makefile @@ -1,5 +1,6 @@ PLUGIN_NAME= helloworld -PLUGIN_VERSION= 1.3 +PLUGIN_VERSION= 1.4 +PLUGIN_REVISION= 2 PLUGIN_COMMENT= A sample framework application PLUGIN_MAINTAINER= ad@opnsense.org diff --git a/devel/helloworld/src/opnsense/mvc/app/controllers/OPNsense/HelloWorld/Api/ServiceController.php b/devel/helloworld/src/opnsense/mvc/app/controllers/OPNsense/HelloWorld/Api/ServiceController.php index 6c7073da19..5b068bae43 100644 --- a/devel/helloworld/src/opnsense/mvc/app/controllers/OPNsense/HelloWorld/Api/ServiceController.php +++ b/devel/helloworld/src/opnsense/mvc/app/controllers/OPNsense/HelloWorld/Api/ServiceController.php @@ -46,13 +46,9 @@ public function reloadAction() { $status = "failed"; if ($this->request->isPost()) { - $backend = new Backend(); - $bckresult = trim($backend->configdRun('template reload OPNsense/HelloWorld')); - if ($bckresult == "OK") { - $status = "ok"; - } + $status = strtolower(trim((new Backend())->configdRun('template reload OPNsense/HelloWorld'))); } - return array("status" => $status); + return ["status" => $status]; } /** @@ -61,13 +57,12 @@ public function reloadAction() public function testAction() { if ($this->request->isPost()) { - $backend = new Backend(); - $bckresult = json_decode(trim($backend->configdRun("helloworld test")), true); + $bckresult = json_decode(trim((new Backend())->configdRun("helloworld test")), true); if ($bckresult !== null) { // only return valid json type responses return $bckresult; } } - return array("message" => "unable to run config action"); + return ["message" => "unable to run config action"]; } } diff --git a/devel/helloworld/src/opnsense/mvc/app/controllers/OPNsense/HelloWorld/Api/SettingsController.php b/devel/helloworld/src/opnsense/mvc/app/controllers/OPNsense/HelloWorld/Api/SettingsController.php index 750e1b986e..2e4fad0656 100644 --- a/devel/helloworld/src/opnsense/mvc/app/controllers/OPNsense/HelloWorld/Api/SettingsController.php +++ b/devel/helloworld/src/opnsense/mvc/app/controllers/OPNsense/HelloWorld/Api/SettingsController.php @@ -1,92 +1,49 @@ request->isGet()) { - $mdlHelloWorld = new HelloWorld(); - $result['helloworld'] = $mdlHelloWorld->getNodes(); - } - return $result; - } + protected static $internalModelClass = 'OPNsense\HelloWorld\HelloWorld'; + protected static $internalModelName = 'helloworld'; - /** - * update HelloWorld settings - * @return array status - * @throws \OPNsense\Base\ModelException - * @throws \ReflectionException - */ - public function setAction() + public function getAction() { - $result = array("result" => "failed"); - if ($this->request->isPost()) { - // load model and update with provided data - $mdlHelloWorld = new HelloWorld(); - $mdlHelloWorld->setNodes($this->request->getPost("helloworld")); - - // perform validation - $valMsgs = $mdlHelloWorld->performValidation(); - foreach ($valMsgs as $field => $msg) { - if (!array_key_exists("validations", $result)) { - $result["validations"] = array(); - } - $result["validations"]["helloworld." . $msg->getField()] = $msg->getMessage(); - } + $data = parent::getAction(); + $data[self::$internalModelName]['general']['%ToEmail'] = gettext('Enter recipient here'); - // serialize model to config and save - if ($valMsgs->count() == 0) { - $mdlHelloWorld->serializeToConfig(); - Config::getInstance()->save(); - $result["result"] = "saved"; - } - } - return $result; + return $data; } } diff --git a/devel/helloworld/src/opnsense/mvc/app/controllers/OPNsense/HelloWorld/Api/SimplifiedSettingsController.php b/devel/helloworld/src/opnsense/mvc/app/controllers/OPNsense/HelloWorld/Api/SimplifiedSettingsController.php deleted file mode 100644 index 72167c2153..0000000000 --- a/devel/helloworld/src/opnsense/mvc/app/controllers/OPNsense/HelloWorld/Api/SimplifiedSettingsController.php +++ /dev/null @@ -1,43 +0,0 @@ - //OPNsense/helloworld - - the OPNsense "Hello World" application - + OPNsense "Hello World" application - 1 + 1 Y Y - sample@example.com + sample@example.com Y Y - please specify a valid email address + Please specify a valid email address. Y diff --git a/devel/helloworld/src/opnsense/mvc/app/views/OPNsense/HelloWorld/index.volt b/devel/helloworld/src/opnsense/mvc/app/views/OPNsense/HelloWorld/index.volt index 33255d209b..d09d70e1db 100644 --- a/devel/helloworld/src/opnsense/mvc/app/views/OPNsense/HelloWorld/index.volt +++ b/devel/helloworld/src/opnsense/mvc/app/views/OPNsense/HelloWorld/index.volt @@ -28,14 +28,13 @@ POSSIBILITY OF SUCH DAMAGE. @@ -63,6 +60,6 @@ POSSIBILITY OF SUCH DAMAGE.
- - + +
diff --git a/devel/helloworld/src/opnsense/scripts/OPNsense/HelloWorld/testConnection.py b/devel/helloworld/src/opnsense/scripts/helloworld/testConnection.py similarity index 100% rename from devel/helloworld/src/opnsense/scripts/OPNsense/HelloWorld/testConnection.py rename to devel/helloworld/src/opnsense/scripts/helloworld/testConnection.py diff --git a/devel/helloworld/src/opnsense/service/conf/actions.d/actions_helloworld.conf b/devel/helloworld/src/opnsense/service/conf/actions.d/actions_helloworld.conf index c5c5217933..1bb2afb595 100644 --- a/devel/helloworld/src/opnsense/service/conf/actions.d/actions_helloworld.conf +++ b/devel/helloworld/src/opnsense/service/conf/actions.d/actions_helloworld.conf @@ -1,5 +1,5 @@ [test] -command:/usr/local/opnsense/scripts/OPNsense/HelloWorld/testConnection.py +command:/usr/local/opnsense/scripts/helloworld/testConnection.py parameters: type:script_output message:hello world module test diff --git a/devel/helloworld/src/opnsense/service/templates/OPNsense/HelloWorld/helloworld.conf b/devel/helloworld/src/opnsense/service/templates/OPNsense/HelloWorld/helloworld.conf index 7ccefce079..28199b1465 100644 --- a/devel/helloworld/src/opnsense/service/templates/OPNsense/HelloWorld/helloworld.conf +++ b/devel/helloworld/src/opnsense/service/templates/OPNsense/HelloWorld/helloworld.conf @@ -1,4 +1,4 @@ -{% if helpers.exists('OPNsense.helloworld.general') and OPNsense.helloworld.general.Enabled|default("0") == "1" %} +{% if not helpers.empty('OPNsense.helloworld.general.Enabled') %} [general] SMTPHost={{ OPNsense.helloworld.general.SMTPHost|default("") }} FromEmail={{ OPNsense.helloworld.general.FromEmail|default("") }} diff --git a/dns/bind/Makefile b/dns/bind/Makefile index 3b6ed1adc2..4d98f7a4b3 100644 --- a/dns/bind/Makefile +++ b/dns/bind/Makefile @@ -1,7 +1,8 @@ PLUGIN_NAME= bind -PLUGIN_VERSION= 1.12 +PLUGIN_VERSION= 1.34 +PLUGIN_REVISION= 2 PLUGIN_COMMENT= BIND domain name service -PLUGIN_DEPENDS= bind914 +PLUGIN_DEPENDS= bind920 PLUGIN_MAINTAINER= m.muenz@gmail.com .include "../../Mk/plugins.mk" diff --git a/dns/bind/pkg-descr b/dns/bind/pkg-descr index bb0f46fb27..d3cbd8afa0 100644 --- a/dns/bind/pkg-descr +++ b/dns/bind/pkg-descr @@ -4,10 +4,120 @@ one computer can find another computer on the basis of its name. The BIND software distribution contains all of the software necessary for asking and answering name service questions. +WWW: https://www.isc.org Plugin Changelog ================ +1.34 + +* Add custom configuration include directory /usr/local/etc/namedb/named.conf.d (contributed by Nicholas Card) +* Add forward zones +* Fix primary zones grid and command column (contributed by benyamin-codez) + +1.33 + +* Add option to allow the rndc-key for zone transfers (contributed by Naomi Rennie-Waldock) +* Switch to Bind 9.20 + +1.32 + +* Fix handling of multiple ACLs in allow-query/allow-transfer (contributed by Nathan Rennie-Waldock) +* Fix multiple select on Recursion field and resulting multiple ACLs (contributed by Jordan Stacy) +* Add SSHFP record type (contributed by doktornotor) + +1.31 + +* Do not add the update-policy if the zone type is secondary (contributed by Brendan Bank) +* Adjust severity log levels (contributed by kulikov-a) + +1.30 + +* Add ability for RNDC key updates from other nameservers (contributed by Joachim Friberg) + +1.29 + +* Migrate General Log to Syslog +* Add Check & Preview button to Primary Zones grid + +1.28 + +* Add break-dnssec toggle when using filter-aaaa on IPv4/IPv6 clients (contributed by doktornotor) +* Fix template error if there is no 'records' in config at all (contributed by kulikov-a) + +1.27 + +* Add DNAME support (contributed by Simon Fischer) + +1.26 + +* Allow multiple ACLs to be selected for Transfers/Queries (contributed by Robbert Rijkse) +* Rename Master/Slave to Primary/Secondary (contributed by Robbert Rijkse) +* Add necessary hooks to allow the plugin to be used as a standalone core DNS server +* Changed default listening addresses to 0.0.0.0/:: for new users and interally translate these to "any" +* Prevent using a port being used by another DNS service + +1.25 + +* Ensure you can only add one ACL with the same name (contributed by Robbert Rijkse) +* Cleanup/Fix the Master/Slave domain dialogs (contributed by Robbert Rijkse) +* Revamp the logging page with proper columns (contributed by Robbert Rijkse) +* Add RNDC key configuration (contributed by Robbert Rijkse) +* Add PR record type (contributed by Robbert Rijkse) +* Update base to BIND 9.18 + +1.24 + +* Separate tables for master and slave zones in UI (contributed by Patrick M. Hausen and Manuel Faux) + +1.23 + +* Avoid errors with repeated primary servers and keys (contributed by Michael Newton) + +1.22 + +* Fix DNS Blacklist download + +1.21 + +* Add support for filter AAAA in DNS responses when A is present (contributed by Zane Chua) + +1.20 + +* Allow signed zone transfers (contributed by Michael Newton) + +1.19 + +* Add support for "Query Source [IP|IPv6]" options. + +1.18 + +* Allow specifying multiple masters for slave zones + +1.17 + +* Make "Allow Transfer" and "Allow Query" configuration available to slave zones (contributed by Ang Iongchun) +* Add "Allow Transfer" to General page for default/fallback (contributed by Ang Iongchun) + +1.16 + +* Fix slave zone templating + +1.15 + +* Add support for "Transfer Source [IP|IPv6]" options + +1.14 + +* Reject built-in ACL names +* Fix truncated checkboxes +* Add button to delete selected items +* Relax ACL name mask (allow underscores and hyphens) + +1.13 + +* Update BIND to 9.16 + 1.12 * Add checkbox to disable prefetch option @@ -72,6 +182,3 @@ Plugin Changelog 1.0 * Initial release - - -WWW: https://www.isc.org diff --git a/dns/bind/src/etc/inc/plugins.inc.d/bind.inc b/dns/bind/src/etc/inc/plugins.inc.d/bind.inc index 495764bb52..e1d8b1b2cf 100644 --- a/dns/bind/src/etc/inc/plugins.inc.d/bind.inc +++ b/dns/bind/src/etc/inc/plugins.inc.d/bind.inc @@ -1,30 +1,31 @@ - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, - INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY - AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, - OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - POSSIBILITY OF SUCH DAMAGE. -*/ + * Copyright (C) 2018 Michael Muenz + * Copyright (C) 2023 Franco Fichtner + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ function bind_enabled() { @@ -32,33 +33,82 @@ function bind_enabled() return (string)$model->enabled == '1'; } +function bind_configure() +{ + return [ + 'dns' => ['bind_configure_do'], + ]; +} + function bind_services() { - $services = array(); + $services = []; if (!bind_enabled()) { return $services; } - $services[] = array( + $model = new \OPNsense\Bind\General(); + + /* DNS service is eligable for core use when both 0.0.0.0 and :: are set */ + $any4 = false; + $any6 = false; + + foreach (explode(',', (string)$model->listenv4) as $addr) { + $any4 |= $addr === '0.0.0.0'; + } + + foreach (explode(',', (string)$model->listenv6) as $addr) { + $any6 |= $addr === '::'; + } + + $services[] = [ + /* the port may still be something other than 53, but it's safe to register a conflict for it */ + 'dns_ports' => ($any4 && $any6 ? [(string)$model->port] : []), 'description' => gettext('BIND Daemon'), - 'configd' => array( - 'restart' => array('bind restart'), - 'start' => array('bind start'), - 'stop' => array('bind stop'), - ), + 'configd' => [ + 'restart' => ['bind restart'], + 'start' => ['bind start'], + 'stop' => ['bind stop'], + ], + 'pidfile' => '/var/run/named/pid', 'name' => 'named', - 'pidfile' => '/var/run/named/pid' - ); + ]; return $services; } function bind_xmlrpc_sync() { - $result = array(); + $result = []; + $result['id'] = 'bind'; $result['section'] = 'OPNsense.bind'; $result['description'] = gettext('BIND domain name service'); - return array($result); + $result['services'] = ['named']; + + return [$result]; +} + +function bind_configure_do($verbose) +{ + service_log('Starting BIND...', $verbose); + + configd_run('template reload OPNsense/Bind'); + configd_run('bind restart'); + + service_log("done.\n", $verbose); +} + +/** + * register syslog facilities + * @return array + */ +function bind_syslog() +{ + $syslogconf = []; + + $syslogconf['bind'] = ['facility' => ['named']]; + + return $syslogconf; } diff --git a/dns/bind/src/etc/namedb/named.conf.d/00-README.conf b/dns/bind/src/etc/namedb/named.conf.d/00-README.conf new file mode 100644 index 0000000000..fac2f0d6bc --- /dev/null +++ b/dns/bind/src/etc/namedb/named.conf.d/00-README.conf @@ -0,0 +1,7 @@ +# Custom BIND Configuration Directory +# +# Place your custom BIND directives in .conf files in this directory +# Files are included in alphabetical order +# +# Examples: +# server 192.168.1.100 { edns no; }; diff --git a/dns/bind/src/opnsense/mvc/app/controllers/OPNsense/Bind/Api/AclController.php b/dns/bind/src/opnsense/mvc/app/controllers/OPNsense/Bind/Api/AclController.php index e092f1f5ba..b7a213ef0e 100644 --- a/dns/bind/src/opnsense/mvc/app/controllers/OPNsense/Bind/Api/AclController.php +++ b/dns/bind/src/opnsense/mvc/app/controllers/OPNsense/Bind/Api/AclController.php @@ -1,31 +1,29 @@ +/* + * Copyright (C) 2018 Michael Muenz + * All rights reserved. * - * All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: * - * 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. * - * 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. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. */ namespace OPNsense\Bind\Api; @@ -39,25 +37,29 @@ class AclController extends ApiMutableModelControllerBase public function searchAclAction() { - return $this->searchBase('acls.acl', array("enabled", "name", "networks")); + return $this->searchBase('acls.acl', ['enabled', 'name', 'networks']); } + public function getAclAction($uuid = null) { - $this->sessionClose(); return $this->getBase('acl', 'acls.acl', $uuid); } + public function addAclAction() { return $this->addBase('acl', 'acls.acl'); } + public function delAclAction($uuid) { return $this->delBase('acls.acl', $uuid); } + public function setAclAction($uuid) { return $this->setBase('acl', 'acls.acl', $uuid); } + public function toggleAclAction($uuid) { return $this->toggleBase('acls.acl', $uuid); diff --git a/dns/bind/src/opnsense/mvc/app/controllers/OPNsense/Bind/Api/DomainController.php b/dns/bind/src/opnsense/mvc/app/controllers/OPNsense/Bind/Api/DomainController.php index df278dc53f..87c71db3f8 100644 --- a/dns/bind/src/opnsense/mvc/app/controllers/OPNsense/Bind/Api/DomainController.php +++ b/dns/bind/src/opnsense/mvc/app/controllers/OPNsense/Bind/Api/DomainController.php @@ -1,32 +1,30 @@ - * Copyright (C) 2019 Deciso B.V. +/* + * Copyright (C) 2019 Michael Muenz + * Copyright (C) 2019 Deciso B.V. + * All rights reserved. * - * All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: * - * 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. * - * 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. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. */ namespace OPNsense\Bind\Api; @@ -39,23 +37,72 @@ class DomainController extends ApiMutableModelControllerBase protected static $internalModelName = 'domain'; protected static $internalModelClass = '\OPNsense\Bind\Domain'; - public function searchDomainAction() + /* XXX backwards-compatibility for 22.7 and below */ + public function searchMasterDomainAction() + { + return $this->searchPrimaryDomainAction(); + } + + /* XXX backwards-compatibility for 22.7 and below */ + public function searchSlaveDomainAction() + { + return $this->searchSecondaryDomainAction(); + } + + public function searchPrimaryDomainAction() + { + return $this->searchBase( + 'domains.domain', + [ 'enabled', 'type', 'domainname', 'ttl', 'refresh', 'retry', 'expire', 'negative' ], + 'domainname', + function ($record) { + return $record->type->getNodeData()['primary']['selected'] === 1; + } + ); + } + + public function searchSecondaryDomainAction() { - return $this->searchBase('domains.domain', array( - "enabled", "type", "masterip", "domainname", "allowtransfer", "allowquery", "ttl", - "refresh", "retry", "expire", "negative", "mailadmin", "dnsserver" - )); + return $this->searchBase( + 'domains.domain', + [ 'enabled', 'type', 'domainname', 'primaryip' ], + 'domainname', + function ($record) { + return $record->type->getNodeData()['secondary']['selected'] === 1; + } + ); + } + + public function searchForwardDomainAction() + { + return $this->searchBase( + 'domains.domain', + [ 'enabled', 'type', 'domainname', 'forwardserver' ], + 'domainname', + function ($record) { + return $record->type->getNodeData()['forward']['selected'] === 1; + } + ); } public function getDomainAction($uuid = null) { - $this->sessionClose(); return $this->getBase('domain', 'domains.domain', $uuid); } - public function addDomainAction($uuid = null) + public function addPrimaryDomainAction($uuid = null) + { + return $this->addBase('domain', 'domains.domain', ['type' => 'primary']); + } + + public function addSecondaryDomainAction($uuid = null) + { + return $this->addBase('domain', 'domains.domain', ['type' => 'secondary']); + } + + public function addForwardDomainAction($uuid = null) { - return $this->addBase('domain', 'domains.domain'); + return $this->addBase('domain', 'domains.domain', ['type' => 'forward']); } public function delDomainAction($uuid) diff --git a/dns/bind/src/opnsense/mvc/app/controllers/OPNsense/Bind/Api/GeneralController.php b/dns/bind/src/opnsense/mvc/app/controllers/OPNsense/Bind/Api/GeneralController.php index d712f7fde9..b0d385cfb0 100644 --- a/dns/bind/src/opnsense/mvc/app/controllers/OPNsense/Bind/Api/GeneralController.php +++ b/dns/bind/src/opnsense/mvc/app/controllers/OPNsense/Bind/Api/GeneralController.php @@ -31,9 +31,32 @@ namespace OPNsense\Bind\Api; use OPNsense\Base\ApiMutableModelControllerBase; +use OPNsense\Core\Backend; class GeneralController extends ApiMutableModelControllerBase { protected static $internalModelClass = '\OPNsense\Bind\General'; protected static $internalModelName = 'general'; + + public function zonetestAction($zonename = null) + { + $response = "request error"; + if ($this->request->hasPost("zone")) { + $zonename = $this->request->getPost("zone"); + $backend = new Backend(); + $response = trim($backend->configdpRun("bind zone check", [$zonename])); + } + return array("response" => $response); + } + + public function zoneshowAction($zonename = null) + { + $response = "request error"; + if ($this->request->hasPost("zone")) { + $zonename = $this->request->getPost("zone"); + $backend = new Backend(); + $response = json_decode($backend->configdpRun("bind zone show", [$zonename]), true); + } + return $response; + } } diff --git a/dns/bind/src/opnsense/mvc/app/controllers/OPNsense/Bind/Api/RecordController.php b/dns/bind/src/opnsense/mvc/app/controllers/OPNsense/Bind/Api/RecordController.php index 49cac764ea..67dc5e30fb 100644 --- a/dns/bind/src/opnsense/mvc/app/controllers/OPNsense/Bind/Api/RecordController.php +++ b/dns/bind/src/opnsense/mvc/app/controllers/OPNsense/Bind/Api/RecordController.php @@ -1,32 +1,30 @@ - * Copyright (C) 2019 Deciso B.V. +/* + * Copyright (C) 2019 Michael Muenz + * Copyright (C) 2019 Deciso B.V. + * All rights reserved. * - * All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: * - * 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. * - * 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. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. */ namespace OPNsense\Bind\Api; @@ -77,7 +75,6 @@ public function searchRecordAction() public function getRecordAction($uuid = null) { - $this->sessionClose(); $domain = $this->request->get('domain'); $result = $this->getBase('record', 'records.record', $uuid); if ($uuid == null && !empty($result['record']['domain'])) { diff --git a/dns/bind/src/opnsense/mvc/app/controllers/OPNsense/Bind/Api/ServiceController.php b/dns/bind/src/opnsense/mvc/app/controllers/OPNsense/Bind/Api/ServiceController.php index a612a11216..8c4935bd0a 100644 --- a/dns/bind/src/opnsense/mvc/app/controllers/OPNsense/Bind/Api/ServiceController.php +++ b/dns/bind/src/opnsense/mvc/app/controllers/OPNsense/Bind/Api/ServiceController.php @@ -48,10 +48,9 @@ class ServiceController extends ApiMutableServiceControllerBase public function dnsblAction() { - $this->sessionClose(); $mdl = new Dnsbl(); $backend = new Backend(); - $response = $backend->configdpRun('bind dnsbl', array((string)$mdl->type)); - return array("response" => $response); + $response = $backend->configdpRun('bind dnsbl', [(string)$mdl->type]); + return ['response' => $response]; } } diff --git a/dns/bind/src/opnsense/mvc/app/controllers/OPNsense/Bind/GeneralController.php b/dns/bind/src/opnsense/mvc/app/controllers/OPNsense/Bind/GeneralController.php index 3f0b046b82..cfa86649db 100644 --- a/dns/bind/src/opnsense/mvc/app/controllers/OPNsense/Bind/GeneralController.php +++ b/dns/bind/src/opnsense/mvc/app/controllers/OPNsense/Bind/GeneralController.php @@ -35,7 +35,9 @@ public function indexAction() $this->view->generalForm = $this->getForm("general"); $this->view->dnsblForm = $this->getForm("dnsbl"); $this->view->formDialogEditBindAcl = $this->getForm("dialogEditBindAcl"); - $this->view->formDialogEditBindDomain = $this->getForm("dialogEditBindDomain"); + $this->view->formDialogEditBindPrimaryDomain = $this->getForm("dialogEditBindPrimaryDomain"); + $this->view->formDialogEditBindSecondaryDomain = $this->getForm("dialogEditBindSecondaryDomain"); + $this->view->formDialogEditBindForwardDomain = $this->getForm("dialogEditBindForwardDomain"); $this->view->formDialogEditBindRecord = $this->getForm("dialogEditBindRecord"); $this->view->pick('OPNsense/Bind/general'); } diff --git a/dns/bind/src/opnsense/mvc/app/controllers/OPNsense/Bind/LogsController.php b/dns/bind/src/opnsense/mvc/app/controllers/OPNsense/Bind/LogsController.php new file mode 100644 index 0000000000..23fde619ef --- /dev/null +++ b/dns/bind/src/opnsense/mvc/app/controllers/OPNsense/Bind/LogsController.php @@ -0,0 +1,46 @@ +view->pick('OPNsense/Bind/logs'); + } +} diff --git a/dns/bind/src/opnsense/mvc/app/controllers/OPNsense/Bind/forms/dialogEditBindDomain.xml b/dns/bind/src/opnsense/mvc/app/controllers/OPNsense/Bind/forms/dialogEditBindDomain.xml deleted file mode 100644 index f1f5adfc98..0000000000 --- a/dns/bind/src/opnsense/mvc/app/controllers/OPNsense/Bind/forms/dialogEditBindDomain.xml +++ /dev/null @@ -1,98 +0,0 @@ -
- - domain.enabled - - checkbox - This will enable or disable this zone. - - - domain.domainname - - text - Set the name for this zone. Both forward and reverse zones may be specified, i.e. example.com or 0.168.192.in-addr.arpa. - - - domain.type - - dropdown - Set the type for this zone. - - - - header - - - - domain.masterip - - text - Set the IP address of master server when using slave mode. - - - domain.allownotifyslave - - - select_multiple - true - A list of allowed IP addresses to receive notifies from. - - - - header - - - - domain.allowtransfer - - dropdown - Define an ACL where you allow which server can retrieve this zone. - - - domain.allowquery - - dropdown - Define an ACL where you allow which client are allowed to query this zone. - - - domain.ttl - - text - Set the general Time To Live for this zone. - - - domain.refresh - - text - Set the time in seconds after which name servers should refresh the zone information. - - - domain.retry - - text - Set the time in seconds after which name servers should retry requests if the master does not respond. - - - domain.expire - - text - Set the time in seconds after which name servers should stop answering requests if the master does not respond. - - - domain.negative - - text - Set the time in seconds after which an entry for a non-existent record should expire from cache. - - - domain.mailadmin - - text - Set the mail address of zone admin. A @-sign will automatically be replaced with a dot in the zone data. - - - domain.dnsserver - - text - Set the DNS server hosting this file. This should usually be the FQDN of your firewall where the BIND plugin is installed. - -
diff --git a/dns/bind/src/opnsense/mvc/app/controllers/OPNsense/Bind/forms/dialogEditBindForwardDomain.xml b/dns/bind/src/opnsense/mvc/app/controllers/OPNsense/Bind/forms/dialogEditBindForwardDomain.xml new file mode 100644 index 0000000000..4ca30f87c6 --- /dev/null +++ b/dns/bind/src/opnsense/mvc/app/controllers/OPNsense/Bind/forms/dialogEditBindForwardDomain.xml @@ -0,0 +1,22 @@ +
+ + domain.enabled + + checkbox + This will enable or disable this zone. + + + domain.domainname + + text + Set the name for this zone. Both forward and reverse zones may be specified, i.e. example.com or 0.168.192.in-addr.arpa. + + + domain.forwardserver + + + select_multiple + true + Set the IP address of server to forward requests to. + +
diff --git a/dns/bind/src/opnsense/mvc/app/controllers/OPNsense/Bind/forms/dialogEditBindPrimaryDomain.xml b/dns/bind/src/opnsense/mvc/app/controllers/OPNsense/Bind/forms/dialogEditBindPrimaryDomain.xml new file mode 100644 index 0000000000..8439403d18 --- /dev/null +++ b/dns/bind/src/opnsense/mvc/app/controllers/OPNsense/Bind/forms/dialogEditBindPrimaryDomain.xml @@ -0,0 +1,80 @@ +
+ + domain.enabled + + checkbox + This will enable or disable this zone. + + + domain.domainname + + text + Set the name for this zone. Both forward and reverse zones may be specified, i.e. example.com or 0.168.192.in-addr.arpa. + + + domain.allowtransfer + + select_multiple + Define the ACLs where you allow which server can retrieve this zone. + + + domain.allowrndctransfer + + checkbox + Allow transfers via the RDNC key named "rndc-key". The key is shown in the general tab. + + + domain.allowquery + + select_multiple + Define the ACLs where you allow which client are allowed to query this zone. + + + domain.allowrndcupdate + + checkbox + Allow updates via the RDNC key named "rndc-key". The key is shown in the general tab. + + + domain.ttl + + text + Set the general Time To Live for this zone. + + + domain.refresh + + text + Set the time in seconds after which name servers should refresh the zone information. + + + domain.retry + + text + Set the time in seconds after which name servers should retry requests if the master does not respond. + + + domain.expire + + text + Set the time in seconds after which name servers should stop answering requests if the master does not respond. + + + domain.negative + + text + Set the time in seconds after which an entry for a non-existent record should expire from cache. + + + domain.mailadmin + + text + Set the mail address of zone admin. A @-sign will automatically be replaced with a dot in the zone data. + + + domain.dnsserver + + text + Set the DNS server hosting this file. This should usually be the FQDN of your firewall where the BIND plugin is installed. + +
diff --git a/dns/bind/src/opnsense/mvc/app/controllers/OPNsense/Bind/forms/dialogEditBindSecondaryDomain.xml b/dns/bind/src/opnsense/mvc/app/controllers/OPNsense/Bind/forms/dialogEditBindSecondaryDomain.xml new file mode 100644 index 0000000000..a6380c2837 --- /dev/null +++ b/dns/bind/src/opnsense/mvc/app/controllers/OPNsense/Bind/forms/dialogEditBindSecondaryDomain.xml @@ -0,0 +1,60 @@ +
+ + domain.enabled + + checkbox + This will enable or disable this zone. + + + domain.domainname + + text + Set the name for this zone. Both forward and reverse zones may be specified, i.e. example.com or 0.168.192.in-addr.arpa. + + + domain.allowtransfer + + select_multiple + Define the ACLs where you allow which server can retrieve this zone. + + + domain.allowquery + + select_multiple + Define the ACLs where you allow which client are allowed to query this zone. + + + domain.primaryip + + + select_multiple + true + Set the IP address of primary server. + + + domain.transferkeyalgo + + dropdown + Set the authentication algorithm for the TSIG key used to transfer domain data from the primary server. + + + domain.transferkeyname + + text + The name of the TSIG key, which must match the value on the primary server. + + + domain.transferkey + + text + The TSIG key used to transfer domain data from the master server in Base64 encoding. + + + domain.allownotifysecondary + + + select_multiple + true + A list of allowed IP addresses to receive notifies from (in addition to the primary server.) + +
diff --git a/dns/bind/src/opnsense/mvc/app/controllers/OPNsense/Bind/forms/general.xml b/dns/bind/src/opnsense/mvc/app/controllers/OPNsense/Bind/forms/general.xml index 6b52705ab9..23e9c92026 100644 --- a/dns/bind/src/opnsense/mvc/app/controllers/OPNsense/Bind/forms/general.xml +++ b/dns/bind/src/opnsense/mvc/app/controllers/OPNsense/Bind/forms/general.xml @@ -33,6 +33,34 @@ text Set the port the service should listen to. + + general.querysource + + text + true + Specify the IPv4 address used as a source for outbound queries. + + + general.querysourcev6 + + text + true + Specify the IPv6 address used as a source for outbound queries. + + + general.transfersource + + text + true + Specify the IPv4 address used as a source for zone transfers. + + + general.transfersourcev6 + + text + true + Specify the IPv6 address used as a source for zone transfers. + general.forwarders @@ -41,11 +69,38 @@ true Set one or more hosts to send your DNS queries if the request is unknown. + + general.filteraaaav4 + + checkbox + This will filter AAAA records on IPv4 Clients. Set "DNSSEC Validation" to "No" and AAAA records will be omitted even if they are signed. + + + general.filteraaaav6 + + checkbox + This will filter AAAA records on IPv6 Clients. Set "DNSSEC Validation" to "No" and AAAA records will be omitted even if they are signed. + + + general.filteraaaaacl + + + select_multiple + true + Specifies a list of client addresses for which AAAA filtering is to be applied. + general.logsize text - Set the amount how big a logfile can growth. + Set the amount how big a logfile can growth. For Query and Blocked logs. + + + general.general_log_level + + + dropdown + Select General Log level. Log levels are listed in the order of increasing verbosity. Setting a certain log level will cause all messages of the specified and more severe log levels to be logged. general.maxcachesize @@ -56,9 +111,21 @@ general.recursion - dropdown + select_multiple Define an ACL where you allow which clients can resolve via this service. Usually use your local LAN. + + general.allowtransfer + + select_multiple + Define the ACLs where you allow which server can retrieve zones. + + + general.allowquery + + select_multiple + Define the ACLs where you allow which client are allowed to query this server. + general.dnssecvalidation @@ -109,4 +176,23 @@ true Except a list of IPs from rate-limiting like ::1 + + header + + true + + + general.rndcalgo + + dropdown + true + Set the authentication algorithm for the RNDC key. This requires a restart of the Bind Service. + + + general.rndcsecret + + text + true + The base64-encoded RNDC key. This requires a restart of the Bind Service. + diff --git a/dns/bind/src/opnsense/mvc/app/library/OPNsense/System/Status/BindOverrideStatus.php b/dns/bind/src/opnsense/mvc/app/library/OPNsense/System/Status/BindOverrideStatus.php new file mode 100644 index 0000000000..eb15970e6a --- /dev/null +++ b/dns/bind/src/opnsense/mvc/app/library/OPNsense/System/Status/BindOverrideStatus.php @@ -0,0 +1,57 @@ +internalPriority = 2; + $this->internalPersistent = true; + $this->internalIsBanner = true; + $this->internalTitle = gettext('BIND config override'); + $this->internalScope = [ + '/ui/bind/general/index' + ]; + } + + public function collectStatus() + { + if (count(glob('/usr/local/etc/namedb/named.conf.d/*')) > 1) { + $this->internalMessage = gettext( + 'The configuration contains manual overwrites, these may interfere with the settings configured here.' + ); + $this->internalStatus = SystemStatusCode::NOTICE; + } + } +} diff --git a/dns/bind/src/opnsense/mvc/app/models/OPNsense/Bind/ACL/ACL.xml b/dns/bind/src/opnsense/mvc/app/models/OPNsense/Bind/ACL/ACL.xml index 83205c526b..76c52ef6c5 100644 --- a/dns/bind/src/opnsense/mvc/app/models/OPNsense/Bind/ACL/ACL.xml +++ b/dns/bind/src/opnsense/mvc/app/models/OPNsense/Bind/ACL/ACL.xml @@ -4,6 +4,7 @@ ui/bind/* api/bind/* + api/diagnostics/log/named/* diff --git a/dns/bind/src/opnsense/mvc/app/models/OPNsense/Bind/Acl.xml b/dns/bind/src/opnsense/mvc/app/models/OPNsense/Bind/Acl.xml index 6466c00332..efa489b85b 100644 --- a/dns/bind/src/opnsense/mvc/app/models/OPNsense/Bind/Acl.xml +++ b/dns/bind/src/opnsense/mvc/app/models/OPNsense/Bind/Acl.xml @@ -6,20 +6,23 @@ - 1 + 1 Y - Y - /^([0-9a-zA-Z]){1,32}$/u - Should be a string between 1 and 32 characters. Allowed characters are 0-9a-zA-Z + /^(?!any$|localhost$|localnets$|none$)[0-9a-zA-Z_\-]{1,32}$/u + Should be a string between 1 and 32 characters. Allowed characters are 0-9, a-z, A-Z, _ and -. Built-in ACL names must not be used: any, localhost, localnets, none. + + + An ACL with this name already exists. + UniqueConstraint + + - - , Y - Y + Y diff --git a/dns/bind/src/opnsense/mvc/app/models/OPNsense/Bind/Dnsbl.xml b/dns/bind/src/opnsense/mvc/app/models/OPNsense/Bind/Dnsbl.xml index 119dbf76fc..103db84e29 100644 --- a/dns/bind/src/opnsense/mvc/app/models/OPNsense/Bind/Dnsbl.xml +++ b/dns/bind/src/opnsense/mvc/app/models/OPNsense/Bind/Dnsbl.xml @@ -4,7 +4,7 @@ 1.0.5 - 0 + 0 Y @@ -41,19 +41,19 @@ N - 0 + 0 Y - 0 + 0 Y - 0 + 0 Y - 0 + 0 Y diff --git a/dns/bind/src/opnsense/mvc/app/models/OPNsense/Bind/Domain.xml b/dns/bind/src/opnsense/mvc/app/models/OPNsense/Bind/Domain.xml index 8fc55f2c8e..6743b66ae4 100644 --- a/dns/bind/src/opnsense/mvc/app/models/OPNsense/Bind/Domain.xml +++ b/dns/bind/src/opnsense/mvc/app/models/OPNsense/Bind/Domain.xml @@ -1,32 +1,45 @@ //OPNsense/bind/domain BIND domain configuration - 1.0.0 + 1.1.2 - 1 + 1 Y - master + primary Y - master - slave + primary + secondary + forward - - N - - - N - , - Y - + + Y + + + Y + + + + HMAC-SHA512 + HMAC-SHA384 + HMAC-SHA256 + HMAC-SHA224 + HMAC-SHA1 + HMAC-MD5 + + + + + + Y + - Y @@ -37,9 +50,9 @@ name - N - N + Y + - N - N + Y - - N - + + 1 + Y + + - 86400 + 86400 Y 60 86400 Set a value between 60 and 86400. - 21600 + 21600 Y 60 86400 Set a value between 60 and 86400. - 3600 + 3600 Y 60 86400 Set a value between 60 and 86400. - 3542400 + 3542400 Y 60 10000000 Set a value between 60 and 10000000. - 3600 + 3600 Y 60 86400 Set a value between 60 and 86400. - mail.opnsense.localdomain + mail.opnsense.localdomain Y - opnsense.localdomain + opnsense.localdomain Y diff --git a/dns/bind/src/opnsense/mvc/app/models/OPNsense/Bind/General.php b/dns/bind/src/opnsense/mvc/app/models/OPNsense/Bind/General.php index 4d6a5bd0c7..1c97234eaa 100644 --- a/dns/bind/src/opnsense/mvc/app/models/OPNsense/Bind/General.php +++ b/dns/bind/src/opnsense/mvc/app/models/OPNsense/Bind/General.php @@ -1,35 +1,66 @@ - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, - INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY - AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, - OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - POSSIBILITY OF SUCH DAMAGE. -*/ + * Copyright (C) 2018 Michael Muenz + * Copyright (C) 2023 Deciso B.V. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ namespace OPNsense\Bind; use OPNsense\Base\BaseModel; +use OPNsense\Base\Messages\Message; +use OPNsense\Core\Backend; class General extends BaseModel { + public function performValidation($validateFullModel = false) + { + $messages = parent::performValidation($validateFullModel); + + if ( + ($validateFullModel || $this->enabled->isFieldChanged() || $this->port->isFieldChanged()) && + !empty((string)$this->enabled) + ) { + foreach (json_decode((new Backend())->configdpRun('service list'), true) as $service) { + if (empty($service['dns_ports'])) { + continue; + } + if (!is_array($service['dns_ports'])) { + syslog(LOG_ERR, sprintf('Service %s (%s) reported a faulty "dns_ports" entry.', $service['description'], $service['name'])); + continue; + } + if ($service['name'] != 'named' && in_array((string)$this->port, $service['dns_ports'])) { + $messages->appendMessage(new Message( + sprintf(gettext('%s is currently using this port.'), $service['description']), + $this->port->getInternalXMLTagName() + )); + break; + } + } + } + + return $messages; + } } diff --git a/dns/bind/src/opnsense/mvc/app/models/OPNsense/Bind/General.xml b/dns/bind/src/opnsense/mvc/app/models/OPNsense/Bind/General.xml index 2411472f18..238c9dc248 100644 --- a/dns/bind/src/opnsense/mvc/app/models/OPNsense/Bind/General.xml +++ b/dns/bind/src/opnsense/mvc/app/models/OPNsense/Bind/General.xml @@ -1,50 +1,86 @@ //OPNsense/bind/general BIND configuration - 1.0.7 + 1.0.12 - 0 + 0 Y - 0 + 0 Y - 1 + 1 Y - 127.0.0.1 - , + 0.0.0.0 Y - Y + Y - ::1 - , + :: Y - Y + Y + + ipv4 + N + + + ipv6 + N + + + ipv4 + N + + + ipv6 + N + - 53530 + 53530 Y - , - N - Y + Y + + 0 + Y + + + 0 + Y + + + Y + - 5 + 5 Y 1 1000 Choose a value between 1 and 1000. + + + Critical + Error + Warning + Notice + Informational + Debug + Dynamic + + Y + info + - 80 + 80 Y 1 99 @@ -58,46 +94,78 @@ name - N - N + Y Choose an ACL. + + + + + Y + + + + + + Y + No Auto - no - N + no Y - 0 + 0 Y - 0 + 0 Y - 0 + 0 Y - 0 + 0 Y - N 1 1000 Choose a value between 1 and 1000. - 127.0.0.1,::1 - , + 0.0.0.0,:: Y - Y + Y + + Y + hmac-sha256 + + HMAC-SHA512 + HMAC-SHA384 + HMAC-SHA256 + HMAC-SHA224 + HMAC-SHA1 + HMAC-MD5 + + + + Y + VxtIzJevSQXqnr7h2qerrcwjnZlMWSGGFBndKeNIDfw= +
diff --git a/dns/bind/src/opnsense/mvc/app/models/OPNsense/Bind/Menu/Menu.xml b/dns/bind/src/opnsense/mvc/app/models/OPNsense/Bind/Menu/Menu.xml index 13b24d1e01..06ed53ca81 100644 --- a/dns/bind/src/opnsense/mvc/app/models/OPNsense/Bind/Menu/Menu.xml +++ b/dns/bind/src/opnsense/mvc/app/models/OPNsense/Bind/Menu/Menu.xml @@ -1,10 +1,8 @@ - - - - + + diff --git a/dns/bind/src/opnsense/mvc/app/models/OPNsense/Bind/Migrations/M1_1_0.php b/dns/bind/src/opnsense/mvc/app/models/OPNsense/Bind/Migrations/M1_1_0.php new file mode 100644 index 0000000000..c38c74facf --- /dev/null +++ b/dns/bind/src/opnsense/mvc/app/models/OPNsense/Bind/Migrations/M1_1_0.php @@ -0,0 +1,78 @@ +object(); + + /* checks to see if there is a bind config section, otherwise skips the rest of the migration */ + if (empty($config->OPNsense->bind)) { + return; + } + + $bindConfig = $config->OPNsense->bind; + + /* loops through the domains in the config */ + foreach ($bindConfig->domain->domains->domain as $domain) { + $domainModel = $model->getNodeByReference('domains.domain.' . $domain->attributes()['uuid']); + + /* migrates the domain type */ + if ($domain->type == 'master') { + $domainModel->type->setValue('primary'); + } else { + $domainModel->type->setValue('secondary'); + } + + /* migrates the Master IP to Primary IP field */ + if (!empty($domain->masterip)) { + $domainModel->primaryip->setValue($domain->masterip); + } + + /* migrates the AllowNotify Slave to AllowNotify Secondary field */ + if (!empty($domain->allownotifyslave)) { + $domainModel->allownotifysecondary->setValue($domain->allownotifyslave); + } + } + + parent::run($model); + } + } +} diff --git a/dns/bind/src/opnsense/mvc/app/models/OPNsense/Bind/Record.xml b/dns/bind/src/opnsense/mvc/app/models/OPNsense/Bind/Record.xml index 797b43b723..ccb589cbfa 100644 --- a/dns/bind/src/opnsense/mvc/app/models/OPNsense/Bind/Record.xml +++ b/dns/bind/src/opnsense/mvc/app/models/OPNsense/Bind/Record.xml @@ -1,12 +1,12 @@ //OPNsense/bind/record BIND record configuration - 1.0.0 + 1.0.1 - 1 + 1 Y @@ -19,25 +19,25 @@ - - - N - + - A + A Y A AAAA CAA CNAME + DNAME DNSKEY DS MX NS PTR + RP RRSIG SRV + SSHFP TLSA TXT diff --git a/dns/bind/src/opnsense/mvc/app/views/OPNsense/Bind/general.volt b/dns/bind/src/opnsense/mvc/app/views/OPNsense/Bind/general.volt index 60d74b68c6..0d9b55eda9 100644 --- a/dns/bind/src/opnsense/mvc/app/views/OPNsense/Bind/general.volt +++ b/dns/bind/src/opnsense/mvc/app/views/OPNsense/Bind/general.volt @@ -1,46 +1,46 @@ {# - -OPNsense® is Copyright © 2014 – 2019 by Deciso B.V. -This file is Copyright © 2018 - 2019 by Michael Muenz -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, -INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY -AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, -OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. - -#} + # + # Copyright (c) 2014-2019 Deciso B.V. + # Copyright (c) 2018-2019 Michael Muenz + # All rights reserved. + # + # Redistribution and use in source and binary forms, with or without modification, + # are permitted provided that the following conditions are met: + # + # 1. Redistributions of source code must retain the above copyright notice, + # this list of conditions and the following disclaimer. + # + # 2. Redistributions in binary form must reproduce the above copyright notice, + # this list of conditions and the following disclaimer in the documentation + # and/or other materials provided with the distribution. + # + # THIS SOFTWARE IS PROVIDED “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + # AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + # AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + # OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + # POSSIBILITY OF SUCH DAMAGE. + #}
{{ partial("layout_partials/base_form",['fields':generalForm,'id':'frm_general_settings'])}} -
-
+
@@ -48,83 +48,86 @@ POSSIBILITY OF SUCH DAMAGE.
{{ partial("layout_partials/base_form",['fields':dnsblForm,'id':'frm_dnsbl_settings'])}} -
-
+
- - - - - - - - - - - - - - - - - - -
{{ lang._('Enabled') }}{{ lang._('Name') }}{{ lang._('Networks') }}{{ lang._('ID') }}{{ lang._('Commands') }}
- -
+
+ + + + + + + + + + + + + + + + + + +
{{ lang._('Enabled') }}{{ lang._('Name') }}{{ lang._('Networks') }}{{ lang._('ID') }}{{ lang._('Commands') }}
+ + +
+



-
- {{ partial("layout_partials/base_dialog",['fields':formDialogEditBindAcl,'id':'dialogEditBindAcl','label':lang._('Edit ACL')])}} -{{ partial("layout_partials/base_dialog",['fields':formDialogEditBindDomain,'id':'dialogEditBindDomain','label':lang._('Edit Zone')])}} +{{ partial("layout_partials/base_dialog",['fields':formDialogEditBindPrimaryDomain,'id':'dialogEditBindPrimaryDomain','label':lang._('Edit Primary Zone')])}} +{{ partial("layout_partials/base_dialog",['fields':formDialogEditBindSecondaryDomain,'id':'dialogEditBindSecondaryDomain','label':lang._('Edit Secondary Zone')])}} +{{ partial("layout_partials/base_dialog",['fields':formDialogEditBindForwardDomain,'id':'dialogEditBindForwardDomain','label':lang._('Edit Forward Zone')])}} {{ partial("layout_partials/base_dialog",['fields':formDialogEditBindRecord,'id':'dialogEditBindRecord','label':lang._('Edit Record')])}} + diff --git a/dns/bind/src/opnsense/mvc/app/views/OPNsense/Bind/logs.volt b/dns/bind/src/opnsense/mvc/app/views/OPNsense/Bind/logs.volt new file mode 100644 index 0000000000..4eab4cb5a9 --- /dev/null +++ b/dns/bind/src/opnsense/mvc/app/views/OPNsense/Bind/logs.volt @@ -0,0 +1,132 @@ +{# + # Copyright (c) 2022 Robbert Rijkse + # Copyright (c) 2019 Deciso B.V. + # All rights reserved. + # + # Redistribution and use in source and binary forms, with or without modification, + # are permitted provided that the following conditions are met: + # + # 1. Redistributions of source code must retain the above copyright notice, + # this list of conditions and the following disclaimer. + # + # 2. Redistributions in binary form must reproduce the above copyright notice, + # this list of conditions and the following disclaimer in the documentation + # and/or other materials provided with the distribution. + # + # THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + # AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + # AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + # OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + # POSSIBILITY OF SUCH DAMAGE. + #} + + + + + + +
+ +
+ {{ partial("OPNsense/Diagnostics/log",['module':'core','scope':'named'])}} +
+ +
+
+
+ + + + + + + + + + + + +
{{ lang._('Date') }}{{ lang._('Client') }}{{ lang._('IP Address') }}{{ lang._('Record') }}{{ lang._('Query') }}
+
+
+
+ +
+
+
+ + + + + + + + + + + + +
{{ lang._('Date') }}{{ lang._('Client') }}{{ lang._('IP Address') }}{{ lang._('Record') }}{{ lang._('Query') }}
+
+
+
+
diff --git a/dns/bind/src/opnsense/scripts/OPNsense/Bind/dnsbl.sh b/dns/bind/src/opnsense/scripts/OPNsense/Bind/dnsbl.sh index 4cef66ada6..9324b6b2a3 100755 --- a/dns/bind/src/opnsense/scripts/OPNsense/Bind/dnsbl.sh +++ b/dns/bind/src/opnsense/scripts/OPNsense/Bind/dnsbl.sh @@ -38,70 +38,70 @@ mkdir -p ${WORKDIR} easylist() { # EasyList ${FETCH} https://justdomains.github.io/blocklists/lists/easylist-justdomains.txt -o ${WORKDIR}/easylist-raw - sed "/\.$/d" ${WORKDIR}/easylist-raw | sed "/^#/d" | sed "/\_/d" | sed "/^\s*$/d" | sed "/\.\./d" | sed "s/^\.//g" > ${WORKDIR}/easylist + sed "/\.$/d" ${WORKDIR}/easylist-raw | sed "/^#/d" | sed "/\_/d" | sed "/^[[:space:]]*$/d" | sed "/\.\./d" | sed "s/^\.//g" > ${WORKDIR}/easylist rm ${WORKDIR}/easylist-raw } easyprivacy() { # EasyPrivacy ${FETCH} https://justdomains.github.io/blocklists/lists/easyprivacy-justdomains.txt -o ${WORKDIR}/easyprivacy-raw - sed "/\.$/d" ${WORKDIR}/easyprivacy-raw | sed "/^#/d" | sed "/\_/d" | sed "/^\s*$/d" | sed "/\.\./d" | sed "s/^\.//g" > ${WORKDIR}/easyprivacy + sed "/\.$/d" ${WORKDIR}/easyprivacy-raw | sed "/^#/d" | sed "/\_/d" | sed "/^[[:space:]]*$/d" | sed "/\.\./d" | sed "s/^\.//g" > ${WORKDIR}/easyprivacy rm ${WORKDIR}/easyprivacy-raw } pornall() { # PornAll ${FETCH} https://raw.githubusercontent.com/chadmayfield/my-pihole-blocklists/master/lists/pi_blocklist_porn_all.list -o ${WORKDIR}/pornall-raw - sed "/\.$/d" ${WORKDIR}/pornall-raw | sed "/^#/d" | sed "/\_/d" | sed "/^\s*$/d" | sed "/\.\./d" | sed "s/^\.//g" > ${WORKDIR}/pornall + sed "/\.$/d" ${WORKDIR}/pornall-raw | sed "/^#/d" | sed "/\_/d" | sed "/^[[:space:]]*$/d" | sed "/\.\./d" | sed "s/^\.//g" > ${WORKDIR}/pornall rm ${WORKDIR}/pornall-raw } porntop() { # PornTop1M ${FETCH} https://raw.githubusercontent.com/chadmayfield/pihole-blocklists/master/lists/pi_blocklist_porn_top1m.list -o ${WORKDIR}/porntop-raw - sed "/\.$/d" ${WORKDIR}/porntop-raw | sed "/^#/d" | sed "/\_/d" | sed "/^\s*$/d" | sed "/\.\./d" | sed "s/^\.//g" > ${WORKDIR}/porntop + sed "/\.$/d" ${WORKDIR}/porntop-raw | sed "/^#/d" | sed "/\_/d" | sed "/^[[:space:]]*$/d" | sed "/\.\./d" | sed "s/^\.//g" > ${WORKDIR}/porntop rm ${WORKDIR}/porntop-raw } emdlist() { # EMD ${FETCH} https://hosts-file.net/emd.txt -o ${WORKDIR}/emdlist-raw - sed "/\.$/d" ${WORKDIR}/emdlist-raw | sed "/^#/d" | sed "/\_/d" | sed "/^\s*$/d" | sed "/\.\./d" | sed "s/^\.//g" | sed "/localhost/d" | tr -d '\r' | awk 'BEGIN{FS=OFS=" ";}{print $2;}' > ${WORKDIR}/emdlist + sed "/\.$/d" ${WORKDIR}/emdlist-raw | sed "/^#/d" | sed "/\_/d" | sed "/^[[:space:]]*$/d" | sed "/\.\./d" | sed "s/^\.//g" | sed "/localhost/d" | tr -d '\r' | awk 'BEGIN{FS=OFS=" ";}{print $2;}' > ${WORKDIR}/emdlist rm ${WORKDIR}/emdlist-raw } adguard() { # AdGuard ${FETCH} https://justdomains.github.io/blocklists/lists/adguarddns-justdomains.txt -o ${WORKDIR}/adguard-raw - sed "/\.$/d" ${WORKDIR}/adguard-raw | sed "/^#/d" | sed "/\_/d" | sed "/^\s*$/d" | sed "/\.\./d" | sed "s/^\.//g" > ${WORKDIR}/adguard + sed "/\.$/d" ${WORKDIR}/adguard-raw | sed "/^#/d" | sed "/\_/d" | sed "/^[[:space:]]*$/d" | sed "/\.\./d" | sed "s/^\.//g" > ${WORKDIR}/adguard rm ${WORKDIR}/adguard-raw } nocoin() { # NoCoin ${FETCH} https://justdomains.github.io/blocklists/lists/nocoin-justdomains.txt -o ${WORKDIR}/nocoin-raw - sed "/\.$/d" ${WORKDIR}/nocoin-raw | sed "/^#/d" | sed "/\_/d" | sed "/^\s*$/d" | sed "/\.\./d" | sed "s/^\.//g" > ${WORKDIR}/nocoin + sed "/\.$/d" ${WORKDIR}/nocoin-raw | sed "/^#/d" | sed "/\_/d" | sed "/^[[:space:]]*$/d" | sed "/\.\./d" | sed "s/^\.//g" > ${WORKDIR}/nocoin rm ${WORKDIR}/nocoin-raw } rwtracker() { # RansomWare Tracker abuse.ch ${FETCH} https://ransomwaretracker.abuse.ch/downloads/RW_DOMBL.txt -o ${WORKDIR}/rwtracker-raw - sed "/\.$/d" ${WORKDIR}/rwtracker-raw | sed "/^#/d" | sed "/\_/d" | sed "/^\s*$/d" | sed "/\.\./d" | sed "s/^\.//g" > ${WORKDIR}/rwtracker + sed "/\.$/d" ${WORKDIR}/rwtracker-raw | sed "/^#/d" | sed "/\_/d" | sed "/^[[:space:]]*$/d" | sed "/\.\./d" | sed "s/^\.//g" > ${WORKDIR}/rwtracker rm ${WORKDIR}/rwtracker-raw } mwdomains() { # MalwareDomains ${FETCH} http://malwaredomains.lehigh.edu/files/justdomains -o ${WORKDIR}/malwaredomains-raw - sed "/\.$/d" ${WORKDIR}/malwaredomains-raw | sed "/^#/d" | sed "/\_/d" | sed "/^\s*$/d" | sed "/\.\./d" | sed "s/^\.//g" > ${WORKDIR}/malwaredomains + sed "/\.$/d" ${WORKDIR}/malwaredomains-raw | sed "/^#/d" | sed "/\_/d" | sed "/^[[:space:]]*$/d" | sed "/\.\./d" | sed "s/^\.//g" > ${WORKDIR}/malwaredomains rm ${WORKDIR}/malwaredomains-raw } windowsspyblockerspy() { # WindowsSpyBlocker (spy) ${FETCH} https://raw.githubusercontent.com/crazy-max/WindowsSpyBlocker/master/data/hosts/spy.txt -o ${WORKDIR}/windowsspyblockerspy-raw - sed "/\.$/d" ${WORKDIR}/windowsspyblockerspy-raw | sed "/^#/d" | sed "/\_/d" | sed "/^\s*$/d" | sed "/\.\./d" | sed "s/^\.//g" | sed "/localhost/d" | tr -d '\r' | awk 'BEGIN{FS=OFS=" ";}{print $2;}' > ${WORKDIR}/windowsspyblockerspy + sed "/\.$/d" ${WORKDIR}/windowsspyblockerspy-raw | sed "/^#/d" | sed "/\_/d" | sed "/^[[:space:]]*$/d" | sed "/\.\./d" | sed "s/^\.//g" | sed "/localhost/d" | tr -d '\r' | awk 'BEGIN{FS=OFS=" ";}{print $2;}' > ${WORKDIR}/windowsspyblockerspy rm ${WORKDIR}/windowsspyblockerspy-raw } @@ -115,98 +115,98 @@ windowsspyblockerupdate() { windowsspyblockerextra() { # WindowsSpyBlocker (extra) ${FETCH} https://raw.githubusercontent.com/crazy-max/WindowsSpyBlocker/master/data/hosts/extra.txt -o ${WORKDIR}/windowsspyblockerextra-raw - sed "/\.$/d" ${WORKDIR}/windowsspyblockerextra-raw | sed "/^#/d" | sed "/\_/d" | sed "/^\s*$/d" | sed "/\.\./d" | sed "s/^\.//g" | sed "/localhost/d" | tr -d '\r' | awk 'BEGIN{FS=OFS=" ";}{print $2;}' > ${WORKDIR}/windowsspyblockerextra + sed "/\.$/d" ${WORKDIR}/windowsspyblockerextra-raw | sed "/^#/d" | sed "/\_/d" | sed "/^[[:space:]]*$/d" | sed "/\.\./d" | sed "s/^\.//g" | sed "/localhost/d" | tr -d '\r' | awk 'BEGIN{FS=OFS=" ";}{print $2;}' > ${WORKDIR}/windowsspyblockerextra rm ${WORKDIR}/windowsspyblockerextra-raw } cameleon() { # Cameleon List ${FETCH} http://sysctl.org/cameleon/hosts -o ${WORKDIR}/cameleon-raw - sed "/\.$/d" ${WORKDIR}/cameleon-raw | sed "/^#/d" | sed "/\_/d" | sed "/^\s*$/d" | sed "/\.\./d" | sed "s/^\.//g" | sed "/localhost/d" | tr -d '\r' | awk 'BEGIN{FS=OFS=" ";}{print $2;}' > ${WORKDIR}/cameleon + sed "/\.$/d" ${WORKDIR}/cameleon-raw | sed "/^#/d" | sed "/\_/d" | sed "/^[[:space:]]*$/d" | sed "/\.\./d" | sed "s/^\.//g" | sed "/localhost/d" | tr -d '\r' | awk 'BEGIN{FS=OFS=" ";}{print $2;}' > ${WORKDIR}/cameleon rm ${WORKDIR}/cameleon-raw } adaway() { # AdAway List ${FETCH} https://adaway.org/hosts.txt -o ${WORKDIR}/adaway-raw - sed "/\.$/d" ${WORKDIR}/adaway-raw | sed "/^#/d" | sed "/\_/d" | sed "/^\s*$/d" | sed "/\.\./d" | sed "s/^\.//g" | sed "/localhost/d" | tr -d '\r' | awk 'BEGIN{FS=OFS=" ";}{print $2;}' > ${WORKDIR}/adaway + sed "/\.$/d" ${WORKDIR}/adaway-raw | sed "/^#/d" | sed "/\_/d" | sed "/^[[:space:]]*$/d" | sed "/\.\./d" | sed "s/^\.//g" | sed "/localhost/d" | tr -d '\r' | awk 'BEGIN{FS=OFS=" ";}{print $2;}' > ${WORKDIR}/adaway rm ${WORKDIR}/adaway-raw } yoyo() { # YoYo List ${FETCH} "http://pgl.yoyo.org/adservers/serverlist.php?hostformat=hosts&mimetype=plaintext" -o ${WORKDIR}/yoyo-raw - sed "/\.$/d" ${WORKDIR}/yoyo-raw | sed "/^#/d" | sed "/\_/d" | sed "/^\s*$/d" | sed "/\.\./d" | sed "s/^\.//g" | sed "/localhost/d" | tr -d '\r' | awk 'BEGIN{FS=OFS=" ";}{print $2;}' > ${WORKDIR}/yoyo + sed "/\.$/d" ${WORKDIR}/yoyo-raw | sed "/^#/d" | sed "/\_/d" | sed "/^[[:space:]]*$/d" | sed "/\.\./d" | sed "s/^\.//g" | sed "/localhost/d" | tr -d '\r' | awk 'BEGIN{FS=OFS=" ";}{print $2;}' > ${WORKDIR}/yoyo rm ${WORKDIR}/yoyo-raw } stevenblack() { # StevenBlack ${FETCH} https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts -o ${WORKDIR}/stevenblack-raw - sed "/\.$/d" ${WORKDIR}/stevenblack-raw | sed "/^#/d" | sed "/\_/d" | sed "/^\s*$/d" | sed "/\.\./d" | sed "s/^\.//g" | sed "/localhost/d" | sed "/127\.0\.0\.1/d" | sed "/255\.255\.255\.255/d" | sed "/\:\:1/d" | sed "/fe80\:\:1/d" | sed "/ff00\:\:/d" | sed "/ff02\:\:/d" | sed "/0\.0\.0\.0 0\.0\.0\.0/d" | tr -d '\r' | awk 'BEGIN{FS=OFS=" ";}{print $2;}' > ${WORKDIR}/stevenblack + sed "/\.$/d" ${WORKDIR}/stevenblack-raw | sed "/^#/d" | sed "/\_/d" | sed "/^[[:space:]]*$/d" | sed "/\.\./d" | sed "s/^\.//g" | sed "/localhost/d" | sed "/127\.0\.0\.1/d" | sed "/255\.255\.255\.255/d" | sed "/\:\:1/d" | sed "/fe80\:\:1/d" | sed "/ff00\:\:/d" | sed "/ff02\:\:/d" | sed "/0\.0\.0\.0 0\.0\.0\.0/d" | tr -d '\r' | awk 'BEGIN{FS=OFS=" ";}{print $2;}' > ${WORKDIR}/stevenblack rm ${WORKDIR}/stevenblack-raw } blocklistads() { # Blocklist.site Ads - ${FETCH} https://blocklist.site/app/dl/ads -o ${WORKDIR}/blocklistads-raw - sed "/\.$/d" ${WORKDIR}/blocklistads-raw | sed "/^#/d" | sed "/\_/d" | sed "/^\s*$/d" | sed "/\.\./d" | sed "s/^\.//g" | sed "/localhost/d" | sed "/127\.0\.0\.1/d" | sed "/255\.255\.255\.255/d" | sed "/\:\:1/d" | sed "/fe80\:\:1/d" | sed "/ff00\:\:/d" | sed "/ff02\:\:/d" | sed "/0\.0\.0\.0 0\.0\.0\.0/d" > ${WORKDIR}/blocklistads + ${FETCH} https://blocklistproject.github.io/Lists/ads.txt -o ${WORKDIR}/blocklistads-raw + sed "/\.$/d" ${WORKDIR}/blocklistads-raw | sed "/^#/d" | sed "/\_/d" | sed "/^[[:space:]]*$/d" | sed "/\.\./d" | sed "s/^\.//g" | sed "/localhost/d" | sed "/127\.0\.0\.1/d" | sed "/255\.255\.255\.255/d" | sed "/\:\:1/d" | sed "/fe80\:\:1/d" | sed "/ff00\:\:/d" | sed "/ff02\:\:/d" | sed "/0\.0\.0\.0 0\.0\.0\.0/d" > ${WORKDIR}/blocklistads rm ${WORKDIR}/blocklistads-raw } blocklistfraud() { # Blocklist.site Fraud - ${FETCH} https://blocklist.site/app/dl/fraud -o ${WORKDIR}/blocklistfraud-raw - sed "/\.$/d" ${WORKDIR}/blocklistfraud-raw | sed "/^#/d" | sed "/\_/d" | sed "/^\s*$/d" | sed "/\.\./d" | sed "s/^\.//g" | sed "/localhost/d" | sed "/127\.0\.0\.1/d" | sed "/255\.255\.255\.255/d" | sed "/\:\:1/d" | sed "/fe80\:\:1/d" | sed "/ff00\:\:/d" | sed "/ff02\:\:/d" | sed "/0\.0\.0\.0 0\.0\.0\.0/d" > ${WORKDIR}/blocklistfraud + ${FETCH} https://blocklistproject.github.io/Lists/fraud.txt -o ${WORKDIR}/blocklistfraud-raw + sed "/\.$/d" ${WORKDIR}/blocklistfraud-raw | sed "/^#/d" | sed "/\_/d" | sed "/^[[:space:]]*$/d" | sed "/\.\./d" | sed "s/^\.//g" | sed "/localhost/d" | sed "/127\.0\.0\.1/d" | sed "/255\.255\.255\.255/d" | sed "/\:\:1/d" | sed "/fe80\:\:1/d" | sed "/ff00\:\:/d" | sed "/ff02\:\:/d" | sed "/0\.0\.0\.0 0\.0\.0\.0/d" > ${WORKDIR}/blocklistfraud rm ${WORKDIR}/blocklistfraud-raw } blocklistphishing() { # Blocklist.site Phishing - ${FETCH} https://blocklist.site/app/dl/phishing -o ${WORKDIR}/blocklistphishing-raw - sed "/\.$/d" ${WORKDIR}/blocklistphishing-raw | sed "/^#/d" | sed "/\_/d" | sed "/^\s*$/d" | sed "/\.\./d" | sed "s/^\.//g" | sed "/localhost/d" | sed "/127\.0\.0\.1/d" | sed "/255\.255\.255\.255/d" | sed "/\:\:1/d" | sed "/fe80\:\:1/d" | sed "/ff00\:\:/d" | sed "/ff02\:\:/d" | sed "/0\.0\.0\.0 0\.0\.0\.0/d" > ${WORKDIR}/blocklistphishing + ${FETCH} https://blocklistproject.github.io/Lists/phishing.txt -o ${WORKDIR}/blocklistphishing-raw + sed "/\.$/d" ${WORKDIR}/blocklistphishing-raw | sed "/^#/d" | sed "/\_/d" | sed "/^[[:space:]]*$/d" | sed "/\.\./d" | sed "s/^\.//g" | sed "/localhost/d" | sed "/127\.0\.0\.1/d" | sed "/255\.255\.255\.255/d" | sed "/\:\:1/d" | sed "/fe80\:\:1/d" | sed "/ff00\:\:/d" | sed "/ff02\:\:/d" | sed "/0\.0\.0\.0 0\.0\.0\.0/d" > ${WORKDIR}/blocklistphishing rm ${WORKDIR}/blocklistphishing-raw } hphosts-ads() { # hphosts-ads ${FETCH} https://hosts-file.net/ad_servers.txt -o ${WORKDIR}/hphosts-ads-raw - sed "/\.$/d" ${WORKDIR}/hphosts-ads-raw | sed "/^#/d" | sed "/\_/d" | sed "/^\s*$/d" | sed "/\.\./d" | sed "s/^\.//g" | sed "/localhost/d" | sed "/255\.255\.255\.255/d" | sed "/\:\:1/d" | sed "/fe80\:\:1/d" | sed "/ff00\:\:/d" | sed "/ff02\:\:/d" | sed "/0\.0\.0\.0 0\.0\.0\.0/d" | tr -d '\r' | awk 'BEGIN{FS=OFS=" ";}{print $2;}' > ${WORKDIR}/hphosts-ads + sed "/\.$/d" ${WORKDIR}/hphosts-ads-raw | sed "/^#/d" | sed "/\_/d" | sed "/^[[:space:]]*$/d" | sed "/\.\./d" | sed "s/^\.//g" | sed "/localhost/d" | sed "/255\.255\.255\.255/d" | sed "/\:\:1/d" | sed "/fe80\:\:1/d" | sed "/ff00\:\:/d" | sed "/ff02\:\:/d" | sed "/0\.0\.0\.0 0\.0\.0\.0/d" | tr -d '\r' | awk 'BEGIN{FS=OFS=" ";}{print $2;}' > ${WORKDIR}/hphosts-ads rm ${WORKDIR}/hphosts-ads-raw } hphosts-fsa() { # hphosts-fsa ${FETCH} https://hosts-file.net/fsa.txt -o ${WORKDIR}/hphosts-fsa-raw - sed "/\.$/d" ${WORKDIR}/hphosts-fsa-raw | sed "/^#/d" | sed "/\_/d" | sed "/^\s*$/d" | sed "/\.\./d" | sed "s/^\.//g" | sed "/localhost/d" | sed "/255\.255\.255\.255/d" | sed "/\:\:1/d" | sed "/fe80\:\:1/d" | sed "/ff00\:\:/d" | sed "/ff02\:\:/d" | sed "/0\.0\.0\.0 0\.0\.0\.0/d" | tr -d '\r' | awk 'BEGIN{FS=OFS=" ";}{print $2;}' > ${WORKDIR}/hphosts-fsa + sed "/\.$/d" ${WORKDIR}/hphosts-fsa-raw | sed "/^#/d" | sed "/\_/d" | sed "/^[[:space:]]*$/d" | sed "/\.\./d" | sed "s/^\.//g" | sed "/localhost/d" | sed "/255\.255\.255\.255/d" | sed "/\:\:1/d" | sed "/fe80\:\:1/d" | sed "/ff00\:\:/d" | sed "/ff02\:\:/d" | sed "/0\.0\.0\.0 0\.0\.0\.0/d" | tr -d '\r' | awk 'BEGIN{FS=OFS=" ";}{print $2;}' > ${WORKDIR}/hphosts-fsa rm ${WORKDIR}/hphosts-fsa-raw } hphosts-psh() { # hphosts-psh ${FETCH} https://hosts-file.net/psh.txt -o ${WORKDIR}/hphosts-psh-raw - sed "/\.$/d" ${WORKDIR}/hphosts-psh-raw | sed "/^#/d" | sed "/\_/d" | sed "/^\s*$/d" | sed "/\.\./d" | sed "s/^\.//g" | sed "/localhost/d" | sed "/255\.255\.255\.255/d" | sed "/\:\:1/d" | sed "/fe80\:\:1/d" | sed "/ff00\:\:/d" | sed "/ff02\:\:/d" | sed "/0\.0\.0\.0 0\.0\.0\.0/d" | tr -d '\r' | awk 'BEGIN{FS=OFS=" ";}{print $2;}' > ${WORKDIR}/hphosts-psh + sed "/\.$/d" ${WORKDIR}/hphosts-psh-raw | sed "/^#/d" | sed "/\_/d" | sed "/^[[:space:]]*$/d" | sed "/\.\./d" | sed "s/^\.//g" | sed "/localhost/d" | sed "/255\.255\.255\.255/d" | sed "/\:\:1/d" | sed "/fe80\:\:1/d" | sed "/ff00\:\:/d" | sed "/ff02\:\:/d" | sed "/0\.0\.0\.0 0\.0\.0\.0/d" | tr -d '\r' | awk 'BEGIN{FS=OFS=" ";}{print $2;}' > ${WORKDIR}/hphosts-psh rm ${WORKDIR}/hphosts-psh-raw } hphosts-pup() { # hphosts-pup ${FETCH} https://hosts-file.net/pup.txt -o ${WORKDIR}/hphosts-pup-raw - sed "/\.$/d" ${WORKDIR}/hphosts-pup-raw | sed "/^#/d" | sed "/\_/d" | sed "/^\s*$/d" | sed "/\.\./d" | sed "s/^\.//g" | sed "/localhost/d" | sed "/255\.255\.255\.255/d" | sed "/\:\:1/d" | sed "/fe80\:\:1/d" | sed "/ff00\:\:/d" | sed "/ff02\:\:/d" | sed "/0\.0\.0\.0 0\.0\.0\.0/d" | tr -d '\r' | awk 'BEGIN{FS=OFS=" ";}{print $2;}' > ${WORKDIR}/hphosts-pup + sed "/\.$/d" ${WORKDIR}/hphosts-pup-raw | sed "/^#/d" | sed "/\_/d" | sed "/^[[:space:]]*$/d" | sed "/\.\./d" | sed "s/^\.//g" | sed "/localhost/d" | sed "/255\.255\.255\.255/d" | sed "/\:\:1/d" | sed "/fe80\:\:1/d" | sed "/ff00\:\:/d" | sed "/ff02\:\:/d" | sed "/0\.0\.0\.0 0\.0\.0\.0/d" | tr -d '\r' | awk 'BEGIN{FS=OFS=" ";}{print $2;}' > ${WORKDIR}/hphosts-pup rm ${WORKDIR}/hphosts-pup-raw } simplead() { # Simple Ad List ${FETCH} https://s3.amazonaws.com/lists.disconnect.me/simple_ad.txt -o ${WORKDIR}/simplead-raw - sed "/\.$/d" ${WORKDIR}/simplead-raw | sed "/^#/d" | sed "/\_/d" | sed "/^\s*$/d" | sed "/\.\./d" | sed "s/^\.//g" > ${WORKDIR}/simplead + sed "/\.$/d" ${WORKDIR}/simplead-raw | sed "/^#/d" | sed "/\_/d" | sed "/^[[:space:]]*$/d" | sed "/\.\./d" | sed "s/^\.//g" > ${WORKDIR}/simplead rm ${WORKDIR}/simplead-raw } simpletrack() { # Simple Tracking List ${FETCH} https://s3.amazonaws.com/lists.disconnect.me/simple_tracking.txt -o ${WORKDIR}/simpletrack-raw - sed "/\.$/d" ${WORKDIR}/simpletrack-raw | sed "/^#/d" | sed "/\_/d" | sed "/^\s*$/d" | sed "/\.\./d" | sed "s/^\.//g" > ${WORKDIR}/simpletrack + sed "/\.$/d" ${WORKDIR}/simpletrack-raw | sed "/^#/d" | sed "/\_/d" | sed "/^[[:space:]]*$/d" | sed "/\.\./d" | sed "s/^\.//g" > ${WORKDIR}/simpletrack rm ${WORKDIR}/simpletrack-raw } diff --git a/dns/bind/src/opnsense/scripts/OPNsense/Bind/setup.sh b/dns/bind/src/opnsense/scripts/OPNsense/Bind/setup.sh index e46b77b4bd..64b2fd7083 100755 --- a/dns/bind/src/opnsense/scripts/OPNsense/Bind/setup.sh +++ b/dns/bind/src/opnsense/scripts/OPNsense/Bind/setup.sh @@ -1,17 +1,7 @@ #!/bin/sh -mkdir -p /var/run/named -chown -R bind:bind /var/run/named -chmod 755 /var/run/named - -mkdir -p /var/dump -chown -R bind:bind /var/dump -chmod 755 /var/dump - -mkdir -p /var/stats -chown -R bind:bind /var/stats -chmod 755 /var/stats - -mkdir -p /var/log/named -chown -R bind:bind /var/log/named -chmod 755 /var/log/named +for DIR in /var/run/named /var/dump /var/stats /var/log/named /usr/local/etc/namedb/primary; do + mkdir -p ${DIR} + chown -R bind:bind ${DIR} + chmod 755 ${DIR} +done diff --git a/dns/bind/src/opnsense/scripts/OPNsense/Bind/zoneCheck.sh b/dns/bind/src/opnsense/scripts/OPNsense/Bind/zoneCheck.sh new file mode 100755 index 0000000000..db3e85dac8 --- /dev/null +++ b/dns/bind/src/opnsense/scripts/OPNsense/Bind/zoneCheck.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +# check the primary zone file validity + +ZONENAME=${1} +ZONEPATH="/usr/local/etc/namedb/primary/${ZONENAME}.db" +if checkzone_errors=$(named-checkzone ${ZONENAME} ${ZONEPATH} 2>&1); then + echo "Zone check completed successfully" + echo "$checkzone_errors" +else + echo "$checkzone_errors" +fi + +exit 0 diff --git a/dns/bind/src/opnsense/scripts/OPNsense/Bind/zoneShow.py b/dns/bind/src/opnsense/scripts/OPNsense/Bind/zoneShow.py new file mode 100755 index 0000000000..38af91d4df --- /dev/null +++ b/dns/bind/src/opnsense/scripts/OPNsense/Bind/zoneShow.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 + +# send primary zone file content to stdout + +import sys +import os.path +import glob +import ujson + +zone_name = sys.argv[1] +result = dict() +zone_config = [] +zone_files_root = '/usr/local/etc/namedb/primary/' +zone_file = zone_files_root + zone_name + '.db' + +def load_db_file(zone_file): + """ load db-file + """ + for line in open(zone_file, 'r').read().split('\n'): + zone_config.append(line.rstrip()) + +if os.path.isfile(zone_file): + result['path'] = zone_file + result['time'] = os.path.getmtime(zone_file) + load_db_file(zone_file) + result['zone_content'] = zone_config +print(ujson.dumps(result)) diff --git a/dns/bind/src/opnsense/scripts/syslog/logformats/bind.py b/dns/bind/src/opnsense/scripts/syslog/logformats/bind.py new file mode 100755 index 0000000000..c4ef34892b --- /dev/null +++ b/dns/bind/src/opnsense/scripts/syslog/logformats/bind.py @@ -0,0 +1,112 @@ +""" + Copyright (c) 2022 Robbert Rijkse + All rights reserved. + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +""" +import datetime +import re +from . import NewBaseLogFormat + +class BindGeneralLogFormat(NewBaseLogFormat): + def __init__(self, filename): + super().__init__(filename) + self._priority = 1 + self._parts = list() + + def match(self, line): + return self._filename.find('named/named.log') > -1 + + def set_line(self, line): + super().set_line(line) + self._parts = self._line.split(maxsplit=4) + + @property + def timestamp(self): + # bind format return actual log data + ts = datetime.datetime.strptime(f"{self._parts[0]} {self._parts[1]}", "%d-%b-%Y %H:%M:%S.%f") + return ts.isoformat() + + @property + def severity(self): + # Grab the log level + severity = self._parts[3].strip(":") + options = { + "critical": 2, + "error": 3, + "warning": 4, + "notice": 5, + "info": 6, + "debug": 7, + "dynamic": 7 + } + if severity in options: + return options[severity] + return None + + @property + def process_name(self): + # Grab the type of log message + return self._parts[2].strip(":") + + @property + def line(self): + # Only grab the left over message + return self._parts[4].strip() + +class BindQueryLogFormat(NewBaseLogFormat): + def __init__(self, filename): + super().__init__(filename) + self._priority = 1 + self._parts = list() + + def match(self, line): + return self._filename.find('named/query.log') > -1 or self._filename.find('named/rpz.log') > -1 + + def set_line(self, line): + super().set_line(line) + self._parts = self._line.split(maxsplit=7) + + @property + def timestamp(self): + # bind format return actual log data + ts = datetime.datetime.strptime(f"{self._parts[0]} {self._parts[1]}", "%d-%b-%Y %H:%M:%S.%f") + return ts.isoformat() + + @property + def pid(self): + # Grab the IP and Port number of the client + # pid is used for this because you can't define custom names + return self._parts[4].strip() + + @property + def facility(self): + # Grab the record the query was for + # facility is used for this because you can't define custom names + return self._parts[5].strip()[1:-2] + + @property + def process_name(self): + # Grab the client memory ID + # process_name is used for this because you can't define custom names + return self._parts[3].strip() + + @property + def line(self): + # Only grab the left over message + return self._parts[7].strip() diff --git a/dns/bind/src/opnsense/service/conf/actions.d/actions_bind.conf b/dns/bind/src/opnsense/service/conf/actions.d/actions_bind.conf index e272a068fb..c3d4a47a5e 100644 --- a/dns/bind/src/opnsense/service/conf/actions.d/actions_bind.conf +++ b/dns/bind/src/opnsense/service/conf/actions.d/actions_bind.conf @@ -1,5 +1,5 @@ [start] -command:/usr/local/opnsense/scripts/OPNsense/Bind/setup.sh;/usr/local/etc/rc.d/named start +command:/usr/local/etc/rc.d/named start parameters: type:script message:starting BIND @@ -11,13 +11,13 @@ type:script message:stopping BIND [restart] -command:/usr/local/opnsense/scripts/OPNsense/Bind/setup.sh;/usr/local/etc/rc.d/named restart +command:/usr/local/etc/rc.d/named restart parameters: type:script message:restarting BIND [status] -command:/usr/local/etc/rc.d/named status;exit 0 +command:/usr/local/etc/rc.d/named status; exit 0 parameters: type:script_output message:request BIND status @@ -35,8 +35,18 @@ type:script message:fetching DNSBLs [dnsblcron] -command:/usr/local/opnsense/scripts/OPNsense/Bind/dnsbl.sh;/usr/local/etc/rc.d/named reload +command:/usr/local/opnsense/scripts/OPNsense/Bind/dnsbl.sh; /usr/local/etc/rc.d/named reload parameters: type:script message:fetching DNSBLs and restart description: Download BIND DNSBLs and restart + +[zone.check] +command:/usr/local/opnsense/scripts/OPNsense/Bind/zoneCheck.sh +parameters: %s +type:script_output + +[zone.show] +command:/usr/local/opnsense/scripts/OPNsense/Bind/zoneShow.py +parameters: %s +type:script_output diff --git a/dns/bind/src/opnsense/service/templates/OPNsense/Bind/+TARGETS b/dns/bind/src/opnsense/service/templates/OPNsense/Bind/+TARGETS index 33d000bba7..1e155e5a5b 100644 --- a/dns/bind/src/opnsense/service/templates/OPNsense/Bind/+TARGETS +++ b/dns/bind/src/opnsense/service/templates/OPNsense/Bind/+TARGETS @@ -1,11 +1,11 @@ -bing.db:/usr/local/etc/namedb/master/bing.db -blacklist.db:/usr/local/etc/namedb/master/blacklist.db -domain.db:/usr/local/etc/namedb/master/[OPNsense.bind.domain.domains.domain.%.domainname].db -duckduckgo.db:/usr/local/etc/namedb/master/duckduckgo.db -google.db:/usr/local/etc/namedb/master/google.db +bing.db:/usr/local/etc/namedb/primary/bing.db +blacklist.db:/usr/local/etc/namedb/primary/blacklist.db +domain.db:/usr/local/etc/namedb/primary/[OPNsense.bind.domain.domains.domain.%.domainname].db +duckduckgo.db:/usr/local/etc/namedb/primary/duckduckgo.db +google.db:/usr/local/etc/namedb/primary/google.db named:/etc/rc.conf.d/named named.conf:/usr/local/etc/namedb/named.conf rndc.conf:/usr/local/etc/namedb/rndc.conf -whitelist.db:/usr/local/etc/namedb/master/whitelist.db +whitelist.db:/usr/local/etc/namedb/primary/whitelist.db whitelist.inc:/usr/local/etc/namedb/whitelist.inc -youtube.db:/usr/local/etc/namedb/master/youtube.db +youtube.db:/usr/local/etc/namedb/primary/youtube.db diff --git a/dns/bind/src/opnsense/service/templates/OPNsense/Bind/domain.db b/dns/bind/src/opnsense/service/templates/OPNsense/Bind/domain.db index d24ecd3ee5..bce3a9ffdd 100644 --- a/dns/bind/src/opnsense/service/templates/OPNsense/Bind/domain.db +++ b/dns/bind/src/opnsense/service/templates/OPNsense/Bind/domain.db @@ -2,14 +2,16 @@ {% if helpers.exists('OPNsense.bind.domain.domains.domain') %} {% for domaindb in helpers.toList('OPNsense.bind.domain.domains.domain') %} {% if TARGET_FILTERS['OPNsense.bind.domain.domains.domain.' ~ loop.index0] or TARGET_FILTERS['OPNsense.bind.domain.domains.domain'] %} -{% if domaindb.enabled == '1' and domaindb.type == 'master' %} +{% if domaindb.enabled == '1' and domaindb.type == 'primary' %} $TTL {{ domaindb.ttl }} @ IN SOA {{ domaindb.dnsserver }}. {{ domaindb.mailadmin|replace('@', '.') }}. ( {{ domaindb.serial|trim }} {{ domaindb.refresh }} {{ domaindb.retry }} {{ domaindb.expire }} {{ domaindb.negative }} ) -{% for record in helpers.sortDictList(OPNsense.bind.record.records.record, 'name', 'type' ) %} -{% if record.domain == domaindb['@uuid'] %} +{% if helpers.exists('OPNsense.bind.record.records.record') %} +{% for record in helpers.sortDictList(OPNsense.bind.record.records.record, 'name', 'type' ) %} +{% if record.domain == domaindb['@uuid'] %} {{ record.name }} {{ record.type }} {{ record.value }} -{% endif %} -{% endfor %} +{% endif %} +{% endfor %} +{% endif %} {% endif %} {% endif %} {% endfor %} diff --git a/dns/bind/src/opnsense/service/templates/OPNsense/Bind/named b/dns/bind/src/opnsense/service/templates/OPNsense/Bind/named index 1a4c02a000..34d82fcae3 100644 --- a/dns/bind/src/opnsense/service/templates/OPNsense/Bind/named +++ b/dns/bind/src/opnsense/service/templates/OPNsense/Bind/named @@ -1,5 +1,4 @@ {% if helpers.exists('OPNsense.bind.general.enabled') and OPNsense.bind.general.enabled == '1' %} -named_var_script="/usr/local/opnsense/scripts/OPNsense/Bind/setup.sh" {% if helpers.exists('OPNsense.bind.general.disablev6') and OPNsense.bind.general.disablev6 == '1' %} named_flags="-4" {% endif %} @@ -8,6 +7,7 @@ named_flags="-4" named_dnsbl="{{ OPNsense.bind.dnsbl.type }}" {% endif %} {% endif %} +named_setup="/usr/local/opnsense/scripts/OPNsense/Bind/setup.sh" named_enable="YES" {% else %} named_enable="NO" diff --git a/dns/bind/src/opnsense/service/templates/OPNsense/Bind/named.conf b/dns/bind/src/opnsense/service/templates/OPNsense/Bind/named.conf index 1a8de537af..9196b5de3e 100644 --- a/dns/bind/src/opnsense/service/templates/OPNsense/Bind/named.conf +++ b/dns/bind/src/opnsense/service/templates/OPNsense/Bind/named.conf @@ -15,27 +15,63 @@ options { dump-file "/var/dump/named_dump.db"; statistics-file "/var/stats/named.stats"; -{% if helpers.exists('OPNsense.bind.general.listenv4') and OPNsense.bind.general.listenv4 != '' %} {% if helpers.exists('OPNsense.bind.general.port') and OPNsense.bind.general.port != '' %} - listen-on port {{ OPNsense.bind.general.port }} { {{ OPNsense.bind.general.listenv4.replace(',', '; ') }}; }; -{% endif %}{% endif %} -{% if helpers.exists('OPNsense.bind.general.listenv6') and OPNsense.bind.general.listenv6 != '' %} {% if helpers.exists('OPNsense.bind.general.port') and OPNsense.bind.general.port != '' %} - listen-on-v6 port {{ OPNsense.bind.general.port }} { {{ OPNsense.bind.general.listenv6.replace(',', '; ') }}; }; -{% endif %}{% endif %} +{% for listenv4 in OPNsense.bind.general.listenv4.split(',') %} + listen-on port {{ OPNsense.bind.general.port }} { {% if listenv4 == '0.0.0.0' %}any{% else %}{{ listenv4 }}{% endif %}; }; +{% endfor %} +{% for listenv6 in OPNsense.bind.general.listenv6.split(',') %} + listen-on-v6 port {{ OPNsense.bind.general.port }} { {% if listenv6 == '::' %}any{% else %}{{ listenv6 }}{% endif %}; }; +{% endfor %} + +{% if helpers.exists('OPNsense.bind.general.querysource') and OPNsense.bind.general.querysource != '' %} + query-source {{ OPNsense.bind.general.querysource }}; +{% endif -%} + +{% if helpers.exists('OPNsense.bind.general.querysourcev6') and OPNsense.bind.general.querysourcev6 != '' %} + query-source-v6 {{ OPNsense.bind.general.querysourcev6 }}; +{% endif -%} + +{% if helpers.exists('OPNsense.bind.general.transfersource') and OPNsense.bind.general.transfersource != '' %} + transfer-source {{ OPNsense.bind.general.transfersource }}; +{% endif -%} + +{% if helpers.exists('OPNsense.bind.general.transfersourcev6') and OPNsense.bind.general.transfersourcev6 != '' %} + transfer-source-v6 {{ OPNsense.bind.general.transfersourcev6 }}; +{% endif -%} {% if helpers.exists('OPNsense.bind.general.forwarders') and OPNsense.bind.general.forwarders != '' %} forwarders { {{ OPNsense.bind.general.forwarders.replace(',', '; ') }}; }; -{% endif %} +{% endif -%} {% if helpers.exists('OPNsense.bind.dnsbl.enabled') and OPNsense.bind.dnsbl.enabled == '1' %} response-policy { {% if helpers.exists('OPNsense.bind.dnsbl.type') and OPNsense.bind.dnsbl.type != '' %}zone "whitelist.localdomain"; zone "blacklist.localdomain";{% endif %}{% if helpers.exists('OPNsense.bind.dnsbl.forcesafegoogle') and OPNsense.bind.dnsbl.forcesafegoogle == '1' %}zone "rpzgoogle";{% endif %}{% if helpers.exists('OPNsense.bind.dnsbl.forcesafeduckduckgo') and OPNsense.bind.dnsbl.forcesafeduckduckgo == '1' %}zone "rpzduckduckgo";{% endif %}{% if helpers.exists('OPNsense.bind.dnsbl.forcesafeyoutube') and OPNsense.bind.dnsbl.forcesafeyoutube == '1' %}zone "rpzyoutube";{% endif %}{% if helpers.exists('OPNsense.bind.dnsbl.forcestrictbing') and OPNsense.bind.dnsbl.forcestrictbing == '1' %}zone "rpzbing";{% endif %} }; {% endif %} {% if helpers.exists('OPNsense.bind.general.recursion') and OPNsense.bind.general.recursion != '' %} -{% for list in helpers.toList('OPNsense.bind.general.recursion') %} -{% set recursionlist = helpers.getUUID(list) %} recursion yes; - allow-recursion { {{ recursionlist.name }}; }; -{% endfor %} + allow-recursion { +{% for acl in OPNsense.bind.general.recursion.split(',') %} +{% set recursion_acl = helpers.getUUID(acl) %} + {{ recursion_acl.name }}; +{% endfor %} + }; +{% endif %} + +{% if helpers.exists('OPNsense.bind.general.allowtransfer') and OPNsense.bind.general.allowtransfer != '' %} + allow-transfer { +{% for acl in OPNsense.bind.general.allowtransfer.split(',') %} +{% set transfer_acl = helpers.getUUID(acl) %} + {{ transfer_acl.name }}; +{% endfor %} + }; +{% endif %} + +{% if helpers.exists('OPNsense.bind.general.allowquery') and OPNsense.bind.general.allowquery != '' %} + allow-query { +{% for acl in OPNsense.bind.general.allowquery.split(',') %} +{% set query_acl = helpers.getUUID(acl) %} + {{ query_acl.name }}; +{% endfor %} + }; {% endif %} {% if helpers.exists('OPNsense.bind.general.maxcachesize') and OPNsense.bind.general.maxcachesize != '' %} @@ -65,54 +101,107 @@ options { {% endif %} }; +{% if helpers.exists('OPNsense.bind.general.rndcalgo') and helpers.exists('OPNsense.bind.general.rndcsecret') %} key "rndc-key" { - algorithm hmac-sha256; - secret "VxtIzJevSQXqnr7h2qerrcwjnZlMWSGGFBndKeNIDfw="; + algorithm "{{ OPNsense.bind.general.rndcalgo }}"; + secret "{{ OPNsense.bind.general.rndcsecret }}"; }; controls { inet 127.0.0.1 port 9530 allow { 127.0.0.1; } keys { "rndc-key"; }; }; +{% endif %} + +include "/usr/local/etc/namedb/named.conf.d/*.conf"; zone "." { type hint; file "/usr/local/etc/namedb/named.root"; }; -zone "localhost" { type master; file "/usr/local/etc/namedb/master/localhost-forward.db"; }; -zone "127.in-addr.arpa" { type master; file "/usr/local/etc/namedb/master/localhost-reverse.db"; }; -zone "0.ip6.arpa" { type master; file "/usr/local/etc/namedb/master/localhost-reverse.db"; }; +zone "localhost" { type primary; file "/usr/local/etc/namedb/primary/localhost-forward.db"; }; +zone "127.in-addr.arpa" { type primary; file "/usr/local/etc/namedb/primary/localhost-reverse.db"; }; +zone "0.ip6.arpa" { type primary; file "/usr/local/etc/namedb/primary/localhost-reverse.db"; }; {% if helpers.exists('OPNsense.bind.dnsbl.enabled') and OPNsense.bind.dnsbl.enabled == '1' %} {% if helpers.exists('OPNsense.bind.dnsbl.type') and OPNsense.bind.dnsbl.type != '' %} -zone "whitelist.localdomain" { type master; file "/usr/local/etc/namedb/master/whitelist.db"; notify no; check-names ignore; }; -zone "blacklist.localdomain" { type master; file "/usr/local/etc/namedb/master/blacklist.db"; notify no; check-names ignore; }; +zone "whitelist.localdomain" { type primary; file "/usr/local/etc/namedb/primary/whitelist.db"; notify no; check-names ignore; }; +zone "blacklist.localdomain" { type primary; file "/usr/local/etc/namedb/primary/blacklist.db"; notify no; check-names ignore; }; {% endif %} {% endif %} {% if helpers.exists('OPNsense.bind.dnsbl.enabled') and OPNsense.bind.dnsbl.enabled == '1' %} {% if helpers.exists('OPNsense.bind.dnsbl.forcesafegoogle') and OPNsense.bind.dnsbl.forcesafegoogle == '1' %} -zone "rpzgoogle" { type master; file "/usr/local/etc/namedb/master/google.db"; notify no; check-names ignore; }; +zone "rpzgoogle" { type primary; file "/usr/local/etc/namedb/primary/google.db"; notify no; check-names ignore; }; {% endif %} {% endif %} {% if helpers.exists('OPNsense.bind.dnsbl.enabled') and OPNsense.bind.dnsbl.enabled == '1' %} {% if helpers.exists('OPNsense.bind.dnsbl.forcesafeduckduckgo') and OPNsense.bind.dnsbl.forcesafeduckduckgo == '1' %} -zone "rpzduckduckgo" { type master; file "/usr/local/etc/namedb/master/duckduckgo.db"; notify no; check-names ignore; }; +zone "rpzduckduckgo" { type primary; file "/usr/local/etc/namedb/primary/duckduckgo.db"; notify no; check-names ignore; }; {% endif %} {% endif %} {% if helpers.exists('OPNsense.bind.dnsbl.enabled') and OPNsense.bind.dnsbl.enabled == '1' %} {% if helpers.exists('OPNsense.bind.dnsbl.forcesafeyoutube') and OPNsense.bind.dnsbl.forcesafeyoutube == '1' %} -zone "rpzyoutube" { type master; file "/usr/local/etc/namedb/master/youtube.db"; notify no; check-names ignore; }; +zone "rpzyoutube" { type primary; file "/usr/local/etc/namedb/primary/youtube.db"; notify no; check-names ignore; }; {% endif %} {% endif %} {% if helpers.exists('OPNsense.bind.dnsbl.enabled') and OPNsense.bind.dnsbl.enabled == '1' %} {% if helpers.exists('OPNsense.bind.dnsbl.forcestrictbing') and OPNsense.bind.dnsbl.forcestrictbing == '1' %} -zone "rpzbing" { type master; file "/usr/local/etc/namedb/master/bing.db"; notify no; check-names ignore; }; +zone "rpzbing" { type primary; file "/usr/local/etc/namedb/primary/bing.db"; notify no; check-names ignore; }; {% endif %} {% endif %} {% if helpers.exists('OPNsense.bind.domain.domains.domain') %} +{% set usedkeys = [] %} {% for domain in helpers.toList('OPNsense.bind.domain.domains.domain') %} {% if domain.enabled == '1' %} -{% set allow_transfer = helpers.getUUID(domain.allowtransfer) %} -{% set allow_query = helpers.getUUID(domain.allowquery) %} -zone "{{ domain.domainname }}" { type {{ domain.type }}; {% if domain.type == 'slave' %}masters { {{ domain.masterip }}; }; {% if domain.allownotifyslave != '' %} allow-notify { {{ domain.allownotifyslave.replace(',', '; ') }}; };{% endif %} file "/usr/local/etc/namedb/slave/{{ domain.domainname }}.db"; {% else %}file "/usr/local/etc/namedb/master/{{ domain.domainname }}.db"; {% endif %}{% if domain.allowtransfer is defined %} allow-transfer { {{ allow_transfer.name }}; };{% endif %}{% if domain.allowquery is defined %} allow-query { {{ allow_query.name }}; };{% endif %} }; +zone "{{ domain.domainname }}" { + type {{ domain.type }}; +{% if domain.type == 'forward' %} + forwarders { {{ domain.forwardserver.replace(',', '; ') }}; }; +{% elif domain.type == 'secondary' %} +{% if domain.transferkey is defined %} + primaries { {{ domain.primaryip.replace(',', ' key "' ~ domain.transferkeyname ~ '"; ') }} key "{{ domain.transferkeyname }}"; }; +{% else %} + primaries { {{ domain.primaryip.replace(',', '; ') }}; }; +{% endif %} +{% if domain.allownotifysecondary is defined %} + allow-notify { {{ domain.allownotifysecondary.replace(',', '; ') }}; }; +{% endif %} + file "/usr/local/etc/namedb/secondary/{{ domain.domainname }}.db"; +{% elif domain.type == 'primary' %} + file "/usr/local/etc/namedb/primary/{{ domain.domainname }}.db"; +{% endif %} +{% if domain.allowtransfer is defined or (domain.allowrndctransfer is defined and domain.allowrndctransfer == "1") %} + allow-transfer { +{% if domain.allowrndctransfer is defined and domain.allowrndctransfer == "1" %} + key "rndc-key"; +{% endif %} +{% if domain.allowtransfer is defined %} +{% for acl in domain.allowtransfer.split(',') %} +{% set transfer_acl = helpers.getUUID(acl) %} + {{ transfer_acl.name }}; +{% endfor %} +{% endif %} + }; +{% endif %} +{% if domain.allowquery is defined %} + allow-query { +{% for acl in domain.allowquery.split(',') %} +{% set query_acl = helpers.getUUID(acl) %} + {{ query_acl.name }}; +{% endfor %} + }; +{% endif %} +{% if domain.allowrndcupdate is defined and domain.allowrndcupdate == "1" and domain.type == 'primary' %} + update-policy { + grant rndc-key zonesub ANY; + }; +{% endif %} +}; +{% if domain.type == 'secondary' and domain.transferkey is defined and not(domain.transferkeyname in usedkeys) %} +{% do usedkeys.append(domain.transferkeyname) %} +key "{{ domain.transferkeyname }}" { + algorithm "{{ domain.transferkeyalgo }}"; + secret "{{ domain.transferkey }}"; +}; +{% endif %} {% endif %} {% endfor %} {% endif %} @@ -126,6 +215,14 @@ logging { print-category yes; }; + channel default_syslog { + print-time yes; + print-category yes; + print-severity yes; + syslog daemon; + severity {% if helpers.exists('OPNsense.bind.general.general_log_level') %}{{ OPNsense.bind.general.general_log_level }}{% else %}info{% endif %}; + }; + channel query_log { file "/var/log/named/query.log" versions 3 size {{ OPNsense.bind.general.logsize }}m; print-time yes; @@ -136,10 +233,35 @@ logging { print-time yes; }; - category default { default_log; }; - category general { default_log; }; + category default { default_syslog; }; + category general { default_syslog; }; + category config { default_syslog; }; + category dispatch { default_syslog; }; + category network { default_syslog; }; category queries { query_log; }; category rpz { rpz_log; }; category lame-servers { null; }; }; {% endif %} + +{% if helpers.exists('OPNsense.bind.general.filteraaaav4') and OPNsense.bind.general.filteraaaav4 == '1' or helpers.exists('OPNsense.bind.general.filteraaaav6') and OPNsense.bind.general.filteraaaav6 == '1' %} +plugin query "/usr/local/lib/bind/filter-aaaa.so" { +{% if helpers.exists('OPNsense.bind.general.filteraaaav4') and OPNsense.bind.general.filteraaaav4 == '1' %} +{% if OPNsense.bind.general.dnssecvalidation == 'no' %} + filter-aaaa-on-v4 break-dnssec; +{% else %} + filter-aaaa-on-v4 yes; +{% endif %} +{% endif %} +{% if helpers.exists('OPNsense.bind.general.filteraaaav6') and OPNsense.bind.general.filteraaaav6 == '1' %} +{% if OPNsense.bind.general.dnssecvalidation == 'no' %} + filter-aaaa-on-v6 break-dnssec; +{% else %} + filter-aaaa-on-v6 yes; +{% endif %} +{% endif %} +{% if helpers.exists('OPNsense.bind.general.filteraaaaacl') and OPNsense.bind.general.filteraaaaacl != '' %} + filter-aaaa { {{ OPNsense.bind.general.filteraaaaacl.replace(',', '; ') }}; }; +{% endif %} + }; +{% endif %} diff --git a/dns/bind/src/opnsense/service/templates/OPNsense/Bind/rndc.conf b/dns/bind/src/opnsense/service/templates/OPNsense/Bind/rndc.conf index ec98967398..d4800520f1 100644 --- a/dns/bind/src/opnsense/service/templates/OPNsense/Bind/rndc.conf +++ b/dns/bind/src/opnsense/service/templates/OPNsense/Bind/rndc.conf @@ -1,6 +1,7 @@ +{% if helpers.exists('OPNsense.bind.general.rndcalgo') and helpers.exists('OPNsense.bind.general.rndcsecret') %} key "rndc-key" { - algorithm hmac-sha256; - secret "VxtIzJevSQXqnr7h2qerrcwjnZlMWSGGFBndKeNIDfw="; + algorithm "{{ OPNsense.bind.general.rndcalgo }}"; + secret "{{ OPNsense.bind.general.rndcsecret }}"; }; options { @@ -8,3 +9,4 @@ options { default-server 127.0.0.1; default-port 9530; }; +{% endif %} diff --git a/dns/bind/src/opnsense/service/templates/OPNsense/Syslog/local/named.conf b/dns/bind/src/opnsense/service/templates/OPNsense/Syslog/local/named.conf new file mode 100644 index 0000000000..d301237e4a --- /dev/null +++ b/dns/bind/src/opnsense/service/templates/OPNsense/Syslog/local/named.conf @@ -0,0 +1,6 @@ +################################################################### +# Local syslog-ng configuration filter definition [named]. +################################################################### +filter f_local_named { + program("named"); +}; diff --git a/dns/ddclient/Makefile b/dns/ddclient/Makefile new file mode 100644 index 0000000000..b4e1a3584f --- /dev/null +++ b/dns/ddclient/Makefile @@ -0,0 +1,8 @@ +PLUGIN_NAME= ddclient +PLUGIN_VERSION= 1.30 +PLUGIN_REVISION= 3 +PLUGIN_DEPENDS= ddclient py${PLUGIN_PYTHON}-boto3 +PLUGIN_COMMENT= Dynamic DNS client +PLUGIN_MAINTAINER= ad@opnsense.org + +.include "../../Mk/plugins.mk" diff --git a/dns/ddclient/pkg-descr b/dns/ddclient/pkg-descr new file mode 100644 index 0000000000..4fb318ec9f --- /dev/null +++ b/dns/ddclient/pkg-descr @@ -0,0 +1,198 @@ +This plugin offers dynamic DNS capabilities using a native backend +or ddclient. The native backend is the default implementation. +ddclient is a Perl client used to update dynamic DNS entries for +accounts on many dynamic DNS services. + +Plugin Changelog +================ + +1.30 + +* Add native backend support for Hostinger (contributed by Leandro Scardua) +* Fix Hetzner existing record update (contributed by Julian Nikodemus) +* Fix PowerDNS URL validation + +1.29 + +* Add native backend support for Hetzner DNS (contributed by Michael J. Arcan) +* Add native backend support for dnspod.cn (contributed by Ansen) +* Add Cloudflare DNS IP check option (contributed by GTechAlpha) + +1.28 + +* Add native backend support for PowerDNS API (contributed by Oliver Traber) + +1.27 + +* Add support for altering IPv6 addresses in native backend (contributed by SaarLAN-Pissbeutel) +* Add Akamai to checkip providers (contributed by Rajiv Aaron Manglani) +* Fix Netcup host/domain recognition (contributed by SaarLAN-Pissbeutel) +* Empty IP send to DNS provider and replace dyndns by dynu (contributed by Meliox) +* Removed defunct ip4only.me and ip6only.me + +1.26 + +* Add ddclient TTL configuration in Gandi and GoDaddy (contributed by David PHAM-VAN) + +1.25 + +* Add DigitalOcean support to native backend (contributed by Olly Baker) + +1.24 + +* Refactored IP matching (contributed by Rob van Oostenrijk) + +1.23 + +* Add dashboard widget + +1.22 + +* Add gandi support +* Optionally support descriptive values for account selection when using native backend + +1.21 + +* Add Netcup support (contributed by Ingo Lafrenz) +* Use '==' instead of 'is' in Domeneshop Python support (contributed by ssmendon) +* Update DNS record instead of overwriting in Cloudflare Python support (contributed by lin-xianming) +* Improve service information fetch by avoiding creation of a model + +1.20 + +* Add system parameter to native dyndns2 requesto (contributed by Jakub Gargul) +* Add Digitalocean support (contributed by Mathias Schneuwly) +* Add Mythin Beasts support (contributed by PeterF) + +1.19 + +* Add Porkbun support (contributed by briandur) +* Add native service for Domeneshop (contributed by Bernhard Frenking) + +1.18 + +* Update to ddclient 3.11.2 FreeBSD ports version +* Default to native backend for new installs +* Fix permission of ddclient.json + +1.17 + +* Update to ddclient 3.11.1 (dnsexit legacy support removed) + +1.16 + +* Add custom GET/PUT protocols to native backend (contributed by DaCookie4u) +* Consider all 2xx status codes as success in native dyndns2 implementation + +1.15 + +* Add AWS Route53 and DuckDNS to native backend (contributed by Greg Glockner) +* Fix JSON output with disabled trailing accounts/escaping and empty stats in AccountField +* Rename Python-based "OPNsense" backend to "native" to prevent ambiguity +* Do not update on native backend when IP detection failed and emit a warning instead +* Add desec to native backend (contributed by Clemens Hardewig) +* Fix ClouDNS missing dynurl= parameter +* Clean up ddclient.conf template + +1.14 + +* Add "post" protocol in custom service type +* Add DNSExit API and regfish.de support to ddclient backend + +1.13 + +* Fix not returning IP address as a string in native backend (contributed by Sean Kelly) +* Fix PID file handling for native backend +* Use API token for cloudflare native backend if available (contributed by juantxorena) +* Read proxied attribute from cloudflare hostname and send it back to prevent it being removed +* Move accounting of "last accessed timestamp" to poller in native backend +* Change if= use to proper ifv4=/ifv6= use (contributed by Rhys Barrie) + +1.12 + +* Add cloudflare implementation for native backend (contributed by Thomas Cekal) +* Allow custom target hostname for dyndns2 protocol in native backend +* Adjust for missing ipv6= option including upstream patches for use=/usev4=/usev6= +* Require a selected interface through validation when interface check method is used + +1.11 + +* Add Python-based native backend support for custom ddclient-like implementation using the same input +* Add AzureDNS backende using OAuth 2.0 +* Add dyndns2 backend using said API + +1.10 + +* Update to ddclient 3.10.0 +* Add 1984 support (contributed by Luca Schoeneberg) +* Add ClouDNS support (contributed by Luca Schoeneberg) +* Add Dinahosting support (contributed by Luca Schoeneberg) +* Add DNSExit support (contributed by Luca Schoeneberg) +* Add DonDominio support (contributed by Luca Schoeneberg) +* Add Freemyip support (contributed by Luca Schoeneberg) +* Add godaddy support (contributed by Luca Schoeneberg) +* Add Hetzner support (contributed by Luca Schoeneberg) +* Add Key-Systems support (contributed by Luca Schoeneberg) +* Add NearlyFreeSpeech.NET support (contributed by Luca Schoeneberg) +* Add Njal.la support (contributed by satrapes) +* Add sitelutions support (contributed by Luca Schoeneberg) +* Add woima support (contributed by Luca Schoeneberg) +* Add Yandex support (contributed by Luca Schoeneberg) + +1.9 + +* Add icanhazip.com as a checkip provider (contributed by Matt Parnell) +* Configurable checkip (contributed by Christian Schulze) +* Allow % characters in usernames +* Fix parsing short IPv6 addresses from external service (contributed by Patrick Grupp) + +1.8 + +* Add a force action available via cron +* Fix expected permission on ddclient.conf +* Make service status and stop more reliable +* Time out checkip script after 10 seconds + +1.7 + +* Add current ip address and updated timestamp to search api and grid + +1.6 + +* Add Gandi support (contributed by Neozlag) + +1.5 + +* Add service control, XMLRPC registration and syslog target +* Add Servercow support (contributed by FreddleSpl0it) + +1.4 + +* Add advanced general setting to allow updates via IPv6 +* Enforce SSL on global level with account setting + +1.3 + +* Add checkip settings per account using selected source interface when provided +* Add OVH DynHost to the DynDNS providers (contributed by toxic0berliner) + +1.2 + +* Add Loopia (contributed by Johan Lilja) +* Add DNS Made Easy, FreeDNS and Dynu (contributed by Rene Schuster) +* Add root zone and wildcard support + +1.1 + +* Add spdyn, inwx and dns-o-matic (contributed by Rene Schuster) +* Add Hurricane Electric provider (contributed by Netboy3) +* Add option to force SSL, on by default (contributed by Robin Mueller) +* Add Cloudflare and custom service (contributed by Robin Mueller) +* Add STRATO provider (contributed by Alex Mi) +* Add use interface as IP source +* Fix ip6only.me (contributed by Robin Mueller) +* Fix uppercase use in usernames + +1.0 + +* Initial release diff --git a/dns/ddclient/src/etc/inc/plugins.inc.d/ddclient.inc b/dns/ddclient/src/etc/inc/plugins.inc.d/ddclient.inc new file mode 100644 index 0000000000..e41cb18354 --- /dev/null +++ b/dns/ddclient/src/etc/inc/plugins.inc.d/ddclient.inc @@ -0,0 +1,79 @@ +object(); + $is_enabled = false; + if ($cnf->OPNsense && $cnf->OPNsense->DynDNS && $cnf->OPNsense->DynDNS->general) { + $is_enabled = $cnf->OPNsense->DynDNS->general->enabled == '1'; + } + + if ($is_enabled) { + $service = [ + 'description' => gettext('ddclient'), + 'configd' => [ + 'restart' => ['ddclient restart'], + 'start' => ['ddclient start'], + 'stop' => ['ddclient stop'], + ], + 'name' => 'ddclient', + ]; + $service['pidfile'] = $cnf->OPNsense->DynDNS->general->backend != 'opnsense' ? '/var/run/ddclient.pid' : '/var/run/ddclient_opn.pid'; + $services[] = $service; + } + + return $services; +} + +function ddclient_xmlrpc_sync() +{ + $result = []; + + $result[] = [ + 'description' => gettext('ddclient'), + 'section' => 'OPNsense.DynDNS', + 'services' => ['ddclient'], + 'id' => 'ddclient', + ]; + + return $result; +} + +function ddclient_syslog() +{ + $logfacilities = []; + + $logfacilities['ddclient'] = [ + 'facility' => ['ddclient'], + ]; + + return $logfacilities; +} diff --git a/dns/ddclient/src/etc/rc.d/ddclient_opn b/dns/ddclient/src/etc/rc.d/ddclient_opn new file mode 100755 index 0000000000..791fbcb682 --- /dev/null +++ b/dns/ddclient/src/etc/rc.d/ddclient_opn @@ -0,0 +1,61 @@ +#!/bin/sh +# +# PROVIDE: ddclient_py +# REQUIRE: SERVERS +# KEYWORD: shutdown +# + +. /etc/rc.subr + +name=ddclient_opn +rcvar=ddclient_opn_enable +command=/usr/local/opnsense/scripts/ddclient/ddclient_opn.py +command_interpreter=/usr/local/bin/python3 +pidfile="/var/run/${name}.pid" +load_rc_config $name + +# Set defaults +: ${ddclient_opn_enable:=NO} + +start_postcmd=ddclient_opn_poststart +stop_cmd=ddclient_opn_stop + +ddclient_opn_poststart() +{ + # give the daemon some time to initialize its configuration + for i in 1 2 3 4 5; do + sleep 1 + + if [ -s ${rc_pid} ]; then + break + fi + done +} + +ddclient_opn_stop() +{ + if [ -z "$rc_pid" ]; then + [ -n "$rc_fast" ] && return 0 + _run_rc_notrunning + return 1 + fi + echo -n "Stopping ${name}." + kill -15 ${rc_pid} + # wait max 2 seconds for gentle exit + for i in $(seq 1 20); + do + if [ -z "`/bin/ps -ex | /usr/bin/awk '{print $1;}' | /usr/bin/grep "^${rc_pid}"`" ]; then + break + fi + sleep 0.1 + done + + for ddclient_pid in `/bin/ps -ex | grep 'ddclient_opn.py' | /usr/bin/awk '{print $1;}' ` + do + kill -9 $ddclient_pid >/dev/null 2>&1 + done + + echo "..done" +} + +run_rc_command $1 diff --git a/dns/ddclient/src/opnsense/mvc/app/controllers/OPNsense/DynDNS/Api/AccountsController.php b/dns/ddclient/src/opnsense/mvc/app/controllers/OPNsense/DynDNS/Api/AccountsController.php new file mode 100644 index 0000000000..f15dab90c9 --- /dev/null +++ b/dns/ddclient/src/opnsense/mvc/app/controllers/OPNsense/DynDNS/Api/AccountsController.php @@ -0,0 +1,84 @@ +searchBase( + "accounts.account", + [ + 'enabled', 'service', 'description', 'username', 'hostnames', 'use_interface', + 'interface', 'protocol', 'current_ip', 'current_mtime' + ], + "description" + ); + foreach ($result['rows'] as &$row) { + if ($row['service'] == 'Custom') { + $row['service'] = 'Custom (' . $row['protocol'] . ')'; + } + unset($row['protocol']); + } + return $result; + } + + public function setItemAction($uuid) + { + return $this->setBase("account", "accounts.account", $uuid); + } + + public function addItemAction() + { + return $this->addBase("account", "accounts.account"); + } + + public function getItemAction($uuid = null) + { + return $this->getBase("account", "accounts.account", $uuid); + } + + public function delItemAction($uuid) + { + return $this->delBase("accounts.account", $uuid); + } + + public function toggleItemAction($uuid, $enabled = null) + { + return $this->toggleBase("accounts.account", $uuid, $enabled); + } +} diff --git a/dns/ddclient/src/opnsense/mvc/app/controllers/OPNsense/DynDNS/Api/ServiceController.php b/dns/ddclient/src/opnsense/mvc/app/controllers/OPNsense/DynDNS/Api/ServiceController.php new file mode 100644 index 0000000000..1804fb3d82 --- /dev/null +++ b/dns/ddclient/src/opnsense/mvc/app/controllers/OPNsense/DynDNS/Api/ServiceController.php @@ -0,0 +1,44 @@ + [ + 'general' => $data['ddclient']['general'] + ] + ]; + } +} diff --git a/dns/ddclient/src/opnsense/mvc/app/controllers/OPNsense/DynDNS/IndexController.php b/dns/ddclient/src/opnsense/mvc/app/controllers/OPNsense/DynDNS/IndexController.php new file mode 100644 index 0000000000..4214f012ff --- /dev/null +++ b/dns/ddclient/src/opnsense/mvc/app/controllers/OPNsense/DynDNS/IndexController.php @@ -0,0 +1,47 @@ +view->formDialogAccount = $this->getForm("dialogAccount"); + $this->view->formSettings = $this->getForm("settings"); + // choose template + $this->view->pick('OPNsense/DynDNS/index'); + } +} diff --git a/dns/ddclient/src/opnsense/mvc/app/controllers/OPNsense/DynDNS/forms/dialogAccount.xml b/dns/ddclient/src/opnsense/mvc/app/controllers/OPNsense/DynDNS/forms/dialogAccount.xml new file mode 100644 index 0000000000..3ada63a843 --- /dev/null +++ b/dns/ddclient/src/opnsense/mvc/app/controllers/OPNsense/DynDNS/forms/dialogAccount.xml @@ -0,0 +1,114 @@ +
+ + account.enabled + + checkbox + Enable this virtual server + + + account.description + + text + + + account.service + + dropdown + Select the service to use. + + + account.protocol + + dropdown + Select the protocol to use. + + + + account.server + + text + DynDNS Server hostname or uri to use (depending on the protocol). + When a URI is provided, the tag __MYIP__ will be replaced with the current detected address for this service + and __HOSTNAME__ will contain the (comma separated) list of hostnames provided. + + + + + account.resourceId + + text + true + + + account.username + + text + Username or login to use + + + account.password + + password + Password associated with this account + + + account.wildcard + + checkbox + + add a DNS wildcard CNAME record that points to the configured host. + + + account.zone + + text + + Zone containing the host entry. + + + account.hostnames + + select_multiple + + true + Hostname to update + + + account.ttl + + text + + Time to Live for the DNS entry + + + account.checkip + + dropdown + How to determine the address to use for this host + + + account.interface + + dropdown + + + account.dynipv6host + + text + true + Swap the interface identifier of the ipv6 address with the given partial ipv6 address (the least significant 64 bits of the address) + + + account.checkip_timeout + + text + How long to wait before the checkip process times out + + + account.force_ssl + + checkbox + Force update using HTTPS, please note setting this option will enforce https updates on all accounts + as ddclient only supports SSL=yes on a global level (the check ip service may still use HTTP on other services) + +
diff --git a/dns/ddclient/src/opnsense/mvc/app/controllers/OPNsense/DynDNS/forms/settings.xml b/dns/ddclient/src/opnsense/mvc/app/controllers/OPNsense/DynDNS/forms/settings.xml new file mode 100644 index 0000000000..5c12c5c428 --- /dev/null +++ b/dns/ddclient/src/opnsense/mvc/app/controllers/OPNsense/DynDNS/forms/settings.xml @@ -0,0 +1,34 @@ +
+ + ddclient.general.enabled + + checkbox + Enable ddclient + + + ddclient.general.verbose + + checkbox + true + Enable verbose logging + + + ddclient.general.allowipv6 + + checkbox + true + Allow IPv6 for updates + + + ddclient.general.daemon_delay + + text + Interval in seconds to check for address changes + + + ddclient.general.backend + + dropdown + Select the backend to use. + +
diff --git a/dns/ddclient/src/opnsense/mvc/app/models/OPNsense/DynDNS/ACL/ACL.xml b/dns/ddclient/src/opnsense/mvc/app/models/OPNsense/DynDNS/ACL/ACL.xml new file mode 100644 index 0000000000..5e0cde48b2 --- /dev/null +++ b/dns/ddclient/src/opnsense/mvc/app/models/OPNsense/DynDNS/ACL/ACL.xml @@ -0,0 +1,11 @@ + + + Services: Dynamic DNS + + ui/dyndns/* + api/dyndns/accounts/* + api/dyndns/service/* + api/dyndns/settings/* + + + diff --git a/dns/ddclient/src/opnsense/mvc/app/models/OPNsense/DynDNS/DynDNS.php b/dns/ddclient/src/opnsense/mvc/app/models/OPNsense/DynDNS/DynDNS.php new file mode 100644 index 0000000000..5ce08ea111 --- /dev/null +++ b/dns/ddclient/src/opnsense/mvc/app/models/OPNsense/DynDNS/DynDNS.php @@ -0,0 +1,90 @@ +getFlatNodes() as $key => $node) { + $tagName = $node->getInternalXMLTagName(); + $parentNode = $node->getParentNode(); + if ($validateFullModel || $node->isFieldChanged()) { + if ($parentNode->getInternalXMLTagName() === 'account' && in_array($tagName, ['protocol', 'server'])) { + $parentKey = $parentNode->__reference; + $validate_servers[$parentKey] = $parentNode; + } + } + } + + foreach ($validate_servers as $key => $node) { + $validate_url = false; + + if ($node->service->isEqual('powerdns')) { + $validate_url = true; + } elseif (!$node->service->isEqual('custom')) { + continue; + } + + $srv = (string)$node->server; + + if (in_array((string)$node->protocol, ['get', 'post', 'put']) || $validate_url) { + if (empty($srv) || filter_var($srv, FILTER_VALIDATE_URL) === false) { + $messages->appendMessage( + new Message( + gettext('A valid URI is required.'), + $key . '.server' + ) + ); + } + } else { + if (empty($srv) || filter_var($srv, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME) === false) { + $messages->appendMessage( + new Message( + gettext('A valid domain is required.'), + $key . '.server' + ) + ); + } + } + } + + return $messages; + } +} diff --git a/dns/ddclient/src/opnsense/mvc/app/models/OPNsense/DynDNS/DynDNS.xml b/dns/ddclient/src/opnsense/mvc/app/models/OPNsense/DynDNS/DynDNS.xml new file mode 100644 index 0000000000..75c390ce1e --- /dev/null +++ b/dns/ddclient/src/opnsense/mvc/app/models/OPNsense/DynDNS/DynDNS.xml @@ -0,0 +1,214 @@ + + //OPNsense/DynDNS + 1.5.1 + Dynamic DNS client + + + + 1 + Y + + + 0 + Y + + + 0 + Y + + + 300 + Y + 1 + 86400 + + + Y + opnsense + A backend is required. + + ddclient + native + + + + + + + 1 + Y + + + Y + A service type is required. + + 1984 + Changeip + Cloudflare + ClouDNS + Digitalocean + dinahosting + DNS Made Easy (digicert) + DNS-O-Matic + DNSExit + DynDNS.com + DnsPark + dnspodcn + DSLReports + DonDominio + Duck DNS + Dynu + easyDNS + FreeDNS + freeMyIP + gandi.net + GoDaddy + Google + HE.net + HE.net TunnelBroker + Hetzner DNS Console + INWX + Key-Systems + Loopia + Mythic Beasts + NameCheap + NearlyFreeSpeech.net + Njalla + no-ip + nsupdate.info (IPv4) + nsupdate.info (IPv6) + OVHcloud DynHost + Porkbun + regfish.de + Servercow + Sitelutions + spDYN + STRATO + Woima + Yandex + Zoneedit + Custom + + + + N + A protocol type is required. + + DynDNS 1 + DynDNS 2 + Custom GET + Custom POST + Custom PUT + + + + N + + + N + /^([a-zA-Z0-9\-.@_:+\%])*$/u + The username contains invalid characters. + + + N + /^[^\n]*$/ + + + N + /^[^\n]*$/ + resourceId contains invalid characters. + + + Y + N + Y + Y + Y + Y + , + + + 0 + Y + + + N + N + + + Y + web_dyndns + An IP service type is required. + + akamai + akamai-ipv4 + akamai-ipv6 + cloudflare + cloudflare-ipv4 + cloudflare-ipv6 + cloudflare-dns + dynu-ipv4 + dynu-ipv6 + freedns + he + icanhazip + ipify-ipv4 + ipify-ipv6 + loopia + myonlineportal + noip-ipv4 + noip-ipv6 + nsupdate.info-ipv4 + nsupdate.info-ipv6 + zoneedit + Interface + + + + interface.check001 + + + + + N + /^::(([0-9a-fA-F]{1,4}:){0,3}[0-9a-fA-F]{1,4})?$/u + Entry is not a valid partial ipv6 address definition (e.g. ::1000). + + + 10 + Y + 10 + 60 + + + 1 + Y + + + 300 + Y + 1 + 604800 + + + N + + + An interface is required for the selected check method + SetIfConstraint + checkip + if + + + + + N + /^(.){1,255}$/u + Description should be a string between 1 and 255 characters + + + + + + + diff --git a/dns/ddclient/src/opnsense/mvc/app/models/OPNsense/DynDNS/FieldTypes/AccountField.php b/dns/ddclient/src/opnsense/mvc/app/models/OPNsense/DynDNS/FieldTypes/AccountField.php new file mode 100644 index 0000000000..0697684326 --- /dev/null +++ b/dns/ddclient/src/opnsense/mvc/app/models/OPNsense/DynDNS/FieldTypes/AccountField.php @@ -0,0 +1,79 @@ +getAttribute('uuid')])) { + $stats = self::$current_stats[$node->getAttribute('uuid')]; + if (!empty($stats)) { + $node->current_ip->setValue($stats['ip']); + $node->current_mtime->setValue(date('c', (int)$stats['mtime'])); + } + } elseif (!empty((string)$node->hostnames)) { + foreach (explode(",", (string)$node->hostnames) as $hostname) { + if (!empty(self::$current_stats[$hostname]) && !empty(self::$current_stats[$hostname]['ip'])) { + $stats = self::$current_stats[$hostname]; + $node->current_ip->setValue($stats['ip']); + $node->current_mtime->setValue(date('c', $stats['mtime'])); + break; + } + } + } + } + + protected function actionPostLoadingEvent() + { + if (self::$current_stats === null) { + self::$current_stats = []; + $stats = json_decode((new Backend())->configdRun('ddclient statistics'), true); + if (!empty($stats) && !empty($stats['hosts'])) { + self::$current_stats = $stats['hosts']; + } elseif (!empty($stats)) { + self::$current_stats = $stats; + } + } + foreach ($this->internalChildnodes as $node) { + if (!$node->getInternalIsVirtual()) { + $this->addStatsFields($node); + } + } + return parent::actionPostLoadingEvent(); + } +} diff --git a/dns/ddclient/src/opnsense/mvc/app/models/OPNsense/DynDNS/FieldTypes/CheckipField.php b/dns/ddclient/src/opnsense/mvc/app/models/OPNsense/DynDNS/FieldTypes/CheckipField.php new file mode 100644 index 0000000000..83962e8054 --- /dev/null +++ b/dns/ddclient/src/opnsense/mvc/app/models/OPNsense/DynDNS/FieldTypes/CheckipField.php @@ -0,0 +1,57 @@ +internalOptionList = self::$internalCacheOptionList; + return; + } + if (is_array($data)) { + $opn_backend = (string)$this->getParentModel()->general->backend == 'opnsense'; + foreach ($data as $key => $value) { + self::$internalCacheOptionList[$key] = gettext($value); + } + if ($opn_backend) { + // OPNsense backend, change interface label and add IPv6 option + self::$internalCacheOptionList['if'] = gettext("Interface [IPv4]"); + self::$internalCacheOptionList['if6'] = gettext("Interface [IPv6]"); + } + $this->internalOptionList = self::$internalCacheOptionList; + } + } +} diff --git a/dns/ddclient/src/opnsense/mvc/app/models/OPNsense/DynDNS/FieldTypes/ServiceField.php b/dns/ddclient/src/opnsense/mvc/app/models/OPNsense/DynDNS/FieldTypes/ServiceField.php new file mode 100644 index 0000000000..46798b5fa1 --- /dev/null +++ b/dns/ddclient/src/opnsense/mvc/app/models/OPNsense/DynDNS/FieldTypes/ServiceField.php @@ -0,0 +1,69 @@ +getParentModel()->general->backend == 'opnsense') { + $supported = json_decode((new Backend())->configdRun("ddclient opnbackend supported"), true); + if (!empty($supported)) { + self::$internalCacheOptionList = $supported; + asort(self::$internalCacheOptionList, SORT_NATURAL | SORT_FLAG_CASE); + } + } + } + $this->internalOptionList = self::$internalCacheOptionList; + } + + /** + * setter for option values + * @param $data + */ + public function setOptionValues($data) + { + if (!empty(self::$internalCacheOptionList) || (string)$this->getParentModel()->general->backend == 'opnsense') { + return; + } + if (is_array($data)) { + foreach ($data as $key => $value) { + self::$internalCacheOptionList[$key] = gettext($value); + } + $this->internalOptionList = self::$internalCacheOptionList; + } + } +} diff --git a/dns/ddclient/src/opnsense/mvc/app/models/OPNsense/DynDNS/Menu/Menu.xml b/dns/ddclient/src/opnsense/mvc/app/models/OPNsense/DynDNS/Menu/Menu.xml new file mode 100644 index 0000000000..0834a4ab75 --- /dev/null +++ b/dns/ddclient/src/opnsense/mvc/app/models/OPNsense/DynDNS/Menu/Menu.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/dns/ddclient/src/opnsense/mvc/app/models/OPNsense/DynDNS/Migrations/M1_2_0.php b/dns/ddclient/src/opnsense/mvc/app/models/OPNsense/DynDNS/Migrations/M1_2_0.php new file mode 100644 index 0000000000..339c9c98d2 --- /dev/null +++ b/dns/ddclient/src/opnsense/mvc/app/models/OPNsense/DynDNS/Migrations/M1_2_0.php @@ -0,0 +1,70 @@ +object(); + + if (empty($config->OPNsense->DynDNS)) { + return; + } + + // migration will move these settings, extract datapoints from raw config + $checkip = (string)$config->OPNsense->DynDNS->general->checkip; + $interface = $checkip == "if" ? (string)$config->OPNsense->DynDNS->general->interface : ""; + $force_ssl = (string)$config->OPNsense->DynDNS->general->force_ssl; + $pre_account = []; + if (!empty($config->OPNsense->DynDNS->accounts->account)) { + foreach ($config->OPNsense->DynDNS->accounts->account as $account) { + $pre_account[(string)$account->attributes()['uuid']] = [ + "checkip" => !empty($account->use_interface) ? "if" : $checkip, + "interface" => !empty($account->use_interface) ? (string)$account->interface : $interface + ]; + } + } + + // update accounts + foreach ($model->accounts->account->iterateItems() as $account) { + $uuid = $account->getAttributes()['uuid']; + $account->checkip = $pre_account[$uuid]['checkip']; + $account->interface = $pre_account[$uuid]['interface']; + $account->force_ssl = $force_ssl; + } + } +} diff --git a/dns/ddclient/src/opnsense/mvc/app/views/OPNsense/DynDNS/index.volt b/dns/ddclient/src/opnsense/mvc/app/views/OPNsense/DynDNS/index.volt new file mode 100644 index 0000000000..88ee8897e5 --- /dev/null +++ b/dns/ddclient/src/opnsense/mvc/app/views/OPNsense/DynDNS/index.volt @@ -0,0 +1,155 @@ +{# + +OPNsense® is Copyright © 2021 by Deciso B.V. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, +OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +#} + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
{{ lang._('ID') }}{{ lang._('Enabled') }}{{ lang._('Service') }}{{ lang._('Hostnames') }}{{ lang._('Username') }}{{ lang._('Interface') }}{{ lang._('Current IP') }}{{ lang._('Updated') }}{{ lang._('Description') }}{{ lang._('Commands') }}
+ + +
+
+
+ {{ partial("layout_partials/base_form",['fields':formSettings,'id':'frm_settings'])}} +
+
+ +
+
+
+
+ + +

+
+
+
+ +{# include dialogs #} +{{ partial("layout_partials/base_dialog",['fields':formDialogAccount,'id':'DialogAccount','label':lang._('Edit Account')])}} diff --git a/dns/ddclient/src/opnsense/scripts/ddclient/checkip b/dns/ddclient/src/opnsense/scripts/ddclient/checkip new file mode 100755 index 0000000000..08d9edc6fe --- /dev/null +++ b/dns/ddclient/src/opnsense/scripts/ddclient/checkip @@ -0,0 +1,43 @@ +#!/usr/local/bin/python3 + +""" + Copyright (c) 2022 Ad Schellevis + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +""" +import argparse +from lib import registered_services, checkip + + +if __name__ == '__main__': + # handle parameters + parser = argparse.ArgumentParser() + parser.add_argument('-s', '--service', help='service name', choices=registered_services(), required=True) + parser.add_argument('-i', '--interface', help='interface', type=str, default='') + parser.add_argument('-t', '--tls', help='enforce tls', choices=['0', '1'], default='1') + parser.add_argument('--timeout', help='timeout', type=str, default='10') + inputargs = parser.parse_args() + + proto = 'http' if inputargs.tls == "0" else 'https' + interface = inputargs.interface if inputargs.interface.strip() != "" else None + print(checkip(inputargs.service, proto, inputargs.timeout, interface)) diff --git a/dns/ddclient/src/opnsense/scripts/ddclient/ddclient_opn.py b/dns/ddclient/src/opnsense/scripts/ddclient/ddclient_opn.py new file mode 100755 index 0000000000..ee1fb9e568 --- /dev/null +++ b/dns/ddclient/src/opnsense/scripts/ddclient/ddclient_opn.py @@ -0,0 +1,50 @@ +#!/usr/local/bin/python3 + +""" + Copyright (c) 2023 Ad Schellevis + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +""" +import argparse +import sys +import json +from lib import AccountFactory, Poller +sys.path.insert(0, "/usr/local/opnsense/site-python") +from daemonize import Daemonize + + +if __name__ == '__main__': + # handle parameters + parser = argparse.ArgumentParser() + parser.add_argument('-c', '--config', help='config file [json]', default='/usr/local/etc/ddclient.json') + parser.add_argument('-s', '--status', help='status output file [json]', default='/var/tmp/ddclient_opn.status') + parser.add_argument('-f', '--foreground', help='run (log) in foreground', default=False, action='store_true') + parser.add_argument('-l', '--list', help='list known services and exit', default=False, action='store_true') + parser.add_argument('-p', '--pid', help='pid file location', default='/var/run/ddclient_opn.pid') + inputargs = parser.parse_args() + if inputargs.list: + print(json.dumps(AccountFactory().known_services())) + else: + cmd = lambda : Poller(inputargs.config, inputargs.status) + daemon = Daemonize(app="ddclient", pid=inputargs.pid, action=cmd, foreground=inputargs.foreground) + daemon.start() diff --git a/dns/ddclient/src/opnsense/scripts/ddclient/lib/__init__.py b/dns/ddclient/src/opnsense/scripts/ddclient/lib/__init__.py new file mode 100755 index 0000000000..762f022d13 --- /dev/null +++ b/dns/ddclient/src/opnsense/scripts/ddclient/lib/__init__.py @@ -0,0 +1,61 @@ +""" + Copyright (c) 2022-2023 Ad Schellevis + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + + ------------------------------------------------------------------------------------------------------- + OPNsense ddclient alternative version. + + The base for the application is a configuration file /usr/local/etc/ddclient.json containing a `general` and + `account` section. Structured dictionaries per account settings determine the services to subscribe to, + general settings contain verbosity settings and poll intervals for example. + + Package structure: + * address.py + - contains all logic to figure out which address to use. + * poller.py + - The main poller class, which drives dyndns resolving. + * accounts + - Account/service type definitions, deriving from `BaseAccount`. + + The BaseAccount type: + + The lifetime of an account starts with the determination if an account object matches a requested configuration + account. Our AccountFactory() class is responsible for figuring our which classes are available and would + fit a provided definition using the `match(account)` method. + + Every account has an `atime` property, which determines when was the last time we compared if the stored address + matches the requested one and if needed was set accordingly at the remote service. + + Since every account likely has a dependency on an ip address, the base acccount implements an `execute()` method + which detects basic change (address, configuration changes) after which the implementation can do the actual + work and report if the address has really changed. This saves code and keeps the implementation simpler. + + The Poller class: + + Upon creation will start reading the configuration and merges the last known state (json), after each poll where something + changed (return status of `execute()`) the state is flushed to disk. + +""" +from .address import registered_services, checkip +from .poller import AccountFactory, Poller diff --git a/dns/ddclient/src/opnsense/scripts/ddclient/lib/account/__init__.py b/dns/ddclient/src/opnsense/scripts/ddclient/lib/account/__init__.py new file mode 100755 index 0000000000..811733acee --- /dev/null +++ b/dns/ddclient/src/opnsense/scripts/ddclient/lib/account/__init__.py @@ -0,0 +1,140 @@ +""" + Copyright (c) 2023 Ad Schellevis + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +""" +import syslog +import hashlib +import uuid +import time +from ..address import checkip + + +class BaseAccount: + _priority = 255 + + def __init__(self, account: dict): + self._account = account + self._account['id'] = account.get('id', str(uuid.uuid4())) + self._account['description'] = account.get('description', '') + self._state = {} + self._last_accessed = 0 + self._current_address = None # last resolved address + + # calculate a hash so we can easily detect configuration changes + hash_list = [] + for fieldname in sorted(account.keys()): + if fieldname not in ['id', 'description', 'checkip', 'checkip_timeout', 'force_ssl']: + hash_list.append(str(account[fieldname])) + self._account['md5'] = hashlib.md5("|".join(hash_list).encode()).hexdigest() + + + def update_state(self, address, status='good'): + """ set ip[v4 or v6] address and update in state dict when address is provided. + """ + if address is not None: + self._state['ip'] = address + self._state['status'] = status + self._state['mtime'] = time.time() + self._state['md5'] = self.md5 + self._last_accessed = time.time() + + @staticmethod + def known_services(): + return {} + + @property + def id(self): + """ account unique id + """ + return self._account['id'] + + @property + def settings(self): + return self._account + + @staticmethod + def match(account): + """ Does this account fit for the provided specification + """ + return False + + @property + def description(self): + return ("%(id)s [%(service)s - %(description)s] " % self._account) + + @property + def state(self): + return self._state + + @state.setter + def state(self, value: dict): + self._state = value + + @property + def mtime(self): + return self._state.get('mtime', 0) + + @property + def atime(self): + return self._last_accessed + + @property + def md5(self): + return self._account.get('md5') + + @property + def is_verbose(self): + return self._account.get('verbose') is True + + @property + def current_address(self): + return self._current_address + + def execute(self): + """ execute account check/update sequence, return true if state changed + """ + self._current_address = checkip( + service = self.settings.get('checkip'), + proto = 'https' if self.settings.get('force_ssl', False) else 'http', + timeout = str(self.settings.get('checkip_timeout', '10')), + interface = self.settings['interface'] if self.settings.get('interface' ,'').strip() != '' else None, + dynipv6host = self.settings['dynipv6host'] if self.settings.get('dynipv6host' ,'').strip() != '' else None + ) + + if not self._current_address: + syslog.syslog( + syslog.LOG_WARNING, + "Account %s no global IP address detected, check config if warning persists" % (self.description) + ) + return False + elif ( + self._state.get('ip') is None or + self._current_address != self._state.get('ip') or + self.state.get('md5') != self.md5 + ): + # if current address doesn't equal the current state, propagate the fact + return True + else: + # unmodified, poller keeps track of last access timestamp + return False diff --git a/dns/ddclient/src/opnsense/scripts/ddclient/lib/account/allinkl.py b/dns/ddclient/src/opnsense/scripts/ddclient/lib/account/allinkl.py new file mode 100755 index 0000000000..c799f62884 --- /dev/null +++ b/dns/ddclient/src/opnsense/scripts/ddclient/lib/account/allinkl.py @@ -0,0 +1,337 @@ +""" + Copyright (c) 2026 Carsten Kallies + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + + all-inkl.com KAS API DynDNS provider for OPNsense ddclient. + + Uses the KAS SOAP API (KasApi.wsdl) to update A/AAAA records. + + API endpoint: https://kasapi.kasserver.com/soap/KasApi.php + WSDL: https://kasapi.kasserver.com/soap/wsdl/KasApi.wsdl + + UI fields: + username - KAS login (all-inkl username, e.g. "w0xxxxx") + password - KAS password (plaintext, transmitted over HTTPS) + hostnames - FQDN(s) to update, comma-separated (e.g. "example.com,*.example.com") + zone - DNS zone (e.g. "example.com"); derived from hostname if left empty +""" +import json +import syslog +import time +import xml.etree.ElementTree as ET + +import requests + +from . import BaseAccount + + +class AllInkl(BaseAccount): + """all-inkl.com DynDNS via KAS SOAP API (KasApi).""" + + _priority = 65535 + + _services = { + 'allinkl': 'kasapi.kasserver.com' + } + + _URL = 'https://kasapi.kasserver.com/soap/KasApi.php' + _ACTION = '"urn:xmethodsKasApi#KasApi"' + + def __init__(self, account: dict): + super().__init__(account) + + @staticmethod + def known_services(): + return {'allinkl': 'all-inkl.com (KAS API)'} + + @staticmethod + def match(account): + return account.get('service') in AllInkl._services + + # ------------------------------------------------------------------ + # SOAP / KAS helpers + # ------------------------------------------------------------------ + + def _build_envelope(self, params_dict): + """Build a KasApi SOAP envelope. params_dict is JSON-serialised into .""" + params_json = json.dumps(params_dict) + # Escape XML special characters in the JSON string + params_json = (params_json + .replace('&', '&') + .replace('<', '<') + .replace('>', '>')) + return ( + '' + '' + '' + '' + '' + params_json + '' + '' + '' + '' + ) + + def _kas_api(self, action, request_params): + """Execute a KAS API action. Returns response text or None on failure.""" + params = { + 'kas_login': self.settings.get('username', ''), + 'kas_auth_type': 'plain', + 'kas_auth_data': self.settings.get('password', ''), + 'kas_action': action, + 'KasRequestParams': request_params, + } + envelope = self._build_envelope(params) + + if self.is_verbose: + syslog.syslog( + syslog.LOG_NOTICE, + "Account %s KAS action '%s' params: %s" % ( + self.description, action, json.dumps(request_params) + ) + ) + + try: + resp = requests.post( + self._URL, + data=envelope.encode('utf-8'), + headers={ + 'Content-Type': 'text/xml; charset=utf-8', + 'SOAPAction': self._ACTION, + 'User-Agent': 'OPNsense-dyndns', + }, + timeout=30 + ) + except requests.RequestException as exc: + syslog.syslog( + syslog.LOG_ERR, + "Account %s KAS request failed: %s" % (self.description, exc) + ) + return None + + if self.is_verbose: + syslog.syslog( + syslog.LOG_NOTICE, + "Account %s KAS '%s' HTTP %d: %s" % ( + self.description, action, resp.status_code, resp.text[:600] + ) + ) + + try: + root = ET.fromstring(resp.text) + except ET.ParseError: + syslog.syslog( + syslog.LOG_ERR, + "Account %s KAS '%s' invalid XML response: %s" % ( + self.description, action, resp.text[:200] + ) + ) + return None + + fault = root.find('.//{*}Fault') + if fault is not None: + faultstring = fault.find('{*}faultstring') or fault.find('faultstring') + syslog.syslog( + syslog.LOG_ERR, + "Account %s KAS '%s' SOAP fault: %s" % ( + self.description, action, + faultstring.text if faultstring is not None else resp.text[:200] + ) + ) + return None + + return resp.text + + # ------------------------------------------------------------------ + # Response parsing + # ------------------------------------------------------------------ + + def _find_record_id(self, xml_text, record_label, record_type): + """ + Parse get_dns_settings response and return record_id for the matching + record_name / record_type, or None. + + The KAS response contains ns2:Map items with key/value pairs: + record_namedyn + record_typeA + record_id12345 + """ + root = ET.fromstring(xml_text) + + for map_item in root.findall('.//{*}KasApiResponse//{*}item'): + kv = {} + for sub in map_item.findall('{*}item'): + key_el = sub.find('{*}key') + value_el = sub.find('{*}value') + if key_el is not None and value_el is not None: + kv[key_el.text or ''] = value_el.text or '' + + if kv.get('record_name') == record_label and kv.get('record_type') == record_type: + return kv.get('record_id') + + return None + + # ------------------------------------------------------------------ + # Zone / label helpers + # ------------------------------------------------------------------ + + def _get_zone(self, hostname): + """Return the DNS zone for a hostname (from config or derived).""" + zone = self.settings.get('zone', '').strip().rstrip('.') + if zone: + return zone + parts = hostname.split('.') + if len(parts) > 2: + return '.'.join(parts[1:]) + return hostname + + def _get_label(self, hostname, zone): + """Return the record label (left of zone) for a hostname. + + Examples: + dyn.example.com / zone example.com → 'dyn' + *.example.com / zone example.com → '*' + example.com / zone example.com → '' (root record) + """ + if hostname == zone: + return '' + if hostname.endswith('.' + zone): + return hostname[:-len(zone) - 1] + return hostname.split('.')[0] + + # ------------------------------------------------------------------ + # Main entry point + # ------------------------------------------------------------------ + + def execute(self): + if not super().execute(): + return False + + record_type = "AAAA" if ':' in str(self.current_address) else "A" + + hostnames_raw = self.settings.get('hostnames', '') + hostnames = [h.strip() for h in hostnames_raw.split(',') if h.strip()] + if not hostnames: + syslog.syslog( + syslog.LOG_ERR, + "Account %s no hostnames configured" % self.description + ) + return False + + all_success = True + last_zone = None + dns_response = None + + for hostname in hostnames: + zone = self._get_zone(hostname) + zone_host = zone + '.' + label = self._get_label(hostname, zone) + + if self.is_verbose: + syslog.syslog( + syslog.LOG_NOTICE, + "Account %s updating %s (zone: %s, label: '%s', type: %s) → %s" % ( + self.description, hostname, zone_host, + label, record_type, self.current_address + ) + ) + + # Fetch DNS records once per zone (cache for multiple hostnames in same zone) + if zone != last_zone: + dns_response = self._kas_api('get_dns_settings', {'zone_host': zone_host}) + last_zone = zone + if dns_response is None: + syslog.syslog( + syslog.LOG_ERR, + "Account %s failed to retrieve DNS settings for %s" % ( + self.description, zone_host + ) + ) + all_success = False + continue + # Respect KasFloodDelay between consecutive API calls + time.sleep(2) + + record_id = self._find_record_id(dns_response, label, record_type) + if record_id is None: + syslog.syslog( + syslog.LOG_ERR, + "Account %s record '%s' type %s not found in zone %s" % ( + self.description, label, record_type, zone_host + ) + ) + all_success = False + continue + + if self.is_verbose: + syslog.syslog( + syslog.LOG_NOTICE, + "Account %s found record_id %s for label '%s' %s" % ( + self.description, record_id, label, record_type + ) + ) + + update_response = self._kas_api('update_dns_settings', { + 'zone_host': zone_host, + 'record_id': record_id, + 'record_name': label, + 'record_type': record_type, + 'record_data': str(self.current_address), + 'record_aux': '0', + }) + + if update_response is None: + all_success = False + continue + + update_root = ET.fromstring(update_response) + if any(v.text and v.text.upper() == 'TRUE' + for v in update_root.findall('.//{*}value')): + syslog.syslog( + syslog.LOG_NOTICE, + "Account %s set new IP %s for %s" % ( + self.description, self.current_address, hostname + ) + ) + else: + syslog.syslog( + syslog.LOG_ERR, + "Account %s update_dns_settings failed for %s: %s" % ( + self.description, hostname, update_response[:300] + ) + ) + all_success = False + + time.sleep(2) + + if all_success: + self.update_state(address=self.current_address) + return True + + return False diff --git a/dns/ddclient/src/opnsense/scripts/ddclient/lib/account/aws.py b/dns/ddclient/src/opnsense/scripts/ddclient/lib/account/aws.py new file mode 100755 index 0000000000..e80e4e0377 --- /dev/null +++ b/dns/ddclient/src/opnsense/scripts/ddclient/lib/account/aws.py @@ -0,0 +1,95 @@ +""" + Copyright (c) 2023 Greg Glockner + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + ---------------------------------------------------------------------------------------------------- + AWS Route53 DNS provider + Usage: + AWS access key: username + AWS secret key: password + Route53 Hosted Zone ID: zone +""" +import syslog +import boto3 +from . import BaseAccount + + +class AWS(BaseAccount): + _services = ['aws'] + + def __init__(self, account: dict): + super().__init__(account) + + @staticmethod + def known_services(): + return AWS._services + + @staticmethod + def match(account): + return account.get('service') in AWS._services + + def execute(self): + """ AWS DNS update + """ + + if super().execute(): + if not self.current_address: + syslog.syslog( + syslog.LOG_WARNING, + f"No address found for {self.description}" + ) + return False + client = boto3.client('route53', + aws_access_key_id = self.settings.get('username'), + aws_secret_access_key = self.settings.get('password')) + ip = str(self.current_address) + if ':' in ip: + addrType = 'AAAA' + else: + addrType = 'A' + TTL = self.settings.get('ttl') + changeBatch = { + 'Changes': [{ + 'Action': 'UPSERT', + 'ResourceRecordSet': { + 'Name': host, + 'Type': addrType, + 'TTL': int(TTL), + 'ResourceRecords': [{'Value': ip}] + } + } for host in self.settings.get('hostnames').split(",")] + } + try: + response = client.change_resource_record_sets( + HostedZoneId = self.settings.get('zone'), + ChangeBatch = changeBatch) + except Exception as e: + syslog.syslog(syslog.LOG_ERR, str(e)) + return False + + syslog.syslog( + syslog.LOG_NOTICE, + f"Account {self.description} set new ip {self.current_address}, ID {response['ChangeInfo']['Id']}") + + self.update_state(address=self.current_address) + return True diff --git a/dns/ddclient/src/opnsense/scripts/ddclient/lib/account/azure.py b/dns/ddclient/src/opnsense/scripts/ddclient/lib/account/azure.py new file mode 100755 index 0000000000..e368b0f613 --- /dev/null +++ b/dns/ddclient/src/opnsense/scripts/ddclient/lib/account/azure.py @@ -0,0 +1,192 @@ +""" + Copyright (c) 2023 Ad Schellevis + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + ---------------------------------------------------------------------------------------------------- + Azure DNS provider, inspired by https://github.com/opnsense/plugins/pull/1547 + + List DNS zones using Azure cloud shell + + #> az network dns zone list + + Returns a structure like: + + [ + { + "etag": "00000000-0000-0000-0000-0000000000000", + "id": "/subscriptions/00000000-0000-0000-0000-00000000000/resourceGroups/example/providers/Microsoft.Network/dnszones/example.com", <---- ResourceId + "location": "global", + "maxNumberOfRecordSets": 10000, + "maxNumberOfRecordsPerRecordSet": null, + "name": "test.deciso.com", + "nameServers": [ + "ns1-07.azure-dns.com.", + "ns2-07.azure-dns.net.", + "ns3-07.azure-dns.org.", + "ns4-07.azure-dns.info." + ], + "numberOfRecordSets": 3, + "registrationVirtualNetworks": null, + "resolutionVirtualNetworks": null, + "resourceGroup": "xxxxxx", + "tags": {}, + "type": "Microsoft.Network/dnszones", + "zoneType": "Public" + } + ] + + Next create a service principal (https://learn.microsoft.com/en-us/cli/azure/ad/sp?view=azure-cli-latest#az_ad_sp_create_for_rbac) + + #> az ad sp create-for-rbac --name "AcmeDnsValidator" --role "DNS Zone Contributor" --scopes /subscriptions/00000000-0000-0000-0000-00000000000/resourceGroups/example/providers/Microsoft.Network/dnszones/example.com + + Which returns a structure like: + + { + "appId": "00000000-0000-0000-0000-000000000000", <--- username + "displayName": "AcmeDnsValidator", + "password": "000000000000000000000000000000000000", <--- Password + "tenant": "00000000-0000-0000-0000-000000000000" + } +""" +import syslog +import requests +from requests.auth import HTTPBasicAuth +from . import BaseAccount + + +class Azure(BaseAccount): + _services = ['azure'] + + def __init__(self, account: dict): + super().__init__(account) + + @staticmethod + def known_services(): + return {'azure': 'Microsoft Azure'} + + @staticmethod + def match(account): + return account.get('service') in Azure._services + + def execute(self): + """ Azure DNS update, uses an oauth2 sequence to login, the following requests are being performed: + - https://management.azure.com/subscriptions/%s --> request target to authenticate against + - https://login.microsoftonline.com/%s/oauth2/token --> login using the tenantId received in the prev req + - https://management.azure.com/%s/XXX/%s --> set hostname address using the bearer token received + """ + if super().execute(): + resourceId = self.settings.get('resourceId', '') + if resourceId.find('subscriptions/') == -1: + syslog.syslog(syslog.LOG_ERR, 'No subscription id found for account %s' % self.description) + return + subscriptionId = resourceId.split('subscriptions/')[-1].split('/')[0] + req = requests.get('https://management.azure.com/subscriptions/%s?api-version=2016-09-01' % subscriptionId) + auth_target = req.headers.get('WWW-Authenticate', '').split(maxsplit=1) + if len(auth_target) < 2 or auth_target[0] != 'Bearer': + syslog.syslog(syslog.LOG_ERR, 'No Bearer token found for account %s' % self.description) + return + elif auth_target[1].find('https://login.windows.net/') == -1: + syslog.syslog( + syslog.LOG_ERR, + 'Unable to find Tenant ID for account %s (response: %s)' % (self.description, auth_target[1]) + ) + return + + tenantId = auth_target[1].split('https://login.windows.net/')[1].split('"')[0] + req_opts = { + 'url': 'https://login.microsoftonline.com/%s/oauth2/token' % tenantId, + 'data': { + 'resource': 'https://management.core.windows.net/', + 'grant_type': 'client_credentials', + 'client_id': self.settings.get('username'), + 'client_secret': self.settings.get('password') + }, + 'headers': { + 'User-Agent': 'OPNsense-dyndns' + } + } + req = requests.post(**req_opts) + try: + token_payload = req.json() + except requests.exceptions.JSONDecodeError: + token_payload = {} + if req.status_code != 200 or 'access_token' not in token_payload: + syslog.syslog( + syslog.LOG_ERR, + 'Unable to authenticate account %s (http_code: %d - %s)' % ( + self.description, + req.status_code, + req.text.replace('\n', '') + ) + ) + return + + for hostname in self.settings.get('hostnames', '').split(','): + req_opts = { + 'headers': { + 'Accept': 'application/json', + 'Authorization': 'Bearer %s' % token_payload['access_token'], + 'Content-Type': 'application/json' + } + } + if self.current_address.find(':') > 1: + # IPv6 + req_opts['url'] = 'https://management.azure.com/%s/AAAA/%s?api-version=2018-05-01' % ( + resourceId, hostname + ) + req_opts['json'] = { + 'properties': { + 'AAAARecords': [ + { + 'ipv6Address': self.current_address + } + ] + } + } + else: + #IPv4 + req_opts['url'] = 'https://management.azure.com/%s/A/%s?api-version=2018-05-01' % ( + resourceId, hostname + ) + req_opts['json'] = { + 'properties': { + 'ARecords': [ + { + 'ipv4Address': self.current_address + } + ] + } + } + + req = requests.patch(**req_opts) + if req.status_code == 200: + if self.is_verbose: + syslog.syslog( + syslog.LOG_NOTICE, + "Account %s set new ip %s [%s]" % (self.description, self.current_address, req.text.strip()) + ) + + self.update_state(address=self.current_address) + return True + + return False diff --git a/dns/ddclient/src/opnsense/scripts/ddclient/lib/account/cloudflare.py b/dns/ddclient/src/opnsense/scripts/ddclient/lib/account/cloudflare.py new file mode 100755 index 0000000000..306cb98088 --- /dev/null +++ b/dns/ddclient/src/opnsense/scripts/ddclient/lib/account/cloudflare.py @@ -0,0 +1,196 @@ +""" + Copyright (c) 2023 Thomas Cekal + Copyright (c) 2023 Ad Schellevis + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +""" +import json +import syslog +import requests +from . import BaseAccount + + +class Cloudflare(BaseAccount): + _priority = 65535 + + _services = { + 'cloudflare': 'api.cloudflare.com' + } + + def __init__(self, account: dict): + super().__init__(account) + + @staticmethod + def known_services(): + return {'cloudflare': 'Cloudflare'} + + @staticmethod + def match(account): + return account.get('service') in Cloudflare._services + + def execute(self): + if super().execute(): + # IPv4/IPv6 + recordType = "AAAA" if str(self.current_address).find(':') > 1 else "A" + + # get ZoneID + url = "https://%s/client/v4/zones" % self._services[self.settings.get('service')] + + headers = { + 'User-Agent': 'OPNsense-dyndns' + } + # switch between bearer and email/key authentication + if self.settings.get('username', '').find('@') == -1: + headers["Authorization"] = "Bearer " + self.settings.get('password') + else: + headers["X-Auth-Email"] = self.settings.get('username') + headers["X-Auth-Key"] = self.settings.get('password') + + # Check if zone exists + req_opts = { + 'url': url, + 'params': { + 'name': self.settings.get('zone') + }, + 'headers': headers + } + response = requests.get(**req_opts) + try: + payload = response.json() + except requests.exceptions.JSONDecodeError: + payload = {} + if 'success' not in payload: + syslog.syslog( + syslog.LOG_ERR, + "Account %s error parsing JSON response [ZoneID] %s" % (self.description, response.text) + ) + return False + if not payload.get('success', False) or len(payload.get('result', [])) == 0: + syslog.syslog( + syslog.LOG_ERR, + "Account %s error receiving ZoneID [%s]" % (self.description, json.dumps(payload.get('errors', {}))) + ) + return False + + zone_id = payload['result'][0]['id'] + if self.is_verbose: + syslog.syslog( + syslog.LOG_NOTICE, + "Account %s ZoneID for %s %s" % (self.description, self.settings.get('zone'), zone_id) + ) + + # Update each hostname + for hostname in self.settings.get("hostnames", "").split(","): + + if self.is_verbose: + syslog.syslog( + syslog.LOG_NOTICE, + "Account %s Getting record ID for hostname %s (%s)" + % (self.description, hostname, recordType), + ) + + # Get record ID + req_opts = { + 'url': '%s/%s/dns_records' % (url, zone_id), + 'params': { + 'name': hostname, + 'type': recordType + }, + 'headers': headers + } + response = requests.get(**req_opts) + try: + payload = response.json() + except requests.exceptions.JSONDecodeError: + syslog.syslog( + syslog.LOG_ERR, + "Account %s error parsing JSON response [RecordID] %s" % (self.description, response.text) + ) + return False + if not payload.get('success', False): + syslog.syslog( + syslog.LOG_ERR, + "Account %s error receiving RecordID [%s]" % ( + self.description, json.dumps(payload.get('errors', {})) + ) + ) + return False + + if len(payload['result']) == 0: + syslog.syslog( + syslog.LOG_ERR, "Account %s error locating hostname %s [%s]" % ( + self.description, hostname, recordType + ) + ) + return False + + record_id = payload['result'][0]['id'] + if self.is_verbose: + syslog.syslog( + syslog.LOG_NOTICE, + "Account %s RecordID for %s %s" % (self.description, hostname, record_id) + ) + + # Send IP address update + req_opts = { + 'url': '%s/%s/dns_records/%s' % (url, zone_id, record_id), + 'json': { + 'type': recordType, + 'name': hostname, + 'content': str(self.current_address), + }, + 'headers': headers + } + response = requests.patch(**req_opts) + try: + payload = response.json() + except requests.exceptions.JSONDecodeError: + payload = {} + if 'success' not in payload: + syslog.syslog( + syslog.LOG_ERR, + "Account %s error parsing JSON response [UpdateIP] %s" % (self.description, response.text) + ) + return False + if payload.get('success', False): + syslog.syslog( + syslog.LOG_NOTICE, + "Account %s set new ip %s [%s]" % ( + self.description, + self.current_address, + payload.get('result', {}).get('content', '') + ) + ) + else: + syslog.syslog( + syslog.LOG_ERR, + "Account %s failed to set new ip %s for hostname %s: [%s]" % (self.description, + self.current_address, hostname, + response.text) + ) + return False + + self.update_state(address=self.current_address) + return True + + return False diff --git a/dns/ddclient/src/opnsense/scripts/ddclient/lib/account/digitalocean.py b/dns/ddclient/src/opnsense/scripts/ddclient/lib/account/digitalocean.py new file mode 100755 index 0000000000..e6a62f25fa --- /dev/null +++ b/dns/ddclient/src/opnsense/scripts/ddclient/lib/account/digitalocean.py @@ -0,0 +1,177 @@ +""" + Copyright (c) 2023 Ad Schellevis + Copyright (c) 2024 Olly Baker + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +""" +import json +import syslog +import requests +from . import BaseAccount + + +class DigitalOcean(BaseAccount): + _priority = 65535 + + _services = {"digitalocean": "api.digitalocean.com"} + + def __init__(self, account: dict): + super().__init__(account) + + @staticmethod + def known_services(): + return {"digitalocean": "DigitalOcean"} + + @staticmethod + def match(account): + return account.get("service") in DigitalOcean._services + + def execute(self): + if super().execute(): + recordType = "AAAA" if str(self.current_address).find(":") > 1 else "A" + + headers = { + "User-Agent": "OPNsense-dyndns", + "Authorization": "Bearer " + self.settings.get("password"), + } + + url = "https://%s/v2/domains/%s/records" % ( + self._services[self.settings.get("service")], + self.settings.get("zone"), + ) + + if self.is_verbose: + syslog.syslog( + syslog.LOG_NOTICE, + "Account %s current IP is %s (%s)" + % (self.description, self.current_address, recordType), + ) + + syslog.syslog( + syslog.LOG_NOTICE, + "Account %s updating hostnames %s" + % (self.description, self.settings.get("hostnames", "")), + ) + + # Update each hostname + for hostname in self.settings.get("hostnames", "").split(","): + + if self.is_verbose: + syslog.syslog( + syslog.LOG_NOTICE, + "Account %s getting record ID for hostname %s (%s)" + % (self.description, hostname, recordType), + ) + + request = { + "url": url, + "params": {"name": hostname, "type": recordType}, + "headers": headers, + } + + # Get record ID + response = requests.get(**request) + + try: + payload = response.json() + except requests.exceptions.JSONDecodeError: + syslog.syslog( + syslog.LOG_ERR, + "Account %s error getting record ID for hostname %s (%s): failed to decode response as JSON. Response: %s" + % (self.description, hostname, recordType, response.text), + ) + continue + + if response.status_code != 200: + syslog.syslog( + syslog.LOG_ERR, + "Account %s error getting record ID for hostname %s (%s): HTTP %s. Response: %s" + % ( + self.description, + hostname, + recordType, + response.status_code, + response.text, + ), + ) + continue + + if not payload.get("domain_records"): + syslog.syslog( + syslog.LOG_ERR, + "Account %s error getting record ID for hostname %s (%s): no results returned. Response: %s" + % (self.description, hostname, recordType, response.text), + ) + continue + + record_id = payload["domain_records"][0]["id"] + if self.is_verbose: + syslog.syslog( + syslog.LOG_NOTICE, + "Account %s record ID for %s (%s) is %s" + % (self.description, hostname, recordType, record_id), + ) + + request = { + "url": "%s/%s" % (url, str(record_id)), + "json": { + "type": recordType, + "data": str(self.current_address), + }, + "headers": headers, + } + + # Update record IP + response = requests.patch(**request) + + if response.status_code == 200: + if self.is_verbose: + syslog.syslog( + syslog.LOG_NOTICE, + "Account %s successfully updated hostname %s (%s) to IP %s" + % ( + self.description, + hostname, + recordType, + self.current_address, + ), + ) + + else: + syslog.syslog( + syslog.LOG_ERR, + "Account %s failed to set new IP (%s) for hostname %s (%s): HTTP %s. Response: %s" + % ( + self.description, + self.current_address, + self.description, + hostname, + recordType, + response.status_code, + response.text, + ), + ) + continue + self.update_state(address=self.current_address) + return True + return False diff --git a/dns/ddclient/src/opnsense/scripts/ddclient/lib/account/dnspod_cn.py b/dns/ddclient/src/opnsense/scripts/ddclient/lib/account/dnspod_cn.py new file mode 100755 index 0000000000..cb4631fcc0 --- /dev/null +++ b/dns/ddclient/src/opnsense/scripts/ddclient/lib/account/dnspod_cn.py @@ -0,0 +1,301 @@ +""" + Copyright (c) 2024 AnShen + Copyright (c) 2023 Ad Schellevis + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +""" +import json +import syslog +import time +import hashlib +import hmac +import requests +from datetime import datetime +from . import BaseAccount + + +# dnspod api 3.0 +# https://cloud.tencent.com/document/api/1427 + +class DNSPod_CN(BaseAccount): + _priority = 65535 + + _services = { + 'dnspodcn': 'dnspod.tencentcloudapi.com' + } + + def __init__(self, account: dict): + super().__init__(account) + self.service = 'dnspod' + + @staticmethod + def known_services(): + return {'dnspodcn': 'dnspodcn'} + + @staticmethod + def match(account): + return account.get('service') in DNSPod_CN._services + + + @staticmethod + def _sign(key, msg): + """ + Generate HMAC-SHA256 signature. + + Args: + key (bytes): Signing key + msg (str): Message to sign + + Returns: + bytes: Signature digest + """ + return hmac.new(key, msg.encode("utf-8"), hashlib.sha256).digest() + + def generate_signature(self, action, payload="{}"): + """ + Generate signature and headers for a Tencent Cloud API request. + + Args: + action (str): API action name + payload (str or dict, optional): Request payload. Defaults to "{}". + + Returns: + tuple: Request headers and canonical request + """ + # Ensure payload is a string + payload = json.dumps(payload) if isinstance(payload, dict) else payload + + # Get current timestamp + timestamp = int(time.time()) + date = datetime.utcfromtimestamp(timestamp).strftime("%Y-%m-%d") + + # Step 1: Create Canonical Request + http_request_method = "POST" + canonical_uri = "/" + canonical_querystring = "" + ct = "application/json; charset=utf-8" + canonical_headers = f"content-type:{ct}\nhost:{self._services[self.settings.get('service')]}\nx-tc-action:{action.lower()}\n" + signed_headers = "content-type;host;x-tc-action" + hashed_request_payload = hashlib.sha256(payload.encode("utf-8")).hexdigest() + + canonical_request = ( + f"{http_request_method}\n" + f"{canonical_uri}\n" + f"{canonical_querystring}\n" + f"{canonical_headers}\n" + f"{signed_headers}\n" + f"{hashed_request_payload}" + ) + + # Step 2: Create String to Sign + algorithm = "TC3-HMAC-SHA256" + credential_scope = f"{date}/{self.service}/tc3_request" + hashed_canonical_request = hashlib.sha256(canonical_request.encode("utf-8")).hexdigest() + + string_to_sign = ( + f"{algorithm}\n" + f"{timestamp}\n" + f"{credential_scope}\n" + f"{hashed_canonical_request}" + ) + + # Step 3: Calculate Signature + secret_date = self._sign(("TC3" + self.settings.get('password')).encode("utf-8"), date) + secret_service = self._sign(secret_date, self.service) + secret_signing = self._sign(secret_service, "tc3_request") + signature = hmac.new(secret_signing, string_to_sign.encode("utf-8"), hashlib.sha256).hexdigest() + + # Step 4: Create Authorization Header + authorization = ( + f"{algorithm} " + f"Credential={self.settings.get('username', '')}/{credential_scope}, " + f"SignedHeaders={signed_headers}, " + f"Signature={signature}" + ) + + # Prepare headers + headers = { + "Authorization": authorization, + "Content-Type": "application/json; charset=utf-8", + "Host": self._services[self.settings.get('service')], + "X-TC-Action": action, + "X-TC-Timestamp": str(timestamp), + "X-TC-Version": "2021-03-23", + 'User-Agent': 'OPNsense-dyndns', + } + + return headers, payload + + def send_request(self, action, payload="{}", region="", token=""): + """ + Send a request to the Tencent Cloud API. + + Args: + action (str): API action name + payload (str or dict, optional): Request payload. Defaults to "{}". + region (str, optional): Optional region parameter + token (str, optional): Optional token parameter + + Returns: + dict: API response JSON + """ + # Get headers and prepared payload + headers, payload = self.generate_signature(action, payload) + + # Add optional headers + if region: + headers["X-TC-Region"] = region + if token: + headers["X-TC-Token"] = token + + try: + # Send request using requests library + response = requests.post( + url=f"https://{self._services[self.settings.get('service')]}", + headers=headers, + data=payload, + timeout=10 + ) + + # Raise an exception for bad responses + response.raise_for_status() + + # Return JSON response + return response + + except requests.RequestException as err: + print(f"Request error: {err}") + + # If there's a response, print its content for debugging + if hasattr(err, 'response') and err.response is not None: + print(f"Response content: {err.response.text}") + + return None + + + def execute(self): + if super().execute(): + # IPv4/IPv6 + recordType = "AAAA" if str(self.current_address).find(':') > 1 else "A" + + subdomains = [] + hostnames = self.settings.get('hostnames').split(',') + for _subdomain in hostnames: + if _subdomain == self.settings.get('zone') or _subdomain == '@': + subdomains.append('@') + else: + subdomains.append(_subdomain.replace(f".{self.settings.get('zone')}", '')) + + if len(subdomains) < 1: + syslog.syslog( + syslog.LOG_ERR, + "Account %s hostnames format error" % self.description + ) + return False + + # Get record ID + response = self.send_request( + action='DescribeRecordList', + payload={ + 'RecordType': recordType, + 'Domain': self.settings.get('zone') + } + ) + try: + payload = response.json() + except requests.exceptions.JSONDecodeError: + payload = {} + if 'Response' in payload and 'Error' in payload['Response']: + syslog.syslog( + syslog.LOG_ERR, + "Account %s error parsing JSON response [ZoneID] %s" % (self.description, payload['Response']['Error']['Code']) + ) + return False + if not payload['Response'].get('RecordList', False): + syslog.syslog( + syslog.LOG_ERR, + "Account %s error receiving ZoneID [%s]" % (self.description, response.text) + ) + return False + + record_id_list = [x['RecordId'] for x in payload['Response']['RecordList'] if x['Name'] in subdomains] + if len(record_id_list) < 1: + syslog.syslog( + syslog.LOG_ERR, + "Account %s error Not Found Record [%s]" % (self.description, self.settings.get('hostnames')) + ) + return False + + if self.is_verbose: + syslog.syslog( + syslog.LOG_NOTICE, + "Account %s ZoneID for %s %s" % (self.description, self.settings.get('zone'), record_id_list) + ) + + # Send IP address update + response = self.send_request( + action='ModifyRecordBatch', + payload={ + 'RecordIdList': record_id_list, + 'Change': 'value', + 'ChangeTo': str(self.current_address), + } + ) + try: + payload = response.json() + except requests.exceptions.JSONDecodeError: + payload = {} + if 'Response' in payload and 'Error' in payload['Response']: + syslog.syslog( + syslog.LOG_ERR, + "Account %s error parsing JSON response [UpdateIP] %s" % (self.description, payload['Response']['Error']['Code']) + ) + return False + if len(payload['Response']['DetailList']) < 1: + syslog.syslog( + syslog.LOG_ERR, + "Account %s failed to set new ip %s [%s]" % (self.description, self.current_address, response.text) + ) + return False + + record_list = payload['Response']['DetailList'][0].get('RecordList', False) + if record_list and len(record_list) == len(subdomains): + syslog.syslog( + syslog.LOG_NOTICE, + "Account %s set new ip %s %s" % ( + self.description, + self.current_address, + subdomains + ) + ) + + self.update_state(address=self.current_address) + return True + + syslog.syslog( + syslog.LOG_ERR, + "Account %s failed to set new ip %s %s" % (self.description, self.current_address, subdomains) + ) + + + return False diff --git a/dns/ddclient/src/opnsense/scripts/ddclient/lib/account/domeneshop.py b/dns/ddclient/src/opnsense/scripts/ddclient/lib/account/domeneshop.py new file mode 100755 index 0000000000..91bbe27f58 --- /dev/null +++ b/dns/ddclient/src/opnsense/scripts/ddclient/lib/account/domeneshop.py @@ -0,0 +1,93 @@ +""" + Copyright (c) 2023 Bernhard Frenking + Copyright (c) 2023 Ad Schellevis + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + ---------------------------------------------------------------------------------------------------- + Domeneshop DNS updater + Token should be set via the username field + Secret should be set via the password field +""" +import syslog +import requests +from requests.auth import HTTPBasicAuth +from . import BaseAccount + + +class Domeneshop(BaseAccount): + + _services = { + 'domeneshop': 'api.domeneshop.no' + } + + def __init__(self, account: dict): + super().__init__(account) + + @staticmethod + def known_services(): + return Domeneshop._services.keys() + + @staticmethod + def match(account): + return account.get('service') in Domeneshop._services + + def execute(self): + if super().execute(): + hostnames = self.settings.get('hostnames') + + # DNS update request using the "IP update protocol" + url = f'https://api.domeneshop.no/v0/dyndns/update?hostname={hostnames}&myip={str(self.current_address)}' + req_opts = { + 'url': url, + 'auth': HTTPBasicAuth(self.settings.get('username'), self.settings.get('password')), + 'headers': { + 'User-Agent': 'OPNsense-dyndns' + } + } + response = requests.get(**req_opts) + + # Parse response and update state and log + if response.status_code == 204: + if self.is_verbose: + syslog.syslog( + syslog.LOG_NOTICE, + "Account %s set new ip %s [%s] for %s" % (self.description, self.current_address, response.text.strip(), hostnames) + ) + self.update_state(address=self.current_address, status=response.text.split()[0] if response.text else '') + return True + elif response.status_code == 404: + syslog.syslog( + syslog.LOG_ERR, + "Account %s failed to set new ip %s [%d - %s], because %s could not be found" % ( + self.description, self.current_address, response.status_code, response.text.replace('\n', ''), hostnames + ) + ) + else: + syslog.syslog( + syslog.LOG_ERR, + "Account %s failed to set new ip %s [%d - %s] for %s" % ( + self.description, self.current_address, response.status_code, response.text.replace('\n', ''), hostnames + ) + ) + + return False diff --git a/dns/ddclient/src/opnsense/scripts/ddclient/lib/account/duckdns.py b/dns/ddclient/src/opnsense/scripts/ddclient/lib/account/duckdns.py new file mode 100755 index 0000000000..185daa112f --- /dev/null +++ b/dns/ddclient/src/opnsense/scripts/ddclient/lib/account/duckdns.py @@ -0,0 +1,80 @@ +""" + Copyright (c) 2023 Greg Glockner + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + ---------------------------------------------------------------------------------------------------- + DuckDNS updater + Token should be set via the password field +""" +import syslog +import requests +from . import BaseAccount + + +class duckdns(BaseAccount): + _services = ['duckdns'] + + def __init__(self, account: dict): + super().__init__(account) + + @staticmethod + def known_services(): + return duckdns._services + + @staticmethod + def match(account): + return account.get('service') in duckdns._services + + def execute(self): + """ Duck DNS update + """ + + if super().execute(): + data = { + 'domains': self.settings.get('hostnames'), + 'token': self.settings.get('password') + } + + ip = str(self.current_address) + if ':' in ip: + data['ipv6'] = ip + else: + data['ip'] = ip + + proto = 'https' if self.settings.get('force_ssl', False) else 'http' + + try: + response = requests.get(proto+'://www.duckdns.org/update', data) + if response.text.startswith('KO'): + raise RuntimeError( + f"DuckDNS update failed for {self.description} with ip {self.current_address} for domains {data['domains']}, response: {response.text}") + except Exception as e: + syslog.syslog(syslog.LOG_ERR, str(e)) + return False + + syslog.syslog( + syslog.LOG_NOTICE, + f"Account {self.description} set new ip {self.current_address} for domains {data['domains']}") + + self.update_state(address=self.current_address) + return True diff --git a/dns/ddclient/src/opnsense/scripts/ddclient/lib/account/dyndns2.py b/dns/ddclient/src/opnsense/scripts/ddclient/lib/account/dyndns2.py new file mode 100755 index 0000000000..e93d0e5abe --- /dev/null +++ b/dns/ddclient/src/opnsense/scripts/ddclient/lib/account/dyndns2.py @@ -0,0 +1,119 @@ +""" + Copyright (c) 2023 Ad Schellevis + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +""" +import syslog +import requests +from requests.auth import HTTPBasicAuth +from . import BaseAccount + + +class DynDNS2(BaseAccount): + _priority = 65535 + + _services = { + 'dyndns2': 'members.dyndns.org', + 'desec-v4': 'update.dedyn.io', + 'desec-v6': 'update6.dedyn.io', + 'dns-o-matic': 'updates.dnsomatic.com', + 'dynu': 'api.dynu.com', + 'he-net': 'dyn.dns.he.net', + 'he-net-tunnel': 'ipv4.tunnelbroker.net', + 'inwx': 'dyndns.inwx.com', + 'loopia': 'dyndns.loopia.se', + 'nsupdatev4': 'ipv4.nsupdate.info', + 'nsupdatev6': 'ipv6.nsupdate.info', + 'ovh': 'www.ovh.com', + 'spdyn': 'update.spdyn.de', + 'strato': 'dyndns.strato.com', + 'noip': 'dynupdate.no-ip.com' + } + + def __init__(self, account: dict): + super().__init__(account) + + @staticmethod + def known_services(): + return list(DynDNS2._services.keys()) + ['custom'] + + @staticmethod + def match(account): + if account.get('service') in DynDNS2.known_services(): + return True + else: + return False + + def execute(self): + if super().execute(): + protocol = self.settings.get('protocol', None) + if protocol in [ 'get', 'post', 'put' ]: + url = self.settings.get('server') + url = url.replace('__MYIP__', self.current_address) + url = url.replace('__HOSTNAME__', self.settings.get('hostnames')) + req = requests.request( + method=protocol, + url=url, + headers={'User-Agent': 'OPNsense-dyndns'}, + auth=HTTPBasicAuth(self.settings.get('username'), self.settings.get('password')) + ) + else: + uri_proto = 'https' if self.settings.get('force_ssl', False) else 'http' + if self.settings.get('service') in self._services: + url = "%s://%s/nic/update" % (uri_proto, self._services[self.settings.get('service')]) + else: + url = "%s://%s/nic/update" % (uri_proto, self.settings.get('server')) + + req_opts = { + 'url': url, + 'params': { + 'hostname': self.settings.get('hostnames'), + 'myip': self.current_address, + 'system': 'dyndns', + 'wildcard': 'ON' if self.settings.get('wildcard', False) else 'NOCHG' + }, + 'auth': HTTPBasicAuth(self.settings.get('username'), self.settings.get('password')), + 'headers': { + 'User-Agent': 'OPNsense-dyndns' + } + } + req = requests.get(**req_opts) + + if 200 <= req.status_code < 300: + if self.is_verbose: + syslog.syslog( + syslog.LOG_NOTICE, + "Account %s set new ip %s [%s]" % (self.description, self.current_address, req.text.strip()) + ) + + self.update_state(address=self.current_address, status=req.text.split()[0] if req.text else '') + return True + else: + syslog.syslog( + syslog.LOG_ERR, + "Account %s failed to set new ip %s [%d - %s]" % ( + self.description, self.current_address, req.status_code, req.text.replace('\n', '') + ) + ) + + return False diff --git a/dns/ddclient/src/opnsense/scripts/ddclient/lib/account/gandi.py b/dns/ddclient/src/opnsense/scripts/ddclient/lib/account/gandi.py new file mode 100755 index 0000000000..c92f1e2549 --- /dev/null +++ b/dns/ddclient/src/opnsense/scripts/ddclient/lib/account/gandi.py @@ -0,0 +1,81 @@ +""" + Copyright (c) 2024 Thomas Cekal + Copyright (c) 2024 Ad Schellevis + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +""" +import json +import syslog +import requests +from . import BaseAccount + + +class Gandi(BaseAccount): + _services = { + 'gandi': 'api.gandi.net' + } + + def __init__(self, account: dict): + super().__init__(account) + + @staticmethod + def known_services(): + return Gandi._services.keys() + + @staticmethod + def match(account): + return account.get('service') in Gandi._services + + def execute(self): + if super().execute(): + # IPv4/IPv6 + recordType = "AAAA" if str(self.current_address).find(':') > 1 else "A" + + # Use bearer (api key) authentication + url = "https://api.gandi.net/v5/livedns/domains/" + self.settings.get('zone') + "/records/" + self.settings.get('hostnames') + "/" + recordType + payload = "{\"rrset_values\":[\"" + self.current_address + "\"],\"rrset_ttl\":300}" + headers = { + 'authorization': "Bearer " + self.settings.get('password'), + 'content-type': "application/json", + 'User-Agent': 'OPNsense-dyndns' + } + # Send IP address update + req = requests.request("PUT", url, data=payload, headers=headers) + if 200 <= req.status_code < 300: + if self.is_verbose: + syslog.syslog( + syslog.LOG_NOTICE, + "Account %s set new ip %s [%s]" % (self.description, self.current_address, req.text.strip()) + ) + + self.update_state(address=self.current_address, status=req.text.split()[0] if req.text else '') + return True + else: + syslog.syslog( + syslog.LOG_ERR, + "Account %s failed to set new ip %s [%d - %s]" % ( + self.description, self.current_address, req.status_code, req.text.replace('\n', '') + ) + ) + + return False diff --git a/dns/ddclient/src/opnsense/scripts/ddclient/lib/account/hetzner.py b/dns/ddclient/src/opnsense/scripts/ddclient/lib/account/hetzner.py new file mode 100755 index 0000000000..1cf241e550 --- /dev/null +++ b/dns/ddclient/src/opnsense/scripts/ddclient/lib/account/hetzner.py @@ -0,0 +1,517 @@ +""" + Copyright (c) 2025 Arcan Consulting + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + + Hetzner DNS providers for OPNsense DynDNS via www.arcan-it.de + + Supports both APIs: + - Hetzner DNS (api.hetzner.cloud) - new Cloud API for migrated zones + - Hetzner DNS Legacy (dns.hetzner.com) - old API, shutting down May 2026 +""" +import syslog +import requests +from . import BaseAccount + + +class HetznerAccount(BaseAccount): + + def _extract_record_name(self, hostname, zone_name): + """Extract record name from hostname, handling FQDN format""" + hostname = hostname.rstrip('.') + if hostname.endswith('.' + zone_name): + record_name = hostname[:-len(zone_name) - 1] + elif hostname == zone_name: + record_name = '@' + else: + record_name = hostname + if not record_name or record_name == '@': + record_name = '@' + return record_name + + +class Hetzner(HetznerAccount): + """ + Hetzner Cloud DNS API provider + Uses the new Cloud API (api.hetzner.cloud) + API Documentation: https://docs.hetzner.cloud/#dns + """ + _priority = 65535 + + _services = ['hetzner'] + + _api_base = "https://api.hetzner.cloud/v1" + + def __init__(self, account: dict): + super().__init__(account) + + @staticmethod + def known_services(): + return {'hetzner': 'Hetzner DNS'} + + @staticmethod + def match(account): + return account.get('service') in Hetzner._services + + def _get_headers(self): + return { + 'User-Agent': 'OPNsense-dyndns', + 'Authorization': 'Bearer ' + self.settings.get('password', ''), + 'Content-Type': 'application/json' + } + + def _get_zone_name(self): + """Get zone name from settings - try 'zone' field first, then 'username' as fallback""" + zone_name = self.settings.get('zone', '').strip() + if not zone_name: + zone_name = self.settings.get('username', '').strip() + return zone_name + + def _get_zone_id(self, headers): + """Get zone ID by zone name""" + zone_name = self._get_zone_name() + + url = f"{self._api_base}/zones" + params = {'name': zone_name} + + response = requests.get(url, headers=headers, params=params) + + if response.status_code != 200: + syslog.syslog( + syslog.LOG_ERR, + "Account %s error fetching zones: HTTP %d - %s" % ( + self.description, response.status_code, response.text + ) + ) + return None + + try: + payload = response.json() + except requests.exceptions.JSONDecodeError: + syslog.syslog( + syslog.LOG_ERR, + "Account %s error parsing JSON response: %s" % (self.description, response.text) + ) + return None + + zones = payload.get('zones', []) + if not zones: + syslog.syslog( + syslog.LOG_ERR, + "Account %s zone '%s' not found" % (self.description, zone_name) + ) + return None + + zone_id = zones[0].get('id') + if self.is_verbose: + syslog.syslog( + syslog.LOG_NOTICE, + "Account %s found zone ID %s for %s" % (self.description, zone_id, zone_name) + ) + + return zone_id + + def _delete_record(self, headers, zone_id, record_name, record_type): + """Delete existing record""" + url = f"{self._api_base}/zones/{zone_id}/rrsets/{record_name}/{record_type}" + response = requests.delete(url, headers=headers) + if response.status_code not in [200, 201, 204]: + syslog.syslog( + syslog.LOG_ERR, + "Account %s error deleting record for update: HTTP %d - %s" % ( + self.description, response.status_code, response.text + ) + ) + return False + if self.is_verbose: + syslog.syslog( + syslog.LOG_NOTICE, + "Account %s deleted record: %s type: %s" % ( + self.description, record_name, record_type + ) + ) + return True + + def _update_record(self, headers, zone_id, record_name, record_type, address): + """Update existing record with new address""" + url = f"{self._api_base}/zones/{zone_id}/rrsets/{record_name}/{record_type}/actions/set_records" + data = { + 'records': [{ + 'value': str(address) + }] + } + response = requests.post(url, headers=headers, json=data) + if response.status_code not in [200, 201]: + syslog.syslog( + syslog.LOG_ERR, + "Account %s error updating record: HTTP %d - %s" % ( + self.description, response.status_code, response.text + ) + ) + + return False + + if self.is_verbose: + syslog.syslog( + syslog.LOG_NOTICE, + "Account %s updated %s %s to %s" % ( + self.description, record_name, record_type, address + ) + ) + + return True + + def _create_record(self, headers, zone_id, record_name, record_type, address): + """Create new record""" + url = f"{self._api_base}/zones/{zone_id}/rrsets" + + data = { + 'name': record_name, + 'type': record_type, + 'records': [{'value': str(address)}], + 'ttl': int(self.settings.get('ttl', 300)) + } + + response = requests.post(url, headers=headers, json=data) + + if response.status_code not in [200, 201]: + syslog.syslog( + syslog.LOG_ERR, + "Account %s error creating record: HTTP %d - %s" % ( + self.description, response.status_code, response.text + ) + ) + return False + + if self.is_verbose: + syslog.syslog( + syslog.LOG_NOTICE, + "Account %s created %s %s with %s" % ( + self.description, record_name, record_type, address + ) + ) + + return True + + def execute(self): + if super().execute(): + record_type = "AAAA" if ':' in str(self.current_address) else "A" + headers = self._get_headers() + + zone_id = self._get_zone_id(headers) + if not zone_id: + return False + + zone_name = self._get_zone_name() + + hostnames_raw = self.settings.get('hostnames', '') + hostnames = [h.strip() for h in hostnames_raw.split(',') if h.strip()] + + if not hostnames: + syslog.syslog( + syslog.LOG_ERR, + "Account %s no hostnames configured" % self.description + ) + return False + + all_success = True + for hostname in hostnames: + record_name = self._extract_record_name(hostname, zone_name) + + if self.is_verbose: + syslog.syslog( + syslog.LOG_NOTICE, + "Account %s updating %s (record: %s, type: %s) to %s" % ( + self.description, hostname, record_name, record_type, self.current_address + ) + ) + success = self._update_record( + headers, zone_id, record_name, record_type, self.current_address + ) + if not success: + success = self._create_record( + headers, zone_id, record_name, record_type, self.current_address + ) + + if success: + syslog.syslog( + syslog.LOG_NOTICE, + "Account %s set new IP %s for %s" % ( + self.description, self.current_address, hostname + ) + ) + else: + all_success = False + + if all_success: + self.update_state(address=self.current_address) + return True + + return False + + +class HetznerLegacy(HetznerAccount): + """ + Hetzner DNS Console (Legacy) API provider + Uses the old API at dns.hetzner.com - will be shut down May 2026 + For zones not yet migrated to Hetzner Cloud Console + API Documentation: https://dns.hetzner.com/api-docs + """ + _priority = 65535 + + _services = ['hetzner-legacy'] + + _api_base = "https://dns.hetzner.com/api/v1" + + def __init__(self, account: dict): + super().__init__(account) + + @staticmethod + def known_services(): + return {'hetzner-legacy': 'Hetzner DNS Legacy (deprecated)'} + + @staticmethod + def match(account): + return account.get('service') in HetznerLegacy._services + + def _get_headers(self): + return { + 'User-Agent': 'OPNsense-dyndns', + 'Auth-API-Token': self.settings.get('password', ''), + 'Content-Type': 'application/json' + } + + def _get_zone_name(self): + """Get zone name from settings - try 'zone' field first, then 'username' as fallback""" + zone_name = self.settings.get('zone', '').strip() + if not zone_name: + zone_name = self.settings.get('username', '').strip() + return zone_name + + def _get_zone_id(self, headers): + """Get zone ID by zone name""" + zone_name = self._get_zone_name() + + url = f"{self._api_base}/zones" + response = requests.get(url, headers=headers) + + if response.status_code != 200: + syslog.syslog( + syslog.LOG_ERR, + "Account %s error fetching zones: HTTP %d - %s" % ( + self.description, response.status_code, response.text + ) + ) + return None + + try: + payload = response.json() + except requests.exceptions.JSONDecodeError: + syslog.syslog( + syslog.LOG_ERR, + "Account %s error parsing JSON response: %s" % (self.description, response.text) + ) + return None + + zones = payload.get('zones', []) + for zone in zones: + if zone.get('name') == zone_name: + zone_id = zone.get('id') + if self.is_verbose: + syslog.syslog( + syslog.LOG_NOTICE, + "Account %s found zone ID %s for %s" % (self.description, zone_id, zone_name) + ) + return zone_id + + syslog.syslog( + syslog.LOG_ERR, + "Account %s zone '%s' not found" % (self.description, zone_name) + ) + return None + + def _get_record_id(self, headers, zone_id, record_name, record_type): + """Get record ID by name and type""" + url = f"{self._api_base}/records" + params = {'zone_id': zone_id} + + response = requests.get(url, headers=headers, params=params) + + if response.status_code != 200: + syslog.syslog( + syslog.LOG_ERR, + "Account %s error fetching records: HTTP %d - %s" % ( + self.description, response.status_code, response.text + ) + ) + return None + + try: + payload = response.json() + except requests.exceptions.JSONDecodeError: + syslog.syslog( + syslog.LOG_ERR, + "Account %s error parsing JSON response: %s" % (self.description, response.text) + ) + return None + + records = payload.get('records', []) + for record in records: + if record.get('name') == record_name and record.get('type') == record_type: + record_id = record.get('id') + if self.is_verbose: + syslog.syslog( + syslog.LOG_NOTICE, + "Account %s found record ID %s for %s %s" % ( + self.description, record_id, record_name, record_type + ) + ) + return record_id + + return None + + def _update_record(self, headers, zone_id, record_id, record_name, record_type, address): + """Update existing record with new address""" + url = f"{self._api_base}/records/{record_id}" + + data = { + 'zone_id': zone_id, + 'type': record_type, + 'name': record_name, + 'value': str(address), + 'ttl': int(self.settings.get('ttl', 300)) + } + + response = requests.put(url, headers=headers, json=data) + + if response.status_code != 200: + syslog.syslog( + syslog.LOG_ERR, + "Account %s error updating record: HTTP %d - %s" % ( + self.description, response.status_code, response.text + ) + ) + return False + + if self.is_verbose: + syslog.syslog( + syslog.LOG_NOTICE, + "Account %s updated %s %s to %s" % ( + self.description, record_name, record_type, address + ) + ) + + return True + + def _create_record(self, headers, zone_id, record_name, record_type, address): + """Create new record""" + url = f"{self._api_base}/records" + + data = { + 'zone_id': zone_id, + 'type': record_type, + 'name': record_name, + 'value': str(address), + 'ttl': int(self.settings.get('ttl', 300)) + } + + response = requests.post(url, headers=headers, json=data) + + if response.status_code not in [200, 201]: + syslog.syslog( + syslog.LOG_ERR, + "Account %s error creating record: HTTP %d - %s" % ( + self.description, response.status_code, response.text + ) + ) + return False + + if self.is_verbose: + syslog.syslog( + syslog.LOG_NOTICE, + "Account %s created %s %s with %s" % ( + self.description, record_name, record_type, address + ) + ) + + return True + + def execute(self): + if super().execute(): + record_type = "AAAA" if ':' in str(self.current_address) else "A" + headers = self._get_headers() + + zone_id = self._get_zone_id(headers) + if not zone_id: + return False + + zone_name = self._get_zone_name() + + hostnames_raw = self.settings.get('hostnames', '') + hostnames = [h.strip() for h in hostnames_raw.split(',') if h.strip()] + + if not hostnames: + syslog.syslog( + syslog.LOG_ERR, + "Account %s no hostnames configured" % self.description + ) + return False + + all_success = True + for hostname in hostnames: + record_name = self._extract_record_name(hostname, zone_name) + + if self.is_verbose: + syslog.syslog( + syslog.LOG_NOTICE, + "Account %s updating %s (record: %s, type: %s) to %s" % ( + self.description, hostname, record_name, record_type, self.current_address + ) + ) + + record_id = self._get_record_id(headers, zone_id, record_name, record_type) + + if record_id: + success = self._update_record( + headers, zone_id, record_id, record_name, record_type, self.current_address + ) + else: + success = self._create_record( + headers, zone_id, record_name, record_type, self.current_address + ) + + if success: + syslog.syslog( + syslog.LOG_NOTICE, + "Account %s set new IP %s for %s" % ( + self.description, self.current_address, hostname + ) + ) + else: + all_success = False + + if all_success: + self.update_state(address=self.current_address) + return True + + return False diff --git a/dns/ddclient/src/opnsense/scripts/ddclient/lib/account/hostinger.py b/dns/ddclient/src/opnsense/scripts/ddclient/lib/account/hostinger.py new file mode 100755 index 0000000000..b9b004e359 --- /dev/null +++ b/dns/ddclient/src/opnsense/scripts/ddclient/lib/account/hostinger.py @@ -0,0 +1,106 @@ +""" + Copyright (c) 2024 Thomas Cekal + Copyright (c) 2024 Ad Schellevis + Copyright (c) 2026 Leandro Scardua + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +""" + +import json +import syslog +import requests +from . import BaseAccount + +# Hostinger DNS API documentation: +# https://developers.hostinger.com/#tag/dns-zone/PUT/api/dns/v1/zones/{domain} + +class Hostinger(BaseAccount): + _services = { + 'hostinger': 'developers.hostinger.com' + } + + def __init__(self, account: dict): + super().__init__(account) + + @staticmethod + def known_services(): + return Hostinger._services.keys() + + @staticmethod + def match(account): + return account.get('service') in Hostinger._services + + def execute(self): + if super().execute(): + # IPv4/IPv6 + recordType = "AAAA" if str(self.current_address).find(':') > 1 else "A" + + # Validate TTL + ttl = int(self.settings.get('ttl', 300)) if (60 <= int(self.settings.get('ttl', 300)) <= 86400) else 300 + + # Use bearer authentication + url = "https://developers.hostinger.com/api/dns/v1/zones/" + self.settings.get('zone') + + # Build the zone update payload + payload = { + "overwrite": True, + "zone": [ + { + "name": self.settings.get('hostnames'), + "type": recordType, + "ttl": ttl, + "records": [ + { + "content": self.current_address + } + ] + } + ] + } + + headers = { + 'authorization': "Bearer " + self.settings.get('password'), + 'content-type': "application/json", + 'User-Agent': 'OPNsense-dyndns' + } + + # Send IP address update + req = requests.request("PUT", url, data=json.dumps(payload), headers=headers) + if 200 <= req.status_code < 300: + if self.is_verbose: + syslog.syslog( + syslog.LOG_NOTICE, + "Account %s set new ip %s [%s]" % (self.description, self.current_address, req.text.strip()) + ) + + self.update_state(address=self.current_address, status='success') + return True + else: + syslog.syslog( + syslog.LOG_ERR, + "Account %s failed to set new ip %s [%d - %s]" % ( + self.description, self.current_address, req.status_code, req.text.replace('\n', '') + ) + ) + + return False diff --git a/dns/ddclient/src/opnsense/scripts/ddclient/lib/account/netcup.py b/dns/ddclient/src/opnsense/scripts/ddclient/lib/account/netcup.py new file mode 100755 index 0000000000..10322a6309 --- /dev/null +++ b/dns/ddclient/src/opnsense/scripts/ddclient/lib/account/netcup.py @@ -0,0 +1,180 @@ +""" + Copyright (c) 2023 Ingo Lafrenz + Copyright (c) 2023 Ad Schellevis + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + ---------------------------------------------------------------------------------------------------- + Netcup DNS provider, see https://ccp.netcup.net/run/webservice/servers/endpoint.php + +""" +import syslog +import requests +from . import BaseAccount + + +class Netcup(BaseAccount): + _services = ['netcup'] + + def __init__(self, account: dict): + super().__init__(account) + self.settings['APIPassword'] = None + self.settings['APIKey'] = None + # min TTL set to 300 + self.settings['ttl'] = max(int(self.settings['ttl']) if self.settings.get('ttl', '').isdigit() else 0, 300) + + + @staticmethod + def known_services(): + return Netcup._services + + @staticmethod + def match(account): + return account.get('service') in Netcup._services + + def execute(self): + if super().execute(): + if self.settings['hostnames'].find(',') > -1: + self.settings['hostnames'] = self.settings['hostnames'].split(',')[0] + syslog.syslog( + syslog.LOG_WARNING, + "Multiple hostnames detected, ignoring all except first. "+ + "Consider using CNAMEs or create separate DynDNS instances for each hostname." + ) + if self.settings['hostnames'].find('.') == -1: + syslog.syslog(syslog.LOG_ERR, "Incomplete FQDN offerred %s" % self.settings['hostnames']) + return False + + self.domain = self.settings['hostnames'].split('.', self.settings['hostnames'].count('.')-1)[-1] + self.hostname = self.settings['hostnames'].rsplit('.', 2)[0] if self.domain != self.settings['hostnames'] else '@' + + if self.settings['password'].count('|') == 1: + self.settings['APIPassword'], self.settings['APIKey'] = self.settings['password'].split('|') + + if self.settings['APIPassword'] is None or self.settings['APIKey'] is None: + syslog.syslog(syslog.LOG_ERR, "Unable to parse APIPassword|APIKey.") + return False + + self.netcupAPISessionID = self._login() + if not self.netcupAPISessionID: + return False + dnsZoneInfo = self._sendRequest(self._createRequestPayload('infoDnsZone')) + if not dnsZoneInfo: + return False + if str(self.settings['ttl']) != dnsZoneInfo['ttl']: + dnsZoneInfo['ttl'] = str(self.settings['ttl']) + self._sendRequest(self._createRequestPayload('updateDnsZone', {'dnszone': dnsZoneInfo})) + dnsRecordsInfo = self._sendRequest(self._createRequestPayload('infoDnsRecords')) + if not dnsRecordsInfo: + return False + recordType = 'AAAA' if ':' in self.current_address else 'A' + self._updateIpAddress(recordType, dnsRecordsInfo) + self._logout() + self.update_state(address=self.current_address) + return True + + def _login(self): + requestPayload = { + 'action': 'login', + 'param': { + 'customernumber': self.settings['username'], + 'apikey': self.settings['APIKey'], + 'apipassword': self.settings['APIPassword'] + } + } + return self._sendRequest(requestPayload).get('apisessionid', None) + + def _updateDnsRecords(self, hostRecord): + return self._sendRequest( + self._createRequestPayload( + 'updateDnsRecords', + {'dnsrecordset': {'dnsrecords': [hostRecord]}} + ) + ) + + def _logout(self): + requestPayload = { + 'action': 'logout', + 'param': { + 'customernumber': self._account['username'], + 'apikey': self.settings['APIKey'], + 'apisessionid': self.netcupAPISessionID + } + } + return self._sendRequest(requestPayload) + + def _updateIpAddress(self, recordType, dnsRecordsInfo): + matchingRecords = [ + r for r in dnsRecordsInfo['dnsrecords'] if r['type'] == recordType and r['hostname'] == self.hostname + ] + if len(matchingRecords) > 1: + raise Exception(f'Too many {recordType} records for hostname {self.hostname} in DNS zone {self.domain}.') + if matchingRecords: + hostRecord = matchingRecords[0] + else: + hostRecord = { + 'hostname': self.hostname, + 'type': recordType, + 'destination': None + } + currentNetcupIPAddress = hostRecord['destination'] + if self.current_address != currentNetcupIPAddress: + syslog.syslog( + syslog.LOG_NOTICE, + f'IP address change detected. Old IP: {currentNetcupIPAddress}, new IP: {self.current_address}' + ) + hostRecord['destination'] = self.current_address + if self._updateDnsRecords(hostRecord): + syslog.syslog( + syslog.LOG_NOTICE, + f'Successfully updated {recordType} record for {self.hostname}.{self.domain} to {self.current_address}' + ) + else: + syslog.syslog(syslog.LOG_NOTICE, 'IP address has not changed. Nothing to do.') + + def _createRequestPayload(self, action, extraParameters={}): + requestPayload = { + 'action': action, + 'param': { + 'domainname': self.domain, + 'customernumber': self._account['username'], + 'apikey': self.settings['APIKey'], + 'apisessionid': self.netcupAPISessionID, + } + } + requestPayload['param'].update(extraParameters) + return requestPayload + + def _sendRequest(self, payload): + req = requests.post(url='https://ccp.netcup.net/run/webservice/servers/endpoint.php?JSON', json=payload) + try: + resp = req.json() + except requests.exceptions.JSONDecodeError: + resp = {} + if resp.get('status', '') == 'success': + return resp.get('responsedata', {}) + else: + syslog.syslog( + syslog.LOG_ERR, + f"{payload['action']} failed with status {resp['status']}. response: {resp}" + ) + return {} diff --git a/dns/ddclient/src/opnsense/scripts/ddclient/lib/account/powerdns.py b/dns/ddclient/src/opnsense/scripts/ddclient/lib/account/powerdns.py new file mode 100755 index 0000000000..ea931a0cf2 --- /dev/null +++ b/dns/ddclient/src/opnsense/scripts/ddclient/lib/account/powerdns.py @@ -0,0 +1,209 @@ +""" + Copyright (c) 2023 Ad Schellevis + Copyright (c) 2024 Olly Baker + Copyright (c) 2025 Oliver Traber + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +""" +import syslog +import requests +from . import BaseAccount + + +class PowerDNS(BaseAccount): + + def __init__(self, account: dict): + super().__init__(account) + # min TTL set to 300 + self.settings['ttl'] = max(int(self.settings["ttl"]) if self.settings.get("ttl", "").isdigit() else 0, 300) + + @staticmethod + def known_services(): + return {"powerdns": "PowerDNS API"} + + @staticmethod + def match(account): + return account.get("service") in ['powerdns'] + + + def _send_request(self, method, url, params=None, json=None): + headers = { + "User-Agent": "OPNsense-dyndns", + "X-API-Key": self.settings.get("password"), + } + + base_url = "%s/api/v1/servers/%s" % ( + self.settings.get('server'), + self.settings.get("server_id", "localhost") + ) + + url = base_url + url + return requests.request(method=method, url=url, headers=headers, params=params, json=json) + + + def _find_zone_id(self, hostname): + # Get the zone that a record belongs to + if self.is_verbose: + syslog.syslog( + syslog.LOG_NOTICE, + "Account %s trying to get zone ID for hostname %s" + % (self.description, hostname), + ) + + zone = hostname + while (True): + if self.is_verbose: + syslog.syslog( + syslog.LOG_NOTICE, + "Account %s checking if zone %s exists" + % (self.description, zone), + ) + + response = self._send_request(method="GET", url="/zones", params={"zone": zone}) + + if response.status_code == 200: + try: + payload = response.json() + # Check if a zone was found + if len(payload) == 0: + # Move one up in hierarchy + zone = '.'.join(zone.split('.')[1:]) + # Fail if root is reached + if zone == "": + syslog.syslog( + syslog.LOG_ERR, + "Account %s error getting zone ID for hostname %s - No matching zone found on server" + % (self.description, hostname), + ) + return None + else: + continue + else: + if self.is_verbose: + syslog.syslog( + syslog.LOG_NOTICE, + "Account %s found zone %s for hostname %s" + % (self.description, zone, hostname), + ) + return payload[0].get("id") + except requests.exceptions.JSONDecodeError: + syslog.syslog( + syslog.LOG_ERR, + "Account %s error getting zone ID for hostname %s - Failed to decode response as JSON. Response: %s" + % (self.description, hostname, response.text), + ) + return None + else: + syslog.syslog( + syslog.LOG_ERR, + "Account %s error getting zone ID for hostname %s HTTP %s. Response: %s" + % ( + self.description, + hostname, + response.status_code, + response.text, + ), + ) + return None + + def _replace_rrset(self, hostname, zone_id, record_type, content): + # Replace or create rrset for record + payload = { + "rrsets": [ + { + "name": hostname, + "type": record_type, + "ttl": int(self.settings.get("ttl")), + "changetype": "REPLACE", + "records": [ + {"content": content} + ] + } + ] + } + + response = self._send_request(method="PATCH", url=("/zones/" + zone_id), json=payload) + if response.status_code == 204: + # Success + return True + else: + # Failure + syslog.syslog( + syslog.LOG_ERR, + "Account %s error updating hostname %s in zone %s - HTTP %s Response: %s" + % ( + self.description, + hostname, + zone_id, + response.status_code, + response.text, + ), + ) + return False + + def execute(self): + if super().execute(): + record_type = "AAAA" if str(self.current_address).find(":") > 1 else "A" + + if self.is_verbose: + syslog.syslog( + syslog.LOG_NOTICE, + "Account %s current IP is %s (%s)" + % (self.description, self.current_address, record_type), + ) + + syslog.syslog( + syslog.LOG_NOTICE, + "Account %s updating hostnames %s" + % (self.description, self.settings.get("hostnames", "")), + ) + + # Update each hostname + for hostname in self.settings.get("hostnames", "").split(","): + + # Make hostname absolute + if not hostname.endswith("."): + hostname = hostname + "." + + # Get id of zone the hostname belongs to + zone_id = self._find_zone_id(hostname) + + # If zone can't be found, skip + if zone_id == None: + continue + + # Update record set + if self._replace_rrset(hostname, zone_id, record_type, content=self.current_address) and self.is_verbose: + syslog.syslog( + syslog.LOG_NOTICE, + "Account %s successfully updated hostname %s (%s) to IP %s" + % ( + self.description, + hostname, + record_type, + self.current_address, + ), + ) + self.update_state(address=self.current_address) + return True + return False diff --git a/dns/ddclient/src/opnsense/scripts/ddclient/lib/address.py b/dns/ddclient/src/opnsense/scripts/ddclient/lib/address.py new file mode 100755 index 0000000000..396b4720b1 --- /dev/null +++ b/dns/ddclient/src/opnsense/scripts/ddclient/lib/address.py @@ -0,0 +1,153 @@ +""" + Copyright (c) 2022-2025 Ad Schellevis + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +""" +import subprocess +import re +import ipaddress +import dns.resolver +import dns.rdataclass +from urllib.parse import urlparse + +checkip_service_list = { + 'akamai': '%s://whatismyip.akamai.com', + 'akamai-ipv4': '%s://ipv4.whatismyip.akamai.com', + 'akamai-ipv6': '%s://ipv6.whatismyip.akamai.com', + 'cloudflare': '%s://one.one.one.one/cdn-cgi/trace', + 'cloudflare-ipv4': '%s://1.1.1.1/cdn-cgi/trace', + 'cloudflare-ipv6': '%s://[2606:4700:4700::1111]/cdn-cgi/trace', + 'dynu-ipv4': '%s://ipcheck.dynu.com/', + 'dynu-ipv6': '%s://ipcheckv6.dynu.com/', + 'freedns': '%s://freedns.afraid.org/dynamic/check.php', + 'he': '%s://checkip.dns.he.net/', + 'icanhazip': '%s://icanhazip.com/', + 'ipify-ipv4': '%s://api.ipify.org/', + 'ipify-ipv6': '%s://api6.ipify.org/', + 'loopia': '%s://dns.loopia.se/checkip/checkip.php', + 'myonlineportal': '%s://myonlineportal.net/checkip', + 'noip-ipv4': '%s://ip1.dynupdate.no-ip.com/', + 'noip-ipv6': '%s://ip1.dynupdate6.no-ip.com/', + 'nsupdate.info-ipv4': '%s://ipv4.nsupdate.info/myip', + 'nsupdate.info-ipv6': '%s://ipv6.nsupdate.info/myip', + 'zoneedit': '%s://dynamic.zoneedit.com/checkip.html' +} + +checkip_dns_list = { + 'cloudflare-dns': { + 'nameservers': ['1.1.1.1','1.0.0.1'], + 'resolve_params': { + 'qname': 'whoami.cloudflare', + 'rdtype': 'TXT', + 'rdclass': dns.rdataclass.from_text('CH') + } + } +} + +def registered_services(): + return list(checkip_service_list.keys()) + list(checkip_dns_list.keys()) + +def extract_address(host, txt): + """ Extract first IPv4 or IPv6 address from provided string + :param txt: text blob + :return: str + """ + for regexp in [r'(?:\d{1,3}\.){3}\d{1,3}', r'([a-f0-9:]+:+)+[a-f0-9]+']: + matches = re.finditer(regexp, txt) + for match in matches: + if match.group() != host: + try: + ipaddress.ip_address(match.group()) + return match.group() + except ValueError: + pass + return "" + + +def transform_ip(ip, ipv6host=None): + """ Changes ipv6 addresses if interface identifier is given + :param ip: ip address + :param ipv6host: 64 bit interface identifier + :return ipaddress.IPv4Address|ipaddress.IPv6Address + :raises ValueError: If the input can not be converted to an IPaddress + """ + if ipv6host and ip.find(':') > 0: + # extract 64 bit long prefix and add ipv6host [64]bits + return ipaddress.ip_address( + ipaddress.ip_network("%s/64" % ip, strict=False).network_address.exploded[0:19] + + ipaddress.ip_address(ipv6host).exploded[19:] + ) + else: + return ipaddress.ip_address(ip) + + +def checkip(service, proto='https', timeout='10', interface=None, dynipv6host=None): + """ find ip address using external web services defined in checkip_service_list + or dns services defined in checkip_dns_list + :param proto: protocol + :param timeout: timeout in seconds + :param interface: bind to interface + :param dynipv6host: optional partial ipv6 address + :return: str + """ + if service.lstrip('web_') in checkip_service_list: + # configuration name, strip web_ part + service = service.lstrip('web_') + params = ['/usr/local/bin/curl', '-m', timeout] + if interface is not None: + params.append("--interface") + params.append(interface) + url = checkip_service_list[service] % proto + params.append(url) + extracted_address = extract_address(urlparse(url).hostname, + subprocess.run(params, capture_output=True, text=True).stdout) + try: + return str(transform_ip(extracted_address, dynipv6host)) + except ValueError: + # invalid address + return "" + elif service in ['if', 'if6'] and interface is not None: + # return first non private IPv[4|6] interface address + ifcfg = subprocess.run(['/sbin/ifconfig', interface], capture_output=True, text=True).stdout + for line in ifcfg.split('\n'): + if line.startswith('\tinet'): + parts = line.split() + if (parts[0] == 'inet' and service == 'if') or (parts[0] == 'inet6' and service == 'if6'): + try: + address = transform_ip(parts[1], dynipv6host) + if address.is_global: + return str(address) + except ValueError: + continue + elif service.lstrip('dns_') in checkip_dns_list: + svc_info = checkip_dns_list[service.lstrip('dns_')] + resolve_params = svc_info['resolve_params'] + dns_resolver = dns.resolver.Resolver() + dns_resolver.nameservers = svc_info['nameservers'] + try: + dns_response = dns_resolver.resolve(**resolve_params) + return dns_response[0].to_text().strip('"') + except: + return "" + else: + return "" diff --git a/dns/ddclient/src/opnsense/scripts/ddclient/lib/poller.py b/dns/ddclient/src/opnsense/scripts/ddclient/lib/poller.py new file mode 100755 index 0000000000..bb1d4900b1 --- /dev/null +++ b/dns/ddclient/src/opnsense/scripts/ddclient/lib/poller.py @@ -0,0 +1,175 @@ +""" + Copyright (c) 2023 Ad Schellevis + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +""" +import fcntl +import syslog +import glob +import importlib +import sys +import os +import time +import ujson +import ipaddress +from .account import BaseAccount + + +class AccountFactory: + def __init__(self): + self._account_classes = list() + self._register() + + def _register(self): + """ Register all account (type) classes. + These usually describe a protocol (like dyndns2) + """ + pkg_name = "%s.account" % __name__[:-len(os.path.splitext(os.path.basename(__file__))[0])-1] + all_account_classes = list() + for filename in glob.glob("%s/account/*.py" % os.path.dirname(__file__)): + importlib.import_module(".%s" % os.path.splitext(os.path.basename(filename))[0], pkg_name) + + for module_name in dir(sys.modules[pkg_name]): + for attribute_name in dir(getattr(sys.modules[pkg_name], module_name)): + cls = getattr(getattr(sys.modules[pkg_name], module_name), attribute_name) + if isinstance(cls, type) and issubclass(cls, BaseAccount) and cls != BaseAccount: + all_account_classes.append(cls) + + self._account_classes = sorted(all_account_classes, key=lambda k: k._priority) + + def get(self, account: dict): + for handler in self._account_classes: + if handler.match(account): + return handler(account) + + def known_services(self): + all_services = {} + for handler in self._account_classes: + data = handler.known_services() + if type(data) is dict: + all_services.update(data) + else: + for item in data: + all_services[item] = item + return all_services + + +class Poller: + def __init__(self, config_filename, status_filename): + self._config_filename = config_filename + self._status_filename = status_filename + self._accounts = {} + self._general_settings = {} + syslog.openlog('ddclient', facility=syslog.LOG_LOCAL4) + self.startup() + self.run() + + @property + def is_verbose(self): + return self._general_settings.get('verbose') is True + + @property + def is_enabled(self): + return self._general_settings.get('enabled') is True + + @property + def poll_interval(self): + return self._general_settings.get('daemon_delay', 60) + + def startup(self): + account_factory = AccountFactory() + with open(self._config_filename) as f: + cnf = ujson.load(f) + if type(cnf.get('general')) is dict: + self._general_settings = cnf.get('general') + if type(cnf.get('accounts')) is list: + for account in cnf.get('accounts'): + account['verbose'] = self.is_verbose + acc = account_factory.get(account) + if acc: + self._accounts[acc.id] = acc + if self.is_verbose: + syslog.syslog( + syslog.LOG_NOTICE, + "Account %s uses %s for service" % (acc.description, acc.__class__.__name__) + ) + elif self.is_verbose: + syslog.syslog( + syslog.LOG_NOTICE, + "Unable to find a suitable target for account %(id)s [%(description)s]" % account + ) + if len(self._accounts) > 0 and os.path.isfile(self._status_filename): + with open(self._status_filename) as f: + try: + state = ujson.load(f) + if type(state) is dict: + for sid in state: + if sid in self._accounts: + self._accounts[sid].state = state[sid] + except ValueError: + syslog.syslog(syslog.LOG_ERR, "Unable to read file %s" % self._status_filename) + + def flush_status(self): + fhandle = open(self._status_filename, 'a+') + try: + fcntl.flock(fhandle, fcntl.LOCK_EX | fcntl.LOCK_NB) + except IOError: + syslog.syslog(syslog.LOG_ERR, "Unable to flush status, %s already locked" % self._status_filename) + return + fhandle.seek(0) + fhandle.truncate() + data = {} + for acc_id in self._accounts: + data[acc_id] = self._accounts[acc_id].state + fhandle.write(ujson.dumps(data)) + fhandle.close() + + def run(self): + while True: + needs_flush = False + for acc in self._accounts.values(): + if time.time() - acc.atime > self.poll_interval: + if self.is_verbose: + syslog.syslog(syslog.LOG_NOTICE, "Account %s executing" % acc.description) + try: + if acc.execute(): + if self.is_verbose: + syslog.syslog(syslog.LOG_NOTICE, "Account %s updated" % acc.description) + needs_flush = True + else: + if self.is_verbose: + syslog.syslog(syslog.LOG_NOTICE, "Account %s not modified" % acc.description) + # update last accessed timestamp + acc.update_state(None) + except Exception as e: + # fatal exception, update atime so we're not going to retry too soon + acc.update_state(None) + syslog.syslog(syslog.LOG_ERR, "Account %s raised fatal error (%s)" % (acc.description, e)) + + if needs_flush: + if self.is_verbose: + syslog.syslog(syslog.LOG_NOTICE, "Flush dyndns status to disk") + self.flush_status() + + # XXX: needs better poll interval calculation + time.sleep(5) diff --git a/dns/ddclient/src/opnsense/scripts/ddclient/setup.sh b/dns/ddclient/src/opnsense/scripts/ddclient/setup.sh new file mode 100755 index 0000000000..7cf7b8d4ad --- /dev/null +++ b/dns/ddclient/src/opnsense/scripts/ddclient/setup.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +for CONF in /usr/local/etc/ddclient.conf /usr/local/etc/ddclient.json; do + chmod 0600 ${CONF} +done diff --git a/dns/ddclient/src/opnsense/scripts/ddclient/stats b/dns/ddclient/src/opnsense/scripts/ddclient/stats new file mode 100755 index 0000000000..750f16eb19 --- /dev/null +++ b/dns/ddclient/src/opnsense/scripts/ddclient/stats @@ -0,0 +1,65 @@ +#!/usr/local/bin/python3 + +""" + Copyright (c) 2022 Ad Schellevis + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +""" + +import os +import json + +filename = '/var/tmp/ddclient.cache' +filename_new = '/var/tmp/ddclient_opn.status' + +result = {"hosts": {}} + +# both ddclient and "opnsense" ddclient exist, only use old when currently in use +if os.path.isfile(filename) and os.path.isfile(filename_new): + if os.path.getmtime(filename) < os.path.getmtime(filename_new): + filename = None + +if filename is not None and os.path.isfile(filename): + with open(filename, "r") as fhandle: + for idx, row in enumerate(fhandle): + if idx == 0: + result['version'] = row.strip('#\n ') + elif idx == 1: + tmp = row.split('(')[-1].split(')')[0] + if tmp.isdigit(): + result['updated'] = int(tmp) + elif tmp.startswith('#') is False: + record = {} + for pair in row.split(','): + parts = pair.split('=') + if len(parts) == 2: + record[parts[0]] = parts[1] + if 'host' in record: + result['hosts'][record['host']] = record +elif os.path.isfile(filename_new): + # output will look completely different when our implementation is used, the model knows how to parse this + # (see AccountField.php) + with open(filename_new) as f: + result = json.load(f) + +print(json.dumps(result)) diff --git a/dns/ddclient/src/opnsense/service/conf/actions.d/actions_ddclient.conf b/dns/ddclient/src/opnsense/service/conf/actions.d/actions_ddclient.conf new file mode 100644 index 0000000000..8349ebdfec --- /dev/null +++ b/dns/ddclient/src/opnsense/service/conf/actions.d/actions_ddclient.conf @@ -0,0 +1,45 @@ +[start] +command: + /usr/local/etc/rc.d/ddclient start; + /usr/local/etc/rc.d/ddclient_opn start +type:script +message:starting ddclient + +[stop] +command:pkill -F /var/run/ddclient.pid 2> /dev/null; /usr/local/etc/rc.d/ddclient_opn onestop 2> /dev/null +errors:no +type:script +message:stopping ddclient + +[status] +command:/usr/local/sbin/pluginctl -s ddclient status +type:script_output +message:get ddclient status + +[restart] +command: + pkill -F /var/run/ddclient.pid 2> /dev/null; + /usr/local/etc/rc.d/ddclient restart; + /usr/local/etc/rc.d/ddclient_opn restart +type:script +message:restarting ddclient +description:Restart ddclient service + +[force] +command: + rm /var/tmp/ddclient_opn.status 2>/dev/null; + /usr/local/etc/rc.d/ddclient_opn restart 2>/dev/null; + /usr/local/sbin/ddclient -force +type:script +message:forcing ddclient update +description:Force ddclient update + +[statistics] +command:/usr/local/opnsense/scripts/ddclient/stats +type:script_output +message:get ddclient statistics + +[opnbackend.supported] +command:/usr/local/opnsense/scripts/ddclient/ddclient_opn.py -l +type:script_output +message:get ddclient statistics diff --git a/dns/ddclient/src/opnsense/service/templates/OPNsense/Syslog/local/ddclient.conf b/dns/ddclient/src/opnsense/service/templates/OPNsense/Syslog/local/ddclient.conf new file mode 100644 index 0000000000..7dc55b0775 --- /dev/null +++ b/dns/ddclient/src/opnsense/service/templates/OPNsense/Syslog/local/ddclient.conf @@ -0,0 +1,6 @@ +################################################################### +# Local syslog-ng configuration filter definition [ddclient]. +################################################################### +filter f_local_ddclient { + program("ddclient"); +}; diff --git a/dns/ddclient/src/opnsense/service/templates/OPNsense/ddclient/+TARGETS b/dns/ddclient/src/opnsense/service/templates/OPNsense/ddclient/+TARGETS new file mode 100644 index 0000000000..63822fd4f2 --- /dev/null +++ b/dns/ddclient/src/opnsense/service/templates/OPNsense/ddclient/+TARGETS @@ -0,0 +1,4 @@ +rc.conf.d:/etc/rc.conf.d/ddclient +ddclient_opn.rc.conf.d:/etc/rc.conf.d/ddclient_opn +ddclient.conf:/usr/local/etc/ddclient.conf +ddclient.json:/usr/local/etc/ddclient.json diff --git a/dns/ddclient/src/opnsense/service/templates/OPNsense/ddclient/ddclient.conf b/dns/ddclient/src/opnsense/service/templates/OPNsense/ddclient/ddclient.conf new file mode 100644 index 0000000000..d82eb19d9d --- /dev/null +++ b/dns/ddclient/src/opnsense/service/templates/OPNsense/ddclient/ddclient.conf @@ -0,0 +1,105 @@ +{% from 'OPNsense/Macros/interface.macro' import physical_interface %} +syslog=yes # log update msgs to syslog +pid=/var/run/ddclient.pid # record PID in file. +{% if not helpers.empty('OPNsense.DynDNS.general.verbose') %} +verbose=yes +{% endif %} +{% set accounts = [] %} +{% set force_ssl = [] %} +{% if helpers.exists('OPNsense.DynDNS.accounts.account') and OPNsense.DynDNS.general.backend == 'ddclient' %} +{% for account in helpers.toList('OPNsense.DynDNS.accounts.account') %} +{% if account.enabled|default('0') == '1' %} +{% do accounts.append(account) %} +{% if account.force_ssl|default('0') == '1' %} +{% do force_ssl.append(1) %} +{% endif %} +{% endif %} +{% endfor %} +{% endif %} +{% if force_ssl %} +ssl=yes +{% endif %} +{% for account in accounts %} + +{% if account.checkip == 'if' %} +{% if not helpers.empty('OPNsense.DynDNS.general.allowipv6') %} +usev6=ifv6, ifv6={{physical_interface(account.interface)}}, \ +{% endif %} +usev4=ifv4, ifv4={{physical_interface(account.interface)}}, \ +{% elif account.checkip.startswith('web_') %} +{% if account.interface %} +use=cmd, cmd="/usr/local/opnsense/scripts/ddclient/checkip -i {{physical_interface(account.interface)}} -t {{account.force_ssl}} -s {{account.checkip[4:]}} --timeout {{account.checkip_timeout|default('10')}}", \ +{% else %} +use=cmd, cmd="/usr/local/opnsense/scripts/ddclient/checkip -t {{account.force_ssl}} -s {{account.checkip[4:]}} --timeout {{account.checkip_timeout|default('10')}}", \ +{% endif %} +{% endif %} +{% if account.service == 'custom' %} +protocol={{account.protocol}}, \ +server={{account.server}}, \ +{% elif account.service in ['cloudflare', 'digitalocean', 'dnspodcn'] %} +protocol={{account.service}}, \ +zone={{account.zone}}, \ +{% elif account.service == 'cloudns' %} +protocol={{account.service}}, \ +dynurl=https://ipv4.cloudns.net/api/dynamicURL/?q={{account.password}}, \ +{% elif account.service == 'hosting1984' %} +protocol=1984, \ +{% elif account.service in ['godaddy', 'gandi'] %} +protocol={{account.service}}, \ +zone={{account.zone}}, \ +ttl={{account.ttl}}, \ +{% elif account.service == 'hetzner' %} +protocol={{account.service}}, \ +zone={{account.zone}}, \ +{% elif account.service == 'dns-o-matic' %} +protocol=dyndns2, \ +server=updates.dnsomatic.com, \ +{% elif account.service == 'dynu' %} +protocol=dyndns2, \ +server=api.dynu.com, \ +{% elif account.service == 'he-net' %} +protocol=dyndns2, \ +server=dyn.dns.he.net, \ +{% elif account.service == 'he-net-tunnel' %} +protocol=dyndns2, \ +server=ipv4.tunnelbroker.net, \ +{% elif account.service == 'inwx' %} +protocol=dyndns2, \ +server=dyndns.inwx.com, \ +{% elif account.service == 'loopia' %} +protocol=dyndns2, \ +server=dyndns.loopia.se, \ +{% elif account.service == 'nsupdatev4' %} +protocol=dyndns2, \ +server=ipv4.nsupdate.info, \ +{% elif account.service == 'nsupdatev6' %} +protocol=dyndns2, \ +server=ipv6.nsupdate.info, \ +{% elif account.service == 'servercow' %} +protocol=dyndns2, \ +server=dyndns.servercow.de, \ +{% elif account.service == 'spdyn' %} +protocol=dyndns2, \ +server=update.spdyn.de, \ +{% elif account.service == 'strato' %} +protocol=dyndns2, \ +server=dyndns.strato.com, \ +{% elif account.service == 'ovh' %} +protocol=dyndns2, \ +server=www.ovh.com, \ +{% elif account.service == 'porkbun' %} +protocol={{account.service}}, \ +apikey={{account.username}}, \ +secretapikey={{account.password}}, \ +{% else %} +protocol={{account.service}}, \ +{% endif %} +{% if account.wildcard|default('0') == '1' %} +wildcard=yes, \ +{% endif %} +{% if account.username %} +login={{account.username}}, \ +{% endif %} +password={{account.password}} \ +{{account.hostnames}} +{% endfor %} diff --git a/dns/ddclient/src/opnsense/service/templates/OPNsense/ddclient/ddclient.json b/dns/ddclient/src/opnsense/service/templates/OPNsense/ddclient/ddclient.json new file mode 100644 index 0000000000..4a2cc7a852 --- /dev/null +++ b/dns/ddclient/src/opnsense/service/templates/OPNsense/ddclient/ddclient.json @@ -0,0 +1,34 @@ +{% from 'OPNsense/Macros/interface.macro' import physical_interface %} +{ + "general": { + "enabled": {{ "true" if not helpers.empty('OPNsense.DynDNS.general.enabled') else "false" }}, + "verbose": {{ "true" if not helpers.empty('OPNsense.DynDNS.general.verbose') else "false" }}, + "allowipv6": {{ "true" if not helpers.empty('OPNsense.DynDNS.general.allowipv6') else "false" }}, + "daemon_delay": {{OPNsense.DynDNS.general.daemon_delay|default('300')}} + }, + "accounts": [ +{% if helpers.exists('OPNsense.DynDNS.accounts.account') %} +{% for account in helpers.toList('OPNsense.DynDNS.accounts.account') | selectattr('enabled', 'equalto', '1') %} + { + "id": "{{ account['@uuid'] }}", + "service": "{{ account.service }}", + "protocol": "{{ account.protocol }}", + "server": {{ account.server | default('') | tojson }}, + "resourceId": {{ account.resourceId | default('') | tojson }}, + "username": {{ account.username | default('') | tojson }}, + "password": {{ account.password | default('') | tojson }}, + "hostnames": "{{ account.hostnames }}", + "wildcard": {{ "true" if account.wildcard == '1' else "false"}}, + "zone": "{{ account.zone }}", + "checkip": "{{ account.checkip }}", + "interface": "{% if account.interface %}{{physical_interface(account.interface)}}{% endif %}", + "dynipv6host": "{{ account.dynipv6host }}", + "checkip_timeout": {{ account.checkip_timeout }}, + "force_ssl": {{ "true" if account.force_ssl == '1' else "false"}}, + "ttl": "{{ account.ttl }}", + "description": {{ account.description | default('') | tojson }} + }{{ "," if not loop.last else ""}} +{% endfor %} +{% endif %} + ] +} diff --git a/dns/ddclient/src/opnsense/service/templates/OPNsense/ddclient/ddclient_opn.rc.conf.d b/dns/ddclient/src/opnsense/service/templates/OPNsense/ddclient/ddclient_opn.rc.conf.d new file mode 100644 index 0000000000..b6a9cf0140 --- /dev/null +++ b/dns/ddclient/src/opnsense/service/templates/OPNsense/ddclient/ddclient_opn.rc.conf.d @@ -0,0 +1,6 @@ +{% if not helpers.empty('OPNsense.DynDNS.general.enabled') and OPNsense.DynDNS.general.backend == 'opnsense' %} +ddclient_opn_enable="YES" +ddclient_opn_setup="/usr/local/opnsense/scripts/ddclient/setup.sh" +{% else %} +ddclient_opn_enable="NO" +{% endif %} diff --git a/dns/ddclient/src/opnsense/service/templates/OPNsense/ddclient/rc.conf.d b/dns/ddclient/src/opnsense/service/templates/OPNsense/ddclient/rc.conf.d new file mode 100644 index 0000000000..ecb315a717 --- /dev/null +++ b/dns/ddclient/src/opnsense/service/templates/OPNsense/ddclient/rc.conf.d @@ -0,0 +1,7 @@ +{% if not helpers.empty('OPNsense.DynDNS.general.enabled') and OPNsense.DynDNS.general.backend == 'ddclient' %} +ddclient_enable="YES" +ddclient_setup="/usr/local/opnsense/scripts/ddclient/setup.sh" +ddclient_flags="-daemon {{OPNsense.DynDNS.general.daemon_delay|default('300')}}" +{% else %} +ddclient_enable="NO" +{% endif %} diff --git a/dns/ddclient/src/opnsense/www/js/widgets/Dyndns.js b/dns/ddclient/src/opnsense/www/js/widgets/Dyndns.js new file mode 100644 index 0000000000..bc4c359abb --- /dev/null +++ b/dns/ddclient/src/opnsense/www/js/widgets/Dyndns.js @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2024 Deciso B.V. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +export default class Dyndns extends BaseTableWidget { + constructor() { + super(); + } + + getGridOptions() { + return { + // Trigger overflow-y:scroll after 650px height + sizeToContent: 650 + }; + } + + getMarkup() { + let $container = $('
'); + let $dyndnsTable = this.createTable('dyndnsTable', { + headerPosition: 'top', + headers: [ + this.translations.service, + this.translations.domains + ] + }); + + $container.append($dyndnsTable); + return $container; + } + + async onWidgetTick() { + // Check if DynDNS is enabled + const statusData = await this.ajaxCall(`/api/dyndns/service/${'status'}`); + if (!statusData || statusData.status !== "running") { + this.displayError(this.translations.servicedisabled); + return; + } + + // Fetch DynDNS account information + const accountData = await this.ajaxCall(`/api/dyndns/accounts/${'search_item'}`); + if (!accountData || !accountData.rows || accountData.rows.length === 0) { + this.displayError(this.translations.noaccount); + return; + } + + this.processAccounts(accountData.rows); + } + + // Utility function to display errors within the widget + displayError(message) { + const $error = $(` + + `); + $('#dyndnsTable').empty().append($error); + } + + processAccounts(accounts) { + if (!this.dataChanged('accounts', accounts)) { + return; + } + + $('.dyndns-tooltip').tooltip('hide'); + + let rows = []; + accounts.forEach(account => { + let colorClass = account.enabled === "1" ? 'text-success' : 'text-danger'; + let tooltipText = account.enabled === "1" ? this.translations.enabled : this.translations.disabled; + + let domainNames = account.hostnames.split(',') + .map(domain => `
${domain}
`) + .join(''); + + // Convert time to a localized format + let localizedTime = account.current_mtime ? new Date(account.current_mtime).toLocaleString() : this.translations.undefined; + let currentIp = account.current_ip || this.translations.undefined; + + let row = [ + ` + +
${this.translations.currentip}: ${currentIp}
+
${this.translations.currentmtime}: ${localizedTime}
+ `, + ` +
${domainNames}
+ ` + ]; + + rows.push(row); + }); + + // Update table with rows + super.updateTable('dyndnsTable', rows); + + // Initialize tooltips + $('.dyndns-tooltip').tooltip({container: 'body'}); + } + + onWidgetResize(elem, width, height) { + if (width < 320) { + $('#header_dyndnsTable').hide(); + $('.domain-names').parent().hide(); + } else { + $('#header_dyndnsTable').show(); + $('.domain-names').parent().show(); + } + return true; // Return true to force the grid to update its layout + } +} diff --git a/dns/ddclient/src/opnsense/www/js/widgets/Metadata/Dyndns.xml b/dns/ddclient/src/opnsense/www/js/widgets/Metadata/Dyndns.xml new file mode 100644 index 0000000000..17569ac10b --- /dev/null +++ b/dns/ddclient/src/opnsense/www/js/widgets/Metadata/Dyndns.xml @@ -0,0 +1,21 @@ + + + Dyndns.js + + /api/dyndns/accounts/* + /api/dyndns/service/* + + + Dynamic DNS + DynDNS service is not running + No DynDNS account information available + Enabled + Disabled + Current IP + Updated + Hostnames + Accounts + N/A + + + diff --git a/dns/dnscrypt-proxy/Makefile b/dns/dnscrypt-proxy/Makefile index 0ab7f039e9..cf19aed0a8 100644 --- a/dns/dnscrypt-proxy/Makefile +++ b/dns/dnscrypt-proxy/Makefile @@ -1,6 +1,6 @@ PLUGIN_NAME= dnscrypt-proxy -PLUGIN_VERSION= 1.7 -PLUGIN_REVISION= 1 +PLUGIN_VERSION= 1.16 +PLUGIN_REVISION= 2 PLUGIN_COMMENT= Flexible DNS proxy supporting DNSCrypt and DoH PLUGIN_DEPENDS= dnscrypt-proxy2 PLUGIN_MAINTAINER= m.muenz@gmail.com diff --git a/dns/dnscrypt-proxy/pkg-descr b/dns/dnscrypt-proxy/pkg-descr index a5b69a78c9..bc9af91cf2 100644 --- a/dns/dnscrypt-proxy/pkg-descr +++ b/dns/dnscrypt-proxy/pkg-descr @@ -1,10 +1,50 @@ A flexible DNS proxy, with support for modern encrypted DNS protocols such as DNSCrypt v2 and DNS-over-HTTPS. - Plugin Changelog ================ +1.16 + +* Fix ODoH servers not working (contributed by Pascal Herget) +* Fix bootstrap_resolvers with multiple comma-separated servers (contributed by Andrei Hodorog) + +1.15 + +* Update plugin for dnscrypt-proxy 2.1 (contributed by Adrian Fedoreanu and Michael Muenz) + +1.14 + +* Fix display of the config with more than one disabled server in GUI (contributed by Evgeny Grin (karlson2k)) + +1.13 + +* Add necessary hooks to allow the plugin to be used as a standalone core DNS server +* Changed default listening addresses to 0.0.0.0 for new users +* Prevent using a port being used by another DNS service + +1.12 + +* Support specifying relays for anonymous DNS + +1.11 + +* Fix DNSBL update due to FreeBSD13 upgrade (sed syntax) +* Fix "manual disable of specific servers" when more than one server is specified (contributed by Evgeny Grin (karlson2k)) + +1.10 + +* Add option to enable/disable local query logs +* Add manual disable of specific servers + +1.9 + +* Fix logging due to Phalcon4 update + +1.8 + +* Remove 8 discontinued DNSBL lists and 2 that are not updated any more + 1.7 * Add comment field to whitelist section diff --git a/dns/dnscrypt-proxy/src/etc/inc/plugins.inc.d/dnscryptproxy.inc b/dns/dnscrypt-proxy/src/etc/inc/plugins.inc.d/dnscryptproxy.inc index 3f3588874e..e236cd6839 100644 --- a/dns/dnscrypt-proxy/src/etc/inc/plugins.inc.d/dnscryptproxy.inc +++ b/dns/dnscrypt-proxy/src/etc/inc/plugins.inc.d/dnscryptproxy.inc @@ -1,30 +1,31 @@ - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, - INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY - AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, - OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - POSSIBILITY OF SUCH DAMAGE. -*/ + * Copyright (C) 2018 Michael Muenz + * Copyright (C) 2023 Franco Fichtner + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ function dnscryptproxy_enabled() { @@ -32,24 +33,55 @@ function dnscryptproxy_enabled() return (string)$model->enabled == '1'; } +function dnscryptproxy_configure() +{ + return [ + 'dns' => ['dnscryptproxy_configure_do'], + ]; +} + function dnscryptproxy_services() { - $services = array(); + $services = []; if (!dnscryptproxy_enabled()) { return $services; } - $services[] = array( + $model = new \OPNsense\Dnscryptproxy\General(); + $ports = []; + + /* DNS service is eligable for core use when either 0.0.0.0 or :: are set */ + foreach (explode(',', (string)$model->listen_addresses) as $addrport) { + if (preg_match('/^0\.0\.0\.0:([\d]+)$/', $addrport, $matches)) { + $ports[$matches[1]] = 1; + } elseif (preg_match('/^\[::\]:([\d]+)$/', $addrport, $matches)) { + $ports[$matches[1]] = 1; + } + } + + $services[] = [ + /* the port may still be something other than 53, but it's safe to register a conflict for it */ + 'dns_ports' => array_keys($ports), 'description' => gettext('DNSCrypt-Proxy'), - 'configd' => array( - 'restart' => array('dnscryptproxy restart'), - 'start' => array('dnscryptproxy start'), - 'stop' => array('dnscryptproxy stop'), - ), + 'configd' => [ + 'restart' => ['dnscryptproxy restart'], + 'start' => ['dnscryptproxy start'], + 'stop' => ['dnscryptproxy stop'], + ], + 'pid' => '/var/run/dnscrypt-proxy.pid', 'name' => 'dnscrypt-proxy', - 'pid' => '/var/run/dnscrypt-proxy.pid' - ); + ]; return $services; } + +function dnscryptproxy_configure_do($verbose) +{ + service_log('Starting DNSCrypt-Proxy...', $verbose); + + configd_run('template reload OPNsense/Dnscryptproxy'); + configd_run('dnscryptproxy restart'); + + service_log("done.\n", $verbose); +} diff --git a/dns/dnscrypt-proxy/src/opnsense/mvc/app/controllers/OPNsense/Dnscryptproxy/Api/CloakController.php b/dns/dnscrypt-proxy/src/opnsense/mvc/app/controllers/OPNsense/Dnscryptproxy/Api/CloakController.php index 98fc77a5b5..efc47624ea 100644 --- a/dns/dnscrypt-proxy/src/opnsense/mvc/app/controllers/OPNsense/Dnscryptproxy/Api/CloakController.php +++ b/dns/dnscrypt-proxy/src/opnsense/mvc/app/controllers/OPNsense/Dnscryptproxy/Api/CloakController.php @@ -1,31 +1,29 @@ +/* + * Copyright (C) 2018 Michael Muenz + * All rights reserved. * - * All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: * - * 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. * - * 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. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. */ namespace OPNsense\Dnscryptproxy\Api; @@ -40,25 +38,29 @@ class CloakController extends ApiMutableModelControllerBase public function searchCloakAction() { - return $this->searchBase('cloaks.cloak', array("enabled", "name", "destination")); + return $this->searchBase('cloaks.cloak', ['enabled', 'name', 'destination']); } + public function getCloakAction($uuid = null) { - $this->sessionClose(); return $this->getBase('cloak', 'cloaks.cloak', $uuid); } + public function addCloakAction() { return $this->addBase('cloak', 'cloaks.cloak'); } + public function delCloakAction($uuid) { return $this->delBase('cloaks.cloak', $uuid); } + public function setCloakAction($uuid) { return $this->setBase('cloak', 'cloaks.cloak', $uuid); } + public function toggleCloakAction($uuid) { return $this->toggleBase('cloaks.cloak', $uuid); diff --git a/dns/dnscrypt-proxy/src/opnsense/mvc/app/controllers/OPNsense/Dnscryptproxy/Api/ForwardController.php b/dns/dnscrypt-proxy/src/opnsense/mvc/app/controllers/OPNsense/Dnscryptproxy/Api/ForwardController.php index cff952a76b..2d2c6dd16d 100644 --- a/dns/dnscrypt-proxy/src/opnsense/mvc/app/controllers/OPNsense/Dnscryptproxy/Api/ForwardController.php +++ b/dns/dnscrypt-proxy/src/opnsense/mvc/app/controllers/OPNsense/Dnscryptproxy/Api/ForwardController.php @@ -1,31 +1,29 @@ +/* + * Copyright (C) 2018 Michael Muenz + * All rights reserved. * - * All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: * - * 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. * - * 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. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. */ namespace OPNsense\Dnscryptproxy\Api; @@ -40,25 +38,29 @@ class ForwardController extends ApiMutableModelControllerBase public function searchForwardAction() { - return $this->searchBase('forwards.forward', array("enabled", "domain", "dnsserver")); + return $this->searchBase('forwards.forward', ['enabled', 'domain', 'dnsserver']); } + public function getForwardAction($uuid = null) { - $this->sessionClose(); return $this->getBase('forward', 'forwards.forward', $uuid); } + public function addForwardAction() { return $this->addBase('forward', 'forwards.forward'); } + public function delForwardAction($uuid) { return $this->delBase('forwards.forward', $uuid); } + public function setForwardAction($uuid) { return $this->setBase('forward', 'forwards.forward', $uuid); } + public function toggleForwardAction($uuid) { return $this->toggleBase('forwards.forward', $uuid); diff --git a/dns/dnscrypt-proxy/src/opnsense/mvc/app/controllers/OPNsense/Dnscryptproxy/Api/ServerController.php b/dns/dnscrypt-proxy/src/opnsense/mvc/app/controllers/OPNsense/Dnscryptproxy/Api/ServerController.php index 778d31a421..58d27fa2aa 100644 --- a/dns/dnscrypt-proxy/src/opnsense/mvc/app/controllers/OPNsense/Dnscryptproxy/Api/ServerController.php +++ b/dns/dnscrypt-proxy/src/opnsense/mvc/app/controllers/OPNsense/Dnscryptproxy/Api/ServerController.php @@ -38,25 +38,29 @@ class ServerController extends ApiMutableModelControllerBase public function searchServerAction() { - return $this->searchBase('servers.server', array("enabled", "name", "stamp")); + return $this->searchBase('servers.server', ['enabled', 'name', 'stamp']); } + public function getServerAction($uuid = null) { - $this->sessionClose(); return $this->getBase('server', 'servers.server', $uuid); } + public function addServerAction() { return $this->addBase('server', 'servers.server'); } + public function delServerAction($uuid) { return $this->delBase('servers.server', $uuid); } + public function setServerAction($uuid) { return $this->setBase('server', 'servers.server', $uuid); } + public function toggleServerAction($uuid) { return $this->toggleBase('servers.server', $uuid); diff --git a/dns/dnscrypt-proxy/src/opnsense/mvc/app/controllers/OPNsense/Dnscryptproxy/Api/ServiceController.php b/dns/dnscrypt-proxy/src/opnsense/mvc/app/controllers/OPNsense/Dnscryptproxy/Api/ServiceController.php index 8a00c2b4ac..835b974c5b 100644 --- a/dns/dnscrypt-proxy/src/opnsense/mvc/app/controllers/OPNsense/Dnscryptproxy/Api/ServiceController.php +++ b/dns/dnscrypt-proxy/src/opnsense/mvc/app/controllers/OPNsense/Dnscryptproxy/Api/ServiceController.php @@ -1,31 +1,29 @@ - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: +/* + * Copyright (C) 2018 Michael Muenz + * All rights reserved. * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: * - * 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. + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. * - * 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. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. */ namespace OPNsense\Dnscryptproxy\Api; @@ -48,10 +46,9 @@ class ServiceController extends ApiMutableServiceControllerBase public function dnsblAction() { - $this->sessionClose(); $mdl = new Dnsbl(); $backend = new Backend(); - $response = $backend->configdpRun('dnscryptproxy dnsbl', array((string)$mdl->type)); - return array("response" => $response); + $response = $backend->configdpRun('dnscryptproxy dnsbl', [(string)$mdl->type]); + return ['response' => $response]; } } diff --git a/dns/dnscrypt-proxy/src/opnsense/mvc/app/controllers/OPNsense/Dnscryptproxy/Api/WhitelistController.php b/dns/dnscrypt-proxy/src/opnsense/mvc/app/controllers/OPNsense/Dnscryptproxy/Api/WhitelistController.php index 31b298fdc7..fbb05c043c 100644 --- a/dns/dnscrypt-proxy/src/opnsense/mvc/app/controllers/OPNsense/Dnscryptproxy/Api/WhitelistController.php +++ b/dns/dnscrypt-proxy/src/opnsense/mvc/app/controllers/OPNsense/Dnscryptproxy/Api/WhitelistController.php @@ -1,31 +1,29 @@ +/* + * Copyright (C) 2018 Michael Muenz + * All rights reserved. * - * All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: * - * 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. * - * 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. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. */ namespace OPNsense\Dnscryptproxy\Api; @@ -40,25 +38,29 @@ class WhitelistController extends ApiMutableModelControllerBase public function searchWhitelistAction() { - return $this->searchBase('whitelists.whitelist', array("enabled", "name", "description")); + return $this->searchBase('whitelists.whitelist', ['enabled', 'name', 'description']); } + public function getWhitelistAction($uuid = null) { - $this->sessionClose(); return $this->getBase('whitelist', 'whitelists.whitelist', $uuid); } + public function addWhitelistAction() { return $this->addBase('whitelist', 'whitelists.whitelist'); } + public function delWhitelistAction($uuid) { return $this->delBase('whitelists.whitelist', $uuid); } + public function setWhitelistAction($uuid) { return $this->setBase('whitelist', 'whitelists.whitelist', $uuid); } + public function toggleWhitelistAction($uuid) { return $this->toggleBase('whitelists.whitelist', $uuid); diff --git a/dns/dnscrypt-proxy/src/opnsense/mvc/app/controllers/OPNsense/Dnscryptproxy/forms/general.xml b/dns/dnscrypt-proxy/src/opnsense/mvc/app/controllers/OPNsense/Dnscryptproxy/forms/general.xml index cda8bf0707..02b73663a2 100644 --- a/dns/dnscrypt-proxy/src/opnsense/mvc/app/controllers/OPNsense/Dnscryptproxy/forms/general.xml +++ b/dns/dnscrypt-proxy/src/opnsense/mvc/app/controllers/OPNsense/Dnscryptproxy/forms/general.xml @@ -11,7 +11,7 @@ select_multiple true - Set the IP address and port combinations this service should listen on, e.g 127.0.0.1:5353 and/or [::1]:5353 + Set the IP address/port combinations this service should listen on. general.allowprivileged @@ -49,6 +49,12 @@ checkbox Let DNSCrypt-Proxy use servers with DNS-over-HTTPS protocol enabled. + + general.odoh_servers + + checkbox + Let DNSCrypt-Proxy use servers with Oblivious-DNS-over-HTTPS protocol enabled. Note: If checked you must provide ODoH target and relay servers manually! + general.require_dnssec @@ -165,4 +171,26 @@ true known servers e.g. if you want to stick with Cisco only. You can also put your manually added servers here. Please use on your own risk.]]> + + general.query_logs + + checkbox + This will enable/disable local logs. This includes both [query_log] and [nx_log] as described in the DNSCrypt-Proxy documentation. + + + general.disabled_serverlist + + select_multiple + + true + + + + general.relaylist + + select_multiple + + true + relays. Will be used for relaying to all configured servers.]]> + diff --git a/dns/dnscrypt-proxy/src/opnsense/mvc/app/models/OPNsense/Dnscryptproxy/Cloak.xml b/dns/dnscrypt-proxy/src/opnsense/mvc/app/models/OPNsense/Dnscryptproxy/Cloak.xml index fd1c125db5..a76121b983 100644 --- a/dns/dnscrypt-proxy/src/opnsense/mvc/app/models/OPNsense/Dnscryptproxy/Cloak.xml +++ b/dns/dnscrypt-proxy/src/opnsense/mvc/app/models/OPNsense/Dnscryptproxy/Cloak.xml @@ -6,7 +6,7 @@ - 1 + 1 Y diff --git a/dns/dnscrypt-proxy/src/opnsense/mvc/app/models/OPNsense/Dnscryptproxy/Dnsbl.xml b/dns/dnscrypt-proxy/src/opnsense/mvc/app/models/OPNsense/Dnscryptproxy/Dnsbl.xml index 35c6c5eca9..ba05d656e2 100644 --- a/dns/dnscrypt-proxy/src/opnsense/mvc/app/models/OPNsense/Dnscryptproxy/Dnsbl.xml +++ b/dns/dnscrypt-proxy/src/opnsense/mvc/app/models/OPNsense/Dnscryptproxy/Dnsbl.xml @@ -4,11 +4,10 @@ 1.0.0 - 0 + 0 Y - N Y AdAway List @@ -16,19 +15,11 @@ Blocklist.site Ads Blocklist.site Fraud Blocklist.site Phishing - Cameleon List Easy List - EMD Malicious Domains List Easyprivacy List - hpHosts Ads - hpHosts FSA - hpHosts PSH - hpHosts PUP - Hbbtv List - Malwaredomain List NoCoin List PornTop1M List - Ransomware Tracker List + Q-Feeds (when installed) Simple Ad List Simple Tracker List Steven Black List diff --git a/dns/dnscrypt-proxy/src/opnsense/mvc/app/models/OPNsense/Dnscryptproxy/Forward.xml b/dns/dnscrypt-proxy/src/opnsense/mvc/app/models/OPNsense/Dnscryptproxy/Forward.xml index 5632cc3a61..243cca945f 100644 --- a/dns/dnscrypt-proxy/src/opnsense/mvc/app/models/OPNsense/Dnscryptproxy/Forward.xml +++ b/dns/dnscrypt-proxy/src/opnsense/mvc/app/models/OPNsense/Dnscryptproxy/Forward.xml @@ -6,7 +6,7 @@ - 1 + 1 Y diff --git a/dns/dnscrypt-proxy/src/opnsense/mvc/app/models/OPNsense/Dnscryptproxy/General.php b/dns/dnscrypt-proxy/src/opnsense/mvc/app/models/OPNsense/Dnscryptproxy/General.php index a10bb4ad9d..6f2a3d0659 100644 --- a/dns/dnscrypt-proxy/src/opnsense/mvc/app/models/OPNsense/Dnscryptproxy/General.php +++ b/dns/dnscrypt-proxy/src/opnsense/mvc/app/models/OPNsense/Dnscryptproxy/General.php @@ -1,35 +1,89 @@ - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, - INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY - AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, - OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - POSSIBILITY OF SUCH DAMAGE. -*/ + * Copyright (C) 2018 Michael Muenz + * Copyright (C) 2023 Deciso B.V. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ namespace OPNsense\Dnscryptproxy; use OPNsense\Base\BaseModel; +use OPNsense\Base\Messages\Message; +use OPNsense\Core\Backend; class General extends BaseModel { + public function performValidation($validateFullModel = false) + { + $messages = parent::performValidation($validateFullModel); + + if ( + ($validateFullModel || $this->enabled->isFieldChanged() || $this->listen_addresses->isFieldChanged()) && + !empty((string)$this->enabled) + ) { + $any4 = []; + $any6 = []; + $ports = []; + + /* grab ALL ports to run a validation against, safer for user in the long run */ + foreach (explode(',', (string)$this->listen_addresses) as $addrport) { + if (preg_match('/(.*):([\d]+)$/', $addrport, $matches)) { + $ports[$matches[2]] = 1; + if ($matches[1] == '0.0.0.0') { + $any4[$matches[2]] = 1; + } elseif ($matches[1] == '[::]') { + $any6[$matches[2]] = 1; + } + } + } + + foreach (json_decode((new Backend())->configdpRun('service list'), true) as $service) { + if (empty($service['dns_ports'])) { + continue; + } + if (!is_array($service['dns_ports'])) { + syslog(LOG_ERR, sprintf('Service %s (%s) reported a faulty "dns_ports" entry.', $service['description'], $service['name'])); + continue; + } + if ($service['name'] != 'dnscrypt-proxy' && count(array_intersect(array_keys($ports), $service['dns_ports']))) { + $messages->appendMessage(new Message( + sprintf(gettext('%s is currently using one of these ports.'), $service['description']), + $this->listen_addresses->getInternalXMLTagName() + )); + break; + } + } + + if (count(array_keys(array_intersect_key($any4, $any6)))) { + $messages->appendMessage(new Message( + gettext('Cannot configure on both "0.0.0.0" and "::" as the first occurence will be treated as dual-stack.'), + $this->listen_addresses->getInternalXMLTagName() + )); + } + } + + return $messages; + } } diff --git a/dns/dnscrypt-proxy/src/opnsense/mvc/app/models/OPNsense/Dnscryptproxy/General.xml b/dns/dnscrypt-proxy/src/opnsense/mvc/app/models/OPNsense/Dnscryptproxy/General.xml index 806459a5bf..b942d30b08 100644 --- a/dns/dnscrypt-proxy/src/opnsense/mvc/app/models/OPNsense/Dnscryptproxy/General.xml +++ b/dns/dnscrypt-proxy/src/opnsense/mvc/app/models/OPNsense/Dnscryptproxy/General.xml @@ -1,140 +1,149 @@ //OPNsense/dnscryptproxy/general dnscrypt-proxy configuration - 0.1.0 + 0.1.3 - 0 + 0 Y - 127.0.0.1:5353,[::1]:5353 - N + 0.0.0.0:5353 + Y - 0 + 1 Y - 250 + 250 Y 1 10000 Choose a number between 1 and 10000. - 1 + 1 Y - 0 + 0 Y - 1 + 1 Y - 1 + 1 Y + + 0 + Y + - 0 + 0 Y - 1 + 1 Y - 0 + 0 Y - 0 + 0 Y - - N - + - 2500 + 2500 Y 100 10000 Choose a number between 100 and 10000. - 30 + 30 Y 1 600 Choose a number between 1 and 600. - 240 + 240 Y 1 3600 Choose a number between 1 and 3600. - 0 + 0 Y - 0 + 0 Y - 9.9.9.9:53 + 9.9.9.9:53 Y - 0 + 0 Y - 1 + 1 Y - 512 + 512 Y 1 20480 Choose a number between 1 and 20480. - 600 + 600 Y 1 3600 Choose a number between 1 and 3600. - 86400 + 86400 Y 1 86400 Choose a number between 1 and 86400. - 60 + 60 Y 1 3600 Choose a number between 1 and 3600. - 600 + 600 Y 1 86400 Choose a number between 1 and 86400. - - N - + + + 1 + Y + + + /^[A-Za-z0-9\._\-]{1,70}(,[A-Za-z0-9\._\-]{1,70})*$/ + Please use valid server names. + + diff --git a/dns/dnscrypt-proxy/src/opnsense/mvc/app/models/OPNsense/Dnscryptproxy/Menu/Menu.xml b/dns/dnscrypt-proxy/src/opnsense/mvc/app/models/OPNsense/Dnscryptproxy/Menu/Menu.xml index 8133628f02..da5d87e56a 100644 --- a/dns/dnscrypt-proxy/src/opnsense/mvc/app/models/OPNsense/Dnscryptproxy/Menu/Menu.xml +++ b/dns/dnscrypt-proxy/src/opnsense/mvc/app/models/OPNsense/Dnscryptproxy/Menu/Menu.xml @@ -2,9 +2,9 @@ - - - + + + diff --git a/dns/dnscrypt-proxy/src/opnsense/mvc/app/models/OPNsense/Dnscryptproxy/Server.xml b/dns/dnscrypt-proxy/src/opnsense/mvc/app/models/OPNsense/Dnscryptproxy/Server.xml index 873cfaf6e6..2af1f6abb1 100644 --- a/dns/dnscrypt-proxy/src/opnsense/mvc/app/models/OPNsense/Dnscryptproxy/Server.xml +++ b/dns/dnscrypt-proxy/src/opnsense/mvc/app/models/OPNsense/Dnscryptproxy/Server.xml @@ -6,7 +6,7 @@ - 1 + 1 Y diff --git a/dns/dnscrypt-proxy/src/opnsense/mvc/app/models/OPNsense/Dnscryptproxy/Whitelist.xml b/dns/dnscrypt-proxy/src/opnsense/mvc/app/models/OPNsense/Dnscryptproxy/Whitelist.xml index f13e2463d3..c3d65113e7 100644 --- a/dns/dnscrypt-proxy/src/opnsense/mvc/app/models/OPNsense/Dnscryptproxy/Whitelist.xml +++ b/dns/dnscrypt-proxy/src/opnsense/mvc/app/models/OPNsense/Dnscryptproxy/Whitelist.xml @@ -6,15 +6,13 @@ - 1 + 1 Y Y - - N - + diff --git a/dns/dnscrypt-proxy/src/opnsense/mvc/app/views/OPNsense/Dnscryptproxy/general.volt b/dns/dnscrypt-proxy/src/opnsense/mvc/app/views/OPNsense/Dnscryptproxy/general.volt index 3879b097eb..c73357a0bc 100644 --- a/dns/dnscrypt-proxy/src/opnsense/mvc/app/views/OPNsense/Dnscryptproxy/general.volt +++ b/dns/dnscrypt-proxy/src/opnsense/mvc/app/views/OPNsense/Dnscryptproxy/general.volt @@ -1,31 +1,29 @@ {# - -OPNsense® is Copyright © 2014 – 2018 by Deciso B.V. -This file is Copyright © 2018 by Michael Muenz -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, -INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY -AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, -OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. - -#} + # Copyright (c) 2014-2018 Deciso B.V. + # Copyright (c) 2018 Michael Muenz + # All rights reserved. + # + # Redistribution and use in source and binary forms, with or without modification, + # are permitted provided that the following conditions are met: + # + # 1. Redistributions of source code must retain the above copyright notice, + # this list of conditions and the following disclaimer. + # + # 2. Redistributions in binary form must reproduce the above copyright notice, + # this list of conditions and the following disclaimer in the documentation + # and/or other materials provided with the distribution. + # + # THIS SOFTWARE IS PROVIDED “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + # AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + # AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + # OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + # POSSIBILITY OF SUCH DAMAGE. + #}
@@ -126,7 +123,7 @@ POSSIBILITY OF SUCH DAMAGE.

- +

@@ -154,15 +151,14 @@ POSSIBILITY OF SUCH DAMAGE.

- +

{{ partial("layout_partials/base_form",['fields':dnsblForm,'id':'frm_dnsbl_settings'])}} -
-
+
@@ -190,42 +186,42 @@ $( document ).ready(function() { }); $("#grid-forwards").UIBootgrid( - { 'search':'/api/dnscryptproxy/forward/searchForward', - 'get':'/api/dnscryptproxy/forward/getForward/', - 'set':'/api/dnscryptproxy/forward/setForward/', - 'add':'/api/dnscryptproxy/forward/addForward/', - 'del':'/api/dnscryptproxy/forward/delForward/', - 'toggle':'/api/dnscryptproxy/forward/toggleForward/' + { 'search':'/api/dnscryptproxy/forward/search_forward', + 'get':'/api/dnscryptproxy/forward/get_forward/', + 'set':'/api/dnscryptproxy/forward/set_forward/', + 'add':'/api/dnscryptproxy/forward/add_forward/', + 'del':'/api/dnscryptproxy/forward/del_forward/', + 'toggle':'/api/dnscryptproxy/forward/toggle_forward/' } ); $("#grid-cloaks").UIBootgrid( - { 'search':'/api/dnscryptproxy/cloak/searchCloak', - 'get':'/api/dnscryptproxy/cloak/getCloak/', - 'set':'/api/dnscryptproxy/cloak/setCloak/', - 'add':'/api/dnscryptproxy/cloak/addCloak/', - 'del':'/api/dnscryptproxy/cloak/delCloak/', - 'toggle':'/api/dnscryptproxy/cloak/toggleCloak/' + { 'search':'/api/dnscryptproxy/cloak/search_cloak', + 'get':'/api/dnscryptproxy/cloak/get_cloak/', + 'set':'/api/dnscryptproxy/cloak/set_cloak/', + 'add':'/api/dnscryptproxy/cloak/add_cloak/', + 'del':'/api/dnscryptproxy/cloak/del_cloak/', + 'toggle':'/api/dnscryptproxy/cloak/toggle_cloak/' } ); $("#grid-whitelists").UIBootgrid( - { 'search':'/api/dnscryptproxy/whitelist/searchWhitelist', - 'get':'/api/dnscryptproxy/whitelist/getWhitelist/', - 'set':'/api/dnscryptproxy/whitelist/setWhitelist/', - 'add':'/api/dnscryptproxy/whitelist/addWhitelist/', - 'del':'/api/dnscryptproxy/whitelist/delWhitelist/', - 'toggle':'/api/dnscryptproxy/whitelist/toggleWhitelist/' + { 'search':'/api/dnscryptproxy/whitelist/search_whitelist', + 'get':'/api/dnscryptproxy/whitelist/get_whitelist/', + 'set':'/api/dnscryptproxy/whitelist/set_whitelist/', + 'add':'/api/dnscryptproxy/whitelist/add_whitelist/', + 'del':'/api/dnscryptproxy/whitelist/del_whitelist/', + 'toggle':'/api/dnscryptproxy/whitelist/toggle_whitelist/' } ); $("#grid-servers").UIBootgrid( - { 'search':'/api/dnscryptproxy/server/searchServer', - 'get':'/api/dnscryptproxy/server/getServer/', - 'set':'/api/dnscryptproxy/server/setServer/', - 'add':'/api/dnscryptproxy/server/addServer/', - 'del':'/api/dnscryptproxy/server/delServer/', - 'toggle':'/api/dnscryptproxy/server/toggleServer/' + { 'search':'/api/dnscryptproxy/server/search_server', + 'get':'/api/dnscryptproxy/server/get_server/', + 'set':'/api/dnscryptproxy/server/set_server/', + 'add':'/api/dnscryptproxy/server/add_server/', + 'del':'/api/dnscryptproxy/server/del_server/', + 'toggle':'/api/dnscryptproxy/server/toggle_server/' } ); @@ -291,5 +287,6 @@ $( document ).ready(function() { }); }); + updateServiceControlUI('dnscryptproxy'); }); diff --git a/dns/dnscrypt-proxy/src/opnsense/scripts/OPNsense/Dnscryptproxy/dnsbl.sh b/dns/dnscrypt-proxy/src/opnsense/scripts/OPNsense/Dnscryptproxy/dnsbl.sh index 44c72135ca..77420eef0c 100755 --- a/dns/dnscrypt-proxy/src/opnsense/scripts/OPNsense/Dnscryptproxy/dnsbl.sh +++ b/dns/dnscrypt-proxy/src/opnsense/scripts/OPNsense/Dnscryptproxy/dnsbl.sh @@ -38,185 +38,129 @@ mkdir -p ${WORKDIR} easylist() { # EasyList ${FETCH} https://justdomains.github.io/blocklists/lists/easylist-justdomains.txt -o ${WORKDIR}/easylist-raw - sed "/\.$/d" ${WORKDIR}/easylist-raw | sed "/^#/d" | sed "/\_/d" | sed "/^\s*$/d" | sed "/\.\./d" | sed "s/^\.//g" > ${WORKDIR}/easylist + sed "/\.$/d" ${WORKDIR}/easylist-raw | sed "/^#/d" | sed "/\_/d" | sed "/^[[:space:]]*$/d" | sed "/\.\./d" | sed "s/^\.//g" > ${WORKDIR}/easylist rm ${WORKDIR}/easylist-raw } easyprivacy() { # EasyPrivacy ${FETCH} https://justdomains.github.io/blocklists/lists/easyprivacy-justdomains.txt -o ${WORKDIR}/easyprivacy-raw - sed "/\.$/d" ${WORKDIR}/easyprivacy-raw | sed "/^#/d" | sed "/\_/d" | sed "/^\s*$/d" | sed "/\.\./d" | sed "s/^\.//g" > ${WORKDIR}/easyprivacy + sed "/\.$/d" ${WORKDIR}/easyprivacy-raw | sed "/^#/d" | sed "/\_/d" | sed "/^[[:space:]]*$/d" | sed "/\.\./d" | sed "s/^\.//g" > ${WORKDIR}/easyprivacy rm ${WORKDIR}/easyprivacy-raw } pornall() { # PornAll ${FETCH} https://raw.githubusercontent.com/chadmayfield/my-pihole-blocklists/master/lists/pi_blocklist_porn_all.list -o ${WORKDIR}/pornall-raw - sed "/\.$/d" ${WORKDIR}/pornall-raw | sed "/^#/d" | sed "/\_/d" | sed "/^\s*$/d" | sed "/\.\./d" | sed "s/^\.//g" > ${WORKDIR}/pornall + sed "/\.$/d" ${WORKDIR}/pornall-raw | sed "/^#/d" | sed "/\_/d" | sed "/^[[:space:]]*$/d" | sed "/\.\./d" | sed "s/^\.//g" > ${WORKDIR}/pornall rm ${WORKDIR}/pornall-raw } porntop() { # PornTop1M ${FETCH} https://raw.githubusercontent.com/chadmayfield/pihole-blocklists/master/lists/pi_blocklist_porn_top1m.list -o ${WORKDIR}/porntop-raw - sed "/\.$/d" ${WORKDIR}/porntop-raw | sed "/^#/d" | sed "/\_/d" | sed "/^\s*$/d" | sed "/\.\./d" | sed "s/^\.//g" > ${WORKDIR}/porntop + sed "/\.$/d" ${WORKDIR}/porntop-raw | sed "/^#/d" | sed "/\_/d" | sed "/^[[:space:]]*$/d" | sed "/\.\./d" | sed "s/^\.//g" > ${WORKDIR}/porntop rm ${WORKDIR}/porntop-raw } -emdlist() { - # EMD - ${FETCH} https://hosts-file.net/emd.txt -o ${WORKDIR}/emdlist-raw - sed "/\.$/d" ${WORKDIR}/emdlist-raw | sed "/^#/d" | sed "/\_/d" | sed "/^\s*$/d" | sed "/\.\./d" | sed "s/^\.//g" | sed "/localhost/d" | tr -d '\r' | awk 'BEGIN{FS=OFS=" ";}{print $2;}' > ${WORKDIR}/emdlist - rm ${WORKDIR}/emdlist-raw -} - adguard() { # AdGuard ${FETCH} https://justdomains.github.io/blocklists/lists/adguarddns-justdomains.txt -o ${WORKDIR}/adguard-raw - sed "/\.$/d" ${WORKDIR}/adguard-raw | sed "/^#/d" | sed "/\_/d" | sed "/^\s*$/d" | sed "/\.\./d" | sed "s/^\.//g" > ${WORKDIR}/adguard + sed "/\.$/d" ${WORKDIR}/adguard-raw | sed "/^#/d" | sed "/\_/d" | sed "/^[[:space:]]*$/d" | sed "/\.\./d" | sed "s/^\.//g" > ${WORKDIR}/adguard rm ${WORKDIR}/adguard-raw } nocoin() { # NoCoin ${FETCH} https://justdomains.github.io/blocklists/lists/nocoin-justdomains.txt -o ${WORKDIR}/nocoin-raw - sed "/\.$/d" ${WORKDIR}/nocoin-raw | sed "/^#/d" | sed "/\_/d" | sed "/^\s*$/d" | sed "/\.\./d" | sed "s/^\.//g" > ${WORKDIR}/nocoin + sed "/\.$/d" ${WORKDIR}/nocoin-raw | sed "/^#/d" | sed "/\_/d" | sed "/^[[:space:]]*$/d" | sed "/\.\./d" | sed "s/^\.//g" > ${WORKDIR}/nocoin rm ${WORKDIR}/nocoin-raw } -rwtracker() { - # RansomWare Tracker abuse.ch - ${FETCH} https://ransomwaretracker.abuse.ch/downloads/RW_DOMBL.txt -o ${WORKDIR}/rwtracker-raw - sed "/\.$/d" ${WORKDIR}/rwtracker-raw | sed "/^#/d" | sed "/\_/d" | sed "/^\s*$/d" | sed "/\.\./d" | sed "s/^\.//g" > ${WORKDIR}/rwtracker - rm ${WORKDIR}/rwtracker-raw -} - -mwdomains() { - # MalwareDomains - ${FETCH} http://malwaredomains.lehigh.edu/files/justdomains -o ${WORKDIR}/malwaredomains-raw - sed "/\.$/d" ${WORKDIR}/malwaredomains-raw | sed "/^#/d" | sed "/\_/d" | sed "/^\s*$/d" | sed "/\.\./d" | sed "s/^\.//g" > ${WORKDIR}/malwaredomains - rm ${WORKDIR}/malwaredomains-raw -} - windowsspyblockerspy() { # WindowsSpyBlocker (spy) ${FETCH} https://raw.githubusercontent.com/crazy-max/WindowsSpyBlocker/master/data/hosts/spy.txt -o ${WORKDIR}/windowsspyblockerspy-raw - sed "/\.$/d" ${WORKDIR}/windowsspyblockerspy-raw | sed "/^#/d" | sed "/\_/d" | sed "/^\s*$/d" | sed "/\.\./d" | sed "s/^\.//g" | sed "/localhost/d" | tr -d '\r' | awk 'BEGIN{FS=OFS=" ";}{print $2;}' > ${WORKDIR}/windowsspyblockerspy + sed "/\.$/d" ${WORKDIR}/windowsspyblockerspy-raw | sed "/^#/d" | sed "/\_/d" | sed "/^[[:space:]]*$/d" | sed "/\.\./d" | sed "s/^\.//g" | sed "/localhost/d" | tr -d '\r' | awk 'BEGIN{FS=OFS=" ";}{print $2;}' > ${WORKDIR}/windowsspyblockerspy rm ${WORKDIR}/windowsspyblockerspy-raw } windowsspyblockerupdate() { # WindowsSpyBlocker (update) ${FETCH} https://raw.githubusercontent.com/crazy-max/WindowsSpyBlocker/master/data/hosts/update.txt -o ${WORKDIR}/windowsspyblockerupdate-raw - sed "/\.$/d" ${WORKDIR}/windowsspyblockerupdate-raw | sed "/^#/d" | sed "/\_/d" | sed "/^\s*$/d" | sed "/\.\./d" | sed "s/^\.//g" | sed "/localhost/d" | tr -d '\r' | awk 'BEGIN{FS=OFS=" ";}{print $2;}' > ${WORKDIR}/windowsspyblockerupdate + sed "/\.$/d" ${WORKDIR}/windowsspyblockerupdate-raw | sed "/^#/d" | sed "/\_/d" | sed "/^[[:space:]]*$/d" | sed "/\.\./d" | sed "s/^\.//g" | sed "/localhost/d" | tr -d '\r' | awk 'BEGIN{FS=OFS=" ";}{print $2;}' > ${WORKDIR}/windowsspyblockerupdate rm ${WORKDIR}/windowsspyblockerupdate-raw } windowsspyblockerextra() { # WindowsSpyBlocker (extra) ${FETCH} https://raw.githubusercontent.com/crazy-max/WindowsSpyBlocker/master/data/hosts/extra.txt -o ${WORKDIR}/windowsspyblockerextra-raw - sed "/\.$/d" ${WORKDIR}/windowsspyblockerextra-raw | sed "/^#/d" | sed "/\_/d" | sed "/^\s*$/d" | sed "/\.\./d" | sed "s/^\.//g" | sed "/localhost/d" | tr -d '\r' | awk 'BEGIN{FS=OFS=" ";}{print $2;}' > ${WORKDIR}/windowsspyblockerextra + sed "/\.$/d" ${WORKDIR}/windowsspyblockerextra-raw | sed "/^#/d" | sed "/\_/d" | sed "/^[[:space:]]*$/d" | sed "/\.\./d" | sed "s/^\.//g" | sed "/localhost/d" | tr -d '\r' | awk 'BEGIN{FS=OFS=" ";}{print $2;}' > ${WORKDIR}/windowsspyblockerextra rm ${WORKDIR}/windowsspyblockerextra-raw } -cameleon() { - # Cameleon List - ${FETCH} http://sysctl.org/cameleon/hosts -o ${WORKDIR}/cameleon-raw - sed "/\.$/d" ${WORKDIR}/cameleon-raw | sed "/^#/d" | sed "/\_/d" | sed "/^\s*$/d" | sed "/\.\./d" | sed "s/^\.//g" | sed "/localhost/d" | tr -d '\r' | awk 'BEGIN{FS=OFS=" ";}{print $2;}' > ${WORKDIR}/cameleon - rm ${WORKDIR}/cameleon-raw -} - adaway() { # AdAway List ${FETCH} https://adaway.org/hosts.txt -o ${WORKDIR}/adaway-raw - sed "/\.$/d" ${WORKDIR}/adaway-raw | sed "/^#/d" | sed "/\_/d" | sed "/^\s*$/d" | sed "/\.\./d" | sed "s/^\.//g" | sed "/localhost/d" | tr -d '\r' | awk 'BEGIN{FS=OFS=" ";}{print $2;}' > ${WORKDIR}/adaway + sed "/\.$/d" ${WORKDIR}/adaway-raw | sed "/^#/d" | sed "/\_/d" | sed "/^[[:space:]]*$/d" | sed "/\.\./d" | sed "s/^\.//g" | sed "/localhost/d" | tr -d '\r' | awk 'BEGIN{FS=OFS=" ";}{print $2;}' > ${WORKDIR}/adaway rm ${WORKDIR}/adaway-raw } yoyo() { # YoYo List ${FETCH} "http://pgl.yoyo.org/adservers/serverlist.php?hostformat=hosts&mimetype=plaintext" -o ${WORKDIR}/yoyo-raw - sed "/\.$/d" ${WORKDIR}/yoyo-raw | sed "/^#/d" | sed "/\_/d" | sed "/^\s*$/d" | sed "/\.\./d" | sed "s/^\.//g" | sed "/localhost/d" | tr -d '\r' | awk 'BEGIN{FS=OFS=" ";}{print $2;}' > ${WORKDIR}/yoyo + sed "/\.$/d" ${WORKDIR}/yoyo-raw | sed "/^#/d" | sed "/\_/d" | sed "/^[[:space:]]*$/d" | sed "/\.\./d" | sed "s/^\.//g" | sed "/localhost/d" | tr -d '\r' | awk 'BEGIN{FS=OFS=" ";}{print $2;}' > ${WORKDIR}/yoyo rm ${WORKDIR}/yoyo-raw } stevenblack() { # StevenBlack ${FETCH} https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts -o ${WORKDIR}/stevenblack-raw - sed "/\.$/d" ${WORKDIR}/stevenblack-raw | sed "/^#/d" | sed "/\_/d" | sed "/^\s*$/d" | sed "/\.\./d" | sed "s/^\.//g" | sed "/localhost/d" | sed "/127\.0\.0\.1/d" | sed "/255\.255\.255\.255/d" | sed "/\:\:1/d" | sed "/fe80\:\:1/d" | sed "/ff00\:\:/d" | sed "/ff02\:\:/d" | sed "/0\.0\.0\.0 0\.0\.0\.0/d" | tr -d '\r' | awk 'BEGIN{FS=OFS=" ";}{print $2;}' > ${WORKDIR}/stevenblack + sed "/\.$/d" ${WORKDIR}/stevenblack-raw | sed "/^#/d" | sed "/\_/d" | sed "/^[[:space:]]*$/d" | sed "/\.\./d" | sed "s/^\.//g" | sed "/localhost/d" | sed "/127\.0\.0\.1/d" | sed "/255\.255\.255\.255/d" | sed "/\:\:1/d" | sed "/fe80\:\:1/d" | sed "/ff00\:\:/d" | sed "/ff02\:\:/d" | sed "/0\.0\.0\.0 0\.0\.0\.0/d" | tr -d '\r' | awk 'BEGIN{FS=OFS=" ";}{print $2;}' > ${WORKDIR}/stevenblack rm ${WORKDIR}/stevenblack-raw } blocklistads() { # Blocklist.site Ads - ${FETCH} https://blocklist.site/app/dl/ads -o ${WORKDIR}/blocklistads-raw - sed "/\.$/d" ${WORKDIR}/blocklistads-raw | sed "/^#/d" | sed "/\_/d" | sed "/^\s*$/d" | sed "/\.\./d" | sed "s/^\.//g" | sed "/localhost/d" | sed "/127\.0\.0\.1/d" | sed "/255\.255\.255\.255/d" | sed "/\:\:1/d" | sed "/fe80\:\:1/d" | sed "/ff00\:\:/d" | sed "/ff02\:\:/d" | sed "/0\.0\.0\.0 0\.0\.0\.0/d" > ${WORKDIR}/blocklistads + ${FETCH} https://blocklistproject.github.io/Lists/ads.txt -o ${WORKDIR}/blocklistads-raw + sed "/\.$/d" ${WORKDIR}/blocklistads-raw | sed "/^#/d" | sed "/\_/d" | sed "/^[[:space:]]*$/d" | sed "/\.\./d" | sed "s/^\.//g" | sed "/localhost/d" | sed "/127\.0\.0\.1/d" | sed "/255\.255\.255\.255/d" | sed "/\:\:1/d" | sed "/fe80\:\:1/d" | sed "/ff00\:\:/d" | sed "/ff02\:\:/d" | sed "/0\.0\.0\.0 0\.0\.0\.0/d" | awk '{print $2}' > ${WORKDIR}/blocklistads rm ${WORKDIR}/blocklistads-raw } blocklistfraud() { # Blocklist.site Fraud - ${FETCH} https://blocklist.site/app/dl/fraud -o ${WORKDIR}/blocklistfraud-raw - sed "/\.$/d" ${WORKDIR}/blocklistfraud-raw | sed "/^#/d" | sed "/\_/d" | sed "/^\s*$/d" | sed "/\.\./d" | sed "s/^\.//g" | sed "/localhost/d" | sed "/127\.0\.0\.1/d" | sed "/255\.255\.255\.255/d" | sed "/\:\:1/d" | sed "/fe80\:\:1/d" | sed "/ff00\:\:/d" | sed "/ff02\:\:/d" | sed "/0\.0\.0\.0 0\.0\.0\.0/d" > ${WORKDIR}/blocklistfraud + ${FETCH} https://blocklistproject.github.io/Lists/fraud.txt -o ${WORKDIR}/blocklistfraud-raw + sed "/\.$/d" ${WORKDIR}/blocklistfraud-raw | sed "/^#/d" | sed "/\_/d" | sed "/^[[:space:]]*$/d" | sed "/\.\./d" | sed "s/^\.//g" | sed "/localhost/d" | sed "/127\.0\.0\.1/d" | sed "/255\.255\.255\.255/d" | sed "/\:\:1/d" | sed "/fe80\:\:1/d" | sed "/ff00\:\:/d" | sed "/ff02\:\:/d" | sed "/0\.0\.0\.0 0\.0\.0\.0/d" |awk '{print $2}' > ${WORKDIR}/blocklistfraud rm ${WORKDIR}/blocklistfraud-raw } blocklistphishing() { # Blocklist.site Phishing - ${FETCH} https://blocklist.site/app/dl/phishing -o ${WORKDIR}/blocklistphishing-raw - sed "/\.$/d" ${WORKDIR}/blocklistphishing-raw | sed "/^#/d" | sed "/\_/d" | sed "/^\s*$/d" | sed "/\.\./d" | sed "s/^\.//g" | sed "/localhost/d" | sed "/127\.0\.0\.1/d" | sed "/255\.255\.255\.255/d" | sed "/\:\:1/d" | sed "/fe80\:\:1/d" | sed "/ff00\:\:/d" | sed "/ff02\:\:/d" | sed "/0\.0\.0\.0 0\.0\.0\.0/d" > ${WORKDIR}/blocklistphishing + ${FETCH} https://blocklistproject.github.io/Lists/phishing.txt -o ${WORKDIR}/blocklistphishing-raw + sed "/\.$/d" ${WORKDIR}/blocklistphishing-raw | sed "/^#/d" | sed "/\_/d" | sed "/^[[:space:]]*$/d" | sed "/\.\./d" | sed "s/^\.//g" | sed "/localhost/d" | sed "/127\.0\.0\.1/d" | sed "/255\.255\.255\.255/d" | sed "/\:\:1/d" | sed "/fe80\:\:1/d" | sed "/ff00\:\:/d" | sed "/ff02\:\:/d" | sed "/0\.0\.0\.0 0\.0\.0\.0/d" | awk '{print $2}' > ${WORKDIR}/blocklistphishing rm ${WORKDIR}/blocklistphishing-raw } -hphosts-ads() { - # hphosts-ads - ${FETCH} https://hosts-file.net/ad_servers.txt -o ${WORKDIR}/hphosts-ads-raw - sed "/\.$/d" ${WORKDIR}/hphosts-ads-raw | sed "/^#/d" | sed "/\_/d" | sed "/^\s*$/d" | sed "/\.\./d" | sed "s/^\.//g" | sed "/localhost/d" | sed "/255\.255\.255\.255/d" | sed "/\:\:1/d" | sed "/fe80\:\:1/d" | sed "/ff00\:\:/d" | sed "/ff02\:\:/d" | sed "/0\.0\.0\.0 0\.0\.0\.0/d" | tr -d '\r' | awk 'BEGIN{FS=OFS=" ";}{print $2;}' > ${WORKDIR}/hphosts-ads - rm ${WORKDIR}/hphosts-ads-raw -} - -hphosts-fsa() { - # hphosts-fsa - ${FETCH} https://hosts-file.net/fsa.txt -o ${WORKDIR}/hphosts-fsa-raw - sed "/\.$/d" ${WORKDIR}/hphosts-fsa-raw | sed "/^#/d" | sed "/\_/d" | sed "/^\s*$/d" | sed "/\.\./d" | sed "s/^\.//g" | sed "/localhost/d" | sed "/255\.255\.255\.255/d" | sed "/\:\:1/d" | sed "/fe80\:\:1/d" | sed "/ff00\:\:/d" | sed "/ff02\:\:/d" | sed "/0\.0\.0\.0 0\.0\.0\.0/d" | tr -d '\r' | awk 'BEGIN{FS=OFS=" ";}{print $2;}' > ${WORKDIR}/hphosts-fsa - rm ${WORKDIR}/hphosts-fsa-raw -} - -hphosts-psh() { - # hphosts-psh - ${FETCH} https://hosts-file.net/psh.txt -o ${WORKDIR}/hphosts-psh-raw - sed "/\.$/d" ${WORKDIR}/hphosts-psh-raw | sed "/^#/d" | sed "/\_/d" | sed "/^\s*$/d" | sed "/\.\./d" | sed "s/^\.//g" | sed "/localhost/d" | sed "/255\.255\.255\.255/d" | sed "/\:\:1/d" | sed "/fe80\:\:1/d" | sed "/ff00\:\:/d" | sed "/ff02\:\:/d" | sed "/0\.0\.0\.0 0\.0\.0\.0/d" | tr -d '\r' | awk 'BEGIN{FS=OFS=" ";}{print $2;}' > ${WORKDIR}/hphosts-psh - rm ${WORKDIR}/hphosts-psh-raw -} - -hphosts-pup() { - # hphosts-pup - ${FETCH} https://hosts-file.net/pup.txt -o ${WORKDIR}/hphosts-pup-raw - sed "/\.$/d" ${WORKDIR}/hphosts-pup-raw | sed "/^#/d" | sed "/\_/d" | sed "/^\s*$/d" | sed "/\.\./d" | sed "s/^\.//g" | sed "/localhost/d" | sed "/255\.255\.255\.255/d" | sed "/\:\:1/d" | sed "/fe80\:\:1/d" | sed "/ff00\:\:/d" | sed "/ff02\:\:/d" | sed "/0\.0\.0\.0 0\.0\.0\.0/d" | tr -d '\r' | awk 'BEGIN{FS=OFS=" ";}{print $2;}' > ${WORKDIR}/hphosts-pup - rm ${WORKDIR}/hphosts-pup-raw -} - -hbbtv() { - # HBBTV List - ${FETCH} https://raw.githubusercontent.com/Akamaru/Pi-Hole-Lists/master/hbbtv.txt -o ${WORKDIR}/hbbtv-raw - sed "/\.$/d" ${WORKDIR}/hbbtv-raw | sed "/^#/d" | sed "/\_/d" | sed "/^\s*$/d" | sed "/\.\./d" | sed "s/^\.//g" > ${WORKDIR}/hbbtv - rm ${WORKDIR}/hbbtv-raw -} - simplead() { # Simple Ad List ${FETCH} https://s3.amazonaws.com/lists.disconnect.me/simple_ad.txt -o ${WORKDIR}/simplead-raw - sed "/\.$/d" ${WORKDIR}/simplead-raw | sed "/^#/d" | sed "/\_/d" | sed "/^\s*$/d" | sed "/\.\./d" | sed "s/^\.//g" > ${WORKDIR}/simplead + sed "/\.$/d" ${WORKDIR}/simplead-raw | sed "/^#/d" | sed "/\_/d" | sed "/^[[:space:]]*$/d" | sed "/\.\./d" | sed "s/^\.//g" > ${WORKDIR}/simplead rm ${WORKDIR}/simplead-raw } simpletrack() { # Simple Tracking List ${FETCH} https://s3.amazonaws.com/lists.disconnect.me/simple_tracking.txt -o ${WORKDIR}/simpletrack-raw - sed "/\.$/d" ${WORKDIR}/simpletrack-raw | sed "/^#/d" | sed "/\_/d" | sed "/^\s*$/d" | sed "/\.\./d" | sed "s/^\.//g" > ${WORKDIR}/simpletrack + sed "/\.$/d" ${WORKDIR}/simpletrack-raw | sed "/^#/d" | sed "/\_/d" | sed "/^[[:space:]]*$/d" | sed "/\.\./d" | sed "s/^\.//g" > ${WORKDIR}/simpletrack rm ${WORKDIR}/simpletrack-raw } +qfeeds() { + # Q-Feeds List + if [ -f "/usr/local/etc/dnscrypt-proxy/blacklist-qfeeds.txt" ] && [ -s "/usr/local/etc/dnscrypt-proxy/blacklist-qfeeds.txt" ]; then + sed "/\.$/d" /usr/local/etc/dnscrypt-proxy/blacklist-qfeeds.txt | sed "/^#/d" | sed "/\_/d" | sed "/^[[:space:]]*$/d" | sed "/\.\./d" | sed "s/^\.//g" > ${WORKDIR}/qfeeds + fi +} + install() { # Put all files in correct format for FILE in $(find ${WORKDIR} -type f); do @@ -252,45 +196,15 @@ for CAT in $(echo ${DNSBL} | tr ',' ' '); do blp) blocklistphishing ;; - ca) - cameleon - ;; el) easylist ;; ep) easyprivacy ;; - emd) - emdlist - ;; - hpa) - hphosts-ads - ;; - hpf) - hphosts-fsa - ;; - hpp) - hphosts-psh - ;; - hup) - hphosts-pup - ;; - ht) - hbbtv - ;; nc) nocoin ;; - rw) - rwtracker - ;; - mw) - mwdomains - ;; - pa) - #pornall - ;; pt) porntop ;; @@ -315,6 +229,9 @@ for CAT in $(echo ${DNSBL} | tr ',' ' '); do yy) yoyo ;; + qf) + qfeeds + ;; esac done diff --git a/dns/dnscrypt-proxy/src/opnsense/scripts/OPNsense/Dnscryptproxy/setup.sh b/dns/dnscrypt-proxy/src/opnsense/scripts/OPNsense/Dnscryptproxy/setup.sh index 0d289f2b70..6c277c5aec 100755 --- a/dns/dnscrypt-proxy/src/opnsense/scripts/OPNsense/Dnscryptproxy/setup.sh +++ b/dns/dnscrypt-proxy/src/opnsense/scripts/OPNsense/Dnscryptproxy/setup.sh @@ -2,3 +2,7 @@ mkdir -p /var/log/dnscrypt-proxy chown -R _dnscrypt-proxy:_dnscrypt-proxy /var/log/dnscrypt-proxy + +rm -f /var/log/dnscryptproxy +(cd /var/log && ln -s dnscrypt-proxy dnscryptproxy) +chown -R _dnscrypt-proxy:_dnscrypt-proxy /var/log/dnscryptproxy diff --git a/dns/dnscrypt-proxy/src/opnsense/service/conf/actions.d/actions_dnscryptproxy.conf b/dns/dnscrypt-proxy/src/opnsense/service/conf/actions.d/actions_dnscryptproxy.conf index 3eae8eadb0..ded64e9db8 100644 --- a/dns/dnscrypt-proxy/src/opnsense/service/conf/actions.d/actions_dnscryptproxy.conf +++ b/dns/dnscrypt-proxy/src/opnsense/service/conf/actions.d/actions_dnscryptproxy.conf @@ -1,5 +1,5 @@ [start] -command:/usr/local/opnsense/scripts/OPNsense/Dnscryptproxy/setup.sh;/usr/local/etc/rc.d/dnscrypt-proxy start +command:/usr/local/etc/rc.d/dnscrypt-proxy start parameters: type:script message:starting dnscrypt-proxy @@ -11,7 +11,7 @@ type:script message:stopping dnscrypt-proxy [restart] -command:/usr/local/opnsense/scripts/OPNsense/Dnscryptproxy/setup.sh;/usr/local/etc/rc.d/dnscrypt-proxy restart +command:/usr/local/etc/rc.d/dnscrypt-proxy restart parameters: type:script message:restarting dnscrypt-proxy @@ -29,7 +29,7 @@ type:script message:fetching DNSBLs [dnsblcron] -command:/usr/local/opnsense/scripts/OPNsense/Dnscryptproxy/dnsbl.sh;/usr/local/etc/rc.d/dnscrypt-proxy restart +command:/usr/local/opnsense/scripts/OPNsense/Dnscryptproxy/dnsbl.sh; /usr/local/etc/rc.d/dnscrypt-proxy restart parameters: type:script message:fetching DNSBLs and restart diff --git a/dns/dnscrypt-proxy/src/opnsense/service/templates/OPNsense/Dnscryptproxy/dnscrypt-proxy.toml b/dns/dnscrypt-proxy/src/opnsense/service/templates/OPNsense/Dnscryptproxy/dnscrypt-proxy.toml index f6627a3a71..7f19af4f94 100644 --- a/dns/dnscrypt-proxy/src/opnsense/service/templates/OPNsense/Dnscryptproxy/dnscrypt-proxy.toml +++ b/dns/dnscrypt-proxy/src/opnsense/service/templates/OPNsense/Dnscryptproxy/dnscrypt-proxy.toml @@ -4,6 +4,10 @@ server_names = [{{ "'" + ("','".join(OPNsense.dnscryptproxy.general.serverlist.split(','))) + "'" }}] {% endif %} +{% if helpers.exists('OPNsense.dnscryptproxy.general.disabled_serverlist') and OPNsense.dnscryptproxy.general.disabled_serverlist != '' %} +disabled_server_names = [{{ "'" + ("','".join(OPNsense.dnscryptproxy.general.disabled_serverlist.split(','))) + "'" }}] +{% endif %} + {% if helpers.exists('OPNsense.dnscryptproxy.general.listen_addresses') and OPNsense.dnscryptproxy.general.listen_addresses != '' %} listen_addresses = [{{ "'" + ("','".join(OPNsense.dnscryptproxy.general.listen_addresses.split(','))) + "'" }}] {% else %} @@ -36,6 +40,12 @@ doh_servers = true doh_servers = false {% endif %} +{% if helpers.exists('OPNsense.dnscryptproxy.general.odoh_servers') and OPNsense.dnscryptproxy.general.odoh_servers == '1' %} +odoh_servers = true +{% else %} +odoh_servers = false +{% endif %} + {% if helpers.exists('OPNsense.dnscryptproxy.general.require_dnssec') and OPNsense.dnscryptproxy.general.require_dnssec == '1' %} require_dnssec = true {% else %} @@ -85,7 +95,7 @@ tls_disable_session_tickets = true tls_disable_session_tickets = false {% endif %} -fallback_resolver = '{{ OPNsense.dnscryptproxy.general.fallback_resolver }}' +bootstrap_resolvers = ['{{ OPNsense.dnscryptproxy.general.fallback_resolver.split(',') | join("','") }}'] {% if helpers.exists('OPNsense.dnscryptproxy.general.ignore_system_dns') and OPNsense.dnscryptproxy.general.ignore_system_dns == '1' %} ignore_system_dns = true @@ -118,6 +128,7 @@ cache_neg_max_ttl = {{ OPNsense.dnscryptproxy.general.cache_neg_max_ttl }} cache = false {% endif %} +{% if helpers.exists('OPNsense.dnscryptproxy.general.query_logs') and OPNsense.dnscryptproxy.general.query_logs == '1' %} [query_log] file = '/var/log/dnscrypt-proxy/query.log' format = 'tsv' @@ -125,27 +136,64 @@ cache = false [nx_log] file = '/var/log/dnscrypt-proxy/nx.log' format = 'tsv' +{% endif %} -[whitelist] - whitelist_file = 'whitelist.txt' +[allowed_names] + allowed_names_file = 'whitelist.txt' log_file = '/var/log/dnscrypt-proxy/whitelisted.log' log_format = 'tsv' {% if helpers.exists('OPNsense.dnscryptproxy.dnsbl.enabled') and OPNsense.dnscryptproxy.dnsbl.enabled == '1' %} -[blacklist] - blacklist_file = 'blacklist.txt' +[blocked_names] + blocked_names_file = 'blacklist.txt' log_file = '/var/log/dnscrypt-proxy/blocked.log' log_format = 'tsv' {% endif %} [sources] [sources.'public-resolvers'] - urls = ['https://raw.githubusercontent.com/DNSCrypt/dnscrypt-resolvers/master/v2/public-resolvers.md', 'https://download.dnscrypt.info/resolvers-list/v2/public-resolvers.md'] + urls = ['https://raw.githubusercontent.com/DNSCrypt/dnscrypt-resolvers/master/v3/public-resolvers.md', 'https://download.dnscrypt.info/resolvers-list/v3/public-resolvers.md', 'https://ipv6.download.dnscrypt.info/resolvers-list/v3/public-resolvers.md'] cache_file = 'public-resolvers.md' minisign_key = 'RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3' refresh_delay = 72 prefix = '' + ## Anonymized DNS relays + + [sources.'relays'] + urls = ['https://raw.githubusercontent.com/DNSCrypt/dnscrypt-resolvers/master/v3/relays.md', 'https://download.dnscrypt.info/resolvers-list/v3/relays.md', 'https://ipv6.download.dnscrypt.info/resolvers-list/v3/relays.md'] + cache_file = 'relays.md' + minisign_key = 'RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3' + refresh_delay = 72 + prefix = '' + + ## Oblivious DoH servers + + [sources.'odoh-servers'] + urls = ['https://raw.githubusercontent.com/DNSCrypt/dnscrypt-resolvers/master/v3/odoh-servers.md', 'https://download.dnscrypt.info/resolvers-list/v3/odoh-servers.md', 'https://ipv6.download.dnscrypt.info/resolvers-list/v3/odoh-servers.md'] + cache_file = 'odoh-servers.md' + minisign_key = 'RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3' + refresh_delay = 72 + prefix = '' + + ## Oblivious DoH relays + + [sources.'odoh-relays'] + urls = ['https://raw.githubusercontent.com/DNSCrypt/dnscrypt-resolvers/master/v3/odoh-relays.md', 'https://download.dnscrypt.info/resolvers-list/v3/odoh-relays.md', 'https://ipv6.download.dnscrypt.info/resolvers-list/v3/odoh-relays.md'] + cache_file = 'odoh-relays.md' + minisign_key = 'RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3' + refresh_delay = 72 + prefix = '' + +[anonymized_dns] + +{% if helpers.exists('OPNsense.dnscryptproxy.general.relaylist') and OPNsense.dnscryptproxy.general.relaylist != '' %} + routes = [ + { server_name='*', via=[{{ "'" + ("','".join(OPNsense.dnscryptproxy.general.relaylist.split(','))) + "'" }}] } + ] +{% endif %} + + [static] {% if helpers.exists('OPNsense.dnscryptproxy.server.servers.server') %} {% for server_list in helpers.toList('OPNsense.dnscryptproxy.server.servers.server') %} diff --git a/dns/dnscrypt-proxy/src/opnsense/service/templates/OPNsense/Dnscryptproxy/dnscrypt_proxy b/dns/dnscrypt-proxy/src/opnsense/service/templates/OPNsense/Dnscryptproxy/dnscrypt_proxy index 6cee01a3b4..6dad9e923d 100644 --- a/dns/dnscrypt-proxy/src/opnsense/service/templates/OPNsense/Dnscryptproxy/dnscrypt_proxy +++ b/dns/dnscrypt-proxy/src/opnsense/service/templates/OPNsense/Dnscryptproxy/dnscrypt_proxy @@ -1,4 +1,5 @@ {% if helpers.exists('OPNsense.dnscryptproxy.general.enabled') and OPNsense.dnscryptproxy.general.enabled == '1' %} +dnscrypt_proxy_setup="/usr/local/opnsense/scripts/OPNsense/Dnscryptproxy/setup.sh" dnscrypt_proxy_enable="YES" {% if helpers.exists('OPNsense.dnscryptproxy.general.allowprivileged') and OPNsense.dnscryptproxy.general.allowprivileged == '1' %} dnscrypt_proxy_suexec="YES" @@ -11,4 +12,3 @@ dnscrypt_proxy_dnsbl="{{ OPNsense.dnscryptproxy.dnsbl.type }}" {% else %} dnscrypt_proxy_enable="NO" {% endif %} -dnscrypt_proxy_var_script="/usr/local/opnsense/scripts/OPNsense/Dnscryptproxy/setup.sh" diff --git a/dns/dyndns/Makefile b/dns/dyndns/Makefile deleted file mode 100644 index 4f03fc2899..0000000000 --- a/dns/dyndns/Makefile +++ /dev/null @@ -1,7 +0,0 @@ -PLUGIN_NAME= dyndns -PLUGIN_VERSION= 1.19 -PLUGIN_REVISION= 1 -PLUGIN_COMMENT= Dynamic DNS Support -PLUGIN_MAINTAINER= franco@opnsense.org - -.include "../../Mk/plugins.mk" diff --git a/dns/dyndns/pkg-descr b/dns/dyndns/pkg-descr deleted file mode 100644 index 16ae728ae9..0000000000 --- a/dns/dyndns/pkg-descr +++ /dev/null @@ -1 +0,0 @@ -Support for numerous Dynamic DNS services (DynDNS et al) diff --git a/dns/dyndns/src/etc/inc/plugins.inc.d/dyndns.inc b/dns/dyndns/src/etc/inc/plugins.inc.d/dyndns.inc deleted file mode 100644 index 02847b288c..0000000000 --- a/dns/dyndns/src/etc/inc/plugins.inc.d/dyndns.inc +++ /dev/null @@ -1,236 +0,0 @@ - - * Copyright (C) 2010 Ermal Luçi - * Copyright (C) 2005-2006 Colin Smith - * Copyright (C) 2003-2004 Manuel Kasper - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - * AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, - * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -require_once('plugins.inc.d/dyndns/phpDynDNS.inc'); -require_once('plugins.inc.d/dyndns/r53.inc'); - -function dyndns_configure() -{ - return array( - 'bootup' => array('dyndns_configure_do'), - 'local' => array('dyndns_configure_do'), - 'newwanip' => array('dyndns_configure_do:2'), - ); -} - -function dyndns_enabled() -{ - global $config; - - if (isset($config['dyndnses']['dyndns'])) { - foreach ($config['dyndnses']['dyndns'] as $conf) { - if (isset($conf['enable'])) { - return true; - } - } - } - - return false; -} - -function dyndns_services() -{ - global $config; - - $services = array(); - - if (dyndns_enabled()) { - $services[] = array( - 'description' => gettext('Dynamic DNS'), - 'configd' => array( - 'restart' => array('dyndns reload'), - ), - 'nocheck' => true, - 'name' => 'dyndns', - ); - } - - return $services; -} - -function dyndns_cron() -{ - $jobs = array(); - - if (dyndns_enabled()) { - $jobs[]['autocron'] = array('/usr/local/etc/rc.dyndns', '11', '1'); - } - - return $jobs; -} - -function dyndns_list() -{ - /* - * XXX something like this would be cool: - * - * https://github.com/openwrt/packages/blob/master/net/ddns-scripts/files/services - */ - - return array( - '3322' => '3322', - 'azure' => 'Azure DNS', - 'azurev6' => 'Azure DNS (v6)', - 'citynetwork' => 'City Network', - 'cloudflare' => 'CloudFlare', - 'cloudflare-v6' => 'CloudFlare (v6)', - 'custom' => 'Custom', - 'custom-v6' => 'Custom (v6)', - 'dhs' => 'DHS', - 'digitalocean' => 'DigitalOcean', - 'dnsexit' => 'DNSexit', - 'dnsomatic' => 'DNS-O-Matic', - 'duckdns' => 'Duck DNS', - 'dyndns' => 'DynDNS (dynamic)', - 'dyndns-custom' => 'DynDNS (custom)', - 'dyndns-static' => 'DynDNS (static)', - 'dyns' => 'DyNS', - 'dynv6' => 'dynv6', - 'dynv6-v6' => 'dynv6 (v6)', - 'easydns' => 'easyDNS', - 'eurodns' => 'EuroDNS', - 'freedns' => 'freeDNS', - 'googledomains' => 'Google Domains', - 'gratisdns' => 'GratisDNS', - 'he-net' => 'HE.net', - 'he-net-tunnelbroker' => 'HE.net Tunnelbroker', - 'he-net-v6' => 'HE.net (v6)', - 'linode' => 'Linode', - 'linode-v6' => 'Linode (v6)', - 'loopia' => 'Loopia', - 'namecheap' => 'Namecheap', - 'noip' => 'No-IP', - 'noip-free' => 'No-IP (free)', - 'ods' => 'ODS.org', - 'oray' => 'Oray', - 'ovh-dynhost' => 'OVH DynHOST', - 'regfish' => 'regfish', - 'regfish-v6' => 'regfish (v6)', - 'route53' => 'Route 53', - 'route53-v6' => 'Route 53 (v6)', - 'selfhost' => 'SelfHost', - 'strato' => 'STRATO', - 'zoneedit' => 'ZoneEdit', - ); -} - -function dyndns_cache_file($conf, $ipver = 4) -{ - $ipver = $ipver == 6 ? '_v6' : ''; - - return "/var/cache/dyndns_{$conf['interface']}_{$conf['host']}_{$conf['id']}{$ipver}.cache"; -} - -function dyndns_configure_client($conf) -{ - if (!isset($conf['enable'])) { - return; - } - - $dns = new updatedns( - $dnsService = $conf['type'], - $dnsHost = $conf['host'], - $dnsUser = $conf['username'], - $dnsPass = $conf['password'], - $dnsWilcard = $conf['wildcard'], - $dnsMX = $conf['mx'], - $dnsIf = "{$conf['interface']}", - $dnsBackMX = null, - $dnsServer = null, - $dnsPort = null, - $dnsUpdateURL = "{$conf['updateurl']}", - $forceUpdate = $conf['force'], - $dnsZoneID = $conf['zoneid'], - $dnsResourceID = $conf['resourceid'], - $dnsTTL = $conf['ttl'], - $dnsResultMatch = "{$conf['resultmatch']}", - $dnsRequestIf = "{$conf['requestif']}", - $dnsID = "{$conf['id']}", - $dnsVerboseLog = $conf['verboselog'], - $curlIpresolveV4 = $conf['curl_ipresolve_v4'], - $curlSslVerifypeer = $conf['curl_ssl_verifypeer'] - ); -} - -function dyndns_configure_do($verbose = false, $int = '') -{ - global $config; - - if (!dyndns_enabled()) { - return; - } - - $dyndnscfg = $config['dyndnses']['dyndns']; - $gwgroups = return_gateway_groups_array(); - - if ($verbose) { - echo 'Configuring dynamic DNS clients...'; - flush(); - } - - foreach ($dyndnscfg as $dyndns) { - if ((empty($int)) || ($int == $dyndns['interface']) || (is_array($gwgroups[$dyndns['interface']]))) { - $dyndns['verboselog'] = isset($dyndns['verboselog']); - $dyndns['curl_ipresolve_v4'] = isset($dyndns['curl_ipresolve_v4']); - $dyndns['curl_ssl_verifypeer'] = isset($dyndns['curl_ssl_verifypeer']); - dyndns_configure_client($dyndns); - sleep(1); - } - } - - if ($verbose) { - echo "done.\n"; - } -} - -function dyndns_failover_interface($interface, $family = 'all') -{ - global $config; - - /* shortcut for known interfaces */ - if (isset($config['interfaces'][$interface])) { - return get_real_interface($interface, $family); - } - - /* compare against gateway groups */ - $a_groups = return_gateway_groups_array(); - if (isset($a_groups[$interface])) { - /* we found a gateway group, fetch the interface or vip */ - if ($a_groups[$interface][0]['vip'] != '') { - return $a_groups[$interface][0]['vip']; - } else { - return $a_groups[$interface][0]['int']; - } - } - - /* fall through to get real interface the hard way */ - return get_real_interface($interface, $family); -} diff --git a/dns/dyndns/src/etc/inc/plugins.inc.d/dyndns/phpDynDNS.inc b/dns/dyndns/src/etc/inc/plugins.inc.d/dyndns/phpDynDNS.inc deleted file mode 100644 index 5173554fbe..0000000000 --- a/dns/dyndns/src/etc/inc/plugins.inc.d/dyndns/phpDynDNS.inc +++ /dev/null @@ -1,1695 +0,0 @@ - $dnsHost, 'id' => $dnsID, 'interface' => $dnsIf); - $this->_cacheFile = dyndns_cache_file($conf, 4); - $this->_cacheFile_v6 = dyndns_cache_file($conf, 6); - $this->_debugFile = dyndns_cache_file($conf, 4) . '.debug'; - $this->_dnsServiceList = dyndns_list(); - - $this->_curlIpresolveV4 = $curlIpresolveV4; - $this->_curlSslVerifypeer = $curlSslVerifypeer; - $this->_dnsVerboseLog = $dnsVerboseLog; - if ($this->_dnsVerboseLog) { - log_error("Dynamic DNS: updatedns() starting"); - } - - $dyndnslck = lock("DDNS" . $dnsID, LOCK_EX); - - if (!$dnsService) { - $this->_error(2); - } - switch ($dnsService) { - case 'freedns': - if (!$dnsHost) { - $this->_error(5); - } - break; - case 'linode': - case 'linode-v6': - case 'namecheap': - if (!$dnsPass) { - $this->_error(4); - } elseif (!$dnsHost) { - $this->_error(5); - } - break; - case 'route53': - case 'route53-v6': - if (!$dnsZoneID) { - $this->_error(8); - } elseif (!$dnsTTL) { - $this->_error(9); - } - break; - case 'custom': - case 'custom-v6': - if (!$dnsUpdateURL) { - $this->_error(7); - } - break; - case 'duckdns': - case 'dynv6': - case 'dynv6-v6': - case 'regfish': - case 'regfish-v6': - if (!$dnsUser) { - $this->_error(3); - } elseif (!$dnsHost) { - $this->_error(5); - } - break; - case 'azure': - case 'azurev6': - if (!$dnsUser) { - $this->_error(3); - } elseif (!$dnsPass) { - $this->_error(4); - } elseif (!$dnsHost) { - $this->_error(5); - } elseif (!$dnsResourceID) { - $this->_error(8); - } elseif (!$dnsTTL) { - $this->_error(9); - } - break; - default: - if (!$dnsUser) { - $this->_error(3); - } elseif (!$dnsPass) { - $this->_error(4); - } elseif (!$dnsHost) { - $this->_error(5); - } - break; - } - - switch ($dnsService) { - case 'azurev6': - case 'cloudflare-v6': - case 'custom-v6': - case 'dynv6-v6': - case 'he-net-v6': - case 'linode-v6': - case 'regfish-v6': - case 'route53-v6': - $this->_useIPv6 = true; - break; - default: - $this->_useIPv6 = false; - } - $this->_dnsService = strtolower($dnsService); - $this->_dnsUser = $dnsUser; - $this->_dnsPass = $dnsPass; - $this->_dnsHost = $dnsHost; - $this->_dnsServer = $dnsServer; - $this->_dnsPort = $dnsPort; - $this->_dnsWildcard = $dnsWildcard; - $this->_dnsMX = $dnsMX; - $this->_dnsZoneID = $dnsZoneID; - $this->_dnsResourceID = $dnsResourceID; - $this->_dnsTTL = $dnsTTL; - $this->_if = dyndns_failover_interface($dnsIf, $this->_useIPv6 ? 'inet6' : 'all'); - $this->_checkIP(); - $this->_dnsUpdateURL = $dnsUpdateURL; - $this->_dnsResultMatch = $dnsResultMatch; - $this->_dnsRequestIf = dyndns_failover_interface($dnsRequestIf, $this->_useIPv6 ? 'inet6' : 'all'); - if ($this->_dnsVerboseLog) { - log_error("Dynamic DNS ({$this->_dnsHost}): running dyndns_failover_interface for {$dnsRequestIf}. found {$this->_dnsRequestIf}"); - } - $this->_dnsRequestIfIP = $this->_useIPv6 ? get_interface_ipv6($this->_dnsRequestIf) : get_interface_ip($this->_dnsRequestIf); - $this->_dnsMaxCacheAgeDays = 25; - $this->_dnsDummyUpdateDone = false; - $this->_forceUpdateNeeded = $forceUpdate; - - // Ensure that we were able to lookup the IP - if (!is_ipaddr($this->_dnsIP)) { - log_error("Dynamic DNS ({$this->_dnsHost}) There was an error trying to determine the public IP for interface - {$dnsIf}({$this->_if}). Probably interface is not a WAN interface."); - unlock($dyndnslck); - return; - } - - $this->_debugID = rand(1000000, 9999999); - - if ($forceUpdate == false && $this->_detectChange() == false) { - $this->_error(10); - } else { - switch ($this->_dnsService) { - case '3322': - case 'azure': - case 'azurev6': - case 'citynetwork': - case 'cloudflare': - case 'cloudflare-v6': - case 'custom': - case 'custom-v6': - case 'dhs': - case 'digitalocean': - case 'dnsexit': - case 'dnsomatic': - case 'duckdns': - case 'dyndns': - case 'dyndns-custom': - case 'dyndns-static': - case 'dyns': - case 'dynv6': - case 'dynv6-v6': - case 'easydns': - case 'eurodns': - case 'freedns': - case 'googledomains': - case 'gratisdns': - case 'he-net': - case 'he-net-tunnelbroker': - case 'he-net-v6': - case 'hn': - case 'linode': - case 'linode-v6': - case 'loopia': - case 'namecheap': - case 'noip': - case 'noip-free': - case 'ods': - case 'oray': - case 'ovh-dynhost': - case 'regfish': - case 'regfish-v6': - case 'route53': - case 'route53-v6': - case 'selfhost': - case 'staticcling': - case 'strato': - case 'zoneedit': - $this->_update(); - if ($this->_dnsDummyUpdateDone == true) { - // If a dummy update was needed, then sleep a while and do the update again to put the proper address back. - // Some providers (e.g. No-IP free accounts) need to have at least 1 address change every month. - // If the address has not changed recently, or the user did "Force Update", then the code does - // a dummy address change for providers like this. - sleep(10); - $this->_update(); - } - break; - default: - $this->_error(6); - break; - } - } - - unlock($dyndnslck); - } - - /* - * Private Function (added 12 July 05) [beta] - * Send Update To Selected Service. - */ - function _update() - { - if ($this->_dnsVerboseLog) { - log_error("Dynamic DNS ({$this->_dnsHost} via {$this->_dnsServiceList[$this->_dnsService]}): _update() starting."); - } - - if ($this->_dnsService != 'ods' and $this->_dnsService != 'route53' and $this->_dnsService != 'route53-v6') { - $ch = curl_init(); - curl_setopt($ch, CURLOPT_HEADER, 0); - curl_setopt($ch, CURLOPT_USERAGENT, $this->_UserAgent); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_INTERFACE, $this->_dnsRequestIfIP); - curl_setopt($ch, CURLOPT_TIMEOUT, 15); - } - - switch ($this->_dnsService) { - case 'dyndns': - case 'dyndns-static': - case 'dyndns-custom': - if (isset($this->_dnsWildcard) && $this->_dnsWildcard != "OFF") { - $this->_dnsWildcard = "ON"; - } - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); - curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser . ':' . $this->_dnsPass); - $server = "https://members.dyndns.org/nic/update"; - $port = ""; - if ($this->_dnsServer) { - $server = $this->_dnsServer; - } - if ($this->_dnsPort) { - $port = ":" . $this->_dnsPort; - } - curl_setopt($ch, CURLOPT_URL, $server . $port . '?system=dyndns&hostname=' . $this->_dnsHost . '&myip=' . $this->_dnsIP . '&wildcard=' . $this->_dnsWildcard . '&mx=' . $this->_dnsMX . '&backmx=NO'); - break; - case 'dhs': - $post_data['hostscmd'] = 'edit'; - $post_data['hostscmdstage'] = '2'; - $post_data['type'] = '4'; - $post_data['updatetype'] = 'Online'; - $post_data['mx'] = $this->_dnsMX; - $post_data['mx2'] = ''; - $post_data['txt'] = ''; - $post_data['offline_url'] = ''; - $post_data['cloak'] = 'Y'; - $post_data['cloak_title'] = ''; - $post_data['ip'] = $this->_dnsIP; - $post_data['domain'] = 'dyn.dhs.org'; - $post_data['hostname'] = $this->_dnsHost; - $post_data['submit'] = 'Update'; - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); - $server = "https://members.dhs.org/nic/hosts"; - $port = ""; - if ($this->_dnsServer) { - $server = $this->_dnsServer; - } - if ($this->_dnsPort) { - $port = ":" . $this->_dnsPort; - } - curl_setopt($ch, CURLOPT_URL, '{$server}{$port}'); - curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser . ':' . $this->_dnsPass); - curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data); - break; - case 'noip': - case 'noip-free': - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); - $server = "https://dynupdate.no-ip.com/ducupdate.php"; - $port = ""; - if ($this->_dnsServer) { - $server = $this->_dnsServer; - } - if ($this->_dnsPort) { - $port = ":" . $this->_dnsPort; - } - if ( - ($this->_dnsService == "noip-free") && - ($this->_forceUpdateNeeded == true) && - ($this->_dnsDummyUpdateDone == false) - ) { - // Update the IP to a dummy value to force No-IP free accounts to see a change. - $iptoset = "192.168.1.1"; - $this->_dnsDummyUpdateDone = true; - log_error("Dynamic DNS ({$this->_dnsHost}): Processing dummy update on No-IP free account. IP temporarily set to " . $iptoset); - } else { - $iptoset = $this->_dnsIP; - } - curl_setopt($ch, CURLOPT_URL, $server . $port . '?username=' . urlencode($this->_dnsUser) . '&pass=' . urlencode($this->_dnsPass) . '&hostname=' . $this->_dnsHost . '&ip=' . $iptoset); - break; - case 'easydns': - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); - curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser . ':' . $this->_dnsPass); - $server = "https://members.easydns.com/dyn/dyndns.php"; - $port = ""; - if ($this->_dnsServer) { - $server = $this->_dnsServer; - } - if ($this->_dnsPort) { - $port = ":" . $this->_dnsPort; - } - curl_setopt($ch, CURLOPT_URL, $server . $port . '?hostname=' . $this->_dnsHost . '&myip=' . $this->_dnsIP . '&wildcard=' . $this->_dnsWildcard . '&mx=' . $this->_dnsMX . '&backmx=' . $this->_dnsBackMX); - break; - case 'hn': - curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser . ':' . $this->_dnsPass); - $server = "http://dup.hn.org/vanity/update"; - $port = ""; - if ($this->_dnsServer) { - $server = $this->_dnsServer; - } - if ($this->_dnsPort) { - $port = ":" . $this->_dnsPort; - } - curl_setopt($ch, CURLOPT_URL, $server . $port . '?ver=1&IP=' . $this->_dnsIP); - break; - case 'zoneedit': - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); - curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); - curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser . ':' . $this->_dnsPass); - - $server = "https://dynamic.zoneedit.com/auth/dynamic.html"; - $port = ""; - if ($this->_dnsServer) { - $server = $this->_dnsServer; - } - if ($this->_dnsPort) { - $port = ":" . $this->_dnsPort; - } - curl_setopt($ch, CURLOPT_URL, "{$server}{$port}?host=" . $this->_dnsHost); - break; - case 'dyns': - /* XXX HTTPS is currently broken for them */ - $server = 'http://www.dyns.cx/postscript011.php'; - $port = ''; - if ($this->_dnsServer) { - $server = $this->_dnsServer; - } - if ($this->_dnsPort) { - $port = ":" . $this->_dnsPort; - } - curl_setopt($ch, CURLOPT_URL, $server . $port . '?username=' . urlencode($this->_dnsUser) . '&password=' . $this->_dnsPass . '&host=' . $this->_dnsHost); - break; - case 'ods': - $misc_errno = 0; - $misc_error = ""; - $server = "ods.org"; - $port = ""; - if ($this->_dnsServer) { - $server = $this->_dnsServer; - } - if ($this->_dnsPort) { - $port = ":" . $this->_dnsPort; - } - $this->con['socket'] = fsockopen("{$server}{$port}", "7070", $misc_errno, $misc_error, 30); - /* Check that we have connected */ - if (!$this->con['socket']) { - print "error! could not connect."; - break; - } - /* Here is the loop. Read the incoming data (from the socket connection) */ - while (!feof($this->con['socket'])) { - $this->con['buffer']['all'] = trim(fgets($this->con['socket'], 4096)); - $code = substr($this->con['buffer']['all'], 0, 3); - sleep(1); - switch ($code) { - case 100: - fputs($this->con['socket'], "LOGIN " . $this->_dnsUser . " " . $this->_dnsPass . "\n"); - break; - case 225: - fputs($this->con['socket'], "DELRR " . $this->_dnsHost . " A\n"); - break; - case 901: - fputs($this->con['socket'], "ADDRR " . $this->_dnsHost . " A " . $this->_dnsIP . "\n"); - break; - case 795: - fputs($this->con['socket'], "QUIT\n"); - break; - } - } - $this->_checkStatus(0, $code); - break; - case 'freedns': - curl_setopt($ch, CURLOPT_URL, 'https://freedns.afraid.org/dynamic/update.php?' . $this->_dnsPass); - break; - case 'dnsexit': - curl_setopt($ch, CURLOPT_URL, 'https://www.dnsexit.com/RemoteUpdate.sv?login=' . urlencode($this->_dnsUser) . '&password=' . $this->_dnsPass . '&host=' . $this->_dnsHost . '&myip=' . $this->_dnsIP); - break; - case 'loopia': - $this->_dnsWildcard = (isset($this->_dnsWildcard) && $this->_dnsWildcard == true) ? 'ON' : 'OFF'; - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); - curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser . ':' . $this->_dnsPass); - curl_setopt($ch, CURLOPT_URL, 'https://dns.loopia.se/XDynDNSServer/XDynDNS.php?hostname=' . $this->_dnsHost . '&myip=' . $this->_dnsIP . '&wildcard=' . $this->_dnsWildcard); - break; - case 'staticcling': - curl_setopt($ch, CURLOPT_URL, 'https://www.staticcling.org/update.html?login=' . urlencode($this->_dnsUser) . '&pass=' . $this->_dnsPass); - break; - case 'dnsomatic': - /* Example syntax - https://username:password@updates.dnsomatic.com/nic/update?hostname=yourhostname&myip=ipaddress&wildcard=NOCHG&mx=NOCHG&backmx=NOCHG - */ - if (isset($this->_dnsWildcard) && $this->_dnsWildcard != "OFF") { - $this->_dnsWildcard = "ON"; - } - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); - /* - Reference: https://www.dnsomatic.com/wiki/api - DNS-O-Matic usernames are 3-25 characters. - DNS-O-Matic passwords are 6-20 characters. - All ASCII letters and numbers accepted. - Dots, dashes, and underscores allowed, but not at the beginning or end of the string. - Required: "rawurlencode" http://www.php.net/manual/en/function.rawurlencode.php - Encodes the given string according to RFC 3986. - */ - $server = "https://" . rawurlencode($this->_dnsUser) . ":" . rawurlencode($this->_dnsPass) . "@updates.dnsomatic.com/nic/update?hostname="; - if ($this->_dnsServer) { - $server = $this->_dnsServer; - } - if ($this->_dnsPort) { - $port = ":" . $this->_dnsPort; - } - curl_setopt($ch, CURLOPT_URL, $server . $this->_dnsHost . '&myip=' . $this->_dnsIP . '&wildcard=' . $this->_dnsWildcard . '&mx=' . $this->_dnsMX . '&backmx=NOCHG'); - break; - case 'namecheap': - /* Example: - https://dynamicdns.park-your-domain.com/update?host=[host_name]&domain=[domain.com]&password=[domain_password]&ip=[your_ip] - */ - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); - $dparts = explode(".", trim($this->_dnsHost)); - $domain_part_count = ($dparts[count($dparts) - 1] == "uk") ? 3 : 2; - $domain_offset = count($dparts) - $domain_part_count; - $hostname = implode(".", array_slice($dparts, 0, $domain_offset)); - $domain = implode(".", array_slice($dparts, $domain_offset)); - $dnspass = trim($this->_dnsPass); - $server = "https://dynamicdns.park-your-domain.com/update?host={$hostname}&domain={$domain}&password={$dnspass}&ip={$this->_dnsIP}"; - curl_setopt($ch, CURLOPT_URL, $server); - break; - case 'he-net': - case 'he-net-v6': - $server = "https://dyn.dns.he.net/nic/update?"; - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); - curl_setopt($ch, CURLOPT_URL, $server . 'hostname=' . $this->_dnsHost . '&password=' . $this->_dnsPass . '&myip=' . $this->_dnsIP); - break; - case 'he-net-tunnelbroker': - $server = "https://ipv4.tunnelbroker.net/nic/update?"; - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); - curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser . ':' . $this->_dnsPass); - curl_setopt($ch, CURLOPT_URL, $server . 'hostname=' . $this->_dnsHost); - break; - case 'digitalocean': - /* - * dnsHost should be the root domain - * dnsUser should be the record ID - * dnsPass should be the API key - */ - $server = "https://api.digitalocean.com/v2/domains/" . $this->_dnsHost . "/records/" . $this->_dnsUser; - $hostData = array("data" => "{$this->_dnsIP}"); - - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); - curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT'); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($ch, CURLOPT_HTTPHEADER, array( - "Authorization: Bearer {$this->_dnsPass}", - 'Content-Type: application/json' - )); - curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($hostData)); - curl_setopt($ch, CURLOPT_URL, $server); - break; - case 'selfhost': - if (isset($this->_dnsWildcard) && $this->_dnsWildcard != "OFF") { - $this->_dnsWildcard = "ON"; - } - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); - curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser . ':' . $this->_dnsPass); - $server = "https://carol.selfhost.de/nic/update"; - $port = ""; - if ($this->_dnsServer) { - $server = $this->_dnsServer; - } - if ($this->_dnsPort) { - $port = ":" . $this->_dnsPort; - } - curl_setopt($ch, CURLOPT_URL, $server . $port . '?system=dyndns&hostname=' . $this->_dnsHost . '&myip=' . $this->_dnsIP . '&wildcard=' . $this->_dnsWildcard . '&mx=' . $this->_dnsMX . '&backmx=NO'); - break; - case 'route53': - case 'route53-v6': - /* Setting Variables */ - $hostname = "{$this->_dnsHost}."; - $ZoneID = $this->_dnsZoneID; - $AccessKeyId = $this->_dnsUser; - $SecretAccessKey = $this->_dnsPass; - $NewIP = $this->_dnsIP; - $NewTTL = $this->_dnsTTL; - $RecordType = ($this->_useIPv6) ? "AAAA" : "A"; - - /* Set Amazon AWS Credentials for this record */ - $r53 = new Route53($AccessKeyId, $SecretAccessKey); - - /* Function to find old values of records in Route 53 */ - if (!function_exists('Searchrecords')) { - function SearchRecords($records, $name) - { - if (!is_array($records)) { - return false; - } - $result = array(); - foreach ($records as $record) { - if (strtolower($record['Name']) == strtolower($name)) { - $result [] = $record; - } - } - return ($result) ? $result : false; - } - } - - $records = $r53->listResourceRecordSets("/hostedzone/$ZoneID"); - - /* Get IP for your hostname in Route 53 */ - if (false !== ($a_result = SearchRecords($records['ResourceRecordSets'], "$hostname"))) { - /** - * if hostname for ipv4 and ipv6 is the same, a_result contains more than 1 item - * we need to get the item that corresponds to the record type - */ - $oldTTLResult = null; - $oldIPResult = null; - foreach ($a_result as $resultItem) { - if ($RecordType === $resultItem['Type']) { - $oldTTLResult = $resultItem["TTL"]; - $oldIPResult = $resultItem["ResourceRecords"][0]; - } - } - - $OldTTL = $oldTTLResult; - $OldIP = $oldIPResult; - } else { - $OldIP = ""; - } - - /* Check if we need to update DNS Record */ - if ($OldIP !== $NewIP) { - if (!empty($OldIP)) { - /* Your Hostname already exists, deleting and creating it again */ - $changes = array(); - $changes[] = $r53->prepareChange("DELETE", $hostname, $RecordType, $OldTTL, $OldIP); - $changes[] = $r53->prepareChange("CREATE", $hostname, $RecordType, $NewTTL, $NewIP); - $result = $r53->changeResourceRecordSets("/hostedzone/$ZoneID", $changes); - } else { - /* Your Hostname does not exist yet, creating it */ - $changes = $r53->prepareChange("CREATE", $hostname, $RecordType, $NewTTL, $NewIP); - $result = $r53->changeResourceRecordSets("/hostedzone/$ZoneID", $changes); - } - } - $this->_checkStatus(0, $result); - break; - case 'custom': - case 'custom-v6': - if ($this->_dnsUser != '') { - if ($this->_curlIpresolveV4) { - curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); - } - if ($this->_curlSslVerifypeer) { - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); - } else { - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); - } - curl_setopt($ch, CURLOPT_USERPWD, "{$this->_dnsUser}:{$this->_dnsPass}"); - } - $server = str_replace("%IP%", $this->_dnsIP, $this->_dnsUpdateURL); - curl_setopt($ch, CURLOPT_URL, $server); - break; - case 'cloudflare': - case 'cloudflare-v6': - $baseUrl = 'https://api.cloudflare.com/client/v4'; - $fqdn = str_replace(' ', '', $this->_dnsHost); - $recordType = ($this->_useIPv6) ? 'AAAA' : 'A'; - $hostData = array( - "content" => "{$this->_dnsIP}", - "type" => $recordType, - "name" => $fqdn - ); - - curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($ch, CURLOPT_HTTPHEADER, array( - "X-Auth-Email: {$this->_dnsUser}", - "X-Auth-Key: {$this->_dnsPass}", - 'Content-Type: application/json' - )); - - // Get all zone info - $zonesUrl = "$baseUrl/zones"; - curl_setopt($ch, CURLOPT_URL, $zonesUrl); - $output = json_decode(curl_exec($ch)); - $zoneId = null; // Set default value - - // Iterate zone objects, check if $fqdn is equal to or ends with zone name - foreach ($output->result as $key => $zoneObj) { - if (preg_match("/^{$zoneObj->name}$|\.{$zoneObj->name}$/", $fqdn)) { - // Found matching zone - $zoneId = $zoneObj->id; - // Get $hostName from $fqdn, set $domainName - // These are only really used for log messages. - $hostName = preg_replace("/\.?{$zoneObj->name}$/", '', $fqdn); - $domainName = $zoneObj->name; - break; - } - } - - if ($zoneId) { // If zone ID was found get host ID - $dnsRecordsUrl = "$zonesUrl/$zoneId/dns_records"; - $getHostId = "$dnsRecordsUrl?name=$fqdn&type=$recordType"; - curl_setopt($ch, CURLOPT_URL, $getHostId); - $output = json_decode(curl_exec($ch)); - $recordCount = !empty($output->result) ? count($output->result) : 0; - if ($recordCount === 0) { - // create record - curl_setopt($ch, CURLOPT_URL, $dnsRecordsUrl); - curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST'); - } elseif ($recordCount >= 1) { - // update record - $recordId = $output->result[0]->id; - $hostData["proxied"] = $output->result[0]->proxied; - curl_setopt($ch, CURLOPT_URL, "$dnsRecordsUrl/$recordId"); - curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT'); - } - curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($hostData)); - if ($recordCount > 1) { - log_error("Dynamic DNS ($fqdn): Warning: multiple records for $hostName found"); - } - } - break; - case 'eurodns': - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); - curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser . ':' . $this->_dnsPass); - $server = "https://update.eurodyndns.org/update/"; - $port = ""; - if ($this->_dnsPort) { - $port = ":" . $this->_dnsPort; - } - curl_setopt($ch, CURLOPT_URL, $server . $port . '?hostname=' . $this->_dnsHost . '&myip=' . $this->_dnsIP); - break; - case 'gratisdns': - $server = "https://ssl.gratisdns.dk/ddns.phtml"; - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); - list($hostname, $domain) = explode(".", $this->_dnsHost, 2); - curl_setopt($ch, CURLOPT_URL, $server . '?u=' . urlencode($this->_dnsUser) . '&p=' . $this->_dnsPass . '&h=' . $this->_dnsHost . '&d=' . $domain); - break; - case 'ovh-dynhost': - if (isset($this->_dnsWildcard) && $this->_dnsWildcard != "OFF") { - $this->_dnsWildcard = "ON"; - } - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); - curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser . ':' . $this->_dnsPass); - $server = "https://www.ovh.com/nic/update"; - $port = ""; - if ($this->_dnsServer) { - $server = $this->_dnsServer; - } - if ($this->_dnsPort) { - $port = ":" . $this->_dnsPort; - } - curl_setopt($ch, CURLOPT_URL, $server . $port . '?system=dyndns&hostname=' . $this->_dnsHost . '&myip=' . $this->_dnsIP . '&wildcard=' . $this->_dnsWildcard . '&mx=' . $this->_dnsMX . '&backmx=NO'); - break; - case 'citynetwork': - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); - curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser . ':' . $this->_dnsPass); - $server = 'https://dyndns.citynetwork.se/nic/update'; - $port = ""; - if ($this->_dnsServer) { - $server = $this->_dnsServer; - } - if ($this->_dnsPort) { - $port = ":" . $this->_dnsPort; - } - curl_setopt($ch, CURLOPT_URL, $server . $port . '?hostname=' . $this->_dnsHost . '&myip=' . $this->_dnsIP); - break; - case 'duckdns': - $server = "https://www.duckdns.org/update"; - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); - curl_setopt($ch, CURLOPT_URL, $server . '?domains=' . str_replace('.duckdns.org', '', $this->_dnsHost) . '&token=' . urlencode($this->_dnsUser)); - break; - case 'dynv6': - $server = "https://ipv4.dynv6.com/api/update"; - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); - curl_setopt($ch, CURLOPT_INTERFACE, $this->_dnsRequestIf); - curl_setopt($ch, CURLOPT_DNS_LOCAL_IP4, $this->_dnsIP); - curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); - curl_setopt($ch, CURLOPT_URL, $server . '?hostname=' . $this->_dnsHost . '&ipv4=' . $this->_dnsIP . '&token=' . $this->_dnsUser); - break; - case 'dynv6-v6': - $server = "https://ipv6.dynv6.com/api/update"; - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); - curl_setopt($ch, CURLOPT_INTERFACE, $this->_dnsRequestIf); - curl_setopt($ch, CURLOPT_DNS_LOCAL_IP6, $this->_dnsIP); - curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V6); - curl_setopt($ch, CURLOPT_URL, $server . '?hostname=' . $this->_dnsHost . '&ipv6=' . $this->_dnsIP . '&token=' . $this->_dnsUser); - break; - case 'googledomains': - $server = "https://domains.google.com/nic/update"; - $post_data['hostname'] = $this->_dnsHost; - $post_data['myip'] = $this->_dnsIP; - $post_data['offline'] = 'no'; - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); - curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser . ':' . $this->_dnsPass); - curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data); - curl_setopt($ch, CURLOPT_URL, $server); - curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); - break; - case 'strato': - $server = "https://dyndns.strato.com/nic/update?hostname={$this->_dnsHost}&myip={$this->_dnsIP}"; - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); - curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser . ':' . $this->_dnsPass); - curl_setopt($ch, CURLOPT_URL, $server); - curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); - break; - case '3322': - $server = "http://members.3322.net/dyndns/update?hostname={$this->_dnsHost}&myip={$this->_dnsIP}"; - curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser . ':' . $this->_dnsPass); - curl_setopt($ch, CURLOPT_URL, $server); - curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); - break; - case 'oray': - $server = "http://ddns.oray.com/ph/update?hostname={$this->_dnsHost}&myip={$this->_dnsIP}"; - curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser . ':' . $this->_dnsPass); - curl_setopt($ch, CURLOPT_URL, $server); - curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); - break; - case 'regfish': - case 'regfish-v6': - $family = $this->_useIPv6 ? 'ipv6' : 'ipv4'; - $server = "https://dyndns.regfish.de/?fqdn={$this->_dnsHost}&{$family}={$this->_dnsIP}&forcehost=1&token=" . urlencode($this->_dnsUser); - curl_setopt($ch, CURLOPT_URL, $server); - break; - case 'linode': - case 'linode-v6': - $baseUrl = "https://api.linode.com/v4"; - $fqdn = trim($this->_dnsHost); - $recordType = ($this->_useIPv6) ? 'AAAA' : 'A'; - - if ($this->_dnsWildcard == 'ON') { - $fqdn = "*.$fqdn"; - } - - curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($ch, CURLOPT_HTTPHEADER, array( - 'Accept: application/json', - 'Authorization: Bearer ' . $this->_dnsPass, - 'Content-Type: application/json' - )); - - $domainsUrl = "$baseUrl/domains"; - curl_setopt($ch, CURLOPT_URL, $domainsUrl); - $output = json_decode(curl_exec($ch)); - $domainId = null; - - // Find matching domain and split the hostname part from it - foreach ($output->data as $key => $domainObj) { - if (preg_match("/^{$domainObj->domain}$|\.{$domainObj->domain}$/", $fqdn)) { - $domainId = $domainObj->id; - $hostName = preg_replace("/\.?{$domainObj->domain}$/", '', $fqdn); - $domainName = $domainObj->domain; - break; - } - } - - if ($domainId) { - if ($this->_dnsVerboseLog) { - log_error("Dynamic DNS ($fqdn): Found domain name: $domainName, ID: $domainId"); - } - - $dnsRecordsUrl = "$domainsUrl/$domainId/records"; - curl_setopt($ch, CURLOPT_URL, $dnsRecordsUrl); - $output = json_decode(curl_exec($ch)); - $recordId = null; - - // Find matching record - foreach ($output->data as $key => $recordObj) { - if ($recordObj->type == $recordType && $recordObj->name == $hostName) { - $recordId = $recordObj->id; - break; - } - } - - $hostData = array( - "target" => "{$this->_dnsIP}", - ); - - if ($recordId) { - // Update record - if ($this->_dnsVerboseLog) { - log_error("Dynamic DNS ($fqdn): Updating existing record ID: $recordId"); - } - - curl_setopt($ch, CURLOPT_URL, "$dnsRecordsUrl/$recordId"); - curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT'); - } else { - // Create record - if ($this->_dnsVerboseLog) { - log_error("Dynamic DNS ($fqdn): Creating new record"); - } - - $hostData['type'] = $recordType; - $hostData['name'] = $hostName; - // Linode will round up to the nearest valid TTL - $hostData['ttl_sec'] = 0; - - curl_setopt($ch, CURLOPT_URL, $dnsRecordsUrl); - curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST'); - } - - curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($hostData)); - } else { - log_error("Dynamic DNS($fqdn): No zone found for domain"); - } - case 'azurev6': - case 'azure': - $hostname = "{$this->_dnsHost}"; - $resourceid = trim($this->_dnsResourceID); - $app_id = $this->_dnsUser; - $client_secret = $this->_dnsPass; - $newip = $this->_dnsIP; - $newttl = $this->_dnsTTL; - // ensure resourceid starts with / and has no trailing / - $resourceid = '/' . trim($resourceid, '/'); - // extract subscription id from resource id - preg_match('/\\/subscriptions\\/(?[^\\/]*)/', $resourceid, $result); - $subscriptionid = isset($result['sid']) ? $result['sid'] : ''; - if (isset($result['sid'])) { - $subscriptionid = $result['sid']; - } else { - log_error("Azure subscription id not found in resource id ({$resourceid})"); - return false; - } - // find tenant id from subscription id - curl_setopt($ch, CURLOPT_URL, "https://management.azure.com/subscriptions/" . $subscriptionid . "?api-version=2016-09-01"); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($ch, CURLOPT_HEADER, 1); - curl_setopt($ch, CURLOPT_NOBODY, 1); - $output = curl_exec($ch); - $pattern = '/Bearer authorization_uri="https:\\/\\/login.windows.net\\/(?[^"]*)/i'; - preg_match($pattern, $output, $result); - if (isset($result['tid'])) { - $tenantid = $result['tid']; - } else { - log_error("Tenant ID not found"); - return false; - } - // get an bearer token - curl_setopt($ch, CURLOPT_URL, "https://login.microsoftonline.com/" . $tenantid . "/oauth2/token"); - curl_setopt($ch, CURLOPT_POST, 1); - $body = "resource=" . urlencode("https://management.core.windows.net/") . "&grant_type=client_credentials&client_id=" . $app_id . "&client_secret=" . urlencode($client_secret); - curl_setopt($ch, CURLOPT_POSTFIELDS, $body); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - $server_output = curl_exec($ch); - $httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE); - preg_match("/\"access_token\":\"(?[^\"]*)\"/", $server_output, $result); - if (isset($result['tok'])) { - $bearertoken = $result['tok']; - } else { - log_error("no valid bearer token"); - return false; - } - // Update the DNS record - if ($this->_useIPv6) { - $url = "https://management.azure.com" . $resourceid . "/AAAA/" . $hostname . "?api-version=2017-09-01"; - $body = '{"properties":{"TTL":"' . $newttl . '", "AAAARecords":[{"ipv6Address":"' . $newip . '"}]}}'; - } else { - $url = "https://management.azure.com" . $resourceid . "/A/" . $hostname . "?api-version=2017-09-01"; - $body = '{"properties":{"TTL":"' . $newttl . '", "ARecords":[{"ipv4Address":"' . $newip . '"}]}}'; - } - $request_headers = array(); - $request_headers[] = 'Accept: application/json'; - $request_headers[] = 'Authorization: Bearer ' . $bearertoken; - $request_headers[] = 'Content-Type: application/json'; - curl_setopt($ch, CURLOPT_URL, $url); - curl_setopt($ch, CURLOPT_USERAGENT, $this->_UserAgent); - curl_setopt($ch, CURLOPT_HTTPHEADER, $request_headers); - curl_setopt($ch, CURLOPT_POSTFIELDS, $body); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_HEADER, 1); - curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT'); - break; - default: - break; - } - if ($this->_dnsService != 'ods' and $this->_dnsService != 'route53' and $this->_dnsService != 'route53-v6') { - $data = curl_exec($ch); - $this->_checkStatus($ch, $data); - @curl_close($ch); - } - } - - /* - * Private Function (added 12 July 2005) [beta] - * Retrieve Update Status - */ - function _checkStatus($ch, $data) - { - if ($this->_dnsVerboseLog) { - log_error("Dynamic DNS ({$this->_dnsHost}): _checkStatus() starting."); - log_error("Dynamic DNS ({$this->_dnsHost}): Current Service: {$this->_dnsService}"); - } - $successful_update = false; - if ($this->_dnsService != 'ods' and $this->_dnsService != 'route53' and $this->_dnsService != 'route53-v6' && @curl_error($ch)) { - $status = "Curl error occurred: " . curl_error($ch); - log_error($status); - $this->status = $status; - return; - } - switch ($this->_dnsService) { - case 'dhs': - break; - case 'noip': - case 'noip-free': - list($ip,$code) = explode(":", $data); - switch ($code) { - case 0: - $status = "Dynamic DNS ({$this->_dnsHost}): (Success) IP address is current, no update performed."; - $successful_update = true; - break; - case 1: - $status = "Dynamic DNS ({$this->_dnsHost}): (Success) DNS hostname update successful."; - $successful_update = true; - break; - case 2: - $status = "Dynamic DNS ({$this->_dnsHost}): (Error) Hostname supplied does not exist."; - break; - case 3: - $status = "Dynamic DNS ({$this->_dnsHost}): (Error) Invalid Username."; - break; - case 4: - $status = "Dynamic DNS ({$this->_dnsHost}): (Error) Invalid Password."; - break; - case 5: - $status = "Dynamic DNS ({$this->_dnsHost}): (Error) To many updates sent."; - break; - case 6: - $status = "Dynamic DNS ({$this->_dnsHost}): (Error) Account disabled due to violation of No-IP terms of service."; - break; - case 7: - $status = "Dynamic DNS ({$this->_dnsHost}): (Error) Invalid IP. IP Address submitted is improperly formatted or is a private IP address or is on a blacklist."; - break; - case 8: - $status = "Dynamic DNS ({$this->_dnsHost}): (Error) Disabled / Locked Hostname."; - break; - case 9: - $status = "Dynamic DNS ({$this->_dnsHost}): (Error) Host updated is configured as a web redirect and no update was performed."; - break; - case 10: - $status = "Dynamic DNS ({$this->_dnsHost}): (Error) Group supplied does not exist."; - break; - case 11: - $status = "Dynamic DNS ({$this->_dnsHost}): (Success) DNS group update is successful."; - $successful_update = true; - break; - case 12: - $status = "Dynamic DNS ({$this->_dnsHost}): (Success) DNS group is current, no update performed."; - $successful_update = true; - break; - case 13: - $status = "Dynamic DNS ({$this->_dnsHost}): (Error) Update client support not available for supplied hostname or group."; - break; - case 14: - $status = "Dynamic DNS ({$this->_dnsHost}): (Error) Hostname supplied does not have offline settings configured."; - break; - case 99: - $status = "Dynamic DNS ({$this->_dnsHost}): (Error) Client disabled. Client should exit and not perform any more updates without user intervention."; - break; - case 100: - $status = "Dynamic DNS ({$this->_dnsHost}): (Error) Client disabled. Client should exit and not perform any more updates without user intervention."; - break; - default: - $status = "Dynamic DNS ({$this->_dnsHost}): (Unknown Response)"; - $this->_debug("Unknown Response: " . $data); - break; - } - break; - case 'easydns': - if (preg_match('/NOACCESS/i', $data)) { - $status = "Dynamic DNS ({$this->_dnsHost}): (Error) Authentication Failed: Username and/or Password was Incorrect."; - } elseif (preg_match('/NOSERVICE/i', $data)) { - $status = "Dynamic DNS ({$this->_dnsHost}): (Error) No Service: Dynamic DNS Service has been disabled for this domain."; - } elseif (preg_match('/ILLEGAL INPUT/i', $data)) { - $status = "Dynamic DNS ({$this->_dnsHost}): (Error) Illegal Input: Self-Explanatory"; - } elseif (preg_match('/TOOSOON/i', $data)) { - $status = "Dynamic DNS ({$this->_dnsHost}): (Error) Too Soon: Not Enough Time Has Elapsed Since Last Update"; - } elseif (preg_match('/NOERROR/i', $data)) { - $status = "Dynamic DNS ({$this->_dnsHost}): (Success) IP Updated Successfully!"; - $successful_update = true; - } else { - $status = "Dynamic DNS ({$this->_dnsHost}): (Unknown Response)"; - log_error("Dynamic DNS ({$this->_dnsHost}): PAYLOAD: {$data}"); - $this->_debug($data); - } - break; - case 'hn': - /* FIXME: add checks */ - break; - case 'zoneedit': - if (preg_match('/799/i', $data)) { - $status = "Dynamic DNS ({$this->_dnsHost}): (Error 799) Update Failed!"; - } elseif (preg_match('/700/i', $data)) { - $status = "Dynamic DNS ({$this->_dnsHost}): (Error 700) Update Failed!"; - } elseif (preg_match('/200/i', $data)) { - $status = "Dynamic DNS ({$this->_dnsHost}): (Success) IP Address Updated Successfully!"; - $successful_update = true; - } elseif (preg_match('/201/i', $data)) { - $status = "Dynamic DNS ({$this->_dnsHost}): (Success) IP Address Updated Successfully!"; - $successful_update = true; - } else { - $status = "Dynamic DNS ({$this->_dnsHost}): (Unknown Response)"; - log_error("Dynamic DNS ({$this->_dnsHost}): PAYLOAD: {$data}"); - $this->_debug($data); - } - break; - case 'dyns': - if (preg_match("/400/i", $data)) { - $status = "Dynamic DNS ({$this->_dnsHost}): (Error) Bad Request - The URL was malformed. Required parameters were not provided."; - } elseif (preg_match('/402/i', $data)) { - $status = "Dynamic DNS ({$this->_dnsHost}): (Error) Update Too Soon - You have tried updating to quickly since last change."; - } elseif (preg_match('/403/i', $data)) { - $status = "Dynamic DNS ({$this->_dnsHost}): (Error) Database Error - There was a server-sided database error."; - } elseif (preg_match('/405/i', $data)) { - $status = "Dynamic DNS ({$this->_dnsHost}): (Error) Hostname Error - The hostname (" . $this->_dnsHost . ") doesn't belong to you."; - } elseif (preg_match('/200/i', $data)) { - $status = "Dynamic DNS ({$this->_dnsHost}): (Success) IP Address Updated Successfully!"; - $successful_update = true; - } else { - $status = "Dynamic DNS ({$this->_dnsHost}): (Unknown Response)"; - log_error("Dynamic DNS ({$this->_dnsHost}): PAYLOAD: {$data}"); - $this->_debug($data); - } - break; - case 'ods': - if (preg_match("/299/i", $data)) { - $status = "Dynamic DNS ({$this->_dnsHost}): (Success) IP Address Updated Successfully!"; - $successful_update = true; - } else { - $status = "Dynamic DNS ({$this->_dnsHost}): (Unknown Response)"; - log_error("Dynamic DNS ({$this->_dnsHost}): PAYLOAD: {$data}"); - $this->_debug($data); - } - break; - case 'freedns': - if (preg_match("/has not changed./i", $data)) { - $status = "Dynamic DNS ({$this->_dnsHost}): (Success) No Change In IP Address"; - $successful_update = true; - } elseif (preg_match("/Updated/i", $data)) { - $status = "Dynamic DNS ({$this->_dnsHost}): (Success) IP Address Changed Successfully!"; - $successful_update = true; - } else { - $status = "Dynamic DNS ({$this->_dnsHost}): (Unknown Response)"; - log_error("Dynamic DNS ({$this->_dnsHost}): PAYLOAD: {$data}"); - $this->_debug($data); - } - break; - case 'dnsexit': - if (preg_match("/is the same/i", $data)) { - $status = "Dynamic DNS ({$this->_dnsHost}): (Success) No Change In IP Address"; - $successful_update = true; - } elseif (preg_match("/Success/i", $data)) { - $status = "Dynamic DNS ({$this->_dnsHost}): (Success) IP Address Changed Successfully!"; - $successful_update = true; - } else { - $status = "Dynamic DNS ({$this->_dnsHost}): (Unknown Response)"; - log_error("Dynamic DNS ({$this->_dnsHost}): PAYLOAD: {$data}"); - $this->_debug($data); - } - break; - case 'staticcling': - if (preg_match("/invalid ip/i", $data)) { - $status = "Dynamic DNS ({$this->_dnsHost}): (Error) Bad Request - The IP provided was invalid."; - } elseif (preg_match('/required info missing/i', $data)) { - $status = "Dynamic DNS ({$this->_dnsHost}): (Error) Bad Request - Required parameters were not provided."; - } elseif (preg_match('/invalid characters/i', $data)) { - $status = "Dynamic DNS ({$this->_dnsHost}): (Error) Bad Request - Illegal characters in either the username or the password."; - } elseif (preg_match('/bad password/i', $data)) { - $status = "Dynamic DNS ({$this->_dnsHost}): (Error) Invalid password."; - } elseif (preg_match('/account locked/i', $data)) { - $status = "Dynamic DNS ({$this->_dnsHost}): (Error) This account has been administratively locked."; - } elseif (preg_match('/update too frequent/i', $data)) { - $status = "Dynamic DNS ({$this->_dnsHost}): (Error) Updating too frequently."; - } elseif (preg_match('/DB error/i', $data)) { - $status = "Dynamic DNS ({$this->_dnsHost}): (Error) Server side error."; - } elseif (preg_match('/success/i', $data)) { - $status = "Dynamic DNS ({$this->_dnsHost}): (Success) IP Address Updated Successfully!"; - $successful_update = true; - } else { - $status = "Dynamic DNS ({$this->_dnsHost}): (Unknown Response)"; - log_error("Dynamic DNS ({$this->_dnsHost}): PAYLOAD: {$data}"); - $this->_debug($data); - } - break; - case 'namecheap': - $tmp = str_replace("^M", "", $data); - $ncresponse = simplexml_load_string($tmp); - if (preg_match("/internal server error/i", $data)) { - $status = "Dynamic DNS: (Error) Server side error."; - } elseif (preg_match("/request is badly formed/i", $data)) { - $status = "Dynamic DNS: (Error) Badly Formed Request (check your settings)."; - } elseif ((string)$ncresponse->ErrCount === "0") { - $status = "Dynamic DNS: (Success) IP Address Updated Successfully!"; - $successful_update = true; - } elseif (isset($ncresponse->ErrCount) && is_numeric((string)$ncresponse->ErrCount) && (string)$ncresponse->ErrCount > 0) { - $status = "Dynamic DNS: (Error) "; - if (isset($ncresponse->errors)) { - foreach ($ncresponse->errors->children() as $err) { - $status .= (string)$err . " "; - } - } - $successful_update = true; - } else { - $status = "Dynamic DNS: (Unknown Response)"; - log_error("Dynamic DNS: PAYLOAD: {$data}"); - $this->_debug($data); - } - break; - case 'route53': - case 'route53-v6': - $successful_update = true; - break; - case 'custom': - case 'custom-v6': - $successful_update = false; - if ($this->_dnsResultMatch == "") { - $successful_update = true; - } else { - $this->_dnsResultMatch = str_replace("%IP%", $this->_dnsIP, $this->_dnsResultMatch); - $matches = preg_split("/(?_dnsResultMatch); - foreach ($matches as $match) { - $match = str_replace("\\|", "|", $match); - if (strcmp($match, trim($data, "\t\n\r")) == 0) { - $successful_update = true; - } - } - unset($matches); - } - if ($successful_update == true) { - $status = "Dynamic DNS: (Success) IP Address Updated Successfully!"; - } else { - $status = "Dynamic DNS: (Error) Result did not match."; - } - break; - case 'cloudflare': - case 'cloudflare-v6': - $output = json_decode($data); - if ($output->result->content === $this->_dnsIP) { - $status = "Dynamic DNS: (Success) {$this->_dnsHost} updated to {$this->_dnsIP}"; - $successful_update = true; - } elseif ($output->errors[0]->code === 9103) { - $status = "Dynamic DNS ({$this->_dnsHost}): ERROR - Invalid Credentials! Don't forget to use API Key for password field with CloudFlare."; - } elseif (($output->success) && (!$output->result[0]->id)) { - $status = "Dynamic DNS ({$this->_dnsHost}): ERROR - Zone ID was not found."; - } else { - $status = "Dynamic DNS ({$this->_dnsHost}): UNKNOWN ERROR - {$output->errors[0]->message}"; - log_error("Dynamic DNS ({$this->_dnsHost}): PAYLOAD: {$data}"); - } - break; - case 'digitalocean': - $output = json_decode($data); - if ($output->domain_record->data === $this->_dnsIP) { - $status = "Dynamic DNS: (Success) Record ID {$this->_dnsUser} updated to {$this->_dnsIP}"; - $successful_update = true; - } else { - $status = "Dynamic DNS Record ID ({$this->_dnsUser}): UNKNOWN ERROR"; - log_error("Dynamic DNS Record ID ({$this->_dnsUser}): PAYLOAD: {$data}"); - } - break; - case 'gratisdns': - if (preg_match('/Forkerte værdier/i', $data)) { - $status = "Dynamic DNS: (Error) Wrong values - Update could not be completed."; - } elseif (preg_match('/Bruger login: Bruger eksistere ikke/i', $data)) { - $status = "Dynamic DNS: (Error) Unknown username - User does not exist."; - } elseif (preg_match('/Bruger login: 1Fejl i kodeord/i', $data)) { - $status = "Dynamic DNS: (Error) Wrong password - Remember password is case sensitive."; - } elseif (preg_match('/Domæne kan IKKE administreres af bruger/i', $data)) { - $status = "Dynamic DNS: (Error) User unable to administer the selected domain."; - } elseif (preg_match('/OK/i', $data)) { - $status = "Dynamic DNS: (Success) IP Address Updated Successfully!"; - $successful_update = true; - } else { - $status = "Dynamic DNS: (Unknown Response)"; - log_error("Dynamic DNS: PAYLOAD: {$data}"); - $this->_debug($data); - } - break; - case 'duckdns': - if (preg_match('/OK/i', $data)) { - $status = "Dynamic DNS: (Success) IP Address Updated Successfully!"; - $successful_update = true; - } else { - $status = "Dynamic DNS: (Unknown Response)"; - log_error("Dynamic DNS: PAYLOAD: {$data}"); - $this->_debug($data); - } - break; - case 'dynv6': - case 'dynv6-v6': - /* API-Documentation: https://dynv6.com/docs/apis */ - if (preg_match('/addresses updated/i', $data)) { - $status = "Dynamic DNS: (Success) IP Address Updated Successfully!"; - $successful_update = true; - } elseif (preg_match('/addresses unchanged/i', $data)) { - $status = "Dynamic DNS: (Success) IP Address Unchanged!"; - $successful_update = true; - } else { - $status = "Dynamic DNS: (Unknown Response)"; - log_error("Dynamic DNS: PAYLOAD: {$data}"); - $this->_debug($data); - } - break; - case '3322': - case 'citynetwork': - case 'dnsomatic': - case 'dyndns': - case 'dyndns-custom': - case 'dyndns-static': - case 'eurodns': - case 'googledomains': - case 'he-net': - case 'he-net-tunnelbroker': - case 'he-net-v6': - case 'loopia': - case 'oray': - case 'ovh-dynhost': - case 'selfhost': - case 'strato': - if (preg_match('/notfqdn/i', $data)) { - $status = "Dynamic DNS: (Error) Not a FQDN"; - } elseif (preg_match('/nochg/i', $data)) { - $status = "Dynamic DNS: (Success) No change in IP address"; - $successful_update = true; - } elseif (preg_match('/good/i', $data)) { - $status = "Dynamic DNS: (Success) IP address updated successfully ({$this->_dnsIP})"; - $successful_update = true; - } elseif (preg_match('/badauth/i', $data)) { - $status = "Dynamic DNS: (Error) Authentication failed"; - } elseif (preg_match("/badip/i", $data)) { - $status = "Dynamic DNS: (Error) IP address provided is invalid"; - } elseif (preg_match('/nohost/i', $data)) { - $status = "Dynamic DNS: (Error) Hostname does not exist or does not have dynamic DNS enabled"; - } elseif (preg_match('/numhost/i', $data)) { - $status = "Dynamic DNS: (Error) You may update up to 20 hosts only"; - } elseif (preg_match('/dnserr/i', $data)) { - $status = "Dynamic DNS: (Error) DNS error, stop updating for 30 minutes."; - } elseif (preg_match('/badagent/i', $data)) { - $status = "Dynamic DNS: (Error) Bad request"; - } elseif (preg_match('/abuse/i', $data)) { - $status = "Dynamic DNS: (Error) Access has been blocked for abuse"; - } elseif (preg_match('/911/i', $data)) { - $status = "Dynamic DNS: (Error) Server-side error or maintenance"; - } elseif (preg_match('/yours/i', $data)) { - $status = "Dynamic DNS: (Error) Specified hostname does not exist under this username"; - } else { - $status = "Dynamic DNS: (Unknown Response)"; - log_error("Dynamic DNS: PAYLOAD: {$data}"); - $this->_debug($data); - } - break; - case 'regfish': - case 'regfish-v6': - if (preg_match('/\|100\|/', $data)) { - $status = 'Dynamic DNS: (Success) Update successful'; - $successful_update = true; - } elseif (preg_match('/\|101\|/', $data)) { - $status = 'Dynamic DNS: (Success) Still up-to-date'; - $successful_update = true; - } elseif (preg_match('/\|401\|/', $data)) { - $status = 'Dynamic DNS: (Error) Standard authentication failed'; - } elseif (preg_match('/\|402\|/', $data)) { - $status = 'Dynamic DNS: (Error) Authentication failed'; - } elseif (preg_match('/\|406\|/', $data)) { - $status = 'Dynamic DNS: (Error) Invalid resource record'; - } elseif (preg_match('/\|407\|/', $data)) { - $status = 'Dynamic DNS: (Error) Invalid TTL range'; - } elseif (preg_match('/\|408\|/', $data)) { - $status = 'Dynamic DNS: (Error) Invalid IPv4'; - } elseif (preg_match('/\|409\|/', $data)) { - $status = 'Dynamic DNS: (Error) Invalid IPv6'; - } elseif (preg_match('/\|410\|/', $data)) { - $status = 'Dynamic DNS: (Error) Unknown authentication type'; - } elseif (preg_match('/\|412\|/', $data)) { - $status = 'Dynamic DNS: (Error) Domain format is wrong, missing trailing dot?'; - } elseif (preg_match('/\|414\|/', $data)) { - $status = 'Dynamic DNS: (Error) Unexpected error'; - } elseif (preg_match('/\|415\|/', $data)) { - $status = 'Dynamic DNS: (Error) Cannot update load balancer'; - } else { - $status = "Dynamic DNS: (Unknown Response)"; - log_error("Dynamic DNS: PAYLOAD: {$data}"); - $this->_debug($data); - } - case 'linode': - case 'linode-v6': - $fqdn = trim($this->_dnsHost); - if ($this->_dnsWildcard == 'ON') { - $fqdn = "*.$fqdn"; - } - - $output = json_decode($data); - if ($output->target === $this->_dnsIP) { - $status = "Dynamic DNS: (Success) $fqdn updated to {$this->_dnsIP}"; - $successful_update = true; - } elseif (!empty($output->errors)) { - $status = "Dynamic DNS ($fqdn): ERROR - Reason: {$output->errors[0]->reason}"; - } else { - $status = "Dynamic DNS ($fqdn): UNKNOWN ERROR"; - log_error("Dynamic DNS ($fqdn): PAYLOAD: {$data}"); - } - break; - case 'azure': - case 'azurev6': - $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); - if ($http_code == 401) { - $status = 'Dynamic DNS: (Error) User Authorization Failed'; - } elseif ($http_code == 201) { - $status = 'Dynamic DNS: (Success) IP Address Changed Successfully!'; - $successful_update = true; - } elseif ($http_code == 200) { - $status = 'Dynamic DNS: (Success) IP Address Changed Successfully!'; - $successful_update = true; - } else { - $status = 'Dynamic DNS: (Error) "Unknown Response"'; - log_error("Dynamic DNS: HTTP Status: {$http_code} PAYLOAD: {$data}"); - $this->_debug($data); - } - break; - default: - break; - } - - if ($successful_update == true) { - /* Write WAN IP to cache file */ - $wan_ip = $this->_checkIP(); - if ($this->_useIPv6 == false && $wan_ip > 0) { - $currentTime = time(); - log_error("Dynamic DNS: updating cache file {$this->_cacheFile}: {$wan_ip}"); - @file_put_contents($this->_cacheFile, "{$wan_ip}|{$currentTime}"); - } else { - @unlink($this->_cacheFile); - } - if ($this->_useIPv6 == true && $wan_ip > 0) { - $currentTime = time(); - log_error("Dynamic DNS: updating cache file {$this->_cacheFile_v6}: {$wan_ip}"); - @file_put_contents($this->_cacheFile_v6, "{$wan_ip}|{$currentTime}"); - } else { - @unlink($this->_cacheFile_v6); - } - } - $this->status = $status; - log_error($status); - } - - /* - * Private Function (added 12 July 05) [beta] - * Return Error, Set Last Error, and Die. - */ - function _error($errorNumber = '1') - { - switch ($errorNumber) { - case 0: - break; - case 2: - $error = 'Dynamic DNS: (ERROR!) No Dynamic DNS Service provider was selected.'; - break; - case 3: - $error = 'Dynamic DNS: (ERROR!) No Username Provided.'; - break; - case 4: - $error = 'Dynamic DNS: (ERROR!) No Password Provided.'; - break; - case 5: - $error = 'Dynamic DNS: (ERROR!) No Hostname Provided.'; - break; - case 6: - $error = 'Dynamic DNS: (ERROR!) The Dynamic DNS Service provided is not yet supported.'; - break; - case 7: - $error = 'Dynamic DNS: (ERROR!) No Update URL Provided.'; - break; - case 8: - $status = "Route 53: (Error) Invalid ZoneID"; - break; - case 9: - $status = "Route 53: (Error) Invalid TTL"; - break; - case 10: - $error = "Dynamic DNS ({$this->_dnsHost}): No change in my IP address and/or " . $this->_dnsMaxCacheAgeDays . " days has not passed. Not updating dynamic DNS entry."; - break; - default: - $error = "Dynamic DNS: (ERROR!) Unknown Response."; - /* FIXME: $data isn't in scope here */ - /* $this->_debug($data); */ - break; - } - $this->lastError = $error; - log_error($error); - } - - /* - * Private Function (added 12 July 05) [beta] - * - Detect whether or not IP needs to be updated. - * | Written Specifically for pfSense (https://www.pfsense.org) may - * | work with other systems. pfSense base is FreeBSD. - */ - function _detectChange() - { - $currentTime = time(); - - $wan_ip = $this->_checkIP(); - if ($wan_ip == 0) { - log_error("Dynamic DNS ({$this->_dnsHost}): Current WAN IP could not be determined, skipping update process."); - return false; - } - $log_error = "Dynamic DNS ({$this->_dnsHost}): Current WAN IP: {$wan_ip} "; - - if ($this->_useIPv6 == true) { - if (file_exists($this->_cacheFile_v6)) { - $contents = file_get_contents($this->_cacheFile_v6); - list($cacheIP,$cacheTime) = explode('|', $contents); - $this->_debug($cacheIP . '/' . $cacheTime); - $initial = false; - $log_error .= "Cached IPv6: {$cacheIP} "; - } else { - $cacheIP = '::'; - @file_put_contents($this->_cacheFile, "::|{$currentTime}"); - $cacheTime = $currentTime; - $initial = true; - $log_error .= "No Cached IPv6 found."; - } - } else { - if (file_exists($this->_cacheFile)) { - $contents = file_get_contents($this->_cacheFile); - list($cacheIP,$cacheTime) = explode('|', $contents); - $this->_debug($cacheIP . '/' . $cacheTime); - $initial = false; - $log_error .= "Cached IP: {$cacheIP} "; - } else { - $cacheIP = '0.0.0.0'; - @file_put_contents($this->_cacheFile, "0.0.0.0|{$currentTime}"); - $cacheTime = $currentTime; - $initial = true; - $log_error .= "No Cached IP found."; - } - } - if ($this->_dnsVerboseLog) { - log_error($log_error); - } - - // Convert seconds = days * hr/day * min/hr * sec/min - $maxCacheAgeSecs = $this->_dnsMaxCacheAgeDays * 24 * 60 * 60; - - $needs_updating = false; - /* lets determine if the item needs updating */ - if ($cacheIP != $wan_ip) { - $needs_updating = true; - $update_reason = "Dynamic DNS: cacheIP != wan_ip. Updating. "; - $update_reason .= "Cached IP: {$cacheIP} WAN IP: {$wan_ip} "; - } - if (($currentTime - $cacheTime) > $maxCacheAgeSecs) { - $needs_updating = true; - $this->_forceUpdateNeeded = true; - $update_reason = "Dynamic DNS: More than " . $this->_dnsMaxCacheAgeDays . " days. Updating. "; - $update_reason .= "{$currentTime} - {$cacheTime} > {$maxCacheAgeSecs} "; - } - if ($initial == true) { - $needs_updating = true; - $update_reason .= "Initial update. "; - } - - /* finally if we need updating then store the - * new cache value and return true - */ - if ($needs_updating == true) { - if ($this->_dnsVerboseLog) { - log_error("Dynamic DNS ({$this->_dnsHost}): {$update_reason}"); - } - return true; - } - - return false; - } - - /* - * Private Function (added 16 July 05) [beta] - * - Writes debug information to a file. - * - This function is only called when a unknown response - * - status is returned from a dynamic DNS service provider. - */ - function _debug($data) - { - $string = date('m-d-y h:i:s') . ' - (' . $this->_debugID . ') - [' . $this->_dnsService . '] - ' . $data . "\n"; - $file = fopen($this->_debugFile, 'a'); - fwrite($file, $string); - fclose($file); - } - - function _checkIP() - { - $ip_address = get_dyndns_ip($this->_if, $this->_useIPv6 ? 6 : 4); - if (!is_ipaddr($ip_address)) { - if ($this->_dnsVerboseLog) { - log_error("Dynamic DNS ({$this->_dnsHost}): IP address could not be extracted"); - } - - $ip_address = 0; - } else { - if ($this->_dnsVerboseLog) { - log_error("Dynamic DNS ({$this->_dnsHost}): {$ip_address} extracted"); - } - - $this->_dnsIP = $ip_address; - } - - return $ip_address; - } -} diff --git a/dns/dyndns/src/etc/inc/plugins.inc.d/dyndns/r53.inc b/dns/dyndns/src/etc/inc/plugins.inc.d/dyndns/r53.inc deleted file mode 100644 index d7ebc65fbc..0000000000 --- a/dns/dyndns/src/etc/inc/plugins.inc.d/dyndns/r53.inc +++ /dev/null @@ -1,753 +0,0 @@ -__accessKey; } - public function getSecretKey() { return $this->__secretKey; } - public function getHost() { return $this->__host; } - - protected $__verifyHost = 1; - protected $__verifyPeer = 1; - - // verifyHost and verifyPeer determine whether curl verifies ssl certificates. - // It may be necessary to disable these checks on certain systems. - // These only have an effect if SSL is enabled. - public function verifyHost() { return $this->__verifyHost; } - public function enableVerifyHost($enable = true) { $this->__verifyHost = $enable; } - - public function verifyPeer() { return $this->__verifyPeer; } - public function enableVerifyPeer($enable = true) { $this->__verifyPeer = $enable; } - - /** - * Constructor - * - * @param string $accessKey Access key - * @param string $secretKey Secret key - * @return void - */ - public function __construct($accessKey = null, $secretKey = null, $host = 'route53.amazonaws.com') { - if ($accessKey !== null && $secretKey !== null) { - $this->setAuth($accessKey, $secretKey); - } - $this->__host = $host; - } - - /** - * Set AWS access key and secret key - * - * @param string $accessKey Access key - * @param string $secretKey Secret key - * @return void - */ - public function setAuth($accessKey, $secretKey) { - $this->__accessKey = $accessKey; - $this->__secretKey = $secretKey; - } - - /** - * Lists the hosted zones on the account - * - * @param string marker A pagination marker returned by a previous truncated call - * @param int maxItems The maximum number of items per page. The service uses min($maxItems, 100). - * @return A list of hosted zones - */ - public function listHostedZones($marker = null, $maxItems = 100) { - $rest = new Route53Request($this, 'hostedzone', 'GET'); - - if($marker !== null) { - $rest->setParameter('marker', $marker); - } - if($maxItems !== 100) { - $rest->setParameter('maxitems', $maxItems); - } - - $rest = $rest->getResponse(); - if($rest->error === false && $rest->code !== 200) { - $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); - } - if($rest->error !== false) { - $this->__triggerError('listHostedZones', $rest->error); - return false; - } - - $response = array(); - if (!isset($rest->body)) - { - return $response; - } - - $zones = array(); - foreach($rest->body->HostedZones->HostedZone as $z) - { - $zones[] = $this->parseHostedZone($z); - } - $response['HostedZone'] = $zones; - - if(isset($rest->body->MaxItems)) { - $response['MaxItems'] = (string)$rest->body->MaxItems; - } - - if(isset($rest->body->IsTruncated)) { - $response['IsTruncated'] = (string)$rest->body->IsTruncated; - if($response['IsTruncated'] == 'true') { - $response['NextMarker'] = (string)$rest->body->NextMarker; - } - } - - return $response; - } - - /** - * Retrieves information on a specified hosted zone - * - * @param string zoneId The id of the hosted zone, as returned by CreateHostedZoneResponse or ListHostedZoneResponse - * In other words, if ListHostedZoneResponse shows the zone's Id as '/hostedzone/Z1PA6795UKMFR9', - * then that full value should be passed here, including the '/hostedzone/' prefix. - * @return A data structure containing information about the specified zone - */ - public function getHostedZone($zoneId) { - // we'll strip off the leading forward slash, so we can use it as the action directly. - $zoneId = trim($zoneId, '/'); - - $rest = new Route53Request($this, $zoneId, 'GET'); - - $rest = $rest->getResponse(); - if($rest->error === false && $rest->code !== 200) { - $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); - } - if($rest->error !== false) { - $this->__triggerError('getHostedZone', $rest->error); - return false; - } - - $response = array(); - if (!isset($rest->body)) - { - return $response; - } - - $response['HostedZone'] = $this->parseHostedZone($rest->body->HostedZone); - $response['NameServers'] = $this->parseDelegationSet($rest->body->DelegationSet); - - return $response; - } - - /** - * Creates a new hosted zone - * - * @param string name The name of the hosted zone (e.g. "example.com.") - * @param string reference A user-specified unique reference for this request - * @param string comment An optional user-specified comment to attach to the zone - * @return A data structure containing information about the newly created zone - */ - public function createHostedZone($name, $reference, $comment = '') { - // hosted zone names must end with a period, but people will forget this a lot... - if(strrpos($name, '.') != (strlen($name) - 1)) { - $name .= '.'; - } - - $data = "\n"; - $data .= '\n"; - $data .= ''.$name."\n"; - $data .= ''.$reference."\n"; - if(strlen($comment) > 0) { - $data .= "\n"; - $data .= ''.$comment."\n"; - $data .= "\n"; - } - $data .= "\n"; - - $rest = new Route53Request($this, 'hostedzone', 'POST', $data); - - $rest = $rest->getResponse(); - - if($rest->error === false && !in_array($rest->code, array(200, 201, 202, 204)) ) { - $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); - } - if($rest->error !== false) { - $this->__triggerError('createHostedZone', $rest->error); - return false; - } - - $response = array(); - if (!isset($rest->body)) - { - return $response; - } - - $response['HostedZone'] = $this->parseHostedZone($rest->body->HostedZone); - $response['ChangeInfo'] = $this->parseChangeInfo($rest->body->ChangeInfo); - $response['NameServers'] = $this->parseDelegationSet($rest->body->DelegationSet); - - return $response; - } - - /** - * Retrieves information on a specified hosted zone - * - * @param string zoneId The id of the hosted zone, as returned by CreateHostedZoneResponse or ListHostedZoneResponse - * In other words, if ListHostedZoneResponse shows the zone's Id as '/hostedzone/Z1PA6795UKMFR9', - * then that full value should be passed here, including the '/hostedzone/' prefix. - * @return The change request data corresponding to this delete - */ - public function deleteHostedZone($zoneId) { - // we'll strip off the leading forward slash, so we can use it as the action directly. - $zoneId = trim($zoneId, '/'); - - $rest = new Route53Request($this, $zoneId, 'DELETE'); - - $rest = $rest->getResponse(); - if($rest->error === false && $rest->code !== 200) { - $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); - } - if($rest->error !== false) { - $this->__triggerError('deleteHostedZone', $rest->error); - return false; - } - - if (!isset($rest->body)) - { - return array(); - } - - return $this->parseChangeInfo($rest->body->ChangeInfo); - } - - /** - * Retrieves a list of resource record sets for a given zone - * - * @param string zoneId The id of the hosted zone, as returned by CreateHostedZoneResponse or ListHostedZoneResponse - * In other words, if ListHostedZoneResponse shows the zone's Id as '/hostedzone/Z1PA6795UKMFR9', - * then that full value should be passed here, including the '/hostedzone/' prefix. - * @param string type The type of resource record set to begin listing from. If this is specified, $name must also be specified. - * Must be one of: A, AAAA, CNAME, MX, NS, PTR, SOA, SPF, SRV, TXT - * @param string name The name at which to begin listing resource records (in the lexographic order of records). - * @param int maxItems The maximum number of results to return. The service uses min($maxItems, 100). - * @return The list of matching resource record sets - */ - public function listResourceRecordSets($zoneId, $type = '', $name = '', $maxItems = 100) { - // we'll strip off the leading forward slash, so we can use it as the action directly. - $zoneId = trim($zoneId, '/'); - - $rest = new Route53Request($this, $zoneId.'/rrset', 'GET'); - - if(strlen($type) > 0) { - $rest->setParameter('type', $type); - } - if(strlen($name) > 0) { - $rest->setParameter('name', $name); - } - if($maxItems != 100) { - $rest->setParameter('maxitems', $maxItems); - } - - $rest = $rest->getResponse(); - if($rest->error === false && $rest->code !== 200) { - $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); - } - if($rest->error !== false) { - $this->__triggerError('listResourceRecordSets', $rest->error); - return false; - } - - $response = array(); - if (!isset($rest->body)) - { - return $response; - } - - $recordSets = array(); - foreach($rest->body->ResourceRecordSets->ResourceRecordSet as $set) { - $recordSets[] = $this->parseResourceRecordSet($set); - } - - $response['ResourceRecordSets'] = $recordSets; - - if(isset($rest->body->MaxItems)) { - $response['MaxItems'] = (string)$rest->body->MaxItems; - } - - if(isset($rest->body->IsTruncated)) { - $response['IsTruncated'] = (string)$rest->body->IsTruncated; - if($response['IsTruncated'] == 'true') { - $response['NextRecordName'] = (string)$rest->body->NextRecordName; - $response['NextRecordType'] = (string)$rest->body->NextRecordType; - } - } - - return $response; - } - - /** - * Makes the specified resource record set changes (create or delete). - * - * @param string zoneId The id of the hosted zone, as returned by CreateHostedZoneResponse or ListHostedZoneResponse - * In other words, if ListHostedZoneResponse shows the zone's Id as '/hostedzone/Z1PA6795UKMFR9', - * then that full value should be passed here, including the '/hostedzone/' prefix. - * @param array changes An array of change objects, as they are returned by the prepareChange utility method. - * You may also pass a single change object. - * @param string comment An optional comment to attach to the change request - * @return The status of the change request - */ - public function changeResourceRecordSets($zoneId, $changes, $comment = '') { - // we'll strip off the leading forward slash, so we can use it as the action directly. - $zoneId = trim($zoneId, '/'); - - $data = "\n"; - $data .= '\n"; - $data .= "\n"; - - if(strlen($comment) > 0) { - $data .= ''.$comment."\n"; - } - - if(!is_array($changes)) { - $changes = array($changes); - } - - $data .= "\n"; - foreach($changes as $change) { - $data .= $change; - } - $data .= "\n"; - - $data .= "\n"; - $data .= "\n"; - - $rest = new Route53Request($this, $zoneId.'/rrset', 'POST', $data); - - $rest = $rest->getResponse(); - if($rest->error === false && !in_array($rest->code, array(200, 201, 202, 204))) { - $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); - } - if($rest->error !== false) { - $this->__triggerError('changeResourceRecordSets', $rest->error); - return false; - } - - if (!isset($rest->body)) - { - return array(); - } - - return $this->parseChangeInfo($rest->body->ChangeInfo); - } - - /** - * Retrieves information on a specified change request - * - * @param string changeId The id of the change, as returned by CreateHostedZoneResponse or ChangeResourceRecordSets - * In other words, if CreateHostedZoneResponse showed the change's Id as '/change/C2682N5HXP0BZ4', - * then that full value should be passed here, including the '/change/' prefix. - * @return The status of the change request - */ - public function getChange($changeId) { - // we'll strip off the leading forward slash, so we can use it as the action directly. - $zoneId = trim($changeId, '/'); - - $rest = new Route53Request($this, $changeId, 'GET'); - - $rest = $rest->getResponse(); - if($rest->error === false && $rest->code !== 200) { - $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); - } - if($rest->error !== false) { - $this->__triggerError('getChange', $rest->error); - return false; - } - - if (!isset($rest->body)) - { - return array(); - } - - return $this->parseChangeInfo($rest->body->ChangeInfo); - } - - - - /** - * Utility function to parse a HostedZone tag structure - */ - private function parseHostedZone($tag) { - $zone = array(); - $zone['Id'] = (string)$tag->Id; - $zone['Name'] = (string)$tag->Name; - $zone['CallerReference'] = (string)$tag->CallerReference; - - // these might always be set, but check just in case, since - // their values are option on CreateHostedZone requests - if(isset($tag->Config) && isset($tag->Config->Comment)) { - $zone['Config'] = array('Comment' => (string)$tag->Config->Comment); - } - - return $zone; - } - - /** - * Utility function to parse a ChangeInfo tag structure - */ - private function parseChangeInfo($tag) { - $info = array(); - $info['Id'] = (string)$tag->Id; - $info['Status'] = (string)$tag->Status; - $info['SubmittedAt'] = (string)$tag->SubmittedAt; - return $info; - } - - /** - * Utility function to parse a DelegationSet tag structure - */ - private function parseDelegationSet($tag) { - $servers = array(); - foreach($tag->NameServers->NameServer as $ns) { - $servers[] = (string)$ns; - } - return $servers; - } - - /** - * Utility function to parse a ResourceRecordSet tag structure - */ - private function parseResourceRecordSet($tag) { - $rrs = array(); - $rrs['Name'] = (string)$tag->Name; - $rrs['Type'] = (string)$tag->Type; - $rrs['TTL'] = (string)$tag->TTL; - $rrs['ResourceRecords'] = array(); - foreach($tag->ResourceRecords->ResourceRecord as $rr) { - $rrs['ResourceRecords'][] = (string)$rr->Value; - } - return $rrs; - } - - /** - * Utility function to prepare a Change object for ChangeResourceRecordSets requests. - * All fields are required. - * - * @param string action The action to perform. One of: CREATE, DELETE - * @param string name The name to perform the action on. - * If it does not end with '.', then AWS treats the name as relative to the zone root. - * @param string type The type of record being modified. - * Must be one of: A, AAAA, CNAME, MX, NS, PTR, SOA, SPF, SRV, TXT - * @param int ttl The time-to-live value for this record, in seconds. - * @param array records An array of resource records to attach to this change. - * Each member of this array can either be a string, or an array of strings. - * Passing an array of strings will attach multiple values to a single resource record. - * If a single string is passed as $records instead of an array, - * it will be treated as a single-member array. - * @return object An opaque object containing the change request. - * Do not write code that depends on the contents of this object, as it may change at any time. - */ - public function prepareChange($action, $name, $type, $ttl, $records) { - $change = "\n"; - $change .= ''.$action."\n"; - $change .= "\n"; - $change .= ''.$name."\n"; - $change .= ''.$type."\n"; - $change .= ''.$ttl."\n"; - $change .= "\n"; - - if(!is_array($records)) { - $records = array($records); - } - - foreach($records as $record) { - $change .= "\n"; - if(is_array($record)) { - foreach($record as $value) { - $change .= ''.$value."\n"; - } - } - else { - $change .= ''.$record."\n"; - } - $change .= "\n"; - } - - $change .= "\n"; - $change .= "\n"; - $change .= "\n"; - - return $change; - } - - /** - * Trigger an error message - * - * @internal Used by member functions to output errors - * @param array $error Array containing error information - * @return string - */ - public function __triggerError($functionname, $error) - { - if($error == false) { - log_error(sprintf("Route53::%s(): Encountered an error, but no description given", $functionname)); - } - else if(isset($error['curl']) && $error['curl']) - { - log_error(sprintf("Route53::%s(): %s %s", $functionname, $error['code'], $error['message'])); - } - else if(isset($error['Error'])) - { - $e = $error['Error']; - $message = sprintf("Route53::%s(): %s - %s: %s\nRequest Id: %s\n", $functionname, $e['Type'], $e['Code'], $e['Message'], $error['RequestId']); - log_error($message); - } - } - - /** - * Callback handler for 503 retries. - * - * @internal Used by SimpleDBRequest to call the user-specified callback, if set - * @param $attempt The number of failed attempts so far - * @return The retry delay in microseconds, or 0 to stop retrying. - */ - public function __executeServiceTemporarilyUnavailableRetryDelay($attempt) - { - if(is_callable($this->__serviceUnavailableRetryDelayCallback)) { - $callback = $this->__serviceUnavailableRetryDelayCallback; - return $callback($attempt); - } - return 0; - } -} - -final class Route53Request -{ - private $r53, $action, $verb, $data, $parameters = array(); - public $response; - - /** - * Constructor - * - * @param string $r53 The Route53 object making this request - * @param string $action SimpleDB action - * @param string $verb HTTP verb - * @param string $data For POST requests, the data being posted (optional) - * @return mixed - */ - function __construct($r53, $action, $verb, $data = '') { - $this->r53 = $r53; - $this->action = $action; - $this->verb = $verb; - $this->data = $data; - $this->response = new STDClass; - $this->response->error = false; - } - - /** - * Set request parameter - * - * @param string $key Key - * @param string $value Value - * @param boolean $replace Whether to replace the key if it already exists (default true) - * @return void - */ - public function setParameter($key, $value, $replace = true) { - if(!$replace && isset($this->parameters[$key])) - { - $temp = (array)($this->parameters[$key]); - $temp[] = $value; - $this->parameters[$key] = $temp; - } - else - { - $this->parameters[$key] = $value; - } - } - - /** - * Get the response - * - * @return object | false - */ - public function getResponse() { - - $params = array(); - foreach ($this->parameters as $var => $value) - { - if(is_array($value)) - { - foreach($value as $v) - { - $params[] = $var.'='.$this->__customUrlEncode($v); - } - } - else - { - $params[] = $var.'='.$this->__customUrlEncode($value); - } - } - - sort($params, SORT_STRING); - - $query = implode('&', $params); - - // must be in format 'Sun, 06 Nov 1994 08:49:37 GMT' - $date = gmdate('D, d M Y H:i:s e'); - - $headers = array(); - $headers[] = 'Date: '.$date; - $headers[] = 'Host: '.$this->r53->getHost(); - - $auth = 'AWS3-HTTPS AWSAccessKeyId='.$this->r53->getAccessKey(); - $auth .= ',Algorithm=HmacSHA256,Signature='.$this->__getSignature($date); - $headers[] = 'X-Amzn-Authorization: '.$auth; - - $url = 'https://'.$this->r53->getHost().'/'.Route53::API_VERSION.'/'.$this->action.'?'.$query; - - // Basic setup - $curl = curl_init(); - curl_setopt($curl, CURLOPT_USERAGENT, 'Route53/php'); - - curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, ($this->r53->verifyHost() ? 1 : 0)); - curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, ($this->r53->verifyPeer() ? 1 : 0)); - - curl_setopt($curl, CURLOPT_URL, $url); - curl_setopt($curl, CURLOPT_RETURNTRANSFER, false); - curl_setopt($curl, CURLOPT_WRITEFUNCTION, array(&$this, '__responseWriteCallback')); - curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); - - // Request types - switch ($this->verb) { - case 'GET': break; - case 'POST': - curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $this->verb); - if(strlen($this->data) > 0) { - curl_setopt($curl, CURLOPT_POSTFIELDS, $this->data); - $headers[] = 'Content-Type: text/plain'; - $headers[] = 'Content-Length: '.strlen($this->data); - } - break; - case 'DELETE': - curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'DELETE'); - break; - default: break; - } - curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); - curl_setopt($curl, CURLOPT_HEADER, false); - - // Execute, grab errors - if (curl_exec($curl)) { - $this->response->code = curl_getinfo($curl, CURLINFO_HTTP_CODE); - } else { - $this->response->error = array( - 'curl' => true, - 'code' => curl_errno($curl), - 'message' => curl_error($curl), - 'resource' => $this->resource - ); - } - - @curl_close($curl); - - // Parse body into XML - if ($this->response->error === false && isset($this->response->body)) { - $this->response->body = simplexml_load_string($this->response->body); - - // Grab Route53 errors - if (!in_array($this->response->code, array(200, 201, 202, 204)) - && isset($this->response->body->Error)) { - $error = $this->response->body->Error; - $output = array(); - $output['curl'] = false; - $output['Error'] = array(); - $output['Error']['Type'] = (string)$error->Type; - $output['Error']['Code'] = (string)$error->Code; - $output['Error']['Message'] = (string)$error->Message; - $output['RequestId'] = (string)$this->response->body->RequestId; - - $this->response->error = $output; - unset($this->response->body); - } - } - - return $this->response; - } - - /** - * CURL write callback - * - * @param resource &$curl CURL resource - * @param string &$data Data - * @return integer - */ - private function __responseWriteCallback(&$curl, &$data) { - $this->response->body .= $data; - return strlen($data); - } - - /** - * Contributed by afx114 - * URL encode the parameters as per http://docs.amazonwebservices.com/AWSECommerceService/latest/DG/index.html?Query_QueryAuth.html - * PHP's rawurlencode() follows RFC 1738, not RFC 3986 as required by Amazon. The only difference is the tilde (~), so convert it back after rawurlencode - * See: http://www.morganney.com/blog/API/AWS-Product-Advertising-API-Requires-a-Signed-Request.php - * - * @param string $var String to encode - * @return string - */ - private function __customUrlEncode($var) { - return str_replace('%7E', '~', rawurlencode($var)); - } - - /** - * Generate the auth string using Hmac-SHA256 - * - * @internal Used by SimpleDBRequest::getResponse() - * @param string $string String to sign - * @return string - */ - private function __getSignature($string) { - return base64_encode(hash_hmac('sha256', $string, $this->r53->getSecretKey(), true)); - } -} diff --git a/dns/dyndns/src/etc/rc.dyndns b/dns/dyndns/src/etc/rc.dyndns deleted file mode 100755 index 5bdc013535..0000000000 --- a/dns/dyndns/src/etc/rc.dyndns +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/local/bin/php - - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - * AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, - * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -require_once("config.inc"); -require_once("interfaces.inc"); -require_once("util.inc"); -require_once("filter.inc"); -require_once("plugins.inc.d/dyndns.inc"); - -if (isset($argv[1])) { - $argument = trim($argv[1], " \n"); -} else { - $argument = null; -} - -if (empty($argument)) { - dyndns_configure_do(true); -} else { - $interface = (new \OPNsense\Routing\Gateways(legacy_interfaces_details()))->getInterfaceName($argument); - if (empty($interface)) { - $interface = $argument; - } - dyndns_configure_do(true, $interface); -} diff --git a/dns/dyndns/src/etc/rc.syshook.d/monitor/50-dyndns b/dns/dyndns/src/etc/rc.syshook.d/monitor/50-dyndns deleted file mode 100755 index 77511becc1..0000000000 --- a/dns/dyndns/src/etc/rc.syshook.d/monitor/50-dyndns +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/sh - -# Copyright (c) 2019 Franco Fichtner -# -# 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. - -GATEWAY="${1}" - -if [ -z "${GATEWAY}" ]; then - # require a gateway - exit 1 -fi - -echo -n "Reloading DynDNS for ${GATEWAY}: " -/usr/local/opnsense/service/configd_ctl.py dyndns reload ${GATEWAY} - -exit 0 diff --git a/dns/dyndns/src/opnsense/mvc/app/models/OPNsense/DynamicDNS/ACL/ACL.xml b/dns/dyndns/src/opnsense/mvc/app/models/OPNsense/DynamicDNS/ACL/ACL.xml deleted file mode 100644 index 108e7e6537..0000000000 --- a/dns/dyndns/src/opnsense/mvc/app/models/OPNsense/DynamicDNS/ACL/ACL.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - Services: Dynamic DNS clients - - services_dyndns.php* - services_dyndns_edit.php* - - - diff --git a/dns/dyndns/src/opnsense/mvc/app/models/OPNsense/DynamicDNS/Menu/Menu.xml b/dns/dyndns/src/opnsense/mvc/app/models/OPNsense/DynamicDNS/Menu/Menu.xml deleted file mode 100644 index 33f6a448cd..0000000000 --- a/dns/dyndns/src/opnsense/mvc/app/models/OPNsense/DynamicDNS/Menu/Menu.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/dns/dyndns/src/opnsense/service/conf/actions.d/actions_dyndns.conf b/dns/dyndns/src/opnsense/service/conf/actions.d/actions_dyndns.conf deleted file mode 100644 index 294b7da846..0000000000 --- a/dns/dyndns/src/opnsense/service/conf/actions.d/actions_dyndns.conf +++ /dev/null @@ -1,6 +0,0 @@ -[reload] -command:/usr/local/etc/rc.dyndns -description:Dynamic DNS Update -parameters:%s -type:script -message:updating dyndns %s diff --git a/dns/dyndns/src/www/services_dyndns.php b/dns/dyndns/src/www/services_dyndns.php deleted file mode 100644 index 03bddbfcb6..0000000000 --- a/dns/dyndns/src/www/services_dyndns.php +++ /dev/null @@ -1,198 +0,0 @@ - gettext('Add'), 'href' => 'services_dyndns_edit.php'), -); - -?> - - - - -
-
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - -
- "> - "> - - - -%s', - $ipaddr != $cached_ip ? 'red' : 'green', - htmlspecialchars($cached_ip) - ); - } elseif (!empty($fdata6)) { - $cached_ipv6_s = explode('|', $fdata6); - $cached_ipv6 = $cached_ipv6_s[0]; - echo sprintf( - '%s', - $ipv6addr != $cached_ipv6 ? 'red' : 'green', - htmlspecialchars($cached_ipv6) - ); - } else { - echo sprintf('%s', gettext('N/A')); - }?> - - - -
-
-
-
-
-
-
-
- diff --git a/dns/dyndns/src/www/services_dyndns_edit.php b/dns/dyndns/src/www/services_dyndns_edit.php deleted file mode 100644 index df29a8eae6..0000000000 --- a/dns/dyndns/src/www/services_dyndns_edit.php +++ /dev/null @@ -1,460 +0,0 @@ - - - - - -
-
-
- 0) print_input_errors($input_errors); ?> -
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
- " /> -
- -
- -
- - -
- - -
- - -
- /> - -
- /> - -
- /> -
- /> - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- -
  - - - - - - -
- ', ''); ?> -
-
-
-
-
-
-
-
-%s', - $ipaddr != $cached_ip ? 'text-danger' : '', - htmlspecialchars($cached_ip) - ); - } elseif (!empty($fdata6)) { - $cached_ipv6_s = explode('|', $fdata6); - $cached_ipv6 = $cached_ipv6_s[0]; - echo sprintf( - '%s', - $ipv6addr != $cached_ipv6 ? 'text-danger' : '', - htmlspecialchars($cached_ipv6) - ); - } else { - echo gettext('N/A'); - } - } - exit; -} - -?> - - - - - - - - - - - - $dyndns) :?> - - - - - - - - -
> - $ifdesc) { - if ($dyndns['interface'] == $if) { - echo "{$ifdesc}"; - break; - } - } - foreach ($groupslist as $if => $group) { - if ($dyndns['interface'] == $if) { - echo "{$if}"; - break; - } - }?> - > - - > - - > -
- -
-
- diff --git a/dns/rfc2136/Makefile b/dns/rfc2136/Makefile index 39ebb08552..d589ee31a2 100644 --- a/dns/rfc2136/Makefile +++ b/dns/rfc2136/Makefile @@ -1,5 +1,6 @@ PLUGIN_NAME= rfc2136 -PLUGIN_VERSION= 1.6 +PLUGIN_VERSION= 1.9 +PLUGIN_REVISION= 5 PLUGIN_COMMENT= RFC-2136 Support PLUGIN_MAINTAINER= franco@opnsense.org PLUGIN_DEPENDS= bind-tools diff --git a/dns/rfc2136/pkg-descr b/dns/rfc2136/pkg-descr index defdef6d05..5ef614b97c 100644 --- a/dns/rfc2136/pkg-descr +++ b/dns/rfc2136/pkg-descr @@ -1 +1,8 @@ Support for RFC-2136 based dynamic DNS updates using Bind + +Plugin Changelog +================ + +1.8 + +* Add support for choosing hash algorithm used by nsupdate command diff --git a/dns/rfc2136/src/etc/inc/plugins.inc.d/rfc2136.inc b/dns/rfc2136/src/etc/inc/plugins.inc.d/rfc2136.inc index 8663b5f250..2f36beac2c 100644 --- a/dns/rfc2136/src/etc/inc/plugins.inc.d/rfc2136.inc +++ b/dns/rfc2136/src/etc/inc/plugins.inc.d/rfc2136.inc @@ -1,41 +1,41 @@ - Copyright (C) 2010 Ermal Luçi - Copyright (C) 2005-2006 Colin Smith - Copyright (C) 2003-2004 Manuel Kasper - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, - INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY - AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, - OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - POSSIBILITY OF SUCH DAMAGE. -*/ + * Copyright (C) 2014-2017 Franco Fichtner + * Copyright (C) 2010 Ermal Luçi + * Copyright (C) 2005-2006 Colin Smith + * Copyright (C) 2003-2004 Manuel Kasper + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ function rfc2136_configure() { - return array( - 'bootup' => array('rfc2136_configure_do'), - 'local' => array('rfc2136_configure_do'), - 'newwanip' => array('rfc2136_configure_do:2'), - ); + return [ + 'bootup' => ['rfc2136_configure_do'], + 'local' => ['rfc2136_configure_do'], + 'newwanip' => ['rfc2136_configure_do:2'], + ]; } function rfc2136_enabled() @@ -55,19 +55,15 @@ function rfc2136_enabled() function rfc2136_services() { - global $config; - - $services = array(); + $services = []; if (rfc2136_enabled()) { - $services[] = array( + $services[] = [ 'description' => gettext('RFC 2136'), - 'configd' => array( - 'restart' => array('rfc2136 reload'), - ), + 'configd' => [ 'restart' => ['rfc2136 reload'] ], 'nocheck' => true, 'name' => 'rfc2136', - ); + ]; } return $services; @@ -75,10 +71,10 @@ function rfc2136_services() function rfc2136_cron() { - $jobs = array(); + $jobs = []; if (rfc2136_enabled()) { - $jobs[]['autocron'] = array('/usr/local/etc/rc.rfc2136', '16', '1'); + $jobs[]['autocron'] = ['/usr/local/etc/rc.rfc2136', '16', '1']; } return $jobs; @@ -91,23 +87,20 @@ function rfc2136_cache_file($dnsupdate, $ipver = 4) return "/var/cache/rfc2136_{$dnsupdate['interface']}_{$dnsupdate['host']}_{$dnsupdate['server']}{$ipver}.cache"; } -function rfc2136_configure_do($verbose = false, $int = '', $updatehost = '', $forced = false) +function rfc2136_configure_do($verbose = false, $int = null, $updatehost = '', $forced = false) { global $config; - if (!rfc2136_enabled()) { + if (!rfc2136_enabled() || !plugins_argument_map($int)) { return; } - if ($verbose) { - echo 'Configuring RFC 2136 clients...'; - flush(); - } + service_log('Configuring RFC 2136 clients...', $verbose); foreach ($config['dnsupdates']['dnsupdate'] as $i => $dnsupdate) { if (!isset($dnsupdate['enable'])) { continue; - } elseif (!empty($int) && $int != $dnsupdate['interface']) { + } elseif (!empty($int) && !in_array($dnsupdate['interface'], $int)) { continue; } elseif (!empty($updatehost) && ($updatehost != $dnsupdate['host'])) { continue; @@ -127,34 +120,17 @@ function rfc2136_configure_do($verbose = false, $int = '', $updatehost = '', $fo $hostname .= "."; } - /* write private key file - this is dumb - public and private keys are the same for HMAC-MD5, - but nsupdate insists on having both */ - $fd = fopen("/var/etc/K{$i}{$keyname}+157+00000.private", "w"); - $privkey = << $maxCacheAgeSecs) || $forced) { @@ -197,12 +173,12 @@ EOD; if (file_exists($cacheFile6)) { list($cachedipv6, $cacheTimev6) = explode('|', file_get_contents($cacheFile6)); } else { - list($cachedipv6, $cacheTimev6) = array('', ''); + list($cachedipv6, $cacheTimev6) = ['', '']; } if (isset($dnsupdate['usepublicip'])) { - $wanipv6 = get_dyndns_ip($dnsupdate['interface'], 6); + $wanipv6 = get_rfc2136_ip_address($dnsupdate['interface'], 6); } else { - $wanipv6 = get_interface_ipv6($dnsupdate['interface']); + list ($wanipv6) = interfaces_primary_address6($dnsupdate['interface']); } if (is_ipaddrv6($wanipv6)) { if (($wanipv6 != $cachedipv6) || (($currentTime - $cacheTimev6) > $maxCacheAgeSecs) || $forced) { @@ -222,20 +198,56 @@ EOD; $upinst .= "\n"; /* mind that trailing newline! */ if ($need_update) { - @file_put_contents("/var/etc/nsupdatecmds{$i}", $upinst); - unset($upinst); - /* invoke nsupdate */ - $cmd = "/usr/local/bin/nsupdate -k /var/etc/K{$i}{$keyname}+157+00000.key"; + file_safe("/var/etc/nsupdatecmds{$i}", $upinst); + + $frmt = ['/usr/local/bin/nsupdate -k %s']; + $args = [$keyfile]; + if (isset($dnsupdate['usetcp'])) { - $cmd .= " -v"; + $frmt[] = '-v'; } - $cmd .= " /var/etc/nsupdatecmds{$i}"; - mwexec_bg($cmd); - unset($cmd); + + $args[] = "/var/etc/nsupdatecmds{$i}"; + $frmt[] = '%s'; + + mwexecfb($frmt, $args); } } - if ($verbose) { - echo "done.\n"; + service_log("done.\n", $verbose); +} + +function get_rfc2136_ip_address($int, $ipver = 4) +{ + list ($ip_address) = $ipver == 6 ? interfaces_primary_address6($int) : interfaces_primary_address($int); + if (empty($ip_address)) { + log_error("Aborted IPv{$ipver} detection: no address for {$int}"); + return 'down'; + } + + if ($ipver != 6 && is_private_ipv4($ip_address)) { + $ip_ch = curl_init('http://checkip.dyndns.org'); + curl_setopt($ip_ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ip_ch, CURLOPT_INTERFACE, $ip_address); + curl_setopt($ip_ch, CURLOPT_TIMEOUT, 30); + curl_setopt($ip_ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); + $ip_result = curl_exec($ip_ch); + if ($ip_result !== false) { + preg_match('=Current IP Address: (.*)=siU', $ip_result, $matches); + $ip_address = trim($matches[1]); + } else { + log_error('Aborted IPv4 detection: ' . curl_error($ip_ch)); + $ip_address = ''; + } + curl_close($ip_ch); + } elseif ($ipver == 6 && is_linklocal($ip_address)) { + log_error('Aborted IPv6 detection: cannot bind to link-local address'); + $ip_address = ''; + } + + if (($ipver == 6 && !is_ipaddrv6($ip_address)) || ($ipver != 6 && !is_ipaddrv4($ip_address))) { + return 'down'; } + + return $ip_address; } diff --git a/dns/rfc2136/src/www/services_rfc2136.php b/dns/rfc2136/src/www/services_rfc2136.php index 5f7aaee8a1..6acf4e7610 100644 --- a/dns/rfc2136/src/www/services_rfc2136.php +++ b/dns/rfc2136/src/www/services_rfc2136.php @@ -54,7 +54,7 @@ write_config(); system_cron_configure(); if (!empty($a_rfc2136[$_POST['id']]['enable'])) { - rfc2136_configure_do(false, '', $a_rfc2136[$_POST['id']]['host'], true); + rfc2136_configure_do(false, null, $a_rfc2136[$_POST['id']]['host'], true); } } exit; @@ -65,10 +65,6 @@ legacy_html_escape_form_data($a_rfc2136); -$main_buttons = array( - array('label' => gettext('Add'), 'href' => 'services_rfc2136_edit.php'), -); - ?> diff --git a/dns/unbound-plus/Makefile b/dns/unbound-plus/Makefile deleted file mode 100644 index 1d12ddf601..0000000000 --- a/dns/unbound-plus/Makefile +++ /dev/null @@ -1,7 +0,0 @@ -PLUGIN_NAME= unbound-plus -PLUGIN_VERSION= 0.5 -PLUGIN_COMMENT= Unbound additions -PLUGIN_MAINTAINER= m.muenz@gmail.com -PLUGIN_DEVEL= yes - -.include "../../Mk/plugins.mk" diff --git a/dns/unbound-plus/pkg-descr b/dns/unbound-plus/pkg-descr deleted file mode 100644 index a93c1a94c0..0000000000 --- a/dns/unbound-plus/pkg-descr +++ /dev/null @@ -1,4 +0,0 @@ -Unbound-Plus is a collecion of additional features to Unbound, -including DNSBL and DNS-over-TLS support. - -WWW: https://github.com/opnsense/plugins/ diff --git a/dns/unbound-plus/src/opnsense/mvc/app/controllers/OPNsense/Unboundplus/Api/DnsblController.php b/dns/unbound-plus/src/opnsense/mvc/app/controllers/OPNsense/Unboundplus/Api/DnsblController.php deleted file mode 100644 index e729939f6c..0000000000 --- a/dns/unbound-plus/src/opnsense/mvc/app/controllers/OPNsense/Unboundplus/Api/DnsblController.php +++ /dev/null @@ -1,37 +0,0 @@ - - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - * AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, - * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -namespace OPNsense\Unboundplus\Api; - -use OPNsense\Base\ApiMutableModelControllerBase; - -class DnsblController extends ApiMutableModelControllerBase -{ - protected static $internalModelClass = '\OPNsense\Unboundplus\Dnsbl'; - protected static $internalModelName = 'dnsbl'; -} diff --git a/dns/unbound-plus/src/opnsense/mvc/app/controllers/OPNsense/Unboundplus/Api/MiscellaneousController.php b/dns/unbound-plus/src/opnsense/mvc/app/controllers/OPNsense/Unboundplus/Api/MiscellaneousController.php deleted file mode 100644 index 5e98788200..0000000000 --- a/dns/unbound-plus/src/opnsense/mvc/app/controllers/OPNsense/Unboundplus/Api/MiscellaneousController.php +++ /dev/null @@ -1,37 +0,0 @@ - - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - * AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, - * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -namespace OPNsense\Unboundplus\Api; - -use OPNsense\Base\ApiMutableModelControllerBase; - -class MiscellaneousController extends ApiMutableModelControllerBase -{ - protected static $internalModelClass = '\OPNsense\Unboundplus\Miscellaneous'; - protected static $internalModelName = 'miscellaneous'; -} diff --git a/dns/unbound-plus/src/opnsense/mvc/app/controllers/OPNsense/Unboundplus/Api/ServiceController.php b/dns/unbound-plus/src/opnsense/mvc/app/controllers/OPNsense/Unboundplus/Api/ServiceController.php deleted file mode 100644 index 66fa748579..0000000000 --- a/dns/unbound-plus/src/opnsense/mvc/app/controllers/OPNsense/Unboundplus/Api/ServiceController.php +++ /dev/null @@ -1,62 +0,0 @@ - - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - * AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, - * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -namespace OPNsense\Unboundplus\Api; - -use OPNsense\Base\ApiMutableServiceControllerBase; -use OPNsense\Core\Backend; -use OPNsense\Unboundplus\Dnsbl; -use OPNsense\Unboundplus\Miscellaneous; - -class ServiceController extends ApiMutableServiceControllerBase -{ - protected static $internalServiceClass = '\OPNsense\Unboundplus\Dnsbl'; - protected static $internalServiceTemplate = 'OPNsense/Unboundplus'; - protected static $internalServiceEnabled = 'enabled'; - protected static $internalServiceName = 'unboundplus'; - - public function dnsblAction() - { - $this->sessionClose(); - $mdl = new Dnsbl(); - $backend = new Backend(); - $backend->configdRun('template reload OPNsense/Unboundplus'); - $response = $backend->configdpRun('unboundplus dnsbl', array((string)$mdl->type)); - return array("response" => $response); - } - - public function reloadunboundAction() - { - $this->sessionClose(); - $mdl = new Miscellaneous(); - $backend = new Backend(); - $backend->configdRun('template reload OPNsense/Unboundplus'); - $response = $backend->configdpRun('unbound reload', array((string)$mdl->type)); - return array("response" => $response); - } -} diff --git a/dns/unbound-plus/src/opnsense/mvc/app/controllers/OPNsense/Unboundplus/DnsblController.php b/dns/unbound-plus/src/opnsense/mvc/app/controllers/OPNsense/Unboundplus/DnsblController.php deleted file mode 100644 index 882e2aae9a..0000000000 --- a/dns/unbound-plus/src/opnsense/mvc/app/controllers/OPNsense/Unboundplus/DnsblController.php +++ /dev/null @@ -1,38 +0,0 @@ - - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - * AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, - * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -namespace OPNsense\Unboundplus; - -class DnsblController extends \OPNsense\Base\IndexController -{ - public function indexAction() - { - $this->view->dnsblForm = $this->getForm('dnsbl'); - $this->view->pick('OPNsense/Unboundplus/dnsbl'); - } -} diff --git a/dns/unbound-plus/src/opnsense/mvc/app/controllers/OPNsense/Unboundplus/MiscellaneousController.php b/dns/unbound-plus/src/opnsense/mvc/app/controllers/OPNsense/Unboundplus/MiscellaneousController.php deleted file mode 100644 index 25bdb9e638..0000000000 --- a/dns/unbound-plus/src/opnsense/mvc/app/controllers/OPNsense/Unboundplus/MiscellaneousController.php +++ /dev/null @@ -1,38 +0,0 @@ - - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - * AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, - * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -namespace OPNsense\Unboundplus; - -class MiscellaneousController extends \OPNsense\Base\IndexController -{ - public function indexAction() - { - $this->view->miscellaneousForm = $this->getForm('miscellaneous'); - $this->view->pick('OPNsense/Unboundplus/miscellaneous'); - } -} diff --git a/dns/unbound-plus/src/opnsense/mvc/app/controllers/OPNsense/Unboundplus/forms/dnsbl.xml b/dns/unbound-plus/src/opnsense/mvc/app/controllers/OPNsense/Unboundplus/forms/dnsbl.xml deleted file mode 100644 index 6fd6fc6077..0000000000 --- a/dns/unbound-plus/src/opnsense/mvc/app/controllers/OPNsense/Unboundplus/forms/dnsbl.xml +++ /dev/null @@ -1,30 +0,0 @@ -
- - dnsbl.enabled - - checkbox - Enable the usage of DNS blocklists. - - - dnsbl.type - - select_multiple - Select which kind of DNSBL you want to use. - - - dnsbl.lists - - select_multiple - - true - List of domains from where blacklist will be downloaded. - - - dnsbl.whitelists - - select_multiple - - true - List of domains to whitelist. You can use regular expressions. - -
diff --git a/dns/unbound-plus/src/opnsense/mvc/app/controllers/OPNsense/Unboundplus/forms/miscellaneous.xml b/dns/unbound-plus/src/opnsense/mvc/app/controllers/OPNsense/Unboundplus/forms/miscellaneous.xml deleted file mode 100644 index 3945c34051..0000000000 --- a/dns/unbound-plus/src/opnsense/mvc/app/controllers/OPNsense/Unboundplus/forms/miscellaneous.xml +++ /dev/null @@ -1,10 +0,0 @@ -
- - miscellaneous.privatedomain - - select_multiple - - true - List of domains to mark as private. You only need this for some DNSBL lists which resolve to private addresses. - -
diff --git a/dns/unbound-plus/src/opnsense/mvc/app/models/OPNsense/Unboundplus/ACL/ACL.xml b/dns/unbound-plus/src/opnsense/mvc/app/models/OPNsense/Unboundplus/ACL/ACL.xml deleted file mode 100644 index 181f635fd8..0000000000 --- a/dns/unbound-plus/src/opnsense/mvc/app/models/OPNsense/Unboundplus/ACL/ACL.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - Services: Unbound DNSBL - - ui/unboundplus/* - api/unboundplus/* - - - diff --git a/dns/unbound-plus/src/opnsense/mvc/app/models/OPNsense/Unboundplus/Dnsbl.php b/dns/unbound-plus/src/opnsense/mvc/app/models/OPNsense/Unboundplus/Dnsbl.php deleted file mode 100644 index b8bd973ef6..0000000000 --- a/dns/unbound-plus/src/opnsense/mvc/app/models/OPNsense/Unboundplus/Dnsbl.php +++ /dev/null @@ -1,35 +0,0 @@ - - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - * AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, - * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -namespace OPNsense\Unboundplus; - -use OPNsense\Base\BaseModel; - -class Dnsbl extends BaseModel -{ -} diff --git a/dns/unbound-plus/src/opnsense/mvc/app/models/OPNsense/Unboundplus/Dnsbl.xml b/dns/unbound-plus/src/opnsense/mvc/app/models/OPNsense/Unboundplus/Dnsbl.xml deleted file mode 100644 index 5b537c08fd..0000000000 --- a/dns/unbound-plus/src/opnsense/mvc/app/models/OPNsense/Unboundplus/Dnsbl.xml +++ /dev/null @@ -1,47 +0,0 @@ - - //OPNsense/unboundplus/dnsbl - Unbound DNSBL configuration - 0.0.1 - - - 0 - Y - - - N - Y - - AdAway List - AdGuard List - Blocklist.site Ads - Blocklist.site Fraud - Blocklist.site Phishing - Cameleon List - Easy List - EMD Malicious Domains List - Easyprivacy List - hpHosts Ads - hpHosts FSA - hpHosts PSH - hpHosts PUP - Malwaredomain List - NoCoin List - PornTop1M List - Ransomware Tracker List - Simple Ad List - Simple Tracker List - Steven Black List - WindowsSpyBlocker (spy) - WindowsSpyBlocker (update) - WindowsSpyBlocker (extra) - YoYo List - - - - N - - - N - - - diff --git a/dns/unbound-plus/src/opnsense/mvc/app/models/OPNsense/Unboundplus/Menu/Menu.xml b/dns/unbound-plus/src/opnsense/mvc/app/models/OPNsense/Unboundplus/Menu/Menu.xml deleted file mode 100644 index 32261d1a24..0000000000 --- a/dns/unbound-plus/src/opnsense/mvc/app/models/OPNsense/Unboundplus/Menu/Menu.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/dns/unbound-plus/src/opnsense/mvc/app/models/OPNsense/Unboundplus/Miscellaneous.php b/dns/unbound-plus/src/opnsense/mvc/app/models/OPNsense/Unboundplus/Miscellaneous.php deleted file mode 100644 index 9cc112378e..0000000000 --- a/dns/unbound-plus/src/opnsense/mvc/app/models/OPNsense/Unboundplus/Miscellaneous.php +++ /dev/null @@ -1,35 +0,0 @@ - - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - * AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, - * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -namespace OPNsense\Unboundplus; - -use OPNsense\Base\BaseModel; - -class Miscellaneous extends BaseModel -{ -} diff --git a/dns/unbound-plus/src/opnsense/mvc/app/models/OPNsense/Unboundplus/Miscellaneous.xml b/dns/unbound-plus/src/opnsense/mvc/app/models/OPNsense/Unboundplus/Miscellaneous.xml deleted file mode 100644 index 5a3b3e8583..0000000000 --- a/dns/unbound-plus/src/opnsense/mvc/app/models/OPNsense/Unboundplus/Miscellaneous.xml +++ /dev/null @@ -1,10 +0,0 @@ - - //OPNsense/unboundplus/miscellaneous - Unbound Miscellaneous configuration - 0.0.1 - - - N - - - diff --git a/dns/unbound-plus/src/opnsense/mvc/app/views/OPNsense/Unboundplus/dnsbl.volt b/dns/unbound-plus/src/opnsense/mvc/app/views/OPNsense/Unboundplus/dnsbl.volt deleted file mode 100644 index eb53cf0e64..0000000000 --- a/dns/unbound-plus/src/opnsense/mvc/app/views/OPNsense/Unboundplus/dnsbl.volt +++ /dev/null @@ -1,54 +0,0 @@ -{# - # Copyright (c) 2019 Deciso B.V. - # Copyright (c) 2019 Michael Muenz - # All rights reserved. - # - # Redistribution and use in source and binary forms, with or without modification, - # are permitted provided that the following conditions are met: - # - # 1. Redistributions of source code must retain the above copyright notice, - # this list of conditions and the following disclaimer. - # - # 2. Redistributions in binary form must reproduce the above copyright notice, - # this list of conditions and the following disclaimer in the documentation - # and/or other materials provided with the distribution. - # - # THIS SOFTWARE IS PROVIDED “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, - # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY - # AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - # AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, - # OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - # POSSIBILITY OF SUCH DAMAGE. - #} - -
- {{ partial("layout_partials/base_form",['fields':dnsblForm,'id':'frm_dnsbl_settings'])}} -
-
- -
-
- - diff --git a/dns/unbound-plus/src/opnsense/mvc/app/views/OPNsense/Unboundplus/miscellaneous.volt b/dns/unbound-plus/src/opnsense/mvc/app/views/OPNsense/Unboundplus/miscellaneous.volt deleted file mode 100644 index dda6369f25..0000000000 --- a/dns/unbound-plus/src/opnsense/mvc/app/views/OPNsense/Unboundplus/miscellaneous.volt +++ /dev/null @@ -1,54 +0,0 @@ -{# - # Copyright (c) 2019 Deciso B.V. - # Copyright (c) 2019 Michael Muenz - # All rights reserved. - # - # Redistribution and use in source and binary forms, with or without modification, - # are permitted provided that the following conditions are met: - # - # 1. Redistributions of source code must retain the above copyright notice, - # this list of conditions and the following disclaimer. - # - # 2. Redistributions in binary form must reproduce the above copyright notice, - # this list of conditions and the following disclaimer in the documentation - # and/or other materials provided with the distribution. - # - # THIS SOFTWARE IS PROVIDED “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, - # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY - # AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - # AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, - # OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - # POSSIBILITY OF SUCH DAMAGE. - #} - -
- {{ partial("layout_partials/base_form",['fields':miscellaneousForm,'id':'frm_miscellaneous_settings'])}} -
-
- -
-
- - diff --git a/dns/unbound-plus/src/opnsense/scripts/OPNsense/Unboundplus/dnsbl.py b/dns/unbound-plus/src/opnsense/scripts/OPNsense/Unboundplus/dnsbl.py deleted file mode 100755 index 054b731491..0000000000 --- a/dns/unbound-plus/src/opnsense/scripts/OPNsense/Unboundplus/dnsbl.py +++ /dev/null @@ -1,195 +0,0 @@ -#!/usr/local/bin/python3 - -# DNS BL script -# Copyright 2020 Petr Kejval - -# Downloads blacklisted domains from user specified URLs and "compile" them into unbound.conf compatible file - -# 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. - -import re, urllib3, threading, subprocess - -re_blacklist = re.compile(r'(^127\.0\.0\.1[\s]+|^0\.0\.0\.0[\s]+)([0-9a-z_.-]+)(?:\s|$)|^([0-9a-z_.-]+)(?:\s|$)', re.I) -re_whitelist = re.compile(r'$^') # default - match nothing -blacklist = set() -urls = set() - -predefined_lists = { - "aa": "https://adaway.org/hosts.txt", - "ag": "https://justdomains.github.io/blocklists/lists/adguarddns-justdomains.txt", - "bla": "https://blocklist.site/app/dl/ads", - "blf": "https://blocklist.site/app/dl/fraud", - "blp": "https://blocklist.site/app/dl/phishing", - "ca": "http://sysctl.org/cameleon/hosts", - "el": "https://justdomains.github.io/blocklists/lists/easylist-justdomains.txt", - "ep": "https://justdomains.github.io/blocklists/lists/easyprivacy-justdomains.txt", - "emd": "https://hosts-file.net/emd.txt", - "hpa": "https://hosts-file.net/ad_servers.txt", - "hpf": "https://hosts-file.net/fsa.txt", - "hpp": "https://hosts-file.net/psh.txt", - "hup": "https://hosts-file.net/pup.txt", - "nc": "https://justdomains.github.io/blocklists/lists/nocoin-justdomains.txt", - "rw": "https://ransomwaretracker.abuse.ch/downloads/RW_DOMBL.txt", - "mw": "http://malwaredomains.lehigh.edu/files/justdomains", - "pa": "https://raw.githubusercontent.com/chadmayfield/my-pihole-blocklists/master/lists/pi_blocklist_porn_all.list", - "pt": "https://raw.githubusercontent.com/chadmayfield/pihole-blocklists/master/lists/pi_blocklist_porn_top1m.list", - "sa": "https://s3.amazonaws.com/lists.disconnect.me/simple_ad.txt", - "sb": "https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts", - "st": "https://s3.amazonaws.com/lists.disconnect.me/simple_tracking.txt", - "ws": "https://raw.githubusercontent.com/crazy-max/WindowsSpyBlocker/master/data/hosts/spy.txt", - "wsu": "https://raw.githubusercontent.com/crazy-max/WindowsSpyBlocker/master/data/hosts/update.txt", - "wse": "https://raw.githubusercontent.com/crazy-max/WindowsSpyBlocker/master/data/hosts/extra.txt", - "yy": "http://pgl.yoyo.org/adservers/serverlist.php?hostformat=hosts&mimetype=plaintext" -} - -def add_to_blacklist(domain): - """ Checks if domain is present in whitelist. If not, domain is addded to BL set. """ - match = re_whitelist.match(domain) - if not match: - blacklist.add(domain) - -def parse_line(line): - """ Checks if line matches re_blacklist. If so, tries add domain to BL set. """ - global blacklist - line = line.replace('\\t', " ") - line = line.replace('\\r', "") - match = re_blacklist.match(line) - if match: - if match.group(2) != None: - add_to_blacklist(match.group(2)) - elif match.group(3) != None: - add_to_blacklist(match.group(3)) - -def process_url(url): - """ Reads and parses blacklisted domains from URL into BL set. """ - print(f"Processing BL items from: {url}") - - try: - http = urllib3.PoolManager() - r = http.request('GET', url) - - for line in str(r.data).split('\\n'): - parse_line(line) - except Exception as e: - print(str(e)) - -def save_config_file(): - """ Saves blacklist in unbound.conf format """ - print(f"Saving {len(blacklist)} blacklisted domains into dnsbl.conf") - - try: - with open("/var/unbound/etc/dnsbl.conf", 'w') as file: - # No domains found or DNSBL is disabled - if (len(blacklist) == 0): - file.write("") - else: - file.write('server:\n') - for line in blacklist: - #file.write('local-zone: "' + str(line) + '" static\n') - file.write('local-data: "' + str(line) + ' A 0.0.0.0"\n') - except Exception as e: - print(str(e)) - exit(1) - -def load_list(path, separator=None): - """ Reads file with specified path into set to ensure unique values. - Splits lines with defined separator. If sperator==None no split is performed. """ - result = set() - - try: - with open(path, 'r') as file: - for line in file.readlines(): - if not separator == None: - for element in line.split(separator): - result.add(element.replace('\n', '')) - else: - result.add(line.replace('\n', '')) - except Exception as e: - print(str(e)) - - return result - -def load_whitelist(): - """ Loads user defined whitelist in regex format and compiles it. """ - print("Loading whitelist") - global re_whitelist - wl = load_list('/var/unbound/etc/whitelist.inc', ',') - wl.add('.*localhost$') - print(f"Loaded {len(wl)} whitelist items") - - try: - re_whitelist = re.compile('|'.join(wl), re.I) - except Exception as e: - print(f"Whitelist regex compile failed: {str(e)}") - -def load_blacklists(): - """ Loads user defined blacklists URLs. """ - print("Loading blacklists URLs") - global urls - urls = load_list('/var/unbound/etc/lists.inc', ',') - print(f"Loaded {len(urls)} blacklists URLs") - -def load_predefined_lists(): - """ Loads user chosen predefined lists """ - print("Loading predefined lists URLs") - global urls - lists = load_list('/var/unbound/etc/dnsbl.inc') - types = set() - - for first in lists: - first = str(first).split('=')[1] - first = str(first).replace('"', '').replace('\n', '') - first = first.split(',') - for type in first: - types.add(type) - break - - print(f"Loaded {len(types)} predefined blacklists URLs") - - for type in types: - try: - urls.add(predefined_lists[type]) - except KeyError: - continue - except Exception as e: - print(str(e)) - -if __name__ == "__main__": - # Prepare lists from config files - load_whitelist() - load_blacklists() - load_predefined_lists() - - # Start processing BLs in threads - threads = [threading.Thread(target=process_url, args=(url,)) for url in urls] - for t in threads: - t.start() - for t in threads: - t.join() - - save_config_file() - - print("Restarting unbound service") - subprocess.Popen(["pluginctl", "-s", "unbound", "restart"]) - exit(0) diff --git a/dns/unbound-plus/src/opnsense/service/conf/actions.d/actions_unboundplus.conf b/dns/unbound-plus/src/opnsense/service/conf/actions.d/actions_unboundplus.conf deleted file mode 100644 index e354a4e126..0000000000 --- a/dns/unbound-plus/src/opnsense/service/conf/actions.d/actions_unboundplus.conf +++ /dev/null @@ -1,6 +0,0 @@ -[dnsbl] -command:/usr/local/opnsense/scripts/OPNsense/Unboundplus/dnsbl.py -parameters: -type:script -message:fetching and applying DNSBLs -description: Download Unbound DNSBLs and restart diff --git a/dns/unbound-plus/src/opnsense/service/templates/OPNsense/Unboundplus/+TARGETS b/dns/unbound-plus/src/opnsense/service/templates/OPNsense/Unboundplus/+TARGETS deleted file mode 100644 index d7f6d3cbb7..0000000000 --- a/dns/unbound-plus/src/opnsense/service/templates/OPNsense/Unboundplus/+TARGETS +++ /dev/null @@ -1,4 +0,0 @@ -dnsbl.inc:/var/unbound/etc/dnsbl.inc -whitelist.inc:/var/unbound/etc/whitelist.inc -miscellaneous.conf:/var/unbound/etc/miscellaneous.conf -lists.inc:/var/unbound/etc/lists.inc diff --git a/dns/unbound-plus/src/opnsense/service/templates/OPNsense/Unboundplus/dnsbl.inc b/dns/unbound-plus/src/opnsense/service/templates/OPNsense/Unboundplus/dnsbl.inc deleted file mode 100644 index a47edf32fe..0000000000 --- a/dns/unbound-plus/src/opnsense/service/templates/OPNsense/Unboundplus/dnsbl.inc +++ /dev/null @@ -1,5 +0,0 @@ -{% if helpers.exists('OPNsense.unboundplus.dnsbl.enabled') and OPNsense.unboundplus.dnsbl.enabled == '1' %} -{% if helpers.exists('OPNsense.unboundplus.dnsbl.type') and OPNsense.unboundplus.dnsbl.type != '' %} -unbound_dnsbl="{{ OPNsense.unboundplus.dnsbl.type }}" -{% endif %} -{% endif %} diff --git a/dns/unbound-plus/src/opnsense/service/templates/OPNsense/Unboundplus/lists.inc b/dns/unbound-plus/src/opnsense/service/templates/OPNsense/Unboundplus/lists.inc deleted file mode 100644 index 947a527616..0000000000 --- a/dns/unbound-plus/src/opnsense/service/templates/OPNsense/Unboundplus/lists.inc +++ /dev/null @@ -1,5 +0,0 @@ -{% if helpers.exists('OPNsense.unboundplus.dnsbl.enabled') and OPNsense.unboundplus.dnsbl.enabled == '1' %} -{% if helpers.exists('OPNsense.unboundplus.dnsbl.lists') and OPNsense.unboundplus.dnsbl.lists != '' %} -{{ OPNsense.unboundplus.dnsbl.lists|default("") }} -{% endif %} -{% endif %} diff --git a/dns/unbound-plus/src/opnsense/service/templates/OPNsense/Unboundplus/miscellaneous.conf b/dns/unbound-plus/src/opnsense/service/templates/OPNsense/Unboundplus/miscellaneous.conf deleted file mode 100644 index 849bf8b100..0000000000 --- a/dns/unbound-plus/src/opnsense/service/templates/OPNsense/Unboundplus/miscellaneous.conf +++ /dev/null @@ -1,6 +0,0 @@ -{% if helpers.exists('OPNsense.unboundplus.miscellaneous.privatedomain') and OPNsense.unboundplus.miscellaneous.privatedomain != '' %} -server: -{% for privatedomain in OPNsense.unboundplus.miscellaneous.privatedomain.split(',') %} -private-domain: {{ privatedomain }} -{% endfor %} -{% endif %} diff --git a/dns/unbound-plus/src/opnsense/service/templates/OPNsense/Unboundplus/whitelist.inc b/dns/unbound-plus/src/opnsense/service/templates/OPNsense/Unboundplus/whitelist.inc deleted file mode 100644 index 925e665f7e..0000000000 --- a/dns/unbound-plus/src/opnsense/service/templates/OPNsense/Unboundplus/whitelist.inc +++ /dev/null @@ -1,5 +0,0 @@ -{% if helpers.exists('OPNsense.unboundplus.dnsbl.enabled') and OPNsense.unboundplus.dnsbl.enabled == '1' %} -{% if helpers.exists('OPNsense.unboundplus.dnsbl.whitelists') and OPNsense.unboundplus.dnsbl.whitelists != '' %} -{{ OPNsense.unboundplus.dnsbl.whitelists|default("") }} -{% endif %} -{% endif %} diff --git a/emulators/qemu-guest-agent/Makefile b/emulators/qemu-guest-agent/Makefile new file mode 100644 index 0000000000..62690e9e82 --- /dev/null +++ b/emulators/qemu-guest-agent/Makefile @@ -0,0 +1,7 @@ +PLUGIN_NAME= qemu-guest-agent +PLUGIN_VERSION= 1.3 +PLUGIN_COMMENT= QEMU Guest Agent for OPNsense +PLUGIN_DEPENDS= qemu-guest-agent +PLUGIN_MAINTAINER= opnsense@moov.de + +.include "../../Mk/plugins.mk" diff --git a/emulators/qemu-guest-agent/pkg-descr b/emulators/qemu-guest-agent/pkg-descr new file mode 100644 index 0000000000..656993fb20 --- /dev/null +++ b/emulators/qemu-guest-agent/pkg-descr @@ -0,0 +1,27 @@ +QEMU Guest Agent for FreeBSD + +Port homepage https://github.com/aborche/qemu-guest-agent +WWW: http://wiki.qemu.org/Main_Page + +Plugin Changelog +================ + +1.3 + +Fixed: +* fix disabled-rpcs break service start (#4287) +* service not working after upgrade to 24.7 (#4255) + +1.2 + +Changed: +* switch to most recent version of qemu guest agent + +1.1 + +Fixed: +* fix service startup by loading required kernel module (#2405) + +1.0 + +* Initial release diff --git a/emulators/qemu-guest-agent/src/etc/inc/plugins.inc.d/qemuguestagent.inc b/emulators/qemu-guest-agent/src/etc/inc/plugins.inc.d/qemuguestagent.inc new file mode 100644 index 0000000000..9304b1f126 --- /dev/null +++ b/emulators/qemu-guest-agent/src/etc/inc/plugins.inc.d/qemuguestagent.inc @@ -0,0 +1,71 @@ + gettext('QEMU Guest Agent'), + 'pidfile' => '/var/run/qemu-ga.pid', + 'configd' => array( + 'restart' => array('qemuguestagent restart'), + 'start' => array('qemuguestagent start'), + 'stop' => array('qemuguestagent stop'), + ), + 'name' => 'qemu-ga', + ); + + return $services; +} + +function qemuguestagent_xmlrpc_sync() +{ + $result = array(); + $result['id'] = 'qemuguestagent'; + $result['section'] = 'OPNsense.QemuGuestAgent'; + $result['description'] = gettext('QEMU Guest Agent'); + $result['services'] = ['qemu-ga']; + return array($result); +} diff --git a/emulators/qemu-guest-agent/src/etc/rc.syshook.d/early/50-qemu-guest-agent b/emulators/qemu-guest-agent/src/etc/rc.syshook.d/early/50-qemu-guest-agent new file mode 100755 index 0000000000..eb8cdd80b7 --- /dev/null +++ b/emulators/qemu-guest-agent/src/etc/rc.syshook.d/early/50-qemu-guest-agent @@ -0,0 +1,9 @@ +#!/bin/sh + +if kldstat -qn virtio_console.ko; then + echo "qemu-guest-agent: virtio_console already loaded." +elif kldload -q virtio_console.ko; then + echo "qemu-guest-agent: successfully loaded virtio_console." +else + echo "qemu-guest-agent: failed to load virtio_console." +fi diff --git a/emulators/qemu-guest-agent/src/opnsense/mvc/app/controllers/OPNsense/QemuGuestAgent/Api/ServiceController.php b/emulators/qemu-guest-agent/src/opnsense/mvc/app/controllers/OPNsense/QemuGuestAgent/Api/ServiceController.php new file mode 100644 index 0000000000..c07d9ff99f --- /dev/null +++ b/emulators/qemu-guest-agent/src/opnsense/mvc/app/controllers/OPNsense/QemuGuestAgent/Api/ServiceController.php @@ -0,0 +1,46 @@ +view->pick('OPNsense/QemuGuestAgent/index'); + // fetch form data "general" in + $this->view->generalForm = $this->getForm("general"); + } +} diff --git a/emulators/qemu-guest-agent/src/opnsense/mvc/app/controllers/OPNsense/QemuGuestAgent/forms/general.xml b/emulators/qemu-guest-agent/src/opnsense/mvc/app/controllers/OPNsense/QemuGuestAgent/forms/general.xml new file mode 100644 index 0000000000..86ff2811df --- /dev/null +++ b/emulators/qemu-guest-agent/src/opnsense/mvc/app/controllers/OPNsense/QemuGuestAgent/forms/general.xml @@ -0,0 +1,23 @@ +
+ + qemuguestagent.general.Enabled + + checkbox + Enable the QEMU guest agent service. + + + qemuguestagent.general.LogDebug + + checkbox + Log extra debugging information. + + + qemuguestagent.general.DisabledRPCs + + select_multiple + + true + true + A list of RPCs to disable. + +
diff --git a/emulators/qemu-guest-agent/src/opnsense/mvc/app/models/OPNsense/QemuGuestAgent/ACL/ACL.xml b/emulators/qemu-guest-agent/src/opnsense/mvc/app/models/OPNsense/QemuGuestAgent/ACL/ACL.xml new file mode 100644 index 0000000000..05a218ed59 --- /dev/null +++ b/emulators/qemu-guest-agent/src/opnsense/mvc/app/models/OPNsense/QemuGuestAgent/ACL/ACL.xml @@ -0,0 +1,11 @@ + + + Services: QEMU Guest Agent + + ui/qemuguestagent/* + api/qemuguestagent/* + ui/diagnostics/log/core/qemu-ga/* + api/diagnostics/log/core/qemu-ga/* + + + diff --git a/emulators/qemu-guest-agent/src/opnsense/mvc/app/models/OPNsense/QemuGuestAgent/Menu/Menu.xml b/emulators/qemu-guest-agent/src/opnsense/mvc/app/models/OPNsense/QemuGuestAgent/Menu/Menu.xml new file mode 100644 index 0000000000..8cecba521b --- /dev/null +++ b/emulators/qemu-guest-agent/src/opnsense/mvc/app/models/OPNsense/QemuGuestAgent/Menu/Menu.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/emulators/qemu-guest-agent/src/opnsense/mvc/app/models/OPNsense/QemuGuestAgent/QemuGuestAgent.php b/emulators/qemu-guest-agent/src/opnsense/mvc/app/models/OPNsense/QemuGuestAgent/QemuGuestAgent.php new file mode 100644 index 0000000000..e84d734e47 --- /dev/null +++ b/emulators/qemu-guest-agent/src/opnsense/mvc/app/models/OPNsense/QemuGuestAgent/QemuGuestAgent.php @@ -0,0 +1,49 @@ +general->Enabled === "1") { + return true; + } + return false; + } +} diff --git a/emulators/qemu-guest-agent/src/opnsense/mvc/app/models/OPNsense/QemuGuestAgent/QemuGuestAgent.xml b/emulators/qemu-guest-agent/src/opnsense/mvc/app/models/OPNsense/QemuGuestAgent/QemuGuestAgent.xml new file mode 100644 index 0000000000..59b056bbc7 --- /dev/null +++ b/emulators/qemu-guest-agent/src/opnsense/mvc/app/models/OPNsense/QemuGuestAgent/QemuGuestAgent.xml @@ -0,0 +1,55 @@ + + //OPNsense/QemuGuestAgent + 1.0.0 + QEMU Guest Agent for OPNsense + + + + 1 + Y + + + + Y + Y + + guest-exec + guest-exec-status + guest-file-close + guest-file-flush + guest-file-open + guest-file-read + guest-file-seek + guest-file-write + guest-fsfreeze-freeze + guest-fsfreeze-freeze-list + guest-fsfreeze-status + guest-fsfreeze-thaw + guest-fstrim + guest-get-fsinfo + guest-get-host-name + guest-get-memory-block-info + guest-get-memory-blocks + guest-get-osinfo + guest-get-time + guest-get-timezone + guest-get-users + guest-get-vcpus + guest-info + guest-network-get-interfaces + guest-ping + guest-set-memory-blocks + guest-set-time + guest-set-user-password + guest-set-vcpus + guest-shutdown + guest-suspend-disk + guest-suspend-hybrid + guest-suspend-ram + guest-sync + guest-sync-delimited + + + + + diff --git a/emulators/qemu-guest-agent/src/opnsense/mvc/app/views/OPNsense/QemuGuestAgent/index.volt b/emulators/qemu-guest-agent/src/opnsense/mvc/app/views/OPNsense/QemuGuestAgent/index.volt new file mode 100644 index 0000000000..49563e73e8 --- /dev/null +++ b/emulators/qemu-guest-agent/src/opnsense/mvc/app/views/OPNsense/QemuGuestAgent/index.volt @@ -0,0 +1,63 @@ +{# + +OPNsense® is Copyright © 2021 Frank Wall +OPNsense® is Copyright © 2014 – 2015 by Deciso B.V. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, +OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +#} + + + + + +
+ {{ partial("layout_partials/base_form",['fields':generalForm,'id':'frm_GeneralSettings'])}} +
+ +
+
+ +
+
+
diff --git a/emulators/qemu-guest-agent/src/opnsense/scripts/OPNsense/QemuGuestAgent/setup.sh b/emulators/qemu-guest-agent/src/opnsense/scripts/OPNsense/QemuGuestAgent/setup.sh new file mode 100755 index 0000000000..f3e18ed33c --- /dev/null +++ b/emulators/qemu-guest-agent/src/opnsense/scripts/OPNsense/QemuGuestAgent/setup.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +# Check if the kernel module exists +KERNMOD='virtio_console' +if [ -e /boot/kernel/${KERNMOD}.ko ]; then + # Load module + kldload $KERNMOD 2>&1 +fi + +exit 0 diff --git a/emulators/qemu-guest-agent/src/opnsense/service/conf/actions.d/actions_qemuguestagent.conf b/emulators/qemu-guest-agent/src/opnsense/service/conf/actions.d/actions_qemuguestagent.conf new file mode 100644 index 0000000000..6a24b46a88 --- /dev/null +++ b/emulators/qemu-guest-agent/src/opnsense/service/conf/actions.d/actions_qemuguestagent.conf @@ -0,0 +1,23 @@ +[start] +command:/usr/local/etc/rc.d/qemu-guest-agent start +parameters: +type:script +message:starting qemu-guest-agent + +[stop] +command:/usr/local/etc/rc.d/qemu-guest-agent stop +parameters: +type:script +message:stopping qemu-guest-agent + +[restart] +command:/usr/local/etc/rc.d/qemu-guest-agent restart +parameters: +type:script +message:restarting qemu-guest-agent + +[status] +command:/usr/local/etc/rc.d/qemu-guest-agent status; exit 0 +parameters: +type:script_output +message:requesting qemu-guest-agent status diff --git a/emulators/qemu-guest-agent/src/opnsense/service/templates/OPNsense/QemuGuestAgent/+TARGETS b/emulators/qemu-guest-agent/src/opnsense/service/templates/OPNsense/QemuGuestAgent/+TARGETS new file mode 100644 index 0000000000..3bc91f3c9f --- /dev/null +++ b/emulators/qemu-guest-agent/src/opnsense/service/templates/OPNsense/QemuGuestAgent/+TARGETS @@ -0,0 +1 @@ +rc.conf.d:/etc/rc.conf.d/qemu_guest_agent diff --git a/emulators/qemu-guest-agent/src/opnsense/service/templates/OPNsense/QemuGuestAgent/rc.conf.d b/emulators/qemu-guest-agent/src/opnsense/service/templates/OPNsense/QemuGuestAgent/rc.conf.d new file mode 100644 index 0000000000..4a14bee7e6 --- /dev/null +++ b/emulators/qemu-guest-agent/src/opnsense/service/templates/OPNsense/QemuGuestAgent/rc.conf.d @@ -0,0 +1,16 @@ +{# Default setting is enabled, so that no GUI interaction is required. #} +{% if not (helpers.exists('OPNsense.QemuGuestAgent.general.LogDebug')) or OPNsense.QemuGuestAgent.general.Enabled|default("0") != "0" %} +{% set optional_flags = [] %} +{% do optional_flags.append('-d -l /var/log/qemu-ga.log') %} +{% if helpers.exists('OPNsense.QemuGuestAgent.general.LogDebug') and OPNsense.QemuGuestAgent.general.LogDebug|default("0") == "1" %} +{% do optional_flags.append('-v') %} +{% endif %} +{% if helpers.exists('OPNsense.QemuGuestAgent.general.DisabledRPCs') and not helpers.empty('OPNsense.QemuGuestAgent.general.DisabledRPCs') %} +{% do optional_flags.append('--block-rpcs=' ~ OPNsense.QemuGuestAgent.general.DisabledRPCs) %} +{% endif %} +qemu_guest_agent_setup="/usr/local/opnsense/scripts/OPNsense/QemuGuestAgent/setup.sh" +qemu_guest_agent_enable="YES" +qemu_guest_agent_flags="{{optional_flags|join(' ')}}" +{% else %} +qemu_guest_agent_enable="NO" +{% endif %} diff --git a/ftp/tftp/Makefile b/ftp/tftp/Makefile new file mode 100644 index 0000000000..7b150eabcb --- /dev/null +++ b/ftp/tftp/Makefile @@ -0,0 +1,7 @@ +PLUGIN_NAME= tftp +PLUGIN_VERSION= 1.0 +PLUGIN_COMMENT= TFTP server +PLUGIN_DEPENDS= tftp-hpa +PLUGIN_MAINTAINER= m.muenz@gmail.com + +.include "../../Mk/plugins.mk" diff --git a/ftp/tftp/pkg-descr b/ftp/tftp/pkg-descr new file mode 100644 index 0000000000..2d721591bc --- /dev/null +++ b/ftp/tftp/pkg-descr @@ -0,0 +1,3 @@ +tftp-hpa is portable, BSD derived tftp server. It supports advanced +options such as blksize, blksize2, tsize, timeout, and utimeout. +It also supported rulebased security options. diff --git a/ftp/tftp/src/etc/inc/plugins.inc.d/tftp.inc b/ftp/tftp/src/etc/inc/plugins.inc.d/tftp.inc new file mode 100644 index 0000000000..71a0d9a39c --- /dev/null +++ b/ftp/tftp/src/etc/inc/plugins.inc.d/tftp.inc @@ -0,0 +1,49 @@ + + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +function tftp_services() +{ + global $config; + + $services = array(); + + if (isset($config['OPNsense']['tftp']['general']['enabled']) && $config['OPNsense']['tftp']['general']['enabled'] == 1) { + $services[] = array( + 'description' => gettext('tftp daemon'), + 'configd' => array( + 'restart' => array('tftp restart'), + 'start' => array('tftp start'), + 'stop' => array('tftp stop'), + ), + 'name' => 'in.tftpd', + 'pidfile' => '/var/run/tftpd.pid' + ); + } + + return $services; +} diff --git a/ftp/tftp/src/opnsense/mvc/app/controllers/OPNsense/Tftp/Api/GeneralController.php b/ftp/tftp/src/opnsense/mvc/app/controllers/OPNsense/Tftp/Api/GeneralController.php new file mode 100644 index 0000000000..8e99066c98 --- /dev/null +++ b/ftp/tftp/src/opnsense/mvc/app/controllers/OPNsense/Tftp/Api/GeneralController.php @@ -0,0 +1,37 @@ + + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +namespace OPNsense\Tftp\Api; + +use OPNsense\Base\ApiMutableModelControllerBase; + +class GeneralController extends ApiMutableModelControllerBase +{ + protected static $internalModelClass = '\OPNsense\Tftp\General'; + protected static $internalModelName = 'general'; +} diff --git a/ftp/tftp/src/opnsense/mvc/app/controllers/OPNsense/Tftp/Api/ServiceController.php b/ftp/tftp/src/opnsense/mvc/app/controllers/OPNsense/Tftp/Api/ServiceController.php new file mode 100644 index 0000000000..28efc5ea71 --- /dev/null +++ b/ftp/tftp/src/opnsense/mvc/app/controllers/OPNsense/Tftp/Api/ServiceController.php @@ -0,0 +1,39 @@ + + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +namespace OPNsense\Tftp\Api; + +use OPNsense\Base\ApiMutableServiceControllerBase; + +class ServiceController extends ApiMutableServiceControllerBase +{ + protected static $internalServiceClass = '\OPNsense\Tftp\General'; + protected static $internalServiceTemplate = 'OPNsense/Tftp'; + protected static $internalServiceEnabled = 'enabled'; + protected static $internalServiceName = 'tftp'; +} diff --git a/ftp/tftp/src/opnsense/mvc/app/controllers/OPNsense/Tftp/GeneralController.php b/ftp/tftp/src/opnsense/mvc/app/controllers/OPNsense/Tftp/GeneralController.php new file mode 100644 index 0000000000..baf4c7d0bf --- /dev/null +++ b/ftp/tftp/src/opnsense/mvc/app/controllers/OPNsense/Tftp/GeneralController.php @@ -0,0 +1,38 @@ + + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +namespace OPNsense\Tftp; + +class GeneralController extends \OPNsense\Base\IndexController +{ + public function indexAction() + { + $this->view->generalForm = $this->getForm('general'); + $this->view->pick('OPNsense/Tftp/general'); + } +} diff --git a/ftp/tftp/src/opnsense/mvc/app/controllers/OPNsense/Tftp/forms/general.xml b/ftp/tftp/src/opnsense/mvc/app/controllers/OPNsense/Tftp/forms/general.xml new file mode 100644 index 0000000000..85a19059bc --- /dev/null +++ b/ftp/tftp/src/opnsense/mvc/app/controllers/OPNsense/Tftp/forms/general.xml @@ -0,0 +1,14 @@ +
+ + general.enabled + + checkbox + Enable TFTP daemon. + + + general.listen + + text + Set the IP addresses to listen to. + +
diff --git a/ftp/tftp/src/opnsense/mvc/app/models/OPNsense/Tftp/ACL/ACL.xml b/ftp/tftp/src/opnsense/mvc/app/models/OPNsense/Tftp/ACL/ACL.xml new file mode 100644 index 0000000000..19a816d40f --- /dev/null +++ b/ftp/tftp/src/opnsense/mvc/app/models/OPNsense/Tftp/ACL/ACL.xml @@ -0,0 +1,9 @@ + + + Services: TFTP + + ui/tftp/* + api/tftp/* + + + diff --git a/ftp/tftp/src/opnsense/mvc/app/models/OPNsense/Tftp/General.php b/ftp/tftp/src/opnsense/mvc/app/models/OPNsense/Tftp/General.php new file mode 100644 index 0000000000..a1c0a0d3fb --- /dev/null +++ b/ftp/tftp/src/opnsense/mvc/app/models/OPNsense/Tftp/General.php @@ -0,0 +1,35 @@ + + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +namespace OPNsense\Tftp; + +use OPNsense\Base\BaseModel; + +class General extends BaseModel +{ +} diff --git a/ftp/tftp/src/opnsense/mvc/app/models/OPNsense/Tftp/General.xml b/ftp/tftp/src/opnsense/mvc/app/models/OPNsense/Tftp/General.xml new file mode 100644 index 0000000000..68a2ba78a7 --- /dev/null +++ b/ftp/tftp/src/opnsense/mvc/app/models/OPNsense/Tftp/General.xml @@ -0,0 +1,15 @@ + + //OPNsense/tftp/general + TFTP configuration + 0.0.1 + + + 0 + Y + + + 127.0.0.1 + Y + + + diff --git a/ftp/tftp/src/opnsense/mvc/app/models/OPNsense/Tftp/Menu/Menu.xml b/ftp/tftp/src/opnsense/mvc/app/models/OPNsense/Tftp/Menu/Menu.xml new file mode 100644 index 0000000000..2c810ee3db --- /dev/null +++ b/ftp/tftp/src/opnsense/mvc/app/models/OPNsense/Tftp/Menu/Menu.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/ftp/tftp/src/opnsense/mvc/app/views/OPNsense/Tftp/general.volt b/ftp/tftp/src/opnsense/mvc/app/views/OPNsense/Tftp/general.volt new file mode 100644 index 0000000000..23bbd73721 --- /dev/null +++ b/ftp/tftp/src/opnsense/mvc/app/views/OPNsense/Tftp/general.volt @@ -0,0 +1,66 @@ +{# + # Copyright (c) 2019 Deciso B.V. + # Copyright (c) 2021 Michael Muenz + # All rights reserved. + # + # Redistribution and use in source and binary forms, with or without modification, + # are permitted provided that the following conditions are met: + # + # 1. Redistributions of source code must retain the above copyright notice, + # this list of conditions and the following disclaimer. + # + # 2. Redistributions in binary form must reproduce the above copyright notice, + # this list of conditions and the following disclaimer in the documentation + # and/or other materials provided with the distribution. + # + # THIS SOFTWARE IS PROVIDED “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + # AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + # AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + # OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + # POSSIBILITY OF SUCH DAMAGE. + #} + +
+
+ + + + +
+ {{ lang._('The root folder for transfering files is /usr/local/tftp.') }} +
+
+ {{ partial("layout_partials/base_form",['fields':generalForm,'id':'frm_general_settings'])}} +
+
+ +
+
+ + diff --git a/ftp/tftp/src/opnsense/service/conf/actions.d/actions_tftp.conf b/ftp/tftp/src/opnsense/service/conf/actions.d/actions_tftp.conf new file mode 100644 index 0000000000..5e05be709a --- /dev/null +++ b/ftp/tftp/src/opnsense/service/conf/actions.d/actions_tftp.conf @@ -0,0 +1,23 @@ +[start] +command:/usr/local/etc/rc.d/tftpd start +parameters: +type:script +message:starting tftp + +[stop] +command:/usr/local/etc/rc.d/tftpd stop +parameters: +type:script +message:stopping tftp + +[restart] +command:/usr/local/etc/rc.d/tftpd restart +parameters: +type:script +message:restarting tftp + +[status] +command:/usr/local/etc/rc.d/tftpd status;exit 0 +parameters: +type:script_output +message:request tftp status diff --git a/ftp/tftp/src/opnsense/service/templates/OPNsense/Tftp/+TARGETS b/ftp/tftp/src/opnsense/service/templates/OPNsense/Tftp/+TARGETS new file mode 100644 index 0000000000..14cc1b4e33 --- /dev/null +++ b/ftp/tftp/src/opnsense/service/templates/OPNsense/Tftp/+TARGETS @@ -0,0 +1 @@ +tftpd:/etc/rc.conf.d/tftpd diff --git a/ftp/tftp/src/opnsense/service/templates/OPNsense/Tftp/tftpd b/ftp/tftp/src/opnsense/service/templates/OPNsense/Tftp/tftpd new file mode 100644 index 0000000000..982879ac71 --- /dev/null +++ b/ftp/tftp/src/opnsense/service/templates/OPNsense/Tftp/tftpd @@ -0,0 +1,6 @@ +{% if helpers.exists('OPNsense.tftp.general.enabled') and OPNsense.tftp.general.enabled == '1' %} +tftpd_enable="YES" +tftpd_flags="-s /usr/local/tftp -a {{ OPNsense.tftp.general.listen }}" +{% else %} +tftpd_enable="NO" +{% endif %} diff --git a/mail/postfix/Makefile b/mail/postfix/Makefile index ca8860ddb5..ca319ebf68 100644 --- a/mail/postfix/Makefile +++ b/mail/postfix/Makefile @@ -1,7 +1,7 @@ PLUGIN_NAME= postfix -PLUGIN_VERSION= 1.13 +PLUGIN_VERSION= 1.24.1 PLUGIN_COMMENT= SMTP mail relay -PLUGIN_DEPENDS= postfix-sasl +PLUGIN_DEPENDS= postfix PLUGIN_MAINTAINER= m.muenz@gmail.com .include "../../Mk/plugins.mk" diff --git a/mail/postfix/pkg-descr b/mail/postfix/pkg-descr index b319d42372..77caa08879 100644 --- a/mail/postfix/pkg-descr +++ b/mail/postfix/pkg-descr @@ -6,6 +6,55 @@ is completely different. Plugin Changelog ================ +1.24.1 + +* Align mailbox and message size + +1.24 + +* Disable broken, insecure, legacy NTLM authentication (contributed by Alfred Egger) + +1.23 + +* Add support for Opportunistic DANE as SMTP client security level +* Disable defunct GSSAPI authentication (contributed by Patrick M. Hausen) + +1.22 + +* Switch table format of header_checks from regexp_table to pcre_table (contributed by Starkstromkonsument) + +1.21 + +* Add static link to root certficiates + +1.20 + +* Make 'delay_warning_time' configurable in the UI + +1.19 + +* Add TLS server/ client compatibility modes based on Mozilla's TLS configuration recommendations (https://ssl-config.mozilla.org). + +1.18 + +* Add 'milter_default_action' choice +* Fix Milter Protocol + +1.17 + +* Add smtpd_sasl_auth_enable to configuration (contributed by fhloston) + +1.16 + +* Add support for header_checks (contributed by Starkstromkonsument) + +1.15 + +* Fix Log viewer + +1.14 + +* Add more anti-spam features into postfix itself 1.13 diff --git a/mail/postfix/src/etc/inc/plugins.inc.d/postfix.inc b/mail/postfix/src/etc/inc/plugins.inc.d/postfix.inc index b83b64bc5d..d486a07d21 100644 --- a/mail/postfix/src/etc/inc/plugins.inc.d/postfix.inc +++ b/mail/postfix/src/etc/inc/plugins.inc.d/postfix.inc @@ -52,10 +52,7 @@ function postfix_syslog() { $syslogconf = array(); - $syslogconf['mail'] = array( - 'facility' => array('postfix'), - 'remote' => 'mail', - ); + $syslogconf['postfix'] = array('facility' => array('postfix')); return $syslogconf; } diff --git a/mail/postfix/src/opnsense/data/OPNsense/Postfix/dh-parameters.2048.rfc7919 b/mail/postfix/src/opnsense/data/OPNsense/Postfix/dh-parameters.2048.rfc7919 new file mode 100644 index 0000000000..9b182b7201 --- /dev/null +++ b/mail/postfix/src/opnsense/data/OPNsense/Postfix/dh-parameters.2048.rfc7919 @@ -0,0 +1,8 @@ +-----BEGIN DH PARAMETERS----- +MIIBCAKCAQEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz ++8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a +87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7 +YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi +7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD +ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg== +-----END DH PARAMETERS----- diff --git a/mail/postfix/src/opnsense/mvc/app/controllers/OPNsense/Postfix/Api/HeaderchecksController.php b/mail/postfix/src/opnsense/mvc/app/controllers/OPNsense/Postfix/Api/HeaderchecksController.php new file mode 100644 index 0000000000..25e1f49b7e --- /dev/null +++ b/mail/postfix/src/opnsense/mvc/app/controllers/OPNsense/Postfix/Api/HeaderchecksController.php @@ -0,0 +1,67 @@ +searchBase('headerchecks.headercheck', array("enabled", "expression", "filter")); + } + + public function getHeadercheckAction($uuid = null) + { + return $this->getBase('headercheck', 'headerchecks.headercheck', $uuid); + } + + public function addHeadercheckAction() + { + return $this->addBase('headercheck', 'headerchecks.headercheck'); + } + + public function delHeadercheckAction($uuid) + { + return $this->delBase('headerchecks.headercheck', $uuid); + } + + public function setHeadercheckAction($uuid) + { + return $this->setBase('headercheck', 'headerchecks.headercheck', $uuid); + } + + public function toggleHeadercheckAction($uuid) + { + return $this->toggleBase('headerchecks.headercheck', $uuid); + } +} diff --git a/mail/postfix/src/opnsense/mvc/app/controllers/OPNsense/Postfix/Api/ServiceController.php b/mail/postfix/src/opnsense/mvc/app/controllers/OPNsense/Postfix/Api/ServiceController.php index e0091751c1..60018990d5 100644 --- a/mail/postfix/src/opnsense/mvc/app/controllers/OPNsense/Postfix/Api/ServiceController.php +++ b/mail/postfix/src/opnsense/mvc/app/controllers/OPNsense/Postfix/Api/ServiceController.php @@ -64,9 +64,6 @@ public function checkrspamdAction() public function reconfigureAction() { if ($this->request->isPost()) { - // close session for long running action - $this->sessionClose(); - $mdlGeneral = new General(); $backend = new Backend(); diff --git a/mail/postfix/src/opnsense/mvc/app/controllers/OPNsense/Postfix/HeaderchecksController.php b/mail/postfix/src/opnsense/mvc/app/controllers/OPNsense/Postfix/HeaderchecksController.php new file mode 100644 index 0000000000..ba4e6e7dd6 --- /dev/null +++ b/mail/postfix/src/opnsense/mvc/app/controllers/OPNsense/Postfix/HeaderchecksController.php @@ -0,0 +1,38 @@ +view->formDialogEditPostfixHeadercheck = $this->getForm("dialogEditPostfixHeadercheck"); + $this->view->pick('OPNsense/Postfix/headerchecks'); + } +} diff --git a/mail/postfix/src/opnsense/mvc/app/controllers/OPNsense/Postfix/forms/antispam.xml b/mail/postfix/src/opnsense/mvc/app/controllers/OPNsense/Postfix/forms/antispam.xml index 56385df57d..031fa2b2f9 100644 --- a/mail/postfix/src/opnsense/mvc/app/controllers/OPNsense/Postfix/forms/antispam.xml +++ b/mail/postfix/src/opnsense/mvc/app/controllers/OPNsense/Postfix/forms/antispam.xml @@ -6,9 +6,9 @@ This will allow Postfix to connect to rspamd via milter protocol. - antispam.milter_rspamd - + antispam.default_action + dropdown - Select the IP version for connecting to rspamd. + What Postfix will do when Rspamd becomes temporarily unavailable. diff --git a/mail/postfix/src/opnsense/mvc/app/controllers/OPNsense/Postfix/forms/dialogEditPostfixHeadercheck.xml b/mail/postfix/src/opnsense/mvc/app/controllers/OPNsense/Postfix/forms/dialogEditPostfixHeadercheck.xml new file mode 100644 index 0000000000..5fe46d1414 --- /dev/null +++ b/mail/postfix/src/opnsense/mvc/app/controllers/OPNsense/Postfix/forms/dialogEditPostfixHeadercheck.xml @@ -0,0 +1,21 @@ +
+ + headercheck.enabled + + checkbox + This will enable or disable the header_check rule. + + + headercheck.expression + + text + /^\s*User-Agent/ IGNORENote: The regexp is not validated by this form. Please test it carefully.]]> + + + headercheck.filter + + dropdown + See the Postfix manual about header_checks(5)]]> + RECEIVING = header_checks / DELIVERING = smtp_header_checks + +
diff --git a/mail/postfix/src/opnsense/mvc/app/controllers/OPNsense/Postfix/forms/general.xml b/mail/postfix/src/opnsense/mvc/app/controllers/OPNsense/Postfix/forms/general.xml index 2432b7b13f..35982885fa 100644 --- a/mail/postfix/src/opnsense/mvc/app/controllers/OPNsense/Postfix/forms/general.xml +++ b/mail/postfix/src/opnsense/mvc/app/controllers/OPNsense/Postfix/forms/general.xml @@ -27,7 +27,7 @@ general.inet_interfaces text - The 'Listen IPs' parameter specifies the IP address to listen to. Default is to listen on all interfaces. + The 'Listen IPs' parameter specifies a comma-separated list of IP addresses to listen to. Default is to listen on all interfaces. general.inet_port @@ -73,7 +73,7 @@ general.message_size_limit text - Set the max size for messages to accept, default is 501200000 Byte which is 50MB. Values must be entered in Bytes. + Set the max size for messages to accept, default is 51200000 Bytes which is 50MB. Values must be entered in Bytes. general.masquerade_domains @@ -81,7 +81,7 @@ select_multiple true - Masquerade internal domains to the outside. When you set example.com, the domain host.internal.example.com will be rewritten to exmaple.com when mail leaves the system. + Masquerade internal domains to the outside. When you set example.com, the domain host.internal.example.com will be rewritten to example.com when mail leaves the system. general.disable_ssl @@ -90,10 +90,16 @@ Disable SSLv2 and SSLv3, only TLS allowed. - general.disable_weak_ciphers - - checkbox - This will disable known weak ciphers like DES, RC4 or MD5. + general.tls_server_compatibility + + dropdown + TLS version/ cipher compatibility of the SMTP service + + + general.tls_client_compatibility + + dropdown + TLS version/ cipher compatibility of the SMTP Client general.tlswrappermode @@ -117,13 +123,19 @@ general.smtpclient_security dropdown - Choose "none" to disable TLS for sending mail. Set encrypt to enforce TLS security, please do not use this for Internet wide communication as not every server supports TLS yet. Default is "may" which will use TLS when offered. + +
  • 'none' will disable TLS for sending mail.
  • +
  • 'may' will use TLS when offered (Opportunistic TLS)
  • +
  • 'encrypt' will enforce TLS on all connections. Please do not use this for Internet wide communication as not every server supports TLS yet.
  • +
  • 'dane' will enforce TLS if a TLSA-Record is published (Opportunistic DANE, RFC 7672). DNSSEC-capable resolver is required.
  • + ]]>
    general.relayhost text - Set the IP address or FQDN where all outgoung mails are sent to. + Set the IP address or FQDN where all outgoing mails are sent to. general.smtpauth_enabled @@ -148,7 +160,37 @@ checkbox true - If you enable this, every entry in Recipients will be checked against. When there is no match mail will be rejected. Be aware that it does not matter if the action is "OK" or "REJECT". This setup allows you to run postfix in front of an internal system and already rejecting unsolicited mail at the border. + If you enable this, every entry in Recipients will be checked against. When there is no match mail will be rejected. Be aware that it does not matter if the action is "OK" or "REJECT". This setup allows you to run postfix in front of an internal system and already reject unsolicited mail at the border. + + + general.extensive_helo_restrictions + + checkbox + + + general.extensive_sender_restrictions + + checkbox + + + general.reject_unknown_client_hostname + + checkbox + + + general.reject_non_fqdn_helo_hostname + + checkbox + + + general.reject_invalid_helo_hostname + + checkbox + + + general.reject_unknown_helo_hostname + + checkbox general.reject_unauth_pipelining @@ -168,13 +210,13 @@ general.reject_non_fqdn_sender - + checkbox For example senders without a domain or only a hostname. general.reject_non_fqdn_recipient - + checkbox For example recipients without a domain or only a hostname. @@ -182,6 +224,7 @@ general.permit_sasl_authenticated checkbox + Allow SASL authenticated senders to relay. Will also enable smtpd_sasl_auth. general.permit_tls_clientcerts @@ -198,4 +241,16 @@ checkbox + + general.reject_unverified_recipient + + checkbox + Use Recipient Address Verification. Please keep in mind that this could put significant load onto the next server. + + + general.delay_warning_time + + text + Time until we send a notification to the sender if mail is delayed (in hours) - 0 or empty to disable. + diff --git a/mail/postfix/src/opnsense/mvc/app/models/OPNsense/Postfix/Address.xml b/mail/postfix/src/opnsense/mvc/app/models/OPNsense/Postfix/Address.xml index ffaf8c949e..73baf4a493 100644 --- a/mail/postfix/src/opnsense/mvc/app/models/OPNsense/Postfix/Address.xml +++ b/mail/postfix/src/opnsense/mvc/app/models/OPNsense/Postfix/Address.xml @@ -6,7 +6,7 @@
    - 1 + 1 Y diff --git a/mail/postfix/src/opnsense/mvc/app/models/OPNsense/Postfix/Antispam.xml b/mail/postfix/src/opnsense/mvc/app/models/OPNsense/Postfix/Antispam.xml index 6e0e02587f..037907446a 100644 --- a/mail/postfix/src/opnsense/mvc/app/models/OPNsense/Postfix/Antispam.xml +++ b/mail/postfix/src/opnsense/mvc/app/models/OPNsense/Postfix/Antispam.xml @@ -1,19 +1,19 @@ //OPNsense/postfix/antispam Postfix Antispam configuration - 1.0.1 + 1.0.2 - 0 + 0 Y - - 6 + + accept Y - IPv6 - IPv4 + accept + tempfail - + diff --git a/mail/postfix/src/opnsense/mvc/app/models/OPNsense/Postfix/Domain.xml b/mail/postfix/src/opnsense/mvc/app/models/OPNsense/Postfix/Domain.xml index f74d697d70..a4b9f5c2fd 100644 --- a/mail/postfix/src/opnsense/mvc/app/models/OPNsense/Postfix/Domain.xml +++ b/mail/postfix/src/opnsense/mvc/app/models/OPNsense/Postfix/Domain.xml @@ -4,21 +4,18 @@ 1.0.1 - - - 1 - Y - - - - Y - - - - N - /^([0-9a-zA-Z.:\-\[\]]){1,64}$/u - Only 64 of the following characters are allowed: 0-9a-zA-Z.:-[] - + + + 1 + Y + + + Y + + + /^([0-9a-zA-Z.:\-\[\]]){1,64}$/u + Only 64 of the following characters are allowed: 0-9a-zA-Z.:-[] + diff --git a/mail/postfix/src/opnsense/mvc/app/models/OPNsense/Postfix/General.xml b/mail/postfix/src/opnsense/mvc/app/models/OPNsense/Postfix/General.xml index e2d2a99e59..ddc1928f9d 100644 --- a/mail/postfix/src/opnsense/mvc/app/models/OPNsense/Postfix/General.xml +++ b/mail/postfix/src/opnsense/mvc/app/models/OPNsense/Postfix/General.xml @@ -1,34 +1,25 @@ //OPNsense/postfix/general Postfix configuration - 1.2.4 + 1.2.7 - 0 + 0 Y - - - N - - - - N - - - - N - + + + - all + all Y - 25 + 25 Y - all + all Y All @@ -37,115 +28,144 @@ - N ipv4 - N ipv6 - 127.0.0.0/8,[::ffff:127.0.0.0]/104,[::1]/128 + 127.0.0.0/8,[::ffff:127.0.0.0]/104,[::1]/128 Y - - - N - + - 51200000 + 51200000 Y - N - /^([0-9a-z\.\-\_]{1,128})(,[0-9a-z\.\-\_]{1,128})*$/ui + /^([0-9a-z\.\-\_]{1,128})(,[0-9a-z\.\-\_]{1,128})*$/ui Only up to 128 of the following characters are allowed: 0-9a-zA-Z.-_ - - 1 + + intermediate Y - - - 1 + + Modern + Intermediate + Old + + + + intermediate Y - + + Modern + Intermediate + Old + + - 0 + 0 Y cert - N ca - N - may + may Y none may encrypt + dane - N - /^([0-9a-zA-Z.:\-\[\]]){1,64}$/u + /^([0-9a-zA-Z.:\-\[\]]){1,64}$/u Only 64 of the following characters are allowed: 0-9a-zA-Z.:-[] - 0 + 0 Y - - - N - - - - N - + + - 0 + 0 Y + + 0 + Y + + + 0 + Y + + + 0 + Y + + + 0 + Y + + + 0 + Y + + + 0 + Y + - 1 + 1 Y - 1 + 1 Y - 1 + 1 Y - 1 + 1 Y - 1 + 1 Y - 1 + 1 Y - 1 + 1 Y - 1 + 1 Y - 1 + 1 Y + + 0 + Y + + + 0 + 24 + Choose a value between 1 and 24 to activate - 0 or empty to disable. + diff --git a/mail/postfix/src/opnsense/mvc/app/models/OPNsense/Postfix/Headerchecks.php b/mail/postfix/src/opnsense/mvc/app/models/OPNsense/Postfix/Headerchecks.php new file mode 100644 index 0000000000..be9da069a9 --- /dev/null +++ b/mail/postfix/src/opnsense/mvc/app/models/OPNsense/Postfix/Headerchecks.php @@ -0,0 +1,34 @@ + + //OPNsense/postfix/headerchecks + Postfix header_checks configuration + 1.0.0 + + + + + 1 + Y + + + Y + + + Y + + while delivering mail + while receiving mail + + + + + + diff --git a/mail/postfix/src/opnsense/mvc/app/models/OPNsense/Postfix/Menu/Menu.xml b/mail/postfix/src/opnsense/mvc/app/models/OPNsense/Postfix/Menu/Menu.xml index b93892062c..9e88d86a1b 100644 --- a/mail/postfix/src/opnsense/mvc/app/models/OPNsense/Postfix/Menu/Menu.xml +++ b/mail/postfix/src/opnsense/mvc/app/models/OPNsense/Postfix/Menu/Menu.xml @@ -8,8 +8,9 @@ +
    - + diff --git a/mail/postfix/src/opnsense/mvc/app/models/OPNsense/Postfix/Recipient.xml b/mail/postfix/src/opnsense/mvc/app/models/OPNsense/Postfix/Recipient.xml index 40b5c48b46..e90726dea8 100644 --- a/mail/postfix/src/opnsense/mvc/app/models/OPNsense/Postfix/Recipient.xml +++ b/mail/postfix/src/opnsense/mvc/app/models/OPNsense/Postfix/Recipient.xml @@ -4,21 +4,21 @@ 1.0.0 - - - 1 - Y - -
    - Y -
    - - Y - - OK - REJECT - - + + + 1 + Y + +
    + Y +
    + + Y + + OK + REJECT + +
    diff --git a/mail/postfix/src/opnsense/mvc/app/models/OPNsense/Postfix/Recipientbcc.xml b/mail/postfix/src/opnsense/mvc/app/models/OPNsense/Postfix/Recipientbcc.xml index f763871c34..1adf180358 100644 --- a/mail/postfix/src/opnsense/mvc/app/models/OPNsense/Postfix/Recipientbcc.xml +++ b/mail/postfix/src/opnsense/mvc/app/models/OPNsense/Postfix/Recipientbcc.xml @@ -6,7 +6,7 @@ - 1 + 1 Y diff --git a/mail/postfix/src/opnsense/mvc/app/models/OPNsense/Postfix/Sender.xml b/mail/postfix/src/opnsense/mvc/app/models/OPNsense/Postfix/Sender.xml index ad250f8027..d293eeaf72 100644 --- a/mail/postfix/src/opnsense/mvc/app/models/OPNsense/Postfix/Sender.xml +++ b/mail/postfix/src/opnsense/mvc/app/models/OPNsense/Postfix/Sender.xml @@ -4,21 +4,21 @@ 1.0.0 - - - 1 - Y - -
    - Y -
    - - Y - - OK - REJECT - - + + + 1 + Y + +
    + Y +
    + + Y + + OK + REJECT + +
    diff --git a/mail/postfix/src/opnsense/mvc/app/models/OPNsense/Postfix/Senderbcc.xml b/mail/postfix/src/opnsense/mvc/app/models/OPNsense/Postfix/Senderbcc.xml index 7f2610a926..4d696bffd5 100644 --- a/mail/postfix/src/opnsense/mvc/app/models/OPNsense/Postfix/Senderbcc.xml +++ b/mail/postfix/src/opnsense/mvc/app/models/OPNsense/Postfix/Senderbcc.xml @@ -6,7 +6,7 @@ - 1 + 1 Y diff --git a/mail/postfix/src/opnsense/mvc/app/models/OPNsense/Postfix/Sendercanonical.xml b/mail/postfix/src/opnsense/mvc/app/models/OPNsense/Postfix/Sendercanonical.xml index 46b342438f..0662d4d1f7 100644 --- a/mail/postfix/src/opnsense/mvc/app/models/OPNsense/Postfix/Sendercanonical.xml +++ b/mail/postfix/src/opnsense/mvc/app/models/OPNsense/Postfix/Sendercanonical.xml @@ -6,7 +6,7 @@ - 1 + 1 Y diff --git a/mail/postfix/src/opnsense/mvc/app/views/OPNsense/Postfix/address.volt b/mail/postfix/src/opnsense/mvc/app/views/OPNsense/Postfix/address.volt index c6bc57777d..7f1695ee1c 100644 --- a/mail/postfix/src/opnsense/mvc/app/views/OPNsense/Postfix/address.volt +++ b/mail/postfix/src/opnsense/mvc/app/views/OPNsense/Postfix/address.volt @@ -35,12 +35,12 @@ POSSIBILITY OF SUCH DAMAGE. *************************************************************************************************************/ $("#grid-addresses").UIBootgrid( - { 'search':'/api/postfix/address/searchAddress', - 'get':'/api/postfix/address/getAddress/', - 'set':'/api/postfix/address/setAddress/', - 'add':'/api/postfix/address/addAddress/', - 'del':'/api/postfix/address/delAddress/', - 'toggle':'/api/postfix/address/toggleAddress/' + { 'search':'/api/postfix/address/search_address', + 'get':'/api/postfix/address/get_address/', + 'set':'/api/postfix/address/set_address/', + 'add':'/api/postfix/address/add_address/', + 'del':'/api/postfix/address/del_address/', + 'toggle':'/api/postfix/address/toggle_address/' } ); diff --git a/mail/postfix/src/opnsense/mvc/app/views/OPNsense/Postfix/domain.volt b/mail/postfix/src/opnsense/mvc/app/views/OPNsense/Postfix/domain.volt index bf7e0b1af2..a98dd06557 100644 --- a/mail/postfix/src/opnsense/mvc/app/views/OPNsense/Postfix/domain.volt +++ b/mail/postfix/src/opnsense/mvc/app/views/OPNsense/Postfix/domain.volt @@ -35,12 +35,12 @@ POSSIBILITY OF SUCH DAMAGE. *************************************************************************************************************/ $("#grid-domains").UIBootgrid( - { 'search':'/api/postfix/domain/searchDomain', - 'get':'/api/postfix/domain/getDomain/', - 'set':'/api/postfix/domain/setDomain/', - 'add':'/api/postfix/domain/addDomain/', - 'del':'/api/postfix/domain/delDomain/', - 'toggle':'/api/postfix/domain/toggleDomain/' + { 'search':'/api/postfix/domain/search_domain', + 'get':'/api/postfix/domain/get_domain/', + 'set':'/api/postfix/domain/set_domain/', + 'add':'/api/postfix/domain/add_domain/', + 'del':'/api/postfix/domain/del_domain/', + 'toggle':'/api/postfix/domain/toggle_domain/' } ); diff --git a/mail/postfix/src/opnsense/mvc/app/views/OPNsense/Postfix/headerchecks.volt b/mail/postfix/src/opnsense/mvc/app/views/OPNsense/Postfix/headerchecks.volt new file mode 100644 index 0000000000..3cbd1be086 --- /dev/null +++ b/mail/postfix/src/opnsense/mvc/app/views/OPNsense/Postfix/headerchecks.volt @@ -0,0 +1,85 @@ +{# + # + # Copyright (C) 2014-2017 Deciso B.V. + # Copyright (C) 2020 Starkstromkonsument + # All rights reserved. + # + # Redistribution and use in source and binary forms, with or without modification, + # are permitted provided that the following conditions are met: + # + # 1. Redistributions of source code must retain the above copyright notice, + # this list of conditions and the following disclaimer. + # + # 2. Redistributions in binary form must reproduce the above copyright notice, + # this list of conditions and the following disclaimer in the documentation + # and/or other materials provided with the distribution. + # + # THIS SOFTWARE IS PROVIDED “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + # AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + # AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + # OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + # POSSIBILITY OF SUCH DAMAGE. + #} + + + +
    +
    + + + + + + + + + + + + + + + + + + + +
    {{ lang._('Enabled') }}{{ lang._('Expression') }}{{ lang._('Filter') }}{{ lang._('ID') }}{{ lang._('Commands') }}
    + + +
    +
    +
    + +

    +
    +
    +
    + +{{ partial("layout_partials/base_dialog",['fields':formDialogEditPostfixHeadercheck,'id':'dialogEditPostfixHeadercheck','label':lang._('Edit header_check rule')])}} diff --git a/mail/postfix/src/opnsense/mvc/app/views/OPNsense/Postfix/recipient.volt b/mail/postfix/src/opnsense/mvc/app/views/OPNsense/Postfix/recipient.volt index ed1e62be1d..e1bf839c7f 100644 --- a/mail/postfix/src/opnsense/mvc/app/views/OPNsense/Postfix/recipient.volt +++ b/mail/postfix/src/opnsense/mvc/app/views/OPNsense/Postfix/recipient.volt @@ -35,12 +35,12 @@ POSSIBILITY OF SUCH DAMAGE. *************************************************************************************************************/ $("#grid-recipients").UIBootgrid( - { 'search':'/api/postfix/recipient/searchRecipient', - 'get':'/api/postfix/recipient/getRecipient/', - 'set':'/api/postfix/recipient/setRecipient/', - 'add':'/api/postfix/recipient/addRecipient/', - 'del':'/api/postfix/recipient/delRecipient/', - 'toggle':'/api/postfix/recipient/toggleRecipient/' + { 'search':'/api/postfix/recipient/search_recipient', + 'get':'/api/postfix/recipient/get_recipient/', + 'set':'/api/postfix/recipient/set_recipient/', + 'add':'/api/postfix/recipient/add_recipient/', + 'del':'/api/postfix/recipient/del_recipient/', + 'toggle':'/api/postfix/recipient/toggle_recipient/' } ); diff --git a/mail/postfix/src/opnsense/mvc/app/views/OPNsense/Postfix/recipientbcc.volt b/mail/postfix/src/opnsense/mvc/app/views/OPNsense/Postfix/recipientbcc.volt index 39bd7edc70..856734592d 100644 --- a/mail/postfix/src/opnsense/mvc/app/views/OPNsense/Postfix/recipientbcc.volt +++ b/mail/postfix/src/opnsense/mvc/app/views/OPNsense/Postfix/recipientbcc.volt @@ -35,12 +35,12 @@ POSSIBILITY OF SUCH DAMAGE. *************************************************************************************************************/ $("#grid-recipientbccs").UIBootgrid( - { 'search':'/api/postfix/recipientbcc/searchRecipientbcc', - 'get':'/api/postfix/recipientbcc/getRecipientbcc/', - 'set':'/api/postfix/recipientbcc/setRecipientbcc/', - 'add':'/api/postfix/recipientbcc/addRecipientbcc/', - 'del':'/api/postfix/recipientbcc/delRecipientbcc/', - 'toggle':'/api/postfix/recipientbcc/toggleRecipientbcc/' + { 'search':'/api/postfix/recipientbcc/search_recipientbcc', + 'get':'/api/postfix/recipientbcc/get_recipientbcc/', + 'set':'/api/postfix/recipientbcc/set_recipientbcc/', + 'add':'/api/postfix/recipientbcc/add_recipientbcc/', + 'del':'/api/postfix/recipientbcc/del_recipientbcc/', + 'toggle':'/api/postfix/recipientbcc/toggle_recipientbcc/' } ); diff --git a/mail/postfix/src/opnsense/mvc/app/views/OPNsense/Postfix/sender.volt b/mail/postfix/src/opnsense/mvc/app/views/OPNsense/Postfix/sender.volt index 078e6d3869..217ce8186a 100644 --- a/mail/postfix/src/opnsense/mvc/app/views/OPNsense/Postfix/sender.volt +++ b/mail/postfix/src/opnsense/mvc/app/views/OPNsense/Postfix/sender.volt @@ -35,12 +35,12 @@ POSSIBILITY OF SUCH DAMAGE. *************************************************************************************************************/ $("#grid-senders").UIBootgrid( - { 'search':'/api/postfix/sender/searchSender', - 'get':'/api/postfix/sender/getSender/', - 'set':'/api/postfix/sender/setSender/', - 'add':'/api/postfix/sender/addSender/', - 'del':'/api/postfix/sender/delSender/', - 'toggle':'/api/postfix/sender/toggleSender/' + { 'search':'/api/postfix/sender/search_sender', + 'get':'/api/postfix/sender/get_sender/', + 'set':'/api/postfix/sender/set_sender/', + 'add':'/api/postfix/sender/add_sender/', + 'del':'/api/postfix/sender/del_sender/', + 'toggle':'/api/postfix/sender/toggle_sender/' } ); diff --git a/mail/postfix/src/opnsense/mvc/app/views/OPNsense/Postfix/senderbcc.volt b/mail/postfix/src/opnsense/mvc/app/views/OPNsense/Postfix/senderbcc.volt index 7b22a077bd..96f324696f 100644 --- a/mail/postfix/src/opnsense/mvc/app/views/OPNsense/Postfix/senderbcc.volt +++ b/mail/postfix/src/opnsense/mvc/app/views/OPNsense/Postfix/senderbcc.volt @@ -35,12 +35,12 @@ POSSIBILITY OF SUCH DAMAGE. *************************************************************************************************************/ $("#grid-senderbccs").UIBootgrid( - { 'search':'/api/postfix/senderbcc/searchSenderbcc', - 'get':'/api/postfix/senderbcc/getSenderbcc/', - 'set':'/api/postfix/senderbcc/setSenderbcc/', - 'add':'/api/postfix/senderbcc/addSenderbcc/', - 'del':'/api/postfix/senderbcc/delSenderbcc/', - 'toggle':'/api/postfix/senderbcc/toggleSenderbcc/' + { 'search':'/api/postfix/senderbcc/search_senderbcc', + 'get':'/api/postfix/senderbcc/get_senderbcc/', + 'set':'/api/postfix/senderbcc/set_senderbcc/', + 'add':'/api/postfix/senderbcc/add_senderbcc/', + 'del':'/api/postfix/senderbcc/del_senderbcc/', + 'toggle':'/api/postfix/senderbcc/toggle_senderbcc/' } ); diff --git a/mail/postfix/src/opnsense/mvc/app/views/OPNsense/Postfix/sendercanonical.volt b/mail/postfix/src/opnsense/mvc/app/views/OPNsense/Postfix/sendercanonical.volt index 5324769fb7..85848ec9ac 100644 --- a/mail/postfix/src/opnsense/mvc/app/views/OPNsense/Postfix/sendercanonical.volt +++ b/mail/postfix/src/opnsense/mvc/app/views/OPNsense/Postfix/sendercanonical.volt @@ -36,12 +36,12 @@ POSSIBILITY OF SUCH DAMAGE. *************************************************************************************************************/ $("#grid-sendercanonicals").UIBootgrid( - { 'search':'/api/postfix/sendercanonical/searchSendercanonical', - 'get':'/api/postfix/sendercanonical/getSendercanonical/', - 'set':'/api/postfix/sendercanonical/setSendercanonical/', - 'add':'/api/postfix/sendercanonical/addSendercanonical/', - 'del':'/api/postfix/sendercanonical/delSendercanonical/', - 'toggle':'/api/postfix/sendercanonical/toggleSendercanonical/' + { 'search':'/api/postfix/sendercanonical/search_sendercanonical', + 'get':'/api/postfix/sendercanonical/get_sendercanonical/', + 'set':'/api/postfix/sendercanonical/set_sendercanonical/', + 'add':'/api/postfix/sendercanonical/add_sendercanonical/', + 'del':'/api/postfix/sendercanonical/del_sendercanonical/', + 'toggle':'/api/postfix/sendercanonical/toggle_sendercanonical/' } ); diff --git a/mail/postfix/src/opnsense/service/conf/actions.d/actions_postfix.conf b/mail/postfix/src/opnsense/service/conf/actions.d/actions_postfix.conf index 1c5f54ab70..b88e4e2e65 100644 --- a/mail/postfix/src/opnsense/service/conf/actions.d/actions_postfix.conf +++ b/mail/postfix/src/opnsense/service/conf/actions.d/actions_postfix.conf @@ -1,23 +1,24 @@ [start] -command:/usr/local/opnsense/scripts/OPNsense/Postfix/setup.sh;/usr/local/etc/rc.d/postfix start +command:/usr/local/etc/rc.d/postfix start parameters: type:script message:starting Postfix [stop] -command:/usr/local/etc/rc.d/postfix stop; exit 0 +command:/usr/local/etc/rc.d/postfix stop parameters: type:script message:stopping Postfix [restart] -command:/usr/local/opnsense/scripts/OPNsense/Postfix/setup.sh;/usr/local/etc/rc.d/postfix restart +command:/usr/local/etc/rc.d/postfix restart parameters: type:script message:restarting Postfix +description:Restart Postfix service [status] -command:/usr/local/etc/rc.d/postfix status;exit 0 +command:/usr/local/etc/rc.d/postfix status; exit 0 parameters: type:script_output message:request Postfix status diff --git a/mail/postfix/src/opnsense/service/templates/OPNsense/Postfix/+TARGETS b/mail/postfix/src/opnsense/service/templates/OPNsense/Postfix/+TARGETS index 9d7dca3cd9..a1de27fd61 100644 --- a/mail/postfix/src/opnsense/service/templates/OPNsense/Postfix/+TARGETS +++ b/mail/postfix/src/opnsense/service/templates/OPNsense/Postfix/+TARGETS @@ -9,3 +9,5 @@ senderbcc:/usr/local/etc/postfix/senderbcc recipientbcc:/usr/local/etc/postfix/recipientbcc smtp_auth:/usr/local/etc/postfix/smtp_auth sendercanonical:/usr/local/etc/postfix/sendercanonical +header_checks_delivering:/usr/local/etc/postfix/header_checks_delivering +header_checks_receiving:/usr/local/etc/postfix/header_checks_receiving diff --git a/mail/postfix/src/opnsense/service/templates/OPNsense/Postfix/header_checks_delivering b/mail/postfix/src/opnsense/service/templates/OPNsense/Postfix/header_checks_delivering new file mode 100644 index 0000000000..0e53f977b6 --- /dev/null +++ b/mail/postfix/src/opnsense/service/templates/OPNsense/Postfix/header_checks_delivering @@ -0,0 +1,9 @@ +{% if helpers.exists('OPNsense.postfix.general.enabled') and OPNsense.postfix.general.enabled == '1' %} +{% if helpers.exists('OPNsense.postfix.headerchecks.headerchecks.headercheck') %} +{% for headercheck_list in helpers.toList('OPNsense.postfix.headerchecks.headerchecks.headercheck') %} +{% if headercheck_list.enabled == '1' and headercheck_list.filter == 'WHILE_DELIVERING' %} +{{ headercheck_list.expression }} +{% endif %} +{% endfor %} +{% endif %} +{% endif %} diff --git a/mail/postfix/src/opnsense/service/templates/OPNsense/Postfix/header_checks_receiving b/mail/postfix/src/opnsense/service/templates/OPNsense/Postfix/header_checks_receiving new file mode 100644 index 0000000000..320e7300c3 --- /dev/null +++ b/mail/postfix/src/opnsense/service/templates/OPNsense/Postfix/header_checks_receiving @@ -0,0 +1,9 @@ +{% if helpers.exists('OPNsense.postfix.general.enabled') and OPNsense.postfix.general.enabled == '1' %} +{% if helpers.exists('OPNsense.postfix.headerchecks.headerchecks.headercheck') %} +{% for headercheck_list in helpers.toList('OPNsense.postfix.headerchecks.headerchecks.headercheck') %} +{% if headercheck_list.enabled == '1' and headercheck_list.filter == 'WHILE_RECEIVING' %} +{{ headercheck_list.expression }} +{% endif %} +{% endfor %} +{% endif %} +{% endif %} diff --git a/mail/postfix/src/opnsense/service/templates/OPNsense/Postfix/main.cf b/mail/postfix/src/opnsense/service/templates/OPNsense/Postfix/main.cf index fed2225dbf..65be3ea0ff 100644 --- a/mail/postfix/src/opnsense/service/templates/OPNsense/Postfix/main.cf +++ b/mail/postfix/src/opnsense/service/templates/OPNsense/Postfix/main.cf @@ -35,6 +35,9 @@ virtual_alias_maps = hash:/usr/local/etc/postfix/virtual sender_bcc_maps = hash:/usr/local/etc/postfix/senderbcc recipient_bcc_maps = hash:/usr/local/etc/postfix/recipientbcc sender_canonical_maps = regexp:/usr/local/etc/postfix/sendercanonical +header_checks = pcre:/usr/local/etc/postfix/header_checks_receiving +smtp_header_checks = pcre:/usr/local/etc/postfix/header_checks_delivering +smtp_tls_CAfile = /usr/local/etc/ssl/cert.pem ########################## # END SYSTEM DEFAULTS ########################## @@ -77,33 +80,76 @@ smtpd_banner = $myhostname ESMTP Postfix {% endif %} {% if helpers.exists('OPNsense.postfix.general.message_size_limit') and OPNsense.postfix.general.message_size_limit != '' %} message_size_limit = {{ OPNsense.postfix.general.message_size_limit }} +mailbox_size_limit = {{ OPNsense.postfix.general.message_size_limit }} {% endif %} {% if helpers.exists('OPNsense.postfix.general.masquerade_domains') and OPNsense.postfix.general.masquerade_domains != '' %} masquerade_domains = {{ OPNsense.postfix.general.masquerade_domains }} {% endif %} -{% if helpers.exists('OPNsense.postfix.general.disable_ssl') and OPNsense.postfix.general.disable_ssl == '1' %} -smtpd_tls_mandatory_protocols=!SSLv2,!SSLv3 -smtp_tls_mandatory_protocols=!SSLv2,!SSLv3 -smtpd_tls_protocols=!SSLv2,!SSLv3 -smtp_tls_protocols=!SSLv2,!SSLv3 -{% endif %} -{% if helpers.exists('OPNsense.postfix.general.disable_weak_ciphers') and OPNsense.postfix.general.disable_weak_ciphers == '1' %} -smtpd_tls_exclude_ciphers = aNULL, eNULL, EXPORT, DES, RC4, MD5, PSK, aECDH, EDH-DSS-DES-CBC3-SHA, EDH-RSA-DES-CDC3-SHA, KRB5-DE5, CBC3-SHA -{% endif %} {% if helpers.exists('OPNsense.postfix.general.tlswrappermode') and OPNsense.postfix.general.tlswrappermode == '1' %} smtp_tls_wrappermode = yes {% endif %} + {% if helpers.exists('OPNsense.postfix.general.smtpclient_security') and OPNsense.postfix.general.smtpclient_security != '' %} +{% if OPNsense.postfix.general.smtpclient_security == 'dane' %} +smtp_dns_support_level = dnssec +{% endif %} smtp_tls_security_level = {{ OPNsense.postfix.general.smtpclient_security }} +smtp_tls_loglevel = 1 +{% endif %} +{% if helpers.exists('OPNsense.postfix.general.tls_client_compatibility') %} +{% if OPNsense.postfix.general.tls_client_compatibility == 'modern' %} +smtp_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1, !TLSv1.2 +{% elif OPNsense.postfix.general.tls_client_compatibility == 'intermediate' %} +smtp_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1 +smtp_tls_mandatory_ciphers = medium +{% elif OPNsense.postfix.general.tls_client_compatibility == 'old' %} +smtp_tls_mandatory_protocols = !SSLv2, !SSLv3 +smtp_tls_mandatory_ciphers = low +{% endif %} +smtp_tls_protocols = $smtp_tls_mandatory_protocols +{% if OPNsense.postfix.general.tls_client_compatibility != 'modern' %} +smtp_tls_ciphers = $smtp_tls_mandatory_ciphers +{% endif %} {% endif %} {% if helpers.exists('OPNsense.postfix.general.certificate') and OPNsense.postfix.general.certificate != '' %} smtpd_use_tls = yes +smtpd_tls_auth_only = yes +smtpd_tls_loglevel = 1 +smtpd_tls_received_header = yes smtpd_tls_cert_file = /usr/local/etc/postfix/cert_opn.pem {% endif %} {% if helpers.exists('OPNsense.postfix.general.ca') and OPNsense.postfix.general.ca != '' %} smtpd_tls_CAfile = /usr/local/etc/postfix/ca_opn.pem +{% else %} +smtpd_tls_CAfile = /usr/local/etc/ssl/cert.pem +{% endif %} +{% if helpers.exists('OPNsense.postfix.general.tls_server_compatibility') %} +{% if OPNsense.postfix.general.tls_server_compatibility == 'modern' %} +smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1, !TLSv1.2 +{% elif OPNsense.postfix.general.tls_server_compatibility == 'intermediate' %} +smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1 +smtpd_tls_dh1024_param_file = /usr/local/opnsense/data/OPNsense/Postfix/dh-parameters.2048.rfc7919 +smtpd_tls_mandatory_ciphers = medium +{% elif OPNsense.postfix.general.tls_server_compatibility == 'old' %} +smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3 +smtpd_tls_dh1024_param_file = /usr/local/opnsense/data/OPNsense/Postfix/dh-parameters.2048.rfc7919 +smtpd_tls_mandatory_ciphers = low +{% endif %} +smtpd_tls_protocols = $smtpd_tls_mandatory_protocols +{% if OPNsense.postfix.general.tls_server_compatibility != 'modern' %} +smtpd_tls_ciphers = $smtpd_tls_mandatory_ciphers +{% endif %} +{% if helpers.exists('OPNsense.postfix.general.tls_client_compatibility') or helpers.exists('OPNsense.postfix.general.tls_server_compatibility') %} +tls_low_cipherlist = ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA256:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA +tls_medium_cipherlist = ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384 +{% if OPNsense.postfix.general.tls_server_compatibility == 'old' %} +tls_preempt_cipherlist = yes +{% else %} +tls_preempt_cipherlist = no {% endif %} +{% endif%} +{% endif %} {% if helpers.exists('OPNsense.postfix.general.relayhost') and OPNsense.postfix.general.relayhost != '' %} relayhost = {{ OPNsense.postfix.general.relayhost }} {% endif %} @@ -112,14 +158,18 @@ relayhost = {{ OPNsense.postfix.general.relayhost }} smtp_sasl_auth_enable = yes smtp_sasl_password_maps = hash:/usr/local/etc/postfix/smtp_auth smtp_sasl_security_options = +smtp_sasl_mechanism_filter = !gssapi, !ntlm, !external, static:all +{% endif %} + +{% if helpers.exists('OPNsense.postfix.general.permit_sasl_authenticated') and OPNsense.postfix.general.permit_sasl_authenticated == '1' %} +smtpd_sasl_auth_enable = yes {% endif %} {% if helpers.exists('OPNsense.postfix.antispam.enable_rspamd') and OPNsense.postfix.antispam.enable_rspamd == '1' %} -smtpd_milters = inet:localhost:11332 -non_smtpd_milters = inet:localhost:11332 -milter_protocol = {{ OPNsense.postfix.antispam.milter_rspamd }} -milter_mail_macros = i {mail_addr} {client_addr} {client_name} {auth_authen} -milter_default_action = accept +smtpd_milters = unix:/var/run/rspamd/milter.sock +non_smtpd_milters = $smtpd_milters +milter_protocol = 6 +milter_default_action = {{ OPNsense.postfix.antispam.default_action }} {% endif %} {% if helpers.exists('OPNsense.postfix.general.enforce_recipient_check') and OPNsense.postfix.general.enforce_recipient_check == '1' %} @@ -134,6 +184,18 @@ relay_recipient_maps = hash:/usr/local/etc/postfix/recipient_access {% if helpers.exists('OPNsense.postfix.recipient.recipients.recipient') %} {% do smtpd_recipient_restrictions.append('check_recipient_access hash:/usr/local/etc/postfix/recipient_access') %} {% endif %} +{% if helpers.exists('OPNsense.postfix.general.reject_unknown_client_hostname') and OPNsense.postfix.general.reject_unknown_client_hostname == '1' %} +{% do smtpd_recipient_restrictions.append('reject_unknown_client_hostname') %} +{% endif %} +{% if helpers.exists('OPNsense.postfix.general.reject_non_fqdn_helo_hostname') and OPNsense.postfix.general.reject_non_fqdn_helo_hostname == '1' %} +{% do smtpd_recipient_restrictions.append('reject_non_fqdn_helo_hostname') %} +{% endif %} +{% if helpers.exists('OPNsense.postfix.general.reject_invalid_helo_hostname') and OPNsense.postfix.general.reject_invalid_helo_hostname == '1' %} +{% do smtpd_recipient_restrictions.append('reject_invalid_helo_hostname') %} +{% endif %} +{% if helpers.exists('OPNsense.postfix.general.reject_unknown_helo_hostname') and OPNsense.postfix.general.reject_unknown_helo_hostname == '1' %} +{% do smtpd_recipient_restrictions.append('reject_unknown_helo_hostname') %} +{% endif %} {% if helpers.exists('OPNsense.postfix.general.reject_unauth_pipelining') and OPNsense.postfix.general.reject_unauth_pipelining == '1' %} {% do smtpd_recipient_restrictions.append('reject_unauth_pipelining') %} {% endif %} @@ -161,13 +223,36 @@ relay_recipient_maps = hash:/usr/local/etc/postfix/recipient_access {% if helpers.exists('OPNsense.postfix.general.reject_unauth_destination') and OPNsense.postfix.general.reject_unauth_destination == '1' %} {% do smtpd_recipient_restrictions.append('reject_unauth_destination') %} {% endif %} +{% if helpers.exists('OPNsense.postfix.general.reject_unverified_recipient') and OPNsense.postfix.general.reject_unverified_recipient == '1' %} +{% do smtpd_recipient_restrictions.append('reject_unverified_recipient') %} +{% endif %} {% if smtpd_recipient_restrictions|length >= 1 %} smtpd_recipient_restrictions = {{ smtpd_recipient_restrictions | join(', ') }} {% endif %} +{% if helpers.exists('OPNsense.postfix.general.delay_warning_time') and OPNsense.postfix.general.delay_warning_time != '0' %} +delay_warning_time = {{ OPNsense.postfix.general.delay_warning_time }}h +{% endif %} + smtpd_helo_required = yes +{% if helpers.exists('OPNsense.postfix.general.extensive_helo_restrictions') and OPNsense.postfix.general.extensive_helo_restrictions == '1' %} +smtpd_helo_restrictions = + permit_mynetworks, + permit_sasl_authenticated, + reject_invalid_helo_hostname, + reject_non_fqdn_hostname, + reject_unknown_hostname +{% endif %} +{% if helpers.exists('OPNsense.postfix.general.extensive_sender_restrictions') and OPNsense.postfix.general.extensive_sender_restrictions == '1' %} +smtpd_sender_restrictions = + permit_mynetworks, + permit_sasl_authenticated, + reject_unknown_reverse_client_hostname, + reject_unknown_sender_domain, + reject_non_fqdn_sender +{% endif %} syslog_facility = mail syslog_name = postfix diff --git a/mail/postfix/src/opnsense/service/templates/OPNsense/Postfix/postfix b/mail/postfix/src/opnsense/service/templates/OPNsense/Postfix/postfix index 1d2cfd8551..65720e2cc6 100644 --- a/mail/postfix/src/opnsense/service/templates/OPNsense/Postfix/postfix +++ b/mail/postfix/src/opnsense/service/templates/OPNsense/Postfix/postfix @@ -1,5 +1,5 @@ {% if helpers.exists('OPNsense.postfix.general.enabled') and OPNsense.postfix.general.enabled == '1' %} -postfix_var_script="/usr/local/opnsense/scripts/OPNsense/Postfix/setup.sh" +postfix_setup="/usr/local/opnsense/scripts/OPNsense/Postfix/setup.sh" postfix_enable="YES" {% else %} postfix_enable="NO" diff --git a/mail/postfix/src/opnsense/service/templates/OPNsense/Syslog/local/postfix.conf b/mail/postfix/src/opnsense/service/templates/OPNsense/Syslog/local/postfix.conf new file mode 100644 index 0000000000..caf523956a --- /dev/null +++ b/mail/postfix/src/opnsense/service/templates/OPNsense/Syslog/local/postfix.conf @@ -0,0 +1,6 @@ +################################################################### +# Local syslog-ng configuration filter definition [postfix]. +################################################################### +filter f_local_postfix { + program("postfix"); +}; diff --git a/mail/rspamd/Makefile b/mail/rspamd/Makefile index fef81f2f23..a1e2148ce0 100644 --- a/mail/rspamd/Makefile +++ b/mail/rspamd/Makefile @@ -1,5 +1,6 @@ PLUGIN_NAME= rspamd -PLUGIN_VERSION= 1.9 +PLUGIN_VERSION= 1.13 +PLUGIN_REVISION= 2 PLUGIN_COMMENT= Protect your network from spam PLUGIN_DEPENDS= rspamd PLUGIN_MAINTAINER= franz.fabian.94@gmail.com diff --git a/mail/rspamd/pkg-descr b/mail/rspamd/pkg-descr index 32b7704d37..a1d6d33b66 100644 --- a/mail/rspamd/pkg-descr +++ b/mail/rspamd/pkg-descr @@ -5,6 +5,24 @@ lua. Plugin Changelog ---------------- +1.13 + +* Make local whitelist by e-mail address possible (contributed by itNGO) +* Fix typo in "whitelisted_ip" option (contributed by Alexander Riedel) +* Add phishing exclusion (contributed by Makss39) + +1.12 + +* Adjusting the multimap setting to make the multimap whitelist work + +1.11 + +* Fix Milter Protocol by binding to Unix Sockets + +1.10 + +* Add nameserver option + 1.9 * Add History Rows field diff --git a/mail/rspamd/src/opnsense/mvc/app/controllers/OPNsense/Rspamd/forms/settings.xml b/mail/rspamd/src/opnsense/mvc/app/controllers/OPNsense/Rspamd/forms/settings.xml index b027f57b46..b7a8d1b780 100644 --- a/mail/rspamd/src/opnsense/mvc/app/controllers/OPNsense/Rspamd/forms/settings.xml +++ b/mail/rspamd/src/opnsense/mvc/app/controllers/OPNsense/Rspamd/forms/settings.xml @@ -62,6 +62,15 @@ true Set the number of rows to be displayed in rspamd UI. + + rspamd.general.nameserver + + select_multiple + + true + true + Set the nameservers for resolving DNS requests. + @@ -288,6 +297,14 @@ text Give a URL where to retrieve the Phishtank feed. + + rspamd.phishing.exclusion + + select_multiple + + true + The exclusion map should only contain a list of host names without a scheme and path. + diff --git a/mail/rspamd/src/opnsense/mvc/app/models/OPNsense/Rspamd/RSpamd.xml b/mail/rspamd/src/opnsense/mvc/app/models/OPNsense/Rspamd/RSpamd.xml index 483d55d6ef..b327dff800 100644 --- a/mail/rspamd/src/opnsense/mvc/app/models/OPNsense/Rspamd/RSpamd.xml +++ b/mail/rspamd/src/opnsense/mvc/app/models/OPNsense/Rspamd/RSpamd.xml @@ -1,505 +1,497 @@ - //OPNsense/Rspamd - rspamd anti spam filter - 1.0.1 - - - - 0 - Y - - - 0 - Y - - - 0 - Y - - - N - 1 - 999 - - - This field must be bigger than the subject score. - ComparedToFieldConstraint - subjectscore - gt - - - This field must be bigger than the reject score. - ComparedToFieldConstraint - headerscore - gt - - - This field must be bigger than the greylist score. - ComparedToFieldConstraint - greylistscore - gt - - - - - N - 1 - 999 - - - This field must be lower than the subject score. - ComparedToFieldConstraint - subjectscore - lt - - - This field must be lower than the reject score. - ComparedToFieldConstraint - rejectscore - lt - - - This field must be bigger than the greylist score. - ComparedToFieldConstraint - greylistscore - gt - - - - - N - 1 - 999 - - - This field must be lower than the header score. - ComparedToFieldConstraint - headerscore - lt - - - This field must be lower than the reject score. - ComparedToFieldConstraint - rejectscore - lt - - - This field must be lower than the subject score. - ComparedToFieldConstraint - subjectscore - lt - - - - - N - 1 - 999 - - - This field must be bigger than the header score. - ComparedToFieldConstraint - headerscore - gt - - - This field must be lower than the reject score. - ComparedToFieldConstraint - rejectscore - lt - - - This field must be bigger than the subject score. - ComparedToFieldConstraint - greylistscore - gt - - - - - N - - - 200 - Y - 1 - 100000 - Choose a value between 1 and 100000. - - - - - - 0 - Y - - - 0 - Y - - - 0 - Y - - - 0 - Y - - - 0 - Y - - - 0 - Y - - - /[a-z0-9\.\-_@,]+/i - N - - - - - - N - 1 - - - N - 1 - - - N - - - 1 - N - 32 - A valid IPv4 mask must be between 1 and 32 bits. - 19 - - - 1 - N - 128 - 64 - A valid IPv6 mask must be between 1 and 128 bits. 64 bits are recommended as this is the recommended subnet size in IPv6. - - - N - /^([a-fA-F0-9\.:\[\]\/]*?,)*([a-fA-F0-9\.:\[\]\/]*)$/ - - - - - - 1 - N - A valid cache size must be set. - - - 1 - N - A valid cache expiration must be set. - - - 1 - N - A valid time jitter must be set. - - - 0 - Y - - - 0 - Y - - - - 1 - Y - - - 0 - Y - - - 0 - Y - - - 0 - Y - - - 1 - Y - - - 1 - Y - - - 0 - Y - - - header - Y - -
    Header
    - Envelope -
    -
    - - 1 - Y - -
    - - - - 0 - Y - - - 1 - N - 86400 - A valid cache expiration must be set. - - - - - - 0 - Y - - - - N - - - 0 - Y - - - 0 - Y - - - - N - - - - - - - 1 - N - The count value must be a positive number. - - - - m - Y - - Seconds - Minutes - Hours - - - - - - 1 - N - The count value must be a positive number. - - - - m - Y - - Seconds - Minutes - Hours - - - - - - 1 - N - The count value must be a positive number. - - - - m - Y - - Seconds - Minutes - Hours - - - - - - 1 - N - The count value must be a positive number. - - - - m - Y - - Seconds - Minutes - Hours - - - - - - 1 - N - The count value must be a positive number. - - - - m - Y - - Seconds - Minutes - Hours - - - - - - 1 - N - The count value must be a positive number. - - - - m - Y - - Seconds - Minutes - Hours - - - - - postmaster,mailer-daemon - - - 1 - Y - 20 - - - - - - 0 - Y - - - 0 - Y - - - 1 - Y - - - N - - - - - - 1 - N - 2 - A valid cache size in kilobytes must be set. - - - 1 - N - A valid expiration time must be set. - - - - - - 1 - Y - - - 1 - Y - - - 1 - 20000000 - N - A valid maximum size in bytes must be set. - - - N - - - - - - N - - - N - - - - - - N - exe,dll,scr,com,cmd,js,bat,vbs,ps1,bat,cpl,lnk,msi,msp,reg - - - N - - -
    + //OPNsense/Rspamd + rspamd anti spam filter + 1.0.2 + + + + 0 + Y + + + 0 + Y + + + 0 + Y + + + N + 1 + 999 + + + This field must be bigger than the subject score. + ComparedToFieldConstraint + subjectscore + gt + + + This field must be bigger than the reject score. + ComparedToFieldConstraint + headerscore + gt + + + This field must be bigger than the greylist score. + ComparedToFieldConstraint + greylistscore + gt + + + + + N + 1 + 999 + + + This field must be lower than the subject score. + ComparedToFieldConstraint + subjectscore + lt + + + This field must be lower than the reject score. + ComparedToFieldConstraint + rejectscore + lt + + + This field must be bigger than the greylist score. + ComparedToFieldConstraint + greylistscore + gt + + + + + N + 1 + 999 + + + This field must be lower than the header score. + ComparedToFieldConstraint + headerscore + lt + + + This field must be lower than the reject score. + ComparedToFieldConstraint + rejectscore + lt + + + This field must be lower than the subject score. + ComparedToFieldConstraint + subjectscore + lt + + + + + N + 1 + 999 + + + This field must be bigger than the header score. + ComparedToFieldConstraint + headerscore + gt + + + This field must be lower than the reject score. + ComparedToFieldConstraint + rejectscore + lt + + + This field must be bigger than the subject score. + ComparedToFieldConstraint + greylistscore + gt + + + + + N + + + 200 + Y + 1 + 100000 + Choose a value between 1 and 100000. + + + 127.0.0.1 + Y + , + Y + + + + + 0 + Y + + + 0 + Y + + + 0 + Y + + + 0 + Y + + + 0 + Y + + + 0 + Y + + + /[a-z0-9\.\-_@,]+/i + N + + + + + N + 1 + + + N + 1 + + + N + + + 1 + N + 32 + A valid IPv4 mask must be between 1 and 32 bits. + 19 + + + 1 + N + 128 + 64 + A valid IPv6 mask must be between 1 and 128 bits. 64 bits are recommended as this is the recommended subnet size in IPv6. + + + N + /^([a-fA-F0-9\.:\[\]\/]*?,)*([a-fA-F0-9\.:\[\]\/]*)$/ + + + + + 1 + N + A valid cache size must be set. + + + 1 + N + A valid cache expiration must be set. + + + 1 + N + A valid time jitter must be set. + + + 0 + Y + + + 0 + Y + + + + 1 + Y + + + 0 + Y + + + 0 + Y + + + 0 + Y + + + 1 + Y + + + 1 + Y + + + 0 + Y + + + header + Y + +
    Header
    + Envelope +
    +
    + + 1 + Y + +
    + + + 0 + Y + + + 1 + N + 86400 + A valid cache expiration must be set. + + + + + 0 + Y + + + + 0 + Y + + + 0 + Y + + + + N + + + + + + 1 + N + The count value must be a positive number. + + + + m + Y + + Seconds + Minutes + Hours + + + + + + 1 + N + The count value must be a positive number. + + + + m + Y + + Seconds + Minutes + Hours + + + + + + 1 + N + The count value must be a positive number. + + + + m + Y + + Seconds + Minutes + Hours + + + + + + 1 + N + The count value must be a positive number. + + + + m + Y + + Seconds + Minutes + Hours + + + + + + 1 + N + The count value must be a positive number. + + + + m + Y + + Seconds + Minutes + Hours + + + + + + 1 + N + The count value must be a positive number. + + + + m + Y + + Seconds + Minutes + Hours + + + + + postmaster,mailer-daemon + + + 1 + Y + 20 + + + + + 0 + Y + + + 0 + Y + + + 1 + Y + + + N + + + + + 1 + N + 2 + A valid cache size in kilobytes must be set. + + + 1 + N + A valid expiration time must be set. + + + + + 1 + Y + + + 1 + Y + + + 1 + 20000000 + N + A valid maximum size in bytes must be set. + + + N + + + + + N + + + N + + + + + N + exe,dll,scr,com,cmd,js,bat,vbs,ps1,bat,cpl,lnk,msi,msp,reg + + + N + + +
    diff --git a/mail/rspamd/src/opnsense/service/conf/actions.d/actions_rspamd.conf b/mail/rspamd/src/opnsense/service/conf/actions.d/actions_rspamd.conf index e4711138a0..1d8c575d90 100644 --- a/mail/rspamd/src/opnsense/service/conf/actions.d/actions_rspamd.conf +++ b/mail/rspamd/src/opnsense/service/conf/actions.d/actions_rspamd.conf @@ -1,23 +1,23 @@ [start] -command:/usr/local/opnsense/scripts/rspamd/setup.sh;/usr/local/etc/rc.d/rspamd start +command:/usr/local/etc/rc.d/rspamd start parameters: type:script message:starting rspamd [stop] -command:/usr/local/etc/rc.d/rspamd onestop +command:/usr/local/etc/rc.d/rspamd stop parameters: type:script message:stopping rspamd [restart] -command:/usr/local/opnsense/scripts/rspamd/setup.sh;/usr/local/etc/rc.d/rspamd restart +command:/usr/local/etc/rc.d/rspamd restart parameters: type:script message:restarting rspamd [status] -command:/usr/local/etc/rc.d/rspamd status;exit 0 +command:/usr/local/etc/rc.d/rspamd status; exit 0 parameters: type:script_output message:request rspamd status diff --git a/mail/rspamd/src/opnsense/service/templates/OPNsense/Rspamd/+TARGETS b/mail/rspamd/src/opnsense/service/templates/OPNsense/Rspamd/+TARGETS index f4db33f7b9..dc9db3cbac 100644 --- a/mail/rspamd/src/opnsense/service/templates/OPNsense/Rspamd/+TARGETS +++ b/mail/rspamd/src/opnsense/service/templates/OPNsense/Rspamd/+TARGETS @@ -13,6 +13,7 @@ surbl-whitelist.inc.local:/var/db/rspamd/surbl-whitelist.inc.local greylist.conf:/usr/local/etc/rspamd/local.d/greylist.conf greylist_ip.wl:/usr/local/etc/rspamd/local.d/greylist_ip.wl phishing.conf:/usr/local/etc/rspamd/local.d/phishing.conf +phishing_whitelist.inc:/usr/local/etc/rspamd/local.d/phishing_whitelist.inc milter_headers.conf:/usr/local/etc/rspamd/local.d/milter_headers.conf multimap.conf:/usr/local/etc/rspamd/local.d/multimap.conf mx_check.conf:/usr/local/etc/rspamd/local.d/mx_check.conf @@ -21,3 +22,6 @@ redis.conf:/usr/local/etc/rspamd/local.d/redis.conf spamtrap-map:/usr/local/etc/rspamd/maps.d/spamtrap.map classifier-bayes.conf:/usr/local/etc/rspamd/local.d/classifier-bayes.conf history_redis.conf:/usr/local/etc/rspamd/local.d/history_redis.conf +options.inc:/usr/local/etc/rspamd/local.d/options.inc +worker-normal.inc:/usr/local/etc/rspamd/local.d/worker-normal.inc +worker-proxy.inc:/usr/local/etc/rspamd/local.d/worker-proxy.inc diff --git a/mail/rspamd/src/opnsense/service/templates/OPNsense/Rspamd/greylist.conf b/mail/rspamd/src/opnsense/service/templates/OPNsense/Rspamd/greylist.conf index 42e19b416a..0d359054db 100644 --- a/mail/rspamd/src/opnsense/service/templates/OPNsense/Rspamd/greylist.conf +++ b/mail/rspamd/src/opnsense/service/templates/OPNsense/Rspamd/greylist.conf @@ -11,5 +11,5 @@ action = "soft reject"; # default greylisted action ipv4_mask = {{ OPNsense.Rspamd.graylist.ipv4mask|default('19') }}; ipv6_mask = {{ OPNsense.Rspamd.graylist.ipv6mask|default('64') }}; - whitelist_ip = "/usr/local/etc/rspamd/local.d/greylist_ip.wl"; + whitelisted_ip = "/usr/local/etc/rspamd/local.d/greylist_ip.wl"; {% endif %} diff --git a/mail/rspamd/src/opnsense/service/templates/OPNsense/Rspamd/multimap.conf b/mail/rspamd/src/opnsense/service/templates/OPNsense/Rspamd/multimap.conf index f357673dc1..8926ee13d2 100644 --- a/mail/rspamd/src/opnsense/service/templates/OPNsense/Rspamd/multimap.conf +++ b/mail/rspamd/src/opnsense/service/templates/OPNsense/Rspamd/multimap.conf @@ -8,13 +8,22 @@ extension_blacklist { filter = "extension"; map = "/${LOCAL_CONFDIR}/local.d/bad_file_extensions.map"; symbol = "FILENAME_BLACKLISTED"; - action = "reject"; -} + score 1000; + } WHITELIST_SENDER_DOMAIN { type = "from"; filter = "email:domain"; map = "/${LOCAL_CONFDIR}/local.d/whitelist_sender_domains.map"; - score = -50.0 + score = -1000; } + +local_wl_from { + type = "from"; + map = "$CONFDIR/maps.d/local_wl_from.inc"; + symbol = "LOCAL_WL_FROM"; + description = "Whitelist map for LOCAL_WL_FROM"; + score = -1000; + } + {% endif %} diff --git a/mail/rspamd/src/opnsense/service/templates/OPNsense/Rspamd/options.inc b/mail/rspamd/src/opnsense/service/templates/OPNsense/Rspamd/options.inc new file mode 100644 index 0000000000..eec227c1bc --- /dev/null +++ b/mail/rspamd/src/opnsense/service/templates/OPNsense/Rspamd/options.inc @@ -0,0 +1,7 @@ +# Please don't modify this file as your changes might be overwritten with +# the next update. +# + +dns { + nameserver = [{{ "'" + ("','".join(OPNsense.Rspamd.general.nameserver.split(','))) + "'" }}]; +} diff --git a/mail/rspamd/src/opnsense/service/templates/OPNsense/Rspamd/phishing_whitelist.inc b/mail/rspamd/src/opnsense/service/templates/OPNsense/Rspamd/phishing_whitelist.inc new file mode 100644 index 0000000000..a46347d158 --- /dev/null +++ b/mail/rspamd/src/opnsense/service/templates/OPNsense/Rspamd/phishing_whitelist.inc @@ -0,0 +1,5 @@ +{% if helpers.exists('OPNsense.Rspamd.general.enabled') and OPNsense.Rspamd.general.enabled == '1' and helpers.exists('OPNsense.Rspamd.phishing.exclusion') and OPNsense.Rspamd.phishing.exclusion != '' %} +{% for sender in OPNsense.Rspamd.phishing.exclusion.split(',') %} +{{ sender }} +{% endfor %} +{% endif %} diff --git a/mail/rspamd/src/opnsense/service/templates/OPNsense/Rspamd/rspamd b/mail/rspamd/src/opnsense/service/templates/OPNsense/Rspamd/rspamd index ca1a5ce4e4..e9cf0d6686 100644 --- a/mail/rspamd/src/opnsense/service/templates/OPNsense/Rspamd/rspamd +++ b/mail/rspamd/src/opnsense/service/templates/OPNsense/Rspamd/rspamd @@ -1,5 +1,5 @@ {% if helpers.exists('OPNsense.Rspamd.general.enabled') and OPNsense.Rspamd.general.enabled == '1' %} -rspamd_var_script="/usr/local/opnsense/scripts/rspamd/setup.sh" +rspamd_setup="/usr/local/opnsense/scripts/rspamd/setup.sh" rspamd_enable="YES" {% else %} rspamd_enable="NO" diff --git a/mail/rspamd/src/opnsense/service/templates/OPNsense/Rspamd/worker-normal.inc b/mail/rspamd/src/opnsense/service/templates/OPNsense/Rspamd/worker-normal.inc new file mode 100644 index 0000000000..68e0849dca --- /dev/null +++ b/mail/rspamd/src/opnsense/service/templates/OPNsense/Rspamd/worker-normal.inc @@ -0,0 +1,3 @@ +{% if helpers.exists('OPNsense.Rspamd.general.enabled') and OPNsense.Rspamd.general.enabled == '1' %} +bind_socket = "/var/run/rspamd/normal.sock mode=0666 owner=rspamd"; +{% endif %} diff --git a/mail/rspamd/src/opnsense/service/templates/OPNsense/Rspamd/worker-proxy.inc b/mail/rspamd/src/opnsense/service/templates/OPNsense/Rspamd/worker-proxy.inc new file mode 100644 index 0000000000..e6941245cf --- /dev/null +++ b/mail/rspamd/src/opnsense/service/templates/OPNsense/Rspamd/worker-proxy.inc @@ -0,0 +1,7 @@ +{% if helpers.exists('OPNsense.Rspamd.general.enabled') and OPNsense.Rspamd.general.enabled == '1' %} +upstream "local" { + default = yes; + hosts = "/var/run/rspamd/normal.sock"; +} +bind_socket = "/var/run/rspamd/milter.sock mode=0666 owner=rspamd"; +{% endif %} diff --git a/misc/theme-advanced/Makefile b/misc/theme-advanced/Makefile new file mode 100644 index 0000000000..2e92be9e10 --- /dev/null +++ b/misc/theme-advanced/Makefile @@ -0,0 +1,8 @@ +PLUGIN_NAME= theme-advanced +PLUGIN_VERSION= 1.1 +PLUGIN_COMMENT= Theme based on AdvancedTomato GUI +PLUGIN_MAINTAINER= jacky@prahec.com +PLUGIN_WWW= https://prahec.com/ +PLUGIN_NO_ABI= yes + +.include "../../Mk/plugins.mk" diff --git a/misc/theme-advanced/pkg-descr b/misc/theme-advanced/pkg-descr new file mode 100644 index 0000000000..10d6368848 --- /dev/null +++ b/misc/theme-advanced/pkg-descr @@ -0,0 +1 @@ +Advanced - Modern theme based on AdvancedTomato project by the original author diff --git a/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/fonts/bootstrap/glyphicons-halflings-regular.eot b/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/fonts/bootstrap/glyphicons-halflings-regular.eot new file mode 100644 index 0000000000..b93a4953ff Binary files /dev/null and b/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/fonts/bootstrap/glyphicons-halflings-regular.eot differ diff --git a/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/fonts/bootstrap/glyphicons-halflings-regular.svg b/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/fonts/bootstrap/glyphicons-halflings-regular.svg new file mode 100644 index 0000000000..94fb5490a2 --- /dev/null +++ b/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/fonts/bootstrap/glyphicons-halflings-regular.svg @@ -0,0 +1,288 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/fonts/bootstrap/glyphicons-halflings-regular.ttf b/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/fonts/bootstrap/glyphicons-halflings-regular.ttf new file mode 100644 index 0000000000..1413fc609a Binary files /dev/null and b/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/fonts/bootstrap/glyphicons-halflings-regular.ttf differ diff --git a/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/fonts/bootstrap/glyphicons-halflings-regular.woff b/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/fonts/bootstrap/glyphicons-halflings-regular.woff new file mode 100644 index 0000000000..9e612858f8 Binary files /dev/null and b/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/fonts/bootstrap/glyphicons-halflings-regular.woff differ diff --git a/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/stylesheets/_bootstrap-compass.scss b/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/stylesheets/_bootstrap-compass.scss new file mode 100644 index 0000000000..82706c47c5 --- /dev/null +++ b/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/stylesheets/_bootstrap-compass.scss @@ -0,0 +1,7 @@ +@function twbs-font-path($path) { + @return font-url($path, true); +} + +@function twbs-image-path($path) { + @return image-url($path, true); +} diff --git a/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/stylesheets/_bootstrap-mincer.scss b/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/stylesheets/_bootstrap-mincer.scss new file mode 100644 index 0000000000..34132501f2 --- /dev/null +++ b/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/stylesheets/_bootstrap-mincer.scss @@ -0,0 +1,17 @@ +// Mincer asset helper functions +// +// This must be imported into a .css.ejs.scss file. +// Then, <% %>-interpolations will be parsed as strings by Sass, and evaluated by EJS after Sass compilation. + + +@function twbs-font-path($path) { + // do something like following + // from "path/to/font.ext#suffix" to "<%- asset_path(path/to/font.ext)) + #suffix %>" + // from "path/to/font.ext?#suffix" to "<%- asset_path(path/to/font.ext)) + ?#suffix %>" + // or from "path/to/font.ext" just "<%- asset_path(path/to/font.ext)) %>" + @return "<%- asset_path('#{$path}'.replace(/[#?].*$/, '')) + '#{$path}'.replace(/(^[^#?]*)([#?]?.*$)/, '$2') %>"; +} + +@function twbs-image-path($file) { + @return "<%- asset_path('#{$file}') %>"; +} diff --git a/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/stylesheets/_bootstrap-sprockets.scss b/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/stylesheets/_bootstrap-sprockets.scss new file mode 100644 index 0000000000..7d3069280e --- /dev/null +++ b/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/stylesheets/_bootstrap-sprockets.scss @@ -0,0 +1,7 @@ +@function twbs-font-path($path) { + @return font-path($path); +} + +@function twbs-image-path($path) { + @return image-path($path); +} diff --git a/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/stylesheets/bootstrap-dialog.less b/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/stylesheets/bootstrap-dialog.less new file mode 100644 index 0000000000..284a99402f --- /dev/null +++ b/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/stylesheets/bootstrap-dialog.less @@ -0,0 +1,137 @@ +.modal-backdrop { + z-index: -1; +} + +.bootstrap-dialog { + + .modal-header { + border-top-left-radius: 4px; + border-top-right-radius: 4px; + } + .bootstrap-dialog-title { + color: #fff; + display: inline-block; + } + .bootstrap-dialog-button-icon { + margin-right: 3px; + } + .bootstrap-dialog-close-button { + float: right; + filter:alpha(opacity=90); + -moz-opacity:0.9; + -khtml-opacity: 0.9; + opacity: 0.9; + &:hover { + cursor: pointer; + filter: alpha(opacity=100); + -moz-opacity: 1; + -khtml-opacity: 1; + opacity: 1; + } + } + + /* dialog types */ + &.type-default { + .modal-header { + background-color: #fff; + } + .bootstrap-dialog-title { + color: #333; + } + } + + &.type-info { + .modal-header { + background-color: #B0CDDB; + } + } + + &.type-primary { + .modal-header { + background-color: #51aded; + } + } + + &.type-success { + .modal-header { + background-color: #7ebc59; + } + } + + &.type-warning { + .modal-header { + background-color: #f0ad4e; + } + } + + &.type-danger { + .modal-header { + background-color: #EE451F; + } + } + + &.size-large { + .bootstrap-dialog-title { + font-size: 24px; + } + .bootstrap-dialog-close-button { + font-size: 30px; + } + .bootstrap-dialog-message { + font-size: 18px; + } + } + + /** + * Icon animation + * Copied from font-awesome: http://fontawesome.io/ + **/ + .icon-spin { + display: inline-block; + -moz-animation: spin 2s infinite linear; + -o-animation: spin 2s infinite linear; + -webkit-animation: spin 2s infinite linear; + animation: spin 2s infinite linear; + } + @-moz-keyframes spin { + 0% { + -moz-transform: rotate(0deg); + } + 100% { + -moz-transform: rotate(359deg); + } + } + @-webkit-keyframes spin { + 0% { + -webkit-transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(359deg); + } + } + @-o-keyframes spin { + 0% { + -o-transform: rotate(0deg); + } + 100% { + -o-transform: rotate(359deg); + } + } + @-ms-keyframes spin { + 0% { + -ms-transform: rotate(0deg); + } + 100% { + -ms-transform: rotate(359deg); + } + } + @keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(359deg); + } + } + /** End of icon animation **/ +} diff --git a/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/stylesheets/bootstrap-select/less/bootstrap-select.less b/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/stylesheets/bootstrap-select/less/bootstrap-select.less new file mode 100644 index 0000000000..49f13a7021 --- /dev/null +++ b/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/stylesheets/bootstrap-select/less/bootstrap-select.less @@ -0,0 +1,348 @@ +@import "variables"; + +// Mixins +.cursor-disabled() { + cursor: not-allowed; +} + +// Rules +.bootstrap-select { + width: 348px \0; /*IE9 and below*/ + + // The selectpicker button + > .dropdown-toggle { + width: 100%; + padding-right: 25px; + z-index: 1; + } + + > select { + position: absolute !important; + bottom: 0; + left: 50%; + width: 0.11px !important; + height: 100% !important; + padding: 0 !important; + opacity: 0 !important; + border: none; + + &.mobile-device { + top: 0; + left: 0; + display: block !important; + width: 100% !important; + z-index: 2; + } + } + + // Error display + .has-error & .dropdown-toggle, + .error & .dropdown-toggle { + border-color: @color-red-error; + } + + &.fit-width { + width: auto !important; + } + + &:not([class*="col-"]):not([class*="form-control"]):not(.input-group-btn) { + width: @width-default; + } + + .dropdown-toggle:focus { + outline: thin dotted #333333 !important; + outline: 5px auto -webkit-focus-ring-color !important; + outline-offset: -2px; + } +} + +.bootstrap-select.form-control { + margin-bottom: 0; + padding: 0; + border: none; + + &:not([class*="col-"]) { + width: 100%; + } + + &.input-group-btn { + z-index: auto; + } +} + +// The selectpicker components +.bootstrap-select.btn-group { + &:not(.input-group-btn), + &[class*="col-"] { + float: none; + display: inline-block; + margin-left: 0; + } + + // Forces the pull to the right, if necessary + &, + &[class*="col-"], + .row &[class*="col-"] { + &.dropdown-menu-right { + float: right; + } + } + + .form-inline &, + .form-horizontal &, + .form-group & { + margin-bottom: 0; + } + + .form-group-lg &.form-control, + .form-group-sm &.form-control { + padding: 0; + } + + // Set the width of the live search (and any other form control within an inline form) + // see https://github.com/silviomoreto/bootstrap-select/issues/685 + .form-inline & .form-control { + width: 100%; + } + + &.disabled, + > .disabled { + .cursor-disabled(); + + &:focus { + outline: none !important; + } + } + + &.bs-container { + position: absolute; + + .dropdown-menu { + z-index: @zindex-select-dropdown; + } + } + + // The selectpicker button + .dropdown-toggle { + .filter-option { + display: inline-block; + overflow: hidden; + width: 100%; + text-align: left; + } + + .caret { + position: absolute; + top: 50%; + right: 12px; + margin-top: -2px; + vertical-align: middle; + } + } + + &[class*="col-"] .dropdown-toggle { + width: 100%; + } + + // The selectpicker dropdown + .dropdown-menu { + min-width: 100%; + box-sizing: border-box; + + &.inner { + position: static; + float: none; + border: 0; + padding: 0; + margin: 0; + border-radius: 0; + box-shadow: none; + } + + li { + position: relative; + + &.active small { + color: #fff; + } + + &.disabled a { + .cursor-disabled(); + } + + a { + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + + &.opt { + position: relative; + padding-left: 2.25em; + } + + span.check-mark { + display: none; + } + + span.text { + display: inline-block; + } + } + + small { + padding-left: 0.5em; + } + } + + .notify { + position: absolute; + bottom: 5px; + width: 96%; + margin: 0 2%; + min-height: 26px; + padding: 3px 5px; + background: rgb(245, 245, 245); + border: 1px solid rgb(227, 227, 227); + box-shadow: inset 0 1px 1px fade(rgb(0, 0, 0), 5%); + pointer-events: none; + opacity: 0.9; + box-sizing: border-box; + } + } + + .no-results { + padding: 3px; + background: #f5f5f5; + margin: 0 5px; + white-space: nowrap; + } + + &.fit-width .dropdown-toggle { + .filter-option { + position: static; + } + + .caret { + position: static; + top: auto; + margin-top: -1px; + } + } + + &.show-tick .dropdown-menu li { + &.selected a span.check-mark { + position: absolute; + display: inline-block; + right: 15px; + margin-top: 5px; + } + + a span.text { + margin-right: 34px; + } + } +} + +.bootstrap-select.show-menu-arrow { + &.open > .dropdown-toggle { + z-index: (@zindex-select-dropdown + 1); + } + + .dropdown-toggle { + &:before { + content: ''; + border-left: 7px solid transparent; + border-right: 7px solid transparent; + border-bottom: 7px solid @color-grey-arrow; + position: absolute; + bottom: -4px; + left: 9px; + display: none; + } + + &:after { + content: ''; + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-bottom: 6px solid white; + position: absolute; + bottom: -4px; + left: 10px; + display: none; + } + } + + &.dropup .dropdown-toggle { + &:before { + bottom: auto; + top: -3px; + border-top: 7px solid @color-grey-arrow; + border-bottom: 0; + } + + &:after { + bottom: auto; + top: -3px; + border-top: 6px solid white; + border-bottom: 0; + } + } + + &.pull-right .dropdown-toggle { + &:before { + right: 12px; + left: auto; + } + + &:after { + right: 13px; + left: auto; + } + } + + &.open > .dropdown-toggle { + &:before, + &:after { + display: block; + } + } +} + +.bs-searchbox, +.bs-actionsbox, +.bs-donebutton { + padding: 4px 8px; +} + +.bs-actionsbox { + width: 100%; + box-sizing: border-box; + + & .btn-group button { + width: 50%; + } +} + +.bs-donebutton { + float: left; + width: 100%; + box-sizing: border-box; + + & .btn-group button { + width: 100%; + } +} + +.bs-searchbox { + & + .bs-actionsbox { + padding: 0 8px 4px; + } + + & .form-control { + margin-bottom: 0; + width: 100%; + float: none; + } +} diff --git a/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/stylesheets/bootstrap-select/less/variables.less b/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/stylesheets/bootstrap-select/less/variables.less new file mode 100644 index 0000000000..97a510c23a --- /dev/null +++ b/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/stylesheets/bootstrap-select/less/variables.less @@ -0,0 +1,7 @@ +@color-red-error: rgb(185, 74, 72); +@color-blue-hover: rgba(100, 177, 216, 0.4); +@color-grey-arrow: rgba(204, 204, 204, 0.2); + +@width-default: 348px; // 3 960px-grid columns + +@zindex-select-dropdown: 1035; // must be lower than a modal background (1040) but higher than the fixed navbar (1030) diff --git a/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/stylesheets/bootstrap-select/sass/bootstrap-select.css b/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/stylesheets/bootstrap-select/sass/bootstrap-select.css new file mode 100644 index 0000000000..b6c2903d8d --- /dev/null +++ b/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/stylesheets/bootstrap-select/sass/bootstrap-select.css @@ -0,0 +1 @@ +@keyframes bs-notify-fadeOut{0%{opacity:0.9}100%{opacity:0}}select.bs-select-hidden,.bootstrap-select>select.bs-select-hidden,select.selectpicker{display:none !important}.bootstrap-select{width:220px \0;vertical-align:middle}.bootstrap-select>.dropdown-toggle{position:relative;width:100%;text-align:right;white-space:nowrap;display:inline-flex;align-items:center;justify-content:space-between}.bootstrap-select>.dropdown-toggle:after{margin-top:-1px}.bootstrap-select>.dropdown-toggle.bs-placeholder,.bootstrap-select>.dropdown-toggle.bs-placeholder:hover,.bootstrap-select>.dropdown-toggle.bs-placeholder:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder:active{color:#999}.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-primary,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-primary:hover,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-primary:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-primary:active,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-secondary,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-secondary:hover,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-secondary:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-secondary:active,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-success,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-success:hover,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-success:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-success:active,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-danger,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-danger:hover,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-danger:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-danger:active,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-info,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-info:hover,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-info:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-info:active,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-dark,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-dark:hover,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-dark:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-dark:active{color:rgba(255,255,255,0.5)}.bootstrap-select>select{position:absolute !important;bottom:0;left:50%;display:block !important;width:0.5px !important;height:100% !important;padding:0 !important;opacity:0 !important;border:none;z-index:0 !important}.bootstrap-select>select.mobile-device{top:0;left:0;display:block !important;width:100% !important;z-index:2 !important}.has-error .bootstrap-select .dropdown-toggle,.error .bootstrap-select .dropdown-toggle,.bootstrap-select.is-invalid .dropdown-toggle,.was-validated .bootstrap-select select:invalid+.dropdown-toggle{border-color:#b94a48}.bootstrap-select.is-valid .dropdown-toggle,.was-validated .bootstrap-select select:valid+.dropdown-toggle{border-color:#28a745}.bootstrap-select.fit-width{width:auto !important}.bootstrap-select:not([class*="col-"]):not([class*="form-control"]):not(.input-group-btn){width:220px}.bootstrap-select>select.mobile-device:focus+.dropdown-toggle{outline:thin dotted #333333 !important;outline:5px auto -webkit-focus-ring-color !important;outline-offset:-2px}.bootstrap-select.form-control{margin-bottom:0;padding:0;border:none;height:auto}:not(.input-group)>.bootstrap-select.form-control:not([class*="col-"]){width:100%}.bootstrap-select.form-control.input-group-btn{float:none;z-index:auto}.form-inline .bootstrap-select,.form-inline .bootstrap-select.form-control:not([class*="col-"]){width:auto}.bootstrap-select:not(.input-group-btn),.bootstrap-select[class*="col-"]{float:none;display:inline-block;margin-left:0}.bootstrap-select.dropdown-menu-right,.bootstrap-select[class*="col-"].dropdown-menu-right,.row .bootstrap-select[class*="col-"].dropdown-menu-right{float:right}.form-inline .bootstrap-select,.form-horizontal .bootstrap-select,.form-group .bootstrap-select{margin-bottom:0}.form-group-lg .bootstrap-select.form-control,.form-group-sm .bootstrap-select.form-control{padding:0}.form-group-lg .bootstrap-select.form-control .dropdown-toggle,.form-group-sm .bootstrap-select.form-control .dropdown-toggle{height:100%;font-size:inherit;line-height:inherit;border-radius:inherit}.bootstrap-select.form-control-sm .dropdown-toggle,.bootstrap-select.form-control-lg .dropdown-toggle{font-size:inherit;line-height:inherit;border-radius:inherit}.bootstrap-select.form-control-sm .dropdown-toggle{padding:.25rem .5rem}.bootstrap-select.form-control-lg .dropdown-toggle{padding:.5rem 1rem}.form-inline .bootstrap-select .form-control{width:100%}.bootstrap-select.disabled,.bootstrap-select>.disabled{cursor:not-allowed}.bootstrap-select.disabled:focus,.bootstrap-select>.disabled:focus{outline:none !important}.bootstrap-select.bs-container{position:absolute;top:0;left:0;height:0 !important;padding:0 !important}.bootstrap-select.bs-container .dropdown-menu{z-index:9998}.bootstrap-select .dropdown-toggle .filter-option{position:static;top:0;left:0;float:left;height:100%;width:100%;text-align:left;overflow:hidden;flex:0 1 auto}.bs3.bootstrap-select .dropdown-toggle .filter-option{padding-right:inherit}.input-group .bs3-has-addon.bootstrap-select .dropdown-toggle .filter-option{position:absolute;padding-top:inherit;padding-bottom:inherit;padding-left:inherit;float:none}.input-group .bs3-has-addon.bootstrap-select .dropdown-toggle .filter-option .filter-option-inner{padding-right:inherit}.bootstrap-select .dropdown-toggle .filter-option-inner-inner{overflow:hidden}.bootstrap-select .dropdown-toggle .filter-expand{width:0 !important;float:left;opacity:0 !important;overflow:hidden}.bootstrap-select .dropdown-toggle .caret{position:absolute;top:50%;right:12px;margin-top:-2px;vertical-align:middle}.input-group .bootstrap-select.form-control .dropdown-toggle{border-radius:inherit}.bootstrap-select[class*="col-"] .dropdown-toggle{width:100%}.bootstrap-select .dropdown-menu{min-width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bootstrap-select .dropdown-menu>.inner:focus{outline:none !important}.bootstrap-select .dropdown-menu.inner{position:static;float:none;border:0;padding:0;margin:0;border-radius:0;box-shadow:none}.bootstrap-select .dropdown-menu li{position:relative}.bootstrap-select .dropdown-menu li.active small{color:rgba(255,255,255,0.5) !important}.bootstrap-select .dropdown-menu li.disabled a{cursor:not-allowed}.bootstrap-select .dropdown-menu li a{cursor:pointer;user-select:none}.bootstrap-select .dropdown-menu li a.opt{position:relative;padding-left:2.25em}.bootstrap-select .dropdown-menu li a span.check-mark{display:none}.bootstrap-select .dropdown-menu li a span.text{display:inline-block}.bootstrap-select .dropdown-menu li small{padding-left:0.5em}.bootstrap-select .dropdown-menu .notify{position:absolute;bottom:5px;width:96%;margin:0 2%;min-height:26px;padding:3px 5px;background:#f5f5f5;border:1px solid #e3e3e3;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);pointer-events:none;opacity:0.9;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bootstrap-select .dropdown-menu .notify.fadeOut{animation:300ms linear 750ms forwards bs-notify-fadeOut}.bootstrap-select .no-results{padding:3px;background:#f5f5f5;margin:0 5px;white-space:nowrap}.bootstrap-select.fit-width .dropdown-toggle .filter-option{position:static;display:inline;padding:0}.bootstrap-select.fit-width .dropdown-toggle .filter-option-inner,.bootstrap-select.fit-width .dropdown-toggle .filter-option-inner-inner{display:inline}.bootstrap-select.fit-width .dropdown-toggle .bs-caret:before{content:'\00a0'}.bootstrap-select.fit-width .dropdown-toggle .caret{position:static;top:auto;margin-top:-1px}.bootstrap-select.show-tick .dropdown-menu .selected span.check-mark{position:absolute;display:inline-block;right:15px;top:5px}.bootstrap-select.show-tick .dropdown-menu li a span.text{margin-right:34px}.bootstrap-select .bs-ok-default:after{content:'';display:block;width:0.5em;height:1em;border-style:solid;border-width:0 0.26em 0.26em 0;transform:rotate(45deg)}.bootstrap-select.show-menu-arrow.open>.dropdown-toggle,.bootstrap-select.show-menu-arrow.show>.dropdown-toggle{z-index:9999}.bootstrap-select.show-menu-arrow .dropdown-toggle .filter-option:before{content:'';border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid rgba(204,204,204,0.2);position:absolute;bottom:-4px;left:9px;display:none}.bootstrap-select.show-menu-arrow .dropdown-toggle .filter-option:after{content:'';border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid white;position:absolute;bottom:-4px;left:10px;display:none}.bootstrap-select.show-menu-arrow.dropup .dropdown-toggle .filter-option:before{bottom:auto;top:-4px;border-top:7px solid rgba(204,204,204,0.2);border-bottom:0}.bootstrap-select.show-menu-arrow.dropup .dropdown-toggle .filter-option:after{bottom:auto;top:-4px;border-top:6px solid white;border-bottom:0}.bootstrap-select.show-menu-arrow.pull-right .dropdown-toggle .filter-option:before{right:12px;left:auto}.bootstrap-select.show-menu-arrow.pull-right .dropdown-toggle .filter-option:after{right:13px;left:auto}.bootstrap-select.show-menu-arrow.open>.dropdown-toggle .filter-option:before,.bootstrap-select.show-menu-arrow.open>.dropdown-toggle .filter-option:after,.bootstrap-select.show-menu-arrow.show>.dropdown-toggle .filter-option:before,.bootstrap-select.show-menu-arrow.show>.dropdown-toggle .filter-option:after{display:block}.bs-searchbox,.bs-actionsbox,.bs-donebutton{padding:4px 8px}.bs-actionsbox{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bs-actionsbox .btn-group button{width:50%}.bs-donebutton{float:left;width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bs-donebutton .btn-group button{width:100%}.bs-searchbox+.bs-actionsbox{padding:0 8px 4px}.bs-searchbox .form-control{margin-bottom:0;width:100%;float:none} diff --git a/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/stylesheets/bootstrap-select/sass/bootstrap-select.scss b/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/stylesheets/bootstrap-select/sass/bootstrap-select.scss new file mode 100644 index 0000000000..32b852e26f --- /dev/null +++ b/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/stylesheets/bootstrap-select/sass/bootstrap-select.scss @@ -0,0 +1,519 @@ +@import "variables"; + +@keyframes bs-notify-fadeOut { + 0% {opacity: 0.9;} + 100% {opacity: 0;} +} + +// Mixins +@mixin cursor-disabled() { + cursor: not-allowed; +} + +@mixin box-sizing($fmt) { + -webkit-box-sizing: $fmt; + -moz-box-sizing: $fmt; + box-sizing: $fmt; +} + +@mixin box-shadow($fmt) { + -webkit-box-shadow: $fmt; + box-shadow: $fmt; +} + +@function fade($color, $amnt) { + @if $amnt > 1 { + $amnt: $amnt / 100; // convert to percentage if int + } + @return rgba($color, $amnt); +} + +// Rules +select.bs-select-hidden, +.bootstrap-select > select.bs-select-hidden, +select.selectpicker { + display: none !important; +} + +.bootstrap-select { + width: 220px \0; /*IE9 and below*/ + vertical-align: middle; + + // The selectpicker button + > .dropdown-toggle { + position: relative; + width: 100%; + // necessary for proper positioning of caret in Bootstrap 4 (pushes caret to the right) + text-align: right; + white-space: nowrap; + // force caret to be vertically centered for Bootstrap 4 multi-line buttons + display: inline-flex; + align-items: center; + justify-content: space-between; + + &:after { + margin-top: -1px; + } + + &.bs-placeholder { + &, + &:hover, + &:focus, + &:active { + color: $input-color-placeholder; + } + + &.btn-primary, + &.btn-secondary, + &.btn-success, + &.btn-danger, + &.btn-info, + &.btn-dark { + &, + &:hover, + &:focus, + &:active { + color: $input-alt-color-placeholder; + } + } + } + } + + > select { + position: absolute !important; + bottom: 0; + left: 50%; + display: block !important; + width: 0.5px !important; + height: 100% !important; + padding: 0 !important; + opacity: 0 !important; + border: none; + z-index: 0 !important; + + &.mobile-device { + top: 0; + left: 0; + display: block !important; + width: 100% !important; + z-index: 2 !important; + } + } + + // Error display + .has-error & .dropdown-toggle, + .error & .dropdown-toggle, + &.is-invalid .dropdown-toggle, + .was-validated & select:invalid + .dropdown-toggle { + border-color: $color-red-error; + } + + &.is-valid .dropdown-toggle, + .was-validated & select:valid + .dropdown-toggle { + border-color: $color-green-success; + } + + &.fit-width { + width: auto !important; + } + + &:not([class*="col-"]):not([class*="form-control"]):not(.input-group-btn) { + width: $width-default; + } + + > select.mobile-device:focus + .dropdown-toggle { + outline: thin dotted #333333 !important; + outline: 5px auto -webkit-focus-ring-color !important; + outline-offset: -2px; + } +} + +// The selectpicker components +.bootstrap-select { + &.form-control { + margin-bottom: 0; + padding: 0; + border: none; + height: auto; + + :not(.input-group) > &:not([class*="col-"]) { + width: 100%; + } + + &.input-group-btn { + float: none; + z-index: auto; + } + } + + .form-inline &, + .form-inline &.form-control:not([class*="col-"]) { + width: auto; + } + + &:not(.input-group-btn), + &[class*="col-"] { + float: none; + display: inline-block; + margin-left: 0; + } + + // Forces the pull to the right, if necessary + &, + &[class*="col-"], + .row &[class*="col-"] { + &.dropdown-menu-right { + float: right; + } + } + + .form-inline &, + .form-horizontal &, + .form-group & { + margin-bottom: 0; + } + + .form-group-lg &.form-control, + .form-group-sm &.form-control { + padding: 0; + + .dropdown-toggle { + height: 100%; + font-size: inherit; + line-height: inherit; + border-radius: inherit; + } + } + + &.form-control-sm .dropdown-toggle, + &.form-control-lg .dropdown-toggle { + font-size: inherit; + line-height: inherit; + border-radius: inherit; + } + + &.form-control-sm .dropdown-toggle { + padding: $input-padding-y-sm $input-padding-x-sm; + } + + &.form-control-lg .dropdown-toggle { + padding: $input-padding-y-lg $input-padding-x-lg; + } + + // Set the width of the live search (and any other form control within an inline form) + // see https://github.com/silviomoreto/bootstrap-select/issues/685 + .form-inline & .form-control { + width: 100%; + } + + &.disabled, + > .disabled { + @include cursor-disabled(); + + &:focus { + outline: none !important; + } + } + + &.bs-container { + position: absolute; + top: 0; + left: 0; + height: 0 !important; + padding: 0 !important; + + .dropdown-menu { + z-index: $zindex-select-dropdown; + } + } + + // The selectpicker button + .dropdown-toggle { + .filter-option { + position: static; + top: 0; + left: 0; + float: left; + height: 100%; + width: 100%; + text-align: left; + overflow: hidden; + flex: 0 1 auto; // for IE10 + + @at-root .bs3#{&} { + padding-right: inherit; + } + + @at-root .input-group .bs3-has-addon#{&} { + position: absolute; + padding-top: inherit; + padding-bottom: inherit; + padding-left: inherit; + float: none; + + .filter-option-inner { + padding-right: inherit; + } + } + } + + .filter-option-inner-inner { + overflow: hidden; + } + + // used to expand the height of the button when inside an input group + .filter-expand { + width: 0 !important; + float: left; + opacity: 0 !important; + overflow: hidden; + } + + .caret { + position: absolute; + top: 50%; + right: 12px; + margin-top: -2px; + vertical-align: middle; + } + } + + .input-group &.form-control .dropdown-toggle { + border-radius: inherit; + } + + &[class*="col-"] .dropdown-toggle { + width: 100%; + } + + // The selectpicker dropdown + .dropdown-menu { + min-width: 100%; + @include box-sizing(border-box); + + > .inner:focus { + outline: none !important; + } + + &.inner { + position: static; + float: none; + border: 0; + padding: 0; + margin: 0; + border-radius: 0; + box-shadow: none; + } + + li { + position: relative; + + &.active small { + color: $input-alt-color-placeholder !important; + } + + &.disabled a { + @include cursor-disabled(); + } + + a { + cursor: pointer; + user-select: none; + + &.opt { + position: relative; + padding-left: 2.25em; + } + + span.check-mark { + display: none; + } + + span.text { + display: inline-block; + } + } + + small { + padding-left: 0.5em; + } + } + + .notify { + position: absolute; + bottom: 5px; + width: 96%; + margin: 0 2%; + min-height: 26px; + padding: 3px 5px; + background: rgb(245, 245, 245); + border: 1px solid rgb(227, 227, 227); + @include box-shadow(inset 0 1px 1px fade(rgb(0, 0, 0), 5)); + pointer-events: none; + opacity: 0.9; + @include box-sizing(border-box); + + &.fadeOut { + animation: 300ms linear 750ms forwards bs-notify-fadeOut; + } + } + } + + .no-results { + padding: 3px; + background: #f5f5f5; + margin: 0 5px; + white-space: nowrap; + } + + &.fit-width .dropdown-toggle { + .filter-option { + position: static; + display: inline; + padding: 0; + } + + .filter-option-inner, + .filter-option-inner-inner { + display: inline; + } + + .bs-caret:before { + content: '\00a0'; + } + + .caret { + position: static; + top: auto; + margin-top: -1px; + } + } + + &.show-tick .dropdown-menu { + .selected span.check-mark { + position: absolute; + display: inline-block; + right: 15px; + top: 5px; + } + + li a span.text { + margin-right: 34px; + } + } + + // default check mark for use without an icon font + .bs-ok-default:after { + content: ''; + display: block; + width: 0.5em; + height: 1em; + border-style: solid; + border-width: 0 0.26em 0.26em 0; + transform: rotate(45deg); + } +} + +.bootstrap-select.show-menu-arrow { + &.open > .dropdown-toggle, + &.show > .dropdown-toggle { + z-index: ($zindex-select-dropdown + 1); + } + + .dropdown-toggle .filter-option { + &:before { + content: ''; + border-left: 7px solid transparent; + border-right: 7px solid transparent; + border-bottom: 7px solid $color-grey-arrow; + position: absolute; + bottom: -4px; + left: 9px; + display: none; + } + + &:after { + content: ''; + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-bottom: 6px solid white; + position: absolute; + bottom: -4px; + left: 10px; + display: none; + } + } + + &.dropup .dropdown-toggle .filter-option { + &:before { + bottom: auto; + top: -4px; + border-top: 7px solid $color-grey-arrow; + border-bottom: 0; + } + + &:after { + bottom: auto; + top: -4px; + border-top: 6px solid white; + border-bottom: 0; + } + } + + &.pull-right .dropdown-toggle .filter-option { + &:before { + right: 12px; + left: auto; + } + + &:after { + right: 13px; + left: auto; + } + } + + &.open > .dropdown-toggle .filter-option, + &.show > .dropdown-toggle .filter-option { + &:before, + &:after { + display: block; + } + } +} + +.bs-searchbox, +.bs-actionsbox, +.bs-donebutton { + padding: 4px 8px; +} + +.bs-actionsbox { + width: 100%; + @include box-sizing(border-box); + + & .btn-group button { + width: 50%; + } +} + +.bs-donebutton { + float: left; + width: 100%; + @include box-sizing(border-box); + + & .btn-group button { + width: 100%; + } +} + +.bs-searchbox { + & + .bs-actionsbox { + padding: 0 8px 4px; + } + + & .form-control { + margin-bottom: 0; + width: 100%; + float: none; + } +} diff --git a/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/stylesheets/bootstrap-select/sass/variables.scss b/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/stylesheets/bootstrap-select/sass/variables.scss new file mode 100644 index 0000000000..6e9c8af262 --- /dev/null +++ b/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/stylesheets/bootstrap-select/sass/variables.scss @@ -0,0 +1,17 @@ +$color-red-error: rgb(185, 74, 72) !default; +$color-green-success: #28a745; +$color-grey-arrow: rgba(204, 204, 204, 0.2) !default; + +$width-default: 220px !default; // 3 960px-grid columns + +$zindex-select-dropdown: 9998 !default; // must be higher than a modal background (1050) + +//** Placeholder text color +$input-color-placeholder: #999 !default; +$input-alt-color-placeholder: rgba(255, 255, 255, 0.5) !default; + +$input-padding-y-sm: .25rem !default; +$input-padding-x-sm: .5rem !default; + +$input-padding-y-lg: 0.5rem !default; +$input-padding-x-lg: 1rem !default; diff --git a/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/stylesheets/bootstrap/_alerts.scss b/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/stylesheets/bootstrap/_alerts.scss new file mode 100644 index 0000000000..e45de83058 --- /dev/null +++ b/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/stylesheets/bootstrap/_alerts.scss @@ -0,0 +1,68 @@ +// +// Alerts +// -------------------------------------------------- + + +// Base styles +// ------------------------- + +.alert { + padding: $alert-padding; + margin-bottom: $line-height-computed; + border: 1px solid transparent; + border-radius: $alert-border-radius; + + // Headings for larger alerts + h4 { + margin-top: 0; + // Specified for the h4 to prevent conflicts of changing $headings-color + color: inherit; + } + // Provide class for links that match alerts + .alert-link { + font-weight: $alert-link-font-weight; + } + + // Improve alignment and spacing of inner content + > p, + > ul { + margin-bottom: 0; + } + > p + p { + margin-top: 5px; + } +} + +// Dismissible alerts +// +// Expand the right padding and account for the close button's positioning. + +.alert-dismissable, // The misspelled .alert-dismissable was deprecated in 3.2.0. +.alert-dismissible { + padding-right: ($alert-padding + 20); + + // Adjust close link position + .close { + position: relative; + top: -2px; + right: -21px; + color: inherit; + } +} + +// Alternate styles +// +// Generate contextual modifier classes for colorizing the alert. + +.alert-success { + @include alert-variant($alert-success-bg, $alert-success-border, $alert-success-text); +} +.alert-info { + @include alert-variant($alert-info-bg, $alert-info-border, $alert-info-text); +} +.alert-warning { + @include alert-variant($alert-warning-bg, $alert-warning-border, $alert-warning-text); +} +.alert-danger { + @include alert-variant($alert-danger-bg, $alert-danger-border, $alert-danger-text); +} diff --git a/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/stylesheets/bootstrap/_badges.scss b/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/stylesheets/bootstrap/_badges.scss new file mode 100644 index 0000000000..02394ae7fa --- /dev/null +++ b/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/stylesheets/bootstrap/_badges.scss @@ -0,0 +1,57 @@ +// +// Badges +// -------------------------------------------------- + + +// Base class +.badge { + display: inline-block; + min-width: 10px; + padding: 3px 7px; + font-size: $font-size-small; + font-weight: $badge-font-weight; + color: $badge-color; + line-height: $badge-line-height; + vertical-align: baseline; + white-space: nowrap; + text-align: center; + background-color: $badge-bg; + border-radius: $badge-border-radius; + + // Empty badges collapse automatically (not available in IE8) + &:empty { + display: none; + } + + // Quick fix for badges in buttons + .btn & { + position: relative; + top: -1px; + } + .btn-xs & { + top: 0; + padding: 1px 5px; + } + + // [converter] extracted a& to a.badge + + // Account for badges in navs + a.list-group-item.active > &, + .nav-pills > .active > a > & { + color: $badge-active-color; + background-color: $badge-active-bg; + } + .nav-pills > li > a > & { + margin-left: 3px; + } +} + +// Hover state, but only for links +a.badge { + &:hover, + &:focus { + color: $badge-link-hover-color; + text-decoration: none; + cursor: pointer; + } +} diff --git a/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/stylesheets/bootstrap/_breadcrumbs.scss b/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/stylesheets/bootstrap/_breadcrumbs.scss new file mode 100644 index 0000000000..3641e333b8 --- /dev/null +++ b/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/stylesheets/bootstrap/_breadcrumbs.scss @@ -0,0 +1,26 @@ +// +// Breadcrumbs +// -------------------------------------------------- + + +.breadcrumb { + padding: $breadcrumb-padding-vertical $breadcrumb-padding-horizontal; + margin-bottom: $line-height-computed; + list-style: none; + background-color: $breadcrumb-bg; + border-radius: $border-radius-base; + + > li { + display: inline-block; + + + li:before { + content: "#{$breadcrumb-separator}\00a0"; // Unicode space added since inline-block means non-collapsing white-space + padding: 0 5px; + color: $breadcrumb-color; + } + } + + > .active { + color: $breadcrumb-active-color; + } +} diff --git a/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/stylesheets/bootstrap/_button-groups.scss b/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/stylesheets/bootstrap/_button-groups.scss new file mode 100644 index 0000000000..63ccd927c8 --- /dev/null +++ b/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/stylesheets/bootstrap/_button-groups.scss @@ -0,0 +1,240 @@ +// +// Button groups +// -------------------------------------------------- + +// Make the div behave like a button +.btn-group, +.btn-group-vertical { + position: relative; + display: inline-block; + vertical-align: middle; // match .btn alignment given font-size hack above + > .btn { + position: relative; + float: left; + // Bring the "active" button to the front + &:hover, + &:focus, + &:active, + &.active { + z-index: 2; + } + &:focus { + // Remove focus outline when dropdown JS adds it after closing the menu + outline: 0; + } + } +} + +// Prevent double borders when buttons are next to each other +.btn-group { + .btn + .btn, + .btn + .btn-group, + .btn-group + .btn, + .btn-group + .btn-group { + margin-left: -1px; + } +} + +// Optional: Group multiple button groups together for a toolbar +.btn-toolbar { + margin-left: -5px; // Offset the first child's margin + @include clearfix(); + + .btn-group, + .input-group { + float: left; + } + > .btn, + > .btn-group, + > .input-group { + margin-left: 5px; + } +} + +.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) { + border-radius: 0; +} + +// Set corners individual because sometimes a single button can be in a .btn-group and we need :first-child and :last-child to both match +.btn-group > .btn:first-child { + margin-left: 0; + &:not(:last-child):not(.dropdown-toggle) { + @include border-right-radius(0); + } +} +// Need .dropdown-toggle since :last-child doesn't apply given a .dropdown-menu immediately after it +.btn-group > .btn:last-child:not(:first-child), +.btn-group > .dropdown-toggle:not(:first-child) { + @include border-left-radius(0); +} + +// Custom edits for including btn-groups within btn-groups (useful for including dropdown buttons within a btn-group) +.btn-group > .btn-group { + float: left; +} +.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn { + border-radius: 0; +} +.btn-group > .btn-group:first-child { + > .btn:last-child, + > .dropdown-toggle { + @include border-right-radius(0); + } +} +.btn-group > .btn-group:last-child > .btn:first-child { + @include border-left-radius(0); +} + +// On active and open, don't show outline +.btn-group .dropdown-toggle:active, +.btn-group.open .dropdown-toggle { + outline: 0; +} + + +// Sizing +// +// Remix the default button sizing classes into new ones for easier manipulation. + +.btn-group-xs > .btn { @extend .btn-xs; } +.btn-group-sm > .btn { @extend .btn-sm; } +.btn-group-lg > .btn { @extend .btn-lg; } + + +// Split button dropdowns +// ---------------------- + +// Give the line between buttons some depth +.btn-group > .btn + .dropdown-toggle { + padding-left: 8px; + padding-right: 8px; +} +.btn-group > .btn-lg + .dropdown-toggle { + padding-left: 12px; + padding-right: 12px; +} + +// The clickable button for toggling the menu +// Remove the gradient and set the same inset shadow as the :active state +.btn-group.open .dropdown-toggle { + @include box-shadow(inset 0 3px 5px rgba(0,0,0,.125)); + + // Show no shadow for `.btn-link` since it has no other button styles. + &.btn-link { + @include box-shadow(none); + } +} + + +// Reposition the caret +.btn .caret { + margin-left: 0; +} +// Carets in other button sizes +.btn-lg .caret { + border-width: $caret-width-large $caret-width-large 0; + border-bottom-width: 0; +} +// Upside down carets for .dropup +.dropup .btn-lg .caret { + border-width: 0 $caret-width-large $caret-width-large; +} + + +// Vertical button groups +// ---------------------- + +.btn-group-vertical { + > .btn, + > .btn-group, + > .btn-group > .btn { + display: block; + float: none; + width: 100%; + max-width: 100%; + } + + // Clear floats so dropdown menus can be properly placed + > .btn-group { + @include clearfix(); + > .btn { + float: none; + } + } + + > .btn + .btn, + > .btn + .btn-group, + > .btn-group + .btn, + > .btn-group + .btn-group { + margin-top: -1px; + margin-left: 0; + } +} + +.btn-group-vertical > .btn { + &:not(:first-child):not(:last-child) { + border-radius: 0; + } + &:first-child:not(:last-child) { + border-top-right-radius: $border-radius-base; + @include border-bottom-radius(0); + } + &:last-child:not(:first-child) { + border-bottom-left-radius: $border-radius-base; + @include border-top-radius(0); + } +} +.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn { + border-radius: 0; +} +.btn-group-vertical > .btn-group:first-child:not(:last-child) { + > .btn:last-child, + > .dropdown-toggle { + @include border-bottom-radius(0); + } +} +.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child { + @include border-top-radius(0); +} + + + +// Justified button groups +// ---------------------- + +.btn-group-justified { + display: table; + width: 100%; + table-layout: fixed; + border-collapse: separate; + > .btn, + > .btn-group { + float: none; + display: table-cell; + width: 1%; + } + > .btn-group .btn { + width: 100%; + } + + > .btn-group .dropdown-menu { + left: auto; + } +} + + +// Checkbox and radio options +// +// In order to support the browser's form validation feedback, powered by the +// `required` attribute, we have to "hide" the inputs via `opacity`. We cannot +// use `display: none;` or `visibility: hidden;` as that also hides the popover. +// This way, we ensure a DOM element is visible to position the popover from. +// +// See https://github.com/twbs/bootstrap/pull/12794 for more. + +[data-toggle="buttons"] > .btn > input[type="radio"], +[data-toggle="buttons"] > .btn > input[type="checkbox"] { + position: absolute; + z-index: -1; + @include opacity(0); +} diff --git a/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/stylesheets/bootstrap/_buttons.scss b/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/stylesheets/bootstrap/_buttons.scss new file mode 100644 index 0000000000..f2684c1b76 --- /dev/null +++ b/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/stylesheets/bootstrap/_buttons.scss @@ -0,0 +1,159 @@ +// +// Buttons +// -------------------------------------------------- + + +// Base styles +// -------------------------------------------------- + +.btn { + display: inline-block; + margin-bottom: 0; // For input.btn + font-weight: $btn-font-weight; + text-align: center; + vertical-align: middle; + cursor: pointer; + background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214 + border: 1px solid transparent; + white-space: nowrap; + @include button-size($padding-base-vertical, $padding-base-horizontal, $font-size-base, $line-height-base, $border-radius-base); + @include user-select(none); + + @include button-variant($btn-default-color, $btn-default-bg, $btn-default-border); + + &, + &:active, + &.active { + &:focus { + @include tab-focus(); + } + } + + &:hover, + &:focus { + color: $btn-default-color; + text-decoration: none; + } + + &:active, + &.active { + outline: 0; + background-image: none; + @include box-shadow(inset 0 3px 5px rgba(0,0,0,.125)); + } + + &.disabled, + &[disabled], + fieldset[disabled] & { + cursor: not-allowed; + pointer-events: none; // Future-proof disabling of clicks + @include opacity(.65); + @include box-shadow(none); + } +} + + +// Alternate buttons +// -------------------------------------------------- + +.btn-default { + @include button-variant($btn-default-color, $btn-default-bg, $btn-default-border); +} +.btn-primary { + @include button-variant($btn-primary-color, $btn-primary-bg, $btn-primary-border); +} +// Success appears as green +.btn-success { + @include button-variant($btn-success-color, $btn-success-bg, $btn-success-border); +} +// Info appears as blue-green +.btn-info { + @include button-variant($btn-info-color, $btn-info-bg, $btn-info-border); +} +// Warning appears as orange +.btn-warning { + @include button-variant($btn-warning-color, $btn-warning-bg, $btn-warning-border); +} +// Danger and error appear as red +.btn-danger { + @include button-variant($btn-danger-color, $btn-danger-bg, $btn-danger-border); +} + + +// Link buttons +// ------------------------- + +// Make a button look and behave like a link +.btn-link { + color: $link-color; + font-weight: normal; + cursor: pointer; + border-radius: 0; + + &, + &:active, + &[disabled], + fieldset[disabled] & { + background-color: transparent; + @include box-shadow(none); + } + &, + &:hover, + &:focus, + &:active { + border-color: transparent; + } + &:hover, + &:focus { + color: $link-hover-color; + text-decoration: underline; + background-color: transparent; + } + &[disabled], + fieldset[disabled] & { + &:hover, + &:focus { + color: $btn-link-disabled-color; + text-decoration: none; + } + } +} + + +// Button Sizes +// -------------------------------------------------- + +.btn-lg { + // line-height: ensure even-numbered height of button next to large input + @include button-size($padding-large-vertical, $padding-large-horizontal, $font-size-large, $line-height-large, $border-radius-large); +} +.btn-sm { + // line-height: ensure proper height of button next to small input + @include button-size($padding-small-vertical, $padding-small-horizontal, $font-size-small, $line-height-small, $border-radius-small); +} +.btn-xs { + @include button-size($padding-xs-vertical, $padding-xs-horizontal, $font-size-small, $line-height-small, $border-radius-small); +} + + +// Block button +// -------------------------------------------------- + +.btn-block { + display: block; + width: 100%; +} + +// Vertically space out multiple block buttons +.btn-block + .btn-block { + margin-top: 5px; +} + +// Specificity overrides +input[type="submit"], +input[type="reset"], +input[type="button"] { + &.btn-block { + width: 100%; + } +} diff --git a/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/stylesheets/bootstrap/_carousel.scss b/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/stylesheets/bootstrap/_carousel.scss new file mode 100644 index 0000000000..e9e2f7ce1e --- /dev/null +++ b/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/stylesheets/bootstrap/_carousel.scss @@ -0,0 +1,243 @@ +// +// Carousel +// -------------------------------------------------- + + +// Wrapper for the slide container and indicators +.carousel { + position: relative; +} + +.carousel-inner { + position: relative; + overflow: hidden; + width: 100%; + + > .item { + display: none; + position: relative; + @include transition(.6s ease-in-out left); + + // Account for jankitude on images + > img, + > a > img { + @include img-responsive(); + line-height: 1; + } + } + + > .active, + > .next, + > .prev { + display: block; + } + + > .active { + left: 0; + } + + > .next, + > .prev { + position: absolute; + top: 0; + width: 100%; + } + + > .next { + left: 100%; + } + > .prev { + left: -100%; + } + > .next.left, + > .prev.right { + left: 0; + } + + > .active.left { + left: -100%; + } + > .active.right { + left: 100%; + } + +} + +// Left/right controls for nav +// --------------------------- + +.carousel-control { + position: absolute; + top: 0; + left: 0; + bottom: 0; + width: $carousel-control-width; + @include opacity($carousel-control-opacity); + font-size: $carousel-control-font-size; + color: $carousel-control-color; + text-align: center; + text-shadow: $carousel-text-shadow; + // We can't have this transition here because WebKit cancels the carousel + // animation if you trip this while in the middle of another animation. + + // Set gradients for backgrounds + &.left { + @include gradient-horizontal($start-color: rgba(0,0,0,.5), $end-color: rgba(0,0,0,.0001)); + } + &.right { + left: auto; + right: 0; + @include gradient-horizontal($start-color: rgba(0,0,0,.0001), $end-color: rgba(0,0,0,.5)); + } + + // Hover/focus state + &:hover, + &:focus { + outline: 0; + color: $carousel-control-color; + text-decoration: none; + @include opacity(.9); + } + + // Toggles + .icon-prev, + .icon-next, + .glyphicon-chevron-left, + .glyphicon-chevron-right { + position: absolute; + top: 50%; + z-index: 5; + display: inline-block; + } + .icon-prev, + .glyphicon-chevron-left { + left: 50%; + margin-left: -10px; + } + .icon-next, + .glyphicon-chevron-right { + right: 50%; + margin-right: -10px; + } + .icon-prev, + .icon-next { + width: 20px; + height: 20px; + margin-top: -10px; + font-family: serif; + } + + + .icon-prev { + &:before { + content: '\2039';// SINGLE LEFT-POINTING ANGLE QUOTATION MARK (U+2039) + } + } + .icon-next { + &:before { + content: '\203a';// SINGLE RIGHT-POINTING ANGLE QUOTATION MARK (U+203A) + } + } +} + +// Optional indicator pips +// +// Add an unordered list with the following class and add a list item for each +// slide your carousel holds. + +.carousel-indicators { + position: absolute; + bottom: 10px; + left: 50%; + z-index: 15; + width: 60%; + margin-left: -30%; + padding-left: 0; + list-style: none; + text-align: center; + + li { + display: inline-block; + width: 10px; + height: 10px; + margin: 1px; + text-indent: -999px; + border: 1px solid $carousel-indicator-border-color; + border-radius: 10px; + cursor: pointer; + + // IE8-9 hack for event handling + // + // Internet Explorer 8-9 does not support clicks on elements without a set + // `background-color`. We cannot use `filter` since that's not viewed as a + // background color by the browser. Thus, a hack is needed. + // + // For IE8, we set solid black as it doesn't support `rgba()`. For IE9, we + // set alpha transparency for the best results possible. + background-color: #000 \9; // IE8 + background-color: rgba(0,0,0,0); // IE9 + } + .active { + margin: 0; + width: 12px; + height: 12px; + background-color: $carousel-indicator-active-bg; + } +} + +// Optional captions +// ----------------------------- +// Hidden by default for smaller viewports +.carousel-caption { + position: absolute; + left: 15%; + right: 15%; + bottom: 20px; + z-index: 10; + padding-top: 20px; + padding-bottom: 20px; + color: $carousel-caption-color; + text-align: center; + text-shadow: $carousel-text-shadow; + & .btn { + text-shadow: none; // No shadow for button elements in carousel-caption + } +} + + +// Scale up controls for tablets and up +@media screen and (min-width: $screen-sm-min) { + + // Scale up the controls a smidge + .carousel-control { + .glyphicon-chevron-left, + .glyphicon-chevron-right, + .icon-prev, + .icon-next { + width: 30px; + height: 30px; + margin-top: -15px; + font-size: 30px; + } + .glyphicon-chevron-left, + .icon-prev { + margin-left: -15px; + } + .glyphicon-chevron-right, + .icon-next { + margin-right: -15px; + } + } + + // Show and left align the captions + .carousel-caption { + left: 20%; + right: 20%; + padding-bottom: 30px; + } + + // Move up the indicators + .carousel-indicators { + bottom: 20px; + } +} diff --git a/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/stylesheets/bootstrap/_close.scss b/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/stylesheets/bootstrap/_close.scss new file mode 100644 index 0000000000..62ce30fa37 --- /dev/null +++ b/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/stylesheets/bootstrap/_close.scss @@ -0,0 +1,35 @@ +// +// Close icons +// -------------------------------------------------- + + +.close { + float: right; + font-size: ($font-size-base * 1.5); + font-weight: $close-font-weight; + line-height: 1; + color: $close-color; + text-shadow: $close-text-shadow; + @include opacity(.2); + + &:hover, + &:focus { + color: $close-color; + text-decoration: none; + cursor: pointer; + @include opacity(.5); + } + + // [converter] extracted button& to button.close +} + +// Additional properties for button version +// iOS requires the button element instead of an anchor tag. +// If you want the anchor version, it requires `href="#"`. +button.close { + padding: 0; + cursor: pointer; + background: transparent; + border: 0; + -webkit-appearance: none; +} diff --git a/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/stylesheets/bootstrap/_code.scss b/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/stylesheets/bootstrap/_code.scss new file mode 100644 index 0000000000..3a434b946b --- /dev/null +++ b/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/stylesheets/bootstrap/_code.scss @@ -0,0 +1,68 @@ +// +// Code (inline and block) +// -------------------------------------------------- + + +// Inline and block code styles +code, +kbd, +pre, +samp { + font-family: $font-family-monospace; +} + +// Inline code +code { + padding: 2px 4px; + font-size: 90%; + color: $code-color; + background-color: $code-bg; + border-radius: $border-radius-base; +} + +// User input typically entered via keyboard +kbd { + padding: 2px 4px; + font-size: 90%; + color: $kbd-color; + background-color: $kbd-bg; + border-radius: $border-radius-small; + box-shadow: inset 0 -1px 0 rgba(0,0,0,.25); + + kbd { + padding: 0; + font-size: 100%; + box-shadow: none; + } +} + +// Blocks of code +pre { + display: block; + padding: calc(($line-height-computed - 100%) / 2); + margin: 0 0 calc($line-height-computed / 2); + font-size: ($font-size-base - 1); // 14px to 13px + line-height: $line-height-base; + word-break: break-all; + word-wrap: break-word; + color: $pre-color; + background-color: $pre-bg; + border: 1px solid $pre-border-color; + border-radius: $border-radius-base; + + // Account for some code outputs that place code tags in pre tags + code { + padding: 0; + font-size: inherit; + color: inherit; + white-space: pre-wrap; + background-color: transparent; + border-radius: 0; + } +} + +// Enable scrollable blocks of code +.pre-scrollable { + max-height: $pre-scrollable-max-height; + overflow-y: scroll; +} diff --git a/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/stylesheets/bootstrap/_component-animations.scss b/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/stylesheets/bootstrap/_component-animations.scss new file mode 100644 index 0000000000..8c3fd07a27 --- /dev/null +++ b/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/stylesheets/bootstrap/_component-animations.scss @@ -0,0 +1,35 @@ +// +// Component animations +// -------------------------------------------------- + +// Heads up! +// +// We don't use the `.opacity()` mixin here since it causes a bug with text +// fields in IE7-8. Source: https://github.com/twbs/bootstrap/pull/3552. + +.fade { + opacity: 0; + @include transition(opacity .15s linear); + &.in { + opacity: 1; + } +} + +.collapse { + display: none; + + &.in { display: block; } + // [converter] extracted tr&.in to tr.collapse.in + // [converter] extracted tbody&.in to tbody.collapse.in +} + +tr.collapse.in { display: table-row; } + +tbody.collapse.in { display: table-row-group; } + +.collapsing { + position: relative; + height: 0; + overflow: hidden; + @include transition(height .35s ease); +} diff --git a/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/stylesheets/bootstrap/_dropdowns.scss b/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/stylesheets/bootstrap/_dropdowns.scss new file mode 100644 index 0000000000..df50ec0cbc --- /dev/null +++ b/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/stylesheets/bootstrap/_dropdowns.scss @@ -0,0 +1,214 @@ +// +// Dropdown menus +// -------------------------------------------------- + + +// Dropdown arrow/caret +.caret { + display: inline-block; + width: 0; + height: 0; + margin-left: 2px; + vertical-align: middle; + border-top: $caret-width-base solid; + border-right: $caret-width-base solid transparent; + border-left: $caret-width-base solid transparent; +} + +// The dropdown wrapper (div) +.dropdown { + position: relative; +} + +// Prevent the focus on the dropdown toggle when closing dropdowns +.dropdown-toggle:focus { + outline: 0; +} + +// The dropdown menu (ul) +.dropdown-menu { + position: absolute; + top: 100%; + left: 0; + z-index: $zindex-dropdown; + display: none; // none by default, but block on "open" of the menu + float: left; + min-width: 160px; + padding: 5px 0; + margin: 2px 0 0; // override default ul + list-style: none; + font-size: $font-size-base; + text-align: left; // Ensures proper alignment if parent has it changed (e.g., modal footer) + background-color: $dropdown-bg; + border: 1px solid $dropdown-fallback-border; // IE8 fallback + border: 1px solid $dropdown-border; + border-radius: $border-radius-base; + @include box-shadow(0 6px 12px rgba(0,0,0,.175)); + background-clip: padding-box; + + // Aligns the dropdown menu to right + // + // Deprecated as of 3.1.0 in favor of `.dropdown-menu-[dir]` + &.pull-right { + right: 0; + left: auto; + } + + // Dividers (basically an hr) within the dropdown + .divider { + @include nav-divider($dropdown-divider-bg); + } + + // Links within the dropdown menu + > li > a { + display: block; + padding: 3px 20px; + clear: both; + font-weight: normal; + line-height: $line-height-base; + color: $dropdown-link-color; + white-space: nowrap; // prevent links from randomly breaking onto new lines + } +} + +// Hover/Focus state +.dropdown-menu > li > a { + &:hover, + &:focus { + text-decoration: none; + color: $dropdown-link-hover-color; + background-color: $dropdown-link-hover-bg; + } +} + +// Active state +.dropdown-menu > .active > a { + &, + &:hover, + &:focus { + color: $dropdown-link-active-color; + text-decoration: none; + outline: 0; + background-color: $dropdown-link-active-bg; + } +} + +// Disabled state +// +// Gray out text and ensure the hover/focus state remains gray + +.dropdown-menu > .disabled > a { + &, + &:hover, + &:focus { + color: $dropdown-link-disabled-color; + } +} +// Nuke hover/focus effects +.dropdown-menu > .disabled > a { + &:hover, + &:focus { + text-decoration: none; + background-color: transparent; + background-image: none; // Remove CSS gradient + @include reset-filter(); + cursor: not-allowed; + } +} + +// Open state for the dropdown +.open { + // Show the menu + > .dropdown-menu { + display: block; + } + + // Remove the outline when :focus is triggered + > a { + outline: 0; + } +} + +// Menu positioning +// +// Add extra class to `.dropdown-menu` to flip the alignment of the dropdown +// menu with the parent. +.dropdown-menu-right { + left: auto; // Reset the default from `.dropdown-menu` + right: 0; +} +// With v3, we enabled auto-flipping if you have a dropdown within a right +// aligned nav component. To enable the undoing of that, we provide an override +// to restore the default dropdown menu alignment. +// +// This is only for left-aligning a dropdown menu within a `.navbar-right` or +// `.pull-right` nav component. +.dropdown-menu-left { + left: 0; + right: auto; +} + +// Dropdown section headers +.dropdown-header { + display: block; + padding: 3px 20px; + font-size: $font-size-small; + line-height: $line-height-base; + color: $dropdown-header-color; + white-space: nowrap; // as with > li > a +} + +// Backdrop to catch body clicks on mobile, etc. +.dropdown-backdrop { + position: fixed; + left: 0; + right: 0; + bottom: 0; + top: 0; + z-index: ($zindex-dropdown - 10); +} + +// Right aligned dropdowns +.pull-right > .dropdown-menu { + right: 0; + left: auto; +} + +// Allow for dropdowns to go bottom up (aka, dropup-menu) +// +// Just add .dropup after the standard .dropdown class and you're set, bro. +// TODO: abstract this so that the navbar fixed styles are not placed here? + +.dropup, +.navbar-fixed-bottom .dropdown { + // Reverse the caret + .caret { + border-top: 0; + border-bottom: $caret-width-base solid; + content: ""; + } + // Different positioning for bottom up menu + .dropdown-menu { + top: auto; + bottom: 100%; + margin-bottom: 1px; + } +} + + +// Component alignment +// +// Reiterate per navbar.less and the modified component alignment there. + +@media (min-width: $grid-float-breakpoint) { + .navbar-right { + .dropdown-menu { + right: 0; left: auto; + } + // Necessary for overrides of the default right aligned menu. + // Will remove come v4 in all likelihood. + .dropdown-menu-left { + left: 0; right: auto; + } + } +} diff --git a/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/stylesheets/bootstrap/_forms.scss b/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/stylesheets/bootstrap/_forms.scss new file mode 100644 index 0000000000..cd5e5f1bf4 --- /dev/null +++ b/misc/theme-advanced/src/opnsense/www/themes/advanced/assets/stylesheets/bootstrap/_forms.scss @@ -0,0 +1,541 @@ +// +// Forms +// -------------------------------------------------- + + +// Normalize non-controls +// +// Restyle and baseline non-control form elements. + +fieldset { + padding: 0; + margin: 0; + border: 0; + // Chrome and Firefox set a min-width: min-content; on fieldsets, + // so we reset that to ensure it behaves more like a standard block element. + // See https://github.com/twbs/bootstrap/issues/12359. + min-width: 0; +} + +legend { + display: block; + width: 100%; + padding: 0; + margin-bottom: $line-height-computed; + font-size: ($font-size-base * 1.5); + line-height: inherit; + color: $legend-color; + border: 0; + border-bottom: 1px solid $legend-border-color; +} + +label { + display: inline-block; + max-width: 100%; // Force IE8 to wrap long content (see https://github.com/twbs/bootstrap/issues/13141) + margin-bottom: 5px; + font-weight: normal; +} + + +// Normalize form controls +// +// While most of our form styles require extra classes, some basic normalization +// is required to ensure optimum display with or without those classes to better +// address browser inconsistencies. + +// Override content-box in Normalize (isnt specific enough) +input[type="search"] { + @include box-sizing(border-box); +} + +// Position radios and checkboxes better +input[type="radio"], +input[type="checkbox"] { + margin: 4px 0 0; + margin-top: 1px \9; // IE8-9 + line-height: normal; +} + +// Set the height of file controls to match text inputs +input[type="file"] { + display: block; +} + +// Make range inputs behave like textual form controls +input[type="range"] { + display: block; + width: 100%; +} + +// Make multiple select elements height not fixed +select[multiple], +select[size] { + height: auto; +} + +// Focus for file, radio, and checkbox +input[type="file"]:focus, +input[type="radio"]:focus, +input[type="checkbox"]:focus { + @include tab-focus(); +} + +// Adjust output element +output { + display: block; + padding-top: ($padding-base-vertical + 1); + font-size: $font-size-base; + line-height: $line-height-base; + color: $input-color; +} + + +// Common form controls +// +// Shared size and type resets for form controls. Apply `.form-control` to any +// of the following form controls: +// +select, +textarea, +input[type="text"], +input[type="password"], +input[type="datetime"], +input[type="datetime-local"], +input[type="date"], +input[type="month"], +input[type="time"], +input[type="week"], +input[type="number"], +input[type="email"], +input[type="url"], +input[type="search"], +input[type="tel"], +input[type="color"] +/* .form-control */ +{ + display: block; + width: 100%; + height: $input-height-base; // Make inputs at least the height of their button counterpart (base line-height + padding + border) + padding: $padding-base-vertical $padding-base-horizontal; + font-size: $font-size-base; + line-height: $line-height-base; + color: $input-color; + background-color: $input-bg; + background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214 + border: 1px solid $input-border; + border-radius: $input-border-radius; + text-overflow: ellipsis; + max-width:450px; + transition: none; + //@include box-shadow(inset 0 1px 1px rgba(0,0,0,.075)); + @include transition(border-color ease-in-out .1s, box-shadow ease-in-out .1s); + + // Customize the `:focus` state to imitate native WebKit styles. + @include form-control-focus(); + + // Placeholder + @include placeholder(); + + // Disabled and read-only inputs + // + // HTML5 says that controls under a fieldset > legend:first-child wont be + // disabled if the fieldset is disabled. Due to implementation difficulty, we + // dont honor that edge case; we style them as disabled anyway. + &[disabled], + &[readonly], + fieldset[disabled] & { + cursor: not-allowed; + background-color: $input-bg-disabled; + opacity: 1; // iOS fix for unreadable disabled content + } + + // [converter] extracted textarea& to textarea.form-control +} + +// Reset height for `textarea`s +textarea{ + height: auto; +} + + +// Search inputs in iOS +// +// This overrides the extra rounded corners on search inputs in iOS so that our +// `.form-control` class can properly style them. Note that this cannot simply +// be added to `.form-control` as its not specific enough. For details, see +// https://github.com/twbs/bootstrap/issues/11586. + +input[type="search"] { + -webkit-appearance: none; +} + + +// Special styles for iOS temporal inputs +// +// In Mobile Safari, setting `display: block` on temporal inputs causes the +// text within the input to become vertically misaligned. +// As a workaround, we set a pixel line-height that matches the +// given height of the input. Since this fucks up everything else, we have to +// appropriately reset it for Internet Explorer and the size variations. + +input[type="date"], +input[type="time"], +input[type="datetime-local"], +input[type="month"] { + line-height: $input-height-base; + // IE8+ misaligns the text within date inputs, so we reset + line-height: $line-height-base #{\0}; + + &.input-sm { + line-height: $input-height-small; + } + &.input-lg { + line-height: $input-height-large; + } +} + + +// Form groups +// +// Designed to help with the organization and spacing of vertical forms. For +// horizontal forms, use the predefined grid classes. + +.form-group { + margin-bottom: 15px; +} + + +// Checkboxes and radios +// +// Indent the labels to position radios/checkboxes as hanging controls. + +.radio, +.checkbox { + position: relative; + display: block; + min-height: $line-height-computed; // clear the floating input if there is no label text + margin-top: 10px; + margin-bottom: 10px; + + label { + padding-left: 20px; + margin-bottom: 0; + font-weight: normal; + cursor: pointer; + } +} +.radio input[type="radio"], +.radio-inline input[type="radio"], +.checkbox input[type="checkbox"], +.checkbox-inline input[type="checkbox"] { + position: absolute; + margin-left: -20px; + margin-top: 4px \9; +} + +.radio + .radio, +.checkbox + .checkbox { + margin-top: -5px; // Move up sibling radios or checkboxes for tighter spacing +} + +// Radios and checkboxes on same line +.radio-inline, +.checkbox-inline { + display: inline-block; + padding-left: 20px; + margin-bottom: 0; + vertical-align: middle; + font-weight: normal; + cursor: pointer; +} +.radio-inline + .radio-inline, +.checkbox-inline + .checkbox-inline { + margin-top: 0; + margin-left: 10px; // space out consecutive inline controls +} + +// Apply same disabled cursor tweak as for inputs +// Some special care is needed because
    + {% endfor %} +
    diff --git a/net/frr/src/opnsense/mvc/app/views/OPNsense/Quagga/diagnosticsbgp.volt b/net/frr/src/opnsense/mvc/app/views/OPNsense/Quagga/diagnosticsbgp.volt deleted file mode 100644 index 06818b0cd4..0000000000 --- a/net/frr/src/opnsense/mvc/app/views/OPNsense/Quagga/diagnosticsbgp.volt +++ /dev/null @@ -1,113 +0,0 @@ -{# - -OPNsense® is Copyright © 2014 – 2017 by Deciso B.V. -Copyright (C) 2017 Fabian Franz -Copyright (C) 2017 Michael Muenz -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, -INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY -AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, -OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. - -#} - -{# -{{ partial("layout_partials/base_form",['fields':diagnosticsForm,'id':'frm_diagnostics_settings'])}} -#} - - - - - - - - -
    -
    - {{ lang._('loading...') }} -
    -
    -
    
    -    
    -
    diff --git a/net/frr/src/opnsense/mvc/app/views/OPNsense/Quagga/diagnosticsgeneral.volt b/net/frr/src/opnsense/mvc/app/views/OPNsense/Quagga/diagnosticsgeneral.volt deleted file mode 100644 index 4e15ea266f..0000000000 --- a/net/frr/src/opnsense/mvc/app/views/OPNsense/Quagga/diagnosticsgeneral.volt +++ /dev/null @@ -1,133 +0,0 @@ -{# - -OPNsense® is Copyright © 2014 – 2017 by Deciso B.V. -Copyright (C) 2017 Fabian Franz -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, -INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY -AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, -OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. - -#} - - - - - - - - -
    -
    -
    -
    -
    
    -    
    -
    diff --git a/net/frr/src/opnsense/mvc/app/views/OPNsense/Quagga/diagnosticsospf.volt b/net/frr/src/opnsense/mvc/app/views/OPNsense/Quagga/diagnosticsospf.volt deleted file mode 100644 index 0ac926d068..0000000000 --- a/net/frr/src/opnsense/mvc/app/views/OPNsense/Quagga/diagnosticsospf.volt +++ /dev/null @@ -1,482 +0,0 @@ -{# - -OPNsense® is Copyright © 2014 – 2017 by Deciso B.V. -Copyright (C) 2017 Fabian Franz -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, -INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY -AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, -OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. - -#} - - - - - - - - - - - - - -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    diff --git a/net/frr/src/opnsense/mvc/app/views/OPNsense/Quagga/diagnosticsospfv3.volt b/net/frr/src/opnsense/mvc/app/views/OPNsense/Quagga/diagnosticsospfv3.volt deleted file mode 100644 index 65c9d38664..0000000000 --- a/net/frr/src/opnsense/mvc/app/views/OPNsense/Quagga/diagnosticsospfv3.volt +++ /dev/null @@ -1,382 +0,0 @@ -{# - -OPNsense® is Copyright © 2014 – 2017 by Deciso B.V. -Copyright (C) 2017 Fabian Franz -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, -INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY -AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, -OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. - -#} - - - - - - - - - - - - - -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    diff --git a/net/frr/src/opnsense/mvc/app/views/OPNsense/Quagga/general.volt b/net/frr/src/opnsense/mvc/app/views/OPNsense/Quagga/general.volt index 01e1746bd9..0421762229 100644 --- a/net/frr/src/opnsense/mvc/app/views/OPNsense/Quagga/general.volt +++ b/net/frr/src/opnsense/mvc/app/views/OPNsense/Quagga/general.volt @@ -1,6 +1,6 @@ {# -OPNsense® is Copyright © 2014 – 2017 by Deciso B.V. +OPNsense® is Copyright © 2014 – 2025 by Deciso B.V. This file is Copyright © 2017 by Fabian Franz All rights reserved. @@ -26,32 +26,36 @@ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #} -
    - {{ partial("layout_partials/base_form",['fields':generalForm,'id':'frm_general_settings'])}} -
    -
    - -
    -
    + +
    + {{ partial("layout_partials/base_form",['fields':generalForm,'id':'frm_general_settings'])}} +
    +{{ partial( + 'layout_partials/base_apply_button', + { + 'data_endpoint': '/api/quagga/service/reconfigure', + 'data_service_widget': 'quagga', + 'data_change_message_content': lang._('Apply will reload the service without causing interruptions. Some changes will need a full restart with the available service control buttons.') + } +) }} diff --git a/net/frr/src/opnsense/mvc/app/views/OPNsense/Quagga/log.volt b/net/frr/src/opnsense/mvc/app/views/OPNsense/Quagga/log.volt deleted file mode 100644 index 6f129beb97..0000000000 --- a/net/frr/src/opnsense/mvc/app/views/OPNsense/Quagga/log.volt +++ /dev/null @@ -1,27 +0,0 @@ -
    - - - - - - - - - -
    {{ lang._('Date') }}{{ lang._('Time') }}{{ lang._('Service') }}{{ lang._('Message') }}
    -
    - - diff --git a/net/frr/src/opnsense/mvc/app/views/OPNsense/Quagga/ospf.volt b/net/frr/src/opnsense/mvc/app/views/OPNsense/Quagga/ospf.volt index 9a25d49132..51d069d388 100644 --- a/net/frr/src/opnsense/mvc/app/views/OPNsense/Quagga/ospf.volt +++ b/net/frr/src/opnsense/mvc/app/views/OPNsense/Quagga/ospf.volt @@ -1,6 +1,6 @@ {# -OPNsense® is Copyright © 2014 – 2017 by Deciso B.V. +OPNsense® is Copyright © 2014 – 2025 by Deciso B.V. This file is Copyright © 2017 by Fabian Franz All rights reserved. @@ -27,216 +27,190 @@ POSSIBILITY OF SUCH DAMAGE. #} - - -
    -
    -
    - {{ partial("layout_partials/base_form",['fields':generalForm,'id':'frm_ospf_settings'])}} -
    -
    - -
    -
    -
    + - $("#grid-networks").UIBootgrid( - { 'search':'/api/quagga/ospfsettings/searchNetwork', - 'get':'/api/quagga/ospfsettings/getNetwork/', - 'set':'/api/quagga/ospfsettings/setNetwork/', - 'add':'/api/quagga/ospfsettings/addNetwork/', - 'del':'/api/quagga/ospfsettings/delNetwork/', - 'toggle':'/api/quagga/ospfsettings/toggleNetwork/', - 'options':{selection:false, multiSelect:false} + + + + +
    + +
    + {{ partial("layout_partials/base_form",['fields':generalForm,'id':'frm_ospf_settings'])}} + {{ partial('layout_partials/base_bootgrid_table', formGridEditRedistribution)}} +
    + +
    + {{ partial('layout_partials/base_bootgrid_table', formGridEditOSPFNeighbor)}} +
    + +
    + {{ partial('layout_partials/base_bootgrid_table', formGridEditOSPFArea)}} +
    + +
    + {{ partial('layout_partials/base_bootgrid_table', formGridEditNetwork)}} +
    + +
    + {{ partial('layout_partials/base_bootgrid_table', formGridEditInterface)}} +
    + +
    + {{ partial('layout_partials/base_bootgrid_table', formGridEditPrefixLists)}} +
    + +
    + {{ partial('layout_partials/base_bootgrid_table', formGridEditRouteMaps)}} +
    +
    +{{ partial( + 'layout_partials/base_apply_button', + { + 'data_endpoint': '/api/quagga/service/reconfigure', + 'data_service_widget': 'quagga', + 'data_change_message_content': lang._('Apply will reload the service without causing interruptions. Some changes will need a full restart with the available service control buttons.') } - ); -}); - - -{{ partial("layout_partials/base_dialog",['fields':formDialogEditNetwork,'id':'DialogEditNetwork','label':lang._('Edit Network')])}} -{{ partial("layout_partials/base_dialog",['fields':formDialogEditInterface,'id':'DialogEditInterface','label':lang._('Edit Interface')])}} -{{ partial("layout_partials/base_dialog",['fields':formDialogEditPrefixLists,'id':'DialogEditPrefixLists','label':lang._('Edit Prefix Lists')])}} -{{ partial("layout_partials/base_dialog",['fields':formDialogEditRouteMaps,'id':'DialogEditRouteMaps','label':lang._('Edit Route Maps')])}} +) }} +{{ partial("layout_partials/base_dialog",['fields':formDialogEditOSPFArea,'id':formGridEditOSPFArea['edit_dialog_id'],'label':lang._('Edit Area')])}} +{{ partial("layout_partials/base_dialog",['fields':formDialogEditOSPFNeighbor,'id':formGridEditOSPFNeighbor['edit_dialog_id'],'label':lang._('Edit Neighbor')])}} +{{ partial("layout_partials/base_dialog",['fields':formDialogEditNetwork,'id':formGridEditNetwork['edit_dialog_id'],'label':lang._('Edit Network')])}} +{{ partial("layout_partials/base_dialog",['fields':formDialogEditInterface,'id':formGridEditInterface['edit_dialog_id'],'label':lang._('Edit Interface')])}} +{{ partial("layout_partials/base_dialog",['fields':formDialogEditPrefixLists,'id':formGridEditPrefixLists['edit_dialog_id'],'label':lang._('Edit Prefix Lists')])}} +{{ partial("layout_partials/base_dialog",['fields':formDialogEditRouteMaps,'id':formGridEditRouteMaps['edit_dialog_id'],'label':lang._('Edit Route Maps')])}} +{{ partial("layout_partials/base_dialog",['fields':formDialogEditRedistribution,'id':formGridEditRedistribution['edit_dialog_id'],'label':lang._('Edit Route Redistribution')])}} diff --git a/net/frr/src/opnsense/mvc/app/views/OPNsense/Quagga/ospf6.volt b/net/frr/src/opnsense/mvc/app/views/OPNsense/Quagga/ospf6.volt index e2a8ed98e4..fda09a55d0 100644 --- a/net/frr/src/opnsense/mvc/app/views/OPNsense/Quagga/ospf6.volt +++ b/net/frr/src/opnsense/mvc/app/views/OPNsense/Quagga/ospf6.volt @@ -1,125 +1,169 @@ {# + # Copyright (c) 2014-2025 Deciso B.V. + # Copyright (c) 2017 Fabian Franz + # Copyright (c) 2017 Michael Muenz + # All rights reserved. + # + # Redistribution and use in source and binary forms, with or without modification, + # are permitted provided that the following conditions are met: + # + # 1. Redistributions of source code must retain the above copyright notice, + # this list of conditions and the following disclaimer. + # + # 2. Redistributions in binary form must reproduce the above copyright notice, + # this list of conditions and the following disclaimer in the documentation + # and/or other materials provided with the distribution. + # + # THIS SOFTWARE IS PROVIDED “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + # AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + # AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + # OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + # POSSIBILITY OF SUCH DAMAGE. + #} -OPNsense® is Copyright © 2014 – 2017 by Deciso B.V. -This file is Copyright © 2017 by Fabian Franz -This file is Copyright © 2017 by Michael Muenz -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: + + + + + +
    + +
    + {{ partial("layout_partials/base_form",['fields':ospf6Form,'id':'frm_ospf6_settings'])}} + {{ partial('layout_partials/base_bootgrid_table', formGridEditRedistribution)}} +
    + +
    + {{ partial('layout_partials/base_bootgrid_table', formGridEditNetwork)}} +
    - - - - - - - - - - - - - - - - - - - -
    {{ lang._('Enabled') }}{{ lang._('Interface Name') }}{{ lang._('Area') }}{{ lang._('Network Type') }}{{ lang._('ID') }}{{ lang._('Commands') }}
    - - - -
    + {{ partial('layout_partials/base_bootgrid_table', formGridEditInterface)}}
    - + +
    + {{ partial('layout_partials/base_bootgrid_table', formGridEditPrefixLists)}}
    - - - -{{ partial("layout_partials/base_dialog",['fields':formDialogEditInterface,'id':'DialogEditInterface','label':lang._('Edit Interface')])}} +) }} +{{ partial("layout_partials/base_dialog",['fields':formDialogEditNetwork,'id':formGridEditNetwork['edit_dialog_id'],'label':lang._('Edit Network')])}} +{{ partial("layout_partials/base_dialog",['fields':formDialogEditInterface,'id':formGridEditInterface['edit_dialog_id'],'label':lang._('Edit Interface')])}} +{{ partial("layout_partials/base_dialog",['fields':formDialogEditPrefixLists,'id':formGridEditPrefixLists['edit_dialog_id'],'label':lang._('Edit Prefix Lists')])}} +{{ partial("layout_partials/base_dialog",['fields':formDialogEditRouteMaps,'id':formGridEditRouteMaps['edit_dialog_id'],'label':lang._('Edit Route Maps')])}} +{{ partial("layout_partials/base_dialog",['fields':formDialogEditRedistribution,'id':formGridEditRedistribution['edit_dialog_id'],'label':lang._('Edit Route Redistribution')])}} diff --git a/net/frr/src/opnsense/mvc/app/views/OPNsense/Quagga/rip.volt b/net/frr/src/opnsense/mvc/app/views/OPNsense/Quagga/rip.volt index 8abc7a3f51..332666f2f7 100644 --- a/net/frr/src/opnsense/mvc/app/views/OPNsense/Quagga/rip.volt +++ b/net/frr/src/opnsense/mvc/app/views/OPNsense/Quagga/rip.volt @@ -1,6 +1,6 @@ {# -OPNsense® is Copyright © 2014 – 2017 by Deciso B.V. +OPNsense® is Copyright © 2014 – 2025 by Deciso B.V. This file is Copyright © 2017 by Fabian Franz All rights reserved. @@ -27,33 +27,35 @@ POSSIBILITY OF SUCH DAMAGE. #} -
    - {{ partial("layout_partials/base_form",['fields':ripForm,'id':'frm_rip_settings'])}} -
    -
    - -
    -
    - + +
    + {{ partial("layout_partials/base_form",['fields':ripForm,'id':'frm_rip_settings'])}} +
    +{{ partial( + 'layout_partials/base_apply_button', + { + 'data_endpoint': '/api/quagga/service/reconfigure', + 'data_service_widget': 'quagga', + 'data_change_message_content': lang._('Apply will reload the service without causing interruptions. Some changes will need a full restart with the available service control buttons.') + } +) }} diff --git a/net/frr/src/opnsense/mvc/app/views/OPNsense/Quagga/static.volt b/net/frr/src/opnsense/mvc/app/views/OPNsense/Quagga/static.volt new file mode 100644 index 0000000000..33c01a198d --- /dev/null +++ b/net/frr/src/opnsense/mvc/app/views/OPNsense/Quagga/static.volt @@ -0,0 +1,82 @@ +{# + # Copyright (c) 2024 Deciso B.V. + # Copyright (c) 2024 Mike Shuey + # All rights reserved. + # + # Redistribution and use in source and binary forms, with or without modification, + # are permitted provided that the following conditions are met: + # + # 1. Redistributions of source code must retain the above copyright notice, + # this list of conditions and the following disclaimer. + # + # 2. Redistributions in binary form must reproduce the above copyright notice, + # this list of conditions and the following disclaimer in the documentation + # and/or other materials provided with the distribution. + # + # THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + # AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + # AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + # OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + # POSSIBILITY OF SUCH DAMAGE. + #} + + + + + + +
    + +
    + {{ partial("layout_partials/base_form",['fields':staticForm,'id':'frm_static_settings'])}} +
    + +
    + {{ partial('layout_partials/base_bootgrid_table', formGridEditSTATICRoute)}} +
    +
    +{{ partial( + 'layout_partials/base_apply_button', + { + 'data_endpoint': '/api/quagga/service/reconfigure', + 'data_service_widget': 'quagga', + 'data_change_message_content': lang._('Apply will reload the service without causing interruptions. Some changes will need a full restart with the available service control buttons.') + } +) }} +{{ partial("layout_partials/base_dialog",['fields':formDialogEditSTATICRoute,'id':formGridEditSTATICRoute['edit_dialog_id'],'label':lang._('Edit Routes')])}} diff --git a/net/frr/src/opnsense/scripts/frr/carp_event_handler b/net/frr/src/opnsense/scripts/frr/carp_event_handler new file mode 100755 index 0000000000..5baa2f63a3 --- /dev/null +++ b/net/frr/src/opnsense/scripts/frr/carp_event_handler @@ -0,0 +1,45 @@ +#!/usr/local/bin/python3 +""" + Copyright (c) 2020 Ad Schellevis + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + +""" +import sys +import syslog +import lib.events +from lib import InterfaceStatus, VtySH + +if __name__ == '__main__': + syslog.openlog('frr_carp', facility=syslog.LOG_LOCAL1) + syslog.syslog(syslog.LOG_NOTICE, 'FRR received carp configuration event.') + ifstatus = InterfaceStatus() + vtysh = VtySH() + if vtysh.is_active: + for event in lib.events.get_events(): + event_object = event(ifstatus=ifstatus, vtysh=vtysh) + if event_object.should_run: + syslog.syslog(syslog.LOG_NOTICE, 'FRR trigger %s event.' % event_object.__class__.__name__) + event_object.execute() + else: + syslog.syslog(syslog.LOG_ERR, 'no frr deamons active.') diff --git a/net/frr/src/opnsense/scripts/frr/frr_wrapper.sh b/net/frr/src/opnsense/scripts/frr/frr_wrapper.sh new file mode 100755 index 0000000000..5b3a812991 --- /dev/null +++ b/net/frr/src/opnsense/scripts/frr/frr_wrapper.sh @@ -0,0 +1,55 @@ +#!/bin/sh + +# Copyright (c) 2025 Andy Binder +# +# 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. + +# Service wrapper for starting/restarting frr service +# This wrapper is needed to react on specific service interactions through watchfrr. +# Startup details with watchfrr enabled (default): +# 1. "service frr start" calls "service frr start watchfrr" +# 2. watchfrr once started calls "service frr restart all" +# 3. "restart all" need to loop the list of $frr_daemons to start each +# of then +# 4. vtysh -b is executed to load boot startup configuration + +ACTION="$1" +COMMAND="$2" + +/usr/sbin/service frr "$ACTION" "$COMMAND" +SERVICE_EXIT_CODE=$? + +# If frr starts/restarts ospfd, e.g. on process error (parameter: start/restart ospfd) +if [ "$2" = "ospfd" ]; then + logger -t frr_wrapper "WATCHFRR - OSPFD - Starting CARP event handler now" + /usr/local/opnsense/scripts/frr/carp_event_handler +fi +# If frr starts up (parameter: restart all) +if [ "$2" = "all" ]; then + ( + /usr/bin/logger -t frr_wrapper "WATCHFRR - STARTUP - Starting CARP event handler now" + /usr/local/opnsense/scripts/frr/carp_event_handler + ) & +fi +exit $SERVICE_EXIT_CODE diff --git a/net/frr/src/opnsense/scripts/frr/lib/__init__.py b/net/frr/src/opnsense/scripts/frr/lib/__init__.py new file mode 100755 index 0000000000..21ef2ce233 --- /dev/null +++ b/net/frr/src/opnsense/scripts/frr/lib/__init__.py @@ -0,0 +1,111 @@ +""" + Copyright (c) 2020 Ad Schellevis + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + +""" +import time +import subprocess +import ujson +from collections.abc import Callable + + +class InterfaceStatus: + def __init__(self): + self._carp_addresses = dict() + self.parse() + + def parse(self): + """ parse ifconfig output + """ + carp_statuses = dict() + carp_addresses = dict() + current_if = None + for line in subprocess.run(['/sbin/ifconfig', '-a'], capture_output=True, text=True).stdout.split('\n'): + parts = line.split() + if not line.startswith('\t'): + current_if = line.split(':')[0] + elif line.startswith('\tcarp: '): + carp_statuses[parts[3]] = parts[1] + elif line.find('vhid') > -1: + carp_addresses[parts[1]] = {'vhid': parts[-1], 'status': 'none'} + + for address in carp_addresses: + if carp_addresses[address]['vhid'] in carp_statuses: + carp_addresses[address]['status'] = carp_statuses[carp_addresses[address]['vhid']].strip().lower() + + self._carp_addresses = carp_addresses + + def address_status(self, address: str): + if address in self._carp_addresses: + return self._carp_addresses[address]['status'] + return 'none' + + +class VtySHExecError(Exception): + pass + +class VtySH: + def __init__(self): + self._daemons = [] + self.init() + + def init(self): + # wait a maximum of 5 seconds for daemon to startup + for i in range(5): + try: + self._daemons = self.execute('show daemons', lambda x: x.decode().split()) + break + except VtySHExecError: + time.sleep(1) + + def is_running(self, daemon: str): + return daemon in self._daemons + + @property + def is_active(self): + return len(self._daemons) > 0 + + def execute(self, command: str, translate: Callable=ujson.loads, configure: bool=False): + args = ['/usr/local/bin/vtysh'] + if configure: + args = args + ['-c', 'configure terminal'] + else: + args.append('-u') + + if type(command) is list: + for cmd in command: + args = args + ['-c', cmd] + else: + args = args + ['-c', command] + + response = subprocess.run(args, capture_output=True) + if response.stderr: + raise VtySHExecError(response.stderr) + if translate: + try: + return translate(response.stdout) + except ValueError: + raise ValueError(response.stdout) + else: + return response.stdout diff --git a/net/frr/src/opnsense/scripts/frr/lib/base.py b/net/frr/src/opnsense/scripts/frr/lib/base.py new file mode 100755 index 0000000000..075bf0032b --- /dev/null +++ b/net/frr/src/opnsense/scripts/frr/lib/base.py @@ -0,0 +1,40 @@ +""" + Copyright (c) 2020 Ad Schellevis + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + +""" +from . import InterfaceStatus, VtySH + + +class BaseEventHandler: + def __init__(self, ifstatus: InterfaceStatus, vtysh: VtySH): + self.ifstatus = ifstatus + self.vtysh = vtysh + + @property + def should_run(self): + return False + + def execute(self): + pass diff --git a/net/frr/src/opnsense/scripts/frr/lib/events/__init__.py b/net/frr/src/opnsense/scripts/frr/lib/events/__init__.py new file mode 100755 index 0000000000..b9dfe7d8b0 --- /dev/null +++ b/net/frr/src/opnsense/scripts/frr/lib/events/__init__.py @@ -0,0 +1,44 @@ +""" + Copyright (c) 2020 Ad Schellevis + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + +""" +import glob +import importlib +import sys +import os +from ..base import BaseEventHandler + + +def get_events(): + """ iterate event handlers + """ + for filename in glob.glob("%s/*.py" % os.path.dirname(__file__)): + importlib.import_module(".%s" % os.path.splitext(os.path.basename(filename))[0], __name__) + + for module_name in dir(sys.modules[__name__]): + for attribute_name in dir(getattr(sys.modules[__name__], module_name)): + cls = getattr(getattr(sys.modules[__name__], module_name), attribute_name) + if isinstance(cls, type) and issubclass(cls, BaseEventHandler) and cls != BaseEventHandler: + yield cls diff --git a/net/frr/src/opnsense/scripts/frr/lib/events/ospf6d.py b/net/frr/src/opnsense/scripts/frr/lib/events/ospf6d.py new file mode 100755 index 0000000000..42d86c9d9d --- /dev/null +++ b/net/frr/src/opnsense/scripts/frr/lib/events/ospf6d.py @@ -0,0 +1,107 @@ +""" + Copyright (c) 2022 Ad Schellevis + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + +""" +import os +import syslog +from configparser import ConfigParser +from ..base import BaseEventHandler + + +class Ospf6dEventHandler(BaseEventHandler): + _config = '/usr/local/etc/frr/ospf6d_carp.conf' + + @property + def should_run(self): + return self.vtysh.is_running('ospf6d') + + def _read_config(self): + result = dict() + if os.path.isfile(self._config): + cnf = ConfigParser() + cnf.read(self._config) + not_empty = lambda x, y: cnf.has_option(x, y) and cnf.get(x, y) != '' and cnf.get(x, y) != '0' + for section in cnf.sections(): + if not_empty(section, 'interface') and not_empty(section, 'interface') \ + and not_empty(section, 'demoted_cost') and not_empty(section, 'carp_depend_on'): + default_cost = cnf.getint(section, 'default_cost') if not_empty(section, 'default_cost') else None + result[cnf.get(section, 'interface')] = { + 'demoted_cost': cnf.getint(section, 'demoted_cost'), + 'carp_depend_on': cnf.get(section, 'carp_depend_on'), + 'default_cost': default_cost, + } + + return result + + def execute(self): + if os.path.isfile(self._config): + # parse ospf6 interface data, keep structure similar to what ospf offers when using json output + ospf_interfaces = { + 'interfaces': {} + } + this_interface = None + for line in self.vtysh.execute('show ipv6 ospf6 interface', translate=None).decode().split('\n'): + if len(line) > 0 and line[0] != ' ': + this_interface = line.split()[0] + ospf_interfaces['interfaces'][this_interface] = {} + elif this_interface is not None: + if line.find('Area ID') > 0 and line.split()[-1].isdigit(): + # Area ID X.X.X.X, Cost XXXX + ospf_interfaces['interfaces'][this_interface]['cost'] = int(line.split()[-1]) + + config_interfaces = self._read_config() + for intf in config_interfaces: + if 'interfaces' in ospf_interfaces and intf in ospf_interfaces['interfaces']: + ospf_intf_cost = ospf_interfaces['interfaces'][intf]['cost'] + is_intf_master = self.ifstatus.address_status(config_interfaces[intf]['carp_depend_on']) == 'master' + is_ospf_dem = ospf_intf_cost == config_interfaces[intf]['demoted_cost'] + if is_intf_master and is_ospf_dem: + # promote ospf6 interface + conf_cost = config_interfaces[intf]['default_cost'] + if conf_cost is None: + syslog.syslog( + syslog.LOG_NOTICE, 'ospf6d promote interface %s (no default cost configured).' % intf + ) + self.vtysh.execute( + ['interface %s' % intf, 'no ipv6 ospf6 cost'], translate=None, configure=True + ) + elif conf_cost != ospf_intf_cost: + syslog.syslog( + syslog.LOG_NOTICE, 'ospf6d promote interface %s (cost %d).' % (intf, conf_cost) + ) + self.vtysh.execute( + ['interface %s' % intf, 'ipv6 ospf6 cost %d' % conf_cost], + translate=None, configure=True + ) + elif not is_intf_master and not is_ospf_dem: + # demote ospf6 interface + conf_cost = config_interfaces[intf]['demoted_cost'] + syslog.syslog( + syslog.LOG_NOTICE, 'ospf6d demote interface %s (cost %d).' % (intf, conf_cost) + ) + self.vtysh.execute( + ['interface %s' % intf, 'ipv6 ospf6 cost %d' % conf_cost], + translate=None, configure=True + ) diff --git a/net/frr/src/opnsense/scripts/frr/lib/events/ospfd.py b/net/frr/src/opnsense/scripts/frr/lib/events/ospfd.py new file mode 100755 index 0000000000..7eabadb9b7 --- /dev/null +++ b/net/frr/src/opnsense/scripts/frr/lib/events/ospfd.py @@ -0,0 +1,94 @@ +""" + Copyright (c) 2020 Ad Schellevis + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + +""" +import os +import syslog +from configparser import ConfigParser +from ..base import BaseEventHandler + + +class OspfdEventHandler(BaseEventHandler): + _config = '/usr/local/etc/frr/ospfd_carp.conf' + + @property + def should_run(self): + return self.vtysh.is_running('ospfd') + + def _read_config(self): + result = dict() + if os.path.isfile(self._config): + cnf = ConfigParser() + cnf.read(self._config) + not_empty = lambda x, y: cnf.has_option(x, y) and cnf.get(x, y) != '' and cnf.get(x, y) != '0' + for section in cnf.sections(): + if not_empty(section, 'interface') and not_empty(section, 'interface') \ + and not_empty(section, 'demoted_cost') and not_empty(section, 'carp_depend_on'): + default_cost = cnf.getint(section, 'default_cost') if not_empty(section, 'default_cost') else None + result[cnf.get(section, 'interface')] = { + 'demoted_cost': cnf.getint(section, 'demoted_cost'), + 'carp_depend_on': cnf.get(section, 'carp_depend_on'), + 'default_cost': default_cost, + } + + return result + + def execute(self): + if os.path.isfile(self._config): + ospf_interfaces = self.vtysh.execute('show ip ospf interface json') + config_interfaces = self._read_config() + for intf in config_interfaces: + if 'interfaces' in ospf_interfaces and intf in ospf_interfaces['interfaces']: + ospf_intf_cost = ospf_interfaces['interfaces'][intf]['cost'] + is_intf_master = self.ifstatus.address_status(config_interfaces[intf]['carp_depend_on']) == 'master' + is_ospf_dem = ospf_intf_cost == config_interfaces[intf]['demoted_cost'] + if is_intf_master and is_ospf_dem: + # promote ospf interface + conf_cost = config_interfaces[intf]['default_cost'] + if conf_cost is None: + syslog.syslog( + syslog.LOG_NOTICE, 'ospfd promote interface %s (no default cost configured).' % intf + ) + self.vtysh.execute( + ['interface %s' % intf, 'no ip ospf cost'], translate=None, configure=True + ) + elif conf_cost != ospf_intf_cost: + syslog.syslog( + syslog.LOG_NOTICE, 'ospfd promote interface %s (cost %d).' % (intf, conf_cost) + ) + self.vtysh.execute( + ['interface %s' % intf, 'ip ospf cost %d' % conf_cost], + translate=None, configure=True + ) + elif not is_intf_master and not is_ospf_dem: + # demote ospf interface + conf_cost = config_interfaces[intf]['demoted_cost'] + syslog.syslog( + syslog.LOG_NOTICE, 'ospfd demote interface %s (cost %d).' % (intf, conf_cost) + ) + self.vtysh.execute( + ['interface %s' % intf, 'ip ospf cost %d' % conf_cost], + translate=None, configure=True + ) diff --git a/net/frr/src/opnsense/scripts/frr/register_sas b/net/frr/src/opnsense/scripts/frr/register_sas new file mode 100755 index 0000000000..33d4fcee7c --- /dev/null +++ b/net/frr/src/opnsense/scripts/frr/register_sas @@ -0,0 +1,68 @@ +#!/usr/local/bin/python3 +""" + Copyright (c) 2022 Ad Schellevis + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + +""" +import os +import subprocess +import tempfile +from configparser import ConfigParser + +if __name__ == '__main__': + frr_sad = {} + frr_sa_database = "/usr/local/etc/frr/sa_policies.conf" + # stage 1, read required FRR policies + if os.path.exists(frr_sa_database): + cnf = ConfigParser() + cnf.read(frr_sa_database) + for section in cnf.sections(): + if cnf.has_option(section, 'src') and cnf.has_option(section, 'dst'): + policy_key = "%s %s" % (cnf.get(section, 'src'), cnf.get(section, 'dst')) + frr_sad[policy_key] = {} + for prop in cnf.items(section): + frr_sad[policy_key][prop[0]] = prop[1] + + # stage 2, red current installed policies which seems to originate from FRR + registered_policies = [] + current_policy = None + for line in subprocess.run(["/sbin/setkey", "-D"], capture_output=True, text=True).stdout.split('\n'): + parts = line.strip().split() + if not line.startswith('\t') and len(parts) > 1: + current_policy = {"src": parts[0], "dst": parts[1]} + elif len(parts) > 2 and parts[0] == 'A:' and parts[1] == 'tcp-md5': + # Let's assume we're the only ones registering these types of entries + registered_policies.append(current_policy) + + # flush changes to temp file and load with setkey + temp_filename = None + with tempfile.NamedTemporaryFile(mode='wt', delete=False) as fo: + temp_filename = fo.name + for policy in registered_policies: + fo.write("delete -n %(src)s %(dst)s tcp 0x1000;\n" % policy) + for new_policy in frr_sad: + fo.write('add -n %(src)s %(dst)s %(protocol)s %(spi)s -A %(aalgo)s "%(key)s";\n' % frr_sad[new_policy]) + + if temp_filename: + subprocess.run(["/sbin/setkey", "-f", fo.name], capture_output=True, text=True) diff --git a/net/frr/src/opnsense/scripts/frr/setup.sh b/net/frr/src/opnsense/scripts/frr/setup.sh new file mode 100755 index 0000000000..bda37d2c33 --- /dev/null +++ b/net/frr/src/opnsense/scripts/frr/setup.sh @@ -0,0 +1,37 @@ +#!/bin/sh + +user=frr +group=frr + +mkdir -p /var/run/frr +chown $user:$group /var/run/frr +chmod 750 /var/run/frr + +mkdir -p /usr/local/etc/frr +chown $user:$group /usr/local/etc/frr +chmod 750 /usr/local/etc/frr + +# ensure that frr can read the configuration files +chown -R $user:$group /usr/local/etc/frr +chown -R $user:$group /var/run/frr + +# logfile (if used) +touch /var/log/frr.log +chown $user:$group /var/log/frr.log + +# register Security Associations +/usr/local/opnsense/scripts/frr/register_sas + +# delete stale configuration files from frr.conf migration +files_to_delete=" + /etc/rc.d/watchfrr + /usr/local/etc/frr/bfdd.conf + /usr/local/etc/frr/bgpd.conf + /usr/local/etc/frr/ospfd.conf + /usr/local/etc/frr/ospf6d.conf + /usr/local/etc/frr/ripd.conf + /usr/local/etc/frr/staticd.conf + /usr/local/etc/frr/zebra.conf +" + +rm -f $files_to_delete diff --git a/net/frr/src/opnsense/scripts/quagga/diag-bgp.sh b/net/frr/src/opnsense/scripts/quagga/diag-bgp.sh deleted file mode 100755 index 4d6e81f268..0000000000 --- a/net/frr/src/opnsense/scripts/quagga/diag-bgp.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/sh - -case "$1" in - bgp) - vtysh -d bgpd -c "show ip bgp" - ;; - summary) - vtysh -d bgpd -c "show bgp summary" - ;; - neighbor) - vtysh -d bgpd -c "show ip bgp neighbors $2" - ;; - neighbor-adv) - vtysh -d bgpd -c "show ip bgp neighbors $2 advertised-routes" - ;; - *) - echo "Usage: $0 bgp|summary|neighbor |neighbor-adv " - exit 1 -esac -exit 0 diff --git a/net/frr/src/opnsense/scripts/quagga/quagga.rb b/net/frr/src/opnsense/scripts/quagga/quagga.rb deleted file mode 100755 index e8881a4d06..0000000000 --- a/net/frr/src/opnsense/scripts/quagga/quagga.rb +++ /dev/null @@ -1,768 +0,0 @@ -#!/usr/local/bin/ruby -=begin -Copyright 2017 Fabian Franz -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 COPYRIGHT HOLDERS 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 COPYRIGHT HOLDER 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. -=end - -require 'json' -require 'shellwords' -require 'pp' - -$QUAGGA_DEBUG = false - -class VTYSH - def initialize(path = '/usr/local/bin/vtysh') - @path = path - end - - def execute(param) - o = `#{@path} -c #{param.shellescape}` - raise "error" if o.length <= 2 - raise "command error - command: #{param}" if o.include? "% Unknown command" - o - end - - #def execute(param) - # fn = param.sub("show","sh").gsub(" ","_") - # File.read(fn) - #end -end - -class QuaggaTableReader - attr_accessor :headers - def initialize(headers = []) - @headers = headers - end - def read_headline(line, start_without_header = false, start_without_header_name = 'status') - # get begin of header (number of the first char of the string) - header = line - header_offset = {} - header_offset[0] = start_without_header_name if start_without_header - @headers.map do |x| - header_offset[header.index(x)] = x.strip - end - - - # make ranges: this will make a range of the first char of the sting until - # the the char befor the next heading begins - ranges = [] - 0.upto (header_offset.keys.length - 2) do |i| - ranges << ((header_offset.keys[i])...(header_offset.keys[i + 1])) - end - # the last one has no next heading - this will go to the end of the line - ranges.push ((header_offset.keys.last)..-1) # path - @header_offset = header_offset - @ranges = ranges - nil - end - - def read_entry(line, expand_fields = {}) - raise "heading missing" unless @ranges - tmp = {} - return tmp unless line&.strip.length > 2 - - @ranges.each do |r| - # the string starts here - b = r.begin - # get the heading starting where the string starts - n = @header_offset[b] - # get the data or return an empty string - tmp[n] = line[r]&.strip || "" - end - # replace characters by the meaning - expand_fields.keys.each do |key| - tmp[key] = tmp[key].split("").map {|x| {dn: expand_fields[key][x], abb: x} } if tmp[key] - end - tmp - end -end - -class General - def initialize(vtysh) - @vtysh = vtysh - end - def routes(ipv6 = false) - lines = @vtysh.execute("show ip#{ipv6 ? 'v6' : ''} route").lines - - # headers - meanings = {} - while (line = lines.shift.strip) != '' - line = line.gsub('Codes: ','') - line.split(",").each do |meaning| - short, long = meaning.strip.split(" - ") - meanings[short] = long - end - end - - # you don't have to understand this regex ;) - entry_regex = /(\S+?)\s+?(\S+?)(?: \[(\d+)\/(\d+)\])? (?:(?:via (\S+?)|is ([^,]+?)|), ([^,\n]+)|(unreachable \(blackhole\)))(?:, (\S+))?/ - entries = [] - while (line = lines.shift&.strip) - if line.length > 10 - code, network, ad, metric, via, direct, interface, unreachable, time = line.scan(entry_regex).first - code = code.split('').map {|c| {short: c, long: meanings[c]}} - entries << {code: code, network: (network || direct), ad: ad, via: via || unreachable, metric: metric, interface: interface, time: time } - end - end - entries - end - - def routes6 - routes(true) - end - - def log - File.read('/var/log/frr.log').lines.select {|l| l.strip.length > 10}.map do |line| - date, time, service, message = line.split(' ', 4) - date = date.split('/').reverse.join(".") # format dd.mm.yyyy - service = service.split(':').first if service - {date: date, time:time, service: service, message: message } - end - end -end - -class OSPF - def initialize(vtysh) - @vtysh = vtysh - end - def neighbors - qta = QuaggaTableReader.new(["Neighbor ID", "Pri", "State", "Dead Time", "Address", "Interface", "RXmtL", "RqstL", "DBsmL"]) - lines = @vtysh.execute("show ip ospf neighbor").lines - lines.shift # empty line - data = [] - qta.read_headline(lines.shift) - while (line = lines.shift) && (line.length > 2) - data << qta.read_entry(line) - end - data - end - - def interface - lines = @vtysh.execute("show ip ospf interface").lines - interfaces = {} - current_if = '' - while line = lines.shift - next if line.strip.length <= 1 - if line[0] != ' ' # we are in a heading - current_if = line.split(" ").first - interfaces[current_if] = {} - current_if = interfaces[current_if] - current_if[:enabled] = true - lines.shift - else - line.strip! - case line - when 'OSPF not enabled on this interface' - current_if[:enabled] = false - when /Internet Address ([^,]+?), Broadcast ([^,]+?), Area (.*)/ - current_if[:address] = $1 - current_if[:broadcast] = $2 - current_if[:area] = $3 - when /MTU mismatch detection:(.*)/ - current_if[:mtu_mismatch_detection] = ($1 == 'enabled') - when /Router ID ([^,]+?), Network Type ([^,]+?), Cost: (\d+)/ - current_if[:router_id] = $1 - current_if[:network_type] = $2 - current_if[:cost] = $3.to_i - when /Transmit Delay is (\d+) sec, State ([^,]+?), Priority (\d+)/ - current_if[:transmit_delay] = $1.to_i - current_if[:state] = $2 - current_if[:priority] = $3.to_i - when "No designated router on this network" - current_if[:designated_router] = nil - when /Designated Router \(ID\) ([^,]+?), Interface Address (.*)/ - current_if[:designated_router] = $1 - current_if[:designated_router_interface_address] = $2 - when "No backup designated router on this network" - current_if[:backup_designated_router] = nil - when /Timer intervals configured, Hello (\d+)s, Dead (\d+)s, Wait (\d+)s, Retransmit (\d+)/ - current_if[:intervals] = {hello: $1.to_i, dead: $2.to_i, wait: $3.to_i, retransmit: $4.to_i} - when /Multicast group memberships: (.*)/ - current_if[:multicast_group_memberships] = $1.split(" ") - when /Hello due in ([\d\.]+|inactive)s?/ - current_if[:hello_due_in] = $1 == 'inactive' ? $1 : $1.to_f - when /Neighbor Count is (\d+), Adjacent neighbor count is (\d+)/ - current_if[:neighbor_count] = $1.to_i - current_if[:adjacent_neighbor_count] = $2.to_i - else - # make sure there is an array to write in - current_if[:unparsed] ||= [] - current_if[:unparsed] << line - puts line if $QUAGGA_DEBUG - end - end - end - interfaces - end - - def database - lines = @vtysh.execute("show ip ospf database").lines - db = {} - heading = '' - router = '' - router_link_states_area = '' - mode = :none - qta = nil - while line = lines.shift - next if line == '' - if line[0] == ' ' # heading - heading = line.strip - case heading - when /OSPF Router with ID \(([\.\d]+)\)/ - router = $1 - db[router] ||= {} - mode = :router - when /Router Link States \(Area ([\.\d]+)\)/ - router_link_states_area = $1 - db[router]['router_link_state_area'] ||= {} - db[router]['router_link_state_area'][$1] ||= [] - mode = :router_link_state - qta = nil - when /Net Link States \(Area ([\.\d]+)\)/ - net_link_states_area = $1 - db[router]['net_link_state_area'] ||= {} - db[router]['net_link_state_area'][$1] ||= [] - mode = :net_link_state - qta = nil - when 'AS External Link States' - mode = :states - db[router]['external_states'] ||= [] - qta = nil - else - puts "unknown heading" if $QUAGGA_DEBUG - end - else - if qta == nil - case mode - when :router_link_state - qta = QuaggaTableReader.new(["Link ID", "ADV Router", "Age", "Seq#", "CkSum", "Link count"]) - when :net_link_state - qta = QuaggaTableReader.new(["Link ID", "ADV Router", "Age", "Seq#", "CkSum"]) - when :states - qta = QuaggaTableReader.new(["Link ID", "ADV Router", "Age", "Seq#", "CkSum", "Route\n"]) - else - next - end - headline = lines.shift - qta.read_headline(headline,true) - else - entry = qta.read_entry(line) - case mode - when :router_link_state - db[router]['router_link_state_area'][router_link_states_area] << entry - when :net_link_state - db[router]['net_link_state_area'][net_link_states_area] << entry - when :states - db[router]['external_states'] << entry - end - end - end - # table - end - db - end - - def route - lines = @vtysh.execute("show ip ospf route").lines - heading = '' - route = {} - last_line = [] - while line = lines.shift - if line[0] == "=" #heading - heading = line.scan(/=* ([^=]*) =*/).first.first - route[heading] = [] - else # data - case line.strip - when /N\s+([\d\.\/]+)\s+\[(\d+)\]\s+area:\s(.*)/ - last_line = {network: $1, cost: $2.to_i, area: $3, type: 'N'} - route[heading] << last_line - when /N (E(?:\d+) (?:\S+))\s+\[([\d\/]+)\] tag: (\d+)/ - last_line = {network: $1, cost: $2, tag: $3.to_i, type: 'N'} - route[heading] << last_line - when /(?:(directly attached) to|via ([^,]+),) (.*)/ - last_line[:via] = $1 || $2 - last_line[:via_interface] = $3 - when /R\s+(\S+)\s+\[(\d+)\] area: ([^,]+)(, ASBR)/ - last_line = {ip: $1, cost: $2.to_i, area: $3, asbr: (", ASBR" == $4), type: 'R'} - route[heading] << last_line - else - puts line if $QUAGGA_DEBUG - end - end - end - route - end - - def overview - lines = @vtysh.execute("show ip ospf").lines - overview = {rfc2328_conform: false, asbr: false} - while line = lines.shift&.strip - case line - when /OSPF Routing Process, Router ID: ([\d\.]+)/ - overview[:router_id] = $1 - when "This implementation conforms to RFC2328" - overview[:rfc2328_conform] = true - when /OpaqueCapability flag is (\S+)/ - overview[:opaque_capability] = ($1 == 'enabled') - when /Initial SPF scheduling delay (\d+) millisec\(s\)/ - overview[:initial_spf_scheduling_delay] = $1.to_i - when /(Min|Max)imum hold time between consecutive SPFs (\d+) millisec\(s\)/ - overview[:hold_time] ||= {} - overview[:hold_time][$1.downcase] = $2.to_i - when "This router is an ASBR (injecting external routing information)" - overview[:asbr] = true - when /Number of external LSA (\d+). Checksum Sum ([x\d]+)/ - overview[:external_lsa] = {count: $1.to_i, checksum: $2} - when /Number of opaque AS LSA (\d+). Checksum Sum ([x\d]+)/ - overview[:opaque_as_lsa] = {count: $1.to_i, checksum: $2} - when /Refresh timer (\d+) secs/ - overview[:refresh_timer] = $1.to_i - when /Number of areas attached to this router: (\d+)/ - overview[:areas_attached_count] = $1.to_i - when /Hold time multiplier is currently (\d+)/ - overview[:current_hold_time_multipier] = $1.to_i - when /RFC1583Compatibility flag is (\S+)/ - overview[:rfc1583_compatibility] = ($1 == 'enabled') - when /SPF timer is (.*)/ - overview[:spf_timer] = $1 - when "" - break - else - puts line if $QUAGGA_DEBUG - end - end - # general overview has ended - now the area overviews come - overview[:areas] = {} - current_area = {} - while line = lines.shift&.strip - case line - when /Area ID: (.*)/ - current_area = {} - overview[:areas][$1] = current_area - when /Number of interfaces in this area: Total: (\d+), Active: (\d+)/ - current_area[:interfaces] = {total: $1.to_i,active: $2.to_i} - when /Number of (router|network|summary) LSA (\d+). Checksum Sum ([\da-fx]+)/ - current_area[:lsa] ||= {} - current_area[:lsa][$1] = {count: $2.to_i, checksum: $3} - when /Number of LSA (\d+)/ - current_area[:lsa] ||= {} - current_area[:lsa][:count] = $1.to_i - when /Number of (opaque (?:area|link)|NSSA|ASBR summary) LSA (\d+). Checksum Sum ([\da-fx]+)/ - current_area[:lsa] ||= {} - current_area[:lsa][$1] = {count: $2.to_i, checksum: $3} - when /Number of fully adjacent neighbors in this area: (\d+)/ - current_area[:fully_adjacent_neighbor_count] = $1.to_i - when /SPF algorithm executed (\d) times/ - current_area[:spf_exec_count] = $1.to_i - when "Area has no authentication" - current_area[:auth] = "none" - else - puts line if $QUAGGA_DEBUG - end - end - overview - end -end - -class BGP - def initialize(sh) - @vtysh = sh - end - - def overview - output = @vtysh.execute('show ip bgp') - return {} if output.include? "No BGP process is configured" - return {} unless output.include? 'version' # we get an empty output if quagga is not running - output = output.split("\n") - bgp = {} - - # Process the header/definitions - while line = output.shift&.rstrip - case line - when /^BGP table version/ - x,y = line.scan(/.*?version is (\d+).*?ID is ([0-9\.]+).*/).first - bgp['table_version'] = x - bgp['local_router_id'] = y - when /^Status codes/ - # find out, what the status abbreviations mean - status_codes = {} - line.split(":").last.strip.split(",").each do |x| - k,v = x.strip.split(" ") - status_codes[k] = v - end - while line.end_with? "," - line = output.shift - line.strip.split(",").each do |x| - k,v = x.strip.split(" ") - status_codes[k] = v - end - end - when /^Origin codes/ - # same like before but for the origin codes - origin_codes = {} - line.split(":").last.strip.split(",").each do |x| - k,v = x.strip.split(" - ") - origin_codes[k] = v - end - when /^Nexthop codes/ - # Just eat this line, nothing to do with it - when "" - # Found the end of the header, reached the table - break - else - # eat all other (unexpected) lines - puts line if $QUAGGA_DEBUG - end - end - - # Process the tabular data - bgp['output'] = [] - qta = QuaggaTableReader.new(["Network", "Next Hop", "Metric", "LocPrf", "Weight", "Path"]) - qta.read_headline(output.shift,true) - while line = output.shift&.strip - break if line == '' - data = qta.read_entry(line) - data['status'] = data['status'].split("").map {|x| {dn: status_codes[x], abb: x} } - data['Path'] = data['Path'].split("").map {|x| {dn: origin_codes[x], abb: x} } - bgp['output'] << data - end - bgp - end -end - -class OSPFv3 - def initialize(sh) - @vtysh = sh - end - - def overview - lines = @vtysh.execute("show ipv6 ospf6").lines - overview = {} - while line = lines.shift&.strip - case line - when /OSPFv3 Routing Process \((\d+)\) with Router-ID ([\d\.]+)/ - overview[:router_id] = $2 - overview[:routing_process] = $1.to_i - when /Initial SPF scheduling delay (\d+) millisec\(s\)/ - overview[:initial_spf_scheduling_delay] = $1.to_i - # this line contains a typo in the output - I made it to work with and without - # this typo - when /(Min|Max)imum hold time between consecutive SPFs (\d+) milli?second\(s\)/ - overview[:hold_time] ||= {} - overview[:hold_time][$1.downcase] = $2.to_i - when "This router is an ASBR (injecting external routing information)" - overview[:asbr] = true - when /SPF timer is (.*)/ - overview[:spf_timer] = $1 - when /Running (.*)/ - overview[:running_time] = $1 - when /Number of AS scoped LSAs is (\d+)/ - overview[:number_as_scoped] = $1.to_i - when /Hold time multiplier is currently (\d+)/ - overview[:current_hold_time_multipier] = $1.to_i - when /Number of areas in this router is (\d+)/ - overview[:number_of_areas] = $1.to_i - when "" - break - else - # debug - puts line if $QUAGGA_DEBUG - end - end - # general overview has ended - now the area overviews come - overview[:areas] = {} - current_area = {} - while line = lines.shift&.strip - case line - when /^Area ([\d\.]*)/ - current_area = {} - overview[:areas][$1] = current_area - when /Interface attached to this area: (.*)/ - current_area[:interfaces] = $1.split(" ") - when /Number of Area scoped LSAs is (.*)/ - current_area[:number_lsas] = $1.to_i - else - puts line if $QUAGGA_DEBUG - end - end - overview - end - - def linkstate - lines = @vtysh.execute("show ipv6 ospf6 linkstate").lines - linkstate = {} - - qta = nil - current_area = [] - while line = lines.shift&.strip - case line - when /SPF Result in Area (.*)/ - linkstate[$1] = current_area = [] - qta = QuaggaTableReader.new(["Type","Router-ID", "Net-ID", "Rtr-Bits", "Options", "Cost"]) - lines.shift - qta.read_headline(lines.shift) - else - if line.length > 10 - current_area << qta.read_entry(line) - end - end - end - linkstate - end - - def route - route = [] - lines = @vtysh.execute("show ipv6 ospf6 route").lines - - lines.each do |line| - f1, f2, network, gateway, interface, time = line.strip.split(/\s+/) - route << { f1: f1, - f2: f2, - network: network, - gateway: gateway, - interface: interface, - time: time } - end - route - end - - def neighbors - qta = QuaggaTableReader.new(["Neighbor ID","Pri", "DeadTime", "State/IfState", "Duration I/F[State]"]) - neighbor = [] - nb = @vtysh.execute("show ipv6 ospf6 neighbor").lines - qta.read_headline(nb.shift.strip) - while line = nb.shift&.strip - puts line - if line.length > 10 - tmp = qta.read_entry(line) - tmp['Pri'] = tmp['Pri'].to_i - neighbor << tmp - end - end - neighbor - end - def database - lines = @vtysh.execute("show ipv6 ospf6 database").lines - database = {} - mode = :none - area = '' - qta = :none - while line = lines.shift&.strip - case line - when /Area Scoped Link State Database \(Area (.*)\)/ - mode = :scoped_link_db - database[:scoped_link_db] ||= {} - database[:scoped_link_db][$1] = area = [] - qta = database_qta(lines) - when /I\/F Scoped Link State Database \(I\/F (\S+) in Area (.*)\)/ - mode = :if_scoped_link_state - database[:if_scoped_link_state] ||= {} - database[:if_scoped_link_state][$1] ||= {} - area = database[:if_scoped_link_state][$1][$2] ||= [] - qta= database_qta(lines) - when "AS Scoped Link State Database" - mode = :as_scoped - area = database[:as_scoped] ||= [] - qta=database_qta(lines) - # note: i have no data for this but i think it looks like the others - else - if line.length > 10 - area << qta.read_entry(line) - end - end - end - database - end - - def interface - lines = @vtysh.execute("show ipv6 ospf6 interface").lines - int = {} - current_if = {} - while line = lines.shift - if line.length > 5 - case line.strip - when /(\S+) is (down|up), type ([A-Z]+)/ - current_if = int[$1] = {up: ($2 == "up" ? true : false), - type: $3, - enabled: true} - when /Interface ID: (\d+)/ - current_if[:id] = $1.to_i - when /OSPF not enabled on this interface/ - current_if[:enabled] = false - when /Instance ID (\d+), Interface MTU (\d+) \(autodetect: (\d+)\)/ - current_if[:instance_id] = $1.to_i - current_if[:interface_mtu] = $2.to_i - current_if[:interface_mtu_autodetect] = $3.to_i - when "Internet Address:" - # ignore - when /(inet |inet6): (\S+)/ - current_if[:IPv6] ||= [] - current_if[:IPv4] ||= [] - family = $1 == 'inet6' ? :IPv6 : :IPv4 - address = $2 - current_if[family] << address - when /MTU mismatch detection: (en|dis)abled/ - current_if[:mtu_mismatch_detection] = $1 == 'en' - when /DR: (\S+) BDR: (\S+)/ - current_if[:designated_router] = $1 - current_if[:backup_designated_router] = $2 - when /State (\S+), Transmit Delay (\d+) sec, Priority (\d+)/ - current_if[:state] = $1 - current_if[:transmit_delay] = $2.to_i - current_if[:priority] = $3.to_i - when /Number of I\/F scoped LSAs is (\d+)/ - current_if[:number_if_scoped_lsas] = $1.to_i - when /(\d+) Pending LSAs for (\S+) in Time ([\d:]+)(?: (.*))/ - current_if[:pending_lsas] ||= {} - current_if[:pending_lsas][$2] = {time: $3, - count: $1, - flags: $4} - when "Timer intervals configured:" - # ignore - when /Hello (\d+), Dead (\d+), Retransmit (\d+)/ - current_if[:timers] = {hello: $1.to_i, - dead: $2.to_i, - retransmit: $3.to_i } - when /Area ID (\S+), Cost (\d+)/ - current_if[:area_cost] ||= [] - current_if[:area_cost] << {area: $1, cost: $2.to_i } - else - puts line if $QUAGGA_DEBUG - end - end - end - int - end - - private - def database_qta(lines) - # DON'T REMOVE THE SPACES!!! - # For some reasons the fields are right aligned with the fields which makes it hard - # to parse. I don't know a better way to get the correct offset except automatically. - # (Detection of semantic of the fields) - qta = QuaggaTableReader.new(["Type", "LSId", "AdvRouter", " Age", " SeqNum"," Payload"]) - lines.shift - qta.read_headline(lines.shift) - qta - end -end - -require 'optparse' -options = {} - -OptionParser.new do |opts| - opts.banner = "Usage: #{__FILE__} -s section [section specific params]" - #### OSPFv2 - opts.on("-d", "--ospf-database", "Prints the OSPF Database") do |od| - options[:ospf_database] = od - end - opts.on("-r", "--ospf-route", 'print OSPF routing table') do |od| - options[:ospf_route] = od - end - opts.on("-i", "--ospf-interface", 'print OSPF interface information') do |od| - options[:ospf_interface] = od - end - opts.on("-n", "--ospf-neighbor", 'Print OSPF neighbors') do |od| - options[:ospf_neighbors] = od - end - opts.on("-o", "--ospf-overview", "Print OSPF summary") do |od| - options[:ospf_overview] = od - end - #### OSPFv3 - opts.on("-D", "--ospfv3-database", "Prints the OSPFv3 Database") do |od| - options[:ospfv3_database] = od - end - opts.on("-t", "--ospfv3-route", 'print OSPFv3 routing table') do |od| - options[:ospfv3_route] = od - end - opts.on("-I", "--ospfv3-interface", 'print OSPFv3 interface information') do |od| - options[:ospfv3_interface] = od - end - opts.on("-N", "--ospfv3-neighbor", 'Print OSPFv3 neighbors') do |od| - options[:ospfv3_neighbors] = od - end - opts.on("-O", "--ospfv3-overview", "Print OSPFv3 summary") do |od| - options[:ospfv3_overview] = od - end - #### general things about routing - opts.on("-R", "--general-routes", "Print Routing Table (IPv4)") do |od| - options[:general_routes] = od - end - opts.on("-6", "--general-routes6", "Print Routing Table (IPv6)") do |od| - options[:general_routes6] = od - end - opts.on("-l", "--general-log", "Print Logs") do |od| - options[:general_log] = od - end - ### BGP - opts.on("-B", "--bgp-overview", "Print an overview of BGP") do |od| - options[:bgp_overview] = od - end - ### program opts - opts.on("-H", "--human-readable", "Print the output human readable (not json)") do |od| - options[:human_readable] = od - end - opts.on("-X", "--debug", "Prints debug output") do |od| - $QUAGGA_DEBUG = true - end - opts.on("-h", "--help", "Prints this help") do - puts opts - exit - end -end.parse! -# use the lib -sh = VTYSH.new -ospf = OSPF.new sh -ospfv3 = OSPFv3.new sh -bgp = BGP.new sh -general = General.new sh - -result = {} -options.keys.each do |k| - # if it is true - if options[k] - begin - if k.to_s.include? 'ospf_' - cmd = k.to_s.split('_').last - result[k] = ospf.send(cmd) - elsif k.to_s.include? 'ospfv3' - cmd = k.to_s.split('_').last - result[k] = ospfv3.send(cmd) - elsif k.to_s.include? 'general' - cmd = k.to_s.split('_').last - result[k] = general.send(cmd) - elsif k.to_s.include? 'bgp' - cmd = k.to_s.split('_').last - result[k] = bgp.send(cmd) - end - rescue # do nothing on an error - result[k] = "error" - puts $! if $QUAGGA_DEBUG - end - end -end - -# ospf.database, general.routes, ospf.interface, ospf.neighbors, ospf.route, ospf.overview -if options[:human_readable] - pp result -else - print result.to_json -end diff --git a/net/frr/src/opnsense/scripts/quagga/setup.sh b/net/frr/src/opnsense/scripts/quagga/setup.sh deleted file mode 100755 index 5cb140a6bc..0000000000 --- a/net/frr/src/opnsense/scripts/quagga/setup.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/sh - -user=frr -group=frr - -mkdir -p /var/run/frr -chown $user:$group /var/run/frr -chmod 750 /var/run/frr - -mkdir -p /usr/local/etc/frr -chown $user:$group /usr/local/etc/frr -chmod 750 /usr/local/etc/frr - -# ensure that frr can read the configuration files -chown -R $user:$group /usr/local/etc/frr -chown -R $user:$group /var/run/frr - -# logfile (if used) -touch /var/log/frr.log -chown $user:$group /var/log/frr.log diff --git a/net/frr/src/opnsense/service/conf/actions.d/actions_quagga.conf b/net/frr/src/opnsense/service/conf/actions.d/actions_quagga.conf index 66a793e04a..bfdeb61978 100644 --- a/net/frr/src/opnsense/service/conf/actions.d/actions_quagga.conf +++ b/net/frr/src/opnsense/service/conf/actions.d/actions_quagga.conf @@ -1,119 +1,199 @@ [start] -command:/usr/local/opnsense/scripts/quagga/setup.sh;/usr/local/etc/rc.d/frr start +command:/usr/local/etc/rc.d/frr start parameters: type:script -message:starting quagga +message:starting frr [stop] -command:/usr/local/etc/rc.d/frr stop; exit 0 +command:/usr/local/etc/rc.d/frr stop parameters: type:script -message:stopping quagga +message:stopping frr [restart] -command:/usr/local/opnsense/scripts/quagga/setup.sh;/usr/local/etc/rc.d/frr restart +command:/usr/local/etc/rc.d/frr restart; /usr/local/opnsense/scripts/frr/carp_event_handler parameters: type:script -message:restarting quagga +message:restarting frr +description:Restart FRR + +[reload] +command:service frr reload; /usr/local/opnsense/scripts/frr/carp_event_handler +parameters: +type:script +message:reloading frr +description:Reload FRR [status] -command:/usr/local/etc/rc.d/frr status;exit 0 +command:/usr/local/etc/rc.d/frr status; exit 0 parameters: type:script_output -message:request quagga +message:request frr -[diag-bgp] -command:/usr/local/opnsense/scripts/quagga/diag-bgp.sh -parameters:%s +[diagnostics.general_running-config] +command:/usr/local/bin/vtysh -c "show running-config" +parameters: +errors:no type:script_output -message:bgp diagnostics +message:FRR diagnosticts "show running-config" -[diag-bgp2] -command:/usr/local/opnsense/scripts/quagga/quagga.rb --bgp-overview -parameters: +[diagnostics.general_route4] +command:/usr/local/bin/vtysh +parameters: -c 'show ip route %s' +errors:no type:script_output -message:bgp diagnostics +message:FRR diagnosticts "show ip route" -[ospf-database] -command:/usr/local/opnsense/scripts/quagga/quagga.rb --ospf-database -parameters: +[diagnostics.general_route6] +command:/usr/local/bin/vtysh +parameters: -c 'show ipv6 route %s' +errors:no type:script_output -message: Shows the OSPF database +message:FRR diagnosticts "show ipv6 route" -[ospf-route] -command:/usr/local/opnsense/scripts/quagga/quagga.rb --ospf-route -parameters: +[diagnostics.bgp_route4] +command:/usr/local/bin/vtysh +parameters: -c 'show bgp ipv4 %s' +errors:no type:script_output -message: print OSPF routing table +message:FRR diagnostics "show bgp ipv4 %s" -[ospf-interface] -command:/usr/local/opnsense/scripts/quagga/quagga.rb --ospf-interface -parameters: +[diagnostics.bgp_route6] +command:/usr/local/bin/vtysh +parameters:-c 'show bgp ipv6 %s' +errors:no type:script_output -message: print OSPF interface information +message:FRR diagnostics "show bgp ipv6 %s" -[ospf-neighbor] -command:/usr/local/opnsense/scripts/quagga/quagga.rb --ospf-neighbor -parameters: +[diagnostics.bgp_summary] +command:/usr/local/bin/vtysh +parameters: -c 'show bgp summary %s' +errors:no type:script_output -message: Print OSPF neighbors +message:FRR diagnostics "show bgp summary %s" -[ospf-overview] -command:/usr/local/opnsense/scripts/quagga/quagga.rb --ospf-overview -parameters: +[diagnostics.bgp_summary4] +command:/usr/local/bin/vtysh +parameters: -c 'show bgp ipv4 summary %s' +errors:no type:script_output -message: Print OSPF summary +message:FRR diagnostics "show bgp ipv4 summary %s" -[ospfv3-database] -command:/usr/local/opnsense/scripts/quagga/quagga.rb --ospfv3-database -parameters: +[diagnostics.bgp_summary6] +command:/usr/local/bin/vtysh +parameters: -c 'show bgp ipv6 summary %s' +errors:no type:script_output -message: Shows the OSPF database +message:FRR diagnostics "show bgp ipv6 summary %s" -[ospfv3-route] -command:/usr/local/opnsense/scripts/quagga/quagga.rb --ospfv3-route -parameters: +[diagnostics.bgp_neighbors] +command:/usr/local/bin/vtysh +parameters: -c 'show bgp neighbors %s' +errors:no type:script_output -message: print OSPF routing table +message:FRR diagnostics "show bgp neighbors %s" -[ospfv3-interface] -command:/usr/local/opnsense/scripts/quagga/quagga.rb --ospfv3-interface -parameters: +[diagnostics.bgp_neighbors4] +command:/usr/local/bin/vtysh +parameters: -c 'show bgp ipv4 neighbors %s' +errors:no type:script_output -message: print OSPF interface information +message:FRR diagnostics "show bgp ipv4 neighbors %s" -[ospfv3-neighbor] -command:/usr/local/opnsense/scripts/quagga/quagga.rb --ospfv3-neighbor -parameters: +[diagnostics.bgp_neighbors6] +command:/usr/local/bin/vtysh +parameters: -c 'show bgp ipv6 neighbors %s' +errors:no type:script_output -message: Print OSPF neighbors +message:FRR diagnostics "show bgp ipv6 neighbors %s" -[ospfv3-overview] -command:/usr/local/opnsense/scripts/quagga/quagga.rb --ospfv3-overview -parameters: +[diagnostics.ospf_overview] +command:/usr/local/bin/vtysh +parameters: -c 'show ip ospf %s' +errors:no type:script_output -message: Print OSPF summary +message:FRR diagnostics "show ip ospf %s" -[general-routes] -command:/usr/local/opnsense/scripts/quagga/quagga.rb --general-routes -parameters: +[diagnostics.ospf_neighbor] +command:/usr/local/bin/vtysh +parameters: -c 'show ip ospf neighbor %s' +errors:no type:script_output -message: Print IPv4 Routing Table +message:FRR diagnostics "show ip ospf neighbor %s" -[general-routes6] -command:/usr/local/opnsense/scripts/quagga/quagga.rb --general-routes6 -parameters: +[diagnostics.ospf_route] +command:/usr/local/bin/vtysh +parameters: -c 'show ip ospf route %s' +errors:no type:script_output -message: Print IPv6 Routing Table +message:FRR diagnostics "show ip ospf route %s" -[general-log] -command:/usr/local/opnsense/scripts/quagga/quagga.rb --general-log -parameters: +[diagnostics.ospf_interface] +command:/usr/local/bin/vtysh +parameters: -c 'show ip ospf interface %s' +errors:no type:script_output -message: Show Quagga logs +message:FRR diagnostics "show ip ospf interface %s" -[general-runningconfig] -command:/usr/local/bin/vtysh -c "show run" -parameters: +[diagnostics.bfd_neighbors] +command:/usr/local/bin/vtysh +parameters: -c 'show bfd peers %s' +errors:no +type:script_output +message:FRR diagnostics "show bfd peers json %s" + +[diagnostics.bfd_summary] +command:/usr/local/bin/vtysh +parameters: -c 'show bfd peers brief %s' +errors:no +type:script_output +message:FRR diagnostics "show bfd peers brief %s" + +[diagnostics.bfd_counters] +command:/usr/local/bin/vtysh +parameters: -c 'show bfd peers counters %s' +errors:no +type:script_output +message:FRR diagnostics "show bfd peers counters %s" + +[diagnostics.bfd_staticroute] +command:/usr/local/bin/vtysh +parameters: -c 'show bfd static route %s' +errors:no +type:script_output +message:FRR diagnostics "show bfd route %s" + +[diagnostics.ospf_database] +command:/usr/local/bin/vtysh +parameters: -c 'show ip ospf database %s' +errors:no +type:script_output +message:FRR diagnostics "show ip ospf database" + +[diagnostics.ospfv3_overview] +command:/usr/local/bin/vtysh +parameters: -c 'show ipv6 ospf6 %s' +errors:no +type:script_output +message:FRR diagnostics "show ipv6 ospf6 %s" + +[diagnostics.ospfv3_route] +command:/usr/local/bin/vtysh +parameters: -c 'show ipv6 ospf6 route %s' +errors:no +type:script_output +message:FRR diagnostics "show ipv6 ospf6 route %s" + +[diagnostics.ospfv3_database] +command:/usr/local/bin/vtysh +parameters: -c 'show ipv6 ospf6 database %s' +errors:no +type:script_output +message:FRR diagnostics "show ipv6 ospf6 database json" + +[diagnostics.ospfv3_interface] +command:/usr/local/bin/vtysh +parameters: -c 'show ipv6 ospf6 interface %s' +errors:no type:script_output -message: Show running configuration +message:FRR diagnostics "show ipv6 ospf6 interface %s" diff --git a/net/frr/src/opnsense/service/templates/OPNsense/Quagga/+TARGETS b/net/frr/src/opnsense/service/templates/OPNsense/Quagga/+TARGETS index b01f28018f..9ee342bb4c 100644 --- a/net/frr/src/opnsense/service/templates/OPNsense/Quagga/+TARGETS +++ b/net/frr/src/opnsense/service/templates/OPNsense/Quagga/+TARGETS @@ -1,8 +1,7 @@ -bgpd.conf:/usr/local/etc/frr/bgpd.conf -ospfd.conf:/usr/local/etc/frr/ospfd.conf -ospf6d.conf:/usr/local/etc/frr/ospf6d.conf -ripd.conf:/usr/local/etc/frr/ripd.conf frr:/etc/rc.conf.d/frr -zebra.conf:/usr/local/etc/frr/zebra.conf +frr.conf:/usr/local/etc/frr/frr.conf vtysh.conf:/usr/local/etc/frr/vtysh.conf +ospf6d_carp.conf:/usr/local/etc/frr/ospf6d_carp.conf +ospfd_carp.conf:/usr/local/etc/frr/ospfd_carp.conf +sa_policies.conf:/usr/local/etc/frr/sa_policies.conf syslog-ng-frr-events.conf:/usr/local/etc/syslog-ng.conf.d/frr-events.conf diff --git a/net/frr/src/opnsense/service/templates/OPNsense/Quagga/bfdd.conf b/net/frr/src/opnsense/service/templates/OPNsense/Quagga/bfdd.conf new file mode 100644 index 0000000000..512e6a860c --- /dev/null +++ b/net/frr/src/opnsense/service/templates/OPNsense/Quagga/bfdd.conf @@ -0,0 +1,14 @@ +{# included in frr.conf #} +{% if helpers.exists('OPNsense.quagga.bfd.enabled') and OPNsense.quagga.bfd.enabled == '1' %} +bfd +{% if helpers.exists('OPNsense.quagga.bfd.neighbors.neighbor') %} +{% for neighbor in helpers.toList('OPNsense.quagga.bfd.neighbors.neighbor') %} +{% if neighbor.enabled == '1' %} + peer {{ neighbor.address }}{% if neighbor.multihop|default('0') == '1' %} multihop{% endif %}{% if neighbor.localaddress %} local-address {{ neighbor.localaddress }}{% endif %}{% if neighbor.interfacename %} interface {{ neighbor.interfacename }}{% endif +%} + detect-multiplier {{ neighbor.detect_multiplier }} + receive-interval {{ neighbor.receive_interval }} + transmit-interval {{ neighbor.transmit_interval }} +{% endif %} +{% endfor %} +{% endif %} +{% endif %} diff --git a/net/frr/src/opnsense/service/templates/OPNsense/Quagga/bgpd.conf b/net/frr/src/opnsense/service/templates/OPNsense/Quagga/bgpd.conf index da858e7c8d..3b1aa0ecb2 100644 --- a/net/frr/src/opnsense/service/templates/OPNsense/Quagga/bgpd.conf +++ b/net/frr/src/opnsense/service/templates/OPNsense/Quagga/bgpd.conf @@ -1,184 +1,281 @@ +{# included in frr.conf #} {% if helpers.exists('OPNsense.quagga.bgp.enabled') and OPNsense.quagga.bgp.enabled == '1' %} -{% from 'OPNsense/Macros/interface.macro' import physical_interface %} -! -! Zebra configuration saved from vty -! 2017/03/03 20:21:04 -! -{% if helpers.exists('OPNsense.quagga.general') %} -{% if helpers.exists('OPNsense.quagga.general.enablelogfile') and OPNsense.quagga.general.enablelogfile == '1' %} -log file /var/log/frr.log {{ OPNsense.quagga.general.logfilelevel }} -{% endif %} -{% if helpers.exists('OPNsense.quagga.general.enablesyslog') and OPNsense.quagga.general.enablesyslog == '1' %} -log syslog {{ OPNsense.quagga.general.sysloglevel }} -{% endif %} -{% endif %} -! -! -! -{% if helpers.exists('OPNsense.quagga.bgp.asnumber') and OPNsense.quagga.bgp.asnumber != '' %} -router bgp {{ OPNsense.quagga.bgp.asnumber }} -{% if helpers.exists('OPNsense.quagga.bgp.routerid') and OPNsense.quagga.bgp.routerid != '' %} - bgp router-id {{ OPNsense.quagga.bgp.routerid }} -{% endif %} +{% from 'OPNsense/Macros/interface.macro' import physical_interface %} +{% set addressFamilies = ['ipv4', 'ipv6' ] %} +{% set neighbors = {'ipv4': [], 'ipv6': []} %} +{% set networks = {'ipv4': [], 'ipv6': []} %} + {% if helpers.exists('OPNsense.quagga.bgp.neighbors.neighbor') %} {% for neighbor in helpers.toList('OPNsense.quagga.bgp.neighbors.neighbor') %} -{% if neighbor.enabled == '1' %} - neighbor {{ neighbor.address }} remote-as {{ neighbor.remoteas }} -{% if 'updatesource' in neighbor and neighbor.updatesource != '' %} - neighbor {{ neighbor.address }} update-source {{ physical_interface(neighbor.updatesource) }} -{% endif %} -{% if 'multihop' in neighbor and neighbor.multihop == '1' %} - neighbor {{ neighbor.address }} ebgp-multihop -{% endif %} +{% if neighbor.enabled == '1' and neighbor.multiprotocol == '1' %} +{# // the .append() method in Jinja2 returns "None", so filter through default() to suppress #} +{{ neighbors['ipv4'].append(neighbor) | default("", True) }} +{{ neighbors['ipv6'].append(neighbor) | default("", True) }} +{% elif neighbor.enabled == '1' and ':' not in neighbor.address %} +{{ neighbors['ipv4'].append(neighbor) | default("", True) }} +{% elif neighbor.enabled == '1' and ':' in neighbor.address %} +{{ neighbors['ipv6'].append(neighbor) | default("", True) }} {% endif %} {% endfor %} {% endif %} - address-family ipv4 unicast {% if helpers.exists('OPNsense.quagga.bgp.networks') %} {% for network in OPNsense.quagga.bgp.networks.split(',') %} {% if ':' not in network %} - network {{ network }} +{{ networks['ipv4'].append(network) | default("", True) }} +{% elif ':' in network %} +{{ networks['ipv6'].append(network) | default("", True) }} {% endif %} {% endfor %} {% endif %} -{% if helpers.exists('OPNsense.quagga.bgp.redistribute') and OPNsense.quagga.bgp.redistribute != '' %} -{% for bgp_redistribute in OPNsense.quagga.bgp.redistribute.split(',') %} - redistribute {{ bgp_redistribute }} -{% endfor %} +{% if helpers.exists('OPNsense.quagga.bgp.asnumber') and OPNsense.quagga.bgp.asnumber != '' %} +router bgp {{ OPNsense.quagga.bgp.asnumber }} +{% if not helpers.empty('OPNsense.quagga.bgp.logneighborchanges') %} + bgp log-neighbor-changes +{% endif %} +{% if OPNsense.quagga.bgp.enforce_first_as == '0' %} + no bgp enforce-first-as +{% endif %} + no bgp default ipv4-unicast + no bgp ebgp-requires-policy +{% if helpers.exists('OPNsense.quagga.bgp.networkimportcheck') and OPNsense.quagga.bgp.networkimportcheck == '1' %} + bgp network import-check +{% else %} + no bgp network import-check +{% endif %} +{% if helpers.exists('OPNsense.quagga.bgp.graceful') and OPNsense.quagga.bgp.graceful == '1' %} + bgp graceful-restart +{% endif %} +{% if OPNsense.quagga.bgp.bestpath %} +{% for option in OPNsense.quagga.bgp.bestpath.split(',') %} + bgp bestpath {{ option }} +{% endfor %} +{% endif %} +{% if helpers.exists('OPNsense.quagga.bgp.routerid') and OPNsense.quagga.bgp.routerid != '' %} + bgp router-id {{ OPNsense.quagga.bgp.routerid }} +{% endif %} +{% if helpers.exists('OPNsense.quagga.bgp.distance') and OPNsense.quagga.bgp.distance != '' %} + distance bgp {{ OPNsense.quagga.bgp.distance }} {{ OPNsense.quagga.bgp.distance }} {{ OPNsense.quagga.bgp.distance }} {% endif %} -{% if helpers.exists('OPNsense.quagga.bgp.neighbors.neighbor') and ':' not in OPNsense.quagga.bgp.neighbors.neighbor.address %} +{% for peergroup in helpers.toList('OPNsense.quagga.bgp.peergroups.peergroup') %} +{% if peergroup.enabled == '1' %} + neighbor {{ peergroup.name }} peer-group +{% if 'remoteas' in peergroup and peergroup.remoteas and not peergroup.remote_as_mode %} + neighbor {{ peergroup.name }} remote-as {{ peergroup.remoteas }} +{% else %} + neighbor {{ peergroup.name }} remote-as {{ peergroup.remote_as_mode }} +{% endif %} +{% if peergroup.updatesource %} + neighbor {{ peergroup.name }} update-source {{ physical_interface(peergroup.updatesource) }} +{% endif %} +{% if peergroup.nexthopself|default('0') == '1' %} + neighbor {{ peergroup.name }} next-hop-self +{% endif %} +{% if peergroup.defaultoriginate|default('0') == '1' %} + neighbor {{ peergroup.name }} default-originate +{% endif %} +{% if peergroup.linkedPrefixlistIn|default("") != "" %} +{% for prefixlist in peergroup.linkedPrefixlistIn.split(",") %} +{% set prefixlist2_data = helpers.getUUID(prefixlist) %} +{% if prefixlist2_data != {} and prefixlist2_data.enabled == '1' %} + neighbor {{ peergroup.name }} prefix-list {{ prefixlist2_data.name }} in +{% endif %} +{% endfor %} +{% endif %} +{% if peergroup.linkedPrefixlistOut|default("") != "" %} +{% for prefixlist in peergroup.linkedPrefixlistOut.split(",") %} +{% set prefixlist_data = helpers.getUUID(prefixlist) %} +{% if prefixlist_data != {} and prefixlist_data.enabled == '1' %} + neighbor {{ peergroup.name }} prefix-list {{ prefixlist_data.name }} out +{% endif %} +{% endfor %} +{% endif %} +{% if peergroup.linkedRoutemapIn|default("") != "" %} +{% for aspath in peergroup.linkedRoutemapIn.split(",") %} +{% set routemap2_data = helpers.getUUID(aspath) %} +{% if routemap2_data != {} and routemap2_data.enabled == '1' %} + neighbor {{ peergroup.name }} route-map {{ routemap2_data.name }} in +{% endif %} +{% endfor %} +{% endif %} +{% if peergroup.linkedRoutemapOut|default("") != "" %} +{% for aspath in peergroup.linkedRoutemapOut.split(",") %} +{% set routemap_data = helpers.getUUID(aspath) %} +{% if routemap_data != {} and routemap_data.enabled == '1' %} + neighbor {{ peergroup.name }} route-map {{ routemap_data.name }} out +{% endif %} +{% endfor %} +{% endif %} +{% if peergroup.listenranges %} +{% for prefix in peergroup.listenranges.split(',') %} + bgp listen range {{ prefix }} peer-group {{ peergroup.name }} +{% endfor %} +{% endif %} +{% endif %} +{% endfor %} +{% if helpers.exists('OPNsense.quagga.bgp.neighbors.neighbor') %} {% for neighbor in helpers.toList('OPNsense.quagga.bgp.neighbors.neighbor') %} -{% if neighbor.enabled == '1' and ':' not in neighbor.address %} -{% if 'nexthopself' in neighbor and neighbor.nexthopself == '1' %} - neighbor {{ neighbor.address }} next-hop-self +{% if neighbor.enabled == '1' %} +{% if 'remoteas' in neighbor and neighbor.remoteas and not neighbor.remote_as_mode %} + neighbor {{ neighbor.address }} remote-as {{ neighbor.remoteas }} +{% else %} + neighbor {{ neighbor.address }} remote-as {{ neighbor.remote_as_mode }} {% endif %} -{% if 'defaultoriginate' in neighbor and neighbor.defaultoriginate == '1' %} - neighbor {{ neighbor.address }} default-originate +{% if 'localas' in neighbor and neighbor.localas != '' %} + neighbor {{ neighbor.address }} local-as {{ neighbor.localas }} {% endif %} -{% if neighbor.linkedPrefixlistIn|default("") != "" %} -{% for prefixlist in neighbor.linkedPrefixlistIn.split(",") %} -{% set prefixlist2_data = helpers.getUUID(prefixlist) %} -{% if prefixlist2_data != {} and prefixlist2_data.enabled == '1' %} - neighbor {{ neighbor.address }} prefix-list {{ prefixlist2_data.name }} in -{% endif %} -{% endfor %} +{% if 'description' in neighbor and neighbor.description != '' %} + neighbor {{ neighbor.address }} description {{ neighbor.description }} {% endif %} -{% if neighbor.linkedPrefixlistOut|default("") != "" %} -{% for prefixlist in neighbor.linkedPrefixlistOut.split(",") %} -{% set prefixlist_data = helpers.getUUID(prefixlist) %} -{% if prefixlist_data != {} and prefixlist_data.enabled == '1' %} - neighbor {{ neighbor.address }} prefix-list {{ prefixlist_data.name }} out -{% endif %} -{% endfor %} +{% if neighbor.bfd|default('') == '1' %} + neighbor {{ neighbor.address }} bfd {% endif %} -{% if neighbor.linkedRoutemapIn|default("") != "" %} -{% for aspath in neighbor.linkedRoutemapIn.split(",") %} -{% set routemap2_data = helpers.getUUID(aspath) %} -{% if routemap2_data != {} and routemap2_data.enabled == '1' %} - neighbor {{ neighbor.address }} route-map {{ routemap2_data.name }} in -{% endif %} -{% endfor %} +{% if 'password' in neighbor and neighbor.password != '' %} + neighbor {{ neighbor.address }} password {{ neighbor.password }} {% endif %} -{% if neighbor.linkedRoutemapOut|default("") != "" %} -{% for aspath in neighbor.linkedRoutemapOut.split(",") %} -{% set routemap_data = helpers.getUUID(aspath) %} -{% if routemap_data != {} and routemap_data.enabled == '1' %} - neighbor {{ neighbor.address }} route-map {{ routemap_data.name }} out -{% endif %} +{% if 'weight' in neighbor and neighbor.weight != '' %} + neighbor {{ neighbor.address }} weight {{ neighbor.weight }} +{% endif %} +{% if 'disable_connected_check' in neighbor and neighbor.disable_connected_check == '1' %} + neighbor {{ neighbor.address }} disable-connected-check +{% endif %} +{% if ':' not in neighbor.address and 'updatesource' in neighbor and neighbor.updatesource != '' %} + neighbor {{ neighbor.address }} update-source {{ physical_interface(neighbor.updatesource) }} +{% endif %} +{% if ':' in neighbor.address and 'linklocalinterface' in neighbor and neighbor.linklocalinterface != '' %} + neighbor {{ neighbor.address }} interface {{ physical_interface(neighbor.linklocalinterface) }} +{% endif %} +{% if 'multihop' in neighbor and neighbor.multihop == '1' %} + neighbor {{ neighbor.address }} ebgp-multihop 255 +{% endif %} +{% if 'keepalive' in neighbor and neighbor.keepalive != '' %} +{% if 'holddown' in neighbor and neighbor.holddown != '' %} + neighbor {{ neighbor.address }} timers {{ neighbor.keepalive }} {{ neighbor.holddown }} +{% endif %} +{% endif %} +{% if 'connecttimer' in neighbor and neighbor.connecttimer != '' %} + neighbor {{ neighbor.address }} timers connect {{ neighbor.connecttimer }} +{% endif %} +{% if 'capabilities' in neighbor and neighbor.capabilities %} +{% for capability in neighbor.capabilities.split(',') %} + neighbor {{ neighbor.address }} capability {{ capability }} {% endfor %} {% endif %} +{% if 'attributeunchanged' in neighbor and neighbor.attributeunchanged != '' %} + neighbor {{ neighbor.address }} attribute-unchanged {{ neighbor.attributeunchanged }} +{% endif %} +{% if neighbor.peergroup|default('') != '' %} +{% set pgname = helpers.getUUID(neighbor.peergroup) %} + neighbor {{ neighbor.address }} peer-group {{ pgname.name }} +{% endif %} {% endif %} {% endfor %} {% endif %} -{% if helpers.exists('OPNsense.quagga.bgp.neighbors.neighbor') %} -{% for neighbor in helpers.toList('OPNsense.quagga.bgp.neighbors.neighbor') %} -{% if neighbor.enabled == '1' and ':' in neighbor.address %} - no neighbor {{ neighbor.address }} activate -{% endif %} -{% endfor %} -{% endif %} - exit-address-family -! - address-family ipv6 unicast -{% if helpers.exists('OPNsense.quagga.bgp.networks') and ':' in OPNsense.quagga.bgp.networks %} -{% for network in OPNsense.quagga.bgp.networks.split(',') %} -{% if ':' in network %} - network {{ network }} -{% endif %} -{% endfor %} + +{% for addressFamily in addressFamilies %} + address-family {{ addressFamily }} unicast +{% if helpers.exists('OPNsense.quagga.bgp.maximumpaths') and OPNsense.quagga.bgp.maximumpaths != '' %} + maximum-paths {{ OPNsense.quagga.bgp.maximumpaths }} {% endif %} -{% if helpers.exists('OPNsense.quagga.bgp.redistribute') and OPNsense.quagga.bgp.redistribute != '' %} -{% for bgp_redistribute in OPNsense.quagga.bgp.redistribute.split(',') %} - redistribute {{ bgp_redistribute }} -{% endfor %} +{% if helpers.exists('OPNsense.quagga.bgp.maximumpathsibgp') and OPNsense.quagga.bgp.maximumpathsibgp != '' %} + maximum-paths ibgp {{ OPNsense.quagga.bgp.maximumpathsibgp }} {% endif %} -{% if helpers.exists('OPNsense.quagga.bgp.neighbors.neighbor') %} -{% for neighbor in helpers.toList('OPNsense.quagga.bgp.neighbors.neighbor') %} -{% if neighbor.enabled == '1' and ':' in neighbor.address %} +{% for redistribution in helpers.toList('OPNsense.quagga.bgp.redistributions.redistribution') %} +{% if redistribution.enabled == '1' %} + redistribute {{ redistribution.redistribute }}{% if redistribution.linkedRoutemap %} route-map {{ helpers.getUUID(redistribution.linkedRoutemap).name }}{% endif +%} +{% endif %} +{% endfor %} +{% for network in networks[addressFamily] %} + network {{ network }} +{% endfor %} +{% for peergroup in helpers.toList('OPNsense.quagga.bgp.peergroups.peergroup') %} +{% if peergroup.enabled == '1' and (addressFamily in peergroup.family.split(',')) %} + neighbor {{ peergroup.name }} activate +{% endif %} +{% endfor %} +{% for neighbor in neighbors[addressFamily] %} neighbor {{ neighbor.address }} activate -{% if 'nexthopself' in neighbor and neighbor.nexthopself == '1' %} - neighbor {{ neighbor.address }} next-hop-self -{% endif %} -{% if 'defaultoriginate' in neighbor and neighbor.defaultoriginate == '1' %} +{% if 'nexthopself' in neighbor and neighbor.nexthopself == '1' %} + neighbor {{ neighbor.address }} next-hop-self {% if 'nexthopselfall' in neighbor and neighbor.nexthopselfall == '1' %}all{% endif %} + +{% endif %} +{% if 'rrclient' in neighbor and neighbor.rrclient == '1' %} + neighbor {{ neighbor.address }} route-reflector-client +{% endif %} +{% if neighbor.soft_reconfiguration_inbound|default('0') == '1' %} + neighbor {{ neighbor.address }} soft-reconfiguration inbound +{% endif %} +{% if 'defaultoriginate' in neighbor and neighbor.defaultoriginate == '1' %} neighbor {{ neighbor.address }} default-originate +{% endif %} +{% if 'asoverride' in neighbor and neighbor.asoverride == '1' %} + neighbor {{ neighbor.address }} as-override +{% endif %} +{% if 'removeprivateas' in neighbor and neighbor.removeprivateas %} + neighbor {{ neighbor.address }} {{ neighbor.removeprivateas }} +{% endif %} +{% if 'allowas_in' in neighbor and neighbor.allowas_in %} + neighbor {{ neighbor.address }} allowas-in {{ neighbor.allowas_in }} {% endif %} -{% if neighbor.linkedPrefixlistIn|default("") != "" %} -{% for prefixlist in neighbor.linkedPrefixlistIn.split(",") %} -{% set prefixlist2_data = helpers.getUUID(prefixlist) %} -{% if prefixlist2_data != {} and prefixlist2_data.enabled == '1' %} +{% if neighbor.linkedPrefixlistIn|default("") != "" %} +{% for prefixlist in neighbor.linkedPrefixlistIn.split(",") %} +{% set prefixlist2_data = helpers.getUUID(prefixlist) %} +{% if prefixlist2_data != {} and prefixlist2_data.enabled == '1' %} neighbor {{ neighbor.address }} prefix-list {{ prefixlist2_data.name }} in -{% endif %} -{% endfor %} {% endif %} -{% if neighbor.linkedPrefixlistOut|default("") != "" %} -{% for prefixlist in neighbor.linkedPrefixlistOut.split(",") %} -{% set prefixlist_data = helpers.getUUID(prefixlist) %} -{% if prefixlist_data != {} and prefixlist_data.enabled == '1' %} +{% endfor %} +{% endif %} +{% if neighbor.linkedPrefixlistOut|default("") != "" %} +{% for prefixlist in neighbor.linkedPrefixlistOut.split(",") %} +{% set prefixlist_data = helpers.getUUID(prefixlist) %} +{% if prefixlist_data != {} and prefixlist_data.enabled == '1' %} neighbor {{ neighbor.address }} prefix-list {{ prefixlist_data.name }} out -{% endif %} -{% endfor %} {% endif %} -{% if neighbor.linkedRoutemapIn|default("") != "" %} -{% for aspath in neighbor.linkedRoutemapIn.split(",") %} -{% set routemap2_data = helpers.getUUID(aspath) %} -{% if routemap2_data != {} and routemap2_data.enabled == '1' %} +{% endfor %} +{% endif %} +{% if neighbor.linkedRoutemapIn|default("") != "" %} +{% for aspath in neighbor.linkedRoutemapIn.split(",") %} +{% set routemap2_data = helpers.getUUID(aspath) %} +{% if routemap2_data != {} and routemap2_data.enabled == '1' %} neighbor {{ neighbor.address }} route-map {{ routemap2_data.name }} in -{% endif %} -{% endfor %} {% endif %} -{% if neighbor.linkedRoutemapOut|default("") != "" %} -{% for aspath in neighbor.linkedRoutemapOut.split(",") %} -{% set routemap_data = helpers.getUUID(aspath) %} -{% if routemap_data != {} and routemap_data.enabled == '1' %} +{% endfor %} +{% endif %} +{% if neighbor.linkedRoutemapOut|default("") != "" %} +{% for aspath in neighbor.linkedRoutemapOut.split(",") %} +{% set routemap_data = helpers.getUUID(aspath) %} +{% if routemap_data != {} and routemap_data.enabled == '1' %} neighbor {{ neighbor.address }} route-map {{ routemap_data.name }} out -{% endif %} -{% endfor %} {% endif %} -{% endif %} -{% endfor %} -{% endif %} +{% endfor %} +{% endif %} +{% endfor %} exit-address-family -! +{% endfor %} + {% if helpers.exists('OPNsense.quagga.bgp.prefixlists.prefixlist') %} {% for prefixlist in helpers.sortDictList(OPNsense.quagga.bgp.prefixlists.prefixlist, 'name', 'seqnumber' ) %} -{% if prefixlist.enabled == '1' and ':' not in prefixlist.network %} +{% if prefixlist.enabled == '1' and prefixlist.version == 'IPv4' %} ip prefix-list {{ prefixlist.name }} seq {{ prefixlist.seqnumber }} {{ prefixlist.action }} {{ prefixlist.network }} {% endif %} -! -{% if prefixlist.enabled == '1' and ':' in prefixlist.network %} +{% if prefixlist.enabled == '1' and prefixlist.version == 'IPv6' %} ipv6 prefix-list {{ prefixlist.name }} seq {{ prefixlist.seqnumber }} {{ prefixlist.action }} {{ prefixlist.network }} {% endif %} {% endfor %} {% endif %} -! {% if helpers.exists('OPNsense.quagga.bgp.aspaths.aspath') %} {% for aspath in helpers.sortDictList(OPNsense.quagga.bgp.aspaths.aspath, 'number' ) %} {% if aspath.enabled == '1' %} -ip as-path access-list {{ aspath.number }} {{ aspath.action }} {{ aspath.as }} +bgp as-path access-list {{ aspath.number }} {{ aspath.action }} {{ aspath.as }} +{% endif %} +{% endfor %} +{% endif %} +{% if helpers.exists('OPNsense.quagga.bgp.communitylists.communitylist') %} +{% for communitylist in helpers.sortDictList(OPNsense.quagga.bgp.communitylists.communitylist, 'number' ) %} +{% if communitylist.enabled == '1' %} +bgp community-list {{ communitylist.number }} seq {{ communitylist.seqnumber }} {{ communitylist.action }} {{ communitylist.community }} {% endif %} {% endfor %} {% endif %} -! {% if helpers.exists('OPNsense.quagga.bgp.routemaps.routemap') %} {% for routemap in helpers.sortDictList(OPNsense.quagga.bgp.routemaps.routemap, 'name', 'id' ) %} {% if routemap.enabled == '1' %} @@ -191,7 +288,7 @@ route-map {{ routemap.name }} {{ routemap.action }} {{ routemap.id }} {% endif %} {% endfor %} {% endif %} -{% if routemap.set|default("") != '' and routemap.match|default("") != '' %} +{% if routemap.set|default("") != '' %} set {{ routemap.set }} {% endif %} {% if routemap.match2|default("") != "" %} @@ -204,15 +301,22 @@ route-map {{ routemap.name }} {{ routemap.action }} {{ routemap.id }} {% endif %} {% endfor %} {% endif %} +{% if routemap.match3|default("") != "" %} +{% for communitylist in routemap.match3.split(",") %} +{% set communitylist_data = helpers.getUUID(communitylist) %} +{% if 'match3' in routemap and routemap.match3 != '' %} + match community {{ communitylist_data.number }} +{% endif %} +{% endfor %} +{% endif %} {% if routemap.set|default("") != '' and routemap.match2|default("") != '' %} set {{ routemap.set }} {% endif %} {% endif %} {% endfor %} {% endif %} -! {% endif %} -! -line vty -! +{% if helpers.exists('OPNsense.quagga.bgpd.enabled') and OPNsense.quagga.general.enablesnmp == '1' %} +agentx +{% endif %} {% endif %} diff --git a/net/frr/src/opnsense/service/templates/OPNsense/Quagga/frr b/net/frr/src/opnsense/service/templates/OPNsense/Quagga/frr index e2b5da14dc..37919d4a23 100644 --- a/net/frr/src/opnsense/service/templates/OPNsense/Quagga/frr +++ b/net/frr/src/opnsense/service/templates/OPNsense/Quagga/frr @@ -1,17 +1,30 @@ {% if helpers.exists('OPNsense.quagga.general.enabled') and OPNsense.quagga.general.enabled == '1' %} -frr_var_script="/usr/local/opnsense/scripts/quagga/setup.sh" +# XXX rc.d/frr splits up defunct "frr" service into frr_daemons +# and we always start "zebra" so for now this works: +watchfrr_setup="/usr/local/opnsense/scripts/frr/setup.sh" frr_enable="YES" {% if helpers.exists('OPNsense.quagga.general.enablecarp') and OPNsense.quagga.general.enablecarp == '1' %} start_precmd="ifconfig | grep 'carp: MASTER'" {% endif %} -frr_daemons="zebra{% +frr_daemons="mgmtd zebra{% if helpers.exists('OPNsense.quagga.ospf.enabled') and OPNsense.quagga.ospf.enabled == '1' %} ospfd{% endif %}{% if helpers.exists('OPNsense.quagga.rip.enabled') and OPNsense.quagga.rip.enabled == '1' %} ripd{% endif %}{% +if helpers.exists('OPNsense.quagga.bfd.enabled') and OPNsense.quagga.bfd.enabled == '1' %} bfdd{% endif %}{% if helpers.exists('OPNsense.quagga.bgp.enabled') and OPNsense.quagga.bgp.enabled == '1' %} bgpd{% endif %}{% if helpers.exists('OPNsense.quagga.ospf6.enabled') and OPNsense.quagga.ospf6.enabled == '1' %} ospf6d{% endif %}{% if helpers.exists('OPNsense.quagga.ripng.enabled') and OPNsense.quagga.ripng.enabled == '1' %} ripngd{% endif %}{% -if helpers.exists('OPNsense.quagga.isis.enabled') and OPNsense.quagga.isis.enabled == '1' %} isisd{% endif %}" -frr_carp_demote="{% if not helpers.empty('OPNsense.quagga.ospf.carp_demote') %} ospfd{% endif %}" +if helpers.exists('OPNsense.quagga.static.enabled') and OPNsense.quagga.static.enabled == '1' %} staticd{% endif %}" +frr_carp_demote="{% + if not helpers.empty('OPNsense.quagga.ospf.carp_demote') %} ospfd{% endif %}{% + if not helpers.empty('OPNsense.quagga.ospf6.carp_demote') %} ospf6d{% endif +%}" +watchfrr_flags="-r /usr/local/opnsense/scripts/frr/frr_wrapper.shbBrestartbB%s -s /usr/local/opnsense/scripts/frr/frr_wrapper.shbBstartbB%s -k /usr/sbin/servicebBfrrbBstopbB%s -b bB -t 30" +{% if OPNsense.quagga.general.enablesnmp == '1' %} +zebra_flags="${zebra_flags} -M snmp" +bgpd_flags="${bgpd_flags} -M snmp" +ospfd_flags="${ospfd_flags} -M snmp" +ospf6d_flags="${ospf6d_flags} -M snmp" +{% endif %} {% else %} frr_enable="NO" {% endif %} diff --git a/net/frr/src/opnsense/service/templates/OPNsense/Quagga/frr.conf b/net/frr/src/opnsense/service/templates/OPNsense/Quagga/frr.conf new file mode 100644 index 0000000000..b8da1a348f --- /dev/null +++ b/net/frr/src/opnsense/service/templates/OPNsense/Quagga/frr.conf @@ -0,0 +1,8 @@ +{# Main configuration file #} +{% include "OPNsense/Quagga/zebra.conf" %} +{% include "OPNsense/Quagga/ripd.conf" %} +{% include "OPNsense/Quagga/ospfd.conf" %} +{% include "OPNsense/Quagga/ospf6d.conf" %} +{% include "OPNsense/Quagga/bgpd.conf" %} +{% include "OPNsense/Quagga/bfdd.conf" %} +{% include "OPNsense/Quagga/staticd.conf" %} diff --git a/net/frr/src/opnsense/service/templates/OPNsense/Quagga/ospf6d.conf b/net/frr/src/opnsense/service/templates/OPNsense/Quagga/ospf6d.conf index ec92113a02..66896f7a7c 100644 --- a/net/frr/src/opnsense/service/templates/OPNsense/Quagga/ospf6d.conf +++ b/net/frr/src/opnsense/service/templates/OPNsense/Quagga/ospf6d.conf @@ -1,27 +1,21 @@ +{# included in frr.conf #} {% macro cline(directive, modelname) -%}{% if modelname %} ipv6 ospf6 {{ directive }} {{ modelname }} {% endif %}{%- endmacro %} {% from 'OPNsense/Macros/interface.macro' import physical_interface %} -{% if helpers.exists('OPNsense.quagga.ospf6.enabled') and OPNsense.quagga.ospf6.enabled == '1' %} -! -! Zebra configuration saved from vty -! 2017/03/03 20:21:04 -! +{% if not helpers.empty('OPNsense.quagga.ospf6.enabled') %} {% if helpers.exists('OPNsense.quagga.general') %} -{% if helpers.exists('OPNsense.quagga.general.enablelogfile') and OPNsense.quagga.general.enablelogfile == '1' %} -log file /var/log/frr.log {{ OPNsense.quagga.general.logfilelevel }} -{% endif %} -{% if helpers.exists('OPNsense.quagga.general.enablesyslog') and OPNsense.quagga.general.enablesyslog == '1' %} -log syslog {{ OPNsense.quagga.general.sysloglevel }} +{% if OPNsense.quagga.general.enablesnmp == '1' %} +agentx {% endif %} {% endif %} -! -! -! -{% if helpers.exists('OPNsense.quagga.ospf6.interfaces.interface') %} -{% for interface in helpers.toList('OPNsense.quagga.ospf6.interfaces.interface') %} -{% if interface.enabled == '1' %} +{% for interface in helpers.toList('OPNsense.quagga.ospf6.interfaces.interface') %} +{% if interface.enabled == '1' %} interface {{ physical_interface(interface.interfacename) }} + ipv6 ospf6 area {{ interface.area }} +{% if interface.bfd|default('') == '1' %} + ipv6 ospf6 bfd +{% endif %} {% if interface.networktype %} ipv6 ospf6 network {{ interface.networktype }} {% endif %} @@ -33,27 +27,71 @@ interface {{ physical_interface(interface.interfacename) }} }}{{ cline("hello-interval",interface.hellointerval) }}{{ cline("priority",interface.priority) }}{{ cline("retransmit-interval",interface.retransmitinterval) -}}! -{% endif %} -{% endfor %} -{% endif %} -! +}} +{% endif %} +{% endfor %} router ospf6 -{% if helpers.exists('OPNsense.quagga.ospf6.redistribute') and OPNsense.quagga.ospf6.redistribute != '' %} -{% for line in OPNsense.quagga.ospf6.redistribute.split(',') %} - redistribute {{ line }} -{% endfor %}{% endif %} -{% if helpers.exists('OPNsense.quagga.ospf6.interfaces.interface') %} -{% for interface in helpers.toList('OPNsense.quagga.ospf6.interfaces.interface') %} -{% if interface.enabled == '1' %} - interface {{ physical_interface(interface.interfacename) }} area {{ interface.area }} +{% if not helpers.empty('OPNsense.quagga.ospf6.routerid') %} + ospf6 router-id {{ OPNsense.quagga.ospf6.routerid }} +{% endif %} +{% if not helpers.empty('OPNsense.quagga.ospf6.originate') %} + default-information originate{% if not helpers.empty('OPNsense.quagga.ospf6.originatealways') %} always {% endif %}{% if OPNsense.quagga.ospf6.originatemetric|default('') != '' %} metric {{ OPNsense.quagga.ospf6.originatemetric }}{% endif %} + +{% endif %} +{% for redistribution in helpers.toList('OPNsense.quagga.ospf6.redistributions.redistribution') %} +{% if redistribution.enabled == '1' %} + redistribute {{ redistribution.redistribute }}{% if redistribution.linkedRoutemap %} route-map {{ helpers.getUUID(redistribution.linkedRoutemap).name }}{% endif +%} +{% endif %} +{% endfor %} +{% if helpers.exists('OPNsense.quagga.ospf6.networks.network') %} +{% for network in helpers.toList('OPNsense.quagga.ospf6.networks.network') %} +{% if network.enabled == '1' %} + network {{ network.ipaddr }}/{{ network.netmask }} area {{ network.area }} +{% endif %} +{% if network.arearange|default("") != "" %} + area {{ network.area }} range {{ network.arearange }} +{% endif %} +{% if network.linkedPrefixlistIn|default("") != "" %} +{% for prefixlist in network.linkedPrefixlistIn.split(",") %} +{% set prefixlist2_data = helpers.getUUID(prefixlist) %} +{% if prefixlist2_data != {} and prefixlist2_data.enabled == '1' %} + area {{ network.area }} filter-list prefix {{ prefixlist2_data.name }} in +{% endif %} +{% endfor %} +{% endif %} +{% if network.linkedPrefixlistOut|default("") != "" %} +{% for prefixlist in network.linkedPrefixlistOut.split(",") %} +{% set prefixlist_data = helpers.getUUID(prefixlist) %} +{% if prefixlist_data != {} and prefixlist_data.enabled == '1' %} + area {{ network.area }} filter-list prefix {{ prefixlist_data.name }} out +{% endif %} +{% endfor %} {% endif %} {% endfor %} {% endif %} -{% if helpers.exists('OPNsense.quagga.ospf6.routerid') and OPNsense.quagga.ospf6.routerid != '' %} - router-id {{ OPNsense.quagga.ospf6.routerid }} -{% endif %} -! -line vty -! +{% if helpers.exists('OPNsense.quagga.ospf6.prefixlists.prefixlist') %} +{% for prefixlist in helpers.sortDictList(OPNsense.quagga.ospf6.prefixlists.prefixlist, 'name', 'seqnumber' ) %} +{% if prefixlist.enabled == '1' %} +ipv6 prefix-list {{ prefixlist.name }} seq {{ prefixlist.seqnumber }} {{ prefixlist.action }} {{ prefixlist.network }} +{% endif %} +{% endfor %} +{% endif %} +{% if helpers.exists('OPNsense.quagga.ospf6.routemaps.routemap') %} +{% for routemap in helpers.sortDictList(OPNsense.quagga.ospf6.routemaps.routemap, 'name', 'id' ) %} +{% if routemap.enabled == '1' %} +route-map {{ routemap.name }} {{ routemap.action }} {{ routemap.id }} +{% if routemap.match2|default("") != "" %} +{% for prefixlist in routemap.match2.split(",") %} +{% set prefixlist_data = helpers.getUUID(prefixlist) %} +{% if 'match2' in routemap and routemap.match2 != '' %} + match ipv6 address prefix-list {{ prefixlist_data.name }} +{% endif %} +{% endfor %} +{% endif %} +{% if routemap.set|default("") != "" %} + set {{ routemap.set }} +{% endif %} +{% endif %} +{% endfor %} +{% endif %} {% endif %} diff --git a/net/frr/src/opnsense/service/templates/OPNsense/Quagga/ospf6d_carp.conf b/net/frr/src/opnsense/service/templates/OPNsense/Quagga/ospf6d_carp.conf new file mode 100644 index 0000000000..3d03df677f --- /dev/null +++ b/net/frr/src/opnsense/service/templates/OPNsense/Quagga/ospf6d_carp.conf @@ -0,0 +1,14 @@ +{# consumed by ospf6d.py #} +{% from 'OPNsense/Macros/interface.macro' import physical_interface %} +{% if helpers.exists('OPNsense.quagga.ospf6.interfaces.interface') %} +{% for interface in helpers.toList('OPNsense.quagga.ospf6.interfaces.interface') %} +{% if interface.enabled == '1' %} +[{{ interface['@uuid'] }}] +enabled={{interface.enabled|default('0')}} +interface={{physical_interface(interface.interfacename)}} +default_cost={{interface.cost|default('')}} +demoted_cost={{interface.cost_demoted|default('')}} +carp_depend_on={{interface.carp_depend_on|default('')}} +{% endif %} +{% endfor %} +{% endif %} diff --git a/net/frr/src/opnsense/service/templates/OPNsense/Quagga/ospfd.conf b/net/frr/src/opnsense/service/templates/OPNsense/Quagga/ospfd.conf index 8b7729c0e7..bbb3d4b0a2 100644 --- a/net/frr/src/opnsense/service/templates/OPNsense/Quagga/ospfd.conf +++ b/net/frr/src/opnsense/service/templates/OPNsense/Quagga/ospfd.conf @@ -1,58 +1,83 @@ +{# included in frr.conf #} {% macro cline(directive, modelname) -%}{% if modelname %} ip ospf {{ directive }} {{ modelname }} {% endif %}{%- endmacro %} {% from 'OPNsense/Macros/interface.macro' import physical_interface %} {% if helpers.exists('OPNsense.quagga.ospf.enabled') and OPNsense.quagga.ospf.enabled == '1' %} -! -! Zebra configuration saved from vty -! 2017/03/03 20:21:04 -! {% if helpers.exists('OPNsense.quagga.general') %} -{% if helpers.exists('OPNsense.quagga.general.enablelogfile') and OPNsense.quagga.general.enablelogfile == '1' %} -log file /var/log/frr.log {{ OPNsense.quagga.general.logfilelevel }} -{% endif %} -{% if helpers.exists('OPNsense.quagga.general.enablesyslog') and OPNsense.quagga.general.enablesyslog == '1' %} -log syslog {{ OPNsense.quagga.general.sysloglevel }} +{% if OPNsense.quagga.general.enablesnmp == '1' %} +agentx {% endif %} {% endif %} -! -! -! +{% if OPNsense.quagga.ospf.passiveinterfaces %} +{% for iface in OPNsense.quagga.ospf.passiveinterfaces.split(',') %} +interface {{ helpers.physical_interface(iface) }} + ip ospf passive +{% endfor %} +{% endif %} +{# vtysh automatically merges passive interfaces with interfaces below #} {% if helpers.exists('OPNsense.quagga.ospf.interfaces.interface') %} -{% for interface in helpers.toList('OPNsense.quagga.ospf.interfaces.interface') %} -{% if interface.enabled == '1' %} -interface {{ physical_interface(interface.interfacename) }} -{% if interface.networktype %} -{{ cline("network",interface.networktype) -}}{% endif %} -{{ cline("authentication",interface.authtype) -}}{% if interface.authtype and interface.authtype == 'message-digest' -%}{{ cline("message-digest-key " + interface.authkey_id + " md5",interface.authkey) -}}{% endif -%}{{ cline("area",interface.area) -}}{{ cline("cost",interface.cost) -}}{{ cline("dead-interval",interface.deadinterval) -}}{{ cline("hello-interval",interface.hellointerval) -}}{{ cline("priority",interface.priority) -}}{{ cline("retransmit-interval",interface.retransmitinterval) -}}! -{% endif %} -{% endfor %} +{% for interface in helpers.toList('OPNsense.quagga.ospf.interfaces.interface') %} +{% set iface = physical_interface(interface.interfacename) %} +{% if interface.enabled == '1' %} +interface {{ iface }} +{% if interface.bfd|default('') == '1' %} + ip ospf bfd +{% endif %} +{% if interface.networktype %} +{% if interface.networktype == 'point-to-multipoint' %} +{% if interface.p2mpoptions %} + {{ cline("network", "point-to-multipoint " ~ interface.p2mpoptions) }} +{% else %} + {{ cline("network", "point-to-multipoint") }} +{% endif %} +{% else %} + {{ cline("network", interface.networktype) }} +{% endif %} +{% endif %} +{% if interface.authtype and interface.authtype == 'message-digest' %} + {{ cline("authentication", interface.authtype) }} + {{ cline("message-digest-key " + interface.authkey_id + " md5", interface.authkey) }} +{% elif interface.authtype and interface.authtype == 'plain' %} + {{ cline("authentication", ' ') }} + {{ cline("authentication-key", interface.authkey) }} +{% endif %} + {{ cline("area", interface.area) }} + {{ cline("cost", interface.cost) }} + {{ cline("dead-interval", interface.deadinterval) }} + {{ cline("hello-interval", interface.hellointerval) }} + {{ cline("priority", interface.priority) }} + {{ cline("retransmit-interval", interface.retransmitinterval) }} +{% endif %} +{% endfor %} {% endif %} -! router ospf +{% if helpers.exists('OPNsense.quagga.ospf.logadjacencychanges') and OPNsense.quagga.ospf.logadjacencychanges == '1' %} + log-adjacency-changes +{% endif %} +{% if helpers.exists('OPNsense.quagga.ospf.costreference') and OPNsense.quagga.ospf.costreference != '' %} + auto-cost reference-bandwidth {{ OPNsense.quagga.ospf.costreference }} +{% endif %} {% if helpers.exists('OPNsense.quagga.ospf.routerid') and OPNsense.quagga.ospf.routerid != '' %} ospf router-id {{ OPNsense.quagga.ospf.routerid }} {% endif %} -{% if helpers.exists('OPNsense.quagga.ospf.redistribute') and OPNsense.quagga.ospf.redistribute != '' %} -{% for line in OPNsense.quagga.ospf.redistribute.split(',') %} -{% if helpers.exists('OPNsense.quagga.ospf.redistributemap') and OPNsense.quagga.ospf.redistributemap != '' %}{% set line = line + " route-map " + helpers.getUUID(OPNsense.quagga.ospf.redistributemap).name %}{% endif %} - redistribute {{ line }} -{% endfor %}{% endif %} -{% if helpers.exists('OPNsense.quagga.ospf.passiveinterfaces') and OPNsense.quagga.ospf.passiveinterfaces != '' %} -{% for line in OPNsense.quagga.ospf.passiveinterfaces.split(',') %} - passive-interface {{ physical_interface(line) }} -{% endfor %}{% endif %} +{% for redistribution in helpers.toList('OPNsense.quagga.ospf.redistributions.redistribution') %} +{% if redistribution.enabled == '1' %} + redistribute {{ redistribution.redistribute }}{% if redistribution.linkedRoutemap %} route-map {{ helpers.getUUID(redistribution.linkedRoutemap).name }}{% endif +%} +{% endif %} +{% endfor %} +{% for area in helpers.toList('OPNsense.quagga.ospf.areas.area') %} +{% if area.enabled == '1' %} + area {{ area.id }} {{ area.type }} +{% endif %} +{% endfor %} +{% if helpers.exists('OPNsense.quagga.ospf.neighbors.neighbor') %} +{% for neighbor in helpers.toList('OPNsense.quagga.ospf.neighbors.neighbor') %} +{% if neighbor.enabled == '1' %} + neighbor {{ neighbor.address }}{% if 'pollinterval' in neighbor and neighbor.pollinterval != '' %} poll-interval {{ neighbor.pollinterval }} {% endif %}{% if 'priority' in neighbor and neighbor.priority != '' %} priority {{ neighbor.priority }} {% endif +%} +{% endif %} +{% endfor %} +{% endif %} {% if helpers.exists('OPNsense.quagga.ospf.networks.network') %} {% for network in helpers.toList('OPNsense.quagga.ospf.networks.network') %} {% if network.enabled == '1' %} @@ -83,7 +108,6 @@ router ospf default-information originate{% if helpers.exists('OPNsense.quagga.ospf.originatealways') and OPNsense.quagga.ospf.originatealways == '1' %} always {% endif %}{% if helpers.exists('OPNsense.quagga.ospf.originatemetric') and OPNsense.quagga.ospf.originatemetric != '' %} metric {{ OPNsense.quagga.ospf.originatemetric }}{% endif %} {% endif %} -! {% if helpers.exists('OPNsense.quagga.ospf.prefixlists.prefixlist') %} {% for prefixlist in helpers.sortDictList(OPNsense.quagga.ospf.prefixlists.prefixlist, 'name', 'seqnumber' ) %} {% if prefixlist.enabled == '1' %} @@ -91,7 +115,6 @@ ip prefix-list {{ prefixlist.name }} seq {{ prefixlist.seqnumber }} {{ prefixlis {% endif %} {% endfor %} {% endif %} -! {% if helpers.exists('OPNsense.quagga.ospf.routemaps.routemap') %} {% for routemap in helpers.sortDictList(OPNsense.quagga.ospf.routemaps.routemap, 'name', 'id' ) %} {% if routemap.enabled == '1' %} @@ -110,7 +133,4 @@ route-map {{ routemap.name }} {{ routemap.action }} {{ routemap.id }} {% endif %} {% endfor %} {% endif %} -! -line vty -! {% endif %} diff --git a/net/frr/src/opnsense/service/templates/OPNsense/Quagga/ospfd_carp.conf b/net/frr/src/opnsense/service/templates/OPNsense/Quagga/ospfd_carp.conf new file mode 100644 index 0000000000..d67fdde4a3 --- /dev/null +++ b/net/frr/src/opnsense/service/templates/OPNsense/Quagga/ospfd_carp.conf @@ -0,0 +1,14 @@ +{# consumed by ospfd.py #} +{% from 'OPNsense/Macros/interface.macro' import physical_interface %} +{% if helpers.exists('OPNsense.quagga.ospf.interfaces.interface') %} +{% for interface in helpers.toList('OPNsense.quagga.ospf.interfaces.interface') %} +{% if interface.enabled == '1' %} +[{{ interface['@uuid'] }}] +enabled={{interface.enabled|default('0')}} +interface={{physical_interface(interface.interfacename)}} +default_cost={{interface.cost|default('')}} +demoted_cost={{interface.cost_demoted|default('')}} +carp_depend_on={{interface.carp_depend_on|default('')}} +{% endif %} +{% endfor %} +{% endif %} diff --git a/net/frr/src/opnsense/service/templates/OPNsense/Quagga/ripd.conf b/net/frr/src/opnsense/service/templates/OPNsense/Quagga/ripd.conf index 6c5756f02a..70d29a8359 100644 --- a/net/frr/src/opnsense/service/templates/OPNsense/Quagga/ripd.conf +++ b/net/frr/src/opnsense/service/templates/OPNsense/Quagga/ripd.conf @@ -1,18 +1,6 @@ +{# included in frr.conf #} {% if helpers.exists('OPNsense.quagga.rip.enabled') and OPNsense.quagga.rip.enabled == '1' %} {% from 'OPNsense/Macros/interface.macro' import physical_interface %} -! -! Zebra configuration saved from vty -! 2017/03/26 22:40:16 -! -{% if helpers.exists('OPNsense.quagga.general') %} -{% if helpers.exists('OPNsense.quagga.general.enablelogfile') and OPNsense.quagga.general.enablelogfile == '1' %} -log file /var/log/frr.log {{ OPNsense.quagga.general.logfilelevel }} -{% endif %} -{% if helpers.exists('OPNsense.quagga.general.enablesyslog') and OPNsense.quagga.general.enablesyslog == '1' %} -log syslog {{ OPNsense.quagga.general.sysloglevel }} -{% endif %} -{% endif %} -! router rip version {{ OPNsense.quagga.rip.version }} {% if helpers.exists('OPNsense.quagga.rip.redistribute') and OPNsense.quagga.rip.redistribute != '' %} @@ -31,7 +19,4 @@ router rip {% if helpers.exists('OPNsense.quagga.rip.defaultmetric') and OPNsense.quagga.rip.defaultmetric != '' %} default-metric {{ OPNsense.quagga.rip.defaultmetric }} {% endif %} -! -line vty -! {% endif %} diff --git a/net/frr/src/opnsense/service/templates/OPNsense/Quagga/sa_policies.conf b/net/frr/src/opnsense/service/templates/OPNsense/Quagga/sa_policies.conf new file mode 100644 index 0000000000..9f0df54223 --- /dev/null +++ b/net/frr/src/opnsense/service/templates/OPNsense/Quagga/sa_policies.conf @@ -0,0 +1,25 @@ +{# consumed by scripts/frr/register_sas #} +{% if helpers.exists('OPNsense.quagga.bgp.enabled') and OPNsense.quagga.bgp.enabled == '1' %} +{% if helpers.exists('OPNsense.quagga.bgp.neighbors.neighbor') %} +{% for neighbor in helpers.toList('OPNsense.quagga.bgp.neighbors.neighbor') %} +{% if neighbor.enabled == '1' and neighbor.password|default('') != '' %} +[policy_{{neighbor['@uuid']}}_in] +src={{ neighbor.address }} +dst={{ neighbor.localip }} +protocol=tcp +spi=0x1000 +aalgo=tcp-md5 +key={{ neighbor.password }} + +[policy_{{neighbor['@uuid']}}_out] +src={{ neighbor.localip }} +dst={{ neighbor.address }} +protocol=tcp +spi=0x1000 +aalgo=tcp-md5 +key={{ neighbor.password }} + +{% endif %} +{% endfor %} +{% endif %} +{% endif %} diff --git a/net/frr/src/opnsense/service/templates/OPNsense/Quagga/staticd.conf b/net/frr/src/opnsense/service/templates/OPNsense/Quagga/staticd.conf new file mode 100644 index 0000000000..59153f6c16 --- /dev/null +++ b/net/frr/src/opnsense/service/templates/OPNsense/Quagga/staticd.conf @@ -0,0 +1,22 @@ +{# included in frr.conf #} +{% if not helpers.empty('OPNsense.quagga.static.enabled') %} +{% for route in helpers.toList('OPNsense.quagga.static.routes.route') %} +{% if route.enabled == '1' %} +{%- if ':' in route.network %} +ipv6 +{%- else %} +ip +{%- endif %} + route {{ route.network }} +{%- if route.gateway %} + {{ route.gateway}} +{%- endif %} +{%- if route.interfacename %} + {{ helpers.physical_interface(route.interfacename) }} +{%- endif %} +{%- if 'bfd' in route and route.bfd == '1' %} + bfd +{%- endif +%} +{% endif %} +{% endfor %} +{% endif %} diff --git a/net/frr/src/opnsense/service/templates/OPNsense/Quagga/syslog-ng-frr-events.conf b/net/frr/src/opnsense/service/templates/OPNsense/Quagga/syslog-ng-frr-events.conf index 9644313b81..309d47627c 100644 --- a/net/frr/src/opnsense/service/templates/OPNsense/Quagga/syslog-ng-frr-events.conf +++ b/net/frr/src/opnsense/service/templates/OPNsense/Quagga/syslog-ng-frr-events.conf @@ -6,7 +6,8 @@ destination d_frr_event { }; filter f_frr_ospf { - program("ospfd") and ( + (program("ospfd") or program("ospf6d")) + and ( ( level("info") or level("notice") ) or ( diff --git a/net/frr/src/opnsense/service/templates/OPNsense/Quagga/vtysh.conf b/net/frr/src/opnsense/service/templates/OPNsense/Quagga/vtysh.conf index e69de29bb2..57d0b081de 100644 --- a/net/frr/src/opnsense/service/templates/OPNsense/Quagga/vtysh.conf +++ b/net/frr/src/opnsense/service/templates/OPNsense/Quagga/vtysh.conf @@ -0,0 +1 @@ +{# file is empty on purpose since vtysh requires a configuration file to exist #} diff --git a/net/frr/src/opnsense/service/templates/OPNsense/Quagga/zebra.conf b/net/frr/src/opnsense/service/templates/OPNsense/Quagga/zebra.conf index 13d12b88c7..1822f47f1f 100644 --- a/net/frr/src/opnsense/service/templates/OPNsense/Quagga/zebra.conf +++ b/net/frr/src/opnsense/service/templates/OPNsense/Quagga/zebra.conf @@ -1,22 +1,14 @@ +{# included in frr.conf #} {% if helpers.exists('OPNsense.quagga.general') %} -! -! Zebra configuration saved from vty -! 2017/03/03 20:21:04 -! -{% if helpers.exists('OPNsense.quagga.general.enablelogfile') and OPNsense.quagga.general.enablelogfile == '1' %} -log file /var/log/frr.log {{ OPNsense.quagga.general.logfilelevel }} +{% if helpers.exists('OPNsense.quagga.general.profile') %} +frr defaults {{ OPNsense.quagga.general.profile }} {% endif %} {% if helpers.exists('OPNsense.quagga.general.enablesyslog') and OPNsense.quagga.general.enablesyslog == '1' %} log syslog {{ OPNsense.quagga.general.sysloglevel }} {% endif %} -! -! -! -! +{% if OPNsense.quagga.general.enablesnmp == '1' %} +agentx +{% endif %} ip forwarding ipv6 forwarding -! -! -line vty -! {% endif %} diff --git a/net/frr/src/opnsense/service/templates/OPNsense/Syslog/local/routing_frr.conf b/net/frr/src/opnsense/service/templates/OPNsense/Syslog/local/routing_frr.conf new file mode 100644 index 0000000000..61a95d16ac --- /dev/null +++ b/net/frr/src/opnsense/service/templates/OPNsense/Syslog/local/routing_frr.conf @@ -0,0 +1,6 @@ +################################################################### +# Local syslog-ng configuration filter definition [FRR]. +################################################################### +filter f_local_routing_frr { + program("bgpd") or program("ospfd") or program("ospf6d") or program("ripd") or program("zebra") or program("frr_carp") or program("frr_wrapper"); +}; diff --git a/net/frr/src/opnsense/www/js/quagga/lodash.js b/net/frr/src/opnsense/www/js/quagga/lodash.js deleted file mode 100644 index 71226cfa05..0000000000 --- a/net/frr/src/opnsense/www/js/quagga/lodash.js +++ /dev/null @@ -1,137 +0,0 @@ -/** - * @license - * Lodash lodash.com/license | Underscore.js 1.8.3 underscorejs.org/LICENSE - */ -;(function(){function n(n,t,r){switch(r.length){case 0:return n.call(t);case 1:return n.call(t,r[0]);case 2:return n.call(t,r[0],r[1]);case 3:return n.call(t,r[0],r[1],r[2])}return n.apply(t,r)}function t(n,t,r,e){for(var u=-1,i=null==n?0:n.length;++u"']/g,G=RegExp(V.source),H=RegExp(K.source),J=/<%-([\s\S]+?)%>/g,Y=/<%([\s\S]+?)%>/g,Q=/<%=([\s\S]+?)%>/g,X=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,nn=/^\w*$/,tn=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g,rn=/[\\^$.*+?()[\]{}|]/g,en=RegExp(rn.source),un=/^\s+|\s+$/g,on=/^\s+/,fn=/\s+$/,cn=/\{(?:\n\/\* \[wrapped with .+\] \*\/)?\n?/,an=/\{\n\/\* \[wrapped with (.+)\] \*/,ln=/,? & /,sn=/[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g,hn=/\\(\\)?/g,pn=/\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g,_n=/\w*$/,vn=/^[-+]0x[0-9a-f]+$/i,gn=/^0b[01]+$/i,dn=/^\[object .+?Constructor\]$/,yn=/^0o[0-7]+$/i,bn=/^(?:0|[1-9]\d*)$/,xn=/[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g,jn=/($^)/,wn=/['\n\r\u2028\u2029\\]/g,mn="[\\ufe0e\\ufe0f]?(?:[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]|\\ud83c[\\udffb-\\udfff])?(?:\\u200d(?:[^\\ud800-\\udfff]|(?:\\ud83c[\\udde6-\\uddff]){2}|[\\ud800-\\udbff][\\udc00-\\udfff])[\\ufe0e\\ufe0f]?(?:[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]|\\ud83c[\\udffb-\\udfff])?)*",An="(?:[\\u2700-\\u27bf]|(?:\\ud83c[\\udde6-\\uddff]){2}|[\\ud800-\\udbff][\\udc00-\\udfff])"+mn,En="(?:[^\\ud800-\\udfff][\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]?|[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]|(?:\\ud83c[\\udde6-\\uddff]){2}|[\\ud800-\\udbff][\\udc00-\\udfff]|[\\ud800-\\udfff])",kn=RegExp("['\u2019]","g"),Sn=RegExp("[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]","g"),On=RegExp("\\ud83c[\\udffb-\\udfff](?=\\ud83c[\\udffb-\\udfff])|"+En+mn,"g"),In=RegExp(["[A-Z\\xc0-\\xd6\\xd8-\\xde]?[a-z\\xdf-\\xf6\\xf8-\\xff]+(?:['\u2019](?:d|ll|m|re|s|t|ve))?(?=[\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000]|[A-Z\\xc0-\\xd6\\xd8-\\xde]|$)|(?:[A-Z\\xc0-\\xd6\\xd8-\\xde]|[^\\ud800-\\udfff\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000\\d+\\u2700-\\u27bfa-z\\xdf-\\xf6\\xf8-\\xffA-Z\\xc0-\\xd6\\xd8-\\xde])+(?:['\u2019](?:D|LL|M|RE|S|T|VE))?(?=[\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000]|[A-Z\\xc0-\\xd6\\xd8-\\xde](?:[a-z\\xdf-\\xf6\\xf8-\\xff]|[^\\ud800-\\udfff\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000\\d+\\u2700-\\u27bfa-z\\xdf-\\xf6\\xf8-\\xffA-Z\\xc0-\\xd6\\xd8-\\xde])|$)|[A-Z\\xc0-\\xd6\\xd8-\\xde]?(?:[a-z\\xdf-\\xf6\\xf8-\\xff]|[^\\ud800-\\udfff\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000\\d+\\u2700-\\u27bfa-z\\xdf-\\xf6\\xf8-\\xffA-Z\\xc0-\\xd6\\xd8-\\xde])+(?:['\u2019](?:d|ll|m|re|s|t|ve))?|[A-Z\\xc0-\\xd6\\xd8-\\xde]+(?:['\u2019](?:D|LL|M|RE|S|T|VE))?|\\d*(?:1ST|2ND|3RD|(?![123])\\dTH)(?=\\b|[a-z_])|\\d*(?:1st|2nd|3rd|(?![123])\\dth)(?=\\b|[A-Z_])|\\d+",An].join("|"),"g"),Rn=RegExp("[\\u200d\\ud800-\\udfff\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff\\ufe0e\\ufe0f]"),zn=/[a-z][A-Z]|[A-Z]{2}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/,Wn="Array Buffer DataView Date Error Float32Array Float64Array Function Int8Array Int16Array Int32Array Map Math Object Promise RegExp Set String Symbol TypeError Uint8Array Uint8ClampedArray Uint16Array Uint32Array WeakMap _ clearTimeout isFinite parseInt setTimeout".split(" "),Bn={}; -Bn["[object Float32Array]"]=Bn["[object Float64Array]"]=Bn["[object Int8Array]"]=Bn["[object Int16Array]"]=Bn["[object Int32Array]"]=Bn["[object Uint8Array]"]=Bn["[object Uint8ClampedArray]"]=Bn["[object Uint16Array]"]=Bn["[object Uint32Array]"]=true,Bn["[object Arguments]"]=Bn["[object Array]"]=Bn["[object ArrayBuffer]"]=Bn["[object Boolean]"]=Bn["[object DataView]"]=Bn["[object Date]"]=Bn["[object Error]"]=Bn["[object Function]"]=Bn["[object Map]"]=Bn["[object Number]"]=Bn["[object Object]"]=Bn["[object RegExp]"]=Bn["[object Set]"]=Bn["[object String]"]=Bn["[object WeakMap]"]=false; -var Ln={};Ln["[object Arguments]"]=Ln["[object Array]"]=Ln["[object ArrayBuffer]"]=Ln["[object DataView]"]=Ln["[object Boolean]"]=Ln["[object Date]"]=Ln["[object Float32Array]"]=Ln["[object Float64Array]"]=Ln["[object Int8Array]"]=Ln["[object Int16Array]"]=Ln["[object Int32Array]"]=Ln["[object Map]"]=Ln["[object Number]"]=Ln["[object Object]"]=Ln["[object RegExp]"]=Ln["[object Set]"]=Ln["[object String]"]=Ln["[object Symbol]"]=Ln["[object Uint8Array]"]=Ln["[object Uint8ClampedArray]"]=Ln["[object Uint16Array]"]=Ln["[object Uint32Array]"]=true, -Ln["[object Error]"]=Ln["[object Function]"]=Ln["[object WeakMap]"]=false;var Un={"\\":"\\","'":"'","\n":"n","\r":"r","\u2028":"u2028","\u2029":"u2029"},Cn=parseFloat,Dn=parseInt,Mn=typeof global=="object"&&global&&global.Object===Object&&global,Tn=typeof self=="object"&&self&&self.Object===Object&&self,$n=Mn||Tn||Function("return this")(),Fn=typeof exports=="object"&&exports&&!exports.nodeType&&exports,Nn=Fn&&typeof module=="object"&&module&&!module.nodeType&&module,Pn=Nn&&Nn.exports===Fn,Zn=Pn&&Mn.process,qn=function(){ -try{var n=Nn&&Nn.f&&Nn.f("util").types;return n?n:Zn&&Zn.binding&&Zn.binding("util")}catch(n){}}(),Vn=qn&&qn.isArrayBuffer,Kn=qn&&qn.isDate,Gn=qn&&qn.isMap,Hn=qn&&qn.isRegExp,Jn=qn&&qn.isSet,Yn=qn&&qn.isTypedArray,Qn=b("length"),Xn=x({"\xc0":"A","\xc1":"A","\xc2":"A","\xc3":"A","\xc4":"A","\xc5":"A","\xe0":"a","\xe1":"a","\xe2":"a","\xe3":"a","\xe4":"a","\xe5":"a","\xc7":"C","\xe7":"c","\xd0":"D","\xf0":"d","\xc8":"E","\xc9":"E","\xca":"E","\xcb":"E","\xe8":"e","\xe9":"e","\xea":"e","\xeb":"e","\xcc":"I", -"\xcd":"I","\xce":"I","\xcf":"I","\xec":"i","\xed":"i","\xee":"i","\xef":"i","\xd1":"N","\xf1":"n","\xd2":"O","\xd3":"O","\xd4":"O","\xd5":"O","\xd6":"O","\xd8":"O","\xf2":"o","\xf3":"o","\xf4":"o","\xf5":"o","\xf6":"o","\xf8":"o","\xd9":"U","\xda":"U","\xdb":"U","\xdc":"U","\xf9":"u","\xfa":"u","\xfb":"u","\xfc":"u","\xdd":"Y","\xfd":"y","\xff":"y","\xc6":"Ae","\xe6":"ae","\xde":"Th","\xfe":"th","\xdf":"ss","\u0100":"A","\u0102":"A","\u0104":"A","\u0101":"a","\u0103":"a","\u0105":"a","\u0106":"C", -"\u0108":"C","\u010a":"C","\u010c":"C","\u0107":"c","\u0109":"c","\u010b":"c","\u010d":"c","\u010e":"D","\u0110":"D","\u010f":"d","\u0111":"d","\u0112":"E","\u0114":"E","\u0116":"E","\u0118":"E","\u011a":"E","\u0113":"e","\u0115":"e","\u0117":"e","\u0119":"e","\u011b":"e","\u011c":"G","\u011e":"G","\u0120":"G","\u0122":"G","\u011d":"g","\u011f":"g","\u0121":"g","\u0123":"g","\u0124":"H","\u0126":"H","\u0125":"h","\u0127":"h","\u0128":"I","\u012a":"I","\u012c":"I","\u012e":"I","\u0130":"I","\u0129":"i", -"\u012b":"i","\u012d":"i","\u012f":"i","\u0131":"i","\u0134":"J","\u0135":"j","\u0136":"K","\u0137":"k","\u0138":"k","\u0139":"L","\u013b":"L","\u013d":"L","\u013f":"L","\u0141":"L","\u013a":"l","\u013c":"l","\u013e":"l","\u0140":"l","\u0142":"l","\u0143":"N","\u0145":"N","\u0147":"N","\u014a":"N","\u0144":"n","\u0146":"n","\u0148":"n","\u014b":"n","\u014c":"O","\u014e":"O","\u0150":"O","\u014d":"o","\u014f":"o","\u0151":"o","\u0154":"R","\u0156":"R","\u0158":"R","\u0155":"r","\u0157":"r","\u0159":"r", -"\u015a":"S","\u015c":"S","\u015e":"S","\u0160":"S","\u015b":"s","\u015d":"s","\u015f":"s","\u0161":"s","\u0162":"T","\u0164":"T","\u0166":"T","\u0163":"t","\u0165":"t","\u0167":"t","\u0168":"U","\u016a":"U","\u016c":"U","\u016e":"U","\u0170":"U","\u0172":"U","\u0169":"u","\u016b":"u","\u016d":"u","\u016f":"u","\u0171":"u","\u0173":"u","\u0174":"W","\u0175":"w","\u0176":"Y","\u0177":"y","\u0178":"Y","\u0179":"Z","\u017b":"Z","\u017d":"Z","\u017a":"z","\u017c":"z","\u017e":"z","\u0132":"IJ","\u0133":"ij", -"\u0152":"Oe","\u0153":"oe","\u0149":"'n","\u017f":"s"}),nt=x({"&":"&","<":"<",">":">",'"':""","'":"'"}),tt=x({"&":"&","<":"<",">":">",""":'"',"'":"'"}),rt=function x(mn){function An(n){if(yu(n)&&!ff(n)&&!(n instanceof Un)){if(n instanceof On)return n;if(oi.call(n,"__wrapped__"))return Fe(n)}return new On(n)}function En(){}function On(n,t){this.__wrapped__=n,this.__actions__=[],this.__chain__=!!t,this.__index__=0,this.__values__=T}function Un(n){this.__wrapped__=n, -this.__actions__=[],this.__dir__=1,this.__filtered__=false,this.__iteratees__=[],this.__takeCount__=4294967295,this.__views__=[]}function Mn(n){var t=-1,r=null==n?0:n.length;for(this.clear();++t=t?n:t)),n}function _t(n,t,e,u,i,o){var f,c=1&t,a=2&t,l=4&t;if(e&&(f=i?e(n,u,i,o):e(n)),f!==T)return f;if(!du(n))return n;if(u=ff(n)){if(f=me(n),!c)return Ur(n,f)}else{var s=vo(n),h="[object Function]"==s||"[object GeneratorFunction]"==s;if(af(n))return Ir(n,c);if("[object Object]"==s||"[object Arguments]"==s||h&&!i){if(f=a||h?{}:Ae(n),!c)return a?Mr(n,lt(f,n)):Dr(n,at(f,n))}else{if(!Ln[s])return i?n:{};f=Ee(n,s,c)}}if(o||(o=new Zn), -i=o.get(n))return i;o.set(n,f),pf(n)?n.forEach(function(r){f.add(_t(r,t,e,r,n,o))}):sf(n)&&n.forEach(function(r,u){f.set(u,_t(r,t,e,u,n,o))});var a=l?a?ve:_e:a?Bu:Wu,p=u?T:a(n);return r(p||n,function(r,u){p&&(u=r,r=n[u]),ot(f,u,_t(r,t,e,u,n,o))}),f}function vt(n){var t=Wu(n);return function(r){return gt(r,n,t)}}function gt(n,t,r){var e=r.length;if(null==n)return!e;for(n=Qu(n);e--;){var u=r[e],i=t[u],o=n[u];if(o===T&&!(u in n)||!i(o))return false}return true}function dt(n,t,r){if(typeof n!="function")throw new ti("Expected a function"); -return bo(function(){n.apply(T,r)},t)}function yt(n,t,r,e){var u=-1,i=o,a=true,l=n.length,s=[],h=t.length;if(!l)return s;r&&(t=c(t,k(r))),e?(i=f,a=false):200<=t.length&&(i=O,a=false,t=new Nn(t));n:for(;++ut}function Rt(n,t){return null!=n&&oi.call(n,t)}function zt(n,t){return null!=n&&t in Qu(n)}function Wt(n,t,r){for(var e=r?f:o,u=n[0].length,i=n.length,a=i,l=Ku(i),s=1/0,h=[];a--;){var p=n[a];a&&t&&(p=c(p,k(t))),s=Ci(p.length,s), -l[a]=!r&&(t||120<=u&&120<=p.length)?new Nn(a&&p):T}var p=n[0],_=-1,v=l[0];n:for(;++_r.length?t:kt(t,hr(r,0,-1)),r=null==t?t:t[Me(Ve(r))],null==r?T:n(r,t,e)}function Ut(n){return yu(n)&&"[object Arguments]"==Ot(n)}function Ct(n){ -return yu(n)&&"[object ArrayBuffer]"==Ot(n)}function Dt(n){return yu(n)&&"[object Date]"==Ot(n)}function Mt(n,t,r,e,u){if(n===t)t=true;else if(null==n||null==t||!yu(n)&&!yu(t))t=n!==n&&t!==t;else n:{var i=ff(n),o=ff(t),f=i?"[object Array]":vo(n),c=o?"[object Array]":vo(t),f="[object Arguments]"==f?"[object Object]":f,c="[object Arguments]"==c?"[object Object]":c,a="[object Object]"==f,o="[object Object]"==c;if((c=f==c)&&af(n)){if(!af(t)){t=false;break n}i=true,a=false}if(c&&!a)u||(u=new Zn),t=i||_f(n)?se(n,t,r,e,Mt,u):he(n,t,f,r,e,Mt,u);else{ -if(!(1&r)&&(i=a&&oi.call(n,"__wrapped__"),f=o&&oi.call(t,"__wrapped__"),i||f)){n=i?n.value():n,t=f?t.value():t,u||(u=new Zn),t=Mt(n,t,r,e,u);break n}if(c)t:if(u||(u=new Zn),i=1&r,f=_e(n),o=f.length,c=_e(t).length,o==c||i){for(a=o;a--;){var l=f[a];if(!(i?l in t:oi.call(t,l))){t=false;break t}}if((c=u.get(n))&&u.get(t))t=c==t;else{c=true,u.set(n,t),u.set(t,n);for(var s=i;++at?r:0,Se(t,r)?n[t]:T}function Xt(n,t,r){var e=-1;return t=c(t.length?t:[$u],k(ye())),n=Gt(n,function(n){return{ -a:c(t,function(t){return t(n)}),b:++e,c:n}}),w(n,function(n,t){var e;n:{e=-1;for(var u=n.a,i=t.a,o=u.length,f=r.length;++e=f?c:c*("desc"==r[e]?-1:1);break n}}e=n.b-t.b}return e})}function nr(n,t){return tr(n,t,function(t,r){return zu(n,r)})}function tr(n,t,r){for(var e=-1,u=t.length,i={};++et||9007199254740991t&&(t=-t>u?0:u+t),r=r>u?u:r,0>r&&(r+=u),u=t>r?0:r-t>>>0,t>>>=0,r=Ku(u);++e=u){for(;e>>1,o=n[i];null!==o&&!wu(o)&&(r?o<=t:ot.length?n:kt(n,hr(t,0,-1)),null==n||delete n[Me(Ve(t))]}function jr(n,t,r,e){for(var u=n.length,i=e?u:-1;(e?i--:++ie)return e?br(n[0]):[];for(var u=-1,i=Ku(e);++u=e?n:hr(n,t,r)}function Ir(n,t){if(t)return n.slice();var r=n.length,r=gi?gi(r):new n.constructor(r);return n.copy(r),r}function Rr(n){var t=new n.constructor(n.byteLength);return new vi(t).set(new vi(n)), -t}function zr(n,t){return new n.constructor(t?Rr(n.buffer):n.buffer,n.byteOffset,n.length)}function Wr(n,t){if(n!==t){var r=n!==T,e=null===n,u=n===n,i=wu(n),o=t!==T,f=null===t,c=t===t,a=wu(t);if(!f&&!a&&!i&&n>t||i&&o&&c&&!f&&!a||e&&o&&c||!r&&c||!u)return 1;if(!e&&!i&&!a&&nu?T:i,u=1),t=Qu(t);++eo&&f[0]!==a&&f[o-1]!==a?[]:L(f,a), -o-=c.length,or?r?or(t,n):t:(r=or(t,Oi(n/D(t))),Rn.test(t)?Or(M(r),0,n).join(""):r.slice(0,n))}function te(t,r,e,u){function i(){for(var r=-1,c=arguments.length,a=-1,l=u.length,s=Ku(l+c),h=this&&this!==$n&&this instanceof i?f:t;++at||e)&&(1&n&&(i[2]=h[2],t|=1&r?0:4),(r=h[3])&&(e=i[3],i[3]=e?Br(e,r,h[4]):r,i[4]=e?L(i[3],"__lodash_placeholder__"):h[4]),(r=h[5])&&(e=i[5],i[5]=e?Lr(e,r,h[6]):r,i[6]=e?L(i[5],"__lodash_placeholder__"):h[6]),(r=h[7])&&(i[7]=r),128&n&&(i[8]=null==i[8]?h[8]:Ci(i[8],h[8])),null==i[9]&&(i[9]=h[9]),i[0]=h[0],i[1]=t),n=i[0], -t=i[1],r=i[2],e=i[3],u=i[4],f=i[9]=i[9]===T?c?0:n.length:Ui(i[9]-a,0),!f&&24&t&&(t&=-25),Ue((h?co:yo)(t&&1!=t?8==t||16==t?Kr(n,t,f):32!=t&&33!=t||u.length?Jr.apply(T,i):te(n,t,r,e):Pr(n,t,r),i),n,t)}function ce(n,t,r,e){return n===T||lu(n,ei[r])&&!oi.call(e,r)?t:n}function ae(n,t,r,e,u,i){return du(n)&&du(t)&&(i.set(t,n),Yt(n,t,T,ae,i),i.delete(t)),n}function le(n){return xu(n)?T:n}function se(n,t,r,e,u,i){var o=1&r,f=n.length,c=t.length;if(f!=c&&!(o&&c>f))return false;if((c=i.get(n))&&i.get(t))return c==t; -var c=-1,a=true,l=2&r?new Nn:T;for(i.set(n,t),i.set(t,n);++cr&&(r=Ui(e+r,0)),_(n,ye(t,3),r)):-1}function Pe(n,t,r){var e=null==n?0:n.length;if(!e)return-1;var u=e-1;return r!==T&&(u=Eu(r),u=0>r?Ui(e+u,0):Ci(u,e-1)), -_(n,ye(t,3),u,true)}function Ze(n){return(null==n?0:n.length)?wt(n,1):[]}function qe(n){return n&&n.length?n[0]:T}function Ve(n){var t=null==n?0:n.length;return t?n[t-1]:T}function Ke(n,t){return n&&n.length&&t&&t.length?er(n,t):n}function Ge(n){return null==n?n:$i.call(n)}function He(n){if(!n||!n.length)return[];var t=0;return n=i(n,function(n){if(hu(n))return t=Ui(n.length,t),true}),A(t,function(t){return c(n,b(t))})}function Je(t,r){if(!t||!t.length)return[];var e=He(t);return null==r?e:c(e,function(t){ -return n(r,T,t)})}function Ye(n){return n=An(n),n.__chain__=true,n}function Qe(n,t){return t(n)}function Xe(){return this}function nu(n,t){return(ff(n)?r:uo)(n,ye(t,3))}function tu(n,t){return(ff(n)?e:io)(n,ye(t,3))}function ru(n,t){return(ff(n)?c:Gt)(n,ye(t,3))}function eu(n,t,r){return t=r?T:t,t=n&&null==t?n.length:t,fe(n,128,T,T,T,T,t)}function uu(n,t){var r;if(typeof t!="function")throw new ti("Expected a function");return n=Eu(n),function(){return 0<--n&&(r=t.apply(this,arguments)),1>=n&&(t=T), -r}}function iu(n,t,r){return t=r?T:t,n=fe(n,8,T,T,T,T,T,t),n.placeholder=iu.placeholder,n}function ou(n,t,r){return t=r?T:t,n=fe(n,16,T,T,T,T,T,t),n.placeholder=ou.placeholder,n}function fu(n,t,r){function e(t){var r=c,e=a;return c=a=T,_=t,s=n.apply(e,r)}function u(n){var r=n-p;return n-=_,p===T||r>=t||0>r||g&&n>=l}function i(){var n=Go();if(u(n))return o(n);var r,e=bo;r=n-_,n=t-(n-p),r=g?Ci(n,l-r):n,h=e(i,r)}function o(n){return h=T,d&&c?e(n):(c=a=T,s)}function f(){var n=Go(),r=u(n);if(c=arguments, -a=this,p=n,r){if(h===T)return _=n=p,h=bo(i,t),v?e(n):s;if(g)return lo(h),h=bo(i,t),e(p)}return h===T&&(h=bo(i,t)),s}var c,a,l,s,h,p,_=0,v=false,g=false,d=true;if(typeof n!="function")throw new ti("Expected a function");return t=Su(t)||0,du(r)&&(v=!!r.leading,l=(g="maxWait"in r)?Ui(Su(r.maxWait)||0,t):l,d="trailing"in r?!!r.trailing:d),f.cancel=function(){h!==T&&lo(h),_=0,c=p=a=h=T},f.flush=function(){return h===T?s:o(Go())},f}function cu(n,t){function r(){var e=arguments,u=t?t.apply(this,e):e[0],i=r.cache; -return i.has(u)?i.get(u):(e=n.apply(this,e),r.cache=i.set(u,e)||i,e)}if(typeof n!="function"||null!=t&&typeof t!="function")throw new ti("Expected a function");return r.cache=new(cu.Cache||Fn),r}function au(n){if(typeof n!="function")throw new ti("Expected a function");return function(){var t=arguments;switch(t.length){case 0:return!n.call(this);case 1:return!n.call(this,t[0]);case 2:return!n.call(this,t[0],t[1]);case 3:return!n.call(this,t[0],t[1],t[2])}return!n.apply(this,t)}}function lu(n,t){return n===t||n!==n&&t!==t; -}function su(n){return null!=n&&gu(n.length)&&!_u(n)}function hu(n){return yu(n)&&su(n)}function pu(n){if(!yu(n))return false;var t=Ot(n);return"[object Error]"==t||"[object DOMException]"==t||typeof n.message=="string"&&typeof n.name=="string"&&!xu(n)}function _u(n){return!!du(n)&&(n=Ot(n),"[object Function]"==n||"[object GeneratorFunction]"==n||"[object AsyncFunction]"==n||"[object Proxy]"==n)}function vu(n){return typeof n=="number"&&n==Eu(n)}function gu(n){return typeof n=="number"&&-1=n; -}function du(n){var t=typeof n;return null!=n&&("object"==t||"function"==t)}function yu(n){return null!=n&&typeof n=="object"}function bu(n){return typeof n=="number"||yu(n)&&"[object Number]"==Ot(n)}function xu(n){return!(!yu(n)||"[object Object]"!=Ot(n))&&(n=di(n),null===n||(n=oi.call(n,"constructor")&&n.constructor,typeof n=="function"&&n instanceof n&&ii.call(n)==li))}function ju(n){return typeof n=="string"||!ff(n)&&yu(n)&&"[object String]"==Ot(n)}function wu(n){return typeof n=="symbol"||yu(n)&&"[object Symbol]"==Ot(n); -}function mu(n){if(!n)return[];if(su(n))return ju(n)?M(n):Ur(n);if(wi&&n[wi]){n=n[wi]();for(var t,r=[];!(t=n.next()).done;)r.push(t.value);return r}return t=vo(n),("[object Map]"==t?W:"[object Set]"==t?U:Uu)(n)}function Au(n){return n?(n=Su(n),n===$||n===-$?1.7976931348623157e308*(0>n?-1:1):n===n?n:0):0===n?n:0}function Eu(n){n=Au(n);var t=n%1;return n===n?t?n-t:n:0}function ku(n){return n?pt(Eu(n),0,4294967295):0}function Su(n){if(typeof n=="number")return n;if(wu(n))return F;if(du(n)&&(n=typeof n.valueOf=="function"?n.valueOf():n, -n=du(n)?n+"":n),typeof n!="string")return 0===n?n:+n;n=n.replace(un,"");var t=gn.test(n);return t||yn.test(n)?Dn(n.slice(2),t?2:8):vn.test(n)?F:+n}function Ou(n){return Cr(n,Bu(n))}function Iu(n){return null==n?"":yr(n)}function Ru(n,t,r){return n=null==n?T:kt(n,t),n===T?r:n}function zu(n,t){return null!=n&&we(n,t,zt)}function Wu(n){return su(n)?qn(n):Vt(n)}function Bu(n){if(su(n))n=qn(n,true);else if(du(n)){var t,r=ze(n),e=[];for(t in n)("constructor"!=t||!r&&oi.call(n,t))&&e.push(t);n=e}else{if(t=[], -null!=n)for(r in Qu(n))t.push(r);n=t}return n}function Lu(n,t){if(null==n)return{};var r=c(ve(n),function(n){return[n]});return t=ye(t),tr(n,r,function(n,r){return t(n,r[0])})}function Uu(n){return null==n?[]:S(n,Wu(n))}function Cu(n){return $f(Iu(n).toLowerCase())}function Du(n){return(n=Iu(n))&&n.replace(xn,Xn).replace(Sn,"")}function Mu(n,t,r){return n=Iu(n),t=r?T:t,t===T?zn.test(n)?n.match(In)||[]:n.match(sn)||[]:n.match(t)||[]}function Tu(n){return function(){return n}}function $u(n){return n; -}function Fu(n){return qt(typeof n=="function"?n:_t(n,1))}function Nu(n,t,e){var u=Wu(t),i=Et(t,u);null!=e||du(t)&&(i.length||!u.length)||(e=t,t=n,n=this,i=Et(t,Wu(t)));var o=!(du(e)&&"chain"in e&&!e.chain),f=_u(n);return r(i,function(r){var e=t[r];n[r]=e,f&&(n.prototype[r]=function(){var t=this.__chain__;if(o||t){var r=n(this.__wrapped__);return(r.__actions__=Ur(this.__actions__)).push({func:e,args:arguments,thisArg:n}),r.__chain__=t,r}return e.apply(n,a([this.value()],arguments))})}),n}function Pu(){} -function Zu(n){return Ie(n)?b(Me(n)):rr(n)}function qu(){return[]}function Vu(){return false}mn=null==mn?$n:rt.defaults($n.Object(),mn,rt.pick($n,Wn));var Ku=mn.Array,Gu=mn.Date,Hu=mn.Error,Ju=mn.Function,Yu=mn.Math,Qu=mn.Object,Xu=mn.RegExp,ni=mn.String,ti=mn.TypeError,ri=Ku.prototype,ei=Qu.prototype,ui=mn["__core-js_shared__"],ii=Ju.prototype.toString,oi=ei.hasOwnProperty,fi=0,ci=function(){var n=/[^.]+$/.exec(ui&&ui.keys&&ui.keys.IE_PROTO||"");return n?"Symbol(src)_1."+n:""}(),ai=ei.toString,li=ii.call(Qu),si=$n._,hi=Xu("^"+ii.call(oi).replace(rn,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$"),pi=Pn?mn.Buffer:T,_i=mn.Symbol,vi=mn.Uint8Array,gi=pi?pi.g:T,di=B(Qu.getPrototypeOf,Qu),yi=Qu.create,bi=ei.propertyIsEnumerable,xi=ri.splice,ji=_i?_i.isConcatSpreadable:T,wi=_i?_i.iterator:T,mi=_i?_i.toStringTag:T,Ai=function(){ -try{var n=je(Qu,"defineProperty");return n({},"",{}),n}catch(n){}}(),Ei=mn.clearTimeout!==$n.clearTimeout&&mn.clearTimeout,ki=Gu&&Gu.now!==$n.Date.now&&Gu.now,Si=mn.setTimeout!==$n.setTimeout&&mn.setTimeout,Oi=Yu.ceil,Ii=Yu.floor,Ri=Qu.getOwnPropertySymbols,zi=pi?pi.isBuffer:T,Wi=mn.isFinite,Bi=ri.join,Li=B(Qu.keys,Qu),Ui=Yu.max,Ci=Yu.min,Di=Gu.now,Mi=mn.parseInt,Ti=Yu.random,$i=ri.reverse,Fi=je(mn,"DataView"),Ni=je(mn,"Map"),Pi=je(mn,"Promise"),Zi=je(mn,"Set"),qi=je(mn,"WeakMap"),Vi=je(Qu,"create"),Ki=qi&&new qi,Gi={},Hi=Te(Fi),Ji=Te(Ni),Yi=Te(Pi),Qi=Te(Zi),Xi=Te(qi),no=_i?_i.prototype:T,to=no?no.valueOf:T,ro=no?no.toString:T,eo=function(){ -function n(){}return function(t){return du(t)?yi?yi(t):(n.prototype=t,t=new n,n.prototype=T,t):{}}}();An.templateSettings={escape:J,evaluate:Y,interpolate:Q,variable:"",imports:{_:An}},An.prototype=En.prototype,An.prototype.constructor=An,On.prototype=eo(En.prototype),On.prototype.constructor=On,Un.prototype=eo(En.prototype),Un.prototype.constructor=Un,Mn.prototype.clear=function(){this.__data__=Vi?Vi(null):{},this.size=0},Mn.prototype.delete=function(n){return n=this.has(n)&&delete this.__data__[n], -this.size-=n?1:0,n},Mn.prototype.get=function(n){var t=this.__data__;return Vi?(n=t[n],"__lodash_hash_undefined__"===n?T:n):oi.call(t,n)?t[n]:T},Mn.prototype.has=function(n){var t=this.__data__;return Vi?t[n]!==T:oi.call(t,n)},Mn.prototype.set=function(n,t){var r=this.__data__;return this.size+=this.has(n)?0:1,r[n]=Vi&&t===T?"__lodash_hash_undefined__":t,this},Tn.prototype.clear=function(){this.__data__=[],this.size=0},Tn.prototype.delete=function(n){var t=this.__data__;return n=ft(t,n),!(0>n)&&(n==t.length-1?t.pop():xi.call(t,n,1), ---this.size,true)},Tn.prototype.get=function(n){var t=this.__data__;return n=ft(t,n),0>n?T:t[n][1]},Tn.prototype.has=function(n){return-1e?(++this.size,r.push([n,t])):r[e][1]=t,this},Fn.prototype.clear=function(){this.size=0,this.__data__={hash:new Mn,map:new(Ni||Tn),string:new Mn}},Fn.prototype.delete=function(n){return n=be(this,n).delete(n),this.size-=n?1:0,n},Fn.prototype.get=function(n){return be(this,n).get(n); -},Fn.prototype.has=function(n){return be(this,n).has(n)},Fn.prototype.set=function(n,t){var r=be(this,n),e=r.size;return r.set(n,t),this.size+=r.size==e?0:1,this},Nn.prototype.add=Nn.prototype.push=function(n){return this.__data__.set(n,"__lodash_hash_undefined__"),this},Nn.prototype.has=function(n){return this.__data__.has(n)},Zn.prototype.clear=function(){this.__data__=new Tn,this.size=0},Zn.prototype.delete=function(n){var t=this.__data__;return n=t.delete(n),this.size=t.size,n},Zn.prototype.get=function(n){ -return this.__data__.get(n)},Zn.prototype.has=function(n){return this.__data__.has(n)},Zn.prototype.set=function(n,t){var r=this.__data__;if(r instanceof Tn){var e=r.__data__;if(!Ni||199>e.length)return e.push([n,t]),this.size=++r.size,this;r=this.__data__=new Fn(e)}return r.set(n,t),this.size=r.size,this};var uo=Fr(mt),io=Fr(At,true),oo=Nr(),fo=Nr(true),co=Ki?function(n,t){return Ki.set(n,t),n}:$u,ao=Ai?function(n,t){return Ai(n,"toString",{configurable:true,enumerable:false,value:Tu(t),writable:true})}:$u,lo=Ei||function(n){ -return $n.clearTimeout(n)},so=Zi&&1/U(new Zi([,-0]))[1]==$?function(n){return new Zi(n)}:Pu,ho=Ki?function(n){return Ki.get(n)}:Pu,po=Ri?function(n){return null==n?[]:(n=Qu(n),i(Ri(n),function(t){return bi.call(n,t)}))}:qu,_o=Ri?function(n){for(var t=[];n;)a(t,po(n)),n=di(n);return t}:qu,vo=Ot;(Fi&&"[object DataView]"!=vo(new Fi(new ArrayBuffer(1)))||Ni&&"[object Map]"!=vo(new Ni)||Pi&&"[object Promise]"!=vo(Pi.resolve())||Zi&&"[object Set]"!=vo(new Zi)||qi&&"[object WeakMap]"!=vo(new qi))&&(vo=function(n){ -var t=Ot(n);if(n=(n="[object Object]"==t?n.constructor:T)?Te(n):"")switch(n){case Hi:return"[object DataView]";case Ji:return"[object Map]";case Yi:return"[object Promise]";case Qi:return"[object Set]";case Xi:return"[object WeakMap]"}return t});var go=ui?_u:Vu,yo=Ce(co),bo=Si||function(n,t){return $n.setTimeout(n,t)},xo=Ce(ao),jo=function(n){n=cu(n,function(n){return 500===t.size&&t.clear(),n});var t=n.cache;return n}(function(n){var t=[];return 46===n.charCodeAt(0)&&t.push(""),n.replace(tn,function(n,r,e,u){ -t.push(e?u.replace(hn,"$1"):r||n)}),t}),wo=fr(function(n,t){return hu(n)?yt(n,wt(t,1,hu,true)):[]}),mo=fr(function(n,t){var r=Ve(t);return hu(r)&&(r=T),hu(n)?yt(n,wt(t,1,hu,true),ye(r,2)):[]}),Ao=fr(function(n,t){var r=Ve(t);return hu(r)&&(r=T),hu(n)?yt(n,wt(t,1,hu,true),T,r):[]}),Eo=fr(function(n){var t=c(n,Er);return t.length&&t[0]===n[0]?Wt(t):[]}),ko=fr(function(n){var t=Ve(n),r=c(n,Er);return t===Ve(r)?t=T:r.pop(),r.length&&r[0]===n[0]?Wt(r,ye(t,2)):[]}),So=fr(function(n){var t=Ve(n),r=c(n,Er);return(t=typeof t=="function"?t:T)&&r.pop(), -r.length&&r[0]===n[0]?Wt(r,T,t):[]}),Oo=fr(Ke),Io=pe(function(n,t){var r=null==n?0:n.length,e=ht(n,t);return ur(n,c(t,function(n){return Se(n,r)?+n:n}).sort(Wr)),e}),Ro=fr(function(n){return br(wt(n,1,hu,true))}),zo=fr(function(n){var t=Ve(n);return hu(t)&&(t=T),br(wt(n,1,hu,true),ye(t,2))}),Wo=fr(function(n){var t=Ve(n),t=typeof t=="function"?t:T;return br(wt(n,1,hu,true),T,t)}),Bo=fr(function(n,t){return hu(n)?yt(n,t):[]}),Lo=fr(function(n){return mr(i(n,hu))}),Uo=fr(function(n){var t=Ve(n);return hu(t)&&(t=T), -mr(i(n,hu),ye(t,2))}),Co=fr(function(n){var t=Ve(n),t=typeof t=="function"?t:T;return mr(i(n,hu),T,t)}),Do=fr(He),Mo=fr(function(n){var t=n.length,t=1=t}),of=Ut(function(){return arguments}())?Ut:function(n){return yu(n)&&oi.call(n,"callee")&&!bi.call(n,"callee")},ff=Ku.isArray,cf=Vn?k(Vn):Ct,af=zi||Vu,lf=Kn?k(Kn):Dt,sf=Gn?k(Gn):Tt,hf=Hn?k(Hn):Nt,pf=Jn?k(Jn):Pt,_f=Yn?k(Yn):Zt,vf=ee(Kt),gf=ee(function(n,t){return n<=t}),df=$r(function(n,t){ -if(ze(t)||su(t))Cr(t,Wu(t),n);else for(var r in t)oi.call(t,r)&&ot(n,r,t[r])}),yf=$r(function(n,t){Cr(t,Bu(t),n)}),bf=$r(function(n,t,r,e){Cr(t,Bu(t),n,e)}),xf=$r(function(n,t,r,e){Cr(t,Wu(t),n,e)}),jf=pe(ht),wf=fr(function(n,t){n=Qu(n);var r=-1,e=t.length,u=2--n)return t.apply(this,arguments)}},An.ary=eu,An.assign=df,An.assignIn=yf,An.assignInWith=bf,An.assignWith=xf,An.at=jf,An.before=uu,An.bind=Ho,An.bindAll=Nf,An.bindKey=Jo,An.castArray=function(){if(!arguments.length)return[];var n=arguments[0];return ff(n)?n:[n]},An.chain=Ye,An.chunk=function(n,t,r){if(t=(r?Oe(n,t,r):t===T)?1:Ui(Eu(t),0),r=null==n?0:n.length,!r||1>t)return[];for(var e=0,u=0,i=Ku(Oi(r/t));et?0:t,e)):[]},An.dropRight=function(n,t,r){var e=null==n?0:n.length;return e?(t=r||t===T?1:Eu(t),t=e-t,hr(n,0,0>t?0:t)):[]},An.dropRightWhile=function(n,t){return n&&n.length?jr(n,ye(t,3),true,true):[]; -},An.dropWhile=function(n,t){return n&&n.length?jr(n,ye(t,3),true):[]},An.fill=function(n,t,r,e){var u=null==n?0:n.length;if(!u)return[];for(r&&typeof r!="number"&&Oe(n,t,r)&&(r=0,e=u),u=n.length,r=Eu(r),0>r&&(r=-r>u?0:u+r),e=e===T||e>u?u:Eu(e),0>e&&(e+=u),e=r>e?0:ku(e);r>>0,r?(n=Iu(n))&&(typeof t=="string"||null!=t&&!hf(t))&&(t=yr(t),!t&&Rn.test(n))?Or(M(n),0,r):n.split(t,r):[]},An.spread=function(t,r){if(typeof t!="function")throw new ti("Expected a function");return r=null==r?0:Ui(Eu(r),0), -fr(function(e){var u=e[r];return e=Or(e,0,r),u&&a(e,u),n(t,this,e)})},An.tail=function(n){var t=null==n?0:n.length;return t?hr(n,1,t):[]},An.take=function(n,t,r){return n&&n.length?(t=r||t===T?1:Eu(t),hr(n,0,0>t?0:t)):[]},An.takeRight=function(n,t,r){var e=null==n?0:n.length;return e?(t=r||t===T?1:Eu(t),t=e-t,hr(n,0>t?0:t,e)):[]},An.takeRightWhile=function(n,t){return n&&n.length?jr(n,ye(t,3),false,true):[]},An.takeWhile=function(n,t){return n&&n.length?jr(n,ye(t,3)):[]},An.tap=function(n,t){return t(n), -n},An.throttle=function(n,t,r){var e=true,u=true;if(typeof n!="function")throw new ti("Expected a function");return du(r)&&(e="leading"in r?!!r.leading:e,u="trailing"in r?!!r.trailing:u),fu(n,t,{leading:e,maxWait:t,trailing:u})},An.thru=Qe,An.toArray=mu,An.toPairs=zf,An.toPairsIn=Wf,An.toPath=function(n){return ff(n)?c(n,Me):wu(n)?[n]:Ur(jo(Iu(n)))},An.toPlainObject=Ou,An.transform=function(n,t,e){var u=ff(n),i=u||af(n)||_f(n);if(t=ye(t,4),null==e){var o=n&&n.constructor;e=i?u?new o:[]:du(n)&&_u(o)?eo(di(n)):{}; -}return(i?r:mt)(n,function(n,r,u){return t(e,n,r,u)}),e},An.unary=function(n){return eu(n,1)},An.union=Ro,An.unionBy=zo,An.unionWith=Wo,An.uniq=function(n){return n&&n.length?br(n):[]},An.uniqBy=function(n,t){return n&&n.length?br(n,ye(t,2)):[]},An.uniqWith=function(n,t){return t=typeof t=="function"?t:T,n&&n.length?br(n,T,t):[]},An.unset=function(n,t){return null==n||xr(n,t)},An.unzip=He,An.unzipWith=Je,An.update=function(n,t,r){return null==n?n:lr(n,t,kr(r)(kt(n,t)),void 0)},An.updateWith=function(n,t,r,e){ -return e=typeof e=="function"?e:T,null!=n&&(n=lr(n,t,kr(r)(kt(n,t)),e)),n},An.values=Uu,An.valuesIn=function(n){return null==n?[]:S(n,Bu(n))},An.without=Bo,An.words=Mu,An.wrap=function(n,t){return nf(kr(t),n)},An.xor=Lo,An.xorBy=Uo,An.xorWith=Co,An.zip=Do,An.zipObject=function(n,t){return Ar(n||[],t||[],ot)},An.zipObjectDeep=function(n,t){return Ar(n||[],t||[],lr)},An.zipWith=Mo,An.entries=zf,An.entriesIn=Wf,An.extend=yf,An.extendWith=bf,Nu(An,An),An.add=Qf,An.attempt=Ff,An.camelCase=Bf,An.capitalize=Cu, -An.ceil=Xf,An.clamp=function(n,t,r){return r===T&&(r=t,t=T),r!==T&&(r=Su(r),r=r===r?r:0),t!==T&&(t=Su(t),t=t===t?t:0),pt(Su(n),t,r)},An.clone=function(n){return _t(n,4)},An.cloneDeep=function(n){return _t(n,5)},An.cloneDeepWith=function(n,t){return t=typeof t=="function"?t:T,_t(n,5,t)},An.cloneWith=function(n,t){return t=typeof t=="function"?t:T,_t(n,4,t)},An.conformsTo=function(n,t){return null==t||gt(n,t,Wu(t))},An.deburr=Du,An.defaultTo=function(n,t){return null==n||n!==n?t:n},An.divide=nc,An.endsWith=function(n,t,r){ -n=Iu(n),t=yr(t);var e=n.length,e=r=r===T?e:pt(Eu(r),0,e);return r-=t.length,0<=r&&n.slice(r,e)==t},An.eq=lu,An.escape=function(n){return(n=Iu(n))&&H.test(n)?n.replace(K,nt):n},An.escapeRegExp=function(n){return(n=Iu(n))&&en.test(n)?n.replace(rn,"\\$&"):n},An.every=function(n,t,r){var e=ff(n)?u:bt;return r&&Oe(n,t,r)&&(t=T),e(n,ye(t,3))},An.find=Fo,An.findIndex=Ne,An.findKey=function(n,t){return p(n,ye(t,3),mt)},An.findLast=No,An.findLastIndex=Pe,An.findLastKey=function(n,t){return p(n,ye(t,3),At); -},An.floor=tc,An.forEach=nu,An.forEachRight=tu,An.forIn=function(n,t){return null==n?n:oo(n,ye(t,3),Bu)},An.forInRight=function(n,t){return null==n?n:fo(n,ye(t,3),Bu)},An.forOwn=function(n,t){return n&&mt(n,ye(t,3))},An.forOwnRight=function(n,t){return n&&At(n,ye(t,3))},An.get=Ru,An.gt=ef,An.gte=uf,An.has=function(n,t){return null!=n&&we(n,t,Rt)},An.hasIn=zu,An.head=qe,An.identity=$u,An.includes=function(n,t,r,e){return n=su(n)?n:Uu(n),r=r&&!e?Eu(r):0,e=n.length,0>r&&(r=Ui(e+r,0)),ju(n)?r<=e&&-1r&&(r=Ui(e+r,0)),v(n,t,r)):-1},An.inRange=function(n,t,r){return t=Au(t),r===T?(r=t,t=0):r=Au(r),n=Su(n),n>=Ci(t,r)&&n=n},An.isSet=pf,An.isString=ju,An.isSymbol=wu,An.isTypedArray=_f,An.isUndefined=function(n){return n===T},An.isWeakMap=function(n){return yu(n)&&"[object WeakMap]"==vo(n)},An.isWeakSet=function(n){return yu(n)&&"[object WeakSet]"==Ot(n)},An.join=function(n,t){return null==n?"":Bi.call(n,t)},An.kebabCase=Lf,An.last=Ve,An.lastIndexOf=function(n,t,r){var e=null==n?0:n.length;if(!e)return-1;var u=e;if(r!==T&&(u=Eu(r),u=0>u?Ui(e+u,0):Ci(u,e-1)), -t===t){for(r=u+1;r--&&n[r]!==t;);n=r}else n=_(n,d,u,true);return n},An.lowerCase=Uf,An.lowerFirst=Cf,An.lt=vf,An.lte=gf,An.max=function(n){return n&&n.length?xt(n,$u,It):T},An.maxBy=function(n,t){return n&&n.length?xt(n,ye(t,2),It):T},An.mean=function(n){return y(n,$u)},An.meanBy=function(n,t){return y(n,ye(t,2))},An.min=function(n){return n&&n.length?xt(n,$u,Kt):T},An.minBy=function(n,t){return n&&n.length?xt(n,ye(t,2),Kt):T},An.stubArray=qu,An.stubFalse=Vu,An.stubObject=function(){return{}},An.stubString=function(){ -return""},An.stubTrue=function(){return true},An.multiply=rc,An.nth=function(n,t){return n&&n.length?Qt(n,Eu(t)):T},An.noConflict=function(){return $n._===this&&($n._=si),this},An.noop=Pu,An.now=Go,An.pad=function(n,t,r){n=Iu(n);var e=(t=Eu(t))?D(n):0;return!t||e>=t?n:(t=(t-e)/2,ne(Ii(t),r)+n+ne(Oi(t),r))},An.padEnd=function(n,t,r){n=Iu(n);var e=(t=Eu(t))?D(n):0;return t&&et){var e=n;n=t,t=e}return r||n%1||t%1?(r=Ti(),Ci(n+r*(t-n+Cn("1e-"+((r+"").length-1))),t)):ir(n,t)},An.reduce=function(n,t,r){var e=ff(n)?l:j,u=3>arguments.length;return e(n,ye(t,4),r,u,uo)},An.reduceRight=function(n,t,r){var e=ff(n)?s:j,u=3>arguments.length; -return e(n,ye(t,4),r,u,io)},An.repeat=function(n,t,r){return t=(r?Oe(n,t,r):t===T)?1:Eu(t),or(Iu(n),t)},An.replace=function(){var n=arguments,t=Iu(n[0]);return 3>n.length?t:t.replace(n[1],n[2])},An.result=function(n,t,r){t=Sr(t,n);var e=-1,u=t.length;for(u||(u=1,n=T);++en||9007199254740991=i)return n;if(i=r-D(e),1>i)return e;if(r=o?Or(o,0,i).join(""):n.slice(0,i),u===T)return r+e;if(o&&(i+=r.length-i),hf(u)){if(n.slice(i).search(u)){ -var f=r;for(u.global||(u=Xu(u.source,Iu(_n.exec(u))+"g")),u.lastIndex=0;o=u.exec(f);)var c=o.index;r=r.slice(0,c===T?i:c)}}else n.indexOf(yr(u),i)!=i&&(u=r.lastIndexOf(u),-1e.__dir__?"Right":"")}),e},Un.prototype[n+"Right"]=function(t){return this.reverse()[n](t).reverse()}}),r(["filter","map","takeWhile"],function(n,t){ -var r=t+1,e=1==r||3==r;Un.prototype[n]=function(n){var t=this.clone();return t.__iteratees__.push({iteratee:ye(n,3),type:r}),t.__filtered__=t.__filtered__||e,t}}),r(["head","last"],function(n,t){var r="take"+(t?"Right":"");Un.prototype[n]=function(){return this[r](1).value()[0]}}),r(["initial","tail"],function(n,t){var r="drop"+(t?"":"Right");Un.prototype[n]=function(){return this.__filtered__?new Un(this):this[r](1)}}),Un.prototype.compact=function(){return this.filter($u)},Un.prototype.find=function(n){ -return this.filter(n).head()},Un.prototype.findLast=function(n){return this.reverse().find(n)},Un.prototype.invokeMap=fr(function(n,t){return typeof n=="function"?new Un(this):this.map(function(r){return Lt(r,n,t)})}),Un.prototype.reject=function(n){return this.filter(au(ye(n)))},Un.prototype.slice=function(n,t){n=Eu(n);var r=this;return r.__filtered__&&(0t)?new Un(r):(0>n?r=r.takeRight(-n):n&&(r=r.drop(n)),t!==T&&(t=Eu(t),r=0>t?r.dropRight(-t):r.take(t-n)),r)},Un.prototype.takeRightWhile=function(n){ -return this.reverse().takeWhile(n).reverse()},Un.prototype.toArray=function(){return this.take(4294967295)},mt(Un.prototype,function(n,t){var r=/^(?:filter|find|map|reject)|While$/.test(t),e=/^(?:head|last)$/.test(t),u=An[e?"take"+("last"==t?"Right":""):t],i=e||/^find/.test(t);u&&(An.prototype[t]=function(){function t(n){return n=u.apply(An,a([n],f)),e&&h?n[0]:n}var o=this.__wrapped__,f=e?[1]:arguments,c=o instanceof Un,l=f[0],s=c||ff(o);s&&r&&typeof l=="function"&&1!=l.length&&(c=s=false);var h=this.__chain__,p=!!this.__actions__.length,l=i&&!h,c=c&&!p; -return!i&&s?(o=c?o:new Un(this),o=n.apply(o,f),o.__actions__.push({func:Qe,args:[t],thisArg:T}),new On(o,h)):l&&c?n.apply(this,f):(o=this.thru(t),l?e?o.value()[0]:o.value():o)})}),r("pop push shift sort splice unshift".split(" "),function(n){var t=ri[n],r=/^(?:push|sort|unshift)$/.test(n)?"tap":"thru",e=/^(?:pop|shift)$/.test(n);An.prototype[n]=function(){var n=arguments;if(e&&!this.__chain__){var u=this.value();return t.apply(ff(u)?u:[],n)}return this[r](function(r){return t.apply(ff(r)?r:[],n)}); -}}),mt(Un.prototype,function(n,t){var r=An[t];if(r){var e=r.name+"";oi.call(Gi,e)||(Gi[e]=[]),Gi[e].push({name:t,func:r})}}),Gi[Jr(T,2).name]=[{name:"wrapper",func:T}],Un.prototype.clone=function(){var n=new Un(this.__wrapped__);return n.__actions__=Ur(this.__actions__),n.__dir__=this.__dir__,n.__filtered__=this.__filtered__,n.__iteratees__=Ur(this.__iteratees__),n.__takeCount__=this.__takeCount__,n.__views__=Ur(this.__views__),n},Un.prototype.reverse=function(){if(this.__filtered__){var n=new Un(this); -n.__dir__=-1,n.__filtered__=true}else n=this.clone(),n.__dir__*=-1;return n},Un.prototype.value=function(){var n,t=this.__wrapped__.value(),r=this.__dir__,e=ff(t),u=0>r,i=e?t.length:0;n=i;for(var o=this.__views__,f=0,c=-1,a=o.length;++c=this.__values__.length;return{done:n,value:n?T:this.__values__[this.__index__++]}},An.prototype.plant=function(n){ -for(var t,r=this;r instanceof En;){var e=Fe(r);e.__index__=0,e.__values__=T,t?u.__wrapped__=e:t=e;var u=e,r=r.__wrapped__}return u.__wrapped__=n,t},An.prototype.reverse=function(){var n=this.__wrapped__;return n instanceof Un?(this.__actions__.length&&(n=new Un(this)),n=n.reverse(),n.__actions__.push({func:Qe,args:[Ge],thisArg:T}),new On(n,this.__chain__)):this.thru(Ge)},An.prototype.toJSON=An.prototype.valueOf=An.prototype.value=function(){return wr(this.__wrapped__,this.__actions__)},An.prototype.first=An.prototype.head, -wi&&(An.prototype[wi]=Xe),An}();typeof define=="function"&&typeof define.amd=="object"&&define.amd?($n._=rt, define(function(){return rt})):Nn?((Nn.exports=rt)._=rt,Fn._=rt):$n._=rt}).call(this); diff --git a/net/ftp-proxy/Makefile b/net/ftp-proxy/Makefile index 20403a1da3..4f655e4bac 100644 --- a/net/ftp-proxy/Makefile +++ b/net/ftp-proxy/Makefile @@ -1,6 +1,6 @@ PLUGIN_NAME= ftp-proxy PLUGIN_VERSION= 1.0 -PLUGIN_REVISION= 2 +PLUGIN_REVISION= 4 PLUGIN_COMMENT= Control ftp-proxy processes PLUGIN_MAINTAINER= frank.brendel@eurolog.com diff --git a/net/ftp-proxy/src/etc/rc.d/os-ftp-proxy b/net/ftp-proxy/src/etc/rc.d/os-ftp-proxy index 76c45ddc8b..b462b6cade 100755 --- a/net/ftp-proxy/src/etc/rc.d/os-ftp-proxy +++ b/net/ftp-proxy/src/etc/rc.d/os-ftp-proxy @@ -1,8 +1,5 @@ #!/bin/sh # -# $FreeBSD$ -# - # PROVIDE: os-ftp-proxy # REQUIRE: DAEMON pf # KEYWORD: shutdown diff --git a/net/ftp-proxy/src/opnsense/mvc/app/controllers/OPNsense/FtpProxy/Api/ServiceController.php b/net/ftp-proxy/src/opnsense/mvc/app/controllers/OPNsense/FtpProxy/Api/ServiceController.php index 6bc2f58537..dec43eeac0 100644 --- a/net/ftp-proxy/src/opnsense/mvc/app/controllers/OPNsense/FtpProxy/Api/ServiceController.php +++ b/net/ftp-proxy/src/opnsense/mvc/app/controllers/OPNsense/FtpProxy/Api/ServiceController.php @@ -41,9 +41,6 @@ class ServiceController extends ApiControllerBase public function statusAction($uuid) { $result = array("result" => "failed", "function" => "status"); - if ($this->request->isPost()) { - $this->sessionClose(); - } if ($uuid != null) { $mdlFtpProxy = new FtpProxy(); $node = $mdlFtpProxy->getNodeByReference('ftpproxy.' . $uuid); @@ -62,9 +59,6 @@ public function statusAction($uuid) public function startAction($uuid) { $result = array("result" => "failed", "function" => "start"); - if ($this->request->isPost()) { - $this->sessionClose(); - } if ($uuid != null) { $mdlFtpProxy = new FtpProxy(); $node = $mdlFtpProxy->getNodeByReference('ftpproxy.' . $uuid); @@ -83,9 +77,6 @@ public function startAction($uuid) public function stopAction($uuid) { $result = array("result" => "failed", "function" => "stop"); - if ($this->request->isPost()) { - $this->sessionClose(); - } if ($uuid != null) { $mdlFtpProxy = new FtpProxy(); $node = $mdlFtpProxy->getNodeByReference('ftpproxy.' . $uuid); @@ -103,9 +94,6 @@ public function stopAction($uuid) */ public function restartAction($uuid) { - if ($this->request->isPost()) { - $this->sessionClose(); - } if ($uuid != null) { $mdlFtpProxy = new FtpProxy(); $node = $mdlFtpProxy->getNodeByReference('ftpproxy.' . $uuid); @@ -123,9 +111,6 @@ public function restartAction($uuid) public function configAction() { $result = array("result" => "failed", "function" => "config"); - if ($this->request->isPost()) { - $this->sessionClose(); - } $result['result'] = $this->callBackend('template'); return $result; } @@ -136,9 +121,6 @@ public function configAction() */ public function reloadAction() { - if ($this->request->isPost()) { - $this->sessionClose(); - } $result = $this->configAction(); if ($result['result'] == 'OK') { $result['function'] = "reload"; diff --git a/net/ftp-proxy/src/opnsense/mvc/app/controllers/OPNsense/FtpProxy/Api/SettingsController.php b/net/ftp-proxy/src/opnsense/mvc/app/controllers/OPNsense/FtpProxy/Api/SettingsController.php index 4e570b30d2..1b25ff5e04 100644 --- a/net/ftp-proxy/src/opnsense/mvc/app/controllers/OPNsense/FtpProxy/Api/SettingsController.php +++ b/net/ftp-proxy/src/opnsense/mvc/app/controllers/OPNsense/FtpProxy/Api/SettingsController.php @@ -1,31 +1,29 @@ sessionClose(); $fields = array( "enabled", "listenaddress", diff --git a/net/ftp-proxy/src/opnsense/mvc/app/controllers/OPNsense/FtpProxy/ItemController.php b/net/ftp-proxy/src/opnsense/mvc/app/controllers/OPNsense/FtpProxy/ItemController.php index 679fc52da1..a9f30d596b 100644 --- a/net/ftp-proxy/src/opnsense/mvc/app/controllers/OPNsense/FtpProxy/ItemController.php +++ b/net/ftp-proxy/src/opnsense/mvc/app/controllers/OPNsense/FtpProxy/ItemController.php @@ -36,5 +36,4 @@ */ class ItemController extends \OPNsense\Base\IndexController { - } diff --git a/net/ftp-proxy/src/opnsense/mvc/app/models/OPNsense/FtpProxy/FtpProxy.xml b/net/ftp-proxy/src/opnsense/mvc/app/models/OPNsense/FtpProxy/FtpProxy.xml index 34d241a977..f105108f07 100644 --- a/net/ftp-proxy/src/opnsense/mvc/app/models/OPNsense/FtpProxy/FtpProxy.xml +++ b/net/ftp-proxy/src/opnsense/mvc/app/models/OPNsense/FtpProxy/FtpProxy.xml @@ -1,77 +1,77 @@ - //OPNsense/ftpproxies - 1.0.0 - Ftp Proxy settings - - - - 1 - Y - - - Y - 127.0.0.1 - /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-4]|2[0-5][0-9]|[01]?[0-9][0-9]?)$/ - Listen address must be a valid IPv4 address - - - 8021 - Y - 1 - 65535 - Listen port needs to be an integer value between 1 and 65535 - - - N - /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-4]|2[0-5][0-9]|[01]?[0-9][0-9]?)$/ - Source address must be a valid IPv4 address - - - 0 - N - - - 86400 - N - 1 - 86400 - Idle timeout needs to be an integer value between 1 and 86400 - - - 100 - N - 1 - 500 - Maximum number of concurrent FTP sessions needs to be an integer value between 1 and 500 - - - N - /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-4]|2[0-5][0-9]|[01]?[0-9][0-9]?)$/ - Reverse address must be a valid IPv4 address - - - 21 - N - 1 - 65535 - Reverse port needs to be an integer value between 1 and 65535 - - - 0 - N - - - 5 - N - 0 - 7 - Debug level needs to be an integer value between 0 and 7 - - - N - /^([\t\n\v\f\r 0-9a-zA-Z.,_\x{00A0}-\x{FFFF}]){1,255}$/u - Enter a description. - - - + //OPNsense/ftpproxies + 1.0.0 + Ftp Proxy settings + + + + 1 + Y + + + Y + 127.0.0.1 + /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-4]|2[0-5][0-9]|[01]?[0-9][0-9]?)$/ + Listen address must be a valid IPv4 address + + + 8021 + Y + 1 + 65535 + Listen port needs to be an integer value between 1 and 65535 + + + N + /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-4]|2[0-5][0-9]|[01]?[0-9][0-9]?)$/ + Source address must be a valid IPv4 address + + + 0 + N + + + 86400 + N + 1 + 86400 + Idle timeout needs to be an integer value between 1 and 86400 + + + 100 + N + 1 + 500 + Maximum number of concurrent FTP sessions needs to be an integer value between 1 and 500 + + + N + /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-4]|2[0-5][0-9]|[01]?[0-9][0-9]?)$/ + Reverse address must be a valid IPv4 address + + + 21 + N + 1 + 65535 + Reverse port needs to be an integer value between 1 and 65535 + + + 0 + N + + + 5 + N + 0 + 7 + Debug level needs to be an integer value between 0 and 7 + + + N + /^([\t\n\v\f\r 0-9a-zA-Z.,_\x{00A0}-\x{FFFF}]){1,255}$/u + Enter a description. + + + diff --git a/net/ftp-proxy/src/opnsense/mvc/app/views/OPNsense/FtpProxy/index.volt b/net/ftp-proxy/src/opnsense/mvc/app/views/OPNsense/FtpProxy/index.volt index 58f543270e..7a51ca9e53 100644 --- a/net/ftp-proxy/src/opnsense/mvc/app/views/OPNsense/FtpProxy/index.volt +++ b/net/ftp-proxy/src/opnsense/mvc/app/views/OPNsense/FtpProxy/index.volt @@ -34,8 +34,8 @@ POSSIBILITY OF SUCH DAMAGE. */ function openDialog(uuid) { var editDlg = "DialogEdit"; - var setUrl = "/api/ftpproxy/settings/setProxy/"; - var getUrl = "/api/ftpproxy/settings/getProxy/"; + var setUrl = "/api/ftpproxy/settings/set_proxy/"; + var getUrl = "/api/ftpproxy/settings/get_proxy/"; var urlMap = {}; urlMap['frm_' + editDlg] = getUrl + uuid; mapDataToFormUI(urlMap).done(function () { @@ -56,12 +56,12 @@ POSSIBILITY OF SUCH DAMAGE. *************************************************************************************************************/ $("#grid-proxies").UIBootgrid( - { 'search':'/api/ftpproxy/settings/searchProxy', - 'get':'/api/ftpproxy/settings/getProxy/', - 'set':'/api/ftpproxy/settings/setProxy/', - 'add':'/api/ftpproxy/settings/addProxy/', - 'del':'/api/ftpproxy/settings/delProxy/', - 'toggle':'/api/ftpproxy/settings/toggleProxy/', + { 'search':'/api/ftpproxy/settings/search_proxy', + 'get':'/api/ftpproxy/settings/get_proxy/', + 'set':'/api/ftpproxy/settings/set_proxy/', + 'add':'/api/ftpproxy/settings/add_proxy/', + 'del':'/api/ftpproxy/settings/del_proxy/', + 'toggle':'/api/ftpproxy/settings/toggle_proxy/', 'options':{selection:false, multiSelect:false} } ); diff --git a/net/google-cloud-sdk/Makefile b/net/google-cloud-sdk/Makefile index 6e832d0562..c836863108 100644 --- a/net/google-cloud-sdk/Makefile +++ b/net/google-cloud-sdk/Makefile @@ -1,5 +1,6 @@ PLUGIN_NAME= google-cloud-sdk PLUGIN_VERSION= 1.0 +PLUGIN_REVISION= 1 PLUGIN_COMMENT= Google Cloud SDK PLUGIN_DEPENDS= google-cloud-sdk PLUGIN_MAINTAINER= opnsense@moov.de diff --git a/net/haproxy/Makefile b/net/haproxy/Makefile index 2ed4ea9f88..fc7cf35177 100644 --- a/net/haproxy/Makefile +++ b/net/haproxy/Makefile @@ -1,7 +1,7 @@ PLUGIN_NAME= haproxy -PLUGIN_VERSION= 2.20 +PLUGIN_VERSION= 5.1 PLUGIN_COMMENT= Reliable, high performance TCP/HTTP load balancer -PLUGIN_DEPENDS= haproxy +PLUGIN_DEPENDS= haproxy py${PLUGIN_PYTHON}-haproxy-cli PLUGIN_MAINTAINER= opnsense@moov.de .include "../../Mk/plugins.mk" diff --git a/net/haproxy/pkg-descr b/net/haproxy/pkg-descr index 5a41f6ad02..fbeff94fe2 100644 --- a/net/haproxy/pkg-descr +++ b/net/haproxy/pkg-descr @@ -3,4 +3,544 @@ availability, load balancing, and proxying for TCP and HTTP-based applications. It is particularly suited for web sites crawling under very high loads while needing persistence or Layer7 processing. -WWW: https://haproxy.org/ +Plugin Changelog +================ + +5.1 + +Added: +* more conditions have support for converters +* add support for mapfile URLs (#4825) + +Fixed: +* migration fails if a http-request/-response "lua" rule is configured + +Changed: +* modernize UI templates + +5.0 + +WARNING: This is a new major release, which may result in +incompatible changes for some users. + +Added: +* add support for HTTP/3 over QUIC to frontends (#4341) +* add new rule: http-request silent-drop +* add new rule: http-after-response +* add new condition: HTTP method +* support custom HTTP status code in "http-request deny" rules +* add new backend option to control PROXY protocol for health checks (#2909) +* add support for new map file types: beg,end,int,ip,reg,str,sub (#3641) +* add support for more sample fetches: quic_enabled, stopping, wait_end (#3702) +* add support for HTTP compression (#4867) +* add all action keywords for http-request/-response and tcp-request/-response rules +* add "enabled" field to rules +* add support for all stick-table data types +* add support for GPC/GPT/SC to conditions and rules (#1123, #5109) +* add support for SSL SNI expression to servers (#3756) +* add column "mode" to servers overview (#4632) +* add support for loading mapfiles in conditions and rules +* add support for sample fetches in rules + +Fixed: +* Maintenance tab "SSL Certificates" not working with only one cert + +Changed: +* upgrade to HAProxy 3.2 release series (#5147) +* refactor http/tcp rules to make extensions easier +* rename some labels in rules +* change LUA boolean conversion (see tune.lua.bool-sample-conversion) +* stick-table "size" and "expiration time" are no longer advanced options (now always visible) +* replace stick-table type "ip" with "ipv4" (#5147) +* show the actual HAProxy option name in conditions for clarity +* allow stick-table types "binary", "integer" and "string" in backends +* make mapfiles more useful in rules + +4.6 + +Changed: +* improve help text for "http-request redirect" rules (#4650) +* rename "http-request redirect" input field (#4650) + +4.5 + +Changed: +* upgrade to HAProxy 3.0 release series (#4411) +* migrate cert export to Trust MVC + +4.4 + +Fixed: +* Cron job "Sync SSL certificate changes" not working (#4035) +* Template error with empty user group (#3364) + +4.3 + +Added: +* Add new global parameter: DNS prefer IP family (#3779) + +Fixed: +* SNI not working when automatic OCSP updates are enabled (#3779) +* HAProxy error: has an OCSP URI but an error occurred (#3779) + +Changed: +* prefer IPv4 results when resolving DNS names (#3779) +* disable OCSP updates if cert contains no OCSP data (#3779) + +4.2 + +Added: +* add support for built-in OCSP update feature +* add support for forwarded header (RFC7239) +* add option "X-Forwarded-For Header" to backend settings +* add options for HTTP/2 performance tuning + +Fixed: +* fix SSL sync cron job (bulk sync was never working properly) + +Changed: +* upgrade to HAProxy 2.8 release series (#3459) +* change default for HTTP/2 to enabled (only new frontends/backends) +* add "no-alpn" option if HTTP/2 is not enabled (only TLS-enabled frontends) +* move OCSP settings from "Service" to "Global" section +* replace bundled haproxyctl library with haproxy-cli + +Deprecated: +* frontend option "X-Forwarded-For Header" (the backend option should be used) + +Removed: +* remove OSCP update cron job + +4.1 + +Fixed: +* fix SSL preferences in health checks (#3221) + +4.0 + +Added: +* add new service option "Gradual connection close time" (close-spread-time) (#3026) +* add new frontend option "shards" (#3026) + +Changed: +* upgrade to HAProxy 2.6 release series (#3026) +* rename frontend option "Type" to "Connection Mode" (#3026) +* migrate options "http-tunnel" and "forceclose" to "http-keep-alive" (#3026) +* replace "process" with "threads" bind keyword for CPU Affinity (#3026) +* no longer duplicate global defaults in backends/frontends (#2642) + +Removed: +* remove Processes/nbproc option (use Threads/nbthread instead) (#3026) +* remove "Process ID" from CPU Affinity settings (now always 1) (#3026) +* remove "bind-process" option (replaced by the "threads" bind keyword) (#3026) +* remove options "http-tunnel" and "forceclose" from "Connection Mode" (#3026) + +3.12 + +Added: +* add support for req.ssl_hello_type (#2311) +* add support for Prometheus exporter (#2764) +* add support for FastCGI applications (#2769) +* add server option to override the multiplexer protocol + +Fixed: +* fix unix sockets in chrooted environment (#3093) +* fix peers by automatically configuring the local peer (#3114) + +Changed: +* update HAProxy documentation URLs + +3.11 + +Added: +* add support for cache parameter (#2908) + +3.10 + +WARNING: This release switches to the HAProxy 2.4 release series, +which may result in incompatible changes for some users. + +Added: +* add support for DNS resolution over TCP (#2644) + +Changed: +* upgrade to HAProxy 2.4 release series (#2644) +* disable strict-limits for safekeeping (#2644) + +Removed: +* remove deprecated option tune.chksize (#2644) + +3.9 + +Added: +* add SSL SNI setting to servers and health checks (#2388) + +Fixed: +* fix custom TCP health checks (#2653) + +Changed: +* replace "force SSL" setting with "SSL preferences" in health checks (#2388) +* health check port is no longer an advanced option + +3.8 + +Added: +* add support for unix sockets (#2040) +* add "max connections" option to servers (#2641) + +Changed: +* allow setting "max connections" to "0" (unlimited) +* raise maximum value for "max connections" to 10000000 + +3.7 + +Added: +* add options "preload" and "filename scheme" to Lua scripts (#2265) +* add syslog-ng socket for logging (#2620) +* show hint to apply changes after every config change (#2590) +* show warning for pending configuration changes (#2590) + +Fixed: +* unable to use the "require" function in Lua scripts (#2265) +* request logging not working (#2587) +* fix syntax error in template (#2619) + +Changed: +* set "lua-prepend-path" so that Lua scripts can be found (#2265) +* show "apply" and "test syntax" buttons on introduction pages + +3.6 + +Added: +* add support for advanced resolver properties (#2330) +* add graceful stop timeout to service settings +* support "monitor-uri" and "monitor fail" in rules (#2387) +* add new option "case-sensitive" to conditions (#2576) + +Fixed: +* no haproxy.conf after restoring a config backup (#2474) + +Changed: +* deploy haproxy.conf if it does not exist (#2474) +* add new timeout (60s) which will terminate open connections when using graceful stop +* allow retries to be set to "0" (#2585) + +3.5 + +Fixed: +* fix maintenance page not loading (#2485) + +3.4 + +Fixed: +* fix empty resolve-prefer option (#2340) + +3.3 + +Changed: +* use HAProxy socket to apply updated OCSP stapling data (in cron job) (#2351) + +3.2 + +Fixed: +* fix config test when HAProxy service is not enabled + +Changed: +* ignore incompatible ciphersuites options when LibreSSL is used (#2013) + +3.1 + +Fixed: +* fix items that cannot be deleted (#2266) + +Changed: +* rules: only accept a single value for backend/server fields (#2266) + +3.0 + +Added: +* add new maintenance page to change server state and weight on-the-fly (#2213) +* add new commands to update SSL certificates in runtime (#2244, #1882) +* add new SSL bind option: prefer-client-ciphers +* add global option to enable old buggy behaviour for PROXY v2 connections +* add support for HTTP/2 in health checks +* add config export (#2035) +* add config diff +* guard against broken config by using a staging config file +* add basic OCSP stapling support (#1430) +* add support for e-mail alerts and mailers (#1669) +* add support for custom header checks (#1907) +* add support for server templates (#1975) +* add support for additional resolver options (#1975) +* add support for resolve-prefer option (#1975) +* add pre-defined cron jobs to maintenance page + +Fixed: +* prevent service outage by aborting "Apply" when configtest fails +* fix direct links to individual statistics tabs +* prevent the deletion of items that are still referenced elsewhere (core/#1897) + +Changed: +* upgrade to HAProxy 2.2 release series (#2092) +* change default SSL version to TLSv1.2 (ssl-min-ver) +* remove weak ciphers from (default) SSL settings +* remove default SSL bind options that would conflict with ssl-min-ver +* move SSL bind options below other SSL settings, they are rarely used nowadays +* change default for tune.ssl.default-dh-param from 1024 to 2048 +* use new "http-check send" command for HTTP health checks +* change default for spreadChecks from 0 to 2 +* no longer overwrite live config file when running a syntax check +* make restart/reload commands usable in cron jobs +* relax GUI input validation for servers, move validation to jinja template (#1975) + +Deprecated: +* nbproc is deprecated and will be removed in os-haproxy 4.0 + +2.26 + +Fixed: +* preserve sort order of default SSL bind options + +2.25 + +Added: +* add support for TLSv1.3 (#790) + +2.24 + +Added: +* add support for http-request set-var and http-response set-var (#1796) +* add group as userlist to HAProxy config to make it usable in rules/conditions (#1796) +* add support for resolvers to customize how HAProxy handles name resolution (#1787) +* add support for init-addr to allow HAProxy to start when DNS does not resolve (#1787) + +Fixed: +* honor sort order of all rules, remove special handling of "use_[backend|server]" options (#1925) + +Changed: +* add "Save & Test syntax" button to all "Settings" pages +* add "introduction" page for Settings tab +* streamline "Settings" subtabs + +2.23 + +Fixed: +* add missing acl SNI regex text field (#1883) + +2.22 + +Added: +* enable SSL verification for a server when "Force SSL" is enabled in the associated health check (#1761) +* use the systems local Root CA Certificates for SSL verification when no CA was selected (#1761) + +Fixed: +* fix label of src_sess_cnt (#1780) +* fix invalid use of option httplog (resolves a warning in config test) +* fix invalid use of option forwardfor (resolves a warning in config test) + +2.21 + +Fixed: +* override "graceful" restart if required (#1745) + +2.20 + +Changed: +* update stats socket permission for easier (non-root) monitoring (#1232) + +2.19 + +Added: +* switch to HAProxy 2.0 release series (#1089) +* add support for the "max-object-size" cache configuration option (#1458) +* add end-to-end HTTP/2 support (details) +* add support for the random balancing algorithm (details) + +Fixed: +* fix IPv6 validation in frontends (#540) + +Changed: +* add IPv6 example to listen address help text +* update URLs to HAProxy 2.0 documentation +* frontends: move HTTP/2 option to HTTP settings +* change order of frontend options + +2.18 + +Added: +* add support for HAProxy cache (#1442) + +Changed: +* change http-reuse default (align with HAProxy's default value, #1439) + +2.17 + +Added: +* allow backends without servers (#1304) +* add support for deciphered SNI check in ACLs (#1365) +* allow to force SSL for health checks (#1282) + +Changed: +* improve wording for SNI conditions to differentiate between deciphered vs. not deciphered + +2.16 + +Fixed: +* allow hyphens in server, frontend and backend names (#1346) + +2.15 + +Added: +* rules can finally be sorted by using drag'n'drop (#582) +* added "enabled" field to servers (#1208) +* TCP inspection delays are supported in rules (#1188) + +Changed: +* server option "mode" is always visible, no longer requires "advanced mode" (#1208) +* most dropdown fields finally have alphanumeric sorting (#687, opnsense/core#3251) +* rules: align indentation of comments in haproxy.conf + +2.14 + +Fixed: +* bulk deleting does not work (#1164) + +Changed: +* migrate to mutable controller (required to fix #1164) + +2.13 + +Added: +* support multiple CAs for SSL verification for servers + +Fixed: +* fix export of CAs (#1074) + +Changed: +* export a frontend's default SSL certificate (#1088) +* it is no longer required to add a default SSL certificate to a frontend's "certificates" list (#1088) +* avoid duplicate entry in certlist file if a default SSL certificate is specified +* always show "Default certificate" option in frontends, it's no longer an "advanced" option + +2.12 + +Added: +* add support for HTTP/2 (#1047) + +2.11 + +Fixed: +* fix warning: a 'http-request' rule placed after a 'use_backend' rule will still be processed before (#999) +* fix wrong parameter name when using tcp-request content lua (#999) + +Changed: +* internal: trim whitespace, remove empty lines in haproxy.conf (#999) + +2.10 + +Added: +* add support for multithreading (available as new option in Settings -> Global Parameters) (#1003) +* add support for client certificate authentication (#426) +* add support for HTTP Basic Auth to frontends/backends/ACLs (#300) +* add basic user/group management functionality (supports Basic Auth as well as stats users) +* add new CPU Affinity Rules feature (which is a combination of HAProxy's cpu-map, bind-process and process options) (see #1003 for a short explanation) + +Fixed: +* function "http-request header-delete" generated a corrupted haproxy.conf (#882) + +Changed: +* migrate all stats users from old (and cumbersome) username:password format to new user management feature +* internal: use /tmp for autogenerated files (now they are automatically cleaned up on boot) +* internal: change filename of cert lists from id.crtlist to id.certlist + +2.9 + +Added: +* add "http-reuse" option (#836) + +2.8 + +Added: +* support truly seamless reloads (#224) +* add support for the "map" feature (#180) + +Fixed: +* fix reload of service template in "reconfigure" action (#690; introduced in 7381101) +* enabling "hard stop" mode resulted in an invalid "hardrestart" RC command + +Changed: +* use "reload" instead of "restart" RC action +* if "reload" fails, also issue a "restart" command (required when enabling seamless reloads) +* start progress animation (spinner) earlier when applying settings + +2.7 + +Added: +* support rise/fall parameters in backends and health checks +* support set-path in ACLs +* support for cookie-based persistence (#680) + +Fixed: +* fix X-Forwarded-For option disappeared (#647) +* fix validation for source address fields (#695) + +2.6 + +Added: +* add support for http-response set-status in ACLs to manipulate HTTP status codes + +Fixed: +* fix invalid backend name when using nbsrv in ACLs + +2.5 + +Added: +* add support for the PROXY protocol (i.e. in combination with postfix or dovecot) +* switch to HAProxy 1.8.4 + +2.4 + +Added: +* add support for "preload" and "includeSubDomains" HSTS options (#447) +* support session sync / HAProxy peers (#165) +* add new HTTP timeout options (to mitigate slowloris attacks) (#202) +* allow tracking additional values in stick-tables (#202) +* add stick-table config for frontends (optional, disabled by default) (#202) +* add support for many new conditions (#202) +* enable sticky counters for frontend stick-tables (required for new conditions) (#202) + +Changed: +* relax validation masks for several "name" fields (to allow more "special" characters) +* switch to new mutable service controller + +2.3 + +Added: +* new option to hide introduction pages (#340) + +Fixed: +* fix wrong introduction for "Advanced" tab (regression introduced in 8cdcbda) + +2.2 + +Fixed: +* fix for rules parameters (values could not be saved, leading to invalid rules) + +2.1 + +Fixed: +* do not enable HSTS unconditionally (now works as described in #380) +* enable HSTS only for HTTP frontends + +2.0 + +Added: +* new GUI to guide new users and improve general usability (#208) +* make server port optional (#341) +* new SSL settings for frontends (#380) +* new global SSL default values (#380) +* new option for HTTP Strict Transport Security (#380) + +Fixed: +* rephrase text to make it clear that aliases cannot be used (#360) +* rephrase text to make it clear that "use_server" will only work for backends (#361) diff --git a/net/haproxy/src/etc/inc/plugins.inc.d/haproxy.inc b/net/haproxy/src/etc/inc/plugins.inc.d/haproxy.inc index 320127ded3..f0a402deec 100644 --- a/net/haproxy/src/etc/inc/plugins.inc.d/haproxy.inc +++ b/net/haproxy/src/etc/inc/plugins.inc.d/haproxy.inc @@ -37,9 +37,7 @@ function haproxy_syslog() $syslogconf = array(); $syslogconf['haproxy'] = array( - 'local' => '/var/haproxy/var/run/log', 'facility' => array('haproxy'), - 'remote' => 'relayd', ); return $syslogconf; @@ -79,5 +77,6 @@ function haproxy_xmlrpc_sync() $result['id'] = 'haproxy'; $result['section'] = 'OPNsense.HAProxy'; $result['description'] = gettext('HAProxy Load Balancer'); + $result['services'] = ['haproxy']; return array($result); } diff --git a/net/haproxy/src/etc/rc.syshook.d/start/50-haproxy b/net/haproxy/src/etc/rc.syshook.d/start/50-haproxy new file mode 100755 index 0000000000..09c0e0ef1f --- /dev/null +++ b/net/haproxy/src/etc/rc.syshook.d/start/50-haproxy @@ -0,0 +1,7 @@ +#!/bin/sh + +# fallback if no config file exists (i.e. after config backup restore) +if [ ! -e /usr/local/etc/haproxy.conf -a -e /usr/local/etc/haproxy.conf.staging ]; then + cp /usr/local/etc/haproxy.conf.staging /usr/local/etc/haproxy.conf + /usr/local/etc/rc.d/haproxy start +fi diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/Api/ExportController.php b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/Api/ExportController.php new file mode 100644 index 0000000000..71e1d39477 --- /dev/null +++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/Api/ExportController.php @@ -0,0 +1,94 @@ +configdRun("haproxy showconf"); + return array("response" => $response); + } + + /** + * get config diff + * @return string + */ + public function diffAction() + { + $backend = new Backend(); + $response = $backend->configdRun("haproxy configdiff"); + return array("response" => $response); + } + + /** + * download config file or config archive + * @return array|mixed + */ + public function downloadAction($type) + { + $backend = new Backend(); + + if ($type == 'config') { + $result = $backend->configdRun("haproxy showconf"); + $filename = 'haproxy.conf'; + $filetype = 'text/plain'; + $content = $result; + } else { + $result = $backend->configdRun("haproxy exportall"); + $filename = 'haproxy_config_export.zip'; + $filetype = 'application/zip'; + $content = file_get_contents('/tmp/haproxy_config_export.zip'); + } + + $response = array( + 'result' => $result, + 'filename' => $filename, + 'filetype' => $filetype, + 'content' => base64_encode($content), + ); + return $response; + } +} diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/Api/MaintenanceController.php b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/Api/MaintenanceController.php new file mode 100644 index 0000000000..e62d995273 --- /dev/null +++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/Api/MaintenanceController.php @@ -0,0 +1,368 @@ +configdRun('template reload OPNsense/HAProxy'); + + return $this->getData( + ["cert_diff_list"], + ["rowCount", "current", "searchPhrase", "sort"] + ); + } + + /** + * jQuery bootstrap server list + * @return array|mixed + */ + public function searchServerAction() + { + $backend = new Backend(); + $backend->configdRun('template reload OPNsense/HAProxy'); + + return $this->getData( + ["server_status_list"], + ["rowCount", "current", "searchPhrase", "sort"] + ); + } + + /** + * sync certificate for frontends + * @return array|mixed + */ + public function certSyncAction() + { + $backend = new Backend(); + $backend->configdRun('template reload OPNsense/HAProxy'); + + return $this->syncCerts( + ["cert_sync"], + ["frontend_ids"] + ); + } + + /** + * sync certificate for frontends + * @return array|mixed + */ + public function certSyncBulkAction() + { + $backend = new Backend(); + $backend->configdRun('template reload OPNsense/HAProxy'); + + return $this->syncCerts( + ["cert_sync_bulk"] + ); + } + + /** + * show certificate diff for frontends + * @return array|mixed + */ + public function certDiffAction() + { + $backend = new Backend(); + $backend->configdRun('template reload OPNsense/HAProxy'); + + return $this->getData( + ["cert_diff"], + ["frontend_ids"] + ); + } + + /** + * show certificate actions for frontends + * @return array|mixed + */ + public function certActionsAction() + { + $backend = new Backend(); + $backend->configdRun('template reload OPNsense/HAProxy'); + + return $this->getData( + ["cert_actions"], + ["frontend_ids"] + ); + } + + /** + * set server weight + * @return array|mixed + */ + public function serverWeightAction() + { + return $this->saveData( + ["server_weight"], + ["backend", "server", "weight"] + ); + } + + /** + * set server administrative state + * @return array|mixed + */ + public function serverStateAction() + { + return $this->saveData( + ["server_state"], + ["backend", "server", "state"] + ); + } + + /** + * set server administrative state for multiple servers + * @return array|mixed + */ + public function serverStateBulkAction() + { + return $this->saveData( + ["server_state_bulk"], + ["server_ids", "state"] + ); + } + + /** + * set server weight for multiple servers + * @return array|mixed + */ + public function serverWeightBulkAction() + { + return $this->saveData( + ["server_weight_bulk"], + ["server_ids", "weight"] + ); + } + + /** + * Execute a backend command securely + * @param array $command + * @param array $arguments + * @return string + */ + protected function safeBackendCmd(array $command, array $arguments = []) + { + $backend = new Backend(); + + foreach ($arguments as $name) { + $val = $this->request->getPost($name); + if (is_array($val) and $name == 'sort') { + $sort = key(array_slice($val, 0, 1)); + $sort_dir = $val[$sort]; + $command[] = $sort; + $command[] = $sort_dir; + continue; + } + $command[] = $val; + } + + $command = array_map(function ($value) { + return escapeshellarg(empty($value = trim($value)) ? null : $value); + }, $command); + + return trim($backend->configdRun("haproxy " . join(" ", $command))); + } + + /** + * Executes a backend command to get data + * @param array $command + * @param array $arguments + * @return string|string[] + */ + protected function getData(array $command, array $arguments = []) + { + if ($this->request->isPost()) { + return $this->safeBackendCmd($command, $arguments); + } + return ["status" => "unavailable"]; + } + + /** + * Executes a backend command which returns output on error + * @param array $command + * @param array $arguments + * @return array|string[] + */ + protected function saveData(array $command, array $arguments = []) + { + if ($this->request->isPost()) { + if ($error = $this->safeBackendCmd($command, $arguments)) { + return [ + "status" => "error", + "message" => $error + ]; + } else { + return ["status" => "ok"]; + } + } + return [ + "status" => 'unavailable', + "message" => 'only accept POST Requests.' + ]; + } + + /** + * Executes a ssl certificate sync + * @param array $command + * @param array $arguments + * @return array|string[] + */ + protected function syncCerts(array $command, array $arguments = []) + { + if ($this->request->isPost()) { + $output = $this->safeBackendCmd($command, $arguments); + $result = json_decode($output, true); + + return [ + "status" => "ok", + "result" => $result, + ]; + } + return [ + "status" => 'unavailable', + "message" => 'only accept POST Requests.' + ]; + } + + /** + * create new cron job or return already available one + * @return array status action + */ + public function fetchCronIntegrationAction() + { + $result = array("result" => "no change"); + + if ($this->request->isPost()) { + $mdlHaproxy = $this->getModel(); + $backend = new Backend(); + + // Define possible cron jobs with their configd actions + $cronjobs = array( + 'syncCerts' => 'cert_sync_bulk', + 'updateOcsp' => 'update_ocsp', + 'reloadService' => 'reload', + 'restartService' => 'restart', + ); + + // Iterate over all possible cron jobs + foreach ($cronjobs as $cron => $cron_action) { + // Name of the item that holds the cron UUID + $cron_ref = "${cron}Cron"; + + // Check if the cron job is enabled or disabled + if ((string)$mdlHaproxy->maintenance->cronjobs->$cron == "1") { + // Check if a cron job already exists + if ((string)$mdlHaproxy->maintenance->cronjobs->$cron_ref == "") { + // Create new cron job + $mdlCron = new Cron(); + // NOTE: Only configd actions are valid commands for cronjobs + // and they *must* provide a description that is not empty. + $cron_uuid = $mdlCron->newDailyJob( + "HAProxy", + "haproxy ${cron_action}", + "Added by HAProxy plugin", + "*", + "1" + ); + $mdlHaproxy->maintenance->cronjobs->$cron_ref = $cron_uuid; + + // Save updated configuration. + if ($mdlCron->performValidation()->count() == 0) { + $mdlCron->serializeToConfig(); + // save data to config, do not validate because the current in memory model doesn't know about the + // cron item just created. + $mdlHaproxy->serializeToConfig($validateFullModel = false, $disable_validation = true); + Config::getInstance()->save(); + // Refresh the crontab + $backend->configdRun('template reload OPNsense/Cron'); + // (res)start daemon + $backend->configdRun("cron restart"); + $this->getLogger()->error("HAProxy: successfully created cron job $cron ($cron_uuid)"); + $result['result'] = "new"; + $result['uuid'] = $cron_uuid; + } else { + $this->getLogger()->error("HAProxy: unable to create cron job $cron"); + $result['result'] = "unable to add cron"; + } + } + } else { + // Check if a cron job exists + if ((string)$mdlHaproxy->maintenance->cronjobs->$cron_ref != "") { + // Clean existin entry + $cron_uuid = (string)$mdlHaproxy->maintenance->cronjobs->$cron_ref; + $mdlHaproxy->maintenance->cronjobs->$cron_ref = ""; + + // Delete the cronjob item + $mdlCron = new Cron(); + if ($mdlCron->jobs->job->del($cron_uuid)) { + // If item is removed, serialize to config and save + $mdlCron->serializeToConfig(); + $mdlHaproxy->serializeToConfig($validateFullModel = false, $disable_validation = true); + Config::getInstance()->save(); + // Regenerate the crontab + $backend->configdRun('template reload OPNsense/Cron'); + // (res)start daemon + $backend->configdRun("cron restart"); + $this->getLogger()->error("HAProxy: successfully deleted cron job $cron ($cron_uuid)"); + $result['result'] = "deleted"; + } else { + $this->getLogger()->error("HAProxy: unable to delete cron job $cron ($cron_uuid)"); + $result['result'] = "unable to delete cron"; + } + } + } + } + } + + return $result; + } +} diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/Api/ServiceController.php b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/Api/ServiceController.php index 524597aef0..0632cef861 100644 --- a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/Api/ServiceController.php +++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/Api/ServiceController.php @@ -56,8 +56,6 @@ public function configtestAction() $backend = new Backend(); // first generate template based on current configuration $backend->configdRun('template reload OPNsense/HAProxy'); - // now export all the required files (or syntax check will fail) - $backend->configdRun("haproxy setup"); // finally run the syntax check $response = $backend->configdRun("haproxy configtest"); return array("result" => $response); diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/Api/SettingsController.php b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/Api/SettingsController.php index 7779d14d56..b19a13df72 100644 --- a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/Api/SettingsController.php +++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/Api/SettingsController.php @@ -1,7 +1,7 @@ searchBase('servers.server', array('enabled', 'name', 'address', 'port', 'description'), 'name'); + return $this->searchBase('servers.server', array('enabled', 'name', 'type', 'mode', 'address', 'port', 'description'), 'name'); } public function getHealthcheckAction($uuid = null) @@ -205,9 +206,14 @@ public function delActionAction($uuid) return $this->delBase('actions.action', $uuid); } + public function toggleActionAction($uuid, $enabled = null) + { + return $this->toggleBase('actions.action', $uuid); + } + public function searchActionsAction() { - return $this->searchBase('actions.action', array('name', 'description'), 'name'); + return $this->searchBase('actions.action', array('enabled', 'name', 'description'), 'name'); } public function getLuaAction($uuid = null) @@ -240,6 +246,31 @@ public function searchLuasAction() return $this->searchBase('luas.lua', array('enabled', 'name', 'description'), 'name'); } + public function getFcgiAction($uuid = null) + { + return $this->getBase('fcgi', 'fcgis.fcgi', $uuid); + } + + public function setFcgiAction($uuid) + { + return $this->setBase('fcgi', 'fcgis.fcgi', $uuid); + } + + public function addFcgiAction() + { + return $this->addBase('fcgi', 'fcgis.fcgi'); + } + + public function delFcgiAction($uuid) + { + return $this->delBase('fcgis.fcgi', $uuid); + } + + public function searchFcgisAction() + { + return $this->searchBase('fcgis.fcgi', array('name', 'description'), 'name'); + } + public function getErrorfileAction($uuid = null) { return $this->getBase('errorfile', 'errorfiles.errorfile', $uuid); @@ -317,7 +348,7 @@ public function toggleCpuAction($uuid, $enabled = null) public function searchCpusAction() { - return $this->searchBase('cpus.cpu', array('enabled', 'name', 'process_id', 'thread_id', 'cpu_id'), 'name'); + return $this->searchBase('cpus.cpu', array('enabled', 'name', 'thread_id', 'cpu_id'), 'name'); } public function getGroupAction($uuid = null) @@ -379,4 +410,64 @@ public function searchUsersAction() { return $this->searchBase('users.user', array('enabled', 'name', 'description'), 'name'); } + + public function getresolverAction($uuid = null) + { + return $this->getBase('resolver', 'resolvers.resolver', $uuid); + } + + public function setresolverAction($uuid) + { + return $this->setBase('resolver', 'resolvers.resolver', $uuid); + } + + public function addresolverAction() + { + return $this->addBase('resolver', 'resolvers.resolver'); + } + + public function delresolverAction($uuid) + { + return $this->delBase('resolvers.resolver', $uuid); + } + + public function toggleresolverAction($uuid, $enabled = null) + { + return $this->toggleBase('resolvers.resolver', $uuid); + } + + public function searchresolversAction() + { + return $this->searchBase('resolvers.resolver', array('enabled', 'name', 'nameservers'), 'name'); + } + + public function getmailerAction($uuid = null) + { + return $this->getBase('mailer', 'mailers.mailer', $uuid); + } + + public function setmailerAction($uuid) + { + return $this->setBase('mailer', 'mailers.mailer', $uuid); + } + + public function addmailerAction() + { + return $this->addBase('mailer', 'mailers.mailer'); + } + + public function delmailerAction($uuid) + { + return $this->delBase('mailers.mailer', $uuid); + } + + public function togglemailerAction($uuid, $enabled = null) + { + return $this->toggleBase('mailers.mailer', $uuid); + } + + public function searchmailersAction() + { + return $this->searchBase('mailers.mailer', array('enabled', 'name', 'mailservers', 'sender', 'recipient'), 'name'); + } } diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/Api/StatisticsController.php b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/Api/StatisticsController.php index 5864728ff0..d90f272fc4 100644 --- a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/Api/StatisticsController.php +++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/Api/StatisticsController.php @@ -45,7 +45,7 @@ class StatisticsController extends ApiControllerBase * get info * @return array|mixed */ - public function infoAction($zoneid = 0) + public function infoAction() { $backend = new Backend(); $responseRaw = $backend->configdRun("haproxy statistics info"); @@ -57,7 +57,7 @@ public function infoAction($zoneid = 0) * get counters * @return array|mixed */ - public function countersAction($zoneid = 0) + public function countersAction() { $backend = new Backend(); $responseRaw = $backend->configdRun("haproxy statistics stat"); @@ -69,7 +69,7 @@ public function countersAction($zoneid = 0) * get tables * @return array|mixed */ - public function tablesAction($zoneid = 0) + public function tablesAction() { $backend = new Backend(); $responseRaw = $backend->configdRun("haproxy statistics table"); diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/ExportController.php b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/ExportController.php new file mode 100644 index 0000000000..390adadede --- /dev/null +++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/ExportController.php @@ -0,0 +1,45 @@ +view->pick('OPNsense/HAProxy/export'); + } +} diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/IndexController.php b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/IndexController.php index 0203015213..791f6bfe67 100644 --- a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/IndexController.php +++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/IndexController.php @@ -1,7 +1,7 @@ view->mainForm = $this->getForm("main"); - $this->view->formDialogFrontend = $this->getForm("dialogFrontend"); - $this->view->formDialogBackend = $this->getForm("dialogBackend"); - $this->view->formDialogServer = $this->getForm("dialogServer"); - $this->view->formDialogHealthcheck = $this->getForm("dialogHealthcheck"); - $this->view->formDialogAction = $this->getForm("dialogAction"); $this->view->formDialogAcl = $this->getForm("dialogAcl"); - $this->view->formDialogUser = $this->getForm("dialogUser"); + $this->view->formDialogAction = $this->getForm("dialogAction"); + $this->view->formDialogBackend = $this->getForm("dialogBackend"); + $this->view->formDialogCpu = $this->getForm("dialogCpu"); + $this->view->formDialogErrorfile = $this->getForm("dialogErrorfile"); + $this->view->formDialogFcgi = $this->getForm("dialogFcgi"); + $this->view->formDialogFrontend = $this->getForm("dialogFrontend"); $this->view->formDialogGroup = $this->getForm("dialogGroup"); + $this->view->formDialogHealthcheck = $this->getForm("dialogHealthcheck"); $this->view->formDialogLua = $this->getForm("dialogLua"); - $this->view->formDialogErrorfile = $this->getForm("dialogErrorfile"); + $this->view->formDialogMailer = $this->getForm("dialogMailer"); $this->view->formDialogMapfile = $this->getForm("dialogMapfile"); - $this->view->formDialogCpu = $this->getForm("dialogCpu"); + $this->view->formDialogResolver = $this->getForm("dialogResolver"); + $this->view->formDialogServer = $this->getForm("dialogServer"); + $this->view->formDialogUser = $this->getForm("dialogUser"); + $this->view->generalCacheForm = $this->getForm("generalCache"); + $this->view->generalDefaultsForm = $this->getForm("generalDefaults"); + $this->view->generalLoggingForm = $this->getForm("generalLogging"); + $this->view->generalPeersForm = $this->getForm("generalPeers"); + $this->view->generalSettingsForm = $this->getForm("generalSettings"); + $this->view->generalStatsForm = $this->getForm("generalStats"); + $this->view->generalTuningForm = $this->getForm("generalTuning"); // set additional view parameters $mdlHAProxy = new \OPNsense\HAProxy\HAProxy(); $this->view->showIntro = (string)$mdlHAProxy->general->showIntro; diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/MaintenanceController.php b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/MaintenanceController.php new file mode 100644 index 0000000000..92ef3a0511 --- /dev/null +++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/MaintenanceController.php @@ -0,0 +1,46 @@ +view->pick('OPNsense/HAProxy/maintenance'); + $this->view->maintenanceCronjobsForm = $this->getForm("maintenanceCronjobs"); + } +} diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogAcl.xml b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogAcl.xml index ee8bb02837..1929789378 100644 --- a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogAcl.xml +++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogAcl.xml @@ -19,7 +19,7 @@ acl.expression dropdown - Select condition type. + layer 4 or layer 7. The syntax check will show errors when using an incompatible sample fetch method.]]> acl.negate @@ -27,6 +27,12 @@ checkbox + + acl.caseSensitive + + checkbox + + header @@ -82,6 +88,16 @@ text + + + header + + + + acl.http_method + + select_multiple + header @@ -148,6 +164,91 @@ text + + + header + + + + acl.cust_hdr_beg_name + + text + + + + acl.cust_hdr_beg + + text + + + + + header + + + + acl.cust_hdr_end_name + + text + + + + acl.cust_hdr_end + + text + + + + + header + + + + acl.cust_hdr_name + + text + + + + acl.cust_hdr + + text + + + + + header + + + + acl.cust_hdr_reg_name + + text + + + + acl.cust_hdr_reg + + text + + + + + header + + + + acl.cust_hdr_sub_name + + text + + + + acl.cust_hdr_sub + + text + + header @@ -165,6 +266,27 @@ text + + + header + + + + acl.var_comparison + + dropdown + + + acl.var + + text + + + + acl.var_value + + text + header @@ -187,6 +309,17 @@ text + + + header + + + + acl.ssl_hello_type + + dropdown + + header @@ -478,6 +611,17 @@ text + + + header + + + + acl.ssl_sni_reg + + text + + header @@ -506,4 +650,1169 @@ textbox + + + header + + + + acl.sc_bytes_in_rate_comparison + + dropdown + + + acl.sc_bytes_in_rate + + text + + + + header + + + + acl.sc_bytes_out_rate_comparison + + dropdown + + + acl.sc_bytes_out_rate + + text + + + + header + + + + acl.sc_clr_gpc_comparison + + dropdown + + + acl.sc_clr_gpc + + text + + + + header + + + + acl.sc_conn_cnt_comparison + + dropdown + + + acl.sc_conn_cnt + + text + + + + header + + + + acl.sc_conn_cur_comparison + + dropdown + + + acl.sc_conn_cur + + text + + + + header + + + + acl.sc_conn_rate_comparison + + dropdown + + + acl.sc_conn_rate + + text + + + + header + + + + acl.sc_get_gpc_comparison + + dropdown + + + acl.sc_get_gpc + + text + + + + header + + + + acl.sc_get_gpt_comparison + + dropdown + + + acl.sc_get_gpt + + text + + + + header + + + + acl.sc_glitch_cnt_comparison + + dropdown + + + acl.sc_glitch_cnt + + text + + + + header + + + + acl.sc_glitch_rate_comparison + + dropdown + + + acl.sc_glitch_rate + + text + + + + header + + + + acl.sc_gpc_rate_comparison + + dropdown + + + acl.sc_gpc_rate + + text + + + + header + + + + acl.sc_http_err_cnt_comparison + + dropdown + + + acl.sc_http_err_cnt + + text + + + + header + + + + acl.sc_http_err_rate_comparison + + dropdown + + + acl.sc_http_err_rate + + text + + + + header + + + + acl.sc_http_fail_cnt_comparison + + dropdown + + + acl.sc_http_fail_cnt + + text + + + + header + + + + acl.sc_http_fail_rate_comparison + + dropdown + + + acl.sc_http_fail_rate + + text + + + + header + + + + acl.sc_http_req_cnt_comparison + + dropdown + + + acl.sc_http_req_cnt + + text + + + + header + + + + acl.sc_http_req_rate_comparison + + dropdown + + + acl.sc_http_req_rate + + text + + + + header + + + + acl.sc_inc_gpc_comparison + + dropdown + + + acl.sc_inc_gpc + + text + + + + header + + + + acl.sc_sess_cnt_comparison + + dropdown + + + acl.sc_sess_cnt + + text + + + + header + + + + acl.sc_sess_rate_comparison + + dropdown + + + acl.sc_sess_rate + + text + + + + header + + + + acl.src_get_gpc_comparison + + dropdown + + + acl.src_get_gpc + + text + + + + header + + + + acl.src_get_gpt_comparison + + dropdown + + + acl.src_get_gpt + + text + + + + header + + + + acl.src_glitch_cnt_comparison + + dropdown + + + acl.src_glitch_cnt + + text + + + + header + + + + acl.src_glitch_rate_comparison + + dropdown + + + acl.src_glitch_rate + + text + + + + header + + + + acl.src_gpc_rate_comparison + + dropdown + + + acl.src_gpc_rate + + text + + + + header + + + + acl.src_http_fail_cnt_comparison + + dropdown + + + acl.src_http_fail_cnt + + text + + + + header + + + + acl.src_http_fail_rate_comparison + + dropdown + + + acl.src_http_fail_rate + + text + + + + header + + + + acl.src_inc_gpc_comparison + + dropdown + + + acl.src_inc_gpc + + text + + + + header + + + + acl.sc_clr_gpc0_comparison + + dropdown + + + acl.sc_clr_gpc0 + + text + + + + header + + + + acl.sc_clr_gpc1_comparison + + dropdown + + + acl.sc_clr_gpc1 + + text + + + + header + + + + acl.sc0_clr_gpc0_comparison + + dropdown + + + acl.sc0_clr_gpc0 + + text + + + + header + + + + acl.sc0_clr_gpc1_comparison + + dropdown + + + acl.sc0_clr_gpc1 + + text + + + + header + + + + acl.sc1_clr_gpc_comparison + + dropdown + + + acl.sc1_clr_gpc + + text + + + + header + + + + acl.sc1_clr_gpc0_comparison + + dropdown + + + acl.sc1_clr_gpc0 + + text + + + + header + + + + acl.sc1_clr_gpc1_comparison + + dropdown + + + acl.sc1_clr_gpc1 + + text + + + + header + + + + acl.sc2_clr_gpc_comparison + + dropdown + + + acl.sc2_clr_gpc + + text + + + + header + + + + acl.sc2_clr_gpc0_comparison + + dropdown + + + acl.sc2_clr_gpc0 + + text + + + + header + + + + acl.sc2_clr_gpc1_comparison + + dropdown + + + acl.sc2_clr_gpc1 + + text + + + + header + + + + acl.sc_get_gpc0_comparison + + dropdown + + + acl.sc_get_gpc0 + + text + + + + header + + + + acl.sc_get_gpc1_comparison + + dropdown + + + acl.sc_get_gpc1 + + text + + + + header + + + + acl.sc0_get_gpc0_comparison + + dropdown + + + acl.sc0_get_gpc0 + + text + + + + header + + + + acl.sc0_get_gpc1_comparison + + dropdown + + + acl.sc0_get_gpc1 + + text + + + + header + + + + acl.sc1_get_gpc0_comparison + + dropdown + + + acl.sc1_get_gpc0 + + text + + + + header + + + + acl.sc1_get_gpc1_comparison + + dropdown + + + acl.sc1_get_gpc1 + + text + + + + header + + + + acl.sc2_get_gpc0_comparison + + dropdown + + + acl.sc2_get_gpc0 + + text + + + + header + + + + acl.sc2_get_gpc1_comparison + + dropdown + + + acl.sc2_get_gpc1 + + text + + + + header + + + + acl.sc_get_gpt_comparison + + dropdown + + + acl.sc_get_gpt + + text + + + + header + + + + acl.sc_get_gpt0_comparison + + dropdown + + + acl.sc_get_gpt0 + + text + + + + header + + + + acl.sc0_get_gpt0_comparison + + dropdown + + + acl.sc0_get_gpt0 + + text + + + + header + + + + acl.sc1_get_gpt0_comparison + + dropdown + + + acl.sc1_get_gpt0 + + text + + + + header + + + + acl.sc2_get_gpt0_comparison + + dropdown + + + acl.sc2_get_gpt0 + + text + + + + header + + + + acl.sc_gpc0_rate_comparison + + dropdown + + + acl.sc_gpc0_rate + + text + + + + header + + + + acl.sc_gpc1_rate_comparison + + dropdown + + + acl.sc_gpc1_rate + + text + + + + header + + + + acl.sc0_gpc0_rate_comparison + + dropdown + + + acl.sc0_gpc0_rate + + text + + + + header + + + + acl.sc0_gpc1_rate_comparison + + dropdown + + + acl.sc0_gpc1_rate + + text + + + + header + + + + acl.sc1_gpc0_rate_comparison + + dropdown + + + acl.sc1_gpc0_rate + + text + + + + header + + + + acl.sc1_gpc1_rate_comparison + + dropdown + + + acl.sc1_gpc1_rate + + text + + + + header + + + + acl.sc2_gpc0_rate_comparison + + dropdown + + + acl.sc2_gpc0_rate + + text + + + + header + + + + acl.sc2_gpc1_rate_comparison + + dropdown + + + acl.sc2_gpc1_rate + + text + + + + header + + + + acl.sc_inc_gpc0_comparison + + dropdown + + + acl.sc_inc_gpc0 + + text + + + + header + + + + acl.sc_inc_gpc1_comparison + + dropdown + + + acl.sc_inc_gpc1 + + text + + + + header + + + + acl.sc0_inc_gpc0_comparison + + dropdown + + + acl.sc0_inc_gpc0 + + text + + + + header + + + + acl.sc0_inc_gpc1_comparison + + dropdown + + + acl.sc0_inc_gpc1 + + text + + + + header + + + + acl.sc1_inc_gpc0_comparison + + dropdown + + + acl.sc1_inc_gpc0 + + text + + + + header + + + + acl.sc1_inc_gpc1_comparison + + dropdown + + + acl.sc1_inc_gpc1 + + text + + + + header + + + + acl.sc2_inc_gpc0_comparison + + dropdown + + + acl.sc2_inc_gpc0 + + text + + + + header + + + + acl.sc2_inc_gpc1_comparison + + dropdown + + + acl.sc2_inc_gpc1 + + text + + + + header + + + + acl.src_clr_gpc0_comparison + + dropdown + + + acl.src_clr_gpc0 + + text + + + + header + + + + acl.src_clr_gpc1_comparison + + dropdown + + + acl.src_clr_gpc1 + + text + + + + header + + + + acl.src_get_gpc0_comparison + + dropdown + + + acl.src_get_gpc0 + + text + + + + header + + + + acl.src_get_gpc1_comparison + + dropdown + + + acl.src_get_gpc1 + + text + + + + header + + + + acl.src_gpc0_rate_comparison + + dropdown + + + acl.src_gpc0_rate + + text + + + + header + + + + acl.src_gpc1_rate_comparison + + dropdown + + + acl.src_gpc1_rate + + text + + + + header + + + + acl.src_inc_gpc0_comparison + + dropdown + + + acl.src_inc_gpc0 + + text + + + + header + + + + acl.src_inc_gpc1_comparison + + dropdown + + + acl.src_inc_gpc1 + + text + + + + header + + + acl.gpc_number + + text + + + + acl.gpt_number + + text + + + + acl.sc_number + + text + + + + acl.table_name + + text + + + + acl.mapfile + + dropdown + + + + acl.converter + + text + + diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogAction.xml b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogAction.xml index 5a7881def1..64c4230b32 100644 --- a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogAction.xml +++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogAction.xml @@ -1,4 +1,10 @@
    + + action.enabled + + checkbox + Enable this rule. + action.name @@ -15,33 +21,33 @@ header - - action.testType - - dropdown - - action.linkedAcls select_multiple + + action.testType + + dropdown + + action.operator - + dropdown - + - + header action.type - + dropdown - + @@ -72,325 +78,269 @@ header - + - action.http_request_auth - + action.monitor_fail_uri + text - + header - + - action.http_request_redirect - - text - HAProxy's documentation for further details and examples.]]> + action.custom + + textbox + header - - - - action.http_request_lua - - text - + - - header - + action.map_use_backend_file + + dropdown + - action.http_request_use_service - - text - + action.map_use_backend_default + + dropdown + header - + - action.http_request_add_header_name - - text - - - - action.http_request_add_header_content - - text - HAProxy's documentation for further details and examples.]]> - - - - header - + action.map_data_use_backend_file + + dropdown + - action.http_request_set_header_name - - text - + action.map_data_use_backend_input + + textbox + - action.http_request_set_header_content - - text - HAProxy's documentation for further details and examples.]]> + action.map_data_use_backend_default + + dropdown + header - + - action.http_request_del_header_name - + action.fcgi_pass_header + text - + header - - - - action.http_request_replace_header_name - - text - + - action.http_request_replace_header_regex - + action.fcgi_set_param + text - + Custom Log format rules. With this directive, it is possible to overwrite the value of default FastCGI parameters.]]> header - + - action.http_request_replace_value_name - - text - + action.compression_direction + + dropdown + - action.http_request_replace_value_regex - - text - + action.compression_algo_res + + dropdown + - - header - + action.compression_algo_req + + dropdown + - action.http_request_set_path - - text - + action.compression_mime_res + + select_multiple + + true + + Enter MIME types here. Finish with TAB. - - header - + action.compression_mime_req + + select_multiple + + true + + Enter MIME types here. Finish with TAB. - action.http_response_lua - + action.compression_minsize_res + text - - - - - header - + - action.http_response_add_header_name - + action.compression_minsize_req + text - + - action.http_response_add_header_content - - text - HAProxy's documentation for further details and examples.]]> + action.compression_offloading + + checkbox + header - - - - action.http_response_set_header_name - - text - - - - action.http_response_set_header_content - - text - HAProxy's documentation for further details and examples.]]> + - - header - + action.http_after_response_action + + dropdown + HAProxy's documentation.]]> - action.http_response_del_header_name - + action.http_after_response_option + text - + HAProxy's documentation.]]> header - + - action.http_response_replace_header_name - - text - + action.http_request_action + + dropdown + HAProxy's documentation.]]> - action.http_response_replace_header_regex - + action.http_request_option + text - + HAProxy's documentation.]]> header - + - action.http_response_replace_value_name - - text - + action.http_response_action + + dropdown + HAProxy's documentation.]]> - action.http_response_replace_value_regex - + action.http_response_option + text - + HAProxy's documentation.]]> header - + - action.http_response_set_status_code - - text - + action.tcp_request_action + + dropdown + HAProxy's documentation.]]> - action.http_response_set_status_reason - + action.tcp_request_option + text - + HAProxy's documentation.]]> header - - - - action.tcp_request_content_lua - - text - + - - header - + action.tcp_response_action + + dropdown + HAProxy's documentation.]]> - action.tcp_request_content_use_service - + action.tcp_response_option + text - + HAProxy's documentation.]]> - + header - - action.tcp_request_inspect_delay - + action.gpc_number + text - + - - header - - - - action.tcp_response_content_lua - + action.gpt_number + text - - - - - header - + - action.tcp_response_inspect_delay - + action.sc_number + text - - - - - header - - - - action.custom - - textbox - + - - header - + action.mapfile + + dropdown + - action.map_use_backend_file - - dropdown - + action.map_default + + text + - action.map_use_backend_default - - dropdown - + action.sample_fetch + + text + http-request set-var(req.rate_limit) path,map_beg(/path/to/mapfile,20)
    http-request set-var(req.request_rate) base32+src,table_http_req_rate()]]>
    diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogBackend.xml b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogBackend.xml index 380bcd3654..2f39423dff 100644 --- a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogBackend.xml +++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogBackend.xml @@ -28,7 +28,7 @@ backend.algorithm dropdown - HAProxy documentation for a full description.]]> + HAProxy documentation for a full description.]]> Choose a load balancing algorithm.
    @@ -42,7 +42,7 @@ backend.proxyProtocol dropdown - HAProxy documentation for a full description.]]> + HAProxy documentation for a full description.]]> true @@ -54,6 +54,35 @@ Type server name or choose from list. + + backend.linkedFcgi + + dropdown + + + + backend.linkedResolver + + dropdown + + true + + + backend.resolverOpts + + select_multiple + + true + + Type option name or choose from list. + + + backend.resolvePrefer + + dropdown + + true + backend.source @@ -112,6 +141,19 @@ true + + backend.linkedMailer + + dropdown + + + + backend.healthCheckProxyProto + + dropdown + + true + header @@ -138,6 +180,27 @@ true + + backend.forwardedHeader + + checkbox + + + + backend.forwardedHeaderParameters + + select_multiple + + true + true + HAProxy documentation for a full description.]]> + + + backend.forwardFor + + checkbox + + header @@ -157,7 +220,7 @@ backend.persistence_cookiemode dropdown - HAProxy documentation for a full description.]]> + HAProxy documentation for a full description.]]> backend.persistence_cookiename @@ -172,34 +235,52 @@ - + header backend.stickiness_pattern dropdown - HAProxy documentation for a full description.
    NOTE: Consider not using this feature in multi-process mode, it can result in random behaviours.
    ]]>
    - Choose a persistence type. + HAProxy documentation for further information.]]>
    backend.stickiness_dataTypes select_multiple - HAProxy documentation for a full description.]]> + HAProxy documentation for a full description.]]> backend.stickiness_expire text - true backend.stickiness_size - + text + + + backend.stickiness_length + + text + + true + + + backend.stickiness_bytesInRatePeriod + + text + + true + + + backend.stickiness_bytesOutRatePeriod + + text + true @@ -222,10 +303,31 @@ true - backend.stickiness_sessRatePeriod - + backend.stickiness_glitchRatePeriod + text - + + true + + + backend.stickiness_gpcElements + + text + + true + + + backend.stickiness_gpcRatePeriod + + text + + true + + + backend.stickiness_gptElements + + text + true @@ -243,17 +345,17 @@ true - backend.stickiness_bytesInRatePeriod - + backend.stickiness_httpFailRatePeriod + text - + true - backend.stickiness_bytesOutRatePeriod - + backend.stickiness_sessRatePeriod + text - + true diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogCpu.xml b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogCpu.xml index a1d97f46ee..8e0b705960 100644 --- a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogCpu.xml +++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogCpu.xml @@ -11,12 +11,6 @@ text Choose a name for this CPU affinity rule. - - cpu.process_id - - dropdown - Process ID that should bind to a specific CPU set. Any process IDs above nbproc are ignored. - cpu.thread_id diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogFcgi.xml b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogFcgi.xml new file mode 100644 index 0000000000..d8883e203c --- /dev/null +++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogFcgi.xml @@ -0,0 +1,77 @@ +
    + + fcgi.enabled + + checkbox + Enable this FastCGI application. + + + fcgi.name + + text + Name to identify this FastCGI application. + + + fcgi.description + + text + Description for this FastCGI application. + + + fcgi.docroot + + text + + + + fcgi.index + + text + + + + fcgi.path_info + + text + HAProxy's documentation for further details and examples.]]> + + + fcgi.log_stderr + + checkbox + + + + fcgi.keep_conn + + checkbox + + + + fcgi.get_values + + checkbox + + + + fcgi.mpxs_conns + + checkbox + + + + fcgi.max_reqs + + text + + + + fcgi.linkedActions + + select_multiple + + true + + Choose rules. + +
    diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogFrontend.xml b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogFrontend.xml index 32e200eb92..6698a6464d 100644 --- a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogFrontend.xml +++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogFrontend.xml @@ -23,7 +23,7 @@ select_multiple true - + Enter address:port here. Finish with TAB.
    @@ -90,19 +90,28 @@ - frontend.ssl_bindOptions - - select_multiple - - true - true - + frontend.ssl_minVersion + + dropdown + + + + frontend.ssl_maxVersion + + dropdown + frontend.ssl_cipherList text - + + + + frontend.ssl_cipherSuites + + text + frontend.ssl_hstsEnabled @@ -128,6 +137,15 @@ text + + frontend.ssl_bindOptions + + select_multiple + + true + true + + header @@ -185,13 +203,34 @@ true true - + frontend.forwardFor - + + checkbox + + + + frontend.prometheus_enabled + checkbox - + + true + + + frontend.prometheus_path + + text + + true + + + frontend.connectionBehaviour + + dropdown + keep-alive mode with regards to persistent connections. Option "httpclose" configures HAProxy to close connections with the server and the client as soon as the request and the response are received. It will also check if a "Connection: close" header is already set in each direction, and will add one if missing. Option "http-server-close" enables HTTP connection-close mode on the server side while keeping the ability to support HTTP keep-alive and pipelining on the client side.]]> + true @@ -224,7 +263,7 @@ frontend.tuning_maxConnections - + text @@ -258,6 +297,13 @@ Choose CPU affinity rules. true + + frontend.tuning_shards + + text + + true + header @@ -297,35 +343,33 @@ true - + header frontend.stickiness_pattern dropdown - HAProxy documentation for further information.]]> + HAProxy documentation for further information.]]> Choose a stick-table type. frontend.stickiness_dataTypes select_multiple - HAProxy documentation for a full description.]]> + HAProxy documentation for a full description.]]> frontend.stickiness_expire text - true frontend.stickiness_size - + text - true frontend.stickiness_counter @@ -338,7 +382,7 @@ frontend.stickiness_counter_key text - HAProxy documentation for a full description.]]> + HAProxy documentation for a full description.]]> true @@ -348,6 +392,20 @@ true + + frontend.stickiness_bytesInRatePeriod + + text + + true + + + frontend.stickiness_bytesOutRatePeriod + + text + + true + frontend.stickiness_connRatePeriod @@ -356,10 +414,31 @@ true - frontend.stickiness_sessRatePeriod - + frontend.stickiness_glitchRatePeriod + text - + + true + + + frontend.stickiness_gpcElements + + text + + true + + + frontend.stickiness_gpcRatePeriod + + text + + true + + + frontend.stickiness_gptElements + + text + true @@ -377,30 +456,23 @@ true - frontend.stickiness_bytesInRatePeriod - + frontend.stickiness_httpFailRatePeriod + text - + true - frontend.stickiness_bytesOutRatePeriod - + frontend.stickiness_sessRatePeriod + text - + true header - - frontend.connectionBehaviour - - dropdown - keep-alive mode with regards to persistent connections. Option "http-tunnel" disables any HTTP processing past the first request and the first response. Option "httpclose" configures HAProxy to work in HTTP tunnel mode and check if a "Connection: close" header is already set in each direction, and will add one if missing. Option "http-server-close" enables HTTP connection-close mode on the server side while keeping the ability to support HTTP keep-alive and pipelining on the client side. With Option "forceclose" HAProxy will actively close the outgoing server channel as soon as the server has finished to respond and release some resources earlier.]]> - true - frontend.customOptions diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogGroup.xml b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogGroup.xml index d868a472db..dd8956ffbd 100644 --- a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogGroup.xml +++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogGroup.xml @@ -24,4 +24,10 @@ true Type username or choose from list. + + group.add_userlist + + checkbox + Usually HAproxy userlists are created automatically in a context sensitive way. This option adds this group as userlist, so that it can be referenced in rules/conditions. All special and non-alphanumeric characters will be removed from the userlist name. + diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogHealthcheck.xml b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogHealthcheck.xml index a3d23ffbc6..179ef4b8e0 100644 --- a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogHealthcheck.xml +++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogHealthcheck.xml @@ -17,24 +17,29 @@ dropdown + + healthcheck.ssl + + dropdown + + + + healthcheck.sslSNI + + text + + healthcheck.interval text - - healthcheck.force_ssl - - checkbox - - healthcheck.checkport text - - true + @@ -91,7 +96,7 @@ healthcheck.http_value text -
    NOTE: It is important to note that the responses will be limited to a certain size defined by the global "tune.chksize" option, which defaults to 16384 bytes.
    ]]>
    +
    @@ -107,7 +112,7 @@ healthcheck.tcp_sendValue text -
    NOTE: It is important to note that the responses will be limited to a certain size defined by the global "tune.chksize" option, which defaults to 16384 bytes.
    ]]>
    +
    healthcheck.tcp_matchType diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogLua.xml b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogLua.xml index 72a791e424..23ef5b0307 100644 --- a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogLua.xml +++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogLua.xml @@ -17,6 +17,18 @@ text Description for this Lua script. + + lua.preload + + checkbox + Whether HAProxy should load and execute this Lua script on startup. This is the default behaviour. However, if this Lua script is included by other Lua scripts using the "require" function, then preloading should be disabled to avoid HAProxy errors. + + + lua.filename_scheme + + dropdown + + lua.content diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogMailer.xml b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogMailer.xml new file mode 100644 index 0000000000..cbede5c7cc --- /dev/null +++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogMailer.xml @@ -0,0 +1,61 @@ +
    + + mailer.enabled + + checkbox + Enable this mailer configuration. + + + mailer.name + + text + Choose a name for this mailer configuration. + + + mailer.description + + text + Choose a optional description for this mailer configuration. + + + mailer.mailservers + + select_multiple + + true + true + + Enter ip:port here. Finish with TAB. + + + mailer.sender + + text + + + + mailer.recipient + + text + + + + mailer.loglevel + + dropdown + + + + mailer.timeout + + text + + + + mailer.hostname + + text + + true + +
    diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogMapfile.xml b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogMapfile.xml index 49888d4e91..23a4394747 100644 --- a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogMapfile.xml +++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogMapfile.xml @@ -11,10 +11,22 @@ text Description for this map file.
    + + mapfile.type + + dropdown + The type of the map data. + mapfile.content textbox - HAProxy documentation for a full description.]]> + HAProxy documentation for a full description.]]> + + + mapfile.url + + text + diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogResolver.xml b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogResolver.xml new file mode 100644 index 0000000000..73ca63cdce --- /dev/null +++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogResolver.xml @@ -0,0 +1,103 @@ +
    + + resolver.enabled + + checkbox + Enable this resolver configuration. + + + resolver.name + + text + Choose a name for this resolver configuration. + + + resolver.description + + text + Choose a optional description for this resolver configuration. + + + resolver.nameservers + + select_multiple + + true + true + + Enter ip:port here. Finish with TAB. + + + resolver.parse_resolv_conf + + checkbox + Add all nameservers found in /etc/resolv.conf to this resolver configuration. + + + resolver.resolve_retries + + text + + + + resolver.timeout_resolve + + text + + + + resolver.timeout_retry + + text + + + + resolver.accepted_payload_size + + text + + true + + + resolver.hold_valid + + text + + true + + + resolver.hold_obsolete + + text + + true + + + resolver.hold_refused + + text + + true + + + resolver.hold_nx + + text + + true + + + resolver.hold_timeout + + text + + true + + + resolver.hold_other + + text + + true + +
    diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogServer.xml b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogServer.xml index 0eede8c31d..4dea54e117 100644 --- a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogServer.xml +++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogServer.xml @@ -7,9 +7,9 @@ server.name - + text - Name to identify this server. + Name to identify a static server. When creating a server template, then this prefix is used for the server names to be built. server.description @@ -17,6 +17,17 @@ text Description for this server. + + server.type + + dropdown + Either configure a static server or a template to initialize multiple servers with shared parameters. + + + + header + + server.address @@ -24,6 +35,54 @@ Enter server address. + + + header + + + + server.serviceName + + text + + + + server.number + + text + + + + server.linkedResolver + + dropdown + + + + server.resolverOpts + + select_multiple + + true + + Type option name or choose from list. + + + + header + + + + server.unix_socket + + dropdown + Select the frontend that provides the UNIX socket. This UNIX socket will be used as the server's address, making it possible to send connections to this frontend. Only frontends that provide the unix@ pattern as listen address can be selected. + + + + header + + server.port @@ -36,12 +95,38 @@ dropdown + + server.multiplexer_protocol + + dropdown + + true + + + server.resolvePrefer + + dropdown + + true + server.ssl checkbox + + server.sslSNI + + text + + + + server.sslSNIExpr + + text + SNI expression to specify the data that will be sent in the SNI TLS extension to the server, e.g. req.hdr(host). When a SNI name is present it will be used instead and this option will be ignored.]]> + server.sslVerify @@ -53,7 +138,7 @@ select_multiple true - To import additional CAs, go to Certificate Manager.]]> + To import additional CAs, go to Authority Manager.]]> Type CA name or choose from list. @@ -72,6 +157,13 @@ Type certificate name or choose from list. true + + server.maxConnections + + text + + true + server.weight diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/generalCache.xml b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/generalCache.xml new file mode 100644 index 0000000000..b38486734b --- /dev/null +++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/generalCache.xml @@ -0,0 +1,42 @@ +
    + + + header + + + haproxy.general.cache.enabled + + checkbox + + + + haproxy.general.cache.totalMaxSize + + text + + + + haproxy.general.cache.maxAge + + text + + + + haproxy.general.cache.maxObjectSize + + text + + + + haproxy.general.cache.processVary + + checkbox + + + + haproxy.general.cache.maxSecondaryEntries + + text + + +
    diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/generalDefaults.xml b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/generalDefaults.xml new file mode 100644 index 0000000000..81a9700f7e --- /dev/null +++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/generalDefaults.xml @@ -0,0 +1,69 @@ +
    + + + header + + + haproxy.general.defaults.maxConnections + + text + + + + haproxy.general.defaults.maxConnectionsServers + + text + + + + haproxy.general.defaults.timeoutClient + + text + + + + haproxy.general.defaults.timeoutConnect + + text + + + + haproxy.general.defaults.timeoutCheck + + text + + + + haproxy.general.defaults.timeoutServer + + text + + + + haproxy.general.defaults.retries + + text + + + + haproxy.general.defaults.redispatch + + dropdown + + + + haproxy.general.defaults.init_addr + + select_multiple + + true + + + + haproxy.general.defaults.customOptions + + textbox +
    NOTE: The syntax will not be checked, use at your own risk!
    ]]>
    + true +
    +
    diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/generalLogging.xml b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/generalLogging.xml new file mode 100644 index 0000000000..cdc79ac14b --- /dev/null +++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/generalLogging.xml @@ -0,0 +1,31 @@ +
    + + + header + + + haproxy.general.logging.host + + text + + + + haproxy.general.logging.facility + + dropdown + + + + haproxy.general.logging.level + + dropdown + + + + haproxy.general.logging.length + + text + + true + +
    diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/generalPeers.xml b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/generalPeers.xml new file mode 100644 index 0000000000..7dba7f9f8c --- /dev/null +++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/generalPeers.xml @@ -0,0 +1,56 @@ +
    + + + header + + + haproxy.general.peers.enabled + + checkbox + + + + + header + + + haproxy.general.peers.name1 + + text + system hostname, then this peer is automatically configured as local peer.]]> + + + haproxy.general.peers.listen1 + + text + + + + haproxy.general.peers.port1 + + text + + + + + header + + + haproxy.general.peers.name2 + + text + system hostname, then this peer is automatically configured as local peer.]]> + + + haproxy.general.peers.listen2 + + text + + + + haproxy.general.peers.port2 + + text + + +
    diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/generalSettings.xml b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/generalSettings.xml new file mode 100644 index 0000000000..6c6a1a22e6 --- /dev/null +++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/generalSettings.xml @@ -0,0 +1,42 @@ +
    + + + header + + + haproxy.general.enabled + + checkbox + Enable or disable the HAProxy service. + + + haproxy.general.gracefulStop + + checkbox + + + + haproxy.general.hardStopAfter + + text + + + + haproxy.general.closeSpreadTime + + text + + + + haproxy.general.seamlessReload + + checkbox + + + + haproxy.general.showIntro + + checkbox + + +
    diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/generalStats.xml b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/generalStats.xml new file mode 100644 index 0000000000..ad63e778ef --- /dev/null +++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/generalStats.xml @@ -0,0 +1,85 @@ +
    + + + header + + + haproxy.general.stats.enabled + + checkbox + + + + haproxy.general.stats.port + + text + + true + + + haproxy.general.stats.remoteEnabled + + checkbox + This may be a security risk if you do not enable authentication! Note that you need to add appropiate firewall rules for this to work.]]> + + + haproxy.general.stats.remoteBind + + select_multiple + + true + + Enter address:port here. Finish with TAB. + + + haproxy.general.stats.authEnabled + + checkbox + + + haproxy.general.stats.allowedUsers + + select_multiple + true + Type username or choose from list. + + + haproxy.general.stats.allowedGroups + + select_multiple + true + Type group or choose from list. + + + haproxy.general.stats.customOptions + + textbox +
    NOTE: The syntax will not be checked, use at your own risk!
    ]]>
    + true +
    + + + header + + + haproxy.general.stats.prometheus_enabled + + checkbox + + + + haproxy.general.stats.prometheus_bind + + select_multiple + + true + + Enter address:port here. Finish with TAB. + + + haproxy.general.stats.prometheus_path + + text + + +
    diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/generalTuning.xml b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/generalTuning.xml new file mode 100644 index 0000000000..069dea4ad8 --- /dev/null +++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/generalTuning.xml @@ -0,0 +1,181 @@ +
    + + + header + + + haproxy.general.tuning.root + + checkbox +
    NOTE: Running as user root could be a security issue but it may be required by some features.
    ]]>
    + true +
    + + haproxy.general.tuning.nbthread + + text + + + + haproxy.general.tuning.maxConnections + + text +
    NOTE: Consider raising the settings for kern.maxfiles and kern.maxfilesperproc in System: Settings: Tunables, otherwise HAProxy will fail to open the specified number of connections.
    ]]>
    +
    + + haproxy.general.tuning.resolversPrefer + + dropdown + + + + haproxy.general.tuning.sslServerVerify + + dropdown + + + + haproxy.general.tuning.maxDHSize + + text +
    NOTE: Higher values will increase the CPU load. For more information about the "tune.ssl.default-dh-param" option please see the HAProxy Documentation.
    ]]>
    +
    + + haproxy.general.tuning.bufferSize + + text +
    NOTE: It is strongly recommended not to change this from the default value, as very low values will break some services such as statistics, and values larger than default size will increase memory usage, possibly causing the system to run out of memory.
    ]]>
    + true +
    + + haproxy.general.tuning.luaMaxMem + + text + + true + + + haproxy.general.tuning.spreadChecks + + text + + + + haproxy.general.tuning.bogusProxyEnabled + + checkbox + + + + haproxy.general.tuning.customOptions + + textbox +
    NOTE: The syntax will not be checked, use at your own risk!
    ]]>
    + true +
    + + + header + + + haproxy.general.tuning.ocspUpdateEnabled + + checkbox + + + + haproxy.general.tuning.ocspUpdateMinDelay + + text + + + + haproxy.general.tuning.ocspUpdateMaxDelay + + text + + + + + header + + + haproxy.general.tuning.ssl_defaultsEnabled + + checkbox + + + + haproxy.general.tuning.ssl_minVersion + + dropdown + + + + haproxy.general.tuning.ssl_maxVersion + + dropdown + + + + haproxy.general.tuning.ssl_cipherList + + text + + + + haproxy.general.tuning.ssl_cipherSuites + + text + + + + haproxy.general.tuning.ssl_bindOptions + + select_multiple + + true + true + + + + + header + + + haproxy.general.tuning.h2_initialWindowSize + + text + + + + haproxy.general.tuning.h2_initialWindowSizeOutgoing + + text + + + + haproxy.general.tuning.h2_initialWindowSizeIncoming + + text + + + + haproxy.general.tuning.h2_maxConcurrentStreams + + text + + + + haproxy.general.tuning.h2_maxConcurrentStreamsOutgoing + + text + + + + haproxy.general.tuning.h2_maxConcurrentStreamsIncoming + + text + + +
    diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/main.xml b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/main.xml deleted file mode 100644 index 15e2cbc434..0000000000 --- a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/main.xml +++ /dev/null @@ -1,348 +0,0 @@ -
    - - - - haproxy.general.enabled - - checkbox - Enable or disable the HAProxy service. - - - haproxy.general.gracefulStop - - checkbox - - - - haproxy.general.seamlessReload - - checkbox - - - - haproxy.general.showIntro - - checkbox - - - - - - haproxy.general.peers.enabled - - checkbox - - - - - header - - - haproxy.general.peers.name1 - - text - - - - haproxy.general.peers.listen1 - - text - - - - haproxy.general.peers.port1 - - text - - - - - header - - - haproxy.general.peers.name2 - - text - - - - haproxy.general.peers.listen2 - - text - - - - haproxy.general.peers.port2 - - text - - - - - - - info - - - haproxy.general.tuning.root - - checkbox -
    NOTE: Enabling root could be a security issue but it's required by some feature.
    ]]>
    - true -
    - - haproxy.general.tuning.nbproc - - text -
    NOTE: You may experience random issues in multi-process mode. For more information about the "nbproc" option please see the HAProxy Documentation.
    ]]>
    - true -
    - - haproxy.general.tuning.nbthread - - text - - - - haproxy.general.tuning.maxConnections - - text -
    NOTE: HAProxy will not be able to allocate enough memory if you set this value too high. Consider raising the settings for kern.maxfiles and kern.maxfilesperproc if you need to specify a non-default value.
    ]]>
    -
    - - haproxy.general.tuning.sslServerVerify - - dropdown - - - - haproxy.general.tuning.maxDHSize - - text -
    NOTE: Higher values will increase the CPU load. For more information about the "tune.ssl.default-dh-param" option please see the HAProxy Documentation.
    ]]>
    -
    - - haproxy.general.tuning.bufferSize - - text -
    NOTE: It is strongly recommended not to change this from the default value, as very low values will break some services such as statistics, and values larger than default size will increase memory usage, possibly causing the system to run out of memory.
    ]]>
    - true -
    - - haproxy.general.tuning.checkBufferSize - - text - - true - - - haproxy.general.tuning.luaMaxMem - - text - - true - - - haproxy.general.tuning.spreadChecks - - text - - - - haproxy.general.tuning.customOptions - - textbox -
    NOTE: The syntax will not be checked, use at your own risk!
    ]]>
    - true -
    - - - header - - - haproxy.general.tuning.ssl_defaultsEnabled - - checkbox - - - - haproxy.general.tuning.ssl_bindOptions - - select_multiple - - true - - - - haproxy.general.tuning.ssl_cipherList - - text - - -
    - - - - info - - - haproxy.general.defaults.maxConnections - - text - - - - haproxy.general.defaults.timeoutClient - - text - - - - haproxy.general.defaults.timeoutConnect - - text - - - - haproxy.general.defaults.timeoutCheck - - text - - - - haproxy.general.defaults.timeoutServer - - text - - - - haproxy.general.defaults.retries - - text - - - - haproxy.general.defaults.redispatch - - dropdown - - - - haproxy.general.defaults.customOptions - - textbox -
    NOTE: The syntax will not be checked, use at your own risk!
    ]]>
    - true -
    -
    - - - haproxy.general.logging.host - - text - - - - haproxy.general.logging.facility - - dropdown - - - - haproxy.general.logging.level - - dropdown - - - - haproxy.general.logging.length - - text - - true - - - - - haproxy.general.stats.enabled - - checkbox - - - - haproxy.general.stats.port - - text - - true - - - haproxy.general.stats.remoteEnabled - - checkbox - This may be a security risk if you do not enable authentication! Note that you need to add appropiate firewall rules for this to work.]]> - - - haproxy.general.stats.remoteBind - - select_multiple - - true - - Enter address:port here. Finish with TAB. - - - haproxy.general.stats.authEnabled - - checkbox - - - haproxy.general.stats.allowedUsers - - select_multiple - true - Type username or choose from list. - - - haproxy.general.stats.allowedGroups - - select_multiple - true - Type group or choose from list. - - - haproxy.general.stats.customOptions - - textbox -
    NOTE: The syntax will not be checked, use at your own risk!
    ]]>
    - true -
    -
    - - - haproxy.general.cache.enabled - - checkbox - - - - haproxy.general.cache.totalMaxSize - - text - - - - haproxy.general.cache.maxAge - - text - - - - haproxy.general.cache.maxObjectSize - - text - - - -
    -
    diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/maintenanceCronjobs.xml b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/maintenanceCronjobs.xml new file mode 100644 index 0000000000..1924980d29 --- /dev/null +++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/maintenanceCronjobs.xml @@ -0,0 +1,35 @@ +
    + + + header + + + + haproxy.maintenance.cronjobs.syncCerts + + checkbox + Automation instead of this cron job.]]> + + + + header + + + + haproxy.maintenance.cronjobs.reloadService + + checkbox + + + + + header + + + + haproxy.maintenance.cronjobs.restartService + + checkbox + + +
    diff --git a/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/HAProxy.php b/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/HAProxy.php index 1028b45b36..7dade59ea6 100644 --- a/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/HAProxy.php +++ b/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/HAProxy.php @@ -138,19 +138,19 @@ public function getByAclID($uuid) /** * create a new ACL * @param string $name - * @param string $description * @param string $expression + * @param string $description * @param string $negate * @param hash $parameters * @return string */ - public function newAcl($name, $description = "", $expression, $negate = "0", $parameters = array()) + public function newAcl($name, $expression, $description = "", $negate = "0", $parameters = array()) { $acl = $this->acls->acl->Add(); $uuid = $acl->getAttributes()['uuid']; $acl->name = $name; - $acl->description = $description; $acl->expression = $expression; + $acl->description = $description; $acl->negate = $negate; foreach ($parameters as $key => $value) { $acl->$key = $value; @@ -161,11 +161,11 @@ public function newAcl($name, $description = "", $expression, $negate = "0", $pa /** * create a new action * @param string $name - * @param string $description * @param string $testType + * @param string $type + * @param string $description * @param string $linkedAcls * @param string $operator - * @param string $type * @param string $useBackend * @param string $useServer * @param string $actionName @@ -173,16 +173,16 @@ public function newAcl($name, $description = "", $expression, $negate = "0", $pa * @param string $actionValue * @return string */ - public function newAction($name, $description = "", $testType, $linkedAcls = "", $operator = "and", $type, $parameters = array()) + public function newAction($name, $testType, $type, $description = "", $linkedAcls = "", $operator = "and", $parameters = array()) { $action = $this->actions->action->Add(); $uuid = $action->getAttributes()['uuid']; $action->name = $name; - $action->description = $description; $action->testType = $testType; + $action->type = $type; + $action->description = $description; $action->linkedAcls = $linkedAcls; $action->operator = $operator; - $action->type = $type; foreach ($parameters as $key => $value) { $action->$key = $value; } @@ -192,24 +192,24 @@ public function newAction($name, $description = "", $testType, $linkedAcls = "", /** * create a new server * @param string $name - * @param string $description * @param string $address * @param string $port * @param string $mode + * @param string $description * @param string $ssl * @param string $sslVerify * @param string $weight * @return string */ - public function newServer($name, $description = "", $address, $port, $mode, $ssl = "0", $sslVerify = "1", $weight = "") + public function newServer($name, $address, $port, $mode, $description = "", $ssl = "0", $sslVerify = "1", $weight = "") { $srv = $this->servers->server->Add(); $uuid = $srv->getAttributes()['uuid']; $srv->name = $name; - $srv->description = $description; $srv->address = $address; $srv->port = $port; $srv->mode = $mode; + $srv->description = $description; $srv->ssl = $ssl; $srv->sslVerify = $sslVerify; $srv->weight = $weight; @@ -218,24 +218,24 @@ public function newServer($name, $description = "", $address, $port, $mode, $ssl /** * create a new backend - * @param string $enabled * @param string $name - * @param string $description * @param string $mode * @param string $algorithm + * @param string $enabled + * @param string $description * @param string $linkedServers * @param string $linkedActions * @return string */ - public function newBackend($enabled = "0", $name, $description = "", $mode, $algorithm, $linkedServers = "", $linkedActions = "") + public function newBackend($name, $mode, $algorithm, $enabled = "0", $description = "", $linkedServers = "", $linkedActions = "") { $backend = $this->backends->backend->Add(); $uuid = $backend->getAttributes()['uuid']; - $backend->enabled = $enabled; $backend->name = $name; - $backend->description = $description; $backend->mode = $mode; $backend->algorithm = $algorithm; + $backend->enabled = $enabled; + $backend->description = $description; $backend->linkedServers = $linkedServers; $backend->linkedActions = $linkedActions; return $uuid; @@ -269,7 +269,7 @@ public function linkAclToAction($acl_uuid, $action_uuid, $replace = false) return $acl_uuid; } else { // Extend existing string. - $linkedAcls .= ",${acl_uuid}"; + $linkedAcls .= ",{$acl_uuid}"; } } else { $linkedAcls = $acl_uuid; @@ -309,7 +309,7 @@ public function linkServerToBackend($server_uuid, $backend_uuid, $replace = fals return $server_uuid; } else { // Extend existing string. - $linkedServers .= ",${server_uuid}"; + $linkedServers .= ",{$server_uuid}"; } } else { $linkedServers = $server_uuid; @@ -349,7 +349,7 @@ public function linkActionToFrontend($action_uuid, $frontend_uuid, $replace = fa return $action_uuid; } else { // Extend existing string. - $linkedActions .= ",${action_uuid}"; + $linkedActions .= ",{$action_uuid}"; } } else { $linkedActions = $action_uuid; diff --git a/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/HAProxy.xml b/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/HAProxy.xml index d70ebc8232..4d0ae9b9b4 100644 --- a/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/HAProxy.xml +++ b/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/HAProxy.xml @@ -1,27 +1,43 @@ //OPNsense/HAProxy - 2.8.0 + 5.0.0 the HAProxy load balancer - 0 + 0 Y - 0 + 0 Y + + 60s + /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u + Should be a number between 1 and 8 characters, optionally followed by either "d", "h", "m", "s", "ms" or "us". + N + + + /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u + Should be a number between 1 and 8 characters, optionally followed by either "d", "h", "m", "s", "ms" or "us". + N + - 0 + 0 Y + + + 0 + N + - 1 + 1 - 0 + 0 Y @@ -31,7 +47,7 @@ N - 1024 + 1024 1 65535 Please specify a value between 1 and 65535. @@ -44,7 +60,7 @@ N - 1024 + 1024 1 65535 Please specify a value between 1 and 65535. @@ -53,32 +69,33 @@ - 0 + 0 Y - 1 - 500000 - Please specify a value between 1 and 500000. + 0 + 10000000 + Please specify a value between 0 and 10000000. N - - 1 - 1 - 128 - Please specify a value between 1 and 128. - Y - - 1 + 1 1 1024 Please specify a value between 1 and 1024. N + + N + ipv4 + + IPv4 + IPv6 + + Y - ignore + ignore no preference [default] enforce verify @@ -86,35 +103,32 @@ - 1024 + 2048 1024 16384 Please specify a value between 1024 and 16384. Y - 16384 + 16384 1024 1048576 Please specify a value between 1024 and 1048576. N - - 16384 - 1024 - 1048576 - Please specify a value between 1024 and 1048576. - N - - 0 + 2 0 50 Please specify a value between 0 and 50. Y + + 0 + Y + - 0 + 0 0 1024 Please specify a value between 0 and 1024. @@ -123,72 +137,161 @@ N + + 0 + Y + + + 300 + 1 + 86400 + Please specify a value between 1 and 86400. + N + + + 3600 + 1 + 86400 + Please specify a value between 1 and 86400. + N + - 0 + 0 Y N - no-sslv3,no-tlsv10,no-tls-tickets + prefer-client-ciphers + Y Y no-sslv3 no-tlsv10 no-tlsv11 no-tlsv12 + no-tlsv13 no-tls-tickets force-sslv3 force-tlsv10 force-tlsv11 force-tlsv12 + force-tlsv13 + prefer-client-ciphers strict-sni + + N + TLSv1.2 + + SSLv3 + TLSv1.0 + TLSv1.1 + TLSv1.2 + TLSv1.3 + + + + N + + SSLv3 + TLSv1.0 + TLSv1.1 + TLSv1.2 + TLSv1.3 + + - ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256 + ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256 N + + TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256 + N + + + 0 + 10000000 + Please specify a value between 0 and 10000000. + N + + + 0 + 10000000 + Please specify a value between 0 and 10000000. + N + + + 0 + 10000000 + Please specify a value between 0 and 10000000. + N + + + 0 + 10000000 + Please specify a value between 0 and 10000000. + N + + + 0 + 10000000 + Please specify a value between 0 and 10000000. + N + + + 0 + 10000000 + Please specify a value between 0 and 10000000. + N + - 1 - 500000 - Please specify a value between 1 and 500000. + 0 + 10000000 + Please specify a value between 0 and 10000000. N + + 0 + 10000000 + Please specify a value between 0 and 10000000. + N + - 30s - /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u + 30s + /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u Should be a number between 1 and 8 characters, optionally followed by either "d", "h", "m", "s", "ms" or "us". N - 30s - /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u + 30s + /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u Should be a number between 1 and 8 characters, optionally followed by either "d", "h", "m", "s", "ms" or "us". N - /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u + /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u Should be a number between 1 and 8 characters, optionally followed by either "d", "h", "m", "s", "ms" or "us". N - 30s - /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u + 30s + /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u Should be a number between 1 and 8 characters, optionally followed by either "d", "h", "m", "s", "ms" or "us". N - 1 + 0 100 - 3 - Please specify a value between 1 and 100. + 3 + Please specify a value between 0 and 100. Y N - x-1 + x-1 redispatch on every 3rd retry redispatch on every 2nd retry @@ -199,21 +302,32 @@ redispatch on the 3rd retry prior to the last retry + + N + last,libc + Y + Y + + last + libc + none + + N - 127.0.0.1 - /^([0-9a-zA-Z\.,_\-:]){0,1024}$/u + 127.0.0.1 + /^([0-9a-zA-Z\.,_\-:]){0,1024}$/u lower Please specify a valid servername or IP address. Y Y - local0 + local0 alert audit @@ -243,7 +357,7 @@ N - info + info alert crit @@ -264,36 +378,36 @@ - 0 + 0 N 1024 65535 - 8822 + 8822 Please specify a value between 1024 and 65535. Y - 0 + 0 N N - Y - /^((([0-9a-zA-Z._\-]+:[0-9a-zA-Z._\-]+)([,]){0,1}))*/u + Y + /^((([0-9a-zA-Z._\-]+:[0-9a-zA-Z._\-]+)([,]){0,1}))*/u lower Please provide a valid listen address, i.e. 10.0.0.1:8080 or haproxy.example.com:8999. - 0 + 0 N N - Y - /^((([0-9a-zA-Z._\-]+:[0-9a-zA-Z._\-]+)([,]){0,1}))*/u + Y + /^((([0-9a-zA-Z._\-]+:[0-9a-zA-Z._\-]+)([,]){0,1}))*/u Please provide a valid user and password, i.e. user:secret123. @@ -305,7 +419,7 @@ Related user not found - Y + Y N @@ -317,29 +431,47 @@ Related group not found - Y + Y N N + + 0 + N + + + *:8404 + N + Y + /^((([0-9a-zA-Z._\-\*:\[\]]+:+[0-9]+(-[0-9]+)?|unix@[0-9a-z_\-]+)([,]){0,1}))*/u + lower + Please provide a valid listen address, i.e. 10.0.0.1:8404 or haproxy.example.com:8404. + + + /metrics + N + /^.{1,2048}$/u + Should be a string between 1 and 2048 characters. + - 0 + 0 N 1 4095 - 4 + 4 Y Please specify a value between 1 and 4095. 1 3600 - 60 + 60 N Please specify a value between 1 and 3600. @@ -349,6 +481,16 @@ N Please specify a value between 1 and 2146435072. + + 0 + N + + + 10 + 1 + N + Please specify a positive integer value. + @@ -357,32 +499,32 @@ N - 1 + 1 Y Y - /^([0-9a-zA-Z._\-]){1,255}$/u + /^([0-9a-zA-Z._\-]){1,255}$/u Should be a string between 1 and 255 characters. N - /^.{1,255}$/u + /^.{1,255}$/u Should be a string between 1 and 255 characters. Y - Y - /^((([0-9a-zA-Z._\-\*:\[\]]+:[0-9]+(-[0-9]+)?)([,]){0,1}))*/u + Y + /^((([quic4@|quic6@]*[0-9a-zA-Z._\-\*:\[\]]+:+[0-9]+(-[0-9]+)?|unix@[0-9a-z_\-]+)([,]){0,1}))*/u lower - Please provide a valid listen address, i.e. 127.0.0.1:8080, [::1]:8080 or www.example.com:443. Port range as start-end, i.e. 127.0.0.1:1220-1240. + Please provide a valid listen address, i.e. 127.0.0.1:8080, [::1]:8080, www.example.com:443, quic4@www.example.com or unix@socket-name. Port range as start-end, i.e. 127.0.0.1:1220-1240. N Y - http + http HTTP / HTTPS (SSL offloading) [default] SSL / HTTPS (TCP mode) @@ -401,7 +543,7 @@ N - 0 + 0 Y @@ -418,12 +560,12 @@ N - 0 + 0 Y N - no-sslv3,no-tlsv10,no-tls-tickets + prefer-client-ciphers Y Y @@ -431,44 +573,72 @@ no-tlsv10 no-tlsv11 no-tlsv12 + no-tlsv13 no-tls-tickets force-sslv3 force-tlsv10 force-tlsv11 force-tlsv12 + force-tlsv13 + prefer-client-ciphers strict-sni + + N + TLSv1.2 + + SSLv3 + TLSv1.0 + TLSv1.1 + TLSv1.2 + TLSv1.3 + + + + N + + SSLv3 + TLSv1.0 + TLSv1.1 + TLSv1.2 + TLSv1.3 + + - ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256 + ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256 N + + TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256 + N + - 1 + 1 Y - 0 + 0 N - 0 + 0 N - 15768000 + 15768000 1 1000000000 Please specify a value between 1 and 1000000000. Y - 0 + 0 N N - required + required none optional @@ -488,7 +658,7 @@ Please select a valid CA from the list. - 0 + 0 N @@ -500,7 +670,7 @@ Related user not found - Y + Y N @@ -512,27 +682,27 @@ Related group not found - Y + Y N - 1 - 500000 - Please specify a value between 1 and 500000. + 0 + 10000000 + Please specify a value between 0 and 10000000. N - /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u + /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u Should be a number between 1 and 8 characters, optionally followed by either "d", "h", "m", "s", "ms" or "us". N - /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u + /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u Should be a number between 1 and 8 characters, optionally followed by either "d", "h", "m", "s", "ms" or "us". N - /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u + /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u Should be a number between 1 and 8 characters, optionally followed by either "d", "h", "m", "s", "ms" or "us". N @@ -548,77 +718,96 @@ Y N + + 2 + 1000 + Please specify a value between 2 and 1000. + N + - 0 + 0 Y - 0 + 0 Y - 0 + 0 Y - 0 + 0 Y - 0 + 0 Y N - only store IPv4 addresses [default] - only store IPv6 addresses - store 32bit integers - store substrings - store binary blocks + Binary + Integer + IPv4 [default] + IPv6 + String N Y - Connection count - Current connections - Connection rate - Session count - Session rate - HTTP request count - HTTP request rate - HTTP error count - HTTP error rate Bytes in count (client to server) Bytes in rate (client to server) Bytes out count (server to client) Bytes out rate (server to client) + Connection count (total) + Connection count (current) + Connection rate + Glitch count + Glitch rate + General Purpose Counters (array of elements) + General Purpose Counter rate + gpc0 + gpc0 rate + gpc1 + gpc1 rate + General Purpose Tags (array of elements) + gpt0 + HTTP error count + HTTP error rate + HTTP fail count + HTTP fail rate + HTTP request count + HTTP request rate + Server ID + Session count + Session rate Y - 30m - /^([0-9]{1,5}(?:ms|s|m|h|d)?)/u + 30m + /^([0-9]{1,5}(?:ms|s|m|h|d)?)/u lower Should be a number between 1 and 5 characters followed by either "d", "h", "m", "s" or "ms". Y - 50k - /^([0-9]{1,5}[k|m|g]{1})*/u + 50k + /^([0-9]{1,5}[k|m|g]{1})*/u lower Should be a number between 1 and 5 characters followed by either "k", "m" or "g". - 1 + 1 N - src + src N - /^([0-9a-zA-Z._]){1,32}$/u + /^([0-9a-zA-Z._]){1,32}$/u Should be a string between 1 and 32 characters. @@ -628,73 +817,115 @@ N - 10s - /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u + 10s + /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u Should be a number between 1 and 8 characters, optionally followed by either "d", "h", "m", "s", "ms" or "us". N - 10s - /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u + 10s + /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u Should be a number between 1 and 8 characters, optionally followed by either "d", "h", "m", "s", "ms" or "us". N - 10s - /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u + 10s + /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u Should be a number between 1 and 8 characters, optionally followed by either "d", "h", "m", "s", "ms" or "us". N - 10s - /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u + 10s + /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u Should be a number between 1 and 8 characters, optionally followed by either "d", "h", "m", "s", "ms" or "us". N - 1m - /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u + 1m + /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u Should be a number between 1 and 8 characters, optionally followed by either "d", "h", "m", "s", "ms" or "us". N - 1m - /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u + 1m + /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u Should be a number between 1 and 8 characters, optionally followed by either "d", "h", "m", "s", "ms" or "us". N + + 0 + 0 + 99 + Please specify a value between 0 and 99. + N + + + 1m + /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u + Should be a number between 1 and 8 characters, optionally followed by either "d", "h", "m", "s", "ms" or "us". + N + + + 0 + 0 + 99 + Please specify a value between 0 and 99. + N + + + 1m + /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u + Should be a number between 1 and 8 characters, optionally followed by either "d", "h", "m", "s", "ms" or "us". + N + + + 1m + /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u + Should be a number between 1 and 8 characters, optionally followed by either "d", "h", "m", "s", "ms" or "us". + N + - 0 + 1 N - 0 + 0 N N - h2,http11 + h2,http11 Y Y +

    HTTP/3

    HTTP/2

    HTTP/1.1 HTTP/1.0
    + - 0 + 0 Y + + 0 + N + + + /metrics + N + /^.{1,2048}$/u + Should be a string between 1 and 2048 characters. + Y - http-keep-alive + http-keep-alive http-keep-alive [default] - http-tunnel httpclose http-server-close - forceclose @@ -710,7 +941,7 @@ Related action item not found Y - Y + Y N @@ -722,7 +953,7 @@ Related error file item not found - Y + Y N @@ -733,22 +964,22 @@ N - 1 + 1 Y Y - /^([0-9a-zA-Z._\-]){1,255}$/u + /^([0-9a-zA-Z._\-]){1,255}$/u Should be a string between 1 and 255 characters. N - /^.{1,255}$/u + /^.{1,255}$/u Should be a string between 1 and 255 characters. Y - http + http HTTP (Layer 7) [default] TCP (Layer 4) @@ -756,7 +987,7 @@ Y - source + source Source-IP Hash [default] Round Robin @@ -768,7 +999,7 @@ Y - 2 + 2 2 1000 Please specify a value between 2 and 1000. @@ -792,14 +1023,56 @@ Y N + + + + + Related fcgi item not found + N + N + + + + + + Related resolver not found + N + N + + + N + Y + Y + + allow-dup-ip + ignore-weight + prevent-dup-ip + + + + N + N + + prefer IPv4 + prefer IPv6 [default] + + - /^((([0-9a-zA-Z._\-\*:]+)))*/u + /^((([0-9a-zA-Z._\-\*:]+)))*/u lower Please specify a valid source address, i.e. 10.0.0.1 or loadbalancer.example.com:50000. Port range as start-end, i.e. 10.0.0.1:50000-60000. N - 1 + 1 Y @@ -815,16 +1088,16 @@ N - 0 + 0 N - /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u + /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u Should be a number between 1 and 8 characters, optionally followed by either "d", "h", "m", "s", "ms" or "us". N - /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u + /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u Should be a number between 1 and 8 characters, optionally followed by either "d", "h", "m", "s", "ms" or "us". N @@ -840,17 +1113,38 @@ Please specify a value between 1 and 100. N + + + + + Related mailer not found + N + N + + + N + backend + + Follow Backend Pool settings [default] + Enable for Health Check + Disable for Health Check + + - 0 + 1 N - 0 + 0 N N - h2,http11 + h2,http11 Y Y @@ -859,9 +1153,30 @@ HTTP/1.0 + + 0 + N + + + 0 + N + + + N + Y + Y + + proto + host + by + by_port + for + for_port + + N - sticktable + sticktable Stick-table persistence [default] Cookie-based persistence (HTTP/HTTPS only) @@ -869,68 +1184,84 @@ Y - piggyback + piggyback Piggyback on existing cookie Insert new cookie - SRVCOOKIE + SRVCOOKIE N - /^([0-9a-zA-Z\.,_\-:]){1,1024}$/u + /^([0-9a-zA-Z\.,_\-:]){1,1024}$/u Does not look like a valid cookie name (most special characters are not allowed). - 1 + 1 Y N - sourceipv4 + sourceipv4 - Source-IP [default] - Source-IPv6 - Existing cookie value + Binary + Cookie value + Integer RDP-Cookie + Source-IPv4 [default] + Source-IPv6 + String N Y - Connection count - Current connections - Connection rate - Session count - Session rate - HTTP request count - HTTP request rate - HTTP error count - HTTP error rate Bytes in count (client to server) Bytes in rate (client to server) Bytes out count (server to client) Bytes out rate (server to client) + Connection count (total) + Connection count (current) + Connection rate + Glitch count + Glitch rate + General Purpose Counters (array of elements) + General Purpose Counter rate + gpc0 + gpc0 rate + gpc1 + gpc1 rate + General Purpose Tags (array of elements) + gpt0 + HTTP error count + HTTP error rate + HTTP fail count + HTTP fail rate + HTTP request count + HTTP request rate + Server ID + Session count + Session rate Y - 30m - /^([0-9]{1,5}(?:ms|s|m|h|d)?)/u + 30m + /^([0-9]{1,5}(?:ms|s|m|h|d)?)/u lower Should be a number between 1 and 5 characters followed by either "d", "h", "m", "s" or "ms". Y - 50k - /^([0-9]{1,5}[k|m|g]{1})*/u + 50k + /^([0-9]{1,5}[k|m|g]{1})*/u lower Should be a number between 1 and 5 characters followed by either "k", "m" or "g". N - /^([0-9a-zA-Z\.,_\-:]){1,1024}$/u + /^([0-9a-zA-Z\.,_\-:]){1,1024}$/u Does not look like a valid cookie name (most special characters are not allowed). @@ -939,44 +1270,82 @@ Please specify a value between 1 and 10000. N + + 1 + 16384 + Please specify a value between 1 and 16384. + N + - 10s - /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u + 10s + /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u Should be a number between 1 and 8 characters, optionally followed by either "d", "h", "m", "s", "ms" or "us". N - 10s - /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u + 10s + /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u Should be a number between 1 and 8 characters, optionally followed by either "d", "h", "m", "s", "ms" or "us". N - 10s - /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u + 10s + /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u Should be a number between 1 and 8 characters, optionally followed by either "d", "h", "m", "s", "ms" or "us". N - 10s - /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u + 10s + /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u Should be a number between 1 and 8 characters, optionally followed by either "d", "h", "m", "s", "ms" or "us". N - 1m - /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u + 1m + /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u Should be a number between 1 and 8 characters, optionally followed by either "d", "h", "m", "s", "ms" or "us". N - 1m - /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u + 1m + /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u Should be a number between 1 and 8 characters, optionally followed by either "d", "h", "m", "s", "ms" or "us". N + + 0 + 0 + 99 + Please specify a value between 0 and 99. + N + + + 0 + 0 + 99 + Please specify a value between 0 and 99. + N + + + 1m + /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u + Should be a number between 1 and 8 characters, optionally followed by either "d", "h", "m", "s", "ms" or "us". + N + + + 1m + /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u + Should be a number between 1 and 8 characters, optionally followed by either "d", "h", "m", "s", "ms" or "us". + N + + + 1m + /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u + Should be a number between 1 and 8 characters, optionally followed by either "d", "h", "m", "s", "ms" or "us". + N + - 0 + 0 N @@ -988,7 +1357,7 @@ Related user not found - Y + Y N @@ -1000,28 +1369,28 @@ Related group not found - Y + Y N - /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u + /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u Should be a number between 1 and 8 characters, optionally followed by either "d", "h", "m", "s", "ms" or "us". N - /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u + /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u Should be a number between 1 and 8 characters, optionally followed by either "d", "h", "m", "s", "ms" or "us". N - /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u + /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u Should be a number between 1 and 8 characters, optionally followed by either "d", "h", "m", "s", "ms" or "us". N - 1 + 0 100 - Please specify a value between 1 and 100. + Please specify a value between 0 and 100. N @@ -1031,12 +1400,12 @@ N - 0 + 0 Y N - safe + safe Never Safe [default] @@ -1045,7 +1414,7 @@ - 0 + 0 N @@ -1058,7 +1427,7 @@ Related action item not found Y - Y + Y N @@ -1070,7 +1439,7 @@ Related error file item not found - Y + Y N @@ -1081,23 +1450,23 @@ Y - 1 + 1 Y - /^([0-9a-zA-Z._\-]){1,255}$/u + /^([0-9a-zA-Z._\-]){1,255}$/u Should be a string between 1 and 255 characters. Y - /^.{1,255}$/u + /^.{1,255}$/u Should be a string between 1 and 255 characters. N
    - /^([0-9a-zA-Z\.,_\-:]){0,1024}$/u + /^([0-9a-zA-Z\.,_\-:]){0,1024}$/u Please specify a valid servername or IP address. - Y + N
    1 @@ -1106,27 +1475,94 @@ N - 1 65535 Please specify a value between 1 and 65535. - N - Y - active + N + active active [default] backup disabled + + N + unspecified + + auto-selection [recommended] + FastCGI +

    HTTP/2

    +

    HTTP/1.1

    +
    +
    + + Y + static + + static + + unix socket + + + + /^([0-9a-zA-Z\.,_\-:]){0,1024}$/u + Please specify a valid service name. + N + + + /^[0-9]+(-[0-9]+)?/u + Please specify a valid number or range. + N + + + + + + Related resolver not found + N + N + + + N + Y + Y + + allow-dup-ip + ignore-weight + prevent-dup-ip + + + + N + N + + prefer IPv4 + prefer IPv6 [default] + + - 0 + 0 Y + + /^([0-9a-zA-Z._\-]){1,255}$/u + Should be a string between 1 and 255 characters. + N + + + /^.{1,255}$/u + Should be a string between 1 and 255 characters. + N + - 1 + 1 Y @@ -1145,6 +1581,12 @@ cert Please select a valid certificate from the list. + + 0 + 10000000 + Please specify a value between 0 and 10000000. + N + 0 256 @@ -1152,17 +1594,17 @@ N - /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u + /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u Should be a number between 1 and 8 characters, optionally followed by either "d", "h", "m", "s", "ms" or "us". N - /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u + /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u Should be a number between 1 and 8 characters, optionally followed by either "d", "h", "m", "s", "ms" or "us". N - /^((([0-9a-zA-Z._\-\*:]+)))*/u + /^((([0-9a-zA-Z._\-\*:]+)))*/u lower Please specify a valid source address, i.e. 10.0.0.1 or loadbalancer.example.com:50000. Port range as start-end, i.e. 10.0.0.1:50000-60000. N @@ -1170,23 +1612,38 @@ N + + + + + Related frontend item not found + N + N + - /^[^\t^,^;^\.^\[^\]^\{^\}]{1,255}$/u + /^[^\t^,^;^\.^\[^\]^\{^\}]{1,255}$/u Should be a string between 1 and 255 characters. Y - /^.{1,255}$/u + /^.{1,255}$/u Should be a string between 1 and 255 characters. N Y - http + http TCP HTTP [default] @@ -1201,25 +1658,39 @@ - 2s - /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u + 2s + /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u Should be a number between 1 and 8 characters, optionally followed by either "d", "h", "m", "s", "ms" or "us". Y + + N + nopref + + Use server settings + Force SSL for health checks + Force SSL+SNI for health checks + Force no SSL for health checks + + + + /^([0-9a-zA-Z._\-]){1,255}$/u + Should be a string between 1 and 255 characters. + N + + - 0 + 0 N - 1 65535 Please specify a value between 1 and 65535. - N N - options + options OPTIONS [default] HEAD @@ -1231,27 +1702,28 @@ - / - /^(.*){1,255}$/u + / + /^(.*){1,255}$/u Should be a string between 1 and 255 characters. N N - http10 + http10 HTTP/1.0 [default] HTTP/1.1 + HTTP/2 - localhost - /^([0-9a-zA-Z._\-]){1,255}$/u + localhost + /^([0-9a-zA-Z._\-]){1,255}$/u Should be a string between 1 and 255 characters. N - 0 + 0 N @@ -1264,14 +1736,14 @@
    - 0 + 0 N N - 0 + 0 N @@ -1279,7 +1751,7 @@ N - string + string test the exact string match in the response buffer [default] test a regular expression on the response buffer @@ -1287,7 +1759,7 @@ - 0 + 0 N @@ -1300,26 +1772,26 @@ N - /^([0-9a-zA-Z._\-]){1,255}$/u + /^([0-9a-zA-Z._\-]){1,255}$/u Should be a string between 1 and 255 characters. N - 0 + 0 N - /^([0-9a-zA-Z._\-]){1,255}$/u + /^([0-9a-zA-Z._\-]){1,255}$/u Should be a string between 1 and 255 characters. N - /^([0-9a-zA-Z._\-]){1,255}$/u + /^([0-9a-zA-Z._\-]){1,255}$/u Should be a string between 1 and 255 characters. N - /^([0-9a-zA-Z._\-]){1,255}$/u + /^([0-9a-zA-Z._\-]){1,255}$/u Should be a string between 1 and 255 characters. N @@ -1341,152 +1813,315 @@ N - /^[^\t^,^;^\.^\[^\]^\{^\}]{1,255}$/u + /^[^\t^,^;^\.^\[^\]^\{^\}]{1,255}$/u Should be a string between 1 and 255 characters. Y - /^.{1,255}$/u + /^.{1,255}$/u Should be a string between 1 and 255 characters. N Y - HTTP Basic Auth: username/password from client matches selected User/Group - Host starts with - Host ends with - Host matches - Host regex - Host contains - Path starts with - Path ends with - Path matches - Path regex - Path contains subdir - Path contains string - URL parameter contains - SSL Client certificate is valid - SSL Client certificate verify error result - SSL Client certificate issued by CA common-name - Source IP matches specified IP - Source IP is local - Source IP: TCP source port - Source IP: incoming bytes rate - Source IP: outgoing bytes rate - Source IP: amount of data received (in kilobytes) - Source IP: amount of data sent (in kilobytes) - - Source IP: cumulative number of connections - Source IP: concurrent connections - Source IP: connection rate - - - - Source IP: cumulative number of HTTP errors - Source IP: rate of HTTP errors - Source IP: number of HTTP requests - Source IP: rate of HTTP requests - - Source IP: cumulative number of connections - Source IP: session rate - Minimum number of usable servers in backend - Traffic is HTTP - Traffic is SSL (TCP request content inspection) - Traffic is SSL (locally deciphered) - SNI TLS extension matches (locally deciphered) - SNI TLS extension matches (TCP request content inspection) - SNI TLS extension contains (TCP request content inspection) - SNI TLS extension starts with (TCP request content inspection) - SNI TLS extension ends with (TCP request content inspection) - SNI TLS extension regex (TCP request content inspection) + hdr_beg - specified HTTP Header starts with + hdr_end - specified HTTP Header ends with + hdr - specified HTTP Header matches + hdr_reg - specified HTTP Header regex + hdr_sub - specified HTTP Header contains + hdr_beg - HTTP Host Header starts with + hdr_end - HTTP Host Header ends with + hdr - HTTP Host Header matches + hdr_reg - HTTP Host Header regex + hdr_sub - HTTP Host Header contains + http_auth - HTTP Basic Auth: username/password from client matches selected User/Group + http_method - HTTP Method + nbsrv - Minimum number of usable servers in backend + path_beg - Path starts with + path_dir - Path contains subdir + path_end - Path ends with + path - Path matches + path_reg - Path regex + path_sub - Path contains string + quic_enabled - QUIC transport protocol is enabled + req.proto_http - Traffic is HTTP + req.ssl_ver - Traffic is SSL (TCP request content inspection) + sc_bytes_in_rate - Sticky counter: incoming bytes rate + sc_bytes_out_rate - Sticky counter: outgoing bytes rate + sc_clr_gpc - Sticky counter: clear General Purpose Counter + sc_clr_gpc0 - Sticky counter: clear General Purpose Counter + sc_clr_gpc1 - Sticky counter: clear General Purpose Counter + sc0_clr_gpc0 - Sticky counter: clear General Purpose Counter + sc0_clr_gpc1 - Sticky counter: clear General Purpose Counter + sc1_clr_gpc - Sticky counter: clear General Purpose Counter + sc1_clr_gpc0 - Sticky counter: clear General Purpose Counter + sc1_clr_gpc1 - Sticky counter: clear General Purpose Counter + sc2_clr_gpc - Sticky counter: clear General Purpose Counter + sc2_clr_gpc0 - Sticky counter: clear General Purpose Counter + sc2_clr_gpc1 - Sticky counter: clear General Purpose Counter + sc_conn_cnt - Sticky counter: cumulative number of connections + sc_conn_cur - Sticky counter: concurrent connections + sc_conn_rate - Sticky counter: connection rate + sc_get_gpc - Sticky counter: get General Purpose Counter value + sc_get_gpc0 - Sticky counter: get General Purpose Counter value + sc_get_gpc1 - Sticky counter: get General Purpose Counter value + sc0_get_gpc0 - Sticky counter: get General Purpose Counter value + sc0_get_gpc1 - Sticky counter: get General Purpose Counter value + sc1_get_gpc0 - Sticky counter: get General Purpose Counter value + sc1_get_gpc1 - Sticky counter: get General Purpose Counter value + sc2_get_gpc0 - Sticky counter: get General Purpose Counter value + sc2_get_gpc1 - Sticky counter: get General Purpose Counter value + sc_get_gpt - Sticky counter: get General Purpose Tag value + sc_get_gpt0 - Sticky counter: get General Purpose Tag value + sc0_get_gpt0 - Sticky counter: get General Purpose Tag value + sc1_get_gpt0 - Sticky counter: get General Purpose Tag value + sc2_get_gpt0 - Sticky counter: get General Purpose Tag value + sc_glitch_cnt - Sticky counter: cumulative number of glitches + sc_glitch_rate - Sticky counter: rate of glitches + sc_gpc_rate - Sticky counter: increment rate of General Purpose Counter + sc_gpc0_rate - Sticky counter: increment rate of General Purpose Counter + sc_gpc1_rate - Sticky counter: increment rate of General Purpose Counter + sc0_gpc0_rate - Sticky counter: increment rate of General Purpose Counter + sc0_gpc1_rate - Sticky counter: increment rate of General Purpose Counter + sc1_gpc0_rate - Sticky counter: increment rate of General Purpose Counter + sc1_gpc1_rate - Sticky counter: increment rate of General Purpose Counter + sc2_gpc0_rate - Sticky counter: increment rate of General Purpose Counter + sc2_gpc1_rate - Sticky counter: increment rate of General Purpose Counter + sc_http_err_cnt - Sticky counter: cumulative number of HTTP errors + sc_http_err_rate - Sticky counter: rate of HTTP errors + sc_http_fail_cnt - Sticky counter: cumulative number of HTTP failures + sc_http_fail_rate - Sticky counter: rate of HTTP failures + sc_http_req_cnt - Sticky counter: cumulative number of HTTP requests + sc_http_req_rate - Sticky counter: rate of HTTP requests + sc_inc_gpc - Sticky counter: increment General Purpose Counter + sc_inc_gpc0 - Sticky counter: increment General Purpose Counter + sc_inc_gpc1 - Sticky counter: increment General Purpose Counter + sc0_inc_gpc0 - Sticky counter: increment General Purpose Counter + sc0_inc_gpc1 - Sticky counter: increment General Purpose Counter + sc1_inc_gpc0 - Sticky counter: increment General Purpose Counter + sc1_inc_gpc1 - Sticky counter: increment General Purpose Counter + sc2_inc_gpc0 - Sticky counter: increment General Purpose Counter + sc2_inc_gpc1 - Sticky counter: increment General Purpose Counter + sc_sess_cnt - Sticky counter: cumulative number of sessions + sc_sess_rate - Sticky counter: session rate + src - Source IP matches specified IP + src_bytes_in_rate - Source IP: incoming bytes rate + src_bytes_out_rate - Source IP: outgoing bytes rate + src_clr_gpc - Source IP: clear General Purpose Counter + src_clr_gpc0 - Source IP: clear General Purpose Counter + src_clr_gpc1 - Source IP: clear General Purpose Counter + src_conn_cnt - Source IP: cumulative number of connections + src_conn_cur - Source IP: concurrent connections + src_conn_rate - Source IP: connection rate + src_get_gpc - Source IP: get General Purpose Counter value + src_get_gpc0 - Source IP: get General Purpose Counter value + src_get_gpc1 - Source IP: get General Purpose Counter value + src_get_gpt - Source IP: get General Purpose Tag value + src_glitch_cnt - Source IP: cumulative number of glitches + src_glitch_rate - Source IP: rate of glitches + src_gpc_rate - Source IP: increment rate of General Purpose Counter + src_gpc0_rate - Source IP: increment rate of General Purpose Counter + src_gpc1_rate - Source IP: increment rate of General Purpose Counter + src_http_err_cnt - Source IP: cumulative number of HTTP errors + src_http_err_rate - Source IP: rate of HTTP errors + src_http_fail_cnt - Source IP: cumulative number of HTTP failures + src_http_fail_rate - Source IP: rate of HTTP failures + src_http_req_cnt - Source IP: number of HTTP requests + src_http_req_rate - Source IP: rate of HTTP requests + src_inc_gpc - Source IP: increment General Purpose Counter + src_inc_gpc0 - Source IP: increment General Purpose Counter + src_inc_gpc1 - Source IP: increment General Purpose Counter + src_is_local - Source IP is local + src_kbytes_in - Source IP: amount of data received (in kilobytes) + src_kbytes_out - Source IP: amount of data sent (in kilobytes) + src_port - Source IP: TCP source port + src_sess_cnt - Source IP: cumulative number of sessions + src_sess_rate - Source IP: session rate + ssl_c_ca_commonname - SSL Client certificate issued by CA common-name + ssl_c_verify_code - SSL Client certificate verify error result + ssl_c_verify - SSL Client certificate is valid + ssl_fc_sni - SNI TLS extension matches (locally deciphered) + ssl_fc - Traffic is SSL (locally deciphered) + ssl_hello_type - SSL Hello Type + ssl_sni_beg - SNI TLS extension starts with (TCP request content inspection) + ssl_sni_end - SNI TLS extension ends with (TCP request content inspection) + ssl_sni_reg - SNI TLS extension regex (TCP request content inspection) + ssl_sni - SNI TLS extension matches (TCP request content inspection) + ssl_sni_sub - SNI TLS extension contains (TCP request content inspection) + stopping - HAProxy process is currently stopping + url_param - URL parameter contains + var - Compare the value of a variable + wait_end - Inspection period is over Custom condition (option pass-through) - 0 + 0 Y + + 0 + N + - /^.{1,255}$/u + /^.{1,255}$/u Should be a string between 1 and 255 characters. N - /^.{1,255}$/u + /^.{1,255}$/u Should be a string between 1 and 255 characters. N - /^.{1,1024}$/u + /^.{1,1024}$/u Should be a string between 1 and 1024 characters. N - /^.{1,1024}$/u + /^.{1,1024}$/u Should be a string between 1 and 1024 characters. N - /^.{1,255}$/u + /^.{1,255}$/u Should be a string between 1 and 255 characters. N - /^.{1,255}$/u + /^.{1,255}$/u Should be a string between 1 and 255 characters. N - /^.{1,255}$/u + /^.{1,255}$/u Should be a string between 1 and 255 characters. N - /^.{1,1024}$/u + /^.{1,1024}$/u Should be a string between 1 and 1024 characters. N - /^.{1,1024}$/u + /^.{1,1024}$/u Should be a string between 1 and 1024 characters. N - /^.{1,255}$/u + /^.{1,255}$/u Should be a string between 1 and 255 characters. N - /^.{1,255}$/u + /^.{1,255}$/u Should be a string between 1 and 255 characters. N - - /^.{1,4096}$/u + + /^.{1,255}$/u + Should be a string between 1 and 255 characters. N - - - /^.{1,4096}$/u + + + /^.{1,255}$/u + Should be a string between 1 and 255 characters. N - - + + + /^.{1,255}$/u + Should be a string between 1 and 255 characters. + N + + + /^.{1,255}$/u + Should be a string between 1 and 255 characters. + N + + + /^.{1,1024}$/u + Should be a string between 1 and 1024 characters. + N + + + /^.{1,1024}$/u + Should be a string between 1 and 1024 characters. + N + + + /^.{1,1024}$/u + Should be a string between 1 and 1024 characters. + N + + + /^.{1,1024}$/u + Should be a string between 1 and 1024 characters. + N + + + /^.{1,255}$/u + Should be a string between 1 and 255 characters. + N + + + /^.{1,255}$/u + Should be a string between 1 and 255 characters. + N + + + /^.{1,4096}$/u + N + + + /^.{1,4096}$/u + N + + + /^.{1,4096}$/u + N + + + /^.{1,4096}$/u + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + 0 500000 Please specify a value between 0 and 500000. N - /^.{1,4096}$/u + /^.{1,4096}$/u N + + N + x1 + + 0 - no client hello + 1 - client hello + 2 - server hello + + - /^.{1,4096}$/u + /^.{1,4096}$/u N N - gt + gt greater than greater equal @@ -1500,7 +2135,7 @@ N - gt + gt greater than greater equal @@ -1514,7 +2149,7 @@ N - gt + gt greater than greater equal @@ -1528,7 +2163,7 @@ N - gt + gt greater than greater equal @@ -1542,7 +2177,7 @@ N - gt + gt greater than greater equal @@ -1556,7 +2191,7 @@ N - gt + gt greater than greater equal @@ -1570,7 +2205,7 @@ N - gt + gt greater than greater equal @@ -1584,7 +2219,7 @@ N - gt + gt greater than greater equal @@ -1598,7 +2233,7 @@ N - gt + gt greater than greater equal @@ -1612,7 +2247,7 @@ N - gt + gt greater than greater equal @@ -1626,7 +2261,7 @@ N - gt + gt greater than greater equal @@ -1640,7 +2275,7 @@ N - gt + gt greater than greater equal @@ -1654,7 +2289,7 @@ N - gt + gt greater than greater equal @@ -1668,7 +2303,7 @@ N - gt + gt greater than greater equal @@ -1698,31 +2333,31 @@ N - /^.{1,4096}$/u + /^.{1,4096}$/u N - /^.{1,4096}$/u + /^.{1,4096}$/u N - /^.{1,4096}$/u + /^.{1,4096}$/u N - /^.{1,4096}$/u + /^.{1,4096}$/u N - /^.{1,4096}$/u + /^.{1,4096}$/u N - /^.{1,4096}$/u + /^.{1,4096}$/u N - /^.{1,4096}$/u + /^.{1,4096}$/u N @@ -1743,48 +2378,1140 @@ Related backend item not found N N - - - - - - Related user not found - Y + + + + + + Related user not found + Y + N + + + + + + Related group not found + Y + N + + + N + Y + + CONNECT + DELETE + GET + HEAD + OPTIONS + PATCH + POST + PUT + TRACE + + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + + + N + + + 0 + 100 + Please specify a value between 0 and 99. + N + + + 0 + 100 + Please specify a value between 0 and 99. + N + + + 0 + 100 + Please specify a value between 0 and 99. + N + + + /^.{1,4096}$/u N - - + + - Related group not found - Y + Related mapfile item not found N - + + + /^.{1,4096}$/u + N + + + 1 + Y + - /^[^\t^,^;^\.^\[^\]^\{^\}]{1,255}$/u + /^[^\t^,^;^\.^\[^\]^\{^\}]{1,255}$/u Should be a string between 1 and 255 characters. Y - /^.{1,255}$/u + /^.{1,255}$/u Should be a string between 1 and 255 characters. N Y - if + if IF [default] UNLESS @@ -1804,7 +3531,7 @@ N - and + and AND [default] OR @@ -1813,43 +3540,19 @@ Y + Compression for HTTP responses/requests + FastCGI pass-header + FastCGI set-param + http-after-response + http-request + http-response + Map data to backend pools using a map file + Map domains to backend pools using a map file + monitor fail: report failure to a monitor request + tcp-request + tcp-response Use specified Backend Pool Override server in Backend Pool - Map domains to backend pools using a map file - http-request allow - http-request deny - http-request tarpit - http-request auth - http-request redirect - http-request lua action - http-request lua service - http-request header add - http-request header set - http-request header delete - http-request header replace - http-request header replace value - http-request set-path - http-response allow - http-response deny - http-response lua script - http-response header add - http-response header set - http-response header delete - http-response header replace - http-response header replace value - Set HTTP status code in response - tcp-request connection accept - tcp-request connection reject - tcp-request content accept - tcp-request content reject - tcp-request content lua script - tcp-request content use-service - tcp-request inspect-delay - tcp-response content accept - tcp-response content close - tcp-response content reject - tcp-response content lua script - tcp-response inspect-delay Custom rule (option pass-through) @@ -1862,7 +3565,7 @@ Related backend item not found - Y + N N @@ -1874,104 +3577,416 @@ Related server item not found - Y + N N + + /^.{1,1024}$/u + Should be a string between 1 and 1024 characters. + N + + + /^.{1,1024}$/u + Should be a string between 1 and 1024 characters. + N + + + /^.{1,4096}$/u + N + + + /^.{1,4096}$/u + N + + + N + + add-header + allow + capture + del-header + del-map + do-log + replace-header + replace-value + sc-add-gpc + sc-inc-gpc + sc-inc-gpc0 + sc-inc-gpc1 + sc-set-gpt + sc-set-gpt0 + set-header + set-log-level + set-map + set-status + set-var + set-var-fmt + strict-mode + unset-var + + + + /^.{1,4096}$/u + N + + + N + + add-acl + add-header + allow + auth + cache-use + capture + del-acl + del-header + del-map + deny + disable-l7-retry + do-log + do-resolve + early-hint + lua + normalize-uri + redirect + reject + replace-header + replace-path + replace-pathq + replace-uri + replace-value + return + sc-add-gpc + sc-inc-gpc + sc-inc-gpc0 + sc-inc-gpc1 + sc-set-gpt + sc-set-gpt0 + send-spoe-group + set-dst + set-dst-port + set-fc-mark + set-fc-tos + set-header + set-log-level + set-map + set-method + set-nice + set-path + set-pathq + set-priority-class + set-priority-offset + set-query + set-src + set-src-port + set-timeout + set-uri + set-var + set-var-fmt + silent-drop + strict-mode + tarpit + track-sc0 + track-sc1 + track-sc2 + unset-var + use-service - use a lua service + wait-for-body + wait-for-handshake + + + + /^.{1,4096}$/u + N + + + N + + add-acl + add-header + allow + cache-store + capture + del-acl + del-header + del-map + deny + do-log + lua + redirect + replace-header + replace-value + return + sc-add-gpc + sc-inc-gpc + sc-inc-gpc0 + sc-inc-gpc1 + sc-set-gpt + sc-set-gpt0 + send-spoe-group + set-fc-mark + set-fc-tos + set-header + set-log-level + set-map + set-nice + set-status + set-timeout + set-var + set-var-fmt + silent-drop + strict-mode + track-sc0 + track-sc1 + track-sc2 + unset-var + wait-for-body + + + + /^.{1,4096}$/u + N + + + N + + connection accept + connection expect-netscaler-cip + connection expect-proxy + connection fc-silent-drop + connection reject + connection sc-add-gpc + connection sc-inc-gpc + connection sc-inc-gpc0 + connection sc-inc-gpc1 + connection sc-set-gpt + connection sc-set-gpt0 + connection send-spoe-group + connection set-dst + connection set-dst-port + connection set-fc-mark + connection set-fc-tos + connection set-log-level + connection set-src + connection set-src-port + connection set-var + connection set-var-fmt + connection silent-drop + connection track-sc0 + connection track-sc1 + connection track-sc2 + connection unset-var + content accept + content capture + content do-resolve + content lua + content reject + content sc-add-gpc + content sc-inc-gpc + content sc-inc-gpc0 + content sc-inc-gpc1 + content sc-set-gpt + content sc-set-gpt0 + content send-spoe-group + content set-dst + content set-dst-port + content set-fc-mark + content set-fc-tos + content set-log-level + content set-nice + content set-priority-class + content set-priority-offset + content set-src + content set-src-port + content set-var + content set-var-fmt + content silent-drop + content switch-mode + content track-sc0 + content track-sc1 + content track-sc2 + content unset-var + content use-service - use a lua service + inspect-delay + session accept + session attach-srv + session reject + session sc-add-gpc + session sc-inc-gpc + session sc-inc-gpc0 + session sc-inc-gpc1 + session sc-set-gpt + session sc-set-gpt0 + session send-spoe-group + session set-dst + session set-dst-port + session set-fc-mark + session set-fc-tos + session set-log-level + session set-src + session set-src-port + session set-var + session set-var-fmt + session silent-drop + session track-sc0 + session track-sc1 + session track-sc2 + session unset-var + + + + /^.{1,4096}$/u + N + + + N + + content accept + content close + content lua + content reject + content sc-add-gpc + content sc-inc-gpc + content sc-inc-gpc0 + content sc-inc-gpc1 + content sc-set-gpt + content sc-set-gpt0 + content send-spoe-group + content set-fc-mark + content set-fc-tos + content set-log-level + content set-nice + content set-var + content set-var-fmt + content silent-drop + content unset-var + inspect-delay + + + + /^.{1,4096}$/u + N + + - /^.{1,4096}$/u + /^.{1,4096}$/u N - + + 100 + 999 + Please specify a value between 100 and 999. + N + - /^.{1,4096}$/u + /^.{1,4096}$/u N - /^.{1,4096}$/u + /^.{1,4096}$/u N - /^.{1,4096}$/u + /^.{1,4096}$/u N - /^.{1,4096}$/u + /^.{1,4096}$/u N - /^.{1,4096}$/u + /^.{1,4096}$/u N - /^.{1,4096}$/u + /^.{1,4096}$/u N - /^.{1,4096}$/u + /^.{1,4096}$/u N - /^.{1,4096}$/u + /^.{1,4096}$/u N - /^.{1,4096}$/u + /^.{1,4096}$/u N - /^.{1,4096}$/u + /^.{1,4096}$/u N - /^.{1,4096}$/u + /^.{1,4096}$/u N - /^.{1,4096}$/u + /^.{1,4096}$/u N - /^.{1,4096}$/u + /^.{1,4096}$/u N + + N + txn + + variable is shared with the whole process + variable is shared with the whole session + variable is shared with the transaction (request/response) + variable is shared only during request processing + variable is shared only during response processing + + + + /^.{1,4096}$/u + N + + + /^.{1,4096}$/u + N + - /^.{1,4096}$/u + /^.{1,4096}$/u N - /^.{1,4096}$/u + /^.{1,4096}$/u N - /^.{1,4096}$/u + /^.{1,4096}$/u N - /^.{1,4096}$/u + /^.{1,4096}$/u N - /^.{1,4096}$/u + /^.{1,4096}$/u N - /^.{1,4096}$/u + /^.{1,4096}$/u N - /^.{1,4096}$/u + /^.{1,4096}$/u N - /^.{1,4096}$/u + /^.{1,4096}$/u N - /^.{1,4096}$/u + /^.{1,4096}$/u N - /^.{1,4096}$/u + /^.{1,4096}$/u N @@ -1981,67 +3996,77 @@ N - /^.{1,4096}$/u + /^.{1,4096}$/u N + + N + txn + + variable is shared with the whole process + variable is shared with the whole session + variable is shared with the transaction (request/response) + variable is shared only during request processing + variable is shared only during response processing + + + + /^.{1,4096}$/u + N + + + /^.{1,4096}$/u + N + - /^.{1,4096}$/u + /^.{1,4096}$/u N - /^.{1,4096}$/u + /^.{1,4096}$/u N - /^.{1,32}$/u + /^.{1,32}$/u N - /^.{1,4096}$/u + /^.{1,4096}$/u N - /^.{1,32}$/u + /^.{1,32}$/u N - - /^.{1,4096}$/u - N - - - + + - Related backend item not found - Y + Related map file item not found + N N - - + + - Related server item not found - Y + Related backend pool item not found + N + N + + + /^.{1,4096}$/u N - - - N - - - N - - - N - +