2 # Copyright (c) 2007-2015 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 SYSCONFDIR=@SYSCONFDIR@
29 LIBEXECDIR=@LIBEXECDIR@
32 # Disregard dhcpcd setting
33 unset interface_order state_dir
35 # If you change this, change the test in VFLAG and libc.in as well
36 local_nameservers="127.* 0.0.0.0 255.255.255.255 ::1"
38 dynamic_order="tap[0-9]* tun[0-9]* vpn vpn[0-9]* ppp[0-9]* ippp[0-9]*"
39 interface_order="lo lo[0-9]*"
40 name_server_blacklist="0.0.0.0"
42 # Support original resolvconf configuration layout
43 # as well as the openresolv config file
44 if [ -f "$SYSCONFDIR"/resolvconf.conf ]; then
45 . "$SYSCONFDIR"/resolvconf.conf
46 [ -n "$state_dir" ] && VARDIR="$state_dir"
47 elif [ -d "$SYSCONFDIR/resolvconf" ]; then
48 SYSCONFDIR="$SYSCONFDIR/resolvconf"
49 if [ -f "$SYSCONFDIR"/interface-order ]; then
50 interface_order="$(cat "$SYSCONFDIR"/interface-order)"
53 IFACEDIR="$VARDIR/interfaces"
54 METRICDIR="$VARDIR/metrics"
55 PRIVATEDIR="$VARDIR/private"
56 EXCLUSIVEDIR="$VARDIR/exclusive"
57 LOCKDIR="$VARDIR/lock"
73 Usage: ${RESOLVCONF##*/} [options]
75 Inform the system about any DNS updates.
78 -a \$INTERFACE Add DNS information to the specified interface
79 (DNS supplied via stdin in resolv.conf format)
80 -m metric Give the added DNS information a metric
81 -p Mark the interface as private
82 -x Mark the interface as exclusive
83 -d \$INTERFACE Delete DNS information from the specified interface
84 -f Ignore non existant interfaces
86 -u Run updates from our current DNS information
87 -l [\$PATTERN] Show DNS information, optionally from interfaces
88 that match the specified pattern
89 -i [\$PATTERN] Show interfaces that have supplied DNS information
90 optionally from interfaces that match the specified
92 -v [\$PATTERN] echo NEWDOMAIN, NEWSEARCH and NEWNS variables to
94 -h Show this help cruft
103 local line= OIFS="$IFS"
105 [ -n "$1" -a -f "$IFACEDIR/$1" ] || return 1
106 echo "# resolv.conf from $1"
107 # Our variable maker works of the fact each resolv.conf per interface
108 # is separated by blank lines.
109 # So we remove them when echoing them.
110 while read -r line; do
112 if [ -n "$line" ]; then
113 # We need to set IFS here to preserve any whitespace
115 printf "%s\n" "$line"
117 done < "$IFACEDIR/$1"
122 # Parse resolv.conf's and make variables
123 # for domain name servers, search name servers and global nameservers
126 local line= ns= ds= search= d= n= newns=
127 local new=true iface= private=false p= domain= l= islocal=
131 while read -r line; do
133 "# resolv.conf from "*)
135 iface="${line#\# resolv.conf from *}"
137 if [ -e "$PRIVATEDIR/$iface" ]; then
143 for p in $private_interfaces; do
145 "$p"|"$p":*) private=true; break;;
153 for l in $local_nameservers; do
157 echo "LOCALNAMESERVERS=\"\$LOCALNAMESERVERS ${line#* }\""
162 $islocal || ns="$ns${line#* } "
165 if [ -z "$domain" ]; then
167 echo "DOMAIN=\"$domain\""
175 [ -n "$line" ] && continue
176 if [ -n "$ns" -a -n "$search" ]; then
179 newns="$newns${newns:+,}$n"
183 ds="$ds${ds:+ }$d:$newns"
185 echo "DOMAINS=\"\$DOMAINS $ds\""
187 echo "SEARCH=\"\$SEARCH $search\""
189 echo "NAMESERVERS=\"\$NAMESERVERS $ns\""
202 while [ -n "$1" ]; do
205 *) result="$result $1";;
214 local dir= OIFS="$IFS"
223 while [ -n "$2" ]; do
234 [ -n "$f" ] || continue
236 if [ ! -d "$d" ]; then
237 if type install >/dev/null 2>&1; then
238 install -d "$d" || e=$?
249 [ -d "$IFACEDIR" ] || return 0
251 local report=false list= retval=0 cmd="$1" excl=
254 case "$IF_EXCLUSIVE" in
255 [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1)
256 if [ -d "$EXCLUSIVEDIR" ]; then
272 # If we have an interface ordering list, then use that.
273 # It works by just using pathname expansion in the interface directory.
276 $force || report=true
279 for i in $interface_order; do
280 [ -f "$i" ] && list="$list $i"
281 for ii in "$i":* "$i".*; do
282 [ -f "$ii" ] && list="$list $ii"
285 for i in $dynamic_order; do
286 if [ -e "$i" -a ! -e "$METRICDIR/"*" $i" ]; then
289 for ii in "$i":* "$i".*; do
290 if [ -f "$ii" -a ! -e "$METRICDIR/"*" $ii" ]; then
295 if [ -d "$METRICDIR" ]; then
298 [ -f "$i" ] && list="$list ${i#* }"
306 for i in $(uniqify $list); do
307 # Only list interfaces which we really have
308 if ! [ -f "$i" ]; then
310 echo "No resolv.conf for interface $i" >&2
316 if [ "$cmd" = i -o "$cmd" = "-i" ]; then
321 [ $? = 0 -a "$retval" = 1 ] && retval=0
323 [ "$cmd" = i -o "$cmd" = "-i" ] && echo
328 local list= e= l= result= found= retval=0
330 [ -z "$2" ] && return 0
344 retval=$(($retval + 1))
356 echo "# Generated by resolvconf"
357 if [ -n "$search_domains" ]; then
358 echo "search $search_domains"
360 for n in $name_servers; do
368 echo "# Generated by resolvconf"
369 if [ -n "$search_domains_append" ]; then
370 echo "search $search_domains_append"
372 for n in $name_servers_append; do
380 local r= k= f= v= val= sub=
382 while read -r keyword value; do
383 for r in $replace; do
398 for sub in $value; do
399 for r in $replace_sub; do
413 val="$val${val:+ }$sub"
415 printf "%s %s\n" "$keyword" "$val"
421 local newdomains= d= dn= newns= ns=
430 if [ -n "$name_servers" -o -n "$search_domains" ]; then
431 eval "$(echo_prepend | parse_resolv)"
433 if [ -z "$VFLAG" ]; then
435 list_resolv -i "$@" >/dev/null || IF_EXCLUSIVE=0
436 eval "$(list_resolv -l "$@" | replace | parse_resolv)"
438 if [ -n "$name_servers_append" -o -n "$search_domains_append" ]; then
439 eval "$(echo_append | parse_resolv)"
442 # Ensure that we only list each domain once
443 for d in $DOMAINS; do
445 list_remove domain_blacklist "$dn" >/dev/null || continue
446 case " $newdomains" in
447 *" ${dn}:"*) continue;;
450 for nd in $DOMAINS; do
451 if [ "$dn" = "${nd%%:*}" ]; then
453 while [ -n "$ns" ]; do
456 *) list_remove name_server_blacklist \
457 "${ns%%,*}" >/dev/null \
458 && newns="$newns${newns:+,}${ns%%,*}";;
460 [ "$ns" = "${ns#*,}" ] && break
465 if [ -n "$newns" ]; then
466 newdomains="$newdomains${newdomains:+ }$dn:$newns"
469 DOMAIN="$(list_remove domain_blacklist $DOMAIN)"
470 SEARCH="$(uniqify $SEARCH)"
471 SEARCH="$(list_remove domain_blacklist $SEARCH)"
472 NAMESERVERS="$(uniqify $NAMESERVERS)"
473 NAMESERVERS="$(list_remove name_server_blacklist $NAMESERVERS)"
474 LOCALNAMESERVERS="$(uniqify $LOCALNAMESERVERS)"
475 LOCALNAMESERVERS="$(list_remove name_server_blacklist $LOCALNAMESERVERS)"
476 echo "DOMAIN='$DOMAIN'"
477 echo "SEARCH='$SEARCH'"
478 echo "NAMESERVERS='$NAMESERVERS'"
479 echo "LOCALNAMESERVERS='$LOCALNAMESERVERS'"
480 echo "DOMAINS='$newdomains'"
485 while getopts a:Dd:fhIilm:puvVx OPT; do
489 m) IF_METRIC="$OPTARG";;
493 if [ "$local_nameservers" = \
494 "127.* 0.0.0.0 255.255.255.255 ::1" ]
501 *) cmd="$OPT"; iface="$OPTARG";;
504 shift $(($OPTIND - 1))
505 args="$iface${iface:+ }$*"
507 # -I inits the state dir
508 if [ "$cmd" = I ]; then
509 if [ -d "$VARDIR" ]; then
515 # -D ensures that the listed config file base dirs exist
516 if [ "$cmd" = D ]; then
521 # -l lists our resolv files, optionally for a specific interface
522 if [ "$cmd" = l -o "$cmd" = i ]; then
523 list_resolv "$cmd" "$args"
527 # Not normally needed, but subscribers should be able to run independently
528 if [ "$cmd" = v -o -n "$VFLAG" ]; then
533 # Test that we have valid options
534 if [ "$cmd" = a -o "$cmd" = d ]; then
535 if [ -z "$iface" ]; then
536 usage "Interface not specified"
538 elif [ "$cmd" != u ]; then
539 [ -n "$cmd" -a "$cmd" != h ] && usage "Unknown option $cmd"
543 if [ "$cmd" = a ]; then
544 for x in '/' \\ ' ' '*'; do
546 *[$x]*) error_exit "$x not allowed in interface name";;
549 for x in '.' '-' '~'; do
552 "$x not allowed at start of interface name";;
555 [ "$cmd" = a -a -t 0 ] && error_exit "No file given via stdin"
558 if [ ! -d "$VARDIR" ]; then
559 if [ -L "$VARDIR" ]; then
560 dir="$(readlink "$VARDIR")"
561 # link maybe relative
563 if ! mkdir -m 0755 -p "$dir"; then
564 error_exit "Failed to create needed" \
568 if ! mkdir -m 0755 -p "$VARDIR"; then
569 error_exit "Failed to create needed" \
575 if [ ! -d "$IFACEDIR" ]; then
576 mkdir -m 0755 -p "$IFACEDIR" || \
577 error_exit "Failed to create needed directory $IFACEDIR"
578 if [ "$cmd" = d ]; then
579 # Provide the same error messages as below
583 warn "No resolv.conf for interface $i"
591 # An interface was added, changed, deleted or a general update was called.
592 # Due to exclusivity we need to ensure that this is an atomic operation.
593 # Our subscribers *may* need this as well if the init system is sub par.
594 # As such we spinlock at this point as best we can.
595 # We don't use flock(1) because it's not widely available and normally resides
596 # in /usr which we do our very best to operate without.
597 [ -w "$VARDIR" ] || error_exit "Cannot write to $LOCKDIR"
598 : ${lock_timeout:=10}
600 if mkdir "$LOCKDIR" 2>/dev/null; then
601 trap 'rm -rf "$LOCKDIR";' EXIT
602 trap 'rm -rf "$LOCKDIR"; exit 1' INT QUIT ABRT SEGV ALRM TERM
603 echo $$ >"$LOCKDIR/pid"
606 pid=$(cat "$LOCKDIR/pid")
607 if ! kill -0 "$pid"; then
608 warn "clearing stale lock pid $pid"
612 lock_timeout=$(($lock_timeout - 1))
613 if [ "$lock_timeout" -le 0 ]; then
614 error_exit "timed out waiting for lock from pid $pid"
621 # Read resolv.conf from stdin
625 # If what we are given matches what we have, then do nothing
626 if [ -e "$IFACEDIR/$iface" ]; then
627 if [ "$(echo "$resolv")" != \
628 "$(cat "$IFACEDIR/$iface")" ]
638 # Set metric and private before creating the interface resolv.conf file
639 # to ensure that it will have the correct flags
640 [ ! -d "$METRICDIR" ] && mkdir "$METRICDIR"
641 oldmetric="$METRICDIR/"*" $iface"
643 if [ -n "$IF_METRIC" ]; then
644 # Pad metric to 6 characters, so 5 is less than 10
645 while [ ${#IF_METRIC} -le 6 ]; do
646 IF_METRIC="0$IF_METRIC"
648 newmetric="$METRICDIR/$IF_METRIC $iface"
650 rm -f "$METRICDIR/"*" $iface"
651 [ "$oldmetric" != "$newmetric" -a \
652 "$oldmetric" != "$METRICDIR/* $iface" ] &&
654 [ -n "$newmetric" ] && echo " " >"$newmetric"
656 case "$IF_PRIVATE" in
657 [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1)
658 if [ ! -d "$PRIVATEDIR" ]; then
659 [ -e "$PRIVATEDIR" ] && rm "$PRIVATEDIR"
662 [ -e "$PRIVATEDIR/$iface" ] || changed=true
663 [ -d "$PRIVATEDIR" ] && echo " " >"$PRIVATEDIR/$iface"
666 if [ -e "$PRIVATEDIR/$iface" ]; then
667 rm -f "$PRIVATEDIR/$iface"
674 for x in "$EXCLUSIVEDIR/"*" $iface"; do
680 case "$IF_EXCLUSIVE" in
681 [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1)
682 if [ ! -d "$EXCLUSIVEDIR" ]; then
683 [ -e "$EXCLUSIVEDIR" ] && rm "$EXCLUSIVEDIR"
684 mkdir "$EXCLUSIVEDIR"
690 if [ "${x#* }" != "$iface" ]; then
691 if [ "$x" = "${x% *}" ]; then
696 if [ "$x" = "0000000" ]; then
697 warn "exclusive underflow"
701 if [ -d "$EXCLUSIVEDIR" ]; then
702 echo " " >"$EXCLUSIVEDIR/$x $iface"
708 if [ -f "$oldexcl" ]; then
715 if $changedfile; then
716 printf "%s\n" "$resolv" >"$IFACEDIR/$iface" || exit $?
717 elif ! $changed; then
720 unset changed changedfile oldmetric newmetric x oldexcl
724 # Delete any existing information about the interface
730 elif ! ${force}; then
731 warn "No resolv.conf for interface $i"
733 rm -f "$i" "$METRICDIR/"*" $i" \
735 "$EXCLUSIVEDIR/"*" $i" || exit $?
737 if ! ${changed}; then
738 # Set the return code based on the forced flag
746 case "${resolvconf:-YES}" in
747 [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) ;;
752 export RESOLVCONF DOMAINS SEARCH NAMESERVERS LOCALNAMESERVERS
753 : ${list_resolv:=list_resolv -l}
755 for script in "$LIBEXECDIR"/*; do
756 if [ -f "$script" ]; then
757 eval script_enabled="\$${script##*/}"
758 case "${script_enabled:-YES}" in
759 [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) ;;
762 if [ -x "$script" ]; then
763 "$script" "$cmd" "$iface"
765 (set -- "$cmd" "$iface"; . "$script")
767 retval=$(($retval + $?))