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实现类似ftp传输文件的网络程序示例
Apr 08 Python
Python爬取国外天气预报网站的方法
Jul 10 Python
Python中Class类用法实例分析
Nov 12 Python
Django模板变量如何传递给外部js调用的方法小结
Jul 24 Python
Python中 传递值 和 传递引用 的区别解析
Feb 22 Python
python3+PyQt5重新实现QT事件处理程序
Apr 19 Python
Python模拟自动存取款机的查询、存取款、修改密码等操作
Sep 02 Python
Python格式化输出字符串方法小结【%与format】
Oct 29 Python
解决Python中list里的中文输出到html模板里的问题
Dec 17 Python
python-Web-flask-视图内容和模板知识点西宁街
Aug 23 Python
Python Serial串口基本操作(收发数据)
Nov 06 Python
浅谈怎么给Python添加类型标注
Jun 08 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
造就帕卡马拉的帕卡斯是怎么被发现的
2021/03/03 咖啡文化
php修改时间格式的代码
2011/05/29 PHP
实例介绍PHP的Reflection反射机制
2014/08/05 PHP
thinkphp3.2点击刷新生成验证码
2016/02/16 PHP
Centos6.5和Centos7 php环境搭建方法
2016/05/27 PHP
Yii2中关联查询简单用法示例
2016/08/10 PHP
yii2 开发api接口时优雅的处理全局异常的方法
2019/05/14 PHP
一个高效的JavaScript压缩工具下载集合
2007/03/06 Javascript
在javascript中对于DOM的加强
2013/04/11 Javascript
jQuery中:text选择器用法实例
2015/01/03 Javascript
jquery中show()、hide()和toggle()用法实例
2015/01/15 Javascript
javascript中replace( )方法的使用
2015/04/24 Javascript
JavaScript html5 canvas画布中删除一个块区域的方法
2016/01/26 Javascript
jquery Ajax 全局调用封装实例详解
2017/01/16 Javascript
js实现首屏延迟加载实现方法 js实现多屏单张图片延迟加载效果
2017/07/17 Javascript
vue语法之拼接字符串的示例代码
2017/10/25 Javascript
如何正确理解vue中的key详解
2019/11/02 Javascript
es6函数之严格模式用法实例分析
2020/03/17 Javascript
vue设置默认首页的操作
2020/08/12 Javascript
[42:20]Secret vs Liquid 2019国际邀请赛小组赛 BO2 第二场 8.15
2019/08/17 DOTA
python类中super()和__init__()的区别
2016/10/18 Python
基于python3 类的属性、方法、封装、继承实例讲解
2017/09/19 Python
如何爬取通过ajax加载数据的网站
2019/08/15 Python
python梯度下降算法的实现
2020/02/24 Python
jupyter notebook 的工作空间设置操作
2020/04/20 Python
pymysql之cur.fetchall() 和cur.fetchone()用法详解
2020/05/15 Python
完美解决Django2.0中models下的ForeignKey()问题
2020/05/19 Python
Debenhams爱尔兰:英国知名的百货公司
2017/01/02 全球购物
Bally澳大利亚官网:瑞士奢侈品牌
2018/11/01 全球购物
机械设计及其自动化求职推荐信
2014/02/17 职场文书
我有一个梦想演讲稿
2014/05/05 职场文书
违反学校规则制度检讨书
2015/01/01 职场文书
员工辞职信范文大全
2015/05/12 职场文书
Python代码风格与编程习惯重要吗?
2021/06/03 Python
深入讲解Vue中父子组件通信与事件触发
2022/03/22 Vue.js
DE1103使用报告
2022/04/05 无线电