openwrt-packages/net/isc-dhcp/files/dhcpd.init

741 lines
15 KiB
Bash
Executable File

#!/bin/sh /etc/rc.common
START=25
USE_PROCD=1
PROG=/usr/sbin/dhcpd
WS=$'[\t ]'
TTL=3600
PREFIX="update add"
lease_file=/tmp/dhcpd.leases
config_file=/tmp/run/dhcpd.conf
dyndir=/tmp/bind
conf_local_file=$dyndir/named.conf.local
session_key_name=local-ddns
session_key_file=/var/run/named/session.key
time2seconds() {
local timestring=$1
local multiplier number suffix
suffix="${timestring//[0-9 ]}"
number="${timestring%%$suffix}"
[ "$number$suffix" != "$timestring" ] && return 1
case "$suffix" in
"" | s)
multiplier=1
;;
m)
multiplier=60
;;
h)
multiplier=3600
;;
d)
multiplier=86400
;;
w)
multiplier=604800
;;
*)
return 1
;;
esac
echo $(( number * multiplier ))
}
explode() {
echo "${1//\./ }"
}
trim() {
local str="$1" prev
while true; do
prev="$str"
str="${str%%$WS}"
[ "$str" = "$prev" ] && break
done
while true; do
prev="$str"
str="${str##$WS}"
[ "$str" = "$prev" ] && break
done
echo "$str"
}
rfc1918_prefix() {
local subnet="${1%/*}"
set -- $(explode "$subnet")
case "$1.$2" in
10.*)
echo "$1" ;;
172.1[6789]|172.2[0-9]|172.3[01]|192.168)
echo "$1.$2" ;;
esac
}
no_ipv6() {
[ -n "$(named-checkconf -px \
| sed -r -ne '1N; N; /^\tlisten-on-v6 ?\{\n\t\t"none";\n\t\};$/{ p; q; }; D')" ]
}
# duplicated from dnsmasq init script
hex_to_hostid() {
local var="$1"
local hex="${2#0x}" # strip optional "0x" prefix
if [ -n "${hex//[0-9a-fA-F]/}" ]; then
# is invalid hex literal
return 1
fi
# convert into host id
export "$var=$(
printf "%0x:%0x" \
$(((0x$hex >> 16) % 65536)) \
$(( 0x$hex % 65536))
)"
return 0
}
typeof() {
echo "$1" | awk '
/^\d+\.\d+\.\d+\.\d+$/ { print "ip\n"; next; }
/^(true|false)$/ { print "bool\n"; next; }
/^\d+$/ { print "integer\n"; next; }
/^"[^"]*"$/ { print "string\n"; next; }
/^[0-9a-fA-F]{2,2}(:[0-9a-fA-F]{2,2})*$/ { print "string\n"; next; }
{ print "other\n"; next; }
'
}
update() {
local lhs="$1" leasetime="$2" family="$3" type="$4"
shift 4
[ $dynamicdns -eq 1 ] && \
echo -e "$PREFIX" "$lhs ${leasetime} $family $type $@\nsend" >> $dyn_file
}
rev_str() {
local str="$1" delim="$2"
local frag result="" IFS="$delim"
for frag in $str; do
result="$frag${result:+$delim}$result"
done
echo "$result"
}
create_zone_update_file() {
local zone=$1
local zonefile="$dyndir/db.$zone"
cp -p /etc/bind/db.empty "$zonefile"
cat <<EOF >> ${conf_local_file}_
zone "$zone" {
type master;
file "$zonefile";
update-policy {
grant $session_key_name zonesub any;
};
};
EOF
}
generate_dhcp_ddns_config() {
local zone=$1
local cfg=$2
local server
config_get server "$cfg" server
cat <<EOF
zone $zone. {
primary $server;
key $session_key_name;
}
EOF
}
generate_session_file() {
local cfg=$1
config_get session_key_name $cfg key_name local-dns
config_get key_algo $cfg key_algo hmac-sha256
config_get key_secret $cfg key_secret
cat <<EOF > "$session_key_file"
key "$session_key_name" {
algorithm $key_algo;
secret "$key_secret";
};
EOF
}
generate_ddns_config() {
local domain=$1
local cfg=dynamicdns
local server zones
local named_reload=0
config_get server $cfg server
config_get zones $cfg zones
generate_session_file "$cfg"
cat <<EOF
ddns-domainname "$domain.";
ddns-update-style standard;
ddns-updates on;
ignore client-updates;
update-static-leases on;
use-host-decl-names on;
update-conflict-detection off;
update-optimization off;
include "$session_key_file";
EOF
config_list_foreach $cfg zones create_zone_update_file "$cfg"
config_list_foreach $cfg zones generate_dhcp_ddns_config "$cfg"
cmp -s $conf_local_file ${conf_local_file}_ || {
named_reload=1
cp ${conf_local_file}_ ${conf_local_file}
}
rm -f ${conf_local_file}_
chmod g+w "$dyndir" -R
chgrp bind "$dyndir" -R
if [ "$named_reload" = "1" ]; then
/etc/init.d/named reload
sleep 1
fi
init_dynamicdns_config
echo -e "server $server" >> $dyn_file
echo -e "zone ${zones// /\\nzone }" >> $dyn_file
}
append_routes() {
local tuple="$(trim "$1")"
local network prefix router subnet compacted octet
subnet="${tuple%%$WS*}"
network="${subnet%/[0-9]*}"
prefix="${subnet#*/}"
set -- $(explode "$network")
case $((($prefix + 7) / 8)) in
0)
compacted= ;;
1)
compacted="$1" ;;
2)
compacted="$1 $2" ;;
3)
compacted="$1 $2 $3" ;;
4)
compacted="$1 $2 $3 $4" ;;
esac
router="${tuple#${subnet}$WS}"
for octet in $prefix $compacted $(explode "$router"); do
append routes "$octet" ", "
done
}
append_dhcp_options() {
local tuple="$1"
# strip redundant "option:" prefix
tuple="${tuple#option:}"
local tag="${tuple%%,*}"
local values="${tuple#$tag,}"
local formatted value
local IFS=$', \n'
for value in $values; do
# detect type of $value and quote if necessary
case $(typeof "$value") in
ip|bool|integer|string)
;;
other)
value="\"$value\""
;;
esac
append formatted "$value" ", "
done
echo " option $tag $formatted;"
}
static_cname_add() {
local cfg="$1"
local cname target
config_get cname "$cfg" "cname"
[ -n "$cname" ] || return 0
config_get target "$cfg" "target"
[ -n "$target" ] || return 0
update "$cname.$domain." "" IN CNAME "$target.$domain."
}
static_cnames() {
config_foreach static_cname_add cname "$@"
}
static_domain_add() {
local cfg="$1"
local name ip ips revip
config_get name "$cfg" "name"
[ -n "$name" ] || return 0
config_get ip "$cfg" "ip"
[ -n "$ip" ] || return 0
ips="$ip"
for ip in $ips; do
revip="$(rev_str "$ip" ".")"
update "$name.$domain." "" IN A "$ip"
[ -n "$(rfc1918_prefix "$ip")" ] && \
update "$revip.in-addr.arpa." "" IN PTR "$name.$domain."
done
}
static_domains() {
config_foreach static_domain_add domain "$@"
}
static_mxhost_add() {
local cfg="$1"
local domain2 relay pref
config_get domain2 "$cfg" "domain"
[ -n "$domain2" ] || return 0
config_get relay "$cfg" "relay"
[ -n "$relay" ] || return 0
config_get pref "$cfg" "pref"
[ -n "$pref" ] || return 0
if [ "$domain2" = "@" ]; then
update "$domain." "" IN MX "$pref" "$relay.$domain."
else
update "$domain2.$domain." "" IN MX "$pref" "$relay.$domain."
fi
}
static_mxhosts() {
config_foreach static_mxhost_add mxhost "$@"
}
static_srvhost_add() {
local cfg="$1"
local srv target port priority weight
config_get srv "$cfg" "srv"
[ -n "$srv" ] || return 0
config_get target "$cfg" "target"
[ -n "$target" ] || return 0
config_get port "$cfg" "port"
[ -n "$port" ] || return 0
config_get priority "$cfg" "priority"
[ -n "$priority" ] || return 0
config_get weight "$cfg" "weight"
[ -n "$weight" ] || return 0
update "$srv.$domain." "" IN SRV "$priority" "$weight" "$port" "$target.$domain"
}
static_srvhosts() {
config_foreach static_srvhost_add srvhost "$@"
}
static_host_add() {
local cfg="$1"
local broadcast hostid macn macs mac name ip ips revip leasetime
local force_send extra_options option
config_get macs "$cfg" "mac"
[ -n "$macs" ] || return 0
config_get name "$cfg" "name"
[ -n "$name" ] || return 0
config_get ip "$cfg" "ip"
[ -n "$ip" ] || return 0
config_get_bool broadcast "$cfg" "broadcast" 0
config_get dns "$cfg" "dns"
config_get gateway "$cfg" "gateway"
config_get leasetime "$cfg" "leasetime"
if [ -n "$leasetime" ] ; then
leasetime="$(time2seconds "$leasetime")"
[ $? -ne 0 ] && return 1
fi
config_get hostid "$cfg" "hostid"
if [ -n "$hostid" ] ; then
hex_to_hostid hostid "$hostid" || return 1
fi
config_get force_send "$cfg" "force_send"
extra_options=
for option in ${force_send//,/ }; do
case "$option" in
hostname)
append extra_options "0c" "," ;;
domain-name)
append extra_options "0f" "," ;;
renewal-time)
append extra_options "3a" "," ;;
rebinding-time)
append extra_options "3b" "," ;;
fqdn)
append extra_options "51" "," ;;
routes)
append extra_options "79" "," ;;
*)
echo "unknown option: $option" >&2 ;;
esac
done
macn=0
for mac in $macs; do
macn=$(( macn + 1 ))
done
for mac in $macs; do
local secname="$name"
if [ $macn -gt 1 ] ; then
secname="${name}-${mac//:}"
fi
echo "host $secname {"
echo " hardware ethernet $mac;"
echo " fixed-address $ip;"
echo " option host-name \"$name\";"
if [ $broadcast -eq 1 ] ; then
echo " always-broadcast true;"
fi
if [ -n "$leasetime" ] ; then
echo " default-lease-time $leasetime;"
echo " max-lease-time $leasetime;"
fi
if [ -n "$hostid" ] ; then
echo " option dhcp-client-identifier $hostid;"
fi
if [ -n "$dns" ] ; then
echo " option domain-name-servers $dns;"
fi
if [ -n "$gateway" ] ; then
echo " option routers $gateway;"
fi
local routes=
config_list_foreach "$cfg" "routes" append_routes
[ -n "$routes" ] && echo " option classless-ipv4-route $routes;"
config_list_foreach "$cfg" "dhcp_option" append_dhcp_options
if [ -n "$extra_options" ]; then
echo -e " if exists dhcp-parameter-request-list {\n option dhcp-parameter-request-list = concat(option dhcp-parameter-request-list, $extra_options);\n }"
fi
echo "}"
done
ips="$ip"
for ip in $ips; do
revip="$(rev_str "$ip" ".")"
update "$name.$domain." "$leasetime" IN A "$ip"
update "$revip.in-addr.arpa." "$leasetime" IN PTR "$name.$domain."
done
}
static_hosts() {
config_foreach static_host_add host "$@"
}
init_dynamicdns_config() {
cat <<EOF > $dyn_file
; Generated by /etc/init.d/dhcpd at $(date)
ttl $TTL
EOF
}
gen_dhcp_subnet() {
local cfg="$1"
echo "subnet $NETWORK netmask $NETMASK {"
if [ -n "$START" ] && [ -n "$END" ]; then
echo " range $START $END;"
fi
echo " option subnet-mask $netmask;"
# check for 0.0.0.0 until all active releases of ipcalc.sh omit it
# for small networks:
if [ -n "$BROADCAST" ] && [ "$BROADCAST" != "0.0.0.0" ] ; then
echo " option broadcast-address $BROADCAST;"
fi
if [ $dynamicdhcp -eq 0 ] ; then
if [ $authoritative -eq 1 ] ; then
echo " deny unknown-clients;"
else
echo " ignore unknown-clients;"
fi
fi
if [ -n "$leasetime" ] ; then
echo " default-lease-time $leasetime;"
echo " max-lease-time $leasetime;"
fi
if [ $defaultroute -eq 1 ] ; then
echo " option routers $gateway;"
fi
echo " option domain-name-servers $DNS;"
[ -n "$domain" ] && echo " option domain-name \"$domain\";"
local routes=
config_list_foreach "$cfg" "routes" append_routes
[ -n "$routes" ] && echo " option classless-ipv4-route $routes;"
config_list_foreach "$cfg" "dhcp_option" append_dhcp_options
echo "}"
}
dhcpd_add() {
local cfg="$1" synthesize="$2"
local dhcp6range="::"
local dynamicdhcp defaultroute end gateway ifname ignore leasetime limit net netmask
local proto networkid start subnet domain
local IP NETMASK BROADCAST NETWORK PREFIX DNS START END
config_get_bool ignore "$cfg" "ignore" 0
[ $ignore -eq 1 ] && return 0
config_get net "$cfg" "interface"
[ -n "$net" ] || return 0
config_get start "$cfg" "start"
config_get limit "$cfg" "limit"
case "$start:$limit" in
":*"|"*:")
echo "dhcpd: start/limit must be used together in $cfg" >&2
return 0
esac
network_get_subnet subnet "$net" || return 0
network_get_device ifname "$net" || return 0
network_get_protocol proto "$net" || return 0
[ "$proto" != "static" ] && return 0
local octets="$(rfc1918_prefix "$subnet")"
[ -n "$octets" ] && append rfc1918_nets "$octets"
[ $synthesize -eq 0 ] && return 0
config_get_bool dynamicdhcp "$cfg" "dynamicdhcp" 1
config_get_bool defaultroute "$cfg" "default_route" 1
append dhcp_ifs "$ifname"
if ! ipcalc "$subnet" "$start" "$limit"; then
echo "invalid range params: $subnet start: $start limit $limit" >&2
return 1
fi
config_get netmask "$cfg" "netmask" "$NETMASK"
config_get leasetime "$cfg" "leasetime"
if [ -n "$leasetime" ] ; then
leasetime="$(time2seconds "$leasetime")"
[ $? -ne 0 ] && return 1
fi
if network_get_dnsserver dnsserver "$net" ; then
for dnsserv in $dnsserver ; do
DNS="$DNS${DNS:+, }$dnsserv"
done
else
DNS="$IP"
fi
if ! network_get_gateway gateway "$net" ; then
gateway="$IP"
fi
config_get domain "$cfg" "domain"
gen_dhcp_subnet "$cfg"
}
general_config() {
local always_broadcast boot_unknown_clients log_facility
local default_lease_time max_lease_time
config_get_bool always_broadcast "isc_dhcpd" "always_broadcast" 0
config_get_bool authoritative "isc_dhcpd" "authoritative" 1
config_get_bool boot_unknown_clients "isc_dhcpd" "boot_unknown_clients" 1
config_get default_lease_time "isc_dhcpd" "default_lease_time" 3600
config_get max_lease_time "isc_dhcpd" "max_lease_time" 86400
config_get log_facility "isc_dhcpd" "log_facility"
config_get domain "isc_dhcpd" "domain"
config_get_bool dynamicdns "isc_dhcpd" dynamicdns 0
[ $always_broadcast -eq 1 ] && echo "always-broadcast true;"
[ $authoritative -eq 1 ] && echo "authoritative;"
[ $boot_unknown_clients -eq 0 ] && echo "boot-unknown-clients false;"
default_lease_time="$(time2seconds "$default_lease_time")"
[ $? -ne 0 ] && return 1
max_lease_time="$(time2seconds "$max_lease_time")"
[ $? -ne 0 ] && return 1
[ $dynamicdns -eq 1 ] && generate_ddns_config "$domain"
if [ -n "$log_facility" ] ; then
echo "log-facility $log_facility;"
fi
echo "default-lease-time $default_lease_time;"
echo "max-lease-time $max_lease_time;"
[ -n "$domain" ] && echo "option domain-name \"$domain\";"
echo -e "\n# additional codes\noption classless-ipv4-route code 121 = array of { unsigned integer 8 };\n"
rm -f /tmp/resolv.conf
echo "# This file is generated by the DHCPD service" > /tmp/resolv.conf
[ -n "$domain" ] && echo "domain $domain" >> /tmp/resolv.conf
echo "nameserver 127.0.0.1" >> /tmp/resolv.conf
}
# base procd hooks
boot() {
DHCPD_BOOT=1
start "$@"
}
start_service() {
local domain dhcp_ifs authoritative dynamicdns
if [ -n "$DHCPD_BOOT" ] ; then
return 0
fi
if [ ! -e $lease_file ] ; then
touch $lease_file
fi
if [ -e "/etc/dhcpd.conf" ] ; then
config_file="/etc/dhcpd.conf"
else
. /lib/functions/network.sh
local dyn_file=$(mktemp -u /tmp/dhcpd.XXXXXX)
config_load dhcp
local rfc1918_nets=""
# alas we have to make 2 passes...
dhcp_ifs=
config_foreach dhcpd_add dhcp 0
rfc1918_nets="$(echo "$rfc1918_nets" | tr ' ' $'\n' | sort | uniq | tr $'\n' ' ')"
general_config > $config_file
if [ $dynamicdns -eq 1 ]; then
cat <<EOF > $dyn_file
; Generated by /etc/init.d/dhcpd at $(date)
ttl $TTL
EOF
fi
rfc1918_nets=
dhcp_ifs=
config_foreach dhcpd_add dhcp 1 >> $config_file
static_hosts >> $config_file
static_cnames >> $config_file
static_domains >> $config_file
static_mxhosts >> $config_file
static_srvhosts >> $config_file
if [ $dynamicdns -eq 1 ]; then
local args=
no_ipv6 && args="-4"
nsupdate -l -v $args $dyn_file
fi
rm -f $dyn_file
[ -z "$dhcp_ifs" ] && return 0
fi
procd_open_instance
procd_set_param command $PROG -q -f -cf $config_file -lf $lease_file $dhcp_ifs
procd_close_instance
}
reload_service() {
rc_procd start_service "$@"
procd_send_signal dhcpd "$@"
}
add_interface_trigger() {
local cfg=$1
local trigger ignore
config_get trigger "$cfg" interface
config_get_bool ignore "$cfg" ignore 0
if [ -n "$trigger" -a $ignore -eq 0 ] ; then
procd_add_reload_interface_trigger "$trigger"
fi
}
service_triggers() {
if [ -n "$DHCPD_BOOT" ] ; then
# Make the first start robust to slow interfaces; wait a while
procd_add_raw_trigger "interface.*.up" 5000 /etc/init.d/dhcpd restart
else
# reload with normal parameters
procd_add_reload_trigger "network" "dhcp"
config_load dhcp
config_foreach add_interface_trigger dhcp
fi
}