support pipes and stdin

This commit is contained in:
AJ ONeal 2018-08-08 00:16:52 -06:00
parent 2235bf3a55
commit c786b0bd07
7 changed files with 126 additions and 28 deletions

View File

@ -96,3 +96,19 @@ Ignore a bad TLS/SSL/HTTPS certificate and connect anyway.
```bash ```bash
sclient -k badtls.telebit.cloud:443 localhost:3000 sclient -k badtls.telebit.cloud:443 localhost:3000
``` ```
Reading from stdin
```bash
sclient telebit.cloud:443 -
```
```bash
sclient telebit.cloud:443 - </path/to/file
```
Piping
```bash
printf "GET / HTTP/1.1\r\nHost: telebit.cloud\r\n\r\n" | sclient telebit.cloud:443
```

View File

@ -25,12 +25,18 @@ func main() {
insecure := flag.Bool("k", false, "ignore bad TLS/SSL/HTTPS certificates") insecure := flag.Bool("k", false, "ignore bad TLS/SSL/HTTPS certificates")
flag.BoolVar(insecure, "insecure", false, "ignore bad TLS/SSL/HTTPS certificates") flag.BoolVar(insecure, "insecure", false, "ignore bad TLS/SSL/HTTPS certificates")
flag.Parse() flag.Parse()
remotestr := flag.Arg(0)
localstr := flag.Arg(1)
// NArg, Arg, Args
i := flag.NArg() i := flag.NArg()
if 2 != i { if 2 != i {
usage() // We may omit the second argument if we're going straight to stdin
os.Exit(0) if stat, _ := os.Stdin.Stat(); 1 == i && (stat.Mode()&os.ModeCharDevice) == 0 {
localstr = "|"
} else {
usage()
os.Exit(1)
}
} }
opts := &SclientOpts{} opts := &SclientOpts{}
@ -38,8 +44,8 @@ func main() {
opts.LocalAddress = "localhost" opts.LocalAddress = "localhost"
opts.InsecureSkipVerify = *insecure opts.InsecureSkipVerify = *insecure
remote := strings.Split(flag.Arg(0), ":") remote := strings.Split(remotestr, ":")
//remoteAddr, remotePort, err := net.SplitHostPort(flag.Arg(0)) //remoteAddr, remotePort, err := net.SplitHostPort(remotestr)
if 2 == len(remote) { if 2 == len(remote) {
rport, err := strconv.Atoi(remote[1]) rport, err := strconv.Atoi(remote[1])
if nil != err { if nil != err {
@ -53,30 +59,37 @@ func main() {
} }
opts.RemoteAddress = remote[0] opts.RemoteAddress = remote[0]
local := strings.Split(flag.Arg(1), ":") if "-" == localstr || "|" == localstr {
//localAddr, localPort, err := net.SplitHostPort(flag.Arg(0)) // User may specify stdin/stdout instead of net
opts.LocalAddress = localstr
if 1 == len(local) { opts.LocalPort = -1
lport, err := strconv.Atoi(local[0])
if nil != err {
usage()
os.Exit(0)
}
opts.LocalPort = lport
} else { } else {
lport, err := strconv.Atoi(local[1]) // Test that argument is a local address
if nil != err { local := strings.Split(localstr, ":")
usage()
os.Exit(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
} }
opts.LocalAddress = local[0]
opts.LocalPort = lport
} }
sclient := &Sclient{} sclient := &Sclient{}
err := sclient.DialAndListen(opts) err := sclient.DialAndListen(opts)
if nil != err { if nil != err {
usage() fmt.Fprintf(os.Stderr, "%s\n", err)
os.Exit(0) //usage()
//os.Exit(6)
} }
} }

View File

@ -10,6 +10,36 @@ import (
"strings" "strings"
) )
// I wonder if I can get this to exactly mirror UnixAddr without passing it in
type stdaddr struct {
net.UnixAddr
}
type stdnet struct {
in *os.File // os.Stdin
out *os.File // os.Stdout
addr *stdaddr
}
func (rw *stdnet) Read(buf []byte) (n int, err error) {
return rw.in.Read(buf)
}
func (rw *stdnet) Write(buf []byte) (n int, err error) {
return rw.out.Write(buf)
}
func (rw *stdnet) Close() error {
return rw.in.Close()
}
func (rw *stdnet) RemoteAddr() net.Addr {
return rw.addr
}
// not all of net.Conn, just RWC and RemoteAddr()
type Rwc interface {
io.ReadWriteCloser
RemoteAddr() net.Addr
}
type SclientOpts struct { type SclientOpts struct {
RemoteAddress string RemoteAddress string
RemotePort int RemotePort int
@ -20,13 +50,13 @@ type SclientOpts struct {
type Sclient struct{} type Sclient struct{}
func pipe(r net.Conn, w net.Conn, t string) { func pipe(r Rwc, w Rwc, t string) {
buffer := make([]byte, 2048) buffer := make([]byte, 2048)
for { for {
done := false done := false
// NOTE: count may be > 0 even if there's an err // 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) //fmt.Fprintf(os.Stdout, "[debug] (%s) reading\n", t)
count, err := r.Read(buffer)
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 {
@ -56,7 +86,7 @@ func pipe(r net.Conn, w net.Conn, t string) {
} }
} }
func handleConnection(remote string, conn net.Conn, opts *SclientOpts) { func handleConnection(remote string, conn Rwc, opts *SclientOpts) {
sclient, err := tls.Dial("tcp", remote, sclient, err := tls.Dial("tcp", remote,
&tls.Config{InsecureSkipVerify: opts.InsecureSkipVerify}) &tls.Config{InsecureSkipVerify: opts.InsecureSkipVerify})
@ -66,8 +96,13 @@ func handleConnection(remote string, conn net.Conn, opts *SclientOpts) {
return return
} }
fmt.Fprintf(os.Stdout, "[connect] %s => %s:%d\n", if "stdio" == conn.RemoteAddr().Network() {
strings.Replace(conn.RemoteAddr().String(), "[::1]:", "localhost:", 1), opts.RemoteAddress, opts.RemotePort) fmt.Fprintf(os.Stdout, "(connected to %s:%d and reading from %s)\n",
opts.RemoteAddress, opts.RemotePort, conn.RemoteAddr().String())
} else {
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") go pipe(conn, sclient, "local")
pipe(sclient, conn, "remote") pipe(sclient, conn, "remote")
@ -84,6 +119,21 @@ func (*Sclient) DialAndListen(opts *SclientOpts) error {
conn.Close() conn.Close()
} }
// use stdin/stdout
if "-" == opts.LocalAddress || "|" == opts.LocalAddress {
var name string
network := "stdio"
if "|" == opts.LocalAddress {
name = "pipe"
} else {
name = "stdin"
}
conn := &stdnet{os.Stdin, os.Stdout, &stdaddr{net.UnixAddr{name, network}}}
handleConnection(remote, conn, opts)
return nil
}
// use net.Conn
local := opts.LocalAddress + ":" + strconv.Itoa(opts.LocalPort) local := opts.LocalAddress + ":" + strconv.Itoa(opts.LocalPort)
ln, err := net.Listen("tcp", local) ln, err := net.Listen("tcp", local)
if err != nil { if err != nil {

5
tests/get.bin Normal file
View File

@ -0,0 +1,5 @@
GET / HTTP/1.1
Host: telebit.cloud
Connection: close

8
tests/localhost.sh Executable file
View File

@ -0,0 +1,8 @@
#!/bin/bash
go run -race sclient*.go telebit.cloud:443 localhost:3000 &
my_pid=$!
sleep 5
netcat localhost 3000 < tests/get.bin
kill $my_pid

3
tests/pipe.sh Executable file
View File

@ -0,0 +1,3 @@
#!/bin/bash
cat tests/get.bin | go run -race sclient*.go telebit.cloud:443

3
tests/stdin.sh Executable file
View File

@ -0,0 +1,3 @@
#!/bin/bash
go run -race sclient*.go telebit.cloud:443 - < ./tests/get.bin