246 lines
5.2 KiB
Go
246 lines
5.2 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 (
|
||
|
"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
|
||
|
}
|