ipaddr.subr revision 244675
1238438Sdteskeif [ ! "$_NETWORKING_IPADDR_SUBR" ]; then _NETWORKING_IPADDR_SUBR=1
2238438Sdteske#
3238438Sdteske# Copyright (c) 2006-2012 Devin Teske
4238438Sdteske# All Rights Reserved.
5238438Sdteske#
6238438Sdteske# Redistribution and use in source and binary forms, with or without
7238438Sdteske# modification, are permitted provided that the following conditions
8238438Sdteske# are met:
9238438Sdteske# 1. Redistributions of source code must retain the above copyright
10238438Sdteske#    notice, this list of conditions and the following disclaimer.
11238438Sdteske# 2. Redistributions in binary form must reproduce the above copyright
12238438Sdteske#    notice, this list of conditions and the following disclaimer in the
13238438Sdteske#    documentation and/or other materials provided with the distribution.
14238438Sdteske#
15238438Sdteske# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16238438Sdteske# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING BUT NOT LIMITED TO, THE
17238438Sdteske# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18238438Sdteske# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19238438Sdteske# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20238438Sdteske# DAMAGES (INLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21238438Sdteske# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22238438Sdteske# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23238438Sdteske# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24238438Sdteske# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25238438Sdteske# SUCH DAMAGE.
26238438Sdteske#
27238438Sdteske# $FreeBSD: head/usr.sbin/bsdconfig/networking/share/ipaddr.subr 244675 2012-12-25 10:47:45Z dteske $
28238438Sdteske#
29238438Sdteske############################################################ INCLUDES
30238438Sdteske
31240684SdteskeBSDCFG_SHARE="/usr/share/bsdconfig"
32240684Sdteske. $BSDCFG_SHARE/common.subr || exit 1
33244675Sdteskef_dprintf "%s: loading includes..." networking/ipaddr.subr
34240684Sdteskef_include $BSDCFG_SHARE/dialog.subr
35240684Sdteskef_include $BSDCFG_SHARE/strings.subr
36240684Sdteskef_include $BSDCFG_SHARE/networking/common.subr
37238438Sdteske
38240684SdteskeBSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="120.networking"
39238438Sdteskef_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
40238438Sdteske
41238438Sdteske############################################################ FUNCTIONS
42238438Sdteske
43238438Sdteske# f_ifconfig_inet $interface
44238438Sdteske#
45238438Sdteske# Returns the IPv4 address associated with $interface.
46238438Sdteske#
47238438Sdteskef_ifconfig_inet()
48238438Sdteske{
49238438Sdteske	local interface="$1"
50240783Sdteske	ifconfig "$interface" 2> /dev/null | awk \
51238438Sdteske	'
52238438Sdteske		BEGIN { found = 0 }
53238438Sdteske		( $1 == "inet" ) \
54238438Sdteske		{
55238438Sdteske			print $2
56238438Sdteske			found = 1
57238438Sdteske			exit
58238438Sdteske		}
59238438Sdteske		END { exit ! found }
60238438Sdteske	'
61238438Sdteske}
62238438Sdteske
63243504Sdteske# f_validate_ipaddr $ipaddr
64238438Sdteske#
65238438Sdteske# Returns zero if the given argument (an IP address) is of the proper format.
66238438Sdteske#
67238438Sdteske# The return status for invalid IP address is one of:
68238438Sdteske# 	1	One or more individual octets within the IP address (separated
69238438Sdteske# 	 	by dots) contains one or more invalid characters.
70238438Sdteske# 	2	One or more individual octets within the IP address are null
71238438Sdteske# 	 	and/or missing.
72238438Sdteske# 	3	One or more individual octets within the IP address exceeds the
73238438Sdteske# 	 	maximum of 255 (or 2^8, being an octet comprised of 8 bits).
74238438Sdteske# 	4	The IP address has either too few or too many octets.
75238438Sdteske#
76243504Sdteskef_validate_ipaddr()
77238438Sdteske{
78238438Sdteske	local ip="$1"
79238438Sdteske
80238438Sdteske	( # Operate within a sub-shell to protect the parent environment
81238438Sdteske
82238438Sdteske		# Track number of octets for error checking
83238438Sdteske		noctets=0
84238438Sdteske
85238438Sdteske		IFS="." # Split on `dot'
86238438Sdteske		for octet in $ip; do
87238438Sdteske
88238438Sdteske			# Return error if the octet is null
89238438Sdteske			[ "$octet" ] || exit 2
90238438Sdteske
91238438Sdteske			# Return error if not a whole integer
92238438Sdteske			f_isinteger "$octet" || exit 1
93238438Sdteske
94238438Sdteske			# Return error if not a positive integer
95238438Sdteske			[ $octet -ge 0 ] || exit 1
96238438Sdteske
97238438Sdteske			# Return error if the octet exceeds 255
98238438Sdteske			[ $octet -gt 255 ] && exit 3
99238438Sdteske
100238438Sdteske			noctets=$(( $noctets + 1 ))
101238438Sdteske
102238438Sdteske		done
103238438Sdteske
104238438Sdteske		[ $noctets -eq 4 ] || exit 4
105238438Sdteske	)
106243504Sdteske}
107243504Sdteske# f_dialog_iperror $error $ipaddr
108243504Sdteske#
109243504Sdteske# Display a msgbox with the appropriate error message for an error returned by
110243634Sdteske# the f_validate_ipaddr function above.
111243504Sdteske#
112243504Sdteskef_dialog_iperror()
113243504Sdteske{
114243504Sdteske	local error="$1" ip="$2"
115238438Sdteske
116243504Sdteske	[ ${error:-0} -ne 0 ] || return $SUCCESS
117243504Sdteske
118243504Sdteske	case "$error" in
119244554Sdteske	1) f_show_msg "$msg_ipv4_addr_octet_contains_invalid_chars" "$ip";;
120244554Sdteske	2) f_show_msg "$msg_ipv4_addr_octet_is_null" "$ip";;
121244554Sdteske	3) f_show_msg "$msg_ipv4_addr_octet_exceeds_max_value" "$ip";;
122244554Sdteske	4) f_show_msg "$msg_ipv4_addr_octet_missing_or_extra" "$ip";;
123238438Sdteske	esac
124243504Sdteske}
125238438Sdteske
126243504Sdteske# f_dialog_validate_ipaddr $ipaddr
127243504Sdteske#
128243504Sdteske# Returns zero if the given argument (an IP address) is of the proper format.
129243504Sdteske#
130243504Sdteske# If the IP address is determined to be invalid, the appropriate error will be
131243504Sdteske# displayed using the f_dialog_iperror function above.
132243504Sdteske#
133243504Sdteskef_dialog_validate_ipaddr()
134243504Sdteske{
135243504Sdteske	local ip="$1"
136243504Sdteske
137243504Sdteske	f_validate_ipaddr "$ip"
138243504Sdteske	local retval=$?
139243504Sdteske
140243504Sdteske	# Produce an appropriate error message if necessary.
141243504Sdteske	[ $retval -eq $SUCCESS ] || f_dialog_iperror $retval "$ip"
142243504Sdteske
143238438Sdteske	return $retval
144238438Sdteske}
145238438Sdteske
146243504Sdteske# f_validate_ipaddr6 $ipv6_addr
147243475Sdteske#
148243475Sdteske# Returns zero if the given argument (an IPv6 address) is of the proper format.
149243475Sdteske#
150243475Sdteske# The return status for invalid IP address is one of:
151243475Sdteske# 	1	One or more individual segments within the IP address
152243475Sdteske# 	 	(separated by colons) contains one or more invalid characters.
153243504Sdteske# 	 	Segments must contain only combinations of the characters 0-9,
154243504Sdteske# 	 	A-F, or a-f.
155243504Sdteske# 	2	Too many/incorrect null segments. A single null segment is
156243504Sdteske# 	 	allowed within the IP address (separated by colons) but not
157243504Sdteske# 	 	allowed at the beginning or end (unless a double-null segment;
158243504Sdteske# 	 	i.e., "::*" or "*::").
159243504Sdteske# 	3	One or more individual segments within the IP address
160243504Sdteske# 	 	(separated by colons) exceeds the length of 4 hex-digits.
161243504Sdteske# 	4	The IP address entered has either too few (less than 3), too
162243504Sdteske# 	 	many (more than 8), or not enough segments, separated by
163243504Sdteske# 	 	colons.
164243504Sdteske# 	5*	The IPv4 address at the end of the IPv6 address is invalid.
165243504Sdteske# 	*	When there is an error with the dotted-quad IPv4 address at the
166243504Sdteske# 	 	end of the IPv6 address, the return value of 5 is OR'd with a
167243504Sdteske# 	 	bit-shifted (<< 4) return of f_validate_ipaddr.
168243475Sdteske#
169243504Sdteskef_validate_ipaddr6()
170243475Sdteske{
171243475Sdteske	local ip="$1"
172243475Sdteske
173243475Sdteske	( # Operate within a sub-shell to protect the parent environment
174243475Sdteske
175243475Sdteske		IFS=":" # Split on `colon'
176243475Sdteske		set -- $ip:
177243475Sdteske
178243475Sdteske		# Return error if too many or too few segments
179243475Sdteske		# Using 9 as max in case of leading or trailing null spanner
180243475Sdteske		[ $# -gt 9 -o $# -lt 3 ] && exit 4
181243475Sdteske
182243475Sdteske		h="[0-9A-Fa-f]"
183243475Sdteske		nulls=0
184243475Sdteske		nsegments=$#
185243475Sdteske		contains_ipv4_segment=
186243475Sdteske
187243475Sdteske		while [ $# -gt 0 ]; do
188243475Sdteske
189243475Sdteske			segment="${1%:}"
190243475Sdteske			shift
191243475Sdteske
192243475Sdteske			#
193243475Sdteske			# Return error if this segment makes one null too-many.
194243475Sdteske			# A single null segment is allowed anywhere in the
195243475Sdteske			# middle as well as double null segments are allowed at
196243475Sdteske			# the beginning or end (but not both).
197243475Sdteske			#
198243475Sdteske			if [ ! "$segment" ]; then
199243475Sdteske				nulls=$(( $nulls + 1 ))
200243475Sdteske				if [ $nulls -eq 3 ]; then
201243475Sdteske					# Only valid syntax for 3 nulls is `::'
202243475Sdteske					[ "$ip" = "::" ] || exit 2
203243475Sdteske				elif [ $nulls -eq 2 ]; then
204243475Sdteske					# Only valid if begins/ends with `::'
205243475Sdteske					case "$ip" in
206243475Sdteske					::*|*::) : fall thru ;;
207243475Sdteske					*) exit 2
208243475Sdteske					esac
209243475Sdteske				fi
210243475Sdteske				continue
211243475Sdteske			fi
212243475Sdteske
213243475Sdteske			#
214243475Sdteske			# Return error if not a valid hexadecimal short
215243475Sdteske			#
216243475Sdteske			case "$segment" in
217243475Sdteske			$h|$h$h|$h$h$h|$h$h$h$h)
218243475Sdteske				: valid segment of 1-4 hexadecimal digits
219243475Sdteske				;;
220243475Sdteske			*[!0-9A-Fa-f]*)
221243475Sdteske				# Segment contains at least one invalid char
222243475Sdteske
223243475Sdteske				# Return error immediately if not last segment
224243475Sdteske				[ $# -eq 0 ] || exit 1
225243475Sdteske
226243475Sdteske				# Otherwise, check for legacy IPv4 notation
227243475Sdteske				case "$segment" in
228243475Sdteske				*[!0-9.]*)
229243475Sdteske					# Segment contains at least one invalid
230243475Sdteske					# character even for an IPv4 address
231243475Sdteske					exit 1
232243475Sdteske				esac
233243475Sdteske
234243475Sdteske				# Return error if not enough segments
235243475Sdteske				if [ $nulls -eq 0 ]; then
236243475Sdteske					[ $nsegments -eq 7 ] || exit 4
237243475Sdteske				fi
238243475Sdteske
239243475Sdteske				contains_ipv4_segment=1
240243475Sdteske
241243475Sdteske				# Validate the IPv4 address
242243504Sdteske				f_validate_ipaddr "$segment" ||
243243504Sdteske					exit $(( 5 | $? << 4 ))
244243475Sdteske				;;
245243475Sdteske			*)
246243475Sdteske				# Segment characters are all valid but too many
247243475Sdteske				exit 3
248243475Sdteske			esac
249243475Sdteske
250243475Sdteske		done
251243475Sdteske
252243475Sdteske		if [ $nulls -eq 1 ]; then
253243475Sdteske			# Single null segment cannot be at beginning/end
254243475Sdteske			case "$ip" in
255243475Sdteske			:*|*:) exit 2
256243475Sdteske			esac
257243475Sdteske		fi
258243475Sdteske
259243475Sdteske		#
260243475Sdteske		# A legacy IPv4 address can span the last two 16-bit segments,
261243475Sdteske		# reducing the amount of maximum allowable segments by-one.
262243475Sdteske		#
263243475Sdteske		maxsegments=8
264243475Sdteske		if [ "$contains_ipv4_segment" ]; then
265243475Sdteske			maxsegments=7
266243475Sdteske		fi
267243475Sdteske
268243475Sdteske		case $nulls in
269243475Sdteske		# Return error if missing segments with no null spanner
270243475Sdteske		0) [ $nsegments -eq $maxsegments ] || exit 4 ;;
271243475Sdteske		# Return error if null spanner with too many segments
272243475Sdteske		1) [ $nsegments -le $maxsegments ] || exit 4 ;;
273243475Sdteske		# Return error if leading/trailing `::' with too many segments
274243475Sdteske		2) [ $nsegments -le $(( $maxsegments + 1 )) ] || exit 4 ;;
275243475Sdteske		esac
276243475Sdteske
277243475Sdteske		exit $SUCCESS
278243475Sdteske	)
279243504Sdteske}
280243475Sdteske
281243504Sdteske# f_dialog_ip6error $error $ipv6_addr
282243504Sdteske#
283243504Sdteske# Display a msgbox with the appropriate error message for an error returned by
284243634Sdteske# the f_validate_ipaddr6 function above.
285243504Sdteske#
286243504Sdteskef_dialog_ip6error()
287243504Sdteske{
288243504Sdteske	local error="$1" ip="$2"
289243504Sdteske
290243504Sdteske	[ ${error:-0} -ne 0 ] || return $SUCCESS
291243504Sdteske
292243504Sdteske	case "$error" in
293244554Sdteske	1) f_show_msg "$msg_ipv6_addr_segment_contains_invalid_chars" "$ip";;
294244554Sdteske	2) f_show_msg "$msg_ipv6_addr_too_many_null_segments" "$ip";;
295244554Sdteske	3) f_show_msg "$msg_ipv6_addr_segment_contains_too_many_chars" "$ip";;
296244554Sdteske	4) f_show_msg "$msg_ipv6_addr_too_few_or_extra_segments" "$ip";;
297243504Sdteske	*)
298243504Sdteske		if [ $(( $error & 0xF )) -eq 5 ]; then
299243504Sdteske			# IPv4 at the end of IPv6 address is invalid
300243504Sdteske			f_dialog_iperror $(( $error >> 4 )) "$ip"
301243504Sdteske		fi
302243475Sdteske	esac
303243504Sdteske}
304243475Sdteske
305243504Sdteske# f_dialog_validate_ipaddr6 $ipv6_addr
306243504Sdteske#
307243504Sdteske# Returns zero if the given argument (an IPv6 address) is of the proper format.
308243504Sdteske#
309243504Sdteske# If the IP address is determined to be invalid, the appropriate error will be
310243504Sdteske# displayed using the f_dialog_ip6error function above.
311243504Sdteske#
312243504Sdteskef_dialog_validate_ipaddr6()
313243504Sdteske{
314243504Sdteske	local ip="$1"
315243504Sdteske
316243504Sdteske	f_validate_ipaddr6 "$ip"
317243504Sdteske	local retval=$?
318243504Sdteske
319243504Sdteske	# Produce an appropriate error message if necessary.
320243504Sdteske	[ $retval -eq $SUCCESS ] || f_dialog_ip6error $retval "$ip"
321243504Sdteske
322243475Sdteske	return $retval
323243475Sdteske}
324243475Sdteske
325238438Sdteske# f_dialog_input_ipaddr $interface $ipaddr
326238438Sdteske#
327238438Sdteske# Allows the user to edit a given IP address. If the user does not cancel or
328238438Sdteske# press ESC, the $ipaddr environment variable will hold the newly-configured
329238438Sdteske# value upon return.
330238438Sdteske#
331238438Sdteske# Optionally, the user can enter the format "IP_ADDRESS/NBITS" to set the
332238438Sdteske# netmask at the same time as the IP address. If such a format is entered by
333238438Sdteske# the user, the $netmask environment variable will hold the newly-configured
334238438Sdteske# netmask upon return.
335238438Sdteske#
336238438Sdteskef_dialog_input_ipaddr()
337238438Sdteske{
338238438Sdteske	local interface="$1" _ipaddr="$2" _input
339238438Sdteske
340238438Sdteske	#
341238438Sdteske	# Return with-error when there are NFS-mounts currently active. If the
342238438Sdteske	# IP address is changed while NFS-exported directories are mounted, the
343238438Sdteske	# system may hang (if any NFS mounts are using that interface).
344238438Sdteske	#
345238438Sdteske	if f_nfs_mounted && ! f_jailed; then
346238438Sdteske		local setting="$( printf "$msg_current_ipaddr" \
347238438Sdteske		                         "$interface" "$_ipaddr" )"
348244554Sdteske		f_show_msg "$msg_nfs_mounts_may_cause_hang" "$setting"
349238438Sdteske		return $FAILURE
350238438Sdteske	fi
351238438Sdteske
352238438Sdteske	local msg="$( printf "$msg_please_enter_new_ip_addr" "$interface" )"
353238438Sdteske
354238438Sdteske	#
355238438Sdteske	# Loop until the user provides taint-free input.
356238438Sdteske	#
357244548Sdteske	local retval
358238438Sdteske	while :; do
359238438Sdteske		#
360238438Sdteske		# Return error status if:
361244548Sdteske		# - User has either pressed ESC or chosen Cancel/No
362238438Sdteske		# - User has not made any changes to the given value
363238438Sdteske		#
364244548Sdteske		_input=$( f_dialog_input "$msg" "$_ipaddr" \
365244548Sdteske		                         "$hline_num_punc_tab_enter"
366244548Sdteske		        ) || return
367238438Sdteske		[ "$_ipaddr" = "$_input" ] && return $FAILURE
368238438Sdteske
369238438Sdteske		# Return success if NULL value was entered
370238438Sdteske		[ "$_input" ] || return $SUCCESS
371238438Sdteske
372238438Sdteske		# Take only the first "word" of the user's input
373238438Sdteske		_ipaddr="$_input"
374238438Sdteske		_ipaddr="${_ipaddr%%[$IFS]*}"
375238438Sdteske
376238438Sdteske		# Taint-check the user's input
377238438Sdteske		f_dialog_validate_ipaddr "${_ipaddr%%/*}" && break
378238438Sdteske	done
379238438Sdteske
380238438Sdteske	#
381238438Sdteske	# Support the syntax: IP_ADDRESS/NBITS
382238438Sdteske	#
383238438Sdteske	local _netmask=""
384238438Sdteske	case "$_ipaddr" in
385238438Sdteske	*/*)
386238438Sdteske		local nbits="${_ipaddr#*/}" n=0
387238438Sdteske		_ipaddr="${_ipaddr%%/*}"
388238438Sdteske
389238438Sdteske		#
390238438Sdteske		# Taint-check $nbits to be (a) a positive whole-integer,
391238438Sdteske		# and (b) to be less than or equal to 32. Otherwise, set
392238438Sdteske		# $n so that the below loop never executes.
393238438Sdteske		#
394238438Sdteske		( f_isinteger "$nbits" && [ $nbits -ge 0 -a $nbits -le 32 ] ) \
395238438Sdteske			|| n=4
396238438Sdteske
397238438Sdteske		while [ $n -lt 4 ]; do
398238438Sdteske			_netmask="$_netmask${_netmask:+.}$((
399238438Sdteske				(65280 >> ($nbits - 8 * $n) & 255)
400238438Sdteske				* ((8*$n) < $nbits & $nbits <= (8*($n+1)))
401238438Sdteske				+ 255 * ($nbits > (8*($n+1)))
402238438Sdteske			))"
403238438Sdteske			n=$(( $n + 1 ))
404238438Sdteske		done
405238438Sdteske		;;
406238438Sdteske	esac
407238438Sdteske
408238438Sdteske	ipaddr="$_ipaddr"
409238438Sdteske	[ "$_netmask" ] && netmask="$_netmask"
410238438Sdteske
411238438Sdteske	return $SUCCESS
412238438Sdteske}
413238438Sdteske
414244675Sdteske############################################################ MAIN
415244675Sdteske
416244675Sdteskef_dprintf "%s: Successfully loaded." networking/ipaddr.subr
417244675Sdteske
418238438Sdteskefi # ! $_NETWORKING_IPADDR_SUBR
419