• 技术文章 >Python技术 >Python基础教程

    Python实现的简易FTP

    PythonPython2019-06-26 09:33:26原创3340
    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 + "\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()
    专题推荐:python
    上一篇:Python中and、or用法实例 下一篇:Python的基本数据类型有哪些

    相关文章推荐

    • Python的函数式编程• Python中的__name__怎么用?• Python中的偏函数怎么用?

    全部评论我要评论

    © 2021 Python学习网 苏ICP备2021003149号-1

  • 取消发布评论
  • 

    Python学习网