Customising a Cisco 79xx IP Phone: directory services

I’m still working on customising the the Cisco 7940 I use with SIP firmware for VoIP calls and one of the items that’s now working well is the directory services functionality.

At the most basic level, the directory_url directive may be set in one of the SIP configuration files (either SIPDefault.cnf or SIPmacaddress.cnf), for example:

directory_url: ”http://webserver/directory.xml”

The contents of the directory.xml file are actually quite simple:

<CiscoIPPhoneDirectory>
  <Title>IP Telephony Directory</Title>
  <Prompt>People reachable via VoIP</Prompt>
  <DirectoryEntry>
    <Name>Bob</Name>
    <Telephone>1234</Telephone>
  </DirectoryEntry>
  <DirectoryEntry>
    <Name>Joe</Name>
    <Telephone>1357</Telephone>
  </DirectoryEntry>
  <DirectoryEntry>
    <Name>Operator</Name>
    <Telephone>0</Telephone>
  </DirectoryEntry>
</CiscoIPPhoneDirectory>

The trouble with this is that it’s just a static file. If I have a large directory, then I need to keep it up-to-date. That’s where a directory service comes into play. The Open 79xx XML Directory looks useful but it’s another application to install and manage on my infrastructure. I already have a directory (Microsoft Active Directory), so I thought it would be great if a piece of code could query the AD and output the file in a format that the 7940 understands.

Luckily I found such a piece of code, courtesy of a message posted to the Asterisk Users forum back in 2004 by Jeff Gustafson:

<?php
$ds=ldap_connect("ldapserver");  // must be a valid LDAP server!

if ($ds) {
  $r=ldap_bind($ds);  // this is an "anonymous" bind, typically read-only access

  $sr=ldap_search($ds, "ou=People,dc=domainname,dc=com",
"telephoneNumber=*");
  echo "<CiscoIPPhoneDirectory>\n";
  echo "<Title>IP Telephony Directory</Title>\n";
  echo "<Prompt>People reachable via VoIP</Prompt>\n";

  $info = ldap_get_entries($ds, $sr);

  for ($i=0; $i<$info["count"]; $i++) {
    echo "<DirectoryEntry>\n";
    echo "<Name>" . $info[$i]["cn"][0] . "</Name>\n";
    echo "<Telephone>" . $info[$i]["telephonenumber"][0] .
"</Telephone>\n";
    echo "</DirectoryEntry>\n";
  }

  echo "</CiscoIPPhoneDirectory>";
  ldap_close($ds);

} else {
  echo "error";
}
?>

Jeff’s code is great (my PHP skills are certainly not good enough to have written this myself) but Active Directory has an attribute for IP phone numbers (ipPhone), so I made a couple of edits to change the phone prompts and to make the LDAP query search on the ipPhone attribute:

<?php
$ds=ldap_connect("domaincontroller.domainname.tld");  // must be a valid LDAP server!

if ($ds) {
  $r=ldap_bind($ds); // this is an "anonymous" bind, typically read-only access

  $sr=ldap_search($ds, "ou=directorycontainer,dc=domainname,dc=tld",
"ipphone=*");
  echo "<CiscoIPPhoneDirectory>\n";
  echo "<Title>IP Telephony Directory</Title>\n";
  echo "<Prompt>Active Directory Users</Prompt>\n";

  $info = ldap_get_entries($ds, $sr);

  for ($i=0; $i<$info["count"]; $i++) {
    echo "<DirectoryEntry>\n";
    echo "<Name>" . $info[$i]["displayname"][0] . "</Name>\n";
    echo "<Telephone>" . $info[$i]["ipphone"][0] .
"</Telephone>\n";
    echo "</DirectoryEntry>\n";
  }

  echo "</CiscoIPPhoneDirectory>";
  ldap_close($ds);

} else {
  echo "error";
}
?>

I still needed a couple of tweaks to get this working though – not to the script, just to: the webserver I used to serve it; to Active Directory; and finally to the phone configuration.

First up, you need a web server with PHP installed (I used PHP 5.2.6 on IIS 6.0). This also needs the LDAP extension to be enabled by uncommenting extension=php_ldap.dll in php.ini. The extensions folder (e.g. C:\phpinstallationfolder\extensionfolder) also needs to be appended to the %path% system variable.

The script is actually for a generic LDAP directory (nothing wrong with that) but recent versions of Active Directory do not allow anonymous access by default. Daniel Petri has a detailed article on anonymous LDAP operations in Windows 2003 AD and that gave me the information that I needed to open up the parts of the directory that I wanted the script to read – basically: setting the 7th bit of the dsHeuristics flag on CN=Directory Service,CN=Windows NT,CN=Services,DC=domainname,DC=tld to 2 on the forest root domain; waiting for replication to complete; granting ANONYMOUS LOGON read access on the appropriate objects and List Contents access on the OU that contains the object(s). Alternatively, it should be possible to edit the script to use an authenticated logon (and sorting by surname wouldn’t go amiss either) but it’s getting late now and that will have to wait for another day! In the meantime, Geoff Jacobs’ post on creating a personal directory for the Linksys SPA942 using LDAP should provide some inspiration.

Last, but by no means least, the directory_url directive needs to be edited to reflect the name of the PHP script instead of the original static XML, for example:

directory_url: ”http://webserver/directory.php”

In order to pick up the changes, the phone will need a reset.

Now, when I access the external directory from the phone using the directory button and option 5, I’m presented with a list of contacts from Active Directory. Furthermore, because the web server uses dynamic content, the details are as current as the directory server that it refers to.

Installing PHP 5 on IIS 6

I’ve run PHP with Microsoft Internet Information Services (IIS) before (running on a Windows XP laptop) and I seem to remember the installation being quite straightforward. Even so, tonight I was installing PHP 5.2.6 with IIS 6 (on Windows Server 2003 R2 Enterprise x64 Edition) and I ran across a few issues. This post describes what was involved:

  • Firstly, PHP can be installed in CGI, FastCGI or ISAPI mode. I used ISAPI.
  • Secondly, there is anecdotal evidence that the Windows Installer version is problematic – for that reason you may prefer to use the ZIP file and perform a manual installation (as I did), following the instructions on the IIS Admin blog, which were:
    • Extract the files to a location of your choice (I used C:\PHP to keep it simple but C:\Program Files (x86)\PHP would be better).
    • Rename php.ini-recommended to php.ini.
    • Edit the extension_dir line in php.ini to read extension_dir = C:\phpinstallationfolder\ext.
    • Add the PHP installation folder to the %path% system variable (e.g. append ;C:\PHP to the existing path).
    • Create a web service extension for PHP using cscript iisext.vbs /AddFile c:\phpinstallationfolder\php5isapi.dll 1 PHPISAPI 1 “PHP ISAPI”. The new extension should show in IIS Manager with a status of Allowed.
    • Create an application extension mapping for .php files. Following the advice on the IIS Admin blog article that I referenced previously will remove all other mappings so I used the IIS Manager MMC instead (Default Web Site Properties, Home Directory, Configuration to add a mapping to the executable at c:\phpinstallationfolder\php5isapi.dll using extension .php for all verbs).
    • Create a test file called phpinfo.php containing <?php phpinfo(); ?>.
    • Use a web browser to navigate to http://servername/phpinfo.php and the PHP information page should be displayed.
    • If you are running on 64-bit Windows there are some extra steps in order to avoid an HTTP 500 Internal server error or the message %1 is not a valid Win32 application. It seems that this is caused by trying to load a 32-bit application (in this case PHP) inside a 64-bit worker process (as described in Microsoft knowledge base article 895976). To resolve this issue, enter cscript adsutil.vbs SET W3SVC/AppPools/Enable32bitAppOnWin64 1. adsutil.vbs is one of the scripts installed into the wwwroot\AdminScripts folder but if you have removed it to secure the server (as I had), then it may be temporarily copied back to the server from another IIS installation.
    • To ensure that PHPinfo reflects the correct location of the php.ini file, create an environment variable called PHPRC referring to c:\phpinstallationfolder and restart the server or, alternatively, set the appropriate registry keys (although neither option seemed to have any effect for me).