2 # Copyright (c) 2007-2023 Roy Marples
5 # Redistribution and use in source and binary forms, with or without
6 # modification, are permitted provided that the following conditions
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.
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.
28 OPENRESOLV_VERSION="3.13.2"
29 SYSCONFDIR=@SYSCONFDIR@
30 LIBEXECDIR=@LIBEXECDIR@
33 RESTARTCMD=@RESTARTCMD@
35 if [ "$1" = "--version" ]; then
36 echo "openresolv $OPENRESOLV_VERSION"
37 echo "Copyright (c) 2007-2020 Roy Marples"
41 # Disregard dhcpcd setting
42 unset interface_order state_dir
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"
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"
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)"
63 IFACEDIR="$VARDIR/interfaces"
64 METRICDIR="$VARDIR/metrics"
65 PRIVATEDIR="$VARDIR/private"
66 EXCLUSIVEDIR="$VARDIR/exclusive"
67 DEPRECATEDDIR="$VARDIR/deprecated"
68 LOCKDIR="$VARDIR/lock"
85 Usage: ${RESOLVCONF##*/} [options] command [argument]
87 Inform the system about any DNS updates.
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
99 -l [\$PATTERN] Show DNS information, optionally from interfaces
100 that match the specified pattern
102 -u Run updates from our current DNS information
103 --version Echo the ${RESOLVCONF##*/} version
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
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
119 -V [\$PATTERN] Same as -v, but only uses configuration in
120 $SYSCONFDIR/resolvconf.conf
122 [ -z "$1" ] && exit 0
127 # Strip any trailing dot from each name as a FQDN does not belong
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 #.
140 printf "%s%s" "$space${word%.}"
151 # Public interfaces override private ones.
152 for p in $public_interfaces; do
154 "$p"|"$p":*) return 1;;
158 if [ -e "$PRIVATEDIR/$iface" ]; then
162 for p in $private_interfaces; do
164 "$p"|"$p":*) return 0;;
168 # Not a private interface
172 # Parse resolv.conf's and make variables
173 # for domain name servers, search name servers and global nameservers
183 while read -r line; do
184 stripped_line="$(resolv_strip ${line#* })"
186 "# resolv.conf from "*)
188 iface="${line#\# resolv.conf from *}"
190 if private_iface "$iface"; then
199 for l in $local_nameservers; do
200 case "$stripped_line" in
208 echo "LOCALNAMESERVERS=\"\$LOCALNAMESERVERS $stripped_line\""
210 ns="$ns$stripped_line "
214 search="$stripped_line"
215 if [ -z "$domain" ]; then
217 echo "DOMAIN=\"$domain\""
221 search="$stripped_line"
224 [ -n "$line" ] && continue
225 if [ -n "$ns" ] && [ -n "$search" ]; then
228 newns="$newns${newns:+,}$n"
232 ds="$ds${ds:+ }$d:$newns"
234 echo "DOMAINS=\"\$DOMAINS $ds\""
236 echo "SEARCH=\"\$SEARCH $search\""
238 echo "NAMESERVERS=\"\$NAMESERVERS $ns\""
251 while [ -n "$1" ]; do
254 *) result="$result $1";;
272 while [ -n "$2" ]; do
282 [ -n "$f" ] || continue
284 if [ ! -d "$d" ]; then
285 mkdir -p "$d" || return $?
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 overridden by configure.
294 # Note that restarting a service is a last resort - the subscribers
295 # should make a reasonable attempt to reconfigure the service via some
296 # method, normally SIGHUP.
299 [ -n "$RESTARTCMD" ] && return 0
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.
306 if [ -x /bin/systemctl ] && [ -S /run/systemd/private ]; then
308 if /bin/systemctl --quiet is-active $1.service
310 /bin/systemctl restart $1.service
312 elif [ -x /usr/bin/systemctl ] && [ -S /run/systemd/private ]; then
314 if /usr/bin/systemctl --quiet is-active $1.service
316 /usr/bin/systemctl restart $1.service
318 elif [ -x /sbin/rc-service ] &&
319 { [ -s /libexec/rc/init.d/softlevel ] ||
320 [ -s /run/openrc/softlevel ]; }
322 RESTARTCMD='/sbin/rc-service -i $1 -- -Ds restart'
323 elif [ -x /usr/sbin/invoke-rc.d ]; then
326 if /usr/sbin/invoke-rc.d --quiet $1 status >/dev/null 2>&1
328 /usr/sbin/invoke-rc.d $1 restart
330 elif [ -x /usr/bin/s6-rc ] && [ -x /usr/bin/s6-svc ]; then
332 if s6-rc -a list | grep -qFx $1-srv
334 s6-svc -r /run/service/$1-srv
336 elif [ -x /sbin/service ]; then
340 if /sbin/service $1; then
341 /sbin/service $1 restart
343 elif [ -x /usr/sbin/service ]; then
346 if /usr/sbin/service \$1 $status >/dev/null 2>&1
348 /usr/sbin/service \$1 restart
350 elif [ -x /bin/sv ]; then
351 RESTARTCMD='/bin/sv status $1 >/dev/null 2>&1 &&
352 /bin/sv try-restart $1'
353 elif [ -x /usr/bin/sv ]; then
354 RESTARTCMD='/usr/bin/sv status $1 >/dev/null 2>&1 &&
355 /usr/bin/sv try-restart $1'
356 elif [ -e /etc/arch-release ] && [ -d /etc/rc.d ]; then
359 if [ -e /var/run/daemons/$1 ]
363 elif [ -e /etc/slackware-version ] && [ -d /etc/rc.d ]; then
365 if /etc/rc.d/rc.$1 status >/dev/null 2>&1
367 /etc/rc.d/rc.$1 restart
369 elif [ -e /etc/rc.d/rc.subr ] && [ -d /etc/rc.d ]; then
372 if /etc/rc.d/$1 check >/dev/null 2>&1
376 elif [ -d /etc/dinit.d ] && command -v dinitctl >/dev/null 2>&1; then
377 RESTARTCMD='dinitctl --quiet restart --ignore-unstarted $1'
379 for x in /etc/init.d/rc.d /etc/rc.d /etc/init.d; do
380 [ -d $x ] || continue
382 if $x/\$1 $status >/dev/null 2>&1
390 if [ -z "$RESTARTCMD" ]; then
391 if [ "$_NOINIT_WARNED" != true ]; then
392 warn "could not detect a useable init system"
405 [ -n "$1" ] && [ -f "$IFACEDIR/$1" ] || return 1
406 echo "# resolv.conf from $1"
407 # Our variable maker works of the fact each resolv.conf per interface
408 # is separated by blank lines.
409 # So we remove them when echoing them.
410 while read -r line; do
412 if [ -n "$line" ]; then
413 # We need to set IFS here to preserve any whitespace
415 printf "%s\n" "$line"
417 done < "$IFACEDIR/$1"
421 deprecated_interface()
423 [ -d "$DEPRECATEDDIR" ] || return 1
428 [ -f "$daf" ] || continue
439 [ -d "$IFACEDIR" ] || return 0
443 pattern_specified="$1"
450 case "$IF_EXCLUSIVE" in
451 [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1)
453 if [ -d "$EXCLUSIVEDIR" ]; then
463 for i in $inclusive_interfaces; do
464 if [ -f "$i" ] && [ "$list" = "$i" ]; then
473 # If we have an interface ordering list, then use that.
474 # It works by just using pathname expansion in the interface directory.
475 if [ -n "$pattern_specified" ]; then
477 $force || report=true
481 for i in $interface_order; do
482 [ -f "$i" ] && list="$list $i"
483 for ii in "$i":* "$i".*; do
484 [ -f "$ii" ] && list="$list $ii"
488 for i in $dynamic_order; do
489 if [ -e "$i" ] && ! [ -e "$METRICDIR/"*" $i" ]; then
492 for ii in "$i":* "$i".*; do
493 if [ -f "$ii" ] && ! [ -e "$METRICDIR/"*" $ii" ]
500 # Interfaces have an implicit metric of 0 if not specified.
502 if [ -f "$i" ] && ! [ -e "$METRICDIR/"*" $i" ]; then
507 if [ -d "$METRICDIR" ]; then
510 [ -f "$i" ] && list="$list ${i#* }"
514 # Move deprecated interfaces to the back
518 if deprecated_interface "$i"; then
519 deprecated="$deprecated $i"
524 list="$active $deprecated"
528 if $excl || [ -n "$pattern_specified" ]; then
533 for i in $(uniqify $list); do
534 # Only list interfaces which we really have
535 if ! [ -f "$i" ]; then
537 echo "No resolv.conf for interface $i" >&2
543 if ! $ALLIFACES; then
544 if [ -n "$allow_interfaces" ]; then
546 for j in $allow_interfaces; do
547 if [ "$i" = "$j" ]; then
553 for j in $deny_interfaces; do
554 if [ "$i" = "$j" ]; then
560 if [ "$cmd" = i ] || [ "$cmd" = "-i" ]; then
563 echo_resolv "$i" && echo
565 [ $? = 0 ] && [ "$retval" = 1 ] && retval=0
567 [ "$cmd" = i ] || [ "$cmd" = "-i" ] && echo
573 [ -z "$2" ] && return 0
589 retval=$(($retval + 1))
601 echo "# Generated by resolvconf"
602 if [ -n "$search_domains" ]; then
603 echo "search $search_domains"
605 for n in $name_servers; do
613 echo "# Generated by resolvconf"
614 if [ -n "$search_domains_append" ]; then
615 echo "search $search_domains_append"
617 for n in $name_servers_append; do
625 while read -r keyword value; do
626 for r in $replace; do
641 for sub in $value; do
642 for r in $replace_sub; do
656 val="$val${val:+ }$sub"
658 printf "%s %s\n" "$keyword" "$val"
671 if [ -n "${name_servers}${search_domains}" ]; then
672 eval "$(echo_prepend | parse_resolv)"
674 if [ -z "$VFLAG" ]; then
676 list_resolv -i "$@" >/dev/null || IF_EXCLUSIVE=0
677 eval "$(list_resolv -l "$@" | replace | parse_resolv)"
679 if [ -n "${name_servers_append}${search_domains_append}" ]; then
680 eval "$(echo_append | parse_resolv)"
683 # Ensure that we only list each domain once
685 for d in $DOMAINS; do
687 list_remove domain_blacklist "$dn" >/dev/null || continue
688 case " $newdomains" in
689 *" ${dn}:"*) continue;;
692 for nd in $DOMAINS; do
693 if [ "$dn" = "${nd%%:*}" ]; then
695 while [ -n "$ns" ]; do
698 *) list_remove name_server_blacklist \
699 "${ns%%,*}" >/dev/null \
700 && newns="$newns${newns:+,}${ns%%,*}";;
702 [ "$ns" = "${ns#*,}" ] && break
707 if [ -n "$newns" ]; then
708 newdomains="$newdomains${newdomains:+ }$dn:$newns"
711 DOMAIN="$(list_remove domain_blacklist $DOMAIN)"
712 SEARCH="$(uniqify $SEARCH)"
713 SEARCH="$(list_remove domain_blacklist $SEARCH)"
714 NAMESERVERS="$(uniqify $NAMESERVERS)"
715 NAMESERVERS="$(list_remove name_server_blacklist $NAMESERVERS)"
716 LOCALNAMESERVERS="$(uniqify $LOCALNAMESERVERS)"
717 LOCALNAMESERVERS="$(list_remove name_server_blacklist $LOCALNAMESERVERS)"
718 echo "DOMAIN='$DOMAIN'"
719 echo "SEARCH='$SEARCH'"
720 echo "NAMESERVERS='$NAMESERVERS'"
721 echo "LOCALNAMESERVERS='$LOCALNAMESERVERS'"
722 echo "DOMAINS='$newdomains'"
727 while getopts a:C:c:Dd:fhIilm:pRruvVx OPT; do
731 m) IF_METRIC="$OPTARG";;
735 if [ "$local_nameservers" = \
736 "127.* 0.0.0.0 255.255.255.255 ::1" ]
743 *) cmd="$OPT"; iface="$OPTARG";;
746 shift $(($OPTIND - 1))
747 args="$iface${iface:+ }$*"
749 # -I inits the state dir
750 if [ "$cmd" = I ]; then
751 if [ -d "$VARDIR" ]; then
757 # -D ensures that the listed config file base dirs exist
758 if [ "$cmd" = D ]; then
763 # -l lists our resolv files, optionally for a specific interface
764 if [ "$cmd" = l ] || [ "$cmd" = i ]; then
766 list_resolv "$cmd" "$args"
771 # Restart a service or echo the command to restart a service
772 if [ "$cmd" = r ] || [ "$cmd" = R ]; then
773 detect_init || exit 1
774 if [ "$cmd" = r ]; then
779 sed -e '/^$/d' -e 's/^ //g'
784 # Not normally needed, but subscribers should be able to run independently
785 if [ "$cmd" = v ] || [ -n "$VFLAG" ]; then
790 # Test that we have valid options
793 if [ -z "$iface" ]; then
794 error_exit "Interface not specified"
799 if [ -n "$cmd" ] && [ "$cmd" != h ]; then
800 error_exit "Unknown option $cmd"
806 if [ "$cmd" = a ]; then
807 for x in '/' \\ ' ' '*'; do
809 *[$x]*) error_exit "$x not allowed in interface name";;
812 for x in '.' '-' '~'; do
815 "$x not allowed at start of interface name";;
818 [ "$cmd" = a ] && [ -t 0 ] && error_exit "No file given via stdin"
821 if [ ! -d "$VARDIR" ]; then
822 if [ -L "$VARDIR" ]; then
823 dir="$(readlink "$VARDIR")"
824 # link maybe relative
826 if ! mkdir -m 0755 -p "$dir"; then
827 error_exit "Failed to create needed" \
831 if ! mkdir -m 0755 -p "$VARDIR"; then
832 error_exit "Failed to create needed" \
838 if [ ! -d "$IFACEDIR" ]; then
839 mkdir -m 0755 -p "$IFACEDIR" || \
840 error_exit "Failed to create needed directory $IFACEDIR"
841 if [ "$cmd" = d ]; then
842 # Provide the same error messages as below
846 warn "No resolv.conf for interface $i"
854 # An interface was added, changed, deleted or a general update was called.
855 # Due to exclusivity we need to ensure that this is an atomic operation.
856 # Our subscribers *may* need this as well if the init system is sub par.
857 # As such we spinlock at this point as best we can.
858 # We don't use flock(1) because it's not widely available and normally resides
859 # in /usr which we do our very best to operate without.
860 [ -w "$VARDIR" ] || error_exit "Cannot write to $LOCKDIR"
861 : ${lock_timeout:=10}
866 if mkdir "$LOCKDIR" 2>/dev/null; then
867 trap 'rm -rf "$LOCKDIR";' EXIT
868 trap 'rm -rf "$LOCKDIR"; exit 1' INT QUIT ABRT SEGV ALRM TERM
869 echo $$ >"$LOCKDIR/pid"
872 pid=$(cat "$LOCKDIR/pid" 2>/dev/null)
873 if [ "$pid" -gt 0 ] 2>/dev/null; then
878 clear_nopids=$(($clear_nopids - 1))
879 if [ "$clear_nopids" -le 0 ]; then
880 warn "not seen a pid, clearing lock directory"
883 lock_timeout=$(($lock_timeout - 1))
888 if $have_pid && ! kill -0 "$pid"; then
889 warn "clearing stale lock pid $pid"
893 lock_timeout=$(($lock_timeout - 1))
894 if [ "$lock_timeout" -le 0 ]; then
896 error_exit "timed out waiting for lock from pid $pid"
899 error_exit "timed out waiting for lock" \
902 error_exit "timed out waiting for lock"
908 unset have_pid had_pid clear_nopids
912 # Read resolv.conf from stdin
916 # If what we are given matches what we have, then do nothing
917 if [ -e "$IFACEDIR/$iface" ]; then
918 if [ "$(echo "$resolv")" != \
919 "$(cat "$IFACEDIR/$iface")" ]
929 # Set metric and private before creating the interface resolv.conf file
930 # to ensure that it will have the correct flags
931 [ ! -d "$METRICDIR" ] && mkdir "$METRICDIR"
932 oldmetric="$METRICDIR/"*" $iface"
934 if [ -n "$IF_METRIC" ]; then
935 # Pad metric to 6 characters, so 5 is less than 10
936 while [ ${#IF_METRIC} -le 6 ]; do
937 IF_METRIC="0$IF_METRIC"
939 newmetric="$METRICDIR/$IF_METRIC $iface"
941 rm -f "$METRICDIR/"*" $iface"
942 [ "$oldmetric" != "$newmetric" ] &&
943 [ "$oldmetric" != "$METRICDIR/* $iface" ] &&
945 [ -n "$newmetric" ] && echo " " >"$newmetric"
947 case "$IF_PRIVATE" in
948 [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1)
949 if [ ! -d "$PRIVATEDIR" ]; then
950 [ -e "$PRIVATEDIR" ] && rm "$PRIVATEDIR"
953 [ -e "$PRIVATEDIR/$iface" ] || changed=true
954 [ -d "$PRIVATEDIR" ] && echo " " >"$PRIVATEDIR/$iface"
957 if [ -e "$PRIVATEDIR/$iface" ]; then
958 rm -f "$PRIVATEDIR/$iface"
965 for x in "$EXCLUSIVEDIR/"*" $iface"; do
971 case "$IF_EXCLUSIVE" in
972 [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1)
973 if [ ! -d "$EXCLUSIVEDIR" ]; then
974 [ -e "$EXCLUSIVEDIR" ] && rm "$EXCLUSIVEDIR"
975 mkdir "$EXCLUSIVEDIR"
981 if [ "${x#* }" != "$iface" ]; then
982 if [ "$x" = "${x% *}" ]; then
987 if [ "$x" = "0000000" ]; then
988 warn "exclusive underflow"
992 if [ -d "$EXCLUSIVEDIR" ]; then
993 echo " " >"$EXCLUSIVEDIR/$x $iface"
999 if [ -f "$oldexcl" ]; then
1006 if $changedfile; then
1007 printf "%s\n" "$resolv" >"$IFACEDIR/$iface" || exit $?
1008 elif ! $changed; then
1011 unset changed changedfile oldmetric newmetric x oldexcl
1015 # Delete any existing information about the interface
1019 if [ -e "$i" ]; then
1021 elif ! ${force}; then
1022 warn "No resolv.conf for interface $i"
1024 rm -f "$i" "$METRICDIR/"*" $i" \
1026 "$EXCLUSIVEDIR/"*" $i" || exit $?
1030 # Set the return code based on the forced flag
1038 # Mark interface as deprecated
1039 [ ! -d "$DEPRECATEDDIR" ] && mkdir "$DEPRECATEDDIR"
1043 if [ ! -e "$i" ]; then
1045 echo " " >"$i" || exit $?
1053 # Mark interface as active
1054 if [ -d "$DEPRECATEDDIR" ]; then
1058 if [ -e "$i" ]; then
1069 case "${resolvconf:-YES}" in
1070 [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) ;;
1074 # Try and detect a suitable init system for our scripts
1076 export RESTARTCMD RCDIR _NOINIT_WARNED
1079 export RESOLVCONF DOMAINS SEARCH NAMESERVERS LOCALNAMESERVERS
1080 : ${list_resolv:=list_resolv -l}
1083 # Run scripts in the same directory resolvconf is run from
1084 # in case any scripts accidentally dump files in the wrong place.
1086 for script in "$LIBEXECDIR"/*; do
1087 if [ -f "$script" ]; then
1088 eval script_enabled="\$${script##*/}"
1089 case "${script_enabled:-YES}" in
1090 [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) ;;
1093 if [ -x "$script" ]; then
1094 "$script" "$cmd" "$iface"
1096 (set -- "$cmd" "$iface"; . "$script")
1098 retval=$(($retval + $?))