Files
mailsystem/deactivate-srs.sh
T

1881 lines
43 KiB
Bash
Executable File

#!/usr/bin/env bash
# ----------
# Selective SRS deactivation
#
# Restores the Postfix configuration state recorded before selective SRS
# was activated.
#
# The script is intentionally conservative:
#
# - DISABLED -> no changes, rc=0
# - ENABLED -> deactivate after explicit confirmation
# - INCONSISTENT -> abort without changes
#
# Persistent pre-SRS state is read from:
#
# /etc/postfix-srs/state.conf
#
# The state file and installed SRS components are intentionally retained.
# ----------
# ----------
# Script setup
# ----------
working_dir="$(dirname "$(realpath "$0")")"
# shellcheck source=helpers/srs-common.sh
source "${working_dir}/helpers/srs-common.sh"
trap 'handle_signal' SIGHUP SIGINT SIGTERM
init_srs_runtime
# ----------
# Deactivation runtime state
# ----------
DEACTIVATION_CHANGES_STARTED=false
DEACTIVATION_BACKUP_DIR=""
POSTFIX_SRS_DROPIN_WAS_PRESENT=false
SRS_DELIVERY_DROPIN_WAS_PRESENT=false
declare -A SERVICE_WAS_ACTIVE
declare -A SERVICE_WAS_ENABLED
# ----------
# Local helper functions
# ----------
main_cf_explicit_value() {
local parameter="$1"
awk -v parameter="$parameter" '
/^[[:space:]]*#/ { next }
$0 ~ "^[[:space:]]*" parameter "[[:space:]]*=" {
line=$0
}
END {
if (line != "") {
sub("^[[:space:]]*" parameter "[[:space:]]*=[[:space:]]*", "", line)
print line
}
}
' "$POSTFIX_MAIN_CF"
}
main_cf_parameter_is_set() {
local parameter="$1"
grep -Eq \
"^[[:space:]]*${parameter}[[:space:]]*=" \
"$POSTFIX_MAIN_CF"
}
master_service_option_value() {
local service="$1"
local option="$2"
awk -v service="$service" -v option="$option" '
/^[[:space:]]*#/ { next }
$1 == service && $2 == "inet" {
in_service=1
next
}
in_service && $0 !~ /^[[:space:]]/ {
exit
}
in_service && $1 == "-o" {
prefix=option "="
if (index($2, prefix) == 1) {
value=$2
sub("^" option "=", "", value)
print value
exit
}
}
' "$POSTFIX_MASTER_CF"
}
master_srs_forward_present() {
awk '
/^[[:space:]]*#/ { next }
$1 == "srs-forward" && $2 == "unix" {
found_service=1
next
}
found_service && $0 !~ /^[[:space:]]/ {
exit
}
found_service && $0 ~ /argv=\/usr\/local\/sbin\/srs-forward\.py/ {
found_argv=1
}
END {
if (found_service && found_argv)
exit 0
exit 1
}
' "$POSTFIX_MASTER_CF"
}
port_is_listening() {
local host="$1"
local port="$2"
ss -ltnH 2>/dev/null |
awk -v host="$host" -v port="$port" '
{
local_address=$4
if (local_address == host ":" port ||
local_address == "[" host "]:" port) {
found=1
exit
}
}
END {
exit(found ? 0 : 1)
}
'
}
service_exists() {
systemctl cat "$1" > /dev/null 2>&1
}
service_active() {
systemctl is-active --quiet "$1"
}
service_enabled_state() {
systemctl is-enabled "$1" 2>/dev/null || true
}
service_enabled() {
case "$(service_enabled_state "$1")" in
enabled|enabled-runtime)
return 0
;;
*)
return 1
;;
esac
}
postsrsd_srs_domain() {
awk '
/^[[:space:]]*#/ { next }
/^[[:space:]]*srs-domain[[:space:]]*=/ {
line=$0
sub(/^[^=]*=[[:space:]]*/, "", line)
gsub(/^[[:space:]]+|[[:space:]]+$/, "", line)
gsub(/^"|"$/, "", line)
print line
exit
}
' "$POSTSRSD_CONF_FILE"
}
state_main_cf_matches() {
local parameter="$1"
local was_set="$2"
local expected_value="$3"
local current_value=""
if "$was_set" ; then
if ! main_cf_parameter_is_set "$parameter" ; then
return 1
fi
current_value="$(main_cf_explicit_value "$parameter")"
[[ "$current_value" == "$expected_value" ]]
return
fi
! main_cf_parameter_is_set "$parameter"
}
state_master_option_matches() {
local service="$1"
local option="$2"
local was_set="$3"
local expected_value="$4"
local current_value=""
current_value="$(master_service_option_value "$service" "$option")"
if "$was_set" ; then
[[ "$current_value" == "$expected_value" ]]
return
fi
[[ -z "$current_value" ]]
}
csv_add_value() {
local current="$1"
local add="$2"
local item
local result=""
local found=false
IFS=',' read -r -a _items <<< "$current"
for item in "${_items[@]}" ; do
item="${item#"${item%%[![:space:]]*}"}"
item="${item%"${item##*[![:space:]]}"}"
[[ -z "$item" ]] && continue
if [[ "$item" == "$add" ]]; then
found=true
fi
if [[ -z "$result" ]]; then
result="$item"
else
result="${result},${item}"
fi
done
if ! $found ; then
if [[ -z "$result" ]]; then
result="$add"
else
result="${result},${add}"
fi
fi
printf '%s\n' "$result"
}
csv_remove_value() {
local current="$1"
local remove="$2"
local item
local result=""
IFS=',' read -r -a _items <<< "$current"
for item in "${_items[@]}" ; do
item="${item#"${item%%[![:space:]]*}"}"
item="${item%"${item##*[![:space:]]}"}"
[[ -z "$item" ]] && continue
[[ "$item" == "$remove" ]] && continue
if [[ -z "$result" ]]; then
result="$item"
else
result="${result},${item}"
fi
done
printf '%s\n' "$result"
}
update_master_service_option() {
local service="$1"
local option="$2"
local value="$3"
python3 - \
"$POSTFIX_MASTER_CF" \
"$service" \
"$option" \
"$value" <<'PY_EOF'
import os
import re
import stat
import sys
import tempfile
path, service, option, value = sys.argv[1:5]
with open(path, "r", encoding="utf-8") as f:
lines = f.readlines()
service_index = None
for idx, line in enumerate(lines):
if not line.strip() or line.lstrip().startswith("#"):
continue
fields = line.split()
if len(fields) >= 2 and fields[0] == service and fields[1] == "inet":
service_index = idx
break
if service_index is None:
raise SystemExit(2)
end_index = len(lines)
for idx in range(service_index + 1, len(lines)):
line = lines[idx]
if line.strip() == "":
end_index = idx
break
if line[0].isspace():
continue
if line.lstrip().startswith("#"):
end_index = idx
break
end_index = idx
break
option_re = re.compile(
r"^(?P<indent>\s*)-o\s+" + re.escape(option) + r"=.*(?:\n)?$"
)
option_index = None
option_indent = " "
for idx in range(service_index + 1, end_index):
match = option_re.match(lines[idx])
if match:
option_index = idx
option_indent = match.group("indent") or " "
break
if value:
new_line = f"{option_indent}-o {option}={value}\n"
if option_index is not None:
lines[option_index] = new_line
else:
lines.insert(end_index, new_line)
else:
if option_index is not None:
del lines[option_index]
st = os.stat(path)
directory = os.path.dirname(path)
fd, tmp_path = tempfile.mkstemp(prefix=".master.cf.srs.", dir=directory)
try:
with os.fdopen(fd, "w", encoding="utf-8") as f:
f.writelines(lines)
os.chmod(tmp_path, stat.S_IMODE(st.st_mode))
os.chown(tmp_path, st.st_uid, st.st_gid)
os.replace(tmp_path, path)
finally:
if os.path.exists(tmp_path):
os.unlink(tmp_path)
PY_EOF
}
remove_managed_block() {
local file="$1"
local begin_marker="$2"
local end_marker="$3"
python3 - \
"$file" \
"$begin_marker" \
"$end_marker" <<'PY_EOF'
import os
import stat
import sys
import tempfile
path, begin_marker, end_marker = sys.argv[1:4]
with open(path, "r", encoding="utf-8") as f:
lines = f.readlines()
begin_matches = [
idx for idx, line in enumerate(lines)
if line.rstrip("\n") == begin_marker
]
end_matches = [
idx for idx, line in enumerate(lines)
if line.rstrip("\n") == end_marker
]
if len(begin_matches) != 1 or len(end_matches) != 1:
raise SystemExit(2)
begin_idx = begin_matches[0]
end_idx = end_matches[0]
if begin_idx >= end_idx:
raise SystemExit(3)
new_lines = lines[:begin_idx] + lines[end_idx + 1:]
# Remove one excessive blank line left at the join point.
while (
begin_idx > 0
and begin_idx < len(new_lines)
and new_lines[begin_idx - 1].strip() == ""
and new_lines[begin_idx].strip() == ""
):
del new_lines[begin_idx]
st = os.stat(path)
directory = os.path.dirname(path)
fd, tmp_path = tempfile.mkstemp(prefix=".srs-managed.", dir=directory)
try:
with os.fdopen(fd, "w", encoding="utf-8") as f:
f.writelines(new_lines)
os.chmod(tmp_path, stat.S_IMODE(st.st_mode))
os.chown(tmp_path, st.st_uid, st.st_gid)
os.replace(tmp_path, path)
finally:
if os.path.exists(tmp_path):
os.unlink(tmp_path)
PY_EOF
}
restore_main_cf_parameter() {
local parameter="$1"
local was_set="$2"
local value="$3"
python3 - \
"$POSTFIX_MAIN_CF" \
"$parameter" \
"$was_set" \
"$value" <<'PY_EOF'
import os
import re
import stat
import sys
import tempfile
path, parameter, was_set, value = sys.argv[1:5]
with open(path, "r", encoding="utf-8") as f:
lines = f.readlines()
pattern = re.compile(
r"^[ \t]*" + re.escape(parameter) + r"[ \t]*="
)
new_lines = []
skip_continuation = False
for line in lines:
if skip_continuation:
if line.startswith((" ", "\t")) and not line.lstrip().startswith("#"):
continue
skip_continuation = False
if line.lstrip().startswith("#"):
new_lines.append(line)
continue
if pattern.match(line):
skip_continuation = True
continue
new_lines.append(line)
if was_set == "true":
if new_lines and new_lines[-1].strip() != "":
new_lines.append("\n")
new_lines.append(f"{parameter} = {value}\n")
st = os.stat(path)
directory = os.path.dirname(path)
fd, tmp_path = tempfile.mkstemp(prefix=".main.cf.srs.", dir=directory)
try:
with os.fdopen(fd, "w", encoding="utf-8") as f:
f.writelines(new_lines)
os.chmod(tmp_path, stat.S_IMODE(st.st_mode))
os.chown(tmp_path, st.st_uid, st.st_gid)
os.replace(tmp_path, path)
finally:
if os.path.exists(tmp_path):
os.unlink(tmp_path)
PY_EOF
}
remove_legacy_srs_forward_service() {
python3 - "$POSTFIX_MASTER_CF" <<'PY_EOF'
import os
import stat
import sys
import tempfile
path = sys.argv[1]
with open(path, "r", encoding="utf-8") as f:
lines = f.readlines()
start = None
for idx, line in enumerate(lines):
if not line.strip() or line.lstrip().startswith("#"):
continue
fields = line.split()
if len(fields) >= 2 and fields[0] == "srs-forward" and fields[1] == "unix":
if start is not None:
raise SystemExit(2)
start = idx
if start is None:
raise SystemExit(3)
end = len(lines)
for idx in range(start + 1, len(lines)):
line = lines[idx]
if line.strip() == "":
end = idx
break
if line[0].isspace():
continue
if line.lstrip().startswith("#"):
end = idx
break
end = idx
break
block = "".join(lines[start:end])
required = [
"flags=Rq null_sender= user=nobody",
"argv=/usr/local/sbin/srs-forward.py",
"${sender}",
"${recipient}",
"${original_recipient}",
"${queue_id}",
"envid=${envid}",
]
for token in required:
if token not in block:
raise SystemExit(4)
new_lines = lines[:start] + lines[end:]
while (
start > 0
and start < len(new_lines)
and new_lines[start - 1].strip() == ""
and new_lines[start].strip() == ""
):
del new_lines[start]
st = os.stat(path)
directory = os.path.dirname(path)
fd, tmp_path = tempfile.mkstemp(prefix=".master.cf.srs.", dir=directory)
try:
with os.fdopen(fd, "w", encoding="utf-8") as f:
f.writelines(new_lines)
os.chmod(tmp_path, stat.S_IMODE(st.st_mode))
os.chown(tmp_path, st.st_uid, st.st_gid)
os.replace(tmp_path, path)
finally:
if os.path.exists(tmp_path):
os.unlink(tmp_path)
PY_EOF
}
systemd_dropins_match_expected() {
[[ -r "$POSTFIX_SRS_DROPIN_FILE" ]] || return 1
[[ -r "$SRS_DELIVERY_DROPIN_FILE" ]] || return 1
grep -Fxq \
"Wants=${SRS_REVERSE_MAP_SERVICE} ${SRS_DELIVERY_SERVICE}" \
"$POSTFIX_SRS_DROPIN_FILE" || return 1
grep -Fxq \
"After=${SRS_REVERSE_MAP_SERVICE} ${SRS_DELIVERY_SERVICE}" \
"$POSTFIX_SRS_DROPIN_FILE" || return 1
grep -Fxq \
"Wants=${POSTSRSD_SERVICE}" \
"$SRS_DELIVERY_DROPIN_FILE" || return 1
grep -Fxq \
"After=${POSTSRSD_SERVICE}" \
"$SRS_DELIVERY_DROPIN_FILE" || return 1
return 0
}
snapshot_systemd_dropins() {
if [[ -f "$POSTFIX_SRS_DROPIN_FILE" ]]; then
POSTFIX_SRS_DROPIN_WAS_PRESENT=true
cp -a "$POSTFIX_SRS_DROPIN_FILE" \
"${DEACTIVATION_BACKUP_DIR}/postfix.service-srs.conf" || return 1
fi
if [[ -f "$SRS_DELIVERY_DROPIN_FILE" ]]; then
SRS_DELIVERY_DROPIN_WAS_PRESENT=true
cp -a "$SRS_DELIVERY_DROPIN_FILE" \
"${DEACTIVATION_BACKUP_DIR}/postfix@srs-delivery.service-srs.conf" || return 1
fi
return 0
}
remove_srs_systemd_dropins() {
rm -f \
"$POSTFIX_SRS_DROPIN_FILE" \
"$SRS_DELIVERY_DROPIN_FILE" || return 1
systemctl daemon-reload || return 1
return 0
}
restore_systemd_dropins() {
local restore_failed=false
if $POSTFIX_SRS_DROPIN_WAS_PRESENT ; then
mkdir -p "$POSTFIX_SRS_DROPIN_DIR" || restore_failed=true
cp -a \
"${DEACTIVATION_BACKUP_DIR}/postfix.service-srs.conf" \
"$POSTFIX_SRS_DROPIN_FILE" || restore_failed=true
else
rm -f "$POSTFIX_SRS_DROPIN_FILE" || restore_failed=true
fi
if $SRS_DELIVERY_DROPIN_WAS_PRESENT ; then
mkdir -p "$SRS_DELIVERY_DROPIN_DIR" || restore_failed=true
cp -a \
"${DEACTIVATION_BACKUP_DIR}/postfix@srs-delivery.service-srs.conf" \
"$SRS_DELIVERY_DROPIN_FILE" || restore_failed=true
else
rm -f "$SRS_DELIVERY_DROPIN_FILE" || restore_failed=true
fi
systemctl daemon-reload || restore_failed=true
! $restore_failed
}
snapshot_service_states() {
local service
for service in \
"$POSTSRSD_SERVICE" \
"$SRS_REVERSE_MAP_SERVICE" \
"$SRS_DELIVERY_SERVICE"
do
if service_active "$service" ; then
SERVICE_WAS_ACTIVE["$service"]="true"
else
SERVICE_WAS_ACTIVE["$service"]="false"
fi
SERVICE_WAS_ENABLED["$service"]="$(service_enabled_state "$service")"
done
}
restore_service_states() {
local service
local restore_failed=false
local enabled_state
for service in \
"$POSTSRSD_SERVICE" \
"$SRS_REVERSE_MAP_SERVICE" \
"$SRS_DELIVERY_SERVICE"
do
enabled_state="${SERVICE_WAS_ENABLED[$service]:-}"
case "$enabled_state" in
enabled|enabled-runtime)
systemctl enable "$service" > /dev/null 2>> "$log_file" || restore_failed=true
;;
disabled)
systemctl disable "$service" > /dev/null 2>> "$log_file" || restore_failed=true
;;
esac
if [[ "${SERVICE_WAS_ACTIVE[$service]:-false}" == "true" ]]; then
systemctl start "$service" > /dev/null 2>> "$log_file" || restore_failed=true
else
systemctl stop "$service" > /dev/null 2>> "$log_file" || restore_failed=true
fi
done
! $restore_failed
}
srs_delivery_queue_is_empty() {
local queue_data
queue_data="$(
postqueue -c "$POSTFIX_SRS_DELIVERY_DIR" -j 2>> "$log_file"
)" || return 2
[[ -z "${queue_data//[[:space:]]/}" ]]
}
rollback_deactivation() {
local rollback_failed=false
if ! $DEACTIVATION_CHANGES_STARTED ; then
return 0
fi
blank_line
warn "SRS deactivation failed. Trying to restore the previous state."
echononl "Restore '${POSTFIX_MAIN_CF}'.."
if cp -a "${DEACTIVATION_BACKUP_DIR}/main.cf" "$POSTFIX_MAIN_CF" > "$log_file" 2>&1 ; then
echo_ok
else
echo_failed
rollback_failed=true
fi
echononl "Restore '${POSTFIX_MASTER_CF}'.."
if cp -a "${DEACTIVATION_BACKUP_DIR}/master.cf" "$POSTFIX_MASTER_CF" > "$log_file" 2>&1 ; then
echo_ok
else
echo_failed
rollback_failed=true
fi
echononl "Restore previous systemd SRS drop-ins.."
if restore_systemd_dropins ; then
echo_ok
else
echo_failed
rollback_failed=true
fi
echononl "Restore previous SRS service states.."
if restore_service_states ; then
echo_ok
else
echo_failed
rollback_failed=true
fi
echononl "Check restored Postfix configuration.."
if postfix check > "$log_file" 2>&1 ; then
echo_ok
else
echo_failed
rollback_failed=true
fi
if systemctl is-active --quiet "$POSTFIX_SERVICE" ; then
echononl "Reload restored Postfix configuration.."
if systemctl reload "$POSTFIX_SERVICE" > "$log_file" 2>&1 ; then
echo_ok
else
echo_failed
rollback_failed=true
fi
fi
if $rollback_failed ; then
error "Automatic rollback could not fully restore the previous state. Manual intervention is required."
return 1
fi
info "Previous enabled SRS state was successfully restored."
return 0
}
fatal_with_deactivation_rollback() {
local message="$1"
if $DEACTIVATION_CHANGES_STARTED ; then
if rollback_deactivation ; then
DEACTIVATION_CHANGES_STARTED=false
fatal "${message} The previous state was successfully restored."
else
fatal "${message} Automatic rollback could not fully restore the previous state."
fi
fi
fatal "$message"
}
handle_signal() {
trap - SIGHUP SIGINT SIGTERM
warn "Deactivation interrupted by signal."
if $DEACTIVATION_CHANGES_STARTED ; then
rollback_deactivation
fi
clean_up 1
}
# ----------
# Header
# ----------
blank_line
echo -e "\033[1m----------\033[m"
echo -e "\033[32m\033[1mRunning script \033[m\033[1m${script_name}\033[32m .. \033[m"
echo -e "\033[1m----------\033[m"
blank_line
# ----------
# Basic checks
# ----------
check_root
check_required_commands \
awk \
cp \
cut \
grep \
hostname \
mkdir \
mktemp \
mv \
postfix \
postqueue \
python3 \
realpath \
rm \
sed \
ss \
stat \
systemctl
load_srs_config
load_srs_state
# ----------
# Expected active master.cf values
# ----------
smtp_pre_value=""
amavis_pre_value=""
if $_PRE_SRS_SMTP_RECEIVE_OVERRIDE_OPTIONS_SET ; then
smtp_pre_value="$_PRE_SRS_SMTP_RECEIVE_OVERRIDE_OPTIONS"
fi
if $_PRE_SRS_AMAVIS_RECEIVE_OVERRIDE_OPTIONS_SET ; then
amavis_pre_value="$_PRE_SRS_AMAVIS_RECEIVE_OVERRIDE_OPTIONS"
fi
smtp_active_value="$(csv_add_value "$smtp_pre_value" "no_address_mappings")"
amavis_active_value="$(csv_remove_value "$amavis_pre_value" "no_address_mappings")"
# ----------
# Check installation
# ----------
blank_line
echo -e "\033[37m\033[1mCheck selective SRS installation..\033[m"
blank_line
installation_ok=true
for _file in \
"$POSTFIX_MAIN_CF" \
"$POSTFIX_MASTER_CF" \
"$POSTSRSD_CONF_FILE" \
"$POSTSRSD_SECRET_FILE" \
"${POSTFIX_SRS_DELIVERY_DIR}/main.cf" \
"${POSTFIX_SRS_DELIVERY_DIR}/master.cf" \
"${POSTFIX_SRS_DELIVERY_DIR}/transport" \
"${POSTFIX_SRS_DELIVERY_DIR}/transport.db"
do
echononl "Check file '${_file}'.."
if [[ -r "$_file" ]]; then
echo_ok
else
echo_failed
installation_ok=false
fi
done
for _script in \
"$SRS_ALIAS_DECISION_SCRIPT" \
"$SRS_FORWARD_SCRIPT" \
"$SRS_REVERSE_MAP_SCRIPT" \
"$SRS_REVERSE_POLICY_SCRIPT"
do
echononl "Check executable '${_script}'.."
if [[ -x "$_script" ]]; then
echo_ok
else
echo_failed
installation_ok=false
fi
done
echononl "Check configured PostSRSd SRS domain.."
if [[ "$(postsrsd_srs_domain)" == "$_SRS_DOMAIN" ]]; then
echo_ok
else
echo_failed
installation_ok=false
fi
# ----------
# Check services and listeners
# ----------
blank_line
echo -e "\033[37m\033[1mCheck selective SRS runtime..\033[m"
blank_line
services_exist=true
services_active=true
services_enabled=true
for _service in \
"$POSTSRSD_SERVICE" \
"$SRS_REVERSE_MAP_SERVICE" \
"$SRS_DELIVERY_SERVICE"
do
echononl "Check systemd service '${_service}' exists.."
if service_exists "$_service" ; then
echo_ok
else
echo_failed
services_exist=false
services_active=false
services_enabled=false
continue
fi
echononl "Check systemd service '${_service}' is active.."
if service_active "$_service" ; then
echo_ok
else
echo_inactive
services_active=false
fi
if service_enabled "$_service" ; then
echononl "Check systemd service '${_service}' enable state (enabled).."
echo_ok
else
echononl "Check systemd service '${_service}' enable state (disabled).."
echo_ok
services_enabled=false
fi
done
listeners_active=true
while IFS='|' read -r _label _host _port ; do
if port_is_listening "$_host" "$_port" ; then
echononl "Check ${_label} state on ${_host}:${_port} (listening).."
echo_ok
else
echononl "Check ${_label} state on ${_host}:${_port} (inactive).."
echo_ok
listeners_active=false
fi
done <<EOF
PostSRSd socketmap|${POSTSRSD_SOCKET_HOST}|${POSTSRSD_SOCKET_PORT}
SRS reverse socketmap|${SRS_REVERSE_SOCKET_HOST}|${SRS_REVERSE_SOCKET_PORT}
SRS reinjection listener|${SRS_REINJECT_HOST}|${SRS_REINJECT_PORT}
SRS reverse listener|${SRS_REVERSE_HOST}|${SRS_REVERSE_PORT}
EOF
if systemd_dropins_match_expected ; then
echononl "Check systemd SRS dependency drop-ins state (present).."
echo_ok
systemd_dropins_active=true
else
echononl "Check systemd SRS dependency drop-ins state (absent).."
echo_ok
systemd_dropins_active=false
fi
# ----------
# Check Postfix integration
# ----------
blank_line
echo -e "\033[37m\033[1mCheck Postfix SRS integration..\033[m"
blank_line
main_block_status="invalid"
master_block_status="invalid"
check_managed_block_markers \
"$POSTFIX_MAIN_CF" \
"$SRS_MAIN_CF_BEGIN_MARKER" \
"$SRS_MAIN_CF_END_MARKER"
case "$?" in
0) main_block_status="present" ;;
1) main_block_status="absent" ;;
*) main_block_status="invalid" ;;
esac
echononl "Validate managed SRS block state in '${POSTFIX_MAIN_CF}'.."
case "$main_block_status" in
present) echo_ok ;;
absent) echo_ok ;;
*) echo_failed ;;
esac
check_managed_block_markers \
"$POSTFIX_MASTER_CF" \
"$SRS_MASTER_CF_BEGIN_MARKER" \
"$SRS_MASTER_CF_END_MARKER"
case "$?" in
0) master_block_status="present" ;;
1) master_block_status="absent" ;;
*) master_block_status="invalid" ;;
esac
echononl "Validate managed SRS block state in '${POSTFIX_MASTER_CF}'.."
case "$master_block_status" in
present) echo_ok ;;
absent) echo_ok ;;
*) echo_failed ;;
esac
main_srs_values_ok=true
echononl "Validate 'default_transport' state.."
if [[ "$(main_cf_explicit_value default_transport)" == "srs-forward" ]]; then
echo_ok
elif state_main_cf_matches \
default_transport \
"$_PRE_SRS_DEFAULT_TRANSPORT_SET" \
"$_PRE_SRS_DEFAULT_TRANSPORT"
then
echo_ok
main_srs_values_ok=false
else
echo_differs
main_srs_values_ok=false
fi
echononl "Validate 'srs-forward_destination_recipient_limit' state.."
if [[ "$(main_cf_explicit_value srs-forward_destination_recipient_limit)" == "1" ]]; then
echo_ok
elif state_main_cf_matches \
srs-forward_destination_recipient_limit \
"$_PRE_SRS_SRS_FORWARD_DESTINATION_RECIPIENT_LIMIT_SET" \
"$_PRE_SRS_SRS_FORWARD_DESTINATION_RECIPIENT_LIMIT"
then
echo_ok
main_srs_values_ok=false
else
echo_differs
main_srs_values_ok=false
fi
echononl "Validate 'recipient_canonical_maps' state.."
if [[ "$(main_cf_explicit_value recipient_canonical_maps)" == \
"socketmap:inet:${SRS_REVERSE_SOCKET_HOST}:${SRS_REVERSE_SOCKET_PORT}:reverse" ]]
then
echo_ok
elif state_main_cf_matches \
recipient_canonical_maps \
"$_PRE_SRS_RECIPIENT_CANONICAL_MAPS_SET" \
"$_PRE_SRS_RECIPIENT_CANONICAL_MAPS"
then
echo_ok
main_srs_values_ok=false
else
echo_differs
main_srs_values_ok=false
fi
master_srs_forward_ok=false
smtp_override_ok=false
amavis_override_ok=false
echononl "Validate 'srs-forward' pipe service state.."
if master_srs_forward_present ; then
echo_ok
master_srs_forward_ok=true
else
# Absent is valid in the stored pre-SRS state.
echo_ok
fi
echononl "Validate port 25 receive_override_options state.."
if [[ "$(master_service_option_value smtp receive_override_options)" == "$smtp_active_value" ]]; then
echo_ok
smtp_override_ok=true
elif state_master_option_matches \
smtp \
receive_override_options \
"$_PRE_SRS_SMTP_RECEIVE_OVERRIDE_OPTIONS_SET" \
"$_PRE_SRS_SMTP_RECEIVE_OVERRIDE_OPTIONS"
then
echo_ok
else
echo_differs
fi
echononl "Validate AmaViS reinjection receive_override_options state.."
if [[ "$(master_service_option_value localhost:10025 receive_override_options)" == "$amavis_active_value" ]]
then
echo_ok
amavis_override_ok=true
elif state_master_option_matches \
localhost:10025 \
receive_override_options \
"$_PRE_SRS_AMAVIS_RECEIVE_OVERRIDE_OPTIONS_SET" \
"$_PRE_SRS_AMAVIS_RECEIVE_OVERRIDE_OPTIONS"
then
echo_ok
else
echo_differs
fi
# ----------
# Validate against active and pre-SRS states
# ----------
blank_line
echo -e "\033[37m\033[1mValidate current Postfix configuration against active/pre-SRS states..\033[m"
blank_line
pre_srs_main_ok=true
pre_srs_master_ok=true
echononl "Validate 'default_transport' transition.."
if state_main_cf_matches \
default_transport \
"$_PRE_SRS_DEFAULT_TRANSPORT_SET" \
"$_PRE_SRS_DEFAULT_TRANSPORT"
then
echo_ok
elif [[ "$(main_cf_explicit_value default_transport)" == "srs-forward" ]]; then
echo_ok
pre_srs_main_ok=false
else
echo_differs
pre_srs_main_ok=false
fi
echononl "Validate 'recipient_canonical_maps' transition.."
if state_main_cf_matches \
recipient_canonical_maps \
"$_PRE_SRS_RECIPIENT_CANONICAL_MAPS_SET" \
"$_PRE_SRS_RECIPIENT_CANONICAL_MAPS"
then
echo_ok
elif [[ "$(main_cf_explicit_value recipient_canonical_maps)" == \
"socketmap:inet:${SRS_REVERSE_SOCKET_HOST}:${SRS_REVERSE_SOCKET_PORT}:reverse" ]]
then
echo_ok
pre_srs_main_ok=false
else
echo_differs
pre_srs_main_ok=false
fi
echononl "Validate recipient limit transition.."
if state_main_cf_matches \
srs-forward_destination_recipient_limit \
"$_PRE_SRS_SRS_FORWARD_DESTINATION_RECIPIENT_LIMIT_SET" \
"$_PRE_SRS_SRS_FORWARD_DESTINATION_RECIPIENT_LIMIT"
then
echo_ok
elif [[ "$(main_cf_explicit_value srs-forward_destination_recipient_limit)" == "1" ]]; then
echo_ok
pre_srs_main_ok=false
else
echo_differs
pre_srs_main_ok=false
fi
echononl "Validate port 25 receive_override_options transition.."
if state_master_option_matches \
smtp \
receive_override_options \
"$_PRE_SRS_SMTP_RECEIVE_OVERRIDE_OPTIONS_SET" \
"$_PRE_SRS_SMTP_RECEIVE_OVERRIDE_OPTIONS"
then
echo_ok
elif [[ "$(master_service_option_value smtp receive_override_options)" == "$smtp_active_value" ]]; then
echo_ok
pre_srs_master_ok=false
else
echo_differs
pre_srs_master_ok=false
fi
echononl "Validate AmaViS receive_override_options transition.."
if state_master_option_matches \
localhost:10025 \
receive_override_options \
"$_PRE_SRS_AMAVIS_RECEIVE_OVERRIDE_OPTIONS_SET" \
"$_PRE_SRS_AMAVIS_RECEIVE_OVERRIDE_OPTIONS"
then
echo_ok
elif [[ "$(master_service_option_value localhost:10025 receive_override_options)" == "$amavis_active_value" ]]
then
echo_ok
pre_srs_master_ok=false
else
echo_differs
pre_srs_master_ok=false
fi
# ----------
# Determine state
# ----------
srs_integration_enabled=false
srs_integration_disabled=false
runtime_enabled=false
runtime_disabled=false
srs_state="inconsistent"
if [[ "$main_block_status" == "present" ]] &&
[[ "$master_block_status" != "invalid" ]] &&
$main_srs_values_ok &&
$master_srs_forward_ok &&
$smtp_override_ok &&
$amavis_override_ok
then
srs_integration_enabled=true
fi
if [[ "$main_block_status" == "absent" ]] &&
[[ "$master_block_status" == "absent" ]] &&
$pre_srs_main_ok &&
$pre_srs_master_ok &&
! $master_srs_forward_ok
then
srs_integration_disabled=true
fi
if $services_active &&
$services_enabled &&
$listeners_active &&
$systemd_dropins_active
then
runtime_enabled=true
fi
if ! $services_active &&
! $services_enabled &&
! $listeners_active &&
! $systemd_dropins_active
then
runtime_disabled=true
fi
if $srs_integration_enabled && $runtime_enabled ; then
srs_state="enabled"
elif $srs_integration_disabled && $runtime_disabled ; then
srs_state="disabled"
else
srs_state="inconsistent"
fi
# ----------
# Summary
# ----------
master_block_summary="$master_block_status"
if [[ "$master_block_status" == "absent" && "$srs_state" == "enabled" ]]; then
master_block_summary="absent (legacy accepted)"
fi
blank_line
echo -e "\033[37m\033[1mSelective SRS summary:\033[m"
blank_line
echo " SRS domain.....................................: ${_SRS_DOMAIN}"
echo " Persistent state..............................: ${SRS_STATE_FILE}"
echo " State origin..................................: ${_STATE_ORIGIN}"
echo ""
echo " Managed main.cf block.........................: ${main_block_status}"
echo " Managed master.cf block.......................: ${master_block_summary}"
echo " SRS main.cf values............................: ${main_srs_values_ok}"
echo " srs-forward master.cf service.................: ${master_srs_forward_ok}"
echo " Port 25 SRS receive_override_options..........: ${smtp_override_ok}"
echo " AmaViS SRS receive_override_options...........: ${amavis_override_ok}"
echo ""
echo " SRS services active...........................: ${services_active}"
echo " SRS services enabled..........................: ${services_enabled}"
echo " SRS listeners active..........................: ${listeners_active}"
echo " systemd SRS dependency drop-ins...............: ${systemd_dropins_active}"
echo ""
echo " Current main.cf matches stored pre-SRS state..: ${pre_srs_main_ok}"
echo " Current master.cf matches stored pre-SRS state: ${pre_srs_master_ok}"
echo ""
case "$srs_state" in
enabled)
echo -e " Current selective SRS state....................: \033[32m\033[1mENABLED\033[m"
;;
disabled)
echo -e " Current selective SRS state....................: \033[33m\033[1mDISABLED\033[m"
;;
*)
echo -e " Current selective SRS state....................: \033[31m\033[1mINCONSISTENT\033[m"
;;
esac
blank_line
# ----------
# Handle detected state
# ----------
if [[ "$srs_state" == "disabled" ]]; then
info "Selective SRS is already disabled. Nothing to do."
clean_up 0
fi
if [[ "$srs_state" == "inconsistent" ]]; then
error "Selective SRS configuration is inconsistent. No changes were made."
clean_up 1
fi
if ! $installation_ok || ! $services_exist ; then
fatal "Selective SRS is enabled, but the installation is incomplete. Deactivation was not attempted."
fi
# ----------
# Check SRS delivery queue
# ----------
echononl "Check SRS delivery queue is empty.."
srs_delivery_queue_is_empty
_queue_rc=$?
case "$_queue_rc" in
0)
echo_ok
;;
1)
echo_failed
fatal_with_queue_help \
"SRS delivery queue is not empty. Deactivation was not attempted." \
"${POSTFIX_SRS_DELIVERY_DIR}"
;;
*)
echo_failed
fatal "Could not inspect SRS delivery queue. Deactivation was not attempted. $(cat "$log_file")"
;;
esac
# ----------
# Confirmation
# ----------
blank_line
echo -e "\033[37m\033[1mSelective SRS deactivation settings:\033[m"
blank_line
echo " Current state.................................: ENABLED"
echo " Target state..................................: DISABLED"
echo " Restore state from............................: ${SRS_STATE_FILE}"
echo ""
echo " main.cf.......................................: ${POSTFIX_MAIN_CF}"
echo " master.cf.....................................: ${POSTFIX_MASTER_CF}"
echo ""
echo " PRE-SRS default_transport set.................: ${_PRE_SRS_DEFAULT_TRANSPORT_SET}"
echo " PRE-SRS recipient_canonical_maps set..........: ${_PRE_SRS_RECIPIENT_CANONICAL_MAPS_SET}"
echo " PRE-SRS recipient limit set...................: ${_PRE_SRS_SRS_FORWARD_DESTINATION_RECIPIENT_LIMIT_SET}"
echo " PRE-SRS port 25 receive_override_options......: ${_PRE_SRS_SMTP_RECEIVE_OVERRIDE_OPTIONS:-<not set>}"
echo " PRE-SRS AmaViS receive_override_options.......: ${_PRE_SRS_AMAVIS_RECEIVE_OVERRIDE_OPTIONS:-<not set>}"
echo ""
echo " SRS configuration and binaries will remain installed."
echo " ${SRS_STATE_FILE} will NOT be deleted."
echo ""
if ! ask_yes_no "einverstanden" "no" ; then
warn "Deactivation cancelled by user. Nothing was changed."
clean_up 0
fi
# ----------
# Prepare rollback
# ----------
snapshot_service_states
DEACTIVATION_BACKUP_DIR="/root/postfix-srs-deactivation-${backup_date}"
echononl "Create deactivation backup directory '${DEACTIVATION_BACKUP_DIR}'.."
if mkdir -m 0700 "$DEACTIVATION_BACKUP_DIR" > "$log_file" 2>&1 ; then
echo_ok
else
echo_failed
fatal "$(cat "$log_file")"
fi
echononl "Backup '${POSTFIX_MAIN_CF}'.."
if cp -a "$POSTFIX_MAIN_CF" "${DEACTIVATION_BACKUP_DIR}/main.cf" > "$log_file" 2>&1 ; then
echo_ok
else
echo_failed
fatal "$(cat "$log_file")"
fi
echononl "Backup '${POSTFIX_MASTER_CF}'.."
if cp -a "$POSTFIX_MASTER_CF" "${DEACTIVATION_BACKUP_DIR}/master.cf" > "$log_file" 2>&1 ; then
echo_ok
else
echo_failed
fatal "$(cat "$log_file")"
fi
echononl "Backup systemd SRS drop-ins.."
if snapshot_systemd_dropins > "$log_file" 2>&1 ; then
echo_ok
else
echo_failed
fatal "$(cat "$log_file")"
fi
DEACTIVATION_CHANGES_STARTED=true
# ----------
# Restore main.cf
# ----------
blank_line
echo -e "\033[37m\033[1mRestore pre-SRS Postfix configuration..\033[m"
blank_line
echononl "Remove managed SRS block from '${POSTFIX_MAIN_CF}'.."
if remove_managed_block \
"$POSTFIX_MAIN_CF" \
"$SRS_MAIN_CF_BEGIN_MARKER" \
"$SRS_MAIN_CF_END_MARKER" > "$log_file" 2>&1
then
echo_ok
else
echo_failed
fatal_with_deactivation_rollback "Could not remove managed SRS block from '${POSTFIX_MAIN_CF}'."
fi
echononl "Restore pre-SRS 'default_transport'.."
if restore_main_cf_parameter \
default_transport \
"$_PRE_SRS_DEFAULT_TRANSPORT_SET" \
"$_PRE_SRS_DEFAULT_TRANSPORT" > "$log_file" 2>&1
then
echo_ok
else
echo_failed
fatal_with_deactivation_rollback "Could not restore pre-SRS 'default_transport'."
fi
echononl "Restore pre-SRS 'recipient_canonical_maps'.."
if restore_main_cf_parameter \
recipient_canonical_maps \
"$_PRE_SRS_RECIPIENT_CANONICAL_MAPS_SET" \
"$_PRE_SRS_RECIPIENT_CANONICAL_MAPS" > "$log_file" 2>&1
then
echo_ok
else
echo_failed
fatal_with_deactivation_rollback "Could not restore pre-SRS 'recipient_canonical_maps'."
fi
echononl "Restore pre-SRS 'srs-forward_destination_recipient_limit'.."
if restore_main_cf_parameter \
srs-forward_destination_recipient_limit \
"$_PRE_SRS_SRS_FORWARD_DESTINATION_RECIPIENT_LIMIT_SET" \
"$_PRE_SRS_SRS_FORWARD_DESTINATION_RECIPIENT_LIMIT" > "$log_file" 2>&1
then
echo_ok
else
echo_failed
fatal_with_deactivation_rollback "Could not restore pre-SRS recipient limit."
fi
# ----------
# Restore master.cf
# ----------
if [[ "$master_block_status" == "present" ]]; then
echononl "Remove managed SRS block from '${POSTFIX_MASTER_CF}'.."
if remove_managed_block \
"$POSTFIX_MASTER_CF" \
"$SRS_MASTER_CF_BEGIN_MARKER" \
"$SRS_MASTER_CF_END_MARKER" > "$log_file" 2>&1
then
echo_ok
else
echo_failed
fatal_with_deactivation_rollback "Could not remove managed SRS block from '${POSTFIX_MASTER_CF}'."
fi
else
echononl "Remove legacy unmarked 'srs-forward' service.."
if remove_legacy_srs_forward_service > "$log_file" 2>&1 ; then
echo_ok
else
echo_failed
fatal_with_deactivation_rollback "Could not safely remove legacy unmarked 'srs-forward' service."
fi
fi
echononl "Restore pre-SRS port 25 receive_override_options.."
if update_master_service_option \
smtp \
receive_override_options \
"$_PRE_SRS_SMTP_RECEIVE_OVERRIDE_OPTIONS" > "$log_file" 2>&1
then
if ! $_PRE_SRS_SMTP_RECEIVE_OVERRIDE_OPTIONS_SET ; then
# An empty value means remove the option.
update_master_service_option smtp receive_override_options "" > "$log_file" 2>&1 || {
echo_failed
fatal_with_deactivation_rollback "Could not remove port 25 receive_override_options."
}
fi
echo_ok
else
echo_failed
fatal_with_deactivation_rollback "Could not restore port 25 receive_override_options."
fi
echononl "Restore pre-SRS AmaViS receive_override_options.."
if $_PRE_SRS_AMAVIS_RECEIVE_OVERRIDE_OPTIONS_SET ; then
_amavis_restore="$_PRE_SRS_AMAVIS_RECEIVE_OVERRIDE_OPTIONS"
else
_amavis_restore=""
fi
if update_master_service_option \
localhost:10025 \
receive_override_options \
"$_amavis_restore" > "$log_file" 2>&1
then
echo_ok
else
echo_failed
fatal_with_deactivation_rollback "Could not restore AmaViS receive_override_options."
fi
# ----------
# Remove systemd SRS dependencies
# ----------
echononl "Remove systemd SRS dependency drop-ins.."
if remove_srs_systemd_dropins > "$log_file" 2>&1 ; then
echo_ok
else
echo_failed
fatal_with_deactivation_rollback "Could not remove systemd SRS dependency drop-ins. $(cat "$log_file")"
fi
# ----------
# Validate and reload main Postfix
# ----------
echononl "Check restored Postfix configuration.."
if postfix check > "$log_file" 2>&1 ; then
echo_ok
else
echo_failed
fatal_with_deactivation_rollback "Postfix configuration check failed. $(cat "$log_file")"
fi
echononl "Reload main Postfix service '${POSTFIX_SERVICE}'.."
if systemctl reload "$POSTFIX_SERVICE" > "$log_file" 2>&1 ; then
echo_ok
else
echo_failed
fatal_with_deactivation_rollback "Could not reload '${POSTFIX_SERVICE}'. $(cat "$log_file")"
fi
# ----------
# Stop and disable SRS services
# ----------
blank_line
echo -e "\033[37m\033[1mStop selective SRS runtime services..\033[m"
blank_line
for _service in \
"$SRS_DELIVERY_SERVICE" \
"$SRS_REVERSE_MAP_SERVICE" \
"$POSTSRSD_SERVICE"
do
echononl "Stop systemd service '${_service}'.."
if systemctl stop "$_service" > "$log_file" 2>&1 ; then
echo_ok
else
echo_failed
fatal_with_deactivation_rollback "Could not stop systemd service '${_service}'. $(cat "$log_file")"
fi
echononl "Disable systemd service '${_service}'.."
if systemctl disable "$_service" > "$log_file" 2>&1 ; then
echo_ok
else
echo_failed
fatal_with_deactivation_rollback "Could not disable systemd service '${_service}'. $(cat "$log_file")"
fi
done
# ----------
# Final verification
# ----------
blank_line
echo -e "\033[37m\033[1mVerify disabled selective SRS configuration..\033[m"
blank_line
verification_ok=true
echononl "Verify managed main.cf block is absent.."
check_managed_block_markers \
"$POSTFIX_MAIN_CF" \
"$SRS_MAIN_CF_BEGIN_MARKER" \
"$SRS_MAIN_CF_END_MARKER"
_marker_rc=$?
if [[ "$_marker_rc" -eq 1 ]]; then
echo_ok
else
echo_failed
verification_ok=false
fi
echononl "Verify managed master.cf block is absent.."
check_managed_block_markers \
"$POSTFIX_MASTER_CF" \
"$SRS_MASTER_CF_BEGIN_MARKER" \
"$SRS_MASTER_CF_END_MARKER"
_marker_rc=$?
if [[ "$_marker_rc" -eq 1 ]]; then
echo_ok
else
echo_failed
verification_ok=false
fi
echononl "Verify 'srs-forward' service is absent.."
if ! master_srs_forward_present ; then
echo_ok
else
echo_failed
verification_ok=false
fi
echononl "Verify pre-SRS main.cf state.."
if state_main_cf_matches \
default_transport \
"$_PRE_SRS_DEFAULT_TRANSPORT_SET" \
"$_PRE_SRS_DEFAULT_TRANSPORT" &&
state_main_cf_matches \
recipient_canonical_maps \
"$_PRE_SRS_RECIPIENT_CANONICAL_MAPS_SET" \
"$_PRE_SRS_RECIPIENT_CANONICAL_MAPS" &&
state_main_cf_matches \
srs-forward_destination_recipient_limit \
"$_PRE_SRS_SRS_FORWARD_DESTINATION_RECIPIENT_LIMIT_SET" \
"$_PRE_SRS_SRS_FORWARD_DESTINATION_RECIPIENT_LIMIT"
then
echo_ok
else
echo_failed
verification_ok=false
fi
echononl "Verify pre-SRS master.cf state.."
if state_master_option_matches \
smtp \
receive_override_options \
"$_PRE_SRS_SMTP_RECEIVE_OVERRIDE_OPTIONS_SET" \
"$_PRE_SRS_SMTP_RECEIVE_OVERRIDE_OPTIONS" &&
state_master_option_matches \
localhost:10025 \
receive_override_options \
"$_PRE_SRS_AMAVIS_RECEIVE_OVERRIDE_OPTIONS_SET" \
"$_PRE_SRS_AMAVIS_RECEIVE_OVERRIDE_OPTIONS"
then
echo_ok
else
echo_failed
verification_ok=false
fi
echononl "Verify systemd SRS dependency drop-ins are absent.."
if [[ ! -e "$POSTFIX_SRS_DROPIN_FILE" && ! -e "$SRS_DELIVERY_DROPIN_FILE" ]]; then
echo_ok
else
echo_failed
verification_ok=false
fi
for _service in \
"$POSTSRSD_SERVICE" \
"$SRS_REVERSE_MAP_SERVICE" \
"$SRS_DELIVERY_SERVICE"
do
echononl "Verify systemd service '${_service}' is inactive.."
if ! service_active "$_service" ; then
echo_ok
else
echo_failed
verification_ok=false
fi
echononl "Verify systemd service '${_service}' is disabled.."
if ! service_enabled "$_service" ; then
echo_ok
else
echo_failed
verification_ok=false
fi
done
for _endpoint in \
"PostSRSd socketmap|${POSTSRSD_SOCKET_HOST}|${POSTSRSD_SOCKET_PORT}" \
"SRS reverse socketmap|${SRS_REVERSE_SOCKET_HOST}|${SRS_REVERSE_SOCKET_PORT}" \
"SRS reinjection listener|${SRS_REINJECT_HOST}|${SRS_REINJECT_PORT}" \
"SRS reverse listener|${SRS_REVERSE_HOST}|${SRS_REVERSE_PORT}"
do
IFS='|' read -r _label _host _port <<< "$_endpoint"
echononl "Verify ${_label} is inactive.."
if ! port_is_listening "$_host" "$_port" ; then
echo_ok
else
echo_failed
verification_ok=false
fi
done
echononl "Final Postfix configuration check.."
if postfix check > "$log_file" 2>&1 ; then
echo_ok
else
echo_failed
verification_ok=false
fi
if ! $verification_ok ; then
fatal_with_deactivation_rollback "Final selective SRS deactivation verification failed."
fi
# ----------
# Success
# ----------
DEACTIVATION_CHANGES_STARTED=false
blank_line
info "Selective SRS was successfully deactivated."
echo " Current selective SRS state....................: DISABLED"
echo " Persistent state retained......................: ${SRS_STATE_FILE}"
echo " Deactivation backup............................: ${DEACTIVATION_BACKUP_DIR}"
echo ""
clean_up 0