We should use lower case for normal variables.
[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 ARGV0="$0"
28 SYSCONFDIR=@SYSCONFDIR@
29 LIBEXECDIR=@LIBEXECDIR@
30 VARDIR=@VARBASE@/run/resolvconf
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="${dynamic_order:-tap[0-9]* tun[0-9]* vpn vpn[0-9]* ppp[0-9]* ippp[0-9]*}"
46 interface_order="${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: ${ARGV0##*/} [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 "$@" ] && 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= domains= search= d= n= newns=
103         local new=true iface=
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                         fi
116                         ;;
117                 "nameserver "*)
118                         case "${line#* }" in
119                         127.*) continue;;
120                         esac
121                         ns="${ns}${line#* } "
122                         ;;
123                 "domain "*|"search "*)
124                         search="${line#* }"
125                         ;;
126                 *)
127                         if [ -z "${line}" ]; then
128                                 if [ -n "${ns}" -a -n "${search}" ]; then
129                                         newns=
130                                         for n in ${ns}; do
131                                                 newns="${newns}${newns:+,}${n}"
132                                         done
133                                         domains=
134                                         for d in ${search}; do
135                                                 domains="${domains}${domains:+ }${d}:${newns}"
136                                         done
137                                         echo "DOMAINS=\"\${DOMAINS} ${domains}\""
138                                 fi
139                                 echo "SEARCH=\"\${SEARCH} ${search}\""
140                                 if [ ! -e "${PRIVATEDIR}/${iface}" ]; then
141                                         echo "NAMESERVERS=\"\${NAMESERVERS} ${ns}\""
142                                 fi
143                                 ns=
144                                 search=
145                                 new=true
146                         fi
147                         ;;
148                 esac
149         done
150 }
151
152 uniqify()
153 {
154         local result=
155         while [ -n "$1" ]; do
156                 case " ${result} " in
157                 *" $1 "*);;
158                 *) result="${result} $1";;
159                 esac
160                 shift
161         done
162         echo "${result# *}"
163 }
164
165 force=false
166 while getopts a:d:fhilm:ps:uv OPT; do
167         case "${OPT}" in
168         f) force=true;;
169         h) usage;;
170         m) IF_METRIC="${OPTARG}";;
171         p) IF_PRIVATE=1;;
172         s) cmd=s; service="${OPTARG}";;
173         '?') ;;
174         *) cmd="${OPT}"; iface="${OPTARG}";;
175         esac
176 done
177 shift $((${OPTIND} - 1))
178 args="${iface}${iface:+ }$@"
179
180 # We do our service restarting here so that our subscribers don't have to know
181 # about the OS's init system.
182 if [ "${cmd}" = "s" ]; then
183         if [ -n "$1" ]; then
184                 action="$1"
185                 shift
186         fi
187         [ -z "${action}" ] && usage "Action not specified"
188
189         # If restarting check if service is running or not if we can
190         if [ "${action}" = "restart" ]; then
191                 if [ -s /var/run/"${service}".pid ]; then
192                         kill -0 $(cat /var/run/"${service}".pid) 2>/dev/null
193                 elif [ -s /var/run/"${service}"/"${service}".pid ]; then
194                         kill -0 $(cat /var/run/"${service}"/"${service}".pid) \
195                                 2>/dev/null
196                 elif [ -s /var/run/"${service}"/pid ]; then
197                         kill -0 $(cat /var/run/"${service}"/pid) 2>/dev/null
198                 else
199                         false
200                 fi
201                 # Service not running, so don't restart
202                 [ $? != 0 ] && exit 0
203         fi      
204         if [ -x /sbin/service ]; then
205                 service "${service}" "${action}" "$@" 
206         elif [ -x /etc/init.d/"${service}" -a -x /sbin/runscript ]; then
207                 if [ "${action}" = "restart" ]; then
208                         /etc/init.d/"${service}" --quiet --nodeps \
209                                 conditionalrestart "$@"
210                 else
211                         /etc/init.d/"${service}" --quiet --nodeps \
212                                 "${action}" "$@"
213                 fi
214         elif [ -x /etc/init.d/"${service}" ]; then
215                 /etc/init.d/"${service}" "${action}" "$@"
216         elif [ -x /etc/rc.d/"${service}" ]; then
217                 /etc/rc.d/"${service}" "${action}" "$@" 
218         elif [ -x /etc/rc.d/rc."${service}" ]; then
219                 /etc/rc.d/rc."${service}" "${action}" "$@"
220         else
221                 error_exit "Don't know how to interact with services on" \
222                         "this platform"
223         fi
224         exit $?
225 fi
226
227 # -l lists our resolv files, optionally for a specific interface
228 if [ "${cmd}" = "l" -o "${cmd}" = "i" ]; then
229         [ -d "${IFACEDIR}" ] || exit 0
230
231         report=false
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 "${args}" ]; then
235                 list="${args}"
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         retval=0
257         cd "${IFACEDIR}"
258         for i in $(uniqify ${list}); do
259                 # Only list interfaces which we really have
260                 if ! [ -e "${i}" ]; then
261                         if ${report}; then
262                                 echo "No resolv.conf for interface ${i}" >&2
263                                 retval=$((${retval} + 1))
264                         fi
265                         continue
266                 fi
267                 
268                 if [ "${cmd}" = "i" ]; then
269                         printf "${i} "
270                 else
271                         echo_resolv "${i}"
272                 fi
273         done
274         [ "${cmd}" = "i" ] && echo
275         exit ${retval} 
276 fi
277
278 if [ "${cmd}" = "v" ]; then
279         eval "$("${ARGV0}" -l "${iface}" | 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                                                 ;;
298                                         esac
299                                         [ "${ns}" = "${ns#*,}" ] && break
300                                         ns="${ns#*,}"
301                                 done
302                         fi
303                 done
304                 newdomains="${newdomains}${newns}"
305         done
306         echo "DOMAINS='${newdomains}'"
307         echo "SEARCH='$(uniqify ${SEARCH})'"
308         echo "NAMESERVERS='$(uniqify ${NAMESERVERS})'"
309         exit 0
310 fi
311
312 # Test that we have valid options
313 if [ "${cmd}" = "a" -o "${cmd}" = "d" ]; then
314         if [ -z "${iface}" ]; then
315                 usage "Interface not specified"
316         fi
317 elif [ "${cmd}" != "u" ]; then
318         [ -n "${cmd}" -a "${cmd}" != "h" ] && usage "Unknown option ${cmd}"
319         usage
320 fi
321 if [ "${cmd}" = "a" ]; then
322         for x in '/' \\ ' ' '*'; do
323                 case "${iface}" in
324                 *[${x}]*) error_exit "${x} not allowed in interface name";;
325                 esac
326         done
327         for x in '.' '-' '~'; do
328                 case "${iface}" in
329                 [${x}]*) error_exit \
330                         "${x} not allowed at start of interface name";;
331                 esac
332         done
333         [ "${cmd}" = "a" -a -t 0 ] && error_exit "No file given via stdin"
334 fi
335
336 # Ensure that libdir exists
337 if [ ! -d "${IFACEDIR}" ]; then
338         if [ ! -d "${VARDIR}" ]; then
339                 if [ -L "${VARDIR}" ]; then
340                         dir="$(readlink "${VARDIR}")"
341                         # link maybe relative
342                         cd "${VARDIR%/*}"
343                         if ! mkdir -m 0755 -p "${dir}"; then
344                                 error_exit "Failed to create needed" \
345                                         "directory ${dir}"
346                         fi
347                 else
348                         if ! mkdir -m 0755 -p "${VARDIR}"; then
349                                 error_exit "Failed to create needed" \
350                                         "directory ${VARDIR}"
351                         fi
352                 fi
353         fi
354         mkdir -m 0755 -p "${IFACEDIR}" || \
355                 error_exit "Failed to create needed directory ${IFACEDIR}"
356 else
357         # Delete any existing information about the interface
358         if [ "${cmd}" = "d" ]; then
359                 cd "${IFACEDIR}"
360                 for i in ${args}; do
361                         if [ "${cmd}" = "d" -a ! -e "${i}" ]; then
362                                 ${force} && continue
363                                 error_exit "No resolv.conf for" \
364                                         "interface ${i}"
365                         fi
366                         rm -f "${i}" "${METRICDIR}/"*" ${i}" \
367                                 "${PRIVATEDIR}/${i}" || exit $?
368                 done
369         fi
370 fi
371
372 if [ "${cmd}" = "a" ]; then
373         # Read resolv.conf from stdin
374         resolv="$(cat)\n"
375         # If what we are given matches what we have, then do nothing
376         if [ -e "${IFACEDIR}/${iface}" ]; then
377                 if [ "$(printf "${resolv}")" = \
378                         "$(cat "${IFACEDIR}/${iface}")" ]
379                 then
380                         exit 0
381                 fi
382                 rm "${IFACEDIR}/${iface}"
383         fi
384         printf "${resolv}" >"${IFACEDIR}/${iface}" || exit $?
385         [ ! -d "${METRICDIR}" ] && mkdir "${METRICDIR}"
386         rm -f "${METRICDIR}/"*" ${iface}"
387         if [ -n "${IF_METRIC}" ]; then
388                 # Pad metric to 6 characters, so 5 is less than 10
389                 while [ ${#IF_METRIC} -le 6 ]; do
390                         IF_METRIC="0${IF_METRIC}"
391                 done
392                 echo " " >"${METRICDIR}/${IF_METRIC} ${iface}"
393         fi
394         case "${IF_PRIVATE}" in
395         [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1)
396                 if [ ! -d "${PRIVATEDIR}" ]; then
397                         [ -e "${PRIVATEDIR}" ] && rm "${PRIVATEDIR}"
398                         mkdir "${PRIVATEDIR}"
399                 fi
400                 [ -d "${PRIVATEDIR}" ] && echo " " >"${PRIVATEDIR}/${iface}"
401                 ;;
402         *)
403                 if [ -e "${PRIVATEDIR}/${iface}" ]; then
404                         rm -f "${PRIVATEDIR}/${iface}"
405                 fi
406                 ;;
407         esac
408 fi
409
410 retval=0
411 for script in "${LIBEXECDIR}"/*; do
412         [ -f "${script}" -a -x "${script}" ] || continue
413         RESOLVCONF="${ARGV0}" "${script}" "${cmd}" "${iface}"
414         retval=$((${retval} + $?))
415 done
416 exit ${retval}