sftpgo-mirror/ftpd/ftpd_test.go
Nicola Murino dccc583b5d
add a dedicated struct to store encrypted credentials
also gcs credentials are now encrypted, both on disk and inside the
provider.

Data provider is automatically migrated and load data will accept
old format too but you should upgrade to the new format to avoid future
issues
2020-11-22 21:53:04 +01:00

1527 lines
49 KiB
Go

package ftpd_test
import (
"crypto/rand"
"crypto/tls"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"os"
"os/exec"
"path"
"path/filepath"
"runtime"
"testing"
"time"
"github.com/jlaffaye/ftp"
"github.com/rs/zerolog"
"github.com/stretchr/testify/assert"
"github.com/drakkan/sftpgo/common"
"github.com/drakkan/sftpgo/config"
"github.com/drakkan/sftpgo/dataprovider"
"github.com/drakkan/sftpgo/ftpd"
"github.com/drakkan/sftpgo/httpd"
"github.com/drakkan/sftpgo/logger"
"github.com/drakkan/sftpgo/vfs"
)
const (
logSender = "ftpdTesting"
ftpServerAddr = "127.0.0.1:2121"
defaultUsername = "test_user_ftp"
defaultPassword = "test_password"
configDir = ".."
osWindows = "windows"
ftpsCert = `-----BEGIN CERTIFICATE-----
MIICHTCCAaKgAwIBAgIUHnqw7QnB1Bj9oUsNpdb+ZkFPOxMwCgYIKoZIzj0EAwIw
RTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGElu
dGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMDAyMDQwOTUzMDRaFw0zMDAyMDEw
OTUzMDRaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYD
VQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwdjAQBgcqhkjOPQIBBgUrgQQA
IgNiAARCjRMqJ85rzMC998X5z761nJ+xL3bkmGVqWvrJ51t5OxV0v25NsOgR82CA
NXUgvhVYs7vNFN+jxtb2aj6Xg+/2G/BNxkaFspIVCzgWkxiz7XE4lgUwX44FCXZM
3+JeUbKjUzBRMB0GA1UdDgQWBBRhLw+/o3+Z02MI/d4tmaMui9W16jAfBgNVHSME
GDAWgBRhLw+/o3+Z02MI/d4tmaMui9W16jAPBgNVHRMBAf8EBTADAQH/MAoGCCqG
SM49BAMCA2kAMGYCMQDqLt2lm8mE+tGgtjDmtFgdOcI72HSbRQ74D5rYTzgST1rY
/8wTi5xl8TiFUyLMUsICMQC5ViVxdXbhuG7gX6yEqSkMKZICHpO8hqFwOD/uaFVI
dV4vKmHUzwK/eIx+8Ay3neE=
-----END CERTIFICATE-----`
ftpsKey = `-----BEGIN EC PARAMETERS-----
BgUrgQQAIg==
-----END EC PARAMETERS-----
-----BEGIN EC PRIVATE KEY-----
MIGkAgEBBDCfMNsN6miEE3rVyUPwElfiJSWaR5huPCzUenZOfJT04GAcQdWvEju3
UM2lmBLIXpGgBwYFK4EEACKhZANiAARCjRMqJ85rzMC998X5z761nJ+xL3bkmGVq
WvrJ51t5OxV0v25NsOgR82CANXUgvhVYs7vNFN+jxtb2aj6Xg+/2G/BNxkaFspIV
CzgWkxiz7XE4lgUwX44FCXZM3+JeUbI=
-----END EC PRIVATE KEY-----`
testFileName = "test_file_ftp.dat"
testDLFileName = "test_download_ftp.dat"
)
var (
allPerms = []string{dataprovider.PermAny}
homeBasePath string
hookCmdPath string
extAuthPath string
preLoginPath string
postConnectPath string
logFilePath string
)
func TestMain(m *testing.M) {
logFilePath = filepath.Join(configDir, "sftpgo_ftpd_test.log")
bannerFileName := "banner_file"
bannerFile := filepath.Join(configDir, bannerFileName)
logger.InitLogger(logFilePath, 5, 1, 28, false, zerolog.DebugLevel)
err := ioutil.WriteFile(bannerFile, []byte("SFTPGo test ready\nsimple banner line\n"), os.ModePerm)
if err != nil {
logger.ErrorToConsole("error creating banner file: %v", err)
}
err = config.LoadConfig(configDir, "")
if err != nil {
logger.ErrorToConsole("error loading configuration: %v", err)
os.Exit(1)
}
providerConf := config.GetProviderConf()
logger.InfoToConsole("Starting FTPD tests, provider: %v", providerConf.Driver)
commonConf := config.GetCommonConfig()
// we run the test cases with UploadMode atomic and resume support. The non atomic code path
// simply does not execute some code so if it works in atomic mode will
// work in non atomic mode too
commonConf.UploadMode = 2
homeBasePath = os.TempDir()
if runtime.GOOS != osWindows {
commonConf.Actions.ExecuteOn = []string{"download", "upload", "rename", "delete"}
commonConf.Actions.Hook = hookCmdPath
hookCmdPath, err = exec.LookPath("true")
if err != nil {
logger.Warn(logSender, "", "unable to get hook command: %v", err)
logger.WarnToConsole("unable to get hook command: %v", err)
}
}
certPath := filepath.Join(os.TempDir(), "test_ftpd.crt")
keyPath := filepath.Join(os.TempDir(), "test_ftpd.key")
err = ioutil.WriteFile(certPath, []byte(ftpsCert), os.ModePerm)
if err != nil {
logger.ErrorToConsole("error writing FTPS certificate: %v", err)
os.Exit(1)
}
err = ioutil.WriteFile(keyPath, []byte(ftpsKey), os.ModePerm)
if err != nil {
logger.ErrorToConsole("error writing FTPS private key: %v", err)
os.Exit(1)
}
common.Initialize(commonConf)
err = dataprovider.Initialize(providerConf, configDir)
if err != nil {
logger.ErrorToConsole("error initializing data provider: %v", err)
os.Exit(1)
}
httpConfig := config.GetHTTPConfig()
httpConfig.Initialize(configDir)
httpdConf := config.GetHTTPDConfig()
httpdConf.BindPort = 8079
httpd.SetBaseURLAndCredentials("http://127.0.0.1:8079", "", "")
ftpdConf := config.GetFTPDConfig()
ftpdConf.BindPort = 2121
ftpdConf.PassivePortRange.Start = 0
ftpdConf.PassivePortRange.End = 0
ftpdConf.BannerFile = bannerFileName
ftpdConf.CertificateFile = certPath
ftpdConf.CertificateKeyFile = keyPath
extAuthPath = filepath.Join(homeBasePath, "extauth.sh")
preLoginPath = filepath.Join(homeBasePath, "prelogin.sh")
postConnectPath = filepath.Join(homeBasePath, "postconnect.sh")
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)
}
}()
go func() {
if err := httpdConf.Initialize(configDir, false); err != nil {
logger.ErrorToConsole("could not start HTTP server: %v", err)
os.Exit(1)
}
}()
waitTCPListening(fmt.Sprintf("%s:%d", ftpdConf.BindAddress, ftpdConf.BindPort))
waitTCPListening(fmt.Sprintf("%s:%d", httpdConf.BindAddress, httpdConf.BindPort))
ftpd.ReloadTLSCertificate() //nolint:errcheck
exitCode := m.Run()
os.Remove(logFilePath)
os.Remove(bannerFile)
os.Remove(extAuthPath)
os.Remove(preLoginPath)
os.Remove(postConnectPath)
os.Remove(certPath)
os.Remove(keyPath)
os.Exit(exitCode)
}
func TestBasicFTPHandling(t *testing.T) {
u := getTestUser()
u.QuotaSize = 6553600
user, _, err := httpd.AddUser(u, http.StatusOK)
assert.NoError(t, err)
client, err := getFTPClient(user, true)
if assert.NoError(t, err) {
assert.Len(t, common.Connections.GetStats(), 1)
testFilePath := filepath.Join(homeBasePath, testFileName)
testFileSize := int64(65535)
expectedQuotaSize := user.UsedQuotaSize + testFileSize
expectedQuotaFiles := user.UsedQuotaFiles + 1
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
err = checkBasicFTP(client)
assert.NoError(t, err)
err = ftpUploadFile(testFilePath, path.Join("/missing_dir", testFileName), testFileSize, client, 0)
assert.Error(t, err)
err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
assert.NoError(t, err)
// overwrite an existing file
err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
assert.NoError(t, err)
localDownloadPath := filepath.Join(homeBasePath, testDLFileName)
err = ftpDownloadFile(testFileName, localDownloadPath, testFileSize, client, 0)
assert.NoError(t, err)
user, _, err = httpd.GetUserByID(user.ID, http.StatusOK)
assert.NoError(t, err)
assert.Equal(t, expectedQuotaFiles, user.UsedQuotaFiles)
assert.Equal(t, expectedQuotaSize, user.UsedQuotaSize)
err = client.Rename(testFileName, testFileName+"1")
assert.NoError(t, err)
err = client.Delete(testFileName)
assert.Error(t, err)
err = client.Delete(testFileName + "1")
assert.NoError(t, err)
user, _, err = httpd.GetUserByID(user.ID, http.StatusOK)
assert.NoError(t, err)
assert.Equal(t, expectedQuotaFiles-1, user.UsedQuotaFiles)
assert.Equal(t, expectedQuotaSize-testFileSize, user.UsedQuotaSize)
curDir, err := client.CurrentDir()
if assert.NoError(t, err) {
assert.Equal(t, "/", curDir)
}
testDir := "testDir"
err = client.MakeDir(testDir)
assert.NoError(t, err)
err = client.ChangeDir(testDir)
assert.NoError(t, err)
curDir, err = client.CurrentDir()
if assert.NoError(t, err) {
assert.Equal(t, path.Join("/", testDir), curDir)
}
err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
assert.NoError(t, err)
size, err := client.FileSize(path.Join("/", testDir, testFileName))
assert.NoError(t, err)
assert.Equal(t, testFileSize, size)
err = client.ChangeDirToParent()
assert.NoError(t, err)
curDir, err = client.CurrentDir()
if assert.NoError(t, err) {
assert.Equal(t, "/", curDir)
}
err = client.Delete(path.Join("/", testDir, testFileName))
assert.NoError(t, err)
err = client.Delete(testDir)
assert.Error(t, err)
err = client.RemoveDir(testDir)
assert.NoError(t, err)
err = os.Remove(testFilePath)
assert.NoError(t, err)
err = os.Remove(localDownloadPath)
assert.NoError(t, err)
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)
assert.Eventually(t, func() bool { return len(common.Connections.GetStats()) == 0 }, 1*time.Second, 50*time.Millisecond)
}
func TestLoginInvalidPwd(t *testing.T) {
u := getTestUser()
user, _, err := httpd.AddUser(u, http.StatusOK)
assert.NoError(t, err)
user.Password = "wrong"
_, err = getFTPClient(user, false)
assert.Error(t, err)
_, err = httpd.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
}
func TestLoginExternalAuth(t *testing.T) {
if runtime.GOOS == osWindows {
t.Skip("this test is not available on Windows")
}
u := getTestUser()
err := dataprovider.Close()
assert.NoError(t, err)
err = config.LoadConfig(configDir, "")
assert.NoError(t, err)
providerConf := config.GetProviderConf()
err = ioutil.WriteFile(extAuthPath, getExtAuthScriptContent(u, false, ""), os.ModePerm)
assert.NoError(t, err)
providerConf.ExternalAuthHook = extAuthPath
providerConf.ExternalAuthScope = 0
err = dataprovider.Initialize(providerConf, configDir)
assert.NoError(t, err)
client, err := getFTPClient(u, true)
if assert.NoError(t, err) {
err = checkBasicFTP(client)
assert.NoError(t, err)
err := client.Quit()
assert.NoError(t, err)
}
u.Username = defaultUsername + "1"
client, err = getFTPClient(u, true)
if !assert.Error(t, err) {
err := client.Quit()
assert.NoError(t, err)
}
users, _, err := httpd.GetUsers(0, 0, defaultUsername, http.StatusOK)
assert.NoError(t, err)
if assert.Len(t, users, 1) {
user := users[0]
assert.Equal(t, defaultUsername, user.Username)
_, err = httpd.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
err = dataprovider.Close()
assert.NoError(t, err)
err = config.LoadConfig(configDir, "")
assert.NoError(t, err)
providerConf = config.GetProviderConf()
err = dataprovider.Initialize(providerConf, configDir)
assert.NoError(t, err)
err = os.Remove(extAuthPath)
assert.NoError(t, err)
}
func TestPreLoginHook(t *testing.T) {
if runtime.GOOS == osWindows {
t.Skip("this test is not available on Windows")
}
u := getTestUser()
err := dataprovider.Close()
assert.NoError(t, err)
err = config.LoadConfig(configDir, "")
assert.NoError(t, err)
providerConf := config.GetProviderConf()
err = ioutil.WriteFile(preLoginPath, getPreLoginScriptContent(u, false), os.ModePerm)
assert.NoError(t, err)
providerConf.PreLoginHook = preLoginPath
err = dataprovider.Initialize(providerConf, configDir)
assert.NoError(t, err)
users, _, err := httpd.GetUsers(0, 0, defaultUsername, http.StatusOK)
assert.NoError(t, err)
assert.Equal(t, 0, len(users))
client, err := getFTPClient(u, false)
if assert.NoError(t, err) {
err = checkBasicFTP(client)
assert.NoError(t, err)
err := client.Quit()
assert.NoError(t, err)
}
users, _, err = httpd.GetUsers(0, 0, defaultUsername, http.StatusOK)
assert.NoError(t, err)
assert.Equal(t, 1, len(users))
user := users[0]
// test login with an existing user
client, err = getFTPClient(user, true)
if assert.NoError(t, err) {
err = checkBasicFTP(client)
assert.NoError(t, err)
err := client.Quit()
assert.NoError(t, err)
}
err = ioutil.WriteFile(preLoginPath, getPreLoginScriptContent(user, true), os.ModePerm)
assert.NoError(t, err)
client, err = getFTPClient(u, false)
if !assert.Error(t, err) {
err := client.Quit()
assert.NoError(t, err)
}
user.Status = 0
err = ioutil.WriteFile(preLoginPath, getPreLoginScriptContent(user, false), os.ModePerm)
assert.NoError(t, err)
client, err = getFTPClient(u, false)
if !assert.Error(t, err, "pre-login script returned a disabled user, login must fail") {
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)
err = dataprovider.Close()
assert.NoError(t, err)
err = config.LoadConfig(configDir, "")
assert.NoError(t, err)
providerConf = config.GetProviderConf()
err = dataprovider.Initialize(providerConf, configDir)
assert.NoError(t, err)
err = os.Remove(preLoginPath)
assert.NoError(t, err)
}
func TestPostConnectHook(t *testing.T) {
if runtime.GOOS == osWindows {
t.Skip("this test is not available on Windows")
}
common.Config.PostConnectHook = postConnectPath
u := getTestUser()
user, _, err := httpd.AddUser(u, http.StatusOK)
assert.NoError(t, err)
err = ioutil.WriteFile(postConnectPath, getPostConnectScriptContent(0), os.ModePerm)
assert.NoError(t, err)
client, err := getFTPClient(user, true)
if assert.NoError(t, err) {
err = checkBasicFTP(client)
assert.NoError(t, err)
err := client.Quit()
assert.NoError(t, err)
}
err = ioutil.WriteFile(postConnectPath, getPostConnectScriptContent(1), os.ModePerm)
assert.NoError(t, err)
client, err = getFTPClient(user, true)
if !assert.Error(t, err) {
err := client.Quit()
assert.NoError(t, err)
}
common.Config.PostConnectHook = "http://127.0.0.1:8079/api/v1/version"
client, err = getFTPClient(user, false)
if assert.NoError(t, err) {
err = checkBasicFTP(client)
assert.NoError(t, err)
err := client.Quit()
assert.NoError(t, err)
}
common.Config.PostConnectHook = "http://127.0.0.1:8079/notfound"
client, err = getFTPClient(user, true)
if !assert.Error(t, err) {
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)
common.Config.PostConnectHook = ""
}
func TestMaxSessions(t *testing.T) {
u := getTestUser()
u.MaxSessions = 1
user, _, err := httpd.AddUser(u, http.StatusOK)
assert.NoError(t, err)
client, err := getFTPClient(user, true)
if assert.NoError(t, err) {
err = checkBasicFTP(client)
assert.NoError(t, err)
_, err = getFTPClient(user, false)
assert.Error(t, err)
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 TestZeroBytesTransfers(t *testing.T) {
u := getTestUser()
user, _, err := httpd.AddUser(u, http.StatusOK)
assert.NoError(t, err)
for _, useTLS := range []bool{true, false} {
client, err := getFTPClient(user, useTLS)
if assert.NoError(t, err) {
testFileName := "testfilename"
err = checkBasicFTP(client)
assert.NoError(t, err)
localDownloadPath := filepath.Join(homeBasePath, "empty_download")
err = ioutil.WriteFile(localDownloadPath, []byte(""), os.ModePerm)
assert.NoError(t, err)
err = ftpUploadFile(localDownloadPath, testFileName, 0, client, 0)
assert.NoError(t, err)
size, err := client.FileSize(testFileName)
assert.NoError(t, err)
assert.Equal(t, int64(0), size)
err = os.Remove(localDownloadPath)
assert.NoError(t, err)
assert.NoFileExists(t, localDownloadPath)
err = ftpDownloadFile(testFileName, localDownloadPath, 0, client, 0)
assert.NoError(t, err)
assert.FileExists(t, localDownloadPath)
err = client.Quit()
assert.NoError(t, err)
err = os.Remove(localDownloadPath)
assert.NoError(t, err)
}
}
_, err = httpd.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestDownloadErrors(t *testing.T) {
u := getTestUser()
u.QuotaFiles = 1
subDir1 := "sub1"
subDir2 := "sub2"
u.Permissions[path.Join("/", subDir1)] = []string{dataprovider.PermListItems}
u.Permissions[path.Join("/", subDir2)] = []string{dataprovider.PermListItems, dataprovider.PermUpload,
dataprovider.PermDelete, dataprovider.PermDownload}
u.Filters.FileExtensions = []dataprovider.ExtensionsFilter{
{
Path: "/sub2",
AllowedExtensions: []string{},
DeniedExtensions: []string{".zip"},
},
}
u.Filters.FilePatterns = []dataprovider.PatternsFilter{
{
Path: "/sub2",
AllowedPatterns: []string{},
DeniedPatterns: []string{"*.jpg"},
},
}
user, _, err := httpd.AddUser(u, http.StatusOK)
assert.NoError(t, err)
client, err := getFTPClient(user, true)
if assert.NoError(t, err) {
testFilePath1 := filepath.Join(user.HomeDir, subDir1, "file.zip")
testFilePath2 := filepath.Join(user.HomeDir, subDir2, "file.zip")
testFilePath3 := filepath.Join(user.HomeDir, subDir2, "file.jpg")
err = os.MkdirAll(filepath.Dir(testFilePath1), os.ModePerm)
assert.NoError(t, err)
err = os.MkdirAll(filepath.Dir(testFilePath2), os.ModePerm)
assert.NoError(t, err)
err = ioutil.WriteFile(testFilePath1, []byte("file1"), os.ModePerm)
assert.NoError(t, err)
err = ioutil.WriteFile(testFilePath2, []byte("file2"), os.ModePerm)
assert.NoError(t, err)
err = ioutil.WriteFile(testFilePath3, []byte("file3"), os.ModePerm)
assert.NoError(t, err)
localDownloadPath := filepath.Join(homeBasePath, testDLFileName)
err = ftpDownloadFile(path.Join("/", subDir1, "file.zip"), localDownloadPath, 5, client, 0)
assert.Error(t, err)
err = ftpDownloadFile(path.Join("/", subDir2, "file.zip"), localDownloadPath, 5, client, 0)
assert.Error(t, err)
err = ftpDownloadFile(path.Join("/", subDir2, "file.jpg"), localDownloadPath, 5, client, 0)
assert.Error(t, err)
err = ftpDownloadFile("/missing.zip", localDownloadPath, 5, client, 0)
assert.Error(t, err)
err = client.Quit()
assert.NoError(t, err)
err = os.Remove(localDownloadPath)
assert.NoError(t, err)
}
_, err = httpd.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestUploadErrors(t *testing.T) {
u := getTestUser()
u.QuotaSize = 65535
subDir1 := "sub1"
subDir2 := "sub2"
u.Permissions[path.Join("/", subDir1)] = []string{dataprovider.PermListItems}
u.Permissions[path.Join("/", subDir2)] = []string{dataprovider.PermListItems, dataprovider.PermUpload,
dataprovider.PermDelete}
u.Filters.FileExtensions = []dataprovider.ExtensionsFilter{
{
Path: "/sub2",
AllowedExtensions: []string{},
DeniedExtensions: []string{".zip"},
},
}
user, _, err := httpd.AddUser(u, http.StatusOK)
assert.NoError(t, err)
client, err := getFTPClient(user, true)
if assert.NoError(t, err) {
testFilePath := filepath.Join(homeBasePath, testFileName)
testFileSize := user.QuotaSize
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
err = client.MakeDir(subDir1)
assert.NoError(t, err)
err = client.MakeDir(subDir2)
assert.NoError(t, err)
err = client.ChangeDir(subDir1)
assert.NoError(t, err)
err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
assert.Error(t, err)
err = client.ChangeDirToParent()
assert.NoError(t, err)
err = client.ChangeDir(subDir2)
assert.NoError(t, err)
err = ftpUploadFile(testFilePath, testFileName+".zip", testFileSize, client, 0)
assert.Error(t, err)
err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
assert.NoError(t, err)
err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
assert.Error(t, err)
err = client.ChangeDir("/")
assert.NoError(t, err)
err = ftpUploadFile(testFilePath, subDir1, testFileSize, client, 0)
assert.Error(t, err)
// overquota
err = ftpUploadFile(testFilePath, testFileName+"1", testFileSize, client, 0)
assert.Error(t, err)
err = client.Delete(path.Join("/", subDir2, testFileName))
assert.NoError(t, err)
err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
assert.NoError(t, err)
err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
assert.Error(t, err)
err = client.Quit()
assert.NoError(t, err)
err = os.Remove(testFilePath)
assert.NoError(t, err)
}
_, err = httpd.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestResume(t *testing.T) {
u := getTestUser()
user, _, err := httpd.AddUser(u, http.StatusOK)
assert.NoError(t, err)
client, err := getFTPClient(user, true)
if assert.NoError(t, err) {
testFilePath := filepath.Join(homeBasePath, testFileName)
data := []byte("test data")
err = ioutil.WriteFile(testFilePath, data, os.ModePerm)
assert.NoError(t, err)
err = ftpUploadFile(testFilePath, testFileName, int64(len(data)), client, 0)
assert.NoError(t, err)
err = ftpUploadFile(testFilePath, testFileName, int64(len(data)+5), client, 5)
assert.NoError(t, err)
readed, err := ioutil.ReadFile(filepath.Join(user.GetHomeDir(), testFileName))
assert.NoError(t, err)
assert.Equal(t, "test test data", string(readed))
localDownloadPath := filepath.Join(homeBasePath, testDLFileName)
err = ftpDownloadFile(testFileName, localDownloadPath, int64(len(data)), client, 5)
assert.NoError(t, err)
readed, err = ioutil.ReadFile(localDownloadPath)
assert.NoError(t, err)
assert.Equal(t, data, readed)
err = client.Delete(testFileName)
assert.NoError(t, err)
err = ftpUploadFile(testFilePath, testFileName, int64(len(data)), client, 0)
assert.NoError(t, err)
// now append to a file
srcFile, err := os.Open(testFilePath)
if assert.NoError(t, err) {
err = client.Append(testFileName, srcFile)
assert.NoError(t, err)
err = srcFile.Close()
assert.NoError(t, err)
size, err := client.FileSize(testFileName)
assert.NoError(t, err)
assert.Equal(t, int64(2*len(data)), size)
err = ftpDownloadFile(testFileName, localDownloadPath, int64(2*len(data)), client, 0)
assert.NoError(t, err)
readed, err = ioutil.ReadFile(localDownloadPath)
assert.NoError(t, err)
expected := append(data, data...)
assert.Equal(t, expected, readed)
}
err = client.Quit()
assert.NoError(t, err)
err = os.Remove(testFilePath)
assert.NoError(t, err)
err = os.Remove(localDownloadPath)
assert.NoError(t, err)
}
_, err = httpd.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
//nolint:dupl
func TestDeniedLoginMethod(t *testing.T) {
u := getTestUser()
u.Filters.DeniedLoginMethods = []string{dataprovider.LoginMethodPassword}
user, _, err := httpd.AddUser(u, http.StatusOK)
assert.NoError(t, err)
_, err = getFTPClient(user, false)
assert.Error(t, err)
user.Filters.DeniedLoginMethods = []string{dataprovider.SSHLoginMethodPublicKey, dataprovider.SSHLoginMethodKeyAndPassword}
user, _, err = httpd.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err)
client, err := getFTPClient(user, true)
if assert.NoError(t, err) {
assert.NoError(t, checkBasicFTP(client))
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)
}
//nolint:dupl
func TestDeniedProtocols(t *testing.T) {
u := getTestUser()
u.Filters.DeniedProtocols = []string{common.ProtocolFTP}
user, _, err := httpd.AddUser(u, http.StatusOK)
assert.NoError(t, err)
_, err = getFTPClient(user, false)
assert.Error(t, err)
user.Filters.DeniedProtocols = []string{common.ProtocolSSH, common.ProtocolWebDAV}
user, _, err = httpd.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err)
client, err := getFTPClient(user, true)
if assert.NoError(t, err) {
assert.NoError(t, checkBasicFTP(client))
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 TestQuotaLimits(t *testing.T) {
u := getTestUser()
u.QuotaFiles = 1
user, _, err := httpd.AddUser(u, http.StatusOK)
assert.NoError(t, err)
testFileSize := int64(65535)
testFilePath := filepath.Join(homeBasePath, testFileName)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
testFileSize1 := int64(131072)
testFileName1 := "test_file1.dat"
testFilePath1 := filepath.Join(homeBasePath, testFileName1)
err = createTestFile(testFilePath1, testFileSize1)
assert.NoError(t, err)
testFileSize2 := int64(32768)
testFileName2 := "test_file2.dat"
testFilePath2 := filepath.Join(homeBasePath, testFileName2)
err = createTestFile(testFilePath2, testFileSize2)
assert.NoError(t, err)
// test quota files
client, err := getFTPClient(user, false)
if assert.NoError(t, err) {
err = ftpUploadFile(testFilePath, testFileName+".quota", testFileSize, client, 0)
assert.NoError(t, err)
err = ftpUploadFile(testFilePath, testFileName+".quota1", testFileSize, client, 0)
assert.Error(t, err)
err = client.Rename(testFileName+".quota", testFileName)
assert.NoError(t, err)
err = client.Quit()
assert.NoError(t, err)
}
// test quota size
user.QuotaSize = testFileSize - 1
user.QuotaFiles = 0
user, _, err = httpd.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err)
client, err = getFTPClient(user, true)
if assert.NoError(t, err) {
err = ftpUploadFile(testFilePath, testFileName+".quota", testFileSize, client, 0)
assert.Error(t, err)
err = client.Rename(testFileName, testFileName+".quota")
assert.NoError(t, err)
err = client.Quit()
assert.NoError(t, err)
}
// now test quota limits while uploading the current file, we have 1 bytes remaining
user.QuotaSize = testFileSize + 1
user.QuotaFiles = 0
user, _, err = httpd.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err)
client, err = getFTPClient(user, false)
if assert.NoError(t, err) {
err = ftpUploadFile(testFilePath1, testFileName1, testFileSize1, client, 0)
assert.Error(t, err)
_, err = client.FileSize(testFileName1)
assert.Error(t, err)
err = client.Rename(testFileName+".quota", testFileName)
assert.NoError(t, err)
// overwriting an existing file will work if the resulting size is lesser or equal than the current one
err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
assert.NoError(t, err)
err = ftpUploadFile(testFilePath2, testFileName, testFileSize2, client, 0)
assert.NoError(t, err)
err = ftpUploadFile(testFilePath1, testFileName, testFileSize1, client, 0)
assert.Error(t, err)
err = ftpUploadFile(testFilePath1, testFileName, testFileSize1, client, 10)
assert.Error(t, err)
err = ftpUploadFile(testFilePath2, testFileName, testFileSize2, client, 0)
assert.NoError(t, err)
err = client.Quit()
assert.NoError(t, err)
}
err = os.Remove(testFilePath)
assert.NoError(t, err)
err = os.Remove(testFilePath1)
assert.NoError(t, err)
err = os.Remove(testFilePath2)
assert.NoError(t, err)
_, err = httpd.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestUploadMaxSize(t *testing.T) {
testFileSize := int64(65535)
u := getTestUser()
u.Filters.MaxUploadFileSize = testFileSize + 1
user, _, err := httpd.AddUser(u, http.StatusOK)
assert.NoError(t, err)
testFilePath := filepath.Join(homeBasePath, testFileName)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
testFileSize1 := int64(131072)
testFileName1 := "test_file1.dat"
testFilePath1 := filepath.Join(homeBasePath, testFileName1)
err = createTestFile(testFilePath1, testFileSize1)
assert.NoError(t, err)
client, err := getFTPClient(user, false)
if assert.NoError(t, err) {
err = ftpUploadFile(testFilePath1, testFileName1, testFileSize1, client, 0)
assert.Error(t, err)
err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
assert.NoError(t, err)
// now test overwrite an existing file with a size bigger than the allowed one
err = createTestFile(filepath.Join(user.GetHomeDir(), testFileName1), testFileSize1)
assert.NoError(t, err)
err = ftpUploadFile(testFilePath1, testFileName1, testFileSize1, client, 0)
assert.Error(t, err)
err = client.Quit()
assert.NoError(t, err)
}
err = os.Remove(testFilePath)
assert.NoError(t, err)
err = os.Remove(testFilePath1)
assert.NoError(t, err)
_, err = httpd.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestLoginWithIPilters(t *testing.T) {
u := getTestUser()
u.Filters.DeniedIP = []string{"192.167.0.0/24", "172.18.0.0/16"}
u.Filters.AllowedIP = []string{"172.19.0.0/16"}
user, _, err := httpd.AddUser(u, http.StatusOK)
assert.NoError(t, err)
client, err := getFTPClient(user, true)
if !assert.Error(t, err) {
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 TestLoginWithDatabaseCredentials(t *testing.T) {
u := getTestUser()
u.FsConfig.Provider = dataprovider.GCSFilesystemProvider
u.FsConfig.GCSConfig.Bucket = "test"
u.FsConfig.GCSConfig.Credentials = vfs.Secret{
Status: vfs.SecretStatusPlain,
Payload: `{ "type": "service_account" }`,
}
providerConf := config.GetProviderConf()
providerConf.PreferDatabaseCredentials = true
credentialsFile := filepath.Join(providerConf.CredentialsPath, fmt.Sprintf("%v_gcs_credentials.json", u.Username))
if !filepath.IsAbs(credentialsFile) {
credentialsFile = filepath.Join(configDir, credentialsFile)
}
assert.NoError(t, dataprovider.Close())
err := dataprovider.Initialize(providerConf, configDir)
assert.NoError(t, err)
if _, err = os.Stat(credentialsFile); err == nil {
// remove the credentials file
assert.NoError(t, os.Remove(credentialsFile))
}
user, _, err := httpd.AddUser(u, http.StatusOK)
assert.NoError(t, err)
assert.Equal(t, vfs.SecretStatusAES256GCM, user.FsConfig.GCSConfig.Credentials.Status)
assert.NotEmpty(t, user.FsConfig.GCSConfig.Credentials.Payload)
assert.Empty(t, user.FsConfig.GCSConfig.Credentials.AdditionalData)
assert.Empty(t, user.FsConfig.GCSConfig.Credentials.Key)
assert.NoFileExists(t, credentialsFile)
client, err := getFTPClient(user, false)
if assert.NoError(t, err) {
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)
assert.NoError(t, dataprovider.Close())
assert.NoError(t, config.LoadConfig(configDir, ""))
providerConf = config.GetProviderConf()
assert.NoError(t, dataprovider.Initialize(providerConf, configDir))
}
func TestLoginInvalidFs(t *testing.T) {
u := getTestUser()
u.FsConfig.Provider = dataprovider.GCSFilesystemProvider
u.FsConfig.GCSConfig.Bucket = "test"
u.FsConfig.GCSConfig.Credentials = vfs.Secret{
Status: vfs.SecretStatusPlain,
Payload: "invalid JSON for credentials",
}
user, _, err := httpd.AddUser(u, http.StatusOK)
assert.NoError(t, err)
providerConf := config.GetProviderConf()
credentialsFile := filepath.Join(providerConf.CredentialsPath, fmt.Sprintf("%v_gcs_credentials.json", u.Username))
if !filepath.IsAbs(credentialsFile) {
credentialsFile = filepath.Join(configDir, credentialsFile)
}
// now remove the credentials file so the filesystem creation will fail
err = os.Remove(credentialsFile)
assert.NoError(t, err)
client, err := getFTPClient(user, false)
if !assert.Error(t, err) {
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 TestClientClose(t *testing.T) {
u := getTestUser()
user, _, err := httpd.AddUser(u, http.StatusOK)
assert.NoError(t, err)
client, err := getFTPClient(user, true)
if assert.NoError(t, err) {
err = checkBasicFTP(client)
assert.NoError(t, err)
stats := common.Connections.GetStats()
if assert.Len(t, stats, 1) {
common.Connections.Close(stats[0].ConnectionID)
assert.Eventually(t, func() bool { return len(common.Connections.GetStats()) == 0 },
1*time.Second, 50*time.Millisecond)
}
}
_, err = httpd.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestRename(t *testing.T) {
u := getTestUser()
user, _, err := httpd.AddUser(u, http.StatusOK)
assert.NoError(t, err)
testDir := "adir"
testFilePath := filepath.Join(homeBasePath, testFileName)
testFileSize := int64(65535)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
client, err := getFTPClient(user, false)
if assert.NoError(t, err) {
err = checkBasicFTP(client)
assert.NoError(t, err)
err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
assert.NoError(t, err)
err = client.MakeDir(testDir)
assert.NoError(t, err)
err = client.Rename(testFileName, path.Join("missing", testFileName))
assert.Error(t, err)
err = client.Rename(testFileName, path.Join(testDir, testFileName))
assert.NoError(t, err)
size, err := client.FileSize(path.Join(testDir, testFileName))
assert.NoError(t, err)
assert.Equal(t, testFileSize, size)
if runtime.GOOS != osWindows {
otherDir := "dir"
err = client.MakeDir(otherDir)
assert.NoError(t, err)
err = client.MakeDir(path.Join(otherDir, testDir))
assert.NoError(t, err)
code, response, err := client.SendCustomCommand(fmt.Sprintf("SITE CHMOD 0001 %v", otherDir))
assert.NoError(t, err)
assert.Equal(t, ftp.StatusCommandOK, code)
assert.Equal(t, "SITE CHMOD command successful", response)
err = client.Rename(testDir, path.Join(otherDir, testDir))
assert.Error(t, err)
code, response, err = client.SendCustomCommand(fmt.Sprintf("SITE CHMOD 755 %v", otherDir))
assert.NoError(t, err)
assert.Equal(t, ftp.StatusCommandOK, code)
assert.Equal(t, "SITE CHMOD command successful", response)
}
err = client.Quit()
assert.NoError(t, err)
}
user.Permissions[path.Join("/", testDir)] = []string{dataprovider.PermListItems}
user, _, err = httpd.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err)
client, err = getFTPClient(user, false)
if assert.NoError(t, err) {
err = client.Rename(path.Join(testDir, testFileName), testFileName)
assert.Error(t, err)
err := client.Quit()
assert.NoError(t, err)
}
err = os.Remove(testFilePath)
assert.NoError(t, err)
_, err = httpd.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestSymlink(t *testing.T) {
u := getTestUser()
user, _, err := httpd.AddUser(u, http.StatusOK)
assert.NoError(t, err)
testFilePath := filepath.Join(homeBasePath, testFileName)
testFileSize := int64(65535)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
client, err := getFTPClient(user, false)
if assert.NoError(t, err) {
err = checkBasicFTP(client)
assert.NoError(t, err)
err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
assert.NoError(t, err)
code, _, err := client.SendCustomCommand(fmt.Sprintf("SITE SYMLINK %v %v", testFileName, testFileName+".link"))
assert.NoError(t, err)
assert.Equal(t, ftp.StatusCommandOK, code)
if runtime.GOOS != osWindows {
testDir := "adir"
otherDir := "dir"
err = client.MakeDir(otherDir)
assert.NoError(t, err)
err = client.MakeDir(path.Join(otherDir, testDir))
assert.NoError(t, err)
code, response, err := client.SendCustomCommand(fmt.Sprintf("SITE CHMOD 0001 %v", otherDir))
assert.NoError(t, err)
assert.Equal(t, ftp.StatusCommandOK, code)
assert.Equal(t, "SITE CHMOD command successful", response)
code, _, err = client.SendCustomCommand(fmt.Sprintf("SITE SYMLINK %v %v", testDir, path.Join(otherDir, testDir)))
assert.NoError(t, err)
assert.Equal(t, ftp.StatusFileUnavailable, code)
code, response, err = client.SendCustomCommand(fmt.Sprintf("SITE CHMOD 755 %v", otherDir))
assert.NoError(t, err)
assert.Equal(t, ftp.StatusCommandOK, code)
assert.Equal(t, "SITE CHMOD command successful", response)
}
err = client.Quit()
assert.NoError(t, err)
}
err = os.Remove(testFilePath)
assert.NoError(t, err)
_, err = httpd.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestStat(t *testing.T) {
u := getTestUser()
u.Permissions["/subdir"] = []string{dataprovider.PermUpload}
user, _, err := httpd.AddUser(u, http.StatusOK)
assert.NoError(t, err)
client, err := getFTPClient(user, false)
if assert.NoError(t, err) {
subDir := "subdir"
testFilePath := filepath.Join(homeBasePath, testFileName)
testFileSize := int64(65535)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
err = client.MakeDir(subDir)
assert.NoError(t, err)
err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
assert.NoError(t, err)
err = ftpUploadFile(testFilePath, path.Join("/", subDir, testFileName), testFileSize, client, 0)
assert.Error(t, err)
size, err := client.FileSize(testFileName)
assert.NoError(t, err)
assert.Equal(t, testFileSize, size)
_, err = client.FileSize(path.Join("/", subDir, testFileName))
assert.Error(t, err)
_, err = client.FileSize("missing file")
assert.Error(t, err)
err = client.Quit()
assert.NoError(t, err)
err = os.Remove(testFilePath)
assert.NoError(t, err)
}
_, err = httpd.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestUploadOverwriteVfolder(t *testing.T) {
u := getTestUser()
vdir := "/vdir"
mappedPath := filepath.Join(os.TempDir(), "vdir")
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
BaseVirtualFolder: vfs.BaseVirtualFolder{
MappedPath: mappedPath,
},
VirtualPath: vdir,
QuotaSize: -1,
QuotaFiles: -1,
})
err := os.MkdirAll(mappedPath, os.ModePerm)
assert.NoError(t, err)
user, _, err := httpd.AddUser(u, http.StatusOK)
assert.NoError(t, err)
client, err := getFTPClient(user, false)
if assert.NoError(t, err) {
testFilePath := filepath.Join(homeBasePath, testFileName)
testFileSize := int64(65535)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
err = ftpUploadFile(testFilePath, path.Join(vdir, testFileName), testFileSize, client, 0)
assert.NoError(t, err)
folder, _, err := httpd.GetFolders(0, 0, mappedPath, http.StatusOK)
assert.NoError(t, err)
if assert.Len(t, folder, 1) {
f := folder[0]
assert.Equal(t, testFileSize, f.UsedQuotaSize)
assert.Equal(t, 1, f.UsedQuotaFiles)
}
err = ftpUploadFile(testFilePath, path.Join(vdir, testFileName), testFileSize, client, 0)
assert.NoError(t, err)
folder, _, err = httpd.GetFolders(0, 0, mappedPath, http.StatusOK)
assert.NoError(t, err)
if assert.Len(t, folder, 1) {
f := folder[0]
assert.Equal(t, testFileSize, f.UsedQuotaSize)
assert.Equal(t, 1, f.UsedQuotaFiles)
}
err = client.Quit()
assert.NoError(t, err)
err = os.Remove(testFilePath)
assert.NoError(t, err)
}
_, err = httpd.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
_, err = httpd.RemoveFolder(vfs.BaseVirtualFolder{MappedPath: mappedPath}, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
err = os.RemoveAll(mappedPath)
assert.NoError(t, err)
}
func TestAllocate(t *testing.T) {
u := getTestUser()
mappedPath := filepath.Join(os.TempDir(), "vdir")
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
BaseVirtualFolder: vfs.BaseVirtualFolder{
MappedPath: mappedPath,
},
VirtualPath: "/vdir",
QuotaSize: 110,
})
err := os.MkdirAll(mappedPath, os.ModePerm)
assert.NoError(t, err)
user, _, err := httpd.AddUser(u, http.StatusOK)
assert.NoError(t, err)
client, err := getFTPClient(user, false)
if assert.NoError(t, err) {
code, response, err := client.SendCustomCommand("allo 2000000")
assert.NoError(t, err)
assert.Equal(t, ftp.StatusCommandOK, code)
assert.Equal(t, "Done !", response)
err = client.Quit()
assert.NoError(t, err)
}
user.QuotaSize = 100
user, _, err = httpd.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err)
client, err = getFTPClient(user, false)
if assert.NoError(t, err) {
testFilePath := filepath.Join(homeBasePath, testFileName)
testFileSize := user.QuotaSize - 1
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
code, response, err := client.SendCustomCommand("allo 99")
assert.NoError(t, err)
assert.Equal(t, ftp.StatusCommandOK, code)
assert.Equal(t, "Done !", response)
code, response, err = client.SendCustomCommand("allo 100")
assert.NoError(t, err)
assert.Equal(t, ftp.StatusCommandOK, code)
assert.Equal(t, "Done !", response)
code, response, err = client.SendCustomCommand("allo 150")
assert.NoError(t, err)
assert.Equal(t, ftp.StatusFileUnavailable, code)
assert.Contains(t, response, common.ErrQuotaExceeded.Error())
err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
assert.NoError(t, err)
// we still have space in vdir
code, response, err = client.SendCustomCommand("allo 50")
assert.NoError(t, err)
assert.Equal(t, ftp.StatusCommandOK, code)
assert.Equal(t, "Done !", response)
err = ftpUploadFile(testFilePath, path.Join("/vdir", testFileName), testFileSize, client, 0)
assert.NoError(t, err)
code, response, err = client.SendCustomCommand("allo 50")
assert.NoError(t, err)
assert.Equal(t, ftp.StatusFileUnavailable, code)
assert.Contains(t, response, common.ErrQuotaExceeded.Error())
err = client.Quit()
assert.NoError(t, err)
err = os.Remove(testFilePath)
assert.NoError(t, err)
}
user.Filters.MaxUploadFileSize = 100
user.QuotaSize = 0
user, _, err = httpd.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err)
client, err = getFTPClient(user, false)
if assert.NoError(t, err) {
code, response, err := client.SendCustomCommand("allo 99")
assert.NoError(t, err)
assert.Equal(t, ftp.StatusCommandOK, code)
assert.Equal(t, "Done !", response)
code, response, err = client.SendCustomCommand("allo 150")
assert.NoError(t, err)
assert.Equal(t, ftp.StatusFileUnavailable, code)
assert.Contains(t, response, common.ErrQuotaExceeded.Error())
err = client.Quit()
assert.NoError(t, err)
}
_, err = httpd.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
_, err = httpd.RemoveFolder(vfs.BaseVirtualFolder{MappedPath: mappedPath}, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
err = os.RemoveAll(mappedPath)
assert.NoError(t, err)
}
func TestChtimes(t *testing.T) {
u := getTestUser()
user, _, err := httpd.AddUser(u, http.StatusOK)
assert.NoError(t, err)
client, err := getFTPClient(user, false)
if assert.NoError(t, err) {
testFilePath := filepath.Join(homeBasePath, testFileName)
testFileSize := int64(65535)
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)
mtime := time.Now().Format("20060102150405")
code, response, err := client.SendCustomCommand(fmt.Sprintf("MFMT %v %v", mtime, testFileName))
assert.NoError(t, err)
assert.Equal(t, ftp.StatusFile, code)
assert.Equal(t, fmt.Sprintf("Modify=%v; %v", mtime, testFileName), response)
err = client.Quit()
assert.NoError(t, err)
err = os.Remove(testFilePath)
assert.NoError(t, err)
}
_, err = httpd.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestChmod(t *testing.T) {
if runtime.GOOS == osWindows {
t.Skip("chmod is partially supported on Windows")
}
u := getTestUser()
user, _, err := httpd.AddUser(u, http.StatusOK)
assert.NoError(t, err)
client, err := getFTPClient(user, true)
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)
code, response, err := client.SendCustomCommand(fmt.Sprintf("SITE CHMOD 600 %v", testFileName))
assert.NoError(t, err)
assert.Equal(t, ftp.StatusCommandOK, code)
assert.Equal(t, "SITE CHMOD command successful", response)
fi, err := os.Stat(filepath.Join(user.HomeDir, testFileName))
if assert.NoError(t, err) {
assert.Equal(t, os.FileMode(0600), fi.Mode().Perm())
}
err = client.Quit()
assert.NoError(t, err)
err = os.Remove(testFilePath)
assert.NoError(t, err)
}
_, err = httpd.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func checkBasicFTP(client *ftp.ServerConn) error {
_, err := client.CurrentDir()
if err != nil {
return err
}
err = client.NoOp()
if err != nil {
return err
}
_, err = client.List(".")
if err != nil {
return err
}
return nil
}
func ftpUploadFile(localSourcePath string, remoteDestPath string, expectedSize int64, client *ftp.ServerConn, offset uint64) error {
srcFile, err := os.Open(localSourcePath)
if err != nil {
return err
}
defer srcFile.Close()
if offset > 0 {
err = client.StorFrom(remoteDestPath, srcFile, offset)
} else {
err = client.Stor(remoteDestPath, srcFile)
}
if err != nil {
return err
}
if expectedSize > 0 {
size, err := client.FileSize(remoteDestPath)
if err != nil {
return err
}
if size != expectedSize {
return fmt.Errorf("uploaded file size does not match, actual: %v, expected: %v", size, expectedSize)
}
}
return nil
}
func ftpDownloadFile(remoteSourcePath string, localDestPath string, expectedSize int64, client *ftp.ServerConn, offset uint64) error {
downloadDest, err := os.Create(localDestPath)
if err != nil {
return err
}
defer downloadDest.Close()
var r *ftp.Response
if offset > 0 {
r, err = client.RetrFrom(remoteSourcePath, offset)
} else {
r, err = client.Retr(remoteSourcePath)
}
if err != nil {
return err
}
defer r.Close()
written, err := io.Copy(downloadDest, r)
if err != nil {
return err
}
if written != expectedSize {
return fmt.Errorf("downloaded file size does not match, actual: %v, expected: %v", written, expectedSize)
}
return nil
}
func getFTPClient(user dataprovider.User, useTLS bool) (*ftp.ServerConn, error) {
ftpOptions := []ftp.DialOption{ftp.DialWithTimeout(5 * time.Second)}
if useTLS {
tlsConfig := &tls.Config{
ServerName: "localhost",
InsecureSkipVerify: true, // use this for tests only
MinVersion: tls.VersionTLS12,
}
ftpOptions = append(ftpOptions, ftp.DialWithExplicitTLS(tlsConfig))
}
client, err := ftp.Dial(ftpServerAddr, ftpOptions...)
if err != nil {
return nil, err
}
pwd := defaultPassword
if len(user.Password) > 0 {
pwd = user.Password
}
err = client.Login(user.Username, pwd)
if err != nil {
return nil, err
}
return client, err
}
func waitTCPListening(address string) {
for {
conn, err := net.Dial("tcp", address)
if err != nil {
logger.WarnToConsole("tcp server %v not listening: %v\n", address, err)
time.Sleep(100 * time.Millisecond)
continue
}
logger.InfoToConsole("tcp server %v now listening\n", address)
conn.Close()
break
}
}
func getTestUser() dataprovider.User {
user := dataprovider.User{
Username: defaultUsername,
Password: defaultPassword,
HomeDir: filepath.Join(homeBasePath, defaultUsername),
Status: 1,
ExpirationDate: 0,
}
user.Permissions = make(map[string][]string)
user.Permissions["/"] = allPerms
return user
}
func getExtAuthScriptContent(user dataprovider.User, nonJSONResponse bool, username string) []byte {
extAuthContent := []byte("#!/bin/sh\n\n")
extAuthContent = append(extAuthContent, []byte(fmt.Sprintf("if test \"$SFTPGO_AUTHD_USERNAME\" = \"%v\"; then\n", user.Username))...)
if len(username) > 0 {
user.Username = username
}
u, _ := json.Marshal(user)
if nonJSONResponse {
extAuthContent = append(extAuthContent, []byte("echo 'text response'\n")...)
} else {
extAuthContent = append(extAuthContent, []byte(fmt.Sprintf("echo '%v'\n", string(u)))...)
}
extAuthContent = append(extAuthContent, []byte("else\n")...)
if nonJSONResponse {
extAuthContent = append(extAuthContent, []byte("echo 'text response'\n")...)
} else {
extAuthContent = append(extAuthContent, []byte("echo '{\"username\":\"\"}'\n")...)
}
extAuthContent = append(extAuthContent, []byte("fi\n")...)
return extAuthContent
}
func getPreLoginScriptContent(user dataprovider.User, nonJSONResponse bool) []byte {
content := []byte("#!/bin/sh\n\n")
if nonJSONResponse {
content = append(content, []byte("echo 'text response'\n")...)
return content
}
if len(user.Username) > 0 {
u, _ := json.Marshal(user)
content = append(content, []byte(fmt.Sprintf("echo '%v'\n", string(u)))...)
}
return content
}
func getPostConnectScriptContent(exitCode int) []byte {
content := []byte("#!/bin/sh\n\n")
content = append(content, []byte(fmt.Sprintf("exit %v", exitCode))...)
return content
}
func createTestFile(path string, size int64) error {
baseDir := filepath.Dir(path)
if _, err := os.Stat(baseDir); os.IsNotExist(err) {
err = os.MkdirAll(baseDir, os.ModePerm)
if err != nil {
return err
}
}
content := make([]byte, size)
_, err := rand.Read(content)
if err != nil {
return err
}
return ioutil.WriteFile(path, content, os.ModePerm)
}