@ -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) | |||
} | |||
} |