Blog: Implementing a self-hosted DD-WRT-compatible DDNS service using Linode: ddns.py

File ddns.py, 3.3 KB (added by retracile, 4 years ago)

CGI script providing DDNS service

Line 
1#!/usr/bin/python
2# License: GPLv2
3"""Utility for providing a DDNS service for DD-WRT and similar routers using
4Linode's DNS manager.
5"""
6import os
7import ConfigParser as configparser
8
9
10# Define this early so we can use it for import errors.
11def 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
24try:
25    from linode.api import Api, ApiError
26except 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
32def 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
50def 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
93def main():
94    """Runs as a CGI script"""
95    status, body = cgi_update()
96    send_response(status, body)
97    return 0
98
99
100if __name__ == '__main__':
101    main()