parent
f4449440f8
commit
347812d1d3
4 changed files with 86 additions and 3 deletions
|
@ -28,6 +28,7 @@ import (
|
||||||
"github.com/rclone/rclone/lib/encoder"
|
"github.com/rclone/rclone/lib/encoder"
|
||||||
"github.com/rclone/rclone/lib/env"
|
"github.com/rclone/rclone/lib/env"
|
||||||
"github.com/rclone/rclone/lib/pacer"
|
"github.com/rclone/rclone/lib/pacer"
|
||||||
|
"github.com/rclone/rclone/lib/proxy"
|
||||||
"github.com/rclone/rclone/lib/readers"
|
"github.com/rclone/rclone/lib/readers"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -174,6 +175,18 @@ Enabled by default. Use 0 to disable.`,
|
||||||
If this is set and no password is supplied then rclone will ask for a password
|
If this is set and no password is supplied then rclone will ask for a password
|
||||||
`,
|
`,
|
||||||
Advanced: true,
|
Advanced: true,
|
||||||
|
}, {
|
||||||
|
Name: "socks_proxy",
|
||||||
|
Default: "",
|
||||||
|
Help: `Socks 5 proxy host.
|
||||||
|
|
||||||
|
Supports the format user:pass@host:port, user@host:port, host:port.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
myUser:myPass@localhost:9005
|
||||||
|
`,
|
||||||
|
Advanced: true,
|
||||||
}, {
|
}, {
|
||||||
Name: config.ConfigEncoding,
|
Name: config.ConfigEncoding,
|
||||||
Help: config.ConfigEncodingHelp,
|
Help: config.ConfigEncodingHelp,
|
||||||
|
@ -218,6 +231,7 @@ type Options struct {
|
||||||
ShutTimeout fs.Duration `config:"shut_timeout"`
|
ShutTimeout fs.Duration `config:"shut_timeout"`
|
||||||
AskPassword bool `config:"ask_password"`
|
AskPassword bool `config:"ask_password"`
|
||||||
Enc encoder.MultiEncoder `config:"encoding"`
|
Enc encoder.MultiEncoder `config:"encoding"`
|
||||||
|
SocksProxy string `config:"socks_proxy"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fs represents a remote FTP server
|
// Fs represents a remote FTP server
|
||||||
|
@ -359,7 +373,12 @@ func (f *Fs) ftpConnection(ctx context.Context) (c *ftp.ServerConn, err error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
fs.Debugf(f, "> dial: conn=%T, err=%v", conn, err)
|
fs.Debugf(f, "> dial: conn=%T, err=%v", conn, err)
|
||||||
}()
|
}()
|
||||||
conn, err = fshttp.NewDialer(ctx).Dial(network, address)
|
baseDialer := fshttp.NewDialer(ctx)
|
||||||
|
if f.opt.SocksProxy != "" {
|
||||||
|
conn, err = proxy.SOCKS5Dial(network, address, f.opt.SocksProxy, baseDialer)
|
||||||
|
} else {
|
||||||
|
conn, err = baseDialer.Dial(network, address)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -416,6 +416,17 @@ An example setting might be:
|
||||||
Note that when using an external ssh binary rclone makes a new ssh
|
Note that when using an external ssh binary rclone makes a new ssh
|
||||||
connection for every hash it calculates.
|
connection for every hash it calculates.
|
||||||
`,
|
`,
|
||||||
|
}, {
|
||||||
|
Name: "socks_proxy",
|
||||||
|
Default: "",
|
||||||
|
Help: `Socks 5 proxy host.
|
||||||
|
|
||||||
|
Supports the format user:pass@host:port, user@host:port, host:port.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
myUser:myPass@localhost:9005
|
||||||
|
`,
|
||||||
Advanced: true,
|
Advanced: true,
|
||||||
}},
|
}},
|
||||||
}
|
}
|
||||||
|
@ -457,6 +468,7 @@ type Options struct {
|
||||||
MACs fs.SpaceSepList `config:"macs"`
|
MACs fs.SpaceSepList `config:"macs"`
|
||||||
HostKeyAlgorithms fs.SpaceSepList `config:"host_key_algorithms"`
|
HostKeyAlgorithms fs.SpaceSepList `config:"host_key_algorithms"`
|
||||||
SSH fs.SpaceSepList `config:"ssh"`
|
SSH fs.SpaceSepList `config:"ssh"`
|
||||||
|
SocksProxy string `config:"socks_proxy"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fs stores the interface to the remote SFTP files
|
// Fs stores the interface to the remote SFTP files
|
||||||
|
|
|
@ -6,9 +6,11 @@ package sftp
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
|
"net"
|
||||||
|
|
||||||
"github.com/rclone/rclone/fs"
|
"github.com/rclone/rclone/fs"
|
||||||
"github.com/rclone/rclone/fs/fshttp"
|
"github.com/rclone/rclone/fs/fshttp"
|
||||||
|
"github.com/rclone/rclone/lib/proxy"
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -22,8 +24,17 @@ type sshClientInternal struct {
|
||||||
// convenience function that connects to the given network address,
|
// convenience function that connects to the given network address,
|
||||||
// initiates the SSH handshake, and then sets up a Client.
|
// initiates the SSH handshake, and then sets up a Client.
|
||||||
func (f *Fs) newSSHClientInternal(ctx context.Context, network, addr string, sshConfig *ssh.ClientConfig) (sshClient, error) {
|
func (f *Fs) newSSHClientInternal(ctx context.Context, network, addr string, sshConfig *ssh.ClientConfig) (sshClient, error) {
|
||||||
dialer := fshttp.NewDialer(ctx)
|
|
||||||
conn, err := dialer.Dial(network, addr)
|
baseDialer := fshttp.NewDialer(ctx)
|
||||||
|
var (
|
||||||
|
conn net.Conn
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if f.opt.SocksProxy != "" {
|
||||||
|
conn, err = proxy.SOCKS5Dial(network, addr, f.opt.SocksProxy, baseDialer)
|
||||||
|
} else {
|
||||||
|
conn, err = baseDialer.Dial(network, addr)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
41
lib/proxy/socks.go
Normal file
41
lib/proxy/socks.go
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
package proxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/net/proxy"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SOCKS5Dial dials a net.Conn using a SOCKS5 proxy server.
|
||||||
|
// The socks5Proxy address can be in the form of [user:password@]host:port, [user@]host:port or just host:port if no auth is required.
|
||||||
|
// It will optionally take a proxyDialer to dial the SOCKS5 proxy server. If nil is passed, it will use the default net.Dialer.
|
||||||
|
func SOCKS5Dial(network, addr, socks5Proxy string, proxyDialer proxy.Dialer) (net.Conn, error) {
|
||||||
|
|
||||||
|
if proxyDialer == nil {
|
||||||
|
proxyDialer = &net.Dialer{}
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
proxyAddress string
|
||||||
|
proxyAuth *proxy.Auth
|
||||||
|
)
|
||||||
|
if credsAndHost := strings.SplitN(socks5Proxy, "@", 2); len(credsAndHost) == 2 {
|
||||||
|
proxyCreds := strings.SplitN(credsAndHost[0], ":", 2)
|
||||||
|
proxyAuth = &proxy.Auth{
|
||||||
|
User: proxyCreds[0],
|
||||||
|
}
|
||||||
|
if len(proxyCreds) == 2 {
|
||||||
|
proxyAuth.Password = proxyCreds[1]
|
||||||
|
}
|
||||||
|
proxyAddress = credsAndHost[1]
|
||||||
|
} else {
|
||||||
|
proxyAddress = credsAndHost[0]
|
||||||
|
}
|
||||||
|
proxyDialer, err := proxy.SOCKS5("tcp", proxyAddress, proxyAuth, proxyDialer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create proxy dialer: %w", err)
|
||||||
|
}
|
||||||
|
return proxyDialer.Dial(network, addr)
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in a new issue