#!/bin/bash
set -e
#Copyright (c) 1999-2000, William L. Stearns <wstearns@pobox.com>
#See top of Mason script for copyright and licensing information.
#Last YYYY code used: 0019, use 0020 next

CKPTMGT=" mgt: Ground0" ; #ckpt $CKPTMGT

MASONDIR=${MASONDIR:-"/var/lib/mason/"}
MASONCONF=${MASONCONF:-"/etc/masonrc"}
MASONLIB=${MASONLIB:-"${MASONDIR}masonlib"}
#MASONLIB=${MASONLIB:-"${MASONDIR}masonlib"}	#My editor of choice (mcedit) gets quoting backwards if there is an odd number of these.

if [ -f $MASONLIB ]; then
	. $MASONLIB
else
	#Can't use wrap here - no library to load it from.
	echo Missing $MASONLIB library file.  Please get a complete copy of Mason from >/dev/stderr
	echo http://www.pobox.com/~wstearns/mason/ .  Exiting. >/dev/stderr
	sleep 10
	exit 1
fi

catchall		#Put in a default handler for everything; don't just let children die.
trap preexit 0		#This gets nullified if we get a SIGHUP or SIGINT -  see mgtcleanup.

killrunningmason () {
	#A backgrounded Mason is a pain in the %$^#^%$ to kill...
	#I think I may have found my wooden stake.  Have the equivalent of a pipe to mason; a 
	#file which is tailed along with /var/log/messages into mason when it's run.  When 
	#we need to kill mason, echo '!!EXIT!!' into it and have mason look for it on stdin.
	#Until I'm sure this works, I'll keep the old "bludgeon it to death with sigs" 
	#approach as a backup.
	if [ -n "$KILLFILES" ]; then
		for ONEKILLFILE in $KILLFILES ; do
			echo '!!EXIT!!' >>$ONEKILLFILE
		done
		sleep 5
		for ONEKILLFILE in $KILLFILES ; do
			rm -f $ONEKILLFILE 2>/dev/null || :
		done
		KILLFILES=""
	fi

	if [ -n "$MASONTAILPID" ]; then	kill -TERM $MASONTAILPID 2>/dev/null || : ;	MASONTAILPID="" ; sleep 1 ;	fi
	if [ -n "$MASONPID" ]; then
		#FIXME - check if it's still running first?
		#wrap Killing active $MASONPID >/dev/stderr
		kill -HUP $MASONPID 2>/dev/null || :
		sleep 2
		kill -TERM $MASONPID 2>/dev/null || :
		MASONPID=""
	fi

	if [ -f "$MASONPIDFILE" ]; then
		for ONEPID in `cat $MASONPIDFILE` ; do
			if type -path ps >/dev/null 2>/dev/null && type -path grep >/dev/null 2>/dev/null ; then
				if [ -n "`ps ax | grep "^[[:space:]]*$ONEPID[[:space:]]" | grep mason`" ]; then
					#wrap Killing background $ONEPID >/dev/stderr
					kill -TERM $ONEPID 2>/dev/null || :
					sleep 2
					kill -9 $ONEPID 2>/dev/null || :
				fi
			else
				kill -TERM $ONEPID 2>/dev/null || :
			fi
		done
		cat /dev/null >$MASONPIDFILE || :	#SUDO?
	fi
}

CKPTMGT=" mgt: trap" ; #ckpt $CKPTMGT

mgtcleanup () {
	#FIXME - should this be 'trap 0'?
	trap - 0	#If we received a signal, no need to process a "crash".
	unset CKPTMGT CKPTMASON CKPTCHECKSYS CKPTCLIENTPORTRANGE \
	 CKPTGENERALIZEIP CKPTIPLE CKPTIPLT CKPTISNUMERICIP CKPTLOADCONF \
	 CKPTSERVERPORTRANGE CKPTADDCOUNTS CKPTNAMEOF CKPTBROADCASTOF \
	 CKPTCHECKCONF CKPTDELCOUNTS CKPTFLUSHFIREWALL CKPTPORT2CISCOPORT \
	 CKPTPROTONUM2NAME CKPTRULETAG CKPTRUNFIREWALL \
	 CKPTSETTOS CKPTSORTRULEFILE \
	 CKPTUNIQRULEFILE CKPTUPDATECOUNTS CKPTNETWORKOF || :
	if [ -n "$VIEWTAILPID" ]; then	kill -TERM $VIEWTAILPID 2>/dev/null || : ;	VIEWTAILPID="" ;			fi
	killrunningmason
	CKPTMGT=""
	echo
	wrap ${WARN}Exiting - you may need to reset the firewall...${NORM}
	exit 0
}
trap mgtcleanup SIGINT		#Ctrl-C generates this

reprocess-newrules () {
	rm -f $NEWRULEFILE.tmp || :
	mv -f $NEWRULEFILE $NEWRULEFILE.tmp || :
	touch $NEWRULEFILE || :
	cat $NEWRULEFILE.tmp | $MASONDECIDE 10<&1 11>>$BASERULEFILE 12>>$NEWRULEFILE 13>/dev/null
	rm -f $NEWRULEFILE.tmp || :
}

if [ -f $MASONCONF ]; then	#the entire loadconf function is not required
	. $MASONCONF
fi

CKPTMGT=" mgt: start" ; #ckpt $CKPTMGT

checksys
checkconf


EXITCONTROL="NO"
MASONPID=""
MASONTAILPID=""
VIEWTAILPID=""
KILLFILES=""

killrunningmason

while [ ! "$EXITCONTROL" = "YES" ]; do
	clear
	echo $ENH "${HEADER}---- ${BLUE}Mason${HEADER} firewall builder for Linux                         ----$NORM"
	echo $ENH "${HEADER}---- Learning shell for Mason.                                ----$NORM"
	echo $ENH "${HEADER}---- see http://www.pobox.com/~wstearns/mason/ for more info. ----$NORM"
	echo $ENH "${HEADER}---- William Stearns <wstearns@pobox.com>                     ----$NORM"
	if [ -n "$MASONVER" ]; then
		echo $ENH "${HEADER}(This is release $MASONVER)$NORM"
	fi
	if [ -n "$MASONPID$MASONTAILPID" ]; then
		echo Mason is currently in learn mode.
		echo
		echo $ENH ${KEY}EL${NORM}: ${KEY}E${NORM}nd ${KEY}L${NORM}earning.
		showstate 'Mason-gui-text: Main menu, learning in the background'
	else
		echo Mason IS NOT currently learning.
		echo
		echo $ENH ${KEY}BL${NORM}: ${KEY}B${NORM}egin ${KEY}L${NORM}earning.
		showstate 'Mason-gui-text: Main menu, not learning'
	fi
	echo $ENH ${KEY}EB${NORM}: ${KEY}E${NORM}dit ${KEY}B${NORM}ase firewall rule file.	#=> end learn
	echo $ENH ${KEY}EN${NORM}: ${KEY}E${NORM}dit ${KEY}N${NORM}ew firewall rule file.	#=> end learn, ask to merge rulefiles when done
	echo $ENH ${KEY}MR${NORM}: ${KEY}M${NORM}erge ${KEY}R${NORM}ules from new to base.
	echo $ENH ${KEY}CS${NORM}: ${KEY}C${NORM}hange mason ${KEY}S${NORM}ettings.			#=> sigusr1 mason when done
	echo $ENH ${KEY}LC${NORM}: ${KEY}L${NORM}ock this ${KEY}C${NORM}onsole and display new rules.
	echo $ENH ${KEY}Q${NORM}: ${KEY}Q${NORM}uit.
	#CKPTMGT=" mgt: Waiting for main menu choice" ; #ckpt $CKPTMGT
	CKPTMGT="" ; #ckpt $CKPTMGT
	read CHOICE || :
	CKPTMGT=" mgt: Calling $CHOICE" ; #ckpt $CKPTMGT
	case $CHOICE in
	[Bb][Ll])
		wrap Begin Learning.
		showstate 'Mason-gui-text: starting to learn'
		echo 
		CKPTMGT=" mgt: BL, flushing" ; #ckpt $CKPTMGT
		flushfirewall
		CKPTMGT=" mgt: BL, learning" ; #ckpt $CKPTMGT
		runfirewall LEARN
		CKPTMGT=" mgt: BL, set killfile" ; #ckpt $CKPTMGT
		NEWKILLFILE="$MASONDIR$RANDOM.tempflagfile"		#FIXME - mktemp/mkstemp?
		touch $NEWKILLFILE || logfail $LINENO mason-gui-text: YYYY 0019
		KILLFILES="$KILLFILES $NEWKILLFILE"
		CKPTMGT=" mgt: BL, tail" ; #ckpt $CKPTMGT
	  if /bin/false ; then
		#Is there _any_ _friggin'_ _way_ to handle this cleanly and catch a non-zero return code?
		#Should we set +e here?
		( tail -q -f --lines=0 $PACKETLOGFILE $KILLFILES | $MASONEXE >>$NEWRULEFILE ) &		#nohup just before tail removed		#SUDO on tail?
		MASONTAILPID="$MASONTAILPID `ps axf | grep -v grep | grep -A 1 '^[[:space:]]*$![[:space:]]' | tail -1 | awk '{print $1}' || logfail $LINENO mason-gui-text: YYYY 0001`"
		#I am open to ideas on better ways to figure out the pid of the tail command.  Anyone?  Bueller?
		MASONPID="$MASONPID `ps axf | grep -v grep | grep -A 2 '^[[:space:]]*$![[:space:]]' | tail -1 | awk '{print $1}' || logfail $LINENO mason-gui-text: YYYY 0002`"
		#FIXME - display this first so as not to mix output?
		wrap Mason is now running in the background.  I will be showing the log output.  Press Enter when you want to return to the main menu - Mason will continue to run in the background until you choose \"EL\" from the main menu.
		#tail -f --lines=0 $PACKETLOGFILE $NEWRULEFILE &
		CKPTMGT=" mgt: BL, background tail" ; #ckpt $CKPTMGT
		set +e      #Turn off failure checking - I don't know how to handle a failure return code from it.
		tail -f --lines=0 $NEWRULEFILE &
		VIEWTAILPID="$VIEWTAILPID $!"
		set -e
		echo $ENH Press ${KEY}ENTER${NORM} to return to the main menu
		CKPTMGT=" mgt: BL, press enter" ; #ckpt $CKPTMGT
		read JUNK || :
		CKPTMGT=" mgt: BL, killtail" ; #ckpt $CKPTMGT
		kill -TERM $VIEWTAILPID 2>/dev/null || : ; VIEWTAILPID=""
	  else
#FIXME - check all files
		trap : SIGINT	#make ctrl-c trap do nothing when running mason-decide
		#trap '{ echo Ctrl-C caught by mgt }' SIGINT		#Ctrl-C
		tail -q -f --lines=0 $PACKETLOGFILE $KILLFILES | $MASONEXE | $MASONDECIDE 10<&1 11>>$BASERULEFILE 12>>$NEWRULEFILE 13>>$NEWKILLFILE	#SUDO on tail?
		killrunningmason
		flushfirewall
		runfirewall STANDARD
		trap mgtcleanup SIGINT		#Ctrl-C
	  fi
																;;
	[Ee][Ll])
		showstate 'Mason-gui-text: Ending the learning process'
		if [ -n "`cat $NEWRULEFILE | sed -e 's/#.*//' | grep -v '^[[:space:]]*$' | grep -v '^export' || :`" ]; then		#formerly YYYY 0003
			wrap You still have unmerged rules in $NEWRULEFILE.  Would you like to process them now? ${KEY}Y${NORM}/${KEY}N${NORM}?
			if askYN ; then reprocess-newrules ; fi
		fi
		wrap End Learning.
		killrunningmason
		flushfirewall
		runfirewall STANDARD
																;;
	[Ee][Bb])
		wrap Edit Base rule file.
		showstate 'Mason-gui-text: Edit base rule file'
		if [ -z "$EDITOR" ]; then
			wrap ${WARN}EDITOR was not set in your environment. Please set it with something like \"export EDITOR=mcedit\"${NORM}
			sleep 10
		else
			cp -pf $BASERULEFILE $BASERULEFILE.bak || logfail $LINENO mason-gui-text: YYYY 0004
			addcounts $BASERULEFILE
			$EDITOR $BASERULEFILE || logfail $LINENO mason-gui-text: YYYY 0005
			if diff -bB <(sed -e 's/#.*//' -e 's/[[:space:]]//g' $BASERULEFILE | uniq || logfail $LINENO mason-gui-text: YYYY 0006) <(sed -e 's/#.*//' -e 's/[[:space:]]//g' $BASERULEFILE.bak | uniq || logfail $LINENO mason-gui-text: YYYY 0007) >/dev/null ; then
				wrap No changes made.
			else
				wrap Changes made, restarting firewall
				flushfirewall
				if [ -n "$MASONPID$MASONTAILPID" ]; then
					runfirewall LEARN
				else
					runfirewall STANDARD
				fi
			fi
		fi
																;;
	[Ee][Nn])		
		wrap Edit the new rule file.
		showstate 'Mason-gui-text: Edit new rule file'
		if [ -z "$EDITOR" ]; then
			wrap ${WARN}EDITOR was not set in your environment. Please set it with something like \"export EDITOR=mcedit\"${NORM}
			sleep 10
		else
			if [ -n "$MASONPID$MASONTAILPID" ]; then wrap Ending the learn process. ;										fi
			killrunningmason
			uniqrulefile $NEWRULEFILE
			$EDITOR $NEWRULEFILE || logfail $LINENO mason-gui-text: YYYY 0008
			if diff -bB <(sed -e 's/#.*//' -e 's/[[:space:]]//g' $NEWRULEFILE | uniq || logfail $LINENO mason-gui-text: YYYY 0009) <(sed -e 's/#.*//' -e 's/[[:space:]]//g' $NEWRULEFILE.bak | uniq || logfail $LINENO mason-gui-text: YYYY 0010) >/dev/null ; then
				wrap No changes made.
			else
				if [ -n "$MASONPID$MASONTAILPID" ]; then
					wrap Changes made, restarting firewall
					flushfirewall
					runfirewall LEARN
				else
					wrap Changes made, but no need to restart firewall as Mason is not running.
				fi
			fi
		fi
																;;
	[Mm][Rr])
		CKPTMGT=" mgt: start mr" ; #ckpt $CKPTMGT
		showstate 'Mason-gui-text: Merging rules'
		wrap Do you want to merge some, all, or none of the rules in this file to the base rule file? Enter \"${KEY}some${NORM}\", \"${KEY}all${NORM}\", or \"${KEY}none${NORM}\".
		unset WHATTOMERGE || :
		read WHATTOMERGE || :
		case $WHATTOMERGE in
		[Aa][Ll][Ll])
			CKPTMGT=" mgt: mr all" ; #ckpt $CKPTMGT
			cp -pf $BASERULEFILE $BASERULEFILE.bak || logfail $LINENO mason-gui-text: YYYY 0011
#FIXME - should we or not?
			#uniqrulefile $NEWRULEFILE
			echo "#Rules merged from the new rule file:"	>>$BASERULEFILE
			#cat $NEWRULEFILE | sed -e 's/^/#/'				>>$BASERULEFILE #Use this version if you want to force them commented
			cat $NEWRULEFILE								>>$BASERULEFILE || logfail $LINENO mason-gui-text: YYYY 0012
			cat /dev/null >$NEWRULEFILE || logfail $LINENO mason-gui-text: YYYY 0013
			wrap You will probably want to edit the base rule file next.
																	;;
		[Ss][Oo][Mm][Ee])
			CKPTMGT=" mgt: mr some" ; #ckpt $CKPTMGT
			wrap What string identifies the rules to merge?
			unset MERGEID || :
			read MERGEID || :
			wrap There are `grep "$MERGEID" $NEWRULEFILE | wc -l` rules with that ID, and `grep -v "$MERGEID" $NEWRULEFILE | wc -l` lines without it, `cat $NEWRULEFILE | wc -l` total.
			if echo "Do you want to continue ({KEY}Y${NORM}/${KEY}N${NORM})" ; askYN ; then
				CKPTMGT=" mgt: mr some continue" ; #ckpt $CKPTMGT
				cp -pf $BASERULEFILE $BASERULEFILE.bak || logfail $LINENO mason-gui-text: YYYY 0014
#FIXME - should we or not?
				#uniqrulefile $NEWRULEFILE
				CKPTMGT=" mgt: mr some continue header" ; #ckpt $CKPTMGT
				echo "#Rules merged from the new rule file:"	>>$BASERULEFILE
				#cat $NEWRULEFILE | grep "$MERGEID" | sed -e 's/^/#/'	>>$BASERULEFILE
				CKPTMGT=" mgt: mr some continue append to base" ; #ckpt $CKPTMGT
				cat $NEWRULEFILE | grep "$MERGEID"						>>$BASERULEFILE || :
				CKPTMGT=" mgt: mr some continue remove from new" ; #ckpt $CKPTMGT
				cat $NEWRULEFILE | grep -v "$MERGEID" >$NEWRULEFILE.tmp || :	#Grep returns false if we happen to merge everything.
				CKPTMGT=" mgt: mr some continue overwrite new" ; #ckpt $CKPTMGT
				cat $NEWRULEFILE.tmp >$NEWRULEFILE || :
				CKPTMGT=" mgt: mr some continue remove tmp" ; #ckpt $CKPTMGT
				rm -f $NEWRULEFILE.tmp || :
				wrap You will probably want to edit the base rule file next.
			else
				wrap Aborting merge.
			fi
																	;;
		esac
																;;
	[Cc][Ss])
		if [ -z "$EDITOR" ]; then
			wrap ${WARN}EDITOR was not set in your environment. Please set it with something like \"export EDITOR=mcedit\"${NORM}
			sleep 10
		else
			#wrap Here are the non-comment lines in $MASONCONF:
			#echo
			#cat $MASONCONF | sed -e 's/#.*//' | grep -v '^$' | uniq
			#echo
			#wrap Press ${KEY}enter${NORM} to edit this file
			#read JUNK
			showstate 'Mason-gui-text: Changing settings'
			cp -pf $MASONCONF ${MASONDIR}masonrc.bak || :
			$EDITOR $MASONCONF || logfail $LINENO mason-gui-text: YYYY 0015
			if diff -bB <(sed -e 's/#.*//' -e 's/[[:space:]]//g' $MASONCONF | uniq || logfail $LINENO mason-gui-text: YYYY 0016) <(sed -e 's/#.*//' -e 's/[[:space:]]//g' ${MASONDIR}masonrc.bak | uniq || logfail $LINENO mason-gui-text: YYYY 0017) >/dev/null ; then
				wrap No changes made.
				rm -f ${MASONDIR}masonrc.bak || :
			else
				if [ -f $MASONCONF ]; then
					. $MASONCONF
				fi
				checkconf
				if [ -n "$MASONPID$MASONTAILPID" ]; then
					wrap Changes made, signalling mason to reread configuration.
					kill -USR1 $MASONPID 2>/dev/null || :
				else
					wrap Changes made.
				fi
			fi
		fi
																;;
	[Ll][Cc])
		if ! type -path vlock >/dev/null 2>/dev/null ; then
			wrap ${WARN}The vlock utility is not on this system. It is required to be able to lock the console. Once installed, this option will be available again.${NORM}
			sleep 15
		else
			showstate 'Mason-gui-text: Locked console'
			if [ -n "$MASONPID$MASONTAILPID" ]; then
				#tail -f --lines=0 $PACKETLOGFILE $NEWRULEFILE &
				#FIXME - 'set +e' here?
				tail -f --lines=0 $NEWRULEFILE &
				VIEWTAILPID="$VIEWTAILPID $!"
			else
				wrap Mason is not currently learning, but I will lock this terminal anyways.
			fi	
			vlock || :
			if [ -n "$VIEWTAILPID" ]; then	kill -TERM $VIEWTAILPID 2>/dev/null || : ; VIEWTAILPID="" ;			fi
		fi
																;;
	[Qq])
		showstate 'Mason-gui-text: Exiting'
		if [ -n "`cat $NEWRULEFILE | sed -e 's/#.*//' | grep -v '^[[:space:]]*$' | grep -v '^export' || :`" ]; then		#formerly YYYY 0018
			echo -n $ENH You may still have unmerged rules in $NEWRULEFILE.  Would you like to process these now? ${KEY}Y${NORM}/${KEY}N${NORM}
			if askYN ; then reprocess-newrules ; fi
		fi
		EXITCONTROL="YES"
		wrap Exiting.  Returning to the standard firewall.
		if [ -n "$VIEWTAILPID" ]; then	kill -TERM $VIEWTAILPID 2>/dev/null || : ;	VIEWTAILPID="" ;			fi
		killrunningmason
		flushfirewall
		runfirewall STANDARD
																;;
	*)	wrap ${WARN}Unknown choice.  Please enter the one or two character code.${NORM}
																;;
	esac

	sleep 2
done

CKPTMGT=""
showstate ''

