2018-01-06 15:30:56 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2018-01-12 07:01:39 +00:00
|
|
|
"flag"
|
2018-01-06 15:30:56 +00:00
|
|
|
"fmt"
|
2018-01-13 06:47:57 +00:00
|
|
|
"io"
|
2018-01-18 05:27:00 +00:00
|
|
|
"io/ioutil"
|
|
|
|
"log"
|
2018-01-13 06:47:57 +00:00
|
|
|
"os"
|
2018-01-18 04:36:53 +00:00
|
|
|
"sync"
|
2018-01-06 20:26:08 +00:00
|
|
|
|
|
|
|
hkex "blitter.com/herradurakex"
|
2018-01-19 05:17:57 +00:00
|
|
|
isatty "github.com/mattn/go-isatty"
|
2018-01-19 02:57:37 +00:00
|
|
|
"golang.org/x/sys/unix"
|
2018-01-06 15:30:56 +00:00
|
|
|
)
|
|
|
|
|
2018-01-21 04:37:27 +00:00
|
|
|
type cmdSpec struct {
|
|
|
|
op []byte
|
|
|
|
who []byte
|
|
|
|
cmd []byte
|
|
|
|
authCookie []byte
|
|
|
|
status int
|
|
|
|
}
|
|
|
|
|
2018-01-09 03:16:55 +00:00
|
|
|
// Demo of a simple client that dials up to a simple test server to
|
|
|
|
// send data.
|
2018-01-13 18:01:27 +00:00
|
|
|
//
|
|
|
|
// While conforming to the basic net.Conn interface HKex.Conn has extra
|
|
|
|
// capabilities designed to allow apps to define connection options,
|
|
|
|
// encryption/hmac settings and operations across the encrypted channel.
|
|
|
|
//
|
|
|
|
// Initial setup is the same as using plain net.Dial(), but one may
|
|
|
|
// specify extra extension tags (strings) to set the cipher and hmac
|
|
|
|
// setting desired; as well as the intended operation mode for the
|
|
|
|
// connection (app-specific, passed through to the server to use or
|
|
|
|
// ignore at its discretion).
|
2018-01-06 15:30:56 +00:00
|
|
|
func main() {
|
2018-01-18 04:36:53 +00:00
|
|
|
var wg sync.WaitGroup
|
|
|
|
|
2018-01-12 07:01:39 +00:00
|
|
|
var cAlg string
|
2018-01-13 06:13:01 +00:00
|
|
|
var hAlg string
|
2018-01-13 06:24:40 +00:00
|
|
|
var server string
|
2018-01-19 05:17:57 +00:00
|
|
|
isInteractive := false
|
2018-01-13 06:47:57 +00:00
|
|
|
|
2018-01-12 07:01:39 +00:00
|
|
|
flag.StringVar(&cAlg, "c", "C_AES_256", "cipher [\"C_AES_256\" | \"C_TWOFISH_128\" | \"C_BLOWFISH_64\"]")
|
2018-01-13 06:13:01 +00:00
|
|
|
flag.StringVar(&hAlg, "h", "H_SHA256", "hmac [\"H_SHA256\"]")
|
2018-01-13 06:24:40 +00:00
|
|
|
flag.StringVar(&server, "s", "localhost:2000", "server hostname/address[:port]")
|
2018-01-12 07:01:39 +00:00
|
|
|
flag.Parse()
|
|
|
|
|
2018-01-21 04:37:27 +00:00
|
|
|
//log.SetOutput(os.Stdout)
|
2018-01-18 05:27:00 +00:00
|
|
|
log.SetOutput(ioutil.Discard)
|
|
|
|
|
2018-01-13 06:24:40 +00:00
|
|
|
conn, err := hkex.Dial("tcp", server, cAlg, hAlg)
|
2018-01-06 15:30:56 +00:00
|
|
|
if err != nil {
|
2018-01-08 06:05:14 +00:00
|
|
|
fmt.Println("Err!")
|
2018-01-13 06:47:57 +00:00
|
|
|
panic(err)
|
|
|
|
}
|
2018-01-18 04:36:53 +00:00
|
|
|
defer conn.Close()
|
|
|
|
|
2018-01-19 05:17:57 +00:00
|
|
|
// Set stdin in raw mode if it's an interactive session
|
|
|
|
if isatty.IsTerminal(os.Stdin.Fd()) {
|
|
|
|
isInteractive = true
|
|
|
|
oldState, err := MakeRaw(int(os.Stdin.Fd()))
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
defer func() { _ = Restore(int(os.Stdin.Fd()), oldState) }() // Best effort.
|
|
|
|
} else {
|
|
|
|
fmt.Println("NOT A TTY")
|
2018-01-19 02:57:37 +00:00
|
|
|
}
|
|
|
|
|
2018-01-21 04:37:27 +00:00
|
|
|
rec := &cmdSpec{op: []byte{'s'},
|
|
|
|
who: []byte("ABCD"),
|
|
|
|
cmd: []byte("EFGH"),
|
|
|
|
authCookie: []byte("99"),
|
|
|
|
status: 0}
|
|
|
|
|
|
|
|
_, err = fmt.Fprintf(conn, "%d %d %d %d\n", len(rec.op), len(rec.who), len(rec.cmd), len(rec.authCookie))
|
|
|
|
_, err = conn.Write(rec.op)
|
|
|
|
_, err = conn.Write(rec.who)
|
|
|
|
_, err = conn.Write(rec.cmd)
|
|
|
|
_, err = conn.Write(rec.authCookie)
|
|
|
|
|
|
|
|
//client reader (from server) goroutine
|
2018-01-18 04:36:53 +00:00
|
|
|
wg.Add(1)
|
|
|
|
go func() {
|
2018-01-19 05:17:57 +00:00
|
|
|
// By deferring a call to wg.Done(),
|
|
|
|
// each goroutine guarantees that it marks
|
|
|
|
// its direction's stream as finished.
|
|
|
|
//
|
2018-01-18 04:36:53 +00:00
|
|
|
// Whichever direction's goroutine finishes first
|
2018-01-19 05:17:57 +00:00
|
|
|
// will call wg.Done() once more, explicitly, to
|
|
|
|
// hang up on the other side, so that this client
|
2018-01-18 04:36:53 +00:00
|
|
|
// exits immediately on an EOF from either side.
|
|
|
|
defer wg.Done()
|
|
|
|
|
|
|
|
// io.Copy() expects EOF so this will
|
|
|
|
// exit with inerr == nil
|
|
|
|
_, inerr := io.Copy(os.Stdout, conn)
|
|
|
|
if inerr != nil {
|
|
|
|
if inerr.Error() != "EOF" {
|
|
|
|
fmt.Println(inerr)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
}
|
2018-01-19 05:17:57 +00:00
|
|
|
if isInteractive {
|
2018-01-21 04:37:27 +00:00
|
|
|
log.Println("[Got EOF]")
|
|
|
|
wg.Done() // server hung up, close WaitGroup to exit client
|
2018-01-19 05:17:57 +00:00
|
|
|
}
|
2018-01-18 04:36:53 +00:00
|
|
|
}()
|
|
|
|
|
2018-01-21 04:37:27 +00:00
|
|
|
// client writer (to server) goroutine
|
2018-01-18 04:36:53 +00:00
|
|
|
wg.Add(1)
|
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
|
|
|
|
|
|
|
// io.Copy() expects EOF so this will
|
|
|
|
// exit with outerr == nil
|
|
|
|
_, outerr := io.Copy(conn, os.Stdin)
|
|
|
|
if outerr != nil {
|
|
|
|
if outerr.Error() != "EOF" {
|
|
|
|
fmt.Println(outerr)
|
|
|
|
os.Exit(2)
|
|
|
|
}
|
|
|
|
}
|
2018-01-21 04:37:27 +00:00
|
|
|
log.Println("[Sent EOF]")
|
|
|
|
wg.Done() // client hung up, close WaitGroup to exit client
|
2018-01-18 04:36:53 +00:00
|
|
|
}()
|
|
|
|
|
|
|
|
// Wait until both stdin and stdout goroutines finish
|
|
|
|
wg.Wait()
|
2018-01-06 15:30:56 +00:00
|
|
|
}
|
2018-01-19 02:57:37 +00:00
|
|
|
|
|
|
|
/* ------------- minimal terminal APIs brought in from ssh/terminal
|
|
|
|
* (they have no real business being there as they aren't specific to
|
2018-01-19 05:17:57 +00:00
|
|
|
* ssh, but as of v1.10, early 2018, core go stdlib hasn't yet done
|
|
|
|
* the planned terminal lib reorgs.)
|
2018-01-19 02:57:37 +00:00
|
|
|
* -------------
|
|
|
|
*/
|
|
|
|
|
2018-01-19 05:17:57 +00:00
|
|
|
// From github.com/golang/crypto/blob/master/ssh/terminal/util_linux.go
|
2018-01-19 02:57:37 +00:00
|
|
|
const ioctlReadTermios = unix.TCGETS
|
|
|
|
const ioctlWriteTermios = unix.TCSETS
|
|
|
|
|
2018-01-19 05:17:57 +00:00
|
|
|
// From github.com/golang/crypto/blob/master/ssh/terminal/util.go
|
2018-01-19 02:57:37 +00:00
|
|
|
// State contains the state of a terminal.
|
|
|
|
type State struct {
|
|
|
|
termios unix.Termios
|
|
|
|
}
|
|
|
|
|
|
|
|
// MakeRaw put the terminal connected to the given file descriptor into raw
|
|
|
|
// mode and returns the previous state of the terminal so that it can be
|
|
|
|
// restored.
|
|
|
|
func MakeRaw(fd int) (*State, error) {
|
|
|
|
termios, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
oldState := State{termios: *termios}
|
|
|
|
|
|
|
|
// This attempts to replicate the behaviour documented for cfmakeraw in
|
|
|
|
// the termios(3) manpage.
|
|
|
|
termios.Iflag &^= unix.IGNBRK | unix.BRKINT | unix.PARMRK | unix.ISTRIP | unix.INLCR | unix.IGNCR | unix.ICRNL | unix.IXON
|
|
|
|
termios.Oflag &^= unix.OPOST
|
|
|
|
termios.Lflag &^= unix.ECHO | unix.ECHONL | unix.ICANON | unix.ISIG | unix.IEXTEN
|
|
|
|
termios.Cflag &^= unix.CSIZE | unix.PARENB
|
|
|
|
termios.Cflag |= unix.CS8
|
|
|
|
termios.Cc[unix.VMIN] = 1
|
|
|
|
termios.Cc[unix.VTIME] = 0
|
|
|
|
if err := unix.IoctlSetTermios(fd, ioctlWriteTermios, termios); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &oldState, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetState returns the current state of a terminal which may be useful to
|
|
|
|
// restore the terminal after a signal.
|
|
|
|
func GetState(fd int) (*State, error) {
|
|
|
|
termios, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &State{termios: *termios}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Restore restores the terminal connected to the given file descriptor to a
|
|
|
|
// previous state.
|
|
|
|
func Restore(fd int, state *State) error {
|
|
|
|
return unix.IoctlSetTermios(fd, ioctlWriteTermios, &state.termios)
|
|
|
|
}
|