Release openresolv-3.5.2
[openresolv] / resolvconf.in
1 #!/bin/sh
2 # Copyright (c) 2007-2012 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 SYSCONFDIR=@SYSCONFDIR@
29 LIBEXECDIR=@LIBEXECDIR@
30 VARDIR=@VARDIR@
31 # Support original resolvconf configuration layout
32 # as well as the openresolv config file
33 if [ -f "$SYSCONFDIR"/resolvconf.conf ]; then
34         . "$SYSCONFDIR"/resolvconf.conf
35         [ -n "$state_dir" ] && VARDIR="$state_dir"
36 elif [ -d "$SYSCONFDIR/resolvconf" ]; then
37         SYSCONFDIR="$SYSCONFDIR/resolvconf"
38         if [ -f "$SYSCONFDIR"/interface-order ]; then
39                 interface_order="$(cat "$SYSCONFDIR"/interface-order)"
40         fi
41 fi
42 IFACEDIR="$VARDIR/interfaces"
43 METRICDIR="$VARDIR/metrics"
44 PRIVATEDIR="$VARDIR/private"
45
46 : ${dynamic_order:=tap[0-9]* tun[0-9]* vpn vpn[0-9]* ppp[0-9]* ippp[0-9]*}
47 : ${interface_order:=lo lo[0-9]*}
48 : ${name_server_blacklist:=0.0.0.0}
49
50 error_exit()
51 {
52         echo "$*" >&2
53         exit 1
54 }
55
56 usage()
57 {
58         cat <<-EOF
59         Usage: ${RESOLVCONF##*/} [options]
60
61         Inform the system about any DNS updates.
62
63         Options:
64           -a \$INTERFACE    Add DNS information to the specified interface
65                            (DNS supplied via stdin in resolv.conf format)
66           -m metric        Give the added DNS information a metric
67           -p               Mark the interface as private
68           -d \$INTERFACE    Delete DNS information from the specified interface
69           -f               Ignore non existant interfaces
70           -I               Init the state dir
71           -u               Run updates from our current DNS information
72           -l [\$PATTERN]    Show DNS information, optionally from interfaces
73                            that match the specified pattern
74           -i [\$PATTERN]    Show interfaces that have supplied DNS information
75                    optionally from interfaces that match the specified
76                    pattern
77           -v [\$PATTERN]    echo NEWDOMAIN, NEWSEARCH and NEWNS variables to
78                            the console
79           -h               Show this help cruft
80         EOF
81         [ -z "$1" ] && exit 0
82         echo
83         error_exit "$*"
84 }
85
86 echo_resolv()
87 {
88         local line= OIFS="$IFS"
89
90         [ -n "$1" -a -e "$IFACEDIR/$1" ] || return 1
91         echo "# resolv.conf from $1"
92         # Our variable maker works of the fact each resolv.conf per interface
93         # is separated by blank lines.
94         # So we remove them when echoing them.
95         while read -r line; do
96                 IFS="$OIFS"
97                 if [ -n "$line" ]; then
98                         # We need to set IFS here to preserve any whitespace
99                         IFS=''
100                         printf "%s\n" "$line"
101                 fi
102         done < "$IFACEDIR/$1"
103         echo
104         IFS="$OIFS"
105 }
106
107 # Parse resolv.conf's and make variables
108 # for domain name servers, search name servers and global nameservers
109 parse_resolv()
110 {
111         local line= ns= ds= search= d= n= newns=
112         local new=true iface= private=false p= domain=
113
114         echo "DOMAIN="
115         echo "DOMAINS="
116         echo "SEARCH=\"$search_domains\""
117         # let our subscribers know about global nameservers
118         for n in $name_servers; do
119                 case "$n" in
120                 127.*|0.0.0.0|255.255.255.255|::1) :;;
121                 *) newns="$newns${newns:+ }$n";;
122                 esac
123         done
124         echo "NAMESERVERS=\"$newns\""
125         echo "LOCALNAMESERVERS="
126         newns=
127
128         while read -r line; do
129                 case "$line" in
130                 "# resolv.conf from "*)
131                         if ${new}; then
132                                 iface="${line#\# resolv.conf from *}"
133                                 new=false
134                                 if [ -e "$PRIVATEDIR/$iface" ]; then
135                                         private=true
136                                 else
137                                         # Allow expansion
138                                         cd "$IFACEDIR"
139                                         private=false
140                                         for p in $private_interfaces; do
141                                                 if [ "$p" = "$iface" ]; then
142                                                         private=true
143                                                         break
144                                                 fi
145                                         done
146                                 fi
147                         fi
148                         ;;
149                 "nameserver "*)
150                         case "${line#* }" in
151                         127.*|0.0.0.0|255.255.255.255|::1)
152                                 echo "LOCALNAMESERVERS=\"\$LOCALNAMESERVERS ${line#* }\""
153                                 continue
154                                 ;;
155                         esac
156                         ns="$ns${line#* } "
157                         ;;
158                 "domain "*)
159                         if [ -z "$domain" ]; then
160                                 domain="${line#* }"
161                                 echo "DOMAIN=\"$domain\""
162                         fi
163                         search="${line#* }"
164                         ;;
165                 "search "*)
166                         search="${line#* }"
167                         ;;
168                 *)
169                         [ -n "$line" ] && continue
170                         if [ -n "$ns" -a -n "$search" ]; then
171                                 newns=
172                                 for n in $ns; do
173                                         newns="$newns${newns:+,}$n"
174                                 done
175                                 ds=
176                                 for d in $search; do
177                                         ds="$ds${ds:+ }$d:$newns"
178                                 done
179                                 echo "DOMAINS=\"\$DOMAINS $ds\""
180                         fi
181                         echo "SEARCH=\"\$SEARCH $search\""
182                         if ! $private; then
183                                 echo "NAMESERVERS=\"\$NAMESERVERS $ns\""
184                         fi
185                         ns=
186                         search=
187                         new=true
188                         ;;
189                 esac
190         done
191 }
192
193 uniqify()
194 {
195         local result=
196         while [ -n "$1" ]; do
197                 case " $result " in
198                 *" $1 "*);;
199                 *) result="$result $1";;
200                 esac
201                 shift
202         done
203         echo "${result# *}"
204 }
205
206 dirname()
207 {
208         local dir= OIFS="$IFS"
209         local IFS=/
210         set -- $@
211         IFS="$OIFS"
212         if [ -n "$1" ]; then
213                 printf %s .
214         else
215                 shift
216         fi
217         while [ -n "$2" ]; do
218                 printf "/%s" "$1"
219                 shift
220         done
221         printf "\n"
222 }
223
224 config_mkdirs()
225 {
226         local e=0 f d
227         for f; do
228                 [ -n "$f" ] || continue
229                 d="$(dirname "$f")"
230                 if [ ! -d "$d" ]; then
231                         if type install >/dev/null 2>&1; then
232                                 install -d "$d" || e=$?
233                         else
234                                 mkdir "$d" || e=$?
235                         fi
236                 fi
237         done
238         return $e
239 }
240
241 list_resolv()
242 {
243         [ -d "$IFACEDIR" ] || return 0
244
245         local report=false list= retval=0 cmd="$1"
246         shift
247
248         # If we have an interface ordering list, then use that.
249         # It works by just using pathname expansion in the interface directory.
250         if [ -n "$1" ]; then
251                 list="$*"
252                 $force || report=true
253         else
254                 cd "$IFACEDIR"
255                 for i in $interface_order; do
256                         [ -e "$i" ] && list="$list $i"
257                 done
258                 for i in $dynamic_order; do
259                         if [ -e "$i" -a ! -e "$METRICDIR/"*" $i" ]; then
260                                 list="$list $i"
261                         fi
262                 done
263                 if [ -d "$METRICDIR" ]; then
264                         cd "$METRICDIR"
265                         for i in *; do
266                                 list="$list ${i#* }"
267                         done
268                 fi
269                 list="$list *"
270         fi
271
272         cd "$IFACEDIR"
273         for i in $(uniqify $list); do
274                 # Only list interfaces which we really have
275                 if ! [ -e "$i" ]; then
276                         if $report; then
277                                 echo "No resolv.conf for interface $i" >&2
278                                 retval=$(($retval + 1))
279                         fi
280                         continue
281                 fi
282                 
283                 if [ "$cmd" = i -o "$cmd" = "-i" ]; then
284                         printf %s "$i "
285                 else
286                         echo_resolv "$i"
287                 fi
288         done
289         [ "$cmd" = i -o "$cmd" = "-i" ] && echo
290         return $retval
291 }
292
293 list_remove() {
294         local list= e= l= result= found= retval=0
295
296         [ -z "$2" ] && return 0
297         eval list=\"\$$1\"
298         shift
299
300         set -f
301         for e; do
302                 found=false
303                 for l in $list; do
304                         case "$e" in
305                         $l) found=true;;
306                         esac
307                         $found && break
308                 done
309                 if $found; then
310                         retval=$(($retval + 1))
311                 else
312                         result="$result $e"
313                 fi
314         done
315         set +f
316         echo "${result# *}"
317         return $retval
318 }
319
320 make_vars()
321 {
322         local newdomains= d= dn= newns= ns=
323         eval "$(list_resolv -l "$@" | parse_resolv)"
324
325         # Ensure that we only list each domain once
326         for d in $DOMAINS; do
327                 dn="${d%%:*}"
328                 list_remove domain_blacklist "$dn" >/dev/null || continue
329                 case " $newdomains" in
330                 *" ${dn}:"*) continue;;
331                 esac
332                 newns=
333                 for nd in $DOMAINS; do
334                         if [ "$dn" = "${nd%%:*}" ]; then
335                                 ns="${nd#*:}"
336                                 while [ -n "$ns" ]; do
337                                         case ",$newns," in
338                                         *,${ns%%,*},*) ;;
339                                         *) list_remove name_server_blacklist \
340                                                 "$ns" >/dev/null \
341                                         && newns="$newns${newns:+,}${ns%%,*}";;
342                                         esac
343                                         [ "$ns" = "${ns#*,}" ] && break
344                                         ns="${ns#*,}"
345                                 done
346                         fi
347                 done
348                 if [ -n "$newns" ]; then
349                         newdomains="$newdomains${newdomains:+ }$dn:$newns"
350                 fi
351         done
352         DOMAIN="$(list_remove domain_blacklist $DOMAIN)"
353         SEARCH="$(uniqify $SEARCH)"
354         SEARCH="$(list_remove domain_blacklist $SEARCH)"
355         NAMESERVERS="$(uniqify $NAMESERVERS)"
356         NAMESERVERS="$(list_remove name_server_blacklist $NAMESERVERS)"
357         LOCALNAMESERVERS="$(uniqify $LOCALNAMESERVERS)"
358         LOCALNAMESERVERS="$(list_remove name_server_blacklist $LOCALNAMESERVERS)"
359         echo "DOMAIN='$DOMAIN'"
360         echo "SEARCH='$SEARCH'"
361         echo "NAMESERVERS='$NAMESERVERS'"
362         echo "LOCALNAMESERVERS='$LOCALNAMESERVERS'"
363         echo "DOMAINS='$newdomains'"
364 }
365
366 force=false
367 while getopts a:Dd:fhIilm:puv OPT; do
368         case "$OPT" in
369         f) force=true;;
370         h) usage;;
371         m) IF_METRIC="$OPTARG";;
372         p) IF_PRIVATE=1;;
373         '?') ;;
374         *) cmd="$OPT"; iface="$OPTARG";;
375         esac
376 done
377 shift $(($OPTIND - 1))
378 args="$iface${iface:+ }$*"
379
380 # -I inits the state dir
381 if [ "$cmd" = I ]; then
382         if [ -d "$VARDIR" ]; then
383                 rm -rf "$VARDIR"/*
384         fi
385         exit $?
386 fi
387
388 # -D ensures that the listed config file base dirs exist
389 if [ "$cmd" = D ]; then
390         config_mkdirs "$@"
391         exit $?
392 fi
393
394 # -l lists our resolv files, optionally for a specific interface
395 if [ "$cmd" = l -o "$cmd" = i ]; then
396         list_resolv "$cmd" "$args"
397         exit $?
398 fi
399
400 # Not normally needed, but subscribers should be able to run independently
401 if [ "$cmd" = v ]; then
402         make_vars "$iface"
403         exit $?
404 fi
405
406 # Test that we have valid options
407 if [ "$cmd" = a -o "$cmd" = d ]; then
408         if [ -z "$iface" ]; then
409                 usage "Interface not specified"
410         fi
411 elif [ "$cmd" != u ]; then
412         [ -n "$cmd" -a "$cmd" != h ] && usage "Unknown option $cmd"
413         usage
414 fi
415 if [ "$cmd" = a ]; then
416         for x in '/' \\ ' ' '*'; do
417                 case "$iface" in
418                 *[$x]*) error_exit "$x not allowed in interface name";;
419                 esac
420         done
421         for x in '.' '-' '~'; do
422                 case "$iface" in
423                 [$x]*) error_exit \
424                         "$x not allowed at start of interface name";;
425                 esac
426         done
427         [ "$cmd" = a -a -t 0 ] && error_exit "No file given via stdin"
428 fi
429
430 if [ ! -d "$IFACEDIR" ]; then
431         if [ ! -d "$VARDIR" ]; then
432                 if [ -L "$VARDIR" ]; then
433                         dir="$(readlink "$VARDIR")"
434                         # link maybe relative
435                         cd "${VARDIR%/*}"
436                         if ! mkdir -m 0755 -p "$dir"; then
437                                 error_exit "Failed to create needed" \
438                                         "directory $dir"
439                         fi
440                 else
441                         if ! mkdir -m 0755 -p "$VARDIR"; then
442                                 error_exit "Failed to create needed" \
443                                         "directory $VARDIR"
444                         fi
445                 fi
446         fi
447         mkdir -m 0755 -p "$IFACEDIR" || \
448                 error_exit "Failed to create needed directory $IFACEDIR"
449 else
450         # Delete any existing information about the interface
451         if [ "$cmd" = d ]; then
452                 cd "$IFACEDIR"
453                 for i in $args; do
454                         if [ "$cmd" = d -a ! -e "$i" ]; then
455                                 $force && continue
456                                 error_exit "No resolv.conf for" \
457                                         "interface $i"
458                         fi
459                         rm -f "$i" "$METRICDIR/"*" $i" \
460                                 "$PRIVATEDIR/$i" || exit $?
461                 done
462         fi
463 fi
464
465 if [ "$cmd" = a ]; then
466         # Read resolv.conf from stdin
467         resolv="$(cat)"
468         changed=false
469         # If what we are given matches what we have, then do nothing
470         if [ -e "$IFACEDIR/$iface" ]; then
471                 if [ "$(echo "$resolv")" != \
472                         "$(cat "$IFACEDIR/$iface")" ]
473                 then
474                         rm "$IFACEDIR/$iface"
475                         changed=true
476                 fi
477         else
478                 changed=true
479         fi
480         if $changed; then
481                 echo "$resolv" >"$IFACEDIR/$iface" || exit $?
482         fi
483         [ ! -d "$METRICDIR" ] && mkdir "$METRICDIR"
484         oldmetric="$METRICDIR/"*" $iface"
485         newmetric=
486         if [ -n "$IF_METRIC" ]; then
487                 # Pad metric to 6 characters, so 5 is less than 10
488                 while [ ${#IF_METRIC} -le 6 ]; do
489                         IF_METRIC="0$IF_METRIC"
490                 done
491                 newmetric="$METRICDIR/$IF_METRIC $iface"
492         fi
493         rm -f "$METRICDIR/"*" $iface"
494         [ "$oldmetric" != "$newmetric" -a \
495             "$oldmetric" != "$METRICDIR/* $iface" ] &&
496                 changed=true
497         [ -n "$newmetric" ] && echo " " >"$newmetric"
498         case "$IF_PRIVATE" in
499         [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1)
500                 if [ ! -d "$PRIVATEDIR" ]; then
501                         [ -e "$PRIVATEDIR" ] && rm "$PRIVATEDIR"
502                         mkdir "$PRIVATEDIR"
503                 fi
504                 [ -e "$PRIVATEDIR/$iface" ] || changed=true
505                 [ -d "$PRIVATEDIR" ] && echo " " >"$PRIVATEDIR/$iface"
506                 ;;
507         *)
508                 if [ -e "$PRIVATEDIR/$iface" ]; then
509                         rm -f "$PRIVATEDIR/$iface"
510                         changed=true
511                 fi
512                 ;;
513         esac
514         $changed || exit 0
515         unset changed oldmetric newmetric
516 fi
517
518 eval "$(make_vars)"
519 export RESOLVCONF DOMAINS SEARCH NAMESERVERS LOCALNAMESERVERS
520 : ${list_resolv:=list_resolv -l}
521 retval=0
522 for script in "$LIBEXECDIR"/*; do
523         if [ -f "$script" ]; then
524                 if [ -x "$script" ]; then
525                         "$script" "$cmd" "$iface"
526                 else
527                         (set -- "$cmd" "$iface"; . "$script")
528                 fi
529                 retval=$(($retval + $?))
530         fi
531 done
532 exit $retval