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