[Zope3-checkins]
SVN: Zope3/branches/srichter-twisted-integration/src/zope/app/server/
No new functionality - just merge the sftp package into the
ftp directory.
Michael Kerrin
michael.kerrin at openapp.biz
Sun May 15 14:37:22 EDT 2005
Log message for revision 30360:
No new functionality - just merge the sftp package into the ftp directory.
Changed:
U Zope3/branches/srichter-twisted-integration/src/zope/app/server/configure.zcml
U Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/__init__.py
A Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/configure.zcml
A Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/sftp.py
A Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/sftpserver.py
D Zope3/branches/srichter-twisted-integration/src/zope/app/server/sftp/
-=-
Modified: Zope3/branches/srichter-twisted-integration/src/zope/app/server/configure.zcml
===================================================================
--- Zope3/branches/srichter-twisted-integration/src/zope/app/server/configure.zcml 2005-05-15 17:07:30 UTC (rev 30359)
+++ Zope3/branches/srichter-twisted-integration/src/zope/app/server/configure.zcml 2005-05-15 18:37:22 UTC (rev 30360)
@@ -22,12 +22,6 @@
provides=".interfaces.IServerType"
/>
- <utility
- name="FTP"
- component=".ftp.ftpserver"
- provides=".interfaces.IServerType"
- />
+ <include package=".ftp" />
- <include package=".sftp" />
-
</configure>
Modified: Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/__init__.py
===================================================================
--- Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/__init__.py 2005-05-15 17:07:30 UTC (rev 30359)
+++ Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/__init__.py 2005-05-15 18:37:22 UTC (rev 30360)
@@ -11,12 +11,14 @@
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
-"""FTP server factories.
+"""FTP and SFTP server factories.
"""
from zope.app.server.utils import FTPRequestFactory
from zope.app.server.server import ServerType
from zope.app.server.ftp.server import FTPFactory
+from zope.app.server.server import SSHServerType
+from sftpserver import SFTPFactory
def createFTPFactory(db):
request_factory = FTPRequestFactory(db)
@@ -26,3 +28,16 @@
return factory
ftpserver = ServerType(createFTPFactory, 8021)
+
+
+def createSFTPFactory(db, hostkey):
+ """
+ Note that all SSH factories must contain the extra hostkey arguement.
+ """
+ request_factory = FTPRequestFactory(db)
+
+ factory = SFTPFactory(request_factory, hostkey = hostkey)
+
+ return factory
+
+sftpserver = SSHServerType(createSFTPFactory, 8115)
Added: Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/configure.zcml
===================================================================
--- Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/configure.zcml 2005-05-15 17:07:30 UTC (rev 30359)
+++ Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/configure.zcml 2005-05-15 18:37:22 UTC (rev 30360)
@@ -0,0 +1,20 @@
+<configure xmlns="http://namespaces.zope.org/zope">
+
+ <utility
+ name="SFTP"
+ component=".sftpserver"
+ provides="..interfaces.IServerType"
+ />
+
+ <utility
+ name="FTP"
+ component=".ftpserver"
+ provides="..interfaces.IServerType"/>
+
+ <adapter
+ factory=".sftp.SFTPServerForZope"
+ for=".sftpserver.ZopeAvatar"
+ provides="twisted.conch.ssh.filetransfer.ISFTPServer"
+ />
+
+</configure>
Property changes on: Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/configure.zcml
___________________________________________________________________
Name: svn:eol-style
+ native
Added: Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/sftp.py
===================================================================
--- Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/sftp.py 2005-05-15 17:07:30 UTC (rev 30359)
+++ Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/sftp.py 2005-05-15 18:37:22 UTC (rev 30360)
@@ -0,0 +1,245 @@
+##############################################################################
+#
+# Copyright (c) 2001,2002,2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (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 the ISFTPServer ssh file transfer protocol for Zope.
+"""
+
+import posixpath, os, stat, array, datetime
+from cStringIO import StringIO
+
+from zope.interface import implements
+
+from twisted.conch.interfaces import ISFTPServer, ISFTPFile
+from twisted.conch.ssh.filetransfer import FXF_APPEND, FXF_READ
+
+class SFTPServerForZope(object):
+ implements(ISFTPServer)
+
+ def __init__(self, avatar):
+ self.avatar = avatar
+
+ def gotVersion(self, otherVersion, extData):
+ return {}
+
+ def openFile(self, filename, flags, attrs):
+ fp = ZopeSFTPFile(self, self._generatePath(filename), flags, attrs)
+
+ return fp
+
+ def removeFile(self, filename):
+ self.avatar.fs_access.remove(self._generatePath(filename))
+
+ def renameFile(self, oldpath, newpath):
+ oldpath = self._generatePath(oldpath)
+ newpath = self._generatePath(newpath)
+ self.avatar.fs_access.rename(oldpath, newpath)
+
+ def makeDirectory(self, path, attrs):
+ self.avatar.fs_access.mkdir(self._generatePath(path))
+
+ def removeDirectory(self, path):
+ self.avatar.fs_access.rmdir(self._generatePath(path))
+
+ def openDirectory(self, path):
+ return ZopeSFTPDirectory(self, self._generatePath(path))
+
+ def getAttrs(self, path, followLinks):
+ fp = ZopeSFTPFile(self, self._generatePath(path), FXF_READ, {})
+
+ return fp.getAttrs()
+
+ def setAttrs(self, path, attrs):
+ pass
+
+ def readLink(self, path):
+ raise NotImplementedError, "readLink not implemented."
+
+ def makeLink(self, linkPath, targetPath):
+ raise NotImplementedError, "makeLink not implemented."
+
+ def realPath(self, path):
+ return self._generatePath(path)
+
+ def extendedRequest(self, extendedName, extendedData):
+ raise NotImplementedError, \
+ "Zope doesn't support any SFTP extensions."
+
+ def _generatePath(self, args):
+ path = posixpath.join('/', args)
+ return posixpath.normpath(path)
+
+
+class ZopeSFTPDirectory(object):
+
+ def __init__(self, server, directory):
+ self.server = server
+ self.dir = directory
+ self.files = self.server.avatar.fs_access.names(directory)
+
+ def __iter__(self):
+ return self
+
+ def next(self):
+ try:
+ f = self.files.pop(0)
+ except IndexError:
+ raise StopIteration
+ else:
+ file = ZopeSFTPFile(self.server, posixpath.join(self.dir, f),
+ FXF_READ, {})
+ s = file.getAttrs()
+ longname = _lsLine(f, s)
+ return (str(f), str(longname), s)
+
+ def close(self):
+ self.files = []
+
+
+class ZopeSFTPFile(object):
+ implements(ISFTPFile)
+
+ def __init__(self, server, filename, flags, attrs):
+ self.server = server
+ self.filename = filename
+ self.attrs = attrs
+
+ if flags & FXF_APPEND == FXF_APPEND:
+ self.append = True
+ else:
+ self.append = False
+
+ def close(self):
+ pass
+
+ def readChunk(self, offset, length):
+ outstream = StringIO()
+ self.server.avatar.fs_access.readfile(self.filename,
+ outstream,
+ start = offset,
+ end = offset + length)
+ chunk = outstream.getvalue()
+ outstream.close()
+
+ return chunk
+
+ def writeChunk(self, offset, data):
+ instream = StringIO(data)
+ self.server.avatar.fs_access.writefile(self.filename,
+ instream,
+ start = offset,
+ end = offset + len(data),
+ append = self.append)
+ instream.close()
+
+ def getAttrs(self):
+ attrs = self.server.avatar.fs_access.lsinfo(self.filename)
+
+ retattrs = {}
+ retattrs['size'] = attrs.get('size', 0)
+ ## uid
+ ## gid
+ ## permissions
+ permissions = 0
+ def _isKeyTrue(key):
+ return attrs.has_key(key) and attrs[key] is True
+ if _isKeyTrue('owner_readable'):
+ permissions |= stat.S_IRUSR
+ if _isKeyTrue('owner_writable'):
+ permissions |= stat.S_IWUSR
+ if _isKeyTrue('owner_executable'):
+ permissions |= stat.S_IXUSR
+ if _isKeyTrue('group_readable'):
+ permissions |= stat.S_IRGRP
+ if _isKeyTrue('group_writable'):
+ permissions |= stat.S_IWGRP
+ if _isKeyTrue('group_executable'):
+ permissions |= stat.S_IXGRP
+ if _isKeyTrue('other_readable'):
+ permissions |= stat.S_IROTH
+ if _isKeyTrue('other_writable'):
+ permissions |= stat.S_IWOTH
+ if _isKeyTrue('other_executable'):
+ permissions |= stat.S_IXOTH
+ filetype = self.server.avatar.fs_access.type(self.filename)
+ if filetype == 'd':
+ permissions |= stat.S_IFDIR
+ elif filetype == 'f':
+ permissions |= stat.S_IFREG
+ retattrs['permissions'] = permissions
+ ## atime
+ if attrs['mtime'] is not None:
+ retattrs['mtime'] = attrs['mtime']
+ return retattrs
+
+ def setAttrs(self, attrs):
+ ## IFileSystem doesn't currently support the setting of attributes.
+ pass
+
+## modified from twisted.consh.unix._lsLine
+def _lsLine(name, s):
+ ## mode = s.st_mode
+ mode = s['permissions']
+ perms = array.array('c', '-'*10)
+ ft = stat.S_IFMT(mode)
+ if stat.S_ISDIR(ft): perms[0] = 'd'
+ elif stat.S_ISCHR(ft): perms[0] = 'c'
+ elif stat.S_ISBLK(ft): perms[0] = 'b'
+ elif stat.S_ISREG(ft): perms[0] = '-'
+ elif stat.S_ISFIFO(ft): perms[0] = 'f'
+ elif stat.S_ISLNK(ft): perms[0] = 'l'
+ elif stat.S_ISSOCK(ft): perms[0] = 's'
+ else: perms[0] = '!'
+ # user
+ if mode&stat.S_IRUSR:perms[1] = 'r'
+ if mode&stat.S_IWUSR:perms[2] = 'w'
+ if mode&stat.S_IXUSR:perms[3] = 'x'
+ # group
+ if mode&stat.S_IRGRP:perms[4] = 'r'
+ if mode&stat.S_IWGRP:perms[5] = 'w'
+ if mode&stat.S_IXGRP:perms[6] = 'x'
+ # other
+ if mode&stat.S_IROTH:perms[7] = 'r'
+ if mode&stat.S_IWOTH:perms[8] = 'w'
+ if mode&stat.S_IXOTH:perms[9] = 'x'
+ # suid/sgid
+ if mode&stat.S_ISUID:
+ if perms[3] == 'x': perms[3] = 's'
+ else: perms[3] = 'S'
+ if mode&stat.S_ISGID:
+ if perms[6] == 'x': perms[6] = 's'
+ else: perms[6] = 'S'
+ l = perms.tostring()
+ ## l += str(s.st_nlink).rjust(5) + ' '
+ l += str(0).rjust(5) + ' '
+ ## un = str(s.st_uid)
+ un = s.get('owner_name', 'na')
+ l += un.ljust(9)
+ ## gr = str(s.st_gid)
+ gr = s.get('group_name', 'na')
+ l += gr.ljust(9)
+ ## sz = str(s.st_size)
+ sz = str(s.get('size', 0))
+ l += sz.rjust(8)
+ l += ' '
+ ## sixmo = 60 * 60 * 24 * 7 * 26
+ sixmo = datetime.timedelta(days = 26 * 7) # six months time delta object
+ ## if s.st_mtime + sixmo < time.time(): # last edited more than 6mo ago
+ mtime = s['mtime']
+ if (mtime + sixmo).date() < datetime.datetime.now().date():
+ ## l += time.strftime("%b %2d %Y ", time.localtime(s.st_mtime))
+ l += mtime.strftime("%b %2d %Y ") ## , time.localtime(mtime))
+ else:
+ ## l += time.strftime("%b %2d %H:%S ", time.localtime(s.st_mtime))
+ l += mtime.strftime("%b %2d %H:%S ") ## , time.localtime(mtime))
+ l += name
+ return l
Property changes on: Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/sftp.py
___________________________________________________________________
Name: svn:eol-style
+ native
Added: Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/sftpserver.py
===================================================================
--- Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/sftpserver.py 2005-05-15 17:07:30 UTC (rev 30359)
+++ Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/sftpserver.py 2005-05-15 18:37:22 UTC (rev 30360)
@@ -0,0 +1,180 @@
+##############################################################################
+#
+# Copyright (c) 2001,2002,2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (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.
+#
+##############################################################################
+""" Twisted specific integration classes for SFTP.
+"""
+from zope.interface import implements
+
+from zope.app.server.utils import ZopeSimpleAuthenticatation
+from zope.app.server.utils import PublisherFileSystem
+from zope.app.server.interfaces import IFileSystem
+
+from twisted.cred.portal import IRealm, Portal
+from twisted.cred.credentials import IUsernamePassword
+
+from twisted.conch.ssh.filetransfer import FileTransferServer
+from twisted.conch.ssh.connection import SSHConnection
+from twisted.conch.ssh.session import SSHSession
+from twisted.conch.ssh.common import getNS
+from twisted.conch.ssh.forwarding import openConnectForwardingClient
+try:
+ from twisted.conch.ssh.factory import SSHFactory
+ from twisted.conch.ssh.userauth import SSHUserAuthServer
+ from twisted.conch.ssh.keys import getPublicKeyString, \
+ getPrivateKeyObject, objectType
+except ImportError, e:
+ SSHFactory = object # so we can create the SFTPFactory
+ class SSHFactory(object):
+ def doStart(self):
+ raise ImportError, \
+ "Please install pycrypto to run the SFTP server."
+ SSHUserAuthServer = None
+from twisted.conch.avatar import ConchUser
+from twisted.conch.interfaces import IConchUser
+
+
+class ZopeAvatar(ConchUser):
+ implements(IConchUser)
+
+ def __init__(self, fs_access):
+ ConchUser.__init__(self)
+
+ assert IFileSystem.providedBy(fs_access), "Invalid File Publisher"
+ self.fs_access = fs_access
+
+ self.channelLookup.update(
+ {'session': SSHSession,
+ 'direct-tcpip': openConnectForwardingClient})
+
+ self.subsystemLookup.update(
+ {'sftp': FileTransferServer})
+
+
+class SFTPRealm(object):
+ implements(IRealm)
+
+ def __init__(self, request_factory):
+ self.request_factory = request_factory
+
+ def requestAvatar(self, avatarId, mind, *interfaces):
+ """
+ >>> from zope.app.server.utils import FTPRequestFactory
+ >>> from ZODB.tests.util import DB
+ >>> from twisted.cred import credentials
+ >>> creds = credentials.UsernamePassword('bob', '123')
+ >>> db = DB()
+ >>> request_factory = FTPRequestFactory(db)
+ >>> realm = SFTPRealm(request_factory)
+ >>> print realm.request_factory is request_factory
+ True
+
+ Now test this method
+
+ >>> result = realm.requestAvatar(creds, None, IConchUser)
+ >>> print result[0] is IConchUser
+ True
+ >>> print isinstance(result[1], ZopeAvatar)
+ True
+
+ ZopeAvatar should contain a PublisherFileSystem instance assigned to
+ its fs_access attribute.
+
+ >>> from zope.app.server.utils import PublisherFileSystem
+ >>> print isinstance(result[1].fs_access, PublisherFileSystem)
+ True
+
+ Make sure the PublisherFileSystems credentials are correct.
+
+ >>> print result[1].fs_access.credentials[0] == 'bob'
+ True
+ >>> print result[1].fs_access.credentials[1] == '123'
+ True
+
+ This method only supports the IConchUser has the interface for
+ the avatar.
+
+ >>> from zope.interface import Interface
+ >>> realm.requestAvatar(creds, None, Interface)
+ Traceback (most recent call last):
+ ...
+ NotImplementedError: Only IConchUser interface is supported by this realm.
+ >>> db.close()
+
+ """
+ if IConchUser in interfaces:
+ fs_access = PublisherFileSystem(
+ (avatarId.username, avatarId.password),
+ self.request_factory)
+ avatar = ZopeAvatar(fs_access)
+ return IConchUser, avatar, lambda : None
+ raise NotImplementedError, \
+ "Only IConchUser interface is supported by this realm."
+
+
+class SFTPFactory(SSHFactory):
+ services = {
+ 'ssh-userauth': SSHUserAuthServer,
+ 'ssh-connection': SSHConnection
+ }
+
+ def getPublicKeys(self):
+ ks = {}
+ k = getPublicKeyString(self.hostkey + '.pub')
+ t = getNS(k)[0]
+ ks[t] = k
+ return ks
+
+ def getPrivateKeys(self):
+ ks = {}
+ k = getPrivateKeyObject(self.hostkey)
+ t = objectType(k)
+ ks[t] = k
+ return ks
+
+ def getPrimes(self):
+ return None
+
+ def __init__(self, request_factory, hostkey):
+ """
+ The portal performs a simple authentication
+
+ >>> from ZODB.tests.util import DB
+ >>> from zope.app.server.utils import FTPRequestFactory
+ >>> db = DB()
+ >>> request_factory = FTPRequestFactory(db)
+ >>> sftpfactory = SFTPFactory(request_factory, hostkey = None)
+ >>> print sftpfactory.portal.realm.request_factory is request_factory
+ True
+
+ So the portal initializes ok.
+
+ >>> from twisted.cred import credentials
+ >>> portal = sftpfactory.portal
+ >>> creds = credentials.UsernamePassword('bob', '123')
+ >>> deferred = portal.login(creds, None, IConchUser)
+ >>> result = deferred.result
+ >>> print type(result)
+ <type 'tuple'>
+ >>> db.close()
+
+ The result variable should be the return value of the 'requestAvatar'
+ method of the SFTPRealm method. This method contains its own test.
+ """
+ self.hostkey = hostkey
+
+ r = SFTPRealm(request_factory)
+ p = Portal(r)
+
+ p.registerChecker(ZopeSimpleAuthenticatation(),
+ IUsernamePassword)
+ self.portal = p
Property changes on: Zope3/branches/srichter-twisted-integration/src/zope/app/server/ftp/sftpserver.py
___________________________________________________________________
Name: svn:eol-style
+ native
More information about the Zope3-Checkins
mailing list