Calamity Lane

Curious Code for Curious Coders

How to Turn a Rails Application Into a Telnet Server in 30 Lines of Code!

Ruby on Rails is a beautiful thing; simple, fast, and powerful. As they say, it takes the pain out of web development.

I’m fortunate enough to have been able to use Rails at work for a few months now, and the more I learn about the Rails framework and the Ruby language, the more enamoured I become of the simplicity, beauty, and zen-like nature of these tools. But you didn’t come here to see me gush with adoration like a schoolgirl at an Elvis concert, you came to see:

How to Turn a Rails Application Into a Telnet Server in 30 Lines of Code!

PLEASE DON’T DO THIS!

What follows was intended to illustrate the power and flexibility of Ruby, not to kickstart somebody’s server project. It’s alarming that this has been the most popular page on my blog for nearly a year, and somewhat embarrassing that I haven’t let people know more explicitly that this should not be used in real software.

If you need to write a telnet server, you should really, really really be using EventMachine anyway, which is better than GServer in just about every possible way. GServer is still OK for knocking out spikes or quick prototypes, just be aware that GServer runs in an event loop, whereas EventMachine uses callbacks– anything you write will need to be retooled to move from one to the other.

That said, here’s the original article.

First, some code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#!/usr/bin/env ruby
require 'config/environment'
require 'gserver'

class TestServer < GServer
  def serve(io)
    begin
      io.puts '>> WELCOME <<'
      loop do
        begin
          telnet_input = io.gets
          break if telnet_input =~ /Aquit/
          io.puts eval(telnet_input)
        rescue Exception => e
          # Got kabooms from trying to eval user input
          # Catch it & let the user know
          io.puts "Oops - #{e}"
        end
        log "Received #{telnet_input}"
      end
      io.puts ">> GOODBYE <<"
      io.close
    rescue Exception => e
      # The server blew up... handle it here
      puts "DOH! - #{e}"
    end
  end
end


ts = TestServer.new 1234
ts.start
ts.audit = true
ts.join

This code should work, as is, on just about any Rails application (as far as I’ve been able to tell; it’s worked on the few I’ve tried).

Last night I was tooling around, playing with code. I’d had an idea for some server software I’d like to write, so I mocked up a quick throwaway system in Rails, and started playing around with some of the logic. I liked the results, and I’d already decided I wanted to write the server in Ruby, but the thought of taking all that functionality and porting everything to DBI calls or whatever was handy just made me tired, especially since Rails does all the tedious database tasks for you.

Fortunately, I’m slowly learning that Ruby is written for lazy programmers, and after a little Googling and some educated guesses, I was able to whack the entire Rails app into a script simply by including:

require 'config/environment'

…at the top of a file in the project root. This makes everything available to your application.

1
2
3
#!/usr/bin/env ruby
require 'config/environment'
require 'gserver'

This sets the scripting environment, includes the Rails app, and makes the GServer class available. GServer is a freebie that comes with Ruby, so there’s no need for gem installs or other libraries.

1
2
class TestServer < GServer
  def serve(io)

We’re then creating our own class which inherits from GServer, and overriding its “serve” method, which is where all the interesting stuff happens.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  io.puts '>> WELCOME <<'
  loop do
    begin
      telnet_input = io.gets
      break if telnet_input =~ /Aquit/
      io.puts eval(telnet_input)
    rescue Exception => e
      # Got kabooms from trying to eval user input
      # Catch it & let the user know
      io.puts "Oops - #{e}"
    end
    log "Received #{telnet_input}"
  end
  io.puts ">> GOODBYE <<"
  io.close

This is the heart of the server (without the exception handling). Simply speaking, the serve() method is what handles user connections, and the “io” object lets us do gets and puts across the wire.

When a user connects, we spit out a welcome message, then go into an endless loop that responds to user requests. The following:

1
      telnet_input = io.gets

…grabs whatever line the user types in, terminating by default on a carriage return. We need to throw the result of io.gets into a variable, since making multiple calls to io.gets will cause the system to grab multiple lines of input from the stream.

1
      break if telnet_input =~ /Aquit/

…kicks us out of the loop if the user types “quit”. The “A” portion of the regex matches the beginning of the string, and the “$” matches the “end of line” character.

1
2
  io.puts eval(telnet_input)
  log "Received #{telnet_input}"

The first line here does an eval() on whatever the user typed in, returning the value back across the wire. This should allow the user to run any command that Ruby (and your Rails app) will accept. The second line spits whatever the user typed in to the console where the server is running.

1
2
  io.puts ">> GOODBYE <<"
  io.close

Once the loop is broken, we burp out a fond farewell and cleanly close the connection.

1
2
3
4
ts = TestServer.new 1234
ts.start
ts.audit = true
ts.join

The rest of the code just sets a few variables and runs the service. The “1234” on the creation of the “ts” object sets the listening port. Turning on auditing allows us to see when someone logs on and off from the console (which can easily be redirected to a log file).

CAVEATS:

This is an extremely simple server. It has no security, no performance optimization, and probably has more holes than a cheesecloth factory. It is very naïve, and while it technically allows multiple users to log on, they’ll all share the same class variables. Really, it’s only interesting at this point to show what can be done.

That having been said, the ease with which I managed it is one of the many testaments to Ruby’s power and simplicity. I’ve programmed for many years, coded a number of servers, and this was the first time I’ve ever felt drunk with power after a coding session. “I can do anything!” I bellowed as I reeled around the room, knocking old monitors and dusty programming tomes off the desk. “No one can stop me!” Fortunately, my wife, who is a lot more level-headed than I am, pointed out that she could, indeed, stop me, since it had gone past two in the morning.