ftpd_test.go 48 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501
  1. package ftpd_test
  2. import (
  3. "crypto/rand"
  4. "crypto/tls"
  5. "encoding/json"
  6. "fmt"
  7. "io"
  8. "io/ioutil"
  9. "net"
  10. "net/http"
  11. "os"
  12. "os/exec"
  13. "path"
  14. "path/filepath"
  15. "runtime"
  16. "testing"
  17. "time"
  18. "github.com/jlaffaye/ftp"
  19. "github.com/rs/zerolog"
  20. "github.com/stretchr/testify/assert"
  21. "github.com/drakkan/sftpgo/common"
  22. "github.com/drakkan/sftpgo/config"
  23. "github.com/drakkan/sftpgo/dataprovider"
  24. "github.com/drakkan/sftpgo/ftpd"
  25. "github.com/drakkan/sftpgo/httpd"
  26. "github.com/drakkan/sftpgo/logger"
  27. "github.com/drakkan/sftpgo/vfs"
  28. )
  29. const (
  30. logSender = "ftpdTesting"
  31. ftpServerAddr = "127.0.0.1:2121"
  32. defaultUsername = "test_user_ftp"
  33. defaultPassword = "test_password"
  34. configDir = ".."
  35. osWindows = "windows"
  36. ftpsCert = `-----BEGIN CERTIFICATE-----
  37. MIICHTCCAaKgAwIBAgIUHnqw7QnB1Bj9oUsNpdb+ZkFPOxMwCgYIKoZIzj0EAwIw
  38. RTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGElu
  39. dGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMDAyMDQwOTUzMDRaFw0zMDAyMDEw
  40. OTUzMDRaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYD
  41. VQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwdjAQBgcqhkjOPQIBBgUrgQQA
  42. IgNiAARCjRMqJ85rzMC998X5z761nJ+xL3bkmGVqWvrJ51t5OxV0v25NsOgR82CA
  43. NXUgvhVYs7vNFN+jxtb2aj6Xg+/2G/BNxkaFspIVCzgWkxiz7XE4lgUwX44FCXZM
  44. 3+JeUbKjUzBRMB0GA1UdDgQWBBRhLw+/o3+Z02MI/d4tmaMui9W16jAfBgNVHSME
  45. GDAWgBRhLw+/o3+Z02MI/d4tmaMui9W16jAPBgNVHRMBAf8EBTADAQH/MAoGCCqG
  46. SM49BAMCA2kAMGYCMQDqLt2lm8mE+tGgtjDmtFgdOcI72HSbRQ74D5rYTzgST1rY
  47. /8wTi5xl8TiFUyLMUsICMQC5ViVxdXbhuG7gX6yEqSkMKZICHpO8hqFwOD/uaFVI
  48. dV4vKmHUzwK/eIx+8Ay3neE=
  49. -----END CERTIFICATE-----`
  50. ftpsKey = `-----BEGIN EC PARAMETERS-----
  51. BgUrgQQAIg==
  52. -----END EC PARAMETERS-----
  53. -----BEGIN EC PRIVATE KEY-----
  54. MIGkAgEBBDCfMNsN6miEE3rVyUPwElfiJSWaR5huPCzUenZOfJT04GAcQdWvEju3
  55. UM2lmBLIXpGgBwYFK4EEACKhZANiAARCjRMqJ85rzMC998X5z761nJ+xL3bkmGVq
  56. WvrJ51t5OxV0v25NsOgR82CANXUgvhVYs7vNFN+jxtb2aj6Xg+/2G/BNxkaFspIV
  57. CzgWkxiz7XE4lgUwX44FCXZM3+JeUbI=
  58. -----END EC PRIVATE KEY-----`
  59. testFileName = "test_file_ftp.dat"
  60. testDLFileName = "test_download_ftp.dat"
  61. )
  62. var (
  63. allPerms = []string{dataprovider.PermAny}
  64. homeBasePath string
  65. hookCmdPath string
  66. extAuthPath string
  67. preLoginPath string
  68. postConnectPath string
  69. logFilePath string
  70. )
  71. func TestMain(m *testing.M) {
  72. logFilePath = filepath.Join(configDir, "sftpgo_ftpd_test.log")
  73. bannerFileName := "banner_file"
  74. bannerFile := filepath.Join(configDir, bannerFileName)
  75. logger.InitLogger(logFilePath, 5, 1, 28, false, zerolog.DebugLevel)
  76. err := ioutil.WriteFile(bannerFile, []byte("SFTPGo test ready\nsimple banner line\n"), os.ModePerm)
  77. if err != nil {
  78. logger.ErrorToConsole("error creating banner file: %v", err)
  79. }
  80. err = config.LoadConfig(configDir, "")
  81. if err != nil {
  82. logger.ErrorToConsole("error loading configuration: %v", err)
  83. os.Exit(1)
  84. }
  85. providerConf := config.GetProviderConf()
  86. logger.InfoToConsole("Starting FTPD tests, provider: %v", providerConf.Driver)
  87. commonConf := config.GetCommonConfig()
  88. // we run the test cases with UploadMode atomic and resume support. The non atomic code path
  89. // simply does not execute some code so if it works in atomic mode will
  90. // work in non atomic mode too
  91. commonConf.UploadMode = 2
  92. homeBasePath = os.TempDir()
  93. if runtime.GOOS != osWindows {
  94. commonConf.Actions.ExecuteOn = []string{"download", "upload", "rename", "delete"}
  95. commonConf.Actions.Hook = hookCmdPath
  96. hookCmdPath, err = exec.LookPath("true")
  97. if err != nil {
  98. logger.Warn(logSender, "", "unable to get hook command: %v", err)
  99. logger.WarnToConsole("unable to get hook command: %v", err)
  100. }
  101. }
  102. certPath := filepath.Join(os.TempDir(), "test_ftpd.crt")
  103. keyPath := filepath.Join(os.TempDir(), "test_ftpd.key")
  104. err = ioutil.WriteFile(certPath, []byte(ftpsCert), os.ModePerm)
  105. if err != nil {
  106. logger.ErrorToConsole("error writing FTPS certificate: %v", err)
  107. os.Exit(1)
  108. }
  109. err = ioutil.WriteFile(keyPath, []byte(ftpsKey), os.ModePerm)
  110. if err != nil {
  111. logger.ErrorToConsole("error writing FTPS private key: %v", err)
  112. os.Exit(1)
  113. }
  114. common.Initialize(commonConf)
  115. err = dataprovider.Initialize(providerConf, configDir)
  116. if err != nil {
  117. logger.ErrorToConsole("error initializing data provider: %v", err)
  118. os.Exit(1)
  119. }
  120. httpConfig := config.GetHTTPConfig()
  121. httpConfig.Initialize(configDir)
  122. httpdConf := config.GetHTTPDConfig()
  123. httpdConf.BindPort = 8079
  124. httpd.SetBaseURLAndCredentials("http://127.0.0.1:8079", "", "")
  125. ftpdConf := config.GetFTPDConfig()
  126. ftpdConf.BindPort = 2121
  127. ftpdConf.PassivePortRange.Start = 0
  128. ftpdConf.PassivePortRange.End = 0
  129. ftpdConf.BannerFile = bannerFileName
  130. ftpdConf.CertificateFile = certPath
  131. ftpdConf.CertificateKeyFile = keyPath
  132. extAuthPath = filepath.Join(homeBasePath, "extauth.sh")
  133. preLoginPath = filepath.Join(homeBasePath, "prelogin.sh")
  134. postConnectPath = filepath.Join(homeBasePath, "postconnect.sh")
  135. go func() {
  136. logger.Debug(logSender, "", "initializing FTP server with config %+v", ftpdConf)
  137. if err := ftpdConf.Initialize(configDir); err != nil {
  138. logger.ErrorToConsole("could not start FTP server: %v", err)
  139. os.Exit(1)
  140. }
  141. }()
  142. go func() {
  143. if err := httpdConf.Initialize(configDir, false); err != nil {
  144. logger.ErrorToConsole("could not start HTTP server: %v", err)
  145. os.Exit(1)
  146. }
  147. }()
  148. waitTCPListening(fmt.Sprintf("%s:%d", ftpdConf.BindAddress, ftpdConf.BindPort))
  149. waitTCPListening(fmt.Sprintf("%s:%d", httpdConf.BindAddress, httpdConf.BindPort))
  150. ftpd.ReloadTLSCertificate() //nolint:errcheck
  151. exitCode := m.Run()
  152. os.Remove(logFilePath)
  153. os.Remove(bannerFile)
  154. os.Remove(extAuthPath)
  155. os.Remove(preLoginPath)
  156. os.Remove(postConnectPath)
  157. os.Remove(certPath)
  158. os.Remove(keyPath)
  159. os.Exit(exitCode)
  160. }
  161. func TestBasicFTPHandling(t *testing.T) {
  162. u := getTestUser()
  163. u.QuotaSize = 6553600
  164. user, _, err := httpd.AddUser(u, http.StatusOK)
  165. assert.NoError(t, err)
  166. client, err := getFTPClient(user, true)
  167. if assert.NoError(t, err) {
  168. assert.Len(t, common.Connections.GetStats(), 1)
  169. testFilePath := filepath.Join(homeBasePath, testFileName)
  170. testFileSize := int64(65535)
  171. expectedQuotaSize := user.UsedQuotaSize + testFileSize
  172. expectedQuotaFiles := user.UsedQuotaFiles + 1
  173. err = createTestFile(testFilePath, testFileSize)
  174. assert.NoError(t, err)
  175. err = checkBasicFTP(client)
  176. assert.NoError(t, err)
  177. err = ftpUploadFile(testFilePath, path.Join("/missing_dir", testFileName), testFileSize, client, 0)
  178. assert.Error(t, err)
  179. err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
  180. assert.NoError(t, err)
  181. // overwrite an existing file
  182. err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
  183. assert.NoError(t, err)
  184. localDownloadPath := filepath.Join(homeBasePath, testDLFileName)
  185. err = ftpDownloadFile(testFileName, localDownloadPath, testFileSize, client, 0)
  186. assert.NoError(t, err)
  187. user, _, err = httpd.GetUserByID(user.ID, http.StatusOK)
  188. assert.NoError(t, err)
  189. assert.Equal(t, expectedQuotaFiles, user.UsedQuotaFiles)
  190. assert.Equal(t, expectedQuotaSize, user.UsedQuotaSize)
  191. err = client.Rename(testFileName, testFileName+"1")
  192. assert.NoError(t, err)
  193. err = client.Delete(testFileName)
  194. assert.Error(t, err)
  195. err = client.Delete(testFileName + "1")
  196. assert.NoError(t, err)
  197. user, _, err = httpd.GetUserByID(user.ID, http.StatusOK)
  198. assert.NoError(t, err)
  199. assert.Equal(t, expectedQuotaFiles-1, user.UsedQuotaFiles)
  200. assert.Equal(t, expectedQuotaSize-testFileSize, user.UsedQuotaSize)
  201. curDir, err := client.CurrentDir()
  202. if assert.NoError(t, err) {
  203. assert.Equal(t, "/", curDir)
  204. }
  205. testDir := "testDir"
  206. err = client.MakeDir(testDir)
  207. assert.NoError(t, err)
  208. err = client.ChangeDir(testDir)
  209. assert.NoError(t, err)
  210. curDir, err = client.CurrentDir()
  211. if assert.NoError(t, err) {
  212. assert.Equal(t, path.Join("/", testDir), curDir)
  213. }
  214. err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
  215. assert.NoError(t, err)
  216. size, err := client.FileSize(path.Join("/", testDir, testFileName))
  217. assert.NoError(t, err)
  218. assert.Equal(t, testFileSize, size)
  219. err = client.ChangeDirToParent()
  220. assert.NoError(t, err)
  221. curDir, err = client.CurrentDir()
  222. if assert.NoError(t, err) {
  223. assert.Equal(t, "/", curDir)
  224. }
  225. err = client.Delete(path.Join("/", testDir, testFileName))
  226. assert.NoError(t, err)
  227. err = client.Delete(testDir)
  228. assert.Error(t, err)
  229. err = client.RemoveDir(testDir)
  230. assert.NoError(t, err)
  231. err = os.Remove(testFilePath)
  232. assert.NoError(t, err)
  233. err = os.Remove(localDownloadPath)
  234. assert.NoError(t, err)
  235. err = client.Quit()
  236. assert.NoError(t, err)
  237. }
  238. _, err = httpd.RemoveUser(user, http.StatusOK)
  239. assert.NoError(t, err)
  240. err = os.RemoveAll(user.GetHomeDir())
  241. assert.NoError(t, err)
  242. assert.Eventually(t, func() bool { return len(common.Connections.GetStats()) == 0 }, 1*time.Second, 50*time.Millisecond)
  243. }
  244. func TestLoginInvalidPwd(t *testing.T) {
  245. u := getTestUser()
  246. user, _, err := httpd.AddUser(u, http.StatusOK)
  247. assert.NoError(t, err)
  248. user.Password = "wrong"
  249. _, err = getFTPClient(user, false)
  250. assert.Error(t, err)
  251. _, err = httpd.RemoveUser(user, http.StatusOK)
  252. assert.NoError(t, err)
  253. }
  254. func TestLoginExternalAuth(t *testing.T) {
  255. if runtime.GOOS == osWindows {
  256. t.Skip("this test is not available on Windows")
  257. }
  258. u := getTestUser()
  259. err := dataprovider.Close()
  260. assert.NoError(t, err)
  261. err = config.LoadConfig(configDir, "")
  262. assert.NoError(t, err)
  263. providerConf := config.GetProviderConf()
  264. err = ioutil.WriteFile(extAuthPath, getExtAuthScriptContent(u, false, ""), os.ModePerm)
  265. assert.NoError(t, err)
  266. providerConf.ExternalAuthHook = extAuthPath
  267. providerConf.ExternalAuthScope = 0
  268. err = dataprovider.Initialize(providerConf, configDir)
  269. assert.NoError(t, err)
  270. client, err := getFTPClient(u, true)
  271. if assert.NoError(t, err) {
  272. err = checkBasicFTP(client)
  273. assert.NoError(t, err)
  274. err := client.Quit()
  275. assert.NoError(t, err)
  276. }
  277. u.Username = defaultUsername + "1"
  278. client, err = getFTPClient(u, true)
  279. if !assert.Error(t, err) {
  280. err := client.Quit()
  281. assert.NoError(t, err)
  282. }
  283. users, _, err := httpd.GetUsers(0, 0, defaultUsername, http.StatusOK)
  284. assert.NoError(t, err)
  285. if assert.Len(t, users, 1) {
  286. user := users[0]
  287. assert.Equal(t, defaultUsername, user.Username)
  288. _, err = httpd.RemoveUser(user, http.StatusOK)
  289. assert.NoError(t, err)
  290. err = os.RemoveAll(user.GetHomeDir())
  291. assert.NoError(t, err)
  292. }
  293. err = dataprovider.Close()
  294. assert.NoError(t, err)
  295. err = config.LoadConfig(configDir, "")
  296. assert.NoError(t, err)
  297. providerConf = config.GetProviderConf()
  298. err = dataprovider.Initialize(providerConf, configDir)
  299. assert.NoError(t, err)
  300. err = os.Remove(extAuthPath)
  301. assert.NoError(t, err)
  302. }
  303. func TestPreLoginHook(t *testing.T) {
  304. if runtime.GOOS == osWindows {
  305. t.Skip("this test is not available on Windows")
  306. }
  307. u := getTestUser()
  308. err := dataprovider.Close()
  309. assert.NoError(t, err)
  310. err = config.LoadConfig(configDir, "")
  311. assert.NoError(t, err)
  312. providerConf := config.GetProviderConf()
  313. err = ioutil.WriteFile(preLoginPath, getPreLoginScriptContent(u, false), os.ModePerm)
  314. assert.NoError(t, err)
  315. providerConf.PreLoginHook = preLoginPath
  316. err = dataprovider.Initialize(providerConf, configDir)
  317. assert.NoError(t, err)
  318. users, _, err := httpd.GetUsers(0, 0, defaultUsername, http.StatusOK)
  319. assert.NoError(t, err)
  320. assert.Equal(t, 0, len(users))
  321. client, err := getFTPClient(u, false)
  322. if assert.NoError(t, err) {
  323. err = checkBasicFTP(client)
  324. assert.NoError(t, err)
  325. err := client.Quit()
  326. assert.NoError(t, err)
  327. }
  328. users, _, err = httpd.GetUsers(0, 0, defaultUsername, http.StatusOK)
  329. assert.NoError(t, err)
  330. assert.Equal(t, 1, len(users))
  331. user := users[0]
  332. // test login with an existing user
  333. client, err = getFTPClient(user, true)
  334. if assert.NoError(t, err) {
  335. err = checkBasicFTP(client)
  336. assert.NoError(t, err)
  337. err := client.Quit()
  338. assert.NoError(t, err)
  339. }
  340. err = ioutil.WriteFile(preLoginPath, getPreLoginScriptContent(user, true), os.ModePerm)
  341. assert.NoError(t, err)
  342. client, err = getFTPClient(u, false)
  343. if !assert.Error(t, err) {
  344. err := client.Quit()
  345. assert.NoError(t, err)
  346. }
  347. user.Status = 0
  348. err = ioutil.WriteFile(preLoginPath, getPreLoginScriptContent(user, false), os.ModePerm)
  349. assert.NoError(t, err)
  350. client, err = getFTPClient(u, false)
  351. if !assert.Error(t, err, "pre-login script returned a disabled user, login must fail") {
  352. err := client.Quit()
  353. assert.NoError(t, err)
  354. }
  355. _, err = httpd.RemoveUser(user, http.StatusOK)
  356. assert.NoError(t, err)
  357. err = os.RemoveAll(user.GetHomeDir())
  358. assert.NoError(t, err)
  359. err = dataprovider.Close()
  360. assert.NoError(t, err)
  361. err = config.LoadConfig(configDir, "")
  362. assert.NoError(t, err)
  363. providerConf = config.GetProviderConf()
  364. err = dataprovider.Initialize(providerConf, configDir)
  365. assert.NoError(t, err)
  366. err = os.Remove(preLoginPath)
  367. assert.NoError(t, err)
  368. }
  369. func TestPostConnectHook(t *testing.T) {
  370. if runtime.GOOS == osWindows {
  371. t.Skip("this test is not available on Windows")
  372. }
  373. common.Config.PostConnectHook = postConnectPath
  374. u := getTestUser()
  375. user, _, err := httpd.AddUser(u, http.StatusOK)
  376. assert.NoError(t, err)
  377. err = ioutil.WriteFile(postConnectPath, getPostConnectScriptContent(0), os.ModePerm)
  378. assert.NoError(t, err)
  379. client, err := getFTPClient(user, true)
  380. if assert.NoError(t, err) {
  381. err = checkBasicFTP(client)
  382. assert.NoError(t, err)
  383. err := client.Quit()
  384. assert.NoError(t, err)
  385. }
  386. err = ioutil.WriteFile(postConnectPath, getPostConnectScriptContent(1), os.ModePerm)
  387. assert.NoError(t, err)
  388. client, err = getFTPClient(user, true)
  389. if !assert.Error(t, err) {
  390. err := client.Quit()
  391. assert.NoError(t, err)
  392. }
  393. common.Config.PostConnectHook = "http://127.0.0.1:8079/api/v1/version"
  394. client, err = getFTPClient(user, false)
  395. if assert.NoError(t, err) {
  396. err = checkBasicFTP(client)
  397. assert.NoError(t, err)
  398. err := client.Quit()
  399. assert.NoError(t, err)
  400. }
  401. common.Config.PostConnectHook = "http://127.0.0.1:8079/notfound"
  402. client, err = getFTPClient(user, true)
  403. if !assert.Error(t, err) {
  404. err := client.Quit()
  405. assert.NoError(t, err)
  406. }
  407. _, err = httpd.RemoveUser(user, http.StatusOK)
  408. assert.NoError(t, err)
  409. err = os.RemoveAll(user.GetHomeDir())
  410. assert.NoError(t, err)
  411. common.Config.PostConnectHook = ""
  412. }
  413. func TestMaxSessions(t *testing.T) {
  414. u := getTestUser()
  415. u.MaxSessions = 1
  416. user, _, err := httpd.AddUser(u, http.StatusOK)
  417. assert.NoError(t, err)
  418. client, err := getFTPClient(user, true)
  419. if assert.NoError(t, err) {
  420. err = checkBasicFTP(client)
  421. assert.NoError(t, err)
  422. _, err = getFTPClient(user, false)
  423. assert.Error(t, err)
  424. err = client.Quit()
  425. assert.NoError(t, err)
  426. }
  427. _, err = httpd.RemoveUser(user, http.StatusOK)
  428. assert.NoError(t, err)
  429. err = os.RemoveAll(user.GetHomeDir())
  430. assert.NoError(t, err)
  431. }
  432. func TestZeroBytesTransfers(t *testing.T) {
  433. u := getTestUser()
  434. user, _, err := httpd.AddUser(u, http.StatusOK)
  435. assert.NoError(t, err)
  436. for _, useTLS := range []bool{true, false} {
  437. client, err := getFTPClient(user, useTLS)
  438. if assert.NoError(t, err) {
  439. testFileName := "testfilename"
  440. err = checkBasicFTP(client)
  441. assert.NoError(t, err)
  442. localDownloadPath := filepath.Join(homeBasePath, "empty_download")
  443. err = ioutil.WriteFile(localDownloadPath, []byte(""), os.ModePerm)
  444. assert.NoError(t, err)
  445. err = ftpUploadFile(localDownloadPath, testFileName, 0, client, 0)
  446. assert.NoError(t, err)
  447. size, err := client.FileSize(testFileName)
  448. assert.NoError(t, err)
  449. assert.Equal(t, int64(0), size)
  450. err = os.Remove(localDownloadPath)
  451. assert.NoError(t, err)
  452. assert.NoFileExists(t, localDownloadPath)
  453. err = ftpDownloadFile(testFileName, localDownloadPath, 0, client, 0)
  454. assert.NoError(t, err)
  455. assert.FileExists(t, localDownloadPath)
  456. err = client.Quit()
  457. assert.NoError(t, err)
  458. err = os.Remove(localDownloadPath)
  459. assert.NoError(t, err)
  460. }
  461. }
  462. _, err = httpd.RemoveUser(user, http.StatusOK)
  463. assert.NoError(t, err)
  464. err = os.RemoveAll(user.GetHomeDir())
  465. assert.NoError(t, err)
  466. }
  467. func TestDownloadErrors(t *testing.T) {
  468. u := getTestUser()
  469. u.QuotaFiles = 1
  470. subDir1 := "sub1"
  471. subDir2 := "sub2"
  472. u.Permissions[path.Join("/", subDir1)] = []string{dataprovider.PermListItems}
  473. u.Permissions[path.Join("/", subDir2)] = []string{dataprovider.PermListItems, dataprovider.PermUpload,
  474. dataprovider.PermDelete, dataprovider.PermDownload}
  475. u.Filters.FileExtensions = []dataprovider.ExtensionsFilter{
  476. {
  477. Path: "/sub2",
  478. AllowedExtensions: []string{},
  479. DeniedExtensions: []string{".zip"},
  480. },
  481. }
  482. user, _, err := httpd.AddUser(u, http.StatusOK)
  483. assert.NoError(t, err)
  484. client, err := getFTPClient(user, true)
  485. if assert.NoError(t, err) {
  486. testFilePath1 := filepath.Join(user.HomeDir, subDir1, "file.zip")
  487. testFilePath2 := filepath.Join(user.HomeDir, subDir2, "file.zip")
  488. err = os.MkdirAll(filepath.Dir(testFilePath1), os.ModePerm)
  489. assert.NoError(t, err)
  490. err = os.MkdirAll(filepath.Dir(testFilePath2), os.ModePerm)
  491. assert.NoError(t, err)
  492. err = ioutil.WriteFile(testFilePath1, []byte("file1"), os.ModePerm)
  493. assert.NoError(t, err)
  494. err = ioutil.WriteFile(testFilePath2, []byte("file2"), os.ModePerm)
  495. assert.NoError(t, err)
  496. localDownloadPath := filepath.Join(homeBasePath, testDLFileName)
  497. err = ftpDownloadFile(path.Join("/", subDir1, "file.zip"), localDownloadPath, 5, client, 0)
  498. assert.Error(t, err)
  499. err = ftpDownloadFile(path.Join("/", subDir2, "file.zip"), localDownloadPath, 5, client, 0)
  500. assert.Error(t, err)
  501. err = ftpDownloadFile("/missing.zip", localDownloadPath, 5, client, 0)
  502. assert.Error(t, err)
  503. err = client.Quit()
  504. assert.NoError(t, err)
  505. err = os.Remove(localDownloadPath)
  506. assert.NoError(t, err)
  507. }
  508. _, err = httpd.RemoveUser(user, http.StatusOK)
  509. assert.NoError(t, err)
  510. err = os.RemoveAll(user.GetHomeDir())
  511. assert.NoError(t, err)
  512. }
  513. func TestUploadErrors(t *testing.T) {
  514. u := getTestUser()
  515. u.QuotaSize = 65535
  516. subDir1 := "sub1"
  517. subDir2 := "sub2"
  518. u.Permissions[path.Join("/", subDir1)] = []string{dataprovider.PermListItems}
  519. u.Permissions[path.Join("/", subDir2)] = []string{dataprovider.PermListItems, dataprovider.PermUpload,
  520. dataprovider.PermDelete}
  521. u.Filters.FileExtensions = []dataprovider.ExtensionsFilter{
  522. {
  523. Path: "/sub2",
  524. AllowedExtensions: []string{},
  525. DeniedExtensions: []string{".zip"},
  526. },
  527. }
  528. user, _, err := httpd.AddUser(u, http.StatusOK)
  529. assert.NoError(t, err)
  530. client, err := getFTPClient(user, true)
  531. if assert.NoError(t, err) {
  532. testFilePath := filepath.Join(homeBasePath, testFileName)
  533. testFileSize := user.QuotaSize
  534. err = createTestFile(testFilePath, testFileSize)
  535. assert.NoError(t, err)
  536. err = client.MakeDir(subDir1)
  537. assert.NoError(t, err)
  538. err = client.MakeDir(subDir2)
  539. assert.NoError(t, err)
  540. err = client.ChangeDir(subDir1)
  541. assert.NoError(t, err)
  542. err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
  543. assert.Error(t, err)
  544. err = client.ChangeDirToParent()
  545. assert.NoError(t, err)
  546. err = client.ChangeDir(subDir2)
  547. assert.NoError(t, err)
  548. err = ftpUploadFile(testFilePath, testFileName+".zip", testFileSize, client, 0)
  549. assert.Error(t, err)
  550. err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
  551. assert.NoError(t, err)
  552. err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
  553. assert.Error(t, err)
  554. err = client.ChangeDir("/")
  555. assert.NoError(t, err)
  556. err = ftpUploadFile(testFilePath, subDir1, testFileSize, client, 0)
  557. assert.Error(t, err)
  558. // overquota
  559. err = ftpUploadFile(testFilePath, testFileName+"1", testFileSize, client, 0)
  560. assert.Error(t, err)
  561. err = client.Delete(path.Join("/", subDir2, testFileName))
  562. assert.NoError(t, err)
  563. err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
  564. assert.NoError(t, err)
  565. err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
  566. assert.Error(t, err)
  567. err = client.Quit()
  568. assert.NoError(t, err)
  569. err = os.Remove(testFilePath)
  570. assert.NoError(t, err)
  571. }
  572. _, err = httpd.RemoveUser(user, http.StatusOK)
  573. assert.NoError(t, err)
  574. err = os.RemoveAll(user.GetHomeDir())
  575. assert.NoError(t, err)
  576. }
  577. func TestResume(t *testing.T) {
  578. u := getTestUser()
  579. user, _, err := httpd.AddUser(u, http.StatusOK)
  580. assert.NoError(t, err)
  581. client, err := getFTPClient(user, true)
  582. if assert.NoError(t, err) {
  583. testFilePath := filepath.Join(homeBasePath, testFileName)
  584. data := []byte("test data")
  585. err = ioutil.WriteFile(testFilePath, data, os.ModePerm)
  586. assert.NoError(t, err)
  587. err = ftpUploadFile(testFilePath, testFileName, int64(len(data)), client, 0)
  588. assert.NoError(t, err)
  589. err = ftpUploadFile(testFilePath, testFileName, int64(len(data)+5), client, 5)
  590. assert.NoError(t, err)
  591. readed, err := ioutil.ReadFile(filepath.Join(user.GetHomeDir(), testFileName))
  592. assert.NoError(t, err)
  593. assert.Equal(t, "test test data", string(readed))
  594. localDownloadPath := filepath.Join(homeBasePath, testDLFileName)
  595. err = ftpDownloadFile(testFileName, localDownloadPath, int64(len(data)), client, 5)
  596. assert.NoError(t, err)
  597. readed, err = ioutil.ReadFile(localDownloadPath)
  598. assert.NoError(t, err)
  599. assert.Equal(t, data, readed)
  600. err = client.Delete(testFileName)
  601. assert.NoError(t, err)
  602. err = ftpUploadFile(testFilePath, testFileName, int64(len(data)), client, 0)
  603. assert.NoError(t, err)
  604. // now append to a file
  605. srcFile, err := os.Open(testFilePath)
  606. if assert.NoError(t, err) {
  607. err = client.Append(testFileName, srcFile)
  608. assert.NoError(t, err)
  609. err = srcFile.Close()
  610. assert.NoError(t, err)
  611. size, err := client.FileSize(testFileName)
  612. assert.NoError(t, err)
  613. assert.Equal(t, int64(2*len(data)), size)
  614. err = ftpDownloadFile(testFileName, localDownloadPath, int64(2*len(data)), client, 0)
  615. assert.NoError(t, err)
  616. readed, err = ioutil.ReadFile(localDownloadPath)
  617. assert.NoError(t, err)
  618. expected := append(data, data...)
  619. assert.Equal(t, expected, readed)
  620. }
  621. err = client.Quit()
  622. assert.NoError(t, err)
  623. err = os.Remove(testFilePath)
  624. assert.NoError(t, err)
  625. err = os.Remove(localDownloadPath)
  626. assert.NoError(t, err)
  627. }
  628. _, err = httpd.RemoveUser(user, http.StatusOK)
  629. assert.NoError(t, err)
  630. err = os.RemoveAll(user.GetHomeDir())
  631. assert.NoError(t, err)
  632. }
  633. //nolint:dupl
  634. func TestDeniedLoginMethod(t *testing.T) {
  635. u := getTestUser()
  636. u.Filters.DeniedLoginMethods = []string{dataprovider.LoginMethodPassword}
  637. user, _, err := httpd.AddUser(u, http.StatusOK)
  638. assert.NoError(t, err)
  639. _, err = getFTPClient(user, false)
  640. assert.Error(t, err)
  641. user.Filters.DeniedLoginMethods = []string{dataprovider.SSHLoginMethodPublicKey, dataprovider.SSHLoginMethodKeyAndPassword}
  642. user, _, err = httpd.UpdateUser(user, http.StatusOK, "")
  643. assert.NoError(t, err)
  644. client, err := getFTPClient(user, true)
  645. if assert.NoError(t, err) {
  646. assert.NoError(t, checkBasicFTP(client))
  647. err = client.Quit()
  648. assert.NoError(t, err)
  649. }
  650. _, err = httpd.RemoveUser(user, http.StatusOK)
  651. assert.NoError(t, err)
  652. err = os.RemoveAll(user.GetHomeDir())
  653. assert.NoError(t, err)
  654. }
  655. //nolint:dupl
  656. func TestDeniedProtocols(t *testing.T) {
  657. u := getTestUser()
  658. u.Filters.DeniedProtocols = []string{common.ProtocolFTP}
  659. user, _, err := httpd.AddUser(u, http.StatusOK)
  660. assert.NoError(t, err)
  661. _, err = getFTPClient(user, false)
  662. assert.Error(t, err)
  663. user.Filters.DeniedProtocols = []string{common.ProtocolSSH, common.ProtocolWebDAV}
  664. user, _, err = httpd.UpdateUser(user, http.StatusOK, "")
  665. assert.NoError(t, err)
  666. client, err := getFTPClient(user, true)
  667. if assert.NoError(t, err) {
  668. assert.NoError(t, checkBasicFTP(client))
  669. err = client.Quit()
  670. assert.NoError(t, err)
  671. }
  672. _, err = httpd.RemoveUser(user, http.StatusOK)
  673. assert.NoError(t, err)
  674. err = os.RemoveAll(user.GetHomeDir())
  675. assert.NoError(t, err)
  676. }
  677. func TestQuotaLimits(t *testing.T) {
  678. u := getTestUser()
  679. u.QuotaFiles = 1
  680. user, _, err := httpd.AddUser(u, http.StatusOK)
  681. assert.NoError(t, err)
  682. testFileSize := int64(65535)
  683. testFilePath := filepath.Join(homeBasePath, testFileName)
  684. err = createTestFile(testFilePath, testFileSize)
  685. assert.NoError(t, err)
  686. testFileSize1 := int64(131072)
  687. testFileName1 := "test_file1.dat"
  688. testFilePath1 := filepath.Join(homeBasePath, testFileName1)
  689. err = createTestFile(testFilePath1, testFileSize1)
  690. assert.NoError(t, err)
  691. testFileSize2 := int64(32768)
  692. testFileName2 := "test_file2.dat"
  693. testFilePath2 := filepath.Join(homeBasePath, testFileName2)
  694. err = createTestFile(testFilePath2, testFileSize2)
  695. assert.NoError(t, err)
  696. // test quota files
  697. client, err := getFTPClient(user, false)
  698. if assert.NoError(t, err) {
  699. err = ftpUploadFile(testFilePath, testFileName+".quota", testFileSize, client, 0)
  700. assert.NoError(t, err)
  701. err = ftpUploadFile(testFilePath, testFileName+".quota1", testFileSize, client, 0)
  702. assert.Error(t, err)
  703. err = client.Rename(testFileName+".quota", testFileName)
  704. assert.NoError(t, err)
  705. err = client.Quit()
  706. assert.NoError(t, err)
  707. }
  708. // test quota size
  709. user.QuotaSize = testFileSize - 1
  710. user.QuotaFiles = 0
  711. user, _, err = httpd.UpdateUser(user, http.StatusOK, "")
  712. assert.NoError(t, err)
  713. client, err = getFTPClient(user, true)
  714. if assert.NoError(t, err) {
  715. err = ftpUploadFile(testFilePath, testFileName+".quota", testFileSize, client, 0)
  716. assert.Error(t, err)
  717. err = client.Rename(testFileName, testFileName+".quota")
  718. assert.NoError(t, err)
  719. err = client.Quit()
  720. assert.NoError(t, err)
  721. }
  722. // now test quota limits while uploading the current file, we have 1 bytes remaining
  723. user.QuotaSize = testFileSize + 1
  724. user.QuotaFiles = 0
  725. user, _, err = httpd.UpdateUser(user, http.StatusOK, "")
  726. assert.NoError(t, err)
  727. client, err = getFTPClient(user, false)
  728. if assert.NoError(t, err) {
  729. err = ftpUploadFile(testFilePath1, testFileName1, testFileSize1, client, 0)
  730. assert.Error(t, err)
  731. _, err = client.FileSize(testFileName1)
  732. assert.Error(t, err)
  733. err = client.Rename(testFileName+".quota", testFileName)
  734. assert.NoError(t, err)
  735. // overwriting an existing file will work if the resulting size is lesser or equal than the current one
  736. err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
  737. assert.NoError(t, err)
  738. err = ftpUploadFile(testFilePath2, testFileName, testFileSize2, client, 0)
  739. assert.NoError(t, err)
  740. err = ftpUploadFile(testFilePath1, testFileName, testFileSize1, client, 0)
  741. assert.Error(t, err)
  742. err = ftpUploadFile(testFilePath1, testFileName, testFileSize1, client, 10)
  743. assert.Error(t, err)
  744. err = ftpUploadFile(testFilePath2, testFileName, testFileSize2, client, 0)
  745. assert.NoError(t, err)
  746. err = client.Quit()
  747. assert.NoError(t, err)
  748. }
  749. err = os.Remove(testFilePath)
  750. assert.NoError(t, err)
  751. err = os.Remove(testFilePath1)
  752. assert.NoError(t, err)
  753. err = os.Remove(testFilePath2)
  754. assert.NoError(t, err)
  755. _, err = httpd.RemoveUser(user, http.StatusOK)
  756. assert.NoError(t, err)
  757. err = os.RemoveAll(user.GetHomeDir())
  758. assert.NoError(t, err)
  759. }
  760. func TestUploadMaxSize(t *testing.T) {
  761. testFileSize := int64(65535)
  762. u := getTestUser()
  763. u.Filters.MaxUploadFileSize = testFileSize + 1
  764. user, _, err := httpd.AddUser(u, http.StatusOK)
  765. assert.NoError(t, err)
  766. testFilePath := filepath.Join(homeBasePath, testFileName)
  767. err = createTestFile(testFilePath, testFileSize)
  768. assert.NoError(t, err)
  769. testFileSize1 := int64(131072)
  770. testFileName1 := "test_file1.dat"
  771. testFilePath1 := filepath.Join(homeBasePath, testFileName1)
  772. err = createTestFile(testFilePath1, testFileSize1)
  773. assert.NoError(t, err)
  774. client, err := getFTPClient(user, false)
  775. if assert.NoError(t, err) {
  776. err = ftpUploadFile(testFilePath1, testFileName1, testFileSize1, client, 0)
  777. assert.Error(t, err)
  778. err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
  779. assert.NoError(t, err)
  780. err = client.Quit()
  781. assert.NoError(t, err)
  782. }
  783. err = os.Remove(testFilePath)
  784. assert.NoError(t, err)
  785. err = os.Remove(testFilePath1)
  786. assert.NoError(t, err)
  787. _, err = httpd.RemoveUser(user, http.StatusOK)
  788. assert.NoError(t, err)
  789. err = os.RemoveAll(user.GetHomeDir())
  790. assert.NoError(t, err)
  791. }
  792. func TestLoginWithIPilters(t *testing.T) {
  793. u := getTestUser()
  794. u.Filters.DeniedIP = []string{"192.167.0.0/24", "172.18.0.0/16"}
  795. u.Filters.AllowedIP = []string{"172.19.0.0/16"}
  796. user, _, err := httpd.AddUser(u, http.StatusOK)
  797. assert.NoError(t, err)
  798. client, err := getFTPClient(user, true)
  799. if !assert.Error(t, err) {
  800. err = client.Quit()
  801. assert.NoError(t, err)
  802. }
  803. _, err = httpd.RemoveUser(user, http.StatusOK)
  804. assert.NoError(t, err)
  805. err = os.RemoveAll(user.GetHomeDir())
  806. assert.NoError(t, err)
  807. }
  808. func TestLoginWithDatabaseCredentials(t *testing.T) {
  809. u := getTestUser()
  810. u.FsConfig.Provider = dataprovider.GCSFilesystemProvider
  811. u.FsConfig.GCSConfig.Bucket = "test"
  812. u.FsConfig.GCSConfig.Credentials = []byte(`{ "type": "service_account" }`)
  813. providerConf := config.GetProviderConf()
  814. providerConf.PreferDatabaseCredentials = true
  815. credentialsFile := filepath.Join(providerConf.CredentialsPath, fmt.Sprintf("%v_gcs_credentials.json", u.Username))
  816. if !filepath.IsAbs(credentialsFile) {
  817. credentialsFile = filepath.Join(configDir, credentialsFile)
  818. }
  819. assert.NoError(t, dataprovider.Close())
  820. err := dataprovider.Initialize(providerConf, configDir)
  821. assert.NoError(t, err)
  822. if _, err = os.Stat(credentialsFile); err == nil {
  823. // remove the credentials file
  824. assert.NoError(t, os.Remove(credentialsFile))
  825. }
  826. user, _, err := httpd.AddUser(u, http.StatusOK)
  827. assert.NoError(t, err)
  828. _, err = os.Stat(credentialsFile)
  829. assert.Error(t, err)
  830. client, err := getFTPClient(user, false)
  831. if assert.NoError(t, err) {
  832. err = client.Quit()
  833. assert.NoError(t, err)
  834. }
  835. _, err = httpd.RemoveUser(user, http.StatusOK)
  836. assert.NoError(t, err)
  837. err = os.RemoveAll(user.GetHomeDir())
  838. assert.NoError(t, err)
  839. assert.NoError(t, dataprovider.Close())
  840. assert.NoError(t, config.LoadConfig(configDir, ""))
  841. providerConf = config.GetProviderConf()
  842. assert.NoError(t, dataprovider.Initialize(providerConf, configDir))
  843. }
  844. func TestLoginInvalidFs(t *testing.T) {
  845. u := getTestUser()
  846. u.FsConfig.Provider = dataprovider.GCSFilesystemProvider
  847. u.FsConfig.GCSConfig.Bucket = "test"
  848. u.FsConfig.GCSConfig.Credentials = []byte("invalid JSON for credentials")
  849. user, _, err := httpd.AddUser(u, http.StatusOK)
  850. assert.NoError(t, err)
  851. providerConf := config.GetProviderConf()
  852. credentialsFile := filepath.Join(providerConf.CredentialsPath, fmt.Sprintf("%v_gcs_credentials.json", u.Username))
  853. if !filepath.IsAbs(credentialsFile) {
  854. credentialsFile = filepath.Join(configDir, credentialsFile)
  855. }
  856. // now remove the credentials file so the filesystem creation will fail
  857. err = os.Remove(credentialsFile)
  858. assert.NoError(t, err)
  859. client, err := getFTPClient(user, false)
  860. if !assert.Error(t, err) {
  861. err = client.Quit()
  862. assert.NoError(t, err)
  863. }
  864. _, err = httpd.RemoveUser(user, http.StatusOK)
  865. assert.NoError(t, err)
  866. err = os.RemoveAll(user.GetHomeDir())
  867. assert.NoError(t, err)
  868. }
  869. func TestClientClose(t *testing.T) {
  870. u := getTestUser()
  871. user, _, err := httpd.AddUser(u, http.StatusOK)
  872. assert.NoError(t, err)
  873. client, err := getFTPClient(user, true)
  874. if assert.NoError(t, err) {
  875. err = checkBasicFTP(client)
  876. assert.NoError(t, err)
  877. stats := common.Connections.GetStats()
  878. if assert.Len(t, stats, 1) {
  879. common.Connections.Close(stats[0].ConnectionID)
  880. assert.Eventually(t, func() bool { return len(common.Connections.GetStats()) == 0 },
  881. 1*time.Second, 50*time.Millisecond)
  882. }
  883. }
  884. _, err = httpd.RemoveUser(user, http.StatusOK)
  885. assert.NoError(t, err)
  886. err = os.RemoveAll(user.GetHomeDir())
  887. assert.NoError(t, err)
  888. }
  889. func TestRename(t *testing.T) {
  890. u := getTestUser()
  891. user, _, err := httpd.AddUser(u, http.StatusOK)
  892. assert.NoError(t, err)
  893. testDir := "adir"
  894. testFilePath := filepath.Join(homeBasePath, testFileName)
  895. testFileSize := int64(65535)
  896. err = createTestFile(testFilePath, testFileSize)
  897. assert.NoError(t, err)
  898. client, err := getFTPClient(user, false)
  899. if assert.NoError(t, err) {
  900. err = checkBasicFTP(client)
  901. assert.NoError(t, err)
  902. err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
  903. assert.NoError(t, err)
  904. err = client.MakeDir(testDir)
  905. assert.NoError(t, err)
  906. err = client.Rename(testFileName, path.Join("missing", testFileName))
  907. assert.Error(t, err)
  908. err = client.Rename(testFileName, path.Join(testDir, testFileName))
  909. assert.NoError(t, err)
  910. size, err := client.FileSize(path.Join(testDir, testFileName))
  911. assert.NoError(t, err)
  912. assert.Equal(t, testFileSize, size)
  913. if runtime.GOOS != osWindows {
  914. otherDir := "dir"
  915. err = client.MakeDir(otherDir)
  916. assert.NoError(t, err)
  917. err = client.MakeDir(path.Join(otherDir, testDir))
  918. assert.NoError(t, err)
  919. code, response, err := client.SendCustomCommand(fmt.Sprintf("SITE CHMOD 0001 %v", otherDir))
  920. assert.NoError(t, err)
  921. assert.Equal(t, ftp.StatusCommandOK, code)
  922. assert.Equal(t, "SITE CHMOD command successful", response)
  923. err = client.Rename(testDir, path.Join(otherDir, testDir))
  924. assert.Error(t, err)
  925. code, response, err = client.SendCustomCommand(fmt.Sprintf("SITE CHMOD 755 %v", otherDir))
  926. assert.NoError(t, err)
  927. assert.Equal(t, ftp.StatusCommandOK, code)
  928. assert.Equal(t, "SITE CHMOD command successful", response)
  929. }
  930. err = client.Quit()
  931. assert.NoError(t, err)
  932. }
  933. user.Permissions[path.Join("/", testDir)] = []string{dataprovider.PermListItems}
  934. user, _, err = httpd.UpdateUser(user, http.StatusOK, "")
  935. assert.NoError(t, err)
  936. client, err = getFTPClient(user, false)
  937. if assert.NoError(t, err) {
  938. err = client.Rename(path.Join(testDir, testFileName), testFileName)
  939. assert.Error(t, err)
  940. err := client.Quit()
  941. assert.NoError(t, err)
  942. }
  943. err = os.Remove(testFilePath)
  944. assert.NoError(t, err)
  945. _, err = httpd.RemoveUser(user, http.StatusOK)
  946. assert.NoError(t, err)
  947. err = os.RemoveAll(user.GetHomeDir())
  948. assert.NoError(t, err)
  949. }
  950. func TestSymlink(t *testing.T) {
  951. u := getTestUser()
  952. user, _, err := httpd.AddUser(u, http.StatusOK)
  953. assert.NoError(t, err)
  954. testFilePath := filepath.Join(homeBasePath, testFileName)
  955. testFileSize := int64(65535)
  956. err = createTestFile(testFilePath, testFileSize)
  957. assert.NoError(t, err)
  958. client, err := getFTPClient(user, false)
  959. if assert.NoError(t, err) {
  960. err = checkBasicFTP(client)
  961. assert.NoError(t, err)
  962. err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
  963. assert.NoError(t, err)
  964. code, _, err := client.SendCustomCommand(fmt.Sprintf("SITE SYMLINK %v %v", testFileName, testFileName+".link"))
  965. assert.NoError(t, err)
  966. assert.Equal(t, ftp.StatusCommandOK, code)
  967. if runtime.GOOS != osWindows {
  968. testDir := "adir"
  969. otherDir := "dir"
  970. err = client.MakeDir(otherDir)
  971. assert.NoError(t, err)
  972. err = client.MakeDir(path.Join(otherDir, testDir))
  973. assert.NoError(t, err)
  974. code, response, err := client.SendCustomCommand(fmt.Sprintf("SITE CHMOD 0001 %v", otherDir))
  975. assert.NoError(t, err)
  976. assert.Equal(t, ftp.StatusCommandOK, code)
  977. assert.Equal(t, "SITE CHMOD command successful", response)
  978. code, _, err = client.SendCustomCommand(fmt.Sprintf("SITE SYMLINK %v %v", testDir, path.Join(otherDir, testDir)))
  979. assert.NoError(t, err)
  980. assert.Equal(t, ftp.StatusFileUnavailable, code)
  981. code, response, err = client.SendCustomCommand(fmt.Sprintf("SITE CHMOD 755 %v", otherDir))
  982. assert.NoError(t, err)
  983. assert.Equal(t, ftp.StatusCommandOK, code)
  984. assert.Equal(t, "SITE CHMOD command successful", response)
  985. }
  986. err = client.Quit()
  987. assert.NoError(t, err)
  988. }
  989. err = os.Remove(testFilePath)
  990. assert.NoError(t, err)
  991. _, err = httpd.RemoveUser(user, http.StatusOK)
  992. assert.NoError(t, err)
  993. err = os.RemoveAll(user.GetHomeDir())
  994. assert.NoError(t, err)
  995. }
  996. func TestStat(t *testing.T) {
  997. u := getTestUser()
  998. u.Permissions["/subdir"] = []string{dataprovider.PermUpload}
  999. user, _, err := httpd.AddUser(u, http.StatusOK)
  1000. assert.NoError(t, err)
  1001. client, err := getFTPClient(user, false)
  1002. if assert.NoError(t, err) {
  1003. subDir := "subdir"
  1004. testFilePath := filepath.Join(homeBasePath, testFileName)
  1005. testFileSize := int64(65535)
  1006. err = createTestFile(testFilePath, testFileSize)
  1007. assert.NoError(t, err)
  1008. err = client.MakeDir(subDir)
  1009. assert.NoError(t, err)
  1010. err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
  1011. assert.NoError(t, err)
  1012. err = ftpUploadFile(testFilePath, path.Join("/", subDir, testFileName), testFileSize, client, 0)
  1013. assert.Error(t, err)
  1014. size, err := client.FileSize(testFileName)
  1015. assert.NoError(t, err)
  1016. assert.Equal(t, testFileSize, size)
  1017. _, err = client.FileSize(path.Join("/", subDir, testFileName))
  1018. assert.Error(t, err)
  1019. _, err = client.FileSize("missing file")
  1020. assert.Error(t, err)
  1021. err = client.Quit()
  1022. assert.NoError(t, err)
  1023. err = os.Remove(testFilePath)
  1024. assert.NoError(t, err)
  1025. }
  1026. _, err = httpd.RemoveUser(user, http.StatusOK)
  1027. assert.NoError(t, err)
  1028. err = os.RemoveAll(user.GetHomeDir())
  1029. assert.NoError(t, err)
  1030. }
  1031. func TestUploadOverwriteVfolder(t *testing.T) {
  1032. u := getTestUser()
  1033. vdir := "/vdir"
  1034. mappedPath := filepath.Join(os.TempDir(), "vdir")
  1035. u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
  1036. BaseVirtualFolder: vfs.BaseVirtualFolder{
  1037. MappedPath: mappedPath,
  1038. },
  1039. VirtualPath: vdir,
  1040. QuotaSize: -1,
  1041. QuotaFiles: -1,
  1042. })
  1043. err := os.MkdirAll(mappedPath, os.ModePerm)
  1044. assert.NoError(t, err)
  1045. user, _, err := httpd.AddUser(u, http.StatusOK)
  1046. assert.NoError(t, err)
  1047. client, err := getFTPClient(user, false)
  1048. if assert.NoError(t, err) {
  1049. testFilePath := filepath.Join(homeBasePath, testFileName)
  1050. testFileSize := int64(65535)
  1051. err = createTestFile(testFilePath, testFileSize)
  1052. assert.NoError(t, err)
  1053. err = ftpUploadFile(testFilePath, path.Join(vdir, testFileName), testFileSize, client, 0)
  1054. assert.NoError(t, err)
  1055. folder, _, err := httpd.GetFolders(0, 0, mappedPath, http.StatusOK)
  1056. assert.NoError(t, err)
  1057. if assert.Len(t, folder, 1) {
  1058. f := folder[0]
  1059. assert.Equal(t, testFileSize, f.UsedQuotaSize)
  1060. assert.Equal(t, 1, f.UsedQuotaFiles)
  1061. }
  1062. err = ftpUploadFile(testFilePath, path.Join(vdir, testFileName), testFileSize, client, 0)
  1063. assert.NoError(t, err)
  1064. folder, _, err = httpd.GetFolders(0, 0, mappedPath, http.StatusOK)
  1065. assert.NoError(t, err)
  1066. if assert.Len(t, folder, 1) {
  1067. f := folder[0]
  1068. assert.Equal(t, testFileSize, f.UsedQuotaSize)
  1069. assert.Equal(t, 1, f.UsedQuotaFiles)
  1070. }
  1071. err = client.Quit()
  1072. assert.NoError(t, err)
  1073. err = os.Remove(testFilePath)
  1074. assert.NoError(t, err)
  1075. }
  1076. _, err = httpd.RemoveUser(user, http.StatusOK)
  1077. assert.NoError(t, err)
  1078. _, err = httpd.RemoveFolder(vfs.BaseVirtualFolder{MappedPath: mappedPath}, http.StatusOK)
  1079. assert.NoError(t, err)
  1080. err = os.RemoveAll(user.GetHomeDir())
  1081. assert.NoError(t, err)
  1082. err = os.RemoveAll(mappedPath)
  1083. assert.NoError(t, err)
  1084. }
  1085. func TestAllocate(t *testing.T) {
  1086. u := getTestUser()
  1087. mappedPath := filepath.Join(os.TempDir(), "vdir")
  1088. u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
  1089. BaseVirtualFolder: vfs.BaseVirtualFolder{
  1090. MappedPath: mappedPath,
  1091. },
  1092. VirtualPath: "/vdir",
  1093. QuotaSize: 110,
  1094. })
  1095. err := os.MkdirAll(mappedPath, os.ModePerm)
  1096. assert.NoError(t, err)
  1097. user, _, err := httpd.AddUser(u, http.StatusOK)
  1098. assert.NoError(t, err)
  1099. client, err := getFTPClient(user, false)
  1100. if assert.NoError(t, err) {
  1101. code, response, err := client.SendCustomCommand("allo 2000000")
  1102. assert.NoError(t, err)
  1103. assert.Equal(t, ftp.StatusCommandOK, code)
  1104. assert.Equal(t, "Done !", response)
  1105. err = client.Quit()
  1106. assert.NoError(t, err)
  1107. }
  1108. user.QuotaSize = 100
  1109. user, _, err = httpd.UpdateUser(user, http.StatusOK, "")
  1110. assert.NoError(t, err)
  1111. client, err = getFTPClient(user, false)
  1112. if assert.NoError(t, err) {
  1113. testFilePath := filepath.Join(homeBasePath, testFileName)
  1114. testFileSize := user.QuotaSize - 1
  1115. err = createTestFile(testFilePath, testFileSize)
  1116. assert.NoError(t, err)
  1117. code, response, err := client.SendCustomCommand("allo 99")
  1118. assert.NoError(t, err)
  1119. assert.Equal(t, ftp.StatusCommandOK, code)
  1120. assert.Equal(t, "Done !", response)
  1121. code, response, err = client.SendCustomCommand("allo 100")
  1122. assert.NoError(t, err)
  1123. assert.Equal(t, ftp.StatusCommandOK, code)
  1124. assert.Equal(t, "Done !", response)
  1125. code, response, err = client.SendCustomCommand("allo 150")
  1126. assert.NoError(t, err)
  1127. assert.Equal(t, ftp.StatusFileUnavailable, code)
  1128. assert.Contains(t, response, common.ErrQuotaExceeded.Error())
  1129. err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
  1130. assert.NoError(t, err)
  1131. // we still have space in vdir
  1132. code, response, err = client.SendCustomCommand("allo 50")
  1133. assert.NoError(t, err)
  1134. assert.Equal(t, ftp.StatusCommandOK, code)
  1135. assert.Equal(t, "Done !", response)
  1136. err = ftpUploadFile(testFilePath, path.Join("/vdir", testFileName), testFileSize, client, 0)
  1137. assert.NoError(t, err)
  1138. code, response, err = client.SendCustomCommand("allo 50")
  1139. assert.NoError(t, err)
  1140. assert.Equal(t, ftp.StatusFileUnavailable, code)
  1141. assert.Contains(t, response, common.ErrQuotaExceeded.Error())
  1142. err = client.Quit()
  1143. assert.NoError(t, err)
  1144. err = os.Remove(testFilePath)
  1145. assert.NoError(t, err)
  1146. }
  1147. user.Filters.MaxUploadFileSize = 100
  1148. user.QuotaSize = 0
  1149. user, _, err = httpd.UpdateUser(user, http.StatusOK, "")
  1150. assert.NoError(t, err)
  1151. client, err = getFTPClient(user, false)
  1152. if assert.NoError(t, err) {
  1153. code, response, err := client.SendCustomCommand("allo 99")
  1154. assert.NoError(t, err)
  1155. assert.Equal(t, ftp.StatusCommandOK, code)
  1156. assert.Equal(t, "Done !", response)
  1157. code, response, err = client.SendCustomCommand("allo 150")
  1158. assert.NoError(t, err)
  1159. assert.Equal(t, ftp.StatusFileUnavailable, code)
  1160. assert.Contains(t, response, common.ErrQuotaExceeded.Error())
  1161. err = client.Quit()
  1162. assert.NoError(t, err)
  1163. }
  1164. _, err = httpd.RemoveUser(user, http.StatusOK)
  1165. assert.NoError(t, err)
  1166. _, err = httpd.RemoveFolder(vfs.BaseVirtualFolder{MappedPath: mappedPath}, http.StatusOK)
  1167. assert.NoError(t, err)
  1168. err = os.RemoveAll(user.GetHomeDir())
  1169. assert.NoError(t, err)
  1170. err = os.RemoveAll(mappedPath)
  1171. assert.NoError(t, err)
  1172. }
  1173. func TestChtimes(t *testing.T) {
  1174. u := getTestUser()
  1175. user, _, err := httpd.AddUser(u, http.StatusOK)
  1176. assert.NoError(t, err)
  1177. client, err := getFTPClient(user, false)
  1178. if assert.NoError(t, err) {
  1179. testFilePath := filepath.Join(homeBasePath, testFileName)
  1180. testFileSize := int64(65535)
  1181. err = createTestFile(testFilePath, testFileSize)
  1182. assert.NoError(t, err)
  1183. err = checkBasicFTP(client)
  1184. assert.NoError(t, err)
  1185. err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
  1186. assert.NoError(t, err)
  1187. mtime := time.Now().Format("20060102150405")
  1188. code, response, err := client.SendCustomCommand(fmt.Sprintf("MFMT %v %v", mtime, testFileName))
  1189. assert.NoError(t, err)
  1190. assert.Equal(t, ftp.StatusFile, code)
  1191. assert.Equal(t, fmt.Sprintf("Modify=%v; %v", mtime, testFileName), response)
  1192. err = client.Quit()
  1193. assert.NoError(t, err)
  1194. err = os.Remove(testFilePath)
  1195. assert.NoError(t, err)
  1196. }
  1197. _, err = httpd.RemoveUser(user, http.StatusOK)
  1198. assert.NoError(t, err)
  1199. err = os.RemoveAll(user.GetHomeDir())
  1200. assert.NoError(t, err)
  1201. }
  1202. func TestChmod(t *testing.T) {
  1203. if runtime.GOOS == osWindows {
  1204. t.Skip("chmod is partially supported on Windows")
  1205. }
  1206. u := getTestUser()
  1207. user, _, err := httpd.AddUser(u, http.StatusOK)
  1208. assert.NoError(t, err)
  1209. client, err := getFTPClient(user, true)
  1210. if assert.NoError(t, err) {
  1211. testFilePath := filepath.Join(homeBasePath, testFileName)
  1212. testFileSize := int64(131072)
  1213. err = createTestFile(testFilePath, testFileSize)
  1214. assert.NoError(t, err)
  1215. err = checkBasicFTP(client)
  1216. assert.NoError(t, err)
  1217. err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
  1218. assert.NoError(t, err)
  1219. code, response, err := client.SendCustomCommand(fmt.Sprintf("SITE CHMOD 600 %v", testFileName))
  1220. assert.NoError(t, err)
  1221. assert.Equal(t, ftp.StatusCommandOK, code)
  1222. assert.Equal(t, "SITE CHMOD command successful", response)
  1223. fi, err := os.Stat(filepath.Join(user.HomeDir, testFileName))
  1224. if assert.NoError(t, err) {
  1225. assert.Equal(t, os.FileMode(0600), fi.Mode().Perm())
  1226. }
  1227. err = client.Quit()
  1228. assert.NoError(t, err)
  1229. err = os.Remove(testFilePath)
  1230. assert.NoError(t, err)
  1231. }
  1232. _, err = httpd.RemoveUser(user, http.StatusOK)
  1233. assert.NoError(t, err)
  1234. err = os.RemoveAll(user.GetHomeDir())
  1235. assert.NoError(t, err)
  1236. }
  1237. func checkBasicFTP(client *ftp.ServerConn) error {
  1238. _, err := client.CurrentDir()
  1239. if err != nil {
  1240. return err
  1241. }
  1242. err = client.NoOp()
  1243. if err != nil {
  1244. return err
  1245. }
  1246. _, err = client.List(".")
  1247. if err != nil {
  1248. return err
  1249. }
  1250. return nil
  1251. }
  1252. func ftpUploadFile(localSourcePath string, remoteDestPath string, expectedSize int64, client *ftp.ServerConn, offset uint64) error {
  1253. srcFile, err := os.Open(localSourcePath)
  1254. if err != nil {
  1255. return err
  1256. }
  1257. defer srcFile.Close()
  1258. if offset > 0 {
  1259. err = client.StorFrom(remoteDestPath, srcFile, offset)
  1260. } else {
  1261. err = client.Stor(remoteDestPath, srcFile)
  1262. }
  1263. if err != nil {
  1264. return err
  1265. }
  1266. if expectedSize > 0 {
  1267. size, err := client.FileSize(remoteDestPath)
  1268. if err != nil {
  1269. return err
  1270. }
  1271. if size != expectedSize {
  1272. return fmt.Errorf("uploaded file size does not match, actual: %v, expected: %v", size, expectedSize)
  1273. }
  1274. }
  1275. return nil
  1276. }
  1277. func ftpDownloadFile(remoteSourcePath string, localDestPath string, expectedSize int64, client *ftp.ServerConn, offset uint64) error {
  1278. downloadDest, err := os.Create(localDestPath)
  1279. if err != nil {
  1280. return err
  1281. }
  1282. defer downloadDest.Close()
  1283. var r *ftp.Response
  1284. if offset > 0 {
  1285. r, err = client.RetrFrom(remoteSourcePath, offset)
  1286. } else {
  1287. r, err = client.Retr(remoteSourcePath)
  1288. }
  1289. if err != nil {
  1290. return err
  1291. }
  1292. defer r.Close()
  1293. written, err := io.Copy(downloadDest, r)
  1294. if err != nil {
  1295. return err
  1296. }
  1297. if written != expectedSize {
  1298. return fmt.Errorf("downloaded file size does not match, actual: %v, expected: %v", written, expectedSize)
  1299. }
  1300. return nil
  1301. }
  1302. func getFTPClient(user dataprovider.User, useTLS bool) (*ftp.ServerConn, error) {
  1303. ftpOptions := []ftp.DialOption{ftp.DialWithTimeout(5 * time.Second)}
  1304. if useTLS {
  1305. tlsConfig := &tls.Config{
  1306. ServerName: "localhost",
  1307. InsecureSkipVerify: true, // use this for tests only
  1308. MinVersion: tls.VersionTLS12,
  1309. }
  1310. ftpOptions = append(ftpOptions, ftp.DialWithExplicitTLS(tlsConfig))
  1311. }
  1312. client, err := ftp.Dial(ftpServerAddr, ftpOptions...)
  1313. if err != nil {
  1314. return nil, err
  1315. }
  1316. pwd := defaultPassword
  1317. if len(user.Password) > 0 {
  1318. pwd = user.Password
  1319. }
  1320. err = client.Login(user.Username, pwd)
  1321. if err != nil {
  1322. return nil, err
  1323. }
  1324. return client, err
  1325. }
  1326. func waitTCPListening(address string) {
  1327. for {
  1328. conn, err := net.Dial("tcp", address)
  1329. if err != nil {
  1330. logger.WarnToConsole("tcp server %v not listening: %v\n", address, err)
  1331. time.Sleep(100 * time.Millisecond)
  1332. continue
  1333. }
  1334. logger.InfoToConsole("tcp server %v now listening\n", address)
  1335. conn.Close()
  1336. break
  1337. }
  1338. }
  1339. func getTestUser() dataprovider.User {
  1340. user := dataprovider.User{
  1341. Username: defaultUsername,
  1342. Password: defaultPassword,
  1343. HomeDir: filepath.Join(homeBasePath, defaultUsername),
  1344. Status: 1,
  1345. ExpirationDate: 0,
  1346. }
  1347. user.Permissions = make(map[string][]string)
  1348. user.Permissions["/"] = allPerms
  1349. return user
  1350. }
  1351. func getExtAuthScriptContent(user dataprovider.User, nonJSONResponse bool, username string) []byte {
  1352. extAuthContent := []byte("#!/bin/sh\n\n")
  1353. extAuthContent = append(extAuthContent, []byte(fmt.Sprintf("if test \"$SFTPGO_AUTHD_USERNAME\" = \"%v\"; then\n", user.Username))...)
  1354. if len(username) > 0 {
  1355. user.Username = username
  1356. }
  1357. u, _ := json.Marshal(user)
  1358. if nonJSONResponse {
  1359. extAuthContent = append(extAuthContent, []byte("echo 'text response'\n")...)
  1360. } else {
  1361. extAuthContent = append(extAuthContent, []byte(fmt.Sprintf("echo '%v'\n", string(u)))...)
  1362. }
  1363. extAuthContent = append(extAuthContent, []byte("else\n")...)
  1364. if nonJSONResponse {
  1365. extAuthContent = append(extAuthContent, []byte("echo 'text response'\n")...)
  1366. } else {
  1367. extAuthContent = append(extAuthContent, []byte("echo '{\"username\":\"\"}'\n")...)
  1368. }
  1369. extAuthContent = append(extAuthContent, []byte("fi\n")...)
  1370. return extAuthContent
  1371. }
  1372. func getPreLoginScriptContent(user dataprovider.User, nonJSONResponse bool) []byte {
  1373. content := []byte("#!/bin/sh\n\n")
  1374. if nonJSONResponse {
  1375. content = append(content, []byte("echo 'text response'\n")...)
  1376. return content
  1377. }
  1378. if len(user.Username) > 0 {
  1379. u, _ := json.Marshal(user)
  1380. content = append(content, []byte(fmt.Sprintf("echo '%v'\n", string(u)))...)
  1381. }
  1382. return content
  1383. }
  1384. func getPostConnectScriptContent(exitCode int) []byte {
  1385. content := []byte("#!/bin/sh\n\n")
  1386. content = append(content, []byte(fmt.Sprintf("exit %v", exitCode))...)
  1387. return content
  1388. }
  1389. func createTestFile(path string, size int64) error {
  1390. baseDir := filepath.Dir(path)
  1391. if _, err := os.Stat(baseDir); os.IsNotExist(err) {
  1392. err = os.MkdirAll(baseDir, os.ModePerm)
  1393. if err != nil {
  1394. return err
  1395. }
  1396. }
  1397. content := make([]byte, size)
  1398. _, err := rand.Read(content)
  1399. if err != nil {
  1400. return err
  1401. }
  1402. return ioutil.WriteFile(path, content, os.ModePerm)
  1403. }