【问题标题】:FTP Files server in TwistedTwisted 中的 FTP 文件服务器
【发布时间】:2013-01-01 22:18:38
【问题描述】:

我目前正在尝试启动并运行 FTP 服务器,但我什至无法连接 Mac OS X 上的 Finder...

查找器错误:

There was a problem connecting to the server “192.168.1.67”.
Check the server name or IP address, and then try again. If you continue to have problems, contact your system administrator.

标准输出

python FTPtoEMAIL.py 
2013-01-01 17:16:01-0500 [-] Log opened.
2013-01-01 17:16:01-0500 [-] FTPFactory starting on 7654
2013-01-01 17:16:01-0500 [-] Starting factory <twisted.protocols.ftp.FTPFactory instance at 0x6aff80>
2013-01-01 17:16:26-0500 [FTP (ProtocolWrapper),0,192.168.1.67] Setting up avatar
2013-01-01 17:16:26-0500 [FTP (ProtocolWrapper),0,192.168.1.67] apple
2013-01-01 17:16:26-0500 [FTP (ProtocolWrapper),1,192.168.1.67] Setting up avatar
2013-01-01 17:16:26-0500 [FTP (ProtocolWrapper),1,192.168.1.67] apple
^C2013-01-01 17:16:35-0500 [-] Received SIGINT, shutting down.
2013-01-01 17:16:35-0500 [twisted.protocols.ftp.FTPFactory] (TCP Port 7654 Closed)
2013-01-01 17:16:35-0500 [twisted.protocols.ftp.FTPFactory] Stopping factory <twisted.protocols.ftp.FTPFactory instance at 0x6aff80>
2013-01-01 17:16:35-0500 [-] Main loop terminated.

代码

import sys
import EMail
from twisted.protocols.ftp import FTPFactory, FTPRealm
from twisted.cred.portal import Portal
from twisted.cred import checkers
from twisted.cred.checkers import AllowAnonymousAccess, FilePasswordDB
from twisted.internet import reactor
from twisted.python import log
from twisted.internet.defer import succeed, failure

from twisted.protocols.ftp import FTPFactory, FTPRealm, FTP, FTPShell, IFTPShell

# EMail Shell
USER = "Apple"
PASSWORD = "Orange"


class EMailtoFTPShell(object):
    """
        An abstraction of the shell commands used by the FTP protocol for
        a given user account.

        All path names must be absolute.
        """

    def __init__(path, avatar):
        print path
        print avatar

        self.root_path = path
        self.avatar = avatar

    def makeDirectory(path):
        """
            Create a directory.

            @param path: The path, as a list of segments, to create
            @type path: C{list} of C{unicode}

            @return: A Deferred which fires when the directory has been
            created, or which fails if the directory cannot be created.
            """
        print path

        path = os.path.join(self.root_path, path)

        os.mkdir(path)

        return succeed(path)

    def removeDirectory(path):
        """
            Remove a directory.

            @param path: The path, as a list of segments, to remove
            @type path: C{list} of C{unicode}

            @return: A Deferred which fires when the directory has been
            removed, or which fails if the directory cannot be removed.
            """
        print path
        path = os.path.join(self.root_path, path)

        os.rremovedir(path)

        return succeed(path)

    def removeFile(path):
        """
            Remove a file.

            @param path: The path, as a list of segments, to remove
            @type path: C{list} of C{unicode}

            @return: A Deferred which fires when the file has been
            removed, or which fails if the file cannot be removed.
            """
        print path

        path = os.path.join(self.root_path, path)

        os.remove(path)

    def rename(fromPath, toPath):
        """
            Rename a file or directory.

            @param fromPath: The current name of the path.
            @type fromPath: C{list} of C{unicode}

            @param toPath: The desired new name of the path.
            @type toPath: C{list} of C{unicode}

            @return: A Deferred which fires when the path has been
            renamed, or which fails if the path cannot be renamed.
            """
        print fromPath
        print toPath

        fromPath = os.path.join(self.root_path, fromPath)
        toPath   = os.path.join(self.root_path, toPath)

        os.rename(fromPath, toPath)

        succeed(toPath)

    def access(path):
        """
            Determine whether access to the given path is allowed.

            @param path: The path, as a list of segments

            @return: A Deferred which fires with None if access is allowed
            or which fails with a specific exception type if access is
            denied.
            """
        print path

        return succeed(None)

    def stat(path, keys=()):
        """
            Retrieve information about the given path.

            This is like list, except it will never return results about
            child paths.
            """
        print path
        print keys

        #todo

    def list(path, keys=()):
        """
            Retrieve information about the given path.

            If the path represents a non-directory, the result list should
            have only one entry with information about that non-directory.
            Otherwise, the result list should have an element for each
            child of the directory.

            @param path: The path, as a list of segments, to list
            @type path: C{list} of C{unicode}

            @param keys: A tuple of keys desired in the resulting
            dictionaries.

            @return: A Deferred which fires with a list of (name, list),
            where the name is the name of the entry as a unicode string
            and each list contains values corresponding to the requested
            keys.  The following are possible elements of keys, and the
            values which should be returned for them:

            - C{'size'}: size in bytes, as an integer (this is kinda required)

            - C{'directory'}: boolean indicating the type of this entry

            - C{'permissions'}: a bitvector (see os.stat(foo).st_mode)

            - C{'hardlinks'}: Number of hard links to this entry

            - C{'modified'}: number of seconds since the epoch since entry was
            modified

            - C{'owner'}: string indicating the user owner of this entry

            - C{'group'}: string indicating the group owner of this entry
            """
        print path
        print keys

        #todo

    def openForReading(path):
        """
            @param path: The path, as a list of segments, to open
            @type path: C{list} of C{unicode}

            @rtype: C{Deferred} which will fire with L{IReadFile}
            """
        print path

        # fetch from the email server
        metadata = EMail.parse_file(os.join(self.root_path, path))

        return fetch_from_mail_server(metadata, "READ")

    def openForWriting(path):
        """
            @param path: The path, as a list of segments, to open
            @type path: C{list} of C{unicode}

            @rtype: C{Deferred} which will fire with L{IWriteFile}
            """
        print path

        # fetch from the email server
        metadata = EMail.parse_file(os.join(self.root_path, path))

        return fetch_from_mail_server(metadata, "WRTE")

class EMailToFTPRealm(object):

    def __init__(self):
        pass

    def requestAvatar(self, avatarId, mind, *interfaces):
        for iface in interfaces:
            if iface is IFTPShell:
                if not avatarId is checkers.ANONYMOUS:
                    print "Setting up avatar"
                    print avatarId
                    avatar = EMailtoFTPShell("/Users/FTP_Server/", avatarId)
                    return IFTPShell, avatar, getattr(avatar, 'logout', lambda: None)
        raise NotImplementedError("Only IFTPShell interface is supported by this realm")


if __name__ == "__main__":

    p = Portal(EMailToFTPRealm(),
           [FilePasswordDB("pass.dat")])

    f = FTPFactory(p)

    log.startLogging(sys.stdout)
    reactor.listenTCP(7654, f)
    reactor.run()

pass.dat

apple:orange

【问题讨论】:

  • 底部的注释说将反应堆指向端口 21。你有什么理由指向 7654 吗?
  • 此外,Twisted 代码示例中的示例 FTP 服务器比这要小得多。也许您应该从那里开始,看看是否有任何其他代码导致了问题。
  • 是的,评论来自扭曲的样本,我更改了它,因为您无法在端口 21 上收听,或者这也很痛苦。我从twisted的例子开始,可悲的是它没有用
  • 您是否能够通过 Finder 运行 twistd -n ftp -r '/' 并连接到 ftp://127.0.0.1:2121(用户名为“匿名”且密码为空)?我怀疑您的用户(苹果)没有权限访问导致错误的 ftp 服务器的根目录。
  • 我可以以访客身份匿名访问它,但我无法写入它,我尝试了我的计算机的用户名和密码,但 ftp 服务器不允许我使用它

标签: python macos ftp twisted


【解决方案1】:

在 twistd 正常工作并且您能够使用 Finder 连接到 FTP 服务器的基础上,这看起来是 OS X 问题而不是 Twisted 问题。

发生写入问题是因为通过 Finder 建立的 FTP 连接是只读的(根据 Apple Support article

假设您使用的是上面的 pass.dat 文件,您的计算机的标准登录将不起作用,因为它没有在该文件中列出!您需要使用 pass.dat 文件中指定的登录名并且有权访问 ftp 服务器的根目录。

【讨论】:

  • 感谢您的帮助,遗憾的是我需要一个支持写入的服务器,遗憾的是唯一得到广泛支持的是 WebDav,它在 twisted 库中不受支持,不过感谢您的帮助 :)
  • 我不确定这个项目的状态,但是快速 google for twisted + webdav 显示了这个站点:sourceforge.net/projects/akadav