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