Some backgroundI recently had to implement a software which needed to communicate with a system over Telnet.
My first thought was "Well that's easy, I just send and receive raw TCP". In the end things actually turned out to be almost that simple. However, "just raw TCP" didn't get me all the way.
In fact, it didn't even get me started. In order to talk with most Telnet hosts, you must first negotiate some capabilities. If you don't the host will think you are too stuck up to talk to. For you guys who already know this, the following post will be way below your level.
The key to solve the problem was to look the RFC up, as usual. The RFC you should be looking for is RFC 854.
I do not intend this post to be a pedagogic Tutorial or Howto. I am simply documenting the conclusions drawn from my own first time implementation of a Telnet Client.
The idea of TelnetThe basis of the Telnet protocol is that it should as if you where sending raw TCP. At least as long as you only send ASCII.
In order to negotiate about terminal capabilities, local echo and such, there is a set of codes outside the ASCII table range in order to help you along.
The idea is for the host to not only respond with ASCII output, but to be able to format it in a nice human readable way as well. To do this, the host needs to know what tricks the terminal can do.
For me, writing an automated software, taking this into consideration was a necessary evil.
Negotiation FlowTelnet negotiations consists of command pairs. There are the positive commands:
... and the negative...
This means for example that in order to request a capability the host or the client sends DO. In order to confirm, the other side sends WILL. There are a few considerations that are mentioned in the RFC to point out:
- Both parties send an identical request simultanously, each party should consider the other's request as an acknoledgement.
- Parties may only request a change in option status.
- A request to set the party in a mode it is already in shouldn't be answered at all.
- Parties may only request a change in option status.
These pointers are really really important! If you don't consider it in your implementation, you might end up with very interesting loops...
Note: Even though most negotiations are handled in the beginning of a session. There might be new requests sent at any time during a session. If you are writing a user interactive client, these requests are important. However, if you are writing an automated software you just need to know what codes to filter once you are done with the initial chit-chat.
So how do I recognize a command?A command is always preceeded by the IAC character (see RFC 854). After that, one or more characters will follow in order to describe the command.
One thing to remember is the SB and SE characters. They are there in order for one party to request additional modes. Common modes are for example terminal color capabilities and terminal geometry. When you trace a SB character from the host, I suggest you simply google the characters sent in the sequence in order to find the corresponding specifications.
Implementation tipsSo, the straight forward way to handle this is to write a class to wrap around your standard client Socket implementation.
Let your little wrapper catch the IAC codes and handle the negotiations, let the rest pass through. Simple as that.