~bosco/dungeonman

ffb893a4062d490d38885a970069102249241429 — Ryan Tolboom 4 years ago 576a5bd
Added godaddy-ddns
3 files changed, 163 insertions(+), 0 deletions(-)

M .gitignore
A home/ryan/godaddy-ddns/godaddy-ddns.config
A home/ryan/godaddy-ddns/godaddy-ddns.py
M .gitignore => .gitignore +1 -0
@@ 9,6 9,7 @@
!home/ryan/.ssh/authorized_keys
!home/ryan/preseed/*
!home/ryan/netconfig/*
!home/ryan/godaddy-ddns/*

# root directory
!root/.ssh/authorized_keys

A home/ryan/godaddy-ddns/godaddy-ddns.config => home/ryan/godaddy-ddns/godaddy-ddns.config +0 -0
A home/ryan/godaddy-ddns/godaddy-ddns.py => home/ryan/godaddy-ddns/godaddy-ddns.py +162 -0
@@ 0,0 1,162 @@
#!/usr/bin/env python3
#
# Update GoDaddy DNS "A" Record.
#
# usage: godaddy_ddns.py [-h] [--version] [--ip IP] [--key KEY]
#                        [--secret SECRET] [--ttl TTL] [--force]
#                        hostname
#
# positional arguments:
#   hostname         DNS fully-qualified host name with an 'A' record.  If the hostname consists of only a domain name
#                    (i.e., it contains only one period), the record for '@' is updated.
#
# optional arguments:
#   -h, --help       show this help message and exit
#   --version        show program's version number and exit
#   --ip IP          DNS Address (defaults to public WAN address from http://ipv4.icanhazip.com/)
#   --key KEY        GoDaddy production key
#   --secret SECRET  GoDaddy production secret
#   --ttl TTL        DNS TTL.
#   --force          force update of GoDaddy DNS record even if DNS query indicates that record is already correct
#
# GoDaddy customers can obtain values for the KEY and SECRET arguments by creating a production key at
# https://developer.godaddy.com/keys/.
#
# Note that command line arguments may be specified in a FILE, one to a line, by instead giving
# the argument "%FILE".  For security reasons, it is particularly recommended to supply the
# KEY and SECRET arguments in such a file, rather than directly on the command line:
#
# Create a file named, e.g., `godaddy-ddns.config` with the content:
#   MY.FULLY.QUALIFIED.HOSTNAME.COM
#   --key
#   MY-KEY-FROM-GODADDY
#   --secret
#   MY-SECRET-FROM-GODADDY
#
# Then just invoke `godaddy-ddns %godaddy-ddns.config`

prog='godaddy-ddns'
version='0.4'
author='Carl Edman (CarlEdman@gmail.com)'

import sys, json, argparse, socket

if sys.version_info > (3,):
  from urllib.request import urlopen, Request
  from urllib.error import URLError, HTTPError
else:
  from urllib2 import urlopen, Request
  from urllib2 import URLError, HTTPError

parser = argparse.ArgumentParser(description='Update GoDaddy DNS "A" Record.', fromfile_prefix_chars='%', epilog= \
'''GoDaddy customers can obtain values for the KEY and SECRET arguments by creating a production key at
https://developer.godaddy.com/keys/.

Note that command line arguments may be specified in a FILE, one to a line, by instead giving
the argument "%FILE".  For security reasons, it is particularly recommended to supply the
KEY and SECRET arguments in such a file, rather than directly on the command line.''')

parser.add_argument('--version', action='version',
  version='{} {}'.format(prog, version))

parser.add_argument('hostname', type=str,
  help='DNS fully-qualified host name with an A record.  If the hostname consists of only a domain name (i.e., it contains only one period), the record for @ is updated.')

parser.add_argument('--ip', type=str, default=None,
  help='IPv4 address to write to DNS record (defaults to public WAN address from http://ipv4.icanhazip.com/)')

parser.add_argument('--key', type=str, default='',
  help='GoDaddy production key')

parser.add_argument('--secret', type=str, default='',
  help='GoDaddy production secret')

parser.add_argument('--ttl', type=int, default=3600,
  help='DNS TTL.')

parser.add_argument('--force', type=bool, default=False,
  help='force update of GoDaddy DNS record even if DNS query indicates that record is already correct.')

args = parser.parse_args()

def main():
  hostnames = args.hostname.split('.')
  if len(hostnames)<2:
    msg = 'Hostname "{}" is not a fully-qualified host name of form "HOST.DOMAIN.TOP".'.format(args.hostname)
    raise Exception(msg)
  elif len(hostnames)<3:
    hostnames.insert(0,'@')

  if not args.ip:
    try:
      with urlopen("https://ipv4.icanhazip.com/") as f: resp=f.read()
      if sys.version_info > (3,): resp = resp.decode('utf-8')
      args.ip = resp.strip()
    except URLError:
      msg = 'Unable to automatically obtain IP address from http://ipv4.icanhazip.com/.'
      raise Exception(msg)
  
  ipslist = args.ip.split(",")
  for ipsiter in ipslist:
    ips = ipsiter.split('.')
    if len(ips)!=4 or \
      not ips[0].isdigit() or not ips[1].isdigit() or not ips[2].isdigit() or not ips[3].isdigit() or \
      int(ips[0])>255 or int(ips[1])>255 or int(ips[2])>255 or int(ips[3])>255:
      msg = '"{}" is not valid IP address.'.format(ips)
      raise Exception(msg)

  if not args.force and len(ipslist)==1:
    try:
      dnsaddr = socket.gethostbyname(args.hostname)
      if ipslist[0] == dnsaddr:
        msg = '{} already has IP address {}.'.format(args.hostname, dnsaddr)
        raise Exception(msg)
    except:
      pass
             
  url = 'https://api.godaddy.com/v1/domains/{}/records/A/{}'.format('.'.join(hostnames[1:]),hostnames[0])
  data = json.dumps([ { "data": ip, "ttl": args.ttl, "name": hostnames[0], "type": "A" } for ip in  ipslist])
  if sys.version_info > (3,):  data = data.encode('utf-8')
  req = Request(url, method='PUT', data=data)

  req.add_header("Content-Type","application/json")
  req.add_header("Accept","application/json")
  if args.key and args.secret:
    req.add_header("Authorization", "sso-key {}:{}".format(args.key,args.secret))

  try:
    with urlopen(req) as f: resp = f.read()
    if sys.version_info > (3,):  resp = resp.decode('utf-8')
    # resp = json.loads(resp)
  except HTTPError as e:
    if e.code==400:
      msg = 'Unable to set IP address: GoDaddy API URL ({}) was malformed.'.format(req.full_url)
    elif e.code==401:
      if args.key and args.secret:
        msg = '''Unable to set IP address: --key or --secret option incorrect.
Correct values can be obtained from from https://developer.godaddy.com/keys/ and are ideally placed in a % file.'''
      else:
        msg = '''Unable to set IP address: --key or --secret option missing.
Correct values can be obtained from from https://developer.godaddy.com/keys/ and are ideally placed in a % file.'''
    elif e.code==403:
        msg = '''Unable to set IP address: customer identified by --key and --secret options denied permission.
Correct values can be obtained from from https://developer.godaddy.com/keys/ and are ideally placed in a % file.'''
    elif e.code==404:
        msg = 'Unable to set IP address: {} not found at GoDaddy.'.format(args.hostname)
    elif e.code==422:
        msg = 'Unable to set IP address: "{}" has invalid domain or lacks A record.'.format(args.hostname)
    elif e.code==429:
        msg = 'Unable to set IP address: too many requests to GoDaddy within brief period.'
    elif e.code==503:
        msg = 'Unable to set IP address: "{}" is unavailable.'.format(args.hostname)
    else:
      msg = 'Unable to set IP address: GoDaddy API failure because "{}".'.format(e.reason)
    raise Exception(msg)
  except URLError as e:
    msg = 'Unable to set IP address: GoDaddy API failure because "{}".'.format(e.reason)
    raise Exception(msg)

  print('IP address for {} set to {}.'.format(args.hostname,args.ip))

if __name__ == '__main__':
  main()