Merge branch 'exitstatus' of Russtopia/hkexsh into master

This commit is contained in:
Russtopia 2018-06-29 19:42:00 -07:00 committed by Gogs
commit ddc85a64f3
3 changed files with 200 additions and 148 deletions

View file

@ -11,7 +11,9 @@ package hkexsh
// Implementation of HKEx-wrapped versions of the golang standard // Implementation of HKEx-wrapped versions of the golang standard
// net package interfaces, allowing clients and servers to simply replace // net package interfaces, allowing clients and servers to simply replace
// 'net.Dial' and 'net.Listen' with 'hkex.Dial' and 'hkex.Listen'. // 'net.Dial' and 'net.Listen' with 'hkex.Dial' and 'hkex.Listen'
// (though some extra methods are implemented and must be used
// for things outside of the scope of plain sockets).
import ( import (
"bytes" "bytes"
"crypto/cipher" "crypto/cipher"
@ -53,7 +55,7 @@ type ChaffConfig struct {
szMax uint // max size in bytes szMax uint // max size in bytes
} }
// Conn is a HKex connection - a drop-in replacement for net.Conn // Conn is a HKex connection - a superset of net.Conn
type Conn struct { type Conn struct {
m *sync.Mutex m *sync.Mutex
c net.Conn // which also implements io.Reader, io.Writer, ... c net.Conn // which also implements io.Reader, io.Writer, ...
@ -66,6 +68,7 @@ type Conn struct {
chaff ChaffConfig chaff ChaffConfig
closeStat *uint8 // close status (shell exit status: UNIX uint8)
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
@ -73,21 +76,30 @@ type Conn struct {
dBuf *bytes.Buffer //decrypt buffer for Read() dBuf *bytes.Buffer //decrypt buffer for Read()
} }
func (hc Conn) GetStatus() uint8 {
return *hc.closeStat
}
func (hc *Conn) SetStatus(stat uint8) {
*hc.closeStat = stat
log.Println("closeStat:", *hc.closeStat)
}
// ConnOpts returns the cipher/hmac options value, which is sent to the // ConnOpts returns the cipher/hmac options value, which is sent to the
// peer but is not itself part of the KEx. // peer but is not itself part of the KEx.
// //
// (Used for protocol-level negotiations after KEx such as // (Used for protocol-level negotiations after KEx such as
// cipher/HMAC algorithm options etc.) // cipher/HMAC algorithm options etc.)
func (c Conn) ConnOpts() uint32 { func (hc Conn) ConnOpts() uint32 {
return c.cipheropts return hc.cipheropts
} }
// SetConnOpts sets the cipher/hmac options value, which is sent to the // SetConnOpts sets the cipher/hmac options value, which is sent to the
// peer as part of KEx but not part of the KEx itself. // peer as part of KEx but not part of the KEx itself.
// //
// opts - bitfields for cipher and hmac alg. to use after KEx // opts - bitfields for cipher and hmac alg. to use after KEx
func (c *Conn) SetConnOpts(copts uint32) { func (hc *Conn) SetConnOpts(copts uint32) {
c.cipheropts = copts hc.cipheropts = copts
} }
// Opts returns the protocol options value, which is sent to the peer // Opts returns the protocol options value, which is sent to the peer
@ -95,8 +107,8 @@ func (c *Conn) SetConnOpts(copts uint32) {
// //
// Consumers of this lib may use this for protocol-level options not part // Consumers of this lib may use this for protocol-level options not part
// of the KEx or encryption info used by the connection. // of the KEx or encryption info used by the connection.
func (c Conn) Opts() uint32 { func (hc Conn) Opts() uint32 {
return c.opts return hc.opts
} }
// SetOpts sets the protocol options value, which is sent to the peer // SetOpts sets the protocol options value, which is sent to the peer
@ -106,32 +118,32 @@ func (c Conn) Opts() uint32 {
// of the KEx of encryption info used by the connection. // of the KEx of encryption info used by the connection.
// //
// opts - a uint32, caller-defined // opts - a uint32, caller-defined
func (c *Conn) SetOpts(opts uint32) { func (hc *Conn) SetOpts(opts uint32) {
c.opts = opts hc.opts = opts
} }
func (c *Conn) applyConnExtensions(extensions ...string) { func (hc *Conn) applyConnExtensions(extensions ...string) {
for _, s := range extensions { for _, s := range extensions {
switch s { switch s {
case "C_AES_256": case "C_AES_256":
log.Println("[extension arg = C_AES_256]") log.Println("[extension arg = C_AES_256]")
c.cipheropts &= (0xFFFFFF00) hc.cipheropts &= (0xFFFFFF00)
c.cipheropts |= CAlgAES256 hc.cipheropts |= CAlgAES256
break break
case "C_TWOFISH_128": case "C_TWOFISH_128":
log.Println("[extension arg = C_TWOFISH_128]") log.Println("[extension arg = C_TWOFISH_128]")
c.cipheropts &= (0xFFFFFF00) hc.cipheropts &= (0xFFFFFF00)
c.cipheropts |= CAlgTwofish128 hc.cipheropts |= CAlgTwofish128
break break
case "C_BLOWFISH_64": case "C_BLOWFISH_64":
log.Println("[extension arg = C_BLOWFISH_64]") log.Println("[extension arg = C_BLOWFISH_64]")
c.cipheropts &= (0xFFFFFF00) hc.cipheropts &= (0xFFFFFF00)
c.cipheropts |= CAlgBlowfish64 hc.cipheropts |= CAlgBlowfish64
break break
case "H_SHA256": case "H_SHA256":
log.Println("[extension arg = H_SHA256]") log.Println("[extension arg = H_SHA256]")
c.cipheropts &= (0xFFFF00FF) hc.cipheropts &= (0xFFFF00FF)
c.cipheropts |= (HmacSHA256 << 8) hc.cipheropts |= (HmacSHA256 << 8)
break break
default: default:
log.Printf("[Dial ext \"%s\" ignored]\n", s) log.Printf("[Dial ext \"%s\" ignored]\n", s)
@ -154,7 +166,7 @@ func Dial(protocol string, ipport string, extensions ...string) (hc *Conn, err e
return nil, err return nil, err
} }
// Init hkexnet.Conn hc over net.Conn c // Init hkexnet.Conn hc over net.Conn c
hc = &Conn{m: &sync.Mutex{}, c: c, h: New(0, 0), dBuf: new(bytes.Buffer)} hc = &Conn{m: &sync.Mutex{}, c: c, closeStat: new(uint8), h: New(0, 0), dBuf: new(bytes.Buffer)}
hc.applyConnExtensions(extensions...) hc.applyConnExtensions(extensions...)
// Send hkexnet.Conn parameters to remote side // Send hkexnet.Conn parameters to remote side
@ -181,25 +193,29 @@ func Dial(protocol string, ipport string, extensions ...string) (hc *Conn, err e
hc.r, hc.rm, err = hc.getStream(hc.h.fa) hc.r, hc.rm, err = hc.getStream(hc.h.fa)
hc.w, hc.wm, err = hc.getStream(hc.h.fa) hc.w, hc.wm, err = hc.getStream(hc.h.fa)
*hc.closeStat = 99 // open or prematurely-closed status
return return
} }
// Close a hkex.Conn // Close a hkex.Conn
func (c Conn) Close() (err error) { func (hc Conn) Close() (err error) {
c.DisableChaff() hc.DisableChaff()
err = c.c.Close() hc.WritePacket([]byte{byte(*hc.closeStat)}, CSOExitStatus)
*hc.closeStat = 0
err = hc.c.Close()
log.Println("[Conn Closing]") log.Println("[Conn Closing]")
return return
} }
// LocalAddr returns the local network address. // LocalAddr returns the local network address.
func (c Conn) LocalAddr() net.Addr { func (hc Conn) LocalAddr() net.Addr {
return c.c.LocalAddr() return hc.c.LocalAddr()
} }
// RemoteAddr returns the remote network address. // RemoteAddr returns the remote network address.
func (c Conn) RemoteAddr() net.Addr { func (hc Conn) RemoteAddr() net.Addr {
return c.c.RemoteAddr() return hc.c.RemoteAddr()
} }
// SetDeadline sets the read and write deadlines associated // SetDeadline sets the read and write deadlines associated
@ -217,8 +233,8 @@ func (c Conn) RemoteAddr() net.Addr {
// the deadline after successful Read or Write calls. // the deadline after successful Read or Write calls.
// //
// A zero value for t means I/O operations will not time out. // A zero value for t means I/O operations will not time out.
func (c Conn) SetDeadline(t time.Time) error { func (hc Conn) SetDeadline(t time.Time) error {
return c.SetDeadline(t) return hc.c.SetDeadline(t)
} }
// SetWriteDeadline sets the deadline for future Write calls // SetWriteDeadline sets the deadline for future Write calls
@ -226,15 +242,15 @@ func (c Conn) SetDeadline(t time.Time) error {
// Even if write times out, it may return n > 0, indicating that // Even if write times out, it may return n > 0, indicating that
// some of the data was successfully written. // some of the data was successfully written.
// A zero value for t means Write will not time out. // A zero value for t means Write will not time out.
func (c Conn) SetWriteDeadline(t time.Time) error { func (hc Conn) SetWriteDeadline(t time.Time) error {
return c.SetWriteDeadline(t) return hc.c.SetWriteDeadline(t)
} }
// SetReadDeadline sets the deadline for future Read calls // SetReadDeadline sets the deadline for future Read calls
// and any currently-blocked Read call. // and any currently-blocked Read call.
// A zero value for t means Read will not time out. // A zero value for t means Read will not time out.
func (c Conn) SetReadDeadline(t time.Time) error { func (hc Conn) SetReadDeadline(t time.Time) error {
return c.SetReadDeadline(t) return hc.c.SetReadDeadline(t)
} }
/*---------------------------------------------------------------------*/ /*---------------------------------------------------------------------*/
@ -282,13 +298,13 @@ func (hl HKExListener) Accept() (hc Conn, err error) {
// Open raw Conn c // Open raw Conn c
c, err := hl.l.Accept() c, err := hl.l.Accept()
if err != nil { if err != nil {
hc := Conn{m: &sync.Mutex{}, c: nil, h: nil, cipheropts: 0, opts: 0, hc := Conn{m: &sync.Mutex{}, c: nil, h: nil, closeStat: new(uint8), cipheropts: 0, opts: 0,
r: nil, w: nil} r: nil, w: nil}
return hc, err return hc, err
} }
log.Println("[Accepted]") log.Println("[Accepted]")
hc = Conn{m: &sync.Mutex{}, c: c, h: New(0, 0), WinCh: make(chan WinSize, 1), hc = Conn{m: &sync.Mutex{}, c: c, h: New(0, 0), closeStat: new(uint8), WinCh: make(chan WinSize, 1),
dBuf: new(bytes.Buffer)} dBuf: new(bytes.Buffer)}
// Read in hkexnet.Conn parameters over raw Conn c // Read in hkexnet.Conn parameters over raw Conn c
@ -324,11 +340,11 @@ func (hl HKExListener) Accept() (hc Conn, err error) {
// Read into a byte slice // Read into a byte slice
// //
// See go doc io.Reader // See go doc io.Reader
func (c Conn) Read(b []byte) (n int, err error) { func (hc Conn) Read(b []byte) (n int, err error) {
//log.Printf("[Decrypting...]\r\n") //log.Printf("[Decrypting...]\r\n")
for { for {
//log.Printf("c.dBuf.Len(): %d\n", c.dBuf.Len()) //log.Printf("hc.dBuf.Len(): %d\n", hc.dBuf.Len())
if c.dBuf.Len() > 0 /* len(b) */ { if hc.dBuf.Len() > 0 /* len(b) */ {
break break
} }
@ -337,16 +353,16 @@ func (c Conn) Read(b []byte) (n int, err error) {
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(hc.c, binary.BigEndian, &ctrlStatOp)
log.Printf("[ctrlStatOp: %v]\n", ctrlStatOp) log.Printf("[ctrlStatOp: %v]\n", ctrlStatOp)
if ctrlStatOp == CSOHmacInvalid { if ctrlStatOp == CSOHmacInvalid {
// Other side indicated channel tampering, close channel // Other side indicated channel tampering, close channel
c.Close() hc.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 **")
} }
// Read the hmac and payload len first // Read the hmac and payload len first
err = binary.Read(c.c, binary.BigEndian, &hmacIn) err = binary.Read(hc.c, binary.BigEndian, &hmacIn)
// Normal client 'exit' from interactive session will cause // Normal client 'exit' from interactive session will cause
// (on server side) err.Error() == "<iface/addr info ...>: use of closed network connection" // (on server side) err.Error() == "<iface/addr info ...>: use of closed network connection"
if err != nil { if err != nil {
@ -358,7 +374,7 @@ func (c Conn) Read(b []byte) (n int, err error) {
return 0, err return 0, err
} }
err = binary.Read(c.c, binary.BigEndian, &payloadLen) err = binary.Read(hc.c, binary.BigEndian, &payloadLen)
if err != nil { if err != nil {
if err.Error() != "EOF" { if err.Error() != "EOF" {
log.Println("unexpected Read() err:", err) log.Println("unexpected Read() err:", err)
@ -371,13 +387,13 @@ func (c Conn) Read(b []byte) (n int, err error) {
if payloadLen > 16384 { if payloadLen > 16384 {
log.Printf("[Insane payloadLen:%v]\n", payloadLen) log.Printf("[Insane payloadLen:%v]\n", payloadLen)
c.Close() hc.Close()
return 1, errors.New("Insane payloadLen") return 1, errors.New("Insane payloadLen")
} }
//log.Println("payloadLen:", payloadLen) //log.Println("payloadLen:", payloadLen)
var payloadBytes = make([]byte, payloadLen) var payloadBytes = make([]byte, payloadLen)
n, err = io.ReadFull(c.c, payloadBytes) n, err = io.ReadFull(hc.c, payloadBytes)
//log.Print(" << Read ", n, " payloadBytes") //log.Print(" << Read ", n, " payloadBytes")
// Normal client 'exit' from interactive session will cause // Normal client 'exit' from interactive session will cause
@ -396,7 +412,7 @@ func (c Conn) Read(b []byte) (n int, err error) {
// The StreamReader acts like a pipe, decrypting // The StreamReader acts like a pipe, decrypting
// whatever is available and forwarding the result // whatever is available and forwarding the result
// to the parameter of Read() as a normal io.Reader // to the parameter of Read() as a normal io.Reader
rs := &cipher.StreamReader{S: c.r, R: db} rs := &cipher.StreamReader{S: hc.r, R: db}
// The caller isn't necessarily reading the full payload so we need // The caller isn't necessarily reading the full payload so we need
// to decrypt ot an intermediate buffer, draining it on demand of caller // to decrypt ot an intermediate buffer, draining it on demand of caller
decryptN, err := rs.Read(payloadBytes) decryptN, err := rs.Read(payloadBytes)
@ -409,75 +425,77 @@ func (c Conn) Read(b []byte) (n int, err error) {
if ctrlStatOp == CSOChaff { if ctrlStatOp == CSOChaff {
log.Printf("[Chaff pkt, discarded (len %d)]\n", decryptN) log.Printf("[Chaff pkt, discarded (len %d)]\n", decryptN)
} else if ctrlStatOp == CSOTermSize { } else if ctrlStatOp == CSOTermSize {
fmt.Sscanf(string(payloadBytes), "%d %d", &c.Rows, &c.Cols) fmt.Sscanf(string(payloadBytes), "%d %d", &hc.Rows, &hc.Cols)
log.Printf("[TermSize pkt: rows %v cols %v]\n", c.Rows, c.Cols) log.Printf("[TermSize pkt: rows %v cols %v]\n", hc.Rows, hc.Cols)
c.WinCh <- WinSize{c.Rows, c.Cols} hc.WinCh <- WinSize{hc.Rows, hc.Cols}
} else if ctrlStatOp == CSOExitStatus {
*hc.closeStat = uint8(payloadBytes[0])
} else { } else {
c.dBuf.Write(payloadBytes) hc.dBuf.Write(payloadBytes)
//log.Printf("c.dBuf: %s\n", hex.Dump(c.dBuf.Bytes())) //log.Printf("hc.dBuf: %s\n", hex.Dump(hc.dBuf.Bytes()))
} }
// Re-calculate hmac, compare with received value // Re-calculate hmac, compare with received value
c.rm.Write(payloadBytes) hc.rm.Write(payloadBytes)
hTmp := c.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 // 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}) _, _ = hc.c.Write([]byte{CSOHmacInvalid})
} }
} }
retN := c.dBuf.Len() retN := hc.dBuf.Len()
if retN > len(b) { if retN > len(b) {
retN = len(b) retN = len(b)
} }
log.Printf("Read() got %d bytes\n", retN) log.Printf("Read() got %d bytes\n", retN)
copy(b, c.dBuf.Next(retN)) copy(b, hc.dBuf.Next(retN))
//log.Printf("As Read() returns, c.dBuf is %d long: %s\n", c.dBuf.Len(), hex.Dump(c.dBuf.Bytes())) //log.Printf("As Read() returns, hc.dBuf is %d long: %s\n", hc.dBuf.Len(), hex.Dump(hc.dBuf.Bytes()))
return retN, nil return retN, nil
} }
// Write a byte slice // Write a byte slice
// //
// See go doc io.Writer // See go doc io.Writer
func (c Conn) Write(b []byte) (n int, err error) { func (hc Conn) Write(b []byte) (n int, err error) {
n, err = c.WritePacket(b, CSONone) n, err = hc.WritePacket(b, CSONone)
return n, err return n, err
} }
// Write a byte slice with specified ctrlStatusOp byte // Write a byte slice with specified ctrlStatusOp byte
func (c Conn) WritePacket(b []byte, op byte) (n int, err error) { func (hc 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
// N.B. Originally this Lock() surrounded only the // N.B. Originally this Lock() surrounded only the
// calls to binary.Write(c.c ..) however there appears // calls to binary.Write(hc.c ..) however there appears
// to be some other unshareable state in the Conn // to be some other unshareable state in the Conn
// struct that must be protected to serialize main and // struct that must be protected to serialize main and
// chaff data written to it. // chaff data written to it.
// //
// Would be nice to determine if the mutex scope // Would be nice to determine if the mutex scope
// could be tightened. // could be tightened.
c.m.Lock() hc.m.Lock()
{ {
log.Printf(" :>ptext:\r\n%s\r\n", hex.Dump(b)) log.Printf(" :>ptext:\r\n%s\r\n", hex.Dump(b))
payloadLen = uint32(len(b)) payloadLen = uint32(len(b))
// Calculate hmac on payload // Calculate hmac on payload
c.wm.Write(b) hc.wm.Write(b)
hmacOut = c.wm.Sum(nil)[0:4] hmacOut = hc.wm.Sum(nil)[0:4]
log.Printf(" (%04x> HMAC(o):%s\r\n", payloadLen, hex.EncodeToString(hmacOut)) log.Printf(" (%04x> HMAC(o):%s\r\n", payloadLen, hex.EncodeToString(hmacOut))
var wb bytes.Buffer var wb bytes.Buffer
// The StreamWriter acts like a pipe, forwarding whatever is // The StreamWriter acts like a pipe, forwarding whatever is
// written to it through the cipher, encrypting as it goes // written to it through the cipher, encrypting as it goes
ws := &cipher.StreamWriter{S: c.w, W: &wb} ws := &cipher.StreamWriter{S: hc.w, W: &wb}
_, err = ws.Write(b) _, err = ws.Write(b)
if err != nil { if err != nil {
panic(err) panic(err)
@ -486,19 +504,19 @@ func (c Conn) WritePacket(b []byte, op byte) (n int, err error) {
ctrlStatOp := op ctrlStatOp := op
err = binary.Write(c.c, binary.BigEndian, &ctrlStatOp) err = binary.Write(hc.c, binary.BigEndian, &ctrlStatOp)
if err == nil { if err == nil {
// Write hmac LSB, payloadLen followed by payload // Write hmac LSB, payloadLen followed by payload
err = binary.Write(c.c, binary.BigEndian, hmacOut) err = binary.Write(hc.c, binary.BigEndian, hmacOut)
if err == nil { if err == nil {
err = binary.Write(c.c, binary.BigEndian, payloadLen) err = binary.Write(hc.c, binary.BigEndian, payloadLen)
if err == nil { if err == nil {
n, err = c.c.Write(wb.Bytes()) n, err = hc.c.Write(wb.Bytes())
} }
} }
} }
} }
c.m.Unlock() hc.m.Unlock()
if err != nil { if err != nil {
//panic(err) //panic(err)
@ -507,48 +525,48 @@ func (c Conn) WritePacket(b []byte, op byte) (n int, err error) {
return return
} }
func (c *Conn) EnableChaff() { func (hc *Conn) EnableChaff() {
c.chaff.shutdown = false hc.chaff.shutdown = false
c.chaff.enabled = true hc.chaff.enabled = true
log.Println("Chaffing ENABLED") log.Println("Chaffing ENABLED")
c.chaffHelper() hc.chaffHelper()
} }
func (c *Conn) DisableChaff() { func (hc *Conn) DisableChaff() {
c.chaff.enabled = false hc.chaff.enabled = false
log.Println("Chaffing DISABLED") log.Println("Chaffing DISABLED")
} }
func (c *Conn) ShutdownChaff() { func (hc *Conn) ShutdownChaff() {
c.chaff.shutdown = true hc.chaff.shutdown = true
log.Println("Chaffing SHUTDOWN") log.Println("Chaffing SHUTDOWN")
} }
func (c *Conn) SetupChaff(msecsMin uint, msecsMax uint, szMax uint) { func (hc *Conn) SetupChaff(msecsMin uint, msecsMax uint, szMax uint) {
c.chaff.msecsMin = msecsMin //move these to params of chaffHelper() ? hc.chaff.msecsMin = msecsMin //move these to params of chaffHelper() ?
c.chaff.msecsMax = msecsMax hc.chaff.msecsMax = msecsMax
c.chaff.szMax = szMax hc.chaff.szMax = szMax
} }
// Helper routine to spawn a chaffing goroutine for each Conn // Helper routine to spawn a chaffing goroutine for each Conn
func (c *Conn) chaffHelper() { func (hc *Conn) chaffHelper() {
go func() { go func() {
for { for {
var nextDuration int var nextDuration int
if c.chaff.enabled { if hc.chaff.enabled {
bufTmp := make([]byte, rand.Intn(int(c.chaff.szMax))) bufTmp := make([]byte, rand.Intn(int(hc.chaff.szMax)))
min := int(c.chaff.msecsMin) min := int(hc.chaff.msecsMin)
nextDuration = rand.Intn(int(c.chaff.msecsMax)-min) + min nextDuration = rand.Intn(int(hc.chaff.msecsMax)-min) + min
_, _ = rand.Read(bufTmp) _, _ = rand.Read(bufTmp)
_, err := c.WritePacket(bufTmp, CSOChaff) _, err := hc.WritePacket(bufTmp, CSOChaff)
if err != nil { if err != nil {
log.Println("[ *** error - chaffHelper quitting *** ]") log.Println("[ *** error - chaffHelper quitting *** ]")
c.chaff.enabled = false hc.chaff.enabled = false
break break
} }
} }
time.Sleep(time.Duration(nextDuration) * time.Millisecond) time.Sleep(time.Duration(nextDuration) * time.Millisecond)
if c.chaff.shutdown { if hc.chaff.shutdown {
log.Println("*** chaffHelper shutting down") log.Println("*** chaffHelper shutting down")
break break
} }

View file

@ -29,7 +29,7 @@ type cmdSpec struct {
who []byte who []byte
cmd []byte cmd []byte
authCookie []byte authCookie []byte
status int status int // though UNIX shell exit status is uint8, os.Exit() wants int
} }
var ( var (
@ -215,11 +215,14 @@ func main() {
} }
} }
rec.status = int(conn.GetStatus())
log.Println("rec.status:", rec.status)
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() wg.Done()
os.Exit(0) //os.Exit(rec.status)
} }
}() }()
@ -243,7 +246,7 @@ func main() {
if outerr.Error() != "EOF" { 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(2) os.Exit(255)
} }
} }
log.Println("[Sent EOF]") log.Println("[Sent EOF]")
@ -253,4 +256,7 @@ func main() {
// Wait until both stdin and stdout goroutines finish // Wait until both stdin and stdout goroutines finish
wg.Wait() wg.Wait()
_ = hkexsh.Restore(int(os.Stdin.Fd()), oldState) // Best effort.
os.Exit(rec.status)
} }

View file

@ -80,7 +80,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, chaffing bool) (err error) { func runShellAs(who string, cmd string, interactive bool, conn hkexsh.Conn, chaffing bool) (err error, exitStatus int) {
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)
@ -117,11 +117,16 @@ func runShellAs(who string, cmd string, interactive bool, conn hkexsh.Conn, chaf
// Start the command with a pty. // Start the command with a pty.
ptmx, err := pty.Start(c) // returns immediately with ptmx file ptmx, err := pty.Start(c) // returns immediately with ptmx file
if err != nil { if err != nil {
return err return err, 0
} }
// 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.
log.Printf("[%s]\n", cmd)
if err != nil {
log.Printf("Command finished with error: %v", err)
} else {
// Watch for term resizes // Watch for term resizes
go func() { go func() {
for sz := range conn.WinCh { for sz := range conn.WinCh {
@ -146,17 +151,30 @@ func runShellAs(who string, cmd string, interactive bool, conn hkexsh.Conn, chaf
defer conn.ShutdownChaff() defer conn.ShutdownChaff()
// ..and the pty to stdout. // ..and the pty to stdout.
go func() {
_, e := io.Copy(conn, ptmx) _, e := io.Copy(conn, ptmx)
if e != nil { if e != nil {
log.Printf("** pty->stdout ended **\n") log.Printf("** pty->stdout ended **\n")
return return
} }
// The above io.Copy() will exit when the command attached
// to the pty exits
}()
//err = c.Run() // returns when c finishes. if err := c.Wait(); err != nil {
if exiterr, ok := err.(*exec.ExitError); ok {
// The program has exited with an exit code != 0
log.Printf("[%s]\n", cmd) // This works on both Unix and Windows. Although package
if err != nil { // syscall is generally platform dependent, WaitStatus is
log.Printf("Command finished with error: %v", err) // defined for both Unix and Windows and in both cases has
// an ExitStatus() method with the same signature.
if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
exitStatus = status.ExitStatus()
log.Printf("Exit Status: %d", exitStatus)
}
}
}
} }
return return
} }
@ -182,7 +200,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.BoolVar(&chaffEnabled, "cE", true, "enabled chaff pkts")
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)")
@ -234,8 +252,8 @@ func main() {
// 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
// multiple connections may be served concurrently. // multiple connections may be served concurrently.
go func(c hkexsh.Conn) (e error) { go func(hc hkexsh.Conn) (e error) {
defer c.Close() defer hc.Close()
//We use io.ReadFull() here to guarantee we consume //We use io.ReadFull() here to guarantee we consume
//just the data we want for the cmdSpec, and no more. //just the data we want for the cmdSpec, and no more.
@ -244,7 +262,7 @@ func main() {
var rec cmdSpec var rec cmdSpec
var len1, len2, len3, len4 uint32 var len1, len2, len3, len4 uint32
n, err := fmt.Fscanf(c, "%d %d %d %d\n", &len1, &len2, &len3, &len4) n, err := fmt.Fscanf(hc, "%d %d %d %d\n", &len1, &len2, &len3, &len4)
log.Printf("cmdSpec read:%d %d %d %d\n", len1, len2, len3, len4) log.Printf("cmdSpec read:%d %d %d %d\n", len1, len2, len3, len4)
if err != nil || n < 4 { if err != nil || n < 4 {
@ -254,27 +272,27 @@ func main() {
//fmt.Printf(" lens:%d %d %d %d\n", len1, len2, len3, len4) //fmt.Printf(" lens:%d %d %d %d\n", len1, len2, len3, len4)
rec.op = make([]byte, len1, len1) rec.op = make([]byte, len1, len1)
_, err = io.ReadFull(c, rec.op) _, err = io.ReadFull(hc, rec.op)
if err != nil { if err != nil {
log.Println("[Bad cmdSpec.op]") log.Println("[Bad cmdSpec.op]")
return err return err
} }
rec.who = make([]byte, len2, len2) rec.who = make([]byte, len2, len2)
_, err = io.ReadFull(c, rec.who) _, err = io.ReadFull(hc, rec.who)
if err != nil { if err != nil {
log.Println("[Bad cmdSpec.who]") log.Println("[Bad cmdSpec.who]")
return err return err
} }
rec.cmd = make([]byte, len3, len3) rec.cmd = make([]byte, len3, len3)
_, err = io.ReadFull(c, rec.cmd) _, err = io.ReadFull(hc, rec.cmd)
if err != nil { if err != nil {
log.Println("[Bad cmdSpec.cmd]") log.Println("[Bad cmdSpec.cmd]")
return err return err
} }
rec.authCookie = make([]byte, len4, len4) rec.authCookie = make([]byte, len4, len4)
_, err = io.ReadFull(c, rec.authCookie) _, err = io.ReadFull(hc, rec.authCookie)
if err != nil { if err != nil {
log.Println("[Bad cmdSpec.authCookie]") log.Println("[Bad cmdSpec.authCookie]")
return err return err
@ -292,36 +310,46 @@ func main() {
if !valid { if !valid {
log.Println("Invalid user", string(rec.who)) log.Println("Invalid user", string(rec.who))
c.Write([]byte(rejectUserMsg())) hc.Write([]byte(rejectUserMsg()))
return return
} }
log.Printf("[allowedCmds:%s]\n", allowedCmds) log.Printf("[allowedCmds:%s]\n", allowedCmds)
if rec.op[0] == 'c' { if rec.op[0] == 'c' {
// Non-interactive command // Non-interactive command
addr := c.RemoteAddr() addr := hc.RemoteAddr()
hname := goutmp.GetHost(addr.String()) hname := goutmp.GetHost(addr.String())
log.Printf("[Running command for [%s@%s]]\n", rec.who, hname) log.Printf("[Running command for [%s@%s]]\n", rec.who, hname)
runShellAs(string(rec.who), string(rec.cmd), false, conn, chaffEnabled) runErr, cmdStatus := runShellAs(string(rec.who), string(rec.cmd), false, hc, 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.Printf("[Command completed for [%s@%s]\n", rec.who, hname) if runErr != nil {
log.Printf("[Error spawning cmd for %s@%s]\n", rec.who, hname)
} else {
log.Printf("[Command completed for %s@%s, status %d]\n", rec.who, hname, cmdStatus)
hc.SetStatus(uint8(cmdStatus))
}
} else if rec.op[0] == 's' { } else if rec.op[0] == 's' {
// Interactive session // Interactive session
addr := c.RemoteAddr() addr := hc.RemoteAddr()
hname := goutmp.GetHost(addr.String()) hname := goutmp.GetHost(addr.String())
log.Printf("[Running shell for [%s@%s]]\n", rec.who, hname) log.Printf("[Running shell for [%s@%s]]\n", rec.who, hname)
utmpx := goutmp.Put_utmp(string(rec.who), hname) utmpx := goutmp.Put_utmp(string(rec.who), hname)
defer func() { goutmp.Unput_utmp(utmpx) }() defer func() { goutmp.Unput_utmp(utmpx) }()
goutmp.Put_lastlog_entry("hkexsh", string(rec.who), hname) goutmp.Put_lastlog_entry("hkexsh", string(rec.who), hname)
runShellAs(string(rec.who), string(rec.cmd), true, conn, chaffEnabled) runErr, cmdStatus := runShellAs(string(rec.who), string(rec.cmd), true, hc, 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.Printf("[Exiting shell for [%s@%s]]\n", rec.who, hname) if runErr != nil {
log.Printf("[Error spawning shell for %s@%s]\n", rec.who, hname)
} else {
log.Printf("[Shell completed for %s@%s, status %d]\n", rec.who, hname, cmdStatus)
hc.SetStatus(uint8(cmdStatus))
}
} else { } else {
log.Println("[Bad cmdSpec]") log.Println("[Bad cmdSpec]")
} }