noumenia/libmilterphp

libMilterPHP is a Postfix/Sendmail Milter library implementation in PHP.

1.9 2023-12-26 09:46 UTC

This package is auto-updated.

Last update: 2024-06-12 16:39:56 UTC


README

  _ _ _     __  __ _ _ _            ____  _   _ ____
 | (_) |__ |  \/  (_) | |_ ___ _ __|  _ \| | | |  _ \
 | | | '_ \| |\/| | | | __/ _ \ '__| |_) | |_| | |_) |
 | | | |_) | |  | | | | ||  __/ |  |  __/|  _  |  __/
 |_|_|_.__/|_|  |_|_|_|_| \___|_|  |_|   |_| |_|_|

libMilterPHP is a Postfix/Sendmail Milter library implementation in PHP.

Features

  • High-performance multi-process
  • Low memory footprint
  • Strict coding standards
  • Support for all milter protocol version 2 commands
  • Listen on IP address/port or UNIX sockets
  • Support for signals
  • Proper memory-resident daemon

Requirements

  • PHP 7.2, 7.3, 7.4, 8.0, 8.1, 8.2, 8.3
  • iconv module
  • posix module
  • sockets module

Install with RPM packages

You may install libMilterPHP via the copr repository, for Alma/Rocky/Oracle Enterprise Linux and Fedora, simply use:

dnf copr enable mksanthi/noumenia
dnf install libMilterPHP

Install with Composer

You may install libMilterPHP with composer, to get the latest version use:

composer require noumenia/libmilterphp

Install manually

Download the repository and copy the libMilterPHP project directory in the appropriate place within your project or within an accessibe location, for example under /usr/share/php.

How to use

Load and initialize the library by loading the common.inc.php file:

require_once("/path/to/libMilterPHP/controller/common.inc.php");

Create your custom class and extend the milter library \libMilterPHP\Milter:

class MilterTestDaemon extends \libMilterPHP\Milter { ... }

Define all or some of the available callbacks, these are:

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

Instantiate your custom class and pass the configuration parameters to the constructor. These parameters are:

// 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)
);

Finally, call the acceptConnections() method, to start the daemon in the foreground:

// Accept connections
$milter->acceptConnections();

Sample milter

The following is a sample milter daemon that checks the FROM and matches it with a known spammer address.

#!/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();

Notes about SMFIF_* actions

The supported actions array uses SMFIF_* constants, to define which actions MAY be taken by the milter. This is an essential part of the milter/MTA conversation, to define what actions the MTA will expect from the milter. All constants are defined within the libMilterPHP/controller/constants.inc.php file.

Notes about SMFIP_* ignored content

The ignored content array uses SMFIP_* constants, to inform the MTA which parts of the SMTP conversation should NOT be sent to the milter. This feature saves a lot of bandwidth and CPU usage by both the MTA and the milter. All constants are defined within the libMilterPHP/controller/constants.inc.php file.

Notes about SMFIR_* response codes

These constants define various responses that the milter can give to the MTA. The ones marked as "Modification" only make changes to the email data, after a modification the MTA expects the milter to provide another response. The ones marked as "Accept/reject" give a final indication about the email and allow the milter and MTA to end their conversation.

ConstantValueModificationAccept/rejectAsynchronous
SMFIR_ADDRCPT+[X][ ][ ]
SMFIR_DELRCPT-[X][ ][ ]
SMFIR_ACCEPTa[ ][X][ ]
SMFIR_REPLBODYb[X][ ][ ]
SMFIR_CONTINUEc[ ][X][ ]
SMFIR_DISCARDd[ ][X][ ]
SMFIR_ADDHEADERh[X][ ][ ]
SMFIR_CHGHEADERm[X][ ][ ]
SMFIR_PROGRESSp[ ][ ][X]
SMFIR_QUARANTINEq[X][ ][ ]
SMFIR_REJECTr[ ][X][ ]
SMFIR_TEMPFAILt[ ][X][ ]
SMFIR_REPLYCODEy[ ][X][ ]

Notes about the milter communication protocol

The milter communication protocol runs over UNIX sockets or over an IP/port connection. It uses the following binary format:

 size = uint32		(the size of the command+data)
 command = char		(a one character command)
 data = data[size-1]	(the data)

For example, the MTA could send a message that in hex looks like:

 +----------+---------+--------------------------------+
 | size     | command | data...                        |
 +----------+---------+----------+----------+----------+
 | 0000000d |      4f | 00000006 | 000001ff | 001fffff |
 +----------+---------+----------+----------+----------+

The libMilterPHP library will first read the first 4 bytes of the message and interpret it as a uint32 number, which indicates the size of the message that is expected. The library will then keep reading for the whole message and once done, it will separate the command from the data. The command will be used to call the equivalent callback function with the data as a parameter.

Design diagram

The following diagram shows a simplified view of the libMilterPHP implementation.

+----------------+         +-------------------+
|                |         |                   |
| 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

Related projects