Making a simple Web based SSH client using Node.js and Socket.io

6
18897
6 min read

If you are reading this post, you probably know what SSH stands for. But just for the sake of formality, here we go:

SSH stands for Secure Shell. It is a network protocol for secure access to the shell on a remote computer.

You can do much more over SSH besides commanding your computer. Here you can find further information: http://en.wikipedia.org/wiki/Secure_Shell.

In this post, we are going to create a very simple web terminal. And when I say simple, I mean it! However much you like colors, it will not support them because the parsing is just beyond the scope of this post. If you want a good client-side terminal library use term.js. It is made by the same guy who wrote pty.js, which we will be using. It is able to handle pretty much all key events and COLORS!!!!


Installation

I am going to assume you already have your node and npm installed. First we will install all of the npm packages we will be using:

npm install express pty.js socket.io

Express is a super cool web framework for Node. We are going to use it to serve our static files. I know it is a bit overkill, but I like Express.

pty.js is where the magic will be happening. It forks processes into virtual pseudo terminals and provides bindings for communication.

Socket.io is what we will use to transmit the data from the web browser to the server and back. It uses modern WebSockets, but provides fallbacks for backward compatibility. Anytime you want to create a real-time application, Socket.io is the way to go.

Planning

First things first, we need to think what we want the program to do.

We want the program to create an instance of a shell on the server (remote machine) and send all of the text to the browser. Back in the browser, we want to capture any user events and send them back to the server shell.

The WebSSH server

This is the code that will power the terminal forwarding.

Open a new file named server.js and start by importing all of the libraries:

var express = require('express');
var https = require('https');
var http = require('http');
var fs = require('fs');
var pty = require('pty.js');

Set up express:

// Setup the express app
var app = express();
// Static file serving
app.use("/",express.static("./"));

Next we are going to create the server.

// Creating an HTTP server
var server = http.createServer(app).listen(8080)

If you want to use HTTPS, which you probably will, you need to generate a key and certificate and import them as shown.

var options = {
key: fs.readFileSync('keys/key.pem'),
cert: fs.readFileSync('keys/cert.pem')
};

Then use the options object to create the actual server. Notice that this time we are using the https package.

// Create an HTTPS server
var server = https.createServer(options, app).listen(8080)

CAUTION: Even if you use HTTPS, do not use this example program on the Internet. You are not authenticating the client in any way and thus providing a free open gate to your computer. Please make sure you only use this on your Private network protected by a firewall!!!

Now bind the socket.io instance to the server:

var io = require('socket.io')(server);

After this, we can set up the place where the magic happens.

// When a new socket connects
io.on('connection', function(socket){
// Create terminal
var term = pty.spawn('sh', [], {
   name: 'xterm-color',
   cols: 80,
   rows: 30,
   cwd: process.env.HOME,
   env: process.env
});
// Listen on the terminal for output and send it to the client
term.on('data', function(data){
   socket.emit('output', data);
});
// Listen on the client and send any input to the terminal
socket.on('input', function(data){
   term.write(data);
});
// When socket disconnects, destroy the terminal
socket.on("disconnect", function(){
   term.destroy();
   console.log("bye");
});
});

In this block, all we do is wait for new connections. When we get one, we spawn a new virtual terminal and start to pump the data from the terminal to the socket and vice versa. After the socket disconnects, we make sure to destroy the terminal.

If you have noticed, I am using the simple sh shell. I did this mainly because I don’t have a fancy prompt on it. Because we are not adding any parsing logic, my bash prompt would show up like this:

]0;[email protected]: ~ _[01;32m✓ [33mpiman_[0m ↣ _[1;34m[~]_[37m$[0m - Eww!

But you may use any shell you like.

This is all that we need on the server side. Save the file and close it.

Client side

The client side is going to be just a very simple HTML file.

Start with a very simple HTML markup:

<!doctype html>
<html>
<head>
<title>SSH Client</title>
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/socket.io/1.3.5/socket.io.min.js"></script>
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<style>
   body {
     margin: 0;
     padding: 0;
   }
   .terminal {
     font-family: monospace;
     color: white;
     background: black;
   }
</style>
</head>
<body>
<h1>SSH</h1>
<div class="terminal">
</div>
<script>
</script>
</body>
</html>

I am downloading the client side libraries jquery and socket.io from cdnjs. All of the client code will be written in the script tag below the terminal div.

Surprisingly the code is very simple:

// Connect to the socket.io server
var socket = io.connect('http://localhost:8080');
// Wait for data from the server
socket.on('output', function (data) {
   // Insert some line breaks where they belong
   data = data.replace("n", "<br>");
   data = data.replace("r", "<br>");
   // Append the data to our terminal
   $('.terminal').append(data);
});
// Listen for user input and pass it to the server
$(document).on("keypress",function(e){
var char = String.fromCharCode(e.which);
socket.emit("input", char);
});

Notice that we do not have to explicitly append the text the client types to the terminal mainly because the server echos it back anyways.

Now we are done! Run the server and open up the URL in your browser.

node server.js

You should see a small prompt and be able to start typing commands.

You can now explore you machine from the browser! Remember that our Web Terminal does not support Tab, Ctrl, Backspace or Esc characters. Implementing this is your homework.

Conclusion

I hope you found this tutorial useful. You can apply the knowledge in any real-time application where communication with the server is critical. All the code is available here. Please note, that if you’d like to use a browser terminal I strongly recommend term.js. It supports colors and styles and all the basic keys including Tabs, Backspace etc. I use it in my PiDashboard project. It is much cleaner and less tedious than the example I have here. I can’t wait what amazing apps you will invent based on this.

About the Author

Jakub Mandula is a student interested in anything to do with technology, computers, mathematics or science.

6 COMMENTS

LEAVE A REPLY

Please enter your comment!
Please enter your name here