summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason D. McCormick <jason@mfamily.org>2022-11-26 12:02:30 -0500
committerJason D. McCormick <jason@mfamily.org>2022-11-26 12:02:30 -0500
commit8c81c35db4c4f5fc42c70c02f0750eaccb868b84 (patch)
treedda0aaad1e0f7c39f1760328a7682650363b00bd
parent9212ef438c3a18d176e856c738b017929b9e75eb (diff)
New, full-fledged python implementation
-rwxr-xr-xhe-dyndns222
-rwxr-xr-xhe-dyndns-bash91
2 files changed, 223 insertions, 90 deletions
diff --git a/he-dyndns b/he-dyndns
index 0ce3730..26a97b6 100755
--- a/he-dyndns
+++ b/he-dyndns
@@ -1,91 +1,133 @@
-#!/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
+#!/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)
diff --git a/he-dyndns-bash b/he-dyndns-bash
new file mode 100755
index 0000000..0ce3730
--- /dev/null
+++ b/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
+