cryptfs_test.go 9.4 KB

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