1#!/bin/sh
2# Copyright (c) 2007-2023 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
27RESOLVCONF="$0"
28OPENRESOLV_VERSION="3.13.2"
29SYSCONFDIR=@SYSCONFDIR@
30LIBEXECDIR=@LIBEXECDIR@
31VARDIR=@VARDIR@
32RCDIR=@RCDIR@
33RESTARTCMD=@RESTARTCMD@
34
35if [ "$1" = "--version" ]; then
36	echo "openresolv $OPENRESOLV_VERSION"
37	echo "Copyright (c) 2007-2020 Roy Marples"
38	exit 0
39fi
40
41# Disregard dhcpcd setting
42unset interface_order state_dir
43
44# If you change this, change the test in VFLAG and libc.in as well
45local_nameservers="127.* 0.0.0.0 255.255.255.255 ::1"
46
47dynamic_order="tap[0-9]* tun[0-9]* vpn vpn[0-9]* wg[0-9]* ppp[0-9]* ippp[0-9]*"
48interface_order="lo lo[0-9]*"
49name_server_blacklist="0.0.0.0"
50
51# Support original resolvconf configuration layout
52# as well as the openresolv config file
53if [ -f "$SYSCONFDIR"/resolvconf.conf ]; then
54	. "$SYSCONFDIR"/resolvconf.conf
55	[ -n "$state_dir" ] && VARDIR="$state_dir"
56elif [ -d "$SYSCONFDIR/resolvconf" ]; then
57	SYSCONFDIR="$SYSCONFDIR/resolvconf"
58	if [ -f "$SYSCONFDIR"/interface-order ]; then
59		interface_order="$(cat "$SYSCONFDIR"/interface-order)"
60	fi
61fi
62
63IFACEDIR="$VARDIR/interfaces"
64METRICDIR="$VARDIR/metrics"
65PRIVATEDIR="$VARDIR/private"
66EXCLUSIVEDIR="$VARDIR/exclusive"
67DEPRECATEDDIR="$VARDIR/deprecated"
68LOCKDIR="$VARDIR/lock"
69_PWD="$PWD"
70
71warn()
72{
73	echo "$*" >&2
74}
75
76error_exit()
77{
78	echo "$*" >&2
79	exit 1
80}
81
82usage()
83{
84	cat <<-EOF
85	Usage: ${RESOLVCONF##*/} [options] command [argument]
86
87	Inform the system about any DNS updates.
88
89	Commands:
90	  -a \$INTERFACE    Add DNS information to the specified interface
91	                   (DNS supplied via stdin in resolv.conf format)
92	  -C \$PATTERN      Deprecate DNS information for matched interfaces
93	  -c \$PATTERN      Configure DNS information for matched interfaces
94	  -d \$INTERFACE    Delete DNS information from the specified interface
95	  -h               Show this help cruft
96	  -i [\$PATTERN]    Show interfaces that have supplied DNS information
97                   optionally from interfaces that match the specified
98                   pattern
99	  -l [\$PATTERN]    Show DNS information, optionally from interfaces
100	                   that match the specified pattern
101
102	  -u               Run updates from our current DNS information
103	  --version        Echo the ${RESOLVCONF##*/} version
104
105	Options:
106	  -f               Ignore non existent interfaces
107	  -m metric        Give the added DNS information a metric
108	  -p               Mark the interface as private
109	  -x               Mark the interface as exclusive
110
111	Subscriber and System Init Commands:
112	  -I               Init the state dir
113	  -r \$SERVICE      Restart the system service
114	                   (restarting a non-existent or non-running service
115	                    should have no output and return 0)
116	  -R               Show the system service restart command
117	  -v [\$PATTERN]    echo NEWDOMAIN, NEWSEARCH and NEWNS variables to
118	  		   the console
119	  -V [\$PATTERN]    Same as -v, but only uses configuration in
120	                   $SYSCONFDIR/resolvconf.conf
121	EOF
122	[ -z "$1" ] && exit 0
123	echo
124	error_exit "$*"
125}
126
127# Strip any trailing dot from each name as a FQDN does not belong
128# in resolv.conf(5)
129# If you think otherwise, capture a DNS trace and you'll see libc
130# will strip it regardless.
131# This also solves setting up duplicate zones in our subscribers.
132# Also strip any comments denoted by #.
133resolv_strip()
134{
135	space=
136	for word; do
137		case "$word" in
138		\#*) break;;
139		esac
140		printf "%s%s" "$space${word%.}"
141		space=" "
142	done
143	printf "\n"
144}
145
146private_iface()
147{
148	# Allow expansion
149	cd "$IFACEDIR"
150
151	# Public interfaces override private ones.
152	for p in $public_interfaces; do
153		case "$iface" in
154		"$p"|"$p":*) return 1;;
155		esac
156	done
157
158	if [ -e "$PRIVATEDIR/$iface" ]; then
159		return 0
160	fi
161
162	for p in $private_interfaces; do
163		case "$iface" in
164		"$p"|"$p":*) return 0;;
165		esac
166	done
167
168	# Not a private interface
169	return 1
170}
171
172# Parse resolv.conf's and make variables
173# for domain name servers, search name servers and global nameservers
174parse_resolv()
175{
176	domain=
177	new=true
178	newns=
179	ns=
180	private=false
181	search=
182
183	while read -r line; do
184		stripped_line="$(resolv_strip ${line#* })"
185		case "$line" in
186		"# resolv.conf from "*)
187			if ${new}; then
188				iface="${line#\# resolv.conf from *}"
189				new=false
190				if private_iface "$iface"; then
191					private=true
192				else
193					private=false
194				fi
195			fi
196			;;
197		"nameserver "*)
198			islocal=false
199			for l in $local_nameservers; do
200				case "$stripped_line" in
201				$l)
202					islocal=true
203					break
204					;;
205				esac
206			done
207			if $islocal; then
208				echo "LOCALNAMESERVERS=\"\$LOCALNAMESERVERS $stripped_line\""
209			else
210				ns="$ns$stripped_line "
211			fi
212			;;
213		"domain "*)
214			search="$stripped_line"
215			if [ -z "$domain" ]; then
216				domain="$search"
217				echo "DOMAIN=\"$domain\""
218			fi
219			;;
220		"search "*)
221			search="$stripped_line"
222			;;
223		*)
224			[ -n "$line" ] && continue
225			if [ -n "$ns" ] && [ -n "$search" ]; then
226				newns=
227				for n in $ns; do
228					newns="$newns${newns:+,}$n"
229				done
230				ds=
231				for d in $search; do
232					ds="$ds${ds:+ }$d:$newns"
233				done
234				echo "DOMAINS=\"\$DOMAINS $ds\""
235			fi
236			echo "SEARCH=\"\$SEARCH $search\""
237			if ! $private; then
238				echo "NAMESERVERS=\"\$NAMESERVERS $ns\""
239			fi
240			ns=
241			search=
242			new=true
243			;;
244		esac
245	done
246}
247
248uniqify()
249{
250	result=
251	while [ -n "$1" ]; do
252		case " $result " in
253		*" $1 "*);;
254		*) result="$result $1";;
255		esac
256		shift
257	done
258	echo "${result# *}"
259}
260
261dirname()
262{
263	OIFS="$IFS"
264	IFS=/
265	set -- $@
266	IFS="$OIFS"
267	if [ -n "$1" ]; then
268		printf %s .
269	else
270		shift
271	fi
272	while [ -n "$2" ]; do
273		printf "/%s" "$1"
274		shift
275	done
276	printf "\n"
277}
278
279config_mkdirs()
280{
281	for f; do
282		[ -n "$f" ] || continue
283		d="$(dirname "$f")"
284		if [ ! -d "$d" ]; then
285			mkdir -p "$d" || return $?
286		fi
287	done
288	return 0
289}
290
291# With the advent of alternative init systems, it's possible to have
292# more than one installed. So we need to try and guess what one we're
293# using unless overridden by configure.
294# Note that restarting a service is a last resort - the subscribers
295# should make a reasonable attempt to reconfigure the service via some
296# method, normally SIGHUP.
297detect_init()
298{
299	[ -n "$RESTARTCMD" ] && return 0
300
301	# Detect the running init system.
302	# As systemd and OpenRC can be installed on top of legacy init
303	# systems we try to detect them first.
304	status="@STATUSARG@"
305	: ${status:=status}
306	if [ -x /bin/systemctl ] && [ -S /run/systemd/private ]; then
307		RESTARTCMD='
308			if /bin/systemctl --quiet is-active $1.service
309			then
310				/bin/systemctl restart $1.service
311			fi'
312	elif [ -x /usr/bin/systemctl ] && [ -S /run/systemd/private ]; then
313		RESTARTCMD='
314			if /usr/bin/systemctl --quiet is-active $1.service
315			then
316				/usr/bin/systemctl restart $1.service
317			fi'
318	elif [ -x /sbin/rc-service ] &&
319	     { [ -s /libexec/rc/init.d/softlevel ] ||
320	     [ -s /run/openrc/softlevel ]; }
321	then
322		RESTARTCMD='/sbin/rc-service -i $1 -- -Ds restart'
323	elif [ -x /usr/sbin/invoke-rc.d ]; then
324		RCDIR=/etc/init.d
325		RESTARTCMD='
326		   if /usr/sbin/invoke-rc.d --quiet $1 status >/dev/null 2>&1
327		   then
328			/usr/sbin/invoke-rc.d $1 restart
329		   fi'
330	elif [ -x /usr/bin/s6-rc ] && [ -x /usr/bin/s6-svc ]; then
331		RESTARTCMD='
332		   if s6-rc -a list | grep -qFx $1-srv
333		   then
334			s6-svc -r /run/service/$1-srv
335		   fi'
336	elif [ -x /sbin/service ]; then
337		# Old RedHat
338		RCDIR=/etc/init.d
339		RESTARTCMD='
340			if /sbin/service $1; then
341				/sbin/service $1 restart
342			fi'
343	elif [ -x /usr/sbin/service ]; then
344		# Could be FreeBSD
345		RESTARTCMD="
346			if /usr/sbin/service \$1 $status >/dev/null 2>&1
347			then
348				/usr/sbin/service \$1 restart
349			fi"
350	elif [ -x /bin/sv ]; then
351		RESTARTCMD='/bin/sv status $1 >/dev/null 2>&1 &&
352			    /bin/sv try-restart $1'
353	elif [ -x /usr/bin/sv ]; then
354		RESTARTCMD='/usr/bin/sv status $1 >/dev/null 2>&1 &&
355			    /usr/bin/sv try-restart $1'
356	elif [ -e /etc/arch-release ] && [ -d /etc/rc.d ]; then
357		RCDIR=/etc/rc.d
358		RESTARTCMD='
359			if [ -e /var/run/daemons/$1 ]
360			then
361				/etc/rc.d/$1 restart
362			fi'
363	elif [ -e /etc/slackware-version ] && [ -d /etc/rc.d ]; then
364		RESTARTCMD='
365			if /etc/rc.d/rc.$1 status >/dev/null 2>&1
366			then
367				/etc/rc.d/rc.$1 restart
368			fi'
369	elif [ -e /etc/rc.d/rc.subr ] && [ -d /etc/rc.d ]; then
370		# OpenBSD
371		RESTARTCMD='
372			if /etc/rc.d/$1 check >/dev/null 2>&1
373			then
374				/etc/rc.d/$1 restart
375			fi'
376	elif [ -d /etc/dinit.d ] && command -v dinitctl >/dev/null 2>&1; then
377		RESTARTCMD='dinitctl --quiet restart --ignore-unstarted $1'
378	else
379		for x in /etc/init.d/rc.d /etc/rc.d /etc/init.d; do
380			[ -d $x ] || continue
381			RESTARTCMD="
382				if $x/\$1 $status >/dev/null 2>&1
383				then
384					$x/\$1 restart
385				fi"
386			break
387		done
388	fi
389
390	if [ -z "$RESTARTCMD" ]; then
391		if [ "$_NOINIT_WARNED" != true ]; then
392			warn "could not detect a useable init system"
393			_NOINIT_WARNED=true
394		fi
395		return 1
396	fi
397	_NOINIT_WARNED=
398	return 0
399}
400
401echo_resolv()
402{
403	OIFS="$IFS"
404
405	[ -n "$1" ] && [ -f "$IFACEDIR/$1" ] || return 1
406	echo "# resolv.conf from $1"
407	# Our variable maker works of the fact each resolv.conf per interface
408	# is separated by blank lines.
409	# So we remove them when echoing them.
410	while read -r line; do
411		IFS="$OIFS"
412		if [ -n "$line" ]; then
413			# We need to set IFS here to preserve any whitespace
414			IFS=''
415			printf "%s\n" "$line"
416		fi
417	done < "$IFACEDIR/$1"
418	IFS="$OIFS"
419}
420
421deprecated_interface()
422{
423	[ -d "$DEPRECATEDDIR" ] || return 1
424
425	cd "$DEPRECATEDDIR"
426	for da; do
427		for daf in *; do
428			[ -f "$daf" ] || continue
429			case "$da" in
430			$daf) return 0;;
431			esac
432		done
433	done
434	return 1
435}
436
437list_resolv()
438{
439	[ -d "$IFACEDIR" ] || return 0
440
441	cmd="$1"
442	shift
443	pattern_specified="$1"
444
445	excl=false
446	list=
447	report=false
448	retval=0
449
450	case "$IF_EXCLUSIVE" in
451	[Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1)
452		excl=true
453		if [ -d "$EXCLUSIVEDIR" ]; then
454			cd "$EXCLUSIVEDIR"
455			for i in *; do
456				if [ -f "$i" ]; then
457					list="${i#* }"
458					break
459				fi
460			done
461		fi
462		cd "$IFACEDIR"
463		for i in $inclusive_interfaces; do
464			if [ -f "$i" ] && [ "$list" = "$i" ]; then
465				list=
466				excl=false
467				break
468			fi
469		done
470		;;
471	esac
472
473	# If we have an interface ordering list, then use that.
474	# It works by just using pathname expansion in the interface directory.
475	if [ -n "$pattern_specified" ]; then
476		list="$*"
477		$force || report=true
478	elif ! $excl; then
479		cd "$IFACEDIR"
480
481		for i in $interface_order; do
482			[ -f "$i" ] && list="$list $i"
483			for ii in "$i":* "$i".*; do
484				[ -f "$ii" ] && list="$list $ii"
485			done
486		done
487
488		for i in $dynamic_order; do
489			if [ -e "$i" ] && ! [ -e "$METRICDIR/"*" $i" ]; then
490				list="$list $i"
491			fi
492			for ii in "$i":* "$i".*; do
493				if [ -f "$ii" ] && ! [ -e "$METRICDIR/"*" $ii" ]
494				then
495					list="$list $ii"
496				fi
497			done
498		done
499
500		# Interfaces have an implicit metric of 0 if not specified.
501		for i in *; do
502			if [ -f "$i" ] && ! [ -e "$METRICDIR/"*" $i" ]; then
503				list="$list $i"
504			fi
505		done
506
507		if [ -d "$METRICDIR" ]; then
508			cd "$METRICDIR"
509			for i in *; do
510				[ -f "$i" ] && list="$list ${i#* }"
511			done
512		fi
513
514		# Move deprecated interfaces to the back
515		active=
516		deprecated=
517		for i in $list; do
518			if deprecated_interface "$i"; then
519				deprecated="$deprecated $i"
520			else
521				active="$active $i"
522			fi
523		done
524		list="$active $deprecated"
525	fi
526
527	cd "$IFACEDIR"
528	if $excl || [ -n "$pattern_specified" ]; then
529		retval=1
530	else
531		retval=0
532	fi
533	for i in $(uniqify $list); do
534		# Only list interfaces which we really have
535		if ! [ -f "$i" ]; then
536			if $report; then
537				echo "No resolv.conf for interface $i" >&2
538				retval=2
539			fi
540			continue
541		fi
542
543		if ! $ALLIFACES; then
544			if [ -n "$allow_interfaces" ]; then
545				x=false
546				for j in $allow_interfaces; do
547					if [ "$i" = "$j" ]; then
548						x=true
549					fi
550				done
551				$x || continue
552			fi
553			for j in $deny_interfaces; do
554				if [ "$i" = "$j" ]; then
555					continue 2
556				fi
557			done
558		fi
559
560		if [ "$cmd" = i ] || [ "$cmd" = "-i" ]; then
561			printf %s "$i "
562		else
563			echo_resolv "$i" && echo
564		fi
565		[ $? = 0 ] && [ "$retval" = 1 ] && retval=0
566	done
567	[ "$cmd" = i ] || [ "$cmd" = "-i" ] && echo
568	return $retval
569}
570
571list_remove()
572{
573	[ -z "$2" ] && return 0
574	eval list=\"\$$1\"
575	shift
576	result=
577	retval=0
578
579	set -f
580	for e; do
581		found=false
582		for l in $list; do
583			case "$e" in
584			$l) found=true;;
585			esac
586			$found && break
587		done
588		if $found; then
589			retval=$(($retval + 1))
590		else
591			result="$result $e"
592		fi
593	done
594	set +f
595	echo "${result# *}"
596	return $retval
597}
598
599echo_prepend()
600{
601	echo "# Generated by resolvconf"
602	if [ -n "$search_domains" ]; then
603		echo "search $search_domains"
604	fi
605	for n in $name_servers; do
606		echo "nameserver $n"
607	done
608	echo
609}
610
611echo_append()
612{
613	echo "# Generated by resolvconf"
614	if [ -n "$search_domains_append" ]; then
615		echo "search $search_domains_append"
616	fi
617	for n in $name_servers_append; do
618		echo "nameserver $n"
619	done
620	echo
621}
622
623replace()
624{
625	while read -r keyword value; do
626		for r in $replace; do
627			k="${r%%/*}"
628			r="${r#*/}"
629			f="${r%%/*}"
630			r="${r#*/}"
631			v="${r%%/*}"
632			case "$keyword" in
633			$k)
634				case "$value" in
635				$f) value="$v";;
636				esac
637				;;
638			esac
639		done
640		val=
641		for sub in $value; do
642			for r in $replace_sub; do
643				k="${r%%/*}"
644				r="${r#*/}"
645				f="${r%%/*}"
646				r="${r#*/}"
647				v="${r%%/*}"
648				case "$keyword" in
649				$k)
650					case "$sub" in
651					$f) sub="$v";;
652					esac
653					;;
654				esac
655			done
656			val="$val${val:+ }$sub"
657		done
658		printf "%s %s\n" "$keyword" "$val"
659	done
660}
661
662make_vars()
663{
664	# Clear variables
665	DOMAIN=
666	DOMAINS=
667	SEARCH=
668	NAMESERVERS=
669	LOCALNAMESERVERS=
670
671	if [ -n "${name_servers}${search_domains}" ]; then
672		eval "$(echo_prepend | parse_resolv)"
673	fi
674	if [ -z "$VFLAG" ]; then
675		IF_EXCLUSIVE=1
676		list_resolv -i "$@" >/dev/null || IF_EXCLUSIVE=0
677		eval "$(list_resolv -l "$@" | replace | parse_resolv)"
678	fi
679	if [ -n "${name_servers_append}${search_domains_append}" ]; then
680		eval "$(echo_append | parse_resolv)"
681	fi
682
683	# Ensure that we only list each domain once
684	newdomains=
685	for d in $DOMAINS; do
686		dn="${d%%:*}"
687		list_remove domain_blacklist "$dn" >/dev/null || continue
688		case " $newdomains" in
689		*" ${dn}:"*) continue;;
690		esac
691		newns=
692		for nd in $DOMAINS; do
693			if [ "$dn" = "${nd%%:*}" ]; then
694				ns="${nd#*:}"
695				while [ -n "$ns" ]; do
696					case ",$newns," in
697					*,${ns%%,*},*) ;;
698					*) list_remove name_server_blacklist \
699						"${ns%%,*}" >/dev/null \
700					&& newns="$newns${newns:+,}${ns%%,*}";;
701					esac
702					[ "$ns" = "${ns#*,}" ] && break
703					ns="${ns#*,}"
704				done
705			fi
706		done
707		if [ -n "$newns" ]; then
708			newdomains="$newdomains${newdomains:+ }$dn:$newns"
709		fi
710	done
711	DOMAIN="$(list_remove domain_blacklist $DOMAIN)"
712	SEARCH="$(uniqify $SEARCH)"
713	SEARCH="$(list_remove domain_blacklist $SEARCH)"
714	NAMESERVERS="$(uniqify $NAMESERVERS)"
715	NAMESERVERS="$(list_remove name_server_blacklist $NAMESERVERS)"
716	LOCALNAMESERVERS="$(uniqify $LOCALNAMESERVERS)"
717	LOCALNAMESERVERS="$(list_remove name_server_blacklist $LOCALNAMESERVERS)"
718	echo "DOMAIN='$DOMAIN'"
719	echo "SEARCH='$SEARCH'"
720	echo "NAMESERVERS='$NAMESERVERS'"
721	echo "LOCALNAMESERVERS='$LOCALNAMESERVERS'"
722	echo "DOMAINS='$newdomains'"
723}
724
725force=false
726VFLAG=
727while getopts a:C:c:Dd:fhIilm:pRruvVx OPT; do
728	case "$OPT" in
729	f) force=true;;
730	h) usage;;
731	m) IF_METRIC="$OPTARG";;
732	p) IF_PRIVATE=1;;
733	V)
734		VFLAG=1
735		if [ "$local_nameservers" = \
736		    "127.* 0.0.0.0 255.255.255.255 ::1" ]
737		then
738			local_nameservers=
739		fi
740		;;
741	x) IF_EXCLUSIVE=1;;
742	'?') exit 1;;
743	*) cmd="$OPT"; iface="$OPTARG";;
744	esac
745done
746shift $(($OPTIND - 1))
747args="$iface${iface:+ }$*"
748
749# -I inits the state dir
750if [ "$cmd" = I ]; then
751	if [ -d "$VARDIR" ]; then
752		rm -rf "$VARDIR"/*
753	fi
754	exit $?
755fi
756
757# -D ensures that the listed config file base dirs exist
758if [ "$cmd" = D ]; then
759	config_mkdirs "$@"
760	exit $?
761fi
762
763# -l lists our resolv files, optionally for a specific interface
764if [ "$cmd" = l ] || [ "$cmd" = i ]; then
765	ALLIFACES=true
766	list_resolv "$cmd" "$args"
767	exit $?
768fi
769ALLIFACES=false
770
771# Restart a service or echo the command to restart a service
772if [ "$cmd" = r ] || [ "$cmd" = R ]; then
773	detect_init || exit 1
774	if [ "$cmd" = r ]; then
775		set -- $args
776		eval "$RESTARTCMD"
777	else
778		echo "$RESTARTCMD" |
779			sed -e '/^$/d' -e 's/^			//g'
780	fi
781	exit $?
782fi
783
784# Not normally needed, but subscribers should be able to run independently
785if [ "$cmd" = v ] || [ -n "$VFLAG" ]; then
786	make_vars "$iface"
787	exit $?
788fi
789
790# Test that we have valid options
791case "$cmd" in
792a|d|C|c)
793	if [ -z "$iface" ]; then
794		error_exit "Interface not specified"
795	fi
796	;;
797u)	;;
798*)
799	if [ -n "$cmd" ] && [ "$cmd" != h ]; then
800		error_exit "Unknown option $cmd"
801	fi
802	usage
803	;;
804esac
805
806if [ "$cmd" = a ]; then
807	for x in '/' \\ ' ' '*'; do
808		case "$iface" in
809		*[$x]*) error_exit "$x not allowed in interface name";;
810		esac
811	done
812	for x in '.' '-' '~'; do
813		case "$iface" in
814		[$x]*) error_exit \
815			"$x not allowed at start of interface name";;
816		esac
817	done
818	[ "$cmd" = a ] && [ -t 0 ] && error_exit "No file given via stdin"
819fi
820
821if [ ! -d "$VARDIR" ]; then
822	if [ -L "$VARDIR" ]; then
823		dir="$(readlink "$VARDIR")"
824		# link maybe relative
825		cd "${VARDIR%/*}"
826		if ! mkdir -m 0755 -p "$dir"; then
827			error_exit "Failed to create needed" \
828				"directory $dir"
829		fi
830	else
831		if ! mkdir -m 0755 -p "$VARDIR"; then
832			error_exit "Failed to create needed" \
833				"directory $VARDIR"
834		fi
835	fi
836fi
837
838if [ ! -d "$IFACEDIR" ]; then
839	mkdir -m 0755 -p "$IFACEDIR" || \
840		error_exit "Failed to create needed directory $IFACEDIR"
841	if [ "$cmd" = d ]; then
842		# Provide the same error messages as below
843		if ! ${force}; then
844			cd "$IFACEDIR"
845			for i in $args; do
846				warn "No resolv.conf for interface $i"
847			done
848		fi
849		${force}
850		exit $?
851	fi
852fi
853
854# An interface was added, changed, deleted or a general update was called.
855# Due to exclusivity we need to ensure that this is an atomic operation.
856# Our subscribers *may* need this as well if the init system is sub par.
857# As such we spinlock at this point as best we can.
858# We don't use flock(1) because it's not widely available and normally resides
859# in /usr which we do our very best to operate without.
860[ -w "$VARDIR" ] || error_exit "Cannot write to $LOCKDIR"
861: ${lock_timeout:=10}
862: ${clear_nopids:=5}
863have_pid=false
864had_pid=false
865while true; do
866	if mkdir "$LOCKDIR" 2>/dev/null; then
867		trap 'rm -rf "$LOCKDIR";' EXIT
868		trap 'rm -rf "$LOCKDIR"; exit 1' INT QUIT ABRT SEGV ALRM TERM
869		echo $$ >"$LOCKDIR/pid"
870		break
871	fi
872	pid=$(cat "$LOCKDIR/pid" 2>/dev/null)
873	if [ "$pid" -gt 0 ] 2>/dev/null; then
874		have_pid=true
875		had_pid=true
876	else
877		have_pid=false
878		clear_nopids=$(($clear_nopids - 1))
879		if [ "$clear_nopids" -le 0 ]; then
880			warn "not seen a pid, clearing lock directory"
881			rm -rf "$LOCKDIR"
882		else
883			lock_timeout=$(($lock_timeout - 1))
884			sleep 1
885		fi
886		continue
887	fi
888	if $have_pid && ! kill -0 "$pid"; then
889		warn "clearing stale lock pid $pid"
890		rm -rf "$LOCKDIR"
891		continue
892	fi
893	lock_timeout=$(($lock_timeout - 1))
894	if [ "$lock_timeout" -le 0 ]; then
895		if $have_pid; then
896			error_exit "timed out waiting for lock from pid $pid"
897		else
898			if $had_pid; then
899				error_exit "timed out waiting for lock" \
900					"from some pids"
901			else
902				error_exit "timed out waiting for lock"
903			fi
904		fi
905	fi
906	sleep 1
907done
908unset have_pid had_pid clear_nopids
909
910case "$cmd" in
911a)
912	# Read resolv.conf from stdin
913	resolv="$(cat)"
914	changed=false
915	changedfile=false
916	# If what we are given matches what we have, then do nothing
917	if [ -e "$IFACEDIR/$iface" ]; then
918		if [ "$(echo "$resolv")" != \
919			"$(cat "$IFACEDIR/$iface")" ]
920		then
921			changed=true
922			changedfile=true
923		fi
924	else
925		changed=true
926		changedfile=true
927	fi
928
929	# Set metric and private before creating the interface resolv.conf file
930	# to ensure that it will have the correct flags
931	[ ! -d "$METRICDIR" ] && mkdir "$METRICDIR"
932	oldmetric="$METRICDIR/"*" $iface"
933	newmetric=
934	if [ -n "$IF_METRIC" ]; then
935		# Pad metric to 6 characters, so 5 is less than 10
936		while [ ${#IF_METRIC} -le 6 ]; do
937			IF_METRIC="0$IF_METRIC"
938		done
939		newmetric="$METRICDIR/$IF_METRIC $iface"
940	fi
941	rm -f "$METRICDIR/"*" $iface"
942	[ "$oldmetric" != "$newmetric" ] &&
943	    [ "$oldmetric" != "$METRICDIR/* $iface" ] &&
944		changed=true
945	[ -n "$newmetric" ] && echo " " >"$newmetric"
946
947	case "$IF_PRIVATE" in
948	[Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1)
949		if [ ! -d "$PRIVATEDIR" ]; then
950			[ -e "$PRIVATEDIR" ] && rm "$PRIVATEDIR"
951			mkdir "$PRIVATEDIR"
952		fi
953		[ -e "$PRIVATEDIR/$iface" ] || changed=true
954		[ -d "$PRIVATEDIR" ] && echo " " >"$PRIVATEDIR/$iface"
955		;;
956	*)
957		if [ -e "$PRIVATEDIR/$iface" ]; then
958			rm -f "$PRIVATEDIR/$iface"
959			changed=true
960		fi
961		;;
962	esac
963
964	oldexcl=
965	for x in "$EXCLUSIVEDIR/"*" $iface"; do
966		if [ -f "$x" ]; then
967			oldexcl="$x"
968			break
969		fi
970	done
971	case "$IF_EXCLUSIVE" in
972	[Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1)
973		if [ ! -d "$EXCLUSIVEDIR" ]; then
974			[ -e "$EXCLUSIVEDIR" ] && rm "$EXCLUSIVEDIR"
975			mkdir "$EXCLUSIVEDIR"
976		fi
977		cd "$EXCLUSIVEDIR"
978		for x in *; do
979			[ -f "$x" ] && break
980		done
981		if [ "${x#* }" != "$iface" ]; then
982			if [ "$x" = "${x% *}" ]; then
983				x=10000000
984			else
985				x="${x% *}"
986			fi
987			if [ "$x" = "0000000" ]; then
988				warn "exclusive underflow"
989			else
990				x=$(($x - 1))
991			fi
992			if [ -d "$EXCLUSIVEDIR" ]; then
993				echo " " >"$EXCLUSIVEDIR/$x $iface"
994			fi
995			changed=true
996		fi
997		;;
998	*)
999		if [ -f "$oldexcl" ]; then
1000			rm -f "$oldexcl"
1001			changed=true
1002		fi
1003		;;
1004	esac
1005
1006	if $changedfile; then
1007		printf "%s\n" "$resolv" >"$IFACEDIR/$iface" || exit $?
1008	elif ! $changed; then
1009		exit 0
1010	fi
1011	unset changed changedfile oldmetric newmetric x oldexcl
1012	;;
1013
1014d)
1015	# Delete any existing information about the interface
1016	cd "$IFACEDIR"
1017	changed=false
1018	for i in $args; do
1019		if [ -e "$i" ]; then
1020			changed=true
1021		elif ! ${force}; then
1022			warn "No resolv.conf for interface $i"
1023		fi
1024		rm -f "$i" "$METRICDIR/"*" $i" \
1025			"$PRIVATEDIR/$i" \
1026			"$EXCLUSIVEDIR/"*" $i" || exit $?
1027	done
1028
1029	if ! $changed; then
1030		# Set the return code based on the forced flag
1031		$force
1032		exit $?
1033	fi
1034	unset changed i
1035	;;
1036
1037C)
1038	# Mark interface as deprecated
1039	[ ! -d "$DEPRECATEDDIR" ] && mkdir "$DEPRECATEDDIR"
1040	cd "$DEPRECATEDDIR"
1041	changed=false
1042	for i in $args; do
1043		if [ ! -e "$i" ]; then
1044			changed=true
1045			echo " " >"$i" || exit $?
1046		fi
1047	done
1048	$changed || exit 0
1049	unset changed i
1050	;;
1051
1052c)
1053	# Mark interface as active
1054	if [ -d "$DEPRECATEDDIR" ]; then
1055		cd "$DEPRECATEDDIR"
1056		changed=false
1057		for i in $args; do
1058			if [ -e "$i" ]; then
1059				changed=true
1060				rm "$i" || exit $?
1061			fi
1062		done
1063		$changed || exit 0
1064		unset changed i
1065	fi
1066	;;
1067esac
1068
1069case "${resolvconf:-YES}" in
1070[Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) ;;
1071*) exit 0;;
1072esac
1073
1074# Try and detect a suitable init system for our scripts
1075detect_init
1076export RESTARTCMD RCDIR _NOINIT_WARNED
1077
1078eval "$(make_vars)"
1079export RESOLVCONF DOMAINS SEARCH NAMESERVERS LOCALNAMESERVERS
1080: ${list_resolv:=list_resolv -l}
1081retval=0
1082
1083# Run scripts in the same directory resolvconf is run from
1084# in case any scripts accidentally dump files in the wrong place.
1085cd "$_PWD"
1086for script in "$LIBEXECDIR"/*; do
1087	if [ -f "$script" ]; then
1088		eval script_enabled="\$${script##*/}"
1089		case "${script_enabled:-YES}" in
1090		[Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) ;;
1091		*) continue;;
1092		esac
1093		if [ -x "$script" ]; then
1094			"$script" "$cmd" "$iface"
1095		else
1096			(set -- "$cmd" "$iface"; . "$script")
1097		fi
1098		retval=$(($retval + $?))
1099	fi
1100done
1101exit $retval
1102