From 7aa0e226d74b56f955cb328d5e7f03d7d3d32efb Mon Sep 17 00:00:00 2001 From: Jeremy Stanley Date: Sun, 12 Apr 2020 20:17:57 +0000 Subject: [PATCH] Obtain terminal type (RFC 1091) Implement rudimentary support for determining the terminal type reported by RFC 1091 TTYPE compatible clients, and store any initial value returned in the User.ttype attribute. This implementation does not iterate over SEND TTYPE commands until UNKNOWN is returned, it only takes the first value returned and assumes this is the default terminal type for the user's current connection. --- mudpy/misc.py | 8 ++++++++ mudpy/telnet.py | 32 +++++++++++++++++++++++++++----- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/mudpy/misc.py b/mudpy/misc.py index d753559..0543aa1 100644 --- a/mudpy/misc.py +++ b/mudpy/misc.py @@ -503,6 +503,7 @@ class User: self.password_tries = 0 self.state = "telopt_negotiation" self.telopts = {} + self.ttype = None self.universe = universe def quit(self): @@ -805,6 +806,13 @@ class User: else: self.check_idle() + # ask the client for their current terminal type (RFC 1091); it's None + # if it's not been initialized, the empty string if it has but the + # output was indeterminate, "UNKNOWN" if the client specified it has no + # terminal types to supply + if self.ttype is None: + mudpy.telnet.request_ttype(self) + # if output is paused, decrement the counter if self.state == "telopt_negotiation": if self.negotiation_pause: diff --git a/mudpy/telnet.py b/mudpy/telnet.py index e483c13..35fb1c6 100644 --- a/mudpy/telnet.py +++ b/mudpy/telnet.py @@ -1,6 +1,6 @@ """Telnet functions and constants for the mudpy engine.""" -# Copyright (c) 2004-2019 mudpy authors. Permission to use, copy, +# Copyright (c) 2004-2020 mudpy authors. Permission to use, copy, # modify, and distribute this software is granted under terms # provided in the LICENSE file distributed with this software. @@ -24,11 +24,11 @@ option_names = { TELOPT_LINEMODE: "line mode", } -# TODO(fungi): implement support for RFC 1091 terminal type information supported = ( TELOPT_BINARY, TELOPT_ECHO, TELOPT_SGA, + TELOPT_TTYPE, TELOPT_EOR, TELOPT_NAWS, TELOPT_LINEMODE @@ -80,6 +80,14 @@ party_names = { US: "us", } +# RFC 1091 commands +IS = 0 +SEND = 1 +ttype_command_names = { + IS: "is", + SEND: "send", +} + def log(message, user): """Log debugging info for Telnet client/server interactions.""" @@ -163,6 +171,17 @@ def disable(user, telopt, party): user.telopts[(telopt, party)] = WANTNO +def request_ttype(user): + """Clear and request the terminal type.""" + + # only actually request if the corresponding telopt is enabled + if is_enabled(user, TELOPT_TTYPE, HIM): + # set to the empty string to indicate it's been requested + user.ttype = "" + user.send(telnet_proto(IAC, SB, TELOPT_TTYPE, SEND, IAC, SE), raw=True) + log('Sent terminal type request to', user) + + def negotiate_telnet_options(user): """Reply to and remove telnet negotiation options from partial_input.""" @@ -245,11 +264,14 @@ def negotiate_telnet_options(user): # subnegotiation options elif len_text > position + 4 and command is SB: telopt = text[position + 2] - if telopt is TELOPT_NAWS: - user.columns = ( - text[position + 3] * 256 + text[position + 4]) end_subnegotiation = text.find(telnet_proto(IAC, SE), position) if end_subnegotiation > 0: + if telopt is TELOPT_NAWS: + user.columns = ( + text[position + 3] * 256 + text[position + 4]) + elif telopt is TELOPT_TTYPE and text[position + 3] is IS: + user.ttype = ( + text[position + 4:end_subnegotiation]).decode("ascii") text = text[:position] + text[end_subnegotiation + 2:] else: position += 1 -- 2.11.0