2 """Dynamic DNS script. Expects URLs from routers in the form
3 http://dyndns.colgarra.priv.at/nic/update?username=<username>&password=<pass>&hostname=<domain>&myip=<ipaddr>
10 from subprocess import call
15 PASSWORD = 'hygCithOrs5'
25 # Base class for our exceptions
26 class DynDnsError(Exception):
29 class AuthError(DynDnsError):
30 returncode = 'badauth'
32 class CredentialsMissing(AuthError):
35 class UsernameMissing(AuthError):
38 class UsernameInvalid(AuthError):
41 class PasswordMissing(AuthError):
44 class PasswordWrong(AuthError):
47 class AuthWrongMethod(AuthError):
48 returncode = 'wrongauthmethod' # not documented at dyn.com
50 class AuthBasicError(AuthError):
51 returncode = 'authbasicerror' # not documented at dyn.com
53 class HostnameError(DynDnsError):
54 returncode = 'notfqdn'
56 class HostnameMissing(HostnameError):
59 class PasswordWrong(HostnameError):
62 class MyipError(DynDnsError):
63 returncode = 'badip' # not documented at dyn.com
65 class MyipMissing(MyipError):
68 class MyipInvalid(MyipError):
71 class OfflineInvalid(DynDnsError):
72 returncode = 'badparam' # not documented at dyn.com
75 fields = cgi.FieldStorage()
77 # the following fields are supported by most dyndns providers
78 # if a parameter is not provided, the .getvalue method returns None
79 username = fields.getvalue('username')
80 password = fields.getvalue('password')
81 hostname = fields.getvalue('hostname')
82 myip = fields.getvalue('myip')
83 wildcard = fields.getvalue('wildcard')
84 mx = fields.getvalue('mx')
85 backmx = fields.getvalue('backmx')
86 offline = fields.getvalue('offline')
87 system = fields.getvalue('system')
88 url = fields.getvalue('url')
93 auth = os.environ.get('HTTP_AUTHORIZATION') # auth == 'Basic cGhpbGlwcDpka2ZhamRrZg=='
94 if auth: # empty string if HTTP_AUTHORIZATION not present
95 auth_parts = auth.split(' ')
97 if len(auth_parts) != 2 or auth_parts[0] != auth_method:
98 raise AuthWrongMethod()
100 auth_decoded = base64.b64decode(auth_parts[1]) # auth_decoded == 'philipp:dkfajdkf'
102 raise AuthBasicError()
103 auth_decoded_parts = auth_decoded.split(':')
104 if len(auth_decoded_parts) != 2:
105 raise AuthBasicError()
106 username, password = auth_decoded_parts
108 # check username and password
109 if username is None and password is None:
110 raise CredentialsMissing()
114 raise UsernameMissing()
116 user_info = pwd.getpwnam(username)
118 raise UsernameInvalid()
119 if user_info.pw_uid < 1000:
120 raise UsernameInvalid()
124 raise PasswordMissing()
125 if password != PASSWORD:
126 raise PasswordWrong()
130 raise HostnameMissing()
131 if re.match(r'[-0-9a-z]+(\.[-0-9a-z]+)*$', hostname) is None:
132 raise HostnameInvalid()
135 hostname = hostname.strip()
138 if offline is None or offline.lower() == 'no':
140 elif offline.lower() == 'yes':
143 raise OfflineInvalid()
148 # try HTTP_X_FORWARDED_FOR
149 myip = os.environ.get('HTTP_X_FORWARDED_FOR')
150 if not myip: # empty string if not present
152 myip = os.environ.get('REMOTE_ADDR')
153 if not myip: # empty string if not present
156 ip = ipaddr.IPAddress(myip) # throws an exception if the IP address is not valid
159 if isinstance(ip, ipaddr.IPv4Address):
161 elif isinstance(ip, ipaddr.IPv6Address):
164 raise MyipInvalid() # should never happen
168 call(['sudo', '/usr/local/bin/nsupdate_dyndns', '--delete', hostname])
170 call(['sudo', '/usr/local/bin/nsupdate_dyndns', '--ip', myip, hostname])
173 print "Content-Type: text/html"
176 # Note: we should return 'nochg' in case the IP has not changed, however we don't know yet.
179 except CredentialsMissing as error:
180 print "Content-Type: text/html"
181 print "Status: 401 Unauthorized"
182 print "WWW-Authenticate: Basic realm='tdyndns'"
185 except DynDnsError as error:
186 print "Content-Type: text/html"
187 print "Status: 200 OK"
189 print error.returncode