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