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