Skip to content

Commit 3a81347

Browse files
authored
Merge pull request #152 from SenseUnit/stdio_handler
StdIO handler (for seamless OpenSSH support)
2 parents 6ec158f + fdd8044 commit 3a81347

File tree

4 files changed

+106
-8
lines changed

4 files changed

+106
-8
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ Simple, scriptable, secure HTTP/SOCKS5 forward proxy.
4242
* Scripting with JavaScript:
4343
* Access filter by JS function
4444
* Upstream proxy selection by JS function
45+
* Seamless proxy client integration with OpenSSH: `ssh -o ProxyCommand="dumbproxy -config proxy.cfg -mode stdio %h %p" root@server1`
4546

4647
## Installation
4748

@@ -540,7 +541,7 @@ Usage of /home/user/go/bin/dumbproxy:
540541
-min-tls-version value
541542
minimum TLS version accepted by server (default TLS12)
542543
-mode value
543-
proxy operation mode (http/socks5) (default http)
544+
proxy operation mode (http/socks5/stdio) (default http)
544545
-passwd string
545546
update given htpasswd file and add/set password for username. Username and password can be passed as positional arguments or requested interactively
546547
-passwd-cost int

dialer/dto/dto.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,10 @@ type filterContextParams struct {
3232
}
3333

3434
func FilterParamsFromContext(ctx context.Context) (*http.Request, string) {
35-
params := ctx.Value(filterContextKey{}).(filterContextParams)
36-
return params.req, params.username
35+
if params, ok := ctx.Value(filterContextKey{}).(filterContextParams); ok {
36+
return params.req, params.username
37+
}
38+
return nil, ""
3739
}
3840

3941
func FilterParamsToContext(ctx context.Context, req *http.Request, username string) context.Context {

handler/stdio.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package handler
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"io"
7+
"net"
8+
"sync"
9+
10+
clog "github.com/SenseUnit/dumbproxy/log"
11+
)
12+
13+
func StdIOHandler(dialer HandlerDialer, logger *clog.CondLogger, forward ForwardFunc) func(ctx context.Context, reader io.Reader, writer io.Writer, dstAddress string) error {
14+
return func(ctx context.Context, reader io.Reader, writer io.Writer, dstAddress string) error {
15+
logger.Debug("Request: %v => %v %q %v %v %v", "<stdio>", "<stdio>", "", "STDIO", "CONNECT", dstAddress)
16+
target, err := dialer.DialContext(ctx, "tcp", dstAddress)
17+
if err != nil {
18+
return fmt.Errorf("connect to %q failed: %w", dstAddress, err)
19+
}
20+
defer target.Close()
21+
22+
return forward(ctx, "", wrapSOCKS(reader, writer), target)
23+
}
24+
}
25+
26+
type DummyAddress struct {
27+
network string
28+
address string
29+
}
30+
31+
func (a DummyAddress) Network() string {
32+
return a.network
33+
}
34+
35+
func (a DummyAddress) String() string {
36+
return a.address
37+
}
38+
39+
type DummyListener struct {
40+
address DummyAddress
41+
closed chan struct{}
42+
closeOnce sync.Once
43+
}
44+
45+
// Accept waits for and returns the next connection to the listener.
46+
func (l *DummyListener) Accept() (net.Conn, error) {
47+
<-l.closed
48+
return nil, net.ErrClosed
49+
}
50+
51+
// Close closes the listener.
52+
// Any blocked Accept operations will be unblocked and return errors.
53+
func (l *DummyListener) Close() error {
54+
l.closeOnce.Do(func() {
55+
close(l.closed)
56+
})
57+
return nil
58+
}
59+
60+
// Addr returns the listener's network address.
61+
func (l *DummyListener) Addr() net.Addr {
62+
return l.address
63+
}
64+
65+
func DummyListen(network, address string) (net.Listener, error) {
66+
return &DummyListener{
67+
address: DummyAddress{
68+
network: network,
69+
address: address,
70+
},
71+
closed: make(chan struct{}),
72+
}, nil
73+
}

main.go

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,7 @@ const (
217217
_ proxyMode = iota
218218
proxyModeHTTP
219219
proxyModeSOCKS5
220+
proxyModeStdIO
220221
)
221222

222223
type proxyModeArg struct {
@@ -230,6 +231,8 @@ func (a *proxyModeArg) Set(arg string) error {
230231
val = proxyModeHTTP
231232
case "socks", "socks5":
232233
val = proxyModeSOCKS5
234+
case "stdio":
235+
val = proxyModeStdIO
233236
default:
234237
return fmt.Errorf("unrecognized proxy mode %q", arg)
235238
}
@@ -243,6 +246,8 @@ func (a *proxyModeArg) String() string {
243246
return "http"
244247
case proxyModeSOCKS5:
245248
return "socks5"
249+
case proxyModeStdIO:
250+
return "stdio"
246251
default:
247252
return fmt.Sprintf("proxyMode(%d)", int(a.value))
248253
}
@@ -379,7 +384,7 @@ func parse_args() *CLIArgs {
379384
})
380385
flag.BoolVar(&args.unixSockUnlink, "unix-sock-unlink", true, "delete file object located at Unix domain socket bind path before binding")
381386
flag.Var(&args.unixSockMode, "unix-sock-mode", "set file mode for bound unix socket")
382-
flag.Var(&args.mode, "mode", "proxy operation mode (http/socks5)")
387+
flag.Var(&args.mode, "mode", "proxy operation mode (http/socks5/stdio)")
383388
flag.StringVar(&args.auth, "auth", "none://", "auth parameters")
384389
flag.IntVar(&args.verbosity, "verbosity", 20, "logging verbosity "+
385390
"(10 - debug, 20 - info, 30 - warning, 40 - error, 50 - critical)")
@@ -510,9 +515,16 @@ func run() int {
510515
return 0
511516
}
512517

513-
// we don't expect positional arguments in the main operation mode
514-
if len(args.positionalArgs) > 0 {
515-
arg_fail("Unexpected positional arguments! Check your command line.")
518+
if args.mode.value == proxyModeStdIO {
519+
// expect exactly two positional arguments
520+
if len(args.positionalArgs) != 2 {
521+
arg_fail("Exactly two positional arguments are expected in this mode: host and port")
522+
}
523+
} else {
524+
// we don't expect positional arguments in the main operation mode
525+
if len(args.positionalArgs) > 0 {
526+
arg_fail("Unexpected positional arguments! Check your command line.")
527+
}
516528
}
517529

518530
// setup logging
@@ -640,7 +652,10 @@ func run() int {
640652
mainLogger.Info("Starting proxy server...")
641653

642654
listenerFactory := net.Listen
643-
if args.bindReusePort {
655+
switch {
656+
case args.mode.value == proxyModeStdIO:
657+
listenerFactory = handler.DummyListen
658+
case args.bindReusePort:
644659
if reuseport.Available() {
645660
listenerFactory = reuseport.Listen
646661
} else {
@@ -872,6 +887,13 @@ func run() int {
872887
mainLogger.Info("Reached normal server termination.")
873888
}
874889
return 0
890+
case proxyModeStdIO:
891+
handler := handler.StdIOHandler(dialerRoot, proxyLogger, forwarder)
892+
address := net.JoinHostPort(args.positionalArgs[0], args.positionalArgs[1])
893+
if err := handler(stopContext, os.Stdin, os.Stdout, address); err != nil {
894+
mainLogger.Error("Connection interrupted: %v", err)
895+
}
896+
return 0
875897
}
876898

877899
mainLogger.Critical("unknown proxy mode")

0 commit comments

Comments
 (0)