diff --git a/fakeftp.py b/fakeftp.py index 8930886..45331c1 100644 --- a/fakeftp.py +++ b/fakeftp.py @@ -1,6 +1,56 @@ import SocketServer +import threading +import socket +from random import randint + +class DataConnection: + def run(self, dchandler): + pass + + +class PassiveDataConnection(DataConnection): + def __init__(self, port): + self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.socket.bind(("",port)) + threading.Thread(target=self.waitForConnection).start() + + def waitForConnection(self): + self.socket.listen(1) + self.connection, _ = self.socket.accept() + + def run(self, dchandler): + threading.Thread(target=self.runHandler, args=(dchandler,) ).start() + + def runHandler(self, dchandler): + dchandler.handle(self.connection) + self.connection.close() + +class ActiveDataConnection(DataConnection): + def __init__(self, host, port): + self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.socket.connect( (host,port) ) + + def run(self, dchandler): + threading.Thread(target=self.runHandler, args=(dchandler,)).start() + + def runHandler(self, dchandler): + dchandler.handle(self.socket) + self.socket.close() + +class FailAuthorizer: + '''Default authorizer. Denys all logins''' + def authorize(self, username, password, handler): + return False + +class AnonymousAuthorizer: + def authorize(self, username, password, handler): + if username in ["ftp","anonymous"]: + return True + else: + return False class FTPMessage: + '''Parses and creates FTP Messages''' def __init__(self,cmd,parameter): self.cmd = cmd self.parameter = parameter @@ -22,50 +72,102 @@ class FTPMessage: param = line[pos+1:] return FTPMessage(cmd.upper(),param) -class GeneralState: - @staticmethod - def handle(self, message): +class QuitMessage(FTPMessage): + '''Message sent to client when client wants to disconnect''' + def __init__(self): + FTPMessage.__init__(self,"221","Goodbye.") + +class InvalidCommandMessage(FTPMessage): + '''Message sent to client when server received message it didn't understand''' + def __init__(self,cmd): + FTPMessage.__init__(self,"500","%s not understood" % cmd) + +class PasswordRequiredMessage(FTPMessage): + '''Message sent to client when it specified a username''' + def __init__(self,user): + FTPMessage.__init__(self,"331","Password required for %s" % user) + +class AuthorizationSuccessfulMessage(FTPMessage): + '''Message sent to client when authorization was successful''' + def __init__(self): + FTPMessage.__init__(self,"230","Welcome user.") + +class AuthorizationFailedMessage(FTPMessage): + '''Message sent to client when authorization failed''' + def __init__(self): + FTPMessage.__init__(self,"530","Login incorrect.") + +class WelcomeMessage(FTPMessage): + '''Message sent to client after client has connected''' + def __init__(self): + FTPMessage.__init__(self,"220","FTP Server ready") + +class EnteringPassiveModeMessage(FTPMessage): + def __init__(self,addrtuple): + FTPMessage.__init__(self,"227","Entering Passive Mode %s." % addrtuple) + +# Data Connection Handler +class HeloHandler: + def handle(self, socket): + socket.send("Hello World") + +# State classes +class State: + def process(self, handler, message): + pass + +class MainState(State): + def process(self, handler, message): if message.cmd == "QUIT": - self.debug("Client wants to quit") - self.send(FTPMessage("221","Goodbye.")) - self.handler.running = False + handler.send( QuitMessage() ) + handler.running = False else: - self.debug("Unknown command %s" % message.cmd) - self.send(FTPMessage("500", "%s not understood" % message.cmd )) + handler.send( InvalidCommandMessage(message.cmd) ) -class UserState: - @staticmethod - def handle(self, message): +class NonAuthorizedState(MainState): + def process(self, handler, message): if message.cmd == "USER": - self.debug("Initiating login for user %s" % message.parameter) - self.send(FTPMessage("331","Password required for %s" % message.parameter)) - self.state = PassState + handler.user = message.parameter + handler.state = AuthorizingState() + handler.send( PasswordRequiredMessage(handler.user) ) else: - GeneralState.handle(self, message) + MainState.process(self, handler, message) -class PassState: - @staticmethod - def handle(self, message): +class AuthorizingState(MainState): + def process(self, handler, message): if message.cmd == "PASS": - self.debug("Denying access with password %s" % message.parameter) - self.send(FTPMessage("530","Login incorrect.")) - self.state = UserState + password = message.parameter + if handler.authorizer.authorize(handler.user, password, handler): + handler.debug("User login %s successful" % handler.user) + handler.state = AuthorizedState() + handler.send( AuthorizationSuccessfulMessage() ) + else: + handler.debug("User login %s failed" % handler.user) + handler.state = NonAuthorizedState() + handler.send( AuthorizationFailedMessage() ) else: - GeneralState.handle(self, message) + MainState.process(self, handler, message) -class FTPMessageHandler: - def __init__(self, handler): - self.state = UserState - self.handler = handler +class AuthorizedState(MainState): + def addrtuple(self, ip, port): + b = ip.split(".") + b.append( str( (port >> 8) & 0xFF ) ) + b.append( str( port & 0xFF ) ) + return "(" + ",".join(b)+ ")" - def send(self,data): - return self.handler.request.send( str(data) ) - - def debug(self,text): - self.handler.debug(text) - - def handle(self, message): - self.state.handle(self, message) + def process(self, handler, message): + if message.cmd == "PASV": + port = handler.createPassiveDataConnection() + handler.debug("Created passive data connection") + handler.send( EnteringPassiveModeMessage( self.addrtuple("127.0.0.1",port) ) ) + elif message.cmd == "HELO": + handler.debug("Running Helo on data connection") + if handler.runDataConnection(HeloHandler()): + handler.debug("successful") + else: + handler.debug("failed") + else: + MainState.process(self, handler, message) class LineReader: def __init__(self): @@ -88,12 +190,33 @@ class FTPHandler(SocketServer.BaseRequestHandler): def debug(self, text): print ("[%s] " % self.client_address[0]) + text + def send(self, message): + self.request.send( str(message) ) + + def createPassiveDataConnection(self): + port = randint(60000,63000) + self.connection = PassiveDataConnection(port) + return port + + def createActiveDataConnection(self,port): + self.connection = ActiveDataConnection(self.client_address[0],port) + + def runDataConnection(self, dchandler): + if self.connection is None: + return False + else: + self.connection.run(dchandler) + self.connection = None + return True + def handle(self): - self.messagehandler = FTPMessageHandler(self) + self.connection = None + + self.state = NonAuthorizedState() self.debug("Client connected"); - self.request.send(FTPMessage("220","FTP Server ready").generate()) + self.send( WelcomeMessage() ) linereader = LineReader() @@ -108,10 +231,13 @@ class FTPHandler(SocketServer.BaseRequestHandler): for line in linereader.get_lines(): message = FTPMessage.parse(line) - self.messagehandler.handle(message) + self.state.process(self,message) self.debug("Client disconnected") -server = SocketServer.ThreadingTCPServer(("",1234),FTPHandler) +handler = FTPHandler +handler.authorizer = AnonymousAuthorizer() + +server = SocketServer.ThreadingTCPServer(("",1234),handler) server.serve_forever() \ No newline at end of file