ftpd_test.go 49 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527
  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. u.Filters.FilePatterns = []dataprovider.PatternsFilter{
  483. {
  484. Path: "/sub2",
  485. AllowedPatterns: []string{},
  486. DeniedPatterns: []string{"*.jpg"},
  487. },
  488. }
  489. user, _, err := httpd.AddUser(u, http.StatusOK)
  490. assert.NoError(t, err)
  491. client, err := getFTPClient(user, true)
  492. if assert.NoError(t, err) {
  493. testFilePath1 := filepath.Join(user.HomeDir, subDir1, "file.zip")
  494. testFilePath2 := filepath.Join(user.HomeDir, subDir2, "file.zip")
  495. testFilePath3 := filepath.Join(user.HomeDir, subDir2, "file.jpg")
  496. err = os.MkdirAll(filepath.Dir(testFilePath1), os.ModePerm)
  497. assert.NoError(t, err)
  498. err = os.MkdirAll(filepath.Dir(testFilePath2), os.ModePerm)
  499. assert.NoError(t, err)
  500. err = ioutil.WriteFile(testFilePath1, []byte("file1"), os.ModePerm)
  501. assert.NoError(t, err)
  502. err = ioutil.WriteFile(testFilePath2, []byte("file2"), os.ModePerm)
  503. assert.NoError(t, err)
  504. err = ioutil.WriteFile(testFilePath3, []byte("file3"), os.ModePerm)
  505. assert.NoError(t, err)
  506. localDownloadPath := filepath.Join(homeBasePath, testDLFileName)
  507. err = ftpDownloadFile(path.Join("/", subDir1, "file.zip"), localDownloadPath, 5, client, 0)
  508. assert.Error(t, err)
  509. err = ftpDownloadFile(path.Join("/", subDir2, "file.zip"), localDownloadPath, 5, client, 0)
  510. assert.Error(t, err)
  511. err = ftpDownloadFile(path.Join("/", subDir2, "file.jpg"), localDownloadPath, 5, client, 0)
  512. assert.Error(t, err)
  513. err = ftpDownloadFile("/missing.zip", localDownloadPath, 5, client, 0)
  514. assert.Error(t, err)
  515. err = client.Quit()
  516. assert.NoError(t, err)
  517. err = os.Remove(localDownloadPath)
  518. assert.NoError(t, err)
  519. }
  520. _, err = httpd.RemoveUser(user, http.StatusOK)
  521. assert.NoError(t, err)
  522. err = os.RemoveAll(user.GetHomeDir())
  523. assert.NoError(t, err)
  524. }
  525. func TestUploadErrors(t *testing.T) {
  526. u := getTestUser()
  527. u.QuotaSize = 65535
  528. subDir1 := "sub1"
  529. subDir2 := "sub2"
  530. u.Permissions[path.Join("/", subDir1)] = []string{dataprovider.PermListItems}
  531. u.Permissions[path.Join("/", subDir2)] = []string{dataprovider.PermListItems, dataprovider.PermUpload,
  532. dataprovider.PermDelete}
  533. u.Filters.FileExtensions = []dataprovider.ExtensionsFilter{
  534. {
  535. Path: "/sub2",
  536. AllowedExtensions: []string{},
  537. DeniedExtensions: []string{".zip"},
  538. },
  539. }
  540. user, _, err := httpd.AddUser(u, http.StatusOK)
  541. assert.NoError(t, err)
  542. client, err := getFTPClient(user, true)
  543. if assert.NoError(t, err) {
  544. testFilePath := filepath.Join(homeBasePath, testFileName)
  545. testFileSize := user.QuotaSize
  546. err = createTestFile(testFilePath, testFileSize)
  547. assert.NoError(t, err)
  548. err = client.MakeDir(subDir1)
  549. assert.NoError(t, err)
  550. err = client.MakeDir(subDir2)
  551. assert.NoError(t, err)
  552. err = client.ChangeDir(subDir1)
  553. assert.NoError(t, err)
  554. err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
  555. assert.Error(t, err)
  556. err = client.ChangeDirToParent()
  557. assert.NoError(t, err)
  558. err = client.ChangeDir(subDir2)
  559. assert.NoError(t, err)
  560. err = ftpUploadFile(testFilePath, testFileName+".zip", testFileSize, client, 0)
  561. assert.Error(t, err)
  562. err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
  563. assert.NoError(t, err)
  564. err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
  565. assert.Error(t, err)
  566. err = client.ChangeDir("/")
  567. assert.NoError(t, err)
  568. err = ftpUploadFile(testFilePath, subDir1, testFileSize, client, 0)
  569. assert.Error(t, err)
  570. // overquota
  571. err = ftpUploadFile(testFilePath, testFileName+"1", testFileSize, client, 0)
  572. assert.Error(t, err)
  573. err = client.Delete(path.Join("/", subDir2, testFileName))
  574. assert.NoError(t, err)
  575. err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
  576. assert.NoError(t, err)
  577. err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
  578. assert.Error(t, err)
  579. err = client.Quit()
  580. assert.NoError(t, err)
  581. err = os.Remove(testFilePath)
  582. assert.NoError(t, err)
  583. }
  584. _, err = httpd.RemoveUser(user, http.StatusOK)
  585. assert.NoError(t, err)
  586. err = os.RemoveAll(user.GetHomeDir())
  587. assert.NoError(t, err)
  588. }
  589. func TestResume(t *testing.T) {
  590. u := getTestUser()
  591. user, _, err := httpd.AddUser(u, http.StatusOK)
  592. assert.NoError(t, err)
  593. client, err := getFTPClient(user, true)
  594. if assert.NoError(t, err) {
  595. testFilePath := filepath.Join(homeBasePath, testFileName)
  596. data := []byte("test data")
  597. err = ioutil.WriteFile(testFilePath, data, os.ModePerm)
  598. assert.NoError(t, err)
  599. err = ftpUploadFile(testFilePath, testFileName, int64(len(data)), client, 0)
  600. assert.NoError(t, err)
  601. err = ftpUploadFile(testFilePath, testFileName, int64(len(data)+5), client, 5)
  602. assert.NoError(t, err)
  603. readed, err := ioutil.ReadFile(filepath.Join(user.GetHomeDir(), testFileName))
  604. assert.NoError(t, err)
  605. assert.Equal(t, "test test data", string(readed))
  606. localDownloadPath := filepath.Join(homeBasePath, testDLFileName)
  607. err = ftpDownloadFile(testFileName, localDownloadPath, int64(len(data)), client, 5)
  608. assert.NoError(t, err)
  609. readed, err = ioutil.ReadFile(localDownloadPath)
  610. assert.NoError(t, err)
  611. assert.Equal(t, data, readed)
  612. err = client.Delete(testFileName)
  613. assert.NoError(t, err)
  614. err = ftpUploadFile(testFilePath, testFileName, int64(len(data)), client, 0)
  615. assert.NoError(t, err)
  616. // now append to a file
  617. srcFile, err := os.Open(testFilePath)
  618. if assert.NoError(t, err) {
  619. err = client.Append(testFileName, srcFile)
  620. assert.NoError(t, err)
  621. err = srcFile.Close()
  622. assert.NoError(t, err)
  623. size, err := client.FileSize(testFileName)
  624. assert.NoError(t, err)
  625. assert.Equal(t, int64(2*len(data)), size)
  626. err = ftpDownloadFile(testFileName, localDownloadPath, int64(2*len(data)), client, 0)
  627. assert.NoError(t, err)
  628. readed, err = ioutil.ReadFile(localDownloadPath)
  629. assert.NoError(t, err)
  630. expected := append(data, data...)
  631. assert.Equal(t, expected, readed)
  632. }
  633. err = client.Quit()
  634. assert.NoError(t, err)
  635. err = os.Remove(testFilePath)
  636. assert.NoError(t, err)
  637. err = os.Remove(localDownloadPath)
  638. assert.NoError(t, err)
  639. }
  640. _, err = httpd.RemoveUser(user, http.StatusOK)
  641. assert.NoError(t, err)
  642. err = os.RemoveAll(user.GetHomeDir())
  643. assert.NoError(t, err)
  644. }
  645. //nolint:dupl
  646. func TestDeniedLoginMethod(t *testing.T) {
  647. u := getTestUser()
  648. u.Filters.DeniedLoginMethods = []string{dataprovider.LoginMethodPassword}
  649. user, _, err := httpd.AddUser(u, http.StatusOK)
  650. assert.NoError(t, err)
  651. _, err = getFTPClient(user, false)
  652. assert.Error(t, err)
  653. user.Filters.DeniedLoginMethods = []string{dataprovider.SSHLoginMethodPublicKey, dataprovider.SSHLoginMethodKeyAndPassword}
  654. user, _, err = httpd.UpdateUser(user, http.StatusOK, "")
  655. assert.NoError(t, err)
  656. client, err := getFTPClient(user, true)
  657. if assert.NoError(t, err) {
  658. assert.NoError(t, checkBasicFTP(client))
  659. err = client.Quit()
  660. assert.NoError(t, err)
  661. }
  662. _, err = httpd.RemoveUser(user, http.StatusOK)
  663. assert.NoError(t, err)
  664. err = os.RemoveAll(user.GetHomeDir())
  665. assert.NoError(t, err)
  666. }
  667. //nolint:dupl
  668. func TestDeniedProtocols(t *testing.T) {
  669. u := getTestUser()
  670. u.Filters.DeniedProtocols = []string{common.ProtocolFTP}
  671. user, _, err := httpd.AddUser(u, http.StatusOK)
  672. assert.NoError(t, err)
  673. _, err = getFTPClient(user, false)
  674. assert.Error(t, err)
  675. user.Filters.DeniedProtocols = []string{common.ProtocolSSH, common.ProtocolWebDAV}
  676. user, _, err = httpd.UpdateUser(user, http.StatusOK, "")
  677. assert.NoError(t, err)
  678. client, err := getFTPClient(user, true)
  679. if assert.NoError(t, err) {
  680. assert.NoError(t, checkBasicFTP(client))
  681. err = client.Quit()
  682. assert.NoError(t, err)
  683. }
  684. _, err = httpd.RemoveUser(user, http.StatusOK)
  685. assert.NoError(t, err)
  686. err = os.RemoveAll(user.GetHomeDir())
  687. assert.NoError(t, err)
  688. }
  689. func TestQuotaLimits(t *testing.T) {
  690. u := getTestUser()
  691. u.QuotaFiles = 1
  692. user, _, err := httpd.AddUser(u, http.StatusOK)
  693. assert.NoError(t, err)
  694. testFileSize := int64(65535)
  695. testFilePath := filepath.Join(homeBasePath, testFileName)
  696. err = createTestFile(testFilePath, testFileSize)
  697. assert.NoError(t, err)
  698. testFileSize1 := int64(131072)
  699. testFileName1 := "test_file1.dat"
  700. testFilePath1 := filepath.Join(homeBasePath, testFileName1)
  701. err = createTestFile(testFilePath1, testFileSize1)
  702. assert.NoError(t, err)
  703. testFileSize2 := int64(32768)
  704. testFileName2 := "test_file2.dat"
  705. testFilePath2 := filepath.Join(homeBasePath, testFileName2)
  706. err = createTestFile(testFilePath2, testFileSize2)
  707. assert.NoError(t, err)
  708. // test quota files
  709. client, err := getFTPClient(user, false)
  710. if assert.NoError(t, err) {
  711. err = ftpUploadFile(testFilePath, testFileName+".quota", testFileSize, client, 0)
  712. assert.NoError(t, err)
  713. err = ftpUploadFile(testFilePath, testFileName+".quota1", testFileSize, client, 0)
  714. assert.Error(t, err)
  715. err = client.Rename(testFileName+".quota", testFileName)
  716. assert.NoError(t, err)
  717. err = client.Quit()
  718. assert.NoError(t, err)
  719. }
  720. // test quota size
  721. user.QuotaSize = testFileSize - 1
  722. user.QuotaFiles = 0
  723. user, _, err = httpd.UpdateUser(user, http.StatusOK, "")
  724. assert.NoError(t, err)
  725. client, err = getFTPClient(user, true)
  726. if assert.NoError(t, err) {
  727. err = ftpUploadFile(testFilePath, testFileName+".quota", testFileSize, client, 0)
  728. assert.Error(t, err)
  729. err = client.Rename(testFileName, testFileName+".quota")
  730. assert.NoError(t, err)
  731. err = client.Quit()
  732. assert.NoError(t, err)
  733. }
  734. // now test quota limits while uploading the current file, we have 1 bytes remaining
  735. user.QuotaSize = testFileSize + 1
  736. user.QuotaFiles = 0
  737. user, _, err = httpd.UpdateUser(user, http.StatusOK, "")
  738. assert.NoError(t, err)
  739. client, err = getFTPClient(user, false)
  740. if assert.NoError(t, err) {
  741. err = ftpUploadFile(testFilePath1, testFileName1, testFileSize1, client, 0)
  742. assert.Error(t, err)
  743. _, err = client.FileSize(testFileName1)
  744. assert.Error(t, err)
  745. err = client.Rename(testFileName+".quota", testFileName)
  746. assert.NoError(t, err)
  747. // overwriting an existing file will work if the resulting size is lesser or equal than the current one
  748. err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
  749. assert.NoError(t, err)
  750. err = ftpUploadFile(testFilePath2, testFileName, testFileSize2, client, 0)
  751. assert.NoError(t, err)
  752. err = ftpUploadFile(testFilePath1, testFileName, testFileSize1, client, 0)
  753. assert.Error(t, err)
  754. err = ftpUploadFile(testFilePath1, testFileName, testFileSize1, client, 10)
  755. assert.Error(t, err)
  756. err = ftpUploadFile(testFilePath2, testFileName, testFileSize2, client, 0)
  757. assert.NoError(t, err)
  758. err = client.Quit()
  759. assert.NoError(t, err)
  760. }
  761. err = os.Remove(testFilePath)
  762. assert.NoError(t, err)
  763. err = os.Remove(testFilePath1)
  764. assert.NoError(t, err)
  765. err = os.Remove(testFilePath2)
  766. assert.NoError(t, err)
  767. _, err = httpd.RemoveUser(user, http.StatusOK)
  768. assert.NoError(t, err)
  769. err = os.RemoveAll(user.GetHomeDir())
  770. assert.NoError(t, err)
  771. }
  772. func TestUploadMaxSize(t *testing.T) {
  773. testFileSize := int64(65535)
  774. u := getTestUser()
  775. u.Filters.MaxUploadFileSize = testFileSize + 1
  776. user, _, err := httpd.AddUser(u, http.StatusOK)
  777. assert.NoError(t, err)
  778. testFilePath := filepath.Join(homeBasePath, testFileName)
  779. err = createTestFile(testFilePath, testFileSize)
  780. assert.NoError(t, err)
  781. testFileSize1 := int64(131072)
  782. testFileName1 := "test_file1.dat"
  783. testFilePath1 := filepath.Join(homeBasePath, testFileName1)
  784. err = createTestFile(testFilePath1, testFileSize1)
  785. assert.NoError(t, err)
  786. client, err := getFTPClient(user, false)
  787. if assert.NoError(t, err) {
  788. err = ftpUploadFile(testFilePath1, testFileName1, testFileSize1, client, 0)
  789. assert.Error(t, err)
  790. err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
  791. assert.NoError(t, err)
  792. // now test overwrite an existing file with a size bigger than the allowed one
  793. err = createTestFile(filepath.Join(user.GetHomeDir(), testFileName1), testFileSize1)
  794. assert.NoError(t, err)
  795. err = ftpUploadFile(testFilePath1, testFileName1, testFileSize1, client, 0)
  796. assert.Error(t, err)
  797. err = client.Quit()
  798. assert.NoError(t, err)
  799. }
  800. err = os.Remove(testFilePath)
  801. assert.NoError(t, err)
  802. err = os.Remove(testFilePath1)
  803. assert.NoError(t, err)
  804. _, err = httpd.RemoveUser(user, http.StatusOK)
  805. assert.NoError(t, err)
  806. err = os.RemoveAll(user.GetHomeDir())
  807. assert.NoError(t, err)
  808. }
  809. func TestLoginWithIPilters(t *testing.T) {
  810. u := getTestUser()
  811. u.Filters.DeniedIP = []string{"192.167.0.0/24", "172.18.0.0/16"}
  812. u.Filters.AllowedIP = []string{"172.19.0.0/16"}
  813. user, _, err := httpd.AddUser(u, http.StatusOK)
  814. assert.NoError(t, err)
  815. client, err := getFTPClient(user, true)
  816. if !assert.Error(t, err) {
  817. err = client.Quit()
  818. assert.NoError(t, err)
  819. }
  820. _, err = httpd.RemoveUser(user, http.StatusOK)
  821. assert.NoError(t, err)
  822. err = os.RemoveAll(user.GetHomeDir())
  823. assert.NoError(t, err)
  824. }
  825. func TestLoginWithDatabaseCredentials(t *testing.T) {
  826. u := getTestUser()
  827. u.FsConfig.Provider = dataprovider.GCSFilesystemProvider
  828. u.FsConfig.GCSConfig.Bucket = "test"
  829. u.FsConfig.GCSConfig.Credentials = vfs.Secret{
  830. Status: vfs.SecretStatusPlain,
  831. Payload: `{ "type": "service_account" }`,
  832. }
  833. providerConf := config.GetProviderConf()
  834. providerConf.PreferDatabaseCredentials = true
  835. credentialsFile := filepath.Join(providerConf.CredentialsPath, fmt.Sprintf("%v_gcs_credentials.json", u.Username))
  836. if !filepath.IsAbs(credentialsFile) {
  837. credentialsFile = filepath.Join(configDir, credentialsFile)
  838. }
  839. assert.NoError(t, dataprovider.Close())
  840. err := dataprovider.Initialize(providerConf, configDir)
  841. assert.NoError(t, err)
  842. if _, err = os.Stat(credentialsFile); err == nil {
  843. // remove the credentials file
  844. assert.NoError(t, os.Remove(credentialsFile))
  845. }
  846. user, _, err := httpd.AddUser(u, http.StatusOK)
  847. assert.NoError(t, err)
  848. assert.Equal(t, vfs.SecretStatusAES256GCM, user.FsConfig.GCSConfig.Credentials.Status)
  849. assert.NotEmpty(t, user.FsConfig.GCSConfig.Credentials.Payload)
  850. assert.Empty(t, user.FsConfig.GCSConfig.Credentials.AdditionalData)
  851. assert.Empty(t, user.FsConfig.GCSConfig.Credentials.Key)
  852. assert.NoFileExists(t, credentialsFile)
  853. client, err := getFTPClient(user, false)
  854. if assert.NoError(t, err) {
  855. err = client.Quit()
  856. assert.NoError(t, err)
  857. }
  858. _, err = httpd.RemoveUser(user, http.StatusOK)
  859. assert.NoError(t, err)
  860. err = os.RemoveAll(user.GetHomeDir())
  861. assert.NoError(t, err)
  862. assert.NoError(t, dataprovider.Close())
  863. assert.NoError(t, config.LoadConfig(configDir, ""))
  864. providerConf = config.GetProviderConf()
  865. assert.NoError(t, dataprovider.Initialize(providerConf, configDir))
  866. }
  867. func TestLoginInvalidFs(t *testing.T) {
  868. u := getTestUser()
  869. u.FsConfig.Provider = dataprovider.GCSFilesystemProvider
  870. u.FsConfig.GCSConfig.Bucket = "test"
  871. u.FsConfig.GCSConfig.Credentials = vfs.Secret{
  872. Status: vfs.SecretStatusPlain,
  873. Payload: "invalid JSON for credentials",
  874. }
  875. user, _, err := httpd.AddUser(u, http.StatusOK)
  876. assert.NoError(t, err)
  877. providerConf := config.GetProviderConf()
  878. credentialsFile := filepath.Join(providerConf.CredentialsPath, fmt.Sprintf("%v_gcs_credentials.json", u.Username))
  879. if !filepath.IsAbs(credentialsFile) {
  880. credentialsFile = filepath.Join(configDir, credentialsFile)
  881. }
  882. // now remove the credentials file so the filesystem creation will fail
  883. err = os.Remove(credentialsFile)
  884. assert.NoError(t, err)
  885. client, err := getFTPClient(user, false)
  886. if !assert.Error(t, err) {
  887. err = client.Quit()
  888. assert.NoError(t, err)
  889. }
  890. _, err = httpd.RemoveUser(user, http.StatusOK)
  891. assert.NoError(t, err)
  892. err = os.RemoveAll(user.GetHomeDir())
  893. assert.NoError(t, err)
  894. }
  895. func TestClientClose(t *testing.T) {
  896. u := getTestUser()
  897. user, _, err := httpd.AddUser(u, http.StatusOK)
  898. assert.NoError(t, err)
  899. client, err := getFTPClient(user, true)
  900. if assert.NoError(t, err) {
  901. err = checkBasicFTP(client)
  902. assert.NoError(t, err)
  903. stats := common.Connections.GetStats()
  904. if assert.Len(t, stats, 1) {
  905. common.Connections.Close(stats[0].ConnectionID)
  906. assert.Eventually(t, func() bool { return len(common.Connections.GetStats()) == 0 },
  907. 1*time.Second, 50*time.Millisecond)
  908. }
  909. }
  910. _, err = httpd.RemoveUser(user, http.StatusOK)
  911. assert.NoError(t, err)
  912. err = os.RemoveAll(user.GetHomeDir())
  913. assert.NoError(t, err)
  914. }
  915. func TestRename(t *testing.T) {
  916. u := getTestUser()
  917. user, _, err := httpd.AddUser(u, http.StatusOK)
  918. assert.NoError(t, err)
  919. testDir := "adir"
  920. testFilePath := filepath.Join(homeBasePath, testFileName)
  921. testFileSize := int64(65535)
  922. err = createTestFile(testFilePath, testFileSize)
  923. assert.NoError(t, err)
  924. client, err := getFTPClient(user, false)
  925. if assert.NoError(t, err) {
  926. err = checkBasicFTP(client)
  927. assert.NoError(t, err)
  928. err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
  929. assert.NoError(t, err)
  930. err = client.MakeDir(testDir)
  931. assert.NoError(t, err)
  932. err = client.Rename(testFileName, path.Join("missing", testFileName))
  933. assert.Error(t, err)
  934. err = client.Rename(testFileName, path.Join(testDir, testFileName))
  935. assert.NoError(t, err)
  936. size, err := client.FileSize(path.Join(testDir, testFileName))
  937. assert.NoError(t, err)
  938. assert.Equal(t, testFileSize, size)
  939. if runtime.GOOS != osWindows {
  940. otherDir := "dir"
  941. err = client.MakeDir(otherDir)
  942. assert.NoError(t, err)
  943. err = client.MakeDir(path.Join(otherDir, testDir))
  944. assert.NoError(t, err)
  945. code, response, err := client.SendCustomCommand(fmt.Sprintf("SITE CHMOD 0001 %v", otherDir))
  946. assert.NoError(t, err)
  947. assert.Equal(t, ftp.StatusCommandOK, code)
  948. assert.Equal(t, "SITE CHMOD command successful", response)
  949. err = client.Rename(testDir, path.Join(otherDir, testDir))
  950. assert.Error(t, err)
  951. code, response, err = client.SendCustomCommand(fmt.Sprintf("SITE CHMOD 755 %v", otherDir))
  952. assert.NoError(t, err)
  953. assert.Equal(t, ftp.StatusCommandOK, code)
  954. assert.Equal(t, "SITE CHMOD command successful", response)
  955. }
  956. err = client.Quit()
  957. assert.NoError(t, err)
  958. }
  959. user.Permissions[path.Join("/", testDir)] = []string{dataprovider.PermListItems}
  960. user, _, err = httpd.UpdateUser(user, http.StatusOK, "")
  961. assert.NoError(t, err)
  962. client, err = getFTPClient(user, false)
  963. if assert.NoError(t, err) {
  964. err = client.Rename(path.Join(testDir, testFileName), testFileName)
  965. assert.Error(t, err)
  966. err := client.Quit()
  967. assert.NoError(t, err)
  968. }
  969. err = os.Remove(testFilePath)
  970. assert.NoError(t, err)
  971. _, err = httpd.RemoveUser(user, http.StatusOK)
  972. assert.NoError(t, err)
  973. err = os.RemoveAll(user.GetHomeDir())
  974. assert.NoError(t, err)
  975. }
  976. func TestSymlink(t *testing.T) {
  977. u := getTestUser()
  978. user, _, err := httpd.AddUser(u, http.StatusOK)
  979. assert.NoError(t, err)
  980. testFilePath := filepath.Join(homeBasePath, testFileName)
  981. testFileSize := int64(65535)
  982. err = createTestFile(testFilePath, testFileSize)
  983. assert.NoError(t, err)
  984. client, err := getFTPClient(user, false)
  985. if assert.NoError(t, err) {
  986. err = checkBasicFTP(client)
  987. assert.NoError(t, err)
  988. err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
  989. assert.NoError(t, err)
  990. code, _, err := client.SendCustomCommand(fmt.Sprintf("SITE SYMLINK %v %v", testFileName, testFileName+".link"))
  991. assert.NoError(t, err)
  992. assert.Equal(t, ftp.StatusCommandOK, code)
  993. if runtime.GOOS != osWindows {
  994. testDir := "adir"
  995. otherDir := "dir"
  996. err = client.MakeDir(otherDir)
  997. assert.NoError(t, err)
  998. err = client.MakeDir(path.Join(otherDir, testDir))
  999. assert.NoError(t, err)
  1000. code, response, err := client.SendCustomCommand(fmt.Sprintf("SITE CHMOD 0001 %v", otherDir))
  1001. assert.NoError(t, err)
  1002. assert.Equal(t, ftp.StatusCommandOK, code)
  1003. assert.Equal(t, "SITE CHMOD command successful", response)
  1004. code, _, err = client.SendCustomCommand(fmt.Sprintf("SITE SYMLINK %v %v", testDir, path.Join(otherDir, testDir)))
  1005. assert.NoError(t, err)
  1006. assert.Equal(t, ftp.StatusFileUnavailable, code)
  1007. code, response, err = client.SendCustomCommand(fmt.Sprintf("SITE CHMOD 755 %v", otherDir))
  1008. assert.NoError(t, err)
  1009. assert.Equal(t, ftp.StatusCommandOK, code)
  1010. assert.Equal(t, "SITE CHMOD command successful", response)
  1011. }
  1012. err = client.Quit()
  1013. assert.NoError(t, err)
  1014. }
  1015. err = os.Remove(testFilePath)
  1016. assert.NoError(t, err)
  1017. _, err = httpd.RemoveUser(user, http.StatusOK)
  1018. assert.NoError(t, err)
  1019. err = os.RemoveAll(user.GetHomeDir())
  1020. assert.NoError(t, err)
  1021. }
  1022. func TestStat(t *testing.T) {
  1023. u := getTestUser()
  1024. u.Permissions["/subdir"] = []string{dataprovider.PermUpload}
  1025. user, _, err := httpd.AddUser(u, http.StatusOK)
  1026. assert.NoError(t, err)
  1027. client, err := getFTPClient(user, false)
  1028. if assert.NoError(t, err) {
  1029. subDir := "subdir"
  1030. testFilePath := filepath.Join(homeBasePath, testFileName)
  1031. testFileSize := int64(65535)
  1032. err = createTestFile(testFilePath, testFileSize)
  1033. assert.NoError(t, err)
  1034. err = client.MakeDir(subDir)
  1035. assert.NoError(t, err)
  1036. err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
  1037. assert.NoError(t, err)
  1038. err = ftpUploadFile(testFilePath, path.Join("/", subDir, testFileName), testFileSize, client, 0)
  1039. assert.Error(t, err)
  1040. size, err := client.FileSize(testFileName)
  1041. assert.NoError(t, err)
  1042. assert.Equal(t, testFileSize, size)
  1043. _, err = client.FileSize(path.Join("/", subDir, testFileName))
  1044. assert.Error(t, err)
  1045. _, err = client.FileSize("missing file")
  1046. assert.Error(t, err)
  1047. err = client.Quit()
  1048. assert.NoError(t, err)
  1049. err = os.Remove(testFilePath)
  1050. assert.NoError(t, err)
  1051. }
  1052. _, err = httpd.RemoveUser(user, http.StatusOK)
  1053. assert.NoError(t, err)
  1054. err = os.RemoveAll(user.GetHomeDir())
  1055. assert.NoError(t, err)
  1056. }
  1057. func TestUploadOverwriteVfolder(t *testing.T) {
  1058. u := getTestUser()
  1059. vdir := "/vdir"
  1060. mappedPath := filepath.Join(os.TempDir(), "vdir")
  1061. u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
  1062. BaseVirtualFolder: vfs.BaseVirtualFolder{
  1063. MappedPath: mappedPath,
  1064. },
  1065. VirtualPath: vdir,
  1066. QuotaSize: -1,
  1067. QuotaFiles: -1,
  1068. })
  1069. err := os.MkdirAll(mappedPath, os.ModePerm)
  1070. assert.NoError(t, err)
  1071. user, _, err := httpd.AddUser(u, http.StatusOK)
  1072. assert.NoError(t, err)
  1073. client, err := getFTPClient(user, false)
  1074. if assert.NoError(t, err) {
  1075. testFilePath := filepath.Join(homeBasePath, testFileName)
  1076. testFileSize := int64(65535)
  1077. err = createTestFile(testFilePath, testFileSize)
  1078. assert.NoError(t, err)
  1079. err = ftpUploadFile(testFilePath, path.Join(vdir, testFileName), testFileSize, client, 0)
  1080. assert.NoError(t, err)
  1081. folder, _, err := httpd.GetFolders(0, 0, mappedPath, http.StatusOK)
  1082. assert.NoError(t, err)
  1083. if assert.Len(t, folder, 1) {
  1084. f := folder[0]
  1085. assert.Equal(t, testFileSize, f.UsedQuotaSize)
  1086. assert.Equal(t, 1, f.UsedQuotaFiles)
  1087. }
  1088. err = ftpUploadFile(testFilePath, path.Join(vdir, testFileName), testFileSize, client, 0)
  1089. assert.NoError(t, err)
  1090. folder, _, err = httpd.GetFolders(0, 0, mappedPath, http.StatusOK)
  1091. assert.NoError(t, err)
  1092. if assert.Len(t, folder, 1) {
  1093. f := folder[0]
  1094. assert.Equal(t, testFileSize, f.UsedQuotaSize)
  1095. assert.Equal(t, 1, f.UsedQuotaFiles)
  1096. }
  1097. err = client.Quit()
  1098. assert.NoError(t, err)
  1099. err = os.Remove(testFilePath)
  1100. assert.NoError(t, err)
  1101. }
  1102. _, err = httpd.RemoveUser(user, http.StatusOK)
  1103. assert.NoError(t, err)
  1104. _, err = httpd.RemoveFolder(vfs.BaseVirtualFolder{MappedPath: mappedPath}, http.StatusOK)
  1105. assert.NoError(t, err)
  1106. err = os.RemoveAll(user.GetHomeDir())
  1107. assert.NoError(t, err)
  1108. err = os.RemoveAll(mappedPath)
  1109. assert.NoError(t, err)
  1110. }
  1111. func TestAllocate(t *testing.T) {
  1112. u := getTestUser()
  1113. mappedPath := filepath.Join(os.TempDir(), "vdir")
  1114. u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
  1115. BaseVirtualFolder: vfs.BaseVirtualFolder{
  1116. MappedPath: mappedPath,
  1117. },
  1118. VirtualPath: "/vdir",
  1119. QuotaSize: 110,
  1120. })
  1121. err := os.MkdirAll(mappedPath, os.ModePerm)
  1122. assert.NoError(t, err)
  1123. user, _, err := httpd.AddUser(u, http.StatusOK)
  1124. assert.NoError(t, err)
  1125. client, err := getFTPClient(user, false)
  1126. if assert.NoError(t, err) {
  1127. code, response, err := client.SendCustomCommand("allo 2000000")
  1128. assert.NoError(t, err)
  1129. assert.Equal(t, ftp.StatusCommandOK, code)
  1130. assert.Equal(t, "Done !", response)
  1131. err = client.Quit()
  1132. assert.NoError(t, err)
  1133. }
  1134. user.QuotaSize = 100
  1135. user, _, err = httpd.UpdateUser(user, http.StatusOK, "")
  1136. assert.NoError(t, err)
  1137. client, err = getFTPClient(user, false)
  1138. if assert.NoError(t, err) {
  1139. testFilePath := filepath.Join(homeBasePath, testFileName)
  1140. testFileSize := user.QuotaSize - 1
  1141. err = createTestFile(testFilePath, testFileSize)
  1142. assert.NoError(t, err)
  1143. code, response, err := client.SendCustomCommand("allo 99")
  1144. assert.NoError(t, err)
  1145. assert.Equal(t, ftp.StatusCommandOK, code)
  1146. assert.Equal(t, "Done !", response)
  1147. code, response, err = client.SendCustomCommand("allo 100")
  1148. assert.NoError(t, err)
  1149. assert.Equal(t, ftp.StatusCommandOK, code)
  1150. assert.Equal(t, "Done !", response)
  1151. code, response, err = client.SendCustomCommand("allo 150")
  1152. assert.NoError(t, err)
  1153. assert.Equal(t, ftp.StatusFileUnavailable, code)
  1154. assert.Contains(t, response, common.ErrQuotaExceeded.Error())
  1155. err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
  1156. assert.NoError(t, err)
  1157. // we still have space in vdir
  1158. code, response, err = client.SendCustomCommand("allo 50")
  1159. assert.NoError(t, err)
  1160. assert.Equal(t, ftp.StatusCommandOK, code)
  1161. assert.Equal(t, "Done !", response)
  1162. err = ftpUploadFile(testFilePath, path.Join("/vdir", testFileName), testFileSize, client, 0)
  1163. assert.NoError(t, err)
  1164. code, response, err = client.SendCustomCommand("allo 50")
  1165. assert.NoError(t, err)
  1166. assert.Equal(t, ftp.StatusFileUnavailable, code)
  1167. assert.Contains(t, response, common.ErrQuotaExceeded.Error())
  1168. err = client.Quit()
  1169. assert.NoError(t, err)
  1170. err = os.Remove(testFilePath)
  1171. assert.NoError(t, err)
  1172. }
  1173. user.Filters.MaxUploadFileSize = 100
  1174. user.QuotaSize = 0
  1175. user, _, err = httpd.UpdateUser(user, http.StatusOK, "")
  1176. assert.NoError(t, err)
  1177. client, err = getFTPClient(user, false)
  1178. if assert.NoError(t, err) {
  1179. code, response, err := client.SendCustomCommand("allo 99")
  1180. assert.NoError(t, err)
  1181. assert.Equal(t, ftp.StatusCommandOK, code)
  1182. assert.Equal(t, "Done !", response)
  1183. code, response, err = client.SendCustomCommand("allo 150")
  1184. assert.NoError(t, err)
  1185. assert.Equal(t, ftp.StatusFileUnavailable, code)
  1186. assert.Contains(t, response, common.ErrQuotaExceeded.Error())
  1187. err = client.Quit()
  1188. assert.NoError(t, err)
  1189. }
  1190. _, err = httpd.RemoveUser(user, http.StatusOK)
  1191. assert.NoError(t, err)
  1192. _, err = httpd.RemoveFolder(vfs.BaseVirtualFolder{MappedPath: mappedPath}, http.StatusOK)
  1193. assert.NoError(t, err)
  1194. err = os.RemoveAll(user.GetHomeDir())
  1195. assert.NoError(t, err)
  1196. err = os.RemoveAll(mappedPath)
  1197. assert.NoError(t, err)
  1198. }
  1199. func TestChtimes(t *testing.T) {
  1200. u := getTestUser()
  1201. user, _, err := httpd.AddUser(u, http.StatusOK)
  1202. assert.NoError(t, err)
  1203. client, err := getFTPClient(user, false)
  1204. if assert.NoError(t, err) {
  1205. testFilePath := filepath.Join(homeBasePath, testFileName)
  1206. testFileSize := int64(65535)
  1207. err = createTestFile(testFilePath, testFileSize)
  1208. assert.NoError(t, err)
  1209. err = checkBasicFTP(client)
  1210. assert.NoError(t, err)
  1211. err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
  1212. assert.NoError(t, err)
  1213. mtime := time.Now().Format("20060102150405")
  1214. code, response, err := client.SendCustomCommand(fmt.Sprintf("MFMT %v %v", mtime, testFileName))
  1215. assert.NoError(t, err)
  1216. assert.Equal(t, ftp.StatusFile, code)
  1217. assert.Equal(t, fmt.Sprintf("Modify=%v; %v", mtime, testFileName), response)
  1218. err = client.Quit()
  1219. assert.NoError(t, err)
  1220. err = os.Remove(testFilePath)
  1221. assert.NoError(t, err)
  1222. }
  1223. _, err = httpd.RemoveUser(user, http.StatusOK)
  1224. assert.NoError(t, err)
  1225. err = os.RemoveAll(user.GetHomeDir())
  1226. assert.NoError(t, err)
  1227. }
  1228. func TestChmod(t *testing.T) {
  1229. if runtime.GOOS == osWindows {
  1230. t.Skip("chmod is partially supported on Windows")
  1231. }
  1232. u := getTestUser()
  1233. user, _, err := httpd.AddUser(u, http.StatusOK)
  1234. assert.NoError(t, err)
  1235. client, err := getFTPClient(user, true)
  1236. if assert.NoError(t, err) {
  1237. testFilePath := filepath.Join(homeBasePath, testFileName)
  1238. testFileSize := int64(131072)
  1239. err = createTestFile(testFilePath, testFileSize)
  1240. assert.NoError(t, err)
  1241. err = checkBasicFTP(client)
  1242. assert.NoError(t, err)
  1243. err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
  1244. assert.NoError(t, err)
  1245. code, response, err := client.SendCustomCommand(fmt.Sprintf("SITE CHMOD 600 %v", testFileName))
  1246. assert.NoError(t, err)
  1247. assert.Equal(t, ftp.StatusCommandOK, code)
  1248. assert.Equal(t, "SITE CHMOD command successful", response)
  1249. fi, err := os.Stat(filepath.Join(user.HomeDir, testFileName))
  1250. if assert.NoError(t, err) {
  1251. assert.Equal(t, os.FileMode(0600), fi.Mode().Perm())
  1252. }
  1253. err = client.Quit()
  1254. assert.NoError(t, err)
  1255. err = os.Remove(testFilePath)
  1256. assert.NoError(t, err)
  1257. }
  1258. _, err = httpd.RemoveUser(user, http.StatusOK)
  1259. assert.NoError(t, err)
  1260. err = os.RemoveAll(user.GetHomeDir())
  1261. assert.NoError(t, err)
  1262. }
  1263. func checkBasicFTP(client *ftp.ServerConn) error {
  1264. _, err := client.CurrentDir()
  1265. if err != nil {
  1266. return err
  1267. }
  1268. err = client.NoOp()
  1269. if err != nil {
  1270. return err
  1271. }
  1272. _, err = client.List(".")
  1273. if err != nil {
  1274. return err
  1275. }
  1276. return nil
  1277. }
  1278. func ftpUploadFile(localSourcePath string, remoteDestPath string, expectedSize int64, client *ftp.ServerConn, offset uint64) error {
  1279. srcFile, err := os.Open(localSourcePath)
  1280. if err != nil {
  1281. return err
  1282. }
  1283. defer srcFile.Close()
  1284. if offset > 0 {
  1285. err = client.StorFrom(remoteDestPath, srcFile, offset)
  1286. } else {
  1287. err = client.Stor(remoteDestPath, srcFile)
  1288. }
  1289. if err != nil {
  1290. return err
  1291. }
  1292. if expectedSize > 0 {
  1293. size, err := client.FileSize(remoteDestPath)
  1294. if err != nil {
  1295. return err
  1296. }
  1297. if size != expectedSize {
  1298. return fmt.Errorf("uploaded file size does not match, actual: %v, expected: %v", size, expectedSize)
  1299. }
  1300. }
  1301. return nil
  1302. }
  1303. func ftpDownloadFile(remoteSourcePath string, localDestPath string, expectedSize int64, client *ftp.ServerConn, offset uint64) error {
  1304. downloadDest, err := os.Create(localDestPath)
  1305. if err != nil {
  1306. return err
  1307. }
  1308. defer downloadDest.Close()
  1309. var r *ftp.Response
  1310. if offset > 0 {
  1311. r, err = client.RetrFrom(remoteSourcePath, offset)
  1312. } else {
  1313. r, err = client.Retr(remoteSourcePath)
  1314. }
  1315. if err != nil {
  1316. return err
  1317. }
  1318. defer r.Close()
  1319. written, err := io.Copy(downloadDest, r)
  1320. if err != nil {
  1321. return err
  1322. }
  1323. if written != expectedSize {
  1324. return fmt.Errorf("downloaded file size does not match, actual: %v, expected: %v", written, expectedSize)
  1325. }
  1326. return nil
  1327. }
  1328. func getFTPClient(user dataprovider.User, useTLS bool) (*ftp.ServerConn, error) {
  1329. ftpOptions := []ftp.DialOption{ftp.DialWithTimeout(5 * time.Second)}
  1330. if useTLS {
  1331. tlsConfig := &tls.Config{
  1332. ServerName: "localhost",
  1333. InsecureSkipVerify: true, // use this for tests only
  1334. MinVersion: tls.VersionTLS12,
  1335. }
  1336. ftpOptions = append(ftpOptions, ftp.DialWithExplicitTLS(tlsConfig))
  1337. }
  1338. client, err := ftp.Dial(ftpServerAddr, ftpOptions...)
  1339. if err != nil {
  1340. return nil, err
  1341. }
  1342. pwd := defaultPassword
  1343. if len(user.Password) > 0 {
  1344. pwd = user.Password
  1345. }
  1346. err = client.Login(user.Username, pwd)
  1347. if err != nil {
  1348. return nil, err
  1349. }
  1350. return client, err
  1351. }
  1352. func waitTCPListening(address string) {
  1353. for {
  1354. conn, err := net.Dial("tcp", address)
  1355. if err != nil {
  1356. logger.WarnToConsole("tcp server %v not listening: %v\n", address, err)
  1357. time.Sleep(100 * time.Millisecond)
  1358. continue
  1359. }
  1360. logger.InfoToConsole("tcp server %v now listening\n", address)
  1361. conn.Close()
  1362. break
  1363. }
  1364. }
  1365. func getTestUser() dataprovider.User {
  1366. user := dataprovider.User{
  1367. Username: defaultUsername,
  1368. Password: defaultPassword,
  1369. HomeDir: filepath.Join(homeBasePath, defaultUsername),
  1370. Status: 1,
  1371. ExpirationDate: 0,
  1372. }
  1373. user.Permissions = make(map[string][]string)
  1374. user.Permissions["/"] = allPerms
  1375. return user
  1376. }
  1377. func getExtAuthScriptContent(user dataprovider.User, nonJSONResponse bool, username string) []byte {
  1378. extAuthContent := []byte("#!/bin/sh\n\n")
  1379. extAuthContent = append(extAuthContent, []byte(fmt.Sprintf("if test \"$SFTPGO_AUTHD_USERNAME\" = \"%v\"; then\n", user.Username))...)
  1380. if len(username) > 0 {
  1381. user.Username = username
  1382. }
  1383. u, _ := json.Marshal(user)
  1384. if nonJSONResponse {
  1385. extAuthContent = append(extAuthContent, []byte("echo 'text response'\n")...)
  1386. } else {
  1387. extAuthContent = append(extAuthContent, []byte(fmt.Sprintf("echo '%v'\n", string(u)))...)
  1388. }
  1389. extAuthContent = append(extAuthContent, []byte("else\n")...)
  1390. if nonJSONResponse {
  1391. extAuthContent = append(extAuthContent, []byte("echo 'text response'\n")...)
  1392. } else {
  1393. extAuthContent = append(extAuthContent, []byte("echo '{\"username\":\"\"}'\n")...)
  1394. }
  1395. extAuthContent = append(extAuthContent, []byte("fi\n")...)
  1396. return extAuthContent
  1397. }
  1398. func getPreLoginScriptContent(user dataprovider.User, nonJSONResponse bool) []byte {
  1399. content := []byte("#!/bin/sh\n\n")
  1400. if nonJSONResponse {
  1401. content = append(content, []byte("echo 'text response'\n")...)
  1402. return content
  1403. }
  1404. if len(user.Username) > 0 {
  1405. u, _ := json.Marshal(user)
  1406. content = append(content, []byte(fmt.Sprintf("echo '%v'\n", string(u)))...)
  1407. }
  1408. return content
  1409. }
  1410. func getPostConnectScriptContent(exitCode int) []byte {
  1411. content := []byte("#!/bin/sh\n\n")
  1412. content = append(content, []byte(fmt.Sprintf("exit %v", exitCode))...)
  1413. return content
  1414. }
  1415. func createTestFile(path string, size int64) error {
  1416. baseDir := filepath.Dir(path)
  1417. if _, err := os.Stat(baseDir); os.IsNotExist(err) {
  1418. err = os.MkdirAll(baseDir, os.ModePerm)
  1419. if err != nil {
  1420. return err
  1421. }
  1422. }
  1423. content := make([]byte, size)
  1424. _, err := rand.Read(content)
  1425. if err != nil {
  1426. return err
  1427. }
  1428. return ioutil.WriteFile(path, content, os.ModePerm)
  1429. }