Paramiko connection to Windows fails silently (appears to succeed) - python

I am attempting to use paramiko to send powershell commands over ssh to a Windows box with OpenSSH on it. The commands appear to be successful (return code 0) even when they should fail, and I'm not getting any output on the pipes. When I try commands like making a directory, it is not created, which makes it seem as though the commands aren't reaching the remote system, but also aren't throwing errors.
First, here's my code:
version = self.oscall.run_remote(['java', '-version'])
def run_remote(self, command): # Command is a list of command + args
string = ""
self.timeout = 300
for arg in command:
string = string + " " + arg
self.client.connect(self.host, username=self.user, password=self.pw, timeout=self.timeout)
self.transport = self.client.get_transport()
self.transport.set_keepalive(1)
self.channel = self.transport.open_session(timeout=self.timeout) # transport is abstract connection, session is socket
if self.channel.gettimeout() == None: self.channel.settimeout(self.timeout)
self.channel.exec_command(string)
self.out = self.channel.makefile()
self.err = self.channel.makefile_stderr()
self.output = CallOutput(self.out, self.err, None)
self.output.returncode = self.channel.recv_exit_status()
self.channel.close()
return self.output
class CallOutput(object):
def __init__(self, out, err, rc):
self.out = out.readlines()
self.err = err.readlines()
self.outfile = tempfile.TemporaryFile()
for line in self.out:
if isinstance(line, unicode): line = line.encode('utf-8')
self.outfile.write(line + '\n')
self.outfile.seek(0)
self.errfile = tempfile.TemporaryFile()
for line in self.err:
if isinstance(line, unicode): line = line.encode('utf-8')
self.errfile.write(line + '\n')
self.errfile.seek(0)
self.returncode = rc
Sorry for the wall of text, but I went for completeness. This is part of a larger application.
This code works perfectly connecting to Linux, so I don't expect there to be many little bugs. The returncode is always 0, even for garbage, and there is never any output on the pipes. If I run the command just using the terminal, I get the correct output:
$ ssh testuser#testwin.internal.com 'java -version'
Warning: Permanently added 'testwin.internal.com,10.10.10.12' (ECDSA) to the
list of known hosts.
testuser#testwin.internal.com's password:
java version "1.8.0_121"
Java(TM) SE Runtime Environment (build 1.8.0_121-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.121-b13, mixed mode)
$ echo $?
0
$ ssh testuser#testwin.internal.com 'foo'
foo : The term 'foo' is not recognized as the name of a cmdlet, function, script file, or
operable program. Check the spelling of the name, or if a path was included, verify that the path
is correct and try again.
At line:1 char:1
+ foo
+ ~~~
+ CategoryInfo : ObjectNotFound: (foo:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
$ echo $?
1
The only difference between our Linux and Windows processes that I can think of is that on Windows we have to use a password, as we haven't setup passwordless ssh yet. What weird Windows idiosyncrasy am I missing? Any insights would be greatly appreciated.

As usual, it turns out to be way simpler than I was thinking.
string = ""
for arg in command:
string = string + " " + arg
The above code produces " whatever command was passed in" and it turns out that Windows reacts extremely poorly to the preceding space, while linux didn't care. The new code snippet is:
string = ""
first = True
for arg in command:
if first:
string = string + arg
first = False
else:
string = string + " " + arg
I'm leaving the title and details the same, to hopefully help anyone who makes the exact same error I did.

Related

Pexect in Windows Batch File > Cygwin > Python > SSH

I have a Linux box that runs Cisco IOS and need to SSH into it sometimes to reboot it. I've written a batch file that calls on Cygwin. Cygwin then calls on Python+PythonScript.
Batch File:
cd c:\cygwin64\bin
bash --login -i -c "python3 /home/Owner/uccxtesting.py"
Python Script
import pexpect
import time
import sys
server_ip = "10.0.81.104"
server_user = "administrator"
server_pass = "secretpassword"
sshuccx1 = pexpect.spawn('ssh %s#%s' % (server_user, server_ip))
sshuccx1.logfile_read = sys.stdout.buffer
sshuccx1.timeout = 180
sshuccx1.expect('.*password:')
sshuccx1.sendline(server_pass)
sshuccx1.expect('.admin:')
sshuccx1.sendline('utils system restart')
sshuccx1.expect('Enter (yes/no)?')
sshuccx1.sendline('yes')
time.sleep(30)
When I run this, it stops at Enter yes/no. This is what I'm getting:
I've seen plenty of examples of pexpect with expect, but there is some white space out beside the question mark. I just don't know how to tell python to expect it.
There may be a bug:
utils system restart prompts for force restart (https://bst.cisco.com/bugsearch/bug/CSCvw22828)
Replace time.sleep(30) with the following code to answer a possible force restart prompt. If it works, you can get rid of the try...except and print commands that I added for debugging:
try:
index = -1
while index != 0:
sshuccx1.expect_exact(['succeeded', 'force', ], timeout=300)
if index == 2:
print('Forcing restart...')
sshuccx1.sendline('yes')
print('Operation succeeded')
print(str(child.before))
except pexpect.ExceptionPexpect:
e_type, e_value, _ = sys.exc_info()
print('Error: ' + pexpect.ExceptionPexpect(e_type).get_trace())
print(e_type, e_value)
Also, change sshuccx1.expect('Enter (yes/no)?') to sshuccx1.expect_exact('Enter (yes/no)?'). The expect method tries to match a regex pattern, and it may get caught on the parentheses (see https://pexpect.readthedocs.io/en/stable/api/pexpect.html#pexpect.spawn.expect_exact)

Nmap not found when run from Python script

I have written basic port scanner for target ip and when I run it through kali vm it says sh: 1: nmap-F192.168.234.135: not found. but when I run nmap -F 192.168.234.135 ... its perfectly working. Can anyone point out the reason behind it. thanks
import os
def get_nmap(options,ip):
command = "nmap" + options + "" + ip
process = os.popen(command)
result = str(process.read())
return result
print(get_nmap('-F','192.168.234.135'))
Better, using the subprocess module:
def get_nmap(options, ip) :
return subprocess.check_output(["nmap", options, ip])
#end get_nmap
You need to add spaces in the command string. Change it to
command = "nmap " + options + " " + ip

Python subprocess (incorrectly?) merging stderr and stdout for ssh

I'm trying to run various linux commands via python's subprocess and ssh. I'd like to be able to run the command and check the stderr and stdout lengths to determine if the command was successful or not. For example if there's no error it was successful. The problem is that after the initial connection all the output is going to stdout.
I did try using paramiko but kept getting unreliable authentication behaviour. This approach seems more robust, if I could just get it to work.
import subprocess
import os
import pty
from select import select
class open_ssh_helper(object):
def __init__(self, host="127.0.0.1", user="root"):
self.host = host
self.user = user
self.cmd = ['ssh',
user +'#'+host,
"-o", "StrictHostKeyChecking=no",
"-o", "UserKnownHostsFile=/dev/null"]
self.prompt = '~#'
self.error = ''
self.output = ''
self.mtty_stdin, self.stty_stdin = pty.openpty()
self.mtty_stdout, self.stty_stdout = pty.openpty()
self.mtty_stderr, self.stty_stderr = pty.openpty()
self.ssh = subprocess.Popen(self.cmd,
shell=False,
stdin=self.stty_stdin,
stdout=self.stty_stdout,
stderr=self.stty_stderr)
self._read_stderr()
self._read_stdout()
def _reader(self, read_pty):
char = ""
buf = ""
while True:
rs, ws, es = select([read_pty], [], [], 0.5)
if read_pty in rs:
char = os.read(rs[0], 1)
buf += char
else:
break
return buf
def _read_stderr(self):
self.error = self._reader(self.mtty_stderr)
def _read_stdout(self):
self.output = self._reader(self.mtty_stdout)
def execute(self, command):
os.write(self.mtty_stdin, command)
self._read_stderr()
self._read_stdout()
if __name__=='__main__':
ss = open_ssh_helper('10.201.202.236', 'root')
print "Error: \t\t: " + ss.error
print "Output: \t: " + ss.output
ss.execute("cat /not/a/file\n")
print "Error: \t\t: " + ss.error
print "Output: \t: " + ss.output
Which outputs something like:
Error: : Warning: Permanently added '10.201.202.236' (ECDSA) to the list of known hosts.
Debian GNU/Linux 8
BeagleBoard.org Debian Image xxxx-xx-xx
Support/FAQ: http://elinux.org/Beagleboard:BeagleBoneBlack_Debian
Output: : Last login: Thu Oct 5 07:32:06 2017 from 10.201.203.29
root#beaglebone:~#
Error: :
Output: : cat /not/a/file
cat: /not/a/file: No such file or directory
root#beaglebone:~#
My hope was that the line cat: /not/a/file: No such file or directory would be printed as the error line above it. However it seems that for some reason the stdout is printing both the output and the error.
ssh pulls back both stdout and stderr on the remote system as a single stream so they both come through on the ssh stdout.
If you want to separate the two streams on the remote system you will have to do that by redirecting one or both of them to a remote file and then reading the files. Or less reliably but possibly easier, you could redirect stderr through a filter that puts something like 'STDERR:' on the start of each line and split the streams again that way.

Does the ampersand need to be escaped?

I am attempting to create a function in python 2.7.8 to delete files that are no longer needed on a remote PC.
If I run it from the command line I am able to do this:
C:\>del /f /q \\RemotePC\d$\temp\testfile.txt && if exist \\RemotePC\d$\temp\testfile.txt (set errorlevel=1) else (set errorlevel=0)
From this I get the expected result:
C:\>echo %errorlevel%
0
But when I attempt to put this into my python function it does not work. The test variable does not get set.
Here is what I have in my python function:
def DelFile(self, address, del_file):
cmd_line = "del /f /q \\\\" + address + del_file + "\&\& if exist \"\\\\" + address + del_file + "\" (set errorlevel=1) else (set errorlevel=0)"
myP = subprocess.Popen(cmd_line, shell=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
out = timeout(myP.communicate, (), {}, 120, (None, None))
result = out[0]
error = out[1]
error_code = None
error_code = myP.returncode
notes = []
return cmd_line, result, error, error_code, notes
I have verified that the address and del_file variables are formatted correctly in order to get me to the desired file. I have also verified that it deletes or does not delete the intended file based on the circumstances. But for some reason the test variable never gets set.
It is my understanding that the ampersand character needs to be escaped, like so "\&" when being used in a string. Is this not true or am I not escaping it correctly?
Your cmd_line should look like this
cmd_line = "del /f /q \\\\{0}{1} & if exist \\\\{0}{1} (set test=1) else (set test=0)".format(address, del_file)
Try using one &
I figured out the issue, turns out I actually had a few. The first being that the ampersand apparently does not need to be escaped (Thank you FirebladeDan), the second being that I was missing a space before the ampersands (Thank you again to FirebladeDan for the tip with formatting, that is how I noticed the missing space). The last being more of a CMD issue than python, I was trying to directly set the errorlevel variable, when I should have been using the exit /b with the respective 1 or 0.
As a side note, I also added in the use of psexec, it was not necessary for the solution, it just helps with the performance of the overall program.
Here is what I ended up with:
def DelFile(self, address, del_file):
cmd_line = r'psexec \\{0} cmd /c "del /f /q "{1}" && if exist "{1}" (exit /b 1) else (exit /b 0)"'.format(address, del_file)
myP = subprocess.Popen(cmd_line, shell=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
out = timeout(myP.communicate, (), {}, 60, (None, None))
result = out[0]
error = out[1]
error_code = None
error_code = myP.returncode
notes = []
return cmd_line, result, error, error_code, notes

How to get the actual shell prompt string in Python?

I have a Python routine which invokes some kind of CLI (e.g telnet) and then executes commands in it. The problem is that sometimes the CLI refuses connection and commands are executed in the host shell resulting in various errors. My idea is to check whether the shell prompt alters or not after invoking the CLI.
The question is: how can I get the shell prompt string in Python?
Echoing PS1 is not a solution, because some CLIs cannot run it and it returns a notation-like string instead of the actual prompt:
SC-2-1:~ # echo $PS1
\[\]\h:\w # \[\]
EDIT
My routine:
def run_cli_command(self, ssh, cli, commands, timeout = 10):
''' Sends one or more commands to some cli and returns answer. '''
try:
channel = ssh.invoke_shell()
channel.settimeout(timeout)
channel.send('%s\n' % (cli))
if 'telnet' in cli:
time.sleep(1)
time.sleep(1)
# I need to check the prompt here
w = 0
while (channel.recv_ready() == False) and (w < timeout):
w += 1
time.sleep(1)
channel.recv(9999)
if type(commands) is not list:
commands = [commands]
ret = ''
for command in commands:
channel.send("%s\r\n" % (command))
w = 0
while (channel.recv_ready() == False) and (w < timeout):
w += 1
time.sleep(1)
ret += channel.recv(9999) ### The size of read buffer can be a bottleneck...
except Exception, e:
#print str(e) ### for debugging
return None
channel.close()
return ret
Some explanation needs here: the ssh parameter is a paramiko.SSHClient() instance. I use this code to login to a server and from there I call another CLI which can be SSH, telnet, etc.
I’d suggest sending commands that alter PS1 to a known string. I’ve done so when I used Oracle sqlplus from a Korn shell script, as coprocess, to know when to end reading data / output from the last statement I issued. So basically, you’d send:
PS1='end1>'; command1
Then you’d read lines until you see "end1>" (for extra easiness, add a newline at the end of PS1).

Categories