vendor: change goftp/server url

Closing #3674
This commit is contained in:
Xiaoxing Ye 2019-10-30 01:41:56 +08:00 committed by Nick Craig-Wood
parent e0356f5aae
commit 6d58d9a86f
24 changed files with 51 additions and 36 deletions

View file

@ -1,10 +0,0 @@
kind: pipeline
name: default
steps:
- name: test
image: golang:1.12
commands:
- go get -t -d -v ./...
- go build -v
- go test -v -race -coverprofile=coverage.txt -covermode=atomic

View file

@ -1,2 +0,0 @@
testdata
coverage.txt

View file

@ -1,20 +0,0 @@
Copyright (c) 2018 Goftp Authors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -1,92 +0,0 @@
# server
[![CircleCI](https://circleci.com/gh/goftp/server.svg?style=shield)](https://circleci.com/gh/goftp/server)
[![](https://goreportcard.com/badge/github.com/goftp/server)](https://goreportcard.com/report/github.com/goftp/server)
[![codecov](https://codecov.io/gh/goftp/server/branch/master/graph/badge.svg)](https://codecov.io/gh/goftp/server)
A FTP server framework forked from [github.com/yob/graval](http://github.com/yob/graval) and changed a lot.
Full documentation for the package is available on [godoc](http://godoc.org/github.com/goftp/server)
## Version
v0.2.3
## Installation
go get github.com/goftp/server
## Usage
To boot a FTP server you will need to provide a driver that speaks to
your persistence layer - the required driver contract is in [the
documentation](http://godoc.org/github.com/goftp/server).
Look at the [file driver](https://github.com/goftp/file-driver) to see
an example of how to build a backend.
There is a [sample ftp server](/exampleftpd) as a demo. You can build it with this
command:
go install github.com/goftp/server/exampleftpd
Then run it if you have add $GOPATH to your $PATH:
exampleftpd -root /tmp
And finally, connect to the server with any FTP client and the following
details:
host: 127.0.0.1
port: 2121
username: admin
password: 123456
This uses the file driver mentioned above to serve files.
## Contributors
see [https://github.com/goftp/server/graphs/contributors](https://github.com/goftp/server/graphs/contributors)
## Warning
FTP is an incredibly insecure protocol. Be careful about forcing users to authenticate
with an 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:
* [http://tools.ietf.org/rfc/rfc959.txt](http://tools.ietf.org/rfc/rfc959.txt)
* [http://tools.ietf.org/rfc/rfc1123.txt](http://tools.ietf.org/rfc/rfc1123.txt)
* [http://tools.ietf.org/rfc/rfc2228.txt](http://tools.ietf.org/rfc/rfc2228.txt)
* [http://tools.ietf.org/rfc/rfc2389.txt](http://tools.ietf.org/rfc/rfc2389.txt)
* [http://tools.ietf.org/rfc/rfc2428.txt](http://tools.ietf.org/rfc/rfc2428.txt)
* [http://tools.ietf.org/rfc/rfc3659.txt](http://tools.ietf.org/rfc/rfc3659.txt)
* [http://tools.ietf.org/rfc/rfc4217.txt](http://tools.ietf.org/rfc/rfc4217.txt)
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:
* [http://cr.yp.to/ftp.html](http://cr.yp.to/ftp.html)
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](https://github.com/yob/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.

View file

@ -1,33 +0,0 @@
// 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 (
"crypto/subtle"
)
// Auth is an interface to auth your ftp user login.
type Auth interface {
CheckPasswd(string, string) (bool, error)
}
var (
_ Auth = &SimpleAuth{}
)
// SimpleAuth implements Auth interface to provide a memory user login auth
type SimpleAuth struct {
Name string
Password string
}
// CheckPasswd will check user's password
func (a *SimpleAuth) CheckPasswd(name, pass string) (bool, error) {
return constantTimeEquals(name, a.Name) && constantTimeEquals(pass, a.Password), nil
}
func constantTimeEquals(a, b string) bool {
return len(a) == len(b) && subtle.ConstantTimeCompare([]byte(a), []byte(b)) == 1
}

View file

@ -1,14 +0,0 @@
dependencies:
override:
- mkdir -p ~/.go_workspace/src/github.com/goftp
- ln -s ${HOME}/${CIRCLE_PROJECT_REPONAME} ${HOME}/.go_workspace/src/github.com/goftp/${CIRCLE_PROJECT_REPONAME}
# './...' is a relative pattern which means all subdirectories
- go get -t -d -v ./...
- go build -v
test:
override:
# './...' is a relative pattern which means all subdirectories
- go test -v -race -coverprofile=coverage.txt -covermode=atomic
post:
- bash <(curl -s https://codecov.io/bash)

1206
vendor/github.com/goftp/server/cmd.go generated vendored

File diff suppressed because it is too large Load diff

View file

@ -1,260 +0,0 @@
// 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"
"crypto/rand"
"crypto/sha256"
"crypto/tls"
"encoding/hex"
"fmt"
"io"
"log"
mrand "math/rand"
"net"
"path/filepath"
"strconv"
"strings"
)
const (
defaultWelcomeMessage = "Welcome to the Go FTP Server"
)
type Conn struct {
conn net.Conn
controlReader *bufio.Reader
controlWriter *bufio.Writer
dataConn DataSocket
driver Driver
auth Auth
logger Logger
server *Server
tlsConfig *tls.Config
sessionID string
namePrefix string
reqUser string
user string
renameFrom string
lastFilePos int64
appendData bool
closed bool
tls bool
}
func (conn *Conn) LoginUser() string {
return conn.user
}
func (conn *Conn) IsLogin() bool {
return len(conn.user) > 0
}
func (conn *Conn) PublicIp() string {
return conn.server.PublicIp
}
func (conn *Conn) passiveListenIP() string {
var listenIP string
if len(conn.PublicIp()) > 0 {
listenIP = conn.PublicIp()
} else {
listenIP = conn.conn.LocalAddr().(*net.TCPAddr).IP.String()
}
lastIdx := strings.LastIndex(listenIP, ":")
if lastIdx <= 0 {
return listenIP
}
return listenIP[:lastIdx]
}
func (conn *Conn) PassivePort() int {
if len(conn.server.PassivePorts) > 0 {
portRange := strings.Split(conn.server.PassivePorts, "-")
if len(portRange) != 2 {
log.Println("empty port")
return 0
}
minPort, _ := strconv.Atoi(strings.TrimSpace(portRange[0]))
maxPort, _ := strconv.Atoi(strings.TrimSpace(portRange[1]))
return minPort + mrand.Intn(maxPort-minPort)
}
// let system automatically chose one port
return 0
}
// returns a random 20 char string that can be used as a unique session ID
func newSessionID() string {
hash := sha256.New()
_, err := io.CopyN(hash, rand.Reader, 50)
if err != nil {
return "????????????????????"
}
md := hash.Sum(nil)
mdStr := hex.EncodeToString(md)
return mdStr[0:20]
}
// Serve starts an endless loop that reads FTP commands from the client and
// responds appropriately. terminated is a channel that will receive a true
// message when the connection closes. This loop will be running inside a
// goroutine, so use this channel to be notified when the connection can be
// cleaned up.
func (conn *Conn) Serve() {
conn.logger.Print(conn.sessionID, "Connection Established")
// send welcome
conn.writeMessage(220, conn.server.WelcomeMessage)
// read commands
for {
line, err := conn.controlReader.ReadString('\n')
if err != nil {
if err != io.EOF {
conn.logger.Print(conn.sessionID, fmt.Sprint("read error:", err))
}
break
}
conn.receiveLine(line)
// QUIT command closes connection, break to avoid error on reading from
// closed socket
if conn.closed == true {
break
}
}
conn.Close()
conn.logger.Print(conn.sessionID, "Connection Terminated")
}
// Close will manually close this connection, even if the client isn't ready.
func (conn *Conn) Close() {
conn.conn.Close()
conn.closed = true
if conn.dataConn != nil {
conn.dataConn.Close()
conn.dataConn = nil
}
}
func (conn *Conn) upgradeToTLS() error {
conn.logger.Print(conn.sessionID, "Upgrading connectiion to TLS")
tlsConn := tls.Server(conn.conn, conn.tlsConfig)
err := tlsConn.Handshake()
if err == nil {
conn.conn = tlsConn
conn.controlReader = bufio.NewReader(tlsConn)
conn.controlWriter = bufio.NewWriter(tlsConn)
conn.tls = true
}
return err
}
// receiveLine accepts a single line FTP command and co-ordinates an
// appropriate response.
func (conn *Conn) receiveLine(line string) {
command, param := conn.parseLine(line)
conn.logger.PrintCommand(conn.sessionID, command, param)
cmdObj := commands[strings.ToUpper(command)]
if cmdObj == nil {
conn.writeMessage(500, "Command not found")
return
}
if cmdObj.RequireParam() && param == "" {
conn.writeMessage(553, "action aborted, required param missing")
} else if cmdObj.RequireAuth() && conn.user == "" {
conn.writeMessage(530, "not logged in")
} else {
cmdObj.Execute(conn, param)
}
}
func (conn *Conn) parseLine(line string) (string, string) {
params := strings.SplitN(strings.Trim(line, "\r\n"), " ", 2)
if len(params) == 1 {
return params[0], ""
}
return params[0], strings.TrimSpace(params[1])
}
// writeMessage will send a standard FTP response back to the client.
func (conn *Conn) writeMessage(code int, message string) (wrote int, err error) {
conn.logger.PrintResponse(conn.sessionID, code, message)
line := fmt.Sprintf("%d %s\r\n", code, message)
wrote, err = conn.controlWriter.WriteString(line)
conn.controlWriter.Flush()
return
}
// writeMessage will send a standard FTP response back to the client.
func (conn *Conn) writeMessageMultiline(code int, message string) (wrote int, err error) {
conn.logger.PrintResponse(conn.sessionID, code, message)
line := fmt.Sprintf("%d-%s\r\n%d END\r\n", code, message, code)
wrote, err = conn.controlWriter.WriteString(line)
conn.controlWriter.Flush()
return
}
// buildPath takes a client supplied path or filename and generates a safe
// absolute path within their account sandbox.
//
// buildpath("/")
// => "/"
// buildpath("one.txt")
// => "/one.txt"
// buildpath("/files/two.txt")
// => "/files/two.txt"
// buildpath("files/two.txt")
// => "/files/two.txt"
// buildpath("/../../../../etc/passwd")
// => "/etc/passwd"
//
// The driver implementation is responsible for deciding how to treat this path.
// Obviously they MUST NOT just read the path off disk. The probably want to
// prefix the path with something to scope the users access to a sandbox.
func (conn *Conn) buildPath(filename string) (fullPath string) {
if len(filename) > 0 && filename[0:1] == "/" {
fullPath = filepath.Clean(filename)
} else if len(filename) > 0 && filename != "-a" {
fullPath = filepath.Clean(conn.namePrefix + "/" + filename)
} else {
fullPath = filepath.Clean(conn.namePrefix)
}
fullPath = strings.Replace(fullPath, "//", "/", -1)
fullPath = strings.Replace(fullPath, string(filepath.Separator), "/", -1)
return
}
// sendOutofbandData will send a string to the client via the currently open
// data socket. Assumes the socket is open and ready to be used.
func (conn *Conn) sendOutofbandData(data []byte) {
bytes := len(data)
if conn.dataConn != nil {
conn.dataConn.Write(data)
conn.dataConn.Close()
conn.dataConn = nil
}
message := "Closing data connection, sent " + strconv.Itoa(bytes) + " bytes"
conn.writeMessage(226, message)
}
func (conn *Conn) sendOutofBandDataWriter(data io.ReadCloser) error {
conn.lastFilePos = 0
bytes, err := io.Copy(conn.dataConn, data)
if err != nil {
conn.dataConn.Close()
conn.dataConn = nil
return err
}
message := "Closing data connection, sent " + strconv.Itoa(int(bytes)) + " bytes"
conn.writeMessage(226, message)
conn.dataConn.Close()
conn.dataConn = nil
return nil
}

View file

@ -1,14 +0,0 @@
// 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.
/*
http://tools.ietf.org/html/rfc959
http://www.faqs.org/rfcs/rfc2389.html
http://www.faqs.org/rfcs/rfc959.html
http://tools.ietf.org/html/rfc2428
*/
package server

View file

@ -1,61 +0,0 @@
// 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 "io"
// DriverFactory is a driver factory to create driver. 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 DriverFactory interface {
NewDriver() (Driver, error)
}
// Driver is an interface that you will create an implementation 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 Driver interface {
// Init init
Init(*Conn)
// 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
Stat(string) (FileInfo, error)
// params - path
// returns - true if the current user is permitted to change to the
// requested path
ChangeDir(string) error
// params - path, function on file or subdir found
// returns - error
// path
ListDir(string, func(FileInfo) error) error
// params - path
// returns - nil if the directory was deleted or any error encountered
DeleteDir(string) error
// params - path
// returns - nil if the file was deleted or any error encountered
DeleteFile(string) error
// params - from_path, to_path
// returns - nil if the file was renamed or any error encountered
Rename(string, string) error
// params - path
// returns - nil if the new directory was created or any error encountered
MakeDir(string) error
// params - path
// returns - a string containing the file data to send to the client
GetFile(string, int64) (int64, io.ReadCloser, error)
// params - destination path, an io.Reader containing the file data
// returns - the number of bytes writen and the first error encountered while writing, if any.
PutFile(string, io.Reader, bool) (int64, error)
}

View file

@ -1,14 +0,0 @@
// 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 "os"
type FileInfo interface {
os.FileInfo
Owner() string
Group() string
}

View file

@ -1,49 +0,0 @@
// 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 (
"bytes"
"fmt"
"strconv"
"strings"
)
type listFormatter []FileInfo
// Short returns a string that lists the collection of files by name only,
// one per line
func (formatter listFormatter) Short() []byte {
var buf bytes.Buffer
for _, file := range formatter {
fmt.Fprintf(&buf, "%s\r\n", file.Name())
}
return buf.Bytes()
}
// Detailed returns a string that lists the collection of files with extra
// detail, one per line
func (formatter listFormatter) Detailed() []byte {
var buf bytes.Buffer
for _, file := range formatter {
fmt.Fprintf(&buf, file.Mode().String())
fmt.Fprintf(&buf, " 1 %s %s ", file.Owner(), file.Group())
fmt.Fprintf(&buf, lpad(strconv.FormatInt(file.Size(), 10), 12))
fmt.Fprintf(&buf, file.ModTime().Format(" Jan _2 15:04 "))
fmt.Fprintf(&buf, "%s\r\n", file.Name())
}
return buf.Bytes()
}
func lpad(input string, length int) (result string) {
if len(input) < length {
result = strings.Repeat(" ", length-len(input)) + input
} else if len(input) == length {
result = input
} else {
result = input[0:length]
}
return
}

View file

@ -1,48 +0,0 @@
// 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 (
"fmt"
"log"
)
type Logger interface {
Print(sessionId string, message interface{})
Printf(sessionId string, format string, v ...interface{})
PrintCommand(sessionId string, command string, params string)
PrintResponse(sessionId string, code int, message string)
}
// Use an instance of this to log in a standard format
type StdLogger struct{}
func (logger *StdLogger) Print(sessionId string, message interface{}) {
log.Printf("%s %s", sessionId, message)
}
func (logger *StdLogger) Printf(sessionId string, format string, v ...interface{}) {
logger.Print(sessionId, fmt.Sprintf(format, v...))
}
func (logger *StdLogger) PrintCommand(sessionId string, command string, params string) {
if command == "PASS" {
log.Printf("%s > PASS ****", sessionId)
} else {
log.Printf("%s > %s %s", sessionId, command, params)
}
}
func (logger *StdLogger) PrintResponse(sessionId string, code int, message string) {
log.Printf("%s < %d %s", sessionId, code, message)
}
// Silent logger, produces no output
type DiscardLogger struct{}
func (logger *DiscardLogger) Print(sessionId string, message interface{}) {}
func (logger *DiscardLogger) Printf(sessionId string, format string, v ...interface{}) {}
func (logger *DiscardLogger) PrintCommand(sessionId string, command string, params string) {}
func (logger *DiscardLogger) PrintResponse(sessionId string, code int, message string) {}

View file

@ -1,52 +0,0 @@
// 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 "os"
type Perm interface {
GetOwner(string) (string, error)
GetGroup(string) (string, error)
GetMode(string) (os.FileMode, error)
ChOwner(string, string) error
ChGroup(string, string) error
ChMode(string, os.FileMode) error
}
type SimplePerm struct {
owner, group string
}
func NewSimplePerm(owner, group string) *SimplePerm {
return &SimplePerm{
owner: owner,
group: group,
}
}
func (s *SimplePerm) GetOwner(string) (string, error) {
return s.owner, nil
}
func (s *SimplePerm) GetGroup(string) (string, error) {
return s.group, nil
}
func (s *SimplePerm) GetMode(string) (os.FileMode, error) {
return os.ModePerm, nil
}
func (s *SimplePerm) ChOwner(string, string) error {
return nil
}
func (s *SimplePerm) ChGroup(string, string) error {
return nil
}
func (s *SimplePerm) ChMode(string, os.FileMode) error {
return nil
}

View file

@ -1,278 +0,0 @@
// 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
}

View file

@ -1,256 +0,0 @@
// 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 (
"crypto/tls"
"io"
"net"
"os"
"runtime"
"strconv"
"strings"
"sync"
"syscall"
"time"
)
// DataSocket describes a data socket is used to send non-control data between the client and
// server.
type DataSocket interface {
Host() string
Port() int
// the standard io.Reader interface
Read(p []byte) (n int, err error)
// the standard io.ReaderFrom interface
ReadFrom(r io.Reader) (int64, error)
// the standard io.Writer interface
Write(p []byte) (n int, err error)
// the standard io.Closer interface
Close() error
}
type ftpActiveSocket struct {
conn *net.TCPConn
host string
port int
logger Logger
}
func newActiveSocket(remote string, port int, logger Logger, sessionID string) (DataSocket, error) {
connectTo := net.JoinHostPort(remote, strconv.Itoa(port))
logger.Print(sessionID, "Opening active data connection to "+connectTo)
raddr, err := net.ResolveTCPAddr("tcp", connectTo)
if err != nil {
logger.Print(sessionID, err)
return nil, err
}
tcpConn, err := net.DialTCP("tcp", nil, raddr)
if err != nil {
logger.Print(sessionID, err)
return nil, err
}
socket := new(ftpActiveSocket)
socket.conn = tcpConn
socket.host = remote
socket.port = port
socket.logger = logger
return socket, nil
}
func (socket *ftpActiveSocket) Host() string {
return socket.host
}
func (socket *ftpActiveSocket) Port() int {
return socket.port
}
func (socket *ftpActiveSocket) Read(p []byte) (n int, err error) {
return socket.conn.Read(p)
}
func (socket *ftpActiveSocket) ReadFrom(r io.Reader) (int64, error) {
return socket.conn.ReadFrom(r)
}
func (socket *ftpActiveSocket) Write(p []byte) (n int, err error) {
return socket.conn.Write(p)
}
func (socket *ftpActiveSocket) Close() error {
return socket.conn.Close()
}
type ftpPassiveSocket struct {
conn net.Conn
port int
host string
ingress chan []byte
egress chan []byte
logger Logger
lock sync.Mutex // protects conn and err
err error
tlsConfig *tls.Config
}
// Detect if an error is "bind: address already in use"
//
// Originally from https://stackoverflow.com/a/52152912/164234
func isErrorAddressAlreadyInUse(err error) bool {
errOpError, ok := err.(*net.OpError)
if !ok {
return false
}
errSyscallError, ok := errOpError.Err.(*os.SyscallError)
if !ok {
return false
}
errErrno, ok := errSyscallError.Err.(syscall.Errno)
if !ok {
return false
}
if errErrno == syscall.EADDRINUSE {
return true
}
const WSAEADDRINUSE = 10048
if runtime.GOOS == "windows" && errErrno == WSAEADDRINUSE {
return true
}
return false
}
func newPassiveSocket(host string, port func() int, logger Logger, sessionID string, tlsConfig *tls.Config) (DataSocket, error) {
socket := new(ftpPassiveSocket)
socket.ingress = make(chan []byte)
socket.egress = make(chan []byte)
socket.logger = logger
socket.host = host
socket.tlsConfig = tlsConfig
const retries = 10
var err error
for i := 1; i <= retries; i++ {
socket.port = port()
err = socket.GoListenAndServe(sessionID)
if err != nil && socket.port != 0 && isErrorAddressAlreadyInUse(err) {
// choose a different port on error already in use
continue
}
break
}
return socket, err
}
func (socket *ftpPassiveSocket) Host() string {
return socket.host
}
func (socket *ftpPassiveSocket) Port() int {
return socket.port
}
func (socket *ftpPassiveSocket) Read(p []byte) (n int, err error) {
socket.lock.Lock()
defer socket.lock.Unlock()
if socket.err != nil {
return 0, socket.err
}
return socket.conn.Read(p)
}
func (socket *ftpPassiveSocket) ReadFrom(r io.Reader) (int64, error) {
socket.lock.Lock()
defer socket.lock.Unlock()
if socket.err != nil {
return 0, socket.err
}
// For normal TCPConn, this will use sendfile syscall; if not,
// it will just downgrade to normal read/write procedure
return io.Copy(socket.conn, r)
}
func (socket *ftpPassiveSocket) Write(p []byte) (n int, err error) {
socket.lock.Lock()
defer socket.lock.Unlock()
if socket.err != nil {
return 0, socket.err
}
return socket.conn.Write(p)
}
func (socket *ftpPassiveSocket) Close() error {
socket.lock.Lock()
defer socket.lock.Unlock()
if socket.conn != nil {
return socket.conn.Close()
}
return nil
}
func (socket *ftpPassiveSocket) GoListenAndServe(sessionID string) (err error) {
laddr, err := net.ResolveTCPAddr("tcp", net.JoinHostPort("", strconv.Itoa(socket.port)))
if err != nil {
socket.logger.Print(sessionID, err)
return
}
var tcplistener *net.TCPListener
tcplistener, err = net.ListenTCP("tcp", laddr)
if err != nil {
socket.logger.Print(sessionID, err)
return
}
// The timeout, for a remote client to establish connection
// with a PASV style data connection.
const acceptTimeout = 60 * time.Second
err = tcplistener.SetDeadline(time.Now().Add(acceptTimeout))
if err != nil {
socket.logger.Print(sessionID, err)
return
}
var listener net.Listener = tcplistener
add := listener.Addr()
parts := strings.Split(add.String(), ":")
port, err := strconv.Atoi(parts[len(parts)-1])
if err != nil {
socket.logger.Print(sessionID, err)
return
}
socket.port = port
if socket.tlsConfig != nil {
listener = tls.NewListener(listener, socket.tlsConfig)
}
socket.lock.Lock()
go func() {
defer socket.lock.Unlock()
conn, err := listener.Accept()
if err != nil {
socket.err = err
return
}
socket.err = nil
socket.conn = conn
_ = listener.Close()
}()
return nil
}