Pythonバージョン
以前のxxftpよりもますます完全な機能を実現
1、 複数のユーザーを引き続きサポート
2、 仮想ディレクトリのサポートを継続
3、 仮想ディレクトリをマッピングするためのユーザールートディレクトリと権限設定のサポートが追加されました
4、 ユーザーのルートディレクトリまたは仮想ディレクトリのスペースサイズを制限するためのサポートが追加されました
**xxftp **の機能
1、 オープンソース、クロスプラットフォーム
2、 シンプルで使いやすい
3、 データベースは必要ありません
4、 超スケーラビリティ
5、 独自のプライベートFTPサーバーを想定して、xxftpを無料で使用できます
匿名アカウントが利用できます!
匿名ルートディレクトリは読み取り専用で、仮想ディレクトリをマップします。ファイルはアップロードできますが、変更することはできません。
指示
C言語で書かれたxxftpと同じ方法を使用します
FTPサーバーのディレクトリ構造
- /root
- xxftp.welcome
- xxftp.goodbye
- user1
- . xxftp
- password
- ...- user2
- . xxftp
- password
- ...- 匿名のソースコード
コードは次のように表示されます。
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()
コンテンツの拡張:
FTPサーバーコード:
import socket,os,time
import hashlib
server =socket.socket()
server.bind(('0.0.0.0',6666))
server.listen()print("待つ....")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() #指示とファイル名を記録する
print(filename)
# ファイルが現在のディレクトリに存在するかどうかを確認します。ファイルはディレクトリではなくファイルである必要があります
if os.path.isfile(filename):
f =open(filename,'rb')
# m = hashlib.md5() #md5を作成します
file_size = os.stat(filename).st_size #stat()ファイルのサイズを返すことができます
conn.send((str(file_size)).encode()) #送信ファイルサイズ
conn.recv(1024) #返品情報を待っています
for line in f:
# m.updata(line)
conn.send(line)
# print("file md5",m.hexdigest()) #md5値を出力
f.close()
これで、PythonがFTP関数を実装する方法に関するこの記事は終わりです。Pythonによって実装されるより単純なFTPコンテンツについては、ZaLou.Cnを検索してください。
Recommended Posts