1 | #!/usr/bin/python |
---|
2 | # License: GPLv2 |
---|
3 | """Utility for providing a DDNS service for DD-WRT and similar routers using |
---|
4 | Linode's DNS manager. |
---|
5 | """ |
---|
6 | import os |
---|
7 | import ConfigParser as configparser |
---|
8 | |
---|
9 | |
---|
10 | # Define this early so we can use it for import errors. |
---|
11 | def send_response(status, body): |
---|
12 | """Utility function for sending a CGI response""" |
---|
13 | print "Content-type: text/html" |
---|
14 | print status |
---|
15 | print "" |
---|
16 | print "<html>" |
---|
17 | print "<head><title>DDNS update</title></head>" |
---|
18 | print "<body>" |
---|
19 | print body |
---|
20 | print "</body>" |
---|
21 | print "</html>" |
---|
22 | |
---|
23 | |
---|
24 | try: |
---|
25 | from linode.api import Api, ApiError |
---|
26 | except ImportError: |
---|
27 | send_response("Status: 500 Internal Server Error", |
---|
28 | "linode-python package not installed. " |
---|
29 | "See https://pypi.python.org/pypi/linode-python/1.0") |
---|
30 | |
---|
31 | |
---|
32 | def update_domain_resource(fqdn, address, key): |
---|
33 | """Update the Linode DNS manager""" |
---|
34 | api = Api(key=key) |
---|
35 | base_domain = '.'.join(fqdn.split('.')[-2:]) |
---|
36 | hostname = fqdn[:-len(base_domain)-1] |
---|
37 | |
---|
38 | domains_by_name = dict((d['DOMAIN'], d) for d in api.domain_list()) |
---|
39 | domain_to_update = domains_by_name[base_domain] |
---|
40 | domain_id = domain_to_update['DOMAINID'] |
---|
41 | resources = api.domain_resource_list(DomainID=domain_id) |
---|
42 | hosts_by_name = dict((r['NAME'], r) for r in resources) |
---|
43 | host_to_update = hosts_by_name[hostname] |
---|
44 | resource_id = host_to_update['RESOURCEID'] |
---|
45 | # Finally, make the call to update the domain |
---|
46 | api.domain_resource_update(DomainID=domain_id, ResourceID=resource_id, |
---|
47 | Target=address) |
---|
48 | |
---|
49 | |
---|
50 | def cgi_update(): |
---|
51 | """Using the configuration file, update the authenticated user's host names |
---|
52 | to the user's IP address. Returns a CGI status and an explanatory message |
---|
53 | for the body. |
---|
54 | """ |
---|
55 | if os.environ['AUTH_TYPE'] != 'Basic': |
---|
56 | return "Status: 401 Unauthorized", "No authentication given" |
---|
57 | config = configparser.ConfigParser() |
---|
58 | try: |
---|
59 | config.readfp(open(os.path.join(os.path.dirname(__file__), 'ddns.ini'))) |
---|
60 | except IOError, error: |
---|
61 | return ("Status: 500 Internal Server Error", |
---|
62 | "Failed to find configuration file; %s" % error) |
---|
63 | try: |
---|
64 | apikey = config.get('ddns', 'key') |
---|
65 | except configparser.Error, error: |
---|
66 | return ("Status: 500 Internal Server Error", |
---|
67 | "Failed to find key in configuration; %s" % error) |
---|
68 | try: |
---|
69 | user = os.environ['REMOTE_USER'] |
---|
70 | except KeyError: |
---|
71 | return ("Status: 401 Unauthorized", "No user given") |
---|
72 | try: |
---|
73 | new_address = os.environ['REMOTE_ADDR'] |
---|
74 | except KeyError: |
---|
75 | return ("Status: 500 Internal Server Error", "No remote IP address") |
---|
76 | try: |
---|
77 | domains = [d.strip() for d in config.get(user, |
---|
78 | 'domains').split(',')] |
---|
79 | except configparser.Error, error: |
---|
80 | return ("Status: 500 Internal Server Error", |
---|
81 | "Failed to find user's domains in configuration; %s" % error) |
---|
82 | try: |
---|
83 | for domain in domains: |
---|
84 | update_domain_resource(domain, new_address, apikey) |
---|
85 | except ApiError, error: |
---|
86 | return ("Status: 500 Internal Server Error", |
---|
87 | "update of %s failed with %s" % (domain, error)) |
---|
88 | |
---|
89 | return ("Status: 200 OK", |
---|
90 | "Updated %s to %s" % (', '.join(domains), new_address)) |
---|
91 | |
---|
92 | |
---|
93 | def main(): |
---|
94 | """Runs as a CGI script""" |
---|
95 | status, body = cgi_update() |
---|
96 | send_response(status, body) |
---|
97 | return 0 |
---|
98 | |
---|
99 | |
---|
100 | if __name__ == '__main__': |
---|
101 | main() |
---|