v1.0.0: first working version
This commit is contained in:
commit
224d005a90
|
@ -0,0 +1,41 @@
|
||||||
|
Copyright 2018 AJ ONeal
|
||||||
|
|
||||||
|
This is open source software; you can redistribute it and/or modify it under the
|
||||||
|
terms of either:
|
||||||
|
|
||||||
|
a) the "MIT License"
|
||||||
|
b) the "Apache-2.0 License"
|
||||||
|
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
|
Apache-2.0 License Summary
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
|
@ -0,0 +1,81 @@
|
||||||
|
sclient.go
|
||||||
|
==========
|
||||||
|
|
||||||
|
Secure Client for exposing TLS (aka SSL) secured services as plain-text connections locally.
|
||||||
|
|
||||||
|
Also ideal for multiplexing a single port with multiple protocols using SNI.
|
||||||
|
|
||||||
|
Unwrap a TLS connection:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ sclient whatever.com:443 localhost:3000
|
||||||
|
> [listening] telebit.cloud:443 <= localhost:3000
|
||||||
|
```
|
||||||
|
|
||||||
|
Connect via Telnet
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ telnet localhost 3000
|
||||||
|
```
|
||||||
|
|
||||||
|
Connect via netcat (nc)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ nc localhost 3000
|
||||||
|
```
|
||||||
|
|
||||||
|
A poor man's (or Windows user's) makeshift replacement for `openssl s_client`, `stunnel`, or `socat`.
|
||||||
|
|
||||||
|
Install
|
||||||
|
=======
|
||||||
|
|
||||||
|
### macOS, Linux, Windows
|
||||||
|
|
||||||
|
For the moment you'll have to install go and compile `sclient` yourself:
|
||||||
|
|
||||||
|
* <https://golang.org/doc/install#install>
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone
|
||||||
|
go build sclient*.go
|
||||||
|
rsync -av sclient-cli /usr/local/bin/sclient
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go run sclient*.go example.com:443 localhost:3000
|
||||||
|
```
|
||||||
|
|
||||||
|
Usage
|
||||||
|
=====
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sclient <remote> <local> [-k | --insecure]
|
||||||
|
```
|
||||||
|
|
||||||
|
* remote
|
||||||
|
* must have servername (i.e. example.com)
|
||||||
|
* port is optional (default is 443)
|
||||||
|
* local
|
||||||
|
* address is optional (default is localhost)
|
||||||
|
* must have port (i.e. 3000)
|
||||||
|
|
||||||
|
Examples
|
||||||
|
========
|
||||||
|
|
||||||
|
Bridge between `telebit.cloud` and local port `3000`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sclient telebit.cloud 3000
|
||||||
|
```
|
||||||
|
|
||||||
|
Same as above, but more explicit
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sclient telebit.cloud:443 localhost:3000
|
||||||
|
```
|
||||||
|
|
||||||
|
Ignore a bad TLS/SSL/HTTPS certificate and connect anyway.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sclient badtls.telebit.cloud:443 localhost:3000 -k
|
||||||
|
```
|
|
@ -0,0 +1,82 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func usage() {
|
||||||
|
fmt.Fprintf(os.Stderr, "\nusage: go run sclient*.go <remote> <local>\n"+
|
||||||
|
"\n"+
|
||||||
|
" ex: sclient example.com 3000\n"+
|
||||||
|
" (sclient example.com:443 localhost:3000)\n"+
|
||||||
|
"\n"+
|
||||||
|
" ex: sclient example.com:8443 0.0.0.0:4080\n"+
|
||||||
|
"\n")
|
||||||
|
flag.PrintDefaults()
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Usage = usage
|
||||||
|
insecure := flag.Bool("k", false, "ignore bad TLS/SSL/HTTPS certificates")
|
||||||
|
flag.BoolVar(insecure, "insecure", false, "ignore bad TLS/SSL/HTTPS certificates")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
// NArg, Arg, Args
|
||||||
|
i := flag.NArg()
|
||||||
|
if 2 != i {
|
||||||
|
usage()
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := &SclientOpts{}
|
||||||
|
opts.RemotePort = 443
|
||||||
|
opts.LocalAddress = "localhost"
|
||||||
|
opts.InsecureSkipVerify = *insecure
|
||||||
|
|
||||||
|
remote := strings.Split(flag.Arg(0), ":")
|
||||||
|
//remoteAddr, remotePort, err := net.SplitHostPort(flag.Arg(0))
|
||||||
|
if 2 == len(remote) {
|
||||||
|
rport, err := strconv.Atoi(remote[1])
|
||||||
|
if nil != err {
|
||||||
|
usage()
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
opts.RemotePort = rport
|
||||||
|
} else if 1 != len(remote) {
|
||||||
|
usage()
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
opts.RemoteAddress = remote[0]
|
||||||
|
|
||||||
|
local := strings.Split(flag.Arg(1), ":")
|
||||||
|
//localAddr, localPort, err := net.SplitHostPort(flag.Arg(0))
|
||||||
|
|
||||||
|
if 1 == len(local) {
|
||||||
|
lport, err := strconv.Atoi(local[0])
|
||||||
|
if nil != err {
|
||||||
|
usage()
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
opts.LocalPort = lport
|
||||||
|
} else {
|
||||||
|
lport, err := strconv.Atoi(local[1])
|
||||||
|
if nil != err {
|
||||||
|
usage()
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
opts.LocalAddress = local[0]
|
||||||
|
opts.LocalPort = lport
|
||||||
|
}
|
||||||
|
|
||||||
|
sclient := &Sclient{}
|
||||||
|
err := sclient.DialAndListen(opts)
|
||||||
|
if nil != err {
|
||||||
|
usage()
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,104 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SclientOpts struct {
|
||||||
|
RemoteAddress string
|
||||||
|
RemotePort int
|
||||||
|
LocalAddress string
|
||||||
|
LocalPort int
|
||||||
|
InsecureSkipVerify bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type Sclient struct{}
|
||||||
|
|
||||||
|
func pipe(r net.Conn, w net.Conn, t string) {
|
||||||
|
buffer := make([]byte, 2048)
|
||||||
|
for {
|
||||||
|
done := false
|
||||||
|
// NOTE: count may be > 0 even if there's an err
|
||||||
|
count, err := r.Read(buffer)
|
||||||
|
//fmt.Fprintf(os.Stdout, "[debug] (%s) reading\n", t)
|
||||||
|
if nil != err {
|
||||||
|
//fmt.Fprintf(os.Stdout, "[debug] (%s:%d) error reading %s\n", t, count, err)
|
||||||
|
if io.EOF != err {
|
||||||
|
fmt.Fprintf(os.Stderr, "[read error] (%s:%s) %s\n", t, count, err)
|
||||||
|
}
|
||||||
|
r.Close()
|
||||||
|
//w.Close()
|
||||||
|
done = true
|
||||||
|
}
|
||||||
|
if 0 == count {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
_, err = w.Write(buffer[:count])
|
||||||
|
if nil != err {
|
||||||
|
//fmt.Fprintf(os.Stdout, "[debug] %s error writing\n", t)
|
||||||
|
if io.EOF != err {
|
||||||
|
fmt.Fprintf(os.Stderr, "[write error] (%s) %s\n", t, err)
|
||||||
|
}
|
||||||
|
// TODO handle error closing?
|
||||||
|
r.Close()
|
||||||
|
//w.Close()
|
||||||
|
done = true
|
||||||
|
}
|
||||||
|
if done {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleConnection(remote string, conn net.Conn, opts *SclientOpts) {
|
||||||
|
sclient, err := tls.Dial("tcp", remote,
|
||||||
|
&tls.Config{InsecureSkipVerify: opts.InsecureSkipVerify})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "[error] (remote) %s\n", err)
|
||||||
|
conn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stdout, "[connect] %s => %s:%d\n",
|
||||||
|
strings.Replace(conn.RemoteAddr().String(), "[::1]:", "localhost:", 1), opts.RemoteAddress, opts.RemotePort)
|
||||||
|
|
||||||
|
go pipe(conn, sclient, "local")
|
||||||
|
pipe(sclient, conn, "remote")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Sclient) DialAndListen(opts *SclientOpts) error {
|
||||||
|
remote := opts.RemoteAddress + ":" + strconv.Itoa(opts.RemotePort)
|
||||||
|
conn, err := tls.Dial("tcp", remote,
|
||||||
|
&tls.Config{InsecureSkipVerify: opts.InsecureSkipVerify})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "[warn] '%s' may not be accepting connections: %s\n", remote, err)
|
||||||
|
} else {
|
||||||
|
conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
local := opts.LocalAddress + ":" + strconv.Itoa(opts.LocalPort)
|
||||||
|
ln, err := net.Listen("tcp", local)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stdout, "[listening] %s:%d <= %s:%d\n",
|
||||||
|
opts.RemoteAddress, opts.RemotePort, opts.LocalAddress, opts.LocalPort)
|
||||||
|
|
||||||
|
for {
|
||||||
|
conn, err := ln.Accept()
|
||||||
|
if nil != err {
|
||||||
|
fmt.Fprintf(os.Stderr, "[error] %s\n", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
go handleConnection(remote, conn, opts)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue