one-file-projects/fakeftp.py

836 lines
22 KiB
Python

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