#!/usr/bin/python import sys import re import argparse from subprocess import Popen, PIPE import ipaddr class NsupdateError(Exception): def __init__(self, returncode): self.returncode = returncode class BlockipError(Exception): def __init__(self, returncode): self.returncode = returncode def ipfamily_by_ip(ip): if isinstance(ip, ipaddr.IPv4Address): return 'A' elif isinstance(ip, ipaddr.IPv6Address): return 'AAAA' assert False def nsupdate_add(fqdn, ttl, ip): """ :param fqdn: Fully qualified domain name :param ip_family: A or AAAA :raises an NsupdateError in case of errors.""" command = "update add {fqdn} {ttl} IN {ip_family} {ip}\n\n".format(fqdn=fqdn, ttl=ttl, ip_family=ipfamily_by_ip(ip), ip=ip) p = Popen(['nsupdate', '-l'], stdin=PIPE) p.communicate(command) if p.returncode != 0: raise NsupdateError(p.returncode) def nsupdate_delete(fqdn, ip_family): """ :param fqdn: Fully qualified domain name :param ip_family: A or AAAA :raises an NsupdateError in case of errors.""" command = "update delete {fqdn} {ip_family}\n\n".format(fqdn=fqdn, ip_family=ip_family) p = Popen(['nsupdate', '-l'], stdin=PIPE) p.communicate(command) if p.returncode != 0: raise NsupdateError(p.returncode) def blockip_whitelist_add(ip): """ :param ip: ipv4 address :raises a BlockipError in case of errors.""" command = "-I blockip -s {ip} -j ACCEPT\n\n".format(ip=ip) p = Popen(['iptables'], stdin=PIPE) p.communicate(command) if p.returncode != 0: raise NsupdateError(p.returncode) def blockip_whitelist_delete(ip): """ :param ip: ipv4 address :raises a BlockipError in case of errors.""" command = "-D blockip -s {ip} -j ACCEPT\n\n".format(ip=ip) p = Popen(['iptables'], stdin=PIPE) p.communicate(command) if p.returncode != 0: raise NsupdateError(p.returncode) def main(args): try: if args.delete: if args.ip is None: nsupdate_delete(args.fqdn, 'A') nsupdate_delete(args.fqdn, 'AAAA') else: nsupdate_delete(args.fqdn, ipfamily_by_ip(args.ip)) if ipfamily_by_ip(args.ip) == 'A': blockip_whitelist_delete(args.ip) else: nsupdate_delete(args.fqdn, ipfamily_by_ip(args.ip)) nsupdate_add(args.fqdn, args.ttl, args.ip) if ipfamily_by_ip(args.ip) == 'A': blockip_whitelist_add(args.ip) except NsupdateError as e: sys.exit(e.returncode) if __name__ == '__main__': parser = argparse.ArgumentParser(description='Add or delete a domain name from dyndns (simplifies call to nsupdate).') parser.add_argument('-d', '--delete', action='store_true', help='delete instead of add') parser.add_argument('-i', '--ip', help='IP address (either IPv4 or IPv6)') parser.add_argument('-t', '--ttl', type=int, default=600, help='TTL (default: 600)') parser.add_argument('fqdn', help='fully qualified domain name to add or delete, e.g. myserver.dyn.example.com') args = parser.parse_args() # check ip if not args.delete and not args.ip: parser.error('The IP address is mandatory') if args.ip: try: args.ip = ipaddr.IPAddress(args.ip) # throws an exception if the IP address is not valid except ValueError: parser.error('The IP address is not valid') # check fqdn if re.match(r'[-0-9a-z]+(\.[-0-9a-z]+)*$', args.fqdn) is None: parser.error('The fqdn has an invalid format.') main(args)