#!/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" } 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" } 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\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 [[ -f "$SRS_ALIAS_HELPER" && ! -L "$SRS_ALIAS_HELPER" ]] || fatal "Missing regular SRS alias helper: ${SRS_ALIAS_HELPER}" # ---------- # 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 <}" echo " PRE-SRS AmaViS receive_override_options.......: ${_PRE_SRS_AMAVIS_RECEIVE_OVERRIDE_OPTIONS:-}" 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 # ---------- # Remove only SRS-owned virtual alias list items; preserve later admin edits # ---------- if [[ "$srs_alias_status" == 'managed' ]]; then echononl "Remove tracked SRS virtual alias items.." if srs_alias_tool remove > "$log_file" 2>&1 ; then echo_ok else echo_failed fatal_with_deactivation_rollback "Cannot safely remove SRS alias items: $(cat "$log_file")" fi 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 SRS virtual alias items are absent.." if [[ "$(srs_alias_tool status 2>"$log_file")" == 'absent' ]]; 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