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