|
Python版本 实现了比之前的xxftp更多更完善的功能 1、继续支持多用户 2、继续支持虚拟目录 3、增加支持用户根目录以及映射虚拟目录的权限设置 4、增加支持限制用户根目录或者虚拟目录的空间大小
xxftp的特点 1、开源、跨平台 2、简单、易用 3、不需要数据库 4、可扩展性超强 5、你可以免费使用xxftp假设自己的私人FTP服务器
测试地址 ftp://xiaoxia.org 匿名帐号可以使用! 匿名根目录只读,映射了一个虚拟目录,可以上传文件但不允许更改!
使用方法 跟之前用C语言写的xxftp使用方法一样:
1. Create a root directory to hold the user directories. Configure it in config.xml. 2. Create user directories under the root directory. If you want to specify a password,create a directory named ".xxftp", under which create a text file named "password" containing the MD5 code of the password. 3. If you want to specify the welcome and goodbye message,write it in xxftp.welcome and xxftp.goodbye under the root directory. 4. Configure config.xml.
The structure of your FTP server root may be like:
-/root -xxftp.welcome -xxftp.goodbye
-user1 -.xxftp -password -... -user2 -.xxftp -password -... -anonymous源代码
复制代码 代码如下:
import socket,threading,os,sys,time import hashlib,platform,stat
listen_ip = "localhost" listen_port = 21 conn_list = [] root_dir = "./home" max_connections = 500 conn_timeout = 120
class FtpConnection(threading.Thread): def __init__(self,fd): threading.Thread.__init__(self) self.fd = fd self.running = True self.setDaemon(True) self.alive_time = time.time() self.option_utf8 = False self.identified = False self.option_pasv = True self.username = "" def process(self,cmd,arg): cmd = cmd.upper(); if self.option_utf8: arg = unicode(arg,"utf8").encode(sys.getfilesystemencoding()) print "<<",arg,self.fd # Ftp Command if cmd == "BYE" or cmd == "QUIT": if os.path.exists(root_dir + "/xxftp.goodbye"): self.message(221,open(root_dir + "/xxftp.goodbye").read()) else: self.message(221,"Bye!") self.running = False return elif cmd == "USER": # Set Anonymous User if arg == "": arg = "anonymous" for c in arg: if not c.isalpha() and not c.isdigit() and c!="_": self.message(530,"Incorrect username.") return self.username = arg self.home_dir = root_dir + "/" + self.username self.curr_dir = "/" self.curr_dir,self.full_path,permission,self.vdir_list, limit_size,is_virtual = self.parse_path("/") if not os.path.isdir(self.home_dir): self.message(530,"User " + self.username + " not exists.") return self.pass_path = self.home_dir + "/.xxftp/password" if os.path.isfile(self.pass_path): self.message(331,"Password required for " + self.username) else: self.message(230,"Identified!") self.identified = True return elif cmd == "PASS": if open(self.pass_path).read() == hashlib.md5(arg).hexdigest(): self.message(230,"Identified!") self.identified = True else: self.message(530,"Not identified!") self.identified = False return elif not self.identified: self.message(530,"Please login with USER and PASS.") return
self.alive_time = time.time() finish = True if cmd == "NOOP": self.message(200,"ok") elif cmd == "TYPE": self.message(200,"ok") elif cmd == "SYST": self.message(200,"UNIX") elif cmd == "EPSV" or cmd == "PASV": self.option_pasv = True try: self.data_fd = socket.socket(socket.AF_INET,socket.SOCK_STREAM) self.data_fd.bind((listen_ip,0)) self.data_fd.listen(1) ip,port = self.data_fd.getsockname() if cmd == "EPSV": self.message(229,"Entering Extended Passive Mode (|||" + str(port) + "|)") else: ipnum = socket.inet_aton(ip) self.message(227,"Entering Passive Mode (%s,%u,%u)." % (",".join(ip.split(".")),(port>>8&0xff),(port&0xff))) except: self.message(500,"failed to create data socket.") elif cmd == "EPRT": self.message(500,"implement EPRT later...") elif cmd == "PORT": self.option_pasv = False self.data_fd = socket.socket(socket.AF_INET,socket.SOCK_STREAM) s = arg.split(",") self.data_ip = ".".join(s[:4]) self.data_port = int(s[4])*256 + int(s[5]) self.message(200,"ok") elif cmd == "PWD" or cmd == "XPWD": if self.curr_dir == "": self.curr_dir = "/" self.message(257,'"' + self.curr_dir + '"') elif cmd == "LIST" or cmd == "NLST": if arg != "" and arg[0] == "-": arg = "" # omit parameters remote,local,perm,vdir_list,limit_size,is_virtual = self.parse_path(arg) if not os.path.exists(local): self.message(550,"failed.") return if not self.establish(): return self.message(150,"ok") for v in vdir_list: f = v[0] if self.option_utf8: f = unicode(f,sys.getfilesystemencoding()).encode("utf8") if cmd == "NLST": info = f + "rn" else: info = "d%s%s------- %04u %8s %8s %8lu %s %srn" % ( "r" if "read" in perm else "-", "w" if "write" in perm else "-", 1,"0", time.strftime("%b %d %Y",time.localtime(time.time())), f) self.data_fd.send(info) for f in os.listdir(local): if f[0] == ".": continue path = local + "/" + f if self.option_utf8: f = unicode(f,sys.getfilesystemencoding()).encode("utf8") if cmd == "NLST": info = f + "rn" else: st = os.stat(path) info = "%s%s%s------- %04u %8s %8s %8lu %s %srn" % ( "-" if os.path.isfile(path) else "d", "r" if "read" in perm else "-",st[stat.ST_SIZE],time.localtime(st[stat.ST_MTIME])), f) self.data_fd.send(info) self.message(226,"Limit size: " + str(limit_size)) self.data_fd.close() self.data_fd = 0 elif cmd == "REST": self.file_pos = int(arg) self.message(250,"ok") elif cmd == "FEAT": features = "211-Features:rnSITESrnEPRTrnEPSVrnMDTMrnPASVrn" "REST STREAMrnSIZErnUTF8rn211 Endrn" self.fd.send(features) elif cmd == "OPTS": arg = arg.upper() if arg == "UTF8 ON": self.option_utf8 = True self.message(200,"ok") elif arg == "UTF8 OFF": self.option_utf8 = False self.message(200,"ok") else: self.message(500,"unrecognized option") elif cmd == "CDUP": finish = False arg = ".." else: finish = False if finish: return # Parse argument ( It's a path ) if arg == "": self.message(500,"where's my argument?") return remote,is_virtual = self.parse_path(arg) # can not do anything to virtual directory if is_virtual: permission = "none" can_read,can_write,can_modify = "read" in permission,"write" in permission,"modify" in permission newpath = local try: if cmd == "CWD": if(os.path.isdir(newpath)): self.curr_dir = remote self.full_path = newpath self.message(250,'"' + remote + '"') else: self.message(550,"failed") elif cmd == "MDTM": if os.path.exists(newpath): self.message(213,time.strftime("%Y%m%d%I%M%S",time.localtime( os.path.getmtime(newpath)))) else: self.message(550,"failed") elif cmd == "SIZE": self.message(231,os.path.getsize(newpath)) elif cmd == "XMKD" or cmd == "MKD": if not can_modify: self.message(550,"permission denied.") return os.mkdir(newpath) self.message(250,"ok") elif cmd == "RNFR": if not can_modify: self.message(550,"permission denied.") return self.temp_path = newpath self.message(350,"rename from " + remote) elif cmd == "RNTO": os.rename(self.temp_path,newpath) self.message(250,"RNTO to " + remote) elif cmd == "XRMD" or cmd == "RMD": if not can_modify: self.message(550,"permission denied.") return os.rmdir(newpath) self.message(250,"ok") elif cmd == "DELE": if not can_modify: self.message(550,"permission denied.") return os.remove(newpath) self.message(250,"ok") elif cmd == "RETR": if not os.path.isfile(newpath): self.message(550,"failed") return if not can_read: self.message(550,"permission denied.") return if not self.establish(): return self.message(150,"ok") f = open(newpath,"rb") while self.running: self.alive_time = time.time() data = f.read(8192) if len(data) == 0: break self.data_fd.send(data) f.close() self.data_fd.close() self.data_fd = 0 self.message(226,"ok") elif cmd == "STOR" or cmd == "APPE": if not can_write: self.message(550,"permission denied.") return if os.path.exists(newpath) and not can_modify: self.message(550,"permission denied.") return # Check space size remained! used_size = 0 if limit_size > 0: used_size = self.get_dir_size(os.path.dirname(newpath)) if not self.establish(): return self.message(150,("ab" if cmd == "APPE" else "wb") ) while self.running: self.alive_time = time.time() data = self.data_fd.recv(8192) if len(data) == 0: break if limit_size > 0: used_size = used_size + len(data) if used_size > limit_size: break f.write(data) f.close() self.data_fd.close() self.data_fd = 0 if limit_size > 0 and used_size > limit_size: self.message(550,"Exceeding user space limit: " + str(limit_size) + " bytes") else: self.message(226,cmd + " not implemented") except: self.message(550,"failed.")
def establish(self): if self.data_fd == 0: self.message(500,"no data connection") return False if self.option_pasv: fd = self.data_fd.accept()[0] self.data_fd.close() self.data_fd = fd else: try: self.data_fd.connect((self.data_ip,self.data_port)) except: self.message(500,"failed to establish data connection") return False return True
def read_virtual(self,path): vdir_list = [] path = path + "/.xxftp/virtual" if os.path.isfile(path): for v in open(path,"r").readlines(): items = v.split() items[1] = items[1].replace("$root",root_dir) vdir_list.append(items) return vdir_list
def get_dir_size(self,folder): size = 0 for path,dirs,files in os.walk(folder): for f in files: size += os.path.getsize(os.path.join(path,f)) return size
def read_size(self,path): size = 0 path = path + "/.xxftp/size" if os.path.isfile(path): size = int(open(path,"r").readline()) return size
def read_permission(self,path): permission = "read,write,modify" path = path + "/.xxftp/permission" if os.path.isfile(path): permission = open(path,"r").readline() return permission
def parse_path(self,path): if path == "": path = "." if path[0] != "/": path = self.curr_dir + "/" + path s = os.path.normpath(path).replace("","/").split("/") local = self.home_dir # reset directory permission vdir_list = self.read_virtual(local) limit_size = self.read_size(local) permission = self.read_permission(local) remote = "" is_virtual = False for name in s: name = name.lstrip(".") if name == "": continue remote = remote + "/" + name is_virtual = False for v in vdir_list: if v[0] == name: permission = v[2] local = v[1] limit_size = self.read_size(local) is_virtual = True if not is_virtual: local = local + "/" + name vdir_list = self.read_virtual(local) return (remote,is_virtual)
def run(self): ''' Connection Process ''' try: if len(conn_list) > max_connections: self.message(500,"too many connections!") self.fd.close() self.running = False return # Welcome Message if os.path.exists(root_dir + "/xxftp.welcome"): self.message(220,open(root_dir + "/xxftp.welcome").read()) else: self.message(220,"xxftp(Python) www.xiaoxia.org") # Command Loop line = "" while self.running: data = self.fd.recv(4096) if len(data) == 0: break line += data if line[-2:] != "rn": continue line = line[:-2] space = line.find(" ") if space == -1: self.process(line,"") else: self.process(line[:space],line[space+1:]) line = "" except: print "error",sys.exc_info() self.running = False self.fd.close() print "connection end",self.fd,"user",self.username
def message(self,code,s): ''' Send Ftp Message ''' s = str(s).replace("r","") ss = s.split("n") if len(ss) > 1: r = (str(code) + "-") + ("rn" + str(code) + "-").join(ss[:-1]) r += "rn" + str(code) + " " + ss[-1] + "rn" else: r = str(code) + " " + ss[0] + "rn" if self.option_utf8: r = unicode(r,sys.getfilesystemencoding()).encode("utf8") self.fd.send(r)
def server_listen(): global conn_list listen_fd = socket.socket(socket.AF_INET,socket.SOCK_STREAM) listen_fd.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) listen_fd.bind((listen_ip,listen_port)) listen_fd.listen(1024) conn_lock = threading.Lock() print "ftpd is listening on ",listen_ip + ":" + str(listen_port)
while True: conn_fd,remote_addr = listen_fd.accept() print "connection from ",remote_addr,"conn_list",len(conn_list) conn = FtpConnection(conn_fd) conn.start()
conn_lock.acquire() conn_list.append(conn) # check timeout try: curr_time = time.time() for conn in conn_list: if int(curr_time - conn.alive_time) > conn_timeout: if conn.running == True: conn.fd.shutdown(socket.SHUT_RDWR) conn.running = False conn_list = [conn for conn in conn_list if conn.running] except: print sys.exc_info() conn_lock.release()
def main(): server_listen()
if __name__ == "__main__": main()
您可能感兴趣的文章:- Python中的FTP通信模块ftplib的用法整理
- python实现ftp客户端示例分享
- Python使用ftplib实现简易FTP客户端的方法
- python实现简单ftp客户端的方法
- python3实现ftp服务功能(客户端)
- Python实现基于多线程、多用户的FTP服务器与客户端功能完整实例
- python连接远程ftp服务器并列出目录下文件的方法
- python实现FTP服务器服务的方法
- python创建一个最简单http webserver服务器的方法
- python实现从ftp服务器下载文件的方法
- Python实现的FTP通信客户端与服务器端功能示例
(编辑:安卓应用网)
【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!
|