More than 400 lines of Python code implements an FTP server

Source: Internet
Author: User
Python version
Achieve more and more complete functionality than the previous XXFTP
1, continue to support multi-user
2. Continue to support virtual directory
3. Added permission settings to support user root directory and map virtual directory
4. Increase the support limit the size of the user root directory or virtual directory space

Features of Xxftp
1, open source, cross-platform
2. Simple and easy to use
3. No database Required
4, the scalability of the 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, mapping a virtual directory, you can upload files but not allow changes!

How to use
The same way Xxftp used before in C:

1. Create a root directory to the user directories.
Configure it in config.
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.

The structure of your FTP server root May is like:

-/root
-xxftp.welcome
-xxftp.goodbye

-user1
-.xxftp
-password
-...
-user2
-.xxftp
-password
-...
-anonymous Source Code

The code is 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 ("identified!")
Self.identified = True
Return
elif cmd = = "PASS":
If open (Self.pass_path). Read () = = HASHLIB.MD5 (ARG). Hexdigest ():
Self.message ("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 ($, "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 (227, "Entering Passive Mode (%s,%u,%u)."
(",". Join (Ip.split (".")), (Port>>8&0xff), (PORT&AMP;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 (550, "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 (226, "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 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 (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 (+, "OK")
elif cmd = = "RNFR":
If not can_modify:
Self.message (550, "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 (550, "Permission denied.")
Return
Os.rmdir (NewPath)
Self.message (+, "OK")
elif cmd = = "DELE":
If not can_modify:
Self.message (550, "Permission denied.")
Return
Os.remove (NewPath)
Self.message (+, "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 ("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 ("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 (+, CMD + "not implemented")
Except
Self.message (550, "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 (UP, open (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 ()

  • Contact Us

    The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

    If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

    A Free Trial That Lets You Build Big!

    Start building with 50+ products and up to 12 months usage for Elastic Compute Service

    • Sales Support

      1 on 1 presale consultation

    • After-Sales Support

      24/7 Technical Support 6 Free Tickets per Quarter Faster Response

    • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.