#!/usr/local/bin/cbsd
#v13.0.12
MYARG=""
MYOPTARG="in inaddr jname mode out outaddr proto fromips"
MYDESC="Expose a port (port forwarding) to env via IPFW or PF"
ADDHELP="
${H3_COLOR}Description${N0_COLOR}:

 By 'cbsd expose' command you can create forward rule for tcp/udp
 port from external IP (default with 'nodeip' from 'cbsd initenv-tui'
 to bhyve or jail.
 The RDR/FWD rules are generated for NAT framework, 
 which you chose through 'cbsd natcfg'. For 'pf', nat rules file
 located in ~cbsd/etc/pf.conf. For 'ipfw' rules applied in runtime
 and have a comment 'Setup by CBSD expose.'. Each '[jb]stop' or '[jb]start'
 sequence trigger 'expose' script every time (when the environment has rules).

 In addition to the rules for an individual port, you can forward all traffic 
 from/to another (for example, external) address to the virtual machine address.

${H3_COLOR}Options${N0_COLOR}:

 ${N2_COLOR}in${N0_COLOR}      - master port for incoming connection.
 ${N2_COLOR}inaddr${N0_COLOR}  - use IP as nodeip (for incoming connection),
           default is '0' - inherit \${nodeip} ( 'cbsd initenv-tui' to change ).
           This is useful when the env is migrating between different hosts 
           (with different nodes). Or set a fixed IPv4 address.
 ${N2_COLOR}jname${N0_COLOR}   - environment name.
 ${N2_COLOR}mode${N0_COLOR}    - add,delete,apply,clear,flush,list:
  - add    : add and apply one rule, e.g: in=222 out=22 proto=tcp;
  - delete : delete and clear one rule, e.g: in=222 out=22;
  - apply  : apply all rules from database;
  - clear  : clear all rules from datatase;
  - flush  : clear and remove all rules;
 ${N2_COLOR}out${N0_COLOR}     - (optional) destination port inside jail.
 ${N2_COLOR}outaddr${N0_COLOR} - use IP as destination address, do not
           use jail/bhyve IPs.
 ${N2_COLOR}proto${N0_COLOR}   - udp, tcp. default: tcp.
 ${N2_COLOR}fromips${N0_COLOR} - allowed IP address list of the port forward. default: any.

${H3_COLOR}Examples${N0_COLOR}:

1) Forward all incoming traffic to \$nodeip:2233 to foo:22 jail:

 # cbsd expose mode=add in=2233 out=22 jname=foo
 
  Or the same with specifying source IPs which can access the very port forward. Not specifying IPs adds 'any' to the pf rules:

 # cbsd expose mode=add in=2233 out=22 fromips=8.8.8.8 jname=foo
 # cbsd expose mode=add in=2233 out=22 fromips=8.8.8.8,1.1.1.1 jname=foo

  Or via CBSDfile (the 'jname=' args can be ommited):
--
jail_foo()
{
}

postcreate_foo()
{
    expose mode=add in=2233 out=22 fromips=8.8.8.8,1.1.1.1
}

2) map (forward) all 1:1 traffic (tcp/udp) from EXT_IP4 <-> VM IP address
   (EXT_IP should be exist on the hoster)

 # cbsd expose mode=add jname=vm1 inaddr=<EXT_IP4>

--

"
CBSDMODULE="bhyve,jail"
EXTHELP="wf_expose"


. ${subrdir}/nc.subr
cloud_api=0
. ${cbsdinit}

if [ -z "${jname}" -a -n "${ojname}" ]; then
	# inherit jname env
	jname="${ojname}"
fi

# check for cloud function when CBSDfile exist
Makefile="${CBSD_PWD}/CBSDfile"
if [ -r "${Makefile}" ]; then
	[ -z "${CBSDFILE_RECURSIVE}" ] && ${ECHO} "${N1_COLOR}found CBSDfile: ${N2_COLOR}${Makefile}${N0_COLOR}" 1>&2
	. ${Makefile}
	all_jail_list=$( ${GREP_CMD} -E '^jail_[a-zA-Z0-9_@%:][-a-zA-Z0-9_@%:]*\(\)$' ${Makefile} | ${XARGS_CMD} | ${TR_CMD} -d "()" | ${SED_CMD} s#jail_##g )
	all_bhyve_list=$( ${GREP_CMD} -E '^bhyve_[a-zA-Z0-9_@%:][-a-zA-Z0-9_@%:]*\(\)$' ${Makefile} | ${XARGS_CMD} | ${TR_CMD} -d "()" | ${SED_CMD} s#bhyve_##g )

	if [ -n "${CLOUD_URL}" -a -n "${CLOUD_KEY}" ]; then
		cbsd_api=1
	else
		cbsd_api=0
	fi

	if [ -n "${jname}" ]; then
		found=0
		for i in ${all_jail_list} ${all_bhyve_list}; do
		if [ "${i}" = "${jname}" ]; then
			found=1
			break
		fi
		done
		[ ${found} -eq 0 ] && err 1 "${N1_COLOR}${CBSD_APP}: no such env: ${N2_COLOR}${jname}${N0_COLOR}"
	fi
else
	#jname="${all_jail_list}"
	cbsd_api=0
fi

if [ ${cbsd_api} -eq 1 ]; then
	err 0 "${N1_COLOR}${CBSD_APP}: expose for API/remote not supported yet, skip for expose rule: ${N2_COLOR}mode=${mode} in=${in}${N0_COLOR}"
fi

# "$1" - check values
is_valid_port()
{
	local _port

	_port="${1}"

	if is_number "${_port}"; then
		err 1 "${W1_COLOR}${CBSD_APP} error: ${N1_COLOR}wrong port values: ${N2_COLOR}${_port}${N0_COLOR}"
	fi

	if [ ${_port} -lt 1 -o ${_port} -gt 65535 ]; then
		err 1 "${W1_COLOR}${CBSD_APP} error: ${N1_COLOR}wrong port range (1-65535): ${N2_COLOR}${_port}${N0_COLOR}"
	fi

	return 0
}

show_all_expose()
{
	local _jname _file

	cbsdsqlro local "SELECT jname FROM jails ASC" | while read _jname; do
		_file="${jailsysdir}/${_jname}/expose.sqlite"
		[ ! -r ${_file} ] && continue
		${ECHO} "${N1_COLOR}Expose for ${N2_COLOR}${_jname}${N1_COLOR}:${N0_COLOR}" 1>&2
		fw_expose_list ${_file}
	done
}

get_first_ip()
{
	local IFS=","
	local ip IWM _res

	for ip in ${ip4_addr}; do
		ipwmask "${ip}"
		if [ -n "${IWM}" ]; then
			# cut network prefix if exist
			_res=${IWM##*#}
			echo "${_res}"
			return 0
		fi
	done
}

get_first_fwnum()
{
	local tmp
	unset fwnum

	tmp=$( for i in $( ${SEQ_CMD} ${fwexpose_st} ${fwexpose_end} ); do
		${IPFW_CMD} list ${i} > /dev/null 2>&1
		[ $? -eq 0 ] && continue
		echo ${i}
		break
	done )

	[ -z "${tmp}" ] && err 1 "${N1_COLOR}Unable to determine first fwnum for expose${N0_COLOR}"
	[ ${tmp} -eq ${fwexpose_end} ] && err 1 "${N1_COLOR}No free ipfw num for expose in expose range: ${N2_COLOR}${fwexpose_st} - ${fwexpose_end}${N0_COLOR}"

	fwnum="${tmp}"
}

# $1 - target file: pfnat.conf or pfrdr.conf
pf_del()
{
	local _target
	[ -z "${1}" ] && return 1

	_target="${1}"

	[ -z "${COMMENT}" ] && ${ECHO} "${N1_COLOR}No comment in pf_del${N0_COLOR}" && return 1

	if [ -r "${etcdir}/${_target}" ]; then
		if ${GREP_CMD} "${COMMENT}" ${etcdir}/${_target} >/dev/null 2>&1; then
			${CP_CMD} -a ${etcdir}/${_target} ${tmpdir}/${_target}
			${GREP_CMD} -v "${COMMENT}" ${tmpdir}/${_target} | ${GREP_CMD} "." > ${etcdir}/${_target}
			${RM_CMD} -f ${tmpdir}/${_target}
		fi
	fi

	return 0
}

fw_expose_add()
{
	local _ret= _inaddr= _res= _test=

	[ "${inaddr}" = "nodeip" ] && inaddr="0"

	if [ -z "${inaddr}" ]; then
		${ECHO} "${N1_COLOR}${CBSD_APP}: inaddr not IPv4 address, skip: ${N2_COLOR}${inaddr}${N0_COLOR}" 1>&2
		return 1
	fi

	if [ "${inaddr}" != "0" ]; then
		_inaddr="${inaddr}"

		ipwmask ${_inaddr}
		iptype ${IWM}
		_ret=$?

		if [ ${_ret} -ne 1 ]; then
			${ECHO} "${N1_COLOR}${CBSD_APP}: inaddr not IPv4 address, skip: ${N2_COLOR}${_inaddr}${N0_COLOR}" 1>&2
			return 1
		fi
		_inaddr="${IWM}"
	else
		_inaddr="${nodeip}"
	fi

	if [ -z "${jip}" -o "${jip}" = "0" ]; then
		${ECHO} "${N1_COLOR}${CBSD_APP}: outaddr not IPv4 address, skip: ${N2_COLOR}${jip}${N0_COLOR}" 1>&2
		return 1
	fi

	ipwmask ${jip}
	iptype ${IWM}
	_ret=$?

	if [ ${_ret} -ne 1 ]; then
		${ECHO} "${N1_COLOR}${CBSD_APP}: outaddr not IPv4 address, skip: ${N2_COLOR}${jip}${N0_COLOR}" 1>&2
		return 1
	fi

	case "${proto}" in
		tcp)
			_test="${NC_CMD} -w1 -z ${_inaddr} ${in}"
			;;
		udp)
			_test="${NC_CMD} -u -w1 -z ${_inaddr} ${in}"
			;;
	esac

	${_test} 2>/dev/null
	_ret=$?

	if [ ${_ret} -eq 0 ]; then
		${ECHO} "${W1_COLOR}${CBSD_APP} warning: ${N1_COLOR}port already in use on ${_inaddr}?: ${N2_COLOR}${proto}/${in}${N0_COLOR}"
		case "${proto}" in
			tcp)
				${ECHO} "${N1_COLOR}Test string: '${N2_COLOR}${_test}${N1_COLOR}' (check errcode)${N0_COLOR}"
				return 1
				;;
			udp)
				${ECHO} "${N1_COLOR}Test string: '${N2_COLOR}${_test}${N1_COLOR}' (check errcode). Possible false positive for UDP, let's try to continue${N0_COLOR}"
				;;
		esac
	fi

	if [ -z "${fromips}" ]; then
		#${ECHO} "\$fromips var is empty: $fromips"
		${fromips:='any'} 2> /dev/null
		#${ECHO} "\$fromips var now modifed: $fromips"
	fi

	[ -f ${ftmpdir}/${jname}-expose_fwnum ] && fwnum=$( ${CAT_CMD} ${ftmpdir}/${jname}-expose_fwnum )

	case "${in}" in
		0)
			${ECHO} "${N1_COLOR}CBSD Expose for ${jname}: ${N2_COLOR}ALL via ${_inaddr} -> ${jip}${N0_COLOR}"
			cbsdlogger NOTICE ${CBSD_APP}: ${jname}: ALL ${_inaddr}:${jip}
			;;
		*)
			${ECHO} "${N1_COLOR}CBSD Expose for ${jname}: ${N2_COLOR}${in} -> ${out} (${proto})${N0_COLOR} from IPs (${fromips})${N0_COLOR}"
			cbsdlogger NOTICE ${CBSD_APP}: ${jname}: proto: ${proto}: ${in}:${out} from IPs: ${fromips}
			;;
	esac

	case "${nat_enable}" in
		pf)
			pf_del pfrdr.conf
			pf_del pfnat.conf

			case "${in}" in
				0)
					rfcnet=${jip%%\.*}
					case "${rfcnet}" in
						10)
							rfcnet="10.0.0.0/8"
							;;
						172)
							rfcnet="172.16.0.0/12"
							;;
						192)
							rfcnet="192.168.0.0/16"
							;;
						*)
							rfcnet="${jip}/32"
							;;
					esac

					_pass_iface=

					for i in $( ${IFCONFIG_CMD} -l ); do
						case "${i}" in
							lo*)
								continue
								;;
						esac
						${IFCONFIG_CMD} ${i} | ${GREP_CMD} "inet " | ${GREP_CMD} -q " ${_inaddr} " > /dev/null
						_ret=$?
						if [ ${_ret} -eq 0 ]; then
							_pass_iface="${i}"
							break
						fi
					done

					if [ -z "${_pass_iface}" ]; then
						${ECHO} "${N1_COLOR}${CBSD_APP}: unable to determine pass interface for: ${N2_COLOR}${_inaddr}${N0_COLOR}" 1>&2
						return 1
					fi

# + iface
#					cbsdlogger NOTICE "${CBSD_APP}: rdr pass on ${_pass_iface} proto {tcp, udp} from any to ${_inaddr}/32 -> ${jip}/32"
#					cbsdlogger NOTICE "${CBSD_APP}: nat pass on ${_pass_iface} from ${jip}/32 to !${rfcnet} -> ${_inaddr}"

#					${CAT_CMD} >> ${etcdir}/pfnat.conf <<EOF
#rdr pass on ${_pass_iface} proto {tcp, udp} from any to ${_inaddr}/32 -> ${jip}/32 # ${COMMENT}
#nat pass on ${_pass_iface} from ${jip}/32 to !${rfcnet} -> ${_inaddr} # ${COMMENT}
#EOF

					cbsdlogger NOTICE "${CBSD_APP}: rdr pass proto {tcp, udp} from ${fromips} to ${_inaddr}/32 -> ${jip}/32"
					cbsdlogger NOTICE "${CBSD_APP}: nat pass from ${jip}/32 to !${rfcnet} -> ${_inaddr}"

					# RDR
					${CAT_CMD} >> ${etcdir}/pfrdr.conf <<EOF
rdr pass proto {tcp, udp} from {${fromips}} to ${_inaddr}/32 -> ${jip}/32 # ${COMMENT}
EOF
					# NAT
					${CAT_CMD} >> ${etcdir}/pfnat.conf <<EOF
nat pass from ${jip}/32 to !${rfcnet} -> ${_inaddr} # ${COMMENT}
EOF
					;;
				*)
					# RDR
					${CAT_CMD} >> ${etcdir}/pfrdr.conf << EOF
rdr pass proto ${proto} from {${fromips}} to ${_inaddr} port ${in} -> ${jip} port ${out} # ${COMMENT}
EOF
					;;
			esac
			# reload rule
			naton
			;;
		nft)
			cbsdlogger NOTICE "${CBSD_APP}: nft: add table ip nat${jname}"
			${NFT_CMD} add table ip nat${jname}
			cbsdlogger NOTICE "${CBSD_APP}: nft: add prerouring/postrouting chain for: nat${jname}"
			${NFT_CMD} -- add chain ip nat${jname} prerouting { type nat hook prerouting priority -100 \; }
			${NFT_CMD} add chain ip nat${jname} postrouting { type nat hook postrouting priority 100 \; }
			cbsdlogger NOTICE "${CBSD_APP}: nft: ${NFT_CMD} add rule ip nat${jname} prerouting tcp dport ${in} dnat to ${jip}:${out}"
			${NFT_CMD} add rule ip nat${jname} prerouting tcp dport ${in} dnat to ${jip}:${out}
			cbsdlogger NOTICE "${CBSD_APP}: nft: ${NFT_CMD} add rule ip nat${jname} postrouting ip daddr ${jip} masquerade"
			${NFT_CMD} add rule ip nat${jname} postrouting ip daddr ${jip} masquerade
			# not needed?
			# sysctl "net.ipv4.ip_forward=1"
			# 
			;;
		*)
			if [ ${freebsdhostversion} -gt 1100120 ]; then
				${IPFW_CMD} add ${fwnum} fwd ${jip},${out} ${proto} from any to ${_inaddr} ${in} in ${COMMENT}
			else
				${IPFW_CMD} add ${fwnum} fwd ${jip},${out} ${proto} from any to ${_inaddr} ${in} in
			fi
			echo "${fwnum}" >"${ftmpdir}/${jname}-expose_fwnum"
			;;
	esac

	return 0
}

fw_expose_apply()
{
	cbsdsqlro ${exposefile} SELECT pin,pout,proto,inaddr,fromips FROM expose | ${TR_CMD} "|" " " | while read in out proto inaddr fromips; do
		COMMENT="// Setup by CBSD expose: ${proto}-${out}-${jname} from IPs ${fromips}"
		fw_expose_add
	done
}

# if $1 than use it as exposefile
fw_expose_list()
{
	local _inaddr

	[ -n "${1}" ] && exposefile="${1}"

	[ ! -r ${exposefile} ] && return 1

	cbsdsqlro ${exposefile} SELECT pin,pout,proto,inaddr,fromips FROM expose | ${TR_CMD} "|" " " | while read in out proto inaddr fromips; do

		case "${inaddr}" in
			nodeip|0)
				_inaddr="inaddr=0 -> ${nodeip}"
				;;
			*)
				_inaddr="${inaddr}"
				;;
		esac

		case "${in}" in
			0)
				echo "map all via ${_inaddr}"
				;;
			*)
				echo "${in} -> ${out} from ${fromips} (${_inaddr} ${proto})"
				;;
		esac
	done
}

fw_expose_clear()
{
	local _need_naton=0 _ret=

	case "${nat_enable}" in
		pf)
			for i in pfnat.conf pfctl.conf pfrdr.conf; do
				if [ -r ${etcdir}/${i} ]; then
					#if ${GREP_CMD} -E "([tcp][udp])-([[:digit:]]){1,5}-${jname}"$ ${etcdir}/${i} 2>&1; then
					if ${GREP_CMD} -E "([tcp][udp])-([[:digit:]]){1,5}-${jname} " ${etcdir}/${i} 2>&1; then
						${CP_CMD} -a ${etcdir}/${i} ${tmpdir}/${i}
						cbsdlogger NOTICE ${CBSD_APP}: fw_expose_clear for ${jname}: via ${i}
						#${GREP_CMD} -E -v "([tcp][udp])-([[:digit:]]{1,5})-${jname}"$ ${tmpdir}/${i} | ${GREP_CMD} "." > ${etcdir}/${i}
						${GREP_CMD} -E -v "([tcp][udp])-([[:digit:]]{1,5})-${jname} " ${tmpdir}/${i} | ${GREP_CMD} "." > ${etcdir}/${i}
						_need_naton=1
					fi
				fi
			done

			[ ${_need_naton} -eq 1 ] && naton

			;;
		ipfw)
			if [ ! -f ${ftmpdir}/${jname}-expose_fwnum ]; then
				return 0
			else
				fwnum=$( ${CAT_CMD} ${ftmpdir}/${jname}-expose_fwnum )
			fi
			cbsdlogger NOTICE ${CBSD_APP}: fw_expose_clear for ${jname}: delete ipfw rule ${fwnum}
			${IPFW_CMD} delete ${fwnum}
			;;
		nft)
			${NFT_CMD} list table ip nat${jname} >/dev/null 2>&1
			_ret=$?
			if [ ${_ret} -eq 0 ]; then
				cbsdlogger NOTICE ${CBSD_APP}: nft_expose_clear for ${jname}: ${NFT_CMD} delete table ip nat${jname}
				${NFT_CMD} delete table ip nat${jname}
			fi
	esac

	return 0
}

fw_expose_delete()
{
	if [ -z "${fromips}" ]; then
		#${ECHO} "\$fromips var is empty: $fromips"
		${fromips:='any'} 2> /dev/null
		#${ECHO} "\$fromips var now modifed: $fromips"
	fi

	if [ "${inaddr}" = "nodeip" -o "${inaddr}" = "0" ]; then
		inaddr="${nodeip}"
		cbsdsqlrw ${exposefile} "DELETE FROM expose WHERE pin=${in} AND pout=${out} AND proto=\"${proto}\" AND ( inaddr=\"${inaddr}\" OR inaddr=\"0\" ) AND outaddr=\"${outaddr}\" AND fromips=\"${fromips}\""
	else
		cbsdsqlrw ${exposefile} "DELETE FROM expose WHERE pin=${in} AND pout=${out} AND proto=\"${proto}\" AND inaddr=\"${inaddr}\" AND outaddr=\"${outaddr}\" AND fromips=\"${fromips}\""
	fi
	cbsdlogger NOTICE ${CBSD_APP}: fw_expose_delete: delete config for: ${jname}

	case "${nat_enable}" in
		pf)
			pf_del pfrdr.conf
			pf_del pfnat.conf
			# reload pf
			naton
			;;
		*)
			if [ ! -f ${ftmpdir}/${jname}-expose_fwnum ]; then
				${ECHO} "${N1_COLOR}No ${ftmpdir}/${jname}-expose_fwnum: skip for deletion expose rule${N0_COLOR}"
				return 0
			else
				fwnum=$( ${CAT_CMD} ${ftmpdir}/${jname}-expose_fwnum )
			fi
			${IPFW_CMD} delete ${fwnum}
			;;
	esac
}


# MAIN
if [ -z "$1" ]; then
	show_all_expose
	exit 0
fi

[ -z "${jname}" ] && err 1 "${N1_COLOR}${CBSD_APP}: give me jname${N0_COLOR}"

. ${subrdir}/rcconf.subr
[ $? -eq 1 ] && err 1 "${N1_COLOR}no such jail: ${N2_COLOR}${jname}${N0_COLOR}"

[ -z "${proto}" ] && proto="tcp"
[ -z "${inaddr}" ] && inaddr="0"		# inherit $nodeip
# back compat
[ "${inaddr}" = "nodeip" ] && inaddr="0"	# "nodeip"  - reserverd word for $nodeip variable, back compatible3

if [ "${nat_enable}" = "ipfw" ]; then
	[ "$( ${SYSCTL_CMD} -qn net.inet.ip.fw.enable 2>/dev/null )" != "1" ] && err 1 "${N1_COLOR}IPFW is not enabled${N0_COLOR}"
fi
# init ipfw number
get_first_fwnum
[ -z "${fwnum}" ] && err 1 "${N1_COLOR}Empty fwnum variable${N0_COLOR}"

if [ -z "${outaddr}" ]; then
	jip=$( get_first_ip )
else
	jip="${outaddr}"
fi

[ -z "${jip}" ] && err 1 "${N1_COLOR}${CBSD_APP}: unable to determine jail ip: ${N2_COLOR}${jname}${N0_COLOR}"

exposefile="${jailsysdir}/${jname}/expose.sqlite"

[ ! -r "${exposefile}" ] && /usr/local/bin/cbsd ${miscdir}/updatesql ${exposefile} ${distdir}/share/system-expose.schema expose

[ -z "${in}" -a -n "${out}" ] && in="${out}"
[ -z "${out}" -a -n "${in}" ] && out="${in}"

[ -n "${in}" ] && is_valid_port "${in}"
[ -n "${out}" ] && is_valid_port "${out}"

case "${mode}" in
	list)
		fw_expose_list
		exit 0
		;;
	apply)
		fw_expose_apply
		exit 0
		;;
	clear)
		fw_expose_clear
		[ -f ${ftmpdir}/${jname}-expose_fwnum ] && ${RM_CMD} -f ${ftmpdir}/${jname}-expose_fwnum
		exit 0
		;;
	flush)
		fw_expose_clear
		${RM_CMD} -f ${exposefile}
		[ -f ${ftmpdir}/${jname}-expose_fwnum ] && ${RM_CMD} -f ${ftmpdir}/${jname}-expose_fwnum
		exit 0
		;;
esac

if [ -z "${in}" ]; then
	[ -z "${inaddr}" ] && err 1 "${N1_COLOR}Empty ${N2_COLOR}in${N0_COLOR}"
	case "${inaddr}" in
		[Nn][Oo][Dd][Ee]*)
			err 1 "${N1_COLOR}${CBSD_APP}: Please use any IPv4 when 'in' args empty: ${N2_COLOR}inaddr=${N0_COLOR}"
			;;
	esac

	if [ "${inaddr}" != "0" -a "${inaddr}" != "nodeip" ]; then
		iptype ${inaddr}
		ret=$?
		[ ${ret} -ne 1 ] && err 1 "${N1_COLOR}${CBSD_APP}: not valid IPv4 for 'inaddr': ${N2_COLOR}${inaddr}${N0_COLOR}"
	fi
	in="0"		# 'all'
fi

if [ "${in}" = "0" ]; then
	out="0"		# 'all'
else
	[ -z "${out}" ] && err 1 "${N1_COLOR}Empty ${N2_COLOR}out${N0_COLOR}"
fi

COMMENT="// Setup by CBSD expose: ${proto}-${out}-${jname} from IPs ${fromips}"

case "${mode}" in
	add)
		fw_expose_add
		ret=$?
		if [ ${ret} -eq 0 ]; then
			# check for dup
			_res=$( cbsdsqlro ${exposefile} "SELECT pin FROM expose WHERE pin=${pin} AND pout=${pout} AND proto=\"${proto}\" AND inaddr=\"${inaddr}\" AND outaddr=\"${outaddr}\" AND fromips=\"${fromips}\"" | ${HEAD_CMD} -n 1 )
			[ -n "${_res}" ] && err 1 "${N1_COLOR}${CBSD_APP}: already exist in DB: pin=${pin} AND pout=${pout} AND proto=\"${proto}\" AND inaddr=\"${inaddr}\" AND outaddr=\"${outaddr}\" AND fromips=\"${fromips}\""
			cbsdsqlrw ${exposefile} "INSERT INTO expose ( pin, pout, proto, inaddr, outaddr, fromips ) VALUES ( ${in}, ${out}, \"${proto}\", \"${inaddr}\", \"${outaddr}\", \"${fromips}\" )"
		else
			err 1 "${N1_COLOR}${CBSD_APP}: fw_expose_add error: ${ret}${N0_COLOR}"
		fi
		;;
	delete)
		fw_expose_delete
		fw_expose_apply
		;;
	*)
		err 1 "${N1_COLOR}Unknown mode (valid: add,delete,apply,clear,flush,list): ${N2_COLOR}${mode}${N0_COLOR}"
		;;
esac

exit 0
