Metadata-Version: 1.1
Name: pysftpserver
Version: 1.4.0
Summary: An OpenSSH SFTP wrapper in Python.
Home-page: https://github.com/unbit/pysftpserver
Author: Unbit
Author-email: info@unbit.com
License: MIT
Description: #pysftpserver
        An OpenSSH SFTP wrapper written in Python.
        
        ##Features
        * Possibility to [automatically jail users](#authorized_keys_magic) in a virtual chroot environment as soon as they login.
        * Possibility to [automatically forward SFTP requests to another server](#usage).
        * Compatible with both Python 2 and Python 3.
        * Fully extensible and customizable (examples below).
        * Totally conforms to the [SFTP RFC](https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt).
        
        ##Installation
        Simply install pysftpserver with pip:
        ```bash
        $ pip install pysftpserver # add the --user flag to install it just for you
        ```
        
        **Note**: if you'd like to use the [automatic forwarding storage](#usage) you have to explicitly specify the paramiko dependency:
        ```bash
        $ pip install pysftpserver[pysftpproxy]
        ```
        
        Otherwise, you could always clone this repository and manually launch `setup.py`:
        ```bash
        $ git clone https://github.com/unbit/pysftpserver.git
        $ cd pysftpserver
        $ python setup.py install
        ```
        
        ##Usage
        We provide a couple of fully working examples:
        
        * **pysftpjail**: an SFTP storage that jails users in a virtual chroot environment.
        * **pysftpproxy**: an SFTP storage that acts as a proxy, forwarding each request to another SFTP server.
        
        You'll find both our storages in your `$PATH` after the installation, so you can simply launch them by using the appropriate command line executable / arguments:
        
        ```
        $ pysftpjail -h
        
        usage: pysftpjail [-h] [--logfile LOGFILE] [--umask UMASK] chroot
        
        An OpenSSH SFTP server wrapper that jails the user in a chroot directory.
        
        positional arguments:
          chroot                the path of the chroot jail
        
        optional arguments:
          -h, --help            show this help message and exit
          --logfile LOGFILE, -l LOGFILE
                                path to the logfile
          --umask UMASK, -u UMASK
                                set the umask of the SFTP server
        ```
        
        ```
        $ pysftpproxy -h
        
        usage: pysftpproxy [-h] [-l LOGFILE] [-k private-key-path] [-p PORT] [-a]
                           [-c ssh config path] [-n known_hosts path] [-d]
                           user[:password]@hostname
        
        An OpenSSH SFTP server proxy that forwards each request to a remote server.
        
        positional arguments:
          user[:password]@hostname
                                the ssh-url ([user[:password]@]hostname) of the remote
                                server. The hostname can be specified as a
                                ssh_config's hostname too. Every missing information
                                will be gathered from there
        
        optional arguments:
          -h, --help            show this help message and exit
          -l LOGFILE, --logfile LOGFILE
                                path to the logfile
          -k private-key-path, --key private-key-path
                                private key identity path (defaults to ~/.ssh/id_rsa)
          -p PORT, --port PORT  SSH remote port (defaults to 22)
          -a, --ssh-agent       enable ssh-agent support
          -c ssh config path, --ssh-config ssh config path
                                path to the ssh-configuration file (default to
                                ~/.ssh/config)
          -n known_hosts path, --known-hosts known_hosts path
                                path to the openSSH known_hosts file
          -d, --disable-known-hosts
                                disable known_hosts fingerprint checking (security
                                warning!)
        ```
        
        ###authorized_keys magic
        With `pysftpjail` you can jail any user in the virtual chroot as soon as she connects to the SFTP server.
        You can do it by simply prepending the `pysftpjail` command to the user entry in your SSH `authorized_keys` file, e.g.:
        ```
        command="pysftpjail path_to_your_jail" ssh-rsa AAAAB3[... and so on]
        ```
        
        Probably, you'll want to add the following options too:
        ```
        no-port-forwarding,no-x11-forwarding,no-agent-forwarding
        ```
        
        Achieving as final result:
        ```
        command="pysftpjail path_to_your_jail",no-port-forwarding,no-x11-forwarding,no-agent-forwarding ssh-rsa AAAAB3[... and so on]
        ```
        
        Obviusly, you could do the same with `pysftpproxy`.
        
        ##Customization
        We provide two complete examples of SFTP storage: simple and jailed.
        Anyway, you can subclass our [generic abstract storage](pysftpserver/abstractstorage.py) and you can adapt it to your needs.
        Any contribution is welcomed, as always. :+1:
        
        ###Real world customization: MongoDB / GridFS storage
        [MongoDB](http://www.mongodb.org/) is an open, NOSQL, document database.
        [GridFS](http://docs.mongodb.org/manual/core/gridfs/) is a specification for storing and retrieving arbitrary files in a MongoDB database.
        The following example will show how to build a storage that handles files in a MongoDB / GridFS database.
        
        ####Preliminary requirements
        I assume you already have a MongoDB database running somewhere and you are using a [`virtualenv`](https://virtualenv.readthedocs.org/en/latest/virtualenv.html).
        Let's install the MongoDB Python driver, `pymongo`, with:
        ```bash
        $ pip install pymongo
        ```
        
        Now clone this project's repository and install the base package in development mode.
        ```bash
        $ git clone https://github.com/unbit/pysftpserver.git
        $ cd pysftpserver
        $ python setup.py develop
        ```
        *Info for those who are asking:* development mode will let us modify the source of the packages and use it globally without needing to reinstall it.
        
        Now you're ready to create the storage.
        
        ####New storage class
        Let's create a new storage (save it as `pysftpserver/mongostorage.py`) that subclasses the [abstract storage](pysftpserver/abstractstorage.py) class.
        
        ```python
        """MongoDB GridFS SFTP storage."""
        
        from pysftpserver.abstractstorage import SFTPAbstractServerStorage
        from pysftpserver.pysftpexceptions import SFTPNotFound
        import pymongo
        import gridfs
        
        
        class SFTPServerMongoStorage(SFTPAbstractServerStorage):
            """MongoDB GridFS SFTP storage class."""
        
            def __init__(self, home, remote, port, db_name):
                """Home sweet home.
        
                NOTE: you should set your home to something reasonable.
                Instruct the client to connect to your MongoDB.
                """
                self.home = "/"
                client = pymongo.MongoClient(remote, port)
                db = client[db_name]
                self.gridfs = gridfs.GridFS(db)
        
            def open(self, filename, flags, mode):
                """Return the file handle."""
                filename = filename.decode()  # needed in Python 3
                if self.gridfs.exists(filename=filename):
                    return self.gridfs.find({'filename': filename})[0]
        
                raise SFTPNotFound
        
            def read(self, handle, off, size):
                """Read size from the handle. Offset is ignored."""
                return handle.read(size)
        
            def close(self, handle):
                """Close the file handle."""
                handle.close()
        
            """
            Warning: 
                this implementation is incomplete, many required methods are missing.
            """
        ```
        
        As you can see, it's all pretty straight-forward.
        
        In the `init` method, we initialize the MongoDB client, select the database to use and then we initialize GridFS.
        Then, in the `open` method, we check if the file exists and return it's handler; in the `read` and `close` methods we simply forward the calls to the GridFS.
        
        ####Testing the new storage
        I strongly encourage you to test your newly created storage. 
        Here's an example (save it as `pysftpserver/tests/test_server_mongo.py`):
        
        ```python
        import unittest
        import os
        from shutil import rmtree
        
        import pymongo
        import gridfs
        
        from pysftpserver.server import *
        from pysftpserver.mongostorage import SFTPServerMongoStorage
        from pysftpserver.tests.utils import *
        
        """To run this tests you must have an instance of MongoDB running somewhere."""
        REMOTE = "localhost"
        PORT = 1727
        DB_NAME = "mydb"
        
        
        class Test(unittest.TestCase):
        
            @classmethod
            def setUpClass(cls):
                client = pymongo.MongoClient(REMOTE, PORT)
                db = client[DB_NAME]
                cls.gridfs = gridfs.GridFS(db)
        
            def setUp(self):
                os.chdir(t_path())
                self.home = 'home'
        
                if not os.path.isdir(self.home):
                    os.mkdir(self.home)
        
                self.server = SFTPServer(
                    SFTPServerMongoStorage(REMOTE, PORT, DB_NAME),
                    logfile=t_path('log'),
                    raise_on_error=True
                )
        
            def tearDown(self):
                os.chdir(t_path())
                rmtree(self.home)
        
            def test_read(self):
                s = b"This is a test file."
                f_name = "test"  # put expects a non byte string!
                b_f_name = b"test"
        
                f = self.gridfs.put(s, filename=f_name)
                self.server.input_queue = sftpcmd(
                    SSH2_FXP_OPEN,
                    sftpstring(b_f_name),
                    sftpint(SSH2_FXF_CREAT),
                    sftpint(0)
                )
                self.server.process()
                handle = get_sftphandle(self.server.output_queue)
        
                self.server.output_queue = b''  # reset the output queue
                self.server.input_queue = sftpcmd(
                    SSH2_FXP_READ,
                    sftpstring(handle),
                    sftpint64(0),
                    sftpint(len(s)),
                )
                self.server.process()
                data = get_sftpdata(self.server.output_queue)
        
                self.assertEqual(s, data)
        
                self.server.output_queue = b''  # reset output queue
                self.server.input_queue = sftpcmd(
                    SSH2_FXP_CLOSE,
                    sftpstring(handle)
                )
                self.server.process()
        
                # Cleanup!
                self.gridfs.delete(f)
        
            @classmethod
            def tearDownClass(cls):
                os.unlink(t_path("log"))  # comment me to see the log!
                rmtree(t_path("home"), ignore_errors=True)
        ```
        
        ####Final results
        Finally, you can create a binary to comfortably launch the server using the created storage.
        Save it as `bin/pysftpmongo`.
        
        ```python
        #!/usr/bin/env python
        """pysftpmongo executable."""
        
        import argparse
        from pysftpserver.server import SFTPServer
        from pysftpserver.mongostorage import SFTPServerMongoStorage
        
        
        def main():
            parser = argparse.ArgumentParser(
                description='An OpenSSH SFTP server wrapper that uses a MongoDB/GridFS storage.'
            )
        
            parser.add_argument('remote', type=str,
                                help='the remote address of the MongoDB instance')
            parser.add_argument('port', type=int,
                                help='the remote port of the MongoDB instance')
            parser.add_argument('db_name', type=str,
                                help='the name of the DB to use')
            parser.add_argument('--logfile', '-l', dest='logfile',
                                help='path to the logfile')
        
            args = parser.parse_args()
            SFTPServer(
                storage=SFTPServerMongoStorage(
                    args.remote,
                    args.port,
                    args.db_name
                ),
                logfile=args.logfile
            ).run()
        
        
        if __name__ == '__main__':
            main()
        ```
        
        Now, `chmod` the binary and check that it starts without a hitch:
        ```bash
        $ chmod +x bin/pysftpmongo
        $ bin/pysftpmongo "localhost" 1727 "mydb"
        ```
        
        Finally, you should edit the `setup.py` `scripts` field to include your new binary. 
        Now, running `python setup.py install` will put it somewhere in your `$PATH`, for later ease: e.g. when [using it in the authorized_keys file](#authorized_keys_magic).
        
        A sneak peek of the final result (in the `authorized_keys` file):
        ```
        command="pysftpmongo REMOTE_TO_YOUR_DB REMOTE_PORT DB_NAME",no-port-forwarding,no-x11-forwarding,no-agent-forwarding ssh-rsa AAAAB3[... and so on]
        ```
        
        That's it!
        
        ####Code used in this example
        All the code used in this example can be found in the [`examples/mongodb_gridfs`](examples/mongodb_gridfs/) directory of this repository.
        
        ##FileZilla compatibility
        FileZilla requires the `longname` returned with each `SSH2_FXP_NAME` response (e.g. each time `readdir` is called) to be a string of the same format of the output of `ls -l` (`-rw-r--r--  1 aldur staff 9596 Dec 29 18:36 README.md`).
        
        So, if you want to keep compatibility with FileZilla, be sure to include a proper `longname` field to the stats dictionary you return from your storage, as we do [here](pysftpserver/storage.py#L78).
        
        ##Tests
        You can use [nose](https://nose.readthedocs.org/en/latest/) for tests.
        From the project directory, simply run:
        ```bash
        $ nosetests
        $ python setup.py test # alternatively
        ```
        
Keywords: pysftpserver,sftp,openssh,ssh,jail
Platform: UNKNOWN
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Development Status :: 6 - Mature
Classifier: Environment :: Other Environment
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: End Users/Desktop
Classifier: Intended Audience :: Information Technology
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Topic :: System :: Shells
Classifier: Topic :: System :: System Shells
Classifier: Topic :: Internet :: File Transfer Protocol (FTP)
Classifier: Topic :: Utilities
