8.5 KiB
History of PyserSSH
PyserSSH Version 1.0 (Filename: "test277.py")
Date Created: September 3, 2023
PyserSSH began as an experimental project aimed at addressing the lack of suitable SSH server libraries available for Python. Initially, it was developed solely for research purposes. The project was driven by the need for a customizable SSH server solution that could be tailored to specific requirements not met by existing libraries. As the project progressed, its potential to evolve into a fully functional and practical library became evident, leading to the decision to develop PyserSSH into a more robust tool for use in both software and server applications.
Before PyserSSH
Before creating PyserSSH, we evaluated several existing libraries:
SSHim
SSShim is an SSH server library designed primarily for testing SSH clients. It is discontinued and not suitable for real-world use. It allows simple interactions with the user, such as input text, but lacks ease of use for more complex scenarios.
Example Code:
import logging
logging.basicConfig(level='DEBUG')
logger = logging.getLogger()
import sshim, re
def hello_world(script):
script.write('Please enter your name: ')
groups = script.expect(re.compile('(?P<name>.*)')).groupdict()
logger.info('%(name)s just connected', **groups)
script.writeline('Hello %(name)s!' % groups)
server = sshim.Server(hello_world, port=3000)
try:
server.run()
except KeyboardInterrupt:
server.stop()
Observation: While SSHim successfully receives user input, it is not very user-friendly or flexible for real-world applications.
Twisted
Twisted is a comprehensive event-driven networking engine in Python. It provides an SSH server implementation but is known for its complexity, making it less accessible to new users.
Example Code (Thank For this gist):
from twisted.conch import avatar, recvline
from twisted.conch.interfaces import IConchUser, ISession
from twisted.conch.ssh import factory, keys, session
from twisted.conch.insults import insults
from twisted.cred import portal, checkers
from twisted.internet import reactor
from zope.interface import implements
class SSHDemoProtocol(recvline.HistoricRecvLine):
def __init__(self, user):
self.user = user
def connectionMade(self):
recvline.HistoricRecvLine.connectionMade(self)
self.terminal.write("Welcome to my test SSH server.")
self.terminal.nextLine()
self.do_help()
self.showPrompt()
def showPrompt(self):
self.terminal.write("$ ")
def getCommandFunc(self, cmd):
return getattr(self, 'do_' + cmd, None)
def lineReceived(self, line):
line = line.strip()
if line:
print(line)
with open('logfile.log', 'w') as f:
f.write(line + '\n')
cmdAndArgs = line.split()
cmd = cmdAndArgs[0]
args = cmdAndArgs[1:]
func = self.getCommandFunc(cmd)
if func:
try:
func(*args)
except Exception as e:
self.terminal.write("Error: %s" % e)
self.terminal.nextLine()
else:
self.terminal.write("No such command.")
self.terminal.nextLine()
self.showPrompt()
def do_help(self):
publicMethods = [funcname for funcname in dir(self) if funcname.startswith('do_')]
commands = [cmd.replace('do_', '', 1) for cmd in publicMethods]
self.terminal.write("Commands: " + " ".join(commands))
self.terminal.nextLine()
def do_echo(self, *args):
self.terminal.write(" ".join(args))
self.terminal.nextLine()
def do_whoami(self):
self.terminal.write(self.user.username)
self.terminal.nextLine()
def do_quit(self):
self.terminal.write("Thanks for playing!")
self.terminal.nextLine()
self.terminal.loseConnection()
def do_clear(self):
self.terminal.reset()
class SSHDemoAvatar(avatar.ConchUser):
implements(ISession)
def __init__(self, username):
avatar.ConchUser.__init__(self)
self.username = username
self.channelLookup.update({'session': session.SSHSession})
def openShell(self, protocol):
serverProtocol = insults.ServerProtocol(SSHDemoProtocol, self)
serverProtocol.makeConnection(protocol)
protocol.makeConnection(session.wrapProtocol(serverProtocol))
def getPty(self, terminal, windowSize, attrs):
return None
def execCommand(self, protocol, cmd):
raise NotImplementedError()
def closed(self):
pass
class SSHDemoRealm(object):
implements(portal.IRealm)
def requestAvatar(self, avatarId, mind, *interfaces):
if IConchUser in interfaces:
return interfaces[0], SSHDemoAvatar(avatarId), lambda: None
else:
raise NotImplementedError("No supported interfaces found.")
def getRSAKeys():
with open('id_rsa') as privateBlobFile:
privateBlob = privateBlobFile.read()
privateKey = keys.Key.fromString(data=privateBlob)
with open('id_rsa.pub') as publicBlobFile:
publicBlob = publicBlobFile.read()
publicKey = keys.Key.fromString(data=publicBlob)
return publicKey, privateKey
if __name__ == "__main__":
sshFactory = factory.SSHFactory()
sshFactory.portal = portal.Portal(SSHDemoRealm())
users = {'admin': 'aaa', 'guest': 'bbb'}
sshFactory.portal.registerChecker(
checkers.InMemoryUsernamePasswordDatabaseDontUse(**users))
pubKey, privKey = getRSAKeys()
sshFactory.publicKeys = {'ssh-rsa': pubKey}
sshFactory.privateKeys = {'ssh-rsa': privKey}
reactor.listenTCP(22222, sshFactory)
reactor.run()
Observation: While Twisted provides a comprehensive SSH server implementation, it is quite complex for new users and requires significant setup and configuration.
Paramiko
Paramiko is a Python library for SSH protocol implementation. It supports both SSH client and server functionalities but is primarily used for SSH client tasks. It is well-documented and widely used, providing a more straightforward approach for SSH operations compared to more complex frameworks.
Example Code:
import paramiko
class SimpleSSHServer(paramiko.ServerInterface):
def __init__(self):
self.event = paramiko.Event()
self.event.set()
def check_channel_request(self, kind, channel):
if kind == 'session':
return paramiko.OPEN_SUCCEEDED
return paramiko.FAILURE
def check_auth_password(self, username, password):
if username == 'test' and password == 'password':
return paramiko.AUTH_SUCCESSFUL
return paramiko.AUTH_FAILED
def handle_client(client_socket):
transport = paramiko.Transport(client_socket)
transport.add_server_key(paramiko.RSAKey.generate(2048))
server = SimpleSSHServer()
transport.start_server(server=server)
channel = transport.accept(20)
if channel is None:
print("No channel.")
return
channel.send("Hello from Paramiko SSH server!")
channel.recv(1024)
transport.close()
if __name__ == "__main__":
import socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind(('0.0.0.0', 2200))
server_socket.listen(100)
while True:
client_socket, addr = server_socket.accept()
handle_client(client_socket)
Observation: Paramiko is straightforward and well-suited for implementing SSH clients and servers. However, it requires additional code for handling specific server features and authentication mechanisms.
Reconversion to PyserSSH
In contrast to SSHim, Twisted, and Paramiko, PyserSSH aims to simplify SSH server implementation. The following example illustrates how PyserSSH makes it easier to handle user input and interactions:
from PyserSSH import Server, Send, wait_input, AccountManager
account = AccountManager(anyuser=True)
server = Server(account, inputsystem=False)
@server.on_user("connect")
def hello_world(client):
name = wait_input(client, "Please enter your name: ")
Send(client, f"Hello {name}!")
server.run("your private key")
Advantages of PyserSSH
- Simplicity: PyserSSH provides an intuitive and user-friendly API, making it easier for developers to create and manage SSH servers.
- Flexibility: PyserSSH offers the functionality needed for more complex interactions while maintaining a straightforward setup and usage.