My FTP server -- ftp. py

Source: Internet
Author: User
Tags ftp protocol

In the above article, we briefly learned the FTP protocol, link: http://everet.org/2012/03/ftp-protocol.html.

If you are interested, you can look around.

I recently read the FTP protocol, so I decided to write an FTP server for fun. After all, I have been writing applications, so I want to write down the server-side programs.

The result is FTP. py, which is inspired by web. py.

FTP. py

FTP. py supports multi-thread and multi-process modes and virtual users.

The multi-process mode supports running as another user, enhancing security.

The user settings are written in FTP. py. config. If the file does not exist, the anonymous account is used by default.

The format of the FTP. py. config file is as follows:

Account_info = {
'Et': {'pass': '000000', 'home _ dir': '/root /'},
'Lst': {'pass': '000000', 'home _ dir': '/tmp /'}
}

To switch the multi-process mode or multi-thread mode, you only need to create different servers. It is easy to add a parameter selection, but it will be done if you have time. I generally use the multi-process mode.

# Server = ftpthreadserver ()
Server = ftpforkserver ()

The source code is as follows:

For the latest source code, see git: https://github.com/cedricporter/et-python/tree/master/ftp.

#!/usr/bin/env python# author:  Hua Liang [ Stupid ET ]# email:   et@everet.org# website: http://EverET.org#import socket, os, stat, threading, time, sys, re, signal, selecthost = '0.0.0.0'port = 21limit_connection_number = 3runas_user = 'www-data'account_info = {    'anonymous':{'pass':'', 'home_dir':'/tmp/'},    } try:    '''You can write your account_info in ftp.py.config'''    execfile('ftp.py.config')except Exception, e:    print eclass FTPConnection:    '''You can add handle func by startswith handle_ prefix.    When the connection receives CWD command, it'll use handle_CWD to handle it.    '''    def __init__(self, fd, remote_ip):        self.fd = fd        self.data_fd = 0        self.options = {'pasv': False, 'utf8': False}        self.data_host = ''        self.data_port = 0        self.localhost = fd.getsockname()[0]        self.home_dir = '/tmp/'        self.curr_dir = '/'        self.running = True        self.handler = dict(            [(method[7:], getattr(self, method)) \            for method in dir(self) \            if method.startswith("handle_") and callable(getattr(self, method))])    def start(self):         try:            self.say_welcome()            while self.running:                success, command, arg = self.recv()                command = command.upper()                if self.options['utf8']:                    arg = unicode(arg, 'utf8').encode(sys.getfilesystemencoding())                print '[', command, ']', arg                if not success:                     self.send_msg(500, "Failed")                    continue                if not self.handler.has_key(command):                    self.send_msg(500, "Command Not Found")                    continue                try:                    self.handler[command](arg)                except OSError, e:                    print e                    self.send_msg(500, 'Permission denied')            self.say_bye()            self.fd.close()        except Exception, e:            self.running = False            print e        return True    def send_msg(self, code, msg):        if self.options['utf8']:            msg = unicode(msg, sys.getfilesystemencoding()).encode('utf8')        message = str(code) + ' ' + msg + '\r\n'        self.fd.send(message)    def recv(self):        '''returns 3 tuples, success, command, arg'''        try:            success, buf, command, arg = True, '', '', ''            while True:                data = self.fd.recv(4096)                if not data:                    self.running = False                    break                buf += data                if buf[-2:] == '\r\n': break            split = buf.find(' ')            command, arg = (buf[:split], buf[split + 1:].strip()) if split != -1 else (buf.strip(), '')        except:            success = False        return success, command, arg    def say_welcome(self):        self.send_msg(220, "Welcome to EverET.org FTP")    def say_bye(self):        self.handle_BYE('')    def data_connect(self):        '''establish data connection'''        if self.data_fd == 0:            self.send_msg(500, "no data connection")            return False        elif self.options['pasv']:            fd, addr = self.data_fd.accept()            self.data_fd.close()            self.data_fd = fd        else:            try:                self.data_fd.connect((self.data_host, self.data_port))            except:                self.send_msg(500, "failed to connect")                return False        return True    def close_data_fd(self):        self.data_fd.close()        self.data_fd = 0    def parse_path(self, path):        if path == '': path = '.'        if path[0] != '/': path = self.curr_dir + '/' + path        print 'parse_path', path        split_path = os.path.normpath(path).replace('\\', '/').split('/')        remote = ''         local = self.home_dir        print split_path        for item in split_path:            if item.startswith('..') or item == '': continue # ignore parent directory            remote += '/' + item            local += '/' + item        if remote == '': remote = '/'        print 'remote', remote, 'local', local        return remote, local    # Command Handlers    def handle_USER(self, arg):        if arg in account_info:            self.username = arg            self.send_msg(331, "Need password")        else:            self.send_msg(500, "Invalid User")            self.running = False    def handle_PASS(self, arg):        if arg == account_info[self.username]['pass']:             self.home_dir = account_info[self.username]['home_dir']            if os.path.isdir(self.home_dir):                self.send_msg(230, "OK")                return        self.send_msg(530, "Password is not corrected")        self.running = False    def handle_QUIT(self, arg):        self.handle_BYE(arg)    def handle_BYE(self, arg):        self.running = False        self.send_msg(200, "OK")    def handle_CDUP(self, arg):        self.handle_CWD('..')    def handle_XPWD(self, arg):        self.handle_PWD(arg)    def handle_PWD(self, arg):        remote, local = self.parse_path(self.curr_dir)        self.send_msg(257, '"' + remote + '"')    def handle_CWD(self, arg):        remote, local = self.parse_path(arg)        try:            os.listdir(local)            self.curr_dir = remote            self.send_msg(250, "OK")        except Exception, e:            print e            self.send_msg(500, "Change directory failed!")    def handle_SIZE(self, arg):        remote, local = self.parse_path(self.curr_dir)        self.send_msg(231, str(os.path.getsize(local)))    def handle_SYST(self, arg):        self.send_msg(215, "UNIX")    def handle_STOR(self, arg):        remote, local = self.parse_path(arg)        if not self.data_connect(): return        self.send_msg(125, "OK")        f = open(local, 'wb')        print f, local        while True:            data = self.data_fd.recv(8192)            if len(data) == 0: break            f.write(data)        f.close()        self.close_data_fd()        self.send_msg(226, "OK")    def handle_RETR(self, arg):        print 'in RETR'        remote, local = self.parse_path(arg)        if not self.data_connect(): return        self.send_msg(125, "OK")        f = open(local, 'rb')        while True:            data = f.read(8192)            if len(data) == 0: break            self.data_fd.send(data)        f.close()        self.close_data_fd()        self.send_msg(226, "OK")    def handle_TYPE(self, arg):        self.send_msg(220, "OK")    def handle_RNFR(self, arg):        remote, local = self.parse_path(arg)        self.rename_tmp_path = local        self.send_msg(350, 'rename from ' + remote)    def handle_RNTO(self, arg):        remote, local = self.parse_path(arg)        os.rename(self.rename_tmp_path, local)        self.send_msg(250, 'rename to ' + remote)    def handle_NLST(self, arg):        if not self.data_connect(): return        self.send_msg(125, "OK")        remote, local = self.parse_path(self.curr_dir)        for filename in os.listdir(local):            self.data_fd.send(filename + '\r\n')        self.send_msg(226, "Limit")        self.close_data_fd()    def handle_XMKD(self, arg):        self.handle_MKD(arg)    def handle_MKD(self, arg):        remote, local = self.parse_path(arg)        if os.path.exists(local):            self.send_msg(500, "Folder is already existed")            return        os.mkdir(local)        self.send_msg(257, "OK")    def handle_XRMD(self, arg):        self.handle_RMD(arg)    def handle_RMD(self, arg):        remote, local = self.parse_path(arg)        if not os.path.exists(local):            self.send_msg(500, "Folder is not existed")            return        os.rmdir(local)        self.send_msg(250, "OK")    def handle_LIST(self, arg):        if not self.data_connect(): return         self.send_msg(125, "OK")        template = "%s%s%s------- %04u %8s %8s %8lu %s %s\r\n"        remote, local = self.parse_path(self.curr_dir)        for filename in os.listdir(local):            path = local + '/' + filename            if os.path.isfile(path) or os.path.isdir(path): # ignores link or block file                status = os.stat(path)                msg = template % (                    'd' if os.path.isdir(path) else '-',                    'r', 'w', 1, '0', '0',                     status[stat.ST_SIZE],                     time.strftime("%b %d  %Y", time.localtime(status[stat.ST_MTIME])),                     filename)                if self.options['utf8']: msg = unicode(msg, sys.getfilesystemencoding()).encode('utf8')                self.data_fd.send(msg)        self.send_msg(226, "Limit")        self.close_data_fd()    def handle_PASV(self, arg):        self.options['pasv'] = True        try:            self.data_fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM)            self.data_fd.bind((self.localhost, 0))            self.data_fd.listen(1)            ip, port = self.data_fd.getsockname()            self.send_msg(227, 'Enter Passive Mode (%s,%u,%u).' %                    (','.join(ip.split('.')), (port >> 8 & 0xff), (port & 0xff)))        except Exception, e:            print e            self.send_msg(500, 'passive mode failed')    def handle_PORT(self, arg):        try:            if self.data_fd:                self.data_fd.close()            t = arg.split(',')            self.data_host = '.'.join(t[:4])            self.data_port = int(t[4]) * 256 + int(t[5])            self.data_fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM)        except:            self.send_msg(500, "PORT failed")        self.send_msg(200, "OK")    def handle_DELE(self, arg):        remote, local = self.parse_path(arg)        if not os.path.exists(local):            self.send_msg(450, "File not exist")            return        os.remove(local)        self.send_msg(250, 'File deleted')    def handle_OPTS(self, arg):        if arg.upper() == "UTF8 ON":            self.options['utf8'] = True            self.send_msg(200, "OK")        elif arg.upper() == "UTF8 OFF":            self.options['utf8'] = False            self.send_msg(200, "OK")        else:            self.send_msg(500, "Invalid argument")            class FTPThread(threading.Thread):    '''FTPConnection Thread Wrapper'''    def __init__(self, fd, remote_ip):        threading.Thread.__init__(self)        self.ftp = FTPConnection(fd, remote_ip)    def run(self):        self.ftp.start()        print "Thread done"class FTPThreadServer:    '''FTP Server which is using thread'''    def serve_forever(self):        listen_fd = socket.socket()        listen_fd.bind((host, port))        listen_fd.listen(512)        while True:            print 'new server'            client_fd, client_addr = listen_fd.accept()            handler = FTPThread(client_fd, client_addr)            handler.start()class FTPForkServer:    '''FTP Fork Server, use process per user'''    def child_main(self, client_fd, client_addr, write_end):        '''never return'''        for fd in self.read_fds:            os.close(fd)        self.read_fds = []        uid = get_uid(runas_user)        os.setgid(uid)        os.setuid(uid)        try:            handler = FTPConnection(client_fd, client_addr)            handler.start()        except: pass        os.write(write_end, str(write_end))        sys.exit()    def serve_forever(self):        listen_fd = socket.socket()        listen_fd.bind((host, port))        listen_fd.listen(512)        self.read_fds = [listen_fd]        while True:            rlist, wlist, xlist = select.select(self.read_fds, [], [])            if listen_fd in rlist:                print 'new server'                 print self.read_fds                client_fd, client_addr = listen_fd.accept()                if len(self.read_fds) > limit_connection_number:                    print 'read_fds length = ', len(self.read_fds)                    client_fd.close()                    continue                try:                    read_end, write_end = os.pipe()                    self.read_fds.append(read_end)                    fork_result = os.fork()                    if fork_result == 0: # child process                        listen_fd.close()                        self.read_fds.remove(listen_fd)                        self.child_main(client_fd, client_addr, write_end) # never return                    else:                        os.close(write_end)                except Exception, e:                    print e                    print 'Fork failed'                                for read_fd in rlist:                if read_fd == listen_fd: continue                data = os.read(read_fd, 32)                self.read_fds.remove(read_fd)                os.close(read_fd)                        def get_uid(username = 'www-data'):    '''get uid by username, I don't know whether there's a    function can get it, so I wrote this function.'''    pwd = open('/etc/passwd', 'r')    pat = re.compile(username + ':.*?:(.*?):.*?')    for line in pwd.readlines():        try:            uid = pat.search(line).group(1)        except: continue        return int(uid)def main():    #server = FTPThreadServer()    server = FTPForkServer()    server.serve_forever()if __name__ == '__main__':    import sys    #sys.stdout = open('/var/log/ftp.py.log', 'w')    signal.signal(signal.SIGCHLD, signal.SIG_IGN)    main()

Link: http://everet.org/2012/03/ftp-server.html

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.