From 245a815267928096ea18d9d49cd395b87a7af175 Mon Sep 17 00:00:00 2001 From: tv Date: Thu, 24 Jan 2013 23:40:05 +0100 Subject: services: minimal services.txt-over-ssh provider (twisted) --- services/Makefile | 14 ++++++ services/authorized_keys | 1 + services/checkers.py | 25 +++++++++++ services/services.txt | 2 + services/test.py | 108 +++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 150 insertions(+) create mode 100644 services/Makefile create mode 100644 services/authorized_keys create mode 100644 services/checkers.py create mode 100644 services/services.txt create mode 100644 services/test.py (limited to 'services') diff --git a/services/Makefile b/services/Makefile new file mode 100644 index 00000000..a68f095d --- /dev/null +++ b/services/Makefile @@ -0,0 +1,14 @@ +help:;@cat Makefile + +export authorized_keys_file := authorized_keys +export services_file := services.txt +export host_key_file := test.key + +test-client: + ssh localhost -p 1337 2>/dev/null + +test-server: + python test.py + +$(host_key_file): + ssh-keygen -t rsa -P '' -f $@ diff --git a/services/authorized_keys b/services/authorized_keys new file mode 100644 index 00000000..dcb8bfeb --- /dev/null +++ b/services/authorized_keys @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC7YrLdnXDRU2XEdZDu1BmgiT0Vaxplf3bfvSm+5o3g4AcR2yCv7h2D633c9uA0gq52EJ3V5m8B1ZcxqA0zqDptKwx+ZTMUGDls7StH5xpJyk9j5gf8DzyDLQPQG2IYszCH+8esKjo3BOFxfey8NaX+k6gvQsG3lyV0PjLvvIy4gDuMn6dPZfVAlwNYFOUNgwpku3W3A0d+UFyVjt3/sgZxM+8C3y6QE1gwT5/NfBbHM5vaEqjHcVq1ui+7a4iOXFGKkZDcd7EX6cQZSbCzZL7sZ0OmB1WpAsDCvIXfzX1YfNA0sso7ldSF6ZUGNgwEk1LootnQlCK/dfbM+i62SZ+1 tv@iiso diff --git a/services/checkers.py b/services/checkers.py new file mode 100644 index 00000000..dbfe1323 --- /dev/null +++ b/services/checkers.py @@ -0,0 +1,25 @@ + +import base64, binascii +from twisted.python.filepath import FilePath +from twisted.conch.checkers import SSHPublicKeyDatabase + + +class PublicKeyChecker(SSHPublicKeyDatabase): + + def __init__(self, filename): + self.filepath = FilePath(filename) + + def getAuthorizedKeysFiles(self, credentials): + return [self.filepath] + + def checkKey(self, credentials): + for line in self.filepath.open(): + parts = line.split() + if len(parts) < 2: + continue + try: + if base64.decodestring(parts[1]) == credentials.blob: + return True + except binascii.Error: + continue + return False diff --git a/services/services.txt b/services/services.txt new file mode 100644 index 00000000..a2b97670 --- /dev/null +++ b/services/services.txt @@ -0,0 +1,2 @@ +# this is a comment +TODO declare proper services format diff --git a/services/test.py b/services/test.py new file mode 100644 index 00000000..06340a54 --- /dev/null +++ b/services/test.py @@ -0,0 +1,108 @@ +#! /usr/bin/env python + +from os import environ as env + +authorized_keys_file = env.get('authorized_keys_file', '/dev/null') +services_file = env.get('services_file', '/dev/null') +host_key_file = env.get('host_key_file', '/dev/null') +host_key_pub_file = host_key_file + '.pub' + + +from checkers import PublicKeyChecker +from twisted.conch.avatar import ConchUser +from twisted.conch.ssh.connection import SSHConnection +from twisted.conch.ssh.factory import SSHFactory +from twisted.conch.ssh.keys import Key +from twisted.conch.ssh.session import SSHSession, ISession, wrapProtocol +from twisted.conch.ssh.userauth import SSHUserAuthServer +from twisted.cred.error import UnauthorizedLogin +from twisted.cred.portal import IRealm, Portal +from twisted.internet.protocol import Protocol +from twisted.internet.reactor import listenTCP, run +from twisted.python.components import registerAdapter +from zope.interface import implements + +from twisted.python.log import startLogging +from sys import stderr +startLogging(stderr) + + +class MyRealm: + implements(IRealm) + + def requestAvatar(self, avatarId, mind, *interfaces): + return interfaces[0], MyUser(), lambda: None + + +class MyUser(ConchUser): + def __init__(self): + ConchUser.__init__(self) + self.channelLookup.update({ 'session': SSHSession }) + + +class MySession: + + def __init__(self, avatar): + pass + + def getPty(self, term, windowSize, attrs): + pass + + def execCommand(self, proto, cmd): + raise Exception("no executing commands") + + def openShell(self, trans): + ep = MyProtocol() + ep.makeConnection(trans) + trans.makeConnection(wrapProtocol(ep)) + + def eofReceived(self): + pass + + def closed(self): + pass + + +registerAdapter(MySession, MyUser, ISession) + + +def slurpTextfile(filename): + file = open(filename, 'r') + try: + return file.read() + finally: + file.close() + +class MyProtocol(Protocol): + def connectionMade(self): + data = slurpTextfile(services_file).replace('\n', '\r\n') + self.transport.write(data) + self.transport.loseConnection() + + #def dataReceived(self, data): + # if data == '\r': + # data = '\r\n' + # elif data == '\x03': #^C + # self.transport.loseConnection() + # return + # self.transport.write(data) + + +class MyFactory(SSHFactory): + privateKeys = { + 'ssh-rsa': Key.fromFile(filename=host_key_file) + } + publicKeys = { + 'ssh-rsa': Key.fromFile(filename=host_key_pub_file) + } + services = { + 'ssh-userauth': SSHUserAuthServer, + 'ssh-connection': SSHConnection + } + +if __name__ == '__main__': + portal = Portal(MyRealm()) + portal.registerChecker(PublicKeyChecker(authorized_keys_file)) + MyFactory.portal = portal + listenTCP(1337, MyFactory()) + run() -- cgit v1.2.3