isc-dhcpd: Generate correct delimiter in hex strings
[feed/packages.git] / net / isc-dhcp / files / dhcpd.init
1 #!/bin/sh /etc/rc.common
2
3 START=25
4 USE_PROCD=1
5 PROG=/usr/sbin/dhcpd
6
7 TTL=3600
8 PREFIX="update add"
9
10 lease_file=/tmp/dhcpd.leases
11 config_file=/tmp/run/dhcpd.conf
12
13 dyndir=/tmp/bind
14 conf_local_file=$dyndir/named.conf.local
15
16 session_key_name=local-ddns
17 session_key_file=/var/run/named/session.key
18
19 time2seconds() {
20 local timestring=$1
21 local multiplier number suffix
22
23 suffix="${timestring//[0-9 ]}"
24 number="${timestring%%$suffix}"
25 [ "$number$suffix" != "$timestring" ] && return 1
26 case "$suffix" in
27 "" | s)
28 multiplier=1
29 ;;
30 m)
31 multiplier=60
32 ;;
33 h)
34 multiplier=3600
35 ;;
36 d)
37 multiplier=86400
38 ;;
39 w)
40 multiplier=604800
41 ;;
42 *)
43 return 1
44 ;;
45 esac
46 echo $(( number * multiplier ))
47 }
48
49 trim() {
50 local arg="$1"
51
52 echo "$arg" | sed -e 's/^ *//' -e 's/ *$//'
53 }
54
55 rfc1918_prefix() {
56 local octets="$(echo "${1%%/*}" | cut -d. -f1)"
57
58 [ "$octets" = "10" ] && { echo "$octets"; return; }
59
60 octets="$(echo "${1%%/*}" | cut -d. -f1-2)"
61
62 case "$octets" in
63 172.1[6789]|172.2[0-9]|172.3[01]|192.168)
64 echo "$octets"
65 ;;
66 esac
67 }
68
69 no_ipv6() {
70 [ -n "$(named-checkconf -px \
71 | sed -r -ne '1N; N; /^\tlisten-on-v6 ?\{\n\t\t"none";\n\t\};$/{ p; q; }; D')" ]
72 }
73
74 # duplicated from dnsmasq init script
75 hex_to_hostid() {
76 local var="$1"
77 local hex="${2#0x}" # strip optional "0x" prefix
78
79 if [ -n "${hex//[0-9a-fA-F]/}" ]; then
80 # is invalid hex literal
81 return 1
82 fi
83
84 # convert into host id
85 export "$var=$(
86 printf "%0x:%0x" \
87 $(((0x$hex >> 16) % 65536)) \
88 $(( 0x$hex % 65536))
89 )"
90
91 return 0
92 }
93
94 typeof() {
95 echo "$1" | awk '
96 /^\d+\.\d+\.\d+\.\d+$/ { print "ip\n"; next; }
97 /^(true|false)$/ { print "bool\n"; next; }
98 /^\d+$/ { print "integer\n"; next; }
99 /^"[^"]*"$/ { print "string\n"; next; }
100 /^[0-9a-fA-F]{2,2}(:[0-9a-fA-F]{2,2})*$/ { print "string\n"; next; }
101 { print "other\n"; next; }
102 '
103 }
104
105 update() {
106 local lhs="$1" family="$2" type="$3"
107 shift 3
108
109 [ $dynamicdns -eq 1 ] && \
110 echo -e "$PREFIX" "$lhs $family $type $@\nsend" >> $dyn_file
111 }
112
113 explode() {
114 local arg="$1"
115
116 echo "$arg" | sed -e 's/\./, /g'
117 }
118
119 rev_str() {
120 local str="$1" delim="$2"
121 local frag result="" IFS="$delim"
122
123 for frag in $str; do
124 result="$frag${result:+$delim}$result"
125 done
126
127 echo "$result"
128 }
129
130 create_empty_zone() {
131 local zone="$1"
132
133 if [ ! -f $dyndir/db."$zone" ]; then
134 cp -p /etc/bind/db.empty $dyndir/db."$zone"
135 chmod g+w $dyndir/db."$zone"
136 chgrp bind $dyndir/db."$zone"
137 fi
138 }
139
140 append_routes() {
141 local tuple tuple="$(trim "$1")"
142 local network prefix router save octets compacted
143
144 save="${tuple% *}"
145 router="$(trim "${tuple#${save} }")"
146
147 network="$(trim "${save%/[0-9]*}")"
148
149 prefix="$(trim "${save##${network}/}")"
150
151 octets=$((($prefix + 7) / 8))
152 compacted="$(echo "$network" | cut -d. -f1-$octets)"
153
154 routes="$routes${routes:+, }$(explode "$prefix${compacted:+.$compacted}.$router")"
155 }
156
157 append_dhcp_options() {
158 local tuple="$1"
159
160 # strip redundant "option:" prefix
161 tuple="${tuple#option:}"
162
163 local tag="${tuple%%,*}"
164 local values="${tuple#$tag,}"
165
166 local formatted value
167 local IFS=$', \n'
168 for value in $values; do
169 # detect type of $value and quote if necessary
170 case $(typeof "$value") in
171 ip|bool|integer|string)
172 ;;
173 other)
174 value="\"$value\""
175 ;;
176 esac
177 formatted="$formatted${formatted:+, }$value"
178 done
179 echo " option $tag $formatted;"
180 }
181
182 static_cname_add() {
183 local cfg="$1"
184 local cname target
185
186 config_get cname "$cfg" "cname"
187 [ -n "$cname" ] || return 0
188 config_get target "$cfg" "target"
189 [ -n "$target" ] || return 0
190
191 update "$cname.$domain." IN CNAME "$target.$domain."
192 }
193
194 static_cnames() {
195 config_foreach static_cname_add cname "$@"
196 }
197
198 static_domain_add() {
199 local cfg="$1"
200 local name ip ips revip
201
202 config_get name "$cfg" "name"
203 [ -n "$name" ] || return 0
204 config_get ip "$cfg" "ip"
205 [ -n "$ip" ] || return 0
206
207 ips="$ip"
208 for ip in $ips; do
209 revip="$(rev_str "$ip" ".")"
210
211 update "$name.$domain." IN A "$ip"
212 [ -n "$(rfc1918_prefix "$ip")" ] && \
213 update "$revip.in-addr.arpa." IN PTR "$name.$domain."
214 done
215 }
216
217 static_domains() {
218 config_foreach static_domain_add domain "$@"
219 }
220
221 static_mxhost_add() {
222 local cfg="$1"
223 local domain2 relay pref
224
225 config_get domain2 "$cfg" "domain"
226 [ -n "$domain2" ] || return 0
227 config_get relay "$cfg" "relay"
228 [ -n "$relay" ] || return 0
229 config_get pref "$cfg" "pref"
230 [ -n "$pref" ] || return 0
231
232 if [ "$domain2" = "@" ]; then
233 update "$domain." IN MX "$pref" "$relay.$domain."
234 else
235 update "$domain2.$domain." IN MX "$pref" "$relay.$domain."
236 fi
237 }
238
239 static_mxhosts() {
240 config_foreach static_mxhost_add mxhost "$@"
241 }
242
243 static_srvhost_add() {
244 local cfg="$1"
245 local srv target port priority weight
246
247 config_get srv "$cfg" "srv"
248 [ -n "$srv" ] || return 0
249 config_get target "$cfg" "target"
250 [ -n "$target" ] || return 0
251 config_get port "$cfg" "port"
252 [ -n "$port" ] || return 0
253 config_get priority "$cfg" "priority"
254 [ -n "$priority" ] || return 0
255 config_get weight "$cfg" "weight"
256 [ -n "$weight" ] || return 0
257
258 update "$srv.$domain." IN SRV "$priority" "$weight" "$port" "$target.$domain"
259 }
260
261 static_srvhosts() {
262 config_foreach static_srvhost_add srvhost "$@"
263 }
264
265 static_host_add() {
266 local cfg="$1"
267 local broadcast hostid macn macs mac name ip ips revip leasetime
268 local force_send extra_options option
269
270 config_get macs "$cfg" "mac"
271 [ -n "$macs" ] || return 0
272 config_get name "$cfg" "name"
273 [ -n "$name" ] || return 0
274 config_get ip "$cfg" "ip"
275 [ -n "$ip" ] || return 0
276
277 config_get_bool broadcast "$cfg" "broadcast" 0
278 config_get dns "$cfg" "dns"
279 config_get gateway "$cfg" "gateway"
280 config_get leasetime "$cfg" "leasetime"
281 if [ -n "$leasetime" ] ; then
282 leasetime="$(time2seconds "$leasetime")"
283 [ "$?" -ne 0 ] && return 1
284 fi
285
286 config_get hostid "$cfg" "hostid"
287 if [ -n "$hostid" ] ; then
288 hex_to_hostid hostid "$hostid" || return 1
289 fi
290
291 config_get force_send "$cfg" "force_send"
292 extra_options=
293 local _IFS="$IFS" IFS=','
294 for option in $force_send; do
295 case "$option" in
296 hostname)
297 extra_options="$extra_options${extra_options:+,}0c" ;;
298 domain-name)
299 extra_options="$extra_options${extra_options:+,}0f" ;;
300 renewal-time)
301 extra_options="$extra_options${extra_options:+,}3a" ;;
302 rebinding-time)
303 extra_options="$extra_options${extra_options:+,}3b" ;;
304 fqdn)
305 extra_options="$extra_options${extra_options:+,}51" ;;
306 routes)
307 extra_options="$extra_options${extra_options:+,}79" ;;
308 *)
309 echo "unknown option: $option" >&2 ;;
310 esac
311 done
312 IFS="$_IFS"
313
314 macn=0
315 for mac in $macs; do
316 macn=$(( macn + 1 ))
317 done
318
319 for mac in $macs; do
320 local secname="$name"
321 if [ $macn -gt 1 ] ; then
322 secname="${name}-${mac//:}"
323 fi
324 echo "host $secname {"
325 echo " hardware ethernet $mac;"
326 echo " fixed-address $ip;"
327 echo " option host-name \"$name\";"
328 if [ "$broadcast" -eq 1 ] ; then
329 echo " always-broadcast true;"
330 fi
331 if [ -n "$leasetime" ] ; then
332 echo " default-lease-time $leasetime;"
333 echo " max-lease-time $leasetime;"
334 fi
335 if [ -n "$hostid" ] ; then
336 echo " option dhcp-client-identifier $hostid;"
337 fi
338 if [ -n "$dns" ] ; then
339 echo " option domain-name-servers $dns;"
340 fi
341 if [ -n "$gateway" ] ; then
342 echo " option routers $gateway;"
343 fi
344
345 local routes=
346 config_list_foreach "$cfg" "routes" append_routes
347 [ -n "$routes" ] && echo " option classless-ipv4-route $routes;"
348
349 config_list_foreach "$cfg" "dhcp_option" append_dhcp_options
350 if [ -n "$extra_options" ]; then
351 echo -e " if exists dhcp-parameter-request-list {\n option dhcp-parameter-request-list = concat(option dhcp-parameter-request-list, $extra_options);\n }"
352 fi
353 echo "}"
354 done
355
356 ips="$ip"
357 for ip in $ips; do
358 revip="$(rev_str "$ip" ".")"
359
360 update "$name.$domain." IN A "$ip"
361 update "$revip.in-addr.arpa." IN PTR "$name.$domain."
362 done
363 }
364
365 static_hosts() {
366 config_foreach static_host_add host "$@"
367 }
368
369 gen_dhcp_subnet() {
370 local cfg="$1"
371
372 echo "subnet $NETWORK netmask $NETMASK {"
373 if [ -n "$START" ] && [ -n "$END" ]; then
374 echo " range $START $END;"
375 fi
376 echo " option subnet-mask $netmask;"
377 if [ "$BROADCAST" != "0.0.0.0" ] ; then
378 echo " option broadcast-address $BROADCAST;"
379 fi
380 if [ "$dynamicdhcp" -eq 0 ] ; then
381 if [ "$authoritative" -eq 1 ] ; then
382 echo " deny unknown-clients;"
383 else
384 echo " ignore unknown-clients;"
385 fi
386 fi
387 if [ -n "$leasetime" ] ; then
388 echo " default-lease-time $leasetime;"
389 echo " max-lease-time $leasetime;"
390 fi
391 if [ "$defaultroute" -eq 1 ] ; then
392 echo " option routers $gateway;"
393 fi
394 echo " option domain-name-servers $DNS;"
395
396 local routes=
397 config_list_foreach "$cfg" "routes" append_routes
398 [ -n "$routes" ] && echo " option classless-ipv4-route $routes;"
399
400 config_list_foreach "$cfg" "dhcp_option" append_dhcp_options
401 echo "}"
402 }
403
404 dhcpd_add() {
405 local cfg="$1" synthesize="$2"
406 local dhcp6range="::"
407 local dynamicdhcp defaultroute end gateway ifname ignore leasetime limit net netmask
408 local proto networkid start subnet
409 local IP NETMASK BROADCAST NETWORK PREFIX DNS START END
410
411 config_get_bool ignore "$cfg" "ignore" 0
412 [ "$ignore" = "0" ] || return 0
413
414 config_get net "$cfg" "interface"
415 [ -n "$net" ] || return 0
416
417 config_get start "$cfg" "start"
418 config_get limit "$cfg" "limit"
419
420 case "$start:$limit" in
421 ":*"|"*:")
422 echo "dhcpd: start/limit must be used together in $cfg" >&2
423 return 0
424 esac
425
426 network_get_subnet subnet "$net" || return 0
427 network_get_device ifname "$net" || return 0
428 network_get_protocol proto "$net" || return 0
429
430 [ static = "$proto" ] || return 0
431
432 local octets="$(rfc1918_prefix "$subnet")"
433
434 [ -n "$octets" ] && rfc1918_nets="$rfc1918_nets${rfc1918_nets:+ }$octets"
435
436 [ $synthesize -eq 0 ] && return
437
438 config_get_bool dynamicdhcp "$cfg" "dynamicdhcp" 1
439
440 config_get_bool defaultroute "$cfg" "default_route" 1
441
442 dhcp_ifs="$dhcp_ifs $ifname"
443
444 eval "$(ipcalc.sh $subnet $start $limit)"
445
446 config_get netmask "$cfg" "netmask" "$NETMASK"
447 config_get leasetime "$cfg" "leasetime"
448 if [ -n "$leasetime" ] ; then
449 leasetime="$(time2seconds "$leasetime")"
450 [ "$?" -ne 0 ] && return 1
451 fi
452
453 if network_get_dnsserver dnsserver "$net" ; then
454 for dnsserv in $dnsserver ; do
455 DNS="$DNS${DNS:+, }$dnsserv"
456 done
457 else
458 DNS="$IP"
459 fi
460
461 if ! network_get_gateway gateway "$net" ; then
462 gateway="$IP"
463 fi
464
465 gen_dhcp_subnet "$cfg"
466 }
467
468 general_config() {
469 local always_broadcast boot_unknown_clients log_facility
470 local default_lease_time max_lease_time
471
472 config_get_bool always_broadcast "isc_dhcpd" "always_broadcast" 0
473 config_get_bool authoritative "isc_dhcpd" "authoritative" 1
474 config_get_bool boot_unknown_clients "isc_dhcpd" "boot_unknown_clients" 1
475 config_get default_lease_time "isc_dhcpd" "default_lease_time" 3600
476 config_get max_lease_time "isc_dhcpd" "max_lease_time" 86400
477 config_get log_facility "isc_dhcpd" "log_facility"
478
479 config_get domain "isc_dhcpd" "domain"
480 config_get_bool dynamicdns "isc_dhcpd" dynamicdns 0
481
482 [ $always_broadcast -eq 1 ] && echo "always-broadcast true;"
483 [ $authoritative -eq 1 ] && echo "authoritative;"
484 [ $boot_unknown_clients -eq 0 ] && echo "boot-unknown-clients false;"
485
486 default_lease_time="$(time2seconds "$default_lease_time")"
487 [ "$?" -ne 0 ] && return 1
488 max_lease_time="$(time2seconds "$max_lease_time")"
489 [ "$?" -ne 0 ] && return 1
490
491 if [ $dynamicdns -eq 1 ]; then
492 create_empty_zone "$domain"
493
494 local mynet
495
496 for mynet in $rfc1918_nets; do
497 mynet="$(rev_str "$mynet" ".")"
498 create_empty_zone "$mynet.in-addr.arpa"
499 done
500
501 local need_reload=
502
503 cp -p $conf_local_file ${conf_local_file}_
504
505 cat <<EOF > $conf_local_file
506 zone "$domain" {
507 type master;
508 file "$dyndir/db.$domain";
509 update-policy {
510 grant $session_key_name zonesub any;
511 };
512 };
513
514 EOF
515
516 for mynet in $rfc1918_nets; do
517 mynet="$(rev_str "$mynet" ".")"
518 cat <<EOF >> $conf_local_file
519 zone "$mynet.in-addr.arpa" {
520 type master;
521 file "$dyndir/db.$mynet.in-addr.arpa";
522 update-policy {
523 grant $session_key_name zonesub any;
524 };
525 };
526
527 EOF
528 done
529
530 cmp -s $conf_local_file ${conf_local_file}_ || need_reload=1
531 rm -f ${conf_local_file}_
532
533 [ -n "$need_reload" ] && /etc/init.d/named reload
534 sleep 1
535
536 cat <<EOF
537 ddns-domainname "$domain.";
538 ddns-update-style standard;
539 ddns-updates on;
540 ignore client-updates;
541
542 update-static-leases on;
543 use-host-decl-names on;
544 update-conflict-detection off;
545 update-optimization off;
546
547 include "$session_key_file";
548
549 zone $domain. {
550 primary 127.0.0.1;
551 key $session_key_name;
552 }
553
554 EOF
555
556 for mynet in $rfc1918_nets; do
557 mynet="$(rev_str "$mynet" ".")"
558 cat <<EOF
559 zone $mynet.in-addr.arpa. {
560 primary 127.0.0.1;
561 key $session_key_name;
562 }
563
564 EOF
565 done
566 fi
567
568 if [ -n "$log_facility" ] ; then
569 echo "log-facility $log_facility;"
570 fi
571 echo "default-lease-time $default_lease_time;"
572 echo "max-lease-time $max_lease_time;"
573
574 [ -n "$domain" ] && echo "option domain-name \"$domain\";"
575
576 echo -e "\n# additional codes\noption classless-ipv4-route code 121 = array of { unsigned integer 8 };\n"
577
578 rm -f /tmp/resolv.conf
579 echo "# This file is generated by the DHCPD service" > /tmp/resolv.conf
580 [ -n "$domain" ] && echo "domain $domain" >> /tmp/resolv.conf
581 echo "nameserver 127.0.0.1" >> /tmp/resolv.conf
582 }
583
584 # base procd hooks
585
586 boot() {
587 DHCPD_BOOT=1
588 start "$@"
589 }
590
591 start_service() {
592 local domain dhcp_ifs authoritative dynamicdns
593
594 if [ -n "$DHCPD_BOOT" ] ; then
595 return 0
596 fi
597
598 if [ ! -e $lease_file ] ; then
599 touch $lease_file
600 fi
601
602 if [ -e "/etc/dhcpd.conf" ] ; then
603 config_file="/etc/dhcpd.conf"
604 else
605 . /lib/functions/network.sh
606
607 local dyn_file=$(mktemp -u /tmp/dhcpd.XXXXXX)
608
609 config_load dhcp
610
611 local rfc1918_nets=""
612
613 # alas we have to make 2 passes...
614 config_foreach dhcpd_add dhcp 0
615
616 rfc1918_nets="$(echo "$rfc1918_nets" | tr ' ' $'\n' | sort | uniq | tr $'\n' ' ')"
617
618 general_config > $config_file
619
620 if [ $dynamicdns -eq 1 ]; then
621 cat <<EOF > $dyn_file
622 ; Generated by /etc/init.d/dhcpd at $(date)
623
624 ttl $TTL
625
626 EOF
627 fi
628
629 rfc1918_nets=
630
631 config_foreach dhcpd_add dhcp 1 >> $config_file
632
633 static_hosts >> $config_file
634
635 static_cnames >> $config_file
636
637 static_domains >> $config_file
638
639 static_mxhosts >> $config_file
640
641 static_srvhosts >> $config_file
642
643 if [ $dynamicdns -eq 1 ]; then
644 local args=
645
646 no_ipv6 && args="-4"
647
648 nsupdate -l -v $args $dyn_file
649
650 fi
651
652 rm -f $dyn_file
653
654 [ -z "$dhcp_ifs" ] && return 0
655 fi
656
657 procd_open_instance
658 procd_set_param command $PROG -q -f -cf $config_file -lf $lease_file $dhcp_ifs
659 procd_close_instance
660 }
661
662 reload_service() {
663 rc_procd start_service "$@"
664 procd_send_signal dhcpd "$@"
665 }
666
667 add_interface_trigger() {
668 local cfg=$1
669 local trigger ignore
670
671 config_get trigger "$cfg" interface
672 config_get_bool ignore "$cfg" ignore 0
673
674 if [ -n "$trigger" -a $ignore -eq 0 ] ; then
675 procd_add_reload_interface_trigger "$trigger"
676 fi
677 }
678
679 service_triggers() {
680 if [ -n "$DHCPD_BOOT" ] ; then
681 # Make the first start robust to slow interfaces; wait a while
682 procd_add_raw_trigger "interface.*.up" 5000 /etc/init.d/dhcpd restart
683
684 else
685 # reload with normal parameters
686 procd_add_reload_trigger "network" "dhcp"
687 config_load dhcp
688 config_foreach add_interface_trigger dhcp
689 fi
690 }
691