diff --git a/hkexnet/hkexnet.go b/hkexnet/hkexnet.go index bad08db..904770b 100644 --- a/hkexnet/hkexnet.go +++ b/hkexnet/hkexnet.go @@ -685,10 +685,8 @@ func (hl *HKExListener) Accept() (hc Conn, err error) { // // See go doc io.Reader func (hc Conn) Read(b []byte) (n int, err error) { - //log.Printf("[Decrypting...]\r\n") for { - //log.Printf("hc.dBuf.Len(): %d\n", hc.dBuf.Len()) - if hc.dBuf.Len() > 0 /* len(b) */ { + if hc.dBuf.Len() > 0 { break } @@ -698,31 +696,52 @@ func (hc Conn) Read(b []byte) (n int, err error) { // Read ctrl/status opcode (CSOHmacInvalid on hmac mismatch) err = binary.Read(*hc.c, binary.BigEndian, &ctrlStatOp) + if err != nil { + if err.Error() == "EOF" { + return 0, io.EOF + } + if strings.HasSuffix(err.Error(), "use of closed network connection") { + logger.LogNotice(fmt.Sprintln("[Client hung up]")) + return 0, io.EOF + } + etxt := fmt.Sprintf("** Failed read:%s (%s) **", "ctrlStatOp", err) + logger.LogErr(etxt) + return 0, errors.New(etxt) + } log.Printf("[ctrlStatOp: %v]\n", ctrlStatOp) if ctrlStatOp == CSOHmacInvalid { // Other side indicated channel tampering, close channel hc.Close() - return 1, errors.New("** ALERT - remote end detected HMAC mismatch - possible channel tampering **") + return 0, errors.New("** ALERT - remote end detected HMAC mismatch - possible channel tampering **") } // Read the hmac and payload len first err = binary.Read(*hc.c, binary.BigEndian, &hmacIn) - // Normal client 'exit' from interactive session will cause - // (on server side) err.Error() == ": use of closed network connection" if err != nil { - if err == io.EOF || strings.HasSuffix(err.Error(), "use of closed network connection") { - logger.LogNotice(fmt.Sprintln("[Client hung up]")) - } else { - log.Println(err) + if err.Error() == "EOF" { + return 0, io.EOF } - return 0, err + if strings.HasSuffix(err.Error(), "use of closed network connection") { + logger.LogNotice(fmt.Sprintln("[Client hung up]")) + return 0, io.EOF + } + etxt := fmt.Sprintf("** Failed read:%s (%s) **", "HMAC", err) + logger.LogErr(etxt) + return 0, errors.New(etxt) } err = binary.Read(*hc.c, binary.BigEndian, &payloadLen) if err != nil { - if err.Error() != "EOF" { - logger.LogErr(fmt.Sprintln("[2]unexpected Read() err:", err)) + if err.Error() == "EOF" { + return 0, io.EOF } + if strings.HasSuffix(err.Error(), "use of closed network connection") { + logger.LogNotice(fmt.Sprintln("[Client hung up]")) + return 0, io.EOF + } + etxt := fmt.Sprintf("** Failed read:%s (%s) **", "payloadLen", err) + logger.LogErr(etxt) + return 0, errors.New(etxt) } if payloadLen > MAX_PAYLOAD_LEN { @@ -733,15 +752,17 @@ func (hc Conn) Read(b []byte) (n int, err error) { var payloadBytes = make([]byte, payloadLen) n, err = io.ReadFull(*hc.c, payloadBytes) - - // Normal client 'exit' from interactive session will cause - // (on server side) err.Error() == ": use of closed network connection" - if err != nil && err.Error() != "EOF" { - if !strings.HasSuffix(err.Error(), "use of closed network connection") { - logger.LogErr(fmt.Sprintln("[3]unexpected Read() err:", err)) - } else { - logger.LogNotice(fmt.Sprintln("[Client hung up]")) + if err != nil { + if err.Error() == "EOF" { + return 0, io.EOF } + if strings.HasSuffix(err.Error(), "use of closed network connection") { + logger.LogNotice(fmt.Sprintln("[Client hung up]")) + return 0, io.EOF + } + etxt := fmt.Sprintf("** Failed read:%s (%s) **", "payloadBytes", err) + logger.LogErr(etxt) + return 0, errors.New(etxt) } log.Printf(" <:ctext:\r\n%s\r\n", hex.Dump(payloadBytes[:n])) @@ -797,19 +818,21 @@ func (hc Conn) Read(b []byte) (n int, err error) { } else if ctrlStatOp == CSOTunData { lport := binary.BigEndian.Uint16(payloadBytes) rport := binary.BigEndian.Uint16(payloadBytes[2:4]) - fmt.Printf("[Got CSOTunData: [lport %d:rport %d] data:%v\n", lport, rport, payloadBytes[4:]) + _ = lport + //fmt.Printf("[Got CSOTunData: [lport %d:rport %d] data:%v\n", lport, rport, payloadBytes[4:]) if hc.tuns[rport] == nil { - fmt.Printf("[Invalid rport:%d]\n", rport) + fmt.Printf("[Invalid rport:%d]\r\n", rport) } else { hc.tuns[rport] <- payloadBytes[4:] } - fmt.Printf("[Done stuffing hc.tuns[rport]\n") + //fmt.Printf("[Done stuffing hc.tuns[rport]\n") } else if ctrlStatOp == CSOTunClose { lport := binary.BigEndian.Uint16(payloadBytes) rport := binary.BigEndian.Uint16(payloadBytes[2:4]) - fmt.Printf("[Got CSOTunClose: [lport %d:rport %d]\n", lport, rport) + fmt.Printf("[Got CSOTunClose: [lport %d:rport %d]\r\n", lport, rport) if hc.tuns[rport] != nil { close(hc.tuns[rport]) + hc.tuns[rport] = nil } } else { hc.dBuf.Write(payloadBytes) diff --git a/hkexnet/hkextun.go b/hkexnet/hkextun.go new file mode 100644 index 0000000..edb9552 --- /dev/null +++ b/hkexnet/hkextun.go @@ -0,0 +1,228 @@ +// hkextun.go - Tunnel setup using an established hkexnet.Conn + +// Copyright (c) 2017-2018 Russell Magee +// Licensed under the terms of the MIT license (see LICENSE.mit in this +// distribution) +// +// golang implementation by Russ Magee (rmagee_at_gmail.com) + +package hkexnet + +import ( + "bytes" + "encoding/binary" + "fmt" + "io" + "log" + "net" + + "blitter.com/go/hkexsh/logger" +) + +type ( + // Tunnels + // -- + // 1. client is given (lport, remhost, rport) by local user + // 2. client sends [CSOTunReq:rport] to server + // client=> [CSOTunReq:rport] =>remhost + // + // remhost starts worker to receive/send data using rport + // remhost replies to client with rport to acknowledge tun is ready + // client<= [CSOTunAck:rport] <=remhost + // ... or if rhost rport refuses connection, sends + // [CSOTunRefused:rport] + // + // client starts worker to receive/send data using lport + // ... client disconnects: sends remhost [CSOTunClose:rport] + // ... or server disconnects: sends client [CSOTunClose:lport] + // server at any time sends [CSOTunRefused:rport] if daemon died + // -- + + // TunEndpoint [securePort:peer:dataPort] + TunEndpoint struct { + Rport uint16 // Names are from client's perspective + Lport uint16 // ... ie., RPort is on server, LPort is on client + Peer string //net.Addr + } + + TunPacket struct { + n uint32 + data []byte + } +) + +func startServerTunnel(hc *Conn, lport, rport uint16) { + if hc.tuns == nil { + hc.tuns = make(map[uint16]chan []byte) + } + if hc.tuns[rport] == nil { + hc.tuns[rport] = make(chan []byte, 32) + } + + addrs, _ := net.InterfaceAddrs() + t := TunEndpoint{Peer: addrs[0].String(), Lport: lport, Rport: rport} + var resp bytes.Buffer + binary.Write(&resp, binary.BigEndian, t.Lport) + + //var dialHangup chan<- bool + + c, err := net.Dial("tcp", fmt.Sprintf(":%d", rport)) + if err != nil { + logger.LogErr(fmt.Sprintf("Nothing is serving at rport :%d!", rport)) + binary.Write(&resp, binary.BigEndian, uint16(0)) + // Inform client of the tunPort + hc.WritePacket(resp.Bytes(), CSOTunRefused) + } else { + binary.Write(&resp, binary.BigEndian, t.Rport) + logger.LogNotice(fmt.Sprintf("[Tunnel Opened - %d:%s:%d]", t.Lport, t.Peer, t.Rport)) + + // + // worker to read data from the rport (to encrypt & send to client) + // + go func() { + defer func() { + //if hc.tuns[rport] != nil { + // close(hc.tuns[rport]) + // hc.tuns[rport] = nil + //} + c.Close() + }() + + var tunDst bytes.Buffer + binary.Write(&tunDst, binary.BigEndian, t.Lport) + binary.Write(&tunDst, binary.BigEndian, t.Rport) + for { + rBuf := make([]byte, 1024) + // Read data from c, encrypt/write via hc to client(lport) + n, e := c.Read(rBuf) + if e != nil { + if e == io.EOF { + logger.LogNotice(fmt.Sprintf("rport Disconnected: shutting down tunnel %v\n", t)) + } else { + logger.LogErr(fmt.Sprintf("Read error from rport of tun %v\n%s", t, e)) + } + hc.WritePacket(resp.Bytes(), CSOTunClose) + fmt.Printf("Closing server rport net.Dial()\n") + break + } + if n > 0 { + rBuf = append(tunDst.Bytes(), rBuf[:n]...) + logger.LogNotice(fmt.Sprintf("Got rport data:%v", tunDst.Bytes())) + hc.WritePacket(rBuf[:n+4], CSOTunData) + } + } + }() + + // worker to read data from client (already decrypted) & fwd to rport + go func() { + defer func() { + //if hc.tuns[rport] != nil { + //close(hc.tuns[rport]) + //hc.tuns[rport] = nil + //} + c.Close() + }() + + for { + rData, ok := <-hc.tuns[rport] + if ok { + logger.LogNotice(fmt.Sprintf("Got client data:%v", rData)) + c.Write(rData) + } else { + logger.LogErr("!!! ERROR reading from hc.tuns[] channel !!!") + break + } + } + }() + + // Inform client of the tunPort + hc.WritePacket(resp.Bytes(), CSOTunAck) + } +} + +func StartClientTunnel(hc *Conn, lport, rport uint16) { + go func() { + if hc.tuns == nil { + hc.tuns = make(map[uint16]chan []byte) + } + if hc.tuns[rport] == nil { + hc.tuns[rport] = make(chan []byte, 32) + } + + l, e := net.Listen("tcp", fmt.Sprintf(":%d", lport)) + if e != nil { + fmt.Printf("[Could not get lport %d! (%s)\n", lport, e) + } else { + defer l.Close() + for { + c, e := l.Accept() + + defer func() { + //if hc.tuns[rport] != nil { + // close(hc.tuns[rport]) + // hc.tuns[rport] = nil + //} + c.Close() + }() + + if e != nil { + log.Printf("Accept() got error(%v), hanging up.\n", e) + break + //log.Fatal(err) + } else { + log.Println("Accepted client") + + // outside client -> tunnel lport + go func() { + var tunDst bytes.Buffer + binary.Write(&tunDst, binary.BigEndian, lport) + binary.Write(&tunDst, binary.BigEndian, rport) + for { + rBuf := make([]byte, 1024) + //Read data from c, encrypt/write via hc to client(lport) + n, e := c.Read(rBuf) + if e != nil { + if e == io.EOF { + logger.LogNotice(fmt.Sprintf("lport Disconnected: shutting down tunnel [%d:%d]\n", lport, rport)) + } else { + logger.LogErr(fmt.Sprintf("Read error from lport of tun [%d:%d]\n%s", lport, rport, e)) + } + hc.WritePacket(tunDst.Bytes(), CSOTunClose) + break + } + if n > 0 { + rBuf = append(tunDst.Bytes(), rBuf[:n]...) + logger.LogNotice(fmt.Sprintf("Got lport data:%v\n", tunDst.Bytes())) + hc.WritePacket(rBuf[:n+4], CSOTunData) + } + } + }() + + // tunnel lport -> outside client (c) + go func() { + defer func() { + //if hc.tuns[rport] != nil { + // close(hc.tuns[rport]) + // hc.tuns[rport] = nil + //} + c.Close() + }() + + for { + //fmt.Printf("Reading from client hc.tuns[%d]\n", lport) + bytes, ok := <-hc.tuns[rport] + if ok { + //fmt.Printf("[Got this through tunnel:%v]\n", bytes) + c.Write(bytes) + } else { + fmt.Printf("[Channel closed? exiting client worker!]\n") + break + } + } + }() + + } + } + } + }() +} diff --git a/hkexsh/hkexsh.go b/hkexsh/hkexsh.go index 76a687b..43a4afd 100755 --- a/hkexsh/hkexsh.go +++ b/hkexsh/hkexsh.go @@ -352,7 +352,7 @@ func requestTunnel(hc *hkexnet.Conn, lp uint16, p string /*net.Addr*/, rp uint16 errL := binary.Read(hc, binary.BigEndian, &lportReply) errR := binary.Read(hc, binary.BigEndian, &rportReply) if errL == nil && errR == nil { - fmt.Printf("Server established tunnel [%d:%d]\n", lportReply, rportReply) + fmt.Printf("Server established tunnel [%d:%d]\r\n", lportReply, rportReply) hkexnet.StartClientTunnel(hc, lp, rp) } else { fmt.Println("FAILED reading remPort")