#!/sbin/sh
#
# Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
# Use is subject to license terms.
#
# ident	"@(#)nddconfig	2.23	05/07/22 SMI"
#
# INTRODUCTION
#
#  This script sets network driver parameters to prevent some network
#  attacks.  Install this script to make changes at system boot.  For
#  further information on the parameters set in this script, see
#  the Sun Blueprints(tm) OnLine article entitled "Solaris Operating
#  Environment Network Settings for Security - Updated for the Solaris
#  9 Operating Environment."
#
#	http://www.sun.com/blueprints/0603/816-5240.pdf
#
#  The latest version of this script is available from the Blueprints
#  OnLine tools area at:
#
#	http://www.sun.com/blueprints/tools/
#
#  This script is written for the Solaris 2.5.1, 2.6, 7, 8, 9, and 10
#  Operating System releases.
#
# WARNING
#
#  This script makes changes to the system default network driver
#  parameters.  The settings included in this script are considered safe
#  in terms of security.  However, some settings may not work in your
#  environment.  The comments provided for each parameter explain the
#  effect the setting has.
#
# INSTALLATION
#
#	# cp <script> /etc/init.d/nddconfig
#	# chmod 744 /etc/init.d/nddconfig
#	# chown root:sys /etc/init.d/nddconfig
#	# ln /etc/init.d/nddconfig /etc/rc2.d/S70nddconfig
#
# WARNING MESSAGES
#
#  When adding specific privileged ports ({tcp|udp}_extra_priv_ports_add),
#  if a specific port number has already been applied, the following
#  warning message is displayed:
#
#	operation failed, File exists
#
#  This is a very poor ndd warning message.  It can be safely ignored.
#

PATH=/usr/bin:/usr/sbin

#
# A note about parameter values:
#     '0' == false/off/disable
#     '1' == true/on/enable
#

#
# verbose
#
#  This option enables verbose output generated by this script.
#

if [ -z "${NDDCONFIG_VERBOSE}" ]; then
   verbose="0"
else
   verbose="${NDDCONFIG_VERBOSE}"
fi

#
# arp_cleanup_interval
#
#  This option determines the period of time the Address Resolution
#  Protocol (ARP) cache maintains entries. ARP attacks may be effective
#  with the default interval. Shortening the timeout interval should
#  reduce the effectiveness of such an attack.
#  The default value is 300000 milliseconds (5 minutes).
#
arp_cleanup_interval=L:60000

#
# ip_forward_directed_broadcasts
#
#  This option determines whether to forward broadcast packets directed
#  to a specific net or subnet, if that net or subnet is directly
#  connected to the machine. If the system is acting as a router, this
#  option can be exploited to generate a great deal of broadcast network
#  traffic. Turning this option off will help prevent broadcast traffic
#  attacks.
#  The default value is 1 (true).
#
ip_forward_directed_broadcasts=E:0

#
# ip_forward_src_routed
# ip6_forward_src_routed (Solaris 8 OS and above)
#
#  This option determines whether to forward packets that are source
#  routed. These packets define the path the packet should take instead
#  of allowing network routers to define the path.
#  The default value is 1 (true).
#
ip_forward_src_routed=E:0
ip6_forward_src_routed=E:0

#
# ip_ignore_redirect
# ip6_ignore_redirect (Solaris 8 OS and above)
#
#  This option determines whether to ignore Internet Control Message
#  Protocol (ICMP) packets that define new routes. If the system is
#  acting as a router, an attacker may send redirect messages to alter
#  routing tables as part of a sophisticated attack (man-in-the-middle
#  attack) or a simple denial of service.
#  The default value is 0 (false).
#
ip_ignore_redirect=E:1
ip6_ignore_redirect=E:1

#
# ip_ire_flush_interval (Solaris 2.5.1, 2.6, and 7 OS releases)
# ip_ire_arp_interval   (Solaris 8 OS and above)
#
#  This option determines the period of time at which a specific route
#  will be kept, even if currently in use. ARP attacks may be effective
#  with the default interval. Shortening the time interval may reduce
#  the effectiveness of attacks.
#  The default interval is 1200000 milliseconds (20 minutes).
#
ip_ire_flush_interval=L:60000
ip_ire_arp_interval=L:60000

#
# ip_respond_to_address_mask_broadcast
#
#  This option determines whether to respond to ICMP netmask requests,
#  which are typically sent by diskless clients when booting. An
#  attacker may use the netmask information for determining network
#  topology or the broadcast address for the subnet.
#  The default value is 0 (false).
#
ip_respond_to_address_mask_broadcast=E:0

#
# ip_respond_to_echo_broadcast
# ip_respond_to_echo_multicast  (Solaris 9 OS)
# ip6_respond_to_echo_multicast (Solaris 8 OS and above)
#
#  This option determines whether to respond to ICMP broadcast (or
#  multicast) echo requests (ping). An attacker may try to create a
#  denial-of-service attack on subnets by sending many broadcast (or
#  multicast) echo requests to which all systems will respond. This
#  also provides information on systems that are available on the
#  network. The default value is 1 (true).
#
ip_respond_to_echo_broadcast=E:0
ip_respond_to_echo_multicast=E:0
ip6_respond_to_echo_multicast=E:0

#
# ip_respond_to_timestamp
#
#  This option determines whether to respond to ICMP timestamp requests,
#  which some systems use to discover the time on a remote system. An
#  attacker may use the time information to schedule an attack at a
#  period of time when the system may run a cron job (or other time-
#  based event) or otherwise be busy. It may also be possible to predict
#  ID or sequence numbers that are based on the time of day for spoofing
#  services.
#  The default value is 1 (true).
#
ip_respond_to_timestamp=E:0

#
# ip_respond_to_timestamp_broadcast
#
#  This option determines whether to respond to ICMP broadcast timestamp
#  requests that are used to discover the time on all systems in the
#  broadcast range. This option is dangerous for the same reasons as
#  responding to a single timestamp request. Additionally, an attacker
#  may try to create a denial-of-service attack by generating many
#  broadcast timestamp requests.
#  The default value is 1 (true).
#
ip_respond_to_timestamp_broadcast=E:0

#
# ip_send_redirects
# ip6_send_redirects (Solaris 8 OS and above)
#
#  This option determines whether to send ICMP redirect messages, which
#  can introduce changes into a remote system's routing table. It should
#  only be used on systems that act as routers.
#  The default value is 1 (true).
#
ip_send_redirects=E:0
ip6_send_redirects=E:0

#
# ip_strict_dst_multihoming
# ip6_strict_dst_multihoming (Solaris 8 OS and above)
#
#  This option determines whether to enable strict destination
#  multihoming. If this is set to 1 and ip_forwarding is set to 0, then
#  a packet sent to an interface from which it did not arrive will be
#  dropped. This setting prevents an attacker from passing packets across
#  a machine with multiple interfaces that is not acting as a router.
#  The default value is 0 (false).
#
#  NOTE: Strict destination multihoming may prevent SunCluster 2.x
#  systems from operating as intended. This script will NOT enable
#  strict destination multihoming if SunCluster 2.x software is installed.
#
ip_strict_dst_multihoming=E:1
ip6_strict_dst_multihoming=E:1

#
# ip_def_ttl
#
#  This option sets the default time to live (TTL) value for IP packets.
#  Normally, this should not be altered from the default value.
#  Changing it to a different value may fool some OS "fingerprinting"
#  tools such as queso or nmap.
#  The default value is 255.
#
ip_def_ttl=E:255

#
# tcp_conn_req_max_q0
#
#  This option sets the size of the queue containing unestablished
#  connections. This queue is part of a protection mechanism against
#  SYN flood attacks. The queue size default is adequate for most
#  systems but should be increased for busy servers.
#  The default value is 1024.
#
tcp_conn_req_max_q0=G:4096

#
# tcp_conn_req_max_q
#
#  This option sets the maximum number of fully established connections.
#  Increasing the size of this queue provides some limited protection
#  against resource consumption attacks. The queue size default is
#  adequate for most systems but should be increased for busy servers.
#  The default value is 128.
#
tcp_conn_req_max_q=G:1024

#
# tcp_rev_src_routes (Solaris 8 OS and above)
#
#  This option determines whether the specified route in a source
#  routed packet will be used in returned packets. TCP source routed
#  packets may be used in spoofing attacks, so the reverse route should
#  not be used.
#  The default value is 0 (false).
#
tcp_rev_src_routes=E:0

#
# Adding specific privileged ports (Solaris 2.6 OS and above)
#
#  These options define additional TCP and UDP privileged ports outside
#  of the 1-1023 range. Any program that attempts to bind the ports
#  listed here must run as root. This prevents normal users from
#  starting server processes on specific ports. Multiple ports can be
#  specified by quoting and separating them with spaces.
#
#  Defaults values:
#	tcp_extra_priv_ports: 2049 (nfsd) 4045 (lockd)
#	udp_extra_priv_ports: 2049 (nfsd) 4045 (lockd)
#
tcp_extra_priv_ports_add=S:"6112"
udp_extra_priv_ports_add=S:""

#
# Ephemeral port range adjustment
#
#  These options define the upper and lower bounds on ephemeral ports.
#  Ephemeral (means short-lived) ports are used when establishing
#  outbound network connections.
#
#  Defaults values:
#	tcp_smallest_anon_port=32768
#	tcp_largest_anon_port=65535
#	udp_smallest_anon_port=32768
#	udp_largest_anon_port=65535
#
tcp_smallest_anon_port=G:32768
tcp_largest_anon_port=L:65535
udp_smallest_anon_port=G:32768
udp_largest_anon_port=L:65535

#
# Nonprivileged port range adjustment
#
#  These options define the start of nonprivileged TCP and UDP ports.
#  The nonprivileged port range normally starts at 1024. Any program
#  that attempts to bind a nonprivileged port does not have to run as
#  root.
#
#  Defaults values:
#	tcp_smallest_nonpriv_port=1024
#	udp_smallest_nonpriv_port=1024
#
tcp_smallest_nonpriv_port=G:1024
udp_smallest_nonpriv_port=G:1024


#		+-----------------------------------------+
#		| No modification needed below this line. |
#		+-----------------------------------------+


#
# base parameters (the same across Solaris release 2.5.1 and above)
#
base_parameters="arp_cleanup_interval \
		 ip_forward_directed_broadcasts \
		 ip_forward_src_routed \
		 ip_ignore_redirect \
		 ip_respond_to_address_mask_broadcast \
		 ip_respond_to_echo_broadcast \
		 ip_respond_to_timestamp \
		 ip_respond_to_timestamp_broadcast \
		 ip_send_redirects \
		 ip_strict_dst_multihoming \
		 ip_def_ttl \
		 tcp_conn_req_max_q0 \
		 tcp_conn_req_max_q \
		 tcp_smallest_anon_port \
		 tcp_largest_anon_port \
		 udp_smallest_anon_port \
		 udp_largest_anon_port \
		 tcp_smallest_nonpriv_port \
		 udp_smallest_nonpriv_port"

#
# OS_revision specific parameters
#

# Solaris 2.5.1 specific parameters
SunOS5_5_1="ip_ire_flush_interval"

# Solaris 2.6 specific parameters
SunOS5_6="ip_ire_flush_interval \
	  tcp_extra_priv_ports_add \
	  udp_extra_priv_ports_add"

# Solaris 7 specific parameters
SunOS5_7="ip_ire_flush_interval \
	  tcp_extra_priv_ports_add \
	  udp_extra_priv_ports_add"

# Solaris 8 specific parameters
SunOS5_8="ip_ire_arp_interval \
	  tcp_extra_priv_ports_add \
	  udp_extra_priv_ports_add \
	  tcp_rev_src_routes"

# Solaris 9 specific parameters
SunOS5_9="ip_ire_arp_interval \
          ip_respond_to_echo_multicast \
	  tcp_extra_priv_ports_add \
	  udp_extra_priv_ports_add \
	  tcp_rev_src_routes"

# Solaris 10 specific parameters
SunOS5_10="ip_ire_arp_interval \
           ip_respond_to_echo_multicast \
	   tcp_extra_priv_ports_add \
	   udp_extra_priv_ports_add \
	   tcp_rev_src_routes"

#
# IPv6 parameters (apply to Solaris 8, 9, and 10 (alpha))
#
ip6_parameters="ip6_forward_src_routed \
		ip6_respond_to_echo_multicast \
		ip6_send_redirects \
		ip6_ignore_redirect \
		ip6_strict_dst_multihoming"

#
# system privilege ports defaults
#
extra_priv_ports_defaults="2049 4045 "

#
# check for the presence of SunCluster 2.x software
#  (disables strict destination multihoming if SunCluster 2.x is installed)
#
if [ -f /opt/SUNWcluster/bin/scconf ]; then
    [ "$verbose" = "1" ] && \
	echo "SunCluster 2.2 detected; disabling IP strict destination multihoming."
    ip_strict_dst_multihoming=0
    ip6_strict_dst_multihoming=0
fi

# check for the presence of SunCluster 3.1u1 software or later
#  (disables strict destination multihoming if SC 3.1u1 is installed
# This check is for SC31u1 for 32-bit and 64-bit mode
# We also need to check for an amd64 driver

if [ -f /kernel/drv/clprivnet -o -f /kernel/drv/sparcv9/clprivnet \
     -o -f /kernel/drv/amd64/clprivnet ]; then
    [ "$verbose" = "1" ] && \
        echo "SunCluster 3.1u1 or later detected; disabling IP strict destination multihoming."
    ip_strict_dst_multihoming=0
    ip6_strict_dst_multihoming=0
fi

#
# get OS name and revision information
#
os=`uname -s`
revision=`uname -r`
OSRev=$os`echo $revision | sed -e 's/\./_/g'`

#
# check if IPv6 is enabled
#
# ip6_interfaces="`echo /etc/hostname6.*[0-9] 2> /dev/null`"
# [ "$ip6_interfaces" != "/etc/hostname6.*[0-9]" ] && ip6_enabled=true
#
# Force IPv6 checks to always be applied. These do not generate an error if
# there are no IPv6 interfaces defined, and the settings will persist if one
# is created at a later point in time.
#

if [ "$revision" = "5.5.1" -o "$revision" = "5.6" -o "$revision" = "5.7" ]; then
   ip6_enabled=false
else
   ip6_enabled=true
fi

#
# check if running from a global zone (zones-enabled systems only)
#

if [ "$revision" = "5.10" ]; then
   if [ -x /usr/bin/zonename ]; then
      zoneName="`/usr/bin/zonename`"
   else
      zoneName="global"
   fi

   if [ "${zoneName}" != "global" ]; then
      [ "$verbose" = "1" ] && \
         echo "This script can only be used from within the 'global' zone."
         exit 1
   fi
fi

#
# do_in_order -- This function executes the specified functions with
#   the appropriate parameters for the local OS, revision, and
#   configuration. Currently it acts on a specific base set of
#   parameters, OS and revision specific parameters, and IPv6
#   parameters.
#
do_in_order() { # function_name

    function_name=$1

    eval OSRev_params=\$$OSRev

    # develop a complete list of parameters (sorted by name)

    param_list="$base_parameters $OSRev_params"
    if [ "$ip6_enabled" = "true" ]; then
       param_list="$param_list $ip6_parameters"
    fi
    sorted_list="`echo $param_list | xargs -n 1 echo | sort -d`"

    # handle the parameters
    for param in $sorted_list; do
	$function_name $param
    done
}

#
# set_parameter -- This function uses ndd to set a parameter.
#   The supplied parameter name has a shell variable with the same
#   name which contains the value for the parameter.
#
set_parameter() { # parameter

    # definition for local variable
    param=$1

    # determine the driver from the first substring in the parameter name
    driver=/dev/`echo $param | sed -e 's/_.*//'`

    eval tmpValues=\$$param

    # strip off any comparison tags (such as "E", "G", or "L"
    values=`echo "${tmpValues}" | cut -d: -f2-`

    # First check that a value for the parameter exists. If not, skip it.

    ndd $driver $param 2>&1 | egrep -s "name is non-existent for this module" 2>/dev/null
    if [ $? != 0 ]; then
       if [ -n "$values" ]; then
	
          # Some parameters may have multiple values specified in one
          #  assignment further up in the script. ndd only accepts one
          #  parameter at a time.  Loop through and set each value.
          for value in $values; do
             [ "$verbose" = "1" ] && echo "Setting $param to $value"
             ndd -set $driver $param $value
          done
       fi
    fi
}

#
# display_parameter -- This function uses ndd to extract the value of
#   a parameter and display it.
#
display_parameter() { # parameter

    # definition for local variable
    param=$1

    # hack for the "write only" extra privileged ports parameters
    param=`echo $param | sed -e 's/_add$//'`

    # determine the driver from the first substring in the parameter name
    driver=/dev/`echo $param | sed -e 's/_.*//'`

    # execute the ndd command to retrieve settings and remove newlines

    ndd $driver $param 2>&1 | egrep -s "name is non-existent for this module" 2>/dev/null
    if [ $? != 0 ]; then
       value=`ndd $driver $param | tr -d '\n'`
    else
       # parameter does not exist, so cannot compare.
       return
    fi

    # print parameter value
    # echo "   $driver $param = '$value'"
    echo "   $param = '$value'"
}

#
# compare_parameter -- This function uses ndd to extract the value of
#   a parameter. It compares the current parameter value to the one
#   defined in this script.
#
compare_parameter() { # parameter

    # definition for local variable
    originalParam="$1"

    # hack for the "write only" extra privileged ports parameters
    modifiedParam=`echo $originalParam | sed -e 's/_add$//'`

    # determine the driver from the first substring in the parameter name
    driver=/dev/`echo $modifiedParam | sed -e 's/_.*//'`

    # execute the ndd command to retrieve settings and remove newlines
    ndd $driver $param 2>&1 | egrep -s "name is non-existent for this module" 2>/dev/null
    if [ $? != 0 ]; then
       currentValue=`ndd $driver $modifiedParam | tr -d '\n'`
    else
       # parameter does not exist, so cannot compare.
       return
    fi

    eval tmpIntendedValue="\$$originalParam"

    # separate the actual value from the comparison tag.  note that the
    # intendedRange parameter is only used when intendedOperator="R".

    intendedOperator=`echo "${tmpIntendedValue}" | awk -F: '{ print $1 }'`
    intendedRange=`echo "${tmpIntendedValue}"    | awk -F: '{ print $2 }'`
    intendedValue=`echo "${tmpIntendedValue}"    | awk -F: '{ print $NF }'`

    # if the modified parameter name is different from the original
    #  parameter, then we are dealing with the privileged port parameters
    if [ "$modifiedParam" != "$originalParam" ]; then

	# the privileged port parameters have system defaults that must
	#  be accounted for in the comparison
	if [ -n "$intendedValue" ]; then
	    intendedValue="$extra_priv_ports_defaults$intendedValue "
	else
	    intendedValue="$extra_priv_ports_defaults"
	fi
        intendedOperator="S"
    fi

    # print parameter value and note all deviations
    # echo "   $driver $modifiedParam = '$currentValue'\c"
    echo "   $modifiedParam = '$currentValue'\c"

    # perform the strict comparison
    strictOK=0
    [ "${currentValue}" = "${intendedValue}" ] && strictOK=1

    # perform the loose comparison. In this case, if the values are within
    # the allowed range (as specified by the "G", "L", or "S" tags), then
    # consider the match a success.

    valOK=0
    case "${intendedOperator}" in
       E) [ "${currentValue}" -eq "${intendedValue}" ] && valOK=1 ;;
       G) [ "${currentValue}" -ge "${intendedValue}" ] && valOK=1 ;;
       L) [ "${currentValue}" -le "${intendedValue}" ] && valOK=1 ;;
       R)
          lowEnd=`echo "${intendedRange}" | awk -F- '{ print $1 }'`
          highEnd=`echo "${intendedRange}" | awk -F- '{ print $2 }'`
          [ "${currentValue}" -ge "${lowEnd}" -a \
             "${currentValue}" -le "${highEnd}" ] && valOK=1
          ;;
       S)
          valOK=1
          for iEntry in ${intendedValue}; do
             found=0
             for cEntry in ${currentValue}; do
                if [ "${cEntry}" = "${iEntry}" ]; then
                   found=1
                   break
                fi
             done
             if [ ${found} = 0 ]; then
                valOK=0
                break
             fi
          done
          ;;
    esac

    if [ "${valOK}" = 0 ]; then
	echo " (fail) [should be '$intendedValue']"
    else
        if [ "${strictOK}" = "1" ]; then
	   echo " (pass) [exact match]"
        else
	   echo " (pass) [loose match]"
        fi
    fi
}

# Process the command argument
case "$1" in

    'start')

	# set the parameters in the defined order
	do_in_order set_parameter
        [ "$verbose" = "0" ] && \
           echo "Sun BluePrints network security settings applied."
	;;

    'show')

	echo "Current ndd parameter settings:"
	do_in_order display_parameter
	;;

    'compare')

	echo "Comparison of ndd parameter settings:"
	do_in_order compare_parameter
	;;

    'stop')
	# ignored
	[ "$verbose" = "1" ] && \
	    echo "$0: 'stop' ignored.  No network changes applied."
	;;

    *)
	echo "Usage: $0 { start | stop | show | compare }"
	exit 1
	;;
esac

exit 0
