From f92085bb8652ebc9370d0e2bf462ff9557067d42 Mon Sep 17 00:00:00 2001 From: Russ Magee Date: Sat, 26 May 2018 13:43:09 -0700 Subject: [PATCH] Further work on term resizing platform support for Linux and Windows/mintty --- hkexsh/hkexsh.go | 28 ++++++++++-------- hkexsh/termsize_linux.go | 6 ++-- hkexsh/termsize_win.go | 63 ++++++++++++++++++++++++++++++++++++++-- hkexshd/hkexshd.go | 20 ++++++++----- 4 files changed, 92 insertions(+), 25 deletions(-) diff --git a/hkexsh/hkexsh.go b/hkexsh/hkexsh.go index 9b7bf2f..b713fd9 100644 --- a/hkexsh/hkexsh.go +++ b/hkexsh/hkexsh.go @@ -32,19 +32,21 @@ type cmdSpec struct { 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) { +var ( + wg sync.WaitGroup +) + +// Get terminal size using 'stty' command +func GetSize() (cols, rows 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) + log.Println(err) + cols, rows = 80, 24 //failsafe + } else { + fmt.Sscanf(string(out), "%d %d\n", &rows, &cols) } return } @@ -62,8 +64,6 @@ func getTermSize() (rows int, cols int, err error) { // connection (app-specific, passed through to the server to use or // ignore at its discretion). func main() { - var wg sync.WaitGroup - version := "0.1pre (NO WARRANTY)" var vopt bool var dbg bool @@ -73,6 +73,7 @@ func main() { var cmdStr string var altUser string var authCookie string + var chaffEnabled bool var chaffFreqMin uint var chaffFreqMax uint var chaffBytesMax uint @@ -86,6 +87,7 @@ func main() { flag.StringVar(&cmdStr, "x", "", "command to run (default empty - interactive shell)") flag.StringVar(&altUser, "u", "", "specify alternate user") flag.StringVar(&authCookie, "a", "", "auth cookie") + flag.BoolVar(&chaffEnabled, "cE", true, "enabled chaff pkts (default true)") flag.UintVar(&chaffFreqMin, "cfm", 100, "chaff pkt freq min (msecs)") flag.UintVar(&chaffFreqMax, "cfM", 5000, "chaff pkt freq max (msecs)") flag.UintVar(&chaffBytesMax, "cbM", 64, "chaff pkt size max (bytes)") @@ -183,7 +185,9 @@ func main() { // Set up chaffing to server conn.Chaff(chaffFreqMin, chaffFreqMax, chaffBytesMax) // enable client->server chaffing - conn.EnableChaff() + if chaffEnabled { + conn.EnableChaff() + } //client reader (from server) goroutine wg.Add(1) @@ -218,7 +222,7 @@ func main() { }() if isInteractive { - handleTermResizes() + handleTermResizes(conn) // client writer (to server) goroutine wg.Add(1) diff --git a/hkexsh/termsize_linux.go b/hkexsh/termsize_linux.go index 5084fa1..3abfb9a 100644 --- a/hkexsh/termsize_linux.go +++ b/hkexsh/termsize_linux.go @@ -12,7 +12,7 @@ import ( ) // Handle pty resizes (notify server side) -func handleTermResizes() { +func handleTermResizes(conn *hkexsh.Conn) { rows := 0 cols := 0 @@ -25,10 +25,10 @@ func handleTermResizes() { for range ch { // Query client's term size so we can communicate it to server // pty after interactive session starts - rows, cols, err = getTermSize() + cols, rows, err = GetSize() log.Printf("[rows %v cols %v]\n", rows, cols) if err != nil { - panic(err) + log.Println(err) } termSzPacket := fmt.Sprintf("%d %d", rows, cols) conn.WritePacket([]byte(termSzPacket), hkexsh.CSOTermSize) diff --git a/hkexsh/termsize_win.go b/hkexsh/termsize_win.go index b1ae088..314a28b 100644 --- a/hkexsh/termsize_win.go +++ b/hkexsh/termsize_win.go @@ -1,8 +1,65 @@ // +build windows package main +import ( + "fmt" + "log" + "time" + + hkexsh "blitter.com/go/hkexsh" +) + // Handle pty resizes (notify server side) -func handleTermResizes() { +func handleTermResizes(conn *hkexsh.Conn) { + var hasStty bool + curCols, curRows := 0, 0 + _, _, err := GetSize() + // The above may fail if user doesn't have msys 'stty' util + // in PATH. GetSize() will log.Error() once here + if err != nil { + fmt.Println("[1st GetSize:", err, "]") + hasStty = false + } else { + hasStty = true + } + + ch := make(chan bool, 1) + + if hasStty { + wg.Add(1) + go func() { + defer wg.Done() + for { + ch <- true + time.Sleep(1 * time.Second) + } + }() + } + + wg.Add(1) + go func() { + defer wg.Done() + + rows := 0 + cols := 0 + for range ch { + // Query client's term size so we can communicate it to server + // pty after interactive session starts + cols, rows, err = GetSize() + if err == nil { + } else { + fmt.Println("[GetSize:", err, "]") + } + if (curRows != rows) || (curCols != curCols) { + curRows = rows + curCols = cols + if err != nil { + log.Println(err) + } + termSzPacket := fmt.Sprintf("%d %d", curRows, curCols) + conn.WritePacket([]byte(termSzPacket), hkexsh.CSOTermSize) + } + } + }() + ch <- true // Initial resize } - - diff --git a/hkexshd/hkexshd.go b/hkexshd/hkexshd.go index d2bbf56..e4bd9df 100644 --- a/hkexshd/hkexshd.go +++ b/hkexshd/hkexshd.go @@ -79,7 +79,7 @@ func runCmdAs(who string, cmd string, conn hkex.Conn) (err error) { // Run a command (via default shell) as a specific user // // Uses ptys to support commands which expect a terminal. -func runShellAs(who string, cmd string, interactive bool, conn hkexsh.Conn) (err error) { +func runShellAs(who string, cmd string, interactive bool, conn hkexsh.Conn, chaffing bool) (err error) { u, _ := user.Lookup(who) var uid, gid uint32 fmt.Sscanf(u.Uid, "%d", &uid) @@ -134,8 +134,10 @@ func runShellAs(who string, cmd string, interactive bool, conn hkexsh.Conn) (err _, _ = io.Copy(ptmx, conn) }() - conn.EnableChaff() - + if chaffing { + conn.EnableChaff() + } + // ..and the pty to stdout. _, _ = io.Copy(conn, ptmx) @@ -160,6 +162,7 @@ func rejectUserMsg() string { func main() { version := "0.1pre (NO WARRANTY)" var vopt bool + var chaffEnabled bool var chaffFreqMin uint var chaffFreqMax uint var chaffBytesMax uint @@ -168,6 +171,7 @@ func main() { flag.BoolVar(&vopt, "v", false, "show version") flag.StringVar(&laddr, "l", ":2000", "interface[:port] to listen") + flag.BoolVar(&chaffEnabled, "cE", true, "enabled chaff pkts (default true)") flag.UintVar(&chaffFreqMin, "cfm", 100, "chaff pkt freq min (msecs)") flag.UintVar(&chaffFreqMax, "cfM", 5000, "chaff pkt freq max (msecs)") flag.UintVar(&chaffBytesMax, "cbM", 64, "chaff pkt size max (bytes)") @@ -175,7 +179,7 @@ func main() { flag.Parse() if vopt { - fmt.Printf("version v%s\n", version) + fmt.Printf("version v%s\n", version) os.Exit(0) } @@ -205,7 +209,9 @@ func main() { log.Println("Accepted client") // Set up chaffing to client - conn.Chaff(chaffFreqMin, chaffFreqMax, chaffBytesMax) // enable client->server chaffing + // Will only start when runShellAs() is called + // after stdin/stdout are hooked up + conn.Chaff(chaffFreqMin, chaffFreqMax, chaffBytesMax) // configure server->client chaffing // Handle the connection in a new goroutine. // The loop then returns to accepting, so that @@ -276,14 +282,14 @@ func main() { if rec.op[0] == 'c' { // Non-interactive command log.Println("[Running command]") - runShellAs(string(rec.who), string(rec.cmd), false, conn) + runShellAs(string(rec.who), string(rec.cmd), false, conn, chaffEnabled) // Returned hopefully via an EOF or exit/logout; // Clear current op so user can enter next, or EOF rec.op[0] = 0 log.Println("[Command complete]") } else if rec.op[0] == 's' { log.Println("[Running shell]") - runShellAs(string(rec.who), string(rec.cmd), true, conn) + runShellAs(string(rec.who), string(rec.cmd), true, conn, chaffEnabled) // Returned hopefully via an EOF or exit/logout; // Clear current op so user can enter next, or EOF rec.op[0] = 0