summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--Makefile21
-rw-r--r--debian/changelog14
-rw-r--r--debian/control22
-rwxr-xr-xdebian/rules16
-rw-r--r--interface-definitions/bcast-relay.xml16
-rw-r--r--interface-definitions/cron.xml3
-rw-r--r--interface-definitions/dns-forwarding.xml107
-rw-r--r--interface-definitions/ntp.xml76
-rw-r--r--interface-definitions/ssh.xml151
-rw-r--r--op-mode-definitions/bandwidth-monitor.xml23
-rw-r--r--op-mode-definitions/dns-forwarding.xml39
-rw-r--r--op-mode-definitions/traffic-dump.xml45
-rw-r--r--op-mode-definitions/version.xml27
-rw-r--r--python/vyos/__init__.py1
-rw-r--r--python/vyos/base.py22
-rw-r--r--python/vyos/limericks.py49
-rw-r--r--python/vyos/version.py (renamed from python/vyos/util.py)4
-rw-r--r--schema/interface_definition.rng5
-rw-r--r--schema/op-mode-definition.rnc5
-rw-r--r--schema/op-mode-definition.rng10
-rwxr-xr-xscripts/build-command-op-templates63
-rwxr-xr-xscripts/build-command-templates63
-rwxr-xr-xsrc/conf-mode/vyos-config-bcast-relay.py8
-rwxr-xr-xsrc/conf-mode/vyos-config-dns-forwarding.py213
-rwxr-xr-xsrc/conf-mode/vyos-config-mdns-repeater.py5
-rwxr-xr-xsrc/conf-mode/vyos-config-ntp.py161
-rwxr-xr-xsrc/conf-mode/vyos-config-ssh.py249
-rwxr-xr-xsrc/conf-mode/vyos-update-crontab.py2
-rwxr-xr-xsrc/helpers/validate-value.py43
-rwxr-xr-xsrc/op-mode/vyos-dns-forwarding-statistics.py24
-rwxr-xr-xsrc/op-mode/vyos-list-dumpable-interfaces.py14
-rwxr-xr-xsrc/op-mode/vyos-list-interfaces.py8
-rwxr-xr-xsrc/op-mode/vyos-restart-dns-forwarding.sh8
-rwxr-xr-xsrc/op-mode/vyos-show-version.py123
-rwxr-xr-xsrc/validators/interface-address3
-rwxr-xr-xsrc/validators/ip-address3
-rwxr-xr-xsrc/validators/ip-host3
-rwxr-xr-xsrc/validators/ip-prefix3
-rwxr-xr-xsrc/validators/ipv4-address3
-rwxr-xr-xsrc/validators/ipv4-host3
-rwxr-xr-xsrc/validators/ipv4-prefix3
-rwxr-xr-xsrc/validators/ipv6-address3
-rwxr-xr-xsrc/validators/ipv6-host3
-rwxr-xr-xsrc/validators/ipv6-prefix3
45 files changed, 1584 insertions, 89 deletions
diff --git a/.gitignore b/.gitignore
index 9082493d3..ff15b4fbb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -104,7 +104,8 @@ ENV/
.mypy_cache/
# Autogenerated files
-templates/*
+templates-cfg/*
+templates-op/*
tests/templates/*
# Debian packaging
diff --git a/Makefile b/Makefile
index f5800da43..8a75a91e4 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,5 @@
-TMPL_DIR := templates
+TMPL_DIR := templates-cfg
+OP_TMPL_DIR := templates-op
.PHONY: interface_definitions
.ONESHELL:
@@ -10,11 +11,27 @@ interface_definitions:
# XXX: delete top level node.def's that now live in other packages
rm -f $(TMPL_DIR)/system/node.def
rm -f $(TMPL_DIR)/service/node.def
+ rm -f $(TMPL_DIR)/service/dns/node.def
rm -f $(TMPL_DIR)/protocols/node.def
+.PHONY: op_mode_definitions
+.ONESHELL:
+op_mode_definitions:
+ mkdir -p $(OP_TMPL_DIR)
+
+ find $(CURDIR)/op-mode-definitions/ -type f | xargs -I {} $(CURDIR)/scripts/build-command-op-templates {} $(CURDIR)/schema/op-mode-definition.rng $(OP_TMPL_DIR)
+
+ # XXX: delete top level op mode node.def's that now live in other packages
+ rm -f $(OP_TMPL_DIR)/show/node.def
+ rm -f $(OP_TMPL_DIR)/show/dns/node.def
+ rm -f $(OP_TMPL_DIR)/reset/node.def
+ rm -f $(OP_TMPL_DIR)/restart/node.def
+ rm -f $(OP_TMPL_DIR)/monitor/node.def
+
.PHONY: all
-all: interface_definitions
+all: interface_definitions op_mode_definitions
.PHONY: clean
clean:
rm -rf $(TMPL_DIR)/*
+ rm -rf $(OP_TMPL_DIR)/*
diff --git a/debian/changelog b/debian/changelog
index 3db7c422e..0f4183837 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,17 @@
+vyos-1x (1.0.5) unstable; urgency=medium
+
+ * T606: Error in DNS Forwarder listen-on
+ * T608: Cannot configure broadcast-relay service
+
+ -- Christian Poessinger <christian@poessinger.com> Thu, 19 Apr 2018 21:16:28 +0200
+
+vyos-1x (1.0.4) unstable; urgency=medium
+
+ * T560: dns-forwarding: replace dnsmasq with pdns-recursor
+ * T588: Rewrite 'service dns forwarding' in new XML style format
+
+ -- Christian Poessinger <christian@poessinger.com> Sun, 15 Apr 2018 16:13:32 +0200
+
vyos-1x (1.0.3) unstable; urgency=medium
* T379: Add UDP broadcast relay support
diff --git a/debian/control b/debian/control
index a19cc80d4..c31c470e9 100644
--- a/debian/control
+++ b/debian/control
@@ -2,13 +2,27 @@ Source: vyos-1x
Section: contrib/net
Priority: extra
Maintainer: VyOS Package Maintainers <maintainers@vyos.net>
-Build-Depends: debhelper (>= 9), python3, python3-setuptools, quilt,
- python3-lxml
+Build-Depends: debhelper (>= 9),
+ quilt,
+ python3,
+ python3-setuptools,
+ quilt,
+ python3-lxml
Standards-Version: 3.9.6
Package: vyos-1x
Architecture: all
-Depends: python3, ${python3:Depends}, python3-netifaces,
- ${shlibs:Depends}, ${misc:Depends}
+Depends: python3,
+ ${python3:Depends},
+ python3-netifaces,
+ python3-jinja2,
+ python3-pystache,
+ ipaddrcheck,
+ tcpdump,
+ bmon,
+ hvinfo,
+ file,
+ ${shlibs:Depends},
+ ${misc:Depends}
Description: VyOS configuration scripts and data
VyOS configuration scripts, interface definitions, and everything
diff --git a/debian/rules b/debian/rules
index 5c3aab7f6..ed33706e4 100755
--- a/debian/rules
+++ b/debian/rules
@@ -2,26 +2,36 @@
DIR := debian/vyos-1x
VYOS_SBIN_DIR := opt/vyatta/sbin/
+VYOS_BIN_DIR := opt/vyatta/bin/
VYOS_LIBEXEC_DIR := opt/vyatta/libexec
VYOS_CFG_TMPL_DIR := /opt/vyatta/share/vyatta-cfg/templates
+VYOS_OP_TMPL_DIR := /opt/vyatta/share/vyatta-op/templates
%:
dh $@ --with python3, --with quilt
override_dh_auto_build:
- make
+ make all
override_dh_auto_install:
dh_install -pvyos-1x
cd python; python3 setup.py install --install-layout=deb --root ../$(DIR); cd ..
- # Install configuration scripts
+ # Install scripts
mkdir -p $(DIR)/$(VYOS_SBIN_DIR)
+ mkdir -p $(DIR)/$(VYOS_BIN_DIR)
cp -r src/conf-mode/* $(DIR)/$(VYOS_SBIN_DIR)
+ cp -r src/op-mode/* $(DIR)/$(VYOS_BIN_DIR)
# Install validators
mkdir -p $(DIR)/$(VYOS_LIBEXEC_DIR)/validators
cp -r src/validators/* $(DIR)/$(VYOS_LIBEXEC_DIR)/validators
+ # Install helper scripts
+ cp -r src/helpers/* $(DIR)/$(VYOS_LIBEXEC_DIR)/
+
mkdir -p $(DIR)/$(VYOS_CFG_TMPL_DIR)
- cp -r templates/* $(DIR)/$(VYOS_CFG_TMPL_DIR)
+ cp -r templates-cfg/* $(DIR)/$(VYOS_CFG_TMPL_DIR)
+
+ mkdir -p $(DIR)/$(VYOS_OP_TMPL_DIR)
+ cp -r templates-op/* $(DIR)/$(VYOS_OP_TMPL_DIR)
diff --git a/interface-definitions/bcast-relay.xml b/interface-definitions/bcast-relay.xml
index e4a009ed4..19f29e340 100644
--- a/interface-definitions/bcast-relay.xml
+++ b/interface-definitions/bcast-relay.xml
@@ -14,11 +14,13 @@
<properties>
<help>Unique ID for each UDP port to forward</help>
<valueHelp>
- <format>u32:1-99</format>
+ <format>1-99</format>
<description>Numerical ID #</description>
</valueHelp>
- <type>u32</type>
<priority>990</priority>
+ <constraint>
+ <validator name="numeric" argument="--range 1-99"/>
+ </constraint>
</properties>
<children>
<leafNode name="address">
@@ -28,7 +30,9 @@
<format>ipv4</format>
<description>Optional source address for forwarded packets</description>
</valueHelp>
- <type>ipv4</type>
+ <constraint>
+ <validator name="ipv4"/>
+ </constraint>
</properties>
</leafNode>
<leafNode name="description">
@@ -49,10 +53,12 @@
<properties>
<help>Destination or source port to listen and retransmit on [REQUIRED]</help>
<valueHelp>
- <format>u32:1-65535</format>
+ <format>1-65535</format>
<description>UDP port to listen on</description>
</valueHelp>
- <type>u32</type>
+ <constraint>
+ <validator name="numeric" argument="--range 1-65535"/>
+ </constraint>
</properties>
</leafNode>
</children>
diff --git a/interface-definitions/cron.xml b/interface-definitions/cron.xml
index a9e9b0401..65b95c5a4 100644
--- a/interface-definitions/cron.xml
+++ b/interface-definitions/cron.xml
@@ -44,6 +44,9 @@
<format>&lt;days&gt;d</format>
<description>Execution interval in days</description>
</valueHelp>
+ <constraint>
+ <regex>[1-9]([0-9]*)([mhd]{0,1})</regex>
+ </constraint>
</properties>
</leafNode>
<node name="executable">
diff --git a/interface-definitions/dns-forwarding.xml b/interface-definitions/dns-forwarding.xml
new file mode 100644
index 000000000..60b90c6a7
--- /dev/null
+++ b/interface-definitions/dns-forwarding.xml
@@ -0,0 +1,107 @@
+<?xml version="1.0"?>
+
+<!-- DNS forwarder configuration -->
+
+<interfaceDefinition>
+ <node name="service">
+ <children>
+ <node name="dns">
+ <children>
+ <node name="forwarding" owner="${vyos_sbindir}/vyos-config-dns-forwarding.py">
+ <properties>
+ <help>DNS forwarding</help>
+ <priority>918</priority>
+ </properties>
+ <children>
+ <leafNode name="cache-size">
+ <properties>
+ <help>DNS forwarding cache size</help>
+ <valueHelp>
+ <format>0-10000</format>
+ <description>DNS forwarding cache size</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-10000"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="dhcp">
+ <properties>
+ <help>Use nameservers received from DHCP server for specified interface</help>
+ <completionHelp>
+ <script>${vyatta_sbindir}/vyatta-interfaces.pl --show all</script>
+ </completionHelp>
+ <multi/>
+ </properties>
+ </leafNode>
+ <tagNode name="domain">
+ <properties>
+ <help>DNS domain to forward to a local server</help>
+ </properties>
+ <children>
+ <leafNode name="server">
+ <properties>
+ <help>Domain Name Server (DNS) to forward queries</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>Domain Name Server (DNS) IPv4 address</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>Domain Name Server (DNS) IPv6 address</description>
+ </valueHelp>
+ <multi/>
+ <constraint>
+ <validator name="ipv4-address"/>
+ <validator name="ipv6-address"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ <leafNode name="ignore-hosts-file">
+ <properties>
+ <help>Do not use local /etc/hosts file in name resolution</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="listen-on">
+ <properties>
+ <help>Interface to listen for DNS queries [REQUIRED]</help>
+ <completionHelp>
+ <script>${vyatta_sbindir}/vyatta-interfaces.pl --show all</script>
+ </completionHelp>
+ <multi/>
+ </properties>
+ </leafNode>
+ <leafNode name="name-server">
+ <properties>
+ <help>Domain Name Server (DNS)</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>Domain Name Server (DNS) IPv4 address</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>Domain Name Server (DNS) IPv6 address</description>
+ </valueHelp>
+ <multi/>
+ <constraint>
+ <validator name="ipv4-address"/>
+ <validator name="ipv6-address"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="system">
+ <properties>
+ <help>DNS forwarding to system nameservers</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/interface-definitions/ntp.xml b/interface-definitions/ntp.xml
new file mode 100644
index 000000000..e8cfc539c
--- /dev/null
+++ b/interface-definitions/ntp.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0"?>
+
+<!-- NTP configuration -->
+
+<interfaceDefinition>
+ <node name="system">
+ <children>
+ <node name="ntp" owner="${vyos_sbindir}/vyos-config-ntp.py">
+ <properties>
+ <help>Network Time Protocol (NTP) configuration</help>
+ <priority>400</priority>
+ </properties>
+ <children>
+ <tagNode name="server">
+ <properties>
+ <help>Network Time Protocol (NTP) server</help>
+ </properties>
+ <children>
+ <leafNode name="dynamic">
+ <properties>
+ <help>Allow server to be configured even if not reachable</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="noselect">
+ <properties>
+ <help>Marks the server as unused</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="preempt">
+ <properties>
+ <help>Specifies the association as preemptable rather than the default persistent</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="prefer">
+ <properties>
+ <help>Marks the server as preferred</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ <node name="allow-clients">
+ <properties>
+ <help>Network Time Protocol (NTP) server options</help>
+ <constraint>
+ <validator name="ip-prefix"/>
+ </constraint>
+ </properties>
+ <children>
+ <leafNode name="address">
+ <properties>
+ <help>IP address</help>
+ <valueHelp>
+ <format>ipv4net</format>
+ <description>IP address and prefix length</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv6net</format>
+ <description>IPv6 address and prefix length</description>
+ </valueHelp>
+ <multi/>
+ <constraint>
+ <validator name="ip-address"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/interface-definitions/ssh.xml b/interface-definitions/ssh.xml
new file mode 100644
index 000000000..dfae1d8ed
--- /dev/null
+++ b/interface-definitions/ssh.xml
@@ -0,0 +1,151 @@
+<?xml version="1.0"?>
+
+<!--SSH configuration -->
+
+<interfaceDefinition>
+ <node name="service">
+ <children>
+ <node name="ssh" owner="${vyos_sbindir}/vyos-config-ssh.py">
+ <properties>
+ <help>Secure SHell (SSH) protocol</help>
+ <priority>500</priority>
+ </properties>
+ <children>
+ <node name="access-control">
+ <properties>
+ <help>SSH user/group access controls. Directives are processed in this: deny-users, allow-users, deny-groups and allow-groups</help>
+ </properties>
+ <children>
+ <node name="allow">
+ <children>
+ <leafNode name="group">
+ <properties>
+ <help>Login is allowed for users whose primary or supplementary group matches</help>
+ <multi/>
+ </properties>
+ </leafNode>
+ <leafNode name="user">
+ <properties>
+ <help>Login is allowed only for user names that match</help>
+ <multi/>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <node name="deny">
+ <children>
+ <leafNode name="group">
+ <properties>
+ <help>Login is disallowed for users whose primary or supplementary group matches</help>
+ <multi/>
+ </properties>
+ </leafNode>
+ <leafNode name="user">
+ <properties>
+ <help>Login is disallowed for user names that match</help>
+ <multi/>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </node>
+ <leafNode name="ciphers">
+ <properties>
+ <help>Specifies allowed Ciphers</help>
+ <completionHelp>
+ <script>ssh -Q cipher | tr '\n' ' '</script>
+ </completionHelp>
+ <multi/>
+ </properties>
+ </leafNode>
+ <leafNode name="disable-host-validation">
+ <properties>
+ <help>Don't validate the remote host name with DNS</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="disable-password-authentication">
+ <properties>
+ <help>Don't allow unknown user to login with password</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="key-exchange">
+ <properties>
+ <help>Specifies available KEX (Key Exchange) algorithms</help>
+ <completionHelp>
+ <script>ssh -Q kex | tr '\n' ' '</script>
+ </completionHelp>
+ <multi/>
+ </properties>
+ </leafNode>
+ <leafNode name="listen-address">
+ <properties>
+ <help>Local addresses SSH service should listen on</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>IP address to listen for incoming connections</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>IPv6 address to listen for incoming connections</description>
+ </valueHelp>
+ <multi/>
+ <constraint>
+ <validator name="ipv4-address"/>
+ <validator name="ipv6-address"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="loglevel">
+ <properties>
+ <help>Log level</help>
+ <valueHelp>
+ <format>QUIET</format>
+ <description>stay silent</description>
+ </valueHelp>
+ <valueHelp>
+ <format>FATAL</format>
+ <description>log fatals only</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ERROR</format>
+ <description>log errors and fatals only</description>
+ </valueHelp>
+ <valueHelp>
+ <format>INFO</format>
+ <description>default log level</description>
+ </valueHelp>
+ <valueHelp>
+ <format>VERBOSE</format>
+ <description>enable logging of failed login attempts</description>
+ </valueHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="mac">
+ <properties>
+ <help>Specifies available MAC (message authentication code) algorithms</help>
+ <completionHelp>
+ <script>ssh -Q mac | tr '\n' ' '</script>
+ </completionHelp>
+ <multi/>
+ </properties>
+ </leafNode>
+ <leafNode name="port">
+ <properties>
+ <help>Port for SSH service</help>
+ <valueHelp>
+ <format>1-65535</format>
+ <description>Numeric IP port</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-65535"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/op-mode-definitions/bandwidth-monitor.xml b/op-mode-definitions/bandwidth-monitor.xml
new file mode 100644
index 000000000..a6ddcfd4b
--- /dev/null
+++ b/op-mode-definitions/bandwidth-monitor.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="monitor">
+ <children>
+ <node name="bandwidth">
+ <properties>
+ <help>Monitor interface bandwidth in real time</help>
+ </properties>
+ <children>
+ <tagNode name="interface">
+ <command>bmon -p $4</command>
+ <properties>
+ <help>Monitor bandwidth usage on specified interface</help>
+ <completionHelp>
+ <script>${vyos_bindir}/vyos-list-interfaces.py</script>
+ </completionHelp>
+ </properties>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/op-mode-definitions/dns-forwarding.xml b/op-mode-definitions/dns-forwarding.xml
new file mode 100644
index 000000000..96fa1a6f4
--- /dev/null
+++ b/op-mode-definitions/dns-forwarding.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0"?>
+
+<interfaceDefinition>
+ <node name="show">
+ <children>
+ <node name="dns">
+ <children>
+ <node name="forwarding">
+ <properties>
+ <help>Show DNS forwarding information</help>
+ </properties>
+ <children>
+ <leafNode name="statistics">
+ <properties>
+ <help>Show DNS forwarding statistics</help>
+ </properties>
+ <command>${vyos_bindir}/vyos-dns-forwarding-statistics.py</command>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </node>
+ </children>
+ </node>
+ <node name="restart">
+ <children>
+ <node name="dns">
+ <children>
+ <leafNode name="forwarding">
+ <properties>
+ <help>Restart DNS forwarding service</help>
+ </properties>
+ <command>${vyos_bindir}/vyos-restart-dns-forwarding.sh</command>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/op-mode-definitions/traffic-dump.xml b/op-mode-definitions/traffic-dump.xml
new file mode 100644
index 000000000..be53f866b
--- /dev/null
+++ b/op-mode-definitions/traffic-dump.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="monitor">
+ <children>
+ <node name="traffic">
+ <properties>
+ <help>Monitor traffic dumps</help>
+ </properties>
+ <children>
+ <tagNode name="interface">
+ <command>tcpdump -i $4</command>
+ <properties>
+ <help>Monitor traffic dump from an interface</help>
+ <completionHelp>
+ <script>${vyos_bindir}/vyos-list-dumpable-interfaces.py</script>
+ </completionHelp>
+ </properties>
+ <children>
+ <tagNode name="filter">
+ <command>tcpdump -n -i $4 $6</command>
+ <properties>
+ <help>Monitor traffic matching filter conditions</help>
+ </properties>
+ </tagNode>
+ <tagNode name="save">
+ <command>tcpdump -n -i $4 -w $6</command>
+ <properties>
+ <help>Save traffic dump from an interface to a file</help>
+ </properties>
+ <children>
+ <tagNode name="filter">
+ <command>tcpdump -n -i $4 -w $6 $8</command>
+ <properties>
+ <help>Save a dump of traffic matching filter conditions to a file</help>
+ </properties>
+ </tagNode>
+ </children>
+ </tagNode>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/op-mode-definitions/version.xml b/op-mode-definitions/version.xml
new file mode 100644
index 000000000..a03b41dcb
--- /dev/null
+++ b/op-mode-definitions/version.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="show">
+ <children>
+ <node name="version">
+ <properties>
+ <help>Show system version information</help>
+ </properties>
+ <command>${vyos_bindir}/vyos-show-version.py</command>
+ <children>
+ <leafNode name="funny">
+ <properties>
+ <help>Show system version and some fun stuff</help>
+ </properties>
+ <command>${vyos_bindir}/vyos-show-version.py --funny</command>
+ </leafNode>
+ <leafNode name="all">
+ <properties>
+ <help>Show system version and versions of all packages</help>
+ </properties>
+ <command>${vyos_bindir}/vyos-show-version.py --all</command>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/python/vyos/__init__.py b/python/vyos/__init__.py
index e69de29bb..9b5ed21c9 100644
--- a/python/vyos/__init__.py
+++ b/python/vyos/__init__.py
@@ -0,0 +1 @@
+from .base import *
diff --git a/python/vyos/base.py b/python/vyos/base.py
new file mode 100644
index 000000000..6197ed074
--- /dev/null
+++ b/python/vyos/base.py
@@ -0,0 +1,22 @@
+# Copyright (c) 2018 VyOS maintainers and contributors
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"),
+# to deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense,
+# and/or sell copies of the Software, and to permit persons to whom the Software
+# is furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
+# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+# IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+
+class ConfigError(Exception):
+ pass
diff --git a/python/vyos/limericks.py b/python/vyos/limericks.py
new file mode 100644
index 000000000..a0779904a
--- /dev/null
+++ b/python/vyos/limericks.py
@@ -0,0 +1,49 @@
+import random
+
+limericks = [
+
+"""
+A programmer who's name was Searle
+Once wrote a long program in Perl.
+Despite very few quirks
+No one got how it works,
+Not even the interpreter.
+""",
+
+"""
+There was a young lady of Maine
+Who set up IPsec VPN.
+Problems didn't arise
+'til other vendors' device
+had to add she to that VPN.
+""",
+
+"""
+One day a programmer from York
+started his own Vyatta fork.
+Though he was a huge geek,
+it still took him a week
+to get the damn build scripts to work.
+""",
+
+"""
+A network admin from Hong Kong
+knew MPPE cipher's not strong.
+But he was behind NAT,
+so he put up we that,
+sad network admin from Hong Kong.
+""",
+
+"""
+A network admin named Drake
+greeted friends with a three-way handshake
+and refused to proceed
+if they didn't complete it,
+that standards-compliant guy Drake.
+"""
+
+]
+
+
+def get_random():
+ return limericks[random.randint(0, len(limericks) - 1)]
diff --git a/python/vyos/util.py b/python/vyos/version.py
index b3eff3965..5d32d878d 100644
--- a/python/vyos/util.py
+++ b/python/vyos/version.py
@@ -19,10 +19,6 @@
import json
-class ConfigError(Exception):
- pass
-
-
def get_version_data(file='/opt/vyatta/etc/version.json'):
with open(file, 'r') as f:
version_data = json.load(f)
diff --git a/schema/interface_definition.rng b/schema/interface_definition.rng
index 5a0a48845..d1bd9a708 100644
--- a/schema/interface_definition.rng
+++ b/schema/interface_definition.rng
@@ -169,11 +169,6 @@
</element>
</optional>
<optional>
- <element name="type">
- <text/>
- </element>
- </optional>
- <optional>
<!-- These are meaningful only for tag nodes -->
<group>
<element name="keepChildOrder">
diff --git a/schema/op-mode-definition.rnc b/schema/op-mode-definition.rnc
index 01276123c..9c84de0e4 100644
--- a/schema/op-mode-definition.rnc
+++ b/schema/op-mode-definition.rnc
@@ -52,8 +52,7 @@ tagNode = element tagNode
leafNode = element leafNode
{
nodeNameAttr,
- command,
- properties
+ (command & properties)
}
# Normal and tag nodes may have children
@@ -105,4 +104,4 @@ completionHelp = element completionHelp
(element list { text })* &
(element path { text })* &
(element script { text })*
-} \ No newline at end of file
+}
diff --git a/schema/op-mode-definition.rng b/schema/op-mode-definition.rng
index 98a231e53..e9e7887cf 100644
--- a/schema/op-mode-definition.rng
+++ b/schema/op-mode-definition.rng
@@ -67,7 +67,9 @@
<optional>
<ref name="properties"/>
</optional>
- <ref name="children"/>
+ <optional>
+ <ref name="children"/>
+ </optional>
<optional>
<ref name="command"/>
</optional>
@@ -81,8 +83,10 @@
<define name="leafNode">
<element name="leafNode">
<ref name="nodeNameAttr"/>
- <ref name="command"/>
- <ref name="properties"/>
+ <interleave>
+ <ref name="command"/>
+ <ref name="properties"/>
+ </interleave>
</element>
</define>
<!-- Normal and tag nodes may have children -->
diff --git a/scripts/build-command-op-templates b/scripts/build-command-op-templates
index 6b6688fba..865590c2c 100755
--- a/scripts/build-command-op-templates
+++ b/scripts/build-command-op-templates
@@ -93,7 +93,7 @@ def get_properties(p):
try:
props["help"] = p.find("help").text
except:
- pass
+ props["help"] = "No help available"
# Get the completion help strings
@@ -113,7 +113,7 @@ def get_properties(p):
for i in paths:
comp_exprs.append("/bin/cli-shell-api listNodes {0}".format(i.text))
for i in scripts:
- comp_exprs.append("sh -c \"{0}\"".format(i.text))
+ comp_exprs.append("{0}".format(i.text))
comp_help = " && ".join(comp_exprs)
props["comp_help"] = comp_help
except:
@@ -128,14 +128,6 @@ def make_node_def(props, command):
node_def = ""
- if "tag" in props:
- node_def += "tag:\n"
-
-
- if "type" in props:
- node_def += "type: {0}\n".format(props["type"])
-
-
if "help" in props:
node_def += "help: {0}\n".format(props["help"])
@@ -145,7 +137,7 @@ def make_node_def(props, command):
if command is not None:
- node_def += "run: sudo sh -c {0}\n".format(command.text)
+ node_def += "run: sudo sh -c \"{0}\"\n".format(command.text)
if debug:
@@ -173,32 +165,45 @@ def process_node(n, tmpl_dir):
props = get_properties(props_elem)
- # Type should not be set for non-tag, non-leaf nodes
- if node_type != "node":
- props["type"] = "txt"
+ if node_type == "node":
+ if debug:
+ print("Processing node {}".format(name))
+
+ with open(os.path.join(make_path(my_tmpl_dir), "node.def"), "w") as f:
+ f.write(make_node_def(props, command))
+
+ if children is not None:
+ inner_nodes = children.iterfind("*")
+ for inner_n in inner_nodes:
+ process_node(inner_n, my_tmpl_dir)
if node_type == "tagNode":
- props["tag"] = "True"
-
+ if debug:
+ print("Processing tag node {}".format(name))
- with open(os.path.join(make_path(my_tmpl_dir), "node.def"), "w") as f:
- f.write(make_node_def(props, command))
+ os.makedirs(make_path(my_tmpl_dir), exist_ok=True)
+ with open(os.path.join(make_path(my_tmpl_dir), "node.def"), "w") as f:
+ f.write('help: {0}\0'.format(props['help']))
- if node_type == "node":
- inner_nodes = children.iterfind("*")
- for inner_n in inner_nodes:
- process_node(inner_n, my_tmpl_dir)
- if node_type == "tagNode":
my_tmpl_dir.append("node.tag")
- if debug:
- print("Created path for the tagNode:", end="")
os.makedirs(make_path(my_tmpl_dir), exist_ok=True)
- inner_nodes = children.iterfind("*")
- for inner_n in inner_nodes:
- process_node(inner_n, my_tmpl_dir)
+ if debug:
+ print("Created path for the tagNode: {}".format(make_path(my_tmpl_dir)), end="")
+
+ with open(os.path.join(make_path(my_tmpl_dir), "node.def"), "w") as f:
+ f.write(make_node_def(props, command))
+
+ if children is not None:
+ inner_nodes = children.iterfind("*")
+ for inner_n in inner_nodes:
+ process_node(inner_n, my_tmpl_dir)
else:
# This is a leaf node
- pass
+ if debug:
+ print("Processing leaf node {}".format(name))
+
+ with open(os.path.join(make_path(my_tmpl_dir), "node.def"), "w") as f:
+ f.write(make_node_def(props, command))
root = xml.getroot()
diff --git a/scripts/build-command-templates b/scripts/build-command-templates
index d1871c1c8..9f51c00cd 100755
--- a/scripts/build-command-templates
+++ b/scripts/build-command-templates
@@ -49,6 +49,8 @@ schema_file = args.SCHEMA_FILE
output_dir = args.OUTPUT_DIR
debug = args.debug
+debug = True
+
## Load and validate the inputs
try:
@@ -114,27 +116,44 @@ def get_properties(p):
except:
pass
- vce = p.findall("constraint")
+ vce = p.find("constraint")
vc = []
- for v in vce:
- if v.find("regex") is not None:
- vc.append("pattern $VAR(@) \"{0}\"; {1}".format(v.find("regex").text, error_msg))
- else:
- validator = v.find("validator")
- v_name = validator.get("name")
+
+ # The old backend doesn't support multiple validators in OR mode
+ # so we emulate it
+
+ regex_elements = vce.findall("regex")
+ regexes = []
+ if regex_elements is not None:
+ regexes = list(map(lambda e: e.text, regex_elements))
+
+ validator_elements = vce.findall("validator")
+ validators = []
+ if validator_elements is not None:
+ for v in validator_elements:
+ v_name = os.path.join(validator_dir, v.get("name"))
# XXX: lxml returns None for empty arguments
v_argument = None
try:
- v_argument = validator.get("argument")
+ v_argument = v.get("argument")
except:
pass
if v_argument is None:
v_argument = ""
- vc.append("exec {0}/{1} {2} $VAR(@); {3}".format(validator_dir, v_name, v_argument, error_msg))
- props["constraints"] = vc
- except:
- props["constraints"] = []
+
+ validators.append("{0} {1}".format(v_name, v_argument))
+
+
+ regex_args = " ".join(map(lambda s: "--regex \\\'{0}\\\'".format(s), regexes))
+ validator_args = " ".join(map(lambda s: "--exec \\\"{0}\\\"".format(s), validators))
+ validator_script = '${vyos_libexecdir}/validate-value.py'
+ validator_string = "exec \"{0} {1} {2} --value \\\'$VAR(@)\\\'\"; \"{3}\"".format(validator_script, regex_args, validator_args, error_msg)
+
+ props["constraint"] = validator_string
+ except Exception as exn:
+ print(exn)
+ pass
# Get the completion help strings
try:
@@ -165,16 +184,14 @@ def get_properties(p):
except:
pass
- # Get type
- try:
- props["type"] = p.find("type").text
- except:
- pass
-
# Get "multi"
if p.find("multi") is not None:
props["multi"] = True
+ # Get "valueless"
+ if p.find("valueless") is not None:
+ props["valueless"] = True
+
return props
def make_node_def(props):
@@ -190,6 +207,7 @@ def make_node_def(props):
node_def += "multi:\n"
if "type" in props:
+ # Will always be txt in practice if it's set
node_def += "type: {0}\n".format(props["type"])
if "priority" in props:
@@ -205,9 +223,8 @@ def make_node_def(props):
if "comp_help" in props:
node_def += "allowed: {0}\n".format(props["comp_help"])
- if "constraints" in props:
- for c in props["constraints"]:
- node_def += "syntax:expression: {0}\n".format(c)
+ if "constraint" in props:
+ node_def += "syntax:expression: {0}\n".format(props["constraint"])
if "owner" in props:
node_def += "end: sudo sh -c \"{0}\"\n".format(props["owner"])
@@ -238,8 +255,10 @@ def process_node(n, tmpl_dir):
if owner:
props["owner"] = owner
# Type should not be set for non-tag, non-leaf nodes
+ # For non-valueless leaf nodes, set the type to txt: to make them have some type,
+ # actual value validation is handled by constraints translated to syntax:expression:
if node_type != "node":
- if "type" not in props.keys():
+ if "valueless" not in props.keys():
props["type"] = "txt"
if node_type == "tagNode":
props["tag"] = "True"
diff --git a/src/conf-mode/vyos-config-bcast-relay.py b/src/conf-mode/vyos-config-bcast-relay.py
index 3107dce43..785690d9c 100755
--- a/src/conf-mode/vyos-config-bcast-relay.py
+++ b/src/conf-mode/vyos-config-bcast-relay.py
@@ -23,7 +23,7 @@ import time
import subprocess
from vyos.config import Config
-from vyos.util import ConfigError
+from vyos import ConfigError
config_file = r'/etc/default/udp-broadcast-relay'
@@ -41,9 +41,9 @@ def get_config():
# split the interface name listing and form a list
if conf.exists("{0} interface".format(id)):
+ intfs_names = []
intfs_names = conf.return_values("{0} interface".format(id))
- intfs_names=intfs_names.replace("'", "")
- intfs_names=intfs_names.split()
+
for name in intfs_names:
interface_list.append(name)
@@ -90,7 +90,7 @@ def generate(relays):
for relay in relays:
file = config_file + str(relay["id"])
interfaces = ' '.join(str(intf) for intf in relay["interfaces"])
- config_args = 'DAEMON_ARGS="{0} {1} {2}"\n'.format(relay["id"], relay["port"], interfaces)
+ config_args = 'DAEMON_ARGS="{0} {1}"\n'.format(relay["port"], interfaces)
f = open(file, 'w')
f.write(config_header)
diff --git a/src/conf-mode/vyos-config-dns-forwarding.py b/src/conf-mode/vyos-config-dns-forwarding.py
new file mode 100755
index 000000000..be48cde60
--- /dev/null
+++ b/src/conf-mode/vyos-config-dns-forwarding.py
@@ -0,0 +1,213 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2018 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+import sys
+import os
+
+import netifaces
+import jinja2
+
+from vyos.config import Config
+from vyos import ConfigError
+
+config_file = r'/etc/powerdns/recursor.conf'
+
+# XXX: pdns recursor doesn't like whitespace near entry separators,
+# especially in the semicolon-separated lists of name servers.
+# Please be careful if you edit the template.
+config_tmpl = """
+### Autogenerated by vyos-config-dns-forwarding.py ###
+
+# Non-configurable defaults
+daemon=yes
+threads=1
+allow-from=0.0.0.0/0
+log-common-errors=yes
+
+# cache-size
+max-cache-entries={{ cache_size }}
+
+# ignore-hosts-file
+export-etc-hosts={{ export_hosts_file }}
+
+# listen-on
+local-address={{ listen_on | join(',') }}
+
+# domain ... server ...
+{% if domains -%}
+
+forward-zones={% for d in domains %}
+{{ d.name }}={{ d.servers | join(";") }}
+{%- if loop.first %}, {% endif %}
+{% endfor %}
+
+{% endif %}
+
+# name-server
+forward-zones-recurse=.={{ name_servers | join(';') }}
+
+"""
+
+default_config_data = {
+ 'cache_size' : 10000,
+ 'export_hosts_file': 'yes',
+ 'listen_on': [],
+ 'interfaces': [],
+ 'name_servers': [],
+ 'domains': []
+}
+
+
+# borrowed from: https://github.com/donjajo/py-world/blob/master/resolvconfReader.py, THX!
+def get_resolvers(file):
+ resolvers = []
+ try:
+ with open(file, 'r') as resolvconf:
+ for line in resolvconf.readlines():
+ line = line.split('#',1)[0];
+ line = line.rstrip();
+ if 'nameserver' in line:
+ resolvers.append(line.split()[1])
+ return resolvers
+ except IOError:
+ return []
+
+def get_config():
+ dns = default_config_data
+ conf = Config()
+ if not conf.exists('service dns forwarding'):
+ return None
+ else:
+ conf.set_level('service dns forwarding')
+
+ if conf.exists('cache-size'):
+ cache_size = conf.return_value('cache-size')
+ dns['cache_size'] = cache_size
+
+ if conf.exists('domain'):
+ for node in conf.list_nodes('domain'):
+ server = conf.return_values("domain {0} server".format(node))
+ domain = {
+ "name": node,
+ "servers": server
+ }
+ dns['domains'].append(domain)
+
+ if conf.exists('ignore-hosts-file'):
+ dns.setdefault('export_hosts_file', "no")
+
+ if conf.exists('name-server'):
+ name_servers = conf.return_values('name-server')
+ dns['name_servers'] = dns['name_servers'] + name_servers
+
+ if conf.exists('system'):
+ conf.set_level('system')
+ system_name_servers = []
+ system_name_servers = conf.return_values('name-server')
+ if not system_name_servers:
+ print("DNS forwarding warning: No name-servers set under 'system name-server'\n")
+ else:
+ dns['name_servers'] = dns['name_servers'] + system_name_servers
+ conf.set_level('service dns forwarding')
+
+ ## Hacks and tricks
+
+ # The old VyOS syntax that comes from dnsmasq was "listen-on $interface".
+ # pdns wants addresses instead, so we emulate it by looking up all addresses
+ # of a given interface and writing them to the config
+ if conf.exists('listen-on'):
+ interfaces = conf.return_values('listen-on')
+
+ listen4 = []
+ listen6 = []
+ for interface in interfaces:
+ addrs = netifaces.ifaddresses(interface)
+ for ip4 in addrs[netifaces.AF_INET]:
+ listen4.append(ip4['addr'])
+
+ for ip6 in addrs[netifaces.AF_INET6]:
+ listen6.append(ip6['addr'])
+
+ dns['listen_on'] = listen4 + listen6
+
+ # Save interfaces in the dict for the reference
+ dns['interfaces'] = interfaces
+
+ # Add name servers received from DHCP
+ if conf.exists('dhcp'):
+ interfaces = []
+ interfaces = conf.return_values('dhcp')
+ for interface in interfaces:
+ dhcp_resolvers = get_resolvers("/etc/resolv.conf.dhclient-new-{0}".format(interface))
+ if dhcp_resolvers:
+ dns['name_servers'] = dns['name_servers'] + dhcp_resolvers
+
+ return dns
+
+def verify(dns):
+ # bail out early - looks like removal from running config
+ if dns is None:
+ return None
+
+ if not dns['interfaces']:
+ raise ConfigError('Error: DNS forwarding requires a configured listen interface!')
+
+ for interface in dns['interfaces']:
+ try:
+ netifaces.ifaddresses(interface)[netifaces.AF_INET]
+ except KeyError as e:
+ raise ConfigError('Error: Interface {0} has no IP address assigned'.format(interface))
+
+ if dns['domains']:
+ for domain in dns['domains']:
+ if not domain['servers']:
+ raise ConfigError('Error: No server configured for domain {0}'.format(domain['name']))
+
+ return None
+
+def generate(dns):
+ # bail out early - looks like removal from running config
+ if dns is None:
+ return None
+
+ tmpl = jinja2.Template(config_tmpl, trim_blocks=True)
+
+ config_text = tmpl.render(dns)
+ with open(config_file, 'w') as f:
+ f.write(config_text)
+ return None
+
+def apply(dns):
+ if dns is not None:
+ os.system("systemctl restart pdns-recursor")
+ else:
+ # DNS forwarding is removed in the commit
+ os.system("systemctl stop pdns-recursor")
+ os.unlink(config_file)
+
+ return None
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ sys.exit(1)
diff --git a/src/conf-mode/vyos-config-mdns-repeater.py b/src/conf-mode/vyos-config-mdns-repeater.py
index 04a17c126..e648fd64f 100755
--- a/src/conf-mode/vyos-config-mdns-repeater.py
+++ b/src/conf-mode/vyos-config-mdns-repeater.py
@@ -22,7 +22,7 @@ import netifaces
import time
from vyos.config import Config
-from vyos.util import ConfigError
+from vyos import ConfigError
config_file = r'/etc/default/mdns-repeater'
@@ -35,9 +35,8 @@ def get_config():
return interface_list
if conf.exists('interface'):
+ intfs_names = []
intfs_names = conf.return_values('interface')
- intfs_names=intfs_names.replace("'", "")
- intfs_names=intfs_names.split()
for name in intfs_names:
interface_list.append(name)
diff --git a/src/conf-mode/vyos-config-ntp.py b/src/conf-mode/vyos-config-ntp.py
new file mode 100755
index 000000000..8be12e44e
--- /dev/null
+++ b/src/conf-mode/vyos-config-ntp.py
@@ -0,0 +1,161 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2018 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+import sys
+import os
+
+import jinja2
+import ipaddress
+
+from vyos.config import Config
+from vyos import ConfigError
+
+config_file = r'/etc/ntp.conf'
+
+# Please be careful if you edit the template.
+config_tmpl = """
+### Autogenerated by vyos-config-ntp.py ###
+
+#
+# Non-configurable defaults
+#
+driftfile /var/lib/ntp/ntp.drift
+# By default, only allow ntpd to query time sources, ignore any incoming requests
+restrict default ignore
+# Local users have unrestricted access, allowing reconfiguration via ntpdc
+restrict 127.0.0.1
+restrict -6 ::1
+
+
+#
+# Configurable section
+#
+
+{% if servers -%}
+{% for s in servers -%}
+# Server configuration for: {{ s.name }}
+server {{ s.name }} iburst {{ s.options | join(" ") }}
+
+{% endfor -%}
+{% endif %}
+
+{% if allowed_networks -%}
+{% for n in allowed_networks -%}
+# Client configuration for network: {{ n.network }}
+restrict {{ n.address }} mask {{ n.netmask }} nomodify notrap nopeer
+
+{% endfor -%}
+{% endif %}
+
+"""
+
+default_config_data = {
+ 'servers': [],
+ 'allowed_networks': []
+}
+
+def get_config():
+ ntp = default_config_data
+ conf = Config()
+ if not conf.exists('system ntp'):
+ return None
+ else:
+ conf.set_level('system ntp')
+
+ if conf.exists('allow-clients address'):
+ networks = conf.return_values('allow-clients address')
+ for n in networks:
+ addr = ipaddress.ip_network(n)
+ net = {
+ "network" : n,
+ "address" : addr.network_address,
+ "netmask" : addr.netmask
+ }
+
+ ntp['allowed_networks'].append(net)
+
+ if conf.exists('server'):
+ for node in conf.list_nodes('server'):
+ options = []
+ server = {
+ "name": node,
+ "options": []
+ }
+ if conf.exists('server {0} dynamic'.format(node)):
+ options.append('dynamic')
+ if conf.exists('server {0} noselect'.format(node)):
+ options.append('noselect')
+ if conf.exists('server {0} preempt'.format(node)):
+ options.append('preempt')
+ if conf.exists('server {0} prefer'.format(node)):
+ options.append('prefer')
+
+ server['options'] = options
+ ntp['servers'].append(server)
+
+ return ntp
+
+def verify(ntp):
+ # bail out early - looks like removal from running config
+ if ntp is None:
+ return None
+
+ # Configuring allowed clients without a server makes no sense
+ if len(ntp['allowed_networks']) and not len(ntp['servers']):
+ raise ConfigError('NTP server not configured')
+
+ for n in ntp['allowed_networks']:
+ try:
+ addr = ipaddress.ip_network( n['network'] )
+ break
+ except ValueError:
+ raise ConfigError("{0} does not appear to be a valid IPv4 or IPv6 network, check host bits!".format(n['network']))
+
+ return None
+
+def generate(ntp):
+ # bail out early - looks like removal from running config
+ if ntp is None:
+ return None
+
+ tmpl = jinja2.Template(config_tmpl)
+ config_text = tmpl.render(ntp)
+ with open(config_file, 'w') as f:
+ f.write(config_text)
+
+ return None
+
+def apply(ntp):
+ if ntp is not None:
+ os.system('sudo /usr/sbin/invoke-rc.d ntp force-reload')
+ else:
+ # NTP suuport is removed in the commit
+ os.system('sudo /usr/sbin/invoke-rc.d ntp stop')
+ os.unlink(config_file)
+
+ return None
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ sys.exit(1)
diff --git a/src/conf-mode/vyos-config-ssh.py b/src/conf-mode/vyos-config-ssh.py
new file mode 100755
index 000000000..a4857bba9
--- /dev/null
+++ b/src/conf-mode/vyos-config-ssh.py
@@ -0,0 +1,249 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2018 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+import sys
+import os
+
+import jinja2
+
+from vyos.config import Config
+from vyos import ConfigError
+
+config_file = r'/etc/ssh/sshd_config'
+
+# Please be careful if you edit the template.
+config_tmpl = """
+
+### Autogenerated by vyos-config-ssh.py ###
+
+# Non-configurable defaults
+Protocol 2
+HostKey /etc/ssh/ssh_host_rsa_key
+HostKey /etc/ssh/ssh_host_dsa_key
+HostKey /etc/ssh/ssh_host_ecdsa_key
+HostKey /etc/ssh/ssh_host_ed25519_key
+UsePrivilegeSeparation yes
+KeyRegenerationInterval 3600
+ServerKeyBits 1024
+SyslogFacility AUTH
+LoginGraceTime 120
+StrictModes yes
+RSAAuthentication yes
+PubkeyAuthentication yes
+IgnoreRhosts yes
+RhostsRSAAuthentication no
+HostbasedAuthentication no
+PermitEmptyPasswords no
+ChallengeResponseAuthentication no
+X11Forwarding yes
+X11DisplayOffset 10
+PrintMotd no
+PrintLastLog yes
+TCPKeepAlive yes
+Banner /etc/issue.net
+Subsystem sftp /usr/lib/openssh/sftp-server
+UsePAM yes
+HostKey /etc/ssh/ssh_host_key
+PermitRootLogin no
+
+# Specifies whether sshd should look up the remote host name,
+# and to check that the resolved host name for the remote IP
+# address maps back to the very same IP address.
+UseDNS {{ host_validation }}
+
+# Specifies the port number that sshd listens on. The default is 22.
+# Multiple options of this type are permitted.
+Port {{ port }}
+
+# Gives the verbosity level that is used when logging messages from sshd
+LogLevel {{ log_level }}
+
+# Specifies whether password authentication is allowed
+PasswordAuthentication {{ password_authentication }}
+
+{% if listen_on -%}
+# Specifies the local addresses sshd should listen on
+{% for a in listen_on -%}
+ListenAddress {{ a }}
+{% endfor -%}
+{% endif %}
+
+{% if ciphers -%}
+# Specifies the ciphers allowed. Multiple ciphers must be comma-separated.
+#
+# NOTE: As of now, there is no 'multi' node for 'ciphers', thus we have only one :/
+Ciphers {{ ciphers | join(",") }}
+{% endif %}
+
+{% if mac -%}
+# Specifies the available MAC (message authentication code) algorithms. The MAC
+# algorithm is used for data integrity protection. Multiple algorithms must be
+# comma-separated.
+#
+# NOTE: As of now, there is no 'multi' node for 'mac', thus we have only one :/
+MACs {{ mac | join(",") }}
+{% endif %}
+
+{% if key_exchange -%}
+# Specifies the available KEX (Key Exchange) algorithms. Multiple algorithms must
+# be comma-separated.
+#
+# NOTE: As of now, there is no 'multi' node for 'key-exchange', thus we have only one :/
+KexAlgorithms {{ key_exchange | join(",") }}
+{% endif %}
+
+{% if allow_users -%}
+# This keyword can be followed by a list of user name patterns, separated by spaces.
+# If specified, login is allowed only for user names that match one of the patterns.
+# Only user names are valid, a numerical user ID is not recognized.
+AllowUsers {{ allow_users | join(" ") }}
+{% endif %}
+
+{% if allow_groups -%}
+# This keyword can be followed by a list of group name patterns, separated by spaces.
+# If specified, login is allowed only for users whose primary group or supplementary
+# group list matches one of the patterns. Only group names are valid, a numerical group
+# ID is not recognized.
+AllowGroups {{ allow_groups | join(" ") }}
+{% endif %}
+
+{% if deny_users -%}
+# This keyword can be followed by a list of user name patterns, separated by spaces.
+# Login is disallowed for user names that match one of the patterns. Only user names
+# are valid, a numerical user ID is not recognized.
+DenyUsers {{ deny_users | join(" ") }}
+{% endif %}
+
+{% if deny_groups -%}
+# This keyword can be followed by a list of group name patterns, separated by spaces.
+# Login is disallowed for users whose primary group or supplementary group list matches
+# one of the patterns. Only group names are valid, a numerical group ID is not recognized.
+DenyGroups {{ deny_groups | join(" ") }}
+{% endif %}
+"""
+
+default_config_data = {
+ 'port' : '22',
+ 'log_level': 'INFO',
+ 'password_authentication': 'yes',
+ 'host_validation': 'yes'
+}
+
+def get_config():
+ ssh = default_config_data
+ conf = Config()
+ if not conf.exists('service ssh'):
+ return None
+ else:
+ conf.set_level('service ssh')
+
+ if conf.exists('access-control allow user'):
+ allow_users = conf.return_values('access-control allow user')
+ ssh.setdefault('allow_users', allow_users)
+
+ if conf.exists('access-control allow group'):
+ allow_groups = conf.return_values('access-control allow group')
+ ssh.setdefault('allow_groups', allow_groups)
+
+ if conf.exists('access-control deny user'):
+ deny_users = conf.return_values('access-control deny user')
+ ssh.setdefault('deny_users', deny_users)
+
+ if conf.exists('access-control deny group'):
+ deny_groups = conf.return_values('access-control deny group')
+ ssh.setdefault('deny_groups', deny_groups)
+
+ if conf.exists('ciphers'):
+ ciphers = conf.return_values('ciphers')
+ ssh.setdefault('ciphers', ciphers)
+
+ if conf.exists('disable-host-validation'):
+ ssh['host_validation'] = 'no'
+
+ if conf.exists('disable-password-authentication'):
+ ssh['password_authentication'] = 'no'
+
+ if conf.exists('key-exchange'):
+ kex = conf.return_values('key-exchange')
+ ssh.setdefault('key_exchange', kex)
+
+ if conf.exists('listen-address'):
+ # We can listen on both IPv4 and IPv6 addresses
+ # Maybe there could be a check in the future if the configured IP address
+ # is configured on this system at all?
+ addresses = conf.return_values('listen-address')
+ listen = []
+
+ for addr in addresses:
+ listen.append(addr)
+
+ ssh.setdefault('listen_on', listen)
+
+ if conf.exists('loglevel'):
+ ssh['log_level'] = conf.return_value('loglevel')
+
+ if conf.exists('mac'):
+ mac = conf.return_values('mac')
+ ssh.setdefault('mac', mac)
+
+ if conf.exists('port'):
+ port = conf.return_value('port')
+ ssh.setdefault('port', port)
+
+ return ssh
+
+def verify(ssh):
+ if ssh is None:
+ return None
+
+ if 'loglevel' in ssh.keys():
+ allowed_loglevel = 'QUIET, FATAL, ERROR, INFO, VERBOSE'
+ if not ssh['loglevel'] in allowed_loglevel:
+ raise ConfigError('loglevel must be one of "{0}"\n'.format(allowed_loglevel))
+
+ return None
+
+def generate(ssh):
+ if ssh is None:
+ return None
+
+ tmpl = jinja2.Template(config_tmpl)
+ config_text = tmpl.render(ssh)
+ with open(config_file, 'w') as f:
+ f.write(config_text)
+ return None
+
+def apply(ssh):
+ if ssh is not None and 'port' in ssh.keys():
+ os.system("sudo systemctl restart ssh")
+ else:
+ # SSH access is removed in the commit
+ os.system("sudo systemctl stop ssh")
+ os.unlink(config_file)
+
+ return None
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ sys.exit(1)
diff --git a/src/conf-mode/vyos-update-crontab.py b/src/conf-mode/vyos-update-crontab.py
index 2d15de8ea..c19b88007 100755
--- a/src/conf-mode/vyos-update-crontab.py
+++ b/src/conf-mode/vyos-update-crontab.py
@@ -21,7 +21,7 @@ import re
import sys
from vyos.config import Config
-from vyos.util import ConfigError
+from vyos import ConfigError
crontab_file = "/etc/cron.d/vyos-crontab"
diff --git a/src/helpers/validate-value.py b/src/helpers/validate-value.py
new file mode 100755
index 000000000..d702739b5
--- /dev/null
+++ b/src/helpers/validate-value.py
@@ -0,0 +1,43 @@
+#!/usr/bin/env python3
+
+import re
+import os
+import sys
+import argparse
+
+parser = argparse.ArgumentParser()
+parser.add_argument('--regex', action='append')
+parser.add_argument('--exec', action='append')
+parser.add_argument('--value', action='store')
+
+args = parser.parse_args()
+
+debug = False
+
+# Multiple arguments work like logical OR
+
+try:
+ for r in args.regex:
+ if re.fullmatch(r, args.value):
+ sys.exit(0)
+except Exception as exn:
+ if debug:
+ print(exn)
+ else:
+ pass
+
+try:
+ for cmd in args.exec:
+ cmd = "{0} {1}".format(cmd, args.value)
+ if debug:
+ print(cmd)
+ res = os.system(cmd)
+ if res == 0:
+ sys.exit(0)
+except Exception as exn:
+ if debug:
+ print(exn)
+ else:
+ pass
+
+sys.exit(1)
diff --git a/src/op-mode/vyos-dns-forwarding-statistics.py b/src/op-mode/vyos-dns-forwarding-statistics.py
new file mode 100755
index 000000000..3d1e30aee
--- /dev/null
+++ b/src/op-mode/vyos-dns-forwarding-statistics.py
@@ -0,0 +1,24 @@
+#!/usr/bin/env python3
+
+import subprocess
+import jinja2
+
+PDNS_CMD='/usr/bin/rec_control'
+
+OUT_TMPL_SRC = """
+DNS forwarding statistics:
+
+Cache entries: {{ cache_entries -}}
+Cache size: {{ cache_size }} kbytes
+
+"""
+
+
+if __name__ == '__main__':
+ data = {}
+
+ data['cache_entries'] = subprocess.check_output([PDNS_CMD, 'get cache-entries']).decode()
+ data['cache_size'] = "{0:.2f}".format( int(subprocess.check_output([PDNS_CMD, 'get cache-bytes']).decode()) / 1024 )
+
+ tmpl = jinja2.Template(OUT_TMPL_SRC)
+ print(tmpl.render(data))
diff --git a/src/op-mode/vyos-list-dumpable-interfaces.py b/src/op-mode/vyos-list-dumpable-interfaces.py
new file mode 100755
index 000000000..53ee89633
--- /dev/null
+++ b/src/op-mode/vyos-list-dumpable-interfaces.py
@@ -0,0 +1,14 @@
+#!/usr/bin/env python3
+
+# Extract the list of interfaces available for traffic dumps from tcpdump -D
+
+import re
+import subprocess
+
+if __name__ == '__main__':
+ out = subprocess.check_output(['/usr/sbin/tcpdump', '-D']).decode().strip()
+ out = out.split("\n")
+
+ intfs = " ".join(map(lambda s: re.search(r'\d+\.(\S+)\s', s).group(1), out))
+
+ print(intfs)
diff --git a/src/op-mode/vyos-list-interfaces.py b/src/op-mode/vyos-list-interfaces.py
new file mode 100755
index 000000000..59c9dffad
--- /dev/null
+++ b/src/op-mode/vyos-list-interfaces.py
@@ -0,0 +1,8 @@
+#!/usr/bin/env python3
+
+import netifaces
+
+if __name__ == '__main__':
+ interfaces = netifaces.interfaces()
+
+ print(" ".join(interfaces))
diff --git a/src/op-mode/vyos-restart-dns-forwarding.sh b/src/op-mode/vyos-restart-dns-forwarding.sh
new file mode 100755
index 000000000..12106fcc1
--- /dev/null
+++ b/src/op-mode/vyos-restart-dns-forwarding.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+if cli-shell-api exists service dns forwarding; then
+ echo "Restarting the DNS forwarding service"
+ systemctl restart pdns-recursor
+else
+ echo "DNS forwarding is not configured"
+fi
diff --git a/src/op-mode/vyos-show-version.py b/src/op-mode/vyos-show-version.py
new file mode 100755
index 000000000..ce3b3b54f
--- /dev/null
+++ b/src/op-mode/vyos-show-version.py
@@ -0,0 +1,123 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2016 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+# File: vyos-show-version
+# Purpose:
+# Displays image version and system information.
+# Used by the "run show version" command.
+
+
+import os
+import sys
+import subprocess
+import argparse
+import json
+
+import pystache
+
+import vyos.version
+import vyos.limericks
+
+
+parser = argparse.ArgumentParser()
+parser.add_argument("-a", "--all", action="store_true", help="Include individual package versions")
+parser.add_argument("-f", "--funny", action="store_true", help="Add something funny to the output")
+parser.add_argument("-j", "--json", action="store_true", help="Produce JSON output")
+
+def read_file(name):
+ try:
+ with open (name, "r") as f:
+ data = f.read()
+ return data.strip()
+ except:
+ # This works since we only read /sys/class/* stuff
+ # with this function
+ return "Unknown"
+
+version_output_tmpl = """
+Version: VyOS {{version}}
+Built by: {{built_by}}
+Built on: {{built_on}}
+Build ID: {{build_id}}
+
+Architecture: {{system_arch}}
+Boot via: {{boot_via}}
+System type: {{system_type}}
+
+Hardware vendor: {{hardware_vendor}}
+Hardware model: {{hardware_model}}
+Hardware S/N: {{hardware_serial}}
+Hardware UUID: {{hardware_uuid}}
+
+Copyright: VyOS maintainers and contributors
+
+"""
+
+if __name__ == '__main__':
+ args = parser.parse_args()
+
+ version_data = vyos.version.get_version_data()
+
+ # Get system architecture (well, kernel architecture rather)
+ version_data['system_arch'] = subprocess.check_output('uname -m', shell=True).decode().strip()
+
+
+ # Get hypervisor name, if any
+ system_type = "bare metal"
+ try:
+ hypervisor = subprocess.check_output('hvinfo 2>/dev/null', shell=True).decode().strip()
+ system_type = "{0} guest".format(hypervisor)
+ except subprocess.CalledProcessError:
+ # hvinfo returns 1 if it cannot detect any hypervisor
+ pass
+ version_data['system_type'] = system_type
+
+
+ # Get boot type, it can be livecd, installed image, or, possible, a system installed
+ # via legacy "install system" mechanism
+ # In installed images, the squashfs image file is named after its image version,
+ # while on livecd it's just "filesystem.squashfs", that's how we tell a livecd boot
+ # from an installed image
+ boot_via = "installed image"
+ if subprocess.call(""" grep -e '^overlay.*/filesystem.squashfs' /proc/mounts >/dev/null""", shell=True) == 0:
+ boot_via = "livecd"
+ elif subprocess.call(""" grep '^overlay /' /proc/mounts >/dev/null """, shell=True) != 0:
+ boot_via = "legacy non-image installation"
+ version_data['boot_via'] = boot_via
+
+
+ # Get hardware details from DMI
+ version_data['hardware_vendor'] = read_file('/sys/class/dmi/id/sys_vendor')
+ version_data['hardware_model'] = read_file('/sys/class/dmi/id/product_name')
+
+ # These two assume script is run as root, normal users can't access those files
+ version_data['hardware_serial'] = read_file('/sys/class/dmi/id/subsystem/id/product_serial')
+ version_data['hardware_uuid'] = read_file('/sys/class/dmi/id/subsystem/id/product_uuid')
+
+
+ if args.json:
+ print(json.dumps(version_data))
+ sys.exit(0)
+ else:
+ output = pystache.render(version_output_tmpl, version_data).strip()
+ print(output)
+
+ if args.all:
+ print("Package versions:")
+ os.system("dpkg -l")
+
+ if args.funny:
+ print(vyos.limericks.get_random())
diff --git a/src/validators/interface-address b/src/validators/interface-address
new file mode 100755
index 000000000..4c203956b
--- /dev/null
+++ b/src/validators/interface-address
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+ipaddrcheck --is-ipv4-host $1 || ipaddrcheck --is-ipv6-host $1
diff --git a/src/validators/ip-address b/src/validators/ip-address
new file mode 100755
index 000000000..51fb72c85
--- /dev/null
+++ b/src/validators/ip-address
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+ipaddrcheck --is-any-single $1
diff --git a/src/validators/ip-host b/src/validators/ip-host
new file mode 100755
index 000000000..f2906e8cf
--- /dev/null
+++ b/src/validators/ip-host
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+ipaddrcheck --is-any-host $1
diff --git a/src/validators/ip-prefix b/src/validators/ip-prefix
new file mode 100755
index 000000000..e58aad395
--- /dev/null
+++ b/src/validators/ip-prefix
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+ipaddrcheck --is-any-net $1
diff --git a/src/validators/ipv4-address b/src/validators/ipv4-address
new file mode 100755
index 000000000..872a7645a
--- /dev/null
+++ b/src/validators/ipv4-address
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+ipaddrcheck --is-ipv4-single $1
diff --git a/src/validators/ipv4-host b/src/validators/ipv4-host
new file mode 100755
index 000000000..f42feffa4
--- /dev/null
+++ b/src/validators/ipv4-host
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+ipaddrcheck --is-ipv4-host $1
diff --git a/src/validators/ipv4-prefix b/src/validators/ipv4-prefix
new file mode 100755
index 000000000..8ec8a2c45
--- /dev/null
+++ b/src/validators/ipv4-prefix
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+ipaddrcheck --is-ipv4-net $1
diff --git a/src/validators/ipv6-address b/src/validators/ipv6-address
new file mode 100755
index 000000000..e5d68d756
--- /dev/null
+++ b/src/validators/ipv6-address
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+ipaddrcheck --is-ipv6-single $1
diff --git a/src/validators/ipv6-host b/src/validators/ipv6-host
new file mode 100755
index 000000000..f7a745077
--- /dev/null
+++ b/src/validators/ipv6-host
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+ipaddrcheck --is-ipv6-host $1
diff --git a/src/validators/ipv6-prefix b/src/validators/ipv6-prefix
new file mode 100755
index 000000000..e43616350
--- /dev/null
+++ b/src/validators/ipv6-prefix
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+ipaddrcheck --is-ipv6-net $1