1
# Copyright 2009 Canonical Ltd. This software is licensed under the
2
# GNU Affero General Public License version 3 (see the file LICENSE).
10
from zope.server.ftp.server import (
12
STORChannel as OriginalSTORChannel,
14
from zope.server.serverbase import ServerBase
15
from zope.server.taskthreads import ThreadedTaskDispatcher
17
from lp.poppy.filesystem import UploadFileSystem
20
class Channel(FTPServerChannel):
22
def __init__(self, server, conn, addr, adj=None):
23
# Work around a zope3 bug where the status messages dict is copied by
24
# reference, not by value.
25
self.status_messages = dict(self.status_messages)
26
self.status_messages['SERVER_READY'] = (
27
'220 %s Canonical FTP server ready.')
29
FTPServerChannel.__init__(self, server, conn, addr, adj=None)
30
self.peername = self.socket.getpeername()
31
self.uploadfilesystem, self.fsroot = server.newClient(self)
32
self.hook = server.auth_verify_hook
35
FTPServerChannel.close(self)
36
self.server.clientFinished(self)
38
def _getFileSystem(self):
39
return self.uploadfilesystem
41
def received(self, data):
42
# XXX Steve Alexander 2005-01-18
43
# This is a work-around for a bug in Zope 3's ServerChannelBase
44
# that it doesn't update self.last_activity.
45
# This method can be removed once Zope3 is fixed, and we're using
47
# http://collector.zope.org/Zope3-dev/350
48
self.record_activity()
49
FTPServerChannel.received(self, data)
51
def record_activity(self):
52
self.last_activity = time()
54
def cmd_pass(self, args):
55
'See IFTPCommandHandler'
56
self.authenticated = 0
58
credentials = (self.username, password)
62
if not self.hook(self.fsroot, self.username, password):
67
self.reply('LOGIN_MISMATCH')
68
self.close_when_done()
70
self.credentials = credentials
71
self.authenticated = 1
72
self.reply('LOGIN_SUCCESS')
74
def cmd_stor(self, args, write_mode='w'):
75
'See IFTPCommandHandler'
77
self.reply('ERR_ARGS')
79
path = self._generatePath(args)
82
if self.restart_position:
83
self.start = self.restart_position
84
mode = write_mode + self.type_mode_map[self.transfer_mode]
86
if not self._getFileSystem().writable(path):
87
self.reply('ERR_OPEN_WRITE', "Can't write file")
90
cdc = STORChannel(self, (path, mode, start))
91
self.syncConnectData(cdc)
92
self.reply('OPEN_CONN', (self.type_map[self.transfer_mode], path))
94
def cmd_cwd(self, args):
95
"""Permissive 'cwd', creates any target directories requested.
97
It relies on the filesystem layer to create directories recursivelly.
99
path = self._generatePath(args)
100
if not self._getFileSystem().type(path) == 'd':
101
self._getFileSystem().mkdir(path)
103
self.reply('SUCCESS_250', 'CWD')
106
class STORChannel(OriginalSTORChannel):
108
def received (self, data):
110
self.inbuf.append(data)
111
self.control_channel.record_activity()
112
# This is the point at which some data for an upload has been
113
# received by the server from a client.
116
class Server(ServerBase):
118
channel_class = Channel
120
def __init__(self, ip, port,
121
new_client_hook, client_done_hook, auth_verify_hook,
123
ServerBase.__init__(self, ip, port, *args, **kw)
124
self.new_client_hook = new_client_hook
125
self.client_done_hook = client_done_hook
126
self.auth_verify_hook = auth_verify_hook
128
def newClient(self, channel):
129
fsroot = tempfile.mkdtemp("-poppy")
130
uploadfilesystem = UploadFileSystem(fsroot)
131
clienthost, clientport = channel.peername
133
self.new_client_hook(fsroot, clienthost, clientport)
135
# Almost bare except, result logged, to keep server running.
136
self.logger.exception("Exception during new client hook")
137
return uploadfilesystem, fsroot
139
def clientFinished(self, channel):
140
clienthost, clientport = channel.peername
142
self.client_done_hook(channel.fsroot, clienthost, clientport)
144
# Almost bare except, result logged, to keep server running.
145
self.logger.exception("Exception during client done hook")
148
def run_server(host, port, ident, numthreads,
149
new_client_hook, client_done_hook, auth_verify_hook=None):
150
task_dispatcher = ThreadedTaskDispatcher()
151
task_dispatcher.setThreadCount(numthreads)
152
server = Server(host, port,
153
new_client_hook, client_done_hook, auth_verify_hook,
154
task_dispatcher=task_dispatcher)
155
server.SERVER_IDENT = ident
158
except KeyboardInterrupt:
159
# Exit without spewing an exception.