]> git.proxmox.com Git - pmg-api.git/commitdiff
fix #2795: add support for DSN
authorStoiko Ivanov <s.ivanov@proxmox.com>
Thu, 25 Nov 2021 17:48:13 +0000 (18:48 +0100)
committerStoiko Ivanov <s.ivanov@proxmox.com>
Fri, 26 Nov 2021 11:01:03 +0000 (12:01 +0100)
store the esmtp parameters for the MAIL and RCPT command needed to
support Delivery status notifications (DSN - RFC 3464 [0]) and pass
them to the outbound postfix instance (port 10025) used for sending
the mail further (see also [1]).

Postfix does syntax-checking before passing the mail to the proxy
also in before-queue filtering mode.

Since the handling is done by postfix we don't need to generate any
DSN in the regular case.
For mail put into quarantine I decided to skip sending a delivery
notification (on the expectation, that few people are using quarantine
outbound, and that I would not consider a mail put in quarantine as
delivered successfully)

We only store a whitelist of parameters, instead of passing all,
because some parameters might not be valid anymore after processing
(e.g. SIZE)

The DSN EHLO keyword was added for the after-queue filtering case -
else the inbound postfix is the system that sends out the
notification.

tested with various combinations of the -V, -N and -R parameters to
sendmail (e.g.):
```
/usr/sbin/sendmail -N success,delay,failure \
-V '<xxxxxxxx@test.proxmox.com>'\
-R hdrs test@test.domain.example
```
tested the following scenarios in before and after-queue filter mode:
* successful delivery
* successful delivery with set DSN
* failed delivery (recipient rejects with 544)
* failed delivery with DSN
* delivering a mail with empty envelope sender (bounce)

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@proxmox.com>
Reviewed-by: Dominik Csapak <d.csapak@proxmox.com>
Tested-by: Dominik Csapak <d.csapak@proxmox.com>
src/PMG/RuleDB/Accept.pm
src/PMG/RuleDB/BCC.pm
src/PMG/SMTP.pm
src/PMG/Utils.pm
src/bin/pmg-smtp-filter

index 0bcf250c259c7e75789df4e11d2172b24bd9da30..cd67ea23c0e6617154003aa1496b8f72cf027b89 100644 (file)
@@ -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);
index a8db3f530d65b0b42fe1490b7c54d5794570b817..d3646906b7cff518cd3c87db4897342404c3a265 100644 (file)
@@ -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);
index b3550d42f68a822743eed8b8df0123d8d6ae15aa..fbf5c955d1e08b84f28b65bcec5aa07e8c6df856 100644 (file)
@@ -38,6 +38,7 @@ sub reset {
     delete $self->{smtputf8};
     delete $self->{xforward};
     delete $self->{status};
+    delete $self->{param};
 }
 
 sub abort {
@@ -77,6 +78,7 @@ sub loop {
            $self->reply ("250-ENHANCEDSTATUSCODES");
            $self->reply ("250-8BITMIME");
            $self->reply ("250-SMTPUTF8");
+           $self->reply ("250-DSN");
            $self->reply ("250-XFORWARD NAME ADDR PROTO HELO");
            $self->reply ("250 OK.");
            $self->{lmtp} = 1 if ($cmd eq 'lhlo');
@@ -103,9 +105,16 @@ sub loop {
            if ($args =~ m/^from:\s*<([^\s\>]*?)>( .*)?$/i) {
                delete $self->{to};
                my ($from, $opts) = ($1, $2 // '');
-               if ($opts =~ m/\sSMTPUTF8/) {
-                   $self->{smtputf8} = 1;
-                   $from = decode('UTF-8', $from);
+
+               for my $opt (split(' ', $opts)) {
+                   if ($opt =~ /(ret|envid)=([^ =]+)/i ) {
+                       $self->{param}->{mail}->{$1} = $2;
+                   } elsif ($opt =~ m/smtputf8/i) {
+                       $self->{smtputf8} = 1;
+                       $from = decode('UTF-8', $from);
+                   } else {
+                       #ignore everything else
+                   }
                }
                $self->{from} = $from;
                $self->reply ('250 2.5.0 OK');
@@ -117,7 +126,15 @@ sub loop {
        } elsif ($cmd eq 'rcpt') {
            if ($args =~ m/^to:\s*<([^\s\>]+?)>( .*)?$/i) {
                my $to = $self->{smtputf8} ? decode('UTF-8', $1) : $1;
+               my $opts = $2 // '';
                push @{$self->{to}} , $to;
+               for my $opt (split(' ', $opts)) {
+                   if ($opt =~ /(notify|orcpt)=([^ =]+)/i ) {
+                       $self->{param}->{rcpt}->{$to}->{$1} = $2;
+                   } else {
+                       #ignore everything else
+                   }
+               }
                $self->reply ('250 2.5.0 OK');
                next;
            } else {
index 92c3a7aad96d2b2b4bfda7b55a077c7dfccbd83d..4eebfa52912d9084b8b68b7d6d1f617d01e44c61 100644 (file)
@@ -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 {
index b070c8ef3dd84540733a1b040b1cb27c11179e44..45eb125b4005610ba0b72362efadba52b4531158 100755 (executable)
@@ -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) {