From de51aad67a1a63f1e6e00cc60d2b4e86bf2bbcbe Mon Sep 17 00:00:00 2001
From: Christian Pointner <equinox@helsinki.at>
Date: Wed, 23 Dec 2015 07:11:54 +0100
Subject: improved IAC handling which treats telnet commands as OOB messages


diff --git a/src/helsinki.at/rhimportd/ctrlTelnet.go b/src/helsinki.at/rhimportd/ctrlTelnet.go
index 74e396f..55701cd 100644
--- a/src/helsinki.at/rhimportd/ctrlTelnet.go
+++ b/src/helsinki.at/rhimportd/ctrlTelnet.go
@@ -53,6 +53,7 @@ type TelnetClient struct {
 	conf    *rhimport.Config
 	rddb    *rhimport.RdDbChan
 	ctx     *rhimport.ImportContext
+	iacout  chan []byte
 }
 
 func (c *TelnetClient) write_string(text string) {
@@ -348,30 +349,19 @@ func (c *TelnetClient) handle_cmd(cmdstr string, done chan<- bool, cancel <-chan
 	done <- false
 }
 
-func (c *TelnetClient) handle_iac(iac []byte, cancel chan<- bool) bool {
-	if len(iac) < 2 {
-		return false // this shouldn't happen
-	}
-
+func handleIac(iac []byte, iacout chan<- []byte) {
 	switch iac[1] {
 	case WILL, WONT: // Don't accept any proposed options
 		iac[1] = DONT
 	case DO, DONT:
 		iac[1] = WONT
 	case IP:
-		select {
-		case cancel <- true:
-		default: // process got canceled already
-		}
-		return false
+		// pass this through to client.handle which will cancel the process
 	default:
 		rhdl.Printf("ignoring unimplemented telnet command: %X", iac[1])
-		return false
+		return
 	}
-	c.writer.Write(iac)
-	c.writer.Flush()
-
-	return false
+	iacout <- iac
 }
 
 func dropCR(data []byte) []byte {
@@ -381,6 +371,37 @@ func dropCR(data []byte) []byte {
 	return data
 }
 
+func dropIAC(data []byte) []byte {
+	var token []byte
+	iiac := 0
+	for {
+		niiac := bytes.IndexByte(data[iiac:], IAC)
+		if niiac >= 0 {
+			token = append(token, data[iiac:iiac+niiac]...)
+			iiac += niiac
+			if (len(data) - iiac) < 2 {
+				return token
+			}
+			switch data[iiac+1] {
+			case DONT, DO, WONT, WILL:
+				if (len(data) - iiac) < 3 {
+					return token
+				}
+				iiac += 3
+			case IAC:
+				token = append(token, IAC)
+				fallthrough
+			default:
+				iiac += 2
+			}
+		} else {
+			token = append(token, data[iiac:]...)
+			break
+		}
+	}
+	return token
+}
+
 func compare_idx(a, b int) int {
 	if a < 0 {
 		a = int(^uint(0) >> 1)
@@ -391,35 +412,52 @@ func compare_idx(a, b int) int {
 	return a - b
 }
 
-func ScanLinesTelnet(data []byte, atEOF bool) (advance int, token []byte, err error) {
+func ScanLinesTelnet(data []byte, atEOF bool, iacout chan<- []byte, lastiiac *int) (advance int, token []byte, err error) {
 	if atEOF && len(data) == 0 {
 		return 0, nil, nil
 	}
 
 	inl := bytes.IndexByte(data, '\n') // index of first newline character
 	ieot := bytes.IndexByte(data, EOT) // index of first End of Transmission
-	iiac := bytes.IndexByte(data, IAC) // index of first telnet IAC
 
-	if inl >= 0 && compare_idx(inl, ieot) < 0 && compare_idx(inl, iiac) < 0 {
-		return inl + 1, dropCR(data[0:inl]), nil // found a complete line
-	}
-	if ieot >= 0 && compare_idx(ieot, iiac) < 0 {
-		return ieot + 1, data[ieot : ieot+1], nil // found a EOT (aka Ctrl-D)
-	}
-	if iiac >= 0 {
-		l := 2
-		if (len(data) - iiac) < 2 {
-			return 0, nil, nil // data does not yet contain the telnet command code -> need more data
+	iiac := *lastiiac
+	for {
+		niiac := bytes.IndexByte(data[iiac:], IAC) // index of first/next telnet IAC
+		if niiac >= 0 {
+			iiac += niiac
+		} else {
+			iiac = niiac
 		}
-		switch data[iiac+1] {
-		case DONT, DO, WONT, WILL:
-			if (len(data) - iiac) < 3 {
-				return 0, nil, nil // this is a 3-byte command and data does not yet contain the option code -> need more data
+
+		if inl >= 0 && compare_idx(inl, ieot) < 0 && compare_idx(inl, iiac) < 0 {
+			*lastiiac = 0
+			return inl + 1, dropCR(dropIAC(data[0:inl])), nil // found a complete line
+		}
+		if ieot >= 0 && compare_idx(ieot, iiac) < 0 {
+			*lastiiac = 0
+			return ieot + 1, data[ieot : ieot+1], nil // found a EOT (aka Ctrl-D)
+		}
+		if iiac >= 0 {
+			l := 2
+			if (len(data) - iiac) < 2 {
+				return 0, nil, nil // data does not yet contain the telnet command code -> need more data
 			}
-			l = 3
+			switch data[iiac+1] {
+			case DONT, DO, WONT, WILL:
+				if (len(data) - iiac) < 3 {
+					return 0, nil, nil // this is a 3-byte command and data does not yet contain the option code -> need more data
+				}
+				l = 3
+			case IAC:
+				iiac += 2
+				continue
+			}
+			handleIac(data[iiac:iiac+l], iacout)
+			iiac += l
+			*lastiiac = iiac
+		} else {
+			break
 		}
-		// TODO: this doesn't handle escaped IAC bytes correctly: this means utf-8 might be broken...
-		return iiac + l, data[iiac : iiac+l], nil // found a Telnet Command
 	}
 	if atEOF {
 		return len(data), dropCR(data), nil // allow last line to have no new line
@@ -460,6 +498,8 @@ func (c *TelnetClient) handle() {
 		}
 	}()
 
+	var cmd_backlog []string
+	busy := false
 	c.write_string(prompt)
 	for {
 		select {
@@ -468,11 +508,11 @@ func (c *TelnetClient) handle() {
 				return
 			}
 			if len(cmd) > 0 {
-				switch cmd[0] {
-				case IAC:
-					c.handle_iac([]byte(cmd), cancel)
-				default:
+				if !busy {
 					go c.handle_cmd(cmd, done, cancel)
+					busy = true
+				} else {
+					cmd_backlog = append(cmd_backlog, cmd)
 				}
 			} else {
 				c.write_string(prompt)
@@ -482,6 +522,22 @@ func (c *TelnetClient) handle() {
 				return
 			}
 			c.write_string(prompt)
+			if len(cmd_backlog) > 0 {
+				go c.handle_cmd(cmd_backlog[0], done, cancel)
+				cmd_backlog = cmd_backlog[1:]
+			} else {
+				busy = false
+			}
+		case iac := <-c.iacout:
+			if iac[1] == IP {
+				select {
+				case cancel <- true:
+				default: // process got canceled already
+				}
+			} else {
+				c.writer.Write(iac)
+				c.writer.Flush()
+			}
 		}
 	}
 }
@@ -491,11 +547,16 @@ func newTelnetClient(conn net.Conn, conf *rhimport.Config, rddb *rhimport.RdDbCh
 	c = &TelnetClient{}
 	c.conn = conn
 	c.scanner = bufio.NewScanner(conn)
-	c.scanner.Split(ScanLinesTelnet)
 	c.writer = bufio.NewWriter(conn)
 	c.conf = conf
 	c.rddb = rddb
 	c.ctx = nil
+	// the telnet split function needs some closures to handle OOB telnet commands
+	c.iacout = make(chan []byte)
+	lastiiac := 0
+	c.scanner.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) {
+		return ScanLinesTelnet(data, atEOF, c.iacout, &lastiiac)
+	})
 	return c
 }
 
diff --git a/src/helsinki.at/rhimportd/main.go b/src/helsinki.at/rhimportd/main.go
index 56fe612..362e8dc 100644
--- a/src/helsinki.at/rhimportd/main.go
+++ b/src/helsinki.at/rhimportd/main.go
@@ -164,7 +164,7 @@ func main() {
 	}
 	defer rddb.Cleanup()
 
-	go session_test(conf, rddb.GetInterface())
+	//	go session_test(conf, rddb.GetInterface())
 
 	var wg sync.WaitGroup
 
-- 
cgit v0.10.2