Adding a secure SIP phone to CUCM using AXL

Warning: This is going to be a bit of a long rant about Cisco AXL API hacking for SIP phones. If you’re not interested in that, stop now! If you are wrestling with this though you might want to pour a hot drink and read through this as its not been as obvious as you’d imagine, especially if you have CUCM version 8.6.x.

I’ve been tasked with a bit of hacking on the Cisco Unified Communications Manager (CUCM) VoIP phone system that we have here at Loughborough. We mostly use Cisco wired phones on campus (using SCCP) but we’ve had lots of requests for DECT wireless phones to be exported. Now Cisco produce a DECT wireless handset and basestation, but it uses SIP to connect the phone to the CUCM servers. My job was to provide a neater workflow for our phone management chaps to add new DECT basestations and handsets to the system, as the manual process is rather involved (you have to set up provisioning files for the phone as well as set up CUCM using many point-n-click web delivered GUI steps to have suitable users and phone devices configured). They just want one CGI script where they can specify all the required information and it will just go and do it for them.

Part of this automated system is using Cisco’s AXL API to create a new user and phone in CUCM. I’ve used AXL in read only mode quite a bit before to help extract management information from CUCM for the phone folk, but this is the first time I’ve really used it in anger for creating new objects in CUCM. Still, its just a SOAP API, I’ve used Perl’s SOAP::Lite quite a bit before and there’s documentation available from Cisco on all the AXL versions, so this should be easy, right?

Wrong. Well, more specifically, most of it was easy but I came unstuck on one specific item that wasted hours of my time. I found I could use the addUser and addPhone AXL methods to create a new user (with digest credentials) and a new phone OK. What didn’t work was adding the “digestUser” element in the addPhone method. This specifies a user object that will be used by CUCM to supply the SIP security digest credentials to allow secure SIP to be used (which we want as we don’t want random unauthenticated SIP devices rocking up on our phone system!). The addPhone method “worked” in that it created the new phone, but checking in the CUCM web management pages should that the newly minted phone had a digest user of “None”. In other words AXL had quietly ignored the digestUser information without any errors or warnings. Grr!

At first I thought I was possibly passing the wrong data into AXL in the digestUser field. I’d assumed that it was the user’s userid, but maybe it should be the GUID (pkid, UUID, call it what you will)? I tried various guesses without success. Cisco’s AXL documentation isn’t terribly helpful either as it just tells us that digestUser is a 255 character string that only applies to SIP phones. Thanks!

Next I tried to find out where this information is stored in the CUCM database using the AXL SQL methods. The bad news was that digestUser isn’t just a foreign key in the device table. The CUCM 8.6.1 Data Dictionary document also didn’t really help too much. Eventually I tracked it down to the enduserdevicemap table entries that have a tkuserassociation value of 3, which according to the typeuserassociation table is “Digest In”. Great! Now how do I set values in this table via non-SQL AXL calls? Ah, you can’t directly as AXL abstracts the underlying database. The only way to tinker with a particular table via AXL is to use the SQL calls, and Cisco discourage those as they reserve the right to completely change the underlying data dictionary between CUCM versions. I bet they’d be even less happy to support CUCM installations that have down insert/update using SQL calls.

So back to trying to work out why the addPhone call was ignoring digestUser. After much hacking I think I’ve found out why: I’d been explicitly specifying AXL version 8.5 in the “uri” method to the Perl SOAP::Lite new constructor call. Cisco documentation on AXL versioning appears to say that the SOAPAction header sent with the SOAP request shouldn’t be in a URL format (which is SOAP::Lite’s default and has worked for all the other AXL calls I’ve made) but should instead look like “CUCM:DB ver=8.5”. I put this in and SOAP request start to fail. WTF?

After a bit of playing, I tried to request an older version of AXL by setting the SOAPAction header to “CUCM:DB ver=7.1”. You also need to use the on_action() method in SOAP::Lite to generate this header, because the delimiter between the “uri” and the SOAP method being called is a space, rather than SOAP::Lite’s default hash (#) and you still need the URI version in the XML namespacing in the SOAP request. Doing this meant that the addPhone now worked again – which was a bit odd as we’ve got CUCM version 8.6.x so the latest AXL version should be 8.5. A quick check in the CUCM web interface then showed even more good news: the digestUser was now filled in correctly! Hoorah!  It turns out that my original guess that digestuser should be the userid of the enduser object containing the digest credentials was correct after all.

For folk who might be doing this in the future, here’s an extract of the SOAP code that works:
my $cm = new SOAP::Lite
encodingStyle => '',
uri => 'http://www.cisco.com/AXL/API/7.1',
trace => 1,
proxy => "https://$cucmip:$axl_port/axl/" ;
$cm->on_action(sub { '"CUCM:DB ver=7.1 ' . $_[1] . '"' });

my $digestUser = 'rsrc.sp.000011116666'; # The userid of the already created enduser object containing the digest credentials.
my $pt = SOAP::Data->name('newPhone' =>
\SOAP::Data->value(
SOAP::Data->name('name' => $name),
SOAP::Data->name('description' => $descr),
SOAP::Data->name('devicePoolName' => $devicePool),
SOAP::Data->name('model' => $model),
SOAP::Data->name('product' => $model),
SOAP::Data->name('protocol' => $protocol),
SOAP::Data->name('subscribeCallingSearchSpaceName' => $subscribeCSSName),
SOAP::Data->name('digestUser' => $digestUser),
SOAP::Data->name('sipProfileName' => $sipProfileName),
SOAP::Data->name('class' => 'Phone'),
SOAP::Data->name('securityProfileName' => 'Third-party SIP Device Advanced - Standard SIP Secure Profile'),
),
);
my $som = $cm->addPhone($pt);
my $refResult;
if ($som->fault) {
warn "Error: ", $som->faultstring, "\n";
return undef;
} else {
$refResult = $som->valueof('//addPhoneResponse/return');
}

So I’ve now progressed to the point that I can start to take this code and glue it together with all the other steps in this process (such as adding lines to this new phone and configuring the physical device with the new SIP digest credentials).  Still, its taken me far longer than I’d expected and I’ve still no idea why the AXL version 8.5 requests ignored the digestUser initially and then failed when I gave them the “correct” format of the SOAPAction header.  It has contributed to my dislike of SOAP though: this would be so much clearer and easier if Cisco used a RESTful interface and had documentation more like Google provide for their calendar and drive APIs.  Live and learn, eh?