#!/bin/bash
#--------------------------------------
# Synametrics Software
# Copyright (C) 2003-2026 Synametrics Technologies, Inc.
# All Rights Reserved.
#
# Migrates an existing Xeams installation that was set up with the older
# Install.sh — which always ran Xeams as root — to
# run as a dedicated non-root user instead, using the same
# AmbientCapabilities approach as the current Install.sh.
#
# This script does NOT touch config/, db/, logs, or any other Xeams data,
# with one exception: if config/AppConfig.xml has a blank
# ntServiceCommand, it is set to "sudo systemctl restart xeams.service"
# so Xeams' own restart-itself feature keeps working under systemd once
# it's no longer running as root. A pre-existing non-blank value is left
# untouched. It also removes the old root-based service registration
# (systemd unit and/or /etc/init.d/xeams) and replaces it with a
# non-root systemd service.
#
# Usage:
#   sudo ./ChangeToNonRoot.sh
#--------------------------------------

set -e

APP_NAME="Xeams"
APPFULL="Xeams Email Server and Spam Filter"
DEFAULT_INSTALL_DIR="/opt/Xeams"
DEFAULT_SERVICE_USER="xeams"
SERVICE_UNIT="xeams.service"

cat <<BANNER
==============================================================================
 $APP_NAME - Migrate to a non-root service account
==============================================================================
This script updates an existing $APP_NAME installation that is currently
running as 'root' (installed via the older Install.sh) so that it instead
runs as a dedicated, unprivileged system user.

What it will do:
  1. Stop the currently running $APP_NAME service.
  2. Remove the old root-based startup registration (systemd unit and/or
     /etc/init.d/xeams).
  3. Create the non-root system user you specify, if it doesn't exist yet.
  4. Hand ownership of the install directory to that user.
  5. Register and start a new systemd service that runs $APP_NAME as that
     user, granting it just enough privilege to bind low-numbered ports
     (e.g. SMTP port 25) without running as root.

What it will NOT do:
  - It will not modify config/, the database, logs, TLS certificates, or
    any other $APP_NAME data, except to set ntServiceCommand in
    config/AppConfig.xml if that field is currently blank (see above).

This script will abort if $APP_NAME is already running as a non-root user,
since there would be nothing left to migrate.

Note: $APP_NAME will be briefly unavailable while its service is stopped
and restarted under the new account (typically a few seconds).

You must run this script as root (or with sudo).
==============================================================================
BANNER

echo -n "Continue? [y/N]: "
read -r CONFIRM </dev/tty
case "$CONFIRM" in
    y|Y|yes|YES) ;;
    *)
        echo "[.] Migration cancelled."
        exit 0
        ;;
esac
echo

echo "[.] Migrating $APP_NAME to run as a non-root user..."

# --- 1. Must be root/sudo. ----------------------------------------------
if [ "$(id -u)" -ne 0 ]; then
    echo "[*] You must run this script as root (or with sudo). [FAILED]"
    exit 1
fi

# --- 2. This migration relies on systemd AmbientCapabilities to let a ---
#        non-root process bind privileged ports (e.g. SMTP port 25).
if ! command -v systemctl >/dev/null 2>&1 || [ ! -d /run/systemd/system ]; then
    echo "[*] systemd was not detected on this system."
    echo "    Without systemd there is no way to grant a non-root user"
    echo "    permission to bind privileged ports (e.g. port 25), so this"
    echo "    migration cannot proceed. $APP_NAME will keep running as root."
    exit 1
fi

# --- 3. Ask for the existing install directory. --------------------------
echo
echo -n "Existing $APP_NAME installation directory [$DEFAULT_INSTALL_DIR]: "
read -r INSTALL_DIR </dev/tty
if [ -z "$INSTALL_DIR" ]; then
    INSTALL_DIR="$DEFAULT_INSTALL_DIR"
fi

if [ ! -d "$INSTALL_DIR" ] || [ ! -f "$INSTALL_DIR/run.sh" ]; then
    echo "[*] $INSTALL_DIR does not look like a $APP_NAME installation (run.sh not found). [FAILED]"
    exit 1
fi
echo "[.] Installation directory = $INSTALL_DIR"

# --- 3b. Abort if Xeams is already running as a non-root user. There is --
#         nothing to migrate in that case, and re-running this script
#         would just tear down a working non-root service.
RUNNING_PID="$(pgrep -f com.synametrics.xeams.ServerStarter | head -1)"
if [ -n "$RUNNING_PID" ]; then
    RUNNING_USER="$(ps -o user= -p "$RUNNING_PID" 2>/dev/null | tr -d '[:space:]')"
    if [ -n "$RUNNING_USER" ] && [ "$RUNNING_USER" != "root" ]; then
        echo "[*] $APP_NAME is already running as non-root user '$RUNNING_USER' (pid $RUNNING_PID)."
        echo "    Nothing to migrate. Aborting."
        exit 1
    fi
fi

# --- 4. Ask for the OS-level service user to migrate to. -----------------
echo
echo -n "OS user to run $APP_NAME as [$DEFAULT_SERVICE_USER]: "
read -r SERVICE_USER </dev/tty
if [ -z "$SERVICE_USER" ]; then
    SERVICE_USER="$DEFAULT_SERVICE_USER"
fi

if [ "$SERVICE_USER" = "root" ]; then
    echo "[*] '$SERVICE_USER' is root - nothing to migrate. [FAILED]"
    exit 1
fi
echo "[.] $APP_NAME will be migrated to run as user = $SERVICE_USER"

# --- 5. Stop and remove whatever the old Install.sh registered: ---------
#        a systemd unit (Fedora/openSUSE/generic-systemd branches) and/or
#        a SysV /etc/init.d/xeams script (Ubuntu/Debian/CentOS branches).
echo "[.] Stopping and removing the old root-based service..."

if systemctl list-unit-files 2>/dev/null | grep -q "^${SERVICE_UNIT}"; then
    systemctl stop "$SERVICE_UNIT" >/dev/null 2>&1 || true
    systemctl disable "$SERVICE_UNIT" >/dev/null 2>&1 || true
    rm -f "/etc/systemd/system/$SERVICE_UNIT"
    echo "[OK] Removed old $SERVICE_UNIT"
fi

if [ -x /etc/init.d/xeams ]; then
    /etc/init.d/xeams stop >/dev/null 2>&1 || true
    if command -v update-rc.d >/dev/null 2>&1; then
        update-rc.d -f xeams remove >/dev/null 2>&1 || true
    elif command -v chkconfig >/dev/null 2>&1; then
        chkconfig --del xeams >/dev/null 2>&1 || true
    fi
    rm -f /etc/init.d/xeams
    echo "[OK] Removed old /etc/init.d/xeams"
fi

# Make sure no leftover java process from the old root-run service is
# still around before we hand the install directory to a new user.
pkill -f com.synametrics.xeams.ServerStarter >/dev/null 2>&1 || true

systemctl daemon-reload
systemctl reset-failed >/dev/null 2>&1 || true

# --- 6. Create the non-root service user and hand it the install dir. ---
if ! id -u "$SERVICE_USER" >/dev/null 2>&1; then
    echo -n "[.] Creating system user '$SERVICE_USER'... "
    useradd --system --create-home --home-dir "/home/$SERVICE_USER" --shell /usr/sbin/nologin "$SERVICE_USER"
    echo "[OK]"
else
    echo "[.] User '$SERVICE_USER' already exists, reusing it."
fi

SERVICE_USER_HOME="$(getent passwd "$SERVICE_USER" | cut -d: -f6)"
if [ -z "$SERVICE_USER_HOME" ] || [ ! -d "$SERVICE_USER_HOME" ]; then
    SERVICE_USER_HOME="/home/$SERVICE_USER"
    echo -n "[.] Creating home directory $SERVICE_USER_HOME... "
    mkdir -p "$SERVICE_USER_HOME"
    chown "$SERVICE_USER:$SERVICE_USER" "$SERVICE_USER_HOME"
    echo "[OK]"
fi

echo -n "[.] Granting ownership of $INSTALL_DIR to $SERVICE_USER... "
chown -R "$SERVICE_USER:$SERVICE_USER" "$INSTALL_DIR"
echo "[OK]"

# --- 7. Build a foreground launch script for systemd to supervise -------
#        The existing run.sh tells us whether this is a FIPS build: FIPS
#        installs launch java with -Djava.security.properties=fips.linux.security
#        and a larger heap, which must be preserved in RunForeground.sh.
JAVA_OPTS="-Xmx1024m"
if grep -q 'fips.linux.security' "$INSTALL_DIR/run.sh"; then
    echo "[.] Detected FIPS build (run.sh references fips.linux.security)."
    JAVA_OPTS="-Djava.security.properties=fips.linux.security -Djdk.tls.trustNameService=true -Xmx2048m"
fi

cat > "$INSTALL_DIR/RunForeground.sh" <<EOF
#!/bin/bash
cd "\$(dirname "\$0")"
CP=
for i in lib/*.jar; do
    CP="\$CP:\$i"
done
ulimit -n 8192
exec jre/bin/java -server $JAVA_OPTS -cp "\$CP" -DLoggingConfigFile=logconfig.xml com.synametrics.xeams.ServerStarter
EOF
chmod +x "$INSTALL_DIR/RunForeground.sh"
chown "$SERVICE_USER:$SERVICE_USER" "$INSTALL_DIR/RunForeground.sh"
echo "[OK] Wrote $INSTALL_DIR/RunForeground.sh"

# --- 8. Register the new non-root systemd service  ------------
#        AmbientCapabilities grants
#        CAP_NET_BIND_SERVICE so the JVM can still bind privileged ports
#        (e.g. SMTP port 25) without running as root.
#
cat > "/etc/systemd/system/$SERVICE_UNIT" <<EOF
[Unit]
Description=$APPFULL
Requires=network.target
After=network.target

[Service]
Type=simple
User=$SERVICE_USER
WorkingDirectory=$INSTALL_DIR
ExecStart=$INSTALL_DIR/RunForeground.sh
Restart=on-failure
RestartSec=60
KillMode=process
AmbientCapabilities=CAP_NET_BIND_SERVICE

[Install]
WantedBy=multi-user.target
EOF

echo "[OK] Wrote /etc/systemd/system/$SERVICE_UNIT"

# --- 9. Let Xeams restart itself through systemd. Xeams' patcher --------

SYSTEMCTL_BIN="$(command -v systemctl)"
SUDOERS_FILE="/etc/sudoers.d/xeams"

echo -n "[.] Writing sudoers entry so '$SERVICE_USER' can restart $SERVICE_UNIT... "
cat > "$SUDOERS_FILE" <<EOF
$SERVICE_USER ALL=(root) NOPASSWD: $SYSTEMCTL_BIN restart $SERVICE_UNIT
EOF
chmod 440 "$SUDOERS_FILE"
chown root:root "$SUDOERS_FILE"

if command -v visudo >/dev/null 2>&1 && visudo -cf "$SUDOERS_FILE" >/dev/null 2>&1; then
    echo "[OK]"
else
    echo "[FAILED]"
    echo "[*] WARNING: $SUDOERS_FILE failed validation, removing it."
    echo "    $APP_NAME's internal restart-via-systemd will not work"
    echo "    until this is fixed."
    rm -f "$SUDOERS_FILE"
fi

APP_CONFIG="$INSTALL_DIR/config/AppConfig.xml"
if [ -f "$APP_CONFIG" ]; then
    if grep -q '<ntServiceCommand></ntServiceCommand>' "$APP_CONFIG"; then
        echo -n "[.] Setting ntServiceCommand in AppConfig.xml... "
        sed -i "s:<ntServiceCommand></ntServiceCommand>:<ntServiceCommand>sudo $SYSTEMCTL_BIN restart $SERVICE_UNIT</ntServiceCommand>:" "$APP_CONFIG"
        echo "[OK]"
    else
        echo "[.] ntServiceCommand in AppConfig.xml already has a value, leaving it unchanged."
    fi
fi

systemctl daemon-reload
systemctl enable "$SERVICE_UNIT"
echo "[OK] Enabled $SERVICE_UNIT to start on boot"

echo -n "[.] Starting $APP_NAME as '$SERVICE_USER'... "
systemctl start "$SERVICE_UNIT" || true
sleep 2
if systemctl is-active --quiet "$SERVICE_UNIT"; then
    echo "[OK]"
else
    echo "[FAILED]"
    echo "[*] Check logs with: journalctl -u $SERVICE_UNIT -n 50"
    echo "[*] Common cause: files under $INSTALL_DIR (logs, db/, TLS certs,"
    echo "    config/) not readable/writable by '$SERVICE_USER'."
fi

# --- 10. Replace the old Uninstall.sh (written by the old root-based -----
#         Install.sh) with one that matches this non-root/systemd setup.
#         The old one stops a root-owned systemd/init.d service and knows
#         nothing about the sudoers entry, so it's renamed aside rather
#         than left in place to be run by mistake.
if [ -f "$INSTALL_DIR/Uninstall.sh" ]; then
    mv "$INSTALL_DIR/Uninstall.sh" "$INSTALL_DIR/Uninstall.sh.old"
    echo "[OK] Renamed old $INSTALL_DIR/Uninstall.sh to Uninstall.sh.old"
fi

cat > "$INSTALL_DIR/Uninstall.sh" <<EOF
#!/bin/bash
set -e

if [ "\$(id -u)" -ne 0 ]; then
    echo "[*] You must run the uninstaller as root (or with sudo)."
    exit 1
fi

echo "[.] Stopping $APP_NAME..."
systemctl stop $SERVICE_UNIT >/dev/null 2>&1 || true
systemctl disable $SERVICE_UNIT >/dev/null 2>&1 || true
rm -f /etc/systemd/system/$SERVICE_UNIT
systemctl daemon-reload
systemctl reset-failed >/dev/null 2>&1 || true

rm -f /etc/sudoers.d/xeams

rm -rf "$INSTALL_DIR"

echo "[OK] $APP_NAME has been uninstalled."
echo "Note: the '$SERVICE_USER' system user (if created) was left in place."
EOF
chmod 755 "$INSTALL_DIR/Uninstall.sh"
echo "[OK] Wrote $INSTALL_DIR/Uninstall.sh"

echo
echo "[.] Migration complete."
echo "    Install directory : $INSTALL_DIR"
echo "    Service user      : $SERVICE_USER"
echo "    Manage with       : systemctl {start|stop|restart|status} $SERVICE_UNIT"
echo "    Uninstall with    : $INSTALL_DIR/Uninstall.sh"
if [ -f "/etc/sudoers.d/xeams" ]; then
    echo "    Self-restart      : via 'sudo systemctl restart $SERVICE_UNIT' (sudoers entry installed)"
fi
if [ -f "$INSTALL_DIR/Uninstall.sh.old" ]; then
    echo "    Note              : the old root-based Uninstall.sh was renamed to Uninstall.sh.old"
fi
echo "    No configuration under $INSTALL_DIR was changed, other than a"
echo "    blank ntServiceCommand being set to enable self-restart (if applicable)."

exit 0
