#!/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, 2004 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 gotchas. 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/ . All configuration of this program is done in the /etc/masonrc file. This script should probably not be directly edited. - Bill Stearns The EOTEXT line is the end of the text and the start of the code. EOTEXT1 MASONDIR=${MASONDIR:-"/var/lib/mason/"} MASONCONF=${MASONCONF:-"/etc/masonrc"} MASONLIB=${MASONLIB:-"/var/lib/mason/masonlib"} if [ -f $MASONLIB ]; then . $MASONLIB else 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 exit fi #------------------------------------------------------------------------- # Start setting up. #------------------------------------------------------------------------- LOGCHAINSEXIST="yes" if [ "$DOCOMMAND" = "ipchains" ]; then ALLCHAINS="`/sbin/ipchains -L -n | grep '^Chain ' | awk '{print $2}'`" if [ -n "$NOLOGSUFFIX" ]; then for ONECHAIN in $ALLCHAINS ; do case $ONECHAIN in *${NOLOGSUFFIX}) : ;; *) # For each chain (except for the nolog chains themselves) check that a corresponding nolog chain exists. #Do not use -n - it does not work for this. if ! /sbin/ipchains -L ${ONECHAIN}${NOLOGSUFFIX} >/dev/null 2>/dev/null ; then #If nolog chain does not exist LOGCHAINSEXIST="no" echo The $ONECHAIN chain exists without a corresponding ${ONECHAIN}${NOLOGSUFFIX} >/dev/stderr echo chain. For the moment, the "nolog" feature will revert to sticking the rules >/dev/stderr echo at the top of the original chain. >/dev/stderr fi ;; esac done if [ "$LOGCHAINSEXIST" = "no" ]; then $NOLOGSUFFIX = "" ; fi fi fi LAST1="" ; LAST2="" ; LAST3="" ; LAST4="" ; LAST5="" ; CURRENT="" EXITMASON=${EXITMASON:-"NO"} trap sigexitscript SIGHUP trap loadconf SIGUSR1 #---------- Start of Main code ---------- echo "---- Mason firewall builder for Linux ----" >/dev/stderr echo "---- see http://www.pobox.com/~wstearns/mason/ for more info. ----" >/dev/stderr echo "---- William Stearns ----" >/dev/stderr loadconf SIGGED="NO" #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 for ONEIF in $DYNIF ; do cat </dev/stderr ;; esac #MESSPOL="$J9" #Unused. IF="$J10" case "$J11" in "PROTO=0") PROTO="ip" ;; "PROTO=1") PROTO="icmp" ;; "PROTO=2") PROTO="igmp" ;; "PROTO=3") PROTO="ggp" ;; "PROTO=6") PROTO="tcp" ;; "PROTO=12") PROTO="pup" ;; "PROTO=17") PROTO="udp" ;; "PROTO=22") PROTO="idp" ;; "PROTO=255") PROTO="raw" ;; *) echo Unknown protocol X${J11}X >/dev/stderr ;; esac SRC="$J12" ; DEST="$J13" SRCPORT=${SRC##*:} DESTPORT=${DEST##*:} #LFLAG="$J14" ; SFLAG="$J15" ; IFLAG="$J16" ; FFLAG="$J17" ; TFLAG="$J18" #Unused ISLOGLINE="YES" elif [ "$J5 $J6" = "kernel: IP" ]; then #Load variables from ipfwadm log entry case $J7 in #Formerly [ "`echo $J7 | cut -b 1-3`" = "fw-" ] fw-out|fw-in|fw-fwd) case $J7 in fw-out) DIR='output ' ; DIRLETTER='O' ;; fw-in) DIR='input ' ; DIRLETTER='I' ;; fw-fwd) DIR='forward' ; DIRLETTER='F' ;; *) echo Unknown direction X${J7}X >/dev/stderr ;; esac IF="$J9" PROTO=`echo $J10 | tr A-Z a-z` SRC="$J11" ; DEST="$J12" #MESSPOL="$J8" ; LFLAG="$J13" ; SFLAG="$J14" ; IFLAG="$J15" ; FOFLAG="$J16" ; TFLAG="$J17" ; DFFLAG="$J18" #Unused #Break up ipfwadmin's habit of gluing the icmp port onto the protocol. if [ "${PROTO%%/*}" = "icmp" ]; then SRCPORT=${PROTO##*/} PROTO="icmp" DESTPORT="" if [ "$DEBUG" = "YES" ]; then echo proto= $PROTO srcport= $SRCPORT destport= $DESTPORT >/dev/stderr ; fi else SRCPORT=${SRC##*:} DESTPORT=${DEST##*:} fi ISLOGLINE="YES" ;; esac fi if [ "$ISLOGLINE" = "YES" ]; then SRCIP=${SRC%%:*} DESTIP=${DEST%%:*} ACK=" " case ${#IF} in 0) IF="${IF} " ;; 1) IF="${IF} " ;; 2) IF="${IF} " ;; 3) IF="${IF} " ;; 4) IF="${IF} " ;; esac #Used to be: if [ ${#IF} -lt 5 ]; then IF=`echo "$IF " | cut -b 1-4` ; fi SRCHOST=`generalizeip ${SRCIP}` for ONESPARSE in $SSP ; do if [ "${SRCPORT}/$PROTO" = "$ONESPARSE" ]; then SRCHOST="`nameof ${SRCIP}`${SINGLEMACHSPEC}" ; fi done unset ONESPARSE for ONESPARSE in $SCP ; do if [ "${DESTPORT}/$PROTO" = "$ONESPARSE" ]; then SRCHOST="`nameof ${SRCIP}`${SINGLEMACHSPEC}" ; fi done unset ONESPARSE DESTHOST=`generalizeip ${DESTIP}` for ONESPARSE in $SSP ; do if [ "${DESTPORT}/$PROTO" = "$ONESPARSE" ]; then DESTHOST="`nameof ${DESTIP}`${SINGLEMACHSPEC}" ; fi done unset ONESPARSE for ONESPARSE in $SCP ; do if [ "${SRCPORT}/$PROTO" = "$ONESPARSE" ]; then DESTHOST="`nameof ${DESTIP}`${SINGLEMACHSPEC}" ; fi done unset ONESPARSE COMMENT2="" ; COMMENT="${CMNT}" ; CISCOSERVICE="" #Clean up protocol type and number fields, visualize source and dest port fields, set ack flag. #If port not in /etc/services and >=1024, generalize to "high port" if [ "$PROTO" != "icmp" ]; then serverportrange $SRCPORT $PROTO SRCSERVICE="$READABLEPORT" SRCCOMMENT="$PARTIALCOMMENT" serverportrange $DESTPORT $PROTO DESTSERVICE="$READABLEPORT" DESTCOMMENT="$PARTIALCOMMENT" if [ -n "$SRCSERVICE" ] && [ "$PROTO" = "tcp" ]; then #The ack flag should be set if port=tcp and source port is a server service. #The one tcpdump I've seen of an ftp connection seems to indicate that the #ftp-data connection is from the _server_ to the client - backwards. if [ "${SRCPORT}/$PROTO" != "20/tcp" ]; then ACK="-k" ; fi fi if [ -n "$SRCSERVICE" ] && [ -n "$DESTSERVICE" ]; then # Both source and destination ports are servers. Rare, but possible. SRCPORT=$SRCSERVICE DESTPORT=$DESTSERVICE ACK=" " #In this case, we can't tell in which direction the connection was made - no ack flag. #We leave TOS alone here - there's no way to tell on which to base it. #What the heck do we do with Cisco in this case? elif [ -n "$SRCSERVICE" ]; then # Source port is a server port. DESTPORT=`clientportrange $DESTPORT $SRCPORT ${PROTO}` settos $SRCPORT $SRCSERVICE $PROTO SRCPORT=$SRCSERVICE ; CISCOSERVICE=" eq $SRCPORT" elif [ -n "$DESTSERVICE" ]; then # Dest port is a server port. SRCPORT=`clientportrange $SRCPORT $DESTPORT ${PROTO}` settos $DESTPORT $DESTSERVICE $PROTO DESTPORT=$DESTSERVICE ; CISCOSERVICE=" eq $DESTPORT" else # Neither source nor dest is a server port. COMMENT2="${CMNT}${CMNT} S=`nameof ${SRCIP}`:${SRCPORT} D=`nameof ${DESTIP}`:${DESTPORT}" ORIGSRCPORT=$SRCPORT SRCPORT=`clientportrange $SRCPORT $DESTPORT ${PROTO}` DESTPORT=`clientportrange $DESTPORT $ORIGSRCPORT ${PROTO}` unset ORIGSRCPORT #TOS=" -t 0x01 0x08" ;; #Maximize throughput on the assumption that this is FTP data or irc dcc? #If we have a high port to high port connection (darn ftp and irc dcc), do _not_ generalize to anywhere if [ "$SRCPORT" = "1024:65535" ] && [ "$DESTPORT" = "1024:65535" ]; then if [ "$SRCHOST" = "0/0" ]; then SRCHOST="${SRCIP}${SINGLEMACHSPEC}" ; fi if [ "$DESTHOST" = "0/0" ]; then DESTHOST="${DESTIP}${SINGLEMACHSPEC}" ; fi fi fi if [ -n "$SRCCOMMENT" ]; then COMMENT="$COMMENT $SRCCOMMENT" ; fi if [ -n "$DESTCOMMENT" ]; then if [ "$SRCCOMMENT" != "$DESTCOMMENT" ]; then COMMENT="$COMMENT $DESTCOMMENT" ; fi fi COMMENT="$COMMENT (${DIRLETTER})" else #Handle ICMP comments #FIXME - handle subcode (in comment2) if dest != 0 case $SRCPORT in 0) COMMENT="${CMNT} Echo reply/icmp (${DIRLETTER})" ;; 3) COMMENT="${CMNT} Dest Unreach/icmp (${DIRLETTER})" ;; 4) COMMENT="${CMNT} Source Quench/icmp (${DIRLETTER})" ;; 5) COMMENT="${CMNT} Redirect/icmp (${DIRLETTER})" ;; 8) COMMENT="${CMNT} Echo req/icmp (${DIRLETTER})" ;; 11) COMMENT="${CMNT} Time exceeded/icmp (${DIRLETTER})" ;; 12) COMMENT="${CMNT} Parameter prob/icmp (${DIRLETTER})" ;; 13) COMMENT="${CMNT} Timestamp req/icmp (${DIRLETTER})" ;; 14) COMMENT="${CMNT} Timestamp reply/icmp (${DIRLETTER})" ;; 15) COMMENT="${CMNT} Info req/icmp (${DIRLETTER})" ;; 16) COMMENT="${CMNT} Info reply/icmp (${DIRLETTER})" ;; 17) COMMENT="${CMNT} Addr Mask req/icmp (${DIRLETTER})" ;; 18) COMMENT="${CMNT} Addr Mask reply/icmp (${DIRLETTER})" ;; #FIXME - include source and dest IPs for the following? *) COMMENT="${CMNT} unknown-${SRCPORT}/icmp (${DIRLETTER})" ;; esac CISCOSERVICE=" eq $SRCPORT" fi #if [ "$LINEHASDYNAMIC" = "YES" ]; then COMMENT="$COMMENT DynamicIP" ; fi #LINEHASDYNAMIC is not exported because generalizeip is a function. Not used. if [ "$DEBUG" = "YES" ]; then echo J1=${J1}, J2=${J2}, J3=${J3}, J4=${J4}, J5=${J5}, J6=${J6}, J7=${J7}, J8=${J8}, >/dev/stderr echo J9=${J9}, J10=${J10}, J11=${J11}, J12=${J12}, J13=${J13}, J14=${J14}, J15=${J15}, J16=${J16}, >/dev/stderr echo J17=${J17}, J18=${J18}, J19=${J19}, DIR=${DIR}, DIRLETTER=${DIRLETTER} >/dev/stderr echo MESSPOL=${MESSPOL}, IF=${IF}, PROTO=${PROTO}, SRC=${SRC}, DEST=${DEST}, LFLAG=${LFLAG} >/dev/stderr echo SFLAG=${SFLAG}, IFLAG=${IFLAG}, FOFLAG=${FOFLAG}, FFLAG=${FFLAG}, TFLAG=${TFLAG}, DFFLAG=${DFFLAG}, TAIL=${TAIL} >/dev/stderr echo Unused: DFFLAG, FFLAG, FOFLAG, IFLAG, LFLAG, MESSPOL, SFLAG, TFLAG >/dev/stderr fi #Actually create and implement the firewall command to display. Pad so rules line up. case $PROTO in ip|IP) PROTO="$PROTO " ;; tcp|TCP|udp|UDP|ggp|GGP|pup|PUP|idp|IDP|raw|RAW) PROTO="$PROTO " ;; esac DODISPLAY="YES" if [ "$ECHOCOMMAND" = "ipchains" ]; then if [ "$ACK" = " " ]; then ECHOACK=" " ; else ECHOACK="! -y" ; fi CURRENT="/sbin/ipchains -A $DIR -i $IF -p $PROTO $ECHOACK -s $SRCHOST $SRCPORT -d $DESTHOST ${DESTPORT}${TOS} -j $UCPOLICY" #TOS is either blank or has a leading space elif [ "$ECHOCOMMAND" = "ipfwadm" ]; then CURRENT="/sbin/ipfwadm -a $LCPOLICY -W $IF -${DIRLETTER} -P $PROTO $ACK -S $SRCHOST $SRCPORT -D $DESTHOST ${DESTPORT}${TOS}" elif [ "$ECHOCOMMAND" = "cisco" ]; then #FIXME handle comments to user for $IF, $DIRLETTER, fix "eq", TOS format? no forwarding rules (maybe in baserules?) #FIXME - is this screwing up the DOCOMMAND=ipchains parameters? if [ "$DIRLETTER" = "F" ]; then DODISPLAY="NO" ; echo "Forwarding rule skipped in cisco mode" >/dev/stderr elif [ "$IF" = "lo" ]; then DODISPLAY="NO" ; echo "Loopback interface skipped in cisco mode" >/dev/stderr else case "$LCPOLICY" in "reject"|"deny "|"deny") CISCOPOLICY="deny " ;; "accept") CISCOPOLICY="permit" ;; *) CISCOPOLICY="unknown" ;; esac if [ "$ACK" = " " ]; then ECHOACK=" " ; else ECHOACK="established" ; fi if [ -n "$TOS" ]; then CISCOTOS=" tos${TOS}" ; else CISCOTOS="" ; fi case $IF in eth0) CISCOIF="E0" ;; eth1) CISCOIF="E1" ;; eth2) CISCOIF="E2" ;; eth3) CISCOIF="E3" ;; ppp0) CISCOIF="S0" ;; ppp1) CISCOIF="S1" ;; ppp2) CISCOIF="S2" ;; ppp3) CISCOIF="S3" ;; tr0) CISCOIF="To0" ;; tr1) CISCOIF="To1" ;; tr2) CISCOIF="To2" ;; tr3) CISCOIF="To3" ;; *) CISCOIF="`echo "$IF" | sed -e 's/^eth/E/' -e 's/^ppp/S/' -e 's/^tr/To/'`" ;; esac CURRENT="access-list ${DIRLETTER}${CISCOIF} $CISCOPOLICY $PROTO $SRCHOST ${DESTHOST}${CISCOSERVICE}${CISCOTOS} $ECHOACK $LOG" fi fi # No need to handle ECHOCOMMAND=none :-) if [ `echo -n "$CURRENT" | wc -c` -lt 115 ]; then #We cannot use ${#CURRENT} because it counts multiple spaces as one. CURRENT=`echo "$CURRENT " | cut -b 1-120` #orig 110 chopped lines, 111 did not pad enough fi CURRENT="$CURRENT $COMMENT" if [ "$DEBUG" = "YES" ]; then echo current= "$CURRENT" >/dev/stderr ; 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) if [ "$HEARTBEAT" = "YES" ]; then echo -n "-" >/dev/stderr ; NEEDLF="YES" ; fi ;; *) if [ "$NEEDLF" = "YES" ]; then echo >/dev/stderr ; NEEDLF="NO" ; fi if [ "$DOBEEP" = "YES" ]; then echo -n -e "\a" >/dev/stderr ; fi if [ "$DODISPLAY" = "YES" ]; then case $ECHOCOMMAND in ipchains|ipfwadm|cisco) echo "$CURRENT $COMMENT2" ;; esac fi #Put the dynamic IP addresses in the current environment. for ONEIF in $DYNIF ; do eval ${ONEIF}ADDR=$(eval echo \${$(eval echo ${ONEIF}ADDR)}) #No, really. That's the IP address. done #Put a real rule in the rule chain so that we don't log it again. We need to use eval since ${xxxHOST} may be a $DYNIP #and may need to be evaluated to its real value. if [ "$DOCOMMAND" = "ipfwadm" ]; then eval "/sbin/ipfwadm -i $LCPOLICY -W $IF -${DIRLETTER} -P $PROTO $ACK -S $SRCHOST $SRCPORT -D $DESTHOST ${DESTPORT}${TOS}" elif [ "$DOCOMMAND" = "ipchains" ]; then if [ "$ACK" = " " ]; then DOACK=" " ; else DOACK="! -y" ; fi case $DIRLETTER in I) DIR="input${NOLOGSUFFIX}" ;; O) DIR="output${NOLOGSUFFIX}" ;; F) DIR="forward${NOLOGSUFFIX}" ;; esac #echo X${DIR}X >/dev/stderr eval "/sbin/ipchains -I $DIR 1 -i $IF -p $PROTO $DOACK -s $SRCHOST $SRCPORT -d $DESTHOST ${DESTPORT}${TOS} -j $UCPOLICY" fi # no need to handle DOCOMMAND=none :-) LAST5=$LAST4 LAST4=$LAST3 LAST3=$LAST2 LAST2=$LAST1 LAST1=$CURRENT ;; esac #Debug if [ "$DEBUG" = "YES" ]; then echo src= $SRCIP $SRCPORT dest= $DESTIP $DESTPORT >/dev/stderr echo if= $IF proto= $PROTO >/dev/stderr echo >/dev/stderr fi #if debugging fi #if ISLOGLINE #Get the next log entry and start over. unset ACK COMMENT DEST DESTHOST DESTIP DESTPORT DIR DOACK ECHOACK IF ISLOGLINE \ LINEHASDYNAMIC PROTO SRC SRCHOST SRCIP SRCPORT TAIL #unset DFFLAG FFLAG FOFLAG IFLAG LFLAG MESSPOL SFLAG TFLAG #These are unused. TOS="" A7=$O7 ; A8=$O8 ; A9=$O9 ; A10=$O10 ; A11=$O11 ; A12=$O12 ; A13=$O13 O7=$J7 ; O8=$J8 ; O9=$J9 ; O10=$J10 ; O11=$J11 ; O12=$J12 ; O13=$J13 read J1 J2 J3 J4 J5 J6 J7 J8 J9 J10 J11 J12 J13 J14 J15 J16 J17 J18 J19 #Keep reading until a line with different firewall values is found. while { [ "$O7" = "$J7" ] && [ "$O8" = "$J8" ] && [ "$O9" = "$J9" ] && [ "$O10" = "$J10" ] && \ [ "$O11" = "$J11" ] && [ "$O12" = "$J12" ] && [ "$O13" = "$J13" ] ; } || { [ "$A7" = "$J7" ] && [ "$A8" = "$J8" ] && [ "$A9" = "$J9" ] && [ "$A10" = "$J10" ] && \ [ "$A11" = "$J11" ] && [ "$A12" = "$J12" ] && [ "$A13" = "$J13" ] ; } || { [ "$SIGGED" = "YES" ] ; } ; do if [ "$SIGGED" = "YES" ]; then SIGGED="NO" else if [ "$HEARTBEAT" = "YES" ]; then echo -n "." >/dev/stderr ; NEEDLF="YES" ; fi A7=$O7 ; A8=$O8 ; A9=$O9 ; A10=$O10 ; A11=$O11 ; A12=$O12 ; A13=$O13 O7=$J7 ; O8=$J8 ; O9=$J9 ; O10=$J10 ; O11=$J11 ; O12=$J12 ; O13=$J13 fi read J1 J2 J3 J4 J5 J6 J7 J8 J9 J10 J11 J12 J13 J14 J15 J16 J17 J18 J19 done done if [ "$NEEDLF" = "YES" ]; then echo >/dev/stderr ; NEEDLF="NO" ; fi if [ "$EXITMASON" = "YES" ]; then echo Mason is exiting because of a SIGHUP or EXITMASON=YES. >/dev/stderr else echo Mason is exiting because of an end of input data. >/dev/stderr fi #unset J1 J2 J3 J4 J5 J6 J7 J8 J9 J10 J11 J12 J13 J14 J15 J16 J17 J18 J19 \ # O7 O8 O9 O10 O11 O12 O13 \ # A7 A8 A9 A10 A11 A12 A13 \