]> ToastFreeware Gitweb - toast/tdyndns.git/blobdiff - cgi-bin/dyndns.py
update docs after renames
[toast/tdyndns.git] / cgi-bin / dyndns.py
index 8291ac237b61b0b9e5bd269ed3d2ae2f7c3c21b6..5cbf1cee2d2ece5b71606fe3a94f0c478a111abf 100755 (executable)
@@ -1,18 +1,18 @@
 #!/usr/bin/python2.7
 """Dynamic DNS script. Expects URLs from routers in the form
-https://info.colgarra.priv.at/dyndns/dyndns.py?username=<username>&password=<pass>&hostname=<domain>&myip=<ipaddr>
+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'
-ZONE = '.dyn.colgarra.priv.at'
 DEBUG = False
 
 
@@ -22,22 +22,6 @@ if DEBUG:
        cgitb.enable()
 
 
-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')
-
-
 # Base class for our exceptions
 class DynDnsError(Exception):
        pass
@@ -45,6 +29,9 @@ class DynDnsError(Exception):
 class AuthError(DynDnsError):
        returncode = 'badauth'
 
+class CredentialsMissing(AuthError):
+       pass
+
 class UsernameMissing(AuthError):
        pass
 
@@ -57,6 +44,12 @@ class PasswordMissing(AuthError):
 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'
 
@@ -66,16 +59,59 @@ class HostnameMissing(HostnameError):
 class PasswordWrong(HostnameError):
        pass
 
-class IpError(DynDnsError):
+class MyipError(DynDnsError):
        returncode = 'badip' # not documented at dyn.com
 
-class IpMissing(IpError):
+class MyipMissing(MyipError):
        pass
 
-class IpInvalid(IpError):
+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()
@@ -100,34 +136,58 @@ try:
 
        # strip zone
        hostname = hostname.strip()
-       if hostname.endswith(ZONE):
-               hostname = hostname[:-len(ZONE)]
 
-       # check IP address
-       if myip is None:
-               raise IpMissing()
-       try:
-               ip = ipaddr.IPAddress(myip) # throws an exception if the IP address is not valid
-       except ValueError:
-               raise IpInvalid()
-       if isinstance(ip, ipaddr.IPv4Address):
-               iptype = 'A'
-       elif isinstance(ip, ipaddr.IPv6Address):
-               iptype = 'AAAA'
+       # check offline
+       if offline is None or offline.lower() == 'no':
+               offline = False
+       elif offline.lower() == 'yes':
+               offline = True
        else:
-               raise IpInvalid() # should never happen
+               raise OfflineInvalid()
 
-       # access granted
+       # 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
-       call(['sudo', '/usr/local/bin/nsupdate_dyndns', hostname, myip, iptype])
        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"
+       print "Status: 200 OK"
        print
        print error.returncode