mirror of
				https://github.com/therootcompany/sclient
				synced 2025-10-31 13:12:49 +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