diff options
Diffstat (limited to 'python/vyos')
-rw-r--r-- | python/vyos/airbag.py | 169 | ||||
-rw-r--r-- | python/vyos/remote.py | 29 | ||||
-rw-r--r-- | python/vyos/util.py | 21 | ||||
-rw-r--r-- | python/vyos/version.py | 15 |
4 files changed, 214 insertions, 20 deletions
diff --git a/python/vyos/airbag.py b/python/vyos/airbag.py new file mode 100644 index 000000000..664974d5f --- /dev/null +++ b/python/vyos/airbag.py @@ -0,0 +1,169 @@ +# Copyright 2019-2020 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +import os +import sys +import logging +import logging.handlers +from datetime import datetime + +from vyos.config import Config +from vyos.version import get_version +from vyos.util import run +from vyos.util import debug + + +# we allow to disable the extra logging +DISABLE = False + + +# emulate a file object +class _IO(object): + def __init__(self, std, log): + self.std = std + self.log = log + + def write(self, message): + self.std.write(message) + if DISABLE: + return + for line in message.split('\n'): + s = line.rstrip() + if s: + self.log(s) + + def flush(self): + self.std.flush() + + def close(self): + pass + + +# The function which will be used to report information +# to users when an exception is unhandled +def bug_report(dtype, value, trace): + from traceback import format_exception + + sys.stdout.flush() + sys.stderr.flush() + + information = { + 'date': datetime.now().strftime('%Y-%m-%d %H:%M:%S'), + 'version': get_version(), + 'trace': format_exception(dtype, value, trace), + 'instructions': COMMUNITY if 'rolling' in get_version() else SUPPORTED, + } + + sys.stdout.write(INTRO.format(**information)) + sys.stdout.flush() + + sys.stderr.write(FAULT.format(**information)) + sys.stderr.flush() + + +# define an exception handler to be run when an exception +# reach the end of __main__ and was not intercepted +def intercepter(dtype, value, trace): + bug_report(dtype, value, trace) + # debug returns either '' or 'developer' if debuging is enabled + if debug('developer'): + import pdb + pdb.pm() + + +def InterceptingLogger(address, _singleton=[False]): + skip = _singleton.pop() + _singleton.append(True) + if skip: + return + + logger = logging.getLogger('VyOS') + logger.setLevel(logging.DEBUG) + handler = logging.handlers.SysLogHandler(address='/dev/log', facility='syslog') + logger.addHandler(handler) + + # log to syslog any message sent to stderr + sys.stderr = _IO(sys.stderr, logger.critical) + + +# lists as default arguments in function is normally dangerous +# as they will keep any modification performed, unless this is +# what you want to do (in that case to only run the code once) +def InterceptingException(excepthook,_singleton=[False]): + skip = _singleton.pop() + _singleton.append(True) + if skip: + return + + # install the handler to replace the default behaviour + # which just prints the exception trace on screen + sys.excepthook = excepthook + + +# Do not attempt the extra logging for operational commands +try: + # This fails during boot + insession = Config().in_session() +except: + # we save info on boot to help debugging + insession = True + + +# Installing the interception, it currently does not work when +# running testing so we are checking that we are on the router +# as otherwise it prevents dpkg-buildpackage to work +if get_version() and insession: + InterceptingLogger('/run/systemd/journal/dev-log') + InterceptingException(intercepter) + + +# Messages to print + +FAULT = """\ +Date: {date} +VyOS image: {version} + +{trace} +""" + +INTRO = """\ +VyOS had an issue completing a command. + +We are sorry that you encountered a problem with VyOS. +There are a few things you can do to help us (and yourself): +{instructions} + +PLEASE, when reporting, do include as much information as you can: +- do not obfuscate any data (feel free to send us a private communication with + the extra information if your business policy is strict on information sharing) +- and include all the information presented below + +""" + +COMMUNITY = """\ +- Make sure you are running the latest version of the code available at + https://downloads.vyos.io/rolling/current/amd64/vyos-rolling-latest.iso +- Consult the forum to see how to handle this issue + https://forum.vyos.io +- Join our community on slack where our users exchange help and advice + https://vyos.slack.com +""".strip() + +SUPPORTED = """\ +- Make sure you are running the latest stable version of VyOS + the code is available at https://downloads.vyos.io/?dir=release/current +- Contact us on our online help desk + https://support.vyos.io/ +""".strip() diff --git a/python/vyos/remote.py b/python/vyos/remote.py index f0bf41cd4..f8a21f068 100644 --- a/python/vyos/remote.py +++ b/python/vyos/remote.py @@ -17,8 +17,7 @@ import sys import os import re import fileinput - -from vyos.util import cmd, DEVNULL +import subprocess def check_and_add_host_key(host_name): @@ -34,8 +33,10 @@ def check_and_add_host_key(host_name): keyscan_cmd = 'ssh-keyscan -t rsa {} 2>/dev/null'.format(host_name) try: - host_key = cmd(keyscan_cmd, stderr=DEVNULL, universal_newlines=True) - except OSError: + host_key = subprocess.check_output(keyscan_cmd, shell=True, + stderr=subprocess.DEVNULL, + universal_newlines=True) + except subprocess.CalledProcessError as err: sys.exit("Can not get RSA host key") # libssh2 (jessie; stretch) does not recognize ec host keys, and curl @@ -63,8 +64,10 @@ def check_and_add_host_key(host_name): fingerprint_cmd = 'ssh-keygen -lf /dev/stdin <<< "{}"'.format(host_key) try: - fingerprint = cmd(fingerprint_cmd, stderr=DEVNULL, universal_newlines=True) - except OSError: + fingerprint = subprocess.check_output(fingerprint_cmd, shell=True, + stderr=subprocess.DEVNULL, + universal_newlines=True) + except subprocess.CalledProcessError as err: sys.exit("Can not get RSA host key fingerprint.") print("RSA host key fingerprint is {}".format(fingerprint.split()[1])) @@ -125,8 +128,9 @@ def get_remote_config(remote_file): # Try header first, and look for 'OK' or 'Moved' codes: curl_cmd = 'curl {0} -q -I {1}'.format(redirect_opt, remote_file) try: - curl_output = cmd(curl_cmd, shell=True, universal_newlines=True) - except OSError: + curl_output = subprocess.check_output(curl_cmd, shell=True, + universal_newlines=True) + except subprocess.CalledProcessError: sys.exit(1) return_vals = re.findall(r'^HTTP\/\d+\.?\d\s+(\d+)\s+(.*)$', @@ -142,6 +146,9 @@ def get_remote_config(remote_file): curl_cmd = 'curl {0} -# {1}'.format(redirect_opt, remote_file) try: - return cmd(curl_cmd, universal_newlines=True) - except OSError: - return None + config_file = subprocess.check_output(curl_cmd, shell=True, + universal_newlines=True) + except subprocess.CalledProcessError: + config_file = None + + return config_file diff --git a/python/vyos/util.py b/python/vyos/util.py index 16cfae92d..c827425ee 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -19,6 +19,12 @@ import sys from subprocess import Popen, PIPE, STDOUT, DEVNULL def debug(flag): + # this is to force all new flags to be registered here so that + # they can be documented: + # - developer: the code will drop into PBD on un-handled exception + # - ifconfig: prints command and sysfs access on stdout for interface + if flag not in ['developer', 'ifconfig']: + return False return flag if os.path.isfile(f'/tmp/vyos.{flag}.debug') else '' @@ -93,16 +99,25 @@ def read_file(path): return data -def chown_file(path, user, group): - """ change file owner """ +def chown(path, user, group): + """ change file/directory owner """ from pwd import getpwnam from grp import getgrnam - if os.path.isfile(path): + if os.path.exists(path): uid = getpwnam(user).pw_uid gid = getgrnam(group).gr_gid os.chown(path, uid, gid) +def chmod_750(path): + """ make file/directory only executable to user and group """ + from stat import S_IRUSR, S_IWUSR, S_IXUSR, S_IRGRP, S_IXGRP + + if os.path.exists(path): + bitmask = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP + os.chmod(path, bitmask) + + def chmod_x(path): """ make file executable """ from stat import S_IRUSR, S_IWUSR, S_IXUSR, S_IRGRP, S_IXGRP, S_IROTH, S_IXOTH diff --git a/python/vyos/version.py b/python/vyos/version.py index 383efbc1e..d51a940d6 100644 --- a/python/vyos/version.py +++ b/python/vyos/version.py @@ -44,7 +44,7 @@ def get_version_data(file=version_file): file (str): path to the version file Returns: - dict: version data + dict: version data, if it can not be found and empty dict The optional ``file`` argument comes in handy in upgrade scripts that need to retrieve information from images other than the running image. @@ -52,17 +52,20 @@ def get_version_data(file=version_file): is an implementation detail and may change in the future, while the interface of this module will stay the same. """ - with open(file, 'r') as f: - version_data = json.load(f) - return version_data + try: + with open(file, 'r') as f: + version_data = json.load(f) + return version_data + except FileNotFoundError: + return {} def get_version(file=None): """ - Get the version number + Get the version number, or an empty string if it could not be determined """ version_data = None if file: version_data = get_version_data(file=file) else: version_data = get_version_data() - return version_data["version"] + return version_data.get('version','') |