sclient/sclient.go

170 lines
3.9 KiB
Go

package sclient
import (
"crypto/tls"
"fmt"
"io"
"net"
"os"
"strconv"
"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
NextProtos []string
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,
NextProtos: t.NextProtos,
})
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
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 netReadWriteCloser interface {
io.ReadWriteCloser
RemoteAddr() net.Addr
}
func pipe(r netReadWriteCloser, w netReadWriteCloser, t string) {
buffer := make([]byte, 2048)
for {
done := false
// NOTE: count may be > 0 even if there's an err
//fmt.Fprintf(os.Stdout, "[debug] (%s) reading\n", t)
count, err := r.Read(buffer)
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:%d) %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 (t *Tunnel) handleConnection(remote string, conn netReadWriteCloser) {
sclient, err := tls.Dial("tcp", remote,
&tls.Config{
ServerName: t.ServerName,
InsecureSkipVerify: t.InsecureSkipVerify,
})
if err != nil {
fmt.Fprintf(os.Stderr, "[error] (remote) %s\n", err)
conn.Close()
return
}
if !t.Silent {
if "stdio" == conn.RemoteAddr().Network() {
fmt.Fprintf(os.Stdout, "(connected to %s:%d and reading from %s)\n",
t.RemoteAddress, t.RemotePort, conn.RemoteAddr().String())
} else {
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")
pipe(sclient, conn, "remote")
}