2022-01-21 13:38:32 -08:00
|
|
|
|
#!/bin/bash
|
|
|
|
|
#
|
|
|
|
|
# powerdns-tools
|
|
|
|
|
# https://git.stack-source.com/msb/powerdns-tools
|
2022-08-22 13:45:17 -07:00
|
|
|
|
# Copyright (c) 2022 Matthew Saunders Brown <matthewsaundersbrown@gmail.com>
|
|
|
|
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
2022-01-21 13:38:32 -08:00
|
|
|
|
#
|
|
|
|
|
# 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.
|
2024-02-06 10:30:35 -08:00
|
|
|
|
if [ "$USER" != "root" ]; then
|
|
|
|
|
exec sudo -u root $0 $@
|
2022-01-21 13:38:32 -08:00
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Load local config
|
2024-02-02 15:32:31 -08:00
|
|
|
|
# '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.
|
2022-01-21 13:38:32 -08:00
|
|
|
|
if [[ -f /usr/local/etc/pdns.conf ]]; then
|
|
|
|
|
source /usr/local/etc/pdns.conf
|
2024-02-22 15:49:33 -08:00
|
|
|
|
else
|
|
|
|
|
echo "ERROR: Local config missing. You need to install and configure /usr/local/etc/pdns.conf"
|
|
|
|
|
exit
|
2022-01-21 13:38:32 -08:00
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# constants
|
|
|
|
|
|
2022-08-22 13:45:17 -07:00
|
|
|
|
# API URL. Consider putting this behind a proxy with https on the front and then setting api_base_url in pdns.conf
|
2022-01-21 13:38:32 -08:00
|
|
|
|
[[ -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)
|
|
|
|
|
|
2024-02-07 12:08:35 -08:00
|
|
|
|
# Hostname of this server.
|
|
|
|
|
# Should normally just use the server configured hostname.
|
|
|
|
|
[[ -z $hostname ]] && hostname=$(hostname --fqdn)
|
|
|
|
|
|
2022-01-21 13:38:32 -08:00
|
|
|
|
# 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"
|
2024-04-03 10:01:00 -07:00
|
|
|
|
"_dmarc.@|TXT|v=DMARC1; p=none;"
|
2022-01-21 13:38:32 -08:00
|
|
|
|
"mail.@|A|$default_ip"
|
2023-06-19 10:39:35 -07:00
|
|
|
|
"imap.@|CNAME|mail.@"
|
|
|
|
|
"smtp.@|CNAME|mail.@"
|
|
|
|
|
"pop.@|CNAME|mail.@"
|
2022-01-21 13:38:32 -08:00
|
|
|
|
"www.@|CNAME|@")
|
|
|
|
|
|
|
|
|
|
# functions
|
|
|
|
|
|
|
|
|
|
# crude but good enough domain name format validation
|
2023-08-08 13:40:24 -07:00
|
|
|
|
function pdns::validate_domain () {
|
2022-01-21 13:38:32 -08:00
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-23 15:42:06 -07:00
|
|
|
|
# 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
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-21 13:38:32 -08:00
|
|
|
|
# yesno prompt
|
|
|
|
|
#
|
|
|
|
|
# Examples:
|
2023-08-08 13:40:24 -07:00
|
|
|
|
# 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
|
2022-01-21 13:38:32 -08:00
|
|
|
|
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
|
2024-02-07 12:08:35 -08:00
|
|
|
|
while getopts "ha:bcd:e:i:l:m:n:o:p:q:r:s:t:w:xz:" opt ; do
|
2022-01-21 13:38:32 -08:00
|
|
|
|
case "${opt}" in
|
|
|
|
|
h ) # display help and exit
|
2024-02-07 12:08:35 -08:00
|
|
|
|
# set thisfilename=$(basename -- "$0")
|
|
|
|
|
if [[ -n $parent ]]; then
|
|
|
|
|
thisfilename=$parent
|
|
|
|
|
else
|
|
|
|
|
thisfilename=$(basename -- "$0")
|
|
|
|
|
fi
|
2022-01-21 13:38:32 -08:00
|
|
|
|
help
|
|
|
|
|
exit
|
|
|
|
|
;;
|
2024-02-02 15:32:31 -08:00
|
|
|
|
a ) # account
|
|
|
|
|
account=${OPTARG}
|
|
|
|
|
;;
|
2022-01-21 13:38:32 -08:00
|
|
|
|
b ) # bare - create empty zone, do not any default records
|
|
|
|
|
bare=true
|
|
|
|
|
;;
|
2024-02-02 15:32:31 -08:00
|
|
|
|
c ) # csv - output in csv format
|
|
|
|
|
csv=true
|
2023-08-08 13:21:39 -07:00
|
|
|
|
;;
|
|
|
|
|
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
|
2023-08-08 13:40:24 -07:00
|
|
|
|
if ! pdns::validate_domain $zone; then
|
2023-08-08 13:21:39 -07:00
|
|
|
|
echo "ERROR: $zone is not a valid domain name."
|
2024-02-12 10:44:45 -08:00
|
|
|
|
exit 1
|
2022-01-21 13:38:32 -08:00
|
|
|
|
fi
|
|
|
|
|
;;
|
2024-02-02 15:32:31 -08:00
|
|
|
|
e ) # max (integer) – Maximum number of entries to return
|
|
|
|
|
max=${OPTARG}
|
|
|
|
|
;;
|
2023-08-23 15:42:06 -07:00
|
|
|
|
i ) # IP
|
|
|
|
|
ip=${OPTARG}
|
|
|
|
|
if ! pdns::validate_ip $ip; then
|
|
|
|
|
echo "ERROR: $ip is not a valid IP address."
|
|
|
|
|
exit
|
|
|
|
|
fi
|
|
|
|
|
;;
|
2024-02-02 15:32:31 -08:00
|
|
|
|
l ) # ttl - Time To Live
|
|
|
|
|
ttl=${OPTARG}
|
|
|
|
|
;;
|
2022-01-21 13:38:32 -08:00
|
|
|
|
m ) # master - IP of master if this is a slave zone
|
|
|
|
|
master=${OPTARG}
|
|
|
|
|
;;
|
|
|
|
|
n ) # name - hostname for this record
|
|
|
|
|
name=${OPTARG,,}
|
|
|
|
|
;;
|
2024-02-02 15:32:31 -08:00
|
|
|
|
o ) # object_type - Type of data to search for, one of 'all', 'zone', 'record', 'comment'
|
|
|
|
|
object=${OPTARG,,}
|
2022-01-26 13:57:48 -08:00
|
|
|
|
;;
|
2024-02-07 12:08:35 -08:00
|
|
|
|
p ) # parent - name of parent script ("hidden" option used by vdns-* scripts)
|
|
|
|
|
parent=${OPTARG,,}
|
|
|
|
|
;;
|
2024-02-02 15:32:31 -08:00
|
|
|
|
q ) # query - The string to search for
|
|
|
|
|
query=${OPTARG}
|
2022-01-21 13:38:32 -08:00
|
|
|
|
;;
|
2024-02-02 15:32:31 -08:00
|
|
|
|
r ) # record data
|
|
|
|
|
record=${OPTARG}
|
2022-01-21 13:38:32 -08:00
|
|
|
|
;;
|
|
|
|
|
s ) # status - disabled status, 0 for active 1 for inactive, default 0
|
|
|
|
|
status=${OPTARG}
|
|
|
|
|
;;
|
2024-02-02 15:32:31 -08:00
|
|
|
|
t ) # type
|
|
|
|
|
type=${OPTARG^^}
|
|
|
|
|
;;
|
|
|
|
|
w ) # comment - a note about the record
|
|
|
|
|
comment=${OPTARG}
|
|
|
|
|
;;
|
2022-01-21 13:38:32 -08:00
|
|
|
|
x ) # eXecute - don't prompt for confirmation
|
|
|
|
|
execute=true
|
|
|
|
|
;;
|
2024-02-02 15:32:31 -08:00
|
|
|
|
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."
|
2024-02-12 10:44:45 -08:00
|
|
|
|
exit 1
|
2024-02-02 15:32:31 -08:00
|
|
|
|
fi
|
|
|
|
|
;;
|
2022-01-21 13:38:32 -08:00
|
|
|
|
\? )
|
|
|
|
|
echo "Invalid option: $OPTARG"
|
|
|
|
|
exit 1
|
|
|
|
|
;;
|
|
|
|
|
: )
|
|
|
|
|
echo "Invalid option: $OPTARG requires an argument"
|
|
|
|
|
exit 1
|
|
|
|
|
;;
|
|
|
|
|
esac
|
|
|
|
|
done
|
|
|
|
|
shift $((OPTIND-1))
|
|
|
|
|
}
|