mirror of
https://gogs.blitter.com/RLabs/xs
synced 2024-08-14 10:26:42 +00:00
Misc. fixes to end-of-session conn handling. Outstanding bug w/client chaff enabled & truncated client data
This commit is contained in:
parent
5920e06748
commit
00e03c1d54
3 changed files with 102 additions and 69 deletions
|
@ -439,7 +439,12 @@ func (hc Conn) Read(b []byte) (n int, err error) {
|
||||||
log.Printf("[TermSize pkt: rows %v cols %v]\n", hc.Rows, hc.Cols)
|
log.Printf("[TermSize pkt: rows %v cols %v]\n", hc.Rows, hc.Cols)
|
||||||
hc.WinCh <- WinSize{hc.Rows, hc.Cols}
|
hc.WinCh <- WinSize{hc.Rows, hc.Cols}
|
||||||
} else if ctrlStatOp == CSOExitStatus {
|
} else if ctrlStatOp == CSOExitStatus {
|
||||||
*hc.closeStat = uint8(payloadBytes[0])
|
if len(payloadBytes) > 0 {
|
||||||
|
*hc.closeStat = uint8(payloadBytes[0])
|
||||||
|
} else {
|
||||||
|
log.Println("[truncated payload, cannot determine CSOExitStatus]")
|
||||||
|
*hc.closeStat = 99
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
hc.dBuf.Write(payloadBytes)
|
hc.dBuf.Write(payloadBytes)
|
||||||
//log.Printf("hc.dBuf: %s\n", hex.Dump(hc.dBuf.Bytes()))
|
//log.Printf("hc.dBuf: %s\n", hex.Dump(hc.dBuf.Bytes()))
|
||||||
|
@ -450,10 +455,14 @@ func (hc Conn) Read(b []byte) (n int, err error) {
|
||||||
hTmp := hc.rm.Sum(nil)[0:4]
|
hTmp := hc.rm.Sum(nil)[0:4]
|
||||||
log.Printf("<%04x) HMAC:(i)%s (c)%02x\r\n", decryptN, hex.EncodeToString([]byte(hmacIn[0:])), hTmp)
|
log.Printf("<%04x) HMAC:(i)%s (c)%02x\r\n", decryptN, hex.EncodeToString([]byte(hmacIn[0:])), hTmp)
|
||||||
|
|
||||||
// Log alert if hmac didn't match, corrupted channel
|
if *hc.closeStat == 99 {
|
||||||
if !bytes.Equal(hTmp, []byte(hmacIn[0:])) /*|| hmacIn[0] > 0xf8*/ {
|
log.Println("[cannot verify HMAC]")
|
||||||
fmt.Println("** ALERT - detected HMAC mismatch, possible channel tampering **")
|
} else {
|
||||||
_, _ = hc.c.Write([]byte{CSOHmacInvalid})
|
// Log alert if hmac didn't match, corrupted channel
|
||||||
|
if !bytes.Equal(hTmp, []byte(hmacIn[0:])) /*|| hmacIn[0] > 0xf8*/ {
|
||||||
|
fmt.Println("** ALERT - detected HMAC mismatch, possible channel tampering **")
|
||||||
|
_, _ = hc.c.Write([]byte{CSOHmacInvalid})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
129
hkexsh/hkexsh.go
129
hkexsh/hkexsh.go
|
@ -30,7 +30,7 @@ type cmdSpec struct {
|
||||||
who []byte
|
who []byte
|
||||||
cmd []byte
|
cmd []byte
|
||||||
authCookie []byte
|
authCookie []byte
|
||||||
status int // though UNIX shell exit status is uint8, os.Exit() wants int
|
status int // UNIX exit status is uint8, but os.Exit() wants int
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -86,13 +86,13 @@ func parseNonSwitchArgs(a []string, dp string) (user, host, port, path string, i
|
||||||
fancyPort = dp
|
fancyPort = dp
|
||||||
}
|
}
|
||||||
|
|
||||||
if fancyPath == "" {
|
//if fancyPath == "" {
|
||||||
fancyPath = "."
|
// fancyPath = "."
|
||||||
}
|
//}
|
||||||
|
|
||||||
if i == len(a)-1 {
|
if i == len(a)-1 {
|
||||||
isDest = true
|
isDest = true
|
||||||
fmt.Println("isDest")
|
fmt.Println("remote path isDest")
|
||||||
}
|
}
|
||||||
fmt.Println("fancyArgs: user:", fancyUser, "host:", fancyHost, "port:", fancyPort, "path:", fancyPath)
|
fmt.Println("fancyArgs: user:", fancyUser, "host:", fancyHost, "port:", fancyPort, "path:", fancyPath)
|
||||||
} else {
|
} else {
|
||||||
|
@ -118,6 +118,7 @@ func main() {
|
||||||
version := "0.1pre (NO WARRANTY)"
|
version := "0.1pre (NO WARRANTY)"
|
||||||
var vopt bool
|
var vopt bool
|
||||||
var dbg bool
|
var dbg bool
|
||||||
|
var shellMode bool // if true act as shell, else file copier
|
||||||
var cAlg string
|
var cAlg string
|
||||||
var hAlg string
|
var hAlg string
|
||||||
var server string
|
var server string
|
||||||
|
@ -154,6 +155,7 @@ func main() {
|
||||||
// hkexsh accepts a command (-x) but not
|
// hkexsh accepts a command (-x) but not
|
||||||
// a srcpath (-r) or dstpath (-t)
|
// a srcpath (-r) or dstpath (-t)
|
||||||
flag.StringVar(&cmdStr, "x", "", "command to run (default empty - interactive shell)")
|
flag.StringVar(&cmdStr, "x", "", "command to run (default empty - interactive shell)")
|
||||||
|
shellMode = true
|
||||||
} // else {
|
} // else {
|
||||||
//// hkexcp accepts srcpath (-r) and dstpath (-t), but not
|
//// hkexcp accepts srcpath (-r) and dstpath (-t), but not
|
||||||
//// a command (-x)
|
//// a command (-x)
|
||||||
|
@ -162,19 +164,19 @@ func main() {
|
||||||
//}
|
//}
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
fancyUser, fancyHost, fancyPort, fancyPath, pathIsDest, otherArgs :=
|
tmpUser, tmpHost, tmpPort, tmpPath, pathIsDest, otherArgs :=
|
||||||
parseNonSwitchArgs(flag.Args(), defPort /* defPort */)
|
parseNonSwitchArgs(flag.Args(), defPort /* defPort */)
|
||||||
fmt.Println("otherArgs:", otherArgs)
|
fmt.Println("otherArgs:", otherArgs)
|
||||||
//fmt.Println("fancyHost:", fancyHost)
|
//fmt.Println("tmpHost:", tmpHost)
|
||||||
fmt.Println("fancyPath:", fancyPath)
|
//fmt.Println("tmpPath:", tmpPath)
|
||||||
if fancyUser != "" {
|
if tmpUser != "" {
|
||||||
altUser = fancyUser
|
altUser = tmpUser
|
||||||
}
|
}
|
||||||
if fancyHost != "" {
|
if tmpHost != "" {
|
||||||
server = fancyHost + ":" + fancyPort
|
server = tmpHost + ":" + tmpPort
|
||||||
//fmt.Println("fancyHost sets server to", server)
|
//fmt.Println("tmpHost sets server to", server)
|
||||||
}
|
}
|
||||||
if fancyPath != "" {
|
if tmpPath != "" {
|
||||||
// -if pathIsSrc && len(otherArgs) > 1 ERROR
|
// -if pathIsSrc && len(otherArgs) > 1 ERROR
|
||||||
// -else flatten otherArgs into space-delim list => copySrc
|
// -else flatten otherArgs into space-delim list => copySrc
|
||||||
if pathIsDest {
|
if pathIsDest {
|
||||||
|
@ -183,17 +185,18 @@ func main() {
|
||||||
copySrc = append(copySrc, v...)
|
copySrc = append(copySrc, v...)
|
||||||
}
|
}
|
||||||
fmt.Println(">> copySrc:", string(copySrc))
|
fmt.Println(">> copySrc:", string(copySrc))
|
||||||
copyDst = fancyPath
|
copyDst = tmpPath
|
||||||
} else {
|
} else {
|
||||||
if len(otherArgs) > 1 {
|
if len(otherArgs) > 1 {
|
||||||
log.Fatal("ERROR: cannot specify more than one dest path for copy")
|
log.Fatal("ERROR: cannot specify more than one dest path for copy")
|
||||||
}
|
}
|
||||||
copySrc = []byte(fancyPath)
|
copySrc = []byte(tmpPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//fmt.Println("server finally is:", server)
|
// Do some more option consistency checks
|
||||||
|
|
||||||
|
//fmt.Println("server finally is:", server)
|
||||||
if flag.NFlag() == 0 && server == "" {
|
if flag.NFlag() == 0 && server == "" {
|
||||||
flag.Usage()
|
flag.Usage()
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
|
@ -208,12 +211,33 @@ func main() {
|
||||||
log.Fatal("incompatible options -- either cmd (-x) or copy ops but not both")
|
log.Fatal("incompatible options -- either cmd (-x) or copy ops but not both")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//-------------------------------------------------------------------
|
||||||
|
// Here we have parsed all options and can now carry out
|
||||||
|
// either the shell session or copy operation.
|
||||||
|
_ = shellMode
|
||||||
|
|
||||||
if dbg {
|
if dbg {
|
||||||
log.SetOutput(os.Stdout)
|
log.SetOutput(os.Stdout)
|
||||||
} else {
|
} else {
|
||||||
log.SetOutput(ioutil.Discard)
|
log.SetOutput(ioutil.Discard)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We must make the decision about interactivity before Dial()
|
||||||
|
// as it affects chaffing behaviour. 20180805
|
||||||
|
if len(cmdStr) == 0 {
|
||||||
|
op = []byte{'s'}
|
||||||
|
isInteractive = true
|
||||||
|
} else {
|
||||||
|
op = []byte{'c'}
|
||||||
|
// non-interactive cmds may complete quickly, so chaff earlier/faster
|
||||||
|
// to help ensure there's some cover to the brief traffic.
|
||||||
|
// (ignoring cmdline values)
|
||||||
|
//!DEBUG
|
||||||
|
//chaffEnabled = false
|
||||||
|
chaffFreqMin = 2
|
||||||
|
chaffFreqMax = 10
|
||||||
|
}
|
||||||
|
|
||||||
conn, err := hkexnet.Dial("tcp", server, cAlg, hAlg)
|
conn, err := hkexnet.Dial("tcp", server, cAlg, hAlg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Err!")
|
fmt.Println("Err!")
|
||||||
|
@ -226,14 +250,16 @@ func main() {
|
||||||
// TODO: send flag to server side indicating this
|
// TODO: send flag to server side indicating this
|
||||||
// affects shell command used
|
// affects shell command used
|
||||||
var oldState *hkexsh.State
|
var oldState *hkexsh.State
|
||||||
if isatty.IsTerminal(os.Stdin.Fd()) {
|
if shellMode {
|
||||||
oldState, err = hkexsh.MakeRaw(int(os.Stdin.Fd()))
|
if isatty.IsTerminal(os.Stdin.Fd()) {
|
||||||
if err != nil {
|
oldState, err = hkexsh.MakeRaw(int(os.Stdin.Fd()))
|
||||||
panic(err)
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer func() { _ = hkexsh.Restore(int(os.Stdin.Fd()), oldState) }() // Best effort.
|
||||||
|
} else {
|
||||||
|
log.Println("NOT A TTY")
|
||||||
}
|
}
|
||||||
defer func() { _ = hkexsh.Restore(int(os.Stdin.Fd()), oldState) }() // Best effort.
|
|
||||||
} else {
|
|
||||||
log.Println("NOT A TTY")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var uname string
|
var uname string
|
||||||
|
@ -244,18 +270,6 @@ func main() {
|
||||||
uname = altUser
|
uname = altUser
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(cmdStr) == 0 {
|
|
||||||
op = []byte{'s'}
|
|
||||||
isInteractive = true
|
|
||||||
} else {
|
|
||||||
op = []byte{'c'}
|
|
||||||
// non-interactive cmds may complete quickly, so chaff earlier/faster
|
|
||||||
// to help ensure there's some cover to the brief traffic.
|
|
||||||
// (ignoring cmdline values)
|
|
||||||
chaffFreqMin = 2
|
|
||||||
chaffFreqMax = 10
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(authCookie) == 0 {
|
if len(authCookie) == 0 {
|
||||||
fmt.Printf("Gimme cookie:")
|
fmt.Printf("Gimme cookie:")
|
||||||
ab, err := hkexsh.ReadPassword(int(os.Stdin.Fd()))
|
ab, err := hkexsh.ReadPassword(int(os.Stdin.Fd()))
|
||||||
|
@ -288,32 +302,25 @@ func main() {
|
||||||
conn.SetupChaff(chaffFreqMin, chaffFreqMax, chaffBytesMax) // enable client->server chaffing
|
conn.SetupChaff(chaffFreqMin, chaffFreqMax, chaffBytesMax) // enable client->server chaffing
|
||||||
if chaffEnabled {
|
if chaffEnabled {
|
||||||
conn.EnableChaff()
|
conn.EnableChaff()
|
||||||
|
//defer conn.DisableChaff()
|
||||||
|
//defer conn.ShutdownChaff()
|
||||||
}
|
}
|
||||||
defer conn.DisableChaff()
|
|
||||||
defer conn.ShutdownChaff()
|
|
||||||
|
|
||||||
//client reader (from server) goroutine
|
//client reader (from server) goroutine
|
||||||
|
//Read remote end's stdout
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
// By deferring a call to wg.Done(),
|
// By deferring a call to wg.Done(),
|
||||||
// each goroutine guarantees that it marks
|
// each goroutine guarantees that it marks
|
||||||
// its direction's stream as finished.
|
// its direction's stream as finished.
|
||||||
//
|
|
||||||
// Whichever direction's goroutine finishes first
|
|
||||||
// will call wg.Done() once more, explicitly, to
|
|
||||||
// hang up on the other side, so that this client
|
|
||||||
// exits immediately on an EOF from either side.
|
|
||||||
defer wg.Done()
|
|
||||||
|
|
||||||
// io.Copy() expects EOF so this will
|
// io.Copy() expects EOF so normally this will
|
||||||
// exit with inerr == nil
|
// exit with inerr == nil
|
||||||
_, inerr := io.Copy(os.Stdout, conn)
|
_, inerr := io.Copy(os.Stdout, conn)
|
||||||
if inerr != nil {
|
if inerr != nil {
|
||||||
if inerr.Error() != "EOF" {
|
fmt.Println(inerr)
|
||||||
fmt.Println(inerr)
|
_ = hkexsh.Restore(int(os.Stdin.Fd()), oldState) // Best effort.
|
||||||
_ = hkexsh.Restore(int(os.Stdin.Fd()), oldState) // Best effort.
|
os.Exit(1)
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rec.status = int(conn.GetStatus())
|
rec.status = int(conn.GetStatus())
|
||||||
|
@ -322,41 +329,43 @@ func main() {
|
||||||
if isInteractive {
|
if isInteractive {
|
||||||
log.Println("[* Got EOF *]")
|
log.Println("[* Got EOF *]")
|
||||||
_ = hkexsh.Restore(int(os.Stdin.Fd()), oldState) // Best effort.
|
_ = hkexsh.Restore(int(os.Stdin.Fd()), oldState) // Best effort.
|
||||||
wg.Done()
|
|
||||||
//os.Exit(rec.status)
|
|
||||||
}
|
}
|
||||||
|
wg.Done()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// Only look for data from stdin to send to remote end
|
||||||
|
// for interactive sessions.
|
||||||
if isInteractive {
|
if isInteractive {
|
||||||
handleTermResizes(conn)
|
handleTermResizes(conn)
|
||||||
|
|
||||||
// client writer (to server) goroutine
|
// client writer (to server) goroutine
|
||||||
|
// Write local stdin to remote end
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
||||||
// Copy() expects EOF so this will
|
// Copy() expects EOF so this will
|
||||||
// exit with outerr == nil
|
// exit with outerr == nil
|
||||||
//!_, outerr := io.Copy(conn, os.Stdin)
|
//!_, outerr := io.Copy(conn, os.Stdin)
|
||||||
_, outerr := func(conn *hkexnet.Conn, r io.Reader) (w int64, e error) {
|
_, outerr := func(conn *hkexnet.Conn, r io.Reader) (w int64, e error) {
|
||||||
return io.Copy(conn, r)
|
w, e = io.Copy(conn, r)
|
||||||
|
return w, e
|
||||||
}(conn, os.Stdin)
|
}(conn, os.Stdin)
|
||||||
|
|
||||||
if outerr != nil {
|
if outerr != nil {
|
||||||
log.Println(outerr)
|
log.Println(outerr)
|
||||||
if outerr.Error() != "EOF" {
|
fmt.Println(outerr)
|
||||||
fmt.Println(outerr)
|
_ = hkexsh.Restore(int(os.Stdin.Fd()), oldState) // Best effort.
|
||||||
_ = hkexsh.Restore(int(os.Stdin.Fd()), oldState) // Best effort.
|
os.Exit(255)
|
||||||
os.Exit(255)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
log.Println("[Sent EOF]")
|
log.Println("[Sent EOF]")
|
||||||
wg.Done() // client hung up, close WaitGroup to exit client
|
wg.Done()
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait until both stdin and stdout goroutines finish
|
// Wait until both stdin and stdout goroutines finish
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
conn.DisableChaff()
|
||||||
|
conn.ShutdownChaff()
|
||||||
|
|
||||||
_ = hkexsh.Restore(int(os.Stdin.Fd()), oldState) // Best effort.
|
_ = hkexsh.Restore(int(os.Stdin.Fd()), oldState) // Best effort.
|
||||||
os.Exit(rec.status)
|
os.Exit(rec.status)
|
||||||
|
|
|
@ -18,6 +18,7 @@ import (
|
||||||
"os/user"
|
"os/user"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"blitter.com/go/goutmp"
|
"blitter.com/go/goutmp"
|
||||||
|
@ -83,6 +84,7 @@ func runCmdAs(who string, cmd string, conn hkex.Conn) (err error) {
|
||||||
//
|
//
|
||||||
// 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 hkexnet.Conn, chaffing bool) (err error, exitStatus int) {
|
func runShellAs(who string, cmd string, interactive bool, conn hkexnet.Conn, chaffing bool) (err error, exitStatus int) {
|
||||||
|
var wg sync.WaitGroup
|
||||||
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)
|
||||||
|
@ -135,15 +137,16 @@ func runShellAs(who string, cmd string, interactive bool, conn hkexnet.Conn, cha
|
||||||
log.Printf("[Setting term size to: %v %v]\n", sz.Rows, sz.Cols)
|
log.Printf("[Setting term size to: %v %v]\n", sz.Rows, sz.Cols)
|
||||||
pty.Setsize(ptmx, &pty.Winsize{Rows: sz.Rows, Cols: sz.Cols})
|
pty.Setsize(ptmx, &pty.Winsize{Rows: sz.Rows, Cols: sz.Cols})
|
||||||
}
|
}
|
||||||
|
fmt.Println("*** WinCh goroutine done ***")
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Copy stdin to the pty.. (bgnd goroutine)
|
// Copy stdin to the pty.. (bgnd goroutine)
|
||||||
go func() {
|
go func() {
|
||||||
_, e := io.Copy(ptmx, conn)
|
_, e := io.Copy(ptmx, conn)
|
||||||
if e != nil {
|
if e != nil {
|
||||||
log.Printf("** std->pty ended **\n")
|
log.Println("** stdin->pty ended **:", e.Error())
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
fmt.Println("*** stdin->pty goroutine done ***")
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if chaffing {
|
if chaffing {
|
||||||
|
@ -153,17 +156,26 @@ func runShellAs(who string, cmd string, interactive bool, conn hkexnet.Conn, cha
|
||||||
defer conn.ShutdownChaff()
|
defer conn.ShutdownChaff()
|
||||||
|
|
||||||
// ..and the pty to stdout.
|
// ..and the pty to stdout.
|
||||||
|
// This may take some time exceeding that of the
|
||||||
|
// actual command's lifetime, so the c.Wait() below
|
||||||
|
// must synchronize with the completion of this goroutine
|
||||||
|
// to ensure all stdout data gets to the client before
|
||||||
|
// connection is closed.
|
||||||
|
wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
_, e := io.Copy(conn, ptmx)
|
_, e := io.Copy(conn, ptmx)
|
||||||
if e != nil {
|
if e != nil {
|
||||||
log.Printf("** pty->stdout ended **\n")
|
log.Println("** pty->stdout ended **:", e.Error())
|
||||||
return
|
//wg.Done() //!return
|
||||||
}
|
}
|
||||||
// The above io.Copy() will exit when the command attached
|
// The above io.Copy() will exit when the command attached
|
||||||
// to the pty exits
|
// to the pty exits
|
||||||
|
fmt.Println("*** pty->stdout goroutine done ***")
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if err := c.Wait(); err != nil {
|
if err := c.Wait(); err != nil {
|
||||||
|
fmt.Println("*** c.Wait() done ***")
|
||||||
if exiterr, ok := err.(*exec.ExitError); ok {
|
if exiterr, ok := err.(*exec.ExitError); ok {
|
||||||
// The program has exited with an exit code != 0
|
// The program has exited with an exit code != 0
|
||||||
|
|
||||||
|
@ -177,6 +189,9 @@ func runShellAs(who string, cmd string, interactive bool, conn hkexnet.Conn, cha
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
wg.Wait() // Wait on pty->stdout completion to client
|
||||||
|
//conn.DisableChaff()
|
||||||
|
//conn.ShutdownChaff()
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue