--- /dev/null
+#!/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 <http://www.gnu.org/licenses/>. #
+# #
+###############################################################################
+
+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 ROUTERS"
+DHCPV6D_SUBNET_SETTINGS="${DHCPD_SUBNET_SETTINGS}"
+DHCPV4D_SUBNET_SETTINGS="${DHCPD_SUBNET_SETTINGS}"
+
+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 \
+"
+
+DHCPD_SUBNET_OPTIONS="${DHCPD_OPTIONS}"
+DHCPV6D_SUBNET_OPTIONS="${DHCPD_SUBNET_OPTIONS}"
+DHCPV4D_SUBNET_OPTIONS="${DHCPD_SUBNET_OPTIONS}"
+
+# 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=""
+
+function dhcpd_service() {
+ case "${1}" in
+ ipv6)
+ print "dhcpd6.service"
+ ;;
+ ipv4)
+ print "dhcpd.service"
+ ;;
+ "")
+ print "dhcpd6.service dhcp.service"
+ ;;
+ esac
+
+ return ${EXIT_OK}
+}
+
+function dhcpd_start() {
+ local services=$(dhcpd_service $@)
+
+ local service
+ for service in ${services}; do
+ service_start ${service}
+ done
+}
+
+function dhcpd_stop() {
+ local services=$(dhcpd_service $@)
+
+ local service
+ for service in ${services}; do
+ service_stop ${service}
+ done
+}
+
+function 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
+}
+
+function dhcpd_reload() {
+ dhcpd_restart $@
+}
+
+function 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}
+}
+
+function _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=*)
+ DEFAULT_LEASE_TIME=$(cli_get_val ${1})
+
+ if ! isinteger DEFAULT_LEASE_TIME; then
+ error "Invalid value for --default-lease-time."
+ return ${EXIT_ERROR}
+ fi
+ ;;
+ --max-lease-time=*)
+ MAX_LEASE_TIME=$(cli_get_val ${1})
+
+ if ! isinteger MAX_LEASE_TIME; then
+ error "Invalid value for --max-lease-time."
+ return ${EXIT_ERROR}
+ fi
+ ;;
+ --min-lease-time=*)
+ MIN_LEASE_TIME=$(cli_get_val ${1})
+
+ if isset MIN_LEASE_TIME; then
+ if ! isinteger MIN_LEASE_TIME; then
+ error "Invalid value for --min-lease-time."
+ 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
+}
+
+function _dhcpd_edit_ipv6() {
+ while [ $# -gt 0 ]; do
+ case "${1}" in
+ --preferred-lifetime=*)
+ PREFERRED_LIFETIME=$(cli_get_val ${1})
+
+ if ! isinteger PREFERRED_LIFETIME; then
+ error "Invalid value for --preferred-lifetime."
+ return ${EXIT_ERROR}
+ fi
+ ;;
+ --valid-lifetime=*)
+ VALID_LIFETIME=$(cli_get_val ${1})
+
+ if ! isinteger VALID_LIFETIME; then
+ error "Invalid value for --valid-lifetime."
+ return ${EXIT_ERROR}
+ fi
+ ;;
+ *)
+ error "Unrecognized argument: ${1}"
+ return ${EXIT_ERROR}
+ ;;
+ esac
+ shift
+ done
+}
+
+function 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}
+}
+
+function dhcpd_settings() {
+ local proto=${1}
+ assert isset proto
+
+ case "${proto}" in
+ ipv6)
+ print "${DHCPV6D_SETTINGS}"
+ ;;
+ ipv4)
+ print "${DHCPV4D_SETTINGS}"
+ ;;
+ esac
+
+ return ${EXIT_OK}
+}
+
+function 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}
+}
+
+function dhcpd_options_list() {
+ local proto=${1}
+ assert isset proto
+
+ case "${proto}" in
+ ipv6)
+ print "DHCPV6D_OPTIONS"
+ ;;
+ ipv4)
+ print "DHCPV4D_OPTIONS"
+ ;;
+ esac
+
+ return ${EXIT_OK}
+}
+
+function dhcpd_options() {
+ local proto=${1}
+ assert isset proto
+
+ case "${proto}" in
+ ipv6)
+ print "${DHCPV6D_OPTIONS}"
+ ;;
+ ipv4)
+ print "${DHCPV4D_OPTIONS}"
+ ;;
+ esac
+
+ return ${EXIT_OK}
+}
+
+function 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
+}
+
+function 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}
+ config_read ${file} ${settings}
+}
+
+function 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
+
+ config_write ${file} ${settings}
+}
+
+function dhcpd_global_options_read() {
+ local proto=${1}
+ assert isset proto
+
+ local options_file=$(dhcpd_options_file ${proto})
+ local options_list=$(dhcpd_options_list ${proto})
+
+ config_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
+}
+
+function dhcpd_subnet_path() {
+ local proto=${1}
+ assert isset proto
+
+ local subnet_id=${2}
+ assert isset subnet_id
+
+ local path
+ case "${proto}" in
+ ipv6)
+ path=${DHCPV6D_CONFIG_DIR}
+ ;;
+ ipv4)
+ path=${DHCPV4D_CONFIG_DIR}
+ ;;
+ esac
+ assert isset path
+
+ print "${path}/${DHCPD_SUBNET_PREFIX}${subnet_id}"
+ return ${EXIT_OK}
+}
+
+function dhcpd_subnet_exists() {
+ local proto=${1}
+ assert isset proto
+
+ local subnet_id=${2}
+ assert isset subnet_id
+
+ local path=$(dhcpd_subnet_path ${proto} ${subnet_id})
+ assert isset path
+
+ [ -d "${path}" ] && return ${EXIT_TRUE} || return ${EXIT_FALSE}
+}
+
+function 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_id ${settings}
+ for subnet_id in $(dhcpd_subnet_list ${proto}); do
+ dhcpd_subnet_read ${proto} ${subnet_id}
+
+ ${proto}_addr_eq "${ADDRESS}/${PREFIX}" "${subnet}" \
+ && return ${EXIT_TRUE}
+ done
+
+ return ${EXIT_FALSE}
+}
+
+function dhcpd_new_subnet_id() {
+ local proto=${1}
+ assert isset proto
+
+ local id=1
+ while :; do
+ if ! dhcpd_subnet_exists ${proto} ${id}; then
+ print "${id}"
+ return ${EXIT_OK}
+ fi
+
+ id=$(( ${id} + 1 ))
+ done
+
+ return ${EXIT_ERROR}
+}
+
+function dhcpd_subnet_new() {
+ local proto=${1}
+ assert isset proto
+ shift
+
+ # Allocate a new subnet id.
+ local subnet_id=$(dhcpd_new_subnet_id ${proto})
+ assert isinteger subnet_id
+
+ # Create directory structure.
+ local path=$(dhcpd_subnet_path ${proto} ${subnet_id})
+ assert isset path
+
+ mkdir -p ${path}
+ touch ${path}/settings
+
+ dhcpd_subnet_edit ${proto} ${subnet_id} $@
+ local ret=$?
+
+ # Remove the new subnet, when the edit method returned
+ # an error.
+ if [ ${ret} -ne ${EXIT_OK} ]; then
+ dhcpd_subnet_remove ${proto} ${subnet_id}
+ fi
+}
+
+function dhcpd_subnet_edit() {
+ local proto=${1}
+ assert isset proto
+ shift
+
+ local id=${1}
+ assert isset id
+ shift
+
+ local settings
+ case "${proto}" in
+ ipv6)
+ settings=${DHCPV6D_SUBNET_SETTINGS}
+ ;;
+ ipv4)
+ settings=${DHCPV4D_SUBNET_SETTINGS}
+ ;;
+ esac
+ assert isset settings
+ local ${settings}
+
+ # Read current settings.
+ dhcpd_subnet_read ${proto} ${id} || :
+
+ while [ $# -gt 0 ]; do
+ case "${1}" in
+ --address=*)
+ ADDRESS=$(cli_get_val ${1})
+
+ local prefix=$(ip_get_prefix ${ADDRESS})
+ if isset prefix; then
+ PREFIX=${prefix}
+ ADDRESS=$(ip_split_prefix ${ADDRESS})
+ fi
+ ;;
+ --prefix=*)
+ PREFIX=$(cli_get_val ${1})
+ ;;
+ --routers=*)
+ ROUTERS=$(cli_get_val ${1})
+ ;;
+ *)
+ error "Unknown argument: ${1}"
+ return ${EXIT_ERROR}
+ ;;
+ esac
+ shift
+ done
+
+ case "${proto}" in
+ ipv6)
+ if ! ipv6_is_valid ${ADDRESS}; then
+ error "'${ADDRESS}' is not a valid IPv6 address."
+ return ${EXIT_ERROR}
+ fi
+
+ if ! ipv6_prefix_is_valid ${PREFIX}; then
+ error "'${PREFIX}' is not a valid IPv6 prefix."
+ return ${EXIT_ERROR}
+ fi
+ ;;
+ ipv4)
+ if ! ipv4_is_valid ${ADDRESS}; then
+ error "'${ADDRESS}' is not a valid IPv4 address."
+ return ${EXIT_ERROR}
+ fi
+
+ if ! ipv4_prefix_is_valid ${PREFIX}; then
+ error "'${PREFIX}' is not a valid IPv4 prefix."
+ return ${EXIT_ERROR}
+ fi
+ ;;
+ esac
+
+ # XXX Check for subnet collisions!
+
+ local file="$(dhcpd_subnet_path ${proto} ${id})/settings"
+ config_write ${file} ${settings}
+}
+
+function dhcpd_subnet_remove() {
+ local proto=${1}
+ assert isset proto
+
+ local id=${2}
+ assert isset id
+
+ local path=$(dhcpd_subnet_path ${proto} ${id})
+ assert isset path
+
+ # Remove everything of this subnet.
+ rm -rf ${path}
+}
+
+function dhcpd_subnet_list() {
+ local proto=${1}
+ assert isset proto
+
+ local path=$(dhcpd_subnet_path ${proto} 0)
+ path=$(dirname ${path})
+
+ # 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})
+ print "${p:${#DHCPD_SUBNET_PREFIX}}"
+ done
+}
+
+function dhcpd_subnet_read() {
+ local proto=${1}
+ assert isset proto
+
+ local id=${2}
+ assert isset id
+
+ local file="$(dhcpd_subnet_path ${proto} ${id})/settings"
+ config_read ${file}
+}
+
+function dhcpd_subnet_range_path() {
+ local proto=${1}
+ assert isset proto
+
+ local subnet_id=${2}
+ assert isinteger subnet_id
+
+ local range_id=${3}
+ assert isinteger range_id
+
+ print "$(dhcpd_subnet_path ${proto} ${subnet_id})/${DHCPD_SUBNET_RANGE_PREFIX}${range_id}"
+ return ${EXIT_OK}
+}
+
+function 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}
+}
+
+function dhcpd_subnet_new_range_id() {
+ local proto=${1}
+ assert isset proto
+
+ local subnet_id=${2}
+ assert isset subnet_id
+
+ local id=1 path
+ while :; do
+ path=$(dhcpd_subnet_range_path ${proto} ${subnet_id} ${id})
+ if [ ! -f "${path}" ]; then
+ print "${id}"
+ return ${EXIT_OK}
+ fi
+
+ id=$(( ${id} + 1 ))
+ done
+
+ return ${EXIT_ERROR}
+}
+
+function dhcpd_subnet_range_new() {
+ local proto=${1}
+ assert isset proto
+ shift
+
+ local subnet_id=${1}
+ assert isset subnet_id
+ shift
+
+ # Allocate a new range id.
+ local range_id=$(dhcpd_subnet_new_range_id ${proto} ${subnet_id})
+ assert isinteger range_id
+
+ local path=$(dhcpd_subnet_range_path ${proto} ${subnet_id} ${range_id})
+ assert isset path
+
+ # Create file (as a placeholder).
+ touch ${path}
+
+ dhcpd_subnet_range_edit ${proto} ${subnet_id} ${range_id} $@
+ local ret=$?
+
+ if [ ${ret} -ne ${EXIT_OK} ]; then
+ dhcpd_subnet_range_remove ${proto} ${subnet_id} ${range_id}
+ return ${EXIT_ERROR}
+ fi
+
+ return ${EXIT_OK}
+}
+
+function dhcpd_subnet_range_edit() {
+ local proto=${1}
+ assert isset proto
+ shift
+
+ local subnet_id=${1}
+ assert isset subnet_id
+ shift
+
+ local range_id=${1}
+ assert isset range_id
+ shift
+
+ local ip_encode ip_is_valid
+ local settings
+ case "${proto}" in
+ ipv6)
+ ip_encode="ipv6_encode"
+ ip_is_valid="ipv6_is_valid"
+ settings=${DHCPV6D_SUBNET_RANGE_SETTINGS}
+ ;;
+ ipv4)
+ ip_encode="ipv4_encode"
+ ip_is_valid="ipv4_is_valid"
+ settings=${DHCPV4D_SUBNET_RANGE_SETTINGS}
+ ;;
+ esac
+ assert isset settings
+ local ${settings}
+
+ while [ $# -gt 0 ]; do
+ case "${1}" in
+ --start=*)
+ START=$(cli_get_val ${1})
+ ;;
+ --end=*)
+ END=$(cli_get_val ${1})
+ ;;
+ *)
+ error "Unknown argument: ${1}"
+ return ${EXIT_ERROR}
+ ;;
+ esac
+ shift
+ done
+
+ if ! isset START; then
+ error "You need to set the start of the IP range with --start=..."
+ return ${EXIT_ERROR}
+ fi
+
+ if ! isset END; then
+ error "You need to set the end of the IP range with --end=..."
+ return ${EXIT_ERROR}
+ fi
+
+ local var
+ for var in START END; do
+ if ! ${ip_is_valid} ${!var}; then
+ error "'${!var}' is not a valid IP address."
+ return ${EXIT_ERROR}
+ fi
+ done
+
+ # XXX currently, this check can only be performed for IPv4
+ if [ "${proto}" = "ipv4" ]; then
+ # Check if the end address is greater than the start address.
+ local start_encoded=$(${ip_encode} ${START})
+ local end_encoded=$(${ip_encode} ${END})
+
+ if [ ${start_encoded} -ge ${end_encoded} ]; then
+ error "The start address of the range must be greater than the end address."
+ return ${EXIT_ERROR}
+ fi
+ fi
+
+ # Write the configuration to file.
+ local file=$(dhcpd_subnet_range_path ${proto} ${subnet_id} ${range_id})
+ assert isset file
+
+ config_write ${file} ${settings}
+}
+
+function dhcpd_subnet_range_remove() {
+ local path=$(dhcpd_subnet_range_path $@)
+ assert isset path
+
+ rm -f ${path}
+}
+
+function dhcpd_subnet_range_list() {
+ local proto=${1}
+ assert isset proto
+
+ local subnet_id=${2}
+ assert isset subnet_id
+
+ local path=$(dhcpd_subnet_range_path ${proto} ${subnet_id} 0)
+ path=$(dirname ${path})
+
+ 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}
+}
+
+function dhcpd_subnet_range_read() {
+ local proto=${1}
+ assert isset proto
+
+ local subnet_id=${2}
+ assert isset subnet_id
+
+ local range_id=${3}
+ assert isset range_id
+
+ local file=$(dhcpd_subnet_range_path ${proto} ${subnet_id} ${range_id})
+ config_read ${file}
+}
+
+function dhcpd_subnet_settings() {
+ local proto=${1}
+
+ case "${proto}" in
+ ipv6)
+ print "${DHCPV6D_SUBNET_SETTINGS}"
+ ;;
+ ipv4)
+ print "${DHCPV4D_SUBNET_SETTINGS}"
+ ;;
+ esac
+
+ return ${EXIT_OK}
+}
+
+function dhcpd_subnet_options_file() {
+ local path=$(dhcpd_subnet_path $@)
+ assert isset path
+
+ print "${path}/options"
+}
+
+function dhcpd_subnet_options_list() {
+ local proto=${1}
+
+ case "${proto}" in
+ ipv6)
+ print "DHCPV6D_SUBNET_OPTIONS"
+ ;;
+ ipv4)
+ print "DHCPV4D_SUBNET_OPTIONS"
+ ;;
+ esac
+
+ return ${EXIT_OK}
+}
+
+function dhcpd_subnet_options() {
+ local proto=${1}
+
+ case "${proto}" in
+ ipv6)
+ print "${DHCPV6D_SUBNET_OPTIONS}"
+ ;;
+ ipv4)
+ print "${DHCPV4D_SUBNET_OPTIONS}"
+ ;;
+ esac
+
+ return ${EXIT_OK}
+}
+
+function dhcpd_subnet_options_read() {
+ local proto=${1}
+ assert isset proto
+
+ local subnet_id=${2}
+ assert isset subnet_id
+
+ local options_file=$(dhcpd_subnet_options_file ${proto} ${subnet_id})
+ local options_list=$(dhcpd_subnet_options_list ${proto})
+
+ _dhcpd_read_options ${options_file} ${options_list}
+}
+
+# Helper functions to create a DHCP configuration file.
+function _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}
+
+ # Dump options array.
+ local key val fmt
+ for key in ${!options_list}; do
+ val=${options[${key}]}
+
+ # Prepend dhcp6 on IPv6 options.
+ if [ "${proto}" = "ipv6" ]; then
+ key="dhcp6.${key}"
+ fi
+
+ if isset val; then
+ if isinteger val; then
+ fmt="option %s %d;"
+ elif isipaddress val; then
+ fmt="option %s %s;"
+ else
+ fmt="option %s \"%s\";"
+ fi
+ print "${ident}${fmt}" "${key}" "${val}"
+ fi
+ done >> ${file}
+
+ # Append an empty line when options have been written.
+ if [ -n "${!options[@]}" ]; then
+ print >> ${file}
+ fi
+}
+
+function _dhcpd_read_options() {
+ local file=${1}
+ assert isset file
+
+ local options_list=${2}
+ assert isset options_list
+
+ config_read_array ${file} options ${!options_list}
+}
+
+function _dhcpd_write_subnet() {
+ local proto=${1}
+ assert isset proto
+
+ local subnet_id=${2}
+ assert isset subnet_id
+
+ local file=${3}
+ assert isset file
+
+ # 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_id}
+
+ print "# Subnet declaration for subnet id ${subnet_id}." >> ${file}
+ case "${proto}" in
+ ipv6)
+ print "subnet6 ${ADDRESS}/${PREFIX} {" >> ${file}
+ ;;
+ ipv4)
+ local netmask=$(ipv4_get_netmask ${ADDRESS}/${PREFIX})
+ print "subnet ${ADDRESS} netmask ${netmask} {" >> ${file}
+ ;;
+ esac
+
+ # Add options.
+ _dhcpd_write_subnet_options ${proto} ${subnet_id} ${file}
+
+ # Add the ranges.
+ local range_id
+ for range_id in $(dhcpd_subnet_range_list ${proto} ${subnet_id} ${range_id}); do
+ _dhcpd_write_subnet_range ${proto} ${subnet_id} ${range_id} ${file}
+ done
+
+ # End this subnet block.
+ print "}\n" >> ${file}
+
+ return ${EXIT_OK}
+}
+
+function _dhcpd_write_subnet_options() {
+ local proto=${1}
+ assert isset proto
+
+ local subnet_id=${2}
+ assert isset subnet_id
+
+ local file=${3}
+ assert isset file
+
+ local settings
+ local options_file="$(dhcpd_subnet_path ${proto} ${subnet_id})/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_id}
+
+ 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"
+}
+
+function _dhcpd_search_routers() {
+ local proto=${1}
+ assert isset proto
+
+ # Do nothing for IPv6 (yet?).
+ [ "${proto}" = "ipv6" ] && return ${EXIT_OK}
+
+ local subnet=${2}
+ assert isset subnet
+
+ local routers
+
+ local zone addr
+ for zone in $(zones_get_all); do
+ addr=$(routing_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 ", "
+}
+
+function _dhcpd_write_subnet_range() {
+ local proto=${1}
+ assert isset proto
+
+ local subnet_id=${2}
+ assert isset subnet_id
+
+ local range_id=${3}
+ assert isset range_id
+
+ local file=${4}
+ assert isset file
+
+ local settings=$(dhcpd_subnet_range_settings ${proto})
+ assert isset settings
+
+ # Read the configuration settings.
+ local ${settings}
+ dhcpd_subnet_range_read ${proto} ${subnet_id} ${range_id}
+
+ # Print the range line.
+ print " # Range id ${range_id}." >> ${file}
+
+ case "${proto}" in
+ ipv6)
+ print " range6 ${START} ${END};" >> ${file}
+ ;;
+ ipv4)
+ print " range ${START} ${END};" >> ${file}
+ ;;
+ esac
+ print >> ${file}
+
+ return ${EXIT_OK}
+}
+
+function dhcpd_write_config() {
+ local proto=${1}
+ assert isset proto
+
+ 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}
+
+ # 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 ininteger 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_id
+ for subnet_id in $(dhcpd_subnet_list ${proto}); do
+ _dhcpd_write_subnet ${proto} ${subnet_id} ${file}
+ done
+
+ return ${EXIT_OK}
+}
exit ${EXIT_OK}
}
+function cli_dhcpd() {
+ local proto=${1}
+ shift
+
+ if cli_help_requested $@; then
+ cli_show_man network-dhcp
+ exit ${EXIT_OK}
+ fi
+
+ local action=${1}
+ shift
+
+ case "${action}" in
+ edit)
+ dhcpd_edit ${proto} $@
+ ;;
+ start)
+ dhcpd_start ${proto}
+ ;;
+ stop)
+ dhcpd_stop ${proto}
+ ;;
+ restart|reload)
+ dhcpd_reload ${proto}
+ ;;
+ subnet)
+ cli_dhcpd_subnet ${proto} $@
+ ;;
+ show|"")
+ cli_dhcpd_show ${proto} $@
+ ;;
+ *)
+ error "Unrecognized action: ${action}"
+ cli_run_help network dhcpvN
+
+ exit ${EXIT_ERROR}
+ ;;
+ esac
+
+ exit ${EXIT_OK}
+}
+
+function cli_dhcpd_show() {
+ local proto=${1}
+ assert isset proto
+
+ local settings=$(dhcpd_settings ${proto})
+ assert isset settings
+
+ local ${settings}
+ dhcpd_global_settings_read ${proto}
+
+ cli_headline 1 "Dynamic Host Configuration Protocol Daemon for ${proto/ip/IP}"
+
+ case "${proto}" in
+ ipv6)
+ cli_headline 2 "Lease times"
+ if isinteger VALID_LIFETIME; then
+ cli_print_fmt1 2 "Valid lifetime" "${VALID_LIFETIME}s"
+ fi
+
+ if isinteger PREFERRED_LIFETIME; then
+ cli_print_fmt1 2 "Preferred lifetime" "${PREFERRED_LIFETIME}s"
+ fi
+
+ cli_space
+ ;;
+ ipv4)
+ cli_print_fmt1 1 "Authoritative" $(cli_print_enabled AUTHORITATIVE)
+ cli_space
+
+ cli_headline 2 "Lease times"
+ cli_print_fmt1 2 "Default lease time" "${DEFAULT_LEASE_TIME}s"
+ cli_print_fmt1 2 "Max. lease time" "${MAX_LEASE_TIME}s"
+
+ if isset MIN_LEASE_TIME; then
+ cli_print_fmt1 2 "Min. lease time" "${MIN_LEASE_TIME}s"
+ fi
+
+ cli_space
+ ;;
+ esac
+
+ # Read the options.
+ local -A options
+ dhcpd_global_options_read ${proto} ${subnet_id}
+
+ # Print the options if any.
+ if [ ${#options[*]} -gt 0 ]; then
+ cli_headline 2 "Options"
+
+ local option
+ for option in $(dhcpd_options ${proto}); do
+ [ -n "${options[${option}]}" ] || continue
+
+ cli_print_fmt1 2 \
+ "${option}" "${options[${option}]}"
+ done
+ cli_space
+ fi
+
+ # Subnets.
+ local subnets=$(dhcpd_subnet_list ${proto})
+ if [ -n "${subnets}" ]; then
+ cli_headline 2 "Subnets"
+ local subnet_id
+ for subnet_id in ${subnets}; do
+ cli_dhcpd_subnet_show ${proto} ${subnet_id} 2
+ done
+ fi
+}
+
+function cli_dhcpd_subnet() {
+ local proto=${1}
+ shift
+
+ if cli_help_requested $@; then
+ cli_show_man network-dhcp-subnet
+ exit ${EXIT_OK}
+ fi
+
+ local action=${1}
+ shift
+
+ case "${action}" in
+ new)
+ dhcpd_subnet_new ${proto} $@
+ ;;
+ remove)
+ dhcpd_subnet_remove ${proto} $@
+ ;;
+ [0-9]*)
+ local subnet_id=${action}
+
+ if ! dhcpd_subnet_exists ${proto} ${subnet_id}; then
+ error "The given subnet with ID ${subnet_id} does not exist."
+ return ${EXIT_ERROR}
+ fi
+
+ # Update the action.
+ action=${1}
+ shift
+
+ case "${action}" in
+ edit)
+ dhcpd_subnet_edit ${proto} ${subnet_id} $@
+ local ret=$?
+
+ if [ ${ret} -eq ${EXIT_OK} ]; then
+ dhcpd_reload ${proto}
+ fi
+ exit ${ret}
+ ;;
+ range)
+ cli_dhcpd_subnet_range ${proto} ${subnet_id} $@
+ exit $?
+ ;;
+ show)
+ cli_dhcpd_subnet_show ${proto} ${subnet_id} $@
+ exit $?
+ ;;
+ options)
+ cli_dhcpd_subnet_options ${proto} ${subnet_id} $@
+ exit $?
+ ;;
+ *)
+ error "Unrecognized action: ${action}"
+ cli_run_help network dhcpvN subnet
+ exit ${EXIT_ERROR}
+ ;;
+ esac
+ ;;
+ show)
+ local subnet_id
+ for subnet_id in $(dhcpd_subnet_list ${proto}); do
+ cli_dhcpd_subnet_show ${proto} ${subnet_id}
+ done
+ ;;
+ *)
+ error "Unrecognized action: ${action}"
+ cli_run_help network dhcpvN subnet
+
+ exit ${EXIT_ERROR}
+ ;;
+ esac
+
+ exit ${EXIT_OK}
+}
+
+function cli_dhcpd_subnet_range() {
+ local proto=${1}
+ assert isset proto
+ shift
+
+ local subnet_id=${1}
+ assert isset subnet_id
+ shift
+
+ local action=${1}
+ shift
+
+ case "${action}" in
+ new)
+ dhcpd_subnet_range_new ${proto} ${subnet_id} $@
+ exit $?
+ ;;
+ remove)
+ dhcpd_subnet_range_remove ${proto} ${subnet_id} $@
+ exit $?
+ ;;
+ *)
+ error "Unrecognized action: ${action}"
+ cli_run_help network dhcpvN subnet range
+ exit ${EXIT_ERROR}
+ ;;
+ esac
+}
+
+function cli_dhcpd_subnet_show() {
+ local proto=${1}
+ assert isset proto
+
+ local subnet_id=${2}
+ assert isset subnet_id
+
+ local level=${3}
+ isset level || level=0
+
+ local $(dhcpd_subnet_settings ${proto})
+
+ # Read in configuration settings.
+ dhcpd_subnet_read ${proto} ${subnet_id}
+
+ cli_headline $(( ${level} + 1 )) \
+ "DHCP${proto/ip/} subnet declaration #${subnet_id}"
+ cli_print_fmt1 $(( ${level} + 1 )) \
+ "Subnet" "${ADDRESS}/${PREFIX}"
+ cli_space
+
+ # Read the options.
+ local -A options
+ dhcpd_subnet_options_read ${proto} ${subnet_id}
+
+ # Print the options if any.
+ if [ ${#options[*]} -gt 0 ]; then
+ cli_headline $(( ${level} + 2 )) "Options"
+
+ local option
+ for option in $(dhcpd_subnet_options ${proto}); do
+ [ -n "${options[${option}]}" ] || continue
+
+ cli_print_fmt1 $(( ${level} + 2 )) \
+ "${option}" "${options[${option}]}"
+ done
+ cli_space
+ fi
+
+ # Ranges.
+ cli_headline $(( ${level} + 2 )) "Ranges"
+
+ local ranges=$(dhcpd_subnet_range_list ${proto} ${subnet_id})
+ if isset ranges; then
+ local range_id $(dhcpd_subnet_range_settings ${proto})
+ for range_id in ${ranges}; do
+ dhcpd_subnet_range_read ${proto} ${subnet_id} ${range_id}
+
+ cli_print $(( ${level} + 2 )) \
+ "#%d: %s - %s" ${range_id} ${START} ${END}
+ done
+ else
+ cli_print $(( ${level} + 2 )) "No ranges have been defined."
+ fi
+
+ cli_space
+}
+
+function cli_dhcpd_options() {
+ local proto=${1}
+ assert isset proto
+ shift
+
+ local subnet_id=${1}
+ assert isset subnet_id
+ shift
+
+ local valid_options=$(dhcpd_subnet_options ${proto})
+
+ local key val
+ while [ $# -gt 0 ]; do
+ case "${1}" in
+ *=*)
+ key=$(cli_get_key ${1})
+ val=$(cli_get_val ${1})
+
+ dhcpd_subnet_option_set ${proto} ${subnet_id} ${key} ${val}
+ esac
+ done
+}
+
function cli_start() {
if cli_help_requested $@; then
cli_show_man network
cli_${action} $@
;;
+ # DHCP server configuration (automatically detects which protocol to use).
+ dhcpv6|dhcpv4)
+ cli_dhcpd ${action/dhcp/ip} $@
+ ;;
+
""|help|--help|-h)
cli_help $@
;;