#!/bin/sh /etc/rc.common START=25 USE_PROCD=1 PROG=/usr/sbin/dhcpd 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 )) } trim() { local arg="$1" echo "$arg" | sed -e 's/^ *//' -e 's/ *$//' } rfc1918_prefix() { local octets="$(echo "${1%%/*}" | cut -d. -f1)" [ "$octets" = "10" ] && { echo "$octets"; return; } octets="$(echo "${1%%/*}" | cut -d. -f1-2)" case "$octets" in 172.1[6789]|172.2[0-9]|172.3[01]|192.168) echo "$octets" ;; 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" family="$2" type="$3" shift 3 [ $dynamicdns -eq 1 ] && \ echo -e "$PREFIX" "$lhs $family $type $@\nsend" >> $dyn_file } explode() { local arg="$1" echo "$arg" | sed -e 's/\./, /g' } 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_empty_zone() { local zone="$1" if [ ! -f $dyndir/db."$zone" ]; then cp -p /etc/bind/db.empty $dyndir/db."$zone" chmod g+w $dyndir/db."$zone" chgrp bind $dyndir/db."$zone" fi } append_routes() { local tuple tuples="$1" local string= local IFS=',' for tuple in $tuples; do local network prefix router save octets compacted tuple="$(trim "$tuple")" save="${tuple% *}" router="$(trim "${tuple#${save} }")" network="$(trim "${save%/[0-9]*}")" prefix="$(trim "${save##${network}/}")" octets=$((($prefix + 7) / 8)) compacted="$(echo "$network" | cut -d. -f1-$octets)" string="${string:+, }$(explode "$prefix.$compacted.$router")" done echo " option classless-ipv4-route $string;" } 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 formatted="$formatted${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 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 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 config_list_foreach "$cfg" "routes" append_routes config_list_foreach "$cfg" "dhcp_option" append_dhcp_options echo "}" done ips="$ip" for ip in $ips; do revip="$(rev_str "$ip" ".")" update "$name.$domain." IN A "$ip" update "$revip.in-addr.arpa." IN PTR "$name.$domain." done } static_hosts() { config_foreach static_host_add host "$@" } gen_dhcp_subnet() { local cfg="$1" echo "subnet $NETWORK netmask $NETMASK {" echo " range $START $END;" echo " option subnet-mask $netmask;" if [ "$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 echo " option routers $gateway;" echo " option domain-name-servers $DNS;" config_list_foreach "$cfg" "routes" append_routes config_list_foreach "$cfg" "dhcp_option" append_dhcp_options echo "}" } dhcpd_add() { local cfg="$1" synthesize="$2" local dhcp6range="::" local dynamicdhcp end gateway ifname ignore leasetime limit net netmask local proto networkid start subnet local IP NETMASK BROADCAST NETWORK PREFIX DNS START END config_get_bool ignore "$cfg" "ignore" 0 [ "$ignore" = "0" ] || return 0 config_get net "$cfg" "interface" [ -n "$net" ] || return 0 config_get start "$cfg" "start" [ -n "$start" ] || return 0 config_get limit "$cfg" "limit" [ -n "$limit" ] || return 0 network_get_subnet subnet "$net" || return 0 network_get_device ifname "$net" || return 0 network_get_protocol proto "$net" || return 0 [ static = "$proto" ] || return 0 local octets="$(rfc1918_prefix "$subnet")" [ -n "$octets" ] && rfc1918_nets="$rfc1918_nets${rfc1918_nets:+ }$octets" [ $synthesize -eq 0 ] && return config_get_bool dynamicdhcp "$cfg" "dynamicdhcp" 1 dhcp_ifs="$dhcp_ifs $ifname" eval "$(ipcalc.sh $subnet $start $limit)" 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 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 if [ $dynamicdns -eq 1 ]; then create_empty_zone "$domain" local mynet for mynet in $rfc1918_nets; do mynet="$(rev_str "$mynet" ".")" create_empty_zone "$mynet.in-addr.arpa" done local need_reload= cp -p $conf_local_file ${conf_local_file}_ cmp -s $conf_local_file ${conf_local_file}_ || need_reload=1 rm -f ${conf_local_file}_ cat < $conf_local_file zone "$domain" { type master; file "$dyndir/db.$domain"; update-policy { grant $session_key_name zonesub any; }; }; EOF for mynet in $rfc1918_nets; do mynet="$(rev_str "$mynet" ".")" cat <> $conf_local_file zone "$mynet.in-addr.arpa" { type master; file "$dyndir/db.$mynet.in-addr.arpa"; update-policy { grant $session_key_name zonesub any; }; }; EOF done [ -n "$need_reload" ] && /etc/init.d/named reload sleep 1 cat < /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... 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 < $dyn_file ; Generated by /etc/init.d/dhcpd at $(date) ttl $TTL EOF fi rfc1918_nets= 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 }