- 即时通讯
- 后端
- 前端
【免费下载链接】roundcubemail
The Roundcube Webmail suite
导读
MarkAsJunk 是 Roundcube Webmail 官方插件体系中负责"标记为垃圾/非垃圾"的核心组件:它向邮件工具栏或"标记"菜单注入 spam/ham 按钮,并将选定邮件移动到 Junk(垃圾箱)或收件箱,同时通过可插拔的"学习驱动"(Learning Driver)把被标记的邮件交给 SpamAssassin(sa-learn)、DSPAM 等外部反垃圾系统进行训练。本文将基于 plugins/markasjunk/README.md 的完整内容,结合 插件主文件、配置文件模板、drivers 目录 及 测试用例 的源码证据,带你从零完成插件的启用、配置、驱动选型与自定义驱动开发。
插件能做什么:两种场景下的消息流转
从 markasjunk.php 的插件头注释与 README 可以看出,插件的核心行为由"当前所在邮箱"决定:
- 不在 Junk 邮箱时:把选中的消息移动到 Junk 邮箱,并标记为已读(
SEEN),同时打上 Junk 标志、清除 NonJunk 标志。 - 在 Junk 邮箱时:按钮自动切换为"标记为非垃圾 / 此消息不是垃圾邮件"(
mark as not spam/this message is not spam),消息被移回收件箱(默认INBOX),清除 Junk 标志并打上 NonJunk 标志。
插件只在mail任务下生效(public $task = 'mail',见 markasjunk.php),并注册了plugin.markasjunk.junk与plugin.markasjunk.not_junk两个动作(markasjunk.php),统一由mark_message()处理。
按钮的呈现位置有两种选择:默认放在主工具栏(markasjunk_toolbar = true),也可以关闭该选项,改为在"标记"菜单(markmenu)中出现菜单项(见 markasjunk.php)。
安装与基础配置
配置文件
默认配置模板位于 plugins/markasjunk/config.inc.php.dist,使用方式与 Roundcube 其他插件一致:
- 将
config.inc.php.dist复制为config.inc.php; - 按需修改参数——README 明确说明所有配置参数都是可选的。
插件通过load_config()读取该文件(markasjunk.php),并额外支持按邮件主机(mail host)加载专属配置。
基础行为参数
以下参数控制消息移动、标志与界面,均来自 config.inc.php.dist:
| 参数 | 默认值 | 作用 |
|---|---|---|
markasjunk_ham_mbox | null(即INBOX) | 标记为 ham 后消息移动到的邮箱;设为false可禁用移动 |
markasjunk_spam_mbox | null(即 Roundcube 设置中指定的垃圾邮件文件夹junk_mbox) | 标记为 spam 后消息移动到的邮箱;设为false可禁用移动 |
markasjunk_read_spam | false | 标记为 spam 时是否同时标记为已读 |
markasjunk_unread_ham | false | 标记为 ham 时是否同时标记为未读 |
markasjunk_spam_flag | 'Junk' | 标记为 spam 时附加的 IMAP 标志(标记为 ham 时移除);设为false关闭标志 |
markasjunk_ham_flag | 'NonJunk' | 标记为 ham 时附加的 IMAP 标志(标记为 spam 时移除);设为false关闭标志 |
markasjunk_toolbar | true | true在工具栏显示按钮,false则改用"标记"菜单 |
markasjunk_move_spam | false | 任何被移入垃圾箱的消息都作为 spam 学习(不仅是点击按钮时) |
markasjunk_move_ham | false | 任何从垃圾箱移出到 ham 邮箱的消息都作为 ham 学习 |
markasjunk_permanently_remove | false | 某些驱动会创建消息的新副本,此时原消息会被删除;设为true时原消息被永久删除而非移入 Trash |
markasjunk_spam_only | false | 只显示"标记为垃圾"按钮 |
markasjunk_allowed_hosts | null | 仅在指定主机启用插件,例如['mail1.domain.tld', 'mail2.domain.tld'] |
markasjunk_host_config | null | 按主机加载专属配置,例如['mail1.domain.tld' => 'mail1_config.inc.php'] |
markasjunk_debug | false | 把 spam/ham 命令的输出写入日志,便于调试 |
从源码实现看,标志逻辑位于_init_flags()(markasjunk.php):当markasjunk_spam_flag === false时移除JUNK标志,否则可用自定义值替换;NONJUNK同理。只要仍有标志启用,插件就会通过storage_init钩子把JUNK/NONJUNK注册进核心存储层(set_flags(),markasjunk.php),保证这些自定义标志在 Roundcube 内部可被识别与渲染。
主机白名单与按主机配置也在init()阶段处理:不在markasjunk_allowed_hosts列表内的主机直接跳过插件初始化(markasjunk.php),_load_host_config()则会针对当前storage_host加载对应配置文件(markasjunk.php)。
学习驱动(Learning Driver)机制
"学习驱动"是插件的灵魂:它允许在每条被标记为 spam/ham 的消息上执行额外的处理(例如调用 sa-learn 训练贝叶斯过滤、把消息副本存盘、转发到指定邮箱、维护黑/白名单等)。
驱动接口约定
README 明确规定了驱动必须满足的契约,源码侧由_init_driver()(markasjunk.php)强制校验:
- 文件与类名:驱动文件放在
plugins/markasjunk/drivers/下,文件名形如xxx.php;类名必须是markasjunk_{文件名},例如cmd_learn.php对应markasjunk_cmd_learn。 - spam 方法:接收 2 个参数——被标记为 spam 的消息 UID 数组、这些消息所在的邮箱名。实际调用时还会额外传入目标邮箱(见
_call_driver(),markasjunk.php)。 - ham 方法:接收同样的 2 个参数,处理被标记为 ham 的消息。
- init 方法(可选):不接收参数,在页面加载时被调用,典型用途是向页面注入 JavaScript,以控制不同邮箱中 spam/ham 按钮的显示状态。
驱动加载的校验逻辑非常严格:文件不可读会抛出错误(Unable to open driver file),类不存在或缺少spam/ham方法则报Broken driver(markasjunk.php)。配置项markasjunk_learning_driver指定要加载的驱动名,留空(null)则不加载任何驱动,插件只做消息移动与标志操作。
驱动的返回值还会影响主流程:_call_driver()通过empty($this->driver->is_error)判断驱动是否报错,若驱动把is_error置真,_spam()/_ham()会中断后续处理(markasjunk.php)。
内置驱动一览
README 列出的默认驱动与 drivers 目录 一一对应,MarkasjunkTest测试用例也逐一验证了这 8 个驱动都能被正确实例化(tests/MarkasjunkTest.php):
| 驱动 | 作用 | 依赖/要求 |
|---|---|---|
cmd_learn | 调用外部命令(例如sa-learn)处理消息 | 无 |
dir_learn | 把消息副本存放到预定义目录,供后续处理 | 目录需 Web 服务器可写 |
email_learn | 把消息以附件形式或直接转发到指定邮箱 | 需 Roundcube 1.4+ |
sa_blacklist | 将 spam 消息的发件人地址加入用户黑名单(ham 则加入白名单) | 需 SAUserPrefs 插件 |
amavis_blacklist | 同上,但面向 Amavis/Amacube 的 wblist 表 | 需 Amacube 插件(驱动作者 Der-Jan) |
sa_detach | 若消息是 SpamAssassin 垃圾报告且内嵌原始邮件附件,则分离附件存入收件箱、删除报告本身 | 无 |
edit_headers | 用preg_replace修改消息头 | 无 |
jsevent | 示例驱动,演示如何利用 JS 事件控制按钮显示 | 无(示例) |
各驱动深入解析
cmd_learn(命令行学习):读取markasjunk_spam_cmd/markasjunk_ham_cmd配置的命令模板,逐条消息展开宏后通过shell_exec执行(drivers/cmd_learn.php)。实现细节上有两点值得注意:
- 对
%s(发件人地址)和%h:(消息头)展开出的值会做escapeshellarg转义,且若值以-开头(可能被解析成命令行选项)则跳过该消息,防止注入; %f(消息文件路径)通过tempnam()在temp_dir生成临时文件并写入get_raw_body()内容,执行后立即unlink清理;- 旧版宏
%xds已被自动替换为%h:x-dspam-signature(DSPAM 支持)。
dir_learn(目录学习):把markasjunk_spam_dir/markasjunk_ham_dir指定目录作为存储目标,将每条消息的原始内容写入tempnam($dest_dir, $filename)生成的临时文件(drivers/dir_learn.php),文件名前缀由markasjunk_filename决定,支持%u、%t(spam/ham)、%l、%d宏。
email_learn(邮件学习):读取markasjunk_email_spam/markasjunk_email_ham目标地址,通过rcmail_sendmail发送(drivers/email_learn.php)。markasjunk_email_attach = true时把原消息作为message/rfc822附件发送(附件名取原主题 +.eml);false时则以rcmail_resend_mail构造 Resent-* 回弹邮件(bounce)形式直接转发。主题由markasjunk_email_subject控制,默认learn this message as %t。
sa_blacklist(SpamAssassin 黑/白名单):依赖 SAUserPrefs 插件,读取其数据库配置(sauserprefs_sql_table_name、sauserprefs_sql_username_field等)与markasjunk_sauserprefs_config指定的配置路径(默认../sauserprefs/config.inc.php)。对每条消息,取发件人地址(校验合法性后)写入blacklist_from/whitelist_from(SpamAssassin v4 下自动切换为blocklist_from/welcomelist_from),并先清除对侧名单避免冲突(drivers/sa_blacklist.php)。
amavis_blacklist(Amavis 黑/白名单):依赖 Amacube 插件,操作 Amavis 的wblist/mailaddr/users三张表(drivers/amavis_blacklist.php):spam 置wb='b'(black),ham 置wb='w'(white)。邮箱地址不存在时自动写入mailaddr表(priority 20)。
sa_detach(SpamAssassin 报告分离):spam()为空操作;ham()遍历消息附件,寻找message/rfc822类型且x-spam-type=original的内嵌原始邮件,分离保存到目标邮箱后删除原报告(drivers/sa_detach.php)。
edit_headers(头部编辑):读取markasjunk_spam_patterns/markasjunk_ham_patterns的patterns+replacements数组,对原始头部(get_raw_headers())执行preg_replace,再用新头部替换原始消息中的旧头部,最后save_message()写入目标邮箱并让主流程移动/删除旧消息(drivers/edit_headers.php)。该驱动直接改动消息源(message source),README 对此有明确警告。
jsevent(JS 事件示例):演示init()的用法——向页面注入监听markasjunk-update事件的脚本,根据当前邮箱名动态切换 spam/ham 按钮(例如对spam2/spam3只显示 ham 按钮、对unknown1/unknown2两个按钮都显示并改文案为 "As possibly spam")(drivers/jsevent.php)。
命令行宏变量
cmd_learn与dir_learn/email_learn的配置支持一套统一的宏展开机制,config.inc.php.dist 中有完整定义:
| 宏 | 替换内容 |
|---|---|
%u | 用户名(来自会话信息) |
%l | 用户名的本地部分(若用户名为邮箱地址) |
%d | 用户名的域名部分(若用户名为邮箱地址;否则为默认邮件域名) |
%i | 用户默认身份(identity)的邮箱地址 |
%s | 消息的发件人邮箱地址(仅cmd_learn) |
%f | 消息文件路径(仅cmd_learn) |
%t | 消息类型spam/ham(dir_learn文件名与email_learn主题) |
%h:<header name> | 消息中指定头的内容(小写),例如%h:x-dspam-signature(仅cmd_learn) |
Spam 与 Ham 学习命令示例
README 给出了 SpamAssassin 的经典命令行写法,可直接填入markasjunk_spam_cmd/markasjunk_ham_cmd:
Spam 学习(markasjunk_spam_cmd):
sa-learn --spam --username=%u %f或使用按域名/用户拆分的主目录预文件:
sa-learn --spam --prefs-file=/var/mail/%d/%l/.spamassassin/user_prefs %fHam 学习(markasjunk_ham_cmd):
sa-learn --ham --username=%u %f或:
sa-learn --ham --prefs-file=/var/mail/%d/%l/.spamassassin/user_prefs %f结合cmd_learn源码可知:%f会被替换为插件生成的临时消息文件路径(位于 Roundcube 的temp_dir),命令执行后临时文件随即被删除;%u、%l、%d、%i、%s均经过escapeshellarg处理。若希望 DSPAM 也能学习,可将%f换成%h:x-dspam-signature等专属头参与拼装。
运行多个驱动(进阶,需谨慎)
README 特别警告:同时运行多个驱动非常危险,务必充分测试,风险自负。更安全的做法是创建一个把所有逻辑都做完的单一驱动。如果确实需要多驱动,可以把多个驱动串联:例如先sa_blacklist再cmd_learn,或edit_headers与cmd_learn组合。
从架构上讲,markasjunk_learning_driver目前只能指定一个驱动类(_init_driver()只实例化一个类,markasjunk.php),因此"多驱动"实际上是通过编写一个内部串联多个逻辑的自定义驱动实现的。README 提供了一个示例多驱动作为起点,但它只是一个起始模板,需要针对具体场景修改后才能使用。
edit_headers 配置示例
edit_headers驱动通过正则改写头部,README 给出了给主题加[SPAM]前缀的完整示例(警告:这是简单示例,自行使用需承担风险):
spam 模式(匹配整个 Subject 行并加前缀):
$config['markasjunk_spam_patterns'] = array( 'patterns' => array('/^(Subject:\s*)(.*)$/m'), 'replacements' => array('$1[SPAM] $2') );ham 模式(去除前缀还原):
$config['markasjunk_ham_patterns'] = array( 'patterns' => array('/^(Subject:\s*)\[SPAM\](https://link.gitcode.com/i/f2f3b11025838adebcc606d40304c7dd)$/m'), 'replacements' => array('$1$2') );重要安全提示(README 与 config 模板双重强调):必须匹配整条头部行,包括头部名称本身;务必使用^和$锚点并启用m修饰符;在真实消息上使用前一定要充分测试。该驱动会改动消息源(message source),一旦正则写错可能破坏邮件结构。patterns与replacements一一对应,替换采用 PHPpreg_replace语义。
消息流转的源码级验证
mark_message()的完整流程(markasjunk.php)印证了 README 描述的两场景行为:
- 根据动作名判断
$is_spam(plugin.markasjunk.junk为 spam); - 从 POST 读取
_uid与_mbox,经rcmail_action::get_uids()解析出messageset(支持多文件夹模式); - 特殊情形:当选择"全选"(
uid == '*')、非多文件夹且有驱动时,自行取整个邮箱索引(markasjunk.php); - 调用
_spam()/_ham():先执行驱动,再设置/清除SEEN、JUNK、NONJUNK标志(markasjunk.php); - 成功后通过
markasjunk_move前端命令把消息移动到目标邮箱(或刷新列表),并显示reportedasjunk/reportedasnotjunk确认提示。
插件还通过set_env()向前端 JS 暴露markasjunk_ham_mailbox、markasjunk_spam_mailbox、markasjunk_move_spam、markasjunk_move_ham、markasjunk_permanently_remove、markasjunk_spam_only等环境变量(markasjunk.php),前端逻辑位于 markasjunk.js。
测试与验证
仓库提供了两个层面的测试:
- 单元测试tests/MarkasjunkTest.php 验证插件对象构造(继承
rcube_plugin),并逐一实例化全部 8 个内置驱动,确认markasjunk_{driver}类加载无误; - 浏览器测试tests/Browser/MailTest.php 覆盖邮件列表场景下的端到端行为。
启用驱动后,建议结合markasjunk_debug = true观察日志输出(cmd_learn会记录完整命令与命令输出、dir_learn/email_learn/黑名单驱动也会记录关键动作),确认命令执行与地址写入是否符合预期。
许可证说明
插件以 GNU General Public License Version 3+(GPLv3+) 发布(见 markasjunk.php)。README 特别说明:即使皮肤(skins)包含部分编程工作,它们不被视为插件的关联部分,因此皮肤不受 GPL 条款约束;皮肤许可详情请查看核心皮肤目录中的 README。
小结
MarkAsJunk 的价值在于把"用户手动纠错"这一行为转化为反垃圾系统的训练信号:通过markasjunk_learning_driver挂接cmd_learn(sa-learn/DSPAM 命令行)、dir_learn(落盘)、email_learn(转发)、sa_blacklist/amavis_blacklist(名单维护)、sa_detach(报告分离)或自定义驱动,可以在消息移动之外完成真正的机器学习闭环。部署时请牢记三条红线:编辑头部必须精确匹配整行并充分测试;多驱动串联前务必验证各步骤的副作用;任何来自邮件的值(发件人、头部内容)都会经过转义与合法性校验,切勿自行绕过。
- 即时通讯
- 后端
- 前端
【免费下载链接】roundcubemail
The Roundcube Webmail suite
相关推荐
终极Postal反垃圾邮件实战:SpamAssassin+ClamAV集成指南,拦截率提升至99%
终极Postal反垃圾邮件实战:SpamAssassin+ClamAV集成指南,拦截率提升至99% Postal作为一款功能全面的开源邮件投递平台,支持收发邮件
后端通信Docker-Mailserver反垃圾邮件实战:Rspamd与SpamAssassin深度配置
Docker Mailserver反垃圾邮件实战:Rspamd与SpamAssassin深度配置 想要搭建一个安全可靠的邮件服务器,却总是被垃圾邮件困扰?😩
后端通信云原生Haraka反垃圾邮件终极指南:集成SpamAssassin和Rspamd的完整配置教程
Haraka反垃圾邮件终极指南:集成SpamAssassin和Rspamd的完整配置教程 Haraka是一款快速、高度可扩展的事件驱动型SMTP服务器,通过插件
后端网络/通信
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考