graval

package module
v0.0.0-...-8ed802b Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 6, 2019 License: MIT Imports: 19 Imported by: 0

README

graval

An experimental FTP server framework. By providing a simple driver class that responds to a handful of methods you can have a complete FTP server.

Some sample use cases include persisting data to:

  • an Amazon S3 bucket
  • a relational database
  • redis
  • memory

There is a sample in-memory driver available - see the usage instructions below for the steps to use it.

Full documentation for the package is available on godoc

Installation

go get github.com/yob/graval

Usage

To boot an FTP server you will need to provide a driver that speaks to your persistence layer - the required driver contract is listed below.

There is a sample in-memory driver available as a demo. You can build it with this command:

go install github.com/yob/graval/graval-mem

Then run it:

./bin/graval-mem

And finally, connect to the server with any FTP client and the following details:

host: 127.0.0.1
username: test
password: 1234
The Driver Contract

Your driver MUST implement a number of simple methods. You can view the required contract in the package docs on godoc

Contributors

Warning

FTP is an incredibly insecure protocol. Be careful about forcing users to authenticate with a username or password that are important.

License

This library is distributed under the terms of the MIT License. See the included file for more detail.

Contributing

All suggestions and patches welcome, preferably via a git repository I can pull from. If this library proves useful to you, please let me know.

Further Reading

There are a range of RFCs that together specify the FTP protocol. In chronological order, the more useful ones are:

For an english summary that's somewhat more legible than the RFCs, and provides some commentary on what features are actually useful or relevant 24 years after RFC959 was published:

For a history lesson, check out Appendix III of RCF959. It lists the preceding (obsolete) RFC documents that relate to file transfers, including the ye old RFC114 from 1971, "A File Transfer Protocol"

This library is heavily based on em-ftpd, an FTPd framework with similar design goals within the ruby and EventMachine ecosystems. It worked well enough, but you know, callbacks and event loops make me something something.

Documentation

Overview

An example FTP server build on top of go-raval. graval handles the details of the FTP protocol, we just provide a basic in-memory persistence driver.

If you're looking to create a custom graval driver, this example is a reasonable starting point. I suggest copying this file and changing the function bodies as required.

USAGE:

go get github.com/yob/graval
go install github.com/yob/graval/graval-mem
./bin/graval-mem

An experimental FTP server framework. By providing a simple driver class that responds to a handful of methods you can have a complete FTP server.

Some sample use cases include persisting data to an Amazon S3 bucket, a relational database, redis or memory.

There is a sample in-memory driver available - see the documentation for the graval-mem package or the graval READEME for more details.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func NewDirItem

func NewDirItem(name string, modtime time.Time) os.FileInfo

NewDirItem creates a new os.FileInfo that represents a single diretory. Use this function to build the response to DirContents() in your FTPDriver implementation.

func NewFileItem

func NewFileItem(name string, bytes int64, modtime time.Time) os.FileInfo

NewFileItem creates a new os.FileInfo that represents a single file. Use this function to build the response to DirContents() in your FTPDriver implementation.

Types

type FTPDriver

type FTPDriver interface {
	// params  - username, password
	// returns - true if the provided details are valid
	Authenticate(string, string) bool

	// params  - a file path
	// returns - an int with the number of bytes in the file or -1 if the file
	//           doesn't exist
	Bytes(string) int64

	// params  - a file path
	// returns - a time indicating when the requested path was last modified
	//         - an error if the file doesn't exist or the user lacks
	//           permissions
	ModifiedTime(string) (time.Time, error)

	// params  - path
	// returns - true if the current user is permitted to change to the
	//           requested path
	ChangeDir(string) bool

	// params  - path
	// returns - a collection of items describing the contents of the requested
	//           path
	DirContents(string) []os.FileInfo

	// params  - path
	// returns - true if the directory was deleted
	DeleteDir(string) bool

	// params  - path
	// returns - true if the file was deleted
	DeleteFile(string) bool

	// params  - from_path, to_path
	// returns - true if the file was renamed
	Rename(string, string) bool

	// params  - path
	// returns - true if the new directory was created
	MakeDir(string) bool

	// params  - path
	// returns - a Reader that will return file data to send to the client
	GetFile(string) (io.ReadCloser, error)

	// params  - desination path, an io.Reader containing the file data
	// returns - true if the data was successfully persisted
	PutFile(string, io.Reader) bool
}

You will create an implementation of this interface that speaks to your chosen persistence layer. graval will create a new instance of your driver for each client that connects and delegate to it as required.

type FTPDriverFactory

type FTPDriverFactory interface {
	NewDriver() (FTPDriver, error)
}

For each client that connects to the server, a new FTPDriver is required. Create an implementation if this interface and provide it to FTPServer.

type FTPServer

type FTPServer struct {
	// contains filtered or unexported fields
}

FTPServer is the root of your FTP application. You should instantiate one of these and call ListenAndServe() to start accepting client connections.

Always use the NewFTPServer() method to create a new FTPServer.

func NewFTPServer

func NewFTPServer(opts *FTPServerOpts) *FTPServer

NewFTPServer initialises a new FTP server. Configuration options are provided via an instance of FTPServerOpts. Calling this function in your code will probably look something like this:

factory := &MyDriverFactory{}
server  := graval.NewFTPServer(&graval.FTPServerOpts{ Factory: factory })

or:

factory := &MyDriverFactory{}
opts    := &graval.FTPServerOpts{
  Factory: factory,
  Port: 2000,
  Hostname: "127.0.0.1",
}
server  := graval.NewFTPServer(opts)

func (*FTPServer) ListenAndServe

func (ftpServer *FTPServer) ListenAndServe() error

ListenAndServe asks a new FTPServer to begin accepting client connections. It accepts no arguments - all configuration is provided via the NewFTPServer function.

If the server fails to start for any reason, an error will be returned. Common errors are trying to bind to a privileged port or something else is already listening on the same port.

type FTPServerOpts

type FTPServerOpts struct {
	// Server name will be used for welcome message
	ServerName string

	// The factory that will be used to create a new FTPDriver instance for
	// each client connection. This is a mandatory option.
	Factory FTPDriverFactory

	// The hostname that the FTP server should listen on. Optional, defaults to
	// "::", which means all hostnames on ipv4 and ipv6.
	Hostname string

	// The port that the FTP should listen on. Optional, defaults to 3000. In
	// a production environment you will probably want to change this to 21.
	Port int

	// The lower bound of port numbers that can be used for passive-mode data sockets
	// Defaults to 0, which allows the server to pick any free port
	PasvMinPort int

	// The upper bound of port numbers that can be used for passive-mode data sockets
	// Defaults to 0, which allows the server to pick any free port
	PasvMaxPort int

	// Use this option to override the IP address that will be advertised in response to the
	// PASV command. Most setups can ignore this, but it can be helpful in situations where
	// the FTP server is behind a NAT gateway or load balancer and the public IP used by
	// clients is different to the IP the server is directly listening on
	PasvAdvertisedIp string
}

serverOpts contains parameters for graval.NewFTPServer()

type MemDriver

type MemDriver struct{}

A minimal driver for graval that stores everything in memory. The authentication details are fixed and the user is unable to upload, delete or rename any files.

This really just exists as a minimal demonstration of the interface graval drivers are required to implement.

func (*MemDriver) Authenticate

func (driver *MemDriver) Authenticate(user string, pass string) bool

func (*MemDriver) Bytes

func (driver *MemDriver) Bytes(path string) (bytes int64)

func (*MemDriver) ChangeDir

func (driver *MemDriver) ChangeDir(path string) bool

func (*MemDriver) DeleteDir

func (driver *MemDriver) DeleteDir(path string) bool

func (*MemDriver) DeleteFile

func (driver *MemDriver) DeleteFile(path string) bool

func (*MemDriver) DirContents

func (driver *MemDriver) DirContents(path string) (files []os.FileInfo)

func (*MemDriver) GetFile

func (driver *MemDriver) GetFile(path string) (reader io.ReadCloser, err error)

func (*MemDriver) MakeDir

func (driver *MemDriver) MakeDir(path string) bool

func (*MemDriver) ModifiedTime

func (driver *MemDriver) ModifiedTime(path string) (time.Time, error)

func (*MemDriver) PutFile

func (driver *MemDriver) PutFile(destPath string, data io.Reader) bool

func (*MemDriver) Rename

func (driver *MemDriver) Rename(fromPath string, toPath string) bool

type MemDriverFactory

type MemDriverFactory struct{}

graval requires a factory that will create a new driver instance for each client connection. Generally the factory will be fairly minimal. This is a good place to read any required config for your driver.

func (*MemDriverFactory) NewDriver

func (factory *MemDriverFactory) NewDriver() (FTPDriver, error)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL