Command handling in extra klassen ausgelagert

This commit is contained in:
trollhase 2013-08-23 14:11:14 +02:00
parent aa0a319234
commit 4f9b38cd78

View file

@ -3,7 +3,8 @@ import threading
import socket import socket
from random import randint from random import randint
import datetime import datetime
class Utils:
@staticmethod
def mask(rights): def mask(rights):
m = "" m = ""
if rights&4 != 0: if rights&4 != 0:
@ -23,9 +24,18 @@ def mask(rights):
return m return m
@staticmethod
def _r( (u,g,o) ): def _r( (u,g,o) ):
return ( (u&7) << 6 ) + ( (g&7) << 3 ) + (o&7) 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: class FSNode:
def __init__(self,name,owner="nobody",group="nobody",rights=(6,4,4),size=0, iun=1): def __init__(self,name,owner="nobody",group="nobody",rights=(6,4,4),size=0, iun=1):
self.name = name self.name = name
@ -33,11 +43,11 @@ class FSNode:
self.group = group self.group = group
if type(rights) == tuple and len(rights) == 3: if type(rights) == tuple and len(rights) == 3:
self.rights = _r( rights ) self.rights = Utils._r( rights )
elif type(rights) == int: elif type(rights) == int:
self.rights = rights self.rights = rights
else: else:
self.rights = _r( (6,4,4) ) self.rights = Utils._r( (6,4,4) )
self.size = size self.size = size
@ -86,9 +96,9 @@ class FSNode:
m += "-" m += "-"
rights = self.get_rights() rights = self.get_rights()
m += mask((rights >> 6)&7) m += Utils.mask((rights >> 6)&7)
m += mask((rights >> 3)&7) m += Utils.mask((rights >> 3)&7)
m += mask(rights&7) m += Utils.mask(rights&7)
return m return m
@ -340,6 +350,10 @@ class SystemTypeMessage(FTPMessage):
def __init__(self, sys="UNIX",typ="L8"): def __init__(self, sys="UNIX",typ="L8"):
FTPMessage.__init__(self,"215","%s Type: %s" % ( sys.upper(), typ )) 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 # Data Connection Handler
class NListHandler: class NListHandler:
def __init__(self, directory, handler): def __init__(self, directory, handler):
@ -381,148 +395,246 @@ class RetrieveHandler:
self.node.send_content(socket) self.node.send_content(socket)
self.handler.send( EndFileRetrieve() ) self.handler.send( EndFileRetrieve() )
# State classes # CommandHandler classes
class State: class CommandHandler:
cmd = None
def process(self, handler, message): def process(self, handler, message):
pass pass
class MainState(State): class ForbiddenCommandHandler(CommandHandler):
def process(self, handler, message): def process(self, handler, message):
if message.cmd == "QUIT": if hasattr(self,'log'):
handler.debug( "Quitting" ) handler.log(self.log % message.parameter)
handler.send( QuitMessage() ) handler.send( OperationNotPermittedMessage() )
handler.running = False
else: class UnknownCommandHandler(CommandHandler):
def process(self, handler, message):
handler.log("Unknown command %s" % message.cmd)
handler.send( InvalidCommandMessage(message.cmd) ) handler.send( InvalidCommandMessage(message.cmd) )
class NonAuthorizedState(MainState): class QuitCommandHandler(CommandHandler):
cmd = "QUIT"
def process(self, handler, message): def process(self, handler, message):
if message.cmd == "USER": handler.log("Client quits")
handler.debug("Setting user to %s" % message.parameter) 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.user = message.parameter
handler.state = AuthorizingState() handler.state = AuthorizingState()
handler.send( PasswordRequiredMessage(handler.user) ) handler.send( PasswordRequiredMessage(handler.user) )
else:
MainState.process(self, handler, message)
class AuthorizingState(MainState): class PassCommandHandler(CommandHandler):
cmd = "PASS"
def process(self, handler, message): 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 password = message.parameter
if handler.authorizer.authorize(handler.user, password, handler): if handler.authorizer.authorize(handler.user, password, handler):
handler.debug("User login %s successful using password %s" % (handler.user,password)) handler.log("User login %s successful using password %s" % (handler.user,password))
handler.state = AuthorizedState() handler.state = AuthorizedState()
handler.send( AuthorizationSuccessfulMessage() ) handler.send( AuthorizationSuccessfulMessage() )
else: else:
handler.debug("User login %s failed using password %s" % (handler.user, password) ) handler.log("User login %s failed using password %s" % (handler.user, password) )
handler.state = NonAuthorizedState() handler.state = NonAuthorizedState()
handler.send( AuthorizationFailedMessage() ) handler.send( AuthorizationFailedMessage() )
else:
MainState.process(self, handler, message)
class AuthorizedState(MainState): class PasvCommandHandler(CommandHandler):
cmd = "PASV"
def process(self, handler, message): def process(self, handler, message):
if message.cmd == "PASV":
addr = handler.createPassiveDataConnection() addr = handler.createPassiveDataConnection()
handler.debug("Created passive data connection") handler.log("Create passive data connection")
handler.send( EnteringPassiveModeMessage( addr ) ) handler.send( EnteringPassiveModeMessage( addr ) )
elif message.cmd == "DELE":
handler.debug( "Attempt to delete file %s" % message.parameter ) class PortCommandHandler(CommandHandler):
handler.send( OperationNotPermittedMessage() ) cmd = "PORT"
elif message.cmd == "RMD": def process(self, handler, message):
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(",") hp = message.parameter.split(",")
if len(hp) != 6: if len(hp) != 6:
handler.send( InvalidParametersMessage() ) handler.send( InvalidParametersMessage() )
return return
if int(hp[0]) == 10 or int(hp[0]) == 127 or int(hp[0]) == 192: #Prevent local scanning 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.log( "Attempt to port to local network." )
handler.send( InvalidPortCommandMessage() ) handler.send( InvalidPortCommandMessage() )
return return
addr = ( ".".join(hp[:4]), int(hp[4])<<8+int(hp[5]) ) addr = ( ".".join(hp[:4]), int(hp[4])<<8+int(hp[5]) )
handler.debug( "Creating active data connection to %s:%d" % addr ) handler.log( "Creating active data connection to %s:%d" % addr )
handler.createActiveDataConnection(addr) handler.createActiveDataConnection(addr)
handler.send( CommandSuccessfulMessage("PORT") ) handler.send( CommandSuccessfulMessage("PORT") )
elif message.cmd == "LIST":
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):
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)): if handler.runDataConnection(ListHandler(handler.currentDirectory, handler)):
handler.debug("Listing current directory") handler.log("Listing current directory")
else: else:
handler.send( UnableToBuildConnectionMessage() ) handler.send( UnableToBuildConnectionMessage() )
elif message.cmd == "NLST":
class SimpleListCommandHandler(CommandHandler):
cmd = "NLST"
def process(self, handler, message):
if handler.runDataConnection(NListHandler(handler.currentDirectory)): if handler.runDataConnection(NListHandler(handler.currentDirectory)):
handler.debug("Listing current directory (simple)") handler.log("Listing current directory (simple)")
else: else:
handler.send( UnableToBuildConnectionMessage() ) handler.send( UnableToBuildConnectionMessage() )
elif message.cmd == "CWD":
class ChangeWorkingDirectoryCommandHandler(CommandHandler):
cmd = "CWD"
def process(self, handler, message):
node = handler.get_node(message.parameter) node = handler.get_node(message.parameter)
if node is None or not node.is_dir(): if node is None or not node.is_dir():
handler.debug("Error trying to switch into non-existent directory") handler.log("Error trying to switch into non-existent directory")
handler.send( ChangeDirectoryFailedMessage() ) handler.send( ChangeDirectoryFailedMessage() )
else: else:
handler.currentDirectory = node handler.currentDirectory = node
handler.debug("Changed working directory to "+node.get_absolute_path()) handler.log("Changed working directory to "+node.get_absolute_path())
handler.send( ChangeDirectorySuccessfulMessage() ) handler.send( ChangeDirectorySuccessfulMessage() )
elif message.cmd == "CDUP":
class ChangeDirectoryUpCommandHandler(CommandHandler):
cmd = "CDUP"
def process(self, handler, message):
node = handler.currentDirectory.get_parent_directory() node = handler.currentDirectory.get_parent_directory()
if node is None or not node.is_dir(): if node is None or not node.is_dir():
handler.debug("Error trying to switch to non-existent parent directory") handler.log("Error trying to switch to non-existent parent directory")
else: else:
handler.debug("Changed working directory to "+node.get_absolute_path()) handler.log("Changed working directory to "+node.get_absolute_path())
handler.currentDirectory = node handler.currentDirectory = node
handler.send( ChangeDirectorySuccessfulMessage() ) handler.send( ChangeDirectorySuccessfulMessage() )
elif message.cmd == "PWD":
handler.debug( "Printing working directory" ) class PrintWorkingDirectoryCommandHandler(CommandHandler):
cmd = "PWD"
def process(self, handler, message):
handler.log( "Printing working directory" )
handler.send( WorkingDirectoryMessage(handler.currentDirectory.get_absolute_path()) ) handler.send( WorkingDirectoryMessage(handler.currentDirectory.get_absolute_path()) )
elif message.cmd == "TYPE":
class SetTypeCommandHandler(CommandHandler):
cmd = "TYPE"
def process(self, handler, message):
if message.parameter == "A": if message.parameter == "A":
handler.debug("Setting type to ASCII") handler.log("Setting type to ASCII")
handler.send( TypeSetMessage(message.parameter) ) handler.send( TypeSetMessage(message.parameter) )
else: else:
handler.debug("Unrecognized type %s" % message.parameter) handler.log("Unrecognized type %s" % message.parameter)
handler.send( InvalidCommandMessage("TYPE") ) handler.send( InvalidCommandMessage("TYPE") )
elif message.cmd == "NOOP":
handler.debug("No operation") class NoOperationCommandHandler(CommandHandler):
cmd = "NOOP"
def process(self, handler, message):
handler.log("No operation")
handler.send( CommandSuccessfulMessage("NOOP") ) handler.send( CommandSuccessfulMessage("NOOP") )
elif message.cmd == "SIZE":
handler.debug("Requested size of %s" % message.parameter) class SizeCommandHandler(CommandHandler):
cmd = "SIZE"
def process(self, handler, message):
handler.log("Requested size of %s" % message.parameter)
handler.send( CommandNotAllowedInAscii("SIZE") ) handler.send( CommandNotAllowedInAscii("SIZE") )
elif message.cmd == "ABOR":
handler.debug("Aborting data connection") class AbortCommandHandler(CommandHandler):
cmd = "ABOR"
def process(self, handler, message):
handler.log("Aborting data connection")
handler.send( AbortSuccessfulMessage() ) handler.send( AbortSuccessfulMessage() )
elif message.cmd == "SYST":
handler.debug("Requested system type") class SystemTypeCommandHandler(CommandHandler):
cmd = "SYST"
def process(self, handler, message):
handler.log("Requested system type")
handler.send( SystemTypeMessage() ) handler.send( SystemTypeMessage() )
else:
MainState.process(self, handler, message) # 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: class LineReader:
def __init__(self): def __init__(self):
@ -542,8 +654,10 @@ class LineReader:
pos = self.buf.find("\n") pos = self.buf.find("\n")
class FTPHandler(SocketServer.BaseRequestHandler): class FTPHandler(SocketServer.BaseRequestHandler):
def debug(self, text): logger = StdoutLogger()
print ("[%s] " % self.client_address[0]) + text
def log(self, message):
self.logger.log(self,message)
def send(self, message): def send(self, message):
self.request.send( str(message) ) self.request.send( str(message) )
@ -594,7 +708,7 @@ class FTPHandler(SocketServer.BaseRequestHandler):
self.state = NonAuthorizedState() self.state = NonAuthorizedState()
self.debug("Client connected"); self.log("Client connected");
self.send( WelcomeMessage() ) self.send( WelcomeMessage() )
@ -613,7 +727,7 @@ class FTPHandler(SocketServer.BaseRequestHandler):
message = FTPMessage.parse(line) message = FTPMessage.parse(line)
self.state.process(self,message) self.state.process(self,message)
self.debug("Client disconnected") self.log("Client disconnected")
handler = FTPHandler handler = FTPHandler
handler.authorizer = AnonymousAuthorizer() handler.authorizer = AnonymousAuthorizer()