Telnet Network Virtual Terminal (NVT)

A day I wanted to write a Telnet server, the problem started when I wanted to disable local echo to prompt the user for a password
So I started learning NVT commands from RFC after finding lots of (almost only in fact) stupidity on google.

2 things to know:
All command start with the IAC code (\xFF or 255)
If you want to send \xFF as DATA you should send \xFF\xFF (it’s a \\ look like)

Some example :
Client want to disable server echo, it send the IAC DONT ECHO command and the server answer IAC WONT ECHO to confirm
If the server want the client to stop the local ECHO it sends IAC WILL ECHO. This tell the client that the server will send back all character it get, this is a good way to hide password when user type, as you can just did not send back character or send * instead.

Telnet NVT Code:

Option Name Hex value Dec value Shor desc Long desc
BINARY \x00 0 BINARY (RCF 856)
ECHO \x01 1 Echo (ECHO) (RFC 857)
SGA \x03 3 Suppress Go Ahead (SGA) (RFC 858)
TTYPE \x18 24 Terminal Type (RFC 1091)
NAWS \x1F 31 Window Size (RFC 1073)
SE \xF0 240 End of subnegotiation parameters.
NOP \xF1 241 No Operation (NOP) No Operation.
DM \xF2 242 Data mark (DM) Indicates the position of a Synch event within the data stream. This should always be accompanied by a TCP urgent notification.
BRK \xf3 243 Break (BRK) Break. Indicates that the “break” or “attention” key was hit.
IP \xF4 244 Interrupt Process (IP) suspend/abort process.
AO \xF5 245 Abort Output (AO) process can complete, but send no more output to users terminal.
AYT \xF6 246 Are You There (AYT) check to see if system is still running.
EC \xF7 247 Erase Character (EC) delete last character sent typically used to edit keyboard input.
EL \xF8 248 Erase Line (EL) delete all input in current line.
GA \xF9 249 Go Ahead (GA) Used, under certain circumstances, to tell the other end that it can transmit.
SB \xFA 250 Indicates that what follows is subnegotiation of the indicated option.
Action Name Hex value Dec value Shor desc Long desc
WILL \xFB 251 Indicates the desire to begin performing, or confirmation that you are now performing, the indicated option.
WONT \xFC 252 Indicates the refusal to perform, or continue performing, the indicated option.
DO \xFD 253 Indicates the request that the other party perform, or confirmation that you are expecting theother party to perform, the indicated option.
DONT \xFE 254 Indicates the demand that the other party stop performing, orconfirmation that you are no longer expecting the other party to perform, the indicated option.
IAC \xFF 255 Interpret as command

All is in RFCs (enjoy them !)

RFC:854 (Telnet)
RFC:857 (Echo) (\x01, 1)
RFC:858 (SGA) (\x03, 3)
RFC:859 (Status) (\x05, 5)
RFC:860 (Timing Mark) (\x06, 6)
RFC:1091 (Terminal Type) (\x18, 24)
RFC:1073 (Window Size) (\x1F, 31)
RFC:1079 (Terminal Speed) (\x20, 32)
RFC:1372 (Remote Flow Control) (\x21, 33)
RFC:1184 (Line Mode) (\x22, 34)
RFC:1408 (Environement Variable) (\x24, 36)

Usefull links:

Here is a sample perl example of how to make a very very simple telnetd:

print $client "\xff\xfd\x00";  ## bin  mode
sysread $client,$buf,512;      ## flush socket
print $client "Login: ";
$login = <$client>;     print $login if $debug;
chomp $login;           print unpack("H*",$login) . "\n" if $debug;
if (!$user->{$login}) { print $client "Bye bye..." }
print $client "\xff\xfb\x01";  ## server echo
sysread $client,$buf,512;      ## flush socket
print $client "Password: ";
$passwd = <$client>;    print $passwd if $debug;
chomp $passwd;          print unpack("H*",$passwd) . "\n" if $debug;
print $client "\xff\xfd\x22";  ## line mode
sysread $client,$buf,512;      ## flush socket
if ($passwd ne $user->{$login}) { print $client "Bye bye..." }
print $client "ok welcome";

You have to echo characters, handle \x7f (delete) correctly and return \x08\x20\x08, handle \x0d and return a crlf, etc…

  1. rick
    April 11th, 2009 at 10:08 | #1

    Wow, I’ve been searching for a tutorial on this for… weeks. Thanks so much!

    Ported to Ruby, it’s something like:
    s.write “Username: ”
    username = s.gets.chomp.gsub(/[^(\x20-\x7F)]*/,”) # clear out weird non-ascii stuff
    s.write “\xff\xfb\x01”
    s.write “Password: ”
    password = s.gets.chomp.gsub(/[^(\x20-\x7F)]*/,”) # clear out weird non-ascii stuff…again.
    s.write “\xff\xfd\x22”

    Where s is the socket variable.

  2. Ajay
    January 27th, 2010 at 17:18 | #2

    How do I send BRK command over telnet? I mean which combination of keys would generate brk value and send it over telnet. Initially I thought ctrl-c or ctrl-break would do that, but when I capture on ethereal I can see only 03 is being sent out.

    It would be great if you could throw some light on this.

    Thanking you in anticipation

  3. Jeb
    May 6th, 2010 at 22:11 | #3

    According to it seems it’s highly dependent of your telnet client.

  4. DaveRandom
    May 14th, 2010 at 12:43 | #4

    Thanks a lot for this, it is by far and away the most useful page on the entire internet relating to this subject.

    Two points, and one question…

    Point 1: The RFC links above no longer seem to be valid – I keep getting timeouts and Googling them instead
    Point 2: Window Size (\x1f) is missing from the option list above

    Question: Which RFC details how AYT (\xf6) should be handled? I’m would assume it would be IAC DO AYT/IAC WILL AYT but this seems to produce varying odd results with puttytel…

  5. Jeb
    May 24th, 2010 at 18:34 | #5

    Thanks, I fixed Point 1 and Point 2.
    Regarding AYT, it’s described in RFC 854, you don’t need to either put DO or WILL, just do IAC AYT, there is no need for WILL/DO/.. as it’s just a query.

  1. No trackbacks yet.