ftpd_test.go 49 KB

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