Working with the Cisco Handset Api In Python3 – Remote ITL Delete / Security Setting Reset

By 1st March 2018Cisco, CiscoIpPhone API, CUCM

Cisco CUCM servers and devices use CTL and ITL files to sign and encrypt the sensitive configuration files that are transferred from server to device. Once a device has received and verified the authenticity of these files it will no longer recognise configuration files which are not signed with the same keys.
As each CUCM cluster has unique CTL and ITL values a phone that has successfully registered to one cluster will not operate with full configuration options if moved to another cluster until the CTL & ITL files are deleted or the cluster wide “Prepare Cluster for Rollback to pre-8.0” parameter is enabled. Situations can occur with upgrades or migrations whereby phones become stuck on an old cluster or indeed register to a new cluster but retain many of the old configuration values or exhibit undesirable behaviour.
In these scenarios the only solution is to delete the CTL/ITL files however this is a manual process for which you need to visit the device, when dealing with medium to large clusters you have a considerable problem on your hands.
A client contacted me after a third party carried out a cluster migration which unfortunately left them with around three thousand problematic devices and were facing the prospect of visiting each device to fix them. I wrote the following script to resolve the issue by deleting the files via the PhoneApi, using the model key press sequence as if you are in front of the device. The cluster was configured with 8800 and legacy 7900 handsets, this method is only possible if you have valid credentials for a user with ownership of the affected devices, commonly an existing CTI or 3rd party application user will suffice.

Full Code
 
  1. __author__ = "Mitch Dawson"
  2. __contact__ = "info@ucdevops.com"
  3. __copyright__ = "Copyright (C) 2017 - 2021 Ucdevops All Rights Reserved"
  4. __license__ = "GPLv3"
  5. """
  6. This program is free software: you can redistribute it and/or modify
  7. it under the terms of the GNU General Public License as published by
  8. the Free Software Foundation, either version 3 of the License, or
  9. (at your option) any later version.
  10. This program is distributed in the hope that it will be useful,
  11. but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13. GNU General Public License for more details.
  14. You should have received a copy of the GNU General Public License
  15. along with this program. If not, see http://www.gnu.org/licenses.
  16. """
  17. from xmltodict import unparse
  18. from collections import OrderedDict
  19. import requests
  20. import time
  21. from ipaddress import IPv4Network
  22. import re
  23. from multiprocessing import Pool
  24. # Phone Message XML Headers.
  25. headers = {"content-type": "application/xml"}
  26. # CUCM Phone Owner / Application User Credentials.
  27. u = "username"
  28. p = "password"
  29. # Sip Handset configuration url, used for extracting ITL signature.
  30. conf_url = "/CGI/Java/Serviceability?adapter=device.statistics.configuration"
  31. # Define the correct ITL Signature value to match against
  32. # used for phones that present the ITL in their html.
  33. itl_sig = "<B>EE 5C 62 E0 5E 2B C6 82 2A 45 64 1E EB BA BF F2 47 6E 1D 93 </B>"
  34. # Define the subnets for the phones that you wish to target.
  35. networks = ["10.10.0.0/16"]
  36. # Define supported Sip models.
  37. sip_models = ("CP-8861", "CP-8841")
  38. # Define supported SCCP models.
  39. sccp_models = ("CP-7965G")
  40. # Model, key press ITL Delete Combination and Delay(model specific).
  41. models = {
  42.     "CP-8861": {
  43.         "keys": [
  44.             "Key:Applications", "Key:KeyPad5", "Key:KeyPad4",
  45.             "Key:KeyPad5", "Key:Soft3", "Key:Soft1"
  46.         ],
  47.         "delay": 1},
  48.     "CP-7965G": {
  49.         "keys": [
  50.             "Init:Settings", "Key:Settings", "Key:KeyPad4", "Key:KeyPad5",
  51.             "Key:KeyPad2", "Key:KeyPadStar", "Key:KeyPadStar",
  52.             "Key:KeyPadPound", "Key:Soft2", "Key:Soft4", "Key:Soft2"
  53.         ],
  54.         "delay": 2},
  55.     "CP-8841": {
  56.         "keys": [
  57.             "Key:Applications", "Key:KeyPad5", "Key:KeyPad4",
  58.             "Key:KeyPad5", "Key:Soft3", "Key:Soft1"
  59.         ],
  60.         "delay": 1}
  61. }
  62. def get_phone_base_html(address):
  63.     # Return phone base html.
  64.     return requests.get("http://" + address).text
  65. def get_conf_html(address):
  66.     # Return phone configuration html.
  67.     return requests.get(
  68.         "http://" + address + conf_url
  69.     ).text
  70. def get_itl_sig(address):
  71.     # Return regex match result of our itl signature
  72.     # For phones that support it.
  73.     return re.search(
  74.         itl_sig, get_conf_html(address)
  75.     )
  76. def get_phone_model(html, regex_list):
  77.     # Classify the phone model by
  78.     # running a regex test on the returned HTML.
  79.     for regex in regex_list:
  80.         search = re.search(regex, html)
  81.         if search:
  82.             return search.group()
  83.         else:
  84.             pass
  85.     print("Could Not Classify...")
  86. # Define our function to process the deletion
  87. # of the itl files per phone model.
  88. def process_phone(address, phone_model):
  89.     # Carries out the required steps for each model of phone.
  90.     if phone_model in sccp_models:
  91.         send_key_press(
  92.             address,
  93.             build_xml(phone_model),
  94.             models[phone_model]["delay"]
  95.         )
  96.     # Sip Phones - Itl signature can be checked.
  97.     elif phone_model in sip_models:
  98.         # Check for itl signature
  99.         if not get_itl_sig(address):
  100.             send_key_press(
  101.                 address,
  102.                 build_xml(phone_model),
  103.                 models[phone_model]["delay"]
  104.             )
  105.         else:
  106.             print("Address '{}' has the correct ITL".format(str(address)))
  107.      else:
  108.          print("Phone model not found")
  109. # Define our function to iterate through our key press list
  110. # and send the key press to the target device.
  111. def send_key_press(address, key_press_list, delay):
  112.     for key_press in key_press_list:
  113.         r = requests.post(
  114.             url="http://" + address + "/CGI/Execute",
  115.             data=key_press, headers=headers, auth=(u, p)
  116.         )
  117.         print(r.text)
  118.         time.sleep(delay)
  119. # Define our function to build our xml key press messages.
  120. def build_xml(phone_model):
  121.     messages = []
  122.     for kp in models[phone_model]["keys"]:
  123.         d = OrderedDict(
  124.             [("CiscoIPPhoneExecute", OrderedDict(
  125.                 [("ExecuteItem", OrderedDict([("@Priority", "0"), ("@URL", kp)])
  126.                   )])
  127.               )])
  128.         messages.append({"XML": unparse(d)})
  129.     return messages
  130. # Define our main function.
  131. def main(address):
  132.     # Create list of phone model Keys
  133.     model_keys = models.keys()
  134.     print("IP = '{}'".format(address))
  135.     try:
  136.         phone_model = get_phone_model(
  137.             get_phone_base_html(address),
  138.             model_keys
  139.         )
  140.     except Exception as e:
  141.         print("Exception : '{}'".format(str(e)))
  142.     else:
  143.         print("Phone model {}".format(phone_model))
  144.         process_phone(address, phone_model)
  145. if __name__ == '__main__':
  146.     # Build a list of ip addresses for the given subnets.
  147.     ip_addresses = [
  148.         str(ip)for net in networks for ip in IPv4Network(net).hosts()
  149.     ]
  150.     # Create a Process Pool so that we can allocate multiple processes and divide up the work.
  151.     pool = Pool(processes=4)
  152.     pool.map(main, ip_addresses)
Mitch

Author Mitch

I am an independent IT Consultant with specialities in Software Development, Enterprise Unified Communication, Network and Security platforms. In addition to my day to day work, I develop bespoke applications and I hope that through ucdevops.com I can build relationships with clients, business partners and fellow engineers by providing solutions to complex problems through the use of programming.

More posts by Mitch
"