Re: [Twisted-Python] Telnet negotiation with Twisted

Hi Patrick! Thanks for the suggestion. After I call self.will(LINEMODE), I get Failure: twisted.conch.telnet.OptionRefused: twisted.conch.telnet.OptionRefused: '"' So I know at least something is happening. I guess this just became a Telnet question instead of a twisted question. But I still don't understand how enableRemote and requestNegotiation are supposed to be used with this. Anyway, this happens in PuTTY as well as the Windows telnet client. Below is the code, if it helps, as simple as I could get it. Maybe there is something obvious that I need to do: from twisted.internet.protocol import Protocol, Factory from twisted.conch.telnet import TelnetTransport, TelnetProtocol, ITelnetProtocol from twisted.internet import reactor import sys class EchoTransport(TelnetTransport): def __init__(self, protocolFactory=None, *a, **kw): TelnetTransport.__init__(self) if protocolFactory is not None: self.protocolFactory = protocolFactory self.protocolArgs = a self.protocolKwArgs = kw def connectionMade(self): TelnetTransport.connectionMade(self) print ("connection made") def connectionLost(self, reason): print ("connection lost") reactor.stop() def applicationDataReceived(self, bytes): TelnetTransport.applicationDataReceived(self, bytes) bytes = bytes.strip() if (bytes == 'b'): self.will(chr(34)) # Error here if (bytes == 'x'): self.transport.write("goodbye") TelnetTransport.loseConnection(self) def main(): f = Factory() f.protocol = lambda: EchoTransport(TelnetProtocol) reactor.listenTCP(8023, f) reactor.run() if __name__ == '__main__': main()

On Thu, Dec 1, 2011 at 7:00 PM, Lee Orsino <lmorsino@gmail.com> wrote:
reactor.listenTCP(8023, f)
It's been a loong time since I've done anything with telnet, but it used to be the case that telnet clients would not do any negotiation if they connected to non-standard ports. Have you tried running your server on port 23? -- Jeff Ollie

On Thu, Dec 1, 2011 at 5:34 PM, Jeffrey Ollie <jeff@ocjtech.us> wrote:
I just happen to have been doing something similar, with TERMINAL-TYPE, and negotiation works fine regardless of port used. Clearly something else is wrong. ~ C. -- When the facts change, I change my mind. What do you do, sir? ~ Keynes Corbin Simpson <MostAwesomeDude@gmail.com>

Hmmm. Do I need to do something with the enableRemote/enableLocal methods of TelnetTransport?

On 2 Dec, 04:43 am, lmorsino@gmail.com wrote:
Yes. The default implementation of enableRemote does not allow any options to be enabled. You must override it if you want the peer to be allowed to enable any options. Jean-Paul

Jean-Paul, Thanks for your response. I still can't get self.will(LINEMODE) to work but I did get self.telnet_WILL(LINEMODE) to run without throwing an Error. Can you elaborate on what needs to happen in the enableRemote? My impression was that once I send the negotiation WILL command from server to the client, the client will respond with either a DO or a DONT. What further logic is necessary? Thanks! On Fri, Dec 2, 2011 at 4:56 PM, <exarkun@twistedmatrix.com> wrote:

On 03:53 am, lmorsino@gmail.com wrote:
I can't say what further logic is necessary, because I don't know what logic you have now. The description of your code you gave above is a good introduction to an actual code listing (<http://sscce.org/>), but it's not sufficient on its own. Jean-Paul

Hi Lee, Here is a complete example that works with Twisted 11.0. LINEMODE itself is enabled at connect, but it has different modes that determine what is sent when by the client. (See section 2.2 of http://www.faqs.org/rfcs/rfc1184.html) ##### #!/usr/bin/env python from twisted.internet.protocol import Factory from twisted.conch.telnet import TelnetProtocol, StatefulTelnetProtocol, TelnetTransport from twisted.conch.telnet import DO, DONT, WILL, WONT LINEMODE = chr(34) LINEMODE_EDIT = chr(1) + chr(1) LINEMODE_TRAPSIG = chr(1) + chr(2) LINEMODE_MODEACK = chr(1) + chr(4) LINEMODE_SOFTTAB = chr(1) + chr(8) LINEMODE_LITECHO = chr(1) + chr(16) class LineModeProtocol(TelnetProtocol): def connectionMade(self): print("Connection made!") def connectionLost(self, reason): print("Connection lost!") def dataReceived(self, data): if self.transport.lines: line = data.rstrip() print("data received (normal): " + line) if line == "gounbuffered": self.transport.requestNegotiation(LINEMODE, LINEMODE_TRAPSIG) # Only trap signals locally self.transport.lines = False else: # manually buffer data here print("data received (linemode): " + data) if data == "n": self.transport.requestNegotiation(LINEMODE, LINEMODE_EDIT) # Change to edit mode self.transport.lines = True class LineModeTransport(TelnetTransport): def connectionMade(self): self.lines = True self.do(LINEMODE) # Ask client to begin sub-negotation of linemode TelnetTransport.connectionMade(self) def commandReceived(self, command, argument): if argument == LINEMODE: if command == WILL: # Client said OK to our DO; let's... do it self.requestNegotiation(LINEMODE, LINEMODE_EDIT) # The normal (buffered) mode else: TelnetTransport.commandReceived(self, command, argument) class LineModeFactory(Factory): def buildProtocol(self, addr): return LineModeTransport(LineModeProtocol) if __name__ == '__main__': from twisted.internet import reactor port = 2222 factory = LineModeFactory() reactor.listenTCP(port, factory) reactor.run() ##### There is probably an easier way to check the state of LINEMODE, e.g. with getOptionState, but it works. Example: Client: # telnet localhost 2222 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. How are you? gounbuffered testnback again Server: # ./linemode.py Connection made! data received (normal): How are you? data received (normal): gounbuffered data received (linemode): t data received (linemode): e data received (linemode): s data received (linemode): t data received (linemode): n data received (normal): back again Best, Patrick On Sat, Dec 3, 2011 at 14:59, <exarkun@twistedmatrix.com> wrote:

Pardon my double-posting. Here it is, a little less confusing: #!/usr/bin/env python from twisted.internet.protocol import Factory from twisted.conch.telnet import TelnetProtocol, TelnetTransport from twisted.conch.telnet import WILL # More info: http://www.faqs.org/rfcs/rfc1184.html LINEMODE = chr(34) LINEMODE_EDIT = chr(1) + chr(1) LINEMODE_TRAPSIG = chr(1) + chr(2) LINEMODE_MODEACK = chr(1) + chr(4) LINEMODE_SOFTTAB = chr(1) + chr(8) LINEMODE_LITECHO = chr(1) + chr(16) class LinemodeProtocol(TelnetProtocol): def dataReceived(self, data): if self.transport.lines: line = data.rstrip() print("data received (normal): " + line) if line == "gounbuffered": self.transport.requestNegotiation(LINEMODE, LINEMODE_TRAPSIG) # Client should only trap signals locally self.transport.lines = False else: # manually buffer data here print("data received (unbuffered): " + data) if data == "n": self.transport.requestNegotiation(LINEMODE, LINEMODE_EDIT) # Go back to buffered self.transport.lines = True class LinemodeTransport(TelnetTransport): def connectionMade(self): self.lines = True self.do(LINEMODE) # Ask client to begin sub-negotation of linemode TelnetTransport.connectionMade(self) def commandReceived(self, command, argument): if argument == LINEMODE: if command == WILL: # Client said OK to our DO; let's... do it self.requestNegotiation(LINEMODE, LINEMODE_EDIT) # Buffered mode else: TelnetTransport.commandReceived(self, command, argument) class LinemodeFactory(Factory): def buildProtocol(self, addr): return LinemodeTransport(LinemodeProtocol) if __name__ == '__main__': from twisted.internet import reactor port = 2222 factory = LinemodeFactory() reactor.listenTCP(port, factory) reactor.run() On Sat, Dec 3, 2011 at 15:22, Patrick Mylund Nielsen <twisted@patrickmylund.com> wrote:

On 02:22 pm, twisted@patrickmylund.com wrote:
You can manage this without a TelnetTransport subclass: #!/usr/bin/env python from twisted.internet.protocol import Factory from twisted.conch.telnet import ( TelnetProtocol, StatefulTelnetProtocol, TelnetTransport) from twisted.conch.telnet import DO, DONT, WILL, WONT LINEMODE = chr(34) LINEMODE_EDIT = chr(1) + chr(1) LINEMODE_TRAPSIG = chr(1) + chr(2) LINEMODE_MODEACK = chr(1) + chr(4) LINEMODE_SOFTTAB = chr(1) + chr(8) LINEMODE_LITECHO = chr(1) + chr(16) class LineModeProtocol(TelnetProtocol): def connectionMade(self): print("Connection made!") self.lines = True # Ask client to begin sub-negotation of linemode self.transport.do(LINEMODE) def connectionLost(self, reason): print("Connection lost!") def enableRemote(self, option): if option == LINEMODE: # The normal (buffered) mode self.transport.requestNegotiation( LINEMODE, LINEMODE_EDIT) return True return False def dataReceived(self, data): if self.lines: line = data.rstrip() print("data received (normal): " + line) if line == "gounbuffered": # Only trap signals locally self.transport.requestNegotiation( LINEMODE, LINEMODE_TRAPSIG) self.lines = False else: # manually buffer data here print("data received (linemode): " + data) if data == "n": # Change to edit mode self.transport.requestNegotiation( LINEMODE, LINEMODE_EDIT) self.lines = True class LineModeFactory(Factory): def buildProtocol(self, addr): return TelnetTransport(LineModeProtocol) if __name__ == '__main__': from twisted.internet import reactor port = 2222 factory = LineModeFactory() reactor.listenTCP(port, factory) reactor.run() Jean-Paul

Patrick, Thanks so much for that example, it was really helpful! I understand a little better about how the protocol works. I tested it on Linux, and it works just like you said. Perhaps not surprisingly I'm having some difficulty when running the server on Windows. When I do a self.will(chr(1)) # set echo mode I get "DO 1" in return from the client. This is what I would expect. But when I do self.do(LINEMODE) # ask client to negotiate LINEMODE I get "WONT LINEMODE" in return. I get this with both clients I'm testing with. If I send "WILL LINEMODE" I get "DONT LINEMODE" in return. So it seems no matter what I do, my clients aren't willing to negotiate about this. I'm using the same code as the Linux server. Anyway, thanks for everyone's help so far...! At least it is working in Linux. Does anyone know if I have to do anything special for Windows? Can anyone confirm it doesn't work in Win 7? Maybe I am missing some configuration property, or...? Thanks! Lee

Hi Lee, You're welcome, although it seems I didn't solve your problem. I've tried running it on Linux (don't think it's significant) and connecting from Windows. It seems like Windows Telnet doesn't support it at all, and PuTTY's implementation is different: It negotiates, but doesn't actually change modes, e.g. send in real time rather than use its own editing. Unfortunately I know next to nothing about these clients' implementation of RFC 1184 (or lack thereof). If you are willing to divulge a little more information about what you are building, maybe someone can suggest a more suitable solution. Perhaps a custom client is your best bet. Best, Patrick On Sun, Dec 4, 2011 at 04:22, Lee Orsino <lmorsino@gmail.com> wrote:

On Thu, Dec 1, 2011 at 7:00 PM, Lee Orsino <lmorsino@gmail.com> wrote:
reactor.listenTCP(8023, f)
It's been a loong time since I've done anything with telnet, but it used to be the case that telnet clients would not do any negotiation if they connected to non-standard ports. Have you tried running your server on port 23? -- Jeff Ollie

On Thu, Dec 1, 2011 at 5:34 PM, Jeffrey Ollie <jeff@ocjtech.us> wrote:
I just happen to have been doing something similar, with TERMINAL-TYPE, and negotiation works fine regardless of port used. Clearly something else is wrong. ~ C. -- When the facts change, I change my mind. What do you do, sir? ~ Keynes Corbin Simpson <MostAwesomeDude@gmail.com>

Hmmm. Do I need to do something with the enableRemote/enableLocal methods of TelnetTransport?

On 2 Dec, 04:43 am, lmorsino@gmail.com wrote:
Yes. The default implementation of enableRemote does not allow any options to be enabled. You must override it if you want the peer to be allowed to enable any options. Jean-Paul

Jean-Paul, Thanks for your response. I still can't get self.will(LINEMODE) to work but I did get self.telnet_WILL(LINEMODE) to run without throwing an Error. Can you elaborate on what needs to happen in the enableRemote? My impression was that once I send the negotiation WILL command from server to the client, the client will respond with either a DO or a DONT. What further logic is necessary? Thanks! On Fri, Dec 2, 2011 at 4:56 PM, <exarkun@twistedmatrix.com> wrote:

On 03:53 am, lmorsino@gmail.com wrote:
I can't say what further logic is necessary, because I don't know what logic you have now. The description of your code you gave above is a good introduction to an actual code listing (<http://sscce.org/>), but it's not sufficient on its own. Jean-Paul

Hi Lee, Here is a complete example that works with Twisted 11.0. LINEMODE itself is enabled at connect, but it has different modes that determine what is sent when by the client. (See section 2.2 of http://www.faqs.org/rfcs/rfc1184.html) ##### #!/usr/bin/env python from twisted.internet.protocol import Factory from twisted.conch.telnet import TelnetProtocol, StatefulTelnetProtocol, TelnetTransport from twisted.conch.telnet import DO, DONT, WILL, WONT LINEMODE = chr(34) LINEMODE_EDIT = chr(1) + chr(1) LINEMODE_TRAPSIG = chr(1) + chr(2) LINEMODE_MODEACK = chr(1) + chr(4) LINEMODE_SOFTTAB = chr(1) + chr(8) LINEMODE_LITECHO = chr(1) + chr(16) class LineModeProtocol(TelnetProtocol): def connectionMade(self): print("Connection made!") def connectionLost(self, reason): print("Connection lost!") def dataReceived(self, data): if self.transport.lines: line = data.rstrip() print("data received (normal): " + line) if line == "gounbuffered": self.transport.requestNegotiation(LINEMODE, LINEMODE_TRAPSIG) # Only trap signals locally self.transport.lines = False else: # manually buffer data here print("data received (linemode): " + data) if data == "n": self.transport.requestNegotiation(LINEMODE, LINEMODE_EDIT) # Change to edit mode self.transport.lines = True class LineModeTransport(TelnetTransport): def connectionMade(self): self.lines = True self.do(LINEMODE) # Ask client to begin sub-negotation of linemode TelnetTransport.connectionMade(self) def commandReceived(self, command, argument): if argument == LINEMODE: if command == WILL: # Client said OK to our DO; let's... do it self.requestNegotiation(LINEMODE, LINEMODE_EDIT) # The normal (buffered) mode else: TelnetTransport.commandReceived(self, command, argument) class LineModeFactory(Factory): def buildProtocol(self, addr): return LineModeTransport(LineModeProtocol) if __name__ == '__main__': from twisted.internet import reactor port = 2222 factory = LineModeFactory() reactor.listenTCP(port, factory) reactor.run() ##### There is probably an easier way to check the state of LINEMODE, e.g. with getOptionState, but it works. Example: Client: # telnet localhost 2222 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. How are you? gounbuffered testnback again Server: # ./linemode.py Connection made! data received (normal): How are you? data received (normal): gounbuffered data received (linemode): t data received (linemode): e data received (linemode): s data received (linemode): t data received (linemode): n data received (normal): back again Best, Patrick On Sat, Dec 3, 2011 at 14:59, <exarkun@twistedmatrix.com> wrote:

Pardon my double-posting. Here it is, a little less confusing: #!/usr/bin/env python from twisted.internet.protocol import Factory from twisted.conch.telnet import TelnetProtocol, TelnetTransport from twisted.conch.telnet import WILL # More info: http://www.faqs.org/rfcs/rfc1184.html LINEMODE = chr(34) LINEMODE_EDIT = chr(1) + chr(1) LINEMODE_TRAPSIG = chr(1) + chr(2) LINEMODE_MODEACK = chr(1) + chr(4) LINEMODE_SOFTTAB = chr(1) + chr(8) LINEMODE_LITECHO = chr(1) + chr(16) class LinemodeProtocol(TelnetProtocol): def dataReceived(self, data): if self.transport.lines: line = data.rstrip() print("data received (normal): " + line) if line == "gounbuffered": self.transport.requestNegotiation(LINEMODE, LINEMODE_TRAPSIG) # Client should only trap signals locally self.transport.lines = False else: # manually buffer data here print("data received (unbuffered): " + data) if data == "n": self.transport.requestNegotiation(LINEMODE, LINEMODE_EDIT) # Go back to buffered self.transport.lines = True class LinemodeTransport(TelnetTransport): def connectionMade(self): self.lines = True self.do(LINEMODE) # Ask client to begin sub-negotation of linemode TelnetTransport.connectionMade(self) def commandReceived(self, command, argument): if argument == LINEMODE: if command == WILL: # Client said OK to our DO; let's... do it self.requestNegotiation(LINEMODE, LINEMODE_EDIT) # Buffered mode else: TelnetTransport.commandReceived(self, command, argument) class LinemodeFactory(Factory): def buildProtocol(self, addr): return LinemodeTransport(LinemodeProtocol) if __name__ == '__main__': from twisted.internet import reactor port = 2222 factory = LinemodeFactory() reactor.listenTCP(port, factory) reactor.run() On Sat, Dec 3, 2011 at 15:22, Patrick Mylund Nielsen <twisted@patrickmylund.com> wrote:

On 02:22 pm, twisted@patrickmylund.com wrote:
You can manage this without a TelnetTransport subclass: #!/usr/bin/env python from twisted.internet.protocol import Factory from twisted.conch.telnet import ( TelnetProtocol, StatefulTelnetProtocol, TelnetTransport) from twisted.conch.telnet import DO, DONT, WILL, WONT LINEMODE = chr(34) LINEMODE_EDIT = chr(1) + chr(1) LINEMODE_TRAPSIG = chr(1) + chr(2) LINEMODE_MODEACK = chr(1) + chr(4) LINEMODE_SOFTTAB = chr(1) + chr(8) LINEMODE_LITECHO = chr(1) + chr(16) class LineModeProtocol(TelnetProtocol): def connectionMade(self): print("Connection made!") self.lines = True # Ask client to begin sub-negotation of linemode self.transport.do(LINEMODE) def connectionLost(self, reason): print("Connection lost!") def enableRemote(self, option): if option == LINEMODE: # The normal (buffered) mode self.transport.requestNegotiation( LINEMODE, LINEMODE_EDIT) return True return False def dataReceived(self, data): if self.lines: line = data.rstrip() print("data received (normal): " + line) if line == "gounbuffered": # Only trap signals locally self.transport.requestNegotiation( LINEMODE, LINEMODE_TRAPSIG) self.lines = False else: # manually buffer data here print("data received (linemode): " + data) if data == "n": # Change to edit mode self.transport.requestNegotiation( LINEMODE, LINEMODE_EDIT) self.lines = True class LineModeFactory(Factory): def buildProtocol(self, addr): return TelnetTransport(LineModeProtocol) if __name__ == '__main__': from twisted.internet import reactor port = 2222 factory = LineModeFactory() reactor.listenTCP(port, factory) reactor.run() Jean-Paul

Patrick, Thanks so much for that example, it was really helpful! I understand a little better about how the protocol works. I tested it on Linux, and it works just like you said. Perhaps not surprisingly I'm having some difficulty when running the server on Windows. When I do a self.will(chr(1)) # set echo mode I get "DO 1" in return from the client. This is what I would expect. But when I do self.do(LINEMODE) # ask client to negotiate LINEMODE I get "WONT LINEMODE" in return. I get this with both clients I'm testing with. If I send "WILL LINEMODE" I get "DONT LINEMODE" in return. So it seems no matter what I do, my clients aren't willing to negotiate about this. I'm using the same code as the Linux server. Anyway, thanks for everyone's help so far...! At least it is working in Linux. Does anyone know if I have to do anything special for Windows? Can anyone confirm it doesn't work in Win 7? Maybe I am missing some configuration property, or...? Thanks! Lee

Hi Lee, You're welcome, although it seems I didn't solve your problem. I've tried running it on Linux (don't think it's significant) and connecting from Windows. It seems like Windows Telnet doesn't support it at all, and PuTTY's implementation is different: It negotiates, but doesn't actually change modes, e.g. send in real time rather than use its own editing. Unfortunately I know next to nothing about these clients' implementation of RFC 1184 (or lack thereof). If you are willing to divulge a little more information about what you are building, maybe someone can suggest a more suitable solution. Perhaps a custom client is your best bet. Best, Patrick On Sun, Dec 4, 2011 at 04:22, Lee Orsino <lmorsino@gmail.com> wrote:
participants (5)
-
Corbin Simpson
-
exarkun@twistedmatrix.com
-
Jeffrey Ollie
-
Lee Orsino
-
Patrick Mylund Nielsen