A Telegram bot to control a Raspberry Pi through an interactive shell - python

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!

Related

bash script that is run from Python reaches sudo timeout

This is a long bash script (400+ lines ) that is originally invoked from a django app like so -
os.system('./bash_script.sh &> bash_log.log')
It stops on a random command in the script. If the order of commands is changed, it hangs on another command in approx. the same location.
sshing to the machine that runs the django app, and running sudo ./bash_script.sh, asks for a password and then runs all the way.
I can't see the message it presents when it hangs in the log file, couldn't make it redirect there. I assume it's a sudo password request.
Tried -
sudo -v in the script - didn't help.
ssh to the machine and manually extend the sudo timeout in /etc/sudoers - didnt help, I think since the django app is already in the air and uses the previos timeout.
splitting the script in two, and running one in separate thread, like so -
def basher(command, log_path):
with open(log_path) as log:
Popen(command, stdout=log, stderr=log).wait()
script_thread = Thread(target=basher, args=('bash_script_pt1.sh', 'bash_log_pt1.log'))
script_thread.start()
os.system('./bash_script_pt2.sh &> bash_log_pt2.log') # I know it's deprecated, not sure if maybe it's better in this case
script_thread.join()
The logs showed that part 1 ended ok, but part 2 still hangs, albeit later in the code than when they were together.
I thought to edit /etc/sudoers from inside the Python code, and then re-login via su - user. There are snippets of how to pass the password using pty, however I don't understand the mechanics of it and could not get it to work.
I also noted that ps aux | grep bash_script.sh shows that the script is being run twice. As -
/bin/bash bash_script.sh
and as
sh -c bash_script.sh.
I assume os.system has an internal shell=True going on.
I don't understand the Linux entities/mechanics in play to figure out what's happening.
My guess is that the django app has different and more limited permissions, than the script itself does, and the script is inheriting said restrictions because it is being executed by it.
You need to find out what permissions the script has when you run it just from bash, and what it has when you run it via django, and then figure out what the difference is.

Pass command to KiTTY UI

I wish to automate the below task
Launch KiTTY
Enter username and password
Login successfully
Enter this below command to see the logs
tail -2000f /apps/test/good.log
I am able to achieve up to point 3 using below code
from subprocess import Popen
Popen("powershell kitty-0.73.1.1.exe sakthi#x.y.w.z -pw YYYY")
(new KiTTY windows is opened and user is logged in successfully)
But I don't know how to pass the below command
tail -2000f /apps/test/good.log
Note:
I am using Python3
I WANT THIS AUTOMATION AT UI LEVEL. I have around 5 to 6 log files to go through while testing. I don't want to open all the logs manually. So I am looking for a way to automate it.
I am using KiTTY, because it can re-connect automatically when there is any network problem.
KiTTY, as well as PuTTY, has -m command-line switch to provide a command for SSH "exec" channel.
This is discussed in: Automating command/script execution using PuTTY
KiTTY additionally has -cmd command-line switch, which (contrary to -m) simulates key strokes on SSH "shell" channel. It is an equivalent of KiTTY "Automatic Command" feature.
See also Open command line in C# and send commands PuTTY or KiTTY
Though if you want to automate testing, you better use a native Python SSH module, like Paramiko.

How to interact with new shell I logged into from script?

Not able to send commands to shell I logged into
Originally, I wrote a Python script. It was able to send commands like
subprocess.run(['kubectl', 'config', 'get-context'], shell=True)
but when it came time to get to the child shell, in this case bash, the command wouldn't run until I exited that shell and it would say things like it couldn't find the command.
I then tried to do it with the module "sh," but was also unsuccessful
I thought maybe using Python was problem and also realized my ultimate goal was to use a different shell (cypher-shell) and so skipped immediately to that with bash as the parent shell. In there I have a line that is sometimes successful, sometimes not
kubectl run -it --rm cypher-shell --image=gcr.io/cloud-marketplace/neo4j-public/causal-cluster-k8s:3.4 --restart=Never --namespace=default --command -- ./bin/cypher-shell -u neo4j -p "password" -a "domain.name"
But even when it successfully logs in it, it just hangs until I manually exit and then it runs the next commands
Note: I saw this and so, perhaps, it's not a child shell? Run shell command from child shell
I can't say I know exactly what you are doing, but if I understand your objective correctly you want the Python program to continue to log while the script continues to run? The problem is that the logger continues to run and holds up your program. The way I would deal with that would be to run the logger as a background process.
With bash, that would be ./script.sh & which would make it run without holding the rest of the program back from running.
Hopefully that may give you an idea! Good luck.

ssh session as python subprocess takes input but does not print it to stdout

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.

Why can't paramiko run this command? (Python)

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.

Categories