diff --git a/.github/goreleaser.yml b/.github/goreleaser.yml index 77ca1d66..ccb19c50 100644 --- a/.github/goreleaser.yml +++ b/.github/goreleaser.yml @@ -12,6 +12,17 @@ builds: - amd64 - arm - arm64 + - ppc64 + - ppc64le + - mips + - mipsle + - mips64 + - mips64le + - mips64p32 + - mips64p32le + - ppc + - s390 + - s390x goarm: - 6 - 7 diff --git a/README.md b/README.md index f71f9c51..17b7e534 100644 --- a/README.md +++ b/README.md @@ -212,9 +212,14 @@ $ chisel client --help --max-retry-interval, Maximum wait time before retrying after a disconnection. Defaults to 5 minutes. - --proxy, An optional HTTP CONNECT proxy which will be used reach - the chisel server. Authentication can be specified inside the URL. + --proxy, An optional HTTP CONNECT or SOCKS5 proxy which will be + used to reach the chisel server. Authentication can be specified + inside the URL. For example, http://admin:password@my-server.com:8081 + or: socks://admin:password@my-server.com:1080 + + --header, Set a custom header in the form "HeaderName: HeaderContent". + Can be used multiple times. (e.g --header "Foo: Bar" --header "Hello: World") --hostname, Optionally set the 'Host' header (defaults to the host defined in the endpoint url). diff --git a/client/client.go b/client/client.go index 3d770136..9101abd7 100644 --- a/client/client.go +++ b/client/client.go @@ -11,10 +11,13 @@ import ( "strings" "time" + "github.com/armon/go-socks5" "github.com/gorilla/websocket" "github.com/jpillora/backoff" chshare "github.com/jpillora/chisel/share" + "golang.org/x/crypto/ssh" + "golang.org/x/net/proxy" ) //Config represents a client configuration @@ -26,22 +29,24 @@ type Config struct { MaxRetryCount int MaxRetryInterval time.Duration Server string - HTTPProxy string + Proxy string Remotes []string - HostHeader string + Headers http.Header + DialContext func(ctx context.Context, network, addr string) (net.Conn, error) } //Client represents a client instance type Client struct { *chshare.Logger - config *Config - sshConfig *ssh.ClientConfig - sshConn ssh.Conn - httpProxyURL *url.URL - server string - running bool - runningc chan error - connStats chshare.ConnStats + config *Config + sshConfig *ssh.ClientConfig + sshConn ssh.Conn + proxyURL *url.URL + server string + running bool + runningc chan error + connStats chshare.ConnStats + socksServer *socks5.Server } //NewClient creates a new client instance @@ -68,11 +73,15 @@ func NewClient(config *Config) (*Client, error) { //swap to websockets scheme u.Scheme = strings.Replace(u.Scheme, "http", "ws", 1) shared := &chshare.Config{} + createSocksServer := false for _, s := range config.Remotes { r, err := chshare.DecodeRemote(s) if err != nil { return nil, fmt.Errorf("Failed to decode remote '%s': %s", s, err) } + if r.Socks && r.Reverse { + createSocksServer = true + } shared.Remotes = append(shared.Remotes, r) } config.shared = shared @@ -85,8 +94,8 @@ func NewClient(config *Config) (*Client, error) { } client.Info = true - if p := config.HTTPProxy; p != "" { - client.httpProxyURL, err = url.Parse(p) + if p := config.Proxy; p != "" { + client.proxyURL, err = url.Parse(p) if err != nil { return nil, fmt.Errorf("Invalid proxy URL (%s)", err) } @@ -102,6 +111,14 @@ func NewClient(config *Config) (*Client, error) { Timeout: 30 * time.Second, } + if createSocksServer { + socksConfig := &socks5.Config{} + client.socksServer, err = socks5.New(socksConfig) + if err != nil { + return nil, err + } + } + return client, nil } @@ -129,8 +146,8 @@ func (c *Client) verifyServer(hostname string, remote net.Addr, key ssh.PublicKe //Start client and does not block func (c *Client) Start(ctx context.Context) error { via := "" - if c.httpProxyURL != nil { - via = " via " + c.httpProxyURL.String() + if c.proxyURL != nil { + via = " via " + c.proxyURL.String() } //prepare non-reverse proxies for i, r := range c.config.shared.Remotes { @@ -192,20 +209,40 @@ func (c *Client) connectionLoop() { WriteBufferSize: 1024, HandshakeTimeout: 45 * time.Second, Subprotocols: []string{chshare.ProtocolVersion}, + NetDialContext: c.config.DialContext, } - //optionally CONNECT proxy - if c.httpProxyURL != nil { - d.Proxy = func(*http.Request) (*url.URL, error) { - return c.httpProxyURL, nil - } - } - wsHeaders := http.Header{} - if c.config.HostHeader != "" { - wsHeaders = http.Header{ - "Host": {c.config.HostHeader}, + //optionally proxy + if c.proxyURL != nil { + if strings.HasPrefix(c.proxyURL.Scheme, "socks") { + // SOCKS5 proxy + if c.proxyURL.Scheme != "socks" && c.proxyURL.Scheme != "socks5h" { + c.Infof( + "unsupported socks proxy type: %s:// (only socks5h:// or socks:// is supported)", + c.proxyURL.Scheme) + break + } + var auth *proxy.Auth = nil + if c.proxyURL.User != nil { + pass, _ := c.proxyURL.User.Password() + auth = &proxy.Auth{ + User: c.proxyURL.User.Username(), + Password: pass, + } + } + socksDialer, err := proxy.SOCKS5("tcp", c.proxyURL.Host, auth, proxy.Direct) + if err != nil { + connerr = err + continue + } + d.NetDial = socksDialer.Dial + } else { + // CONNECT proxy + d.Proxy = func(*http.Request) (*url.URL, error) { + return c.proxyURL, nil + } } } - wsConn, _, err := d.Dial(c.server, wsHeaders) + wsConn, _, err := d.Dial(c.server, c.config.Headers) if err != nil { connerr = err continue @@ -272,6 +309,12 @@ func (c *Client) Close() error { func (c *Client) connectStreams(chans <-chan ssh.NewChannel) { for ch := range chans { remote := string(ch.ExtraData()) + socks := remote == "socks" + if socks && c.socksServer == nil { + c.Debugf("Denied socks request, please enable client socks remote.") + ch.Reject(ssh.Prohibited, "SOCKS5 is not enabled on the client") + continue + } stream, reqs, err := ch.Accept() if err != nil { c.Debugf("Failed to accept stream: %s", err) @@ -279,6 +322,10 @@ func (c *Client) connectStreams(chans <-chan ssh.NewChannel) { } go ssh.DiscardRequests(reqs) l := c.Logger.Fork("conn#%d", c.connStats.New()) - go chshare.HandleTCPStream(l, &c.connStats, stream, remote) + if socks { + go chshare.HandleSocksStream(l, c.socksServer, &c.connStats, stream) + } else { + go chshare.HandleTCPStream(l, &c.connStats, stream, remote) + } } } diff --git a/client/client_test.go b/client/client_test.go new file mode 100644 index 00000000..0121f359 --- /dev/null +++ b/client/client_test.go @@ -0,0 +1,38 @@ +package chclient + +import ( + "log" + "net/http" + "net/http/httptest" + "testing" + "time" +) + +func TestCustomHeaders(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + if req.Header.Get("Foo") != "Bar" { + t.Fatal("expected header Foo to be 'Bar'") + } + })) + // Close the server when test finishes + defer server.Close() + headers := http.Header{} + headers.Set("Foo", "Bar") + config := Config{ + Fingerprint: "", + Auth: "", + KeepAlive: time.Second, + MaxRetryCount: 0, + MaxRetryInterval: time.Second, + Server: server.URL, + Remotes: []string{"socks"}, + Headers: headers, + } + c, err := NewClient(&config) + if err != nil { + log.Fatal(err) + } + if err = c.Run(); err != nil { + log.Fatal(err) + } +} diff --git a/go.mod b/go.mod index 8832e690..ac9b8f10 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/jpillora/requestlog v1.0.0 github.com/jpillora/sizestr v1.0.0 github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce // indirect - golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59 - golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e // indirect - golang.org/x/sys v0.0.0-20200331124033-c3d80250170d // indirect + golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 + golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 + golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 // indirect ) diff --git a/go.sum b/go.sum index c60cf22f..cb5d510d 100644 --- a/go.sum +++ b/go.sum @@ -2,48 +2,30 @@ github.com/andrew-d/go-termutil v0.0.0-20150726205930-009166a695a2 h1:axBiC50cNZ github.com/andrew-d/go-termutil v0.0.0-20150726205930-009166a695a2/go.mod h1:jnzFpU88PccN/tPPhCpnNU8mZphvKxYM9lLNkd8e+os= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= -github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/jpillora/ansi v0.0.0-20170202005112-f496b27cd669 h1:l5rH/CnVVu+HPxjtxjM90nHrm4nov3j3RF9/62UjgLs= -github.com/jpillora/ansi v0.0.0-20170202005112-f496b27cd669/go.mod h1:kOeLNvjNBGSV3uYtFjvb72+fnZCMFJF1XDvRIjdom0g= github.com/jpillora/ansi v1.0.2 h1:+Ei5HCAH0xsrQRCT2PDr4mq9r4Gm4tg+arNdXRkB22s= github.com/jpillora/ansi v1.0.2/go.mod h1:D2tT+6uzJvN1nBVQILYWkIdq7zG+b5gcFN5WI/VyjMY= -github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7 h1:K//n/AqR5HjG3qxbrBCL4vJPW0MVFSs9CPK1OOJdRME= -github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0= github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= -github.com/jpillora/requestlog v0.0.0-20181015073026-df8817be5f82 h1:7ufdyC3aMxFcCv+ABZy/dmIVGKFoGNBCqOgLYPIckD8= -github.com/jpillora/requestlog v0.0.0-20181015073026-df8817be5f82/go.mod h1:w8buj+yNfmLEP0ENlbG/FRnK6bVmuhqXnukYCs9sDvY= github.com/jpillora/requestlog v1.0.0 h1:bg++eJ74T7DYL3DlIpiwknrtfdUA9oP/M4fL+PpqnyA= github.com/jpillora/requestlog v1.0.0/go.mod h1:HTWQb7QfDc2jtHnWe2XEIEeJB7gJPnVdpNn52HXPvy8= -github.com/jpillora/sizestr v0.0.0-20160130011556-e2ea2fa42fb9 h1:0c9jcgBtHRtDU//jTrcCgWG6UHjMZytiq/3WhraNgUM= -github.com/jpillora/sizestr v0.0.0-20160130011556-e2ea2fa42fb9/go.mod h1:1ffp+CRe0eAwwRb0/BownUAjMBsmTLwgAvRbfj9dRwE= github.com/jpillora/sizestr v1.0.0 h1:4tr0FLxs1Mtq3TnsLDV+GYUWG7Q26a6s+tV5Zfw2ygw= github.com/jpillora/sizestr v1.0.0/go.mod h1:bUhLv4ctkknatr6gR42qPxirmd5+ds1u7mzD+MZ33f0= github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce h1:fb190+cK2Xz/dvi9Hv8eCYJYvIGUTN2/KLq1pT6CjEc= github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4= -golang.org/x/crypto v0.0.0-20181015023909-0c41d7ab0a0e h1:IzypfodbhbnViNUO/MEh0FzCUooG97cIGfdggUrUSyU= -golang.org/x/crypto v0.0.0-20181015023909-0c41d7ab0a0e/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59 h1:3zb4D3T4G8jdExgVU/95+vQXfpEPiMdCaZgmGVxjNHM= -golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/net v0.0.0-20181017193950-04a2e542c03f h1:4pRM7zYwpBjCnfA1jRmhItLxYJkaEnsmuAcRtA347DA= -golang.org/x/net v0.0.0-20181017193950-04a2e542c03f/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 h1:cg5LA/zNPRzIXIWSCxQW10Rvpy94aQh3LT/ShoCpkHw= +golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/sys v0.0.0-20181019160139-8e24a49d80f8 h1:R91KX5nmbbvEd7w370cbVzKC+EzCTGqZq63Zad5IcLM= -golang.org/x/sys v0.0.0-20181019160139-8e24a49d80f8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 h1:AeiKBIuRw3UomYXSbLy0Mc2dDLfdtbT/IVn4keq83P0= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d h1:nc5K6ox/4lTFbMVSL9WRR81ixkcwXThoiF6yf+R9scA= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/main.go b/main.go index 570b508e..c6b3cdfe 100644 --- a/main.go +++ b/main.go @@ -5,11 +5,14 @@ import ( "fmt" "io/ioutil" "log" + "net/http" "os" + "runtime" "strconv" + "strings" - "github.com/jpillora/chisel/client" - "github.com/jpillora/chisel/server" + chclient "github.com/jpillora/chisel/client" + chserver "github.com/jpillora/chisel/server" chshare "github.com/jpillora/chisel/share" ) @@ -73,7 +76,7 @@ var commonHelp = ` a SIGHUP to short-circuit the client reconnect timer Version: - ` + chshare.BuildVersion + ` + ` + chshare.BuildVersion + ` (` + runtime.Version() + `) Read more: https://github.com/jpillora/chisel @@ -192,6 +195,32 @@ func server(args []string) { } } +type headerFlags struct { + http.Header +} + +func (flag *headerFlags) String() string { + out := "" + for k, v := range flag.Header { + out += fmt.Sprintf("%s: %s\n", k, v) + } + return out +} + +func (flag *headerFlags) Set(arg string) error { + index := strings.Index(arg, ":") + if index < 0 { + return fmt.Errorf(`Invalid header (%s). Should be in the format "HeaderName: HeaderContent"`, arg) + } + if flag.Header == nil { + flag.Header = http.Header{} + } + key := arg[0:index] + value := arg[index+1:] + flag.Header.Set(key, strings.TrimSpace(value)) + return nil +} + var clientHelp = ` Usage: chisel client [options] [remote] [remote] ... @@ -224,6 +253,8 @@ var clientHelp = ` socks 5000:socks R:2222:localhost:22 + R:socks + R:5000:socks When the chisel server has --socks5 enabled, remotes can specify "socks" in place of remote-host and remote-port. @@ -235,6 +266,9 @@ var clientHelp = ` be prefixed with R to denote that they are reversed. That is, the server will listen and accept connections, and they will be proxied through the client which specified the remote. + Reverse remotes specifying "R:socks" will listen on the server's + default socks port (1080) and terminate the connection at the + client's internal SOCKS5 proxy. Options: @@ -260,26 +294,31 @@ var clientHelp = ` --max-retry-interval, Maximum wait time before retrying after a disconnection. Defaults to 5 minutes. - --proxy, An optional HTTP CONNECT proxy which will be used reach - the chisel server. Authentication can be specified inside the URL. + --proxy, An optional HTTP CONNECT or SOCKS5 proxy which will be + used to reach the chisel server. Authentication can be specified + inside the URL. For example, http://admin:password@my-server.com:8081 + or: socks://admin:password@my-server.com:1080 + + --header, Set a custom header in the form "HeaderName: HeaderContent". + Can be used multiple times. (e.g --header "Foo: Bar" --header "Hello: World") --hostname, Optionally set the 'Host' header (defaults to the host found in the server url). ` + commonHelp func client(args []string) { - flags := flag.NewFlagSet("client", flag.ContinueOnError) - - fingerprint := flags.String("fingerprint", "", "") - auth := flags.String("auth", "", "") - keepalive := flags.Duration("keepalive", 0, "") - maxRetryCount := flags.Int("max-retry-count", -1, "") - maxRetryInterval := flags.Duration("max-retry-interval", 0, "") - proxy := flags.String("proxy", "", "") - pid := flags.Bool("pid", false, "") + config := chclient.Config{Headers: http.Header{}} + flags.StringVar(&config.Fingerprint, "fingerprint", "", "") + flags.StringVar(&config.Auth, "auth", "", "") + flags.DurationVar(&config.KeepAlive, "keepalive", 0, "") + flags.IntVar(&config.MaxRetryCount, "max-retry-count", -1, "") + flags.DurationVar(&config.MaxRetryInterval, "max-retry-interval", 0, "") + flags.StringVar(&config.Proxy, "proxy", "", "") + flags.Var(&headerFlags{config.Headers}, "header", "") hostname := flags.String("hostname", "", "") + pid := flags.Bool("pid", false, "") verbose := flags.Bool("v", false, "") flags.Usage = func() { fmt.Print(clientHelp) @@ -291,20 +330,18 @@ func client(args []string) { if len(args) < 2 { log.Fatalf("A server and least one remote is required") } - if *auth == "" { - *auth = os.Getenv("AUTH") + config.Server = args[0] + config.Remotes = args[1:] + //default auth + if config.Auth == "" { + config.Auth = os.Getenv("AUTH") } - c, err := chclient.NewClient(&chclient.Config{ - Fingerprint: *fingerprint, - Auth: *auth, - KeepAlive: *keepalive, - MaxRetryCount: *maxRetryCount, - MaxRetryInterval: *maxRetryInterval, - HTTPProxy: *proxy, - Server: args[0], - Remotes: args[1:], - HostHeader: *hostname, - }) + //move hostname onto headers + if *hostname != "" { + config.Headers.Set("Host", *hostname) + } + //ready + c, err := chclient.NewClient(&config) if err != nil { log.Fatal(err) } diff --git a/server/handler.go b/server/handler.go index d48eeb6c..31c0982c 100644 --- a/server/handler.go +++ b/server/handler.go @@ -2,7 +2,6 @@ package chserver import ( "context" - "io" "net/http" "strings" "sync/atomic" @@ -178,22 +177,9 @@ func (s *Server) handleSSHChannels(clientLog *chshare.Logger, chans <-chan ssh.N //handle stream type connID := s.connStats.New() if socks { - go s.handleSocksStream(clientLog.Fork("socksconn#%d", connID), stream) + go chshare.HandleSocksStream(clientLog.Fork("socksconn#%d", connID), s.socksServer, &s.connStats, stream) } else { go chshare.HandleTCPStream(clientLog.Fork("conn#%d", connID), &s.connStats, stream, remote) } } } - -func (s *Server) handleSocksStream(l *chshare.Logger, src io.ReadWriteCloser) { - conn := chshare.NewRWCConn(src) - s.connStats.Open() - l.Debugf("%s Opening", s.connStats) - err := s.socksServer.ServeConn(conn) - s.connStats.Close() - if err != nil && !strings.HasSuffix(err.Error(), "EOF") { - l.Debugf("%s: Closed (error: %s)", s.connStats, err) - } else { - l.Debugf("%s: Closed", s.connStats) - } -} diff --git a/server/server.go b/server/server.go index 8a60dd5b..074c94a2 100644 --- a/server/server.go +++ b/server/server.go @@ -144,7 +144,9 @@ func (s *Server) Start(host, port string) error { s.Infof("Listening on %s:%s...", host, port) h := http.Handler(http.HandlerFunc(s.handleClientHandler)) if s.Debug { - h = requestlog.Wrap(h) + o := requestlog.DefaultOptions + o.TrustProxy = true + h = requestlog.WrapWith(h, o) } return s.httpServer.GoListenAndServe(host+":"+port, h) } diff --git a/share/remote.go b/share/remote.go index 3aad30e1..dce3a8e8 100644 --- a/share/remote.go +++ b/share/remote.go @@ -43,11 +43,6 @@ func DecodeRemote(s string) (*Remote, error) { p := parts[i] //last part "socks"? if i == len(parts)-1 && p == "socks" { - if reverse { - // TODO allow reverse+socks by having client - // automatically start local SOCKS5 server - return nil, errors.New("'socks' incompatible with reverse port forwarding") - } r.Socks = true continue } diff --git a/share/ssh.go b/share/ssh.go index 610c59bf..85b28b38 100644 --- a/share/ssh.go +++ b/share/ssh.go @@ -12,6 +12,7 @@ import ( "net" "strings" + "github.com/armon/go-socks5" "github.com/jpillora/sizestr" "golang.org/x/crypto/ssh" ) @@ -56,3 +57,16 @@ func HandleTCPStream(l *Logger, connStats *ConnStats, src io.ReadWriteCloser, re connStats.Close() l.Debugf("%s: Close (sent %s received %s)", connStats, sizestr.ToString(s), sizestr.ToString(r)) } + +func HandleSocksStream(l *Logger, server *socks5.Server, connStats *ConnStats, src io.ReadWriteCloser) { + conn := NewRWCConn(src) + connStats.Open() + l.Debugf("%s Opening", connStats) + err := server.ServeConn(conn) + connStats.Close() + if err != nil && !strings.HasSuffix(err.Error(), "EOF") { + l.Debugf("%s: Closed (error: %s)", connStats, err) + } else { + l.Debugf("%s: Closed", connStats) + } +}