resolv.subr revision 252980
1223328Sgavinif [ ! "$_NETWORKING_RESOLV_SUBR" ]; then _NETWORKING_RESOLV_SUBR=1
2223328Sgavin#
3116424Smikeh# Copyright (c) 2006-2013 Devin Teske
4116424Smikeh# All rights reserved.
5223328Sgavin#
6116424Smikeh# Redistribution and use in source and binary forms, with or without
7116424Smikeh# modification, are permitted provided that the following conditions
8116424Smikeh# are met:
9116424Smikeh# 1. Redistributions of source code must retain the above copyright
10116424Smikeh#    notice, this list of conditions and the following disclaimer.
11116424Smikeh# 2. Redistributions in binary form must reproduce the above copyright
12116424Smikeh#    notice, this list of conditions and the following disclaimer in the
13116424Smikeh#    documentation and/or other materials provided with the distribution.
14116424Smikeh#
15116424Smikeh# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16116424Smikeh# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING BUT NOT LIMITED TO, THE
17116424Smikeh# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18116424Smikeh# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19116424Smikeh# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20116424Smikeh# DAMAGES (INLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21116424Smikeh# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22116424Smikeh# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23116424Smikeh# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24116424Smikeh# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25116424Smikeh# SUCH DAMAGE.
26116424Smikeh#
27116424Smikeh# $FreeBSD: head/usr.sbin/bsdconfig/networking/share/resolv.subr 252980 2013-07-07 18:21:30Z dteske $
28116424Smikeh#
29116424Smikeh############################################################ INCLUDES
30116424Smikeh
31116424SmikehBSDCFG_SHARE="/usr/share/bsdconfig"
32116424Smikeh. $BSDCFG_SHARE/common.subr || exit 1
33116424Smikehf_dprintf "%s: loading includes..." networking/resolv.subr
34116424Smikehf_include $BSDCFG_SHARE/dialog.subr
35116424Smikehf_include $BSDCFG_SHARE/media/tcpip.subr
36116424Smikehf_include $BSDCFG_SHARE/networking/common.subr
37116424Smikehf_include $BSDCFG_SHARE/networking/ipaddr.subr
38116424Smikehf_include $BSDCFG_SHARE/strings.subr
39116424Smikeh
40116424SmikehBSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="120.networking"
41116424Smikehf_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
42116424Smikeh
43116424Smikeh############################################################ CONFIGURATION
44116424Smikeh
45116424Smikeh#
46116424Smikeh# When updating resolv.conf(5), should we populate the `search' directive with
47116424Smikeh# all possible sub-domains? In example, if the domain is "sub.domain.com", when
48116424Smikeh# the below option is set to 1, include both "sub.domain.com" and "domain.com"
49116424Smikeh# in the `search' directive, otherwise use only "sub.domain.com".
50116424Smikeh#
51116424Smikeh# When enabled (set to 1), specify the minimum number of dots required for each
52116424Smikeh# `search' domain by setting the second option below, `RESOLVER_SEARCH_NDOTS'.
53116424Smikeh#
54116424Smikeh: ${RESOLVER_SEARCH_DOMAINS_ALL:=1}
55128671Smikeh: ${RESOLVER_SEARCH_NDOTS:=1}
56116424Smikeh
57116424Smikeh############################################################ FUNCTIONS
58116424Smikeh
59116424Smikeh# f_resolv_conf_domain
60116424Smikeh#
61116424Smikeh# Returns the domain configured in resolv.conf(5).
62116424Smikeh#
63223328Sgavinf_resolv_conf_domain()
64116424Smikeh{
65116424Smikeh	tail -r "$RESOLV_CONF" 2> /dev/null | awk \
66116424Smikeh	'
67116424Smikeh		BEGIN { found = 0 }
68116424Smikeh		( tolower($1) == "domain" ) \
69116424Smikeh		{
70116424Smikeh			print $2
71116424Smikeh			found = 1
72116424Smikeh			exit
73116424Smikeh		}
74116424Smikeh		END { exit ! found }
75116424Smikeh	'
76116424Smikeh}
77116424Smikeh
78116424Smikeh# f_resolv_conf_search
79116424Smikeh#
80116424Smikeh# Returns the search configured in resolv.conf(5).
81116424Smikeh#
82116424Smikehf_resolv_conf_search()
83116424Smikeh{
84116424Smikeh	tail -r "$RESOLV_CONF" 2> /dev/null | awk \
85116424Smikeh	'
86116424Smikeh		BEGIN { found = 0 }
87223328Sgavin		{
88223328Sgavin			tl0 = tolower($0)
89223328Sgavin			if ( match(tl0, /^[[:space:]]*search[[:space:]]+/) ) {
90223328Sgavin				search = substr($0, RLENGTH + 1)
91223328Sgavin				sub(/[[:space:]]*#.*$/, "", search)
92223328Sgavin				gsub(/[[:space:]]+/, " ", search)
93223328Sgavin				print search
94223328Sgavin				found = 1
95116424Smikeh				exit
96116424Smikeh			}
97116424Smikeh		}
98116424Smikeh		END { exit ! found }
99116424Smikeh	'
100116424Smikeh}
101223328Sgavin
102116424Smikeh# f_dialog_resolv_conf_update $hostname
103#
104# Updates the search/domain directives in resolv.conf(5) given a valid fully-
105# qualified hostname.
106#
107# This function is a two-parter. Below is the awk(1) portion of the function,
108# afterward is the sh(1) function which utilizes the below awk script.
109#
110f_dialog_resolv_conf_update_awk='
111# Variables that should be defined on the invocation line:
112# 	-v domain="domain"
113# 	-v search_all="0|1"
114# 	-v search_ndots="1+"
115#
116BEGIN {
117	domain_found = search_found = 0
118
119	if ( search_all ) {
120		search = ""
121		subdomain = domain
122		if ( search_ndots < 1 )
123			search_ndots = 1
124
125		ndots = split(subdomain, labels, ".") - 1
126		while ( ndots-- >= search_ndots ) {
127			if ( length(search) ) search = search " "
128			search = search subdomain
129			sub(/[^.]*\./, "", subdomain)
130		}
131	}
132	else search = domain
133}
134{
135	if ( domain_found && search_found ) { print; next }
136
137	tl0 = tolower($0)
138	if ( ! domain_found && \
139	     match(tl0, /^[[:space:]]*domain[[:space:]]+/) ) \
140	{
141		if ( length(domain) ) {
142			printf "%s%s\n", substr($0, 0, RLENGTH), domain
143			domain_found = 1
144		}
145	}
146	else if ( ! search_found && \
147	          match(tl0, /^[[:space:]]*search[[:space:]]+/) ) \
148	{
149		if ( length(search) ) {
150			printf "%s%s\n", substr($0, 0, RLENGTH), search
151			search_found = 1
152		}
153	}
154	else print
155}
156END {
157	if ( ! search_found && length(search) )
158		printf "search\t%s\n", search
159	if ( ! domain_found && length(domain) )
160		printf "domain\t%s\n", domain
161}
162'
163f_dialog_resolv_conf_update()
164{
165	local hostname="$1"
166
167	#
168	# Extrapolate the desired domain search parameter for resolv.conf(5)
169	#
170	local search ndots domain="${hostname#*.}"
171	if [ "$RESOLVER_SEARCH_DOMAINS_ALL" = "1" ]; then
172		search=""
173		ndots=$( IFS=.; set -- $domain; echo $(( $# - 1 )) )
174		while [ $ndots -ge ${RESOLVER_SEARCH_NDOTS:-1} ]; do
175			search="$search${search:+ }$domain"
176			domain="${domain#*.}"
177			ndots=$(( $ndots - 1 ))
178		done
179		domain="${hostname#*.}"
180	else
181		search="$domain"
182	fi
183
184	#
185	# Save domain/search information only if different from resolv.conf(5)
186	#
187	if [ "$domain" != "$( f_resolv_conf_domain )" -o \
188	     "$search" != "$( f_resolv_conf_search )" ]
189	then
190		f_dialog_info "Saving new domain/search settings" \
191		              "to resolv.conf(5)..."
192
193		#
194		# Create a new temporary file to write our resolv.conf(5)
195		# update with our new `domain' and `search' directives.
196		#
197		local tmpfile="$( mktemp -t "$pgm" )"
198		[ "$tmpfile" ] || return $FAILURE
199
200		#
201		# Fixup permissions and ownership (mktemp(1) creates the
202		# temporary file with 0600 permissions -- change the
203		# permissions and ownership to match resolv.conf(5) before
204		# we write it out and mv(1) it into place).
205		#
206		local mode="$( stat -f '%#Lp' "$RESOLV_CONF" 2> /dev/null )"
207		local owner="$( stat -f '%u:%g' "$RESOLV_CONF" 2> /dev/null )"
208		f_quietly chmod "${mode:-0644}" "$tmpfile"
209		f_quietly chown "${owner:-root:wheel}" "$tmpfile"
210
211		#
212		# Operate on resolv.conf(5), replacing only the last
213		# occurrences of `domain' and `search' directives (or add
214		# them to the top if not found), in strict-adherence to the
215		# following entry in resolver(5):
216		#
217		# 	The domain and search keywords are mutually exclusive.
218		# 	If more than one instance of these keywords is present,
219		# 	the last instance will override.
220		#
221		# NOTE: If RESOLVER_SEARCH_DOMAINS_ALL is set to `1' in the
222		# environment, all sub-domains will be added to the `search'
223		# directive, not just the FQDN.
224		#
225		local domain="${hostname#*.}" new_contents
226		[ "$domain" = "$hostname" ] && domain=
227		new_contents=$( tail -r "$RESOLV_CONF" 2> /dev/null )
228		new_contents=$( echo "$new_contents" | awk \
229			-v domain="$domain" \
230			-v search_all="${RESOLVER_SEARCH_DOMAINS_ALL:-1}" \
231			-v search_ndots="${RESOLVER_SEARCH_NDOTS:-1}" \
232			"$f_dialog_resolv_conf_update_awk" )
233
234		#
235		# Write the temporary file contents and move the temporary
236		# file into place.
237		#
238		echo "$new_contents" | tail -r > "$tmpfile" || return $FAILURE
239		f_quietly mv "$tmpfile" "$RESOLV_CONF"
240
241	fi
242}
243
244# f_dialog_input_nameserver [ $n $nameserver ]
245#
246# Allows the user to edit a given nameserver. The first argument is the
247# resolv.conf(5) nameserver ``instance'' integer. For example, this will be one
248# if editing the first nameserver instance, two if editing the second, three if
249# the third, ad nauseum. If this argument is zero, null, or missing, the value
250# entered by the user (if non-null) will be added to resolv.conf(5) as a new
251# `nameserver' entry. The second argument is the IPv4 address of the nameserver
252# to be edited -- this will be displayed as the initial value during the edit.
253#
254# Taint-checking is performed when editing an existing entry (when the second
255# argument is one or higher) in that the first argument must match the current
256# value of the Nth `nameserver' instance in resolv.conf(5) else an error is
257# generated discarding any/all changes.
258#
259# This function is a two-parter. Below is the awk(1) portion of the function,
260# afterward is the sh(1) function which utilizes the below awk script.
261#
262f_dialog_input_nameserver_edit_awk='
263# Variables that should be defined on the invocation line:
264# 	-v nsindex="1+"
265# 	-v old_value="..."
266# 	-v new_value="..."
267#
268BEGIN {
269	if ( nsindex < 1 ) exit 1
270	found = n = 0
271}
272{
273	if ( found ) { print; next }
274
275	if ( match(tolower($0), /^[[:space:]]*nameserver[[:space:]]+/)) {
276		if ( ++n == nsindex ) {
277			if ( $2 != old_value ) exit 2
278			if ( new_value != "" ) printf "%s%s\n", \
279				substr($0, 0, RLENGTH), new_value
280			found = 1
281		}
282		else print
283	}
284	else print
285}
286END { if ( ! found ) exit 3 }
287'
288f_dialog_input_nameserver()
289{
290	local index="${1:-0}" old_ns="$2" new_ns
291	local ns="$old_ns"
292
293	#
294	# Perform sanity checks
295	#
296	f_isinteger "$index" || return $FAILURE
297	[ $index -ge 0 ] || return $FAILURE
298
299	local msg
300	if [ $index -gt 0 ]; then
301		if [ "$USE_XDIALOG" ]; then
302			msg="$xmsg_please_enter_nameserver_existing"
303		else
304			msg="$msg_please_enter_nameserver_existing"
305		fi
306	else
307		msg="$msg_please_enter_nameserver"
308	fi
309
310	#
311	# Loop until the user provides taint-free input.
312	#
313	while :; do
314		f_dialog_input new_ns "$msg" "$ns" \
315		               "$hline_num_punc_tab_enter" || return
316
317		# Take only the first "word" of the user's input
318		new_ns="${new_ns%%[$IFS]*}"
319
320		# Taint-check the user's input
321		[ "$new_ns" ] || break
322		f_dialog_validate_ipaddr "$new_ns" && break
323
324		# Update prompt to allow user to re-edit previous entry
325		ns="$new_ns"
326	done
327
328	#
329	# Save only if the user changed the nameserver.
330	#
331	if [ $index -eq "0" -a "$new_ns" ]; then
332		f_dialog_info "$msg_saving_nameserver"
333		printf "nameserver\t%s\n" "$new_ns" >> "$RESOLV_CONF"
334		return $SUCCESS
335	elif [ $index -gt 0 -a "$old_ns" != "$new_ns" ]; then
336		if [ "$new_ns" ]; then
337			msg="$msg_saving_nameserver_existing"
338		else
339			msg="$msg_removing_nameserver"
340		fi
341		f_dialog_info "$msg"
342
343		#
344		# Create a new temporary file to write our new resolv.conf(5)
345		#
346		local tmpfile="$( mktemp -t "$pgm" )"
347		[ "$tmpfile" ] || return $FAILURE
348
349		#
350		# Quietly fixup permissions and ownership
351		#
352		local mode owner
353		mode=$( stat -f '%#Lp' "$RESOLV_CONF" 2> /dev/null )
354		owner=$( stat -f '%u:%g' "$RESOLV_CONF" 2> /dev/null )
355		f_quietly chmod "${mode:-0644}" "$tmpfile"
356		f_quietly chown "${owner:-root:wheel}" "$tmpfile"
357
358		#
359		# Operate on resolv.conf(5)
360		#
361		local new_contents
362		new_contents=$( awk -v nsindex="$index"    \
363		                    -v old_value="$old_ns" \
364		                    -v new_value="$new_ns" \
365		                    "$f_dialog_input_nameserver_edit_awk" \
366		                    "$RESOLV_CONF" )
367
368		#
369		# Produce an appropriate error message if necessary.
370		#
371		local retval=$?
372		case $retval in
373		1) f_die 1 "$msg_internal_error_nsindex_value" "$nsindex" ;;
374		2) f_show_msg "$msg_resolv_conf_changed_while_editing"
375		   return $retval ;;
376		3) f_show_msg "$msg_resolv_conf_entry_no_longer_exists"
377		   return $retval ;;
378		esac
379
380		#
381		# Write the temporary file contents and move the temporary
382		# file into place.
383		#
384		echo "$new_contents" > "$tmpfile" || return $FAILURE
385		f_quietly mv "$tmpfile" "$RESOLV_CONF"
386	fi
387}
388
389# f_dialog_menu_nameservers
390#
391# Edit the nameservers in resolv.conf(5).
392#
393f_dialog_menu_nameservers()
394{
395	local prompt="$msg_dns_configuration"
396	local menu_list # Calculated below
397	local hline="$hline_arrows_tab_enter"
398	local defaultitem=
399
400	local height width rows
401	local opt_exit="$msg_return_to_previous_menu"
402	local opt_add="$msg_add_nameserver"
403
404	#
405	# Loop forever until the user has finished configuring nameservers
406	#
407	while :; do
408		#
409		# Re/Build list of nameservers
410		#
411		local nameservers="$( f_resolv_conf_nameservers )"
412		menu_list=$(
413			index=1
414
415			echo "'X $msg_exit' '$opt_exit'" 
416			index=$(( $index + 1 ))
417
418			echo "'A $msg_add'  '$opt_add'" 
419			index=$(( $index + 1 ))
420
421			for ns in $nameservers; do
422				[ $index -lt ${#DIALOG_MENU_TAGS} ] || break
423				tag=$( f_substr "$DIALOG_MENU_TAGS" $index 1 )
424				echo "'$tag nameserver' '$ns'"
425				index=$(( $index + 1 ))
426			done
427		)
428
429		#
430		# Display configuration-edit menu
431		#
432		eval f_dialog_menu_size height width rows \
433		                        \"\$DIALOG_TITLE\"     \
434		                        \"\$DIALOG_BACKTITLE\" \
435		                        \"\$prompt\"           \
436		                        \"\$hline\"            \
437		                        $menu_list
438		local tag
439		tag=$( eval $DIALOG \
440			--title \"\$DIALOG_TITLE\"         \
441			--backtitle \"\$DIALOG_BACKTITLE\" \
442			--hline \"\$hline\"                \
443			--ok-label \"\$msg_ok\"            \
444			--cancel-label \"\$msg_cancel\"    \
445			--default-item \"\$defaultitem\"   \
446			--menu \"\$prompt\"                \
447			$height $width $rows               \
448			$menu_list                         \
449			2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
450		)
451		local retval=$?
452		f_dialog_data_sanitize tag
453
454		# Return if "Cancel" was chosen (-1) or ESC was pressed (255)
455		if [ $retval -ne $SUCCESS ]; then
456			return $retval
457		else
458			# Only update default-item on success
459			defaultitem="$tag"
460		fi
461
462		case "$tag" in
463		"X $msg_exit") break ;;
464		"A $msg_add")
465			f_dialog_input_nameserver
466			;;
467		*)
468			local n ns
469			n=$( eval f_dialog_menutag2index \"\$tag\" $menu_list )
470			ns=$( eval f_dialog_menutag2item \"\$tag\" $menu_list )
471			f_dialog_input_nameserver $(( $n - 2 )) "$ns"
472			;;
473		esac
474	done
475}
476
477############################################################ MAIN
478
479f_dprintf "%s: Successfully loaded." networking/resolv.subr
480
481fi # ! $_NETWORKING_RESOLV_SUBR
482