aboutsummaryrefslogtreecommitdiff
path: root/src/usr/bin
diff options
context:
space:
mode:
Diffstat (limited to 'src/usr/bin')
-rwxr-xr-xsrc/usr/bin/dhcpd-control478
-rwxr-xr-xsrc/usr/bin/updatezone434
2 files changed, 912 insertions, 0 deletions
diff --git a/src/usr/bin/dhcpd-control b/src/usr/bin/dhcpd-control
new file mode 100755
index 0000000..7ee4e0a
--- /dev/null
+++ b/src/usr/bin/dhcpd-control
@@ -0,0 +1,478 @@
+#!/bin/sh
+# Filename: dhcpd-control.sh
+# Location:
+# Author: bgstack15@gmail.com
+# Startdate: 2017-05-28 18:18:46
+# Title: Script that Facilitates the Configuration of DHCPD Across a Server Pair
+# Purpose: Provides a single command for would take a series of steps
+# Package: ddtools
+# History:
+# 2024-08-19 fix framework path
+# Usage:
+# Reference: ftemplate.sh 2017-05-24a; framework.sh 2017-05-24a
+# order of dhcpd servers to restart https://kb.isc.org/article/AA-01043/0/Recommendations-for-restarting-a-DHCP-failover-pair.html
+# merge lines with sed http://www.linuxquestions.org/questions/programming-9/merge-lines-in-a-file-using-sed-191121/
+# Improve:
+# provide mechanisms for non-systemd service control
+# provide ways to specify what to see in the --list output
+# list leases on both servers
+# Dependencies:
+# systemd
+fiversion="2017-05-24a"
+dhcpdcontrolversion="2024-08-19a"
+
+usage() {
+ less -F >&2 <<ENDUSAGE
+usage: dhcpd-control.sh [-duV] [ --flush | --edit | --edit-local | --edit-other | --remove-mac <mac> | --list ] [ --force ]
+version ${dhcpdcontrolversion}
+ -d debug Show debugging info, including parsed variables.
+ -u usage Show this usage block.
+ -V version Show script version number.
+ --flush Clears all current leases
+ --edit Edit the combined file-- the one shared by both servers.
+ --edit-local Edit the local file.
+ --edit-other Edit the other server dhcpd file.
+ --remove-mac <MAC> Clears the leases for this MAC address.
+ --list List current leases.
+Return values:
+0 Normal
+1 Help or version info displayed
+2 Count or type of flaglessvals is incorrect
+3 Incorrect OS type
+4 Unable to find dependency
+5 Not run as root or sudo
+ENDUSAGE
+}
+
+# DEFINE FUNCTIONS
+
+# DEFINE TRAPS
+
+clean_dhcpdcontrol() {
+ {
+ rm -f ${tmp_dhcpd_combined_file} ${tmp_dhcpd_local_file} ${tmp_dhcpd_other_file} ${tmp_mac_local_file} ${tmp_macless_local_file} ${tmp_leases_other_file} ${tmp_mac_other_file} ${tmp_macless_other_file}
+ } 1>/dev/null 2>&1
+ #use at end of entire script if you need to clean up tmpfiles
+}
+
+CTRLC() {
+ #trap "CTRLC" 2
+ clean_dhcpdcontrol
+ #useful for controlling the ctrl+c keystroke
+}
+
+CTRLZ() {
+ #trap "CTRLZ" 18
+ [ ] #useful for controlling the ctrl+z keystroke
+}
+
+parseFlag() {
+ flag="$1"
+ hasval=0
+ case ${flag} in
+ # INSERT FLAGS HERE
+ "d" | "debug" | "DEBUG" | "dd" ) setdebug; ferror "debug level ${debug}";;
+ "u" | "usage" | "help" | "h" ) usage; exit 1;;
+ "V" | "fcheck" | "version" ) ferror "${scriptfile} version ${dhcpdcontrolversion}"; exit 1;;
+ #"i" | "infile" | "inputfile" ) getval;infile1=${tempval};;
+ "f" | "force" ) DHCPD_CONTROL_FORCE=1;;
+ "flush" ) action="flush";;
+ "edit-local" ) action="edit-local";;
+ "edit-other" ) action="edit-other";;
+ "edit" ) action="edit";;
+ "remove-mac" ) getval; DHCPD_CONTROL_MAC_TO_REMOVE="${tempval}"; action="remove-mac";;
+ "list" ) action="list";;
+ esac
+
+ debuglev 10 && { test ${hasval} -eq 1 && ferror "flag: ${flag} = ${tempval}" || ferror "flag: ${flag}"; }
+}
+
+# DETERMINE LOCATION OF FRAMEWORK
+while read flocation; do if test -x ${flocation} && test "$( ${flocation} --fcheck )" -ge 20170524; then frameworkscript="${flocation}"; break; fi; done <<EOFLOCATIONS
+./framework.sh
+${scriptdir}/framework.sh
+~/bin/bgscripts/framework.sh
+~/bin/framework.sh
+~/bgscripts/framework.sh
+~/framework.sh
+/usr/local/bin/bgscripts/framework.sh
+/usr/local/bin/framework.sh
+/usr/bin/bgscripts/framework.sh
+/usr/bin/framework.sh
+/bin/bgscripts/framework.sh
+/usr/share/bgscripts/framework.sh
+/usr/libexec/bgscripts/framework.sh
+EOFLOCATIONS
+test -z "${frameworkscript}" && echo "$0: framework not found. Aborted." 1>&2 && exit 4
+
+# INITIALIZE VARIABLES
+# variables set in framework:
+# today server thistty scriptdir scriptfile scripttrim
+# is_cronjob stdin_piped stdout_piped stderr_piped sendsh sendopts
+. ${frameworkscript} || echo "$0: framework did not run properly. Continuing..." 1>&2
+infile1=
+outfile1=
+logfile=${scriptdir}/${scripttrim}.${today}.out
+action=""
+define_if_new interestedparties "bgstack15@gmail.com"
+# SIMPLECONF
+#define_if_new default_conffile "/etc/sysconfig/dhcpd-control"
+define_if_new default_conffile "/etc/sysconfig/dhcpd-control"
+#define_if_new defuser_conffile ~/.config/dhcpdcontrol/dhcpdcontrol.conf
+define_if_new EDITOR vi
+
+# REACT TO OPERATING SYSTEM TYPE
+case $( uname -s ) in
+ Linux) [ ];;
+ FreeBSD) [ ];;
+ *) echo "${scriptfile}: 3. Indeterminate OS: $( uname -s )" 1>&2 && exit 3;;
+esac
+
+## REACT TO ROOT STATUS
+#case ${is_root} in
+# 1) # proper root
+# [ ] ;;
+# sudo) # sudo to root
+# [ ] ;;
+# "") # not root at all
+# #ferror "${scriptfile}: 5. Please run as root or sudo. Aborted."
+# #exit 5
+# [ ]
+# ;;
+#esac
+
+# SET CUSTOM SCRIPT AND VALUES
+#setval 1 sendsh sendopts<<EOFSENDSH # if $1="1" then setvalout="critical-fail" on failure
+#/usr/share/bgscripts/send.sh -hs # setvalout maybe be "fail" otherwise
+#/usr/local/bin/send.sh -hs # on success, setvalout="valid-sendsh"
+#/usr/bin/mail -s
+#EOFSENDSH
+#test "${setvalout}" = "critical-fail" && ferror "${scriptfile}: 4. mailer not found. Aborted." && exit 4
+
+# VALIDATE PARAMETERS
+# objects before the dash are options, which get filled with the optvals
+# to debug flags, use option DEBUG. Variables set in framework: fallopts
+validateparams - "$@"
+
+# CONFIRM TOTAL NUMBER OF FLAGLESSVALS IS CORRECT
+#if test ${thiscount} -lt 2;
+#then
+# ferror "${scriptfile}: 2. Fewer than 2 flaglessvals. Aborted."
+# exit 2
+#fi
+
+# CONFIGURE VARIABLES AFTER PARAMETERS
+
+## LOAD CONFIG FROM SIMPLECONF
+## This section follows a simple hierarchy of precedence, with first being used:
+## 1. parameters and flags
+## 2. environment
+## 3. config file
+## 4. default user config: ~/.config/script/script.conf
+## 5. default config: /etc/script/script.conf
+#if test -f "${conffile}";
+#then
+# get_conf "${conffile}"
+#else
+# if test "${conffile}" = "${default_conffile}" || test "${conffile}" = "${defuser_conffile}"; then :; else ferror "${scriptfile}: Ignoring conf file which is not found: ${conffile}."; fi
+#fi
+#test -f "${defuser_conffile}" && get_conf "${defuser_conffile}"
+test -f "${default_conffile}" && get_conf "${default_conffile}"
+
+## START READ CONFIG FILE TEMPLATE
+#oIFS="${IFS}"; IFS="$( printf '\n' )"
+#infiledata=$( ${sed} ':loop;/^\/\*/{s/.//;:ccom;s,^.[^*]*,,;/^$/n;/^\*\//{s/..//;bloop;};bccom;}' "${infile1}") #the crazy sed removes c style multiline comments
+#IFS="${oIFS}"; infilelines=$( echo "${infiledata}" | wc -l )
+#{ echo "${infiledata}"; echo "ENDOFFILE"; } | {
+# while read line; do
+# # the crazy sed removes leading and trailing whitespace, blank lines, and comments
+# if test ! "${line}" = "ENDOFFILE";
+# then
+# line=$( echo "${line}" | sed -e 's/^\s*//;s/\s*$//;/^[#$]/d;s/\s*[^\]#.*$//;' )
+# if test -n "${line}";
+# then
+# debuglev 8 && ferror "line=\"${line}\""
+# if echo "${line}" | grep -qiE "\[.*\]";
+# then
+# # new zone
+# zone=$( echo "${line}" | tr -d '[]' )
+# debuglev 7 && ferror "zone=${zone}"
+# else
+# # directive
+# varname=$( echo "${line}" | awk -F= '{print $1}' )
+# varval=$( echo "${line}" | awk -F= '{$1=""; printf "%s", $0}' | sed 's/^ //;' )
+# debuglev 7 && ferror "${zone}${varname}=\"${varval}\""
+# # simple define variable
+# eval "${zone}${varname}=\${varval}"
+# fi
+# ## this part is untested
+# #read -p "Please type something here:" response < ${thistty}
+# #echo "${response}"
+# fi
+# else
+
+## REACT TO BEING A CRONJOB
+#if test ${is_cronjob} -eq 1;
+#then
+# [ ]
+#else
+# [ ]
+#fi
+
+# SET TRAPS
+trap "CTRLC" 2
+#trap "CTRLZ" 18
+trap "clean_dhcpdcontrol" 0
+
+# MAIN LOOP
+#{
+
+ # use DHCPD_CONTROL_COMBINED_FILE and DHCPD_CONTROL_DHCPD_FILE
+
+ # derive if primary or secondary server
+ is_primary="$( sed -n -r -e '/failover.*\{/,/\}/p' ${DHCPD_CONTROL_DHCPD_FILE} | grep -iE "^\s*primary" )"
+ if ! test -n "${is_primary}";
+ then
+ if ! fistruthy "${DHCPD_CONTROL_FORCE}";
+ then
+ ferror "${scriptfile}: 4. Canot determine that this is the primary server. Try --force option. Aborted."
+ exit 4
+ fi
+ fi
+
+ # Derive secondary server for later actions
+ define_if_new DHCPD_CONTROL_OTHER_SERVER "$( grep -oiE "peer address [0-9.]{7,15}\s*;" "${DHCPD_CONTROL_DHCPD_FILE}" | tr -dc '[0-9.]' )"
+
+ # DEBUG SIMPLECONF
+ debuglev 5 && {
+ ferror "Using values"
+ # used values: EX_(OPT1|OPT2|VERBOSE)
+ set | grep -iE "^DHCPD_CONTROL_" 1>&2
+ }
+
+ update_conf_local=0
+ update_leases_local=0
+ restart_service_local=0
+ update_conf_other=0
+ update_leases_other=0
+ restart_service_other=0
+ debuglev 8 && ferror "BEGIN action ${action}"
+ case "${action}" in
+
+ "flush")
+ debuglev 8 && ferror "BEGIN flush"
+ # Clear temorary leases file
+ debuglev 4 && ferror "Flushing all leases"
+ if test -z "${DHCPD_CONTROL_LEASES_TEMP_FILE}";
+ then
+ ferror "Skipping leases temp file. Variable not defined: DHCPD_CONTROL_LEASES_TEMP_FILE."
+ else
+ if test -f "${DHCPD_CONTROL_LEASES_TEMP_FILE}";
+ then
+ case "${DHCPD_CONTROL_LEASES_TEMP_FILE}" in
+ /var/lib/dhcp*)
+ systemctl stop "${DHCPD_CONTROL_SERVICE}"
+ rm -f "${DHCPD_CONTROL_LEASES_TEMP_FILE}"
+ update_leases_other=1;
+ restart_service_other=1;
+ restart_service_local=1;
+ ;;
+ *)
+ ferror "Will not delete unsafe leases temp file ${DHCPD_CONTROL_LEASES_TEMP_FILE}."
+ ;;
+ esac
+ fi
+ fi
+ # Clear leases file
+ if test -z "${DHCPD_CONTROL_LEASES_FILE}";
+ then
+ ferror "Skipping leases file. Variable not defined: DHCPD_CONTROL_LEASES_FILE."
+ else
+ if test -f "${DHCPD_CONTROL_LEASES_FILE}";
+ then
+ case "${DHCPD_CONTROL_LEASES_FILE}" in
+ /var/lib/dhcp*)
+ systemctl stop "${DHCPD_CONTROL_SERVICE}"
+ printf "" > "${DHCPD_CONTROL_LEASES_FILE}"
+ update_leases_other=1;
+ restart_service_other=1;
+ restart_service_local=1;
+ ;;
+ *)
+ ferror "Will not clear unsafe leases file ${DHCPD_CONTROL_LEASES_FILE}."
+ ;;
+ esac
+ fi
+ fi
+ ;;
+
+ "edit")
+ debuglev 8 && ferror "BEGIN edit"
+ # prepare temp file
+ tmp_dhcpd_combined_file="$( mktemp -p /tmp dhcpd.combined.XXXXX )"
+ cp -p "${DHCPD_CONTROL_COMBINED_FILE}" "${tmp_dhcpd_combined_file}"
+ # edit file
+ $EDITOR "${tmp_dhcpd_combined_file}"
+ # if change occurred, prepare to replace
+ if ! cmp -s "${DHCPD_CONTROL_COMBINED_FILE}" "${tmp_dhcpd_combined_file}";
+ then
+ debuglev 1 && ferror "Updating dhcpd combined file."
+ bup "${DHCPD_CONTROL_COMBINED_FILE}"
+ cp -p "${tmp_dhcpd_combined_file}" "${DHCPD_CONTROL_COMBINED_FILE}"
+ update_conf_other=1
+ restart_service_local=1
+ restart_service_other=1
+ fi
+ ;;
+
+ "edit-local")
+ debuglev 8 && ferror "BEGIN edit-local"
+ # prepare temp file
+ tmp_dhcpd_local_file="$( mktemp -p /tmp dhcpd.XXXXX )"
+ cp -p "${DHCPD_CONTROL_DHCPD_FILE}" "${tmp_dhcpd_local_file}"
+ $EDITOR "${tmp_dhcpd_local_file}"
+ # if change occurred, prepare to replace
+ if ! cmp -s "${DHCPD_CONTROL_DHCPD_FILE}" "${tmp_dhcpd_local_file}";
+ then
+ debuglev 1 && ferror "Updating local dhcpd file."
+ bup "${DHCPD_CONTROL_DHCPD_FILE}"
+ cp -p "${tmp_dhcpd_local_file}" "${DHCPD_CONTROL_DHCPD_FILE}"
+ restart_service_local=1
+ fi
+ ;;
+
+ "edit-other")
+ debuglev 8 && ferror "BEGIN edit-other"
+ tmp_dhcpd_other_file="$( mktemp -p /tmp dhcpd.other.XXXXX )"
+ scp -p "${DHCPD_CONTROL_OTHER_SERVER}:${DHCPD_CONTROL_DHCPD_FILE}" "${tmp_dhcpd_other_file}"
+ cp -p "${tmp_dhcpd_other_file}" "${tmp_dhcpd_other_file}8" #arbitrary number # edit file
+ ${EDITOR} "${tmp_dhcpd_other_file}8"
+ if ! cmp -s "${tmp_dhcpd_other_file}" "${tmp_dhcpd_other_file}8";
+ then
+ debuglev 1 && ferror "Updating other server dhcpd file."
+ ssh "${DHCPD_CONTROL_OTHER_SERVER}" bup "${DHCPD_CONTROL_DHCPD_FILE}";
+ scp -p "${tmp_dhcpd_other_file}8" "${DHCPD_CONTROL_OTHER_SERVER}:${DHCPD_CONTROL_DHCPD_FILE}";
+ restart_service_other=1
+ fi
+ ;;
+
+ "remove-mac")
+ debuglev 8 && ferror "BEGIN remove-mac"
+ # working on this
+ # sed -n -r -e '/^lease.*\{/,/^\}/{/^lease|hardware|\}/{p}}' /tmp/foo1 | sed -e ':a;/\}/!{N;s/\n/ /;ba};' # base form
+ # sed -n -r -e '/^lease.*\{/,/^\}/{p}' /tmp/foo1 | sed -e ':a;/\}/!{N;s/\n/ /;ba};' -e 's/\s\+/ /g;' # slightly trimmed
+ # sed -n -r -e '/\{/,/^\}/{p}' /tmp/foo1 | sed -e ':a;/\}/!{N;s/\n/ /;ba};' -e 's/\s\+/ /g;' | grep -iE "ec:9a:74:48:bc:c4" # find the one mac address
+
+ # prepare temp files
+ tmp_mac_local_file="$( mktemp -p /tmp leases.mac.XXXXX )"
+ tmp_macless_local_file="$( mktemp -p /tmp leases.macless.XXXXX )"
+ tmp_leases_other_file="$( mktemp -p /tmp leases.other.XXXXX )"
+ tmp_mac_other_file="$( mktemp -p /tmp leases.mac.XXXXX )"
+ tmp_macless_other_file="$( mktemp -p /tmp leases.macless.XXXXX )"
+ if test -z "${DHCPD_CONTROL_MAC_TO_REMOVE}";
+ then
+ ferror "${scripttrim}: 2. No MAC address provided. aborted."
+ exit 2
+ fi
+
+ scp -p "${DHCPD_CONTROL_OTHER_SERVER}:${DHCPD_CONTROL_LEASES_FILE}" "${tmp_leases_other_file}"
+
+ # prepare list of local leases to clear
+ sed -n -r -e '/\{/,/^\}/{p}' "${DHCPD_CONTROL_LEASES_FILE}" | sed -e ':a;/\}/!{N;s/\n/ /;ba};' -e 's/\s\+/ /g;' | grep -iE "${DHCPD_CONTROL_MAC_TO_REMOVE}" > "${tmp_mac_local_file}"
+ # prepare list of other leases to clear
+ sed -n -r -e '/\{/,/^\}/{p}' "${tmp_leases_other_file}" | sed -e ':a;/\}/!{N;s/\n/ /;ba};' -e 's/\s\+/ /g;' | grep -iE "${DHCPD_CONTROL_MAC_TO_REMOVE}" > "${tmp_mac_other_file}"
+
+ # remove leases from this dhcpd server
+ if test -n "$( cat "${tmp_mac_local_file}" )";
+ then
+ ferror "Removing leases from this dhcpd server:"
+ cat "${tmp_mac_local_file}" 1>&2
+ fi
+ sed -n -r -e '/\{/,/^\}/{p}' "${DHCPD_CONTROL_LEASES_FILE}" | sed -e ':a;/\}/!{N;s/\n/ /;ba};' -e 's/\s\+/ /g;' | grep -viE "${DHCPD_CONTROL_MAC_TO_REMOVE}" > "${tmp_macless_local_file}"
+ if ! cmp -s "${tmp_macless_local_file}" "${DHCPD_CONTROL_LEASES_FILE}"
+ then
+ systemctl stop "${DHCPD_CONTROL_SERVICE}"
+ cp -p "${tmp_macless_local_file}" "${DHCPD_CONTROL_LEASES_FILE}"
+ restart_service_local=1
+ fi
+
+ # remove leases from other dhcpd server
+ if test -n "$( cat "${tmp_mac_other_file}" )";
+ then
+ ferror "Removing leases from other dhcpd server:"
+ cat "${tmp_mac_other_file}" 1>&2
+ fi
+ sed -n -r -e '/\{/,/^\}/{p}' "${tmp_leases_other_file}" | sed -e ':a;/\}/!{N;s/\n/ /;ba};' -e 's/\s\+/ /g;' | grep -viE "${DHCPD_CONTROL_MAC_TO_REMOVE}" > "${tmp_macless_other_file}"
+ if ! cmp -s "${tmp_macless_other_file}" "${tmp_leases_other_file}";
+ then
+ ssh "${DHCPD_CONTROL_OTHER_SERVER}" systemctl stop "${DHCPD_CONTROL_SERVICE}"
+ scp -p "${tmp_macless_other_file}" "${DHCPD_CONTROL_LEASES_FILE}"
+ restart_service_other=1
+ fi
+ ;;
+
+ "list")
+ debuglev 8 && ferror "BEGIN list"
+ lease_type="active"
+ #sed -n -r -e '/\{/,/^\}/{p}' /var/lib/dhcpd/dhcpd.leases | sed -e ':a;/\}/!{N;s/\n/ /;ba};' -e 's/\s\+/ /g;' | grep -iE "active"
+ #sed -n -r -e '/\{/,/^\}/{p}' /var/lib/dhcpd/dhcpd.leases | grep -iE "\{|\}|client-fqdn|hostname|hardware|starts|ends" | sed -e ':a;/\}/!{N;s/\n/ /;ba};' -e 's/\s\+/ /g;' | awk '!x[$14]++' | sort -k2
+ #sed -n -r -e '/\{/,/^\}/{p}' /var/lib/dhcpd/dhcpd.leases | grep -iE "\{|\}|client-fqdn|hostname|hardware|starts|ends" | sed -e ':a;/\}/!{N;s/\n/ /;ba};' -e 's/\s\+/ /g;' -e 's | awk '!x[$14]++' | sort -k2
+ #sed -n -r -e '/\{/,/^\}/{p}' /var/lib/dhcpd/dhcpd.leases | grep -iE "\{|\}|client-fqdn|hostname|hardware|starts|ends" | sed -e ':a;/\}/!{N;s/\n/ /;ba};' -e 's/\s\+/ /g;' -e 's/hardware ethernet/mac/;' | awk '!x[$13]++' | sort -k2
+ sed -n -r -e '/\{/,/^\}/{p}' "${DHCPD_CONTROL_LEASES_FILE}" | grep -iE "\{|\}|client-fqdn|hostname|hardware|starts|ends" | sed -e ':a;/\}/!{N;s/\n/ /;ba};' -e 's/\s\+/ /g;' -e 's/hardware ethernet/mac/;' | grep -viE "failover peer" | awk '!x[$13]++' | sort -k2
+ ;;
+ *)
+ ferror "Gotta say unh! Action ${action} ${fallopts} not yet implemented. Aborted."
+ exit 1
+ ;;
+ esac
+
+ # Prepare instructions for other server
+ debuglev 8 && ferror "BEGIN prepare instructions for other server"
+ local_instructions=""
+ other_instructions=""
+ instructions=""
+ fistruthy "${update_leases_other}" && \
+ other_instructions="${other_instructions}systemctl stop ${DHCPD_CONTROL_SERVICE}\; rm -f ${DHCPD_CONTROL_LEASES_TEMP_FILE}\; echo \"\" \> ${DHCPD_CONTROL_LEASES_FILE}\; "
+ fistruthy "${update_conf_other}" && \
+ local_instructions="${local_instructions}scp ${DHCPD_CONTROL_COMBINED_FILE} ${DHCPD_CONTROL_OTHER_SERVER}:${DHCPD_CONTROL_COMBINED_FILE}; "
+ fistruthy "${restart_service_local}" && \
+ instructions="${instructions}systemctl restart ${DHCPD_CONTROL_SERVICE}.service"
+ fistruthy "${restart_service_other}" && \
+ other_instructions="${other_instructions}systemctl restart ${DHCPD_CONTROL_SERVICE}"
+
+ # Instruct other server to act
+ debuglev 8 && ferror "BEGIN instruct other server to act"
+ if test -n "${DHCPD_CONTROL_OTHER_SERVER}";
+ then
+ debuglev 1 && {
+ test -n "${local_instructions}" && {
+ ferror "run local commands:"
+ ferror "${local_instructions}"
+ }
+ test -n "${other_instructions}" && {
+ ferror "run on other server ${DHCPD_CONTROL_OTHER_SERVER}:"
+ ferror "ssh ${DHCPD_CONTROL_OTHER_SERVER} ${other_instructions}"
+ }
+ }
+ test -n "${local_instructions}" && ${local_instructions}
+ test -n "${other_instructions}" && ssh ${DHCPD_CONTROL_OTHER_SERVER} eval ${other_instructions}
+ fi
+
+ # Local actions regardless of other server
+ if test -n "${instructions}";
+ then
+ debuglev 1 && {
+ ferror "run commands:"
+ ferror "${instructions}"
+ }
+ ${instructions}
+ fi
+
+#} | tee -a ${logfile}
+
+# EMAIL LOGFILE
+#${sendsh} ${sendopts} "${server} ${scriptfile} out" ${logfile} ${interestedparties}
+
+## STOP THE READ CONFIG FILE
+#exit 0
+#fi; done; }
diff --git a/src/usr/bin/updatezone b/src/usr/bin/updatezone
new file mode 100755
index 0000000..dc6efde
--- /dev/null
+++ b/src/usr/bin/updatezone
@@ -0,0 +1,434 @@
+#!/bin/sh
+# Filename: updatezone.sh
+# Location:
+# Author: bgstack15@gmail.com
+# Startdate: 2017-05-26 07:02:47
+# Title: Script that Updates a DNS Zone
+# Purpose: Provides a single command to update dns zones
+# Package: updatezone
+# History:
+# 2017-10-15 Added --flush option
+# 2024-08-19 fix typo in tmp_rev_file2
+# Usage:
+# Primarily intended for updating forward and reverse zones for bind9.
+# Reference: ftemplate.sh 2017-05-24a; framework.sh 2017-05-24a
+# Improve:
+# Dependencies:
+# rndc
+# ssh with password-less authentication to slave servers
+# each zone file has only a single zone
+fiversion="2017-05-24a"
+updatezoneversion="2024-08-19a"
+
+usage() {
+ less -F >&2 <<ENDUSAGE
+usage: updatezone.sh [-duV] [ --flush ] [ -c conffile | zone1 zone2 ... ]
+version ${updatezoneversion}
+ -d debug Show debugging info, including parsed variables.
+ -u usage Show this usage block.
+ -V version Show script version number.
+ --flush Wipe all the dhcpd-defined entries.
+ -c conffile Choose which conffile. Required if you do not name specific zones
+ zone1 Dns zone defined as UZ_ZONE_NAME in any .conf file in ${default_dir}/
+If the flush action is not requested, the normal action is edit, so you will interactively edit the zonefiles.
+Return values:
+0 Normal
+1 Help or version info displayed
+2 Count or type of flaglessvals is incorrect
+3 Incorrect OS type
+4 Unable to find dependency
+5 Not run as root or sudo
+6 Invalid configuration
+7 Unable to modify zone files
+ENDUSAGE
+}
+
+# DEFINE FUNCTIONS
+
+check_zone_file() {
+ # call: check_zone_file forward "${zone_name}" "${forward_file}" "{tmp_forward_file}"
+ debuglev 9 && ferror "check_zone_file $@"
+ local zone_type="$1"
+ local zone_name="$2"
+ local zone_real_file="$3"
+ local zone_temp_file="$4"
+
+ # if this zone is defined
+ if test -n "${zone_real_file}";
+ then
+ # if this zone file does not exist
+ if test ! -f "${zone_real_file}";
+ then
+ ferror "${scriptfile}: 6. Cannot find file: ${zone_real_file}. Skipping ${zone_type} zone."
+ pause_to_show_error=1
+ rm -f "${zone_temp_file}"
+ else
+ # so the zone file exists
+
+ # make sure we can modify it
+ if ! touch "${zone_real_file}";
+ then
+ ferror "${scriptfile}: 7. Unable to modify zone file ${zone_real_file}. Aborted."
+ exit 7
+ fi
+
+ # freeze zone so the file is up to date
+ zone_action freeze "${zone_name}"
+ echo "${zone_name}" >> "${zones_to_thaw_file}"
+
+ # prepare temp file
+ cp -p "${zone_real_file}" "${zone_temp_file}"
+ fi
+ fi
+}
+
+zone_action() {
+ # call: zone_action ${forwardzone}
+ debuglev 9 && ferror "zone_action $@"
+ local action="$1"
+ local zone="$2"
+ case "${action}" in
+ freeze|thaw)
+ rndc "${action}" "${zone}" 2>&1 | grep -viE "a zone reload and thaw|Check the logs to see"
+ ;;
+ *)
+ ferror "${scriptfile} minor error: ignoring unknown zone_action $@"
+ ;;
+ esac
+}
+
+update_real_zone_if_updated() {
+ # call: update_real_zone_if_updated "${UZ_REVERSE_ZONE}" "${UZ_REVERSE_FILE}" "${tmp_rev_file}"
+ debuglev 9 && ferror "update_real_zone_if_updated $@"
+ local zone_name="$1"
+ local zone_real_file="$2"
+ local zone_temp_file="$3"
+ if test -n "${zone_temp_file}" && test -f "${zone_temp_file}";
+ then
+ if ! cmp -s "${zone_real_file}" "${zone_temp_file}";
+ then
+ # a change occurred, so increment the serial number and replace the original zone file
+ increment_serial_in_zone_file "${zone_temp_file}"
+ cat "${zone_temp_file}" > "${zone_real_file}"
+
+ # plan to notify the dns slaves
+ echo "${zone_name}" >> "${zones_to_update_file}"
+ fi
+ fi
+
+ # If the temp file does not exist, it was deleted because the real file was invalid for whatever reason.
+}
+
+increment_serial_in_zone_file() {
+ # call: increment_serial_in_zone_file "${zone_temp_file}"
+ # dependencies: a single zone in the zone file, with the ";serial" comment after the number.
+ debuglev 9 && ferror "increment_serial_in_zone_file $@"
+ local infile="$1"
+ currentnum="$( grep -iE "[0-9]+\s*;\s*serial" "${infile}" | grep -oIE "[0-9]+" )"
+ nextnum=$(( currentnum + 1 ))
+ sed -i -r -e "s/${currentnum}(\s*;\s*serial)/${nextnum}\1/" "${infile}"
+}
+
+# DEFINE TRAPS
+
+clean_updatezone() {
+ rm -rf ${tempdir} > /dev/null 2>&1
+ [ ] #use at end of entire script if you need to clean up tmpfiles
+}
+
+CTRLC() {
+ #trap "CTRLC" 2
+ [ ] #useful for controlling the ctrl+c keystroke
+ exit 0
+}
+
+CTRLZ() {
+ #trap "CTRLZ" 18
+ [ ] #useful for controlling the ctrl+z keystroke
+}
+
+parseFlag() {
+ flag="$1"
+ hasval=0
+ case ${flag} in
+ # INSERT FLAGS HERE
+ "d" | "debug" | "DEBUG" | "dd" ) setdebug; ferror "debug level ${debug}";;
+ "u" | "usage" | "help" | "h" ) usage; exit 1;;
+ "V" | "fcheck" | "version" ) ferror "${scriptfile} version ${updatezoneversion}"; exit 1;;
+ #"i" | "infile" | "inputfile" ) getval;infile1=${tempval};;
+ "c" | "conf" | "config" | "conffile" ) getval;conffile=${tempval};;
+ "flush" ) action=flush;;
+ esac
+
+ debuglev 10 && { test ${hasval} -eq 1 && ferror "flag: ${flag} = ${tempval}" || ferror "flag: ${flag}"; }
+}
+
+# DETERMINE LOCATION OF FRAMEWORK
+while read flocation; do if test -x ${flocation} && test "$( ${flocation} --fcheck )" -ge 20170524; then frameworkscript="${flocation}"; break; fi; done <<EOFLOCATIONS
+./framework.sh
+${scriptdir}/framework.sh
+~/bin/bgscripts/framework.sh
+~/bin/framework.sh
+~/bgscripts/framework.sh
+~/framework.sh
+/usr/local/bin/bgscripts/framework.sh
+/usr/local/bin/framework.sh
+/usr/bin/bgscripts/framework.sh
+/usr/bin/framework.sh
+/bin/bgscripts/framework.sh
+/usr/share/bgscripts/framework.sh
+/usr/libexec/bgscripts/framework.sh
+EOFLOCATIONS
+test -z "${frameworkscript}" && echo "$0: framework not found. Aborted." 1>&2 && exit 4
+
+# INITIALIZE VARIABLES
+# variables set in framework:
+# today server thistty scriptdir scriptfile scripttrim
+# is_cronjob stdin_piped stdout_piped stderr_piped sendsh sendopts
+. ${frameworkscript} || echo "$0: framework did not run properly. Continuing..." 1>&2
+infile1=
+outfile1=
+logfile=${scriptdir}/${scripttrim}.${today}.out
+define_if_new interestedparties "bgstack15@gmail.com"
+# SIMPLECONF
+#define_if_new default_conffile "/etc/ddtools/updatezone.conf"
+#define_if_new defuser_conffile ~/.config/ddtools/updatezone.conf
+define_if_new EDITOR vi
+define_if_new default_dir "/etc/ddtools"
+
+# REACT TO OPERATING SYSTEM TYPE
+case $( uname -s ) in
+ Linux) [ ];;
+ FreeBSD) [ ];;
+ *) echo "${scriptfile}: 3. Indeterminate OS: $( uname -s )" 1>&2 && exit 3;;
+esac
+
+## REACT TO ROOT STATUS
+#case ${is_root} in
+# 1) # proper root
+# [ ] ;;
+# sudo) # sudo to root
+# [ ] ;;
+# "") # not root at all
+# #ferror "${scriptfile}: 5. Please run as root or sudo. Aborted."
+# #exit 5
+# [ ]
+# ;;
+#esac
+
+# VALIDATE PARAMETERS
+# objects before the dash are options, which get filled with the optvals
+# to debug flags, use option DEBUG. Variables set in framework: fallopts
+validateparams - "$@"
+
+# CONFIRM TOTAL NUMBER OF FLAGLESSVALS IS CORRECT
+#if test ${thiscount} -lt 1;
+#then
+# #ferror "${scriptfile}: 2. Fewer than 2 flaglessvals. Aborted."
+# #exit 2
+#fi
+
+# CONFIGURE VARIABLES AFTER PARAMETERS
+
+## LOAD CONFIG FROM SIMPLECONF
+## This section follows a simple hierarchy of precedence, with first being used:
+## 1. parameters and flags
+## 2. environment
+## 3. config file
+## 4. default user config: ~/.config/script/script.conf
+## 5. default config: /etc/script/script.conf
+#if test -f "${conffile}";
+#then
+# get_conf "${conffile}"
+#else
+# #if test "${conffile}" = "${default_conffile}" || test "${conffile}" = "${defuser_conffile}"; then :; else
+# ferror "${scriptfile}: Ignoring conf file which is not found: ${conffile}."
+# #fi
+#fi
+#test -f "${defuser_conffile}" && get_conf "${defuser_conffile}"
+#test -f "${default_conffile}" && get_conf "${default_conffile}"
+
+## REACT TO BEING A CRONJOB
+#if test ${is_cronjob} -eq 1;
+#then
+# [ ]
+#else
+# [ ]
+#fi
+
+# SET TRAPS
+trap "CTRLC" 2
+#trap "CTRLZ" 18
+trap "clean_updatezone" 0
+
+## DEBUG SIMPLECONF
+#debuglev 5 && {
+# ferror "Using values"
+# # used values: EX_(OPT1|OPT2|VERBOSE)
+# set | grep -iE "^UZ_" 1>&2
+#}
+
+# MAKE TEMP LOCATIONS
+tempdir=/tmp/updatezone/
+if ! mkdir -p "${tempdir}";
+then
+ ferror "${scriptfile}: 4. Unable to make temp directory ${tempdir}. Aborted."
+ exit 4
+fi
+
+# MAIN ACTIONS
+
+# EDIT
+main_action() {
+ # call: main_action "${action}" "${conffile}"
+ local action="${1}"
+ get_conf "${2}"
+ # DEBUG SIMPLECONF
+ debuglev 5 && {
+ ferror "Using values"
+ # used values: EX_(OPT1|OPT2|VERBOSE)
+ set | grep -iE "^UZ_" 1>&2
+ }
+ local tmp_for_file="$( mktemp -p "${tempdir}" forward.XXXX 2>/dev/null )"
+ local tmp_rev_file="$( mktemp -p "${tempdir}" reverse.XXXX 2>/dev/null )"
+ local zones_to_thaw_file="$( mktemp -p "${tempdir}" thaw.XXXX )"
+ local zones_to_update_file="$( mktemp -p "${tempdir}" update.XXXX )"
+ for word in "${tmp_for_file}" "${tmp_rev_file}";
+ do
+ if test ! -f "${word}";
+ then
+ ferror "${scriptfile}: 4. Unable to make temp file ${word}. Aborted."
+ exit 4
+ fi
+ done
+
+ # Freezing the zone ensures all records are in the primary files which we check.
+ local pause_to_show_error=0
+ # Check forward zone file and freeze
+ check_zone_file forward "${UZ_FORWARD_ZONE}" "${UZ_FORWARD_FILE}" "${tmp_for_file}"
+
+ # Check reverse zone file and freeze
+ check_zone_file reverse "${UZ_REVERSE_ZONE}" "${UZ_REVERSE_FILE}" "${tmp_rev_file}"
+
+ # Slow down to show errors if any
+ fistruthy "${pause_to_show_error}" && sleep 1.3
+
+ case "${action}" in
+
+ edit)
+ # EDIT FILES INTERACTIVELY
+ local these_temp_files="$( find "${tmp_for_file}" "${tmp_rev_file}" 2>/dev/null | xargs )"
+ test -n "${these_temp_files}" && $EDITOR ${these_temp_files}
+ ;;
+
+ flush)
+ # FLUSH FILES AUTOMATICALLY
+
+ # CALCULATE WHICH RECORDS TO FLUSH
+ # get dhcpd ttl.
+ #set -x
+ local tmp_flush_master_file="$( mktemp -p "${tempdir}" flush.master.XXXX 2>/dev/null )"
+ local tmp_flush_for_file="$( mktemp -p "${tempdir}" flush.for.XXXX 2>/dev/null )"
+ local tmp_flush_rev_file="$( mktemp -p "${tempdir}" flush.rev.XXXX 2>/dev/null )"
+
+ local dhcpd_ttl="$( grep -hE "default-lease-time" $( { $( which dhcpd-control ) --debug 5 nop; } 2>&1 | grep -E "_FILE=" | grep -vE "LEASES_" | cut -d'=' -f2 | xargs ) | cut -d' ' -f2 | tr -d ';' )"
+ # this next statement only returns an integer, but the rounding should be the same as the dns ttl rounding
+ local dns_ttl="$( printf "${dhcpd_ttl}/2\n" | bc )"
+
+ debuglev 2 && ferror "Flushing entries that have ttl ${dns_ttl} and have a TXT hash"
+ # fetch all dns A and TXT records that have the requested TTL
+ awk '$1 == "$TTL" {a=$2;} $1 != "$TTL" {if(a=='${dns_ttl}' && ($1=="TXT" || $2=="A")) print;}' "${tmp_for_file}" | \
+ # restrict to the A records that have associated TXT records
+ awk 'NR>1{if ($1=="TXT") print prev,$0;} {prev=$0;}' | \
+ # only keep ones whose TXT hash is the correct length
+ awk '$4=="TXT" && $5 ~ /"[a-fA-F0-9]{34}"/ {print;}' > "${tmp_flush_master_file}"
+
+ # prepare items to flush, forward
+ # convert to each a single line in a file, for future grep -v
+ grep -oE "(([0-9]{1,3}\.){3}[0-9]{1,3}|"[a-fA-F0-9]{34}")" "${tmp_flush_master_file}" > "${tmp_flush_for_file}"
+ # prepare items to flush, reverse
+ awk '{print $1}' "${tmp_flush_master_file}" | sed -e 's/^/PTR\\s\*/;' > "${tmp_flush_rev_file}"
+
+ # flush forward records
+ grep -v -f "${tmp_flush_for_file}" "${tmp_for_file}" > "${tmp_for_file}2"; mv -f "${tmp_for_file}2" "${tmp_for_file}"
+ # flush reverse records
+ grep -v -E -f "${tmp_flush_rev_file}" "${tmp_rev_file}" > "${tmp_rev_file}2"; mv -f "${tmp_rev_file}2" "${tmp_rev_file}"
+ ;;
+
+ esac
+
+ # Update the real zone if the temp file was updated
+ update_real_zone_if_updated "${UZ_FORWARD_ZONE}" "${UZ_FORWARD_FILE}" "${tmp_for_file}"
+ update_real_zone_if_updated "${UZ_REVERSE_ZONE}" "${UZ_REVERSE_FILE}" "${tmp_rev_file}"
+ # Thaw zones that need it
+ while read thiszone;
+ do
+ zone_action thaw "${thiszone}"
+ done < "${zones_to_thaw_file}"
+
+ # Transfer zones that need it
+ # This section exists because my automatic zone transfers/updates do not work.
+
+ # Build list of commands to run on each dns slave server
+ transfercommand=""
+ while read thiszone;
+ do
+ transfercommand="${transfercommand}rndc retransfer ${thiszone}; "
+ done < "${zones_to_update_file}"
+
+ # Execute command on each slave server
+ if test -n "${transfercommand}";
+ then
+ x=0
+ while test ${x} -lt ${UZ_SLAVE_COUNT};
+ do
+ x=$(( x + 1 ))
+ eval this_dns_slave=\"\${UZ_SLAVE_${x}}\"
+ debuglev 5 && ferror "ssh ${this_dns_slave} ${transfercommand}"
+ ssh ${this_dns_slave} ${transfercommand}
+ done
+ fi
+
+} #| tee -a ${logfile}
+
+#######################################################
+
+# DETERMINE ACTION
+case "${action}" in
+ "flush")
+ debuglev 2 && ferror "Action is flush."
+ ;;
+ *)
+ debuglev 2 && ferror "Action is edit."
+ action=edit
+ ;;
+esac
+
+# MAIN LOOP
+if test -n "${conffile}";
+then
+ ( main_action "${action}" "${conffile}"; )
+else
+ # assume the $opt items are the zone names
+ y=0
+ while test $y -lt $thiscount;
+ do
+ y=$(( y + 1 ))
+ eval "thiszonename=\${opt${y}}"
+ debuglev 1 && ferror "Will try to ${action} zone ${thiszonename}"
+ file_for_this_zone="$( grep -liE "UZ_ZONE_NAME=${thiszonename}" "${default_dir}/"*.conf 2>/dev/null )"
+ if test -n "${file_for_this_zone}" && test -f "${file_for_this_zone}";
+ then
+ ( main_action "${action}" "${file_for_this_zone}"; )
+ else
+ ferror "Skipping zone ${thiszonename} for which no file was found in ${default_dir}/"
+ fi
+ done
+fi
+
+# EMAIL LOGFILE
+#${sendsh} ${sendopts} "${server} ${scriptfile} out" ${logfile} ${interestedparties}
+
+## STOP THE READ CONFIG FILE
+#exit 0
+#fi; done; }
bgstack15