]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - usr.sbin/bsdconfig/share/media/tcpip.subr
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.git] / usr.sbin / bsdconfig / share / media / tcpip.subr
1 if [ ! "$_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
31 BSDCFG_SHARE="/usr/share/bsdconfig"
32 . $BSDCFG_SHARE/common.subr || exit 1
33 f_dprintf "%s: loading includes..." media/tcpip.subr
34 f_include $BSDCFG_SHARE/device.subr
35 f_include $BSDCFG_SHARE/dialog.subr
36 f_include $BSDCFG_SHARE/struct.subr
37 f_include $BSDCFG_SHARE/variable.subr
38
39 BSDCFG_LIBE="/usr/libexec/bsdconfig"
40 f_include_lang $BSDCFG_LIBE/include/messages.subr
41
42 TCP_HELPFILE=$BSDCFG_LIBE/include/tcp.hlp
43 NETWORK_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 #
65 f_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 #
118 f_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 #
166 f_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 #
197 f_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 #
271 f_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 #
394 f_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 #
431 f_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 #
452 f_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 #
490 f_ifconfig_inet_awk='
491 BEGIN { found = 0 }
492 ( $1 == "inet" ) \
493 {
494         print $2
495         found = 1
496         exit
497 }
498 END { exit ! found }
499 '
500 f_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 #
524 f_ifconfig_inet6_awk='
525 BEGIN { found = 0 }
526 ( $1 == "inet6" ) \
527 {
528         print $2
529         found = 1
530         exit
531 }
532 END { exit ! found }
533 '
534 f_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 #
555 f_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 #
596 f_route_get_default_awk='
597 BEGIN { found = 0 }
598 ( $1 == "gateway:" ) \
599 {
600         print $2
601         found = 1
602         exit
603 }
604 END { exit ! found }
605 '
606 f_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 #
630 f_resolv_conf_nameservers_awk='
631 BEGIN { found = 0 }
632 ( $1 == "nameserver" ) \
633 {
634         print $2
635         found = 1
636 }
637 END { exit ! found }
638 '
639 f_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 #
675 f_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 #
781 f_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 #
894 f_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 #
938 f_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 #
985 f_host_lookup_awk='
986 BEGIN{ addrs = "" }
987 !/^[[:space:]]*(#|$)/ \
988 {
989         for (n=1; n++ < NF;) if ($n == name)
990                 addrs = addrs (addrs ? " " : "") $1
991 }
992 END {
993         if (addrs) print addrs
994         exit !addrs
995 }
996 '
997 f_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 #
1134 f_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 #
1535 f_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 #
1588 f_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 #
1693 f_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
1715 f_dprintf "%s: Successfully loaded." media/tcpip.subr
1716
1717 fi # ! $_MEDIA_TCPIP_SUBR