summaryrefslogtreecommitdiff
path: root/he-dyndns
blob: 26a97b676ee96b06ffdec016a719c77c279f9fa4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
#!/usr/bin/env python3

import argparse
import base64
import dns.resolver
import ipaddress
import logging
import logging.handlers
import requests
import socket
import sys
import urllib


HE_DNS_API_HOST = "dyn.dns.he.net"

def build_ip(ip_string):
	try:
		ip = ipaddress.ip_address(ip_string)
	except ValueError:
		log.error("manually specified address is invalid format: %s" % ip_string)
		sys.exit(1)
	except:
		log.error(str(e))
		sys.exit(1)

	return ip

def haveIPv6():
	log.debug("inside haveIPv6()")
	haveIPv6 = True
	try:
		s = socket.socket(socket.AF_INET6)
		s.connect((HE_DNS_API_HOST,80))
	except socket.gaierror as e:
		log.debug("Exception {}".format(e.__class__))
		log.error("Testing IPv6 connection: " + str(e))
		sys.exit(1)
	except OSError as e:
		log.debug("Exception {}".format(e.__class__))
		log.debug(str(e))
		haveIPv6 = False
	except TimeoutError as e:
		log.error("Testing IPv6 connection: " + str(e))
		sys.exit(1)
	except Exception as e:
		log.debug("Exception {}".format(e.__class__))
		log.error(str(e))
		haveIPv6 = False
	return haveIPv6

def main():
	ap = argparse.ArgumentParser(description="Update Hurricane Electric DNS dynamic record")
	ap.add_argument("record", help="DNS record to update")
	ap.add_argument("--v4", help="Update IPv4 A record (default)", action="store_true", default=True)
	ap.add_argument("--v6", help="Update IPv6 AAAA record", action="store_true", default=False)
	ap.add_argument("--addr4", help="Update A record with provided IP rather than detected IP")
	ap.add_argument("--addr6", help="Update AAAA record with provided IP rather than detected IP")
	ap.add_argument("--key", help="HE DDNS key for record (by default read from /etc/he-dns-secret)")
	ap.add_argument("--debug", help="Enable debug logging", action="store_true", default=False)
	args = ap.parse_args()

	if args.debug:
		log.setLevel(logging.DEBUG)
	else:
		log.setLevel(logging.ERROR)

	# Handle manual addressing
	if args.addr4:
		ip = build_ip(args.addr4)	

	if args.addr6:
		ip = build_ip(args.addr6)

	if args.v6:
		if not haveIPv6  and not args.addr6:
			log.error("no IPv6 detected and --v6 specified without --addr6 (try --debug?)")
			sys.exit(1)			
	try:
		
		if args.v6:
			ans = dns.resolver.resolve(HE_DNS_API_HOST, "AAAA")[0]
			log.debug("DNS resolved: {}".format(ans.to_text()))
			apihost = "[{}]".format(ans.to_text())
		else:
			ans = dns.resolver.resolve(HE_DNS_API_HOST, "A")[0]
			log.debug("DNS resolved: {}".format(ans.to_text()))
			apihost = ans.to_text()

		# build the API URL to be called
		url = "http://{}/nic/update?hostname={}".format(apihost, args.record)
		if args.addr4 or args.addr6:
			url += "&myip={}".format(ip)
		log.debug("API request URL: " + url)

		# make the HTTP request
		authstr = base64.b64encode(bytes("%s:%s" % ( args.record, "yEuFfO74TskIX2NG0Yod" ), "ascii"))
		log.debug("auth string: %s" % authstr.decode("utf-8"))
		req = urllib.request.Request(url)
		req.add_header("Host", HE_DNS_API_HOST)
		req.add_header("Authorization", "Basic %s" % authstr.decode("utf-8"))
		with urllib.request.urlopen(req) as response:
			this_page = response.read()
		log.debug("HE response: " + this_page.decode("utf-8"))
		res = this_page.decode("utf-8").split()[0]

		if res == "good":
			return True
		elif res == "badauth":
			raise Exception("HE badauth: wrong key or record not configured for dynamic updates")
		elif res == "nochg":
			return True
		else:
			raise Exception("HE returned {}: unknown response code".format(res))

	except dns.resolver.NXDOMAIN as e:
		log.debug("Exception class: {}".format(e.__class__))
		log.error(str(e))
		sys.exit(1)

	except Exception as e:
		log.debug("Exception class: {}".format(e.__class__))
		log.error(str(e))    
		sys.exit(1)

if __name__ == "__main__":
	log = logging.getLogger("he-dyndns")
	lh = logging.StreamHandler()
	lf = logging.Formatter(fmt='%(name)s: %(levelname)s: %(message)s')
	lh.setFormatter(lf)
	log.addHandler(lh)
	main()
	sys.exit(0)