noumenia / libmilterphp
libMilterPHP 是 PHP 中的 Postfix/Sendmail Milter 库实现。
Requires
- php: >=7.2
- ext-iconv: *
- ext-posix: *
- ext-sockets: *
README
_ _ _ __ __ _ _ _ ____ _ _ ____
| (_) |__ | \/ (_) | |_ ___ _ __| _ \| | | | _ \
| | | '_ \| |\/| | | | __/ _ \ '__| |_) | |_| | |_) |
| | | |_) | | | | | | || __/ | | __/| _ | __/
|_|_|_.__/|_| |_|_|_|_| \___|_| |_| |_| |_|_|
libMilterPHP 是 PHP 中的 Postfix/Sendmail Milter 库实现。
特性
- 高性能多进程
- 低内存占用
- 严格的编码标准
- 支持所有 Milter 协议版本 2 命令
- 监听 IP 地址/端口或 UNIX 套接字
- 支持信号
- 合适的内存驻留守护进程
要求
- PHP 7.2, 7.3, 7.4, 8.0, 8.1, 8.2, 8.3
- iconv 模块
- posix 模块
- sockets 模块
使用 RPM 软件包安装
您可以通过 copr 仓库安装 libMilterPHP,对于 Alma/Rocky/Oracle 企业 Linux 和 Fedora,只需使用
dnf copr enable mksanthi/noumenia
dnf install libMilterPHP
使用 Composer 安装
您可以使用 composer 安装 libMilterPHP,要获取最新版本,请使用
composer require noumenia/libmilterphp
手动安装
下载仓库并复制 libMilterPHP 项目目录到您的项目适当位置或可访问位置,例如在 /usr/share/php 之下。
如何使用
通过加载 common.inc.php 文件来加载和初始化库
require_once("/path/to/libMilterPHP/controller/common.inc.php");
创建自定义类并扩展 milter 库 \libMilterPHP\Milter
class MilterTestDaemon extends \libMilterPHP\Milter { ... }
定义所有或部分可用的回调,这些是
public function smficAbort(array $message): void // Required
public function smficBody(array $message): void
public function smficConnect(array $message): void
public function smficMacro(array $message): void // Required
public function smficBodyeob(array $message): void // Required
public function smficHelo(array $message): void
public function smficHeader(array $message): void
public function smficMail(array $message): void
public function smficEoh(array $message): void
public function smficRcpt(array $message): void
public function smficQuit(array $message): void // Required
实例化您的自定义类并将配置参数传递给构造函数。这些参数是
// Create milter object
$milter = new MilterTestDaemon(
"postfix", // Effective user owner
"mail", // Effective group owner
"/run/daemon.pid", // PID file
1024, // Process limit
"unix:/run/daemon.sock", // Connection string ("unix:/path/to/file.sock" or "inet:port@IP")
array(SMFIF_QUARANTINE), // Supported actions (array of SMFIF_* constants)
array(SMFIP_NOBODY) // Ignored content (array of SMFIP_* constants)
);
最后,调用 acceptConnections() 方法,在前台启动守护进程
// Accept connections
$milter->acceptConnections();
示例 milter
以下是一个示例 milter 守护进程,它会检查 FROM 并将其与已知的垃圾邮件地址匹配。
#!/usr/bin/env php
<?php
/**
* Milter test daemon
*
* @copyright Noumenia (C) 2019 - All rights reserved - Software Development - www.noumenia.gr
* @license GNU GPL v3.0
* @package libMilterPHP
* @subpackage miltertestdaemon
*/
require_once("./libMilterPHP/controller/common.inc.php");
class MilterTestDaemon extends \libMilterPHP\Milter {
/**
* SMFIC_ABORT - Abort current filter checks
* @param array{size: int, command: string, binary: string} $message Message array
* @return void
*/
public function smficAbort(array $message): void
{
// No-op
}
/**
* SMFIC_MAIL - MAIL FROM: information - use SMFIP_NOMAIL to suppress
* @param array{size: int, command: string, binary: string, args: array<string>} $message Message array
* @return void
*/
public function smficMail(array $message): void
{
// Mark this email for quarantine
if(preg_match('/spameri@tiscali\.it/i', strval($message['args'][0])) === 1)
$reply = array('command' => SMFIR_QUARANTINE, 'data' => array('reason' => "Spammer in quarantine!"));
else
$reply = array('command' => SMFIR_CONTINUE, 'data' => array());
$this->reply($reply);
// Since SMFIR_QUARANTINE is a "Modification" action, the MTA is waiting for further commands...
// So continue processing
$reply = array('command' => SMFIR_CONTINUE, 'data' => array());
$this->reply($reply);
}
/**
* SMFIC_BODYEOB - End of body marker
* @param array{size: int, command: string, binary: string} $message Message array
* @return void
*/
public function smficBodyeob(array $message): void
{
// Default reply
$reply = array(
// Set the command (char)
'command' => SMFIR_CONTINUE,
// Set the data (chars of uint32 size)
'data' => array()
);
$this->reply($reply);
}
}
// Create milter object
$milter = new MilterTestDaemon(
"miltertestuser", // Effective user owner
"mail", // Effective group owner
"/run/daemon.pid", // PID file
1024, // Process limit
"unix:/run/daemon.sock", // Connection string ("unix:/path/to/file.sock" or "inet:port@IP")
array(SMFIF_QUARANTINE), // Supported actions (array of SMFIF_* constants)
array( // Ignored content (array of SMFIP_* constants)
SMFIP_NOCONNECT,
SMFIP_NOHELO,
SMFIP_NORCPT,
SMFIP_NOBODY,
SMFIP_NOHDRS,
SMFIP_NOEOH
)
);
// Accept connections
$milter->acceptConnections();
关于 SMFIF_* 动作的说明
支持的动作数组使用 SMFIF_* 常量,以定义 milter 可能采取哪些操作。这是 milter/MTA 通信的重要组成部分,用于定义 MTA 期望从 milter 接收哪些操作。所有常量都在 libMilterPHP/controller/constants.inc.php 文件中定义。
关于 SMFIP_* 忽略内容的说明
忽略内容数组使用 SMFIP_* 常量,以通知 MTA 哪些 SMTP 通信部分不应该发送给 milter。这个特性通过 MTA 和 milter 的大量带宽和 CPU 使用节省了很多。所有常量都在 libMilterPHP/controller/constants.inc.php 文件中定义。
关于 SMFIR_* 响应代码的说明
这些常量定义了 milter 可以向 MTA 提供的各种响应。标记为 "修改" 的那些仅修改电子邮件数据,修改后 MTA 期望 milter 提供另一个响应。标记为 "接受/拒绝" 的那些给出了关于电子邮件的最终指示,允许 milter 和 MTA 结束他们的通信。
| 常量 | 值 | 修改 | 接受/拒绝 | 异步 |
|---|---|---|---|---|
| SMFIR_ADDRCPT | + | [X] | [ ] | [ ] |
| SMFIR_DELRCPT | - | [X] | [ ] | [ ] |
| SMFIR_ACCEPT | a | [ ] | [X] | [ ] |
| SMFIR_REPLBODY | b | [X] | [ ] | [ ] |
| SMFIR_CONTINUE | c | [ ] | [X] | [ ] |
| SMFIR_DISCARD | d | [ ] | [X] | [ ] |
| SMFIR_ADDHEADER | h | [X] | [ ] | [ ] |
| SMFIR_CHGHEADER | m | [X] | [ ] | [ ] |
| SMFIR_PROGRESS | p | [ ] | [ ] | [X] |
| SMFIR_QUARANTINE | q | [X] | [ ] | [ ] |
| SMFIR_REJECT | r | [ ] | [X] | [ ] |
| SMFIR_TEMPFAIL | t | [ ] | [X] | [ ] |
| SMFIR_REPLYCODE | y | [ ] | [X] | [ ] |
关于 milter 通信协议的说明
milter 通信协议在 UNIX 套接字或 IP/端口连接上运行。它使用以下二进制格式
size = uint32 (the size of the command+data)
command = char (a one character command)
data = data[size-1] (the data)
例如,MTA可以发送一条看起来像十六进制的消息
+----------+---------+--------------------------------+
| size | command | data... |
+----------+---------+----------+----------+----------+
| 0000000d | 4f | 00000006 | 000001ff | 001fffff |
+----------+---------+----------+----------+----------+
libMilterPHP库首先读取消息的前4个字节,并将其解释为uint32数字,该数字表示预期的消息大小。库将接着读取整个消息,一旦完成,它将命令和数据分开。命令将用于调用相应的回调函数,并将数据作为参数传递。
设计图
以下图展示了libMilterPHP实现的简化视图。
+----------------+ +-------------------+
| | | |
| common.inc.php | +---+-> | constants.inc.php |
| | | | |
+----------------+ | +-------------------+
|
Constants | Set system constants
Autoloader | Set milter protocol constants
Initialize logging |
|
| +----------------------------------------+
| | |
+-> | NoumeniaLibMilterPHPAutoloader.inc.php |
| | |
| +----------------------------------------+
|
| Define the autoloader function
|
| +-----------------+
| | |
+-> | loginit.inc.php |
| |
+-----------------+
Set log destination
Set message priority
+-------------------------+
| |
| SocketInterface.inc.php |
implements | |
+----------------+ +-----------------------+ +--------> +-------------------------+
| | extends | |
| Milter.inc.php | <-----> | SocketManager.inc.php |
| | | | extends
+----------------+ +-----------------------+ <--------> +-----------------------+ +-------------------------+
| | implements | |
__construct() __construct() | DaemonManager.inc.php | +--------> | DaemonInterface.inc.php |
reply() acceptConnections() | | | |
processChild() socketReadByHttp() +-----------------------+ +-------------------------+
socketWriteByHttp()
socketReadByNul() __construct()
socketWriteByNul() checkProcessLimit()
socketReadBySize() fork()
socketWriteBySize() signalHandler()
remoteConnect()
__destruct()
+-------------------------+
| |
| LoggerInterface.inc.php |
implements | |
+-------------+ +--------> +-------------------------+
| |
| Log.inc.php | Abstract log destination interface
| |
+-------------+ +------+-> +----------------------------+
| | |
Event logging | | LogDestinationNull.inc.php | +----+
Display handler | | | |
| +----------------------------+ |
| |
| Log to null |
| |
| +-------------------------------+ | +---------------------------------+
| | | |implements | |
+-> | LogDestinationConsole.inc.php | +-+---------> | LogDestinationInterface.inc.php |
| | | | | |
| +-------------------------------+ | +---------------------------------+
| |
| Log to console | Abstract log destination interface
| |
| +------------------------------+ |
| | | |
+-> | LogDestinationSyslog.inc.php | +--+
| |
+------------------------------+
Log to syslog