Chanded example URL in comment.
[toast/tdyndns.git] / cgi-bin / dyndns.py
1 #!/usr/bin/python2.7
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>
4 """
5
6 import re
7 import cgi
8 import pwd
9 from subprocess import call
10 import ipaddr
11
12
13 # Configuration
14 PASSWORD = 'hygCithOrs5'
15 ZONE = '.dyn.colgarra.priv.at'
16 DEBUG = False
17
18
19 # Just for debugging:
20 if DEBUG:
21         import cgitb
22         cgitb.enable()
23
24
25 fields = cgi.FieldStorage()
26
27 # the following fields are supported by most dyndns providers
28 # if a parameter is not provided, the .getvalue method returns None
29 username = fields.getvalue('username')
30 password = fields.getvalue('password')
31 hostname = fields.getvalue('hostname')
32 myip     = fields.getvalue('myip')
33 wildcard = fields.getvalue('wildcard')
34 mx       = fields.getvalue('mx')
35 backmx   = fields.getvalue('backmx')
36 offline  = fields.getvalue('offline')
37 system   = fields.getvalue('system')
38 url      = fields.getvalue('url')
39
40
41 # Base class for our exceptions
42 class DynDnsError(Exception):
43         pass
44
45 class AuthError(DynDnsError):
46         returncode = 'badauth'
47
48 class UsernameMissing(AuthError):
49         pass
50
51 class UsernameInvalid(AuthError):
52         pass
53
54 class PasswordMissing(AuthError):
55         pass
56
57 class PasswordWrong(AuthError):
58         pass
59
60 class HostnameError(DynDnsError):
61         returncode = 'notfqdn'
62
63 class HostnameMissing(HostnameError):
64         pass
65
66 class PasswordWrong(HostnameError):
67         pass
68
69 class MyipError(DynDnsError):
70         returncode = 'badip' # not documented at dyn.com
71
72 class MyipMissing(MyipError):
73         pass
74
75 class MyipInvalid(MyipError):
76         pass
77
78 class OfflineInvalid(DynDnsError):
79         returncode = 'badparam' # not documented at dyn.com
80
81 try:
82         # check username
83         if username is None:
84                 raise UsernameMissing()
85         try:
86                 user_info = pwd.getpwnam(username)
87         except KeyError:
88                 raise UsernameInvalid()
89         if user_info.pw_uid < 1000:
90                 raise UsernameInvalid()
91
92         # check password
93         if password is None:
94                 raise PasswordMissing()
95         if password != PASSWORD:
96                 raise PasswordWrong()
97
98         # check hostname
99         if hostname is None:
100                 raise HostnameMissing()
101         if re.match(r'[-0-9a-z]+(\.[-0-9a-z]+)*$', hostname) is None:
102                 raise HostnameInvalid()
103
104         # strip zone
105         hostname = hostname.strip()
106         if hostname.endswith(ZONE):
107                 hostname = hostname[:-len(ZONE)]
108
109         # check offline
110         if offline is None or offline.lower() == 'no':
111                 offline = False
112         elif offline.lower() == 'yes':
113                 offline = True
114         else:
115                 raise OfflineInvalid()
116
117         # check IP address
118         if not offline:
119                 if myip is None:
120                         raise MyipMissing()
121                 try:
122                         ip = ipaddr.IPAddress(myip) # throws an exception if the IP address is not valid
123                 except ValueError:
124                         raise MyipInvalid()
125                 if isinstance(ip, ipaddr.IPv4Address):
126                         iptype = 'A'
127                 elif isinstance(ip, ipaddr.IPv6Address):
128                         iptype = 'AAAA'
129                 else:
130                         raise MyipInvalid() # should never happen
131
132         # update bind
133         if offline:
134                 call(['sudo', '/usr/local/bin/nsupdate_dyndns_del', hostname, 'A'])
135                 call(['sudo', '/usr/local/bin/nsupdate_dyndns_del', hostname, 'AAAA'])
136         else:
137                 call(['sudo', '/usr/local/bin/nsupdate_dyndns', hostname, myip, iptype])
138
139         # return success
140         print "Content-Type: text/html"
141         print
142         print "good"
143         # Note: we should return 'nochg' in case the IP has not changed, however we don't know yet.
144
145
146 except DynDnsError as error:
147         print "Content-Type: text/html"
148         print "Status: 200"
149         print
150         print error.returncode
151