echo Something=Something > file
I can use paramiko's exec_command to do cat, grep and ls, but whenever I try to modify a file it does nothing. I already ran su before this. The file remains exactly the same as it was before running the command.
This is because you have to open a new channel for each exec_command call. This loses the authentication of the su command since it is associated to a specific channel.
You have a couple of options.
run the command with sudo, which may not be possible over paramiko
Log in as root, which is not necessarily a good idea
Use invoke_shell() on your channel, then send commands via std in to the shell
Option 3 allows for interactive use of ssh with paramiko, keeping state information intact. That is what you need for su commands. It also allows you to create a pexpect type wrapper around your shell connection, watching the stdout pipe for indications that things are done, and you can send additional commands through stdin. Just watch out for the pipes filling up and blocking until you read data.
Related
I am trying to write the program for a Telegram bot to control my Raspberry Pi, so that every message I send the bot shall be interpreted as a shell command [1][2].
The Raspberry Pi is a version 2 model B and runs Arch Linux ARM. The program is written in Python 3.6.0 with the module Telepot, and is executed from the Pi.
So far, I have been using the module subprocess in order to execute the commands, like this:
# Execute a shell command (assuming that the message received is «text»)
P = subprocess.Popen(text, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
# Store the output and the error
(output, error) = P.communicate()
# Send a message with the output
if str(output) != "b''":
bot.sendMessage(my_id, output)
# And another message with the error
if str(error) != "b''":
bot.sendMessage(my_id, error)
The bot works just fine, but the problem is that I cannot execute every command like I would in a real shell. I am talking especially about those interactive commands that bypass the STDOUT and send their output directly to the tty (ssh, ftp... which typically prompt for a password and wait until the user types it). subprocess does not allow to read from or write to the tty. Infact, if I send my bot the command tty, it replies: not a tty.
Additionally, some shell commands update their output dinamically or keep running until some event happens (for example ping 8.8.8.8, or grep pattern), and I have not succeeded in reproduce them neither, because I cannot send a Ctrl+C or Ctrl+D. Note that this second one is a different problem, because it is possible to redirect ping´s output to a file (while it is not possible to redirect [sudo] Password for user:); but in this case I am unable to send input to the shell.
Also, as an optional, it would be fun if the bot could understand pipes, redirections and globbing.
What accomunates these problems is, to my mind, the fact that I cannot interact with the shell. I believe the solution to all of them is just one.
So the question is, broadly, how can I implement such a bot program that allows me to run interactive shell commands through it [3]?
An example of the final result I would like to achieve is (in the form of a conversation between me and the bot):
Me: pwd
Bot: /home/user/some/directory
Me: sudo chmod 777 file.txt
Bot: [sudo] password for user:
Me: qwerty
Me: ssh user#host
Bot: user#host´s password:
Me: qwerty2
Bot: Welcome to host...
or
Me: cat
Me: hello
Bot: hello
Me: test
Bot: test
Me: Ctrl+D
P.S.
I have tried the pexpect module for Python, but I think it is not so good for me because I do not expect any particular output; I just want to get anything the shell gives me. (Perhaps there is a way to do what I want that I do not know?)
I have also tried using fifo files, but they do not work since they are connected to STDIN and STDOUT, not to the tty.
[1] Example: I send the message pwd and the bot replies /working/directory.
[2] True, I could use ssh or similar; however, what intrigues me is that a bot would work independently of the operating system of the machine that performs the request, be it Linux, Windows, Android or anything, without installing additional software (without even installing Telegram, since there exists Telegram web).
[3] The question could also have been: how can I connect directly to a tty through a script? The script can also be written in bash or another language, if it is easier. But I do not want to bias you, I am open to every solution. Once I have the input/output in a variable, sending the messages is not a problem.
Forgive me if I answer my own question, but I have found precisely what I was looking for, and I'd like to share it with you.
https://jmendeth.com/blog/telegram-shell-bot/
At the link above you can find the instruction to install and use 'shell bot'; from there you can also reach the source code on github.
Though not written in Python, but in node.js, it seems perfect to me. It updates the messages on the fly and can also execute graphical commands.
P.S. You can even run vim through the bot!
So I'm writing a command line utility and I've run into a problem I'm curious about.
The utility can be called with file arguments or can read from sys.stdin. Originally I was using sys.stdin.isatty() to figure out if data is being piped in, however, I found that if I call the utility remotely via ssh server utility, sys.stdin.isatty() will return false, despite the fact that there is no actual data being piped in.
As a workaround I'm using - as the file argument to force reading from stdin (eg: echo "data_here" | utility -f -), but I'm curious to know if there's a reliable way to tell the difference between a pipe getting data from a process and a pipe that's only open because the call is via ssh.
Systems programming is not my forte, so I'm grateful for any help I can get from you guys.
You can tell if you're being invoked via SSH by checking your environment. If you're being invoked via an SSH connection, the environment variables SSH_CONNECTION and SSH_CLIENT will be set. You can test if they are set with, say:
if "SSH_CONNECTION" in os.environ:
# do something
Another option, if you wanted to just stick with your original approach of sys.stdin.isatty(), would be to to allocate a pseudo-tty for the SSH connection. Normally SSH does this by default if you just SSH in for an interactive session, but not if you supply a command. However, you can force it to do so when supplying a command by passing the -t flag:
ssh -t server utility
However, I would caution you against doing either of these. As you can see, trying to detect whether you should accept input from stdin based on whether it's a TTY can cause some surprising behavior. It could also cause frustration from users if they wanted a way to interactively provide input to your program when debugging something.
The approach of adding an explicit - argument makes it a lot more explicit and less surprising which behavior you get. Some utilities also just use the lack of any file arguments to mean to read from stdin, so that would also be a less-surprising alternative.
According to this answer the SSH_CLIENT or SSH_TTY environment variable should be declared. From that, the following code should work:
import os
def running_ssh():
return 'SSH_CLIENT' in os.environ or 'SSH_TTY' in os.environ
A more complete example would examine the parent processes to check if any of them are sshd which would probably require the psutil module.
I'm trying use python's cmd library to create a shell with limited commands. One requirement I have is to be able to run a command that executes an existing shell script which opens an ssh session on a remote machine and from there allows the user to interact with the remote shell as if it was a regular ssh session.
Simply using subprocess.Popen('[/path/to/connect.sh]') works well at least as a starting point except for one issue. You can interact with the remote shell but the input that you type is not shown on stdout...so for example you see the prompt on your stdout but when you type 'ls' you don't see it being typed but when you hit return it works as expected.
I'm trying to wrap my head around how to print the input to stdout and still send it along to the remote ssh session.
EDIT:
Actual code without using cmd was just the one line:
ssh_session = subprocess.Popen(['connect.sh'])
it was fired from a do_* method in a class which extended cmd.Cmd. I think I may end up using paramiko but would still be interested in anyone's input on this.
Assuming you are using a Unix like system, SSH detects if you are on a terminal or not. When it detects that you are not on a terminal, like when using subprocess, it will not echo the characters typed. Instead you might want to use a pseudo-terminal, see pexpect, or pty. This way you can get the output from SSH as if it was running on a true terminal.
I am trying to create a Telnet Server using Python on Ubuntu 12.04. In order to be able to execute commands as a different user, I need to use the su command, which then prompts for the password. Now, I know that the prompt is sent to the STDERR stream, but I have no idea which stream I am supposed to send the password to. If I try to send it via STDIN, I get the error: su: must be run from a terminal. How do I proceed?
If you really want to use system's su program, you will need to create a terminal pair, see man 7 pty, in python that's pty.openpty call that returns you a pair of file descriptors, one for you and one for su. Then you have to fork, in the child process change stdin/out/err to slave fd and exec su. In the parent process you send data to and receive data from master fd. Linux kernel connects those together.
Alternatively you could perhaps emulate su instead?
I'm trying to execute a command remotely via SSH from Python, and in this particular case need stdin to be redirected to /dev/null.
That is, the same as using the OpenSSH client with its -n flag:
ssh -n user#host cmd
How do you achieve this (-n) with Paramiko?
paramiko.SSHClient.exec_command() doesn't seem to allow this, but maybe I'm missing something?
Unless I understand your question incorrectly: you don't need to achieve this. Nothing is automatically read from stdin/written to the remote process' stdin unless you yourself explicitly do so. So you don't need to prevent reading from stdin from happening?
EDIT: there might be an issue if the remote process expects data on stdin, and keeps waiting for it? Try calling shutdown_write() on the channel:
stdin, stdout, stderr = client.exec_command(cmd)
stdin.channel.shutdown_write()
I would ditch paramiko and start using fabric. It will let you do remote calls on the system. It uses paramiko for the ssh connection and provides the nice clean interface for doing alot more.
I am not sure why you need to pip stdin to /dev/null but there are settings to suppress it with fabric.
Goodluck!