diff options
| author | Jason D. McCormick <jason@mfamily.org> | 2022-11-26 12:02:30 -0500 |
|---|---|---|
| committer | Jason D. McCormick <jason@mfamily.org> | 2022-11-26 12:02:30 -0500 |
| commit | 8c81c35db4c4f5fc42c70c02f0750eaccb868b84 (patch) | |
| tree | dda0aaad1e0f7c39f1760328a7682650363b00bd | |
| parent | 9212ef438c3a18d176e856c738b017929b9e75eb (diff) | |
New, full-fledged python implementation
| -rwxr-xr-x | he-dyndns | 222 | ||||
| -rwxr-xr-x | he-dyndns-bash | 91 |
2 files changed, 223 insertions, 90 deletions
@@ -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 + |
