1package ftpd 2 3import ( 4 "crypto/tls" 5 "crypto/x509" 6 "fmt" 7 "net" 8 "os" 9 "path/filepath" 10 "runtime" 11 "testing" 12 "time" 13 14 "github.com/eikenb/pipeat" 15 ftpserver "github.com/fclairamb/ftpserverlib" 16 "github.com/pires/go-proxyproto" 17 "github.com/stretchr/testify/assert" 18 "github.com/stretchr/testify/require" 19 20 "github.com/drakkan/sftpgo/v2/common" 21 "github.com/drakkan/sftpgo/v2/dataprovider" 22 "github.com/drakkan/sftpgo/v2/sdk" 23 "github.com/drakkan/sftpgo/v2/vfs" 24) 25 26const ( 27 configDir = ".." 28 ftpsCert = `-----BEGIN CERTIFICATE----- 29MIICHTCCAaKgAwIBAgIUHnqw7QnB1Bj9oUsNpdb+ZkFPOxMwCgYIKoZIzj0EAwIw 30RTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGElu 31dGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMDAyMDQwOTUzMDRaFw0zMDAyMDEw 32OTUzMDRaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYD 33VQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwdjAQBgcqhkjOPQIBBgUrgQQA 34IgNiAARCjRMqJ85rzMC998X5z761nJ+xL3bkmGVqWvrJ51t5OxV0v25NsOgR82CA 35NXUgvhVYs7vNFN+jxtb2aj6Xg+/2G/BNxkaFspIVCzgWkxiz7XE4lgUwX44FCXZM 363+JeUbKjUzBRMB0GA1UdDgQWBBRhLw+/o3+Z02MI/d4tmaMui9W16jAfBgNVHSME 37GDAWgBRhLw+/o3+Z02MI/d4tmaMui9W16jAPBgNVHRMBAf8EBTADAQH/MAoGCCqG 38SM49BAMCA2kAMGYCMQDqLt2lm8mE+tGgtjDmtFgdOcI72HSbRQ74D5rYTzgST1rY 39/8wTi5xl8TiFUyLMUsICMQC5ViVxdXbhuG7gX6yEqSkMKZICHpO8hqFwOD/uaFVI 40dV4vKmHUzwK/eIx+8Ay3neE= 41-----END CERTIFICATE-----` 42 ftpsKey = `-----BEGIN EC PARAMETERS----- 43BgUrgQQAIg== 44-----END EC PARAMETERS----- 45-----BEGIN EC PRIVATE KEY----- 46MIGkAgEBBDCfMNsN6miEE3rVyUPwElfiJSWaR5huPCzUenZOfJT04GAcQdWvEju3 47UM2lmBLIXpGgBwYFK4EEACKhZANiAARCjRMqJ85rzMC998X5z761nJ+xL3bkmGVq 48WvrJ51t5OxV0v25NsOgR82CANXUgvhVYs7vNFN+jxtb2aj6Xg+/2G/BNxkaFspIV 49CzgWkxiz7XE4lgUwX44FCXZM3+JeUbI= 50-----END EC PRIVATE KEY-----` 51 caCRT = `-----BEGIN CERTIFICATE----- 52MIIE5jCCAs6gAwIBAgIBATANBgkqhkiG9w0BAQsFADATMREwDwYDVQQDEwhDZXJ0 53QXV0aDAeFw0yMTAxMDIyMTIwNTVaFw0yMjA3MDIyMTMwNTJaMBMxETAPBgNVBAMT 54CENlcnRBdXRoMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA4Tiho5xW 55AC15JRkMwfp3/TJwI2As7MY5dele5cmdr5bHAE+sRKqC+Ti88OJWCV5saoyax/1S 56CjxJlQMZMl169P1QYJskKjdG2sdv6RLWLMgwSNRRjxp/Bw9dHdiEb9MjLgu28Jro 579peQkHcRHeMf5hM9WvlIJGrdzbC4hUehmqggcqgARainBkYjf0SwuWxHeu4nMqkp 58Ak5tcSTLCjHfEFHZ9Te0TIPG5YkWocQKyeLgu4lvuU+DD2W2lym+YVUtRMGs1Env 59k7p+N0DcGU26qfzZ2sF5ZXkqm7dBsGQB9pIxwc2Q8T1dCIyP9OQCKVILdc5aVFf1 60cryQFHYzYNNZXFlIBims5VV5Mgfp8ESHQSue+v6n6ykecLEyKt1F1Y/MWY/nWUSI 618zdq83jdBAZVjo9MSthxVn57/06s/hQca65IpcTZV2gX0a+eRlAVqaRbAhL3LaZe 62bYsW3WHKoUOftwemuep3nL51TzlXZVL7Oz/ClGaEOsnGG9KFO6jh+W768qC0zLQI 63CdE7v2Zex98sZteHCg9fGJHIaYoF0aJG5P3WI5oZf2fy7UIYN9ADLFZiorCXAZEh 64CSU6mDoRViZ4RGR9GZxbDZ9KYn7O8M/KCR72bkQg73TlMsk1zSXEw0MKLUjtsw6c 65rZ0Jt8t3sRatHO3JrYHALMt9vZfyNCZp0IsCAwEAAaNFMEMwDgYDVR0PAQH/BAQD 66AgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFO1yCNAGr/zQTJIi8lw3 67w5OiuBvMMA0GCSqGSIb3DQEBCwUAA4ICAQA6gCNuM7r8mnx674dm31GxBjQy5ZwB 687CxDzYEvL/oiZ3Tv3HlPfN2LAAsJUfGnghh9DOytenL2CTZWjl/emP5eijzmlP+9 69zva5I6CIMCf/eDDVsRdO244t0o4uG7+At0IgSDM3bpVaVb4RHZNjEziYChsEYY8d 70HK6iwuRSvFniV6yhR/Vj1Ymi9yZ5xclqseLXiQnUB0PkfIk23+7s42cXB16653fH 71O/FsPyKBLiKJArizLYQc12aP3QOrYoYD9+fAzIIzew7A5C0aanZCGzkuFpO6TRlD 72Tb7ry9Gf0DfPpCgxraH8tOcmnqp/ka3hjqo/SRnnTk0IFrmmLdarJvjD46rKwBo4 73MjyAIR1mQ5j8GTlSFBmSgETOQ/EYvO3FPLmra1Fh7L+DvaVzTpqI9fG3TuyyY+Ri 74Fby4ycTOGSZOe5Fh8lqkX5Y47mCUJ3zHzOA1vUJy2eTlMRGpu47Eb1++Vm6EzPUP 752EF5aD+zwcssh+atZvQbwxpgVqVcyLt91RSkKkmZQslh0rnlTb68yxvUnD3zw7So 76o6TAf9UvwVMEvdLT9NnFd6hwi2jcNte/h538GJwXeBb8EkfpqLKpTKyicnOdkamZ 777E9zY8SHNRYMwB9coQ/W8NvufbCgkvOoLyMXk5edbXofXl3PhNGOlraWbghBnzf5 78r3rwjFsQOoZotA== 79-----END CERTIFICATE-----` 80 caKey = `-----BEGIN RSA PRIVATE KEY----- 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 130-----END RSA PRIVATE KEY-----` 131 caCRL = `-----BEGIN X509 CRL----- 132MIICpzCBkAIBATANBgkqhkiG9w0BAQsFADATMREwDwYDVQQDEwhDZXJ0QXV0aBcN 133MjEwMTAyMjEzNDA1WhcNMjMwMTAyMjEzNDA1WjAkMCICEQC+l04DbHWMyC3fG09k 134VXf+Fw0yMTAxMDIyMTM0MDVaoCMwITAfBgNVHSMEGDAWgBTtcgjQBq/80EySIvJc 135N8OTorgbzDANBgkqhkiG9w0BAQsFAAOCAgEAEJ7z+uNc8sqtxlOhSdTGDzX/xput 136E857kFQkSlMnU2whQ8c+XpYrBLA5vIZJNSSwohTpM4+zVBX/bJpmu3wqqaArRO9/ 137YcW5mQk9Anvb4WjQW1cHmtNapMTzoC9AiYt/OWPfy+P6JCgCr4Hy6LgQyIRL6bM9 138VYTalolOm1qa4Y5cIeT7iHq/91mfaqo8/6MYRjLl8DOTROpmw8OS9bCXkzGKdCat 139AbAzwkQUSauyoCQ10rpX+Y64w9ng3g4Dr20aCqPf5osaqplEJ2HTK8ljDTidlslv 1409anQj8ax3Su89vI8+hK+YbfVQwrThabgdSjQsn+veyx8GlP8WwHLAQ379KjZjWg+ 141OlOSwBeU1vTdP0QcB8X5C2gVujAyuQekbaV86xzIBOj7vZdfHZ6ee30TZ2FKiMyg 1427/N2OqW0w77ChsjB4MSHJCfuTgIeg62GzuZXLM+Q2Z9LBdtm4Byg+sm/P52adOEg 143gVb2Zf4KSvsAmA0PIBlu449/QXUFcMxzLFy7mwTeZj2B4Ln0Hm0szV9f9R8MwMtB 144SyLYxVH+mgqaR6Jkk22Q/yYyLPaELfafX5gp/AIXG8n0zxfVaTvK3auSgb1Q6ZLS 1455QH9dSIsmZHlPq7GoSXmKpMdjUL8eaky/IMteioyXgsBiATzl5L2dsw6MTX3MDF0 146QbDK+MzhmbKfDxs= 147-----END X509 CRL-----` 148 client1Crt = `-----BEGIN CERTIFICATE----- 149MIIEITCCAgmgAwIBAgIRAIppZHoj1hM80D7WzTEKLuAwDQYJKoZIhvcNAQELBQAw 150EzERMA8GA1UEAxMIQ2VydEF1dGgwHhcNMjEwMTAyMjEyMzEwWhcNMjIwNzAyMjEz 151MDUxWjASMRAwDgYDVQQDEwdjbGllbnQxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A 152MIIBCgKCAQEAoKbYY9MdF2kF/nhBESIiZTdVYtA8XL9xrIZyDj9EnCiTxHiVbJtH 153XVwszqSl5TRrotPmnmAQcX3r8OCk+z+RQZ0QQj257P3kG6q4rNnOcWCS5xEd20jP 154yhQ3m+hMGfZsotNTQze1ochuQgLUN6IPyPxZkH22ia3jX4iu1eo/QxeLYHj1UHw4 1553Cii9yE+j5kPUC21xmnrGKdUrB55NYLXHx6yTIqYR5znSOVB8oJi18/hwdZmH859 156DHhm0Hx1HrS+jbjI3+CMorZJ3WUyNf+CkiVLD3xYutPbxzEpwiqkG/XYzLH0habT 157cDcILo18n+o3jvem2KWBrDhyairjIDscwQIDAQABo3EwbzAOBgNVHQ8BAf8EBAMC 158A7gwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBSJ5GIv 159zIrE4ZSQt2+CGblKTDswizAfBgNVHSMEGDAWgBTtcgjQBq/80EySIvJcN8OTorgb 160zDANBgkqhkiG9w0BAQsFAAOCAgEALh4f5GhvNYNou0Ab04iQBbLEdOu2RlbK1B5n 161K9P/umYenBHMY/z6HT3+6tpcHsDuqE8UVdq3f3Gh4S2Gu9m8PRitT+cJ3gdo9Plm 1623rD4ufn/s6rGg3ppydXcedm17492tbccUDWOBZw3IO/ASVq13WPgT0/Kev7cPq0k 163sSdSNhVeXqx8Myc2/d+8GYyzbul2Kpfa7h9i24sK49E9ftnSmsIvngONo08eT1T0 1643wAOyK2981LIsHaAWcneShKFLDB6LeXIT9oitOYhiykhFlBZ4M1GNlSNfhQ8IIQP 165xbqMNXCLkW4/BtLhGEEcg0QVso6Kudl9rzgTfQknrdF7pHp6rS46wYUjoSyIY6dl 166oLmnoAVJX36J3QPWelePI9e07X2wrTfiZWewwgw3KNRWjd6/zfPLe7GoqXnK1S2z 167PT8qMfCaTwKTtUkzXuTFvQ8bAo2My/mS8FOcpkt2oQWeOsADHAUX7fz5BCoa2DL3 168k/7Mh4gVT+JYZEoTwCFuYHgMWFWe98naqHi9lB4yR981p1QgXgxO7qBeipagKY1F 169LlH1iwXUqZ3MZnkNA+4e1Fglsw3sa/rC+L98HnznJ/YbTfQbCP6aQ1qcOymrjMud 1707MrFwqZjtd/SK4Qx1VpK6jGEAtPgWBTUS3p9ayg6lqjMBjsmySWfvRsDQbq6P5Ct 171O/e3EH8= 172-----END CERTIFICATE-----` 173 client1Key = `-----BEGIN RSA PRIVATE KEY----- 174MIIEpAIBAAKCAQEAoKbYY9MdF2kF/nhBESIiZTdVYtA8XL9xrIZyDj9EnCiTxHiV 175bJtHXVwszqSl5TRrotPmnmAQcX3r8OCk+z+RQZ0QQj257P3kG6q4rNnOcWCS5xEd 17620jPyhQ3m+hMGfZsotNTQze1ochuQgLUN6IPyPxZkH22ia3jX4iu1eo/QxeLYHj1 177UHw43Cii9yE+j5kPUC21xmnrGKdUrB55NYLXHx6yTIqYR5znSOVB8oJi18/hwdZm 178H859DHhm0Hx1HrS+jbjI3+CMorZJ3WUyNf+CkiVLD3xYutPbxzEpwiqkG/XYzLH0 179habTcDcILo18n+o3jvem2KWBrDhyairjIDscwQIDAQABAoIBAEBSjVFqtbsp0byR 180aXvyrtLX1Ng7h++at2jca85Ihq//jyqbHTje8zPuNAKI6eNbmb0YGr5OuEa4pD9N 181ssDmMsKSoG/lRwwcm7h4InkSvBWpFShvMgUaohfHAHzsBYxfnh+TfULsi0y7c2n6 182t/2OZcOTRkkUDIITnXYiw93ibHHv2Mv2bBDu35kGrcK+c2dN5IL5ZjTjMRpbJTe2 18344RBJbdTxHBVSgoGBnugF+s2aEma6Ehsj70oyfoVpM6Aed5kGge0A5zA1JO7WCn9 184Ay/DzlULRXHjJIoRWd2NKvx5n3FNppUc9vJh2plRHalRooZ2+MjSf8HmXlvG2Hpb 185ScvmWgECgYEA1G+A/2KnxWsr/7uWIJ7ClcGCiNLdk17Pv3DZ3G4qUsU2ITftfIbb 186tU0Q/b19na1IY8Pjy9ptP7t74/hF5kky97cf1FA8F+nMj/k4+wO8QDI8OJfzVzh9 187PwielA5vbE+xmvis5Hdp8/od1Yrc/rPSy2TKtPFhvsqXjqoUmOAjDP8CgYEAwZjH 1889dt1sc2lx/rMxihlWEzQ3JPswKW9/LJAmbRBoSWF9FGNjbX7uhWtXRKJkzb8ZAwa 18988azluNo2oftbDD/+jw8b2cDgaJHlLAkSD4O1D1RthW7/LKD15qZ/oFsRb13NV85 190ZNKtwslXGbfVNyGKUVFm7fVA8vBAOUey+LKDFj8CgYEAg8WWstOzVdYguMTXXuyb 191ruEV42FJaDyLiSirOvxq7GTAKuLSQUg1yMRBIeQEo2X1XU0JZE3dLodRVhuO4EXP 192g7Dn4X7Th9HSvgvNuIacowWGLWSz4Qp9RjhGhXhezUSx2nseY6le46PmFavJYYSR 1934PBofMyt4PcyA6Cknh+KHmkCgYEAnTriG7ETE0a7v4DXUpB4TpCEiMCy5Xs2o8Z5 194ZNva+W+qLVUWq+MDAIyechqeFSvxK6gRM69LJ96lx+XhU58wJiFJzAhT9rK/g+jS 195bsHH9WOfu0xHkuHA5hgvvV2Le9B2wqgFyva4HJy82qxMxCu/VG/SMqyfBS9OWbb7 196ibQhdq0CgYAl53LUWZsFSZIth1vux2LVOsI8C3X1oiXDGpnrdlQ+K7z57hq5EsRq 197GC+INxwXbvKNqp5h0z2MvmKYPDlGVTgw8f8JjM7TkN17ERLcydhdRrMONUryZpo8 1981xTob+8blyJgfxZUIAKbMbMbIiU0WAF0rfD/eJJwS4htOW/Hfv4TGA== 199-----END RSA PRIVATE KEY-----` 200 // client 2 crt is revoked 201 client2Crt = `-----BEGIN CERTIFICATE----- 202MIIEITCCAgmgAwIBAgIRAL6XTgNsdYzILd8bT2RVd/4wDQYJKoZIhvcNAQELBQAw 203EzERMA8GA1UEAxMIQ2VydEF1dGgwHhcNMjEwMTAyMjEyMzIwWhcNMjIwNzAyMjEz 204MDUxWjASMRAwDgYDVQQDEwdjbGllbnQyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A 205MIIBCgKCAQEA6xjW5KQR3/OFQtV5M75WINqQ4AzXSu6DhSz/yumaaQZP/UxY+6hi 206jcrFzGo9MMie/Sza8DhkXOFAl2BelUubrOeB2cl+/Gr8OCyRi2Gv6j3zCsuN/4jQ 207tNaoez/IbkDvI3l/ZpzBtnuNY2RiemGgHuORXHRVf3qVlsw+npBIRW5rM2HkO/xG 208oZjeBErWVu390Lyn+Gvk2TqQDnkutWnxUC60/zPlHhXZ4BwaFAekbSnjsSDB1YFM 209s8HwW4oBryoxdj3/+/qLrBHt75IdLw3T7/V1UDJQM3EvSQOr12w4egpldhtsC871 210nnBQZeY6qA5feffIwwg/6lJm70o6S6OX6wIDAQABo3EwbzAOBgNVHQ8BAf8EBAMC 211A7gwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBTB84v5 212t9HqhLhMODbn6oYkEQt3KzAfBgNVHSMEGDAWgBTtcgjQBq/80EySIvJcN8OTorgb 213zDANBgkqhkiG9w0BAQsFAAOCAgEALGtBCve5k8tToL3oLuXp/oSik6ovIB/zq4I/ 2144zNMYPU31+ZWz6aahysgx1JL1yqTa3Qm8o2tu52MbnV10dM7CIw7c/cYa+c+OPcG 2155LF97kp13X+r2axy+CmwM86b4ILaDGs2Qyai6VB6k7oFUve+av5o7aUrNFpqGCJz 216HWdtHZSVA3JMATzy0TfWanwkzreqfdw7qH0yZ9bDURlBKAVWrqnCstva9jRuv+AI 217eqxr/4Ro986TFjJdoAP3Vr16CPg7/B6GA/KmsBWJrpeJdPWq4i2gpLKvYZoy89qD 218mUZf34RbzcCtV4NvV1DadGnt4us0nvLrvS5rL2+2uWD09kZYq9RbLkvgzF/cY0fz 219i7I1bi5XQ+alWe0uAk5ZZL/D+GTRYUX1AWwCqwJxmHrMxcskMyO9pXvLyuSWRDLo 220YNBrbX9nLcfJzVCp+X+9sntTHjs4l6Cw+fLepJIgtgqdCHtbhTiv68vSM6cgb4br 2216n2xrXRKuioiWFOrTSRr+oalZh8dGJ/xvwY8IbWknZAvml9mf1VvfE7Ma5P777QM 222fsbYVTq0Y3R/5hIWsC3HA5z6MIM8L1oRe/YyhP3CTmrCHkVKyDOosGXpGz+JVcyo 223cfYkY5A3yFKB2HaCwZSfwFmRhxkrYWGEbHv3Cd9YkZs1J3hNhGFZyVMC9Uh0S85a 2246zdDidU= 225-----END CERTIFICATE-----` 226 client2Key = `-----BEGIN RSA PRIVATE KEY----- 227MIIEpAIBAAKCAQEA6xjW5KQR3/OFQtV5M75WINqQ4AzXSu6DhSz/yumaaQZP/UxY 228+6hijcrFzGo9MMie/Sza8DhkXOFAl2BelUubrOeB2cl+/Gr8OCyRi2Gv6j3zCsuN 229/4jQtNaoez/IbkDvI3l/ZpzBtnuNY2RiemGgHuORXHRVf3qVlsw+npBIRW5rM2Hk 230O/xGoZjeBErWVu390Lyn+Gvk2TqQDnkutWnxUC60/zPlHhXZ4BwaFAekbSnjsSDB 2311YFMs8HwW4oBryoxdj3/+/qLrBHt75IdLw3T7/V1UDJQM3EvSQOr12w4egpldhts 232C871nnBQZeY6qA5feffIwwg/6lJm70o6S6OX6wIDAQABAoIBAFatstVb1KdQXsq0 233cFpui8zTKOUiduJOrDkWzTygAmlEhYtrccdfXu7OWz0x0lvBLDVGK3a0I/TGrAzj 2344BuFY+FM/egxTVt9in6fmA3et4BS1OAfCryzUdfK6RV//8L+t+zJZ/qKQzWnugpy 235QYjDo8ifuMFwtvEoXizaIyBNLAhEp9hnrv+Tyi2O2gahPvCHsD48zkyZRCHYRstD 236NH5cIrwz9/RJgPO1KI+QsJE7Nh7stR0sbr+5TPU4fnsL2mNhMUF2TJrwIPrc1yp+ 237YIUjdnh3SO88j4TQT3CIrWi8i4pOy6N0dcVn3gpCRGaqAKyS2ZYUj+yVtLO4KwxZ 238SZ1lNvECgYEA78BrF7f4ETfWSLcBQ3qxfLs7ibB6IYo2x25685FhZjD+zLXM1AKb 239FJHEXUm3mUYrFJK6AFEyOQnyGKBOLs3S6oTAswMPbTkkZeD1Y9O6uv0AHASLZnK6 240pC6ub0eSRF5LUyTQ55Jj8D7QsjXJueO8v+G5ihWhNSN9tB2UA+8NBmkCgYEA+weq 241cvoeMIEMBQHnNNLy35bwfqrceGyPIRBcUIvzQfY1vk7KW6DYOUzC7u+WUzy/hA52 242DjXVVhua2eMQ9qqtOav7djcMc2W9RbLowxvno7K5qiCss013MeWk64TCWy+WMp5A 243AVAtOliC3hMkIKqvR2poqn+IBTh1449agUJQqTMCgYEAu06IHGq1GraV6g9XpGF5 244wqoAlMzUTdnOfDabRilBf/YtSr+J++ThRcuwLvXFw7CnPZZ4TIEjDJ7xjj3HdxeE 245fYYjineMmNd40UNUU556F1ZLvJfsVKizmkuCKhwvcMx+asGrmA+tlmds4p3VMS50 246KzDtpKzLWlmU/p/RINWlRmkCgYBy0pHTn7aZZx2xWKqCDg+L2EXPGqZX6wgZDpu7 247OBifzlfM4ctL2CmvI/5yPmLbVgkgBWFYpKUdiujsyyEiQvWTUKhn7UwjqKDHtcsk 248G6p7xS+JswJrzX4885bZJ9Oi1AR2yM3sC9l0O7I4lDbNPmWIXBLeEhGMmcPKv/Kc 24991Ff4wKBgQCF3ur+Vt0PSU0ucrPVHjCe7tqazm0LJaWbPXL1Aw0pzdM2EcNcW/MA 250w0kqpr7MgJ94qhXCBcVcfPuFN9fBOadM3UBj1B45Cz3pptoK+ScI8XKno6jvVK/p 251xr5cb9VBRBtB9aOKVfuRhpatAfS2Pzm2Htae9lFn7slGPUmu2hkjDw== 252-----END RSA PRIVATE KEY-----` 253) 254 255type mockFTPClientContext struct { 256 lastDataChannel ftpserver.DataChannel 257 remoteIP string 258 localIP string 259} 260 261func (cc mockFTPClientContext) Path() string { 262 return "" 263} 264 265func (cc mockFTPClientContext) SetDebug(debug bool) {} 266 267func (cc mockFTPClientContext) Debug() bool { 268 return false 269} 270 271func (cc mockFTPClientContext) ID() uint32 { 272 return 1 273} 274 275func (cc mockFTPClientContext) RemoteAddr() net.Addr { 276 ip := "127.0.0.1" 277 if cc.remoteIP != "" { 278 ip = cc.remoteIP 279 } 280 return &net.IPAddr{IP: net.ParseIP(ip)} 281} 282 283func (cc mockFTPClientContext) LocalAddr() net.Addr { 284 ip := "127.0.0.1" 285 if cc.localIP != "" { 286 ip = cc.localIP 287 } 288 return &net.IPAddr{IP: net.ParseIP(ip)} 289} 290 291func (cc mockFTPClientContext) GetClientVersion() string { 292 return "mock version" 293} 294 295func (cc mockFTPClientContext) Close() error { 296 return nil 297} 298 299func (cc mockFTPClientContext) HasTLSForControl() bool { 300 return false 301} 302 303func (cc mockFTPClientContext) HasTLSForTransfers() bool { 304 return false 305} 306 307func (cc mockFTPClientContext) GetLastCommand() string { 308 return "" 309} 310 311func (cc mockFTPClientContext) GetLastDataChannel() ftpserver.DataChannel { 312 return cc.lastDataChannel 313} 314 315// MockOsFs mockable OsFs 316type MockOsFs struct { 317 vfs.Fs 318 err error 319 statErr error 320 isAtomicUploadSupported bool 321} 322 323// Name returns the name for the Fs implementation 324func (fs MockOsFs) Name() string { 325 return "mockOsFs" 326} 327 328// IsUploadResumeSupported returns true if resuming uploads is supported 329func (MockOsFs) IsUploadResumeSupported() bool { 330 return false 331} 332 333// IsAtomicUploadSupported returns true if atomic upload is supported 334func (fs MockOsFs) IsAtomicUploadSupported() bool { 335 return fs.isAtomicUploadSupported 336} 337 338// Stat returns a FileInfo describing the named file 339func (fs MockOsFs) Stat(name string) (os.FileInfo, error) { 340 if fs.statErr != nil { 341 return nil, fs.statErr 342 } 343 return os.Stat(name) 344} 345 346// Lstat returns a FileInfo describing the named file 347func (fs MockOsFs) Lstat(name string) (os.FileInfo, error) { 348 if fs.statErr != nil { 349 return nil, fs.statErr 350 } 351 return os.Lstat(name) 352} 353 354// Remove removes the named file or (empty) directory. 355func (fs MockOsFs) Remove(name string, isDir bool) error { 356 if fs.err != nil { 357 return fs.err 358 } 359 return os.Remove(name) 360} 361 362// Rename renames (moves) source to target 363func (fs MockOsFs) Rename(source, target string) error { 364 if fs.err != nil { 365 return fs.err 366 } 367 return os.Rename(source, target) 368} 369 370func newMockOsFs(err, statErr error, atomicUpload bool, connectionID, rootDir string) vfs.Fs { 371 return &MockOsFs{ 372 Fs: vfs.NewOsFs(connectionID, rootDir, ""), 373 err: err, 374 statErr: statErr, 375 isAtomicUploadSupported: atomicUpload, 376 } 377} 378 379func TestInitialization(t *testing.T) { 380 oldMgr := certMgr 381 certMgr = nil 382 383 binding := Binding{ 384 Port: 2121, 385 } 386 c := &Configuration{ 387 Bindings: []Binding{binding}, 388 CertificateFile: "acert", 389 CertificateKeyFile: "akey", 390 } 391 assert.False(t, binding.HasProxy()) 392 assert.Equal(t, "Disabled", binding.GetTLSDescription()) 393 err := c.Initialize(configDir) 394 assert.Error(t, err) 395 c.CertificateFile = "" 396 c.CertificateKeyFile = "" 397 c.BannerFile = "afile" 398 server := NewServer(c, configDir, binding, 0) 399 assert.Equal(t, "", server.initialMsg) 400 _, err = server.GetTLSConfig() 401 assert.Error(t, err) 402 403 binding.TLSMode = 1 404 server = NewServer(c, configDir, binding, 0) 405 _, err = server.GetSettings() 406 assert.Error(t, err) 407 408 binding.PassiveConnectionsSecurity = 100 409 binding.ActiveConnectionsSecurity = 100 410 server = NewServer(c, configDir, binding, 0) 411 _, err = server.GetSettings() 412 if assert.Error(t, err) { 413 assert.Contains(t, err.Error(), "invalid passive_connections_security") 414 } 415 binding.PassiveConnectionsSecurity = 1 416 server = NewServer(c, configDir, binding, 0) 417 _, err = server.GetSettings() 418 if assert.Error(t, err) { 419 assert.Contains(t, err.Error(), "invalid active_connections_security") 420 } 421 binding = Binding{ 422 Port: 2121, 423 ForcePassiveIP: "192.168.1", 424 } 425 server = NewServer(c, configDir, binding, 0) 426 _, err = server.GetSettings() 427 if assert.Error(t, err) { 428 assert.Contains(t, err.Error(), "is not valid") 429 } 430 431 binding.ForcePassiveIP = "::ffff:192.168.89.9" 432 err = binding.checkPassiveIP() 433 assert.NoError(t, err) 434 assert.Equal(t, "192.168.89.9", binding.ForcePassiveIP) 435 436 binding.ForcePassiveIP = "::1" 437 err = binding.checkPassiveIP() 438 if assert.Error(t, err) { 439 assert.Contains(t, err.Error(), "is not a valid IPv4 address") 440 } 441 442 err = ReloadCertificateMgr() 443 assert.NoError(t, err) 444 445 certMgr = oldMgr 446 447 binding = Binding{ 448 Port: 2121, 449 ClientAuthType: 1, 450 } 451 server = NewServer(c, configDir, binding, 0) 452 cfg, err := server.GetTLSConfig() 453 assert.NoError(t, err) 454 assert.Equal(t, tls.RequireAndVerifyClientCert, cfg.ClientAuth) 455} 456 457func TestServerGetSettings(t *testing.T) { 458 oldConfig := common.Config 459 460 binding := Binding{ 461 Port: 2121, 462 ApplyProxyConfig: true, 463 } 464 c := &Configuration{ 465 Bindings: []Binding{binding}, 466 PassivePortRange: PortRange{ 467 Start: 10000, 468 End: 11000, 469 }, 470 } 471 assert.False(t, binding.HasProxy()) 472 server := NewServer(c, configDir, binding, 0) 473 settings, err := server.GetSettings() 474 assert.NoError(t, err) 475 assert.Equal(t, 10000, settings.PassiveTransferPortRange.Start) 476 assert.Equal(t, 11000, settings.PassiveTransferPortRange.End) 477 478 common.Config.ProxyProtocol = 1 479 common.Config.ProxyAllowed = []string{"invalid"} 480 assert.True(t, binding.HasProxy()) 481 _, err = server.GetSettings() 482 assert.Error(t, err) 483 server.binding.Port = 8021 484 _, err = server.GetSettings() 485 assert.Error(t, err) 486 487 assert.Equal(t, "Plain and explicit", binding.GetTLSDescription()) 488 489 binding.TLSMode = 1 490 assert.Equal(t, "Explicit required", binding.GetTLSDescription()) 491 492 binding.TLSMode = 2 493 assert.Equal(t, "Implicit", binding.GetTLSDescription()) 494 495 certPath := filepath.Join(os.TempDir(), "test.crt") 496 keyPath := filepath.Join(os.TempDir(), "test.key") 497 err = os.WriteFile(certPath, []byte(ftpsCert), os.ModePerm) 498 assert.NoError(t, err) 499 err = os.WriteFile(keyPath, []byte(ftpsKey), os.ModePerm) 500 assert.NoError(t, err) 501 502 common.Config.ProxyAllowed = nil 503 c.CertificateFile = certPath 504 c.CertificateKeyFile = keyPath 505 server = NewServer(c, configDir, binding, 0) 506 server.binding.Port = 9021 507 settings, err = server.GetSettings() 508 assert.NoError(t, err) 509 assert.NotNil(t, settings.Listener) 510 511 listener, err := net.Listen("tcp", ":0") 512 assert.NoError(t, err) 513 listener, err = server.WrapPassiveListener(listener) 514 assert.NoError(t, err) 515 516 _, ok := listener.(*proxyproto.Listener) 517 assert.True(t, ok) 518 519 err = os.Remove(certPath) 520 assert.NoError(t, err) 521 err = os.Remove(keyPath) 522 assert.NoError(t, err) 523 524 common.Config = oldConfig 525} 526 527func TestUserInvalidParams(t *testing.T) { 528 u := dataprovider.User{ 529 BaseUser: sdk.BaseUser{ 530 HomeDir: "invalid", 531 }, 532 } 533 binding := Binding{ 534 Port: 2121, 535 } 536 c := &Configuration{ 537 Bindings: []Binding{binding}, 538 PassivePortRange: PortRange{ 539 Start: 10000, 540 End: 11000, 541 }, 542 } 543 server := NewServer(c, configDir, binding, 3) 544 _, err := server.validateUser(u, mockFTPClientContext{}, dataprovider.LoginMethodPassword) 545 assert.Error(t, err) 546 547 u.Username = "a" 548 u.HomeDir = filepath.Clean(os.TempDir()) 549 subDir := "subdir" 550 mappedPath1 := filepath.Join(os.TempDir(), "vdir1") 551 vdirPath1 := "/vdir1" 552 mappedPath2 := filepath.Join(os.TempDir(), "vdir1", subDir) 553 vdirPath2 := "/vdir2" 554 u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ 555 BaseVirtualFolder: vfs.BaseVirtualFolder{ 556 MappedPath: mappedPath1, 557 }, 558 VirtualPath: vdirPath1, 559 }) 560 u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ 561 BaseVirtualFolder: vfs.BaseVirtualFolder{ 562 MappedPath: mappedPath2, 563 }, 564 VirtualPath: vdirPath2, 565 }) 566 _, err = server.validateUser(u, mockFTPClientContext{}, dataprovider.LoginMethodPassword) 567 assert.Error(t, err) 568 u.VirtualFolders = nil 569 _, err = server.validateUser(u, mockFTPClientContext{}, dataprovider.LoginMethodPassword) 570 assert.Error(t, err) 571} 572 573func TestFTPMode(t *testing.T) { 574 connection := &Connection{ 575 BaseConnection: common.NewBaseConnection("", common.ProtocolFTP, "", "", dataprovider.User{}), 576 } 577 assert.Empty(t, connection.getFTPMode()) 578 connection.clientContext = mockFTPClientContext{lastDataChannel: ftpserver.DataChannelActive} 579 assert.Equal(t, "active", connection.getFTPMode()) 580 connection.clientContext = mockFTPClientContext{lastDataChannel: ftpserver.DataChannelPassive} 581 assert.Equal(t, "passive", connection.getFTPMode()) 582 connection.clientContext = mockFTPClientContext{lastDataChannel: 0} 583 assert.Empty(t, connection.getFTPMode()) 584} 585 586func TestClientVersion(t *testing.T) { 587 mockCC := mockFTPClientContext{} 588 connID := fmt.Sprintf("2_%v", mockCC.ID()) 589 user := dataprovider.User{} 590 connection := &Connection{ 591 BaseConnection: common.NewBaseConnection(connID, common.ProtocolFTP, "", "", user), 592 clientContext: mockCC, 593 } 594 common.Connections.Add(connection) 595 stats := common.Connections.GetStats() 596 if assert.Len(t, stats, 1) { 597 assert.Equal(t, "mock version", stats[0].ClientVersion) 598 common.Connections.Remove(connection.GetID()) 599 } 600 assert.Len(t, common.Connections.GetStats(), 0) 601} 602 603func TestDriverMethodsNotImplemented(t *testing.T) { 604 mockCC := mockFTPClientContext{} 605 connID := fmt.Sprintf("2_%v", mockCC.ID()) 606 user := dataprovider.User{} 607 connection := &Connection{ 608 BaseConnection: common.NewBaseConnection(connID, common.ProtocolFTP, "", "", user), 609 clientContext: mockCC, 610 } 611 _, err := connection.Create("") 612 assert.EqualError(t, err, errNotImplemented.Error()) 613 err = connection.MkdirAll("", os.ModePerm) 614 assert.EqualError(t, err, errNotImplemented.Error()) 615 _, err = connection.Open("") 616 assert.EqualError(t, err, errNotImplemented.Error()) 617 _, err = connection.OpenFile("", 0, os.ModePerm) 618 assert.EqualError(t, err, errNotImplemented.Error()) 619 err = connection.RemoveAll("") 620 assert.EqualError(t, err, errNotImplemented.Error()) 621 assert.Equal(t, connection.GetID(), connection.Name()) 622} 623 624func TestResolvePathErrors(t *testing.T) { 625 user := dataprovider.User{ 626 BaseUser: sdk.BaseUser{ 627 HomeDir: "invalid", 628 }, 629 } 630 user.Permissions = make(map[string][]string) 631 user.Permissions["/"] = []string{dataprovider.PermAny} 632 mockCC := mockFTPClientContext{} 633 connID := fmt.Sprintf("%v", mockCC.ID()) 634 connection := &Connection{ 635 BaseConnection: common.NewBaseConnection(connID, common.ProtocolFTP, "", "", user), 636 clientContext: mockCC, 637 } 638 err := connection.Mkdir("", os.ModePerm) 639 if assert.Error(t, err) { 640 assert.EqualError(t, err, common.ErrGenericFailure.Error()) 641 } 642 err = connection.Remove("") 643 if assert.Error(t, err) { 644 assert.EqualError(t, err, common.ErrGenericFailure.Error()) 645 } 646 err = connection.RemoveDir("") 647 if assert.Error(t, err) { 648 assert.EqualError(t, err, common.ErrGenericFailure.Error()) 649 } 650 err = connection.Rename("", "") 651 if assert.Error(t, err) { 652 assert.EqualError(t, err, common.ErrGenericFailure.Error()) 653 } 654 err = connection.Symlink("", "") 655 if assert.Error(t, err) { 656 assert.EqualError(t, err, common.ErrGenericFailure.Error()) 657 } 658 _, err = connection.Stat("") 659 if assert.Error(t, err) { 660 assert.EqualError(t, err, common.ErrGenericFailure.Error()) 661 } 662 err = connection.Chmod("", os.ModePerm) 663 if assert.Error(t, err) { 664 assert.EqualError(t, err, common.ErrGenericFailure.Error()) 665 } 666 err = connection.Chtimes("", time.Now(), time.Now()) 667 if assert.Error(t, err) { 668 assert.EqualError(t, err, common.ErrGenericFailure.Error()) 669 } 670 _, err = connection.ReadDir("") 671 if assert.Error(t, err) { 672 assert.EqualError(t, err, common.ErrGenericFailure.Error()) 673 } 674 _, err = connection.GetHandle("", 0, 0) 675 if assert.Error(t, err) { 676 assert.EqualError(t, err, common.ErrGenericFailure.Error()) 677 } 678 _, err = connection.GetAvailableSpace("") 679 if assert.Error(t, err) { 680 assert.EqualError(t, err, common.ErrGenericFailure.Error()) 681 } 682} 683 684func TestUploadFileStatError(t *testing.T) { 685 if runtime.GOOS == "windows" { 686 t.Skip("this test is not available on Windows") 687 } 688 user := dataprovider.User{ 689 BaseUser: sdk.BaseUser{ 690 Username: "user", 691 HomeDir: filepath.Clean(os.TempDir()), 692 }, 693 } 694 user.Permissions = make(map[string][]string) 695 user.Permissions["/"] = []string{dataprovider.PermAny} 696 mockCC := mockFTPClientContext{} 697 connID := fmt.Sprintf("%v", mockCC.ID()) 698 fs := vfs.NewOsFs(connID, user.HomeDir, "") 699 connection := &Connection{ 700 BaseConnection: common.NewBaseConnection(connID, common.ProtocolFTP, "", "", user), 701 clientContext: mockCC, 702 } 703 testFile := filepath.Join(user.HomeDir, "test", "testfile") 704 err := os.MkdirAll(filepath.Dir(testFile), os.ModePerm) 705 assert.NoError(t, err) 706 err = os.WriteFile(testFile, []byte("data"), os.ModePerm) 707 assert.NoError(t, err) 708 err = os.Chmod(filepath.Dir(testFile), 0001) 709 assert.NoError(t, err) 710 _, err = connection.uploadFile(fs, testFile, "test", 0) 711 assert.Error(t, err) 712 err = os.Chmod(filepath.Dir(testFile), os.ModePerm) 713 assert.NoError(t, err) 714 err = os.RemoveAll(filepath.Dir(testFile)) 715 assert.NoError(t, err) 716} 717 718func TestAVBLErrors(t *testing.T) { 719 user := dataprovider.User{ 720 BaseUser: sdk.BaseUser{ 721 Username: "user", 722 HomeDir: filepath.Clean(os.TempDir()), 723 }, 724 } 725 user.Permissions = make(map[string][]string) 726 user.Permissions["/"] = []string{dataprovider.PermAny} 727 mockCC := mockFTPClientContext{} 728 connID := fmt.Sprintf("%v", mockCC.ID()) 729 connection := &Connection{ 730 BaseConnection: common.NewBaseConnection(connID, common.ProtocolFTP, "", "", user), 731 clientContext: mockCC, 732 } 733 _, err := connection.GetAvailableSpace("/") 734 assert.NoError(t, err) 735 _, err = connection.GetAvailableSpace("/missing-path") 736 assert.Error(t, err) 737 assert.True(t, os.IsNotExist(err)) 738} 739 740func TestUploadOverwriteErrors(t *testing.T) { 741 user := dataprovider.User{ 742 BaseUser: sdk.BaseUser{ 743 Username: "user", 744 HomeDir: filepath.Clean(os.TempDir()), 745 }, 746 } 747 user.Permissions = make(map[string][]string) 748 user.Permissions["/"] = []string{dataprovider.PermAny} 749 mockCC := mockFTPClientContext{} 750 connID := fmt.Sprintf("%v", mockCC.ID()) 751 fs := newMockOsFs(nil, nil, false, connID, user.GetHomeDir()) 752 connection := &Connection{ 753 BaseConnection: common.NewBaseConnection(connID, common.ProtocolFTP, "", "", user), 754 clientContext: mockCC, 755 } 756 flags := 0 757 flags |= os.O_APPEND 758 _, err := connection.handleFTPUploadToExistingFile(fs, flags, "", "", 0, "") 759 if assert.Error(t, err) { 760 assert.EqualError(t, err, common.ErrOpUnsupported.Error()) 761 } 762 763 f, err := os.CreateTemp("", "temp") 764 assert.NoError(t, err) 765 err = f.Close() 766 assert.NoError(t, err) 767 flags = 0 768 flags |= os.O_CREATE 769 flags |= os.O_TRUNC 770 tr, err := connection.handleFTPUploadToExistingFile(fs, flags, f.Name(), f.Name(), 123, f.Name()) 771 if assert.NoError(t, err) { 772 transfer := tr.(*transfer) 773 transfers := connection.GetTransfers() 774 if assert.Equal(t, 1, len(transfers)) { 775 assert.Equal(t, transfers[0].ID, transfer.GetID()) 776 assert.Equal(t, int64(123), transfer.InitialSize) 777 err = transfer.Close() 778 assert.NoError(t, err) 779 assert.Equal(t, 0, len(connection.GetTransfers())) 780 } 781 } 782 err = os.Remove(f.Name()) 783 assert.NoError(t, err) 784 785 _, err = connection.handleFTPUploadToExistingFile(fs, os.O_TRUNC, filepath.Join(os.TempDir(), "sub", "file"), 786 filepath.Join(os.TempDir(), "sub", "file1"), 0, "/sub/file1") 787 assert.Error(t, err) 788 fs = vfs.NewOsFs(connID, user.GetHomeDir(), "") 789 _, err = connection.handleFTPUploadToExistingFile(fs, 0, "missing1", "missing2", 0, "missing") 790 assert.Error(t, err) 791} 792 793func TestTransferErrors(t *testing.T) { 794 testfile := "testfile" 795 file, err := os.Create(testfile) 796 assert.NoError(t, err) 797 user := dataprovider.User{ 798 BaseUser: sdk.BaseUser{ 799 Username: "user", 800 HomeDir: filepath.Clean(os.TempDir()), 801 }, 802 } 803 user.Permissions = make(map[string][]string) 804 user.Permissions["/"] = []string{dataprovider.PermAny} 805 mockCC := mockFTPClientContext{} 806 connID := fmt.Sprintf("%v", mockCC.ID()) 807 fs := newMockOsFs(nil, nil, false, connID, user.GetHomeDir()) 808 connection := &Connection{ 809 BaseConnection: common.NewBaseConnection(connID, common.ProtocolFTP, "", "", user), 810 clientContext: mockCC, 811 } 812 baseTransfer := common.NewBaseTransfer(file, connection.BaseConnection, nil, file.Name(), file.Name(), testfile, 813 common.TransferDownload, 0, 0, 0, false, fs) 814 tr := newTransfer(baseTransfer, nil, nil, 0) 815 err = tr.Close() 816 assert.NoError(t, err) 817 _, err = tr.Seek(10, 0) 818 assert.Error(t, err) 819 buf := make([]byte, 64) 820 _, err = tr.Read(buf) 821 assert.Error(t, err) 822 err = tr.Close() 823 if assert.Error(t, err) { 824 assert.EqualError(t, err, common.ErrTransferClosed.Error()) 825 } 826 assert.Len(t, connection.GetTransfers(), 0) 827 828 r, _, err := pipeat.Pipe() 829 assert.NoError(t, err) 830 baseTransfer = common.NewBaseTransfer(nil, connection.BaseConnection, nil, testfile, testfile, testfile, 831 common.TransferUpload, 0, 0, 0, false, fs) 832 tr = newTransfer(baseTransfer, nil, r, 10) 833 pos, err := tr.Seek(10, 0) 834 assert.NoError(t, err) 835 assert.Equal(t, pos, tr.expectedOffset) 836 err = tr.closeIO() 837 assert.NoError(t, err) 838 839 r, w, err := pipeat.Pipe() 840 assert.NoError(t, err) 841 pipeWriter := vfs.NewPipeWriter(w) 842 baseTransfer = common.NewBaseTransfer(nil, connection.BaseConnection, nil, testfile, testfile, testfile, 843 common.TransferUpload, 0, 0, 0, false, fs) 844 tr = newTransfer(baseTransfer, pipeWriter, nil, 0) 845 846 err = r.Close() 847 assert.NoError(t, err) 848 errFake := fmt.Errorf("fake upload error") 849 go func() { 850 time.Sleep(100 * time.Millisecond) 851 pipeWriter.Done(errFake) 852 }() 853 err = tr.closeIO() 854 assert.EqualError(t, err, errFake.Error()) 855 _, err = tr.Seek(1, 0) 856 if assert.Error(t, err) { 857 assert.EqualError(t, err, common.ErrOpUnsupported.Error()) 858 } 859 err = os.Remove(testfile) 860 assert.NoError(t, err) 861} 862 863func TestVerifyTLSConnection(t *testing.T) { 864 oldCertMgr := certMgr 865 866 caCrlPath := filepath.Join(os.TempDir(), "testcrl.crt") 867 certPath := filepath.Join(os.TempDir(), "test.crt") 868 keyPath := filepath.Join(os.TempDir(), "test.key") 869 err := os.WriteFile(caCrlPath, []byte(caCRL), os.ModePerm) 870 assert.NoError(t, err) 871 err = os.WriteFile(certPath, []byte(ftpsCert), os.ModePerm) 872 assert.NoError(t, err) 873 err = os.WriteFile(keyPath, []byte(ftpsKey), os.ModePerm) 874 assert.NoError(t, err) 875 876 certMgr, err = common.NewCertManager(certPath, keyPath, "", "ftp_test") 877 assert.NoError(t, err) 878 879 certMgr.SetCARevocationLists([]string{caCrlPath}) 880 err = certMgr.LoadCRLs() 881 assert.NoError(t, err) 882 883 crt, err := tls.X509KeyPair([]byte(client1Crt), []byte(client1Key)) 884 assert.NoError(t, err) 885 x509crt, err := x509.ParseCertificate(crt.Certificate[0]) 886 assert.NoError(t, err) 887 888 server := Server{} 889 state := tls.ConnectionState{ 890 PeerCertificates: []*x509.Certificate{x509crt}, 891 } 892 893 err = server.verifyTLSConnection(state) 894 assert.Error(t, err) // no verified certification chain 895 896 crt, err = tls.X509KeyPair([]byte(caCRT), []byte(caKey)) 897 assert.NoError(t, err) 898 899 x509CAcrt, err := x509.ParseCertificate(crt.Certificate[0]) 900 assert.NoError(t, err) 901 902 state.VerifiedChains = append(state.VerifiedChains, []*x509.Certificate{x509crt, x509CAcrt}) 903 err = server.verifyTLSConnection(state) 904 assert.NoError(t, err) 905 906 crt, err = tls.X509KeyPair([]byte(client2Crt), []byte(client2Key)) 907 assert.NoError(t, err) 908 x509crtRevoked, err := x509.ParseCertificate(crt.Certificate[0]) 909 assert.NoError(t, err) 910 911 state.VerifiedChains = append(state.VerifiedChains, []*x509.Certificate{x509crtRevoked, x509CAcrt}) 912 state.PeerCertificates = []*x509.Certificate{x509crtRevoked} 913 err = server.verifyTLSConnection(state) 914 assert.EqualError(t, err, common.ErrCrtRevoked.Error()) 915 916 err = os.Remove(caCrlPath) 917 assert.NoError(t, err) 918 err = os.Remove(certPath) 919 assert.NoError(t, err) 920 err = os.Remove(keyPath) 921 assert.NoError(t, err) 922 923 certMgr = oldCertMgr 924} 925 926func TestCiphers(t *testing.T) { 927 b := Binding{ 928 TLSCipherSuites: []string{}, 929 } 930 b.setCiphers() 931 require.Nil(t, b.ciphers) 932 b.TLSCipherSuites = []string{"TLS_AES_128_GCM_SHA256", "TLS_AES_256_GCM_SHA384"} 933 b.setCiphers() 934 require.Len(t, b.ciphers, 2) 935 require.Equal(t, []uint16{tls.TLS_AES_128_GCM_SHA256, tls.TLS_AES_256_GCM_SHA384}, b.ciphers) 936} 937 938func TestPassiveIPResolver(t *testing.T) { 939 b := Binding{ 940 PassiveIPOverrides: []PassiveIPOverride{ 941 {}, 942 }, 943 } 944 err := b.checkPassiveIP() 945 assert.Error(t, err) 946 assert.Contains(t, err.Error(), "passive IP networks override cannot be empty") 947 b = Binding{ 948 PassiveIPOverrides: []PassiveIPOverride{ 949 { 950 IP: "invalid ip", 951 }, 952 }, 953 } 954 err = b.checkPassiveIP() 955 assert.Error(t, err) 956 assert.Contains(t, err.Error(), "is not valid") 957 958 b = Binding{ 959 PassiveIPOverrides: []PassiveIPOverride{ 960 { 961 IP: "192.168.1.1", 962 Networks: []string{"192.168.1.0/24", "invalid cidr"}, 963 }, 964 }, 965 } 966 err = b.checkPassiveIP() 967 assert.Error(t, err) 968 assert.Contains(t, err.Error(), "invalid passive IP networks override") 969 b = Binding{ 970 ForcePassiveIP: "192.168.2.1", 971 PassiveIPOverrides: []PassiveIPOverride{ 972 { 973 IP: "::ffff:192.168.1.1", 974 Networks: []string{"192.168.1.0/24"}, 975 }, 976 }, 977 } 978 err = b.checkPassiveIP() 979 assert.NoError(t, err) 980 assert.Equal(t, "192.168.1.1", b.PassiveIPOverrides[0].IP) 981 require.Len(t, b.PassiveIPOverrides[0].parsedNetworks, 1) 982 ip := net.ParseIP("192.168.1.2") 983 assert.True(t, b.PassiveIPOverrides[0].parsedNetworks[0](ip)) 984 ip = net.ParseIP("192.168.0.2") 985 assert.False(t, b.PassiveIPOverrides[0].parsedNetworks[0](ip)) 986 987 mockCC := mockFTPClientContext{ 988 remoteIP: "192.168.1.10", 989 localIP: "192.168.1.3", 990 } 991 passiveIP, err := b.passiveIPResolver(mockCC) 992 assert.NoError(t, err) 993 assert.Equal(t, "192.168.1.1", passiveIP) 994 b.PassiveIPOverrides[0].IP = "" 995 passiveIP, err = b.passiveIPResolver(mockCC) 996 assert.NoError(t, err) 997 assert.Equal(t, "192.168.1.3", passiveIP) 998 mockCC.remoteIP = "172.16.2.3" 999 passiveIP, err = b.passiveIPResolver(mockCC) 1000 assert.NoError(t, err) 1001 assert.Equal(t, b.ForcePassiveIP, passiveIP) 1002} 1003