diff options
99 files changed, 2173 insertions, 2087 deletions
diff --git a/data/templates/dhcp-relay/config.tmpl b/data/templates/dhcp-relay/config.tmpl index 7203ae9fb..b223807cf 100644 --- a/data/templates/dhcp-relay/config.tmpl +++ b/data/templates/dhcp-relay/config.tmpl @@ -1,17 +1,4 @@ ### Autogenerated by dhcp_relay.py ### -# Defaults for isc-dhcp-relay initscript -# sourced by /etc/init.d/isc-dhcp-relay - -# -# This is a POSIX shell fragment -# - -# What servers should the DHCP relay forward requests to? -SERVERS="{{ server | join(' ') }}" - -# On what interfaces should the DHCP relay (dhrelay) serve DHCP requests? -INTERFACES="{{ interface | join(' ') }}" - -# Additional options that are passed to the DHCP relay daemon? -OPTIONS="-4 {{ options | join(' ') }}" +# Defaults for isc-dhcp-relay6.service +OPTIONS="{{ options | join(' ') }} -i {{ interface | join(' -i ') }} {{ server | join(' ') }}" diff --git a/data/templates/dhcp-server/daemon.tmpl b/data/templates/dhcp-server/daemon.tmpl deleted file mode 100644 index f88032d38..000000000 --- a/data/templates/dhcp-server/daemon.tmpl +++ /dev/null @@ -1,8 +0,0 @@ -### Autogenerated by dhcp_server.py ### - -# sourced by /etc/init.d/isc-dhcpv4-server - -DHCPD_CONF={{ config_file }} -DHCPD_PID={{ pid_file }} -OPTIONS="-4 -lf {{ lease_file }}" -INTERFACES="" diff --git a/data/templates/dhcpv6-relay/config.tmpl b/data/templates/dhcpv6-relay/config.tmpl index 28f7a1a58..55035ae6c 100644 --- a/data/templates/dhcpv6-relay/config.tmpl +++ b/data/templates/dhcpv6-relay/config.tmpl @@ -1,4 +1,4 @@ ### Autogenerated by dhcpv6_relay.py ### -# Defaults for isc-dhcpv6-relay initscript sourced by /etc/init.d/isc-dhcpv6-relay -OPTIONS="-6 -l {{ listen_addr | join(' -l ') }} -u {{ upstream_addr | join(' -u ') }} {{ options | join(' ') }}" +# Defaults for isc-dhcp-relay6.service +OPTIONS="-l {{ listen_addr | join(' -l ') }} -u {{ upstream_addr | join(' -u ') }} {{ options | join(' ') }}" diff --git a/data/templates/dhcpv6-server/daemon.tmpl b/data/templates/dhcpv6-server/daemon.tmpl deleted file mode 100644 index a4967e7c3..000000000 --- a/data/templates/dhcpv6-server/daemon.tmpl +++ /dev/null @@ -1,8 +0,0 @@ -### Autogenerated by dhcpv6_server.py ### - -# sourced by /etc/init.d/isc-dhcpv6-server - -DHCPD_CONF={{ config_file }} -DHCPD_PID={{ pid_file }} -OPTIONS="-6 -lf {{ lease_file }}" -INTERFACES="" diff --git a/data/templates/dynamic-dns/ddclient.conf.tmpl b/data/templates/dynamic-dns/ddclient.conf.tmpl index 22cb38f4e..9c7219230 100644 --- a/data/templates/dynamic-dns/ddclient.conf.tmpl +++ b/data/templates/dynamic-dns/ddclient.conf.tmpl @@ -1,10 +1,7 @@ - ### Autogenerated by dynamic_dns.py ### daemon=1m syslog=yes ssl=yes -pid={{ pid_file }} -cache={{ cache_file }} {% for interface in interfaces -%} diff --git a/data/templates/l2tp/chap-secrets.tmpl b/data/templates/l2tp/chap-secrets.tmpl index 0db295fdc..dd00d7bd0 100644 --- a/data/templates/l2tp/chap-secrets.tmpl +++ b/data/templates/l2tp/chap-secrets.tmpl @@ -1,10 +1,10 @@ -# username server password acceptable local IP addresses shaper -{% for user in authentication['local-users'] %} -{% if authentication['local-users'][user]['state'] == 'enabled' %} -{% if authentication['local-users'][user]['upload'] and authentication['local-users'][user]['download'] %} -{{ "%-12s" | format(user) }} * {{ "%-16s" | format(authentication['local-users'][user]['passwd']) }} {{ "%-16s" | format(authentication['local-users'][user]['ip']) }} {{ authentication['local-users'][user]['download'] }} / {{ authentication['local-users'][user]['upload'] }} +# username server password acceptable local IP addresses shaper +{% for user in local_users %} +{% if user.state == 'enabled' %} +{% if user.upload and user.download %} +{{ "%-12s" | format(user.name) }} * {{ "%-16s" | format(user.password) }} {{ "%-16s" | format(user.ip) }} {{ user.download }} / {{ user.upload }} {% else %} -{{ "%-12s" | format(user) }} * {{ "%-16s" | format(authentication['local-users'][user]['passwd']) }} {{ "%-16s" | format(authentication['local-users'][user]['ip']) }} +{{ "%-12s" | format(user.name) }} * {{ "%-16s" | format(user.password) }} {{ "%-16s" | format(user.ip) }} {% endif %} {% endif %} {% endfor %} diff --git a/data/templates/l2tp/l2tp.config.tmpl b/data/templates/l2tp/l2tp.config.tmpl index b8637e256..ba78cadcd 100644 --- a/data/templates/l2tp/l2tp.config.tmpl +++ b/data/templates/l2tp/l2tp.config.tmpl @@ -3,12 +3,14 @@ log_syslog l2tp chap-secrets -{% for proto in authentication['auth_proto']: %} +{% for proto in auth_proto: %} {{proto}} {% endfor%} -{% if authentication['mode'] == 'radius' %} + +{% if auth_mode == 'radius' %} radius {% endif -%} + ippool shaper ipv6pool @@ -23,52 +25,46 @@ syslog=accel-l2tp,daemon copy=1 level=5 -{% if dns %} +{% if dnsv4 %} [dns] -{% if dns[0] %} -dns1={{dns[0]}} -{% endif %} -{% if dns[1] %} -dns2={{dns[1]}} +{% for dns in dnsv4 -%} +dns{{ loop.index }}={{ dns }} +{% endfor -%} {% endif %} -{% endif -%} {% if dnsv6 %} [ipv6-dns] -{% for srv in dnsv6: %} -{{srv}} -{% endfor %} +{% for dns in dnsv6 -%} +{{ dns }} +{% endfor -%} {% endif %} {% if wins %} [wins] -{% if wins[0] %} -wins1={{wins[0]}} -{% endif %} -{% if wins[1] %} -wins2={{wins[1]}} +{% for server in wins -%} +wins{{ loop.index }}={{ server }} +{% endfor -%} {% endif %} -{% endif -%} [l2tp] verbose=1 ifname=l2tp%d -ppp-max-mtu={{mtu}} -mppe={{authentication['mppe']}} +ppp-max-mtu={{ mtu }} +mppe={{ ppp_mppe }} {% if outside_addr %} -bind={{outside_addr}} +bind={{ outside_addr }} {% endif %} {% if lns_shared_secret %} -secret={{lns_shared_secret}} +secret={{ lns_shared_secret }} {% endif %} [client-ip-range] 0.0.0.0/0 -{% if (client_ip_pool) or (client_ip_subnets) %} +{% if client_ip_pool or client_ip_subnets %} [ip-pool] {% if client_ip_pool %} -{{client_ip_pool}} +{{ client_ip_pool }} {% endif -%} {% if client_ip_subnets %} {% for sn in client_ip_subnets %} @@ -77,34 +73,41 @@ secret={{lns_shared_secret}} {% endif %} {% endif %} {% if gateway_address %} -gw-ip-address={{gateway_address}} +gw-ip-address={{ gateway_address }} {% endif %} -{% if authentication['mode'] == 'local' %} +{% if auth_mode == 'local' %} [chap-secrets] -chap-secrets=/etc/accel-ppp/l2tp/chap-secrets -{% if gateway_address %} -gw-ip-address={{gateway_address}} -{% endif %} +chap-secrets={{ chap_secrets_file }} +{% elif auth_mode == 'radius' %} +[radius] +verbose=1 +{% for r in radius_server %} +server={{ r.server }},{{ r.key }},auth-port={{ r.port }},req-limit=0,fail-time={{ r.fail_time }} +{% endfor -%} {% endif %} +acct-timeout={{ radius_acct_tmo }} +timeout={{ radius_timeout }} +max-try={{ radius_max_try }} + +{% if radius_nas_id %} +nas-identifier={{ radius_nas_id }} +{% endif -%} +{% if radius_nas_ip %} +nas-ip-address={{ radius_nas_ip }} +{% endif -%} +{% if radius_source_address %} +bind={{ radius_source_address }} +{% endif -%} + [ppp] verbose=1 check-ip=1 single-session=replace -{% if idle_timeout %} -lcp-echo-timeout={{idle_timeout}} -{% endif %} -{% if ppp_options['lcp-echo-interval'] %} -lcp-echo-interval={{ppp_options['lcp-echo-interval']}} -{% else %} -lcp-echo-interval=30 -{% endif %} -{% if ppp_options['lcp-echo-failure'] %} -lcp-echo-failure={{ppp_options['lcp-echo-failure']}} -{% else %} -lcp-echo-failure=3 -{% endif %} +lcp-echo-timeout={{ ppp_echo_timeout }} +lcp-echo-interval={{ ppp_echo_interval }} +lcp-echo-failure={{ ppp_echo_failure }} {% if ccp_disable %} ccp=0 {% endif %} @@ -112,62 +115,33 @@ ccp=0 ipv6=allow {% endif %} -{% if authentication['mode'] == 'radius' %} -[radius] -{% for rsrv in authentication['radiussrv']: %} -server={{rsrv}},{{authentication['radiussrv'][rsrv]['secret']}},\ -req-limit={{authentication['radiussrv'][rsrv]['req-limit']}},\ -fail-time={{authentication['radiussrv'][rsrv]['fail-time']}} -{% endfor %} -{% if authentication['radiusopt']['timeout'] %} -timeout={{authentication['radiusopt']['timeout']}} -{% endif %} -{% if authentication['radiusopt']['acct-timeout'] %} -acct-timeout={{authentication['radiusopt']['acct-timeout']}} -{% endif %} -{% if authentication['radiusopt']['max-try'] %} -max-try={{authentication['radiusopt']['max-try']}} -{% endif %} -{% if authentication['radiusopt']['nas-id'] %} -nas-identifier={{authentication['radiusopt']['nas-id']}} -{% endif %} -{% if authentication['radius_source_address'] %} -nas-ip-address={{authentication['radius_source_address']}} -{% endif -%} -{% if authentication['radiusopt']['dae-srv'] %} -dae-server={{authentication['radiusopt']['dae-srv']['ip-addr']}}:\ -{{authentication['radiusopt']['dae-srv']['port']}},\ -{{authentication['radiusopt']['dae-srv']['secret']}} -{% endif -%} -gw-ip-address={{gateway_address}} -verbose=1 -{% endif -%} {% if client_ipv6_pool %} [ipv6-pool] -{% for prfx in client_ipv6_pool.prefix: %} -{{prfx}} +{% for p in client_ipv6_pool %} +{{ p.prefix }},{{ p.mask }} {% endfor %} -{% for prfx in client_ipv6_pool.delegate_prefix: %} -delegate={{prfx}} +{% for p in client_ipv6_delegate_prefix %} +delegate={{ p.prefix }},{{ p.mask }} {% endfor %} + {% endif %} -{% if client_ipv6_pool['delegate_prefix'] %} +{% if client_ipv6_delegate_prefix %} [ipv6-dhcp] verbose=1 {% endif %} -{% if authentication['radiusopt']['shaper'] %} +{% if radius_shaper_attr %} [shaper] verbose=1 -attr={{authentication['radiusopt']['shaper']['attr']}} -{% if authentication['radiusopt']['shaper']['vendor'] %} -vendor={{authentication['radiusopt']['shaper']['vendor']}} +attr={{ radius_shaper_attr }} +{% if radius_shaper_vendor %} +vendor={{ radius_shaper_vendor }} {% endif -%} {% endif %} [cli] tcp=127.0.0.1:2004 -sessions-columns=ifname,username,calling-sid,ip,{{ip6_column}}{{ip6_dp_column}}rate-limit,type,comp,state,rx-bytes,tx-bytes,uptime +sessions-columns=ifname,username,calling-sid,ip,{{ ip6_column | join(',') }}{{ ',' if ip6_column }}rate-limit,type,comp,state,rx-bytes,tx-bytes,uptime diff --git a/data/templates/openvpn/server.conf.tmpl b/data/templates/openvpn/server.conf.tmpl index d0af3d2e3..a9dacd36e 100644 --- a/data/templates/openvpn/server.conf.tmpl +++ b/data/templates/openvpn/server.conf.tmpl @@ -9,8 +9,6 @@ {% endif -%} verb 3 -status /opt/vyatta/etc/openvpn/status/{{ intf }}.status 30 -writepid /var/run/openvpn/{{ intf }}.pid user {{ uid }} group {{ gid }} @@ -73,13 +71,18 @@ nobind # {%- if server_topology %} -topology {% if 'point-to-point' in server_topology %}p2p{% else %}subnet{% endif %} +topology {% if server_topology == 'point-to-point' %}p2p{% else %}{{ server_topology }}{% endif %} {%- endif %} {%- if bridge_member %} -server-bridge nogw +mode server +tls-server {%- else %} -server {{ server_subnet }} +server {{ server_subnet }}{% if server_pool_start %} nopool{% endif %} +{%- endif %} + +{%- if server_pool_start %} +ifconfig-pool {{ server_pool_start }} {{ server_pool_stop }}{% if server_pool_netmask %} {{ server_pool_netmask }}{% endif %} {%- endif %} {%- if server_max_conn %} @@ -87,7 +90,7 @@ max-clients {{ server_max_conn }} {%- endif %} {%- if client %} -client-config-dir /opt/vyatta/etc/openvpn/ccd/{{ intf }} +client-config-dir /run/openvpn/ccd/{{ intf }} {%- endif %} {%- if server_reject_unconfigured %} diff --git a/data/templates/sstp/sstp.config.tmpl b/data/templates/sstp/sstp.config.tmpl index 6c09c52ad..acdb6c76b 100644 --- a/data/templates/sstp/sstp.config.tmpl +++ b/data/templates/sstp/sstp.config.tmpl @@ -53,7 +53,7 @@ dns{{ loop.index }}={{ dns }} {% if auth_mode == 'local' %} [chap-secrets] -chap-secrets=/etc/accel-ppp/sstp/chap-secrets +chap-secrets={{ chap_secrets_file }} {% elif auth_mode == 'radius' %} [radius] verbose=1 diff --git a/data/templates/wifi/hostapd.conf.tmpl b/data/templates/wifi/hostapd.conf.tmpl index 031fb6c90..e2fb9ca8f 100644 --- a/data/templates/wifi/hostapd.conf.tmpl +++ b/data/templates/wifi/hostapd.conf.tmpl @@ -73,7 +73,6 @@ channel={{ channel }} # offloaded ACS is used. {% if 'n' in mode -%} hw_mode=g -ieee80211n=1 {% elif 'ac' in mode -%} hw_mode=a ieee80211h=1 @@ -421,6 +420,12 @@ vht_capab= ieee80211n=0 # Require stations to support VHT PHY (reject association if they do not) require_vht=1 +{% else -%} +{% if 'n' in mode or 'ac' in mode -%} +ieee80211n=1 +{% else -%} +ieee80211n=0 +{%- endif %} {% endif %} {% if cap_vht_center_freq_1 -%} diff --git a/debian/vyos-1x.install b/debian/vyos-1x.install index 5004d111f..dd8eebc0b 100644 --- a/debian/vyos-1x.install +++ b/debian/vyos-1x.install @@ -1,5 +1,4 @@ etc/dhcp -etc/init.d etc/ppp etc/rsyslog.d etc/systemd diff --git a/interface-definitions/include/interface-hw-id.xml.i b/interface-definitions/include/interface-hw-id.xml.i new file mode 100644 index 000000000..cefc9f0a0 --- /dev/null +++ b/interface-definitions/include/interface-hw-id.xml.i @@ -0,0 +1,12 @@ +<leafNode name="mac"> + <properties> + <help>Associate Ethernet Interface with given Media Access Control (MAC) address</help> + <valueHelp> + <format>h:h:h:h:h:h</format> + <description>Hardware Media Access Control (MAC) address</description> + </valueHelp> + <constraint> + <validator name="mac-address"/> + </constraint> + </properties> +</leafNode> diff --git a/interface-definitions/interfaces-ethernet.xml.in b/interface-definitions/interfaces-ethernet.xml.in index f8ec26d04..89669f966 100644 --- a/interface-definitions/interfaces-ethernet.xml.in +++ b/interface-definitions/interfaces-ethernet.xml.in @@ -56,18 +56,7 @@ <constraintErrorMessage>duplex must be auto, half or full</constraintErrorMessage> </properties> </leafNode> - <leafNode name="hw-id"> - <properties> - <help>Media Access Control (MAC) address</help> - <valueHelp> - <format>h:h:h:h:h:h</format> - <description>Hardware (MAC) address</description> - </valueHelp> - <constraint> - <validator name="mac-address"/> - </constraint> - </properties> - </leafNode> + #include <include/interface-hw-id.xml.i> <node name="ip"> <children> #include <include/interface-arp-cache-timeout.xml.i> diff --git a/interface-definitions/interfaces-openvpn.xml.in b/interface-definitions/interfaces-openvpn.xml.in index 92bac3fab..d926876f7 100644 --- a/interface-definitions/interfaces-openvpn.xml.in +++ b/interface-definitions/interfaces-openvpn.xml.in @@ -444,6 +444,52 @@ </leafNode> </children> </tagNode> + <node name="client-ip-pool"> + <properties> + <help>Pool of client IP addresses</help> + </properties> + <children> + <leafNode name="start"> + <properties> + <help>First IP address in the pool</help> + <constraint> + <validator name="ipv4-address"/> + </constraint> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address</description> + </valueHelp> + </properties> + </leafNode> + <leafNode name="stop"> + <properties> + <help>Last IP address in the pool</help> + <constraint> + <validator name="ipv4-address"/> + </constraint> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address</description> + </valueHelp> + </properties> + </leafNode> + <leafNode name="subnet-mask"> + <properties> + <help>Subnet mask pushed to dynamic clients. + If not set the server subnet mask will be used. + Only used with topology subnet or device type tap. + Not used with bridged interfaces.</help> + <constraint> + <validator name="ipv4-address"/> + </constraint> + <valueHelp> + <format>ipv4</format> + <description>IPv4 subnet mask</description> + </valueHelp> + </properties> + </leafNode> + </children> + </node> <leafNode name="domain-name"> <properties> <help>DNS suffix to be pushed to all clients</help> @@ -501,7 +547,7 @@ <help>Server-mode subnet (from which client IPs are allocated)</help> <valueHelp> <format>ipv4net</format> - <description>IPv4 address and prefix length</description> + <description>IPv4 network and prefix length</description> </valueHelp> <constraint> <validator name="ipv4-prefix"/> @@ -512,9 +558,13 @@ <properties> <help>Topology for clients</help> <completionHelp> - <list>point-to-point subnet</list> + <list>net30 point-to-point subnet</list> </completionHelp> <valueHelp> + <format>net30</format> + <description>net30 topology</description> + </valueHelp> + <valueHelp> <format>point-to-point</format> <description>Point-to-point topology</description> </valueHelp> diff --git a/interface-definitions/interfaces-wireless.xml.in b/interface-definitions/interfaces-wireless.xml.in index 194669f77..a5c6315fa 100644 --- a/interface-definitions/interfaces-wireless.xml.in +++ b/interface-definitions/interfaces-wireless.xml.in @@ -476,18 +476,7 @@ #include <include/ipv6-dup-addr-detect-transmits.xml.i> </children> </node> - <leafNode name="hw-id"> - <properties> - <help>Media Access Control (MAC) address</help> - <valueHelp> - <format>h:h:h:h:h:h</format> - <description>Hardware (MAC) address</description> - </valueHelp> - <constraint> - <validator name="mac-address"/> - </constraint> - </properties> - </leafNode> + #include <include/interface-hw-id.xml.i> <leafNode name="isolate-stations"> <properties> <help>Isolate stations on the AP so they cannot see each other</help> diff --git a/interface-definitions/vpn-l2tp.xml.in b/interface-definitions/vpn-l2tp.xml.in index 7fc844054..d4286a810 100644 --- a/interface-definitions/vpn-l2tp.xml.in +++ b/interface-definitions/vpn-l2tp.xml.in @@ -2,7 +2,7 @@ <interfaceDefinition> <node name="vpn"> <children> - <node name="l2tp" owner="${vyos_conf_scripts_dir}/accel_l2tp.py"> + <node name="l2tp" owner="${vyos_conf_scripts_dir}/vpn_l2tp.py"> <properties> <help>L2TP Virtual Private Network (VPN)</help> </properties> @@ -36,48 +36,22 @@ </constraint> </properties> </leafNode> - <node name="dns-servers"> + <leafNode name="name-server"> <properties> - <help>IPv4 Domain Name Service (DNS) server</help> - </properties> - <children> - <leafNode name="server-1"> - <properties> - <help>Primary DNS server</help> - <valueHelp> - <format>ipv4</format> - <description>IPv4 address</description> - </valueHelp> - <constraint> - <validator name="ipv4-address"/> - </constraint> - </properties> - </leafNode> - <leafNode name="server-2"> - <properties> - <help>Secondary DNS server</help> - <valueHelp> - <format>ipv4</format> - <description>IPv4 address</description> - </valueHelp> - <constraint> - <validator name="ipv4-address"/> - </constraint> - </properties> - </leafNode> - </children> - </node> - <leafNode name="dnsv6-servers"> - <properties> - <help>IPv6 Domain Name Service (DNS) server</help> + <help>Domain Name Server (DNS) propagated to client</help> <valueHelp> - <format>ipv6</format> - <description>IPv6 DNS address</description> + <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> <constraint> + <validator name="ipv4-address"/> <validator name="ipv6-address"/> </constraint> - <multi /> + <multi/> </properties> </leafNode> <node name="lns"> @@ -208,29 +182,19 @@ </leafNode> </children> </node> - <node name="wins-servers"> + <leafNode name="wins-server"> <properties> - <help>Windows Internet Name Service (WINS) server settings</help> + <help>Windows Internet Name Service (WINS) servers propagated to client</help> + <valueHelp> + <format>ipv4</format> + <description>Domain Name Server (DNS) IPv4 address</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + <multi/> </properties> - <children> - <leafNode name="server-1"> - <properties> - <help>Primary WINS server</help> - <constraint> - <validator name="ipv4-address"/> - </constraint> - </properties> - </leafNode> - <leafNode name="server-2"> - <properties> - <help>Secondary WINS server</help> - <constraint> - <validator name="ipv4-address"/> - </constraint> - </properties> - </leafNode> - </children> - </node> + </leafNode> <node name="client-ip-pool"> <properties> <help>Pool of client IP addresses (must be within a /24)</help> @@ -273,26 +237,58 @@ <help>Pool of client IPv6 addresses</help> </properties> <children> - <leafNode name="prefix"> + <tagNode name="prefix"> <properties> - <help>IPV6 prefix delegation</help> + <help>Pool of addresses used to assign to clients</help> <valueHelp> - <format>ipv6prefix/mask,prefix_len</format> - <description>e.g.: fc00:0:1::/48,64 - divides prefix into /64 subnets for clients</description> + <format>ipv6net</format> + <description>IPv6 address and prefix length</description> </valueHelp> - <multi /> + <constraint> + <validator name="ipv6-prefix"/> + </constraint> </properties> - </leafNode> - <leafNode name="delegate-prefix"> + <children> + <leafNode name="mask"> + <properties> + <help>Prefix length used for individual client</help> + <valueHelp> + <format><48-128></format> + <description>Client prefix length (default: 64)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 48-128"/> + </constraint> + </properties> + </leafNode> + </children> + </tagNode> + <tagNode name="delegate"> <properties> - <help>DHCPv6 prefix delegation - rfc3633</help> + <help>Subnet used to delegate prefix through DHCPv6-PD (RFC3633)</help> <valueHelp> - <format>ipv6prefix/mask,prefix_len</format> - <description>Delegate to clients through DHCPv6 prefix delegation - rfc3633</description> + <format>ipv6net</format> + <description>IPv6 address and prefix length</description> </valueHelp> - <multi /> + <constraint> + <validator name="ipv6-prefix"/> + </constraint> </properties> - </leafNode> + <children> + <leafNode name="delegation-prefix"> + <properties> + <help>Prefix length delegated to client</help> + <valueHelp> + <format><32-64></format> + <description>Delegated prefix length</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 32-64"/> + </constraint> + </properties> + </leafNode> + </children> + </tagNode> </children> </node> <leafNode name="description"> @@ -445,46 +441,26 @@ </tagNode> </children> </node> + #include <include/radius-server.xml.i> <node name="radius"> - <properties> - <help>RADIUS specific configuration</help> - </properties> <children> <tagNode name="server"> - <properties> - <help>IP address of RADIUS server</help> - <valueHelp> - <format>ipv4</format> - <description>IPv4 address of RADIUS server</description> - </valueHelp> - </properties> <children> - <leafNode name="key"> - <properties> - <help>Key for accessing the specified server</help> - </properties> - </leafNode> - <leafNode name="req-limit"> - <properties> - <help>Maximum number of simultaneous requests to server (default: unlimited)</help> - </properties> - </leafNode> <leafNode name="fail-time"> <properties> - <help>If server doesn not responds mark it unavailable for this time (seconds)</help> + <help>Mark server unavailable for <n> seconds on failure</help> + <valueHelp> + <format>0-600</format> + <description>Fail time penalty</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-600"/> + </constraint> + <constraintErrorMessage>Fail time must be between 0 and 600 seconds</constraintErrorMessage> </properties> </leafNode> </children> </tagNode> - <leafNode name="source-address"> - <properties> - <help>Local RADIUS client address from which packets are sent.</help> - <valueHelp> - <format><x.x.x.x></format> - <description>Local RADIUS client address from which packets are sent</description> - </valueHelp> - </properties> - </leafNode> <leafNode name="timeout"> <properties> <help>Timeout to wait response from server (seconds)</help> diff --git a/op-mode-definitions/show-ip-bgp.xml b/op-mode-definitions/show-ip-bgp.xml new file mode 100644 index 000000000..5eb2ae56e --- /dev/null +++ b/op-mode-definitions/show-ip-bgp.xml @@ -0,0 +1,342 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="ip"> + <children> + <node name="bgp"> + <properties> + <help>Show Border Gateway Protocol (BGP) information</help> + </properties> + <command>/usr/bin/vtysh -c "show ip bgp"</command> + <children> + <leafNode name="attribute-info"> + <properties> + <help>Show BGP attribute information</help> + </properties> + <command>/usr/bin/vtysh -c "show ip bgp attribute-info"</command> + </leafNode> + <leafNode name="cidr-only"> + <properties> + <help>Display only routes with non-natural netmasks</help> + </properties> + <command>/usr/bin/vtysh -c "show ip bgp cidr-only"</command> + </leafNode> + <node name="community"> + <properties> + <help>Show BGP routes matching the communities</help> + </properties> + <command>/usr/bin/vtysh -c "show ip bgp community"</command> + </node> + <tagNode name="community"> + <properties> + <help>Display routes matching the specified communities</help> + <completionHelp> + <list><AA:NN> local-AS no-advertise no-export</list> + </completionHelp> + </properties> + <command>/usr/bin/vtysh -c "show ip bgp community $5"</command> + </tagNode> + <leafNode name="community-info"> + <properties> + <help>List all bgp community information</help> + </properties> + <command>/usr/bin/vtysh -c "show ip bgp community-info"</command> + </leafNode> + <tagNode name="community-list"> + <properties> + <help>Show BGP routes matching specified community list</help> + </properties> + <command>/usr/bin/vtysh -c "show ip bgp community-list $5"</command> + <children> + <leafNode name="exact-match"> + <properties> + <help>Show BGP routes exactly matching specified community list</help> + </properties> + <command>/usr/bin/vtysh -c "show ip bgp community-list $5 exact-match"</command> + </leafNode> + </children> + </tagNode> + <leafNode name="dampened-paths"> + <properties> + <help>Show dampened BGP paths</help> + </properties> + <command>/usr/bin/vtysh -c "show ip bgp dampening dampened-paths"</command> + </leafNode> + <tagNode name="filter-list"> + <properties> + <help>Show BGP information for specified word</help> + </properties> + <command>/usr/bin/vtysh -c "show ip bgp filter-list $5"</command> + </tagNode> + <leafNode name="flap-statistics"> + <properties> + <help>Show flap statistics of routes</help> + </properties> + <command>/usr/bin/vtysh -c "show ip bgp dampening flap-statistics"</command> + </leafNode> + <node name="ipv4"> + <properties> + <help>Show BGP IPv4 information</help> + </properties> + <children> + <node name="unicast"> + <properties> + <help>Show BGP IPv4 unicast information</help> + </properties> + <children> + <leafNode name="cidr-only"> + <properties> + <help>Display only routes with non-natural netmasks</help> + </properties> + <command>/usr/bin/vtysh -c "show ip bgp ipv4 unicast cidr-only"</command> + </leafNode> + <node name="community"> <!-- START new code --> + <properties> + <help>Show BGP routes matching the communities</help> + </properties> + <command>/usr/bin/vtysh -c "show ip bgp ipv4 unicast community"</command> + </node> + <tagNode name="community"> + <properties> + <help>Display routes matching the specified communities</help> + <completionHelp> + <list><AA:NN> local-AS no-advertise no-export</list> + </completionHelp> + </properties> + <command>/usr/bin/vtysh -c "show ip bgp ipv4 unicast community $7"</command> + </tagNode> + <tagNode name="community-list"> + <properties> + <help>Show BGP routes matching specified community list</help> + </properties> + <command>/usr/bin/vtysh -c "show ip bgp ipv4 unicast community-list $7"</command> + <children> + <leafNode name="exact-match"> + <properties> + <help>Show BGP routes exactly matching specified community list</help> + </properties> + <command>/usr/bin/vtysh -c "show ip bgp ipv4 unicast community-list $7 exact-match"</command> + </leafNode> + </children> + </tagNode> + <tagNode name="filter-list"> + <properties> + <help>Show BGP information for specified word</help> + </properties> + <command>/usr/bin/vtysh -c "show ip bgp filter-list $5"</command> + </tagNode> + <tagNode name="neighbors"> + <properties> + <help>Show detailed BGP IPv4 unicast neighbor information</help> + <completionHelp> + <script>vtysh -c "show ip bgp ipv4 unicast summary" | awk '{print $1}' | grep -oE "\b([0-9]{1,3}\.){3}[0-9]{1,3}\b"</script> + </completionHelp> + </properties> + <command>/usr/bin/vtysh -c "show ip bgp ipv4 unicast neighbors $7"</command> + <children> + <leafNode name="advertised-routes"> + <properties> + <help>Show routes advertised to a BGP neighbor</help> + </properties> + <command>/usr/bin/vtysh -c "show ip bgp ipv4 unicast neighbor $7 advertised-routes"</command> + </leafNode> + <leafNode name="prefix-counts"> + <properties> + <help>Show detailed prefix count information</help> + </properties> + <command>/usr/bin/vtysh -c "show ip bgp ipv4 unicast neighbor $7 prefix-counts"</command> + </leafNode> + <leafNode name="received-routes"> + <properties> + <help>Show the received routes from neighbor</help> + </properties> + <command>/usr/bin/vtysh -c "show ip bgp ipv4 unicast neighbor $7 received-routes"</command> + </leafNode> + <leafNode name="routes"> + <properties> + <help>Show routes learned from neighbor</help> + </properties> + <command>/usr/bin/vtysh -c "show ip bgp ipv4 unicast neighbor $7 routes"</command> + </leafNode> + </children> + </tagNode> + <leafNode name="paths"> + <properties> + <help>Show BGP path information</help> + </properties> + <command>/usr/bin/vtysh -c "show ip bgp ipv4 unicast paths"</command> + </leafNode> + <tagNode name="prefix-list"> + <properties> + <help>Show BGP routes matching the specified prefix list</help> + </properties> + <command>/usr/bin/vtysh -c "show ip bgp ipv4 unicast prefix-list $7"</command> + </tagNode> + <tagNode name="regexp"> + <properties> + <help>Show BGP routes matching the specified AS path regular expression</help> + </properties> + <command>/usr/bin/vtysh -c "show ip bgp ipv4 unicast regexp $5"</command> + </tagNode> + <tagNode name="route-map"> + <properties> + <help>Show BGP routes matching the specified route map</help> + </properties> + <command>/usr/bin/vtysh -c "show ip bgp route-map $5"</command> + </tagNode> + <leafNode name="summary"> + <properties> + <help>Show summary of BGP information</help> + </properties> + <command>/usr/bin/vtysh -c "show ip bgp summary"</command> + </leafNode> + </children> + </node> + <tagNode name="unicast"> + <properties> + <help>Show BGP information for specified IP address or prefix</help> + <completionHelp> + <list><x.x.x.x> <x.x.x.x/x></list> + </completionHelp> + </properties> + <command>/usr/bin/vtysh -c "show ip bgp $6"</command> + </tagNode> + </children> + </node> + <node name="large-community"> + <properties> + <help>Show BGP routes matching the specified large-communities</help> + </properties> + <command>/usr/bin/vtysh -c "show ip bgp large-community"</command> + </node> + <leafNode name="large-community-info"> + <properties> + <help>Show BGP large-community information</help> + </properties> + <command>/usr/bin/vtysh -c "show ip bgp large-community-info"</command> + </leafNode> + <tagNode name="large-community-list"> + <properties> + <help>Show BGP routes matching the specified large-community list</help> + </properties> + <command>/usr/bin/vtysh -c "show ip bgp large-community-list $5"</command> + </tagNode> + <leafNode name="memory"> + <properties> + <help>Show BGP memory usage</help> + </properties> + <command>/usr/bin/vtysh -c "show ip bgp memory"</command> + </leafNode> + <tagNode name="neighbors"> + <properties> + <help>Show detailed BGP IPv4 unicast neighbor information</help> + <completionHelp> + <script>vtysh -c "show ip bgp summary" | awk '{print $1}' | grep -oE "\b([0-9]{1,3}\.){3}[0-9]{1,3}\b"</script> + </completionHelp> + </properties> + <command>/usr/bin/vtysh -c "show ip bgp neighbors $5"</command> + <children> + <leafNode name="advertised-routes"> + <properties> + <help>Show routes advertised to a BGP neighbor</help> + </properties> + <command>/usr/bin/vtysh -c "show ip bgp neighbor $5 advertised-routes"</command> + </leafNode> + <leafNode name="dampened-routes"> + <properties> + <help>Show dampened routes received from BGP neighbor</help> + </properties> + <command>/usr/bin/vtysh -c "show ip bgp neighbor $5 dampened-routes"</command> + </leafNode> + <leafNode name="flap-statistics"> + <properties> + <help>Show flap statistics of the routes learned from BGP neighbor</help> + </properties> + <command>/usr/bin/vtysh -c "show ip bgp neighbor $5 flap-statistics"</command> + </leafNode> + <leafNode name="prefix-counts"> + <properties> + <help>Show detailed prefix count information for BGP neighbor</help> + </properties> + <command>/usr/bin/vtysh -c "show ip bgp neighbor $5 prefix-counts"</command> + </leafNode> + <node name="received"> + <properties> + <help>Show information received from BGP neighbor</help> + </properties> + <children> + <leafNode name="prefix-filter"> + <properties> + <help>Show prefixlist filter</help> + </properties> + <command>/usr/bin/vtysh -c "show ip bgp neighbor $5 received prefix-filter"</command> + </leafNode> + </children> + </node> + <leafNode name="received-routes"> + <properties> + <help>Show received routes from BGP neighbor</help> + </properties> + <command>/usr/bin/vtysh -c "show ip bgp neighbor $5 received-routes"</command> + </leafNode> + <leafNode name="routes"> + <properties> + <help>Show routes learned from BGP neighbor</help> + </properties> + <command>/usr/bin/vtysh -c "show ip bgp neighbor $5 routes"</command> + </leafNode> + </children> + </tagNode> + <leafNode name="paths"> + <properties> + <help>Show BGP path information</help> + </properties> + <command>/usr/bin/vtysh -c "show ip bgp paths"</command> + </leafNode> + <tagNode name="prefix-list"> + <properties> + <help>Show BGP routes matching the specified prefix list</help> + </properties> + <command>/usr/bin/vtysh -c "show ip bgp prefix-list $5"</command> + </tagNode> + <tagNode name="regexp"> + <properties> + <help>Show BGP routes matching the specified AS path regular expression</help> + </properties> + <command>/usr/bin/vtysh -c "show ip bgp regexp $5"</command> + </tagNode> + <tagNode name="route-map"> + <properties> + <help>Show BGP routes matching the specified route map</help> + </properties> + <command>/usr/bin/vtysh -c "show ip bgp route-map $5"</command> + </tagNode> + <leafNode name="statistics"> + <properties> + <help>Show summary of BGP information</help> + </properties> + <command>/usr/bin/vtysh -c "show ip bgp statistics"</command> + </leafNode> + <leafNode name="summary"> + <properties> + <help>Show summary of BGP information</help> + </properties> + <command>/usr/bin/vtysh -c "show ip bgp summary"</command> + </leafNode> + </children> + </node> + <tagNode name="bgp"> + <properties> + <help>Show BGP information for specified IP address or prefix</help> + <completionHelp> + <list><x.x.x.x> <x.x.x.x/x></list> + </completionHelp> + </properties> + <command>/usr/bin/vtysh -c "show ip bgp $4"</command> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/python/vyos/airbag.py b/python/vyos/airbag.py index 664974d5f..b0565192d 100644 --- a/python/vyos/airbag.py +++ b/python/vyos/airbag.py @@ -19,10 +19,10 @@ import logging import logging.handlers from datetime import datetime +from vyos import debug from vyos.config import Config from vyos.version import get_version from vyos.util import run -from vyos.util import debug # we allow to disable the extra logging @@ -77,8 +77,7 @@ def bug_report(dtype, value, trace): # reach the end of __main__ and was not intercepted def intercepter(dtype, value, trace): bug_report(dtype, value, trace) - # debug returns either '' or 'developer' if debuging is enabled - if debug('developer'): + if debug.enabled('developer'): import pdb pdb.pm() diff --git a/python/vyos/authutils.py b/python/vyos/authutils.py index 90a46ffb4..66b5f4a74 100644 --- a/python/vyos/authutils.py +++ b/python/vyos/authutils.py @@ -22,7 +22,7 @@ def make_password_hash(password): """ Makes a password hash for /etc/shadow using mkpasswd """ mkpassword = 'mkpasswd --method=sha-512 --stdin' - return cmd(mkpassword, input=password.encode(), timeout=5) + return cmd(mkpassword, input=password, timeout=5) def split_ssh_public_key(key_string, defaultname=""): """ Splits an SSH public key into its components """ diff --git a/python/vyos/debug.py b/python/vyos/debug.py new file mode 100644 index 000000000..20090fb85 --- /dev/null +++ b/python/vyos/debug.py @@ -0,0 +1,182 @@ +# Copyright 2019 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +import os +import sys + + +def message(message, flag='', destination=sys.stdout): + """ + print a debug message line on stdout if debugging is enabled for the flag + also log it to a file if the flag 'log' is enabled + + message: the message to print + flag: which flag must be set for it to print + destination: which file like object to write to (default: sys.stdout) + + returns if any message was logged or not + """ + enable = enabled(flag) + if enable: + destination.write(_format(flag,message)) + + # the log flag is special as it logs all the commands + # executed to a log + logfile = _logfile('log', '/tmp/developer-log') + if not logfile: + return enable + + try: + # at boot the file is created as root:vyattacfg + # at runtime the file is created as user:vyattacfg + # the default permission are 644 + mask = os.umask(0o113) + + with open(logfile, 'a') as f: + f.write(_format('log', message)) + finally: + os.umask(mask) + + return enable + + +def enabled(flag): + """ + a flag can be set by touching the file in /tmp or /config + + The current flags are: + - developer: the code will drop into PBD on un-handled exception + - log: the code will log all command to a file + - ifconfig: when modifying an interface, + prints command with result and sysfs access on stdout for interface + - command: print command run with result + + Having the flag setup on the filesystem is required to have + debuging at boot time, however, setting the flag via environment + does not require a seek to the filesystem and is more efficient + it can be done on the shell on via .bashrc for the user + + The function returns an empty string if the flag was not set otherwise + the function returns either the file or environment name used to set it up + """ + + # this is to force all new flags to be registered here to be + # documented both here and a reminder to update readthedocs :-) + if flag not in ['developer', 'log', 'ifconfig', 'command']: + return '' + + return _fromenv(flag) or _fromfile(flag) + + +def _format(flag, message): + """ + format a log message + """ + return f'DEBUG/{flag.upper():<7} {message}\n' + + +def _fromenv(flag): + """ + check if debugging is set for this flag via environment + + For a given debug flag named "test" + The presence of the environment VYOS_TEST_DEBUG (uppercase) enables it + + return empty string if not + return content of env value it is + """ + + flagname = f'VYOS_{flag.upper()}_DEBUG' + flagenv = os.environ.get(flagname, None) + + if flagenv is None: + return '' + return flagenv + + +def _fromfile(flag): + """ + Check if debug exist for a given debug flag name + + Check is a debug flag was set by the user. the flag can be set either: + - in /tmp for a non-persistent presence between reboot + - in /config for always on (an existence at boot time) + + For a given debug flag named "test" + The presence of the file vyos.test.debug (all lowercase) enables it + + The function returns an empty string if the flag was not set otherwise + the function returns the full flagname + """ + + for folder in ('/tmp', '/config'): + flagfile = f'{folder}/vyos.{flag}.debug' + if os.path.isfile(flagfile): + return flagfile + + return '' + + +def _contentenv(flag): + return os.environ.get(f'VYOS_{flag.upper()}_DEBUG', '').strip() + + +def _contentfile(flag): + """ + Check if debug exist for a given debug flag name + + Check is a debug flag was set by the user. the flag can be set either: + - in /tmp for a non-persistent presence between reboot + - in /config for always on (an existence at boot time) + + For a given debug flag named "test" + The presence of the file vyos.test.debug (all lowercase) enables it + + The function returns an empty string if the flag was not set otherwise + the function returns the full flagname + """ + + for folder in ('/tmp', '/config'): + flagfile = f'{folder}/vyos.{flag}.debug' + if not os.path.isfile(flagfile): + continue + with open(flagfile) as f: + return f.readline().strip() + + return '' + + +def _logfile(flag, default): + """ + return the name of the file to use for logging when the flag 'log' is set + if it could not be established or the location is invalid it returns + an empty string + """ + + # For log we return the location of the log file + log_location = _contentenv(flag) or _contentfile(flag) + + # it was not set + if not log_location: + return '' + + # Make sure that the logs can only be in /tmp, /var/log, or /tmp + if not log_location.startswith('/tmp/') and \ + not log_location.startswith('/config/') and \ + not log_location.startswith('/var/log/'): + return default + if '..' in log_location: + return default + return log_location diff --git a/python/vyos/ifconfig/__init__.py b/python/vyos/ifconfig/__init__.py index 1f9956af0..cd1696ca1 100644 --- a/python/vyos/ifconfig/__init__.py +++ b/python/vyos/ifconfig/__init__.py @@ -14,6 +14,7 @@ # License along with this library. If not, see <http://www.gnu.org/licenses/>. +from vyos.ifconfig.section import Section from vyos.ifconfig.interface import Interface from vyos.ifconfig.bond import BondIf diff --git a/python/vyos/ifconfig/control.py b/python/vyos/ifconfig/control.py index c7a2fa2d6..7bb63beed 100644 --- a/python/vyos/ifconfig/control.py +++ b/python/vyos/ifconfig/control.py @@ -16,12 +16,13 @@ import os -from vyos.util import debug, debug_msg -from vyos.util import popen, cmd -from vyos.ifconfig.register import Register +from vyos import debug +from vyos.util import popen +from vyos.util import cmd +from vyos.ifconfig.section import Section -class Control(Register): +class Control(Section): _command_get = {} _command_set = {} @@ -35,10 +36,10 @@ class Control(Register): # if debug is not explicitely disabled the the config, enable it self.debug = '' if kargs.get('debug', True): - self.debug = debug('ifconfig') + self.debug = debug.enabled('ifconfig') def _debug_msg (self, message): - return debug_msg(message, self.debug) + return debug.message(message, self.debug) def _popen(self, command): return popen(command, self.debug) diff --git a/python/vyos/ifconfig/input.py b/python/vyos/ifconfig/input.py new file mode 100644 index 000000000..bfab36335 --- /dev/null +++ b/python/vyos/ifconfig/input.py @@ -0,0 +1,31 @@ +# Copyright 2020 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + + +from vyos.ifconfig.interface import Interface + + +@Interface.register +class InputIf(Interface): + default = { + 'type': '', + } + definition = { + **Interface.definition, + **{ + 'section': 'input', + 'prefixes': ['ifb', ], + }, + } diff --git a/python/vyos/ifconfig/register.py b/python/vyos/ifconfig/section.py index c90782b70..ab340d247 100644 --- a/python/vyos/ifconfig/register.py +++ b/python/vyos/ifconfig/section.py @@ -16,9 +16,10 @@ import netifaces -class Register: +class Section: # the known interface prefixes _prefixes = {} + _classes = [] # class need to define: definition['prefixes'] # the interface prefixes declared by a class used to name interface with @@ -26,9 +27,16 @@ class Register: @classmethod def register(cls, klass): + """ + A function to use as decorator the interfaces classes + It register the prefix for the interface (eth, dum, vxlan, ...) + with the class which can handle it (EthernetIf, DummyIf,VXLANIf, ...) + """ if not klass.definition.get('prefixes',[]): raise RuntimeError(f'valid interface prefixes not defined for {klass.__name__}') + cls._classes.append(klass) + for ifprefix in klass.definition['prefixes']: if ifprefix in cls._prefixes: raise RuntimeError(f'only one class can be registered for prefix "{ifprefix}" type') @@ -38,7 +46,11 @@ class Register: @classmethod def _basename (cls, name, vlan): - # remove number from interface name + """ + remove the number at the end of interface name + name: name of the interface + vlan: if vlan is True, do not stop at the vlan number + """ name = name.rstrip('0123456789') name = name.rstrip('.') if vlan: @@ -47,15 +59,13 @@ class Register: @classmethod def section(cls, name, vlan=True): - # return the name of a section an interface should be under + """ + return the name of a section an interface should be under + name: name of the interface (eth0, dum1, ...) + vlan: should we try try to remove the VLAN from the number + """ name = cls._basename(name, vlan) - # XXX: To leave as long as vti and input are not moved to vyos - if name == 'vti': - return 'vti' - if name == 'ifb': - return 'input' - if name in cls._prefixes: return cls._prefixes[name].definition['section'] return '' @@ -68,15 +78,13 @@ class Register: raise ValueError(f'No type found for interface name: {name}') @classmethod - def _listing (cls,section=''): + def _intf_under_section (cls,section=''): + """ + return a generator with the name of the interface which are under a section + """ interfaces = netifaces.interfaces() for ifname in interfaces: - # XXX: Temporary hack as vti and input are not yet moved from vyatta to vyos - if ifname.startswith('vti') or ifname.startswith('input'): - yield ifname - continue - ifsection = cls.section(ifname) if not ifsection: continue @@ -87,9 +95,37 @@ class Register: yield ifname @classmethod - def listing(cls, section=''): - return list(cls._listing(section)) + def interfaces(cls, section=''): + """ + return a list of the name of the interface which are under a section + if no section is provided, then it returns all configured interfaces + """ + return list(cls._intf_under_section(section)) + @classmethod + def _intf_with_feature(cls, feature=''): + """ + return a generator with the name of the interface which have + a particular feature set in their definition such as: + bondable, broadcast, bridgeable, ... + """ + for klass in cls._classes: + if klass.definition[feature]: + yield klass.definition['section'] -# XXX: TODO - limit name for VRF interfaces + @classmethod + def feature(cls, feature=''): + """ + return list with the name of the interface which have + a particular feature set in their definition such as: + bondable, broadcast, bridgeable, ... + """ + return list(cls._intf_with_feature(feature)) + @classmethod + def reserved(cls): + """ + return list with the interface name prefixes + eth, lo, vxlan, dum, ... + """ + return list(cls._prefixes.keys()) diff --git a/python/vyos/ifconfig/tunnel.py b/python/vyos/ifconfig/tunnel.py index 05060669a..009a53a82 100644 --- a/python/vyos/ifconfig/tunnel.py +++ b/python/vyos/ifconfig/tunnel.py @@ -143,7 +143,7 @@ class GREIf(_Tunnel): options = ['local', 'remote', 'ttl', 'tos', 'key'] updates = ['local', 'remote', 'ttl', 'tos', - 'multicast', 'allmulticast'] + 'mtu', 'multicast', 'allmulticast'] create = 'ip tunnel add {ifname} mode {type}' change = 'ip tunnel cha {ifname}' @@ -167,7 +167,7 @@ class GRETapIf(_Tunnel): required = ['local', ] options = ['local', 'remote', ] - updates = [] + updates = ['mtu', ] create = 'ip link add {ifname} type {type}' change = '' @@ -193,7 +193,7 @@ class IP6GREIf(_Tunnel): 'hoplimit', 'tclass', 'flowlabel'] updates = ['local', 'remote', 'encaplimit', 'hoplimit', 'tclass', 'flowlabel', - 'multicast', 'allmulticast'] + 'mtu', 'multicast', 'allmulticast'] create = 'ip tunnel add {ifname} mode {type}' change = 'ip tunnel cha {ifname} mode {type}' @@ -227,7 +227,7 @@ class IPIPIf(_Tunnel): options = ['local', 'remote', 'ttl', 'tos', 'key'] updates = ['local', 'remote', 'ttl', 'tos', - 'multicast', 'allmulticast'] + 'mtu', 'multicast', 'allmulticast'] create = 'ip tunnel add {ifname} mode {type}' change = 'ip tunnel cha {ifname}' @@ -252,7 +252,7 @@ class IPIP6If(_Tunnel): 'hoplimit', 'tclass', 'flowlabel'] updates = ['local', 'remote', 'encaplimit', 'hoplimit', 'tclass', 'flowlabel', - 'multicast', 'allmulticast'] + 'mtu', 'multicast', 'allmulticast'] create = 'ip -6 tunnel add {ifname} mode {type}' change = 'ip -6 tunnel cha {ifname}' @@ -288,7 +288,7 @@ class SitIf(_Tunnel): options = ['local', 'remote', 'ttl', 'tos', 'key'] updates = ['local', 'remote', 'ttl', 'tos', - 'multicast', 'allmulticast'] + 'mtu', 'multicast', 'allmulticast'] create = 'ip tunnel add {ifname} mode {type}' change = 'ip tunnel cha {ifname}' @@ -309,7 +309,7 @@ class Sit6RDIf(SitIf): # TODO: check if key can really be used with 6RD options = ['remote', 'ttl', 'tos', 'key', '6rd-prefix', '6rd-relay-prefix'] updates = ['remote', 'ttl', 'tos', - 'multicast', 'allmulticast'] + 'mtu', 'multicast', 'allmulticast'] def _create(self): # do not call _Tunnel.create, building fully here diff --git a/python/vyos/ifconfig/vti.py b/python/vyos/ifconfig/vti.py new file mode 100644 index 000000000..56ebe01d1 --- /dev/null +++ b/python/vyos/ifconfig/vti.py @@ -0,0 +1,31 @@ +# Copyright 2020 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + + +from vyos.ifconfig.interface import Interface + + +@Interface.register +class VTIIf(Interface): + default = { + 'type': 'vti', + } + definition = { + **Interface.definition, + **{ + 'section': 'vti', + 'prefixes': ['vti', ], + }, + } diff --git a/python/vyos/remote.py b/python/vyos/remote.py index f918461d1..1b4d3876e 100644 --- a/python/vyos/remote.py +++ b/python/vyos/remote.py @@ -18,7 +18,8 @@ import os import re import fileinput -from vyos.util import cmd, DEVNULL +from vyos.util import cmd +from vyos.util import DEVNULL def check_and_add_host_key(host_name): @@ -31,10 +32,10 @@ def check_and_add_host_key(host_name): mode = 0o600 os.mknod(known_hosts, 0o600) - keyscan_cmd = 'ssh-keyscan -t rsa {} 2>/dev/null'.format(host_name) + keyscan_cmd = 'ssh-keyscan -t rsa {}'.format(host_name) try: - host_key = cmd(keyscan_cmd, shell=True, stderr=DEVNULL) + host_key = cmd(keyscan_cmd, stderr=DEVNULL) except OSError: sys.exit("Can not get RSA host key") @@ -61,9 +62,9 @@ def check_and_add_host_key(host_name): print("Host key has changed!") print("If you trust the host key fingerprint below, continue.") - fingerprint_cmd = 'ssh-keygen -lf /dev/stdin <<< "{}"'.format(host_key) + fingerprint_cmd = 'ssh-keygen -lf /dev/stdin' try: - fingerprint = cmd(fingerprint_cmd, shell=True, stderr=DEVNULL) + fingerprint = cmd(fingerprint_cmd, stderr=DEVNULL, input=host_key) except OSError: sys.exit("Can not get RSA host key fingerprint.") @@ -125,7 +126,7 @@ def get_remote_config(remote_file): # Try header first, and look for 'OK' or 'Moved' codes: curl_cmd = 'curl {0} -q -I {1}'.format(redirect_opt, remote_file) try: - curl_output = cmd(curl_cmd, shell=True) + curl_output = cmd(curl_cmd) except OSError: sys.exit(1) @@ -142,6 +143,6 @@ def get_remote_config(remote_file): curl_cmd = 'curl {0} -# {1}'.format(redirect_opt, remote_file) try: - return cmd(curl_cmd, shell=True, stderr=None) + return cmd(curl_cmd, stderr=None) except OSError: return None diff --git a/python/vyos/template.py b/python/vyos/template.py index e559120c0..6c73ce753 100644 --- a/python/vyos/template.py +++ b/python/vyos/template.py @@ -22,10 +22,17 @@ from vyos.defaults import directories # reuse the same Environment to improve performance -_templates_env = Environment(loader=FileSystemLoader(directories['templates'])) -_templates_mem = {} +_templates_env = { + False: Environment(loader=FileSystemLoader(directories['templates'])), + True: Environment(loader=FileSystemLoader(directories['templates']), trim_blocks=True), +} +_templates_mem = { + False: {}, + True: {}, +} -def render(destination, template, content): + +def render(destination, template, content, trim_blocks=False, formater=None): """ render a template from the template directory, it will raise on any errors destination: the file where the rendered template must be saved @@ -41,15 +48,18 @@ def render(destination, template, content): # Setup a renderer for the given template # This is cached and re-used for performance - if template not in _templates_mem: - _templates_mem[template] = _templates_env.get_template(template) - template = _templates_mem[template] + if template not in _templates_mem[trim_blocks]: + _templates_mem[trim_blocks][template] = _templates_env[trim_blocks].get_template(template) + template = _templates_mem[trim_blocks][template] # As we are opening the file with 'w', we are performing the rendering # before calling open() to not accidentally erase the file if the # templating fails content = template.render(content) + if formater: + content = formater(content) + # Write client config file with open(destination, 'w') as f: f.write(content) diff --git a/python/vyos/util.py b/python/vyos/util.py index 291ce64ea..49c47cd85 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -16,67 +16,121 @@ import os import re import sys -from subprocess import Popen, PIPE, STDOUT, DEVNULL +from subprocess import Popen +from subprocess import PIPE +from subprocess import STDOUT +from subprocess import DEVNULL -def debug(flag): - # this is to force all new flags to be registered here so that - # they can be documented: - # - developer: the code will drop into PBD on un-handled exception - # - ifconfig: prints command and sysfs access on stdout for interface - if flag not in ['developer', 'ifconfig']: - return '' - return flag if os.path.isfile(f'/tmp/vyos.{flag}.debug') else '' +from vyos import debug +# There is many (too many) ways to run command with python +# os.system, subprocess.Popen, subproces.{run,call,check_output} +# which all have slighty different behaviour -def debug_msg(message, section=''): - if debug(section): - print(f'DEBUG/{section:<6} {message}') +def popen(command, flag='', shell=None, input=None, timeout=None, env=None, + stdout=PIPE, stderr=None, decode=None): + """ + popen is a wrapper helper aound subprocess.Popen + with it default setting it will return a tuple (out, err) + out: the output of the program run + err: the error code returned by the program + + it can be affected by the following flags: + shell: do not try to auto-detect if a shell is required + for example if a pipe (|) or redirection (>, >>) is used + input: data to sent to the child process via STDIN + the data should be bytes but string will be converted + timeout: time after which the command will be considered to have failed + env: mapping that defines the environment variables for the new process + stdout: define how the output of the program should be handled + - PIPE (default), sends stdout to the output + - DEVNULL, discard the output + stderr: define how the output of the program should be handled + - None (default), send/merge the data to/with stderr + - PIPE, popen will append it to output + - STDOUT, send the data to be merged with stdout + - DEVNULL, discard the output + decode: specify the expected text encoding (utf-8, ascii, ...) + + usage: + to get both stdout, and stderr: popen('command', stdout=PIPE, stderr=STDOUT) + to discard stdout and get stderr: popen('command', stdout=DEVNUL, stderr=PIPE) + """ + + # log if the flag is set, otherwise log if command is set + if not debug.enabled(flag): + flag = 'command' + + cmd_msg = f"cmd '{command}'" + debug.message(cmd_msg, flag) -def popen(command, section='', shell=None, input=None, timeout=None, env=None, - universal_newlines=None, stdout=PIPE, stderr=STDOUT, decode=None): - """ popen does not raise, returns the output and error code of command """ use_shell = shell + stdin = None if shell is None: - use_shell = True if ' ' in command else False + use_shell = False + if ' ' in command: + use_shell = True + if env: + use_shell = True + if input: + stdin = PIPE + input = input.encode() if type(input) is str else input p = Popen( command, - stdout=stdout, stderr=stderr, + stdin=stdin, stdout=stdout, stderr=stderr, env=env, shell=use_shell, - universal_newlines=universal_newlines, ) - tmp = p.communicate(input, timeout)[0].strip() - debug_msg(f"cmd '{command}'", section) - decoded = tmp.decode(decode) if decode else tmp.decode() + tmp = p.communicate(input, timeout) + out1 = b'' + out2 = b'' + if stdout == PIPE: + out1 = tmp[0] + if stderr == PIPE: + out2 += tmp[1] + decoded1 = out1.decode(decode) if decode else out1.decode() + decoded2 = out2.decode(decode) if decode else out2.decode() + decoded1 = decoded1.replace('\r\n', '\n').strip() + decoded2 = decoded2.replace('\r\n', '\n').strip() + nl = '\n' if decoded1 and decoded2 else '' + decoded = decoded1 + nl + decoded2 if decoded: - debug_msg(f"returned:\n{decoded}", section) + ret_msg = f"returned:\n{decoded}" + debug.message(ret_msg, flag) return decoded, p.returncode -def run(command, section='', shell=None, input=None, timeout=None, env=None, - universal_newlines=None, stdout=PIPE, stderr=STDOUT, decode=None): - """ does not raise exception on error, returns error code """ +def run(command, flag='', shell=None, input=None, timeout=None, env=None, + stdout=DEVNULL, stderr=None, decode=None): + """ + A wrapper around vyos.util.popen, which discard the stdout and + will return the error code of a command + """ _, code = popen( - command, section, + command, flag, stdout=stdout, stderr=stderr, input=input, timeout=timeout, env=env, shell=shell, - universal_newlines=universal_newlines, decode=decode, ) return code -def cmd(command, section='', shell=None, input=None, timeout=None, env=None, - universal_newlines=None, stdout=PIPE, stderr=STDOUT, decode=None, +def cmd(command, flag='', shell=None, input=None, timeout=None, env=None, + stdout=PIPE, stderr=None, decode=None, raising=None, message=''): - """ does raise exception, returns output of command """ + """ + A wrapper around vyos.util.popen, which returns the stdout and + will raise the error code of a command + + raising: specify which call should be used when raising (default is OSError) + the class should only require a string as parameter + """ decoded, code = popen( - command, section, + command, flag, stdout=stdout, stderr=stderr, input=input, timeout=timeout, env=env, shell=shell, - universal_newlines=universal_newlines, decode=decode, ) if code != 0: @@ -92,15 +146,17 @@ def cmd(command, section='', shell=None, input=None, timeout=None, env=None, return decoded -def call(command, section='', shell=None, input=None, timeout=None, env=None, - universal_newlines=None, stdout=PIPE, stderr=STDOUT, decode=None): - """ does not raise exception on error, returns error code, print output """ +def call(command, flag='', shell=None, input=None, timeout=None, env=None, + stdout=PIPE, stderr=None, decode=None): + """ + A wrapper around vyos.util.popen, which print the stdout and + will return the error code of a command + """ out, code = popen( - command, section, + command, flag, stdout=stdout, stderr=stderr, input=input, timeout=timeout, env=env, shell=shell, - universal_newlines=universal_newlines, decode=decode, ) print(out) @@ -124,6 +180,16 @@ def chown(path, user, group): gid = getgrnam(group).gr_gid os.chown(path, uid, gid) + +def chmod_600(path): + """ make file only read/writable by owner """ + from stat import S_IRUSR, S_IWUSR + + if os.path.exists(path): + bitmask = S_IRUSR | S_IWUSR + os.chmod(path, bitmask) + + def chmod_750(path): """ make file/directory only executable to user and group """ from stat import S_IRUSR, S_IWUSR, S_IXUSR, S_IRGRP, S_IXGRP @@ -133,8 +199,8 @@ def chmod_750(path): os.chmod(path, bitmask) -def chmod_x(path): - """ make file executable """ +def chmod_755(path): + """ make file executable by all """ from stat import S_IRUSR, S_IWUSR, S_IXUSR, S_IRGRP, S_IXGRP, S_IROTH, S_IXOTH if os.path.exists(path): diff --git a/src/completion/list_interfaces.py b/src/completion/list_interfaces.py index 98b32797a..e27281433 100755 --- a/src/completion/list_interfaces.py +++ b/src/completion/list_interfaces.py @@ -2,7 +2,14 @@ import sys import argparse -from vyos.ifconfig import Interface +from vyos.ifconfig import Section + + +def matching(feature): + for section in Section.feature(feature): + for intf in Section.interfaces(section): + yield intf + parser = argparse.ArgumentParser() group = parser.add_mutually_exclusive_group() @@ -13,46 +20,23 @@ group.add_argument("-bo", "--bondable", action="store_true", help="List all bond args = parser.parse_args() -# XXX: Need to be rewritten using the data in the class definition -# XXX: It can be done once vti and input are moved into vyos -# XXX: We store for each class what type they are (broadcast, bridgeabe, ...) - if args.type: try: - interfaces = Interface.listing(args.type) - + interfaces = Section.interfaces(args.type) + print(" ".join(interfaces)) except ValueError as e: print(e, file=sys.stderr) print("") elif args.broadcast: - eth = Interface.listing("ethernet") - bridge = Interface.listing("bridge") - bond = Interface.listing("bonding") - interfaces = eth + bridge + bond + print(" ".join(matching("broadcast"))) elif args.bridgeable: - eth = Interface.listing("ethernet") - bond = Interface.listing("bonding") - l2tpv3 = Interface.listing("l2tpv3") - openvpn = Interface.listing("openvpn") - wireless = Interface.listing("wireless") - tunnel = Interface.listing("tunnel") - vxlan = Interface.listing("vxlan") - geneve = Interface.listing("geneve") - - interfaces = eth + bond + l2tpv3 + openvpn + vxlan + tunnel + wireless + geneve + print(" ".join(matching("bridgeable"))) elif args.bondable: - interfaces = [] - eth = Interface.listing("ethernet") - # we need to filter out VLAN interfaces identified by a dot (.) in their name - for intf in eth: - if not '.' in intf: - interfaces.append(intf) + print(" ".join([intf for intf in matching("bondable") if '.' not in intf])) else: - interfaces = Interface.listing() - -print(" ".join(interfaces)) + print(" ".join(Section.interfaces())) diff --git a/src/completion/list_openvpn_clients.py b/src/completion/list_openvpn_clients.py index 17b0c7008..177ac90c9 100755 --- a/src/completion/list_openvpn_clients.py +++ b/src/completion/list_openvpn_clients.py @@ -18,7 +18,7 @@ import os import sys import argparse -from vyos.ifconfig import Interface +from vyos.ifconfig import Section def get_client_from_interface(interface): clients = [] @@ -50,7 +50,7 @@ if __name__ == "__main__": if args.interface: clients = get_client_from_interface(args.interface) elif args.all: - for interface in Interface.listing("openvpn"): + for interface in Section.interfaces("openvpn"): clients += get_client_from_interface(interface) print(" ".join(clients)) diff --git a/src/conf_mode/accel_l2tp.py b/src/conf_mode/accel_l2tp.py deleted file mode 100755 index 4ca5a858a..000000000 --- a/src/conf_mode/accel_l2tp.py +++ /dev/null @@ -1,397 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2019-2020 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 re -import jinja2 -import socket -import time - -from jinja2 import FileSystemLoader, Environment - -from vyos.config import Config -from vyos.defaults import directories as vyos_data_dir -from vyos import ConfigError -from vyos.util import run - - -pidfile = r'/var/run/accel_l2tp.pid' -l2tp_cnf_dir = r'/etc/accel-ppp/l2tp' -chap_secrets = l2tp_cnf_dir + '/chap-secrets' -l2tp_conf = l2tp_cnf_dir + '/l2tp.config' -# accel-pppd -d -c /etc/accel-ppp/l2tp/l2tp.config -p /var/run/accel_l2tp.pid - -# config path creation -if not os.path.exists(l2tp_cnf_dir): - os.makedirs(l2tp_cnf_dir) - -### -# inline helper functions -### -# depending on hw and threads, daemon needs a little to start -# if it takes longer than 100 * 0.5 secs, exception is being raised -# not sure if that's the best way to check it, but it worked so far quite well -### - - -def chk_con(): - cnt = 0 - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - while True: - try: - s.connect(("127.0.0.1", 2004)) - break - except ConnectionRefusedError: - time.sleep(0.5) - cnt += 1 - if cnt == 100: - raise("failed to start l2tp server") - break - - -def _accel_cmd(command): - return run(f'/usr/bin/accel-cmd -p 2004 {command}') - -### -# inline helper functions end -### - - -def get_config(): - c = Config() - if not c.exists('vpn l2tp remote-access '): - return None - - c.set_level('vpn l2tp remote-access') - config_data = { - 'authentication': { - 'mode': 'local', - 'local-users': { - }, - 'radiussrv': {}, - 'radiusopt': {}, - 'auth_proto': [], - 'mppe': 'prefer' - }, - 'outside_addr': '', - 'gateway_address': '10.255.255.0', - 'dns': [], - 'dnsv6': [], - 'wins': [], - 'client_ip_pool': None, - 'client_ip_subnets': [], - 'client_ipv6_pool': {}, - 'mtu': '1436', - 'ip6_column': '', - 'ip6_dp_column': '', - 'ppp_options': {}, - } - - ### general options ### - - if c.exists('dns-servers server-1'): - config_data['dns'].append(c.return_value('dns-servers server-1')) - if c.exists('dns-servers server-2'): - config_data['dns'].append(c.return_value('dns-servers server-2')) - if c.exists('dnsv6-servers'): - for dns6_server in c.return_values('dnsv6-servers'): - config_data['dnsv6'].append(dns6_server) - if c.exists('wins-servers server-1'): - config_data['wins'].append(c.return_value('wins-servers server-1')) - if c.exists('wins-servers server-2'): - config_data['wins'].append(c.return_value('wins-servers server-2')) - if c.exists('outside-address'): - config_data['outside_addr'] = c.return_value('outside-address') - - # auth local - if c.exists('authentication mode local'): - if c.exists('authentication local-users username'): - for usr in c.list_nodes('authentication local-users username'): - config_data['authentication']['local-users'].update( - { - usr: { - 'passwd': '', - 'state': 'enabled', - 'ip': '*', - 'upload': None, - 'download': None - } - } - ) - - if c.exists('authentication local-users username ' + usr + ' password'): - config_data['authentication']['local-users'][usr]['passwd'] = c.return_value( - 'authentication local-users username ' + usr + ' password') - if c.exists('authentication local-users username ' + usr + ' disable'): - config_data['authentication']['local-users'][usr]['state'] = 'disable' - if c.exists('authentication local-users username ' + usr + ' static-ip'): - config_data['authentication']['local-users'][usr]['ip'] = c.return_value( - 'authentication local-users username ' + usr + ' static-ip') - if c.exists('authentication local-users username ' + usr + ' rate-limit download'): - config_data['authentication']['local-users'][usr]['download'] = c.return_value( - 'authentication local-users username ' + usr + ' rate-limit download') - if c.exists('authentication local-users username ' + usr + ' rate-limit upload'): - config_data['authentication']['local-users'][usr]['upload'] = c.return_value( - 'authentication local-users username ' + usr + ' rate-limit upload') - - # authentication mode radius servers and settings - - if c.exists('authentication mode radius'): - config_data['authentication']['mode'] = 'radius' - rsrvs = c.list_nodes('authentication radius server') - for rsrv in rsrvs: - if c.return_value('authentication radius server ' + rsrv + ' fail-time') == None: - ftime = '0' - else: - ftime = str(c.return_value( - 'authentication radius server ' + rsrv + ' fail-time')) - if c.return_value('authentication radius-server ' + rsrv + ' req-limit') == None: - reql = '0' - else: - reql = str(c.return_value( - 'authentication radius server ' + rsrv + ' req-limit')) - - config_data['authentication']['radiussrv'].update( - { - rsrv: { - 'secret': c.return_value('authentication radius server ' + rsrv + ' key'), - 'fail-time': ftime, - 'req-limit': reql - } - } - ) - # Source ip address feature - if c.exists('authentication radius source-address'): - config_data['authentication']['radius_source_address'] = c.return_value( - 'authentication radius source-address') - - # advanced radius-setting - if c.exists('authentication radius acct-timeout'): - config_data['authentication']['radiusopt']['acct-timeout'] = c.return_value( - 'authentication radius acct-timeout') - if c.exists('authentication radius max-try'): - config_data['authentication']['radiusopt']['max-try'] = c.return_value( - 'authentication radius max-try') - if c.exists('authentication radius timeout'): - config_data['authentication']['radiusopt']['timeout'] = c.return_value( - 'authentication radius timeout') - if c.exists('authentication radius nas-identifier'): - config_data['authentication']['radiusopt']['nas-id'] = c.return_value( - 'authentication radius nas-identifier') - if c.exists('authentication radius dae-server'): - # Set default dae-server port if not defined - if c.exists('authentication radius dae-server port'): - dae_server_port = c.return_value( - 'authentication radius dae-server port') - else: - dae_server_port = "3799" - config_data['authentication']['radiusopt'].update( - { - 'dae-srv': { - 'ip-addr': c.return_value('authentication radius dae-server ip-address'), - 'port': dae_server_port, - 'secret': str(c.return_value('authentication radius dae-server secret')) - } - } - ) - # filter-id is the internal accel default if attribute is empty - # set here as default for visibility which may change in the future - if c.exists('authentication radius rate-limit enable'): - if not c.exists('authentication radius rate-limit attribute'): - config_data['authentication']['radiusopt']['shaper'] = { - 'attr': 'Filter-Id' - } - else: - config_data['authentication']['radiusopt']['shaper'] = { - 'attr': c.return_value('authentication radius rate-limit attribute') - } - if c.exists('authentication radius rate-limit vendor'): - config_data['authentication']['radiusopt']['shaper']['vendor'] = c.return_value( - 'authentication radius rate-limit vendor') - - if c.exists('client-ip-pool'): - if c.exists('client-ip-pool start') and c.exists('client-ip-pool stop'): - config_data['client_ip_pool'] = c.return_value( - 'client-ip-pool start') + '-' + re.search('[0-9]+$', c.return_value('client-ip-pool stop')).group(0) - - if c.exists('client-ip-pool subnet'): - config_data['client_ip_subnets'] = c.return_values( - 'client-ip-pool subnet') - - if c.exists('client-ipv6-pool prefix'): - config_data['client_ipv6_pool']['prefix'] = c.return_values( - 'client-ipv6-pool prefix') - config_data['ip6_column'] = 'ip6,' - if c.exists('client-ipv6-pool delegate-prefix'): - config_data['client_ipv6_pool']['delegate_prefix'] = c.return_values( - 'client-ipv6-pool delegate-prefix') - config_data['ip6_dp_column'] = 'ip6-dp,' - - if c.exists('mtu'): - config_data['mtu'] = c.return_value('mtu') - - # gateway address - if c.exists('gateway-address'): - config_data['gateway_address'] = c.return_value('gateway-address') - else: - # calculate gw-ip-address - if c.exists('client-ip-pool start'): - # use start ip as gw-ip-address - config_data['gateway_address'] = c.return_value( - 'client-ip-pool start') - elif c.exists('client-ip-pool subnet'): - # use first ip address from first defined pool - lst_ip = re.findall("\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}", c.return_values( - 'client-ip-pool subnet')[0]) - config_data['gateway_address'] = lst_ip[0] - - if c.exists('authentication require'): - auth_mods = {'pap': 'pap', 'chap': 'auth_chap_md5', - 'mschap': 'auth_mschap_v1', 'mschap-v2': 'auth_mschap_v2'} - for proto in c.return_values('authentication require'): - config_data['authentication']['auth_proto'].append( - auth_mods[proto]) - else: - config_data['authentication']['auth_proto'] = ['auth_mschap_v2'] - - if c.exists('authentication mppe'): - config_data['authentication']['mppe'] = c.return_value( - 'authentication mppe') - - if c.exists('idle'): - config_data['idle_timeout'] = c.return_value('idle') - - # LNS secret - if c.exists('lns shared-secret'): - config_data['lns_shared_secret'] = c.return_value('lns shared-secret') - - if c.exists('ccp-disable'): - config_data['ccp_disable'] = True - - # ppp_options - ppp_options = {} - if c.exists('ppp-options'): - if c.exists('ppp-options lcp-echo-failure'): - ppp_options['lcp-echo-failure'] = c.return_value( - 'ppp-options lcp-echo-failure') - if c.exists('ppp-options lcp-echo-interval'): - ppp_options['lcp-echo-interval'] = c.return_value( - 'ppp-options lcp-echo-interval') - - if len(ppp_options) != 0: - config_data['ppp_options'] = ppp_options - - return config_data - - -def verify(c): - if c == None: - return None - - if c['authentication']['mode'] == 'local': - if not c['authentication']['local-users']: - raise ConfigError( - 'l2tp-server authentication local-users required') - for usr in c['authentication']['local-users']: - if not c['authentication']['local-users'][usr]['passwd']: - raise ConfigError('user ' + usr + ' requires a password') - - if c['authentication']['mode'] == 'radius': - if len(c['authentication']['radiussrv']) == 0: - raise ConfigError('radius server required') - for rsrv in c['authentication']['radiussrv']: - if c['authentication']['radiussrv'][rsrv]['secret'] == None: - raise ConfigError('radius server ' + rsrv + - ' needs a secret configured') - - # check for the existence of a client ip pool - if not c['client_ip_pool'] and not c['client_ip_subnets']: - raise ConfigError( - "set vpn l2tp remote-access client-ip-pool requires subnet or start/stop IP pool") - - # check ipv6 - if 'delegate_prefix' in c['client_ipv6_pool'] and not 'prefix' in c['client_ipv6_pool']: - raise ConfigError( - "\"set vpn l2tp remote-access client-ipv6-pool prefix\" required for delegate-prefix ") - - if len(c['dnsv6']) > 3: - raise ConfigError("Maximum allowed dnsv6-servers addresses is 3") - - -def generate(c): - if c == None: - return None - - # Prepare Jinja2 template loader from files - tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'l2tp') - fs_loader = FileSystemLoader(tmpl_path) - env = Environment(loader=fs_loader, trim_blocks=True) - - # accel-cmd reload doesn't work so any change results in a restart of the daemon - try: - if os.cpu_count() == 1: - c['thread_cnt'] = 1 - else: - c['thread_cnt'] = int(os.cpu_count()/2) - except KeyError: - if os.cpu_count() == 1: - c['thread_cnt'] = 1 - else: - c['thread_cnt'] = int(os.cpu_count()/2) - - tmpl = env.get_template('l2tp.config.tmpl') - config_text = tmpl.render(c) - open(l2tp_conf, 'w').write(config_text) - - if c['authentication']['local-users']: - tmpl = env.get_template('chap-secrets.tmpl') - chap_secrets_txt = tmpl.render(c) - old_umask = os.umask(0o077) - open(chap_secrets, 'w').write(chap_secrets_txt) - os.umask(old_umask) - - return c - - -def apply(c): - if c == None: - if os.path.exists(pidfile): - _accel_cmd('shutdown hard') - if os.path.exists(pidfile): - os.remove(pidfile) - return None - - if not os.path.exists(pidfile): - ret = run(f'/usr/sbin/accel-pppd -c {l2tp_conf} -p {pidfile} -d') - chk_con() - if ret != 0 and os.path.exists(pidfile): - os.remove(pidfile) - raise ConfigError('accel-pppd failed to start') - else: - # if gw ip changes, only restart doesn't work - _accel_cmd('restart') - - -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/bcast_relay.py b/src/conf_mode/bcast_relay.py index 8d4c4a89a..a3bc76ef8 100755 --- a/src/conf_mode/bcast_relay.py +++ b/src/conf_mode/bcast_relay.py @@ -19,12 +19,11 @@ import fnmatch from sys import exit from copy import deepcopy -from jinja2 import FileSystemLoader, Environment from vyos.config import Config -from vyos.defaults import directories as vyos_data_dir from vyos import ConfigError from vyos.util import call +from vyos.template import render config_file = r'/etc/default/udp-broadcast-relay' @@ -112,11 +111,6 @@ def generate(relay): if relay is None: return None - # Prepare Jinja2 template loader from files - tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'bcast-relay') - fs_loader = FileSystemLoader(tmpl_path) - env = Environment(loader=fs_loader) - config_dir = os.path.dirname(config_file) config_filename = os.path.basename(config_file) active_configs = [] @@ -146,16 +140,13 @@ def generate(relay): # configuration filename contains instance id file = config_file + str(r['id']) - tmpl = env.get_template('udp-broadcast-relay.tmpl') - config_text = tmpl.render(r) - with open(file, 'w') as f: - f.write(config_text) + render(file, 'bcast-relay/udp-broadcast-relay.tmpl', r) return None def apply(relay): # first stop all running services - call('sudo systemctl stop udp-broadcast-relay@{1..99}') + call('systemctl stop udp-broadcast-relay@{1..99}.service') if (relay is None) or relay['disabled']: return None @@ -165,7 +156,7 @@ def apply(relay): # Don't start individual instance when it's disabled if r['disabled']: continue - call('sudo systemctl start udp-broadcast-relay@{0}'.format(r['id'])) + call('systemctl start udp-broadcast-relay@{0}.service'.format(r['id'])) return None diff --git a/src/conf_mode/dhcp_relay.py b/src/conf_mode/dhcp_relay.py index c92d6a4e1..ce0e01308 100755 --- a/src/conf_mode/dhcp_relay.py +++ b/src/conf_mode/dhcp_relay.py @@ -16,15 +16,14 @@ import os -from jinja2 import FileSystemLoader, Environment from sys import exit from vyos.config import Config -from vyos.defaults import directories as vyos_data_dir -from vyos import ConfigError +from vyos.template import render from vyos.util import call +from vyos import ConfigError -config_file = r'/etc/default/isc-dhcp-relay' +config_file = r'/run/dhcp-relay/dhcp.conf' default_config_data = { 'interface': [], @@ -96,28 +95,25 @@ def verify(relay): def generate(relay): # bail out early - looks like removal from running config - if relay is None: + if not relay: return None - # Prepare Jinja2 template loader from files - tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'dhcp-relay') - fs_loader = FileSystemLoader(tmpl_path) - env = Environment(loader=fs_loader) - - tmpl = env.get_template('config.tmpl') - config_text = tmpl.render(relay) - with open(config_file, 'w') as f: - f.write(config_text) + # Create configuration directory on demand + dirname = os.path.dirname(config_file) + if not os.path.isdir(dirname): + os.mkdir(dirname) + render(config_file, 'dhcp-relay/config.tmpl', relay) return None def apply(relay): - if relay is not None: - call('sudo systemctl restart isc-dhcp-relay.service') + if relay: + call('systemctl restart isc-dhcp-relay.service') else: # DHCP relay support is removed in the commit - call('sudo systemctl stop isc-dhcp-relay.service') - os.unlink(config_file) + call('systemctl stop isc-dhcp-relay.service') + if os.path.exists(config_file): + os.unlink(config_file) return None diff --git a/src/conf_mode/dhcp_server.py b/src/conf_mode/dhcp_server.py index 553247b88..da01f16eb 100755 --- a/src/conf_mode/dhcp_server.py +++ b/src/conf_mode/dhcp_server.py @@ -17,25 +17,19 @@ import os from ipaddress import ip_address, ip_network -from jinja2 import FileSystemLoader, Environment from socket import inet_ntoa from struct import pack from sys import exit from vyos.config import Config -from vyos.defaults import directories as vyos_data_dir from vyos.validate import is_subnet_connected from vyos import ConfigError -from vyos.util import call +from vyos.template import render +from vyos.util import call, chown - -config_file = r'/etc/dhcp/dhcpd.conf' -lease_file = r'/config/dhcpd.leases' -pid_file = r'/var/run/dhcpd.pid' -daemon_config_file = r'/etc/default/isc-dhcpv4-server' +config_file = r'/run/dhcp-server/dhcpd.conf' default_config_data = { - 'lease_file': lease_file, 'disabled': False, 'ddns_enable': False, 'global_parameters': [], @@ -451,7 +445,7 @@ def get_config(): return dhcp def verify(dhcp): - if (dhcp is None) or (dhcp['disabled'] is True): + if not dhcp or dhcp['disabled']: return None # If DHCP is enabled we need one share-network @@ -597,49 +591,29 @@ def verify(dhcp): return None def generate(dhcp): - if dhcp is None: - return None - - if dhcp['disabled'] is True: - print('Warning: DHCP server will be deactivated because it is disabled') + if not dhcp or dhcp['disabled']: return None - # Prepare Jinja2 template loader from files - tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'dhcp-server') - fs_loader = FileSystemLoader(tmpl_path) - env = Environment(loader=fs_loader) + # Create configuration directory on demand + dirname = os.path.dirname(config_file) + if not os.path.isdir(dirname): + os.mkdir(dirname) - tmpl = env.get_template('dhcpd.conf.tmpl') - config_text = tmpl.render(dhcp) # Please see: https://phabricator.vyos.net/T1129 for quoting of the raw parameters # we can pass to ISC DHCPd - config_text = config_text.replace(""",'"') - - with open(config_file, 'w') as f: - f.write(config_text) - - tmpl = env.get_template('daemon.tmpl') - config_text = tmpl.render(dhcp) - with open(daemon_config_file, 'w') as f: - f.write(config_text) - + render(config_file, 'dhcp-server/dhcpd.conf.tmpl', dhcp, + formater=lambda _: _.replace(""", '"')) return None def apply(dhcp): - if (dhcp is None) or dhcp['disabled']: + if not dhcp or dhcp['disabled']: # DHCP server is removed in the commit - call('sudo systemctl stop isc-dhcpv4-server.service') + call('systemctl stop isc-dhcp-server.service') if os.path.exists(config_file): os.unlink(config_file) - if os.path.exists(daemon_config_file): - os.unlink(daemon_config_file) - else: - # If our file holding DHCP leases does yet not exist - create it - if not os.path.exists(lease_file): - os.mknod(lease_file) - - call('sudo systemctl restart isc-dhcpv4-server.service') + return None + call('systemctl restart isc-dhcp-server.service') return None if __name__ == '__main__': diff --git a/src/conf_mode/dhcpv6_relay.py b/src/conf_mode/dhcpv6_relay.py index 9355d9794..cb5a4bbfb 100755 --- a/src/conf_mode/dhcpv6_relay.py +++ b/src/conf_mode/dhcpv6_relay.py @@ -18,15 +18,13 @@ import os from sys import exit from copy import deepcopy -from jinja2 import FileSystemLoader, Environment from vyos.config import Config -from vyos.defaults import directories as vyos_data_dir from vyos import ConfigError from vyos.util import call +from vyos.template import render - -config_file = r'/etc/default/isc-dhcpv6-relay' +config_file = r'/run/dhcp-relay/dhcpv6.conf' default_config_data = { 'listen_addr': [], @@ -86,25 +84,22 @@ def generate(relay): if relay is None: return None - # Prepare Jinja2 template loader from files - tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'dhcpv6-relay') - fs_loader = FileSystemLoader(tmpl_path) - env = Environment(loader=fs_loader) - - tmpl = env.get_template('config.tmpl') - config_text = tmpl.render(relay) - with open(config_file, 'w') as f: - f.write(config_text) + # Create configuration directory on demand + dirname = os.path.dirname(config_file) + if not os.path.isdir(dirname): + os.mkdir(dirname) + render(config_file, 'dhcpv6-relay/config.tmpl', relay) return None def apply(relay): if relay is not None: - call('sudo systemctl restart isc-dhcpv6-relay.service') + call('systemctl restart isc-dhcp-relay6.service') else: # DHCPv6 relay support is removed in the commit - call('sudo systemctl stop isc-dhcpv6-relay.service') - os.unlink(config_file) + call('systemctl stop isc-dhcp-relay6.service') + if os.path.exists(config_file): + os.unlink(config_file) return None diff --git a/src/conf_mode/dhcpv6_server.py b/src/conf_mode/dhcpv6_server.py index 950ca1ce2..94a307826 100755 --- a/src/conf_mode/dhcpv6_server.py +++ b/src/conf_mode/dhcpv6_server.py @@ -19,22 +19,16 @@ import ipaddress from sys import exit from copy import deepcopy -from jinja2 import FileSystemLoader, Environment from vyos.config import Config -from vyos.defaults import directories as vyos_data_dir +from vyos.template import render +from vyos.util import call from vyos.validate import is_subnet_connected from vyos import ConfigError -from vyos.util import call - -config_file = r'/etc/dhcp/dhcpdv6.conf' -lease_file = r'/config/dhcpdv6.leases' -pid_file = r'/var/run/dhcpdv6.pid' -daemon_config_file = r'/etc/default/isc-dhcpv6-server' +config_file = r'/run/dhcp-server/dhcpdv6.conf' default_config_data = { - 'lease_file': lease_file, 'preference': '', 'disabled': False, 'shared_network': [] @@ -222,10 +216,7 @@ def get_config(): return dhcpv6 def verify(dhcpv6): - if dhcpv6 is None: - return None - - if dhcpv6['disabled']: + if not dhcpv6 or dhcpv6['disabled']: return None # If DHCP is enabled we need one share-network @@ -337,44 +328,25 @@ def verify(dhcpv6): return None def generate(dhcpv6): - if dhcpv6 is None: + if not dhcpv6 or dhcpv6['disabled']: return None - if dhcpv6['disabled']: - print('Warning: DHCPv6 server will be deactivated because it is disabled') - return None - - # Prepare Jinja2 template loader from files - tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'dhcpv6-server') - fs_loader = FileSystemLoader(tmpl_path) - env = Environment(loader=fs_loader) - - tmpl = env.get_template('dhcpdv6.conf.tmpl') - config_text = tmpl.render(dhcpv6) - with open(config_file, 'w') as f: - f.write(config_text) - - tmpl = env.get_template('daemon.tmpl') - config_text = tmpl.render(dhcpv6) - with open(daemon_config_file, 'w') as f: - f.write(config_text) + # Create configuration directory on demand + dirname = os.path.dirname(config_file) + if not os.path.isdir(dirname): + os.mkdir(dirname) + render(config_file, 'dhcpv6-server/dhcpdv6.conf.tmpl', dhcpv6) return None def apply(dhcpv6): - if (dhcpv6 is None) or dhcpv6['disabled']: + if not dhcpv6 or dhcpv6['disabled']: # DHCP server is removed in the commit - call('sudo systemctl stop isc-dhcpv6-server.service') + call('systemctl stop isc-dhcp-server6.service') if os.path.exists(config_file): os.unlink(config_file) - if os.path.exists(daemon_config_file): - os.unlink(daemon_config_file) - else: - # If our file holding DHCPv6 leases does yet not exist - create it - if not os.path.exists(lease_file): - os.mknod(lease_file) - call('sudo systemctl restart isc-dhcpv6-server.service') + call('systemctl restart isc-dhcp-server6.service') return None diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py index 4071c05c9..567dfa4b3 100755 --- a/src/conf_mode/dns_forwarding.py +++ b/src/conf_mode/dns_forwarding.py @@ -19,20 +19,19 @@ import argparse from sys import exit from copy import deepcopy -from jinja2 import FileSystemLoader, Environment from vyos.config import Config -from vyos.defaults import directories as vyos_data_dir from vyos.hostsd_client import Client as hostsd_client from vyos.util import wait_for_commit_lock from vyos import ConfigError from vyos.util import call +from vyos.template import render parser = argparse.ArgumentParser() parser.add_argument("--dhclient", action="store_true", help="Started from dhclient-script") -config_file = r'/etc/powerdns/recursor.conf' +config_file = r'/run/powerdns/recursor.conf' default_config_data = { 'allow_from': [], @@ -153,25 +152,21 @@ def generate(dns): if dns is None: return None - # Prepare Jinja2 template loader from files - tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'dns-forwarding') - fs_loader = FileSystemLoader(tmpl_path) - env = Environment(loader=fs_loader, trim_blocks=True) + dirname = os.path.dirname(config_file) + if not os.path.exists(dirname): + os.mkdir(dirname) - tmpl = env.get_template('recursor.conf.tmpl') - config_text = tmpl.render(dns) - with open(config_file, 'w') as f: - f.write(config_text) + render(config_file, 'dns-forwarding/recursor.conf.tmpl', dns, trim_blocks=True) return None def apply(dns): if dns is None: # DNS forwarding is removed in the commit - call("systemctl stop pdns-recursor") + call("systemctl stop pdns-recursor.service") if os.path.isfile(config_file): os.unlink(config_file) else: - call("systemctl restart pdns-recursor") + call("systemctl restart pdns-recursor.service") if __name__ == '__main__': args = parser.parse_args() diff --git a/src/conf_mode/dynamic_dns.py b/src/conf_mode/dynamic_dns.py index b54d76b06..038f77cf9 100755 --- a/src/conf_mode/dynamic_dns.py +++ b/src/conf_mode/dynamic_dns.py @@ -18,18 +18,14 @@ import os from sys import exit from copy import deepcopy -from jinja2 import FileSystemLoader, Environment from stat import S_IRUSR, S_IWUSR from vyos.config import Config -from vyos.defaults import directories as vyos_data_dir from vyos import ConfigError from vyos.util import call +from vyos.template import render - -config_file = r'/etc/ddclient/ddclient.conf' -cache_file = r'/var/cache/ddclient/ddclient.cache' -pid_file = r'/var/run/ddclient/ddclient.pid' +config_file = r'/run/ddclient/ddclient.conf' # Mapping of service name to service protocol default_service_protocol = { @@ -48,9 +44,7 @@ default_service_protocol = { default_config_data = { 'interfaces': [], - 'cache_file': cache_file, - 'deleted': False, - 'pid_file': pid_file + 'deleted': False } def get_config(): @@ -221,28 +215,13 @@ def verify(dyndns): def generate(dyndns): # bail out early - looks like removal from running config if dyndns['deleted']: - if os.path.exists(config_file): - os.unlink(config_file) - return None - # Prepare Jinja2 template loader from files - tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'dynamic-dns') - fs_loader = FileSystemLoader(tmpl_path) - env = Environment(loader=fs_loader) - - dirname = os.path.dirname(dyndns['pid_file']) - if not os.path.exists(dirname): - os.mkdir(dirname) - dirname = os.path.dirname(config_file) if not os.path.exists(dirname): os.mkdir(dirname) - tmpl = env.get_template('ddclient.conf.tmpl') - config_text = tmpl.render(dyndns) - with open(config_file, 'w') as f: - f.write(config_text) + render(config_file, 'dynamic-dns/ddclient.conf.tmpl', dyndns) # Config file must be accessible only by its owner os.chmod(config_file, S_IRUSR | S_IWUSR) @@ -250,18 +229,13 @@ def generate(dyndns): return None def apply(dyndns): - if os.path.exists(dyndns['cache_file']): - os.unlink(dyndns['cache_file']) - - if os.path.exists('/etc/ddclient.conf'): - os.unlink('/etc/ddclient.conf') - if dyndns['deleted']: - call('/etc/init.d/ddclient stop') - if os.path.exists(dyndns['pid_file']): - os.unlink(dyndns['pid_file']) + call('systemctl stop ddclient.service') + if os.path.exists(config_file): + os.unlink(config_file) + else: - call('/etc/init.d/ddclient restart') + call('systemctl restart ddclient.service') return None diff --git a/src/conf_mode/flow_accounting_conf.py b/src/conf_mode/flow_accounting_conf.py index 1008f3fae..1354488ac 100755 --- a/src/conf_mode/flow_accounting_conf.py +++ b/src/conf_mode/flow_accounting_conf.py @@ -16,17 +16,18 @@ import os import re +from sys import exit import ipaddress from ipaddress import ip_address from jinja2 import FileSystemLoader, Environment -from sys import exit +from vyos.ifconfig import Section from vyos.ifconfig import Interface from vyos.config import Config -from vyos.defaults import directories as vyos_data_dir from vyos import ConfigError from vyos.util import cmd +from vyos.render import render # default values @@ -60,7 +61,7 @@ def _sflow_default_agentip(config): return config.return_value('protocols ospfv3 parameters router-id') # if router-id was not found, use first available ip of any interface - for iface in Interface.listing(): + for iface in Section.interfaces(): for address in Interface(iface).get_addr(): # return an IP, if this is not loopback regex_filter = re.compile('^(?!(127)|(::1)|(fe80))(?P<ipaddr>[a-f\d\.:]+)/\d+$') @@ -82,7 +83,7 @@ def _iptables_get_nflog(): for iptables_variant in ['iptables', 'ip6tables']: # run iptables, save output and split it by lines iptables_command = "sudo {0} -t {1} -S {2}".format(iptables_variant, iptables_nflog_table, iptables_nflog_chain) - cmd(iptables_command, universal_newlines=True, message='Failed to get flows list') + cmd(iptables_command, message='Failed to get flows list') iptables_out = stdout.splitlines() # parse each line and add information to list @@ -234,7 +235,7 @@ def verify(config): # check that all configured interfaces exists in the system for iface in config['interfaces']: - if not iface in Interface.listing(): + if not iface in Section.interfaces(): # chnged from error to warning to allow adding dynamic interfaces and interface templates # raise ConfigError("The {} interface is not presented in the system".format(iface)) print("Warning: the {} interface is not presented in the system".format(iface)) @@ -262,7 +263,7 @@ def verify(config): # check if configured sFlow agent-id exist in the system agent_id_presented = None - for iface in Interface.listing(): + for iface in Section.interfaces(): for address in Interface(iface).get_addr(): # check an IP, if this is not loopback regex_filter = re.compile('^(?!(127)|(::1)|(fe80))(?P<ipaddr>[a-f\d\.:]+)/\d+$') @@ -334,16 +335,10 @@ def generate(config): timeout_string = "{}:{}={}".format(timeout_string, timeout_type, timeout_value) config['netflow']['timeout_string'] = timeout_string - # Prepare Jinja2 template loader from files - tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'netflow') - fs_loader = FileSystemLoader(tmpl_path) - env = Environment(loader=fs_loader) - - # Generate daemon configs - tmpl = env.get_template('uacctd.conf.tmpl') - config_text = tmpl.render(templatecfg = config, snaplen = default_captured_packet_size) - with open(uacctd_conf_path, 'w') as file: - file.write(config_text) + render(uacctd_conf_path, 'netflow/uacctd.conf.tmpl', { + 'templatecfg': config, + 'snaplen': default_captured_packet_size, + }) def apply(config): @@ -351,9 +346,9 @@ def apply(config): command = None # Check if flow-accounting was removed and define command if not config['flow-accounting-configured']: - command = '/usr/bin/sudo /bin/systemctl stop uacctd' + command = 'systemctl stop uacctd.service' else: - command = '/usr/bin/sudo /bin/systemctl restart uacctd' + command = 'systemctl restart uacctd.service' # run command to start or stop flow-accounting cmd(command, raising=ConfigError, message='Failed to start/stop flow-accounting') diff --git a/src/conf_mode/host_name.py b/src/conf_mode/host_name.py index 7c2f79abc..dd5819f9f 100755 --- a/src/conf_mode/host_name.py +++ b/src/conf_mode/host_name.py @@ -173,7 +173,7 @@ def apply(config): # restart pdns if it is used ret = run('/usr/bin/rec_control ping') if ret == 0: - call('/etc/init.d/pdns-recursor restart >/dev/null') + call('systemctl restart pdns-recursor.service') return None diff --git a/src/conf_mode/https.py b/src/conf_mode/https.py index da7193c9b..7d3a1b9cb 100755 --- a/src/conf_mode/https.py +++ b/src/conf_mode/https.py @@ -18,15 +18,14 @@ import os from sys import exit from copy import deepcopy -from jinja2 import FileSystemLoader, Environment import vyos.defaults import vyos.certbot_util from vyos.config import Config -from vyos.defaults import directories as vyos_data_dir from vyos import ConfigError from vyos.util import call +from vyos.template import render config_file = '/etc/nginx/sites-available/default' @@ -133,18 +132,10 @@ def generate(https): if https is None: return None - # Prepare Jinja2 template loader from files - tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'https') - fs_loader = FileSystemLoader(tmpl_path) - env = Environment(loader=fs_loader, trim_blocks=True) - if 'server_block_list' not in https or not https['server_block_list']: https['server_block_list'] = [default_server_block] - tmpl = env.get_template('nginx.default.tmpl') - config_text = tmpl.render(https) - with open(config_file, 'w') as f: - f.write(config_text) + render(config_file, 'https/nginx.default.tmpl', https, trim_blocks=True) return None diff --git a/src/conf_mode/igmp_proxy.py b/src/conf_mode/igmp_proxy.py index 77e2bb150..9fa591a2c 100755 --- a/src/conf_mode/igmp_proxy.py +++ b/src/conf_mode/igmp_proxy.py @@ -18,13 +18,12 @@ import os from sys import exit from copy import deepcopy -from jinja2 import FileSystemLoader, Environment from netifaces import interfaces from vyos.config import Config -from vyos.defaults import directories as vyos_data_dir from vyos import ConfigError from vyos.util import call +from vyos.template import render config_file = r'/etc/igmpproxy.conf' @@ -116,16 +115,7 @@ def generate(igmp_proxy): print('Warning: IGMP Proxy will be deactivated because it is disabled') return None - # Prepare Jinja2 template loader from files - tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'igmp-proxy') - fs_loader = FileSystemLoader(tmpl_path) - env = Environment(loader=fs_loader) - - tmpl = env.get_template('igmpproxy.conf.tmpl') - config_text = tmpl.render(igmp_proxy) - with open(config_file, 'w') as f: - f.write(config_text) - + render(config_file, 'igmp-proxy/igmpproxy.conf.tmpl', igmp_proxy) return None def apply(igmp_proxy): diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py index 8a615ec62..b42765586 100755 --- a/src/conf_mode/interfaces-openvpn.py +++ b/src/conf_mode/interfaces-openvpn.py @@ -17,23 +17,20 @@ import os import re -from jinja2 import FileSystemLoader, Environment from copy import deepcopy -from sys import exit -from stat import S_IRUSR,S_IRWXU,S_IRGRP,S_IXGRP,S_IROTH,S_IXOTH -from grp import getgrnam -from ipaddress import ip_address,ip_network,IPv4Interface +from sys import exit,stderr +from ipaddress import IPv4Address,IPv4Network,summarize_address_range from netifaces import interfaces -from pwd import getpwnam from time import sleep from shutil import rmtree from vyos.config import Config -from vyos.defaults import directories as vyos_data_dir from vyos.ifconfig import VTunIf -from vyos.util import process_running, cmd, is_bridge_member +from vyos.util import call, is_bridge_member, chown, chmod_600, chmod_755 from vyos.validate import is_addr_assigned from vyos import ConfigError +from vyos.template import render + user = 'openvpn' group = 'openvpn' @@ -75,10 +72,14 @@ default_config_data = { 'server_domain': '', 'server_max_conn': '', 'server_dns_nameserver': [], + 'server_pool': False, + 'server_pool_start': '', + 'server_pool_stop': '', + 'server_pool_netmask': '', 'server_push_route': [], 'server_reject_unconfigured': False, 'server_subnet': '', - 'server_topology': '', + 'server_topology': 'net30', 'shared_secret_file': '', 'tls': False, 'tls_auth': '', @@ -97,32 +98,9 @@ default_config_data = { def get_config_name(intf): - cfg_file = r'/opt/vyatta/etc/openvpn/openvpn-{}.conf'.format(intf) + cfg_file = f'/run/openvpn/{intf}.conf' return cfg_file -def openvpn_mkdir(directory): - # create directory on demand - if not os.path.exists(directory): - os.mkdir(directory) - - # fix permissions - corresponds to mode 755 - os.chmod(directory, S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH) - uid = getpwnam(user).pw_uid - gid = getgrnam(group).gr_gid - os.chown(directory, uid, gid) - -def fixup_permission(filename, permission=S_IRUSR): - """ - Check if the given file exists and change ownershit to root/vyattacfg - and appripriate file access permissions - default is user and group readable - """ - if os.path.isfile(filename): - os.chmod(filename, permission) - - # make file owned by root / vyattacfg - uid = getpwnam('root').pw_uid - gid = getgrnam('vyattacfg').gr_gid - os.chown(filename, uid, gid) def checkCertHeader(header, filename): """ @@ -139,6 +117,66 @@ def checkCertHeader(header, filename): return False +def getDefaultServer(network, topology, devtype): + """ + Gets the default server parameters for a "server" directive. + Currently only IPv4 routed but may be extended to support bridged and/or IPv6 in the future. + Logic from openvpn's src/openvpn/helper.c. + Returns a dict with addresses or False if the input parameters were incorrect. + """ + if not (topology and devtype): + return False + + if not (devtype == 'tun' or devtype == 'tap'): + return False + + if not network.prefixlen: + return False + elif (devtype == 'tun' and network.prefixlen > 29) or (devtype == 'tap' and network.prefixlen > 30): + return False + + server = { + 'local': '', + 'remote_netmask': '', + 'client_remote_netmask': '', + 'pool_start': '', + 'pool_stop': '', + 'pool_netmask': '' + } + + if devtype == 'tun': + if topology == 'net30' or topology == 'point-to-point': + server['local'] = network[1] + server['remote_netmask'] = network[2] + server['client_remote_netmask'] = server['local'] + + # pool start is 4th host IP in subnet (.4 in a /24) + server['pool_start'] = network[4] + + if network.prefixlen == 29: + server['pool_stop'] = network.broadcast_address + else: + # pool end is -4 from the broadcast address (.251 in a /24) + server['pool_stop'] = network[-5] + + elif topology == 'subnet': + server['local'] = network[1] + server['remote_netmask'] = str(network.netmask) + server['client_remote_netmask'] = server['remote_netmask'] + server['pool_start'] = network[2] + server['pool_stop'] = network[-3] + server['pool_netmask'] = server['remote_netmask'] + + elif devtype == 'tap': + server['local'] = network[1] + server['remote_netmask'] = str(network.netmask) + server['client_remote_netmask'] = server['remote_netmask'] + server['pool_start'] = network[2] + server['pool_stop'] = network[-2] + server['pool_netmask'] = server['remote_netmask'] + + return server + def get_config(): openvpn = deepcopy(default_config_data) conf = Config() @@ -308,10 +346,10 @@ def get_config(): # Server-mode subnet (from which client IPs are allocated) if conf.exists('server subnet'): - network = conf.return_value('server subnet') - tmp = IPv4Interface(network).with_netmask + # server_network is used later in this function + server_network = IPv4Network(conf.return_value('server subnet')) # convert the network in format: "192.0.2.0 255.255.255.0" for later use in template - openvpn['server_subnet'] = tmp.replace(r'/', ' ') + openvpn['server_subnet'] = server_network.with_netmask.replace(r'/', ' ') # Client-specific settings for client in conf.list_nodes('server client'): @@ -326,19 +364,6 @@ def get_config(): 'remote_netmask': '' } - # note: with "topology subnet", this is "<ip> <netmask>". - # with "topology p2p", this is "<ip> <our_ip>". - if openvpn['server_topology'] == 'subnet': - # we are only interested in the netmask portion of server_subnet - data['remote_netmask'] = openvpn['server_subnet'].split(' ')[1] - else: - # we need the server subnet in format 192.0.2.0/255.255.255.0 - subnet = openvpn['server_subnet'].replace(' ', r'/') - # get iterator over the usable hosts in the network - tmp = ip_network(subnet).hosts() - # OpenVPN always uses the subnets first available IP address - data['remote_netmask'] = list(tmp)[0] - # Option to disable client connection if conf.exists('disable'): data['disable'] = True @@ -349,13 +374,11 @@ def get_config(): # Route to be pushed to the client for network in conf.return_values('push-route'): - tmp = IPv4Interface(network).with_netmask - data['push_route'].append(tmp.replace(r'/', ' ')) + data['push_route'].append(IPv4Network(network).with_netmask.replace(r'/', ' ')) # Subnet belonging to the client for network in conf.return_values('subnet'): - tmp = IPv4Interface(network).with_netmask - data['subnet'].append(tmp.replace(r'/', ' ')) + data['subnet'].append(IPv4Network(network).with_netmask.replace(r'/', ' ')) # Append to global client list openvpn['client'].append(data) @@ -363,6 +386,19 @@ def get_config(): # re-set configuration level conf.set_level('interfaces openvpn ' + openvpn['intf']) + # Server client IP pool + if conf.exists('server client-ip-pool'): + openvpn['server_pool'] = True + + if conf.exists('server client-ip-pool start'): + openvpn['server_pool_start'] = conf.return_value('server client-ip-pool start') + + if conf.exists('server client-ip-pool stop'): + openvpn['server_pool_stop'] = conf.return_value('server client-ip-pool stop') + + if conf.exists('server client-ip-pool netmask'): + openvpn['server_pool_netmask'] = conf.return_value('server client-ip-pool netmask') + # DNS suffix to be pushed to all clients if conf.exists('server domain-name'): openvpn['server_domain'] = conf.return_value('server domain-name') @@ -378,8 +414,7 @@ def get_config(): # Route to be pushed to all clients if conf.exists('server push-route'): for network in conf.return_values('server push-route'): - tmp = IPv4Interface(network).with_netmask - openvpn['server_push_route'].append(tmp.replace(r'/', ' ')) + openvpn['server_push_route'].append(IPv4Network(network).with_netmask.replace(r'/', ' ')) # Reject connections from clients that are not explicitly configured if conf.exists('server reject-unconfigured-clients'): @@ -441,6 +476,26 @@ def get_config(): if not openvpn['tls_dh'] and openvpn['tls_key'] and checkCertHeader('-----BEGIN EC PRIVATE KEY-----', openvpn['tls_key']): openvpn['tls_dh'] = 'none' + # Set defaults where necessary. + # If any of the input parameters are missing or wrong, + # this will return False and no defaults will be set. + default_server = getDefaultServer(server_network, openvpn['server_topology'], openvpn['type']) + if default_server: + # server-bridge doesn't require a pool so don't set defaults for it + if not openvpn['bridge_member']: + openvpn['server_pool'] = True + if not openvpn['server_pool_start']: + openvpn['server_pool_start'] = default_server['pool_start'] + + if not openvpn['server_pool_stop']: + openvpn['server_pool_stop'] = default_server['pool_stop'] + + if not openvpn['server_pool_netmask']: + openvpn['server_pool_netmask'] = default_server['pool_netmask'] + + for client in openvpn['client']: + client['remote_netmask'] = default_server['client_remote_netmask'] + return openvpn def verify(openvpn): @@ -535,10 +590,42 @@ def verify(openvpn): if not openvpn['tls_dh'] and not checkCertHeader('-----BEGIN EC PRIVATE KEY-----', openvpn['tls_key']): raise ConfigError('Must specify "tls dh-file" when not using EC keys in server mode') - if not openvpn['server_subnet']: + if openvpn['server_subnet']: + subnet = IPv4Network(openvpn['server_subnet'].replace(' ', '/')) + + if openvpn['type'] == 'tun' and subnet.prefixlen > 29: + raise ConfigError('Server subnets smaller than /29 with device type "tun" are not supported') + elif openvpn['type'] == 'tap' and subnet.prefixlen > 30: + raise ConfigError('Server subnets smaller than /30 with device type "tap" are not supported') + + for client in openvpn['client']: + if client['ip'] and not IPv4Address(client['ip']) in subnet: + raise ConfigError(f'Client IP "{client["ip"]}" not in server subnet "{subnet}"') + + else: if not openvpn['bridge_member']: raise ConfigError('Must specify "server subnet" or "bridge member interface" in server mode') + + if openvpn['server_pool']: + if not (openvpn['server_pool_start'] and openvpn['server_pool_stop']): + raise ConfigError('Server client-ip-pool requires both start and stop addresses in bridged mode') + else: + v4PoolStart = IPv4Address(openvpn['server_pool_start']) + v4PoolStop = IPv4Address(openvpn['server_pool_stop']) + if v4PoolStart > v4PoolStop: + raise ConfigError(f'Server client-ip-pool start address {v4PoolStart} is larger than stop address {v4PoolStop}') + if (int(v4PoolStop) - int(v4PoolStart) >= 65536): + raise ConfigError(f'Server client-ip-pool is too large [{v4PoolStart} -> {v4PoolStop}], maximum is 65536 addresses.') + + v4PoolNets = list(summarize_address_range(v4PoolStart, v4PoolStop)) + for client in openvpn['client']: + if client['ip']: + for v4PoolNet in v4PoolNets: + if IPv4Address(client['ip']) in v4PoolNet: + print(f'Warning: Client "{client["name"]}" IP {client["ip"]} is in server IP pool, it is not reserved for this client.', + file=stderr) + else: # checks for both client and site-to-site go here if openvpn['server_reject_unconfigured']: @@ -665,143 +752,98 @@ def verify(openvpn): if not openvpn['auth_pass']: raise ConfigError('Password for authentication is missing') - # - # Client - # - subnet = openvpn['server_subnet'].replace(' ', '/') - for client in openvpn['client']: - if client['ip'] and not ip_address(client['ip']) in ip_network(subnet): - raise ConfigError('Client IP "{}" not in server subnet "{}'.format(client['ip'], subnet)) - return None def generate(openvpn): if openvpn['deleted'] or openvpn['disable']: return None - # Prepare Jinja2 template loader from files - tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'openvpn') - fs_loader = FileSystemLoader(tmpl_path) - env = Environment(loader=fs_loader) - interface = openvpn['intf'] directory = os.path.dirname(get_config_name(interface)) - # we can't know which clients were deleted, remove all client configs - if os.path.isdir(os.path.join(directory, 'ccd', interface)): - rmtree(os.path.join(directory, 'ccd', interface), ignore_errors=True) + # we can't know in advance which clients have been, + # remove all client configs + ccd_dir = os.path.join(directory, 'ccd', interface) + if os.path.isdir(ccd_dir): + rmtree(ccd_dir, ignore_errors=True) # create config directory on demand - openvpn_mkdir(directory) - # create status directory on demand - openvpn_mkdir(directory + '/status') - # create client config dir on demand - openvpn_mkdir(directory + '/ccd') - # crete client config dir per interface on demand - openvpn_mkdir(directory + '/ccd/' + interface) + directories = [] + directories.append(f'{directory}/status') + directories.append(f'{directory}/ccd/{interface}') + for onedir in directories: + if not os.path.exists(onedir): + os.makedirs(onedir, 0o755) + chown(onedir, user, group) # Fix file permissons for keys - fixup_permission(openvpn['shared_secret_file']) - fixup_permission(openvpn['tls_key']) + fix_permissions = [] + fix_permissions.append(openvpn['shared_secret_file']) + fix_permissions.append(openvpn['tls_key']) # Generate User/Password authentication file + user_auth_file = f'/tmp/openvpn-{interface}-pw' if openvpn['auth']: - auth_file = '/tmp/openvpn-{}-pw'.format(interface) - with open(auth_file, 'w') as f: + with open(user_auth_file, 'w') as f: f.write('{}\n{}'.format(openvpn['auth_user'], openvpn['auth_pass'])) - - fixup_permission(auth_file) + # also change permission on auth file + fix_permissions.append(user_auth_file) else: # delete old auth file if present - if os.path.isfile('/tmp/openvpn-{}-pw'.format(interface)): - os.remove('/tmp/openvpn-{}-pw'.format(interface)) - - # get numeric uid/gid - uid = getpwnam(user).pw_uid - gid = getgrnam(group).gr_gid + if os.path.isfile(user_auth_file): + os.remove(user_auth_file) # Generate client specific configuration for client in openvpn['client']: - client_file = directory + '/ccd/' + interface + '/' + client['name'] - tmpl = env.get_template('client.conf.tmpl') - client_text = tmpl.render(client) - with open(client_file, 'w') as f: - f.write(client_text) - os.chown(client_file, uid, gid) - - tmpl = env.get_template('server.conf.tmpl') - config_text = tmpl.render(openvpn) + client_file = os.path.join(ccd_dir, client['name']) + render(client_file, 'openvpn/client.conf.tmpl', client) + chown(client_file, user, group) + # we need to support quoting of raw parameters from OpenVPN CLI # see https://phabricator.vyos.net/T1632 - config_text = config_text.replace(""",'"') - with open(get_config_name(interface), 'w') as f: - f.write(config_text) - os.chown(get_config_name(interface), uid, gid) + render(get_config_name(interface), 'openvpn/server.conf.tmpl', openvpn, + formater=lambda _: _.replace(""", '"')) + chown(get_config_name(interface), user, group) + + # Fixup file permissions + for file in fix_permissions: + chmod_600(file) return None def apply(openvpn): - pidfile = '/var/run/openvpn/{}.pid'.format(openvpn['intf']) - - # Always stop OpenVPN service. We can not send a SIGUSR1 for restart of the - # service as the configuration is not re-read. Stop daemon only if it's - # running - it could have died or killed by someone evil - if process_running(pidfile): - command = 'start-stop-daemon' - command += ' --stop ' - command += ' --quiet' - command += ' --oknodo' - command += ' --pidfile ' + pidfile - cmd(command) - - # cleanup old PID file - if os.path.isfile(pidfile): - os.remove(pidfile) + interface = openvpn['intf'] + call(f'systemctl stop openvpn@{interface}.service') # Do some cleanup when OpenVPN is disabled/deleted if openvpn['deleted'] or openvpn['disable']: # cleanup old configuration file - if os.path.isfile(get_config_name(openvpn['intf'])): - os.remove(get_config_name(openvpn['intf'])) + if os.path.isfile(get_config_name(interface)): + os.remove(get_config_name(interface)) # cleanup client config dir - directory = os.path.dirname(get_config_name(openvpn['intf'])) - if os.path.isdir(os.path.join(directory, 'ccd', openvpn['intf'])): - rmtree(os.path.join(directory, 'ccd', openvpn['intf']), ignore_errors=True) - - # cleanup auth file - if os.path.isfile('/tmp/openvpn-{}-pw'.format(openvpn['intf'])): - os.remove('/tmp/openvpn-{}-pw'.format(openvpn['intf'])) + directory = os.path.dirname(get_config_name(interface)) + ccd_dir = os.path.join(directory, 'ccd', interface) + if os.path.isdir(ccd_dir): + rmtree(ccd_dir, ignore_errors=True) return None # On configuration change we need to wait for the 'old' interface to # vanish from the Kernel, if it is not gone, OpenVPN will report: # ERROR: Cannot ioctl TUNSETIFF vtun10: Device or resource busy (errno=16) - while openvpn['intf'] in interfaces(): + while interface in interfaces(): sleep(0.250) # 250ms # No matching OpenVPN process running - maybe it got killed or none # existed - nevertheless, spawn new OpenVPN process - command = 'start-stop-daemon' - command += ' --start ' - command += ' --quiet' - command += ' --oknodo' - command += ' --pidfile ' + pidfile - command += ' --exec /usr/sbin/openvpn' - # now pass arguments to openvpn binary - command += ' --' - command += ' --daemon openvpn-' + openvpn['intf'] - command += ' --config ' + get_config_name(openvpn['intf']) - - # execute assembled command - cmd(command) + call(f'systemctl start openvpn@{interface}.service') # better late then sorry ... but we can only set interface alias after # OpenVPN has been launched and created the interface cnt = 0 - while openvpn['intf'] not in interfaces(): + while interface not in interfaces(): # If VPN tunnel can't be established because the peer/server isn't # (temporarily) available, the vtun interface never becomes registered # with the kernel, and the commit would hang if there is no bail out @@ -816,7 +858,7 @@ def apply(openvpn): try: # we need to catch the exception if the interface is not up due to # reason stated above - o = VTunIf(openvpn['intf']) + o = VTunIf(interface) # update interface description used e.g. within SNMP o.set_alias(openvpn['description']) # IPv6 address autoconfiguration @@ -834,7 +876,7 @@ def apply(openvpn): # TAP interface needs to be brought up explicitly if openvpn['type'] == 'tap': if not openvpn['disable']: - VTunIf(openvpn['intf']).set_admin_state('up') + VTunIf(interface).set_admin_state('up') return None diff --git a/src/conf_mode/interfaces-pppoe.py b/src/conf_mode/interfaces-pppoe.py index 353a5a12c..f942b7d2f 100755 --- a/src/conf_mode/interfaces-pppoe.py +++ b/src/conf_mode/interfaces-pppoe.py @@ -18,14 +18,14 @@ import os from sys import exit from copy import deepcopy -from jinja2 import FileSystemLoader, Environment from netifaces import interfaces from vyos.config import Config -from vyos.defaults import directories as vyos_data_dir from vyos.ifconfig import Interface -from vyos.util import chown, chmod_x, cmd +from vyos.util import chown, chmod_755, cmd from vyos import ConfigError +from vyos.template import render + default_config_data = { 'access_concentrator': '', @@ -161,11 +161,6 @@ def verify(pppoe): return None def generate(pppoe): - # Prepare Jinja2 template loader from files - tmpl_path = os.path.join(vyos_data_dir["data"], "templates", "pppoe") - fs_loader = FileSystemLoader(tmpl_path) - env = Environment(loader=fs_loader, trim_blocks=True) - # set up configuration file path variables where our templates will be # rendered into intf = pppoe['intf'] @@ -195,40 +190,26 @@ def generate(pppoe): else: # Create PPP configuration files - tmpl = env.get_template('peer.tmpl') - config_text = tmpl.render(pppoe) - with open(config_pppoe, 'w') as f: - f.write(config_text) - + render(config_pppoe, 'pppoe/peer.tmpl', + pppoe, trim_blocks=True) # Create script for ip-pre-up.d - tmpl = env.get_template('ip-pre-up.script.tmpl') - config_text = tmpl.render(pppoe) - with open(script_pppoe_pre_up, 'w') as f: - f.write(config_text) - + render(script_pppoe_pre_up, 'pppoe/ip-pre-up.script.tmpl', + pppoe, trim_blocks=True) # Create script for ip-up.d - tmpl = env.get_template('ip-up.script.tmpl') - config_text = tmpl.render(pppoe) - with open(script_pppoe_ip_up, 'w') as f: - f.write(config_text) - + render(script_pppoe_ip_up, 'pppoe/ip-up.script.tmpl', + pppoe, trim_blocks=True) # Create script for ip-down.d - tmpl = env.get_template('ip-down.script.tmpl') - config_text = tmpl.render(pppoe) - with open(script_pppoe_ip_down, 'w') as f: - f.write(config_text) - + render(script_pppoe_ip_down, 'pppoe/ip-down.script.tmpl', + pppoe, trim_blocks=True) # Create script for ipv6-up.d - tmpl = env.get_template('ipv6-up.script.tmpl') - config_text = tmpl.render(pppoe) - with open(script_pppoe_ipv6_up, 'w') as f: - f.write(config_text) + render(script_pppoe_ipv6_up, 'pppoe/ipv6-up.script.tmpl', + pppoe, trim_blocks=True) # make generated script file executable - chmod_x(script_pppoe_pre_up) - chmod_x(script_pppoe_ip_up) - chmod_x(script_pppoe_ip_down) - chmod_x(script_pppoe_ipv6_up) + chmod_755(script_pppoe_pre_up) + chmod_755(script_pppoe_ip_up) + chmod_755(script_pppoe_ip_down) + chmod_755(script_pppoe_ipv6_up) return None diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py index 19538da72..c51048aeb 100755 --- a/src/conf_mode/interfaces-tunnel.py +++ b/src/conf_mode/interfaces-tunnel.py @@ -584,11 +584,17 @@ def apply(conf): if changes['section'] in 'create' and option in tunnel.options: # it was setup at creation continue + if not options[option]: + # remote can be set to '' and it would generate an invalide command + continue tunnel.set_interface(option, options[option]) # set other interface properties for option in ('alias', 'mtu', 'link_detect', 'multicast', 'allmulticast', 'vrf', 'ipv6_autoconf', 'ipv6_forwarding', 'ipv6_dad_transmits'): + if not options[option]: + # should never happen but better safe + continue tunnel.set_interface(option, options[option]) # Configure interface address(es) diff --git a/src/conf_mode/interfaces-wireless.py b/src/conf_mode/interfaces-wireless.py index 07c4537b4..498c24df0 100755 --- a/src/conf_mode/interfaces-wireless.py +++ b/src/conf_mode/interfaces-wireless.py @@ -19,21 +19,18 @@ from sys import exit from re import findall from copy import deepcopy -from jinja2 import FileSystemLoader, Environment from netifaces import interfaces from netaddr import EUI, mac_unix_expanded from vyos.config import Config from vyos.configdict import list_diff, vlan_to_dict -from vyos.defaults import directories as vyos_data_dir from vyos.ifconfig import WiFiIf from vyos.ifconfig_vlan import apply_vlan_config, verify_vlan_config -from vyos.util import process_running, chmod_x, chown, run, is_bridge_member +from vyos.util import chown, is_bridge_member, call from vyos import ConfigError +from vyos.template import render -user = 'root' -group = 'vyattacfg' default_config_data = { 'address': [], @@ -115,43 +112,16 @@ default_config_data = { } def get_conf_file(conf_type, intf): - cfg_dir = '/var/run/' + conf_type + cfg_dir = '/run/' + conf_type # create directory on demand if not os.path.exists(cfg_dir): - os.mkdir(cfg_dir) - chmod_x(cfg_dir) - chown(cfg_dir, user, group) + os.makedirs(cfg_dir, 0o755) + chown(cfg_dir, 'root', 'vyattacfg') - cfg_file = cfg_dir + r'/{}.cfg'.format(intf) + cfg_file = cfg_dir + r'/{}.conf'.format(intf) return cfg_file -def get_pid(conf_type, intf): - cfg_dir = '/var/run/' + conf_type - - # create directory on demand - if not os.path.exists(cfg_dir): - os.mkdir(cfg_dir) - chmod_x(cfg_dir) - chown(cfg_dir, user, group) - - cfg_file = cfg_dir + r'/{}.pid'.format(intf) - return cfg_file - - -def get_wpa_suppl_config_name(intf): - cfg_dir = '/var/run/wpa_supplicant' - - # create directory on demand - if not os.path.exists(cfg_dir): - os.mkdir(cfg_dir) - chmod_x(cfg_dir) - chown(cfg_dir, user, group) - - cfg_file = cfg_dir + r'/{}.cfg'.format(intf) - return cfg_file - - def get_config(): wifi = deepcopy(default_config_data) conf = Config() @@ -570,6 +540,9 @@ def verify(wifi): if not wifi['phy']: raise ConfigError('You must specify physical-device') + if not wifi['mode']: + raise ConfigError('You must specify a WiFi mode') + if wifi['op_mode'] == 'ap': c = Config() if not c.exists('system wifi-regulatory-domain'): @@ -627,38 +600,20 @@ def verify(wifi): return None def generate(wifi): - # Prepare Jinja2 template loader from files - tmpl_path = os.path.join(vyos_data_dir["data"], "templates", "wifi") - fs_loader = FileSystemLoader(tmpl_path) - env = Environment(loader=fs_loader) + interface = wifi['intf'] # always stop hostapd service first before reconfiguring it - pidfile = get_pid('hostapd', wifi['intf']) - if process_running(pidfile): - command = 'start-stop-daemon' - command += ' --stop ' - command += ' --quiet' - command += ' --oknodo' - command += ' --pidfile ' + pidfile - run(command) - + call(f'systemctl stop hostapd@{interface}.service') # always stop wpa_supplicant service first before reconfiguring it - pidfile = get_pid('wpa_supplicant', wifi['intf']) - if process_running(pidfile): - command = 'start-stop-daemon' - command += ' --stop ' - command += ' --quiet' - command += ' --oknodo' - command += ' --pidfile ' + pidfile - run(command) + call(f'systemctl stop wpa_supplicant@{interface}.service') # Delete config files if interface is removed if wifi['deleted']: - if os.path.isfile(get_conf_file('hostapd', wifi['intf'])): - os.unlink(get_conf_file('hostapd', wifi['intf'])) + if os.path.isfile(get_conf_file('hostapd', interface)): + os.unlink(get_conf_file('hostapd', interface)) - if os.path.isfile(get_conf_file('wpa_supplicant', wifi['intf'])): - os.unlink(get_conf_file('wpa_supplicant', wifi['intf'])) + if os.path.isfile(get_conf_file('wpa_supplicant', interface)): + os.unlink(get_conf_file('wpa_supplicant', interface)) return None @@ -676,7 +631,7 @@ def generate(wifi): tmp |= 0x020000000000 # we now need to add an offset to our MAC address indicating this # subinterfaces index - tmp += int(findall(r'\d+', wifi['intf'])[0]) + tmp += int(findall(r'\d+', interface)[0]) # convert integer to "real" MAC address representation mac = EUI(hex(tmp).split('x')[-1]) @@ -686,22 +641,19 @@ def generate(wifi): # render appropriate new config files depending on access-point or station mode if wifi['op_mode'] == 'ap': - tmpl = env.get_template('hostapd.conf.tmpl') - config_text = tmpl.render(wifi) - with open(get_conf_file('hostapd', wifi['intf']), 'w') as f: - f.write(config_text) + conf = get_conf_file('hostapd', interface) + render(conf, 'wifi/hostapd.conf.tmpl', wifi) elif wifi['op_mode'] == 'station': - tmpl = env.get_template('wpa_supplicant.conf.tmpl') - config_text = tmpl.render(wifi) - with open(get_conf_file('wpa_supplicant', wifi['intf']), 'w') as f: - f.write(config_text) + conf = get_conf_file('wpa_supplicant', interface) + render(conf, 'wifi/wpa_supplicant.conf.tmpl', wifi) return None def apply(wifi): + interface = wifi['intf'] if wifi['deleted']: - w = WiFiIf(wifi['intf']) + w = WiFiIf(interface) # delete interface w.remove() else: @@ -714,7 +666,7 @@ def apply(wifi): conf['phy'] = wifi['phy'] # Finally create the new interface - w = WiFiIf(wifi['intf'], **conf) + w = WiFiIf(interface, **conf) # assign/remove VRF w.set_vrf(wifi['vrf']) @@ -774,7 +726,7 @@ def apply(wifi): # remove no longer required VLAN interfaces (vif) for vif in wifi['vif_remove']: - e.del_vlan(vif) + w.del_vlan(vif) # create VLAN interfaces (vif) for vif in wifi['vif']: @@ -784,11 +736,11 @@ def apply(wifi): try: # on system bootup the above condition is true but the interface # does not exists, which throws an exception, but that's legal - e.del_vlan(vif['id']) + w.del_vlan(vif['id']) except: pass - vlan = e.add_vlan(vif['id']) + vlan = w.add_vlan(vif['id']) apply_vlan_config(vlan, vif) # Enable/Disable interface - interface is always placed in @@ -799,38 +751,10 @@ def apply(wifi): # Physical interface is now configured. Proceed by starting hostapd or # wpa_supplicant daemon. When type is monitor we can just skip this. if wifi['op_mode'] == 'ap': - command = 'start-stop-daemon' - command += ' --start ' - command += ' --quiet' - command += ' --oknodo' - command += ' --pidfile ' + get_pid('hostapd', wifi['intf']) - command += ' --exec /usr/sbin/hostapd' - # now pass arguments to hostapd binary - command += ' -- ' - command += ' -B' - command += ' -P ' + get_pid('hostapd', wifi['intf']) - command += ' ' + get_conf_file('hostapd', wifi['intf']) - - # execute assembled command - run(command) + call(f'systemctl start hostapd@{interface}.service') elif wifi['op_mode'] == 'station': - command = 'start-stop-daemon' - command += ' --start ' - command += ' --quiet' - command += ' --oknodo' - command += ' --pidfile ' + get_pid('hostapd', wifi['intf']) - command += ' --exec /sbin/wpa_supplicant' - # now pass arguments to hostapd binary - command += ' -- ' - command += ' -s -B -D nl80211' - command += ' -P ' + get_pid('wpa_supplicant', wifi['intf']) - command += ' -i ' + wifi['intf'] - command += ' -c ' + \ - get_conf_file('wpa_supplicant', wifi['intf']) - - # execute assembled command - run(command) + call(f'systemctl start wpa_supplicant@{interface}.service') return None diff --git a/src/conf_mode/interfaces-wirelessmodem.py b/src/conf_mode/interfaces-wirelessmodem.py index c44a993c4..da1855cd9 100755 --- a/src/conf_mode/interfaces-wirelessmodem.py +++ b/src/conf_mode/interfaces-wirelessmodem.py @@ -18,15 +18,17 @@ import os from sys import exit from copy import deepcopy -from jinja2 import FileSystemLoader, Environment from netifaces import interfaces from vyos.config import Config -from vyos.defaults import directories as vyos_data_dir -from vyos.util import chown, chmod_x, is_bridge_member +from vyos.util import chown +from vyos.util import chmod_755 +from vyos.util import is_bridge_member from vyos.util import cmd from vyos.util import call from vyos import ConfigError +from vyos.template import render + default_config_data = { 'address': [], @@ -141,11 +143,6 @@ def verify(wwan): return None def generate(wwan): - # Prepare Jinja2 template loader from files - tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'wwan') - fs_loader = FileSystemLoader(tmpl_path) - env = Environment(loader=fs_loader) - # set up configuration file path variables where our templates will be # rendered into intf = wwan['intf'] @@ -175,39 +172,20 @@ def generate(wwan): else: # Create PPP configuration files - tmpl = env.get_template('peer.tmpl') - config_text = tmpl.render(wwan) - with open(config_wwan, 'w') as f: - f.write(config_text) - + render(config_wwan, 'wwan/peer.tmpl', wwan) # Create PPP chat script - tmpl = env.get_template('chat.tmpl') - config_text = tmpl.render(wwan) - with open(config_wwan_chat, 'w') as f: - f.write(config_text) - + render(config_wwan_chat, 'wwan/chat.tmpl', wwan) # Create script for ip-pre-up.d - tmpl = env.get_template('ip-pre-up.script.tmpl') - config_text = tmpl.render(wwan) - with open(script_wwan_pre_up, 'w') as f: - f.write(config_text) - + render(script_wwan_pre_up, 'wwan/ip-pre-up.script.tmpl', wwan) # Create script for ip-up.d - tmpl = env.get_template('ip-up.script.tmpl') - config_text = tmpl.render(wwan) - with open(script_wwan_ip_up, 'w') as f: - f.write(config_text) - + render(script_wwan_ip_up, 'wwan/ip-up.script.tmpl', wwan) # Create script for ip-down.d - tmpl = env.get_template('ip-down.script.tmpl') - config_text = tmpl.render(wwan) - with open(script_wwan_ip_down, 'w') as f: - f.write(config_text) + render(script_wwan_ip_down, 'wwan/ip-down.script.tmpl', wwan) # make generated script file executable - chmod_x(script_wwan_pre_up) - chmod_x(script_wwan_ip_up) - chmod_x(script_wwan_ip_down) + chmod_755(script_wwan_pre_up) + chmod_755(script_wwan_ip_up) + chmod_755(script_wwan_ip_down) return None diff --git a/src/conf_mode/ipsec-settings.py b/src/conf_mode/ipsec-settings.py index dc04e9131..4fffa11ee 100755 --- a/src/conf_mode/ipsec-settings.py +++ b/src/conf_mode/ipsec-settings.py @@ -18,13 +18,13 @@ import re import os from time import sleep -from jinja2 import FileSystemLoader, Environment from sys import exit from vyos.config import Config -from vyos.defaults import directories as vyos_data_dir from vyos import ConfigError from vyos.util import call +from vyos.template import render + ra_conn_name = "remote-access" charon_conf_file = "/etc/strongswan.d/charon.conf" @@ -147,43 +147,26 @@ def verify(data): raise ConfigError("L2TP VPN configuration error: \"vpn ipsec ipsec-interfaces\" must be specified.") def generate(data): - tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'ipsec') - fs_loader = FileSystemLoader(tmpl_path) - env = Environment(loader=fs_loader, trim_blocks=True) - - tmpl = env.get_template('charon.tmpl') - config_text = tmpl.render(data) - with open(charon_conf_file, 'w') as f: - f.write(config_text) + render(charon_conf_file, 'ipsec/charon.tmpl', data, trim_blocks=True) if data["ipsec_l2tp"]: remove_confs(delim_ipsec_l2tp_begin, delim_ipsec_l2tp_end, ipsec_conf_flie) - tmpl = env.get_template('ipsec.secrets.tmpl') - l2pt_ipsec_secrets_txt = tmpl.render(c) old_umask = os.umask(0o077) - with open(ipsec_secrets_flie,'w') as f: - f.write(l2pt_ipsec_secrets_txt) + render(ipsec_secrets_flie, 'ipsec/ipsec.secrets.tmpl', c, trim_blocks=True) os.umask(old_umask) - tmpl = env.get_template('remote-access.tmpl') - ipsec_ra_conn_txt = tmpl.render(c) old_umask = os.umask(0o077) # Create tunnels directory if does not exist if not os.path.exists(ipsec_ra_conn_dir): os.makedirs(ipsec_ra_conn_dir) - with open(ipsec_ra_conn_file,'w') as f: - f.write(ipsec_ra_conn_txt) + render(ipsec_ra_conn_file, 'ipsec/remote-access.tmpl', c, trim_blocks=True) os.umask(old_umask) - - tmpl = env.get_template('ipsec.conf.tmpl') - l2pt_ipsec_conf_txt = tmpl.render(c) old_umask = os.umask(0o077) - with open(ipsec_conf_flie,'a') as f: - f.write(l2pt_ipsec_conf_txt) + render(ipsec_conf_flie, 'ipsec/ipsec.conf.tmpl', c, trim_blocks=True) os.umask(old_umask) else: diff --git a/src/conf_mode/le_cert.py b/src/conf_mode/le_cert.py index 4b365a566..2db31d3fc 100755 --- a/src/conf_mode/le_cert.py +++ b/src/conf_mode/le_cert.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019 VyOS maintainers and contributors +# Copyright (C) 2019-2020 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 @@ -13,8 +13,6 @@ # # 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 @@ -25,7 +23,6 @@ from vyos import ConfigError from vyos.util import cmd from vyos.util import call - vyos_conf_scripts_dir = vyos.defaults.directories['conf_mode'] dependencies = [ @@ -86,17 +83,17 @@ def generate(cert): # certbot will attempt to reload nginx, even with 'certonly'; # start nginx if not active - ret = call('systemctl is-active --quiet nginx.ervice') + ret = call('systemctl is-active --quiet nginx.service') if ret: - call('sudo systemctl start nginx.service') + call('systemctl start nginx.service') request_certbot(cert) def apply(cert): if cert is not None: - call('sudo systemctl restart certbot.timer') + call('systemctl restart certbot.timer') else: - call('sudo systemctl stop certbot.timer') + call('systemctl stop certbot.timer') return None for dep in dependencies: diff --git a/src/conf_mode/lldp.py b/src/conf_mode/lldp.py index ec59c68d0..d128c1fe6 100755 --- a/src/conf_mode/lldp.py +++ b/src/conf_mode/lldp.py @@ -18,15 +18,14 @@ import os import re from copy import deepcopy -from jinja2 import FileSystemLoader, Environment from sys import exit from vyos.config import Config from vyos.validate import is_addr_assigned,is_loopback_addr -from vyos.defaults import directories as vyos_data_dir from vyos.version import get_version_data from vyos import ConfigError from vyos.util import call +from vyos.template import render config_file = "/etc/default/lldpd" @@ -210,11 +209,6 @@ def generate(lldp): if lldp is None: return - # Prepare Jinja2 template loader from files - tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'lldp') - fs_loader = FileSystemLoader(tmpl_path) - env = Environment(loader=fs_loader) - # generate listen on interfaces for intf in lldp['interface_list']: tmp = '' @@ -226,16 +220,9 @@ def generate(lldp): lldp['options']['listen_on'].append(tmp) # generate /etc/default/lldpd - tmpl = env.get_template('lldpd.tmpl') - config_text = tmpl.render(lldp) - with open(config_file, 'w') as f: - f.write(config_text) - + render(config_file, 'lldp/lldpd.tmpl', lldp) # generate /etc/lldpd.d/01-vyos.conf - tmpl = env.get_template('vyos.conf.tmpl') - config_text = tmpl.render(lldp) - with open(vyos_config_file, 'w') as f: - f.write(config_text) + render(vyos_config_file, 'lldp/vyos.conf.tmpl', lldp) def apply(lldp): diff --git a/src/conf_mode/mdns_repeater.py b/src/conf_mode/mdns_repeater.py index 9230aaf61..a652553f7 100755 --- a/src/conf_mode/mdns_repeater.py +++ b/src/conf_mode/mdns_repeater.py @@ -18,14 +18,12 @@ import os from sys import exit from copy import deepcopy -from jinja2 import FileSystemLoader, Environment from netifaces import ifaddresses, AF_INET from vyos.config import Config -from vyos.defaults import directories as vyos_data_dir from vyos import ConfigError from vyos.util import call - +from vyos.template import render config_file = r'/etc/default/mdns-repeater' @@ -82,25 +80,16 @@ def generate(mdns): print('Warning: mDNS repeater will be deactivated because it is disabled') return None - # Prepare Jinja2 template loader from files - tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'mdns-repeater') - fs_loader = FileSystemLoader(tmpl_path) - env = Environment(loader=fs_loader) - - tmpl = env.get_template('mdns-repeater.tmpl') - config_text = tmpl.render(mdns) - with open(config_file, 'w') as f: - f.write(config_text) - + render(config_file, 'mdns-repeater/mdns-repeater.tmpl', mdns) return None def apply(mdns): if (mdns is None) or mdns['disabled']: - call('sudo systemctl stop mdns-repeater') + call('systemctl stop mdns-repeater.service') if os.path.exists(config_file): os.unlink(config_file) else: - call('sudo systemctl restart mdns-repeater') + call('systemctl restart mdns-repeater.service') return None diff --git a/src/conf_mode/ntp.py b/src/conf_mode/ntp.py index 75328dfd7..6d32f7fd6 100755 --- a/src/conf_mode/ntp.py +++ b/src/conf_mode/ntp.py @@ -18,14 +18,12 @@ import os from copy import deepcopy from ipaddress import ip_network -from jinja2 import FileSystemLoader, Environment from sys import exit from vyos.config import Config -from vyos.defaults import directories as vyos_data_dir -from vyos import ConfigError from vyos.util import call - +from vyos.template import render +from vyos import ConfigError config_file = r'/etc/ntp.conf' @@ -100,16 +98,7 @@ def generate(ntp): if ntp is None: return None - # Prepare Jinja2 template loader from files - tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'ntp') - fs_loader = FileSystemLoader(tmpl_path) - env = Environment(loader=fs_loader) - - tmpl = env.get_template('ntp.conf.tmpl') - config_text = tmpl.render(ntp) - with open(config_file, 'w') as f: - f.write(config_text) - + render(config_file, 'ntp/ntp.conf.tmpl', ntp) return None def apply(ntp): diff --git a/src/conf_mode/protocols_bfd.py b/src/conf_mode/protocols_bfd.py index cf4db5f54..ed8c3637b 100755 --- a/src/conf_mode/protocols_bfd.py +++ b/src/conf_mode/protocols_bfd.py @@ -18,13 +18,12 @@ import os from sys import exit from copy import deepcopy -from jinja2 import FileSystemLoader, Environment from vyos.config import Config -from vyos.defaults import directories as vyos_data_dir from vyos.validate import is_ipv6_link_local, is_ipv6 from vyos import ConfigError from vyos.util import call +from vyos.template import render config_file = r'/tmp/bfd.frr' @@ -191,16 +190,7 @@ def generate(bfd): if bfd is None: return None - # Prepare Jinja2 template loader from files - tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'frr-bfd') - fs_loader = FileSystemLoader(tmpl_path) - env = Environment(loader=fs_loader) - - tmpl = env.get_template('bfd.frr.tmpl') - config_text = tmpl.render(bfd) - with open(config_file, 'w') as f: - f.write(config_text) - + render(config_file, 'frr-bfd/bfd.frr.tmpl', bfd) return None def apply(bfd): diff --git a/src/conf_mode/protocols_igmp.py b/src/conf_mode/protocols_igmp.py index 141b1950d..9b338c5b9 100755 --- a/src/conf_mode/protocols_igmp.py +++ b/src/conf_mode/protocols_igmp.py @@ -17,13 +17,12 @@ import os from ipaddress import IPv4Address -from jinja2 import FileSystemLoader, Environment from sys import exit from vyos import ConfigError from vyos.config import Config -from vyos.defaults import directories as vyos_data_dir from vyos.util import call +from vyos.template import render config_file = r'/tmp/igmp.frr' @@ -88,16 +87,7 @@ def generate(igmp): if igmp is None: return None - # Prepare Jinja2 template loader from files - tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'igmp') - fs_loader = FileSystemLoader(tmpl_path) - env = Environment(loader=fs_loader) - - tmpl = env.get_template('igmp.frr.tmpl') - config_text = tmpl.render(igmp) - with open(config_file, 'w') as f: - f.write(config_text) - + render(config_file, 'igmp/igmp.frr.tmpl', igmp) return None def apply(igmp): diff --git a/src/conf_mode/protocols_mpls.py b/src/conf_mode/protocols_mpls.py index b5753aea8..0a241277d 100755 --- a/src/conf_mode/protocols_mpls.py +++ b/src/conf_mode/protocols_mpls.py @@ -16,12 +16,10 @@ import os -from jinja2 import FileSystemLoader, Environment - from vyos.config import Config -from vyos.defaults import directories as vyos_data_dir from vyos import ConfigError from vyos.util import call +from vyos.template import render config_file = r'/tmp/ldpd.frr' @@ -129,16 +127,7 @@ def generate(mpls): if mpls is None: return None - # Prepare Jinja2 template loader from files - tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'mpls') - fs_loader = FileSystemLoader(tmpl_path) - env = Environment(loader=fs_loader) - - tmpl = env.get_template('ldpd.frr.tmpl') - config_text = tmpl.render(mpls) - with open(config_file, 'w') as f: - f.write(config_text) - + render(config_file, 'mpls/ldpd.frr.tmpl', mpls) return None def apply(mpls): diff --git a/src/conf_mode/protocols_pim.py b/src/conf_mode/protocols_pim.py index 44fc9293b..f12de4a72 100755 --- a/src/conf_mode/protocols_pim.py +++ b/src/conf_mode/protocols_pim.py @@ -17,13 +17,12 @@ import os from ipaddress import IPv4Address -from jinja2 import FileSystemLoader, Environment from sys import exit from vyos.config import Config -from vyos.defaults import directories as vyos_data_dir from vyos import ConfigError from vyos.util import call +from vyos.template import render config_file = r'/tmp/pimd.frr' @@ -115,16 +114,7 @@ def generate(pim): if pim is None: return None - # Prepare Jinja2 template loader from files - tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'pim') - fs_loader = FileSystemLoader(tmpl_path) - env = Environment(loader=fs_loader) - - tmpl = env.get_template('pimd.frr.tmpl') - config_text = tmpl.render(pim) - with open(config_file, 'w') as f: - f.write(config_text) - + render(config_file, 'pim/pimd.frr.tmpl', pim) return None def apply(pim): diff --git a/src/conf_mode/salt-minion.py b/src/conf_mode/salt-minion.py index bfc3a707e..236480854 100755 --- a/src/conf_mode/salt-minion.py +++ b/src/conf_mode/salt-minion.py @@ -17,16 +17,15 @@ import os from copy import deepcopy -from jinja2 import FileSystemLoader, Environment from pwd import getpwnam from socket import gethostname from sys import exit from urllib3 import PoolManager from vyos.config import Config -from vyos.defaults import directories as vyos_data_dir from vyos import ConfigError from vyos.util import call +from vyos.template import render config_file = r'/etc/salt/minion' @@ -88,18 +87,10 @@ def generate(salt): if salt is None: return None - # Prepare Jinja2 template loader from files - tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'salt-minion') - fs_loader = FileSystemLoader(tmpl_path) - env = Environment(loader=fs_loader) - if not os.path.exists(directory): os.makedirs(directory) - tmpl = env.get_template('minion.tmpl') - config_text = tmpl.render(salt) - with open(config_file, 'w') as f: - f.write(config_text) + render(config_file, 'salt-minion/minion.tmpl', salt) path = "/etc/salt/" for path in paths: diff --git a/src/conf_mode/service-ipoe.py b/src/conf_mode/service-ipoe.py index 5bd4aea2e..3a14d92ef 100755 --- a/src/conf_mode/service-ipoe.py +++ b/src/conf_mode/service-ipoe.py @@ -17,15 +17,15 @@ import os import re -from jinja2 import FileSystemLoader, Environment from socket import socket, AF_INET, SOCK_STREAM from sys import exit from time import sleep from vyos.config import Config -from vyos.defaults import directories as vyos_data_dir from vyos import ConfigError from vyos.util import run +from vyos.template import render + ipoe_cnf_dir = r'/etc/accel-ppp/ipoe' ipoe_cnf = ipoe_cnf_dir + r'/ipoe.config' @@ -219,25 +219,15 @@ def generate(c): if c == None or not c: return None - # Prepare Jinja2 template loader from files - tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'ipoe-server') - fs_loader = FileSystemLoader(tmpl_path) - env = Environment(loader=fs_loader, trim_blocks=True) - c['thread_cnt'] = _get_cpu() if c['auth']['mech'] == 'local': - tmpl = env.get_template('chap-secrets.tmpl') - chap_secrets_txt = tmpl.render(c) old_umask = os.umask(0o077) - with open(chap_secrets, 'w') as f: - f.write(chap_secrets_txt) + render(chap_secrets, 'ipoe-server/chap-secrets.tmpl', c, trim_blocks=True) os.umask(old_umask) - tmpl = env.get_template('ipoe.config.tmpl') - config_text = tmpl.render(c) - with open(ipoe_cnf, 'w') as f: - f.write(config_text) + render(ipoe_cnf, 'ipoe-server/ipoe.config.tmpl', c, trim_blocks=True) + # return c ?? return c diff --git a/src/conf_mode/service-pppoe.py b/src/conf_mode/service-pppoe.py index d3fc82406..a96249199 100755 --- a/src/conf_mode/service-pppoe.py +++ b/src/conf_mode/service-pppoe.py @@ -17,15 +17,15 @@ import os import re -from jinja2 import FileSystemLoader, Environment from socket import socket, AF_INET, SOCK_STREAM from sys import exit from time import sleep from vyos.config import Config -from vyos.defaults import directories as vyos_data_dir from vyos import ConfigError from vyos.util import run +from vyos.template import render + pidfile = r'/var/run/accel_pppoe.pid' pppoe_cnf_dir = r'/etc/accel-ppp/pppoe' @@ -376,11 +376,6 @@ def generate(c): if c == None: return None - # Prepare Jinja2 template loader from files - tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'pppoe-server') - fs_loader = FileSystemLoader(tmpl_path) - env = Environment(loader=fs_loader, trim_blocks=True) - # accel-cmd reload doesn't work so any change results in a restart of the # daemon try: @@ -394,17 +389,11 @@ def generate(c): else: c['thread_cnt'] = int(os.cpu_count() / 2) - tmpl = env.get_template('pppoe.config.tmpl') - config_text = tmpl.render(c) - with open(pppoe_conf, 'w') as f: - f.write(config_text) + render(pppoe_conf, 'pppoe-server/pppoe.config.tmpl', c, trim_blocks=True) if c['authentication']['local-users']: - tmpl = env.get_template('chap-secrets.tmpl') - chap_secrets_txt = tmpl.render(c) old_umask = os.umask(0o077) - with open(chap_secrets, 'w') as f: - f.write(chap_secrets_txt) + render(chap_secrets, 'pppoe-server/chap-secrets.tmpl', c, trim_blocks=True) os.umask(old_umask) return c diff --git a/src/conf_mode/service-router-advert.py b/src/conf_mode/service-router-advert.py index 75a324260..620f3eacf 100755 --- a/src/conf_mode/service-router-advert.py +++ b/src/conf_mode/service-router-advert.py @@ -16,14 +16,13 @@ import os -from jinja2 import FileSystemLoader, Environment from stat import S_IRUSR, S_IWUSR, S_IRGRP from sys import exit from vyos.config import Config -from vyos.defaults import directories as vyos_data_dir from vyos import ConfigError from vyos.util import call +from vyos.template import render config_file = r'/etc/radvd.conf' @@ -139,15 +138,7 @@ def generate(rtradv): if not rtradv['interfaces']: return None - # Prepare Jinja2 template loader from files - tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'router-advert') - fs_loader = FileSystemLoader(tmpl_path) - env = Environment(loader=fs_loader, trim_blocks=True) - - tmpl = env.get_template('radvd.conf.tmpl') - config_text = tmpl.render(rtradv) - with open(config_file, 'w') as f: - f.write(config_text) + render(config_file, 'router-advert/radvd.conf.tmpl', rtradv, trim_blocks=True) # adjust file permissions of new configuration file if os.path.exists(config_file): diff --git a/src/conf_mode/snmp.py b/src/conf_mode/snmp.py index 4a69e8742..d654dcb84 100755 --- a/src/conf_mode/snmp.py +++ b/src/conf_mode/snmp.py @@ -20,14 +20,13 @@ from binascii import hexlify from time import sleep from stat import S_IRWXU, S_IXGRP, S_IXOTH, S_IROTH, S_IRGRP from sys import exit -from jinja2 import FileSystemLoader, Environment from vyos.config import Config -from vyos.defaults import directories as vyos_data_dir from vyos.validate import is_ipv4, is_addr_assigned from vyos.version import get_version_data from vyos import ConfigError from vyos.util import call +from vyos.template import render config_file_client = r'/etc/snmp/snmp.conf' @@ -518,34 +517,14 @@ def generate(snmp): if snmp is None: return None - # Prepare Jinja2 template loader from files - tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'snmp') - fs_loader = FileSystemLoader(tmpl_path) - env = Environment(loader=fs_loader) - # Write client config file - tmpl = env.get_template('etc.snmp.conf.tmpl') - config_text = tmpl.render(snmp) - with open(config_file_client, 'w') as f: - f.write(config_text) - + render(config_file_client, 'snmp/etc.snmp.conf.tmpl', snmp) # Write server config file - tmpl = env.get_template('etc.snmpd.conf.tmpl') - config_text = tmpl.render(snmp) - with open(config_file_daemon, 'w') as f: - f.write(config_text) - + render(config_file_daemon, 'snmp/etc.snmpd.conf.tmpl', snmp) # Write access rights config file - tmpl = env.get_template('usr.snmpd.conf.tmpl') - config_text = tmpl.render(snmp) - with open(config_file_access, 'w') as f: - f.write(config_text) - + render(config_file_access, 'snmp/usr.snmpd.conf.tmpl', snmp) # Write access rights config file - tmpl = env.get_template('var.snmpd.conf.tmpl') - config_text = tmpl.render(snmp) - with open(config_file_user, 'w') as f: - f.write(config_text) + render(config_file_user, 'snmp/var.snmpd.conf.tmpl', snmp) return None diff --git a/src/conf_mode/ssh.py b/src/conf_mode/ssh.py index a6cdb7ccc..ae79eac2d 100755 --- a/src/conf_mode/ssh.py +++ b/src/conf_mode/ssh.py @@ -15,13 +15,12 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. import os -from jinja2 import FileSystemLoader, Environment from sys import exit from vyos.config import Config -from vyos.defaults import directories as vyos_data_dir from vyos import ConfigError from vyos.util import call +from vyos.template import render config_file = r'/etc/ssh/sshd_config' @@ -120,15 +119,7 @@ def generate(ssh): if ssh is None: return None - # Prepare Jinja2 template loader from files - tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'ssh') - fs_loader = FileSystemLoader(tmpl_path) - env = Environment(loader=fs_loader, trim_blocks=True) - - tmpl = env.get_template('sshd_config.tmpl') - config_text = tmpl.render(ssh) - with open(config_file, 'w') as f: - f.write(config_text) + render(config_file, 'ssh/sshd_config.tmpl', ssh, trim_blocks=True) return None def apply(ssh): diff --git a/src/conf_mode/system-login.py b/src/conf_mode/system-login.py index 43732cfae..6008ca0b3 100755 --- a/src/conf_mode/system-login.py +++ b/src/conf_mode/system-login.py @@ -16,7 +16,6 @@ import os -from jinja2 import FileSystemLoader, Environment from psutil import users from pwd import getpwall, getpwnam from stat import S_IRUSR, S_IWUSR, S_IRWXU, S_IRGRP, S_IXGRP @@ -24,10 +23,12 @@ from sys import exit from vyos.config import Config from vyos.configdict import list_diff -from vyos.defaults import directories as vyos_data_dir from vyos import ConfigError from vyos.util import cmd from vyos.util import call +from vyos.util import DEVNULL +from vyos.template import render + radius_config_file = "/etc/pam_radius_auth.conf" @@ -211,16 +212,16 @@ def generate(login): os.system("vyos_libexec_dir=/usr/libexec/vyos /opt/vyatta/sbin/my_set system login user '{}' authentication plaintext-password '' >/dev/null".format(user['name'])) os.system("vyos_libexec_dir=/usr/libexec/vyos /opt/vyatta/sbin/my_set system login user '{}' authentication encrypted-password '{}' >/dev/null".format(user['name'], user['password_encrypted'])) - if len(login['radius_server']) > 0: - # Prepare Jinja2 template loader from files - tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'system-login') - fs_loader = FileSystemLoader(tmpl_path) - env = Environment(loader=fs_loader) + # env = os.environ.copy() + # env['vyos_libexec_dir'] = '/usr/libexec/vyos' - tmpl = env.get_template('pam_radius_auth.conf.tmpl') - config_text = tmpl.render(login) - with open(radius_config_file, 'w') as f: - f.write(config_text) + # call("/opt/vyatta/sbin/my_set system login user '{}' authentication plaintext-password ''".format(user['name']), + # env=env) + # call("/opt/vyatta/sbin/my_set system login user '{}' authentication encrypted-password '{}'".format(user['name'], user['password_encrypted']), + # env=env) + + if len(login['radius_server']) > 0: + render(radius_config_file, 'system-login/pam_radius_auth.conf.tmpl', login) uid = getpwnam('root').pw_uid gid = getpwnam('root').pw_gid @@ -256,7 +257,7 @@ def apply(login): command += " {}".format(user['name']) try: - call(command) + cmd(command) uid = getpwnam(user['name']).pw_uid gid = getpwnam(user['name']).pw_gid @@ -299,7 +300,7 @@ def apply(login): call('pkill -HUP -u {}'.format(user)) # Remove user account but leave home directory to be safe - call('userdel -r {} 2>/dev/null'.format(user)) + call(f'userdel -r {user}', stderr=DEVNULL) except Exception as e: raise ConfigError('Deleting user "{}" raised an exception: {}'.format(user, e)) @@ -309,8 +310,10 @@ def apply(login): # if len(login['radius_server']) > 0: try: + env = os.environ.copy() + env['DEBIAN_FRONTEND'] = 'noninteractive' # Enable RADIUS in PAM - os.system("DEBIAN_FRONTEND=noninteractive pam-auth-update --package --enable radius") + cmd("pam-auth-update --package --enable radius", env=env) # Make NSS system aware of RADIUS, too command = "sed -i -e \'/\smapname/b\' \ @@ -321,15 +324,18 @@ def apply(login): -e \'/^group:[^#]*$/s/: */&mapname /\' \ /etc/nsswitch.conf" - call(command) + cmd(command) except Exception as e: raise ConfigError('RADIUS configuration failed: {}'.format(e)) else: try: + env = os.environ.copy() + env['DEBIAN_FRONTEND'] = 'noninteractive' + # Disable RADIUS in PAM - os.system("DEBIAN_FRONTEND=noninteractive pam-auth-update --package --remove radius") + cmd("pam-auth-update --package --remove radius", env=env) command = "sed -i -e \'/^passwd:.*mapuid[ \t]/s/mapuid[ \t]//\' \ -e \'/^passwd:.*[ \t]mapname/s/[ \t]mapname//\' \ @@ -337,10 +343,10 @@ def apply(login): -e \'s/[ \t]*$//\' \ /etc/nsswitch.conf" - call(command) + cmd(command) except Exception as e: - raise ConfigError('Removing RADIUS configuration failed'.format(e)) + raise ConfigError('Removing RADIUS configuration failed.\n{}'.format(e)) return None diff --git a/src/conf_mode/system-syslog.py b/src/conf_mode/system-syslog.py index 25b9b5bed..9da3d9157 100755 --- a/src/conf_mode/system-syslog.py +++ b/src/conf_mode/system-syslog.py @@ -17,13 +17,13 @@ import os import re -from jinja2 import FileSystemLoader, Environment from sys import exit from vyos.config import Config -from vyos.defaults import directories as vyos_data_dir from vyos import ConfigError from vyos.util import run +from vyos.template import render + def get_config(): c = Config() @@ -192,22 +192,13 @@ def generate(c): if c == None: return None - # Prepare Jinja2 template loader from files - tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'syslog') - fs_loader = FileSystemLoader(tmpl_path) - env = Environment(loader=fs_loader, trim_blocks=True) - - tmpl = env.get_template('rsyslog.conf.tmpl') - config_text = tmpl.render(c) - with open('/etc/rsyslog.d/vyos-rsyslog.conf', 'w') as f: - f.write(config_text) + conf = '/etc/rsyslog.d/vyos-rsyslog.conf' + render(conf, 'syslog/rsyslog.conf.tmpl', c, trim_blocks=True) # eventually write for each file its own logrotate file, since size is # defined it shouldn't matter - tmpl = env.get_template('logrotate.tmpl') - config_text = tmpl.render(c) - with open('/etc/logrotate.d/vyos-rsyslog', 'w') as f: - f.write(config_text) + conf = '/etc/logrotate.d/vyos-rsyslog' + render(conf, 'syslog/logrotate.tmpl', c, trim_blocks=True) def verify(c): @@ -253,8 +244,8 @@ def verify(c): def apply(c): if not c: - return run('systemctl stop syslog') - return run('systemctl restart syslog') + return run('systemctl stop syslog.service') + return run('systemctl restart syslog.service') if __name__ == '__main__': try: diff --git a/src/conf_mode/system-wifi-regdom.py b/src/conf_mode/system-wifi-regdom.py index 943c42274..b222df0a9 100755 --- a/src/conf_mode/system-wifi-regdom.py +++ b/src/conf_mode/system-wifi-regdom.py @@ -18,11 +18,11 @@ import os from copy import deepcopy from sys import exit -from jinja2 import FileSystemLoader, Environment from vyos.config import Config -from vyos.defaults import directories as vyos_data_dir from vyos import ConfigError +from vyos.template import render + config_80211_file='/etc/modprobe.d/cfg80211.conf' config_crda_file='/etc/default/crda' @@ -67,21 +67,8 @@ def generate(regdom): return None - # Prepare Jinja2 template loader from files - tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'wifi') - fs_loader = FileSystemLoader(tmpl_path) - env = Environment(loader=fs_loader) - - tmpl = env.get_template('cfg80211.conf.tmpl') - config_text = tmpl.render(regdom) - with open(config_80211_file, 'w') as f: - f.write(config_text) - - tmpl = env.get_template('crda.tmpl') - config_text = tmpl.render(regdom) - with open(config_crda_file, 'w') as f: - f.write(config_text) - + render(config_80211_file, 'wifi/cfg80211.conf.tmpl', regdom) + render(config_crda_file, 'wifi/crda.tmpl', regdom) return None def apply(regdom): diff --git a/src/conf_mode/tftp_server.py b/src/conf_mode/tftp_server.py index 7a7246783..94c8bcf03 100755 --- a/src/conf_mode/tftp_server.py +++ b/src/conf_mode/tftp_server.py @@ -20,14 +20,13 @@ import pwd from copy import deepcopy from glob import glob -from jinja2 import FileSystemLoader, Environment from sys import exit from vyos.config import Config -from vyos.defaults import directories as vyos_data_dir from vyos.validate import is_ipv4, is_addr_assigned from vyos import ConfigError from vyos.util import call +from vyos.template import render config_file = r'/etc/default/tftpd' @@ -90,11 +89,6 @@ def generate(tftpd): if tftpd is None: return None - # Prepare Jinja2 template loader from files - tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'tftp-server') - fs_loader = FileSystemLoader(tmpl_path) - env = Environment(loader=fs_loader) - idx = 0 for listen in tftpd['listen']: config = deepcopy(tftpd) @@ -103,11 +97,8 @@ def generate(tftpd): else: config['listen'] = ["[" + listen + "]" + tftpd['port'] + " -6"] - tmpl = env.get_template('default.tmpl') - config_text = tmpl.render(config) file = config_file + str(idx) - with open(file, 'w') as f: - f.write(config_text) + render(file, 'tftp-server/default.tmpl', config) idx = idx + 1 @@ -115,7 +106,7 @@ def generate(tftpd): def apply(tftpd): # stop all services first - then we will decide - call('systemctl stop tftpd@{0..20}') + call('systemctl stop tftpd@{0..20}.service') # bail out early - e.g. service deletion if tftpd is None: diff --git a/src/conf_mode/vpn-pptp.py b/src/conf_mode/vpn-pptp.py index 45b2c4b40..15b80f984 100755 --- a/src/conf_mode/vpn-pptp.py +++ b/src/conf_mode/vpn-pptp.py @@ -17,15 +17,15 @@ import os import re -from jinja2 import FileSystemLoader, Environment from socket import socket, AF_INET, SOCK_STREAM from sys import exit from time import sleep from vyos.config import Config -from vyos.defaults import directories as vyos_data_dir from vyos import ConfigError from vyos.util import run +from vyos.template import render + pidfile = r'/var/run/accel_pptp.pid' pptp_cnf_dir = r'/etc/accel-ppp/pptp' @@ -206,11 +206,6 @@ def generate(c): if c == None: return None - # Prepare Jinja2 template loader from files - tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'pptp') - fs_loader = FileSystemLoader(tmpl_path) - env = Environment(loader=fs_loader, trim_blocks=True) - # accel-cmd reload doesn't work so any change results in a restart of the daemon try: if os.cpu_count() == 1: @@ -223,19 +218,13 @@ def generate(c): else: c['thread_cnt'] = int(os.cpu_count()/2) - tmpl = env.get_template('pptp.config.tmpl') - config_text = tmpl.render(c) - with open(pptp_conf, 'w') as f: - f.write(config_text) + render(pptp_conf, 'pptp/pptp.config.tmpl', c, trim_blocks=True) if c['authentication']['local-users']: - tmpl = env.get_template('chap-secrets.tmpl') - chap_secrets_txt = tmpl.render(c) old_umask = os.umask(0o077) - with open(chap_secrets, 'w') as f: - f.write(chap_secrets_txt) + render(chap_secrets, 'pptp/chap-secrets.tmpl', c, trim_blocks=True) os.umask(old_umask) - + # return c ?? return c diff --git a/src/conf_mode/vpn_l2tp.py b/src/conf_mode/vpn_l2tp.py new file mode 100755 index 000000000..a8b183bef --- /dev/null +++ b/src/conf_mode/vpn_l2tp.py @@ -0,0 +1,386 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019-2020 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 os +import re + +from copy import deepcopy +from stat import S_IRUSR, S_IWUSR, S_IRGRP +from sys import exit +from time import sleep + +from ipaddress import ip_network + +from vyos.config import Config +from vyos.util import call +from vyos.validate import is_ipv4 +from vyos import ConfigError +from vyos.template import render + + +l2tp_conf = '/run/accel-pppd/l2tp.conf' +l2tp_chap_secrets = '/run/accel-pppd/l2tp.chap-secrets' + +default_config_data = { + 'auth_mode': 'local', + 'auth_ppp_mppe': 'prefer', + 'auth_proto': ['auth_mschap_v2'], + 'chap_secrets_file': l2tp_chap_secrets, # used in Jinja2 template + 'client_ip_pool': None, + 'client_ip_subnets': [], + 'client_ipv6_pool': [], + 'client_ipv6_delegate_prefix': [], + 'dnsv4': [], + 'dnsv6': [], + 'gateway_address': '10.255.255.0', + 'local_users' : [], + 'mtu': '1436', + 'outside_addr': '', + 'ppp_mppe': 'prefer', + 'ppp_echo_failure' : '3', + 'ppp_echo_interval' : '30', + 'ppp_echo_timeout': '0', + 'radius_server': [], + 'radius_acct_tmo': '3', + 'radius_max_try': '3', + 'radius_timeout': '3', + 'radius_nas_id': '', + 'radius_nas_ip': '', + 'radius_source_address': '', + 'radius_shaper_attr': '', + 'radius_shaper_vendor': '', + 'radius_dynamic_author': '', + 'wins': [], + 'ip6_column': [], + 'thread_cnt': 1 +} + +def get_config(): + conf = Config() + base_path = ['vpn', 'l2tp', 'remote-access'] + if not conf.exists(base_path): + return None + + conf.set_level(base_path) + l2tp = deepcopy(default_config_data) + + cpu = os.cpu_count() + if cpu > 1: + l2tp['thread_cnt'] = int(cpu/2) + + ### general options ### + if conf.exists(['name-server']): + for name_server in conf.return_values(['name-server']): + if is_ipv4(name_server): + l2tp['dnsv4'].append(name_server) + else: + l2tp['dnsv6'].append(name_server) + + if conf.exists(['wins-server']): + l2tp['wins'] = conf.return_values(['wins-server']) + + if conf.exists('outside-address'): + l2tp['outside_addr'] = conf.return_value('outside-address') + + if conf.exists(['authentication', 'mode']): + l2tp['auth_mode'] = conf.return_value(['authentication', 'mode']) + + if conf.exists(['authentication', 'protocols']): + auth_mods = { + 'pap': 'auth_pap', + 'chap': 'auth_chap_md5', + 'mschap': 'auth_mschap_v1', + 'mschap-v2': 'auth_mschap_v2' + } + + for proto in conf.return_values(['authentication', 'protocols']): + l2tp['auth_proto'].append(auth_mods[proto]) + + if conf.exists(['authentication', 'mppe']): + l2tp['auth_ppp_mppe'] = conf.return_value(['authentication', 'mppe']) + + # + # local auth + if conf.exists(['authentication', 'local-users']): + for username in conf.list_nodes(['authentication', 'local-users', 'username']): + user = { + 'name' : username, + 'password' : '', + 'state' : 'enabled', + 'ip' : '*', + 'upload' : None, + 'download' : None + } + + conf.set_level(base_path + ['authentication', 'local-users', 'username', username]) + + if conf.exists(['password']): + user['password'] = conf.return_value(['password']) + + if conf.exists(['disable']): + user['state'] = 'disable' + + if conf.exists(['static-ip']): + user['ip'] = conf.return_value(['static-ip']) + + if conf.exists(['rate-limit', 'download']): + user['download'] = conf.return_value(['rate-limit', 'download']) + + if conf.exists(['rate-limit', 'upload']): + user['upload'] = conf.return_value(['rate-limit', 'upload']) + + l2tp['local_users'].append(user) + + # + # RADIUS auth and settings + conf.set_level(base_path + ['authentication', 'radius']) + if conf.exists(['server']): + for server in conf.list_nodes(['server']): + radius = { + 'server' : server, + 'key' : '', + 'fail_time' : 0, + 'port' : '1812' + } + + conf.set_level(base_path + ['authentication', 'radius', 'server', server]) + + if conf.exists(['fail-time']): + radius['fail-time'] = conf.return_value(['fail-time']) + + if conf.exists(['port']): + radius['port'] = conf.return_value(['port']) + + if conf.exists(['key']): + radius['key'] = conf.return_value(['key']) + + if not conf.exists(['disable']): + l2tp['radius_server'].append(radius) + + # + # advanced radius-setting + conf.set_level(base_path + ['authentication', 'radius']) + + if conf.exists(['acct-timeout']): + l2tp['radius_acct_tmo'] = conf.return_value(['acct-timeout']) + + if conf.exists(['max-try']): + l2tp['radius_max_try'] = conf.return_value(['max-try']) + + if conf.exists(['timeout']): + l2tp['radius_timeout'] = conf.return_value(['timeout']) + + if conf.exists(['nas-identifier']): + l2tp['radius_nas_id'] = conf.return_value(['nas-identifier']) + + if conf.exists(['nas-ip-address']): + l2tp['radius_nas_ip'] = conf.return_value(['nas-ip-address']) + + if conf.exists(['source-address']): + l2tp['radius_source_address'] = conf.return_value(['source-address']) + + # Dynamic Authorization Extensions (DOA)/Change Of Authentication (COA) + if conf.exists(['dynamic-author']): + dae = { + 'port' : '', + 'server' : '', + 'key' : '' + } + + if conf.exists(['dynamic-author', 'server']): + dae['server'] = conf.return_value(['dynamic-author', 'server']) + + if conf.exists(['dynamic-author', 'port']): + dae['port'] = conf.return_value(['dynamic-author', 'port']) + + if conf.exists(['dynamic-author', 'key']): + dae['key'] = conf.return_value(['dynamic-author', 'key']) + + l2tp['radius_dynamic_author'] = dae + + if conf.exists(['rate-limit', 'enable']): + l2tp['radius_shaper_attr'] = 'Filter-Id' + c_attr = ['rate-limit', 'enable', 'attribute'] + if conf.exists(c_attr): + l2tp['radius_shaper_attr'] = conf.return_value(c_attr) + + c_vendor = ['rate-limit', 'enable', 'vendor'] + if conf.exists(c_vendor): + l2tp['radius_shaper_vendor'] = conf.return_value(c_vendor) + + conf.set_level(base_path) + if conf.exists(['client-ip-pool']): + if conf.exists(['client-ip-pool', 'start']) and conf.exists(['client-ip-pool', 'stop']): + start = conf.return_value(['client-ip-pool', 'start']) + stop = conf.return_value(['client-ip-pool', 'stop']) + l2tp['client_ip_pool'] = start + '-' + re.search('[0-9]+$', stop).group(0) + + if conf.exists(['client-ip-pool', 'subnet']): + l2tp['client_ip_subnets'] = conf.return_values(['client-ip-pool', 'subnet']) + + if conf.exists(['client-ipv6-pool', 'prefix']): + l2tp['ip6_column'].append('ip6') + for prefix in conf.list_nodes(['client-ipv6-pool', 'prefix']): + tmp = { + 'prefix': prefix, + 'mask': '64' + } + + if conf.exists(['client-ipv6-pool', 'prefix', prefix, 'mask']): + tmp['mask'] = conf.return_value(['client-ipv6-pool', 'prefix', prefix, 'mask']) + + l2tp['client_ipv6_pool'].append(tmp) + + if conf.exists(['client-ipv6-pool', 'delegate']): + l2tp['ip6_column'].append('ip6-db') + for prefix in conf.list_nodes(['client-ipv6-pool', 'delegate']): + tmp = { + 'prefix': prefix, + 'mask': '' + } + + if conf.exists(['client-ipv6-pool', 'delegate', prefix, 'mask']): + tmp['mask'] = conf.return_value(['client-ipv6-pool', 'delegate', prefix, 'delegation-prefix']) + + l2tp['client_ipv6_delegate_prefix'].append(tmp) + + if conf.exists(['mtu']): + l2tp['mtu'] = conf.return_value(['mtu']) + + # gateway address + if conf.exists(['gateway-address']): + l2tp['gateway_address'] = conf.return_value(['gateway-address']) + else: + # calculate gw-ip-address + if conf.exists(['client-ip-pool', 'start']): + # use start ip as gw-ip-address + l2tp['gateway_address'] = conf.return_value(['client-ip-pool', 'start']) + + elif conf.exists(['client-ip-pool', 'subnet']): + # use first ip address from first defined pool + subnet = conf.return_values(['client-ip-pool', 'subnet'])[0] + subnet = ip_network(subnet) + l2tp['gateway_address'] = str(list(subnet.hosts())[0]) + + # LNS secret + if conf.exists(['lns', 'shared-secret']): + l2tp['lns_shared_secret'] = conf.return_value(['lns', 'shared-secret']) + + if conf.exists(['ccp-disable']): + l2tp[['ccp_disable']] = True + + # PPP options + if conf.exists(['idle']): + l2tp['ppp_echo_timeout'] = conf.return_value(['idle']) + + if conf.exists(['ppp-options', 'lcp-echo-failure']): + l2tp['ppp_echo_failure'] = conf.return_value(['ppp-options', 'lcp-echo-failure']) + + if conf.exists(['ppp-options', 'lcp-echo-interval']): + l2tp['ppp_echo_interval'] = conf.return_value(['ppp-options', 'lcp-echo-interval']) + + return l2tp + + +def verify(l2tp): + if not l2tp: + return None + + if l2tp['auth_mode'] == 'local': + if not l2tp['local_users']: + raise ConfigError('L2TP local auth mode requires local users to be configured!') + + for user in l2tp['local_users']: + if not user['password']: + raise ConfigError(f"Password required for user {user['name']}") + + elif l2tp['auth_mode'] == 'radius': + if len(l2tp['radius_server']) == 0: + raise ConfigError("RADIUS authentication requires at least one server") + + for radius in l2tp['radius_server']: + if not radius['key']: + raise ConfigError(f"Missing RADIUS secret for server {{ radius['key'] }}") + + # check for the existence of a client ip pool + if not (l2tp['client_ip_pool'] or l2tp['client_ip_subnets']): + raise ConfigError( + "set vpn l2tp remote-access client-ip-pool requires subnet or start/stop IP pool") + + # check ipv6 + if l2tp['client_ipv6_delegate_prefix'] and not l2tp['client_ipv6_pool']: + raise ConfigError('IPv6 prefix delegation requires client-ipv6-pool prefix') + + for prefix in l2tp['client_ipv6_delegate_prefix']: + if not prefix['mask']: + raise ConfigError('Delegation-prefix required for individual delegated networks') + + if len(l2tp['wins']) > 2: + raise ConfigError('Not more then two IPv4 WINS name-servers can be configured') + + if len(l2tp['dnsv4']) > 2: + raise ConfigError('Not more then two IPv4 DNS name-servers can be configured') + + if len(l2tp['dnsv6']) > 3: + raise ConfigError('Not more then three IPv6 DNS name-servers can be configured') + + return None + + +def generate(l2tp): + if not l2tp: + return None + + dirname = os.path.dirname(l2tp_conf) + if not os.path.exists(dirname): + os.mkdir(dirname) + + render(l2tp_conf, 'l2tp/l2tp.config.tmpl', c, trim_blocks=True) + + if l2tp['auth_mode'] == 'local': + render(l2tp_chap_secrets, 'l2tp/chap-secrets.tmpl', l2tp) + os.chmod(l2tp_chap_secrets, S_IRUSR | S_IWUSR | S_IRGRP) + + else: + if os.path.exists(l2tp_chap_secrets): + os.unlink(l2tp_chap_secrets) + + return None + + +def apply(l2tp): + if not l2tp: + call('systemctl stop accel-ppp@l2tp.service') + + if os.path.exists(l2tp_conf): + os.unlink(l2tp_conf) + + if os.path.exists(l2tp_chap_secrets): + os.unlink(l2tp_chap_secrets) + + return None + + call('systemctl restart accel-ppp@l2tp.service') + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/vpn_sstp.py b/src/conf_mode/vpn_sstp.py index ca0844c50..438731972 100755 --- a/src/conf_mode/vpn_sstp.py +++ b/src/conf_mode/vpn_sstp.py @@ -18,49 +18,24 @@ import os from time import sleep from sys import exit -from socket import socket, AF_INET, SOCK_STREAM from copy import deepcopy from stat import S_IRUSR, S_IWUSR, S_IRGRP -from jinja2 import FileSystemLoader, Environment from vyos.config import Config from vyos import ConfigError -from vyos.defaults import directories as vyos_data_dir -from vyos.util import process_running -from vyos.util import process_running, cmd, run - -pidfile = r'/var/run/accel_sstp.pid' -sstp_cnf_dir = r'/etc/accel-ppp/sstp' -chap_secrets = sstp_cnf_dir + '/chap-secrets' -sstp_conf = sstp_cnf_dir + '/sstp.config' - -# config path creation -if not os.path.exists(sstp_cnf_dir): - os.makedirs(sstp_cnf_dir) - -def chk_con(): - cnt = 0 - s = socket(AF_INET, SOCK_STREAM) - while True: - try: - s.connect(("127.0.0.1", 2005)) - s.close() - break - except ConnectionRefusedError: - sleep(0.5) - cnt += 1 - if cnt == 100: - raise("failed to start sstp server") - break - - -def _accel_cmd(command): - return run(f'/usr/bin/accel-cmd -p 2005 {command}') +from vyos.util import call, run +from vyos.template import render + + +sstp_conf = '/run/accel-pppd/sstp.conf' +sstp_chap_secrets = '/run/accel-pppd/sstp.chap-secrets' default_config_data = { 'local_users' : [], 'auth_mode' : 'local', - 'auth_proto' : [], + 'auth_proto' : ['auth_mschap_v2'], + 'chap_secrets_file': sstp_chap_secrets, # used in Jinja2 template + 'client_gateway': '', 'radius_server' : [], 'radius_acct_tmo' : '3', 'radius_max_try' : '3', @@ -77,11 +52,11 @@ default_config_data = { 'client_ip_pool' : [], 'dnsv4' : [], 'mtu' : '', - 'ppp_mppe' : '', + 'ppp_mppe' : 'prefer', 'ppp_echo_failure' : '', 'ppp_echo_interval' : '', 'ppp_echo_timeout' : '', - 'thread_cnt' : '' + 'thread_cnt' : 1 } def get_config(): @@ -93,10 +68,9 @@ def get_config(): conf.set_level(base_path) - cpu = int(os.cpu_count()/2) - if cpu < 1: - cpu = 1 - sstp['thread_cnt'] = cpu + cpu = os.cpu_count() + if cpu > 1: + sstp['thread_cnt'] = int(cpu/2) if conf.exists(['authentication', 'mode']): sstp['auth_mode'] = conf.return_value(['authentication', 'mode']) @@ -214,6 +188,8 @@ def get_config(): # authentication protocols conf.set_level(base_path + ['authentication']) if conf.exists(['protocols']): + # clear default list content, now populate with actual CLI values + sstp['auth_proto'] = [] auth_mods = { 'pap': 'auth_pap', 'chap': 'auth_chap_md5', @@ -224,9 +200,6 @@ def get_config(): for proto in conf.return_values(['protocols']): sstp['auth_proto'].append(auth_mods[proto]) - else: - sstp['auth_proto'] = ['auth_mschap_v2'] - # # read in SSL certs conf.set_level(base_path + ['ssl']) @@ -262,7 +235,7 @@ def get_config(): # read in PPP stuff conf.set_level(base_path + ['ppp-settings']) if conf.exists('mppe'): - sstp['ppp_mppe'] = conf.return_value('ppp-settings mppe') + sstp['ppp_mppe'] = conf.return_value(['ppp-settings', 'mppe']) if conf.exists(['lcp-echo-failure']): sstp['ppp_echo_failure'] = conf.return_value(['lcp-echo-failure']) @@ -283,7 +256,7 @@ def verify(sstp): # vertify auth settings if sstp['auth_mode'] == 'local': if not sstp['local_users']: - raise ConfigError('sstp-server authentication local-users required') + raise ConfigError('SSTP local auth mode requires local users to be configured!') for user in sstp['local_users']: if not user['password']: @@ -303,7 +276,7 @@ def verify(sstp): raise ConfigError("Client gateway IP address required") if len(sstp['dnsv4']) > 2: - raise ConfigError("Only 2 DNS name-servers can be configured") + raise ConfigError('Not more then two IPv4 DNS name-servers can be configured') if not sstp['ssl_ca'] or not sstp['ssl_cert'] or not sstp['ssl_key']: raise ConfigError('One or more SSL certificates missing') @@ -326,69 +299,38 @@ def verify(sstp): raise ConfigError(f"Missing RADIUS secret for server {{ radius['key'] }}") def generate(sstp): - if sstp is None: + if not sstp: return None - # Prepare Jinja2 template loader from files - tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'sstp') - fs_loader = FileSystemLoader(tmpl_path) - env = Environment(loader=fs_loader, trim_blocks=True) + dirname = os.path.dirname(sstp_conf) + if not os.path.exists(dirname): + os.mkdir(dirname) # accel-cmd reload doesn't work so any change results in a restart of the daemon - tmpl = env.get_template('sstp.config.tmpl') - config_text = tmpl.render(sstp) - with open(sstp_conf, 'w') as f: - f.write(config_text) + render(sstp_conf, 'sstp/sstp.config.tmpl', sstp, trim_blocks=True) if sstp['local_users']: - tmpl = env.get_template('chap-secrets.tmpl') - config_text = tmpl.render(sstp) - with open(chap_secrets, 'w') as f: - f.write(config_text) - - os.chmod(chap_secrets, S_IRUSR | S_IWUSR | S_IRGRP) + render(sstp_chap_secrets, 'sstp/chap-secrets.tmpl', sstp, trim_blocks=True) + os.chmod(sstp_chap_secrets, S_IRUSR | S_IWUSR | S_IRGRP) else: - if os.path.exists(chap_secrets): - os.unlink(chap_secrets) + if os.path.exists(sstp_chap_secrets): + os.unlink(sstp_chap_secrets) return sstp def apply(sstp): - if sstp is None: - if process_running(pidfile): - command = 'start-stop-daemon' - command += ' --stop ' - command += ' --quiet' - command += ' --oknodo' - command += ' --pidfile ' + pidfile - cmd(command) + if not sstp: + call('systemctl stop accel-ppp@sstp.service') - if os.path.exists(pidfile): - os.remove(pidfile) + if os.path.exists(sstp_conf): + os.unlink(sstp_conf) - return None + if os.path.exists(sstp_chap_secrets): + os.unlink(sstp_chap_secrets) - if not process_running(pidfile): - if os.path.exists(pidfile): - os.remove(pidfile) - - command = 'start-stop-daemon' - command += ' --start ' - command += ' --quiet' - command += ' --oknodo' - command += ' --pidfile ' + pidfile - command += ' --exec /usr/sbin/accel-pppd' - # now pass arguments to accel-pppd binary - command += ' --' - command += ' -c ' + sstp_conf - command += ' -p ' + pidfile - command += ' -d' - cmd(command) - - chk_con() + return None - else: - _accel_cmd('restart') + call('systemctl restart accel-ppp@sstp.service') if __name__ == '__main__': diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py index 586424c09..eb73293a9 100755 --- a/src/conf_mode/vrf.py +++ b/src/conf_mode/vrf.py @@ -18,15 +18,15 @@ import os from sys import exit from copy import deepcopy -from jinja2 import FileSystemLoader, Environment from json import loads from vyos.config import Config from vyos.configdict import list_diff -from vyos.defaults import directories as vyos_data_dir from vyos.ifconfig import Interface from vyos.util import read_file, cmd from vyos import ConfigError +from vyos.template import render + config_file = r'/etc/iproute2/rt_tables.d/vyos-vrf.conf' @@ -178,16 +178,7 @@ def verify(vrf_config): return None def generate(vrf_config): - # Prepare Jinja2 template loader from files - tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'vrf') - fs_loader = FileSystemLoader(tmpl_path) - env = Environment(loader=fs_loader) - - tmpl = env.get_template('vrf.conf.tmpl') - config_text = tmpl.render(vrf_config) - with open(config_file, 'w') as f: - f.write(config_text) - + render(config_file, 'vrf/vrf.conf.tmpl', vrf_config) return None def apply(vrf_config): diff --git a/src/conf_mode/vrrp.py b/src/conf_mode/vrrp.py index 3f1b73385..b9b0405e2 100755 --- a/src/conf_mode/vrrp.py +++ b/src/conf_mode/vrrp.py @@ -18,16 +18,16 @@ import os from sys import exit from ipaddress import ip_address, ip_interface, IPv4Interface, IPv6Interface, IPv4Address, IPv6Address -from jinja2 import FileSystemLoader, Environment from json import dumps from pathlib import Path import vyos.config import vyos.keepalived -from vyos.defaults import directories as vyos_data_dir from vyos import ConfigError from vyos.util import call +from vyos.template import render + daemon_file = "/etc/default/keepalived" config_file = "/etc/keepalived/keepalived.conf" @@ -201,11 +201,6 @@ def verify(data): def generate(data): - # Prepare Jinja2 template loader from files - tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'vrrp') - fs_loader = FileSystemLoader(tmpl_path) - env = Environment(loader=fs_loader) - vrrp_groups, sync_groups = data # Remove disabled groups from the sync group member lists @@ -217,16 +212,9 @@ def generate(data): # Filter out disabled groups vrrp_groups = list(filter(lambda x: x["disable"] is not True, vrrp_groups)) - tmpl = env.get_template('keepalived.conf.tmpl') - config_text = tmpl.render({"groups": vrrp_groups, "sync_groups": sync_groups}) - with open(config_file, 'w') as f: - f.write(config_text) - - tmpl = env.get_template('daemon.tmpl') - config_text = tmpl.render() - with open(daemon_file, 'w') as f: - f.write(config_text) - + render(config_file, 'vrrp/keepalived.conf.tmpl', + {"groups": vrrp_groups, "sync_groups": sync_groups}) + render(daemon_file, 'vrrp/daemon.tmpl', {}) return None diff --git a/src/etc/init.d/isc-dhcpv4-server b/src/etc/init.d/isc-dhcpv4-server deleted file mode 100755 index 94a1020ac..000000000 --- a/src/etc/init.d/isc-dhcpv4-server +++ /dev/null @@ -1,113 +0,0 @@ -#!/bin/sh -# -# - -### BEGIN INIT INFO -# Provides: isc-dhcpv4-server -# Required-Start: $remote_fs $network $syslog -# Required-Stop: $remote_fs $network $syslog -# Should-Start: $local_fs slapd $named -# Should-Stop: $local_fs slapd -# Default-Start: 2 3 4 5 -# Default-Stop: 0 1 6 -# Short-Description: IPv4 DHCP server -# Description: Dynamic Host Configuration Protocol Server for IPv4 -### END INIT INFO - -PATH=/sbin:/bin:/usr/sbin:/usr/bin - -test -f /usr/sbin/dhcpd || exit 0 - -DHCPD_DEFAULT="${DHCPD_DEFAULT:-/etc/default/isc-dhcpv4-server}" - -# It is not safe to start if we don't have a default configuration... -if [ ! -f "$DHCPD_DEFAULT" ]; then - echo "$DHCPD_DEFAULT does not exist! - Aborting..." - exit 0 -fi - -. /lib/lsb/init-functions - -# Read init script configuration -[ -f "$DHCPD_DEFAULT" ] && . "$DHCPD_DEFAULT" - -NAME=dhcpd -DESC="ISC DHCP server" -# fallback to default config file -DHCPD_CONF=${DHCPD_CONF:-/etc/dhcp/dhcpd.conf} -# try to read pid file name from config file, with fallback to /var/run/dhcpd.pid -if [ -z "$DHCPD_PID" ]; then - DHCPD_PID=$(sed -n -e 's/^[ \t]*pid-file-name[ \t]*"(.*)"[ \t]*;.*$/\1/p' < "$DHCPD_CONF" 2>/dev/null | head -n 1) -fi -DHCPD_PID="${DHCPD_PID:-/var/run/dhcpd.pid}" - -test_config() -{ - if ! /usr/sbin/dhcpd -t $OPTIONS -q -cf "$DHCPD_CONF" > /dev/null 2>&1; then - echo "dhcpd self-test failed. Please fix $DHCPD_CONF." - echo "The error was: " - /usr/sbin/dhcpd -t $OPTIONS -cf "$DHCPD_CONF" - exit 1 - fi - touch /var/lib/dhcp/dhcpd.leases -} - -# single arg is -v for messages, -q for none -check_status() -{ - if [ ! -r "$DHCPD_PID" ]; then - test "$1" != -v || echo "$NAME is not running." - return 3 - fi - if read pid < "$DHCPD_PID" && ps -p "$pid" > /dev/null 2>&1; then - test "$1" != -v || echo "$NAME is running." - return 0 - else - test "$1" != -v || echo "$NAME is not running but $DHCPD_PID exists." - return 1 - fi -} - -case "$1" in - start) - test_config - log_daemon_msg "Starting $DESC" "$NAME" - start-stop-daemon --start --oknodo --quiet --pidfile "$DHCPD_PID" \ - --exec /usr/sbin/dhcpd -- \ - -q $OPTIONS -cf "$DHCPD_CONF" -pf "$DHCPD_PID" $INTERFACES - sleep 2 - - if check_status -q; then - log_end_msg 0 - else - log_failure_msg "check syslog for diagnostics." - log_end_msg 1 - exit 1 - fi - ;; - stop) - log_daemon_msg "Stopping $DESC" "$NAME" - start-stop-daemon --stop --oknodo --quiet --pidfile "$DHCPD_PID" - log_end_msg $? - rm -f "$DHCPD_PID" - ;; - restart | force-reload) - test_config - $0 stop - sleep 2 - $0 start - if [ "$?" != "0" ]; then - exit 1 - fi - ;; - status) - echo -n "Status of $DESC: " - check_status -v - exit "$?" - ;; - *) - echo "Usage: $0 {start|stop|restart|force-reload|status}" - exit 1 -esac - -exit 0 diff --git a/src/etc/init.d/isc-dhcpv6-relay b/src/etc/init.d/isc-dhcpv6-relay deleted file mode 100755 index e553eafd1..000000000 --- a/src/etc/init.d/isc-dhcpv6-relay +++ /dev/null @@ -1,50 +0,0 @@ -#!/bin/sh -# -# - -### BEGIN INIT INFO -# Provides: isc-dhcpv6-relay -# Required-Start: $remote_fs $network -# Required-Stop: $remote_fs $network -# Should-Start: $local_fs -# Should-Stop: $local_fs -# Default-Start: 2 3 4 5 -# Default-Stop: 0 1 6 -# Short-Description: IPv6 DHCP relay -# Description: Dynamic Host Configuration Protocol Relay for IPv6 -### END INIT INFO - -# It is not safe to start if we don't have a default configuration... -if [ ! -f /etc/default/isc-dhcpv6-relay ]; then - echo "/etc/default/isc-dhcpv6-relay does not exist! - Aborting..." - exit 1 -fi - -# Source init functions -. /lib/lsb/init-functions - -# Read init script configuration (interfaces the daemon should listen on -# and the DHCP server we should forward requests to.) -[ -f /etc/default/isc-dhcpv6-relay ] && . /etc/default/isc-dhcpv6-relay - -DHCRELAYPID=/var/run/dhcv6relay.pid - -case "$1" in - start) - start-stop-daemon --start --oknodo --quiet --pidfile $DHCRELAYPID \ - --exec /usr/sbin/dhcrelay -- -q $OPTIONS -pf $DHCRELAYPID - ;; - stop) - start-stop-daemon --stop --oknodo --quiet --pidfile $DHCRELAYPID - ;; - restart | force-reload) - $0 stop - sleep 2 - $0 start - ;; - *) - echo "Usage: /etc/init.d/isc-dhcpv6-relay {start|stop|restart|force-reload}" - exit 1 -esac - -exit 0 diff --git a/src/etc/init.d/isc-dhcpv6-server b/src/etc/init.d/isc-dhcpv6-server deleted file mode 100755 index f6b27cb4a..000000000 --- a/src/etc/init.d/isc-dhcpv6-server +++ /dev/null @@ -1,113 +0,0 @@ -#!/bin/sh -# -# - -### BEGIN INIT INFO -# Provides: isc-dhcpv6-server -# Required-Start: $remote_fs $network $syslog -# Required-Stop: $remote_fs $network $syslog -# Should-Start: $local_fs slapd $named -# Should-Stop: $local_fs slapd -# Default-Start: 2 3 4 5 -# Default-Stop: 0 1 6 -# Short-Description: IPv6 DHCP server -# Description: Dynamic Host Configuration Protocol Server for IPv6 -### END INIT INFO - -PATH=/sbin:/bin:/usr/sbin:/usr/bin - -test -f /usr/sbin/dhcpd || exit 0 - -DHCPD_DEFAULT="${DHCPD_DEFAULT:-/etc/default/isc-dhcpv6-server}" - -# It is not safe to start if we don't have a default configuration... -if [ ! -f "$DHCPD_DEFAULT" ]; then - echo "$DHCPD_DEFAULT does not exist! - Aborting..." - exit 0 -fi - -. /lib/lsb/init-functions - -# Read init script configuration -[ -f "$DHCPD_DEFAULT" ] && . "$DHCPD_DEFAULT" - -NAME=dhcpdv6 -DESC="ISC DHCP server IPv6" -# fallback to default config file -DHCPD_CONF=${DHCPD_CONF:-/etc/dhcp/dhcpdv6.conf} -# try to read pid file name from config file, with fallback to /var/run/dhcpdv6.pid -if [ -z "$DHCPD_PID" ]; then - DHCPD_PID=$(sed -n -e 's/^[ \t]*pid-file-name[ \t]*"(.*)"[ \t]*;.*$/\1/p' < "$DHCPD_CONF" 2>/dev/null | head -n 1) -fi -DHCPD_PID="${DHCPD_PID:-/var/run/dhcpdv6.pid}" - -test_config() -{ - if ! /usr/sbin/dhcpd -t $OPTIONS -q -cf "$DHCPD_CONF" > /dev/null 2>&1; then - echo "dhcpd self-test failed. Please fix $DHCPD_CONF." - echo "The error was: " - /usr/sbin/dhcpd -t $OPTIONS -cf "$DHCPD_CONF" - exit 1 - fi - touch /var/lib/dhcp/dhcpdv6.leases -} - -# single arg is -v for messages, -q for none -check_status() -{ - if [ ! -r "$DHCPD_PID" ]; then - test "$1" != -v || echo "$NAME is not running." - return 3 - fi - if read pid < "$DHCPD_PID" && ps -p "$pid" > /dev/null 2>&1; then - test "$1" != -v || echo "$NAME is running." - return 0 - else - test "$1" != -v || echo "$NAME is not running but $DHCPD_PID exists." - return 1 - fi -} - -case "$1" in - start) - test_config - log_daemon_msg "Starting $DESC" "$NAME" - start-stop-daemon --start --oknodo --quiet --pidfile "$DHCPD_PID" \ - --exec /usr/sbin/dhcpd -- \ - -q $OPTIONS -cf "$DHCPD_CONF" -pf "$DHCPD_PID" $INTERFACES - sleep 2 - - if check_status -q; then - log_end_msg 0 - else - log_failure_msg "check syslog for diagnostics." - log_end_msg 1 - exit 1 - fi - ;; - stop) - log_daemon_msg "Stopping $DESC" "$NAME" - start-stop-daemon --stop --oknodo --quiet --pidfile "$DHCPD_PID" - log_end_msg $? - rm -f "$DHCPD_PID" - ;; - restart | force-reload) - test_config - $0 stop - sleep 2 - $0 start - if [ "$?" != "0" ]; then - exit 1 - fi - ;; - status) - echo -n "Status of $DESC: " - check_status -v - exit "$?" - ;; - *) - echo "Usage: $0 {start|stop|restart|force-reload|status}" - exit 1 -esac - -exit 0 diff --git a/src/etc/systemd/system/hostapd@.service.d/override.conf b/src/etc/systemd/system/hostapd@.service.d/override.conf new file mode 100644 index 000000000..bb8e81d7a --- /dev/null +++ b/src/etc/systemd/system/hostapd@.service.d/override.conf @@ -0,0 +1,10 @@ +[Unit] +After= +After=vyos-router.service + +[Service] +WorkingDirectory=/run/hostapd +EnvironmentFile= +ExecStart= +ExecStart=/usr/sbin/hostapd -B -P /run/hostapd/%i.pid /run/hostapd/%i.conf +PIDFile=/run/hostapd/%i.pid diff --git a/src/etc/systemd/system/openvpn@.service.d/override.conf b/src/etc/systemd/system/openvpn@.service.d/override.conf new file mode 100644 index 000000000..7946484a3 --- /dev/null +++ b/src/etc/systemd/system/openvpn@.service.d/override.conf @@ -0,0 +1,9 @@ +[Unit] +After= +After=vyos-router.service + +[Service] +WorkingDirectory= +WorkingDirectory=/run/openvpn +ExecStart= +ExecStart=/usr/sbin/openvpn --daemon openvpn-%i --config %i.conf --status %i.status 30 --writepid %i.pid diff --git a/src/etc/systemd/system/pdns-recursor.service.d/override.conf b/src/etc/systemd/system/pdns-recursor.service.d/override.conf new file mode 100644 index 000000000..602d7b774 --- /dev/null +++ b/src/etc/systemd/system/pdns-recursor.service.d/override.conf @@ -0,0 +1,5 @@ +[Service] +WorkingDirectory= +WorkingDirectory=/run/powerdns +ExecStart= +ExecStart=/usr/sbin/pdns_recursor --daemon=no --write-pid=no --disable-syslog --log-timestamp=no --config-dir=/run/powerdns diff --git a/src/etc/systemd/system/wpa_supplicant@.service.d/override.conf b/src/etc/systemd/system/wpa_supplicant@.service.d/override.conf new file mode 100644 index 000000000..20b25b726 --- /dev/null +++ b/src/etc/systemd/system/wpa_supplicant@.service.d/override.conf @@ -0,0 +1,10 @@ +[Unit] +After= +After=vyos-router.service + +[Service] +WorkingDirectory= +WorkingDirectory=/run/wpa_supplicant +EnvironmentFile= +ExecStart= +ExecStart=/sbin/wpa_supplicant -c%I.conf -Dnl80211,wext -i%I diff --git a/src/migration-scripts/l2tp/2-to-3 b/src/migration-scripts/l2tp/2-to-3 new file mode 100755 index 000000000..bd0839e03 --- /dev/null +++ b/src/migration-scripts/l2tp/2-to-3 @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020 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/>. + +# - remove primary/secondary identifier from nameserver +# - TODO: remove radius server req-limit + +import os +import sys + +from sys import argv, exit +from vyos.configtree import ConfigTree + +if (len(argv) < 1): + print("Must specify file name!") + exit(1) + +file_name = argv[1] + +with open(file_name, 'r') as f: + config_file = f.read() + +config = ConfigTree(config_file) +base = ['vpn', 'l2tp', 'remote-access'] +if not config.exists(base): + # Nothing to do + exit(0) +else: + + # Migrate IPv4 DNS servers + dns_base = base + ['dns-servers'] + if config.exists(dns_base): + for server in ['server-1', 'server-2']: + if config.exists(dns_base + [server]): + dns = config.return_value(dns_base + [server]) + config.set(base + ['name-server'], value=dns, replace=False) + + config.delete(dns_base) + + # Migrate IPv6 DNS servers + dns_base = base + ['dnsv6-servers'] + if config.exists(dns_base): + for server in config.return_values(dns_base): + config.set(base + ['name-server'], value=server, replace=False) + + config.delete(dns_base) + + # Migrate IPv4 WINS servers + wins_base = base + ['wins-servers'] + if config.exists(wins_base): + for server in ['server-1', 'server-2']: + if config.exists(wins_base + [server]): + wins = config.return_value(wins_base + [server]) + config.set(base + ['wins-server'], value=wins, replace=False) + + config.delete(wins_base) + + + # Remove RADIUS server req-limit node + radius_base = base + ['authentication', 'radius'] + if config.exists(radius_base): + for server in config.list_nodes(radius_base + ['server']): + if config.exists(radius_base + ['server', server, 'req-limit']): + config.delete(radius_base + ['server', server, 'req-limit']) + + # Migrate IPv6 prefixes + ipv6_base = base + ['client-ipv6-pool'] + if config.exists(ipv6_base + ['prefix']): + prefix_old = config.return_values(ipv6_base + ['prefix']) + # delete old prefix CLI nodes + config.delete(ipv6_base + ['prefix']) + # create ned prefix tag node + config.set(ipv6_base + ['prefix']) + config.set_tag(ipv6_base + ['prefix']) + + for p in prefix_old: + prefix = p.split(',')[0] + mask = p.split(',')[1] + config.set(ipv6_base + ['prefix', prefix, 'mask'], value=mask) + + if config.exists(ipv6_base + ['delegate-prefix']): + prefix_old = config.return_values(ipv6_base + ['delegate-prefix']) + # delete old delegate prefix CLI nodes + config.delete(ipv6_base + ['delegate-prefix']) + # create ned delegation tag node + config.set(ipv6_base + ['delegate ']) + config.set_tag(ipv6_base + ['delegate ']) + + for p in prefix_old: + prefix = p.split(',')[0] + mask = p.split(',')[1] + config.set(ipv6_base + ['delegate', prefix, 'mask'], value=mask) + + try: + with open(file_name, 'w') as f: + f.write(config.to_string()) + except OSError as e: + print("Failed to save the modified config: {}".format(e)) + exit(1) diff --git a/src/op_mode/dns_forwarding_restart.sh b/src/op_mode/dns_forwarding_restart.sh index 8e556f2f0..64cc92115 100755 --- a/src/op_mode/dns_forwarding_restart.sh +++ b/src/op_mode/dns_forwarding_restart.sh @@ -2,7 +2,7 @@ if cli-shell-api existsEffective service dns forwarding; then echo "Restarting the DNS forwarding service" - systemctl restart pdns-recursor + systemctl restart pdns-recursor.service else echo "DNS forwarding is not configured" fi diff --git a/src/op_mode/dynamic_dns.py b/src/op_mode/dynamic_dns.py index 405dd9f04..e4e5043d5 100755 --- a/src/op_mode/dynamic_dns.py +++ b/src/op_mode/dynamic_dns.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2018 VyOS maintainers and contributors +# Copyright (C) 2018-2020 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 @@ -23,8 +23,7 @@ import time from vyos.config import Config from vyos.util import call - -cache_file = r'/var/cache/ddclient/ddclient.cache' +cache_file = r'/run/ddclient/ddclient.cache' OUT_TMPL_SRC = """ {%- for entry in hosts -%} @@ -86,9 +85,9 @@ def show_status(): def update_ddns(): - call('systemctl stop ddclient') + call('systemctl stop ddclient.service') os.remove(cache_file) - call('systemctl start ddclient') + call('systemctl start ddclient.service') def main(): diff --git a/src/op_mode/flow_accounting_op.py b/src/op_mode/flow_accounting_op.py index 7f3ad7476..bf8c39fd6 100755 --- a/src/op_mode/flow_accounting_op.py +++ b/src/op_mode/flow_accounting_op.py @@ -70,13 +70,13 @@ def _is_host(host): # check if flow-accounting running def _uacctd_running(): - command = '/usr/bin/sudo /bin/systemctl status uacctd > /dev/null' + command = 'systemctl status uacctd.service > /dev/null' return run(command) == 0 # get list of interfaces def _get_ifaces_dict(): # run command to get ifaces list - out = cmd('/bin/ip link show', universal_newlines=True) + out = cmd('/bin/ip link show') # read output ifaces_out = out.splitlines() @@ -95,7 +95,6 @@ def _get_ifaces_dict(): def _get_flows_list(): # run command to get flows list out = cmd(f'/usr/bin/pmacct -s -O json -T flows -p {uacctd_pipefile}', - universal_newlines=True, message='Failed to get flows list') # read output @@ -196,7 +195,7 @@ if not _uacctd_running(): # restart pmacct daemon if cmd_args.action == 'restart': # run command to restart flow-accounting - cmd('/usr/bin/sudo /bin/systemctl restart uacctd', + cmd('systemctl restart uacctd.service', message='Failed to restart flow-accounting') # clear in-memory collected flows diff --git a/src/op_mode/generate_ssh_server_key.py b/src/op_mode/generate_ssh_server_key.py index f65d383c0..cbc9ef973 100755 --- a/src/op_mode/generate_ssh_server_key.py +++ b/src/op_mode/generate_ssh_server_key.py @@ -14,14 +14,13 @@ # 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 - +from sys import exit from vyos.util import ask_yes_no from vyos.util import cmd if not ask_yes_no('Do you really want to remove the existing SSH host keys?'): - sys.exit(0) + exit(0) -cmd('sudo rm -v /etc/ssh/ssh_host_*') -cmd('sudo dpkg-reconfigure openssh-server') -cmd('sudo systemctl restart ssh') +cmd('rm -v /etc/ssh/ssh_host_*') +cmd('dpkg-reconfigure openssh-server') +cmd('systemctl restart ssh.service') diff --git a/src/op_mode/powerctrl.py b/src/op_mode/powerctrl.py index 0f3619411..4ab91384b 100755 --- a/src/op_mode/powerctrl.py +++ b/src/op_mode/powerctrl.py @@ -24,6 +24,7 @@ from vyos.util import ask_yes_no from vyos.util import cmd from vyos.util import call from vyos.util import run +from vyos.util import STDOUT systemd_sched_file = "/run/systemd/shutdown/scheduled" @@ -97,14 +98,14 @@ def execute_shutdown(time, reboot = True, ask=True): chk_vyatta_based_reboots() ### - out = cmd(f'/sbin/shutdown {action} now') + out = cmd(f'/sbin/shutdown {action} now', stderr=STDOUT) print(out.split(",",1)[0]) return elif len(time) == 1: # Assume the argument is just time ts = parse_time(time[0]) if ts: - cmd(f'/sbin/shutdown {action} {time[0]}') + cmd(f'/sbin/shutdown {action} {time[0]}', stderr=STDOUT) else: sys.exit("Invalid time \"{0}\". The valid format is HH:MM".format(time[0])) elif len(time) == 2: @@ -115,7 +116,7 @@ def execute_shutdown(time, reboot = True, ask=True): t = datetime.combine(ds, ts) td = t - datetime.now() t2 = 1 + int(td.total_seconds())//60 # Get total minutes - cmd('/sbin/shutdown {action} {t2}') + cmd('/sbin/shutdown {action} {t2}', stderr=STDOUT) else: if not ts: sys.exit("Invalid time \"{0}\". The valid format is HH:MM".format(time[0])) diff --git a/src/op_mode/reset_openvpn.py b/src/op_mode/reset_openvpn.py index 618cad5ea..dbd3eb4d1 100755 --- a/src/op_mode/reset_openvpn.py +++ b/src/op_mode/reset_openvpn.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2018 VyOS maintainers and contributors +# Copyright (C) 2018-2020 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 @@ -14,57 +14,18 @@ # 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 - -from time import sleep -from netifaces import interfaces -from vyos.util import process_running, cmd - -def get_config_name(intf): - cfg_file = r'/opt/vyatta/etc/openvpn/openvpn-{}.conf'.format(intf) - return cfg_file - -def get_pid_file(intf): - pid_file = r'/var/run/openvpn/{}.pid'.format(intf) - return pid_file - +from sys import argv, exit +from vyos.util import call if __name__ == '__main__': - if (len(sys.argv) < 1): - print("Must specify OpenVPN interface name!") - sys.exit(1) - - interface = sys.argv[1] - if os.path.isfile(get_config_name(interface)): - pidfile = '/var/run/openvpn/{}.pid'.format(interface) - if process_running(pidfile): - command = 'start-stop-daemon' - command += ' --stop' - command += ' --oknodo' - command += ' --quiet' - command += ' --pidfile ' + pidfile - cmd(command) - - # When stopping OpenVPN we need to wait for the 'old' interface to - # vanish from the Kernel, if it is not gone, OpenVPN will report: - # ERROR: Cannot ioctl TUNSETIFF vtun10: Device or resource busy (errno=16) - while interface in interfaces(): - sleep(0.250) # 250ms - - # re-start OpenVPN process - command = 'start-stop-daemon' - command += ' --start' - command += ' --oknodo' - command += ' --quiet' - command += ' --pidfile ' + get_pid_file(interface) - command += ' --exec /usr/sbin/openvpn' - # now pass arguments to openvpn binary - command += ' --' - command += ' --daemon openvpn-' + interface - command += ' --config ' + get_config_name(interface) + if (len(argv) < 1): + print('Must specify OpenVPN interface name!') + exit(1) - cmd(command) + interface = argv[1] + if os.path.isfile(f'/run/openvpn/{interface}.conf'): + call(f'systemctl restart openvpn@{interface}.service') else: - print("OpenVPN interface {} does not exist!".format(interface)) - sys.exit(1) + print(f'OpenVPN interface "{interface}" does not exist!') + exit(1) diff --git a/src/op_mode/restart_dhcp_relay.py b/src/op_mode/restart_dhcp_relay.py index 66dc435b3..af4fb2d15 100755 --- a/src/op_mode/restart_dhcp_relay.py +++ b/src/op_mode/restart_dhcp_relay.py @@ -39,7 +39,7 @@ if __name__ == '__main__': if not c.exists_effective('service dhcp-relay'): print("DHCP relay service not configured") else: - call('sudo systemctl restart isc-dhcp-relay.service') + call('systemctl restart isc-dhcp-server.service') sys.exit(0) elif args.ipv6: @@ -47,7 +47,7 @@ if __name__ == '__main__': if not c.exists_effective('service dhcpv6-relay'): print("DHCPv6 relay service not configured") else: - call('sudo systemctl restart isc-dhcpv6-relay.service') + call('systemctl restart isc-dhcp-server6.service') sys.exit(0) else: diff --git a/src/op_mode/show_dhcp.py b/src/op_mode/show_dhcp.py index a79033f69..c49e604b7 100755 --- a/src/op_mode/show_dhcp.py +++ b/src/op_mode/show_dhcp.py @@ -193,7 +193,7 @@ if __name__ == '__main__': sys.exit(0) # if dhcp server is down, inactive leases may still be shown as active, so warn the user. - if call('systemctl -q is-active isc-dhcpv4-server.service') != 0: + if call('systemctl -q is-active isc-dhcp-server.service') != 0: print("WARNING: DHCP server is configured but not started. Data may be stale.") if args.leases: diff --git a/src/op_mode/show_dhcpv6.py b/src/op_mode/show_dhcpv6.py index 18baa5517..d686defc0 100755 --- a/src/op_mode/show_dhcpv6.py +++ b/src/op_mode/show_dhcpv6.py @@ -179,7 +179,7 @@ if __name__ == '__main__': sys.exit(0) # if dhcp server is down, inactive leases may still be shown as active, so warn the user. - if call('systemctl -q is-active isc-dhcpv6-server.service') != 0: + if call('systemctl -q is-active isc-dhcp-server6.service') != 0: print("WARNING: DHCPv6 server is configured but not started. Data may be stale.") if args.leases: diff --git a/src/op_mode/version.py b/src/op_mode/version.py index fe6ecbae5..8599c958f 100755 --- a/src/op_mode/version.py +++ b/src/op_mode/version.py @@ -33,6 +33,7 @@ import vyos.limericks from vyos.util import cmd from vyos.util import call from vyos.util import run +from vyos.util import DEVNULL parser = argparse.ArgumentParser() @@ -82,7 +83,7 @@ if __name__ == '__main__': # Get hypervisor name, if any system_type = "bare metal" try: - hypervisor = cmd('hvinfo 2>/dev/null') + hypervisor = cmd('hvinfo',stderr=DEVNULL) system_type = "{0} guest".format(hypervisor) except OSError: # hvinfo returns 1 if it cannot detect any hypervisor diff --git a/src/system/keepalived-fifo.py b/src/system/keepalived-fifo.py index 2778deaab..7e2076820 100755 --- a/src/system/keepalived-fifo.py +++ b/src/system/keepalived-fifo.py @@ -87,7 +87,7 @@ class KeepalivedFifo: def _run_command(self, command): logger.debug("Running the command: {}".format(command)) try: - cmd(command, universal_newlines=True) + cmd(command) except OSError as err: logger.error(f'Unable to execute command "{command}": {err}') diff --git a/src/systemd/accel-ppp@.service b/src/systemd/accel-ppp@.service new file mode 100644 index 000000000..256112769 --- /dev/null +++ b/src/systemd/accel-ppp@.service @@ -0,0 +1,16 @@ +[Unit] +Description=Accel-PPP - High performance VPN server application for Linux +RequiresMountsFor=/run +ConditionPathExists=/run/accel-pppd/%i.conf +After=vyos-router.service + +[Service] +WorkingDirectory=/run/accel-pppd +ExecStart=/usr/sbin/accel-pppd -d -p /run/accel-pppd/%i.pid -c /run/accel-pppd/%i.conf +ExecReload=/bin/kill -SIGUSR1 $MAINPID +PIDFile=/run/accel-pppd/%i.pid +Type=forking +Restart=always + +[Install] +WantedBy=multi-user.target diff --git a/src/systemd/ddclient.service b/src/systemd/ddclient.service new file mode 100644 index 000000000..a4d55827a --- /dev/null +++ b/src/systemd/ddclient.service @@ -0,0 +1,14 @@ +[Unit] +Description=Dynamic DNS Update Client +RequiresMountsFor=/run +ConditionPathExists=/run/ddclient/ddclient.conf +After=vyos-router.service + +[Service] +WorkingDirectory=/run/ddclient +Type=forking +PIDFile=/run/ddclient/ddclient.pid +ExecStart=/usr/sbin/ddclient -cache /run/ddclient/ddclient.cache -pid /run/ddclient/ddclient.pid -file /run/ddclient/ddclient.conf + +[Install] +WantedBy=multi-user.target diff --git a/src/systemd/isc-dhcp-relay.service b/src/systemd/isc-dhcp-relay.service new file mode 100644 index 000000000..ebf4d234e --- /dev/null +++ b/src/systemd/isc-dhcp-relay.service @@ -0,0 +1,14 @@ +[Unit] +Description=ISC DHCP IPv4 relay +Documentation=man:dhcrelay(8) +Wants=network-online.target +ConditionPathExists=/run/dhcp-relay/dhcp.conf +After=vyos-router.service + +[Service] +WorkingDirectory=/run/dhcp-relay +EnvironmentFile=/run/dhcp-relay/dhcp.conf +ExecStart=/usr/sbin/dhcrelay -d -4 $OPTIONS + +[Install] +WantedBy=multi-user.target diff --git a/src/systemd/isc-dhcp-relay6.service b/src/systemd/isc-dhcp-relay6.service new file mode 100644 index 000000000..a477618b1 --- /dev/null +++ b/src/systemd/isc-dhcp-relay6.service @@ -0,0 +1,14 @@ +[Unit] +Description=ISC DHCP IPv6 relay +Documentation=man:dhcrelay(8) +Wants=network-online.target +ConditionPathExists=/run/dhcp-relay/dhcpv6.conf +After=vyos-router.service + +[Service] +WorkingDirectory=/run/dhcp-relay +EnvironmentFile=/run/dhcp-relay/dhcpv6.conf +ExecStart=/usr/sbin/dhcrelay -d -6 $OPTIONS + +[Install] +WantedBy=multi-user.target diff --git a/src/systemd/isc-dhcp-server.service b/src/systemd/isc-dhcp-server.service new file mode 100644 index 000000000..d848e3df1 --- /dev/null +++ b/src/systemd/isc-dhcp-server.service @@ -0,0 +1,19 @@ +[Unit] +Description=ISC DHCP IPv4 server +Documentation=man:dhcpd(8) +RequiresMountsFor=/run +ConditionPathExists=/run/dhcp-server/dhcpd.conf +After=vyos-router.service + +[Service] +WorkingDirectory=/run/dhcp-server +# The leases files need to be root:vyattacfg even when dropping privileges +ExecStart=/bin/sh -ec '\ + CONFIG_FILE=/run/dhcp-server/dhcpd.conf; \ + [ -e /config/dhcpd.leases ] || touch /config/dhcpd.leases; \ + chown root:vyattacfg /config/dhcpd.leases; \ + chmod 664 /config/dhcpd.leases; \ + exec /usr/sbin/dhcpd -user nobody -group nogroup -f -4 -pf /run/dhcp-server/dhcpd.pid -cf $CONFIG_FILE -lf /config/dhcpd.leases' + +[Install] +WantedBy=multi-user.target diff --git a/src/systemd/isc-dhcp-server6.service b/src/systemd/isc-dhcp-server6.service new file mode 100644 index 000000000..743f16840 --- /dev/null +++ b/src/systemd/isc-dhcp-server6.service @@ -0,0 +1,18 @@ +[Unit] +Description=ISC DHCP IPv6 server +Documentation=man:dhcpd(8) +RequiresMountsFor=/run +ConditionPathExists=/run/dhcp-server/dhcpd.conf +After=vyos-router.service + +[Service] +WorkingDirectory=/run/dhcp-server +# The leases files need to be root:vyattacfg even when dropping privileges +ExecStart=/bin/sh -ec '\ + [ -e /config/dhcpdv6.leases ] || touch /config/dhcpdv6.leases; \ + chown root:vyattacfg /config/dhcpdv6.leases; \ + chmod 664 /config/dhcpdv6.leases; \ + exec /usr/sbin/dhcpd -user nobody -group nogroup -f -6 -pf /run/dhcp-server/dhcpdv6.pid -cf /run/dhcp-server/dhcpdv6.conf -lf /config/dhcpdv6.leases' + +[Install] +WantedBy=multi-user.target diff --git a/src/etc/systemd/system/ppp@.service b/src/systemd/ppp@.service index d271efb41..bb4622034 100644 --- a/src/etc/systemd/system/ppp@.service +++ b/src/systemd/ppp@.service @@ -1,6 +1,6 @@ [Unit] Description=Dialing PPP connection %I -After=network.target +After=vyos-router.service [Service] ExecStart=/usr/sbin/pppd call %I nodetach nolog diff --git a/src/systemd/tftpd@.service b/src/systemd/tftpd@.service index e5c289466..266bc0962 100644 --- a/src/systemd/tftpd@.service +++ b/src/systemd/tftpd@.service @@ -1,6 +1,6 @@ [Unit] Description=TFTP server -After=network.target +After=vyos-router.service RequiresMountsFor=/run [Service] |