ipaddr.subr revision 243504
1if [ ! "$_NETWORKING_IPADDR_SUBR" ]; then _NETWORKING_IPADDR_SUBR=1
2#
3# Copyright (c) 2006-2012 Devin Teske
4# All Rights Reserved.
5#
6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions
8# are met:
9# 1. Redistributions of source code must retain the above copyright
10#    notice, this list of conditions and the following disclaimer.
11# 2. Redistributions in binary form must reproduce the above copyright
12#    notice, this list of conditions and the following disclaimer in the
13#    documentation and/or other materials provided with the distribution.
14#
15# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING BUT NOT LIMITED TO, THE
17# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20# DAMAGES (INLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25# SUCH DAMAGE.
26#
27# $FreeBSD: head/usr.sbin/bsdconfig/networking/share/ipaddr.subr 243504 2012-11-25 10:37:10Z dteske $
28#
29############################################################ INCLUDES
30
31BSDCFG_SHARE="/usr/share/bsdconfig"
32. $BSDCFG_SHARE/common.subr || exit 1
33f_include $BSDCFG_SHARE/sysrc.subr
34f_include $BSDCFG_SHARE/dialog.subr
35f_include $BSDCFG_SHARE/strings.subr
36f_include $BSDCFG_SHARE/networking/common.subr
37
38BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="120.networking"
39f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
40
41############################################################ FUNCTIONS
42
43# f_ifconfig_inet $interface
44#
45# Returns the IPv4 address associated with $interface.
46#
47f_ifconfig_inet()
48{
49	local interface="$1"
50	ifconfig "$interface" 2> /dev/null | awk \
51	'
52		BEGIN { found = 0 }
53		( $1 == "inet" ) \
54		{
55			print $2
56			found = 1
57			exit
58		}
59		END { exit ! found }
60	'
61}
62
63# f_validate_ipaddr $ipaddr
64#
65# Returns zero if the given argument (an IP address) is of the proper format.
66#
67# The return status for invalid IP address is one of:
68# 	1	One or more individual octets within the IP address (separated
69# 	 	by dots) contains one or more invalid characters.
70# 	2	One or more individual octets within the IP address are null
71# 	 	and/or missing.
72# 	3	One or more individual octets within the IP address exceeds the
73# 	 	maximum of 255 (or 2^8, being an octet comprised of 8 bits).
74# 	4	The IP address has either too few or too many octets.
75#
76f_validate_ipaddr()
77{
78	local ip="$1"
79
80	( # Operate within a sub-shell to protect the parent environment
81
82		# Track number of octets for error checking
83		noctets=0
84
85		IFS="." # Split on `dot'
86		for octet in $ip; do
87
88			# Return error if the octet is null
89			[ "$octet" ] || exit 2
90
91			# Return error if not a whole integer
92			f_isinteger "$octet" || exit 1
93
94			# Return error if not a positive integer
95			[ $octet -ge 0 ] || exit 1
96
97			# Return error if the octet exceeds 255
98			[ $octet -gt 255 ] && exit 3
99
100			noctets=$(( $noctets + 1 ))
101
102		done
103
104		[ $noctets -eq 4 ] || exit 4
105	)
106}
107# f_dialog_iperror $error $ipaddr
108#
109# Display a msgbox with the appropriate error message for an error returned by
110# f_validate_ipaddr above.
111#
112f_dialog_iperror()
113{
114	local error="$1" ip="$2"
115
116	[ ${error:-0} -ne 0 ] || return $SUCCESS
117
118	case "$error" in
119	1) f_dialog_msgbox "$( printf \
120		"$msg_ipv4_addr_octet_contains_invalid_chars" "$ip" )";;
121	2) f_dialog_msgbox "$( printf \
122		"$msg_ipv4_addr_octet_is_null" "$ip" )";;
123	3) f_dialog_msgbox "$( printf \
124		"$msg_ipv4_addr_octet_exceeds_max_value" "$ip" )";;
125	4) f_dialog_msgbox "$( printf \
126		"$msg_ipv4_addr_octet_missing_or_extra" "$ip" )";;
127	esac
128}
129
130# f_dialog_validate_ipaddr $ipaddr
131#
132# Returns zero if the given argument (an IP address) is of the proper format.
133#
134# If the IP address is determined to be invalid, the appropriate error will be
135# displayed using the f_dialog_iperror function above.
136#
137f_dialog_validate_ipaddr()
138{
139	local ip="$1"
140
141	f_validate_ipaddr "$ip"
142	local retval=$?
143
144	# Produce an appropriate error message if necessary.
145	[ $retval -eq $SUCCESS ] || f_dialog_iperror $retval "$ip"
146
147	return $retval
148}
149
150# f_validate_ipaddr6 $ipv6_addr
151#
152# Returns zero if the given argument (an IPv6 address) is of the proper format.
153#
154# The return status for invalid IP address is one of:
155# 	1	One or more individual segments within the IP address
156# 	 	(separated by colons) contains one or more invalid characters.
157# 	 	Segments must contain only combinations of the characters 0-9,
158# 	 	A-F, or a-f.
159# 	2	Too many/incorrect null segments. A single null segment is
160# 	 	allowed within the IP address (separated by colons) but not
161# 	 	allowed at the beginning or end (unless a double-null segment;
162# 	 	i.e., "::*" or "*::").
163# 	3	One or more individual segments within the IP address
164# 	 	(separated by colons) exceeds the length of 4 hex-digits.
165# 	4	The IP address entered has either too few (less than 3), too
166# 	 	many (more than 8), or not enough segments, separated by
167# 	 	colons.
168# 	5*	The IPv4 address at the end of the IPv6 address is invalid.
169# 	*	When there is an error with the dotted-quad IPv4 address at the
170# 	 	end of the IPv6 address, the return value of 5 is OR'd with a
171# 	 	bit-shifted (<< 4) return of f_validate_ipaddr.
172#
173f_validate_ipaddr6()
174{
175	local ip="$1"
176
177	( # Operate within a sub-shell to protect the parent environment
178
179		IFS=":" # Split on `colon'
180		set -- $ip:
181
182		# Return error if too many or too few segments
183		# Using 9 as max in case of leading or trailing null spanner
184		[ $# -gt 9 -o $# -lt 3 ] && exit 4
185
186		h="[0-9A-Fa-f]"
187		nulls=0
188		nsegments=$#
189		contains_ipv4_segment=
190
191		while [ $# -gt 0 ]; do
192
193			segment="${1%:}"
194			shift
195
196			#
197			# Return error if this segment makes one null too-many.
198			# A single null segment is allowed anywhere in the
199			# middle as well as double null segments are allowed at
200			# the beginning or end (but not both).
201			#
202			if [ ! "$segment" ]; then
203				nulls=$(( $nulls + 1 ))
204				if [ $nulls -eq 3 ]; then
205					# Only valid syntax for 3 nulls is `::'
206					[ "$ip" = "::" ] || exit 2
207				elif [ $nulls -eq 2 ]; then
208					# Only valid if begins/ends with `::'
209					case "$ip" in
210					::*|*::) : fall thru ;;
211					*) exit 2
212					esac
213				fi
214				continue
215			fi
216
217			#
218			# Return error if not a valid hexadecimal short
219			#
220			case "$segment" in
221			$h|$h$h|$h$h$h|$h$h$h$h)
222				: valid segment of 1-4 hexadecimal digits
223				;;
224			*[!0-9A-Fa-f]*)
225				# Segment contains at least one invalid char
226
227				# Return error immediately if not last segment
228				[ $# -eq 0 ] || exit 1
229
230				# Otherwise, check for legacy IPv4 notation
231				case "$segment" in
232				*[!0-9.]*)
233					# Segment contains at least one invalid
234					# character even for an IPv4 address
235					exit 1
236				esac
237
238				# Return error if not enough segments
239				if [ $nulls -eq 0 ]; then
240					[ $nsegments -eq 7 ] || exit 4
241				fi
242
243				contains_ipv4_segment=1
244
245				# Validate the IPv4 address
246				f_validate_ipaddr "$segment" ||
247					exit $(( 5 | $? << 4 ))
248				;;
249			*)
250				# Segment characters are all valid but too many
251				exit 3
252			esac
253
254		done
255
256		if [ $nulls -eq 1 ]; then
257			# Single null segment cannot be at beginning/end
258			case "$ip" in
259			:*|*:) exit 2
260			esac
261		fi
262
263		#
264		# A legacy IPv4 address can span the last two 16-bit segments,
265		# reducing the amount of maximum allowable segments by-one.
266		#
267		maxsegments=8
268		if [ "$contains_ipv4_segment" ]; then
269			maxsegments=7
270		fi
271
272		case $nulls in
273		# Return error if missing segments with no null spanner
274		0) [ $nsegments -eq $maxsegments ] || exit 4 ;;
275		# Return error if null spanner with too many segments
276		1) [ $nsegments -le $maxsegments ] || exit 4 ;;
277		# Return error if leading/trailing `::' with too many segments
278		2) [ $nsegments -le $(( $maxsegments + 1 )) ] || exit 4 ;;
279		esac
280
281		exit $SUCCESS
282	)
283}
284
285# f_dialog_ip6error $error $ipv6_addr
286#
287# Display a msgbox with the appropriate error message for an error returned by
288# f_validate_ipaddr6 above.
289#
290f_dialog_ip6error()
291{
292	local error="$1" ip="$2"
293
294	[ ${error:-0} -ne 0 ] || return $SUCCESS
295
296	case "$error" in
297	1) f_dialog_msgbox "$( printf \
298		"$msg_ipv6_addr_segment_contains_invalid_chars" "$ip" )";;
299	2) f_dialog_msgbox "$( printf \
300		"$msg_ipv6_addr_too_many_null_segments" "$ip" )";;
301	3) f_dialog_msgbox "$( printf \
302		"$msg_ipv6_addr_segment_contains_too_many_chars" "$ip" )";;
303	4) f_dialog_msgbox "$( printf \
304		"$msg_ipv6_addr_too_few_or_extra_segments" "$ip" )";;
305	*)
306		if [ $(( $error & 0xF )) -eq 5 ]; then
307			# IPv4 at the end of IPv6 address is invalid
308			f_dialog_iperror $(( $error >> 4 )) "$ip"
309		fi
310	esac
311}
312
313# f_dialog_validate_ipaddr6 $ipv6_addr
314#
315# Returns zero if the given argument (an IPv6 address) is of the proper format.
316#
317# If the IP address is determined to be invalid, the appropriate error will be
318# displayed using the f_dialog_ip6error function above.
319#
320f_dialog_validate_ipaddr6()
321{
322	local ip="$1"
323
324	f_validate_ipaddr6 "$ip"
325	local retval=$?
326
327	# Produce an appropriate error message if necessary.
328	[ $retval -eq $SUCCESS ] || f_dialog_ip6error $retval "$ip"
329
330	return $retval
331}
332
333# f_dialog_input_ipaddr $interface $ipaddr
334#
335# Allows the user to edit a given IP address. If the user does not cancel or
336# press ESC, the $ipaddr environment variable will hold the newly-configured
337# value upon return.
338#
339# Optionally, the user can enter the format "IP_ADDRESS/NBITS" to set the
340# netmask at the same time as the IP address. If such a format is entered by
341# the user, the $netmask environment variable will hold the newly-configured
342# netmask upon return.
343#
344f_dialog_input_ipaddr()
345{
346	local interface="$1" _ipaddr="$2" _input
347
348	#
349	# Return with-error when there are NFS-mounts currently active. If the
350	# IP address is changed while NFS-exported directories are mounted, the
351	# system may hang (if any NFS mounts are using that interface).
352	#
353	if f_nfs_mounted && ! f_jailed; then
354		local setting="$( printf "$msg_current_ipaddr" \
355		                         "$interface" "$_ipaddr" )"
356		local message="$( printf "$msg_nfs_mounts_may_cause_hang" \
357		                         "$setting" )"
358		f_dialog_msgbox "$message"
359		return $FAILURE
360	fi
361
362	local msg="$( printf "$msg_please_enter_new_ip_addr" "$interface" )"
363	local hline="$hline_num_punc_tab_enter"
364	local size="$( f_dialog_inputbox_size \
365	               		"$DIALOG_TITLE"     \
366	               		"$DIALOG_BACKTITLE" \
367	               		"$msg"              \
368	               		"$_ipaddr"          \
369	               		"$hline"            )"
370
371	#
372	# Loop until the user provides taint-free input.
373	#
374	while :; do
375		local dialog_inputbox
376		dialog_inputbox=$( eval $DIALOG \
377			--title \"\$DIALOG_TITLE\"         \
378		        --backtitle \"\$DIALOG_BACKTITLE\" \
379			--hline \"\$hline\"                \
380			--ok-label \"\$msg_ok\"            \
381			--cancel-label \"\$msg_cancel\"    \
382			--inputbox \"\$msg\" $size         \
383			\"\$_ipaddr\"                      \
384			2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
385		)
386
387		local retval=$?
388		setvar DIALOG_INPUTBOX_$$ "$dialog_inputbox"
389		_input=$( f_dialog_inputstr )
390
391		#
392		# Return error status if:
393		# - User has not made any changes to the given value
394		# - User has either pressed ESC or chosen Cancel/No
395		#
396		[ "$_ipaddr" = "$_input" ] && return $FAILURE
397		[ $retval -eq $SUCCESS ] || return $retval
398
399		# Return success if NULL value was entered
400		[ "$_input" ] || return $SUCCESS
401
402		# Take only the first "word" of the user's input
403		_ipaddr="$_input"
404		_ipaddr="${_ipaddr%%[$IFS]*}"
405
406		# Taint-check the user's input
407		f_dialog_validate_ipaddr "${_ipaddr%%/*}" && break
408	done
409
410	#
411	# Support the syntax: IP_ADDRESS/NBITS
412	#
413	local _netmask=""
414	case "$_ipaddr" in
415	*/*)
416		local nbits="${_ipaddr#*/}" n=0
417		_ipaddr="${_ipaddr%%/*}"
418
419		#
420		# Taint-check $nbits to be (a) a positive whole-integer,
421		# and (b) to be less than or equal to 32. Otherwise, set
422		# $n so that the below loop never executes.
423		#
424		( f_isinteger "$nbits" && [ $nbits -ge 0 -a $nbits -le 32 ] ) \
425			|| n=4
426
427		while [ $n -lt 4 ]; do
428			_netmask="$_netmask${_netmask:+.}$((
429				(65280 >> ($nbits - 8 * $n) & 255)
430				* ((8*$n) < $nbits & $nbits <= (8*($n+1)))
431				+ 255 * ($nbits > (8*($n+1)))
432			))"
433			n=$(( $n + 1 ))
434		done
435		;;
436	esac
437
438	ipaddr="$_ipaddr"
439	[ "$_netmask" ] && netmask="$_netmask"
440
441	return $SUCCESS
442}
443
444fi # ! $_NETWORKING_IPADDR_SUBR
445