Browse Source

support pipes and stdin

AJ ONeal 4 months ago
parent
commit
c786b0bd07
7 changed files with 126 additions and 28 deletions
  1. 16
    0
      README.md
  2. 36
    23
      sclient-cli.go
  3. 55
    5
      sclient.go
  4. 5
    0
      tests/get.bin
  5. 8
    0
      tests/localhost.sh
  6. 3
    0
      tests/pipe.sh
  7. 3
    0
      tests/stdin.sh

+ 16
- 0
README.md View File

@@ -96,3 +96,19 @@ Ignore a bad TLS/SSL/HTTPS certificate and connect anyway.
96 96
 ```bash
97 97
 sclient -k badtls.telebit.cloud:443 localhost:3000
98 98
 ```
99
+
100
+Reading from stdin
101
+
102
+```bash
103
+sclient telebit.cloud:443 -
104
+```
105
+
106
+```bash
107
+sclient telebit.cloud:443 - </path/to/file
108
+```
109
+
110
+Piping
111
+
112
+```bash
113
+printf "GET / HTTP/1.1\r\nHost: telebit.cloud\r\n\r\n" | sclient telebit.cloud:443
114
+```

+ 36
- 23
sclient-cli.go View File

@@ -25,12 +25,18 @@ func main() {
25 25
 	insecure := flag.Bool("k", false, "ignore bad TLS/SSL/HTTPS certificates")
26 26
 	flag.BoolVar(insecure, "insecure", false, "ignore bad TLS/SSL/HTTPS certificates")
27 27
 	flag.Parse()
28
+	remotestr := flag.Arg(0)
29
+	localstr := flag.Arg(1)
28 30
 
29
-	// NArg, Arg, Args
30 31
 	i := flag.NArg()
31 32
 	if 2 != i {
32
-		usage()
33
-		os.Exit(0)
33
+		// We may omit the second argument if we're going straight to stdin
34
+		if stat, _ := os.Stdin.Stat(); 1 == i && (stat.Mode()&os.ModeCharDevice) == 0 {
35
+			localstr = "|"
36
+		} else {
37
+			usage()
38
+			os.Exit(1)
39
+		}
34 40
 	}
35 41
 
36 42
 	opts := &SclientOpts{}
@@ -38,8 +44,8 @@ func main() {
38 44
 	opts.LocalAddress = "localhost"
39 45
 	opts.InsecureSkipVerify = *insecure
40 46
 
41
-	remote := strings.Split(flag.Arg(0), ":")
42
-	//remoteAddr, remotePort, err := net.SplitHostPort(flag.Arg(0))
47
+	remote := strings.Split(remotestr, ":")
48
+	//remoteAddr, remotePort, err := net.SplitHostPort(remotestr)
43 49
 	if 2 == len(remote) {
44 50
 		rport, err := strconv.Atoi(remote[1])
45 51
 		if nil != err {
@@ -53,30 +59,37 @@ func main() {
53 59
 	}
54 60
 	opts.RemoteAddress = remote[0]
55 61
 
56
-	local := strings.Split(flag.Arg(1), ":")
57
-	//localAddr, localPort, err := net.SplitHostPort(flag.Arg(0))
58
-
59
-	if 1 == len(local) {
60
-		lport, err := strconv.Atoi(local[0])
61
-		if nil != err {
62
-			usage()
63
-			os.Exit(0)
64
-		}
65
-		opts.LocalPort = lport
62
+	if "-" == localstr || "|" == localstr {
63
+		// User may specify stdin/stdout instead of net
64
+		opts.LocalAddress = localstr
65
+		opts.LocalPort = -1
66 66
 	} else {
67
-		lport, err := strconv.Atoi(local[1])
68
-		if nil != err {
69
-			usage()
70
-			os.Exit(0)
67
+		// Test that argument is a local address
68
+		local := strings.Split(localstr, ":")
69
+
70
+		if 1 == len(local) {
71
+			lport, err := strconv.Atoi(local[0])
72
+			if nil != err {
73
+				usage()
74
+				os.Exit(0)
75
+			}
76
+			opts.LocalPort = lport
77
+		} else {
78
+			lport, err := strconv.Atoi(local[1])
79
+			if nil != err {
80
+				usage()
81
+				os.Exit(0)
82
+			}
83
+			opts.LocalAddress = local[0]
84
+			opts.LocalPort = lport
71 85
 		}
72
-		opts.LocalAddress = local[0]
73
-		opts.LocalPort = lport
74 86
 	}
75 87
 
76 88
 	sclient := &Sclient{}
77 89
 	err := sclient.DialAndListen(opts)
78 90
 	if nil != err {
79
-		usage()
80
-		os.Exit(0)
91
+		fmt.Fprintf(os.Stderr, "%s\n", err)
92
+		//usage()
93
+		//os.Exit(6)
81 94
 	}
82 95
 }

+ 55
- 5
sclient.go View File

@@ -10,6 +10,36 @@ import (
10 10
 	"strings"
11 11
 )
12 12
 
13
+// I wonder if I can get this to exactly mirror UnixAddr without passing it in
14
+type stdaddr struct {
15
+	net.UnixAddr
16
+}
17
+
18
+type stdnet struct {
19
+	in   *os.File // os.Stdin
20
+	out  *os.File // os.Stdout
21
+	addr *stdaddr
22
+}
23
+
24
+func (rw *stdnet) Read(buf []byte) (n int, err error) {
25
+	return rw.in.Read(buf)
26
+}
27
+func (rw *stdnet) Write(buf []byte) (n int, err error) {
28
+	return rw.out.Write(buf)
29
+}
30
+func (rw *stdnet) Close() error {
31
+	return rw.in.Close()
32
+}
33
+func (rw *stdnet) RemoteAddr() net.Addr {
34
+	return rw.addr
35
+}
36
+
37
+// not all of net.Conn, just RWC and RemoteAddr()
38
+type Rwc interface {
39
+	io.ReadWriteCloser
40
+	RemoteAddr() net.Addr
41
+}
42
+
13 43
 type SclientOpts struct {
14 44
 	RemoteAddress      string
15 45
 	RemotePort         int
@@ -20,13 +50,13 @@ type SclientOpts struct {
20 50
 
21 51
 type Sclient struct{}
22 52
 
23
-func pipe(r net.Conn, w net.Conn, t string) {
53
+func pipe(r Rwc, w Rwc, t string) {
24 54
 	buffer := make([]byte, 2048)
25 55
 	for {
26 56
 		done := false
27 57
 		// NOTE: count may be > 0 even if there's an err
28
-		count, err := r.Read(buffer)
29 58
 		//fmt.Fprintf(os.Stdout, "[debug] (%s) reading\n", t)
59
+		count, err := r.Read(buffer)
30 60
 		if nil != err {
31 61
 			//fmt.Fprintf(os.Stdout, "[debug] (%s:%d) error reading %s\n", t, count, err)
32 62
 			if io.EOF != err {
@@ -56,7 +86,7 @@ func pipe(r net.Conn, w net.Conn, t string) {
56 86
 	}
57 87
 }
58 88
 
59
-func handleConnection(remote string, conn net.Conn, opts *SclientOpts) {
89
+func handleConnection(remote string, conn Rwc, opts *SclientOpts) {
60 90
 	sclient, err := tls.Dial("tcp", remote,
61 91
 		&tls.Config{InsecureSkipVerify: opts.InsecureSkipVerify})
62 92
 
@@ -66,8 +96,13 @@ func handleConnection(remote string, conn net.Conn, opts *SclientOpts) {
66 96
 		return
67 97
 	}
68 98
 
69
-	fmt.Fprintf(os.Stdout, "[connect] %s => %s:%d\n",
70
-		strings.Replace(conn.RemoteAddr().String(), "[::1]:", "localhost:", 1), opts.RemoteAddress, opts.RemotePort)
99
+	if "stdio" == conn.RemoteAddr().Network() {
100
+		fmt.Fprintf(os.Stdout, "(connected to %s:%d and reading from %s)\n",
101
+			opts.RemoteAddress, opts.RemotePort, conn.RemoteAddr().String())
102
+	} else {
103
+		fmt.Fprintf(os.Stdout, "[connect] %s => %s:%d\n",
104
+			strings.Replace(conn.RemoteAddr().String(), "[::1]:", "localhost:", 1), opts.RemoteAddress, opts.RemotePort)
105
+	}
71 106
 
72 107
 	go pipe(conn, sclient, "local")
73 108
 	pipe(sclient, conn, "remote")
@@ -84,6 +119,21 @@ func (*Sclient) DialAndListen(opts *SclientOpts) error {
84 119
 		conn.Close()
85 120
 	}
86 121
 
122
+	// use stdin/stdout
123
+	if "-" == opts.LocalAddress || "|" == opts.LocalAddress {
124
+		var name string
125
+		network := "stdio"
126
+		if "|" == opts.LocalAddress {
127
+			name = "pipe"
128
+		} else {
129
+			name = "stdin"
130
+		}
131
+		conn := &stdnet{os.Stdin, os.Stdout, &stdaddr{net.UnixAddr{name, network}}}
132
+		handleConnection(remote, conn, opts)
133
+		return nil
134
+	}
135
+
136
+	// use net.Conn
87 137
 	local := opts.LocalAddress + ":" + strconv.Itoa(opts.LocalPort)
88 138
 	ln, err := net.Listen("tcp", local)
89 139
 	if err != nil {

+ 5
- 0
tests/get.bin View File

@@ -0,0 +1,5 @@
1
+GET / HTTP/1.1
2
+Host: telebit.cloud
3
+Connection: close
4
+
5
+

+ 8
- 0
tests/localhost.sh View File

@@ -0,0 +1,8 @@
1
+#!/bin/bash
2
+
3
+go run -race sclient*.go telebit.cloud:443 localhost:3000 &
4
+my_pid=$!
5
+sleep 5
6
+
7
+netcat localhost 3000 < tests/get.bin
8
+kill $my_pid

+ 3
- 0
tests/pipe.sh View File

@@ -0,0 +1,3 @@
1
+#!/bin/bash
2
+
3
+cat tests/get.bin | go run -race sclient*.go telebit.cloud:443

+ 3
- 0
tests/stdin.sh View File

@@ -0,0 +1,3 @@
1
+#!/bin/bash
2
+
3
+go run -race sclient*.go telebit.cloud:443 - < ./tests/get.bin