本文解決三大核心痛點:① 用戶創建時自動初始化過濾規則 ② Webmail (sqwebmail) 定義的規則生效機制 ③ 權限與腳本執行陷阱。基於 qmail-local (MDA) + maildrop 架構,實現從賬號創建到規則生效的全鏈路自動化。
qmailadmin (CGI)
│ 創建用戶 → 觸發 .qmailadmin-hooks
▼
inituser.sh (腳本)
├── 創建 .qmail-user → |maildrop $DOMAIN/$USER/.mailfilter
├── 創建 .mailfilter → include $USER/.userfilter
├── 創建 .userfilter (默認規則 + sqwebmail 標記)
└── 創建 Maildir/maildirfilterconfig → 指向 ../.userfilter
▼
sqwebmail 讀取 maildirfilterconfig → 編輯 .userfilter → 即時生效
maildrop 執行 .mailfilter → 包含 .userfilter → 規則生效
域目錄由 /var/qmail/users/assign 定位,例:+domain.com-:domain.com:515:511:/home/vpopmail/domains/domain.com:-::
在域目錄下創建 .qmailadmin-hooks(屬主 vpopmail:vchkpw,權限 644):
adduser: /home/vpopmail/bin/inituser.sh
deluser: /home/vpopmail/bin/deluser.sh
moduser: /home/vpopmail/bin/moduser.sh
inituser.sh#!/bin/bash
# 參數位置修正:qmailadmin 1.0.6+ 傳參順序 $1=User $2=Domain $3=Password $4=RealName
User=$1
Domain=$2
Passwd=$3
RealName=$4
DomainPath=/home/vpopmail/domains/$Domain
umask 0177
exec 1> /tmp/adduser.log
exec 2>&1
echo "Initializing $User@$Domain"
# 1. .qmail-user → 管道給 maildrop
cat > $DomainPath/.qmail-$User <<EOF
|maildrop $DomainPath/$User/.mailfilter
EOF
# 2. .mailfilter → 包含用戶可編輯的 .userfilter
cat > $DomainPath/$User/.mailfilter <<EOF
include $User/.userfilter
EOF
# 3. .userfilter → 默認規則 + sqwebmail 識別標記(關鍵!)
cat > $DomainPath/$User/.userfilter <<EOF
#MFMAILDROP=2
# DO NOT EDIT THIS FILE. This is an automatically generated filter.
FROM='$User@$Domain'
import SENDER
if (\$SENDER ne "")
{
FROM=\$SENDER
}
to "$DomainPath/$User/Maildir/."
EOF
# 4. maildirfilterconfig → 指向 .userfilter(而非 .mailfilter,便於擴展)
cat > $DomainPath/$User/Maildir/maildirfilterconfig <<EOF
MAILDIRFILTER=../.userfilter
MAILDIR=$DomainPath/$User/Maildir
EOF
deluser.sh#!/bin/bash
User=$1; Domain=$2; Passwd=$3; RealName=$4
DomainPath=/home/vpopmail/domains/$Domain
rm -f $DomainPath/.qmail-$User
# 注意:不刪除 Maildir 內容,由 vpopmail 主流程處理
文件:qmailadmin-1.0.6/user.c 函數 call_hooks() 的 fork() 前插入權限提升,修正參數傳遞邏輯:
// 權限提升:http 用戶 → root → vpopmail 用戶
setuid(0);
setgid(VPOPMAILGID);
setuid(VPOPMAILUID);
pid = fork();
#ifdef DEBUG
fprintf(actout, "Hook params: %s, %s, %s, %s, %s\n", cmd, hooks[hook_type], Newu, Domain, Password1);
#endif
if (pid == 0) {
// 修正:新建用戶用 Newu,修改/刪除用 ActionUser
if (Newu && *Newu) {
error = execl(cmd, cmd, Newu, Domain, Password1, Gecos, NULL);
} else {
error = execl(cmd, cmd, ActionUser, Domain, Password1, Gecos, NULL);
}
_exit(error);
}
編譯替換:make clean && make,將生成的 qmailadmin.cgi 複製至 CGI 目錄,設置 chown root:root qmailadmin.cgi && chmod 4755 qmailadmin.cgi (SUID root)。
| 問題 | 原因 | 解決 |
|---|---|---|
| sqwebmail 報錯無法識別過濾器 | .userfilter 缺少 #MFMAILDROP=2 標記行 | 腳本中強制寫入首行標記,不可刪除 |
| 為何 maildirfilterconfig 指向 .userfilter 而非 .mailfilter | 便於在 .mailfilter 中包含系統級規則(如病毒掃描、全局黑名單),用戶僅編輯 .userfilter | 架構分層:.mailfilter(系統+用戶) → include .userfilter(用戶自定義) |
| Hook 腳本權限失敗 | qmailadmin 以 Apache 用戶運行,fork 子進程無權寫 vpopmail 目錄 | 源碼注入 setuid(0)/setgid/setuid 提權,或編寫 SUID 包裝程序 |
| 為何不用 su 執行腳本 | Apache 重定向了 stdin,su 檢測非 tty 拒絕執行;fork+exec 不複製 euid/egid | 源碼提權是唯一可靠方案 |
| moduser/deluser 取不到用戶名 | 原代碼 execl 參數錯誤,Gecos 只有新建時有值 | 區分 Newu/ActionUser 參數傳遞(見上文修正代碼) |
hook_type (adduser/moduser/deluser...) 作為參數傳給腳本,單腳本內部 case 分發,減少腳本文件數量.userfilter 可為 SQL 查詢生成的動態規則文件/tmp/adduser.log 排查初始化失敗