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