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