#!/bin/bash ############################################################################### # # # IPFire.org - A linux based firewall # # Copyright (C) 2012 IPFire Network Development Team # # # # 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 . # # # ############################################################################### DHCPV6D_CONFIG_FILE="/etc/dhcp/dhcpd6.conf" DHCPV4D_CONFIG_FILE="/etc/dhcp/dhcpd.conf" DHCPV6D_CONFIG_DIR="${NETWORK_CONFIG_DIR}/dhcpd/ipv6" DHCPV4D_CONFIG_DIR="${NETWORK_CONFIG_DIR}/dhcpd/ipv4" DHCPV6D_OPTIONS_FILE="${DHCPV6D_CONFIG_DIR}/options" DHCPV4D_OPTIONS_FILE="${DHCPV4D_CONFIG_DIR}/options" DHCPV6D_SETTINGS_FILE="${DHCPV6D_CONFIG_DIR}/settings" DHCPV4D_SETTINGS_FILE="${DHCPV4D_CONFIG_DIR}/settings" DHCPD_SETTINGS="\ AUTHORITATIVE " DHCPV6D_SETTINGS="\ ${DHCPD_SETTINGS} \ PREFERRED_LIFETIME \ VALID_LIFETIME " DHCPV4D_SETTINGS="\ ${DHCPD_SETTINGS} \ DEFAULT_LEASE_TIME \ MAX_LEASE_TIME \ MIN_LEASE_TIME \ " DHCPD_SUBNET_PREFIX="subnet-" #DHCPD_SUBNET_POOL_PREFIX="pool-" DHCPD_SUBNET_RANGE_PREFIX="range-" DHCPD_SUBNET_SETTINGS="ADDRESS PREFIX" DHCPV6D_SUBNET_SETTINGS="${DHCPD_SUBNET_SETTINGS} PREFIX_DELEGATION \ DELEGATED_PREFIX_FIRST DELEGATED_PREFIX_LAST DELEGATED_PREFIX_SIZE" DHCPV4D_SUBNET_SETTINGS="${DHCPD_SUBNET_SETTINGS} ROUTERS" DHCPD_SUBNET_RANGE_SETTINGS="START END" DHCPV6D_SUBNET_RANGE_SETTINGS="${DHCPD_SUBNET_RANGE_SETTINGS}" DHCPV4D_SUBNET_RANGE_SETTINGS="${DHCPD_SUBNET_RANGE_SETTINGS}" DHCPV6D_OPTIONS="\ domain-search \ name-servers \ " DHCPV4D_OPTIONS="\ all-subnets-local \ arp-cache-timeout \ bootfile-name \ broadcast-address \ default-ip-ttl \ default-tcp-ttl \ dhcp-client-identifier \ dhcp-lease-time \ dhcp-max-message-size \ dhcp-rebinding-time \ dhcp-renewal-time \ domain-name \ domain-name-servers \ domain-search \ interface-mtu \ ntp-servers \ root-path \ routers \ tftp-server-name \ " DHCPV6D_SUBNET_OPTIONS="${DHCPV6D_OPTIONS}" DHCPV4D_SUBNET_OPTIONS="${DHCPV4D_OPTIONS}" # Global defaults DHCP_DEFAULT_DELEGATED_PREFIX_SIZE="64" # Defaults for DHCPv6. DHCPV6D_PREFERRED_LIFETIME="" DHCPV6D_VALID_LIFETIME="43200" # 12h # Defaults for DHCPv4. DHCPV4D_AUTHORITATIVE="true" DHCPV4D_DEFAULT_LEASE_TIME="43200" # 12h DHCPV4D_MAX_LEASE_TIME="86400" # 24h DHCPV4D_MIN_LEASE_TIME="" dhcpd_service() { case "${1}" in ipv6) print "dhcpd6.service" ;; ipv4) print "dhcpd.service" ;; "") print "dhcpd6.service dhcp.service" ;; esac return ${EXIT_OK} } dhcpd_start() { local services=$(dhcpd_service "$@") local service for service in ${services}; do service_start ${service} done } dhcpd_stop() { local services=$(dhcpd_service "$@") local service for service in ${services}; do service_stop ${service} done } dhcpd_restart() { # DHCP does not support a reload, so # we retsart it. local services=$(dhcpd_service "$@") local service for service in ${services}; do service_restart ${service} done } dhcpd_reload() { dhcpd_restart "$@" } dhcpd_enable() { local services=$(dhcpd_service "$@") local service for service in ${services}; do service_enable ${service} done } dhcpd_disable() { local services=$(dhcpd_service "$@") local service for service in ${services}; do service_disable ${service} done } dhcpd_edit() { local proto=${1} assert isset proto shift local settings=$(dhcpd_settings ${proto}) assert isset settings local ${settings} dhcpd_global_settings_read ${proto} case "${proto}" in ipv6) _dhcpd_edit_ipv6 "$@" || return $? ;; ipv4) _dhcpd_edit_ipv4 "$@" || return $? ;; esac dhcpd_global_settings_write ${proto} } _dhcpd_edit_ipv4() { local val while [ $# -gt 0 ]; do case "${1}" in --authoritative=*) val=$(cli_get_val "${1}") if enabled val; then AUTHORITATIVE="true" else AUTHORITATIVE="false" fi ;; --default-lease-time=*) local val=$(cli_get_val "${1}") DEFAULT_LEASE_TIME=$(parse_time ${val}) if ! isinteger DEFAULT_LEASE_TIME; then error "Invalid value for --default-lease-time: ${val}" return ${EXIT_ERROR} fi ;; --max-lease-time=*) local val=$(cli_get_val "${1}") MAX_LEASE_TIME=$(parse_time ${val}) if ! isinteger MAX_LEASE_TIME; then error "Invalid value for --max-lease-time: ${val}" return ${EXIT_ERROR} fi ;; --min-lease-time=*) local val=$(cli_get_val "${1}") MIN_LEASE_TIME=$(parse_time ${val}) if isset MIN_LEASE_TIME; then if ! isinteger MIN_LEASE_TIME; then error "Invalid value for --min-lease-time: ${val}" return ${EXIT_ERROR} fi fi ;; *) error "Unrecognized argument: ${1}" return ${EXIT_ERROR} ;; esac shift done if [ ${MAX_LEASE_TIME} -le ${DEFAULT_LEASE_TIME} ]; then error "The max. lease time must be higher than the default lease time." return ${EXIT_ERROR} fi } _dhcpd_edit_ipv6() { while [ $# -gt 0 ]; do case "${1}" in --preferred-lifetime=*) local val=$(cli_get_val "${1}") PREFERRED_LIFETIME=$(parse_time ${val}) if ! isinteger PREFERRED_LIFETIME; then error "Invalid value for --preferred-lifetime: ${val}" return ${EXIT_ERROR} fi ;; --valid-lifetime=*) local val=$(cli_get_val "${1}") VALID_LIFETIME=$(parse_time ${val}) if ! isinteger VALID_LIFETIME; then error "Invalid value for --valid-lifetime: ${val}" return ${EXIT_ERROR} fi ;; *) error "Unrecognized argument: ${1}" return ${EXIT_ERROR} ;; esac shift done } dhcpd_settings_file() { local proto=${1} assert isset proto case "${proto}" in ipv6) print "${DHCPV6D_SETTINGS_FILE}" ;; ipv4) print "${DHCPV4D_SETTINGS_FILE}" ;; esac return ${EXIT_OK} } dhcpd_settings() { local proto=${1} assert isset proto case "${proto}" in ipv6) print "${DHCPV6D_SETTINGS}" ;; ipv4) print "${DHCPV4D_SETTINGS}" ;; esac return ${EXIT_OK} } dhcpd_options_file() { local proto=${1} assert isset proto case "${proto}" in ipv6) print "${DHCPV6D_OPTIONS_FILE}" ;; ipv4) print "${DHCPV4D_OPTIONS_FILE}" ;; esac return ${EXIT_OK} } dhcpd_options_list() { local proto=${1} assert isset proto case "${proto}" in ipv6) print "DHCPV6D_OPTIONS" ;; ipv4) print "DHCPV4D_OPTIONS" ;; esac return ${EXIT_OK} } dhcpd_options() { local proto=${1} assert isset proto case "${proto}" in ipv6) print "${DHCPV6D_OPTIONS}" ;; ipv4) print "${DHCPV4D_OPTIONS}" ;; esac return ${EXIT_OK} } dhcpd_global_settings_list() { local proto="${1}" assert isset proto dhcpd_settings "${proto}" } dhcpd_global_settings_defaults() { local proto=${1} assert isset proto local settings=$(dhcpd_settings ${proto}) assert isset settings local prefix="DHCPV${proto/ipv/}D_" local setting setting_default for setting in ${settings}; do setting_default="${prefix}${setting}" printf -v ${setting} "%s" "${!setting_default}" done } dhcpd_global_settings_read() { local proto=${1} assert isset proto local file=$(dhcpd_settings_file ${proto}) assert isset file local settings=$(dhcpd_settings ${proto}) assert isset settings dhcpd_global_settings_defaults ${proto} settings_read ${file} ${settings} } dhcpd_global_settings_write() { local proto=${1} assert isset proto local file=$(dhcpd_settings_file ${proto}) assert isset file local settings=$(dhcpd_settings ${proto}) assert isset settings settings_write ${file} ${settings} } dhcpd_global_options_read() { local proto=${1} assert isset proto local options_file=$(dhcpd_options_file ${proto}) local options_list=$(dhcpd_options_list ${proto}) settings_read_array ${options_file} options ${!options_list} # Check if domain-name is set. if [ -z "${options["domain-name"]}" ]; then options["domain-name"]=$(config_domainname) fi } dhcpd_subnet_escape() { assert [ $# -eq 1 ] local subnet="${1}" # Escape any slashes subnet="${subnet//\//-}" print "${subnet}" } dhcpd_subnet_unescape() { assert [ $# -eq 1 ] local subnet="${1}" # Unescape any slashes subnet="${subnet//-/\/}" print "${subnet}" } dhcpd_subnet_path() { assert [ $# -ge 1 -a $# -le 2 ] local proto=${1} local subnet=${2} local path case "${proto}" in ipv6) path=${DHCPV6D_CONFIG_DIR} ;; ipv4) path=${DHCPV4D_CONFIG_DIR} ;; esac assert isset path if ! isset subnet; then print "${path}" return ${EXIT_OK} fi # Escape subnet subnet="$(dhcpd_subnet_escape ${subnet})" # Add path prefix subnet="${DHCPD_SUBNET_PREFIX}${subnet}" print "${path}/${subnet}" return ${EXIT_OK} } dhcpd_subnet_exists() { local proto=${1} assert isset proto local subnet=${2} assert isset subnet local path=$(dhcpd_subnet_path ${proto} ${subnet}) assert isset path [ -d "${path}" ] && return ${EXIT_TRUE} || return ${EXIT_FALSE} } dhcpd_subnet_match() { local proto=${1} assert isset proto local subnet=${2} assert isset subnet local settings=$(dhcpd_subnet_settings ${proto}) assert isset settings local _subnet ${settings} for _subnet in $(dhcpd_subnet_list ${proto}); do dhcpd_subnet_read ${proto} ${_subnet} ${proto}_addr_eq "${ADDRESS}/${PREFIX}" "${subnet}" \ && return ${EXIT_TRUE} done return ${EXIT_FALSE} } dhcpd_subnet_exists() { dhcpd_subnet_match "$@" } dhcpd_subnet_new() { local proto=${1} assert isset proto shift dhcpd_subnet_edit ${proto} "new" "$@" } dhcpd_subnet_edit() { assert [ $# -ge 2 ] local proto=${1} local subnet=${2} shift 2 local mode="edit" if [ "${subnet}" = "new" ]; then mode="new" subnet="" fi local settings case "${proto}" in ipv6) settings=${DHCPV6D_SUBNET_SETTINGS} ;; ipv4) settings=${DHCPV4D_SUBNET_SETTINGS} ;; esac assert isset settings local ${settings} # Read current settings if [ "${mode}" = "edit" ]; then dhcpd_subnet_read ${proto} ${subnet} fi while [ $# -gt 0 ]; do case "${proto},${mode},${1}" in # Common options ipv6,new,*:*/*|ipv4,new,*.*.*.*/*) local subnet="$(cli_get_val "${1}")" ADDRESS="$(ip_split_prefix ${subnet})" PREFIX="$(ip_get_prefix ${subnet})" ;; # IPv6 options ipv6,*,--delegated-prefix=*) local subnet="$(cli_get_val "${1}")" if [[ -n "${subnet}" ]]; then local delegated_prefix_first="${subnet%-*}" local delegated_prefix_last="${subnet#*-}" # Check for correct syntax if ! isset delegated_prefix_first || ! isset delegated_prefix_last; then error "--delegated-prefix= must be formatted like 2001:db8:aaaa::-2001:db8:bbbb::" return ${EXIT_ERROR} fi # Check if the addresses are correct local addr for addr in ${delegated_prefix_first} ${delegated_prefix_last}; do if ! ipv6_is_valid "${addr}"; then error "Invalid IP address: ${addr}" return ${EXIT_ERROR} fi done # Make sure this is a range if ! ipv6_addr_gt "${delegated_prefix_last}" "${delegated_prefix_first}"; then error "--delegated-prefix: The second address must be larger than the first one" return ${EXIT_ERROR} fi PREFIX_DELEGATION="on" DELEGATED_PREFIX_FIRST="${delegated_prefix_first}" DELEGATED_PREFIX_LAST="${delegated_prefix_last}" # Prefix delegation has been disabled else PREFIX_DELEGATION="off" fi ;; ipv6,*,--delegated-prefix-size=*) local prefix_size="$(cli_get_val "${1}")" if ipv6_prefix_size_is_valid_for_delegation "${prefix_size}"; then DELEGATED_PREFIX_SIZE="${prefix_size}" else error "Invalid prefix size for prefix delegation: ${prefix_size}" return ${EXIT_ERROR} fi ;; # IPv4 options ipv4,*,--routers=*) ROUTERS=$(cli_get_val "${1}") ;; *) error "Unknown argument: ${1}" return ${EXIT_ERROR} ;; esac shift done if ! ${proto}_is_valid ${ADDRESS} || ! ${proto}_prefix_is_valid ${PREFIX}; then error "Invalid subnet: ${ADDRESS}/${PREFIX}" return ${EXIT_ERROR} fi # XXX Check for subnet collisions! case "${mode}" in new) if dhcpd_subnet_exists ${proto} "${ADDRESS}/${PREFIX}"; then error "DHCP subnet configuration already exists for subnet ${ADDRESS}/${PREFIX}" return ${EXIT_ERROR} fi ;; esac local file="$(dhcpd_subnet_path ${proto} "${ADDRESS}/${PREFIX}")/settings" settings_write ${file} ${settings} } dhcpd_subnet_remove() { assert [ $# -eq 2 ] local proto=${1} local subnet=${2} local path=$(dhcpd_subnet_path ${proto} ${subnet}) assert isset path # Remove everything of this subnet. rm -rf ${path} } dhcpd_subnet_list() { local proto=${1} assert isset proto local path=$(dhcpd_subnet_path ${proto}) # Return an error of the directory does not exist. [ -d "${path}" ] || return ${EXIT_ERROR} local p for p in ${path}/${DHCPD_SUBNET_PREFIX}*; do [ -d "${p}" ] || continue p=$(basename ${p}) p="${p:${#DHCPD_SUBNET_PREFIX}}" # Return unescaped subnet dhcpd_subnet_unescape "${p}" done } dhcpd_subnet_read() { local proto=${1} assert isset proto local subnet=${2} assert isset subnet local file="$(dhcpd_subnet_path ${proto} ${subnet})/settings" settings_read ${file} } dhcpd_subnet_range_path() { assert [ $# -ge 2 -a $# -le 3 ] local proto=${1} local subnet=${2} local range=${3} if ! isset range; then dhcpd_subnet_path ${proto} ${subnet} return $? fi # Add prefix range="${DHCPD_SUBNET_RANGE_PREFIX}${range}" print "$(dhcpd_subnet_path ${proto} ${subnet})/${range}" return ${EXIT_OK} } dhcpd_subnet_range_settings() { local proto=${1} case "${proto}" in ipv6) print "${DHCPV6D_SUBNET_RANGE_SETTINGS}" ;; ipv4) print "${DHCPV4D_SUBNET_RANGE_SETTINGS}" ;; esac return ${EXIT_OK} } dhcpd_subnet_range_new() { local proto=${1} assert isset proto shift local subnet=${1} assert isset subnet shift local settings case "${proto}" in ipv6) settings=${DHCPV6D_SUBNET_RANGE_SETTINGS} ;; ipv4) settings=${DHCPV4D_SUBNET_RANGE_SETTINGS} ;; esac assert isset settings local range ${settings} while [ $# -gt 0 ]; do case "${1}" in *-*) range=${1} START="${range%-*}" END="${range#*-}" ;; *) error "Unknown argument: ${1}" return ${EXIT_ERROR} ;; esac shift done local var for var in START END; do if ! ${proto}_is_valid ${!var}; then error "'${!var}' is not a valid IP address" return ${EXIT_ERROR} fi done # Check if the end address is larger than the start address if ! ${proto}_addr_gt "${END}" "${START}"; then error "The end address of the range must be greater than the start address" return ${EXIT_ERROR} fi # Check if range already exists if dhcpd_subnet_range_exists ${proto} ${subnet} ${range}; then error "DHCP subnet range ${range} already exists" return ${EXIT_ERROR} fi # Search for any overlaps local overlaps=$(dhcpd_subnet_range_overlaps ${proto} ${subnet} ${range}) if isset overlaps; then error "DHCP subnet range ${range} overlaps with ${overlaps}" return ${EXIT_ERROR} fi # Write the configuration to file. local file=$(dhcpd_subnet_range_path ${proto} ${subnet} ${range}) assert isset file settings_write ${file} ${settings} log INFO "DHCP subnet range ${range} created" return ${EXIT_OK} } dhcpd_subnet_range_remove() { assert [ $# -eq 3 ] local proto=${1} local subnet=${2} local range=${3} if ! dhcpd_subnet_range_exists ${proto} ${subnet} ${range}; then error "DHCP subnet range ${range} does not exist" return ${EXIT_ERROR} fi local path=$(dhcpd_subnet_range_path ${proto} ${subnet} ${range}) assert isset path rm -f ${path} log INFO "DHCP subnet range ${range} removed" return ${EXIT_OK} } dhcpd_subnet_range_list() { assert [ $# -eq 2 ] local proto=${1} local subnet=${2} local path=$(dhcpd_subnet_range_path ${proto} ${subnet}) local p for p in ${path}/${DHCPD_SUBNET_RANGE_PREFIX}*; do [ -r "${p}" ] || continue p=$(basename ${p}) print "${p:${#DHCPD_SUBNET_RANGE_PREFIX}}" done return ${EXIT_OK} } dhcpd_subnet_range_read() { assert [ $# -eq 3 ] local proto=${1} local subnet=${2} local range=${3} local file=$(dhcpd_subnet_range_path ${proto} ${subnet} ${range}) settings_read ${file} } dhcpd_subnet_range_exists() { assert [ $# -eq 3 ] local proto=${1} local subnet=${2} local range=${3} local start=${range%-*} local end=${range#*-} assert isset start assert isset end local settings=$(dhcpd_subnet_range_settings ${proto}) local r ${settings} for r in $(dhcpd_subnet_range_list ${proto} ${subnet}); do dhcpd_subnet_range_read ${proto} ${subnet} ${r} # If start and end addresses equal we got a match if ${proto}_addr_eq "${START}" "${start}" && ${proto}_addr_eq "${END}" "${end}"; then return ${EXIT_TRUE} fi done return ${EXIT_FALSE} } dhcpd_subnet_range_overlaps() { assert [ $# -eq 3 ] local proto=${1} local subnet=${2} local range=${3} local start=${range%-*} local end=${range#*-} assert isset start assert isset end local settings=$(dhcpd_subnet_range_settings ${proto}) local r ${settings} for r in $(dhcpd_subnet_range_list ${proto} ${subnet}); do dhcpd_subnet_range_read ${proto} ${subnet} ${r} # Check if the new range is a sub-range of any existing range # Check if the start address is somewhere in this range if ${proto}_addr_ge ${START} ${start} && ${proto}_addr_le ${START} ${end}; then print "${r}" return ${EXIT_TRUE} fi # Check if the end address is somewhere in this range if ${proto}_addr_ge ${END} ${start} && ${proto}_addr_le ${END} ${end}; then print "${r}" return ${EXIT_TRUE} fi # Check if any existing range is a sub-range of the new range # Check if the start address is somewhere in this range if ${proto}_addr_ge ${start} ${START} && ${proto}_addr_le ${start} ${END}; then print "${r}" return ${EXIT_TRUE} fi # Check if the end address is somewhere in this range if ${proto}_addr_ge ${end} ${START} && ${proto}_addr_le ${end} ${END}; then print "${r}" return ${EXIT_TRUE} fi done return ${EXIT_FALSE} } dhcpd_subnet_settings() { local proto=${1} case "${proto}" in ipv6) print "${DHCPV6D_SUBNET_SETTINGS}" ;; ipv4) print "${DHCPV4D_SUBNET_SETTINGS}" ;; esac return ${EXIT_OK} } dhcpd_subnet_options_file() { local path=$(dhcpd_subnet_path "$@") assert isset path print "${path}/options" } dhcpd_subnet_options_list() { local proto=${1} assert isset proto case "${proto}" in ipv6) print "${DHCPV6D_SUBNET_OPTIONS}" ;; ipv4) print "${DHCPV4D_SUBNET_OPTIONS}" ;; esac return ${EXIT_OK} } dhcpd_subnet_options_read() { local proto=${1} assert isset proto local subnet=${2} assert isset subnet local options_file=$(dhcpd_subnet_options_file ${proto} ${subnet}) local options_list=$(dhcpd_subnet_options_list ${proto}) _dhcpd_read_options ${options_file} ${options_list} } # Helper functions to create a DHCP configuration file. _dhcpd_write_options() { local proto=${1} assert isset proto local file=${2} assert isset file local options_list=${3} assert isset options_list local ident=${4} print "${ident}# Options" >> ${file} # Dump options array. local key val fmt for key in ${!options_list}; do val=${options[${key}]} # Skip the rest if val is empty isset val || continue # Enable raw formatting (i.e. quote the value?) local raw="false" # Update the formatting of some options case "${key}" in name-servers) val="$(list_join val ", ")" raw="true" ;; esac # Prepend dhcp6 on IPv6 options. if [ "${proto}" = "ipv6" ]; then key="dhcp6.${key}" fi if isinteger val; then fmt="option %s %d;" elif enabled raw || isipaddress val; then fmt="option %s %s;" else fmt="option %s \"%s\";" fi print "${ident}${fmt}" "${key}" "${val}" done >> ${file} # Empty line print >> ${file} } _dhcpd_read_options() { local file=${1} assert isset file local options_list=${2} assert isset options_list settings_read_array ${file} options ${!options_list} } _dhcpd_write_subnet() { assert [ $# -eq 3 ] local proto=${1} local subnet=${2} local file=${3} # Check which settings we do expect. local settings case "${proto}" in ipv6) settings=${DHCPV6D_SUBNET_SETTINGS} ;; ipv4) settings=${DHCPV4D_SUBNET_SETTINGS} ;; esac assert isset settings local ${settings} # Read configuration settings. dhcpd_subnet_read ${proto} ${subnet} print "# Subnet declaration" >> ${file} case "${proto}" in ipv6) print "subnet6 ${ADDRESS}/${PREFIX} {" >> ${file} ;; ipv4) local netmask="$(ipv4_prefix2netmask "${PREFIX}")" print "subnet ${ADDRESS} netmask ${netmask} {" >> ${file} ;; esac # Add options. _dhcpd_write_subnet_options ${proto} ${subnet} ${file} # Prefix Delegation for IPv6 if [[ "${proto}" = "ipv6" ]]; then _dhcpd_write_subnet_pd "${subnet}" "${file}" fi # Add the ranges. local range for range in $(dhcpd_subnet_range_list ${proto} ${subnet} ${range}); do _dhcpd_write_subnet_range ${proto} ${subnet} ${range} ${file} done # End this subnet block. print "}\n" >> ${file} return ${EXIT_OK} } _dhcpd_write_subnet_options() { assert [ $# -eq 3 ] local proto=${1} local subnet=${2} local file=${3} local settings local options_file="$(dhcpd_subnet_path ${proto} ${subnet})/options" local options_list case "${proto}" in ipv6) settings=${DHCPV6D_SUBNET_SETTINGS} options_list="DHCPV6D_OPTIONS" ;; ipv4) settings=${DHCPV4D_SUBNET_SETTINGS} options_list="DHCPV4D_OPTIONS" ;; esac assert isset settings assert isset options_list local ${settings} dhcpd_subnet_read ${proto} ${subnet} local -A options _dhcpd_read_options ${options_file} ${options_list} # Fill in router, if not already set. if [ -z "${options["routers"]}" ]; then options["routers"]=$(_dhcpd_search_routers ${proto} "${ADDRESS}/${PREFIX}") fi _dhcpd_write_options ${proto} ${file} ${options_list} "\t" } _dhcpd_write_subnet_pd() { # Nothing to do if prefix delegation is not enabled enabled PREFIX_DELEGATION || return ${EXIT_OK} assert [ $# -eq 2 ] local subnet="${1}" local file="${2}" local prefix_size="${DELEGATED_PREFIX_SIZE}" isset prefix_size || prefix_size="${DHCP_DEFAULT_DELEGATED_PREFIX_SIZE}" assert ipv6_is_valid "${DELEGATED_PREFIX_FIRST}" assert ipv6_is_valid "${DELEGATED_PREFIX_LAST}" assert ipv6_prefix_size_is_valid_for_delegation "${prefix_size}" ( print " # Prefix Delegation" print " prefix6 ${DELEGATED_PREFIX_FIRST} ${DELEGATED_PREFIX_LAST} /${prefix_size};" print "" ) >> "${file}" } _dhcpd_search_routers() { assert [ $# -eq 2 ] local proto=${1} local subnet=${2} # Do nothing for IPv6 (yet?). [ "${proto}" = "ipv6" ] && return ${EXIT_OK} local routers zone addr for zone in $(zones_get_all); do addr="$(db_get "${zone}/${proto}/local-ip-address")" isset addr || continue if ipv4_in_subnet ${addr} ${subnet}; then list_append routers $(ip_split_prefix ${addr}) fi done list_join routers ", " } _dhcpd_write_subnet_range() { assert [ $# -eq 4 ] local proto=${1} local subnet=${2} local range=${3} local file=${4} local settings=$(dhcpd_subnet_range_settings ${proto}) assert isset settings # Read the configuration settings. local ${settings} dhcpd_subnet_range_read ${proto} ${subnet} ${range} case "${proto}" in ipv6) print " range6 ${START} ${END};" >> ${file} ;; ipv4) print " range ${START} ${END};" >> ${file} ;; esac print >> ${file} return ${EXIT_OK} } dhcpd_write_config() { assert [ $# -eq 1 ] local proto=${1} local file options_list case "${proto}" in ipv6) file=${DHCPV6D_CONFIG_FILE} options_list="DHCPV6D_OPTIONS" ;; ipv4) file=${DHCPV4D_CONFIG_FILE} options_list="DHCPV4D_OPTIONS" ;; esac assert isset file assert isset options_list # Writing header. config_header "DHCP ${proto} daemon configuration file" > ${file} # Read global DHCP configuration dhcpd_global_settings_read "${proto}" # Authoritative. if enabled AUTHORITATIVE; then ( print "# This is an authoritative DHCP server for this network." print "authoritative;\n" ) >> ${file} else ( print "# This is NOT an authoritative DHCP server for this network." print "not authoritative;\n" ) >> ${file} fi case "${proto}" in ipv6) # Lease times. if isinteger VALID_LIFETIME; then print "default-lease-time %d;" "${VALID_LIFETIME}" >> ${file} fi if isinteger PREFERRED_LIFETIME; then print "preferred-lifetime %d;" "${PREFERRED_LIFETIME}" >> ${file} fi ;; ipv4) # Lease times. if isinteger DEFAULT_LEASE_TIME; then print "default-lease-time %d;" "${DEFAULT_LEASE_TIME}" >> ${file} fi if isinteger MAX_LEASE_TIME; then print "max-lease-time %d;" "${MAX_LEASE_TIME}" >> ${file} fi if isinteger MIN_LEASE_TIME; then print "min-lease-time %d;" "${MIN_LEASE_TIME}" >> ${file} fi ;; esac # Write the options to file. local -A options dhcpd_global_options_read ${proto} _dhcpd_write_options ${proto} ${file} ${options_list} # Add all subnet declarations. local subnet for subnet in $(dhcpd_subnet_list ${proto}); do _dhcpd_write_subnet ${proto} ${subnet} ${file} done return ${EXIT_OK} }