Files
infix/board/common/rootfs/usr/share/udhcpc/default.script
T
Joachim Wiberg 3aa22a7c63 board/common: only apply explicitly requested DHCP options in client
Validate that DHCP options were requested in the parameter request
list before applying them. This prevents malicious or misconfigured
DHCP servers from forcing unwanted configuration changes.

Validates: hostname (12), DNS (6), domain (15), search (119),
router (3), static routes (121), and NTP (42).

Fail-safe behavior: rejects options if config file unavailable.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-11-20 09:34:00 +01:00

238 lines
5.7 KiB
Bash
Executable File

#!/bin/sh
# This script expect a system with resolvconf (openresolv) and iproute2
[ -z "$1" ] && echo "Error: should be called from udhcpc" && exit 1
ACTION="$1"
IP_CACHE="/var/lib/misc/${interface}.cache"
RESOLV_CONF="/run/resolvconf/interfaces/${interface}.conf"
NTPFILE="/run/chrony/dhcp-sources.d/${interface}.sources"
NAME="/etc/frr/static.d/${interface}-dhcp.conf"
NEXT="${NAME}+"
[ -n "$broadcast" ] && BROADCAST="broadcast $broadcast"
[ -n "$subnet" ] || subnet=32
[ -n "$metric" ] || metric=5
# Handle stateful DHCPv6 like DHCPv4
[ -n "$ipv6" ] && ip="$ipv6/128"
if [ -z "${IF_WAIT_DELAY}" ]; then
IF_WAIT_DELAY=10
fi
log()
{
logger -I $$ -t udhcpc -p user.notice "${interface}: $*"
}
dbg()
{
logger -I $$ -t udhcpc -p user.debug "${interface}: $*"
}
err()
{
logger -I $$ -t udhcpc -p user.err "${interface}: $*"
}
wait_for_ipv6_default_route()
{
dbg "waiting for IPv6 default route to be installed."
while [ $IF_WAIT_DELAY -gt 0 ]; do
if ip -6 route list proto dhcp dev $interface | grep -q default; then
return
fi
sleep 1
printf "."
: $((IF_WAIT_DELAY -= 1))
done
err "Timed out waiting for IPv6 default route!"
}
# Check if a DHCP option was requested in the parameter request list
# Returns: 0 if requested, 1 if not requested or config unavailable
was_option_requested()
{
local opt_num="$1"
local config="/etc/finit.d/available/dhcp-client-${interface}.conf"
if [ ! -f "$config" ]; then
dbg "config file not found: $config"
return 1
fi
# Extract udhcpc command line and check for -O <option_num>
if grep -q -- "-O ${opt_num}\b" "$config"; then
return 0
fi
return 1
}
# RFC3442: If the DHCP server returns both a Classless
# Static Routes option and a Router option, the DHCP
# client MUST ignore the Router option.
set_dhcp_routes()
{
echo "! Generated by udhcpc" > "$NEXT"
if [ -n "$staticroutes" ]; then
if was_option_requested 121; then
# format: dest1/mask gw1 ... destn/mask gwn
set -- $staticroutes
while [ -n "$1" -a -n "$2" ]; do
dbg "adding route $1 via $2 metric $metric tag 100"
echo "ip route $1 $2 $metric tag 100" >> "$NEXT"
shift 2
done
else
log "ignoring unrequested staticroutes (option 121)"
fi
elif [ -n "$router" ] ; then
if was_option_requested 3; then
for i in $router ; do
echo "ip route 0.0.0.0/0 $i $metric tag 100" >> "$NEXT"
done
else
log "ignoring unrequested router (option 3)"
fi
fi
# Reduce changes needed by comparing with previous route(s)
cmp -s "$NAME" "$NEXT" && return
mv "$NEXT" "$NAME"
initctl -nbq restart staticd
}
clr_dhcp_routes()
{
log "deleting DHCP routes"
[ -f "$NAME" ] || return
rm "$NAME"
initctl -nbq restart staticd
}
clr_dhcp_addresses()
{
addrs=$(ip -j addr show dev $interface \
| jq -c '.[0].addr_info[] | select(.family == "inet") | select(.protocol == "dhcp")')
for addr in $addrs; do
ip="$(echo "$addr" | jq -r '."local"')"
prefix="$(echo "$addr" | jq -r '."prefixlen"')"
log "removing $ip/$prefix"
ip addr del "$ip/$prefix" dev "$interface"
done
}
log "action $ACTION"
case "$ACTION" in
deconfig)
clr_dhcp_addresses
clr_dhcp_routes
# drop info from this interface
rm -f "$RESOLV_CONF"
rm -f "$NTPFILE"
if [ -f "/etc/hostname.d/90-dhcp-${interface}" ]; then
log "removing /etc/hostname.d/90-dhcp-${interface}"
rm -f "/etc/hostname.d/90-dhcp-${interface}"
/usr/libexec/infix/hostname dhcp
fi
if [ -x /usr/sbin/avahi-autoipd ]; then
/usr/sbin/avahi-autoipd -c $interface && /usr/sbin/avahi-autoipd -k $interface
fi
;;
leasefail|nak)
if [ -x /usr/sbin/avahi-autoipd ]; then
/usr/sbin/avahi-autoipd -c $interface || /usr/sbin/avahi-autoipd -wD $interface --no-chroot
fi
;;
renew|bound)
if [ -x /usr/sbin/avahi-autoipd ]; then
/usr/sbin/avahi-autoipd -c $interface && /usr/sbin/avahi-autoipd -k $interface
fi
if /bin/ip addr add dev $interface $ip/$subnet $BROADCAST proto dhcp; then
echo "$ip" > "$IP_CACHE"
fi
if [ -n "$ipv6" ] ; then
wait_for_ipv6_default_route
fi
set_dhcp_routes
# set hostname if given and requested
if [ -n "$hostname" ]; then
if was_option_requested 12; then
log "received DHCP hostname: $hostname"
mkdir -p /etc/hostname.d
echo "$hostname" > "/etc/hostname.d/90-dhcp-${interface}"
/usr/libexec/infix/hostname dhcp
else
log "ignoring unrequested hostname (option 12): $hostname"
fi
fi
# drop info from this interface
truncate -s 0 "$RESOLV_CONF"
# prefer rfc3397 domain search list (option 119) if available
search_list=""
if [ -n "$search" ]; then
if was_option_requested 119; then
search_list=$search
else
log "ignoring unrequested search (option 119): $search"
fi
elif [ -n "$domain" ]; then
if was_option_requested 15; then
search_list=$domain
else
log "ignoring unrequested domain (option 15): $domain"
fi
fi
if [ -n "$search_list" ]; then
dbg "adding search $search_list"
echo "search $search_list # $interface" >> $RESOLV_CONF
fi
if [ -n "$dns" ]; then
if was_option_requested 6; then
for i in $dns ; do
dbg "adding dns $i"
echo "nameserver $i # $interface" >> $RESOLV_CONF
resolvconf -u
done
else
log "ignoring unrequested dns (option 6): $dns"
fi
fi
if [ -n "$ntpsrv" ]; then
if was_option_requested 42; then
truncate -s 0 "$NTPFILE"
for srv in $ntpsrv; do
dbg "got NTP server $srv"
echo "server $srv iburst" >> "$NTPFILE"
done
chronyc reload sources >/dev/null
else
log "ignoring unrequested ntpsrv (option 42): $ntpsrv"
fi
fi
esac
HOOK_DIR="$0.d"
for hook in "${HOOK_DIR}/"*; do
[ -f "${hook}" -a -x "${hook}" ] || continue
"${hook}" "$ACTION"
done
exit 0