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