diff --git a/climatic/CoreCli.py b/climatic/CoreCli.py index 3f7934d..dd2bfeb 100644 --- a/climatic/CoreCli.py +++ b/climatic/CoreCli.py @@ -50,6 +50,7 @@ def __init__(self, connection, username: Optional[str]='admin', password: Optional[str]='admin', + identification: Optional[str]='', timeout: Optional[int]=15, quiet: Optional[bool]=False, marker: Optional[str]="#", @@ -103,6 +104,8 @@ def __init__(self, self.wait_cmd_timeout = wait_cmd_timeout if not hasattr(self, 'strip_cmds'): self.strip_cmds = strip_cmds + if not hasattr(self, 'identification'): + self.identification = identification self.logger = Logger.start(self.name) self.connection = connection @@ -357,10 +360,13 @@ def _sync(self, marker: str): @param marker regex used to identify the start of a command line. """ - # consume all markers since last 'expect' - while self.connection.terminal.expect([marker, pexpect.TIMEOUT], timeout=0.01) == 0: - continue - + try: + while True: + self.connection.terminal.read_nonblocking(size=1024, timeout=0.1) + except pexpect.exceptions.TIMEOUT: + pass + except pexpect.exceptions.EOF: + pass def _get_prompt_size(self) -> int: """ Returns the prompt size: the number of visible chars from the beginning of the diff --git a/climatic/cli/Linux.py b/climatic/cli/Linux.py index 46b6dff..d437194 100644 --- a/climatic/cli/Linux.py +++ b/climatic/cli/Linux.py @@ -3,6 +3,7 @@ from typing import Optional from ..CoreCli import CoreCli +from ..connections.Ser2Net import Ser2Net from ..connections.Ssh import Ssh, PTY_WINSIZE_COLS from ..connections.Ssh import PTY_WINSIZE_COLS as SSH_PTY_WINSIZE_COLS @@ -42,6 +43,7 @@ def __init__(self, username: str, password: str, port: Optional[int]=22, + id_file: Optional[str]=None, **opts): """ Initialize Linux Shell. @param ip IP address of target. Ex: '234.168.10.12' @@ -49,13 +51,15 @@ def __init__(self, @param password String with password corresponding to the username to login into the connection that provides access to the CLI. @param port Port used for SSH connection. Defaults to 22 + @param id_file Path of file to be used as user identification (usually a private key) @param opts Same options as CoreCli initializer. """ if not 'marker' in opts: opts['marker'] = '#|>' self.name = "Linux.SSH" - ssh = Ssh(ip, username, port=port) + self.has_id = id_file != None + ssh = Ssh(ip, username, port=port, identification=id_file) Linux.__init__(self, ssh, username=username, @@ -83,3 +87,55 @@ def logout(self): """ Logout from CLI interface. """ self.connection.terminal.sendline('exit') + + +#################################################################################################### +## Ser2NetLinux + +class Ser2NetLinux(SshLinux): + """ Connects to a remote Linux Shell using SSH. + Core implementation is done by Ssh and Linux. + """ + + def __init__(self, + ip: str, + port: int, + username: str, + password: str, + **opts): + """ Initialize Linux Shell. + @param ip IP address of ser2net server. Ex: '234.168.10.12' + @param port Port used for ser2net connection. + @param username username for opening ser2net connection + @param password String with password corresponding to the username to login into + the connection that provides access to the CLI. + + @param opts Same options as CoreCli initializer. + """ + if not 'marker' in opts: + opts['marker'] = '#|>' + + self.name = "Linux.Ser2Net" + ser2net = Ser2Net(ip, port) + Linux.__init__(self, + ser2net, + username=username, + password=password, + pty_winsize_cols=SSH_PTY_WINSIZE_COLS, + **opts) + + def login(self): + """ Login to CLI interface. + """ + while True: + index = self.connection.terminal.expect( + ['.ogin', '.assword', self.marker], + timeout=10) + + if index == 0: + self.connection.terminal.sendline(self.username) + if index == 1: + self.connection.terminal.waitnoecho() + self.connection.terminal.sendline(self.password) + if index >= 2: + break diff --git a/climatic/connections/Ser2Net.py b/climatic/connections/Ser2Net.py index af741c8..9c318db 100644 --- a/climatic/connections/Ser2Net.py +++ b/climatic/connections/Ser2Net.py @@ -6,7 +6,6 @@ PTY_WINSIZE_ROWS = 24 PTY_WINSIZE_COLS = 500 - class Ser2Net(Connection): """ Connects to a CLI using Ser2net. The Ser2Net should be available and configured with an IP and port @@ -36,7 +35,7 @@ def connect(self, logfile, logger=None): self.terminal.setwinsize(PTY_WINSIZE_ROWS, PTY_WINSIZE_COLS) def disconnect(self, logger=None): - """ For Ser2Net, the connection needs to be explicitly closed + """ For Ser2Net, the connection is closed during the logout @param logger Optional logger for debug messages """ if logger != None: diff --git a/climatic/connections/Ssh.py b/climatic/connections/Ssh.py index cf74ee1..aa6cd73 100644 --- a/climatic/connections/Ssh.py +++ b/climatic/connections/Ssh.py @@ -14,17 +14,19 @@ class Ssh(Connection): The device should have the IP configured. """ - def __init__(self, ip: str, user: str, port=SSH_PORT, ciphers: str = None): + def __init__(self, ip: str, user: str, port=SSH_PORT, ciphers: str = None, identification: str = None): """ Initialize the SSH connection object. @param ip IP address to connect to. Ex: '192.168.33.4'. @param user The SSH connection user. @param port The SSH connection port. Default is 22. - @param ciphers A comma sepparated list of ciphers. Ex: 'blowfish-cbc,3des-cbc' + @param ciphers A comma sepparated list of ciphers. Ex: 'blowfish-cbc,3des-cbc' + @param identification Path of file to be used for "-i" argument, usually a private key """ self.user = user self.ip = ip self.port = port self.ciphers = ciphers + self.identification = identification Connection.__init__(self) @@ -40,8 +42,13 @@ def connect(self, logfile, logger=None): if self.ciphers != None: cipher_spec = '-c {}'.format(self.ciphers) - self.terminal = pexpect.spawn('ssh -p {2} {0}@{1} {3}'.format( - self.user, self.ip, self.port, cipher_spec), logfile=logfile, encoding='utf-8') + if self.identification != None: + self.terminal = pexpect.spawn('ssh -i {4} -p {2} {0}@{1} {3}'.format( + self.user, self.ip, self.port, cipher_spec, self.identification), logfile=logfile, encoding='utf-8') + else: + self.terminal = pexpect.spawn('ssh -p {2} {0}@{1} {3}'.format( + self.user, self.ip, self.port, cipher_spec), logfile=logfile, encoding='utf-8') + self.terminal.setwinsize(PTY_WINSIZE_ROWS, PTY_WINSIZE_COLS) def disconnect(self, logger=None):