#!/bin/bash cat </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 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. if [ "${SRCPORT}/${PROTO}" != "20/tcp" ]; then ACK="-k" fi 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]) : ;; #FIXME generalize ssh client (1023 and down) to 1000:1023 ? *) 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