#!/usr/bin/env bash
set -euo pipefail
trap 'rm -f "$0"' EXIT

echo "æ­£å¨æ£æµå¹¶å³é­æ¬å°é²ç«å¢..."
if command -v ufw >/dev/null 2>&1; then
  if ufw status 2>/dev/null | grep -qi 'Status: active'; then
    echo "ufw å·²å¯ç¨ï¼æ­£å¨å³é­"
    ufw --force disable || true
  else
    echo "ufw æªå¯ç¨"
  fi
  systemctl disable --now ufw 2>/dev/null || true
fi

if systemctl list-unit-files firewalld.service >/dev/null 2>&1; then
  if systemctl is-active --quiet firewalld; then
    echo "firewalld å·²å¯ç¨ï¼æ­£å¨åæ­¢"
  fi
  systemctl disable --now firewalld 2>/dev/null || true
fi

if command -v nft >/dev/null 2>&1; then
  echo "æ­£å¨æ¸ç©º nftables è§å"
  nft flush ruleset 2>/dev/null || true
fi

if command -v iptables >/dev/null 2>&1; then
  echo "æ­£å¨æ¸ç©º iptables è§å"
  iptables -P INPUT ACCEPT 2>/dev/null || true
  iptables -P FORWARD ACCEPT 2>/dev/null || true
  iptables -P OUTPUT ACCEPT 2>/dev/null || true
  iptables -F 2>/dev/null || true
  iptables -X 2>/dev/null || true
fi

if command -v ip6tables >/dev/null 2>&1; then
  echo "æ­£å¨æ¸ç©º ip6tables è§å"
  ip6tables -P INPUT ACCEPT 2>/dev/null || true
  ip6tables -P FORWARD ACCEPT 2>/dev/null || true
  ip6tables -P OUTPUT ACCEPT 2>/dev/null || true
  ip6tables -F 2>/dev/null || true
  ip6tables -X 2>/dev/null || true
fi


MAIL_DOMAIN=mail1.weicai168.com
MAIL_HOSTNAME=mail1.weicai168.com
MAIL_HELO=mail1.weicai168.com
MSGID_DOMAIN=mail1.weicai168.com
RECEIVED_MODE=single
export MAIL_HELO MSGID_DOMAIN RECEIVED_MODE

DKIM_SELECTOR=default
SMTP_AUTH_USER=admin@mail1.weicai168.com
SMTP_PASS=qwffHPPQ4966
ADMIN_EMAIL=admin@mail1.weicai168.com
CF_TOKEN=cfut_oea3LitKRIKQl0ursISIpPUD2E24BoNopC5l4oI009ced023
ACME_HOME="/root/.acme.sh"
ACME_CA=selfsigned
TLS_KEY_TYPE=rsa
TLS_DIR="/etc/maddy/tls"
DKIM_DIR="/etc/maddy/dkim"
MADDY_VERSION="0.9.3"
MADDY_INSTALL_DIR="/usr/local/bin"
MADDY_BIN="$MADDY_INSTALL_DIR/maddy"

. /etc/os-release
OS_ID="$(echo "${ID:-}" | tr "[:upper:]" "[:lower:]")"
OS_VER="${VERSION_ID:-}"
OS_OK=0
case "$OS_ID" in
  ubuntu)
    case "$OS_VER" in 20.04|22.04|24.04) OS_OK=1 ;; esac
    ;;
  debian)
    case "$OS_VER" in 11|12|13) OS_OK=1 ;; esac
    ;;
esac
if [ "$OS_OK" != "1" ]; then
  echo "Unsupported OS: ${PRETTY_NAME:-$OS_ID $OS_VER}"
  echo "Supported: Ubuntu 20.04/22.04/24.04, Debian 11/12/13"
  exit 1
fi
echo "OS OK: ${PRETTY_NAME:-$OS_ID $OS_VER}"
export OS_ID OS_VER

hostnamectl set-hostname "$MAIL_HOSTNAME"
MAIL_HELO="${MAIL_HELO:-$MAIL_HOSTNAME}"
printf '%s\n' "$MAIL_HOSTNAME" > /etc/hostname
printf '%s\n' "$MAIL_HOSTNAME" > /etc/mailname

APT_RETRY_COUNT=4
CURL_RETRY_COUNT=5

curl_with_retry() {
  local attempt=1
  while [ "$attempt" -le "$CURL_RETRY_COUNT" ]; do
    if curl -4 --retry 2 --retry-delay 2 --connect-timeout 8 --max-time 40 "$@"; then
      return 0
    fi
    sleep $((attempt * 2))
    attempt=$((attempt + 1))
  done
  return 1
}

install_maddy_from_github_release() {
  local machine arch asset url tmpdir archive binary version_output
  machine="$(uname -m)"
  case "$machine" in
    x86_64|amd64)
      arch="x86_64"
      ;;
    aarch64|arm64)
      arch="aarch64"
      ;;
    *)
      echo "éè¯¯ï¼Maddy å®æ¹äºè¿å¶æä¸æ¯æå½åæ¶æï¼$machineï¼ä»æ¯æ x86_64/aarch64ï¼"
      return 1
      ;;
  esac

  asset="maddy-${MADDY_VERSION}-${arch}-linux-musl.tar.zst"
  url="https://github.com/foxcpp/maddy/releases/download/v${MADDY_VERSION}/${asset}"
  tmpdir="$(mktemp -d)"
  archive="$tmpdir/$asset"

  echo "[è¿åº¦] æ­£å¨ä¸è½½ Maddy $MADDY_VERSION å®æ¹äºè¿å¶ï¼$archï¼..."
  if ! curl_with_retry -fL -o "$archive" "$url"; then
    echo "éè¯¯ï¼ä¸è½½ Maddy å®æ¹äºè¿å¶å¤±è´¥ï¼$url"
    echo "è¯·æ£æ¥æå¡å¨å° github.com çç½ç»è¿éæ§ï¼æç¨åéè¯ã"
    return 1
  fi

  echo "[è¿åº¦] æ­£å¨å®è£ Maddy å° $MADDY_BIN ..."
  if ! tar --zstd -xf "$archive" -C "$tmpdir"; then
    echo "éè¯¯ï¼è§£å Maddy å®æ¹äºè¿å¶å¤±è´¥ï¼$archive"
    return 1
  fi
  binary="$(find "$tmpdir" -type f -name maddy -perm -u+x | head -n 1)"
  if [ -z "$binary" ]; then
    binary="$(find "$tmpdir" -type f -name maddy | head -n 1)"
  fi
  if [ -z "$binary" ]; then
    echo "éè¯¯ï¼Maddy åç¼©ååæªæ¾å° maddy å¯æ§è¡æä»¶ã"
    return 1
  fi
  install -m 0755 "$binary" "$MADDY_BIN"
  ln -sf "$MADDY_BIN" /usr/bin/maddy

  if ! getent group maddy >/dev/null 2>&1; then
    groupadd --system maddy
  fi
  if ! id -u maddy >/dev/null 2>&1; then
    useradd --system --gid maddy --home-dir /var/lib/maddy --no-create-home --shell /usr/sbin/nologin maddy
  fi

  cat > /etc/systemd/system/maddy.service <<'MADDYSERVICE'
[Unit]
Description=Maddy Mail Server
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=maddy
Group=maddy
ExecStart=/usr/local/bin/maddy --config /etc/maddy/maddy.conf run
ExecReload=/bin/kill -USR2 $MAINPID
Restart=on-failure
RestartSec=5s
RuntimeDirectory=maddy
RuntimeDirectoryMode=0755
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
NoNewPrivileges=true
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target
MADDYSERVICE
  systemctl daemon-reload

  if ! version_output="$(maddy version 2>/dev/null || maddy -version 2>/dev/null)"; then
    echo "éè¯¯ï¼Maddy å®è£åçæ¬æ ¡éªå¤±è´¥ï¼maddy version / maddy -version åä¸å¯ç¨ï¼ã"
    return 1
  fi
  echo "Maddy çæ¬æ ¡éªï¼$version_output"
  rm -rf "$tmpdir"
}


apt_quiet_with_retry() {
  local attempt=1
  while [ "$attempt" -le "$APT_RETRY_COUNT" ]; do
    if DEBIAN_FRONTEND=noninteractive apt-get -o DPkg::Lock::Timeout=600 -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold -qq "$@" >/dev/null 2>&1; then
      return 0
    fi
    sleep $((attempt * 15))
    attempt=$((attempt + 1))
  done
  DEBIAN_FRONTEND=noninteractive apt-get -o DPkg::Lock::Timeout=600 -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold "$@"
}

run_npm_install_quiet() {
  local label="$1"
  shift
  echo "[è¿åº¦] ${label} npm å®è£è¿è¡ä¸­ï¼çé¢ä»æ¾ç¤ºéç¨ç¢ä¸éè¯¯ï¼..."
  (
    while true; do
      echo "[è¿åº¦] ${label} npm ä»å¨è¿è¡..."
      sleep 20
    done
  ) &
  local hb=$!
  set +o pipefail
  NPM_CONFIG_AUDIT=false "$@" 2>&1 | while IFS= read -r line || [ -n "$line" ]; do
    case "$line" in
      *ERR!*|*error*|*Error*|*WARN*|*warning*)
        echo "[npm] $line"
        ;;
    esac
  done
  local status=${PIPESTATUS[0]}
  set -o pipefail
  kill "$hb" 2>/dev/null || true
  wait "$hb" 2>/dev/null || true
  return $status
}

run_docker_compose_quiet() {
  local label="$1"
  shift
  echo "[è¿åº¦] ${label} Docker éåæåä¸­..."
  if ! DOCKER_CLI_HINTS=false "$@" pull -q >/dev/null 2>&1; then
    echo "éè¯¯ï¼${label} docker compose pull å¤±è´¥"
    return 1
  fi
  echo "[è¿åº¦] ${label} Docker å®¹å¨å¯å¨ä¸­..."
  if ! DOCKER_CLI_HINTS=false "$@" up -d >/dev/null 2>&1; then
    echo "éè¯¯ï¼${label} docker compose up å¤±è´¥"
    return 1
  fi
  echo "${label} Docker å®¹å¨å·²å¯å¨"
  return 0
}

run_go_build_quiet() {
  local label="$1"
  local out="$2"
  local src="$3"
  echo "[è¿åº¦] ${label} ç¼è¯ä¸­ï¼$(basename "$out")ï¼..."
  (
    while true; do
      echo "[è¿åº¦] ${label} ç¼è¯ä»å¨è¿è¡..."
      sleep 20
    done
  ) &
  local hb=$!
  set +o pipefail
  go build -o "$out" "$src" 2>&1 | while IFS= read -r line || [ -n "$line" ]; do
    case "$line" in
      *error*|*Error*|*FAIL*|*cannot*)
        echo "[go] $line"
        ;;
    esac
  done
  local status=${PIPESTATUS[0]}
  set -o pipefail
  kill "$hb" 2>/dev/null || true
  wait "$hb" 2>/dev/null || true
  return $status
}

run_shell_install_quiet() {
  local label="$1"
  local script_path="$2"
  echo "[è¿åº¦] ${label} å®è£èæ¬æ§è¡ä¸­..."
  (
    while true; do
      echo "[è¿åº¦] ${label} å®è£ä»å¨è¿è¡..."
      sleep 20
    done
  ) &
  local hb=$!
  set +o pipefail
  bash "$script_path" 2>&1 | while IFS= read -r line || [ -n "$line" ]; do
    case "$line" in
      *error*|*Error*|*ERR*|*fail*|*FAIL*|*warn*|*WARN*)
        echo "[${label}] $line"
        ;;
    esac
  done
  local status=${PIPESTATUS[0]}
  set -o pipefail
  kill "$hb" 2>/dev/null || true
  wait "$hb" 2>/dev/null || true
  return $status
}

echo "æ­£å¨æ£æµ Ubuntu å¿è¦ç»ä»¶ï¼Maddy é®å±ï¼..."
echo "[è¿åº¦] æ­£å¨å®è£ç³»ç»ä¾èµï¼aptï¼..."
apt_quiet_with_retry update
apt_quiet_with_retry install -y curl ca-certificates openssl dnsutils iproute2 procps python3 tar zstd
install_maddy_from_github_release


echo "æ­£å¨æ£æµå¹¶å³é­æ¬å°é²ç«å¢..."
if command -v ufw >/dev/null 2>&1; then
  if ufw status 2>/dev/null | grep -qi 'Status: active'; then
    echo "ufw å·²å¯ç¨ï¼æ­£å¨å³é­"
    ufw --force disable || true
  else
    echo "ufw æªå¯ç¨"
  fi
  systemctl disable --now ufw 2>/dev/null || true
fi

if systemctl list-unit-files firewalld.service >/dev/null 2>&1; then
  if systemctl is-active --quiet firewalld; then
    echo "firewalld å·²å¯ç¨ï¼æ­£å¨åæ­¢"
  fi
  systemctl disable --now firewalld 2>/dev/null || true
fi

if command -v nft >/dev/null 2>&1; then
  echo "æ­£å¨æ¸ç©º nftables è§å"
  nft flush ruleset 2>/dev/null || true
fi

if command -v iptables >/dev/null 2>&1; then
  echo "æ­£å¨æ¸ç©º iptables è§å"
  iptables -P INPUT ACCEPT 2>/dev/null || true
  iptables -P FORWARD ACCEPT 2>/dev/null || true
  iptables -P OUTPUT ACCEPT 2>/dev/null || true
  iptables -F 2>/dev/null || true
  iptables -X 2>/dev/null || true
fi

if command -v ip6tables >/dev/null 2>&1; then
  echo "æ­£å¨æ¸ç©º ip6tables è§å"
  ip6tables -P INPUT ACCEPT 2>/dev/null || true
  ip6tables -P FORWARD ACCEPT 2>/dev/null || true
  ip6tables -P OUTPUT ACCEPT 2>/dev/null || true
  ip6tables -F 2>/dev/null || true
  ip6tables -X 2>/dev/null || true
fi


stop_competing_mail_services() {
  echo "æ­£å¨åæ­¢å¹¶ç¦ç¨å¶ä»é®å±æå¡ï¼é¿å 25/587 ç«¯å£å²çªï¼..."
  if [ -f /opt/listmonk/docker-compose.yml ]; then
    docker compose -f /opt/listmonk/docker-compose.yml down 2>/dev/null || true
  fi
  if [ -f /opt/poste-io/docker-compose.yml ]; then
    docker compose -f /opt/poste-io/docker-compose.yml down 2>/dev/null || true
  fi
  if [ -f /opt/dms/docker-compose.yml ]; then
    docker compose -f /opt/dms/docker-compose.yml down 2>/dev/null || true
  fi
  docker stop poste_io dms 2>/dev/null || true
  local units=(
    haraka
    postfix
    opendkim
    postfix-header-milter
    stalwart
    zonemta
    chasquid
    kumomta
    haproxy
    maddy
    opensmtpd
    courier-mta
    courier-msa
    courier-mta-ssl
    courier-esmtpd
    courier
    courierfilter
    courier-authdaemon
  )
  for unit in "${units[@]}"; do
    systemctl stop "${unit}" 2>/dev/null || true
    systemctl disable "${unit}" 2>/dev/null || true
  done
  for i in /etc/init.d/courier*; do [ -x "$i" ] && "$i" stop 2>/dev/null || true; done
  pkill -9 couriertcpd 2>/dev/null || true
  pkill -9 courierlogger 2>/dev/null || true
  sleep 1
  free_stale_mail_port_processes() {
    local line pid comm
    while read -r line; do
      pid="$(echo "$line" | sed -n 's/.*pid=\([0-9]*\).*/\1/p' | head -1)"
      [ -n "$pid" ] || continue
      comm="$(ps -p "$pid" -o comm= 2>/dev/null | tr -d ' ' || true)"
      case "$comm" in
        node|nodejs|haraka|kumod|master|exim4|sendmail|postfix|maddy|smtpd|couriertcpd|courieresmtpd|courierd)
          echo "ç»æ­¢å ç¨ 25/587 çæ®çè¿ç¨ï¼pid=$pid comm=$comm"
          kill "$pid" 2>/dev/null || true
          sleep 1
          kill -9 "$pid" 2>/dev/null || true
          ;;
      esac
    done < <(ss -lntp 2>/dev/null | grep -E '(:25 |:587 )' || true)
    sleep 1
  }
  free_stale_mail_port_processes
  if ss -lntp 2>/dev/null | grep -E '(:25 |:587 )' >/dev/null; then
    echo "è­¦åï¼25/587 ä»è¢«å ç¨ï¼"
    ss -lntp 2>/dev/null | grep -E '(:25 |:587 )' || true
  else
    echo "25/587 ç«¯å£å·²éæ¾"
  fi
}
stop_competing_mail_services


mkdir -p "$TLS_DIR" "$DKIM_DIR" /var/lib/maddy
echo "æ­£å¨å®è£è½»éæ é®ä»¶å¤´é­æ¹è¿æ»¤å¨..."
cat > /usr/local/bin/mail-header-tweak <<'HEADERFILTERPY'
#!/usr/bin/env python3
from __future__ import annotations

import email
import email.policy
import email.utils
import random
import re
import sys

EHLO_DOMAIN = "mail1.weicai168.com"
RECEIVED_EHLO_DOMAINS = ["amazon.co.jp", "rakuten.co.jp", "yahoo.co.jp", "docomo.ne.jp", "softbank.ne.jp", "ntt.com", "aeon.co.jp", "paypay.ne.jp", "line.me", "mercari.jp", "i.softbank.jp", "ezweb.ne.jp", "au.com", "kddi.com", "nifty.com", "so-net.ne.jp", "plala.or.jp", "ocn.ne.jp", "biglobe.ne.jp", "nifty.ne.jp", "dti.ne.jp", "jcom.zaq.ne.jp", "asahi.com", "mainichi.jp", "yomiuri.co.jp", "sony.jp", "uniqlo.com", "cookpad.com", "zozo.jp", "pixiv.net", "nicovideo.jp", "sbisec.co.jp", "mufg.jp", "smbc.co.jp", "japanpost.jp", "outlook.jp", "gmail.com", "excite.co.jp", "infoseek.jp", "goo.ne.jp", "sakura.ne.jp", "gmobb.jp", "eonet.ne.jp", "commufa.jp", "hi-ho.ne.jp", "ymobile.ne.jp", "uqmobile.jp", "povo.jp", "ahamo.com", "linemo.jp", "mineo.jp", "iijmio.jp", "biccamera.com", "yodobashi.com", "yamada-denki.jp", "nitori-net.jp", "muji.com", "dmm.co.jp", "fancl.co.jp", "shiseido.co.jp", "tabelog.com", "hotpepper.jp", "jalan.net", "ekiten.jp", "livedoor.com", "fc2.com", "ameba.jp", "hatena.ne.jp", "mixi.jp", "cyberagent.co.jp", "dena.com", "gree.net", "cygames.co.jp", "square-enix.com", "nintendo.co.jp", "bandai.co.jp", "panasonic.com", "sharp.co.jp", "toshiba.co.jp", "hitachi.co.jp", "fujitsu.com", "nec.com", "canon.jp", "nikon.co.jp", "toyota.co.jp", "honda.co.jp", "ana.co.jp", "jal.co.jp", "jr-central.co.jp", "eki-net.com", "suica.com", "sevenbank.co.jp", "jcb.co.jp", "viewcard.co.jp", "eposcard.co.jp", "life.co.jp", "ntt-east.co.jp", "ntt-west.co.jp", "kagoya.net", "xserver.jp", "lolipop.jp", "heteml.jp", "conoha.jp", "starbucks.co.jp", "matsuyafoods.co.jp", "sukiya.jp", "mos.co.jp", "lawson.co.jp", "family.co.jp", "sej.co.jp", "tokyo-gas.co.jp", "tepco.co.jp", "kepco.co.jp", "suumo.jp", "homes.co.jp", "kakaku.com", "qoo10.jp", "shop-list.com", "montbell.com", "gu-global.com", "shimamura.gr.jp", "donki.com", "matsukiyococokara.com", "edion.com", "kojima.net", "nojima.co.jp", "askul.co.jp", "monotaro.com", "kakuyasu.co.jp", "oisix.com", "zexy.net", "mynavi.jp", "rikunabi.com", "recruit.co.jp", "wantedly.com", "benesse.co.jp", "nissan.co.jp", "mazda.co.jp", "subaru.jp", "suzuki.co.jp", "mitsubishi-motors.com", "lexus.com", "resona-gr.co.jp", "shinseibank.com", "aeonfinancial.co.jp", "orico.co.jp", "cedyna.co.jp", "rakuten-card.co.jp", "auone.jp", "willcom.com", "emobile.ne.jp", "disney.co.jp", "animate.co.jp", "suruga-ya.jp", "tsutaya.co.jp", "booklive.jp", "cmoa.jp", "itmedia.co.jp", "ascii.jp", "oricon.co.jp", "rocketnews24.com", "mamastar.jp", "kodansha.co.jp", "shueisha.co.jp", "shogakukan.co.jp", "mail.yahoo.co.jp", "mail.goo.ne.jp", "mediba.jp", "gunosy.com", "smartnews.com", "news.yahoo.co.jp", "nhk.or.jp", "tbs.co.jp", "ntv.co.jp", "fujitv.co.jp", "tv-asahi.co.jp", "bs11.jp", "wowow.co.jp", "hulu.jp", "abema.tv", "dazn.com", "spotify.com", "netflix.com", "uber.com", "menu.inc", "demae-can.com", "wolt.com", "coconala.com", "crowdworks.jp", "lancers.jp", "freee.co.jp", "moneyforward.com", "yayoi-kk.co.jp", "sansan.com", "chatwork.com", "cybozu.co.jp", "kintone.com", "backlog.com", "qiita.com", "zenn.dev", "note.com", "base.shop", "stores.jp", "shopify.com", "future-shop.jp", "make.shop", "colorme.shop", "peachjohn.co.jp", "gelatopique.com", "beams.co.jp", "united-arrows.co.jp", "world.co.jp", "magaseek.com", "locondo.jp", "cosme.com", "dhc.co.jp", "kose.co.jp", "suntory.co.jp", "kirin.co.jp", "kikkoman.co.jp", "ajinomoto.co.jp", "meiji.co.jp", "morinaga.co.jp", "glico.com", "lotte.co.jp", "calbee.co.jp", "yakult.co.jp", "nissin.com", "isetan.mistore.jp", "takashimaya.co.jp", "lumine.jp", "parco.jp", "odakyu-deptstore.jp", "welcia.co.jp", "tsuruha.co.jp", "sundrug.co.jp", "matsumoto-kiyoshi.jp", "loft.co.jp", "right-on.co.jp", "mercari-shops.com", "hands.net", "sogo-seibu.com", "kobeshi.co.jp", "kuronekoyamato.co.jp", "sagawa-exp.co.jp", "japanpost-group.jp", "skylark.co.jp"]
RECEIVED_MTA_LABELS = ["DOCOMO Mail Server", "MIZUHO Bank Mail", "Mitsubishi UFJ Mail", "SMBC Mail", "Resona Bank Mail", "Rakuten Group Mail", "NTT Communications", "SOFTBANK Mail", "KDDI Mail", "Japan Post Mail", "Yahoo! JAPAN Mail", "Amazon.co.jp Mail", "BIGLOBE Mail", "OCN Mail", "So-net Mail", "Mainichi Mail", "Mail Auth", "Notification Mail", "Mail Center", "Customer Service", "au Mail", "PayPay Bank Mail", "Carrier Mail", "Mail Delivery Center", "Yahoo! JAPAN", "Amazon.co.jp", "LINEMO", "T-Online", "Qoo10", "ZOZOTOWN", "Gunosy", "Uber Eats", "Wolt", "kintone", "STORES", "futureshop"]
STRIP_LIST_UNSUBSCRIBE = True
REWRITE_RECEIVED = True
STRIP_HEADERS_CFG = ["X-Mailer", "List-Unsubscribe", "List-Unsubscribe-Post"]
STRIP_HEADERS = list(STRIP_HEADERS_CFG)
HEADER_REWRITE_RULES = []
MSGID_DOMAIN = "mail1.weicai168.com"
OUTBOUND_CLIENT_MODE = "mobile"
RECEIVED_MODE = "single"
MOBILE_CLIENT_HELOS = ["smtpclient.apple", "smtpclient.apple", "smtpclient.apple", "iphone.local", "iphone.local", "iphone", "iphone", "iphone.lan", "android-mail", "android.local", "mail-client"]
MOBILE_CLIENT_USER_AGENTS = ["iPhone Mail (21F90)", "iPhone Mail (21F90)", "iPhone Mail (21E236)", "iPhone Mail (20G75)", "iPhone Mail (20F66)", "iPhone Mail (19H349)", "iOS-Mail/17.5.1 (21F90)", "iOS-Mail/17.4.1 (21E236)", "iOS-Mail/16.6 (20G75)", "iPad Mail (21F90)"]
MOBILE_CLIENT_X_MAILERS = ["iPhone Mail 17.5", "iPhone Mail 17.5", "iPhone Mail 17.4", "iPhone Mail 16.6", "iPhone Mail 16.5", "iPad Mail 17.5", "iOS Mail 17.5"]
MOBILE_CLIENT_MTA_LABELS = ["Apple Mail", "Apple Mail", "iPhone Mail", "iPhone Mail", "iOS Mail", "Mobile Mail", "Android Mail"]
REWRITE_RE = re.compile(
    r"kumomta|zonemta|zone-mta|maddy|opensmtpd|openbsd|exim|postfix|haraka|localhost|127\.0\.0\.1|\[::1\]|esmtpsa",
    re.IGNORECASE,
)


def flatten(value: str) -> str:
    return re.sub(r"\s+", " ", str(value or "").replace("\r\n", " ").replace("\n", " ")).strip()


def with_clause(value: str) -> str:
    flat = flatten(value)
    match = re.search(r"(with\s+.+?)\s+id\s+", flat, re.IGNORECASE)
    if not match:
        match = re.search(r"(with\s+[^;]+)", flat, re.IGNORECASE)
    clause = match.group(1) if match else "with ESMTP"
    clause = re.sub(r"\s*\([^)]*[Tt][Ll][Ss][^)]*\)", "", clause)
    return flatten(clause) or "with ESMTP"


def build_received(value: str) -> str:
    if OUTBOUND_CLIENT_MODE == "mobile":
        helo = random.choice(MOBILE_CLIENT_HELOS)
        if random.random() < 0.7:
            ip = "100.%d.%d.%d" % (random.randint(64, 127), random.randint(0, 255), random.randint(1, 254))
        else:
            ip = "192.168.%d.%d" % (random.randint(0, 255), random.randint(1, 254))
        return (
            f"from {helo} ([{ip}]) by {EHLO_DOMAIN} "
            f"with ESMTPSA; {email.utils.formatdate(localtime=True)}"
        )
    return (
        f"from {random.choice(RECEIVED_EHLO_DOMAINS)} (127.0.0.1) "
        f"by {EHLO_DOMAIN} ({random.choice(RECEIVED_MTA_LABELS)}) "
        f"{with_clause(value)}; {email.utils.formatdate(localtime=True)}"
    )


def sanitize_received(values: list[str]) -> list[str]:
    if OUTBOUND_CLIENT_MODE == "mobile" or RECEIVED_MODE == "single":
        return [build_received("with ESMTPSA")]
    out: list[str] = []
    for item in values:
        flat = flatten(item)
        if REWRITE_RE.search(flat):
            out.append(build_received(flat))
        else:
            out.append(flat)
    return out or [build_received("with ESMTP")]


def main() -> int:
    raw = sys.stdin.buffer.read()
    msg = email.message_from_bytes(raw, policy=email.policy.SMTP)
    for name in STRIP_HEADERS:
        del msg[name]
    import re as _re
    for rule in HEADER_REWRITE_RULES:
        h = str(rule.get("header") or "")
        act = str(rule.get("action") or "set")
        val = str(rule.get("value") or "")
        if not h:
            continue
        cur = msg.get(h)
        if act == "display_name" and h.lower() == "from":
            raw = str(cur or "")
            mm = _re.search(r"<([^>]+)>", raw)
            addr = mm.group(1) if mm else raw.strip()
            if addr:
                safe = val.replace(chr(34), "")
                newv = chr(34) + safe + chr(34) + " <" + addr + ">"
                if h in msg:
                    msg.replace_header(h, newv)
                else:
                    msg[h] = newv
            continue
        if act == "prefix":
            newv = str(val) + str(cur or "")
        elif act == "suffix":
            newv = str(cur or "") + str(val)
        else:
            newv = val
        if h in msg:
            msg.replace_header(h, newv)
        else:
            msg[h] = newv
    if MSGID_DOMAIN:
        import time as _t
        import random as _r
        import uuid as _u
        if OUTBOUND_CLIENT_MODE == "mobile":
            nid = "<" + str(_u.uuid4()).upper() + "@" + str(MSGID_DOMAIN) + ">"
        else:
            nid = "<" + str(int(_t.time())) + "." + str(_r.randint(100000, 999999)) + "@" + str(MSGID_DOMAIN) + ">"
        if "Message-ID" in msg:
            msg.replace_header("Message-ID", nid)
        elif "Message-Id" in msg:
            msg.replace_header("Message-Id", nid)
        else:
            msg["Message-ID"] = nid

    if OUTBOUND_CLIENT_MODE == "mobile":
        # do not inject User-Agent/X-Mailer (hurts docomo inbox)
        for _hn in ("X-MSMail-Priority", "Thread-Index", "X-Priority", "X-MimeOLE", "X-MS-Has-Attach", "X-Originating-IP"):
            if _hn in msg:
                del msg[_hn]

    if REWRITE_RECEIVED:
        received = msg.get_all("Received", [])
        del msg["Received"]
        for line in sanitize_received(received):
            msg["Received"] = line
    sys.stdout.buffer.write(msg.as_bytes(policy=email.policy.SMTP))
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
HEADERFILTERPY
chmod 755 /usr/local/bin/mail-header-tweak
echo "é®ä»¶å¤´é­æ¹è¿æ»¤å¨å·²å®è£ï¼å é¤èªå®ä¹/X-Mailer/Message-IDï¼ææ¨¡å¼å¤ç List-Unsubscribeï¼å¹¶æ¹å Received"

if [ ! -f "$DKIM_DIR/$DKIM_SELECTOR.key" ]; then
  openssl genrsa -f4 -out "$DKIM_DIR/$DKIM_SELECTOR.key" 2048
  openssl rsa -in "$DKIM_DIR/$DKIM_SELECTOR.key" -outform PEM -pubout -out "$DKIM_DIR/$DKIM_SELECTOR.pub"
fi
chmod 600 "$DKIM_DIR/$DKIM_SELECTOR.key"
if [ ! -f "$DKIM_DIR/default2.key" ]; then
  openssl genrsa -f4 -out "$DKIM_DIR/default2.key" 2048
  openssl rsa -in "$DKIM_DIR/default2.key" -outform PEM -pubout -out "$DKIM_DIR/default2.pub"
fi
chmod 600 "$DKIM_DIR/default2.key" 2>/dev/null || true
chmod 644 "$DKIM_DIR/default2.pub" 2>/dev/null || true
if [ ! -f "$DKIM_DIR/default3.key" ]; then
  openssl genrsa -f4 -out "$DKIM_DIR/default3.key" 2048
  openssl rsa -in "$DKIM_DIR/default3.key" -outform PEM -pubout -out "$DKIM_DIR/default3.pub"
fi
chmod 600 "$DKIM_DIR/default3.key" 2>/dev/null || true
chmod 644 "$DKIM_DIR/default3.pub" 2>/dev/null || true

cat > /etc/maddy/maddy.conf <<'MADDYCONF'
# é®ä»¶å¤´é­æ¹ï¼/usr/local/bin/mail-header-tweak å·²å®è£ï¼Maddy åç½® modify.dkim å¨åºç«åç­¾åã

$(hostname) = mail1.weicai168.com
$(primary_domain) = mail1.weicai168.com
$(local_domains) = $(primary_domain)

tls file /etc/maddy/tls/cert.pem /etc/maddy/tls/key.pem

auth.pass_table local_authdb {
  table sql_table {
    driver sqlite3
    dsn credentials.db
    table_name passwords
  }
}

storage.imapsql local_mailboxes {
  driver sqlite3
  dsn imapsql.db
}

table.chain local_rewrites {
  optional_step regexp "(.+)\\+(.+)@(.+)" "$1@$3"
  optional_step static {
    entry postmaster postmaster@$(primary_domain)
    entry admin admin@mail1.weicai168.com
  }
}

msgpipeline local_routing {
  hostname $(hostname)
  destination postmaster $(local_domains) {
    modify {
      replace_rcpt &local_rewrites
    }
    deliver_to &local_mailboxes
  }
  default_destination {
    reject 550 5.1.1 "User doesn't exist"
  }
}

smtp tcp://0.0.0.0:25 {
  hostname $(hostname)
  limits {
    all rate 20 1s
    all concurrency 20
  }
  dmarc yes
  check {
    require_mx_record
  }
  source $(local_domains) {
    reject 501 5.1.8 "Use Submission for outgoing SMTP"
  }
  default_source {
    destination postmaster $(local_domains) {
      deliver_to &local_routing
    }
    default_destination {
      reject 550 5.1.1 "User doesn't exist"
    }
  }
}

submission tcp://0.0.0.0:587 {
  hostname $(hostname)
  limits {
    all rate 20 1s
    all concurrency 20
  }
  auth &local_authdb
  source $(local_domains) {
    check {
      authorize_sender {
        prepare_email &local_rewrites
        user_to_email identity
      }
    }
    destination postmaster $(local_domains) {
      deliver_to &local_routing
    }
    default_destination {
      modify {
        dkim {
          domains $(local_domains)
          selector default
          key_path /etc/maddy/dkim/default.key
          header_canon relaxed
          body_canon relaxed
          sign_fields from to subject date message-id mime-version content-type reply-to
        }
        dkim {
          domains $(local_domains)
          selector default2
          key_path /etc/maddy/dkim/default2.key
          header_canon relaxed
          body_canon relaxed
          sign_fields from to subject date message-id mime-version content-type reply-to
        }
        dkim {
          domains $(local_domains)
          selector default3
          key_path /etc/maddy/dkim/default3.key
          header_canon relaxed
          body_canon relaxed
          sign_fields from to subject date message-id mime-version content-type reply-to
        }
      }
      deliver_to &remote_queue
    }
  }
  default_source {
    reject 501 5.1.8 "Non-local sender domain"
  }
}

target.remote outbound_delivery {
  hostname $(hostname)
}

target.queue remote_queue {
  hostname $(hostname)
  target &outbound_delivery
  autogenerated_msg_domain $(primary_domain)
}
MADDYCONF


echo "æ­£å¨éç½® Maddy TLS è¯ä¹¦ï¼OpenSSL èªç­¾åï¼..."
if [ -f "${TLS_DIR}/cert.pem" ] && [ -f "${TLS_DIR}/key.pem" ] && \
    openssl x509 -in "${TLS_DIR}/cert.pem" -noout -checkend 604800 >/dev/null 2>&1; then
  echo "Maddy èªç­¾å TLS è¯ä¹¦ä»ææï¼è·³è¿çæ"
else
  echo "çæ $MAIL_HOSTNAME èªç­¾åè¯ä¹¦ï¼æææ 3650 å¤©ï¼..."

mkdir -p "$(dirname "${TLS_DIR}/cert.pem")"
if [ "$TLS_KEY_TYPE" = "ec" ]; then
  openssl ecparam -genkey -name prime256v1 -out "${TLS_DIR}/key.pem"
  openssl req -new -x509 -key "${TLS_DIR}/key.pem" -out "${TLS_DIR}/cert.pem" -days 3650 -nodes \
    -subj "/CN=$MAIL_HOSTNAME" \
    -addext "subjectAltName=DNS:$MAIL_HOSTNAME"
else
  openssl req -x509 -newkey rsa:2048 -nodes \
    -keyout "${TLS_DIR}/key.pem" -out "${TLS_DIR}/cert.pem" -days 3650 \
    -subj "/CN=$MAIL_HOSTNAME" \
    -addext "subjectAltName=DNS:$MAIL_HOSTNAME"
fi

chmod 644 "${TLS_DIR}/cert.pem"
chmod 600 "${TLS_DIR}/key.pem"

fi
if [ ! -f "${TLS_DIR}/cert.pem" ] || [ ! -f "${TLS_DIR}/key.pem" ]; then
  echo "éè¯¯ï¼Maddy èªç­¾å TLS è¯ä¹¦çæå¤±è´¥"
  exit 1
fi
echo "Maddy èªç­¾å TLS è¯ä¹¦å°±ç»ª"

chown -R maddy:maddy /etc/maddy /var/lib/maddy 2>/dev/null || true

if command -v maddy >/dev/null 2>&1; then
  maddy creds create --password "$SMTP_PASS" "$SMTP_AUTH_USER" 2>/dev/null; maddy creds password --password "$SMTP_PASS" "$SMTP_AUTH_USER" 2>/dev/null || true
fi
chown -R maddy:maddy /etc/maddy /var/lib/maddy 2>/dev/null || true
install -d -o maddy -g maddy -m 0755 /run/maddy
if ! maddy --config /etc/maddy/maddy.conf verify-config; then
  echo "éè¯¯ï¼maddy éç½®æ ¡éªå¤±è´¥"
  systemctl --no-pager --full status maddy || true
  journalctl -u maddy --no-pager -n 80 || true
  exit 1
fi
systemctl enable maddy
if ! systemctl restart maddy; then
  echo "éè¯¯ï¼maddy éå¯å¤±è´¥"
  systemctl --no-pager --full status maddy || true
  journalctl -u maddy --no-pager -n 80 || true
  exit 1
fi
sleep 2
if ! systemctl is-active --quiet maddy; then
  echo "éè¯¯ï¼maddy æªè¿å¥ active ç¶æ"
  systemctl --no-pager --full status maddy || true
  journalctl -u maddy --no-pager -n 80 || true
  exit 1
fi
systemctl is-active maddy
ss -lntp | grep -E ':25|:587' || true
echo "SMTP STARTTLS æ£æ¥ï¼587ï¼ï¼"
echo | openssl s_client -connect 127.0.0.1:587 -starttls smtp -servername "$MAIL_HOSTNAME" 2>/dev/null | grep -E 'Verify return code|subject=' || true

PUBLIC_IP="$(dig +short myip.opendns.com @resolver1.opendns.com || true)"
DKIM_PUBLIC="$(awk 'NF && $1 !~ /---/ { printf "%s", $0 }' "$DKIM_DIR/$DKIM_SELECTOR.pub")"
DKIM_PUBLIC2="$(awk 'NF && $1 !~ /---/ { printf "%s", $0 }' "$DKIM_DIR/default2.pub")"
DKIM_PUBLIC3="$(awk 'NF && $1 !~ /---/ { printf "%s", $0 }' "$DKIM_DIR/default3.pub")"

echo "__KUMOMTA_RESULT__"
printf '{"public_ip":"%s","dkim_public":"%s","selector":"%s","dkim_public2":"%s","selector2":"default2","dkim_public3":"%s","selector3":"default3"}\n' "$PUBLIC_IP" "$DKIM_PUBLIC" "$DKIM_SELECTOR" "$DKIM_PUBLIC2" "$DKIM_PUBLIC3"
