File: //usr/sbin/ie-config
#!/bin/sh
source /usr/lib/ie-common
# usage
# print usage and exit
usage() {
cat << EOF
Usage: $0 [-rv] command [arguments...]
Commands:
install - perform automatic configuration and start the IE
disable - disable IE for the Exim and stop the daemons
enable - configure Exim and start the IE daemons
postinstall - run post-install checks and fixes
set-nport [port] - set DEC node server TCP port number
set-nsr [rate] - configure DEC sampling rate
set-ntoken [token] - install DEC security token
set-qport [port] - set quarantine server TCP port number
set-qtoken [token] - install quarantine security token
set-rwc [token] - set rspamd worker-controller configuration
start - start the imunify email daemons
status - print status of the imunify email system
is-on - exit with 0 if IE is ON, 1 in other cases
stop - stop the imunify email daemons
version-packages - list all imunifyemail packages
version - print imunifyemail suite version
enable-incoming - enable incoming filtration feature
disable-incoming - disable incoming filtration feature
Options:
-r - submit last error message to the Imunify Email team
-v - be verbose about configuration progress
EOF
}
#### CONSTANTS ####
EXIM_CONF="/etc/exim.conf"
EXIM_CONF_LOCAL="/etc/exim.conf.local"
EXIM_BUILD_CONF_CMD="/usr/local/cpanel/scripts/buildeximconf"
SPAMFILTER_CMD="/usr/libexec/ie-spamfilter"
SPAMFILTER_CONF="/etc/rspamd/ie-spamfilter.conf"
INCOMING_FILTER_CONF_FILE="/etc/ie_incoming_conf.json"
CPANEL_ACL_CONF="/usr/local/cpanel/etc/exim/acls/ACL_OUTGOING_NOTSMTP_CHECKALL_BLOCK/custom_begin_outgoing_notsmtp_checkall"
CPANEL_JAILSHELL_FILES="/var/cpanel/jailshell-additional-files"
IE_EXIM_ROUTER_CONF="/etc/rspamd/ie-exim-router.conf"
IE_EXIM_ACL_CONF="/etc/rspamd/ie-exim-acl.conf"
IE_EXIM_TRANS_GT494_CONF="/etc/rspamd/ie-exim-trans-gt494.conf"
IE_EXIM_TRANS_LT494_CONF="/etc/rspamd/ie-exim-trans-lt494.conf"
# cfg_exim
# configure Exim to use the imunifyemail postfilter
cfg_exim()
{
_name="cfg_exim"
dprint 2 "$_name: analyzing files"
if [ ! -f "$EXIM_CONF_LOCAL" ] ; then
# if no config file available, create our own
dprint 2 "$_name: creating empty $EXIM_CONF_LOCAL template"
cat <<- EOF > "$EXIM_CONF_LOCAL"
@PREROUTERS@
@ROUTERSTART@
@ROUTERMIDDLE@
@ROUTEREND@
@TRANSPORTSTART@
@TRANSPORTMIDDLE@
@TRANSPORTEND@
EOF
fi
if [ ! -x "$EXIM_BUILD_CONF_CMD" ] ; then
err 3 "$_name: $EXIM_BUILD_CONF_CMD: non executable"
fi
if [ ! -w "$EXIM_CONF_LOCAL" ] ; then
err 3 "$_name: $EXIM_CONF_LOCAL: file unwriteable"
fi
if ! grep -q '@PREROUTERS@' $EXIM_CONF_LOCAL ; then
cat <<-EOF >> "$EXIM_CONF_LOCAL"
@PREROUTERS@
EOF
fi
if ! grep -q '@TRANSPORTSTART@' $EXIM_CONF_LOCAL ; then
cat <<-EOF >> "$EXIM_CONF_LOCAL"
@TRANSPORTSTART@
EOF
fi
if [ ! -d $(dirname "$CPANEL_ACL_CONF") ] ; then
err 3 "$_name:" $(dirname "$CPANEL_ACL_CONF")": no such directory"
fi
if [ ! -f "$CPANEL_ACL_CONF" ] ; then
# if no file available, create our own
dprint 2 "$_name: creating empty $CPANEL_ACL_CONF file"
touch "$CPANEL_ACL_CONF"
fi
if [ ! -w "$CPANEL_ACL_CONF" ] ; then
err 3 "$_name: $CPANEL_ACL_CONF: file unwriteable"
fi
if [ ! -x "$SPAMFILTER_CMD" ] ; then
err 3 "$_name: $SPAMFILTER_CMD: non executable"
fi
if [ ! -f "$SPAMFILTER_CONF" ] ; then
err 3 "$_name: $SPAMFILTER_CONF: not found"
fi
# postpone exim.conf rebuild until all necessary files will be updated
_rebuildeximconf=no
if ! grep -q '^#imunifyemail_spamfilter.*BEGIN' $EXIM_CONF_LOCAL ; then
dprint 2 "$_name: editing $EXIM_CONF_LOCAL"
backup_file "$BACKUP" "$EXIM_CONF_LOCAL"
_rebuildeximconf=yes
if exim_version_gt 4.94; then
IE_EXIM_TRANS_CONF="$IE_EXIM_TRANS_GT494_CONF"
else
IE_EXIM_TRANS_CONF="$IE_EXIM_TRANS_LT494_CONF"
fi
sed -i '
/^ *@PREROUTERS@/a\
\
#imunifyemail_spamfilter_router BEGIN\
.include '"$IE_EXIM_ROUTER_CONF"'\
#imunifyemail_spamfilter_router END\
/^ *@TRANSPORTSTART@/a\
\
#imunifyemail_spamfilter_transport BEGIN\
.include '"$IE_EXIM_TRANS_CONF"'\
#imunifyemail_spamfilter_transport END\
' $EXIM_CONF_LOCAL || err 250 "$_name: unexpected error (sed)"
else
dprint 1 "$_name: $EXIM_CONF_LOCAL was edited before"
fi
if ! grep -q "^#imunifyemail_spamfilter_acl_non_smtp" "$CPANEL_ACL_CONF" ; then
dprint 2 "$_name: editing $CPANEL_ACL_CONF"
backup_file "$BACKUP" "$CPANEL_ACL_CONF"
# ensure the sed that follows has a stream to work on -
# put an empty line into the file
if [ ! -s "$CPANEL_ACL_CONF" ] ; then
cat <<- EOF > "$CPANEL_ACL_CONF"
EOF
fi
sed -i '1i\
#imunifyemail_spamfilter_acl_non_smtp\
.include '"$IE_EXIM_ACL_CONF"'\
#imunifyemail_spamfilter_acl_non_smtp END\
' "$CPANEL_ACL_CONF" || err 250 "$_name: unexpected error (sed)"
_rebuildeximconf=yes
else
dprint 1 "$_name: $CPANEL_ACL_CONF was edited before"
fi
if [ "$_rebuildeximconf" = "yes" ] ; then
dprint 2 "$_name: calling buildeximconf"
if ! $EXIM_BUILD_CONF_CMD ; then
# XXX how to recover if it fails?
err 5 "$_name: buildeximconf failed; please check configs"
fi
dprint 2 "$_name: restarting exim"
# should we also catch errors from the folowing?
/bin/systemctl restart exim.service
fi
}
exim_version_gt()
{
version_to_compare=$1
# run exim --version command and parse version number in the first line. If the version number is greater than 4.94 use a different config
current_version=$(exim --version | head -n 1 | sed -n 's/Exim version \([0-9]\+\.[0-9]\+\).*$/\1/p')
dprint 1 "exim_version_ge($version_to_compare): current exim version is $current_version"
v_major=$(echo ${version_to_compare} | cut -d . -f 1)
v_minor=$(echo ${version_to_compare} | cut -d . -f 2)
curr_major=$(echo ${current_version} | cut -d . -f 1)
curr_minor=$(echo ${current_version} | cut -d . -f 2)
if [ $curr_major -gt $v_major -o $curr_major -eq $v_major -a $curr_minor -gt $v_minor ]
then
return 0
else
return 1
fi
}
# uncfg_exim
# remove the spamfilter declarations from the exim.conf
uncfg_exim()
{
_name="uncfg_exim"
local _restart="${1:-true}"
dprint 2 "$_name: analyzing files"
if [ ! -f "$EXIM_CONF_LOCAL" ] ; then
err 3 "$_name: $EXIM_CONF_LOCAL: no such file"
fi
if [ ! -w "$EXIM_CONF_LOCAL" ] ; then
err 3 "$_name: $EXIM_CONF_LOCAL: file unwriteable"
fi
if [ ! -f "$CPANEL_ACL_CONF" ] ; then
err 3 "$_name: $CPANEL_ACL_CONF: no such file"
fi
if [ ! -w "$CPANEL_ACL_CONF" ] ; then
err 3 "$_name: $CPANEL_ACL_CONF: file unwriteable"
fi
if [ ! -x "$EXIM_BUILD_CONF_CMD" ] ; then
err 3 "$_name: $EXIM_BUILD_CONF_CMD: non executable"
fi
# postpone exim.conf rebuild until all necessary files will be updated
_rebuildeximconf=no
if grep -q '^#imunifyemail_spamfilter.*BEGIN' $EXIM_CONF_LOCAL ; then
dprint 2 "$_name: editing $EXIM_CONF_LOCAL"
backup_file "$BACKUP" "$EXIM_CONF_LOCAL"
# For now, drop everything between the ^imunifyemail_spamfilter
# and the end of the relevant block.
# TODO: Would be nice to replace it with /^start/,/^stop/ logic though.
sed -i '/^#imunifyemail_spamfilter.*BEGIN$/h
/^#imunifyemail_spamfilter.*END/{h;d}
/^[[:space:]]*$/!{
x
/^#imunifyemail_spamfilter.*BEGIN$/{
x;d;b
}
x
}' $EXIM_CONF_LOCAL || err 250 "$_name: unexpected error (sed)"
_rebuildeximconf=yes
else
dprint 1 "$_name: $EXIM_CONF_LOCAL is clean"
fi
if grep -q "^#imunifyemail_spamfilter.*" "$CPANEL_ACL_CONF" ; then
dprint 2 "$_name: editing $CPANEL_ACL_CONF"
backup_file "$BACKUP" "$CPANEL_ACL_CONF"
sed -i '/^#imunifyemail_spamfilter/,/^#imunifyemail_spamfilter.* END/d' \
"$CPANEL_ACL_CONF" \
|| err 250 "$_name: unexpected error (sed)"
_rebuildeximconf=yes
else
dprint 1 "$_name: $CPANEL_ACL_CONF is clean"
fi
if [ "$_rebuildeximconf" = "yes" ] && [ "$_restart" == true ] ; then
dprint 2 "$_name: calling buildeximconf"
if ! $EXIM_BUILD_CONF_CMD ; then
# XXX how to recover if it fails?
err 5 "$_name: buildeximconf failed; please check configs"
fi
dprint 2 "$_name: restarting exim"
# should we also catch errors from the folowing?
/bin/systemctl restart exim.service
fi
}
# exim conf status
_exim_status()
{
_name="exim_status"
if [ -r "$EXIM_CONF" ] ; then
if grep -q '^#imunifyemail_spamfilter.*BEGIN' "$EXIM_CONF" ; then
echo "enabled"
else
echo "disabled"
fi
else
warn "$_name: $EXIM_CONF: unreadable"
fi
}
# _has_postfix
# check if Postfix MTA and ie-spamfilter service are available
_has_postfix()
{
command -v postfix >/dev/null 2>&1 && \
[ -f /usr/lib/systemd/system/ie-spamfilter.service ]
}
# svc_ie
# start the imunify daemons, in order
svc_ie()
{
_name="svc_ie"
_cmd=$1
case "$_cmd" in
stop|status|enable|disable|restart|start)
;;
*)
err 1 "$_name: $1: bad command" ;;
esac
_svcs="rspamd ie-quarantine ie-dec-node"
if _has_postfix ; then
_svcs="$_svcs ie-spamfilter"
fi
for _svc in $_svcs ; do
dprint 2 "$_name: $_cmd $_svc"
if ! /bin/systemctl $_cmd ${_svc}.service ; then
err 5 "$_name: $_svc $_cmd failed"
fi
done
# operations with notification socket
_ie_notifier="ie-notifier.socket"
if ! /bin/systemctl $_cmd $_ie_notifier ; then
err 5 "$_name: $_ie_notifier $_cmd failed"
fi
}
# print_status
# check the imunify email system status
print_status()
{
_name="print_status"
dprint 2 "$_name: checking system state"
echo -n "spamfilter exim configuration: "
echo $(_exim_status)
_status_svcs="rspamd ie-quarantine ie-dec-node"
if _has_postfix ; then
_status_svcs="$_status_svcs ie-spamfilter"
fi
for _svc in $_status_svcs ; do
echo -n "$_svc state: "
/bin/systemctl show $_svc --property=ActiveState,SubState \
| sort \
| sed '/^ActiveState=/{
N
s/^ActiveState=\([^ ]*\)/\1/
s/\nSubState=\([^ ]*\)$/ (\1)/
}'
done
incoming_state=$(_incoming_filtration_state)
echo "incoming filtration: ${incoming_state}"
}
# is_on
# check the imunify email system status, exit 0 if IE enabled and 1 in other cases
is_on()
{
if [ -r "$EXIM_CONF" ] ; then
if grep -q '^#imunifyemail_spamfilter.*BEGIN' "$EXIM_CONF" ; then
exit 0
fi
else
warn "is_on: $EXIM_CONF: unreadable"
fi
exit 1
}
# postinstall
# run positinstall checks and fixes
postinstall()
{
_name="postinstall"
add_user_in_group _imunify _rspamd
add_user_in_group mail _rspamd
add_user_in_group mail _imunifyemail
# rspamd controller UDS support, leave it here for backward compatibility with the IE version < 0.9-5
if [ -f "$RSPMAD_OVERRIDED_WORKER_CONTROLLER_INC" ]; then
if grep -q bind_socket "$RSPMAD_OVERRIDED_WORKER_CONTROLLER_INC"; then
sed -i 's/^bind_socket = "localhost:11334";//g' "$RSPMAD_OVERRIDED_WORKER_CONTROLLER_INC"
fi
fi
cfg_ntoken # DEC security token
cfg_qtoken # rspamd security token
/usr/sbin/ie_ruleupdate
#get the first line of the output, parse the string after : and remove leading and trailing spaces
local _status="$(_exim_status)"
if [ "$_status" == "enabled" ]; then
echo "" # need end of line
echo "Restarting services..."
svc_ie restart # restart everything because of the global password changes
uncfg_exim false
cfg_exim
fi
dprint 1 "trigger rules and scores update"
old_backup_cleanup
}
_add_line_to_file() {
local file="$1"
local value="$2"
local case_sensitive="${3:-false}"
local cdb="$4"
local lock_file="${file}.lock"
local temp_file="${file}.tmp"
local _ocf_name="_add_line_to_file(conf_file=$file, value=$value, cdb=$cdb)"
dprint 2 "$_ocf_name"
# validate parameters
if [ -z "$value" ] || [ -z "$file" ]; then
err 100 "$_ocf_name: wrong parameters"
fi
# Create the file if it doesn't exist
if [ ! -f "$file" ]; then
touch "$file"
fi
# Use flock to ensure only one process can modify the file at a time
(
flock -e 200 # Acquire an exclusive lock
if [ "$case_sensitive" = true ]; then
grep_cmd="grep -q"
else
grep_cmd="grep -qi"
fi
# Check if the line already exists (case-insensitive)
if ! eval "$grep_cmd" "^${value}$" "$file"; then
# Write the current content to a temporary file and append the new line
if cat "$file" > "$temp_file" && echo "$value" >> "$temp_file"; then
# Move the temp file to the original file atomically
if mv "$temp_file" "$file"; then
if [ ! -z "$cdb" ]; then
if ! cdb -c -m "$cdb" "$file"; then
err 1 "Failed to create cdb file"
fi
fi
echo "The config added successfully to $file"
else
rm -f "$temp_file" # Clean up the temp file if the move fails
err 1 "Failed to replace the original file."
fi
else
err 1 "Failed to write to the temporary file."
fi
fi
) 200>"$lock_file" # Lock file descriptor 200 using the lock file
# Remove the lock file after the operation
rm -f "$lock_file"
}
_remove_line_from_file() {
local file="$1"
local value="$2"
local case_sensitive="${3:-false}"
local cdb="$4"
local temp_file="${file}.tmp"
local lock_file="${file}.lock"
local _ocf_name="_remove_line_from_file(conf_file=$file, value=$value)"
dprint 2 "$_ocf_name"
# validate parameters
if [ -z "$value" ] || [ -z "$file" ]; then
err 100 "$_ocf_name: wrong parameters"
fi
if [ -f "$file" ]; then
# Use flock to ensure only one process can modify the file at a time
(
flock -e 200 # Acquire an exclusive lock
# Escape any / characters in the value
local escaped=$(echo "$value" | sed 's/\//\\\//g')
# Set sed command based on case sensitivity
if [ "$case_sensitive" = true ]; then
sed_cmd="sed '/^${escaped}$/d'"
else
sed_cmd="sed '/^${escaped}$/I d'"
fi
# Use sed to delete the matching line in a case-insensitive way
if eval "${sed_cmd}" "$file" > "$temp_file"; then
# Move the temp file to the original file atomically
if mv "$temp_file" "$file"; then
if [ ! -z "$cdb" ]; then
if ! cdb -c -m "$cdb" "$file"; then
err 1 "Failed to create cdb file"
fi
fi
echo "The config removed successfully from $file"
else
rm -f "$temp_file" # Clean up the temp file if the move fails
err 1 "Failed to replace the original file."
fi
else
err 1 "Failed to write to the temporary file."
fi
) 200>"$lock_file" # Lock file descriptor 200 using the lock file
# Remove the lock file after the operation
rm -f "$lock_file"
fi
}
cfg_jailshell()
{
# add configuration files to CPanel's jail shell.
# So that ie-spamfilter can access the file when Exim is invoked from a jailshell
_add_line_to_file "$CPANEL_JAILSHELL_FILES" "$SPAMFILTER_CONF" true
_add_line_to_file "$CPANEL_JAILSHELL_FILES" "$IE_EXIM_ROUTER_CONF" true
_add_line_to_file "$CPANEL_JAILSHELL_FILES" "$IE_EXIM_ACL_CONF" true
_add_line_to_file "$CPANEL_JAILSHELL_FILES" "$IE_EXIM_TRANS_LT494_CONF" true
_add_line_to_file "$CPANEL_JAILSHELL_FILES" "$IE_EXIM_TRANS_GT494_CONF" true
}
uncfg_jailshell()
{
# removes the config from the file. But this does not take any effect. So this is only
# to avoid leaving garbage config after the de-installation.
_remove_line_from_file "$CPANEL_JAILSHELL_FILES" "$SPAMFILTER_CONF" true
_remove_line_from_file "$CPANEL_JAILSHELL_FILES" "$IE_EXIM_ROUTER_CONF" true
_remove_line_from_file "$CPANEL_JAILSHELL_FILES" "$IE_EXIM_ACL_CONF" true
_remove_line_from_file "$CPANEL_JAILSHELL_FILES" "$IE_EXIM_TRANS_LT494_CONF" true
_remove_line_from_file "$CPANEL_JAILSHELL_FILES" "$IE_EXIM_TRANS_GT494_CONF" true
}
#
# Entry point
#
# we don't want the environment to affect the script
export LC_ALL=C
export LC_CTYPE=C
export LANG=""
export PATH=/bin:/sbin:/usr/bin:/usr/sbin
export HOME=$(getpwuid_dir $(id -u))
# use global backup archive to save everything
BACKUP=$(date +"$HOME/imunifyemail-backup-%s-$$.tar")
export BACKUP
export DEBUG_LEVEL=0
export REPORT_ERROR=no
while getopts qrv f ; do
case "$f" in
q) ;; # OBSOLETE, will be removed in future
r) export REPORT_ERROR="yes" ;;
v) export DEBUG_LEVEL=2 ;;
\?) usage ; exit 0 ;;
esac
done
shift $(expr $OPTIND - 1)
case "$1" in
install)
auth_root
cfg_ntoken
cfg_qtoken
svc_ie enable
svc_ie start
cfg_exim
/usr/sbin/ie_ruleupdate
;;
enable)
auth_root
svc_ie enable
svc_ie start
cfg_jailshell
cfg_exim
;;
disable)
auth_root
uncfg_exim
uncfg_jailshell
svc_ie stop
svc_ie disable
;;
checkperms)
#TODO
err 255 'checkperms: not implemented'
;;
set-qport)
arg=$2
auth_root
cfg_qport "$arg"
;;
set-qtoken)
arg=$2
auth_root
cfg_qtoken "$arg"
;;
set-nport)
arg=$2
auth_root
cfg_nport "$arg"
;;
set-ntoken)
arg=$2
auth_root
cfg_ntoken "$arg"
;;
set-nsr)
arg=$2
auth_root
cfg_nsr "$arg"
;;
start)
auth_root
svc_ie start
;;
stop)
auth_root
svc_ie stop
;;
status)
print_status
;;
is-on)
auth_root
is_on
;;
postinstall)
auth_root
postinstall
;;
version)
print_version
;;
version-packages)
print_version packages
;;
enable-incoming)
_toggle_incoming_filtration "enabled"
;;
disable-incoming)
_toggle_incoming_filtration "disabled"
;;
*)
usage
exit 1
;;
esac
dprint 2 "finished successfully"
backup_cleanup "$BACKUP"