#!/usr/local/bin/cbsd
#v11.1.19
MYARG=""
MYOPTARG="jname mode proto in out inaddr outaddr"
MYDESC="Exposing a port to jail from master host"
ADDHELP="mode - add,delete,apply,clear,flush,list\n\
  - add : add and apply one rule, e.g: in=222 out=22 proto=tcp\n\
  - delete : delete and clear one rule, e.g: in=222 out=22\n\
  - apply : apply all rules from database\n\
  - clear : clear all rules from database\n\
  - flush : clear and remove all rules\n\
proto = udp, tcp. default: tcp\n\
in - master port for incoming connection\n\
out - \(optional\) destination port inside jail\n\
inaddr - use IP as nodeip (for incoming connection). Default is $nodeip\n\
outaddr - use IP as destination address, do not use jail IPs\n"
CBSDMODULE="jail"

. ${subr}
. ${strings}
init $*


show_all_expose()
{
	/usr/bin/find ${jailsysdir}/*/  -type f -depth 1 -maxdepth 1 -name expose.sqlite -exec /bin/realpath {} \; | while read _file; do
		myjail=$( echo ${_file} | /usr/bin/sed -Ees:${jailsysdir}/::g -es:/expose.sqlite::g )
		jstatus jname=${myjail} > /dev/null 2>&1
		[ $? -eq 0 ] && continue
		${ECHO} "${MAGENTA}Expose for ${GREEN}${myjail}${MAGENTA}:${NORMAL}"
		fw_expose_list ${_file}
	done
}

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

	for ip in ${ip4_addr}; do
		ipwmask "${ip}"
		if [ -n "${IWM}" ]; then
			echo "${IWM}"
			return 0
		fi
	done
}

get_first_fwnum()
{
	local tmp
	unset fwnum

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

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

	fwnum="${tmp}"

}

pf_del()
{
	[ -z "${COMMENT}" ] && ${ECHO} "${MAGENTA}No comment in pf_del${NORMAL}" && return 1

	if [ -f "${etcdir}/pfnat.conf" ]; then
		if ${GREP_CMD} "${COMMENT}" ${etcdir}/pfnat.conf >/dev/null 2>&1; then
			/bin/cp -a ${etcdir}/pfnat.conf ${tmpdir}/pfnat.conf
			${GREP_CMD} -v "${COMMENT}" ${tmpdir}/pfnat.conf | ${GREP_CMD} "." > ${etcdir}/pfnat.conf
			/bin/rm -f ${tmpdir}/pfnat.conf
		fi
	fi
}


fw_expose_add()
{
	[ "${inaddr}" = "nodeip" ] && inaddr="${nodeip}"

	res=$( /usr/bin/nc -w1 -z ${inaddr} ${in} 2>/dev/null )

	if [ $? -eq 0 ]; then
		${ECHO} "${MAGENTA}Port already in use on ${inaddr}: ${GREEN}${in}${NORMAL}"
		return 0
	fi

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

	${ECHO} "${MAGENTA}CBSD Expose for ${jname}: ${GREEN}${in} -> ${out} (${proto})${NORMAL}"

	case "${nat_enable}" in
		pf)
			pf_del
			/bin/cat >> ${etcdir}/pfnat.conf << EOF
rdr pass proto ${proto} from any to ${inaddr} port ${in} -> ${jip} port ${out} # ${COMMENT}
EOF
			# reload rule
			naton
			;;
		*)
			if [ ${freebsdhostversion} -gt 1100120 ]; then
				/sbin/ipfw add ${fwnum} fwd ${jip},${out} ${proto} from any to ${inaddr} ${in} in ${COMMENT}
			else
				/sbin/ipfw add ${fwnum} fwd ${jip},${out} ${proto} from any to ${inaddr} ${in} in
			fi
			echo "${fwnum}" >"${ftmpdir}/${jname}-expose_fwnum"
			;;
	esac
}


fw_expose_apply()
{
	cbsdsql ${exposefile} SELECT pin,pout,proto,inaddr FROM expose | /usr/bin/tr "|" " " | while read in out proto inaddr; do
		COMMENT="// Setup by CBSD expose: ${proto}-${out}-${jname}"
		fw_expose_add
	done
}

# if $1 than use it as exposefile
fw_expose_list()
{
	[ -n "${1}" ] && exposefile="${1}"

	cbsdsql ${exposefile} SELECT pin,pout,proto,inaddr FROM expose | /usr/bin/tr "|" " " | while read in out proto inaddr; do
		echo "${in} -> ${out} (via ${inaddr} ${proto})"
	done
}


fw_expose_clear()
{

	case "${nat_enable}" in
		pf)
			[ ! -f ${etcdir}/pfnat.conf ] && return 0
			if ${GREP_CMD} -E "([tcp][udp])-([[:digit:]]{1,5})-${jname}"$ ${etcdir}/pfnat.conf 2>&1; then
				/bin/cp -a ${etcdir}/pfnat.conf ${tmpdir}/pfnat.conf
				${GREP_CMD} -E -v "([tcp][udp])-([[:digit:]]{1,5})-${jname}"$ ${tmpdir}/pfnat.conf | ${GREP_CMD} "." > ${etcdir}/pfnat.conf
				naton
			fi
			;;
		ipfw)
			if [ ! -f ${ftmpdir}/${jname}-expose_fwnum ]; then
				return 0
			else
				fwnum=$( /bin/cat ${ftmpdir}/${jname}-expose_fwnum )
			fi
			/sbin/ipfw delete ${fwnum}
			;;
	esac

}


fw_expose_delete()
{
	[ "${inaddr}" = "nodeip" ] && inaddr="${nodeip}"

	cbsdsql ${exposefile} "DELETE FROM expose WHERE pin=$in AND pout=$out AND proto=\"${proto}\" AND inaddr=\"${inaddr}\" AND outaddr=\"${outaddr}\""

	if [ ! -f ${ftmpdir}/${jname}-expose_fwnum ]; then
		${ECHO} "${MAGENTA}No ${ftmpdir}/${jname}-expose_fwnum: skip for deletion expose rule${NORMAL}"
		return 0
	else
		fwnum=$( /bin/cat ${ftmpdir}/${jname}-expose_fwnum )
	fi

	case "${nat_enable}" in
		pf)
			pf_del
			# reload pf
			naton
			;;
		*)
			/sbin/ipfw delete ${fwnum}
			;;
	esac
}


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

[ -z "${jname}" ] && err 1 "Give me jname"

. ${jrcconf}
[ $? -eq 1 ] && err 1 "${MAGENTA}No such jail: ${GREEN}${jname}${NORMAL}"

[ -z "${proto}" ] && proto="tcp"
[ -z "${inaddr}" ] && inaddr="nodeip"  # "nodeip"  - reserverd word for $nodeip variable
if [ "${nat_enable}" = "ipfw" ]; then
	[ "$( /sbin/sysctl -qn net.inet.ip.fw.enable 2>/dev/null )" != "1" ] && err 1 "${MAGENTA}IPFW is not enabled${NORMAL}"
fi
# init ipfw number
get_first_fwnum
[ -z "${fwnum}" ] && err 1 "${MAGENTA}Empty fwnum variable${NORMAL}"

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

[ -z "${jip}" ] && err 1 "${MAGENTA}Unable to determine jail ip${NORMAL}"

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}"

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

[ -z "${in}" ] && err 1 "${MAGENTA}Empty ${GREEN}in${NORMAL}"
[ -z "${out}" ] && err 1 "${MAGENTA}Empty ${GREEN}out${NORMAL}"

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

case "${mode}" in
	add)
		fw_expose_add
		cbsdsql ${exposefile} "INSERT INTO expose ( pin, pout, proto, inaddr, outaddr ) VALUES ( ${in}, ${out}, \"${proto}\", \"${inaddr}\", \"${outaddr}\" )"
		;;
	delete)
		fw_expose_delete
		fw_expose_apply
		;;
	*)
		err 1 "${MAGENTA}Unknown mode: ${GREEN}${mode}${NORMAL}"
		;;
esac
