diff options
-rw-r--r-- | src/helsinki.at/rhimportd/ctrlTelnet.go | 150 |
1 files changed, 129 insertions, 21 deletions
diff --git a/src/helsinki.at/rhimportd/ctrlTelnet.go b/src/helsinki.at/rhimportd/ctrlTelnet.go index 04d90da..4cbeb56 100644 --- a/src/helsinki.at/rhimportd/ctrlTelnet.go +++ b/src/helsinki.at/rhimportd/ctrlTelnet.go @@ -26,6 +26,7 @@ package main import ( "bufio" + "bytes" "fmt" "helsinki.at/rhimport" "net" @@ -36,6 +37,13 @@ import ( const ( prompt = "> " + EOT = byte(4) + IP = byte(244) + WILL = byte(251) + WONT = byte(252) + DO = byte(253) + DONT = byte(254) + IAC = byte(255) ) type TelnetClient struct { @@ -47,15 +55,25 @@ type TelnetClient struct { ctx *rhimport.ImportContext } -func (c *TelnetClient) write_string(text string) (err error) { - if _, err = c.writer.WriteString(text); err != nil { - return +func (c *TelnetClient) write_string(text string) { + defer c.writer.Flush() + + data := []byte(text) + for { + idx := bytes.IndexByte(data, IAC) + if idx >= 0 { + c.writer.Write(data[:idx+1]) + c.writer.WriteByte(IAC) + data = data[idx+1:] + } else { + c.writer.Write(data) + return + } } - return c.writer.Flush() } -func (c *TelnetClient) say(format string, a ...interface{}) (err error) { - return c.write_string(fmt.Sprintf(format, a...) + "\n") +func (c *TelnetClient) say(format string, a ...interface{}) { + c.write_string(fmt.Sprintf(format, a...) + "\n") } func (c *TelnetClient) handle_cmd_help(args []string) { @@ -272,6 +290,8 @@ func telnet_progress_callback(step int, step_name string, progress float64, user } func telnet_cmd_run(ctx rhimport.ImportContext, out chan<- string) { + defer close(out) + out <- fmt.Sprintf("fetching file from '%s'\n", ctx.SourceUri) if res, err := rhimport.FetchFile(&ctx); err != nil { out <- fmt.Sprintf("fetch file error: %s\n", err) @@ -294,7 +314,6 @@ func telnet_cmd_run(ctx rhimport.ImportContext, out chan<- string) { rhl.Printf("Fileimport has failed (Cart/Cut %d/%d): %s", res.Cart, res.Cut, res.ErrorString) } } - close(out) } func (c *TelnetClient) handle_cmd_run(args []string) { @@ -343,25 +362,113 @@ func (c *TelnetClient) handle_cmd(cmdstr string) bool { return false } -func (c *TelnetClient) handle() { - defer c.conn.Close() +func (c *TelnetClient) handle_iac(iac []byte) bool { + if len(iac) < 2 { + return false // this shouldn't happen + } - if err := c.write_string(prompt); err != nil { - return + switch iac[1] { + case WILL, WONT: // Don't accept any proposed options + iac[1] = DONT + case DO, DONT: + iac[1] = WONT + case IP: + // TODO: cancel running command (if any) + rhdl.Printf("canceling running process - is not yet implemented!") + return false + default: + rhdl.Printf("ignoring unimplemented telnet command: %X", iac[1]) + return false } - for c.scanner.Scan() { - if exit := c.handle_cmd(c.scanner.Text()); exit { - return - } + c.writer.Write(iac) + c.writer.Flush() - if err := c.write_string(prompt); err != nil { - return + return false +} + +func dropCR(data []byte) []byte { + if len(data) > 0 && data[len(data)-1] == '\r' { + return data[0 : len(data)-1] + } + return data +} + +func compare_idx(a, b int) int { + if a < 0 { + a = int(^uint(0) >> 1) + } + if b < 0 { + b = int(^uint(0) >> 1) + } + return a - b +} + +func ScanLinesTelnet(data []byte, atEOF bool) (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) < 1 { + return 0, nil, nil // data does not yet contain the telnet command code -> need more data } + switch data[iiac+1] { + case DONT, DO, WONT, WILL: + if (len(data) - iiac) < 2 { + return 0, nil, nil // this is a 3-byte command and data does not yet contain the option code -> need more data + } + l = 3 + } + + return iiac + l, data[iiac : iiac+l], nil // found a Telnet Command } - if err := c.scanner.Err(); err != nil { - rhdl.Printf("telnet-ctrl(%s): read command error: %s", c.conn.RemoteAddr(), err) - } else { - rhdl.Printf("telnet-ctrl(%s): connection closed", c.conn.RemoteAddr()) + if atEOF { + return len(data), dropCR(data), nil // allow last line to have no new line + } + return 0, nil, nil // we have found none of the escape codes -> need more data +} + +func (c *TelnetClient) handle() { + defer func() { + if err := c.scanner.Err(); err != nil { + rhdl.Printf("telnet-ctrl(%s): read command error: %s", c.conn.RemoteAddr(), err) + } else { + rhdl.Printf("telnet-ctrl(%s): connection closed", c.conn.RemoteAddr()) + } + c.conn.Close() + }() + + c.write_string(prompt) + for c.scanner.Scan() { + b := c.scanner.Bytes() + if len(b) > 0 { + switch b[0] { + case EOT: + return + case IAC: + if exit := c.handle_iac(c.scanner.Bytes()); exit { + return + } + default: + if exit := c.handle_cmd(string(b)); exit { + return + } + c.write_string(prompt) + } + } else { + c.write_string(prompt) + } } } @@ -370,6 +477,7 @@ 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 |