#!/usr/bin/python
#
# Easy Python Dynamic DNS
# By Jed Smith <jed@jedsmith.org> 4/29/2009
# Bugfixes and modified for Python2 by Andrew Beekhof <andrew@beekhof.net> 2011
#
# This code and associated documentation is released into the public domain.
#
# To use:
#
#   0. You'll probably have to edit the shebang above.
#
#   1. In the Linode DNS manager, edit your zone (must be master) and create
#      an A record for your home computer.  You can name it whatever you like;
#      I call mine 'home'.  Fill in 0.0.0.0 for the IP.
#
#   2. Save it.
#
#   3. Edit the five configuration options below, following the directions for
#      each.  As this is a quick hack, it assumes everything goes right.
#
#
# If you define an interval (in seconds) here , the script will
# repeatedly check the DNS entry has the correct value and not
# terminate
#
INTERVAL = 0

#
# Domain to which the host belongs
#
DOMAIN   = "example.com"

#
# The host name
#
HOST     = "mymachine"

#
# Your Linode API key.  You can generate this by going to your profile in the
# Linode manager.  It should be fairly long.
#
KEY = "abcdefghijklmnopqrstuvwxyz"

#
# The URI of a Web service that returns your IP address as plaintext.  
#
# To run your own, ensure PHP is enabled for your webserver and place
# the following source code into ip.php somewhere beneath the docroot.
#
#     <?php
#     header("Content-type: text/plain");
#     printf("%s", $_SERVER["REMOTE_ADDR"]);
#     ?>
#
GETIP = "http://example.com/ip.php"

#
# If for some reason the API URI changes, or you wish to send requests to a
# different URI for debugging reasons, edit this.  {0} will be replaced with the
# API key set above, and & will be added automatically for parameters.
#
API = "https://api.linode.com/api/?api_key={0}&resultFormat=JSON"
#
# Comment or remove this line to indicate that you edited the options above.
#
exit("Did you edit the options?  vi this file open.")
#
# That's it!
#
# Now run dyndns.py manually, or add it to cron, or whatever.  You can even have
# multiple copies of the script doing different zones.
#
# For automated processing, this script will always print EXACTLY one line, and
# will also communicate via a return code.  The return codes are:
#
#    0 - No need to update, A record matches my public IP
#    1 - Updated record
#    2 - Some kind of error or exception occurred
#
# The script will also output one line that starts with either OK or FAIL.  If
# an update was necessary, OK will have extra information after it.
#
# If you want to see responses for troubleshooting, set this:
#
DEBUG = False


#####################
# STOP EDITING HERE #

try:
	from json import load
	from urllib import urlencode
	from urllib import urlretrieve
	import time, sys
except Exception as excp:
	exit("Couldn't import the standard library. Are you running Python 3?")

def execute(action, parameters):
	# Execute a query and return a Python dictionary.
	uri = "{0}&action={1}".format(API.format(KEY), action)
	if parameters and len(parameters) > 0:
		uri = "{0}&{1}".format(uri, urlencode(parameters))
	if DEBUG:
		print("-->", uri)
	file, headers = urlretrieve(uri)
	if DEBUG:
		print("<--", file)
		print headers,
		print(open(file).read())
		print()
	json = load(open(file), encoding="utf-8")
	if len(json["ERRORARRAY"]) > 0:
		err = json["ERRORARRAY"][0]
		raise Exception("Error {0}: {1}".format(int(err["ERRORCODE"]),
							err["ERRORMESSAGE"]))
	return load(open(file), encoding="utf-8")

def ip():
	if DEBUG:
		print("-->", GETIP)
	file, headers = urlretrieve(GETIP)
	if DEBUG:
		print("<--", file)
		print headers,
		print(open(file).read())
		print()
	return open(file).read().strip()

def domainid_lookup(name):
	res = execute("domainList", {})["DATA"]
	for r in res:
		if r["DOMAIN"] == DOMAIN:
			return int(r['DOMAINID'])

	raise Exception("No such domain?".format(DOMAIN))

def check(host, domain):
	domainID = domainid_lookup(domain)
	public = ip()

	res = execute("domainResourceList", {"DomainID": domainID})["DATA"]
	for r in res:
		if r["NAME"] == host:
			resourceID = r['RESOURCEID']
			if DEBUG:
				print "Got resourceID: %d" % resourceID

			if r["TARGET"] != public:
				old = r["TARGET"]
				request = {
					"ResourceID": r["RESOURCEID"],
					"DomainID": r["DOMAINID"],
					"Name": r["NAME"],
					"Type": r["TYPE"],
					"Target": public,
					"TTL_Sec": r["TTL_SEC"]
					}
				execute("domainResourceSave", request)
				print("OK {0} -> {1}".format(old, public))
				return 1
			else:
				print("OK")
				return 0

	raise Exception("No such host? ".format(host))

def main():
	try:
		if INTERVAL > 0:
			while True:
				check(HOST, DOMAIN)
				time.sleep(INTERVAL)
		else:
			return check(HOST, DOMAIN)

	except Exception as excp:
		print("FAIL {0}: {1}".format(type(excp).__name__, excp))
		return 2

if __name__ == "__main__":
	exit(main())
