Command handling in extra klassen ausgelagert
This commit is contained in:
parent
aa0a319234
commit
4f9b38cd78
1 changed files with 267 additions and 153 deletions
420
fakeftp.py
420
fakeftp.py
|
@ -3,28 +3,38 @@ import threading
|
|||
import socket
|
||||
from random import randint
|
||||
import datetime
|
||||
class Utils:
|
||||
@staticmethod
|
||||
def mask(rights):
|
||||
m = ""
|
||||
if rights&4 != 0:
|
||||
m += "r"
|
||||
else:
|
||||
m += "-"
|
||||
|
||||
def mask(rights):
|
||||
m = ""
|
||||
if rights&4 != 0:
|
||||
m += "r"
|
||||
else:
|
||||
m += "-"
|
||||
if rights&2 != 0:
|
||||
m += "w"
|
||||
else:
|
||||
m += "-"
|
||||
|
||||
if rights&2 != 0:
|
||||
m += "w"
|
||||
else:
|
||||
m += "-"
|
||||
if rights&1 != 0:
|
||||
m += "x"
|
||||
else:
|
||||
m+= "-"
|
||||
|
||||
if rights&1 != 0:
|
||||
m += "x"
|
||||
else:
|
||||
m+= "-"
|
||||
return m
|
||||
|
||||
return m
|
||||
@staticmethod
|
||||
def _r( (u,g,o) ):
|
||||
return ( (u&7) << 6 ) + ( (g&7) << 3 ) + (o&7)
|
||||
|
||||
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):
|
||||
|
@ -33,11 +43,11 @@ class FSNode:
|
|||
self.group = group
|
||||
|
||||
if type(rights) == tuple and len(rights) == 3:
|
||||
self.rights = _r( rights )
|
||||
self.rights = Utils._r( rights )
|
||||
elif type(rights) == int:
|
||||
self.rights = rights
|
||||
else:
|
||||
self.rights = _r( (6,4,4) )
|
||||
self.rights = Utils._r( (6,4,4) )
|
||||
|
||||
self.size = size
|
||||
|
||||
|
@ -86,9 +96,9 @@ class FSNode:
|
|||
m += "-"
|
||||
|
||||
rights = self.get_rights()
|
||||
m += mask((rights >> 6)&7)
|
||||
m += mask((rights >> 3)&7)
|
||||
m += mask(rights&7)
|
||||
m += Utils.mask((rights >> 6)&7)
|
||||
m += Utils.mask((rights >> 3)&7)
|
||||
m += Utils.mask(rights&7)
|
||||
|
||||
return m
|
||||
|
||||
|
@ -340,6 +350,10 @@ 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):
|
||||
|
@ -381,148 +395,246 @@ class RetrieveHandler:
|
|||
self.node.send_content(socket)
|
||||
self.handler.send( EndFileRetrieve() )
|
||||
|
||||
# State classes
|
||||
class State:
|
||||
# CommandHandler classes
|
||||
class CommandHandler:
|
||||
cmd = None
|
||||
def process(self, handler, message):
|
||||
pass
|
||||
|
||||
class MainState(State):
|
||||
class ForbiddenCommandHandler(CommandHandler):
|
||||
def process(self, handler, message):
|
||||
if message.cmd == "QUIT":
|
||||
handler.debug( "Quitting" )
|
||||
handler.send( QuitMessage() )
|
||||
handler.running = False
|
||||
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( InvalidCommandMessage(message.cmd) )
|
||||
|
||||
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.send( InvalidCommandMessage(message.cmd) )
|
||||
handler.log("User login %s failed using password %s" % (handler.user, password) )
|
||||
handler.state = NonAuthorizedState()
|
||||
handler.send( AuthorizationFailedMessage() )
|
||||
|
||||
class NonAuthorizedState(MainState):
|
||||
class PasvCommandHandler(CommandHandler):
|
||||
cmd = "PASV"
|
||||
def process(self, handler, message):
|
||||
if message.cmd == "USER":
|
||||
handler.debug("Setting user to %s" % message.parameter)
|
||||
handler.user = message.parameter
|
||||
handler.state = AuthorizingState()
|
||||
handler.send( PasswordRequiredMessage(handler.user) )
|
||||
else:
|
||||
MainState.process(self, handler, message)
|
||||
addr = handler.createPassiveDataConnection()
|
||||
handler.log("Create passive data connection")
|
||||
handler.send( EnteringPassiveModeMessage( addr ) )
|
||||
|
||||
class AuthorizingState(MainState):
|
||||
class PortCommandHandler(CommandHandler):
|
||||
cmd = "PORT"
|
||||
def process(self, handler, message):
|
||||
if message.cmd == "USER":
|
||||
handler.debug("Changing user to %s" % message.parameter)
|
||||
handler.user = message.parameter
|
||||
handler.send( PasswordRequiredMessage(handler.user) )
|
||||
elif message.cmd == "PASS":
|
||||
password = message.parameter
|
||||
if handler.authorizer.authorize(handler.user, password, handler):
|
||||
handler.debug("User login %s successful using password %s" % (handler.user,password))
|
||||
handler.state = AuthorizedState()
|
||||
handler.send( AuthorizationSuccessfulMessage() )
|
||||
else:
|
||||
handler.debug("User login %s failed using password %s" % (handler.user, password) )
|
||||
handler.state = NonAuthorizedState()
|
||||
handler.send( AuthorizationFailedMessage() )
|
||||
else:
|
||||
MainState.process(self, handler, message)
|
||||
hp = message.parameter.split(",")
|
||||
if len(hp) != 6:
|
||||
handler.send( InvalidParametersMessage() )
|
||||
return
|
||||
|
||||
class AuthorizedState(MainState):
|
||||
if int(hp[0]) == 10 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
|
||||
|
||||
addr = ( ".".join(hp[:4]), int(hp[4])<<8+int(hp[5]) )
|
||||
|
||||
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(ForbiddenCommandHandler):
|
||||
cmd = "STOR"
|
||||
log = "Attempt to store file %s"
|
||||
|
||||
class RetrieveCommandHandler(CommandHandler):
|
||||
cmd = "RETR"
|
||||
def process(self, handler, message):
|
||||
if message.cmd == "PASV":
|
||||
addr = handler.createPassiveDataConnection()
|
||||
handler.debug("Created passive data connection")
|
||||
handler.send( EnteringPassiveModeMessage( addr ) )
|
||||
elif message.cmd == "DELE":
|
||||
handler.debug( "Attempt to delete file %s" % message.parameter )
|
||||
handler.send( OperationNotPermittedMessage() )
|
||||
elif message.cmd == "RMD":
|
||||
handler.debug( "Attempt to delete directory %s" % message.parameter )
|
||||
handler.send( OperationNotPermittedMessage() )
|
||||
elif message.cmd == "RNFR":
|
||||
handler.debug( "Attempt to rename %s" % message.parameter )
|
||||
handler.send( OperationNotPermittedMessage() )
|
||||
elif message.cmd == "RNTO":
|
||||
handler.debug( "Attempt to rename to %s" % message.parameter )
|
||||
handler.send( OperationNotPermittedMessage() )
|
||||
elif message.cmd == "STOR":
|
||||
handler.debug( "Attempt to store a file %s" % message.parameter )
|
||||
handler.send( OperationNotPermittedMessage() )
|
||||
elif message.cmd == "RETR":
|
||||
node = handler.get_node(message.parameter)
|
||||
if node.is_file():
|
||||
if handler.runDataConnection( RetrieveHandler(node, handler) ):
|
||||
handler.debug( "Retrieving file %s" % message.parameter )
|
||||
else:
|
||||
handler.send( UnableToBuildConnectionMessage() )
|
||||
else:
|
||||
handler.send( OperationNotPermittedMessage() )
|
||||
elif message.cmd == "PORT":
|
||||
hp = message.parameter.split(",")
|
||||
if len(hp) != 6:
|
||||
handler.send( InvalidParametersMessage() )
|
||||
return
|
||||
|
||||
if int(hp[0]) == 10 or int(hp[0]) == 127 or int(hp[0]) == 192: #Prevent local scanning
|
||||
handler.debug( "Attempt to port to local network." )
|
||||
handler.send( InvalidPortCommandMessage() )
|
||||
return
|
||||
|
||||
addr = ( ".".join(hp[:4]), int(hp[4])<<8+int(hp[5]) )
|
||||
|
||||
handler.debug( "Creating active data connection to %s:%d" % addr )
|
||||
|
||||
handler.createActiveDataConnection(addr)
|
||||
|
||||
handler.send( CommandSuccessfulMessage("PORT") )
|
||||
elif message.cmd == "LIST":
|
||||
if handler.runDataConnection(ListHandler(handler.currentDirectory, handler)):
|
||||
handler.debug("Listing current directory")
|
||||
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() )
|
||||
elif message.cmd == "NLST":
|
||||
if handler.runDataConnection(NListHandler(handler.currentDirectory)):
|
||||
handler.debug("Listing current directory (simple)")
|
||||
else:
|
||||
handler.send( UnableToBuildConnectionMessage() )
|
||||
elif message.cmd == "CWD":
|
||||
node = handler.get_node(message.parameter)
|
||||
if node is None or not node.is_dir():
|
||||
handler.debug("Error trying to switch into non-existent directory")
|
||||
handler.send( ChangeDirectoryFailedMessage() )
|
||||
else:
|
||||
handler.currentDirectory = node
|
||||
handler.debug("Changed working directory to "+node.get_absolute_path())
|
||||
handler.send( ChangeDirectorySuccessfulMessage() )
|
||||
elif message.cmd == "CDUP":
|
||||
node = handler.currentDirectory.get_parent_directory()
|
||||
if node is None or not node.is_dir():
|
||||
handler.debug("Error trying to switch to non-existent parent directory")
|
||||
else:
|
||||
handler.debug("Changed working directory to "+node.get_absolute_path())
|
||||
handler.currentDirectory = node
|
||||
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() )
|
||||
elif message.cmd == "PWD":
|
||||
handler.debug( "Printing working directory" )
|
||||
handler.send( WorkingDirectoryMessage(handler.currentDirectory.get_absolute_path()) )
|
||||
elif message.cmd == "TYPE":
|
||||
if message.parameter == "A":
|
||||
handler.debug("Setting type to ASCII")
|
||||
handler.send( TypeSetMessage(message.parameter) )
|
||||
else:
|
||||
handler.debug("Unrecognized type %s" % message.parameter)
|
||||
handler.send( InvalidCommandMessage("TYPE") )
|
||||
elif message.cmd == "NOOP":
|
||||
handler.debug("No operation")
|
||||
handler.send( CommandSuccessfulMessage("NOOP") )
|
||||
elif message.cmd == "SIZE":
|
||||
handler.debug("Requested size of %s" % message.parameter)
|
||||
handler.send( CommandNotAllowedInAscii("SIZE") )
|
||||
elif message.cmd == "ABOR":
|
||||
handler.debug("Aborting data connection")
|
||||
handler.send( AbortSuccessfulMessage() )
|
||||
elif message.cmd == "SYST":
|
||||
handler.debug("Requested system type")
|
||||
handler.send( SystemTypeMessage() )
|
||||
|
||||
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:
|
||||
MainState.process(self, handler, message)
|
||||
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":
|
||||
handler.log("Setting type to ASCII")
|
||||
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):
|
||||
|
@ -542,8 +654,10 @@ class LineReader:
|
|||
pos = self.buf.find("\n")
|
||||
|
||||
class FTPHandler(SocketServer.BaseRequestHandler):
|
||||
def debug(self, text):
|
||||
print ("[%s] " % self.client_address[0]) + text
|
||||
logger = StdoutLogger()
|
||||
|
||||
def log(self, message):
|
||||
self.logger.log(self,message)
|
||||
|
||||
def send(self, message):
|
||||
self.request.send( str(message) )
|
||||
|
@ -594,7 +708,7 @@ class FTPHandler(SocketServer.BaseRequestHandler):
|
|||
|
||||
self.state = NonAuthorizedState()
|
||||
|
||||
self.debug("Client connected");
|
||||
self.log("Client connected");
|
||||
|
||||
self.send( WelcomeMessage() )
|
||||
|
||||
|
@ -613,7 +727,7 @@ class FTPHandler(SocketServer.BaseRequestHandler):
|
|||
message = FTPMessage.parse(line)
|
||||
self.state.process(self,message)
|
||||
|
||||
self.debug("Client disconnected")
|
||||
self.log("Client disconnected")
|
||||
|
||||
handler = FTPHandler
|
||||
handler.authorizer = AnonymousAuthorizer()
|
||||
|
|
Loading…
Reference in a new issue