Python实现的简易FTP

python

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(".")), (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 + "

else: 

info = "d%s%s------- %04u %8s %8s %8lu %s %s

" % ( 

"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 + "

else: 

st = os.stat(path) 

info = "%s%s%s------- %04u %8s %8s %8lu %s %s

" % ( 

"-" 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:

SITES

EPRT

EPSV

MDTM

PASV

"REST STREAM

SIZE

UTF8

211 End

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:] != "

": 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("", "") 

ss = s.split("

") 

if len(ss) > 1: 

r = (str(code) + "-") + ("

" + str(code) + "-").join(ss[:-1]) 

r += "

" + str(code) + " " + ss[-1] + "

else: 

r = str(code) + " " + ss[0] + "

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 的全部内容, 来源链接: utcz.com/z/523364.html

回到顶部