#!/usr/bin/python2.7 """Dynamic DNS script. Expects URLs from routers in the form http://dyndns.colgarra.priv.at/nic/update??username=&password=&hostname=&myip= """ import os import re import cgi import pwd import base64 from subprocess import call import ipaddr # Configuration PASSWORD = 'hygCithOrs5' ZONE = '.dyn.colgarra.priv.at' 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 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') # 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 try: # 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() if hostname.endswith(ZONE): hostname = hostname[:-len(ZONE)] # 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: raise MyipMissing() try: ip = ipaddr.IPAddress(myip) # throws an exception if the IP address is not valid except ValueError: raise MyipInvalid() if isinstance(ip, ipaddr.IPv4Address): iptype = 'A' elif isinstance(ip, ipaddr.IPv6Address): iptype = 'AAAA' else: raise MyipInvalid() # should never happen # update bind if offline: call(['sudo', '/usr/local/bin/nsupdate_dyndns_del', hostname, 'A']) call(['sudo', '/usr/local/bin/nsupdate_dyndns_del', hostname, 'AAAA']) else: call(['sudo', '/usr/local/bin/nsupdate_dyndns', hostname, myip, iptype]) # 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