- got term resizing working (client SIGWINCH signals -> server_pty(rows,cols)

This commit is contained in:
Russ Magee 2018-04-28 19:28:37 -07:00
parent 50f0433579
commit 8162707ffa
3 changed files with 91 additions and 15 deletions

View file

@ -29,19 +29,28 @@ import (
) )
const ( const (
csoNone = iota // No error, normal packet CSONone = iota // No error, normal packet
csoHmacInvalid // HMAC mismatch detect on remote end CSOHmacInvalid // HMAC mismatch detected on remote end
csoChaff // This packet is a dummy, do not process beyond decryption CSOTermSize // set term size (rows:cols)
CSOChaff // Dummy packet, do not pass beyond decryption
) )
/*---------------------------------------------------------------------*/ /*---------------------------------------------------------------------*/
type WinSize struct {
Rows uint16
Cols uint16
}
// Conn is a HKex connection - a drop-in replacement for net.Conn // Conn is a HKex connection - a drop-in replacement for net.Conn
type Conn struct { type Conn struct {
c net.Conn // which also implements io.Reader, io.Writer, ... c net.Conn // which also implements io.Reader, io.Writer, ...
h *HerraduraKEx h *HerraduraKEx
cipheropts uint32 // post-KEx cipher/hmac options cipheropts uint32 // post-KEx cipher/hmac options
opts uint32 // post-KEx protocol options (caller-defined) opts uint32 // post-KEx protocol options (caller-defined)
WinCh chan WinSize
Rows uint16
Cols uint16
r cipher.Stream //read cipherStream r cipher.Stream //read cipherStream
rm hash.Hash rm hash.Hash
w cipher.Stream //write cipherStream w cipher.Stream //write cipherStream
@ -262,7 +271,8 @@ func (hl HKExListener) Accept() (hc Conn, err error) {
} }
log.Println("[Accepted]") log.Println("[Accepted]")
hc = Conn{c: c, h: New(0, 0), dBuf: new(bytes.Buffer)} hc = Conn{c: c, h: New(0, 0), WinCh: make(chan WinSize, 1),
dBuf: new(bytes.Buffer)}
// Read in hkexnet.Conn parameters over raw Conn c // Read in hkexnet.Conn parameters over raw Conn c
// d is value for Herradura key exchange // d is value for Herradura key exchange
@ -311,9 +321,10 @@ func (c Conn) Read(b []byte) (n int, err error) {
var hmacIn [4]uint8 var hmacIn [4]uint8
var payloadLen uint32 var payloadLen uint32
// Read ctrl/status opcode (csoHmacInvalid on hmac mismatch) // Read ctrl/status opcode (CSOHmacInvalid on hmac mismatch)
err = binary.Read(c.c, binary.BigEndian, &ctrlStatOp) err = binary.Read(c.c, binary.BigEndian, &ctrlStatOp)
if ctrlStatOp == csoHmacInvalid { log.Printf("[ctrlStatOp: %v]\n", ctrlStatOp)
if ctrlStatOp == CSOHmacInvalid {
// Other side indicated channel tampering, close channel // Other side indicated channel tampering, close channel
c.Close() c.Close()
return 1, errors.New("** ALERT - remote end detected HMAC mismatch - possible channel tampering **") return 1, errors.New("** ALERT - remote end detected HMAC mismatch - possible channel tampering **")
@ -379,8 +390,12 @@ func (c Conn) Read(b []byte) (n int, err error) {
} }
// Throw away pkt if it's chaff (ie., caller to Read() won't see this data) // Throw away pkt if it's chaff (ie., caller to Read() won't see this data)
if ctrlStatOp == csoChaff { if ctrlStatOp == CSOChaff {
log.Printf("[Chaff pkt]\n") log.Printf("[Chaff pkt]\n")
} else if ctrlStatOp == CSOTermSize {
fmt.Sscanf(string(payloadBytes), "%d %d", &c.Rows, &c.Cols)
log.Printf("[TermSize pkt: rows %v cols %v]\n", c.Rows, c.Cols)
c.WinCh <- WinSize{c.Rows, c.Cols}
} else { } else {
c.dBuf.Write(payloadBytes) c.dBuf.Write(payloadBytes)
//log.Printf("c.dBuf: %s\n", hex.Dump(c.dBuf.Bytes())) //log.Printf("c.dBuf: %s\n", hex.Dump(c.dBuf.Bytes()))
@ -394,7 +409,7 @@ func (c Conn) Read(b []byte) (n int, err error) {
// Log alert if hmac didn't match, corrupted channel // Log alert if hmac didn't match, corrupted channel
if !bytes.Equal(hTmp, []byte(hmacIn[0:])) /*|| hmacIn[0] > 0xf8*/ { if !bytes.Equal(hTmp, []byte(hmacIn[0:])) /*|| hmacIn[0] > 0xf8*/ {
fmt.Println("** ALERT - detected HMAC mismatch, possible channel tampering **") fmt.Println("** ALERT - detected HMAC mismatch, possible channel tampering **")
_, _ = c.c.Write([]byte{csoHmacInvalid}) _, _ = c.c.Write([]byte{CSOHmacInvalid})
} }
} }
@ -413,6 +428,11 @@ func (c Conn) Read(b []byte) (n int, err error) {
// //
// See go doc io.Writer // See go doc io.Writer
func (c Conn) Write(b []byte) (n int, err error) { func (c Conn) Write(b []byte) (n int, err error) {
return c.WritePacket(b, CSONone)
}
// Write a byte slice with specified ctrlStatusOp byte
func (c Conn) WritePacket(b []byte, op byte) (n int, err error) {
//log.Printf("[Encrypting...]\r\n") //log.Printf("[Encrypting...]\r\n")
var hmacOut []uint8 var hmacOut []uint8
var payloadLen uint32 var payloadLen uint32
@ -437,8 +457,7 @@ func (c Conn) Write(b []byte) (n int, err error) {
} }
log.Printf(" ->ctext:\r\n%s\r\n", hex.Dump(wb.Bytes())) log.Printf(" ->ctext:\r\n%s\r\n", hex.Dump(wb.Bytes()))
var ctrlStatOp byte ctrlStatOp := op
ctrlStatOp = csoNone
_ = binary.Write(c.c, binary.BigEndian, &ctrlStatOp) _ = binary.Write(c.c, binary.BigEndian, &ctrlStatOp)
// Write hmac LSB, payloadLen followed by payload // Write hmac LSB, payloadLen followed by payload

View file

@ -14,9 +14,12 @@ import (
"io/ioutil" "io/ioutil"
"log" "log"
"os" "os"
"os/exec"
"os/signal"
"os/user" "os/user"
"strings" "strings"
"sync" "sync"
"syscall"
hkexsh "blitter.com/go/hkexsh" hkexsh "blitter.com/go/hkexsh"
isatty "github.com/mattn/go-isatty" isatty "github.com/mattn/go-isatty"
@ -30,6 +33,23 @@ type cmdSpec struct {
status int status int
} }
// get terminal size using 'stty' command
// (Most portable btwn Linux and MSYS/win32, but
// TODO: remove external dep on 'stty' utility)
func getTermSize() (rows int, cols int, err error) {
cmd := exec.Command("stty", "size")
cmd.Stdin = os.Stdin
out, err := cmd.Output()
//fmt.Printf("out: %#v\n", string(out))
//fmt.Printf("err: %#v\n", err)
fmt.Sscanf(string(out), "%d %d\n", &rows, &cols)
if err != nil {
log.Fatal(err)
}
return
}
// Demo of a simple client that dials up to a simple test server to // Demo of a simple client that dials up to a simple test server to
// send data. // send data.
// //
@ -77,6 +97,9 @@ func main() {
defer conn.Close() defer conn.Close()
// From this point on, conn is a secure encrypted channel // From this point on, conn is a secure encrypted channel
rows := 0
cols := 0
// Set stdin in raw mode if it's an interactive session // Set stdin in raw mode if it's an interactive session
// TODO: send flag to server side indicating this // TODO: send flag to server side indicating this
// affects shell command used // affects shell command used
@ -131,7 +154,9 @@ func main() {
authCookie: []byte(authCookie), authCookie: []byte(authCookie),
status: 0} status: 0}
_, err = fmt.Fprintf(conn, "%d %d %d %d\n", len(rec.op), len(rec.who), len(rec.cmd), len(rec.authCookie)) _, 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.op)
_, err = conn.Write(rec.who) _, err = conn.Write(rec.who)
_, err = conn.Write(rec.cmd) _, err = conn.Write(rec.cmd)
@ -168,6 +193,27 @@ func main() {
}() }()
if isInteractive { if isInteractive {
// Handle pty resizes (notify server side)
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGWINCH)
wg.Add(1)
go func() {
defer wg.Done()
for range ch {
// Query client's term size so we can communicate it to server
// pty after interactive session starts
rows, cols, err = getTermSize()
log.Printf("[rows %v cols %v]\n", rows, cols)
if err != nil {
panic(err)
}
termSzPacket := fmt.Sprintf("%d %d", rows, cols)
conn.WritePacket([]byte(termSzPacket), hkexsh.CSOTermSize)
}
}()
ch <- syscall.SIGWINCH // Initial resize.
// client writer (to server) goroutine // client writer (to server) goroutine
wg.Add(1) wg.Add(1)
go func() { go func() {

View file

@ -28,6 +28,8 @@ type cmdSpec struct {
who []byte who []byte
cmd []byte cmd []byte
authCookie []byte authCookie []byte
termRows []byte
termCols []byte
status int status int
} }
@ -119,6 +121,15 @@ func runShellAs(who string, cmd string, interactive bool, conn hkexsh.Conn) (err
} }
// Make sure to close the pty at the end. // Make sure to close the pty at the end.
defer func() { _ = ptmx.Close() }() // Best effort. defer func() { _ = ptmx.Close() }() // Best effort.
// Watch for term resizes
go func() {
for sz := range conn.WinCh {
log.Printf("[Setting term size to: %v %v]\n", sz.Rows, sz.Cols)
pty.Setsize(ptmx, &pty.Winsize{Rows: sz.Rows, Cols: sz.Cols})
}
}()
// Copy stdin to the pty.. (bgnd goroutine) // Copy stdin to the pty.. (bgnd goroutine)
go func() { go func() {
_, _ = io.Copy(ptmx, conn) _, _ = io.Copy(ptmx, conn)
@ -171,8 +182,8 @@ func main() {
// Wait for a connection. // Wait for a connection.
conn, err := l.Accept() conn, err := l.Accept()
if err != nil { if err != nil {
log.Printf("Accept() got error(%v), hanging up.\n", err) log.Printf("Accept() got error(%v), hanging up.\n", err)
conn.Close() conn.Close()
//log.Fatal(err) //log.Fatal(err)
} else { } else {
log.Println("Accepted client") log.Println("Accepted client")