fgimian / paramiko-expect

A Python expect-like extension for the Paramiko SSH library which also supports tailing logs.
MIT License
204 stars 78 forks source link

Long commands break output cleaning #28

Open voetsjoeba opened 7 years ago

voetsjoeba commented 7 years ago

If you send a command whose length + the prompt length exceeds the tty_width, spaces are inserted into the command line that gets echoed back out at positions where it linewraps. This breaks output cleaning because the echoed command line no longer matches the input.

On my Ubuntu 16.04 box, my banner and prompt looks like this:

$ ssh localhost
Welcome to Ubuntu 16.04.2 LTS (GNU/Linux 4.4.0-83-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

0 packages can be updated.
0 updates are security updates.

Last login: Fri Jul  7 02:33:10 2017 from 127.0.0.1
vmuser@vm:~$ 

Note that the prompt vmuser@vm:~$ has a space at the end, i.e. it is 13 characters long.

On this machine, running

import paramiko
import paramiko_expect as pe

if __name__ == "__main__":
    client = paramiko.SSHClient()
    client.load_system_host_keys()
    client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    client.connect(hostname="localhost", username="vmuser")

    with pe.SSHClientInteraction(client, timeout=10, display=False, tty_width=40) as interact:
        user_prompt = r".*\$\s+"
        interact.expect(user_prompt)

        interact.send("echo " + "a"*100)
        interact.expect(user_prompt)
        cmd_output = interact.current_output_clean

        print(cmd_output)

prints:

vmuser@vm:~/paramiko-expect$ ./main.py 
echo aaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

vmuser@vm:~/paramiko-expect$

Replace the "a"*100 with "a"*10, and it works as expected:

vmuser@vm:~/paramiko-expect$ ./main.py 
aaaaaaaaaa

vmuser@vm:~/paramiko-expect$
fruch commented 6 years ago

just of curiosity ? do you really need the echo of your own command ? :)

voetsjoeba commented 6 years ago

Well no, that's the issue, right? It's been a while since I reported this so forgive if I'm wrong, but as I understand it, current_output includes the command you sent, because the remote TTY echoes it back to you as you send it -- that's to be expected. current_output_clean is meant to fix that by looking for the command string you sent at the start of the output and stripping it off.

However, when the command is sufficiently long, it apparently gets these extra spaces inserted at TTY wrapping points, and the string match that the cleaner does to look for the command now fails. So now this chopped-up version of the command you sent appears in current_output_clean, while it should not be there at all.

fruch commented 6 years ago

o.k. now I get the problem you are talking about... I think I have a fix, I'll upload a branch, so you can check it out

fruch commented 6 years ago

@voetsjoeba please take this branch, and tell me if this solves your issue, cause this change is a bit tricky, and might break existing code for people.

I want to make sure it's actully solves your problem, before commit this to master and issuing a new release

fruch commented 6 years ago

see the branch, I've create for this issue. meanwhile I'm marking this as wontfix.

mooregit commented 4 years ago

I am running into this same issue. I modified my existing paramiko_expect.py with the branch changes you provided, but now it doesn't recognize the expect. It works prior to your changes but adds the spaces.

ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(ip, username=username, password=password)
interact = SSHClientInteraction(ssh, timeout=60, display=False)
interact.expect('Prompt')

for adj in list_of_adj:
    interact.send(adj)
    interact.expect('Prompt')
    output = interact.current_output_clean
    outputList.append([adj, output])

ssh.close()