]> git.ipfire.org Git - people/stevee/network.git/blob - src/functions/functions.dhcpd
network fix parameter passing when using ""
[people/stevee/network.git] / src / functions / functions.dhcpd
1 #!/bin/bash
2 ###############################################################################
3 # #
4 # IPFire.org - A linux based firewall #
5 # Copyright (C) 2012 IPFire Network Development Team #
6 # #
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. #
11 # #
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. #
16 # #
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/>. #
19 # #
20 ###############################################################################
21
22 DHCPV6D_CONFIG_FILE="/etc/dhcp/dhcpd6.conf"
23 DHCPV4D_CONFIG_FILE="/etc/dhcp/dhcpd.conf"
24
25 DHCPV6D_CONFIG_DIR="${NETWORK_CONFIG_DIR}/dhcpd/ipv6"
26 DHCPV4D_CONFIG_DIR="${NETWORK_CONFIG_DIR}/dhcpd/ipv4"
27
28 DHCPV6D_OPTIONS_FILE="${DHCPV6D_CONFIG_DIR}/options"
29 DHCPV4D_OPTIONS_FILE="${DHCPV4D_CONFIG_DIR}/options"
30
31 DHCPV6D_SETTINGS_FILE="${DHCPV6D_CONFIG_DIR}/settings"
32 DHCPV4D_SETTINGS_FILE="${DHCPV4D_CONFIG_DIR}/settings"
33
34 DHCPD_SETTINGS="\
35 AUTHORITATIVE
36 "
37 DHCPV6D_SETTINGS="\
38 ${DHCPD_SETTINGS} \
39 PREFERRED_LIFETIME \
40 VALID_LIFETIME
41 "
42 DHCPV4D_SETTINGS="\
43 ${DHCPD_SETTINGS} \
44 DEFAULT_LEASE_TIME \
45 MAX_LEASE_TIME \
46 MIN_LEASE_TIME \
47 "
48
49 DHCPD_SUBNET_PREFIX="subnet-"
50 #DHCPD_SUBNET_POOL_PREFIX="pool-"
51 DHCPD_SUBNET_RANGE_PREFIX="range-"
52
53 DHCPD_SUBNET_SETTINGS="ADDRESS PREFIX"
54 DHCPV6D_SUBNET_SETTINGS="${DHCPD_SUBNET_SETTINGS} PREFIX_DELEGATION \
55 DELEGATED_PREFIX_FIRST DELEGATED_PREFIX_LAST DELEGATED_PREFIX_SIZE"
56 DHCPV4D_SUBNET_SETTINGS="${DHCPD_SUBNET_SETTINGS} ROUTERS"
57
58 DHCPD_SUBNET_RANGE_SETTINGS="START END"
59 DHCPV6D_SUBNET_RANGE_SETTINGS="${DHCPD_SUBNET_RANGE_SETTINGS}"
60 DHCPV4D_SUBNET_RANGE_SETTINGS="${DHCPD_SUBNET_RANGE_SETTINGS}"
61
62 DHCPV6D_OPTIONS="\
63 domain-search \
64 name-servers \
65 "
66 DHCPV4D_OPTIONS="\
67 all-subnets-local \
68 arp-cache-timeout \
69 bootfile-name \
70 broadcast-address \
71 default-ip-ttl \
72 default-tcp-ttl \
73 dhcp-client-identifier \
74 dhcp-lease-time \
75 dhcp-max-message-size \
76 dhcp-rebinding-time \
77 dhcp-renewal-time \
78 domain-name \
79 domain-name-servers \
80 domain-search \
81 interface-mtu \
82 ntp-servers \
83 root-path \
84 routers \
85 tftp-server-name \
86 "
87
88 DHCPV6D_SUBNET_OPTIONS="${DHCPV6D_OPTIONS}"
89 DHCPV4D_SUBNET_OPTIONS="${DHCPV4D_OPTIONS}"
90
91 # Global defaults
92 DHCP_DEFAULT_DELEGATED_PREFIX_SIZE="64"
93
94 # Defaults for DHCPv6.
95 DHCPV6D_PREFERRED_LIFETIME=""
96 DHCPV6D_VALID_LIFETIME="43200" # 12h
97
98 # Defaults for DHCPv4.
99 DHCPV4D_AUTHORITATIVE="true"
100 DHCPV4D_DEFAULT_LEASE_TIME="43200" # 12h
101 DHCPV4D_MAX_LEASE_TIME="86400" # 24h
102 DHCPV4D_MIN_LEASE_TIME=""
103
104 dhcpd_service() {
105 case "${1}" in
106 ipv6)
107 print "dhcpd6.service"
108 ;;
109 ipv4)
110 print "dhcpd.service"
111 ;;
112 "")
113 print "dhcpd6.service dhcp.service"
114 ;;
115 esac
116
117 return ${EXIT_OK}
118 }
119
120 dhcpd_start() {
121 local services=$(dhcpd_service "$@")
122
123 local service
124 for service in ${services}; do
125 service_start ${service}
126 done
127 }
128
129 dhcpd_stop() {
130 local services=$(dhcpd_service "$@")
131
132 local service
133 for service in ${services}; do
134 service_stop ${service}
135 done
136 }
137
138 dhcpd_restart() {
139 # DHCP does not support a reload, so
140 # we retsart it.
141 local services=$(dhcpd_service "$@")
142
143 local service
144 for service in ${services}; do
145 service_restart ${service}
146 done
147 }
148
149 dhcpd_reload() {
150 dhcpd_restart "$@"
151 }
152
153 dhcpd_enable() {
154 local services=$(dhcpd_service "$@")
155
156 local service
157 for service in ${services}; do
158 service_enable ${service}
159 done
160 }
161
162 dhcpd_disable() {
163 local services=$(dhcpd_service "$@")
164
165 local service
166 for service in ${services}; do
167 service_disable ${service}
168 done
169 }
170
171 dhcpd_edit() {
172 local proto=${1}
173 assert isset proto
174 shift
175
176 local settings=$(dhcpd_settings ${proto})
177 assert isset settings
178
179 local ${settings}
180 dhcpd_global_settings_read ${proto}
181
182 case "${proto}" in
183 ipv6)
184 _dhcpd_edit_ipv6 "$@" || return $?
185 ;;
186 ipv4)
187 _dhcpd_edit_ipv4 "$@" || return $?
188 ;;
189 esac
190
191 dhcpd_global_settings_write ${proto}
192 }
193
194 _dhcpd_edit_ipv4() {
195 local val
196
197 while [ $# -gt 0 ]; do
198 case "${1}" in
199 --authoritative=*)
200 val=$(cli_get_val "${1}")
201
202 if enabled val; then
203 AUTHORITATIVE="true"
204 else
205 AUTHORITATIVE="false"
206 fi
207 ;;
208 --default-lease-time=*)
209 local val=$(cli_get_val "${1}")
210 DEFAULT_LEASE_TIME=$(parse_time ${val})
211
212 if ! isinteger DEFAULT_LEASE_TIME; then
213 error "Invalid value for --default-lease-time: ${val}"
214 return ${EXIT_ERROR}
215 fi
216 ;;
217 --max-lease-time=*)
218 local val=$(cli_get_val "${1}")
219 MAX_LEASE_TIME=$(parse_time ${val})
220
221 if ! isinteger MAX_LEASE_TIME; then
222 error "Invalid value for --max-lease-time: ${val}"
223 return ${EXIT_ERROR}
224 fi
225 ;;
226 --min-lease-time=*)
227 local val=$(cli_get_val "${1}")
228 MIN_LEASE_TIME=$(parse_time ${val})
229
230 if isset MIN_LEASE_TIME; then
231 if ! isinteger MIN_LEASE_TIME; then
232 error "Invalid value for --min-lease-time: ${val}"
233 return ${EXIT_ERROR}
234 fi
235 fi
236 ;;
237 *)
238 error "Unrecognized argument: ${1}"
239 return ${EXIT_ERROR}
240 ;;
241 esac
242 shift
243 done
244
245 if [ ${MAX_LEASE_TIME} -le ${DEFAULT_LEASE_TIME} ]; then
246 error "The max. lease time must be higher than the default lease time."
247 return ${EXIT_ERROR}
248 fi
249 }
250
251 _dhcpd_edit_ipv6() {
252 while [ $# -gt 0 ]; do
253 case "${1}" in
254 --preferred-lifetime=*)
255 local val=$(cli_get_val "${1}")
256 PREFERRED_LIFETIME=$(parse_time ${val})
257
258 if ! isinteger PREFERRED_LIFETIME; then
259 error "Invalid value for --preferred-lifetime: ${val}"
260 return ${EXIT_ERROR}
261 fi
262 ;;
263 --valid-lifetime=*)
264 local val=$(cli_get_val "${1}")
265 VALID_LIFETIME=$(parse_time ${val})
266
267 if ! isinteger VALID_LIFETIME; then
268 error "Invalid value for --valid-lifetime: ${val}"
269 return ${EXIT_ERROR}
270 fi
271 ;;
272 *)
273 error "Unrecognized argument: ${1}"
274 return ${EXIT_ERROR}
275 ;;
276 esac
277 shift
278 done
279 }
280
281 dhcpd_settings_file() {
282 local proto=${1}
283 assert isset proto
284
285 case "${proto}" in
286 ipv6)
287 print "${DHCPV6D_SETTINGS_FILE}"
288 ;;
289 ipv4)
290 print "${DHCPV4D_SETTINGS_FILE}"
291 ;;
292 esac
293
294 return ${EXIT_OK}
295 }
296
297 dhcpd_settings() {
298 local proto=${1}
299 assert isset proto
300
301 case "${proto}" in
302 ipv6)
303 print "${DHCPV6D_SETTINGS}"
304 ;;
305 ipv4)
306 print "${DHCPV4D_SETTINGS}"
307 ;;
308 esac
309
310 return ${EXIT_OK}
311 }
312
313 dhcpd_options_file() {
314 local proto=${1}
315 assert isset proto
316
317 case "${proto}" in
318 ipv6)
319 print "${DHCPV6D_OPTIONS_FILE}"
320 ;;
321 ipv4)
322 print "${DHCPV4D_OPTIONS_FILE}"
323 ;;
324 esac
325
326 return ${EXIT_OK}
327 }
328
329 dhcpd_options_list() {
330 local proto=${1}
331 assert isset proto
332
333 case "${proto}" in
334 ipv6)
335 print "DHCPV6D_OPTIONS"
336 ;;
337 ipv4)
338 print "DHCPV4D_OPTIONS"
339 ;;
340 esac
341
342 return ${EXIT_OK}
343 }
344
345 dhcpd_options() {
346 local proto=${1}
347 assert isset proto
348
349 case "${proto}" in
350 ipv6)
351 print "${DHCPV6D_OPTIONS}"
352 ;;
353 ipv4)
354 print "${DHCPV4D_OPTIONS}"
355 ;;
356 esac
357
358 return ${EXIT_OK}
359 }
360
361 dhcpd_global_settings_list() {
362 local proto="${1}"
363 assert isset proto
364
365 dhcpd_settings "${proto}"
366 }
367
368 dhcpd_global_settings_defaults() {
369 local proto=${1}
370 assert isset proto
371
372 local settings=$(dhcpd_settings ${proto})
373 assert isset settings
374
375 local prefix="DHCPV${proto/ipv/}D_"
376
377 local setting setting_default
378 for setting in ${settings}; do
379 setting_default="${prefix}${setting}"
380 printf -v ${setting} "%s" "${!setting_default}"
381 done
382 }
383
384 dhcpd_global_settings_read() {
385 local proto=${1}
386 assert isset proto
387
388 local file=$(dhcpd_settings_file ${proto})
389 assert isset file
390
391 local settings=$(dhcpd_settings ${proto})
392 assert isset settings
393
394 dhcpd_global_settings_defaults ${proto}
395 settings_read ${file} ${settings}
396 }
397
398 dhcpd_global_settings_write() {
399 local proto=${1}
400 assert isset proto
401
402 local file=$(dhcpd_settings_file ${proto})
403 assert isset file
404
405 local settings=$(dhcpd_settings ${proto})
406 assert isset settings
407
408 settings_write ${file} ${settings}
409 }
410
411 dhcpd_global_options_read() {
412 local proto=${1}
413 assert isset proto
414
415 local options_file=$(dhcpd_options_file ${proto})
416 local options_list=$(dhcpd_options_list ${proto})
417
418 settings_read_array ${options_file} options ${!options_list}
419
420 # Check if domain-name is set.
421 if [ -z "${options["domain-name"]}" ]; then
422 options["domain-name"]=$(config_domainname)
423 fi
424 }
425
426 dhcpd_subnet_escape() {
427 assert [ $# -eq 1 ]
428
429 local subnet="${1}"
430
431 # Escape any slashes
432 subnet="${subnet//\//-}"
433
434 print "${subnet}"
435 }
436
437 dhcpd_subnet_unescape() {
438 assert [ $# -eq 1 ]
439
440 local subnet="${1}"
441
442 # Unescape any slashes
443 subnet="${subnet//-/\/}"
444
445 print "${subnet}"
446 }
447
448 dhcpd_subnet_path() {
449 assert [ $# -ge 1 -a $# -le 2 ]
450
451 local proto=${1}
452 local subnet=${2}
453
454 local path
455 case "${proto}" in
456 ipv6)
457 path=${DHCPV6D_CONFIG_DIR}
458 ;;
459 ipv4)
460 path=${DHCPV4D_CONFIG_DIR}
461 ;;
462 esac
463 assert isset path
464
465 if ! isset subnet; then
466 print "${path}"
467 return ${EXIT_OK}
468 fi
469
470 # Escape subnet
471 subnet="$(dhcpd_subnet_escape ${subnet})"
472
473 # Add path prefix
474 subnet="${DHCPD_SUBNET_PREFIX}${subnet}"
475
476 print "${path}/${subnet}"
477 return ${EXIT_OK}
478 }
479
480 dhcpd_subnet_exists() {
481 local proto=${1}
482 assert isset proto
483
484 local subnet=${2}
485 assert isset subnet
486
487 local path=$(dhcpd_subnet_path ${proto} ${subnet})
488 assert isset path
489
490 [ -d "${path}" ] && return ${EXIT_TRUE} || return ${EXIT_FALSE}
491 }
492
493 dhcpd_subnet_match() {
494 local proto=${1}
495 assert isset proto
496
497 local subnet=${2}
498 assert isset subnet
499
500 local settings=$(dhcpd_subnet_settings ${proto})
501 assert isset settings
502
503 local _subnet ${settings}
504 for _subnet in $(dhcpd_subnet_list ${proto}); do
505 dhcpd_subnet_read ${proto} ${_subnet}
506
507 ${proto}_addr_eq "${ADDRESS}/${PREFIX}" "${subnet}" \
508 && return ${EXIT_TRUE}
509 done
510
511 return ${EXIT_FALSE}
512 }
513
514 dhcpd_subnet_exists() {
515 dhcpd_subnet_match "$@"
516 }
517
518 dhcpd_subnet_new() {
519 local proto=${1}
520 assert isset proto
521 shift
522
523 dhcpd_subnet_edit ${proto} "new" "$@"
524 }
525
526 dhcpd_subnet_edit() {
527 assert [ $# -ge 2 ]
528
529 local proto=${1}
530 local subnet=${2}
531 shift 2
532
533 local mode="edit"
534 if [ "${subnet}" = "new" ]; then
535 mode="new"
536 subnet=""
537 fi
538
539 local settings
540 case "${proto}" in
541 ipv6)
542 settings=${DHCPV6D_SUBNET_SETTINGS}
543 ;;
544 ipv4)
545 settings=${DHCPV4D_SUBNET_SETTINGS}
546 ;;
547 esac
548 assert isset settings
549 local ${settings}
550
551 # Read current settings
552 if [ "${mode}" = "edit" ]; then
553 dhcpd_subnet_read ${proto} ${subnet}
554 fi
555
556 while [ $# -gt 0 ]; do
557 case "${proto},${mode},${1}" in
558 # Common options
559 ipv6,new,*:*/*|ipv4,new,*.*.*.*/*)
560 local subnet="$(cli_get_val "${1}")"
561
562 ADDRESS="$(ip_split_prefix ${subnet})"
563 PREFIX="$(ip_get_prefix ${subnet})"
564 ;;
565
566 # IPv6 options
567
568 ipv6,*,--delegated-prefix=*)
569 local subnet="$(cli_get_val "${1}")"
570 if [[ -n "${subnet}" ]]; then
571 local delegated_prefix_first="${subnet%-*}"
572 local delegated_prefix_last="${subnet#*-}"
573
574 # Check for correct syntax
575 if ! isset delegated_prefix_first || ! isset delegated_prefix_last; then
576 error "--delegated-prefix= must be formatted like 2001:db8:aaaa::-2001:db8:bbbb::"
577 return ${EXIT_ERROR}
578 fi
579
580 # Check if the addresses are correct
581 local addr
582 for addr in ${delegated_prefix_first} ${delegated_prefix_last}; do
583 if ! ipv6_is_valid "${addr}"; then
584 error "Invalid IP address: ${addr}"
585 return ${EXIT_ERROR}
586 fi
587 done
588
589 # Make sure this is a range
590 if ! ipv6_addr_gt "${delegated_prefix_last}" "${delegated_prefix_first}"; then
591 error "--delegated-prefix: The second address must be larger than the first one"
592 return ${EXIT_ERROR}
593 fi
594
595 PREFIX_DELEGATION="on"
596 DELEGATED_PREFIX_FIRST="${delegated_prefix_first}"
597 DELEGATED_PREFIX_LAST="${delegated_prefix_last}"
598
599 # Prefix delegation has been disabled
600 else
601 PREFIX_DELEGATION="off"
602 fi
603 ;;
604
605 ipv6,*,--delegated-prefix-size=*)
606 local prefix_size="$(cli_get_val "${1}")"
607
608 if ipv6_prefix_size_is_valid_for_delegation "${prefix_size}"; then
609 DELEGATED_PREFIX_SIZE="${prefix_size}"
610 else
611 error "Invalid prefix size for prefix delegation: ${prefix_size}"
612 return ${EXIT_ERROR}
613 fi
614 ;;
615
616
617 # IPv4 options
618
619 ipv4,*,--routers=*)
620 ROUTERS=$(cli_get_val "${1}")
621 ;;
622
623 *)
624 error "Unknown argument: ${1}"
625 return ${EXIT_ERROR}
626 ;;
627 esac
628 shift
629 done
630
631 if ! ${proto}_is_valid ${ADDRESS} || ! ${proto}_prefix_is_valid ${PREFIX}; then
632 error "Invalid subnet: ${ADDRESS}/${PREFIX}"
633 return ${EXIT_ERROR}
634 fi
635
636 # XXX Check for subnet collisions!
637
638 case "${mode}" in
639 new)
640 if dhcpd_subnet_exists ${proto} "${ADDRESS}/${PREFIX}"; then
641 error "DHCP subnet configuration already exists for subnet ${ADDRESS}/${PREFIX}"
642 return ${EXIT_ERROR}
643 fi
644 ;;
645 esac
646
647 local file="$(dhcpd_subnet_path ${proto} "${ADDRESS}/${PREFIX}")/settings"
648 settings_write ${file} ${settings}
649 }
650
651 dhcpd_subnet_remove() {
652 assert [ $# -eq 2 ]
653
654 local proto=${1}
655 local subnet=${2}
656
657 local path=$(dhcpd_subnet_path ${proto} ${subnet})
658 assert isset path
659
660 # Remove everything of this subnet.
661 rm -rf ${path}
662 }
663
664 dhcpd_subnet_list() {
665 local proto=${1}
666 assert isset proto
667
668 local path=$(dhcpd_subnet_path ${proto})
669
670 # Return an error of the directory does not exist.
671 [ -d "${path}" ] || return ${EXIT_ERROR}
672
673 local p
674 for p in ${path}/${DHCPD_SUBNET_PREFIX}*; do
675 [ -d "${p}" ] || continue
676
677 p=$(basename ${p})
678 p="${p:${#DHCPD_SUBNET_PREFIX}}"
679
680 # Return unescaped subnet
681 dhcpd_subnet_unescape "${p}"
682 done
683 }
684
685 dhcpd_subnet_read() {
686 local proto=${1}
687 assert isset proto
688
689 local subnet=${2}
690 assert isset subnet
691
692 local file="$(dhcpd_subnet_path ${proto} ${subnet})/settings"
693 settings_read ${file}
694 }
695
696 dhcpd_subnet_range_path() {
697 assert [ $# -ge 2 -a $# -le 3 ]
698
699 local proto=${1}
700 local subnet=${2}
701
702 local range=${3}
703 if ! isset range; then
704 dhcpd_subnet_path ${proto} ${subnet}
705 return $?
706 fi
707
708 # Add prefix
709 range="${DHCPD_SUBNET_RANGE_PREFIX}${range}"
710
711 print "$(dhcpd_subnet_path ${proto} ${subnet})/${range}"
712 return ${EXIT_OK}
713 }
714
715 dhcpd_subnet_range_settings() {
716 local proto=${1}
717
718 case "${proto}" in
719 ipv6)
720 print "${DHCPV6D_SUBNET_RANGE_SETTINGS}"
721 ;;
722 ipv4)
723 print "${DHCPV4D_SUBNET_RANGE_SETTINGS}"
724 ;;
725 esac
726
727 return ${EXIT_OK}
728 }
729
730 dhcpd_subnet_range_new() {
731 local proto=${1}
732 assert isset proto
733 shift
734
735 local subnet=${1}
736 assert isset subnet
737 shift
738
739 local settings
740 case "${proto}" in
741 ipv6)
742 settings=${DHCPV6D_SUBNET_RANGE_SETTINGS}
743 ;;
744 ipv4)
745 settings=${DHCPV4D_SUBNET_RANGE_SETTINGS}
746 ;;
747 esac
748 assert isset settings
749 local range ${settings}
750
751 while [ $# -gt 0 ]; do
752 case "${1}" in
753 *-*)
754 range=${1}
755
756 START="${range%-*}"
757 END="${range#*-}"
758 ;;
759 *)
760 error "Unknown argument: ${1}"
761 return ${EXIT_ERROR}
762 ;;
763 esac
764 shift
765 done
766
767 local var
768 for var in START END; do
769 if ! ${proto}_is_valid ${!var}; then
770 error "'${!var}' is not a valid IP address"
771 return ${EXIT_ERROR}
772 fi
773 done
774
775 # Check if the end address is larger than the start address
776 if ! ${proto}_addr_gt "${END}" "${START}"; then
777 error "The end address of the range must be greater than the start address"
778 return ${EXIT_ERROR}
779 fi
780
781 # Check if range already exists
782 if dhcpd_subnet_range_exists ${proto} ${subnet} ${range}; then
783 error "DHCP subnet range ${range} already exists"
784 return ${EXIT_ERROR}
785 fi
786
787 # Search for any overlaps
788 local overlaps=$(dhcpd_subnet_range_overlaps ${proto} ${subnet} ${range})
789 if isset overlaps; then
790 error "DHCP subnet range ${range} overlaps with ${overlaps}"
791 return ${EXIT_ERROR}
792 fi
793
794 # Write the configuration to file.
795 local file=$(dhcpd_subnet_range_path ${proto} ${subnet} ${range})
796 assert isset file
797
798 settings_write ${file} ${settings}
799
800 log INFO "DHCP subnet range ${range} created"
801
802 return ${EXIT_OK}
803 }
804
805 dhcpd_subnet_range_remove() {
806 assert [ $# -eq 3 ]
807
808 local proto=${1}
809 local subnet=${2}
810 local range=${3}
811
812 if ! dhcpd_subnet_range_exists ${proto} ${subnet} ${range}; then
813 error "DHCP subnet range ${range} does not exist"
814 return ${EXIT_ERROR}
815 fi
816
817 local path=$(dhcpd_subnet_range_path ${proto} ${subnet} ${range})
818 assert isset path
819
820 rm -f ${path}
821
822 log INFO "DHCP subnet range ${range} removed"
823 return ${EXIT_OK}
824 }
825
826 dhcpd_subnet_range_list() {
827 assert [ $# -eq 2 ]
828
829 local proto=${1}
830 local subnet=${2}
831
832 local path=$(dhcpd_subnet_range_path ${proto} ${subnet})
833
834 local p
835 for p in ${path}/${DHCPD_SUBNET_RANGE_PREFIX}*; do
836 [ -r "${p}" ] || continue
837
838 p=$(basename ${p})
839 print "${p:${#DHCPD_SUBNET_RANGE_PREFIX}}"
840 done
841
842 return ${EXIT_OK}
843 }
844
845 dhcpd_subnet_range_read() {
846 assert [ $# -eq 3 ]
847
848 local proto=${1}
849 local subnet=${2}
850 local range=${3}
851
852 local file=$(dhcpd_subnet_range_path ${proto} ${subnet} ${range})
853 settings_read ${file}
854 }
855
856 dhcpd_subnet_range_exists() {
857 assert [ $# -eq 3 ]
858
859 local proto=${1}
860 local subnet=${2}
861 local range=${3}
862
863 local start=${range%-*}
864 local end=${range#*-}
865
866 assert isset start
867 assert isset end
868
869 local settings=$(dhcpd_subnet_range_settings ${proto})
870
871 local r ${settings}
872 for r in $(dhcpd_subnet_range_list ${proto} ${subnet}); do
873 dhcpd_subnet_range_read ${proto} ${subnet} ${r}
874
875 # If start and end addresses equal we got a match
876 if ${proto}_addr_eq "${START}" "${start}" && ${proto}_addr_eq "${END}" "${end}"; then
877 return ${EXIT_TRUE}
878 fi
879 done
880
881 return ${EXIT_FALSE}
882 }
883
884 dhcpd_subnet_range_overlaps() {
885 assert [ $# -eq 3 ]
886
887 local proto=${1}
888 local subnet=${2}
889 local range=${3}
890
891 local start=${range%-*}
892 local end=${range#*-}
893
894 assert isset start
895 assert isset end
896
897 local settings=$(dhcpd_subnet_range_settings ${proto})
898
899 local r ${settings}
900 for r in $(dhcpd_subnet_range_list ${proto} ${subnet}); do
901 dhcpd_subnet_range_read ${proto} ${subnet} ${r}
902
903 # Check if the new range is a sub-range of any existing range
904
905 # Check if the start address is somewhere in this range
906 if ${proto}_addr_ge ${START} ${start} && ${proto}_addr_le ${START} ${end}; then
907 print "${r}"
908 return ${EXIT_TRUE}
909 fi
910
911 # Check if the end address is somewhere in this range
912 if ${proto}_addr_ge ${END} ${start} && ${proto}_addr_le ${END} ${end}; then
913 print "${r}"
914 return ${EXIT_TRUE}
915 fi
916
917 # Check if any existing range is a sub-range of the new range
918
919 # Check if the start address is somewhere in this range
920 if ${proto}_addr_ge ${start} ${START} && ${proto}_addr_le ${start} ${END}; then
921 print "${r}"
922 return ${EXIT_TRUE}
923 fi
924
925 # Check if the end address is somewhere in this range
926 if ${proto}_addr_ge ${end} ${START} && ${proto}_addr_le ${end} ${END}; then
927 print "${r}"
928 return ${EXIT_TRUE}
929 fi
930 done
931
932 return ${EXIT_FALSE}
933 }
934
935 dhcpd_subnet_settings() {
936 local proto=${1}
937
938 case "${proto}" in
939 ipv6)
940 print "${DHCPV6D_SUBNET_SETTINGS}"
941 ;;
942 ipv4)
943 print "${DHCPV4D_SUBNET_SETTINGS}"
944 ;;
945 esac
946
947 return ${EXIT_OK}
948 }
949
950 dhcpd_subnet_options_file() {
951 local path=$(dhcpd_subnet_path "$@")
952 assert isset path
953
954 print "${path}/options"
955 }
956
957 dhcpd_subnet_options_list() {
958 local proto=${1}
959 assert isset proto
960
961 case "${proto}" in
962 ipv6)
963 print "${DHCPV6D_SUBNET_OPTIONS}"
964 ;;
965 ipv4)
966 print "${DHCPV4D_SUBNET_OPTIONS}"
967 ;;
968 esac
969
970 return ${EXIT_OK}
971 }
972
973 dhcpd_subnet_options_read() {
974 local proto=${1}
975 assert isset proto
976
977 local subnet=${2}
978 assert isset subnet
979
980 local options_file=$(dhcpd_subnet_options_file ${proto} ${subnet})
981 local options_list=$(dhcpd_subnet_options_list ${proto})
982
983 _dhcpd_read_options ${options_file} ${options_list}
984 }
985
986 # Helper functions to create a DHCP configuration file.
987 _dhcpd_write_options() {
988 local proto=${1}
989 assert isset proto
990
991 local file=${2}
992 assert isset file
993
994 local options_list=${3}
995 assert isset options_list
996
997 local ident=${4}
998
999 print "${ident}# Options" >> ${file}
1000
1001 # Dump options array.
1002 local key val fmt
1003 for key in ${!options_list}; do
1004 val=${options[${key}]}
1005
1006 # Skip the rest if val is empty
1007 isset val || continue
1008
1009 # Enable raw formatting (i.e. quote the value?)
1010 local raw="false"
1011
1012 # Update the formatting of some options
1013 case "${key}" in
1014 name-servers)
1015 val="$(list_join val ", ")"
1016 raw="true"
1017 ;;
1018 esac
1019
1020 # Prepend dhcp6 on IPv6 options.
1021 if [ "${proto}" = "ipv6" ]; then
1022 key="dhcp6.${key}"
1023 fi
1024
1025 if isinteger val; then
1026 fmt="option %s %d;"
1027 elif enabled raw || isipaddress val; then
1028 fmt="option %s %s;"
1029 else
1030 fmt="option %s \"%s\";"
1031 fi
1032
1033 print "${ident}${fmt}" "${key}" "${val}"
1034 done >> ${file}
1035
1036 # Empty line
1037 print >> ${file}
1038 }
1039
1040 _dhcpd_read_options() {
1041 local file=${1}
1042 assert isset file
1043
1044 local options_list=${2}
1045 assert isset options_list
1046
1047 settings_read_array ${file} options ${!options_list}
1048 }
1049
1050 _dhcpd_write_subnet() {
1051 assert [ $# -eq 3 ]
1052
1053 local proto=${1}
1054 local subnet=${2}
1055 local file=${3}
1056
1057 # Check which settings we do expect.
1058 local settings
1059 case "${proto}" in
1060 ipv6)
1061 settings=${DHCPV6D_SUBNET_SETTINGS}
1062 ;;
1063 ipv4)
1064 settings=${DHCPV4D_SUBNET_SETTINGS}
1065 ;;
1066 esac
1067 assert isset settings
1068 local ${settings}
1069
1070 # Read configuration settings.
1071 dhcpd_subnet_read ${proto} ${subnet}
1072
1073 print "# Subnet declaration" >> ${file}
1074 case "${proto}" in
1075 ipv6)
1076 print "subnet6 ${ADDRESS}/${PREFIX} {" >> ${file}
1077 ;;
1078 ipv4)
1079 local netmask="$(ipv4_prefix2netmask "${PREFIX}")"
1080 print "subnet ${ADDRESS} netmask ${netmask} {" >> ${file}
1081 ;;
1082 esac
1083
1084 # Add options.
1085 _dhcpd_write_subnet_options ${proto} ${subnet} ${file}
1086
1087 # Prefix Delegation for IPv6
1088 if [[ "${proto}" = "ipv6" ]]; then
1089 _dhcpd_write_subnet_pd "${subnet}" "${file}"
1090 fi
1091
1092 # Add the ranges.
1093 local range
1094 for range in $(dhcpd_subnet_range_list ${proto} ${subnet} ${range}); do
1095 _dhcpd_write_subnet_range ${proto} ${subnet} ${range} ${file}
1096 done
1097
1098 # End this subnet block.
1099 print "}\n" >> ${file}
1100
1101 return ${EXIT_OK}
1102 }
1103
1104 _dhcpd_write_subnet_options() {
1105 assert [ $# -eq 3 ]
1106
1107 local proto=${1}
1108 local subnet=${2}
1109 local file=${3}
1110
1111 local settings
1112 local options_file="$(dhcpd_subnet_path ${proto} ${subnet})/options"
1113 local options_list
1114 case "${proto}" in
1115 ipv6)
1116 settings=${DHCPV6D_SUBNET_SETTINGS}
1117 options_list="DHCPV6D_OPTIONS"
1118 ;;
1119 ipv4)
1120 settings=${DHCPV4D_SUBNET_SETTINGS}
1121 options_list="DHCPV4D_OPTIONS"
1122 ;;
1123 esac
1124 assert isset settings
1125 assert isset options_list
1126
1127 local ${settings}
1128 dhcpd_subnet_read ${proto} ${subnet}
1129
1130 local -A options
1131 _dhcpd_read_options ${options_file} ${options_list}
1132
1133 # Fill in router, if not already set.
1134 if [ -z "${options["routers"]}" ]; then
1135 options["routers"]=$(_dhcpd_search_routers ${proto} "${ADDRESS}/${PREFIX}")
1136 fi
1137
1138 _dhcpd_write_options ${proto} ${file} ${options_list} "\t"
1139 }
1140
1141 _dhcpd_write_subnet_pd() {
1142 # Nothing to do if prefix delegation is not enabled
1143 enabled PREFIX_DELEGATION || return ${EXIT_OK}
1144
1145 assert [ $# -eq 2 ]
1146
1147 local subnet="${1}"
1148 local file="${2}"
1149
1150 local prefix_size="${DELEGATED_PREFIX_SIZE}"
1151 isset prefix_size || prefix_size="${DHCP_DEFAULT_DELEGATED_PREFIX_SIZE}"
1152
1153 assert ipv6_is_valid "${DELEGATED_PREFIX_FIRST}"
1154 assert ipv6_is_valid "${DELEGATED_PREFIX_LAST}"
1155 assert ipv6_prefix_size_is_valid_for_delegation "${prefix_size}"
1156
1157 (
1158 print " # Prefix Delegation"
1159 print " prefix6 ${DELEGATED_PREFIX_FIRST} ${DELEGATED_PREFIX_LAST} /${prefix_size};"
1160 print ""
1161 ) >> "${file}"
1162 }
1163
1164 _dhcpd_search_routers() {
1165 assert [ $# -eq 2 ]
1166
1167 local proto=${1}
1168 local subnet=${2}
1169
1170 # Do nothing for IPv6 (yet?).
1171 [ "${proto}" = "ipv6" ] && return ${EXIT_OK}
1172
1173 local routers zone addr
1174 for zone in $(zones_get_all); do
1175 addr="$(db_get "${zone}/${proto}/local-ip-address")"
1176 isset addr || continue
1177
1178 if ipv4_in_subnet ${addr} ${subnet}; then
1179 list_append routers $(ip_split_prefix ${addr})
1180 fi
1181 done
1182
1183 list_join routers ", "
1184 }
1185
1186 _dhcpd_write_subnet_range() {
1187 assert [ $# -eq 4 ]
1188
1189 local proto=${1}
1190 local subnet=${2}
1191 local range=${3}
1192 local file=${4}
1193
1194 local settings=$(dhcpd_subnet_range_settings ${proto})
1195 assert isset settings
1196
1197 # Read the configuration settings.
1198 local ${settings}
1199 dhcpd_subnet_range_read ${proto} ${subnet} ${range}
1200
1201 case "${proto}" in
1202 ipv6)
1203 print " range6 ${START} ${END};" >> ${file}
1204 ;;
1205 ipv4)
1206 print " range ${START} ${END};" >> ${file}
1207 ;;
1208 esac
1209 print >> ${file}
1210
1211 return ${EXIT_OK}
1212 }
1213
1214 dhcpd_write_config() {
1215 assert [ $# -eq 1 ]
1216
1217 local proto=${1}
1218
1219 local file options_list
1220 case "${proto}" in
1221 ipv6)
1222 file=${DHCPV6D_CONFIG_FILE}
1223 options_list="DHCPV6D_OPTIONS"
1224 ;;
1225 ipv4)
1226 file=${DHCPV4D_CONFIG_FILE}
1227 options_list="DHCPV4D_OPTIONS"
1228 ;;
1229 esac
1230 assert isset file
1231 assert isset options_list
1232
1233 # Writing header.
1234 config_header "DHCP ${proto} daemon configuration file" > ${file}
1235
1236 # Read global DHCP configuration
1237 dhcpd_global_settings_read "${proto}"
1238
1239 # Authoritative.
1240 if enabled AUTHORITATIVE; then
1241 (
1242 print "# This is an authoritative DHCP server for this network."
1243 print "authoritative;\n"
1244 ) >> ${file}
1245 else
1246 (
1247 print "# This is NOT an authoritative DHCP server for this network."
1248 print "not authoritative;\n"
1249 ) >> ${file}
1250 fi
1251
1252 case "${proto}" in
1253 ipv6)
1254 # Lease times.
1255 if isinteger VALID_LIFETIME; then
1256 print "default-lease-time %d;" "${VALID_LIFETIME}" >> ${file}
1257 fi
1258
1259 if isinteger PREFERRED_LIFETIME; then
1260 print "preferred-lifetime %d;" "${PREFERRED_LIFETIME}" >> ${file}
1261 fi
1262 ;;
1263 ipv4)
1264 # Lease times.
1265 if isinteger DEFAULT_LEASE_TIME; then
1266 print "default-lease-time %d;" "${DEFAULT_LEASE_TIME}" >> ${file}
1267 fi
1268
1269 if isinteger MAX_LEASE_TIME; then
1270 print "max-lease-time %d;" "${MAX_LEASE_TIME}" >> ${file}
1271 fi
1272
1273 if isinteger MIN_LEASE_TIME; then
1274 print "min-lease-time %d;" "${MIN_LEASE_TIME}" >> ${file}
1275 fi
1276 ;;
1277 esac
1278
1279 # Write the options to file.
1280 local -A options
1281 dhcpd_global_options_read ${proto}
1282 _dhcpd_write_options ${proto} ${file} ${options_list}
1283
1284 # Add all subnet declarations.
1285 local subnet
1286 for subnet in $(dhcpd_subnet_list ${proto}); do
1287 _dhcpd_write_subnet ${proto} ${subnet} ${file}
1288 done
1289
1290 return ${EXIT_OK}
1291 }