Cisco 7942 API/XML Application Hacking

I came across a few Cisco 7942 IP Phones a while back. Typically, these aren't that much use as they usually come with the Cisco SCCP (Skinny) firmware flashed, meaning they'll only work with the proprietary Cisco CallManager software (or with Asterisk using the skinny channel driver, but that's something else...).

As it happened, it was quite easy to flash the SIP firmware with a TFTP server (I used the standard inetd server) and a DHCP server (I used isc-dhcp-server) to point the phones to the right place (with DHCP option 66). Once they were flashed, they'd happily talk to Asterisk after a little more configuration, most of which you can find out about in the Asterisk Book.

After all the initial setup and playing around with Asterisk is done, there's a few other nice things you can do with these phones in particular.

They have a rather neat set of APIs for performing various actions on the phone remotely using simple HTTP requests with XMLish payloads. Some of these involve requesting content from remote servers and displaying it on the phone for the user to interact with, and some of them involve POSTing instructions to the phone to invoke certain actions. I'll detail some of them here but you can of course also (at your peril) refer to the colossal PDF manual Cisco offer on the subject.

Authentication

Authentication works in a fairly bizarre but ultimately straightforward way.

Essentially, the configuration file for each phone (SEPXXXXXXXXXXXX.cnf.xml, where the Xs are replaced with an uppercase hexadecimal representation of the phone's Ethernet MAC address) which resides on the TFTP server contains an XML node called <authenticationURL> under the top-level <device> node. This node contains a URL, to which the phone is to query using a HTTP GET request whenever an authenticated service is required. The phone passes a few query string parameters (UserID, Password and devicename containing the MAC address of the phone, including the SEP prefix).

The expectation is that the body of the response to this request will contain only the 10 bytes - AUTHORIZED - if the user is accepted by the server, or any other content if not.

Display Screenshot

Perhaps one of the coolest (but least useful, aside from maybe debugging phones on the other side of a building/the world) that the 7942 offers is the ability to request an image of the display. This one is a good one to test your authentication setup described earlier as it's a simple GET request that you can make in the browser. When you make the request, you'll be prompted for a username and password (the phone uses HTTP Basic authentication for API requests sent to the phone).

A very grayscale looking screenshot

The request URI will be something like http://<IP>/CGI/Screenshot, obviously replacing <IP> with the IP address of your 7942 IP Phone.

Fortune Teller

The phone's XML applications are a little dated in concept but still fun to play with. Simply put, you can write sort of primitive hypermedia applications that work by serving up XML using a special proprietary Cisco schema to the phone when requested.

As an example, I wrote a Fortune Teller one using fortune.

The application has two parts. Firstly, there's a Services Menu XML document that describes the services menu items that will be shown to the user when they press the Services button on their phone:

<CiscoIPPhoneMenu>  
 <Title>IP Phone Services</Title>
 <Prompt>Choose a service...</Prompt>
<MenuItem>  
  <Name>Fortune Teller</Name>
  <URL>http://[Server-IP]/fortune.php</URL>
</MenuItem>  
</CiscoIPPhoneMenu>  

You can add more <MenuItem> nodes for additional items. I think there's some arbitrary limit like 32 menu items per <CiscoIPPhoneMenu> but this might vary on different models of IP Phone.

The second part of the application is the resource at http://[Server-IP]/fortune.php that will be the pithy quip displayed by the service. A PHP script renders this for us:

<?php  
header('Content-Type: text/xml');  
header('Connection: close');  
?>
<CiscoIPPhoneText>  
<Title>Fortune Teller</Title>  
<Prompt/>  
<Text><?php echo htmlspecialchars(`/usr/games/fortune`, ENT_XML1); ?></Text>  
</CiscoIPPhoneText>  

Note that we escaped the output of /usr/games/fortune just in case the characters it contains aren't so fortunate.

A silly pithy quip displayed on the 7942

There's a handy Update softkey to re-send the GET request and display another quip too.

Dial-on-Click with Protocol Handlers

One of the Free Desktop standards, the Desktop Entry standard details a file format for describing how programs should be launched from within the desktop environment.

One of the ways programs using this standard can be launched is via protocol handlers like tel:.

I wanted to set up a script such that when a tel: link was clicked in any application on my desktop, my desk phone would automatically dial the corresponding number. As it turns out, using the 7942's XML API and the Desktop Entry file standard, it was quite easy to accomplish.

The first step was defining the desktop entry - this took the form of a file in /usr/share/applications/ with content as follows:

[Desktop Entry]
Name=Desk Phone  
Comment=Calls a number on your Desk Phone  
Exec=/usr/local/bin/dial %U  
Terminal=false  
Type=Application  
Encoding=UTF-8  
Categories=Network;Application;  
MimeType=x-scheme-handler/tel;  
X-KDE-Protocols=tel  

After an invocation of /usr/bin/update-desktop-database to refresh the database of Desktop Entries, clicking a tel: link automatically executes /usr/local/bin/dial and passes the link string to it as the first argument string.

My implementation of /usr/local/bin/dial was pretty simple too, just a Python script using the Requests library to send of a HTTP POST request to the phone. This request uses the CiscoIPPhoneExecute XML node to trigger an action on the phone itself.

#!/usr/bin/env python
import re  
import sys  
import requests

# Do we have a tel: URI to act upon?
if len(sys.argv) < 2:  
    exit(1)

# Extract the number portion of the tel: URI
tel_number_matches = re.search('tel:(?://)?([0-9]+)', sys.argv[1]) or exit(2)

# Initiate the dial procedure on the desk phone
tel_number = tel_number_matches.group(1)  
url = 'http://<Phone IP>/CGI/Execute'  
headers = {'authorization': 'Basic Zm9vOmZvbw==', 'content-type': 'text/xml'}  
xml = 'XML=<CiscoIPPhoneExecute><ExecuteItem URL="Dial:' + tel_number + '" /></CiscoIPPhoneExecute>'  
requests.post(url, data=xml, headers=headers)  

Worth noting the odd XML= syntax around the start of the request body - not sure why this is required but it took some digging to find out that it was. It's also worth noting the HTTP Basic auth that's taking place here - this is the same authentication as described previously - the credentials are just passed on to your <authenticationUrl> URL - as long as the response is AUTHORIZED, it'll work.

And as if by magic, when I click tel: link in Chrome or Firefox, my desk phone instantly dials it and I can pick up the receiver and talk.


comments powered by Disqus