Python实现的简易FTP

Python (186) 2023-05-24 01:37:33

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源代码

代码如下:

importsocket,threading,os,sys,time
importhashlib,platform,stat
listen_ip="localhost"
listen_port=21
conn_list=[]
root_dir="./home"
max_connections=500
conn_timeout=120
classFtpConnection(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=""
defprocess(self,cmd,arg):
cmd=cmd.upper();
ifself.option_utf8:
arg=unicode(arg,"utf8").encode(sys.getfilesystemencoding())
print"<<",cmd,arg,self.fd
#FtpCommand
ifcmd=="BYE"orcmd=="QUIT":
ifos.path.exists(root_dir+"/xxftp.goodbye"):
self.message(221,open(root_dir+"/xxftp.goodbye").read())
else:
self.message(221,"Bye!")
self.running=False
return
elifcmd=="USER":
#SetAnonymousUser
ifarg=="":arg="anonymous"
forcinarg:
ifnotc.isalpha()andnotc.isdigit()andc!="_":
self.message(530,"Incorrectusername.")
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("/")
ifnotos.path.isdir(self.home_dir):
self.message(530,"User"+self.username+"notexists.")
return
self.pass_path=self.home_dir+"/.xxftp/password"
ifos.path.isfile(self.pass_path):
self.message(331,"Passwordrequiredfor"+self.username)
else:
self.message(230,"Identified!")
self.identified=True
return
elifcmd=="PASS":
ifopen(self.pass_path).read()==hashlib.md5(arg).hexdigest():
self.message(230,"Identified!")
self.identified=True
else:
self.message(530,"Notidentified!")
self.identified=False
return
elifnotself.identified:
self.message(530,"PleaseloginwithUSERandPASS.")
return
self.alive_time=time.time()
finish=True
ifcmd=="NOOP":
self.message(200,"ok")
elifcmd=="TYPE":
self.message(200,"ok")
elifcmd=="SYST":
self.message(200,"UNIX")
elifcmd=="EPSV"orcmd=="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()
ifcmd=="EPSV":
self.message(229,"EnteringExtendedPassiveMode(|||"+str(port)+"|)")
else:
ipnum=socket.inet_aton(ip)
self.message(227,"EnteringPassiveMode(%s,%u,%u)."%
(",".join(ip.split(".")),(port>>8&0xff),(port&0xff)))
except:
self.message(500,"failedtocreatedatasocket.")
elifcmd=="EPRT":
self.message(500,"implementEPRTlater...")
elifcmd=="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")
elifcmd=="PWD"orcmd=="XPWD":
ifself.curr_dir=="":self.curr_dir="/"
self.message(257,'"'+self.curr_dir+'"')
elifcmd=="LIST"orcmd=="NLST":
ifarg!=""andarg[0]=="-":arg=""#omitparameters
remote,local,perm,vdir_list,limit_size,is_virtual=self.parse_path(arg)
ifnotos.path.exists(local):
self.message(550,"failed.")
return
ifnotself.establish():return
self.message(150,"ok")
forvinvdir_list:
f=v[0]
ifself.option_utf8:
f=unicode(f,sys.getfilesystemencoding()).encode("utf8")
ifcmd=="NLST":
info=f+"\r\n"
else:
info="d%s%s-------%04u%8s%8s%8lu%s%s\r\n"%(
"r"if"read"inpermelse"-",
"w"if"write"inpermelse"-",
1,"0","0",0,
time.strftime("%b%d%Y",time.localtime(time.time())),
f)
self.data_fd.send(info)
forfinos.listdir(local):
iff[0]==".":continue
path=local+"/"+f
ifself.option_utf8:
f=unicode(f,sys.getfilesystemencoding()).encode("utf8")
ifcmd=="NLST":
info=f+"\r\n"
else:
st=os.stat(path)
info="%s%s%s-------%04u%8s%8s%8lu%s%s\r\n"%(
"-"ifos.path.isfile(path)else"d",
"r"if"read"inpermelse"-",
"w"if"write"inpermelse"-",
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,"Limitsize:"+str(limit_size))
self.data_fd.close()
self.data_fd=0
elifcmd=="REST":
self.file_pos=int(arg)
self.message(250,"ok")
elifcmd=="FEAT":
features="211-Features:\r\nSITES\r\nEPRT\r\nEPSV\r\nMDTM\r\nPASV\r\n"\
"RESTSTREAM\r\nSIZE\r\nUTF8\r\n211End\r\n"
self.fd.send(features)
elifcmd=="OPTS":
arg=arg.upper()
ifarg=="UTF8ON":
self.option_utf8=True
self.message(200,"ok")
elifarg=="UTF8OFF":
self.option_utf8=False
self.message(200,"ok")
else:
self.message(500,"unrecognizedoption")
elifcmd=="CDUP":
finish=False
arg=".."
else:
finish=False
iffinish:return
#Parseargument(It'sapath)
ifarg=="":
self.message(500,"where'smyargument?")
return
remote,local,permission,vdir_list,limit_size,is_virtual=\
self.parse_path(arg)
#cannotdoanythingtovirtualdirectory
ifis_virtual:permission="none"
can_read,can_write,can_modify="read"inpermission,"write"inpermission,"modify"inpermission
newpath=local
try:
ifcmd=="CWD":
if(os.path.isdir(newpath)):
self.curr_dir=remote
self.full_path=newpath
self.message(250,'"'+remote+'"')
else:
self.message(550,"failed")
elifcmd=="MDTM":
ifos.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")
elifcmd=="SIZE":
self.message(231,os.path.getsize(newpath))
elifcmd=="XMKD"orcmd=="MKD":
ifnotcan_modify:
self.message(550,"permissiondenied.")
return
os.mkdir(newpath)
self.message(250,"ok")
elifcmd=="RNFR":
ifnotcan_modify:
self.message(550,"permissiondenied.")
return
self.temp_path=newpath
self.message(350,"renamefrom"+remote)
elifcmd=="RNTO":
os.rename(self.temp_path,newpath)
self.message(250,"RNTOto"+remote)
elifcmd=="XRMD"orcmd=="RMD":
ifnotcan_modify:
self.message(550,"permissiondenied.")
return
os.rmdir(newpath)
self.message(250,"ok")
elifcmd=="DELE":
ifnotcan_modify:
self.message(550,"permissiondenied.")
return
os.remove(newpath)
self.message(250,"ok")
elifcmd=="RETR":
ifnotos.path.isfile(newpath):
self.message(550,"failed")
return
ifnotcan_read:
self.message(550,"permissiondenied.")
return
ifnotself.establish():return
self.message(150,"ok")
f=open(newpath,"rb")
whileself.running:
self.alive_time=time.time()
data=f.read(8192)
iflen(data)==0:break
self.data_fd.send(data)
f.close()
self.data_fd.close()
self.data_fd=0
self.message(226,"ok")
elifcmd=="STOR"orcmd=="APPE":
ifnotcan_write:
self.message(550,"permissiondenied.")
return
ifos.path.exists(newpath)andnotcan_modify:
self.message(550,"permissiondenied.")
return
#Checkspacesizeremained!
used_size=0
iflimit_size>0:
used_size=self.get_dir_size(os.path.dirname(newpath))
ifnotself.establish():return
self.message(150,"ok")
f=open(newpath,("ab"ifcmd=="APPE"else"wb"))
whileself.running:
self.alive_time=time.time()
data=self.data_fd.recv(8192)
iflen(data)==0:break
iflimit_size>0:
used_size=used_size+len(data)
ifused_size>limit_size:break
f.write(data)
f.close()
self.data_fd.close()
self.data_fd=0
iflimit_size>0andused_size>limit_size:
self.message(550,"Exceedinguserspacelimit:"+str(limit_size)+"bytes")
else:
self.message(226,"ok")
else:
self.message(500,cmd+"notimplemented")
except:
self.message(550,"failed.")
defestablish(self):
ifself.data_fd==0:
self.message(500,"nodataconnection")
returnFalse
ifself.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,"failedtoestablishdataconnection")
returnFalse
returnTrue
defread_virtual(self,path):
vdir_list=[]
path=path+"/.xxftp/virtual"
ifos.path.isfile(path):
forvinopen(path,"r").readlines():
items=v.split()
items[1]=items[1].replace("$root",root_dir)
vdir_list.append(items)
returnvdir_list
defget_dir_size(self,folder):
size=0
forpath,dirs,filesinos.walk(folder):
forfinfiles:
size+=os.path.getsize(os.path.join(path,f))
returnsize
defread_size(self,path):
size=0
path=path+"/.xxftp/size"
ifos.path.isfile(path):
size=int(open(path,"r").readline())
returnsize
defread_permission(self,path):
permission="read,write,modify"
path=path+"/.xxftp/permission"
ifos.path.isfile(path):
permission=open(path,"r").readline()
returnpermission
defparse_path(self,path):
ifpath=="":path="."
ifpath[0]!="/":
path=self.curr_dir+"/"+path
s=os.path.normpath(path).replace("\\","/").split("/")
local=self.home_dir
#resetdirectorypermission
vdir_list=self.read_virtual(local)
limit_size=self.read_size(local)
permission=self.read_permission(local)
remote=""
is_virtual=False
fornameins:
name=name.lstrip(".")
ifname=="":continue
remote=remote+"/"+name
is_virtual=False
forvinvdir_list:
ifv[0]==name:
permission=v[2]
local=v[1]
limit_size=self.read_size(local)
is_virtual=True
ifnotis_virtual:local=local+"/"+name
vdir_list=self.read_virtual(local)
return(remote,local,permission,vdir_list,limit_size,is_virtual)
defrun(self):
'''ConnectionProcess'''
try:
iflen(conn_list)>max_connections:
self.message(500,"toomanyconnections!")
self.fd.close()
self.running=False
return
#WelcomeMessage
ifos.path.exists(root_dir+"/xxftp.welcome"):
self.message(220,open(root_dir+"/xxftp.welcome").read())
else:
self.message(220,"xxftp(Python)www.xiaoxia.org")
#CommandLoop
line=""
whileself.running:
data=self.fd.recv(4096)
iflen(data)==0:break
line+=data
ifline[-2:]!="\r\n":continue
line=line[:-2]
space=line.find("")
ifspace==-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"connectionend",self.fd,"user",self.username
defmessage(self,code,s):
'''SendFtpMessage'''
s=str(s).replace("\r","")
ss=s.split("\n")
iflen(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"
ifself.option_utf8:
r=unicode(r,sys.getfilesystemencoding()).encode("utf8")
self.fd.send(r)
defserver_listen():
globalconn_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"ftpdislisteningon",listen_ip+":"+str(listen_port)
whileTrue:
conn_fd,remote_addr=listen_fd.accept()
print"connectionfrom",remote_addr,"conn_list",len(conn_list)
conn=FtpConnection(conn_fd)
conn.start()
conn_lock.acquire()
conn_list.append(conn)
#checktimeout
try:
curr_time=time.time()
forconninconn_list:
ifint(curr_time-conn.alive_time)>conn_timeout:
ifconn.running==True:
conn.fd.shutdown(socket.SHUT_RDWR)
conn.running=False
conn_list=[connforconninconn_listifconn.running]
except:
printsys.exc_info()
conn_lock.release()
defmain():
server_listen()
if__name__=="__main__":
main()
THE END

发表回复