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