import SocketServer import threading import socket from random import randint import datetime SocketServer.ThreadingTCPServer.address_family = socket.AF_INET class Utils: @staticmethod def mask(rights): m = "" if rights&4 != 0: m += "r" else: m += "-" if rights&2 != 0: m += "w" else: m += "-" if rights&1 != 0: m += "x" else: m+= "-" return m @staticmethod def _r( (u,g,o) ): return ( (u&7) << 6 ) + ( (g&7) << 3 ) + (o&7) class Logger: def log(self, handler, message): pass class StdoutLogger: def log(self, handler, message): print "[%s] %s" % (handler.client_address[0],message) class FSNode: def __init__(self,name,owner="nobody",group="nobody",rights=(6,4,4),size=0, iun=1): self.name = name self.owner = owner self.group = group if type(rights) == tuple and len(rights) == 3: self.rights = Utils._r( rights ) elif type(rights) == int: self.rights = rights else: self.rights = Utils._r( (6,4,4) ) self.size = size self.parent = None self.iun = iun def is_dir(self): return False def is_file(self): return False def get_name(self): return self.name def get_size(self): return self.size def get_rights(self): return self.rights def get_last_change(self): return datetime.date(2013,8,22) #Thanks to a mumble session with lod :3 def get_boner(self): return "large" def get_parent_directory(self): return self.parent def get_owner(self): return self.owner def get_group(self): return self.group def get_imaginary_unicorn_number(self): return self.iun def get_mask(self): m = "" if self.is_dir(): m += "d" else: m += "-" rights = self.get_rights() m += Utils.mask((rights >> 6)&7) m += Utils.mask((rights >> 3)&7) m += Utils.mask(rights&7) return m def get_absolute_path(self): if self.parent is None: if self.is_dir(): return "/" else: return self.get_name() else: p = self.parent.get_absolute_path() if p[-1] != "/": p+= "/" p+=self.get_name() return p class Directory(FSNode): def __init__(self,name,owner="nobody",group="nobody",rights=(6,4,4),size=4096,files=[]): FSNode.__init__(self, name=name, owner=owner, group=group,rights=rights, size=size, iun=randint(2,10)) self.files = files for f in self.files: f.parent = self def is_dir(self): return True def get_node(self,name): for f in self.files: if f.name == name: return f return None def add_file(self,f): self.files.append(f) class File(FSNode): def __init__(self,name,owner="nobody",group="nobody",rights=384,size=4096, content=None): FSNode.__init__(self, name=name, owner=owner, group=group,rights=rights, size=size, iun=1) if content is None: content = TextFileContent() self.content = content def is_file(self): return True def send_content(self,socket): self.content.send_content(socket) class FileContent: def send_content(self, socket): pass class TextFileContent(FileContent): def __init__(self, text=""): self.text = text def send_content(self, socket): socket.send(self.text) class FileFileContent(FileContent): def __init__(self, filepath): self.filepath = filepath def send_content(self, socket): fh = file(self.filepath,"r") buf = fh.read(1024) while len(buf) > 0: socket.send(buf) buf = fh.read(1024) class HoneypotFileContent(FileContent): def send_content(self, socket): for i in range(4000): socket.send('HONEYPOT'); class URandomFileContent(FileFileContent): def __init__(self): FileFileContent.__init__(self, "/dev/urandom") 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)) self.connection = None threading.Thread(target=self.waitForConnection).start() def waitForConnection(self): self.socket.listen(1) self.connection, _ = self.socket.accept() def run(self, dchandler): while self.connection is None: pass 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, addr): self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.socket.connect( addr ) 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 def generate(self): return "%s %s\r\n" % (self.cmd.upper(), self.parameter) def __str__(self): return self.generate() @staticmethod def parse(line): pos = line.find(" ") if pos == -1: cmd = line param = None else: cmd = line[:pos] param = line[pos+1:] return FTPMessage(cmd.upper(),param) class QuitMessage(FTPMessage): '''Message sent to client when client wants to disconnect''' def __init__(self): FTPMessage.__init__(self,"221","Goodbye.") class CommandNotImplementedMessage(FTPMessage): ''' Message sent to client when command not implemented''' def __init__(self): FTPMessage.__init__(self,"502","Command not implemented") 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 FileActionCompletedMessage(FTPMessage): '''Message sent to client after file action has been completed''' def __init__(self): FTPMessage.__init__(self,"250","Requested file action okay, completed.") class EnteringPassiveModeMessage(FTPMessage): def __init__(self,(ip,port)): addr = ip.split(".") addr.append( str( (port >> 8) & 0xFF ) ) addr.append( str( port & 0xFF ) ) FTPMessage.__init__(self,"227","Entering Passive Mode (%s)." % (",".join(addr) ) ) class BeginDirectoryListingMessage(FTPMessage): def __init__(self): FTPMessage.__init__(self,"150","Here comes the directory listing.") class EndDirectoryListingMessage(FTPMessage): def __init__(self): FTPMessage.__init__(self,"226","Directory send OK.") class WorkingDirectoryMessage(FTPMessage): def __init__(self, workingDirectory): FTPMessage.__init__(self,"257","\"%s\" is the current directory" % workingDirectory) class ChangeDirectorySuccessfulMessage(FTPMessage): def __init__(self): FTPMessage.__init__(self,"250","command successful") class ChangeDirectoryFailedMessage(FTPMessage): def __init__(self): FTPMessage.__init__(self,"550","No such directory") class InvalidParametersMessage(FTPMessage): def __init__(self): FTPMessage.__init__(self,"504","Command not implemented for that parameter") class InvalidPortCommandMessage(FTPMessage): def __init__(self): FTPMessage.__init__(self,"500","Illegal port command") class CommandSuccessfulMessage(FTPMessage): def __init__(self,cmd): FTPMessage.__init__(self,"200","%s command successful" % cmd) class OperationNotPermittedMessage(FTPMessage): def __init__(self): FTPMessage.__init__(self,"550","Operation not permitted") class TypeSetMessage(FTPMessage): def __init__(self,val): FTPMessage.__init__(self,"200","Type set to %s" % val) class BeginFileRetrieve(FTPMessage): def __init__(self,node): FTPMessage.__init__(self,"150","Opening ASCII mode data connection for %s (%d bytes)" % (node.get_name(),node.get_size())) class EndFileRetrieve(FTPMessage): def __init__(self): FTPMessage.__init__(self,"226","Transfer complete") class UnableToBuildConnectionMessage(FTPMessage): def __init__(self): FTPMessage.__init__(self,"425","Unable to build data connection") class CommandNotAllowedInAscii(FTPMessage): def __init__(self,cmd): FTPMessage.__init__(self,"550","%s not allowed in ASCII mode") class AbortSuccessfulMessage(FTPMessage): def __init__(self): FTPMessage.__init__(self,"226","Abort successful") class SystemTypeMessage(FTPMessage): def __init__(self, sys="UNIX",typ="L8"): FTPMessage.__init__(self,"215","%s Type: %s" % ( sys.upper(), typ )) class FatalServerErrorMessage(FTPMessage): def __init__(self): FTPMessage.__init__(self, "421", "Fatal server error") # Data Connection Handler class NListHandler: def __init__(self, directory, handler): self.directory = directory self.handler = handler def handle(self, socket): for f in self.directory.files: socket.send(f.get_name()+"\n") class ListHandler: def __init__(self, directory, handler): self.directory = directory self.handler = handler def handle(self, socket): self.handler.send( BeginDirectoryListingMessage() ) for f in self.directory.files: socket.send( "%10s %3d %-8s %-8s %8d %3s %2d %5d %s\n" % ( f.get_mask(), f.get_imaginary_unicorn_number(), f.get_owner(), f.get_group(), f.get_size(), f.get_last_change().strftime("%b"), f.get_last_change().day, f.get_last_change().year, f.get_name() ) ) self.handler.send( EndDirectoryListingMessage() ) class RetrieveHandler: def __init__(self, node, handler): self.node = node self.handler = handler def handle(self, socket): self.handler.send( BeginFileRetrieve(self.node) ) self.node.send_content(socket) self.handler.send( EndFileRetrieve() ) class VoidStoreHandler: def __init__(self, handler): self.handler = handler def handle(self, socket): data = socket.recv(1024) while len(data) != 0: data = socket.recv(1024) self.handler.send( FileActionCompletedMessage() ) # CommandHandler classes class CommandHandler: cmd = None def process(self, handler, message): pass class ForbiddenCommandHandler(CommandHandler): def process(self, handler, message): if hasattr(self,'log'): handler.log(self.log % message.parameter) handler.send( OperationNotPermittedMessage() ) class UnknownCommandHandler(CommandHandler): def process(self, handler, message): handler.log("Unknown command %s" % message.cmd) handler.send( CommandNotImplementedMessage() ) class QuitCommandHandler(CommandHandler): cmd = "QUIT" def process(self, handler, message): handler.log("Client quits") handler.send( QuitMessage() ) handler.running = False class UserCommandHandler(CommandHandler): cmd = "USER" def process(self, handler, message): handler.log("Setting user to %s" % message.parameter) handler.user = message.parameter handler.state = AuthorizingState() handler.send( PasswordRequiredMessage(handler.user) ) class PassCommandHandler(CommandHandler): cmd = "PASS" def process(self, handler, message): password = message.parameter if handler.authorizer.authorize(handler.user, password, handler): handler.log("User login %s successful using password %s" % (handler.user,password)) handler.state = AuthorizedState() handler.send( AuthorizationSuccessfulMessage() ) else: handler.log("User login %s failed using password %s" % (handler.user, password) ) handler.state = NonAuthorizedState() handler.send( AuthorizationFailedMessage() ) class PasvCommandHandler(CommandHandler): cmd = "PASV" def process(self, handler, message): addr = handler.createPassiveDataConnection() handler.log("Create passive data connection") handler.send( EnteringPassiveModeMessage( addr ) ) class PortCommandHandler(CommandHandler): cmd = "PORT" def process(self, handler, message): handler.log("PORT: %s" % message.parameter) hp = message.parameter.split(",") if len(hp) != 6: handler.send( InvalidParametersMessage() ) return #if int(hp[0]) == 11 or int(hp[0]) == 127 or int(hp[0]) == 192: #Prevent local scanning # handler.log( "Attempt to port to local network." ) # handler.send( InvalidPortCommandMessage() ) # return port = ( int(hp[4])<<8 )+int(hp[5]) addr = ( ".".join(hp[:4]), port ) handler.log( "Creating active data connection to %s:%d" % addr ) handler.createActiveDataConnection(addr) handler.send( CommandSuccessfulMessage("PORT") ) class DeleteCommandHandler(ForbiddenCommandHandler): cmd = "DELE" log = "Attempt to delete file %s" class RemoveDirCommandHandler(ForbiddenCommandHandler): cmd = "RMD" log = "Attempt to delete directory %s" class RenameFromCommandHandler(ForbiddenCommandHandler): cmd = "RNFR" log = "Attempt to rename %s" class RenameToCommandHandler(ForbiddenCommandHandler): cmd = "RNTO" log = "Attempt to rename to %s" class StoreCommandHandler(CommandHandler): cmd = "STOR" def process(self, handler, message): node = handler.get_node(message.parameter) if node is None or node.is_file(): if handler.runDataConnection( VoidStoreHandler() ): handler.log( "Writing file %s (voided)" ) else: handler.send( UnableToBuildConnectionMessage() ) else: handler.send( OperationNotPermittedMessage() ) class RetrieveCommandHandler(CommandHandler): cmd = "RETR" def process(self, handler, message): node = handler.get_node(message.parameter) if node.is_file(): if handler.runDataConnection( RetrieveHandler(node, handler) ): handler.log( "Retrieving file %s" % message.parameter ) else: handler.send( UnableToBuildConnectionMessage() ) else: handler.send( OperationNotPermittedMessage() ) class ListCommandHandler(CommandHandler): cmd = "LIST" def process(self, handler, message): if handler.runDataConnection(ListHandler(handler.currentDirectory, handler)): handler.log("Listing current directory") else: handler.send( UnableToBuildConnectionMessage() ) class SimpleListCommandHandler(CommandHandler): cmd = "NLST" def process(self, handler, message): if handler.runDataConnection(NListHandler(handler.currentDirectory)): handler.log("Listing current directory (simple)") else: handler.send( UnableToBuildConnectionMessage() ) class ChangeWorkingDirectoryCommandHandler(CommandHandler): cmd = "CWD" def process(self, handler, message): node = handler.get_node(message.parameter) if node is None or not node.is_dir(): handler.log("Error trying to switch into non-existent directory") handler.send( ChangeDirectoryFailedMessage() ) else: handler.currentDirectory = node handler.log("Changed working directory to "+node.get_absolute_path()) handler.send( ChangeDirectorySuccessfulMessage() ) class ChangeDirectoryUpCommandHandler(CommandHandler): cmd = "CDUP" def process(self, handler, message): node = handler.currentDirectory.get_parent_directory() if node is None or not node.is_dir(): handler.log("Error trying to switch to non-existent parent directory") else: handler.log("Changed working directory to "+node.get_absolute_path()) handler.currentDirectory = node handler.send( ChangeDirectorySuccessfulMessage() ) class PrintWorkingDirectoryCommandHandler(CommandHandler): cmd = "PWD" def process(self, handler, message): handler.log( "Printing working directory" ) handler.send( WorkingDirectoryMessage(handler.currentDirectory.get_absolute_path()) ) class SetTypeCommandHandler(CommandHandler): cmd = "TYPE" def process(self, handler, message): if message.parameter == "A" or message.parameter == "I": handler.log("Setting type to " + message.parameter) handler.send( TypeSetMessage(message.parameter) ) else: handler.log("Unrecognized type %s" % message.parameter) handler.send( InvalidCommandMessage("TYPE") ) class NoOperationCommandHandler(CommandHandler): cmd = "NOOP" def process(self, handler, message): handler.log("No operation") handler.send( CommandSuccessfulMessage("NOOP") ) class SizeCommandHandler(CommandHandler): cmd = "SIZE" def process(self, handler, message): handler.log("Requested size of %s" % message.parameter) handler.send( CommandNotAllowedInAscii("SIZE") ) class AbortCommandHandler(CommandHandler): cmd = "ABOR" def process(self, handler, message): handler.log("Aborting data connection") handler.send( AbortSuccessfulMessage() ) class SystemTypeCommandHandler(CommandHandler): cmd = "SYST" def process(self, handler, message): handler.log("Requested system type") handler.send( SystemTypeMessage() ) # State classes class State: handlers = [] defaulthandler = UnknownCommandHandler() @classmethod def get_handlers(cls): hlds = [] + cls.handlers for b in cls.__bases__: hlds += b.get_handlers() return hlds @classmethod def get_handler(cls, cmd): for hld in cls.get_handlers(): if hld.cmd == cmd: return hld return cls.defaulthandler def process(self, handler, message): try: self.get_handler(message.cmd).process(handler,message) except Exception: handler.send( FatalServerErrorMessage() ) handler.running = False class BaseState(State): handlers = [ QuitCommandHandler(), NoOperationCommandHandler() ] class NonAuthorizedState(BaseState): handlers = [ UserCommandHandler() ] class AuthorizingState(NonAuthorizedState): handlers = [ PassCommandHandler() ] class AuthorizedState(BaseState): handlers = [ PasvCommandHandler(), PortCommandHandler(), DeleteCommandHandler(), RemoveDirCommandHandler(), RenameToCommandHandler(), RenameFromCommandHandler(), StoreCommandHandler(), RetrieveCommandHandler(), ListCommandHandler(), SimpleListCommandHandler(), ChangeWorkingDirectoryCommandHandler(), ChangeDirectoryUpCommandHandler(), PrintWorkingDirectoryCommandHandler(), SetTypeCommandHandler(), SizeCommandHandler(), AbortCommandHandler(), SystemTypeCommandHandler() ] class LineReader: def __init__(self): self.buf = "" def push(self,data): self.buf += data def get_lines(self): pos = self.buf.find("\n") while pos != -1: line = self.buf[:pos] if line[-1] == "\r": line = line[:-1] yield line self.buf = self.buf[pos+1:] pos = self.buf.find("\n") class FTPHandler(SocketServer.BaseRequestHandler): logger = StdoutLogger() rootDirectory = Directory("") authorizer = FailAuthorizer() def log(self, message): self.logger.log(self,message) def send(self, message): self.request.send( str(message) ) def createPassiveDataConnection(self): port = randint(60000,63000) self.connection = PassiveDataConnection(port) myaddr = self.request.getsockname() return (myaddr[0],port) def createActiveDataConnection(self,addr): self.connection = ActiveDataConnection(addr) def get_node(self,path): if path == "/": return self.rootDirectory if path[0] == "/": cnode = self.rootDirectory path = path[1:] else: cnode = self.currentDirectory if path[-1] == "/": path = path[:-1] parts = path.split("/") for part in parts: if cnode == None or not cnode.is_dir(): return None cnode = cnode.get_node(part) return cnode 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.currentDirectory = self.rootDirectory self.connection = None self.state = NonAuthorizedState() self.log("Client connected"); self.send( WelcomeMessage() ) linereader = LineReader() self.running = True while self.running: data = self.request.recv(1024) if len(data) == 0: break linereader.push(data) for line in linereader.get_lines(): message = FTPMessage.parse(line) self.state.process(self,message) self.log("Client disconnected") #Sample configuration: handler = FTPHandler handler.authorizer = AnonymousAuthorizer() recdir = Directory("recursive",files=[ File("is") ]) recdir.files.append(recdir) handler.rootDirectory = Directory("",files=[ Directory("etc", files=[ File("passwd",content=URandomFileContent()), File("shadow",content=URandomFileContent()), recdir ]), Directory("usr", files=[ Directory("share"), Directory("bin", files=[ File("sh") ]) ]), Directory("home", files=[ Directory("john",owner="john"), Directory("jane",owner="jane"), Directory("peter",owner="peter") ]), Directory("root", files=[ Directory("ssh",files=[ File("authorized_keys",content=URandomFileContent()) ]), File("db.dump", content=URandomFileContent()) ]), Directory("dev"), Directory("mnt"), Directory("var", files=[ Directory("www") ]), Directory("bin"), Directory("tmp"), Directory("lost+found"), Directory("boot"), Directory("lib"), Directory("lib32"), Directory("proc"), Directory("src"), File("vmlinuz",size=4000000), Directory("selinux"), Directory("opt"), File("initrd.img",size=4000000) ]) port = 21 while True: try: server = SocketServer.TCPServer(("0.0.0.0",port),handler) print "Listening on port %d" % port server.serve_forever() break except Exception: if port < 21: port += 1 else: print "Failed to find a port" break