Pberndt V4

Direkt zum Inhalt springen


Quellcode iftpd.py

Beschreibung

iftpd ist ein kleiner FTP-Server, der ohne Konfigurationsdatei auskommt

Sourcecode

#!/usr/bin/python
import SocketServer
import sys
import socket
import os
import threading
import getopt

try:
    import dbus
    import avahi
    has_avahi = True
except:
    has_avahi = False
   

_socket = socket
_pasv_ports = {}
_portmap_lock = threading.Lock()
def acquirePort():
    _portmap_lock.acquire()
    usePort = 0
    for port, active in _pasv_ports.items():
        if active == False:
            usePort = port
            break
    if usePort in _pasv_ports:
        _pasv_ports[usePort] = True
    _portmap_lock.release()
    return usePort

class FTPHandler(SocketServer.StreamRequestHandler):
    def build_data(self):
        if self.connect_to == "PASV":
            srv = socket.socket()
            srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            srv.bind(('', self.pasv_port))
            srv.listen(1)
            data = srv.accept()[0]
            srv.shutdown(socket.SHUT_RDWR)
            srv.close()
            del srv
            return data
        else:
            data = socket.socket()
            data.connect(self.connect_to)
            return data
    data_socket = False
    def finish(self):
        if self.pasv_port != 0:
            _pasv_ports[self.pasv_port] = False
    def handle(self):
        print "Connection from %s" % self.connection.getpeername()[0]
        self.wfile.write("200 Hello out there\r\n")
        self.pasv_port = acquirePort()
        debug("Using port %d for passive ftp " % self.pasv_port)
        self.path = "/"
        self.rest = 0
        self.type = "I"
        self.renameFrom = None
        self.login = False
        while True:
            try:
                line = self.rfile.readline().strip().split()
            except:
                # Connection lost
                break
            if debug_mode:
                print "Received command: ", line
                try:
                    arg = " ".join(line[1:]).replace('"', '')
                    pwd = os.getcwd()
                    if arg[0] == "/":
                        newpath = os.path.abspath(pwd + arg)
                    else:
                        newpath = os.path.abspath(pwd + "/" + self.path + "/" + arg)
                    print " Interpreted as file ", newpath
                except:
                    pass
            if not line:
                break
            # Allow pre login
            if line[0] == "SYST":
                self.wfile.write("215 UNIX Type: L8\r\n")
                continue
            elif line[0] == "FEAT":
                self.wfile.write("211-Features:\r\nUTF8\r\nPASV\r\n211 End\r\n")
                continue
            elif line[0] == "NOOP":
                self.wfile.write("200 Pong\r\n")
                continue
            # Login
            if require_login and self.login != 2:
                if line[0] == "USER" and line[1] == require_login[0] and self.login == False:
                    self.login = 1
                    self.wfile.write("331 Ok\r\n")
                    continue
                if line[0] == "USER":
                    self.wfile.write("331 Ok\r\n")
                    continue
                elif line[0] == "PASS" and len(line) > 1 and line[1] == require_login[1] and self.login == 1:
                    self.login = 2
                    self.wfile.write("200 Ok\r\n")
                    continue
                self.wfile.write("530 Bah. Wrong. Authenticate yourself!\r\n")
                continue
            # Post login
            if line[0] == "USER" or line[0] == "PASS":
                self.wfile.write("230 Ill accept whatever you say\r\n")
            elif line[0] == "PORT":
                data = line[1].split(',')
                if len(data) != 6:
                    self.wfile.write("500 Oops\r\n")
                else:
                    ip = ".".join(data[0:4])
                    port = (int(data[4]) << 8) + int(data[5])
                    self.connect_to = (ip, port)
                    self.wfile.write("200 Ok Ill connect to %s:%d\r\n" % (ip, port))
            elif line[0] == "PASV":
                if self.pasv_port == 0:
                    self.wfile.write("425 Out of resources, sorry\r\n")
                else:
                    if my_ip:
                        the_ip = my_ip
                    else:
                        the_ip = self.connection.getsockname()[0]
                    self.wfile.write("227 Entering passive mode (%s,%d,%d)\r\n" % (",".join(the_ip.split(".")),
                        (self.pasv_port & 0xFF00) >> 8, (self.pasv_port & 0x00FF)))
                    self.connect_to = "PASV"
                    try:
                        self.data_socket = self.build_data()
                    except _socket.error:
                        debug("Failed to build data connection")
                        self.wfile.write("425 Failed to build data connection\r\n")
                        continue
            elif line[0] == "QUIT":
                self.wfile.write("200 Bye\r\n")
                self.wfile.close()
                break
            elif line[0] == "RNFR":
                if not allow_store:
                    self.wfile.write("550 Naa. Not here.\r\n")
                    continue
                arg = " ".join(line[1:]).replace('"', '')
                pwd = os.getcwd()
                if arg[0] == "/":
                    newpath = os.path.abspath(pwd + arg)
                else:
                    newpath = os.path.abspath(pwd + "/" + self.path + "/" + arg)
                if newpath[0:len(pwd)] != pwd:
                    self.wfile.write("553 You just tried to leave root. I cant let you do that\r\n")
                elif not os.access(newpath, os.F_OK):
                    self.wfile.write("400 Not found\r\n")
                else:
                    self.renameFrom = newpath
                    self.wfile.write("350 Ok. To which file?\r\n")
            elif line[0] == "RNTO":
                if not allow_store:
                    self.wfile.write("550 Naa. Not here.\r\n")
                    continue
                if not self.renameFrom:
                    self.wfile.write("500 Specify RNFR first\r\n")
                    continue
                arg = " ".join(line[1:]).replace('"', '')
                pwd = os.getcwd()
                if arg[0] == "/":
                    newpath = os.path.abspath(pwd + arg)
                else:
                    newpath = os.path.abspath(pwd + "/" + self.path + "/" + arg)
                if newpath[0:len(pwd)] != pwd:
                    self.wfile.write("553 You just tried to leave root. I cant let you do that\r\n")
                else:
                    debug("Renamed " + self.renameFrom + " to " + newpath)
                    os.rename(self.renameFrom, newpath)
                    self.renameFrom = None
                    self.wfile.write("250 Ok\r\n")
            elif line[0] == "DELE":
                if not allow_store:
                    self.wfile.write("550 Naa. Not here.\r\n")
                    continue
                arg = " ".join(line[1:]).replace('"', '')
                pwd = os.getcwd()
                if arg[0] == "/":
                    newpath = os.path.abspath(pwd + arg)
                else:
                    newpath = os.path.abspath(pwd + "/" + self.path + "/" + arg)
                if newpath[0:len(pwd)] != pwd:
                    self.wfile.write("553 You just tried to leave root. I cant let you do that\r\n")
                else:
                    os.unlink(newpath)
                    self.wfile.write("250 Ok\r\n")
                    debug("Removed file " + newpath)
            elif line[0] == "SITE":
                if not allow_store:
                    self.wfile.write("550 Naa. Not here.\r\n")
                    continue
                if line[1] != "CHMOD":
                    self.wfile.write("500 I dont know anything but chmod\r\n")
                    continue
                mode = line[2]
                arg = " ".join(line[3:]).replace('"', '')
                pwd = os.getcwd()
                if arg[0] == "/":
                    newpath = os.path.abspath(pwd + arg)
                else:
                    newpath = os.path.abspath(pwd + "/" + self.path + "/" + arg)
                if newpath[0:len(pwd)] != pwd:
                    self.wfile.write("553 You just tried to leave root. I cant let you do that\r\n")
                elif not os.access(newpath, os.F_OK):
                    self.wfile.write("400 Not found\r\n")
                else:
                    os.chmod(newpath, int(mode, 8))
                    self.wfile.write("200 Ok\r\n")
            elif line[0] == "CWD":
                if line[1] == "/":
                    self.path = "/"
                    self.wfile.write("200 Ok\r\n")
                    continue
                arg = " ".join(line[1:]).replace('"', '')
                pwd = os.getcwd()
                if arg[0] == "/":
                    newpath = os.path.abspath(pwd + arg)
                else:
                    newpath = os.path.abspath(pwd + "/" + self.path + "/" + arg)
                if newpath[0:len(pwd)] != pwd:
                    self.wfile.write("553 You just tried to leave root. I cant let you do that\r\n")
                elif not os.access(newpath, os.F_OK):
                    self.wfile.write("400 Not found\r\n")
                else:
                    self.path = newpath[len(pwd):]
                    self.wfile.write("200 Ok\r\n")
            elif line[0] == "CDUP":
                pwd = os.getcwd()
                newpath = os.path.abspath(pwd + "/" + self.path + "/..")
                if newpath[0:len(pwd)] != pwd:
                    self.wfile.write("553 You just tried to leave root. I cant let you do that\r\n")
                else:
                    self.path = newpath[len(pwd):]
                    self.wfile.write("200 Ok\r\n")
            elif line[0] == "PWD":
                self.wfile.write("257 \"%s\"\r\n" % self.path)
            elif line[0] == "TYPE":
                self.type = line[1]
                self.wfile.write("200 Ok\r\n")
            elif line[0] == "REST":
                self.rest = abs(int(line[1]))
                self.wfile.write("350 Position accepted")
            elif line[0] == "RMD":
                if not allow_store:
                    self.wfile.write("550 Naa. Not here.\r\n")
                    continue
                arg = " ".join(line[1:]).replace('"', '')
                pwd = os.getcwd()
                if arg[0] == "/":
                    newpath = os.path.abspath(pwd + arg)
                else:
                    newpath = os.path.abspath(pwd + "/" + self.path + "/" + arg)
                if newpath[0:len(pwd)] != pwd:
                    self.wfile.write("553 You just tried to leave root. I cant let you do that\r\n")
                    continue
                else:
                    try:
                        os.rmdir(newpath)
                        debug("Removed directory %s" % newpath)
                        self.wfile.write("200 Ok\r\n")
                    except:
                        self.wfile.write("400 Failure\r\n")
            elif line[0] == "MKD":
                if not allow_store:
                    self.wfile.write("550 Naa. Not here.\r\n")
                    continue
                arg = " ".join(line[1:]).replace('"', '')
                pwd = os.getcwd()
                if arg[0] == "/":
                    newpath = os.path.abspath(pwd + arg)
                else:
                    newpath = os.path.abspath(pwd + "/" + self.path + "/" + arg)
                if newpath[0:len(pwd)] != pwd:
                    self.wfile.write("553 You just tried to leave root. I cant let you do that\r\n")
                    continue
                else:
                    try:
                        os.mkdir(newpath)
                        self.wfile.write("257 Ok\r\n")
                        debug("Made dir " + newpath)
                    except:
                        self.wfile.write("400 Failure\r\n")
            elif line[0] == "STOR" or line[0] == "APPE":
                arg = " ".join(line[1:]).replace('"', '')
                pwd = os.getcwd()
                if arg[0] == "/":
                    newpath = os.path.abspath(pwd + arg)
                else:
                    newpath = os.path.abspath(pwd + "/" + self.path + "/" + arg)
                if newpath[0:len(pwd)] != pwd:
                    self.wfile.write("553 You just tried to leave root. I cant let you do that\r\n")
                    continue
                if not allow_store:
                    self.wfile.write("550 Naa. Not here.\r\n")
                    continue
                try:
                    if self.data_socket:
                        socket = self.data_socket
                        self.data_socket = False
                    else:
                        socket = self.build_data()
                except _socket.error:
                    self.wfile.write("425 Failed to build data connection\r\n")
                    continue
                writeto = open(newpath, "w" if line[0] == "STOR" else "a")
                self.wfile.write("150 Here we go\r\n")
                print "Receiving " + newpath
               
                while True:
                    data = socket.recv(1024)
                    if self.type == "A":
                        if len(data) > 0 and data[-1] == "r": data = data + socket.recv(1)
                        data = data.replace("\r\n", "\n")
                    if data == "":
                        break
                    writeto.write(data)
                debug("Done")

                writeto.close()               
                socket.close()
                self.wfile.write("226 Ok.\r\n")
            elif line[0] == "RETR":
                arg = " ".join(line[1:]).replace('"', '')
                pwd = os.getcwd()
                if arg[0] == "/":
                    newpath = os.path.abspath(pwd + arg)
                else:
                    newpath = os.path.abspath(pwd + "/" + self.path + "/" + arg)
                if newpath[0:len(pwd)] != pwd:
                    self.wfile.write("553 You just tried to leave root. I cant let you do that\r\n")
                    continue
                elif not os.access(newpath, os.F_OK):
                    self.wfile.write("400 Not found\r\n")
                print "Sending " + newpath

                try:
                    if self.data_socket:
                        socket = self.data_socket
                        self.data_socket = False
                    else:
                        socket = self.build_data()
                except _socket.error:
                    debug("Failed to build data connection")
                    self.wfile.write("425 Failed to build data connection\r\n")
                    continue
                self.wfile.write("150 Here we go\r\n")


                fcont = open(newpath)
                fcont.seek(self.rest)
                self.rest = 0
                while True:
                    content = fcont.read(1024)
                    if self.type == "A":
                        content = content.replace("\n", "\r\n")
                    if content == "": break
                    try:
                        socket.send(content)
                    except _socket.error:
                        # Connection lost
                        break
                fcont.close()
                socket.close()
                self.wfile.write("226 Ok what now?\r\n")
            elif line[0] == "LIST":
                try:
                    if self.data_socket:
                        socket = self.data_socket
                        self.data_socket = False
                    else:
                        socket = self.build_data()
                except _socket.error:
                    self.wfile.write("425 Failed to build data connection\r\n")
                    continue
                self.wfile.write("150 Here we go\r\n")
                args = ""
                if len(line) > 1 and line[1] == "-a":
                    args = "a"
                os.environ["LC_ALL"] = "en_US.utf8"
                socket.send(os.popen("ls -ln%s --time-style='+%%b %%d %%Y' .%r" %
                    (args, self.path)).read().replace("\n", "\r\n"))
                socket.close()
                self.wfile.write("226 Ok what now?\r\n")
            else:
                self.wfile.write("500 I dont know about that as Im dumb (You said: %s)\r\n" % line[0])

                   
my_ip = ""
allow_store = False
baseport = 12000
trange = 20
add = 0
debug_mode = False
daemon_mode = False
require_login = False
use_avahi = False

try:
    (options, rest) = getopt.getopt(sys.argv[1:], "sp:r:h:dDl:a")
except:
    print "iftpd - Instant FTPD"
    print "Copyright (c) 2009-2010, Phillip Berndt"
    print
    print "Creates a FTP daemon in ./"
    print
    print "Syntax: iftpd -s -p <port> -r <range> -h <host>"
    print
    print " -s            Allow users to modify data"
    print " -p            Listen on port <port>          (Default: 12000)"
    print " -r            Use <port> - <port> + <range>"
    print "                for passive mode FTP          (Default: 20)"
    print " -h            Send <host> for passive mode"
    print "                connection host"
    print " -d            Debug mode"
    print " -D            Daemon mode"
    print " -l            Require <user>:<pass> login    (Default: Allow all)"
    print " -a            Announce service to local network via Avahi"
    print
    sys.exit(1)

for option, arg in options:
    if option == "-s":
        print "WARNING: Activating store mode"
        allow_store = True
    if option == "-p":
        baseport = int(arg)
    if option == "-r":
        trange = int(arg)
    if option == "-h":
        my_ip = socket.gethostbyname(arg)
    if option == "-d":
        debug_mode = True
    if option == "-D":
        daemon_mode = True
    if option == "-l":
        require_login = arg.split(":", 2)
    if option == "-a":
        if not has_avahi:
            print "python-dbus or python-avahi not available. Ignoring -a."
        else:
            use_avahi = True

if debug_mode:
    def debug(string):
        print string
else:
    def debug(string):
        pass

while True:
    try:
        server = SocketServer.ThreadingTCPServer(('', 12000 + add), FTPHandler, False)
        server.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        server.server_bind()
        server.server_activate()
        print "Serving on port %d" % (baseport + add)
    except:
        add = add + 1
        if add > 20:
            print "Failed to find a port :/"
            sys.exit(1)
        continue
    break
_pasv_ports = dict(zip(range(baseport + add + 1, baseport + add + trange + 1), [ False ] * trange))
for port in _pasv_ports.keys():
    srv = socket.socket()
    srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    try:
        srv.bind(('', port))
        srv.close()
        del srv
    except _socket.error:
        del _pasv_ports[port]
        del srv
print "Using ports %d to %d for passive mode (%d are useable)" % (baseport + 1 + add, baseport + 1 + add + trange, len(_pasv_ports))
if use_avahi:
    debug("Announcing service via Avahi")
    bus = dbus.SystemBus()
    dbserver = dbus.Interface(bus.get_object(avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER), avahi.DBUS_INTERFACE_SERVER)
    group = dbus.Interface(bus.get_object(avahi.DBUS_NAME, dbserver.EntryGroupNew()), avahi.DBUS_INTERFACE_ENTRY_GROUP)
    group.AddService(avahi.IF_UNSPEC, avahi.PROTO_UNSPEC, dbus.UInt32(0), "iftpd on " + socket.gethostname(), "_ftp._tcp", "", "", dbus.UInt16(baseport + add), "")
    group.Commit()

if daemon_mode:
    debug("Switching to daemon mode")
    if os.fork() != 0:
        os._exit(0)
    os.setsid()
    if os.fork() != 0:
        os._exit(0)
    for i in range(3): os.close(i)
    os.open(os.devnull if hasattr(os, "devnull") else "/dev/null", os.O_RDWR)
    os.dup2(0, 1)
    os.dup2(0, 2)
try:
    server.serve_forever()
except KeyboardInterrupt:
    if use_avahi:
        group.Reset()
    os._exit(0)
finally:
    if use_avahi:
        group.Reset()
   

Download

Dateiname
iftpd.py
Größe
15kb