Python version
To achieve more and better features than previous xxftp
1, continue to support multiple users
2, continue to support the virtual directory
3, increase the support user root directory and the right to map the virtual directory settings
4. Increase support limit the size of the user's root directory or virtual directory
Characteristics of Xxftp
1, open source, Cross-platform
2. Simple and easy to use
3. No database Required
4, the scalability of Ultra strong
5, you can use XXFTP for free to assume your own private FTP server
Test address
ftp://xiaoxia.org
Anonymous account can be used!
Anonymous root directory is read-only, mapped a virtual directory, you can upload files but not allowed to change!
How to use
The same way as the xxftp used in C language:
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 are 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 is like:
-/root
-xxftp.welcome
-xxftp.goodbye
-user1
-.xxftp
-password
-...
-user2
-.xxftp
-password
-...
-anonymous Source Code
Copy Code code as follows:
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 the USER and pass.")
Return
Self.alive_time = Time.time ()
finish = True
if cmd = = "NOOP":
Self.message ("OK")
elif cmd = = "TYPE":
Self.message ("OK")
elif cmd = = "Syst":
Self.message ("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 ("Entering passive Mode" (%s,%u,%u). "%
(",". Join (Ip.split (".")), (Port>>8&0xff), (PORT&0XFF)))
Except
Self.message ("Failed to create data socket.")
elif cmd = = "Eprt":
Self.message ("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 ("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 ("failed.")
Return
If not Self.establish (): Return
Self.message ("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 ("Limit size:" + str (limit_size))
Self.data_fd.close ()
SELF.DATA_FD = 0
elif cmd = = "REST":
self.file_pos = Int (ARG)
Self.message ("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 ("OK")
elif arg = = "UTF8 off":
Self.option_utf8 = False
Self.message ("OK")
Else
Self.message ("unrecognized option")
elif cmd = = "Cdup":
finish = False
arg = ".."
Else
finish = False
If Finish:return
# Parse argument (It ' s a path)
if arg = = "":
Self.message ("where ' s my argument?")
Return
Remote, local, permission, Vdir_list, limit_size, is_virtual = \
Self.parse_path (ARG)
# can not does 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 (+, ' "' + Remote + '")
Else
Self.message ("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 ("Failed")
elif cmd = = "SIZE":
Self.message (231, Os.path.getsize (NewPath))
elif cmd = = "XMKD" or cmd = = "MKD":
If not can_modify:
Self.message ("Permission denied.")
Return
Os.mkdir (NewPath)
Self.message ("OK")
elif cmd = = "RNFR":
If not can_modify:
Self.message ("Permission denied.")
Return
Self.temp_path = NewPath
Self.message ("Rename from" + remote)
elif cmd = = "Rnto":
Os.rename (Self.temp_path, NewPath)
Self.message ("Rnto to" + remote)
elif cmd = = "Xrmd" or cmd = = "RMD":
If not can_modify:
Self.message ("Permission denied.")
Return
Os.rmdir (NewPath)
Self.message ("OK")
elif cmd = = "DELE":
If not can_modify:
Self.message ("Permission denied.")
Return
Os.remove (NewPath)
Self.message ("OK")
elif cmd = = "RETR":
If not Os.path.isfile (NewPath):
Self.message ("Failed")
Return
If not can_read:
Self.message ("Permission denied.")
Return
If not Self.establish (): Return
Self.message ("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 ("OK")
elif cmd = = "STOR" or cmd = = "AppE":
If not can_write:
Self.message ("Permission denied.")
Return
If Os.path.exists (NewPath) and not can_modify:
Self.message ("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 ("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 ("Exceeding User space limit:" + str (limit_size) + "bytes")
Else
Self.message ("OK")
Else
Self.message (+, CMD + "not implemented")
Except
Self.message ("failed.")
def establish (self):
If self.data_fd = 0:
Self.message ("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 ("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 ("Too Many connections!")
Self.fd.close ()
self.running = False
Return
# Welcome Message
If os.path.exists (Root_dir + "/xxftp.welcome"):
Self.message (Root_dir + "/xxftp.welcome"). Read ())
Else
Self.message ("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 ()