FTPD: allow to set different passive IPs based on the client's IP address
This commit is contained in:
parent
531cb5b5a1
commit
4652f9ede8
9 changed files with 214 additions and 23 deletions
|
@ -53,6 +53,7 @@ var (
|
||||||
ApplyProxyConfig: true,
|
ApplyProxyConfig: true,
|
||||||
TLSMode: 0,
|
TLSMode: 0,
|
||||||
ForcePassiveIP: "",
|
ForcePassiveIP: "",
|
||||||
|
PassiveIPOverrides: nil,
|
||||||
ClientAuthType: 0,
|
ClientAuthType: 0,
|
||||||
TLSCipherSuites: nil,
|
TLSCipherSuites: nil,
|
||||||
PassiveConnectionsSecurity: 0,
|
PassiveConnectionsSecurity: 0,
|
||||||
|
@ -852,6 +853,31 @@ func getSFTPDBindindFromEnv(idx int) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getFTPDPassiveIPOverridesFromEnv(idx int) []ftpd.PassiveIPOverride {
|
||||||
|
var overrides []ftpd.PassiveIPOverride
|
||||||
|
|
||||||
|
for subIdx := 0; subIdx < 10; subIdx++ {
|
||||||
|
var override ftpd.PassiveIPOverride
|
||||||
|
|
||||||
|
ip, ok := os.LookupEnv(fmt.Sprintf("SFTPGO_FTPD__BINDINGS__%v__PASSIVE_IP_OVERRIDES__%v__IP", idx, subIdx))
|
||||||
|
if ok {
|
||||||
|
override.IP = ip
|
||||||
|
}
|
||||||
|
|
||||||
|
networks, ok := lookupStringListFromEnv(fmt.Sprintf("SFTPGO_FTPD__BINDINGS__%v__PASSIVE_IP_OVERRIDES__%v__NETWORKS",
|
||||||
|
idx, subIdx))
|
||||||
|
if ok {
|
||||||
|
override.Networks = networks
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(override.Networks) > 0 {
|
||||||
|
overrides = append(overrides, override)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return overrides
|
||||||
|
}
|
||||||
|
|
||||||
func getFTPDBindingFromEnv(idx int) {
|
func getFTPDBindingFromEnv(idx int) {
|
||||||
binding := ftpd.Binding{
|
binding := ftpd.Binding{
|
||||||
ApplyProxyConfig: true,
|
ApplyProxyConfig: true,
|
||||||
|
@ -892,6 +918,12 @@ func getFTPDBindingFromEnv(idx int) {
|
||||||
isSet = true
|
isSet = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
passiveIPOverrides := getFTPDPassiveIPOverridesFromEnv(idx)
|
||||||
|
if len(passiveIPOverrides) > 0 {
|
||||||
|
binding.PassiveIPOverrides = passiveIPOverrides
|
||||||
|
isSet = true
|
||||||
|
}
|
||||||
|
|
||||||
clientAuthType, ok := lookupIntFromEnv(fmt.Sprintf("SFTPGO_FTPD__BINDINGS__%v__CLIENT_AUTH_TYPE", idx))
|
clientAuthType, ok := lookupIntFromEnv(fmt.Sprintf("SFTPGO_FTPD__BINDINGS__%v__CLIENT_AUTH_TYPE", idx))
|
||||||
if ok {
|
if ok {
|
||||||
binding.ClientAuthType = int(clientAuthType)
|
binding.ClientAuthType = int(clientAuthType)
|
||||||
|
|
|
@ -628,12 +628,15 @@ func TestFTPDBindingsFromEnv(t *testing.T) {
|
||||||
os.Setenv("SFTPGO_FTPD__BINDINGS__0__APPLY_PROXY_CONFIG", "f")
|
os.Setenv("SFTPGO_FTPD__BINDINGS__0__APPLY_PROXY_CONFIG", "f")
|
||||||
os.Setenv("SFTPGO_FTPD__BINDINGS__0__TLS_MODE", "2")
|
os.Setenv("SFTPGO_FTPD__BINDINGS__0__TLS_MODE", "2")
|
||||||
os.Setenv("SFTPGO_FTPD__BINDINGS__0__FORCE_PASSIVE_IP", "127.0.1.2")
|
os.Setenv("SFTPGO_FTPD__BINDINGS__0__FORCE_PASSIVE_IP", "127.0.1.2")
|
||||||
|
os.Setenv("SFTPGO_FTPD__BINDINGS__0__PASSIVE_IP_OVERRIDES__0__IP", "172.16.1.1")
|
||||||
os.Setenv("SFTPGO_FTPD__BINDINGS__0__TLS_CIPHER_SUITES", "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256")
|
os.Setenv("SFTPGO_FTPD__BINDINGS__0__TLS_CIPHER_SUITES", "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256")
|
||||||
os.Setenv("SFTPGO_FTPD__BINDINGS__0__PASSIVE_CONNECTIONS_SECURITY", "1")
|
os.Setenv("SFTPGO_FTPD__BINDINGS__0__PASSIVE_CONNECTIONS_SECURITY", "1")
|
||||||
os.Setenv("SFTPGO_FTPD__BINDINGS__9__ADDRESS", "127.0.1.1")
|
os.Setenv("SFTPGO_FTPD__BINDINGS__9__ADDRESS", "127.0.1.1")
|
||||||
os.Setenv("SFTPGO_FTPD__BINDINGS__9__PORT", "2203")
|
os.Setenv("SFTPGO_FTPD__BINDINGS__9__PORT", "2203")
|
||||||
os.Setenv("SFTPGO_FTPD__BINDINGS__9__TLS_MODE", "1")
|
os.Setenv("SFTPGO_FTPD__BINDINGS__9__TLS_MODE", "1")
|
||||||
os.Setenv("SFTPGO_FTPD__BINDINGS__9__FORCE_PASSIVE_IP", "127.0.1.1")
|
os.Setenv("SFTPGO_FTPD__BINDINGS__9__FORCE_PASSIVE_IP", "127.0.1.1")
|
||||||
|
os.Setenv("SFTPGO_FTPD__BINDINGS__9__PASSIVE_IP_OVERRIDES__3__IP", "192.168.1.1")
|
||||||
|
os.Setenv("SFTPGO_FTPD__BINDINGS__9__PASSIVE_IP_OVERRIDES__3__NETWORKS", "192.168.1.0/24, 192.168.3.0/25")
|
||||||
os.Setenv("SFTPGO_FTPD__BINDINGS__9__CLIENT_AUTH_TYPE", "2")
|
os.Setenv("SFTPGO_FTPD__BINDINGS__9__CLIENT_AUTH_TYPE", "2")
|
||||||
os.Setenv("SFTPGO_FTPD__BINDINGS__9__DEBUG", "1")
|
os.Setenv("SFTPGO_FTPD__BINDINGS__9__DEBUG", "1")
|
||||||
os.Setenv("SFTPGO_FTPD__BINDINGS__9__ACTIVE_CONNECTIONS_SECURITY", "1")
|
os.Setenv("SFTPGO_FTPD__BINDINGS__9__ACTIVE_CONNECTIONS_SECURITY", "1")
|
||||||
|
@ -644,12 +647,15 @@ func TestFTPDBindingsFromEnv(t *testing.T) {
|
||||||
os.Unsetenv("SFTPGO_FTPD__BINDINGS__0__APPLY_PROXY_CONFIG")
|
os.Unsetenv("SFTPGO_FTPD__BINDINGS__0__APPLY_PROXY_CONFIG")
|
||||||
os.Unsetenv("SFTPGO_FTPD__BINDINGS__0__TLS_MODE")
|
os.Unsetenv("SFTPGO_FTPD__BINDINGS__0__TLS_MODE")
|
||||||
os.Unsetenv("SFTPGO_FTPD__BINDINGS__0__FORCE_PASSIVE_IP")
|
os.Unsetenv("SFTPGO_FTPD__BINDINGS__0__FORCE_PASSIVE_IP")
|
||||||
|
os.Unsetenv("SFTPGO_FTPD__BINDINGS__0__PASSIVE_IP_OVERRIDES__0__IP")
|
||||||
os.Unsetenv("SFTPGO_FTPD__BINDINGS__0__TLS_CIPHER_SUITES")
|
os.Unsetenv("SFTPGO_FTPD__BINDINGS__0__TLS_CIPHER_SUITES")
|
||||||
os.Unsetenv("SFTPGO_FTPD__BINDINGS__0__ACTIVE_CONNECTIONS_SECURITY")
|
os.Unsetenv("SFTPGO_FTPD__BINDINGS__0__ACTIVE_CONNECTIONS_SECURITY")
|
||||||
os.Unsetenv("SFTPGO_FTPD__BINDINGS__9__ADDRESS")
|
os.Unsetenv("SFTPGO_FTPD__BINDINGS__9__ADDRESS")
|
||||||
os.Unsetenv("SFTPGO_FTPD__BINDINGS__9__PORT")
|
os.Unsetenv("SFTPGO_FTPD__BINDINGS__9__PORT")
|
||||||
os.Unsetenv("SFTPGO_FTPD__BINDINGS__9__TLS_MODE")
|
os.Unsetenv("SFTPGO_FTPD__BINDINGS__9__TLS_MODE")
|
||||||
os.Unsetenv("SFTPGO_FTPD__BINDINGS__9__FORCE_PASSIVE_IP")
|
os.Unsetenv("SFTPGO_FTPD__BINDINGS__9__FORCE_PASSIVE_IP")
|
||||||
|
os.Unsetenv("SFTPGO_FTPD__BINDINGS__9__PASSIVE_IP_OVERRIDES__3__IP")
|
||||||
|
os.Unsetenv("SFTPGO_FTPD__BINDINGS__9__PASSIVE_IP_OVERRIDES__3__NETWORKS")
|
||||||
os.Unsetenv("SFTPGO_FTPD__BINDINGS__9__CLIENT_AUTH_TYPE")
|
os.Unsetenv("SFTPGO_FTPD__BINDINGS__9__CLIENT_AUTH_TYPE")
|
||||||
os.Unsetenv("SFTPGO_FTPD__BINDINGS__9__DEBUG")
|
os.Unsetenv("SFTPGO_FTPD__BINDINGS__9__DEBUG")
|
||||||
os.Unsetenv("SFTPGO_FTPD__BINDINGS__9__ACTIVE_CONNECTIONS_SECURITY")
|
os.Unsetenv("SFTPGO_FTPD__BINDINGS__9__ACTIVE_CONNECTIONS_SECURITY")
|
||||||
|
@ -665,6 +671,7 @@ func TestFTPDBindingsFromEnv(t *testing.T) {
|
||||||
require.False(t, bindings[0].ApplyProxyConfig)
|
require.False(t, bindings[0].ApplyProxyConfig)
|
||||||
require.Equal(t, 2, bindings[0].TLSMode)
|
require.Equal(t, 2, bindings[0].TLSMode)
|
||||||
require.Equal(t, "127.0.1.2", bindings[0].ForcePassiveIP)
|
require.Equal(t, "127.0.1.2", bindings[0].ForcePassiveIP)
|
||||||
|
require.Len(t, bindings[0].PassiveIPOverrides, 0)
|
||||||
require.Equal(t, 0, bindings[0].ClientAuthType)
|
require.Equal(t, 0, bindings[0].ClientAuthType)
|
||||||
require.Len(t, bindings[0].TLSCipherSuites, 2)
|
require.Len(t, bindings[0].TLSCipherSuites, 2)
|
||||||
require.Equal(t, "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", bindings[0].TLSCipherSuites[0])
|
require.Equal(t, "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", bindings[0].TLSCipherSuites[0])
|
||||||
|
@ -677,6 +684,11 @@ func TestFTPDBindingsFromEnv(t *testing.T) {
|
||||||
require.True(t, bindings[1].ApplyProxyConfig) // default value
|
require.True(t, bindings[1].ApplyProxyConfig) // default value
|
||||||
require.Equal(t, 1, bindings[1].TLSMode)
|
require.Equal(t, 1, bindings[1].TLSMode)
|
||||||
require.Equal(t, "127.0.1.1", bindings[1].ForcePassiveIP)
|
require.Equal(t, "127.0.1.1", bindings[1].ForcePassiveIP)
|
||||||
|
require.Len(t, bindings[1].PassiveIPOverrides, 1)
|
||||||
|
require.Equal(t, "192.168.1.1", bindings[1].PassiveIPOverrides[0].IP)
|
||||||
|
require.Len(t, bindings[1].PassiveIPOverrides[0].Networks, 2)
|
||||||
|
require.Equal(t, "192.168.1.0/24", bindings[1].PassiveIPOverrides[0].Networks[0])
|
||||||
|
require.Equal(t, "192.168.3.0/25", bindings[1].PassiveIPOverrides[0].Networks[1])
|
||||||
require.Equal(t, 2, bindings[1].ClientAuthType)
|
require.Equal(t, 2, bindings[1].ClientAuthType)
|
||||||
require.Nil(t, bindings[1].TLSCipherSuites)
|
require.Nil(t, bindings[1].TLSCipherSuites)
|
||||||
require.Equal(t, 0, bindings[1].PassiveConnectionsSecurity)
|
require.Equal(t, 0, bindings[1].PassiveConnectionsSecurity)
|
||||||
|
|
|
@ -121,6 +121,9 @@ The configuration file contains the following sections:
|
||||||
- `apply_proxy_config`, boolean. If enabled the common proxy configuration, if any, will be applied. Please note that we expect the proxy header on control and data connections. Default `true`.
|
- `apply_proxy_config`, boolean. If enabled the common proxy configuration, if any, will be applied. Please note that we expect the proxy header on control and data connections. Default `true`.
|
||||||
- `tls_mode`, integer. 0 means accept both cleartext and encrypted sessions. 1 means TLS is required for both control and data connection. 2 means implicit TLS. Do not enable this blindly, please check that a proper TLS config is in place if you set `tls_mode` is different from 0.
|
- `tls_mode`, integer. 0 means accept both cleartext and encrypted sessions. 1 means TLS is required for both control and data connection. 2 means implicit TLS. Do not enable this blindly, please check that a proper TLS config is in place if you set `tls_mode` is different from 0.
|
||||||
- `force_passive_ip`, ip address. External IP address to expose for passive connections. Leavy empty to autodetect. If not empty, it must be a valid IPv4 address. Defaut: "".
|
- `force_passive_ip`, ip address. External IP address to expose for passive connections. Leavy empty to autodetect. If not empty, it must be a valid IPv4 address. Defaut: "".
|
||||||
|
- `passive_ip_overrides`, list of struct that allows to return a different passive ip based on the client IP address. Each struct has the following fields:
|
||||||
|
- `networks`, list of strings. Each string must define a network in CIDR notation, for example 192.168.1.0/24.
|
||||||
|
- `ip`, string. Passive IP to return if the client IP address belongs to the defined networks. Empty means autodetect.
|
||||||
- `client_auth_type`, integer. Set to `1` to require a client certificate and verify it. Set to `2` to request a client certificate during the TLS handshake and verify it if given, in this mode the client is allowed not to send a certificate. At least one certification authority must be defined in order to verify client certificates. If no certification authority is defined, this setting is ignored. Default: 0.
|
- `client_auth_type`, integer. Set to `1` to require a client certificate and verify it. Set to `2` to request a client certificate during the TLS handshake and verify it if given, in this mode the client is allowed not to send a certificate. At least one certification authority must be defined in order to verify client certificates. If no certification authority is defined, this setting is ignored. Default: 0.
|
||||||
- `tls_cipher_suites`, list of strings. List of supported cipher suites for TLS version 1.2. If empty, a default list of secure cipher suites is used, with a preference order based on hardware performance. Note that TLS 1.3 ciphersuites are not configurable. The supported ciphersuites names are defined [here](https://github.com/golang/go/blob/master/src/crypto/tls/cipher_suites.go#L52). Any invalid name will be silently ignored. The order matters, the ciphers listed first will be the preferred ones. Default: empty.
|
- `tls_cipher_suites`, list of strings. List of supported cipher suites for TLS version 1.2. If empty, a default list of secure cipher suites is used, with a preference order based on hardware performance. Note that TLS 1.3 ciphersuites are not configurable. The supported ciphersuites names are defined [here](https://github.com/golang/go/blob/master/src/crypto/tls/cipher_suites.go#L52). Any invalid name will be silently ignored. The order matters, the ciphers listed first will be the preferred ones. Default: empty.
|
||||||
- `passive_connections_security`, integer. Defines the security checks for passive data connections. Set to `0` to require matching peer IP addresses of control and data connection. Set to `1` to disable any checks. Please note that if you run the FTP service behind a proxy you must enable the proxy protocol for control and data connections. Default: `0`.
|
- `passive_connections_security`, integer. Defines the security checks for passive data connections. Set to `0` to require matching peer IP addresses of control and data connection. Set to `1` to disable any checks. Please note that if you run the FTP service behind a proxy you must enable the proxy protocol for control and data connections. Default: `0`.
|
||||||
|
|
81
ftpd/ftpd.go
81
ftpd/ftpd.go
|
@ -2,9 +2,11 @@
|
||||||
package ftpd
|
package ftpd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
ftpserver "github.com/fclairamb/ftpserverlib"
|
ftpserver "github.com/fclairamb/ftpserverlib"
|
||||||
|
|
||||||
|
@ -22,6 +24,14 @@ var (
|
||||||
serviceStatus ServiceStatus
|
serviceStatus ServiceStatus
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// PassiveIPOverride defines an exception for the configured passive IP
|
||||||
|
type PassiveIPOverride struct {
|
||||||
|
Networks []string `json:"networks" mapstructure:"networks"`
|
||||||
|
// if empty the local address will be returned
|
||||||
|
IP string `json:"ip" mapstructure:"ip"`
|
||||||
|
parsedNetworks []func(net.IP) bool
|
||||||
|
}
|
||||||
|
|
||||||
// Binding defines the configuration for a network listener
|
// Binding defines the configuration for a network listener
|
||||||
type Binding struct {
|
type Binding struct {
|
||||||
// The address to listen on. A blank value means listen on all available network interfaces.
|
// The address to listen on. A blank value means listen on all available network interfaces.
|
||||||
|
@ -35,6 +45,9 @@ type Binding struct {
|
||||||
TLSMode int `json:"tls_mode" mapstructure:"tls_mode"`
|
TLSMode int `json:"tls_mode" mapstructure:"tls_mode"`
|
||||||
// External IP address to expose for passive connections.
|
// External IP address to expose for passive connections.
|
||||||
ForcePassiveIP string `json:"force_passive_ip" mapstructure:"force_passive_ip"`
|
ForcePassiveIP string `json:"force_passive_ip" mapstructure:"force_passive_ip"`
|
||||||
|
// PassiveIPOverrides allows to define different IP addresses to expose for passive connections
|
||||||
|
// based on the client IP address
|
||||||
|
PassiveIPOverrides []PassiveIPOverride `json:"passive_ip_overrides" mapstructure:"passive_ip_overrides"`
|
||||||
// Set to 1 to require client certificate authentication.
|
// Set to 1 to require client certificate authentication.
|
||||||
// Set to 2 to require a client certificate and verfify it if given. In this mode
|
// Set to 2 to require a client certificate and verfify it if given. In this mode
|
||||||
// the client is allowed not to send a certificate.
|
// the client is allowed not to send a certificate.
|
||||||
|
@ -99,19 +112,61 @@ func (b *Binding) checkSecuritySettings() error {
|
||||||
|
|
||||||
func (b *Binding) checkPassiveIP() error {
|
func (b *Binding) checkPassiveIP() error {
|
||||||
if b.ForcePassiveIP != "" {
|
if b.ForcePassiveIP != "" {
|
||||||
ip := net.ParseIP(b.ForcePassiveIP)
|
ip, err := parsePassiveIP(b.ForcePassiveIP)
|
||||||
if ip == nil {
|
if err != nil {
|
||||||
return fmt.Errorf("the provided passive IP %#v is not valid", b.ForcePassiveIP)
|
return err
|
||||||
}
|
}
|
||||||
ip = ip.To4()
|
b.ForcePassiveIP = ip
|
||||||
if ip == nil {
|
|
||||||
return fmt.Errorf("the provided passive IP %#v is not a valid IPv4 address", b.ForcePassiveIP)
|
|
||||||
}
|
}
|
||||||
b.ForcePassiveIP = ip.String()
|
for idx, passiveOverride := range b.PassiveIPOverrides {
|
||||||
|
var ip string
|
||||||
|
|
||||||
|
if passiveOverride.IP != "" {
|
||||||
|
var err error
|
||||||
|
ip, err = parsePassiveIP(passiveOverride.IP)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(passiveOverride.Networks) == 0 {
|
||||||
|
return errors.New("passive IP networks override cannot be empty")
|
||||||
|
}
|
||||||
|
checkFuncs, err := util.ParseAllowedIPAndRanges(passiveOverride.Networks)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid passive IP networks override %+v: %w", passiveOverride.Networks, err)
|
||||||
|
}
|
||||||
|
b.PassiveIPOverrides[idx].IP = ip
|
||||||
|
b.PassiveIPOverrides[idx].parsedNetworks = checkFuncs
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *Binding) getPassiveIP(cc ftpserver.ClientContext) string {
|
||||||
|
if b.ForcePassiveIP != "" {
|
||||||
|
return b.ForcePassiveIP
|
||||||
|
}
|
||||||
|
return strings.Split(cc.LocalAddr().String(), ":")[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Binding) passiveIPResolver(cc ftpserver.ClientContext) (string, error) {
|
||||||
|
if len(b.PassiveIPOverrides) > 0 {
|
||||||
|
clientIP := net.ParseIP(util.GetIPFromRemoteAddress(cc.RemoteAddr().String()))
|
||||||
|
if clientIP != nil {
|
||||||
|
for _, override := range b.PassiveIPOverrides {
|
||||||
|
for _, fn := range override.parsedNetworks {
|
||||||
|
if fn(clientIP) {
|
||||||
|
if override.IP == "" {
|
||||||
|
return strings.Split(cc.LocalAddr().String(), ":")[0], nil
|
||||||
|
}
|
||||||
|
return override.IP, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return b.getPassiveIP(cc), nil
|
||||||
|
}
|
||||||
|
|
||||||
// HasProxy returns true if the proxy protocol is active for this binding
|
// HasProxy returns true if the proxy protocol is active for this binding
|
||||||
func (b *Binding) HasProxy() bool {
|
func (b *Binding) HasProxy() bool {
|
||||||
return b.ApplyProxyConfig && common.Config.ProxyProtocol > 0
|
return b.ApplyProxyConfig && common.Config.ProxyProtocol > 0
|
||||||
|
@ -268,6 +323,18 @@ func GetStatus() ServiceStatus {
|
||||||
return serviceStatus
|
return serviceStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parsePassiveIP(passiveIP string) (string, error) {
|
||||||
|
ip := net.ParseIP(passiveIP)
|
||||||
|
if ip == nil {
|
||||||
|
return "", fmt.Errorf("the provided passive IP %#v is not valid", passiveIP)
|
||||||
|
}
|
||||||
|
ip = ip.To4()
|
||||||
|
if ip == nil {
|
||||||
|
return "", fmt.Errorf("the provided passive IP %#v is not a valid IPv4 address", passiveIP)
|
||||||
|
}
|
||||||
|
return ip.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
func getConfigPath(name, configDir string) string {
|
func getConfigPath(name, configDir string) string {
|
||||||
if !util.IsFileInputValid(name) {
|
if !util.IsFileInputValid(name) {
|
||||||
return ""
|
return ""
|
||||||
|
|
|
@ -254,6 +254,8 @@ xr5cb9VBRBtB9aOKVfuRhpatAfS2Pzm2Htae9lFn7slGPUmu2hkjDw==
|
||||||
|
|
||||||
type mockFTPClientContext struct {
|
type mockFTPClientContext struct {
|
||||||
lastDataChannel ftpserver.DataChannel
|
lastDataChannel ftpserver.DataChannel
|
||||||
|
remoteIP string
|
||||||
|
localIP string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cc mockFTPClientContext) Path() string {
|
func (cc mockFTPClientContext) Path() string {
|
||||||
|
@ -271,11 +273,19 @@ func (cc mockFTPClientContext) ID() uint32 {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cc mockFTPClientContext) RemoteAddr() net.Addr {
|
func (cc mockFTPClientContext) RemoteAddr() net.Addr {
|
||||||
return &net.IPAddr{IP: []byte("127.0.0.1")}
|
ip := "127.0.0.1"
|
||||||
|
if cc.remoteIP != "" {
|
||||||
|
ip = cc.remoteIP
|
||||||
|
}
|
||||||
|
return &net.IPAddr{IP: net.ParseIP(ip)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cc mockFTPClientContext) LocalAddr() net.Addr {
|
func (cc mockFTPClientContext) LocalAddr() net.Addr {
|
||||||
return &net.IPAddr{IP: []byte("127.0.0.1")}
|
ip := "127.0.0.1"
|
||||||
|
if cc.localIP != "" {
|
||||||
|
ip = cc.localIP
|
||||||
|
}
|
||||||
|
return &net.IPAddr{IP: net.ParseIP(ip)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cc mockFTPClientContext) GetClientVersion() string {
|
func (cc mockFTPClientContext) GetClientVersion() string {
|
||||||
|
@ -924,3 +934,69 @@ func TestCiphers(t *testing.T) {
|
||||||
require.Len(t, b.ciphers, 2)
|
require.Len(t, b.ciphers, 2)
|
||||||
require.Equal(t, []uint16{tls.TLS_AES_128_GCM_SHA256, tls.TLS_AES_256_GCM_SHA384}, b.ciphers)
|
require.Equal(t, []uint16{tls.TLS_AES_128_GCM_SHA256, tls.TLS_AES_256_GCM_SHA384}, b.ciphers)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPassiveIPResolver(t *testing.T) {
|
||||||
|
b := Binding{
|
||||||
|
PassiveIPOverrides: []PassiveIPOverride{
|
||||||
|
{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
err := b.checkPassiveIP()
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), "passive IP networks override cannot be empty")
|
||||||
|
b = Binding{
|
||||||
|
PassiveIPOverrides: []PassiveIPOverride{
|
||||||
|
{
|
||||||
|
IP: "invalid ip",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
err = b.checkPassiveIP()
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), "is not valid")
|
||||||
|
|
||||||
|
b = Binding{
|
||||||
|
PassiveIPOverrides: []PassiveIPOverride{
|
||||||
|
{
|
||||||
|
IP: "192.168.1.1",
|
||||||
|
Networks: []string{"192.168.1.0/24", "invalid cidr"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
err = b.checkPassiveIP()
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), "invalid passive IP networks override")
|
||||||
|
b = Binding{
|
||||||
|
ForcePassiveIP: "192.168.2.1",
|
||||||
|
PassiveIPOverrides: []PassiveIPOverride{
|
||||||
|
{
|
||||||
|
IP: "::ffff:192.168.1.1",
|
||||||
|
Networks: []string{"192.168.1.0/24"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
err = b.checkPassiveIP()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "192.168.1.1", b.PassiveIPOverrides[0].IP)
|
||||||
|
require.Len(t, b.PassiveIPOverrides[0].parsedNetworks, 1)
|
||||||
|
ip := net.ParseIP("192.168.1.2")
|
||||||
|
assert.True(t, b.PassiveIPOverrides[0].parsedNetworks[0](ip))
|
||||||
|
ip = net.ParseIP("192.168.0.2")
|
||||||
|
assert.False(t, b.PassiveIPOverrides[0].parsedNetworks[0](ip))
|
||||||
|
|
||||||
|
mockCC := mockFTPClientContext{
|
||||||
|
remoteIP: "192.168.1.10",
|
||||||
|
localIP: "192.168.1.3",
|
||||||
|
}
|
||||||
|
passiveIP, err := b.passiveIPResolver(mockCC)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "192.168.1.1", passiveIP)
|
||||||
|
b.PassiveIPOverrides[0].IP = ""
|
||||||
|
passiveIP, err = b.passiveIPResolver(mockCC)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "192.168.1.3", passiveIP)
|
||||||
|
mockCC.remoteIP = "172.16.2.3"
|
||||||
|
passiveIP, err = b.passiveIPResolver(mockCC)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, b.ForcePassiveIP, passiveIP)
|
||||||
|
}
|
||||||
|
|
|
@ -124,7 +124,7 @@ func (s *Server) GetSettings() (*ftpserver.Settings, error) {
|
||||||
return &ftpserver.Settings{
|
return &ftpserver.Settings{
|
||||||
Listener: ftpListener,
|
Listener: ftpListener,
|
||||||
ListenAddr: s.binding.GetAddress(),
|
ListenAddr: s.binding.GetAddress(),
|
||||||
PublicHost: s.binding.ForcePassiveIP,
|
PublicIPResolver: s.binding.passiveIPResolver,
|
||||||
PassiveTransferPortRange: portRange,
|
PassiveTransferPortRange: portRange,
|
||||||
ActiveTransferPortNon20: s.config.ActiveTransfersPortNon20,
|
ActiveTransferPortNon20: s.config.ActiveTransfersPortNon20,
|
||||||
IdleTimeout: -1,
|
IdleTimeout: -1,
|
||||||
|
|
10
go.mod
10
go.mod
|
@ -7,7 +7,7 @@ require (
|
||||||
github.com/Azure/azure-storage-blob-go v0.14.0
|
github.com/Azure/azure-storage-blob-go v0.14.0
|
||||||
github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962
|
github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962
|
||||||
github.com/alexedwards/argon2id v0.0.0-20210511081203-7d35d68092b8
|
github.com/alexedwards/argon2id v0.0.0-20210511081203-7d35d68092b8
|
||||||
github.com/aws/aws-sdk-go v1.42.9
|
github.com/aws/aws-sdk-go v1.42.12
|
||||||
github.com/cockroachdb/cockroach-go/v2 v2.2.4
|
github.com/cockroachdb/cockroach-go/v2 v2.2.4
|
||||||
github.com/eikenb/pipeat v0.0.0-20210603033007-44fc3ffce52b
|
github.com/eikenb/pipeat v0.0.0-20210603033007-44fc3ffce52b
|
||||||
github.com/fclairamb/ftpserverlib v0.16.0
|
github.com/fclairamb/ftpserverlib v0.16.0
|
||||||
|
@ -52,8 +52,8 @@ require (
|
||||||
go.uber.org/automaxprocs v1.4.0
|
go.uber.org/automaxprocs v1.4.0
|
||||||
gocloud.dev v0.24.0
|
gocloud.dev v0.24.0
|
||||||
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871
|
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871
|
||||||
golang.org/x/net v0.0.0-20211118161319-6a13c67c3ce4
|
golang.org/x/net v0.0.0-20211123203042-d83791d6bcd9
|
||||||
golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1
|
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881
|
||||||
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11
|
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11
|
||||||
google.golang.org/api v0.60.0
|
google.golang.org/api v0.60.0
|
||||||
google.golang.org/grpc v1.42.0
|
google.golang.org/grpc v1.42.0
|
||||||
|
@ -101,7 +101,7 @@ require (
|
||||||
github.com/lestrrat-go/option v1.0.0 // indirect
|
github.com/lestrrat-go/option v1.0.0 // indirect
|
||||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||||
github.com/magiconair/properties v1.8.5 // indirect
|
github.com/magiconair/properties v1.8.5 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.11 // indirect
|
github.com/mattn/go-colorable v0.1.12 // indirect
|
||||||
github.com/mattn/go-ieproxy v0.0.1 // indirect
|
github.com/mattn/go-ieproxy v0.0.1 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||||
|
@ -139,5 +139,5 @@ replace (
|
||||||
github.com/fclairamb/ftpserverlib => github.com/drakkan/ftpserverlib v0.0.0-20211107071448-34ff70e85dfb
|
github.com/fclairamb/ftpserverlib => github.com/drakkan/ftpserverlib v0.0.0-20211107071448-34ff70e85dfb
|
||||||
github.com/jlaffaye/ftp => github.com/drakkan/ftp v0.0.0-20201114075148-9b9adce499a9
|
github.com/jlaffaye/ftp => github.com/drakkan/ftp v0.0.0-20201114075148-9b9adce499a9
|
||||||
golang.org/x/crypto => github.com/drakkan/crypto v0.0.0-20211120085116-d3e2208cd0bd
|
golang.org/x/crypto => github.com/drakkan/crypto v0.0.0-20211120085116-d3e2208cd0bd
|
||||||
golang.org/x/net => github.com/drakkan/net v0.0.0-20211120084140-32033f6a21da
|
golang.org/x/net => github.com/drakkan/net v0.0.0-20211125114103-f7adc41924ea
|
||||||
)
|
)
|
||||||
|
|
16
go.sum
16
go.sum
|
@ -137,8 +137,8 @@ github.com/aws/aws-sdk-go v1.15.27/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZo
|
||||||
github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
||||||
github.com/aws/aws-sdk-go v1.38.68/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
github.com/aws/aws-sdk-go v1.38.68/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
||||||
github.com/aws/aws-sdk-go v1.40.34/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=
|
github.com/aws/aws-sdk-go v1.40.34/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=
|
||||||
github.com/aws/aws-sdk-go v1.42.9 h1:8ptAGgA+uC2TUbdvUeOVSfBocIZvGE2NKiLxkAcn1GA=
|
github.com/aws/aws-sdk-go v1.42.12 h1:zVrAgi3/HuMPygZknc+f2KAHcn+Zuq767857hnHBMPA=
|
||||||
github.com/aws/aws-sdk-go v1.42.9/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=
|
github.com/aws/aws-sdk-go v1.42.12/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=
|
||||||
github.com/aws/aws-sdk-go-v2 v1.7.0/go.mod h1:tb9wi5s61kTDA5qCkcDbt3KRVV74GGslQkl/DRdX/P4=
|
github.com/aws/aws-sdk-go-v2 v1.7.0/go.mod h1:tb9wi5s61kTDA5qCkcDbt3KRVV74GGslQkl/DRdX/P4=
|
||||||
github.com/aws/aws-sdk-go-v2 v1.9.0/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4=
|
github.com/aws/aws-sdk-go-v2 v1.9.0/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4=
|
||||||
github.com/aws/aws-sdk-go-v2/config v1.7.0/go.mod h1:w9+nMZ7soXCe5nT46Ri354SNhXDQ6v+V5wqDjnZE+GY=
|
github.com/aws/aws-sdk-go-v2/config v1.7.0/go.mod h1:w9+nMZ7soXCe5nT46Ri354SNhXDQ6v+V5wqDjnZE+GY=
|
||||||
|
@ -224,8 +224,8 @@ github.com/drakkan/ftp v0.0.0-20201114075148-9b9adce499a9 h1:LPH1dEblAOO/LoG7yHP
|
||||||
github.com/drakkan/ftp v0.0.0-20201114075148-9b9adce499a9/go.mod h1:2lmrmq866uF2tnje75wQHzmPXhmSWUt7Gyx2vgK1RCU=
|
github.com/drakkan/ftp v0.0.0-20201114075148-9b9adce499a9/go.mod h1:2lmrmq866uF2tnje75wQHzmPXhmSWUt7Gyx2vgK1RCU=
|
||||||
github.com/drakkan/ftpserverlib v0.0.0-20211107071448-34ff70e85dfb h1:cT/w4XStm7m022JgVqmrXZLcZ4UjoUER1VW5/5gd6ec=
|
github.com/drakkan/ftpserverlib v0.0.0-20211107071448-34ff70e85dfb h1:cT/w4XStm7m022JgVqmrXZLcZ4UjoUER1VW5/5gd6ec=
|
||||||
github.com/drakkan/ftpserverlib v0.0.0-20211107071448-34ff70e85dfb/go.mod h1:fBiQ19WDhtvKArMu0Pifg71k+0xqRYn+F0d9AsjkZw8=
|
github.com/drakkan/ftpserverlib v0.0.0-20211107071448-34ff70e85dfb/go.mod h1:fBiQ19WDhtvKArMu0Pifg71k+0xqRYn+F0d9AsjkZw8=
|
||||||
github.com/drakkan/net v0.0.0-20211120084140-32033f6a21da h1:RAs8vjTnp+stqm/Ieq0n5akxtVzyAlq4aVoGCIM2JtQ=
|
github.com/drakkan/net v0.0.0-20211125114103-f7adc41924ea h1:M5ZyIIYPyx1dhIQm1QbF4ofccWkjWFdSbJJ5qYziWek=
|
||||||
github.com/drakkan/net v0.0.0-20211120084140-32033f6a21da/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
github.com/drakkan/net v0.0.0-20211125114103-f7adc41924ea/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
github.com/drakkan/pipeat v0.0.0-20210805162858-70e57fa8a639 h1:8tfGdb4kg/YCvAbIrsMazgoNtnqdOqQVDKW12uUCuuU=
|
github.com/drakkan/pipeat v0.0.0-20210805162858-70e57fa8a639 h1:8tfGdb4kg/YCvAbIrsMazgoNtnqdOqQVDKW12uUCuuU=
|
||||||
github.com/drakkan/pipeat v0.0.0-20210805162858-70e57fa8a639/go.mod h1:kltMsfRMTHSFdMbK66XdS8mfMW77+FZA1fGY1xYMF84=
|
github.com/drakkan/pipeat v0.0.0-20210805162858-70e57fa8a639/go.mod h1:kltMsfRMTHSFdMbK66XdS8mfMW77+FZA1fGY1xYMF84=
|
||||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||||
|
@ -591,8 +591,8 @@ github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVc
|
||||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs=
|
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
|
||||||
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||||
github.com/mattn/go-ieproxy v0.0.1 h1:qiyop7gCflfhwCzGyeT0gro3sF9AIg9HU98JORTkqfI=
|
github.com/mattn/go-ieproxy v0.0.1 h1:qiyop7gCflfhwCzGyeT0gro3sF9AIg9HU98JORTkqfI=
|
||||||
github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E=
|
github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E=
|
||||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||||
|
@ -985,8 +985,8 @@ golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1 h1:kwrAHlwJ0DUBZwQ238v+Uod/3eZ8B2K5rYsUHBQvzmI=
|
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881 h1:TyHqChC80pFkXWraUUf6RuB5IqFdQieMLwwCJokV2pc=
|
||||||
golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
|
|
@ -86,6 +86,7 @@
|
||||||
"apply_proxy_config": true,
|
"apply_proxy_config": true,
|
||||||
"tls_mode": 0,
|
"tls_mode": 0,
|
||||||
"force_passive_ip": "",
|
"force_passive_ip": "",
|
||||||
|
"passive_ip_overrides": [],
|
||||||
"client_auth_type": 0,
|
"client_auth_type": 0,
|
||||||
"tls_cipher_suites": [],
|
"tls_cipher_suites": [],
|
||||||
"passive_connections_security": 0,
|
"passive_connections_security": 0,
|
||||||
|
|
Loading…
Reference in a new issue