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

Compare commits

..

No commits in common. "master" and "v1.3.0" have entirely different histories.

8 changed files with 154 additions and 234 deletions

3
.gitignore vendored
View File

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

View File

@ -1,37 +0,0 @@
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,4 +1,5 @@
# sclient sclient.go
==========
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.
@ -7,75 +8,81 @@ 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`.
# Table of Contents Install
=======
- [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
Check the [Github Releases](https://github.com/therootcompany/sclient/releases) for * [Windows 10](https://telebit.cloud/sclient/dist/windows/amd64/sclient.exe)
* [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/>
- macOS (x64) Apple Silicon [coming soon](https://github.com/golang/go/issues/39782) ### Build from source
- Linux (x64, i386, arm64, arm6, arm7)
- Windows 10 (x64, i386)
# Usage For the moment you'll have to install go and compile `sclient` yourself:
* <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`.
@ -120,32 +127,3 @@ 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
```

33
build-all.sh Normal file
View File

@ -0,0 +1,33 @@
#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,49 +10,23 @@ 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, "\n%s\n"+ fmt.Fprintf(os.Stderr, "\nusage: sclient <remote> <local>\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, "alias for --insecure") insecure := flag.Bool("k", false, "ignore bad TLS/SSL/HTTPS certificates")
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)
@ -73,7 +47,6 @@ 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,20 +1,23 @@
/* /*
sclient unwraps SSL. Package 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 take security protocols - like SSH, OpenVPN, and Postgres - to be able to take
advantage of the features of modern TLS, such as ALPN and SNI advantage of the features 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.
Package Basics Try the CLI
In the simplest case you'll just be setting a ServerName and connection info: 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
servername := "example.com" servername := "example.com"
@ -28,14 +31,6 @@ In the simplest case you'll just be setting a ServerName and connection info:
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,69 +10,6 @@ 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
@ -103,6 +40,15 @@ 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 {
@ -113,7 +59,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:%d) %s\n", t, count, err) fmt.Fprintf(os.Stderr, "[read error] (%s:%s) %s\n", t, count, err)
} }
r.Close() r.Close()
//w.Close() //w.Close()
@ -152,16 +98,62 @@ func (t *Tunnel) handleConnection(remote string, conn netReadWriteCloser) {
return return
} }
if !t.Silent { if "stdio" == conn.RemoteAddr().Network() {
if "stdio" == conn.RemoteAddr().Network() { fmt.Fprintf(os.Stdout, "(connected to %s:%d and reading from %s)\n",
fmt.Fprintf(os.Stdout, "(connected to %s:%d and reading from %s)\n", t.RemoteAddress, t.RemotePort, conn.RemoteAddr().String())
t.RemoteAddress, t.RemotePort, conn.RemoteAddr().String()) } else {
} else { fmt.Fprintf(os.Stdout, "[connect] %s => %s:%d\n",
fmt.Fprintf(os.Stdout, "[connect] %s => %s:%d\n", strings.Replace(conn.RemoteAddr().String(), "[::1]:", "localhost:", 1), t.RemoteAddress, t.RemotePort)
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)
}
}

View File

@ -1,11 +0,0 @@
# 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"]