resolvconf.in revision 1.9 1 #!/bin/sh
2 # Copyright (c) 2007-2020 Roy Marples
3 # All rights reserved
4
5 # Redistribution and use in source and binary forms, with or without
6 # modification, are permitted provided that the following conditions
7 # are met:
8 # * Redistributions of source code must retain the above copyright
9 # notice, this list of conditions and the following disclaimer.
10 # * Redistributions in binary form must reproduce the above
11 # copyright notice, this list of conditions and the following
12 # disclaimer in the documentation and/or other materials provided
13 # with the distribution.
14 #
15 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
27 RESOLVCONF="$0"
28 OPENRESOLV_VERSION="3.12.0"
29 SYSCONFDIR=@SYSCONFDIR@
30 LIBEXECDIR=@LIBEXECDIR@
31 VARDIR=@VARDIR@
32 RCDIR=@RCDIR@
33 RESTARTCMD=@RESTARTCMD@
34
35 if [ "$1" = "--version" ]; then
36 echo "openresolv $OPENRESOLV_VERSION"
37 echo "Copyright (c) 2007-2020 Roy Marples"
38 exit 0
39 fi
40
41 # Disregard dhcpcd setting
42 unset interface_order state_dir
43
44 # If you change this, change the test in VFLAG and libc.in as well
45 local_nameservers="127.* 0.0.0.0 255.255.255.255 ::1"
46
47 dynamic_order="tap[0-9]* tun[0-9]* vpn vpn[0-9]* wg[0-9]* ppp[0-9]* ippp[0-9]*"
48 interface_order="lo lo[0-9]*"
49 name_server_blacklist="0.0.0.0"
50
51 # Support original resolvconf configuration layout
52 # as well as the openresolv config file
53 if [ -f "$SYSCONFDIR"/resolvconf.conf ]; then
54 . "$SYSCONFDIR"/resolvconf.conf
55 [ -n "$state_dir" ] && VARDIR="$state_dir"
56 elif [ -d "$SYSCONFDIR/resolvconf" ]; then
57 SYSCONFDIR="$SYSCONFDIR/resolvconf"
58 if [ -f "$SYSCONFDIR"/interface-order ]; then
59 interface_order="$(cat "$SYSCONFDIR"/interface-order)"
60 fi
61 fi
62
63 IFACEDIR="$VARDIR/interfaces"
64 METRICDIR="$VARDIR/metrics"
65 PRIVATEDIR="$VARDIR/private"
66 EXCLUSIVEDIR="$VARDIR/exclusive"
67 DEPRECATEDDIR="$VARDIR/deprecated"
68 LOCKDIR="$VARDIR/lock"
69 _PWD="$PWD"
70
71 warn()
72 {
73 echo "$*" >&2
74 }
75
76 error_exit()
77 {
78 echo "$*" >&2
79 exit 1
80 }
81
82 usage()
83 {
84 cat <<-EOF
85 Usage: ${RESOLVCONF##*/} [options] command [argument]
86
87 Inform the system about any DNS updates.
88
89 Commands:
90 -a \$INTERFACE Add DNS information to the specified interface
91 (DNS supplied via stdin in resolv.conf format)
92 -C \$PATTERN Deprecate DNS information for matched interfaces
93 -c \$PATTERN Configure DNS information for matched interfaces
94 -d \$INTERFACE Delete DNS information from the specified interface
95 -h Show this help cruft
96 -i [\$PATTERN] Show interfaces that have supplied DNS information
97 optionally from interfaces that match the specified
98 pattern
99 -l [\$PATTERN] Show DNS information, optionally from interfaces
100 that match the specified pattern
101
102 -u Run updates from our current DNS information
103 --version Echo the ${RESOLVCONF##*/} version
104
105 Options:
106 -f Ignore non existent interfaces
107 -m metric Give the added DNS information a metric
108 -p Mark the interface as private
109 -x Mark the interface as exclusive
110
111 Subscriber and System Init Commands:
112 -I Init the state dir
113 -r \$SERVICE Restart the system service
114 (restarting a non-existent or non-running service
115 should have no output and return 0)
116 -R Show the system service restart command
117 -v [\$PATTERN] echo NEWDOMAIN, NEWSEARCH and NEWNS variables to
118 the console
119 -V [\$PATTERN] Same as -v, but only uses configuration in
120 $SYSCONFDIR/resolvconf.conf
121 EOF
122 [ -z "$1" ] && exit 0
123 echo
124 error_exit "$*"
125 }
126
127 # Strip any trailing dot from each name as a FQDN does not belong
128 # in resolv.conf(5)
129 # If you think otherwise, capture a DNS trace and you'll see libc
130 # will strip it regardless.
131 # This also solves setting up duplicate zones in our subscribers.
132 # Also strip any comments denoted by #.
133 resolv_strip()
134 {
135 space=
136 for word; do
137 case "$word" in
138 \#*) break;;
139 esac
140 printf "%s%s" "$space${word%.}"
141 space=" "
142 done
143 printf "\n"
144 }
145
146 private_iface()
147 {
148 # Allow expansion
149 cd "$IFACEDIR"
150
151 # Public interfaces override private ones.
152 for p in $public_interfaces; do
153 case "$iface" in
154 "$p"|"$p":*) return 1;;
155 esac
156 done
157
158 if [ -e "$PRIVATEDIR/$iface" ]; then
159 return 0
160 fi
161
162 for p in $private_interfaces; do
163 case "$iface" in
164 "$p"|"$p":*) return 0;;
165 esac
166 done
167
168 # Not a private interface
169 return 1
170 }
171
172 # Parse resolv.conf's and make variables
173 # for domain name servers, search name servers and global nameservers
174 parse_resolv()
175 {
176 domain=
177 new=true
178 newns=
179 ns=
180 private=false
181 search=
182
183 while read -r line; do
184 stripped_line="$(resolv_strip ${line#* })"
185 case "$line" in
186 "# resolv.conf from "*)
187 if ${new}; then
188 iface="${line#\# resolv.conf from *}"
189 new=false
190 if private_iface "$iface"; then
191 private=true
192 else
193 private=false
194 fi
195 fi
196 ;;
197 "nameserver "*)
198 islocal=false
199 for l in $local_nameservers; do
200 case "$stripped_line" in
201 $l)
202 islocal=true
203 break
204 ;;
205 esac
206 done
207 if $islocal; then
208 echo "LOCALNAMESERVERS=\"\$LOCALNAMESERVERS $stripped_line\""
209 else
210 ns="$ns$stripped_line "
211 fi
212 ;;
213 "domain "*)
214 search="$stripped_line"
215 if [ -z "$domain" ]; then
216 domain="$search"
217 echo "DOMAIN=\"$domain\""
218 fi
219 ;;
220 "search "*)
221 search="$stripped_line"
222 ;;
223 *)
224 [ -n "$line" ] && continue
225 if [ -n "$ns" ] && [ -n "$search" ]; then
226 newns=
227 for n in $ns; do
228 newns="$newns${newns:+,}$n"
229 done
230 ds=
231 for d in $search; do
232 ds="$ds${ds:+ }$d:$newns"
233 done
234 echo "DOMAINS=\"\$DOMAINS $ds\""
235 fi
236 echo "SEARCH=\"\$SEARCH $search\""
237 if ! $private; then
238 echo "NAMESERVERS=\"\$NAMESERVERS $ns\""
239 fi
240 ns=
241 search=
242 new=true
243 ;;
244 esac
245 done
246 }
247
248 uniqify()
249 {
250 result=
251 while [ -n "$1" ]; do
252 case " $result " in
253 *" $1 "*);;
254 *) result="$result $1";;
255 esac
256 shift
257 done
258 echo "${result# *}"
259 }
260
261 dirname()
262 {
263 OIFS="$IFS"
264 IFS=/
265 set -- $@
266 IFS="$OIFS"
267 if [ -n "$1" ]; then
268 printf %s .
269 else
270 shift
271 fi
272 while [ -n "$2" ]; do
273 printf "/%s" "$1"
274 shift
275 done
276 printf "\n"
277 }
278
279 config_mkdirs()
280 {
281 for f; do
282 [ -n "$f" ] || continue
283 d="$(dirname "$f")"
284 if [ ! -d "$d" ]; then
285 mkdir -p "$d" || return $?
286 fi
287 done
288 return 0
289 }
290
291 # With the advent of alternative init systems, it's possible to have
292 # more than one installed. So we need to try and guess what one we're
293 # using unless overriden by configure.
294 # Note that restarting a service is a last resort - the subscribers
295 # should make a reasonable attempt to reconfigre the service via some
296 # method, normally SIGHUP.
297 detect_init()
298 {
299 [ -n "$RESTARTCMD" ] && return 0
300
301 # Detect the running init system.
302 # As systemd and OpenRC can be installed on top of legacy init
303 # systems we try to detect them first.
304 status="@STATUSARG@"
305 : ${status:=status}
306 if [ -x /bin/systemctl ] && [ -S /run/systemd/private ]; then
307 RESTARTCMD='
308 if /bin/systemctl --quiet is-active $1.service
309 then
310 /bin/systemctl restart $1.service
311 fi'
312 elif [ -x /usr/bin/systemctl ] && [ -S /run/systemd/private ]; then
313 RESTARTCMD='
314 if /usr/bin/systemctl --quiet is-active $1.service
315 then
316 /usr/bin/systemctl restart $1.service
317 fi'
318 elif [ -x /sbin/rc-service ] &&
319 { [ -s /libexec/rc/init.d/softlevel ] ||
320 [ -s /run/openrc/softlevel ]; }
321 then
322 RESTARTCMD='/sbin/rc-service -i $1 -- -Ds restart'
323 elif [ -x /usr/sbin/invoke-rc.d ]; then
324 RCDIR=/etc/init.d
325 RESTARTCMD='
326 if /usr/sbin/invoke-rc.d --quiet $1 status >/dev/null 2>&1
327 then
328 /usr/sbin/invoke-rc.d $1 restart
329 fi'
330 elif [ -x /sbin/service ]; then
331 # Old RedHat
332 RCDIR=/etc/init.d
333 RESTARTCMD='
334 if /sbin/service $1; then
335 /sbin/service $1 restart
336 fi'
337 elif [ -x /usr/sbin/service ]; then
338 # Could be FreeBSD
339 RESTARTCMD="
340 if /usr/sbin/service \$1 $status >/dev/null 2>&1
341 then
342 /usr/sbin/service \$1 restart
343 fi"
344 elif [ -x /bin/sv ]; then
345 RESTARTCMD='/bin/sv status $1 >/dev/null 2>&1 &&
346 /bin/sv try-restart $1'
347 elif [ -x /usr/bin/sv ]; then
348 RESTARTCMD='/usr/bin/sv status $1 >/dev/null 2>&1 &&
349 /usr/bin/sv try-restart $1'
350 elif [ -e /etc/arch-release ] && [ -d /etc/rc.d ]; then
351 RCDIR=/etc/rc.d
352 RESTARTCMD='
353 if [ -e /var/run/daemons/$1 ]
354 then
355 /etc/rc.d/$1 restart
356 fi'
357 elif [ -e /etc/slackware-version ] && [ -d /etc/rc.d ]; then
358 RESTARTCMD='
359 if /etc/rc.d/rc.$1 status >/dev/null 2>&1
360 then
361 /etc/rc.d/rc.$1 restart
362 fi'
363 elif [ -e /etc/rc.d/rc.subr ] && [ -d /etc/rc.d ]; then
364 # OpenBSD
365 RESTARTCMD='
366 if /etc/rc.d/$1 check >/dev/null 2>&1
367 then
368 /etc/rc.d/$1 restart
369 fi'
370 else
371 for x in /etc/init.d/rc.d /etc/rc.d /etc/init.d; do
372 [ -d $x ] || continue
373 RESTARTCMD="
374 if $x/\$1 $status >/dev/null 2>&1
375 then
376 $x/\$1 restart
377 fi"
378 break
379 done
380 fi
381
382 if [ -z "$RESTARTCMD" ]; then
383 if [ "$_NOINIT_WARNED" != true ]; then
384 warn "could not detect a useable init system"
385 _NOINIT_WARNED=true
386 fi
387 return 1
388 fi
389 _NOINIT_WARNED=
390 return 0
391 }
392
393 echo_resolv()
394 {
395 OIFS="$IFS"
396
397 [ -n "$1" ] && [ -f "$IFACEDIR/$1" ] || return 1
398 echo "# resolv.conf from $1"
399 # Our variable maker works of the fact each resolv.conf per interface
400 # is separated by blank lines.
401 # So we remove them when echoing them.
402 while read -r line; do
403 IFS="$OIFS"
404 if [ -n "$line" ]; then
405 # We need to set IFS here to preserve any whitespace
406 IFS=''
407 printf "%s\n" "$line"
408 fi
409 done < "$IFACEDIR/$1"
410 IFS="$OIFS"
411 }
412
413 deprecated_interface()
414 {
415 [ -d "$DEPRECATEDDIR" ] || return 1
416
417 cd "$DEPRECATEDDIR"
418 for da; do
419 for daf in *; do
420 [ -f "$daf" ] || continue
421 case "$da" in
422 $daf) return 0;;
423 esac
424 done
425 done
426 return 1
427 }
428
429 list_resolv()
430 {
431 [ -d "$IFACEDIR" ] || return 0
432
433 cmd="$1"
434 shift
435 excl=false
436 list=
437 report=false
438 retval=0
439
440 case "$IF_EXCLUSIVE" in
441 [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1)
442 excl=true
443 if [ -d "$EXCLUSIVEDIR" ]; then
444 cd "$EXCLUSIVEDIR"
445 for i in *; do
446 if [ -f "$i" ]; then
447 list="${i#* }"
448 break
449 fi
450 done
451 fi
452 cd "$IFACEDIR"
453 for i in $inclusive_interfaces; do
454 if [ -f "$i" ] && [ "$list" = "$i" ]; then
455 list=
456 excl=false
457 break
458 fi
459 done
460 ;;
461 esac
462
463 # If we have an interface ordering list, then use that.
464 # It works by just using pathname expansion in the interface directory.
465 if [ -n "$1" ]; then
466 list="$*"
467 $force || report=true
468 elif ! $excl; then
469 cd "$IFACEDIR"
470
471 for i in $interface_order; do
472 [ -f "$i" ] && list="$list $i"
473 for ii in "$i":* "$i".*; do
474 [ -f "$ii" ] && list="$list $ii"
475 done
476 done
477
478 for i in $dynamic_order; do
479 if [ -e "$i" ] && ! [ -e "$METRICDIR/"*" $i" ]; then
480 list="$list $i"
481 fi
482 for ii in "$i":* "$i".*; do
483 if [ -f "$ii" ] && ! [ -e "$METRICDIR/"*" $ii" ]
484 then
485 list="$list $ii"
486 fi
487 done
488 done
489
490 # Interfaces have an implicit metric of 0 if not specified.
491 for i in *; do
492 if [ -f "$i" ] && ! [ -e "$METRICDIR/"*" $i" ]; then
493 list="$list $i"
494 fi
495 done
496
497 if [ -d "$METRICDIR" ]; then
498 cd "$METRICDIR"
499 for i in *; do
500 [ -f "$i" ] && list="$list ${i#* }"
501 done
502 fi
503
504 # Move deprecated interfaces to the back
505 active=
506 deprecated=
507 for i in $list; do
508 if deprecated_interface "$i"; then
509 deprecated="$deprecated $i"
510 else
511 active="$active $i"
512 fi
513 done
514 list="$active $deprecated"
515 fi
516
517 cd "$IFACEDIR"
518 retval=1
519 for i in $(uniqify $list); do
520 # Only list interfaces which we really have
521 if ! [ -f "$i" ]; then
522 if $report; then
523 echo "No resolv.conf for interface $i" >&2
524 retval=2
525 fi
526 continue
527 fi
528
529 if ! $ALLIFACES; then
530 if [ -n "$allow_interfaces" ]; then
531 x=false
532 for j in $allow_interfaces; do
533 if [ "$i" = "$j" ]; then
534 x=true
535 fi
536 done
537 $x || continue
538 fi
539 for j in $deny_interfaces; do
540 if [ "$i" = "$j" ]; then
541 continue 2
542 fi
543 done
544 fi
545
546 if [ "$cmd" = i ] || [ "$cmd" = "-i" ]; then
547 printf %s "$i "
548 else
549 echo_resolv "$i" && echo
550 fi
551 [ $? = 0 ] && [ "$retval" = 1 ] && retval=0
552 done
553 [ "$cmd" = i ] || [ "$cmd" = "-i" ] && echo
554 return $retval
555 }
556
557 list_remove()
558 {
559 [ -z "$2" ] && return 0
560 eval list=\"\$$1\"
561 shift
562 result=
563 retval=0
564
565 set -f
566 for e; do
567 found=false
568 for l in $list; do
569 case "$e" in
570 $l) found=true;;
571 esac
572 $found && break
573 done
574 if $found; then
575 retval=$(($retval + 1))
576 else
577 result="$result $e"
578 fi
579 done
580 set +f
581 echo "${result# *}"
582 return $retval
583 }
584
585 echo_prepend()
586 {
587 echo "# Generated by resolvconf"
588 if [ -n "$search_domains" ]; then
589 echo "search $search_domains"
590 fi
591 for n in $name_servers; do
592 echo "nameserver $n"
593 done
594 echo
595 }
596
597 echo_append()
598 {
599 echo "# Generated by resolvconf"
600 if [ -n "$search_domains_append" ]; then
601 echo "search $search_domains_append"
602 fi
603 for n in $name_servers_append; do
604 echo "nameserver $n"
605 done
606 echo
607 }
608
609 replace()
610 {
611 while read -r keyword value; do
612 for r in $replace; do
613 k="${r%%/*}"
614 r="${r#*/}"
615 f="${r%%/*}"
616 r="${r#*/}"
617 v="${r%%/*}"
618 case "$keyword" in
619 $k)
620 case "$value" in
621 $f) value="$v";;
622 esac
623 ;;
624 esac
625 done
626 val=
627 for sub in $value; do
628 for r in $replace_sub; do
629 k="${r%%/*}"
630 r="${r#*/}"
631 f="${r%%/*}"
632 r="${r#*/}"
633 v="${r%%/*}"
634 case "$keyword" in
635 $k)
636 case "$sub" in
637 $f) sub="$v";;
638 esac
639 ;;
640 esac
641 done
642 val="$val${val:+ }$sub"
643 done
644 printf "%s %s\n" "$keyword" "$val"
645 done
646 }
647
648 make_vars()
649 {
650 # Clear variables
651 DOMAIN=
652 DOMAINS=
653 SEARCH=
654 NAMESERVERS=
655 LOCALNAMESERVERS=
656
657 if [ -n "${name_servers}${search_domains}" ]; then
658 eval "$(echo_prepend | parse_resolv)"
659 fi
660 if [ -z "$VFLAG" ]; then
661 IF_EXCLUSIVE=1
662 list_resolv -i "$@" >/dev/null || IF_EXCLUSIVE=0
663 eval "$(list_resolv -l "$@" | replace | parse_resolv)"
664 fi
665 if [ -n "${name_servers_append}${search_domains_append}" ]; then
666 eval "$(echo_append | parse_resolv)"
667 fi
668
669 # Ensure that we only list each domain once
670 newdomains=
671 for d in $DOMAINS; do
672 dn="${d%%:*}"
673 list_remove domain_blacklist "$dn" >/dev/null || continue
674 case " $newdomains" in
675 *" ${dn}:"*) continue;;
676 esac
677 newns=
678 for nd in $DOMAINS; do
679 if [ "$dn" = "${nd%%:*}" ]; then
680 ns="${nd#*:}"
681 while [ -n "$ns" ]; do
682 case ",$newns," in
683 *,${ns%%,*},*) ;;
684 *) list_remove name_server_blacklist \
685 "${ns%%,*}" >/dev/null \
686 && newns="$newns${newns:+,}${ns%%,*}";;
687 esac
688 [ "$ns" = "${ns#*,}" ] && break
689 ns="${ns#*,}"
690 done
691 fi
692 done
693 if [ -n "$newns" ]; then
694 newdomains="$newdomains${newdomains:+ }$dn:$newns"
695 fi
696 done
697 DOMAIN="$(list_remove domain_blacklist $DOMAIN)"
698 SEARCH="$(uniqify $SEARCH)"
699 SEARCH="$(list_remove domain_blacklist $SEARCH)"
700 NAMESERVERS="$(uniqify $NAMESERVERS)"
701 NAMESERVERS="$(list_remove name_server_blacklist $NAMESERVERS)"
702 LOCALNAMESERVERS="$(uniqify $LOCALNAMESERVERS)"
703 LOCALNAMESERVERS="$(list_remove name_server_blacklist $LOCALNAMESERVERS)"
704 echo "DOMAIN='$DOMAIN'"
705 echo "SEARCH='$SEARCH'"
706 echo "NAMESERVERS='$NAMESERVERS'"
707 echo "LOCALNAMESERVERS='$LOCALNAMESERVERS'"
708 echo "DOMAINS='$newdomains'"
709 }
710
711 force=false
712 VFLAG=
713 while getopts a:C:c:Dd:fhIilm:pRruvVx OPT; do
714 case "$OPT" in
715 f) force=true;;
716 h) usage;;
717 m) IF_METRIC="$OPTARG";;
718 p) IF_PRIVATE=1;;
719 V)
720 VFLAG=1
721 if [ "$local_nameservers" = \
722 "127.* 0.0.0.0 255.255.255.255 ::1" ]
723 then
724 local_nameservers=
725 fi
726 ;;
727 x) IF_EXCLUSIVE=1;;
728 '?') exit 1;;
729 *) cmd="$OPT"; iface="$OPTARG";;
730 esac
731 done
732 shift $(($OPTIND - 1))
733 args="$iface${iface:+ }$*"
734
735 # -I inits the state dir
736 if [ "$cmd" = I ]; then
737 if [ -d "$VARDIR" ]; then
738 rm -rf "$VARDIR"/*
739 fi
740 exit $?
741 fi
742
743 # -D ensures that the listed config file base dirs exist
744 if [ "$cmd" = D ]; then
745 config_mkdirs "$@"
746 exit $?
747 fi
748
749 # -l lists our resolv files, optionally for a specific interface
750 if [ "$cmd" = l ] || [ "$cmd" = i ]; then
751 ALLIFACES=true
752 list_resolv "$cmd" "$args"
753 exit $?
754 fi
755 ALLIFACES=false
756
757 # Restart a service or echo the command to restart a service
758 if [ "$cmd" = r ] || [ "$cmd" = R ]; then
759 detect_init || exit 1
760 if [ "$cmd" = r ]; then
761 set -- $args
762 eval "$RESTARTCMD"
763 else
764 echo "$RESTARTCMD" |
765 sed -e '/^$/d' -e 's/^ //g'
766 fi
767 exit $?
768 fi
769
770 # Not normally needed, but subscribers should be able to run independently
771 if [ "$cmd" = v ] || [ -n "$VFLAG" ]; then
772 make_vars "$iface"
773 exit $?
774 fi
775
776 # Test that we have valid options
777 case "$cmd" in
778 a|d|C|c)
779 if [ -z "$iface" ]; then
780 error_exit "Interface not specified"
781 fi
782 ;;
783 u) ;;
784 *)
785 if [ -n "$cmd" ] && [ "$cmd" != h ]; then
786 error_exit "Unknown option $cmd"
787 fi
788 usage
789 ;;
790 esac
791
792 if [ "$cmd" = a ]; then
793 for x in '/' \\ ' ' '*'; do
794 case "$iface" in
795 *[$x]*) error_exit "$x not allowed in interface name";;
796 esac
797 done
798 for x in '.' '-' '~'; do
799 case "$iface" in
800 [$x]*) error_exit \
801 "$x not allowed at start of interface name";;
802 esac
803 done
804 [ "$cmd" = a ] && [ -t 0 ] && error_exit "No file given via stdin"
805 fi
806
807 if [ ! -d "$VARDIR" ]; then
808 if [ -L "$VARDIR" ]; then
809 dir="$(readlink "$VARDIR")"
810 # link maybe relative
811 cd "${VARDIR%/*}"
812 if ! mkdir -m 0755 -p "$dir"; then
813 error_exit "Failed to create needed" \
814 "directory $dir"
815 fi
816 else
817 if ! mkdir -m 0755 -p "$VARDIR"; then
818 error_exit "Failed to create needed" \
819 "directory $VARDIR"
820 fi
821 fi
822 fi
823
824 if [ ! -d "$IFACEDIR" ]; then
825 mkdir -m 0755 -p "$IFACEDIR" || \
826 error_exit "Failed to create needed directory $IFACEDIR"
827 if [ "$cmd" = d ]; then
828 # Provide the same error messages as below
829 if ! ${force}; then
830 cd "$IFACEDIR"
831 for i in $args; do
832 warn "No resolv.conf for interface $i"
833 done
834 fi
835 ${force}
836 exit $?
837 fi
838 fi
839
840 # An interface was added, changed, deleted or a general update was called.
841 # Due to exclusivity we need to ensure that this is an atomic operation.
842 # Our subscribers *may* need this as well if the init system is sub par.
843 # As such we spinlock at this point as best we can.
844 # We don't use flock(1) because it's not widely available and normally resides
845 # in /usr which we do our very best to operate without.
846 [ -w "$VARDIR" ] || error_exit "Cannot write to $LOCKDIR"
847 : ${lock_timeout:=10}
848 : ${clear_nopids:=5}
849 have_pid=false
850 had_pid=false
851 while true; do
852 if mkdir "$LOCKDIR" 2>/dev/null; then
853 trap 'rm -rf "$LOCKDIR";' EXIT
854 trap 'rm -rf "$LOCKDIR"; exit 1' INT QUIT ABRT SEGV ALRM TERM
855 echo $$ >"$LOCKDIR/pid"
856 break
857 fi
858 pid=$(cat "$LOCKDIR/pid" 2>/dev/null)
859 if [ "$pid" -gt 0 ] 2>/dev/null; then
860 have_pid=true
861 had_pid=true
862 else
863 have_pid=false
864 clear_nopids=$(($clear_nopids - 1))
865 if [ "$clear_nopids" -le 0 ]; then
866 warn "not seen a pid, clearing lock directory"
867 rm -rf "$LOCKDIR"
868 else
869 lock_timeout=$(($lock_timeout - 1))
870 sleep 1
871 fi
872 continue
873 fi
874 if $have_pid && ! kill -0 "$pid"; then
875 warn "clearing stale lock pid $pid"
876 rm -rf "$LOCKDIR"
877 continue
878 fi
879 lock_timeout=$(($lock_timeout - 1))
880 if [ "$lock_timeout" -le 0 ]; then
881 if $have_pid; then
882 error_exit "timed out waiting for lock from pid $pid"
883 else
884 if $had_pid; then
885 error_exit "timed out waiting for lock" \
886 "from some pids"
887 else
888 error_exit "timed out waiting for lock"
889 fi
890 fi
891 fi
892 sleep 1
893 done
894 unset have_pid had_pid clear_nopids
895
896 case "$cmd" in
897 a)
898 # Read resolv.conf from stdin
899 resolv="$(cat)"
900 changed=false
901 changedfile=false
902 # If what we are given matches what we have, then do nothing
903 if [ -e "$IFACEDIR/$iface" ]; then
904 if [ "$(echo "$resolv")" != \
905 "$(cat "$IFACEDIR/$iface")" ]
906 then
907 changed=true
908 changedfile=true
909 fi
910 else
911 changed=true
912 changedfile=true
913 fi
914
915 # Set metric and private before creating the interface resolv.conf file
916 # to ensure that it will have the correct flags
917 [ ! -d "$METRICDIR" ] && mkdir "$METRICDIR"
918 oldmetric="$METRICDIR/"*" $iface"
919 newmetric=
920 if [ -n "$IF_METRIC" ]; then
921 # Pad metric to 6 characters, so 5 is less than 10
922 while [ ${#IF_METRIC} -le 6 ]; do
923 IF_METRIC="0$IF_METRIC"
924 done
925 newmetric="$METRICDIR/$IF_METRIC $iface"
926 fi
927 rm -f "$METRICDIR/"*" $iface"
928 [ "$oldmetric" != "$newmetric" ] &&
929 [ "$oldmetric" != "$METRICDIR/* $iface" ] &&
930 changed=true
931 [ -n "$newmetric" ] && echo " " >"$newmetric"
932
933 case "$IF_PRIVATE" in
934 [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1)
935 if [ ! -d "$PRIVATEDIR" ]; then
936 [ -e "$PRIVATEDIR" ] && rm "$PRIVATEDIR"
937 mkdir "$PRIVATEDIR"
938 fi
939 [ -e "$PRIVATEDIR/$iface" ] || changed=true
940 [ -d "$PRIVATEDIR" ] && echo " " >"$PRIVATEDIR/$iface"
941 ;;
942 *)
943 if [ -e "$PRIVATEDIR/$iface" ]; then
944 rm -f "$PRIVATEDIR/$iface"
945 changed=true
946 fi
947 ;;
948 esac
949
950 oldexcl=
951 for x in "$EXCLUSIVEDIR/"*" $iface"; do
952 if [ -f "$x" ]; then
953 oldexcl="$x"
954 break
955 fi
956 done
957 case "$IF_EXCLUSIVE" in
958 [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1)
959 if [ ! -d "$EXCLUSIVEDIR" ]; then
960 [ -e "$EXCLUSIVEDIR" ] && rm "$EXCLUSIVEDIR"
961 mkdir "$EXCLUSIVEDIR"
962 fi
963 cd "$EXCLUSIVEDIR"
964 for x in *; do
965 [ -f "$x" ] && break
966 done
967 if [ "${x#* }" != "$iface" ]; then
968 if [ "$x" = "${x% *}" ]; then
969 x=10000000
970 else
971 x="${x% *}"
972 fi
973 if [ "$x" = "0000000" ]; then
974 warn "exclusive underflow"
975 else
976 x=$(($x - 1))
977 fi
978 if [ -d "$EXCLUSIVEDIR" ]; then
979 echo " " >"$EXCLUSIVEDIR/$x $iface"
980 fi
981 changed=true
982 fi
983 ;;
984 *)
985 if [ -f "$oldexcl" ]; then
986 rm -f "$oldexcl"
987 changed=true
988 fi
989 ;;
990 esac
991
992 if $changedfile; then
993 printf "%s\n" "$resolv" >"$IFACEDIR/$iface" || exit $?
994 elif ! $changed; then
995 exit 0
996 fi
997 unset changed changedfile oldmetric newmetric x oldexcl
998 ;;
999
1000 d)
1001 # Delete any existing information about the interface
1002 cd "$IFACEDIR"
1003 changed=false
1004 for i in $args; do
1005 if [ -e "$i" ]; then
1006 changed=true
1007 elif ! ${force}; then
1008 warn "No resolv.conf for interface $i"
1009 fi
1010 rm -f "$i" "$METRICDIR/"*" $i" \
1011 "$PRIVATEDIR/$i" \
1012 "$EXCLUSIVEDIR/"*" $i" || exit $?
1013 done
1014
1015 if ! $changed; then
1016 # Set the return code based on the forced flag
1017 $force
1018 exit $?
1019 fi
1020 unset changed i
1021 ;;
1022
1023 C)
1024 # Mark interface as deprecated
1025 [ ! -d "$DEPRECATEDDIR" ] && mkdir "$DEPRECATEDDIR"
1026 cd "$DEPRECATEDDIR"
1027 changed=false
1028 for i in $args; do
1029 if [ ! -e "$i" ]; then
1030 changed=true
1031 echo " " >"$i" || exit $?
1032 fi
1033 done
1034 $changed || exit 0
1035 unset changed i
1036 ;;
1037
1038 c)
1039 # Mark interface as active
1040 if [ -d "$DEPRECATEDDIR" ]; then
1041 cd "$DEPRECATEDDIR"
1042 changed=false
1043 for i in $args; do
1044 if [ -e "$i" ]; then
1045 changed=true
1046 rm "$i" || exit $?
1047 fi
1048 done
1049 $changed || exit 0
1050 unset changed i
1051 fi
1052 ;;
1053 esac
1054
1055 case "${resolvconf:-YES}" in
1056 [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) ;;
1057 *) exit 0;;
1058 esac
1059
1060 # Try and detect a suitable init system for our scripts
1061 detect_init
1062 export RESTARTCMD RCDIR _NOINIT_WARNED
1063
1064 eval "$(make_vars)"
1065 export RESOLVCONF DOMAINS SEARCH NAMESERVERS LOCALNAMESERVERS
1066 : ${list_resolv:=list_resolv -l}
1067 retval=0
1068
1069 # Run scripts in the same directory resolvconf is run from
1070 # in case any scripts accidentally dump files in the wrong place.
1071 cd "$_PWD"
1072 for script in "$LIBEXECDIR"/*; do
1073 if [ -f "$script" ]; then
1074 eval script_enabled="\$${script##*/}"
1075 case "${script_enabled:-YES}" in
1076 [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) ;;
1077 *) continue;;
1078 esac
1079 if [ -x "$script" ]; then
1080 "$script" "$cmd" "$iface"
1081 else
1082 (set -- "$cmd" "$iface"; . "$script")
1083 fi
1084 retval=$(($retval + $?))
1085 fi
1086 done
1087 exit $retval
1088