mirror of https://gogs.blitter.com/RLabs/xs
Exit status of remote commands now reflect in client exit
This commit is contained in:
parent
c64797f2d9
commit
216bfa3326
196
hkexnet.go
196
hkexnet.go
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -157,10 +157,9 @@ func runShellAs(who string, cmd string, interactive bool, conn hkexsh.Conn, chaf
|
||||||
log.Printf("** pty->stdout ended **\n")
|
log.Printf("** pty->stdout ended **\n")
|
||||||
return
|
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
|
||||||
|
}()
|
||||||
|
|
||||||
if err := c.Wait(); err != nil {
|
if err := c.Wait(); err != nil {
|
||||||
if exiterr, ok := err.(*exec.ExitError); ok {
|
if exiterr, ok := err.(*exec.ExitError); ok {
|
||||||
|
@ -201,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)")
|
||||||
|
@ -253,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.
|
||||||
|
@ -263,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 {
|
||||||
|
@ -273,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
|
||||||
|
@ -311,18 +310,18 @@ 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)
|
||||||
runErr, cmdStatus := 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
|
||||||
|
@ -330,17 +329,18 @@ func main() {
|
||||||
log.Printf("[Error spawning cmd for %s@%s]\n", rec.who, hname)
|
log.Printf("[Error spawning cmd for %s@%s]\n", rec.who, hname)
|
||||||
} else {
|
} else {
|
||||||
log.Printf("[Command completed for %s@%s, status %d]\n", rec.who, hname, cmdStatus)
|
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)
|
||||||
runErr, cmdStatus := 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
|
||||||
|
@ -348,6 +348,7 @@ func main() {
|
||||||
log.Printf("[Error spawning shell for %s@%s]\n", rec.who, hname)
|
log.Printf("[Error spawning shell for %s@%s]\n", rec.who, hname)
|
||||||
} else {
|
} else {
|
||||||
log.Printf("[Shell completed for %s@%s, status %d]\n", rec.who, hname, cmdStatus)
|
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]")
|
||||||
|
|
Loading…
Reference in New Issue