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