xref: /freebsd/usr.sbin/bsdconfig/networking/share/ipaddr.subr (revision 3c91c65a2b999a8d27e99bcc6493de7cf812c948)
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$
28#
29############################################################ INCLUDES
30
31BSDCFG_SHARE="/usr/share/bsdconfig"
32. $BSDCFG_SHARE/common.subr || exit 1
33f_dprintf "%s: loading includes..." networking/ipaddr.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
108# f_dialog_iperror $error $ipaddr
109#
110# Display a msgbox with the appropriate error message for an error returned by
111# the f_validate_ipaddr function above.
112#
113f_dialog_iperror()
114{
115	local error="$1" ip="$2"
116
117	[ ${error:-0} -ne 0 ] || return $SUCCESS
118
119	case "$error" in
120	1) f_show_msg "$msg_ipv4_addr_octet_contains_invalid_chars" "$ip";;
121	2) f_show_msg "$msg_ipv4_addr_octet_is_null" "$ip";;
122	3) f_show_msg "$msg_ipv4_addr_octet_exceeds_max_value" "$ip";;
123	4) f_show_msg "$msg_ipv4_addr_octet_missing_or_extra" "$ip";;
124	esac
125}
126
127# f_dialog_validate_ipaddr $ipaddr
128#
129# Returns zero if the given argument (an IP address) is of the proper format.
130#
131# If the IP address is determined to be invalid, the appropriate error will be
132# displayed using the f_dialog_iperror function above.
133#
134f_dialog_validate_ipaddr()
135{
136	local ip="$1"
137
138	f_validate_ipaddr "$ip"
139	local retval=$?
140
141	# Produce an appropriate error message if necessary.
142	[ $retval -eq $SUCCESS ] || f_dialog_iperror $retval "$ip"
143
144	return $retval
145}
146
147# f_validate_ipaddr6 $ipv6_addr
148#
149# Returns zero if the given argument (an IPv6 address) is of the proper format.
150#
151# The return status for invalid IP address is one of:
152# 	1	One or more individual segments within the IP address
153# 	 	(separated by colons) contains one or more invalid characters.
154# 	 	Segments must contain only combinations of the characters 0-9,
155# 	 	A-F, or a-f.
156# 	2	Too many/incorrect null segments. A single null segment is
157# 	 	allowed within the IP address (separated by colons) but not
158# 	 	allowed at the beginning or end (unless a double-null segment;
159# 	 	i.e., "::*" or "*::").
160# 	3	One or more individual segments within the IP address
161# 	 	(separated by colons) exceeds the length of 4 hex-digits.
162# 	4	The IP address entered has either too few (less than 3), too
163# 	 	many (more than 8), or not enough segments, separated by
164# 	 	colons.
165# 	5*	The IPv4 address at the end of the IPv6 address is invalid.
166# 	*	When there is an error with the dotted-quad IPv4 address at the
167# 	 	end of the IPv6 address, the return value of 5 is OR'd with a
168# 	 	bit-shifted (<< 4) return of f_validate_ipaddr.
169#
170f_validate_ipaddr6()
171{
172	local ip="$1"
173
174	( # Operate within a sub-shell to protect the parent environment
175
176		IFS=":" # Split on `colon'
177		set -- $ip:
178
179		# Return error if too many or too few segments
180		# Using 9 as max in case of leading or trailing null spanner
181		[ $# -gt 9 -o $# -lt 3 ] && exit 4
182
183		h="[0-9A-Fa-f]"
184		nulls=0
185		nsegments=$#
186		contains_ipv4_segment=
187
188		while [ $# -gt 0 ]; do
189
190			segment="${1%:}"
191			shift
192
193			#
194			# Return error if this segment makes one null too-many.
195			# A single null segment is allowed anywhere in the
196			# middle as well as double null segments are allowed at
197			# the beginning or end (but not both).
198			#
199			if [ ! "$segment" ]; then
200				nulls=$(( $nulls + 1 ))
201				if [ $nulls -eq 3 ]; then
202					# Only valid syntax for 3 nulls is `::'
203					[ "$ip" = "::" ] || exit 2
204				elif [ $nulls -eq 2 ]; then
205					# Only valid if begins/ends with `::'
206					case "$ip" in
207					::*|*::) : fall thru ;;
208					*) exit 2
209					esac
210				fi
211				continue
212			fi
213
214			#
215			# Return error if not a valid hexadecimal short
216			#
217			case "$segment" in
218			$h|$h$h|$h$h$h|$h$h$h$h)
219				: valid segment of 1-4 hexadecimal digits
220				;;
221			*[!0-9A-Fa-f]*)
222				# Segment contains at least one invalid char
223
224				# Return error immediately if not last segment
225				[ $# -eq 0 ] || exit 1
226
227				# Otherwise, check for legacy IPv4 notation
228				case "$segment" in
229				*[!0-9.]*)
230					# Segment contains at least one invalid
231					# character even for an IPv4 address
232					exit 1
233				esac
234
235				# Return error if not enough segments
236				if [ $nulls -eq 0 ]; then
237					[ $nsegments -eq 7 ] || exit 4
238				fi
239
240				contains_ipv4_segment=1
241
242				# Validate the IPv4 address
243				f_validate_ipaddr "$segment" ||
244					exit $(( 5 | $? << 4 ))
245				;;
246			*)
247				# Segment characters are all valid but too many
248				exit 3
249			esac
250
251		done
252
253		if [ $nulls -eq 1 ]; then
254			# Single null segment cannot be at beginning/end
255			case "$ip" in
256			:*|*:) exit 2
257			esac
258		fi
259
260		#
261		# A legacy IPv4 address can span the last two 16-bit segments,
262		# reducing the amount of maximum allowable segments by-one.
263		#
264		maxsegments=8
265		if [ "$contains_ipv4_segment" ]; then
266			maxsegments=7
267		fi
268
269		case $nulls in
270		# Return error if missing segments with no null spanner
271		0) [ $nsegments -eq $maxsegments ] || exit 4 ;;
272		# Return error if null spanner with too many segments
273		1) [ $nsegments -le $maxsegments ] || exit 4 ;;
274		# Return error if leading/trailing `::' with too many segments
275		2) [ $nsegments -le $(( $maxsegments + 1 )) ] || exit 4 ;;
276		esac
277
278		exit $SUCCESS
279	)
280}
281
282# f_dialog_ip6error $error $ipv6_addr
283#
284# Display a msgbox with the appropriate error message for an error returned by
285# the f_validate_ipaddr6 function above.
286#
287f_dialog_ip6error()
288{
289	local error="$1" ip="$2"
290
291	[ ${error:-0} -ne 0 ] || return $SUCCESS
292
293	case "$error" in
294	1) f_show_msg "$msg_ipv6_addr_segment_contains_invalid_chars" "$ip";;
295	2) f_show_msg "$msg_ipv6_addr_too_many_null_segments" "$ip";;
296	3) f_show_msg "$msg_ipv6_addr_segment_contains_too_many_chars" "$ip";;
297	4) f_show_msg "$msg_ipv6_addr_too_few_or_extra_segments" "$ip";;
298	*)
299		if [ $(( $error & 0xF )) -eq 5 ]; then
300			# IPv4 at the end of IPv6 address is invalid
301			f_dialog_iperror $(( $error >> 4 )) "$ip"
302		fi
303	esac
304}
305
306# f_dialog_validate_ipaddr6 $ipv6_addr
307#
308# Returns zero if the given argument (an IPv6 address) is of the proper format.
309#
310# If the IP address is determined to be invalid, the appropriate error will be
311# displayed using the f_dialog_ip6error function above.
312#
313f_dialog_validate_ipaddr6()
314{
315	local ip="$1"
316
317	f_validate_ipaddr6 "$ip"
318	local retval=$?
319
320	# Produce an appropriate error message if necessary.
321	[ $retval -eq $SUCCESS ] || f_dialog_ip6error $retval "$ip"
322
323	return $retval
324}
325
326# f_dialog_input_ipaddr $interface $ipaddr
327#
328# Allows the user to edit a given IP address. If the user does not cancel or
329# press ESC, the $ipaddr environment variable will hold the newly-configured
330# value upon return.
331#
332# Optionally, the user can enter the format "IP_ADDRESS/NBITS" to set the
333# netmask at the same time as the IP address. If such a format is entered by
334# the user, the $netmask environment variable will hold the newly-configured
335# netmask upon return.
336#
337f_dialog_input_ipaddr()
338{
339	local interface="$1" _ipaddr="$2" _input
340
341	#
342	# Return with-error when there are NFS-mounts currently active. If the
343	# IP address is changed while NFS-exported directories are mounted, the
344	# system may hang (if any NFS mounts are using that interface).
345	#
346	if f_nfs_mounted && ! f_jailed; then
347		local setting="$( printf "$msg_current_ipaddr" \
348		                         "$interface" "$_ipaddr" )"
349		f_show_msg "$msg_nfs_mounts_may_cause_hang" "$setting"
350		return $FAILURE
351	fi
352
353	local msg="$( printf "$msg_please_enter_new_ip_addr" "$interface" )"
354
355	#
356	# Loop until the user provides taint-free input.
357	#
358	local retval
359	while :; do
360		#
361		# Return error status if:
362		# - User has either pressed ESC or chosen Cancel/No
363		# - User has not made any changes to the given value
364		#
365		_input=$( f_dialog_input "$msg" "$_ipaddr" \
366		                         "$hline_num_punc_tab_enter"
367		        ) || return
368		[ "$_ipaddr" = "$_input" ] && return $FAILURE
369
370		# Return success if NULL value was entered
371		[ "$_input" ] || return $SUCCESS
372
373		# Take only the first "word" of the user's input
374		_ipaddr="$_input"
375		_ipaddr="${_ipaddr%%[$IFS]*}"
376
377		# Taint-check the user's input
378		f_dialog_validate_ipaddr "${_ipaddr%%/*}" && break
379	done
380
381	#
382	# Support the syntax: IP_ADDRESS/NBITS
383	#
384	local _netmask=""
385	case "$_ipaddr" in
386	*/*)
387		local nbits="${_ipaddr#*/}" n=0
388		_ipaddr="${_ipaddr%%/*}"
389
390		#
391		# Taint-check $nbits to be (a) a positive whole-integer,
392		# and (b) to be less than or equal to 32. Otherwise, set
393		# $n so that the below loop never executes.
394		#
395		( f_isinteger "$nbits" && [ $nbits -ge 0 -a $nbits -le 32 ] ) \
396			|| n=4
397
398		while [ $n -lt 4 ]; do
399			_netmask="$_netmask${_netmask:+.}$((
400				(65280 >> ($nbits - 8 * $n) & 255)
401				* ((8*$n) < $nbits & $nbits <= (8*($n+1)))
402				+ 255 * ($nbits > (8*($n+1)))
403			))"
404			n=$(( $n + 1 ))
405		done
406		;;
407	esac
408
409	ipaddr="$_ipaddr"
410	[ "$_netmask" ] && netmask="$_netmask"
411
412	return $SUCCESS
413}
414
415############################################################ MAIN
416
417f_dprintf "%s: Successfully loaded." networking/ipaddr.subr
418
419fi # ! $_NETWORKING_IPADDR_SUBR
420