#!/bin/bash # # powerdns-tools # https://git.stack-source.com/msb/powerdns-tools # Copyright (c) 2022 Matthew Saunders Brown # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # # powerdns-tools include file, used by other powerdns-tools bash scripts # Must be root, attempt sudo if need be. root is not actually required for the API commands, but we want to restrict access. if [ "$USER" != "root" ]; then exec sudo -u root $0 $@ fi # Load local config # 'api_key' & 'dns_domain' must be set in the local config. # If 'account' is set in the local config it is set for all 'zone' & 'comment' PDNS entries. # 'account' can also be set/overridden on command line. # Any of the other constants below can be overriden in the local config. if [[ -f /usr/local/etc/pdns.conf ]]; then source /usr/local/etc/pdns.conf else echo "ERROR: Local config missing. You need to install and configure /usr/local/etc/pdns.conf" exit fi # constants # API URL. Consider putting this behind a proxy with https on the front and then setting api_base_url in pdns.conf [[ -z $api_base_url ]] && api_base_url=http://127.0.0.1:8081/api/v1/servers/localhost # Default IP address to use for new zone records # Defaults to IP of server script is run from [[ -z $default_ip ]] && default_ip=$(hostname --ip-address) # Hostname of this server. # Should normally just use the server configured hostname. [[ -z $hostname ]] && hostname=$(hostname --fqdn) # Array of allowed Resource Record Types [[ -z $rr_types ]] && declare -a rr_types=(A AAAA CNAME MX NS PTR SRV TXT) # Minimum/maximum values for SOA and RR records [[ -z $min_ttl ]] && min_ttl=300 [[ -z $max_ttl ]] && max_ttl=2419200 [[ -z $min_refresh ]] && min_refresh=300 [[ -z $min_retry ]] && min_retry=300 [[ -z $min_expire ]] && min_expire=86400 # Default values for new zones. [[ -z $zone_default_ns ]] && zone_default_ns="ns1.$dns_domain" [[ -z $zone_defaults_mbox ]] && zone_defaults_mbox="hostmaster.$dns_domain" [[ -z $zone_defaults_ttl ]] && zone_defaults_ttl='3600' [[ -z $zone_defaults_refresh ]] && zone_defaults_refresh='86400' [[ -z $zone_defaults_retry ]] && zone_defaults_retry='7200' [[ -z $zone_defaults_expire ]] && zone_defaults_expire='1209600' [[ -z $zone_defaults_minimum ]] && zone_defaults_minimum='3600' readonly zone_defaults_pri='0' # zone_defaults_pri must be 0, do not change # The following array specifies default records for new zone records. # These get inserted automatically whenever a zone is created. # The format of each record is (name|type|content). TTL is taken from defaults above. # @ will be replace with the zone (domain name) # TXT records will get quoted (don't add quotes around the content field here) # Only MX & SRV records use priority, and it should be specified as part of the content/data field. [[ -z $default_records ]] && declare -a default_records=("@|A|$default_ip" "@|MX|10 mail.@" "@|NS|ns1.$dns_domain" "@|NS|ns2.$dns_domain" "@|NS|ns3.$dns_domain" "@|TXT|v=spf1 a mx -all" "_dmarc.@|TXT|v=DMARC1; p=none;" "mail.@|A|$default_ip" "imap.@|CNAME|mail.@" "smtp.@|CNAME|mail.@" "pop.@|CNAME|mail.@" "www.@|CNAME|@") # functions # crude but good enough domain name format validation function pdns::validate_domain () { local my_domain=$1 if [[ $my_domain =~ ^(([a-zA-Z0-9](-?[a-zA-Z0-9])*)\.)+[a-zA-Z]{2,}\.?$ ]] ; then return 0 else return 1 fi } # crude but good enough IP address format validation function pdns::validate_ip() { local ip=$1 local status=1 if [[ $ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then OIFS=$IFS IFS='.' ip=($ip) IFS=$OIFS [[ ${ip[0]} -le 255 && ${ip[1]} -le 255 && ${ip[2]} -le 255 && ${ip[3]} -le 255 ]] status=$? fi return $status } # yesno prompt # # Examples: # loop until y or n: if pdns::yesno "Continue?"; then # default y: if pdns::yesno "Continue?" Y; then # default n: if pdns::yesno "Continue?" N; then function pdns::yesno() { local prompt default reply if [ "${2:-}" = "Y" ]; then prompt="Y/n" default=Y elif [ "${2:-}" = "N" ]; then prompt="y/N" default=N else prompt="y/n" default= fi while true; do read -p "$1 [$prompt] " -n 1 -r reply # Default? if [ -z "$reply" ]; then reply=$default fi # Check if the reply is valid case "$reply" in Y*|y*) return 0 ;; N*|n*) return 1 ;; esac done } function pdns:getoptions () { local OPTIND while getopts "ha:bcd:e:i:l:m:n:o:p:q:r:s:t:w:xz:" opt ; do case "${opt}" in h ) # display help and exit # set thisfilename=$(basename -- "$0") if [[ -n $parent ]]; then thisfilename=$parent else thisfilename=$(basename -- "$0") fi help exit ;; a ) # account account=${OPTARG} ;; b ) # bare - create empty zone, do not any default records bare=true ;; c ) # csv - output in csv format csv=true ;; d ) # domain (alias of zone) zone=${OPTARG,,} # pdns stores zone name without trailing dot, remove if found if [[ ${zone: -1} = '.' ]]; then zone=${zone::-1} fi if ! pdns::validate_domain $zone; then echo "ERROR: $zone is not a valid domain name." exit 1 fi ;; e ) # max (integer) – Maximum number of entries to return max=${OPTARG} ;; i ) # IP ip=${OPTARG} if ! pdns::validate_ip $ip; then echo "ERROR: $ip is not a valid IP address." exit fi ;; l ) # ttl - Time To Live ttl=${OPTARG} ;; m ) # master - IP of master if this is a slave zone master=${OPTARG} ;; n ) # name - hostname for this record name=${OPTARG,,} ;; o ) # object_type - Type of data to search for, one of 'all', 'zone', 'record', 'comment' object=${OPTARG,,} ;; p ) # parent - name of parent script ("hidden" option used by vdns-* scripts) parent=${OPTARG,,} ;; q ) # query - The string to search for query=${OPTARG} ;; r ) # record data record=${OPTARG} ;; s ) # status - disabled status, 0 for active 1 for inactive, default 0 status=${OPTARG} ;; t ) # type type=${OPTARG^^} ;; w ) # comment - a note about the record comment=${OPTARG} ;; x ) # eXecute - don't prompt for confirmation execute=true ;; z ) # zone zone=${OPTARG,,} # pdns stores zone name without trailing dot, remove if found if [[ ${zone: -1} = '.' ]]; then zone=${zone::-1} fi if ! pdns::validate_domain $zone; then echo "ERROR: $zone is not a valid domain name." exit 1 fi ;; \? ) echo "Invalid option: $OPTARG" exit 1 ;; : ) echo "Invalid option: $OPTARG requires an argument" exit 1 ;; esac done shift $((OPTIND-1)) }