400多行Python代码实现了一个FTP服务器


Posted in Python onMay 10, 2012

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 "<<", 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获取豆瓣电影简介代码分享
Jan 16 Python
从零学Python之入门(二)基本数据类型
May 25 Python
python获取指定目录下所有文件名列表的方法
May 20 Python
sublime text 3配置使用python操作方法
Jun 11 Python
微信小程序跳一跳游戏 python脚本跳一跳刷高分技巧
Jan 04 Python
python中matplotlib的颜色及线条控制的示例
Mar 16 Python
Python iter()函数用法实例分析
Mar 17 Python
Anaconda下安装mysql-python的包实例
Jun 11 Python
对python xlrd读取datetime类型数据的方法详解
Dec 26 Python
如何用OpenCV -python3实现视频物体追踪
Dec 04 Python
Anaconda3中的Jupyter notebook添加目录插件的实现
May 18 Python
django form和field具体方法和属性说明
Jul 09 Python
使用PYTHON接收多播数据的代码
Mar 01 #Python
使用PYTHON创建XML文档
Mar 01 #Python
基于python的汉字转GBK码实现代码
Feb 19 #Python
python 装饰器功能以及函数参数使用介绍
Jan 27 #Python
Python常见文件操作的函数示例代码
Nov 15 #Python
python 控制语句
Nov 03 #Python
python 不关闭控制台的实现方法
Oct 23 #Python
You might like
php 全文搜索和替换的实现代码
2008/07/29 PHP
PHP中的array数组类型分析说明
2010/07/27 PHP
自定义php类(查找/修改)xml文档
2013/03/26 PHP
php获取发送给用户的header信息的方法
2015/03/16 PHP
yii2中LinkPager增加总页数和总记录数的实例
2017/08/28 PHP
PHP+redis实现的悲观锁机制示例
2018/06/12 PHP
json-lib出现There is a cycle in the hierarchy解决办法
2010/02/24 Javascript
JavaScript中奇葩的假值示例应用
2014/03/11 Javascript
javascript中setTimeout使用指南
2015/07/26 Javascript
Jquery中巧用Ajax的beforeSend方法
2016/01/20 Javascript
数据结构中的各种排序方法小结(JS实现)
2016/07/23 Javascript
js实现鼠标拖动功能
2017/03/20 Javascript
JS中定位 position 的使用实例代码
2017/08/06 Javascript
关于vue.extend和vue.component的区别浅析
2017/08/16 Javascript
微信小程序组件之srcoll-view的详解
2017/10/19 Javascript
vue 弹框产生的滚动穿透问题的解决
2018/09/21 Javascript
js的对象与函数详解
2019/01/21 Javascript
Vue插件之滑动验证码
2019/09/21 Javascript
Vue 列表页带参数进详情页的操作(router-link)
2020/11/13 Javascript
Python实现快速排序和插入排序算法及自定义排序的示例
2016/02/16 Python
详谈python在windows中的文件路径问题
2018/04/28 Python
Python Flask前后端Ajax交互的方法示例
2018/07/31 Python
python实时检测键盘输入函数的示例
2019/07/17 Python
Python+AutoIt实现界面工具开发过程详解
2019/08/07 Python
python列表返回重复数据的下标
2020/02/10 Python
详解pytorch tensor和ndarray转换相关总结
2020/09/03 Python
python实现控制台输出颜色
2021/03/02 Python
纯CSS3大转盘抽奖示例代码(响应式、可配置)
2017/01/13 HTML / CSS
编写用C语言实现的求n阶阶乘问题的递归算法
2014/10/21 面试题
会计系毕业个人自荐信格式
2013/09/23 职场文书
本科毕业生应聘自荐信范文
2014/06/26 职场文书
不尊敬老师检讨书范文
2014/11/19 职场文书
学校通报表扬范文
2015/05/04 职场文书
2019员工保密协议书(3篇)
2019/09/23 职场文书
Jupyter notebook 不自动弹出网页的解决方案
2021/05/21 Python
利用Python实时获取steam特惠游戏数据
2022/06/25 Python