#!/bin/bash

cat <<EOTEXT1 >/dev/null
	(The above line allows me to put the documentation right in the
script... Cool, eh?)

>>>>>>>>>>>>>>>If you read nothing else, please read this<<<<<<<<<<<<<<<<

	This program offers an aid to creating firewall rules.  It offers
ABSOLUTELY NO intelligence in deciding what should be allowed or
disallowed.  It has ABSOLUTELY NO ability to understand your security
policy and implement it.  YOU are responsible for reviewing the rules and
massaging them to fit your needs.
	While the documentation in mason.txt attempts to provide some
general guidelines on how to use Mason, please remember:  the author has
no knowledge of what you want your firewall to do and has not tailored the
documentation or program to specially fit your needs.  If there is ever a
discrepancy between your needs and the program output or your needs and
the documentation, the program and/or documentation are _dead_ _wrong_. 


Copyleft:
    Mason interactively creates a Linux packet filtering firewall.
    Copyright (C) 1998 William Stearns <wstearns@pobox.com>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

    The author can also be reached at:
        William Stearns
email:  wstearns@pobox.com              (preferred)
web:    http://www.pobox.com/~wstearns
snail:  544 Winchester Place
        Colchester VT, 05446, USA


	This code is entirely owned by William Stearns
(wstearns@pobox.com) and has no relation to any employer or employer
sponsored project.  

------------------------------ Mason ------------------------------
	The Mason script interactively builds a (fire)wall on a Linux
machine. For more details about how this is done, please see mason.txt,
which gives background, theory of operation, a quick start, and additional
documentation on firewalls and firewall gotcha's. 
	mason.txt and related documentation should have been installed to
/usr/doc/mason-{version}/ .  If they are missing or you would like to make
sure you have the latest version, please go to
http://www.pobox.com/~wstearns/mason/ . 


- Bill Stearns

The EOTEXT line is the end of the text and the start of the code. 
EOTEXT1


#-------------------------------------------------------------------------
# User customizable options below
#-------------------------------------------------------------------------

#NAMECACHE _could_ be /etc/hosts, but this was really intended to be a
#local cache for Mason only.  This really should be in some directory like
#/var/lib/mason.
NAMECACHE="/tmp/morehosts"

#If you change this, make sure an identical value is placed in 
#mason_policy as well.
POLICYFILE="/tmp/current_policy"

#What to use if there is no policyfile or its contents are not accept, 
#reject, or deny.  Please, no typos...
DEFAULTPOLICY="accept"

# "YES" to debug, anything else = dont
DEBUG="NO"

# "YES" = echo command to STDOUT, "NO" = dont
ECHOCOMMAND="YES"

# "YES" = actually run the command, "NO" = dont.  NO is useful if you're 
# not running Mason as root or are running Mason on some machine other
# than the actual operating firewall.
DOCOMMAND="YES"

#-------------------------------------------------------------------------
# Probably not a good idea to edit below this line...
#-------------------------------------------------------------------------

#Beeps to be added later
#DOBEEP="YES"		# "YES" = beep at user with new rule, "NO" = dont

LAST1=""
LAST2=""
LAST3=""
LAST4=""
LAST5=""
CURRENT=""

#Not currently used...
ipeq () {
    I1A=`echo ${1} | gawk --field-separator '.' '{ print $1 }'`
    I1B=`echo ${1} | gawk --field-separator '.' '{ print $2 }'`
    I1C=`echo ${1} | gawk --field-separator '.' '{ print $3 }'`
    I1D=`echo ${1} | gawk --field-separator '.' '{ print $4 }'`

    I2A=`echo ${2} | gawk --field-separator '.' '{ print $1 }'`
    I2B=`echo ${2} | gawk --field-separator '.' '{ print $2 }'`
    I2C=`echo ${2} | gawk --field-separator '.' '{ print $3 }'`
    I2D=`echo ${2} | gawk --field-separator '.' '{ print $4 }'`

    if [ I1A -eq I2A -a I1B -eq I2B -a I1C -eq I2C -a I1D -eq I2D ]; then
	return 0 #True
    else
	return 1 #False
    fi
}



#Hmmm... I _hate_ overwriting /etc/passwd...
if [ -L ${NAMECACHE} ]; then
    rm -f ${NAMECACHE}
fi

#Create the name cache file if it doesn't exist
if [ ! -e ${NAMECACHE} ]; then
    touch ${NAMECACHE}
    #This is a security-related program.  I don't want to let people know 
    #who we're even talking to.
    chmod og-rwx ${NAMECACHE}
fi


#Get the first log entry
unset ACK COMMENT DEST DFFLAG DESTHOST DESTIP DESTPORT DIR FOFLAG IF IFLAG J1 \
J2 J3 J4 J5 J6 LFLAG MESSPOL PROTO SFLAG SRC SRCHOST SRCIP SRCPORT TAIL TFLAG
read J1 J2 J3 J4 J5 J6 DIR MESSPOL IF PROTO SRC DEST \
     LFLAG SFLAG IFLAG FOFLAG TFLAG DFFLAG TAIL

while [ -n "${J1}" ]; do
#Debug statements
    if [ "${DEBUG}" = "YES" ]; then
	echo J1=${J1}, J2=${J2}, J3=${J3}, J4=${J4}
	echo J5=${J5}, J6=${J6}, DIR=${DIR}
	echo MESSPOL=${MESSPOL}, IF=${IF}, PROTO=${PROTO}
	echo SRC=${SRC}, DEST=${DEST}, LFLAG=${LFLAG}
	echo SFLAG=${SFLAG}, IFLAG=${IFLAG}, FOFLAG=${FOFLAG}
	echo TFLAG=${TFLAG}, DFFLAG=${DFFLAG}, TAIL=${TAIL}
    fi

#Only do the work if the line is a firewalling log entry.
    if [ "${J5} ${J6}" = "kernel: IP" ] && [ "`echo ${DIR} | cut -b 1-3`" = "fw-" ]; then

        case ${DIR} in
        fw-out)	DIR='O'							;;
        fw-in)	DIR='I'							;;
        fw-fwd)	DIR='F'							;;
        *)	echo Unknown direction X${DIR}X				;;
        esac

           PROTO=`echo ${PROTO} | tr A-Z a-z`
           SRCIP=${SRC%%:*}
         SRCPORT=${SRC##*:}
          DESTIP=${DEST%%:*}
        DESTPORT=${DEST##*:}
	     ACK="  "

	if [ ${#IF} -lt 5 ]; then
	    IF=`echo "${IF}     " | cut -b 1-4`
	fi

#Use the namecache file, /etc/hosts, and finally "host" command to look
#up names for source and destination addresses.
	SRCHOST=`cat ${NAMECACHE} /etc/hosts | egrep "^${SRCIP}[^0-9]" | tail --lines=1 | awk '{print $2}'`
	if [ -n "${SRCHOST}" ]; then
	    SRCIP=${SRCHOST}
	elif [ "${SRCPORT}" != "53" -a "${DESTPORT}" != "53" ]; then
#FIXME - split pattern matching onto its own line and do with bash?
	    SRCHOST=`host ${SRCIP} | grep '^Name' | sed -e 's/.* //'`
	    if [ -n "${SRCHOST}" ]; then
		echo -e "${SRCIP}\t${SRCHOST}" >>${NAMECACHE}
	        SRCIP=${SRCHOST}
	    fi
	fi

	DESTHOST=`cat ${NAMECACHE} /etc/hosts | egrep "^${DESTIP}[^0-9]" | tail --lines=1 | awk '{print $2}'`
	if [ -n "${DESTHOST}" ]; then
	    DESTIP=${DESTHOST}
	elif [ "${SRCPORT}" != "53" -a "${DESTPORT}" != "53" ]; then
	    DESTHOST=`host ${DESTIP} | grep '^Name' | sed -e 's/.* //'`
	    if [ -n "${DESTHOST}" ]; then
		echo -e "${DESTIP}\t${DESTHOST}" >>${NAMECACHE}
	        DESTIP=${DESTHOST}
	    fi
	fi

#Clean up protocol type and number fields.
	if [ "${PROTO%%/*}" = "icmp" ]; then
	    SRCPORT=${PROTO##*/}
	    PROTO="icmp"
	    DESTPORT=""
	    if [ "${DEBUG}" = "YES" ]; then echo proto= ${PROTO} srcport= ${SRCPORT} destport= ${DESTPORT} ; fi

	    case ${SRCPORT} in
	    0)		COMMENT="# Echo reply/icmp(${DIR})"		;;
	    3)		COMMENT="# Dest Unreach/icmp(${DIR})"		;;
	    4)		COMMENT="# Source Quench/icmp(${DIR})"		;;
	    5)		COMMENT="# Redirect/icmp(${DIR})"		;;
	    8)		COMMENT="# Echo req/icmp(${DIR})"		;;
	    11)		COMMENT="# Time exceeded/icmp(${DIR})"		;;
	    12)		COMMENT="# Parameter prob/icmp(${DIR})"		;;
	    13)		COMMENT="# Timestamp req/icmp(${DIR})"		;;
	    14)		COMMENT="# Timestamp reply/icmp(${DIR})"	;;
	    15)		COMMENT="# Info req/icmp(${DIR})"		;;
	    16)		COMMENT="# Info reply/icmp(${DIR})"		;;
	    17)		COMMENT="# Addr Mask req/icmp(${DIR})"		;;
	    18)		COMMENT="# Addr Mask reply/icmp(${DIR})"	;;
	    *)		COMMENT="# unknown-${SRCPORT}/icmp(${DIR})"	;;
	    esac
	else
	    COMMENT="#"
#If port not in /etc/services and >=1024, generalize to "high port"
	    MATCHSERVICE=`cat /etc/services | grep "${SRCPORT}/${PROTO}"`
	    if [ -n "${MATCHSERVICE}" ]; then
		if [ "${PROTO}" = "tcp" ]; then
#The ack flag should be set if port=tcp and source port is a server service.
		    ACK="-k"
		fi
	        SRCPORT=`echo ${MATCHSERVICE} | awk '{print $1}'`
	    else
	        case ${SRCPORT} in
	        [0-9]|[1-9][0-9]|[1-9][0-9][0-9]|10[0-1][0-9]|102[0-3])	: ;;
	        *)			SRCPORT="1024:65535"		;;
	        esac
	    fi
	    if [ "${SRCPORT}" != "1024:65535" ]; then
		COMMENT="${COMMENT} ${SRCPORT}/${PROTO}"
	    fi

	    MATCHSERVICE=`cat /etc/services | grep "${DESTPORT}/${PROTO}"`
	    if [ -n "${MATCHSERVICE}" ]; then
	        DESTPORT=`echo ${MATCHSERVICE} | awk '{print $1}'`
	    else
	        case ${DESTPORT} in
	        [0-9]|[1-9][0-9]|[1-9][0-9][0-9]|10[0-1][0-9]|102[0-3]) : ;;
	        *)			DESTPORT="1024:65535"		;;
	        esac
	    fi
	    if [ "${DESTPORT}" != "1024:65535" ]; then
	        if [ "${SRCPORT}" != "${DESTPORT}" ]; then
		    COMMENT="${COMMENT} ${DESTPORT}/${PROTO}"
		fi
	    fi
	    COMMENT="${COMMENT} (${DIR})"
	fi


#Pad so rules line up.
	case ${PROTO} in
	tcp)		PROTO="tcp "					;;
	udp)		PROTO="udp "					;;
	esac

#Figure out what policy to use.  This is checked at every rule so user can 
#change the policy on the fly.
	if [ -f ${POLICYFILE} ]; then
	    POLICY=`cat ${POLICYFILE} | tr A-Z a-z | head --lines=1 | sed -e 's/[^acdejnprty]//g'`
	    case ${POLICY} in
	    accept|reject|deny)	:					;;
	    *)			POLICY=${DEFAULTPOLICY}			;;
	    esac
	else
	    POLICY=${DEFAULTPOLICY}
	fi
	if [ "${POLICY}" = "deny" ]; then POLICY="deny  " ; fi

#Actually create and implement the firewall command.
        CURRENT="/sbin/ipfwadm -a ${POLICY} -W ${IF} -${DIR} -P ${PROTO} ${ACK} -S ${SRCIP}/32 ${SRCPORT} -D ${DESTIP}/32 ${DESTPORT}"
	if [ ${#CURRENT} -lt 111 ]; then
	    CURRENT=`echo "${CURRENT}                                         " | cut -b 1-110`
	fi
	CURRENT="${CURRENT} ${COMMENT}"

	if [ "${DEBUG}" = "YES" ]; then echo current= ${CURRENT} ; fi

#Don't do anything if this is the same as one of the last 5 rules.  This 
#reduces the occurence of repeated rules showing up.
	case ${CURRENT} in
	${LAST1})	:	;;
	${LAST2})	:	;;
	${LAST3})	:	;;
	${LAST4})	:	;;
	${LAST5})	:	;;
	*)
	    #Beeps later...
	    #if [ "${DOBEEP}" = "YES" ]; then echo -e "\a" ; fi
            if [ "${ECHOCOMMAND}" = "YES" ]; then 
                echo "${CURRENT}"
            fi

            if [ "${DOCOMMAND}" = "YES" ]; then 
#               /sbin/ipfwadm -i ${POLICY} -W ${IF} -${DIR} -P ${PROTO} ${ACK} -S ${SRCIP}/32 ${SRCPORT} -D ${DESTIP}/32 ${DESTPORT}
                /sbin/ipfwadm -i ${POLICY} -W ${IF} -${DIR} -P ${PROTO} ${ACK} -S ${SRC%%:*}/32 ${SRCPORT} -D ${DEST%%:*}/32 ${DESTPORT}
            fi
	    LAST5=${LAST4}
	    LAST4=${LAST3}
	    LAST3=${LAST2}
	    LAST2=${LAST1}
	    LAST1=${CURRENT}	;;
	esac

#Debug
	if [ "${DEBUG}" = "YES" ]; then 
	    echo src= ${SRCIP} ${SRCPORT} dest= ${DESTIP} ${DESTPORT}
	    echo if= ${IF} proto= ${PROTO}
	    echo
	fi
    fi

#Get the next log entry and start over.
    unset ACK COMMENT DEST DFFLAG DESTHOST DESTIP DESTPORT DIR FOFLAG IF IFLAG J1 \
    J2 J3 J4 J5 J6 LFLAG MESSPOL PROTO SFLAG SRC SRCHOST SRCIP SRCPORT TAIL TFLAG
    read J1 J2 J3 J4 J5 J6 DIR MESSPOL IF PROTO SRC DEST \
         LFLAG SFLAG IFLAG FOFLAG TFLAG DFFLAG TAIL
done
