# A case study over LDAP, Postfix and MtPolicyd ## Abstract With MtPolicyd you can use LDAP to profile your accounts with policies. For instance, for each **account** you can set a number of maximum message rate (for single message, or message x recipient), or a size rate too. We see how to implement these policies per account with a working example. Suitable for a large environment. ## Requisite Many email service implementations adopt LDAP as a DB to profile user preferences, SMTP routing information and authentication. You should have an LDAP server (ldap.example.com) with email account like this: ``` dn: uid=account@example.com,[base dn] objectClass: top objectClass: person objectClass: organizationalPerson objectClass: inetOrgPerson objectClass: mailRecipient objectClass: inetMailUser mailAlternateAddress: alias@example.com mail: account@example.com mailDeliveryOption: mailbox uid: account@example.com userPassword: mypassword cn: Account name mailUserStatus: active sn: Account mailHost: imapserver.example.com ``` In this example the uid is the `sasl_username` used by Postfix to authenticate and authorize the account to send mail. You can build this authentication process using saslauthd over LDAP mechanism, for instance. Here we don't explain how to implement authentication, other SMTP routing mechanism or email aliases over LDAP. Anyway, let suppose the above entry is a working LDAP account used for SMTP authentication. You can imagine other policies for _client_address_ or other keys too, using different Postfix _context_. This is not the scope of this document. Postfix, Mtpolicyd and the LDAP server could stay on different hosts. All of them can interface each other through TCP sockets. For instance you can install * MtPolicyd on mtpolicyd.example.com * Postfix on postfix.example.com * Directory Server on ldap.example.com ## Configure As you can see in [Plugin Accounting](http://search.cpan.org/~benning/Mail-MtPolicyd-1.16/lib/Mail/MtPolicyd/Plugin/Accounting.pm), we have four counters for each key. Our key will be `sasl_username`, because we want policies per account. So we first have to declare a schema for the LDAP server. Mtpolicyd doesn't provide an official schema. Here you can find a schema useful for the result we want achieve in this case. The schema works with Red Hat/Fedora Directory Server, but with little adjustment probably can work with OpenLDAP or other Directory Servers which support custom, **unofficial OIDs**. This unofficial schema provides the attributes for the four counters: * mtpolicydMailMessageLimit * mtpolicydMailRecipientLimit * mtpolicydMailSizeLimit * mtpolicydMailSizeRecipientLimit These attributes comes with the objectClass * mtpolicyd which extend the objectclass "mailRecipient". This choice is not mandatory, you can change it if you don't like it. Once you have extended the schema, our LDAP entry can be profiled for MtPolicyd. For instance we can choose to limit the account "account@example.com" to send a maximum of 100 mails per time unit. To achieve this the entry is: ``` dn: uid=account@example.com,[base dn] mtpolicydMailMessageLimit: 100 objectClass: top objectClass: person objectClass: organizationalPerson objectClass: inetOrgPerson objectClass: mailRecipient objectClass: inetMailUser objectClass: mtpolicyd mailAlternateAddress: alias@example.com mail: account@example.com mailDeliveryOption: mailbox uid: account@example.com userPassword: mypassword cn: Account name mailUserStatus: active sn: Account mailHost: imapserver.example.com ``` We could also set the `mtpolicydMailRecipientLimit` attribute and configure MtPolicyd to refuse the mails if at least one counter triggers the threshold defined in the LDAP attribute. So, here is the complete MtPolicyd virtual host: ``` vhost_by_policy_context=1 name="accounting" module="LdapUserConfig" basedn="[base dn]" # sasl_username attribute is uid. filter_field="sasl_username" filter="(&(uid=%s)(objectClass=mailRecipient)(objectclass=mtpolicyd)(mailUserStatus=active))" # copy these fields to current mtpolicyd session config_fields="mtpolicydMailMessageLimit,mtpolicydMailRecipientLimit" module = "Quota" time_pattern = "%Y-%m-%d" field = "sasl_username" metric = "count" threshold = 500 # if this field is set it will overwrite the default threshold uc_threshold = "mtpolicydMailMessageLimit" # for MSA you may reject, for MTAs you may defer action = "reject you exceeded your daily message limit" module = "Quota" time_pattern = "%Y-%m-%d" field = "sasl_username" metric = "count_rcpt" threshold = 5000 # if this field is set it will overwrite the default threshold uc_threshold = "mtpolicydMailRecipientLimit" # for MSA you may reject, for MTAs you may defer action = "reject you exceeded your daily mail recipient limit" module = "Accounting" fields = "sasl_username" # Perform day based limit time_pattern = "%Y-%m-%d" ``` To understand how it works, we strongly suggest to read the [How to Accounting Quota CookBook](https://metacpan.org/pod/release/BENNING/Mail-MtPolicyd-2.03/lib/Mail/MtPolicyd/Cookbook/HowtoAccountingQuota.pod). In this example the rate time unit is _day_, but you can configure hours or other just setting the proper `time_pattern`. ### Postfix interface This is very simple. The main.cf of the Postfix server can be configured with ``` smtpd_end_of_data_restrictions = check_policy_service { inet:mtpolicyd.example.com:12345, policy_context=accounting } ``` ### LDAP connection This is an example for the LDAP connection: ``` module = "Ldap" host = "ldap.example.com" port = 389 timeout = 20 binddn = "uid=mtpolicyd,ou=admins,[base dn]" password = "mtpolicyd" starttls = 0 ``` Don't worry if connections between MtPolicyd and LDAP server die. MtPolicyd checks if the connection is alive. If the connection dies, MtPolicyd tries to renegotiate it. This behavior has tested with load balancer and LDAP server which expires idle sessions. The user _mtpolicyd_ can be: ``` dn: uid=mtpolicyd,ou=admins,[base dn] uid: mtpolicyd givenName: Mail Team objectClass: top objectClass: person objectClass: organizationalPerson objectClass: inetorgperson sn: Policyd cn: Mail Team Policyd userPassword: mtpolicyd ``` Remember to set a Password Policy which doesn't expire the password of the user mtpolicyd. On [base dn], or where mail accounts stay, you can set an aci: ``` aci: (targetattr = "objectClass || mtpolicydMailSizeLimit || uid || mtpolicydM ailSizeRecipientLimit || mtpolicydMailMessageLimit || mailUserStatus || mtpol icydMailRecipientLimit") (target = "ldap:///[base dn]") (targetfilter = objectclass=mtpolicyd) (version 3.0;acl "Allow MtPolicyd access ";allow (read,compare,search)(userdn = "ldap:///uid=mtpolicyd,ou=admins,[base dn]");) ``` This aci limits what user mtpolicyd can perform over LDAP data. But you can imagine more complex situations, where an aci time-defined can enforce a policy only during a specific time interval, such as night hours or weekend. ## The complete example * [LDAP schema](97mtpolicyd.ldif) * [a complete mtpolicyd.conf example](mtpolicyd.conf)