5 from subprocess import Popen, PIPE, call
9 class ExternalProgramError(RuntimeError):
13 class NsupdateError(ExternalProgramError):
14 def __init__(self, returncode):
15 self.returncode = returncode
18 class BlockipError(ExternalProgramError):
19 def __init__(self, returncode):
20 self.returncode = returncode
23 def ipfamily_by_ip(ip):
24 if isinstance(ip, ipaddr.IPv4Address):
26 elif isinstance(ip, ipaddr.IPv6Address):
31 def nsupdate_add(fqdn, ttl, ip):
33 :param fqdn: Fully qualified domain name
34 :param ip_family: A or AAAA
35 :raises an NsupdateError in case of errors."""
36 command = "update add {fqdn} {ttl} IN {ip_family} {ip}\n\n".format(fqdn=fqdn, ttl=ttl, ip_family=ipfamily_by_ip(ip), ip=ip)
37 p = Popen(['nsupdate', '-l'], stdin=PIPE)
38 p.communicate(command)
40 raise NsupdateError(p.returncode)
42 def nsupdate_delete(fqdn, ip_family):
44 :param fqdn: Fully qualified domain name
45 :param ip_family: A or AAAA
46 :raises an NsupdateError in case of errors."""
47 command = "update delete {fqdn} {ip_family}\n\n".format(fqdn=fqdn, ip_family=ip_family)
48 p = Popen(['nsupdate', '-l'], stdin=PIPE)
49 p.communicate(command)
51 raise NsupdateError(p.returncode)
54 def blockip_whitelist_add(ip):
56 :param ip: ipv4 address
57 :raises a BlockipError in case of errors."""
58 command = "iptables -I blockip -s {ip} -j ACCEPT".format(ip=ip)
59 p = call(command, shell=True)
64 def blockip_whitelist_delete(ip):
66 :param ip: ipv4 address
67 :raises a BlockipError in case of errors."""
68 command = "iptables -D blockip -s {ip} -j ACCEPT".format(ip=ip)
69 p = call(command, shell=True)
78 nsupdate_delete(args.fqdn, 'A')
79 nsupdate_delete(args.fqdn, 'AAAA')
81 nsupdate_delete(args.fqdn, ipfamily_by_ip(args.ip))
82 if ipfamily_by_ip(args.ip) == 'A':
83 blockip_whitelist_delete(args.ip)
85 nsupdate_delete(args.fqdn, ipfamily_by_ip(args.ip))
86 nsupdate_add(args.fqdn, args.ttl, args.ip)
87 if ipfamily_by_ip(args.ip) == 'A':
88 blockip_whitelist_add(args.ip)
89 except ExternalProgramError as e:
90 sys.exit(e.returncode)
94 if __name__ == '__main__':
95 parser = argparse.ArgumentParser(description='Add or delete a domain name from dyndns (simplifies call to nsupdate).')
96 parser.add_argument('-d', '--delete', action='store_true', help='delete instead of add')
97 parser.add_argument('-i', '--ip', help='IP address (either IPv4 or IPv6)')
98 parser.add_argument('-t', '--ttl', type=int, default=600, help='TTL (default: 600)')
99 parser.add_argument('fqdn', help='fully qualified domain name to add or delete, e.g. myserver.dyn.example.com')
100 args = parser.parse_args()
103 if not args.delete and not args.ip:
104 parser.error('The IP address is mandatory')
107 args.ip = ipaddr.IPAddress(args.ip) # throws an exception if the IP address is not valid
109 parser.error('The IP address is not valid')
112 if re.match(r'[-0-9a-z]+(\.[-0-9a-z]+)*$', args.fqdn) is None:
113 parser.error('The fqdn has an invalid format.')