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