Exemplo n.º 1
0
class Manager:
    """ The class that holds the methods used to configure the devices. """

    CONFIG_FILE_PATH = os.path.join(os.path.dirname(__file__), 'data',
                                    'config.json')
    variables = {
        "DEVICE_HOSTNAME": None,
        "ENABLE_PASSWORD": None,
        "DOMAIN_NAME": None,
        "VLAN_NUMBER": None
    }
    keys = list(variables.keys(
    ))  # keywords that will be replaced if found on 'config.json' commands.
    configuration_keywords = ('DEFAULT_CONFIG', 'SET_HOSTNAME', "CREATE_VLAN",
                              'DELETE_VLAN', 'ACCESS_SSH_ONLY',
                              'ACCESS_TELNET_ONLY', 'ACCESS_SSH_TELNET',
                              'SHOW_INTERFACES_STATUS', 'SHOW_INTERFACES_IP')
    config_file = open(CONFIG_FILE_PATH, "r")
    config_json = json.load(config_file)

    def __init__(self):
        self.__devices = []
        self.__vlans_to_configure = None
        self.__obj_connect = None
        self.__shell = None

    def add_device(self, device):
        """ Add the device (obj) to the list of devices to be configured.

            Args:
                device (:obj: `Device`): The device to be added.

        """
        self.__devices.append(device)

    def configure_devices(self, *args):
        """ Starts the configuration of devices on the devices list. """
        for _, device in enumerate(self.__devices):
            self.variables['ENABLE_PASSWORD'] = device.enable_secret
            self.variables['DOMAIN_NAME'] = device.domain_name
            auxiliar_functions.clear()
            print(f"\n[ + ] CONFIGURING  THE {_+1}º DEVICE...\n")
            if len(args) < 1:
                print(
                    f"\n\n[ ! ] You must choose at least one configuration (see README or documentation)."
                )
                auxiliar_functions.close()
            if device.connection_protocol == "TELNET":
                try:
                    self.__login_over_telnet(device)
                    self.__configure(device, args)
                    self.__obj_connnect.close()
                except Exception as e:
                    print(f"\n\n{e}")
                    auxiliar_functions.close()
            else:
                try:
                    self.__login_over_ssh(device)
                    self.__configure(device, args)
                    self.__obj_connect.close()
                except Exception as e:
                    print(f"\n\n{e}")
                    auxiliar_functions.close()
        auxiliar_functions.close()

    @property
    def vlans_to_configure(self):
        return self.__vlans_to_configure

    @vlans_to_configure.setter
    def vlans_to_configure(self, vlans):
        self.__vlans_to_configure = vlans

    def __login_over_telnet(self, device):
        """ Creates an Telnet client object, then tries to login to the device using its attributes.

            Args:
                device (:obj: `Device`): The device that the manager will connect to.

        """
        try:
            self.__obj_connect = Telnet(device.ip_address, "23", 5)
            # self.obj_connect.set_debuglevel(1) # uncomment this line to enable debug for Telnet obj.
        except Exception as error:
            print(f"[ ! ] {error}.")
            auxiliar_functions.close()
        else:
            if device.vty_username != "":
                self.__obj_connect.read_until(b"Username:"******"\n")
                sleep(0.5)
            self.__obj_connect.read_until(b"Password:"******"\n")
            sleep(0.5)
            self.__identify_errors(device)
            self.__obj_connect.write(b"enable\n")
            self.__obj_connect.read_until(b"Password:"******"\n")
            sleep(0.5)
            self.__identify_errors(device)

    def __login_over_ssh(self, device):
        """ Creates an SSHClient object, load the keys, then tries to login to the device using its attributes.

            Args:
                device (:obj: `Device`): The device that the manager will connect to.

        """
        self.__obj_connect = SSHClient()
        self.__obj_connect.load_system_host_keys()
        self.__obj_connect.set_missing_host_key_policy(AutoAddPolicy())
        try:
            self.__obj_connect.connect(device.ip_address, 22,
                                       device.vty_username,
                                       device.vty_password)
            self.__shell = self.__obj_connect.invoke_shell(
            )  # Opens a shell to run commands.
        except Exception as error:
            print(f"[ ! ] {error}")
            self.__obj_connect.close()
            auxiliar_functions.close()
        else:
            self.__identify_errors(device)
            self.__shell.send(b"enable\n")
            sleep(0.5)
            self.__shell.send(device.enable_secret.encode() + b"\n")
            sleep(0.5)
            self.__identify_errors(device)

    def __configure(self, device, configurations):
        """ Start running commands on device if the configurations keys exists on 'config.json' file.

            Args:
                device (:obj: `Device`): The device to be configured.
                configurations (str): The command keys based on 'config.json'.

        """
        for config_key in configurations:
            if config_key in self.configuration_keywords:
                if config_key == 'SET_HOSTNAME':
                    variables["DEVICE_HOSTNAME"] = input("[->] Set hostname: ")
                    self.__send_commands(device, config_key)
                elif config_key == 'CREATE_VLAN' or config_key == 'DELETE_VLAN':
                    if self.__vlans_to_configure == None:
                        print(
                            "\n\n[ ! ] You must specify the VLANs to be configured!(see README or documentation.)"
                        )
                        auxiliar_functions.close()
                    for vlan in self.__vlans_to_configure:
                        if int(vlan) not in range(3968,
                                                  4048) and int(vlan) != 4094:
                            self.variables["VLAN_NUMBER"] = vlan
                            self.__send_commands(device, config_key)
                    self.variables["VLAN_NUMBER"] = self.vlans_to_configure[0]
                else:
                    self.__send_commands(device, config_key)
            else:
                print(
                    f"\n\n[ ! ] There is no valid configuration for '{config_key}'."
                )
                auxiliar_functions.close()

    def __send_commands(self, device, config_key):
        """ Run the commands on the device based on the choosen configuration keyword.

            Args:
                device (:obj: `Device`): The device that will receive the commands.
                config_key (int): The configuration key to run based on the 'config.json' file.

        """
        commands = list(self.config_json[config_key].values())[0]
        if device.connection_protocol == "SSH":
            for command in commands:
                found_key = list(filter(lambda key: key in command, self.keys))
                if len(found_key) > 0:
                    self.__shell.send(
                        command.replace(
                            found_key[0],
                            self.variables[found_key[0]]).encode('ascii'))
                    self.__identify_errors(device)
                    sleep(0.6)
                else:
                    self.__shell.send(command.encode())
                    self.__identify_errors(device)
                    sleep(0.6)
        else:
            for command in commands:
                found_key = list(
                    filter(lambda key: key in command,
                           self.keys))  # found commands to be replaced.
                if len(found_key) > 0:
                    self.__obj_connect.write(
                        command.replace(
                            found_key[0],
                            self.variables[found_key[0]]).encode('ascii'))
                    self.__identify_errors(device)
                    sleep(
                        0.6
                    )  # timeout before send another command to prevent errors.
                else:
                    self.__obj_connect.write(command.encode('ascii'))
                    self.__identify_errors(device)
                    sleep(0.6)

    def __identify_errors(self, device):
        """ Handle the command output to verify if there is errors based on a dict with some errors keywords
            and its descriptions.

            Args:
                device (:obj: `Device`): The current device being configurated.

        """
        def find_error_on_line(output_line):
            """ Verify if the output command line has errors based on a predefined dict.

                Args:
                    output_line (str): The device output command line to be verified.

            """
            found_error = list(
                filter(lambda error: error in output_line, errors_keywords))
            if len(found_error) > 0:
                print(f"[ ! ] {errors_dict[found_error[0]]}")
                auxiliar_functions.close()
            else:
                print(output_line, end='')

        errors_dict = {
            '% Login invalid':
            "\n\n[ ! ] Invalid VTY login credentials!",
            '% Bad passwords':
            "\n\n[ ! ] Invalid VTY password!",
            '% Bad secrets':
            "\n\n[ ! ] Invalid secret! Can't configure",
            '% No password set':
            "\n\n[ ! ] No enable password configured on device! Cant run scripts.",
            'Translating':
            "\n\n[ ! ] Username not configured on device. Set a username or leave it blank.",
            'number which is out of the range 1..4094':
            "\n\n[ ! ] Invalid vlan number!"
        }
        errors_keywords = [key for key in errors_dict]
        if device.connection_protocol == 'TELNET':
            errors_keywords = [key for key in errors_dict]
            line = self.__obj_connect.read_very_eager().decode('ascii')
            if '% Do you really want to replace them?' in line or '% You already have RSA keys defined' in line:
                self.__obj_connect.write(b'no\n')
            find_error_on_line(line)
        else:
            line = self.__shell.recv(65535).decode('ascii')
            if '% Do you really want to replace them?' in line or '% You already have RSA keys defined' in line:
                self.__shell.send(b'no\n')
            find_error_on_line(line)