mirror of
https://github.com/rclone/rclone.git
synced 2025-08-10 06:09:44 +02:00
sftp: add --sftp-http-proxy to connect via HTTP CONNECT proxy
This commit is contained in:
@@ -10,6 +10,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
iofs "io/fs"
|
iofs "io/fs"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"regexp"
|
"regexp"
|
||||||
@@ -482,6 +483,14 @@ Example:
|
|||||||
myUser:myPass@localhost:9005
|
myUser:myPass@localhost:9005
|
||||||
`,
|
`,
|
||||||
Advanced: true,
|
Advanced: true,
|
||||||
|
}, {
|
||||||
|
Name: "http_proxy",
|
||||||
|
Default: "",
|
||||||
|
Help: `URL for HTTP CONNECT proxy
|
||||||
|
|
||||||
|
Set this to a URL for an HTTP proxy which supports the HTTP CONNECT verb.
|
||||||
|
`,
|
||||||
|
Advanced: true,
|
||||||
}, {
|
}, {
|
||||||
Name: "copy_is_hardlink",
|
Name: "copy_is_hardlink",
|
||||||
Default: false,
|
Default: false,
|
||||||
@@ -545,6 +554,7 @@ type Options struct {
|
|||||||
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"`
|
SocksProxy string `config:"socks_proxy"`
|
||||||
|
HTTPProxy string `config:"http_proxy"`
|
||||||
CopyIsHardlink bool `config:"copy_is_hardlink"`
|
CopyIsHardlink bool `config:"copy_is_hardlink"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -570,6 +580,7 @@ type Fs struct {
|
|||||||
savedpswd string
|
savedpswd string
|
||||||
sessions atomic.Int32 // count in use sessions
|
sessions atomic.Int32 // count in use sessions
|
||||||
tokens *pacer.TokenDispenser
|
tokens *pacer.TokenDispenser
|
||||||
|
proxyURL *url.URL // address of HTTP proxy read from environment
|
||||||
}
|
}
|
||||||
|
|
||||||
// Object is a remote SFTP file that has been stat'd (so it exists, but is not necessarily open for reading)
|
// Object is a remote SFTP file that has been stat'd (so it exists, but is not necessarily open for reading)
|
||||||
@@ -867,6 +878,15 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e
|
|||||||
opt.Port = "22"
|
opt.Port = "22"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get proxy URL if set
|
||||||
|
if opt.HTTPProxy != "" {
|
||||||
|
proxyURL, err := url.Parse(opt.HTTPProxy)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse HTTP Proxy URL: %w", err)
|
||||||
|
}
|
||||||
|
f.proxyURL = proxyURL
|
||||||
|
}
|
||||||
|
|
||||||
sshConfig := &ssh.ClientConfig{
|
sshConfig := &ssh.ClientConfig{
|
||||||
User: opt.User,
|
User: opt.User,
|
||||||
Auth: []ssh.AuthMethod{},
|
Auth: []ssh.AuthMethod{},
|
||||||
|
@@ -31,6 +31,8 @@ func (f *Fs) newSSHClientInternal(ctx context.Context, network, addr string, ssh
|
|||||||
)
|
)
|
||||||
if f.opt.SocksProxy != "" {
|
if f.opt.SocksProxy != "" {
|
||||||
conn, err = proxy.SOCKS5Dial(network, addr, f.opt.SocksProxy, baseDialer)
|
conn, err = proxy.SOCKS5Dial(network, addr, f.opt.SocksProxy, baseDialer)
|
||||||
|
} else if f.proxyURL != nil {
|
||||||
|
conn, err = proxy.HTTPConnectDial(network, addr, f.proxyURL, baseDialer)
|
||||||
} else {
|
} else {
|
||||||
conn, err = baseDialer.Dial(network, addr)
|
conn, err = baseDialer.Dial(network, addr)
|
||||||
}
|
}
|
||||||
|
75
lib/proxy/http.go
Normal file
75
lib/proxy/http.go
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
package proxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/net/proxy"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HTTPConnectDial connects using HTTP CONNECT via proxyDialer
|
||||||
|
//
|
||||||
|
// It will read the HTTP proxy address from the environment in the
|
||||||
|
// standard way.
|
||||||
|
//
|
||||||
|
// It optionally takes a proxyDialer to dial the HTTP proxy server.
|
||||||
|
// If nil is passed, it will use the default net.Dialer.
|
||||||
|
func HTTPConnectDial(network, addr string, proxyURL *url.URL, proxyDialer proxy.Dialer) (net.Conn, error) {
|
||||||
|
if proxyDialer == nil {
|
||||||
|
proxyDialer = &net.Dialer{}
|
||||||
|
}
|
||||||
|
if proxyURL == nil {
|
||||||
|
return proxyDialer.Dial(network, addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepare proxy host with default ports
|
||||||
|
host := proxyURL.Host
|
||||||
|
if !strings.Contains(host, ":") {
|
||||||
|
if strings.EqualFold(proxyURL.Scheme, "https") {
|
||||||
|
host += ":443"
|
||||||
|
} else {
|
||||||
|
host += ":80"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// connect to proxy
|
||||||
|
conn, err := proxyDialer.Dial(network, host)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("HTTP CONNECT proxy failed to Dial: %q", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// wrap TLS if HTTPS proxy
|
||||||
|
if strings.EqualFold(proxyURL.Scheme, "https") {
|
||||||
|
tlsConfig := &tls.Config{ServerName: proxyURL.Hostname()}
|
||||||
|
tlsConn := tls.Client(conn, tlsConfig)
|
||||||
|
if err := tlsConn.Handshake(); err != nil {
|
||||||
|
_ = conn.Close()
|
||||||
|
return nil, fmt.Errorf("HTTP CONNECT proxy failed to make TLS connection: %q", err)
|
||||||
|
}
|
||||||
|
conn = tlsConn
|
||||||
|
}
|
||||||
|
|
||||||
|
// send CONNECT
|
||||||
|
_, err = fmt.Fprintf(conn, "CONNECT %s HTTP/1.1\r\nHost: %s\r\n\r\n", addr, addr)
|
||||||
|
if err != nil {
|
||||||
|
_ = conn.Close()
|
||||||
|
return nil, fmt.Errorf("HTTP CONNECT proxy failed to send CONNECT: %q", err)
|
||||||
|
}
|
||||||
|
br := bufio.NewReader(conn)
|
||||||
|
req := &http.Request{URL: &url.URL{Scheme: "http", Host: addr}}
|
||||||
|
resp, err := http.ReadResponse(br, req)
|
||||||
|
if err != nil {
|
||||||
|
_ = conn.Close()
|
||||||
|
return nil, fmt.Errorf("HTTP CONNECT proxy failed to read response: %q", err)
|
||||||
|
}
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
_ = conn.Close()
|
||||||
|
return nil, fmt.Errorf("HTTP CONNECT proxy failed: %s", resp.Status)
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
|
}
|
Reference in New Issue
Block a user