File: //tmp/csf/csfpre.sh
#!/bin/sh
# #
# @app ConfigServer Security & Firewall (CSF)
# Login Failure Daemon (LFD)
# @website https://configserver.dev
# @docs https://docs.configserver.dev
# @download https://download.configserver.dev
# @repo https://github.com/Aetherinox/csf-firewall
# @copyright Copyright (C) 2025-2026 Aetherinox
# Copyright (C) 2006-2025 Jonathan Michaelson
# Copyright (C) 2006-2025 Way to the Web Ltd.
# @license GPLv3
# @updated 02.27.2026
#
# 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 3 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, see <https://www.gnu.org/licenses>.
# #
# #
# This script looks in the folder /usr/local/include/csf/pre.d and loads
# any scripts that are in there.
#
# Pre.d are ran BEFORE CSF imports its own firewall rules into iptables.
# Post.d are ran AFTER CSF imports its own firewall rules into iptables.
#
# These scripts must be installed in:
# /usr/local/csf/bin/csfpre.sh
# /usr/local/csf/bin/csfpost.sh
#
# Add your pre and post scripts in:
# /usr/local/include/csf/pre.d/
# /usr/local/include/csf/post.d/
# #
path_csfpred="/usr/local/include/csf/pre.d"
count_loaded=0
# #
# define › app
# #
app_name="CSF Script › Pre.d Initialization" # name of app
app_desc="Loads custom scripts into CSF after adding iptable rules."
app_ver="15.10" # current script version
app_repo="Aetherinox/csf-firewall" # repository
app_repo_branch="main" # repository branch
# #
# define › app
# #
app_file_this=$(basename "$0") # csfpost.sh (with ext)
app_file_bin="${app_file_this%.*}" # csfpost (without ext)
# #
# define › folders
# #
app_dir_this="$(cd "$(dirname "$0")" >/dev/null 2>&1 && pwd)" # path where script was last found in
app_dir_this_usr="${PWD}" # the path where script is called from
# #
# Define › Icons
# #
icoSheckmark='✔' # ✔ $'\u2714'
icoXmark='✗' # ❌ $'\u274C'
# #
# Define › Colors
#
# Use the color table at:
# - https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797
# #
esc=$(printf '\033')
end="${esc}[0m"
bgEnd="${esc}[49m"
fgEnd="${esc}[39m"
bold="${esc}[1m"
dim="${esc}[2m"
underline="${esc}[4m"
blink="${esc}[5m"
white="${esc}[97m"
black="${esc}[0;30m"
redl="${esc}[0;91m"
redd="${esc}[38;5;196m"
magental="${esc}[38;5;197m"
magentad="${esc}[38;5;161m"
fuchsial="${esc}[38;5;206m"
fuchsiad="${esc}[38;5;199m"
bluel="${esc}[38;5;33m"
blued="${esc}[38;5;27m"
greenl="${esc}[38;5;47m"
greend="${esc}[38;5;35m"
orangel="${esc}[38;5;208m"
oranged="${esc}[38;5;202m"
yellowl="${esc}[38;5;226m"
yellowd="${esc}[38;5;214m"
greyl="${esc}[38;5;250m"
greym="${esc}[38;5;244m"
greyd="${esc}[38;5;240m"
navy="${esc}[38;5;62m"
olive="${esc}[38;5;144m"
peach="${esc}[38;5;204m"
cyan="${esc}[38;5;6m"
bgVerbose="${esc}[1;38;5;15;48;5;125m"
bgDebug="${esc}[1;38;5;15;48;5;237m"
bgInfo="${esc}[1;38;5;15;48;5;27m"
bgOk="${esc}[1;38;5;15;48;5;64m"
bgWarn="${esc}[1;38;5;16;48;5;214m"
bgDanger="${esc}[1;38;5;15;48;5;202m"
bgError="${esc}[1;38;5;15;48;5;160m"
# #
# Define › Logging functions
#
# verbose "This is an verbose message"
# debug "This is an debug message"
# info "This is an info message"
# ok "This is an ok message"
# warn "This is a warn message"
# danger "This is a danger message"
# error "This is an error message"
# #
verbose( )
{
case "${argVerbose:-0}" in
1|true|TRUE|yes|YES)
printf '\033[0m\r%-42s %-65s\n' " ${bgVerbose} VRBO ${end}" "${greym} $1 ${end}"
;;
esac
}
debug( )
{
if [ "$argDevEnabled" = "true" ] || [ "$argDryrun" = "true" ]; then
printf '\033[0m\r%-42s %-65s\n' " ${bgDebug} DBUG ${end}" "${greym} $1 ${end}"
fi
}
info( )
{
printf '\033[0m\r%-41s %-65s\n' " ${bgInfo} INFO ${end}" "${greym} $1 ${end}"
}
ok( )
{
printf '\033[0m\r%-41s %-65s\n' " ${bgOk} PASS ${end}" "${greym} $1 ${end}"
}
warn( )
{
printf '\033[0m\r%-42s %-65s\n' " ${bgWarn} WARN ${end}" "${greym} $1 ${end}"
}
danger( )
{
printf '\033[0m\r%-42s %-65s\n' " ${bgDanger} DNGR ${end}" "${greym} $1 ${end}"
}
error( )
{
printf '\033[0m\r%-42s %-65s\n' " ${bgError} FAIL ${end}" "${greym} $1 ${end}"
}
label( )
{
printf '\033[0m\r%-31s %-65s\n' " ${greyd} ${end}" "${greyd} $1 ${end}"
}
print( )
{
echo "${greym}$1${end}"
}
# #
# truncate text; add ...
#
# @usage
# truncate "This is a long string" 10 "..."
# #
truncate()
{
text=$1
maxlen=$2
suffix=${3:-}
len=$(printf %s "${text}" | wc -c | tr -d '[:space:]')
if [ "${len}" -gt "${maxlen}" ]; then
printf '%s%s\n' "$(printf %s "${text}" | cut -c1-"${maxlen}")" "${suffix}"
else
printf '%s\n' "${text}"
fi
}
# #
# Print › Demo Notifications
#
# Outputs a list of example notifications
#
# @usage demoNoti
# #
demoNoti()
{
verbose "This is an verbose message"
debug "This is an debug message"
info "This is an info message"
ok "This is an ok message"
warn "This is a warn message"
danger "This is a danger message"
error "This is an error message"
}
# #
# Print › Line
#
# Prints single line horizontal line, no text
#
# @usage prin0
# #
prin0()
{
_p0_indent=" "
_p0_box_width=110
_p0_line_width=$(( _p0_box_width + 2 ))
_p0_line=""
i=1
while [ "$i" -le "${_p0_line_width}" ]; do
_p0_line="${_p0_line}─"
i=$(( i + 1 ))
done
printf '\n'
printf "%b%s%s%b\n" "${greyd}" "${_p0_indent}" "${_p0_line}" "${reset}"
printf '\n'
unset _p0_indent _p0_box_width _p0_line_width _p0_line i
}
# #
# Print › Box › Paragraph
#
# Places an ASCII box around text. Supports multi-lines with \n.
#
# Determines the character count if color codes are used and ensures that the box borders are aligned properly.
#
# If using emojis; adjust the spacing so that the far-right line will align with the rest. Add the number of spaces
# to increase the value, which is represented with a number enclosed in square brackets.
# [1] add 1 space to the right.
# [2] add 2 spaces to the right.
# [-1] remove 1 space to the right (needed for some emojis depending on if the emoji is 1 or 2 bytes)
#
# @usage prinp "Certificate Generation Successful" "Your new certificate and keys have been generated successfully.\n\nYou can find them in the ${greenl}${app_dir_output}${greyd} folder."
# prinp "🎗️[1] ${file_domain_base}" "The following description will show on multiple lines with a ASCII box around it."
# prinp "📄[-1] File Overview" "The following list outlines the files that you have generated using this utility, and what certs/keys may be missing."
# prinp "➡️[15] ${bluel}Paths${end}"
# #
prinp()
{
local title="$1"
shift
local text="$*"
local indent=" "
local box_width=110
local pad=1
local content_width=$(( box_width ))
local inner_width=$(( box_width - pad*2 ))
print
print
local hline
hline=$(printf '─%.0s' $(seq 1 "$content_width"))
printf "${greyd}%s┌%s┐\n" "$indent" "$hline"
# #
# Title
#
# Extract optional [N] adjustment from title (signed integer), portably
# #
local emoji_adjust=0
local display_title="$title"
# #
# Get content inside first [...] (if present)
# #
if printf '%s\n' "$title" | grep -q '\[[[:space:]]*[-0-9][-0-9[:space:]]*\]'; then
# #
# Extract numeric inside brackets (allow optional leading -)
# - use sed to capture first bracketed token, then strip non-digit except leading -
# #
local bracket
bracket=$(printf '%s' "$title" | sed -n 's/.*\[\([-0-9][-0-9]*\)\].*/\1/p')
# #
# Validate numeric and assign, otherwise fallback to 0
# #
if printf '%s\n' "$bracket" | grep -qE '^-?[0-9]+$'; then
emoji_adjust=$bracket
else
emoji_adjust=0
fi
# #
# Remove the first [...] token from the display_title
# #
display_title=$(printf '%s' "$title" | sed 's/\[[^]]*\]//')
fi
# #
# Sanity: ensure emoji_adjust is a decimal integer so math works
# #
case "$emoji_adjust" in
''|*[!0-9-]*)
emoji_adjust=0
;;
esac
local title_width=$(( content_width - pad ))
# #
# Account for emoji adjustment in visible length calculation
# #
local title_vis_len=$(( ${#display_title} - emoji_adjust ))
printf "${greyd}%s│%*s${bluel}%s${greyd}%*s│\n" \
"$indent" "$pad" "" "$display_title" "$(( title_width - title_vis_len ))" ""
# #
# Only render body text if provided
# #
if [ -n "$text" ]; then
printf "${greyd}%s│%-${content_width}s│\n" "$indent" ""
# #
# Convert literal \n to real newlines
# #
text=$(printf "%b" "$text")
# #
# Handle each line with ANSI-aware wrapping and true padding
# #
printf "%s" "$text" | while IFS= read -r line || [ -n "$line" ]; do
# #
# Blank line
# #
if [ -z "$line" ]; then
printf "${greyd}%s│%-*s│\n" "$indent" "$content_width" ""
continue
fi
local out="" word
for word in $line; do
# #
# Strip ANSI for visible width
# #
local vis_out vis_len vis_word
vis_out=$(printf "%s" "$out" | sed 's/\x1B\[[0-9;]*[A-Za-z]//g')
vis_word=$(printf "%s" "$word" | sed 's/\x1B\[[0-9;]*[A-Za-z]//g')
vis_len=$(( ${#vis_out} + ( ${#vis_out} > 0 ? 1 : 0 ) + ${#vis_word} ))
if [ -z "$out" ]; then
out="$word"
elif [ $vis_len -le $inner_width ]; then
out="$out $word"
else
# #
# Print and pad manually based on visible length
# #
local vis_len_full
vis_len_full=$(printf "%s" "$out" | sed 's/\x1B\[[0-9;]*[A-Za-z]//g' | wc -c | tr -d ' ')
local pad_spaces=$(( inner_width - vis_len_full ))
[ $pad_spaces -lt 0 ] && pad_spaces=0
printf "${greyd}%s│%*s%s%*s│\n" "$indent" "$pad" "" "$out" "$(( pad + pad_spaces ))" ""
out="$word"
fi
done
# #
# Final flush line
# #
if [ -n "$out" ]; then
local vis_len_full pad_spaces
vis_len_full=$(printf "%s" "$out" | sed 's/\x1B\[[0-9;]*[A-Za-z]//g' | wc -c | tr -d ' ')
pad_spaces=$(( inner_width - vis_len_full ))
[ $pad_spaces -lt 0 ] && pad_spaces=0
printf "${greyd}%s│%*s%s%*s│\n" "$indent" "$pad" "" "$out" "$(( pad + pad_spaces ))" ""
fi
done
fi
printf "${greyd}%s└%s┘${reset}\n" "$indent" "$hline"
print
}
# #
# Define › Logging › Verbose
# #
log()
{
case "${argVerbose:-0}" in
1|true|TRUE|yes|YES)
verbose "$@"
;;
esac
}
# #
# Check Sudo
# #
check_sudo( )
{
if [ "$(id -u)" != "0" ]; then
error " ❌ Must run script with ${redl}sudo"
exit 1
fi
}
# #
# Run Command
#
# Added when dryrun mode was added to the install.sh.
# Allows for a critical command to be skipped if in --dryrun mode.
# Throws a debug message instead of executing.
#
# argDryrun comes from global export in csf/install.sh
#
# @usage run /sbin/chkconfig csf off
# run echo "ConfigServer"
# run chmod -v 700 "./${CSF_AUTO_GENERIC}"
# #
run()
{
if [ "${argDryrun}" = "true" ]; then
debug " Drymode (skip): $*"
else
debug " Run: $*"
"$@"
fi
}
# #
# Start Script
# #
info " Script ${bluel}${app_dir_this}/$app_file_this${greym} loading ${bluel}pre.d${greym} initialzation scripts in folder ${bluel}${path_csfpred}"
# #
# Loader
# #
if [ -d "$path_csfpred" ]; then
for i in "$path_csfpred"/*.sh; do
[ -e "$i" ] || continue # skip if no files match
[ -f "$i" ] || continue # only regular files
[ -r "$i" ] || continue # must be readable
. "$i"
count_loaded=$((count_loaded + 1))
ok " Loaded pre.d script ${greenl}$1 "
done
unset i
fi
# #
# Finished loading
# #
ok " Loaded ${greenl}${count_loaded}${greym} pre.d initialization scripts"