#!/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 -r -h " print print " -s Allow users to modify data" print " -p Listen on port (Default: 12000)" print " -r Use - + " print " for passive mode FTP (Default: 20)" print " -h Send for passive mode" print " connection host" print " -d Debug mode" print " -D Daemon mode" print " -l Require : 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()