2

I want to ssh into a server, and execute a number of bash commands on that server after logging in to that server, and I want to do that with a python script. I am limited to use subprocess (I am not allowed to import other modules like pexpect or paramiko) Here is the code I have so far:

import sys
import os
import subprocess
import time

user = "let's say a user"
host = "the remote server's ip"
sshCommand = "sshpass -p 'the remote server's password' ssh -o     UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no %s@%s" %(user, host)
process1 = subprocess.Popen(sshCommand, shell=True, stdout = subprocess.PIPE)
copyfileCommand = "scp afile.file user@serverip: path of server directory"
process2 = subprocess.Popen(copyfileCommand, shell=True, stdin = process1.stdout, stdout = subprocess.PIPE,  stderr=subprocess.STDOUT)

pwdcommand = "pwd"
process3 = subprocess.Popen(pwdcommand, shell=True, stdin = process2.stdout, stdout=subprocess.PIPE,  stderr=subprocess.STDOUT)
out, err = process3.communicate()[0]

From what I understand, to execute the second command after the first one, I need to set stdin of second command to the stdout of first command, and by the same logic, do it for the third command. However, when the script is executed to the third command, pwd gives me my local computer's path instead of the path on the remote server, and the file i want to copy to the remote server is also not copied. What am i doing wrong? This is just the first few command that I need to execute on the remote server, once I understand how it works, the other commands is easy.

thank you

2
  • 4
    Use the paramiko module instead. Commented Jan 27, 2015 at 15:49
  • 3
    This entire problem is exactly what Fabric is meant to solve. Commented Jan 27, 2015 at 16:02

3 Answers 3

1

It can be challenging to run the remote shell interactively because you are using a pipe instead of a pty (terminal). If you just want to send a canned set of commands, write them to your ssh's stdin as shown below. If you want to be interactive, you'll need to run the local ssh via the python pty module. You can use the pexpect source to see how that's done.

EDIT

Added code to escape the password on the local command. I didn't add any escapes to the remote commands assume the OP really does want to pass things like environment variables, but the same technique applies.

import sys
import os
import subprocess
import time
import pipes

user = "let's say a user"
host = "the remote server's ip"
password = "the remote server's password"
remote_commands = """scp afile.file user@serverip: path of server directory
pwd
"""
sshCommand = "sshpass -p %s ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no %s@%s" % (pipes.quote(password), user, host)
process1 = subprocess.Popen(sshCommand, shell=True, stdin = subprocess.PIPE, stdout = subprocess.PIPE)
out, err = process1.communicate(remote_commands)
Sign up to request clarification or add additional context in comments.

6 Comments

I'd argue that an answer here should probably show the proper escaping of an arbitrary filename (to survive both local and remote shell interpretation passes) to be sufficient/complete to give someone the information they need to use scp securely from Python.
As @CharlesDuffy notes, normal shell escaping rules apply for filenames with spaces or shell metachars. sshCommand is executed by the local shell only and remotes_commands are executed by the remote shell only.
When remotes_commands include scp, however, the filename (or, in this case, the "path of server directory") is interpreted not only by the remote shell, but also via glob expansion on the system named by serverip.
Passing the remote server's password in as part of a string passed to subprocess.Popen with shell=True, rather than an array element with shell=False, is also a risk. What if a password contains the literal characters '"$(rm -rf /)"'? Passing an explicit argv array protects you from shell interpretation of inputs.
Looking back at the question, I see that these practices are copied from there rather than introduced. That's considerably more forgivable.
|
0

You are mixing up some things. Popen only works on the local host; if you want to execute remote commands, you have several options:

  • Pass the command to be executed along with ssh, on the command line.
  • Pass the command via stdin to the remote shell, via stdin.
  • Use paramiko.

1 Comment

pass each command to be executed along with ssh on the command line does work indeed, but my friend helped me to figure out a way to do it by feeding all the commands to process.stdin. If anyone in the future wants to do something similar, please refer to this link: stackoverflow.com/questions/28214455/…
0

If you want to generate a string you can pass over ssh to run a series of remote commands securely, this requires correct shell quoting for the command inner command (which is necessarily invoked with a shell), and not using shell=True for the outer command (thus allowing the use of a local shell -- with the security implications of same -- to be avoided):

# Let's say our user is Bobby Tables.
user = 'bobby_tables'
host = 'localhost'
remote_password = """ '"$(rm -rf /)"' """

# commands to run on system named in host variable
commands = [
    ['scp', '/path/to/filename', 'user@thirdhost:/remote/path'],
    ['pwd'],
]
commands_str = '; '.join([' '.join([pipes.quote(word) for word in command])
                          for command in commands])
ssh_command = [
    'sshpass',
    '-p', remote_password,
    'ssh', '-o', 'UserKnownHostsFile=/dev/null',
    '-o', 'StrictHostKeyChecking=no',
    '%s@%s' % (user, host),
    commands_str
]
process1 = subprocess.Popen(ssh_command, stdout=subprocess.PIPE)

This formulation -- unlike the original -- is robust against malicious content in passwords. Being robust against malicious content in filenames is somewhat harder, due to faults in the design of scp (copied from its predecessor, rcp); if paranoid, I would suggest checking for source filenames containing a : character (if these are parameterized in the final product) and aborting operation if present.

2 Comments

Of course that doesn't work on the remote side if OP wants environment variable or glob expansion.
@tdelaney, indeed -- preventing such things from happening unless explicitly desired is the point.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.