Further work on term resizing platform support for Linux and Windows/mintty

This commit is contained in:
Russ Magee 2018-05-26 13:43:09 -07:00
parent 89dd225910
commit f92085bb86
4 changed files with 92 additions and 25 deletions

View File

@ -32,19 +32,21 @@ type cmdSpec struct {
status int status int
} }
// get terminal size using 'stty' command var (
// (Most portable btwn Linux and MSYS/win32, but wg sync.WaitGroup
// TODO: remove external dep on 'stty' utility) )
func getTermSize() (rows int, cols int, err error) {
// Get terminal size using 'stty' command
func GetSize() (cols, rows int, err error) {
cmd := exec.Command("stty", "size") cmd := exec.Command("stty", "size")
cmd.Stdin = os.Stdin cmd.Stdin = os.Stdin
out, err := cmd.Output() 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 { 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 return
} }
@ -62,8 +64,6 @@ func getTermSize() (rows int, cols int, err error) {
// connection (app-specific, passed through to the server to use or // connection (app-specific, passed through to the server to use or
// ignore at its discretion). // ignore at its discretion).
func main() { func main() {
var wg sync.WaitGroup
version := "0.1pre (NO WARRANTY)" version := "0.1pre (NO WARRANTY)"
var vopt bool var vopt bool
var dbg bool var dbg bool
@ -73,6 +73,7 @@ func main() {
var cmdStr string var cmdStr string
var altUser string var altUser string
var authCookie string var authCookie string
var chaffEnabled bool
var chaffFreqMin uint var chaffFreqMin uint
var chaffFreqMax uint var chaffFreqMax uint
var chaffBytesMax uint var chaffBytesMax uint
@ -86,6 +87,7 @@ func main() {
flag.StringVar(&cmdStr, "x", "", "command to run (default empty - interactive shell)") flag.StringVar(&cmdStr, "x", "", "command to run (default empty - interactive shell)")
flag.StringVar(&altUser, "u", "", "specify alternate user") flag.StringVar(&altUser, "u", "", "specify alternate user")
flag.StringVar(&authCookie, "a", "", "auth cookie") 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(&chaffFreqMin, "cfm", 100, "chaff pkt freq min (msecs)")
flag.UintVar(&chaffFreqMax, "cfM", 5000, "chaff pkt freq max (msecs)") flag.UintVar(&chaffFreqMax, "cfM", 5000, "chaff pkt freq max (msecs)")
flag.UintVar(&chaffBytesMax, "cbM", 64, "chaff pkt size max (bytes)") flag.UintVar(&chaffBytesMax, "cbM", 64, "chaff pkt size max (bytes)")
@ -183,7 +185,9 @@ func main() {
// Set up chaffing to server // Set up chaffing to server
conn.Chaff(chaffFreqMin, chaffFreqMax, chaffBytesMax) // enable client->server chaffing conn.Chaff(chaffFreqMin, chaffFreqMax, chaffBytesMax) // enable client->server chaffing
if chaffEnabled {
conn.EnableChaff() conn.EnableChaff()
}
//client reader (from server) goroutine //client reader (from server) goroutine
wg.Add(1) wg.Add(1)
@ -218,7 +222,7 @@ func main() {
}() }()
if isInteractive { if isInteractive {
handleTermResizes() handleTermResizes(conn)
// client writer (to server) goroutine // client writer (to server) goroutine
wg.Add(1) wg.Add(1)

View File

@ -12,7 +12,7 @@ import (
) )
// Handle pty resizes (notify server side) // Handle pty resizes (notify server side)
func handleTermResizes() { func handleTermResizes(conn *hkexsh.Conn) {
rows := 0 rows := 0
cols := 0 cols := 0
@ -25,10 +25,10 @@ func handleTermResizes() {
for range ch { for range ch {
// Query client's term size so we can communicate it to server // Query client's term size so we can communicate it to server
// pty after interactive session starts // pty after interactive session starts
rows, cols, err = getTermSize() cols, rows, err = GetSize()
log.Printf("[rows %v cols %v]\n", rows, cols) log.Printf("[rows %v cols %v]\n", rows, cols)
if err != nil { if err != nil {
panic(err) log.Println(err)
} }
termSzPacket := fmt.Sprintf("%d %d", rows, cols) termSzPacket := fmt.Sprintf("%d %d", rows, cols)
conn.WritePacket([]byte(termSzPacket), hkexsh.CSOTermSize) conn.WritePacket([]byte(termSzPacket), hkexsh.CSOTermSize)

View File

@ -1,8 +1,65 @@
// +build windows // +build windows
package main package main
import (
"fmt"
"log"
"time"
hkexsh "blitter.com/go/hkexsh"
)
// Handle pty resizes (notify server side) // 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
} }

View File

@ -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 // Run a command (via default shell) as a specific user
// //
// Uses ptys to support commands which expect a terminal. // 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) u, _ := user.Lookup(who)
var uid, gid uint32 var uid, gid uint32
fmt.Sscanf(u.Uid, "%d", &uid) fmt.Sscanf(u.Uid, "%d", &uid)
@ -134,7 +134,9 @@ func runShellAs(who string, cmd string, interactive bool, conn hkexsh.Conn) (err
_, _ = io.Copy(ptmx, conn) _, _ = io.Copy(ptmx, conn)
}() }()
if chaffing {
conn.EnableChaff() conn.EnableChaff()
}
// ..and the pty to stdout. // ..and the pty to stdout.
_, _ = io.Copy(conn, ptmx) _, _ = io.Copy(conn, ptmx)
@ -160,6 +162,7 @@ func rejectUserMsg() string {
func main() { func main() {
version := "0.1pre (NO WARRANTY)" version := "0.1pre (NO WARRANTY)"
var vopt bool var vopt bool
var chaffEnabled bool
var chaffFreqMin uint var chaffFreqMin uint
var chaffFreqMax uint var chaffFreqMax uint
var chaffBytesMax uint var chaffBytesMax uint
@ -168,6 +171,7 @@ func main() {
flag.BoolVar(&vopt, "v", false, "show version") flag.BoolVar(&vopt, "v", false, "show version")
flag.StringVar(&laddr, "l", ":2000", "interface[:port] to listen") 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(&chaffFreqMin, "cfm", 100, "chaff pkt freq min (msecs)")
flag.UintVar(&chaffFreqMax, "cfM", 5000, "chaff pkt freq max (msecs)") flag.UintVar(&chaffFreqMax, "cfM", 5000, "chaff pkt freq max (msecs)")
flag.UintVar(&chaffBytesMax, "cbM", 64, "chaff pkt size max (bytes)") flag.UintVar(&chaffBytesMax, "cbM", 64, "chaff pkt size max (bytes)")
@ -205,7 +209,9 @@ func main() {
log.Println("Accepted client") log.Println("Accepted client")
// Set up chaffing to 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. // Handle the connection in a new goroutine.
// The loop then returns to accepting, so that // The loop then returns to accepting, so that
@ -276,14 +282,14 @@ func main() {
if rec.op[0] == 'c' { if rec.op[0] == 'c' {
// Non-interactive command // Non-interactive command
log.Println("[Running 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; // Returned hopefully via an EOF or exit/logout;
// Clear current op so user can enter next, or EOF // Clear current op so user can enter next, or EOF
rec.op[0] = 0 rec.op[0] = 0
log.Println("[Command complete]") log.Println("[Command complete]")
} else if rec.op[0] == 's' { } else if rec.op[0] == 's' {
log.Println("[Running shell]") 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; // Returned hopefully via an EOF or exit/logout;
// Clear current op so user can enter next, or EOF // Clear current op so user can enter next, or EOF
rec.op[0] = 0 rec.op[0] = 0