cryptfs_test.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. // Copyright (C) 2019 Nicola Murino
  2. //
  3. // This program is free software: you can redistribute it and/or modify
  4. // it under the terms of the GNU Affero General Public License as published
  5. // by the Free Software Foundation, version 3.
  6. //
  7. // This program is distributed in the hope that it will be useful,
  8. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. // GNU Affero General Public License for more details.
  11. //
  12. // You should have received a copy of the GNU Affero General Public License
  13. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  14. package ftpd_test
  15. import (
  16. "crypto/sha256"
  17. "fmt"
  18. "hash"
  19. "io"
  20. "net/http"
  21. "os"
  22. "path"
  23. "path/filepath"
  24. "testing"
  25. "time"
  26. "github.com/minio/sio"
  27. "github.com/sftpgo/sdk"
  28. "github.com/stretchr/testify/assert"
  29. "github.com/drakkan/sftpgo/v2/internal/common"
  30. "github.com/drakkan/sftpgo/v2/internal/dataprovider"
  31. "github.com/drakkan/sftpgo/v2/internal/httpdtest"
  32. "github.com/drakkan/sftpgo/v2/internal/kms"
  33. )
  34. func TestBasicFTPHandlingCryptFs(t *testing.T) {
  35. u := getTestUserWithCryptFs()
  36. u.QuotaSize = 6553600
  37. user, _, err := httpdtest.AddUser(u, http.StatusCreated)
  38. assert.NoError(t, err)
  39. client, err := getFTPClient(user, true, nil)
  40. if assert.NoError(t, err) {
  41. assert.Len(t, common.Connections.GetStats(""), 1)
  42. testFilePath := filepath.Join(homeBasePath, testFileName)
  43. testFileSize := int64(65535)
  44. encryptedFileSize, err := getEncryptedFileSize(testFileSize)
  45. assert.NoError(t, err)
  46. expectedQuotaSize := encryptedFileSize
  47. expectedQuotaFiles := 1
  48. err = createTestFile(testFilePath, testFileSize)
  49. assert.NoError(t, err)
  50. err = checkBasicFTP(client)
  51. assert.NoError(t, err)
  52. err = ftpUploadFile(testFilePath, path.Join("/missing_dir", testFileName), testFileSize, client, 0)
  53. assert.Error(t, err)
  54. err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
  55. assert.NoError(t, err)
  56. // overwrite an existing file
  57. err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
  58. assert.NoError(t, err)
  59. localDownloadPath := filepath.Join(homeBasePath, testDLFileName)
  60. err = ftpDownloadFile(testFileName, localDownloadPath, testFileSize, client, 0)
  61. assert.NoError(t, err)
  62. info, err := os.Stat(localDownloadPath)
  63. if assert.NoError(t, err) {
  64. assert.Equal(t, testFileSize, info.Size())
  65. }
  66. list, err := client.List(".")
  67. if assert.NoError(t, err) {
  68. if assert.Len(t, list, 1) {
  69. assert.Equal(t, testFileSize, int64(list[0].Size))
  70. }
  71. }
  72. user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
  73. assert.NoError(t, err)
  74. assert.Equal(t, expectedQuotaFiles, user.UsedQuotaFiles)
  75. assert.Equal(t, expectedQuotaSize, user.UsedQuotaSize)
  76. err = client.Rename(testFileName, testFileName+"1")
  77. assert.NoError(t, err)
  78. err = client.Delete(testFileName)
  79. assert.Error(t, err)
  80. err = client.Delete(testFileName + "1")
  81. assert.NoError(t, err)
  82. user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
  83. assert.NoError(t, err)
  84. assert.Equal(t, expectedQuotaFiles-1, user.UsedQuotaFiles)
  85. assert.Equal(t, expectedQuotaSize-encryptedFileSize, user.UsedQuotaSize)
  86. curDir, err := client.CurrentDir()
  87. if assert.NoError(t, err) {
  88. assert.Equal(t, "/", curDir)
  89. }
  90. testDir := "testDir"
  91. err = client.MakeDir(testDir)
  92. assert.NoError(t, err)
  93. err = client.ChangeDir(testDir)
  94. assert.NoError(t, err)
  95. curDir, err = client.CurrentDir()
  96. if assert.NoError(t, err) {
  97. assert.Equal(t, path.Join("/", testDir), curDir)
  98. }
  99. err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
  100. assert.NoError(t, err)
  101. size, err := client.FileSize(path.Join("/", testDir, testFileName))
  102. assert.NoError(t, err)
  103. assert.Equal(t, testFileSize, size)
  104. err = client.ChangeDirToParent()
  105. assert.NoError(t, err)
  106. curDir, err = client.CurrentDir()
  107. if assert.NoError(t, err) {
  108. assert.Equal(t, "/", curDir)
  109. }
  110. err = client.Delete(path.Join("/", testDir, testFileName))
  111. assert.NoError(t, err)
  112. err = client.Delete(testDir)
  113. assert.Error(t, err)
  114. err = client.RemoveDir(testDir)
  115. assert.NoError(t, err)
  116. err = os.Remove(testFilePath)
  117. assert.NoError(t, err)
  118. err = os.Remove(localDownloadPath)
  119. assert.NoError(t, err)
  120. err = client.Quit()
  121. assert.NoError(t, err)
  122. }
  123. _, err = httpdtest.RemoveUser(user, http.StatusOK)
  124. assert.NoError(t, err)
  125. err = os.RemoveAll(user.GetHomeDir())
  126. assert.NoError(t, err)
  127. assert.Eventually(t, func() bool { return len(common.Connections.GetStats("")) == 0 }, 1*time.Second, 50*time.Millisecond)
  128. assert.Eventually(t, func() bool { return common.Connections.GetClientConnections() == 0 }, 1000*time.Millisecond,
  129. 50*time.Millisecond)
  130. }
  131. func TestBufferedCryptFs(t *testing.T) {
  132. u := getTestUserWithCryptFs()
  133. u.FsConfig.CryptConfig.OSFsConfig = sdk.OSFsConfig{
  134. ReadBufferSize: 1,
  135. WriteBufferSize: 1,
  136. }
  137. user, _, err := httpdtest.AddUser(u, http.StatusCreated)
  138. assert.NoError(t, err)
  139. client, err := getFTPClient(user, true, nil)
  140. if assert.NoError(t, err) {
  141. testFilePath := filepath.Join(homeBasePath, testFileName)
  142. testFileSize := int64(65535)
  143. err = createTestFile(testFilePath, testFileSize)
  144. assert.NoError(t, err)
  145. err = checkBasicFTP(client)
  146. assert.NoError(t, err)
  147. err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
  148. assert.NoError(t, err)
  149. // overwrite an existing file
  150. err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
  151. assert.NoError(t, err)
  152. localDownloadPath := filepath.Join(homeBasePath, testDLFileName)
  153. err = ftpDownloadFile(testFileName, localDownloadPath, testFileSize, client, 0)
  154. assert.NoError(t, err)
  155. info, err := os.Stat(localDownloadPath)
  156. if assert.NoError(t, err) {
  157. assert.Equal(t, testFileSize, info.Size())
  158. }
  159. err = os.Remove(testFilePath)
  160. assert.NoError(t, err)
  161. err = os.Remove(localDownloadPath)
  162. assert.NoError(t, err)
  163. err = client.Quit()
  164. assert.NoError(t, err)
  165. }
  166. _, err = httpdtest.RemoveUser(user, http.StatusOK)
  167. assert.NoError(t, err)
  168. err = os.RemoveAll(user.GetHomeDir())
  169. assert.NoError(t, err)
  170. assert.Eventually(t, func() bool { return len(common.Connections.GetStats("")) == 0 }, 1*time.Second, 50*time.Millisecond)
  171. assert.Eventually(t, func() bool { return common.Connections.GetClientConnections() == 0 }, 1000*time.Millisecond,
  172. 50*time.Millisecond)
  173. }
  174. func TestZeroBytesTransfersCryptFs(t *testing.T) {
  175. u := getTestUserWithCryptFs()
  176. user, _, err := httpdtest.AddUser(u, http.StatusCreated)
  177. assert.NoError(t, err)
  178. client, err := getFTPClient(user, true, nil)
  179. if assert.NoError(t, err) {
  180. testFileName := "testfilename"
  181. err = checkBasicFTP(client)
  182. assert.NoError(t, err)
  183. localDownloadPath := filepath.Join(homeBasePath, "emptydownload")
  184. err = os.WriteFile(localDownloadPath, []byte(""), os.ModePerm)
  185. assert.NoError(t, err)
  186. err = ftpUploadFile(localDownloadPath, testFileName, 0, client, 0)
  187. assert.NoError(t, err)
  188. size, err := client.FileSize(testFileName)
  189. assert.NoError(t, err)
  190. assert.Equal(t, int64(0), size)
  191. err = os.Remove(localDownloadPath)
  192. assert.NoError(t, err)
  193. assert.NoFileExists(t, localDownloadPath)
  194. err = ftpDownloadFile(testFileName, localDownloadPath, 0, client, 0)
  195. assert.NoError(t, err)
  196. info, err := os.Stat(localDownloadPath)
  197. if assert.NoError(t, err) {
  198. assert.Equal(t, int64(0), info.Size())
  199. }
  200. err = client.Quit()
  201. assert.NoError(t, err)
  202. err = os.Remove(localDownloadPath)
  203. assert.NoError(t, err)
  204. }
  205. _, err = httpdtest.RemoveUser(user, http.StatusOK)
  206. assert.NoError(t, err)
  207. err = os.RemoveAll(user.GetHomeDir())
  208. assert.NoError(t, err)
  209. }
  210. func TestResumeCryptFs(t *testing.T) {
  211. u := getTestUserWithCryptFs()
  212. user, _, err := httpdtest.AddUser(u, http.StatusCreated)
  213. assert.NoError(t, err)
  214. client, err := getFTPClient(user, true, nil)
  215. if assert.NoError(t, err) {
  216. testFilePath := filepath.Join(homeBasePath, testFileName)
  217. data := []byte("test data")
  218. err = os.WriteFile(testFilePath, data, os.ModePerm)
  219. assert.NoError(t, err)
  220. err = ftpUploadFile(testFilePath, testFileName, int64(len(data)), client, 0)
  221. assert.NoError(t, err)
  222. // resuming uploads is not supported
  223. err = ftpUploadFile(testFilePath, testFileName, int64(len(data)+5), client, 5)
  224. assert.Error(t, err)
  225. localDownloadPath := filepath.Join(homeBasePath, testDLFileName)
  226. err = ftpDownloadFile(testFileName, localDownloadPath, int64(4), client, 5)
  227. assert.NoError(t, err)
  228. readed, err := os.ReadFile(localDownloadPath)
  229. assert.NoError(t, err)
  230. assert.Equal(t, data[5:], readed)
  231. err = ftpDownloadFile(testFileName, localDownloadPath, int64(8), client, 1)
  232. assert.NoError(t, err)
  233. readed, err = os.ReadFile(localDownloadPath)
  234. assert.NoError(t, err)
  235. assert.Equal(t, data[1:], readed)
  236. err = ftpDownloadFile(testFileName, localDownloadPath, int64(0), client, 9)
  237. assert.NoError(t, err)
  238. err = client.Delete(testFileName)
  239. assert.NoError(t, err)
  240. err = ftpUploadFile(testFilePath, testFileName, int64(len(data)), client, 0)
  241. assert.NoError(t, err)
  242. // now append to a file
  243. srcFile, err := os.Open(testFilePath)
  244. if assert.NoError(t, err) {
  245. err = client.Append(testFileName, srcFile)
  246. assert.Error(t, err)
  247. err = srcFile.Close()
  248. assert.NoError(t, err)
  249. size, err := client.FileSize(testFileName)
  250. assert.NoError(t, err)
  251. assert.Equal(t, int64(len(data)), size)
  252. err = ftpDownloadFile(testFileName, localDownloadPath, int64(len(data)), client, 0)
  253. assert.NoError(t, err)
  254. readed, err = os.ReadFile(localDownloadPath)
  255. assert.NoError(t, err)
  256. assert.Equal(t, data, readed)
  257. }
  258. // now test a download resume using a bigger file
  259. testFileSize := int64(655352)
  260. err = createTestFile(testFilePath, testFileSize)
  261. assert.NoError(t, err)
  262. initialHash, err := computeHashForFile(sha256.New(), testFilePath)
  263. assert.NoError(t, err)
  264. err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
  265. assert.NoError(t, err)
  266. err = ftpDownloadFile(testFileName, localDownloadPath, testFileSize, client, 0)
  267. assert.NoError(t, err)
  268. downloadHash, err := computeHashForFile(sha256.New(), localDownloadPath)
  269. assert.NoError(t, err)
  270. assert.Equal(t, initialHash, downloadHash)
  271. err = os.Truncate(localDownloadPath, 32767)
  272. assert.NoError(t, err)
  273. err = ftpDownloadFile(testFileName, localDownloadPath+"_partial", testFileSize-32767, client, 32767) //nolint:goconst
  274. assert.NoError(t, err)
  275. file, err := os.OpenFile(localDownloadPath, os.O_APPEND|os.O_WRONLY, os.ModePerm)
  276. assert.NoError(t, err)
  277. file1, err := os.Open(localDownloadPath + "_partial") //nolint:goconst
  278. assert.NoError(t, err)
  279. _, err = io.Copy(file, file1)
  280. assert.NoError(t, err)
  281. err = file.Close()
  282. assert.NoError(t, err)
  283. err = file1.Close()
  284. assert.NoError(t, err)
  285. downloadHash, err = computeHashForFile(sha256.New(), localDownloadPath)
  286. assert.NoError(t, err)
  287. assert.Equal(t, initialHash, downloadHash)
  288. err = client.Quit()
  289. assert.NoError(t, err)
  290. err = os.Remove(testFilePath)
  291. assert.NoError(t, err)
  292. err = os.Remove(localDownloadPath)
  293. assert.NoError(t, err)
  294. err = os.Remove(localDownloadPath + "_partial")
  295. assert.NoError(t, err)
  296. }
  297. _, err = httpdtest.RemoveUser(user, http.StatusOK)
  298. assert.NoError(t, err)
  299. err = os.RemoveAll(user.GetHomeDir())
  300. assert.NoError(t, err)
  301. }
  302. func getTestUserWithCryptFs() dataprovider.User {
  303. user := getTestUser()
  304. user.FsConfig.Provider = sdk.CryptedFilesystemProvider
  305. user.FsConfig.CryptConfig.Passphrase = kms.NewPlainSecret("testPassphrase")
  306. return user
  307. }
  308. func getEncryptedFileSize(size int64) (int64, error) {
  309. encSize, err := sio.EncryptedSize(uint64(size))
  310. return int64(encSize) + 33, err
  311. }
  312. func computeHashForFile(hasher hash.Hash, path string) (string, error) {
  313. hash := ""
  314. f, err := os.Open(path)
  315. if err != nil {
  316. return hash, err
  317. }
  318. defer f.Close()
  319. _, err = io.Copy(hasher, f)
  320. if err == nil {
  321. hash = fmt.Sprintf("%x", hasher.Sum(nil))
  322. }
  323. return hash, err
  324. }