summaryrefslogtreecommitdiff
path: root/src/conf_mode
diff options
context:
space:
mode:
Diffstat (limited to 'src/conf_mode')
-rwxr-xr-xsrc/conf_mode/accel_l2tp.py397
-rwxr-xr-xsrc/conf_mode/bcast_relay.py17
-rwxr-xr-xsrc/conf_mode/dhcp_relay.py32
-rwxr-xr-xsrc/conf_mode/dhcp_server.py56
-rwxr-xr-xsrc/conf_mode/dhcpv6_relay.py27
-rwxr-xr-xsrc/conf_mode/dhcpv6_server.py54
-rwxr-xr-xsrc/conf_mode/dns_forwarding.py21
-rwxr-xr-xsrc/conf_mode/dynamic_dns.py44
-rwxr-xr-xsrc/conf_mode/flow_accounting_conf.py31
-rwxr-xr-xsrc/conf_mode/host_name.py2
-rwxr-xr-xsrc/conf_mode/https.py13
-rwxr-xr-xsrc/conf_mode/igmp_proxy.py14
-rwxr-xr-xsrc/conf_mode/interfaces-openvpn.py334
-rwxr-xr-xsrc/conf_mode/interfaces-pppoe.py53
-rwxr-xr-xsrc/conf_mode/interfaces-tunnel.py6
-rwxr-xr-xsrc/conf_mode/interfaces-wireless.py134
-rwxr-xr-xsrc/conf_mode/interfaces-wirelessmodem.py48
-rwxr-xr-xsrc/conf_mode/ipsec-settings.py29
-rwxr-xr-xsrc/conf_mode/le_cert.py13
-rwxr-xr-xsrc/conf_mode/lldp.py19
-rwxr-xr-xsrc/conf_mode/mdns_repeater.py19
-rwxr-xr-xsrc/conf_mode/ntp.py17
-rwxr-xr-xsrc/conf_mode/protocols_bfd.py14
-rwxr-xr-xsrc/conf_mode/protocols_igmp.py14
-rwxr-xr-xsrc/conf_mode/protocols_mpls.py15
-rwxr-xr-xsrc/conf_mode/protocols_pim.py14
-rwxr-xr-xsrc/conf_mode/salt-minion.py13
-rwxr-xr-xsrc/conf_mode/service-ipoe.py20
-rwxr-xr-xsrc/conf_mode/service-pppoe.py19
-rwxr-xr-xsrc/conf_mode/service-router-advert.py13
-rwxr-xr-xsrc/conf_mode/snmp.py31
-rwxr-xr-xsrc/conf_mode/ssh.py13
-rwxr-xr-xsrc/conf_mode/system-login.py42
-rwxr-xr-xsrc/conf_mode/system-syslog.py25
-rwxr-xr-xsrc/conf_mode/system-wifi-regdom.py21
-rwxr-xr-xsrc/conf_mode/tftp_server.py15
-rwxr-xr-xsrc/conf_mode/vpn-pptp.py21
-rwxr-xr-xsrc/conf_mode/vpn_l2tp.py386
-rwxr-xr-xsrc/conf_mode/vpn_sstp.py130
-rwxr-xr-xsrc/conf_mode/vrf.py15
-rwxr-xr-xsrc/conf_mode/vrrp.py22
41 files changed, 868 insertions, 1355 deletions
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("&quot;",'"')
-
- 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("&quot;", '"'))
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("&quot;",'"')
- 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("&quot;", '"'))
+ 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