summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Pointner <equinox@helsinki.at>2015-12-21 07:49:09 (GMT)
committerChristian Pointner <equinox@helsinki.at>2015-12-21 07:49:09 (GMT)
commitb3a4b4f49597aae618f374347117cfa762c5fd9a (patch)
tree6c8c61d8150719a583bd2b7985af5a9aa31806d3
parent87e91b6487644ab9de12d0cb031b90cc599062e1 (diff)
the telnet interface now handles IAC aka telnet commands
-rw-r--r--src/helsinki.at/rhimportd/ctrlTelnet.go150
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