Code Samples

Updating DNS entries using the cPanel API

A client in Asia used an internet service provider that resets router connections every 24 hours and reassigns dynamic IP addresses. A dedicated IP address wasn't justified for this small office, yet traveling web designers did need to reference resources on a server hosted in the office.

The office has a web hosting plan that included DNS management through cPanel. Pulling together some sample code from around the web and with a fair amount of trial-and-error, we developed a script to query the office external IP address every five minutes. If that address changed, we logged into the cPanel API to update the corresponding DNS entry.

Place the following PHP file on your public internet server at any convenient location (we suggest /adm/reflect.php). If you use a different location, be sure to update reflectLink in the script below. The script simply returns as text the requesting Internet address for every HTTP GET request.

<?php 
header('Content-Type: text/text');
echo $_SERVER['REMOTE_ADDR'] ?>

On the server within the office, schedule the following script every 5 minutes. Adjust global values as appropriate.

#!/usr/bin/env python
#
# inspired by neobitti_update_ip.pl by Stefan Gofferje
#             http://stefan.gofferje.net/it-stuff/scripts/50-dynamic-dns-update-via-cpanel-api
#
# Copyright 2016 Frontier Tech Team LLC - http://www.frontiertechteam.com/
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 2 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.


import base64
import datetime
import json
import os
import requests
import sys


# globals
# Update values in ALL-CAPS to your configuration
statusFile    = '/var/tmp/dnsLastIP'
cpanelURI     = 'https://CPANEL.MYISP.COM'
reflectLink   = 'http://WWW.MYWEBSITE.COM/adm/reflect.php'
targetDomain  = 'MYWEBSITE.COM'
targetRecord  = 'OFFICE.MYWEBSITE.COM.'
cpanelUser    = 'MYCPANELUSERNAME'

# We're not big fans of hardcoded, plaintext passwords in scripts.
# For this example we achieve at least some obfuscation (albeit no encryption)
# by base16 encoding our password. In the Python console run:
#     import base64
#     base64.b16encode( 'MYCPANELPASSWORD')
cpanelPwd     = base64.b16decode( '4D594350414E454C50415353574F5244' )


# utility functions
def errorExit( msg=None ):
    if msg:
        print msg
    sys.exit(1)


################################################################################
## main script
################################################################################


print datetime.datetime.utcnow().strftime( 'starting cpanelDnsUpdate on %c' )


# read in previous address
try:
    with open( statusFile ) as fp:
        lastIP = fp.read()
except Exception, e:
    print 'unable to read status file: '+str(e)
    lastIP = None


# determine current IP address
try:
    response = requests.get( reflectLink )
    currentIP = response.text
except Exception, e:
    errorExit( 'failed to get current IP with exception '+str(e) )
if response.status_code != 200:
     errorExit( 'failed to get current IP with code '+str(response.status_code ) )
print 'current IP is '+currentIP


# see if an update is required
if currentIP == lastIP:
    print 'no update required'
    print datetime.datetime.utcnow().strftime( 'completed cpanelDnsUpdate on %c' )
    sys.exit(0)


# log in
try:
    response = requests.get( cpanelURI+'/login', params = { 'login' : '1',
                                                         'user'  : cpanelUser,
                                                         'pass'  : cpanelPwd } )
except Exception, e:
    errorExit( 'failed to log in with exception '+str(e) )
if response.status_code != 200:
     errorExit( 'failed to log in with code '+str(response.status_code ) )
print 'logged in'
loginCookies = response.history[0].cookies


# update cpanelURI to include session subdirectory
if '/frontend' not in response.url:
    errorExit( 'missing "/frontend" in login redirect '+response.url )
cpanelURI = response.url.split( '/frontend' )[0]
print 'session URI '+cpanelURI


# download relevant zone file to get line number
try:
    response = requests.get( cpanelURI + '/json-api/cpanel',
                             params = { 'cpanel_jsonapi_module' : 'ZoneEdit',
                                        'cpanel_jsonapi_func'   : 'fetchzone',
                                        'domain'                : targetDomain, },
                             cookies = loginCookies,
                         )
except Exception, e:
    errorExit( 'failed to fetch zones with exception '+str(e) )
if response.status_code != 200:
     errorExit( 'failed to fetch zones with code '+str(response.status_code ) )


# find relevant line
try:
    for r in response.json()['cpanelresult']['data'][0]['record']:
        if 'name' in r and r['name'] == targetRecord:
            break
    else:
        raise( Exception('not found') )
except Exception, e:
    errorExit( "can't locate %s in zone: %s" % (targetRecord, str(e)) )
print 'found record: '+repr(r)


# update IP record
try:
    response = requests.get( cpanelURI + '/json-api/cpanel',
                             params = { 'cpanel_jsonapi_module' : 'ZoneEdit',
                                        'cpanel_jsonapi_func'   : 'edit_zone_record',
                                        'domain'                : targetDomain,
                                        'line'                  : r['line'],
                                        'address'               : currentIP },
                             cookies = loginCookies,
                         )
except Exception, e:
    errorExit( 'failed to update IP with exception '+str(e) )
if response.status_code != 200:
     errorExit( 'failed to update IP with code '+str(response.status_code ) )
print 'IP address updated'


# write out new address
try:
    with open( statusFile, 'w' ) as fp:
        fp.write( currentIP )
except Exception, e:
    print 'unable to write status file: '+str(e)

print datetime.datetime.utcnow().strftime( 'completed cpanelDnsUpdate on %c' )