diff options
Diffstat (limited to 'src')
45 files changed, 1244 insertions, 4511 deletions
diff --git a/src/conf_mode/accel_l2tp.py b/src/conf_mode/accel_l2tp.py index a7af9cc68..77e1ee874 100755 --- a/src/conf_mode/accel_l2tp.py +++ b/src/conf_mode/accel_l2tp.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,19 +13,18 @@  #  # You should have received a copy of the GNU General Public License  # along with this program.  If not, see <http://www.gnu.org/licenses/>. -# -#  import sys  import os  import re  import subprocess -import jinja2  import socket  import time -import syslog as sl + +from jinja2 import FileSystemLoader, Environment  from vyos.config import Config +from vyos.defaults import directories as vyos_data_dir  from vyos import ConfigError  pidfile = r'/var/run/accel_l2tp.pid' @@ -34,526 +33,371 @@ 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 +# config path creation  if not os.path.exists(l2tp_cnf_dir): -  os.makedirs(l2tp_cnf_dir) -  sl.syslog(sl.LOG_NOTICE, l2tp_cnf_dir  + " created") - -l2tp_config = ''' -### generated by accel_l2tp.py ### -[modules] -log_syslog -l2tp -chap-secrets -{% for proto in authentication['auth_proto']: %} -{{proto}} -{% endfor%} -{% if authentication['mode'] == 'radius' %} -radius -{% endif -%} -ippool -shaper -ipv6pool -ipv6_nd -ipv6_dhcp - -[core] -thread-count={{thread_cnt}} - -[log] -syslog=accel-l2tp,daemon -copy=1 -level=5 - -{% if dns %} -[dns] -{% if dns[0] %} -dns1={{dns[0]}} -{% endif %} -{% if dns[1] %} -dns2={{dns[1]}} -{% endif %} -{% endif -%} - -{% if dnsv6 %} -[ipv6-dns] -{% for srv in dnsv6: %} -{{srv}} -{% endfor %} -{% endif %} - -{% if wins %} -[wins] -{% if wins[0] %} -wins1={{wins[0]}} -{% endif %} -{% if wins[1] %} -wins2={{wins[1]}} -{% endif %} -{% endif -%} - -[l2tp] -verbose=1 -ifname=l2tp%d -ppp-max-mtu={{mtu}} -mppe={{authentication['mppe']}} -{% if outside_addr %} -bind={{outside_addr}} -{% endif %} -{% if lns_shared_secret %} -secret={{lns_shared_secret}} -{% endif %} - -[client-ip-range] -0.0.0.0/0 - -{% if (client_ip_pool) or (client_ip_subnets) %} -[ip-pool] -{% if client_ip_pool %} -{{client_ip_pool}} -{% endif -%} -{% if client_ip_subnets %} -{% for sn in client_ip_subnets %} -{{sn}} -{% endfor -%} -{% endif %} -{% endif %} -{% if gateway_address %} -gw-ip-address={{gateway_address}} -{% endif %} - -{% if authentication['mode'] == 'local' %} -[chap-secrets] -chap-secrets=/etc/accel-ppp/l2tp/chap-secrets -{% if gateway_address %} -gw-ip-address={{gateway_address}} -{% endif %} -{% endif %} - -[ppp] -verbose=1 -check-ip=1 -single-session=replace -{% if idle_timeout %} -lcp-echo-timeout={{idle_timeout}} -{% endif %} -{% if ppp_options['lcp-echo-interval'] %} -lcp-echo-interval={{ppp_options['lcp-echo-interval']}} -{% else %} -lcp-echo-interval=30 -{% endif %} -{% if ppp_options['lcp-echo-failure'] %} -lcp-echo-failure={{ppp_options['lcp-echo-failure']}} -{% else %} -lcp-echo-failure=3 -{% endif %} -{% if ccp_disable %} -ccp=0 -{% endif %} -{% if client_ipv6_pool %} -ipv6=allow -{% endif %} - -{% if authentication['mode'] == 'radius' %} -[radius] -{% for rsrv in authentication['radiussrv']: %} -server={{rsrv}},{{authentication['radiussrv'][rsrv]['secret']}},\ -req-limit={{authentication['radiussrv'][rsrv]['req-limit']}},\ -fail-time={{authentication['radiussrv'][rsrv]['fail-time']}} -{% endfor %} -{% if authentication['radiusopt']['timeout'] %} -timeout={{authentication['radiusopt']['timeout']}} -{% endif %} -{% if authentication['radiusopt']['acct-timeout'] %} -acct-timeout={{authentication['radiusopt']['acct-timeout']}} -{% endif %} -{% if authentication['radiusopt']['max-try'] %} -max-try={{authentication['radiusopt']['max-try']}} -{% endif %} -{% if authentication['radiusopt']['nas-id'] %} -nas-identifier={{authentication['radiusopt']['nas-id']}} -{% endif %} -{% if authentication['radius_source_address'] %} -nas-ip-address={{authentication['radius_source_address']}} -{% endif -%} -{% if authentication['radiusopt']['dae-srv'] %} -dae-server={{authentication['radiusopt']['dae-srv']['ip-addr']}}:\ -{{authentication['radiusopt']['dae-srv']['port']}},\ -{{authentication['radiusopt']['dae-srv']['secret']}} -{% endif -%} -gw-ip-address={{gateway_address}} -verbose=1 -{% endif -%} - -{% if client_ipv6_pool %} -[ipv6-pool] -{% for prfx in client_ipv6_pool.prefix: %} -{{prfx}} -{% endfor %} -{% for prfx in client_ipv6_pool.delegate_prefix: %} -delegate={{prfx}} -{% endfor %} -{% endif %} - -{% if client_ipv6_pool['delegate_prefix'] %} -[ipv6-dhcp] -verbose=1 -{% endif %} - -{% if authentication['radiusopt']['shaper'] %} -[shaper] -verbose=1 -attr={{authentication['radiusopt']['shaper']['attr']}} -{% if authentication['radiusopt']['shaper']['vendor'] %} -vendor={{authentication['radiusopt']['shaper']['vendor']}} -{% endif -%} -{% endif %} - -[cli] -tcp=127.0.0.1:2004 -sessions-columns=ifname,username,calling-sid,ip,{{ip6_column}}{{ip6_dp_column}}rate-limit,type,comp,state,rx-bytes,tx-bytes,uptime - -''' - -### l2tp chap secrets -chap_secrets_conf = ''' -# username  server  password  acceptable local IP addresses shaper -{% for user in authentication['local-users'] %} -{% if authentication['local-users'][user]['state'] == 'enabled' %} -{% if (authentication['local-users'][user]['upload']) and (authentication['local-users'][user]['download']) %} -{{user}}\t*\t{{authentication['local-users'][user]['passwd']}}\t{{authentication['local-users'][user]['ip']}}\t\ -{{authentication['local-users'][user]['download']}}/{{authentication['local-users'][user]['upload']}} -{% else %} -{{user}}\t*\t{{authentication['local-users'][user]['passwd']}}\t{{authentication['local-users'][user]['ip']}} -{% endif %} -{% endif %} -{% endfor %} -''' +    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  +# 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 - -### chap_secrets file if auth mode local -def write_chap_secrets(c): -  tmpl = jinja2.Template(chap_secrets_conf, trim_blocks=True) -  chap_secrets_txt = tmpl.render(c) -  old_umask = os.umask(0o077) -  open(chap_secrets,'w').write(chap_secrets_txt) -  os.umask(old_umask) -  sl.syslog(sl.LOG_NOTICE, chap_secrets + ' written') +    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(cmd=''): -  if not cmd: -    return None -  try: -    ret = subprocess.check_output(['/usr/bin/accel-cmd','-p','2004',cmd]).decode().strip() -    return ret -  except: -    return 1 - -###  +    if not cmd: +        return None +    try: +        ret = subprocess.check_output( +            ['/usr/bin/accel-cmd', '-p', '2004', cmd]).decode().strip() +        return ret +    except: +        return 1 + +###  # 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'     : { +    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'          }, -    '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 +        '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") +    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 -   -  ### 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) +    if c == None: +        return None -  tmpl = jinja2.Template(l2tp_config, trim_blocks=True) -  config_text = tmpl.render(c) -  open(l2tp_conf,'w').write(config_text) +    # 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) -  if c['authentication']['local-users']: -    write_chap_secrets(c) +    # 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 -  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 = subprocess.call(['/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') -    sl.syslog(sl.LOG_NOTICE, "reloading config via daemon restart") +    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 = subprocess.call( +            ['/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) +    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 8889e701c..96576ddd4 100755 --- a/src/conf_mode/bcast_relay.py +++ b/src/conf_mode/bcast_relay.py @@ -1,6 +1,6 @@  #!/usr/bin/env python3  # -# Copyright (C) 2017 VyOS maintainers and contributors +# Copyright (C) 2017-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,42 +13,34 @@  #  # 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 fnmatch -import jinja2 + +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  config_file = r'/etc/default/udp-broadcast-relay' -config_tmpl = """ -### Autogenerated by bcast_relay.py ### - -# UDP broadcast relay configuration for instance {{ id }} -{%- if description %} -# Comment: {{ description }} -{% endif %} -DAEMON_ARGS="{% if address %}-s {{ address }} {% endif %}{{ id }} {{ port }} {{ interfaces | join(' ') }}" - -""" -  default_config_data = {      'disabled': False,      'instances': []  }  def get_config(): -    relay = default_config_data +    relay = deepcopy(default_config_data)      conf = Config() -    if not conf.exists('service broadcast-relay'): +    base = ['service', 'broadcast-relay'] + +    if not conf.exists(base):          return None      else: -        conf.set_level('service broadcast-relay') +        conf.set_level(base)      # Service can be disabled by user      if conf.exists('disable'): @@ -58,7 +50,7 @@ def get_config():      # Parse configuration of each individual instance      if conf.exists('id'):          for id in conf.list_nodes('id'): -            conf.set_level('service broadcast-relay id {0}'.format(id)) +            conf.set_level(base + ['id', id])              config = {                  'id': id,                  'disabled': False, @@ -69,24 +61,24 @@ def get_config():              }              # Check if individual broadcast relay service is disabled -            if conf.exists('disable'): +            if conf.exists(['disable']):                  config['disabled'] = True              # Source IP of forwarded packets, if empty original senders address is used -            if conf.exists('address'): -                config['address'] = conf.return_value('address') +            if conf.exists(['address']): +                config['address'] = conf.return_value(['address'])              # A description for each individual broadcast relay service -            if conf.exists('description'): -                config['description'] = conf.return_value('description') +            if conf.exists(['description']): +                config['description'] = conf.return_value(['description'])              # UDP port to listen on for broadcast frames -            if conf.exists('port'): -                config['port'] = conf.return_value('port') +            if conf.exists(['port']): +                config['port'] = conf.return_value(['port'])              # Network interfaces to listen on for broadcast frames to be relayed -            if conf.exists('interface'): -                config['interfaces'] = conf.return_values('interface') +            if conf.exists(['interface']): +                config['interfaces'] = conf.return_values(['interface'])              relay['instances'].append(config) @@ -119,6 +111,11 @@ 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 = [] @@ -148,7 +145,7 @@ def generate(relay):          # configuration filename contains instance id          file = config_file + str(r['id']) -        tmpl = jinja2.Template(config_tmpl) +        tmpl = env.get_template('udp-broadcast-relay.tmpl')          config_text = tmpl.render(r)          with open(file, 'w') as f:              f.write(config_text) @@ -179,4 +176,4 @@ if __name__ == '__main__':          apply(c)      except ConfigError as e:          print(e) -        sys.exit(1) +        exit(1) diff --git a/src/conf_mode/dhcp_relay.py b/src/conf_mode/dhcp_relay.py index a1af2575f..6f8d66e7b 100755 --- a/src/conf_mode/dhcp_relay.py +++ b/src/conf_mode/dhcp_relay.py @@ -1,6 +1,6 @@  #!/usr/bin/env python3  # -# Copyright (C) 2018 VyOS maintainers and contributors +# Copyright (C) 2018-2020 VyOS maintainers and contributors  #  # This program is free software; you can redistribute it and/or modify  # it under the terms of the GNU General Public License version 2 or later as @@ -13,39 +13,18 @@  #  # You should have received a copy of the GNU General Public License  # along with this program.  If not, see <http://www.gnu.org/licenses/>. -# -# -import sys  import os -import jinja2 + +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  config_file = r'/etc/default/isc-dhcp-relay' -# Please be careful if you edit the template. -config_tmpl = """ -### Autogenerated by dhcp_relay.py ### - -# Defaults for isc-dhcp-relay initscript -# sourced by /etc/init.d/isc-dhcp-relay - -# -# This is a POSIX shell fragment -# - -# What servers should the DHCP relay forward requests to? -SERVERS="{{ server | join(' ') }}" - -# On what interfaces should the DHCP relay (dhrelay) serve DHCP requests? -INTERFACES="{{ interface | join(' ') }}" - -# Additional options that are passed to the DHCP relay daemon? -OPTIONS="-4 {{ options | join(' ') }}" -""" -  default_config_data = {      'interface': [],      'server': [], @@ -57,23 +36,23 @@ default_config_data = {  def get_config():      relay = default_config_data      conf = Config() -    if not conf.exists('service dhcp-relay'): +    if not conf.exists(['service', 'dhcp-relay']):          return None      else: -        conf.set_level('service dhcp-relay') +        conf.set_level(['service', 'dhcp-relay'])      # Network interfaces to listen on -    if conf.exists('interface'): -        relay['interface'] = conf.return_values('interface') +    if conf.exists(['interface']): +        relay['interface'] = conf.return_values(['interface'])      # Servers equal to the address of the DHCP server(s) -    if conf.exists('server'): -        relay['server'] = conf.return_values('server') +    if conf.exists(['server']): +        relay['server'] = conf.return_values(['server']) -    conf.set_level('service dhcp-relay relay-options') +    conf.set_level(['service', 'dhcp-relay', 'relay-options']) -    if conf.exists('hop-count'): -        count = '-c ' + conf.return_value('hop-count') +    if conf.exists(['hop-count']): +        count = '-c ' + conf.return_value(['hop-count'])          relay['options'].append(count)      # Specify the maximum packet size to send to a DHCPv4/BOOTP server. @@ -81,8 +60,8 @@ def get_config():      # options while still fitting into the Ethernet MTU size.      #      # Available in DHCPv4 mode only: -    if conf.exists('max-size'): -        size = '-A ' + conf.return_value('max-size') +    if conf.exists(['max-size']): +        size = '-A ' + conf.return_value(['max-size'])          relay['options'].append(size)      # Control the handling of incoming DHCPv4 packets which already contain @@ -94,8 +73,8 @@ def get_config():      # field; it may forward the packet unchanged; or, it may discard it.      #      # Available in DHCPv4 mode only: -    if conf.exists('relay-agents-packets'): -        pkt = '-a -m ' + conf.return_value('relay-agents-packets') +    if conf.exists(['relay-agents-packets']): +        pkt = '-a -m ' + conf.return_value(['relay-agents-packets'])          relay['options'].append(pkt)      return relay @@ -119,7 +98,12 @@ def generate(relay):      if relay is None:          return None -    tmpl = jinja2.Template(config_tmpl) +    # 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) @@ -144,4 +128,4 @@ if __name__ == '__main__':          apply(c)      except ConfigError as e:          print(e) -        sys.exit(1) +        exit(1) diff --git a/src/conf_mode/dhcp_server.py b/src/conf_mode/dhcp_server.py index 39cc72574..3d75414f5 100755 --- a/src/conf_mode/dhcp_server.py +++ b/src/conf_mode/dhcp_server.py @@ -1,6 +1,6 @@  #!/usr/bin/env python3  # -# Copyright (C) 2018-2019 VyOS maintainers and contributors +# Copyright (C) 2018-2020 VyOS maintainers and contributors  #  # This program is free software; you can redistribute it and/or modify  # it under the terms of the GNU General Public License version 2 or later as @@ -14,16 +14,17 @@  # You should have received a copy of the GNU General Public License  # along with this program.  If not, see <http://www.gnu.org/licenses/>. -import sys  import os -import jinja2 -import socket -import struct - -import vyos.validate  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  config_file = r'/etc/dhcp/dhcpd.conf' @@ -31,215 +32,6 @@ lease_file = r'/config/dhcpd.leases'  pid_file = r'/var/run/dhcpd.pid'  daemon_config_file = r'/etc/default/isc-dhcpv4-server' -# Please be careful if you edit the template. -config_tmpl = """ -### Autogenerated by dhcp_server.py ### - -# For options please consult the following website: -# https://www.isc.org/wp-content/uploads/2017/08/dhcp43options.html -# -# log-facility local7; - -{% if hostfile_update %} -on release { -    set ClientName = pick-first-value(host-decl-name, option fqdn.hostname, option host-name); -    set ClientIp = binary-to-ascii(10, 8, ".",leased-address); -    set ClientMac = binary-to-ascii(16, 8, ":",substring(hardware, 1, 6)); -    set ClientDomain = pick-first-value(config-option domain-name, "..YYZ!"); -    execute("/usr/libexec/vyos/system/on-dhcp-event.sh", "release", ClientName, ClientIp, ClientMac, ClientDomain); -} - -on expiry { -    set ClientName = pick-first-value(host-decl-name, option fqdn.hostname, option host-name); -    set ClientIp = binary-to-ascii(10, 8, ".",leased-address); -    set ClientMac = binary-to-ascii(16, 8, ":",substring(hardware, 1, 6)); -    set ClientDomain = pick-first-value(config-option domain-name, "..YYZ!"); -    execute("/usr/libexec/vyos/system/on-dhcp-event.sh", "release", ClientName, ClientIp, ClientMac, ClientDomain); -} -{% endif %} -{%- if host_decl_name %} -use-host-decl-names on; -{%- endif %} -ddns-update-style {% if ddns_enable -%} interim {%- else -%} none {%- endif %}; -{% if static_route -%} -option rfc3442-static-route code 121 = array of integer 8; -option windows-static-route code 249 = array of integer 8; -{%- endif %} -{% if wpad -%} -option wpad-url code 252 = text; -{% endif %} - -{%- if global_parameters %} -# The following {{ global_parameters | length }} line(s) were added as global-parameters in the CLI and have not been validated -{%- for param in global_parameters %} -{{ param }} -{%- endfor -%} -{%- endif %} - -# Failover configuration -{% for network in shared_network %} -{%- if not network.disabled -%} -{%- for subnet in network.subnet %} -{%- if subnet.failover_name -%} -failover peer "{{ subnet.failover_name }}" { -{%- if subnet.failover_status == 'primary' %} -    primary; -    mclt 1800; -    split 128; -{%- elif subnet.failover_status == 'secondary' %} -    secondary; -{%- endif %} -    address {{ subnet.failover_local_addr }}; -    port 520; -    peer address {{ subnet.failover_peer_addr }}; -    peer port 520; -    max-response-delay 30; -    max-unacked-updates 10; -    load balance max seconds 3; -} -{% endif -%} -{% endfor -%} -{% endif -%} -{% endfor %} - -# Shared network configration(s) -{% for network in shared_network %} -{%- if not network.disabled -%} -shared-network {{ network.name }} { -    {%- if network.authoritative %} -    authoritative; -    {%- endif %} -    {%- if network.network_parameters %} -    # The following {{ network.network_parameters | length }} line(s) were added as shared-network-parameters in the CLI and have not been validated -    {%- for param in network.network_parameters %} -    {{ param }} -    {%- endfor %} -    {%- endif %} -    {%- for subnet in network.subnet %} -    subnet {{ subnet.address }} netmask {{ subnet.netmask }} { -        {%- if subnet.dns_server %} -        option domain-name-servers {{ subnet.dns_server | join(', ') }}; -        {%- endif %} -        {%- if subnet.domain_search %} -        option domain-search {{ subnet.domain_search | join(', ') }}; -        {%- endif %} -        {%- if subnet.ntp_server %} -        option ntp-servers {{ subnet.ntp_server | join(', ') }}; -        {%- endif %} -        {%- if subnet.pop_server %} -        option pop-server {{ subnet.pop_server | join(', ') }}; -        {%- endif %} -        {%- if subnet.smtp_server %} -        option smtp-server {{ subnet.smtp_server | join(', ') }}; -        {%- endif %} -        {%- if subnet.time_server %} -        option time-servers {{ subnet.time_server | join(', ') }}; -        {%- endif %} -        {%- if subnet.wins_server %} -        option netbios-name-servers {{ subnet.wins_server | join(', ') }}; -        {%- endif %} -        {%- if subnet.static_route %} -        option rfc3442-static-route {{ subnet.static_route }}{% if subnet.rfc3442_default_router %}, {{ subnet.rfc3442_default_router }}{% endif %}; -        option windows-static-route {{ subnet.static_route }}; -        {%- endif %} -        {%- if subnet.ip_forwarding %} -        option ip-forwarding true; -        {%- endif -%} -        {%- if subnet.default_router %} -        option routers {{ subnet.default_router }}; -        {%- endif -%} -        {%- if subnet.server_identifier %} -        option dhcp-server-identifier {{ subnet.server_identifier }}; -        {%- endif -%} -        {%- if subnet.domain_name %} -        option domain-name "{{ subnet.domain_name }}"; -        {%- endif -%} -        {%- if subnet.subnet_parameters %} -        # The following {{ subnet.subnet_parameters | length }} line(s) were added as subnet-parameters in the CLI and have not been validated -        {%- for param in subnet.subnet_parameters %} -        {{ param }} -        {%- endfor -%} -        {%- endif %} -        {%- if subnet.tftp_server %} -        option tftp-server-name "{{ subnet.tftp_server }}"; -        {%- endif -%} -        {%- if subnet.bootfile_name %} -        option bootfile-name "{{ subnet.bootfile_name }}"; -        filename "{{ subnet.bootfile_name }}"; -        {%- endif -%} -        {%- if subnet.bootfile_server %} -        next-server {{ subnet.bootfile_server }}; -        {%- endif -%} -        {%- if subnet.time_offset %} -        option time-offset {{ subnet.time_offset }}; -        {%- endif -%} -        {%- if subnet.wpad_url %} -        option wpad-url "{{ subnet.wpad_url }}"; -        {%- endif -%} -        {%- if subnet.client_prefix_length %} -        option subnet-mask {{ subnet.client_prefix_length }}; -        {%- endif -%} -        {% if subnet.lease %} -        default-lease-time {{ subnet.lease }}; -        max-lease-time {{ subnet.lease }}; -        {%- endif -%} -        {%- for host in subnet.static_mapping %} -        {% if not host.disabled -%} -        host {% if host_decl_name -%} {{ host.name }} {%- else -%} {{ network.name }}_{{ host.name }} {%- endif %} { -            {%- if host.ip_address %} -            fixed-address {{ host.ip_address }}; -            {%- endif %} -            hardware ethernet {{ host.mac_address }}; -            {%- if host.static_parameters %} -            # The following {{ host.static_parameters | length }} line(s) were added as static-mapping-parameters in the CLI and have not been validated -            {%- for param in host.static_parameters %} -            {{ param }} -            {%- endfor -%} -            {%- endif %} -        } -        {%- endif %} -        {%- endfor %} -        {%- if subnet.failover_name %} -        pool { -            failover peer "{{ subnet.failover_name }}"; -            deny dynamic bootp clients; -            {%- for range in subnet.range %} -            range {{ range.start }} {{ range.stop }}; -            {%- endfor %} -        } -        {%- else %} -        {%- for range in subnet.range %} -        range {{ range.start }} {{ range.stop }}; -        {%- endfor %} -        {%- endif %} -    } -    {%- endfor %} -    on commit { -        set shared-networkname = "{{ network.name }}"; -        {% if hostfile_update -%} -        set ClientName = pick-first-value(host-decl-name, option fqdn.hostname, option host-name); -        set ClientIp = binary-to-ascii(10, 8, ".", leased-address); -        set ClientMac = binary-to-ascii(16, 8, ":", substring(hardware, 1, 6)); -        set ClientDomain = pick-first-value(config-option domain-name, "..YYZ!"); -        execute("/usr/libexec/vyos/system/on-dhcp-event.sh", "commit", ClientName, ClientIp, ClientMac, ClientDomain); -        {%- endif %} -    } -} -{%- endif %} -{% endfor %} -""" - -daemon_tmpl = """ -### Autogenerated by dhcp_server.py ### - -# sourced by /etc/init.d/isc-dhcpv4-server - -DHCPD_CONF={{ config_file }} -DHCPD_PID={{ pid_file }} -OPTIONS="-4 -lf {{ lease_file }}" -INTERFACES="" -""" -  default_config_data = {      'lease_file': lease_file,      'disabled': False, @@ -459,7 +251,7 @@ def get_config():                      if conf.exists('client-prefix-length'):                          # snippet borrowed from https://stackoverflow.com/questions/33750233/convert-cidr-to-subnet-mask-in-python                          host_bits = 32 - int(conf.return_value('client-prefix-length')) -                        subnet['client_prefix_length'] = socket.inet_ntoa(struct.pack('!I', (1 << 32) - (1 << host_bits))) +                        subnet['client_prefix_length'] = inet_ntoa(pack('!I', (1 << 32) - (1 << host_bits)))                      # Default router IP address on the client's subnet                      if conf.exists('default-router'): @@ -778,7 +570,7 @@ def verify(dhcp):              # There must be one subnet connected to a listen interface.              # This only counts if the network itself is not disabled!              if not network['disabled']: -                if vyos.validate.is_subnet_connected(subnet['network'], primary=True): +                if is_subnet_connected(subnet['network'], primary=True):                      listen_ok = True              # Subnets must be non overlapping @@ -810,9 +602,13 @@ def generate(dhcp):          print('Warning: DHCP server will be deactivated because it is disabled')          return None -    tmpl = jinja2.Template(config_tmpl) -    config_text = tmpl.render(dhcp) +    # 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) +    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(""",'"') @@ -820,7 +616,7 @@ def generate(dhcp):      with open(config_file, 'w') as f:          f.write(config_text) -    tmpl = jinja2.Template(daemon_tmpl) +    tmpl = env.get_template('daemon.tmpl')      config_text = tmpl.render(dhcp)      with open(daemon_config_file, 'w') as f:          f.write(config_text) @@ -852,4 +648,4 @@ if __name__ == '__main__':          apply(c)      except ConfigError as e:          print(e) -        sys.exit(1) +        exit(1) diff --git a/src/conf_mode/dhcpv6_relay.py b/src/conf_mode/dhcpv6_relay.py index ccabc901d..d942daf37 100755 --- a/src/conf_mode/dhcpv6_relay.py +++ b/src/conf_mode/dhcpv6_relay.py @@ -1,6 +1,6 @@  #!/usr/bin/env python3  # -# Copyright (C) 2018 VyOS maintainers and contributors +# Copyright (C) 2018-2020 VyOS maintainers and contributors  #  # This program is free software; you can redistribute it and/or modify  # it under the terms of the GNU General Public License version 2 or later as @@ -13,27 +13,19 @@  #  # You should have received a copy of the GNU General Public License  # along with this program.  If not, see <http://www.gnu.org/licenses/>. -# -# -import sys  import os -import jinja2 + +from 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  config_file = r'/etc/default/isc-dhcpv6-relay' -# Please be careful if you edit the template. -config_tmpl = """ -### Autogenerated by dhcpv6_relay.py ### - -# Defaults for isc-dhcpv6-relay initscript sourced by /etc/init.d/isc-dhcpv6-relay -OPTIONS="-6 -l {{ listen_addr | join(' -l ') }} -u {{ upstream_addr | join(' -u ') }} {{ options | join(' ') }}" - -""" -  default_config_data = {      'listen_addr': [],      'upstream_addr': [], @@ -41,7 +33,7 @@ default_config_data = {  }  def get_config(): -    relay = default_config_data +    relay = deepcopy(default_config_data)      conf = Config()      if not conf.exists('service dhcpv6-relay'):          return None @@ -92,7 +84,12 @@ def generate(relay):      if relay is None:          return None -    tmpl = jinja2.Template(config_tmpl) +    # 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) @@ -117,4 +114,4 @@ if __name__ == '__main__':          apply(c)      except ConfigError as e:          print(e) -        sys.exit(1) +        exit(1) diff --git a/src/conf_mode/dhcpv6_server.py b/src/conf_mode/dhcpv6_server.py index 44a927789..10b40baa4 100755 --- a/src/conf_mode/dhcpv6_server.py +++ b/src/conf_mode/dhcpv6_server.py @@ -1,6 +1,6 @@  #!/usr/bin/env python3  # -# Copyright (C) 2018-2019 VyOS maintainers and contributors +# Copyright (C) 2018-2020 VyOS maintainers and contributors  #  # This program is free software; you can redistribute it and/or modify  # it under the terms of the GNU General Public License version 2 or later as @@ -13,18 +13,17 @@  #  # 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 ipaddress -import jinja2 - -import vyos.validate +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_subnet_connected  from vyos import ConfigError  config_file = r'/etc/dhcp/dhcpdv6.conf' @@ -32,100 +31,6 @@ lease_file = r'/config/dhcpdv6.leases'  pid_file = r'/var/run/dhcpdv6.pid'  daemon_config_file = r'/etc/default/isc-dhcpv6-server' -# Please be careful if you edit the template. -config_tmpl = """ -### Autogenerated by dhcpv6_server.py ### - -# For options please consult the following website: -# https://www.isc.org/wp-content/uploads/2017/08/dhcp43options.html - -log-facility local7; -{%- if preference %} -option dhcp6.preference {{ preference }}; -{%- endif %} - -# Shared network configration(s) -{% for network in shared_network %} -{%- if not network.disabled -%} -shared-network {{ network.name }} { -    {%- for subnet in network.subnet %} -    subnet6 {{ subnet.network }} { -        {%- for range in subnet.range6_prefix %} -        range6 {{ range.prefix }}{{ " temporary" if range.temporary }}; -        {%- endfor %} -        {%- for range in subnet.range6 %} -        range6 {{ range.start }} {{ range.stop }}; -        {%- endfor %} -        {%- if subnet.domain_search %} -        option dhcp6.domain-search {{ subnet.domain_search | join(', ') }}; -        {%- endif %} -        {%- if subnet.lease_def %} -        default-lease-time {{ subnet.lease_def }}; -        {%- endif %} -        {%- if subnet.lease_max %} -        max-lease-time {{ subnet.lease_max }}; -        {%- endif %} -        {%- if subnet.lease_min %} -        min-lease-time {{ subnet.lease_min }}; -        {%- endif %} -        {%- if subnet.dns_server %} -        option dhcp6.name-servers {{ subnet.dns_server | join(', ') }}; -        {%- endif %} -        {%- if subnet.nis_domain %} -        option dhcp6.nis-domain-name "{{ subnet.nis_domain }}"; -        {%- endif %} -        {%- if subnet.nis_server %} -        option dhcp6.nis-servers {{ subnet.nis_server | join(', ') }}; -        {%- endif %} -        {%- if subnet.nisp_domain %} -        option dhcp6.nisp-domain-name "{{ subnet.nisp_domain }}"; -        {%- endif %} -        {%- if subnet.nisp_server %} -        option dhcp6.nisp-servers {{ subnet.nisp_server | join(', ') }}; -        {%- endif %} -        {%- if subnet.sip_address %} -        option dhcp6.sip-servers-addresses {{ subnet.sip_address | join(', ') }}; -        {%- endif %} -        {%- if subnet.sip_hostname %} -        option dhcp6.sip-servers-names {{ subnet.sip_hostname | join(', ') }}; -        {%- endif %} -        {%- if subnet.sntp_server %} -        option dhcp6.sntp-servers {{ subnet.sntp_server | join(', ') }}; -        {%- endif %} -        {%- for host in subnet.static_mapping %} -        {% if not host.disabled -%} -        host {{ network.name }}_{{ host.name }} { -            {%- if host.client_identifier %} -            host-identifier option dhcp6.client-id {{ host.client_identifier }}; -            {%- endif %} -            {%- if host.ipv6_address %} -            fixed-address6 {{ host.ipv6_address }}; -            {%- endif %} -        } -        {%- endif %} -        {%- endfor %} -    } -    {%- endfor %} -    on commit { -        set shared-networkname = "{{ network.name }}"; -    } -} -{%- endif %} -{% endfor %} - -""" - -daemon_tmpl = """ -### Autogenerated by dhcpv6_server.py ### - -# sourced by /etc/init.d/isc-dhcpv6-server - -DHCPD_CONF={{ config_file }} -DHCPD_PID={{ pid_file }} -OPTIONS="-6 -lf {{ lease_file }}" -INTERFACES="" -""" -  default_config_data = {      'lease_file': lease_file,      'preference': '', @@ -134,7 +39,7 @@ default_config_data = {  }  def get_config(): -    dhcpv6 = default_config_data +    dhcpv6 = deepcopy(default_config_data)      conf = Config()      if not conf.exists('service dhcpv6-server'):          return None @@ -409,7 +314,7 @@ def verify(dhcpv6):          # There must be one subnet connected to a listen interface if network is not disabled.          if not network['disabled']: -            if vyos.validate.is_subnet_connected(subnet['network']): +            if is_subnet_connected(subnet['network']):                  listen_ok = True          # DHCPv6 subnet must not overlap. ISC DHCP also complains about overlapping @@ -437,12 +342,17 @@ def generate(dhcpv6):          print('Warning: DHCPv6 server will be deactivated because it is disabled')          return None -    tmpl = jinja2.Template(config_tmpl) +    # 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 = jinja2.Template(daemon_tmpl) +    tmpl = env.get_template('daemon.tmpl')      config_text = tmpl.render(dhcpv6)      with open(daemon_config_file, 'w') as f:          f.write(config_text) @@ -474,4 +384,4 @@ if __name__ == '__main__':          apply(c)      except ConfigError as e:          print(e) -        sys.exit(1) +        exit(1) diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py index 38f3cb4de..bbb69cdf7 100755 --- a/src/conf_mode/dns_forwarding.py +++ b/src/conf_mode/dns_forwarding.py @@ -1,6 +1,6 @@  #!/usr/bin/env python3  # -# Copyright (C) 2018 VyOS maintainers and contributors +# Copyright (C) 2018-2020 VyOS maintainers and contributors  #  # This program is free software; you can redistribute it and/or modify  # it under the terms of the GNU General Public License version 2 or later as @@ -13,76 +13,26 @@  #  # 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 argparse -import jinja2 -import netifaces -import vyos.util -import vyos.hostsd_client +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 -  parser = argparse.ArgumentParser()  parser.add_argument("--dhclient", action="store_true",                      help="Started from dhclient-script")  config_file = r'/etc/powerdns/recursor.conf' -# XXX: pdns recursor doesn't like whitespace near entry separators, -# especially in the semicolon-separated lists of name servers. -# Please be careful if you edit the template. -config_tmpl = """ -### Autogenerated by dns_forwarding.py ### - -# Non-configurable defaults -daemon=yes -threads=1 -allow-from={{ allow_from | join(',') }} -log-common-errors=yes -non-local-bind=yes -query-local-address=0.0.0.0 -query-local-address6=:: - -# cache-size -max-cache-entries={{ cache_size }} - -# negative TTL for NXDOMAIN -max-negative-ttl={{ negative_ttl }} - -# ignore-hosts-file -export-etc-hosts={{ export_hosts_file }} - -# listen-on -local-address={{ listen_on | join(',') }} - -# dnssec -dnssec={{ dnssec }} - -# forward-zones / recursion -# -# statement is only inserted if either one forwarding domain or nameserver is configured -# if nothing is given at all, powerdns will act as a real recursor and resolve all requests by its own -# -{% if name_servers or domains %}forward-zones-recurse= -{%- for d in domains %} -{{ d.name }}={{ d.servers | join(";") }} -{{- ", " if not loop.last -}} -{%- endfor -%} -{%- if name_servers -%} -{%- if domains -%}, {% endif -%}.={{ name_servers | join(';') }} -{% endif %} -{% endif %} - -""" -  default_config_data = {      'allow_from': [],      'cache_size': 10000, @@ -96,74 +46,74 @@ default_config_data = {  def get_config(arguments): -    dns = default_config_data +    dns = deepcopy(default_config_data)      conf = Config() +    base = ['service', 'dns', 'forwarding']      if arguments.dhclient:          conf.exists = conf.exists_effective          conf.return_value = conf.return_effective_value          conf.return_values = conf.return_effective_values -    if not conf.exists('service dns forwarding'): +    if not conf.exists(base):          return None -    conf.set_level('service dns forwarding') +    conf.set_level(base) -    if conf.exists('allow-from'): -        dns['allow_from'] = conf.return_values('allow-from') +    if conf.exists(['allow-from']): +        dns['allow_from'] = conf.return_values(['allow-from']) -    if conf.exists('cache-size'): -        cache_size = conf.return_value('cache-size') +    if conf.exists(['cache-size']): +        cache_size = conf.return_value(['cache-size'])          dns['cache_size'] = cache_size      if conf.exists('negative-ttl'): -        negative_ttl = conf.return_value('negative-ttl') +        negative_ttl = conf.return_value(['negative-ttl'])          dns['negative_ttl'] = negative_ttl -    if conf.exists('domain'): -        for node in conf.list_nodes('domain'): -            servers = conf.return_values("domain {0} server".format(node)) +    if conf.exists(['domain']): +        for node in conf.list_nodes(['domain']): +            servers = conf.return_values(['domain', node, 'server'])              domain = {                  "name": node,                  "servers": bracketize_ipv6_addrs(servers)              }              dns['domains'].append(domain) -    if conf.exists('ignore-hosts-file'): +    if conf.exists(['ignore-hosts-file']):          dns['export_hosts_file'] = "no" -    if conf.exists('name-server'): -        name_servers = conf.return_values('name-server') +    if conf.exists(['name-server']): +        name_servers = conf.return_values(['name-server'])          dns['name_servers'] = dns['name_servers'] + name_servers -    if conf.exists('system'): -        conf.set_level('system') +    if conf.exists(['system']): +        conf.set_level(['system'])          system_name_servers = [] -        system_name_servers = conf.return_values('name-server') +        system_name_servers = conf.return_values(['name-server'])          if not system_name_servers: -            print( -                "DNS forwarding warning: No name-servers set under 'system name-server'\n") +            print("DNS forwarding warning: No name-servers set under 'system name-server'\n")          else:              dns['name_servers'] = dns['name_servers'] + system_name_servers -        conf.set_level('service dns forwarding') +        conf.set_level(base)      dns['name_servers'] = bracketize_ipv6_addrs(dns['name_servers']) -    if conf.exists('listen-address'): -        dns['listen_on'] = conf.return_values('listen-address') +    if conf.exists(['listen-address']): +        dns['listen_on'] = conf.return_values(['listen-address']) -    if conf.exists('dnssec'): -        dns['dnssec'] = conf.return_value('dnssec') +    if conf.exists(['dnssec']): +        dns['dnssec'] = conf.return_value(['dnssec'])      # Add name servers received from DHCP -    if conf.exists('dhcp'): +    if conf.exists(['dhcp']):          interfaces = [] -        interfaces = conf.return_values('dhcp') -        hc = vyos.hostsd_client.Client() +        interfaces = conf.return_values(['dhcp']) +        hc = hostsd_client()          for interface in interfaces: -            dhcp_resolvers = hc.get_name_servers("dhcp-{0}".format(interface)) -            dhcpv6_resolvers = hc.get_name_servers("dhcpv6-{0}".format(interface)) +            dhcp_resolvers = hc.get_name_servers(f'dhcp-{interface}') +            dhcpv6_resolvers = hc.get_name_servers(f'dhcpv6-{interface}')              if dhcp_resolvers:                  dns['name_servers'] = dns['name_servers'] + dhcp_resolvers @@ -202,7 +152,12 @@ def generate(dns):      if dns is None:          return None -    tmpl = jinja2.Template(config_tmpl, trim_blocks=True) +    # 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) + +    tmpl = env.get_template('recursor.conf.tmpl')      config_text = tmpl.render(dns)      with open(config_file, 'w') as f:          f.write(config_text) @@ -223,7 +178,7 @@ if __name__ == '__main__':      if args.dhclient:          # There's a big chance it was triggered by a commit still in progress          # so we need to wait until the new values are in the running config -        vyos.util.wait_for_commit_lock() +        wait_for_commit_lock()      try:          c = get_config(args) @@ -232,4 +187,4 @@ if __name__ == '__main__':          apply(c)      except ConfigError as e:          print(e) -        sys.exit(1) +        exit(1) diff --git a/src/conf_mode/dynamic_dns.py b/src/conf_mode/dynamic_dns.py index faf0663ab..56ce4fedc 100755 --- a/src/conf_mode/dynamic_dns.py +++ b/src/conf_mode/dynamic_dns.py @@ -1,6 +1,6 @@  #!/usr/bin/env python3  # -# Copyright (C) 2018-2019 VyOS maintainers and contributors +# Copyright (C) 2018-2020 VyOS maintainers and contributors  #  # This program is free software; you can redistribute it and/or modify  # it under the terms of the GNU General Public License version 2 or later as @@ -15,68 +15,20 @@  # along with this program.  If not, see <http://www.gnu.org/licenses/>.  import os -import sys -import jinja2 +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  config_file = r'/etc/ddclient/ddclient.conf'  cache_file = r'/var/cache/ddclient/ddclient.cache'  pid_file = r'/var/run/ddclient/ddclient.pid' -config_tmpl = """ -### Autogenerated by dynamic_dns.py ### -daemon=1m -syslog=yes -ssl=yes -pid={{ pid_file }} -cache={{ cache_file }} - -{% for interface in interfaces -%} - -# -# ddclient configuration for interface "{{ interface.interface }}": -# -{% if interface.web_url -%} -use=web, web='{{ interface.web_url}}' {%- if interface.web_skip %}, web-skip='{{ interface.web_skip }}'{% endif %} -{% else -%} -use=if, if={{ interface.interface }} -{% endif -%} - -{% for rfc in interface.rfc2136 -%} -{% for record in rfc.record %} -# RFC2136 dynamic DNS configuration for {{ record }}.{{ rfc.zone }} -server={{ rfc.server }} -protocol=nsupdate -password={{ rfc.keyfile }} -ttl={{ rfc.ttl }} -zone={{ rfc.zone }} -{{ record }} -{% endfor -%} -{% endfor -%} - -{% for srv in interface.service %} -{% for host in srv.host %} -# DynDNS provider configuration for {{ host }} -protocol={{ srv.protocol }}, -max-interval=28d, -login={{ srv.login }}, -password='{{ srv.password }}', -{% if srv.server -%} -server={{ srv.server }}, -{% endif -%} -{% if srv.zone -%} -zone={{ srv.zone }}, -{% endif -%} -{{ host }} -{% endfor %} -{% endfor %} - -{% endfor %} -""" -  # Mapping of service name to service protocol  default_service_protocol = {      'afraid': 'freedns', @@ -100,7 +52,7 @@ default_config_data = {  }  def get_config(): -    dyndns = default_config_data +    dyndns = deepcopy(default_config_data)      conf = Config()      base_level = ['service', 'dns', 'dynamic'] @@ -200,17 +152,17 @@ def get_config():          # Set config back to appropriate level for these options          conf.set_level(base_level + ['interface', interface]) -         +          # Additional settings in CLI          if conf.exists(['use-web', 'skip']):              node['web_skip'] = conf.return_value(['use-web', 'skip'])          if conf.exists(['use-web', 'url']):              node['web_url'] = conf.return_value(['use-web', 'url']) -         +          # set config level back to top level          conf.set_level(base_level) -         +          dyndns['interfaces'].append(node)      return dyndns @@ -272,6 +224,11 @@ def generate(dyndns):          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) @@ -280,7 +237,7 @@ def generate(dyndns):      if not os.path.exists(dirname):          os.mkdir(dirname) -    tmpl = jinja2.Template(config_tmpl) +    tmpl = env.get_template('ddclient.conf.tmpl')      config_text = tmpl.render(dyndns)      with open(config_file, 'w') as f:          f.write(config_text) @@ -314,4 +271,4 @@ if __name__ == '__main__':          apply(c)      except ConfigError as e:          print(e) -        sys.exit(1) +        exit(1) diff --git a/src/conf_mode/flow_accounting_conf.py b/src/conf_mode/flow_accounting_conf.py index 2e941de0a..b040c8b64 100755 --- a/src/conf_mode/flow_accounting_conf.py +++ b/src/conf_mode/flow_accounting_conf.py @@ -1,6 +1,6 @@  #!/usr/bin/env python3  # -# Copyright (C) 2018 VyOS maintainers and contributors +# Copyright (C) 2018-2020 VyOS maintainers and contributors  #  # This program is free software; you can redistribute it and/or modify  # it under the terms of the GNU General Public License version 2 or later as @@ -13,17 +13,19 @@  #  # 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 ipaddress  import subprocess +from ipaddress import ip_address +from jinja2 import FileSystemLoader, Environment +from sys import exit + +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.ifconfig import Interface -from jinja2 import Template  # default values  default_sflow_server_port = 6343 @@ -36,78 +38,6 @@ uacctd_conf_path = '/etc/pmacct/uacctd.conf'  iptables_nflog_table = 'raw'  iptables_nflog_chain = 'VYATTA_CT_PREROUTING_HOOK' -# pmacct config template -uacct_conf_jinja = '''# Genereated from VyOS configuration -daemonize: true -promisc: false -pidfile: /var/run/uacctd.pid -uacctd_group: 2 -uacctd_nl_size: 2097152 -snaplen: {{ snaplen }} -aggregate: in_iface,src_mac,dst_mac,vlan,src_host,dst_host,src_port,dst_port,proto,tos,flows -plugin_pipe_size: {{ templatecfg['plugin_pipe_size'] }} -plugin_buffer_size: {{ templatecfg['plugin_buffer_size'] }} -{%- if templatecfg['syslog-facility'] != none %} -syslog: {{ templatecfg['syslog-facility'] }} -{%- endif %} -{%- if templatecfg['disable-imt'] == none %} -imt_path: /tmp/uacctd.pipe -imt_mem_pools_number: 169 -{%- endif %} -plugins: -{%- if templatecfg['netflow']['servers'] != none -%} -    {% for server in templatecfg['netflow']['servers'] %} -        {%- if loop.last -%}nfprobe[nf_{{ server['address'] }}]{%- else %}nfprobe[nf_{{ server['address'] }}],{%- endif %} -    {%- endfor -%} -    {% set plugins_presented = true %} -{%- endif %} -{%- if templatecfg['sflow']['servers'] != none -%} -    {% if plugins_presented -%} -        {%- for server in templatecfg['sflow']['servers'] -%} -            ,sfprobe[sf_{{ server['address'] }}] -        {%- endfor %} -    {%- else %} -        {%- for server in templatecfg['sflow']['servers'] %} -            {%- if loop.last -%}sfprobe[sf_{{ server['address'] }}]{%- else %}sfprobe[sf_{{ server['address'] }}],{%- endif %} -        {%- endfor %} -    {%- endif -%} -    {% set plugins_presented = true %} -{%- endif %} -{%- if templatecfg['disable-imt'] == none %} -    {%- if plugins_presented -%},memory{%- else %}memory{%- endif %} -{%- endif %} -{%- if templatecfg['netflow']['servers'] != none %} -{%- for server in templatecfg['netflow']['servers'] %} -nfprobe_receiver[nf_{{ server['address'] }}]: {{ server['address'] }}:{{ server['port'] }} -nfprobe_version[nf_{{ server['address'] }}]: {{ templatecfg['netflow']['version'] }} -{%- if templatecfg['netflow']['engine-id'] != none %} -nfprobe_engine[nf_{{ server['address'] }}]: {{ templatecfg['netflow']['engine-id'] }} -{%- endif %} -{%- if templatecfg['netflow']['max-flows'] != none %} -nfprobe_maxflows[nf_{{ server['address'] }}]: {{ templatecfg['netflow']['max-flows'] }} -{%- endif %} -{%- if templatecfg['netflow']['sampling-rate'] != none %} -sampling_rate[nf_{{ server['address'] }}]: {{ templatecfg['netflow']['sampling-rate'] }} -{%- endif %} -{%- if templatecfg['netflow']['source-ip'] != none %} -nfprobe_source_ip[nf_{{ server['address'] }}]: {{ templatecfg['netflow']['source-ip'] }} -{%- endif %} -{%- if templatecfg['netflow']['timeout_string'] != '' %} -nfprobe_timeouts[nf_{{ server['address'] }}]: {{ templatecfg['netflow']['timeout_string'] }} -{%- endif %} -{%- endfor %} -{%- endif %} -{%- if templatecfg['sflow']['servers'] != none %} -{%- for server in templatecfg['sflow']['servers'] %} -sfprobe_receiver[sf_{{ server['address'] }}]: {{ server['address'] }}:{{ server['port'] }} -sfprobe_agentip[sf_{{ server['address'] }}]: {{ templatecfg['sflow']['agent-address'] }} -{%- if templatecfg['sflow']['sampling-rate'] != none %} -sampling_rate[sf_{{ server['address'] }}]: {{ templatecfg['sflow']['sampling-rate'] }} -{%- endif %} -{%- endfor %} -{% endif %} -''' -  # helper functions  # check if node exists and return True if this is true  def _node_exists(path): @@ -154,7 +84,7 @@ def _iptables_get_nflog():          stdout, stderr = process.communicate()          if not process.returncode == 0:              print("Failed to get flows list: command \"{}\" returned exit code: {}\nError: {}".format(command, process.returncode, stderr)) -            sys.exit(1) +            exit(1)          iptables_out = stdout.splitlines()          # parse each line and add information to list @@ -179,7 +109,7 @@ def _iptables_config(configured_ifaces):      # get currently configured interfaces with iptables rules      active_nflog_rules = _iptables_get_nflog() -     +      # compare current active list with configured one and delete excessive interfaces, add missed      active_nflog_ifaces = []      for rule in active_nflog_rules: @@ -314,15 +244,15 @@ def verify(config):          sflow_collector_ipver = None          for sflow_collector in config['sflow']['servers']:              if sflow_collector_ipver: -                if sflow_collector_ipver != ipaddress.ip_address(sflow_collector['address']).version: +                if sflow_collector_ipver != ip_address(sflow_collector['address']).version:                      raise ConfigError("All sFlow servers must use the same IP protocol")              else: -                sflow_collector_ipver = ipaddress.ip_address(sflow_collector['address']).version +                sflow_collector_ipver = ip_address(sflow_collector['address']).version          # check agent-id for sFlow: we should avoid mixing IPv4 agent-id with IPv6 collectors and vice-versa          for sflow_collector in config['sflow']['servers']: -            if ipaddress.ip_address(sflow_collector['address']).version != ipaddress.ip_address(config['sflow']['agent-address']).version: +            if ip_address(sflow_collector['address']).version != ip_address(config['sflow']['agent-address']).version:                  raise ConfigError("Different IP address versions cannot be mixed in \"sflow agent-address\" and \"sflow server\". You need to set manually the same IP version for \"agent-address\" as for all sFlow servers")          # check if configured sFlow agent-id exist in the system @@ -378,7 +308,7 @@ def generate(config):      # skip all checks if flow-accounting was removed      if not config['flow-accounting-configured']:          return True -     +      # Calculate all necessary values      if config['buffer-size']:          # circular queue size @@ -399,13 +329,16 @@ def generate(config):                  timeout_string = "{}:{}={}".format(timeout_string, timeout_type, timeout_value)      config['netflow']['timeout_string'] = timeout_string -    # Generate daemon configs -    uacct_conf_template = Template(uacct_conf_jinja) -    uacct_conf_file_data = uacct_conf_template.render(templatecfg = config, snaplen = default_captured_packet_size) +    # 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) -    # save generated config to uacctd.conf +    # 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(uacct_conf_file_data) +        file.write(config_text)  def apply(config): @@ -436,4 +369,4 @@ if __name__ == '__main__':          apply(config)      except ConfigError as e:          print(e) -        sys.exit(1) +        exit(1) diff --git a/src/conf_mode/https.py b/src/conf_mode/https.py index 889b62cf4..83a5f3602 100755 --- a/src/conf_mode/https.py +++ b/src/conf_mode/https.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,88 +13,22 @@  #  # You should have received a copy of the GNU General Public License  # along with this program.  If not, see <http://www.gnu.org/licenses/>. -# -# -import sys  import os -from copy import deepcopy -import jinja2 +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  config_file = '/etc/nginx/sites-available/default' -# Please be careful if you edit the template. -config_tmpl = """ - -### Autogenerated by https.py ### -# Default server configuration -# -server { -        listen 80 default_server; -        listen [::]:80 default_server; -        server_name _; -        return 301 https://$server_name$request_uri; -} - -{% for server in server_block_list %} -server { - -        # SSL configuration -        # -{% if server.address == '*' %} -        listen {{ server.port }} ssl; -        listen [::]:{{ server.port }} ssl; -{% else %} -        listen {{ server.address }}:{{ server.port }} ssl; -{% endif %} - -{% for name in server.name %} -        server_name {{ name }}; -{% endfor %} - -{% if server.certbot %} -        ssl_certificate /etc/letsencrypt/live/{{ server.certbot_dir }}/fullchain.pem; -        ssl_certificate_key /etc/letsencrypt/live/{{ server.certbot_dir }}/privkey.pem; -        include /etc/letsencrypt/options-ssl-nginx.conf; -        ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; -{% elif server.vyos_cert %} -        include {{ server.vyos_cert.conf }}; -{% else %} -        # -        # Self signed certs generated by the ssl-cert package -        # Don't use them in a production server! -        # -        include snippets/snakeoil.conf; -{% endif %} - -        # proxy settings for HTTP API, if enabled; 503, if not -        location ~ /(retrieve|configure|config-file|image|generate|show) { -{% if server.api %} -                proxy_pass http://localhost:{{ server.api.port }}; -                proxy_buffering off; -{% else %} -                return 503; -{% endif %} -        } - -        error_page 501 502 503 =200 @50*_json; - -        location @50*_json { -                default_type application/json; -                return 200 '{"error": "Start service in configuration mode: set service https api"}'; -        } - -} - -{% endfor %} -""" -  default_server_block = {      'id'        : '',      'address'   : '*', @@ -193,10 +127,15 @@ 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 = jinja2.Template(config_tmpl, trim_blocks=True) +    tmpl = env.get_template('nginx.default.tmpl')      config_text = tmpl.render(https)      with open(config_file, 'w') as f:          f.write(config_text) @@ -217,4 +156,4 @@ if __name__ == '__main__':          apply(c)      except ConfigError as e:          print(e) -        sys.exit(1) +        exit(1) diff --git a/src/conf_mode/igmp_proxy.py b/src/conf_mode/igmp_proxy.py index cd0704124..aa46f2c4e 100755 --- a/src/conf_mode/igmp_proxy.py +++ b/src/conf_mode/igmp_proxy.py @@ -1,6 +1,6 @@  #!/usr/bin/env python3  # -# Copyright (C) 2018 VyOS maintainers and contributors +# Copyright (C) 2018-2020 VyOS maintainers and contributors  #  # This program is free software; you can redistribute it and/or modify  # it under the terms of the GNU General Public License version 2 or later as @@ -13,60 +13,20 @@  #  # You should have received a copy of the GNU General Public License  # along with this program.  If not, see <http://www.gnu.org/licenses/>. -# -# -import sys  import os -import jinja2 + +from 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  config_file = r'/etc/igmpproxy.conf' -# Please be careful if you edit the template. -config_tmpl = """ -######################################################## -# -# autogenerated by igmp_proxy.py -# -#   The configuration file must define one upstream -#   interface, and one or more downstream interfaces. -# -#   If multicast traffic originates outside the -#   upstream subnet, the "altnet" option can be -#   used in order to define legal multicast sources. -#   (Se example...) -# -#   The "quickleave" should be used to avoid saturation -#   of the upstream link. The option should only -#   be used if it's absolutely nessecary to -#   accurately imitate just one Client. -# -######################################################## - -{% if not disable_quickleave -%} -quickleave -{% endif -%} - -{% for interface in interfaces %} -# Configuration for {{ interface.name }} ({{ interface.role }} interface) -{% if interface.role == 'disabled' -%} -phyint {{ interface.name }} disabled -{%- else -%} -phyint {{ interface.name }} {{ interface.role }} ratelimit 0 threshold {{ interface.threshold }} -{%- endif -%} -{%- for subnet in interface.alt_subnet %} -        altnet {{ subnet }} -{%- endfor %} -{%- for subnet in interface.whitelist %} -        whitelist {{ subnet }} -{%- endfor %} -{% endfor %} -""" -  default_config_data = {      'disable': False,      'disable_quickleave': False, @@ -74,23 +34,24 @@ default_config_data = {  }  def get_config(): -    igmp_proxy = default_config_data +    igmp_proxy = deepcopy(default_config_data)      conf = Config() -    if not conf.exists('protocols igmp-proxy'): +    base = ['protocols', 'igmp-proxy'] +    if not conf.exists(base):          return None      else: -        conf.set_level('protocols igmp-proxy') +        conf.set_level(base)      # Network interfaces to listen on -    if conf.exists('disable'): +    if conf.exists(['disable']):          igmp_proxy['disable'] = True      # Option to disable "quickleave" -    if conf.exists('disable-quickleave'): +    if conf.exists(['disable-quickleave']):          igmp_proxy['disable_quickleave'] = True -    for intf in conf.list_nodes('interface'): -        conf.set_level('protocols igmp-proxy interface {0}'.format(intf)) +    for intf in conf.list_nodes(['interface']): +        conf.set_level(base + ['interface', intf])          interface = {              'name': intf,              'alt_subnet': [], @@ -99,17 +60,17 @@ def get_config():              'whitelist': []          } -        if conf.exists('alt-subnet'): -            interface['alt_subnet'] = conf.return_values('alt-subnet') +        if conf.exists(['alt-subnet']): +            interface['alt_subnet'] = conf.return_values(['alt-subnet']) -        if conf.exists('role'): -            interface['role'] = conf.return_value('role') +        if conf.exists(['role']): +            interface['role'] = conf.return_value(['role']) -        if conf.exists('threshold'): -            interface['threshold'] = conf.return_value('threshold') +        if conf.exists(['threshold']): +            interface['threshold'] = conf.return_value(['threshold']) -        if conf.exists('whitelist'): -            interface['whitelist'] = conf.return_values('whitelist') +        if conf.exists(['whitelist']): +            interface['whitelist'] = conf.return_values(['whitelist'])          # Append interface configuration to global configuration list          igmp_proxy['interfaces'].append(interface) @@ -153,7 +114,12 @@ def generate(igmp_proxy):          print('Warning: IGMP Proxy will be deactivated because it is disabled')          return None -    tmpl = jinja2.Template(config_tmpl) +    # 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) @@ -167,7 +133,7 @@ def apply(igmp_proxy):           if os.path.exists(config_file):               os.unlink(config_file)      else: -        os.system('sudo systemctl restart igmpproxy.service') +        os.system('systemctl restart igmpproxy.service')      return None @@ -179,4 +145,4 @@ if __name__ == '__main__':          apply(c)      except ConfigError as e:          print(e) -        sys.exit(1) +        exit(1) diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py index fb2d6e6d9..faaee9ac0 100755 --- a/src/conf_mode/interfaces-openvpn.py +++ b/src/conf_mode/interfaces-openvpn.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 @@ -17,7 +17,7 @@  import os  import re -from jinja2 import Template +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 @@ -29,257 +29,16 @@ from subprocess import Popen, PIPE  from time import sleep  from shutil import rmtree -from vyos import ConfigError  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  from vyos.validate import is_addr_assigned +from vyos import ConfigError  user = 'openvpn'  group = 'openvpn' -# Please be careful if you edit the template. -config_tmpl = """ -### Autogenerated by interfaces-openvpn.py ### -# -# See https://community.openvpn.net/openvpn/wiki/Openvpn24ManPage -# for individual keyword definition - -{% if description %} -# {{ description }} -{% endif %} - -verb 3 -status /opt/vyatta/etc/openvpn/status/{{ intf }}.status 30 -writepid /var/run/openvpn/{{ intf }}.pid - -dev-type {{ type }} -dev {{ intf }} -user {{ uid }} -group {{ gid }} -persist-key -iproute /usr/libexec/vyos/system/unpriv-ip - -proto {% if 'tcp-active' in protocol -%}tcp-client{% elif 'tcp-passive' in protocol -%}tcp-server{% else %}udp{% endif %} - -{%- if local_host %} -local {{ local_host }} -{% endif %} - -{%- if local_port %} -lport {{ local_port }} -{% endif %} - -{%- if remote_port %} -rport {{ remote_port }} -{% endif %} - -{%- if remote_host %} -{% for remote in remote_host -%} -remote {{ remote }} -{% endfor -%} -{% endif %} - -{%- if shared_secret_file %} -secret {{ shared_secret_file }} -{% endif %} - -{%- if persistent_tunnel %} -persist-tun -{% endif %} - -{%- if mode %} -{%- if 'client' in mode %} -# -# OpenVPN Client mode -# -client -nobind -{%- elif 'server' in mode %} -# -# OpenVPN Server mode -# -mode server -tls-server -keepalive {{ ping_interval }} {{ ping_restart }} -management /tmp/openvpn-mgmt-intf unix - -{%- if server_topology %} -topology {% if 'point-to-point' in server_topology %}p2p{% else %}subnet{% endif %} -{% endif %} - -{% for ns in server_dns_nameserver -%} -push "dhcp-option DNS {{ ns }}" -{% endfor -%} - -{% for route in server_push_route -%} -push "route {{ route }}" -{% endfor -%} - -{%- if server_domain %} -push "dhcp-option DOMAIN {{ server_domain }}" -{% endif %} - -{%- if server_max_conn %} -max-clients {{ server_max_conn }} -{% endif %} - -{%- if bridge_member %} -server-bridge nogw -{%- else %} -server {{ server_subnet }} -{% endif %} - -{%- if server_reject_unconfigured %} -ccd-exclusive -{% endif %} - -{%- else %} -# -# OpenVPN site-2-site mode -# -ping {{ ping_interval }} -ping-restart {{ ping_restart }} - -{%- if local_address_subnet %} -ifconfig {{ local_address }} {{ local_address_subnet }} -{% elif remote_address %} -ifconfig {{ local_address }} {{ remote_address }} -{% endif %} - -{% endif %} -{% endif %} - -{%- if tls_ca_cert %} -ca {{ tls_ca_cert }} -{% endif %} - -{%- if tls_cert %} -cert {{ tls_cert }} -{% endif %} - -{%- if tls_key %} -key {{ tls_key }} -{% endif %} - -{%- if tls_crypt %} -tls-crypt {{ tls_crypt }} -{% endif %} - -{%- if tls_crl %} -crl-verify {{ tls_crl }} -{% endif %} - -{%- if tls_version_min %} -tls-version-min {{tls_version_min}} -{% endif %} - -{%- if tls_dh %} -dh {{ tls_dh }} -{% endif %} - -{%- if tls_auth %} -tls-auth {{tls_auth}} -{% endif %} - -{%- if 'active' in tls_role %} -tls-client -{%- elif 'passive' in tls_role %} -tls-server -{% endif %} - -{%- if redirect_gateway %} -push "redirect-gateway {{ redirect_gateway }}" -{% endif %} - -{%- if compress_lzo %} -compress lzo -{% endif %} - -{%- if hash %} -auth {{ hash }} -{% endif %} - -{%- if encryption %} -{%- if 'des' in encryption %} -cipher des-cbc -{%- elif '3des' in encryption %} -cipher des-ede3-cbc -{%- elif 'bf128' in encryption %} -cipher bf-cbc -keysize 128 -{%- elif 'bf256' in encryption %} -cipher bf-cbc -keysize 25 -{%- elif 'aes128gcm' in encryption %} -cipher aes-128-gcm -{%- elif 'aes128' in encryption %} -cipher aes-128-cbc -{%- elif 'aes192gcm' in encryption %} -cipher aes-192-gcm -{%- elif 'aes192' in encryption %} -cipher aes-192-cbc -{%- elif 'aes256gcm' in encryption %} -cipher aes-256-gcm -{%- elif 'aes256' in encryption %} -cipher aes-256-cbc -{% endif %} -{% endif %} - -{%- if ncp_ciphers %} -ncp-ciphers {{ncp_ciphers}} -{% endif %} -{%- if disable_ncp %} -ncp-disable -{% endif %} - -{%- if auth %} -auth-user-pass /tmp/openvpn-{{ intf }}-pw -auth-retry nointeract -{% endif %} - -{%- if client %} -client-config-dir /opt/vyatta/etc/openvpn/ccd/{{ intf }} -{% endif %} - -# DEPRECATED This option will be removed in OpenVPN 2.5 -# Until OpenVPN v2.3 the format of the X.509 Subject fields was formatted like this: -# /C=US/L=Somewhere/CN=John Doe/emailAddress=john@example.com In addition the old -# behaviour was to remap any character other than alphanumeric, underscore ('_'), -# dash ('-'), dot ('.'), and slash ('/') to underscore ('_'). The X.509 Subject -# string as returned by the tls_id environmental variable, could additionally -# contain colon (':') or equal ('='). When using the --compat-names option, this -# old formatting and remapping will be re-enabled again. This is purely implemented -# for compatibility reasons when using older plug-ins or scripts which does not -# handle the new formatting or UTF-8 characters. -# -# See https://phabricator.vyos.net/T1512 -compat-names - -{% for option in options -%} -{{ option }} -{% endfor -%} -""" - -client_tmpl = """ -### Autogenerated by interfaces-openvpn.py ### - -{% if ip -%} -ifconfig-push {{ ip }} {{ remote_netmask }} -{% endif -%} -{% for route in push_route -%} -push "route {{ route }}" -{% endfor -%} - -{% for net in subnet -%} -iroute {{ net }} -{% endfor -%} - -{% if disable -%} -disable -{% endif -%} -""" -  default_config_data = {      'address': [],      'auth_user': '', @@ -308,7 +67,7 @@ default_config_data = {      'ncp_ciphers': '',      'options': [],      'persistent_tunnel': False, -    'protocol': '', +    'protocol': 'udp',      'redirect_gateway': '',      'remote_address': '',      'remote_host': [], @@ -512,8 +271,7 @@ def get_config():      # OpenVPN operation mode      if conf.exists('mode'): -        mode = conf.return_value('mode') -        openvpn['mode'] = mode +        openvpn['mode'] = conf.return_value('mode')      # Additional OpenVPN options      if conf.exists('openvpn-option'): @@ -917,6 +675,11 @@ 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)) @@ -957,19 +720,17 @@ def generate(openvpn):      # Generate client specific configuration      for client in openvpn['client']:          client_file = directory + '/ccd/' + interface + '/' + client['name'] -        tmpl = Template(client_tmpl) +        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 = Template(config_tmpl) +    tmpl = env.get_template('server.conf.tmpl')      config_text = tmpl.render(openvpn) -      # we need to support quoting of raw parameters from OpenVPN CLI      # see https://phabricator.vyos.net/T1632      config_text = config_text.replace(""",'"') -      with open(get_config_name(interface), 'w') as f:          f.write(config_text)      os.chown(get_config_name(interface), uid, gid) diff --git a/src/conf_mode/interfaces-pppoe.py b/src/conf_mode/interfaces-pppoe.py index 9c045534c..a396af4ea 100755 --- a/src/conf_mode/interfaces-pppoe.py +++ b/src/conf_mode/interfaces-pppoe.py @@ -18,153 +18,14 @@ import os  from sys import exit  from copy import deepcopy -from jinja2 import Template -from subprocess import Popen, PIPE +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_file, chmod_x_file +from vyos.util import chown_file, chmod_x, subprocess_cmd  from vyos import ConfigError -from netifaces import interfaces - -# Please be careful if you edit the template. -config_pppoe_tmpl = """### Autogenerated by interfaces-pppoe.py ### -{% if description %} -# {{ description }} -{% endif %} - -# Require peer to provide the local IP address if it is not -# specified explicitly in the config file. -noipdefault - -# Don't show the password in logfiles: -hide-password - -# Standard Link Control Protocol (LCP) parameters: -lcp-echo-interval 20 -lcp-echo-failure 3 - -# RFC 2516, paragraph 7 mandates that the following options MUST NOT be -# requested and MUST be rejected if requested by the peer: -# Address-and-Control-Field-Compression (ACFC) -noaccomp - -# Asynchronous-Control-Character-Map (ACCM) -default-asyncmap - -# Override any connect script that may have been set in /etc/ppp/options. -connect /bin/true - -# Don't try to authenticate the remote node -noauth - -# Don't try to proxy ARP for the remote endpoint. User can set proxy -# arp entries up manually if they wish.  More importantly, having -# the "proxyarp" parameter set disables the "defaultroute" option. -noproxyarp - -# Unlimited connection attempts -maxfail 0 - -plugin rp-pppoe.so -{{ source_interface }} -persist -ifname {{ intf }} -ipparam {{ intf }} -debug -logfile {{ logfile }} -{% if 'auto' in default_route -%} -defaultroute -{% elif 'force' in default_route -%} -defaultroute -replacedefaultroute -{% endif %} -mtu {{ mtu }} -mru {{ mtu }} -user "{{ auth_username }}" -password "{{ auth_password }}" -{% if name_server -%} -usepeerdns -{% endif %} -{% if ipv6_enable -%} -+ipv6 -ipv6cp-use-ipaddr -{% endif %} -{% if service_name -%} -rp_pppoe_service "{{ service_name }}" -{% endif %} -{% if on_demand %} -demand -{% endif %} - -""" - -# Please be careful if you edit the template. -# There must be no blank line at the top pf the script file -config_pppoe_ipv6_up_tmpl = """#!/bin/sh - -# As PPPoE is an "on demand" interface we need to re-configure it when it -# becomes up - -if [ "$6" != "{{ intf }}" ]; then -    exit -fi - -{% if ipv6_autoconf -%} -# add some info to syslog -DIALER_PID=$(cat /var/run/{{ intf }}.pid) -logger -t pppd[$DIALER_PID] "executing $0" -logger -t pppd[$DIALER_PID] "configuring interface {{ intf }} via $2" - -# Configure interface-specific Host/Router behaviour. -# Note: It is recommended to have the same setting on all interfaces; mixed -# router/host scenarios are rather uncommon. Possible values are: -# -# 0  Forwarding disabled -# 1  Forwarding enabled -# -echo 1 > /proc/sys/net/ipv6/conf/{{ intf }}/forwarding - -# Accept Router Advertisements; autoconfigure using them. -# -# It also determines whether or not to transmit Router -# Solicitations. If and only if the functional setting is to -# accept Router Advertisements, Router Solicitations will be -# transmitted. Possible values are: -# -# 0  Do not accept Router Advertisements. -# 1  Accept Router Advertisements if forwarding is disabled. -# 2  Overrule forwarding behaviour. Accept Router Advertisements -#    even if forwarding is enabled. -# -echo 2 > /proc/sys/net/ipv6/conf/{{ intf }}/accept_ra - -# Autoconfigure addresses using Prefix Information in Router Advertisements. -echo 1 > /proc/sys/net/ipv6/conf/{{ intf }}/autoconfigure -{% endif %} -""" - -config_pppoe_ip_up_tmpl = """#!/bin/sh - -# As PPPoE is an "on demand" interface we need to re-configure it when it -# becomes up - -if [ "$6" != "{{ intf }}" ]; then -    exit -fi - -# add some info to syslog -DIALER_PID=$(cat /var/run/{{ intf }}.pid) -logger -t pppd[$DIALER_PID] "executing $0" - -echo "{{ description }}" > /sys/class/net/{{ intf }}/ifalias - -{% if vrf -%} -logger -t pppd[$DIALER_PID] "configuring dialer interface $6 for VRF {{ vrf }}" -ip link set dev {{ intf }} master {{ vrf }} -{% endif %} - -"""  default_config_data = {      'access_concentrator': '', @@ -189,10 +50,6 @@ default_config_data = {      'vrf': ''  } -def subprocess_cmd(command): -    p = Popen(command, stdout=PIPE, shell=True) -    p.communicate() -  def get_config():      pppoe = deepcopy(default_config_data)      conf = Config() @@ -301,12 +158,22 @@ 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) + +    # set up configuration file path variables where our templates will be +    # rendered into      intf = pppoe['intf'] -    config_file_pppoe = f'/etc/ppp/peers/{intf}' -    ip_up_script_file = f'/etc/ppp/ip-up.d/9990-vyos-vrf-{intf}' -    ipv6_if_up_script_file = f'/etc/ppp/ipv6-up.d/9990-vyos-autoconf-{intf}' +    config_pppoe = f'/etc/ppp/peers/{intf}' +    script_pppoe_pre_up = f'/etc/ppp/ip-pre-up.d/1000-vyos-pppoe-{intf}' +    script_pppoe_ip_up = f'/etc/ppp/ip-up.d/1000-vyos-pppoe-{intf}' +    script_pppoe_ip_down = f'/etc/ppp/ip-down.d/1000-vyos-pppoe-{intf}' +    script_pppoe_ipv6_up = f'/etc/ppp/ipv6-up.d/1000-vyos-pppoe-{intf}' -    config_files = [config_file_pppoe, ip_up_script_file, ipv6_if_up_script_file] +    config_files = [config_pppoe, script_pppoe_pre_up, script_pppoe_ip_up, +                    script_pppoe_ip_down, script_pppoe_ipv6_up]      # Ensure directories for config files exist - otherwise create them on demand      for file in config_files: @@ -326,24 +193,40 @@ def generate(pppoe):      else:          # Create PPP configuration files -        tmpl = Template(config_pppoe_tmpl) +        tmpl = env.get_template('peer.tmpl') +        config_text = tmpl.render(pppoe) +        with open(config_pppoe, 'w') as f: +            f.write(config_text) + +        # 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) + +        # Create script for ip-up.d +        tmpl = env.get_template('ip-up.script.tmpl')          config_text = tmpl.render(pppoe) -        with open(config_file_pppoe, 'w') as f: +        with open(script_pppoe_ip_up, 'w') as f:              f.write(config_text) -        tmpl = Template(config_pppoe_ip_up_tmpl) +        # Create script for ip-down.d +        tmpl = env.get_template('ip-down.script.tmpl')          config_text = tmpl.render(pppoe) -        with open(ip_up_script_file, 'w') as f: +        with open(script_pppoe_ip_down, 'w') as f:              f.write(config_text) -        tmpl = Template(config_pppoe_ipv6_up_tmpl) +        # Create script for ipv6-up.d +        tmpl = env.get_template('ipv6-up.script.tmpl')          config_text = tmpl.render(pppoe) -        with open(ipv6_if_up_script_file, 'w') as f: +        with open(script_pppoe_ipv6_up, 'w') as f:              f.write(config_text)          # make generated script file executable -        chmod_x_file(ip_up_script_file) -        chmod_x_file(ipv6_if_up_script_file) +        chmod_x(script_pppoe_pre_up) +        chmod_x(script_pppoe_ip_up) +        chmod_x(script_pppoe_ip_down) +        chmod_x(script_pppoe_ipv6_up)      return None diff --git a/src/conf_mode/interfaces-vxlan.py b/src/conf_mode/interfaces-vxlan.py index 3d2638c6f..1f9636729 100755 --- a/src/conf_mode/interfaces-vxlan.py +++ b/src/conf_mode/interfaces-vxlan.py @@ -160,9 +160,6 @@ def verify(vxlan):          if not vxlan['link'] in interfaces():              raise ConfigError('VXLAN source interface does not exist') -    if not (vxlan['group'] or vxlan['remote']): -        raise ConfigError('Group or remote must be configured') -      if not vxlan['vni']:          raise ConfigError('Must configure VNI for VXLAN') diff --git a/src/conf_mode/interfaces-wireguard.py b/src/conf_mode/interfaces-wireguard.py index d8c327e19..5c0c07dc4 100755 --- a/src/conf_mode/interfaces-wireguard.py +++ b/src/conf_mode/interfaces-wireguard.py @@ -1,6 +1,6 @@  #!/usr/bin/env python3  # -# Copyright (C) 2018 VyOS maintainers and contributors +# Copyright (C) 2018-2020 VyOS maintainers and contributors  #  # This program is free software; you can redistribute it and/or modify  # it under the terms of the GNU General Public License version 2 or later as @@ -13,13 +13,12 @@  #  # 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 subprocess +  from copy import deepcopy  from netifaces import interfaces @@ -30,10 +29,9 @@ from vyos.ifconfig import WireGuardIf  kdir = r'/config/auth/wireguard' -  def _check_kmod():      if not os.path.exists('/sys/module/wireguard'): -        if os.system('sudo modprobe wireguard') != 0: +        if os.system('modprobe wireguard') != 0:              raise ConfigError("modprobe wireguard failed") @@ -135,7 +133,8 @@ def get_config():                          {                              p: {                                  'allowed-ips': [], -                              'endpoint': '', +                              'address': '', +                              'port': '',                                'pubkey': ''                              }                          } @@ -144,10 +143,14 @@ def get_config():                      if c.exists(['peer', p, 'allowed-ips']):                          wg['peer'][p]['allowed-ips'] = c.return_values(                              ['peer', p, 'allowed-ips']) -                    # peer endpoint -                    if c.exists(['peer', p, 'endpoint']): -                        wg['peer'][p]['endpoint'] = c.return_value( -                            ['peer', p, 'endpoint']) +                    # peer address +                    if c.exists(['peer', p, 'address']): +                        wg['peer'][p]['address'] = c.return_value( +                            ['peer', p, 'address']) +                    # peer port +                    if c.exists(['peer', p, 'port']): +                        wg['peer'][p]['port'] = c.return_value( +                            ['peer', p, 'port'])                      # persistent-keepalive                      if c.exists(['peer', p, 'persistent-keepalive']):                          wg['peer'][p]['persistent-keepalive'] = c.return_value( @@ -251,8 +254,8 @@ def apply(c):          if c['fwmark']:              intfc.config['fwmark'] = c['fwmark']          # endpoint -        if c['peer'][p]['endpoint']: -            intfc.config['endpoint'] = c['peer'][p]['endpoint'] +        if c['peer'][p]['address'] and c['peer'][p]['port']: +            intfc.config['endpoint'] = "{}:{}".format(c['peer'][p]['address'], c['peer'][p]['port'])          # persistent-keepalive          if 'persistent-keepalive' in c['peer'][p]: diff --git a/src/conf_mode/interfaces-wireless.py b/src/conf_mode/interfaces-wireless.py index b6e62b0aa..da8470f7e 100755 --- a/src/conf_mode/interfaces-wireless.py +++ b/src/conf_mode/interfaces-wireless.py @@ -15,763 +15,27 @@  # along with this program.  If not, see <http://www.gnu.org/licenses/>.  import os - -from jinja2 import Template -from copy import deepcopy  from sys import exit -from stat import S_IRWXU,S_IRGRP,S_IXGRP,S_IROTH,S_IXOTH -from pwd import getpwnam -from grp import getgrnam  from re import findall +from copy import deepcopy +from jinja2 import FileSystemLoader, Environment +  from subprocess import Popen, PIPE  from netifaces import interfaces -from netaddr import * +from netaddr import EUI, mac_unix_expanded -from vyos import ConfigError -from vyos.configdict import list_diff, vlan_to_dict  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 +from vyos.util import process_running, chmod_x, chown_file +from vyos import ConfigError  user = 'root'  group = 'vyattacfg' -# Please be careful if you edit the template. -config_hostapd_tmpl = """ -### Autogenerated by interfaces-wireless.py ### -{% if description %} -# Description: {{ description }} -# User-friendly description of device; up to 32 octets encoded in UTF-8 -device_name={{ description | truncate(32, True) }} -{% endif %} - -# AP netdevice name (without 'ap' postfix, i.e., wlan0 uses wlan0ap for -# management frames with the Host AP driver); wlan0 with many nl80211 drivers -# Note: This attribute can be overridden by the values supplied with the '-i' -# command line parameter. -interface={{ intf }} - -# Driver interface type (hostap/wired/none/nl80211/bsd); -# default: hostap). nl80211 is used with all Linux mac80211 drivers. -# Use driver=none if building hostapd as a standalone RADIUS server that does -# not control any wireless/wired driver. -driver=nl80211 - -# Levels (minimum value for logged events): -#  0 = verbose debugging -#  1 = debugging -#  2 = informational messages -#  3 = notification -#  4 = warning -logger_syslog=-1 -logger_syslog_level=0 -logger_stdout=-1 -logger_stdout_level=0 - -{%- if country_code %} - -# Country code (ISO/IEC 3166-1). Used to set regulatory domain. -# Set as needed to indicate country in which device is operating. -# This can limit available channels and transmit power. -country_code={{ country_code }} - -# Enable IEEE 802.11d. This advertises the country_code and the set of allowed -# channels and transmit power levels based on the regulatory limits. The -# country_code setting must be configured with the correct country for -# IEEE 802.11d functions. -ieee80211d=1 -{% endif %} - -{%- if ssid %} - -# SSID to be used in IEEE 802.11 management frames -ssid={{ ssid }} -{% endif %} - -{%- if channel %} - -# Channel number (IEEE 802.11) -# (default: 0, i.e., not set) -# Please note that some drivers do not use this value from hostapd and the -# channel will need to be configured separately with iwconfig. -# -# If CONFIG_ACS build option is enabled, the channel can be selected -# automatically at run time by setting channel=acs_survey or channel=0, both of -# which will enable the ACS survey based algorithm. -channel={{ channel }} -{% endif %} - -{%- if mode %} - -# Operation mode (a = IEEE 802.11a (5 GHz), b = IEEE 802.11b (2.4 GHz), -# g = IEEE 802.11g (2.4 GHz), ad = IEEE 802.11ad (60 GHz); a/g options are used -# with IEEE 802.11n (HT), too, to specify band). For IEEE 802.11ac (VHT), this -# needs to be set to hw_mode=a. For IEEE 802.11ax (HE) on 6 GHz this needs -# to be set to hw_mode=a. When using ACS (see channel parameter), a -# special value "any" can be used to indicate that any support band can be used. -# This special case is currently supported only with drivers with which -# offloaded ACS is used. -{% if 'n' in mode -%} -hw_mode=g -ieee80211n=1 -{% elif 'ac' in mode -%} -hw_mode=a -ieee80211h=1 -ieee80211ac=1 -{% else -%} -hw_mode={{ mode }} -{% endif %} -{% endif %} - -# ieee80211w: Whether management frame protection (MFP) is enabled -# 0 = disabled (default) -# 1 = optional -# 2 = required -{% if 'disabled' in mgmt_frame_protection -%} -ieee80211w=0 -{% elif 'optional' in mgmt_frame_protection -%} -ieee80211w=1 -{% elif 'required' in mgmt_frame_protection -%} -ieee80211w=2 -{% endif %} - -# ht_capab: HT capabilities (list of flags) -# LDPC coding capability: [LDPC] = supported -# Supported channel width set: [HT40-] = both 20 MHz and 40 MHz with secondary -#	channel below the primary channel; [HT40+] = both 20 MHz and 40 MHz -#	with secondary channel above the primary channel -#	(20 MHz only if neither is set) -#	Note: There are limits on which channels can be used with HT40- and -#	HT40+. Following table shows the channels that may be available for -#	HT40- and HT40+ use per IEEE 802.11n Annex J: -#	freq		HT40-		HT40+ -#	2.4 GHz		5-13		1-7 (1-9 in Europe/Japan) -#	5 GHz		40,48,56,64	36,44,52,60 -#	(depending on the location, not all of these channels may be available -#	for use) -#	Please note that 40 MHz channels may switch their primary and secondary -#	channels if needed or creation of 40 MHz channel maybe rejected based -#	on overlapping BSSes. These changes are done automatically when hostapd -#	is setting up the 40 MHz channel. -# Spatial Multiplexing (SM) Power Save: [SMPS-STATIC] or [SMPS-DYNAMIC] -#	(SMPS disabled if neither is set) -# HT-greenfield: [GF] (disabled if not set) -# Short GI for 20 MHz: [SHORT-GI-20] (disabled if not set) -# Short GI for 40 MHz: [SHORT-GI-40] (disabled if not set) -# Tx STBC: [TX-STBC] (disabled if not set) -# Rx STBC: [RX-STBC1] (one spatial stream), [RX-STBC12] (one or two spatial -#	streams), or [RX-STBC123] (one, two, or three spatial streams); Rx STBC -#	disabled if none of these set -# HT-delayed Block Ack: [DELAYED-BA] (disabled if not set) -# Maximum A-MSDU length: [MAX-AMSDU-7935] for 7935 octets (3839 octets if not -#	set) -# DSSS/CCK Mode in 40 MHz: [DSSS_CCK-40] = allowed (not allowed if not set) -# 40 MHz intolerant [40-INTOLERANT] (not advertised if not set) -# L-SIG TXOP protection support: [LSIG-TXOP-PROT] (disabled if not set) -{% if cap_ht %} -ht_capab= -{%- endif -%} - -{%- if cap_ht_40mhz_incapable -%} -[40-INTOLERANT] -{%- endif -%} - -{%- if cap_ht_delayed_block_ack -%} -[DELAYED-BA] -{%- endif -%} - -{%- if cap_ht_dsss_cck_40 -%} -[DSSS_CCK-40] -{%- endif -%} - -{%- if cap_ht_greenfield -%} -[GF] -{%- endif -%} - -{%- if cap_ht_ldpc -%} -[LDPC] -{%- endif -%} - -{%- if cap_ht_lsig_protection -%} -[LSIG-TXOP-PROT] -{%- endif -%} - -{%- if cap_ht_max_amsdu -%} -[MAX-AMSDU-{{ cap_ht_max_amsdu }}] -{%- endif -%} - -{%- if cap_ht_smps -%} -[SMPS-{{ cap_ht_smps | upper }}] -{%- endif -%} - -{%- if cap_ht_chan_set_width -%} -{%- for csw in cap_ht_chan_set_width -%} -[{{ csw | upper }}] -{%- endfor -%} -{%- endif -%} - -{%- if cap_ht_short_gi -%} -{%- for gi in cap_ht_short_gi -%} -[SHORT-GI-{{ gi }}] -{%- endfor -%} -{%- endif -%} - -{%- if cap_ht_stbc_tx -%} -[TX-STBC] -{%- endif -%} -{%- if cap_ht_stbc_rx -%} -[RX-STBC{{ cap_ht_stbc_rx }}] -{%- endif %} - -# Required for full HT and VHT functionality -wme_enabled=1 - -{% if cap_ht_powersave -%} -# WMM-PS Unscheduled Automatic Power Save Delivery [U-APSD] -# Enable this flag if U-APSD supported outside hostapd (eg., Firmware/driver) -uapsd_advertisement_enabled=1 -{%- endif %} - -{% if cap_req_ht -%} -# Require stations to support HT PHY (reject association if they do not) -require_ht=1 -{% endif %} - -# vht_capab: VHT capabilities (list of flags) -# -# vht_max_mpdu_len: [MAX-MPDU-7991] [MAX-MPDU-11454] -# Indicates maximum MPDU length -# 0 = 3895 octets (default) -# 1 = 7991 octets -# 2 = 11454 octets -# 3 = reserved -# -# supported_chan_width: [VHT160] [VHT160-80PLUS80] -# Indicates supported Channel widths -# 0 = 160 MHz & 80+80 channel widths are not supported (default) -# 1 = 160 MHz channel width is supported -# 2 = 160 MHz & 80+80 channel widths are supported -# 3 = reserved -# -# Rx LDPC coding capability: [RXLDPC] -# Indicates support for receiving LDPC coded pkts -# 0 = Not supported (default) -# 1 = Supported -# -# Short GI for 80 MHz: [SHORT-GI-80] -# Indicates short GI support for reception of packets transmitted with TXVECTOR -# params format equal to VHT and CBW = 80Mhz -# 0 = Not supported (default) -# 1 = Supported -# -# Short GI for 160 MHz: [SHORT-GI-160] -# Indicates short GI support for reception of packets transmitted with TXVECTOR -# params format equal to VHT and CBW = 160Mhz -# 0 = Not supported (default) -# 1 = Supported -# -# Tx STBC: [TX-STBC-2BY1] -# Indicates support for the transmission of at least 2x1 STBC -# 0 = Not supported (default) -# 1 = Supported -# -# Rx STBC: [RX-STBC-1] [RX-STBC-12] [RX-STBC-123] [RX-STBC-1234] -# Indicates support for the reception of PPDUs using STBC -# 0 = Not supported (default) -# 1 = support of one spatial stream -# 2 = support of one and two spatial streams -# 3 = support of one, two and three spatial streams -# 4 = support of one, two, three and four spatial streams -# 5,6,7 = reserved -# -# SU Beamformer Capable: [SU-BEAMFORMER] -# Indicates support for operation as a single user beamformer -# 0 = Not supported (default) -# 1 = Supported -# -# SU Beamformee Capable: [SU-BEAMFORMEE] -# Indicates support for operation as a single user beamformee -# 0 = Not supported (default) -# 1 = Supported -# -# Compressed Steering Number of Beamformer Antennas Supported: -# [BF-ANTENNA-2] [BF-ANTENNA-3] [BF-ANTENNA-4] -#   Beamformee's capability indicating the maximum number of beamformer -#   antennas the beamformee can support when sending compressed beamforming -#   feedback -# If SU beamformer capable, set to maximum value minus 1 -# else reserved (default) -# -# Number of Sounding Dimensions: -# [SOUNDING-DIMENSION-2] [SOUNDING-DIMENSION-3] [SOUNDING-DIMENSION-4] -# Beamformer's capability indicating the maximum value of the NUM_STS parameter -# in the TXVECTOR of a VHT NDP -# If SU beamformer capable, set to maximum value minus 1 -# else reserved (default) -# -# MU Beamformer Capable: [MU-BEAMFORMER] -# Indicates support for operation as an MU beamformer -# 0 = Not supported or sent by Non-AP STA (default) -# 1 = Supported -# -# VHT TXOP PS: [VHT-TXOP-PS] -# Indicates whether or not the AP supports VHT TXOP Power Save Mode -#  or whether or not the STA is in VHT TXOP Power Save mode -# 0 = VHT AP doesn't support VHT TXOP PS mode (OR) VHT STA not in VHT TXOP PS -#  mode -# 1 = VHT AP supports VHT TXOP PS mode (OR) VHT STA is in VHT TXOP power save -#  mode -# -# +HTC-VHT Capable: [HTC-VHT] -# Indicates whether or not the STA supports receiving a VHT variant HT Control -# field. -# 0 = Not supported (default) -# 1 = supported -# -# Maximum A-MPDU Length Exponent: [MAX-A-MPDU-LEN-EXP0]..[MAX-A-MPDU-LEN-EXP7] -# Indicates the maximum length of A-MPDU pre-EOF padding that the STA can recv -# This field is an integer in the range of 0 to 7. -# The length defined by this field is equal to -# 2 pow(13 + Maximum A-MPDU Length Exponent) -1 octets -# -# VHT Link Adaptation Capable: [VHT-LINK-ADAPT2] [VHT-LINK-ADAPT3] -# Indicates whether or not the STA supports link adaptation using VHT variant -# HT Control field -# If +HTC-VHTcapable is 1 -#  0 = (no feedback) if the STA does not provide VHT MFB (default) -#  1 = reserved -#  2 = (Unsolicited) if the STA provides only unsolicited VHT MFB -#  3 = (Both) if the STA can provide VHT MFB in response to VHT MRQ and if the -#      STA provides unsolicited VHT MFB -# Reserved if +HTC-VHTcapable is 0 -# -# Rx Antenna Pattern Consistency: [RX-ANTENNA-PATTERN] -# Indicates the possibility of Rx antenna pattern change -# 0 = Rx antenna pattern might change during the lifetime of an association -# 1 = Rx antenna pattern does not change during the lifetime of an association -# -# Tx Antenna Pattern Consistency: [TX-ANTENNA-PATTERN] -# Indicates the possibility of Tx antenna pattern change -# 0 = Tx antenna pattern might change during the lifetime of an association -# 1 = Tx antenna pattern does not change during the lifetime of an association -{% if cap_vht %} -vht_capab= -{%- endif -%} - -{%- if cap_vht_max_mpdu -%} -[MAX-MPDU-{{ cap_vht_max_mpdu }}] -{%- endif -%} - -{%- if cap_vht_max_mpdu_exp -%} -[MAX-A-MPDU-LEN-EXP{{ cap_vht_max_mpdu_exp }}] -{%- endif -%} - -{%- if cap_vht_chan_set_width -%} -[MAX-A-MPDU-LEN-EXP{{ cap_vht_max_mpdu_exp }}] -{%- endif -%} - -{%- if cap_vht_chan_set_width -%} -{%- if '2' in cap_vht_chan_set_width -%} -[VHT160] -{%- elif '3' in cap_vht_chan_set_width -%} -[VHT160-80PLUS80] -{%- endif -%} -{%- endif -%} - -{%- if cap_vht_stbc_tx -%} -[TX-STBC-2BY1] -{%- endif -%} - -{%- if cap_vht_stbc_rx -%} -[RX-STBC-{{ cap_vht_stbc_rx }}] -{%- endif -%} - -{%- if cap_vht_link_adaptation -%} -{%- if 'unsolicited' in cap_vht_link_adaptation -%} -[VHT-LINK-ADAPT2] -{%- elif 'both' in cap_vht_link_adaptation -%} -[VHT-LINK-ADAPT3] -{%- endif -%} -{%- endif -%} - -{%- if cap_vht_short_gi -%} -{%- for gi in cap_vht_short_gi -%} -[SHORT-GI-{{ gi }}] -{%- endfor -%} -{%- endif -%} - -{%- if cap_vht_ldpc -%} -[RXLDPC] -{%- endif -%} - -{%- if cap_vht_tx_powersave -%} -[VHT-TXOP-PS] -{%- endif -%} - -{%- if cap_vht_vht_cf -%} -[HTC-VHT] -{%- endif -%} - -{%- if cap_vht_beamform -%} -{%- for beamform in cap_vht_beamform -%} -{%- if 'single-user-beamformer' in beamform -%} -[SU-BEAMFORMER] -{%- elif 'single-user-beamformee' in beamform -%} -[SU-BEAMFORMEE] -{%- elif 'multi-user-beamformer' in beamform -%} -[MU-BEAMFORMER] -{%- elif 'multi-user-beamformee' in beamform -%} -[MU-BEAMFORMEE] -{%- endif -%} -{%- endfor -%} -{%- endif -%} - -{%- if cap_vht_antenna_fixed -%} -[RX-ANTENNA-PATTERN][TX-ANTENNA-PATTERN] -{%- endif -%} - -{%- if cap_vht_antenna_cnt -%} -{%- for beamform in cap_vht_beamform -%} -{%- if 'single-user-beamformer' in beamform -%} -[BF-ANTENNA-{{ cap_vht_antenna_cnt|int -1 }}][SOUNDING-DIMENSION-{{ cap_vht_antenna_cnt|int -1}}] -{%- else -%} -[BF-ANTENNA-{{ cap_vht_antenna_cnt }}][SOUNDING-DIMENSION-{{ cap_vht_antenna_cnt }}] -{%- endif -%} -{%- endfor -%} -{%- endif %} - -# ieee80211n: Whether IEEE 802.11n (HT) is enabled -# 0 = disabled (default) -# 1 = enabled -# Note: You will also need to enable WMM for full HT functionality. -# Note: hw_mode=g (2.4 GHz) and hw_mode=a (5 GHz) is used to specify the band. -{% if cap_req_vht -%} -ieee80211n=0 -# Require stations to support VHT PHY (reject association if they do not) -require_vht=1 -{% endif %} - -{% if cap_vht_center_freq_1 -%} -# center freq = 5 GHz + (5 * index) -# So index 42 gives center freq 5.210 GHz -# which is channel 42 in 5G band -vht_oper_centr_freq_seg0_idx={{ cap_vht_center_freq_1 }} -{% endif %} - -{% if cap_vht_center_freq_2 -%} -# center freq = 5 GHz + (5 * index) -# So index 159 gives center freq 5.795 GHz -# which is channel 159 in 5G band -vht_oper_centr_freq_seg1_idx={{ cap_vht_center_freq_2 }} -{% endif %} - -{% if disable_broadcast_ssid -%} -# Send empty SSID in beacons and ignore probe request frames that do not -# specify full SSID, i.e., require stations to know SSID. -# default: disabled (0) -# 1 = send empty (length=0) SSID in beacon and ignore probe request for -#     broadcast SSID -# 2 = clear SSID (ASCII 0), but keep the original length (this may be required -#     with some clients that do not support empty SSID) and ignore probe -#     requests for broadcast SSID -ignore_broadcast_ssid=1 -{% endif %} - -# Station MAC address -based authentication -# Please note that this kind of access control requires a driver that uses -# hostapd to take care of management frame processing and as such, this can be -# used with driver=hostap or driver=nl80211, but not with driver=atheros. -# 0 = accept unless in deny list -# 1 = deny unless in accept list -# 2 = use external RADIUS server (accept/deny lists are searched first) -macaddr_acl=0 - -{% if max_stations -%} -# Maximum number of stations allowed in station table. New stations will be -# rejected after the station table is full. IEEE 802.11 has a limit of 2007 -# different association IDs, so this number should not be larger than that. -# (default: 2007) -max_num_sta={{ max_stations }} -{% endif %} - -{% if isolate_stations -%} -# Client isolation can be used to prevent low-level bridging of frames between -# associated stations in the BSS. By default, this bridging is allowed. -ap_isolate=1 -{% endif %} - -{% if reduce_transmit_power -%} -# Add Power Constraint element to Beacon and Probe Response frames -# This config option adds Power Constraint element when applicable and Country -# element is added. Power Constraint element is required by Transmit Power -# Control. This can be used only with ieee80211d=1. -# Valid values are 0..255. -local_pwr_constraint={{ reduce_transmit_power }} -{% endif %} - -{% if expunge_failing_stations -%} -# Disassociate stations based on excessive transmission failures or other -# indications of connection loss. This depends on the driver capabilities and -# may not be available with all drivers. -disassoc_low_ack=1 -{% endif %} - -{% if sec_wep -%} -# IEEE 802.11 specifies two authentication algorithms. hostapd can be -# configured to allow both of these or only one. Open system authentication -# should be used with IEEE 802.1X. -# Bit fields of allowed authentication algorithms: -# bit 0 = Open System Authentication -# bit 1 = Shared Key Authentication (requires WEP) -auth_algs=2 - -# WEP rekeying (disabled if key lengths are not set or are set to 0) -# Key lengths for default/broadcast and individual/unicast keys: -# 5 = 40-bit WEP (also known as 64-bit WEP with 40 secret bits) -# 13 = 104-bit WEP (also known as 128-bit WEP with 104 secret bits) -wep_key_len_broadcast=5 -wep_key_len_unicast=5 - -# Static WEP key configuration -# -# The key number to use when transmitting. -# It must be between 0 and 3, and the corresponding key must be set. -# default: not set -wep_default_key=0 - -# The WEP keys to use. -# A key may be a quoted string or unquoted hexadecimal digits. -# The key length should be 5, 13, or 16 characters, or 10, 26, or 32 -# digits, depending on whether 40-bit (64-bit), 104-bit (128-bit), or -# 128-bit (152-bit) WEP is used. -# Only the default key must be supplied; the others are optional. -{% if sec_wep_key -%} -{% for key in sec_wep_key -%} -wep_key{{ loop.index -1 }}={{ key}} -{% endfor %} -{%- endif %} - -{% elif sec_wpa -%} -##### WPA/IEEE 802.11i configuration ########################################## - -# Enable WPA. Setting this variable configures the AP to require WPA (either -# WPA-PSK or WPA-RADIUS/EAP based on other configuration). For WPA-PSK, either -# wpa_psk or wpa_passphrase must be set and wpa_key_mgmt must include WPA-PSK. -# Instead of wpa_psk / wpa_passphrase, wpa_psk_radius might suffice. -# For WPA-RADIUS/EAP, ieee8021x must be set (but without dynamic WEP keys), -# RADIUS authentication server must be configured, and WPA-EAP must be included -# in wpa_key_mgmt. -# This field is a bit field that can be used to enable WPA (IEEE 802.11i/D3.0) -# and/or WPA2 (full IEEE 802.11i/RSN): -# bit0 = WPA -# bit1 = IEEE 802.11i/RSN (WPA2) (dot11RSNAEnabled) -{% if 'both' in sec_wpa_mode -%} -wpa=3 -{%- elif 'wpa2' in sec_wpa_mode -%} -wpa=2 -{%- elif 'wpa' in sec_wpa_mode -%} -wpa=1 -{%- endif %} - -{% if sec_wpa_cipher -%} -# Set of accepted cipher suites (encryption algorithms) for pairwise keys -# (unicast packets). This is a space separated list of algorithms: -# CCMP = AES in Counter mode with CBC-MAC (CCMP-128) -# TKIP = Temporal Key Integrity Protocol -# CCMP-256 = AES in Counter mode with CBC-MAC with 256-bit key -# GCMP = Galois/counter mode protocol (GCMP-128) -# GCMP-256 = Galois/counter mode protocol with 256-bit key -# Group cipher suite (encryption algorithm for broadcast and multicast frames) -# is automatically selected based on this configuration. If only CCMP is -# allowed as the pairwise cipher, group cipher will also be CCMP. Otherwise, -# TKIP will be used as the group cipher. The optional group_cipher parameter can -# be used to override this automatic selection. -{% if 'wpa2' in sec_wpa_mode -%} -# Pairwise cipher for RSN/WPA2 (default: use wpa_pairwise value) -rsn_pairwise={{ sec_wpa_cipher | join(" ") }} -{% else -%} -# Pairwise cipher for WPA (v1) (default: TKIP) -wpa_pairwise={{ sec_wpa_cipher | join(" ") }} -{%- endif -%} -{% endif %} - -{% if sec_wpa_passphrase -%} -# IEEE 802.11 specifies two authentication algorithms. hostapd can be -# configured to allow both of these or only one. Open system authentication -# should be used with IEEE 802.1X. -# Bit fields of allowed authentication algorithms: -# bit 0 = Open System Authentication -# bit 1 = Shared Key Authentication (requires WEP) -auth_algs=1 - -# WPA pre-shared keys for WPA-PSK. This can be either entered as a 256-bit -# secret in hex format (64 hex digits), wpa_psk, or as an ASCII passphrase -# (8..63 characters) that will be converted to PSK. This conversion uses SSID -# so the PSK changes when ASCII passphrase is used and the SSID is changed. -wpa_passphrase={{ sec_wpa_passphrase }} - -# Set of accepted key management algorithms (WPA-PSK, WPA-EAP, or both). The -# entries are separated with a space. WPA-PSK-SHA256 and WPA-EAP-SHA256 can be -# added to enable SHA256-based stronger algorithms. -# WPA-PSK = WPA-Personal / WPA2-Personal -# WPA-PSK-SHA256 = WPA2-Personal using SHA256 -wpa_key_mgmt=WPA-PSK - -{% elif sec_wpa_radius -%} -##### IEEE 802.1X-2004 related configuration ################################## -# Require IEEE 802.1X authorization -ieee8021x=1 - -# Set of accepted key management algorithms (WPA-PSK, WPA-EAP, or both). The -# entries are separated with a space. WPA-PSK-SHA256 and WPA-EAP-SHA256 can be -# added to enable SHA256-based stronger algorithms. -# WPA-EAP = WPA-Enterprise / WPA2-Enterprise -# WPA-EAP-SHA256 = WPA2-Enterprise using SHA256 -wpa_key_mgmt=WPA-EAP - -{% if sec_wpa_radius_source -%} -# RADIUS client forced local IP address for the access point -# Normally the local IP address is determined automatically based on configured -# IP addresses, but this field can be used to force a specific address to be -# used, e.g., when the device has multiple IP addresses. -radius_client_addr={{ sec_wpa_radius_source }} - -# The own IP address of the access point (used as NAS-IP-Address) -own_ip_addr={{ sec_wpa_radius_source }} -{% else %} -# The own IP address of the access point (used as NAS-IP-Address) -own_ip_addr=127.0.0.1 -{% endif %} - -{% for radius in sec_wpa_radius -%} -{%- if not radius.disabled -%} -# RADIUS authentication server -auth_server_addr={{ radius.server }} -auth_server_port={{ radius.port }} -auth_server_shared_secret={{ radius.key }} -{% if radius.acc_port -%} -# RADIUS accounting server -acct_server_addr={{ radius.server }} -acct_server_port={{ radius.acc_port }} -acct_server_shared_secret={{ radius.key }} -{% endif %} -{% endif %} -{% endfor %} - -{% endif %} - -{% else %} -# Open system -auth_algs=1 -{% endif %} - -# TX queue parameters (EDCF / bursting) -# tx_queue_<queue name>_<param> -# queues: data0, data1, data2, data3 -#		(data0 is the highest priority queue) -# parameters: -#   aifs: AIFS (default 2) -#   cwmin: cwMin (1, 3, 7, 15, 31, 63, 127, 255, 511, 1023, 2047, 4095, 8191, -#	   16383, 32767) -#   cwmax: cwMax (same values as cwMin, cwMax >= cwMin) -#   burst: maximum length (in milliseconds with precision of up to 0.1 ms) for -#          bursting -# -# Default WMM parameters (IEEE 802.11 draft; 11-03-0504-03-000e): -# These parameters are used by the access point when transmitting frames -# to the clients. -# -# Low priority / AC_BK = background -tx_queue_data3_aifs=7 -tx_queue_data3_cwmin=15 -tx_queue_data3_cwmax=1023 -tx_queue_data3_burst=0 -# Note: for IEEE 802.11b mode: cWmin=31 cWmax=1023 burst=0 -# -# Normal priority / AC_BE = best effort -tx_queue_data2_aifs=3 -tx_queue_data2_cwmin=15 -tx_queue_data2_cwmax=63 -tx_queue_data2_burst=0 -# Note: for IEEE 802.11b mode: cWmin=31 cWmax=127 burst=0 -# -# High priority / AC_VI = video -tx_queue_data1_aifs=1 -tx_queue_data1_cwmin=7 -tx_queue_data1_cwmax=15 -tx_queue_data1_burst=3.0 -# Note: for IEEE 802.11b mode: cWmin=15 cWmax=31 burst=6.0 -# -# Highest priority / AC_VO = voice -tx_queue_data0_aifs=1 -tx_queue_data0_cwmin=3 -tx_queue_data0_cwmax=7 -tx_queue_data0_burst=1.5 - -# Default WMM parameters (IEEE 802.11 draft; 11-03-0504-03-000e): -# for 802.11a or 802.11g networks -# These parameters are sent to WMM clients when they associate. -# The parameters will be used by WMM clients for frames transmitted to the -# access point. -# -# note - txop_limit is in units of 32microseconds -# note - acm is admission control mandatory flag. 0 = admission control not -# required, 1 = mandatory -# note - Here cwMin and cmMax are in exponent form. The actual cw value used -# will be (2^n)-1 where n is the value given here. The allowed range for these -# wmm_ac_??_{cwmin,cwmax} is 0..15 with cwmax >= cwmin. -# -wmm_enabled=1 - -# Low priority / AC_BK = background -wmm_ac_bk_cwmin=4 -wmm_ac_bk_cwmax=10 -wmm_ac_bk_aifs=7 -wmm_ac_bk_txop_limit=0 -wmm_ac_bk_acm=0 -# Note: for IEEE 802.11b mode: cWmin=5 cWmax=10 -# -# Normal priority / AC_BE = best effort -wmm_ac_be_aifs=3 -wmm_ac_be_cwmin=4 -wmm_ac_be_cwmax=10 -wmm_ac_be_txop_limit=0 -wmm_ac_be_acm=0 -# Note: for IEEE 802.11b mode: cWmin=5 cWmax=7 -# -# High priority / AC_VI = video -wmm_ac_vi_aifs=2 -wmm_ac_vi_cwmin=3 -wmm_ac_vi_cwmax=4 -wmm_ac_vi_txop_limit=94 -wmm_ac_vi_acm=0 -# Note: for IEEE 802.11b mode: cWmin=4 cWmax=5 txop_limit=188 -# -# Highest priority / AC_VO = voice -wmm_ac_vo_aifs=2 -wmm_ac_vo_cwmin=2 -wmm_ac_vo_cwmax=3 -wmm_ac_vo_txop_limit=47 -wmm_ac_vo_acm=0 - -""" - -# Please be careful if you edit the template. -config_wpa_suppl_tmpl = """ -# WPA supplicant config -network={ -    ssid="{{ ssid }}" -{%- if sec_wpa_passphrase %} -    psk="{{ sec_wpa_passphrase }}" -{% else %} -    key_mgmt=NONE -{% endif %} -} - -""" -  default_config_data = {      'address': [],      'address_remove': [], @@ -799,7 +63,7 @@ default_config_data = {      'cap_vht_center_freq_2' : '',      'cap_vht_chan_set_width' : '',      'cap_vht_ldpc' : False, -    'cap_vht_link_adaptation' : False, +    'cap_vht_link_adaptation' : '',      'cap_vht_max_mpdu_exp' : '',      'cap_vht_max_mpdu' : '',      'cap_vht_short_gi' : [], @@ -857,11 +121,8 @@ def get_conf_file(conf_type, intf):      # create directory on demand      if not os.path.exists(cfg_dir):          os.mkdir(cfg_dir) -        # fix permissions - corresponds to mode 755 -        os.chmod(cfg_dir, S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH) -        uid = getpwnam(user).pw_uid -        gid = getgrnam(group).gr_gid -        os.chown(cfg_dir, uid, gid) +        chmod_x(cfg_dir) +        chown_file(cfg_dir, user, group)      cfg_file = cfg_dir + r'/{}.cfg'.format(intf)      return cfg_file @@ -872,11 +133,8 @@ def get_pid(conf_type, intf):      # create directory on demand      if not os.path.exists(cfg_dir):          os.mkdir(cfg_dir) -        # fix permissions - corresponds to mode 755 -        os.chmod(cfg_dir, S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH) -        uid = getpwnam(user).pw_uid -        gid = getgrnam(group).gr_gid -        os.chown(cfg_dir, uid, gid) +        chmod_x(cfg_dir) +        chown_file(cfg_dir, user, group)      cfg_file = cfg_dir + r'/{}.pid'.format(intf)      return cfg_file @@ -888,11 +146,8 @@ def get_wpa_suppl_config_name(intf):      # create directory on demand      if not os.path.exists(cfg_dir):          os.mkdir(cfg_dir) -        # fix permissions - corresponds to mode 755 -        os.chmod(cfg_dir, S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH) -        uid = getpwnam(user).pw_uid -        gid = getgrnam(group).gr_gid -        os.chown(cfg_dir, uid, gid) +        chmod_x(cfg_dir) +        chown_file(cfg_dir, user, group)      cfg_file = cfg_dir + r'/{}.cfg'.format(intf)      return cfg_file @@ -1043,7 +298,7 @@ def get_config():      # VHT link adaptation capabilities      if conf.exists('capabilities vht link-adaptation'):          wifi['cap_vht'] = True -        wifi['cap_vht_link_adaptation'] = True +        wifi['cap_vht_link_adaptation'] = conf.return_value('capabilities vht link-adaptation')      # Set the maximum length of A-MPDU pre-EOF padding that the station can receive      if conf.exists('capabilities vht max-mpdu-exp'): @@ -1329,6 +584,10 @@ def verify(wifi):      if wifi['cap_vht_beamform'] and wifi['cap_vht_antenna_cnt'] == 1:          raise ConfigError('Cannot use beam forming with just one antenna!') +    if wifi['cap_vht_beamform'] == 'single-user-beamformer' and wifi['cap_vht_antenna_cnt'] < 3: +        # Nasty Gotcha: see https://w1.fi/cgit/hostap/plain/hostapd/hostapd.conf lines 692-705 +        raise ConfigError('Single-user beam former requires at least 3 antennas!') +      if wifi['sec_wep'] and (len(wifi['sec_wep_key']) == 0):          raise ConfigError('Missing WEP keys') @@ -1364,6 +623,11 @@ 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) +      # always stop hostapd service first before reconfiguring it      pidfile = get_pid('hostapd', wifi['intf'])      if process_running(pidfile): @@ -1418,13 +682,13 @@ def generate(wifi):      # render appropriate new config files depending on access-point or station mode      if wifi['op_mode'] == 'ap': -        tmpl = Template(config_hostapd_tmpl) +        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)      elif wifi['op_mode'] == 'station': -        tmpl = Template(config_wpa_suppl_tmpl) +        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) diff --git a/src/conf_mode/interfaces-wirelessmodem.py b/src/conf_mode/interfaces-wirelessmodem.py index 7aee5c9bd..da33d54e4 100755 --- a/src/conf_mode/interfaces-wirelessmodem.py +++ b/src/conf_mode/interfaces-wirelessmodem.py @@ -18,84 +18,14 @@ import os  from sys import exit  from copy import deepcopy -from jinja2 import Template -from subprocess import Popen, PIPE +from jinja2 import FileSystemLoader, Environment  from netifaces import interfaces  from vyos.config import Config -from vyos.util import chown_file, chmod_x_file +from vyos.defaults import directories as vyos_data_dir +from vyos.util import chown_file, chmod_x, subprocess_cmd  from vyos import ConfigError -# Please be careful if you edit the template. -config_wwan_tmpl = """### Autogenerated by interfaces-wirelessmodem.py ### -{% if description %} -# {{ description }} -{% endif %} -ifname {{ intf }} -ipparam "{{ intf }} {{ metric }}" -linkname {{ intf }} -{% if name_server -%} -usepeerdns -{%- endif %} -# physical device -/dev/{{ device }} -lcp-echo-failure 0 -115200 -debug -logfile {{ logfile }} -nodefaultroute -ipcp-max-failure 4 -ipcp-accept-local -ipcp-accept-remote -noauth -crtscts -lock -persist -{% if on_demand -%} -demand -{%- endif %} - -connect '/usr/sbin/chat -v -t6 -f {{ chat_script }}' - -""" - -# Please be careful if you edit the template. -chat_wwan_tmpl = """ -ABORT 'NO DIAL TONE' ABORT 'NO ANSWER' ABORT 'NO CARRIER' ABORT DELAYED -'' AT -OK ATZ -OK 'AT+CGDCONT=1,"IP","{{ apn }}"' -OK ATD*99# -CONNECT '' - -""" - -config_wwan_ip_up_tmpl = """#!/bin/sh -# As WWAN is an "on demand" interface we need to re-configure it when it -# becomes 'up' - -ipparam=$6 - -# device name and metric are received using ipparam -device=`echo "$ipparam"|awk '{ print $1 }'` - -if [ "$device" != "{{ intf }}" ]; then -    exit -fi - -# add some info to syslog -DIALER_PID=$(cat /var/run/{{ intf }}.pid) -logger -t pppd[$DIALER_PID] "executing $0" - -echo "{{ description }}" > /sys/class/net/{{ intf }}/ifalias - -{% if vrf -%} -logger -t pppd[$DIALER_PID] "configuring interface {{ intf }} for VRF {{ vrf }}" -ip link set dev {{ intf }} master {{ vrf }} -{% endif %} - -""" -  default_config_data = {      'address': [],      'apn': '', @@ -114,10 +44,6 @@ default_config_data = {      'vrf': ''  } -def subprocess_cmd(command): -    p = Popen(command, stdout=PIPE, shell=True) -    p.communicate() -  def check_kmod():      modules = ['option', 'usb_wwan', 'usbserial']      for module in modules: @@ -206,12 +132,22 @@ 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'] -    config_file_wwan = f'/etc/ppp/peers/{intf}' -    config_file_wwan_chat = wwan['chat_script'] -    ip_up_script_file = f'/etc/ppp/ip-up.d/9991-vyos-vrf-{intf}' +    config_wwan = f'/etc/ppp/peers/{intf}' +    config_wwan_chat = wwan['chat_script'] +    script_wwan_pre_up = f'/etc/ppp/ip-pre-up.d/1010-vyos-wwan-{intf}' +    script_wwan_ip_up = f'/etc/ppp/ip-up.d/1010-vyos-wwan-{intf}' +    script_wwan_ip_down = f'/etc/ppp/ip-down.d/1010-vyos-wwan-{intf}' -    config_files = [config_file_wwan, config_file_wwan_chat, ip_up_script_file] +    config_files = [config_wwan, config_wwan_chat, script_wwan_pre_up, +                    script_wwan_ip_up, script_wwan_ip_down]      # Ensure directories for config files exist - otherwise create them on demand      for file in config_files: @@ -231,25 +167,39 @@ def generate(wwan):      else:          # Create PPP configuration files -        tmpl = Template(config_wwan_tmpl) +        tmpl = env.get_template('peer.tmpl')          config_text = tmpl.render(wwan) -        with open(config_file_wwan, 'w') as f: +        with open(config_wwan, 'w') as f:              f.write(config_text)          # Create PPP chat script -        tmpl = Template(chat_wwan_tmpl) +        tmpl = env.get_template('chat.tmpl') +        config_text = tmpl.render(wwan) +        with open(config_wwan_chat, 'w') as f: +            f.write(config_text) + +        # 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) + +        # Create script for ip-up.d +        tmpl = env.get_template('ip-up.script.tmpl')          config_text = tmpl.render(wwan) -        with open(wwan['chat_script'], 'w') as f: +        with open(script_wwan_ip_up, 'w') as f:              f.write(config_text) -        # Create ip-pre-up script -        tmpl = Template(config_wwan_ip_up_tmpl) +        # Create script for ip-down.d +        tmpl = env.get_template('ip-down.script.tmpl')          config_text = tmpl.render(wwan) -        with open(ip_up_script_file, 'w') as f: +        with open(script_wwan_ip_down, 'w') as f:              f.write(config_text)          # make generated script file executable -        chmod_x_file(ip_up_script_file) +        chmod_x(script_wwan_pre_up) +        chmod_x(script_wwan_ip_up) +        chmod_x(script_wwan_ip_down)      return None diff --git a/src/conf_mode/ipsec-settings.py b/src/conf_mode/ipsec-settings.py index e80c6caf0..90b6b0d57 100755 --- a/src/conf_mode/ipsec-settings.py +++ b/src/conf_mode/ipsec-settings.py @@ -1,6 +1,6 @@  #!/usr/bin/env python3  # -# Copyright (C) 2018 VyOS maintainers and contributors +# Copyright (C) 2018-2020 VyOS maintainers and contributors  #  # This program is free software; you can redistribute it and/or modify  # it under the terms of the GNU General Public License version 2 or later as @@ -13,22 +13,18 @@  #  # You should have received a copy of the GNU General Public License  # along with this program.  If not, see <http://www.gnu.org/licenses/>. -# -# -import sys  import re  import os -import jinja2 -import syslog as sl -import time -import vyos.config -import vyos.defaults +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 -  ra_conn_name = "remote-access"  charon_conf_file = "/etc/strongswan.d/charon.conf"  ipsec_secrets_flie = "/etc/ipsec.secrets" @@ -42,55 +38,8 @@ delim_ipsec_l2tp_begin = "### VyOS L2TP VPN Begin ###"  delim_ipsec_l2tp_end = "### VyOS L2TP VPN End ###"  charon_pidfile = "/var/run/charon.pid" -l2pt_ipsec_conf = ''' -{{delim_ipsec_l2tp_begin}} -include {{ipsec_ra_conn_file}} -{{delim_ipsec_l2tp_end}} -''' - -l2pt_ipsec_secrets_conf = ''' -{{delim_ipsec_l2tp_begin}} -{% if ipsec_l2tp_auth_mode == 'pre-shared-secret' %} -{{outside_addr}} %any : PSK "{{ipsec_l2tp_secret}}" -{% elif ipsec_l2tp_auth_mode == 'x509' %} -: RSA {{server_key_file_copied}} -{% endif%} -{{delim_ipsec_l2tp_end}} -''' - -l2tp_ipsec_ra_conn_conf = ''' -{{delim_ipsec_l2tp_begin}} -conn {{ra_conn_name}} -  type=transport -  left={{outside_addr}} -  leftsubnet=%dynamic[/1701] -  rightsubnet=%dynamic -  mark_in=%unique -  auto=add -  ike=aes256-sha1-modp1024,3des-sha1-modp1024,3des-sha1-modp1024! -  dpddelay=15 -  dpdtimeout=45 -  dpdaction=clear -  esp=aes256-sha1,3des-sha1! -  rekey=no -{% if ipsec_l2tp_auth_mode == 'pre-shared-secret' %} -  authby=secret -  leftauth=psk -  rightauth=psk -{% elif ipsec_l2tp_auth_mode == 'x509' %} -  authby=rsasig -  leftrsasigkey=%cert -  rightrsasigkey=%cert -  rightca=%same -  leftcert={{server_cert_file_copied}} -{% endif %} -  ikelifetime={{ipsec_l2tp_ike_lifetime}} -  keylife={{ipsec_l2tp_lifetime}} -{{delim_ipsec_l2tp_end}} -''' -  def get_config(): -    config = vyos.config.Config() +    config = Config()      data = {"install_routes": "yes"}      if config.exists("vpn ipsec options disable-route-autoinstall"): @@ -146,44 +95,12 @@ def get_config():      return data -### ipsec secret l2tp -def write_ipsec_secrets(c): -    tmpl = jinja2.Template(l2pt_ipsec_secrets_conf, trim_blocks=True) -    l2pt_ipsec_secrets_txt = tmpl.render(c) -    old_umask = os.umask(0o077) -    open(ipsec_secrets_flie,'w').write(l2pt_ipsec_secrets_txt) -    os.umask(old_umask) -    sl.syslog(sl.LOG_NOTICE, ipsec_secrets_flie + ' written') - -### ipsec remote access connection config -def write_ipsec_ra_conn(c): -    tmpl = jinja2.Template(l2tp_ipsec_ra_conn_conf, trim_blocks=True) -    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) -        sl.syslog(sl.LOG_NOTICE, ipsec_ra_conn_dir  + " created") - -    open(ipsec_ra_conn_file,'w').write(ipsec_ra_conn_txt) -    os.umask(old_umask) -    sl.syslog(sl.LOG_NOTICE, ipsec_ra_conn_file + ' written')  ### Remove config from file by delimiter  def remove_confs(delim_begin, delim_end, conf_file):      os.system("sed -i '/"+delim_begin+"/,/"+delim_end+"/d' "+conf_file) -### Append "include /path/to/ra_conn" to ipsec conf file -def append_ipsec_conf(c): -    tmpl = jinja2.Template(l2pt_ipsec_conf, trim_blocks=True) -    l2pt_ipsec_conf_txt = tmpl.render(c) -    old_umask = os.umask(0o077) -    open(ipsec_conf_flie,'a').write(l2pt_ipsec_conf_txt) -    os.umask(old_umask) -    sl.syslog(sl.LOG_NOTICE, ipsec_conf_flie + ' written') -  ### Checking certificate storage and notice if certificate not in /config directory  def check_cert_file_store(cert_name, file_path, dts_path):      if not re.search('^\/config\/.+', file_path): @@ -197,8 +114,6 @@ def check_cert_file_store(cert_name, file_path, dts_path):        ret = os.system('cp -f '+file_path+' '+dts_path)        if ret:           raise ConfigError("L2TP VPN configuration error: Cannot copy "+file_path) -      else: -        sl.syslog(sl.LOG_NOTICE, file_path + ' copied to '+dts_path)  def verify(data):      # l2tp ipsec check @@ -231,22 +146,45 @@ 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.defaults.directories["data"], "templates", "ipsec") -    fs_loader = jinja2.FileSystemLoader(tmpl_path) -    env = jinja2.Environment(loader=fs_loader) - - -    charon_conf_tmpl = env.get_template("charon.tmpl") -    charon_conf = charon_conf_tmpl.render(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(charon_conf) +        f.write(config_text)      if data["ipsec_l2tp"]:          remove_confs(delim_ipsec_l2tp_begin, delim_ipsec_l2tp_end, ipsec_conf_flie) -        write_ipsec_secrets(data) -        write_ipsec_ra_conn(data) -        append_ipsec_conf(data) + +        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) +        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) +        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) +        os.umask(old_umask) +      else:          if os.path.exists(ipsec_ra_conn_file):              remove_confs(delim_ipsec_l2tp_begin, delim_ipsec_l2tp_end, ipsec_ra_conn_file) @@ -254,15 +192,15 @@ def generate(data):          remove_confs(delim_ipsec_l2tp_begin, delim_ipsec_l2tp_end, ipsec_conf_flie)  def restart_ipsec(): -    os.system("ipsec restart >&/dev/null") +    os.system('ipsec restart >&/dev/null')      # counter for apply swanctl config      counter = 10      while counter <= 10:          if os.path.exists(charon_pidfile): -            os.system("swanctl -q >&/dev/null") +            os.system('swanctl -q >&/dev/null')              break          counter -=1 -        time.sleep(1) +        sleep(1)          if counter == 0:              raise ConfigError('VPN configuration error: IPSec is not running.') @@ -278,4 +216,4 @@ if __name__ == '__main__':          apply(c)      except ConfigError as e:          print(e) -        sys.exit(1) +        exit(1) diff --git a/src/conf_mode/lldp.py b/src/conf_mode/lldp.py index b72916ab8..4e3dfc0b6 100755 --- a/src/conf_mode/lldp.py +++ b/src/conf_mode/lldp.py @@ -1,6 +1,6 @@  #!/usr/bin/env python3  # -# Copyright (C) 2017-2019 VyOS maintainers and contributors +# Copyright (C) 2017-2020 VyOS maintainers and contributors  #  # This program is free software; you can redistribute it and/or modify  # it under the terms of the GNU General Public License version 2 or later as @@ -14,47 +14,22 @@  # 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 re -import sys  import os -import jinja2 +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 -# Please be careful if you edit the template.  config_file = "/etc/default/lldpd" -lldp_tmpl = """ -### Autogenerated by lldp.py ### -DAEMON_ARGS="-M 4{% if options.snmp %} -x{% endif %}{% if options.cdp %} -c{% endif %}{% if options.edp %} -e{% endif %}{% if options.fdp %} -f{% endif %}{% if options.sonmp %} -s{% endif %}" - -""" -  vyos_config_file = "/etc/lldpd.d/01-vyos.conf" -vyos_tmpl = """ -### Autogenerated by lldp.py ### - -configure system platform VyOS -configure system description "VyOS {{ options.description }}" -{% if options.listen_on -%} -configure system interface pattern "{{ ( options.listen_on | select('equalto','all') | map('replace','all','*') | list + options.listen_on | select('equalto','!all') | map('replace','!all','!*') | list + options.listen_on | reject('equalto','all') | reject('equalto','!all') | list ) | unique | join(",") }}" -{%- endif %} -{% if options.mgmt_addr -%} -configure system ip management pattern {{ options.mgmt_addr | join(",") }} -{%- endif %} -{%- for loc in location -%} -{%- if loc.elin %} -configure ports {{ loc.name }} med location elin "{{ loc.elin }}" -{%- endif %} -{%- if loc.coordinate_based %} -configure ports {{ loc.name }} med location coordinate {% if loc.coordinate_based.latitude %}latitude {{ loc.coordinate_based.latitude }}{% endif %} {% if loc.coordinate_based.longitude %}longitude {{ loc.coordinate_based.longitude }}{% endif %} {% if loc.coordinate_based.altitude %}altitude {{ loc.coordinate_based.altitude }} m{% endif %} {% if loc.coordinate_based.datum %}datum {{ loc.coordinate_based.datum }}{% endif %} -{%- endif %} - - -{% endfor %} -""" +base = ['service', 'lldp']  default_config_data = {      "options": '', @@ -64,7 +39,7 @@ default_config_data = {  def get_options(config):      options = {} -    config.set_level('service lldp') +    config.set_level(base)      options['listen_vlan'] = config.exists('listen-vlan')      options['mgmt_addr'] = [] @@ -84,30 +59,31 @@ def get_options(config):      if snmp:          config.set_level('')          options["sys_snmp"] = config.exists('service snmp') -        config.set_level('service lldp') +        config.set_level(base) -    config.set_level('service lldp legacy-protocols') +    config.set_level(base + ['legacy-protocols'])      options['cdp'] = config.exists('cdp')      options['edp'] = config.exists('edp')      options['fdp'] = config.exists('fdp')      options['sonmp'] = config.exists('sonmp')      # start with an unknown version information -    options['description'] = 'unknown' +    version_data = get_version_data() +    options['description'] = version_data['version']      options['listen_on'] = []      return options  def get_interface_list(config): -    config.set_level('service lldp') -    intfs_names = config.list_nodes('interface') +    config.set_level(base) +    intfs_names = config.list_nodes(['interface'])      if len(intfs_names) < 0:          return 0      interface_list = []      for name in intfs_names: -        config.set_level('service lldp interface {0}'.format(name)) -        disable = config.exists('disable') +        config.set_level(base + ['interface', name]) +        disable = config.exists(['disable'])          intf = {              'name': name,              'disable': disable @@ -117,10 +93,10 @@ def get_interface_list(config):  def get_location_intf(config, name): -    path = 'service lldp interface {0}'.format(name) +    path = base + ['interface', name]      config.set_level(path) -    config.set_level('{} location'.format(path)) +    config.set_level(path + ['location'])      elin = ''      coordinate_based = {} @@ -128,18 +104,18 @@ def get_location_intf(config, name):          elin = config.return_value('elin')      if config.exists('coordinate-based'): -        config.set_level('{} location coordinate-based'.format(path)) +        config.set_level(path + ['location', 'coordinate-based']) -        coordinate_based['latitude'] = config.return_value('latitude') -        coordinate_based['longitude'] = config.return_value('longitude') +        coordinate_based['latitude'] = config.return_value(['latitude']) +        coordinate_based['longitude'] = config.return_value(['longitude'])          coordinate_based['altitude'] = '0' -        if config.exists('altitude'): -            coordinate_based['altitude'] = config.return_value('altitude') +        if config.exists(['altitude']): +            coordinate_based['altitude'] = config.return_value(['altitude'])          coordinate_based['datum'] = 'WGS84' -        if config.exists('datum'): -            coordinate_based['datum'] = config.return_value('datum') +        if config.exists(['datum']): +            coordinate_based['datum'] = config.return_value(['datum'])      intf = {          'name': name, @@ -151,8 +127,8 @@ def get_location_intf(config, name):  def get_location(config): -    config.set_level('service lldp') -    intfs_names = config.list_nodes('interface') +    config.set_level(base) +    intfs_names = config.list_nodes(['interface'])      if len(intfs_names) < 0:          return 0 @@ -170,7 +146,7 @@ def get_location(config):  def get_config():      lldp = deepcopy(default_config_data)      conf = Config() -    if not conf.exists('service lldp'): +    if not conf.exists(base):          return None      else:          lldp['options'] = get_options(conf) @@ -232,10 +208,10 @@ def generate(lldp):      if lldp is None:          return -    with open('/opt/vyatta/etc/version', 'r') as f: -        tmp = f.read() -        lldp['options']['description'] = tmp.split()[1] - +    # 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']: @@ -248,13 +224,13 @@ def generate(lldp):          lldp['options']['listen_on'].append(tmp)      # generate /etc/default/lldpd -    tmpl = jinja2.Template(lldp_tmpl) +    tmpl = env.get_template('lldpd.tmpl')      config_text = tmpl.render(lldp)      with open(config_file, 'w') as f:          f.write(config_text)      # generate /etc/lldpd.d/01-vyos.conf -    tmpl = jinja2.Template(vyos_tmpl) +    tmpl = env.get_template('vyos.conf.tmpl')      config_text = tmpl.render(lldp)      with open(vyos_config_file, 'w') as f:          f.write(config_text) @@ -278,5 +254,5 @@ if __name__ == '__main__':          apply(c)      except ConfigError as e:          print(e) -        sys.exit(1) +        exit(1) diff --git a/src/conf_mode/mdns_repeater.py b/src/conf_mode/mdns_repeater.py index cef735c0d..f738cc6a6 100755 --- a/src/conf_mode/mdns_repeater.py +++ b/src/conf_mode/mdns_repeater.py @@ -1,6 +1,6 @@  #!/usr/bin/env python3  # -# Copyright (C) 2017 VyOS maintainers and contributors +# Copyright (C) 2017-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,45 +13,42 @@  #  # You should have received a copy of the GNU General Public License  # along with this program.  If not, see <http://www.gnu.org/licenses/>. -# -# -import sys  import os -import jinja2 -import netifaces + +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  config_file = r'/etc/default/mdns-repeater' -config_tmpl = """ -### Autogenerated by mdns_repeater.py ### -DAEMON_ARGS="{{ interfaces | join(' ') }}" -""" -  default_config_data = {      'disabled': False,      'interfaces': []  }  def get_config(): -    mdns = default_config_data +    mdns = deepcopy(default_config_data)      conf = Config() -    if not conf.exists('service mdns repeater'): +    base = ['service', 'mdns', 'repeater'] +    if not conf.exists(base):          return None      else: -        conf.set_level('service mdns repeater') +        conf.set_level(base)      # Service can be disabled by user -    if conf.exists('disable'): +    if conf.exists(['disable']):          mdns['disabled'] = True          return mdns      # Interface to repeat mDNS advertisements -    if conf.exists('interface'): -        mdns['interfaces'] = conf.return_values('interface') +    if conf.exists(['interface']): +        mdns['interfaces'] = conf.return_values(['interface'])      return mdns @@ -69,8 +66,8 @@ def verify(mdns):      # For mdns-repeater to work it is essential that the interfaces has      # an IPv4 address assigned      for interface in mdns['interfaces']: -        if netifaces.AF_INET in netifaces.ifaddresses(interface).keys(): -            if len(netifaces.ifaddresses(interface)[netifaces.AF_INET]) < 1: +        if AF_INET in ifaddresses(interface).keys(): +            if len(ifaddresses(interface)[AF_INET]) < 1:                  raise ConfigError('mDNS repeater requires an IPv6 address configured on interface %s!'.format(interface))      return None @@ -83,7 +80,12 @@ def generate(mdns):          print('Warning: mDNS repeater will be deactivated because it is disabled')          return None -    tmpl = jinja2.Template(config_tmpl) +    # 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) @@ -108,4 +110,4 @@ if __name__ == '__main__':          apply(c)      except ConfigError as e:          print(e) -        sys.exit(1) +        exit(1) diff --git a/src/conf_mode/ntp.py b/src/conf_mode/ntp.py index c3e8d51b3..0f635556b 100755 --- a/src/conf_mode/ntp.py +++ b/src/conf_mode/ntp.py @@ -14,61 +14,19 @@  # You should have received a copy of the GNU General Public License  # along with this program.  If not, see <http://www.gnu.org/licenses/>. -import sys  import os -import jinja2 -import ipaddress  from 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  config_file = r'/etc/ntp.conf' -# Please be careful if you edit the template. -config_tmpl = """ -### Autogenerated by ntp.py ### - -# -# Non-configurable defaults -# -driftfile /var/lib/ntp/ntp.drift -# By default, only allow ntpd to query time sources, ignore any incoming requests -restrict default noquery nopeer notrap nomodify -# Local users have unrestricted access, allowing reconfiguration via ntpdc -restrict 127.0.0.1 -restrict -6 ::1 - -# -# Configurable section -# - -{% if servers -%} -{% for s in servers -%} -# Server configuration for: {{ s.name }} -server {{ s.name }} iburst {{ s.options | join(" ") }} -{% endfor -%} -{% endif %} - -{% if allowed_networks -%} -{% for n in allowed_networks -%} -# Client configuration for network: {{ n.network }} -restrict {{ n.address }} mask {{ n.netmask }} nomodify notrap nopeer - -{% endfor -%} -{% endif %} - -{% if listen_address -%} -# NTP should listen on configured addresses only -interface ignore wildcard -{% for a in listen_address -%} -interface listen {{ a }} -{% endfor -%} -{% endif %} - -""" -  default_config_data = {      'servers': [],      'allowed_networks': [], @@ -86,7 +44,7 @@ def get_config():      if conf.exists('allow-clients address'):          networks = conf.return_values('allow-clients address')          for n in networks: -            addr = ipaddress.ip_network(n) +            addr = ip_network(n)              net = {                  "network" : n,                  "address" : addr.network_address, @@ -128,7 +86,7 @@ def verify(ntp):      for n in ntp['allowed_networks']:          try: -            addr = ipaddress.ip_network( n['network'] ) +            addr = ip_network( n['network'] )              break          except ValueError:              raise ConfigError("{0} does not appear to be a valid IPv4 or IPv6 network, check host bits!".format(n['network'])) @@ -140,7 +98,12 @@ def generate(ntp):      if ntp is None:          return None -    tmpl = jinja2.Template(config_tmpl) +    # 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) @@ -165,4 +128,4 @@ if __name__ == '__main__':          apply(c)      except ConfigError as e:          print(e) -        sys.exit(1) +        exit(1) diff --git a/src/conf_mode/protocols_bfd.py b/src/conf_mode/protocols_bfd.py index 58f5b5a0e..9940c80c5 100755 --- a/src/conf_mode/protocols_bfd.py +++ b/src/conf_mode/protocols_bfd.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,39 +13,20 @@  #  # 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 jinja2 -import copy  import os -import vyos.validate -from vyos import ConfigError +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  config_file = r'/tmp/bfd.frr' -# Please be careful if you edit the template. -config_tmpl = """ -! -bfd -{% for peer in old_peers -%} - no peer {{ peer.remote }}{% if peer.multihop %} multihop{% endif %}{% if peer.src_addr %} local-address {{ peer.src_addr }}{% endif %}{% if peer.src_if %} interface {{ peer.src_if }}{% endif %} -{% endfor -%} -! -{% for peer in new_peers -%} - peer {{ peer.remote }}{% if peer.multihop %} multihop{% endif %}{% if peer.src_addr %} local-address {{ peer.src_addr }}{% endif %}{% if peer.src_if %} interface {{ peer.src_if }}{% endif %} - detect-multiplier {{ peer.multiplier }} - receive-interval {{ peer.rx_interval }} - transmit-interval {{ peer.tx_interval }} - {% if peer.echo_mode %}echo-mode{% endif %} - {% if peer.echo_interval != '' %}echo-interval {{ peer.echo_interval }}{% endif %} - {% if not peer.shutdown %}no {% endif %}shutdown -{% endfor -%} -! -""" -  default_config_data = {      'new_peers': [],      'old_peers' : [] @@ -132,7 +113,7 @@ def get_bfd_peer_config(peer, conf_mode="proposed"):      return bfd_peer  def get_config(): -    bfd = copy.deepcopy(default_config_data) +    bfd = deepcopy(default_config_data)      conf = Config()      if not (conf.exists('protocols bfd') or conf.exists_effective('protocols bfd')):          return None @@ -164,12 +145,12 @@ def verify(bfd):      for peer in bfd['new_peers']:          # IPv6 link local peers require an explicit local address/interface -        if vyos.validate.is_ipv6_link_local(peer['remote']): +        if is_ipv6_link_local(peer['remote']):              if not (peer['src_if'] and peer['src_addr']):                  raise ConfigError('BFD IPv6 link-local peers require explicit local address and interface setting')          # IPv6 peers require an explicit local address -        if vyos.validate.is_ipv6(peer['remote']): +        if is_ipv6(peer['remote']):              if not peer['src_addr']:                  raise ConfigError('BFD IPv6 peers require explicit local address setting') @@ -208,18 +189,23 @@ 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) +      return None  def apply(bfd):      if bfd is None:          return None -    tmpl = jinja2.Template(config_tmpl) -    config_text = tmpl.render(bfd) -    with open(config_file, 'w') as f: -        f.write(config_text) - -    os.system("sudo vtysh -d bfdd -f " + config_file) +    os.system(f'vtysh -d bfdd -f {config_file}')      if os.path.exists(config_file):          os.remove(config_file) @@ -233,4 +219,4 @@ if __name__ == '__main__':          apply(c)      except ConfigError as e:          print(e) -        sys.exit(1) +        exit(1) diff --git a/src/conf_mode/protocols_igmp.py b/src/conf_mode/protocols_igmp.py index 8d25fe9d5..0148b5dac 100755 --- a/src/conf_mode/protocols_igmp.py +++ b/src/conf_mode/protocols_igmp.py @@ -13,46 +13,19 @@  #  # 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 jinja2 -import copy  import os -import vyos.validate + +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  config_file = r'/tmp/igmp.frr' -# Please be careful if you edit the template. -config_tmpl = """ -! -{% for iface in old_ifaces -%} -interface {{ iface }} -no ip igmp -! -{% endfor -%} -{% for iface in ifaces -%} -interface {{ iface }} -{% if ifaces[iface].version -%} -ip igmp version {{ ifaces[iface].version }} -{% else -%} -{# IGMP default version 3 #} -ip igmp -{% endif -%} -{% if ifaces[iface].query_interval -%} -ip igmp query-interval {{ ifaces[iface].query_interval }} -{% endif -%} -{% if ifaces[iface].query_max_resp_time -%} -ip igmp query-max-response-time {{ ifaces[iface].query_max_resp_time }} -{% endif -%} -! -{% endfor -%} -! -""" -  def get_config():      conf = Config()      igmp_conf = { @@ -74,18 +47,24 @@ def get_config():              iface : {                  'version' : conf.return_effective_value('interface {0} version'.format(iface)),                  'query_interval' : conf.return_effective_value('interface {0} query-interval'.format(iface)), -                'query_max_resp_time' : conf.return_effective_value('interface {0} query-max-response-time'.format(iface)) +                'query_max_resp_time' : conf.return_effective_value('interface {0} query-max-response-time'.format(iface)), +                'gr_join' : {}              }          }) +        for gr_join in conf.list_effective_nodes('interface {0} join'.format(iface)): +            igmp_conf['old_ifaces'][iface]['gr_join'][gr_join] = conf.return_effective_values('interface {0} join {1} source'.format(iface, gr_join))      for iface in conf.list_nodes('interface'):          igmp_conf['ifaces'].update({              iface : {                  'version' : conf.return_value('interface {0} version'.format(iface)),                  'query_interval' : conf.return_value('interface {0} query-interval'.format(iface)), -                'query_max_resp_time' : conf.return_value('interface {0} query-max-response-time'.format(iface)) +                'query_max_resp_time' : conf.return_value('interface {0} query-max-response-time'.format(iface)), +                'gr_join' : {}              }          }) +        for gr_join in conf.list_nodes('interface {0} join'.format(iface)): +            igmp_conf['ifaces'][iface]['gr_join'][gr_join] = conf.return_values('interface {0} join {1} source'.format(iface, gr_join))      return igmp_conf @@ -97,12 +76,22 @@ def verify(igmp):          # Check interfaces          if not igmp['ifaces']:              raise ConfigError(f"IGMP require defined interfaces!") +        # Check, is this multicast group +        for intfc in igmp['ifaces']: +            for gr_addr in igmp['ifaces'][intfc]['gr_join']: +                if IPv4Address(gr_addr) < IPv4Address('224.0.0.0'): +                    raise ConfigError(gr_addr + " not a multicast group")  def generate(igmp):      if igmp is None:          return None -    tmpl = jinja2.Template(config_tmpl) +    # 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) @@ -127,4 +116,4 @@ if __name__ == '__main__':          apply(c)      except ConfigError as e:          print(e) -        sys.exit(1) +        exit(1) diff --git a/src/conf_mode/protocols_mpls.py b/src/conf_mode/protocols_mpls.py index 5f2497660..514fe5efb 100755 --- a/src/conf_mode/protocols_mpls.py +++ b/src/conf_mode/protocols_mpls.py @@ -13,81 +13,17 @@  #  # 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 jinja2 -import copy  import os -import vyos.validate -from vyos import ConfigError +from jinja2 import FileSystemLoader, Environment +  from vyos.config import Config +from vyos.defaults import directories as vyos_data_dir +from vyos import ConfigError  config_file = r'/tmp/ldpd.frr' -# Please be careful if you edit the template. -config_tmpl = """ -! -{% if mpls_ldp -%} -mpls ldp -{% if old_router_id -%} -no router-id {{ old_router_id }} -{% endif -%} -{% if router_id -%} -router-id {{ router_id }} -{% endif -%} -{% for neighbor_id in old_ldp.neighbors -%} -no neighbor {{neighbor_id}} password {{old_ldp.neighbors[neighbor_id].password}} -{% endfor -%} -{% for neighbor_id in ldp.neighbors -%} -neighbor {{neighbor_id}} password {{ldp.neighbors[neighbor_id].password}} -{% endfor -%} -address-family ipv4 -label local allocate host-routes -{% if old_ldp.d_transp_ipv4 -%} -no discovery transport-address {{ old_ldp.d_transp_ipv4 }} -{% endif -%} -{% if ldp.d_transp_ipv4 -%} -discovery transport-address {{ ldp.d_transp_ipv4 }} -{% endif -%} -{% for interface in old_ldp.interfaces -%} -no interface {{interface}} -{% endfor -%} -{% for interface in ldp.interfaces -%} -interface {{interface}} -{% endfor -%} -! -! -exit-address-family -! -{% if ldp.d_transp_ipv6 -%} -address-family ipv6 -label local allocate host-routes -{% if old_ldp.d_transp_ipv6 -%} -no discovery transport-address {{ old_ldp.d_transp_ipv6 }} -{% endif -%} -{% if ldp.d_transp_ipv6 -%} -discovery transport-address {{ ldp.d_transp_ipv6 }} -{% endif -%} -{% for interface in old_ldp.interfaces -%} -no interface {{interface}} -{% endfor -%} -{% for interface in ldp.interfaces -%} -interface {{interface}} -{% endfor -%} -! -exit-address-family -{% else -%} -no address-family ipv6 -{% endif -%} -! -{% else -%} -no mpls ldp -{% endif -%} -! -""" -  def sysctl(name, value):      os.system('sysctl -wq {}={}'.format(name, value)) @@ -159,12 +95,10 @@ def get_config():              }          }) -    # print(mpls_conf) -    # sys.exit(1)      return mpls_conf  def operate_mpls_on_intfc(interfaces, action): -    rp_filter = 0  +    rp_filter = 0      if action == 1:          rp_filter = 2      for iface in interfaces: @@ -193,7 +127,12 @@ def generate(mpls):      if mpls is None:          return None -    tmpl = jinja2.Template(config_tmpl) +    # 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) @@ -234,4 +173,4 @@ if __name__ == '__main__':          apply(c)      except ConfigError as e:          print(e) -        sys.exit(1) +        exit(1) diff --git a/src/conf_mode/protocols_pim.py b/src/conf_mode/protocols_pim.py index dc65a59a6..7b360d62c 100755 --- a/src/conf_mode/protocols_pim.py +++ b/src/conf_mode/protocols_pim.py @@ -13,55 +13,19 @@  #  # 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 jinja2 -import copy  import os -import vyos.validate +  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 import ConfigError  config_file = r'/tmp/pimd.frr' -# Please be careful if you edit the template. -config_tmpl = """ -! -{% for rp_addr in old_pim.rp -%} -{% for group in old_pim.rp[rp_addr] -%} -no ip pim rp {{ rp_addr }} {{ group }} -{% endfor -%} -{% endfor -%} -{% if old_pim.rp_keep_alive -%} -no ip pim rp keep-alive-timer {{ old_pim.rp_keep_alive }} -{% endif -%} -{% for iface in old_pim.ifaces -%} -interface {{ iface }} -no ip pim -! -{% endfor -%} -{% for iface in pim.ifaces -%} -interface {{ iface }} -{% if pim.ifaces[iface].hello -%} -ip pim hello {{ pim.ifaces[iface].hello }} -{% endif -%} -ip pim -! -{% endfor -%} -{% for rp_addr in pim.rp -%} -{% for group in pim.rp[rp_addr] -%} -ip pim rp {{ rp_addr }} {{ group }} -{% endfor -%} -{% endfor -%} -{% if pim.rp_keep_alive -%} -ip pim rp keep-alive-timer {{ pim.rp_keep_alive }} -{% endif -%} -! -""" -  def get_config():      conf = Config()      pim_conf = { @@ -87,14 +51,16 @@ def get_config():      for iface in conf.list_effective_nodes('interface'):          pim_conf['old_pim']['ifaces'].update({              iface : { -                'hello' : conf.return_effective_value('interface {0} hello'.format(iface)) +                'hello' : conf.return_effective_value('interface {0} hello'.format(iface)), +                'dr_prio' : conf.return_effective_value('interface {0} dr-priority'.format(iface))              }          })      for iface in conf.list_nodes('interface'):          pim_conf['pim']['ifaces'].update({              iface : { -                'hello' : conf.return_value('interface {0} hello'.format(iface)) +                'hello' : conf.return_value('interface {0} hello'.format(iface)), +                'dr_prio' : conf.return_value('interface {0} dr-priority'.format(iface)),              }          }) @@ -147,7 +113,12 @@ def generate(pim):      if pim is None:          return None -    tmpl = jinja2.Template(config_tmpl) +    # 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) @@ -159,7 +130,7 @@ def apply(pim):          return None      if os.path.exists(config_file): -        os.system("sudo vtysh -d pimd -f " + config_file) +        os.system("vtysh -d pimd -f " + config_file)          os.remove(config_file)      return None @@ -172,4 +143,4 @@ if __name__ == '__main__':          apply(c)      except ConfigError as e:          print(e) -        sys.exit(1) +        exit(1) diff --git a/src/conf_mode/salt-minion.py b/src/conf_mode/salt-minion.py index 303ddae48..bc1767454 100755 --- a/src/conf_mode/salt-minion.py +++ b/src/conf_mode/salt-minion.py @@ -1,6 +1,6 @@  #!/usr/bin/env python3  # -# Copyright (C) 2018 VyOS maintainers and contributors +# Copyright (C) 2018-2020 VyOS maintainers and contributors  #  # This program is free software; you can redistribute it and/or modify  # it under the terms of the GNU General Public License version 2 or later as @@ -13,102 +13,35 @@  #  # 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 pwd -import socket -import urllib3 -import jinja2 +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  config_file = r'/etc/salt/minion' -# Please be careful if you edit the template. -config_tmpl = """ -### Autogenerated by salt-minion.py ### - -##### Primary configuration settings ##### -########################################## - -# The hash_type is the hash to use when discovering the hash of a file on -# the master server. The default is sha256, but md5, sha1, sha224, sha384 and -# sha512 are also supported. -# -# WARNING: While md5 and sha1 are also supported, do not use them due to the -# high chance of possible collisions and thus security breach. -# -# Prior to changing this value, the master should be stopped and all Salt -# caches should be cleared. -hash_type: {{ hash_type }} - -#####         Logging settings       ##### -########################################## -# The location of the minion log file -# The minion log can be sent to a regular file, local path name, or network -# location. Remote logging works best when configured to use rsyslogd(8) (e.g.: -# ``file:///dev/log``), with rsyslogd(8) configured for network logging. The URI -# format is: <file|udp|tcp>://<host|socketpath>:<port-if-required>/<log-facility> -#log_file: /var/log/salt/minion -#log_file: file:///dev/log -#log_file: udp://loghost:10514 -# -log_file: {{ log_file }} - -# The level of messages to send to the console. -# One of 'garbage', 'trace', 'debug', info', 'warning', 'error', 'critical'. -# -# The following log levels are considered INSECURE and may log sensitive data: -# ['garbage', 'trace', 'debug'] -# -# Default: 'warning' -log_level: {{ log_level }} - -# Set the location of the salt master server, if the master server cannot be -# resolved, then the minion will fail to start. -master: -{% for host in master -%} -- {{ host }} -{% endfor %} - -# The user to run salt -user: {{ user }} - -# The directory to store the pki information in -pki_dir: /config/salt/pki/minion - -# Explicitly declare the id for this minion to use, if left commented the id -# will be the hostname as returned by the python call: socket.getfqdn() -# Since salt uses detached ids it is possible to run multiple minions on the -# same machine but with different ids, this can be useful for salt compute -# clusters. -id: {{ salt_id }} - - -# The number of minutes between mine updates. -mine_interval: {{ mine_interval }} - -verify_master_pubkey_sign: {{ verify_master_pubkey_sign }} -""" -  default_config_data = {      'hash_type': 'sha256',      'log_file': '/var/log/salt/minion',      'log_level': 'warning',      'master' : 'salt',      'user': 'minion', -    'salt_id': socket.gethostname(), +    'salt_id': gethostname(),      'mine_interval': '60',      'verify_master_pubkey_sign': 'false'  }  def get_config(): -    salt = default_config_data +    salt = deepcopy(default_config_data)      conf = Config()      if not conf.exists('service salt-minion'):          return None @@ -145,25 +78,31 @@ def get_config():      return salt  def generate(salt): -    paths = ['/etc/salt/','/var/run/salt','/opt/vyatta/etc/config/salt/']  +    paths = ['/etc/salt/','/var/run/salt','/opt/vyatta/etc/config/salt/']      directory = '/opt/vyatta/etc/config/salt/pki/minion' -    uid = pwd.getpwnam(salt['user']).pw_uid -    http = urllib3.PoolManager() +    uid = getpwnam(salt['user']).pw_uid +    http = PoolManager()      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 = jinja2.Template(config_tmpl) +    tmpl = env.get_template('minion.tmpl')      config_text = tmpl.render(salt)      with open(config_file, 'w') as f:          f.write(config_text) -    path = "/etc/salt/"   + +    path = "/etc/salt/"      for path in paths: -      for root, dirs, files in os.walk(path):   -        for usgr in dirs:   +      for root, dirs, files in os.walk(path): +        for usgr in dirs:            os.chown(os.path.join(root, usgr), uid, 100)          for usgr in files:            os.chown(os.path.join(root, usgr), uid, 100) @@ -171,14 +110,14 @@ def generate(salt):      if not os.path.exists('/opt/vyatta/etc/config/salt/pki/minion/master_sign.pub'):          if not salt['master-key'] is None:              r = http.request('GET', salt['master-key'], preload_content=False) -     +              with open('/opt/vyatta/etc/config/salt/pki/minion/master_sign.pub', 'wb') as out:                  while True:                      data = r.read(1024)                      if not data:                          break                      out.write(data) -     +              r.release_conn()      return None @@ -200,4 +139,4 @@ if __name__ == '__main__':          apply(c)      except ConfigError as e:          print(e) -        sys.exit(1) +        exit(1) diff --git a/src/conf_mode/service-ipoe.py b/src/conf_mode/service-ipoe.py index bd7a898d0..dd9616a62 100755 --- a/src/conf_mode/service-ipoe.py +++ b/src/conf_mode/service-ipoe.py @@ -1,6 +1,6 @@  #!/usr/bin/env python3  # -# Copyright (C) 2018 VyOS maintainers and contributors +# Copyright (C) 2018-2020 VyOS maintainers and contributors  #  # This program is free software; you can redistribute it and/or modify  # it under the terms of the GNU General Public License version 2 or later as @@ -13,19 +13,18 @@  #  # You should have received a copy of the GNU General Public License  # along with this program.  If not, see <http://www.gnu.org/licenses/>. -# -# -import sys  import os  import re -import time -import socket  import subprocess -import jinja2 -import syslog as sl + +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  ipoe_cnf_dir = r'/etc/accel-ppp/ipoe' @@ -37,157 +36,8 @@ cmd_port = r'2002'  chap_secrets = ipoe_cnf_dir + '/chap-secrets'  ## accel-pppd -d -c /etc/accel-ppp/pppoe/pppoe.config -p /var/run/accel_pppoe.pid -ipoe_config = ''' -### generated by ipoe.py ###  -[modules] -log_syslog -ipoe -shaper -ipv6pool -ipv6_nd -ipv6_dhcp -{% if auth['mech'] == 'radius' %} -radius -{% endif -%} -ippool -{% if auth['mech'] == 'local' %} -chap-secrets -{% endif %} - -[core] -thread-count={{thread_cnt}} - -[log] -syslog=accel-ipoe,daemon -copy=1 -level=5 - -[ipoe] -verbose=1 -{% for intfc in interfaces %} -{% if interfaces[intfc]['vlan_mon'] %} -interface=re:{{intfc}}\.\d+,\ -{% else %} -interface={{intfc}},\ -{% endif %} -shared={{interfaces[intfc]['shared']}},\ -mode={{interfaces[intfc]['mode']}},\ -ifcfg={{interfaces[intfc]['ifcfg']}},\ -range={{interfaces[intfc]['range']}},\ -start={{interfaces[intfc]['sess_start']}},\ -ipv6=1 -{% endfor %} -{% if auth['mech'] == 'noauth' %} -noauth=1 -{% endif %} -{% if auth['mech'] == 'local' %} -username=ifname -password=csid -{% endif %} - -{%- for intfc in interfaces %} -{% if (interfaces[intfc]['shared'] == '0') and (interfaces[intfc]['vlan_mon']) %} -vlan-mon={{intfc}},{{interfaces[intfc]['vlan_mon']|join(',')}} -{% endif %} -{% endfor %} - -{% if (dns['server1']) or (dns['server2']) %} -[dns] -{% if dns['server1'] %} -dns1={{dns['server1']}} -{% endif -%} -{% if dns['server2'] %} -dns2={{dns['server2']}} -{% endif -%} -{% endif -%} - -{% if (dnsv6['server1']) or (dnsv6['server2']) or (dnsv6['server3']) %} -[dnsv6] -dns={{dnsv6['server1']}} -dns={{dnsv6['server2']}} -dns={{dnsv6['server3']}} -{% endif %} - -[ipv6-nd] -verbose=1 - -[ipv6-dhcp] -verbose=1 - -{% if ipv6['prfx'] %} -[ipv6-pool] -{% for prfx in ipv6['prfx'] %} -{{prfx}} -{% endfor %} -{% for pd in ipv6['pd'] %} -delegate={{pd}} -{% endfor %} -{% endif %} - -{% if auth['mech'] == 'local' %} -[chap-secrets] -chap-secrets=/etc/accel-ppp/ipoe/chap-secrets  -{% endif %} - -{% if auth['mech'] == 'radius' %} -[radius] -verbose=1 -{% for srv in auth['radius'] %} -server={{srv}},{{auth['radius'][srv]['secret']}},\ -req-limit={{auth['radius'][srv]['req-limit']}},\ -fail-time={{auth['radius'][srv]['fail-time']}} -{% endfor %} -{% if auth['radsettings']['dae-server']['ip-address'] %} -dae-server={{auth['radsettings']['dae-server']['ip-address']}}:\ -{{auth['radsettings']['dae-server']['port']}},\ -{{auth['radsettings']['dae-server']['secret']}} -{% endif -%} -{% if auth['radsettings']['acct-timeout'] %} -acct-timeout={{auth['radsettings']['acct-timeout']}} -{% endif -%} -{% if auth['radsettings']['max-try'] %} -max-try={{auth['radsettings']['max-try']}} -{% endif -%} -{% if auth['radsettings']['timeout'] %} -timeout={{auth['radsettings']['timeout']}} -{% endif -%} -{% if auth['radsettings']['nas-ip-address'] %} -nas-ip-address={{auth['radsettings']['nas-ip-address']}} -{% endif -%} -{% if auth['radsettings']['nas-identifier'] %} -nas-identifier={{auth['radsettings']['nas-identifier']}} -{% endif -%} -{% endif %} - -[cli] -tcp=127.0.0.1:2002 -''' - -# chap secrets -chap_secrets_conf = ''' -# username  server  password  acceptable local IP addresses   shaper -{% for aifc in auth['auth_if'] %} -{% for mac in auth['auth_if'][aifc] %} -{% if (auth['auth_if'][aifc][mac]['up']) and (auth['auth_if'][aifc][mac]['down']) %} -{% if auth['auth_if'][aifc][mac]['vlan'] %} -{{aifc}}.{{auth['auth_if'][aifc][mac]['vlan']}}\t*\t{{mac.lower()}}\t*\t{{auth['auth_if'][aifc][mac]['down']}}/{{auth['auth_if'][aifc][mac]['up']}} -{% else %} -{{aifc}}\t*\t{{mac.lower()}}\t*\t{{auth['auth_if'][aifc][mac]['down']}}/{{auth['auth_if'][aifc][mac]['up']}} -{% endif %} -{% else %} -{% if auth['auth_if'][aifc][mac]['vlan'] %} -{{aifc}}.{{auth['auth_if'][aifc][mac]['vlan']}}\t*\t{{mac.lower()}}\t* -{% else %} -{{aifc}}\t*\t{{mac.lower()}}\t* -{% endif %} -{% endif %} -{% endfor %} -{% endfor %} -''' -  if not os.path.exists(ipoe_cnf_dir):      os.makedirs(ipoe_cnf_dir) -    sl.syslog(sl.LOG_NOTICE, ipoe_cnf_dir + " created")  def _get_cpu(): @@ -201,13 +51,13 @@ def _get_cpu():  def _chk_con():      cnt = 0 -    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +    s = socket(AF_INET, SOCK_STREAM)      while True:          try:              s.connect(("127.0.0.1", int(cmd_port)))              break          except ConnectionRefusedError: -            time.sleep(0.5) +            sleep(0.5)              cnt += 1              if cnt == 100:                  raise("failed to start pppoe server") @@ -224,18 +74,6 @@ def _accel_cmd(cmd=''):      except:          return 1 -# chap_secrets file if auth mode local - - -def _gen_chap_secrets(c): - -    tmpl = jinja2.Template(chap_secrets_conf, trim_blocks=True) -    chap_secrets_txt = tmpl.render(c) -    old_umask = os.umask(0o077) -    open(chap_secrets, 'w').write(chap_secrets_txt) -    os.umask(old_umask) -    sl.syslog(sl.LOG_NOTICE, chap_secrets + ' written') -  ##### Inline functions end #### @@ -388,14 +226,25 @@ 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': -        _gen_chap_secrets(c) - -    tmpl = jinja2.Template(ipoe_config, trim_blocks=True) +        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) +        os.umask(old_umask) + +    tmpl = env.get_template('ipoe.config.tmpl')      config_text = tmpl.render(c) -    open(ipoe_cnf, 'w').write(config_text) +    with open(ipoe_cnf, 'w') as f: +        f.write(config_text)      return c @@ -465,7 +314,6 @@ def apply(c):              raise ConfigError('accel-pppd failed to start')      else:          _accel_cmd('restart') -        sl.syslog(sl.LOG_NOTICE, "reloading config via daemon restart")  if __name__ == '__main__': @@ -476,4 +324,4 @@ if __name__ == '__main__':          apply(c)      except ConfigError as e:          print(e) -        sys.exit(1) +        exit(1) diff --git a/src/conf_mode/service-pppoe.py b/src/conf_mode/service-pppoe.py index 22250d18b..afcc5ba99 100755 --- a/src/conf_mode/service-pppoe.py +++ b/src/conf_mode/service-pppoe.py @@ -1,6 +1,6 @@  #!/usr/bin/env python3  # -# Copyright (C) 2018 VyOS maintainers and contributors +# Copyright (C) 2018-2020 VyOS maintainers and contributors  #  # This program is free software; you can redistribute it and/or modify  # it under the terms of the GNU General Public License version 2 or later as @@ -13,19 +13,18 @@  #  # You should have received a copy of the GNU General Public License  # along with this program.  If not, see <http://www.gnu.org/licenses/>. -# -# -import sys  import os  import re  import subprocess -import jinja2 -import socket -import time -import syslog as sl + +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  pidfile = r'/var/run/accel_pppoe.pid' @@ -38,282 +37,26 @@ pppoe_conf = pppoe_cnf_dir + '/pppoe.config'  # config path creation  if not os.path.exists(pppoe_cnf_dir):      os.makedirs(pppoe_cnf_dir) -    sl.syslog(sl.LOG_NOTICE, pppoe_cnf_dir + " created") - -pppoe_config = ''' -### generated by accel_pppoe.py ### -[modules] -log_syslog -pppoe -{% if authentication['mode'] == 'radius' %} -radius -{% endif %} -ippool -{% if ppp_options['ipv6'] != 'deny' %} -ipv6pool -ipv6_nd -ipv6_dhcp -{% endif %} -chap-secrets -auth_pap -auth_chap_md5 -auth_mschap_v1 -auth_mschap_v2 -#pppd_compat -shaper -{% if snmp == 'enable' or snmp == 'enable-ma' %} -net-snmp -{% endif %} -{% if limits %} -connlimit -{% endif %} - -[core] -thread-count={{thread_cnt}} - -[log] -syslog=accel-pppoe,daemon -copy=1 -level=5 - -{% if snmp == 'enable-ma' %} -[snmp] -master=1 -{% endif -%} - -[client-ip-range] -disable - -{% if ppp_gw %} -[ip-pool] -gw-ip-address={{ppp_gw}} -{% if client_ip_pool %} -{{client_ip_pool}} -{% endif -%} - -{% if client_ip_subnets %} -{% for sn in client_ip_subnets %} -{{sn}} -{% endfor %} -{% endif %} -{% endif -%} - -{% if client_ipv6_pool %} -[ipv6-pool] -{% for prfx in client_ipv6_pool['prefix']: %} -{{prfx}} -{% endfor %} -{% for prfx in client_ipv6_pool['delegate-prefix']: %} -delegate={{prfx}} -{% endfor %} -{% endif %} - -{% if dns %} -[dns] -{% if dns[0] %} -dns1={{dns[0]}} -{% endif -%} -{% if dns[1] %} -dns2={{dns[1]}} -{% endif -%} -{% endif %} - -{% if dnsv6 %} -[ipv6-dns] -{% for srv in dnsv6: %} -{{srv}} -{% endfor %} -{% endif %} - -{% if wins %} -[wins] -{% if wins[0] %} -wins1={{wins[0]}} -{% endif %} -{% if wins[1] %} -wins2={{wins[1]}} -{% endif -%} -{% endif -%} - -{% if authentication['mode'] == 'local' %} -[chap-secrets] -chap-secrets=/etc/accel-ppp/pppoe/chap-secrets -{% endif -%} - -{% if authentication['mode'] == 'radius' %} -[radius] -{% for rsrv in authentication['radiussrv']: %} -server={{rsrv}},{{authentication['radiussrv'][rsrv]['secret']}},\ -req-limit={{authentication['radiussrv'][rsrv]['req-limit']}},\ -fail-time={{authentication['radiussrv'][rsrv]['fail-time']}} -{% endfor %} -{% if authentication['radiusopt']['timeout'] %} -timeout={{authentication['radiusopt']['timeout']}} -{% endif %} -{% if authentication['radiusopt']['acct-timeout'] %} -acct-timeout={{authentication['radiusopt']['acct-timeout']}} -{% endif %} -{% if authentication['radiusopt']['max-try'] %} -max-try={{authentication['radiusopt']['max-try']}} -{% endif %} -{% if authentication['radiusopt']['nas-id'] %} -nas-identifier={{authentication['radiusopt']['nas-id']}} -{% endif %} -{% if authentication['radiusopt']['nas-ip'] %} -nas-ip-address={{authentication['radiusopt']['nas-ip']}} -{% endif -%} -{% if authentication['radiusopt']['dae-srv'] %} -dae-server={{authentication['radiusopt']['dae-srv']['ip-addr']}}:\ -{{authentication['radiusopt']['dae-srv']['port']}},\ -{{authentication['radiusopt']['dae-srv']['secret']}} -{% endif -%} -gw-ip-address={{ppp_gw}} -verbose=1 - -{% if authentication['radiusopt']['shaper'] %} -[shaper] -verbose=1 -attr={{authentication['radiusopt']['shaper']['attr']}} -{% if authentication['radiusopt']['shaper']['vendor'] %} -vendor={{authentication['radiusopt']['shaper']['vendor']}} -{% endif -%} -{% endif -%} -{% endif %} - -[ppp] -verbose=1 -check-ip=1 -{% if not sesscrtl == 'disable' %} -single-session={{sesscrtl}} -{% endif -%} -{% if ppp_options['ccp'] %} -ccp=1 -{% endif %} -{% if ppp_options['min-mtu'] %} -min-mtu={{ppp_options['min-mtu']}} -{% else %} -min-mtu={{mtu}} -{% endif %} -{% if ppp_options['mru'] %} -mru={{ppp_options['mru']}} -{% endif %} -{% if ppp_options['mppe'] %} -mppe={{ppp_options['mppe']}} -{% else %} -mppe=prefer -{% endif %} -{% if ppp_options['lcp-echo-interval'] %} -lcp-echo-interval={{ppp_options['lcp-echo-interval']}} -{% else %} -lcp-echo-interval=30 -{% endif %} -{% if ppp_options['lcp-echo-timeout'] %} -lcp-echo-timeout={{ppp_options['lcp-echo-timeout']}} -{% endif %} -{% if ppp_options['lcp-echo-failure'] %} -lcp-echo-failure={{ppp_options['lcp-echo-failure']}} -{% else %} -lcp-echo-failure=3 -{% endif %} -{% if ppp_options['ipv4'] %} -ipv4={{ppp_options['ipv4']}} -{% endif %} -{% if client_ipv6_pool %} -ipv6=allow -{% endif %} - -{% if ppp_options['ipv6'] %} -ipv6={{ppp_options['ipv6']}} -{% if ppp_options['ipv6-intf-id'] %} -ipv6-intf-id={{ppp_options['ipv6-intf-id']}} -{% endif %} -{% if ppp_options['ipv6-peer-intf-id'] %} -ipv6-peer-intf-id={{ppp_options['ipv6-peer-intf-id']}} -{% endif %} -{% if ppp_options['ipv6-accept-peer-intf-id'] %} -ipv6-accept-peer-intf-id={{ppp_options['ipv6-accept-peer-intf-id']}} -{% endif %} -{% endif %} -mtu={{mtu}} - -[pppoe] -verbose=1 -{% if concentrator %} -ac-name={{concentrator}} -{% endif %} -{% if interface %} -{% for int in interface %} -interface={{int}} -{% if interface[int]['vlans'] %} -vlan-mon={{int}},{{interface[int]['vlans']|join(',')}} -interface=re:{{int}}\.\d+ -{% endif %} -{% endfor -%} -{% endif -%} - -{% if svc_name %} -service-name={{svc_name|join(',')}} -{% endif -%} - -{% if pado_delay %} -pado-delay={{pado_delay}} -{% endif %} - -{% if limits %} -[connlimit] -limit={{limits['conn-limit']}} -burst={{limits['burst']}} -timeout={{limits['timeout']}} -{% endif %} - -[cli] -tcp=127.0.0.1:2001 -''' - -# pppoe chap secrets -chap_secrets_conf = ''' -# username  server  password  acceptable local IP addresses   shaper -{% for user in authentication['local-users'] %} -{% if authentication['local-users'][user]['state'] == 'enabled' %} -{% if (authentication['local-users'][user]['upload']) and (authentication['local-users'][user]['download']) %} -{{user}}\t*\t{{authentication['local-users'][user]['passwd']}}\t{{authentication['local-users'][user]['ip']}}\t\ -{{authentication['local-users'][user]['download']}}/{{authentication['local-users'][user]['upload']}} -{% else %} -{{user}}\t*\t{{authentication['local-users'][user]['passwd']}}\t{{authentication['local-users'][user]['ip']}} -{% endif %} -{% endif %} -{% endfor %} -''' +  #  # 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) +    s = socket(AF_INET, SOCK_STREAM)      while True:          try:              s.connect(("127.0.0.1", 2001))              break          except ConnectionRefusedError: -            time.sleep(0.5) +            sleep(0.5)              cnt += 1              if cnt == 100:                  raise("failed to start pppoe server") -def _write_chap_secrets(c): -    tmpl = jinja2.Template(chap_secrets_conf, trim_blocks=True) -    chap_secrets_txt = tmpl.render(c) -    old_umask = os.umask(0o077) -    open(chap_secrets, 'w').write(chap_secrets_txt) -    os.umask(old_umask) -    sl.syslog(sl.LOG_NOTICE, chap_secrets + ' written') - -  def _accel_cmd(cmd=''):      if not cmd:          return None @@ -640,6 +383,11 @@ 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: @@ -653,12 +401,18 @@ def generate(c):          else:              c['thread_cnt'] = int(os.cpu_count() / 2) -    tmpl = jinja2.Template(pppoe_config, trim_blocks=True) +    tmpl = env.get_template('pppoe.config.tmpl')      config_text = tmpl.render(c) -    open(pppoe_conf, 'w').write(config_text) +    with open(pppoe_conf, 'w') as f: +        f.write(config_text)      if c['authentication']['local-users']: -        _write_chap_secrets(c) +        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) +        os.umask(old_umask)      return c @@ -680,7 +434,6 @@ def apply(c):              raise ConfigError('accel-pppd failed to start')      else:          _accel_cmd('restart') -        sl.syslog(sl.LOG_NOTICE, "reloading config via daemon restart")  if __name__ == '__main__': @@ -691,4 +444,4 @@ if __name__ == '__main__':          apply(c)      except ConfigError as e:          print(e) -        sys.exit(1) +        exit(1) diff --git a/src/conf_mode/service-router-advert.py b/src/conf_mode/service-router-advert.py index 1e0d28397..38c5cb2dc 100755 --- a/src/conf_mode/service-router-advert.py +++ b/src/conf_mode/service-router-advert.py @@ -15,55 +15,17 @@  # along with this program.  If not, see <http://www.gnu.org/licenses/>.  import os -import sys -import jinja2 +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  config_file = r'/etc/radvd.conf' -config_tmpl = """ -### Autogenerated by service-router-advert.py ### - -{% for i in interfaces -%} -interface {{ i.name }} { -    IgnoreIfMissing on; -    AdvDefaultPreference {{ i.default_preference }}; -    AdvManagedFlag {{ i.managed_flag }}; -    MaxRtrAdvInterval {{ i.interval_max }}; -{% if i.interval_min %} -    MinRtrAdvInterval {{ i.interval_min }}; -{% endif %} -    AdvReachableTime {{ i.reachable_time }}; -    AdvIntervalOpt {{ i.send_advert }}; -    AdvSendAdvert {{ i.send_advert }}; -{% if i.default_lifetime %} -    AdvDefaultLifetime {{ i.default_lifetime }}; -{% endif %} -{% if i.link_mtu %} -    AdvLinkMTU {{ i.link_mtu }}; -{% endif %} -    AdvOtherConfigFlag {{ i.other_config_flag }}; -    AdvRetransTimer {{ i.retrans_timer }}; -    AdvCurHopLimit {{ i.hop_limit }}; -{% for p in i.prefixes %} -    prefix {{ p.prefix }} { -        AdvAutonomous {{ p.autonomous_flag }}; -        AdvValidLifetime {{ p.valid_lifetime }}; -        AdvOnLink {{ p.on_link }}; -        AdvPreferredLifetime {{ p.preferred_lifetime }}; -    }; -{% endfor %} -{% if i.name_server %} -    RDNSS {{ i.name_server | join(" ") }} { -    }; -{% endif %} -}; -{% endfor -%} -""" -  default_config_data = {      'interfaces': []  } @@ -175,7 +137,12 @@ def generate(rtradv):      if not rtradv['interfaces']:          return None -    tmpl = jinja2.Template(config_tmpl, trim_blocks=True) +    # 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) @@ -189,13 +156,13 @@ def generate(rtradv):  def apply(rtradv):      if not rtradv['interfaces']:          # bail out early - looks like removal from running config -        os.system('sudo systemctl stop radvd.service') +        os.system('systemctl stop radvd.service')          if os.path.exists(config_file):              os.unlink(config_file)          return None -    os.system('sudo systemctl restart radvd.service') +    os.system('systemctl restart radvd.service')      return None  if __name__ == '__main__': @@ -206,4 +173,4 @@ if __name__ == '__main__':          apply(c)      except ConfigError as e:          print(e) -        sys.exit(1) +        exit(1) diff --git a/src/conf_mode/snmp.py b/src/conf_mode/snmp.py index 34832aac1..ed8c1d7e1 100755 --- a/src/conf_mode/snmp.py +++ b/src/conf_mode/snmp.py @@ -15,16 +15,17 @@  # along with this program.  If not, see <http://www.gnu.org/licenses/>.  import os -import jinja2  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.config import Config  from vyos import ConfigError  config_file_client  = r'/etc/snmp/snmp.conf' @@ -42,168 +43,6 @@ OIDs = {      'none': '.1.3.6.1.6.3.10.1.2.1'  } -# SNMP template (/etc/snmp/snmp.conf) - be careful if you edit the template. -client_config_tmpl = """ -### Autogenerated by snmp.py ### -{% if trap_source -%} -clientaddr {{ trap_source }} -{% endif %} - -""" - -# SNMP template (/usr/share/snmp/snmpd.conf) - be careful if you edit the template. -access_config_tmpl = """ -### Autogenerated by snmp.py ### -{%- for u in v3_users %} -{{ u.mode }}user {{ u.name }} -{%- endfor %} - -rwuser {{ vyos_user }} - -""" - -# SNMP template (/var/lib/snmp/snmpd.conf) - be careful if you edit the template. -user_config_tmpl = """ -### Autogenerated by snmp.py ### -# user -{%- for u in v3_users %} -{%- if u.authOID == 'none' %} -createUser {{ u.name }} -{%- elif u.authPassword %} -createUser {{ u.name }} {{ u.authProtocol | upper }} "{{ u.authPassword }}" {{ u.privProtocol | upper }} {{ u.privPassword }} -{%- else %} -usmUser 1 3 {{ v3_engineid }} "{{ u.name }}" "{{ u.name }}" NULL {{ u.authOID }} {{ u.authMasterKey }} {{ u.privOID }} {{ u.privMasterKey }} 0x -{%- endif %} -{%- endfor %} - -createUser {{ vyos_user }} MD5 "{{ vyos_user_pass }}" DES -{%- if v3_engineid %} -oldEngineID {{ v3_engineid }} -{%- endif %} -""" - -# SNMP template (/etc/snmp/snmpd.conf) - be careful if you edit the template. -daemon_config_tmpl = """ -### Autogenerated by snmp.py ### - -# non configurable defaults -sysObjectID 1.3.6.1.4.1.44641 -sysServices 14 -master agentx -agentXPerms 0777 0777 -pass .1.3.6.1.2.1.31.1.1.1.18 /opt/vyatta/sbin/if-mib-alias -smuxpeer .1.3.6.1.2.1.83 -smuxpeer .1.3.6.1.2.1.157 -smuxsocket localhost - -# linkUp/Down configure the Event MIB tables to monitor -# the ifTable for network interfaces being taken up or down -# for making internal queries to retrieve any necessary information -iquerySecName {{ vyos_user }} - -# Modified from the default linkUpDownNotification -# to include more OIDs and poll more frequently -notificationEvent  linkUpTrap    linkUp   ifIndex ifDescr ifType ifAdminStatus ifOperStatus -notificationEvent  linkDownTrap  linkDown ifIndex ifDescr ifType ifAdminStatus ifOperStatus -monitor  -r 10 -e linkUpTrap   "Generate linkUp" ifOperStatus != 2 -monitor  -r 10 -e linkDownTrap "Generate linkDown" ifOperStatus == 2 - -######################## -# configurable section # -######################## - -# Default system description is VyOS version -sysDescr VyOS {{ version }} - -{% if description %} -# Description -SysDescr {{ description }} -{%- endif %} - -# Listen -agentaddress unix:/run/snmpd.socket{% if listen_on %}{% for li in listen_on %},{{ li }}{% endfor %}{% else %},udp:161{% if ipv6_enabled %},udp6:161{% endif %}{% endif %} - -# SNMP communities -{%- for c in communities %} - -{%- if c.network_v4 %} -{%- for network in c.network_v4 %} -{{ c.authorization }}community {{ c.name }} {{ network }} -{%- endfor %} -{%- elif not c.has_source %} -{{ c.authorization }}community {{ c.name }} -{%- endif %} - -{%- if c.network_v6 %} -{%- for network in c.network_v6 %} -{{ c.authorization }}community6 {{ c.name }} {{ network }} -{%- endfor %} -{%- elif not c.has_source %} -{{ c.authorization }}community6 {{ c.name }} -{%- endif %} - -{%- endfor %} - -{% if contact %} -# system contact information -SysContact {{ contact }} -{%- endif %} - -{% if location %} -# system location information -SysLocation {{ location }} -{%- endif %} - -{% if smux_peers -%} -# additional smux peers -{%- for sp in smux_peers %} -smuxpeer {{ sp }} -{%- endfor %} -{%- endif %} - -{% if trap_targets -%} -# if there is a problem - tell someone! -{%- for t in trap_targets %} -trap2sink {{ t.target }}{% if t.port -%}:{{ t.port }}{% endif %} {{ t.community }} -{%- endfor %} -{%- endif %} - -{%- if v3_enabled %} -# -# SNMPv3 stuff goes here -# -# views -{%- for v in v3_views %} -{%- for oid in v.oids %} -view {{ v.name }} included .{{ oid.oid }} -{%- endfor %} -{%- endfor %} - -# access -#             context sec.model sec.level match  read    write  notif -{%- for g in v3_groups %} -access {{ g.name }} "" usm {{ g.seclevel }} exact {{ g.view }} {% if g.mode == 'ro' %}none{% else %}{{ g.view }}{% endif %} none -{%- endfor %} - -# trap-target -{%- for t in v3_traps %} -trapsess -v 3 {{ '-Ci' if t.type == 'inform' }} -e {{ v3_engineid }} -u {{ t.secName }} -l {{ t.secLevel }} -a {{ t.authProtocol }} {% if t.authPassword %}-A {{ t.authPassword }}{% elif t.authMasterKey %}-3m {{ t.authMasterKey }}{% endif %} -x {{ t.privProtocol }} {% if t.privPassword %}-X {{ t.privPassword }}{% elif t.privMasterKey %}-3M {{ t.privMasterKey }}{% endif %} {{ t.ipProto }}:{{ t.ipAddr }}:{{ t.ipPort }} -{%- endfor %} - -# group -{%- for u in v3_users %} -group {{ u.group }} usm {{ u.name }} -{% endfor %} -{%- endif %} - -{% if script_ext %} -# extension scripts -{%- for ext in script_ext|sort(attribute='name') %} -extend {{ ext.name }} {{ ext.script }} -{%- endfor %} -{% endif %} -""" -  default_config_data = {      'listen_on': [],      'listen_address': [], @@ -669,34 +508,39 @@ def generate(snmp):      # As we are manipulating the snmpd user database we have to stop it first!      # This is even save if service is going to be removed      os.system("systemctl stop snmpd.service") -    rmfile(config_file_client) -    rmfile(config_file_daemon) -    rmfile(config_file_access) -    rmfile(config_file_user) +    config_files = [config_file_client, config_file_daemon, config_file_access, +                    config_file_user] +    for file in config_files: +        rmfile(file)      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 = jinja2.Template(client_config_tmpl) +    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)      # Write server config file -    tmpl = jinja2.Template(daemon_config_tmpl) +    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)      # Write access rights config file -    tmpl = jinja2.Template(access_config_tmpl) +    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)      # Write access rights config file -    tmpl = jinja2.Template(user_config_tmpl) +    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) diff --git a/src/conf_mode/ssh.py b/src/conf_mode/ssh.py index 9fe22bfee..014045796 100755 --- a/src/conf_mode/ssh.py +++ b/src/conf_mode/ssh.py @@ -1,6 +1,6 @@  #!/usr/bin/env python3  # -# Copyright (C) 2018 VyOS maintainers and contributors +# Copyright (C) 2018-2020 VyOS maintainers and contributors  #  # This program is free software; you can redistribute it and/or modify  # it under the terms of the GNU General Public License version 2 or later as @@ -13,149 +13,17 @@  #  # You should have received a copy of the GNU General Public License  # along with this program.  If not, see <http://www.gnu.org/licenses/>. -# -# -import sys  import os - -import jinja2 +from 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  config_file = r'/etc/ssh/sshd_config' -# Please be careful if you edit the template. -config_tmpl = """ - -### Autogenerated by ssh.py ### - -# Non-configurable defaults -Protocol 2 -HostKey /etc/ssh/ssh_host_rsa_key -HostKey /etc/ssh/ssh_host_dsa_key -HostKey /etc/ssh/ssh_host_ecdsa_key -HostKey /etc/ssh/ssh_host_ed25519_key -SyslogFacility AUTH -LoginGraceTime 120 -StrictModes yes -PubkeyAuthentication yes -IgnoreRhosts yes -HostbasedAuthentication no -PermitEmptyPasswords no -ChallengeResponseAuthentication no -X11Forwarding yes -X11DisplayOffset 10 -PrintMotd no -PrintLastLog yes -TCPKeepAlive yes -Banner /etc/issue.net -Subsystem sftp /usr/lib/openssh/sftp-server -UsePAM yes -HostKey /etc/ssh/ssh_host_rsa_key - -# Specifies whether sshd should look up the remote host name, -# and to check that the resolved host name for the remote IP -# address maps back to the very same IP address. -UseDNS {{ host_validation }} - -# Specifies the port number that sshd listens on.  The default is 22. -# Multiple options of this type are permitted. -{% if mport|length != 0 %} -{% for p in mport %} -Port {{ p }} -{% endfor %} -{% else %} -Port {{ port }} -{% endif %} - -# Gives the verbosity level that is used when logging messages from sshd -LogLevel {{ log_level }} - -# Specifies whether root can log in using ssh -PermitRootLogin no - -# Specifies whether password authentication is allowed -PasswordAuthentication {{ password_authentication }} - -{% if listen_on %} -# Specifies the local addresses sshd should listen on -{% for a in listen_on %} -ListenAddress {{ a }} -{% endfor %} -{{ "\n" }} -{% endif %} - -{%- if ciphers %} -# Specifies the ciphers allowed. Multiple ciphers must be comma-separated. -# -# NOTE: As of now, there is no 'multi' node for 'ciphers', thus we have only one :/ -Ciphers {{ ciphers | join(",") }} -{{ "\n" }} -{% endif %} - -{%- if mac %} -# Specifies the available MAC (message authentication code) algorithms. The MAC -# algorithm is used for data integrity protection. Multiple algorithms must be -# comma-separated. -# -# NOTE: As of now, there is no 'multi' node for 'mac', thus we have only one :/ -MACs {{ mac | join(",") }} -{{ "\n" }} -{% endif %} - -{%- if key_exchange %} -# Specifies the available KEX (Key Exchange) algorithms. Multiple algorithms must -# be comma-separated. -# -# NOTE: As of now, there is no 'multi' node for 'key-exchange', thus we have only one :/ -KexAlgorithms {{ key_exchange | join(",") }} -{{ "\n" }} -{% endif %} - -{%- if allow_users %} -# This keyword can be followed by a list of user name patterns, separated by spaces. -# If specified, login is allowed only for user names that match one of the patterns.  -# Only user names are valid, a numerical user ID is not recognized. -AllowUsers {{ allow_users | join(" ") }} -{{ "\n" }} -{% endif %} - -{%- if allow_groups %} -# This keyword can be followed by a list of group name patterns, separated by spaces. -# If specified, login is allowed only for users whose primary group or supplementary -# group list matches one of the patterns. Only group names are valid, a numerical group -# ID is not recognized. -AllowGroups {{ allow_groups | join(" ") }} -{{ "\n" }} -{% endif %} - -{%- if deny_users %} -# This keyword can be followed by a list of user name patterns, separated by spaces. -# Login is disallowed for user names that match one of the patterns. Only user names -# are valid, a numerical user ID is not	recognized. -DenyUsers {{ deny_users | join(" ") }} -{{ "\n" }} -{% endif %} - -{%- if deny_groups %} -# This keyword can be followed by a list of group name patterns, separated by spaces. -# Login is disallowed for users whose primary group or supplementary group list matches -# one of the patterns. Only group names are valid, a numerical group ID is not recognized. -DenyGroups {{ deny_groups | join(" ") }} -{{ "\n" }} -{% endif %} - -{%- if client_keepalive %} -# Sets a timeout interval in seconds after which if no data has been received from the client, -# sshd will send a message through the encrypted channel to request a response from the client. -# The default is 0, indicating that these messages will not be sent to the client. -# This option applies to protocol version 2 only.  -ClientAliveInterval {{ client_keepalive }} -{% endif %} -""" -  default_config_data = {      'port' : '22',      'log_level': 'INFO', @@ -250,7 +118,12 @@ def generate(ssh):      if ssh is None:          return None -    tmpl = jinja2.Template(config_tmpl, trim_blocks=True) +    # 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) @@ -275,4 +148,4 @@ if __name__ == '__main__':          apply(c)      except ConfigError as e:          print(e) -        sys.exit(1) +        exit(1) diff --git a/src/conf_mode/system-login.py b/src/conf_mode/system-login.py index 959e86e5b..7acb0a9a2 100755 --- a/src/conf_mode/system-login.py +++ b/src/conf_mode/system-login.py @@ -14,36 +14,21 @@  # You should have received a copy of the GNU General Public License  # along with this program.  If not, see <http://www.gnu.org/licenses/>. -import sys  import os -import jinja2 +from 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  from subprocess import Popen, PIPE, STDOUT -from psutil import users +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  radius_config_file = "/etc/pam_radius_auth.conf" -radius_config_tmpl = """ -# Automatically generated by VyOS -# RADIUS configuration file -{%- if radius_server %} -# server[:port]         shared_secret                           timeout (s)     source_ip -{% for s in radius_server %} -{%- if not s.disabled -%} -{{ s.address }}:{{ s.port }} {{ s.key }} {{ s.timeout }} {% if radius_source_address -%}{{ radius_source_address }}{% endif %} -{% endif %} -{%- endfor %} - -priv-lvl 15 -mapped_priv_user radius_priv_user -{% endif %} - -"""  default_config_data = {      'deleted': False, @@ -229,7 +214,12 @@ def generate(login):              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: -        tmpl = jinja2.Template(radius_config_tmpl) +        # 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) + +        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) @@ -364,4 +354,4 @@ if __name__ == '__main__':          apply(c)      except ConfigError as e:          print(e) -        sys.exit(1) +        exit(1) diff --git a/src/conf_mode/system-syslog.py b/src/conf_mode/system-syslog.py index 2d47cc061..8c0a6629c 100755 --- a/src/conf_mode/system-syslog.py +++ b/src/conf_mode/system-syslog.py @@ -1,6 +1,6 @@  #!/usr/bin/env python3  # -# Copyright (C) 2018 VyOS maintainers and contributors +# Copyright (C) 2018-2020 VyOS maintainers and contributors  #  # This program is free software; you can redistribute it and/or modify  # it under the terms of the GNU General Public License version 2 or later as @@ -13,85 +13,18 @@  #  # You should have received a copy of the GNU General Public License  # along with this program.  If not, see <http://www.gnu.org/licenses/>. -# -# -import sys  import os  import re -import subprocess -import jinja2 + +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.util import subprocess_cmd  from vyos import ConfigError -# config templates - -# /etc/rsyslog.d/vyos-rsyslog.conf ### -configs = ''' -## generated by syslog.py ## -## file based logging -{% if files['global']['marker'] -%} -$ModLoad immark -{% if files['global']['marker-interval'] %} -$MarkMessagePeriod  {{files['global']['marker-interval']}} -{% endif %} -{% endif -%} -{% if files['global']['preserver_fqdn'] -%} -$PreserveFQDN on -{% endif -%} -{% for file in files %} -$outchannel {{file}},{{files[file]['log-file']}},{{files[file]['max-size']}},{{files[file]['action-on-max-size']}} -{{files[file]['selectors']}} :omfile:${{file}} -{% endfor %} -{% if console %} -## console logging -{% for con in console %} -{{console[con]['selectors']}} /dev/console -{% endfor %} -{% endif %} -{% if hosts %} -## remote logging -{% for host in hosts %} -{% if hosts[host]['proto'] == 'tcp' %} -{% if hosts[host]['port'] %} -{{hosts[host]['selectors']}} @@{{host}}:{{hosts[host]['port']}} -{% else %} -{{hosts[host]['selectors']}} @@{{host}} -{% endif %} -{% else %} -{% if hosts[host]['port'] %} -{{hosts[host]['selectors']}} @{{host}}:{{hosts[host]['port']}} -{% else %} -{{hosts[host]['selectors']}} @{{host}} -{% endif %} -{% endif %} -{% endfor %} -{% endif %} -{% if user %} -{% for u in user %} -{{user[u]['selectors']}} :omusrmsg:{{u}} -{% endfor %} -{% endif %} -''' - -logrotate_configs = ''' -{% for file in files %} -{{files[file]['log-file']}} { -  missingok -  notifempty -  create -  rotate {{files[file]['max-files']}} -  size={{files[file]['max-size']//1024}}k -  postrotate -    invoke-rc.d rsyslog rotate > /dev/null -  endscript -} -{% endfor %} -''' -# config templates end - -  def get_config():      c = Config()      if not c.exists('system syslog'): @@ -259,14 +192,19 @@ def generate(c):      if c == None:          return None -    tmpl = jinja2.Template(configs, trim_blocks=True) +    # 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)      # eventually write for each file its own logrotate file, since size is      # defined it shouldn't matter -    tmpl = jinja2.Template(logrotate_configs, trim_blocks=True) +    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) @@ -315,10 +253,10 @@ def verify(c):  def apply(c):      if not c: -        subprocess.call(['sudo', 'systemctl', 'stop', 'syslog']) -        return 0 +        subprocess_cmd('systemctl stop syslog') +        return None -    subprocess.call(['sudo', 'systemctl', 'restart', 'syslog']) +    subprocess_cmd('systemctl restart syslog')  if __name__ == '__main__':      try: @@ -328,4 +266,4 @@ if __name__ == '__main__':          apply(c)      except ConfigError as e:          print(e) -        sys.exit(1) +        exit(1) diff --git a/src/conf_mode/system-wifi-regdom.py b/src/conf_mode/system-wifi-regdom.py index 01dc92a20..943c42274 100755 --- a/src/conf_mode/system-wifi-regdom.py +++ b/src/conf_mode/system-wifi-regdom.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 @@ -15,52 +15,34 @@  # along with this program.  If not, see <http://www.gnu.org/licenses/>.  import os -import jinja2  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  config_80211_file='/etc/modprobe.d/cfg80211.conf'  config_crda_file='/etc/default/crda' -# Please be careful if you edit the template. -config_80211_tmpl = """ -{%- if regdom -%} -options cfg80211 ieee80211_regdom={{ regdom }} -{% endif %} -""" - -# Please be careful if you edit the template. -config_crda_tmpl = """ -{%- if regdom -%} -REGDOMAIN={{ regdom }} -{% endif %} -""" -  default_config_data = {      'regdom' : '',      'deleted' : False  } -  def get_config():      regdom = deepcopy(default_config_data)      conf = Config() - -    # set new configuration level -    conf.set_level('system') +    base = ['system', 'wifi-regulatory-domain']      # Check if interface has been removed -    if not conf.exists('wifi-regulatory-domain'): +    if not conf.exists(base):          regdom['deleted'] = True          return regdom - -    # retrieve configured regulatory domain -    if conf.exists('wifi-regulatory-domain'): -        regdom['regdom'] = conf.return_value('wifi-regulatory-domain') +    else: +        regdom['regdom'] = conf.return_value(base)      return regdom @@ -85,12 +67,17 @@ def generate(regdom):          return None -    tmpl = jinja2.Template(config_80211_tmpl) +    # 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 = jinja2.Template(config_crda_tmpl) +    tmpl = env.get_template('crda.tmpl')      config_text = tmpl.render(regdom)      with open(config_crda_file, 'w') as f:          f.write(config_text) diff --git a/src/conf_mode/tftp_server.py b/src/conf_mode/tftp_server.py index ff7cad0c9..fe2da8455 100755 --- a/src/conf_mode/tftp_server.py +++ b/src/conf_mode/tftp_server.py @@ -1,6 +1,6 @@  #!/usr/bin/env python3  # -# Copyright (C) 2018 VyOS maintainers and contributors +# Copyright (C) 2018-2020 VyOS maintainers and contributors  #  # This program is free software; you can redistribute it and/or modify  # it under the terms of the GNU General Public License version 2 or later as @@ -13,32 +13,23 @@  #  # 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 stat  import pwd -import copy -import glob -import jinja2 -import vyos.validate +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  config_file = r'/etc/default/tftpd' -# Please be careful if you edit the template. -config_tmpl = """ -### Autogenerated by tftp_server.py ### -DAEMON_ARGS="--listen --user tftp --address {% for a in listen-%}{{ a }}{% endfor %}{% if allow_upload %} --create --umask 000{% endif %} --secure {{ directory }}" - - -""" -  default_config_data = {      'directory': '',      'allow_upload': False, @@ -47,23 +38,25 @@ default_config_data = {  }  def get_config(): -    tftpd = copy.deepcopy(default_config_data) +    tftpd = deepcopy(default_config_data)      conf = Config() -    if not conf.exists('service tftp-server'): +    base = ['service', 'tftp-server'] +    if not conf.exists(base):          return None      else: -        conf.set_level('service tftp-server') +        conf.set_level(base) -    if conf.exists('directory'): -        tftpd['directory'] = conf.return_value('directory') +    if conf.exists(['directory']): +        tftpd['directory'] = conf.return_value(['directory']) -    if conf.exists('allow-upload'): +    if conf.exists(['allow-upload']):          tftpd['allow_upload'] = True -    if conf.exists('port'): -        tftpd['port'] = conf.return_value('port') +    if conf.exists(['port']): +        tftpd['port'] = conf.return_value(['port']) -    tftpd['listen'] = conf.return_values('listen-address') +    if conf.exists(['listen-address']): +        tftpd['listen'] = conf.return_values(['listen-address'])      return tftpd @@ -80,7 +73,7 @@ def verify(tftpd):          raise ConfigError('TFTP server listen address must be configured!')      for addr in tftpd['listen']: -        if not vyos.validate.is_addr_assigned(addr): +        if not is_addr_assigned(addr):              print('WARNING: TFTP server listen address {0} not assigned to any interface!'.format(addr))      return None @@ -88,22 +81,27 @@ def verify(tftpd):  def generate(tftpd):      # cleanup any available configuration file      # files will be recreated on demand -    for i in glob.glob(config_file + '*'): +    for i in glob(config_file + '*'):          os.unlink(i)      # bail out early - looks like removal from running config      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 = copy.deepcopy(tftpd) -        if vyos.validate.is_ipv4(listen): +        config = deepcopy(tftpd) +        if is_ipv4(listen):              config['listen'] = [listen + ":" + tftpd['port'] + " -4"]          else:              config['listen'] = ["[" + listen + "]" + tftpd['port'] + " -6"] -        tmpl = jinja2.Template(config_tmpl) +        tmpl = env.get_template('default.tmpl')          config_text = tmpl.render(config)          file = config_file + str(idx)          with open(file, 'w') as f: @@ -115,7 +113,7 @@ def generate(tftpd):  def apply(tftpd):      # stop all services first - then we will decide -    os.system('sudo systemctl stop tftpd@{0..20}') +    os.system('systemctl stop tftpd@{0..20}')      # bail out early - e.g. service deletion      if tftpd is None: @@ -140,7 +138,7 @@ def apply(tftpd):      idx = 0      for listen in tftpd['listen']: -        os.system('sudo systemctl restart tftpd@{0}.service'.format(idx)) +        os.system('systemctl restart tftpd@{0}.service'.format(idx))          idx = idx + 1      return None @@ -153,4 +151,4 @@ if __name__ == '__main__':          apply(c)      except ConfigError as e:          print(e) -        sys.exit(1) +        exit(1) diff --git a/src/conf_mode/vpn-pptp.py b/src/conf_mode/vpn-pptp.py index 355adf715..b1204a505 100755 --- a/src/conf_mode/vpn-pptp.py +++ b/src/conf_mode/vpn-pptp.py @@ -1,6 +1,6 @@  #!/usr/bin/env python3  # -# Copyright (C) 2018 VyOS maintainers and contributors +# Copyright (C) 2018-2020 VyOS maintainers and contributors  #  # This program is free software; you can redistribute it and/or modify  # it under the terms of the GNU General Public License version 2 or later as @@ -13,19 +13,18 @@  #  # You should have received a copy of the GNU General Public License  # along with this program.  If not, see <http://www.gnu.org/licenses/>. -# -# -import sys  import os  import re  import subprocess -import jinja2 -import socket -import time -import syslog as sl + +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  pidfile = r'/var/run/accel_pptp.pid' @@ -36,117 +35,16 @@ pptp_conf = pptp_cnf_dir + '/pptp.config'  # config path creation  if not os.path.exists(pptp_cnf_dir):      os.makedirs(pptp_cnf_dir) -    sl.syslog(sl.LOG_NOTICE, pptp_cnf_dir + " created") - -pptp_config = ''' -### generated by accel_pptp.py ### -[modules] -log_syslog -pptp -ippool -chap-secrets -{% if authentication['auth_proto'] %} -{{ authentication['auth_proto'] }} -{% else %} -auth_mschap_v2 -{% endif %} -{% if authentication['mode'] == 'radius' %} -radius -{% endif -%} - -[core] -thread-count={{thread_cnt}} - -[log] -syslog=accel-pptp,daemon -copy=1 -level=5 - -{% if dns %} -[dns] -{% if dns[0] %} -dns1={{dns[0]}} -{% endif %} -{% if dns[1] %} -dns2={{dns[1]}} -{% endif %} -{% endif %} - -{% if wins %} -[wins] -{% if wins[0] %} -wins1={{wins[0]}} -{% endif %} -{% if wins[1] %} -wins2={{wins[1]}} -{% endif %} -{% endif %} - -[pptp] -ifname=pptp%d -{% if outside_addr %} -bind={{outside_addr}} -{% endif %} -verbose=1 -ppp-max-mtu={{mtu}} -mppe={{authentication['mppe']}} -echo-interval=10 -echo-failure=3 - - -[client-ip-range] -0.0.0.0/0 - -[ip-pool] -tunnel={{client_ip_pool}} -gw-ip-address={{gw_ip}} - -{% if authentication['mode'] == 'local' %} -[chap-secrets] -chap-secrets=/etc/accel-ppp/pptp/chap-secrets -{% endif %} - -[ppp] -verbose=5 -check-ip=1 -single-session=replace - -{% if authentication['mode'] == 'radius' %} -[radius] -{% for rsrv in authentication['radiussrv']: %} -server={{rsrv}},{{authentication['radiussrv'][rsrv]['secret']}},\ -req-limit={{authentication['radiussrv'][rsrv]['req-limit']}},\ -fail-time={{authentication['radiussrv'][rsrv]['fail-time']}} -{% endfor %} -timeout=30 -acct-timeout=30 -max-try=3 -{%endif %} - -[cli] -tcp=127.0.0.1:2003 -''' - -# pptp chap secrets -chap_secrets_conf = ''' -# username  server  password  acceptable local IP addresses -{% for user in authentication['local-users'] %} -{% if authentication['local-users'][user]['state'] == 'enabled' %} -{{user}}\t*\t{{authentication['local-users'][user]['passwd']}}\t{{authentication['local-users'][user]['ip']}} -{% endif %} -{% endfor %} -''' -  def _chk_con():      cnt = 0 -    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +    s = socket(AF_INET, SOCK_STREAM)      while True:          try:              s.connect(("127.0.0.1", 2003))              break          except ConnectionRefusedError: -            time.sleep(0.5) +            sleep(0.5)              cnt += 1              if cnt == 100:                  raise("failed to start pptp server") @@ -154,16 +52,6 @@ def _chk_con():  # chap_secrets file if auth mode local - -def _write_chap_secrets(c): -    tmpl = jinja2.Template(chap_secrets_conf, trim_blocks=True) -    chap_secrets_txt = tmpl.render(c) -    old_umask = os.umask(0o077) -    open(chap_secrets, 'w').write(chap_secrets_txt) -    os.umask(old_umask) -    sl.syslog(sl.LOG_NOTICE, chap_secrets + ' written') - -  def _accel_cmd(cmd=''):      if not cmd:          return None @@ -326,6 +214,11 @@ 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: @@ -338,12 +231,18 @@ def generate(c):          else:              c['thread_cnt'] = int(os.cpu_count()/2) -    tmpl = jinja2.Template(pptp_config, trim_blocks=True) +    tmpl = env.get_template('pptp.config.tmpl')      config_text = tmpl.render(c) -    open(pptp_conf, 'w').write(config_text) +    with open(pptp_conf, 'w') as f: +        f.write(config_text)      if c['authentication']['local-users']: -        _write_chap_secrets(c) +        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) +        os.umask(old_umask)      return c @@ -366,8 +265,6 @@ def apply(c):      else:          # if gw ip changes, only restart doesn't work          _accel_cmd('restart') -        sl.syslog(sl.LOG_NOTICE, "reloading config via daemon restart") -  if __name__ == '__main__':      try: @@ -377,4 +274,4 @@ if __name__ == '__main__':          apply(c)      except ConfigError as e:          print(e) -        sys.exit(1) +        exit(1) diff --git a/src/conf_mode/vpn_sstp.py b/src/conf_mode/vpn_sstp.py index 070437443..66b1822df 100755 --- a/src/conf_mode/vpn_sstp.py +++ b/src/conf_mode/vpn_sstp.py @@ -15,17 +15,18 @@  # along with this program.  If not, see <http://www.gnu.org/licenses/>.  import os -import jinja2  from time import sleep  from sys import exit -from subprocess import Popen, PIPE, check_output +from subprocess import check_output  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.util import process_running +from vyos.defaults import directories as vyos_data_dir +from vyos.util import process_running, subprocess_cmd  from vyos import ConfigError  pidfile = r'/var/run/accel_sstp.pid' @@ -37,140 +38,6 @@ sstp_conf = sstp_cnf_dir + '/sstp.config'  if not os.path.exists(sstp_cnf_dir):      os.makedirs(sstp_cnf_dir) -sstp_config = """### generated by vpn_sstp.py ### -[modules] -log_syslog -sstp -shaper -{% if auth_mode == 'local' %} -chap-secrets -{% elif auth_mode == 'radius' %} -radius -{% endif -%} -ippool - -{% for proto in auth_proto %} -{{proto}} -{% endfor %} - -[core] -thread-count={{thread_cnt}} - -[common] -single-session=replace - -[log] -syslog=accel-sstp,daemon -copy=1 -level=5 - -[client-ip-range] -disable - -[sstp] -verbose=1 -accept=ssl -ssl-ca-file={{ ssl_ca }} -ssl-pemfile={{ ssl_cert }} -ssl-keyfile={{ ssl_key }} - -{% if client_ip_pool %} -[ip-pool] -gw-ip-address={{ client_gateway }} -{% for subnet in client_ip_pool %} -{{ subnet }} -{% endfor %} -{% endif %} - -{% if dnsv4 %} -[dns] -{% for dns in dnsv4 -%} -dns{{ loop.index }}={{ dns }} -{% endfor -%} -{% endif %} - -{% if auth_mode == 'local' %} -[chap-secrets] -chap-secrets=/etc/accel-ppp/sstp/chap-secrets -{% elif auth_mode == 'radius' %} -[radius] -verbose=1 -{% for r in radius_server %} -server={{ r.server }},{{ r.key }},auth-port={{ r.port }},req-limit=0,fail-time={{ r.fail_time }} -{% endfor -%} - -acct-timeout={{ radius_acct_tmo }} -timeout={{ radius_timeout }} -max-try={{ radius_max_try }} - -{% if radius_nas_id %} -nas-identifier={{ radius_nas_id }} -{% endif -%} -{% if radius_nas_ip %} -nas-ip-address={{ radius_nas_ip }} -{% endif -%} -{% if radius_source_address %} -bind={{ radius_source_address }} -{% endif -%} - - -{% if radius_dynamic_author %} -dae-server={{ radius_dynamic_author.server }}:{{ radius_dynamic_author.port }},{{ radius_dynamic_author.key }} -{% endif -%} -{% endif %} - -[ppp] -verbose=1 -check-ip=1 -{% if mtu %} -mtu={{ mtu }} -{% endif -%} - -{% if ppp_mppe %} -mppe={{ ppp_mppe }} -{% endif -%} -{% if ppp_echo_interval %} -lcp-echo-interval={{ ppp_echo_interval }} -{% endif -%} -{% if ppp_echo_failure %} -lcp-echo-failure={{ ppp_echo_failure }} -{% endif -%} -{% if ppp_echo_timeout %} -lcp-echo-timeout={{ ppp_echo_timeout }} -{% endif %} - -{% if radius_shaper_attr %} -[shaper] -verbose=1 -attr={{ radius_shaper_attr }} -{% if radius_shaper_vendor %} -vendor={{ radius_shaper_vendor }} -{% endif -%} -{% endif %} - -[cli] -tcp=127.0.0.1:2005 - -""" - -# sstp chap secrets -chap_secrets_conf = """ -# username  server  password  acceptable local IP addresses   shaper -{% for user in local_users %} -{% if user.state == 'enabled' %} -{% if user.upload and user.download %} -{{ "%-12s" | format(user.name) }} * {{ "%-16s" | format(user.password) }} {{ "%-16s" | format(user.ip) }} {{ user.download }} / {{ user.upload }} -{% else %} -{{ "%-12s" | format(user.name) }} * {{ "%-16s" | format(user.password) }} {{ "%-16s" | format(user.ip) }} -{% endif %} -{% endif %} -{% endfor %} -""" - -def subprocess_cmd(command): -    p = Popen(command, stdout=PIPE, shell=True) -    p.communicate() -  def chk_con():      cnt = 0      s = socket(AF_INET, SOCK_STREAM) @@ -469,14 +336,19 @@ def generate(sstp):      if sstp is None:          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) +      # accel-cmd reload doesn't work so any change results in a restart of the daemon -    tmpl = jinja2.Template(sstp_config, trim_blocks=True) +    tmpl = env.get_template('sstp.config.tmpl')      config_text = tmpl.render(sstp)      with open(sstp_conf, 'w') as f:          f.write(config_text)      if sstp['local_users']: -        tmpl = jinja2.Template(chap_secrets_conf, trim_blocks=True) +        tmpl = env.get_template('chap-secrets.tmpl')          config_text = tmpl.render(sstp)          with open(chap_secrets, 'w') as f:              f.write(config_text) diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py index a74b79317..8cf4b72ae 100755 --- a/src/conf_mode/vrf.py +++ b/src/conf_mode/vrf.py @@ -15,35 +15,24 @@  # along with this program.  If not, see <http://www.gnu.org/licenses/>.  import os -import jinja2  from sys import exit  from copy import deepcopy +from jinja2 import FileSystemLoader, Environment  from json import loads  from subprocess import check_output, CalledProcessError  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  from vyos import ConfigError  config_file = r'/etc/iproute2/rt_tables.d/vyos-vrf.conf' -# Please be careful if you edit the template. -config_tmpl = """ -### Autogenerated by vrf.py ### -# -# Routing table ID to name mapping reference - -# id       vrf name         comment -{% for vrf in vrf_add -%} -{{ "%-10s" | format(vrf.table) }} {{ "%-16s" | format(vrf.name) }} # {{ vrf.description }} -{% endfor -%} - -""" -  default_config_data = { -    'bind_to_all': 0, +    'bind_to_all': '0',      'deleted': False,      'vrf_add': [],      'vrf_existing': [], @@ -103,7 +92,7 @@ def get_config():          # Should services be allowed to bind to all VRFs?          if conf.exists(['bind-to-all']): -            vrf_config['bind_to_all'] = 1 +            vrf_config['bind_to_all'] = '1'          # Determine vrf interfaces (currently effective) - to determine which          # vrf interface is no longer present and needs to be removed @@ -193,7 +182,12 @@ def verify(vrf_config):      return None  def generate(vrf_config): -    tmpl = jinja2.Template(config_tmpl) +    # 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) @@ -210,12 +204,15 @@ def apply(vrf_config):      # set the default VRF global behaviour      bind_all = vrf_config['bind_to_all'] -    _cmd(f'sysctl -wq net.ipv4.tcp_l3mdev_accept={bind_all}') -    _cmd(f'sysctl -wq net.ipv4.udp_l3mdev_accept={bind_all}') +    if read_file('/proc/sys/net/ipv4/tcp_l3mdev_accept') != bind_all: +        _cmd(f'sysctl -wq net.ipv4.tcp_l3mdev_accept={bind_all}') +        _cmd(f'sysctl -wq net.ipv4.udp_l3mdev_accept={bind_all}')      for vrf in vrf_config['vrf_remove']:          name = vrf['name']          if os.path.isdir(f'/sys/class/net/{name}'): +            _cmd(f'sudo ip -4 route del vrf {name} unreachable default metric 4278198272') +            _cmd(f'sudo ip -6 route del vrf {name} unreachable default metric 4278198272')              _cmd(f'ip link delete dev {name}')      for vrf in vrf_config['vrf_add']: diff --git a/src/conf_mode/vrrp.py b/src/conf_mode/vrrp.py index b17f1ce82..8683faca7 100755 --- a/src/conf_mode/vrrp.py +++ b/src/conf_mode/vrrp.py @@ -1,6 +1,6 @@  #!/usr/bin/env python3  # -# Copyright (C) 2018 VyOS maintainers and contributors +# Copyright (C) 2018-2020 VyOS maintainers and contributors  #  # This program is free software; you can redistribute it and/or modify  # it under the terms of the GNU General Public License version 2 or later as @@ -13,135 +13,26 @@  #  # 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 sys  import subprocess -import ipaddress -import json -import jinja2 + +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 pathlib import Path  daemon_file = "/etc/default/keepalived"  config_file = "/etc/keepalived/keepalived.conf"  config_dict_path = "/run/keepalived_config.dict" -config_tmpl = """ -# Autogenerated by VyOS -# Do not edit this file, all your changes will be lost -# on next commit or reboot - -global_defs { -    dynamic_interfaces -    script_user root -    notify_fifo /run/keepalived_notify_fifo -    notify_fifo_script /usr/libexec/vyos/system/keepalived-fifo.py -} - -{% for group in groups -%} - -{% if group.health_check_script -%} -vrrp_script healthcheck_{{ group.name }} { -    script "{{ group.health_check_script }}" -    interval {{ group.health_check_interval }} -    fall {{ group.health_check_count }} -    rise 1 - -} -{% endif %} - -vrrp_instance {{ group.name }} { -    {% if group.description -%} -    # {{ group.description }} -    {% endif -%} - -    state BACKUP -    interface {{ group.interface }} -    virtual_router_id {{ group.vrid }} -    priority {{ group.priority }} -    advert_int {{ group.advertise_interval }} - -    {% if group.preempt -%} -    preempt_delay {{ group.preempt_delay }} -    {% else -%} -    nopreempt -    {% endif -%} - -    {% if group.peer_address -%} -    unicast_peer { {{ group.peer_address }} } -    {% endif -%} - -    {% if group.hello_source -%} -      {%- if group.peer_address -%} -      unicast_src_ip {{ group.hello_source }} -      {%- else -%} -      mcast_src_ip {{ group.hello_source }} -      {%- endif %} -    {% endif -%} - -    {% if group.use_vmac and group.peer_address -%} -      use_vmac {{group.interface}}v{{group.vrid}} -      vmac_xmit_base -    {% elif group.use_vmac -%} -      use_vmac {{group.interface}}v{{group.vrid}} -    {% endif -%} - -    {% if group.auth_password -%} -      authentication { -        auth_pass "{{ group.auth_password }}" -        auth_type {{ group.auth_type }} -      } -    {% endif -%} - -    virtual_ipaddress { -    {% for addr in group.virtual_addresses -%} -        {{ addr }} -    {% endfor -%} -    } - -    {% if group.health_check_script -%} -    track_script { -        healthcheck_{{ group.name }} -    } -    {% endif -%} -} - -{% endfor -%} - -{% for sync_group in sync_groups -%} -vrrp_sync_group {{ sync_group.name }} { -       group { -            {% for member in sync_group.members -%} -                {{ member }} -            {% endfor -%} -        } - -        {% if sync_group.conntrack_sync -%} -            notify_master "/opt/vyatta/sbin/vyatta-vrrp-conntracksync.sh master {{ sync_group.name }}" -            notify_backup "/opt/vyatta/sbin/vyatta-vrrp-conntracksync.sh backup {{ sync_group.name }}" -            notify_fault "/opt/vyatta/sbin/vyatta-vrrp-conntracksync.sh fault {{ sync_group.name }}" -        {% endif -%} -} - -{% endfor -%} - -""" - -daemon_tmpl = """ -# Autogenerated by VyOS -# Options to pass to keepalived - -# DAEMON_ARGS are appended to the keepalived command-line -DAEMON_ARGS="--snmp" -""" - -  def get_config():      vrrp_groups = []      sync_groups = [] @@ -209,7 +100,7 @@ def get_config():          vrrp_groups.append(group) -    config.set_level("")  +    config.set_level("")      # Get the sync group used for conntrack-sync      conntrack_sync_group = None @@ -227,11 +118,17 @@ def get_config():              if conntrack_sync_group == sync_group_name:                  sync_group["conntrack_sync"] = True +        # add transition script configuration +        sync_group["master_script"] = config.return_value("transition-script master") +        sync_group["backup_script"] = config.return_value("transition-script backup") +        sync_group["fault_script"] = config.return_value("transition-script fault") +        sync_group["stop_script"] = config.return_value("transition-script stop") +          sync_groups.append(sync_group)      # create a file with dict with proposed configuration      with open("{}.temp".format(config_dict_path), 'w') as dict_file: -        dict_file.write(json.dumps({'vrrp_groups': vrrp_groups, 'sync_groups': sync_groups})) +        dict_file.write(dumps({'vrrp_groups': vrrp_groups, 'sync_groups': sync_groups}))      return (vrrp_groups, sync_groups) @@ -256,31 +153,31 @@ def verify(data):          # XXX: filter on map object is destructive, so we force it to list.          # Additionally, filter objects always evaluate to True, empty or not,          # so we force them to lists as well. -        vaddrs = list(map(lambda i: ipaddress.ip_interface(i), group["virtual_addresses"])) -        vaddrs4 = list(filter(lambda x: isinstance(x, ipaddress.IPv4Interface), vaddrs)) -        vaddrs6 = list(filter(lambda x: isinstance(x, ipaddress.IPv6Interface), vaddrs)) +        vaddrs = list(map(lambda i: ip_interface(i), group["virtual_addresses"])) +        vaddrs4 = list(filter(lambda x: isinstance(x, IPv4Interface), vaddrs)) +        vaddrs6 = list(filter(lambda x: isinstance(x, IPv6Interface), vaddrs))          if vaddrs4 and vaddrs6:              raise ConfigError("VRRP group {0} mixes IPv4 and IPv6 virtual addresses, this is not allowed. Create separate groups for IPv4 and IPv6".format(group["name"]))          if vaddrs4:              if group["hello_source"]: -                hsa = ipaddress.ip_address(group["hello_source"]) -                if isinstance(hsa, ipaddress.IPv6Address): +                hsa = ip_address(group["hello_source"]) +                if isinstance(hsa, IPv6Address):                      raise ConfigError("VRRP group {0} uses IPv4 but its hello-source-address is IPv6".format(group["name"]))              if group["peer_address"]: -                pa = ipaddress.ip_address(group["peer_address"]) -                if isinstance(pa, ipaddress.IPv6Address): +                pa = ip_address(group["peer_address"]) +                if isinstance(pa, IPv6Address):                      raise ConfigError("VRRP group {0} uses IPv4 but its peer-address is IPv6".format(group["name"]))          if vaddrs6:              if group["hello_source"]: -                hsa = ipaddress.ip_address(group["hello_source"]) -                if isinstance(hsa, ipaddress.IPv4Address): +                hsa = ip_address(group["hello_source"]) +                if isinstance(hsa, IPv4Address):                      raise ConfigError("VRRP group {0} uses IPv6 but its hello-source-address is IPv4".format(group["name"]))              if group["peer_address"]: -                pa = ipaddress.ip_address(group["peer_address"]) -                if isinstance(pa, ipaddress.IPv4Address): +                pa = ip_address(group["peer_address"]) +                if isinstance(pa, IPv4Address):                      raise ConfigError("VRRP group {0} uses IPv6 but its peer-address is IPv4".format(group["name"]))      # Disallow same VRID on multiple interfaces @@ -304,6 +201,11 @@ 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 @@ -315,13 +217,15 @@ def generate(data):      # Filter out disabled groups      vrrp_groups = list(filter(lambda x: x["disable"] is not True, vrrp_groups)) -    tmpl = jinja2.Template(config_tmpl) +    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(daemon_tmpl) +        f.write(config_text)      return None @@ -362,4 +266,4 @@ if __name__ == '__main__':          apply(c)      except ConfigError as e:          print("VRRP error: {0}".format(str(e))) -        sys.exit(1) +        exit(1) diff --git a/src/etc/ppp/ip-down.d/0020-wirelessmodem b/src/etc/ppp/ip-down.d/0020-wirelessmodem deleted file mode 100755 index c93c7cabe..000000000 --- a/src/etc/ppp/ip-down.d/0020-wirelessmodem +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/sh - -tty=$2 -ipparam=$6 - -# Only applicable for Wireless Modems (WWAN) -if [ -z "$(echo $tty |  egrep "tty(USB|ACM)")" ]; then -    exit 0 -fi - -# device name and metric are received using ipparam -device=`echo "$ipparam"|awk '{ print $1 }'` -metric=`echo "$ipparam"|awk '{ print $2 }'` - -vtysh -c "conf t" -c "no ip route 0.0.0.0/0 ${device} ${metric}" - -DIALER_PID=$(cat /var/run/${device}.pid) -logger -t pppd[$DIALER_PID] "removed default route via $device metric $metric" diff --git a/src/etc/ppp/ip-up.d/0020-wirelessmodem b/src/etc/ppp/ip-up.d/0020-wirelessmodem deleted file mode 100755 index 95549387b..000000000 --- a/src/etc/ppp/ip-up.d/0020-wirelessmodem +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/sh - -tty=$2 -ipparam=$6 - -# Only applicable for Wireless Modems (WWAN) -if [ -z "$(echo $tty |  egrep "tty(USB|ACM)")" ]; then -    exit 0 -fi - -# device name and metric are received using ipparam -device=`echo "$ipparam"|awk '{ print $1 }'` -metric=`echo "$ipparam"|awk '{ print $2 }'` - -vtysh -c "conf t" -c "ip route 0.0.0.0/0 ${device} ${metric}" - -DIALER_PID=$(cat /var/run/${device}.pid) -logger -t pppd[$DIALER_PID] "added default route via $device metric $metric" diff --git a/src/migration-scripts/interfaces/6-to-7 b/src/migration-scripts/interfaces/6-to-7 index b4f59c363..220c7e601 100755 --- a/src/migration-scripts/interfaces/6-to-7 +++ b/src/migration-scripts/interfaces/6-to-7 @@ -35,7 +35,7 @@ if __name__ == '__main__':          # Nothing to do          sys.exit(0) -    # list all individual interface types like dummy, ethernet and so on +    # list all individual wwan/wireless modem interfaces      for i in config.list_nodes(base):          iface = base + [i] diff --git a/src/migration-scripts/interfaces/7-to-8 b/src/migration-scripts/interfaces/7-to-8 new file mode 100755 index 000000000..78bd2781b --- /dev/null +++ b/src/migration-scripts/interfaces/7-to-8 @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. + +# Remove network provider name from CLI and rather use provider APN from CLI + +from sys import exit, argv +from vyos.configtree import ConfigTree + +if __name__ == '__main__': +    if (len(argv) < 1): +        print("Must specify file name!") +        exit(1) + +    file_name = argv[1] +    with open(file_name, 'r') as f: +        config_file = f.read() + +    config = ConfigTree(config_file) +    base = ['interfaces', 'wireguard'] + +    if not config.exists(base): +        # Nothing to do +        exit(0) + +    # list all individual wireguard interface isntance +    for i in config.list_nodes(base): +        iface = base + [i] +        for peer in config.list_nodes(iface + ['peer']): +            base_peer = iface + ['peer', peer] +            if config.exists(base_peer + ['endpoint']): +                endpoint = config.return_value(base_peer + ['endpoint']) +                address = endpoint.split(':')[0] +                port = endpoint.split(':')[1] +                # delete old node +                config.delete(base_peer + ['endpoint']) +                # setup new nodes +                config.set(base_peer + ['address'], value=address) +                config.set(base_peer + ['port'], value=port) + +    try: +        with open(file_name, 'w') as f: +            f.write(config.to_string()) +    except OSError as e: +        print("Failed to save the modified config: {}".format(e)) +        exit(1) diff --git a/src/op_mode/show_openvpn.py b/src/op_mode/show_openvpn.py index 06b90296f..32918ddce 100755 --- a/src/op_mode/show_openvpn.py +++ b/src/op_mode/show_openvpn.py @@ -15,6 +15,7 @@  # along with this program.  If not, see <http://www.gnu.org/licenses/>.  # +import os  import jinja2  import argparse @@ -63,6 +64,9 @@ def get_status(mode, interface):          'clients': [],      } +    if not os.path.exists(status_file): +        return data +      with open(status_file, 'r') as f:          lines = f.readlines()          for line_no, line in enumerate(lines): diff --git a/src/op_mode/wireguard.py b/src/op_mode/wireguard.py index 512c80dda..c684f8a47 100755 --- a/src/op_mode/wireguard.py +++ b/src/op_mode/wireguard.py @@ -150,7 +150,7 @@ if __name__ == '__main__':          if args.listkdir:              list_key_dirs()          if args.showinterface: -            intf = WireGuardIf(args.showinterface, debug=False) +            intf = WireGuardIf(args.showinterface, create=False, debug=False)              intf.op_show_interface()          if args.delkdir:              if args.location:  | 
