From de51aad67a1a63f1e6e00cc60d2b4e86bf2bbcbe Mon Sep 17 00:00:00 2001 From: Christian Pointner 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