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