diff --git a/to_system/etc/chrony.conf b/apu_system/etc/chrony.conf similarity index 100% rename from to_system/etc/chrony.conf rename to apu_system/etc/chrony.conf diff --git a/to_system/etc/profile.d/welcome.sh b/apu_system/etc/profile.d/welcome.sh similarity index 100% rename from to_system/etc/profile.d/welcome.sh rename to apu_system/etc/profile.d/welcome.sh diff --git a/to_system/etc/systemd/system/sethostname.service b/apu_system/etc/systemd/system/sethostname.service similarity index 100% rename from to_system/etc/systemd/system/sethostname.service rename to apu_system/etc/systemd/system/sethostname.service diff --git a/display.py b/display.py index ce0ce41..8937340 100644 --- a/display.py +++ b/display.py @@ -7,7 +7,6 @@ import threading import re import queue from utils import MainIf -from subprocess import Popen, PIPE from configparser import ConfigParser # display tty device diff --git a/install.py b/install.py index e16d6be..16e3974 100755 --- a/install.py +++ b/install.py @@ -18,9 +18,8 @@ import types import socket from subprocess import Popen, PIPE from ipaddress import IPv4Interface -from configparser import ConfigParser from os.path import join, getmtime, exists, basename -from utils import BoxInfo +from utils import BoxInfo, check_service, unix_cmd, change_firewall if os.geteuid() != 0: exit("You need to have root privileges to run this script.\nPlease try again, this time using 'sudo'. Exiting.") @@ -66,7 +65,7 @@ max-lease-time 7200; authoritative; """ -ROUTER_TEMPLATE = f"""[Unit] +ROUTER_SERVICE = f"""[Unit] Description = Routing to locally connected hardware After = network.target @@ -77,7 +76,7 @@ ExecStart = /usr/bin/python3 {TOOLS}/router.py WantedBy = multi-user.target """ -FRAPPY_TEMPLATE = """[Unit] +FRAPPY_SERVICE = """[Unit] Description = Running frappy server After = network.target @@ -89,7 +88,7 @@ ExecStart = /usr/bin/python3 /home/l_samenv/frappy/bin/frappy-server %s WantedBy = multi-user.target """ -DISPLAY_TEMPLATE = f"""[Unit] +DISPLAY_SERVICE = f"""[Unit] Description = status display After = network.target @@ -111,10 +110,15 @@ pip_requirements = { box = BoxInfo() dhcp_server_cfg = [] -TO_SYSTEM = f'{TOOLS}/to_system' +TO_SYSTEM = f'{TOOLS}/{box.typ}_system' os.chdir(TO_SYSTEM) +def do_cmd(command): + unix_cmd(command, doit) + show.dirty = True + + def frappy(cfg=None, port=None, requirements='', **kwds): if not cfg: return None @@ -124,10 +128,26 @@ def frappy(cfg=None, port=None, requirements='', **kwds): cfg = '-p %s %s' % (port, cfg) with open('/home/l_samenv/frappy/requirements.txt') as f: req['frappy main'] = f.read() - return FRAPPY_TEMPLATE % cfg + return FRAPPY_SERVICE % cfg -def router(**opts): +def router(firewall=False, **opts): + if firewall: + active, enabled = check_service('nftables') + ports = {22} + if opts: + for port in firewall.split(','): + ports.add(int(port)) + for key in opts: + try: + ports.add(int(key)) + except ValueError: + pass + ports.update((1110, 1111, 1112)) + if change_firewall(True, ports, doit): + show.dirty = True + elif change_firewall(False, set(), doit): + show.dirty = True if not opts: return None try: @@ -136,7 +156,7 @@ def router(**opts): pip_requirements['root']['tools'] = f.read() except FileNotFoundError: pass - return ROUTER_TEMPLATE + return ROUTER_SERVICE def display_update(cfg): @@ -154,7 +174,7 @@ def display_update(cfg): def display(**opts): if not opts: return None - return DISPLAY_TEMPLATE + return DISPLAY_SERVICE def pip(): @@ -176,23 +196,13 @@ def pip(): os.remove(tmpname) else: print(pipcmd) - unix_cmd(pipcmd, stdout=None) + unix_cmd(pipcmd, doit, stdout=None) show.dirty = True SERVICES = dict(router=router, frappy=frappy, display=display) -def unix_cmd(command, always=False, stdout=PIPE): - if doit or always: - if not always: - print('$ %s' % command) - result = Popen(command.split(), stdout=stdout).communicate()[0] - return (result or b'').decode() - else: - print('> %s' % command) - - def write_when_new(filename, content, ignore_reduction=False): if content is None: lines = [] @@ -490,21 +500,20 @@ def handle_config(): replace_in_file('/etc/default/isc-dhcp-server', r'INTERFACESv4="(.*)"', ' '.join([n for n in box.macaddr if n != box.main_if])) elif doit: - unix_cmd('systemctl stop isc-dhcp-server') - unix_cmd('systemctl disable isc-dhcp-server') + check_service('isc-dhcp-server', False) content = [] dirty = False for section, section_dict in config.items(): - if COMMENT not in section_dict: - dirty = True - content.append('[%s]' % section) - content.append(COMMENT) + # if COMMENT not in section_dict: + # dirty = True + # content.append('[%s]' % section) + # content.append(COMMENT) if section == 'DISPLAY': if display_update(section_dict): to_start['display'] = 'restart' - for key, value in section_dict.items(): - content.append('%s=%s' % (key, value)) - content.append('') + # for key, value in section_dict.items(): + # content.append('%s=%s' % (key, value)) + # content.append('') if dirty: print(cfgfile) print('\n'.join(content)) @@ -520,33 +529,17 @@ def handle_config(): section = service.upper() section_dict = config.get(section, {}) servicecfg = service_func(**section_dict) - result = unix_cmd('systemctl show -p WantedBy -p ActiveState %s' % service, True) - active = False - enabled = False - for line in result.split('\n'): - if line.startswith('WantedBy=m'): - enabled = True - elif line.strip() == 'ActiveState=active': - active = True + active, enabled = check_service(service) if servicecfg is None: - if active: - unix_cmd('systemctl stop %s' % service) + if active or enabled: + check_service(service, False, doit) show.dirty = True - if enabled: - unix_cmd('systemctl disable %s' % service) - show.dirty = True - if service == 'router' and active or enabled: - if doit: - shutil.copy(join(TOOLS, 'nftables.conf'), '/etc/nftables.conf') - else: - print('cp nftables.conf /etc/nftables.conf') - unix_cmd('systemctl restart nftables') else: if not enabled: to_start[service] = 'enable' elif not active: to_start[service] = 'restart' - if write_when_new('/etc/systemd/system/%s.service' % service, servicecfg): + if write_when_new(f'/etc/systemd/system/{service}.service', servicecfg): show.dirty = True reload_systemd = True if servicecfg and to_start.get('service') is None: @@ -554,25 +547,25 @@ def handle_config(): pip() if reload_systemd: - unix_cmd('systemctl daemon-reload') + do_cmd('systemctl daemon-reload') for service, action in to_start.items(): show.dirty = True if action == 'if_restart': - unix_cmd('ifdown %s' % service) + do_cmd(f'ifdown {service}') for service, action in to_start.items(): if action == 'if_restart': - unix_cmd('ifup %s' % service) + do_cmd(f'ifup {service}') else: if action == 'restart': - unix_cmd('systemctl restart %s' % service) - unix_cmd('systemctl enable %s' % service) + do_cmd(f'systemctl restart {service}') + do_cmd(f'systemctl enable {service}') if box.change_if_names: print('interface name system has to be changed from enp*s0 to eth*') if replace_in_file('/etc/default/grub', r'GRUB_CMDLINE_LINUX_DEFAULT="quiet(.*)"', ' net.ifnames=0"'): - unix_cmd('update-grub') + do_cmd('update-grub') result = [f'config file:\n {cfgfile}'] for section, section_dict in config.items(): diff --git a/router.py b/router.py index 65e0b00..7c34f2d 100644 --- a/router.py +++ b/router.py @@ -9,9 +9,7 @@ from glob import glob from select import select from serial import serial_for_url from subprocess import Popen, PIPE, check_output, call, DEVNULL -from utils import BoxInfo - -FIREWALL_CONF = '/etc/nftables.conf' +from utils import BoxInfo, change_firewall class Log: DEBUG = 0 @@ -169,7 +167,7 @@ class Router(IoHandler): return except Exception as e: msg = f'error in request: {e!r}' - service.failures[service.port] = msg + self.service.failures[service.port] = msg log.error(msg) self.close() @@ -401,36 +399,12 @@ class Service: self.failures.pop(self.port, None) self.handlers[handler.fno] = handler - @classmethod - def change_firewall(cls): - pattern = re.compile('(tcp dport ({.*}) ct state new accept)', re.MULTILINE | re.DOTALL) - - with open(FIREWALL_CONF) as f: - content = f.read() - - try: - ((prevline, prevports),) = pattern.findall(content) - except (TypeError, ValueError): - print(f'{FIREWALL_CONF} does not contain expected pattern for open ports - firewall off?') - return - - # parse previous port set - prevportset = {int(p) for p in prevports[1:-1].split(',')} - # keep port numbers below 1024 - ports = {p for p in prevportset if p < 1024} | set(cls.firewall_ports) - line = prevline.replace(prevports, '{ %s }' % ', '.join((str(p) for p in sorted(ports)))) - if prevportset != ports: - if os.geteuid() == 0: - with open('f{FIREWALL_CONF}.tmp', 'w') as f: - f.write(content.replace(prevline, line)) - os.rename('f{FIREWALL_CONF}.tmp', FIREWALL_CONF) - unix_cmd('systemctl restart nftables') - unix_cmd('systemctl enable nftables') - else: - print('need sudo rights to modify firewall') - @classmethod def run(cls, routes): + firewall = routes.pop('firewall', None) + firewall_on = firewall is not None + if firewall_on: + cls.firewall_ports = set(int(r) for r in firewall.split(',') if r.strip()) Service(1110, None, InfoHandler, 5, handler_args=(log.DEBUG,)) Service(1111, None, InfoHandler, 5, handler_args=(log.INFO,)) Service(1112, None, InfoHandler, 5, handler_args=(log.WARN,)) @@ -445,7 +419,7 @@ class Service: else: remoteport = port Service(port, (host, remoteport), TcpHandler) - cls.change_firewall() + change_firewall(firewall_on, cls.firewall_ports) while True: try: # log.debug('select %r', list(cls.readers)) diff --git a/utils.py b/utils.py index 3b0b026..cd00ad0 100644 --- a/utils.py +++ b/utils.py @@ -1,9 +1,14 @@ import os import socket import threading +import re from glob import glob from configparser import ConfigParser from netifaces import interfaces, ifaddresses, gateways, AF_INET, AF_LINK +from subprocess import Popen, PIPE + + +FIREWALL_CONF = '/etc/nftables.conf' if os.geteuid(): @@ -130,3 +135,90 @@ class MainIf: else: self.prev_ip = None return self.carrier, self.ip, self.hostnameresult[0], self.gateway + + +def unix_cmd(command, execute=None, stdout=PIPE): + if execute != False: # None or True + if execute: + print('$ %s' % command) + result = Popen(command.split(), stdout=stdout).communicate()[0] + return (result or b'').decode() + else: + print('> %s' % command) + + +def check_service(service, set_on=None, execute=None): + """check or set state of systemd service + + set_on is None or not given: query only + bool(set_on) is True: start and enable if not yet done + set_on == False/0: stop and disable if not yet done + + sim: print out command instead of executing + """ + result = unix_cmd(f'systemctl show -p WantedBy -p ActiveState {service}') + enabled = False + active = False + for line in result.split('\n'): + if line.startswith('WantedBy=') and line.strip() != 'WantedBy=': + enabled = True + elif line.strip() == 'ActiveState=active': + active = True + if set_on: + if not active: + unix_cmd(f'systemctl start {service}', execute) + if not enabled: + unix_cmd(f'systemctl enable {service}', execute) + elif set_on is not None: + if active: + unix_cmd(f'systemctl stop {service}', execute) + if enabled: + unix_cmd(f'systemctl disable {service}', execute) + return active, enabled + + +def change_firewall(set_on, ports, execute=None): + ports.add(22) # always add ssh + active, enabled = check_service('nftables') + if not set_on: + if os.geteuid() == 0: + check_service('nftables', False, execute) + else: + print('need sudo rights to modify firewall') + return active or enabled + + pattern = re.compile('(tcp dport ({.*}) ct state new accept)', re.MULTILINE | re.DOTALL) + + for filename in FIREWALL_CONF, os.path.join(BoxInfo.TOOLS, 'nftables.conf'): + with open(filename) as f: + content = f.read() + + try: + ((prevline, prevports),) = pattern.findall(content) + break + except (TypeError, ValueError): + pass + else: + print(f'{FIREWALL_CONF} does not contain expected pattern for open ports - firewall off?') + return False + + # parse previous port set + prevportset = {int(p) for p in prevports[1:-1].split(',')} + line = prevline.replace(prevports, '{ %s }' % ', '.join((str(p) for p in sorted(ports)))) + if prevportset == ports: + if active and enabled: + return False + check_service('nftables', True, execute) + return True + if os.geteuid() == 0: + if execute is not None: + print(f'change firewall ports to {ports}') + if execute != 0: + with open('f{FIREWALL_CONF}.tmp', 'w') as f: + f.write(content.replace(prevline, line)) + os.rename('f{FIREWALL_CONF}.tmp', FIREWALL_CONF) + unix_cmd('systemctl restart nftables', execute) + unix_cmd('systemctl enable nftables', execute) + elif ports - prevportset: + print('need sudo rights to modify firewall') + return True