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