Python version
Achieved more and more complete functions than the previous xxftp
1、 Continue to support multiple users
2、 Continue to support virtual directories
3、 Added support for user root directory and permission settings for mapping virtual directories
4、 Added support for limiting the space size of the user's root directory or virtual directory
Features of xxftp
1、 Open source, cross platform
2、 Simple and easy to use
3、 No database required
4、 Super scalability
5、 You can use xxftp for free assuming your own private FTP server
Anonymous account can be used!
The anonymous root directory is read-only and maps a virtual directory. Files can be uploaded but not changed!
Instructions
Use the same method as xxftp written in C language
FTP server directory structure
- /root
- xxftp.welcome
- xxftp.goodbye
- user1
- . xxftp
- password
- ...- user2
- . xxftp
- password
- ...- anonymous source code
code show as below:
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 =120classFtpConnection(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":ifopen(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.")returnif 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")returnif not can_read:
self.message(550,"permission denied.")returnif not self.establish():return
self.message(150,"ok")
f =open(newpath,"rb")while self.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")
elif cmd =="STOR" or cmd =="APPE":if not can_write:
self.message(550,"permission denied.")returnif os.path.exists(newpath) and not can_modify:
self.message(550,"permission denied.")return
# Check space size remained!
used_size =0if 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)iflen(data)==0:breakif 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 =0if 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 inopen(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 =0for 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:iflen(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)iflen(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")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"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:ifint(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()
Content expansion:
FTP server code:
import socket,os,time
import hashlib
server =socket.socket()
server.bind(('0.0.0.0',6666))
server.listen()print("wait....")while True:
conn,addr = server.accept()print("new conn:",conn)while True:
data = conn.recv(1024)if not data:print("client is disconnection")break
cmd,filename = data.decode().split() #Record instructions and file names
print(filename)
# Determine whether the file exists in the current directory, and it must be a file, not a directory
if os.path.isfile(filename):
f =open(filename,'rb')
# m = hashlib.md5() #Create md5
file_size = os.stat(filename).st_size #stat()Can return the size of the file
conn.send((str(file_size)).encode()) #Send file size
conn.recv(1024) #Waiting for return information
for line in f:
# m.updata(line)
conn.send(line)
# print("file md5",m.hexdigest()) #Print md5 value
f.close()
So far this article on how Python implements the FTP function is introduced. For more simple FTP content implemented by Python, please search ZaLou.Cn
Recommended Posts