xref: /freebsd/usr.sbin/bsdconfig/share/media/tcpip.subr (revision a4e5e0106ac7145f56eb39a691e302cabb4635be)
1if [ ! "$_MEDIA_TCPIP_SUBR" ]; then _MEDIA_TCPIP_SUBR=1
2#
3# Copyright (c) 2012-2013 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 (INCLUDING, 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#
28############################################################ INCLUDES
29
30BSDCFG_SHARE="/usr/share/bsdconfig"
31. $BSDCFG_SHARE/common.subr || exit 1
32f_dprintf "%s: loading includes..." media/tcpip.subr
33f_include $BSDCFG_SHARE/device.subr
34f_include $BSDCFG_SHARE/dialog.subr
35f_include $BSDCFG_SHARE/strings.subr
36f_include $BSDCFG_SHARE/struct.subr
37f_include $BSDCFG_SHARE/variable.subr
38
39BSDCFG_LIBE="/usr/libexec/bsdconfig"
40f_include_lang $BSDCFG_LIBE/include/messages.subr
41
42TCP_HELPFILE=$BSDCFG_LIBE/include/tcp.hlp
43NETWORK_DEVICE_HELPFILE=$BSDCFG_LIBE/include/network_device.hlp
44
45############################################################ GLOBALS
46
47#
48# Path to resolv.conf(5).
49#
50: ${RESOLV_CONF:="/etc/resolv.conf"}
51
52#
53# Path to nsswitch.conf(5).
54#
55: ${NSSWITCH_CONF:="/etc/nsswitch.conf"}
56
57#
58# Path to hosts(5)
59#
60: ${ETC_HOSTS:="/etc/hosts"}
61
62#
63# Structure of dhclient.leases(5) lease { ... } entry
64#
65f_struct_define DHCP_LEASE \
66	interface		\
67	fixed_address		\
68	filename		\
69	server_name		\
70	script			\
71	medium			\
72	host_name		\
73	subnet_mask		\
74	routers			\
75	domain_name_servers	\
76	domain_name		\
77	broadcast_address	\
78	dhcp_lease_time		\
79	dhcp_message_type	\
80	dhcp_server_identifier	\
81	dhcp_renewal_time	\
82	dhcp_rebinding_time	\
83	renew			\
84	rebind			\
85	expire
86
87############################################################ FUNCTIONS
88
89# f_validate_hostname $hostname
90#
91# Returns zero if the given argument (a fully-qualified hostname) is compliant
92# with standards set-forth in RFC's 952 and 1123 of the Network Working Group:
93#
94# RFC 952 - DoD Internet host table specification
95# http://tools.ietf.org/html/rfc952
96#
97# RFC 1123 - Requirements for Internet Hosts - Application and Support
98# http://tools.ietf.org/html/rfc1123
99#
100# See http://en.wikipedia.org/wiki/Hostname for a brief overview.
101#
102# The return status for invalid hostnames is one of:
103# 	255	Entire hostname exceeds the maximum length of 255 characters.
104# 	 63	One or more individual labels within the hostname (separated by
105# 	   	dots) exceeds the maximum of 63 characters.
106# 	  1	One or more individual labels within the hostname contains one
107# 	   	or more invalid characters.
108# 	  2	One or more individual labels within the hostname starts or
109# 	   	ends with a hyphen (hyphens are allowed, but a label cannot
110# 	   	begin or end with a hyphen).
111# 	  3	One or more individual labels within the hostname are null.
112#
113# To call this function and display an appropriate error message to the user
114# based on the above error codes, use the following function defined in
115# dialog.subr:
116#
117# 	f_dialog_validate_hostname $hostname
118#
119f_validate_hostname()
120{
121	local fqhn="$1"
122
123	# Return error if the hostname exceeds 255 characters
124	[ ${#fqhn} -gt 255 ] && return 255
125
126	local IFS="." # Split on `dot'
127	for label in $fqhn; do
128		# Return error if the label exceeds 63 characters
129		[ ${#label} -gt 63 ] && return 63
130
131		# Return error if the label is null
132		[ "$label" ] || return 3
133
134		# Return error if label begins/ends with dash
135		case "$label" in -*|*-) return 2; esac
136
137		# Return error if the label contains any invalid chars
138		case "$label" in *[!0-9a-zA-Z-]*) return 1; esac
139	done
140
141	return $SUCCESS
142}
143
144# f_inet_atoi $ipv4_address [$var_to_set]
145#
146# Convert an IPv4 address or mask from dotted-quad notation (e.g., `127.0.0.1'
147# or `255.255.255.0') to a 32-bit unsigned integer for the purpose of network
148# and broadcast calculations. For example, one can validate that two addresses
149# are on the same network:
150#
151# 	f_inet_atoi 1.2.3.4 ip1num
152# 	f_inet_atoi 1.2.4.5 ip2num
153# 	f_inet_atoi 255.255.0.0 masknum
154# 	if [ $(( $ip1num & $masknum )) -eq \
155# 	     $(( $ip2num & $masknum )) ]
156# 	then
157# 		: IP addresses are on same network
158# 	fi
159#
160# See f_validate_ipaddr() below for an additional example usage, on calculating
161# network and broadcast addresses.
162#
163# If $var_to_set is missing or NULL, the converted IP address is printed to
164# standard output for capturing in a sub-shell (which is less-recommended
165# because of performance degredation; for example, when called in a loop).
166#
167f_inet_atoi()
168{
169	local __addr="$1" __var_to_set="$2" __num=0
170	if f_validate_ipaddr "$__addr"; then
171		local IFS=.
172		set -- $__addr
173		__num=$(( ($1 << 24) + ($2 << 16) + ($3 << 8) + $4 ))
174	fi
175	if [ "$__var_to_set" ]; then
176		setvar "$__var_to_set" $__num
177	else
178		echo $__num
179	fi
180}
181
182# f_validate_ipaddr $ipaddr [$netmask]
183#
184# Returns zero if the given argument (an IP address) is of the proper format.
185#
186# The return status for invalid IP address is one of:
187# 	1	One or more individual octets within the IP address (separated
188# 	 	by dots) contains one or more invalid characters.
189# 	2	One or more individual octets within the IP address are null
190# 	 	and/or missing.
191# 	3	One or more individual octets within the IP address exceeds the
192# 	 	maximum of 255 (or 2^8, being an octet comprised of 8 bits).
193# 	4	The IP address has either too few or too many octets.
194#
195# If a netmask is provided, the IP address is checked further:
196#
197# 	5	The IP address must not be the network or broadcast address.
198#
199f_validate_ipaddr()
200{
201	local ip="$1" mask="$2"
202
203	# Track number of octets for error checking
204	local noctets=0
205
206	local oldIFS="$IFS" IFS="." # Split on `dot'
207	for octet in $ip; do
208		# Return error if the octet is null
209		[ "$octet" ] || return 2
210
211		# Return error if not a whole integer
212		f_isinteger "$octet" || return 1
213
214		# Return error if not a positive integer
215		[ $octet -ge 0 ] || return 1
216
217		# Return error if the octet exceeds 255
218		[ $octet -gt 255 ] && return 3
219
220		noctets=$(( $noctets + 1 ))
221	done
222	IFS="$oldIFS"
223
224	[ $noctets -eq 4 ] || return 4
225
226	#
227	# The IP address must not be network or broadcast address.
228	#
229	if [ "$mask" ]; then
230		local ipnum masknum netnum bcastnum
231		local max_addr=4294967295 # 255.255.255.255
232
233		f_inet_atoi $ip ipnum
234		f_inet_atoi $mask masknum
235
236		netnum=$(( $ipnum & $masknum ))
237		bcastnum=$(( ($ipnum & $masknum)+$max_addr-$masknum ))
238
239		if [ "$masknum" ] &&
240		   [ $ipnum -eq $netnum -o $ipnum -eq $bcastnum ]
241		then
242			return 5
243		fi
244	fi
245
246	return $SUCCESS
247}
248
249# f_validate_ipaddr6 $ipv6_addr
250#
251# Returns zero if the given argument (an IPv6 address) is of the proper format.
252#
253# The return status for invalid IP address is one of:
254# 	1	One or more individual segments within the IP address
255# 	 	(separated by colons) contains one or more invalid characters.
256# 	 	Segments must contain only combinations of the characters 0-9,
257# 	 	A-F, or a-f.
258# 	2	Too many/incorrect null segments. A single null segment is
259# 	 	allowed within the IP address (separated by colons) but not
260# 	 	allowed at the beginning or end (unless a double-null segment;
261# 	 	i.e., "::*" or "*::").
262# 	3	One or more individual segments within the IP address
263# 	 	(separated by colons) exceeds the length of 4 hex-digits.
264# 	4	The IP address entered has either too few (less than 3), too
265# 	 	many (more than 8), or not enough segments, separated by
266# 	 	colons.
267# 	5*	The IPv4 address at the end of the IPv6 address is invalid.
268# 	*	When there is an error with the dotted-quad IPv4 address at the
269# 	 	end of the IPv6 address, the return value of 5 is OR'd with a
270# 	 	bit-shifted (<< 4) return of f_validate_ipaddr.
271#
272f_validate_ipaddr6()
273{
274	local ip="${1%\%*}" # removing the interface specification if-present
275
276	local IFS=":" # Split on `colon'
277	set -- $ip:
278
279	# Return error if too many or too few segments
280	# Using 9 as max in case of leading or trailing null spanner
281	[ $# -gt 9 -o $# -lt 3 ] && return 4
282
283	local h="[0-9A-Fa-f]"
284	local nulls=0 nsegments=$# contains_ipv4_segment=
285
286	while [ $# -gt 0 ]; do
287
288		segment="${1%:}"
289		shift
290
291		#
292		# Return error if this segment makes one null too-many. A
293		# single null segment is allowed anywhere in the middle as well
294		# as double null segments are allowed at the beginning or end
295		# (but not both).
296		#
297		if [ ! "$segment" ]; then
298			nulls=$(( $nulls + 1 ))
299			if [ $nulls -eq 3 ]; then
300				# Only valid syntax for 3 nulls is `::'
301				[ "$ip" = "::" ] || return 2
302			elif [ $nulls -eq 2 ]; then
303				# Only valid if begins/ends with `::'
304				case "$ip" in
305				::*|*::) : fall thru ;;
306				*) return 2
307				esac
308			fi
309			continue
310		fi
311
312		#
313		# Return error if not a valid hexadecimal short
314		#
315		case "$segment" in
316		$h|$h$h|$h$h$h|$h$h$h$h)
317			: valid segment of 1-4 hexadecimal digits
318			;;
319		*[!0-9A-Fa-f]*)
320			# Segment contains at least one invalid char
321
322			# Return error immediately if not last segment
323			[ $# -eq 0 ] || return 1
324
325			# Otherwise, check for legacy IPv4 notation
326			case "$segment" in
327			*[!0-9.]*)
328				# Segment contains at least one invalid
329				# character even for an IPv4 address
330				return 1
331			esac
332
333			# Return error if not enough segments
334			if [ $nulls -eq 0 ]; then
335				[ $nsegments -eq 7 ] || return 4
336			fi
337
338			contains_ipv4_segment=1
339
340			# Validate the IPv4 address
341			f_validate_ipaddr "$segment" ||
342				return $(( 5 | $? << 4 ))
343			;;
344		*)
345			# Segment characters are all valid but too many
346			return 3
347		esac
348
349	done
350
351	if [ $nulls -eq 1 ]; then
352		# Single null segment cannot be at beginning/end
353		case "$ip" in
354		:*|*:) return 2
355		esac
356	fi
357
358	#
359	# A legacy IPv4 address can span the last two 16-bit segments,
360	# reducing the amount of maximum allowable segments by-one.
361	#
362	maxsegments=8
363	if [ "$contains_ipv4_segment" ]; then
364		maxsegments=7
365	fi
366
367	case $nulls in
368	# Return error if missing segments with no null spanner
369	0) [ $nsegments -eq $maxsegments ] || return 4 ;;
370	# Return error if null spanner with too many segments
371	1) [ $nsegments -le $maxsegments ] || return 4 ;;
372	# Return error if leading/trailing `::' with too many segments
373	2) [ $nsegments -le $(( $maxsegments + 1 )) ] || return 4 ;;
374	esac
375
376	return $SUCCESS
377}
378
379# f_validate_netmask $netmask
380#
381# Returns zero if the given argument (a subnet mask) is of the proper format.
382#
383# The return status for invalid netmask is one of:
384# 	1	One or more individual fields within the subnet mask (separated
385# 	 	by dots) contains one or more invalid characters.
386# 	2	One or more individual fields within the subnet mask are null
387# 	 	and/or missing.
388# 	3	One or more individual fields within the subnet mask exceeds
389# 	 	the maximum of 255 (a full 8-bit register).
390# 	4	The subnet mask has either too few or too many fields.
391# 	5	One or more individual fields within the subnet mask is an
392# 	 	invalid integer (only 0,128,192,224,240,248,252,254,255 are
393# 	 	valid integers).
394#
395f_validate_netmask()
396{
397	local mask="$1"
398
399	# Track number of fields for error checking
400	local nfields=0
401
402	local IFS="." # Split on `dot'
403	for field in $mask; do
404		# Return error if the field is null
405		[ "$field" ] || return 2
406
407		# Return error if not a whole positive integer
408		f_isinteger "$field" || return 1
409
410		# Return error if the field exceeds 255
411		[ $field -gt 255 ] && return 3
412
413		# Return error if the field is an invalid integer
414		case "$field" in
415		0|128|192|224|240|248|252|254|255) : ;;
416		*) return 5 ;;
417		esac
418
419		nfields=$(( $nfields + 1 ))
420	done
421
422	[ $nfields -eq 4 ] || return 4
423}
424
425# f_validate_gateway $gateway $ipaddr $netmask
426#
427# Validate an IPv4 default gateway (aka router) address for a given IP address
428# making sure the two are in the same network (able to ``talk'' to each other).
429# Returns success if $ipaddr and $gateway are in the same network given subnet
430# mask $netmask.
431#
432f_validate_gateway()
433{
434	local gateway="$1" ipaddr="$2" netmask="$3"
435	local gwnum ipnum masknum
436
437	f_validate_ipaddr "$gateway" "$netmask" || return $FAILURE
438
439	f_inet_atoi "$netmask" masknum
440	f_inet_atoi "$ipaddr"  ipnum
441	f_inet_atoi "$gateway" gwnum
442
443	# Gateway must be within set of IPs reachable through interface
444	[ $(( $ipnum & $masknum )) -eq \
445	  $(( $gwnum & $masknum )) ] # Return status
446}
447
448# f_dialog_validate_tcpip $hostname $gateway $nameserver $ipaddr $netmask
449#
450# Returns success if the arguments provided are valid for accessing a TCP/IP
451# network, otherwise returns failure.
452#
453f_dialog_validate_tcpip()
454{
455	local hostname="$1" gateway="$2" nameserver="$3"
456	local ipaddr="$4" netmask="$5"
457	local ipnum masknum
458
459	if [ ! "$hostname" ]; then
460		f_show_msg "$msg_must_specify_a_host_name_of_some_sort"
461	elif ! f_validate_hostname "$hostname"; then
462		f_show_msg "$msg_invalid_hostname_value"
463	elif [ "$netmask" ] && ! f_validate_netmask "$netmask"; then
464		f_show_msg "$msg_invalid_netmask_value"
465	elif [ "$nameserver" ] &&
466	     ! f_validate_ipaddr "$nameserver" &&
467	     ! f_validate_ipaddr6 "$nameserver"; then
468		f_show_msg "$msg_invalid_name_server_ip_address_specified"
469	elif [ "$ipaddr" ] && ! f_validate_ipaddr "$ipaddr" "$netmask"; then
470		f_show_msg "$msg_invalid_ipv4_address"
471	elif [ "$gateway" -a "$gateway" != "NO" ] &&
472	     ! f_validate_gateway "$gateway" "$ipaddr" "$netmask"; then
473		f_show_msg "$msg_invalid_gateway_ipv4_address_specified"
474	else
475		return $DIALOG_OK
476	fi
477
478	return $DIALOG_CANCEL
479}
480
481# f_ifconfig_inet $interface [$var_to_set]
482#
483# Returns the IPv4 address associated with $interface. If $var_to_set is
484# missing or NULL, the IP address is printed to standard output for capturing
485# in a sub-shell (which is less-recommended because of performance degredation;
486# for example, when called in a loop).
487#
488# This function is a two-parter. Below is the awk(1) portion of the function,
489# afterward is the sh(1) function which utilizes the below awk script.
490#
491f_ifconfig_inet_awk='
492BEGIN { found = 0 }
493( $1 == "inet" ) \
494{
495	print $2
496	found = 1
497	exit
498}
499END { exit ! found }
500'
501f_ifconfig_inet()
502{
503	local __interface="$1" __var_to_set="$2"
504	if [ "$__var_to_set" ]; then
505		local __ip
506		__ip=$( ifconfig "$__interface" 2> /dev/null |
507			awk "$f_ifconfig_inet_awk" )
508		setvar "$__var_to_set" "$__ip"
509	else
510		ifconfig "$__interface" 2> /dev/null |
511			awk "$f_ifconfig_inet_awk"
512	fi
513}
514
515# f_ifconfig_inet6 $interface [$var_to_set]
516#
517# Returns the IPv6 address associated with $interface. If $var_to_set is
518# missing or NULL, the IP address is printed to standard output for capturing
519# in a sub-shell (which is less-recommended because of performance degredation;
520# for example, when called in a loop).
521#
522# This function is a two-parter. Below is the awk(1) portion of the function,
523# afterward is the sh(1) function which utilizes the below awk script.
524#
525f_ifconfig_inet6_awk='
526BEGIN { found = 0 }
527( $1 == "inet6" ) \
528{
529	print $2
530	found = 1
531	exit
532}
533END { exit ! found }
534'
535f_ifconfig_inet6()
536{
537	local __interface="$1" __var_to_set="$2"
538	if [ "$__var_to_set" ]; then
539		local __ip6
540		__ip6=$( ifconfig "$__interface" 2> /dev/null |
541			awk "$f_ifconfig_inet6_awk" )
542		setvar "$__var_to_set" "$__ip6"
543	else
544		ifconfig "$__interface" 2> /dev/null |
545			awk "$f_ifconfig_inet6_awk"
546	fi
547}
548
549# f_ifconfig_netmask $interface [$var_to_set]
550#
551# Returns the IPv4 subnet mask associated with $interface. If $var_to_set is
552# missing or NULL, the netmask is printed to standard output for capturing in a
553# sub-shell (which is less-recommended because of performance degredation; for
554# example, when called in a loop).
555#
556f_ifconfig_netmask()
557{
558	local __interface="$1" __var_to_set="$2" __octets
559	__octets=$( ifconfig "$__interface" 2> /dev/null | awk \
560	'
561		BEGIN { found = 0 }
562		( $1 == "inet" ) \
563		{
564			printf "%s %s %s %s\n",
565				substr($4,3,2),
566				substr($4,5,2),
567				substr($4,7,2),
568				substr($4,9,2)
569			found = 1
570			exit
571		}
572		END { exit ! found }
573	' ) || return $FAILURE
574
575	local __octet __netmask=
576	for __octet in $__octets; do
577		f_sprintf __netmask "%s.%u" "$__netmask" "0x$__octet"
578	done
579	__netmask="${__netmask#.}"
580	if [ "$__var_to_set" ]; then
581		setvar "$__var_to_set" "$__netmask"
582	else
583		echo $__netmask
584	fi
585}
586
587# f_route_get_default [$var_to_set]
588#
589# Returns the IP address of the currently active default router. If $var_to_set
590# is missing or NULL, the IP address is printed to standard output for
591# capturing in a sub-shell (which is less-recommended because of performance
592# degredation; for example, when called in a loop).
593#
594# This function is a two-parter. Below is the awk(1) portion of the function,
595# afterward is the sh(1) function which utilizes the below awk script.
596#
597f_route_get_default_awk='
598BEGIN { found = 0 }
599( $1 == "gateway:" ) \
600{
601	print $2
602	found = 1
603	exit
604}
605END { exit ! found }
606'
607f_route_get_default()
608{
609	local __var_to_set="$1"
610	if [ "$__var_to_set" ]; then
611		local __ip
612		__ip=$( route -n get default 2> /dev/null |
613			awk "$f_route_get_default_awk" )
614		setvar "$__var_to_set" "$__ip"
615	else
616		route -n get default 2> /dev/null |
617			awk "$f_route_get_default_awk"
618	fi
619}
620
621# f_resolv_conf_nameservers [$var_to_set]
622#
623# Returns nameserver(s) configured in resolv.conf(5). If $var_to_set is missing
624# or NULL, the list of nameservers is printed to standard output for capturing
625# in a sub-shell (which is less-recommended because of performance degredation;
626# for example, when called in a loop).
627#
628# This function is a two-parter. Below is the awk(1) portion of the function,
629# afterward is the sh(1) function which utilizes the below awk script.
630#
631f_resolv_conf_nameservers_awk='
632BEGIN { found = 0 }
633( $1 == "nameserver" ) \
634{
635	print $2
636	found = 1
637}
638END { exit ! found }
639'
640f_resolv_conf_nameservers()
641{
642	local __var_to_set="$1"
643	if [ "$__var_to_set" ]; then
644		local __ns
645		__ns=$( awk "$f_resolv_conf_nameservers_awk" "$RESOLV_CONF" \
646			2> /dev/null )
647		setvar "$__var_to_set" "$__ns"
648	else
649		awk "$f_resolv_conf_nameservers_awk" "$RESOLV_CONF" \
650			2> /dev/null
651	fi
652}
653
654# f_config_resolv
655#
656# Attempts to configure resolv.conf(5) and ilk. Returns success if able to
657# write the file(s), otherwise returns error status.
658#
659# Variables from variable.subr that are used in configuring resolv.conf(5) are
660# as follows (all of which can be configured automatically through functions
661# like f_dhcp_get_info() or manually):
662#
663# 	VAR_NAMESERVER
664#		The nameserver to add in resolv.conf(5).
665# 	VAR_DOMAINNAME
666# 		The domain to configure in resolv.conf(5). Also used in the
667# 		configuration of hosts(5).
668# 	VAR_IPADDR
669# 		The IPv4 address to configure in hosts(5).
670# 	VAR_IPV6ADDR
671# 		The IPv6 address to configure in hosts(5).
672# 	VAR_HOSTNAME
673# 		The hostname to associate with the IPv4 and/or IPv6 address in
674# 		hosts(5).
675#
676f_config_resolv()
677{
678	local cp c6p dp hp
679
680	f_getvar $VAR_NAMESERVER cp
681	if [ "$cp" ]; then
682		case "$RESOLV_CONF" in
683		*/*) f_quietly mkdir -p "${RESOLV_CONF%/*}" ;;
684		esac
685
686		# Attempt to create/truncate the file
687		( :> "$RESOLV_CONF" ) 2> /dev/null || return $FAILURE
688
689		f_getvar $VAR_DOMAINNAME dp &&
690			printf "domain\t%s\n" "$dp" >> "$RESOLV_CONF"
691		printf "nameserver\t%s\n" "$cp" >> "$RESOLV_CONF"
692
693		f_dprintf "Wrote out %s" "$RESOLV_CONF"
694	fi
695
696	f_getvar $VAR_DOMAINNAME dp
697	f_getvar $VAR_IPADDR cp
698	f_getvar $VAR_IPV6ADDR c6p
699	f_getvar $VAR_HOSTNAME hp
700
701	# Attempt to create the file if it doesn't already exist
702	if [ ! -e "$ETC_HOSTS" ]; then
703		case "$ETC_HOSTS" in
704		*/*) f_quietly mkdir -p "${ETC_HOSTS%/*}" ;;
705		esac
706
707		( :> "$ETC_HOSTS" ) 2> /dev/null || return $FAILURE
708	fi
709
710	# Scan the file and add ourselves if not already configured
711	awk -v dn="$dp" -v ip4="$cp" -v ip6="$c6p" -v hn="$hp" '
712		BEGIN {
713			local4found = local6found = 0
714			hn4found = hn6found = h4found = h6found = 0
715			h = ( match(hn, /\./) ? substr(hn, 0, RSTART-1) : "" )
716		}
717		($1 == "127.0.0.1") { local4found = 1 }
718		($1 == "::1") { local6found = 1 }
719		{
720			for (n = 2; n <= NF; n++)
721			{
722				if ( $1 == ip4 ) {
723					if ( $n == h ) h4found = 1
724					if ( $n == hn ) hn4found = 1
725					if ( $n == hn "." ) hn4found = 1
726				}
727				if ( $1 == ip6 ) {
728					if ( $n == h ) h6found = 1
729					if ( $n == hn ) hn6found = 1
730					if ( $n == hn "." ) hn6found = 1
731				}
732			}
733		}
734		END {
735			hosts = FILENAME
736
737			if ( ! local6found )
738				printf "::1\t\t\tlocalhost%s\n",
739				       ( dn ? " localhost." dn : "" ) >> hosts
740			if ( ! local4found )
741				printf "127.0.0.1\t\tlocalhost%s\n",
742				       ( dn ? " localhost." dn : "" ) >> hosts
743
744			if ( ip6 && ! (h6found && hn6found))
745			{
746				printf "%s\t%s %s\n", ip6, hn, h >> hosts
747				printf "%s\t%s.\n", ip6, hn >> hosts
748			}
749			else if ( ip6 )
750			{
751				if ( ! h6found )
752					printf "%s\t%s.\n", ip6, h >> hosts
753				if ( ! hn6found )
754					printf "%s\t%s\n", ip6, hn >> hosts
755			}
756
757			if ( ip4 && ! (h4found && hn4found))
758			{
759				printf "%s\t\t%s %s\n", ip4, hn, h >> hosts
760				printf "%s\t\t%s.\n", ip4, hn >> hosts
761			}
762			else if ( ip4 )
763			{
764				if ( ! h4found )
765					printf "%s\t\t%s.\n", ip4, h >> hosts
766				if ( ! hn4found )
767					printf "%s\t\t%s\n", ip4, hn >> hosts
768			}
769		}
770	' "$ETC_HOSTS" 2> /dev/null || return $FAILURE
771
772	f_dprintf "Wrote out %s" "$ETC_HOSTS"
773	return $SUCCESS
774}
775
776# f_dhcp_parse_leases $leasefile struct_name
777#
778# Parse $leasefile and store the information for the most recent lease in a
779# struct (see struct.subr for additional details) named `struct_name'. See
780# DHCP_LEASE struct definition in the GLOBALS section above.
781#
782f_dhcp_parse_leases()
783{
784	local leasefile="$1" struct_name="$2"
785
786	[ "$struct_name" ] || return $FAILURE
787
788	if [ ! -e "$leasefile" ]; then
789		f_dprintf "%s: No such file or directory" "$leasefile"
790		return $FAILURE
791	fi
792
793	f_struct "$struct_name" && f_struct_free "$struct_name"
794	f_struct_new DHCP_LEASE "$struct_name"
795
796	eval "$( awk -v struct="$struct_name" '
797		BEGIN {
798			lease_found = 0
799			keyword_list = " \
800				interface	\
801				fixed-address	\
802				filename	\
803				server-name	\
804				script		\
805				medium		\
806			"
807			split(keyword_list, keywords, FS)
808
809			time_list = "renew rebind expire"
810			split(time_list, times, FS)
811
812			option_list = " \
813				host-name		\
814				subnet-mask		\
815				routers			\
816				domain-name-servers	\
817				domain-name		\
818				broadcast-address	\
819				dhcp-lease-time		\
820				dhcp-message-type	\
821				dhcp-server-identifier	\
822				dhcp-renewal-time	\
823				dhcp-rebinding-time	\
824			"
825			split(option_list, options, FS)
826		}
827		function set_value(prop,value)
828		{
829			lease_found = 1
830			gsub(/[^[:alnum:]_]/, "_", prop)
831			sub(/;$/, "", value)
832			sub(/^"/, "", value)
833			sub(/"$/, "", value)
834			sub(/,.*/, "", value)
835			printf "%s set %s \"%s\"\n", struct, prop, value
836		}
837		/^lease {$/, /^}$/ \
838		{
839			if ( $0 ~ /^lease {$/ ) next
840			if ( $0 ~ /^}$/ ) exit
841
842			for (k in keywords)
843			{
844				keyword = keywords[k]
845				if ( $1 == keyword )
846				{
847					set_value(keyword, $2)
848					next
849				}
850			}
851
852			for (t in times)
853			{
854				time = times[t]
855				if ( $1 == time )
856				{
857					set_value(time, $2 " " $3 " " $4)
858					next
859				}
860			}
861
862			if ( $1 != "option" ) next
863			for (o in options)
864			{
865				option = options[o]
866				if ( $2 == option )
867				{
868					set_value(option, $3)
869					next
870				}
871			}
872		}
873		EXIT {
874			if ( ! lease_found )
875			{
876				printf "f_struct_free \"%s\"\n", struct
877				print "return $FAILURE"
878			}
879		}
880	' "$leasefile" )"
881}
882
883# f_dhcp_get_info $interface
884#
885# Parse the dhclient(8) lease database for $interface to obtain all the
886# necessary IPv4 details necessary to communicate on the network. The retrieved
887# information is stored in VAR_IPADDR, VAR_NETMASK, VAR_GATEWAY, and
888# VAR_NAMESERVER.
889#
890# If reading the lease database fails, values are obtained from ifconfig(8) and
891# route(8). If the DHCP lease did not provide a nameserver (or likewise, we
892# were unable to parse the lease database), fall-back to resolv.conf(5) for
893# obtaining the nameserver. Always returns success.
894#
895f_dhcp_get_info()
896{
897	local interface="$1" cp
898	local leasefile="/var/db/dhclient.leases.$interface"
899
900	# If it fails, do it the old-fashioned way
901	if f_dhcp_parse_leases "$leasefile" lease; then
902		lease get fixed_address $VAR_IPADDR
903		lease get subnet_mask $VAR_NETMASK
904		lease get routers cp
905		setvar $VAR_GATEWAY "${cp%%,*}"
906		lease get domain_name_servers cp
907		setvar $VAR_NAMESERVER "${cp%%,*}"
908		lease get host_name cp &&
909			setvar $VAR_HOSTNAME "$cp"
910		f_struct_free lease
911	else
912		# Bah, now we have to get the information from ifconfig
913		if f_debugging; then
914			f_dprintf "DHCP configured interface returns %s" \
915			          "$( ifconfig "$interface" )"
916		fi
917		f_ifconfig_inet "$interface" $VAR_IPADDR
918		f_ifconfig_netmask "$interface" $VAR_NETMASK
919		f_route_get_default $VAR_GATEWAY
920	fi
921
922	# If we didn't get a name server value, hunt for it in resolv.conf
923	local ns
924	if [ -r "$RESOLV_CONF" ] && ! {
925		f_getvar $VAR_NAMESERVER ns || [ "$ns" ]
926	}; then
927		f_resolv_conf_nameservers cp &&
928			setvar $VAR_NAMESERVER ${cp%%[$IFS]*}
929	fi
930
931	return $SUCCESS
932}
933
934# f_rtsol_get_info $interface
935#
936# Returns the rtsol-provided IPv6 address associated with $interface. The
937# retrieved IP address is stored in VAR_IPV6ADDR. Always returns success.
938#
939f_rtsol_get_info()
940{
941	local interface="$1" cp
942	cp=$( ifconfig "$interface" 2> /dev/null | awk \
943	'
944		BEGIN { found = 0 }
945		( $1 == "inet6" ) && ( $2 ~ /^fe80:/ ) \
946		{
947			print $2
948			found = 1
949			exit
950		}
951		END { exit ! found }
952	' ) && setvar $VAR_IPV6ADDR "$cp"
953}
954
955# f_host_lookup $host [$var_to_set]
956#
957# Use host(1) to lookup (or reverse) an Internet number from (or to) a name.
958# Multiple answers are returned separated by a single space. If host(1) does
959# not exit cleanly, its full output is provided and the return status is 1.
960#
961# If nsswitch.conf(5) has been configured to query local access first for the
962# `hosts' database, we'll manually check hosts(5) first (preventing host(1)
963# from hanging in the event that DNS goes awry).
964#
965# If $var_to_set is missing or NULL, the list of IP addresses is printed to
966# standard output for capturing in a sub-shell (which is less-recommended
967# because of performance degredation; for example, when called in a loop).
968#
969# The variables from variable.subr used in looking up the host are as follows
970# (which are set manually):
971#
972# 	VAR_IPV6_ENABLE [Optional]
973# 		If set to "YES", enables the lookup of IPv6 addresses and IPv4
974# 		address. IPv6 addresses, if any, will come before IPv4. Note
975# 		that if nsswitch.conf(5) shows an affinity for "files" for the
976# 		"host" database and there is a valid entry in hosts(5) for
977# 		$host, this setting currently has no effect (an IPv4 address
978# 		can supersede an IPv6 address). By design, hosts(5) overrides
979# 		any preferential treatment. Otherwise, if this variable is not
980# 		set, IPv6 addresses will not be used (IPv4 addresses will
981# 		specifically be requested from DNS).
982#
983# This function is a two-parter. Below is the awk(1) portion of the function,
984# afterward is the sh(1) function which utilizes the below awk script.
985#
986f_host_lookup_awk='
987BEGIN{ addrs = "" }
988!/^[[:space:]]*(#|$)/ \
989{
990	for (n=1; n++ < NF;) if ($n == name)
991		addrs = addrs (addrs ? " " : "") $1
992}
993END {
994	if (addrs) print addrs
995	exit !addrs
996}
997'
998f_host_lookup()
999{
1000	local __host="$1" __var_to_set="$2"
1001	f_dprintf "f_host_lookup: host=[%s]" "$__host"
1002
1003	# If we're configured to look at local files first, do that
1004	if awk '/^hosts:/{exit !($2=="files")}' "$NSSWITCH_CONF"; then
1005		if [ "$__var_to_set" ]; then
1006			local __cp
1007			if __cp=$( awk -v name="$__host" \
1008				"$f_host_lookup_awk" "$ETC_HOSTS" )
1009			then
1010				setvar "$__var_to_set" "$__cp"
1011				return $SUCCESS
1012			fi
1013		else
1014			awk -v name="$__host" \
1015				"$f_host_lookup_awk" "$ETC_HOSTS" &&
1016				return $SUCCESS
1017		fi
1018	fi
1019
1020	#
1021	# Fall back to host(1) -- which is further governed by nsswitch.conf(5)
1022	#
1023
1024	local __output __ip6 __addrs=
1025	f_getvar $VAR_IPV6_ENABLE __ip6
1026
1027	# If we have a TCP media type configured, check for an SRV record
1028	local __srvtypes=
1029	{ f_quietly f_getvar $VAR_HTTP_PATH ||
1030	  f_quietly f_getvar $VAR_HTTP_PROXY_PATH
1031	} && __srvtypes="$__srvtypes _http._tcp"
1032	f_quietly f_getvar $VAR_NFS_PATH &&
1033		__srvtypes="$__srvtypes _nfs._tcp _nfs._udp"
1034
1035	# Calculate wait time as dividend of total time and host(1) invocations
1036	local __host_runs __wait
1037	f_count __host_runs $__srvtypes
1038	if [ "$__ip6" = "YES" ]; then
1039		__host_runs=$(( $__host_runs + 2 ))
1040	else
1041		__host_runs=$(( $__host_runs + 1 ))
1042	fi
1043	f_getvar $VAR_MEDIA_TIMEOUT __wait
1044	[ "$__wait" ] && __wait="-W $(( $__wait / $__host_runs ))"
1045
1046	# Query SRV types first (1st host response taken as new host to query)
1047	for __type in $__srvtypes; do
1048		if __output=$(
1049			host -t SRV $__wait -- "$__type.$__host" \
1050			2> /dev/null
1051		); then
1052			__host=$( echo "$__output" |
1053					awk '/ SRV /{print $NF;exit}' )
1054			break
1055		fi
1056	done
1057
1058	# Try IPv6 first (if enabled)
1059	if [ "$__ip6" = "YES" ]; then
1060		if ! __output=$( host -t AAAA $__wait -- "$__host" 2>&1 ); then
1061			# An error occurred, display in-full and return error
1062			[ "$__var_to_set" ] &&
1063				setvar "$__var_to_set" "$__output"
1064			return $FAILURE
1065		fi
1066		# Add the IPv6 addresses and fall-through to collect IPv4 too
1067		__addrs=$( echo "$__output" | awk '/ address /{print $NF}' )
1068	fi
1069
1070	# Good ol' IPv4
1071	if ! __output=$( host -t A $__wait -- "$__host" 2>&1 ); then
1072		# An error occurred, display it in-full and return error
1073		[ "$__var_to_set" ] && setvar "$__var_to_set" "$__output"
1074		return $FAILURE
1075	fi
1076
1077	__addrs="$__addrs${__addrs:+ }$(
1078		echo "$__output" | awk '/ address /{print $NF}' )"
1079	if [ "$__var_to_set" ]; then
1080		setvar "$__var_to_set" "$__addrs"
1081	else
1082		echo $__addrs
1083	fi
1084}
1085
1086# f_device_dialog_tcp $device
1087#
1088# This is it - how to get TCP setup values. Prompt the user to edit/confirm the
1089# interface, gateway, nameserver, and hostname settings -- all required for
1090# general TCP/IP access.
1091#
1092# Variables from variable.subr that can be used to sript user input:
1093#
1094# 	VAR_NO_INET6
1095# 		If set, prevents asking the user if they would like to use
1096# 		rtsol(8) to check for an IPv6 router.
1097# 	VAR_TRY_RTSOL
1098# 		If set to "YES" (and VAR_NONINTERACTIVE is unset), asks the
1099# 		user if they would like to try the IPv6 RouTer SOLicitation
1100# 		utility (rtsol(8)) to get IPv6 information. Ignored if
1101# 		VAR_NO_INET6 is set.
1102# 	VAR_TRY_DHCP
1103# 		If set to "YES" (and VAR_NONINTERACTIVE is unset), asks the
1104# 		user if they would like to try to acquire IPv4 connection
1105# 		settings from a DHCP server using dhclient(8).
1106#
1107# 	VAR_GATEWAY	Default gateway to use.
1108# 	VAR_IPADDR	Interface address to assign.
1109# 	VAR_NETMASK	Interface subnet mask.
1110# 	VAR_EXTRAS	Extra interface options to ifconfig(8).
1111# 	VAR_HOSTNAME	Hostname to set.
1112# 	VAR_DOMAINNAME	Domain name to use.
1113# 	VAR_NAMESERVER	DNS nameserver to use when making lookups.
1114# 	VAR_IPV6ADDR	IPv6 interface address.
1115#
1116# In addition, the following variables are used in acquiring network settings
1117# from the user:
1118#
1119# 	VAR_NONINTERACTIVE
1120# 		If set (such as when running in a script), prevents asking the
1121# 		user questions or displaying the usual prompts, etc.
1122# 	VAR_NETINTERACTIVE
1123# 		The one exception to VAR_NONINTERACTIVE is VAR_NETINTERACTIVE,
1124# 		which if set will prompt the user to try RTSOL (unless
1125# 		VAR_TRY_RTSOL has been set), try DHCP (unless VAR_TRY_DHCP has
1126# 		been set), and display the network verification dialog. This
1127# 		allows you to have a mostly non-interactive script that still
1128# 		prompts for network setup/confirmation.
1129#
1130# After successful execution, the following variables are set:
1131#
1132# 	VAR_IFCONFIG + $device (e.g., `ifconfig_em0')
1133#               Defines the ifconfig(8) properties specific to $device.
1134#
1135f_device_dialog_tcp()
1136{
1137	local dev="$1" devname cp n
1138	local use_dhcp="" use_rtsol=""
1139	local _ipaddr _netmask _extras
1140
1141	[ "$dev" ] || return $DIALOG_CANCEL
1142	f_struct "$dev" get name devname || return $DIALOG_CANCEL
1143
1144	# Initialize vars from previous device values
1145	local private
1146	$dev get private private
1147	if [ "$private" ] && f_struct "$private"; then
1148		$private get ipaddr    _ipaddr
1149		$private get netmask   _netmask
1150		$private get extras    _extras
1151		$private get use_dhcp  use_dhcp
1152		$private get use_rtsol use_rtsol
1153	else # See if there are any defaults
1154
1155		#
1156		# This is a hack so that the dialogs below are interactive in a
1157		# script if we have requested interactive behavior.
1158		#
1159		local old_interactive=
1160		if ! f_interactive && f_netinteractive; then
1161			f_getvar $VAR_NONINTERACTIVE old_interactive
1162			unset $VAR_NONINTERACTIVE
1163		fi
1164
1165		#
1166		# Try a RTSOL scan if such behavior is desired.
1167		# If the variable was configured and is YES, do it.
1168		# If it was configured to anything else, treat it as NO.
1169		# Otherwise, ask the question interactively.
1170		#
1171		local try6
1172		if ! f_isset $VAR_NO_INET6 && {
1173		   { f_getvar $VAR_TRY_RTSOL try6 && [ "$try6" = "YES" ]; } ||
1174		   {
1175			# Only prompt the user when VAR_TRY_RTSOL is unset
1176			! f_isset $VAR_TRY_RTSOL &&
1177				f_dialog_noyes "$msg_try_ipv6_configuration"
1178		   }
1179		}; then
1180			local i
1181
1182			f_quietly sysctl net.inet6.ip6.forwarding=0
1183			f_quietly sysctl net.inet6.ip6.accept_rtadv=1
1184			f_quietly ifconfig $devname up
1185
1186			i=$( sysctl -n net.inet6.ip6.dad_count )
1187			sleep $(( $i + 1 ))
1188
1189			f_quietly mkdir -p /var/run
1190			f_dialog_info "$msg_scanning_for_ra_servers"
1191			if f_quietly rtsol $devname; then
1192				i=$( sysctl -n net.inet6.ip6.dad_count )
1193				sleep $(( $i + 1 ))
1194				f_rtsol_get_info $devname
1195				use_rtsol=1
1196			else
1197				use_rtsol=
1198			fi
1199		fi
1200
1201		#
1202		# Try a DHCP scan if such behavior is desired.
1203		# If the variable was configured and is YES, do it.
1204		# If it was configured to anything else, treat it as NO.
1205		# Otherwise, ask the question interactively.
1206		#
1207		local try4
1208		if { f_getvar $VAR_TRY_DHCP try4 && [ "$try4" = "YES" ]; } || {
1209			# Only prompt the user when VAR_TRY_DHCP is unset
1210			! f_isset $VAR_TRY_DHCP &&
1211				f_dialog_noyes "$msg_try_dhcp_configuration"
1212		}; then
1213			f_quietly ifconfig $devname delete
1214			f_quietly mkdir -p /var/db
1215			f_quietly mkdir -p /var/run
1216			f_quietly mkdir -p /tmp
1217
1218			local msg="$msg_scanning_for_dhcp_servers"
1219			trap - SIGINT
1220			( # Execute in sub-shell to allow/catch Ctrl-C
1221			  trap 'exit $FAILURE' SIGINT
1222			  if [ "$USE_XDIALOG" ]; then
1223			  	f_quietly dhclient $devname |
1224			  			f_xdialog_info "$msg"
1225			  else
1226			  	f_dialog_info "$msg"
1227			  	f_quietly dhclient $devname
1228			  fi
1229			)
1230			local retval=$?
1231			trap 'f_interrupt' SIGINT
1232			if [ $retval -eq $SUCCESS ]; then
1233				f_dhcp_get_info $devname
1234				use_dhcp=1
1235			else
1236				use_dhcp=
1237			fi
1238		fi
1239
1240		# Restore old VAR_NONINTERACTIVE if needed.
1241		[ "$old_interactive" ] &&
1242			setvar $VAR_NONINTERACTIVE "$old_interactive"
1243
1244		# Special hack so it doesn't show up oddly in the menu
1245		local gw
1246		if f_getvar $VAR_GATEWAY gw && [ "$gw" = "NO" ]; then
1247			setvar $VAR_GATEWAY ""
1248		fi
1249
1250		# Get old IP address from variable space, if available
1251		if [ ! "$_ipaddr" ]; then
1252			if f_getvar $VAR_IPADDR cp; then
1253				_ipaddr="$cp"
1254			elif f_getvar ${devname}_$VAR_IPADDR cp; then
1255				_ipaddr="$cp"
1256			fi
1257		fi
1258
1259		# Get old netmask from variable space, if available
1260		if [ ! "$_netmask" ]; then
1261			if f_getvar $VAR_NETMASK cp; then
1262				_netmask="$cp"
1263			elif f_getvar ${devname}_$VAR_NETMASK cp; then
1264				_netmask="$cp"
1265			fi
1266		fi
1267
1268		# Get old extras string from variable space, if available
1269		if [ ! "$_extras" ]; then
1270			if f_getvar $VAR_EXTRAS cp; then
1271				_extras="$cp"
1272			elif f_getvar ${devname}_$VAR_EXTRAS cp; then
1273				_extras="$cp"
1274			fi
1275		fi
1276	fi
1277
1278	# Look up values already recorded with the system, or blank the string
1279	# variables ready to accept some new data
1280	local _hostname _gateway _nameserver
1281	f_getvar $VAR_HOSTNAME _hostname
1282	case "$_hostname" in
1283	*.*) : do nothing ;; # Already fully-qualified
1284	*)
1285		f_getvar $VAR_DOMAINNAME cp
1286		[ "$cp" ] && _hostname="$_hostname.$cp"
1287	esac
1288	f_getvar $VAR_GATEWAY _gateway
1289	f_getvar $VAR_NAMESERVER _nameserver
1290
1291	# Re-check variables for initial inheritance before heading into dialog
1292	[ "$_hostname" ] || _hostname="${HOSTNAME:-$( hostname )}"
1293	[ "$_gateway" ] || f_route_get_default _gateway
1294	[ ! "$_nameserver" ] &&
1295		f_resolv_conf_nameservers cp && _nameserver=${cp%%[$IFS]*}
1296	[ "$_ipaddr" ] || f_ifconfig_inet $devname _ipaddr
1297	[ "$_netmask" ] || f_ifconfig_netmask $devname _netmask
1298
1299	# If non-interactive, jump over dialog section and into config section
1300	if f_netinteractive || f_interactive || [ ! "$_hostname" ]
1301	then
1302		[ ! "$_hostname" ] && f_interactive &&
1303			f_show_msg "$msg_hostname_variable_not_set"
1304
1305		local title=" $msg_network_configuration "
1306		local hline="$hline_alnum_arrows_punc_tab_enter"
1307		local extras_help="$tcplayout_extras_help"
1308
1309		# Modify the help line for PLIP config
1310		[ "${devname#plip}" != "$devname" ] &&
1311			extras_help="$tcplayout_extras_help_for_plip"
1312
1313		f_getvar $VAR_IPV6ADDR cp && [ "$cp" ] &&
1314			title="$title($msg_ipv6_ready) "
1315
1316		if [ ! "$USE_XDIALOG" ]; then
1317			local prompt="$msg_dialog_mixedform_navigation_help"
1318			# Calculate center position for displaying device label
1319			local devlabel="$msg_configuration_for_interface"
1320			devlabel="$devlabel $devname"
1321			local width=54
1322			local n=$(( $width/2 - (${#devlabel} + 4)/2 - 2 ))
1323
1324			while :; do
1325				cp=$( $DIALOG \
1326					--title "$title"                     \
1327					--backtitle "$DIALOG_BACKTITLE"      \
1328					--hline "$hline"                     \
1329					--item-help                          \
1330					--ok-label "$msg_ok"                 \
1331					--cancel-label "$msg_cancel"         \
1332					--help-button                        \
1333					--help-label "$msg_help"             \
1334					--mixedform "$prompt" 16 $width 9    \
1335					"$msg_host_name_including_domain:" 1 2 \
1336						"$_hostname" 2 3 45 255 0    \
1337						"$tcplayout_hostname_help"   \
1338					"$msg_ipv4_gateway:" 3 2             \
1339						"$_gateway" 4 3 16 15 0      \
1340						"$tcplayout_gateway_help"    \
1341					"$msg_name_server:" 3 31             \
1342						"$_nameserver" 4 32 16 15 0  \
1343						"$tcplayout_nameserver_help" \
1344					"- $devlabel -" 5 $n "" 0 0 0 0 3 "" \
1345					"$msg_ipv4_address:" 6 6             \
1346						"$_ipaddr" 7 7 16 15 0       \
1347						"$tcplayout_ipaddr_help"     \
1348					"$msg_netmask:" 6 31                 \
1349						"$_netmask" 7 32 16 15 0     \
1350						"$tcplayout_netmask_help"    \
1351					"$msg_extra_options_to_ifconfig" 8 6 \
1352						"$_extras" 9 7 41 2048 0     \
1353						"$extras_help"               \
1354					2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD )
1355
1356				# --mixed-form always returns 0, we have to
1357				# use the returned data to determine button
1358				if [ ! "$cp" ]; then
1359					# User either chose "Cancel", pressed
1360					# ESC, or blanked every form field
1361					return $DIALOG_CANCEL
1362				else
1363					n=$( echo "$cp" | f_number_of_lines )
1364					[ $n -eq 1 ] && case "$cp" in HELP*)
1365						# User chose "Help"
1366						f_show_help "$TCP_HELPFILE"
1367						continue
1368					esac
1369				fi
1370
1371				# Turn mixed-form results into env variables
1372				eval "$( echo "$cp" | awk '
1373				BEGIN {
1374					n = 0
1375					field[++n] = "_hostname"
1376					field[++n] = "_gateway"
1377					field[++n] = "_nameserver"
1378					field[++n] = "_ipaddr"
1379					field[++n] = "_netmask"
1380					field[++n] = "_extras"
1381					nfields = n
1382					n = 0
1383				}
1384				{
1385					gsub(/'\''/, "'\'\\\\\'\''")
1386					sub(/[[:space:]]*$/, "")
1387					value[field[++n]] = $0
1388				}
1389				END {
1390					for ( n = 1; n <= nfields; n++ )
1391					{
1392						printf "%s='\''%s'\'';\n",
1393						       field[n],
1394						       value[field[n]]
1395					}
1396				}' )"
1397
1398				f_dialog_validate_tcpip \
1399					"$_hostname" \
1400					"$_gateway" \
1401					"$_nameserver" \
1402					"$_ipaddr" \
1403					"$_netmask" \
1404					&& break
1405			done
1406		else
1407			# Xdialog(1) does not support --mixed-form
1408			# Create a persistent menu instead
1409
1410			f_dialog_title "$msg_network_configuration"
1411			local prompt=
1412
1413			while :; do
1414				cp=$( $DIALOG \
1415					--title "$DIALOG_TITLE"               \
1416					--backtitle "$DIALOG_BACKTITLE"       \
1417					--hline "$hline"                      \
1418					--item-help                           \
1419					--ok-label "$msg_ok"                  \
1420					--cancel-label "$msg_cancel"          \
1421					--help ""                             \
1422					--menu "$prompt" 21 60 8              \
1423					"$msg_accept_continue" ""             \
1424						"$tcplayout_accept_cont_help" \
1425					"$msg_host_name_including_domain:"    \
1426						"$_hostname"                  \
1427						"$tcplayout_hostname_help"    \
1428					"$msg_ipv4_gateway:" "$_gateway"      \
1429						"$tcplayout_gateway_help"     \
1430					"$msg_name_server:" "$_nameserver"    \
1431						"$tcplayout_nameserver_help"  \
1432					"$msg_ipv4_address:" "$_ipaddr"       \
1433						"$tcplayout_ipaddr_help"      \
1434					"$msg_netmask:" "$_netmask"           \
1435						"$tcplayout_netmask_help"     \
1436					"$msg_extra_options_to_ifconfig"      \
1437						"$_extras" "$extras_help"     \
1438					2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
1439				)
1440				local retval=$?
1441				f_dialog_data_sanitize cp
1442				f_dprintf "retval=%u mtag=[%s]" $retval "$cp"
1443
1444				if [ $retval -eq $DIALOG_HELP ]; then
1445					f_show_help "$TCP_HELPFILE"
1446					continue
1447				elif [ $retval -ne $DIALOG_OK ]; then
1448					f_dialog_title_restore
1449					return $DIALOG_CANCEL
1450				fi
1451
1452				case "$cp" in
1453				"$msg_accept_continue")
1454					f_dialog_validate_tcpip \
1455						"$_hostname" \
1456						"$_gateway" \
1457						"$_nameserver" \
1458						"$_ipaddr" \
1459						"$_netmask" \
1460						&& break ;;
1461				"$msg_host_name_including_domain:")
1462					f_dialog_input cp "$cp" "$_hostname" \
1463						&& _hostname="$cp" ;;
1464				"$msg_ipv4_gateway:")
1465					f_dialog_input cp "$cp" "$_gateway" \
1466						&& _gateway="$cp" ;;
1467				"$msg_name_server:")
1468					f_dialog_input cp "$cp" "$_nameserver" \
1469						&& _nameserver="$cp" ;;
1470				"$msg_ipv4_address:")
1471					f_dialog_input cp "$cp" "$_ipaddr" \
1472						&& _ipaddr="$cp" ;;
1473				"$msg_netmask:")
1474					f_dialog_input cp "$cp" "$_netmask" \
1475						&& _netmask="$cp" ;;
1476				"$msg_extra_options_to_ifconfig")
1477					f_dialog_input cp "$cp" "$_extras" \
1478						&& _extras="$cp" ;;
1479				esac
1480			done
1481
1482			f_dialog_title_restore
1483
1484		fi # XDIALOG
1485
1486	fi # interactive
1487
1488	# We actually need to inform the rest of bsdconfig about this
1489	# data now if the user hasn't selected cancel.
1490
1491	if [ "$_hostname" ]; then
1492		setvar $VAR_HOSTNAME "$_hostname"
1493		f_quietly hostname "$_hostname"
1494		case "$_hostname" in
1495		*.*) setvar $VAR_DOMAINNAME "${_hostname#*.}" ;;
1496		esac
1497	fi
1498	[ "$_gateway"    ] && setvar $VAR_GATEWAY    "$_gateway"
1499	[ "$_nameserver" ] && setvar $VAR_NAMESERVER "$_nameserver"
1500	[ "$_ipaddr"     ] && setvar $VAR_IPADDR     "$_ipaddr"
1501	[ "$_netmask"    ] && setvar $VAR_NETMASK    "$_netmask"
1502	[ "$_extras"     ] && setvar $VAR_EXTRAS     "$_extras"
1503
1504	f_dprintf "Creating struct DEVICE_INFO devinfo_%s" "$dev"
1505	f_struct_new DEVICE_INFO devinfo_$dev
1506	$dev set private devinfo_$dev
1507
1508	devinfo_$dev set ipaddr    $_ipaddr
1509	devinfo_$dev set netmask   $_netmask
1510	devinfo_$dev set extras    $_extras
1511	devinfo_$dev set use_rtsol $use_rtsol
1512	devinfo_$dev set use_dhcp  $use_dhcp
1513
1514	if [ "$use_dhcp" -o "$_ipaddr" ]; then
1515		if [ "$use_dhcp" ]; then
1516			cp="DHCP${extras:+ $extras}"
1517		else
1518			cp="inet $_ipaddr netmask $_netmask${extras:+ $extras}"
1519		fi
1520		setvar $VAR_IFCONFIG$devname "$cp"
1521	fi
1522	[ "$use_rtsol" ] &&
1523		setvar $VAR_IPV6_ENABLE "YES"
1524
1525	[ "$use_dhcp" ] ||
1526		f_config_resolv # XXX this will do it on the MFS copy
1527
1528	return $DIALOG_OK
1529}
1530
1531# f_device_scan_tcp [$var_to_set]
1532#
1533# Scan for the first active/configured TCP/IP device. The name of the interface
1534# is printed to stderr like other dialog(1)-based functions (stdout is reserved
1535# for dialog(1) interaction) if $var_to_set is missing or NULL. Returns failure
1536# if no active/configured interface
1537#
1538f_device_scan_tcp()
1539{
1540	local __var_to_set="$1" __iface
1541	for __iface in $( ifconfig -l ); do
1542		if ifconfig $__iface | awk '
1543		BEGIN {
1544			has_inet = has_inet6 = is_ethernet = 0
1545			is_usable = 1
1546		}
1547		( $1 == "status:" && $2 != "active" ) { is_usable = 0; exit }
1548		( $1 == "inet" ) {
1549			if ($2 == "0.0.0.0") { is_usable = 0; exit }
1550			has_inet++
1551		}
1552		( $1 == "inet6") { has_inet6++ }
1553		( $1 == "media:" ) {
1554			if ($2 != "Ethernet") { is_usable = 0; exit }
1555			is_ethernet = 1
1556		}
1557		END {
1558			if (!(is_ethernet && (has_inet || has_inet6)))
1559				is_usable = 0
1560			exit ! is_usable
1561		}'; then
1562			f_interactive &&
1563				f_show_msg "$msg_using_interface" "$__iface"
1564			f_dprintf "f_device_scan_tcp found %s" "$__iface"
1565			if [ "$__var_to_set" ]; then
1566				setvar "$__var_to_set" "$__iface"
1567			else
1568				echo "$__iface" >&2
1569			fi
1570			return $SUCCESS
1571		fi
1572	done
1573
1574	return $FAILURE
1575}
1576
1577# f_device_select_tcp
1578#
1579# Prompt the user to select network interface to use for TCP/IP access.
1580# Variables from variable.subr that can be used to script user input:
1581#
1582# 	VAR_NETWORK_DEVICE [Optional]
1583# 		Either a comma-separated list of network interfaces to try when
1584# 		setting up network access (e.g., "fxp0,em0") or "ANY" (case-
1585# 		sensitive) to indicate that the first active and configured
1586# 		interface is acceptable. If unset, the user is presented with a
1587# 		menu of all available network interfaces.
1588#
1589# Returns success if a valid network interface has been selected.
1590#
1591f_device_select_tcp()
1592{
1593	local devs dev cnt if network_dev
1594	f_getvar $VAR_NETWORK_DEVICE network_dev
1595
1596	f_dprintf "f_device_select_tcp: %s=[%s]" \
1597	          VAR_NETWORK_DEVICE "$network_dev"
1598
1599	if [ "$network_dev" ]; then
1600		#
1601		# This can be set to several types of values. If set to ANY,
1602		# scan all network devices looking for a valid link, and go
1603		# with the first device found. Can also be specified as a
1604		# comma delimited list, with each network device tried in
1605		# order. Can also be set to a single network device.
1606		#
1607		[ "$network_dev" = "ANY" ] && f_device_scan_tcp network_dev
1608
1609		while [ "$network_dev" ]; do
1610			case "$network_dev" in
1611			*,*) if="${network_dev%%,*}"
1612			     network_dev="${network_dev#*,}"
1613			     ;;
1614			  *) if="$network_dev"
1615			     network_dev=
1616			esac
1617
1618			f_device_find -1 "$if" $DEVICE_TYPE_NETWORK dev
1619			f_device_dialog_tcp $dev
1620			if [ $? -eq $DIALOG_OK ]; then
1621				setvar $VAR_NETWORK_DEVICE $if
1622				return $DIALOG_OK
1623			fi
1624		done
1625
1626		f_interactive && f_show_msg "$msg_no_network_devices"
1627		return $DIALOG_CANCEL
1628
1629	fi # $network_dev
1630
1631	f_device_find "" $DEVICE_TYPE_NETWORK devs
1632	f_count cnt $devs
1633	dev="${devs%%[$IFS]*}"
1634	$dev get name if
1635
1636	f_quietly f_getvar NETWORK_CONFIGURED # for debugging info
1637	if ! f_running_as_init &&
1638	   ! [ "${NETWORK_CONFIGURED+set}" -a "$NETWORK_CONFIGURED" = "NO" ]
1639	then
1640		trap 'f_interrupt' SIGINT
1641		if f_dialog_yesno "$msg_assume_network_is_already_configured"
1642		then
1643			setvar $VAR_NETWORK_DEVICE $if
1644			return $DIALOG_OK
1645		fi
1646	fi
1647
1648	local retval=$SUCCESS
1649	if [ ${cnt:=0} -eq 0 ]; then
1650		f_show_msg "$msg_no_network_devices"
1651		retval=$DIALOG_CANCEL
1652	elif [ $cnt -eq 1 ]; then
1653		f_device_dialog_tcp $dev
1654		retval=$?
1655		[ $retval -eq $DIALOG_OK ] && setvar $VAR_NETWORK_DEVICE $if
1656	else
1657		local title="$msg_network_interface_information_required"
1658		local prompt="$msg_please_select_ethernet_device_to_configure"
1659		local hline="$hline_arrows_tab_enter"
1660
1661		dev=$( f_device_menu \
1662			"$title" "$prompt" "$hline" $DEVICE_TYPE_NETWORK \
1663			"$NETWORK_DEVICE_HELPFILE" \
1664			2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD ) ||
1665			return $DIALOG_CANCEL
1666
1667		f_device_dialog_tcp $dev
1668		retval=$?
1669		if [ $retval -eq $DIALOG_OK ]; then
1670			f_struct_copy "$dev" device_network
1671			setvar $VAR_NETWORK_DEVICE device_network
1672		else
1673			f_struct_free device_network
1674		fi
1675	fi
1676
1677	return $retval
1678}
1679
1680# f_dialog_menu_select_tcp
1681#
1682# Like f_dialog_select_tcp() above, but do it from a menu that doesn't care
1683# about status. In other words, where f_dialog_select_tcp() will not display a
1684# menu if scripted, this function will always display the menu of available
1685# network interfaces.
1686#
1687f_dialog_menu_select_tcp()
1688{
1689	local private use_dhcp name
1690	NETWORK_CONFIGURED=NO f_device_select_tcp
1691	if f_struct device_network &&
1692	   device_network get private private &&
1693	   f_struct_copy "$private" di &&
1694	   di get use_dhcp use_dhcp &&
1695	   [ ! "$use_dhcp" ] &&
1696	   device_network get name name &&
1697	   f_yesno "$msg_would_you_like_to_bring_interface_up" "$name"
1698	then
1699		if ! f_device_init device_network; then
1700			f_show_msg "$msg_initialization_of_device_failed" \
1701			           "$name"
1702		fi
1703	fi
1704	return $DIALOG_OK
1705}
1706
1707############################################################ MAIN
1708
1709f_dprintf "%s: Successfully loaded." media/tcpip.subr
1710
1711fi # ! $_MEDIA_TCPIP_SUBR
1712