Renamed files to include tdyndns in the name. 0.0.2
authorPhilipp Spitzer <philipp@spitzer.priv.at>
Tue, 8 Apr 2014 20:51:55 +0000 (22:51 +0200)
committerPhilipp Spitzer <philipp@spitzer.priv.at>
Tue, 8 Apr 2014 20:56:13 +0000 (22:56 +0200)
bin/nsupdate_dyndns [deleted file]
bin/tdyndns_update [new file with mode: 0755]
cgi-bin/dyndns.py [deleted file]
cgi-bin/tdyndns.py [new file with mode: 0755]

diff --git a/bin/nsupdate_dyndns b/bin/nsupdate_dyndns
deleted file mode 100755 (executable)
index 4452fe0..0000000
+++ /dev/null
@@ -1,83 +0,0 @@
-#!/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
-
-
-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 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))
-               else:
-                       nsupdate_delete(args.fqdn, ipfamily_by_ip(args.ip))
-                       nsupdate_add(args.fqdn, args.ttl, 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)
diff --git a/bin/tdyndns_update b/bin/tdyndns_update
new file mode 100755 (executable)
index 0000000..4452fe0
--- /dev/null
@@ -0,0 +1,83 @@
+#!/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
+
+
+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 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))
+               else:
+                       nsupdate_delete(args.fqdn, ipfamily_by_ip(args.ip))
+                       nsupdate_add(args.fqdn, args.ttl, 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)
diff --git a/cgi-bin/dyndns.py b/cgi-bin/dyndns.py
deleted file mode 100755 (executable)
index 5cbf1ce..0000000
+++ /dev/null
@@ -1,193 +0,0 @@
-#!/usr/bin/python2.7
-"""Dynamic DNS script. Expects URLs from routers in the form
-http://dyndns.colgarra.priv.at/nic/update?username=<username>&password=<pass>&hostname=<domain>&myip=<ipaddr>
-"""
-import os
-import re
-import cgi
-import pwd
-import base64
-from subprocess import call
-import ipaddr
-
-
-# Configuration
-PASSWORD = 'hygCithOrs5'
-DEBUG = False
-
-
-# Just for debugging:
-if DEBUG:
-       import cgitb
-       cgitb.enable()
-
-
-# Base class for our exceptions
-class DynDnsError(Exception):
-       pass
-
-class AuthError(DynDnsError):
-       returncode = 'badauth'
-
-class CredentialsMissing(AuthError):
-       pass
-
-class UsernameMissing(AuthError):
-       pass
-
-class UsernameInvalid(AuthError):
-       pass
-
-class PasswordMissing(AuthError):
-       pass
-
-class PasswordWrong(AuthError):
-       pass
-
-class AuthWrongMethod(AuthError):
-       returncode = 'wrongauthmethod' # not documented at dyn.com
-
-class AuthBasicError(AuthError):
-       returncode = 'authbasicerror' # not documented at dyn.com
-
-class HostnameError(DynDnsError):
-       returncode = 'notfqdn'
-
-class HostnameMissing(HostnameError):
-       pass
-
-class PasswordWrong(HostnameError):
-       pass
-
-class MyipError(DynDnsError):
-       returncode = 'badip' # not documented at dyn.com
-
-class MyipMissing(MyipError):
-       pass
-
-class MyipInvalid(MyipError):
-       pass
-
-class OfflineInvalid(DynDnsError):
-       returncode = 'badparam' # not documented at dyn.com
-
-class NsupdateError(DynDnsError):
-       returncode = 'nohost'
-
-
-fields = cgi.FieldStorage()
-
-# the following fields are supported by most dyndns providers
-# if a parameter is not provided, the .getvalue method returns None
-username = fields.getvalue('username')
-password = fields.getvalue('password')
-hostname = fields.getvalue('hostname')
-myip     = fields.getvalue('myip')
-wildcard = fields.getvalue('wildcard')
-mx       = fields.getvalue('mx')
-backmx   = fields.getvalue('backmx')
-offline  = fields.getvalue('offline')
-system   = fields.getvalue('system')
-url      = fields.getvalue('url')
-
-
-try:
-       # Optional Auth Basic
-       auth = os.environ.get('HTTP_AUTHORIZATION') # auth == 'Basic cGhpbGlwcDpka2ZhamRrZg=='
-       if auth: # empty string if HTTP_AUTHORIZATION not present
-               auth_parts = auth.split(' ')
-               auth_method = 'Basic'
-               if len(auth_parts) != 2 or auth_parts[0] != auth_method:
-                       raise AuthWrongMethod()
-               try:
-                       auth_decoded = base64.b64decode(auth_parts[1]) # auth_decoded == 'philipp:dkfajdkf'
-               except TypeError:
-                       raise AuthBasicError()
-               auth_decoded_parts = auth_decoded.split(':')
-               if len(auth_decoded_parts) != 2:
-                       raise AuthBasicError()
-               username, password = auth_decoded_parts
-
-       # check username and password
-       if username is None and password is None:
-               raise CredentialsMissing()
-
-       # check username
-       if username is None:
-               raise UsernameMissing()
-       try:
-               user_info = pwd.getpwnam(username)
-       except KeyError:
-               raise UsernameInvalid()
-       if user_info.pw_uid < 1000:
-               raise UsernameInvalid()
-
-       # check password
-       if password is None:
-               raise PasswordMissing()
-       if password != PASSWORD:
-               raise PasswordWrong()
-
-       # check hostname
-       if hostname is None:
-               raise HostnameMissing()
-       if re.match(r'[-0-9a-z]+(\.[-0-9a-z]+)*$', hostname) is None:
-               raise HostnameInvalid()
-
-       # strip zone
-       hostname = hostname.strip()
-
-       # check offline
-       if offline is None or offline.lower() == 'no':
-               offline = False
-       elif offline.lower() == 'yes':
-               offline = True
-       else:
-               raise OfflineInvalid()
-
-       # check IP address
-       if not offline:
-               if myip is None:
-                       # try HTTP_X_FORWARDED_FOR
-                       myip = os.environ.get('HTTP_X_FORWARDED_FOR')
-                       if not myip: # empty string if not present
-                               # try REMOTE_ADDR
-                               myip = os.environ.get('REMOTE_ADDR')
-                               if not myip: # empty string if not present
-                                       raise MyipMissing()
-       if not myip is None:
-               try:
-                       ipaddr.IPAddress(myip) # throws an exception if the IP address is not valid
-               except ValueError:
-                       raise MyipInvalid()
-
-       # update bind
-       call_params = ['sudo', 'nsupdate_dyndns']
-       if offline:
-               call_params.append('--delete')
-       if myip is not None:
-               call_params.extend(['--ip', myip])
-       call_params.append(hostname)
-       retcode = call(call_params)
-       if retcode != 0:
-               raise NsupdateError()
-
-       # return success
-       print "Content-Type: text/html"
-       print
-       print "good"
-       # Note: we should return 'nochg' in case the IP has not changed, however we don't know yet.
-
-
-except CredentialsMissing as error:
-       print "Content-Type: text/html"
-       print "Status: 401 Unauthorized"
-       print "WWW-Authenticate: Basic realm='tdyndns'"
-       print
-
-except DynDnsError as error:
-       print "Content-Type: text/html"
-       print "Status: 200 OK"
-       print
-       print error.returncode
-
diff --git a/cgi-bin/tdyndns.py b/cgi-bin/tdyndns.py
new file mode 100755 (executable)
index 0000000..3b5db5a
--- /dev/null
@@ -0,0 +1,193 @@
+#!/usr/bin/python2.7
+"""Dynamic DNS script. Expects URLs from routers in the form
+http://dyndns.colgarra.priv.at/nic/update?username=<username>&password=<pass>&hostname=<domain>&myip=<ipaddr>
+"""
+import os
+import re
+import cgi
+import pwd
+import base64
+from subprocess import call
+import ipaddr
+
+
+# Configuration
+PASSWORD = 'hygCithOrs5'
+DEBUG = False
+
+
+# Just for debugging:
+if DEBUG:
+       import cgitb
+       cgitb.enable()
+
+
+# Base class for our exceptions
+class DynDnsError(Exception):
+       pass
+
+class AuthError(DynDnsError):
+       returncode = 'badauth'
+
+class CredentialsMissing(AuthError):
+       pass
+
+class UsernameMissing(AuthError):
+       pass
+
+class UsernameInvalid(AuthError):
+       pass
+
+class PasswordMissing(AuthError):
+       pass
+
+class PasswordWrong(AuthError):
+       pass
+
+class AuthWrongMethod(AuthError):
+       returncode = 'wrongauthmethod' # not documented at dyn.com
+
+class AuthBasicError(AuthError):
+       returncode = 'authbasicerror' # not documented at dyn.com
+
+class HostnameError(DynDnsError):
+       returncode = 'notfqdn'
+
+class HostnameMissing(HostnameError):
+       pass
+
+class PasswordWrong(HostnameError):
+       pass
+
+class MyipError(DynDnsError):
+       returncode = 'badip' # not documented at dyn.com
+
+class MyipMissing(MyipError):
+       pass
+
+class MyipInvalid(MyipError):
+       pass
+
+class OfflineInvalid(DynDnsError):
+       returncode = 'badparam' # not documented at dyn.com
+
+class NsupdateError(DynDnsError):
+       returncode = 'nohost'
+
+
+fields = cgi.FieldStorage()
+
+# the following fields are supported by most dyndns providers
+# if a parameter is not provided, the .getvalue method returns None
+username = fields.getvalue('username')
+password = fields.getvalue('password')
+hostname = fields.getvalue('hostname')
+myip     = fields.getvalue('myip')
+wildcard = fields.getvalue('wildcard')
+mx       = fields.getvalue('mx')
+backmx   = fields.getvalue('backmx')
+offline  = fields.getvalue('offline')
+system   = fields.getvalue('system')
+url      = fields.getvalue('url')
+
+
+try:
+       # Optional Auth Basic
+       auth = os.environ.get('HTTP_AUTHORIZATION') # auth == 'Basic cGhpbGlwcDpka2ZhamRrZg=='
+       if auth: # empty string if HTTP_AUTHORIZATION not present
+               auth_parts = auth.split(' ')
+               auth_method = 'Basic'
+               if len(auth_parts) != 2 or auth_parts[0] != auth_method:
+                       raise AuthWrongMethod()
+               try:
+                       auth_decoded = base64.b64decode(auth_parts[1]) # auth_decoded == 'philipp:dkfajdkf'
+               except TypeError:
+                       raise AuthBasicError()
+               auth_decoded_parts = auth_decoded.split(':')
+               if len(auth_decoded_parts) != 2:
+                       raise AuthBasicError()
+               username, password = auth_decoded_parts
+
+       # check username and password
+       if username is None and password is None:
+               raise CredentialsMissing()
+
+       # check username
+       if username is None:
+               raise UsernameMissing()
+       try:
+               user_info = pwd.getpwnam(username)
+       except KeyError:
+               raise UsernameInvalid()
+       if user_info.pw_uid < 1000:
+               raise UsernameInvalid()
+
+       # check password
+       if password is None:
+               raise PasswordMissing()
+       if password != PASSWORD:
+               raise PasswordWrong()
+
+       # check hostname
+       if hostname is None:
+               raise HostnameMissing()
+       if re.match(r'[-0-9a-z]+(\.[-0-9a-z]+)*$', hostname) is None:
+               raise HostnameInvalid()
+
+       # strip zone
+       hostname = hostname.strip()
+
+       # check offline
+       if offline is None or offline.lower() == 'no':
+               offline = False
+       elif offline.lower() == 'yes':
+               offline = True
+       else:
+               raise OfflineInvalid()
+
+       # check IP address
+       if not offline:
+               if myip is None:
+                       # try HTTP_X_FORWARDED_FOR
+                       myip = os.environ.get('HTTP_X_FORWARDED_FOR')
+                       if not myip: # empty string if not present
+                               # try REMOTE_ADDR
+                               myip = os.environ.get('REMOTE_ADDR')
+                               if not myip: # empty string if not present
+                                       raise MyipMissing()
+       if not myip is None:
+               try:
+                       ipaddr.IPAddress(myip) # throws an exception if the IP address is not valid
+               except ValueError:
+                       raise MyipInvalid()
+
+       # update bind
+       call_params = ['sudo', 'tdyndns_update']
+       if offline:
+               call_params.append('--delete')
+       if myip is not None:
+               call_params.extend(['--ip', myip])
+       call_params.append(hostname)
+       retcode = call(call_params)
+       if retcode != 0:
+               raise NsupdateError()
+
+       # return success
+       print "Content-Type: text/html"
+       print
+       print "good"
+       # Note: we should return 'nochg' in case the IP has not changed, however we don't know yet.
+
+
+except CredentialsMissing as error:
+       print "Content-Type: text/html"
+       print "Status: 401 Unauthorized"
+       print "WWW-Authenticate: Basic realm='tdyndns'"
+       print
+
+except DynDnsError as error:
+       print "Content-Type: text/html"
+       print "Status: 200 OK"
+       print
+       print error.returncode
+