#!/bin/sh # Copyright 2007-2008 Roy Marples # All rights reserved # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ARGV0="$0" SYSCONFDIR=@SYSCONFDIR@ VARDIR=@VARBASE@/run/resolvconf IFACEDIR="${VARDIR}/interfaces" METRICDIR="${VARDIR}/metrics" PRIVATEDIR="${VARDIR}/private" error_exit() { echo "$*" >&2 exit 1 } usage() { cat <<-EOF Usage: ${ARGV0##*/} [options] Inform the system about any DNS updates. Options: -a \$INTERFACE Add DNS information to the specified interface (DNS supplied via stdin in resolv.conf format) -m metric Give the added DNS information a metric -p Mark the interface as private -d \$INTERFACE Delete DNS information from the specified interface -f Ignore non existant interfaces -u Run updates from our current DNS information -l [\$PATTERN] Show DNS information, optionally from interfaces that match the specified pattern -i [\$PATTERN] Show interfaces that have supplied DNS information optionally from interfaces that match the specified pattern -v [\$PATTERN] echo NEWDOMAIN, NEWSEARCH and NEWNS variables to the console -s \$SVC \$CMD Do \$CMD for the system service \$SVC -h Show this help cruft EOF [ -z "$@" ] && exit 0 echo error_exit "$*" } echo_resolv() { local LINE= [ -n "$1" -a -e "${IFACEDIR}/$1" ] || return 1 echo "# resolv.conf from $1" # Our variable maker works of the fact each resolv.conf per interface # is separated by blank lines. # So we remove them when echoing them. while read LINE; do [ -n "${LINE}" ] && echo "${LINE}" done < "${IFACEDIR}/$1" echo } # Return 1 if an interface is private # ie, we only use the listed nameservers for the domain/search path is_private() { [ -e "${PRIVATEDIR}/$1" ] && return 0 if [ -e "${SYSCONFDIR}/private-interfaces" ]; then for IFACE in $(cat "${SYSCONFDIR}/private-interfaces"); do [ "${IFACE}" = "$1" ] && return 0 done else cd "${IFACEDIR}" for IFACE in tap[0-9]* tun[0-9]* vpn vpn[0-9]*; do [ -e "${IFACE}" ] || continue [ "${IFACE}" = "$1" ] && return 0 done fi return 1 } # Parse resolv.conf's and make variables # for domain name servers, search name servers and global nameservers parse_resolv() { local LINE= NS= DOMAINS= SEARCH= D= N= NEWNS= local NEW=true PRIVATE=false echo "DOMAINS=" echo "SEARCH=" echo "NAMESERVERS=" while read LINE; do case "${LINE}" in "# resolv.conf from "*) if ${NEW}; then if is_private "${LINE#\# resolv.conf from *}"; then PRIVATE=true else PRIVATE=false fi NEW=false fi ;; "nameserver "*) case "${LINE#* }" in 127.*) continue;; esac NS="${NS}${LINE#* } " ;; "domain "*) SEARCH="${LINE#* }" ;; "search "*) SEARCH="${LINE#* }" ;; *) if [ -z "${LINE}" ]; then if [ -n "${NS}" -a -n "${SEARCH}" ] && ${PRIVATE}; then unset NEWNS for N in ${NS}; do NEWNS="${NEWNS}${NEWNS:+,}${N}" done DOMAINS= for D in ${SEARCH}; do DOMAINS="${DOMAINS}${DOMAINS:+ }${D}:${NEWNS}" done echo "DOMAINS=\"\${DOMAINS} ${DOMAINS}\"" else echo "NAMESERVERS=\"\${NAMESERVERS} ${NS}\"" fi echo "SEARCH=\"\${SEARCH} ${SEARCH}\"" NS= SEARCH= PRIVATE=false NEW=true fi ;; esac done } uniqify() { local result= while [ -n "$1" ]; do case " ${result} " in *" $1 "*);; *) result="${result} $1";; esac shift done echo "${result# *}" } FORCE=false while getopts a:d:fhilm:ps:uv OPT; do case "${OPT}" in f) FORCE=true;; h) usage;; m) IF_METRIC="${OPTARG}";; p) IF_PRIVATE=1;; s) CMD=s; SERVICE="${OPTARG}";; '?') ;; *) CMD="${OPT}"; IFACE="${OPTARG}";; esac done shift $((${OPTIND} - 1)) ARGS="${IFACE}${IFACE:+ }$@" # We do our service restarting here so that our subscribers don't have to know # about the OS's init system. if [ "${CMD}" = "s" ]; then if [ -n "$1" ]; then ACTION="$1" shift fi [ -z "${ACTION}" ] && usage "Action not specified" # If restarting check if service is running or not if we can if [ "${ACTION}" = "restart" ]; then if [ -s /var/run/"${SERVICE}".pid ]; then kill -0 $(cat /var/run/"${SERVICE}".pid) 2>/dev/null elif [ -s /var/run/"${SERVICE}"/"${SERVICE}".pid ]; then kill -0 $(cat /var/run/"${SERVICE}"/"${SERVICE}".pid) 2>/dev/null elif [ -s /var/run/"${SERVICE}"/pid ]; then kill -0 $(cat /var/run/"${SERVICE}"/pid) 2>/dev/null else false fi # Service not running, so don't restart [ $? != 0 ] && exit 0 fi if [ -x /sbin/service ]; then service "${SERVICE}" "${ACTION}" "$@" elif [ -x /etc/init.d/"${SERVICE}" -a -x /sbin/runscript ]; then if [ "${ACTION}" = "restart" ]; then /etc/init.d/"${SERVICE}" --quiet --nodeps conditionalrestart "$@" else /etc/init.d/"${SERVICE}" --quiet --nodeps "${ACTION}" "$@" fi elif [ -x /etc/init.d/"${SERVICE}" ]; then /etc/init.d/"${SERVICE}" "${ACTION}" "$@" elif [ -x /etc/rc.d/"${SERVICE}" ]; then /etc/rc.d/"${SERVICE}" "${ACTION}" "$@" elif [ -x /etc/rc.d/rc."${SERVICE}" ]; then /etc/rc.d/rc."${SERVICE}" "${ACTION}" "$@" else error_exit "Don't know how to interact with services on this platform" fi exit $? fi # -l lists our resolv files, optionally for a specific interface if [ "${CMD}" = "l" -o "${CMD}" = "i" ]; then [ -d "${IFACEDIR}" ] || exit 0 REPORT=false # If we have an interface ordering list, then use that. # It works by just using pathname expansion in the interface directory. if [ -n "${ARGS}" ]; then LIST="${ARGS}" ${FORCE} || REPORT=true elif [ -r "${SYSCONFDIR}"/interface-order ]; then LIST="lo lo[0-9]* $(cat "${SYSCONFDIR}"/interface-order) *" fi # If we don't have a list then prefer lo, metrics, tunnels, ppp # and then anything else. if [ -z "${LIST}" ]; then LIST="lo lo[0-9]* " if [ -d "${METRCICDIR}" ]; then cd "${METRICDIR}" for METRIC in *; do LIST="${LIST} ${METRIC#* }" done fi LIST="${LIST} tap[0-9]* tun[0-9]* vpn vpn[0-9]* ppp[0-9]* ippp[0-9]* *" fi RETVAL=0 cd "${IFACEDIR}" for IFACE in $(uniqify ${LIST}); do # Only list interfaces which we really have if ! [ -e "${IFACE}" ]; then if ${REPORT}; then echo "No resolv.conf for interface ${IFACE}" >&2 RETVAL=$((${RETVAL} + 1)) fi continue fi if [ "${CMD}" = "i" ]; then printf "${IFACE} " else echo_resolv "${IFACE}" fi done [ "${CMD}" = "i" ] && echo exit ${RETVAL} fi if [ "${CMD}" = "v" ]; then eval "$("${ARGV0}" -l "${IFACE}" | parse_resolv)" DOMAINS="$(uniqify ${DOMAINS})" SEARCH="$(uniqify ${SEARCH})" # If we don't have any non private nameservers # then make them all private ones public if [ -z "${NAMESERVERS}" ]; then NAMESERVERS= for D in ${DOMAINS}; do NS="${D#*:}" while [ -n "${NS}" ]; do NAMESERVERS="${NAMESERVERS} ${NS%%,*}" [ "${NS}" = "${NS#*,}" ] && break NS="${NS#*,}" done done fi NAMESERVERS="$(uniqify ${NAMESERVERS})" echo "DOMAINS='${DOMAINS}'" echo "SEARCH='${SEARCH}'" echo "NAMESERVERS='${NAMESERVERS}'" exit 0 fi # Test that we have valid options if [ "${CMD}" = "a" -o "${CMD}" = "d" ]; then if [ -z "${IFACE}" ]; then usage "Interface not specified" fi elif [ "${CMD}" != "u" ]; then [ -n "${CMD}" -a "${CMD}" != "h" ] && usage "Unknown option ${CMD}" usage fi if [ "${CMD}" = "a" ]; then for x in '/' \\ ' ' '*'; do case "${IFACE}" in *[${x}]*) error_exit "${x} not allowed in interface name";; esac done for x in '.' '-' '~'; do case "${IFACE}" in [${x}]*) error_exit "${x} not allowed at start of interface name";; esac done [ "${CMD}" = "a" -a -t 0 ] && error_exit "No file given via stdin" IFACERESOLV="${IFACEDIR}/${IFACE}" fi # Ensure that libdir exists if [ ! -d "${IFACEDIR}" ]; then if [ ! -d "${VARDIR}" ]; then if [ -L "${VARDIR}" ]; then DIR="$(readlink "${VARDIR}")" # Change to /etc as link maybe relative cd "${VARDIR%/*}" if ! mkdir -m 0755 -p "${DIR}"; then error_exit "Failed to create needed directory ${DIR}" fi else if ! mkdir -m 0755 -p "${VARDIR}"; then error_exit "Failed to create needed directory ${VARDIR}" fi fi fi mkdir -m 0755 -p "${IFACEDIR}" || \ error_exit "Failed to create needed directory ${IFACEDIR}" else # Delete any existing information about the interface if [ "${CMD}" = "d" ]; then cd "${IFACEDIR}" for ARG in ${ARGS}; do if [ "${CMD}" = "d" -a ! -e "${ARG}" ]; then ${FORCE} && continue error_exit "No resolv.conf for interface ${ARG}" fi rm -f "${ARG}" "${METRICDIR}/"*" ${ARG}" "${PRIVATEDIR}/${ARG}" || exit $? done fi fi if [ "${CMD}" = "a" ]; then # Read resolv.conf from stdin RESOLV="$(cat)\n" # If what we are given matches what we have, then do nothing if [ -e "${IFACEDIR}/${IFACE}" ]; then if [ "$(printf "${RESOLV}")" = "$(cat "${IFACEDIR}/${IFACE}")" ] then exit 0 fi rm "${IFACEDIR}/${IFACE}" fi printf "${RESOLV}" >"${IFACEDIR}/${IFACE}" || exit $? rm -f "${METRICDIR}/"*" ${IFACE}" if [ ! -d "${METRICDIR}" ]; then mkdir "${METRICDIR}" fi rm -f "${METRICDIR}/"*" ${IFACE}" # Pad metric to 6 characters, so 5 is less than 10 # All interfaces will get a default metric of 0 while [ ${#IF_METRIC} -le 6 ]; do IF_METRIC="0${IF_METRIC}" done echo " " >"${METRICDIR}/${IF_METRIC} ${IFACE}" case "${IF_PRIVATE}" in [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) if [ ! -d "${PRIVATEDIR}" ]; then [ -e "${PRIVATEDIR}" ] && rm "${PRIVATEDIR}" mkdir "${PRIVATEDIR}" fi [ -d "${PRIVATEDIR}" ] && echo " " >"${PRIVATEDIR}/${IFACE}" ;; *) [ -e "${PRIVATEDIR}/${IFACE}" ] && rm -f "${PRIVATEDIR}/${IFACE}" ;; esac fi RETVAL=0 for SCRIPT in "${SYSCONFDIR}"/update.d/*; do if [ -e "${SCRIPT}" ]; then "${SCRIPT}" "${CMD}" "${IFACE}" RETVAL=$((${RETVAL} + $?)) fi done exit ${RETVAL}