python ftp服务器定义handle_python实现的ftp服务器

#!/usr/bin/env python#author: Hua Liang [ Stupid ET ]#email: et@everet.org#website: http://EverET.org#importsocket, os, stat, threading, time, struct, getoptimportsys, re, signal, select, logging, logging.handlers

host= '127.0.0.1'port= 21limit_connection_number= 5 #max client number

timeout = 60 * 3 #timeout in second

default_home_dir = os.path.normpath(os.path.abspath(os.curdir)).replace('\\', '/')

logfile= '/var/log/ftp.py.log' if os.name == 'posix' else default_home_dir + 'ftp.py.log'runas_user= 'www-data'global_options= {'run_mode':'fork'}#current working directory

account_info ={'anonymous':{'pass':'', 'home_dir':default_home_dir, 'runas_user':runas_user},

}defrunas(username):if os.name != 'posix': returnuid=get_uid(username)

os.setgid(uid)

os.setuid(uid)classFTPConnection:'''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=default_home_dir

self.curr_dir= '/'self.running=True

self.handler=dict(

[(method[7:], getattr(self, method)) \for method indir(self) \if method.startswith("handle_") andcallable(getattr(self, method))])defstart(self):try:

self.say_welcome()whileself.running:

success, command, arg=self.recv()

command=command.upper()if self.options['utf8']:

arg= unicode(arg, 'utf8').encode(sys.getfilesystemencoding())

logger.info('[' + command + ']' +arg)if notsuccess:

self.send_msg(500, "Failed")continue

if notself.handler.has_key(command):

self.send_msg(500, "Command Not Found")continue

try:

self.handler[command](arg)exceptOSError, e:

logger.error(e)

logger.error("in start")

self.send_msg(500, 'Permission denied')

self.say_bye()exceptException, e:

self.running=False

logger.error(e)

logger.error("in start")finally:

self.fd.close()

logger.info("FTP connnection done.")returnTruedefsend_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)defrecv(self):'''returns 3 tuples, success, command, arg'''

try:

success, buf, command, arg= True, '', '', ''

whileTrue:

data= self.fd.recv(4096)if not data or data <=0:

self.running=False

success=Falsebreakbuf+=dataif buf[-2:] == '\r\n': breaksplit= buf.find(' ')

command, arg= (buf[:split], buf[split + 1:].strip()) if split != -1 else (buf.strip(), '')exceptException, e:

logger.error(e)

logger.error("in recv")

self.running=False

success=Falsereturnsuccess, command, argdefsay_welcome(self):

self.send_msg(220, "Welcome to EverET.org FTP")defsay_bye(self):

self.handle_BYE('')defdata_connect(self):'''establish data connection'''

if self.data_fd ==0:

self.send_msg(500, "no data connection")returnFalseelif self.options['pasv']:

fd, addr=self.data_fd.accept()

self.data_fd.close()

self.data_fd=fdelse:try:

self.data_fd.connect((self.data_host, self.data_port))except:

self.send_msg(500, "failed to connect")returnFalsereturnTruedefclose_data_fd(self):

self.data_fd.close()

self.data_fd=0defparse_path(self, path):if path == '': path = '.'

if path[0] != '/': path = self.curr_dir + '/' +path

logger.info('parse_path' +path)

split_path= os.path.normpath(path).replace('\\', '/').split('/')

remote= ''local=self.home_dirfor item insplit_path:if item.startswith('..') or item == '': continue #ignore parent directory

remote += '/' +item

local+= '/' +itemif remote == '': remote = '/'logger.info(split_path)

logger.info('remote: %s local: %s' %(remote, local))returnremote, local#Command Handlers

defhandle_USER(self, arg):if arg inaccount_info:

self.username=argif self.username == 'anonymous':

self.send_msg(230, 'OK')else:

self.send_msg(331, "Need password")else:

self.send_msg(500, "Invalid User")

self.running=Falsedefhandle_PASS(self, arg):if arg == account_info[self.username]['pass']:

self.home_dir= account_info[self.username]['home_dir']if account_info[self.username].has_key('runas_user'):

user= account_info[self.username]['runas_user']else:

user= 'www-data'runas(user)ifos.path.isdir(self.home_dir):

self.send_msg(230, "OK")returnself.send_msg(530, "Password is not corrected")

self.running=Falsedefhandle_QUIT(self, arg):

self.handle_BYE(arg)defhandle_BYE(self, arg):

self.running=False

self.send_msg(200, "OK")defhandle_CDUP(self, arg):

self.handle_CWD('..')defhandle_XPWD(self, arg):

self.handle_PWD(arg)defhandle_PWD(self, arg):

remote, local=self.parse_path(self.curr_dir)

self.send_msg(257, '"' + remote + '"')defhandle_CWD(self, arg):

remote, local=self.parse_path(arg)try:

os.listdir(local)

self.curr_dir=remote

self.send_msg(250, "OK")exceptException, e:

logger.error(e)

logger.error("in cwd")

self.send_msg(500, "Change directory failed!")defhandle_SIZE(self, arg):

remote, local=self.parse_path(self.curr_dir)

self.send_msg(231, str(os.path.getsize(local)))defhandle_SYST(self, arg):

self.send_msg(215, "UNIX")defhandle_STOR(self, arg):

remote, local=self.parse_path(arg)if not self.data_connect(): returnself.send_msg(125, "OK")

f= open(local, 'wb')whileTrue:

data= self.data_fd.recv(8192)if len(data) == 0: breakf.write(data)

f.close()

self.close_data_fd()

self.send_msg(226, "OK")defhandle_RETR(self, arg):

remote, local=self.parse_path(arg)if not self.data_connect(): returnself.send_msg(125, "OK")

f= open(local, 'rb')whileTrue:

data= f.read(8192)if len(data) == 0: breakself.data_fd.send(data)

f.close()

self.close_data_fd()

self.send_msg(226, "OK")defhandle_TYPE(self, arg):

self.send_msg(220, "OK")defhandle_RNFR(self, arg):

remote, local=self.parse_path(arg)

self.rename_tmp_path=local

self.send_msg(350, 'rename from' +remote)defhandle_RNTO(self, arg):

remote, local=self.parse_path(arg)

os.rename(self.rename_tmp_path, local)

self.send_msg(250, 'rename to' +remote)defhandle_NLST(self, arg):if not self.data_connect(): returnself.send_msg(125, "OK")

remote, local=self.parse_path(self.curr_dir)for filename inos.listdir(local):

self.data_fd.send(filename+ '\r\n')

self.send_msg(226, "Limit")

self.close_data_fd()defhandle_XMKD(self, arg):

self.handle_MKD(arg)defhandle_MKD(self, arg):

remote, local=self.parse_path(arg)ifos.path.exists(local):

self.send_msg(500, "Folder is already existed")returnos.mkdir(local)

self.send_msg(257, "OK")defhandle_XRMD(self, arg):

self.handle_RMD(arg)defhandle_RMD(self, arg):

remote, local=self.parse_path(arg)if notos.path.exists(local):

self.send_msg(500, "Folder is not existed")returnos.rmdir(local)

self.send_msg(250, "OK")defhandle_LIST(self, arg):if not self.data_connect(): returnself.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 inos.listdir(local):

path= local + '/' +filenameif 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()defhandle_PASV(self, arg):

self.options['pasv'] =Truetry:

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)))exceptException, e:

logger.error(e)

logger.error("in pasv")

self.send_msg(500, 'passive mode failed')defhandle_PORT(self, arg):try:ifself.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")defhandle_DELE(self, arg):

remote, local=self.parse_path(arg)if notos.path.exists(local):

self.send_msg(450, "File not exist")returnos.remove(local)

self.send_msg(250, 'File deleted')defhandle_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")classFTPThread(threading.Thread):'''FTPConnection Thread Wrapper'''

def __init__(self, fd, remote_ip):

threading.Thread.__init__(self)

self.ftp=FTPConnection(fd, remote_ip)defrun(self):

self.ftp.start()

logger.info("Thread done")classFTPThreadServer:'''FTP Server which is using thread'''

defserve_forever(self):

listen_fd=socket.socket()

listen_fd.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,1)

listen_fd.bind((host, port))

listen_fd.listen(512)whileTrue:

logger.info('new server')

client_fd, client_addr=listen_fd.accept()

handler=FTPThread(client_fd, client_addr)

handler.start()classFTPForkServer:'''FTP Fork Server, use process per user'''

defchild_main(self, client_fd, client_addr, write_end):'''never return'''

for fd inself.read_fds:

os.close(fd)

self.read_fds=[]try:

handler=FTPConnection(client_fd, client_addr)

handler.start()exceptException, e:

logger.error(e)

logger.error("in child_main")

os.write(write_end, str(write_end))

sys.exit()defserve_forever(self):

listen_fd=socket.socket(socket.AF_INET, socket.SOCK_STREAM)

listen_fd.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,1)

listen_fd.bind((host, port))

listen_fd.listen(512)

self.read_fds=[listen_fd]whileTrue:

rlist, wlist, xlist=select.select(self.read_fds, [], [])if listen_fd inrlist:

client_fd, client_addr=listen_fd.accept()if len(self.read_fds) >limit_connection_number:

logger.error('reject client:' +str(client_addr))

client_fd.close()continue

try:

logger.info('new client:' +str(client_addr))

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)exceptException, e:

logger.error(e)

logger.error('Fork failed')for read_fd inrlist:if read_fd == listen_fd: continuedata= 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 inpwd.readlines():try:

uid= pat.search(line).group(1)except: continue

returnint(uid)def get_logger(handler =logging.StreamHandler()):

logger=logging.getLogger()

formatter= logging.Formatter('%(asctime)s %(levelname)s %(message)s')

handler.setFormatter(formatter)

logger.addHandler(handler)

logger.setLevel(logging.NOTSET)returnloggerdef daemonize(stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'):'''becomes a daemon'''

try:

pid=os.fork()if pid >0: sys.exit(0)exceptOSError, e:

sys.stderr.write("fork #1 failed\n")

sys.exit(1)

os.umask(0)

os.setsid()try:

pid=os.fork()if pid >0: sys.exit(0)exceptOSError, e:

sys.stderr.write("fork #2 failed\n")

sys.exit(1)for f insys.stdout, sys.stderr: f.flush()

si= file(stdin, 'r')

so= file(stdout, 'a+')

se= file(stderr, 'a+', 0)

os.dup2(si.fileno(), sys.stdin.fileno())#0

os.dup2(so.fileno(), sys.stdout.fileno()) #1

os.dup2(se.fileno(), sys.stderr.fileno()) #2

defserve_forever():globalglobal_optionsprintglobal_optionsif global_options['run_mode'] == 'fork':

signal.signal(signal.SIGCHLD, signal.SIG_IGN)

server=FTPForkServer()else:

server=FTPThreadServer()

server.serve_forever()defusage():print '''usage: %s [-d] [-h] [-p port] [-o] [-t]

-d become a daemon

-h help

-p listen port

-o output log to stdout, by default, it outputs to a log file.

-t thread mode, fork model by default

Waring:

The Thread Mode is not complete.

Author:

Hua Liang [ Stupid ET ]

http://EverET.org''' %os.path.basename(sys.argv[0])defparam_handler(opts):globalport, logger, global_options

be_daemon=False

logger=get_logger(logging.FileHandler(logfile))for o, a inopts:if o == '-h':

usage()

sys.exit(0)elif o == '-d':if not os.name == 'posix':print 'Only support the os with posix specifications.'sys.exit(-1)

be_daemon=Trueelif o == '-o':

logger=get_logger()elif o == '-p':try: port =int(a)exceptException, e:

usage()

sys.exit(0)elif o == '-t':

global_options['run_mode'] = 'thread'

if os.name != 'posix' and global_options['run_mode'] == 'fork':print "You can NOT run fork mode in a non posix os,\

please use -t options to run in thread mode"sys.exit(-1)ifbe_daemon: daemonize()if __name__ == '__main__':try:

opts, args= getopt.getopt(sys.argv[1:], 'hdp:ot')exceptgetopt.GetoptError:

usage()

sys.exit(2)

param_handler(opts)

socket.setdefaulttimeout(timeout)'''You can write your account_info in ftp.py.config'''

try: execfile('ftp.py.config')exceptException, e: logger.error(e)

serve_forever()

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值