From fb5cbdf10ed7e7cf1db4ffa6b2298037cb2db74e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B4natas=20Rech?= Date: Sun, 17 Oct 2021 22:44:53 -0300 Subject: [PATCH 1/5] Add missing Ser2NetLinux class Also fix typos on connections/Ser2Net.py. --- climatic/cli/Linux.py | 53 +++++++++++++++++++++++++++++++++ climatic/connections/Ser2Net.py | 3 +- 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/climatic/cli/Linux.py b/climatic/cli/Linux.py index 46b6dff..c6e353d 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 @@ -83,3 +84,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: From 6a90b10d52e5d16f5fd91bdf83da7fd1a2727f2d Mon Sep 17 00:00:00 2001 From: Jonatas Romani Rech Date: Wed, 5 Oct 2022 10:24:48 -0300 Subject: [PATCH 2/5] Add support for "-i" argument of ssh --- climatic/cli/Linux.py | 9 ++++++++- climatic/connections/Ssh.py | 15 +++++++++++---- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/climatic/cli/Linux.py b/climatic/cli/Linux.py index c6e353d..bac48aa 100644 --- a/climatic/cli/Linux.py +++ b/climatic/cli/Linux.py @@ -43,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' @@ -50,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, @@ -67,6 +70,10 @@ def __init__(self, def login(self): """ Login to CLI interface. """ + if self.has_id: + # Already logged in + return + while True: index = self.connection.terminal.expect( ['Are you sure you want to continue connecting', '.assword', self.marker], diff --git a/climatic/connections/Ssh.py b/climatic/connections/Ssh.py index cf74ee1..9b096e2 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 {0} -p {2} {1} {3}'.format( + self.identification, self.ip, self.port, cipher_spec), 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): From 4e2e94f588e32210714f29cded122629afd0154e Mon Sep 17 00:00:00 2001 From: Jonatas Romani Rech Date: Wed, 5 Oct 2022 16:02:07 -0300 Subject: [PATCH 3/5] WIP --- climatic/CoreCli.py | 3 +++ climatic/cli/Linux.py | 4 ---- climatic/connections/Ssh.py | 4 ++-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/climatic/CoreCli.py b/climatic/CoreCli.py index 3f7934d..dfe1146 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 diff --git a/climatic/cli/Linux.py b/climatic/cli/Linux.py index bac48aa..d437194 100644 --- a/climatic/cli/Linux.py +++ b/climatic/cli/Linux.py @@ -70,10 +70,6 @@ def __init__(self, def login(self): """ Login to CLI interface. """ - if self.has_id: - # Already logged in - return - while True: index = self.connection.terminal.expect( ['Are you sure you want to continue connecting', '.assword', self.marker], diff --git a/climatic/connections/Ssh.py b/climatic/connections/Ssh.py index 9b096e2..aa6cd73 100644 --- a/climatic/connections/Ssh.py +++ b/climatic/connections/Ssh.py @@ -43,8 +43,8 @@ def connect(self, logfile, logger=None): cipher_spec = '-c {}'.format(self.ciphers) if self.identification != None: - self.terminal = pexpect.spawn('ssh -i {0} -p {2} {1} {3}'.format( - self.identification, self.ip, self.port, cipher_spec), logfile=logfile, encoding='utf-8') + 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') From 20a72db764edc7f543f5e2897f6bd4e9d549f64e Mon Sep 17 00:00:00 2001 From: Jonatas Rech Date: Mon, 21 Apr 2025 12:29:53 -0300 Subject: [PATCH 4/5] Use custom Connection._sync --- climatic/CoreCli.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/climatic/CoreCli.py b/climatic/CoreCli.py index dfe1146..f4b9de5 100644 --- a/climatic/CoreCli.py +++ b/climatic/CoreCli.py @@ -360,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.expect.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 From 89bdc39b8939ab8b1315324e13b97c523a5383b3 Mon Sep 17 00:00:00 2001 From: Jonatas Rech Date: Mon, 21 Apr 2025 12:35:54 -0300 Subject: [PATCH 5/5] Fix _sync --- climatic/CoreCli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/climatic/CoreCli.py b/climatic/CoreCli.py index f4b9de5..dd2bfeb 100644 --- a/climatic/CoreCli.py +++ b/climatic/CoreCli.py @@ -362,7 +362,7 @@ def _sync(self, marker: str): """ try: while True: - self.connection.terminal.expect.read_nonblocking(size=1024, timeout=0.1) + self.connection.terminal.read_nonblocking(size=1024, timeout=0.1) except pexpect.exceptions.TIMEOUT: pass except pexpect.exceptions.EOF: