fafda/internal/ftp/server.go
2024-12-19 01:41:46 +05:30

128 lines
3.5 KiB
Go

package ftp
import (
"crypto/tls"
"errors"
"fmt"
"io"
"net/http"
"github.com/fclairamb/ftpserverlib"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/spf13/afero"
"fafda/config"
)
const IPResolveURL = "https://ipinfo.io/ip"
var (
ErrNoTLS = errors.New("TLS is not configured")
ErrBadUserNameOrPassword = errors.New("bad username or password")
)
func Serv(cfg config.FTPServer, fs afero.Fs) error {
logger := log.With().Str("component", "ftpserver").Logger()
driver := &Driver{
Fs: fs,
Debug: true,
username: cfg.Username,
password: cfg.Password,
Settings: &ftpserver.Settings{
ListenAddr: cfg.Addr,
DefaultTransferType: ftpserver.TransferTypeBinary,
// Stooopid FTP thinks connection is idle, even when file transfer is going on.
// Default is 900 seconds after which the server will drop the connection
// Increased it to 24 hours to allow big file transfers
IdleTimeout: 86400, // 24 hour
},
logger: logger,
}
if cfg.PortRange != nil {
portRange := &ftpserver.PortRange{}
if cfg.PortRange.Start < 1 || cfg.PortRange.Start > 65535 {
return fmt.Errorf("invalid start port: must be between 1-65535, got %d", cfg.PortRange.Start)
}
if cfg.PortRange.End < 1 || cfg.PortRange.End > 65535 {
return fmt.Errorf("invalid end port: must be between 1-65535, got %d", cfg.PortRange.End)
}
if cfg.PortRange.Start >= cfg.PortRange.End {
return fmt.Errorf("start port (%d) must be less than end port (%d)",
cfg.PortRange.Start, cfg.PortRange.End)
}
portRange.Start = cfg.PortRange.Start
portRange.End = cfg.PortRange.End
driver.Settings.PassiveTransferPortRange = portRange
driver.Settings.PublicIPResolver = func(context ftpserver.ClientContext) (string, error) {
resp, err := http.Get(IPResolveURL)
if err != nil {
return "", err
}
ip, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}
return string(ip), nil
}
}
server := ftpserver.NewFtpServer(driver)
logger.Info().Str("address", cfg.Addr).Msg("starting server")
return server.ListenAndServe()
}
type Driver struct {
Fs afero.Fs
Debug bool
Settings *ftpserver.Settings
username string
password string
logger zerolog.Logger
}
func (d *Driver) ClientConnected(cc ftpserver.ClientContext) (string, error) {
d.logger.Info().
Str("address", cc.RemoteAddr().String()).
Str("version", cc.GetClientVersion()).
Uint32("sessionId", cc.ID()).
Msg("client connected")
return "Fafda FTP Server", nil
}
func (d *Driver) ClientDisconnected(cc ftpserver.ClientContext) {
d.logger.Info().
Str("address", cc.RemoteAddr().String()).
Str("version", cc.GetClientVersion()).
Uint32("sessionId", cc.ID()).
Msg("client disconnected")
}
func (d *Driver) AuthUser(cc ftpserver.ClientContext, user, pass string) (ftpserver.ClientDriver, error) {
if d.username != "" && d.username != user || d.password != "" && d.password != pass {
d.logger.Warn().
Str("address", cc.RemoteAddr().String()).
Uint32("session_id", cc.ID()).
Str("user", user).
Err(ErrBadUserNameOrPassword).
Msg("authentication failed")
return nil, ErrBadUserNameOrPassword
}
d.logger.Info().
Str("address", cc.RemoteAddr().String()).
Uint32("sessionId", cc.ID()).
Str("user", user).
Msg("authentication successful")
return d.Fs, nil
}
func (d *Driver) GetSettings() (*ftpserver.Settings, error) { return d.Settings, nil }
func (d *Driver) GetTLSConfig() (*tls.Config, error) { return nil, ErrNoTLS }