// <-- lines starting with // here are comments // lines starting with: // "$ " are showing customary non-root PS1 prompt // "# " are showing customary root PS1 prompt (may or may not require root) // "> " are showing customary PS2 prompt // Check A, AAAA, and TXT records for foo.tmp.balug.org. for all IPs for // all the name servers: for NS in $(dig +short balug.org. NS) do for IP in $(dig +short "$NS" A "$NS" AAAA) do dig @"$IP" +noall +answer \ foo.tmp.balug.org. IN A \ foo.tmp.balug.org. IN AAAA \ foo.tmp.balug.org. IN TXT \ | sed -e ' s/[ \t]\{1,\}/ /g s/$/; '"@$IP $NS"/ done done // or more compactly as single line and in a subshell: (for NS in $(dig +short balug.org. NS); do for IP in $(dig +short "$NS" A "$NS" AAAA); do dig @"$IP" +noall +answer foo.tmp.balug.org. IN A foo.tmp.balug.org. IN AAAA foo.tmp.balug.org. IN TXT | sed -e 's/[ \t]\{1,\}/ /g;s/$/; '"@$IP $NS"/; done; done) // So, first we see what we have of those records: $ (for NS in $(dig +short balug.org. NS); do for IP in $(dig +short "$NS" A "$NS" AAAA); do dig @"$IP" +noall +answer foo.tmp.balug.org. IN A foo.tmp.balug.org. IN AAAA foo.tmp.balug.org. IN TXT | sed -e 's/[ \t]\{1,\}/ /g;s/$/; '"@$IP $NS"/; done; done) // And we see we have none. // Then we use DDNS to add a record: # echo -e 'update add foo.tmp.balug.org. 30 IN TXT "foo"\nsend' | nsupdate -l # // and recheck almost immediately after: $ (for NS in $(dig +short balug.org. NS); do for IP in $(dig +short "$NS" A "$NS" AAAA); do dig @"$IP" +noall +answer foo.tmp.balug.org. IN A foo.tmp.balug.org. IN AAAA foo.tmp.balug.org. IN TXT | sed -e 's/[ \t]\{1,\}/ /g;s/$/; '"@$IP $NS"/; done; done) foo.tmp.balug.org. 30 IN TXT "foo"; @192.147.248.10 nsx.sunnyside.com. foo.tmp.balug.org. 30 IN TXT "foo"; @50.242.105.52 nsx.sunnyside.com. foo.tmp.balug.org. 30 IN TXT "foo"; @2001:1890:1672:1a00:8192:147:248:10 nsx.sunnyside.com. foo.tmp.balug.org. 30 IN TXT "foo"; @96.95.217.99 ns1.linuxmafia.com. foo.tmp.balug.org. 30 IN TXT "foo"; @96.86.170.229 ns0.balug.org. foo.tmp.balug.org. 30 IN TXT "foo"; @2001:470:1f05:19e::2 ns0.balug.org. // And we see that each IP for each name server has the added record. // Or to simply check our default name server for TXT record there: $ dig +noall +answer foo.tmp.balug.org. TXT foo.tmp.balug.org. 30 IN TXT "foo" $ // We then remove the record ... in fact all TXT records for that domain: # echo -e 'update del foo.tmp.balug.org. IN TXT\nsend' | nsupdate -l # // And we recheck and see none there: $ dig +noall +answer foo.tmp.balug.org. TXT $ // Let's look at a user that has more limited access to make changes via // sudo: $ id; sudo -l | fgrep nsupdate uid=24567(piberk) gid=24567(piberk) groups=24567(piberk) (piberk : bind) NOPASSWD: /usr/bin/nsupdate -l -k /var/cache/bind/keys/ddns-key.pi.berkeleylug.com $ // First we check for any TXT records for txt.pi.berkeleylug.com.: $ dig +noall +answer txt.pi.berkeleylug.com. TXT $ // And find none. // Then we add one via the user's sudo access, // here the sudo access is used to be able to read the key via group bind // if the user had direct access to the key, sudo wouldn't even be needed, // but here we didn't even give the user direct access to read the key: $ echo -e 'update add txt.pi.berkeleylug.com. 30 IN TXT "txt"\nsend' | sudo -g bind /usr/bin/nsupdate -l -k /var/cache/bind/keys/ddns-key.pi.berkeleylug.com $ // And we can then check that it was added: $ dig +noall +answer txt.pi.berkeleylug.com. TXT txt.pi.berkeleylug.com. 30 IN TXT "txt" $ // Note however, we limited this key - notably it can do stuff with // pi.bekerleylug.com. and subdomains thereof, but not elsewhere within // the same zone, e.g. this attempt fails as the restrictions are intended:: $ echo -e 'update add txt.berkeleylug.com. 30 IN TXT "txt"\nsend' | sudo -g bind /usr/bin/nsupdate -l -k /var/cache/bind/keys/ddns-key.pi.berkeleylug.com update failed: REFUSED $ // We can again delete the record we added earlier: $ echo -e 'update del txt.pi.berkeleylug.com. 30 IN TXT "txt"\nsend' | sudo -g bind /usr/bin/nsupdate -l -k /var/cache/bind/keys/ddns-key.pi.berkeleylug.com $ // And see that it's no longer there: $ dig +noall +answer txt.pi.berkeleylug.com. TXT $ // We can also do conditional actions. // We can see/check that at present we've got no resource records (RRs) // at or under foo.tmp/balug.org.: $ dig foo.tmp.balug.org. | fgrep NX ;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 23050 $ // Let's conditionally add an A record only if foo.tmp.balug.org. isn't // NXDOMAIN: # nsupdate -l << \__EOT__ > prereq yxdomain foo.tmp.balug.org. > update add foo.tmp.balug.org. 30 IN A 127.0.0.1 > send > __EOT__ update failed: NXDOMAIN # // Since it doesn't exist, it fails our prerequisite check, hence isn't added, and we can see that also in checking: $ dig foo.tmp.balug.org. | fgrep NX ;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 51875 $ // Now let's change our prerequisite to it not existing (NXDOMAIN): # nsupdate -l << \__EOT__ > prereq nxdomain foo.tmp.balug.org. > update add foo.tmp.balug.org. 30 IN A 127.0.0.1 > send > __EOT__ # // And now we check and see it there. $ dig +noall +answer foo.tmp.balug.org. A foo.tmp.balug.org. 30 IN A 127.0.0.1 $ // Let's do another conditional, where all the A records for that // domain are only and exactly 127.0.0.1, if so we add 127.0.0.2: # nsupdate -l << \__EOT__ > prereq yxrrset foo.tmp.balug.org. IN A 127.0.0.1 > update add foo.tmp.balug.org. 30 IN A 127.0.0.2 > send > __EOT__ # // And checking, we find the added record: $ dig +noall +answer foo.tmp.balug.org. A | sort foo.tmp.balug.org. 30 IN A 127.0.0.1 foo.tmp.balug.org. 30 IN A 127.0.0.2 $ // Let's try similar again, adding 127.0.0.3: # nsupdate -l << \__EOT__ > prereq yxrrset foo.tmp.balug.org. IN A 127.0.0.1 > update add foo.tmp.balug.org. 30 IN A 127.0.0.3 > send > __EOT__ update failed: NXRRSET # // That failed, as we didn't match the full set: $ dig +noall +answer foo.tmp.balug.org. A | sort foo.tmp.balug.org. 30 IN A 127.0.0.1 foo.tmp.balug.org. 30 IN A 127.0.0.2 $ // Let's try again with full match to the existing set: # nsupdate -l << \__EOT__ > prereq yxrrset foo.tmp.balug.org. IN A 127.0.0.1 > prereq yxrrset foo.tmp.balug.org. IN A 127.0.0.2 > update add foo.tmp.balug.org. 30 IN A 127.0.0.3 > send > __EOT__ # // And checking, we see they're now all there: $ dig +noall +answer foo.tmp.balug.org. A | sort foo.tmp.balug.org. 30 IN A 127.0.0.1 foo.tmp.balug.org. 30 IN A 127.0.0.2 foo.tmp.balug.org. 30 IN A 127.0.0.3 $ // We can also do a less restrictive check, just that some record(s) of // the requisite type exist: # nsupdate -l << \__EOT__ > prereq yxrrset foo.tmp.balug.org. IN A > update add foo.tmp.balug.org. 30 IN A 127.0.0.4 > send > __EOT__ # // And we can see we added the record: $ dig +noall +answer foo.tmp.balug.org. A | sort foo.tmp.balug.org. 30 IN A 127.0.0.1 foo.tmp.balug.org. 30 IN A 127.0.0.2 foo.tmp.balug.org. 30 IN A 127.0.0.3 foo.tmp.balug.org. 30 IN A 127.0.0.4 $ // We can also stack our requests for the same zone, before we have: $ dig +noall +answer foo.tmp.balug.org. A foo.tmp.balug.org. AAAA foo.tmp.balug.org. TXT | sort foo.tmp.balug.org. 30 IN A 127.0.0.1 foo.tmp.balug.org. 30 IN A 127.0.0.2 foo.tmp.balug.org. 30 IN A 127.0.0.3 foo.tmp.balug.org. 30 IN A 127.0.0.4 $ # nsupdate -l << \__EOT__ > update del foo.tmp.balug.org. 30 IN A 127.0.0.4 > update add foo.tmp.balug.org. 30 IN TXT "127.0.0.4" > update del foo.tmp.balug.org. 30 IN A 127.0.0.2 > update add foo.tmp.balug.org. 30 IN TXT "127.0.0.2" > send > __EOT__ # // And we can see we made our requested changes: $ dig +noall +answer foo.tmp.balug.org. A foo.tmp.balug.org. AAAA foo.tmp.balug.org. TXT | sort foo.tmp.balug.org. 30 IN A 127.0.0.1 foo.tmp.balug.org. 30 IN A 127.0.0.3 foo.tmp.balug.org. 30 IN TXT "127.0.0.2" foo.tmp.balug.org. 30 IN TXT "127.0.0.4" $ // And we can clean it up all at once, removing all RRs for foo.tmp.balug.org.: # nsupdate -l << \__EOT__ > update del foo.tmp.balug.org. > send > __EOT__ # // And we can see there's nothing left there: $ dig foo.tmp.balug.org. | fgrep NX ;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 7861 $