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