1893 lines
46 KiB
Bash
Executable File
1893 lines
46 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
# ----------
|
|
# Selective SRS activation
|
|
#
|
|
# Activates selective SRS on a prepared mail server.
|
|
#
|
|
# The script is intentionally conservative:
|
|
#
|
|
# - ENABLED -> no changes, rc=0
|
|
# - DISABLED -> activate after explicit confirmation
|
|
# - INCONSISTENT -> abort without changes
|
|
#
|
|
# Persistent pre-SRS state is read from:
|
|
#
|
|
# /etc/postfix-srs/state.conf
|
|
#
|
|
# That state file is never overwritten by this script.
|
|
# ----------
|
|
|
|
|
|
# ----------
|
|
# 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
|
|
|
|
|
|
# ----------
|
|
# Activation runtime state
|
|
# ----------
|
|
|
|
ACTIVATION_CHANGES_STARTED=false
|
|
ACTIVATION_BACKUP_DIR=""
|
|
POSTFIX_RELOADED=false
|
|
|
|
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() {
|
|
local master_contents
|
|
|
|
# Read failures must not be indistinguishable from an absent service.
|
|
master_contents="$(< "$POSTFIX_MASTER_CF")" || return 2
|
|
|
|
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
|
|
}
|
|
' <<< "$master_contents"
|
|
}
|
|
|
|
|
|
port_is_listening() {
|
|
local host="$1"
|
|
local port="$2"
|
|
local sockets rc
|
|
|
|
# Capture the status of ss separately: without this, an ss failure
|
|
# can look like an empty socket listing.
|
|
sockets="$(ss -ltnH 2>> "$log_file")" || return 2
|
|
|
|
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)
|
|
}
|
|
' <<< "$sockets"
|
|
rc=$?
|
|
case "$rc" in
|
|
0|1) return "$rc" ;;
|
|
*) return 2 ;;
|
|
esac
|
|
}
|
|
|
|
|
|
service_exists() {
|
|
systemctl cat "$1" > /dev/null 2>&1
|
|
}
|
|
|
|
|
|
read_srs_service_state() {
|
|
local output line key value
|
|
local load_state="" active_state="" enabled_state=""
|
|
|
|
SRS_SERVICE_ACTIVE_STATE=""
|
|
SRS_SERVICE_ENABLED_STATE=""
|
|
|
|
output="$(systemctl show "$1" --no-pager \
|
|
-p LoadState -p ActiveState -p UnitFileState 2>> "$log_file")" || return 2
|
|
|
|
while IFS= read -r line ; do
|
|
key="${line%%=*}"
|
|
value="${line#*=}"
|
|
[[ "$line" == *=* ]] || return 2
|
|
|
|
case "$key" in
|
|
LoadState)
|
|
[[ -z "$load_state" ]] || return 2
|
|
load_state="$value"
|
|
;;
|
|
ActiveState)
|
|
[[ -z "$active_state" ]] || return 2
|
|
active_state="$value"
|
|
;;
|
|
UnitFileState)
|
|
[[ -z "$enabled_state" ]] || return 2
|
|
enabled_state="$value"
|
|
;;
|
|
*) return 2 ;;
|
|
esac
|
|
done <<< "$output"
|
|
|
|
[[ "$load_state" == "loaded" ]] || return 2
|
|
case "$active_state" in
|
|
active|inactive) ;;
|
|
*) return 2 ;;
|
|
esac
|
|
case "$enabled_state" in
|
|
enabled|enabled-runtime|disabled) ;;
|
|
*) return 2 ;;
|
|
esac
|
|
|
|
SRS_SERVICE_ACTIVE_STATE="$active_state"
|
|
SRS_SERVICE_ENABLED_STATE="$enabled_state"
|
|
return 0
|
|
}
|
|
|
|
service_active() {
|
|
read_srs_service_state "$1" || return 2
|
|
[[ "$SRS_SERVICE_ACTIVE_STATE" == "active" ]]
|
|
}
|
|
|
|
|
|
service_enabled_state() {
|
|
read_srs_service_state "$1" || return 2
|
|
printf '%s\n' "$SRS_SERVICE_ENABLED_STATE"
|
|
}
|
|
|
|
|
|
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"
|
|
}
|
|
|
|
|
|
srs_alias_tool() {
|
|
local action="$1"
|
|
python3 "$SRS_ALIAS_HELPER" "$action" \
|
|
--main-cf "$POSTFIX_MAIN_CF" \
|
|
--domain "$_SRS_DOMAIN" \
|
|
--reverse-map "socketmap:inet:${SRS_REVERSE_SOCKET_HOST}:${SRS_REVERSE_SOCKET_PORT}:reverse"
|
|
}
|
|
|
|
# Upgrade an already running instance without touching binaries/systemd units.
|
|
# A new snapshot is taken on every attempt and restored on any failure.
|
|
upgrade_existing_srs_aliases() {
|
|
local action="$1" folder snapshot
|
|
folder="/root/postfix-srs-alias-upgrade-${backup_date}"
|
|
snapshot="${folder}/main.cf"
|
|
mkdir -m 0700 "$folder" || fatal "Cannot create alias upgrade backup directory."
|
|
cp -a "$POSTFIX_MAIN_CF" "$snapshot" || fatal "Cannot snapshot main.cf for alias upgrade."
|
|
echononl "Track incoming SRS bounce aliases (${action}).."
|
|
if srs_alias_tool "$action" > "$log_file" 2>&1 &&
|
|
postfix check >> "$log_file" 2>&1 &&
|
|
[[ "$(srs_alias_tool status 2>>"$log_file")" == 'managed' ]] &&
|
|
{ [[ "$action" == 'adopt' ]] || systemctl reload "$POSTFIX_SERVICE" >> "$log_file" 2>&1; }
|
|
then
|
|
echo_ok
|
|
info "SRS inbound alias settings now managed. Backup: ${snapshot}"
|
|
clean_up 0
|
|
fi
|
|
echo_failed
|
|
error "Alias upgrade failed: $(cat "$log_file")"
|
|
# A pure adoption changes only a comment. Never reload Postfix on
|
|
# either the success or rollback path for that operation.
|
|
if cp -a "$snapshot" "$POSTFIX_MAIN_CF" &&
|
|
postfix check > "$log_file" 2>&1 &&
|
|
{ [[ "$action" == 'adopt' ]] || systemctl reload "$POSTFIX_SERVICE" >> "$log_file" 2>&1; }
|
|
then
|
|
fatal "Alias upgrade failed; previous main.cf restored successfully."
|
|
fi
|
|
fatal "Alias upgrade failed; automatic restore incomplete. Manual review: ${snapshot}"
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
|
|
append_main_cf_managed_block() {
|
|
local tmp
|
|
|
|
tmp="$(mktemp "${POSTFIX_CONF_DIR}/.main.cf.srs.XXXXXX")" || return 1
|
|
|
|
if ! cp -a "$POSTFIX_MAIN_CF" "$tmp" ; then
|
|
rm -f "$tmp"
|
|
return 1
|
|
fi
|
|
|
|
cat >> "$tmp" <<EOF
|
|
|
|
${SRS_MAIN_CF_BEGIN_MARKER}
|
|
# ============================================================================
|
|
# Selective Sender Rewriting Scheme (SRS)
|
|
#
|
|
# ACHTUNG: AUTOMATISCH VERWALTETER KONFIGURATIONSBLOCK
|
|
#
|
|
# Dieser Block wird von den SRS-Verwaltungs-Scripten automatisch erzeugt
|
|
# und gepflegt. Er sollte nicht manuell veraendert werden.
|
|
#
|
|
# Start- und End-Marker duerfen nicht umbenannt oder entfernt werden.
|
|
# Manuelle Aenderungen innerhalb dieses Blocks koennen bei einem spaeteren
|
|
# Installations-, Update-, Enable- oder Disable-Lauf ueberschrieben werden.
|
|
# ============================================================================
|
|
# ID: postfix-selective-srs
|
|
# Version: 2
|
|
# State: enabled
|
|
#
|
|
# default_transport leitet Empfaenger zunaechst an den selektiven
|
|
# srs-forward Transport. Das Entscheidungsscript entscheidet je Empfaenger,
|
|
# ob SRS notwendig ist oder die Nachricht unveraendert weitergegeben wird.
|
|
#
|
|
# Der recipient limit von 1 stellt sicher, dass die Entscheidung fuer jeden
|
|
# Empfaenger einzeln getroffen werden kann.
|
|
#
|
|
# recipient_canonical_maps dient der Rueckumsetzung eingehender SRS-Adressen.
|
|
#
|
|
default_transport = srs-forward
|
|
srs-forward_destination_recipient_limit = 1
|
|
recipient_canonical_maps = socketmap:inet:${SRS_REVERSE_SOCKET_HOST}:${SRS_REVERSE_SOCKET_PORT}:reverse
|
|
${SRS_MAIN_CF_END_MARKER}
|
|
EOF
|
|
|
|
if ! mv "$tmp" "$POSTFIX_MAIN_CF" ; then
|
|
rm -f "$tmp"
|
|
return 1
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
append_master_cf_managed_block() {
|
|
local tmp
|
|
|
|
tmp="$(mktemp "${POSTFIX_CONF_DIR}/.master.cf.srs.XXXXXX")" || return 1
|
|
|
|
if ! cp -a "$POSTFIX_MASTER_CF" "$tmp" ; then
|
|
rm -f "$tmp"
|
|
return 1
|
|
fi
|
|
|
|
cat >> "$tmp" <<'EOF'
|
|
|
|
# BEGIN OOPEN-SRS-MANAGED
|
|
# ============================================================================
|
|
# Selective Sender Rewriting Scheme (SRS)
|
|
#
|
|
# This block is maintained by the SRS management scripts.
|
|
# Do not rename or remove the BEGIN/END markers.
|
|
# ============================================================================
|
|
srs-forward unix - n n - - pipe
|
|
flags=Rq null_sender= user=nobody
|
|
argv=/usr/local/sbin/srs-forward.py ${sender} ${recipient} ${original_recipient} ${queue_id} envid=${envid}
|
|
# END OOPEN-SRS-MANAGED
|
|
EOF
|
|
|
|
if ! mv "$tmp" "$POSTFIX_MASTER_CF" ; then
|
|
rm -f "$tmp"
|
|
return 1
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
snapshot_systemd_dropins() {
|
|
|
|
if [[ -f "$POSTFIX_SRS_DROPIN_FILE" ]]; then
|
|
POSTFIX_SRS_DROPIN_WAS_PRESENT=true
|
|
cp -a "$POSTFIX_SRS_DROPIN_FILE" \
|
|
"${ACTIVATION_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" \
|
|
"${ACTIVATION_BACKUP_DIR}/postfix@srs-delivery.service-srs.conf" || return 1
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
|
|
write_srs_systemd_dropins() {
|
|
|
|
mkdir -p "$POSTFIX_SRS_DROPIN_DIR" "$SRS_DELIVERY_DROPIN_DIR" || return 1
|
|
|
|
cat > "$POSTFIX_SRS_DROPIN_FILE" <<EOF
|
|
[Unit]
|
|
Wants=${SRS_REVERSE_MAP_SERVICE} ${SRS_DELIVERY_SERVICE}
|
|
After=${SRS_REVERSE_MAP_SERVICE} ${SRS_DELIVERY_SERVICE}
|
|
EOF
|
|
|
|
cat > "$SRS_DELIVERY_DROPIN_FILE" <<EOF
|
|
[Unit]
|
|
Wants=${POSTSRSD_SERVICE}
|
|
After=${POSTSRSD_SERVICE}
|
|
EOF
|
|
|
|
chown root:root \
|
|
"$POSTFIX_SRS_DROPIN_FILE" \
|
|
"$SRS_DELIVERY_DROPIN_FILE" || return 1
|
|
|
|
chmod 0644 \
|
|
"$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
|
|
cp -a \
|
|
"${ACTIVATION_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
|
|
cp -a \
|
|
"${ACTIVATION_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
|
|
local -A snapshot_active=() snapshot_enabled=()
|
|
|
|
for service in \
|
|
"$POSTSRSD_SERVICE" \
|
|
"$SRS_REVERSE_MAP_SERVICE" \
|
|
"$SRS_DELIVERY_SERVICE"
|
|
do
|
|
if ! read_srs_service_state "$service" ; then
|
|
warn "Cannot determine a supported systemd state for '${service}'."
|
|
return 1
|
|
fi
|
|
|
|
# The caller has classified this installation as DISABLED. Reject
|
|
# a state transition since that check rather than capturing an
|
|
# unexpectedly started/enabled service as the rollback baseline.
|
|
if [[ "$SRS_SERVICE_ACTIVE_STATE" != "inactive" ||
|
|
"$SRS_SERVICE_ENABLED_STATE" != "disabled" ]]; then
|
|
warn "SRS service '${service}' changed state before activation."
|
|
return 1
|
|
fi
|
|
snapshot_active["$service"]="false"
|
|
snapshot_enabled["$service"]="$SRS_SERVICE_ENABLED_STATE"
|
|
done
|
|
|
|
# Only publish a complete, validated snapshot.
|
|
for service in \
|
|
"$POSTSRSD_SERVICE" \
|
|
"$SRS_REVERSE_MAP_SERVICE" \
|
|
"$SRS_DELIVERY_SERVICE"
|
|
do
|
|
SERVICE_WAS_ACTIVE["$service"]="${snapshot_active[$service]}"
|
|
SERVICE_WAS_ENABLED["$service"]="${snapshot_enabled[$service]}"
|
|
done
|
|
return 0
|
|
}
|
|
|
|
|
|
restore_service_states() {
|
|
local service
|
|
local restore_failed=false
|
|
local enabled_state active_state
|
|
|
|
# Validate the entire snapshot before changing *any* unit.
|
|
for service in \
|
|
"$POSTSRSD_SERVICE" \
|
|
"$SRS_REVERSE_MAP_SERVICE" \
|
|
"$SRS_DELIVERY_SERVICE"
|
|
do
|
|
enabled_state="${SERVICE_WAS_ENABLED[$service]:-}"
|
|
active_state="${SERVICE_WAS_ACTIVE[$service]:-}"
|
|
case "$enabled_state" in
|
|
enabled|enabled-runtime|disabled) ;;
|
|
*) warn "Invalid saved enable state for '${service}': '${enabled_state}'."; return 1 ;;
|
|
esac
|
|
case "$active_state" in
|
|
true|false) ;;
|
|
*) warn "Invalid saved active state for '${service}': '${active_state}'."; return 1 ;;
|
|
esac
|
|
done
|
|
|
|
for service in \
|
|
"$POSTSRSD_SERVICE" \
|
|
"$SRS_REVERSE_MAP_SERVICE" \
|
|
"$SRS_DELIVERY_SERVICE"
|
|
do
|
|
enabled_state="${SERVICE_WAS_ENABLED[$service]}"
|
|
|
|
case "$enabled_state" in
|
|
enabled)
|
|
systemctl enable "$service" > /dev/null 2>> "$log_file" || restore_failed=true
|
|
;;
|
|
enabled-runtime)
|
|
systemctl enable --runtime "$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]}" == "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
|
|
}
|
|
|
|
|
|
rollback_activation() {
|
|
local rollback_failed=false
|
|
|
|
if ! $ACTIVATION_CHANGES_STARTED ; then
|
|
return 0
|
|
fi
|
|
|
|
blank_line
|
|
warn "SRS activation failed. Trying to restore the previous state."
|
|
|
|
if [[ -n "$ACTIVATION_BACKUP_DIR" &&
|
|
-r "${ACTIVATION_BACKUP_DIR}/main.cf" &&
|
|
-r "${ACTIVATION_BACKUP_DIR}/master.cf" ]]
|
|
then
|
|
echononl "Restore '${POSTFIX_MAIN_CF}'.."
|
|
if cp -a "${ACTIVATION_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 "${ACTIVATION_BACKUP_DIR}/master.cf" "$POSTFIX_MASTER_CF" > "$log_file" 2>&1 ; then
|
|
echo_ok
|
|
else
|
|
echo_failed
|
|
rollback_failed=true
|
|
fi
|
|
else
|
|
error "Activation backup is incomplete. Cannot automatically restore Postfix configuration."
|
|
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
|
|
else
|
|
echononl "Check primary Postfix service '${POSTFIX_SERVICE}' is active.."
|
|
echo_failed
|
|
warn "Primary Postfix is inactive; restored configuration could not be reloaded."
|
|
rollback_failed=true
|
|
fi
|
|
|
|
if $rollback_failed ; then
|
|
error "Automatic rollback could not fully restore the previous state. Manual intervention is required."
|
|
return 1
|
|
fi
|
|
|
|
info "Previous state was successfully restored."
|
|
return 0
|
|
}
|
|
|
|
|
|
fatal_with_activation_rollback() {
|
|
local message="$1"
|
|
|
|
if $ACTIVATION_CHANGES_STARTED ; then
|
|
if rollback_activation ; then
|
|
ACTIVATION_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 "Activation interrupted by signal."
|
|
|
|
if $ACTIVATION_CHANGES_STARTED ; then
|
|
rollback_activation
|
|
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 \
|
|
rm \
|
|
python3 \
|
|
realpath \
|
|
sed \
|
|
ss \
|
|
stat \
|
|
systemctl
|
|
|
|
load_srs_config
|
|
load_srs_state
|
|
[[ -f "$SRS_ALIAS_HELPER" && ! -L "$SRS_ALIAS_HELPER" ]] ||
|
|
fatal "Missing regular SRS alias helper: ${SRS_ALIAS_HELPER}"
|
|
|
|
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 required installation files
|
|
# ----------
|
|
|
|
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 systemd services
|
|
# ----------
|
|
|
|
blank_line
|
|
echo -e "\033[37m\033[1mCheck selective SRS services..\033[m"
|
|
blank_line
|
|
|
|
services_exist=true
|
|
services_active=true
|
|
services_enabled=true
|
|
services_disabled=true
|
|
services_status_known=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
|
|
services_disabled=false
|
|
services_status_known=false
|
|
continue
|
|
fi
|
|
|
|
echononl "Read systemd state of '${_service}'.."
|
|
if ! read_srs_service_state "$_service" ; then
|
|
echo_failed
|
|
services_status_known=false
|
|
services_active=false
|
|
services_enabled=false
|
|
services_disabled=false
|
|
continue
|
|
fi
|
|
echo_ok
|
|
|
|
if [[ "$SRS_SERVICE_ACTIVE_STATE" != "active" ]]; then
|
|
services_active=false
|
|
fi
|
|
if [[ "$SRS_SERVICE_ENABLED_STATE" == "disabled" ]]; then
|
|
services_enabled=false
|
|
fi
|
|
if [[ "$SRS_SERVICE_ACTIVE_STATE" != "inactive" ||
|
|
"$SRS_SERVICE_ENABLED_STATE" != "disabled" ]]; then
|
|
services_disabled=false
|
|
fi
|
|
done
|
|
|
|
echononl "Check main Postfix service '${POSTFIX_SERVICE}' is active.."
|
|
primary_postfix_active=false
|
|
if systemctl is-active --quiet "$POSTFIX_SERVICE" ; then
|
|
echo_ok
|
|
primary_postfix_active=true
|
|
else
|
|
echo_failed
|
|
fi
|
|
|
|
|
|
# ----------
|
|
# Check listeners
|
|
# ----------
|
|
|
|
blank_line
|
|
echo -e "\033[37m\033[1mCheck selective SRS listeners..\033[m"
|
|
blank_line
|
|
|
|
listeners_ok=true
|
|
listeners_absent=true
|
|
listeners_status_known=true
|
|
|
|
while IFS='|' read -r _label _host _port ; do
|
|
port_is_listening "$_host" "$_port"
|
|
_port_rc=$?
|
|
case "$_port_rc" in
|
|
0)
|
|
echononl "Check ${_label} state on ${_host}:${_port} (listening).."
|
|
echo_ok
|
|
listeners_absent=false
|
|
;;
|
|
1)
|
|
echononl "Check ${_label} state on ${_host}:${_port} (inactive).."
|
|
echo_ok
|
|
listeners_ok=false
|
|
;;
|
|
*)
|
|
echononl "Check ${_label} state on ${_host}:${_port} (query failed).."
|
|
echo_failed
|
|
listeners_ok=false
|
|
listeners_absent=false
|
|
listeners_status_known=false
|
|
;;
|
|
esac
|
|
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
|
|
|
|
dropins_absent=true
|
|
for _dropin in "$POSTFIX_SRS_DROPIN_FILE" "$SRS_DELIVERY_DROPIN_FILE" ; do
|
|
if [[ -e "$_dropin" || -L "$_dropin" ]]; then
|
|
dropins_absent=false
|
|
fi
|
|
done
|
|
|
|
|
|
# ----------
|
|
# Determine main.cf state
|
|
# ----------
|
|
|
|
blank_line
|
|
echo -e "\033[37m\033[1mCheck main Postfix SRS integration..\033[m"
|
|
blank_line
|
|
|
|
main_block_status="invalid"
|
|
|
|
check_managed_block_markers \
|
|
"$POSTFIX_MAIN_CF" \
|
|
"$SRS_MAIN_CF_BEGIN_MARKER" \
|
|
"$SRS_MAIN_CF_END_MARKER"
|
|
|
|
_marker_rc=$?
|
|
|
|
case "$_marker_rc" in
|
|
0)
|
|
main_block_status="present"
|
|
echononl "Validate managed SRS block state in '${POSTFIX_MAIN_CF}'.."
|
|
echo_ok
|
|
;;
|
|
1)
|
|
main_block_status="absent"
|
|
echononl "Validate managed SRS block state in '${POSTFIX_MAIN_CF}'.."
|
|
echo_ok
|
|
;;
|
|
*)
|
|
main_block_status="invalid"
|
|
echononl "Validate managed SRS block state in '${POSTFIX_MAIN_CF}'.."
|
|
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
|
|
|
|
|
|
# ----------
|
|
# Determine master.cf state
|
|
# ----------
|
|
|
|
blank_line
|
|
echo -e "\033[37m\033[1mCheck Postfix master.cf SRS integration..\033[m"
|
|
blank_line
|
|
|
|
master_block_status="invalid"
|
|
|
|
check_managed_block_markers \
|
|
"$POSTFIX_MASTER_CF" \
|
|
"$SRS_MASTER_CF_BEGIN_MARKER" \
|
|
"$SRS_MASTER_CF_END_MARKER"
|
|
|
|
_master_marker_rc=$?
|
|
|
|
case "$_master_marker_rc" in
|
|
0)
|
|
master_block_status="present"
|
|
echononl "Validate managed SRS block state in '${POSTFIX_MASTER_CF}'.."
|
|
echo_ok
|
|
;;
|
|
1)
|
|
master_block_status="absent"
|
|
echononl "Validate managed SRS block state in '${POSTFIX_MASTER_CF}'.."
|
|
echo_ok
|
|
;;
|
|
*)
|
|
master_block_status="invalid"
|
|
echononl "Validate managed SRS block state in '${POSTFIX_MASTER_CF}'.."
|
|
echo_failed
|
|
;;
|
|
esac
|
|
|
|
master_srs_forward_ok=false
|
|
master_srs_forward_known=true
|
|
smtp_override_ok=false
|
|
amavis_override_ok=false
|
|
|
|
echononl "Validate 'srs-forward' pipe service state.."
|
|
master_srs_forward_present
|
|
_master_service_rc=$?
|
|
case "$_master_service_rc" in
|
|
0)
|
|
echo_ok
|
|
master_srs_forward_ok=true
|
|
;;
|
|
1)
|
|
# Absent is valid in the stored pre-SRS state.
|
|
echo_ok
|
|
;;
|
|
*)
|
|
echo_failed
|
|
master_srs_forward_known=false
|
|
;;
|
|
esac
|
|
|
|
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 selective SRS state
|
|
# ----------
|
|
|
|
srs_integration_enabled=false
|
|
srs_integration_disabled=false
|
|
srs_runtime_ready=false
|
|
activation_ready=false
|
|
srs_state="inconsistent"
|
|
|
|
if [[ "$main_block_status" == "present" ]] &&
|
|
[[ "$master_block_status" != "invalid" ]] &&
|
|
$master_srs_forward_known &&
|
|
$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" ]] &&
|
|
$master_srs_forward_known &&
|
|
$pre_srs_main_ok &&
|
|
$pre_srs_master_ok &&
|
|
! $master_srs_forward_ok
|
|
then
|
|
srs_integration_disabled=true
|
|
fi
|
|
|
|
if $installation_ok &&
|
|
$services_exist &&
|
|
$services_status_known &&
|
|
$services_active &&
|
|
$services_enabled &&
|
|
$primary_postfix_active &&
|
|
$listeners_status_known &&
|
|
$listeners_ok
|
|
then
|
|
srs_runtime_ready=true
|
|
fi
|
|
|
|
if $installation_ok &&
|
|
$services_exist
|
|
then
|
|
activation_ready=true
|
|
fi
|
|
|
|
if $srs_integration_enabled && $srs_runtime_ready ; then
|
|
srs_state="enabled"
|
|
elif $srs_integration_disabled &&
|
|
$services_exist &&
|
|
$services_status_known &&
|
|
$services_disabled &&
|
|
$primary_postfix_active &&
|
|
$listeners_status_known &&
|
|
$listeners_absent &&
|
|
$dropins_absent
|
|
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 no_address_mappings...................: ${smtp_override_ok}"
|
|
echo " AmaViS without no_address_mappings............: ${amavis_override_ok}"
|
|
echo ""
|
|
echo " SRS installation files........................: ${installation_ok}"
|
|
echo " SRS services exist............................: ${services_exist}"
|
|
echo " SRS services active...........................: ${services_active}"
|
|
echo " SRS listeners.................................: ${listeners_ok}"
|
|
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
|
|
# ----------
|
|
|
|
# A running v1 installation can be upgraded without reinstalling PostSRSd.
|
|
srs_alias_status="$(srs_alias_tool status 2>"$log_file")" ||
|
|
fatal "Unsafe virtual alias configuration: $(cat "$log_file")"
|
|
|
|
if [[ "$srs_state" == "enabled" ]]; then
|
|
case "$srs_alias_status" in
|
|
managed)
|
|
info "Selective SRS with inbound bounce reception is already enabled. Nothing to do."
|
|
clean_up 0
|
|
;;
|
|
absent|legacy)
|
|
warn "SRS is enabled but bounce alias ownership is not tracked (${srs_alias_status})."
|
|
info "Only virtual alias parameters will be updated; PostSRSd binaries and services remain untouched."
|
|
if ! ask_yes_no "Upgrade incoming SRS alias management" "no"; then
|
|
info "Upgrade cancelled without changes."
|
|
clean_up 0
|
|
fi
|
|
if [[ "$srs_alias_status" == 'legacy' ]]; then
|
|
upgrade_existing_srs_aliases adopt
|
|
else
|
|
upgrade_existing_srs_aliases add
|
|
fi
|
|
;;
|
|
*) fatal "Unexpected SRS alias status: ${srs_alias_status}" ;;
|
|
esac
|
|
fi
|
|
|
|
if [[ "$srs_state" == "inconsistent" ]]; then
|
|
error "Selective SRS configuration is inconsistent. No changes were made."
|
|
clean_up 1
|
|
fi
|
|
|
|
if ! $activation_ready ; then
|
|
fatal "Selective SRS is disabled, but the installation is incomplete. Activation is not possible."
|
|
fi
|
|
[[ "$srs_alias_status" == 'absent' ]] ||
|
|
fatal "SRS disabled but alias entries are not absent. Refusing activation."
|
|
|
|
|
|
# ----------
|
|
# Activation summary
|
|
# ----------
|
|
|
|
blank_line
|
|
echo -e "\033[37m\033[1mSelective SRS activation settings:\033[m"
|
|
blank_line
|
|
|
|
echo " Current state.................................: DISABLED"
|
|
echo " Target state..................................: ENABLED"
|
|
echo " SRS domain....................................: ${_SRS_DOMAIN}"
|
|
echo ""
|
|
echo " main.cf.......................................: ${POSTFIX_MAIN_CF}"
|
|
echo " master.cf.....................................: ${POSTFIX_MASTER_CF}"
|
|
echo ""
|
|
echo " Port 25 receive_override_options..............: ${smtp_active_value}"
|
|
echo " AmaViS receive_override_options...............: ${amavis_active_value:-<not set>}"
|
|
echo ""
|
|
|
|
if ! ask_yes_no "einverstanden" "no" ; then
|
|
warn "Activation cancelled by user. Nothing was changed."
|
|
clean_up 0
|
|
fi
|
|
|
|
|
|
# ----------
|
|
# Prepare rollback
|
|
# ----------
|
|
|
|
echononl "Snapshot disabled SRS service states.."
|
|
if snapshot_service_states ; then
|
|
echo_ok
|
|
else
|
|
echo_failed
|
|
fatal "Cannot safely snapshot disabled SRS services. Activation was not attempted."
|
|
fi
|
|
|
|
ACTIVATION_BACKUP_DIR="/root/postfix-srs-activation-${backup_date}"
|
|
|
|
echononl "Create activation backup directory.."
|
|
if mkdir -m 0700 "$ACTIVATION_BACKUP_DIR" > "$log_file" 2>&1 ; then
|
|
echo_ok
|
|
printf ' Directory: %s\n' "$ACTIVATION_BACKUP_DIR"
|
|
else
|
|
echo_failed
|
|
fatal "$(cat "$log_file")"
|
|
fi
|
|
|
|
echononl "Backup '${POSTFIX_MAIN_CF}'.."
|
|
if cp -a "$POSTFIX_MAIN_CF" "${ACTIVATION_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" "${ACTIVATION_BACKUP_DIR}/master.cf" > "$log_file" 2>&1 ; then
|
|
echo_ok
|
|
else
|
|
echo_failed
|
|
fatal "$(cat "$log_file")"
|
|
fi
|
|
|
|
echononl "Backup existing systemd SRS drop-ins.."
|
|
if snapshot_systemd_dropins > "$log_file" 2>&1 ; then
|
|
echo_ok
|
|
else
|
|
echo_failed
|
|
fatal "$(cat "$log_file")"
|
|
fi
|
|
|
|
echononl "Recheck primary Postfix service '${POSTFIX_SERVICE}' is active.."
|
|
if systemctl is-active --quiet "$POSTFIX_SERVICE" ; then
|
|
echo_ok
|
|
else
|
|
echo_failed
|
|
fatal "Primary Postfix became inactive. Activation was not attempted."
|
|
fi
|
|
|
|
ACTIVATION_CHANGES_STARTED=true
|
|
|
|
|
|
# ----------
|
|
# Activate Postfix integration
|
|
# ----------
|
|
|
|
blank_line
|
|
echo -e "\033[37m\033[1mActivate selective SRS Postfix integration..\033[m"
|
|
blank_line
|
|
|
|
echononl "Add managed SRS block to '${POSTFIX_MAIN_CF}'.."
|
|
if append_main_cf_managed_block > "$log_file" 2>&1 ; then
|
|
echo_ok
|
|
else
|
|
echo_failed
|
|
fatal_with_activation_rollback "Could not add managed SRS block to '${POSTFIX_MAIN_CF}'."
|
|
fi
|
|
|
|
echononl "Add managed SRS service block to '${POSTFIX_MASTER_CF}'.."
|
|
if append_master_cf_managed_block > "$log_file" 2>&1 ; then
|
|
echo_ok
|
|
else
|
|
echo_failed
|
|
fatal_with_activation_rollback "Could not add managed SRS block to '${POSTFIX_MASTER_CF}'."
|
|
fi
|
|
|
|
echononl "Add incoming SRS virtual alias domain and reverse map.."
|
|
if srs_alias_tool add > "$log_file" 2>&1 ; then
|
|
echo_ok
|
|
else
|
|
echo_failed
|
|
fatal_with_activation_rollback "Could not add tracked virtual alias items: $(cat "$log_file")"
|
|
fi
|
|
|
|
echononl "Set port 25 receive_override_options.."
|
|
if update_master_service_option \
|
|
smtp \
|
|
receive_override_options \
|
|
"$smtp_active_value" > "$log_file" 2>&1
|
|
then
|
|
echo_ok
|
|
else
|
|
echo_failed
|
|
fatal_with_activation_rollback "Could not update port 25 receive_override_options."
|
|
fi
|
|
|
|
echononl "Set AmaViS reinjection receive_override_options.."
|
|
if update_master_service_option \
|
|
localhost:10025 \
|
|
receive_override_options \
|
|
"$amavis_active_value" > "$log_file" 2>&1
|
|
then
|
|
echo_ok
|
|
else
|
|
echo_failed
|
|
fatal_with_activation_rollback "Could not update AmaViS reinjection receive_override_options."
|
|
fi
|
|
|
|
echononl "Check Postfix configuration.."
|
|
if postfix check > "$log_file" 2>&1 ; then
|
|
echo_ok
|
|
else
|
|
echo_failed
|
|
fatal_with_activation_rollback "Postfix configuration check failed. $(cat "$log_file")"
|
|
fi
|
|
|
|
|
|
# ----------
|
|
# Install systemd dependencies and start/enable SRS runtime services
|
|
# ----------
|
|
|
|
blank_line
|
|
echo -e "\033[37m\033[1mStart selective SRS runtime services..\033[m"
|
|
blank_line
|
|
|
|
echononl "Install systemd SRS dependency drop-ins.."
|
|
if write_srs_systemd_dropins > "$log_file" 2>&1 ; then
|
|
echo_ok
|
|
else
|
|
echo_failed
|
|
fatal_with_activation_rollback "Could not install systemd SRS dependency drop-ins. $(cat "$log_file")"
|
|
fi
|
|
|
|
for _service in \
|
|
"$POSTSRSD_SERVICE" \
|
|
"$SRS_REVERSE_MAP_SERVICE" \
|
|
"$SRS_DELIVERY_SERVICE"
|
|
do
|
|
echononl "Enable systemd service '${_service}'.."
|
|
|
|
if systemctl enable "$_service" > "$log_file" 2>&1 ; then
|
|
echo_ok
|
|
else
|
|
echo_failed
|
|
fatal_with_activation_rollback "Could not enable systemd service '${_service}'. $(cat "$log_file")"
|
|
fi
|
|
|
|
echononl "Start systemd service '${_service}'.."
|
|
|
|
if systemctl start "$_service" > "$log_file" 2>&1 ; then
|
|
echo_ok
|
|
else
|
|
echo_failed
|
|
fatal_with_activation_rollback "Could not start systemd service '${_service}'. $(cat "$log_file")"
|
|
fi
|
|
done
|
|
|
|
|
|
# ----------
|
|
# Verify listeners before Postfix reload
|
|
# ----------
|
|
|
|
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} on ${_host}:${_port}.."
|
|
|
|
if port_is_listening "$_host" "$_port" ; then
|
|
echo_ok
|
|
else
|
|
echo_failed
|
|
fatal_with_activation_rollback "${_label} is not listening on ${_host}:${_port}."
|
|
fi
|
|
done
|
|
|
|
|
|
# ----------
|
|
# Reload main Postfix
|
|
# ----------
|
|
|
|
blank_line
|
|
echononl "Reload main Postfix service '${POSTFIX_SERVICE}'.."
|
|
|
|
if systemctl reload "$POSTFIX_SERVICE" > "$log_file" 2>&1 ; then
|
|
POSTFIX_RELOADED=true
|
|
echo_ok
|
|
else
|
|
echo_failed
|
|
fatal_with_activation_rollback "Could not reload '${POSTFIX_SERVICE}'. $(cat "$log_file")"
|
|
fi
|
|
|
|
|
|
# ----------
|
|
# Final verification
|
|
# ----------
|
|
|
|
blank_line
|
|
echo -e "\033[37m\033[1mVerify activated selective SRS configuration..\033[m"
|
|
blank_line
|
|
|
|
verification_ok=true
|
|
|
|
echononl "Verify managed main.cf block.."
|
|
if check_managed_block_markers \
|
|
"$POSTFIX_MAIN_CF" \
|
|
"$SRS_MAIN_CF_BEGIN_MARKER" \
|
|
"$SRS_MAIN_CF_END_MARKER"
|
|
then
|
|
echo_ok
|
|
else
|
|
echo_failed
|
|
verification_ok=false
|
|
fi
|
|
|
|
echononl "Verify managed master.cf block.."
|
|
if check_managed_block_markers \
|
|
"$POSTFIX_MASTER_CF" \
|
|
"$SRS_MASTER_CF_BEGIN_MARKER" \
|
|
"$SRS_MASTER_CF_END_MARKER"
|
|
then
|
|
echo_ok
|
|
else
|
|
echo_failed
|
|
verification_ok=false
|
|
fi
|
|
|
|
echononl "Verify 'default_transport = srs-forward'.."
|
|
if [[ "$(main_cf_explicit_value default_transport)" == "srs-forward" ]]; then
|
|
echo_ok
|
|
else
|
|
echo_failed
|
|
verification_ok=false
|
|
fi
|
|
|
|
echononl "Verify SRS recipient canonical map.."
|
|
if [[ "$(main_cf_explicit_value recipient_canonical_maps)" == \
|
|
"socketmap:inet:${SRS_REVERSE_SOCKET_HOST}:${SRS_REVERSE_SOCKET_PORT}:reverse" ]]
|
|
then
|
|
echo_ok
|
|
else
|
|
echo_failed
|
|
verification_ok=false
|
|
fi
|
|
|
|
echononl "Verify incoming SRS alias ownership.."
|
|
if [[ "$(srs_alias_tool status 2>"$log_file")" == 'managed' ]]; then
|
|
echo_ok
|
|
else
|
|
echo_failed
|
|
verification_ok=false
|
|
fi
|
|
|
|
echononl "Verify 'srs-forward' pipe service.."
|
|
if master_srs_forward_present ; then
|
|
echo_ok
|
|
else
|
|
echo_failed
|
|
verification_ok=false
|
|
fi
|
|
|
|
echononl "Verify port 25 receive_override_options.."
|
|
if [[ "$(master_service_option_value smtp receive_override_options)" == "$smtp_active_value" ]]; then
|
|
echo_ok
|
|
else
|
|
echo_failed
|
|
verification_ok=false
|
|
fi
|
|
|
|
echononl "Verify AmaViS reinjection receive_override_options.."
|
|
if [[ "$(master_service_option_value localhost:10025 receive_override_options)" == "$amavis_active_value" ]]; 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 active.."
|
|
|
|
if read_srs_service_state "$_service" &&
|
|
[[ "$SRS_SERVICE_ACTIVE_STATE" == "active" ]] &&
|
|
[[ "$SRS_SERVICE_ENABLED_STATE" != "disabled" ]]
|
|
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} remains listening on ${_host}:${_port}.."
|
|
if port_is_listening "$_host" "$_port" ; then
|
|
echo_ok
|
|
else
|
|
echo_failed
|
|
verification_ok=false
|
|
fi
|
|
done
|
|
|
|
echononl "Verify primary Postfix remains active.."
|
|
if systemctl is-active --quiet "$POSTFIX_SERVICE" ; then
|
|
echo_ok
|
|
else
|
|
echo_failed
|
|
verification_ok=false
|
|
fi
|
|
|
|
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_activation_rollback "Final selective SRS verification failed."
|
|
fi
|
|
|
|
|
|
# ----------
|
|
# Success
|
|
# ----------
|
|
|
|
ACTIVATION_CHANGES_STARTED=false
|
|
|
|
blank_line
|
|
info "Selective SRS was successfully activated."
|
|
|
|
echo " Current selective SRS state....................: ENABLED"
|
|
echo " Activation backup..............................: ${ACTIVATION_BACKUP_DIR}"
|
|
echo ""
|
|
|
|
clean_up 0
|