Blame SPECS/ipset.start-stop

Packit Bot c1d7e5
#!/bin/sh
Packit Bot c1d7e5
#
Packit Bot c1d7e5
# ipset      Start and stop ipset firewall sets
Packit Bot c1d7e5
#
Packit Bot c1d7e5
# config: /etc/sysconfig/ipset-config
Packit Bot c1d7e5
Packit Bot c1d7e5
IPSET_BIN=/usr/sbin/ipset
Packit Bot c1d7e5
IPSET_CONFIG=/etc/sysconfig/ipset-config
Packit Bot c1d7e5
IPSET_DATA_COMPAT=/etc/sysconfig/ipset
Packit Bot c1d7e5
IPSET_DATA_COMPAT_BACKUP=${IPSET_DATA_COMPAT}.save
Packit Bot c1d7e5
IPSET_DATA_DIR=/etc/sysconfig/ipset.d
Packit Bot c1d7e5
IPSET_DATA_DIR_BACKUP=${IPSET_DATA_DIR}.save
Packit Bot c1d7e5
IPSET_DATA_SAVED_FLAG=${IPSET_DATA_DIR}/.saved
Packit Bot c1d7e5
IPSET_LOCK=/run/ipset.lock
Packit Bot c1d7e5
IPSET_RUN=/run/ipset.run
Packit Bot c1d7e5
CLEAN_FILES=""
Packit Bot c1d7e5
Packit Bot c1d7e5
trap "rm -rf \${CLEAN_FILES}" EXIT
Packit Bot c1d7e5
Packit Bot c1d7e5
info() {
Packit Bot c1d7e5
	echo "ipset: ${*}" >&2
Packit Bot c1d7e5
}
Packit Bot c1d7e5
Packit Bot c1d7e5
warn() {
Packit Bot c1d7e5
	echo "<4>ipset: ${*}" >&2
Packit Bot c1d7e5
}
Packit Bot c1d7e5
Packit Bot c1d7e5
err() {
Packit Bot c1d7e5
	echo "<3>ipset: ${*}" >&2
Packit Bot c1d7e5
}
Packit Bot c1d7e5
Packit Bot c1d7e5
[ -x ${IPSET_BIN} ] || { err "Cannot execute ${IPSET_BIN}"; exit 1; }
Packit Bot c1d7e5
Packit Bot c1d7e5
# Source ipset configuration
Packit Bot c1d7e5
# shellcheck source=ipset-config
Packit Bot c1d7e5
[ -f ${IPSET_CONFIG} ] && . ${IPSET_CONFIG}
Packit Bot c1d7e5
Packit Bot c1d7e5
set -f
Packit Bot c1d7e5
Packit Bot c1d7e5
lock() {
Packit Bot c1d7e5
	CLEAN_FILES="${CLEAN_FILES} ${IPSET_LOCK}"
Packit Bot c1d7e5
	until mkdir ${IPSET_LOCK} 2>/dev/null; do :; done
Packit Bot c1d7e5
}
Packit Bot c1d7e5
Packit Bot c1d7e5
save() {
Packit Bot c1d7e5
	fail=0
Packit Bot c1d7e5
Packit Bot c1d7e5
	# Make backups of existing configuration first, if any
Packit Bot c1d7e5
	[ -d ${IPSET_DATA_DIR} ] && mv -Tf ${IPSET_DATA_DIR} ${IPSET_DATA_DIR_BACKUP}
Packit Bot c1d7e5
	[ -f ${IPSET_DATA_COMPAT} ] && mv -Tf ${IPSET_DATA_COMPAT} ${IPSET_DATA_COMPAT_BACKUP}
Packit Bot c1d7e5
Packit Bot c1d7e5
	rm -f ${IPSET_DATA_SAVED_FLAG}
Packit Bot c1d7e5
Packit Bot c1d7e5
	# Save each set in a separate file
Packit Bot c1d7e5
	mkdir -p ${IPSET_DATA_DIR}
Packit Bot c1d7e5
	chmod 0700 ${IPSET_DATA_DIR}
Packit Bot c1d7e5
	IFS="
Packit Bot c1d7e5
"
Packit Bot c1d7e5
	for set in $(${IPSET_BIN} list -n -t); do
Packit Bot c1d7e5
		# Empty name allowed, use ".set" as suffix. 'ipset save' doesn't
Packit Bot c1d7e5
		# quote set names with spaces: if we have a space in the name,
Packit Bot c1d7e5
		# work around this by quoting it ourselves in the output.
Packit Bot c1d7e5
		# shellcheck disable=SC2003 # No POSIX equivalent to expr index
Packit Bot c1d7e5
		if expr index "${set}" " " >/dev/null; then
Packit Bot c1d7e5
			:> "${IPSET_DATA_DIR}/${set}.set"
Packit Bot c1d7e5
			for line in $(${IPSET_BIN} save "${set}"); do
Packit Bot c1d7e5
				create=0
Packit Bot c1d7e5
				echo "${line}" | grep -q "^create " && create=1
Packit Bot c1d7e5
				if [ $create -eq 1 ]; then
Packit Bot c1d7e5
					line=${line#create *}
Packit Bot c1d7e5
				else
Packit Bot c1d7e5
					line=${line#add *}
Packit Bot c1d7e5
				fi
Packit Bot c1d7e5
				line=${line#${set} *}
Packit Bot c1d7e5
				set="$(echo "${set}" | sed 's/"/\\"/g')"
Packit Bot c1d7e5
				if [ $create -eq 1 ]; then
Packit Bot c1d7e5
					echo "create \"${set}\" ${line}" >> "${IPSET_DATA_DIR}/${set}.set"
Packit Bot c1d7e5
				else
Packit Bot c1d7e5
					echo "add \"${set}\" ${line}" >> "${IPSET_DATA_DIR}/${set}.set"
Packit Bot c1d7e5
				fi
Packit Bot c1d7e5
			done
Packit Bot c1d7e5
		else
Packit Bot c1d7e5
			${IPSET_BIN} save "${set}" > "${IPSET_DATA_DIR}/${set}.set" || fail=1
Packit Bot c1d7e5
		fi
Packit Bot c1d7e5
		[ -f "${IPSET_DATA_DIR}/${set}.set" ] && chmod 600 "${IPSET_DATA_DIR}/${set}.set"
Packit Bot c1d7e5
		[ $fail -eq 1 ] && err "Cannot save set ${set}" && unset IFS && return 1
Packit Bot c1d7e5
	done
Packit Bot c1d7e5
	touch ${IPSET_DATA_SAVED_FLAG} || { unset IFS; return 1; }
Packit Bot c1d7e5
	unset IFS
Packit Bot c1d7e5
Packit Bot c1d7e5
	# Done: remove backups
Packit Bot c1d7e5
	rm -rf ${IPSET_DATA_DIR_BACKUP}
Packit Bot c1d7e5
	rm -rf ${IPSET_DATA_COMPAT_BACKUP}
Packit Bot c1d7e5
Packit Bot c1d7e5
	return 0
Packit Bot c1d7e5
}
Packit Bot c1d7e5
Packit Bot c1d7e5
# Generate a grep regexp matching abbreviated command forms. E.g., for create:
Packit Bot c1d7e5
# \(c\|cr\|cre\|crea\|creat\|create\)
Packit Bot c1d7e5
cmd_short_expr() {
Packit Bot c1d7e5
	out=
Packit Bot c1d7e5
	cmd_len=1
Packit Bot c1d7e5
	while [ "${cmd_len}" -le "${#1}" ]; do
Packit Bot c1d7e5
		[ -z "${out}" ] && out='\(' || out="${out}"'\|'
Packit Bot c1d7e5
		# shellcheck disable=SC2003 # No POSIX equivalent to expr substr
Packit Bot c1d7e5
		out="${out}$(expr substr "${1}" 1 "${cmd_len}")"
Packit Bot c1d7e5
		cmd_len=$((cmd_len + 1))
Packit Bot c1d7e5
	done
Packit Bot c1d7e5
	echo "${out}"'\)'
Packit Bot c1d7e5
}
Packit Bot c1d7e5
Packit Bot c1d7e5
ipset_restore() {
Packit Bot c1d7e5
	file="${1}"
Packit Bot c1d7e5
Packit Bot c1d7e5
	retfile="$(mktemp -q /tmp/ipset.XXXXXX)"
Packit Bot c1d7e5
	CLEAN_FILES="${CLEAN_FILES} ${retfile}"
Packit Bot c1d7e5
Packit Bot c1d7e5
	# If restore fails due to invalid lines, remove them and retry
Packit Bot c1d7e5
	while ! restore_err="$( (${IPSET_BIN} -f "${file}" -! restore 2>&1; echo $? >"${retfile}") | head -n1; exit "$(cat "${retfile}")" )"; do
Packit Bot c1d7e5
		warn "${restore_err}"
Packit Bot c1d7e5
		case ${restore_err#*: } in
Packit Bot c1d7e5
		"No command specified"*)
Packit Bot c1d7e5
			line="$(grep -m1 -n "^${restore_err##* }" "${file}")"
Packit Bot c1d7e5
			line="${line%:*}"
Packit Bot c1d7e5
			;;
Packit Bot c1d7e5
		"Missing second mandatory argument to command "*)
Packit Bot c1d7e5
			cmd="${restore_err##* }"
Packit Bot c1d7e5
			cmd_expr="$(cmd_short_expr "${cmd}")"
Packit Bot c1d7e5
			line="$(grep -n '^'"${cmd_expr}" "${file}" | grep -m1 -v '^[0-9]\+\:'"${cmd_expr}"'[[:blank:]]\+[^[:blank:]]\+[[:blank:]]\+[^[:blank:]]\+')"
Packit Bot c1d7e5
			line="${line%:*}"
Packit Bot c1d7e5
			;;
Packit Bot c1d7e5
		"Missing mandatory argument to command "*)
Packit Bot c1d7e5
			cmd="${restore_err##* }"
Packit Bot c1d7e5
			cmd_expr="$(cmd_short_expr "${cmd}")"
Packit Bot c1d7e5
			line="$(grep -n '^'"${cmd_expr}" "${file}" | grep -m1 -v '^[0-9]\+\:'"${cmd_expr}"'[[:blank:]]\+[^[:blank:]]\+')"
Packit Bot c1d7e5
			line="${line%:*}"
Packit Bot c1d7e5
			;;
Packit Bot c1d7e5
		"Command "*"is invalid in restore mode"*)
Packit Bot c1d7e5
			restore_err_cmd="${restore_err#*: }"
Packit Bot c1d7e5
			restore_err_cmd="${restore_err_cmd#*\`}"
Packit Bot c1d7e5
			restore_err_cmd="${restore_err_cmd%%\'*}"
Packit Bot c1d7e5
			cmd="${restore_err_cmd##* }"
Packit Bot c1d7e5
			cmd_expr="$(cmd_short_expr "${cmd}")"
Packit Bot c1d7e5
			line="$(grep -m1 -ne '^'"${cmd_expr}"'[[:blank:]]\+' -e '^'"${restore_err_cmd}"'$' "${file}")"
Packit Bot c1d7e5
			line="${line%:*}"
Packit Bot c1d7e5
			;;
Packit Bot c1d7e5
		"Error in line "*)
Packit Bot c1d7e5
			line="${restore_err%: *}"
Packit Bot c1d7e5
			line="${line##* }"
Packit Bot c1d7e5
			;;
Packit Bot c1d7e5
		*)
Packit Bot c1d7e5
			rm "${retfile}"
Packit Bot c1d7e5
			CLEAN_FILES="${CLEAN_FILES%* ${retfile}}"
Packit Bot c1d7e5
			return 1
Packit Bot c1d7e5
			;;
Packit Bot c1d7e5
		esac
Packit Bot c1d7e5
Packit Bot c1d7e5
		[ -z "${line}" ] && return 1
Packit Bot c1d7e5
Packit Bot c1d7e5
		warn "Skipped invalid entry: $(sed "${line}q;d" "${file}")"
Packit Bot c1d7e5
		sed -i -e "${line}d" "${file}"
Packit Bot c1d7e5
Packit Bot c1d7e5
		[ -s "${file}" ] || return 1
Packit Bot c1d7e5
	done
Packit Bot c1d7e5
Packit Bot c1d7e5
	rm "${retfile}"
Packit Bot c1d7e5
	CLEAN_FILES="${CLEAN_FILES%* ${retfile}}"
Packit Bot c1d7e5
}
Packit Bot c1d7e5
Packit Bot c1d7e5
load() {
Packit Bot c1d7e5
	if [ -f ${IPSET_DATA_SAVED_FLAG} ]; then
Packit Bot c1d7e5
		# If we have a cleanly saved directory with all sets, we can
Packit Bot c1d7e5
		# delete any left-overs and use it
Packit Bot c1d7e5
		rm -rf ${IPSET_DATA_DIR_BACKUP}
Packit Bot c1d7e5
		rm -f ${IPSET_DATA_COMPAT_BACKUP}
Packit Bot c1d7e5
	else
Packit Bot c1d7e5
		# If sets weren't cleanly saved, restore from backups
Packit Bot c1d7e5
		[ -d ${IPSET_DATA_DIR_BACKUP} ] && rm -rf ${IPSET_DATA_DIR} && mv -Tf ${IPSET_DATA_DIR_BACKUP} ${IPSET_DATA_DIR}
Packit Bot c1d7e5
		[ -f ${IPSET_DATA_COMPAT_BACKUP} ] && rm -f ${IPSET_DATA_COMPAT} && mv -Tf ${IPSET_DATA_COMPAT_BACKUP} ${IPSET_DATA_COMPAT}
Packit Bot c1d7e5
	fi
Packit Bot c1d7e5
Packit Bot c1d7e5
	if [ ! -d ${IPSET_DATA_DIR} ] && [ ! -f ${IPSET_DATA_COMPAT} ]; then
Packit Bot c1d7e5
		info "No existing configuration available, none loaded"
Packit Bot c1d7e5
		touch ${IPSET_RUN}
Packit Bot c1d7e5
		return
Packit Bot c1d7e5
	fi
Packit Bot c1d7e5
Packit Bot c1d7e5
	# Merge all sets into temporary file
Packit Bot c1d7e5
	merged="$(mktemp -q /tmp/ipset.XXXXXX)"
Packit Bot c1d7e5
	CLEAN_FILES="${CLEAN_FILES} ${merged}"
Packit Bot c1d7e5
	chmod 600 "${merged}"
Packit Bot c1d7e5
	set +f
Packit Bot c1d7e5
	if [ -d ${IPSET_DATA_DIR} ]; then
Packit Bot c1d7e5
		# Copy create commands from each saved set first, then the rest:
Packit Bot c1d7e5
		# list:set entries depend on other sets, so make sure they all
Packit Bot c1d7e5
		# get created first
Packit Bot c1d7e5
		for f in "${IPSET_DATA_DIR}"/*; do
Packit Bot c1d7e5
			[ "${f}" = "${IPSET_DATA_DIR}/*" ] && break
Packit Bot c1d7e5
			[ -f "${f}" ] || continue
Packit Bot c1d7e5
			grep '^c' "${f}" >> "${merged}"
Packit Bot c1d7e5
		done
Packit Bot c1d7e5
		for f in "${IPSET_DATA_DIR}"/*; do
Packit Bot c1d7e5
			[ "${f}" = "${IPSET_DATA_DIR}/*" ] && break
Packit Bot c1d7e5
			[ -f "${f}" ] || continue
Packit Bot c1d7e5
			grep -v '^c' "${f}" >> "${merged}"
Packit Bot c1d7e5
		done
Packit Bot c1d7e5
	fi
Packit Bot c1d7e5
	set -f
Packit Bot c1d7e5
	[ -f ${IPSET_DATA_COMPAT} ] && cat ${IPSET_DATA_COMPAT} >> "${merged}"
Packit Bot c1d7e5
Packit Bot c1d7e5
	# Drop sets that aren't in saved data, mark conflicts with existing sets
Packit Bot c1d7e5
	conflicts=""
Packit Bot c1d7e5
	IFS="
Packit Bot c1d7e5
"
Packit Bot c1d7e5
	for set in $(${IPSET_BIN} list -n -t); do
Packit Bot c1d7e5
		grep -q "^create ${set} " "${merged}" && conflicts="${conflicts}|${set}" && continue
Packit Bot c1d7e5
Packit Bot c1d7e5
		# We can't destroy the set if it's in use, flush it instead
Packit Bot c1d7e5
		if ! ${IPSET_BIN} destroy "${set}" 2>/dev/null; then
Packit Bot c1d7e5
			${IPSET_BIN} flush "${set}"
Packit Bot c1d7e5
		fi
Packit Bot c1d7e5
	done
Packit Bot c1d7e5
	unset IFS
Packit Bot c1d7e5
	conflicts="${conflicts#|*}"
Packit Bot c1d7e5
Packit Bot c1d7e5
	# Common case: if we have no conflicts, just restore in one shot
Packit Bot c1d7e5
	if [ -z "${conflicts}" ]; then
Packit Bot c1d7e5
		if ! ipset_restore "${merged}"; then
Packit Bot c1d7e5
			err "Failed to restore configured sets"
Packit Bot c1d7e5
			exit 1
Packit Bot c1d7e5
		fi
Packit Bot c1d7e5
		rm "${merged}"
Packit Bot c1d7e5
		CLEAN_FILES="${CLEAN_FILES%* ${merged}}"
Packit Bot c1d7e5
		touch ${IPSET_RUN}
Packit Bot c1d7e5
		return
Packit Bot c1d7e5
	fi
Packit Bot c1d7e5
Packit Bot c1d7e5
	# Find a salt for md5sum that makes names of saved sets unique
Packit Bot c1d7e5
	salt=0
Packit Bot c1d7e5
	while true; do
Packit Bot c1d7e5
		unique=1
Packit Bot c1d7e5
		IFS="
Packit Bot c1d7e5
"
Packit Bot c1d7e5
		for set in $(${IPSET_BIN} list -n -t); do
Packit Bot c1d7e5
			if grep -q "^create $(echo "${salt}${set}" | md5sum | head -c31) " "${merged}"; then
Packit Bot c1d7e5
				unique=0
Packit Bot c1d7e5
				break
Packit Bot c1d7e5
			fi
Packit Bot c1d7e5
		done
Packit Bot c1d7e5
		unset IFS
Packit Bot c1d7e5
		[ ${unique} -eq 1 ] && break
Packit Bot c1d7e5
		salt=$((salt + 1))
Packit Bot c1d7e5
	done
Packit Bot c1d7e5
Packit Bot c1d7e5
	# Add sets, mangling names for conflicting sets
Packit Bot c1d7e5
	mangled="$(mktemp -q /tmp/ipset.XXXXXX)"
Packit Bot c1d7e5
	CLEAN_FILES="${CLEAN_FILES} ${mangled}"
Packit Bot c1d7e5
	chmod 600 "${mangled}"
Packit Bot c1d7e5
Packit Bot c1d7e5
	awk '/^(add|create) ('"${conflicts}"')/ { printf "%s ",$1; system("echo '${salt}'" $2 " | md5sum | head -c31"); $1=""; $2=""; print; next} {print}' "${merged}" > "${mangled}"
Packit Bot c1d7e5
	if ! ipset_restore "${mangled}"; then
Packit Bot c1d7e5
		err "Failed to restore configured sets"
Packit Bot c1d7e5
		exit 1
Packit Bot c1d7e5
	fi
Packit Bot c1d7e5
Packit Bot c1d7e5
	rm "${mangled}"
Packit Bot c1d7e5
	CLEAN_FILES="${CLEAN_FILES%* ${mangled}}"
Packit Bot c1d7e5
Packit Bot c1d7e5
	# Swap and delete old sets
Packit Bot c1d7e5
	IFS='|'
Packit Bot c1d7e5
	for set in ${conflicts}; do
Packit Bot c1d7e5
		mangled="$(echo "${salt}${set}" | md5sum | head -c31)"
Packit Bot c1d7e5
		if ! ${IPSET_BIN} swap "${set}" "${mangled}" 2>/dev/null; then
Packit Bot c1d7e5
			# This fails if set types are different: try to destroy
Packit Bot c1d7e5
			# existing set
Packit Bot c1d7e5
			if ! ${IPSET_BIN} destroy "${set}" 2>/dev/null; then
Packit Bot c1d7e5
				# Conflicting set is in use, we can only warn
Packit Bot c1d7e5
				# and flush the existing set
Packit Bot c1d7e5
				err "Cannot load set \"${set}\", set with same name and conflicting type in use"
Packit Bot c1d7e5
				${IPSET_BIN} flush "${set}"
Packit Bot c1d7e5
				${IPSET_BIN} destroy "${mangled}"
Packit Bot c1d7e5
			else
Packit Bot c1d7e5
				${IPSET_BIN} rename "${mangled}" "${set}"
Packit Bot c1d7e5
			fi
Packit Bot c1d7e5
		else
Packit Bot c1d7e5
			${IPSET_BIN} destroy "${mangled}"
Packit Bot c1d7e5
		fi
Packit Bot c1d7e5
	done
Packit Bot c1d7e5
	unset IFS
Packit Bot c1d7e5
Packit Bot c1d7e5
	rm "${merged}"
Packit Bot c1d7e5
	CLEAN_FILES="${CLEAN_FILES%* ${merged}}"
Packit Bot c1d7e5
	touch ${IPSET_RUN}
Packit Bot c1d7e5
}
Packit Bot c1d7e5
Packit Bot c1d7e5
cleanup() {
Packit Bot c1d7e5
	${IPSET_BIN} flush || err "Failed to flush sets"
Packit Bot c1d7e5
Packit Bot c1d7e5
	# Try to destroy all sets at once. This will fail if some are in use,
Packit Bot c1d7e5
	# destroy all the other ones in that case
Packit Bot c1d7e5
	${IPSET_BIN} destroy 2>/dev/null && return
Packit Bot c1d7e5
	IFS="
Packit Bot c1d7e5
"
Packit Bot c1d7e5
	for set in $(${IPSET_BIN} list -n -t); do
Packit Bot c1d7e5
		if ! ${IPSET_BIN} destroy "${set}"; then
Packit Bot c1d7e5
			err "Failed to destroy set ${set}"
Packit Bot c1d7e5
		fi
Packit Bot c1d7e5
	done
Packit Bot c1d7e5
	unset IFS
Packit Bot c1d7e5
}
Packit Bot c1d7e5
Packit Bot c1d7e5
stop() {
Packit Bot c1d7e5
	[ -f ${IPSET_RUN} ] || { info "Not running"; return 0; }
Packit Bot c1d7e5
	[ "${IPSET_SAVE_ON_STOP}" = "yes" ] && { save || err "Failed to save sets"; }
Packit Bot c1d7e5
Packit Bot c1d7e5
	# Nothing to stop if the ip_set module is not loaded
Packit Bot c1d7e5
	lsmod | grep -q "^ip_set " || { info "Not running"; rm ${IPSET_RUN}; return 0; }
Packit Bot c1d7e5
Packit Bot c1d7e5
	# If the xt_set module is in use, then iptables is using ipset, so
Packit Bot c1d7e5
	# refuse to stop the service
Packit Bot c1d7e5
	if mod="$(lsmod | grep ^xt_set)"; then
Packit Bot c1d7e5
		if [ "$(echo "${mod}" | tr -s ' ' | cut -d' ' -f3)" != "0" ]; then
Packit Bot c1d7e5
			err "Current iptables configuration requires ipset" && return 1
Packit Bot c1d7e5
		fi
Packit Bot c1d7e5
	fi
Packit Bot c1d7e5
Packit Bot c1d7e5
	cleanup
Packit Bot c1d7e5
Packit Bot c1d7e5
	rm ${IPSET_RUN}
Packit Bot c1d7e5
	return 0
Packit Bot c1d7e5
}
Packit Bot c1d7e5
Packit Bot c1d7e5
lock
Packit Bot c1d7e5
case "$1" in
Packit Bot c1d7e5
start)
Packit Bot c1d7e5
	load
Packit Bot c1d7e5
	;;
Packit Bot c1d7e5
stop)
Packit Bot c1d7e5
	stop
Packit Bot c1d7e5
	;;
Packit Bot c1d7e5
reload)
Packit Bot c1d7e5
	cleanup
Packit Bot c1d7e5
	load
Packit Bot c1d7e5
	;;
Packit Bot c1d7e5
save)
Packit Bot c1d7e5
	save
Packit Bot c1d7e5
	;;
Packit Bot c1d7e5
*)
Packit Bot c1d7e5
	info "Usage: $0 {start|stop|reload|save}"
Packit Bot c1d7e5
	exit 1
Packit Bot c1d7e5
esac
Packit Bot c1d7e5
Packit Bot c1d7e5
exit $?