Python版本
实现了比之前的xxftp更多更完善的功能
1、继续支持多用户
2、继续支持虚拟目录
3、增加支持用户根目录以及映射虚拟目录的权限设置
4、增加支持限制用户根目录或者虚拟目录的空间大小
xxftp的特点
1、开源、跨平台
2、简单、易用
3、不需要数据库
4、可扩展性超强
5、你可以免费使用xxftp假设自己的私人FTP服务器
匿名帐号可以使用!
匿名根目录只读,映射了一个虚拟目录,可以上传文件但不允许更改!
使用方法
跟用C语言写的xxftp使用方法一样
FTP服务器目录结构
-/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 "<<", cmd, 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(".")), (port8&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 + "\r\n" else: info = "d%s%s------- %04u %8s %8s %8lu %s %s\r\n" % ( "r" if "read" in perm else "-", "w" if "write" in perm else "-", 1, "0", "0", 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 + "\r\n" else: st = os.stat(path) info = "%s%s%s------- %04u %8s %8s %8lu %s %s\r\n" % ( "-" if os.path.isfile(path) else "d", "r" if "read" in perm else "-", "w" if "write" in perm else "-", 1, "0", "0", st[stat.ST_SIZE], time.strftime("%b %d %Y", 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:\r\nSITES\r\nEPRT\r\nEPSV\r\nMDTM\r\nPASV\r\n"\ "REST STREAM\r\nSIZE\r\nUTF8\r\n211 End\r\n" 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, local, permission, vdir_list, limit_size, 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, "ok") f = open(newpath, ("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, "ok") else: self.message(500, 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, local, permission, vdir_list, limit_size, 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:] != "\r\n": 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) + "-") + ("\r\n" + str(code) + "-").join(ss[:-1]) r += "\r\n" + str(code) + " " + ss[-1] + "\r\n" else: r = str(code) + " " + ss[0] + "\r\n" 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()
内容扩展:
FTP服务器端代码:
import socket,os,time import hashlib server =socket.socket() server.bind(('0.0.0.0',6666)) server.listen() print("等待....") while True: conn,addr = server.accept() print("new conn:",conn) while True: data = conn.recv(1024) if not data: print("client is disconnection") break cmd,filename = data.decode().split() #记录指令和文件名 print(filename) #判断当前目录是否存在该文件,而且必须是文件,而不是目录 if os.path.isfile(filename): f = open(filename,'rb') #m = hashlib.md5() # 创建md5 file_size = os.stat(filename).st_size #stat() 可以返回文件的大小值 conn.send((str(file_size)).encode()) # 发送文件大小 conn.recv(1024) #等待返回信息 for line in f: # m.updata(line) conn.send(line) #print("file md5",m.hexdigest()) #打印md5值 f.close()
广告合作:本站广告合作请联系QQ:858582 申请时备注:广告合作(否则不回)
免责声明:本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除!
免责声明:本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除!
暂无评论...
《魔兽世界》大逃杀!60人新游玩模式《强袭风暴》3月21日上线
暴雪近日发布了《魔兽世界》10.2.6 更新内容,新游玩模式《强袭风暴》即将于3月21 日在亚服上线,届时玩家将前往阿拉希高地展开一场 60 人大逃杀对战。
艾泽拉斯的冒险者已经征服了艾泽拉斯的大地及遥远的彼岸。他们在对抗世界上最致命的敌人时展现出过人的手腕,并且成功阻止终结宇宙等级的威胁。当他们在为即将于《魔兽世界》资料片《地心之战》中来袭的萨拉塔斯势力做战斗准备时,他们还需要在熟悉的阿拉希高地面对一个全新的敌人──那就是彼此。在《巨龙崛起》10.2.6 更新的《强袭风暴》中,玩家将会进入一个全新的海盗主题大逃杀式限时活动,其中包含极高的风险和史诗级的奖励。
《强袭风暴》不是普通的战场,作为一个独立于主游戏之外的活动,玩家可以用大逃杀的风格来体验《魔兽世界》,不分职业、不分装备(除了你在赛局中捡到的),光是技巧和战略的强弱之分就能决定出谁才是能坚持到最后的赢家。本次活动将会开放单人和双人模式,玩家在加入海盗主题的预赛大厅区域前,可以从强袭风暴角色画面新增好友。游玩游戏将可以累计名望轨迹,《巨龙崛起》和《魔兽世界:巫妖王之怒 经典版》的玩家都可以获得奖励。
更新日志
2024年11月24日
2024年11月24日
- 凤飞飞《我们的主题曲》飞跃制作[正版原抓WAV+CUE]
- 刘嘉亮《亮情歌2》[WAV+CUE][1G]
- 红馆40·谭咏麟《歌者恋歌浓情30年演唱会》3CD[低速原抓WAV+CUE][1.8G]
- 刘纬武《睡眠宝宝竖琴童谣 吉卜力工作室 白噪音安抚》[320K/MP3][193.25MB]
- 【轻音乐】曼托凡尼乐团《精选辑》2CD.1998[FLAC+CUE整轨]
- 邝美云《心中有爱》1989年香港DMIJP版1MTO东芝首版[WAV+CUE]
- 群星《情叹-发烧女声DSD》天籁女声发烧碟[WAV+CUE]
- 刘纬武《睡眠宝宝竖琴童谣 吉卜力工作室 白噪音安抚》[FLAC/分轨][748.03MB]
- 理想混蛋《Origin Sessions》[320K/MP3][37.47MB]
- 公馆青少年《我其实一点都不酷》[320K/MP3][78.78MB]
- 群星《情叹-发烧男声DSD》最值得珍藏的完美男声[WAV+CUE]
- 群星《国韵飘香·贵妃醉酒HQCD黑胶王》2CD[WAV]
- 卫兰《DAUGHTER》【低速原抓WAV+CUE】
- 公馆青少年《我其实一点都不酷》[FLAC/分轨][398.22MB]
- ZWEI《迟暮的花 (Explicit)》[320K/MP3][57.16MB]