[Zope-Checkins] CVS: Zope3/lib/python/Zope/Server/FTP - CommonFTPActivityLogger.py:1.2 FTPServer.py:1.2 FTPServerChannel.py:1.2 FTPStatusMessages.py:1.2 IFTPCommandHandler.py:1.2 OSEmulators.py:1.2 PassiveAcceptor.py:1.2 PublisherFTPServer.py:1.2 PublisherFTPServerChannel.py:1.2 PublisherFTPTask.py:1.2 PublisherFilesystemAccess.py:1.2 RecvChannel.py:1.2 TestFilesystemAccess.py:1.2 XmitChannel.py:1.2 __init__.py:1.2
Jim Fulton
jim@zope.com
Mon, 10 Jun 2002 19:30:07 -0400
Update of /cvs-repository/Zope3/lib/python/Zope/Server/FTP
In directory cvs.zope.org:/tmp/cvs-serv20468/lib/python/Zope/Server/FTP
Added Files:
CommonFTPActivityLogger.py FTPServer.py FTPServerChannel.py
FTPStatusMessages.py IFTPCommandHandler.py OSEmulators.py
PassiveAcceptor.py PublisherFTPServer.py
PublisherFTPServerChannel.py PublisherFTPTask.py
PublisherFilesystemAccess.py RecvChannel.py
TestFilesystemAccess.py XmitChannel.py __init__.py
Log Message:
Merged Zope-3x-branch into newly forked Zope3 CVS Tree.
=== Zope3/lib/python/Zope/Server/FTP/CommonFTPActivityLogger.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+
+$Id$
+"""
+
+
+import time
+import sys
+
+from Zope.Server.Logger.FileLogger import FileLogger
+from Zope.Server.Logger.ResolvingLogger import ResolvingLogger
+from Zope.Server.Logger.UnresolvingLogger import UnresolvingLogger
+
+
+class CommonFTPActivityLogger:
+ """Outputs hits in common HTTP log format.
+ """
+
+ def __init__(self, logger_object=None, resolver=None):
+ if logger_object is None:
+ logger_object = FileLogger(sys.stdout)
+
+ if resolver is not None:
+ self.output = ResolvingLogger(resolver, logger_object)
+ else:
+ self.output = UnresolvingLogger(logger_object)
+
+
+ def log(self, task):
+ """
+ Receives a completed task and logs it in the
+ common log format.
+ """
+
+ now = time.localtime(time.time())
+
+ message = '%s [%s] "%s %s"' %(task.channel.username,
+ time.strftime('%Y/%m/%d %H:%M', now),
+ task.m_name[4:].upper(),
+ task.channel.cwd,
+ )
+
+ self.output.log('127.0.0.1', message)
=== Zope3/lib/python/Zope/Server/FTP/FTPServer.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+
+$Id$
+"""
+import asyncore
+from FTPServerChannel import FTPServerChannel
+from Zope.Server.ServerBase import ServerBase
+from Zope.Server.VFS.IFilesystemAccess import IFilesystemAccess
+
+
+
+class FTPServer(ServerBase):
+ """Generic FTP Server"""
+
+ channel_class = FTPServerChannel
+ SERVER_IDENT = 'Zope.Server.FTPServer'
+
+
+ def __init__(self, ip, port, fs_access, *args, **kw):
+
+ assert IFilesystemAccess.isImplementedBy(fs_access)
+ self.fs_access = fs_access
+
+ super(FTPServer, self).__init__(ip, port, *args, **kw)
+
+
+if __name__ == '__main__':
+ from Zope.Server.TaskThreads import ThreadedTaskDispatcher
+ from Zope.Server.VFS.OSFileSystem import OSFileSystem
+ from Zope.Server.VFS.TestFilesystemAccess import TestFilesystemAccess
+ td = ThreadedTaskDispatcher()
+ td.setThreadCount(4)
+ fs = OSFileSystem('/')
+ fs_access = TestFilesystemAccess(fs)
+ FTPServer('', 8021, fs_access, task_dispatcher=td)
+ try:
+ while 1:
+ asyncore.poll(5)
+ print 'active channels:', FTPServerChannel.active_channels
+ except KeyboardInterrupt:
+ print 'shutting down...'
+ td.shutdown()
=== Zope3/lib/python/Zope/Server/FTP/FTPServerChannel.py 1.1 => 1.2 === (440/540 lines abridged)
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+
+$Id$
+"""
+
+import posixpath
+import stat
+import sys
+import socket
+import time
+
+from Zope.Server.LineReceiver.LineServerChannel import LineServerChannel
+from FTPStatusMessages import status_msgs
+from OSEmulators import ls_longify
+
+from IFTPCommandHandler import IFTPCommandHandler
+from PassiveAcceptor import PassiveAcceptor
+from RecvChannel import RecvChannel
+from XmitChannel import XmitChannel, ApplicationXmitStream
+from Zope.Server.VFS.UsernamePassword import UsernamePassword
+from Zope.Exceptions import Unauthorized
+
+
+class FTPServerChannel(LineServerChannel):
+ """The FTP Server Channel represents a connection to a particular
+ client. We can therefore store information here."""
+
+ __implements__ = LineServerChannel.__implements__, IFTPCommandHandler
+
+
+ # List of commands that are always available
+ special_commands = ('cmd_quit', 'cmd_type', 'cmd_noop', 'cmd_user',
+ 'cmd_pass')
+
+ # These are the commands that are accessing the filesystem.
+ # Since this could be also potentially a longer process, these commands
+ # are also the ones that are executed in a different thread.
+ thread_commands = ('cmd_appe', 'cmd_cdup', 'cmd_cwd', 'cmd_dele',
[-=- -=- -=- 440 lines omitted -=- -=- -=-]
+ pass
+ if len(path_args) < 1:
+ dir = '.'
+ else:
+ dir = path_args[0]
+
+ dir = self._generatePath(dir)
+ return self.listdir(dir, long)
+
+
+ def connectDataChannel(self, cdc):
+ pa = self.passive_acceptor
+ if pa:
+ # PASV mode.
+ if pa.ready:
+ # a connection has already been made.
+ conn, addr = pa.ready
+ cdc.set_socket (conn)
+ cdc.connected = 1
+ self.passive_acceptor.close()
+ self.passive_acceptor = None
+ # else we're still waiting for a connect to the PASV port.
+ # FTP Explorer is known to do this.
+ else:
+ # not in PASV mode.
+ ip, port = self.client_addr
+ cdc.create_socket(socket.AF_INET, socket.SOCK_STREAM)
+ if self.bind_local_minus_one:
+ cdc.bind(('', self.server.port - 1))
+ try:
+ cdc.connect((ip, port))
+ except socket.error, err:
+ cdc.close('NO_DATA_CONN')
+
+
+ def notifyClientDCClosing(self, *reply_args):
+ if self.client_dc is not None:
+ self.client_dc = None
+ if reply_args:
+ self.reply(*reply_args)
+
+
+ def close(self):
+ LineServerChannel.close(self)
+ # Make sure the client DC gets closed too.
+ cdc = self.client_dc
+ if cdc is not None:
+ self.client_dc = None
+ cdc.close()
+
=== Zope3/lib/python/Zope/Server/FTP/FTPStatusMessages.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+
+$Id$
+"""
+
+
+status_msgs = {
+ 'OPEN_DATA_CONN' : '150 Opening %s mode data connection for file list',
+ 'OPEN_CONN' : '150 Opening %s connection for %s',
+ 'SUCCESS_200' : '200 %s command successful.',
+ 'TYPE_SET_OK' : '200 Type set to %s.',
+ 'STRU_OK' : '200 STRU F Ok.',
+ 'MODE_OK' : '200 MODE S Ok.',
+ 'FILE_DATE' : '213 %4d%02d%02d%02d%02d%02d',
+ 'FILE_SIZE' : '213 %d Bytes',
+ 'HELP_START' : '214-The following commands are recognized',
+ 'HELP_END' : '214 Help done.',
+ 'SERVER_TYPE' : '215 %s Type: %s',
+ 'SERVER_READY' : '220 %s FTP server (Zope Async/Thread V0.1) ready.',
+ 'GOODBYE' : '221 Goodbye.',
+ 'SUCCESS_226' : '226 %s command successful.',
+ 'TRANS_SUCCESS' : '226 Transfer successful.',
+ 'PASV_MODE_MSG' : '227 Entering Passive Mode (%s,%d,%d)',
+ 'LOGIN_SUCCESS' : '230 Login Successful.',
+ 'SUCCESS_250' : '250 %s command successful.',
+ 'SUCCESS_257' : '257 %s command successful.',
+ 'ALREADY_CURRENT' : '257 "%s" is the current directory.',
+ 'PASS_REQUIRED' : '331 Password required',
+ 'RESTART_TRANSFER' : '350 Restarting at %d. Send STORE or '
+ 'RETRIEVE to initiate transfer.',
+ 'READY_FOR_DEST' : '350 File exists, ready for destination.',
+ 'NO_DATA_CONN' : "425 Can't build data connection",
+ 'TRANSFER_ABORTED' : '426 Connection closed; transfer aborted.',
+ 'CMD_UNKNOWN' : "500 '%s': command not understood.",
+ 'INTERNAL_ERROR' : "500 Internal error: %s",
+ 'ERR_ARGS' : '500 Bad command arguments',
+ 'MODE_UNKOWN' : '502 Unimplemented MODE type',
+ 'WRONG_BYTE_SIZE' : '504 Byte size must be 8',
+ 'STRU_UNKNOWN' : '504 Unimplemented STRU type',
+ 'NOT_AUTH' : "530 You are not authorized to perform the "
+ "'%s' command",
+ 'LOGIN_REQUIRED' : '530 Please log in with USER and PASS',
+ 'LOGIN_MISMATCH' : '530 The username and password do not match.',
+ 'ERR_NO_LIST' : '550 Could not list directory: %s',
+ 'ERR_NO_DIR' : '550 "%s": No such directory.',
+ 'ERR_NO_FILE' : '550 "%s": No such file.',
+ 'ERR_IS_NOT_FILE' : '550 "%s": Is not a file',
+ 'ERR_CREATE_FILE' : '550 Error creating file.',
+ 'ERR_CREATE_DIR' : '550 Error creating directory: %s',
+ 'ERR_DELETE_FILE' : '550 Error deleting file: %s',
+ 'ERR_DELETE_DIR' : '550 Error removing directory: %s',
+ 'ERR_OPEN_READ' : '553 Could not open file for reading: %s',
+ 'ERR_OPEN_WRITE' : '553 Could not open file for writing: %s',
+ 'ERR_IO' : '553 I/O Error: %s',
+ 'ERR_RENAME' : '560 Could not rename "%s" to "%s": %s',
+ 'ERR_RNFR_SOURCE' : '560 No source filename specify. Call RNFR first.',
+ }
+
=== Zope3/lib/python/Zope/Server/FTP/IFTPCommandHandler.py 1.1 => 1.2 ===
+
+from Interface import Interface
+
+class IFTPCommandHandler(Interface):
+ """This interface defines all the FTP commands that are supported by the
+ server.
+
+ Every command takes the command line as first arguments, since it is
+ responsible
+ """
+
+ def cmd_abor(args):
+ """Abort operation. No read access required.
+ """
+
+ def cmd_appe(args):
+ """Append to a file. Write access required.
+ """
+
+ def cmd_cdup(args):
+ """Change to parent of current working directory.
+ """
+
+ def cmd_cwd(args):
+ """Change working directory.
+ """
+
+ def cmd_dele(args):
+ """Delete a file. Write access required.
+ """
+
+ def cmd_help(args):
+ """Give help information. No read access required.
+ """
+
+ def cmd_list(args):
+ """Give list files in a directory.
+ """
+
+ def cmd_mdtm(args):
+ """Show last modification time of file.
+
+ Example output: 213 19960301204320
+
+ Geez, there seems to be a second syntax for this fiel, where one
+ can also set the modification time using:
+ MDTM datestring pathname
+
+ """
+
+ def cmd_mkd(args):
+ """Make a directory. Write access required.
+ """
+
+ def cmd_mode(args):
+ """Set file transfer mode. No read access required. Obselete.
+ """
+
+ def cmd_nlst(args):
+ """Give name list of files in directory.
+ """
+
+ def cmd_noop(args):
+ """Do nothing. No read access required.
+ """
+
+ def cmd_pass(args):
+ """Specify password.
+ """
+
+ def cmd_pasv(args):
+ """Prepare for server-to-server transfer. No read access required.
+ """
+
+ def cmd_port(args):
+ """Specify data connection port. No read access required.
+ """
+
+ def cmd_pwd(args):
+ """Print the current working directory.
+ """
+
+ def cmd_quit(args):
+ """Terminate session. No read access required.
+ """
+
+ def cmd_rest(args):
+ """Restart incomplete transfer.
+ """
+
+ def cmd_retr(args):
+ """Retrieve a file.
+ """
+
+ def cmd_rmd(args):
+ """Remove a directory. Write access required.
+ """
+
+ def cmd_rnfr(args):
+ """Specify rename-from file name. Write access required.
+ """
+
+ def cmd_rnto(args):
+ """Specify rename-to file name. Write access required.
+ """
+
+ def cmd_size(args):
+ """Return size of file.
+ """
+
+ def cmd_stat(args):
+ """Return status of server. No read access required.
+ """
+
+ def cmd_stor(args):
+ """Store a file. Write access required.
+ """
+
+ def cmd_stru(args):
+ """Set file transfer structure. Obselete."""
+
+ def cmd_syst(args):
+ """Show operating system type of server system.
+
+ No read access required.
+
+ Replying to this command is of questionable utility,
+ because this server does not behave in a predictable way
+ w.r.t. the output of the LIST command. We emulate Unix ls
+ output, but on win32 the pathname can contain drive
+ information at the front Currently, the combination of
+ ensuring that os.sep == '/' and removing the leading slash
+ when necessary seems to work. [cd'ing to another drive
+ also works]
+
+ This is how wuftpd responds, and is probably the most
+ expected. The main purpose of this reply is so that the
+ client knows to expect Unix ls-style LIST output.
+
+ one disadvantage to this is that some client programs
+ assume they can pass args to /bin/ls. a few typical
+ responses:
+
+ 215 UNIX Type: L8 (wuftpd)
+ 215 Windows_NT version 3.51
+ 215 VMS MultiNet V3.3
+ 500 'SYST': command not understood. (SVR4)
+ """
+
+ def cmd_type(args):
+ """Specify data transfer type. No read access required.
+ """
+
+ def cmd_user(args):
+ """Specify user name. No read access required.
+ """
+
+
+
+# this is the command list from the wuftpd man page
+# '!' requires write access
+#
+not_implemented_commands = {
+ 'acct': 'specify account (ignored)',
+ 'allo': 'allocate storage (vacuously)',
+ 'site': 'non-standard commands (see next section)',
+ 'stou': 'store a file with a unique name', #!
+ 'xcup': 'change to parent of current working directory (deprecated)',
+ 'xcwd': 'change working directory (deprecated)',
+ 'xmkd': 'make a directory (deprecated)', #!
+ 'xpwd': 'print the current working directory (deprecated)',
+ 'xrmd': 'remove a directory (deprecated)', #!
+}
=== Zope3/lib/python/Zope/Server/FTP/OSEmulators.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+
+$Id$
+"""
+
+import stat
+import time
+
+months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
+ 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
+
+mode_table = {
+ '0':'---',
+ '1':'--x',
+ '2':'-w-',
+ '3':'-wx',
+ '4':'r--',
+ '5':'r-x',
+ '6':'rw-',
+ '7':'rwx'
+ }
+
+
+def ls_longify((filename, stat_info)):
+ """Formats a directory entry similarly to the 'ls' command.
+ """
+
+ # Note that we expect a little deviance from the result of os.stat():
+ # we expect the ST_UID and ST_GID fields to contain user IDs.
+ username = str(stat_info[stat.ST_UID])[:8]
+ grpname = str(stat_info[stat.ST_GID])[:8]
+
+ mode_octal = ('%o' % stat_info[stat.ST_MODE])[-3:]
+ mode = ''.join(map(mode_table.get, mode_octal))
+ if stat.S_ISDIR (stat_info[stat.ST_MODE]):
+ dirchar = 'd'
+ else:
+ dirchar = '-'
+ date = ls_date (long(time.time()), stat_info[stat.ST_MTIME])
+ return '%s%s %3d %-8s %-8s %8d %s %s' % (
+ dirchar,
+ mode,
+ stat_info[stat.ST_NLINK],
+ username,
+ grpname,
+ stat_info[stat.ST_SIZE],
+ date,
+ filename
+ )
+
+
+def ls_date (now, t):
+ """Emulate the 'ls' command's date field. It has two formats.
+ If the date is more than 180 days in the past or future, then
+ it's like this:
+ Oct 19 1995
+ otherwise, it looks like this:
+ Oct 19 17:33
+ """
+ try:
+ info = time.localtime(t)
+ except:
+ info = time.localtime(0)
+
+ # 15,600,000 == 86,400 * 180
+ if abs((now - t) > 15600000):
+ return '%s %2d %5d' % (
+ months[info[1]-1],
+ info[2],
+ info[0]
+ )
+ else:
+ return '%s %2d %02d:%02d' % (
+ months[info[1]-1],
+ info[2],
+ info[3],
+ info[4]
+ )
+
+
+def msdos_longify((file, stat_info)):
+ """This matches the output of NT's ftp server (when in MSDOS mode)
+ exactly.
+ """
+ if stat.S_ISDIR(stat_info[stat.ST_MODE]):
+ dir = '<DIR>'
+ else:
+ dir = ' '
+ date = msdos_date(stat_info[stat.ST_MTIME])
+ return '%s %s %8d %s' % (date, dir, stat_info[stat.ST_SIZE], file)
+
+
+def msdos_date(t):
+ try:
+ info = time.gmtime(t)
+ except:
+ info = time.gmtime(0)
+
+ # year, month, day, hour, minute, second, ...
+ if info[3] > 11:
+ merid = 'PM'
+ info[3] = info[3] - 12
+ else:
+ merid = 'AM'
+
+ return '%02d-%02d-%02d %02d:%02d%s' % (
+ info[1], info[2], info[0]%100, info[3], info[4], merid )
=== Zope3/lib/python/Zope/Server/FTP/PassiveAcceptor.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+
+$Id$
+"""
+
+import asyncore
+import socket
+
+
+class PassiveAcceptor(asyncore.dispatcher):
+ """This socket accepts a data connection, used when the server has
+ been placed in passive mode. Although the RFC implies that we
+ ought to be able to use the same acceptor over and over again,
+ this presents a problem: how do we shut it off, so that we are
+ accepting connections only when we expect them? [we can't]
+
+ wuftpd, and probably all the other servers, solve this by
+ allowing only one connection to hit this acceptor. They then
+ close it. Any subsequent data-connection command will then try
+ for the default port on the client side [which is of course
+ never there]. So the 'always-send-PORT/PASV' behavior seems
+ required.
+
+ Another note: wuftpd will also be listening on the channel as
+ soon as the PASV command is sent. It does not wait for a data
+ command first.
+
+ --- we need to queue up a particular behavior:
+ 1) xmit : queue up producer[s]
+ 2) recv : the file object
+
+ It would be nice if we could make both channels the same.
+ Hmmm.."""
+
+ __implements__ = asyncore.dispatcher.__implements__
+
+ ready = None
+
+ def __init__ (self, control_channel):
+ asyncore.dispatcher.__init__ (self)
+ self.control_channel = control_channel
+ self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
+ # bind to an address on the interface that the
+ # control connection is coming from.
+ self.bind ( (self.control_channel.getsockname()[0], 0) )
+ self.addr = self.getsockname()
+ self.listen(1)
+
+
+ def log (self, *ignore):
+ pass
+
+
+ def handle_accept (self):
+ conn, addr = self.accept()
+ conn.setblocking(0)
+ dc = self.control_channel.client_dc
+ if dc is not None:
+ dc.set_socket(conn)
+ dc.addr = addr
+ dc.connected = 1
+ self.control_channel.passive_acceptor = None
+ else:
+ self.ready = conn, addr
+ self.close()
+
=== Zope3/lib/python/Zope/Server/FTP/PublisherFTPServer.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+
+$Id$
+"""
+from FTPServer import FTPServer
+
+from Zope.Server.FTP.PublisherFilesystemAccess import PublisherFilesystemAccess
+
+class PublisherFTPServer(FTPServer):
+ """Generic FTP Server"""
+
+
+ def __init__(self, request_factory, name, ip, port, *args, **kw):
+ self.request_factory = request_factory
+ fs_access = PublisherFilesystemAccess(request_factory)
+ super(PublisherFTPServer, self).__init__(ip, port, fs_access,
+ *args, **kw)
=== Zope3/lib/python/Zope/Server/FTP/PublisherFTPServerChannel.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+
+$Id$
+"""
+
+from FTPServerChannel import FTPServerChannel
+
+class PublisherFTPServerChannel(FTPServerChannel):
+ """The FTP Server Channel represents a connection to a particular
+ client. We can therefore store information here."""
+
+ __implements__ = FTPServerChannel.__implements__
+
+
+ def authenticate(self):
+ if self._getFilesystem()._authenticate():
+ return 1, 'User successfully authenticated.'
+ else:
+ return 0, 'User could not be authenticated.'
+
+
+
+
+
+
=== Zope3/lib/python/Zope/Server/FTP/PublisherFTPTask.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+
+$Id$
+"""
+
+from FTPTask import FTPTask
+from Zope.Publisher.Publish import publish
+
+
+class PublisherFTPTask(FTPTask):
+ """ """
+
+ __implements__ = FTPTask.__implements__
+
+
+ def execute(self):
+ """ """
+ server = self.channel.server
+ env = self.create_environment()
+ instream = self.request_data.getBodyStream()
+
+ request = server.request_factory(instream, self, env)
+ publish(request)
+
+
+ def create_environment(self):
+ request_data = self.request_data
+ channel = self.channel
+ server = channel.server
+
+ # This should probably change to reflect calling the FileSystem
+ # methods
+ env = {'command': request_data.command
+ 'args': request_data.args
+ }
+
+
+ return env
=== Zope3/lib/python/Zope/Server/FTP/PublisherFilesystemAccess.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Implementation of IFilesystemAccess intended only for testing.
+
+$Id$
+"""
+
+from cStringIO import StringIO
+from Zope.Exceptions import Unauthorized
+from Zope.App.Security.PrincipalRegistry import principalRegistry
+
+from Zope.Server.VFS.PublisherFileSystem import PublisherFileSystem
+from Zope.Server.VFS.IFilesystemAccess import IFilesystemAccess
+from Zope.Server.VFS.IUsernamePassword import IUsernamePassword
+
+
+class PublisherFilesystemAccess:
+
+ __implements__ = IFilesystemAccess
+
+ def __init__(self, request_factory):
+ self.request_factory = request_factory
+
+
+ def authenticate(self, credentials):
+ assert IUsernamePassword.isImplementedBy(credentials)
+ env = {'credentials' : credentials}
+ request = self.request_factory(StringIO(''), StringIO(), env)
+ id = principalRegistry.authenticate(request)
+ if id is None:
+ raise Unauthorized
+
+
+ def open(self, credentials):
+ return PublisherFileSystem(credentials, self.request_factory)
+
+
=== Zope3/lib/python/Zope/Server/FTP/RecvChannel.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+
+$Id$
+"""
+from Zope.Server.ServerChannelBase import ChannelBaseClass
+from Zope.Server.Buffers import OverflowableBuffer
+from Zope.Server.ITask import ITask
+
+
+class RecvChannel(ChannelBaseClass):
+ """ """
+
+ complete_transfer = 0
+ _fileno = None # provide a default for asyncore.dispatcher._fileno
+
+ def __init__ (self, control_channel, finish_args):
+ self.control_channel = control_channel
+ self.finish_args = finish_args
+ self.inbuf = OverflowableBuffer(control_channel.adj.inbuf_overflow)
+ ChannelBaseClass.__init__(self, None, None, control_channel.adj)
+ # Note that this channel starts out in async mode.
+
+ def writable (self):
+ return 0
+
+ def handle_connect (self):
+ pass
+
+ def received (self, data):
+ if data:
+ self.inbuf.append(data)
+
+ def handle_close (self):
+ """Client closed, indicating EOF."""
+ c = self.control_channel
+ task = FinishedRecvTask(c, self.inbuf, self.finish_args)
+ self.complete_transfer = 1
+ self.close()
+ c.start_task(task)
+
+ def close(self, *reply_args):
+ try:
+ c = self.control_channel
+ if c is not None:
+ self.control_channel = None
+ if not self.complete_transfer and not reply_args:
+ # Not all data transferred
+ reply_args = ('TRANSFER_ABORTED',)
+ c.notifyClientDCClosing(*reply_args)
+ finally:
+ if self.socket is not None:
+ # XXX asyncore.dispatcher.close() doesn't like socket == None
+ ChannelBaseClass.close(self)
+
+
+
+class FinishedRecvTask:
+
+ __implements__ = ITask
+
+ def __init__(self, control_channel, inbuf, finish_args):
+ self.control_channel = control_channel
+ self.inbuf = inbuf
+ self.finish_args = finish_args
+
+
+ ############################################################
+ # Implementation methods for interface
+ # Zope.Server.ITask
+
+ def service(self):
+ """Called to execute the task.
+ """
+ close_on_finish = 0
+ c = self.control_channel
+ try:
+ try:
+ c.finishedRecv(self.inbuf, self.finish_args)
+ except socket.error:
+ close_on_finish = 1
+ if c.adj.log_socket_errors:
+ raise
+ finally:
+ c.end_task(close_on_finish)
+
+
+ def cancel(self):
+ 'See Zope.Server.ITask.ITask'
+ self.control_channel.close_when_done()
+
+
+ def defer(self):
+ 'See Zope.Server.ITask.ITask'
+ pass
+
+ #
+ ############################################################
+
=== Zope3/lib/python/Zope/Server/FTP/TestFilesystemAccess.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Implementation of IFilesystemAccess intended only for testing.
+
+$Id$
+"""
+
+from Zope.Server.VFS.IFilesystemAccess import IFilesystemAccess
+from Zope.Server.VFS.IUsernamePassword import IUsernamePassword
+from Zope.Exceptions import Unauthorized
+
+
+class TestFilesystemAccess:
+
+ __implements__ = IFilesystemAccess
+
+ passwords = {'foo': 'bar'}
+
+ def __init__(self, fs):
+ self.fs = fs
+
+ def authenticate(self, credentials):
+ if not IUsernamePassword.isImplementedBy(credentials):
+ raise Unauthorized
+ name = credentials.getUserName()
+ if not (name in self.passwords):
+ raise Unauthorized
+ if credentials.getPassword() != self.passwords[name]:
+ raise Unauthorized
+
+ def open(self, credentials):
+ self.authenticate(credentials)
+ return self.fs
+
+
=== Zope3/lib/python/Zope/Server/FTP/XmitChannel.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+
+$Id$
+"""
+
+from Zope.Server.ServerChannelBase import ChannelBaseClass
+
+
+class XmitChannel(ChannelBaseClass):
+
+ opened = 0
+ _fileno = None # provide a default for asyncore.dispatcher._fileno
+
+ def __init__ (self, control_channel, ok_reply_args):
+ self.control_channel = control_channel
+ self.ok_reply_args = ok_reply_args
+ self.set_sync()
+ ChannelBaseClass.__init__(self, None, None, control_channel.adj)
+
+ def _open(self):
+ """Signal the client to open the connection."""
+ self.opened = 1
+ self.control_channel.reply(*self.ok_reply_args)
+ self.control_channel.connectDataChannel(self)
+
+ def write(self, data):
+ if self.control_channel is None:
+ raise IOError, 'Client FTP connection closed'
+ if not self.opened:
+ self._open()
+ ChannelBaseClass.write(self, data)
+
+ def readable(self):
+ return not self.connected
+
+ def handle_read(self):
+ # This is only called when making the connection.
+ try:
+ self.recv(1)
+ except:
+ # The connection failed.
+ self.close('NO_DATA_CONN')
+
+ def handle_connect(self):
+ pass
+
+ def handle_comm_error(self):
+ self.close('TRANSFER_ABORTED')
+
+ def close(self, *reply_args):
+ try:
+ c = self.control_channel
+ if c is not None:
+ self.control_channel = None
+ if not reply_args:
+ if not len(self.outbuf):
+ # All data transferred
+ if not self.opened:
+ # Zero-length file
+ self._open()
+ reply_args = ('TRANS_SUCCESS',)
+ else:
+ # Not all data transferred
+ reply_args = ('TRANSFER_ABORTED',)
+ c.notifyClientDCClosing(*reply_args)
+ finally:
+ if self.socket is not None:
+ # XXX asyncore.dispatcher.close() doesn't like socket == None
+ ChannelBaseClass.close(self)
+
+
+class ApplicationXmitStream:
+ """Provide stream output, remapping close() to close_when_done().
+ """
+
+ def __init__(self, xmit_channel):
+ self.write = xmit_channel.write
+ self.flush = xmit_channel.flush
+ self.close = xmit_channel.close_when_done
+
=== Zope3/lib/python/Zope/Server/FTP/__init__.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+
+$Id$
+"""