DeutschEnglish

Submenu

 - - - By CrazyStat - - -

2. June 2015

Zimbra: Using zmpostfixpolicyd to stop backscatter

Filed under: Linux,Server Administration — Tags: , , , — Christopher Kramer @ 19:20

A Zimbra 8.0 Server (on Ubuntu) was backscattering. Unfortunately Zimbra servers cannot use the postfix setting

smtpd_reject_unlisted_sender=yes

when an alias domain is setup. Everybody says it is easy, just install the policyd as a recipient_restriction and that’s it. All you need to do is:

su - zimbra
zmlocalconfig -e postfix_enable_smtpd_policyd=yes
zmprov mcf +zimbraMtaRestriction "check_policy_service unix:private/policy"
zmmtactl stop
zmmtactl start

Unfortunately, this did not work for me with Zimbra 8.0. The server was still backscattering.

First, I enabled verbose output for the policyd. To do so, open /opt/zimbra/postfix/conf/master.cf.in (the .in is important) in a texteditor and search for this part:

%%uncomment LOCAL:postfix_enable_smtpd_policyd%%policy    unix  -       n       n       -       -       spawn
%%uncomment LOCAL:postfix_enable_smtpd_policyd%%        user=zimbra argv=/usr/bin/perl /opt/zimbra/libexec/zmpostfixpolicyd

Add a -v ad the end of the command:

%%uncomment LOCAL:postfix_enable_smtpd_policyd%%policy    unix  -       n       n       -       -       spawn
%%uncomment LOCAL:postfix_enable_smtpd_policyd%%        user=zimbra argv=/usr/bin/perl /opt/zimbra/libexec/zmpostfixpolicyd -v

Then (as zimbra user), recreate the master.cf and restart postfix with these commands:

zmmtactl stop
zmmtactl start

Now when a mail is received (from an external sender), you will see output like this in your mail log (on Debian/Ubuntu: /var/log/mail.log):

/opt/zimbra/libexec/zmpostfixpolicyd[29326]: Attribute: ccert_fingerprint=
/opt/zimbra/libexec/zmpostfixpolicyd[29326]: Attribute: sasl_method=
/opt/zimbra/libexec/zmpostfixpolicyd[29326]: Attribute: sasl_sender=
/opt/zimbra/libexec/zmpostfixpolicyd[29326]: Attribute: size=3428
/opt/zimbra/libexec/zmpostfixpolicyd[29326]: Attribute: helo_name=[...]
/opt/zimbra/libexec/zmpostfixpolicyd[29326]: Attribute: reverse_client_name=[...]
/opt/zimbra/libexec/zmpostfixpolicyd[29326]: Attribute: queue_id=
/opt/zimbra/libexec/zmpostfixpolicyd[29326]: Attribute: encryption_cipher=AECDH-AES256-SHA
/opt/zimbra/libexec/zmpostfixpolicyd[29326]: Attribute: encryption_protocol=TLSv1.2
/opt/zimbra/libexec/zmpostfixpolicyd[29326]: Attribute: etrn_domain=
/opt/zimbra/libexec/zmpostfixpolicyd[29326]: Attribute: ccert_subject=
/opt/zimbra/libexec/zmpostfixpolicyd[29326]: Attribute: request=smtpd_access_policy
/opt/zimbra/libexec/zmpostfixpolicyd[29326]: Attribute: protocol_state=RCPT
/opt/zimbra/libexec/zmpostfixpolicyd[29326]: Attribute: stress=
/opt/zimbra/libexec/zmpostfixpolicyd[29326]: Attribute: recipient=does-not-exist@example.com
/opt/zimbra/libexec/zmpostfixpolicyd[29326]: Attribute: sasl_username=
/opt/zimbra/libexec/zmpostfixpolicyd[29326]: Attribute: ccert_pubkey_fingerprint=
/opt/zimbra/libexec/zmpostfixpolicyd[29326]: Attribute: instance=7289.556db23f.b6b7d.0
/opt/zimbra/libexec/zmpostfixpolicyd[29326]: Attribute: protocol_name=ESMTP
/opt/zimbra/libexec/zmpostfixpolicyd[29326]: Attribute: encryption_keysize=256
/opt/zimbra/libexec/zmpostfixpolicyd[29326]: Attribute: recipient_count=0
/opt/zimbra/libexec/zmpostfixpolicyd[29326]: Attribute: ccert_issuer=
/opt/zimbra/libexec/zmpostfixpolicyd[29326]: Attribute: sender=[...]
/opt/zimbra/libexec/zmpostfixpolicyd[29326]: Attribute: client_name=[...]
/opt/zimbra/libexec/zmpostfixpolicyd[29326]: Attribute: client_address=[...]
/opt/zimbra/libexec/zmpostfixpolicyd[29326]: Recipient Domain: example.com
/opt/zimbra/libexec/zmpostfixpolicyd[29326]: Recipient userid: does-not-exist
/opt/zimbra/libexec/zmpostfixpolicyd[29326]: Action: dunno

If you see these logs, then zmpostfixpolicyd is set up correctly in postfix. If you don’t, check the postfix configuration.

But the policy daemon always returned “dunno”, even if the recipient did not exist. Therefore, I looked into the perl code of the policy daemon (/opt/zimbra/libexec/zmpostfixpolicyd):

The original code includes this part:

    if (lc(($ldap->get_values("zimbraDomainType"))[0]) eq "alias") {
      my $robject = ($ldap->get_values("zimbraMailCatchAllForwardingAddress"))[0];
      syslog $syslog_priority, "Real Domain: %s", $robject if $verbose;
      @attrs=('1.1');
      $mesg = $ldap->search_s(
                "",
                LDAP_SCOPE_SUBTREE,
                "(&(|(zimbraMailDeliveryAddress=$user"."$robject)(zimbraMailDeliveryAddress=$daddr)(zimbraMailAlias=$user".
                "$robject)(zimbraMailAlias=$daddr)(zimbraMailCatchAllAddress=$user"."$robject)(zimbraMailCatchAllAddress=$robject)".
                "(zimbraMailCatchAllAddress=$daddr))(zimbraMailStatus=enabled))",
                \@attrs,
                0,
                $result
              );
      $ent = $ldap->first_entry();
      $ldap->unbind;
      if ($ent != 0) {
        return "dunno";
      } else {
        return "reject 5.1.1 Mailbox unavailable";
      }
    } else {
      $ldap->unbind;
      return "dunno";
    }

So basically, this code only rejects mail to recipients for alias-domains, but accepts all mail to domains that are not alias-domains. I adjusted the code as follows:

    if (lc(($ldap->get_values("zimbraDomainType"))[0]) eq "alias") {
      my $robject = ($ldap->get_values("zimbraMailCatchAllForwardingAddress"))[0];
      syslog $syslog_priority, "Real Domain: %s", $robject if $verbose;
      @attrs=('1.1');
      $mesg = $ldap->search_s(
                "",
                LDAP_SCOPE_SUBTREE,
                "(&(|(zimbraMailDeliveryAddress=$user"."$robject)(zimbraMailDeliveryAddress=$daddr)(zimbraMailAlias=$user".
                "$robject)(zimbraMailAlias=$daddr)(zimbraMailCatchAllAddress=$user"."$robject)(zimbraMailCatchAllAddress=$robject)".
                "(zimbraMailCatchAllAddress=$daddr))(zimbraMailStatus=enabled))",
                \@attrs,
                0,
                $result
              );
      $ent = $ldap->first_entry();
      $ldap->unbind;
      if ($ent != 0) {
        syslog $syslog_priority, "Existing Alias-Recipient";
        return "dunno";
      } else {
        syslog $syslog_priority, "Non-Existing Alias-Recipient";
        return "reject 5.1.1 Mailbox unavailable";
      }
    } else {


      @attrs=('1.1');
      $mesg = $ldap->search_s(
                "",
                LDAP_SCOPE_SUBTREE,
                "(&(|(zimbraMailDeliveryAddress=$user"."$domain)(zimbraMailDeliveryAddress=$daddr)(zimbraMailAlias=$user".
                "$domain)(zimbraMailAlias=$daddr)(zimbraMailCatchAllAddress=$user"."$domain)(zimbraMailCatchAllAddress=$domain)".
                "(zimbraMailCatchAllAddress=$daddr))(zimbraMailStatus=enabled))",
                \@attrs,
                0,
                $result
              );
      $ent = $ldap->first_entry();
      $ldap->unbind;
      if ($ent != 0) {
        syslog $syslog_priority, "Existing Recipient";
        return "dunno";
      } else {
        syslog $syslog_priority, "Non-Existing Recipient";
        return "reject 5.1.1 Mailbox unavailable";
      }
    }

 

Then I restarted postfix as a zimbra user with:

zmmtactl stop
zmmtactl start

And finally, the policy daemon rejected mails to unexisting recipients 🙂

/opt/zimbra/libexec/zmpostfixpolicyd[5799]: Recipient Domain: example.com
/opt/zimbra/libexec/zmpostfixpolicyd[5799]: Recipient userid: does-not-exist
/opt/zimbra/libexec/zmpostfixpolicyd[5799]: Non-Existing Recipient
/opt/zimbra/libexec/zmpostfixpolicyd[5799]: Action: reject 5.1.1 Mailbox unavailable
postfix/smtpd[5791]: NOQUEUE: reject: RCPT from [...]: 554 5.1.1 <does-not-exist@example.com>: Recipient address rejected: Mailbox unavailable; [...]

Please use the described solution with care. It is my workaround, it is working on the system in question. It might not work with another setup. If you use it, test thoroughly if only the correct mails get rejected.

A Zimbra update will overwrite the zmpostfixpolicyd code and thus revert the change, so you might need to readjust the code after an update.

This drove me nuts and I hope this information is of help for somebody. Please comment if you have a better solution or find the described solution useful.

Recommendation

Try my Open Source PHP visitor analytics script CrazyStat.

13. August 2014

Icinga: Monitor refused mails in postfix mailqueue

Filed under: Linux,Server Administration — Tags: , , , , , , , , , , — Christopher Kramer @ 12:09

In case your server gets listed on blacklists, mails will get refused by destination servers and stick in the deferred mail queue for some time until the sender finally gets a mailer daemon.

As it takes some time until the sender gets the mailer daemon and informs the server admin, it would be better if you could directly get notified by Icinga/Nagios when a mail is in the deferred queue because the destination server refused it.

Therefore I wrote a small shell script which I want to share with you here. I am assuming Debian Wheezy with Icinga and a postfix mailserver.

Create the shell script with the actual plugin in

/usr/lib/nagios/plugins/check_mailq_blacklist :
#!/bin/sh
# detects if mails in mail queue were refused by destination server (because of blacklist?)
# From http://blog.christosoft.de/2014/08/icinga-monitor-refused-mails-postfix-mailqueue/
# Version: 2015-04-27

if mailq | -qP "(refused to talk to me)|(unsolicited mail originating from your IP)"
then
  mails=`mailq | grep -oP "(refused to talk to me)|(unsolicited mail originating from your IP)" | wc -l`
  echo "$mails mail(s) were refused, check mailq!"
  if [ "$mails" -le 10 ]; then
    # 10 or less mails -> warning
    return 1;
  else
    # more than 10 mails -> critical
    return 2;
  fi
  return 1;
else
  echo "Ok, there seems to be no refused mail in the mailq"
  exit 0;
fi

This will check for the texts “refused to talk to me” and “unsolicited mail originating from your IP” in the mailq output. These are the most common errors you get when the destination server has your server’s IP blacklisted.  In case at least one mail was refused, this causes a warning state in icinga. If more than 10 mails were refused, it causes a critical state.

Now you need to make this script executable:

chmod +x /usr/lib/nagios/plugins/check_mailq_blacklist

Now create the config file for the plugin in

/etc/nagios-plugins/config/mailq_blacklist.cfg :
# 'check_mailq_blacklist' command definition
define command{
        command_name    check_mailq_blacklist
        command_line    /usr/lib/nagios/plugins/check_mailq_blacklist
}

So now we have the command and need to define a service that uses it. Let’s say we use this locally for localhost. In

/etc/icinga/objects/localhost_icinga.cfg

add:

define service{
        use                             generic-service
        host_name                       localhost
        service_description             Mail Queue Refused Mail
        check_command                   check_mailq_blacklist
        }

This is it, just restart icinga and you are done:

service icinga restart

I hope this is of use to somebody.

Of course it is also useful to monitor in Icinga, if you are on some of the most used blacklists. A script to do this can be found here.