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