We need to process dynamic interfaces without metrics before ones
[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 "*)
124                         SEARCH="${LINE#* }"
125                         ;;
126                 "search "*)
127                         SEARCH="${LINE#* }"
128                         ;;
129                 *)
130                         if [ -z "${LINE}" ]; then
131                                 if [ -n "${NS}" -a -n "${SEARCH}" ]; then
132                                         NEWNS=
133                                         for N in ${NS}; do
134                                                 NEWNS="${NEWNS}${NEWNS:+,}${N}"
135                                         done
136                                         DOMAINS=
137                                         for D in ${SEARCH}; do
138                                                 DOMAINS="${DOMAINS}${DOMAINS:+ }${D}:${NEWNS}"
139                                         done
140                                         echo "DOMAINS=\"\${DOMAINS} ${DOMAINS}\""
141                                 fi
142                                 echo "SEARCH=\"\${SEARCH} ${SEARCH}\""
143                                 if [ ! -e "${PRIVATEDIR}/${IFACE}" ]; then
144                                         echo "NAMESERVERS=\"\${NAMESERVERS} ${NS}\""
145                                 fi
146                                 NS=
147                                 SEARCH=
148                                 PRIVATE=false
149                                 NEW=true
150                         fi
151                         ;;
152                 esac
153         done
154 }
155
156 uniqify()
157 {
158         local result=
159         while [ -n "$1" ]; do
160                 case " ${result} " in
161                 *" $1 "*);;
162                 *) result="${result} $1";;
163                 esac
164                 shift
165         done
166         echo "${result# *}"
167 }
168
169 FORCE=false
170 while getopts a:d:fhilm:ps:uv OPT; do
171         case "${OPT}" in
172         f) FORCE=true;;
173         h) usage;;
174         m) IF_METRIC="${OPTARG}";;
175         p) IF_PRIVATE=1;;
176         s) CMD=s; SERVICE="${OPTARG}";;
177         '?') ;;
178         *) CMD="${OPT}"; IFACE="${OPTARG}";;
179         esac
180 done
181 shift $((${OPTIND} - 1))
182 ARGS="${IFACE}${IFACE:+ }$@"
183
184 # We do our service restarting here so that our subscribers don't have to know
185 # about the OS's init system.
186 if [ "${CMD}" = "s" ]; then
187         if [ -n "$1" ]; then
188                 ACTION="$1"
189                 shift
190         fi
191         [ -z "${ACTION}" ] && usage "Action not specified"
192
193         # If restarting check if service is running or not if we can
194         if [ "${ACTION}" = "restart" ]; then
195                 if [ -s /var/run/"${SERVICE}".pid ]; then
196                         kill -0 $(cat /var/run/"${SERVICE}".pid) 2>/dev/null
197                 elif [ -s /var/run/"${SERVICE}"/"${SERVICE}".pid ]; then
198                         kill -0 $(cat /var/run/"${SERVICE}"/"${SERVICE}".pid) \
199                                 2>/dev/null
200                 elif [ -s /var/run/"${SERVICE}"/pid ]; then
201                         kill -0 $(cat /var/run/"${SERVICE}"/pid) 2>/dev/null
202                 else
203                         false
204                 fi
205                 # Service not running, so don't restart
206                 [ $? != 0 ] && exit 0
207         fi      
208         if [ -x /sbin/service ]; then
209                 service "${SERVICE}" "${ACTION}" "$@" 
210         elif [ -x /etc/init.d/"${SERVICE}" -a -x /sbin/runscript ]; then
211                 if [ "${ACTION}" = "restart" ]; then
212                         /etc/init.d/"${SERVICE}" --quiet --nodeps \
213                                 conditionalrestart "$@"
214                 else
215                         /etc/init.d/"${SERVICE}" --quiet --nodeps \
216                                 "${ACTION}" "$@"
217                 fi
218         elif [ -x /etc/init.d/"${SERVICE}" ]; then
219                 /etc/init.d/"${SERVICE}" "${ACTION}" "$@"
220         elif [ -x /etc/rc.d/"${SERVICE}" ]; then
221                 /etc/rc.d/"${SERVICE}" "${ACTION}" "$@" 
222         elif [ -x /etc/rc.d/rc."${SERVICE}" ]; then
223                 /etc/rc.d/rc."${SERVICE}" "${ACTION}" "$@"
224         else
225                 error_exit "Don't know how to interact with services on" \
226                         "this platform"
227         fi
228         exit $?
229 fi
230
231 # -l lists our resolv files, optionally for a specific interface
232 if [ "${CMD}" = "l" -o "${CMD}" = "i" ]; then
233         [ -d "${IFACEDIR}" ] || exit 0
234
235         REPORT=false
236         # If we have an interface ordering list, then use that.
237         # It works by just using pathname expansion in the interface directory.
238         if [ -n "${ARGS}" ]; then
239                 LIST="${ARGS}"
240                 ${FORCE} || REPORT=true
241         else
242                 cd "${IFACEDIR}"
243                 for LST in ${interface_order}; do
244                         [ -e "${LST}" ] && LIST="${LIST} ${LST}"
245                 done
246                 for DYN in ${dynamic_order}; do
247                         if [ -e "${DYN}" -a ! -e "${METRICDIR}/"*" ${DYN}" ]
248                         then
249                                 LIST="${LIST} ${DYN}"
250                         fi
251                 done
252                 if [ -d "${METRICDIR}" ]; then
253                         cd "${METRICDIR}"
254                         for METRIC in *; do
255                                 LIST="${LIST} ${METRIC#* }"
256                         done
257                 fi
258                 LIST="${LIST} *"
259         fi
260
261         RETVAL=0
262         cd "${IFACEDIR}"
263         for IFACE in $(uniqify ${LIST}); do
264                 # Only list interfaces which we really have
265                 if ! [ -e "${IFACE}" ]; then
266                         if ${REPORT}; then
267                                 echo "No resolv.conf for interface" \
268                                         "${IFACE}" >&2
269                                 RETVAL=$((${RETVAL} + 1))
270                         fi
271                         continue
272                 fi
273                 
274                 if [ "${CMD}" = "i" ]; then
275                         printf "${IFACE} "
276                 else
277                         echo_resolv "${IFACE}"
278                 fi
279         done
280         [ "${CMD}" = "i" ] && echo
281         exit ${RETVAL} 
282 fi
283
284 if [ "${CMD}" = "v" ]; then
285         eval "$("${ARGV0}" -l "${IFACE}" | parse_resolv)"
286
287         # Ensure that we only list each domain once
288         NEWDOMAINS=
289         for D in ${DOMAINS}; do
290                 DN="${D%%:*}"
291                 case " ${NEWDOMAINS}" in
292                 *" ${DN}:"*) continue;;
293                 esac
294                 DONE="${DONE} ${DN}"
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                                         NEWNS="${NEWNS}${NEWNS:+,}${NS%%,*}"
302                                         [ "${NS}" = "${NS#*,}" ] && break
303                                         NS="${NS#*,}"
304                                 done
305                         fi
306                 done
307                 NEWDOMAINS="${NEWDOMAINS}${NEWNS}"
308         done
309         echo "DOMAINS='${NEWDOMAINS}'"
310         echo "SEARCH='$(uniqify ${SEARCH})'"
311         echo "NAMESERVERS='$(uniqify ${NAMESERVERS})'"
312         exit 0
313 fi
314
315 # Test that we have valid options
316 if [ "${CMD}" = "a" -o "${CMD}" = "d" ]; then
317         if [ -z "${IFACE}" ]; then
318                 usage "Interface not specified"
319         fi
320 elif [ "${CMD}" != "u" ]; then
321         [ -n "${CMD}" -a "${CMD}" != "h" ] && usage "Unknown option ${CMD}"
322         usage
323 fi
324 if [ "${CMD}" = "a" ]; then
325         for x in '/' \\ ' ' '*'; do
326                 case "${IFACE}" in
327                 *[${x}]*) error_exit "${x} not allowed in interface name";;
328                 esac
329         done
330         for x in '.' '-' '~'; do
331                 case "${IFACE}" in
332                 [${x}]*) error_exit \
333                         "${x} not allowed at start of interface name";;
334                 esac
335         done
336         [ "${CMD}" = "a" -a -t 0 ] && error_exit "No file given via stdin"
337         IFACERESOLV="${IFACEDIR}/${IFACE}"
338 fi
339
340 # Ensure that libdir exists
341 if [ ! -d "${IFACEDIR}" ]; then
342         if [ ! -d "${VARDIR}" ]; then
343                 if [ -L "${VARDIR}" ]; then
344                         DIR="$(readlink "${VARDIR}")"
345                         # Change to /etc as link maybe relative
346                         cd "${VARDIR%/*}"
347                         if ! mkdir -m 0755 -p "${DIR}"; then
348                                 error_exit "Failed to create needed" \
349                                         "directory ${DIR}"
350                         fi
351                 else
352                         if ! mkdir -m 0755 -p "${VARDIR}"; then
353                                 error_exit "Failed to create needed" \
354                                         "directory ${VARDIR}"
355                         fi
356                 fi
357         fi
358         mkdir -m 0755 -p "${IFACEDIR}" || \
359                 error_exit "Failed to create needed directory ${IFACEDIR}"
360 else
361         # Delete any existing information about the interface
362         if [ "${CMD}" = "d" ]; then
363                 cd "${IFACEDIR}"
364                 for ARG in ${ARGS}; do
365                         if [ "${CMD}" = "d" -a ! -e "${ARG}" ]; then
366                                 ${FORCE} && continue
367                                 error_exit "No resolv.conf for" \
368                                         "interface ${ARG}"
369                         fi
370                         rm -f "${ARG}" "${METRICDIR}/"*" ${ARG}" \
371                                 "${PRIVATEDIR}/${ARG}" || exit $?
372                 done
373         fi
374 fi
375
376 if [ "${CMD}" = "a" ]; then
377         # Read resolv.conf from stdin
378         RESOLV="$(cat)\n"
379         # If what we are given matches what we have, then do nothing
380         if [ -e "${IFACEDIR}/${IFACE}" ]; then
381                 if [ "$(printf "${RESOLV}")" = \
382                         "$(cat "${IFACEDIR}/${IFACE}")" ]
383                 then
384                         exit 0
385                 fi
386                 rm "${IFACEDIR}/${IFACE}"
387         fi
388         printf "${RESOLV}" >"${IFACEDIR}/${IFACE}" || exit $?
389         [ ! -d "${METRICDIR}" ] && mkdir "${METRICDIR}"
390         rm -f "${METRICDIR}/"*" ${IFACE}"
391         if [ -n "${IF_METRIC}" ]; then
392                 # Pad metric to 6 characters, so 5 is less than 10
393                 while [ ${#IF_METRIC} -le 6 ]; do
394                         IF_METRIC="0${IF_METRIC}"
395                 done
396                 echo " " >"${METRICDIR}/${IF_METRIC} ${IFACE}"
397         fi
398         case "${IF_PRIVATE}" in
399         [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1)
400                 if [ ! -d "${PRIVATEDIR}" ]; then
401                         [ -e "${PRIVATEDIR}" ] && rm "${PRIVATEDIR}"
402                         mkdir "${PRIVATEDIR}"
403                 fi
404                 [ -d "${PRIVATEDIR}" ] && echo " " >"${PRIVATEDIR}/${IFACE}"
405                 ;;
406         *)
407                 if [ -e "${PRIVATEDIR}/${IFACE}" ]; then
408                         rm -f "${PRIVATEDIR}/${IFACE}"
409                 fi
410                 ;;
411         esac
412 fi
413
414 RETVAL=0
415 for SCRIPT in "${LIBEXECDIR}"/*; do
416         [ -f "${SCRIPT}" -a -x "${SCRIPT}" ] || continue
417         RESOLVCONF="${ARGV0}" "${SCRIPT}" "${CMD}" "${IFACE}"
418         RETVAL=$((${RETVAL} + $?))
419 done
420 exit ${RETVAL}