diff options
Diffstat (limited to 'src/op_mode')
-rwxr-xr-x | src/op_mode/powerctrl.py | 237 | ||||
-rwxr-xr-x | src/op_mode/show_dhcp.py | 165 | ||||
-rwxr-xr-x | src/op_mode/show_dhcpv6.py | 82 | ||||
-rwxr-xr-x | src/op_mode/system_integrity.py | 69 | ||||
-rwxr-xr-x | src/op_mode/wireguard.py (renamed from src/op_mode/wireguard_key.py) | 29 |
5 files changed, 464 insertions, 118 deletions
diff --git a/src/op_mode/powerctrl.py b/src/op_mode/powerctrl.py index f73d6c005..5a0345fbf 100755 --- a/src/op_mode/powerctrl.py +++ b/src/op_mode/powerctrl.py @@ -2,141 +2,158 @@ import os import sys import argparse +import subprocess from datetime import datetime, timedelta, time as type_time, date as type_date -from subprocess import check_output,CalledProcessError,STDOUT +from subprocess import check_output, CalledProcessError, STDOUT import re def yn(msg, default=False): - default_msg = "[Y/n]" if default else "[y/N]" - while True: - sys.stdout.write("%s %s " % (msg,default_msg)) - c = input().lower() - if c == '': - return default - elif c in ("y", "ye","yes"): - return True - elif c in ("n", "no"): - return False - else: - sys.stdout.write("Please respond with yes/y or no/n\n") + default_msg = "[Y/n]" if default else "[y/N]" + while True: + sys.stdout.write("%s %s " % (msg,default_msg)) + c = input().lower() + if c == '': + return default + elif c in ("y", "ye","yes"): + return True + elif c in ("n", "no"): + return False + else: + sys.stdout.write("Please respond with yes/y or no/n\n") def valid_time(s): - try: - return datetime.strptime(s, "%H:%M").time() - except ValueError: - return None + try: + return datetime.strptime(s, "%H:%M").time() + except ValueError: + return None def valid_date(s): + try: + return datetime.strptime(s, "%d%m%Y").date() + except ValueError: try: - return datetime.strptime(s, "%d%m%Y").date() + return datetime.strptime(s, "%d/%m/%Y").date() except ValueError: + try: + return datetime.strptime(s, "%d.%m.%Y").date() + except ValueError: try: - return datetime.strptime(s, "%d/%m/%Y").date() + return datetime.strptime(s, "%d:%m:%Y").date() except ValueError: - try: - return datetime.strptime(s, "%d.%m.%Y").date() - except ValueError: - try: - return datetime.strptime(s, "%d:%m:%Y").date() - except ValueError: - return None + return None def check_shutdown(): - try: - cmd = check_output(["/bin/systemctl","status","systemd-shutdownd.service"]) - #Shutodwn is scheduled - r = re.findall(r'Status: \"(.*)\"\n', cmd.decode())[0] - print(r) - except CalledProcessError as e: - #Shutdown is not scheduled - print("Shutdown is not scheduled") + try: + cmd = check_output(["/bin/systemctl","status","systemd-shutdownd.service"]) + #Shutodwn is scheduled + r = re.findall(r'Status: \"(.*)\"\n', cmd.decode())[0] + print(r) + except CalledProcessError as e: + #Shutdown is not scheduled + print("Shutdown is not scheduled") def cancel_shutdown(): - try: - cmd = check_output(["/sbin/shutdown","-c"]) - except CalledProcessError as e: - sys.exit("Error aborting shutdown: %s" % e) + try: + cmd = check_output(["/sbin/shutdown","-c"]) + except CalledProcessError as e: + sys.exit("Error aborting shutdown: %s" % e) def execute_shutdown(time, reboot = True, ask=True): - if not ask: - action = "reboot" if reboot else "poweroff" - if not yn("Are you sure you want to %s this system?" % action): - sys.exit(0) - - action = "-r" if reboot else "-P" - - if len(time) == 0: - cmd = check_output(["/sbin/shutdown",action,"now"],stderr=STDOUT) - print(cmd.decode().split(",",1)[0]) - return - - # Try to extract date from the first argument - if len(time) == 1: - time = time[0].split(" ",1) - - if len(time) == 1: - ts=valid_time(time[0]) - if time[0].isdigit() or valid_time(time[0]): - cmd = check_output(["/sbin/shutdown",action,time[0]],stderr=STDOUT) - else: - sys.exit("Timestamp needs to be in format of 12:34") - - elif len(time) == 2: - ts = valid_time(time[0]) - ds = valid_date(time[1]) - if ts and ds: - t = datetime.combine(ds, ts) - td = t-datetime.now() - t2 = 1+int(td.total_seconds())//60 # Get total minutes - cmd = check_output(["/sbin/shutdown",action,str(t2)],stderr=STDOUT) - else: - sys.exit("Timestamp needs to be in format of 12:34\nDatestamp in the format of DD.MM.YY") + if not ask: + action = "reboot" if reboot else "poweroff" + if not yn("Are you sure you want to %s this system?" % action): + sys.exit(0) + + action = "-r" if reboot else "-P" + + if len(time) == 0: + ### T870 legacy reboot job support + chk_vyatta_based_reboots() + ### + + cmd = check_output(["/sbin/shutdown",action,"now"],stderr=STDOUT) + print(cmd.decode().split(",",1)[0]) + return + + # Try to extract date from the first argument + if len(time) == 1: + time = time[0].split(" ",1) + + if len(time) == 1: + ts = valid_time(time[0]) + if time[0].isdigit() or valid_time(time[0]): + cmd = check_output(["/sbin/shutdown",action,time[0]],stderr=STDOUT) + else: + sys.exit("Timestamp needs to be in format of 12:34") + + elif len(time) == 2: + ts = valid_time(time[0]) + ds = valid_date(time[1]) + if ts and ds: + t = datetime.combine(ds, ts) + td = t - datetime.now() + t2 = 1 + int(td.total_seconds())//60 # Get total minutes + cmd = check_output(["/sbin/shutdown",action,str(t2)],stderr=STDOUT) else: - sys.exit("Could not decode time and date") + sys.exit("Timestamp needs to be in format of 12:34\nDatestamp in the format of DD.MM.YY") + else: + sys.exit("Could not decode time and date") - print(cmd.decode().split(",",1)[0]) + print(cmd.decode().split(",",1)[0]) -def main(): - parser = argparse.ArgumentParser() - parser.add_argument("--yes", "-y", - help="dont as for shutdown", - action="store_true", - dest="yes") - action = parser.add_mutually_exclusive_group(required=True) - action.add_argument("--reboot", "-r", - help="Reboot the system", - nargs="*", - metavar="Minutes|HH:MM") - - action.add_argument("--poweroff", "-p", - help="Poweroff the system", - nargs="*", - metavar="Minutes|HH:MM") - - action.add_argument("--cancel", "-c", - help="Cancel pending shutdown", - action="store_true") - - action.add_argument("--check", - help="Check pending chutdown", - action="store_true") - args = parser.parse_args() +def chk_vyatta_based_reboots(): + ### T870 commit-confirm is still using the vyatta code base, once gone, the code below can be removed + ### legacy scheduled reboot s are using at and store the is as /var/run/<name>.job + ### name is the node of scheduled the job, commit-confirm checks for that - try: - if args.reboot: - execute_shutdown(args.reboot, reboot=True, ask=args.yes) - if args.poweroff: - execute_shutdown(args.poweroff, reboot=False,ask=args.yes) - if args.cancel: - cancel_shutdown() - if args.check: - check_shutdown() - except KeyboardInterrupt: - sys.exit("Interrupted") + f = r'/var/run/confirm.job' + if os .path.exists(f): + jid = open(f).read().strip() + if jid != 0: + subprocess.call(['sudo', 'atrm', jid]) + os.remove(f) + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--yes", "-y", + help="dont as for shutdown", + action="store_true", + dest="yes") + action = parser.add_mutually_exclusive_group(required=True) + action.add_argument("--reboot", "-r", + help="Reboot the system", + nargs="*", + metavar="Minutes|HH:MM") + + action.add_argument("--poweroff", "-p", + help="Poweroff the system", + nargs="*", + metavar="Minutes|HH:MM") + + action.add_argument("--cancel", "-c", + help="Cancel pending shutdown", + action="store_true") + + action.add_argument("--check", + help="Check pending chutdown", + action="store_true") + args = parser.parse_args() + + try: + if args.reboot is not None: + execute_shutdown(args.reboot, reboot=True, ask=args.yes) + if args.poweroff is not None: + execute_shutdown(args.poweroff, reboot=False,ask=args.yes) + if args.cancel: + cancel_shutdown() + if args.check: + check_shutdown() + except KeyboardInterrupt: + sys.exit("Interrupted") if __name__ == "__main__": - main() + main() diff --git a/src/op_mode/show_dhcp.py b/src/op_mode/show_dhcp.py new file mode 100755 index 000000000..0005d62a3 --- /dev/null +++ b/src/op_mode/show_dhcp.py @@ -0,0 +1,165 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +import json +import argparse +import ipaddress + +import tabulate + +import vyos.config + +from isc_dhcp_leases import Lease, IscDhcpLeases + + +lease_file = "/config/dhcpd.leases" +pool_key = "shared-networkname" + +def in_pool(lease, pool): + if pool_key in lease.sets: + if lease.sets[pool_key] == pool: + return True + + return False + +def get_lease_data(lease): + data = {} + + # End time may not be present in backup leases + try: + data["expires"] = lease.end.strftime("%Y/%m/%d %H:%M:%S") + except: + data["expires"] = "" + + data["hardware_address"] = lease.ethernet + data["hostname"] = lease.hostname + data["ip"] = lease.ip + + try: + data["pool"] = lease.sets[pool_key] + except: + data["pool"] = "" + + return data + +def get_leases(leases, state=None, pool=None): + leases = IscDhcpLeases(lease_file).get() + + if state is not None: + leases = list(filter(lambda x: x.binding_state == 'active', leases)) + + if pool is not None: + leases = list(filter(lambda x: in_pool(x, pool), leases)) + + return list(map(get_lease_data, leases)) + +def show_leases(leases): + headers = ["IP address", "Hardware address", "Lease expiration", "Pool", "Client Name"] + + lease_list = [] + for l in leases: + lease_list.append([l["ip"], l["hardware_address"], l["expires"], l["pool"], l["hostname"]]) + + output = tabulate.tabulate(lease_list, headers) + + print(output) + +def get_pool_size(config, pool): + size = 0 + subnets = config.list_effective_nodes("service dhcp-server shared-network-name {0} subnet".format(pool)) + for s in subnets: + ranges = config.list_effective_nodes("service dhcp-server shared-network-name {0} subnet {1} range".format(pool, s)) + for r in ranges: + start = config.return_effective_value("service dhcp-server shared-network-name {0} subnet {1} range {2} start".format(pool, s, r)) + stop = config.return_effective_value("service dhcp-server shared-network-name {0} subnet {1} range {2} stop".format(pool, s, r)) + + size += int(ipaddress.IPv4Address(stop)) - int(ipaddress.IPv4Address(start)) + + return size + +def show_pool_stats(stats): + headers = ["Pool", "Size", "Leases", "Available", "Usage"] + output = tabulate.tabulate(stats, headers) + + print(output) + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + + group = parser.add_mutually_exclusive_group() + group.add_argument("-l", "--leases", action="store_true", help="Show DHCP leases") + group.add_argument("-s", "--statistics", action="store_true", help="Show DHCP statistics") + + parser.add_argument("-e", "--expired", action="store_true", help="Show expired leases") + parser.add_argument("-p", "--pool", type=str, action="store", help="Show lease for specific pool") + parser.add_argument("-j", "--json", action="store_true", default=False, help="Product JSON output") + + args = parser.parse_args() + + if args.leases: + if args.expired: + if args.pool: + leases = get_leases(lease_file, state='free', pool=args.pool) + else: + leases = get_leases(lease_file, state='free') + else: + if args.pool: + leases = get_leases(lease_file, state='active', pool=args.pool) + else: + leases = get_leases(lease_file, state='active') + + if args.json: + print(json.dumps(leases, indent=4)) + else: + show_leases(leases) + elif args.statistics: + config = vyos.config.Config() + + pools = [] + + # Get relevant pools + if args.pool: + pools = [args.pool] + else: + pools = config.list_effective_nodes("service dhcp-server shared-network-name") + + # Get pool usage stats + stats = [] + for p in pools: + size = get_pool_size(config, p) + leases = len(get_leases(lease_file, state='active', pool=args.pool)) + + if size != 0: + use_percentage = round(leases / size) * 100 + else: + use_percentage = 0 + + if args.json: + pool_stats = {"pool": p, "size": size, "leases": leases, + "available": (size - leases), "percentage": use_percentage} + else: + # For tabulate + pool_stats = [p, size, leases, size - leases, "{0}%".format(use_percentage)] + stats.append(pool_stats) + + # Print stats + if args.json: + print(json.dumps(stats, indent=4)) + else: + show_pool_stats(stats) + else: + print("Use either --leases or --statistics option") diff --git a/src/op_mode/show_dhcpv6.py b/src/op_mode/show_dhcpv6.py new file mode 100755 index 000000000..8879a45c5 --- /dev/null +++ b/src/op_mode/show_dhcpv6.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +import json +import argparse +import ipaddress + +import tabulate + +import vyos.config + +from isc_dhcp_leases import Lease, IscDhcpLeases + + +lease_file = "/config/dhcpdv6.leases" + +def get_lease_data(lease): + data = {} + + # End time may not be present in backup leases + try: + data["expires"] = lease.end.strftime("%Y/%m/%d %H:%M:%S") + except: + data["expires"] = "" + + data["duid"] = lease.host_identifier_string + data["ip"] = lease.ip + + return data + +def get_leases(leases, state=None): + leases = IscDhcpLeases(lease_file).get() + + if state is not None: + leases = list(filter(lambda x: x.binding_state == 'active', leases)) + + return list(map(get_lease_data, leases)) + +def show_leases(leases): + headers = ["IPv6 address", "Lease expiration", "DUID"] + + lease_list = [] + for l in leases: + lease_list.append([l["ip"], l["expires"], l["duid"]]) + + output = tabulate.tabulate(lease_list, headers) + + print(output) + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + + group = parser.add_mutually_exclusive_group() + group.add_argument("-l", "--leases", action="store_true", help="Show DHCP leases") + group.add_argument("-s", "--statistics", action="store_true", help="Show DHCP statistics") + + parser.add_argument("-p", "--pool", type=str, action="store", help="Show lease for specific pool") + parser.add_argument("-j", "--json", action="store_true", default=False, help="Product JSON output") + + args = parser.parse_args() + + if args.leases: + leases = get_leases(lease_file, state='active') + show_leases(leases) + elif args.statistics: + print("DHCPv6 statistics option is not available") + else: + print("Invalid option") diff --git a/src/op_mode/system_integrity.py b/src/op_mode/system_integrity.py new file mode 100755 index 000000000..886d94f16 --- /dev/null +++ b/src/op_mode/system_integrity.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# + +import sys +import os +import subprocess +import re +import itertools +from datetime import datetime, timedelta + +verf = r'/usr/libexec/vyos/op_mode/version.py' + +def get_sys_build_version(): + if not os.path.exists(verf): + return None + + a = subprocess.check_output(['/usr/libexec/vyos/op_mode/version.py']).decode() + if re.search('^Built on:.+',a, re.M) == None: + return None + + dt = ( re.sub('Built on: +','', re.search('^Built on:.+',a, re.M).group(0)) ) + return datetime.strptime(dt,'%a %d %b %Y %H:%M %Z') + +def check_pkgs(dt): + pkg_diffs = { + 'buildtime' : str(dt), + 'pkg' : {} + } + + pkg_info = os.listdir('/var/lib/dpkg/info/') + for file in pkg_info: + if re.search('\.list$', file): + fts = os.stat('/var/lib/dpkg/info/' + file).st_mtime + dt_str = (datetime.utcfromtimestamp(fts).strftime('%Y-%m-%d %H:%M:%S')) + fdt = datetime.strptime(dt_str, '%Y-%m-%d %H:%M:%S') + if fdt > dt: + pkg_diffs['pkg'].update( { str(re.sub('\.list','',file)) : str(fdt)}) + + if len(pkg_diffs['pkg']) != 0: + return pkg_diffs + else: + return None + +def main(): + dt = get_sys_build_version() + pkgs = check_pkgs(dt) + if pkgs != None: + print ("The following packages don\'t fit the image creation time\nbuild time:\t" + pkgs['buildtime']) + for k, v in pkgs['pkg'].items(): + print ("installed: " + v + '\t' + k) + +if __name__ == '__main__': + sys.exit( main() ) + diff --git a/src/op_mode/wireguard_key.py b/src/op_mode/wireguard.py index 811cff1ca..73da72aa7 100755 --- a/src/op_mode/wireguard_key.py +++ b/src/op_mode/wireguard.py @@ -19,18 +19,18 @@ import argparse import os import sys -import syslog as sl import subprocess +import syslog as sl from vyos import ConfigError dir = r'/config/auth/wireguard' pk = dir + '/private.key' pub = dir + '/public.key' +psk = dir + '/preshared.key' -### check_kmod may be removed in the future, -### once it's loaded automatically def check_kmod(): + """ check if kmod is loaded, if not load it """ if not os.path.exists('/sys/module/wireguard'): sl.syslog(sl.LOG_NOTICE, "loading wirguard kmod") if os.system('sudo modprobe wireguard') != 0: @@ -38,6 +38,7 @@ def check_kmod(): raise ConfigError("modprobe wireguard failed") def generate_keypair(): + """ generates a keypair which is stored in /config/auth/wireguard """ ret = subprocess.call(['wg genkey | tee ' + pk + '|wg pubkey > ' + pub], shell=True) if ret != 0: raise ConfigError("wireguard key-pair generation failed") @@ -45,18 +46,23 @@ def generate_keypair(): sl.syslog(sl.LOG_NOTICE, "new keypair wireguard key generated in " + dir) def genkey(): - ### if umask 077 makes trouble, 027 will work + """ helper function to check, regenerate the keypair """ old_umask = os.umask(0o077) if os.path.exists(pk) and os.path.exists(pub): - choice = input("You have a wireguard key-pair already, do you want to re-generate? [y/n] ") - if choice == 'y' or choice == 'Y': - generate_keypair() + try: + choice = input("You already have a wireguard key-pair already, do you want to re-generate? [y/n] ") + if choice == 'y' or choice == 'Y': + generate_keypair() + except KeyboardInterrupt: + sys.exit(0) else: - os.mkdir(dir) + if not os.path.exists(dir): + os.mkdir(dir) generate_keypair() os.umask(old_umask) def showkey(key): + """ helper function to show privkey or pubkey """ if key == "pub": if os.path.exists(pub): print ( open(pub).read().strip() ) @@ -69,6 +75,10 @@ def showkey(key): else: print("no private key found") +def genpsk(): + """ generates a preshared key and shows it on stdout, it's stroed only in the config """ + subprocess.call(['wg genpsk'], shell=True) + if __name__ == '__main__': check_kmod() @@ -76,6 +86,7 @@ if __name__ == '__main__': parser.add_argument('--genkey', action="store_true", help='generate key-pair') parser.add_argument('--showpub', action="store_true", help='shows public key') parser.add_argument('--showpriv', action="store_true", help='shows private key') + parser.add_argument('--genpsk', action="store_true", help='generates preshared-key') args = parser.parse_args() try: @@ -85,6 +96,8 @@ if __name__ == '__main__': showkey("pub") if args.showpriv: showkey("pk") + if args.genpsk: + genpsk() except ConfigError as e: print(e) |