cryptfs_test.go 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. package ftpd_test
  2. import (
  3. "crypto/sha256"
  4. "fmt"
  5. "hash"
  6. "io"
  7. "net/http"
  8. "os"
  9. "path"
  10. "path/filepath"
  11. "testing"
  12. "time"
  13. "github.com/minio/sio"
  14. "github.com/stretchr/testify/assert"
  15. "github.com/drakkan/sftpgo/v2/common"
  16. "github.com/drakkan/sftpgo/v2/dataprovider"
  17. "github.com/drakkan/sftpgo/v2/httpdtest"
  18. "github.com/drakkan/sftpgo/v2/kms"
  19. "github.com/drakkan/sftpgo/v2/vfs"
  20. )
  21. func TestBasicFTPHandlingCryptFs(t *testing.T) {
  22. u := getTestUserWithCryptFs()
  23. u.QuotaSize = 6553600
  24. user, _, err := httpdtest.AddUser(u, http.StatusCreated)
  25. assert.NoError(t, err)
  26. client, err := getFTPClient(user, true, nil)
  27. if assert.NoError(t, err) {
  28. assert.Len(t, common.Connections.GetStats(), 1)
  29. testFilePath := filepath.Join(homeBasePath, testFileName)
  30. testFileSize := int64(65535)
  31. encryptedFileSize, err := getEncryptedFileSize(testFileSize)
  32. assert.NoError(t, err)
  33. expectedQuotaSize := encryptedFileSize
  34. expectedQuotaFiles := 1
  35. err = createTestFile(testFilePath, testFileSize)
  36. assert.NoError(t, err)
  37. err = checkBasicFTP(client)
  38. assert.NoError(t, err)
  39. err = ftpUploadFile(testFilePath, path.Join("/missing_dir", testFileName), testFileSize, client, 0)
  40. assert.Error(t, err)
  41. err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
  42. assert.NoError(t, err)
  43. // overwrite an existing file
  44. err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
  45. assert.NoError(t, err)
  46. localDownloadPath := filepath.Join(homeBasePath, testDLFileName)
  47. err = ftpDownloadFile(testFileName, localDownloadPath, testFileSize, client, 0)
  48. assert.NoError(t, err)
  49. info, err := os.Stat(localDownloadPath)
  50. if assert.NoError(t, err) {
  51. assert.Equal(t, testFileSize, info.Size())
  52. }
  53. list, err := client.List(".")
  54. if assert.NoError(t, err) {
  55. assert.Len(t, list, 1)
  56. assert.Equal(t, testFileSize, int64(list[0].Size))
  57. }
  58. user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
  59. assert.NoError(t, err)
  60. assert.Equal(t, expectedQuotaFiles, user.UsedQuotaFiles)
  61. assert.Equal(t, expectedQuotaSize, user.UsedQuotaSize)
  62. err = client.Rename(testFileName, testFileName+"1")
  63. assert.NoError(t, err)
  64. err = client.Delete(testFileName)
  65. assert.Error(t, err)
  66. err = client.Delete(testFileName + "1")
  67. assert.NoError(t, err)
  68. user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
  69. assert.NoError(t, err)
  70. assert.Equal(t, expectedQuotaFiles-1, user.UsedQuotaFiles)
  71. assert.Equal(t, expectedQuotaSize-encryptedFileSize, user.UsedQuotaSize)
  72. curDir, err := client.CurrentDir()
  73. if assert.NoError(t, err) {
  74. assert.Equal(t, "/", curDir)
  75. }
  76. testDir := "testDir"
  77. err = client.MakeDir(testDir)
  78. assert.NoError(t, err)
  79. err = client.ChangeDir(testDir)
  80. assert.NoError(t, err)
  81. curDir, err = client.CurrentDir()
  82. if assert.NoError(t, err) {
  83. assert.Equal(t, path.Join("/", testDir), curDir)
  84. }
  85. err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
  86. assert.NoError(t, err)
  87. size, err := client.FileSize(path.Join("/", testDir, testFileName))
  88. assert.NoError(t, err)
  89. assert.Equal(t, testFileSize, size)
  90. err = client.ChangeDirToParent()
  91. assert.NoError(t, err)
  92. curDir, err = client.CurrentDir()
  93. if assert.NoError(t, err) {
  94. assert.Equal(t, "/", curDir)
  95. }
  96. err = client.Delete(path.Join("/", testDir, testFileName))
  97. assert.NoError(t, err)
  98. err = client.Delete(testDir)
  99. assert.Error(t, err)
  100. err = client.RemoveDir(testDir)
  101. assert.NoError(t, err)
  102. err = os.Remove(testFilePath)
  103. assert.NoError(t, err)
  104. err = os.Remove(localDownloadPath)
  105. assert.NoError(t, err)
  106. err = client.Quit()
  107. assert.NoError(t, err)
  108. }
  109. _, err = httpdtest.RemoveUser(user, http.StatusOK)
  110. assert.NoError(t, err)
  111. err = os.RemoveAll(user.GetHomeDir())
  112. assert.NoError(t, err)
  113. assert.Eventually(t, func() bool { return len(common.Connections.GetStats()) == 0 }, 1*time.Second, 50*time.Millisecond)
  114. assert.Eventually(t, func() bool { return common.Connections.GetClientConnections() == 0 }, 1000*time.Millisecond,
  115. 50*time.Millisecond)
  116. }
  117. func TestZeroBytesTransfersCryptFs(t *testing.T) {
  118. u := getTestUserWithCryptFs()
  119. user, _, err := httpdtest.AddUser(u, http.StatusCreated)
  120. assert.NoError(t, err)
  121. client, err := getFTPClient(user, true, nil)
  122. if assert.NoError(t, err) {
  123. testFileName := "testfilename"
  124. err = checkBasicFTP(client)
  125. assert.NoError(t, err)
  126. localDownloadPath := filepath.Join(homeBasePath, "emptydownload")
  127. err = os.WriteFile(localDownloadPath, []byte(""), os.ModePerm)
  128. assert.NoError(t, err)
  129. err = ftpUploadFile(localDownloadPath, testFileName, 0, client, 0)
  130. assert.NoError(t, err)
  131. size, err := client.FileSize(testFileName)
  132. assert.NoError(t, err)
  133. assert.Equal(t, int64(0), size)
  134. err = os.Remove(localDownloadPath)
  135. assert.NoError(t, err)
  136. assert.NoFileExists(t, localDownloadPath)
  137. err = ftpDownloadFile(testFileName, localDownloadPath, 0, client, 0)
  138. assert.NoError(t, err)
  139. info, err := os.Stat(localDownloadPath)
  140. if assert.NoError(t, err) {
  141. assert.Equal(t, int64(0), info.Size())
  142. }
  143. err = client.Quit()
  144. assert.NoError(t, err)
  145. err = os.Remove(localDownloadPath)
  146. assert.NoError(t, err)
  147. }
  148. _, err = httpdtest.RemoveUser(user, http.StatusOK)
  149. assert.NoError(t, err)
  150. err = os.RemoveAll(user.GetHomeDir())
  151. assert.NoError(t, err)
  152. }
  153. func TestResumeCryptFs(t *testing.T) {
  154. u := getTestUserWithCryptFs()
  155. user, _, err := httpdtest.AddUser(u, http.StatusCreated)
  156. assert.NoError(t, err)
  157. client, err := getFTPClient(user, true, nil)
  158. if assert.NoError(t, err) {
  159. testFilePath := filepath.Join(homeBasePath, testFileName)
  160. data := []byte("test data")
  161. err = os.WriteFile(testFilePath, data, os.ModePerm)
  162. assert.NoError(t, err)
  163. err = ftpUploadFile(testFilePath, testFileName, int64(len(data)), client, 0)
  164. assert.NoError(t, err)
  165. // resuming uploads is not supported
  166. err = ftpUploadFile(testFilePath, testFileName, int64(len(data)+5), client, 5)
  167. assert.Error(t, err)
  168. localDownloadPath := filepath.Join(homeBasePath, testDLFileName)
  169. err = ftpDownloadFile(testFileName, localDownloadPath, int64(4), client, 5)
  170. assert.NoError(t, err)
  171. readed, err := os.ReadFile(localDownloadPath)
  172. assert.NoError(t, err)
  173. assert.Equal(t, data[5:], readed)
  174. err = ftpDownloadFile(testFileName, localDownloadPath, int64(8), client, 1)
  175. assert.NoError(t, err)
  176. readed, err = os.ReadFile(localDownloadPath)
  177. assert.NoError(t, err)
  178. assert.Equal(t, data[1:], readed)
  179. err = ftpDownloadFile(testFileName, localDownloadPath, int64(0), client, 9)
  180. assert.NoError(t, err)
  181. err = client.Delete(testFileName)
  182. assert.NoError(t, err)
  183. err = ftpUploadFile(testFilePath, testFileName, int64(len(data)), client, 0)
  184. assert.NoError(t, err)
  185. // now append to a file
  186. srcFile, err := os.Open(testFilePath)
  187. if assert.NoError(t, err) {
  188. err = client.Append(testFileName, srcFile)
  189. assert.Error(t, err)
  190. err = srcFile.Close()
  191. assert.NoError(t, err)
  192. size, err := client.FileSize(testFileName)
  193. assert.NoError(t, err)
  194. assert.Equal(t, int64(len(data)), size)
  195. err = ftpDownloadFile(testFileName, localDownloadPath, int64(len(data)), client, 0)
  196. assert.NoError(t, err)
  197. readed, err = os.ReadFile(localDownloadPath)
  198. assert.NoError(t, err)
  199. assert.Equal(t, data, readed)
  200. }
  201. // now test a download resume using a bigger file
  202. testFileSize := int64(655352)
  203. err = createTestFile(testFilePath, testFileSize)
  204. assert.NoError(t, err)
  205. initialHash, err := computeHashForFile(sha256.New(), testFilePath)
  206. assert.NoError(t, err)
  207. err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
  208. assert.NoError(t, err)
  209. err = ftpDownloadFile(testFileName, localDownloadPath, testFileSize, client, 0)
  210. assert.NoError(t, err)
  211. downloadHash, err := computeHashForFile(sha256.New(), localDownloadPath)
  212. assert.NoError(t, err)
  213. assert.Equal(t, initialHash, downloadHash)
  214. err = os.Truncate(localDownloadPath, 32767)
  215. assert.NoError(t, err)
  216. err = ftpDownloadFile(testFileName, localDownloadPath+"_partial", testFileSize-32767, client, 32767)
  217. assert.NoError(t, err)
  218. file, err := os.OpenFile(localDownloadPath, os.O_APPEND|os.O_WRONLY, os.ModePerm)
  219. assert.NoError(t, err)
  220. file1, err := os.Open(localDownloadPath + "_partial")
  221. assert.NoError(t, err)
  222. _, err = io.Copy(file, file1)
  223. assert.NoError(t, err)
  224. err = file.Close()
  225. assert.NoError(t, err)
  226. err = file1.Close()
  227. assert.NoError(t, err)
  228. downloadHash, err = computeHashForFile(sha256.New(), localDownloadPath)
  229. assert.NoError(t, err)
  230. assert.Equal(t, initialHash, downloadHash)
  231. err = client.Quit()
  232. assert.NoError(t, err)
  233. err = os.Remove(testFilePath)
  234. assert.NoError(t, err)
  235. err = os.Remove(localDownloadPath)
  236. assert.NoError(t, err)
  237. err = os.Remove(localDownloadPath + "_partial")
  238. assert.NoError(t, err)
  239. }
  240. _, err = httpdtest.RemoveUser(user, http.StatusOK)
  241. assert.NoError(t, err)
  242. err = os.RemoveAll(user.GetHomeDir())
  243. assert.NoError(t, err)
  244. }
  245. func getTestUserWithCryptFs() dataprovider.User {
  246. user := getTestUser()
  247. user.FsConfig.Provider = vfs.CryptedFilesystemProvider
  248. user.FsConfig.CryptConfig.Passphrase = kms.NewPlainSecret("testPassphrase")
  249. return user
  250. }
  251. func getEncryptedFileSize(size int64) (int64, error) {
  252. encSize, err := sio.EncryptedSize(uint64(size))
  253. return int64(encSize) + 33, err
  254. }
  255. func computeHashForFile(hasher hash.Hash, path string) (string, error) {
  256. hash := ""
  257. f, err := os.Open(path)
  258. if err != nil {
  259. return hash, err
  260. }
  261. defer f.Close()
  262. _, err = io.Copy(hasher, f)
  263. if err == nil {
  264. hash = fmt.Sprintf("%x", hasher.Sum(nil))
  265. }
  266. return hash, err
  267. }