+# remove all entries from the ipfw config, and create an empty db
+clean_db() {
+ rm -f ${DBFILE}
+ touch ${DBFILE}
+ # we would like to delete ranges of rules and pipes but this
+ # is not supported so for the time being we kill them all
+ ${IPFW} -q flush
+ ${IPFW} -q pipe flush
+ ${IPFW} -q table $SLICE_TABLE flush
+ #${IPFW} delete ${IPFW_RULE_MIN}-${IPFW_RULE_MAX}
+ #${IPFW} pipe delete ${IPFW_PIPE_MIN}-${IPFW_PIPE_MAX}
+ # since all rules are now deleted, we should initialize the firewall
+ ipfw_init
+}
+
+#
+# Add the ipfw rule/pipe and update the database.
+# The pipe-in and pipe-out config are through global variables
+# rule_in rule_out because they may be long. XXX why ?
+# Other arguments are on the command line
+#
+# the new_rule variable is set if the rule to be installed is new
+# we need to know this because we do not want to clean
+# rule counters on pipes reconfiguration
+add_rule() { # slice_id new_rule type arg ipfw_rule pipe_index timeout
+ local slice_id=$1 new_rule=$2 type=$3 arg=$4
+ local ipfw_rule=$5 pipe_index=$6 timeout=$7
+ local ipfw_pipe_in ipfw_pipe_out check_timeout
+ local p h # used to split the argument
+
+ local h_in h_out
+ # local rule_in rule_out # XXX test if this works
+ # find actual pipe numbers
+ ipfw_pipe_in=$(($IPFW_PIPE_MIN + $((2 * $(($pipe_index - 1)))) ))
+ ipfw_pipe_out=$(($ipfw_pipe_in + 1))
+ local del # used to delete incompatible configurations
+
+ # split the argument, and prepare PORTLIST (p) and ADDRLIST (h)
+ p=`echo $arg | cut -s -d "@" -f1-` # empty if no separator
+ if [ "$p" = "" ] ; then
+ p=$arg
+ else
+ p=`echo $arg | cut -d "@" -f1`
+ h=`echo $arg | cut -d "@" -f2`
+ fi
+
+ if [ "$h" = "" ] ; then
+ h_in=""
+ h_out=""
+ else
+ h_in=" src-ip ${h} "
+ h_out=" dst-ip ${h} "
+ fi
+
+ # first, call ipfw -n to check syntax, if ok move on and do the action
+ if [ x"$new_rule" != x"0" ] ; then
+ case $type in
+ SERVER|server)
+ rule_in="dst-port $p"
+ rule_out="src-port $p"
+ del=service
+ ;;
+ CLIENT|client)
+ rule_in="src-port $p"
+ rule_out="dst-port $p"
+ del=service
+ ;;
+ SERVICE|service)
+ rule_in="{ src-port $p or dst-port $p }"
+ rule_out="{ src-port $p or dst-port $p }"
+ del="cli_ser"
+ ;;
+ *)
+ abort "invalid service type $type"
+ ;;
+ esac
+
+ rule_in="pipe ${ipfw_pipe_in} in ${h_in} ${rule_in} // $type $arg $slice_id"
+ rule_out="pipe ${ipfw_pipe_out} out ${h_out} ${rule_out} // $type $arg $slice_id"
+
+ # Move into the user root directory. The profile should be located there
+ ( cd /vservers/${SLICENAME}/`pwd`/ ; ${IPFW_CHECK} add ${ipfw_rule} ${rule_in} ) > /dev/null || \
+ abort "ipfw syntax error ${rule_in}"
+ ( cd /vservers/${SLICENAME}/`pwd`/ ; ${IPFW_CHECK} add ${ipfw_rule} ${rule_out} ) > /dev/null || \
+ abort "ipfw syntax error ${rule_out}"
+ fi
+
+ # check error reporting
+ ( cd /vservers/${SLICENAME}/`pwd`/ ; ${IPFW_CHECK} pipe ${ipfw_pipe_in} config ${CONFIG_PIPE_IN} ) > /dev/null || \
+ abort "ipfw syntax error pipe_in"
+ ( cd /vservers/${SLICENAME}/`pwd`/ ; ${IPFW_CHECK} pipe ${ipfw_pipe_out} config ${CONFIG_PIPE_OUT} ) > /dev/null || \
+ abort "ipfw syntax error pipe_out"
+
+ # all good, delete and add rules if necessary
+ [ "$del" = "service" ] && do_delete 0 $slice_id service $arg
+ [ "$del" = "cli_ser" ] && do_delete 0 $slice_id client $arg
+ [ "$del" = "cli_ser" ] && do_delete 0 $slice_id server $arg
+ [ "$new_rule" != "0" ] && ${IPFW} add ${ipfw_rule} $rule_in > /dev/null
+ [ "$new_rule" != "0" ] && ${IPFW} add ${ipfw_rule} $rule_out > /dev/null
+ # config pipes
+ ( cd /vservers/${SLICENAME}/`pwd`/ ; ${IPFW} pipe ${ipfw_pipe_in} config ${CONFIG_PIPE_IN} )
+ ( cd /vservers/${SLICENAME}/`pwd`/ ; ${IPFW} pipe ${ipfw_pipe_out} config ${CONFIG_PIPE_OUT} )
+
+ # send output to the user
+ ${IPFW} show ${ipfw_rule}
+ ${IPFW} pipe ${ipfw_pipe_in} show
+ ${IPFW} pipe ${ipfw_pipe_out} show
+
+ # do not write on the database on test-only
+ [ "$TEST" = "1" ] && return
+ # add to the database
+ ( grep -iv -- "^${slice_id} ${type} ${arg} " $DBFILE; \
+ echo "${slice_id} ${type} ${arg} ${ipfw_rule} ${pipe_index} ${timeout}" ) > ${DBFILE}.tmp
+ mv ${DBFILE}.tmp ${DBFILE}
+}
+
+#
+# Delete a given configuration
+# if block_deletion !0 free block resources (if necessary)
+# otherwise leave the block allocated in case
+# we are adding the first rule
+do_delete() { # block_deletion slice_id type arg
+ local ipfw_pipe_in ipfw_pipe_out pipe_index ipfw_rule
+ local block_deletion=$1 slice_id=$2 type=$3 arg=$4
+
+ [ "${type}" = "BLOCK" ] && abort "A BLOCK can not be deleted"
+ [ "${arg}" = "" ] && abort "Missing args on 'delete', expected on of {CLIENT|SERVER|SERVICE} arg"
+ set `find_rule $slice_id $type $arg`
+ ipfw_rule=$1; pipe_index=$2
+ [ "$ipfw_rule" = "0" ] && return # no rules found
+
+ # find actual pipe numbers XXX do as function
+ ipfw_pipe_in=$(($IPFW_PIPE_MIN + $((2 * $(($pipe_index - 1)))) ))
+ ipfw_pipe_out=$(($ipfw_pipe_in + 1))
+
+ echo "removing configuration ${slice_id} ${type} ${arg}"
+ [ "$TEST" = "1" ] && return 0
+ $IPFW delete ${ipfw_rule}
+ $IPFW pipe delete ${ipfw_pipe_in}
+ $IPFW pipe delete ${ipfw_pipe_out}
+ # remove from the database (case insensitive)
+ grep -iv -- "^${slice_id} ${type} ${arg} " $DBFILE > ${DBFILE}.tmp
+ mv ${DBFILE}.tmp ${DBFILE}
+
+ # if there are no more rules for the user
+ # remove the table entry from ipfw and from the db
+ [ $block_deletion = 0 ] && return 0
+
+ local rule_counter=`grep ^${slice_id} ${DBFILE} | wc -l`
+ [ $rule_counter -gt 1 ] && return 0 # there are still user rules
+ # delete the block and clean the table
+ local block_n=`grep "^${slice_id} BLOCK" ${DBFILE} | cut -d " " -f 3`
+ debug "Deleting BLOCK <${block_n}> entry from ipfw and from the database"
+ table_remove $slice_id $block_n
+}
+
+# compare the argument with the first two field of
+# the database.
+# On match returns the block number, otherwise returns 0.
+# no echo inside
+find_block() { # $1 slice_id
+ local ret
+ ret=`grep -- "^$1 BLOCK " $DBFILE`
+
+ [ x"$ret" = x ] && echo "0" && return # nothing found
+ # ignore multiple matches. If the db is corrupt we are
+ # screwed anyways
+ set $ret
+ echo "$3"
+}
+
+#
+# remove the default user rule and
+# the a BLOCK entry from ipfw and update the db
+# no echo inside
+table_remove() { # $slice_id $block_n
+ [ "$TEST" = "1" ] && return 0
+
+ # compute and delete the last user rule
+ local ipfw_rulemax=$(($IPFW_RULE_MIN + $(($M *${block_n})) -1))
+ ${IPFW} table $SLICE_TABLE delete $slice_id
+ ${IPFW} delete ${ipfw_rulemax}
+ ( grep -iv -- "^${slice_id} BLOCK ${block_n}" $DBFILE; ) > ${DBFILE}.tmp
+ mv ${DBFILE}.tmp ${DBFILE}
+ return 0
+}
+
+#
+# Find a rule and pipe_index for the given key (xid type arg)
+# Allocate a new block if first entry for this xid.
+# Rule and pipe are not written into the database, only the block is.
+#
+# Return ipfw_rule pipe_index new_rule
+# 'new_rule' is 0 if the rule existed, 1 if it is new
+#
+# return ipfw_rule = 0 if there are no resources available
+find_allocate() { # slice_id type arg
+ local slice_id=$1 type=$2 arg=$3
+ local ipfw_rule pipe_index new_block=0
+
+ # search for already allocated rule and pipes
+ set `find_rule $slice_id $type $arg`
+ ipfw_rule=$1; pipe_index=$2
+ [ ! ${ipfw_rule} = 0 ] && echo $ipfw_rule $pipe_index "0" && return 0 # rules found, return
+
+ # no rules found, search for an already existing block, or
+ # allocate a new one
+ local block_n=`find_block ${slice_id}`
+ [ ${block_n} = "0" ] && new_block=1 && block_n=`find_free_block`
+ [ ${block_n} = "0" -o ${block_n} -gt $BLOCK_MAX ] && echo 0 && return 0;
+
+ # We have a valid block, compute the range for user rules
+ local ipfw_rulemin=$(($IPFW_RULE_MIN + $(($M *$(($block_n - 1))))))
+ local ipfw_rulemax=$(($(($ipfw_rulemin + $M)) - 1 ))
+
+ # Find rule and pipes, reserve the last rule for the user's
+ # default rule that catches regular traffic.
+ set `allocate_resources $ipfw_rulemin $(($ipfw_rulemax - 1))`
+ ipfw_rule=$1; pipe_index=$2
+ [ $ipfw_rule = 0 ] && echo 0 && return 0 # no resources
+
+ # If this is a new block, add the slice to the lookup table
+ # and put a default rule at the end of the block.
+ if [ "$TEST" = "0" -a $new_block = 1 ] ; then
+ ${IPFW} table $SLICE_TABLE add ${slice_id} ${ipfw_rulemin} > /dev/null
+ ${IPFW} add ${ipfw_rulemax} allow all from any to any > /dev/null
+ ( echo "${slice_id} BLOCK ${block_n}" ) >> ${DBFILE}
+ fi
+
+ echo $ipfw_rule $pipe_index "1"
+ return 0
+}
+
+#
+# called with the database file as input
+# compare the tuple <slice_id type arg> with
+# the current firewall configuration. The database contains
+# slice_id type arg ipfw_rule pipe_index timeout
+# On match returns <ipfw_rule pipe_index timeout>
+# On non match returns 0 0 0
+# no echo inside
+find_rule() { # slice_id type arg
+ local ret
+ ret=`grep -i -- "^$1 $2 $3 " $DBFILE | grep -v BLOCK`
+
+ [ x"$ret" = x ] && echo "0 0 0 " && return # nothing found
+ # ignore multiple matches. If the db is corrupt we are
+ # screwed anyways
+ set $ret
+ echo "$4 $5 $6"
+}
+
+#
+# Find a hole in a list of numbers within a range (boundaries included)
+# The input is passed as a sorted list of numbers on stdin.
+# Return a "0" rule if there is no rule free
+find_hole() { # min max
+ local min=$1 cand=$1 max=$2 line
+ while read line ; do
+ [ $line -lt $min ] && continue
+ [ $line -ne $cand ] && break # found
+ [ $cand -ge $max ] && cand=0 && break # no space
+ cand=$(($cand + 1))
+ done
+ echo $cand
+}
+
+# XXX despite the name this does not allocate but only finds holes.
+# returns a free rule and pipe base for client|server|service
+# within a block
+# Returns r=0 if there are no resources available
+# no echo inside
+allocate_resources() { # ipfw_minrule ipfw_maxrule
+ local p r
+ # remove comments, extract field, sort
+ p=`grep -v '^#' $DBFILE | grep -v BLOCK | awk '{print $5}' | sort -n | \
+ find_hole $PIPE_MIN $PIPE_MAX`
+ r=`grep -v '^#' $DBFILE | grep -v BLOCK | awk '{print $4}' | sort -n | \
+ find_hole $1 $2`
+ [ $r = 0 -o $p = 0 ] && r=0 # no resources available
+ echo $r $p
+}
+
+
+# Returns the index of a free block
+# Returns 0 if there are no resources available
+# no debug inside
+find_free_block() {
+ b=`grep -v '^#' $DBFILE | grep BLOCK | awk '{print $3}' | sort -n | \
+ find_hole $BLOCK_MIN $BLOCK_MAX`
+ echo $b
+}
+
+# parse the ipfw database and remove expired rules
+#
+# Each timeout value stored in the database is compared against
+# the current time. If the timeout is older than current,
+# the rules and related pipes will be deleted.
+kill_expired() { # slice_id type arg
+ local match timeout
+
+ # if there is no database file exit
+ [ ! -f ${DBFILE} ] && return 0
+
+ # Get the current time
+ now=`date -u +%s`
+
+ cp ${DBFILE} ${DBFILE}.kill
+ cat ${DBFILE}.kill | grep -v BLOCK |
+ while read line; do
+ match=`echo $line|cut -d " " -f 1-3`
+ timeout=`echo $line|cut -d " " -f 6`
+ [ $now -gt $timeout ] && do_delete 1 $match
+ done
+ rm ${DBFILE}.kill
+}
+
+# execute functions from root context
+# can be used from root context as follow:
+# echo "super $command $args" | /vsys/ipfw-be 0
+do_super() { # $arguments...
+ case $1 in
+ init)
+ ipfw_init; return 0
+ ;;
+ dbcleanup)
+ clean_db; return 0
+ ;;
+ killexpired)
+ kill_expired; return 0
+ ;;
+ *)
+ abort "Invalid super command"
+ ;;
+ esac
+}
+
+# refresh the rule timeout
+do_refresh() { # slice_id type arg timeout
+ local ipfw_pipe_in ipfw_pipe_out pipe_index
+ local slice_id=$1 type=$2 arg=$3 timeout=$4
+
+ debug "do_refresh type: <$type> arg: <$arg> timeout: <$timeout>"
+ [ "${type}" = "BLOCK" ] && abort "BLOCK rule not valid"
+ [ "${timeout}" = "" ] && abort "Missing args on 'refresh', expected on of {SERVICE|SERVER|CLIENT} port_number"
+ set `find_rule $slice_id $type $arg`
+ ipfw_rule=$1; pipe_index=$2
+ [ "${ipfw_rule}" = "0" ] && debug "no rules found" && return 0 # no rules found
+
+ [ "$TEST" = "1" ] && return
+ # update the database with the new timeout value
+ ( grep -iv -- "^${slice_id} ${type} ${arg} " $DBFILE; \
+ echo "${slice_id} ${type} ${arg} ${ipfw_rule} ${pipe_index} ${timeout}" ) > ${DBFILE}.tmp
+ mv ${DBFILE}.tmp ${DBFILE}
+ echo "refreshed timeout for rule ${type} ${arg}"
+}
+
+# process a request.
+# A request is made by a set of arguments formatted as follow:
+#
+# config {server|client|service} arg [-t timeout] IN <pipe_conf> OUT <pipe_conf>
+# show {rules|pipes} [args]
+# delete type arg
+# refresh type arg [-t timeout]
+#
+# The timeout value is expressed as:
+# week, day, month or anything else accepted by the date command.
+# The id of the slice issuing the request is in the $SLICE_ID variable,
+# set at the beginning of this script.
+process() {
+ local new_pipe=0
+ local timeout TMP i rule_base pipe_base
+ local cmd=$1 ; shift
+ local debug_args="$*";
+ local type=$1 ; shift
+ local args="$*"
+ debug "Received command: <$cmd> arguments: <$debug_args>"
+
+ # set the timeout value
+ # if present, extract the '-t timeout' substring from the command line
+ timeout=`echo ${args} | ${SED} ${SEDOPT} 's/(.+)( -t [a-zA-Z0-9]+ )(.*)/\2/'`
+ # if the '-t timeout' is specified, use the timeout provided by the user
+ if [ "${timeout}" != "${args}" ] ; then # match
+ # remove the '-t ' option
+ timeout=`echo ${timeout} | ${SED} ${SEDOPT} 's/-t //'`
+ timeout=`check_timeout ${timeout}`
+ [ $timeout = 0 ] && abort "Date format $1 not valid"
+ # clean the arguments
+ args=`echo ${args} | ${SED} ${SEDOPT} 's/(.+)( -t [a-zA-Z0-9]+ )(.*)/\1 \3/'`
+ else
+ # use the default value, no need to check for correctness, no need to clean arguments
+ timeout=`date --date="1day" +%s` # default to 1 day
+ fi
+
+ # if the table rule is not present, add it
+ local table_rule=`${IPFW} show $S | grep "skipto tablearg" | grep "lookup jail $SLICE_TABLE"`
+ [ -z "$table_rule" ] && ipfw_init
+
+ debug "Timeout $timeout"
+ # Handle special requests: show and delete
+ case x"$cmd" in
+ x"config")
+ case x"$type" in
+ xserver|xSERVER|xclient|xCLIENT|xservice|xSERVICE)
+ do_config $SLICE_ID $timeout $type $args && return 0
+ ;;
+ esac
+ abort "'config' should be followed by {CLIENT|SERVER|SERVICE}"
+ ;;
+ x"delete")
+ do_delete 1 $SLICE_ID $type $args
+ ;;
+ x"refresh")
+ do_refresh $SLICE_ID $type $args $timeout && return 0
+ ;;
+ x"show")
+ # XXX filter out sliver rules
+ [ "$type" = "rules" ] && ${IPFW} show && return 0
+ [ "$type" = "pipes" ] && ${IPFW} pipe show && return 0
+ abort "'show' should be followed by {rules|pipes}"
+ ;;
+ x"super")
+ [ $SLICE_ID = 0 ] && do_super $type $args && return 0
+ abort "no permission for ipfw-be super execution"
+ ;;
+ x"help")
+ do_help && return 0
+ ;;
+ *)
+ # help XXX to be done
+ abort "'command' should be one of {show|config|delete|refresh|release}"
+ ;;
+ esac
+}