#!/bin/sh
# Copyright 2007-2009 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=/etc
LIBEXECDIR=/lib/resolvconf
VARDIR=/var/run/resolvconf
IFACEDIR="${VARDIR}/interfaces"
METRICDIR="${VARDIR}/metrics"
PRIVATEDIR="${VARDIR}/private"

# Support original resolvconf configuration layout
# as well as the openresolv config file
if [ -f "${SYSCONFDIR}"/resolvconf.conf ]; then
	. "${SYSCONFDIR}"/resolvconf.conf
elif [ -d "${SYSCONFDIR}/resolvconf" ]; then
	SYSCONFDIR="${SYSCONFDIR}/resolvconf"
	if [ -f "${SYSCONFDIR}"/interface-order ]; then
		interface_order="$(cat "${SYSCONFDIR}"/interface-order)"
	fi
fi
dynamic_order="${dynamic_order:-tap[0-9]* tun[0-9]* vpn vpn[0-9]* ppp[0-9]* ippp[0-9]*}"
interface_order="${interface_order:-lo lo[0-9]*}"

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
}

# Parse resolv.conf's and make variables
# for domain name servers, search name servers and global nameservers
parse_resolv()
{
	local line= ns= ds= search= d= n= newns=
	local new=true iface= private=false

	echo "DOMAINS="
	echo "SEARCH="
	echo "NAMESERVERS="

	while read line; do
		case "${line}" in
		"# resolv.conf from "*)
			if ${new}; then
				iface="${line#\# resolv.conf from *}"
				new=false
				case " ${private_interfaces} " in
				*" ${iface} "*)
					private=true
					;;
				*)
					if [ -e "${PRIVATEDIR}/${iface}" ]; then
						private=true
					else
						private=false
					fi
					;;
				esac
			fi
			;;
		"nameserver "*)
			case "${line#* }" in
			127.*|0.0.0.0|255.255.255.255) continue;;
			esac
			ns="${ns}${line#* } "
			;;
		"domain "*|"search "*)
			search="${line#* }"
			;;
		*)
			[ -n "${line}" ] && continue
			if [ -n "${ns}" -a -n "${search}" ]; then
				newns=
				for n in ${ns}; do
					newns="${newns}${newns:+,}${n}"
				done
				ds=
				for d in ${search}; do
					ds="${ds}${ds:+ }${d}:${newns}"
				done
				echo "DOMAINS=\"\${DOMAINS} ${ds}\""
			fi
			echo "SEARCH=\"\${SEARCH} ${search}\""
			if ! ${private}; then
				echo "NAMESERVERS=\"\${NAMESERVERS} ${ns}\""
			fi
			ns=
			search=
			new=true
			;;
		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"

	service "${service}" "${action}" "$@"
	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
	else
		cd "${IFACEDIR}"
		for i in ${interface_order}; do
			[ -e "${i}" ] && list="${list} ${i}"
		done
		for i in ${dynamic_order}; do
			if [ -e "${i}" -a ! -e "${METRICDIR}/"*" ${i}" ]; then
				list="${list} ${i}"
			fi
		done
		if [ -d "${METRICDIR}" ]; then
			cd "${METRICDIR}"
			for i in *; do
				list="${list} ${i#* }"
			done
		fi
		list="${list} *"
	fi

	retval=0
	cd "${IFACEDIR}"
	for i in $(uniqify ${list}); do
		# Only list interfaces which we really have
		if ! [ -e "${i}" ]; then
			if ${report}; then
				echo "No resolv.conf for interface ${i}" >&2
				retval=$((${retval} + 1))
			fi
			continue
		fi
		
		if [ "${cmd}" = "i" ]; then
			printf "${i} "
		else
			echo_resolv "${i}"
		fi
	done
	[ "${cmd}" = "i" ] && echo
	exit ${retval} 
fi

if [ "${cmd}" = "v" ]; then
	eval "$("${ARGV0}" -l "${iface}" | parse_resolv)"

	# Ensure that we only list each domain once
	newdomains=
	for d in ${DOMAINS}; do
		dn="${d%%:*}"
		case " ${newdomains}" in
		*" ${dn}:"*) continue;;
		esac
		newdomains="${newdomains}${newdomains:+ }${dn}:"
		newns=
		for nd in ${DOMAINS}; do
			if [ "${dn}" = "${nd%%:*}" ]; then
				ns="${nd#*:}"
				while [ -n "${ns}" ]; do
					case ",${newns}," in
					*,${ns%%,*},*) ;;
					*) newns="${newns}${newns:+,}${ns%%,*}"
						;;
					esac
					[ "${ns}" = "${ns#*,}" ] && break
					ns="${ns#*,}"
				done
			fi
		done
		newdomains="${newdomains}${newns}"
	done
	echo "DOMAINS='${newdomains}'"
	echo "SEARCH='$(uniqify ${SEARCH})'"
	echo "NAMESERVERS='$(uniqify ${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"
fi

if [ ! -d "${IFACEDIR}" ]; then
	if [ ! -d "${VARDIR}" ]; then
		if [ -L "${VARDIR}" ]; then
			dir="$(readlink "${VARDIR}")"
			# 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 i in ${args}; do
			if [ "${cmd}" = "d" -a ! -e "${i}" ]; then
				${force} && continue
				error_exit "No resolv.conf for" \
					"interface ${i}"
			fi
			rm -f "${i}" "${METRICDIR}/"*" ${i}" \
				"${PRIVATEDIR}/${i}" || 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 $?
	[ ! -d "${METRICDIR}" ] && mkdir "${METRICDIR}"
	rm -f "${METRICDIR}/"*" ${iface}"
	if [ -n "${IF_METRIC}" ]; then
		# Pad metric to 6 characters, so 5 is less than 10
		while [ ${#IF_METRIC} -le 6 ]; do
			IF_METRIC="0${IF_METRIC}"
		done
		echo " " >"${METRICDIR}/${IF_METRIC} ${iface}"
	fi
	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}"
		;;
	*)
		if [ -e "${PRIVATEDIR}/${iface}" ]; then
			rm -f "${PRIVATEDIR}/${iface}"
		fi
		;;
	esac
fi

run-parts "${LIBEXECDIR}"

