1
0
mirror of https://github.com/therootcompany/sclient synced 2024-11-16 17:09:00 +00:00

Compare commits

...

9 Commits

Author SHA1 Message Date
8ba42a09a4 silence more output, fix minor output bugs 2021-06-21 23:22:34 -06:00
66f2d41c6b -q => -s (to be curl-like), make staticcheck happy 2021-06-21 23:05:27 -06:00
Kevin Chung
ca84ed48de Add -quiet and -q flags 2021-06-17 15:27:21 -04:00
455db50928 ignore builds 2020-12-02 15:05:10 -07:00
5b0374f2e9 add version 2020-12-02 15:04:58 -07:00
af639f0b2e update build / install instructions 2020-12-02 14:23:07 -07:00
828344802b add .goreleaser.yml 2020-12-02 14:05:25 -07:00
5334a377a4 doc updates 2019-05-21 19:25:06 -06:00
959268bf31 doc updates 2019-05-21 19:15:43 -06:00
8 changed files with 234 additions and 154 deletions

3
.gitignore vendored
View File

@ -1 +1,4 @@
/sclient
/cmd/sclient/sclient
dist dist

37
.goreleaser.yml Normal file
View File

@ -0,0 +1,37 @@
before:
hooks:
- go mod download
- go generate ./...
builds:
- main: ./cmd/sclient/main.go
env:
- CGO_ENABLED=0
goos:
- linux
- windows
- darwin
goarch:
- 386
- amd64
- arm
- arm64
goarm:
- 6
- 7
archives:
- replacements:
386: i386
amd64: x86_64
format_overrides:
- goos: windows
format: zip
checksum:
name_template: 'checksums.txt'
snapshot:
name_template: "{{ .Tag }}-next"
changelog:
sort: asc
filters:
exclude:
- '^docs:'
- '^test:'

114
README.md
View File

@ -1,5 +1,4 @@
sclient.go # sclient
==========
Secure Client for exposing TLS (aka SSL) secured services as plain-text connections locally. Secure Client for exposing TLS (aka SSL) secured services as plain-text connections locally.
@ -8,81 +7,75 @@ Also ideal for multiplexing a single port with multiple protocols using SNI.
Unwrap a TLS connection: Unwrap a TLS connection:
```bash ```bash
$ sclient whatever.com:443 localhost:3000 sclient whatever.com:443 localhost:3000
> [listening] whatever.com:443 <= localhost:3000 > [listening] whatever.com:443 <= localhost:3000
``` ```
Connect via Telnet Connect via Telnet
```bash ```bash
$ telnet localhost 3000 telnet localhost 3000
``` ```
Connect via netcat (nc) Connect via netcat (nc)
```bash ```bash
$ nc localhost 3000 nc localhost 3000
``` ```
cURL cURL
```bash ```bash
$ curl http://localhost:3000 -H 'Host: whatever.com' curl http://localhost:3000 -H 'Host: whatever.com'
``` ```
A poor man's (or Windows user's) makeshift replacement for `openssl s_client`, `stunnel`, or `socat`. A poor man's (or Windows user's) makeshift replacement for `openssl s_client`, `stunnel`, or `socat`.
Install # Table of Contents
=======
- [Install](#install)
- [Usage](#usage)
- [Examples](#examples)
- [Build from Source](#build-from-source)
# Install
### Mac, Linux
```bash
curl -sS https://webinstall.dev/sclient | bash
```
```bash
curl.exe -A MS https://webinstall.dev/sclient | powershell
```
### Downloads ### Downloads
* [Windows 10](https://telebit.cloud/sclient/dist/windows/amd64/sclient.exe) Check the [Github Releases](https://github.com/therootcompany/sclient/releases) for
* [Mac OS X](https://telebit.cloud/sclient/dist/darwin/amd64/sclient)
* [Linux (x64)](https://telebit.cloud/sclient/dist/linux/amd64/sclient)
* [Raspberry Pi (armv7)](https://telebit.cloud/sclient/dist/linux/armv7/sclient)
* more downloads <https://telebit.cloud/sclient/>
### Build from source - macOS (x64) Apple Silicon [coming soon](https://github.com/golang/go/issues/39782)
- Linux (x64, i386, arm64, arm6, arm7)
- Windows 10 (x64, i386)
For the moment you'll have to install go and compile `sclient` yourself: # Usage
* <https://golang.org/doc/install#install>
```bash
git clone https://git.rootprojects.org/root/sclient.go.git
pushd sclient.go
go build -o dist/sclient cmd/sclient/main.go
rsync -av dist/sclient /usr/local/bin/sclient
sclient example.com:443 localhost:3000
```
Or
```bash
go get git.rootprojects.org/root/sclient.go/cmd/sclient
go run git.rootprojects.org/root/sclient.go/cmd/sclient example.com:443 localhost:3000
```
Usage
=====
```bash ```bash
sclient [flags] <remote> <local> sclient [flags] <remote> <local>
``` ```
* flags - flags
* -k, --insecure ignore invalid TLS (SSL/HTTPS) certificates - -k, --insecure ignore invalid TLS (SSL/HTTPS) certificates
* --servername <string> spoof SNI (to disable use IP as &lt;remote&gt; and do not use this option) - --servername <string> spoof SNI (to disable use IP as &lt;remote&gt; and do not use this option)
* remote - remote
* must have servername (i.e. example.com) - must have servername (i.e. example.com)
* port is optional (default is 443) - port is optional (default is 443)
* local - local
* address is optional (default is localhost) - address is optional (default is localhost)
* must have port (i.e. 3000) - must have port (i.e. 3000)
Examples # Examples
========
Bridge between `telebit.cloud` and local port `3000`. Bridge between `telebit.cloud` and local port `3000`.
@ -127,3 +120,32 @@ sclient --servername "Robert'); DROP TABLE Students;" -k example.com localhost:3
```bash ```bash
sclient --servername "../../../.hidden/private.txt" -k example.com localhost:3000 sclient --servername "../../../.hidden/private.txt" -k example.com localhost:3000
``` ```
# Build from source
You'll need to install [Go](https://golang.org).
See [webinstall.dev/golang](https://webinstall.dev/golang) for install instructions.
```bash
curl -sS https://webinstall.dev/golang | bash
```
Then you can install and run as per usual.
```bash
git clone https://git.rootprojects.org/root/sclient.go.git
pushd sclient.go
go build -o dist/sclient cmd/sclient/main.go
sudo rsync -av dist/sclient /usr/local/bin/sclient
popd
sclient example.com:443 localhost:3000
```
## Install or Run with Go
```bash
go get git.rootprojects.org/root/sclient.go/cmd/sclient
go run git.rootprojects.org/root/sclient.go/cmd/sclient example.com:443 localhost:3000
```

View File

@ -1,33 +0,0 @@
#GOOS=windows GOARCH=amd64 go install
go tool dist list
gocmd=cmd/sclient/main.go
golib=""
echo ""
echo ""
echo "Windows amd64"
GOOS=windows GOARCH=amd64 go build -o dist/windows/amd64/sclient.exe $gocmd $golib
echo "Windows 386"
GOOS=windows GOARCH=386 go build -o dist/windows/386/sclient.exe $gocmd $golib
echo ""
echo "Darwin (macOS) amd64"
GOOS=darwin GOARCH=amd64 go build -o dist/darwin/amd64/sclient $gocmd $golib
echo ""
echo "Linux amd64"
GOOS=linux GOARCH=amd64 go build -o dist/linux/amd64/sclient $gocmd $golib
echo "Linux 386"
echo ""
GOOS=linux GOARCH=386 go build -o dist/linux/386/sclient $gocmd $golib
echo "RPi 3 B+ ARMv7"
GOOS=linux GOARCH=arm GOARM=7 go build -o dist/linux/armv7/sclient $gocmd $golib
echo "RPi Zero ARMv5"
GOOS=linux GOARCH=arm GOARM=5 go build -o dist/linux/armv5/sclient $gocmd $golib
echo ""
echo ""
rsync -av ./dist/ root@telebit.cloud:/opt/telebit-relay/lib/extensions/admin/sclient/dist/

View File

@ -10,23 +10,49 @@ import (
sclient "git.rootprojects.org/root/sclient.go" sclient "git.rootprojects.org/root/sclient.go"
) )
var (
// commit refers to the abbreviated commit hash
commit = "0000000"
// version refers to the most recent tag, plus any commits made since then
version = "v0.0.0-pre0+0000000"
// GitTimestamp refers to the timestamp of the most recent commit
date = "0000-00-00T00:00:00+0000"
)
func ver() string {
return fmt.Sprintf("sclient %s (%s) %s", version, commit[:7], date)
}
func usage() { func usage() {
fmt.Fprintf(os.Stderr, "\nusage: sclient <remote> <local>\n"+ fmt.Fprintf(os.Stderr, "\n%s\n"+
"\nusage: sclient <remote> <local>\n"+
"\n"+ "\n"+
" ex: sclient example.com 3000\n"+ " ex: sclient example.com 3000\n"+
" (sclient example.com:443 localhost:3000)\n"+ " (sclient example.com:443 localhost:3000)\n"+
"\n"+ "\n"+
" ex: sclient example.com:8443 0.0.0.0:4080\n"+ " ex: sclient example.com:8443 0.0.0.0:4080\n"+
"\n") "\n"+
" ex: sclient example.com:443 -\n"+
"\n", ver())
flag.PrintDefaults() flag.PrintDefaults()
fmt.Println() fmt.Println()
} }
func main() { func main() {
if len(os.Args) >= 2 {
if "version" == strings.TrimLeft(os.Args[1], "-") {
fmt.Printf("%s\n", ver())
os.Exit(0)
return
}
}
flag.Usage = usage flag.Usage = usage
insecure := flag.Bool("k", false, "ignore bad TLS/SSL/HTTPS certificates") insecure := flag.Bool("k", false, "alias for --insecure")
silent := flag.Bool("s", false, "alias of --silent")
servername := flag.String("servername", "", "specify a servername different from <remote> (to disable SNI use an IP as <remote> and do use this option)") servername := flag.String("servername", "", "specify a servername different from <remote> (to disable SNI use an IP as <remote> and do use this option)")
flag.BoolVar(insecure, "insecure", false, "ignore bad TLS/SSL/HTTPS certificates") flag.BoolVar(insecure, "insecure", false, "ignore bad TLS/SSL/HTTPS certificates")
flag.BoolVar(silent, "silent", false, "less verbose output")
flag.Parse() flag.Parse()
remotestr := flag.Arg(0) remotestr := flag.Arg(0)
localstr := flag.Arg(1) localstr := flag.Arg(1)
@ -47,6 +73,7 @@ func main() {
LocalAddress: "localhost", LocalAddress: "localhost",
InsecureSkipVerify: *insecure, InsecureSkipVerify: *insecure,
ServerName: *servername, ServerName: *servername,
Silent: *silent,
} }
remote := strings.Split(remotestr, ":") remote := strings.Split(remotestr, ":")

21
doc.go
View File

@ -1,24 +1,21 @@
/* /*
Package sclient unwraps SSL. sclient unwraps SSL.
It makes secure remote connections (such as HTTPS) available locally as plain-text connections - It makes secure remote connections (such as HTTPS) available locally as plain-text connections -
similar to `stunnel` or `openssl s_client`. similar to `stunnel` or `openssl s_client`.
There are a variety of reasons that you might want to do that, There are a variety of reasons that you might want to do that,
but we created it specifically to be able to upgrade applications with legacy but we created it specifically to be able to upgrade applications with legacy
security protocols - like SSH, OpenVPN, and Postgres - to be able to take security protocols - like SSH, OpenVPN, and Postgres - to take
advantage of the features modern TLS, such as ALPN and SNI advantage of the features of modern TLS, such as ALPN and SNI
(which makes them routable through almost every type of firewall). (which makes them routable through almost every type of firewall).
See https://telebit.cloud/sclient for more info. See https://telebit.cloud/sclient for more info.
Try the CLI
go get git.rootprojects.org/root/sclient.go/cmd/sclient
go run git.rootprojects.org/root/sclient.go/cmd/sclient example.com:443 localhost:3000
Package Basics Package Basics
In the simplest case you'll just be setting a ServerName and connection info:
servername := "example.com" servername := "example.com"
sclient := &sclient.Tunnel{ sclient := &sclient.Tunnel{
@ -31,6 +28,14 @@ Package Basics
err := sclient.DialAndListen() err := sclient.DialAndListen()
Try the CLI
If you'd like to better understand what sclient does, you can try it out with `go run`:
go get git.rootprojects.org/root/sclient.go/cmd/sclient
go run git.rootprojects.org/root/sclient.go/cmd/sclient example.com:443 localhost:3000
curl http://localhost:3000 -H "Host: example.com"
Pre-built versions for various platforms are also available at Pre-built versions for various platforms are also available at
https://telebit.cloud/sclient https://telebit.cloud/sclient

View File

@ -10,6 +10,69 @@ import (
"strings" "strings"
) )
// Tunnel specifies which remote encrypted connection to make available as a plain connection locally.
type Tunnel struct {
RemoteAddress string
RemotePort int
LocalAddress string
LocalPort int
InsecureSkipVerify bool
ServerName string
Silent bool
}
// DialAndListen will create a test TLS connection to the remote address and then
// begin listening locally. Each local connection will result in a separate remote connection.
func (t *Tunnel) DialAndListen() error {
remote := t.RemoteAddress + ":" + strconv.Itoa(t.RemotePort)
conn, err := tls.Dial("tcp", remote,
&tls.Config{
ServerName: t.ServerName,
InsecureSkipVerify: t.InsecureSkipVerify,
})
if err != nil {
fmt.Fprintf(os.Stderr, "[warn] '%s' may not be accepting connections: %s\n", remote, err)
} else {
conn.Close()
}
// use stdin/stdout
if "-" == t.LocalAddress || "|" == t.LocalAddress {
var name string
network := "stdio"
if "|" == t.LocalAddress {
name = "pipe"
} else {
name = "stdin"
}
conn := &stdnet{os.Stdin, os.Stdout, &stdaddr{net.UnixAddr{Name: name, Net: network}}}
t.handleConnection(remote, conn)
return nil
}
// use net.Conn
local := t.LocalAddress + ":" + strconv.Itoa(t.LocalPort)
ln, err := net.Listen("tcp", local)
if err != nil {
return err
}
if !t.Silent {
fmt.Fprintf(os.Stdout, "[listening] %s:%d <= %s:%d\n",
t.RemoteAddress, t.RemotePort, t.LocalAddress, t.LocalPort)
}
for {
conn, err := ln.Accept()
if nil != err {
fmt.Fprintf(os.Stderr, "[error] %s\n", err)
continue
}
go t.handleConnection(remote, conn)
}
}
// I wonder if I can get this to exactly mirror UnixAddr without passing it in // I wonder if I can get this to exactly mirror UnixAddr without passing it in
type stdaddr struct { type stdaddr struct {
net.UnixAddr net.UnixAddr
@ -40,15 +103,6 @@ type netReadWriteCloser interface {
RemoteAddr() net.Addr RemoteAddr() net.Addr
} }
type Tunnel struct {
RemoteAddress string
RemotePort int
LocalAddress string
LocalPort int
InsecureSkipVerify bool
ServerName string
}
func pipe(r netReadWriteCloser, w netReadWriteCloser, t string) { func pipe(r netReadWriteCloser, w netReadWriteCloser, t string) {
buffer := make([]byte, 2048) buffer := make([]byte, 2048)
for { for {
@ -59,7 +113,7 @@ func pipe(r netReadWriteCloser, w netReadWriteCloser, t string) {
if nil != err { if nil != err {
//fmt.Fprintf(os.Stdout, "[debug] (%s:%d) error reading %s\n", t, count, err) //fmt.Fprintf(os.Stdout, "[debug] (%s:%d) error reading %s\n", t, count, err)
if io.EOF != err { if io.EOF != err {
fmt.Fprintf(os.Stderr, "[read error] (%s:%s) %s\n", t, count, err) fmt.Fprintf(os.Stderr, "[read error] (%s:%d) %s\n", t, count, err)
} }
r.Close() r.Close()
//w.Close() //w.Close()
@ -98,62 +152,16 @@ func (t *Tunnel) handleConnection(remote string, conn netReadWriteCloser) {
return return
} }
if "stdio" == conn.RemoteAddr().Network() { if !t.Silent {
fmt.Fprintf(os.Stdout, "(connected to %s:%d and reading from %s)\n", if "stdio" == conn.RemoteAddr().Network() {
t.RemoteAddress, t.RemotePort, conn.RemoteAddr().String()) fmt.Fprintf(os.Stdout, "(connected to %s:%d and reading from %s)\n",
} else { t.RemoteAddress, t.RemotePort, conn.RemoteAddr().String())
fmt.Fprintf(os.Stdout, "[connect] %s => %s:%d\n", } else {
strings.Replace(conn.RemoteAddr().String(), "[::1]:", "localhost:", 1), t.RemoteAddress, t.RemotePort) fmt.Fprintf(os.Stdout, "[connect] %s => %s:%d\n",
strings.Replace(conn.RemoteAddr().String(), "[::1]:", "localhost:", 1), t.RemoteAddress, t.RemotePort)
}
} }
go pipe(conn, sclient, "local") go pipe(conn, sclient, "local")
pipe(sclient, conn, "remote") pipe(sclient, conn, "remote")
} }
func (t *Tunnel) DialAndListen() error {
remote := t.RemoteAddress + ":" + strconv.Itoa(t.RemotePort)
conn, err := tls.Dial("tcp", remote,
&tls.Config{
ServerName: t.ServerName,
InsecureSkipVerify: t.InsecureSkipVerify,
})
if err != nil {
fmt.Fprintf(os.Stderr, "[warn] '%s' may not be accepting connections: %s\n", remote, err)
} else {
conn.Close()
}
// use stdin/stdout
if "-" == t.LocalAddress || "|" == t.LocalAddress {
var name string
network := "stdio"
if "|" == t.LocalAddress {
name = "pipe"
} else {
name = "stdin"
}
conn := &stdnet{os.Stdin, os.Stdout, &stdaddr{net.UnixAddr{name, network}}}
t.handleConnection(remote, conn)
return nil
}
// use net.Conn
local := t.LocalAddress + ":" + strconv.Itoa(t.LocalPort)
ln, err := net.Listen("tcp", local)
if err != nil {
return err
}
fmt.Fprintf(os.Stdout, "[listening] %s:%d <= %s:%d\n",
t.RemoteAddress, t.RemotePort, t.LocalAddress, t.LocalPort)
for {
conn, err := ln.Accept()
if nil != err {
fmt.Fprintf(os.Stderr, "[error] %s\n", err)
continue
}
go t.handleConnection(remote, conn)
}
}

11
staticcheck.conf Normal file
View File

@ -0,0 +1,11 @@
# I like my yoda conditions ST1017
checks = ["all", "-ST1017", "-ST1000", "-ST1003", "-ST1016", "-ST1020", "-ST1021", "-ST1022", "-ST1023"]
initialisms = ["ACL", "API", "ASCII", "CPU", "CSS", "DNS",
"EOF", "GUID", "HTML", "HTTP", "HTTPS", "ID",
"IP", "JSON", "QPS", "RAM", "RPC", "SLA",
"SMTP", "SQL", "SSH", "TCP", "TLS", "TTL",
"UDP", "UI", "GID", "UID", "UUID", "URI",
"URL", "UTF8", "VM", "XML", "XMPP", "XSRF",
"XSS", "SIP", "RTP", "AMQP", "DB", "TS"]
dot_import_whitelist = []
http_status_code_whitelist = ["200", "400", "404", "500"]