one-file-projects/fakeftp.py
2013-08-22 09:44:10 +02:00

624 lines
16 KiB
Python

import SocketServer
import threading
import socket
from random import randint
import datetime
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
class FSNode:
def __init__(self,name,owner="nobody",group="nobody",rights=384,size=0, iun=1):
self.name = name
self.owner = owner
self.group = group
self.rights = rights
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 += mask((rights >> 6)&7)
m += mask((rights >> 3)&7)
m += 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=384,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
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 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)
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")
# 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() )
# State classes
class State:
def process(self, handler, message):
pass
class MainState(State):
def process(self, handler, message):
if message.cmd == "QUIT":
handler.send( QuitMessage() )
handler.running = False
else:
handler.send( InvalidCommandMessage(message.cmd) )
class NonAuthorizedState(MainState):
def process(self, handler, message):
if message.cmd == "USER":
handler.user = message.parameter
handler.state = AuthorizingState()
handler.send( PasswordRequiredMessage(handler.user) )
else:
MainState.process(self, handler, message)
class AuthorizingState(MainState):
def process(self, handler, message):
if message.cmd == "PASS":
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:
MainState.process(self, handler, message)
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 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 in ["DELE","RMD","RNFR","RNTO","STOR"]:
handler.send( OperationNotPermittedMessage() )
elif message.cmd == "RETR":
node = handler.get_node(message.parameter)
if node.is_file():
handler.runDataConnection( RetrieveHandler(node, handler) )
else:
handler.send( OperationNotPermittedMessage() )
elif message.cmd == "PORT":
hp = message.parameter.split(",")
if len(hp) != 6:
handler.debug( "Invalid port information" )
handler.send( InvalidParametersMessage() )
return
if int(hp[0]) == 10 or int(hp[0]) == 127 or int(hp[0]) == 192: #Prevent local scanning
handler.send( InvalidPortCommandMessage() )
return
addr = ( ".".join(hp[:4]), int(hp[4])<<8+int(hp[5]) )
handler.createActiveDataConnection(addr)
handler.debug("Created active data connection to %s:%d" % addr)
handler.send( CommandSuccessfulMessage("PORT") )
elif message.cmd == "LIST":
handler.debug("Listing current directory")
if handler.runDataConnection(ListHandler(handler.currentDirectory, handler)):
handler.debug("successful")
else:
handler.debug("failed")
elif message.cmd == "NLST":
handler.debug("Listing current directory (simple)")
if handler.runDataConnection(NListHandler(handler.currentDirectory)):
handler.debug("successful")
else:
handler.debug("failed")
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 parent directory")
else:
handler.debug("Changed working directory to "+node.get_absolute_path())
handler.currentDirectory = node
handler.send( ChangeDirectorySuccessfulMessage() )
elif message.cmd == "PWD":
handler.send( WorkingDirectoryMessage(handler.currentDirectory.get_absolute_path()) )
elif message.cmd == "TYPE":
if message.parameter == "A":
handler.send( TypeSetMessage(message.parameter) )
else:
handler.send( InvalidCommandMessage("TYPE") )
elif message.cmd == "NOOP":
handler.send( CommandSuccessfulMessage("NOOP") )
else:
MainState.process(self, handler, message)
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):
def debug(self, text):
print ("[%s] " % self.client_address[0]) + text
def send(self, message):
line = str(message)
self.debug("< "+line)
self.request.send( line )
def createPassiveDataConnection(self):
port = randint(60000,63000)
self.connection = PassiveDataConnection(port)
return 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.debug("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():
self.debug("> "+line)
message = FTPMessage.parse(line)
self.state.process(self,message)
self.debug("Client disconnected")
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 = 1234
while True:
try:
server = SocketServer.ThreadingTCPServer(("",port),handler)
print "Listening on port %d" % port
server.serve_forever()
break
except Exception:
if port < 1500:
port += 1
else:
print "Failed to find a port"
break