Skip to content

Instantly share code, notes, and snippets.

@Nockiro
Last active October 10, 2024 19:24
Show Gist options
  • Save Nockiro/582b4fe5843652557a03ab7efa1773f1 to your computer and use it in GitHub Desktop.
Save Nockiro/582b4fe5843652557a03ab7efa1773f1 to your computer and use it in GitHub Desktop.
DHCP/Samba DynDNS
#!/bin/bash
# /usr/local/bin/dhcp-dyndns.sh
# This script is for secure DDNS updates on Samba,
# it can also add the 'macAddress' to the Computers object.
#
# Modifications: Updates for faster local access to databases and compatibility with freebsds samba
#
# Version: 0.9.2
#
# Copyright (C) Rowland Penny 2020
# Extended by Robin Freund 2020-2021
#
# This program is free software; you can redistribute it 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 program is 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, see <http://www.gnu.org/licenses/>.
##########################################################################
# #
# You can optionally add the 'macAddress' to the Computers object. #
# Add 'dhcpduser' to the 'Domain Admins' group if used #
# Change the next line to 'yes' to make this happen #
Add_macAddress='yes'
# #
##########################################################################
usage() {
echo "USAGE:"
echo " $(basename $0) add ip-address dhcid|mac-address hostname"
echo " $(basename $0) delete ip-address dhcid|mac-address"
}
_KERBEROS () {
# get current time as a number
test=$(date +%d'-'%m'-'%y' '%H':'%M':'%S)
# Note: there have been problems with this
# check that 'date' returns something like
# Check for valid kerberos ticket
#logger "${test} [dyndns] : Running check for valid kerberos ticket"
klist -c "${KRB5CCNAME}" -s
if [ "$?" != "0" ]; then
logger "${test} [dyndns] : Getting new ticket, old one has expired"
if [[ "$OSTYPE" == "freebsd"* ]]; then
kinit --no-forwardable -k -t /etc/dhcpduser.keytab "${SETPRINCIPAL}"
else
kinit -F -k -t /etc/dhcpduser.keytab "${SETPRINCIPAL}"
fi
if [ "$?" != "0" ]; then
logger "${test} [dyndns] : dhcpd kinit for dynamic DNS failed"
exit 1;
fi
fi
}
rev_zone_info () {
local RevZone="$1"
local IP="$2"
local rzoneip=$(echo "$RevZone" | sed 's/\.in-addr.arpa//')
local rzonenum=$(echo "$rzoneip" | sed 's/\./ /g')
local words=($rzonenum)
local numwords="${#words[@]}"
case "$numwords" in
1) # single ip rev zone '192'
ZoneIP=$(echo "${IP}" | awk -F '.' '{print $1}')
RZIP=$(echo "${rzoneip}" | awk -F '.' '{print $3}')
IP2add=$(echo "${IP}" | awk -F '.' '{print $4"."$3"."$2}')
;;
2) # double ip rev zone '168.192'
ZoneIP=$(echo "${IP}" | awk -F '.' '{print $1"."$2}')
RZIP=$(echo "${rzoneip}" | awk -F '.' '{print $2"."$1}')
IP2add=$(echo "${IP}" | awk -F '.' '{print $4"."$3}')
;;
3) # triple ip rev zone '178.168.192'
ZoneIP=$(echo "${IP}" | awk -F '.' '{print $1"."$2"."$3}')
RZIP=$(echo "${rzoneip}" | awk -F '.' '{print $3"."$2"."$1}')
IP2add=$(echo "${IP}" | awk -F '.' '{print $4}')
;;
*) # should never happen
exit 1
;;
esac
echo "$ZoneIP"
echo "$RZIP"
echo "$IP2add"
}
BINDIR=$(samba -b | grep 'BINDIR' | grep -v 'SBINDIR' | awk '{print $NF}')
WBINFO="$BINDIR/wbinfo"
LDBCOMMAND="ldbsearch"
if [[ "$OSTYPE" == "freebsd"* ]]; then
LDBCOMMAND="samba-ldbsearch"
fi
LDBCOMMANDMOD="ldbmodify"
if [[ "$OSTYPE" == "freebsd"* ]]; then
LDBCOMMANDMOD="samba-ldbmodify"
fi
# DHCP Server hostname
Server=$(hostname -s)
# DNS domain
domain=$(hostname -d)
if [ -z ${domain} ]; then
logger "Cannot obtain domain name, is DNS set up correctly?"
logger "Cannot continue... Exiting."
exit 1
fi
# Samba realm
REALM=$(echo ${domain^^})
# Samba SAM file - can also be ldap://${SERVER}, but that could get very slow
if [[ "$OSTYPE" == "freebsd"* ]]; then
DATABASE="/var/db/samba4/private/sam.ldb"
else
DATABASE="/var/lib/samba/private/sam.ldb"
fi
# krbcc ticket cache
export KRB5CCNAME="/tmp/dhcp-dyndns.cc"
# Kerberos principal
SETPRINCIPAL="dhcpduser@${REALM}"
# Kerberos keytab : /etc/dhcpduser.keytab
# krbcc ticket cache : /tmp/dhcp-dyndns.cc
TESTUSER="$($WBINFO -u | grep 'dhcpduser')"
if [ -z "${TESTUSER}" ]; then
logger "No AD dhcp user exists, need to create it first.. exiting."
logger "you can do this by typing the following commands"
logger "kinit Administrator@${REALM}"
logger "samba-tool user create dhcpduser --random-password --description='Unprivileged user for DNS updates via ISC DHCP server'"
logger "samba-tool user setexpiry dhcpduser --noexpiry"
logger "samba-tool group addmembers DnsAdmins dhcpduser"
exit 1
fi
# Check for Kerberos keytab
if [ ! -f /etc/dhcpduser.keytab ]; then
echo "Required keytab /etc/dhcpduser.keytab not found, it needs to be created."
echo "Use the following commands as root"
echo "samba-tool domain exportkeytab --principal=${SETPRINCIPAL} /etc/dhcpduser.keytab"
echo "chown XXXX:XXXX /etc/dhcpduser.keytab"
echo "Replace 'XXXX:XXXX' with the user & group that dhcpd runs as on your distro"
echo "chmod 400 /etc/dhcpduser.keytab"
exit 1
fi
# Variables supplied by dhcpd.conf
action="$1"
ip="$2"
DHCID="$3"
name="${4%%.*}"
# Exit if no ip address or mac-address
if [ -z "${ip}" ] || [ -z "${DHCID}" ]; then
usage
exit 1
fi
# Exit if no computer name supplied, unless the action is 'delete'
if [ -z "${name}" ]; then
if [ "${action}" = "delete" ]; then
name=$(host -t PTR "${ip}" | awk '{print $NF}' | awk -F '.' '{print $1}')
else
usage
exit 1;
fi
fi
# exit if name contains a space
case ${name} in
*\ * ) logger "Invalid hostname '${name}' ...Exiting"
exit
;;
# * ) : ;;
esac
# exit if $name starts with 'dhcp'
# if you do not want computers without a hostname in AD
# uncomment the following block of code.
if [[ $name == dhcp* ]]; then
logger "not updating DNS record in AD, invalid name"
exit 0
fi
## update ##
case "${action}" in
add)
_KERBEROS
# does host have an existing 'A' record ?
A_REC=$(host -t A "${name}" | awk '{print $NF}')
# check for dots
if [[ $A_REC == *.* ]]; then
if [[ ${ip} = $A_REC ]]; then
logger "Existing A record of ${name} matches new IP, not updating."
result1="0"
result2="0"
else
samba-tool dns delete ${Server} ${domain} "${name}" A ${ip} -k yes
result1="$?"
logger "Deleted existing host A record for ${name}"
samba-tool dns add ${Server} ${domain} "${name}" A ${ip} -k yes
result2="$?"
logger "Added new A record"
fi
else
result1=0
logger "No A record yet"
samba-tool dns add ${Server} ${domain} "${name}" A ${ip} -k yes
result2="$?"
logger "Added new A record"
fi
# get ptr record for possible early exit
PTR_REC=$(host -t PTR "${ip}" | awk '{print $NF}')
if [[ $PTR_REC = "${name}".${domain}. ]]; then
logger "Existing PTR record of ${ip} matches new hostname, not updating."
result3='0'
result4='0'
else
# get existing reverse zones (if any)
ReverseZones=$(samba-tool dns zonelist ${Server} --reverse | grep 'pszZoneName' | awk '{print $NF}')
if [ -z "$ReverseZones" ]; then
echo "No reverse zone found, not updating."
result3="0"
result4="0"
else
for revzone in $ReverseZones
do
logger "Looking in zone ${revzone}.${domain}"
rev_zone_info "$revzone" "${ip}"
if [[ ${ip} = $ZoneIP* ]] && [ "$ZoneIP" = "$RZIP" ]; then
if [[ $PTR_REC == *.${domain}. ]]; then
samba-tool dns delete ${Server} ${revzone} ${IP2add} PTR "${name}".${domain} -k yes
result3="$?"
logger "Deleted existing host PTR record for {ip}"
samba-tool dns add ${Server} ${revzone} ${IP2add} PTR "${name}".${domain} -k yes
result4="$?"
logger "Added new PTR record"
else
result3='0'
logger "No PTR record for ${ip} yet - found $PTR_REC for ${domain}"
samba-tool dns add ${Server} ${revzone} ${IP2add} PTR "${name}".${domain} -k yes
result4="$?"
logger "Added new PTR record"
fi
break
else
continue
fi
done
fi
fi
;;
delete)
_KERBEROS
samba-tool dns delete ${Server} ${domain} "${name}" A ${ip} -k yes
result1="$?"
# get existing reverse zones (if any)
ReverseZones=$(samba-tool dns zonelist ${Server} --reverse | grep 'pszZoneName' | awk '{print $NF}')
if [ -z "$ReverseZones" ]; then
logger "No reverse zone found, not updating"
result2='0'
else
for revzone in $ReverseZones
do
rev_zone_info "$revzone" "${ip}"
if [[ ${ip} = $ZoneIP* ]] && [ "$ZoneIP" = "$RZIP" ]; then
host -t PTR ${ip} > /dev/null 2>&1
if [ "$?" -eq 0 ]; then
samba-tool dns delete ${Server} ${revzone} ${IP2add} PTR "${name}".${domain} -k yes
result2="$?"
else
result2='0'
fi
break
else
continue
fi
done
fi
result3='0'
result4='0'
;;
*)
logger "Invalid action specified"
exit 103
;;
esac
result="${result1}:${result2}:${result3}:${result4}"
if [ "${result}" != "0:0:0:0" ]; then
logger "DHCP-DNS Update failed: ${result}"
exit 1
else
logger "DHCP-DNS Update succeeded"
fi
if [ "$Add_macAddress" != 'no' ]; then
logger "Searching for $name in AD.."
Computer_Object=$(ldbsearch -k yes -H "$DATABASE" "(&(objectclass=computer)(objectclass=ieee802Device)(cn=$name))" | grep -v '#' | grep -v 'ref:')
if [ -z "$Computer_Object" ]; then
# Computer object not found with the 'ieee802Device' objectclass, does the computer actually exist, it should.
Computer_Object=$(ldbsearch -k yes -H "$DATABASE" "(&(objectclass=computer)(cn=$name))" | grep -v '#' | grep -v 'ref:')
if [ -z "$Computer_Object" ]; then
logger "Computer '$name' not found. Exiting."
exit 68
else
DN=$(echo "$Computer_Object" | grep 'dn:')
objldif="$DN
changetype: modify
add: objectclass
objectclass: ieee802Device"
attrldif="$DN
changetype: modify
add: macAddress
macAddress: $DHCID"
# add the ldif
echo "$objldif" | ldbmodify -k yes -H "$DATABASE" 2&>1 | logger &
ret="$?"
if [ "$ret" -ne 0 ]; then
logger "Error modifying Computer objectclass $name in AD."
exit "${ret}"
fi
sleep 2
echo "$attrldif" | ldbmodify -k yes -H "$DATABASE" 2&>1 | logger &
ret="$?"
if [ "$ret" -ne 0 ]; then
logger "Error modifying Computer attribute $name in AD."
exit "${ret}"
fi
unset objldif
unset attrldif
logger "Successfully modified Computer $name in AD"
fi
else
DN=$(echo "$Computer_Object" | grep 'dn:')
attrldif="$DN
changetype: modify
replace: macAddress
macAddress: $DHCID"
echo "$attrldif" | ldbmodify -k yes -H ldap://"$Server" 2&>1 | logger &
ret="$?"
if [ "$ret" -ne 0 ]; then
logger "Error modifying Computer attribute $name in AD."
exit "${ret}"
fi
unset attrldif
logger "Successfully modified Computer $name in AD"
fi
fi
exit 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment