#!/bin/bash

### GLOBAL VARIABLE INITIALIZATIONS ###
acl_type=""; source_ip=""; source_wcm=""; dest_ip=""; dest_wcm=""; eq_port=""; port=""; acl_string=""; named_acl=""
estab=""; protocol=""; numbered_acl=0; 
Q="Quitting..."; D="---------------------------------------------------------------"

function Permit_Deny {

	permit_deny=""
	while [ ! $permit_deny ]
	do
		echo; echo "Do you want to:"
		echo "(A) permit"; echo "(B) deny"; echo "(Z) quit the script"
		echo; read -p "Choice: " input

		case $input in
			A | a) permit_deny="permit" ;;		
			B | b) permit_deny="deny" ;;
			Z | z) permit_deny="quit" ;;		
			*) permit_deny="" ;;
		esac
	done
	return
}

function IP_Address {

	local octet1; local octet2; local octet3; local octet4
	local ip_address=""; local start_string=""
	local end_string=" address (x.x.x.x, where x is a number between 0 and 255)?"
	local note="If first octet is 255, the script will assume it is a subnet mask and convert it for you."

	case $1 in
                sip) start_string="Source IP "; note="" ;;
                swcm) start_string="Source wildcard mask "	;;
                dip) start_string="Destination IP "; note="" ;;
                dwcm) start_string="Destination wildcard mask " ;;
		*) #ERROR CONDITION
			echo; echo "ERROR: Value sent to function 'IP_Address' is not sip, swcm, dip, or dwcm."; echo
			ERROR=1
			return
		;;
        esac

	while [ ! $ip_address ]
	do
		echo; echo $start_string"(any = all source IPs)"$end_string

		if [[ $note ]]
		then
			echo $note
		fi

		read -p "" ip_address

		if [[ $ip_address != "any" ]]
		then
			Check_Octets $ip_address
			if [[ Octets_OK -eq 0 ]]
			then
				ip_address=""
			fi
		fi
	done

	octet=(0,0,0,0)
	case $1 in
		swcm | dwcm)

			if [[ `echo $ip_address | cut -d. -f1` == "255" ]]
			then
				for (( i=0 ; i<4; i++))
				do
					octet[$i]=$((255-`echo $ip_address | cut -d. -f$((i+1))`))
				done

				ip_address=${octet[0]}"."${octet[1]}"."${octet[2]}"."${octet[3]}
			fi
		;;
	esac

	case $1 in
		sip) source_ip=$ip_address ;;
		swcm) source_wcm=$ip_address ;;
		dip) dest_ip=$ip_address ;;
		dwcm) dest_wcm=$ip_address ;;
	esac

	return
}

function Choose_Protocol {

	protocol=""
	while [ ! $protocol ]
	do
		echo
		echo "Please choose the protocol you are filtering:"
		echo "(A) tcp"
		echo "(B) udp"
		echo "(C) icmp"
		echo "(D) ip (used to permit, or deny, all ports)"
		echo "(Z) quit this script"
		echo""; read -p "Choice: " input
	
		case $input in
			A | a) protocol="tcp" ;;
			B | b) protocol="udp" ;;
			C | c) protocol="icmp" ;;
			D | d) protocol="ip" ;;
			Z | z) protocol="quit" ;;
			*) protocol="" ;;
		esac
	done
	return
}

function Choose_Port  {

	port=""; eq_port=""

	if [[ $protocol != "ip" ]]
	then
		while [ ! $port ]
		do
			echo
	
	                if [[ $protocol == "icmp" ]]
	                then
				port=""
	                        while [ ! $port ]
	                        do
					echo "Using "$protocol", choose from:"
	                                echo "(A) echo"; echo "(B) echo-reply"; echo "(Z) quit the script"
	                                echo""; read -p "Choice: " input
	
	                                case $input in
						A | a) port="echo" ;;
	                                        B | b) port="echo-reply" ;;
	                                        Z | z) port="quit"; return ;;
	                                        *) port="" ;;
	                                 esac
				done
			else
				while [ ! $eq_port ]
	                        do
	                        	echo "Do you want the port to be made:"
	                                echo "(A) equal to 'eq'"
	                                echo "(B) less than 'lt'"
	                                echo "(C) greater than 'gt'"
	                                echo "(D) NOT equal to 'neq'"
	                                echo "(Z) quit the script"
	                                echo""; read -p "Choice: " input

	                                case $input in
	                                	A | a) eq_port="eq" ;;
	                                        B | b) eq_port="lt" ;;
	                                        C | c) eq_port="gt" ;;
	                                        D | d) eq_port="neq" ;;
	                                        Z | z) eq_port="quit"; return ;;
	                                        *) eq_port="" ;;
					esac
				done

				while [ ! $port ]
	                        do
	                        	echo; echo "Which port do you want to "$permit_deny"?"; echo
					echo "PORT		PROTOCOL	COMMENT		CISCO KEYWORD"
	                                echo $D; echo "(A) 20		FTP      	data		ftp-data"
	                                echo $D; echo "(B) 21		FTP      	control		ftp"
	                                echo $D; echo "(C) 22		SSH                             ssh"
	                                echo $D; echo "(D) 23		Telnet				telnet"
	                                echo $D; echo "(E) 25		SMTP"
					echo $D; echo "(F) 49		TACACS				tacacs"
	                                echo $D; echo "(G) 53		DNS               		domain"
	                                echo $D; echo "(H) 67		DHCP     	bootps          bootps"
	                                echo $D; echo "(I) 68		DHCP     	bootpc          bootpc"
	                                echo $D; echo "(J) 69		TFTP                            tftp"          
	                                echo $D; echo "(K) 80		HTTP            		www"
	                                echo $D; echo "(L) 110		POPv3"
	                                echo $D; echo "(M) 119		NNTP"
	                                echo $D; echo "(N) 123		NTP                             ntp"
	                                echo $D; echo "(O) 143		IMAP"
	                                echo $D; echo "(P) 161		SNMP                            snmp"
	                                echo $D; echo "(Q) 194		IRC"
	                                echo $D; echo "(R) 220		IMAPv3"
	                                echo $D; echo "(S) 443		HTTPS 		SSL             https"
	                                echo $D; echo "(T) *** Enter your own port number ***"
	                                echo $D; echo "(Z) quit the script"
	                                echo $D; echo; read -p "Choice: " input

	                                case $input in
	                                	A | a) port="20" ;;
	                                        B | b) port="21" ;;
	                                        C | c) port="22" ;;
	                                        D | d) port="23" ;;
	                                        E | e) port="25" ;;
						F | f) port="49" ;;
	                                        G | g) port="53" ;;
	                                        H | h) port="67" ;;
	                                        I | i) port="68" ;;
	                                        J | j) port="69" ;;
	                                        K | k) port="80" ;;
	                                        L | l) port="110" ;;
	                                        M | m) port="119" ;;
	                                        N | n) port="123" ;;
	                                        O | o) port="143" ;;
	                                        P | p) port="161" ;;
	                                        Q | q) port="194" ;;
	                                        R | r) port="220" ;;
	                                        S | s) port="443" ;;
	                                        T | t)
							port=""
	                                                while [ ! $port ]
	                                                do
	                                                	echo
	                                                        read -p "Port number to "$permit_deny"? " port

								if [[ $port -lt 1 || $port -gt 65536 ]]
								then
									echo; echo "Port must be between 1 and 65536."
									port=""
								fi
	                                                done
						;;
	                                        Z | z) port="quit"; return ;;
	                                        *) port="" ;;
					esac
				done
			fi
		done
	fi
	return
}

function Check_Octets {

	local field; local length; local i; local string; local check_num=1; local report

	string=$1

	for (( i=1; i<5; i++ ))
	do
		field=`echo $string | cut -d. -f$i`
		length=`echo ${#string}`

		case $field in 
			*[!0-9]*) check_num=0 ;;
		esac

		if [[ $length -lt 7 || $field -lt 0 || $field -gt 255 || $check_num -eq 0 ]]
		then
			Octets_OK=0
			report="ERROR: Incorrect format entered ("$1"). "
			echo

			if [[ length -lt 7 ]]
			then
				echo $report"[Entry is too short: minimum 7 characters required.]"
			fi

			if [[ $field -lt 0 || $field -gt 255 ]]
                        then
                                echo $report"[Octets must be between 0 and 255.]"
                        fi

			if [[ $check_num -eq 0 ]]
                        then
                                echo $report"[Each octect must only be numbers {0-255}.]"
                        fi

			return 
		fi
	done

	Octets_OK=1
	return
}

function Single_Host {

	while [ ! $single_host ]
	do
		echo; read -p "Is this a single host? (y/n) " single_host
		single_host=${single_host^^}

		case $single_host in
			Y) 
				case $1 in
					1) source_ip="host "$source_ip ;;
					2) dest_ip="host "$dest_ip ;;
					*) #ERROR CONDITION
			                        echo
						echo "ERROR: Value sent to function 'Single_Host' is not 1 or 2."
			                        echo
			                        ERROR=1
			                        return
			                ;;
				esac
				return
			;;
			N) return ;;
			*) single_host="" ;;
		esac
	done
	return
}

function Show_ACL {

	input=""
        while [ ! $input ]
        do
        	echo; echo "ACL = "$acl_string; echo; read -p "Is this correct? (y/n) " input

	        case $input in
        		Y | y)
	                	echo; echo "Writing (appending) ACL to acl.txt."
	                        echo $acl_string >> acl.txt
	                ;;
			N | n) echo; echo "ACL is NOT being writted to acl.txt."  ;;
                        *) input="" ;;
		esac
	done

        echo; echo "Here is "$acl_type" ACL "$1" so far..."
        echo $D; cat acl.txt; echo $D
	return
}

function Check_To_Continue {

	input=""
        while [ ! $input ]
        do
        	echo; read -p "Enter another line for "$acl_type" ACL "$1"? (y/n) " input

                case $input in
                	Y | y) more="" ;;
                        N | n) more="n" ;;
                        *) input="" ;;
                esac
        done
	return
}

### START SCRIPT ###

clear
echo "Welcome to Tracy Awesome Access Control List (ACL) Generator for Cisco Routers (TAACLG4CR) [tm]. v1.0"
echo
echo "Answer the following questions, and an ACL will automagically be generated for you!"

rm -f acl.txt

while [ ! $acl_type ]
do
	echo; echo "Do you want to make a standard or extended ACL?"
	echo "(A) standard"; echo "(B) extended"; echo "(Z) quit the script"
	echo; read -p "Choice: " input

	case $input in
		A | a) acl_type="standard" ;;
		B | b) acl_type="extended" ;;
		Z | z) echo; echo $Q; echo; return ;;
		*) acl_type="" ;;
	esac
done

named_or_numbered=0
while [ $named_or_numbered -eq 0 ]
do

	echo; echo "Is the "$acl_type" ACL named or numbered?"
	echo "(A) named"; echo "(B) numbered"; echo "(Z) quit the script"
	echo; read -p "Choice: " input
	
	case $input in
		A | a) named_or_numbered=1 ;;
		B | b) named_or_numbered=2 ;;
		Z | z) echo; echo $Q; echo; return ;;
		*) named_or_numbered=0 ;;
	esac

done

case $named_or_numbered in
	1) #named

		while [ ! $named_acl ]
		do
			echo; read -p "Name for the "$acl_type" ACL (the script will make it all uppercase)? " named_acl
		done

		named_acl=${named_acl^^}; named_acl=`echo ${named_acl// /_}`
		acl_string="ip access-list "$acl_type" "$named_acl
		echo $acl_string >> acl.txt
	;;
	2) #numbered

		if [[ $acl_type == "standard" ]]
                then
                        # standed numbered
                        min=1; max=99
                else
                        # extended numbered
                        min=100; max=199
                fi

                while [[ $numbered_acl -lt $min || $numbered_acl -gt $max ]]
                do
                        echo; read -p "Number for the "$acl_type" ACL ("$min"-"$max")? " numbered_acl

                        if [[ $numbered_acl -lt $min || $numbered_acl -gt $max ]]
                        then
                                echo; echo $acl_type" ACLs numbers must be between "$min" and "$max"..."; echo
                        fi
                done
	;;
esac

more=""
while [ ! $more ]
do
	ERROR=0
	
	Permit_Deny
        if [[ $permit_deny == "quit" ]]
        then
                echo; echo $Q; echo; return
        fi

	IP_Address sip
	if [[ $ERROR -eq 1 ]]
	then
		return
	fi

	if [[ $source_ip != "any" ]]
        then
                single_host=""
                Single_Host 1
		if [[ $ERROR -eq 1 ]]
                then
	                return
                fi

        fi

        source_wcm=""
        if [[ $single_host != "Y" && $source_ip != "any" ]]
        then
		IP_Address swcm
		if [[ $ERROR -eq 1 ]]
	        then
        		return
	        fi
	fi

	if [[ $acl_type == "extended" ]]
	then
		IP_Address dip
		if [[ $ERROR -eq 1 ]]
                then
        	        return
                fi

                if [[ $dest_ip != "any" ]]
                then
	        	single_host=""
                        Single_Host 2
			if [[ $ERROR -eq 1 ]]
	                then
        	        	return
                	fi
                fi

                dest_wcm=""
                if [[ $single_host != "Y" && $source_ip != "any" ]]
                then
	        	IP_Address dwcm
			if [[ $ERROR -eq 1 ]]
	                then
        	        	return
                	fi
		fi

                Choose_Protocol
                if [[ $protocol == "quit" ]]
                then
	        	echo; echo $Q; echo; return
                fi

                Choose_Port
		if [[ $port == "quit" || $eq_port == "quit" ]]
                then
                	echo; echo $Q; echo; return
                fi
		
		estab=""
		if [[ $protocol == "tcp" ]]
		then
			input=""
			while [ ! $input ]
			do
				echo; echo "Is this an 'established' connection? [TCP traffic must originate from"
				read -p "inside your network first, not from an external network.] (y/n) "  input
				case $input in
					Y | y) estab="established" ;;
					N | n) estab="" ;;
					*) input=""
				esac
			done
		fi
	fi
		
	case $named_or_numbered in
               	1) #named
		        acl_string=$permit_deny" "$protocol" "$source_ip" "$source_wcm" "$dest_ip" "$dest_wcm" "$eq_port" "$port" "$estab
	                Show_ACL $named_acl
	                Check_To_Continue $named_acl
        	;;
	        2) #numbered
	                acl_string="access-list "$numbered_acl" "$permit_deny" "$protocol" "$source_ip" "$source_wcm" "$dest_ip" "$dest_wcm" "$eq_port" "$port" "$estab
	                Show_ACL $numbered_acl
	                Check_To_Continue $numbered_acl
	        ;;
		*) #ERROR CONDITION
                        echo
			echo "ERROR: variable named_or_numbered is not '1' (named) or '2' (numbered) as expected."
                        echo
                        return
                ;;
	esac
done

case $named_or_numbered in
	1) #named
		case $acl_type in
	                standard) echo "deny any" >> acl.txt ;;
                        extended) echo "deny ip any any" >> acl.txt  ;;
                        *) #ERROR CONDITION
        	                echo; echo "ERROR: variable acl_type is not 'standard' or 'extended' as expected."; echo
                                return
                        ;;
                esac

	;;
	2) #numbered
		output_string="access-list "$numbered_acl

		case $acl_type in
			standard) output_string=$output_string" deny any" ;;
			extended) output_string=$output_string" deny ip any any" ;;
			*) #ERROR CONDITION
				echo; echo "ERROR: variable acl_type is not 'standard' or 'extended' as expected."; echo
				return
			;;
		esac
			
		echo $output_string >> acl.txt
	;;
	*) #ERROR CONDITION
		echo; echo "ERROR: variable named_or_numbered is not '1' (named) or '2' (numbered) as expected."; echo
                return
	;;
esac

echo "" >> acl.txt
echo "---------------------- Interface / Line Assignments ----------------------" >> acl.txt
echo "" >> acl.txt
echo "NOTE: Assumes that you are already logged into the router and are global configuration mode." >> acl.txt
echo "" >> acl.txt

if [[ $numbered_acl -eq 0 ]]
then
	numbered_acl=""
fi

more=""
while [ ! $more ]
do
	input=""
	while [ ! $input ]
	do
		echo; echo "What will the "$acl_type" ACL "$named_acl$numbered_acl" be applied to?"; echo
		echo "(A) physical interface"
		echo "(B) vty line(s)"
		echo "(Z) quit this script"
		echo; read -p "Choice: " input
	
		case $input in
			A | a) 
				apply_acl="ip access-group "$named_acl$numbered_acl
				config_mode="interface"
				
				int_or_line=""
			        while [ ! $int_or_line ]
			        do
			                echo; read -p "Enter interface (e.g.: fa0/0, s0/0/1, or gi1/2): " int_or_line
			        done
			;;
			B | b)
				apply_acl="access-class "$named_acl$numbered_acl
				config_mode="line vty"
				
				int_or_line=""
                                while [ ! $int_or_line ]
                                do
                                        echo
					read -p "Enter vty line(s), 0 - 15. (E.g.: 0, 4, or 0 4 [for a range]): " int_or_line

					if [[ $int_or_line -lt 0 || $int_or_line -gt 15 ]]
					then
						echo; echo "vty lines can only be bewteen 0 and 15."
						int_or_line=""
					fi
                                done

			;;
			Z | z) echo; echo $Q; echo; return ;;
			*) input="" ;;
		esac
	done

	input=""
	while [ ! $input ]
	do
		echo
		echo "How is the "$acl_type" ACL "$named_acl$numbered_acl" to be applied to the interface ot line?"
		echo
		echo "(A) in 	(incoming to the router -- before the routing table is processed)"
		echo "(B) out	(outgoing from the router -- after the routing table is processed)"
		echo "(Z) quit this script"
		echo; read -p "Choice: " input
	
		case $input in
			A | a) in_or_out="in" ;; 
			B | b) in_or_out="out" ;; 
			Z | z) echo""; echo $Q; echo; return ;;
			*) input="" ;;
		esac
	done

	echo $config_mode" "$int_or_line >> acl.txt
	echo $apply_acl" "$in_or_out >> acl.txt

	input=""
	while [ ! $input ]
	do
		echo; read -p "Apply to another interface? (y/n) " input
		
		case $input in
			Y | y) more="" ;; N | n) more="n" ;; *) input="" ;;
		esac
	done
done

clear
echo "Here is the acl.txt file:"; echo
cat acl.txt
echo
