2 ###############################################################################
4 # IPFire.org - A linux based firewall #
5 # Copyright (C) 2012 IPFire Network Development Team #
7 # This program is free software: you can redistribute it and/or modify #
8 # it under the terms of the GNU General Public License as published by #
9 # the Free Software Foundation, either version 3 of the License, or #
10 # (at your option) any later version. #
12 # This program is distributed in the hope that it will be useful, #
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of #
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
15 # GNU General Public License for more details. #
17 # You should have received a copy of the GNU General Public License #
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. #
20 ###############################################################################
22 DHCPV6D_CONFIG_FILE
="/etc/dhcp/dhcpd6.conf"
23 DHCPV4D_CONFIG_FILE
="/etc/dhcp/dhcpd.conf"
25 DHCPV6D_CONFIG_DIR
="${NETWORK_CONFIG_DIR}/dhcpd/ipv6"
26 DHCPV4D_CONFIG_DIR
="${NETWORK_CONFIG_DIR}/dhcpd/ipv4"
28 DHCPV6D_OPTIONS_FILE
="${DHCPV6D_CONFIG_DIR}/options"
29 DHCPV4D_OPTIONS_FILE
="${DHCPV4D_CONFIG_DIR}/options"
31 DHCPV6D_SETTINGS_FILE
="${DHCPV6D_CONFIG_DIR}/settings"
32 DHCPV4D_SETTINGS_FILE
="${DHCPV4D_CONFIG_DIR}/settings"
49 DHCPD_SUBNET_PREFIX
="subnet-"
50 #DHCPD_SUBNET_POOL_PREFIX="pool-"
51 DHCPD_SUBNET_RANGE_PREFIX
="range-"
53 DHCPD_SUBNET_SETTINGS
="ADDRESS PREFIX ROUTERS"
54 DHCPV6D_SUBNET_SETTINGS
="${DHCPD_SUBNET_SETTINGS}"
55 DHCPV4D_SUBNET_SETTINGS
="${DHCPD_SUBNET_SETTINGS}"
57 DHCPD_SUBNET_RANGE_SETTINGS
="START END"
58 DHCPV6D_SUBNET_RANGE_SETTINGS
="${DHCPD_SUBNET_RANGE_SETTINGS}"
59 DHCPV4D_SUBNET_RANGE_SETTINGS
="${DHCPD_SUBNET_RANGE_SETTINGS}"
72 dhcp-client-identifier \
74 dhcp-max-message-size \
87 DHCPD_SUBNET_OPTIONS
="${DHCPD_OPTIONS}"
88 DHCPV6D_SUBNET_OPTIONS
="${DHCPD_SUBNET_OPTIONS}"
89 DHCPV4D_SUBNET_OPTIONS
="${DHCPD_SUBNET_OPTIONS}"
91 # Defaults for DHCPv6.
92 DHCPV6D_PREFERRED_LIFETIME
=""
93 DHCPV6D_VALID_LIFETIME
="43200" # 12h
95 # Defaults for DHCPv4.
96 DHCPV4D_AUTHORITATIVE
="true"
97 DHCPV4D_DEFAULT_LEASE_TIME
="43200" # 12h
98 DHCPV4D_MAX_LEASE_TIME
="86400" # 24h
99 DHCPV4D_MIN_LEASE_TIME
=""
101 function dhcpd_service
() {
104 print
"dhcpd6.service"
107 print
"dhcpd.service"
110 print
"dhcpd6.service dhcp.service"
117 function dhcpd_start
() {
118 local services
=$
(dhcpd_service $@
)
121 for service
in ${services}; do
122 service_start
${service}
126 function dhcpd_stop
() {
127 local services
=$
(dhcpd_service $@
)
130 for service
in ${services}; do
131 service_stop
${service}
135 function dhcpd_restart
() {
136 # DHCP does not support a reload, so
138 local services
=$
(dhcpd_service $@
)
141 for service
in ${services}; do
142 service_restart
${service}
146 function dhcpd_reload
() {
150 function dhcpd_edit
() {
155 local settings
=$
(dhcpd_settings
${proto})
156 assert isset settings
159 dhcpd_global_settings_read
${proto}
163 _dhcpd_edit_ipv6 $@ ||
return $?
166 _dhcpd_edit_ipv4 $@ ||
return $?
170 dhcpd_global_settings_write
${proto}
173 function _dhcpd_edit_ipv4
() {
176 while [ $# -gt 0 ]; do
179 val
=$
(cli_get_val
${1})
184 AUTHORITATIVE
="false"
187 --default-lease-time=*)
188 DEFAULT_LEASE_TIME
=$
(cli_get_val
${1})
190 if ! isinteger DEFAULT_LEASE_TIME
; then
191 error
"Invalid value for --default-lease-time."
196 MAX_LEASE_TIME
=$
(cli_get_val
${1})
198 if ! isinteger MAX_LEASE_TIME
; then
199 error
"Invalid value for --max-lease-time."
204 MIN_LEASE_TIME
=$
(cli_get_val
${1})
206 if isset MIN_LEASE_TIME
; then
207 if ! isinteger MIN_LEASE_TIME
; then
208 error
"Invalid value for --min-lease-time."
214 error
"Unrecognized argument: ${1}"
221 if [ ${MAX_LEASE_TIME} -le ${DEFAULT_LEASE_TIME} ]; then
222 error
"The max. lease time must be higher than the default lease time."
227 function _dhcpd_edit_ipv6
() {
228 while [ $# -gt 0 ]; do
230 --preferred-lifetime=*)
231 PREFERRED_LIFETIME
=$
(cli_get_val
${1})
233 if ! isinteger PREFERRED_LIFETIME
; then
234 error
"Invalid value for --preferred-lifetime."
239 VALID_LIFETIME
=$
(cli_get_val
${1})
241 if ! isinteger VALID_LIFETIME
; then
242 error
"Invalid value for --valid-lifetime."
247 error
"Unrecognized argument: ${1}"
255 function dhcpd_settings_file
() {
261 print
"${DHCPV6D_SETTINGS_FILE}"
264 print
"${DHCPV4D_SETTINGS_FILE}"
271 function dhcpd_settings
() {
277 print
"${DHCPV6D_SETTINGS}"
280 print
"${DHCPV4D_SETTINGS}"
287 function dhcpd_options_file
() {
293 print
"${DHCPV6D_OPTIONS_FILE}"
296 print
"${DHCPV4D_OPTIONS_FILE}"
303 function dhcpd_options_list
() {
309 print
"DHCPV6D_OPTIONS"
312 print
"DHCPV4D_OPTIONS"
319 function dhcpd_options
() {
325 print
"${DHCPV6D_OPTIONS}"
328 print
"${DHCPV4D_OPTIONS}"
335 function dhcpd_global_settings_list
() {
339 dhcpd_settings
"${proto}"
342 function dhcpd_global_settings_defaults
() {
346 local settings
=$
(dhcpd_settings
${proto})
347 assert isset settings
349 local prefix
="DHCPV${proto/ipv/}D_"
351 local setting setting_default
352 for setting
in ${settings}; do
353 setting_default
="${prefix}${setting}"
354 printf -v ${setting} "%s" "${!setting_default}"
358 function dhcpd_global_settings_read
() {
362 local file=$
(dhcpd_settings_file
${proto})
365 local settings
=$
(dhcpd_settings
${proto})
366 assert isset settings
368 dhcpd_global_settings_defaults
${proto}
369 settings_read
${file} ${settings}
372 function dhcpd_global_settings_write
() {
376 local file=$
(dhcpd_settings_file
${proto})
379 local settings
=$
(dhcpd_settings
${proto})
380 assert isset settings
382 settings_write
${file} ${settings}
385 function dhcpd_global_options_read
() {
389 local options_file
=$
(dhcpd_options_file
${proto})
390 local options_list
=$
(dhcpd_options_list
${proto})
392 settings_read_array
${options_file} options
${!options_list}
394 # Check if domain-name is set.
395 if [ -z "${options["domain-name"]}" ]; then
396 options
["domain-name"]=$
(config_domainname
)
400 function dhcpd_subnet_path
() {
405 assert isset subnet_id
410 path
=${DHCPV6D_CONFIG_DIR}
413 path
=${DHCPV4D_CONFIG_DIR}
418 print
"${path}/${DHCPD_SUBNET_PREFIX}${subnet_id}"
422 function dhcpd_subnet_exists
() {
427 assert isset subnet_id
429 local path
=$
(dhcpd_subnet_path
${proto} ${subnet_id})
432 [ -d "${path}" ] && return ${EXIT_TRUE} || return ${EXIT_FALSE}
435 function dhcpd_subnet_match() {
442 local settings=$(dhcpd_subnet_settings ${proto})
443 assert isset settings
445 local subnet_id ${settings}
446 for subnet_id in $(dhcpd_subnet_list ${proto}); do
447 dhcpd_subnet_read ${proto} ${subnet_id}
449 ${proto}_addr_eq "${ADDRESS}/${PREFIX}" "${subnet}" \
450 && return ${EXIT_TRUE}
456 function dhcpd_new_subnet_id
() {
462 if ! dhcpd_subnet_exists
${proto} ${id}; then
473 function dhcpd_subnet_new
() {
478 # Allocate a new subnet id.
479 local subnet_id
=$
(dhcpd_new_subnet_id
${proto})
480 assert isinteger subnet_id
482 # Create directory structure.
483 local path
=$
(dhcpd_subnet_path
${proto} ${subnet_id})
487 touch ${path}/settings
489 dhcpd_subnet_edit
${proto} ${subnet_id} $@
492 # Remove the new subnet, when the edit method returned
494 if [ ${ret} -ne ${EXIT_OK} ]; then
495 dhcpd_subnet_remove
${proto} ${subnet_id}
499 function dhcpd_subnet_edit
() {
511 settings
=${DHCPV6D_SUBNET_SETTINGS}
514 settings
=${DHCPV4D_SUBNET_SETTINGS}
517 assert isset settings
520 # Read current settings.
521 dhcpd_subnet_read
${proto} ${id} ||
:
523 while [ $# -gt 0 ]; do
526 ADDRESS
=$
(cli_get_val
${1})
528 local prefix
=$
(ip_get_prefix
${ADDRESS})
529 if isset prefix
; then
531 ADDRESS
=$
(ip_split_prefix
${ADDRESS})
535 PREFIX
=$
(cli_get_val
${1})
538 ROUTERS
=$
(cli_get_val
${1})
541 error
"Unknown argument: ${1}"
550 if ! ipv6_is_valid
${ADDRESS}; then
551 error
"'${ADDRESS}' is not a valid IPv6 address."
555 if ! ipv6_prefix_is_valid
${PREFIX}; then
556 error
"'${PREFIX}' is not a valid IPv6 prefix."
561 if ! ipv4_is_valid
${ADDRESS}; then
562 error
"'${ADDRESS}' is not a valid IPv4 address."
566 if ! ipv4_prefix_is_valid
${PREFIX}; then
567 error
"'${PREFIX}' is not a valid IPv4 prefix."
573 # XXX Check for subnet collisions!
575 local file="$(dhcpd_subnet_path ${proto} ${id})/settings"
576 settings_write
${file} ${settings}
579 function dhcpd_subnet_remove
() {
586 local path
=$
(dhcpd_subnet_path
${proto} ${id})
589 # Remove everything of this subnet.
593 function dhcpd_subnet_list
() {
597 local path
=$
(dhcpd_subnet_path
${proto} 0)
598 path
=$
(dirname ${path})
600 # Return an error of the directory does not exist.
601 [ -d "${path}" ] ||
return ${EXIT_ERROR}
604 for p
in ${path}/${DHCPD_SUBNET_PREFIX}*; do
605 [ -d "${p}" ] ||
continue
608 print
"${p:${#DHCPD_SUBNET_PREFIX}}"
612 function dhcpd_subnet_read
() {
619 local file="$(dhcpd_subnet_path ${proto} ${id})/settings"
620 settings_read
${file}
623 function dhcpd_subnet_range_path
() {
628 assert isinteger subnet_id
631 assert isinteger range_id
633 print
"$(dhcpd_subnet_path ${proto} ${subnet_id})/${DHCPD_SUBNET_RANGE_PREFIX}${range_id}"
637 function dhcpd_subnet_range_settings
() {
642 print
"${DHCPV6D_SUBNET_RANGE_SETTINGS}"
645 print
"${DHCPV4D_SUBNET_RANGE_SETTINGS}"
652 function dhcpd_subnet_new_range_id
() {
657 assert isset subnet_id
661 path
=$
(dhcpd_subnet_range_path
${proto} ${subnet_id} ${id})
662 if [ ! -f "${path}" ]; then
673 function dhcpd_subnet_range_new
() {
679 assert isset subnet_id
682 # Allocate a new range id.
683 local range_id
=$
(dhcpd_subnet_new_range_id
${proto} ${subnet_id})
684 assert isinteger range_id
686 local path
=$
(dhcpd_subnet_range_path
${proto} ${subnet_id} ${range_id})
689 # Create file (as a placeholder).
692 dhcpd_subnet_range_edit
${proto} ${subnet_id} ${range_id} $@
695 if [ ${ret} -ne ${EXIT_OK} ]; then
696 dhcpd_subnet_range_remove
${proto} ${subnet_id} ${range_id}
703 function dhcpd_subnet_range_edit
() {
709 assert isset subnet_id
713 assert isset range_id
716 local ip_encode ip_is_valid
720 ip_encode
="ipv6_encode"
721 ip_is_valid
="ipv6_is_valid"
722 settings
=${DHCPV6D_SUBNET_RANGE_SETTINGS}
725 ip_encode
="ipv4_encode"
726 ip_is_valid
="ipv4_is_valid"
727 settings
=${DHCPV4D_SUBNET_RANGE_SETTINGS}
730 assert isset settings
733 while [ $# -gt 0 ]; do
736 START
=$
(cli_get_val
${1})
739 END
=$
(cli_get_val
${1})
742 error
"Unknown argument: ${1}"
749 if ! isset START
; then
750 error
"You need to set the start of the IP range with --start=..."
755 error
"You need to set the end of the IP range with --end=..."
760 for var
in START END
; do
761 if ! ${ip_is_valid} ${!var}; then
762 error
"'${!var}' is not a valid IP address."
767 # XXX currently, this check can only be performed for IPv4
768 if [ "${proto}" = "ipv4" ]; then
769 # Check if the end address is greater than the start address.
770 local start_encoded
=$
(${ip_encode} ${START})
771 local end_encoded
=$
(${ip_encode} ${END})
773 if [ ${start_encoded} -ge ${end_encoded} ]; then
774 error
"The start address of the range must be greater than the end address."
779 # Write the configuration to file.
780 local file=$
(dhcpd_subnet_range_path
${proto} ${subnet_id} ${range_id})
783 settings_write
${file} ${settings}
786 function dhcpd_subnet_range_remove
() {
787 local path
=$
(dhcpd_subnet_range_path $@
)
793 function dhcpd_subnet_range_list
() {
798 assert isset subnet_id
800 local path
=$
(dhcpd_subnet_range_path
${proto} ${subnet_id} 0)
801 path
=$
(dirname ${path})
804 for p
in ${path}/${DHCPD_SUBNET_RANGE_PREFIX}*; do
805 [ -r "${p}" ] ||
continue
808 print
"${p:${#DHCPD_SUBNET_RANGE_PREFIX}}"
814 function dhcpd_subnet_range_read
() {
819 assert isset subnet_id
822 assert isset range_id
824 local file=$
(dhcpd_subnet_range_path
${proto} ${subnet_id} ${range_id})
825 settings_read
${file}
828 function dhcpd_subnet_settings
() {
833 print
"${DHCPV6D_SUBNET_SETTINGS}"
836 print
"${DHCPV4D_SUBNET_SETTINGS}"
843 function dhcpd_subnet_options_file
() {
844 local path
=$
(dhcpd_subnet_path $@
)
847 print
"${path}/options"
850 function dhcpd_subnet_options_list
() {
856 print
"${DHCPV6D_SUBNET_OPTIONS}"
859 print
"${DHCPV4D_SUBNET_OPTIONS}"
866 function dhcpd_subnet_options_read
() {
871 assert isset subnet_id
873 local options_file
=$
(dhcpd_subnet_options_file
${proto} ${subnet_id})
874 local options_list
=$
(dhcpd_subnet_options_list
${proto})
876 _dhcpd_read_options
${options_file} ${options_list}
879 # Helper functions to create a DHCP configuration file.
880 function _dhcpd_write_options
() {
887 local options_list
=${3}
888 assert isset options_list
892 # Dump options array.
894 for key
in ${!options_list}; do
895 val
=${options[${key}]}
897 # Prepend dhcp6 on IPv6 options.
898 if [ "${proto}" = "ipv6" ]; then
903 if isinteger val
; then
905 elif isipaddress val
; then
908 fmt="option %s \"%s\";"
910 print
"${ident}${fmt}" "${key}" "${val}"
914 # Append an empty line when options have been written.
915 if [ -n "${!options[@]}" ]; then
920 function _dhcpd_read_options
() {
924 local options_list
=${2}
925 assert isset options_list
927 settings_read_array
${file} options
${!options_list}
930 function _dhcpd_write_subnet
() {
935 assert isset subnet_id
940 # Check which settings we do expect.
944 settings
=${DHCPV6D_SUBNET_SETTINGS}
947 settings
=${DHCPV4D_SUBNET_SETTINGS}
950 assert isset settings
953 # Read configuration settings.
954 dhcpd_subnet_read
${proto} ${subnet_id}
956 print
"# Subnet declaration for subnet id ${subnet_id}." >> ${file}
959 print
"subnet6 ${ADDRESS}/${PREFIX} {" >> ${file}
962 local netmask=$(ipv4_get_netmask ${ADDRESS}/${PREFIX})
963 print "subnet
${ADDRESS} netmask ${netmask} {" >> ${file}
968 _dhcpd_write_subnet_options
${proto} ${subnet_id} ${file}
972 for range_id
in $
(dhcpd_subnet_range_list
${proto} ${subnet_id} ${range_id}); do
973 _dhcpd_write_subnet_range
${proto} ${subnet_id} ${range_id} ${file}
976 # End this subnet block.
977 print
"}\n" >> ${file}
982 function _dhcpd_write_subnet_options
() {
987 assert isset subnet_id
993 local options_file
="$(dhcpd_subnet_path ${proto} ${subnet_id})/options"
997 settings
=${DHCPV6D_SUBNET_SETTINGS}
998 options_list
="DHCPV6D_OPTIONS"
1001 settings
=${DHCPV4D_SUBNET_SETTINGS}
1002 options_list
="DHCPV4D_OPTIONS"
1005 assert isset settings
1006 assert isset options_list
1009 dhcpd_subnet_read
${proto} ${subnet_id}
1012 _dhcpd_read_options
${options_file} ${options_list}
1014 # Fill in router, if not already set.
1015 if [ -z "${options["routers"]}" ]; then
1016 options
["routers"]=$
(_dhcpd_search_routers
${proto} "${ADDRESS}/${PREFIX}")
1019 _dhcpd_write_options ${proto} ${file} ${options_list} "\t"
1022 function _dhcpd_search_routers() {
1026 # Do nothing for IPv6 (yet?).
1027 [ "${proto}" = "ipv6
" ] && return ${EXIT_OK}
1035 for zone in $(zones_get_all); do
1036 addr=$(routing_db_get ${zone} ${proto} local-ip-address)
1037 isset addr || continue
1039 if ipv4_in_subnet ${addr} ${subnet}; then
1040 list_append routers $(ip_split_prefix ${addr})
1044 list_join routers ", "
1047 function _dhcpd_write_subnet_range() {
1051 local subnet_id=${2}
1052 assert isset subnet_id
1055 assert isset range_id
1060 local settings=$(dhcpd_subnet_range_settings ${proto})
1061 assert isset settings
1063 # Read the configuration settings.
1065 dhcpd_subnet_range_read ${proto} ${subnet_id} ${range_id}
1067 # Print the range line.
1068 print " # Range id ${range_id}." >> ${file}
1072 print
" range6 ${START} ${END};" >> ${file}
1075 print " range
${START} ${END};" >> ${file}
1083 function dhcpd_write_config
() {
1087 local file options_list
1090 file=${DHCPV6D_CONFIG_FILE}
1091 options_list
="DHCPV6D_OPTIONS"
1094 file=${DHCPV4D_CONFIG_FILE}
1095 options_list
="DHCPV4D_OPTIONS"
1099 assert isset options_list
1102 config_header
"DHCP ${proto} daemon configuration file" > ${file}
1105 if enabled AUTHORITATIVE
; then
1107 print
"# This is an authoritative DHCP server for this network."
1108 print
"authoritative;\n"
1112 print
"# This is NOT an authoritative DHCP server for this network."
1113 print
"not authoritative;\n"
1120 if ininteger VALID_LIFETIME
; then
1121 print
"default-lease-time %d;" "${VALID_LIFETIME}" >> ${file}
1124 if isinteger PREFERRED_LIFETIME
; then
1125 print
"preferred-lifetime %d;" "${PREFERRED_LIFETIME}" >> ${file}
1130 if isinteger DEFAULT_LEASE_TIME
; then
1131 print
"default-lease-time %d;" "${DEFAULT_LEASE_TIME}" >> ${file}
1134 if isinteger MAX_LEASE_TIME
; then
1135 print
"max-lease-time %d;" "${MAX_LEASE_TIME}" >> ${file}
1138 if isinteger MIN_LEASE_TIME
; then
1139 print
"min-lease-time %d;" "${MIN_LEASE_TIME}" >> ${file}
1144 # Write the options to file.
1146 dhcpd_global_options_read
${proto}
1147 _dhcpd_write_options
${proto} ${file} ${options_list}
1149 # Add all subnet declarations.
1151 for subnet_id
in $
(dhcpd_subnet_list
${proto}); do
1152 _dhcpd_write_subnet
${proto} ${subnet_id} ${file}