summaryrefslogtreecommitdiff
path: root/he-dyndns
diff options
context:
space:
mode:
authorJason D. McCormick <jason@mfamily.org>2022-12-11 13:20:54 -0500
committerJason D. McCormick <jason@mfamily.org>2022-12-11 13:20:54 -0500
commit97459518fad82b19059d7e5cd796ead961c38db6 (patch)
tree0708732142bfc279005065d65a9974bc95747d6f /he-dyndns
parentcb0e1dd0e84ab16110d1f377ba27872f771fb9af (diff)
go back to the original orgHEADmaster
Diffstat (limited to 'he-dyndns')
-rwxr-xr-xhe-dyndns162
1 files changed, 162 insertions, 0 deletions
diff --git a/he-dyndns b/he-dyndns
new file mode 100755
index 0000000..bec3b60
--- /dev/null
+++ b/he-dyndns
@@ -0,0 +1,162 @@
+#!/usr/bin/env python3
+
+import argparse
+import base64
+import configparser
+import dns.resolver
+import ipaddress
+import logging
+import logging.handlers
+import os.path
+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():
+ 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 getKey(keyfile, record):
+ if not os.path.exists(keyfile):
+ raise Exception("missing keyfile {}".format(keyfile))
+
+ config = configparser.ConfigParser()
+ config.read(keyfile)
+
+ if not "keys" in config:
+ raise Exception("keyfile lacks [keys] section")
+
+ if not record in config["keys"]:
+ raise Exception("keyfile has no entry for {}".format(record))
+
+ return config["keys"][record]
+
+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("--keyfile", help="Alternate location for key config file (default /etc/he-dns-secret.conf)")
+ ap.add_argument("--key", help="HE DDNS key for record (by default read from /etc/he-dns-secret.conf or --keyfile)")
+ 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)
+
+ if args.keyfile:
+ log.debug("KEYFILE: {}".format(args.keyfile))
+ keyfile = args.keyfile
+ else:
+ log.debug("KEYFILE: /etc/he-dyndns.conf")
+ keyfile = "/etc/he-dyndns.conf"
+
+ 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.key:
+ log.debug("--key override; ignore --kefile")
+ apikey = args.key
+ else:
+ apikey = getKey(keyfile, args.record)
+
+ 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, apikey ), "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)