cryptfs_test.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. // Copyright (C) 2019-2023 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 TestZeroBytesTransfersCryptFs(t *testing.T) {
  132. u := getTestUserWithCryptFs()
  133. user, _, err := httpdtest.AddUser(u, http.StatusCreated)
  134. assert.NoError(t, err)
  135. client, err := getFTPClient(user, true, nil)
  136. if assert.NoError(t, err) {
  137. testFileName := "testfilename"
  138. err = checkBasicFTP(client)
  139. assert.NoError(t, err)
  140. localDownloadPath := filepath.Join(homeBasePath, "emptydownload")
  141. err = os.WriteFile(localDownloadPath, []byte(""), os.ModePerm)
  142. assert.NoError(t, err)
  143. err = ftpUploadFile(localDownloadPath, testFileName, 0, client, 0)
  144. assert.NoError(t, err)
  145. size, err := client.FileSize(testFileName)
  146. assert.NoError(t, err)
  147. assert.Equal(t, int64(0), size)
  148. err = os.Remove(localDownloadPath)
  149. assert.NoError(t, err)
  150. assert.NoFileExists(t, localDownloadPath)
  151. err = ftpDownloadFile(testFileName, localDownloadPath, 0, client, 0)
  152. assert.NoError(t, err)
  153. info, err := os.Stat(localDownloadPath)
  154. if assert.NoError(t, err) {
  155. assert.Equal(t, int64(0), info.Size())
  156. }
  157. err = client.Quit()
  158. assert.NoError(t, err)
  159. err = os.Remove(localDownloadPath)
  160. assert.NoError(t, err)
  161. }
  162. _, err = httpdtest.RemoveUser(user, http.StatusOK)
  163. assert.NoError(t, err)
  164. err = os.RemoveAll(user.GetHomeDir())
  165. assert.NoError(t, err)
  166. }
  167. func TestResumeCryptFs(t *testing.T) {
  168. u := getTestUserWithCryptFs()
  169. user, _, err := httpdtest.AddUser(u, http.StatusCreated)
  170. assert.NoError(t, err)
  171. client, err := getFTPClient(user, true, nil)
  172. if assert.NoError(t, err) {
  173. testFilePath := filepath.Join(homeBasePath, testFileName)
  174. data := []byte("test data")
  175. err = os.WriteFile(testFilePath, data, os.ModePerm)
  176. assert.NoError(t, err)
  177. err = ftpUploadFile(testFilePath, testFileName, int64(len(data)), client, 0)
  178. assert.NoError(t, err)
  179. // resuming uploads is not supported
  180. err = ftpUploadFile(testFilePath, testFileName, int64(len(data)+5), client, 5)
  181. assert.Error(t, err)
  182. localDownloadPath := filepath.Join(homeBasePath, testDLFileName)
  183. err = ftpDownloadFile(testFileName, localDownloadPath, int64(4), client, 5)
  184. assert.NoError(t, err)
  185. readed, err := os.ReadFile(localDownloadPath)
  186. assert.NoError(t, err)
  187. assert.Equal(t, data[5:], readed)
  188. err = ftpDownloadFile(testFileName, localDownloadPath, int64(8), client, 1)
  189. assert.NoError(t, err)
  190. readed, err = os.ReadFile(localDownloadPath)
  191. assert.NoError(t, err)
  192. assert.Equal(t, data[1:], readed)
  193. err = ftpDownloadFile(testFileName, localDownloadPath, int64(0), client, 9)
  194. assert.NoError(t, err)
  195. err = client.Delete(testFileName)
  196. assert.NoError(t, err)
  197. err = ftpUploadFile(testFilePath, testFileName, int64(len(data)), client, 0)
  198. assert.NoError(t, err)
  199. // now append to a file
  200. srcFile, err := os.Open(testFilePath)
  201. if assert.NoError(t, err) {
  202. err = client.Append(testFileName, srcFile)
  203. assert.Error(t, err)
  204. err = srcFile.Close()
  205. assert.NoError(t, err)
  206. size, err := client.FileSize(testFileName)
  207. assert.NoError(t, err)
  208. assert.Equal(t, int64(len(data)), size)
  209. err = ftpDownloadFile(testFileName, localDownloadPath, int64(len(data)), client, 0)
  210. assert.NoError(t, err)
  211. readed, err = os.ReadFile(localDownloadPath)
  212. assert.NoError(t, err)
  213. assert.Equal(t, data, readed)
  214. }
  215. // now test a download resume using a bigger file
  216. testFileSize := int64(655352)
  217. err = createTestFile(testFilePath, testFileSize)
  218. assert.NoError(t, err)
  219. initialHash, err := computeHashForFile(sha256.New(), testFilePath)
  220. assert.NoError(t, err)
  221. err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
  222. assert.NoError(t, err)
  223. err = ftpDownloadFile(testFileName, localDownloadPath, testFileSize, client, 0)
  224. assert.NoError(t, err)
  225. downloadHash, err := computeHashForFile(sha256.New(), localDownloadPath)
  226. assert.NoError(t, err)
  227. assert.Equal(t, initialHash, downloadHash)
  228. err = os.Truncate(localDownloadPath, 32767)
  229. assert.NoError(t, err)
  230. err = ftpDownloadFile(testFileName, localDownloadPath+"_partial", testFileSize-32767, client, 32767)
  231. assert.NoError(t, err)
  232. file, err := os.OpenFile(localDownloadPath, os.O_APPEND|os.O_WRONLY, os.ModePerm)
  233. assert.NoError(t, err)
  234. file1, err := os.Open(localDownloadPath + "_partial")
  235. assert.NoError(t, err)
  236. _, err = io.Copy(file, file1)
  237. assert.NoError(t, err)
  238. err = file.Close()
  239. assert.NoError(t, err)
  240. err = file1.Close()
  241. assert.NoError(t, err)
  242. downloadHash, err = computeHashForFile(sha256.New(), localDownloadPath)
  243. assert.NoError(t, err)
  244. assert.Equal(t, initialHash, downloadHash)
  245. err = client.Quit()
  246. assert.NoError(t, err)
  247. err = os.Remove(testFilePath)
  248. assert.NoError(t, err)
  249. err = os.Remove(localDownloadPath)
  250. assert.NoError(t, err)
  251. err = os.Remove(localDownloadPath + "_partial")
  252. assert.NoError(t, err)
  253. }
  254. _, err = httpdtest.RemoveUser(user, http.StatusOK)
  255. assert.NoError(t, err)
  256. err = os.RemoveAll(user.GetHomeDir())
  257. assert.NoError(t, err)
  258. }
  259. func getTestUserWithCryptFs() dataprovider.User {
  260. user := getTestUser()
  261. user.FsConfig.Provider = sdk.CryptedFilesystemProvider
  262. user.FsConfig.CryptConfig.Passphrase = kms.NewPlainSecret("testPassphrase")
  263. return user
  264. }
  265. func getEncryptedFileSize(size int64) (int64, error) {
  266. encSize, err := sio.EncryptedSize(uint64(size))
  267. return int64(encSize) + 33, err
  268. }
  269. func computeHashForFile(hasher hash.Hash, path string) (string, error) {
  270. hash := ""
  271. f, err := os.Open(path)
  272. if err != nil {
  273. return hash, err
  274. }
  275. defer f.Close()
  276. _, err = io.Copy(hasher, f)
  277. if err == nil {
  278. hash = fmt.Sprintf("%x", hasher.Sum(nil))
  279. }
  280. return hash, err
  281. }