Console esc seqs no longer affect in-band input

Signed-off-by: Russ Magee <rmagee@gmail.com>
This commit is contained in:
Russ Magee 2018-12-08 21:37:26 -08:00
parent f83cdd23b1
commit 9641fd3fff
2 changed files with 61 additions and 67 deletions

View file

@ -42,6 +42,7 @@ import (
"math/big" "math/big"
"math/rand" "math/rand"
"net" "net"
"os"
"strings" "strings"
"sync" "sync"
"time" "time"
@ -122,11 +123,8 @@ type (
dBuf *bytes.Buffer //decrypt buffer for Read() dBuf *bytes.Buffer //decrypt buffer for Read()
} }
EscSeqs struct { EscHandler func(io.Writer)
idx int EscSeqs map[byte]EscHandler
seqs []byte
outstr []string
}
) )
var ( var (
@ -1136,60 +1134,40 @@ func Copy(dst io.Writer, src io.Reader) (written int64, err error) {
return return
} }
func escSeqScanner(s *EscSeqs, dst io.Writer, b byte) (passthru bool) {
passthru = true
if s.idx > 0 {
switch b {
case '~':
return
case s.seqs[0]:
dst.Write([]byte(s.outstr[0]))
passthru = false
b = '~'
case s.seqs[1]:
dst.Write([]byte(s.outstr[1]))
passthru = false
b = '~'
case s.seqs[2]:
dst.Write([]byte(s.outstr[2]))
passthru = false
b = '~'
}
}
if b == '~' {
s.idx++
} else {
s.idx = 0
}
return
}
// copyBuffer is the actual implementation of Copy and CopyBuffer. // copyBuffer is the actual implementation of Copy and CopyBuffer.
// if buf is nil, one is allocated. // if buf is nil, one is allocated.
func copyBuffer(dst io.Writer, src io.Reader, buf []byte) (written int64, err error) { func copyBuffer(dst io.Writer, src io.Reader, buf []byte) (written int64, err error) {
escs := &EscSeqs{idx: 0, // NOTE: using dst.Write() in these esc funcs will cause the output
seqs: []byte{ // to function as a 'macro', outputting as if user typed the sequence.
'i', //
't', // Using os.Stdout outputs to the client's term w/o it or the server
'B', // 'seeing' the output.
}, //
outstr: []string{ // TODO: Devise a way to signal to main client thread that
"\x1b[s\x1b[2;1H\x1b[1;31m[HKEXSH]\x1b[39;49m\x1b[u", // a goroutine should be spawned to do long-lived tasks for
"\x1b[1;32m[HKEXSH]\x1b[39;49m", // some esc sequences (eg., a time ticker in the corner of terminal,
"\x1b[1;32m" + Bob + "\x1b[39;49m", // or tunnel traffic indicator - note we cannot just spawn a goroutine
}, // here, as copyBuffer() returns after each burst of data. Scope must
// outlive individual copyBuffer calls).
// (Note that since this custom copyBuffer func is used only by
// the hkexsh client, it should eventually be moved to client.)
escs := EscSeqs{
'i': func(io.Writer) { os.Stdout.Write([]byte("\x1b[s\x1b[2;1H\x1b[1;31m[HKEXSH]\x1b[39;49m\x1b[u")) },
't': func(io.Writer) { os.Stdout.Write([]byte("\x1b[1;32m[HKEXSH]\x1b[39;49m")) },
'B': func(io.Writer) { os.Stdout.Write([]byte("\x1b[1;32m" + Bob + "\x1b[39;49m")) },
} }
// If the reader has a WriteTo method, use it to do the copy. /*
// Avoids an allocation and a copy. // If the reader has a WriteTo method, use it to do the copy.
if wt, ok := src.(io.WriterTo); ok { // Avoids an allocation and a copy.
return wt.WriteTo(dst) if wt, ok := src.(io.WriterTo); ok {
} return wt.WriteTo(dst)
// Similarly, if the writer has a ReadFrom method, use it to do the copy. }
if rt, ok := dst.(io.ReaderFrom); ok { // Similarly, if the writer has a ReadFrom method, use it to do the copy.
return rt.ReadFrom(src) if rt, ok := dst.(io.ReaderFrom); ok {
} return rt.ReadFrom(src)
}
*/
if buf == nil { if buf == nil {
size := 32 * 1024 size := 32 * 1024
if l, ok := src.(*io.LimitedReader); ok && int64(size) > l.N { if l, ok := src.(*io.LimitedReader); ok && int64(size) > l.N {
@ -1201,23 +1179,39 @@ func copyBuffer(dst io.Writer, src io.Reader, buf []byte) (written int64, err er
} }
buf = make([]byte, size) buf = make([]byte, size)
} }
var seqPos int
for { for {
nr, er := src.Read(buf) nr, er := src.Read(buf)
if nr > 0 { if nr > 0 {
// Look for sequences to trigger client-side diags // Look for sequences to trigger client-side diags
if escSeqScanner(escs, dst, buf[0]) { // A repeat of 4 keys (conveniently 'dead' chars for most
nw, ew := dst.Write(buf[0:nr]) // interactive shells; here CTRL-]) shall introduce
if nw > 0 { // some special responses or actions on the client side.
written += int64(nw) if seqPos < 4 {
if buf[0] == 0x1d {
seqPos++
} }
if ew != nil { } else /* seqPos > 0 */ {
err = ew if v, ok := escs[buf[0]]; ok {
break v(dst)
} nr--
if nr != nw { buf = buf[1:]
err = io.ErrShortWrite
break
} }
seqPos = 0
}
nw, ew := dst.Write(buf[0:nr])
if nw > 0 {
written += int64(nw)
}
if ew != nil {
err = ew
break
}
if nr != nw {
err = io.ErrShortWrite
break
} }
} }
if er != nil { if er != nil {

View file

@ -249,7 +249,7 @@ func doShellMode(isInteractive bool, conn *hkexnet.Conn, oldState *hkexsh.State,
// pkg io/Copy expects EOF so normally this will // pkg io/Copy expects EOF so normally this will
// exit with inerr == nil // exit with inerr == nil
_, inerr := hkexnet.Copy(os.Stdout, conn) _, inerr := io.Copy(os.Stdout, conn)
if inerr != nil { if inerr != nil {
_ = hkexsh.Restore(int(os.Stdin.Fd()), oldState) // #nosec _ = hkexsh.Restore(int(os.Stdin.Fd()), oldState) // #nosec
// Copy operations and user logging off will cause // Copy operations and user logging off will cause
@ -288,7 +288,7 @@ func doShellMode(isInteractive bool, conn *hkexnet.Conn, oldState *hkexsh.State,
_, outerr := func(conn *hkexnet.Conn, r io.Reader) (w int64, e error) { _, outerr := func(conn *hkexnet.Conn, r io.Reader) (w int64, e error) {
// Copy() expects EOF so this will // Copy() expects EOF so this will
// exit with outerr == nil // exit with outerr == nil
w, e = io.Copy(conn, r) w, e = hkexnet.Copy(conn, r)
return w, e return w, e
}(conn, os.Stdin) }(conn, os.Stdin)