Harden the locking mechanism
[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.11.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 overriden by configure.
294 # Note that restarting a service is a last resort - the subscribers
295 # should make a reasonable attempt to reconfigre 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         else
371                 for x in /etc/init.d/rc.d /etc/rc.d /etc/init.d; do
372                         [ -d $x ] || continue
373                         RESTARTCMD="
374                                 if $x/\$1 $status >/dev/null 2>&1
375                                 then
376                                         $x/\$1 restart
377                                 fi"
378                         break
379                 done
380         fi
381
382         if [ -z "$RESTARTCMD" ]; then
383                 if [ "$_NOINIT_WARNED" != true ]; then
384                         warn "could not detect a useable init system"
385                         _NOINIT_WARNED=true
386                 fi
387                 return 1
388         fi
389         _NOINIT_WARNED=
390         return 0
391 }
392
393 echo_resolv()
394 {
395         OIFS="$IFS"
396
397         [ -n "$1" ] && [ -f "$IFACEDIR/$1" ] || return 1
398         echo "# resolv.conf from $1"
399         # Our variable maker works of the fact each resolv.conf per interface
400         # is separated by blank lines.
401         # So we remove them when echoing them.
402         while read -r line; do
403                 IFS="$OIFS"
404                 if [ -n "$line" ]; then
405                         # We need to set IFS here to preserve any whitespace
406                         IFS=''
407                         printf "%s\n" "$line"
408                 fi
409         done < "$IFACEDIR/$1"
410         IFS="$OIFS"
411 }
412
413 deprecated_interface()
414 {
415         [ -d "$DEPRECATEDDIR" ] || return 1
416
417         cd "$DEPRECATEDDIR"
418         for da; do
419                 for daf in *; do
420                         [ -f "$daf" ] || continue
421                         case "$da" in
422                         $daf) return 0;;
423                         esac
424                 done
425         done
426         return 1
427 }
428
429 list_resolv()
430 {
431         [ -d "$IFACEDIR" ] || return 0
432
433         cmd="$1"
434         shift
435         excl=false
436         list=
437         report=false
438         retval=0
439
440         case "$IF_EXCLUSIVE" in
441         [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1)
442                 excl=true
443                 if [ -d "$EXCLUSIVEDIR" ]; then
444                         cd "$EXCLUSIVEDIR"
445                         for i in *; do
446                                 if [ -f "$i" ]; then
447                                         list="${i#* }"
448                                         break
449                                 fi
450                         done
451                 fi
452                 cd "$IFACEDIR"
453                 for i in $inclusive_interfaces; do
454                         if [ -f "$i" ] && [ "$list" = "$i" ]; then
455                                 list=
456                                 excl=false
457                                 break
458                         fi
459                 done
460                 ;;
461         esac
462
463         # If we have an interface ordering list, then use that.
464         # It works by just using pathname expansion in the interface directory.
465         if [ -n "$1" ]; then
466                 list="$*"
467                 $force || report=true
468         elif ! $excl; then
469                 cd "$IFACEDIR"
470
471                 for i in $interface_order; do
472                         [ -f "$i" ] && list="$list $i"
473                         for ii in "$i":* "$i".*; do
474                                 [ -f "$ii" ] && list="$list $ii"
475                         done
476                 done
477
478                 for i in $dynamic_order; do
479                         if [ -e "$i" ] && ! [ -e "$METRICDIR/"*" $i" ]; then
480                                 list="$list $i"
481                         fi
482                         for ii in "$i":* "$i".*; do
483                                 if [ -f "$ii" ] && ! [ -e "$METRICDIR/"*" $ii" ]
484                                 then
485                                         list="$list $ii"
486                                 fi
487                         done
488                 done
489
490                 # Interfaces have an implicit metric of 0 if not specified.
491                 for i in *; do
492                         if [ -f "$i" ] && ! [ -e "$METRICDIR/"*" $i" ]; then
493                                 list="$list $i"
494                         fi
495                 done
496
497                 if [ -d "$METRICDIR" ]; then
498                         cd "$METRICDIR"
499                         for i in *; do
500                                 [ -f "$i" ] && list="$list ${i#* }"
501                         done
502                 fi
503
504                 # Move deprecated interfaces to the back
505                 active=
506                 deprecated=
507                 for i in $list; do
508                         if deprecated_interface "$i"; then
509                                 deprecated="$deprecated $i"
510                         else
511                                 active="$active $i"
512                         fi
513                 done
514                 list="$active $deprecated"
515         fi
516
517         cd "$IFACEDIR"
518         retval=1
519         for i in $(uniqify $list); do
520                 # Only list interfaces which we really have
521                 if ! [ -f "$i" ]; then
522                         if $report; then
523                                 echo "No resolv.conf for interface $i" >&2
524                                 retval=2
525                         fi
526                         continue
527                 fi
528
529                 if ! $ALLIFACES; then
530                         if [ -n "$allow_interfaces" ]; then
531                                 x=false
532                                 for j in $allow_interfaces; do
533                                         if [ "$i" = "$j" ]; then
534                                                 x=true
535                                         fi
536                                 done
537                                 $x || continue
538                         fi
539                         for j in $deny_interfaces; do
540                                 if [ "$i" = "$j" ]; then
541                                         continue 2
542                                 fi
543                         done
544                 fi
545                 
546                 if [ "$cmd" = i ] || [ "$cmd" = "-i" ]; then
547                         printf %s "$i "
548                 else
549                         echo_resolv "$i" && echo
550                 fi
551                 [ $? = 0 ] && [ "$retval" = 1 ] && retval=0
552         done
553         [ "$cmd" = i ] || [ "$cmd" = "-i" ] && echo
554         return $retval
555 }
556
557 list_remove()
558 {
559         [ -z "$2" ] && return 0
560         eval list=\"\$$1\"
561         shift
562         result=
563         retval=0
564
565         set -f
566         for e; do
567                 found=false
568                 for l in $list; do
569                         case "$e" in
570                         $l) found=true;;
571                         esac
572                         $found && break
573                 done
574                 if $found; then
575                         retval=$(($retval + 1))
576                 else
577                         result="$result $e"
578                 fi
579         done
580         set +f
581         echo "${result# *}"
582         return $retval
583 }
584
585 echo_prepend()
586 {
587         echo "# Generated by resolvconf"
588         if [ -n "$search_domains" ]; then
589                 echo "search $search_domains"
590         fi
591         for n in $name_servers; do
592                 echo "nameserver $n"
593         done
594         echo
595 }
596
597 echo_append()
598 {
599         echo "# Generated by resolvconf"
600         if [ -n "$search_domains_append" ]; then
601                 echo "search $search_domains_append"
602         fi
603         for n in $name_servers_append; do
604                 echo "nameserver $n"
605         done
606         echo
607 }
608
609 replace()
610 {
611         while read -r keyword value; do
612                 for r in $replace; do
613                         k="${r%%/*}"
614                         r="${r#*/}"
615                         f="${r%%/*}"
616                         r="${r#*/}"
617                         v="${r%%/*}"
618                         case "$keyword" in
619                         $k)
620                                 case "$value" in
621                                 $f) value="$v";;
622                                 esac
623                                 ;;
624                         esac
625                 done
626                 val=
627                 for sub in $value; do
628                         for r in $replace_sub; do
629                                 k="${r%%/*}"
630                                 r="${r#*/}"
631                                 f="${r%%/*}"
632                                 r="${r#*/}"
633                                 v="${r%%/*}"
634                                 case "$keyword" in
635                                 $k)
636                                         case "$sub" in
637                                         $f) sub="$v";;
638                                         esac
639                                         ;;
640                                 esac
641                         done
642                         val="$val${val:+ }$sub"
643                 done
644                 printf "%s %s\n" "$keyword" "$val"
645         done
646 }
647
648 make_vars()
649 {
650         # Clear variables
651         DOMAIN=
652         DOMAINS=
653         SEARCH=
654         NAMESERVERS=
655         LOCALNAMESERVERS=
656
657         if [ -n "${name_servers}${search_domains}" ]; then
658                 eval "$(echo_prepend | parse_resolv)"
659         fi
660         if [ -z "$VFLAG" ]; then
661                 IF_EXCLUSIVE=1
662                 list_resolv -i "$@" >/dev/null || IF_EXCLUSIVE=0
663                 eval "$(list_resolv -l "$@" | replace | parse_resolv)"
664         fi
665         if [ -n "${name_servers_append}${search_domains_append}" ]; then
666                 eval "$(echo_append | parse_resolv)"
667         fi
668
669         # Ensure that we only list each domain once
670         newdomains=
671         for d in $DOMAINS; do
672                 dn="${d%%:*}"
673                 list_remove domain_blacklist "$dn" >/dev/null || continue
674                 case " $newdomains" in
675                 *" ${dn}:"*) continue;;
676                 esac
677                 newns=
678                 for nd in $DOMAINS; do
679                         if [ "$dn" = "${nd%%:*}" ]; then
680                                 ns="${nd#*:}"
681                                 while [ -n "$ns" ]; do
682                                         case ",$newns," in
683                                         *,${ns%%,*},*) ;;
684                                         *) list_remove name_server_blacklist \
685                                                 "${ns%%,*}" >/dev/null \
686                                         && newns="$newns${newns:+,}${ns%%,*}";;
687                                         esac
688                                         [ "$ns" = "${ns#*,}" ] && break
689                                         ns="${ns#*,}"
690                                 done
691                         fi
692                 done
693                 if [ -n "$newns" ]; then
694                         newdomains="$newdomains${newdomains:+ }$dn:$newns"
695                 fi
696         done
697         DOMAIN="$(list_remove domain_blacklist $DOMAIN)"
698         SEARCH="$(uniqify $SEARCH)"
699         SEARCH="$(list_remove domain_blacklist $SEARCH)"
700         NAMESERVERS="$(uniqify $NAMESERVERS)"
701         NAMESERVERS="$(list_remove name_server_blacklist $NAMESERVERS)"
702         LOCALNAMESERVERS="$(uniqify $LOCALNAMESERVERS)"
703         LOCALNAMESERVERS="$(list_remove name_server_blacklist $LOCALNAMESERVERS)"
704         echo "DOMAIN='$DOMAIN'"
705         echo "SEARCH='$SEARCH'"
706         echo "NAMESERVERS='$NAMESERVERS'"
707         echo "LOCALNAMESERVERS='$LOCALNAMESERVERS'"
708         echo "DOMAINS='$newdomains'"
709 }
710
711 force=false
712 VFLAG=
713 while getopts a:C:c:Dd:fhIilm:pRruvVx OPT; do
714         case "$OPT" in
715         f) force=true;;
716         h) usage;;
717         m) IF_METRIC="$OPTARG";;
718         p) IF_PRIVATE=1;;
719         V)
720                 VFLAG=1
721                 if [ "$local_nameservers" = \
722                     "127.* 0.0.0.0 255.255.255.255 ::1" ]
723                 then
724                         local_nameservers=
725                 fi
726                 ;;
727         x) IF_EXCLUSIVE=1;;
728         '?') exit 1;;
729         *) cmd="$OPT"; iface="$OPTARG";;
730         esac
731 done
732 shift $(($OPTIND - 1))
733 args="$iface${iface:+ }$*"
734
735 # -I inits the state dir
736 if [ "$cmd" = I ]; then
737         if [ -d "$VARDIR" ]; then
738                 rm -rf "$VARDIR"/*
739         fi
740         exit $?
741 fi
742
743 # -D ensures that the listed config file base dirs exist
744 if [ "$cmd" = D ]; then
745         config_mkdirs "$@"
746         exit $?
747 fi
748
749 # -l lists our resolv files, optionally for a specific interface
750 if [ "$cmd" = l ] || [ "$cmd" = i ]; then
751         ALLIFACES=true
752         list_resolv "$cmd" "$args"
753         exit $?
754 fi
755 ALLIFACES=false
756
757 # Restart a service or echo the command to restart a service
758 if [ "$cmd" = r ] || [ "$cmd" = R ]; then
759         detect_init || exit 1
760         if [ "$cmd" = r ]; then
761                 set -- $args
762                 eval "$RESTARTCMD"
763         else
764                 echo "$RESTARTCMD" |
765                         sed -e '/^$/d' -e 's/^                  //g'
766         fi
767         exit $?
768 fi
769
770 # Not normally needed, but subscribers should be able to run independently
771 if [ "$cmd" = v ] || [ -n "$VFLAG" ]; then
772         make_vars "$iface"
773         exit $?
774 fi
775
776 # Test that we have valid options
777 case "$cmd" in
778 a|d|C|c)
779         if [ -z "$iface" ]; then
780                 error_exit "Interface not specified"
781         fi
782         ;;
783 u)      ;;
784 *)
785         if [ -n "$cmd" ] && [ "$cmd" != h ]; then
786                 error_exit "Unknown option $cmd"
787         fi
788         usage
789         ;;
790 esac
791
792 if [ "$cmd" = a ]; then
793         for x in '/' \\ ' ' '*'; do
794                 case "$iface" in
795                 *[$x]*) error_exit "$x not allowed in interface name";;
796                 esac
797         done
798         for x in '.' '-' '~'; do
799                 case "$iface" in
800                 [$x]*) error_exit \
801                         "$x not allowed at start of interface name";;
802                 esac
803         done
804         [ "$cmd" = a ] && [ -t 0 ] && error_exit "No file given via stdin"
805 fi
806
807 if [ ! -d "$VARDIR" ]; then
808         if [ -L "$VARDIR" ]; then
809                 dir="$(readlink "$VARDIR")"
810                 # link maybe relative
811                 cd "${VARDIR%/*}"
812                 if ! mkdir -m 0755 -p "$dir"; then
813                         error_exit "Failed to create needed" \
814                                 "directory $dir"
815                 fi
816         else
817                 if ! mkdir -m 0755 -p "$VARDIR"; then
818                         error_exit "Failed to create needed" \
819                                 "directory $VARDIR"
820                 fi
821         fi
822 fi
823
824 if [ ! -d "$IFACEDIR" ]; then
825         mkdir -m 0755 -p "$IFACEDIR" || \
826                 error_exit "Failed to create needed directory $IFACEDIR"
827         if [ "$cmd" = d ]; then
828                 # Provide the same error messages as below
829                 if ! ${force}; then
830                         cd "$IFACEDIR"
831                         for i in $args; do
832                                 warn "No resolv.conf for interface $i"
833                         done
834                 fi
835                 ${force}
836                 exit $?
837         fi
838 fi
839
840 # An interface was added, changed, deleted or a general update was called.
841 # Due to exclusivity we need to ensure that this is an atomic operation.
842 # Our subscribers *may* need this as well if the init system is sub par.
843 # As such we spinlock at this point as best we can.
844 # We don't use flock(1) because it's not widely available and normally resides
845 # in /usr which we do our very best to operate without.
846 [ -w "$VARDIR" ] || error_exit "Cannot write to $LOCKDIR"
847 : ${lock_timeout:=10}
848 : ${clear_nopids:=5}
849 have_pid=false
850 had_pid=false
851 while true; do
852         if mkdir "$LOCKDIR" 2>/dev/null; then
853                 trap 'rm -rf "$LOCKDIR";' EXIT
854                 trap 'rm -rf "$LOCKDIR"; exit 1' INT QUIT ABRT SEGV ALRM TERM
855                 echo $$ >"$LOCKDIR/pid"
856                 break
857         fi
858         pid=$(cat "$LOCKDIR/pid" 2>/dev/null)
859         if [ "$pid" -gt 0 ] 2>/dev/null; then
860                 have_pid=true
861                 had_pid=true
862         else
863                 have_pid=false
864                 clear_nopids=$(($clear_nopids - 1))
865                 if [ "$clear_nopids" -le 0 ]; then
866                         warn "not seen a pid, clearing lock directory"
867                         rm -rf "$LOCKDIR"
868                 else
869                         lock_timeout=$(($lock_timeout - 1))
870                         sleep 1
871                 fi
872                 continue
873         fi
874         if $have_pid && ! kill -0 "$pid"; then
875                 warn "clearing stale lock pid $pid"
876                 rm -rf "$LOCKDIR"
877                 continue
878         fi
879         lock_timeout=$(($lock_timeout - 1))
880         if [ "$lock_timeout" -le 0 ]; then
881                 if $have_pid; then
882                         error_exit "timed out waiting for lock from pid $pid"
883                 else
884                         if $had_pid; then
885                                 error_exit "timed out waiting for lock" \
886                                         "from some pids"
887                         else
888                                 error_exit "timed out waiting for lock"
889                         fi
890                 fi
891         fi
892         sleep 1
893 done
894 unset have_pid had_pid clear_nopids
895
896 case "$cmd" in
897 a)
898         # Read resolv.conf from stdin
899         resolv="$(cat)"
900         changed=false
901         changedfile=false
902         # If what we are given matches what we have, then do nothing
903         if [ -e "$IFACEDIR/$iface" ]; then
904                 if [ "$(echo "$resolv")" != \
905                         "$(cat "$IFACEDIR/$iface")" ]
906                 then
907                         changed=true
908                         changedfile=true
909                 fi
910         else
911                 changed=true
912                 changedfile=true
913         fi
914
915         # Set metric and private before creating the interface resolv.conf file
916         # to ensure that it will have the correct flags
917         [ ! -d "$METRICDIR" ] && mkdir "$METRICDIR"
918         oldmetric="$METRICDIR/"*" $iface"
919         newmetric=
920         if [ -n "$IF_METRIC" ]; then
921                 # Pad metric to 6 characters, so 5 is less than 10
922                 while [ ${#IF_METRIC} -le 6 ]; do
923                         IF_METRIC="0$IF_METRIC"
924                 done
925                 newmetric="$METRICDIR/$IF_METRIC $iface"
926         fi
927         rm -f "$METRICDIR/"*" $iface"
928         [ "$oldmetric" != "$newmetric" ] &&
929             [ "$oldmetric" != "$METRICDIR/* $iface" ] &&
930                 changed=true
931         [ -n "$newmetric" ] && echo " " >"$newmetric"
932
933         case "$IF_PRIVATE" in
934         [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1)
935                 if [ ! -d "$PRIVATEDIR" ]; then
936                         [ -e "$PRIVATEDIR" ] && rm "$PRIVATEDIR"
937                         mkdir "$PRIVATEDIR"
938                 fi
939                 [ -e "$PRIVATEDIR/$iface" ] || changed=true
940                 [ -d "$PRIVATEDIR" ] && echo " " >"$PRIVATEDIR/$iface"
941                 ;;
942         *)
943                 if [ -e "$PRIVATEDIR/$iface" ]; then
944                         rm -f "$PRIVATEDIR/$iface"
945                         changed=true
946                 fi
947                 ;;
948         esac
949
950         oldexcl=
951         for x in "$EXCLUSIVEDIR/"*" $iface"; do
952                 if [ -f "$x" ]; then
953                         oldexcl="$x"
954                         break
955                 fi
956         done
957         case "$IF_EXCLUSIVE" in
958         [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1)
959                 if [ ! -d "$EXCLUSIVEDIR" ]; then
960                         [ -e "$EXCLUSIVEDIR" ] && rm "$EXCLUSIVEDIR"
961                         mkdir "$EXCLUSIVEDIR"
962                 fi
963                 cd "$EXCLUSIVEDIR"
964                 for x in *; do
965                         [ -f "$x" ] && break
966                 done
967                 if [ "${x#* }" != "$iface" ]; then
968                         if [ "$x" = "${x% *}" ]; then
969                                 x=10000000
970                         else
971                                 x="${x% *}"
972                         fi
973                         if [ "$x" = "0000000" ]; then
974                                 warn "exclusive underflow"
975                         else
976                                 x=$(($x - 1))
977                         fi
978                         if [ -d "$EXCLUSIVEDIR" ]; then
979                                 echo " " >"$EXCLUSIVEDIR/$x $iface"
980                         fi
981                         changed=true
982                 fi
983                 ;;
984         *)
985                 if [ -f "$oldexcl" ]; then
986                         rm -f "$oldexcl"
987                         changed=true
988                 fi
989                 ;;
990         esac
991
992         if $changedfile; then
993                 printf "%s\n" "$resolv" >"$IFACEDIR/$iface" || exit $?
994         elif ! $changed; then
995                 exit 0
996         fi
997         unset changed changedfile oldmetric newmetric x oldexcl
998         ;;
999
1000 d)
1001         # Delete any existing information about the interface
1002         cd "$IFACEDIR"
1003         changed=false
1004         for i in $args; do
1005                 if [ -e "$i" ]; then
1006                         changed=true
1007                 elif ! ${force}; then
1008                         warn "No resolv.conf for interface $i"
1009                 fi
1010                 rm -f "$i" "$METRICDIR/"*" $i" \
1011                         "$PRIVATEDIR/$i" \
1012                         "$EXCLUSIVEDIR/"*" $i" || exit $?
1013         done
1014
1015         if ! $changed; then
1016                 # Set the return code based on the forced flag
1017                 $force
1018                 exit $?
1019         fi
1020         unset changed i
1021         ;;
1022
1023 C)
1024         # Mark interface as deprecated
1025         [ ! -d "$DEPRECATEDDIR" ] && mkdir "$DEPRECATEDDIR"
1026         cd "$DEPRECATEDDIR"
1027         changed=false
1028         for i in $args; do
1029                 if [ ! -e "$i" ]; then
1030                         changed=true
1031                         echo " " >"$i" || exit $?
1032                 fi
1033         done
1034         $changed || exit 0
1035         unset changed i
1036         ;;
1037
1038 c)
1039         # Mark interface as active
1040         if [ -d "$DEPRECATEDDIR" ]; then
1041                 cd "$DEPRECATEDDIR"
1042                 changed=false
1043                 for i in $args; do
1044                         if [ -e "$i" ]; then
1045                                 changed=true
1046                                 rm "$i" || exit $?
1047                         fi
1048                 done
1049                 $changed || exit 0
1050                 unset changed i
1051         fi
1052         ;;
1053 esac
1054
1055 case "${resolvconf:-YES}" in
1056 [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) ;;
1057 *) exit 0;;
1058 esac
1059
1060 # Try and detect a suitable init system for our scripts
1061 detect_init
1062 export RESTARTCMD RCDIR _NOINIT_WARNED
1063
1064 eval "$(make_vars)"
1065 export RESOLVCONF DOMAINS SEARCH NAMESERVERS LOCALNAMESERVERS
1066 : ${list_resolv:=list_resolv -l}
1067 retval=0
1068
1069 # Run scripts in the same directory resolvconf is run from
1070 # in case any scripts accidentally dump files in the wrong place.
1071 cd "$_PWD"
1072 for script in "$LIBEXECDIR"/*; do
1073         if [ -f "$script" ]; then
1074                 eval script_enabled="\$${script##*/}"
1075                 case "${script_enabled:-YES}" in
1076                 [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) ;;
1077                 *) continue;;
1078                 esac
1079                 if [ -x "$script" ]; then
1080                         "$script" "$cmd" "$iface"
1081                 else
1082                         (set -- "$cmd" "$iface"; . "$script")
1083                 fi
1084                 retval=$(($retval + $?))
1085         fi
1086 done
1087 exit $retval