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

Shuixi Li
  • 103
  • 3
  • 11

3 Answers3

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)
tdelaney
  • 73,364
  • 6
  • 83
  • 116
  • 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. – Charles Duffy Jan 27 '15 at 18:54
  • 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. – tdelaney Jan 27 '15 at 19:55
  • 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`. – Charles Duffy Jan 27 '15 at 20:44
  • 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. – Charles Duffy Jan 27 '15 at 20:48
  • Looking back at the question, I see that these practices are copied from there rather than introduced. That's considerably more forgivable. – Charles Duffy Jan 27 '15 at 20:50
  • @CharlesDuffy - added code to escape the password on the local side. Since OP may want shell expansion on the remote side, I left that part as an exercise for the user. – tdelaney Jan 27 '15 at 21:13
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.
glglgl
  • 89,107
  • 13
  • 149
  • 217
  • 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: http://stackoverflow.com/questions/28214455/capturing-live-output-of-shell-script-while-running-it-in-python – Shuixi Li Jan 29 '15 at 12:13
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.

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441