AJ ONeal
6 years ago
commit
224d005a90
4 changed files with 308 additions and 0 deletions
@ -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