class OWFUpdate:
    def __init__(self):
        self.bitbucket_base_url = 'https://api.bitbucket.org/2.0'
        self.bitbucket_download_url = "https://bitbucket.org/octowire/{}/get/{}.tar.gz"
        self.logger = Logger()
        self.not_updated = []
        self.to_update = []
        self.to_install = []

    def _get_installed_modules(self):
        """
        Return a dict of currently installed module(s).
        :return: A dict of currently installed module(s) {'module_name': 'version', ...}.
        """
        module_name = "owfmodules"
        installed_modules = {}
        try:
            package = import_module(module_name)
        except ImportError:
            self.logger.handle('No modules currently installed', Logger.ERROR)
            return installed_modules
        for loader, module, is_pkg in pkgutil.walk_packages(
                package.__path__, prefix=package.__name__ + '.'):
            try:
                imported_module = import_module(module)
                for x in dir(imported_module):
                    obj = getattr(imported_module, x)
                    if inspect.isclass(obj) and issubclass(
                            obj, AModule) and obj is not AModule:
                        installed_modules[
                            module] = pkg_resources.get_distribution(
                                module).version
            except ImportError:
                self.logger.handle(
                    'Error while dynamically importing package "{}"... Unable to update it'
                    .format(module), Logger.ERROR)
                self.not_updated.append(module)
        return installed_modules

    def _get_available_modules(self):
        """
        Return a dict of modules that have a release on the octowire-framework project.
        :return: A dict of available modules {'module_name': 'version', ...}.
        """
        self.logger.handle(
            "Obtaining the list of package releases... This may take a while...",
            self.logger.USER_INTERACT)
        modules = {}
        resp = requests.get('{}{}'.format(
            self.bitbucket_base_url,
            '/repositories/octowire/?q=project.key="MOD"'))
        while True:
            if resp.status_code == 200:
                for pkg in resp.json()["values"]:
                    pkg_tag_url = pkg["links"]["tags"]["href"]
                    resp_tags = requests.get(pkg_tag_url +
                                             "?sort=-name&pagelen=1")
                    if resp_tags.status_code == 200:
                        latest_release = resp_tags.json()["values"]
                        if latest_release:
                            modules[pkg["name"]] = latest_release[0]["name"]
                if "next" in resp.json():
                    resp = requests.get('{}'.format(resp.json()["next"]))
                else:
                    break
            elif resp.status_code == 429:
                self.logger.handle(
                    "API rate limit reached, please try updating again later.",
                    self.logger.ERROR)
                break
            else:
                self.logger.handle(
                    "failed to load module list - HTTP response code: {}".
                    format(resp.status_code), self.logger.ERROR)
                break
        if not modules:
            self.logger.handle('No releases/modules found', Logger.ERROR)
        return modules

    def _get_latest_framework_version(self):
        """
        Return the latest release version of the Octowire framework.
        :return: String
        """
        module_release_url = self.bitbucket_base_url + '/repositories/octowire/octowire-framework/' \
                                                       'refs/tags?sort=-name&pagelen=1'
        resp = requests.get(module_release_url)
        if resp.status_code == 200:
            latest_release = resp.json()["values"]
            if latest_release:
                return latest_release[0]["name"]
            else:
                self.logger.handle(
                    'No release found for the Octowire framework',
                    Logger.ERROR)
                return None
        else:
            self.logger.handle('Unable to get the latest framework release',
                               Logger.ERROR)
            return None

    def _get_latest_library_version(self):
        """
        Return the latest release version of the Octowire-lib package.
        :return: String
        """
        pypi_api_url = 'https://pypi.org/pypi/octowire-lib/json'
        resp = requests.get(pypi_api_url)
        if resp.status_code == 200:
            releases = collections.OrderedDict(
                sorted(resp.json()["releases"].items()))
            latest_release_version = list(releases.keys())[-1]
            if latest_release_version:
                return latest_release_version
            else:
                self.logger.handle(
                    'No release found for the octowire-lib package',
                    Logger.ERROR)
                return None
        else:
            self.logger.handle('Unable to get the latest octowire-lib release',
                               Logger.ERROR)
            return None

    @staticmethod
    def _get_filename_from_cd(cd):
        """
        Get filename from content-disposition.
        :param cd: Content-Disposition HTTP header.
        :return: filename from Content-Disposition or None.
        """
        if not cd:
            return None
        fname = re.findall('filename=(.+)', cd)
        if len(fname) == 0:
            return None
        return fname[0]

    def _download_release(self, package_name, package_version):
        """
        Download the latest release of a package (module or framework).
        :param package_version: The package version.
        :param package_name: The name of the package to downloade (module or framework).
        :return: Filename or None.
        """
        self.logger.handle("Downloading {} v{}...".format(
            package_name, package_version))
        resp = requests.get(self.bitbucket_download_url.format(
            package_name, package_version),
                            stream=True)
        if resp.status_code == 200:
            file = tempfile.NamedTemporaryFile(suffix=".tar.gz", delete=False)
            file.write(resp.content)
            file.close()
            return file.name
        else:
            self.logger.handle(
                "failed to download the latest release for the module '{}' - "
                "HTTP response code: {}".format(package_name,
                                                resp.status_code),
                self.logger.ERROR)
        return None

    @staticmethod
    def _extract_tarball(filename):
        """
        Extract the specified tarball.
        :param filename: The tarball file path.
        :return: Directory path of the extracted archive.
        """
        tar = tarfile.open(filename)
        path = re.sub(r'\.tar.gz$', '', filename)
        setup_dir = '{}/{}'.format(path, tar.getmembers()[0].name)
        tar.extractall(path)
        tar.close()
        return setup_dir

    def _manage_install(self, package_name, package_version):
        """
        Manage the installation of the specified package (module or framework).
        :param package_name: The name of the package to install (module or framework).
        :param package_version: The package version to install (module or framework).
        :return: Bool: True if successfully installed, False otherwise.
        """
        filename = self._download_release(package_name, package_version)
        python_path = sys.executable
        if filename:
            setup_dir = self._extract_tarball(filename)
            package_dir = setup_dir.split("/")[-1]
            setup_dir = '/'.join(setup_dir.split("/")[:-1])
            try:
                if package_name != "octowire-framework":
                    pipes = subprocess.Popen([
                        python_path, '-m', 'pip', 'install', '--upgrade',
                        f"./{package_dir}"
                    ],
                                             stdout=subprocess.PIPE,
                                             stderr=subprocess.PIPE,
                                             cwd=setup_dir)
                    stdout, stderr = pipes.communicate()
                    if pipes.returncode != 0:
                        self.logger.handle(
                            "Error while installing {} package: {}".format(
                                package_name, stderr.strip()), Logger.ERROR)
                        return False
                    else:
                        self.logger.handle(
                            "'{}' successfully installed".format(package_name),
                            Logger.SUCCESS)
                        return True
                else:
                    # This method is necessary to update the framework. Indeed, this allows releasing the owfupdate
                    # executable in order to replace it.
                    current_dir = pathlib.Path().absolute()
                    log_file = current_dir / "framework_install.log"
                    if os.path.isfile(setup_dir + "/update_framework.py"):
                        if platform.system() == "Windows":
                            subprocess.Popen(
                                [
                                    python_path, 'update_framework.py', '-p',
                                    str(os.getpid()), '-f',
                                    str(log_file)
                                ],
                                cwd=setup_dir,
                                creationflags=subprocess.DETACHED_PROCESS)
                        else:
                            subprocess.Popen([
                                python_path, 'update_framework.py', '-p',
                                str(os.getpid()), '-f',
                                str(log_file)
                            ],
                                             cwd=setup_dir)
                        self.logger.handle(
                            "The framework update was launched in background... check the following "
                            "file to see if it was successfully updated: {}".
                            format(str(log_file)), self.logger.WARNING)
                        return True
                    else:
                        self.logger.handle(
                            "The 'update_framework.py' file is missing from the installer package... "
                            "Unable to update the octowire-framework package...",
                            self.logger.ERROR)
                        return False
            except subprocess.CalledProcessError as err:
                self.logger.handle(
                    "The setup command failed for the '{}' module".format(
                        package_name), Logger.ERROR)
                print(err.stderr)
                return False
        else:
            self.logger.handle(
                "Failed to download the latest release for '{}'".format(
                    package_name), Logger.ERROR)
            return False

    def _update_framework(self):
        """
        This function updates the Octowire framework.
        :return: Nothing
        """
        latest_release_version = self._get_latest_framework_version()
        if latest_release_version:
            latest_release_version = pkg_resources.parse_version(
                latest_release_version)
            try:
                current_version = pkg_resources.parse_version(
                    pkg_resources.get_distribution(
                        'octowire-framework').version)
            except pkg_resources.DistributionNotFound:
                current_version = pkg_resources.parse_version('')
            if latest_release_version > current_version:
                self.logger.handle(
                    'A new framework release is available, running update...',
                    Logger.INFO)
                if not self._manage_install(
                        'octowire-framework',
                        latest_release_version.base_version):
                    self.not_updated.append('octowire-framework')
            else:
                self.logger.handle('Octowire framework is already up-to-date',
                                   Logger.SUCCESS)
        else:
            self.not_updated.append('octowire-framework')

    def _update_library(self):
        """
        This function updates the octowire-lib package.
        :return: Nothing
        """
        python_path = sys.executable
        latest_release_version = self._get_latest_library_version()
        if latest_release_version:
            latest_release_version = pkg_resources.parse_version(
                latest_release_version)
            try:
                current_version = pkg_resources.parse_version(
                    pkg_resources.get_distribution('octowire-lib').version)
            except pkg_resources.DistributionNotFound:
                current_version = pkg_resources.parse_version('')
            if latest_release_version > current_version:
                self.logger.handle(
                    'A new octowire-lib release is available, running update...',
                    Logger.INFO)
                pipes = subprocess.Popen([
                    python_path, '-m', 'pip', 'install', '--upgrade',
                    'octowire-lib'
                ],
                                         stdout=subprocess.PIPE,
                                         stderr=subprocess.PIPE)
                stdout, stderr = pipes.communicate()
                if pipes.returncode != 0:
                    self.logger.handle(
                        "Error while installing the octowire-lib package: {}".
                        format(stderr.strip()), Logger.ERROR)
                    self.not_updated.append('octowire-lib')
                else:
                    self.logger.handle("'octowire-lib' successfully installed",
                                       Logger.SUCCESS)
            else:
                self.logger.handle('Octowire library is already up-to-date',
                                   Logger.SUCCESS)
        else:
            self.not_updated.append('octowire-lib')

    def update(self, update_framework=None):
        """
        This script checks all released Octowire modules and compares them with currently installed modules.
        If an update is available, this script installs it. Moreover, if a module is available and not installed,
        it will be installed.
        :param update_framework: If True, check whether a framework update is available.
        :return: Nothing
        """
        try:
            available_modules = self._get_available_modules()
            installed_modules = self._get_installed_modules()
            for name, version in available_modules.items():
                installed_module_version = installed_modules.get(name, None)
                if installed_module_version is not None:
                    if pkg_resources.parse_version(
                            installed_module_version
                    ) < pkg_resources.parse_version(version):
                        self.to_update.append({
                            "name": name,
                            "version": version
                        })
                else:
                    self.to_install.append({"name": name, "version": version})
            if not self.to_update and not self.to_install:
                self.logger.handle("Everything is up-to-date", Logger.SUCCESS)
            for module in self.to_update:
                self.logger.handle(
                    "Updating module '{}' to version {}".format(
                        module["name"], module["version"]), Logger.INFO)
                if not self._manage_install(module["name"], module["version"]):
                    self.not_updated.append(module["name"])
            for module in self.to_install:
                self.logger.handle(
                    "Installing module '{}' version {}".format(
                        module["name"], module["version"]), Logger.INFO)
                if not self._manage_install(module["name"], module["version"]):
                    self.not_updated.append(module["name"])
            if len(self.not_updated) > 0:
                self.logger.handle(
                    "Unable to update/install the following package(s):",
                    Logger.ERROR)
                for module in self.not_updated:
                    print(" - {}".format(module))
            # Update the octowire-lib package
            self._update_library()
            if update_framework:
                self._update_framework()
        except requests.exceptions.ConnectionError:
            self.logger.handle(
                "Failed to reach servers. Please check your internet connection.",
                Logger.ERROR)
Beispiel #2
0
class AModule(ABC):
    def __init__(self, owf_config):
        self.logger = Logger()
        self.config = owf_config
        self.owf_serial = None
        self.meta = {
            'name': '',
            'version': '',
            'description': '',
            'author': ''
        }
        self.options = {}
        self.advanced_options = {}
        self.dependencies = []

    def __name__(self):
        """
        Simply return the module name
        :return: Module name
        :rtype: string
        """
        return self.meta["name"]

    def _check_dependencies(self):
        """
        Verify the module dependencies.
        :return: Bool
        """
        update_msg = "Please run 'owfupdate' in your terminal to install the latest module version"
        for dependency in self.dependencies:
            req = pkg_resources.Requirement.parse(dependency)
            try:
                pkg_resources.get_provider(req)
            except pkg_resources.RequirementParseError as err:
                self.logger.handle(err, self.logger.ERROR)
                return False
            except pkg_resources.DistributionNotFound:
                self.logger.handle(
                    "The '{}' distribution was not found and is required "
                    "by the module".format(dependency), self.logger.ERROR)
                self.logger.handle(update_msg, self.logger.USER_INTERACT)
                return False
            except pkg_resources.VersionConflict:
                dist = pkg_resources.get_distribution(req.name)
                Logger().handle(
                    "Version conflict: '{}' is required. Version '{}' is currently "
                    "installed".format(dependency, dist.version),
                    Logger().ERROR)
                self.logger.handle(update_msg, self.logger.USER_INTERACT)
                return False
            # Recursively check sub_module dependencies
            if req.name == "octowire-lib":
                sub_module = import_module("octowire")
            else:
                sub_module = import_module(req.name)
            for d_amodule_class in get_amodule_class(sub_module, req.name,
                                                     AModule):
                amodule_class = d_amodule_class["class"](load_config())
                if not amodule_class._check_dependencies():
                    return False
        return True

    def connect(self):
        """
        Connect to the Octowire using configured options.
        If owf_serial is a valid serial instance, skip the connexion part (manage by a parent module).
        :return: Nothing
        """
        if not isinstance(self.owf_serial,
                          serial.Serial) or not self.owf_serial.is_open:
            if self.config["OCTOWIRE"].getint("detect"):
                self.owf_serial = self._manage_connection()
            else:
                port = self.config["OCTOWIRE"]["port"]
                baudrate = self.config["OCTOWIRE"].getint("baudrate")
                timeout = self.config["OCTOWIRE"].getint("read_timeout")
                self.owf_serial = self._manage_connection(
                    auto_connect=False,
                    octowire_port=port,
                    octowire_baudrate=baudrate,
                    octowire_timeout=timeout)

    def _manage_connection(self,
                           auto_connect=True,
                           octowire_port=None,
                           octowire_baudrate=None,
                           octowire_timeout=1):
        """
        Manage connection to the Octowire hardware and return a serial instance.
        :param auto_connect: Automatically detect and connect to the octowire if set to True.
        :param octowire_port: The Octowire port (required if auto_connect is False).
        :param octowire_baudrate: The Octowire baudrate. Default=7372800 (required if auto_connect is False).
        :param octowire_timeout: The Octowire timeout. Default=1 (required if auto_connect is False).
        :return: Serial instance or None.
        """
        try:
            if auto_connect:
                return detect_and_connect()
            else:
                if not octowire_port:
                    self.logger.handle(
                        "If auto_detect is set to False, please specify the Octowire port.",
                        self.logger.ERROR)
                    return None
                return serial.Serial(port=octowire_port,
                                     baudrate=octowire_baudrate,
                                     timeout=octowire_timeout)
        except serial.SerialException as err:
            self.logger.handle(
                "Error during serial connection: {}".format(err))
            return None

    def show_options(self, options):
        """
        Print available options for the module to the console.
        :return: Nothing
        """
        formatted_options = []
        if len(options) > 0:
            self.print_meta()
            for option_name, option in options.items():
                if option["Default"] != "" and option["Value"] == "":
                    formatted_options.append({
                        'Name':
                        option_name,
                        'Value':
                        option["Default"],
                        'Required':
                        option["Required"],
                        'Description':
                        option["Description"]
                    })
                else:
                    formatted_options.append({
                        'Name':
                        option_name,
                        'Value':
                        option["Value"],
                        'Required':
                        option["Required"],
                        'Description':
                        option["Description"]
                    })
            self.logger.print_tabulate(formatted_options,
                                       headers={
                                           "Name": "Name",
                                           "Value": "Value",
                                           "Required": "Required",
                                           "Description": "Description"
                                       })

    def print_meta(self):
        """
        Print meta information of the module (author, module name, description).
        :return: Nothing
        """
        self.logger.handle('Author: {}'.format(self.meta['author']),
                           Logger.HEADER)
        self.logger.handle(
            'Module name: {}, version {}'.format(self.meta['name'],
                                                 self.meta['version']),
            Logger.HEADER)
        self.logger.handle('Description: {}'.format(self.meta['description']),
                           Logger.HEADER)

    @abstractmethod
    def run(self):
        """
        Main function prototype.
        """
        pass
Beispiel #3
0
class Octowire:
    """
    Octowire superclass.
    Each protocol should inherit from it.
    It implements basic functionality.
    """
    def __init__(self, serial_instance=None):
        """
        Init function.
        :param serial_instance: Octowire USB CDC Serial (pyserial) instance.
        """
        self.serial_instance = serial_instance
        self.logger = Logger()

    def is_connected(self):
        """
        Check octowire serial port connection status.
        :return: Bool
        """
        if self._is_serial_instance():
            if self.serial_instance.is_open:
                return True
            else:
                self.logger.handle("Serial port is a valid serial instance but no connection was detected.",
                                   self.logger.ERROR)
                return False
        else:
            self.logger.handle("{}{}".format(type(self.serial_instance), " is not a Serial instance."),
                               self.logger.ERROR)
            return False

    def _is_serial_instance(self):
        """
        Check whether serial_port is a valid Serial instance.
        :return:
        """
        return isinstance(self.serial_instance, serial.Serial)

    def ensure_binary_mode(self):
        """
        Ensure that the Octowire is in binary mode. Otherwise enter it.
        :return: bool
        """
        if self.is_connected():
            current_mode = self.mode
            if current_mode == 't':
                binmode_opcode = b"binmode\n"
                self.serial_instance.write(binmode_opcode)
                time.sleep(1)
                # read local echo
                self.serial_instance.read(self.serial_instance.in_waiting)
                if self.mode == 'b':
                    return True
                else:
                    self.logger.handle("Unable to switch the Octowire to binary mode.", self.logger.ERROR)
                    return False
            elif current_mode == 'b':
                return True
            else:
                return False

    def ensure_text_mode(self):
        """
        Ensure that the Octowire is in text mode. Otherwise enter it.
        :return: bool
        """
        if self.is_connected():
            current_mode = self.mode
            if current_mode == 'b':
                mode_opcode = b"\x00\x00\x01"
                self.serial_instance.write(mode_opcode)
                time.sleep(1)
                # read local echo
                self.serial_instance.read(self.serial_instance.in_waiting)
                if self.mode == 't':
                    return True
                else:
                    self.logger.handle("Unable to switch the Octowire to text mode.", self.logger.ERROR)
                    return False
            elif current_mode == 't':
                return True
            else:
                return False

    def octowire_status_is_valid(self):
        """
        Determine whether Octowire is in a valid mode (b or t).
        :return: Bool
        """
        if self._is_serial_instance():
            octowire_mode = self.mode
            if octowire_mode in ["t", "b"]:
                return True
            else:
                self.logger.handle("Invalid string mode received", self.logger.ERROR)
                return False
        else:
            self.logger.handle("The serial_instance parameter is not a valid Serial instance.", self.logger.ERROR)
            return False

    def get_octowire_version(self):
        """
        Return the Octowire version.
        :return: string (Octowire version).
        """
        if self.octowire_status_is_valid():
            version_opcode = b"\x00\x00\x02"
            mode = self.mode
            # If the Octowire is in text mode, enter in binary mode
            if mode == "t":
                self.ensure_binary_mode()
            # Get Octowire version
            self.serial_instance.write(version_opcode)
            # First, read the command status
            status = self.serial_instance.read(1)
            if status == b"\x00":
                # Then, read 4 bytes to get the response size sent by the Octowire
                size = self.serial_instance.read(4)
                if int.from_bytes(size, byteorder='little') > 0:
                    # Finally, read the Octowire version string
                    version = self.serial_instance.read(struct.unpack("<L", size)[0])
                    return version.decode()
                else:
                    self.logger.handle("Invalid version size returned by the Octowire.", self.logger.ERROR)
                    return None
            else:
                self.logger.handle("Error while trying to get Octowire version.", self.logger.ERROR)
                return None
        else:
            return None

    def _read_response_code(self, operation_name=None, disable_timeout=False):
        """
        This function handles reading the response code.
        :param operation_name: String Octowire operation name.
        :param disable_timeout: If true, disable the Serial instance timeout.
        :return: Nothing
        """
        timeout = self.serial_instance.timeout
        if disable_timeout:
            self.serial_instance.timeout = None
        status = self.serial_instance.read(1)
        self.serial_instance.timeout = timeout
        if status != b"\x00":
            raise Exception("Operation '{}' returned an error.".format(operation_name))

    def _get_size_read_data(self, operation_name=None):
        """
        This function receives the 4 bytes data size to receive from the Octowire and returns it.
        :param operation_name: String Octowire operation name.
        :return: Size of the next data chunk to read.
        :rtype: int
        """
        resp = self.serial_instance.read(4)
        if not resp:
            raise Exception("Unable to get the size of the data to"
                            " receive from the Octowire (Operation: {}).".format(operation_name))
        return struct.unpack("<L", resp)[0]

    def _read_chunk(self, chunk_size, operation_name=None):
        """
        This function reads a chunk of data.
        :param chunk_size: The data length to read (bytes).
        :param operation_name: Octowire operation name.
        :return: the data chunk that was read.
        :rtype: bytes
        """
        chunk = self.serial_instance.read(chunk_size)
        if not chunk:
            raise Exception("No data received from the Octowire (Operation: {}).".format(operation_name))
        return chunk

    def _read_data(self, expected_size=None, operation_name=None):
        """
        This function handles data reception.
        :param operation_name: Octowire operation name.
        :param expected_size: The expected size to read.
        :return: Data read from the Octowire.
        :rtype: bytes
        """
        data = bytearray()
        if expected_size is not None:
            while expected_size > 0:
                chunk_size = self._get_size_read_data(operation_name)
                data.extend(self._read_chunk(chunk_size, operation_name=operation_name))
                expected_size = expected_size - chunk_size
        # When the size of data to receive is not known, for get Octowire version for example, read only the first chunk
        else:
            chunk_size = self._get_size_read_data(operation_name)
            data.extend(self._read_chunk(chunk_size, operation_name=operation_name))
        return data

    @property
    def mode(self):
        """
        This function sends the 0x0a character to detect the current mode (binary or text).
        :return: 'b' or 't'.
        :rtype: string
        """
        self.serial_instance.write(b"\x0a")
        time.sleep(0.1)
        if self.serial_instance.in_waiting:
            self.serial_instance.read(self.serial_instance.in_waiting)
            return "t"
        else:
            # If in bin mode, we have to terminate the command (args size + version opcode + empty arguments)
            self.serial_instance.write(b"\x00\x02" + 10 * b"\x00")
            self._read_response_code(operation_name="Get current Octowire's mode")
            self._read_data(operation_name="Detect current Octowire's mode")
            return "b"

    @mode.setter
    def mode(self, value):
        """
        Allow to switch between the binary and text mode.
        :param value: 'b' for binary, 't' for text.
        """
        if value in ["t", "b"]:
            if value == "t":
                if not self.ensure_text_mode():
                    raise Exception("Unable to switch the Octowire to text mode.")
            else:
                if not self.ensure_binary_mode():
                    raise Exception("Unable to switch the Octowire to binary mode.")
        else:
            raise ValueError("Invalid mode selector. 'b' or 't' is waiting.")
Beispiel #4
0
class OWFRemove:
    def __init__(self):
        self.logger = Logger()
        self.not_removed = []

    def _get_installed_modules(self):
        """
        Return a dict of currently installed module(s).
        :return: A dict of currently installed module(s) {'module_name': 'version', ...}.
        """
        module_name = "owfmodules"
        installed_modules = {}
        try:
            package = import_module(module_name)
        except ImportError:
            return installed_modules
        for loader, module, is_pkg in pkgutil.walk_packages(package.__path__, prefix=package.__name__ + '.'):
            try:
                imported_module = import_module(module)
                for x in dir(imported_module):
                    obj = getattr(imported_module, x)
                    if inspect.isclass(obj) and issubclass(obj, AModule) and obj is not AModule:
                        installed_modules[module] = pkg_resources.get_distribution(module).version
            except ImportError:
                self.logger.handle('Error while dynamically importing package "{}"... Unable to removed it'
                                   .format(module), Logger.ERROR)
                self.not_removed.append(module)
        return installed_modules

    @staticmethod
    def _create_uninstall_script():
        """
        Create the uninstall script that will be executed in a subprocess.
        :return: Bool
        """
        file = tempfile.NamedTemporaryFile(mode="w+", suffix=".py", delete=False)
        file.write(script)
        file.close()
        return file.name

    def _manage_uninstall(self, package_name):
        """
        Removing the specified package (module or framework).
        :param package_name: The name of the package to install (module or framework).
        :return: Bool: True if successfully removed, False otherwise.
        """
        python_path = sys.executable
        current_dir = pathlib.Path().absolute()
        if package_name != "octowire-framework":
            pipes = subprocess.Popen([python_path, '-m', 'pip', 'uninstall', '-y', package_name],
                                     stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            stdout, stderr = pipes.communicate()
            if pipes.returncode != 0:
                self.logger.handle("Error while removing the '{}' package: {}".format(package_name, stderr.strip()),
                                   Logger.ERROR)
                return False
            else:
                self.logger.handle("'{}' successfully removed".format(package_name), Logger.SUCCESS)
                return True
        else:
            # This method is necessary to remove the framework. Indeed, this allows releasing the owfremove
            # executable in order to removed it.
            script_name = self._create_uninstall_script()
            log_file = current_dir / "framework_remove.log"
            if platform.system() == "Windows":
                subprocess.Popen([python_path, script_name, '-p', str(os.getpid()), '-f', str(log_file)],
                                 creationflags=subprocess.DETACHED_PROCESS)
            else:
                subprocess.Popen([python_path, script_name, '-p', str(os.getpid()), '-f', str(log_file)])
            self.logger.handle("The remove of the framework was launched in background... check the following "
                               "file to see if it was successfully removed: {}".format(str(log_file)),
                               self.logger.WARNING)
            return True

    def remove(self, remove_framework=None):
        """
        This script checks all installed Octowire modules and remove it.
        :param remove_framework: If True, remove the octowire Framework.
        :return: Nothing
        """
        installed_modules = self._get_installed_modules()
        if not installed_modules:
            self.logger.handle("No module seems installed", Logger.WARNING)
        for module_name, _ in installed_modules.items():
            self.logger.handle(f"Removing module '{module_name}'..", Logger.INFO)
            if not self._manage_uninstall(module_name):
                self.not_removed.append(module_name)

        if len(self.not_removed) > 0:
            self.logger.handle("Unable to remove the following package(s):", Logger.ERROR)
            for module in self.not_removed:
                print(" - {}".format(module))
            self.logger.handle("Please try to uninstall it manually with the following command: "
                               "'pip3 uninstall owfmodules.<category>.<module_name>'", Logger.ERROR)
        if remove_framework:
            self._manage_uninstall("octowire-framework")
            self.logger.handle("User configuration files need to be manually removed; these are present in '~/.owf' "
                               "directory for any user which has run the framework at least once.",
                               self.logger.USER_INTERACT)
class Validator:
    """
    This class checks module options, verifying whether format is valid.
    """
    def __init__(self):
        self.logger = Logger()

    def _args_validator(self, option_name, option):
        """
        Check argument type validity & convert to the specified format.
        :param option: Module option.
        :return: bool
        """
        try:
            # Do not validated empty value if option is not required
            if option["Value"] == "" and not option["Required"]:
                return True
            if option["Type"] == "int":
                if not isinstance(option["Value"], int):
                    option["Value"] = int(option["Value"], 10)
            if option["Type"] == "float":
                if not isinstance(option["Value"], float):
                    option["Value"] = float(option["Value"])
            # Hex string to int
            elif option["Type"] == "hex":
                if not isinstance(option["Value"], int):
                    option["Value"] = int(option["Value"], 16)
            elif option["Type"] == "bool":
                if not isinstance(option["Value"], bool):
                    if str(option["Value"]).upper() == "FALSE":
                        option["Value"] = False
                    elif str(option["Value"]).upper() == "TRUE":
                        option["Value"] = True
                    else:
                        raise ValueError(
                            "option {}: True or False are expected.".format(
                                option["Value"]))
            # Hex string to bytes
            elif option["Type"] == "hextobytes":
                if not isinstance(option["Value"], bytes):
                    option["Value"] = bytes.fromhex(option["Value"])
            # File to read
            elif option["Type"] == "file_r":
                if not os.access(option["Value"], os.R_OK):
                    raise Exception(
                        "{}: file does not exist or permission denied.".format(
                            option["Value"]))
            # File to write
            elif option["Type"] == "file_w":
                dirname = os.path.dirname(option["Value"])
                dirname = './' if dirname == '' else dirname
                if not os.access(dirname, os.W_OK):
                    raise Exception("{}: permission denied.".format(
                        option["Value"]))
            if option["Value"] == "None":
                option["Value"] = None
        except ValueError:
            self.logger.handle("Value error: {} is not a valid {}".format(
                option_name, option["Type"]))
            return False
        return True

    def check_args(self, options_dict):
        """
        Check whether all arguments are defined by user, setting them to their default values otherwise (if available).
        :param options_dict: Module options dictionary.
        :return: bool
        """
        if len(options_dict) > 0:
            for option_name, option in options_dict.items():
                if option["Value"] == "":
                    if option["Default"] == "" and option["Required"]:
                        self.logger.handle(
                            "OptionValidationError: The following options failed to validate: {}."
                            .format(option_name), Logger.ERROR)
                        return False
                    else:
                        option["Value"] = option["Default"]
                if not self._args_validator(option_name, option):
                    return False
        return True
Beispiel #6
0
def miniterm(owf_instance=None, *args):
    """
    Run a serial console session (using miniterm from serial package).
    :param args: Varargs command options.
    :param owf_instance: Octowire framework instance (self).
    :return: Nothing.
    """
    if len(args) < 1:
        config = None
    else:
        config = args[0] if isinstance(args[0], configparser.ConfigParser) else None
    logger = Logger()
    filters = []
    if owf_instance is None and config is None:
        logger.handle("You need to set owf_instance or config", logger.ERROR)
        return False
    if owf_instance is not None:
        config = owf_instance.config
    octowire_cfg = config['OCTOWIRE']
    miniterm_cfg = config['MINITERM']
    filters.append(miniterm_cfg['filters'])

    try:
        serial_instance = serial.serial_for_url(
            octowire_cfg['port'],
            octowire_cfg['baudrate'],
            parity=miniterm_cfg['parity'],
            rtscts=0,
            xonxoff=config.getboolean('MINITERM', 'xonxoff'),
            do_not_open=True)

        if not hasattr(serial_instance, 'cancel_read'):
            # enable timeout for alive flag polling if cancel_read is not available
            serial_instance.timeout = 1

        if isinstance(serial_instance, serial.Serial):
            serial_instance.exclusive = True

        serial_instance.open()
    except serial.SerialException as e:
        logger.handle("could not open port {!r}: {}".format(octowire_cfg['port'], e), logger.ERROR)
        return False

    mt = Miniterm(
        serial_instance,
        echo=config.getboolean('MINITERM', 'echo'),
        eol=miniterm_cfg['eol'].lower(),
        filters=filters)
    mt.exit_character = chr(int(miniterm_cfg['exit_char']))
    mt.menu_character = chr(int(miniterm_cfg['menu_char']))
    mt.raw = miniterm_cfg['raw']
    mt.set_rx_encoding(miniterm_cfg['serial_port_encoding'])
    mt.set_tx_encoding(miniterm_cfg['serial_port_encoding'])

    if not config.getboolean('MINITERM', 'quiet'):
        Logger().handle('--- Miniterm on {p.name} {p.baudrate},{p.bytesize},{p.parity},{p.stopbits} ---\n'.format(
            p=mt.serial))

    toolbar = '--- Quit: <{}> | Menu: <{}> | Help: <{}> followed by <{}> ---\n'.format(
        key_description(int(miniterm_cfg['exit_char'])),
        key_description(int(miniterm_cfg['menu_char'])),
        key_description(int(miniterm_cfg['menu_char'])),
        key_description(int(miniterm_cfg['menu_char_help'])))
    print(toolbar)

    mt.start()
    try:
        mt.join(True)
    except KeyboardInterrupt:
        pass
    if not config.getboolean('MINITERM', 'quiet'):
        Logger().handle('\n--- exit ---\n')
    mt.join()
    mt.close()