#!/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/ssl/opensmtpd"
DKIM_DIR="/etc/mail/dkim"

. /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
}


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 å¿è¦ç»ä»¶ï¼OpenSMTPD é®å±ï¼..."
apt_quiet_with_retry update
apt_quiet_with_retry install -y curl ca-certificates openssl dnsutils iproute2 procps python3 opensmtpd opensmtpd-filter-dkimsign apache2-utils || apt_quiet_with_retry install -y curl ca-certificates openssl dnsutils iproute2 procps python3 opensmtpd apache2-utils

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" /etc/mail
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.private" ]; then
  openssl genrsa -f4 -out "$DKIM_DIR/$DKIM_SELECTOR.private" 2048
  openssl rsa -in "$DKIM_DIR/$DKIM_SELECTOR.private" -outform PEM -pubout -out "$DKIM_DIR/$DKIM_SELECTOR.pub"
fi
if [ ! -f "$DKIM_DIR/default2.private" ]; then
  openssl genrsa -f4 -out "$DKIM_DIR/default2.private" 2048
  openssl rsa -in "$DKIM_DIR/default2.private" -outform PEM -pubout -out "$DKIM_DIR/default2.pub"
fi
chmod 640 "$DKIM_DIR/default2.private" 2>/dev/null || true
chmod 644 "$DKIM_DIR/default2.pub" 2>/dev/null || true
if [ ! -f "$DKIM_DIR/default3.private" ]; then
  openssl genrsa -f4 -out "$DKIM_DIR/default3.private" 2048
  openssl rsa -in "$DKIM_DIR/default3.private" -outform PEM -pubout -out "$DKIM_DIR/default3.pub"
fi
chmod 640 "$DKIM_DIR/default3.private" 2>/dev/null || true
chmod 644 "$DKIM_DIR/default3.pub" 2>/dev/null || true

# filter-dkimsign ä»¥ user nobody è¿è¡ï¼ç§é¥å¿é¡»è®©è¯¥è´¦æ·çå±ç»å¯è¯»ï¼å¦å dkimsign æä¸å¼å¯é¥æä»¶èå¼å¸¸éåº
DKIM_FILTER_GROUP="$(id -gn nobody 2>/dev/null || echo nogroup)"
chown "root:$DKIM_FILTER_GROUP" "$DKIM_DIR"/*.private 2>/dev/null || true
chmod 640 "$DKIM_DIR"/*.private 2>/dev/null || true
chmod 755 "$DKIM_DIR"

echo "æ­£å¨éç½® OpenSMTPD 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 "OpenSMTPD èªç­¾å 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 "éè¯¯ï¼OpenSMTPD èªç­¾å TLS è¯ä¹¦çæå¤±è´¥"
  exit 1
fi
echo "OpenSMTPD èªç­¾å TLS è¯ä¹¦å°±ç»ª"

HASHED_PASS="$(smtpctl encrypt "$SMTP_PASS" 2>/dev/null || htpasswd -bnBC 10 "" "$SMTP_PASS" | sed 's/^://')"
printf '%s:%s\n' "$SMTP_AUTH_USER" "$HASHED_PASS" > /etc/mail/passwd
chmod 600 /etc/mail/passwd
echo "æ­£å¨å®è£ OpenSMTPD é®ä»¶å¤´é­æ¹è¿æ»¤å¨..."
cat > /usr/local/bin/opensmtpd-header-tweak <<'OPENSMTPDHTWEAKEOF'
#!/usr/bin/env python3
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
PRESERVE_MESSAGE_ID = False
REWRITE_RECEIVED = True
OUTBOUND_CLIENT_MODE = "mobile"
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"]
STRIP_HEADERS = ["X-\u6df1\u60c5", "X-KumoRef", "X-Mailer-Trace",
                 "X-PHP-Originating-Script", "X-Mailer"]
if STRIP_LIST_UNSUBSCRIBE:
    STRIP_HEADERS.extend(["List-Unsubscribe", "List-Unsubscribe-Post"])
REWRITE_RE = re.compile(
    r"kumomta|zonemta|zone-mta|maddy|opensmtpd|openbsd|exim|postfix|haraka|"
    r"localhost|127\.0\.0\.1|\[::1\]|esmtpsa",
    re.IGNORECASE,
)


def flatten(v):
    return re.sub(r"\s+", " ", str(v or "").replace("\r\n", " ").replace("\n", " ")).strip()


def with_clause(v):
    flat = flatten(v)
    m = re.search(r"(with\s+.+?)\s+id\s+", flat, re.IGNORECASE) or re.search(r"(with\s+[^;]+)", flat, re.IGNORECASE)
    clause = m.group(1) if m else "with ESMTP"
    clause = re.sub(r"\s*\([^)]*[Tt][Ll][Ss][^)]*\)", "", clause)
    return flatten(clause) or "with ESMTP"


def build_received(v):
    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 ("from %s ([%s]) by %s with ESMTPSA; %s" % (
            helo, ip, EHLO_DOMAIN, email.utils.formatdate(localtime=True)))
    return ("from %s (127.0.0.1) by %s (%s) %s; %s" % (
        random.choice(RECEIVED_EHLO_DOMAINS), EHLO_DOMAIN,
        random.choice(RECEIVED_MTA_LABELS), with_clause(v),
        email.utils.formatdate(localtime=True)))


def transform(raw):
    msg = email.message_from_bytes(raw, policy=email.policy.SMTP)
    for name in STRIP_HEADERS:
        del msg[name]
    if not PRESERVE_MESSAGE_ID:
        del msg["Message-ID"]
        del msg["Message-Id"]
    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"]
        if OUTBOUND_CLIENT_MODE == "mobile":
            out = [build_received("with ESMTPSA")]
        else:
            out = []
            for item in received:
                flat = flatten(item)
                out.append(build_received(flat) if REWRITE_RE.search(flat) else flat)
            if not out:
                out.append(build_received("with ESMTP"))
        for line in out:
            msg["Received"] = line
    return msg.as_bytes(policy=email.policy.SMTP)


def emit(session, token, line):
    sys.stdout.write("filter-dataline|%s|%s|%s\n" % (session, token, line))
    sys.stdout.flush()


def main():
    buffers = {}
    for raw in sys.stdin:
        line = raw.rstrip("\n")
        if line.startswith("config|"):
            if line == "config|ready":
                sys.stdout.write("register|filter|smtp-in|data-line\n")
                sys.stdout.write("register|ready\n")
                sys.stdout.flush()
            continue
        parts = line.split("|")
        if len(parts) >= 7 and parts[0] == "filter" and parts[4] == "data-line":
            session, token = parts[5], parts[6]
            data = "|".join(parts[7:])
            key = session + token
            if data == ".":
                raw_msg = ("\r\n".join(buffers.pop(key, []))).encode("utf-8", "surrogateescape")
                try:
                    new = transform(raw_msg)
                except Exception:
                    new = raw_msg
                text = new.decode("utf-8", "surrogateescape")
                sep = "\r\n" if "\r\n" in text else "\n"
                for out_line in text.split(sep):
                    emit(session, token, out_line)
                emit(session, token, ".")
            else:
                buffers.setdefault(key, []).append(data)


if __name__ == "__main__":
    main()
OPENSMTPDHTWEAKEOF
chmod 755 /usr/local/bin/opensmtpd-header-tweak
cat > /etc/smtpd.conf <<SMTPDCONF
pki "$MAIL_HOSTNAME" cert "$TLS_DIR/cert.pem"
pki "$MAIL_HOSTNAME" key "$TLS_DIR/key.pem"
table passwd file:/etc/mail/passwd
table aliases file:/etc/aliases
filter "dkimsign" proc-exec "filter-dkimsign -c relaxed/relaxed -d $MAIL_DOMAIN -s $DKIM_SELECTOR -k $DKIM_DIR/$DKIM_SELECTOR.private" user nobody
filter "htweak" proc-exec "/usr/bin/python3 /usr/local/bin/opensmtpd-header-tweak"
filter "dkimsign2" proc-exec "filter-dkimsign -c relaxed/relaxed -d $MAIL_DOMAIN -s default2 -k $DKIM_DIR/default2.private" user nobody
filter "dkimsign3" proc-exec "filter-dkimsign -c relaxed/relaxed -d $MAIL_DOMAIN -s default3 -k $DKIM_DIR/default3.private" user nobody
filter "mailchain" chain { "htweak" "dkimsign" "dkimsign2" "dkimsign3" }
listen on 0.0.0.0 port 25 tls pki "$MAIL_HOSTNAME" hostname "$MAIL_HOSTNAME" filter "mailchain"
listen on 0.0.0.0 port 587 tls-require pki "$MAIL_HOSTNAME" hostname "$MAIL_HOSTNAME" auth <passwd> filter "mailchain"
action "local" mbox alias <aliases>
action "relay" relay helo "$MAIL_HOSTNAME"
match from any for domain "$MAIL_DOMAIN" action "local"
match auth from any for any action "relay"
SMTPDCONF
newaliases 2>/dev/null || true
smtpd -n
DKIM_FILTER_GROUP="$(id -gn nobody 2>/dev/null || echo nogroup)"
chown "root:$DKIM_FILTER_GROUP" "$DKIM_DIR"/*.private 2>/dev/null || true
chmod 640 "$DKIM_DIR"/*.private 2>/dev/null || true
chmod 755 "$DKIM_DIR" 2>/dev/null || true
systemctl enable opensmtpd 2>/dev/null || systemctl enable smtpd
if ! systemctl restart opensmtpd 2>/dev/null && ! systemctl restart smtpd; then
  echo "éè¯¯ï¼opensmtpd éå¯å¤±è´¥"; systemctl --no-pager --full status opensmtpd smtpd || true; exit 1
fi
sleep 2
ss -lntp | grep -E ':25|:587' || 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"
