Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Remote executing of program via xterm run using paramiko python ssh library

Flow of the program is:

  1. Connect to OpenSSH server on Linux machine using Paramiko library
  2. Open X11 session
  3. Run xterm executable
  4. Run some other program (e.g. Firefox) by typing executable name in the terminal and running it.

I would be grateful if someone can explain how to cause some executable to run in a terminal which was open by using the following code and provide sample source code (source):

import select
import sys
import paramiko
import Xlib.support.connect as xlib_connect
import os
import socket
import subprocess



# run xming
XmingProc = subprocess.Popen("C:/Program Files (x86)/Xming/Xming.exe :0 -clipboard -multiwindow")
ssh_client = paramiko.SSHClient()
ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh_client.connect(SSHServerIP, SSHServerPort, username=user, password=pwd)
transport = ssh_client.get_transport()
channelOppositeEdges = {}

local_x11_display = xlib_connect.get_display(os.environ['DISPLAY'])
inputSockets = []

def x11_handler(channel, (src_addr, src_port)):
    local_x11_socket = xlib_connect.get_socket(*local_x11_display[:3])
    inputSockets.append(local_x11_socket)
    inputSockets.append(channel)
    channelOppositeEdges[local_x11_socket.fileno()] = channel
    channelOppositeEdges[channel.fileno()] = local_x11_socket
    transport._queue_incoming_channel(channel)

session = transport.open_session()
inputSockets.append(session)
session.request_x11(handler = x11_handler)
session.exec_command('xterm')
transport.accept()

while not session.exit_status_ready():
    readable, writable, exceptional = select.select(inputSockets,[],[])
    if len(transport.server_accepts) > 0:
        transport.accept()
    for sock in readable:
        if sock is session:
            while session.recv_ready():
                sys.stdout.write(session.recv(4096))
            while session.recv_stderr_ready():
                sys.stderr.write(session.recv_stderr(4096))   
        else: 
            try:
                data = sock.recv(4096)
                counterPartSocket  = channelOppositeEdges[sock.fileno()]
                counterPartSocket.sendall(data)
            except socket.error:
                inputSockets.remove(sock)
                inputSockets.remove(counterPartSocket)
                del channelOppositeEdges[sock.fileno()]
                del channelOppositeEdges[counterPartSocket.fileno()]
                sock.close()
                counterPartSocket.close()

print 'Exit status:', session.recv_exit_status()
while session.recv_ready():
    sys.stdout.write(session.recv(4096))
while session.recv_stderr_ready():
    sys.stdout.write(session.recv_stderr(4096))
session.close()
XmingProc.terminate()
XmingProc.wait()

I was thinking about running the program in child thread, while the thread running the xterm is waiting for the child to terminate.

like image 552
rok Avatar asked Nov 02 '22 13:11

rok


1 Answers

Well, this is a bit of a hack, but hey.

What you can do on the remote end is the following: Inside the xterm, you run netcat, listen to any data coming in on some port, and pipe whatever you get into bash. It's not quite the same as typing it into xterm direclty, but it's almost as good as typing it into bash directly, so I hope it'll get you a bit closer to your goal. If you really want to interact with xterm directly, you might want to read this.

For example:

terminal 1:

% nc -l 3333 | bash

terminal 2 (type echo hi here):

% nc localhost 3333
echo hi

Now you should see hi pop out of the first terminal. Now try it with xterm&. It worked for me.

Here's how you can automate this in Python. You may want to add some code that enables the server to tell the client when it's ready, rather than using the silly time.sleeps.

import select
import sys
import paramiko
import Xlib.support.connect as xlib_connect
import os
import socket
import subprocess

# for connecting to netcat running remotely
from multiprocessing import Process
import time

# data
import getpass
SSHServerPort=22
SSHServerIP = "localhost"
# get username/password interactively, or use some other method..
user = getpass.getuser()
pwd = getpass.getpass("enter pw for '" + user + "': ")
NETCAT_PORT = 3333
FIREFOX_CMD="/path/to/firefox &"
#FIREFOX_CMD="xclock&"#or this :)

def run_stuff_in_xterm():
    time.sleep(5)
    s = socket.socket(socket.AF_INET6 if ":" in SSHServerIP else socket.AF_INET, socket.SOCK_STREAM)
    s.connect((SSHServerIP, NETCAT_PORT))
    s.send("echo \"Hello there! Are you watching?\"\n")
    s.send(FIREFOX_CMD + "\n")
    time.sleep(30)
    s.send("echo bye bye\n")
    time.sleep(2)
    s.close()

# run xming
XmingProc = subprocess.Popen("C:/Program Files (x86)/Xming/Xming.exe :0 -clipboard -multiwindow")
ssh_client = paramiko.SSHClient()
ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh_client.connect(SSHServerIP, SSHServerPort, username=user, password=pwd)
transport = ssh_client.get_transport()
channelOppositeEdges = {}

local_x11_display = xlib_connect.get_display(os.environ['DISPLAY'])
inputSockets = []

def x11_handler(channel, (src_addr, src_port)):
    local_x11_socket = xlib_connect.get_socket(*local_x11_display[:3])
    inputSockets.append(local_x11_socket)
    inputSockets.append(channel)
    channelOppositeEdges[local_x11_socket.fileno()] = channel
    channelOppositeEdges[channel.fileno()] = local_x11_socket
    transport._queue_incoming_channel(channel)

session = transport.open_session()
inputSockets.append(session)
session.request_x11(handler = x11_handler)
session.exec_command("xterm -e \"nc -l 0.0.0.0 %d | /bin/bash\"" % NETCAT_PORT)
p = Process(target=run_stuff_in_xterm)
transport.accept()
p.start()

while not session.exit_status_ready():
    readable, writable, exceptional = select.select(inputSockets,[],[])
    if len(transport.server_accepts) > 0:
        transport.accept()
    for sock in readable:
        if sock is session:
            while session.recv_ready():
                sys.stdout.write(session.recv(4096))
            while session.recv_stderr_ready():
                sys.stderr.write(session.recv_stderr(4096))   
        else: 
            try:
                data = sock.recv(4096)
                counterPartSocket  = channelOppositeEdges[sock.fileno()]
                counterPartSocket.sendall(data)
            except socket.error:
                inputSockets.remove(sock)
                inputSockets.remove(counterPartSocket)
                del channelOppositeEdges[sock.fileno()]
                del channelOppositeEdges[counterPartSocket.fileno()]
                sock.close()
                counterPartSocket.close()

p.join()
print 'Exit status:', session.recv_exit_status()
while session.recv_ready():
    sys.stdout.write(session.recv(4096))
while session.recv_stderr_ready():
    sys.stdout.write(session.recv_stderr(4096))
session.close()
XmingProc.terminate()
XmingProc.wait()

I tested this on a Mac, so I commented out the XmingProc bits and used /Applications/Firefox.app/Contents/MacOS/firefox as FIREFOX_CMD (and xclock).

The above isn't exactly a secure setup, as anyone connecting to the port at the right time could run arbitrary code on your remote server, but it sounds like you're planning to use this for testing purposes anyway. If you want to improve the security, you could make netcat bind to 127.0.0.1 rather than 0.0.0.0, setup an ssh tunnel (run ssh -L3333:localhost:3333 [email protected] to tunnel all traffic received locally on port 3333 to remote-host.com:3333), and let Python connect to ("localhost", 3333).

Now you can combine this with selenium for browser automation:

Follow the instructions from this page, i.e. download the selenium standalone server jar file, put it into /path/to/some/place (on the server), and pip install -U selenium (again, on the server).

Next, put the following code into selenium-example.py in /path/to/some/place:

#!/usr/bin/env python
from selenium import webdriver
from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver.common.keys import Keys
import time

browser = webdriver.Firefox() # Get local session of firefox
browser.get("http://www.yahoo.com") # Load page
assert "Yahoo" in browser.title
elem = browser.find_element_by_name("p") # Find the query box
elem.send_keys("seleniumhq" + Keys.RETURN)
time.sleep(0.2) # Let the page load, will be added to the API
try:
    browser.find_element_by_xpath("//a[contains(@href,'http://docs.seleniumhq.org')]")
except NoSuchElementException:
    assert 0, "can't find seleniumhq"
browser.close()

and change the firefox command:

FIREFOX_CMD="cd /path/to/some/place && python selenium-example.py"

And watch firefox do a Yahoo search. You might also want to increase the time.sleep.

If you want to run more programs, you can do things like this before or after running firefox:

# start up xclock, wait for some time to pass, kill it.
s.send("xclock&\n")
time.sleep(1)
s.send("XCLOCK_PID=$!\n")  # stash away the process id (into a bash variable)
time.sleep(30)
s.send("echo \"killing $XCLOCK_PID\"\n")
s.send("kill $XCLOCK_PID\n\n")
time.sleep(5)

If you want to do perform general X11 application control, I think you might need to write similar "driver applications", albeit using different libraries. You might want search for "x11 send {mouse|keyboard} events" to find more general approaches. That brings up these questions, but I'm sure there's lots more.

If the remote end isn't responding instantaneously, you might want to sniff your network traffic in Wireshark, and check whether or not TCP is batching up the data, rather than sending it line by line (the \n seems to help here, but I guess there's no guarantee). If this is the case, you might be out of luck, but nothing is impossible. I hope you don't need to go that far though ;-)

One more note: if you need to communicate with CLI programs' STDIN/STDOUT, you might want to look at expect scripting (e.g. using pexpect, or for simple cases you might be able to use subprocess.Popen.communicate](http://docs.python.org/2/library/subprocess.html#subprocess.Popen.communicate)).

like image 107
m01 Avatar answered Nov 09 '22 14:11

m01