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