1

I am trying to connect to a device using SSH. Earlier I was trying PXSSH but it was throwing EOF Exception. Please refer here: PXSSH Connection fails sometimes randomly after upgrading to Python3. Now I am trying to use pexpect to connect the device but I am facing the same problem. The code I have written is below:

class MyPXSSH(spawn):
    def __init__(self, *args, **kwargs):
        super(MyPXSSH, self).__init__(None, *args, **kwargs)
        self.PROMPT = r"\[PEXPECT\][\$\#] "
        self.PROMPT_SET_SH = r"PS1='[PEXPECT]\$ '"
        self.PROMPT_SET_CSH = r"set prompt='[PEXPECT]\$ '"

    def login(self, server, username=None, password='', port=None,
              cmd='ssh', sync_multiplier=5, check_local_ip=False,
              auto_prompt_reset=True):

        if not check_local_ip:
            cmd += " -o'NoHostAuthenticationForLocalhost=yes' -o RSAAuthentication=no" \
                   " -o UserKnownHostsFile=/dev/null -o PubkeyAuthentication=no -o CheckHostIP=no" \
                   " -o StrictHostKeyChecking=no -q"

        cmd += " -p {} {}@{}".format(port, username, server)
        spawn._spawn(self, cmd)
        expect_list = [r"(?i)are you sure you want to continue connecting", r"[$#]", "password:", pexpect.EOF, pexpect.TIMEOUT]

        i = self.expect(expect_list)
        if i == 0:
            self.sendline('yes')
            i = self.expect(expect_list)
        if i == 2:
            self.sendline(password)
            i = self.expect(expect_list)
            if i == 2:
                self.sendline(password)
                i = self.expect(expect_list)
                if i == 2:
                    log.error("PERMISSION DENIED. PLEASE CHECK PASSWORD.")
                    return False
                elif i == 3 or i == 1:
                    if not self.isalive():
                        raise Exception("Error connecting...")
                    log.info("CONNECTED!")
                    log.info("HERE 1")
                    pass
                elif i == 4:
                    return False
            elif i == 3 or i == 1:
                if not self.isalive():
                    log.info(">>>> STATE: {}".format(i))
                    raise Exception("Error connecting...")
                log.info("CONNECTED!")
                log.info("HERE 2")
                pass
            elif i == 4:
                return False
        elif i == 3 or i == 4:
            raise Exception("Error connecting...")

        if not self.isalive():
            raise Exception("Error connecting...")

        self.sendline()
        self.expect([pexpect.TIMEOUT, r'[$#]'], timeout=10)

        if auto_prompt_reset and not self.set_unique_prompt():
            raise Exception("failed to set prompt!")

        log.info("CONNECTED!")
        return True

    def prompt(self, timeout=-1):
        if timeout == -1:
            timeout = self.timeout
        i = self.expect([self.PROMPT, TIMEOUT], timeout=timeout)
        if i==1:
            return False
        return True

    def set_unique_prompt(self):
        self.sendline("unset PROMPT_COMMAND")
        self.sendline(self.PROMPT_SET_SH) # sh-style
        i = self.expect ([TIMEOUT, self.PROMPT], timeout=10)
        if i == 0:  # csh-style
            self.sendline(self.PROMPT_SET_CSH)
            i = self.expect([TIMEOUT, self.PROMPT], timeout=10)
            if i == 0:
                return False
        return True

The connection does receive a password prompt but then after sending password it returns EOF. Notes:
OS: macOS
Code works with Python 2.7 and Pexpect 4.2.0 but fails with Python 3.9.4 with Pexpect 4.8.0
Code also works with other ssh clients like Paramiko. [Cannot use Paramiko because of some other problems with the types of commands that can be sent and some other required features that are supported by pexpect but not Paramiko]

LOGS:

2021-07-14 19:01:56 - device.py:124 - INFO - Attemping to connect to device on port 10022.
user@localhost's password: password

2021-07-14 19:01:57 - ssh.py:55 - INFO - >>>> STATE: 3
2021-07-14 19:01:57 - ssh.py:113 - CRITICAL - Error connecting...
user@localhost's password: password

2021-07-14 19:02:27 - ssh.py:55 - INFO - >>>> STATE: 3
2021-07-14 19:02:27 - ssh.py:113 - CRITICAL - Error connecting...

EDIT: output of ssh with -vv

debug1: Next authentication method: password
user@localhost's password: password

debug2: we sent a password packet, wait for reply
debug1: Authentication succeeded (password).
Authenticated to localhost ([::1]:10022).
debug2: fd 6 setting O_NONBLOCK
debug1: channel 0: new [client-session]
debug2: channel 0: send open
debug1: Requesting no-more-sessions@openssh.com
debug1: Entering interactive session.
debug1: pledge: network
debug1: client_input_global_request: rtype hostkeys-00@openssh.com want_reply 0
debug2: callback start
debug2: fd 5 setting TCP_NODELAY
debug2: client_session2_setup: id 0
debug2: channel 0: request shell confirm 1
debug2: callback done
debug2: channel 0: open confirm rwindow 0 rmax 32768
debug2: channel 0: rcvd adjust 2097152
debug2: channel_input_status_confirm: type 99 id 0
debug2: shell request accepted on channel 0
debug2: channel 0: read<=0 rfd 6 len 0
debug2: channel 0: read failed
debug2: channel 0: close_read
debug2: channel 0: input open -> drain
debug2: channel 0: ibuf empty
debug2: channel 0: send eof
debug2: channel 0: input drain -> closed
debug1: client_input_channel_req: channel 0 rtype exit-status reply 0
debug2: channel 0: rcvd eof
debug2: channel 0: output open -> drain
debug2: channel 0: obuf empty
debug2: channel 0: close_write
debug2: channel 0: output drain -> closed
debug2: channel 0: rcvd close
debug2: channel 0: almost dead
debug2: channel 0: gc: notify user
debug2: channel 0: gc: user detached
debug2: channel 0: send close
debug2: channel 0: is dead
debug2: channel 0: garbage collecting
debug1: channel 0: free: client-session, nchannels 1
debug1: fd 0 clearing O_NONBLOCK
Transferred: sent 1896, received 2064 bytes, in 0.0 seconds
Bytes per second: sent 123695.8, received 134656.1
debug1: Exit status 0
Satya Prakash
  • 35
  • 1
  • 6
  • could you try `ssh -v` (or `-vv`) and see what's happening? – sexpect - Expect for Shells Jul 15 '21 at 02:30
  • Thanks for the reply. I added the output with -vv option. I am not sure I understand why EOF is received from the logs. – Satya Prakash Jul 15 '21 at 02:52
  • according to the log the ssh auth is successful. which line of code reported EOF? – sexpect - Expect for Shells Jul 15 '21 at 03:45
  • Could you show how `_spawn()` is defined? – pynexj Jul 15 '21 at 03:45
  • @sexpect-ExpectforShells the line "log.info(">>>> STATE: {}".format(i))" prints state as 3 which corresponds to EOF. So after password is sent the connection receives EOF. – Satya Prakash Jul 15 '21 at 03:53
  • @pynexj The _spawn() method is part of the pexpect spawn class: https://github.com/pexpect/pexpect/blob/master/pexpect/pty_spawn.py – Satya Prakash Jul 15 '21 at 03:53
  • I don't know much about the internal details. Any reason you don't call the documented `spawn()` directly? – pynexj Jul 15 '21 at 05:05
  • @pynexj No specific reason to use _spawn it just made the implementation a bit cleaner for me. But I tried spawn too and it gave the same error. – Satya Prakash Jul 15 '21 at 06:00
  • OK. Just realized you're implementing your own pxssh logic. – pynexj Jul 15 '21 at 06:07
  • When @meuh was suggesting you use `pexpect.spawn()` in another Q I don't think he meant to say reimplementing your own pxssh. :) – pynexj Jul 15 '21 at 06:56
  • @pynexj The problem is that the connection works properly in a standalone environment but fails to do so when I use in my tool. But I don't think an old connection which was closed should in anyway affect a new connection. I tried just pexpect spawn and creating multiple connections and closing and it worked but the same logic fails when used with some other operations I am doing in the tool. – Satya Prakash Jul 15 '21 at 23:58
  • @pynexj thanks for your help. I had to switch to paramiko to ensure a reliable connection. The code works now. – Satya Prakash Jul 21 '21 at 18:49

0 Answers0