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