diff --git a/README.md b/README.md new file mode 100644 index 0000000..f737c4e --- /dev/null +++ b/README.md @@ -0,0 +1,335 @@ +dyndns.pl +========= + +Perl CGI-BIN script to handle Dynamic DNS updates through HTTP (e.g. from a +router), updating DNS records through secure DNS update statements. + +**Version 1.0**, latest version, documentation and bugtracker available on my +[GitLab instance](https://gitlab.lindenaar.net/scripts/dyndns) + +Copyright (c) 2013 - 2015 Frederik Lindenaar. free for distribution under the +GNU License, see [below](#license) + + +Introduction +------------ +This script provides a simple interface to allow Dynamic DNS updates for DNS +zones. It is intended to be used for routers and (aDSL) modems to register their +IP address by simply opening a URL (this is supported by many modern devices) +but can also be used by end-users (either directly by using a client). Please +bear in mind that this script suits my setup and still might have glitches, but +so far turned out to be a quite stable solution for my needs and I use it in a +production setup. In case you have any comments / questions or issues, please +raise them through my +[GitLab instance](https://gitlab.lindenaar.net/scripts/dyndns) so that all +users benefit. + +Setup +----- +This script is to be executed as CGI-BIN script by a web server. As it is +written in Perl, it requires that installed (which is pretty standard nowadays +on all *nix platforms). This description covers the installation on Apache 2.4, +which should be similar for other web servers, with ISC Bind v9. For performance +reasons consider using the Apache mod_perl module for highly a volatile domain. + +The setup of this solution consists of the following steps: + + 1. Ensure that the Perl modules CGI and Net::DNS are installed. + * on Debian/Ubunto linux this can be done by: + + ~~~~ + sudo apt-get install libcgi-pm-perl libnet-dns-perl + ~~~~ + + * or if you have cpan installed: + + ~~~ + cpan CGI Net::DNS + ~~~ + + 2. Install the file `dyndns.pl` either in your cgi-bin directory or in a + separate folder + + 3. Update the configuration section at the top of the script to match your + environment (see the section on [configuration](#configuration) below). + The least you need to change `$DNSServer` to point to your DNS server and + you probably want to have a look at the `$AllowDebugKey` (useful for + getting things started but you want to set this to 'off' in production. + + 4. To have a nicer URL (or in case the script is not installed in the web + server's cgi-bin directory) add the following line to your Apache virtual + host configuration (replacing `[INSTALL_DIR]` with the install directory): + + ScriptAlias /dyndns [INSTALL_DIR]/dyndns.pl + + in case you have installed the script in a non-standard folder, you will + also need the following to make this work on Apache 2.4 (again replacing + `[INSTALL_DIR]` with the install directory): + + ~~~ + <Directory [INSTALL_DIR]/> + AllowOverride None + Options +ExecCGI -MultiViews -Indexes + Require all granted + </Directory> + ~~~ + + reload apache with `/etc/init.d/apache reload` to make the script + available at <http://myserver.mydomain.tld/dyndns> + + 5. To setup your Bind nameserver, either update `named.conf` direcly or create + a separate file (e.g. `named.dyndns.conf` in the Bind configuration + directory and include that in your setup with the `include` directive + (e.g. `include "named.dyndns.conf";`). For a basic dynamic DNS setup a + configuration like below is required: + + ~~~ + // Define the keys for DynDNS + key "dyndns.mydomain.tld" { + algorithm hmac-md5; secret "QdDJC7QVYmsCxgWoSAUmBg=="; + }; + + key "siteuser" { + algorithm hmac-md5; secret "R6Xkbn+FP85Hq3EDNmv+GQ=="; + }; + + // Define the DDNS zone + zone "dyndns.mydomain.tld" IN { + type master; + file "dyndns/db.dyndns.mydomain.tld"; + + // enable this for list and expire support + // allow-transfer { 192.168.0.2; }; + + update-policy { + grant dyndns.mydomain.tld zonesub ANY; + grant siteuser name site.dyndns.mydomain.tld ANY; + }; + }; + ~~~ + + The above defines a domain zone file `dyndns/db.dyndns.mydomain.tld` with + two signer/keys. *siteuser* only can update `site.dyndns.mydomain.tld` + while *dyndns.mydomain.tld* can update all entries in the domain (intended + for expiry). If you intend to use expiry or want to be able to retrieve a + list of all entries, comment out the `allow-transfer` statement and update + the IP adres to that of your web server. + + To seed these entries with fresh keys), use the following + commands and copy the generated keys into the config file. + + * to generate a new key *dyndns.mydomain.tld*: + + ~~~ + ddns-confgen -a hmac-md5 -k dyndns.mydomain.tld -z dyndns.mydomain.tld + ~~~ + + * generate the required configuration for *siteuser* (or any new user): + + ~~~ + ddns-confgen -a hmac-md5 -k siteuser -s site.dyndns.mydomain.tld + ~~~ + + 6. Generate an initial zone file like the one below for the dyndns domain in + the location specified in the config file above. + + ~~~ + $TTL 3600 ; 1 hour + @ IN SOA auth.dns.mydomain.tld. hostmaster.mydomain.tld. ( + 2015051401 ; serial + 43200 ; refresh (12 hours) + 3600 ; retry (1 hour) + 86400 ; expire (24 hours) + 900 ; minimum (15 minutes) + ) + TXT "Dynamic DNS zone for mydomain.tld" + + site A 1.2.3.4 + ~~~ + + Please note that Bind will rewrite this file and you need to be careful + with it. Entries do not need to exist initially, as long as the signer/key + has access to a hostname, the entry can be created (so the only thing + required to setup a new host is to register a signer/key). + + If you do need to update the zone file to change entries, consider using + the bind `nsupdate` command instead. If that is inconvenient, the following + steps must be followed not to get our of sync with Bind's zone database + (please note that when you have views this works slightly differently): + + * execute the command `rndc freeze [zone]` + * edit the zone file for [zone] + * execute the command `rndc unfreeze [zone]` + + 7. Last step is to instruct bind to reload it's configuration (`rndc reload`) + and test the setup. please see [below how to invoke the script](#invoking). + + URLs / checks to perform are: + + * <http://myserver.mydomain.tld/dyndns/list?domain=dyndns.mydomain.tld> + to list the entries in the domain (requires zone transfer rights!) + * <http://myserver.mydomain.tld/dyndns/update?host=site.dyndns.mydomain.tld&user=siteuser&key=......> + to add/update a site and + * <http://myserver.mydomain.tld/dyndns/delete?host=site.dyndns.mydomain.tld&user=siteuser&key=......> + to delete (clear) it. + +Please read the section below as well on the configuration and different modes +(operations) available. + +<a name=configuration>Configuration</a> +--------------------------------------- + +At the top of the script is a "Configuration" section, which contains the +configurable options of the scripts. + +Parameter | Description +:----------------+:------------------------------------------------------------- +`$DNSServer` | IP address of the DNS Server to send DNS update requests to +`$ExpandCNAMEs` | Max. CNAME lookups for `$host` (0 to disable), see below +`$AllowDebugKey` | Output debug log after result when `debug` parameter equals this value. Set to '' to always enable and to 'off' to disable debugging +`$AuthMode` | Defines how to authenticate DNS update requests, see below +`$StaticSigner` | Static signer ID to be used for AuthMode `static` or `both` +`$StaticKey` | Static signing key to be used for AuthMode `static` or `both` +`$RequireRR` | Require an existing DNS record of this type to allow updates. +`$ExpireAfter` | Expire time for registrations in minutes, hours, weeks or seconds. Format is number optionally followed by m, h, w, s (seconds is default). +`@ReplaceRR` | List of DNS Record types to remove (clear) as part of update. +`$UpdateTXT` | Add host TXT record during update with this text followed by a timestamp. Used for expiry (so don't change!), leave empty to not add this +`$DeleteTXT` | Set TXT record upon deletion with this text and a timestamp. + +Please note that the values must be correctly quoted, etc. not to break the script. + + +#### CNAME Support +The script supports using separate subdomain (e.g. dyndns.mydomain.tld) for +dynamic DNS and CNAMEs to entries in that subdomain from another zone (e.g. +mydomain.tld). The advantage of such a setup is that only one zone (SOA file) +within the domain will have frequent updates (and hence requires a short TTL +so prevent it from being cached) while the rest of the domain's zones can be +cached. + +The user does not have to notice this at all as script supports check whether +the host provided is a CNAME and if so, performs the request for the actual +hostname instead of the provided one. The value of `$ExpandCNAMEs` determines +the maximum number of CNAME lookups supported (so nesting is allowed and this +limits the level of nesting to prevent loops). +To disable lookups for CNAME expansion, set `$ExpandCNAMEs` to 0. + + +#### Authentication Modes +For signing DNS update requests sent to the DNS server the script supports 3 +ways to obtain the signer and key: + +AuthMode | Description +:--------+:--------------------------------------------------------------------- +*static* | use only static authentication information from `$StaticSigner` and`$StaticKey` (and ignore authentication information provided in the request) +*remote* | use only authentication information provided in the request +*both* | use authentication information provided in the request (fields `user` and `secret`) when provided, otherwise use static values from `$StaticSigner` and `$StaticKey`. Please note that this is checked per parameter + + +Supported Operations +-------------------- +The script can perform the following operations (modes): + +Mode | Description | Required Parameters | Optional Parameters +:------+:-------------------------+:--------------------+:---------------------- +list | Show DDNS domain entries | `domain`__**__ | +view | Show DDNS hostname entry | `host` | +update | Update/add a DDNS host | `host` + auth.__*__ | `ipv4addr`, `ipv6addr` +delete | Remove DDNS registration | `host` + auth.__*__ | +expire | Expire registrations | `domain`__**__ + auth.__*__ + +__*__ Modes that change registrations require authentication, depending on the + value of `$AuthMode` the parameters `user` and `secret` may be required + (`$AuthMode` *remote*) required or optional (`$AuthMode` *both*) + +__**__ in case `domain` is omitted, it will be determined using the `host` + parameter, if provided + + +#### Parameters +The script supports the following parameters (please see the table above for which is needed for what mode): + +Parameter | Description +:---------+:-------------------------------------------------------------------- +`mode` | the action to perform (if not provided as part of the path name) +`domain` | domain for list/expire request, determined from `host` if ommitted +`host` | hostname to act on, expand CNAMEs max. `$ExpandCNAMEs` levels deep +`ip` | alias / shortcut for `ipv4addr` +`ipv4addr`| The IPv4 address to register for the host (update mode only) __*__ +`ipv6addr`| The IPv6 address to register for the host (update mode only) __*__ +`user` | signer of the DNS Update, used for `AuthMode` *remote* and *both* +`key` | key to sign the DNS Update, used for `AuthMode` *remote* and *both* +`debug` | debug key, show debug information if this equals `$AllowDebugKey` + +__*__ in update mode, if `ipv4addr` or `ipv6addr` is not provided with the + request, the CGI variable `$REMOTE_ADDR` (the client address), its value + will be used instead as IPv4/IPv6 address. + + +#### <a name="invoking">Invoking the script</a> +The script is implemented using the perl CGI module so for testing purposes it +can be called from the command line with parameters as arguments, i.e. + + ./dyndns.pl mode=expire domain=mydomain.tld debug=.... + +Which is quite handy for debugging purposes. Please note that the Perl CGI +library sets `$REMOTE_ADDR` to 127.0.0.1 and that the output will always be +the HTML-based result. + +The standard way to use the script is to place it in the cgi-bin folder your +server, which allows it to be called as: + + http://myserver.mydomain.tld/cgi-bin/dyndns.pl?mode=list&domain=mydomain.tld&debug=... + +As per the setup instruction above, there are various ways to make the URL +cleaner, i.e. + + http://myserver.mydomain.tld/dyndns?mode=list&domain=mydomain.tld&debug=... + +The script also supports include the mode variable as part of the location +(using and the CGI variable `$PATH_INFO` to set the mode), i.e. + + http://myserver.mydomain.tld/cgi-bin/dyndns.pl/list?domain=mydomain.tld&debug=... + +When combining the setup would become: + + http://myserver.mydomain.tld/dyndns/list?domain=mydomain.tld&debug=... + +Which is how I use it. + + +Name Server Setup Requirements +------------------------------ +As the script is only translating requests, depends heavily on the setup of the +nameserver. The DNS server (obviously) needs to allow DNS updates. In addition +to the setup described above, please note that: + + * For the modes list and expire to work, the script needs to perform a DNS + zone transfer (AXFR). This must be allowed for the host running the script. + * for each DDNS host, a signer and key must have the rights to change the + entry (one signer/key can be setup to change multiple hosts). + * The expire mode requires a signer and key that can change all DDNS hosts + within the domain. + * The script currently only supports HMAC-MD5 type keys (limitation of the + used Perl Net::DNS library). The keys setup in the nameservers must + therefore be of the same time or authentication won't work. + +The solution scales reasonable well, although adding the keys to the nameserver +configuration is still manual in my setup (but since it does not happen that +often, it's no hassle). This setup has been tested against ISC Bind version 9. + + +<a name="license">License</a> +----------------------------- +This script, documentation and configration examples are free software: you can +redistribute and/or modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, either version 3 of the License, +or (at your option) any later version. + +This script, documenatation and configuration examples are distributed in the +hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied +warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program. If not, download it from <http://www.gnu.org/licenses/>.