cbpolicyd Access Control on Zimbra
Access control is the simplest module in cbpolicyd and has the smallest table: a policy, a verdict and some text. Everything interesting happens in the policy that selects which transactions it sees, which is why it is worth reading the policy model before using it.
Part of the series that starts with cbpolicyd on Zimbra: An Introduction.
Enable it
Unlike quotas, this module is off by default:
zmprov ms $(zmhostname) zimbraCBPolicydAccessControlEnabled TRUE
zmmtactl restart
The table
CREATE TABLE access_control (
ID ...,
PolicyID ..., -- which policy this belongs to
Name ...,
Verdict ..., -- OK, REJECT, DEFER, HOLD, DISCARD, ...
Data ..., -- text returned with the verdict
Comment ...,
Disabled ...
);
That is the whole module. There are no conditions inside it, because the conditions are the policy's job.
The verdicts
| Verdict | Effect |
|---|---|
OK | Accept immediately, stop evaluating further restrictions |
REJECT | 5xx, permanent refusal during the session |
DEFER | 4xx, the sender retries later |
HOLD | Accepted into Postfix's hold queue instead of being delivered |
DISCARD | Accepted and silently dropped |
REDIRECT | Delivered to a different address instead |
DUNNO | No opinion, let the remaining restrictions decide |
DUNNO is the default when nothing matches, and it is what makes the module safe to layer:
a policy that does not apply simply says nothing rather than accepting or refusing.
Use case: block a domain outright
INSERT INTO policies (Name, Priority, Description, Disabled)
VALUES ('Blocked senders', 5, 'Domains and addresses we refuse', 0);
INSERT INTO policy_members (PolicyID, Source, Destination, Disabled)
VALUES ((SELECT ID FROM policies WHERE Name = 'Blocked senders'),
'@spam.example', 'any', 0);
INSERT INTO access_control (PolicyID, Name, Verdict, Data, Disabled)
VALUES ((SELECT ID FROM policies WHERE Name = 'Blocked senders'),
'Refuse blocked domains',
'REJECT',
'Mail from this domain is not accepted.',
0);
Priority 5 puts this ahead of the stock direction policies at 10 and 20, so the refusal
is decided before anything else weighs in. Add more senders by adding policy_members
rows to the same policy rather than creating a policy each time.
Use case: stop one person mailing one person
Harassment and dispute cases usually need a narrow block rather than a domain-wide one, and
Destination is what makes that possible:
INSERT INTO policies (Name, Priority, Description, Disabled)
VALUES ('Targeted blocks', 4, 'Specific sender to specific recipient', 0);
INSERT INTO policy_members (PolicyID, Source, Destination, Disabled)
VALUES ((SELECT ID FROM policies WHERE Name = 'Targeted blocks'),
'someone@external.example',
'victim@yourdomain.com', 0);
INSERT INTO access_control (PolicyID, Name, Verdict, Data, Disabled)
VALUES ((SELECT ID FROM policies WHERE Name = 'Targeted blocks'),
'Blocked pairing', 'REJECT',
'Delivery to this recipient is not permitted.', 0);
Consider DISCARD instead of REJECT here. A rejection tells the sender they have been
blocked, which in a harassment case often prompts them to find another route. A discard
tells them nothing. Weigh that against the fact that discarded mail is gone with no record,
and that you are silently accepting responsibility for a message you then destroy.
Use case: quarantine instead of refusing
HOLD puts the message in Postfix's hold queue, where it sits untouched until you decide:
INSERT INTO access_control (PolicyID, Name, Verdict, Data, Disabled)
VALUES ((SELECT ID FROM policies WHERE Name = 'Blocked senders'),
'Hold for review', 'HOLD', 'Held for administrator review', 0);
Inspect and release with the normal Postfix tools:
mailq # held messages show with a ! marker
postsuper -H ALL # release everything back into the queue
postsuper -d <queue-id> # delete one
This is a good way to trial a block before committing to it. Run it as HOLD for a week,
look at what accumulated, then switch the verdict to REJECT once you trust it.
Use case: an allow rule in front of a block
Because every matching policy contributes in priority order, an OK at a lower number acts
as an exemption from a broader refusal below it:
INSERT INTO policies (Name, Priority, Description, Disabled)
VALUES ('Always allow', 1, 'Partners that must never be blocked', 0);
INSERT INTO policy_members (PolicyID, Source, Destination, Disabled)
VALUES ((SELECT ID FROM policies WHERE Name = 'Always allow'),
'@partner.example', 'any', 0);
INSERT INTO access_control (PolicyID, Name, Verdict, Data, Disabled)
VALUES ((SELECT ID FROM policies WHERE Name = 'Always allow'),
'Allow partner', 'OK', 'Whitelisted partner', 0);
OK tells Postfix to accept and stop processing further restrictions, so use it
deliberately. It bypasses the checks that would have run afterwards, including ones you may
want to keep, such as recipient validation.
When a Postfix access map is the better tool
Be honest about this one. For a static list of blocked senders, Postfix's own
check_sender_access map is simpler, faster, needs no database and is maintained with a
text file:
zmprov mcf +zimbraMtaRestriction \
"check_sender_access lmdb:/opt/zimbra/conf/postfix_reject_sender"
cbpolicyd access control earns its place when the decision depends on something a flat map cannot express: the combination of sender and recipient, the client network, whether the session was authenticated, or a rule that should only apply to one direction of mail. If your rule is just a list of bad senders, use the map.
MSH Zimbra Rules covers the same ground with a managed interface and per-domain delegation, which matters when the people maintaining the list are not the people with shell access.
Checking what you built
SELECT p.Priority, p.Name AS Policy, m.Source, m.Destination,
a.Verdict, a.Data
FROM access_control a
JOIN policies p ON p.ID = a.PolicyID
LEFT JOIN policy_members m ON m.PolicyID = p.ID
WHERE a.Disabled = 0
ORDER BY p.Priority;
Reading that ordered by priority is the quickest way to spot the usual mistake: a broad
REJECT at a lower number than the OK that was supposed to exempt something from it.
zmcbpolicydctl restart
Next in this series
cbpolicyd Greylisting, which refuses mail temporarily on purpose and is far more effective than it sounds.