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