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