1package ftpd_test
2
3import (
4	"crypto/rand"
5	"crypto/sha256"
6	"crypto/tls"
7	"encoding/hex"
8	"encoding/json"
9	"fmt"
10	"io"
11	"net"
12	"net/http"
13	"os"
14	"os/exec"
15	"path"
16	"path/filepath"
17	"runtime"
18	"strconv"
19	"testing"
20	"time"
21
22	ftpserver "github.com/fclairamb/ftpserverlib"
23	"github.com/jlaffaye/ftp"
24	"github.com/pquerna/otp"
25	"github.com/pquerna/otp/totp"
26	"github.com/rs/zerolog"
27	"github.com/stretchr/testify/assert"
28	"github.com/stretchr/testify/require"
29
30	"github.com/drakkan/sftpgo/v2/common"
31	"github.com/drakkan/sftpgo/v2/config"
32	"github.com/drakkan/sftpgo/v2/dataprovider"
33	"github.com/drakkan/sftpgo/v2/ftpd"
34	"github.com/drakkan/sftpgo/v2/httpdtest"
35	"github.com/drakkan/sftpgo/v2/kms"
36	"github.com/drakkan/sftpgo/v2/logger"
37	"github.com/drakkan/sftpgo/v2/mfa"
38	"github.com/drakkan/sftpgo/v2/sdk"
39	"github.com/drakkan/sftpgo/v2/sftpd"
40	"github.com/drakkan/sftpgo/v2/vfs"
41)
42
43const (
44	logSender       = "ftpdTesting"
45	ftpServerAddr   = "127.0.0.1:2121"
46	sftpServerAddr  = "127.0.0.1:2122"
47	ftpSrvAddrTLS   = "127.0.0.1:2124" // ftp server with implicit tls
48	defaultUsername = "test_user_ftp"
49	defaultPassword = "test_password"
50	configDir       = ".."
51	osWindows       = "windows"
52	ftpsCert        = `-----BEGIN CERTIFICATE-----
53MIICHTCCAaKgAwIBAgIUHnqw7QnB1Bj9oUsNpdb+ZkFPOxMwCgYIKoZIzj0EAwIw
54RTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGElu
55dGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMDAyMDQwOTUzMDRaFw0zMDAyMDEw
56OTUzMDRaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYD
57VQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwdjAQBgcqhkjOPQIBBgUrgQQA
58IgNiAARCjRMqJ85rzMC998X5z761nJ+xL3bkmGVqWvrJ51t5OxV0v25NsOgR82CA
59NXUgvhVYs7vNFN+jxtb2aj6Xg+/2G/BNxkaFspIVCzgWkxiz7XE4lgUwX44FCXZM
603+JeUbKjUzBRMB0GA1UdDgQWBBRhLw+/o3+Z02MI/d4tmaMui9W16jAfBgNVHSME
61GDAWgBRhLw+/o3+Z02MI/d4tmaMui9W16jAPBgNVHRMBAf8EBTADAQH/MAoGCCqG
62SM49BAMCA2kAMGYCMQDqLt2lm8mE+tGgtjDmtFgdOcI72HSbRQ74D5rYTzgST1rY
63/8wTi5xl8TiFUyLMUsICMQC5ViVxdXbhuG7gX6yEqSkMKZICHpO8hqFwOD/uaFVI
64dV4vKmHUzwK/eIx+8Ay3neE=
65-----END CERTIFICATE-----`
66	ftpsKey = `-----BEGIN EC PARAMETERS-----
67BgUrgQQAIg==
68-----END EC PARAMETERS-----
69-----BEGIN EC PRIVATE KEY-----
70MIGkAgEBBDCfMNsN6miEE3rVyUPwElfiJSWaR5huPCzUenZOfJT04GAcQdWvEju3
71UM2lmBLIXpGgBwYFK4EEACKhZANiAARCjRMqJ85rzMC998X5z761nJ+xL3bkmGVq
72WvrJ51t5OxV0v25NsOgR82CANXUgvhVYs7vNFN+jxtb2aj6Xg+/2G/BNxkaFspIV
73CzgWkxiz7XE4lgUwX44FCXZM3+JeUbI=
74-----END EC PRIVATE KEY-----`
75	caCRT = `-----BEGIN CERTIFICATE-----
76MIIE5jCCAs6gAwIBAgIBATANBgkqhkiG9w0BAQsFADATMREwDwYDVQQDEwhDZXJ0
77QXV0aDAeFw0yMTAxMDIyMTIwNTVaFw0yMjA3MDIyMTMwNTJaMBMxETAPBgNVBAMT
78CENlcnRBdXRoMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA4Tiho5xW
79AC15JRkMwfp3/TJwI2As7MY5dele5cmdr5bHAE+sRKqC+Ti88OJWCV5saoyax/1S
80CjxJlQMZMl169P1QYJskKjdG2sdv6RLWLMgwSNRRjxp/Bw9dHdiEb9MjLgu28Jro
819peQkHcRHeMf5hM9WvlIJGrdzbC4hUehmqggcqgARainBkYjf0SwuWxHeu4nMqkp
82Ak5tcSTLCjHfEFHZ9Te0TIPG5YkWocQKyeLgu4lvuU+DD2W2lym+YVUtRMGs1Env
83k7p+N0DcGU26qfzZ2sF5ZXkqm7dBsGQB9pIxwc2Q8T1dCIyP9OQCKVILdc5aVFf1
84cryQFHYzYNNZXFlIBims5VV5Mgfp8ESHQSue+v6n6ykecLEyKt1F1Y/MWY/nWUSI
858zdq83jdBAZVjo9MSthxVn57/06s/hQca65IpcTZV2gX0a+eRlAVqaRbAhL3LaZe
86bYsW3WHKoUOftwemuep3nL51TzlXZVL7Oz/ClGaEOsnGG9KFO6jh+W768qC0zLQI
87CdE7v2Zex98sZteHCg9fGJHIaYoF0aJG5P3WI5oZf2fy7UIYN9ADLFZiorCXAZEh
88CSU6mDoRViZ4RGR9GZxbDZ9KYn7O8M/KCR72bkQg73TlMsk1zSXEw0MKLUjtsw6c
89rZ0Jt8t3sRatHO3JrYHALMt9vZfyNCZp0IsCAwEAAaNFMEMwDgYDVR0PAQH/BAQD
90AgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFO1yCNAGr/zQTJIi8lw3
91w5OiuBvMMA0GCSqGSIb3DQEBCwUAA4ICAQA6gCNuM7r8mnx674dm31GxBjQy5ZwB
927CxDzYEvL/oiZ3Tv3HlPfN2LAAsJUfGnghh9DOytenL2CTZWjl/emP5eijzmlP+9
93zva5I6CIMCf/eDDVsRdO244t0o4uG7+At0IgSDM3bpVaVb4RHZNjEziYChsEYY8d
94HK6iwuRSvFniV6yhR/Vj1Ymi9yZ5xclqseLXiQnUB0PkfIk23+7s42cXB16653fH
95O/FsPyKBLiKJArizLYQc12aP3QOrYoYD9+fAzIIzew7A5C0aanZCGzkuFpO6TRlD
96Tb7ry9Gf0DfPpCgxraH8tOcmnqp/ka3hjqo/SRnnTk0IFrmmLdarJvjD46rKwBo4
97MjyAIR1mQ5j8GTlSFBmSgETOQ/EYvO3FPLmra1Fh7L+DvaVzTpqI9fG3TuyyY+Ri
98Fby4ycTOGSZOe5Fh8lqkX5Y47mCUJ3zHzOA1vUJy2eTlMRGpu47Eb1++Vm6EzPUP
992EF5aD+zwcssh+atZvQbwxpgVqVcyLt91RSkKkmZQslh0rnlTb68yxvUnD3zw7So
100o6TAf9UvwVMEvdLT9NnFd6hwi2jcNte/h538GJwXeBb8EkfpqLKpTKyicnOdkamZ
1017E9zY8SHNRYMwB9coQ/W8NvufbCgkvOoLyMXk5edbXofXl3PhNGOlraWbghBnzf5
102r3rwjFsQOoZotA==
103-----END CERTIFICATE-----`
104	caCRL = `-----BEGIN X509 CRL-----
105MIICpzCBkAIBATANBgkqhkiG9w0BAQsFADATMREwDwYDVQQDEwhDZXJ0QXV0aBcN
106MjEwMTAyMjEzNDA1WhcNMjMwMTAyMjEzNDA1WjAkMCICEQC+l04DbHWMyC3fG09k
107VXf+Fw0yMTAxMDIyMTM0MDVaoCMwITAfBgNVHSMEGDAWgBTtcgjQBq/80EySIvJc
108N8OTorgbzDANBgkqhkiG9w0BAQsFAAOCAgEAEJ7z+uNc8sqtxlOhSdTGDzX/xput
109E857kFQkSlMnU2whQ8c+XpYrBLA5vIZJNSSwohTpM4+zVBX/bJpmu3wqqaArRO9/
110YcW5mQk9Anvb4WjQW1cHmtNapMTzoC9AiYt/OWPfy+P6JCgCr4Hy6LgQyIRL6bM9
111VYTalolOm1qa4Y5cIeT7iHq/91mfaqo8/6MYRjLl8DOTROpmw8OS9bCXkzGKdCat
112AbAzwkQUSauyoCQ10rpX+Y64w9ng3g4Dr20aCqPf5osaqplEJ2HTK8ljDTidlslv
1139anQj8ax3Su89vI8+hK+YbfVQwrThabgdSjQsn+veyx8GlP8WwHLAQ379KjZjWg+
114OlOSwBeU1vTdP0QcB8X5C2gVujAyuQekbaV86xzIBOj7vZdfHZ6ee30TZ2FKiMyg
1157/N2OqW0w77ChsjB4MSHJCfuTgIeg62GzuZXLM+Q2Z9LBdtm4Byg+sm/P52adOEg
116gVb2Zf4KSvsAmA0PIBlu449/QXUFcMxzLFy7mwTeZj2B4Ln0Hm0szV9f9R8MwMtB
117SyLYxVH+mgqaR6Jkk22Q/yYyLPaELfafX5gp/AIXG8n0zxfVaTvK3auSgb1Q6ZLS
1185QH9dSIsmZHlPq7GoSXmKpMdjUL8eaky/IMteioyXgsBiATzl5L2dsw6MTX3MDF0
119QbDK+MzhmbKfDxs=
120-----END X509 CRL-----`
121	client1Crt = `-----BEGIN CERTIFICATE-----
122MIIEITCCAgmgAwIBAgIRAIppZHoj1hM80D7WzTEKLuAwDQYJKoZIhvcNAQELBQAw
123EzERMA8GA1UEAxMIQ2VydEF1dGgwHhcNMjEwMTAyMjEyMzEwWhcNMjIwNzAyMjEz
124MDUxWjASMRAwDgYDVQQDEwdjbGllbnQxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
125MIIBCgKCAQEAoKbYY9MdF2kF/nhBESIiZTdVYtA8XL9xrIZyDj9EnCiTxHiVbJtH
126XVwszqSl5TRrotPmnmAQcX3r8OCk+z+RQZ0QQj257P3kG6q4rNnOcWCS5xEd20jP
127yhQ3m+hMGfZsotNTQze1ochuQgLUN6IPyPxZkH22ia3jX4iu1eo/QxeLYHj1UHw4
1283Cii9yE+j5kPUC21xmnrGKdUrB55NYLXHx6yTIqYR5znSOVB8oJi18/hwdZmH859
129DHhm0Hx1HrS+jbjI3+CMorZJ3WUyNf+CkiVLD3xYutPbxzEpwiqkG/XYzLH0habT
130cDcILo18n+o3jvem2KWBrDhyairjIDscwQIDAQABo3EwbzAOBgNVHQ8BAf8EBAMC
131A7gwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBSJ5GIv
132zIrE4ZSQt2+CGblKTDswizAfBgNVHSMEGDAWgBTtcgjQBq/80EySIvJcN8OTorgb
133zDANBgkqhkiG9w0BAQsFAAOCAgEALh4f5GhvNYNou0Ab04iQBbLEdOu2RlbK1B5n
134K9P/umYenBHMY/z6HT3+6tpcHsDuqE8UVdq3f3Gh4S2Gu9m8PRitT+cJ3gdo9Plm
1353rD4ufn/s6rGg3ppydXcedm17492tbccUDWOBZw3IO/ASVq13WPgT0/Kev7cPq0k
136sSdSNhVeXqx8Myc2/d+8GYyzbul2Kpfa7h9i24sK49E9ftnSmsIvngONo08eT1T0
1373wAOyK2981LIsHaAWcneShKFLDB6LeXIT9oitOYhiykhFlBZ4M1GNlSNfhQ8IIQP
138xbqMNXCLkW4/BtLhGEEcg0QVso6Kudl9rzgTfQknrdF7pHp6rS46wYUjoSyIY6dl
139oLmnoAVJX36J3QPWelePI9e07X2wrTfiZWewwgw3KNRWjd6/zfPLe7GoqXnK1S2z
140PT8qMfCaTwKTtUkzXuTFvQ8bAo2My/mS8FOcpkt2oQWeOsADHAUX7fz5BCoa2DL3
141k/7Mh4gVT+JYZEoTwCFuYHgMWFWe98naqHi9lB4yR981p1QgXgxO7qBeipagKY1F
142LlH1iwXUqZ3MZnkNA+4e1Fglsw3sa/rC+L98HnznJ/YbTfQbCP6aQ1qcOymrjMud
1437MrFwqZjtd/SK4Qx1VpK6jGEAtPgWBTUS3p9ayg6lqjMBjsmySWfvRsDQbq6P5Ct
144O/e3EH8=
145-----END CERTIFICATE-----`
146	client1Key = `-----BEGIN RSA PRIVATE KEY-----
147MIIEpAIBAAKCAQEAoKbYY9MdF2kF/nhBESIiZTdVYtA8XL9xrIZyDj9EnCiTxHiV
148bJtHXVwszqSl5TRrotPmnmAQcX3r8OCk+z+RQZ0QQj257P3kG6q4rNnOcWCS5xEd
14920jPyhQ3m+hMGfZsotNTQze1ochuQgLUN6IPyPxZkH22ia3jX4iu1eo/QxeLYHj1
150UHw43Cii9yE+j5kPUC21xmnrGKdUrB55NYLXHx6yTIqYR5znSOVB8oJi18/hwdZm
151H859DHhm0Hx1HrS+jbjI3+CMorZJ3WUyNf+CkiVLD3xYutPbxzEpwiqkG/XYzLH0
152habTcDcILo18n+o3jvem2KWBrDhyairjIDscwQIDAQABAoIBAEBSjVFqtbsp0byR
153aXvyrtLX1Ng7h++at2jca85Ihq//jyqbHTje8zPuNAKI6eNbmb0YGr5OuEa4pD9N
154ssDmMsKSoG/lRwwcm7h4InkSvBWpFShvMgUaohfHAHzsBYxfnh+TfULsi0y7c2n6
155t/2OZcOTRkkUDIITnXYiw93ibHHv2Mv2bBDu35kGrcK+c2dN5IL5ZjTjMRpbJTe2
15644RBJbdTxHBVSgoGBnugF+s2aEma6Ehsj70oyfoVpM6Aed5kGge0A5zA1JO7WCn9
157Ay/DzlULRXHjJIoRWd2NKvx5n3FNppUc9vJh2plRHalRooZ2+MjSf8HmXlvG2Hpb
158ScvmWgECgYEA1G+A/2KnxWsr/7uWIJ7ClcGCiNLdk17Pv3DZ3G4qUsU2ITftfIbb
159tU0Q/b19na1IY8Pjy9ptP7t74/hF5kky97cf1FA8F+nMj/k4+wO8QDI8OJfzVzh9
160PwielA5vbE+xmvis5Hdp8/od1Yrc/rPSy2TKtPFhvsqXjqoUmOAjDP8CgYEAwZjH
1619dt1sc2lx/rMxihlWEzQ3JPswKW9/LJAmbRBoSWF9FGNjbX7uhWtXRKJkzb8ZAwa
16288azluNo2oftbDD/+jw8b2cDgaJHlLAkSD4O1D1RthW7/LKD15qZ/oFsRb13NV85
163ZNKtwslXGbfVNyGKUVFm7fVA8vBAOUey+LKDFj8CgYEAg8WWstOzVdYguMTXXuyb
164ruEV42FJaDyLiSirOvxq7GTAKuLSQUg1yMRBIeQEo2X1XU0JZE3dLodRVhuO4EXP
165g7Dn4X7Th9HSvgvNuIacowWGLWSz4Qp9RjhGhXhezUSx2nseY6le46PmFavJYYSR
1664PBofMyt4PcyA6Cknh+KHmkCgYEAnTriG7ETE0a7v4DXUpB4TpCEiMCy5Xs2o8Z5
167ZNva+W+qLVUWq+MDAIyechqeFSvxK6gRM69LJ96lx+XhU58wJiFJzAhT9rK/g+jS
168bsHH9WOfu0xHkuHA5hgvvV2Le9B2wqgFyva4HJy82qxMxCu/VG/SMqyfBS9OWbb7
169ibQhdq0CgYAl53LUWZsFSZIth1vux2LVOsI8C3X1oiXDGpnrdlQ+K7z57hq5EsRq
170GC+INxwXbvKNqp5h0z2MvmKYPDlGVTgw8f8JjM7TkN17ERLcydhdRrMONUryZpo8
1711xTob+8blyJgfxZUIAKbMbMbIiU0WAF0rfD/eJJwS4htOW/Hfv4TGA==
172-----END RSA PRIVATE KEY-----`
173	// client 2 crt is revoked
174	client2Crt = `-----BEGIN CERTIFICATE-----
175MIIEITCCAgmgAwIBAgIRAL6XTgNsdYzILd8bT2RVd/4wDQYJKoZIhvcNAQELBQAw
176EzERMA8GA1UEAxMIQ2VydEF1dGgwHhcNMjEwMTAyMjEyMzIwWhcNMjIwNzAyMjEz
177MDUxWjASMRAwDgYDVQQDEwdjbGllbnQyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
178MIIBCgKCAQEA6xjW5KQR3/OFQtV5M75WINqQ4AzXSu6DhSz/yumaaQZP/UxY+6hi
179jcrFzGo9MMie/Sza8DhkXOFAl2BelUubrOeB2cl+/Gr8OCyRi2Gv6j3zCsuN/4jQ
180tNaoez/IbkDvI3l/ZpzBtnuNY2RiemGgHuORXHRVf3qVlsw+npBIRW5rM2HkO/xG
181oZjeBErWVu390Lyn+Gvk2TqQDnkutWnxUC60/zPlHhXZ4BwaFAekbSnjsSDB1YFM
182s8HwW4oBryoxdj3/+/qLrBHt75IdLw3T7/V1UDJQM3EvSQOr12w4egpldhtsC871
183nnBQZeY6qA5feffIwwg/6lJm70o6S6OX6wIDAQABo3EwbzAOBgNVHQ8BAf8EBAMC
184A7gwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBTB84v5
185t9HqhLhMODbn6oYkEQt3KzAfBgNVHSMEGDAWgBTtcgjQBq/80EySIvJcN8OTorgb
186zDANBgkqhkiG9w0BAQsFAAOCAgEALGtBCve5k8tToL3oLuXp/oSik6ovIB/zq4I/
1874zNMYPU31+ZWz6aahysgx1JL1yqTa3Qm8o2tu52MbnV10dM7CIw7c/cYa+c+OPcG
1885LF97kp13X+r2axy+CmwM86b4ILaDGs2Qyai6VB6k7oFUve+av5o7aUrNFpqGCJz
189HWdtHZSVA3JMATzy0TfWanwkzreqfdw7qH0yZ9bDURlBKAVWrqnCstva9jRuv+AI
190eqxr/4Ro986TFjJdoAP3Vr16CPg7/B6GA/KmsBWJrpeJdPWq4i2gpLKvYZoy89qD
191mUZf34RbzcCtV4NvV1DadGnt4us0nvLrvS5rL2+2uWD09kZYq9RbLkvgzF/cY0fz
192i7I1bi5XQ+alWe0uAk5ZZL/D+GTRYUX1AWwCqwJxmHrMxcskMyO9pXvLyuSWRDLo
193YNBrbX9nLcfJzVCp+X+9sntTHjs4l6Cw+fLepJIgtgqdCHtbhTiv68vSM6cgb4br
1946n2xrXRKuioiWFOrTSRr+oalZh8dGJ/xvwY8IbWknZAvml9mf1VvfE7Ma5P777QM
195fsbYVTq0Y3R/5hIWsC3HA5z6MIM8L1oRe/YyhP3CTmrCHkVKyDOosGXpGz+JVcyo
196cfYkY5A3yFKB2HaCwZSfwFmRhxkrYWGEbHv3Cd9YkZs1J3hNhGFZyVMC9Uh0S85a
1976zdDidU=
198-----END CERTIFICATE-----`
199	client2Key = `-----BEGIN RSA PRIVATE KEY-----
200MIIEpAIBAAKCAQEA6xjW5KQR3/OFQtV5M75WINqQ4AzXSu6DhSz/yumaaQZP/UxY
201+6hijcrFzGo9MMie/Sza8DhkXOFAl2BelUubrOeB2cl+/Gr8OCyRi2Gv6j3zCsuN
202/4jQtNaoez/IbkDvI3l/ZpzBtnuNY2RiemGgHuORXHRVf3qVlsw+npBIRW5rM2Hk
203O/xGoZjeBErWVu390Lyn+Gvk2TqQDnkutWnxUC60/zPlHhXZ4BwaFAekbSnjsSDB
2041YFMs8HwW4oBryoxdj3/+/qLrBHt75IdLw3T7/V1UDJQM3EvSQOr12w4egpldhts
205C871nnBQZeY6qA5feffIwwg/6lJm70o6S6OX6wIDAQABAoIBAFatstVb1KdQXsq0
206cFpui8zTKOUiduJOrDkWzTygAmlEhYtrccdfXu7OWz0x0lvBLDVGK3a0I/TGrAzj
2074BuFY+FM/egxTVt9in6fmA3et4BS1OAfCryzUdfK6RV//8L+t+zJZ/qKQzWnugpy
208QYjDo8ifuMFwtvEoXizaIyBNLAhEp9hnrv+Tyi2O2gahPvCHsD48zkyZRCHYRstD
209NH5cIrwz9/RJgPO1KI+QsJE7Nh7stR0sbr+5TPU4fnsL2mNhMUF2TJrwIPrc1yp+
210YIUjdnh3SO88j4TQT3CIrWi8i4pOy6N0dcVn3gpCRGaqAKyS2ZYUj+yVtLO4KwxZ
211SZ1lNvECgYEA78BrF7f4ETfWSLcBQ3qxfLs7ibB6IYo2x25685FhZjD+zLXM1AKb
212FJHEXUm3mUYrFJK6AFEyOQnyGKBOLs3S6oTAswMPbTkkZeD1Y9O6uv0AHASLZnK6
213pC6ub0eSRF5LUyTQ55Jj8D7QsjXJueO8v+G5ihWhNSN9tB2UA+8NBmkCgYEA+weq
214cvoeMIEMBQHnNNLy35bwfqrceGyPIRBcUIvzQfY1vk7KW6DYOUzC7u+WUzy/hA52
215DjXVVhua2eMQ9qqtOav7djcMc2W9RbLowxvno7K5qiCss013MeWk64TCWy+WMp5A
216AVAtOliC3hMkIKqvR2poqn+IBTh1449agUJQqTMCgYEAu06IHGq1GraV6g9XpGF5
217wqoAlMzUTdnOfDabRilBf/YtSr+J++ThRcuwLvXFw7CnPZZ4TIEjDJ7xjj3HdxeE
218fYYjineMmNd40UNUU556F1ZLvJfsVKizmkuCKhwvcMx+asGrmA+tlmds4p3VMS50
219KzDtpKzLWlmU/p/RINWlRmkCgYBy0pHTn7aZZx2xWKqCDg+L2EXPGqZX6wgZDpu7
220OBifzlfM4ctL2CmvI/5yPmLbVgkgBWFYpKUdiujsyyEiQvWTUKhn7UwjqKDHtcsk
221G6p7xS+JswJrzX4885bZJ9Oi1AR2yM3sC9l0O7I4lDbNPmWIXBLeEhGMmcPKv/Kc
22291Ff4wKBgQCF3ur+Vt0PSU0ucrPVHjCe7tqazm0LJaWbPXL1Aw0pzdM2EcNcW/MA
223w0kqpr7MgJ94qhXCBcVcfPuFN9fBOadM3UBj1B45Cz3pptoK+ScI8XKno6jvVK/p
224xr5cb9VBRBtB9aOKVfuRhpatAfS2Pzm2Htae9lFn7slGPUmu2hkjDw==
225-----END RSA PRIVATE KEY-----`
226	testFileName       = "test_file_ftp.dat"
227	testDLFileName     = "test_download_ftp.dat"
228	tlsClient1Username = "client1"
229	tlsClient2Username = "client2"
230)
231
232var (
233	allPerms        = []string{dataprovider.PermAny}
234	homeBasePath    string
235	hookCmdPath     string
236	extAuthPath     string
237	preLoginPath    string
238	postConnectPath string
239	preDownloadPath string
240	preUploadPath   string
241	logFilePath     string
242	caCrtPath       string
243	caCRLPath       string
244)
245
246func TestMain(m *testing.M) {
247	logFilePath = filepath.Join(configDir, "sftpgo_ftpd_test.log")
248	bannerFileName := "banner_file"
249	bannerFile := filepath.Join(configDir, bannerFileName)
250	logger.InitLogger(logFilePath, 5, 1, 28, false, false, zerolog.DebugLevel)
251	err := os.WriteFile(bannerFile, []byte("SFTPGo test ready\nsimple banner line\n"), os.ModePerm)
252	if err != nil {
253		logger.ErrorToConsole("error creating banner file: %v", err)
254	}
255	// we run the test cases with UploadMode atomic and resume support. The non atomic code path
256	// simply does not execute some code so if it works in atomic mode will
257	// work in non atomic mode too
258	os.Setenv("SFTPGO_COMMON__UPLOAD_MODE", "2")
259	os.Setenv("SFTPGO_DATA_PROVIDER__CREATE_DEFAULT_ADMIN", "1")
260	os.Setenv("SFTPGO_DEFAULT_ADMIN_USERNAME", "admin")
261	os.Setenv("SFTPGO_DEFAULT_ADMIN_PASSWORD", "password")
262	err = config.LoadConfig(configDir, "")
263	if err != nil {
264		logger.ErrorToConsole("error loading configuration: %v", err)
265		os.Exit(1)
266	}
267	providerConf := config.GetProviderConf()
268	logger.InfoToConsole("Starting FTPD tests, provider: %v", providerConf.Driver)
269
270	commonConf := config.GetCommonConfig()
271	homeBasePath = os.TempDir()
272	if runtime.GOOS != osWindows {
273		commonConf.Actions.ExecuteOn = []string{"download", "upload", "rename", "delete"}
274		commonConf.Actions.Hook = hookCmdPath
275		hookCmdPath, err = exec.LookPath("true")
276		if err != nil {
277			logger.Warn(logSender, "", "unable to get hook command: %v", err)
278			logger.WarnToConsole("unable to get hook command: %v", err)
279		}
280	}
281
282	certPath := filepath.Join(os.TempDir(), "test_ftpd.crt")
283	keyPath := filepath.Join(os.TempDir(), "test_ftpd.key")
284	caCrtPath = filepath.Join(os.TempDir(), "test_ftpd_ca.crt")
285	caCRLPath = filepath.Join(os.TempDir(), "test_ftpd_crl.crt")
286	err = writeCerts(certPath, keyPath, caCrtPath, caCRLPath)
287	if err != nil {
288		os.Exit(1)
289	}
290
291	err = common.Initialize(commonConf)
292	if err != nil {
293		logger.WarnToConsole("error initializing common: %v", err)
294		os.Exit(1)
295	}
296	err = dataprovider.Initialize(providerConf, configDir, true)
297	if err != nil {
298		logger.ErrorToConsole("error initializing data provider: %v", err)
299		os.Exit(1)
300	}
301
302	httpConfig := config.GetHTTPConfig()
303	httpConfig.Initialize(configDir) //nolint:errcheck
304
305	kmsConfig := config.GetKMSConfig()
306	err = kmsConfig.Initialize()
307	if err != nil {
308		logger.ErrorToConsole("error initializing kms: %v", err)
309		os.Exit(1)
310	}
311	mfaConfig := config.GetMFAConfig()
312	err = mfaConfig.Initialize()
313	if err != nil {
314		logger.ErrorToConsole("error initializing MFA: %v", err)
315		os.Exit(1)
316	}
317
318	httpdConf := config.GetHTTPDConfig()
319	httpdConf.Bindings[0].Port = 8079
320	httpdtest.SetBaseURL("http://127.0.0.1:8079")
321
322	ftpdConf := config.GetFTPDConfig()
323	ftpdConf.Bindings = []ftpd.Binding{
324		{
325			Port:           2121,
326			ClientAuthType: 2,
327		},
328	}
329	ftpdConf.PassivePortRange.Start = 0
330	ftpdConf.PassivePortRange.End = 0
331	ftpdConf.BannerFile = bannerFileName
332	ftpdConf.CertificateFile = certPath
333	ftpdConf.CertificateKeyFile = keyPath
334	ftpdConf.CACertificates = []string{caCrtPath}
335	ftpdConf.CARevocationLists = []string{caCRLPath}
336	ftpdConf.EnableSite = true
337
338	// required to test sftpfs
339	sftpdConf := config.GetSFTPDConfig()
340	sftpdConf.Bindings = []sftpd.Binding{
341		{
342			Port: 2122,
343		},
344	}
345	hostKeyPath := filepath.Join(os.TempDir(), "id_ed25519")
346	sftpdConf.HostKeys = []string{hostKeyPath}
347
348	extAuthPath = filepath.Join(homeBasePath, "extauth.sh")
349	preLoginPath = filepath.Join(homeBasePath, "prelogin.sh")
350	postConnectPath = filepath.Join(homeBasePath, "postconnect.sh")
351	preDownloadPath = filepath.Join(homeBasePath, "predownload.sh")
352	preUploadPath = filepath.Join(homeBasePath, "preupload.sh")
353
354	status := ftpd.GetStatus()
355	if status.IsActive {
356		logger.ErrorToConsole("ftpd is already active")
357		os.Exit(1)
358	}
359
360	go func() {
361		logger.Debug(logSender, "", "initializing FTP server with config %+v", ftpdConf)
362		if err := ftpdConf.Initialize(configDir); err != nil {
363			logger.ErrorToConsole("could not start FTP server: %v", err)
364			os.Exit(1)
365		}
366	}()
367
368	go func() {
369		logger.Debug(logSender, "", "initializing SFTP server with config %+v", sftpdConf)
370		if err := sftpdConf.Initialize(configDir); err != nil {
371			logger.ErrorToConsole("could not start SFTP server: %v", err)
372			os.Exit(1)
373		}
374	}()
375
376	go func() {
377		if err := httpdConf.Initialize(configDir); err != nil {
378			logger.ErrorToConsole("could not start HTTP server: %v", err)
379			os.Exit(1)
380		}
381	}()
382
383	waitTCPListening(ftpdConf.Bindings[0].GetAddress())
384	waitTCPListening(httpdConf.Bindings[0].GetAddress())
385	waitTCPListening(sftpdConf.Bindings[0].GetAddress())
386	ftpd.ReloadCertificateMgr() //nolint:errcheck
387
388	ftpdConf = config.GetFTPDConfig()
389	ftpdConf.Bindings = []ftpd.Binding{
390		{
391			Port:    2124,
392			TLSMode: 2,
393		},
394	}
395	ftpdConf.CertificateFile = certPath
396	ftpdConf.CertificateKeyFile = keyPath
397	ftpdConf.CACertificates = []string{caCrtPath}
398	ftpdConf.CARevocationLists = []string{caCRLPath}
399	ftpdConf.EnableSite = false
400	ftpdConf.DisableActiveMode = true
401	ftpdConf.CombineSupport = 1
402	ftpdConf.HASHSupport = 1
403
404	go func() {
405		logger.Debug(logSender, "", "initializing FTP server with config %+v", ftpdConf)
406		if err := ftpdConf.Initialize(configDir); err != nil {
407			logger.ErrorToConsole("could not start FTP server: %v", err)
408			os.Exit(1)
409		}
410	}()
411
412	waitTCPListening(ftpdConf.Bindings[0].GetAddress())
413	waitNoConnections()
414
415	exitCode := m.Run()
416	os.Remove(logFilePath)
417	os.Remove(bannerFile)
418	os.Remove(extAuthPath)
419	os.Remove(preLoginPath)
420	os.Remove(postConnectPath)
421	os.Remove(preDownloadPath)
422	os.Remove(preUploadPath)
423	os.Remove(certPath)
424	os.Remove(keyPath)
425	os.Remove(caCrtPath)
426	os.Remove(caCRLPath)
427	os.Remove(hostKeyPath)
428	os.Remove(hostKeyPath + ".pub")
429	os.Exit(exitCode)
430}
431
432func TestInitializationFailure(t *testing.T) {
433	ftpdConf := config.GetFTPDConfig()
434	ftpdConf.Bindings = []ftpd.Binding{}
435	ftpdConf.CertificateFile = filepath.Join(os.TempDir(), "test_ftpd.crt")
436	ftpdConf.CertificateKeyFile = filepath.Join(os.TempDir(), "test_ftpd.key")
437	err := ftpdConf.Initialize(configDir)
438	require.EqualError(t, err, common.ErrNoBinding.Error())
439	ftpdConf.Bindings = []ftpd.Binding{
440		{
441			Port: 0,
442		},
443		{
444			Port: 2121,
445		},
446	}
447	ftpdConf.BannerFile = "a-missing-file"
448	err = ftpdConf.Initialize(configDir)
449	require.Error(t, err)
450
451	ftpdConf.BannerFile = ""
452	ftpdConf.Bindings[1].TLSMode = 10
453	err = ftpdConf.Initialize(configDir)
454	require.Error(t, err)
455
456	ftpdConf.CertificateFile = ""
457	ftpdConf.CertificateKeyFile = ""
458	ftpdConf.Bindings[1].TLSMode = 1
459	err = ftpdConf.Initialize(configDir)
460	require.Error(t, err)
461
462	certPath := filepath.Join(os.TempDir(), "test_ftpd.crt")
463	keyPath := filepath.Join(os.TempDir(), "test_ftpd.key")
464	ftpdConf.CertificateFile = certPath
465	ftpdConf.CertificateKeyFile = keyPath
466	ftpdConf.CACertificates = []string{"invalid ca cert"}
467	err = ftpdConf.Initialize(configDir)
468	require.Error(t, err)
469
470	ftpdConf.CACertificates = nil
471	ftpdConf.CARevocationLists = []string{""}
472	err = ftpdConf.Initialize(configDir)
473	require.Error(t, err)
474
475	ftpdConf.CACertificates = []string{caCrtPath}
476	ftpdConf.CARevocationLists = []string{caCRLPath}
477	ftpdConf.Bindings[1].ForcePassiveIP = "127001"
478	err = ftpdConf.Initialize(configDir)
479	require.Error(t, err)
480	require.Contains(t, err.Error(), "the provided passive IP \"127001\" is not valid")
481	ftpdConf.Bindings[1].ForcePassiveIP = ""
482	err = ftpdConf.Initialize(configDir)
483	require.Error(t, err)
484}
485
486func TestBasicFTPHandling(t *testing.T) {
487	u := getTestUser()
488	u.QuotaSize = 6553600
489	localUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
490	assert.NoError(t, err)
491	u = getTestSFTPUser()
492	u.QuotaSize = 6553600
493	sftpUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
494	assert.NoError(t, err)
495
496	for _, user := range []dataprovider.User{localUser, sftpUser} {
497		client, err := getFTPClient(user, true, nil)
498		if assert.NoError(t, err) {
499			if user.Username == defaultUsername {
500				assert.Len(t, common.Connections.GetStats(), 1)
501			} else {
502				assert.Len(t, common.Connections.GetStats(), 2)
503			}
504			testFilePath := filepath.Join(homeBasePath, testFileName)
505			testFileSize := int64(65535)
506			expectedQuotaSize := testFileSize
507			expectedQuotaFiles := 1
508			err = createTestFile(testFilePath, testFileSize)
509			assert.NoError(t, err)
510
511			err = checkBasicFTP(client)
512			assert.NoError(t, err)
513			err = ftpUploadFile(testFilePath, path.Join("/missing_dir", testFileName), testFileSize, client, 0)
514			assert.Error(t, err)
515			err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
516			assert.NoError(t, err)
517			// overwrite an existing file
518			err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
519			assert.NoError(t, err)
520			localDownloadPath := filepath.Join(homeBasePath, testDLFileName)
521			err = ftpDownloadFile(testFileName, localDownloadPath, testFileSize, client, 0)
522			assert.NoError(t, err)
523			user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
524			assert.NoError(t, err)
525			assert.Equal(t, expectedQuotaFiles, user.UsedQuotaFiles)
526			assert.Equal(t, expectedQuotaSize, user.UsedQuotaSize)
527			err = client.Rename(testFileName, testFileName+"1")
528			assert.NoError(t, err)
529			err = client.Delete(testFileName)
530			assert.Error(t, err)
531			err = client.Delete(testFileName + "1")
532			assert.NoError(t, err)
533			user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
534			assert.NoError(t, err)
535			assert.Equal(t, expectedQuotaFiles-1, user.UsedQuotaFiles)
536			assert.Equal(t, expectedQuotaSize-testFileSize, user.UsedQuotaSize)
537			curDir, err := client.CurrentDir()
538			if assert.NoError(t, err) {
539				assert.Equal(t, "/", curDir)
540			}
541			testDir := "testDir"
542			err = client.MakeDir(testDir)
543			assert.NoError(t, err)
544			err = client.ChangeDir(testDir)
545			assert.NoError(t, err)
546			curDir, err = client.CurrentDir()
547			if assert.NoError(t, err) {
548				assert.Equal(t, path.Join("/", testDir), curDir)
549			}
550			res, err := client.List(path.Join("/", testDir))
551			assert.NoError(t, err)
552			if assert.Len(t, res, 2) {
553				assert.Equal(t, ".", res[0].Name)
554				assert.Equal(t, "..", res[1].Name)
555			}
556			res, err = client.List(path.Join("/"))
557			assert.NoError(t, err)
558			if assert.Len(t, res, 2) {
559				assert.Equal(t, ".", res[0].Name)
560				assert.Equal(t, testDir, res[1].Name)
561			}
562			err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
563			assert.NoError(t, err)
564			size, err := client.FileSize(path.Join("/", testDir, testFileName))
565			assert.NoError(t, err)
566			assert.Equal(t, testFileSize, size)
567			err = client.ChangeDirToParent()
568			assert.NoError(t, err)
569			curDir, err = client.CurrentDir()
570			if assert.NoError(t, err) {
571				assert.Equal(t, "/", curDir)
572			}
573			err = client.Delete(path.Join("/", testDir, testFileName))
574			assert.NoError(t, err)
575			err = client.Delete(testDir)
576			assert.Error(t, err)
577			err = client.RemoveDir(testDir)
578			assert.NoError(t, err)
579
580			err = os.Remove(testFilePath)
581			assert.NoError(t, err)
582			err = os.Remove(localDownloadPath)
583			assert.NoError(t, err)
584			err = client.Quit()
585			assert.NoError(t, err)
586		}
587	}
588	_, err = httpdtest.RemoveUser(sftpUser, http.StatusOK)
589	assert.NoError(t, err)
590	_, err = httpdtest.RemoveUser(localUser, http.StatusOK)
591	assert.NoError(t, err)
592	err = os.RemoveAll(localUser.GetHomeDir())
593	assert.NoError(t, err)
594	assert.Eventually(t, func() bool { return len(common.Connections.GetStats()) == 0 }, 1*time.Second, 50*time.Millisecond)
595	assert.Eventually(t, func() bool { return common.Connections.GetClientConnections() == 0 }, 1000*time.Millisecond,
596		50*time.Millisecond)
597}
598
599func TestMultiFactorAuth(t *testing.T) {
600	u := getTestUser()
601	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
602	assert.NoError(t, err)
603
604	configName, _, secret, _, err := mfa.GenerateTOTPSecret(mfa.GetAvailableTOTPConfigNames()[0], user.Username)
605	assert.NoError(t, err)
606	user.Password = defaultPassword
607	user.Filters.TOTPConfig = sdk.TOTPConfig{
608		Enabled:    true,
609		ConfigName: configName,
610		Secret:     kms.NewPlainSecret(secret),
611		Protocols:  []string{common.ProtocolFTP},
612	}
613	err = dataprovider.UpdateUser(&user, "", "")
614	assert.NoError(t, err)
615
616	user.Password = defaultPassword
617	_, err = getFTPClient(user, true, nil)
618	if assert.Error(t, err) {
619		assert.Contains(t, err.Error(), dataprovider.ErrInvalidCredentials.Error())
620	}
621	passcode, err := generateTOTPPasscode(secret, otp.AlgorithmSHA1)
622	assert.NoError(t, err)
623	user.Password = defaultPassword + passcode
624	client, err := getFTPClient(user, true, nil)
625	if assert.NoError(t, err) {
626		err = checkBasicFTP(client)
627		assert.NoError(t, err)
628		err := client.Quit()
629		assert.NoError(t, err)
630	}
631	// reusing the same passcode should not work
632	_, err = getFTPClient(user, true, nil)
633	if assert.Error(t, err) {
634		assert.Contains(t, err.Error(), dataprovider.ErrInvalidCredentials.Error())
635	}
636
637	_, err = httpdtest.RemoveUser(user, http.StatusOK)
638	assert.NoError(t, err)
639	err = os.RemoveAll(user.GetHomeDir())
640	assert.NoError(t, err)
641}
642
643func TestLoginInvalidCredentials(t *testing.T) {
644	u := getTestUser()
645	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
646	assert.NoError(t, err)
647	user.Username = "wrong username"
648	_, err = getFTPClient(user, false, nil)
649	if assert.Error(t, err) {
650		assert.Contains(t, err.Error(), dataprovider.ErrInvalidCredentials.Error())
651	}
652	user.Username = u.Username
653	user.Password = "wrong pwd"
654	_, err = getFTPClient(user, false, nil)
655	if assert.Error(t, err) {
656		assert.Contains(t, err.Error(), dataprovider.ErrInvalidCredentials.Error())
657	}
658	_, err = httpdtest.RemoveUser(user, http.StatusOK)
659	assert.NoError(t, err)
660}
661
662func TestLoginNonExistentUser(t *testing.T) {
663	user := getTestUser()
664	_, err := getFTPClient(user, false, nil)
665	assert.Error(t, err)
666}
667
668func TestLoginExternalAuth(t *testing.T) {
669	if runtime.GOOS == osWindows {
670		t.Skip("this test is not available on Windows")
671	}
672	u := getTestUser()
673	err := dataprovider.Close()
674	assert.NoError(t, err)
675	err = config.LoadConfig(configDir, "")
676	assert.NoError(t, err)
677	providerConf := config.GetProviderConf()
678	err = os.WriteFile(extAuthPath, getExtAuthScriptContent(u, false, ""), os.ModePerm)
679	assert.NoError(t, err)
680	providerConf.ExternalAuthHook = extAuthPath
681	providerConf.ExternalAuthScope = 0
682	err = dataprovider.Initialize(providerConf, configDir, true)
683	assert.NoError(t, err)
684	client, err := getFTPClient(u, true, nil)
685	if assert.NoError(t, err) {
686		err = checkBasicFTP(client)
687		assert.NoError(t, err)
688		err := client.Quit()
689		assert.NoError(t, err)
690	}
691	u.Username = defaultUsername + "1"
692	client, err = getFTPClient(u, true, nil)
693	if !assert.Error(t, err) {
694		err := client.Quit()
695		assert.NoError(t, err)
696	}
697
698	user, _, err := httpdtest.GetUserByUsername(defaultUsername, http.StatusOK)
699	assert.NoError(t, err)
700	assert.Equal(t, defaultUsername, user.Username)
701	_, err = httpdtest.RemoveUser(user, http.StatusOK)
702	assert.NoError(t, err)
703	err = os.RemoveAll(user.GetHomeDir())
704	assert.NoError(t, err)
705
706	err = dataprovider.Close()
707	assert.NoError(t, err)
708	err = config.LoadConfig(configDir, "")
709	assert.NoError(t, err)
710	providerConf = config.GetProviderConf()
711	err = dataprovider.Initialize(providerConf, configDir, true)
712	assert.NoError(t, err)
713	err = os.Remove(extAuthPath)
714	assert.NoError(t, err)
715}
716
717func TestPreLoginHook(t *testing.T) {
718	if runtime.GOOS == osWindows {
719		t.Skip("this test is not available on Windows")
720	}
721	u := getTestUser()
722	err := dataprovider.Close()
723	assert.NoError(t, err)
724	err = config.LoadConfig(configDir, "")
725	assert.NoError(t, err)
726	providerConf := config.GetProviderConf()
727	err = os.WriteFile(preLoginPath, getPreLoginScriptContent(u, false), os.ModePerm)
728	assert.NoError(t, err)
729	providerConf.PreLoginHook = preLoginPath
730	err = dataprovider.Initialize(providerConf, configDir, true)
731	assert.NoError(t, err)
732	_, _, err = httpdtest.GetUserByUsername(defaultUsername, http.StatusNotFound)
733	assert.NoError(t, err)
734	client, err := getFTPClient(u, false, nil)
735	if assert.NoError(t, err) {
736		err = checkBasicFTP(client)
737		assert.NoError(t, err)
738		err := client.Quit()
739		assert.NoError(t, err)
740	}
741
742	user, _, err := httpdtest.GetUserByUsername(defaultUsername, http.StatusOK)
743	assert.NoError(t, err)
744
745	// test login with an existing user
746	client, err = getFTPClient(user, true, nil)
747	if assert.NoError(t, err) {
748		err = checkBasicFTP(client)
749		assert.NoError(t, err)
750		err := client.Quit()
751		assert.NoError(t, err)
752	}
753
754	err = os.WriteFile(preLoginPath, getPreLoginScriptContent(user, true), os.ModePerm)
755	assert.NoError(t, err)
756	client, err = getFTPClient(u, false, nil)
757	if !assert.Error(t, err) {
758		err := client.Quit()
759		assert.NoError(t, err)
760	}
761	user.Status = 0
762	err = os.WriteFile(preLoginPath, getPreLoginScriptContent(user, false), os.ModePerm)
763	assert.NoError(t, err)
764	client, err = getFTPClient(u, false, nil)
765	if !assert.Error(t, err, "pre-login script returned a disabled user, login must fail") {
766		err := client.Quit()
767		assert.NoError(t, err)
768	}
769
770	_, err = httpdtest.RemoveUser(user, http.StatusOK)
771	assert.NoError(t, err)
772	err = os.RemoveAll(user.GetHomeDir())
773	assert.NoError(t, err)
774	err = dataprovider.Close()
775	assert.NoError(t, err)
776	err = config.LoadConfig(configDir, "")
777	assert.NoError(t, err)
778	providerConf = config.GetProviderConf()
779	err = dataprovider.Initialize(providerConf, configDir, true)
780	assert.NoError(t, err)
781	err = os.Remove(preLoginPath)
782	assert.NoError(t, err)
783}
784
785func TestPreDownloadHook(t *testing.T) {
786	if runtime.GOOS == osWindows {
787		t.Skip("this test is not available on Windows")
788	}
789	oldExecuteOn := common.Config.Actions.ExecuteOn
790	oldHook := common.Config.Actions.Hook
791
792	common.Config.Actions.ExecuteOn = []string{common.OperationPreDownload}
793	common.Config.Actions.Hook = preDownloadPath
794
795	user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
796	assert.NoError(t, err)
797	err = os.WriteFile(preDownloadPath, getExitCodeScriptContent(0), os.ModePerm)
798	assert.NoError(t, err)
799	testFilePath := filepath.Join(homeBasePath, testFileName)
800	testFileSize := int64(65535)
801	err = createTestFile(testFilePath, testFileSize)
802	assert.NoError(t, err)
803
804	client, err := getFTPClient(user, true, nil)
805	if assert.NoError(t, err) {
806		err = checkBasicFTP(client)
807		assert.NoError(t, err)
808		err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
809		assert.NoError(t, err)
810		localDownloadPath := filepath.Join(homeBasePath, testDLFileName)
811		err = ftpDownloadFile(testFileName, localDownloadPath, testFileSize, client, 0)
812		assert.NoError(t, err)
813		err := client.Quit()
814		assert.NoError(t, err)
815		err = os.Remove(localDownloadPath)
816		assert.NoError(t, err)
817	}
818	// now return an error from the pre-download hook
819	err = os.WriteFile(preDownloadPath, getExitCodeScriptContent(1), os.ModePerm)
820	assert.NoError(t, err)
821	client, err = getFTPClient(user, true, nil)
822	if assert.NoError(t, err) {
823		err = checkBasicFTP(client)
824		assert.NoError(t, err)
825		err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
826		assert.NoError(t, err)
827		localDownloadPath := filepath.Join(homeBasePath, testDLFileName)
828		err = ftpDownloadFile(testFileName, localDownloadPath, testFileSize, client, 0)
829		if assert.Error(t, err) {
830			assert.Contains(t, err.Error(), "permission denied")
831		}
832		err := client.Quit()
833		assert.NoError(t, err)
834		err = os.Remove(localDownloadPath)
835		assert.NoError(t, err)
836	}
837
838	_, err = httpdtest.RemoveUser(user, http.StatusOK)
839	assert.NoError(t, err)
840	err = os.RemoveAll(user.GetHomeDir())
841	assert.NoError(t, err)
842	err = os.Remove(testFilePath)
843	assert.NoError(t, err)
844
845	common.Config.Actions.ExecuteOn = oldExecuteOn
846	common.Config.Actions.Hook = oldHook
847}
848
849func TestPreUploadHook(t *testing.T) {
850	if runtime.GOOS == osWindows {
851		t.Skip("this test is not available on Windows")
852	}
853	oldExecuteOn := common.Config.Actions.ExecuteOn
854	oldHook := common.Config.Actions.Hook
855
856	common.Config.Actions.ExecuteOn = []string{common.OperationPreUpload}
857	common.Config.Actions.Hook = preUploadPath
858
859	user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
860	assert.NoError(t, err)
861	err = os.WriteFile(preUploadPath, getExitCodeScriptContent(0), os.ModePerm)
862	assert.NoError(t, err)
863	testFilePath := filepath.Join(homeBasePath, testFileName)
864	testFileSize := int64(65535)
865	err = createTestFile(testFilePath, testFileSize)
866	assert.NoError(t, err)
867
868	client, err := getFTPClient(user, true, nil)
869	if assert.NoError(t, err) {
870		err = checkBasicFTP(client)
871		assert.NoError(t, err)
872		err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
873		assert.NoError(t, err)
874		localDownloadPath := filepath.Join(homeBasePath, testDLFileName)
875		err = ftpDownloadFile(testFileName, localDownloadPath, testFileSize, client, 0)
876		assert.NoError(t, err)
877		err := client.Quit()
878		assert.NoError(t, err)
879		err = os.Remove(localDownloadPath)
880		assert.NoError(t, err)
881	}
882	// now return an error from the pre-upload hook
883	err = os.WriteFile(preUploadPath, getExitCodeScriptContent(1), os.ModePerm)
884	assert.NoError(t, err)
885	client, err = getFTPClient(user, true, nil)
886	if assert.NoError(t, err) {
887		err = checkBasicFTP(client)
888		assert.NoError(t, err)
889		err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
890		if assert.Error(t, err) {
891			assert.Contains(t, err.Error(), ftpserver.ErrFileNameNotAllowed.Error())
892		}
893		err = ftpUploadFile(testFilePath, testFileName+"1", testFileSize, client, 0)
894		if assert.Error(t, err) {
895			assert.Contains(t, err.Error(), ftpserver.ErrFileNameNotAllowed.Error())
896		}
897		err := client.Quit()
898		assert.NoError(t, err)
899	}
900
901	_, err = httpdtest.RemoveUser(user, http.StatusOK)
902	assert.NoError(t, err)
903	err = os.RemoveAll(user.GetHomeDir())
904	assert.NoError(t, err)
905	err = os.Remove(testFilePath)
906	assert.NoError(t, err)
907
908	common.Config.Actions.ExecuteOn = oldExecuteOn
909	common.Config.Actions.Hook = oldHook
910}
911
912func TestPostConnectHook(t *testing.T) {
913	if runtime.GOOS == osWindows {
914		t.Skip("this test is not available on Windows")
915	}
916	common.Config.PostConnectHook = postConnectPath
917
918	u := getTestUser()
919	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
920	assert.NoError(t, err)
921	err = os.WriteFile(postConnectPath, getExitCodeScriptContent(0), os.ModePerm)
922	assert.NoError(t, err)
923	client, err := getFTPClient(user, true, nil)
924	if assert.NoError(t, err) {
925		err = checkBasicFTP(client)
926		assert.NoError(t, err)
927		err := client.Quit()
928		assert.NoError(t, err)
929	}
930	err = os.WriteFile(postConnectPath, getExitCodeScriptContent(1), os.ModePerm)
931	assert.NoError(t, err)
932	client, err = getFTPClient(user, true, nil)
933	if !assert.Error(t, err) {
934		err := client.Quit()
935		assert.NoError(t, err)
936	}
937
938	common.Config.PostConnectHook = "http://127.0.0.1:8079/healthz"
939
940	client, err = getFTPClient(user, false, nil)
941	if assert.NoError(t, err) {
942		err = checkBasicFTP(client)
943		assert.NoError(t, err)
944		err := client.Quit()
945		assert.NoError(t, err)
946	}
947
948	common.Config.PostConnectHook = "http://127.0.0.1:8079/notfound"
949
950	client, err = getFTPClient(user, true, nil)
951	if !assert.Error(t, err) {
952		err := client.Quit()
953		assert.NoError(t, err)
954	}
955
956	_, err = httpdtest.RemoveUser(user, http.StatusOK)
957	assert.NoError(t, err)
958	err = os.RemoveAll(user.GetHomeDir())
959	assert.NoError(t, err)
960
961	common.Config.PostConnectHook = ""
962}
963
964//nolint:dupl
965func TestMaxConnections(t *testing.T) {
966	oldValue := common.Config.MaxTotalConnections
967	common.Config.MaxTotalConnections = 1
968
969	assert.Eventually(t, func() bool {
970		return common.Connections.GetClientConnections() == 0
971	}, 1000*time.Millisecond, 50*time.Millisecond)
972
973	user := getTestUser()
974	err := dataprovider.AddUser(&user, "", "")
975	assert.NoError(t, err)
976	user.Password = ""
977	client, err := getFTPClient(user, true, nil)
978	if assert.NoError(t, err) {
979		err = checkBasicFTP(client)
980		assert.NoError(t, err)
981		_, err = getFTPClient(user, false, nil)
982		assert.Error(t, err)
983		err = client.Quit()
984		assert.NoError(t, err)
985	}
986	err = dataprovider.DeleteUser(user.Username, "", "")
987	assert.NoError(t, err)
988	err = os.RemoveAll(user.GetHomeDir())
989	assert.NoError(t, err)
990
991	common.Config.MaxTotalConnections = oldValue
992}
993
994//nolint:dupl
995func TestMaxPerHostConnections(t *testing.T) {
996	oldValue := common.Config.MaxPerHostConnections
997	common.Config.MaxPerHostConnections = 1
998
999	assert.Eventually(t, func() bool {
1000		return common.Connections.GetClientConnections() == 0
1001	}, 1000*time.Millisecond, 50*time.Millisecond)
1002
1003	user := getTestUser()
1004	err := dataprovider.AddUser(&user, "", "")
1005	assert.NoError(t, err)
1006	user.Password = ""
1007	client, err := getFTPClient(user, true, nil)
1008	if assert.NoError(t, err) {
1009		err = checkBasicFTP(client)
1010		assert.NoError(t, err)
1011		_, err = getFTPClient(user, false, nil)
1012		assert.Error(t, err)
1013		err = client.Quit()
1014		assert.NoError(t, err)
1015	}
1016	err = dataprovider.DeleteUser(user.Username, "", "")
1017	assert.NoError(t, err)
1018	err = os.RemoveAll(user.GetHomeDir())
1019	assert.NoError(t, err)
1020
1021	common.Config.MaxPerHostConnections = oldValue
1022}
1023
1024func TestRateLimiter(t *testing.T) {
1025	oldConfig := config.GetCommonConfig()
1026
1027	cfg := config.GetCommonConfig()
1028	cfg.DefenderConfig.Enabled = true
1029	cfg.DefenderConfig.Threshold = 5
1030	cfg.DefenderConfig.ScoreLimitExceeded = 3
1031	cfg.RateLimitersConfig = []common.RateLimiterConfig{
1032		{
1033			Average:                1,
1034			Period:                 1000,
1035			Burst:                  1,
1036			Type:                   2,
1037			Protocols:              []string{common.ProtocolFTP},
1038			GenerateDefenderEvents: true,
1039			EntriesSoftLimit:       100,
1040			EntriesHardLimit:       150,
1041		},
1042	}
1043
1044	err := common.Initialize(cfg)
1045	assert.NoError(t, err)
1046
1047	user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
1048	assert.NoError(t, err)
1049
1050	client, err := getFTPClient(user, false, nil)
1051	if assert.NoError(t, err) {
1052		err = checkBasicFTP(client)
1053		assert.NoError(t, err)
1054		err = client.Quit()
1055		assert.NoError(t, err)
1056	}
1057
1058	_, err = getFTPClient(user, true, nil)
1059	if assert.Error(t, err) {
1060		assert.Contains(t, err.Error(), "rate limit exceed")
1061	}
1062
1063	_, err = getFTPClient(user, false, nil)
1064	if assert.Error(t, err) {
1065		assert.Contains(t, err.Error(), "rate limit exceed")
1066	}
1067
1068	_, err = getFTPClient(user, true, nil)
1069	if assert.Error(t, err) {
1070		assert.Contains(t, err.Error(), "banned client IP")
1071	}
1072
1073	err = dataprovider.DeleteUser(user.Username, "", "")
1074	assert.NoError(t, err)
1075	err = os.RemoveAll(user.GetHomeDir())
1076	assert.NoError(t, err)
1077
1078	err = common.Initialize(oldConfig)
1079	assert.NoError(t, err)
1080}
1081
1082func TestDefender(t *testing.T) {
1083	oldConfig := config.GetCommonConfig()
1084
1085	cfg := config.GetCommonConfig()
1086	cfg.DefenderConfig.Enabled = true
1087	cfg.DefenderConfig.Threshold = 3
1088	cfg.DefenderConfig.ScoreLimitExceeded = 2
1089
1090	err := common.Initialize(cfg)
1091	assert.NoError(t, err)
1092
1093	user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
1094	assert.NoError(t, err)
1095	client, err := getFTPClient(user, false, nil)
1096	if assert.NoError(t, err) {
1097		err = checkBasicFTP(client)
1098		assert.NoError(t, err)
1099		err = client.Quit()
1100		assert.NoError(t, err)
1101	}
1102
1103	for i := 0; i < 3; i++ {
1104		user.Password = "wrong_pwd"
1105		_, err = getFTPClient(user, false, nil)
1106		assert.Error(t, err)
1107	}
1108
1109	user.Password = defaultPassword
1110	_, err = getFTPClient(user, false, nil)
1111	if assert.Error(t, err) {
1112		assert.Contains(t, err.Error(), "banned client IP")
1113	}
1114
1115	err = dataprovider.DeleteUser(user.Username, "", "")
1116	assert.NoError(t, err)
1117	err = os.RemoveAll(user.GetHomeDir())
1118	assert.NoError(t, err)
1119
1120	err = common.Initialize(oldConfig)
1121	assert.NoError(t, err)
1122}
1123
1124func TestMaxSessions(t *testing.T) {
1125	u := getTestUser()
1126	u.MaxSessions = 1
1127	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
1128	assert.NoError(t, err)
1129	client, err := getFTPClient(user, true, nil)
1130	if assert.NoError(t, err) {
1131		err = checkBasicFTP(client)
1132		assert.NoError(t, err)
1133		_, err = getFTPClient(user, false, nil)
1134		assert.Error(t, err)
1135		err = client.Quit()
1136		assert.NoError(t, err)
1137	}
1138	_, err = httpdtest.RemoveUser(user, http.StatusOK)
1139	assert.NoError(t, err)
1140	err = os.RemoveAll(user.GetHomeDir())
1141	assert.NoError(t, err)
1142}
1143
1144func TestZeroBytesTransfers(t *testing.T) {
1145	u := getTestUser()
1146	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
1147	assert.NoError(t, err)
1148	for _, useTLS := range []bool{true, false} {
1149		client, err := getFTPClient(user, useTLS, nil)
1150		if assert.NoError(t, err) {
1151			testFileName := "testfilename"
1152			err = checkBasicFTP(client)
1153			assert.NoError(t, err)
1154			localDownloadPath := filepath.Join(homeBasePath, "empty_download")
1155			err = os.WriteFile(localDownloadPath, []byte(""), os.ModePerm)
1156			assert.NoError(t, err)
1157			err = ftpUploadFile(localDownloadPath, testFileName, 0, client, 0)
1158			assert.NoError(t, err)
1159			size, err := client.FileSize(testFileName)
1160			assert.NoError(t, err)
1161			assert.Equal(t, int64(0), size)
1162			err = os.Remove(localDownloadPath)
1163			assert.NoError(t, err)
1164			assert.NoFileExists(t, localDownloadPath)
1165			err = ftpDownloadFile(testFileName, localDownloadPath, 0, client, 0)
1166			assert.NoError(t, err)
1167			assert.FileExists(t, localDownloadPath)
1168			err = client.Quit()
1169			assert.NoError(t, err)
1170			err = os.Remove(localDownloadPath)
1171			assert.NoError(t, err)
1172		}
1173	}
1174	_, err = httpdtest.RemoveUser(user, http.StatusOK)
1175	assert.NoError(t, err)
1176	err = os.RemoveAll(user.GetHomeDir())
1177	assert.NoError(t, err)
1178}
1179
1180func TestDownloadErrors(t *testing.T) {
1181	u := getTestUser()
1182	u.QuotaFiles = 1
1183	subDir1 := "sub1"
1184	subDir2 := "sub2"
1185	u.Permissions[path.Join("/", subDir1)] = []string{dataprovider.PermListItems}
1186	u.Permissions[path.Join("/", subDir2)] = []string{dataprovider.PermListItems, dataprovider.PermUpload,
1187		dataprovider.PermDelete, dataprovider.PermDownload}
1188	u.Filters.FilePatterns = []sdk.PatternsFilter{
1189		{
1190			Path:            "/sub2",
1191			AllowedPatterns: []string{},
1192			DeniedPatterns:  []string{"*.jpg", "*.zip"},
1193		},
1194	}
1195	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
1196	assert.NoError(t, err)
1197	client, err := getFTPClient(user, true, nil)
1198	if assert.NoError(t, err) {
1199		testFilePath1 := filepath.Join(user.HomeDir, subDir1, "file.zip")
1200		testFilePath2 := filepath.Join(user.HomeDir, subDir2, "file.zip")
1201		testFilePath3 := filepath.Join(user.HomeDir, subDir2, "file.jpg")
1202		err = os.MkdirAll(filepath.Dir(testFilePath1), os.ModePerm)
1203		assert.NoError(t, err)
1204		err = os.MkdirAll(filepath.Dir(testFilePath2), os.ModePerm)
1205		assert.NoError(t, err)
1206		err = os.WriteFile(testFilePath1, []byte("file1"), os.ModePerm)
1207		assert.NoError(t, err)
1208		err = os.WriteFile(testFilePath2, []byte("file2"), os.ModePerm)
1209		assert.NoError(t, err)
1210		err = os.WriteFile(testFilePath3, []byte("file3"), os.ModePerm)
1211		assert.NoError(t, err)
1212		localDownloadPath := filepath.Join(homeBasePath, testDLFileName)
1213		err = ftpDownloadFile(path.Join("/", subDir1, "file.zip"), localDownloadPath, 5, client, 0)
1214		assert.Error(t, err)
1215		err = ftpDownloadFile(path.Join("/", subDir2, "file.zip"), localDownloadPath, 5, client, 0)
1216		assert.Error(t, err)
1217		err = ftpDownloadFile(path.Join("/", subDir2, "file.jpg"), localDownloadPath, 5, client, 0)
1218		assert.Error(t, err)
1219		err = ftpDownloadFile("/missing.zip", localDownloadPath, 5, client, 0)
1220		assert.Error(t, err)
1221		err = client.Quit()
1222		assert.NoError(t, err)
1223		err = os.Remove(localDownloadPath)
1224		assert.NoError(t, err)
1225	}
1226	_, err = httpdtest.RemoveUser(user, http.StatusOK)
1227	assert.NoError(t, err)
1228	err = os.RemoveAll(user.GetHomeDir())
1229	assert.NoError(t, err)
1230}
1231
1232func TestUploadErrors(t *testing.T) {
1233	u := getTestUser()
1234	u.QuotaSize = 65535
1235	subDir1 := "sub1"
1236	subDir2 := "sub2"
1237	u.Permissions[path.Join("/", subDir1)] = []string{dataprovider.PermListItems}
1238	u.Permissions[path.Join("/", subDir2)] = []string{dataprovider.PermListItems, dataprovider.PermUpload,
1239		dataprovider.PermDelete}
1240	u.Filters.FilePatterns = []sdk.PatternsFilter{
1241		{
1242			Path:            "/sub2",
1243			AllowedPatterns: []string{},
1244			DeniedPatterns:  []string{"*.zip"},
1245		},
1246	}
1247	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
1248	assert.NoError(t, err)
1249	client, err := getFTPClient(user, true, nil)
1250	if assert.NoError(t, err) {
1251		testFilePath := filepath.Join(homeBasePath, testFileName)
1252		testFileSize := user.QuotaSize
1253		err = createTestFile(testFilePath, testFileSize)
1254		assert.NoError(t, err)
1255		err = client.MakeDir(subDir1)
1256		assert.NoError(t, err)
1257		err = client.MakeDir(subDir2)
1258		assert.NoError(t, err)
1259		err = client.ChangeDir(subDir1)
1260		assert.NoError(t, err)
1261		err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
1262		assert.Error(t, err)
1263		err = client.ChangeDirToParent()
1264		assert.NoError(t, err)
1265		err = client.ChangeDir(subDir2)
1266		assert.NoError(t, err)
1267		err = ftpUploadFile(testFilePath, testFileName+".zip", testFileSize, client, 0)
1268		assert.Error(t, err)
1269		err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
1270		assert.NoError(t, err)
1271		err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
1272		assert.Error(t, err)
1273		err = client.ChangeDir("/")
1274		assert.NoError(t, err)
1275		err = ftpUploadFile(testFilePath, subDir1, testFileSize, client, 0)
1276		assert.Error(t, err)
1277		// overquota
1278		err = ftpUploadFile(testFilePath, testFileName+"1", testFileSize, client, 0)
1279		assert.Error(t, err)
1280		err = client.Delete(path.Join("/", subDir2, testFileName))
1281		assert.NoError(t, err)
1282		err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
1283		assert.NoError(t, err)
1284		err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
1285		assert.Error(t, err)
1286		err = client.Quit()
1287		assert.NoError(t, err)
1288		err = os.Remove(testFilePath)
1289		assert.NoError(t, err)
1290	}
1291	_, err = httpdtest.RemoveUser(user, http.StatusOK)
1292	assert.NoError(t, err)
1293	err = os.RemoveAll(user.GetHomeDir())
1294	assert.NoError(t, err)
1295}
1296
1297func TestSFTPBuffered(t *testing.T) {
1298	u := getTestUser()
1299	localUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
1300	assert.NoError(t, err)
1301	u = getTestSFTPUser()
1302	u.QuotaFiles = 100
1303	u.FsConfig.SFTPConfig.BufferSize = 2
1304	u.HomeDir = filepath.Join(os.TempDir(), u.Username)
1305	sftpUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
1306	assert.NoError(t, err)
1307	client, err := getFTPClient(sftpUser, true, nil)
1308	if assert.NoError(t, err) {
1309		testFilePath := filepath.Join(homeBasePath, testFileName)
1310		testFileSize := int64(65535)
1311		expectedQuotaSize := testFileSize
1312		expectedQuotaFiles := 1
1313		err = createTestFile(testFilePath, testFileSize)
1314		assert.NoError(t, err)
1315		err = checkBasicFTP(client)
1316		assert.NoError(t, err)
1317		err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
1318		assert.NoError(t, err)
1319		// overwrite an existing file
1320		err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
1321		assert.NoError(t, err)
1322		localDownloadPath := filepath.Join(homeBasePath, testDLFileName)
1323		err = ftpDownloadFile(testFileName, localDownloadPath, testFileSize, client, 0)
1324		assert.NoError(t, err)
1325		user, _, err := httpdtest.GetUserByUsername(sftpUser.Username, http.StatusOK)
1326		assert.NoError(t, err)
1327		assert.Equal(t, expectedQuotaFiles, user.UsedQuotaFiles)
1328		assert.Equal(t, expectedQuotaSize, user.UsedQuotaSize)
1329
1330		data := []byte("test data")
1331		err = os.WriteFile(testFilePath, data, os.ModePerm)
1332		assert.NoError(t, err)
1333		err = ftpUploadFile(testFilePath, testFileName, int64(len(data)), client, 0)
1334		assert.NoError(t, err)
1335		err = ftpUploadFile(testFilePath, testFileName, int64(len(data)+5), client, 5)
1336		if assert.Error(t, err) {
1337			assert.Contains(t, err.Error(), "operation unsupported")
1338		}
1339		err = ftpDownloadFile(testFileName, localDownloadPath, int64(4), client, 5)
1340		assert.NoError(t, err)
1341		readed, err := os.ReadFile(localDownloadPath)
1342		assert.NoError(t, err)
1343		assert.Equal(t, []byte("data"), readed)
1344		// try to append to a file, it should fail
1345		// now append to a file
1346		srcFile, err := os.Open(testFilePath)
1347		if assert.NoError(t, err) {
1348			err = client.Append(testFileName, srcFile)
1349			if assert.Error(t, err) {
1350				assert.Contains(t, err.Error(), "operation unsupported")
1351			}
1352			err = srcFile.Close()
1353			assert.NoError(t, err)
1354			size, err := client.FileSize(testFileName)
1355			assert.NoError(t, err)
1356			assert.Equal(t, int64(len(data)), size)
1357			err = ftpDownloadFile(testFileName, localDownloadPath, int64(len(data)), client, 0)
1358			assert.NoError(t, err)
1359		}
1360
1361		err = os.Remove(testFilePath)
1362		assert.NoError(t, err)
1363		err = os.Remove(localDownloadPath)
1364		assert.NoError(t, err)
1365		err = client.Quit()
1366		assert.NoError(t, err)
1367	}
1368
1369	_, err = httpdtest.RemoveUser(sftpUser, http.StatusOK)
1370	assert.NoError(t, err)
1371	_, err = httpdtest.RemoveUser(localUser, http.StatusOK)
1372	assert.NoError(t, err)
1373	err = os.RemoveAll(localUser.GetHomeDir())
1374	assert.NoError(t, err)
1375	err = os.RemoveAll(sftpUser.GetHomeDir())
1376	assert.NoError(t, err)
1377}
1378
1379func TestResume(t *testing.T) {
1380	u := getTestUser()
1381	localUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
1382	assert.NoError(t, err)
1383	sftpUser, _, err := httpdtest.AddUser(getTestSFTPUser(), http.StatusCreated)
1384	assert.NoError(t, err)
1385	for _, user := range []dataprovider.User{localUser, sftpUser} {
1386		client, err := getFTPClient(user, true, nil)
1387		if assert.NoError(t, err) {
1388			testFilePath := filepath.Join(homeBasePath, testFileName)
1389			data := []byte("test data")
1390			err = os.WriteFile(testFilePath, data, os.ModePerm)
1391			assert.NoError(t, err)
1392			err = ftpUploadFile(testFilePath, testFileName, int64(len(data)), client, 0)
1393			assert.NoError(t, err)
1394			err = ftpUploadFile(testFilePath, testFileName, int64(len(data)+5), client, 5)
1395			assert.NoError(t, err)
1396			readed, err := os.ReadFile(filepath.Join(user.GetHomeDir(), testFileName))
1397			assert.NoError(t, err)
1398			assert.Equal(t, "test test data", string(readed))
1399			localDownloadPath := filepath.Join(homeBasePath, testDLFileName)
1400			err = ftpDownloadFile(testFileName, localDownloadPath, int64(len(data)), client, 5)
1401			assert.NoError(t, err)
1402			readed, err = os.ReadFile(localDownloadPath)
1403			assert.NoError(t, err)
1404			assert.Equal(t, data, readed)
1405			err = client.Delete(testFileName)
1406			assert.NoError(t, err)
1407			err = ftpUploadFile(testFilePath, testFileName, int64(len(data)), client, 0)
1408			assert.NoError(t, err)
1409			// now append to a file
1410			srcFile, err := os.Open(testFilePath)
1411			if assert.NoError(t, err) {
1412				err = client.Append(testFileName, srcFile)
1413				assert.NoError(t, err)
1414				err = srcFile.Close()
1415				assert.NoError(t, err)
1416				size, err := client.FileSize(testFileName)
1417				assert.NoError(t, err)
1418				assert.Equal(t, int64(2*len(data)), size)
1419				err = ftpDownloadFile(testFileName, localDownloadPath, int64(2*len(data)), client, 0)
1420				assert.NoError(t, err)
1421				readed, err = os.ReadFile(localDownloadPath)
1422				assert.NoError(t, err)
1423				expected := append(data, data...)
1424				assert.Equal(t, expected, readed)
1425			}
1426			err = client.Quit()
1427			assert.NoError(t, err)
1428			err = os.Remove(testFilePath)
1429			assert.NoError(t, err)
1430			err = os.Remove(localDownloadPath)
1431			assert.NoError(t, err)
1432			if user.Username == defaultUsername {
1433				err = os.RemoveAll(user.GetHomeDir())
1434				assert.NoError(t, err)
1435			}
1436		}
1437	}
1438	_, err = httpdtest.RemoveUser(sftpUser, http.StatusOK)
1439	assert.NoError(t, err)
1440	_, err = httpdtest.RemoveUser(localUser, http.StatusOK)
1441	assert.NoError(t, err)
1442	err = os.RemoveAll(localUser.GetHomeDir())
1443	assert.NoError(t, err)
1444}
1445
1446//nolint:dupl
1447func TestDeniedLoginMethod(t *testing.T) {
1448	u := getTestUser()
1449	u.Filters.DeniedLoginMethods = []string{dataprovider.LoginMethodPassword}
1450	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
1451	assert.NoError(t, err)
1452	_, err = getFTPClient(user, false, nil)
1453	assert.Error(t, err)
1454	user.Filters.DeniedLoginMethods = []string{dataprovider.SSHLoginMethodPublicKey, dataprovider.SSHLoginMethodKeyAndPassword}
1455	user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
1456	assert.NoError(t, err)
1457	client, err := getFTPClient(user, true, nil)
1458	if assert.NoError(t, err) {
1459		assert.NoError(t, checkBasicFTP(client))
1460		err = client.Quit()
1461		assert.NoError(t, err)
1462	}
1463	_, err = httpdtest.RemoveUser(user, http.StatusOK)
1464	assert.NoError(t, err)
1465	err = os.RemoveAll(user.GetHomeDir())
1466	assert.NoError(t, err)
1467}
1468
1469//nolint:dupl
1470func TestDeniedProtocols(t *testing.T) {
1471	u := getTestUser()
1472	u.Filters.DeniedProtocols = []string{common.ProtocolFTP}
1473	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
1474	assert.NoError(t, err)
1475	_, err = getFTPClient(user, false, nil)
1476	assert.Error(t, err)
1477	user.Filters.DeniedProtocols = []string{common.ProtocolSSH, common.ProtocolWebDAV}
1478	user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
1479	assert.NoError(t, err)
1480	client, err := getFTPClient(user, true, nil)
1481	if assert.NoError(t, err) {
1482		assert.NoError(t, checkBasicFTP(client))
1483		err = client.Quit()
1484		assert.NoError(t, err)
1485	}
1486	_, err = httpdtest.RemoveUser(user, http.StatusOK)
1487	assert.NoError(t, err)
1488	err = os.RemoveAll(user.GetHomeDir())
1489	assert.NoError(t, err)
1490}
1491
1492func TestQuotaLimits(t *testing.T) {
1493	u := getTestUser()
1494	u.QuotaFiles = 1
1495	localUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
1496	assert.NoError(t, err)
1497	u = getTestSFTPUser()
1498	u.QuotaFiles = 1
1499	sftpUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
1500	assert.NoError(t, err)
1501	for _, user := range []dataprovider.User{localUser, sftpUser} {
1502		testFileSize := int64(65535)
1503		testFilePath := filepath.Join(homeBasePath, testFileName)
1504		err = createTestFile(testFilePath, testFileSize)
1505		assert.NoError(t, err)
1506		testFileSize1 := int64(131072)
1507		testFileName1 := "test_file1.dat"
1508		testFilePath1 := filepath.Join(homeBasePath, testFileName1)
1509		err = createTestFile(testFilePath1, testFileSize1)
1510		assert.NoError(t, err)
1511		testFileSize2 := int64(32768)
1512		testFileName2 := "test_file2.dat"
1513		testFilePath2 := filepath.Join(homeBasePath, testFileName2)
1514		err = createTestFile(testFilePath2, testFileSize2)
1515		assert.NoError(t, err)
1516		// test quota files
1517		client, err := getFTPClient(user, false, nil)
1518		if assert.NoError(t, err) {
1519			err = ftpUploadFile(testFilePath, testFileName+".quota", testFileSize, client, 0)
1520			assert.NoError(t, err)
1521			err = ftpUploadFile(testFilePath, testFileName+".quota1", testFileSize, client, 0)
1522			assert.Error(t, err)
1523			err = client.Rename(testFileName+".quota", testFileName)
1524			assert.NoError(t, err)
1525			err = client.Quit()
1526			assert.NoError(t, err)
1527		}
1528		// test quota size
1529		user.QuotaSize = testFileSize - 1
1530		user.QuotaFiles = 0
1531		user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
1532		assert.NoError(t, err)
1533		client, err = getFTPClient(user, true, nil)
1534		if assert.NoError(t, err) {
1535			err = ftpUploadFile(testFilePath, testFileName+".quota", testFileSize, client, 0)
1536			assert.Error(t, err)
1537			err = client.Rename(testFileName, testFileName+".quota")
1538			assert.NoError(t, err)
1539			err = client.Quit()
1540			assert.NoError(t, err)
1541		}
1542		// now test quota limits while uploading the current file, we have 1 bytes remaining
1543		user.QuotaSize = testFileSize + 1
1544		user.QuotaFiles = 0
1545		user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
1546		assert.NoError(t, err)
1547		client, err = getFTPClient(user, false, nil)
1548		if assert.NoError(t, err) {
1549			err = ftpUploadFile(testFilePath1, testFileName1, testFileSize1, client, 0)
1550			assert.Error(t, err)
1551			_, err = client.FileSize(testFileName1)
1552			assert.Error(t, err)
1553			err = client.Rename(testFileName+".quota", testFileName)
1554			assert.NoError(t, err)
1555			// overwriting an existing file will work if the resulting size is lesser or equal than the current one
1556			err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
1557			assert.NoError(t, err)
1558			err = ftpUploadFile(testFilePath2, testFileName, testFileSize2, client, 0)
1559			assert.NoError(t, err)
1560			err = ftpUploadFile(testFilePath1, testFileName, testFileSize1, client, 0)
1561			assert.Error(t, err)
1562			err = ftpUploadFile(testFilePath1, testFileName, testFileSize1, client, 10)
1563			assert.Error(t, err)
1564			err = ftpUploadFile(testFilePath2, testFileName, testFileSize2, client, 0)
1565			assert.NoError(t, err)
1566			err = client.Quit()
1567			assert.NoError(t, err)
1568		}
1569
1570		err = os.Remove(testFilePath)
1571		assert.NoError(t, err)
1572		err = os.Remove(testFilePath1)
1573		assert.NoError(t, err)
1574		err = os.Remove(testFilePath2)
1575		assert.NoError(t, err)
1576		if user.Username == defaultUsername {
1577			err = os.RemoveAll(user.GetHomeDir())
1578			assert.NoError(t, err)
1579			user.QuotaFiles = 0
1580			user.QuotaSize = 0
1581			_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
1582			assert.NoError(t, err)
1583		}
1584	}
1585	_, err = httpdtest.RemoveUser(sftpUser, http.StatusOK)
1586	assert.NoError(t, err)
1587	_, err = httpdtest.RemoveUser(localUser, http.StatusOK)
1588	assert.NoError(t, err)
1589	err = os.RemoveAll(localUser.GetHomeDir())
1590	assert.NoError(t, err)
1591}
1592
1593func TestUploadMaxSize(t *testing.T) {
1594	testFileSize := int64(65535)
1595	u := getTestUser()
1596	u.Filters.MaxUploadFileSize = testFileSize + 1
1597	localUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
1598	assert.NoError(t, err)
1599	u = getTestSFTPUser()
1600	u.Filters.MaxUploadFileSize = testFileSize + 1
1601	sftpUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
1602	assert.NoError(t, err)
1603	for _, user := range []dataprovider.User{localUser, sftpUser} {
1604		testFilePath := filepath.Join(homeBasePath, testFileName)
1605		err = createTestFile(testFilePath, testFileSize)
1606		assert.NoError(t, err)
1607		testFileSize1 := int64(131072)
1608		testFileName1 := "test_file1.dat"
1609		testFilePath1 := filepath.Join(homeBasePath, testFileName1)
1610		err = createTestFile(testFilePath1, testFileSize1)
1611		assert.NoError(t, err)
1612		client, err := getFTPClient(user, false, nil)
1613		if assert.NoError(t, err) {
1614			err = ftpUploadFile(testFilePath1, testFileName1, testFileSize1, client, 0)
1615			assert.Error(t, err)
1616			err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
1617			assert.NoError(t, err)
1618			// now test overwrite an existing file with a size bigger than the allowed one
1619			err = createTestFile(filepath.Join(user.GetHomeDir(), testFileName1), testFileSize1)
1620			assert.NoError(t, err)
1621			err = ftpUploadFile(testFilePath1, testFileName1, testFileSize1, client, 0)
1622			assert.Error(t, err)
1623			err = client.Quit()
1624			assert.NoError(t, err)
1625		}
1626		err = os.Remove(testFilePath)
1627		assert.NoError(t, err)
1628		err = os.Remove(testFilePath1)
1629		assert.NoError(t, err)
1630		if user.Username == defaultUsername {
1631			err = os.RemoveAll(user.GetHomeDir())
1632			assert.NoError(t, err)
1633			user.Filters.MaxUploadFileSize = 65536000
1634			_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
1635			assert.NoError(t, err)
1636		}
1637	}
1638	_, err = httpdtest.RemoveUser(sftpUser, http.StatusOK)
1639	assert.NoError(t, err)
1640	_, err = httpdtest.RemoveUser(localUser, http.StatusOK)
1641	assert.NoError(t, err)
1642	err = os.RemoveAll(localUser.GetHomeDir())
1643	assert.NoError(t, err)
1644}
1645
1646func TestLoginWithIPilters(t *testing.T) {
1647	u := getTestUser()
1648	u.Filters.DeniedIP = []string{"192.167.0.0/24", "172.18.0.0/16"}
1649	u.Filters.AllowedIP = []string{"172.19.0.0/16"}
1650	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
1651	assert.NoError(t, err)
1652	client, err := getFTPClient(user, true, nil)
1653	if !assert.Error(t, err) {
1654		err = client.Quit()
1655		assert.NoError(t, err)
1656	}
1657
1658	_, err = httpdtest.RemoveUser(user, http.StatusOK)
1659	assert.NoError(t, err)
1660	err = os.RemoveAll(user.GetHomeDir())
1661	assert.NoError(t, err)
1662}
1663
1664func TestLoginWithDatabaseCredentials(t *testing.T) {
1665	u := getTestUser()
1666	u.FsConfig.Provider = sdk.GCSFilesystemProvider
1667	u.FsConfig.GCSConfig.Bucket = "test"
1668	u.FsConfig.GCSConfig.Credentials = kms.NewPlainSecret(`{ "type": "service_account" }`)
1669
1670	providerConf := config.GetProviderConf()
1671	providerConf.PreferDatabaseCredentials = true
1672	credentialsFile := filepath.Join(providerConf.CredentialsPath, fmt.Sprintf("%v_gcs_credentials.json", u.Username))
1673	if !filepath.IsAbs(credentialsFile) {
1674		credentialsFile = filepath.Join(configDir, credentialsFile)
1675	}
1676
1677	assert.NoError(t, dataprovider.Close())
1678
1679	err := dataprovider.Initialize(providerConf, configDir, true)
1680	assert.NoError(t, err)
1681
1682	if _, err = os.Stat(credentialsFile); err == nil {
1683		// remove the credentials file
1684		assert.NoError(t, os.Remove(credentialsFile))
1685	}
1686
1687	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
1688	assert.NoError(t, err)
1689	assert.Equal(t, kms.SecretStatusSecretBox, user.FsConfig.GCSConfig.Credentials.GetStatus())
1690	assert.NotEmpty(t, user.FsConfig.GCSConfig.Credentials.GetPayload())
1691	assert.Empty(t, user.FsConfig.GCSConfig.Credentials.GetAdditionalData())
1692	assert.Empty(t, user.FsConfig.GCSConfig.Credentials.GetKey())
1693
1694	assert.NoFileExists(t, credentialsFile)
1695
1696	client, err := getFTPClient(user, false, nil)
1697	if assert.NoError(t, err) {
1698		err = client.Quit()
1699		assert.NoError(t, err)
1700	}
1701
1702	_, err = httpdtest.RemoveUser(user, http.StatusOK)
1703	assert.NoError(t, err)
1704	err = os.RemoveAll(user.GetHomeDir())
1705	assert.NoError(t, err)
1706
1707	assert.NoError(t, dataprovider.Close())
1708	assert.NoError(t, config.LoadConfig(configDir, ""))
1709	providerConf = config.GetProviderConf()
1710	assert.NoError(t, dataprovider.Initialize(providerConf, configDir, true))
1711}
1712
1713func TestLoginInvalidFs(t *testing.T) {
1714	u := getTestUser()
1715	u.FsConfig.Provider = sdk.GCSFilesystemProvider
1716	u.FsConfig.GCSConfig.Bucket = "test"
1717	u.FsConfig.GCSConfig.Credentials = kms.NewPlainSecret("invalid JSON for credentials")
1718	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
1719	assert.NoError(t, err)
1720
1721	providerConf := config.GetProviderConf()
1722	credentialsFile := filepath.Join(providerConf.CredentialsPath, fmt.Sprintf("%v_gcs_credentials.json", u.Username))
1723	if !filepath.IsAbs(credentialsFile) {
1724		credentialsFile = filepath.Join(configDir, credentialsFile)
1725	}
1726
1727	// now remove the credentials file so the filesystem creation will fail
1728	err = os.Remove(credentialsFile)
1729	assert.NoError(t, err)
1730
1731	client, err := getFTPClient(user, false, nil)
1732	if !assert.Error(t, err) {
1733		err = client.Quit()
1734		assert.NoError(t, err)
1735	}
1736	_, err = httpdtest.RemoveUser(user, http.StatusOK)
1737	assert.NoError(t, err)
1738	err = os.RemoveAll(user.GetHomeDir())
1739	assert.NoError(t, err)
1740}
1741
1742func TestClientClose(t *testing.T) {
1743	u := getTestUser()
1744	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
1745	assert.NoError(t, err)
1746	client, err := getFTPClient(user, true, nil)
1747	if assert.NoError(t, err) {
1748		err = checkBasicFTP(client)
1749		assert.NoError(t, err)
1750		stats := common.Connections.GetStats()
1751		if assert.Len(t, stats, 1) {
1752			common.Connections.Close(stats[0].ConnectionID)
1753			assert.Eventually(t, func() bool { return len(common.Connections.GetStats()) == 0 },
1754				1*time.Second, 50*time.Millisecond)
1755		}
1756	}
1757	_, err = httpdtest.RemoveUser(user, http.StatusOK)
1758	assert.NoError(t, err)
1759	err = os.RemoveAll(user.GetHomeDir())
1760	assert.NoError(t, err)
1761}
1762
1763func TestRename(t *testing.T) {
1764	u := getTestUser()
1765	localUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
1766	assert.NoError(t, err)
1767	sftpUser, _, err := httpdtest.AddUser(getTestSFTPUser(), http.StatusCreated)
1768	assert.NoError(t, err)
1769	for _, user := range []dataprovider.User{localUser, sftpUser} {
1770		testDir := "adir"
1771		testFilePath := filepath.Join(homeBasePath, testFileName)
1772		testFileSize := int64(65535)
1773		err = createTestFile(testFilePath, testFileSize)
1774		assert.NoError(t, err)
1775		client, err := getFTPClient(user, false, nil)
1776		if assert.NoError(t, err) {
1777			err = checkBasicFTP(client)
1778			assert.NoError(t, err)
1779			err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
1780			assert.NoError(t, err)
1781			err = client.MakeDir(testDir)
1782			assert.NoError(t, err)
1783			err = client.Rename(testFileName, path.Join("missing", testFileName))
1784			assert.Error(t, err)
1785			err = client.Rename(testFileName, path.Join(testDir, testFileName))
1786			assert.NoError(t, err)
1787			size, err := client.FileSize(path.Join(testDir, testFileName))
1788			assert.NoError(t, err)
1789			assert.Equal(t, testFileSize, size)
1790			if runtime.GOOS != osWindows {
1791				otherDir := "dir"
1792				err = client.MakeDir(otherDir)
1793				assert.NoError(t, err)
1794				err = client.MakeDir(path.Join(otherDir, testDir))
1795				assert.NoError(t, err)
1796				code, response, err := client.SendCustomCommand(fmt.Sprintf("SITE CHMOD 0001 %v", otherDir))
1797				assert.NoError(t, err)
1798				assert.Equal(t, ftp.StatusCommandOK, code)
1799				assert.Equal(t, "SITE CHMOD command successful", response)
1800				err = client.Rename(testDir, path.Join(otherDir, testDir))
1801				assert.Error(t, err)
1802
1803				code, response, err = client.SendCustomCommand(fmt.Sprintf("SITE CHMOD 755 %v", otherDir))
1804				assert.NoError(t, err)
1805				assert.Equal(t, ftp.StatusCommandOK, code)
1806				assert.Equal(t, "SITE CHMOD command successful", response)
1807			}
1808			err = client.Quit()
1809			assert.NoError(t, err)
1810		}
1811		user.Permissions[path.Join("/", testDir)] = []string{dataprovider.PermListItems}
1812		user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
1813		assert.NoError(t, err)
1814		client, err = getFTPClient(user, false, nil)
1815		if assert.NoError(t, err) {
1816			err = client.Rename(path.Join(testDir, testFileName), testFileName)
1817			assert.Error(t, err)
1818			err := client.Quit()
1819			assert.NoError(t, err)
1820		}
1821
1822		err = os.Remove(testFilePath)
1823		assert.NoError(t, err)
1824		if user.Username == defaultUsername {
1825			user.Permissions = make(map[string][]string)
1826			user.Permissions["/"] = allPerms
1827			user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
1828			assert.NoError(t, err)
1829			err = os.RemoveAll(user.GetHomeDir())
1830			assert.NoError(t, err)
1831		}
1832	}
1833	_, err = httpdtest.RemoveUser(sftpUser, http.StatusOK)
1834	assert.NoError(t, err)
1835	_, err = httpdtest.RemoveUser(localUser, http.StatusOK)
1836	assert.NoError(t, err)
1837	err = os.RemoveAll(localUser.GetHomeDir())
1838	assert.NoError(t, err)
1839}
1840
1841func TestSymlink(t *testing.T) {
1842	u := getTestUser()
1843	localUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
1844	assert.NoError(t, err)
1845	sftpUser, _, err := httpdtest.AddUser(getTestSFTPUser(), http.StatusCreated)
1846	assert.NoError(t, err)
1847	testFilePath := filepath.Join(homeBasePath, testFileName)
1848	testFileSize := int64(65535)
1849	for _, user := range []dataprovider.User{localUser, sftpUser} {
1850		err = createTestFile(testFilePath, testFileSize)
1851		assert.NoError(t, err)
1852		client, err := getFTPClient(user, false, nil)
1853		if assert.NoError(t, err) {
1854			err = checkBasicFTP(client)
1855			assert.NoError(t, err)
1856			err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
1857			assert.NoError(t, err)
1858			code, _, err := client.SendCustomCommand(fmt.Sprintf("SITE SYMLINK %v %v", testFileName, testFileName+".link"))
1859			assert.NoError(t, err)
1860			assert.Equal(t, ftp.StatusCommandOK, code)
1861
1862			if runtime.GOOS != osWindows {
1863				testDir := "adir"
1864				otherDir := "dir"
1865				err = client.MakeDir(otherDir)
1866				assert.NoError(t, err)
1867				err = client.MakeDir(path.Join(otherDir, testDir))
1868				assert.NoError(t, err)
1869				code, response, err := client.SendCustomCommand(fmt.Sprintf("SITE CHMOD 0001 %v", otherDir))
1870				assert.NoError(t, err)
1871				assert.Equal(t, ftp.StatusCommandOK, code)
1872				assert.Equal(t, "SITE CHMOD command successful", response)
1873				code, _, err = client.SendCustomCommand(fmt.Sprintf("SITE SYMLINK %v %v", testDir, path.Join(otherDir, testDir)))
1874				assert.NoError(t, err)
1875				assert.Equal(t, ftp.StatusFileUnavailable, code)
1876
1877				code, response, err = client.SendCustomCommand(fmt.Sprintf("SITE CHMOD 755 %v", otherDir))
1878				assert.NoError(t, err)
1879				assert.Equal(t, ftp.StatusCommandOK, code)
1880				assert.Equal(t, "SITE CHMOD command successful", response)
1881			}
1882			err = client.Quit()
1883			assert.NoError(t, err)
1884			if user.Username == defaultUsername {
1885				err = os.RemoveAll(user.GetHomeDir())
1886				assert.NoError(t, err)
1887			}
1888		}
1889		err = os.Remove(testFilePath)
1890		assert.NoError(t, err)
1891	}
1892	_, err = httpdtest.RemoveUser(sftpUser, http.StatusOK)
1893	assert.NoError(t, err)
1894	_, err = httpdtest.RemoveUser(localUser, http.StatusOK)
1895	assert.NoError(t, err)
1896	err = os.RemoveAll(localUser.GetHomeDir())
1897	assert.NoError(t, err)
1898}
1899
1900func TestStat(t *testing.T) {
1901	u := getTestUser()
1902	u.Permissions["/subdir"] = []string{dataprovider.PermUpload}
1903	localUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
1904	assert.NoError(t, err)
1905	sftpUser, _, err := httpdtest.AddUser(getTestSFTPUser(), http.StatusCreated)
1906	assert.NoError(t, err)
1907
1908	for _, user := range []dataprovider.User{localUser, sftpUser} {
1909		client, err := getFTPClient(user, false, nil)
1910		if assert.NoError(t, err) {
1911			subDir := "subdir"
1912			testFilePath := filepath.Join(homeBasePath, testFileName)
1913			testFileSize := int64(65535)
1914			err = createTestFile(testFilePath, testFileSize)
1915			assert.NoError(t, err)
1916			err = client.MakeDir(subDir)
1917			assert.NoError(t, err)
1918			err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
1919			assert.NoError(t, err)
1920			err = ftpUploadFile(testFilePath, path.Join("/", subDir, testFileName), testFileSize, client, 0)
1921			assert.Error(t, err)
1922			size, err := client.FileSize(testFileName)
1923			assert.NoError(t, err)
1924			assert.Equal(t, testFileSize, size)
1925			_, err = client.FileSize(path.Join("/", subDir, testFileName))
1926			assert.Error(t, err)
1927			_, err = client.FileSize("missing file")
1928			assert.Error(t, err)
1929			err = client.Quit()
1930			assert.NoError(t, err)
1931
1932			err = os.Remove(testFilePath)
1933			assert.NoError(t, err)
1934			if user.Username == defaultUsername {
1935				err = os.RemoveAll(user.GetHomeDir())
1936				assert.NoError(t, err)
1937			}
1938		}
1939	}
1940
1941	_, err = httpdtest.RemoveUser(sftpUser, http.StatusOK)
1942	assert.NoError(t, err)
1943	_, err = httpdtest.RemoveUser(localUser, http.StatusOK)
1944	assert.NoError(t, err)
1945	err = os.RemoveAll(localUser.GetHomeDir())
1946	assert.NoError(t, err)
1947}
1948
1949func TestUploadOverwriteVfolder(t *testing.T) {
1950	u := getTestUser()
1951	vdir := "/vdir"
1952	mappedPath := filepath.Join(os.TempDir(), "vdir")
1953	folderName := filepath.Base(mappedPath)
1954	u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
1955		BaseVirtualFolder: vfs.BaseVirtualFolder{
1956			Name:       folderName,
1957			MappedPath: mappedPath,
1958		},
1959		VirtualPath: vdir,
1960		QuotaSize:   -1,
1961		QuotaFiles:  -1,
1962	})
1963	err := os.MkdirAll(mappedPath, os.ModePerm)
1964	assert.NoError(t, err)
1965	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
1966	assert.NoError(t, err)
1967	client, err := getFTPClient(user, false, nil)
1968	if assert.NoError(t, err) {
1969		testFilePath := filepath.Join(homeBasePath, testFileName)
1970		testFileSize := int64(65535)
1971		err = createTestFile(testFilePath, testFileSize)
1972		assert.NoError(t, err)
1973		err = ftpUploadFile(testFilePath, path.Join(vdir, testFileName), testFileSize, client, 0)
1974		assert.NoError(t, err)
1975		folder, _, err := httpdtest.GetFolderByName(folderName, http.StatusOK)
1976		assert.NoError(t, err)
1977		assert.Equal(t, testFileSize, folder.UsedQuotaSize)
1978		assert.Equal(t, 1, folder.UsedQuotaFiles)
1979		err = ftpUploadFile(testFilePath, path.Join(vdir, testFileName), testFileSize, client, 0)
1980		assert.NoError(t, err)
1981		folder, _, err = httpdtest.GetFolderByName(folderName, http.StatusOK)
1982		assert.NoError(t, err)
1983		assert.Equal(t, testFileSize, folder.UsedQuotaSize)
1984		assert.Equal(t, 1, folder.UsedQuotaFiles)
1985		err = client.Quit()
1986		assert.NoError(t, err)
1987		err = os.Remove(testFilePath)
1988		assert.NoError(t, err)
1989	}
1990	_, err = httpdtest.RemoveUser(user, http.StatusOK)
1991	assert.NoError(t, err)
1992	_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName}, http.StatusOK)
1993	assert.NoError(t, err)
1994	err = os.RemoveAll(user.GetHomeDir())
1995	assert.NoError(t, err)
1996	err = os.RemoveAll(mappedPath)
1997	assert.NoError(t, err)
1998}
1999
2000func TestAllocateAvailable(t *testing.T) {
2001	u := getTestUser()
2002	mappedPath := filepath.Join(os.TempDir(), "vdir")
2003	folderName := filepath.Base(mappedPath)
2004	u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
2005		BaseVirtualFolder: vfs.BaseVirtualFolder{
2006			Name:       folderName,
2007			MappedPath: mappedPath,
2008		},
2009		VirtualPath: "/vdir",
2010		QuotaSize:   110,
2011	})
2012	err := os.MkdirAll(mappedPath, os.ModePerm)
2013	assert.NoError(t, err)
2014	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
2015	assert.NoError(t, err)
2016	client, err := getFTPClient(user, false, nil)
2017	if assert.NoError(t, err) {
2018		code, response, err := client.SendCustomCommand("allo 2000000")
2019		assert.NoError(t, err)
2020		assert.Equal(t, ftp.StatusCommandOK, code)
2021		assert.Equal(t, "Done !", response)
2022
2023		code, response, err = client.SendCustomCommand("AVBL /vdir")
2024		assert.NoError(t, err)
2025		assert.Equal(t, ftp.StatusFile, code)
2026		assert.Equal(t, "110", response)
2027
2028		code, _, err = client.SendCustomCommand("AVBL")
2029		assert.NoError(t, err)
2030		assert.Equal(t, ftp.StatusFile, code)
2031
2032		err = client.Quit()
2033		assert.NoError(t, err)
2034	}
2035	user.QuotaSize = 100
2036	user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
2037	assert.NoError(t, err)
2038	client, err = getFTPClient(user, false, nil)
2039	if assert.NoError(t, err) {
2040		testFilePath := filepath.Join(homeBasePath, testFileName)
2041		testFileSize := user.QuotaSize - 1
2042		err = createTestFile(testFilePath, testFileSize)
2043		assert.NoError(t, err)
2044		code, response, err := client.SendCustomCommand("allo 99")
2045		assert.NoError(t, err)
2046		assert.Equal(t, ftp.StatusCommandOK, code)
2047		assert.Equal(t, "Done !", response)
2048		code, response, err = client.SendCustomCommand("allo 100")
2049		assert.NoError(t, err)
2050		assert.Equal(t, ftp.StatusCommandOK, code)
2051		assert.Equal(t, "Done !", response)
2052		code, response, err = client.SendCustomCommand("allo 150")
2053		assert.NoError(t, err)
2054		assert.Equal(t, ftp.StatusFileUnavailable, code)
2055		assert.Contains(t, response, ftpserver.ErrStorageExceeded.Error())
2056
2057		err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
2058		assert.NoError(t, err)
2059
2060		code, response, err = client.SendCustomCommand("AVBL")
2061		assert.NoError(t, err)
2062		assert.Equal(t, ftp.StatusFile, code)
2063		assert.Equal(t, "1", response)
2064
2065		// we still have space in vdir
2066		code, response, err = client.SendCustomCommand("allo 50")
2067		assert.NoError(t, err)
2068		assert.Equal(t, ftp.StatusCommandOK, code)
2069		assert.Equal(t, "Done !", response)
2070		err = ftpUploadFile(testFilePath, path.Join("/vdir", testFileName), testFileSize, client, 0)
2071		assert.NoError(t, err)
2072		code, response, err = client.SendCustomCommand("allo 50")
2073		assert.NoError(t, err)
2074		assert.Equal(t, ftp.StatusFileUnavailable, code)
2075		assert.Contains(t, response, ftpserver.ErrStorageExceeded.Error())
2076
2077		err = client.Quit()
2078		assert.NoError(t, err)
2079		err = os.Remove(testFilePath)
2080		assert.NoError(t, err)
2081	}
2082
2083	user.Filters.MaxUploadFileSize = 100
2084	user.QuotaSize = 0
2085	user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
2086	assert.NoError(t, err)
2087	client, err = getFTPClient(user, false, nil)
2088	if assert.NoError(t, err) {
2089		code, response, err := client.SendCustomCommand("allo 99")
2090		assert.NoError(t, err)
2091		assert.Equal(t, ftp.StatusCommandOK, code)
2092		assert.Equal(t, "Done !", response)
2093		code, response, err = client.SendCustomCommand("allo 150")
2094		assert.NoError(t, err)
2095		assert.Equal(t, ftp.StatusFileUnavailable, code)
2096		assert.Contains(t, response, ftpserver.ErrStorageExceeded.Error())
2097
2098		code, response, err = client.SendCustomCommand("AVBL")
2099		assert.NoError(t, err)
2100		assert.Equal(t, ftp.StatusFile, code)
2101		assert.Equal(t, "100", response)
2102
2103		err = client.Quit()
2104		assert.NoError(t, err)
2105	}
2106
2107	user.QuotaSize = 50
2108	user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
2109	assert.NoError(t, err)
2110	client, err = getFTPClient(user, false, nil)
2111	if assert.NoError(t, err) {
2112		code, response, err := client.SendCustomCommand("AVBL")
2113		assert.NoError(t, err)
2114		assert.Equal(t, ftp.StatusFile, code)
2115		assert.Equal(t, "0", response)
2116	}
2117
2118	user.QuotaSize = 1000
2119	user.Filters.MaxUploadFileSize = 1
2120	user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
2121	assert.NoError(t, err)
2122	client, err = getFTPClient(user, false, nil)
2123	if assert.NoError(t, err) {
2124		code, response, err := client.SendCustomCommand("AVBL")
2125		assert.NoError(t, err)
2126		assert.Equal(t, ftp.StatusFile, code)
2127		assert.Equal(t, "1", response)
2128	}
2129
2130	_, err = httpdtest.RemoveUser(user, http.StatusOK)
2131	assert.NoError(t, err)
2132	_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName}, http.StatusOK)
2133	assert.NoError(t, err)
2134	err = os.RemoveAll(user.GetHomeDir())
2135	assert.NoError(t, err)
2136	err = os.RemoveAll(mappedPath)
2137	assert.NoError(t, err)
2138}
2139
2140func TestAvailableSFTPFs(t *testing.T) {
2141	u := getTestUser()
2142	localUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
2143	assert.NoError(t, err)
2144	sftpUser, _, err := httpdtest.AddUser(getTestSFTPUser(), http.StatusCreated)
2145	assert.NoError(t, err)
2146	client, err := getFTPClient(sftpUser, false, nil)
2147	if assert.NoError(t, err) {
2148		code, response, err := client.SendCustomCommand("AVBL /")
2149		assert.NoError(t, err)
2150		assert.Equal(t, ftp.StatusFile, code)
2151		avblSize, err := strconv.ParseInt(response, 10, 64)
2152		assert.NoError(t, err)
2153		assert.Greater(t, avblSize, int64(0))
2154
2155		err = client.Quit()
2156		assert.NoError(t, err)
2157	}
2158	_, err = httpdtest.RemoveUser(sftpUser, http.StatusOK)
2159	assert.NoError(t, err)
2160	_, err = httpdtest.RemoveUser(localUser, http.StatusOK)
2161	assert.NoError(t, err)
2162	err = os.RemoveAll(localUser.GetHomeDir())
2163	assert.NoError(t, err)
2164}
2165
2166func TestChtimes(t *testing.T) {
2167	u := getTestUser()
2168	localUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
2169	assert.NoError(t, err)
2170	sftpUser, _, err := httpdtest.AddUser(getTestSFTPUser(), http.StatusCreated)
2171	assert.NoError(t, err)
2172
2173	for _, user := range []dataprovider.User{localUser, sftpUser} {
2174		client, err := getFTPClient(user, false, nil)
2175		if assert.NoError(t, err) {
2176			testFilePath := filepath.Join(homeBasePath, testFileName)
2177			testFileSize := int64(65535)
2178			err = createTestFile(testFilePath, testFileSize)
2179			assert.NoError(t, err)
2180			err = checkBasicFTP(client)
2181			assert.NoError(t, err)
2182			err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
2183			assert.NoError(t, err)
2184
2185			mtime := time.Now().Format("20060102150405")
2186			code, response, err := client.SendCustomCommand(fmt.Sprintf("MFMT %v %v", mtime, testFileName))
2187			assert.NoError(t, err)
2188			assert.Equal(t, ftp.StatusFile, code)
2189			assert.Equal(t, fmt.Sprintf("Modify=%v; %v", mtime, testFileName), response)
2190			err = client.Quit()
2191			assert.NoError(t, err)
2192
2193			err = os.Remove(testFilePath)
2194			assert.NoError(t, err)
2195			if user.Username == defaultUsername {
2196				err = os.RemoveAll(user.GetHomeDir())
2197				assert.NoError(t, err)
2198			}
2199		}
2200	}
2201	_, err = httpdtest.RemoveUser(sftpUser, http.StatusOK)
2202	assert.NoError(t, err)
2203	_, err = httpdtest.RemoveUser(localUser, http.StatusOK)
2204	assert.NoError(t, err)
2205	err = os.RemoveAll(localUser.GetHomeDir())
2206	assert.NoError(t, err)
2207}
2208
2209func TestChown(t *testing.T) {
2210	if runtime.GOOS == osWindows {
2211		t.Skip("chown is not supported on Windows")
2212	}
2213	user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
2214	assert.NoError(t, err)
2215	client, err := getFTPClient(user, true, nil)
2216	if assert.NoError(t, err) {
2217		testFilePath := filepath.Join(homeBasePath, testFileName)
2218		testFileSize := int64(131072)
2219		err = createTestFile(testFilePath, testFileSize)
2220		assert.NoError(t, err)
2221		err = checkBasicFTP(client)
2222		assert.NoError(t, err)
2223		err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
2224		assert.NoError(t, err)
2225		code, response, err := client.SendCustomCommand(fmt.Sprintf("SITE CHOWN 1000:1000 %v", testFileName))
2226		assert.NoError(t, err)
2227		assert.Equal(t, ftp.StatusFileUnavailable, code)
2228		assert.Equal(t, "Couldn't chown: operation unsupported", response)
2229		err = client.Quit()
2230		assert.NoError(t, err)
2231
2232		err = os.Remove(testFilePath)
2233		assert.NoError(t, err)
2234	}
2235
2236	_, err = httpdtest.RemoveUser(user, http.StatusOK)
2237	assert.NoError(t, err)
2238	err = os.RemoveAll(user.GetHomeDir())
2239	assert.NoError(t, err)
2240}
2241
2242func TestChmod(t *testing.T) {
2243	if runtime.GOOS == osWindows {
2244		t.Skip("chmod is partially supported on Windows")
2245	}
2246	u := getTestUser()
2247	localUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
2248	assert.NoError(t, err)
2249	sftpUser, _, err := httpdtest.AddUser(getTestSFTPUser(), http.StatusCreated)
2250	assert.NoError(t, err)
2251	for _, user := range []dataprovider.User{localUser, sftpUser} {
2252		client, err := getFTPClient(user, true, nil)
2253		if assert.NoError(t, err) {
2254			testFilePath := filepath.Join(homeBasePath, testFileName)
2255			testFileSize := int64(131072)
2256			err = createTestFile(testFilePath, testFileSize)
2257			assert.NoError(t, err)
2258			err = checkBasicFTP(client)
2259			assert.NoError(t, err)
2260			err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
2261			assert.NoError(t, err)
2262
2263			code, response, err := client.SendCustomCommand(fmt.Sprintf("SITE CHMOD 600 %v", testFileName))
2264			assert.NoError(t, err)
2265			assert.Equal(t, ftp.StatusCommandOK, code)
2266			assert.Equal(t, "SITE CHMOD command successful", response)
2267
2268			fi, err := os.Stat(filepath.Join(user.HomeDir, testFileName))
2269			if assert.NoError(t, err) {
2270				assert.Equal(t, os.FileMode(0600), fi.Mode().Perm())
2271			}
2272			err = client.Quit()
2273			assert.NoError(t, err)
2274
2275			err = os.Remove(testFilePath)
2276			assert.NoError(t, err)
2277			if user.Username == defaultUsername {
2278				err = os.RemoveAll(user.GetHomeDir())
2279				assert.NoError(t, err)
2280			}
2281		}
2282	}
2283	_, err = httpdtest.RemoveUser(sftpUser, http.StatusOK)
2284	assert.NoError(t, err)
2285	_, err = httpdtest.RemoveUser(localUser, http.StatusOK)
2286	assert.NoError(t, err)
2287	err = os.RemoveAll(localUser.GetHomeDir())
2288	assert.NoError(t, err)
2289}
2290
2291func TestCombineDisabled(t *testing.T) {
2292	u := getTestUser()
2293	localUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
2294	assert.NoError(t, err)
2295	sftpUser, _, err := httpdtest.AddUser(getTestSFTPUser(), http.StatusCreated)
2296	assert.NoError(t, err)
2297	for _, user := range []dataprovider.User{localUser, sftpUser} {
2298		client, err := getFTPClient(user, true, nil)
2299		if assert.NoError(t, err) {
2300			err = checkBasicFTP(client)
2301			assert.NoError(t, err)
2302
2303			code, response, err := client.SendCustomCommand("COMB file file.1 file.2")
2304			assert.NoError(t, err)
2305			assert.Equal(t, ftp.StatusNotImplemented, code)
2306			assert.Equal(t, "COMB support is disabled", response)
2307
2308			err = client.Quit()
2309			assert.NoError(t, err)
2310		}
2311	}
2312
2313	_, err = httpdtest.RemoveUser(sftpUser, http.StatusOK)
2314	assert.NoError(t, err)
2315	_, err = httpdtest.RemoveUser(localUser, http.StatusOK)
2316	assert.NoError(t, err)
2317	err = os.RemoveAll(localUser.GetHomeDir())
2318	assert.NoError(t, err)
2319}
2320
2321func TestActiveModeDisabled(t *testing.T) {
2322	u := getTestUser()
2323	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
2324	assert.NoError(t, err)
2325	client, err := getFTPClientImplicitTLS(user)
2326	if assert.NoError(t, err) {
2327		err = checkBasicFTP(client)
2328		assert.NoError(t, err)
2329		code, response, err := client.SendCustomCommand("PORT 10,2,0,2,4,31")
2330		assert.NoError(t, err)
2331		assert.Equal(t, ftp.StatusNotAvailable, code)
2332		assert.Equal(t, "PORT command is disabled", response)
2333
2334		code, response, err = client.SendCustomCommand("EPRT |1|132.235.1.2|6275|")
2335		assert.NoError(t, err)
2336		assert.Equal(t, ftp.StatusNotAvailable, code)
2337		assert.Equal(t, "EPRT command is disabled", response)
2338
2339		err = client.Quit()
2340		assert.NoError(t, err)
2341	}
2342
2343	client, err = getFTPClient(user, false, nil)
2344	if assert.NoError(t, err) {
2345		code, response, err := client.SendCustomCommand("PORT 10,2,0,2,4,31")
2346		assert.NoError(t, err)
2347		assert.Equal(t, ftp.StatusBadArguments, code)
2348		assert.Equal(t, "Your request does not meet the configured security requirements", response)
2349
2350		code, response, err = client.SendCustomCommand("EPRT |1|132.235.1.2|6275|")
2351		assert.NoError(t, err)
2352		assert.Equal(t, ftp.StatusBadArguments, code)
2353		assert.Equal(t, "Your request does not meet the configured security requirements", response)
2354
2355		err = client.Quit()
2356		assert.NoError(t, err)
2357	}
2358
2359	_, err = httpdtest.RemoveUser(user, http.StatusOK)
2360	assert.NoError(t, err)
2361	err = os.RemoveAll(user.GetHomeDir())
2362	assert.NoError(t, err)
2363}
2364
2365func TestSITEDisabled(t *testing.T) {
2366	u := getTestUser()
2367	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
2368	assert.NoError(t, err)
2369	client, err := getFTPClientImplicitTLS(user)
2370	if assert.NoError(t, err) {
2371		err = checkBasicFTP(client)
2372		assert.NoError(t, err)
2373
2374		code, response, err := client.SendCustomCommand("SITE CHMOD 600 afile.txt")
2375		assert.NoError(t, err)
2376		assert.Equal(t, ftp.StatusBadCommand, code)
2377		assert.Equal(t, "SITE support is disabled", response)
2378
2379		err = client.Quit()
2380		assert.NoError(t, err)
2381	}
2382	_, err = httpdtest.RemoveUser(user, http.StatusOK)
2383	assert.NoError(t, err)
2384	err = os.RemoveAll(user.GetHomeDir())
2385	assert.NoError(t, err)
2386}
2387
2388func TestHASH(t *testing.T) {
2389	u := getTestUser()
2390	localUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
2391	assert.NoError(t, err)
2392	sftpUser, _, err := httpdtest.AddUser(getTestSFTPUser(), http.StatusCreated)
2393	assert.NoError(t, err)
2394	u = getTestUserWithCryptFs()
2395	u.Username += "_crypt"
2396	cryptUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
2397	assert.NoError(t, err)
2398	for _, user := range []dataprovider.User{localUser, sftpUser, cryptUser} {
2399		client, err := getFTPClientImplicitTLS(user)
2400		if assert.NoError(t, err) {
2401			testFilePath := filepath.Join(homeBasePath, testFileName)
2402			testFileSize := int64(131072)
2403			err = createTestFile(testFilePath, testFileSize)
2404			assert.NoError(t, err)
2405			err = checkBasicFTP(client)
2406			assert.NoError(t, err)
2407			err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
2408			assert.NoError(t, err)
2409
2410			h := sha256.New()
2411			f, err := os.Open(testFilePath)
2412			assert.NoError(t, err)
2413			_, err = io.Copy(h, f)
2414			assert.NoError(t, err)
2415			hash := hex.EncodeToString(h.Sum(nil))
2416			err = f.Close()
2417			assert.NoError(t, err)
2418
2419			code, response, err := client.SendCustomCommand(fmt.Sprintf("XSHA256 %v", testFileName))
2420			assert.NoError(t, err)
2421			assert.Equal(t, ftp.StatusRequestedFileActionOK, code)
2422			assert.Contains(t, response, hash)
2423
2424			code, response, err = client.SendCustomCommand(fmt.Sprintf("HASH %v", testFileName))
2425			assert.NoError(t, err)
2426			assert.Equal(t, ftp.StatusFile, code)
2427			assert.Contains(t, response, hash)
2428
2429			err = client.Quit()
2430			assert.NoError(t, err)
2431
2432			err = os.Remove(testFilePath)
2433			assert.NoError(t, err)
2434			if user.Username == defaultUsername {
2435				err = os.RemoveAll(user.GetHomeDir())
2436				assert.NoError(t, err)
2437			}
2438		}
2439	}
2440
2441	_, err = httpdtest.RemoveUser(sftpUser, http.StatusOK)
2442	assert.NoError(t, err)
2443	_, err = httpdtest.RemoveUser(localUser, http.StatusOK)
2444	assert.NoError(t, err)
2445	err = os.RemoveAll(localUser.GetHomeDir())
2446	assert.NoError(t, err)
2447	_, err = httpdtest.RemoveUser(cryptUser, http.StatusOK)
2448	assert.NoError(t, err)
2449	err = os.RemoveAll(cryptUser.GetHomeDir())
2450	assert.NoError(t, err)
2451}
2452
2453func TestCombine(t *testing.T) {
2454	u := getTestUser()
2455	localUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
2456	assert.NoError(t, err)
2457	sftpUser, _, err := httpdtest.AddUser(getTestSFTPUser(), http.StatusCreated)
2458	assert.NoError(t, err)
2459	for _, user := range []dataprovider.User{localUser, sftpUser} {
2460		client, err := getFTPClientImplicitTLS(user)
2461		if assert.NoError(t, err) {
2462			testFilePath := filepath.Join(homeBasePath, testFileName)
2463			testFileSize := int64(131072)
2464			err = createTestFile(testFilePath, testFileSize)
2465			assert.NoError(t, err)
2466			err = checkBasicFTP(client)
2467			assert.NoError(t, err)
2468			err = ftpUploadFile(testFilePath, testFileName+".1", testFileSize, client, 0)
2469			assert.NoError(t, err)
2470			err = ftpUploadFile(testFilePath, testFileName+".2", testFileSize, client, 0)
2471			assert.NoError(t, err)
2472
2473			code, response, err := client.SendCustomCommand(fmt.Sprintf("COMB %v %v %v", testFileName, testFileName+".1", testFileName+".2"))
2474			assert.NoError(t, err)
2475			if user.Username == defaultUsername {
2476				assert.Equal(t, ftp.StatusRequestedFileActionOK, code)
2477				assert.Equal(t, "COMB succeeded!", response)
2478			} else {
2479				assert.Equal(t, ftp.StatusFileUnavailable, code)
2480				assert.Contains(t, response, "COMB is not supported for this filesystem")
2481			}
2482
2483			err = client.Quit()
2484			assert.NoError(t, err)
2485
2486			err = os.Remove(testFilePath)
2487			assert.NoError(t, err)
2488			if user.Username == defaultUsername {
2489				err = os.RemoveAll(user.GetHomeDir())
2490				assert.NoError(t, err)
2491			}
2492		}
2493	}
2494
2495	_, err = httpdtest.RemoveUser(sftpUser, http.StatusOK)
2496	assert.NoError(t, err)
2497	_, err = httpdtest.RemoveUser(localUser, http.StatusOK)
2498	assert.NoError(t, err)
2499	err = os.RemoveAll(localUser.GetHomeDir())
2500	assert.NoError(t, err)
2501}
2502
2503func TestClientCertificateAuthRevokedCert(t *testing.T) {
2504	u := getTestUser()
2505	u.Username = tlsClient2Username
2506	u.Filters.TLSUsername = sdk.TLSUsernameCN
2507	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
2508	assert.NoError(t, err)
2509	tlsConfig := &tls.Config{
2510		ServerName:         "localhost",
2511		InsecureSkipVerify: true, // use this for tests only
2512		MinVersion:         tls.VersionTLS12,
2513	}
2514	tlsCert, err := tls.X509KeyPair([]byte(client2Crt), []byte(client2Key))
2515	assert.NoError(t, err)
2516	tlsConfig.Certificates = append(tlsConfig.Certificates, tlsCert)
2517	_, err = getFTPClient(user, true, tlsConfig)
2518	if assert.Error(t, err) {
2519		assert.Contains(t, err.Error(), "bad certificate")
2520	}
2521
2522	_, err = httpdtest.RemoveUser(user, http.StatusOK)
2523	assert.NoError(t, err)
2524	err = os.RemoveAll(user.GetHomeDir())
2525	assert.NoError(t, err)
2526}
2527
2528func TestClientCertificateAuth(t *testing.T) {
2529	u := getTestUser()
2530	u.Username = tlsClient1Username
2531	u.Filters.DeniedLoginMethods = []string{dataprovider.LoginMethodPassword}
2532	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
2533	assert.NoError(t, err)
2534	tlsConfig := &tls.Config{
2535		ServerName:         "localhost",
2536		InsecureSkipVerify: true, // use this for tests only
2537		MinVersion:         tls.VersionTLS12,
2538	}
2539	tlsCert, err := tls.X509KeyPair([]byte(client1Crt), []byte(client1Key))
2540	assert.NoError(t, err)
2541	tlsConfig.Certificates = append(tlsConfig.Certificates, tlsCert)
2542	// TLS username is not enabled, mutual TLS should fail
2543	_, err = getFTPClient(user, true, tlsConfig)
2544	if assert.Error(t, err) {
2545		assert.Contains(t, err.Error(), "login method password is not allowed")
2546	}
2547
2548	user.Filters.TLSUsername = sdk.TLSUsernameCN
2549	user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
2550	assert.NoError(t, err)
2551	client, err := getFTPClient(user, true, tlsConfig)
2552	if assert.NoError(t, err) {
2553		err = checkBasicFTP(client)
2554		assert.NoError(t, err)
2555		err = client.Quit()
2556		assert.NoError(t, err)
2557	}
2558
2559	// now use a valid certificate with a CN different from username
2560	u = getTestUser()
2561	u.Username = tlsClient2Username
2562	u.Filters.TLSUsername = sdk.TLSUsernameCN
2563	u.Filters.DeniedLoginMethods = []string{dataprovider.LoginMethodPassword}
2564	user2, _, err := httpdtest.AddUser(u, http.StatusCreated)
2565	assert.NoError(t, err)
2566	_, err = getFTPClient(user2, true, tlsConfig)
2567	if assert.Error(t, err) {
2568		assert.Contains(t, err.Error(), "does not match username")
2569	}
2570
2571	// now disable certificate authentication
2572	user.Filters.DeniedLoginMethods = append(user.Filters.DeniedLoginMethods, dataprovider.LoginMethodTLSCertificate,
2573		dataprovider.LoginMethodTLSCertificateAndPwd)
2574	user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
2575	assert.NoError(t, err)
2576	_, err = getFTPClient(user, true, tlsConfig)
2577	if assert.Error(t, err) {
2578		assert.Contains(t, err.Error(), "login method TLSCertificate+password is not allowed")
2579	}
2580
2581	// disable FTP protocol
2582	user.Filters.DeniedLoginMethods = []string{dataprovider.LoginMethodPassword}
2583	user.Filters.DeniedProtocols = append(user.Filters.DeniedProtocols, common.ProtocolFTP)
2584	user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
2585	assert.NoError(t, err)
2586	_, err = getFTPClient(user, true, tlsConfig)
2587	if assert.Error(t, err) {
2588		assert.Contains(t, err.Error(), "protocol FTP is not allowed")
2589	}
2590
2591	_, err = httpdtest.RemoveUser(user, http.StatusOK)
2592	assert.NoError(t, err)
2593	err = os.RemoveAll(user.GetHomeDir())
2594	assert.NoError(t, err)
2595
2596	_, err = httpdtest.RemoveUser(user2, http.StatusOK)
2597	assert.NoError(t, err)
2598	err = os.RemoveAll(user2.GetHomeDir())
2599	assert.NoError(t, err)
2600
2601	_, err = getFTPClient(user, true, tlsConfig)
2602	assert.Error(t, err)
2603}
2604
2605func TestClientCertificateAndPwdAuth(t *testing.T) {
2606	u := getTestUser()
2607	u.Username = tlsClient1Username
2608	u.Filters.TLSUsername = sdk.TLSUsernameCN
2609	u.Filters.DeniedLoginMethods = []string{dataprovider.LoginMethodPassword, dataprovider.LoginMethodTLSCertificate}
2610	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
2611	assert.NoError(t, err)
2612	tlsConfig := &tls.Config{
2613		ServerName:         "localhost",
2614		InsecureSkipVerify: true, // use this for tests only
2615		MinVersion:         tls.VersionTLS12,
2616	}
2617	tlsCert, err := tls.X509KeyPair([]byte(client1Crt), []byte(client1Key))
2618	assert.NoError(t, err)
2619	tlsConfig.Certificates = append(tlsConfig.Certificates, tlsCert)
2620	client, err := getFTPClient(user, true, tlsConfig)
2621	if assert.NoError(t, err) {
2622		err = checkBasicFTP(client)
2623		assert.NoError(t, err)
2624		err = client.Quit()
2625		assert.NoError(t, err)
2626	}
2627
2628	_, err = getFTPClient(user, true, nil)
2629	if assert.Error(t, err) {
2630		assert.Contains(t, err.Error(), "login method password is not allowed")
2631	}
2632	user.Password = defaultPassword + "1"
2633	_, err = getFTPClient(user, true, tlsConfig)
2634	if assert.Error(t, err) {
2635		assert.Contains(t, err.Error(), "invalid credentials")
2636	}
2637
2638	tlsCert, err = tls.X509KeyPair([]byte(client2Crt), []byte(client2Key))
2639	assert.NoError(t, err)
2640	tlsConfig.Certificates = []tls.Certificate{tlsCert}
2641	_, err = getFTPClient(user, true, tlsConfig)
2642	if assert.Error(t, err) {
2643		assert.Contains(t, err.Error(), "bad certificate")
2644	}
2645
2646	_, err = httpdtest.RemoveUser(user, http.StatusOK)
2647	assert.NoError(t, err)
2648	err = os.RemoveAll(user.GetHomeDir())
2649	assert.NoError(t, err)
2650}
2651
2652func TestExternatAuthWithClientCert(t *testing.T) {
2653	if runtime.GOOS == osWindows {
2654		t.Skip("this test is not available on Windows")
2655	}
2656	u := getTestUser()
2657	u.Username = tlsClient1Username
2658	u.Filters.DeniedLoginMethods = append(u.Filters.DeniedLoginMethods, dataprovider.LoginMethodPassword)
2659	u.Filters.TLSUsername = sdk.TLSUsernameCN
2660	err := dataprovider.Close()
2661	assert.NoError(t, err)
2662	err = config.LoadConfig(configDir, "")
2663	assert.NoError(t, err)
2664	providerConf := config.GetProviderConf()
2665	err = os.WriteFile(extAuthPath, getExtAuthScriptContent(u, false, ""), os.ModePerm)
2666	assert.NoError(t, err)
2667	providerConf.ExternalAuthHook = extAuthPath
2668	providerConf.ExternalAuthScope = 8
2669	err = dataprovider.Initialize(providerConf, configDir, true)
2670	assert.NoError(t, err)
2671
2672	// external auth not called, auth scope is 8
2673	_, err = getFTPClient(u, true, nil)
2674	assert.Error(t, err)
2675	_, _, err = httpdtest.GetUserByUsername(u.Username, http.StatusNotFound)
2676	assert.NoError(t, err)
2677
2678	tlsConfig := &tls.Config{
2679		ServerName:         "localhost",
2680		InsecureSkipVerify: true, // use this for tests only
2681		MinVersion:         tls.VersionTLS12,
2682	}
2683	tlsCert, err := tls.X509KeyPair([]byte(client1Crt), []byte(client1Key))
2684	assert.NoError(t, err)
2685	tlsConfig.Certificates = append(tlsConfig.Certificates, tlsCert)
2686	client, err := getFTPClient(u, true, tlsConfig)
2687	if assert.NoError(t, err) {
2688		err = checkBasicFTP(client)
2689		assert.NoError(t, err)
2690		err := client.Quit()
2691		assert.NoError(t, err)
2692	}
2693
2694	user, _, err := httpdtest.GetUserByUsername(u.Username, http.StatusOK)
2695	assert.NoError(t, err)
2696	assert.Equal(t, u.Username, user.Username)
2697	_, err = httpdtest.RemoveUser(user, http.StatusOK)
2698	assert.NoError(t, err)
2699	err = os.RemoveAll(user.GetHomeDir())
2700	assert.NoError(t, err)
2701
2702	u.Username = tlsClient2Username
2703	_, err = getFTPClient(u, true, tlsConfig)
2704	if assert.Error(t, err) {
2705		assert.Contains(t, err.Error(), "invalid credentials")
2706	}
2707
2708	err = dataprovider.Close()
2709	assert.NoError(t, err)
2710	err = config.LoadConfig(configDir, "")
2711	assert.NoError(t, err)
2712	providerConf = config.GetProviderConf()
2713	err = dataprovider.Initialize(providerConf, configDir, true)
2714	assert.NoError(t, err)
2715	err = os.Remove(extAuthPath)
2716	assert.NoError(t, err)
2717}
2718
2719func TestPreLoginHookWithClientCert(t *testing.T) {
2720	if runtime.GOOS == osWindows {
2721		t.Skip("this test is not available on Windows")
2722	}
2723	u := getTestUser()
2724	u.Username = tlsClient1Username
2725	u.Filters.DeniedLoginMethods = append(u.Filters.DeniedLoginMethods, dataprovider.LoginMethodPassword)
2726	u.Filters.TLSUsername = sdk.TLSUsernameCN
2727	err := dataprovider.Close()
2728	assert.NoError(t, err)
2729	err = config.LoadConfig(configDir, "")
2730	assert.NoError(t, err)
2731	providerConf := config.GetProviderConf()
2732	err = os.WriteFile(preLoginPath, getPreLoginScriptContent(u, false), os.ModePerm)
2733	assert.NoError(t, err)
2734	providerConf.PreLoginHook = preLoginPath
2735	err = dataprovider.Initialize(providerConf, configDir, true)
2736	assert.NoError(t, err)
2737	_, _, err = httpdtest.GetUserByUsername(tlsClient1Username, http.StatusNotFound)
2738	assert.NoError(t, err)
2739	tlsConfig := &tls.Config{
2740		ServerName:         "localhost",
2741		InsecureSkipVerify: true, // use this for tests only
2742		MinVersion:         tls.VersionTLS12,
2743	}
2744	tlsCert, err := tls.X509KeyPair([]byte(client1Crt), []byte(client1Key))
2745	assert.NoError(t, err)
2746	tlsConfig.Certificates = append(tlsConfig.Certificates, tlsCert)
2747	client, err := getFTPClient(u, true, tlsConfig)
2748	if assert.NoError(t, err) {
2749		err = checkBasicFTP(client)
2750		assert.NoError(t, err)
2751		err := client.Quit()
2752		assert.NoError(t, err)
2753	}
2754
2755	user, _, err := httpdtest.GetUserByUsername(tlsClient1Username, http.StatusOK)
2756	assert.NoError(t, err)
2757
2758	// test login with an existing user
2759	client, err = getFTPClient(user, true, tlsConfig)
2760	if assert.NoError(t, err) {
2761		err = checkBasicFTP(client)
2762		assert.NoError(t, err)
2763		err := client.Quit()
2764		assert.NoError(t, err)
2765	}
2766
2767	u.Username = tlsClient2Username
2768	err = os.WriteFile(preLoginPath, getPreLoginScriptContent(u, false), os.ModePerm)
2769	assert.NoError(t, err)
2770	_, err = getFTPClient(u, true, tlsConfig)
2771	if assert.Error(t, err) {
2772		assert.Contains(t, err.Error(), "does not match username")
2773	}
2774
2775	user2, _, err := httpdtest.GetUserByUsername(tlsClient2Username, http.StatusOK)
2776	assert.NoError(t, err)
2777	_, err = httpdtest.RemoveUser(user2, http.StatusOK)
2778	assert.NoError(t, err)
2779	err = os.RemoveAll(user2.GetHomeDir())
2780	assert.NoError(t, err)
2781
2782	_, err = httpdtest.RemoveUser(user, http.StatusOK)
2783	assert.NoError(t, err)
2784	err = os.RemoveAll(user.GetHomeDir())
2785	assert.NoError(t, err)
2786	err = dataprovider.Close()
2787	assert.NoError(t, err)
2788	err = config.LoadConfig(configDir, "")
2789	assert.NoError(t, err)
2790	providerConf = config.GetProviderConf()
2791	err = dataprovider.Initialize(providerConf, configDir, true)
2792	assert.NoError(t, err)
2793	err = os.Remove(preLoginPath)
2794	assert.NoError(t, err)
2795}
2796
2797func TestNestedVirtualFolders(t *testing.T) {
2798	u := getTestUser()
2799	localUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
2800	assert.NoError(t, err)
2801	u = getTestSFTPUser()
2802	mappedPathCrypt := filepath.Join(os.TempDir(), "crypt")
2803	folderNameCrypt := filepath.Base(mappedPathCrypt)
2804	vdirCryptPath := "/vdir/crypt"
2805	u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
2806		BaseVirtualFolder: vfs.BaseVirtualFolder{
2807			Name: folderNameCrypt,
2808			FsConfig: vfs.Filesystem{
2809				Provider: sdk.CryptedFilesystemProvider,
2810				CryptConfig: vfs.CryptFsConfig{
2811					CryptFsConfig: sdk.CryptFsConfig{
2812						Passphrase: kms.NewPlainSecret(defaultPassword),
2813					},
2814				},
2815			},
2816			MappedPath: mappedPathCrypt,
2817		},
2818		VirtualPath: vdirCryptPath,
2819	})
2820	mappedPath := filepath.Join(os.TempDir(), "local")
2821	folderName := filepath.Base(mappedPath)
2822	vdirPath := "/vdir/local"
2823	u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
2824		BaseVirtualFolder: vfs.BaseVirtualFolder{
2825			Name:       folderName,
2826			MappedPath: mappedPath,
2827		},
2828		VirtualPath: vdirPath,
2829	})
2830	mappedPathNested := filepath.Join(os.TempDir(), "nested")
2831	folderNameNested := filepath.Base(mappedPathNested)
2832	vdirNestedPath := "/vdir/crypt/nested"
2833	u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
2834		BaseVirtualFolder: vfs.BaseVirtualFolder{
2835			Name:       folderNameNested,
2836			MappedPath: mappedPathNested,
2837		},
2838		VirtualPath: vdirNestedPath,
2839		QuotaFiles:  -1,
2840		QuotaSize:   -1,
2841	})
2842	sftpUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
2843	assert.NoError(t, err)
2844	client, err := getFTPClient(sftpUser, false, nil)
2845	if assert.NoError(t, err) {
2846		err = checkBasicFTP(client)
2847		assert.NoError(t, err)
2848		testFilePath := filepath.Join(homeBasePath, testFileName)
2849		testFileSize := int64(65535)
2850		err = createTestFile(testFilePath, testFileSize)
2851		assert.NoError(t, err)
2852		localDownloadPath := filepath.Join(homeBasePath, testDLFileName)
2853
2854		err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
2855		assert.NoError(t, err)
2856		err = ftpDownloadFile(testFileName, localDownloadPath, testFileSize, client, 0)
2857		assert.NoError(t, err)
2858		err = ftpUploadFile(testFilePath, path.Join("/vdir", testFileName), testFileSize, client, 0)
2859		assert.NoError(t, err)
2860		err = ftpDownloadFile(path.Join("/vdir", testFileName), localDownloadPath, testFileSize, client, 0)
2861		assert.NoError(t, err)
2862		err = ftpUploadFile(testFilePath, path.Join(vdirPath, testFileName), testFileSize, client, 0)
2863		assert.NoError(t, err)
2864		err = ftpDownloadFile(path.Join(vdirPath, testFileName), localDownloadPath, testFileSize, client, 0)
2865		assert.NoError(t, err)
2866		err = ftpUploadFile(testFilePath, path.Join(vdirCryptPath, testFileName), testFileSize, client, 0)
2867		assert.NoError(t, err)
2868		err = ftpDownloadFile(path.Join(vdirCryptPath, testFileName), localDownloadPath, testFileSize, client, 0)
2869		assert.NoError(t, err)
2870		err = ftpUploadFile(testFilePath, path.Join(vdirNestedPath, testFileName), testFileSize, client, 0)
2871		assert.NoError(t, err)
2872		err = ftpDownloadFile(path.Join(vdirNestedPath, testFileName), localDownloadPath, testFileSize, client, 0)
2873		assert.NoError(t, err)
2874
2875		err = client.Quit()
2876		assert.NoError(t, err)
2877		err = os.Remove(testFilePath)
2878		assert.NoError(t, err)
2879		err = os.Remove(localDownloadPath)
2880		assert.NoError(t, err)
2881	}
2882
2883	_, err = httpdtest.RemoveUser(sftpUser, http.StatusOK)
2884	assert.NoError(t, err)
2885	_, err = httpdtest.RemoveUser(localUser, http.StatusOK)
2886	assert.NoError(t, err)
2887	_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderNameCrypt}, http.StatusOK)
2888	assert.NoError(t, err)
2889	_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName}, http.StatusOK)
2890	assert.NoError(t, err)
2891	_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderNameNested}, http.StatusOK)
2892	assert.NoError(t, err)
2893	err = os.RemoveAll(mappedPathCrypt)
2894	assert.NoError(t, err)
2895	err = os.RemoveAll(mappedPath)
2896	assert.NoError(t, err)
2897	err = os.RemoveAll(mappedPathNested)
2898	assert.NoError(t, err)
2899	err = os.RemoveAll(localUser.GetHomeDir())
2900	assert.NoError(t, err)
2901	assert.Eventually(t, func() bool { return len(common.Connections.GetStats()) == 0 }, 1*time.Second, 50*time.Millisecond)
2902	assert.Eventually(t, func() bool { return common.Connections.GetClientConnections() == 0 }, 1000*time.Millisecond,
2903		50*time.Millisecond)
2904}
2905
2906func checkBasicFTP(client *ftp.ServerConn) error {
2907	_, err := client.CurrentDir()
2908	if err != nil {
2909		return err
2910	}
2911	err = client.NoOp()
2912	if err != nil {
2913		return err
2914	}
2915	_, err = client.List(".")
2916	if err != nil {
2917		return err
2918	}
2919	return nil
2920}
2921
2922func ftpUploadFile(localSourcePath string, remoteDestPath string, expectedSize int64, client *ftp.ServerConn, offset uint64) error {
2923	srcFile, err := os.Open(localSourcePath)
2924	if err != nil {
2925		return err
2926	}
2927	defer srcFile.Close()
2928	if offset > 0 {
2929		err = client.StorFrom(remoteDestPath, srcFile, offset)
2930	} else {
2931		err = client.Stor(remoteDestPath, srcFile)
2932	}
2933	if err != nil {
2934		return err
2935	}
2936	if expectedSize > 0 {
2937		size, err := client.FileSize(remoteDestPath)
2938		if err != nil {
2939			return err
2940		}
2941		if size != expectedSize {
2942			return fmt.Errorf("uploaded file size does not match, actual: %v, expected: %v", size, expectedSize)
2943		}
2944	}
2945	return nil
2946}
2947
2948func ftpDownloadFile(remoteSourcePath string, localDestPath string, expectedSize int64, client *ftp.ServerConn, offset uint64) error {
2949	downloadDest, err := os.Create(localDestPath)
2950	if err != nil {
2951		return err
2952	}
2953	defer downloadDest.Close()
2954	var r *ftp.Response
2955	if offset > 0 {
2956		r, err = client.RetrFrom(remoteSourcePath, offset)
2957	} else {
2958		r, err = client.Retr(remoteSourcePath)
2959	}
2960	if err != nil {
2961		return err
2962	}
2963	defer r.Close()
2964
2965	written, err := io.Copy(downloadDest, r)
2966	if err != nil {
2967		return err
2968	}
2969	if written != expectedSize {
2970		return fmt.Errorf("downloaded file size does not match, actual: %v, expected: %v", written, expectedSize)
2971	}
2972	return nil
2973}
2974
2975func getFTPClientImplicitTLS(user dataprovider.User) (*ftp.ServerConn, error) {
2976	ftpOptions := []ftp.DialOption{ftp.DialWithTimeout(5 * time.Second)}
2977	tlsConfig := &tls.Config{
2978		ServerName:         "localhost",
2979		InsecureSkipVerify: true, // use this for tests only
2980		MinVersion:         tls.VersionTLS12,
2981	}
2982	ftpOptions = append(ftpOptions, ftp.DialWithTLS(tlsConfig))
2983	ftpOptions = append(ftpOptions, ftp.DialWithDisabledEPSV(true))
2984	client, err := ftp.Dial(ftpSrvAddrTLS, ftpOptions...)
2985	if err != nil {
2986		return nil, err
2987	}
2988	pwd := defaultPassword
2989	if user.Password != "" {
2990		pwd = user.Password
2991	}
2992	err = client.Login(user.Username, pwd)
2993	if err != nil {
2994		return nil, err
2995	}
2996	return client, err
2997}
2998
2999func getFTPClient(user dataprovider.User, useTLS bool, tlsConfig *tls.Config) (*ftp.ServerConn, error) {
3000	ftpOptions := []ftp.DialOption{ftp.DialWithTimeout(5 * time.Second)}
3001	if useTLS {
3002		if tlsConfig == nil {
3003			tlsConfig = &tls.Config{
3004				ServerName:         "localhost",
3005				InsecureSkipVerify: true, // use this for tests only
3006				MinVersion:         tls.VersionTLS12,
3007			}
3008		}
3009		ftpOptions = append(ftpOptions, ftp.DialWithExplicitTLS(tlsConfig))
3010	}
3011	client, err := ftp.Dial(ftpServerAddr, ftpOptions...)
3012	if err != nil {
3013		return nil, err
3014	}
3015	pwd := defaultPassword
3016	if user.Password != "" {
3017		pwd = user.Password
3018	}
3019	err = client.Login(user.Username, pwd)
3020	if err != nil {
3021		return nil, err
3022	}
3023	return client, err
3024}
3025
3026func waitTCPListening(address string) {
3027	for {
3028		conn, err := net.Dial("tcp", address)
3029		if err != nil {
3030			logger.WarnToConsole("tcp server %v not listening: %v", address, err)
3031			time.Sleep(100 * time.Millisecond)
3032			continue
3033		}
3034		logger.InfoToConsole("tcp server %v now listening", address)
3035		conn.Close()
3036		break
3037	}
3038}
3039
3040func waitNoConnections() {
3041	time.Sleep(50 * time.Millisecond)
3042	for len(common.Connections.GetStats()) > 0 {
3043		time.Sleep(50 * time.Millisecond)
3044	}
3045}
3046
3047func getTestUser() dataprovider.User {
3048	user := dataprovider.User{
3049		BaseUser: sdk.BaseUser{
3050			Username:       defaultUsername,
3051			Password:       defaultPassword,
3052			HomeDir:        filepath.Join(homeBasePath, defaultUsername),
3053			Status:         1,
3054			ExpirationDate: 0,
3055		},
3056	}
3057	user.Permissions = make(map[string][]string)
3058	user.Permissions["/"] = allPerms
3059	return user
3060}
3061
3062func getTestSFTPUser() dataprovider.User {
3063	u := getTestUser()
3064	u.Username = u.Username + "_sftp"
3065	u.FsConfig.Provider = sdk.SFTPFilesystemProvider
3066	u.FsConfig.SFTPConfig.Endpoint = sftpServerAddr
3067	u.FsConfig.SFTPConfig.Username = defaultUsername
3068	u.FsConfig.SFTPConfig.Password = kms.NewPlainSecret(defaultPassword)
3069	return u
3070}
3071
3072func getExtAuthScriptContent(user dataprovider.User, nonJSONResponse bool, username string) []byte {
3073	extAuthContent := []byte("#!/bin/sh\n\n")
3074	extAuthContent = append(extAuthContent, []byte(fmt.Sprintf("if test \"$SFTPGO_AUTHD_USERNAME\" = \"%v\"; then\n", user.Username))...)
3075	if len(username) > 0 {
3076		user.Username = username
3077	}
3078	u, _ := json.Marshal(user)
3079	if nonJSONResponse {
3080		extAuthContent = append(extAuthContent, []byte("echo 'text response'\n")...)
3081	} else {
3082		extAuthContent = append(extAuthContent, []byte(fmt.Sprintf("echo '%v'\n", string(u)))...)
3083	}
3084	extAuthContent = append(extAuthContent, []byte("else\n")...)
3085	if nonJSONResponse {
3086		extAuthContent = append(extAuthContent, []byte("echo 'text response'\n")...)
3087	} else {
3088		extAuthContent = append(extAuthContent, []byte("echo '{\"username\":\"\"}'\n")...)
3089	}
3090	extAuthContent = append(extAuthContent, []byte("fi\n")...)
3091	return extAuthContent
3092}
3093
3094func getPreLoginScriptContent(user dataprovider.User, nonJSONResponse bool) []byte {
3095	content := []byte("#!/bin/sh\n\n")
3096	if nonJSONResponse {
3097		content = append(content, []byte("echo 'text response'\n")...)
3098		return content
3099	}
3100	if len(user.Username) > 0 {
3101		u, _ := json.Marshal(user)
3102		content = append(content, []byte(fmt.Sprintf("echo '%v'\n", string(u)))...)
3103	}
3104	return content
3105}
3106
3107func getExitCodeScriptContent(exitCode int) []byte {
3108	content := []byte("#!/bin/sh\n\n")
3109	content = append(content, []byte(fmt.Sprintf("exit %v", exitCode))...)
3110	return content
3111}
3112
3113func createTestFile(path string, size int64) error {
3114	baseDir := filepath.Dir(path)
3115	if _, err := os.Stat(baseDir); os.IsNotExist(err) {
3116		err = os.MkdirAll(baseDir, os.ModePerm)
3117		if err != nil {
3118			return err
3119		}
3120	}
3121	content := make([]byte, size)
3122	_, err := rand.Read(content)
3123	if err != nil {
3124		return err
3125	}
3126	return os.WriteFile(path, content, os.ModePerm)
3127}
3128
3129func writeCerts(certPath, keyPath, caCrtPath, caCRLPath string) error {
3130	err := os.WriteFile(certPath, []byte(ftpsCert), os.ModePerm)
3131	if err != nil {
3132		logger.ErrorToConsole("error writing FTPS certificate: %v", err)
3133		return err
3134	}
3135	err = os.WriteFile(keyPath, []byte(ftpsKey), os.ModePerm)
3136	if err != nil {
3137		logger.ErrorToConsole("error writing FTPS private key: %v", err)
3138		return err
3139	}
3140	err = os.WriteFile(caCrtPath, []byte(caCRT), os.ModePerm)
3141	if err != nil {
3142		logger.ErrorToConsole("error writing FTPS CA crt: %v", err)
3143		return err
3144	}
3145	err = os.WriteFile(caCRLPath, []byte(caCRL), os.ModePerm)
3146	if err != nil {
3147		logger.ErrorToConsole("error writing FTPS CRL: %v", err)
3148		return err
3149	}
3150	return nil
3151}
3152
3153func generateTOTPPasscode(secret string, algo otp.Algorithm) (string, error) {
3154	return totp.GenerateCodeCustom(secret, time.Now(), totp.ValidateOpts{
3155		Period:    30,
3156		Skew:      1,
3157		Digits:    otp.DigitsSix,
3158		Algorithm: algo,
3159	})
3160}
3161