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