Improve telnet negotiation compatbility
Respond to client "do" requests during the negotiation. This prevents the negotiation from failing when connecting from the z/VM and CSI z/VSE telnet clients. Also don't treat unknown terminal device type as an error, just fall back on assuming a 24x80 terminal. This shouldn't affect z/VM / z/VSE clients, which identify correctly, but will return to the original go3270 behavior of not caring about specific client device type.
This commit is contained in:
1 parent
f71d36fd9e
commit
c6be50cec6
1 file changed
+86
-18
@@ -6,6 +6,7 @@ package go3270
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"regexp"
|
||||
"time"
|
||||
@@ -76,11 +77,17 @@ var errOptionRejected = errors.New("option rejected")
|
||||
// telnet connection, conn.
|
||||
func NegotiateTelnet(conn net.Conn) (DevInfo, error) {
|
||||
|
||||
// Sometimes the client will trigger us to send our "will" assertions
|
||||
// sooner than we otherwise would. Keep track here so we know not to send
|
||||
// them again.
|
||||
var sentWillBin, sentWillEOR bool
|
||||
|
||||
// Enable terminal type option
|
||||
if _, err := conn.Write([]byte{iac, do, terminalType}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err := checkOptionResponse(conn, terminalType, do)
|
||||
err := checkOptionResponse(conn, terminalType, do,
|
||||
&sentWillEOR, &sentWillBin)
|
||||
if err == errOptionRejected || err == ErrTelnetError {
|
||||
return nil, ErrNo3270
|
||||
} else if err != nil {
|
||||
@@ -98,7 +105,8 @@ func NegotiateTelnet(conn net.Conn) (DevInfo, error) {
|
||||
|
||||
// Request end of record mode
|
||||
conn.Write([]byte{iac, do, eorOption})
|
||||
err = checkOptionResponse(conn, eorOption, do)
|
||||
err = checkOptionResponse(conn, eorOption, do,
|
||||
&sentWillEOR, &sentWillBin)
|
||||
if err == errOptionRejected || err == ErrTelnetError {
|
||||
return nil, ErrNo3270
|
||||
} else if err != nil {
|
||||
@@ -107,29 +115,65 @@ func NegotiateTelnet(conn net.Conn) (DevInfo, error) {
|
||||
|
||||
// Request binary mode
|
||||
conn.Write([]byte{iac, do, binaryOption})
|
||||
err = checkOptionResponse(conn, binaryOption, do)
|
||||
err = checkOptionResponse(conn, binaryOption, do,
|
||||
&sentWillEOR, &sentWillBin)
|
||||
if err == errOptionRejected || err == ErrTelnetError {
|
||||
return nil, ErrNo3270
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// It's possible there are already some client requests in the queue
|
||||
// that we haven't processed yet. We'll need to consume any outstanding
|
||||
// requests here and respond if necessary.
|
||||
var buf [3]byte
|
||||
for {
|
||||
conn.SetReadDeadline(time.Now().Add(10 * time.Millisecond))
|
||||
n, err := conn.Read(buf[:])
|
||||
conn.SetReadDeadline(time.Time{})
|
||||
if err != nil {
|
||||
if ne, ok := err.(net.Error); ok && ne.Timeout() {
|
||||
// No data waiting. We expect to eventually break out of the
|
||||
// for loop here.
|
||||
break
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
} else if n == 3 {
|
||||
if buf[0] == iac && buf[1] == do && buf[2] == eorOption {
|
||||
conn.Write([]byte{iac, will, eorOption})
|
||||
sentWillEOR = true
|
||||
} else if buf[0] == iac && buf[1] == do && buf[2] == binaryOption {
|
||||
conn.Write([]byte{iac, will, binaryOption})
|
||||
sentWillBin = true
|
||||
}
|
||||
} else {
|
||||
fmt.Println("SHORT READ SHORT READ")
|
||||
}
|
||||
}
|
||||
|
||||
// Enter end of record mode
|
||||
conn.Write([]byte{iac, will, eorOption})
|
||||
err = checkOptionResponse(conn, eorOption, will)
|
||||
if err == errOptionRejected || err == ErrTelnetError {
|
||||
return nil, ErrNo3270
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
if !sentWillEOR {
|
||||
conn.Write([]byte{iac, will, eorOption})
|
||||
err = checkOptionResponse(conn, eorOption, will,
|
||||
&sentWillEOR, &sentWillBin)
|
||||
if err == errOptionRejected || err == ErrTelnetError {
|
||||
return nil, ErrNo3270
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Enter binary mode
|
||||
conn.Write([]byte{iac, will, binaryOption})
|
||||
err = checkOptionResponse(conn, binaryOption, will)
|
||||
if err == errOptionRejected || err == ErrTelnetError {
|
||||
return nil, ErrNo3270
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
if !sentWillBin {
|
||||
conn.Write([]byte{iac, will, binaryOption})
|
||||
err = checkOptionResponse(conn, binaryOption, will,
|
||||
&sentWillEOR, &sentWillBin)
|
||||
if err == errOptionRejected || err == ErrTelnetError {
|
||||
return nil, ErrNo3270
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
devinfo, err := makeDeviceInfo(conn, devtype)
|
||||
@@ -143,7 +187,11 @@ func NegotiateTelnet(conn net.Conn) (DevInfo, error) {
|
||||
// checkOptionResponse will check for the client's "will/wont" (if mode is do)
|
||||
// or "do/dont" (if mode is will) response. mode is the option command the
|
||||
// server just sent, and option is the option code to check for.
|
||||
func checkOptionResponse(conn net.Conn, option, mode byte) error {
|
||||
//
|
||||
// If we end up getting a client request instead, we'll response and set
|
||||
// sentEor or sentBin before trying to read the response again.
|
||||
func checkOptionResponse(conn net.Conn, option, mode byte,
|
||||
sentEor, sentBin *bool) error {
|
||||
var buf [3]byte
|
||||
|
||||
var expectedYes, expectedNo byte
|
||||
@@ -165,6 +213,25 @@ func checkOptionResponse(conn net.Conn, option, mode byte) error {
|
||||
if n < 3 || buf[0] != iac {
|
||||
return ErrTelnetError
|
||||
}
|
||||
|
||||
// If the client is requesting to negotiate a mode with us before the
|
||||
// response to our request, we'll satisfy it if it's one of the expected
|
||||
// modes and then try to read the client's response again.
|
||||
//
|
||||
// We only want to do this if we're not already expecting a "do" response
|
||||
// for the particular option.
|
||||
if !(expectedYes == do && buf[2] == option) {
|
||||
if buf[0] == iac && buf[1] == do && buf[2] == eorOption {
|
||||
conn.Write([]byte{iac, will, eorOption})
|
||||
*sentEor = true
|
||||
return checkOptionResponse(conn, option, mode, sentEor, sentBin)
|
||||
} else if buf[0] == iac && buf[1] == do && buf[2] == binaryOption {
|
||||
conn.Write([]byte{iac, will, binaryOption})
|
||||
*sentBin = true
|
||||
return checkOptionResponse(conn, option, mode, sentEor, sentBin)
|
||||
}
|
||||
}
|
||||
|
||||
if buf[1] == expectedNo {
|
||||
// Was the correct option rejected?
|
||||
if buf[2] != option {
|
||||
@@ -234,9 +301,10 @@ func makeDeviceInfo(conn net.Conn, termtype string) (DevInfo, error) {
|
||||
}
|
||||
|
||||
// If it's not a fixed-size type, it should be IBM-DYNAMIC. If it isn't,
|
||||
// we don't know how to deal with it.
|
||||
// we don't know how to deal with it. We'll just fall back on a simple
|
||||
// 24x80 assumption.
|
||||
if termtype != "IBM-DYNAMIC" {
|
||||
return nil, ErrUnknownTerminal
|
||||
return &deviceInfo{24, 80, "unknown (" + termtype + ")"}, nil
|
||||
}
|
||||
|
||||
// For IBM-DYNAMIC, we need to discover the alternate screen size with
|
||||
|
||||
Reference in new issue
Block a user