Telnet

This post is a recount of the insights I gained while attempting to create a telnet client and server. As you read this and any post in this series, remember that I put clarity of the process ahead of simplicity and compactness of the code. Everything is done as strictly linearly as possible, so it is easy to track what is going on. If one were to create these programs in earnest, this design choice would be a bad one as the code got more complicated with more conditionals. But for understanding the basic flow, it works quite well.

Objectives:

  1. Create a telnet client that can connect to a remote server and issue commands.
  2. Create a telnet server that can receive connections from any telnet client and run commands.

Introduction

Telnet is the predecessor to Secure Shell, ssh. It was, as far as I can tell, the first ratified system for remote access. Considering its initial document is rfc15 in 1969, I find it hard to believe anything came before it. It predates even the socket by about 14 years. Though in the same year the socket specification was released, 1983, telnet had an update as well, rfc855. Since I'm not writing a series on operating system primitives, we will stick to implementing using sockets.

The basic principle of Telnet is a glorified socket. You could accomplish the basic interactions in python with 5 lines:

    import socket
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(('towel.blinkenlights.nl',23))
    while True:
        print(s.recv(4096))

Sit back and enjoy "Episode VI: A New Hope" in ASCII!

Put simply, we read bytes out of the socket and write bytes into the socket, that simple. And that's what we'll try to recreate here.

But first, some things that my code doesn't explain: Telnet has these four commands, Do, Don't, Will, Won't. These are simple statements of state change. Do and Don't are things the sending server requests the other side "Do" or "Don't" do. Will and Won't are things that the sending server "Will" or "Won't" do. These also function as acknowledgments. If I say "Do Echo" you respond with "Will Echo" or "Won't Echo".

Telnet Client

Let us begin with a client. You can see what I have written here. I went into great detail explaining each of the steps in the process, so here I'm only going to give a high-level overview. The idea of this program is that it is going to read, one byte at a time, off the socket. The reason for that is we are looking for the byte "\xff", or 255, which is the escape character. When 255 comes up, it means the next byte is a command—one of the dos or don'ts from above.

If we do receive a 255 byte, then we switch state. We go from the writing to the screen state to the executing commands state. In that state, we read two bytes, one for do/don't, and one for what it is we will/won't do. Once that is done, we pass back to the main loop.

One at a time, we read each byte and write it to the screen. Once we have read everything, we leave the while loop. Then we wait for the user to enter a command. Ideally, we would wait for a moment and then go back to check the port. But that kind of interactivity is beyond the scope of this little exercise.

This operation is the basic idea of a telnet client. Read from the server till there is nothing, then read from the user and then send it to the server. Repeat.

Telnet Server

As with the client, I wrote many comments in the code, here. So I will give a simple overview in this post.

The server turned out to be even easier than the client. The server listens until it receives a new line character, rn. Once it has it executes the line of characters it has received, then it returns the result. There are ways this could be improved, such as the ability to send information back from interactive processes like editors or ongoing processes like top. But this operation gives you the essential service at its simplest.

Lessons

I started here for two reasons. One, it is a simple system that is easy to understand. Two, I technically had already done it here. Between those to facts, I felt like I would have an excellent first step on this project. The flip side of that though, is that I didn't have as much to learn. But between the two implementations, I must say: Telnet is deceptively simple. It would not be hard to make a simple little telnet agent in another program to access it quickly. The prevalence of SSH mostly obscures this use, but if you can't use ssh and install other programs, you could always consider rolling your own telnet server.

Real code

The code I have written here is a toy at best. Python has a real telnet library and if you are going to do anything more than a toy, use a real library. Not only will it take a considerable amount of fiddliness off your shoulders, but it also reduces security risk.