#!/bin/sh
# Copyright (c) 2023-2024 Eric Fahlgren <eric.fahlgren@gmail.com>
# SPDX-License-Identifier: GPL-2.0
# shellcheck disable=SC2039,SC2155  # "local" not defined in POSIX sh

set -o nounset

alias log='logger -s -t "snort-rules[$$]" -p "info"'

download_rules() {
	# Further information:
	#    https://www.snort.org/products#rule_subscriptions
	#    https://www.snort.org/oinkcodes
	#
	# Also, what to do about "subscription" vs Talos_LightSPD rules when subbed?
	# Add a "use_rules" list or option or something?
	local oinkcode=$(uci -q get snort.snort.oinkcode)

	local conf_dir=$(uci -q get snort.snort.config_dir || echo "/etc/snort")
	local rules_dir="$conf_dir/rules"
	local data_dir=$(uci -q get snort.snort.temp_dir || echo "/var/snort.d")
	local data_tar="$data_dir/rules.tar.gz"

	local new_rules
	local rules_file
	local archive_loc

	# Make sure everything exists.
	[ -d "$data_dir" ] || mkdir -p "$data_dir"

	if $testing ; then
		log "Generating testing rules..."
		archive_loc="testing-rules"
		new_rules="$data_dir/$archive_loc"
		rm -fr "${new_rules:?}"
		mkdir -p "$new_rules"
		rules_file="$new_rules/testing.rules"
		{
			echo 'alert icmp any any <> any any (msg:"TEST ALERT ICMP v4"; icode:0; itype: 8; sid:99010;)'
			echo 'alert icmp any any <> any any (msg:"TEST ALERT ICMP v6"; icode:0; itype:33; sid:99011;)'
			echo 'alert icmp any any <> any any (msg:"TEST ALERT ICMP v6"; icode:0; itype:34; sid:99012;)'
		} >> "$rules_file"

	else
		if [ -z "$oinkcode" ]; then
			# If you do not have a subscription, then we use the community rules:
			log "Downloading community rules..."
			url="https://www.snort.org/downloads/community/snort3-community-rules.tar.gz"
			archive_loc="snort3-community-rules"

		else
			# If you have a subscription and its corresponding oinkcode, use this:
			#
			# 'snortver' is the version number of the snort executable in use on your
			# router.
			#
			# Ideally, the 'snort --version' output would work, but OpenWrt builds
			# are often between (or, more likely, newer than) those listed on the
			# snort.org downloads page.
			#
			# So instead, we define it manually to be the value just before the
			# installed version.  Look on https://www.snort.org/advisories/ and
			# select the most recent date.  On that page, find the closest version
			# number preceding your installed version and modify the hard-coded
			# value below (for example, installed is 31600 then use 31470):

			#snortver=$(snort --version | awk '/Version/ {print gensub("\\.", "", "", $NF)}')
			snortver=31470

			log "Downloading subscription rules..."
			url="https://www.snort.org/rules/snortrules-snapshot-$snortver.tar.gz?oinkcode=$oinkcode"
			# Non-community tar contains many "*.rules" file, we only care about
			# the one directory.
			archive_loc="rules"
		fi

		wget "$url" -O "$data_tar" 2>&1 | log || exit 1

		old_rules="$data_dir/old.rules"
		if $backup; then
			rm -fr "${old_rules:?}"
			mkdir -p "$old_rules"

			for rules_file in "$rules_dir"/*; do
				# Before we overwrite with the new download.
				log "Stashing '$rules_file' to '$old_rules/'..."
				mv -f "$rules_file" "$old_rules/"
			done
		fi

		log "Unpacking '$data_tar'..."
		tar xzvof "$data_tar" "$archive_loc" -C "$data_dir" | log || exit 1

		# Get rid of the non-rule files and aggregator.
		new_rules="$data_dir/$archive_loc"
		find "$new_rules" \( -iname 'includes.rules' -o ! -iname '*.rules' -type f \) -exec rm '{}' \;

		# Old unfinished experiment with diffing old and new rules.
		#for rules_file in "$new_rules"/*; do
		#blah blah
		#if [ -e "$old_rules" ] && ! cmp -s "$new_rules" "$old_rules" ; then
		#	diff "$new_rules" "$old_rules" 2>&1 | log
		#fi
	fi


	mkdir -p "$conf_dir"
	rm -fr "${rules_dir:?}"
	if $persist; then
		mv -f "$new_rules" "$rules_dir"
	else
		ln -s "$new_rules" "$rules_dir"
	fi

	log "Snort rules loaded, restart snort now."
}

#-------------------------------------------------------------------------------

testing=false
persist=false
backup=false

usage() {
	local msg="$1"
	[ -n "$msg" ] && printf "ERROR: %s\n\n" "$msg"

	cat <<USAGE
Usage:

  $0 [-b/--backup] [-t/--testing] [-p/--persist]

  -b = Attempt to copy current rules to '\$temp_dir/old.rules/' before
       installing new rules.

  -t = Don't download any rules, instead create synthetic testing rules.

  -p = Move the downloaded rules to '\$conf_dir/rules/' (usually '/etc/snort/'),
       so that they persist across reboots and sysupgrades.  If you do not
       specify this option, then the rules are stored in '\$temp_dir', and a
       symbolic link is created to them.

After running 'snort-rules', you should run 'snort-mgr check -v' to verify
that there are no errors.

USAGE
	exit 1
}

while [ "${1:-}" ]; do
	case "$1" in
		-h|--help)
			usage
		;;
		-b|--backup)
			backup=true
		;;
		-t|--testing)
			testing=true
		;;
		-p|--persist)
			persist=true
		;;
		*)
			usage "'$1' is not a valid option"
		;;
	esac
	shift
done

download_rules
