Add the --version command and document it.
[openresolv] / resolvconf.in
1 #!/bin/sh
2 # Copyright (c) 2007-2016 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.8.1"
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-2016 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]* 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 IFACEDIR="$VARDIR/interfaces"
63 METRICDIR="$VARDIR/metrics"
64 PRIVATEDIR="$VARDIR/private"
65 EXCLUSIVEDIR="$VARDIR/exclusive"
66 LOCKDIR="$VARDIR/lock"
67 _PWD="$PWD"
68
69 warn()
70 {
71         echo "$*" >&2
72 }
73
74 error_exit()
75 {
76         echo "$*" >&2
77         exit 1
78 }
79
80 usage()
81 {
82         cat <<-EOF
83         Usage: ${RESOLVCONF##*/} [options] command [argument]
84
85         Inform the system about any DNS updates.
86
87         Commands:
88           -a \$INTERFACE    Add DNS information to the specified interface
89                            (DNS supplied via stdin in resolv.conf format)
90           -d \$INTERFACE    Delete DNS information from the specified interface
91           -h               Show this help cruft
92           -i [\$PATTERN]    Show interfaces that have supplied DNS information
93                    optionally from interfaces that match the specified
94                    pattern
95           -l [\$PATTERN]    Show DNS information, optionally from interfaces
96                            that match the specified pattern
97
98           -u               Run updates from our current DNS information
99           --version        Echo the ${RESOLVCONF##*/} version
100
101         Options:
102           -f               Ignore non existent interfaces
103           -m metric        Give the added DNS information a metric
104           -p               Mark the interface as private
105           -x               Mark the interface as exclusive
106
107         Subscriber and System Init Commands:
108           -I               Init the state dir
109           -r \$SERVICE      Restart the system service
110                            (restarting a non-existent or non-running service
111                             should have no output and return 0)
112           -R               Show the system service restart command
113           -v [\$PATTERN]    echo NEWDOMAIN, NEWSEARCH and NEWNS variables to
114                            the console
115           -V [\$PATTERN]    Same as -v, but only uses configuration in
116                            $SYSCONFDIR/resolvconf.conf
117         EOF
118         [ -z "$1" ] && exit 0
119         echo
120         error_exit "$*"
121 }
122
123 # Strip any trailing dot from each name as a FQDN does not belong
124 # in resolv.conf(5)
125 # If you think otherwise, capture a DNS trace and you'll see libc
126 # will strip it regardless.
127 # This also solves setting up duplicate zones in our subscribers.
128 strip_trailing_dots()
129 {
130         local n= d=
131
132         for n; do
133                 printf "$d%s" "${n%.}"
134                 d=" "
135         done
136         printf "\n"
137 }
138
139 # Parse resolv.conf's and make variables
140 # for domain name servers, search name servers and global nameservers
141 parse_resolv()
142 {
143         local line= ns= ds= search= d= n= newns=
144         local new=true iface= private=false p= domain= l= islocal=
145
146         newns=
147
148         while read -r line; do
149                 case "$line" in
150                 "# resolv.conf from "*)
151                         if ${new}; then
152                                 iface="${line#\# resolv.conf from *}"
153                                 new=false
154                                 if [ -e "$PRIVATEDIR/$iface" ]; then
155                                         private=true
156                                 else
157                                         # Allow expansion
158                                         cd "$IFACEDIR"
159                                         private=false
160                                         for p in $private_interfaces; do
161                                                 case "$iface" in
162                                                 "$p"|"$p":*)
163                                                         private=true
164                                                         break
165                                                         ;;
166                                                 esac
167                                         done
168                                 fi
169                         fi
170                         ;;
171                 "nameserver "*)
172                         islocal=false
173                         for l in $local_nameservers; do
174                                 case "${line#* }" in
175                                 $l)
176                                         islocal=true
177                                         echo "LOCALNAMESERVERS=\"\$LOCALNAMESERVERS ${line#* }\""
178                                         break
179                                         ;;
180                                 esac
181                         done
182                         $islocal || ns="$ns${line#* } "
183                         ;;
184                 "domain "*)
185                         search="$(strip_trailing_dots ${line#* })"
186                         if [ -z "$domain" ]; then
187                                 domain="$search"
188                                 echo "DOMAIN=\"$domain\""
189                         fi
190                         ;;
191                 "search "*)
192                         search="$(strip_trailing_dots ${line#* })"
193                         ;;
194                 *)
195                         [ -n "$line" ] && continue
196                         if [ -n "$ns" -a -n "$search" ]; then
197                                 newns=
198                                 for n in $ns; do
199                                         newns="$newns${newns:+,}$n"
200                                 done
201                                 ds=
202                                 for d in $search; do
203                                         ds="$ds${ds:+ }$d:$newns"
204                                 done
205                                 echo "DOMAINS=\"\$DOMAINS $ds\""
206                         fi
207                         echo "SEARCH=\"\$SEARCH $search\""
208                         if ! $private; then
209                                 echo "NAMESERVERS=\"\$NAMESERVERS $ns\""
210                         fi
211                         ns=
212                         search=
213                         new=true
214                         ;;
215                 esac
216         done
217 }
218
219 uniqify()
220 {
221         local result=
222         while [ -n "$1" ]; do
223                 case " $result " in
224                 *" $1 "*);;
225                 *) result="$result $1";;
226                 esac
227                 shift
228         done
229         echo "${result# *}"
230 }
231
232 dirname()
233 {
234         local dir= OIFS="$IFS"
235         local IFS=/
236         set -- $@
237         IFS="$OIFS"
238         if [ -n "$1" ]; then
239                 printf %s .
240         else
241                 shift
242         fi
243         while [ -n "$2" ]; do
244                 printf "/%s" "$1"
245                 shift
246         done
247         printf "\n"
248 }
249
250 config_mkdirs()
251 {
252         local e=0 f d
253         for f; do
254                 [ -n "$f" ] || continue
255                 d="$(dirname "$f")"
256                 if [ ! -d "$d" ]; then
257                         if type install >/dev/null 2>&1; then
258                                 install -d "$d" || e=$?
259                         else
260                                 mkdir "$d" || e=$?
261                         fi
262                 fi
263         done
264         return $e
265 }
266
267 # With the advent of alternative init systems, it's possible to have
268 # more than one installed. So we need to try and guess what one we're
269 # using unless overriden by configure.
270 # Note that restarting a service is a last resort - the subscribers
271 # should make a reasonable attempt to reconfigre the service via some
272 # method, normally SIGHUP.
273 detect_init()
274 {
275         [ -n "$RESTARTCMD" ] && return 0
276
277         # Detect the running init system.
278         # As systemd and OpenRC can be installed on top of legacy init
279         # systems we try to detect them first.
280         local status="@STATUSARG@"
281         : ${status:=status}
282         if [ -x /bin/systemctl -a -S /run/systemd/private ]; then
283                 RESTARTCMD="if /bin/systemctl --quiet is-active \$1.service; then
284         /bin/systemctl restart \$1.service;
285 fi"
286         elif [ -x /usr/bin/systemctl -a -S /run/systemd/private ]; then
287                 RESTARTCMD="if /usr/bin/systemctl --quiet is-active \$1.service; then
288         /usr/bin/systemctl restart \$1.service;
289 fi"
290         elif [ -x /sbin/rc-service -a \
291             -s /libexec/rc/init.d/softlevel -o -s /run/openrc/softlevel ]
292         then
293                 RESTARTCMD="/sbin/rc-service -i \$1 -- -Ds restart"
294         elif [ -x /usr/sbin/invoke-rc.d ]; then
295                 RCDIR=/etc/init.d
296                 RESTARTCMD="if /usr/sbin/invoke-rc.d --quiet \$1 status 1>/dev/null 2>&1; then
297         /usr/sbin/invoke-rc.d \$1 restart;
298 fi"
299         elif [ -x /sbin/service ]; then
300                 # Old RedHat
301                 RCDIR=/etc/init.d
302                 RESTARTCMD="if /sbin/service \$1; then
303         /sbin/service \$1 restart;
304 fi"
305         elif [ -x /usr/sbin/service ]; then
306                 # Could be FreeBSD
307                 RESTARTCMD="if /usr/sbin/service \$1 $status 1>/dev/null 2>&1; then
308         /usr/sbin/service \$1 restart;
309 fi"
310         elif [ -x /bin/sv ]; then
311                 RESTARTCMD="/bin/sv try-restart \$1"
312         elif [ -x /usr/bin/sv ]; then
313                 RESTARTCMD="/usr/bin/sv try-restart \$1"
314         elif [ -e /etc/arch-release -a -d /etc/rc.d ]; then
315                 RCDIR=/etc/rc.d
316                 RESTARTCMD="if [ -e /var/run/daemons/\$1 ]; then
317         /etc/rc.d/\$1 restart;
318 fi"
319         elif [ -e /etc/slackware-version -a -d /etc/rc.d ]; then
320                 RESTARTCMD="if /etc/rc.d/rc.\$1 status 1>/dev/null 2>&1; then
321         /etc/rc.d/rc.\$1 restart;
322 fi"
323         elif [ -e /etc/rc.d/rc.subr -a -d /etc/rc.d ]; then
324                 # OpenBSD
325                 RESTARTCMD="if /etc/rc.d/\$1 check 1>/dev/null 2>&1; then
326         /etc/rc.d/\$1 restart;
327 fi"
328         else
329                 for x in /etc/init.d/rc.d /etc/rc.d /etc/init.d; do
330                         [ -d $x ] || continue
331                         RESTARTCMD="if $x/\$1 $status 1>/dev/null 2>&1; then
332         $x/\$1 restart;
333 fi"
334                         break
335                 done
336         fi
337
338         if [ -z "$RESTARTCMD" ]; then
339                 if [ "$NOINIT_WARNED" != true ]; then
340                         warn "could not detect a useable init system"
341                         _NOINIT_WARNED=true
342                 fi
343                 return 1
344         fi
345         _NOINIT_WARNED=
346         return 0
347 }
348
349 echo_resolv()
350 {
351         local line= OIFS="$IFS"
352
353         [ -n "$1" -a -f "$IFACEDIR/$1" ] || return 1
354         echo "# resolv.conf from $1"
355         # Our variable maker works of the fact each resolv.conf per interface
356         # is separated by blank lines.
357         # So we remove them when echoing them.
358         while read -r line; do
359                 IFS="$OIFS"
360                 if [ -n "$line" ]; then
361                         # We need to set IFS here to preserve any whitespace
362                         IFS=''
363                         printf "%s\n" "$line"
364                 fi
365         done < "$IFACEDIR/$1"
366         IFS="$OIFS"
367 }
368
369 list_resolv()
370 {
371         [ -d "$IFACEDIR" ] || return 0
372
373         local report=false list= retval=0 cmd="$1" excl=
374         shift
375
376         case "$IF_EXCLUSIVE" in
377         [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1)
378                 if [ -d "$EXCLUSIVEDIR" ]; then
379                         cd "$EXCLUSIVEDIR"
380                         for i in *; do
381                                 if [ -f "$i" ]; then
382                                         list="${i#* }"
383                                         break
384                                 fi
385                         done
386                 fi
387                 excl=true
388                 ;;
389         *)
390                 excl=false
391                 ;;
392         esac
393
394         # If we have an interface ordering list, then use that.
395         # It works by just using pathname expansion in the interface directory.
396         if [ -n "$1" ]; then
397                 list="$*"
398                 $force || report=true
399         elif ! $excl; then
400                 cd "$IFACEDIR"
401                 for i in $interface_order; do
402                         [ -f "$i" ] && list="$list $i"
403                         for ii in "$i":* "$i".*; do
404                                 [ -f "$ii" ] && list="$list $ii"
405                         done
406                 done
407                 for i in $dynamic_order; do
408                         if [ -e "$i" -a ! -e "$METRICDIR/"*" $i" ]; then
409                                 list="$list $i"
410                         fi
411                         for ii in "$i":* "$i".*; do
412                                 if [ -f "$ii" -a ! -e "$METRICDIR/"*" $ii" ]; then
413                                         list="$list $ii"
414                                 fi
415                         done
416                 done
417                 if [ -d "$METRICDIR" ]; then
418                         cd "$METRICDIR"
419                         for i in *; do
420                                 [ -f "$i" ] && list="$list ${i#* }"
421                         done
422                 fi
423                 list="$list *"
424         fi
425
426         cd "$IFACEDIR"
427         retval=1
428         excl=true
429         for i in $(uniqify $list); do
430                 # Only list interfaces which we really have
431                 if ! [ -f "$i" ]; then
432                         if $report; then
433                                 echo "No resolv.conf for interface $i" >&2
434                                 retval=2
435                         fi
436                         continue
437                 fi
438                 
439                 if [ "$cmd" = i -o "$cmd" = "-i" ]; then
440                         printf %s "$i "
441                 else
442                         echo_resolv "$i"
443                         echo
444                 fi
445                 [ $? = 0 -a "$retval" = 1 ] && retval=0
446         done
447         [ "$cmd" = i -o "$cmd" = "-i" ] && echo
448         return $retval
449 }
450
451 list_remove() {
452         local list= e= l= result= found= retval=0
453
454         [ -z "$2" ] && return 0
455         eval list=\"\$$1\"
456         shift
457
458         set -f
459         for e; do
460                 found=false
461                 for l in $list; do
462                         case "$e" in
463                         $l) found=true;;
464                         esac
465                         $found && break
466                 done
467                 if $found; then
468                         retval=$(($retval + 1))
469                 else
470                         result="$result $e"
471                 fi
472         done
473         set +f
474         echo "${result# *}"
475         return $retval
476 }
477
478 echo_prepend()
479 {
480         echo "# Generated by resolvconf"
481         if [ -n "$search_domains" ]; then
482                 echo "search $search_domains"
483         fi
484         for n in $name_servers; do
485                 echo "nameserver $n"
486         done
487         echo
488 }
489
490 echo_append()
491 {
492         echo "# Generated by resolvconf"
493         if [ -n "$search_domains_append" ]; then
494                 echo "search $search_domains_append"
495         fi
496         for n in $name_servers_append; do
497                 echo "nameserver $n"
498         done
499         echo
500 }
501
502 replace()
503 {
504         local r= k= f= v= val= sub=
505
506         while read -r keyword value; do
507                 for r in $replace; do
508                         k="${r%%/*}"
509                         r="${r#*/}"
510                         f="${r%%/*}"
511                         r="${r#*/}"
512                         v="${r%%/*}"
513                         case "$keyword" in
514                         $k)
515                                 case "$value" in
516                                 $f) value="$v";;
517                                 esac
518                                 ;;
519                         esac
520                 done
521                 val=
522                 for sub in $value; do
523                         for r in $replace_sub; do
524                                 k="${r%%/*}"
525                                 r="${r#*/}"
526                                 f="${r%%/*}"
527                                 r="${r#*/}"
528                                 v="${r%%/*}"
529                                 case "$keyword" in
530                                 $k)
531                                         case "$sub" in
532                                         $f) sub="$v";;
533                                         esac
534                                         ;;
535                                 esac
536                         done
537                         val="$val${val:+ }$sub"
538                 done
539                 printf "%s %s\n" "$keyword" "$val"
540         done
541 }
542
543 make_vars()
544 {
545         local newdomains= d= dn= newns= ns=
546
547         # Clear variables
548         DOMAIN=
549         DOMAINS=
550         SEARCH=
551         NAMESERVERS=
552         LOCALNAMESERVERS=
553         
554         if [ -n "$name_servers" -o -n "$search_domains" ]; then
555                 eval "$(echo_prepend | parse_resolv)"
556         fi
557         if [ -z "$VFLAG" ]; then
558                 IF_EXCLUSIVE=1
559                 list_resolv -i "$@" >/dev/null || IF_EXCLUSIVE=0
560                 eval "$(list_resolv -l "$@" | replace | parse_resolv)"
561         fi
562         if [ -n "$name_servers_append" -o -n "$search_domains_append" ]; then
563                 eval "$(echo_append | parse_resolv)"
564         fi
565
566         # Ensure that we only list each domain once
567         for d in $DOMAINS; do
568                 dn="${d%%:*}"
569                 list_remove domain_blacklist "$dn" >/dev/null || continue
570                 case " $newdomains" in
571                 *" ${dn}:"*) continue;;
572                 esac
573                 newns=
574                 for nd in $DOMAINS; do
575                         if [ "$dn" = "${nd%%:*}" ]; then
576                                 ns="${nd#*:}"
577                                 while [ -n "$ns" ]; do
578                                         case ",$newns," in
579                                         *,${ns%%,*},*) ;;
580                                         *) list_remove name_server_blacklist \
581                                                 "${ns%%,*}" >/dev/null \
582                                         && newns="$newns${newns:+,}${ns%%,*}";;
583                                         esac
584                                         [ "$ns" = "${ns#*,}" ] && break
585                                         ns="${ns#*,}"
586                                 done
587                         fi
588                 done
589                 if [ -n "$newns" ]; then
590                         newdomains="$newdomains${newdomains:+ }$dn:$newns"
591                 fi
592         done
593         DOMAIN="$(list_remove domain_blacklist $DOMAIN)"
594         SEARCH="$(uniqify $SEARCH)"
595         SEARCH="$(list_remove domain_blacklist $SEARCH)"
596         NAMESERVERS="$(uniqify $NAMESERVERS)"
597         NAMESERVERS="$(list_remove name_server_blacklist $NAMESERVERS)"
598         LOCALNAMESERVERS="$(uniqify $LOCALNAMESERVERS)"
599         LOCALNAMESERVERS="$(list_remove name_server_blacklist $LOCALNAMESERVERS)"
600         echo "DOMAIN='$DOMAIN'"
601         echo "SEARCH='$SEARCH'"
602         echo "NAMESERVERS='$NAMESERVERS'"
603         echo "LOCALNAMESERVERS='$LOCALNAMESERVERS'"
604         echo "DOMAINS='$newdomains'"
605 }
606
607 force=false
608 VFLAG=
609 while getopts a:Dd:fhIilm:pRruvVx OPT; do
610         case "$OPT" in
611         f) force=true;;
612         h) usage;;
613         m) IF_METRIC="$OPTARG";;
614         p) IF_PRIVATE=1;;
615         V)
616                 VFLAG=1
617                 if [ "$local_nameservers" = \
618                     "127.* 0.0.0.0 255.255.255.255 ::1" ]
619                 then
620                         local_nameservers=
621                 fi
622                 ;;
623         x) IF_EXCLUSIVE=1;;
624         '?') ;;
625         *) cmd="$OPT"; iface="$OPTARG";;
626         esac
627 done
628 shift $(($OPTIND - 1))
629 args="$iface${iface:+ }$*"
630
631 # -I inits the state dir
632 if [ "$cmd" = I ]; then
633         if [ -d "$VARDIR" ]; then
634                 rm -rf "$VARDIR"/*
635         fi
636         exit $?
637 fi
638
639 # -D ensures that the listed config file base dirs exist
640 if [ "$cmd" = D ]; then
641         config_mkdirs "$@"
642         exit $?
643 fi
644
645 # -l lists our resolv files, optionally for a specific interface
646 if [ "$cmd" = l -o "$cmd" = i ]; then
647         list_resolv "$cmd" "$args"
648         exit $?
649 fi
650
651 # Restart a service or echo the command to restart a service
652 if [ "$cmd" = r -o "$cmd" = R ]; then
653         detect_init || exit 1
654         if [ "$cmd" = r ]; then
655                 set -- $args
656                 eval $RESTARTCMD
657         else
658                 echo "$RESTARTCMD"
659         fi
660         exit $?
661 fi
662
663 # Not normally needed, but subscribers should be able to run independently
664 if [ "$cmd" = v -o -n "$VFLAG" ]; then
665         make_vars "$iface"
666         exit $?
667 fi
668
669 # Test that we have valid options
670 if [ "$cmd" = a -o "$cmd" = d ]; then
671         if [ -z "$iface" ]; then
672                 usage "Interface not specified"
673         fi
674 elif [ "$cmd" != u ]; then
675         [ -n "$cmd" -a "$cmd" != h ] && usage "Unknown option $cmd"
676         usage
677 fi
678
679 if [ "$cmd" = a ]; then
680         for x in '/' \\ ' ' '*'; do
681                 case "$iface" in
682                 *[$x]*) error_exit "$x not allowed in interface name";;
683                 esac
684         done
685         for x in '.' '-' '~'; do
686                 case "$iface" in
687                 [$x]*) error_exit \
688                         "$x not allowed at start of interface name";;
689                 esac
690         done
691         [ "$cmd" = a -a -t 0 ] && error_exit "No file given via stdin"
692 fi
693
694 if [ ! -d "$VARDIR" ]; then
695         if [ -L "$VARDIR" ]; then
696                 dir="$(readlink "$VARDIR")"
697                 # link maybe relative
698                 cd "${VARDIR%/*}"
699                 if ! mkdir -m 0755 -p "$dir"; then
700                         error_exit "Failed to create needed" \
701                                 "directory $dir"
702                 fi
703         else
704                 if ! mkdir -m 0755 -p "$VARDIR"; then
705                         error_exit "Failed to create needed" \
706                                 "directory $VARDIR"
707                 fi
708         fi
709 fi
710
711 if [ ! -d "$IFACEDIR" ]; then
712         mkdir -m 0755 -p "$IFACEDIR" || \
713                 error_exit "Failed to create needed directory $IFACEDIR"
714         if [ "$cmd" = d ]; then
715                 # Provide the same error messages as below
716                 if ! ${force}; then
717                         cd "$IFACEDIR"
718                         for i in $args; do
719                                 warn "No resolv.conf for interface $i"
720                         done
721                 fi
722                 ${force}
723                 exit $?
724         fi
725 fi
726
727 # An interface was added, changed, deleted or a general update was called.
728 # Due to exclusivity we need to ensure that this is an atomic operation.
729 # Our subscribers *may* need this as well if the init system is sub par.
730 # As such we spinlock at this point as best we can.
731 # We don't use flock(1) because it's not widely available and normally resides
732 # in /usr which we do our very best to operate without.
733 [ -w "$VARDIR" ] || error_exit "Cannot write to $LOCKDIR"
734 : ${lock_timeout:=10}
735 while true; do
736         if mkdir "$LOCKDIR" 2>/dev/null; then
737                 trap 'rm -rf "$LOCKDIR";' EXIT
738                 trap 'rm -rf "$LOCKDIR"; exit 1' INT QUIT ABRT SEGV ALRM TERM
739                 echo $$ >"$LOCKDIR/pid"
740                 break
741         fi
742         pid=$(cat "$LOCKDIR/pid")
743         if ! kill -0 "$pid"; then
744                 warn "clearing stale lock pid $pid"
745                 rm -rf "$LOCKDIR"
746                 continue
747         fi
748         lock_timeout=$(($lock_timeout - 1))
749         if [ "$lock_timeout" -le 0 ]; then
750                 error_exit "timed out waiting for lock from pid $pid"
751         fi
752         sleep 1
753 done
754
755 case "$cmd" in
756 a)
757         # Read resolv.conf from stdin
758         resolv="$(cat)"
759         changed=false
760         changedfile=false
761         # If what we are given matches what we have, then do nothing
762         if [ -e "$IFACEDIR/$iface" ]; then
763                 if [ "$(echo "$resolv")" != \
764                         "$(cat "$IFACEDIR/$iface")" ]
765                 then
766                         changed=true
767                         changedfile=true
768                 fi
769         else
770                 changed=true
771                 changedfile=true
772         fi
773
774         # Set metric and private before creating the interface resolv.conf file
775         # to ensure that it will have the correct flags
776         [ ! -d "$METRICDIR" ] && mkdir "$METRICDIR"
777         oldmetric="$METRICDIR/"*" $iface"
778         newmetric=
779         if [ -n "$IF_METRIC" ]; then
780                 # Pad metric to 6 characters, so 5 is less than 10
781                 while [ ${#IF_METRIC} -le 6 ]; do
782                         IF_METRIC="0$IF_METRIC"
783                 done
784                 newmetric="$METRICDIR/$IF_METRIC $iface"
785         fi
786         rm -f "$METRICDIR/"*" $iface"
787         [ "$oldmetric" != "$newmetric" -a \
788             "$oldmetric" != "$METRICDIR/* $iface" ] &&
789                 changed=true
790         [ -n "$newmetric" ] && echo " " >"$newmetric"
791
792         case "$IF_PRIVATE" in
793         [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1)
794                 if [ ! -d "$PRIVATEDIR" ]; then
795                         [ -e "$PRIVATEDIR" ] && rm "$PRIVATEDIR"
796                         mkdir "$PRIVATEDIR"
797                 fi
798                 [ -e "$PRIVATEDIR/$iface" ] || changed=true
799                 [ -d "$PRIVATEDIR" ] && echo " " >"$PRIVATEDIR/$iface"
800                 ;;
801         *)
802                 if [ -e "$PRIVATEDIR/$iface" ]; then
803                         rm -f "$PRIVATEDIR/$iface"
804                         changed=true
805                 fi
806                 ;;
807         esac
808
809         oldexcl=
810         for x in "$EXCLUSIVEDIR/"*" $iface"; do
811                 if [ -f "$x" ]; then
812                         oldexcl="$x"
813                         break
814                 fi
815         done
816         case "$IF_EXCLUSIVE" in
817         [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1)
818                 if [ ! -d "$EXCLUSIVEDIR" ]; then
819                         [ -e "$EXCLUSIVEDIR" ] && rm "$EXCLUSIVEDIR"
820                         mkdir "$EXCLUSIVEDIR"
821                 fi
822                 cd "$EXCLUSIVEDIR"
823                 for x in *; do
824                         [ -f "$x" ] && break
825                 done
826                 if [ "${x#* }" != "$iface" ]; then
827                         if [ "$x" = "${x% *}" ]; then
828                                 x=10000000
829                         else
830                                 x="${x% *}"
831                         fi
832                         if [ "$x" = "0000000" ]; then
833                                 warn "exclusive underflow"
834                         else
835                                 x=$(($x - 1))
836                         fi
837                         if [ -d "$EXCLUSIVEDIR" ]; then
838                                 echo " " >"$EXCLUSIVEDIR/$x $iface"
839                         fi
840                         changed=true
841                 fi
842                 ;;
843         *)
844                 if [ -f "$oldexcl" ]; then
845                         rm -f "$oldexcl"
846                         changed=true
847                 fi
848                 ;;
849         esac
850
851         if $changedfile; then
852                 printf "%s\n" "$resolv" >"$IFACEDIR/$iface" || exit $?
853         elif ! $changed; then
854                 exit 0
855         fi
856         unset changed changedfile oldmetric newmetric x oldexcl
857         ;;
858
859 d)
860         # Delete any existing information about the interface
861         cd "$IFACEDIR"
862         changed=false
863         for i in $args; do
864                 if [ -e "$i" ]; then
865                         changed=true
866                 elif ! ${force}; then
867                         warn "No resolv.conf for interface $i"
868                 fi
869                 rm -f "$i" "$METRICDIR/"*" $i" \
870                         "$PRIVATEDIR/$i" \
871                         "$EXCLUSIVEDIR/"*" $i" || exit $?
872         done
873         if ! ${changed}; then
874                 # Set the return code based on the forced flag
875                 ${force}
876                 exit $?
877         fi
878         unset changed i
879         ;;
880 esac
881
882 case "${resolvconf:-YES}" in
883 [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) ;;
884 *) exit 0;;
885 esac
886
887 # Try and detect a suitable init system for our scripts
888 detect_init
889 export RESTARTCMD RCDIR _NOINIT_WARNED
890
891 eval "$(make_vars)"
892 export RESOLVCONF DOMAINS SEARCH NAMESERVERS LOCALNAMESERVERS
893 : ${list_resolv:=list_resolv -l}
894 retval=0
895
896 # Run scripts in the same directory resolvconf is run from
897 # in case any scripts accidentally dump files in the wrong place.
898 cd "$_PWD"
899 for script in "$LIBEXECDIR"/*; do
900         if [ -f "$script" ]; then
901                 eval script_enabled="\$${script##*/}"
902                 case "${script_enabled:-YES}" in
903                 [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) ;;
904                 *) continue;;
905                 esac
906                 if [ -x "$script" ]; then
907                         "$script" "$cmd" "$iface"
908                 else
909                         (set -- "$cmd" "$iface"; . "$script")
910                 fi
911                 retval=$(($retval + $?))
912         fi
913 done
914 exit $retval