mirror of
https://github.com/rclone/rclone.git
synced 2024-12-11 09:41:47 +01:00
279 lines
6.9 KiB
Go
279 lines
6.9 KiB
Go
|
// Copyright 2018 The goftp Authors. All rights reserved.
|
||
|
// Use of this source code is governed by a MIT-style
|
||
|
// license that can be found in the LICENSE file.
|
||
|
|
||
|
package server
|
||
|
|
||
|
import (
|
||
|
"bufio"
|
||
|
"context"
|
||
|
"crypto/tls"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"net"
|
||
|
"strconv"
|
||
|
)
|
||
|
|
||
|
// Version returns the library version
|
||
|
func Version() string {
|
||
|
return "0.3.0"
|
||
|
}
|
||
|
|
||
|
// ServerOpts contains parameters for server.NewServer()
|
||
|
type ServerOpts struct {
|
||
|
// The factory that will be used to create a new FTPDriver instance for
|
||
|
// each client connection. This is a mandatory option.
|
||
|
Factory DriverFactory
|
||
|
|
||
|
Auth Auth
|
||
|
|
||
|
// Server Name, Default is Go Ftp Server
|
||
|
Name string
|
||
|
|
||
|
// The hostname that the FTP server should listen on. Optional, defaults to
|
||
|
// "::", which means all hostnames on ipv4 and ipv6.
|
||
|
Hostname string
|
||
|
|
||
|
// Public IP of the server
|
||
|
PublicIp string
|
||
|
|
||
|
// Passive ports
|
||
|
PassivePorts 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
|
||
|
|
||
|
// use tls, default is false
|
||
|
TLS bool
|
||
|
|
||
|
// if tls used, cert file is required
|
||
|
CertFile string
|
||
|
|
||
|
// if tls used, key file is required
|
||
|
KeyFile string
|
||
|
|
||
|
// If ture TLS is used in RFC4217 mode
|
||
|
ExplicitFTPS bool
|
||
|
|
||
|
WelcomeMessage string
|
||
|
|
||
|
// A logger implementation, if nil the StdLogger is used
|
||
|
Logger Logger
|
||
|
}
|
||
|
|
||
|
// Server is the root of your FTP application. You should instantiate one
|
||
|
// of these and call ListenAndServe() to start accepting client connections.
|
||
|
//
|
||
|
// Always use the NewServer() method to create a new Server.
|
||
|
type Server struct {
|
||
|
*ServerOpts
|
||
|
listenTo string
|
||
|
logger Logger
|
||
|
listener net.Listener
|
||
|
tlsConfig *tls.Config
|
||
|
ctx context.Context
|
||
|
cancel context.CancelFunc
|
||
|
feats string
|
||
|
}
|
||
|
|
||
|
// ErrServerClosed is returned by ListenAndServe() or Serve() when a shutdown
|
||
|
// was requested.
|
||
|
var ErrServerClosed = errors.New("ftp: Server closed")
|
||
|
|
||
|
// serverOptsWithDefaults copies an ServerOpts struct into a new struct,
|
||
|
// then adds any default values that are missing and returns the new data.
|
||
|
func serverOptsWithDefaults(opts *ServerOpts) *ServerOpts {
|
||
|
var newOpts ServerOpts
|
||
|
if opts == nil {
|
||
|
opts = &ServerOpts{}
|
||
|
}
|
||
|
if opts.Hostname == "" {
|
||
|
newOpts.Hostname = "::"
|
||
|
} else {
|
||
|
newOpts.Hostname = opts.Hostname
|
||
|
}
|
||
|
if opts.Port == 0 {
|
||
|
newOpts.Port = 3000
|
||
|
} else {
|
||
|
newOpts.Port = opts.Port
|
||
|
}
|
||
|
newOpts.Factory = opts.Factory
|
||
|
if opts.Name == "" {
|
||
|
newOpts.Name = "Go FTP Server"
|
||
|
} else {
|
||
|
newOpts.Name = opts.Name
|
||
|
}
|
||
|
|
||
|
if opts.WelcomeMessage == "" {
|
||
|
newOpts.WelcomeMessage = defaultWelcomeMessage
|
||
|
} else {
|
||
|
newOpts.WelcomeMessage = opts.WelcomeMessage
|
||
|
}
|
||
|
|
||
|
if opts.Auth != nil {
|
||
|
newOpts.Auth = opts.Auth
|
||
|
}
|
||
|
|
||
|
newOpts.Logger = &StdLogger{}
|
||
|
if opts.Logger != nil {
|
||
|
newOpts.Logger = opts.Logger
|
||
|
}
|
||
|
|
||
|
newOpts.TLS = opts.TLS
|
||
|
newOpts.KeyFile = opts.KeyFile
|
||
|
newOpts.CertFile = opts.CertFile
|
||
|
newOpts.ExplicitFTPS = opts.ExplicitFTPS
|
||
|
|
||
|
newOpts.PublicIp = opts.PublicIp
|
||
|
newOpts.PassivePorts = opts.PassivePorts
|
||
|
|
||
|
return &newOpts
|
||
|
}
|
||
|
|
||
|
// NewServer initialises a new FTP server. Configuration options are provided
|
||
|
// via an instance of ServerOpts. Calling this function in your code will
|
||
|
// probably look something like this:
|
||
|
//
|
||
|
// factory := &MyDriverFactory{}
|
||
|
// server := server.NewServer(&server.ServerOpts{ Factory: factory })
|
||
|
//
|
||
|
// or:
|
||
|
//
|
||
|
// factory := &MyDriverFactory{}
|
||
|
// opts := &server.ServerOpts{
|
||
|
// Factory: factory,
|
||
|
// Port: 2000,
|
||
|
// Hostname: "127.0.0.1",
|
||
|
// }
|
||
|
// server := server.NewServer(opts)
|
||
|
//
|
||
|
func NewServer(opts *ServerOpts) *Server {
|
||
|
opts = serverOptsWithDefaults(opts)
|
||
|
s := new(Server)
|
||
|
s.ServerOpts = opts
|
||
|
s.listenTo = net.JoinHostPort(opts.Hostname, strconv.Itoa(opts.Port))
|
||
|
s.logger = opts.Logger
|
||
|
return s
|
||
|
}
|
||
|
|
||
|
// NewConn constructs a new object that will handle the FTP protocol over
|
||
|
// an active net.TCPConn. The TCP connection should already be open before
|
||
|
// it is handed to this functions. driver is an instance of FTPDriver that
|
||
|
// will handle all auth and persistence details.
|
||
|
func (server *Server) newConn(tcpConn net.Conn, driver Driver) *Conn {
|
||
|
c := new(Conn)
|
||
|
c.namePrefix = "/"
|
||
|
c.conn = tcpConn
|
||
|
c.controlReader = bufio.NewReader(tcpConn)
|
||
|
c.controlWriter = bufio.NewWriter(tcpConn)
|
||
|
c.driver = driver
|
||
|
c.auth = server.Auth
|
||
|
c.server = server
|
||
|
c.sessionID = newSessionID()
|
||
|
c.logger = server.logger
|
||
|
c.tlsConfig = server.tlsConfig
|
||
|
|
||
|
driver.Init(c)
|
||
|
return c
|
||
|
}
|
||
|
|
||
|
func simpleTLSConfig(certFile, keyFile string) (*tls.Config, error) {
|
||
|
config := &tls.Config{}
|
||
|
if config.NextProtos == nil {
|
||
|
config.NextProtos = []string{"ftp"}
|
||
|
}
|
||
|
|
||
|
var err error
|
||
|
config.Certificates = make([]tls.Certificate, 1)
|
||
|
config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return config, nil
|
||
|
}
|
||
|
|
||
|
// ListenAndServe asks a new Server to begin accepting client connections. It
|
||
|
// accepts no arguments - all configuration is provided via the NewServer
|
||
|
// 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.
|
||
|
//
|
||
|
func (server *Server) ListenAndServe() error {
|
||
|
var listener net.Listener
|
||
|
var err error
|
||
|
var curFeats = featCmds
|
||
|
|
||
|
if server.ServerOpts.TLS {
|
||
|
server.tlsConfig, err = simpleTLSConfig(server.CertFile, server.KeyFile)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
curFeats += " AUTH TLS\n PBSZ\n PROT\n"
|
||
|
|
||
|
if server.ServerOpts.ExplicitFTPS {
|
||
|
listener, err = net.Listen("tcp", server.listenTo)
|
||
|
} else {
|
||
|
listener, err = tls.Listen("tcp", server.listenTo, server.tlsConfig)
|
||
|
}
|
||
|
} else {
|
||
|
listener, err = net.Listen("tcp", server.listenTo)
|
||
|
}
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
server.feats = fmt.Sprintf(feats, curFeats)
|
||
|
|
||
|
sessionID := ""
|
||
|
server.logger.Printf(sessionID, "%s listening on %d", server.Name, server.Port)
|
||
|
|
||
|
return server.Serve(listener)
|
||
|
}
|
||
|
|
||
|
// Serve accepts connections on a given net.Listener and handles each
|
||
|
// request in a new goroutine.
|
||
|
//
|
||
|
func (server *Server) Serve(l net.Listener) error {
|
||
|
server.listener = l
|
||
|
server.ctx, server.cancel = context.WithCancel(context.Background())
|
||
|
sessionID := ""
|
||
|
for {
|
||
|
tcpConn, err := server.listener.Accept()
|
||
|
if err != nil {
|
||
|
select {
|
||
|
case <-server.ctx.Done():
|
||
|
return ErrServerClosed
|
||
|
default:
|
||
|
}
|
||
|
server.logger.Printf(sessionID, "listening error: %v", err)
|
||
|
if ne, ok := err.(net.Error); ok && ne.Temporary() {
|
||
|
continue
|
||
|
}
|
||
|
return err
|
||
|
}
|
||
|
driver, err := server.Factory.NewDriver()
|
||
|
if err != nil {
|
||
|
server.logger.Printf(sessionID, "Error creating driver, aborting client connection: %v", err)
|
||
|
tcpConn.Close()
|
||
|
} else {
|
||
|
ftpConn := server.newConn(tcpConn, driver)
|
||
|
go ftpConn.Serve()
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Shutdown will gracefully stop a server. Already connected clients will retain their connections
|
||
|
func (server *Server) Shutdown() error {
|
||
|
if server.cancel != nil {
|
||
|
server.cancel()
|
||
|
}
|
||
|
if server.listener != nil {
|
||
|
return server.listener.Close()
|
||
|
}
|
||
|
// server wasnt even started
|
||
|
return nil
|
||
|
}
|