mirror of
https://github.com/drakkan/sftpgo.git
synced 2025-12-07 14:50:55 +03:00
improve FTP support
- allow to disable active mode - allow to disable SITE commands - add optional support for calculating hash value of files - add optional support for the non standard COMB command
This commit is contained in:
@@ -2,7 +2,9 @@ package ftpd_test
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -37,6 +39,7 @@ const (
|
||||
logSender = "ftpdTesting"
|
||||
ftpServerAddr = "127.0.0.1:2121"
|
||||
sftpServerAddr = "127.0.0.1:2122"
|
||||
ftpSrvAddrTLS = "127.0.0.1:2124" // ftp server with implicit tls
|
||||
defaultUsername = "test_user_ftp"
|
||||
defaultPassword = "test_password"
|
||||
configDir = ".."
|
||||
@@ -157,6 +160,7 @@ func TestMain(m *testing.M) {
|
||||
ftpdConf.BannerFile = bannerFileName
|
||||
ftpdConf.CertificateFile = certPath
|
||||
ftpdConf.CertificateKeyFile = keyPath
|
||||
ftpdConf.EnableSite = true
|
||||
|
||||
// required to test sftpfs
|
||||
sftpdConf := config.GetSFTPDConfig()
|
||||
@@ -165,7 +169,8 @@ func TestMain(m *testing.M) {
|
||||
Port: 2122,
|
||||
},
|
||||
}
|
||||
sftpdConf.HostKeys = []string{filepath.Join(os.TempDir(), "id_ed25519")}
|
||||
hostKeyPath := filepath.Join(os.TempDir(), "id_ed25519")
|
||||
sftpdConf.HostKeys = []string{hostKeyPath}
|
||||
|
||||
extAuthPath = filepath.Join(homeBasePath, "extauth.sh")
|
||||
preLoginPath = filepath.Join(homeBasePath, "prelogin.sh")
|
||||
@@ -205,6 +210,35 @@ func TestMain(m *testing.M) {
|
||||
waitTCPListening(sftpdConf.Bindings[0].GetAddress())
|
||||
ftpd.ReloadTLSCertificate() //nolint:errcheck
|
||||
|
||||
ftpdConf = config.GetFTPDConfig()
|
||||
ftpdConf.Bindings = []ftpd.Binding{
|
||||
{
|
||||
Port: 2124,
|
||||
TLSMode: 2,
|
||||
},
|
||||
}
|
||||
ftpdConf.CertificateFile = certPath
|
||||
ftpdConf.CertificateKeyFile = keyPath
|
||||
ftpdConf.EnableSite = false
|
||||
ftpdConf.DisableActiveMode = true
|
||||
ftpdConf.CombineSupport = 1
|
||||
ftpdConf.HASHSupport = 1
|
||||
|
||||
go func() {
|
||||
logger.Debug(logSender, "", "initializing FTP server with config %+v", ftpdConf)
|
||||
if err := ftpdConf.Initialize(configDir); err != nil {
|
||||
logger.ErrorToConsole("could not start FTP server: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}()
|
||||
|
||||
waitTCPListening(ftpdConf.Bindings[0].GetAddress())
|
||||
|
||||
// ensure all the initial connections to check if the service is alive are disconnected
|
||||
for len(common.Connections.GetStats()) > 0 {
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
|
||||
exitCode := m.Run()
|
||||
os.Remove(logFilePath)
|
||||
os.Remove(bannerFile)
|
||||
@@ -213,6 +247,8 @@ func TestMain(m *testing.M) {
|
||||
os.Remove(postConnectPath)
|
||||
os.Remove(certPath)
|
||||
os.Remove(keyPath)
|
||||
os.Remove(hostKeyPath)
|
||||
os.Remove(hostKeyPath + ".pub")
|
||||
os.Exit(exitCode)
|
||||
}
|
||||
|
||||
@@ -1578,6 +1614,218 @@ func TestChmod(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestCombineDisabled(t *testing.T) {
|
||||
u := getTestUser()
|
||||
localUser, _, err := httpd.AddUser(u, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
sftpUser, _, err := httpd.AddUser(getTestSFTPUser(), http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
for _, user := range []dataprovider.User{localUser, sftpUser} {
|
||||
client, err := getFTPClient(user, true)
|
||||
if assert.NoError(t, err) {
|
||||
err = checkBasicFTP(client)
|
||||
assert.NoError(t, err)
|
||||
|
||||
code, response, err := client.SendCustomCommand("COMB file file.1 file.2")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, ftp.StatusNotImplemented, code)
|
||||
assert.Equal(t, "COMB support is disabled", response)
|
||||
|
||||
err = client.Quit()
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
_, err = httpd.RemoveUser(sftpUser, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
_, err = httpd.RemoveUser(localUser, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
err = os.RemoveAll(localUser.GetHomeDir())
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestActiveModeDisabled(t *testing.T) {
|
||||
u := getTestUser()
|
||||
user, _, err := httpd.AddUser(u, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
client, err := getFTPClientImplicitTLS(user)
|
||||
if assert.NoError(t, err) {
|
||||
err = checkBasicFTP(client)
|
||||
assert.NoError(t, err)
|
||||
code, response, err := client.SendCustomCommand("PORT 10,2,0,2,4,31")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, ftp.StatusNotAvailable, code)
|
||||
assert.Equal(t, "PORT command is disabled", response)
|
||||
|
||||
code, response, err = client.SendCustomCommand("EPRT |1|132.235.1.2|6275|")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, ftp.StatusNotAvailable, code)
|
||||
assert.Equal(t, "EPRT command is disabled", response)
|
||||
|
||||
err = client.Quit()
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
client, err = getFTPClient(user, false)
|
||||
if assert.NoError(t, err) {
|
||||
code, response, err := client.SendCustomCommand("PORT 10,2,0,2,4,31")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, ftp.StatusCommandOK, code)
|
||||
assert.Equal(t, "PORT command successful", response)
|
||||
|
||||
code, response, err = client.SendCustomCommand("EPRT |1|132.235.1.2|6275|")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, ftp.StatusCommandOK, code)
|
||||
assert.Equal(t, "EPRT command successful", response)
|
||||
|
||||
err = client.Quit()
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
_, err = httpd.RemoveUser(user, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
err = os.RemoveAll(user.GetHomeDir())
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestSITEDisabled(t *testing.T) {
|
||||
u := getTestUser()
|
||||
user, _, err := httpd.AddUser(u, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
client, err := getFTPClientImplicitTLS(user)
|
||||
if assert.NoError(t, err) {
|
||||
err = checkBasicFTP(client)
|
||||
assert.NoError(t, err)
|
||||
|
||||
code, response, err := client.SendCustomCommand("SITE CHMOD 600 afile.txt")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, ftp.StatusBadCommand, code)
|
||||
assert.Equal(t, "SITE support is disabled", response)
|
||||
|
||||
err = client.Quit()
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
_, err = httpd.RemoveUser(user, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
err = os.RemoveAll(user.GetHomeDir())
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestHASH(t *testing.T) {
|
||||
u := getTestUser()
|
||||
localUser, _, err := httpd.AddUser(u, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
sftpUser, _, err := httpd.AddUser(getTestSFTPUser(), http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
u = getTestUserWithCryptFs()
|
||||
u.Username += "_crypt"
|
||||
cryptUser, _, err := httpd.AddUser(u, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
for _, user := range []dataprovider.User{localUser, sftpUser, cryptUser} {
|
||||
client, err := getFTPClientImplicitTLS(user)
|
||||
if assert.NoError(t, err) {
|
||||
testFilePath := filepath.Join(homeBasePath, testFileName)
|
||||
testFileSize := int64(131072)
|
||||
err = createTestFile(testFilePath, testFileSize)
|
||||
assert.NoError(t, err)
|
||||
err = checkBasicFTP(client)
|
||||
assert.NoError(t, err)
|
||||
err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
|
||||
assert.NoError(t, err)
|
||||
|
||||
h := sha256.New()
|
||||
f, err := os.Open(testFilePath)
|
||||
assert.NoError(t, err)
|
||||
_, err = io.Copy(h, f)
|
||||
assert.NoError(t, err)
|
||||
hash := hex.EncodeToString(h.Sum(nil))
|
||||
err = f.Close()
|
||||
assert.NoError(t, err)
|
||||
|
||||
code, response, err := client.SendCustomCommand(fmt.Sprintf("XSHA256 %v", testFileName))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, ftp.StatusRequestedFileActionOK, code)
|
||||
assert.Contains(t, response, hash)
|
||||
|
||||
code, response, err = client.SendCustomCommand(fmt.Sprintf("HASH %v", testFileName))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, ftp.StatusFile, code)
|
||||
assert.Contains(t, response, hash)
|
||||
|
||||
err = client.Quit()
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = os.Remove(testFilePath)
|
||||
assert.NoError(t, err)
|
||||
if user.Username == defaultUsername {
|
||||
err = os.RemoveAll(user.GetHomeDir())
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_, err = httpd.RemoveUser(sftpUser, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
_, err = httpd.RemoveUser(localUser, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
err = os.RemoveAll(localUser.GetHomeDir())
|
||||
assert.NoError(t, err)
|
||||
_, err = httpd.RemoveUser(cryptUser, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
err = os.RemoveAll(cryptUser.GetHomeDir())
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestCombine(t *testing.T) {
|
||||
u := getTestUser()
|
||||
localUser, _, err := httpd.AddUser(u, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
sftpUser, _, err := httpd.AddUser(getTestSFTPUser(), http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
for _, user := range []dataprovider.User{localUser, sftpUser} {
|
||||
client, err := getFTPClientImplicitTLS(user)
|
||||
if assert.NoError(t, err) {
|
||||
testFilePath := filepath.Join(homeBasePath, testFileName)
|
||||
testFileSize := int64(131072)
|
||||
err = createTestFile(testFilePath, testFileSize)
|
||||
assert.NoError(t, err)
|
||||
err = checkBasicFTP(client)
|
||||
assert.NoError(t, err)
|
||||
err = ftpUploadFile(testFilePath, testFileName+".1", testFileSize, client, 0)
|
||||
assert.NoError(t, err)
|
||||
err = ftpUploadFile(testFilePath, testFileName+".2", testFileSize, client, 0)
|
||||
assert.NoError(t, err)
|
||||
|
||||
code, response, err := client.SendCustomCommand(fmt.Sprintf("COMB %v %v %v", testFileName, testFileName+".1", testFileName+".2"))
|
||||
assert.NoError(t, err)
|
||||
if user.Username == defaultUsername {
|
||||
assert.Equal(t, ftp.StatusRequestedFileActionOK, code)
|
||||
assert.Equal(t, "COMB succeeded!", response)
|
||||
} else {
|
||||
assert.Equal(t, ftp.StatusFileUnavailable, code)
|
||||
assert.Contains(t, response, "COMB is not supported for this filesystem")
|
||||
}
|
||||
|
||||
err = client.Quit()
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = os.Remove(testFilePath)
|
||||
assert.NoError(t, err)
|
||||
if user.Username == defaultUsername {
|
||||
err = os.RemoveAll(user.GetHomeDir())
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_, err = httpd.RemoveUser(sftpUser, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
_, err = httpd.RemoveUser(localUser, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
err = os.RemoveAll(localUser.GetHomeDir())
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func checkBasicFTP(client *ftp.ServerConn) error {
|
||||
_, err := client.CurrentDir()
|
||||
if err != nil {
|
||||
@@ -1647,6 +1895,30 @@ func ftpDownloadFile(remoteSourcePath string, localDestPath string, expectedSize
|
||||
return nil
|
||||
}
|
||||
|
||||
func getFTPClientImplicitTLS(user dataprovider.User) (*ftp.ServerConn, error) {
|
||||
ftpOptions := []ftp.DialOption{ftp.DialWithTimeout(5 * time.Second)}
|
||||
tlsConfig := &tls.Config{
|
||||
ServerName: "localhost",
|
||||
InsecureSkipVerify: true, // use this for tests only
|
||||
MinVersion: tls.VersionTLS12,
|
||||
}
|
||||
ftpOptions = append(ftpOptions, ftp.DialWithTLS(tlsConfig))
|
||||
ftpOptions = append(ftpOptions, ftp.DialWithDisabledEPSV(true))
|
||||
client, err := ftp.Dial(ftpSrvAddrTLS, ftpOptions...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pwd := defaultPassword
|
||||
if user.Password != "" {
|
||||
pwd = user.Password
|
||||
}
|
||||
err = client.Login(user.Username, pwd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return client, err
|
||||
}
|
||||
|
||||
func getFTPClient(user dataprovider.User, useTLS bool) (*ftp.ServerConn, error) {
|
||||
ftpOptions := []ftp.DialOption{ftp.DialWithTimeout(5 * time.Second)}
|
||||
if useTLS {
|
||||
@@ -1662,7 +1934,7 @@ func getFTPClient(user dataprovider.User, useTLS bool) (*ftp.ServerConn, error)
|
||||
return nil, err
|
||||
}
|
||||
pwd := defaultPassword
|
||||
if len(user.Password) > 0 {
|
||||
if user.Password != "" {
|
||||
pwd = user.Password
|
||||
}
|
||||
err = client.Login(user.Username, pwd)
|
||||
|
||||
Reference in New Issue
Block a user