mirror of
				https://github.com/therootcompany/sclient
				synced 2025-11-04 07:12:48 +00:00 
			
		
		
		
	v1.0.0: first working version
This commit is contained in:
		
						commit
						224d005a90
					
				
							
								
								
									
										41
									
								
								LICENSE
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								LICENSE
									
									
									
									
									
										Normal file
									
								
							@ -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.
 | 
			
		||||
							
								
								
									
										81
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							@ -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
 | 
			
		||||
```
 | 
			
		||||
							
								
								
									
										82
									
								
								sclient-cli.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								sclient-cli.go
									
									
									
									
									
										Normal file
									
								
							@ -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)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										104
									
								
								sclient.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								sclient.go
									
									
									
									
									
										Normal file
									
								
							@ -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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user