#!/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' 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