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