NAME
Mailmunge::Filter - base class for Mailmunge filtering
ABSTRACT
This is the base class for all filters. Implement your own filtering policy by subclassing Mailmunge::Filter and overriding various functions to implement your policy.
SYNOPSIS
package MyFilter;
use base qw(Mailmunge::Filter);
sub filter_relay {
# ... implement your policy
}
my $filter = MyFilter->new();
$filter->run();
1;
IMPORTANT NOTE
Before you base your filter on Mailmunge::Filter
, consider looking at Mailmunge::Filter::Compat instead. That class provides a more convenient set of methods for traversing parts of a MIME message, and is what most people should base their filters on.
However, you should still familiarize yourself with Mailmunge::Filter
because many useful methods are documented here.
THINGS YOU SHOULD NEVER DO IN YOUR FILTER CODE
Because Mailmunge filters use STDIN and STDOUT to communicate with the multiplexor, you should never read from STDIN or write to STDOUT from filter code you write. It's OK to write to STDERR, however; if mailmunge-multiplexor
was invoked with the -l
option, then anything your filter prints to STDERR will be logged in the mail log.
CLASS METHODS
Class methods may be called either as Mailmunge::Filter->method(...)
or $filter->method(...)
, where $filter
is an instance of Mailmunge::Filter
or some derived class.
Mailmunge::Filter->new
Construct a new Mailmunge::Filter object and parse the command-line arguments to determine whether or not to update status tags and to enter the main loop.
ip_to_hostname ($ip [, $fcrdns])
Converts a human-readable IPv4 or IPv6 to a hostname if reverse-dns exists. If it does not reverse-resolve, returns "[$ip]"
If $fcrdns is true (the default if not supplied), then this function performs Forward-confirmed reverse DNS to make sure that the hostname include $ip in one of its forward-resolving records. If FCrDNS fails, then "[$ip]" is returned.
mailmunge_version
Returns the version of Mailmunge as a string.
filter_version
Returns the version of the filter as a string.
This function currently returns the empty string. It can be overridden in a derived class if a filter wants to declare its version (as retrieved by "mm-mx-ctrl filterversion") for informational purposes. The version can be whatever you like and doesn't affect Mailmunge operation; it is purely for you to keep track of the version of your filter that is installed.
Note that runs of whitespaces in the return value of filter_version
are converted to single underscores before the value is communicated back to mailmunge-multiplexor. An undef value or the empty string is converted to "(Not set)"
log_identifier()
Returns mailmunge-filter
and is used as the syslog program identifier. Derived classes can override this if they wish.
log_options()
Returnc pid,ndelay
, passed as the option parameter to openlog
in Sys::Syslog
. Derived classes can override this if they wish.
log_facility()
Returns mail
, the default syslog facility. Derived classes can override this to log using a different facility.
canonical_email($email)
Returns $email all lower-case with leading or trailing angle-brackets stripped. If $email is undef
, then returns undef
.
domain_of($email)
Return the domain part of $email
mta_is_postfix ()
Returns true if the MTA is Postfix; false if not (or could not be determined)
mta_is_sendmail ()
Returns true if the MTA is Sendmail; false if not (or could not be determined)
inputmsg()
Returns the relative path to the input message file received from the MTA
headers_file()
Returns the relative path to the HEADERS file. This file contains only the top-level headers of the email message. The headers are unwrapped, so this file is guaranteed to contain exactly one header per line.
pristine_headers_file()
Returns the relative path to the PRISTINE_HEADERS file. This file contains only the top-level headers of the email message, exactly as they came in from the SMTP client.
INSTANCE METHODS
action_from_response ($ctx, $resp)
NOTE: This function still exists for backward-compatibility, but you should use $ctx->action_from_response($resp)
instead.
Given a Mailmunge::Response object $resp
, take the appropriate action. This function operates as follows:
if $resp
is not defined, or is not a Mailmunge::Response
object, return 0.
If $resp->is_tempfail
, call $self->action_tempfail($ctx, $resp->message)
and return 1
If $resp->is_reject
, call $self->action_bounce($ctx, $resp->message)
and return 1
If $resp->is_discard
, call $self->action_discard($ctx)
and return 1
Otherwise, return 0.
log($ctx, $level, $msg)
Log a message to syslog of the specified level. $level must be one of 'emerg', 'alert', 'crit', 'err', 'warning', 'notice', 'info' or 'debug' $ctx is either an Mailmunge::Context object, or a string representing a Queue-ID.
run()
Run the filter. In non-embedded-Perl mode, starts the server main loop. In embedded-Perl mode, registers the filter in a global variable and returns; the multiplexor will start the server main loop at an appropriate time.
action_bounce($ctx, $reply, $code, $dsn)
NOTE: This function still exists for backward-compatibility, but you should use $ctx->action_bounce($reply, $code, $dsn)
instead.
Ask the MTA to bounce the message. $ctx is the Mailmunge::Context object; $reply is the text of the bounce; code is a 3-digit 5xy reply code, and $dsn is a three-numbered 5.x.y DSN code.
Writes the 'B' line to RESULTS to tell the C code to bounce the message.
This method may only be called from filter_message
or filter_wrapup
(or from functions called while they are active.)
action_discard($ctx)
NOTE: This function still exists for backward-compatibility, but you should use $ctx->action_discard()
instead.
Ask the MTA to discard the message. $ctx is the Mailmunge::Context object.
Writes the 'D' line to RESULTS to tell the C code to discard the message.
This method may only be called from filter_message
or filter_wrapup
(or from functions called while they are active.)
action_tempfail($ctx, $reply, $code, $dsn)
NOTE: This function still exists for backward-compatibility, but you should use $ctx->action_tempfail($reply, $code, $dsn)
instead.
Ask the MTA to tempfail the message. $ctx is the Mailmunge::Context object; $reply is the text of the tempfail response; code is a 3-digit 4xy reply code, and $dsn is a three-numbered 4.x.y DSN code.
Writes the 'T' line to RESULTS to tell the C code to tempfail the message.
This method may only be called from filter_message
or filter_wrapup
(or from functions called while they are active.)
action_change_header($ctx, $hdr, $value, $idx)
NOTE: This function still exists for backward-compatibility, but you should use $ctx->action_change_header($hdr, $value, $idx)
instead.
Ask the MTA to change the value of header "$hdr" to "$value". $ctx is the Mailmunge::Context object, and $idx (if supplied) is the 1-based index of the header to change in the case of multiple headers. If "$hdr" was not present, then the MTA is asked to add it.
Do not include a colon in the header name.
This method may only be called from filter_message
or filter_wrapup
(or from functions called while they are active.)
action_delete_header($ctx, $hdr, $idx)
NOTE: This function still exists for backward-compatibility, but you should use $ctx->action_delete_header($hdr, $idx)
instead.
Ask the MTA to delete the header header "$hdr" $ctx is the Mailmunge::Context object, and $idx (if supplied) is the 1-based index of the header to delete in the case of multiple headers.
Do not include a colon in the header name.
This method may only be called from filter_message
or filter_wrapup
(or from functions called while they are active.)
action_delete_all_headers($ctx, $hdr)
NOTE: This function still exists for backward-compatibility, but you should use $ctx->delete_all_headers($hdr)
instead.
Ask the MTA to delete all headers "$hdr". Do not include a colon in the header name.
This method may only be called from filter_message
or filter_wrapup
(or from functions called while they are active.)
change_sender($ctx, $sender)
NOTE: This function still exists for backward-compatibility, but you should use $ctx->change_sender($sender)
instead.
Asks the MTA to change the envelope sender
This method may only be called from filter_message
or filter_wrapup
(or from functions called while they are active.)
add_recipient($ctx, $recip)
NOTE: This function still exists for backward-compatibility, but you should use $ctx->add_recipient($recip)
instead.
Asks the MTA to add a recipient to the envelope
This method may only be called from filter_message
or filter_wrapup
(or from functions called while they are active.)
delete_recipient($ctx, $recip)
NOTE: This function still exists for backward-compatibility, but you should use $ctx->delete_recipient($recip)
instead.
Asks the MTA to delete $recip from the list of envelope recipients
This method may only be called from filter_message
or filter_wrapup
(or from functions called while they are active.)
action_add_header($ctx, $hdr, $val)
NOTE: This function still exists for backward-compatibility, but you should use $ctx->action_add_header($hdr, $val)
instead.
Add a header to the message
This method may only be called from filter_message
or filter_wrapup
(or from functions called while they are active.)
action_insert_header($ctx, $hdr, $val, $pos)
NOTE: This function still exists for backward-compatibility, but you should use $ctx->action_insert_header($hdr, $val, $pos)
instead.
Add a header to the message in the specified position.
This method may only be called from filter_message
or filter_wrapup
(or from functions called while they are active.)
As a special case, if $pos
is negative or not supplied, then the header is added at the end, as with action_add_header
action_sm_quarantine($ctx, $reason)
NOTE: This function still exists for backward-compatibility, but you should use $ctx->action_sm_quarantine($reason)
instead.
Ask the MTA to quarantine the message. $reason is the reason for the quarantine.
Note that this is different from Mailmunge's quarantine function. Instead, it ends up calling the Milter function smfi_quarantine.
This method may only be called from filter_message
or filter_wrapup
(or from functions called while they are active.)
action_quarantine_entire_message($ctx, $reason)
NOTE: This function still exists for backward-compatibility, but you should use $ctx->action_quarantine_entire_message($reason)
instead.
Quarantines the message in the Mailmunge quarantine directory. $reason is the reason for quarantining. Note that calling this function does not affect disposition of the message. If you do not want the original message delivered, you must call action_bounce or action_discard.
On success, returns the directory in which the message was quarantined. On failure, returns undef.
This method may only be called from filter_message
or filter_wrapup
(or from functions called while they are active.)
copy_or_link($src, $dst)
Attempt to hard-link the file $src to $dst. $dst must be the full desired path of the destination. If hard-linking fails, copy the file instead.
Returns 1 on success; 0 on failure.
unknown_command($cmd, @args)
Called when we get an unknown command. By default, returns an error to the multiplexor. Subclasses can override this method to do something useful.
inputmsg_fh()
Returns a filehandle open for reading at the start of the raw input message file received from the MTA. If something goes wrong, returns undef.
product_name()
Returns 'Mailmunge'. Derived classes may override this if they wish.
spooldir()
Returns the full path to the Mailmunge spool directory. Exactly equivalent to Mailmunge::Constants->get('Path:SPOOLDIR')
.
inputmsg_absolute($ctx)
Returns the absolute path to the input message file received from the MTA. This is the path that should be passed to virus-scanners, for example.
tick($tick_number)
This method is called periodically by the multiplexor if given the "-X interval" option. It should be overridden in a derived class and should return 1. The base class implementation does nothing and returns 0, causing a warning to be logged.
Note that the "tick" functionality of Mailmunge is deprecated.
filter_map($map, $key)
Look up the key $key in the map named $map. Return an array that looks like one of the following:
('PERM', 'Permanent failure message')
('TEMP', 'Temporary failure message')
('TIMEOUT', 'Timeout message')
('OK', 'Result-Of-Lookup')
('NOTFOUND', '')
The base class implementation simply returns:
('PERM', 'Filter does not implement map method')
filter_relay ($ctx)
$ctx is an Mailmunge::Context object. This method is called as part of the xxfi_connect Milter callback, when a remote machine attempts to connect. It should be overridden by a derived class.
The following $ctx fields are available:
$ctx->hostip IP address of connecting host
$ctx->hostname Hostname of the connecting host
$ctx->client_port Client TCP port
$ctx->my_ip Server's IP address
$ctx->my_port Server's TCP port
$ctx->qid Queue ID (Note: May be NOQUEUE if queue ID not available)
The function must return an Mailmunge::Response object instructing the MTA how to handle the callback.
The default base class method returns Mailmunge::Response->CONTINUE().
filter_helo ($ctx)
$ctx is an Mailmunge::Context object. This method is called as part of the xxfi_helo Milter callback, when a remote machine issues a HELO/EHLO command. It should be overridden by a derived class.
The following $ctx fields are available:
$ctx->hostip IP address of connecting host
$ctx->hostname Hostname of the connecting host
$ctx->helo The argument to the EHLO/HELO command
$ctx->client_port Client TCP port
$ctx->my_ip Server's IP address
$ctx->my_port Server's TCP port
$ctx->qid Queue ID (Note: May be NOQUEUE if queue ID not available)
The function must return an Mailmunge::Response object instructing the MTA how to handle the callback.
The default base class method returns Mailmunge::Response->CONTINUE().
filter_sender ($ctx)
$ctx is an Mailmunge::Context object. This method is called as part of the xxfi_envfrom Milter callback, when a remote machine issues a MAIL From: command. It should be overridden by a derived class.
The following $ctx fields are available:
$ctx->sender Envelope sender address
$ctx->hostip IP address of connecting host
$ctx->hostname Hostname of the connecting host
$ctx->helo The argument to the EHLO/HELO command
$ctx->qid Queue ID (Note: May be NOQUEUE if queue ID not available)
$ctx->esmtp_args Arrayref of ESMTP arguments to MAIL From:
$ctx->cwd The current working directory
The function must return an Mailmunge::Response object instructing the MTA how to handle the callback.
The default base class method returns Mailmunge::Response->CONTINUE().
filter_recipient ($ctx)
$ctx is an Mailmunge::Context object. This method is called as part of the xxfi_envrcpt Milter callback, when a remote machine issues a RCPT To: command. It should be overridden by a derived class.
The following $ctx fields are available:
$ctx->recipients An arrayref consisting of a single recipient
$ctx->sender Envelope sender address
$ctx->hostip IP address of connecting host
$ctx->hostname Hostname of the connecting host
$ctx->first_recip The recipient from the I<first> RCPT To: command
$ctx->helo The argument to the EHLO/HELO command
$ctx->cwd The current working directory
$ctx->qid Queue ID
$ctx->rcpt_mailer The ${rcpt_mailer} macro value for this recipient
$ctx->rcpt_host The ${rcpt_host} macro value for this recipient
$ctx->rcpt_addr The ${rcpt_addr} macro value for this recipient
$ctx->esmtp_args Arrayref of ESMTP arguments to MAIL From:
The function must return an Mailmunge::Response object instructing the MTA how to handle the callback.
The default base class method returns Mailmunge::Response->CONTINUE().
filter_message ($ctx)
$ctx is an Mailmunge::Context object. This method is called when a message is to be scanned. The return value of this method is normally ignored; filter_message
normally indicates disposition of the message by calling one of the action_ methods. If no disposition is specified, then the message is delivered.
If, however, the return value of filter_message
is a Mailmunge::Response object whose status is one of TEMPFAIL, REJECT or DISCARD, then the corresponding action_tempfail
, action_bounce
or action_discard
actions are called. (See action_from_response
for the mechanism used to interpret the return value.)
In other words, the following pairs of lines are equivalent if called from filter_message
:
# Equivalent ways to tempfail
$self->action_tempfail($ctx, "some msg"); return;
return Mailmunge::Response->TEMPFAIL(message => "some msg");
# Equivalent ways to reject
$self->action_bounce($ctx, "some msg"); return;
return Mailmunge::Response->REJECT(message => "some msg");
# Equivalent ways to discard
$self->action_discard($ctx); return;
return Mailmunge::Response->DISCARD;
The following $ctx fields are available; see Mailmunge::Context for details.
$ctx->ambiguous_content
$ctx->connecting_ip
$ctx->connecting_name
$ctx->esmtp_args
$ctx->helo
$ctx->hostip
$ctx->hostname
$ctx->message_id
$ctx->mime_entity
$ctx->mailmunge_id
$ctx->qid
$ctx->recipients
$ctx->recipient_esmtp_args
$ctx->sender
$ctx->subject
$ctx->subject_count
$ctx->suspicious_chars_in_body
$ctx->suspicious_chars_in_headers
$ctx->was_resent
$ctx->cwd
The most important field is probably $ctx->mime_entity, which is the MIME::Entity representing the message being filtered. If you replace the entity by calling:
$ctx->new_mime_entity($new_entity);
then the MTA will replace the body of the message with the body of $new_entity. Setting new_mime_entity
also updates mime_entity
.
The base class implementation of filter_message
does nothing.
filter_wrapup ($ctx)
$ctx is an Mailmunge::Context object. This method is called immediately after filter_message() and the $ctx object has the same available fields as in filter_message().
In filter_wrapup, it is not possible to change the message body (that is, calling $ctx->new_mime_entity($new_entity)
will have no effect.)
You can only change headers or change the delivery disposition of the message. Typically, filter_wrapup is used for something like DKIM-signing a message.
The base class implementation does nothing.
Normally, the return value of filter_wrapup
is ignored, but if it returns a Mailmunge::Response object, then it has the same effect on message disposition as a Mailmunge::Response
object returned by filter_message
.
initialize()
This method is called once when the filter process starts up. It can be used to establish per-process resources such as database connections.
If you are using an embedded Perl interpreter in mailmunge-multiplexor, then this function is called after a new scanning process has forked.
The base class implementation does nothing.
NOTE: you should do all per-process initialization in initialize()
and not in top-level Perl functions outside of any method. The reason is that if you run the multiplexor using embedded Perl, then initialize()
is called each time a new scanner is forked. Code outside of methods is called just once, which may lead to inappropriate sharing of resources such as filehandles between scanner processes.
To reiterate: Do all per-process initialization in $filter->initialize()
prefork_initialize ()
This function is called once when the run()
method is called. If you are not using an embedded Perl interpreter in mailmunge-multiplexor, then prefork_initialize()
is called in exactly the same circumstances as initialize()
and there's no point in using it.
If you are using an embedded Perl interpreter, then prefork_initialize()
is called once, before any workers are forked, and can be used to initialize resources that can be shared across a fork.
The base class implementation does nothing.
cleanup
This method is called just before the filter process exits. It is the cleanup counterpart to initialize(); the return value of this filter is used as the argument to exit()
.
The base class implementation does nothing and returns 0
push_tag($ctx, $tag)
Updates the process title (as seen by "ps") to show what the worker is doing; the text "$tag" is displayed.
Also updates the worker status in the multiplexor to be "$tag" with the given queue-ID. This only has an effect if the multiplexor was invoked with the "-Z" option.
Judicious use of push_tag
and pop_tag
calls around time-consuming parts of your filter can be very helpful to let you see what the filter is doing using ps
or mm-mx-ctrl busyworkers
.
pop_tag($ctx)
Restores the previous tag (if any) in effect prior to the corresponding push_tag call.
read_commands_file($ctx, $need_f)
Reads the COMMANDS file and fills in fields in the Mailmunge::Context object $ctx. If $ctx is supplied as undef, then read_commands_file allocates a new one.
If $need_f is true, then the function fails if the COMMANDS file does not end with an 'F' command.
Returns $ctx on success (or a newly-allocated Mailmunge::Context if $ctx was undef); undef on failure.
rfc2822_date([$now])
Returns an RFC2822-formatted date for the Unix time $now (defaults to time() if $now is nto supplied.) An RFC2822-formatted date looks something like:
Fri, 1 Jan 2021 15:49:21 -0500
header_timezone ()
Returns the appropriate value to use for this host's timezone in a mail header. Returns something of the form "+HHMM" or "-HHMM" depending on your local time zone.
synthesize_received_header ($ctx)
Returns a Received: header similar to the one that would be added by the MTA if it delivered the message currently being processed.
Use this for message scanners such as SpamAssassin that expect to find the most-recent MTA header at the beginning of the message.
get_host_name()
(Attempt to) get the host's fully-qualified name. Returns the best guess at the hostname.
privdata($key [,$val)
Get or set private data. The data's lifetime is the lifetime of the filter process. One use of this, for example, could be to connect to a database and store the handle. In initialize(), you could say:
my $dbh = DBI->connect($connect_string, $username, $password);
$self->privdata('dbh', $dbh);
and then in the remaining functions you could retrieve the database handle:
my $dbh = $self->privdata('dbh');
Using privdata ensures that you won't interfere with any built-in state stored by the filter for internal purposes, and it is cleaner than littering your code with global variables.
The $key can be any string you like; however, keys whose names start with '@' are reserved for internal use. If you use a key whose name starts with '@', it might conflict with internal Mailmunge code and cause unexpected behavior.
decode_mime_string($str)
Given a MIME-encoded header string $str, decode it as a native Perl string. Tries very, very hard to return something sensible, even for malformed $str.
Note that this is a *native* Perl string. If you want to print it or do any sort of I/O on it, you probably need to encode it as UTF-8 first. For example, if you want to log the decoded subject, use something like this:
# Decode the subject
my $decoded_sub = $self->decode_mime_string($ctx->subject);
# Encode as UTF-8 for logging purposes
my $enc_sub = Encode::encode('utf-8', $decoded_sub);
# Log it
$self->log($ctx, 'info', "subject=$enc_sub");
SEE ALSO
Mailmunge::Filter::Compat, Mailmunge::Context, Mailmunge::Response, Mailmunge
AUTHOR
Dianne Skoll <dianne@skollsoft.com>
LICENSE
This code is licensed under the terms of the GNU General Public License, version 2.
Copyright © 2024 Skoll Software Consulting