Skip to main content

cbpolicyd Greylisting on Zimbra

Greylisting refuses mail from a sender it has not seen before, temporarily, and waits to see whether they come back. A real mail server queues the message and retries a few minutes later. A lot of spam software does not, because retrying costs time and state that a throwaway sender does not want to spend.

It is the most effective and the most disruptive thing in cbpolicyd, in roughly equal measure.

Part of the series that starts with cbpolicyd on Zimbra: An Introduction.

Enable it

zmprov ms $(zmhostname) zimbraCBPolicydGreylistingEnabled TRUE
zmmtactl restart

There is also a training mode, which records what greylisting would have done without actually deferring anything:

zmprov ms $(zmhostname) zimbraCBPolicydGreylistingTrainingEnabled TRUE

Run in training mode first. It costs you nothing and tells you how much of your real mail would have been delayed.

How the triplet works

Greylisting tracks a triplet: the client address, the sender and the recipient. The first time cbpolicyd sees a given combination it returns a 4xx and records it. If the same triplet comes back after GreylistPeriod seconds, it is accepted and remembered.

greylisting_tracking holds those triplets, with FirstSeen, LastUpdate, Tries and Count.

The settings

The greylisting table has more columns than any other module, and NULL in any of them means inherit rather than off.

ColumnWhat it does
UseGreylistingActually greylist, 1 or 0
GreylistPeriodSeconds a new triplet must wait before being accepted
TrackWhat to key on, normally SenderIP:/24
GreylistAuthValidityHow long a proven triplet stays proven
GreylistUnAuthValidityThe same for triplets that never came back
UseAutoWhitelistPromote well-behaved servers automatically
AutoWhitelistPeriodHow far back to look when deciding
AutoWhitelistCountHow many good triplets before whitelisting
AutoWhitelistPercentageWhat share of them must have been good
UseAutoBlacklistThe same machinery in reverse

Track should be SenderIP:/24, not a single address. Large senders deliver from a pool of machines, so the retry frequently arrives from a different IP than the first attempt. Keyed on the exact address, that looks like a brand new triplet and the mail is deferred again, and again. Masking to a /24 treats the pool as one sender.

A working configuration

INSERT INTO policies (Name, Priority, Description, Disabled)
VALUES ('Greylisting inbound', 25, 'Greylist unauthenticated inbound mail', 0);

INSERT INTO policy_members (PolicyID, Source, Destination, Disabled)
VALUES ((SELECT ID FROM policies WHERE Name = 'Greylisting inbound'),
'$-', -- unauthenticated sessions only
'%internal_domains', -- to our own domains
0);

INSERT INTO greylisting
(PolicyID, Name, UseGreylisting, GreylistPeriod, Track,
GreylistAuthValidity, GreylistUnAuthValidity,
UseAutoWhitelist, AutoWhitelistPeriod, AutoWhitelistCount, AutoWhitelistPercentage,
Disabled)
VALUES
((SELECT ID FROM policies WHERE Name = 'Greylisting inbound'),
'Inbound greylisting',
1,
240, -- 4 minutes before a retry is accepted
'SenderIP:/24',
2592000, -- proven triplets stay proven for 30 days
14400, -- unproven ones expire after 4 hours
1, -- auto-whitelist good senders
604800, -- looking back over a week
50, -- after 50 triplets
80, -- of which 80% behaved
0);

The $- in Source is the important part. It restricts this to unauthenticated sessions, so your own users submitting mail are never greylisted. Greylisting an authenticated user means their client shows a send failure, which is unacceptable.

Whitelisting senders that retry badly

Some large senders retry from unpredictable networks or with long backoffs. Put them in greylisting_whitelist, which takes a CIDR or a reversed hostname pattern:

INSERT INTO greylisting_whitelist (Source, Comment, Disabled)
VALUES ('209.85.128.0/17', 'Google', 0);

INSERT INTO greylisting_whitelist (Source, Comment, Disabled)
VALUES ('40.92.0.0/15', 'Microsoft', 0);

Maintaining that list by hand is a real ongoing cost, and it is the main reason people eventually turn greylisting off. The auto-whitelist settings above exist to reduce it, by promoting servers that have proven themselves.

Watching it work

-- triplets currently waiting
SELECT TrackKey, Sender, Recipient,
datetime(FirstSeen, 'unixepoch') AS FirstSeen,
datetime(LastUpdate, 'unixepoch') AS LastUpdate,
Tries, Count
FROM greylisting_tracking
ORDER BY LastUpdate DESC LIMIT 20;

-- servers promoted automatically
SELECT TrackKey, datetime(Added, 'unixepoch') AS Added,
datetime(LastSeen, 'unixepoch') AS LastSeen
FROM greylisting_autowhitelist ORDER BY LastSeen DESC LIMIT 20;

A healthy system shows triplets with Count climbing and a steadily growing autowhitelist. Triplets with high Tries and Count at zero are senders being deferred repeatedly and never getting through, which is what you are looking for when someone reports missing mail.

The trade-offs, honestly

Every new correspondent is delayed. The first message from anyone you have never heard from arrives minutes late, and that includes password resets, one-time codes, order confirmations and the message from the person you are about to interview. Users notice this, and they report it as the mail server being broken.

Some senders never retry. A poorly written notification system or a transactional provider with no queue will simply drop the message. You will not find out; the sender will not tell you. This is the risk that matters, and it falls on exactly the automated mail people most rely on.

It is less effective than it was. Greylisting worked spectacularly when spam came from simple software. Modern spam largely comes through compromised accounts and real mail servers that retry correctly, and passes straight through.

Zimbra already filters. Amavis and SpamAssassin run on every message. Greylisting adds a layer in front of them that costs latency rather than CPU, and the question is whether your remaining spam problem justifies it.

Given all of that, greylisting is worth trying when spam is getting through despite filtering, and worth skipping when it is not. Run training mode for a fortnight, look at what it would have delayed, and decide with data.

To turn it off again:

zmprov ms $(zmhostname) zimbraCBPolicydGreylistingEnabled FALSE
zmmtactl restart

Next in this series

cbpolicyd SPF Checking, which decides on sender authorisation during the session rather than after delivery.