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
ServerName string
Quiet 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 , 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 )
}
}
// 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:%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 ( 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 "stdio" == conn . RemoteAddr ( ) . Network ( ) {
if t . Quiet == false {
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" )
}