[pmg-devel] [PATCH pmg-api 1/1] fix #2795: add support for DSN

Dominik Csapak d.csapak at proxmox.com
Wed Nov 24 10:33:19 CET 2021


works generally as advertised, i have some (minor) remarks
though (comments inline)

On 11/22/21 20:50, Stoiko Ivanov wrote:
> Delivery status notifications (DSN) are defined in RFC 3464 [0].
> They introduce additional key-value parameters for the MAIL and RCPT
> commands.
> 
> The default config we ship offers DSN on the internal port (but not on
> the external port) - which works fine in after-queue filtering mode,
> but not in before-queue filtering mode.
> 
> This patchset adds support for collecting the relevant parameters and
> passing them on to the postfix instance [1], which actually sends out the
> mail.
> 
> additionally postfix does syntax-checking before passing the mail to
> the proxy (before queue filtering).
> 
> Since the handling is done by postfix we don't need to generate the
> DSN for any of the cases.
> 
> tested with various combinations of the -V, -N and -R parameters to
> sendmail (e.g.):
> ```
> /usr/sbin/sendmail -N success,delay,failure \
> -V '<xxxxxxxx at test.proxmox.com>'\
> -R hdrs test at test.domain.example
> ```
> 
> some tests with invalid combinations were also done with netcat.
> 
> [0] https://tools.ietf.org/html/rfc3464
> [1] http://www.postfix.org/DSN_README.html
> Signed-off-by: Stoiko Ivanov <s.ivanov at proxmox.com>
> ---
>   src/PMG/RuleDB/Accept.pm |  2 +-
>   src/PMG/RuleDB/BCC.pm    |  6 +++++-
>   src/PMG/SMTP.pm          | 46 +++++++++++++++++++++++++++++++++-------
>   src/PMG/Utils.pm         | 19 ++++++++++++++---
>   src/bin/pmg-smtp-filter  |  1 +
>   5 files changed, 61 insertions(+), 13 deletions(-)
> 
> diff --git a/src/PMG/RuleDB/Accept.pm b/src/PMG/RuleDB/Accept.pm
> index 0bcf250..cd67ea2 100644
> --- a/src/PMG/RuleDB/Accept.pm
> +++ b/src/PMG/RuleDB/Accept.pm
> @@ -122,7 +122,7 @@ sub execute {
>   	} else {
>   	    my ($qid, $code, $mess) = PMG::Utils::reinject_mail(
>   		$entity, $msginfo->{sender}, $tg,
> -		$msginfo->{xforward}, $msginfo->{fqdn});
> +		$msginfo->{xforward}, $msginfo->{fqdn}, $msginfo->{param});
>   	    if ($qid) {
>   		foreach (@$tg) {
>   		    syslog('info', "%s: accept mail to <%s> (%s) (rule: %s)", $queue->{logid}, encode('UTF-8', $_), $qid, $rulename);
> diff --git a/src/PMG/RuleDB/BCC.pm b/src/PMG/RuleDB/BCC.pm
> index a8db3f5..d364690 100644
> --- a/src/PMG/RuleDB/BCC.pm
> +++ b/src/PMG/RuleDB/BCC.pm
> @@ -156,9 +156,13 @@ sub execute {
>   	    $entity->print ($fh);
>   	    print $fh "bcc end\n";
>   	} else {
> +	    my $param = {};
> +	    for my $bcc (@bcc_targets) {
> +		$param->{rcpt}->{$bcc}->{notify} = "never";
> +	    }
>   	    my $qid = PMG::Utils::reinject_mail(
>   		$entity, $msginfo->{sender}, \@bcc_targets,
> -		$msginfo->{xforward}, $msginfo->{fqdn}, 1);
> +		$msginfo->{xforward}, $msginfo->{fqdn}, $param);
>   	    foreach (@bcc_targets) {
>   		if ($qid) {
>   		    syslog('info', "%s: bcc to <%s> (rule: %s, %s)", $queue->{logid}, $_, $rulename, $qid);
> diff --git a/src/PMG/SMTP.pm b/src/PMG/SMTP.pm
> index 68c833e..c7aba01 100644
> --- a/src/PMG/SMTP.pm
> +++ b/src/PMG/SMTP.pm
> @@ -38,6 +38,7 @@ sub reset {
>       delete $self->{smtputf8};
>       delete $self->{xforward};
>       delete $self->{status};
> +    delete $self->{param};
>   }
>   
>   sub abort {
> @@ -100,25 +101,54 @@ sub loop {
>   	    $self->reply ("250 2.5.0 OK");
>   	    next;
>   	} elsif ($cmd eq 'mail') {
> -	    if ($args =~ m/^from:\s*<([^\s\>]*)>([^>]*)$/i) {
> +	    if ($args =~ m/^from:\s*<([^\s\>]*?)>(.*)$/i) {

this change looks like it's unrelated on the first glance
i understand this is because options can contain '>'
(i mean local parts of mails can too, but this'll break other
parts already...)

a small notice in the commit message (or a comment) would be good though

could also be it's own patch (smthg like 'fix smtp option parsing' ?)


>   		delete $self->{to};
> -		my ($from, $opts) = ($1, $2);
> -		if ($opts =~ m/\sSMTPUTF8/) {
> -		    $self->{smtputf8} = 1;
> -		    $from = decode('UTF-8', $from);
> +		my ($from, $opts) = ($1, lc($2));
> +
> +		my $err = 0;
> +		for my $opt (split(' ', $opts)) {
> +		    if ($opt =~ /(ret|envid)=([^ =]+)/ ) {
> +			$self->{param}->{mail}->{$1} = $2;
> +		    } elsif ($opt =~ m/smtputf8/) {
> +			$self->{smtputf8} = 1;
> +			$from = decode('UTF-8', $from);
> +		    } elsif ($opt =~ m/(?:size=(:?\d+)|body=8bitmime)/) {
> +			#skip
> +		    } else {
> +			$err = 1;
> +			last;
> +		    }

as talked off-list, i am unsure if rejecting 'unknown' parameters is
the best way. before, we simply ignore them, so if a mail-server already
sent invalid/unknown paramters it will now be blocked which is not
that good imho. i think we should still ignore/drop them or pass them
along.

>   		}
>   		$self->{from} = $from;
> -		$self->reply ('250 2.5.0 OK');
> +		if ($err) {
> +		    $self->reply ("501 5.5.2 Syntax: MAIL FROM: unknown parameter");
> +		} else {
> +		    $self->reply ('250 2.5.0 OK');
> +		}
>   		next;
>   	    } else {
>   		$self->reply ("501 5.5.2 Syntax: MAIL FROM: <address>");
>   		next;
>   	    }
>   	} elsif ($cmd eq 'rcpt') {
> -	    if ($args =~ m/^to:\s*<([^\s\>]+)>[^>]*$/i) {
> +	    if ($args =~ m/^to:\s*<([^\s\>]+?)>(.*)$/i) {
>   		my $to = $self->{smtputf8} ? decode('UTF-8', $1) : $1;
> +		my $opts = lc($2);
>   		push @{$self->{to}} , $to;
> -		$self->reply ('250 2.5.0 OK');
> +		my $err = 0;
> +		for my $opt (split(' ', $opts)) {
> +		    if ($opt =~ /(notify|orcpt)=([^ =]+)/i ) {
> +			$self->{param}->{rcpt}->{$to}->{$1} = $2;
> +		    } else {
> +			$err = 1;
> +			last;
> +		    }
> +		}
> +		if ($err) {
> +		    $self->reply ("501 5.5.2 Syntax: RCPT TO: unknown parameter");
> +		} else {
> +		    $self->reply ('250 2.5.0 OK');
> +		}
>   		next;
>   	    } else {
>   		$self->reply ("501 5.5.2 Syntax: RCPT TO: <address>");
> diff --git a/src/PMG/Utils.pm b/src/PMG/Utils.pm
> index 92c3a7a..4eebfa5 100644
> --- a/src/PMG/Utils.pm
> +++ b/src/PMG/Utils.pm
> @@ -203,7 +203,7 @@ sub subst_values {
>   }
>   
>   sub reinject_mail {
> -    my ($entity, $sender, $targets, $xforward, $me, $nodsn) = @_;
> +    my ($entity, $sender, $targets, $xforward, $me, $params) = @_;
>   
>       my $smtp;
>       my $resid;
> @@ -244,15 +244,28 @@ sub reinject_mail {
>   	    $mail_opts .= " SMTPUTF8" if $has_utf8_targets;
>   	}
>   
> +	if (defined($params->{mail})) {
> +	    my $mailparams = $params->{mail};
> +	    for my $p (keys %$mailparams) {
> +		$mail_opts .= " $p=$mailparams->{$p}";
> +	    }
> +	}
> + >   	if (!$smtp->_MAIL("FROM:" . $sender_addr . $mail_opts)) {
>   	    syslog('err', "smtp error - got: %s %s", $smtp->code, scalar ($smtp->message));
>   	    die "smtp from: ERROR";
>   	}
>   
> -	my $rcpt_opts = $nodsn ? " NOTIFY=NEVER" : "";
> -
>   	foreach my $target (@$targets) {
>   	    my $rcpt_addr;
> +	    my $rcpt_opts = '';
> +	    if (defined($params->{rcpt}->{$target})) {
> +		my $rcptparams = $params->{rcpt}->{$target};
> +		for my $p (keys %$rcptparams) {
> +		    $rcpt_opts .= " $p=$rcptparams->{$p}";
> +		}
> +	    }
> +
>   	    if (utf8::is_utf8($target)) {
>   		$rcpt_addr = encode('UTF-8', $smtp->_addr($target));
>   	    } else {
> diff --git a/src/bin/pmg-smtp-filter b/src/bin/pmg-smtp-filter
> index b070c8e..45eb125 100755
> --- a/src/bin/pmg-smtp-filter
> +++ b/src/bin/pmg-smtp-filter
> @@ -640,6 +640,7 @@ sub handle_smtp {
>   	$msginfo->{sender} = $smtp->{from};
>   	$msginfo->{xforward} = $smtp->{xforward};
>   	$msginfo->{targets} = $smtp->{to};
> +	$msginfo->{param} = $smtp->{param};
>   
>   	my $dkim_sign = $msginfo->{trusted} && $pmg_cfg->get('admin', 'dkim_sign');
>   	if ($dkim_sign) {
> 





More information about the pmg-devel mailing list