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