← Documentation Index

NAME

Mailmunge::Filter::Compat - base class for Mailmunge filtering that includes some backward-compatibility for migrating MIMEDefang filter code.

SYNOPSIS

Mailmunge::Filter::Compat is a subclass of Mailmunge::Filter. As such, all methods documented in Mailmunge::Filter are also available here.

Mailmunge::Filter::Compat implements a filter_message function that in turn calls filter_begin, filter, filter_multipart, filter_end and filter_wrapup methods. These methods operate similarly to their counterparts in MIMEDefang filtering code and make it easier to migrate MIMEDefang filters to Mailmunge.

The return values from filter_begin, filter, filter_multipart, filter_end and filter_wrapup are normally ignored. However, if any of these functions returns a Mailmunge::Response object, then it is interpreted the same way as a Mailmunge::Response object returned by filter_message.

If you derive your filter from Mailmunge::Filter::Compat, you must not override filter_message. Instead, override filter_begin, filter, filter_multipart, filter_end and filter_wrapup as required.

Any functions that are callable from filter_message as well as Mailmunge::Context methods available to filter_message are available in filter_begin, filter, flter_multipart and filter_end.

OPERATION

The body filtering functions are called as follows:

  1. filter_begin is called once.

  2. Recursing through the MIME::Entity object $ctx->mime_entity, filter_multipart is called for each multipart/* sub-part and filter is called for each non-multipart sub-part.

  3. filter_end is called once. This is the last point at which you are allowed to modify the message body.

  4. filter_wrapup is called once. In filter_wrapup, modifications to the message body are not allowed, but you are allowed to modify top-level headers. Typically, this is where you would do DKIM-signing.

Note that if any method rejects a message by calling action_bounce, action_discard or action_tempfail, then filtering is short-circuited and remaining callbacks are not called.

ABSTRACT

package MyFilter;
use base qw(Mailmunge::Filter::Compat);
sub filter_begin {
    my ($self, $ctx) = @_;
    # ... etc
}

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.

METHODS

filter_message($ctx)

Overrides filter_message from the base Mailmunge::Filter class. Do not tamper with or override this method.

The $ctx fields available are documented in Mailmunge::Filter's filter_message documentation; these same fields are available in filter_begin, filter_multipart, filter, filter_end and filter_wrapup.

filter_begin($ctx)

Called once at the beginning of filtering. See "filter_message" in Mailmunge::Filter for the list of $ctx fields available.

filter_multipart($ctx, $part, $fname, $extension, $type)

filter_multipart is called once for each multipart/* part in the message. $part is the sub-part being filtered and is a MIME::Entity. $fname is the best-guess at the filename associated with the part (if any); it is taken from the Content-Type.name or Content-Disposition.filename MIME fields. $ext is the filename extension including the leading dot associated with $fname, and $type is the MIME type of the part.

filter($ctx, $part, $fname, $extension, $type)

filter is called once for each non-multipart part in the message. The arguments are the same as filter_multipart.

filter_end($ctx)

filter_end is called once at the end of filtering. This is the last place you can modify the message (which you can do with action_add_entity or action_add_part).

action_accept($ctx [, $warning])

This method may only be called in filter or filter_multipart. It causes the part to remain in the message. If no method that removes or modifies a part is called, then action_accept is implicitly the default.

If $warning is supplied, then we call action_accept_with_warning to add a warning message in a new text/plain part appended to the message.

action_drop($ctx [, $warning])

This method may only be called in filter or filter_multipart. It causes the part (and if multipart, all sub-parts) to be silently removed from the message. However, if $warning is supplied, then we call action_drop_with_warning instead.

action_drop_with_warning($ctx, $warning)

This method may only be called in filter or filter_multipart. It causes the part (and if multipart, all sub-parts) to be removed from the message. Additionally, a warning message is added in a new text/plain part that is appended to the message.

action_accept_with_warning($ctx, $warning)

This method may only be called in filter or filter_multipart. It causes a warning message to be added in a new text/plain part that is appended to the message.

action_replace_with_warning($ctx, $warning)

This method may only be called in filter or filter_multipart. It causes the part to be removed from the message and replaced with a new text/plain part containing the $warning message.

action_add_entity($ctx, $entity [, $offset])

Causes a new MIME::Entity to be added to the message at offset $offset, which is the zero-based index in the top-level message at which to add the entity. If $offset is not supplied, the part is added to the end of the message.

action_add_part($ctx, $type, $encoding, $data, $fname, $disposition, $offset)

Creates a new MIME::Entity whose MIME type is $type, Content-Encoding is $encoding, Content-Disposition is $disposition, Content-Disposition.filename is $fname and contents are $data. Then calls action_add_entity with the new part and supplied $offset. This is really just a convenience function that builds the MIME::Entity for you.

take_stab_at_filename($entity)

Returns Mailmunge's best-guess at the filename associated with MIME::Entity $entity. Note that the decoded filename is returned, so any MIME encoding is parsed and decoded.

CONVERTING FROM MIMEDefang

Conversion of a filter from MIMEDefang to Mailmunge can range from very mechanical to quite complicated, depending on the filter. This section is a MIMEDefang-to-Mailmunge conversion guide.

The Filter

A MIMEDefang filter is a fragment of a Perl program, whereas a Mailmunge filter is a complete Perl program.

To convert a MIMEDefang filter to Mailmunge, your Mailmunge filter should start something like this:

package MyFilter;
use strict;
use warnings;

use base qw(Mailmunge::Filter::Compat);

my $filter = MyFilter->new();
$filter->run();

Callbacks

Mailmunge callbacks are similar to MIMEDefang, but have different arguments. The Following table shows the correspondence.

MIMEDefang                                  Mailmunge
==========                                  =========

sub filter_initialize {                     sub initialize {
    # ...                                       my ($self) = @_;
}                                               # ...
                                            }

sub filter_cleanup {                        sub cleanup {
    # ...                                       my ($self) = @_;
}                                               # ...
                                            }


sub filter_relay {                          sub filter_relay {
    my ($ip, $name, $port,                      my ($self, $ctx) = @_;
        $my_ip, $my_port, $qid) = @_;           # ...
    # ...                                   }
}


sub filter_helo {                           sub filter_helo {
    my ($ip, $name, $helo, $port,               my ($self, $ctx) = @_;
        $my_ip, $my_port, $qid) = @_;           # ...
    # ...                                   }
}


sub filter_sender {                         sub filter_sender {
    my ($sender, $ip, $name, $helo) = @_;       my ($self, $ctx) = @_;
    # ...                                       # ...
}                                           }


sub filter_recipient {                      sub filter_recipient {
    my ($recip, $sender, $ip, $name,            my ($self, $ctx) = @_;
        $first_recip, $helo,                    # ...
        $mailer, $host, $addr) = @_;        }
        # ...
}

sub filter_begin {                          sub filter_begin {
    my ($entity) = @_;                          my ($self, $ctx) = @_;
    # ...                                       # ... Entity is $ctx->mime_entity
}                                           }

sub filter {                                sub filter {
    my ($entity, $fname,                        my ($self, $ctx, $entity, $fname,
        $extension, $mime_type) = @_;               $extension, $mime_type) = @_;
    # ...                                       # ...
}                                           }


sub filter_multipart {                      sub filter_multipart {
    my ($entity, $fname,                        my ($self, $ctx, $entity, $fname,
        $extension, $mime_type) = @_;               $extension, $mime_type) = @_;
    # ...                                       # ...
}                                           }


sub filter_end {                            sub filter_end {
    my ($entity) = @_;                          my ($self, $ctx) = @_;
    # ...                                       # ... Entity is $ctx->mime_entity
}                                           }


sub filter_wrapup {                         sub filter_wrapup {
    my ($entity) = @_;                          my ($self, $ctx) = @_;
    # ...                                       # ... Entity is $ctx->mime_entity
}                                           }


sub filter_map {                            sub filter_map {
    my ($map, $key) = @_;                       my ($self, $map, $key) = @_;
    # ...                                       # ...
}                                           }


sub filter_tick {                           sub tick {
    my ($tick_no) = @_;                         my ($self, $tick_no) = @_;
    # ...                                       # ...
}                                           }


sub filter_validate { ... }                 No equivalent in Mailmunge


sub defang_warning { ... }                  No equivalent in Mailmunge

Return Values

Many MIMEDefang functions return an array of elements. In Mailmunge, they instead return a Mailmunge::Response object.

MIMEDefang                                        Mailmunge
==========                                        =========

return ('CONTINUE', 'ok');                        return Mailmunge::Response->CONTINUE();

return ('TEMPFAIL', 'Message', 421, '4.1.1');     return Mailmunge::Response->TEMPFAIL(message => 'Message', code => 421, dsn => '4.1.1');

return ('TEMPFAIL', 'Message', 571, '5.2.1');     return Mailmunge::Response->REJECT(message => 'Message', code => 571, dsn => '5.2.1');

return ('DISCARD', 'Message');                    return Mailmunge::Response->DISCARD(message => 'Message');

return ('ACCEPT_AND_NO_MORE_FILTERING', 'ok');    return Mailmunge::Response->ACCEPT_AND_NO_MORE_FILTERING();

Global Variables

MIMEDefang filters make use of a plethora of global variables. Mailmunge does not use any global variables. The correspondences for the most important variables are shown below.

MIMEDefang                                        Mailmunge
==========                                        =========

$MessageID                                        $ctx->message_id
$RealRelayAddr                                    $ctx->connecting_ip
$RealRelayHostname                                $ctx->connecting_name
$CWD                                              $ctx->cwd
@ESMTPArgs                                        @{$ctx->esmtp_args}
@SenderESMTPArgs                                  @{$ctx->esmtp_args}
$Helo                                             $ctx->helo
$RelayAddr                                        $ctx->hostip
$RelayHostname                                    $ctx->hostname
$MIMEDefangID                                     $ctx->mailmunge_id
$MessageID                                        $ctx->message_id
$QueueID                                          $ctx->qid
%RecipientESMTPArgs                               %{$ctx->recipient_esmtp_args}
@Recipients                                       @{$ctx->recipients}
$Sender                                           $ctx->sender
$Subject                                          $ctx->subject
$SubjectCount                                     $ctx->subject_count
$SuspiciousCharsInHeaders                         $ctx->suspicious_chars_in_headers
$SuspiciousCharsInBody                            $ctx->suspicious_chars_in_body
$WasResent                                        $ctx->was_resent

VARIOUS BITS OF MIMEDEFANG FUNCTIONALITY

Mailmunge moves a lot of functionality out of the core filter into modules. Here is a rough correspondence between MIMEDefang and Mailmunge functionality. Note that in some cases, we recommend external CPAN modules that already have the required functionality; duplicating that effort within Mailmunge would not be efficient.

DNSBL lookups

Instead of the various relay_is_blacklisted functions, use Net::DNSBL::Client.

Streaming

Instead of stream_by_recipient or stream_by_domain, use Mailmunge::Action::Stream.

Virus-Scanning

Instead of all the *_contains_virus_* functions, use File::VirusScan.

Bogus MX Host Checks

Instead of md_get_bogus_mx_hosts, use Mailmunge::Test::GetMX.

Boilerplate

Instead of append_text_boilerplate or append_html_boilerplate, use Mailmunge::Action::Boilerplate.

SpamAssassin

Instead of the various spam_assassin_* functions, use Mailmunge::Test::SpamAssassin.

Rspamd

Instead of rspamd_check, use Mailmunge::Test::Rspamd.

SMTP Call-forwards

Instead of md_check_against_smtp_server, use Mailmunge::Test::SMTPForward.

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