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