278 lines · 6.9 KB · Go
RawDownload
// 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 versionfunc 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 connectionsfunc (server *Server) Shutdown() error {	if server.cancel != nil {		server.cancel()	}	if server.listener != nil {		return server.listener.Close()	}	// server wasnt even started	return nil}