Browse Source

v1.0.0: first working version

AJ ONeal 4 months ago
commit
224d005a90
4 changed files with 308 additions and 0 deletions
  1. 41
    0
      LICENSE
  2. 81
    0
      README.md
  3. 82
    0
      sclient-cli.go
  4. 104
    0
      sclient.go

+ 41
- 0
LICENSE View File

@@ -0,0 +1,41 @@
1
+Copyright 2018 AJ ONeal
2
+
3
+This is open source software; you can redistribute it and/or modify it under the
4
+terms of either:
5
+
6
+   a) the "MIT License"
7
+   b) the "Apache-2.0 License"
8
+
9
+MIT License
10
+
11
+   Permission is hereby granted, free of charge, to any person obtaining a copy
12
+   of this software and associated documentation files (the "Software"), to deal
13
+   in the Software without restriction, including without limitation the rights
14
+   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15
+   copies of the Software, and to permit persons to whom the Software is
16
+   furnished to do so, subject to the following conditions:
17
+
18
+   The above copyright notice and this permission notice shall be included in all
19
+   copies or substantial portions of the Software.
20
+
21
+   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
+   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
+   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
+   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
+   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
+   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27
+   SOFTWARE.
28
+
29
+Apache-2.0 License Summary
30
+
31
+   Licensed under the Apache License, Version 2.0 (the "License");
32
+   you may not use this file except in compliance with the License.
33
+   You may obtain a copy of the License at
34
+
35
+     http://www.apache.org/licenses/LICENSE-2.0
36
+
37
+   Unless required by applicable law or agreed to in writing, software
38
+   distributed under the License is distributed on an "AS IS" BASIS,
39
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
40
+   See the License for the specific language governing permissions and
41
+   limitations under the License.

+ 81
- 0
README.md View File

@@ -0,0 +1,81 @@
1
+sclient.go
2
+==========
3
+
4
+Secure Client for exposing TLS (aka SSL) secured services as plain-text connections locally.
5
+
6
+Also ideal for multiplexing a single port with multiple protocols using SNI.
7
+
8
+Unwrap a TLS connection:
9
+
10
+```bash
11
+$ sclient whatever.com:443 localhost:3000
12
+> [listening] telebit.cloud:443 <= localhost:3000
13
+```
14
+
15
+Connect via Telnet
16
+
17
+```bash
18
+$ telnet localhost 3000
19
+```
20
+
21
+Connect via netcat (nc)
22
+
23
+```bash
24
+$ nc localhost 3000
25
+```
26
+
27
+A poor man's (or Windows user's) makeshift replacement for `openssl s_client`, `stunnel`, or `socat`.
28
+
29
+Install
30
+=======
31
+
32
+### macOS, Linux, Windows
33
+
34
+For the moment you'll have to install go and compile `sclient` yourself:
35
+
36
+* <https://golang.org/doc/install#install>
37
+
38
+```bash
39
+git clone
40
+go build sclient*.go
41
+rsync -av sclient-cli /usr/local/bin/sclient
42
+```
43
+
44
+```bash
45
+go run sclient*.go example.com:443 localhost:3000
46
+```
47
+
48
+Usage
49
+=====
50
+
51
+```bash
52
+sclient <remote> <local> [-k | --insecure]
53
+```
54
+
55
+* remote
56
+  * must have servername (i.e. example.com)
57
+  * port is optional (default is 443)
58
+* local
59
+  * address is optional (default is localhost)
60
+  * must have port (i.e. 3000)
61
+
62
+Examples
63
+========
64
+
65
+Bridge between `telebit.cloud` and local port `3000`.
66
+
67
+```bash
68
+sclient telebit.cloud 3000
69
+```
70
+
71
+Same as above, but more explicit
72
+
73
+```bash
74
+sclient telebit.cloud:443 localhost:3000
75
+```
76
+
77
+Ignore a bad TLS/SSL/HTTPS certificate and connect anyway.
78
+
79
+```bash
80
+sclient badtls.telebit.cloud:443 localhost:3000 -k
81
+```

+ 82
- 0
sclient-cli.go View File

@@ -0,0 +1,82 @@
1
+package main
2
+
3
+import (
4
+	"flag"
5
+	"fmt"
6
+	"os"
7
+	"strconv"
8
+	"strings"
9
+)
10
+
11
+func usage() {
12
+	fmt.Fprintf(os.Stderr, "\nusage: go run sclient*.go <remote> <local>\n"+
13
+		"\n"+
14
+		"   ex: sclient example.com 3000\n"+
15
+		"      (sclient example.com:443 localhost:3000)\n"+
16
+		"\n"+
17
+		"   ex: sclient example.com:8443 0.0.0.0:4080\n"+
18
+		"\n")
19
+	flag.PrintDefaults()
20
+	fmt.Println()
21
+}
22
+
23
+func main() {
24
+	flag.Usage = usage
25
+	insecure := flag.Bool("k", false, "ignore bad TLS/SSL/HTTPS certificates")
26
+	flag.BoolVar(insecure, "insecure", false, "ignore bad TLS/SSL/HTTPS certificates")
27
+	flag.Parse()
28
+
29
+	// NArg, Arg, Args
30
+	i := flag.NArg()
31
+	if 2 != i {
32
+		usage()
33
+		os.Exit(0)
34
+	}
35
+
36
+	opts := &SclientOpts{}
37
+	opts.RemotePort = 443
38
+	opts.LocalAddress = "localhost"
39
+	opts.InsecureSkipVerify = *insecure
40
+
41
+	remote := strings.Split(flag.Arg(0), ":")
42
+	//remoteAddr, remotePort, err := net.SplitHostPort(flag.Arg(0))
43
+	if 2 == len(remote) {
44
+		rport, err := strconv.Atoi(remote[1])
45
+		if nil != err {
46
+			usage()
47
+			os.Exit(0)
48
+		}
49
+		opts.RemotePort = rport
50
+	} else if 1 != len(remote) {
51
+		usage()
52
+		os.Exit(0)
53
+	}
54
+	opts.RemoteAddress = remote[0]
55
+
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
66
+	} else {
67
+		lport, err := strconv.Atoi(local[1])
68
+		if nil != err {
69
+			usage()
70
+			os.Exit(0)
71
+		}
72
+		opts.LocalAddress = local[0]
73
+		opts.LocalPort = lport
74
+	}
75
+
76
+	sclient := &Sclient{}
77
+	err := sclient.DialAndListen(opts)
78
+	if nil != err {
79
+		usage()
80
+		os.Exit(0)
81
+	}
82
+}

+ 104
- 0
sclient.go View File

@@ -0,0 +1,104 @@
1
+package main
2
+
3
+import (
4
+	"crypto/tls"
5
+	"fmt"
6
+	"io"
7
+	"net"
8
+	"os"
9
+	"strconv"
10
+	"strings"
11
+)
12
+
13
+type SclientOpts struct {
14
+	RemoteAddress      string
15
+	RemotePort         int
16
+	LocalAddress       string
17
+	LocalPort          int
18
+	InsecureSkipVerify bool
19
+}
20
+
21
+type Sclient struct{}
22
+
23
+func pipe(r net.Conn, w net.Conn, t string) {
24
+	buffer := make([]byte, 2048)
25
+	for {
26
+		done := false
27
+		// NOTE: count may be > 0 even if there's an err
28
+		count, err := r.Read(buffer)
29
+		//fmt.Fprintf(os.Stdout, "[debug] (%s) reading\n", t)
30
+		if nil != err {
31
+			//fmt.Fprintf(os.Stdout, "[debug] (%s:%d) error reading %s\n", t, count, err)
32
+			if io.EOF != err {
33
+				fmt.Fprintf(os.Stderr, "[read error] (%s:%s) %s\n", t, count, err)
34
+			}
35
+			r.Close()
36
+			//w.Close()
37
+			done = true
38
+		}
39
+		if 0 == count {
40
+			break
41
+		}
42
+		_, err = w.Write(buffer[:count])
43
+		if nil != err {
44
+			//fmt.Fprintf(os.Stdout, "[debug] %s error writing\n", t)
45
+			if io.EOF != err {
46
+				fmt.Fprintf(os.Stderr, "[write error] (%s) %s\n", t, err)
47
+			}
48
+			// TODO handle error closing?
49
+			r.Close()
50
+			//w.Close()
51
+			done = true
52
+		}
53
+		if done {
54
+			break
55
+		}
56
+	}
57
+}
58
+
59
+func handleConnection(remote string, conn net.Conn, opts *SclientOpts) {
60
+	sclient, err := tls.Dial("tcp", remote,
61
+		&tls.Config{InsecureSkipVerify: opts.InsecureSkipVerify})
62
+
63
+	if err != nil {
64
+		fmt.Fprintf(os.Stderr, "[error] (remote) %s\n", err)
65
+		conn.Close()
66
+		return
67
+	}
68
+
69
+	fmt.Fprintf(os.Stdout, "[connect] %s => %s:%d\n",
70
+		strings.Replace(conn.RemoteAddr().String(), "[::1]:", "localhost:", 1), opts.RemoteAddress, opts.RemotePort)
71
+
72
+	go pipe(conn, sclient, "local")
73
+	pipe(sclient, conn, "remote")
74
+}
75
+
76
+func (*Sclient) DialAndListen(opts *SclientOpts) error {
77
+	remote := opts.RemoteAddress + ":" + strconv.Itoa(opts.RemotePort)
78
+	conn, err := tls.Dial("tcp", remote,
79
+		&tls.Config{InsecureSkipVerify: opts.InsecureSkipVerify})
80
+
81
+	if err != nil {
82
+		fmt.Fprintf(os.Stderr, "[warn] '%s' may not be accepting connections: %s\n", remote, err)
83
+	} else {
84
+		conn.Close()
85
+	}
86
+
87
+	local := opts.LocalAddress + ":" + strconv.Itoa(opts.LocalPort)
88
+	ln, err := net.Listen("tcp", local)
89
+	if err != nil {
90
+		return err
91
+	}
92
+
93
+	fmt.Fprintf(os.Stdout, "[listening] %s:%d <= %s:%d\n",
94
+		opts.RemoteAddress, opts.RemotePort, opts.LocalAddress, opts.LocalPort)
95
+
96
+	for {
97
+		conn, err := ln.Accept()
98
+		if nil != err {
99
+			fmt.Fprintf(os.Stderr, "[error] %s\n", err)
100
+			continue
101
+		}
102
+		go handleConnection(remote, conn, opts)
103
+	}
104
+}