Write a script for Pilot SSH in Python

By Geoffroy Couprie

Geoffroy, a freelance security geek, enjoys useful tools and useless hacks. When he is not busy looking for new ways to protect applications, he launches fun new projects.

Usable SSH on phones with Pilot SSH

Pilot SSH is an iPhone app for server administration, but not yet another shell app. Instead, it generates a simple user interface to launch server side scripts and display their results.

I tried multiple SSH clients for smartphones (Prompt and iSSH for iPhone, ConnectBot for Android, and even PocketPutty for Windows Mobile), and it felt clunky: maybe the shell was not the right way to interact with a phone. So I started working on a better way to manage my servers.

Most of the administration tasks can be abstracted away in a script, so why not take advantage of that and create a nice UI for them? That way, the keyboard will not be the first mean of interaction with a server.

Thanks to this idea, flushing a cache, upgrading a WordPress website or restarting a web server can be done in a few taps on your screen! This is very useful for quick fixes, when you're on call in a bar, or when you want to hide from your significant other the fact that you're still working on vacation.

The application is currently available for iPhone, and there will be an Android version very soon.

How to Use It

Pilot SSH launches scripts stored one the server side, and by parsing their (JSON) output, generates the user interface. The scripts are open source, and can be downloaded and shared on Pilot SSH's Github repository. Any language can be used to develop them, as long as they conform to the script API.

Let's make a simple script to illustrate this. How about a Python script displaying the network interfaces?

First, we will create the ~/.pilotssh/index file:

#!/bin/bash

echo '{ "version": 1,
          "title": "Commands",
           "type":"commands",
        "values" : [ { "name" : "Network interfaces",
                      "value" : "",
                    "command" : ".pilotssh/network/network.py"
                     }
                   ]
       }'

This file is the first script called by Pilot SSH after connecting. This script will create one table line in the interface. If you touch this line, the script .pilotssh/network/network.py will be launched (note that you can put an absolute path if you want). You can remark that the title attribute writes to the window's title, and that every hash in the values array creates a line in the table view:

index

Now, let's create network.py. You must put a shebang at the beginning (#!/usr/bin/python) and make the file executable. The script will need the netifaces package, so install it through easy_install.

#!/usr/bin/python
import netifaces, sys

def command_from_interface(intf):
    return '{ "name" : "' + intf + '", "value" : "'+ netifaces.ifaddresses(intf)[netifaces.AF_INET][0]["addr"] + '", "command" : ".pilotssh/network/network.py ' + intf + '" }'

def index():
    result = '{ "version": 1, "title": "Network Interfaces", "type":"commands", "values" : [ '
    interfaces = netifaces.interfaces()
    length = len(interfaces)

    if(length >= 1):
        result += command_from_interface(interfaces[0])

    if(length > 1):
        for i in xrange(1, length):
            result += ', ' + command_from_interface(interfaces[i])

    result += ' ] }'

    print result

def command_from_key_value(key, value):
    return '{ "name" : "' + key + '", "value" : "'+ value + '", "command" : "" }'

def interface_info(intf):
    result = '{ "version": 1, "title": "' + intf + '", "type":"commands", "values" : [ '

    af_inet = netifaces.ifaddresses(intf)[netifaces.AF_INET][0]
    address = af_inet["addr"]
    result += command_from_key_value("IP", address)

    if "broadcast" in af_inet:
        broadcast = af_inet["broadcast"]
        result += ', ' + command_from_key_value("Broadcast", broadcast)

    netmask = af_inet["netmask"]
    result += ', ' + command_from_key_value("Netmask", netmask)

    mac = netifaces.ifaddresses(intf)[netifaces.AF_LINK][0]["addr"]
    result += ', ' + command_from_key_value("MAC", mac)

    result += ' ] }'
    return result


if(len(sys.argv) == 1):
    index()
else:
    print interface_info(sys.argv[1])

This script will first generate an index of all the interfaces, and provide a command to get more information on each one of them. I could have used a library to generate the JSON, but this example is simple enough, so I wrote the output directly.

There is a common pattern in the scripts: first, generate the header (title, etc), then gather information, then loop on the information to fill the values array.

Network interfaces

Here is the JSON generated from the index for my machine:

{ "version": 1,
    "title": "Network Interfaces",
     "type":"commands",
  "values" : [ { "name" : "lo",
                "value" : "127.0.0.1",
              "command" : ".pilotssh/network/network.py lo" },
               { "name" : "eth0",
                "value" : "10.0.2.15",
              "command" : ".pilotssh/network/network.py eth0" },
               { "name" : "eth1",
                "value" : "192.168.56.101",
              "command" : ".pilotssh/network/network.py eth1" }
             ]
}

You can see that I added a value. It is be displayed in yellow in the table. The command key indicates the command sent if we touch one of the lines.

Let's see the result for .pilotssh/network/network.py eth0:

eth0 interface

And here is the corresponding JSON output:

{ "version": 1,
    "title": "eth0",
     "type":"commands",
  "values" : [ { "name" : "IP",
                "value" : "10.0.2.15",
              "command" : "" }, 
               { "name" : "Broadcast",
                "value" : "10.0.2.255",
              "command" : "" },
               { "name" : "Netmask",
                "value" : "255.255.255.0",
              "command" : "" }, 
               { "name" : "MAC",
                "value" : "08:00:27:e9:ae:4e",
              "command" : "" } 
             ]
}

There is no command here, so touching the corresponding lines will not send any command. But we could quite easily use the query attribute to change the value, add a button to put up or down the interface, reload the DHCP, etc.

As you can see, writing a script for Pilot SSH is very easy (seriously, it took me more time to write this article than to write the script). More scripts can be downloaded on the Github repository, and you can use the issues to discuss new script ideas and future features of the application. By the way, I already pushed this Python script on the repository, so feel free to fork it and contribute!

There is a lot more coming for this app in future versions, and it will get more and more useful with all the scripts people will develop and share.


See More Posts About:

Python


Posted by Geoffroy Couprie

LinkedIn Website