summaryrefslogtreecommitdiff
path: root/he-dns
diff options
context:
space:
mode:
Diffstat (limited to 'he-dns')
-rwxr-xr-xhe-dns/he-dyndns162
-rwxr-xr-xhe-dns/he-dyndns-bash91
-rw-r--r--he-dns/he-dyndns.conf4
3 files changed, 257 insertions, 0 deletions
diff --git a/he-dns/he-dyndns b/he-dns/he-dyndns
new file mode 100755
index 0000000..bec3b60
--- /dev/null
+++ b/he-dns/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)
diff --git a/he-dns/he-dyndns-bash b/he-dns/he-dyndns-bash
new file mode 100755
index 0000000..0ce3730
--- /dev/null
+++ b/he-dns/he-dyndns-bash
@@ -0,0 +1,91 @@
+#!/bin/bash
+#
+# Copyright (C)2019 by Jason D. McCormick <jason@mfamily.org>
+# Licensed under the Apache License 2.0. See LICENSE included
+# with the distribution bundle that contained this file.
+#
+
+PATH="/usr/bin:/bin:/usr/sbin:/sbin"
+
+## KNOWLEDGE IS GOOD ##
+usage() { echo "Usage: $0 [-s SECRET] -d HOSTNAME[,HOSTNAME,...]" 1>&2; exit 1; }
+
+## DO THE UPDATE FUNCTION ONCE FOR HAPPY EDITING ##
+doUpdate() {
+
+ if [ ! -z $2 ]; then
+ myip="&myip=$2"
+ fi
+
+ /usr/bin/curl -s$1 "https://$FQDN:$CODE@$DNSSITE/nic/update?hostname=$FQDN$myip" > $tfile
+ if ! egrep -q '(good|nochg)' $tfile; then
+ echo -n "v$1 change: "
+ cat $tfile
+ echo
+ retval=1
+ fi
+}
+
+## MAIN ##
+while getopts "46a:A:hd:s:" arg; do
+ case "${arg}" in
+ 4)
+ DO4=Y
+ ;;
+ 6)
+ DO6=Y
+ ;;
+ a)
+ ADDR4=${OPTARG}
+ ;;
+ A)
+ ADDR6=${OPTARG}
+ ;;
+ d)
+ FQDNS=${OPTARG}
+ ;;
+ s)
+ CODE=${OPTARG}
+ ;;
+ *)
+ usage
+ ;;
+ esac
+done
+
+if [ -z "${FQDNS}" ]; then
+ usage
+fi
+
+if [ ! -f /etc/he-dns-secret ] && [ -z "${CODE}" ]; then
+ usage
+fi
+
+if [ -z "${CODE}" ]; then
+ . /etc/he-dns-secret
+
+ if [ -z "${CODE}" ]; then
+ echo "/etc/he-dns-secret does not contain a CODE= variable"
+ exit 1
+ fi
+fi
+
+DNSSITE="dyn.dns.he.net";
+retval=0
+tfile=$(/bin/mktemp)
+
+for FQDN in $(echo $FQDNS | tr ',' ' ')
+do
+ if [ ! -z ${DO4} ]; then
+ doUpdate 4 ${ADDR4}
+ fi
+
+ if [ ! -z ${DO6} ]; then
+ doUpdate 6 ${ADDR6}
+ fi
+done
+
+rm $tfile
+
+exit $retval
+
diff --git a/he-dns/he-dyndns.conf b/he-dns/he-dyndns.conf
new file mode 100644
index 0000000..26b387b
--- /dev/null
+++ b/he-dns/he-dyndns.conf
@@ -0,0 +1,4 @@
+# List API keys one key per domain record in the format
+# record = key
+[keys]
+dyntest.mfamily.org = yEuFfO74TskIX2NG0Yod