vendor: add github.com/goftp/server

s3-about
Nick Craig-Wood 2018-09-14 17:16:54 +01:00
parent a25875170b
commit a14f0d46d7
18 changed files with 2312 additions and 0 deletions

1
go.mod
View File

@ -17,6 +17,7 @@ require (
github.com/djherbis/times v1.0.1
github.com/dropbox/dropbox-sdk-go-unofficial v4.1.0+incompatible
github.com/go-ini/ini v1.37.0 // indirect
github.com/goftp/server v0.0.0-20180914132916-1fd52c8552f1
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7 // indirect
github.com/golang/protobuf v1.1.0 // indirect
github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 // indirect

2
go.sum
View File

@ -32,6 +32,8 @@ github.com/dropbox/dropbox-sdk-go-unofficial v4.1.0+incompatible h1:ZFvUIiBbGhDY
github.com/dropbox/dropbox-sdk-go-unofficial v4.1.0+incompatible/go.mod h1:lr+LhMM3F6Y3lW1T9j2U5l7QeuWm87N9+PPXo3yH4qY=
github.com/go-ini/ini v1.37.0 h1:/FpMfveJbc7ExTTDgT5nL9Vw+aZdst/c2dOxC931U+M=
github.com/go-ini/ini v1.37.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/goftp/server v0.0.0-20180914132916-1fd52c8552f1 h1:WjgeEHEDLGx56ndxS6FYi6qFjZGajSVHPuEPdpJ60cI=
github.com/goftp/server v0.0.0-20180914132916-1fd52c8552f1/go.mod h1:k/SS6VWkxY7dHPhoMQ8IdRu8L4lQtmGbhyXGg+vCnXE=
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7 h1:2hRPrmiwPrp3fQX967rNJIhQPtiGXdlQWAxKbKw3VHA=
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
github.com/golang/protobuf v1.1.0 h1:0iH4Ffd/meGoXqF2lSAhZHt8X+cPgkfn/cb6Cce5Vpc=

2
vendor/github.com/goftp/server/.gitignore generated vendored Normal file
View File

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

20
vendor/github.com/goftp/server/LICENSE generated vendored Normal file
View File

@ -0,0 +1,20 @@
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.

92
vendor/github.com/goftp/server/README.md generated vendored Normal file
View File

@ -0,0 +1,92 @@
# 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.

28
vendor/github.com/goftp/server/auth.go generated vendored Normal file
View File

@ -0,0 +1,28 @@
// 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
// 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) {
if name != a.Name || pass != a.Password {
return false, nil
}
return true, nil
}

14
vendor/github.com/goftp/server/circle.yml generated vendored Normal file
View File

@ -0,0 +1,14 @@
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)

1138
vendor/github.com/goftp/server/cmd.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

252
vendor/github.com/goftp/server/conn.go generated vendored Normal file
View File

@ -0,0 +1,252 @@
// 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 {
if len(conn.PublicIp()) > 0 {
return conn.PublicIp()
}
return conn.conn.LocalAddr().String()
}
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
}

14
vendor/github.com/goftp/server/doc.go generated vendored Normal file
View File

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

61
vendor/github.com/goftp/server/driver.go generated vendored Normal file
View File

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

14
vendor/github.com/goftp/server/fileinfo.go generated vendored Normal file
View File

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

49
vendor/github.com/goftp/server/listformatter.go generated vendored Normal file
View File

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

48
vendor/github.com/goftp/server/logger.go generated vendored Normal file
View File

@ -0,0 +1,48 @@
// 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) {}

52
vendor/github.com/goftp/server/perm.go generated vendored Normal file
View File

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

278
vendor/github.com/goftp/server/server.go generated vendored Normal file
View File

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

245
vendor/github.com/goftp/server/socket.go generated vendored Normal file
View File

@ -0,0 +1,245 @@
// 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"
)
// 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 listener net.Listener
listener, err = net.ListenTCP("tcp", laddr)
if err != nil {
socket.logger.Print(sessionID, err)
return
}
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
}

2
vendor/modules.txt vendored
View File

@ -74,6 +74,8 @@ github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/team_policies
github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/users_common
# github.com/go-ini/ini v1.37.0
github.com/go-ini/ini
# github.com/goftp/server v0.0.0-20180914132916-1fd52c8552f1
github.com/goftp/server
# github.com/golang/protobuf v1.1.0
github.com/golang/protobuf/proto
# github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135