141 lines
4.2 KiB
Go
141 lines
4.2 KiB
Go
// This file is part of https://github.com/racingmars/go3270/
|
|
// Copyright 2020 by Matthew R. Wilson, licensed under the MIT license. See
|
|
// LICENSE in the project root for license information.
|
|
|
|
package go3270
|
|
|
|
import (
|
|
"net"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
binary = 0
|
|
send = 1
|
|
se = 240 // f0
|
|
sb = 250 // fa
|
|
will = 251 // fb
|
|
wont = 252 // fc
|
|
do = 253 // fd
|
|
dont = 254 // fe
|
|
iac = 255 // ff
|
|
terminalType = 24 // 18
|
|
eoroption = 25 // 19
|
|
eor = 239 // f1
|
|
)
|
|
|
|
// NegotiateTelnet will naively (e.g. not checking client responses) negotiate
|
|
// the options necessary for tn3270 on a new telnet connection, conn.
|
|
func NegotiateTelnet(conn net.Conn) error {
|
|
conn.Write([]byte{iac, do, terminalType})
|
|
conn.Write([]byte{iac, sb, terminalType, send, iac, se})
|
|
conn.Write([]byte{iac, do, eoroption})
|
|
conn.Write([]byte{iac, do, binary})
|
|
conn.Write([]byte{iac, will, eoroption, iac, will, binary})
|
|
flushConnection(conn, time.Second*5)
|
|
return nil
|
|
}
|
|
|
|
// UnNegotiateTelnet will naively (e.g. not checking client responses) attempt
|
|
// to restore the telnet options state to what it was before NegotiateTelnet()
|
|
// was called.
|
|
func UnNegotiateTelnet(conn net.Conn, timeout time.Duration) error {
|
|
conn.Write([]byte{iac, wont, eoroption, iac, wont, binary})
|
|
conn.Write([]byte{iac, dont, binary})
|
|
conn.Write([]byte{iac, dont, eoroption})
|
|
conn.Write([]byte{iac, dont, terminalType})
|
|
flushConnection(conn, timeout)
|
|
return nil
|
|
}
|
|
|
|
// flushConnection discards all bytes that it can read from conn, allowing up
|
|
// to the duration timeout for the first byte to be read.
|
|
func flushConnection(conn net.Conn, timeout time.Duration) error {
|
|
defer conn.SetReadDeadline(time.Time{})
|
|
buffer := make([]byte, 1024)
|
|
for {
|
|
conn.SetReadDeadline(time.Now().Add(timeout))
|
|
n, err := conn.Read(buffer)
|
|
if neterr, ok := err.(net.Error); ok && neterr.Timeout() {
|
|
debugf("nothing to flush\n")
|
|
return nil
|
|
}
|
|
if err != nil {
|
|
debugf("error while flushing: %v\n", err)
|
|
return err
|
|
}
|
|
debugf("%d bytes read while flushing connection\n", n)
|
|
// for follow-up reads, reduce the timeout
|
|
timeout = time.Second / 2
|
|
}
|
|
}
|
|
|
|
// telnetRead returns the next byte of data from the connection c, but
|
|
// filters out all telnet commands. If passEOR is true, then telnetRead will
|
|
// return upon encountering the telnet End of Record command, setting isEor to
|
|
// true. When isEor is true, the value of b is meaningless and must be ignored
|
|
// (valid will be false). When valid is true, the value in byte b is a real
|
|
// value read from the connection; when value is false, do not use the value
|
|
// in b. (For example, a valid byte AND error can be returned in the same
|
|
// call.)
|
|
func telnetRead(c net.Conn, passEOR bool) (b byte, valid, isEor bool, err error) {
|
|
const (
|
|
normal = iota
|
|
command
|
|
subneg
|
|
)
|
|
|
|
buf := make([]byte, 1)
|
|
state := normal
|
|
|
|
for {
|
|
bn, berr := c.Read(buf)
|
|
|
|
// When there are no bytes to process and we received an error, we
|
|
// are done no matter what state we're in. Any non-command bytes will
|
|
// already be in p, so we return.
|
|
if bn == 0 && berr != nil {
|
|
return 0, false, false, berr
|
|
}
|
|
|
|
// If we received 0 bytes but no error, we'll just read again.
|
|
if bn == 0 {
|
|
continue
|
|
}
|
|
|
|
// We got a byte! Let's progress through our state machine.
|
|
switch state {
|
|
case normal:
|
|
if buf[0] == iac {
|
|
state = command
|
|
debugf("entering telnet command state\n")
|
|
} else {
|
|
return buf[0], true, false, berr
|
|
}
|
|
case command:
|
|
if buf[0] == 0xff {
|
|
debugf("leaving telnet command state; was an escaped 0xff\n")
|
|
return 0xff, true, false, nil
|
|
} else if buf[0] == sb {
|
|
state = subneg
|
|
debugf("entering telnet command subnegotiation state\n")
|
|
} else if passEOR && buf[0] == eor {
|
|
debugf("leaving telnet command state; returning EOR\n")
|
|
return 0, false, true, nil
|
|
} else {
|
|
state = normal
|
|
debugf("leaving telnet command state; command was %02x\n",
|
|
buf[0])
|
|
}
|
|
case subneg:
|
|
if buf[0] == se {
|
|
state = normal
|
|
debugf("leaving telnet command subnegotiation state\n")
|
|
} else {
|
|
// remain in subnegotiation consuming bytes until we get se
|
|
debugf("consumed telnet subnegotiation byte: %02x\n", buf[0])
|
|
}
|
|
}
|
|
}
|
|
}
|