#!/sbin/sh
#
# Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T.
# All rights reserved.
#
# THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF AT&T
# The copyright notice above does not evidence any
# actual or intended publication of such source code.
#
# Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
# Use is subject to license terms.
#
#ident	"@(#)network	1.17	04/03/16 SMI"

# Make sure that the libraries essential to this stage of booting can be found.
LD_LIBRARY_PATH=/etc/lib export LD_LIBRARY_PATH

#
# Cause ifconfig to not automatically start in.mpathd when IPMP groups are
# configured.  This is not strictly necessary but makes it so that in.mpathd
# will always be started explicitly from /etc/init.d/inetinit, when we're
# sure that /usr is mounted.
#
SUNW_NO_MPATHD= export SUNW_NO_MPATHD

#
# A number of interface lists are used by the script.
#
# Inet_list, list of IPv4 interfaces.
# Inet_plumbed, list of plumbed IPv4 interfaces.
# Inet_failed, list of IPv4 interfaces that failed to plumb.
# Inet6_list, list of IPv6 interfaces.
# Inet6_plumbed, list of plumbed IPv6 interfaces.
# Inet6_failed, list of IPv6 interfaces that failed to plumb.
#
unset inet_list inet_plumbed inet_failed \
	inet6_list inet6_plumbed inet6_failed
#
# get_physical interface
#
# Return physical interface corresponding to the given logical
# interface.
#
get_physical()
{
	ORIGIFS="$IFS"
	IFS="${IFS}:"
	set -- $1
	IFS="$ORIGIFS"

	echo $1
}

#
# get_logical interface
#
# Return logical interface number.  Zero will be returned
# if there is no explicit logical device number.
#
get_logical()
{
	ORIGIFS="$IFS"
	IFS="${IFS}:"
	set -- $1
	IFS="$ORIGIFS"

	if [ -z "$2" ]; then
		echo 0
	else
		echo $2
	fi
}

#
# if_comp if1 if2
#
# Compare Interfaces.  Do the physical interface names and logical interface
# numbers match?
#
if_comp()
{
	[ "`get_physical $1`" = "`get_physical $2`" ] && \
		[ `get_logical $1` -eq `get_logical $2` ]
}
	
#
# physical_comp if1 if2
# 
# Do the two devices share a physical interface?
#
physical_comp()
{
	[ "`get_physical $1`" = "`get_physical $2`" ]
}

#
# in_list op item list
#
# Is "item" in the given list?  Use "op" to do the test, applying it to
# "item" and each member of the list in turn until it returns success.
#
in_list()
{
	op=$1
	item=$2
	shift 2

	while [ $# -gt 0 ]; do
		$op $item $1 && return 0
		shift
	done

	return 1
}

#
# get_group_from_hostname interface type
#
# Return all group settings from hostname file for a given interface.
#
# Example:
#	get_group_from_hostname  hme0 inet
#
get_group_from_hostname()
{
	case "$2" in
		inet) file=/etc/hostname.$1
			;;
		inet6) file=/etc/hostname6.$1
			;;
		*)
			return
			;;
	esac

	[ -r "$file" ] || return 

	#
	# Read through the hostname file looking for group settings
	# There may be several group settings in the file.  It is up
	# to the caller to pick the right one (i.e. the last one).
	#
	while read line; do
		[ -z "$line" ] && continue
		/sbin/ifparse -s "$2" $line
	done < "$file" | while read one two three; do
		[ "$one" = "group" ] && echo "$two"
	done
}

#
# get_group_for_type interface type list
#
# Look through the set of hostname files associated with the same physical
# interface as "interface", and determine which group they would configure.
# Only hostname files associated with the physical interface or logical
# interface zero are allowed to set the group.
#
get_group_for_type()
{
	physical=`get_physical $1`

	type=$2
	group=""

	#
	# The last setting of the group is the one that counts, which is
	# the reason for the second while loop.
	#
	shift 2
	while [ $# -gt 0 ]; do
		if if_comp "$physical" $1; then 
			get_group_from_hostname $1 $type
		fi
		shift
	done | while :; do
		read next || {
			echo "$group"
			break
		}
		group="$next"
	done
}

#
# get_group interface [ configured | failed ]
#
# If there is both an inet and inet6 version of an interface, the group
# could be set in either set of hostname files.
#
# Inet6 is configured after inet, so if the group is set in both
# sets of hostname files, the inet6 file wins.
#
# The "configured" argument should be used to get the group for
# an interface that has been plumbed into the stack and configured.  Use
# the "failed" argument to get the group for an interface that failed to
# plumb.
#
get_group()
{
	group=""

	case "$2" in
		configured)
			group=`get_group_for_type $1 inet6 $inet6_plumbed`
			;;
		failed)
			group=`get_group_for_type $1 inet6 $inet6_list`
			;;
		*)
			return
			;;
	esac

	if [ -z "$group" ]; then
		if [ "$2" = configured ]; then
			group=`get_group_for_type $1 inet $inet_plumbed`
		else
			group=`get_group_for_type $1 inet $inet_list`
		fi
	fi

	echo $group
}

#
# get_standby_from_hostname interface type
# 
# Return any "standby" or "-standby" flags in the hostname file.
#
# Example:
#	get_standby_from_hostname hme0 inet6
#
#
get_standby_from_hostname()
{
	case "$2" in
		inet) file=/etc/hostname.$1
			;;
		inet6) file=/etc/hostname6.$1
			;;
		*)
			return
			;;
	esac

	[ -r "$file" ] || return

	#
	# There may be several instances of the "standby" and
	# "-standby" flags in the hostname file.  It is up to
	# the caller to pick the correct one.
	#
	while read line; do
		[ -z "$line" ] && continue
		/sbin/ifparse -s "$2" $line
	done < "$file" | while read one two; do
		[ "$one" = "standby" ] || [ "$one" = "-standby" ] \
			&& echo "$one"
	done 
}

#
# get_standby_for_type interface type plumbed_list
#
# Look through the set of hostname files associated with the same physical
# interface as "interface", and determine whether they would configure
# the interface as a standby interface.
#
get_standby_for_type()
{

	physical=`get_physical $1`
	type=$2

	final=""

	#
	# The last "standby" or "-standby" flag is the one that counts,
	# which is the reason for the second while loop.
	#
	shift 2
	while [ $# -gt 0 ]; do
		if [ "`get_physical $1`" = "$physical" ]; then 
			get_standby_from_hostname $1 $type
		fi
		shift
	done | while :; do
		read next || {
			echo "$final"
			break
		}
		final="$next"
	done
}

#
# is_standby interface
#
# Determine whether a configured interface is a standby interface.
#
# Both the inet and inet6 hostname file sets must be checked.
# If "standby" or "-standby" is set in the inet6 hostname file set,
# don't bother looking at the inet set.
#
is_standby()
{
	standby=`get_standby_for_type $1 inet6 $inet6_plumbed`

	if [ -z "$standby" ]; then
		standby=`get_standby_for_type $1 inet $inet_plumbed`
	fi

	# The return value is the value of the following test.
	[ "$standby" = "standby" ]
}

#
# get_alternate interface plumbed_list
#
# Look for a plumbed interface in the same group as "interface".
# A standby interface is preferred over a non-standby interface.
#
# Example:
#	get_alternate hme0 $inet_plumbed
#
get_alternate()
{
	mygroup=`get_group $1 failed`
	[ -z "$mygroup" ] && return

	maybe=""

	shift
	while [ $# -gt 0 ]; do
		group=`get_group $1 configured`
		if [ "$group" = "$mygroup" ]; then
			if is_standby $1; then
				get_physical $1
				return
			else
				[ -z "$maybe" ] && maybe=$1
			fi
		fi
		shift
	done

	get_physical $maybe
}

#
# doDHCPhostname interface
# Pass to this function the name of an interface.  It will return
# true if one should enable the use of DHCP client-side host name
# requests on the interface, and false otherwise.
#
doDHCPhostname()
{
	if [ -f /etc/dhcp.$1 ] && [ -f /etc/hostname.$1 ]; then
                set -- `shcat /etc/hostname.$1`
                [ $# -eq 2 -a "$1" = "inet" ]
                return $?      
        fi
        return 1
}

#
# inet_process_hostname processor [ args ]
#
# Process an inet hostname file.  The contents of the file
# are taken from standard input. Each line is passed
# on the command line to the "processor" command.
# Command line arguments can be passed to the processor.
#
# Examples:
#	inet_process_hostname /sbin/ifconfig hme0 < /etc/hostname.hme0
#	
#	inet_process_hostname /sbin/ifparse -f < /etc/hostname.hme0
#
# If there is only line in an hostname file we assume it contains
# the old style address which results in the interface being brought up 
# and the netmask and broadcast address being set.
#
# If there are multiple lines we assume the file contains a list of
# commands to the processor with neither the implied bringing up of the
# interface nor the setting of the default netmask and broadcast address.
#
inet_process_hostname()
{
	if doDHCPhostname $2; then
		:
	else
		#
		# Redirecting input from a file results in a sub-shell being
		# used, hence this outer loop surrounding the "multiple_lines"
		# and "ifcmds" variables.
		#
		while :; do
			multiple_lines=false
			ifcmds=""

			while read line; do
				if [ -n "$ifcmds" ]; then
					#
					# This handles the first N-1
					# lines of a N-line hostname file.
					#
					$* $ifcmds
					multiple_lines=true
				fi
				ifcmds="$line"
			done

			#
			# If the hostname file is empty or consists of only
			# blank lines, break out of the outer loop without
			# configuring the newly plumbed interface.
			#
			[ -z "$ifcmds" ] && break
			if [ $multiple_lines = false ]; then
				# The traditional single-line hostname file.
				ifcmds="$ifcmds netmask + broadcast + up"
			fi

			#
			# This handles either the single-line case or
			# the last line of the N-line case.
			#
			$* $ifcmds
			break
		done
	fi
}

#
# inet6_process_hostname processor [ args ]
#
# Process an inet6 hostname file.  The contents of the file
# are taken from standard input. Each line is passed
# on the command line to the "processor" command.
# Command line arguments can be passed to the processor.
#
# Examples:
#	inet6_process_hostname /sbin/ifconfig hme0 inet6 < /etc/hostname6.hme0
#	
#	inet6_process_hostname /sbin/ifparse -f inet6 < /etc/hostname6.hme0
#
inet6_process_hostname()
{
	while read ifcmds; do
		if [ -n "$ifcmds" ]; then
			$* $ifcmds
		fi
	done
}

#
# Process interfaces that failed to plumb.  Find an alternative
# interface to host the addresses.  For IPv6, only static addresses
# defined in hostname6 files are moved, autoconfigured addresses are
# not moved.
#
# Example:
#	move_addresses inet6
#
move_addresses()
{
	type="$1"
	eval "failed=\"\$${type}_failed\""
	eval "plumbed=\"\$${type}_plumbed\""
	eval "list=\"\$${type}_list\""
	process_hostname="${type}_process_hostname"
	processed=""

	if [ "$type" = inet ]; then
		echo "moving addresses from failed IPv4 interfaces:\c"
		zaddr="0.0.0.0"
		hostpfx="/etc/hostname"
	else
		echo "moving addresses from failed IPv6 interfaces:\c"
		zaddr="::"
		hostpfx="/etc/hostname6"
	fi

	set -- $failed
	while [ $# -gt 0 ]; do
		in_list if_comp $1 $processed && { shift; continue; }

		alternate="`get_alternate $1 $plumbed`"
		if [ -z "$alternate" ]; then
			in_list physical_comp $1 $processed || { 
				echo " $1 (couldn't move, no" \
					"alternative interface)\c"
				processed="$processed $1"
			}
			shift
			continue
		fi
		#
		# The hostname files are processed twice.  In the first
		# pass, we are looking for all commands that apply
		# to the non-additional interface address.  These may be
		# scattered over several files.  We won't know
		# whether the address represents a failover address
		# or not until we've read all the files associated with the
		# interface.

		# In the first pass through the hostname files, all
		# additional logical interface commands are removed.
		# The remaining commands are concatenated together and
		# passed to ifparse to determine whether the 
		# non-additional logical interface address is a failover
		# address.  If it as a failover address, the
		# address may not be the first item on the line,
		# so we can't just substitute "addif" for "set".
		# We prepend an "addif $zaddr" command, and let
		# the embedded "set" command set the address later.	
		#
		/sbin/ifparse -f $type `
				for item in $list; do
					if_comp $1 $item && \
					$process_hostname /sbin/ifparse \
					$type < $hostpfx.$item 
					done  | while read three four; do
					[ "$three" != addif ] && \
						echo "$three $four \c"
				done` | while read one two; do
					[ -z "$one" ] && continue
					line="addif $zaddr $one $two"
					/sbin/ifconfig $alternate $type \
						-standby $line >/dev/null
				done

		#
		# In the second pass, look for the the "addif" commands
		# that configure additional failover addresses.  Addif
		# commands are not valid in logical interface hostname
		# files.
		#
		if [ "$1" = "`get_physical $1`" ]; then
			$process_hostname /sbin/ifparse -f $type \
			<$hostpfx.$1 | while read one two; do
			[ "$one" = addif ] && \
				/sbin/ifconfig $alternate $type -standby \
				    addif $two >/dev/null
			done
		fi

		in_list physical_comp $1 $processed || { 
			echo " $1 (moved to $alternate)\c"
			processed="$processed $1"
		}
		shift
	done
	echo "."
}

#
# Configure the software loopback driver. The network initialization is
# done early to support diskless and dataless configurations.
#
/sbin/ifconfig lo0 plumb 127.0.0.1 up

#
# If the system was net booted by DHCP, hand DHCP management off to the
# DHCP agent (ifconfig communicates to the DHCP agent through the loopback
# interface).
#
if [ -n "$_INIT_NET_IF" -a "$_INIT_NET_STRATEGY" = "dhcp" ]; then
	/sbin/dhcpagent -a 
fi

#
# The network initialization is done early to support diskless and dataless
# configurations.  For IPv4 interfaces that were configured by the kernel (e.g.
# those on diskless machines) and not configured by DHCP, reset the netmask
# using the local "/etc/netmasks" file if one exists, and then reset the
# broadcast address based on the netmask.
#
/sbin/ifconfig -auD4 netmask + broadcast +

#
# All the IPv4 and IPv6 interfaces are plumbed before doing any interface
# configuration.  This prevents errors from plumb failures getting
# mixed in with the configured interface lists that the script outputs.
#

#
# Get the list of IPv4 interfaces to configure by breaking
# /etc/hostname.* into separate args by using "." as a shell separator
# character.
#
interface_names="`echo /etc/hostname.*[0-9] 2>/dev/null`"
if [ "$interface_names" != "/etc/hostname.*[0-9]" ]; then
	ORIGIFS="$IFS"
	IFS="$IFS."
	set -- $interface_names
	IFS="$ORIGIFS"
	while [ $# -ge 2 ]; do
		shift
		if [ "$1" = "xx0" ]; then
			#
			# For some unknown historical reason the xx0
			# ifname is ignored.
			#
			shift
			continue
		fi
		if [ $# -gt 1 -a "$2" != "/etc/hostname" ]; then
			while [ $# -gt 1 -a "$1" != "/etc/hostname" ]; do
				shift
			done
		else
			inet_list="$inet_list $1"
			shift
		fi
	done
fi

#
# Get the list of IPv6 interfaces to configure by breaking
# /etc/hostname6.* into separate args by using "." as a shell separator
# character.
#
interface_names="`echo /etc/hostname6.*[0-9] 2>/dev/null`"
if [ "$interface_names" != "/etc/hostname6.*[0-9]" ]; then
	ORIGIFS="$IFS"
	IFS="$IFS."
	set -- $interface_names
	IFS="$ORIGIFS"
	while [ $# -ge 2 ]; do
		shift
		if [ $# -gt 1 -a "$2" != "/etc/hostname6" ]; then
			while [ $# -gt 1 -a "$1" != "/etc/hostname6" ]; do
				shift
			done
		else
			inet6_list="$inet6_list $1"
			shift
		fi
	done
fi

#
# Step through the IPv4 interface list and try to plumb every interface.
# Generate list of plumbed and failed IPv4 interfaces.
#
if [ -n "$inet_list" ]; then
	set -- $inet_list
	while [ $# -gt 0 ]; do
		/sbin/ifconfig $1 plumb
		if /sbin/ifconfig $1 inet >/dev/null 2>&1; then
			inet_plumbed="$inet_plumbed $1"
		else
			inet_failed="$inet_failed $1"
		fi
		shift
	done
fi

#
# If there is at least one IPv6 interface configure the IPv6 loopback.
# Step through the IPv6 interface list and plumb every interface.
# Generate list of plumbed and failed IPv6 interfaces
#
if [ -n "$inet6_list" ]; then
	/sbin/ifconfig lo0 inet6 plumb ::1 up

	set -- $inet6_list
	while [ $# -gt 0 ]; do
		/sbin/ifconfig $1 inet6 plumb up
		if /sbin/ifconfig $1 inet6 >/dev/null 2>&1; then
			inet6_plumbed="$inet6_plumbed $1"
		else
			inet6_failed="$inet6_failed $1"
		fi
		shift
	done
fi

#
# Process the /etc/hostname.* files of plumbed IPv4 interfaces.
# If an /etc/hostname file is not present or is empty, the ifconfig
# auto-dhcp / auto-revarp command will attempt to set the address,
# later.
# If /etc/hostname.lo0 exists the loop below will do additional
# configuration of lo0.
#
if [ -n "$inet_plumbed" ]; then
	echo "configuring IPv4 interfaces:\c"
	set -- $inet_plumbed
	while [ $# -gt 0 ]; do
		inet_process_hostname /sbin/ifconfig $1 inet \
		    </etc/hostname.$1 >/dev/null
		echo " $1\c"
		shift
	done
	echo "."
fi

#
# Process the /etc/hostname6.* files of plumbed IPv6 interfaces.
# If /etc/hostname6.lo0 exists the loop below will do
# additional configuration of lo0.
#
if [ -n "$inet6_plumbed" ]; then
	echo "configuring IPv6 interfaces:\c"
	set -- $inet6_plumbed
	while [ $# -gt 0 ]; do
		inet6_process_hostname /sbin/ifconfig $1 inet6 \
		    </etc/hostname6.$1 >/dev/null
		echo " $1\c"
		shift
	done
	echo "."
fi

# Run DHCP if requested. Skip boot-configured interface.
interface_names="`echo /etc/dhcp.*[0-9] 2>/dev/null`"
if [ "$interface_names" != '/etc/dhcp.*[0-9]' ]; then
	#
	# First find the primary interface. Default to the first
	# interface if not specified. First primary interface found
	# "wins". Use care not to "reconfigure" a net-booted interface
	# configured using DHCP. Run through the list of interfaces
	# again, this time trying DHCP.
	#
	firstif=
	primary=
	ORIGIFS="$IFS"
	IFS="${IFS}."
	set -- $interface_names

	while [ $# -ge 2 ]; do
		shift
		[ -z "$firstif" ] && firstif=$1

		for i in `shcat /etc/dhcp\.$1`; do
			if [ "$i" = primary ]; then
				primary=$1
				break
			fi
		done

		[ -n "$primary" ] && break
		shift
	done

	[ -z "$primary" ] && primary="$firstif"
	cmdline=`shcat /etc/dhcp\.${primary}`

	if [ "$_INIT_NET_IF" != "$primary" ]; then
		echo "starting DHCP on primary interface $primary"
		/sbin/ifconfig $primary auto-dhcp primary $cmdline

		#
		# diskfull machine which uses dhcp. Set strategy for
		# the use of the rest of this run level.
		#
		_INIT_NET_STRATEGY="dhcp"
		export _INIT_NET_STRATEGY
	fi

	set -- $interface_names

	while [ $# -ge 2 ]; do
		shift
		cmdline=`shcat /etc/dhcp\.$1`
		if [ "$1" != "$primary" -a \
		    "$1" != "$_INIT_NET_IF"  ]; then
			echo "starting DHCP on interface $1"
			/sbin/ifconfig $1 dhcp start wait 0 $cmdline
		fi
		shift
	done
	IFS="$ORIGIFS"
	unset ORIGIFS
fi

# Configure the rest of the IPv4 interfaces automatically, quietly.
/sbin/ifconfig -adD4 auto-revarp netmask + broadcast + up

#
# If DHCP was used on a primary interface then set the hostname
# that was returned. If no hostname was returned, set the name
# to be "unknown". The hostname must be set to something, because
# tooltalk will hang unless the name can be locally resolved.
# Sendmail also requires the name to be resolvable locally.
# Later, in inetsvc, we create a name "unknown" and create a entry
# in the local /etc/inet/hosts file pairing "unknown" with the IP
# address assigned by DHCP. The use of bootparams as a fallback
# for all non-DHCP cases provides compatibility with the
# behavior of the system before netstrategy was introduced.
#
case "$_INIT_NET_STRATEGY" in
	"dhcp") hostname=`/sbin/dhcpinfo Hostname` ;;
	"rarp") hostname=`/sbin/hostconfig -h -p bootparams`
		trap 'intr=1' 2 3
		while [ -z "$hostname" -a ! -f /etc/.UNCONFIGURED -a \
		    -z "$intr" ]; do
			echo "re-trying host configuration..."
			# Restrict this to IPv4 interfaces.
			/sbin/ifconfig -adD4 auto-revarp up
			hostname=`/sbin/hostconfig -h -p bootparams`
		done
		trap 2 3 ;;
	"none") hostname="`shcat /etc/nodename 2>/dev/null`"
			if [ -z "$hostname" ]; then
				hostname=`/sbin/hostconfig -h -p bootparams`
			fi ;;
esac

#
# If the netstrategy was unsuccessful and we haven't got a locally configured
# name, default to "unknown"
#
if [ -z "$hostname" ]; then
	hostname="`shcat /etc/nodename 2>/dev/null`"
	if [ -z "$hostname" ]; then
		hostname="unknown"
	fi
fi

/sbin/uname -S $hostname

#
# Process IPv4 and IPv6 interfaces that failed to plumb.
# Find an alternative interface to host the addresses.
#
[ -n "$inet_failed" ] && move_addresses inet

[ -n "$inet6_failed" ] && move_addresses inet6

echo "Hostname: `/sbin/uname -n`"

# Reset the library path now that we are past the critical stage
LD_LIBRARY_PATH=; export LD_LIBRARY_PATH
# Restore standard ifconfig in.mpathd behavior.
unset SUNW_NO_MPATHD
