Home Download FAQ / Knowledge Base Screenshots Documentation Support

Configuring Postfix to validate email addresses against a Citadel server

There are situations in which you may wish to run Postfix as a front end MTA, and place your Citadel server behind it as the final MDA for your domains. For example, you might run Postfix on a dedicated relay hub outside your firewall, and Citadel inside your firewall, and only allow SMTP between those two machines through the firewall. Or you might have some great new mail filtering plugin that only runs on Postfix.

So far you would have run into a major drawback: how would Postfix know, whether it should deliver a mail, or reject it due to being sent to a non existant user? These days, spammers tend to perform dictionary attacks, and use spoofed sender addresses. If Postfix accepts the mail, and then Citadel rejects it, Postfix will try to notify the sender that the message could not be delivered. In most cases, the sender is spoofed or nonexistent, which results in an Aide folder full of "double bounce" messages.

The combination of Postfix and Citadel offers an elegant solution to this problem. Postfix supports non local (unix users) delivery tables. You can do user lookup for example in a MySQL table, or Postgres, dbm, ldap and, since later versions of postfix (2.2 and later, earlier in patches, for example Debian sarge; search for dict_tcp.so) via a new type of TCP lookup. If you don't know how to find out the version of your postfix with your package management, try

  # Display the version of Postfix that is installed:
  postconf -v 2>/dev/null|grep  '^mail_version'

  # Output "tcp" if your system has support for dict_tcp:
  postconf -m | grep tcp

Citadel now offers support for the TCP lookup service. To configure it, follow these steps:

Now for Postfix. Check if /etc/postfix/dynamicmaps.cf contains something like:

  tcp /usr/lib/postfix/dict_tcp.so dict_tcp_open  

As we're trying to use LMTP transport, postfix must not do that in a chroot (it won't find Citadel's LMTP socket there), so your /etc/postfix/master.cf should contain a line like this:

  lmtp      unix  -       -       n       -       -       lmtp

note that the "n" in the 5th column is essential!

Take the main.cf from the last box (or add the matching lines to your config), replace 777 with your port and sample.yourcitadel.org with your hostname. Reload Postfix config using postfix reload as root on your shell. Use tail -f /var/log/mail or tail -f /var/log/mail/current depending on your syslog facility to check the effort:

Aug 27 23:29:00 [postfix/postfix-script] 
    refreshing the Postfix mail system 
Aug 27 23:29:00 [postfix/master] 
    reload configuration  

or if you do postfix stop; postfix start :

Aug 28 22:32:30 [postfix/postfix-script] 
    stopping the Postfix mail system
Aug 28 22:32:33 [postfix/postfix-script] 
    starting the Postfix mail system
Aug 28 22:32:33 [postfix/master] 
    daemon started -- version 2.1.5  

Use another mail account to verify that it's working

Sending mail to an existing user should look like this:

Aug 29 00:34:41 [postfix/smtpd] connect from 
    yourtestmta.org[testmta ip]

Aug 29 00:34:41 [postfix/smtpd] E69F29ED1: 
    client=yourtestmta.org[testmta ip]

Aug 29 00:34:41 [postfix/cleanup] E69F29ED1: message-id= 

if you view citadel logging:

Aug 29 00:34:32 [citadel] : get 

Aug 29 00:34:32 [citadel] sending 200 OK 

and the usual delivery and sorting afterwards.

now the non match case:

(lines wrapped are indended)

Aug 29 00:33:40 [postfix/smtpd] connect from 
    unknown[testmta ip]

Aug 29 00:33:41 [postfix/smtpd] NOQUEUE: reject: 
    RCPT from unknown[testmta ip]: 
    550 : 
    Recipient address rejected: 
    User unknown in local recipient table;
    to= proto=SMTP

Aug 29 00:33:41 [postfix/smtpd] 
    disconnect from unknown[testmta ip] 


Postfix: If you didn't get the above lines and you've got trouble with it maybe you want to enable debugging for the host you're testing with and Increase the postfix logging verbosity. Also starting the citadel server from the commandline may give you more useful output.

Citadel: If you're suspecting citadel not to do the right thing, you can try to talk to its dict_tcp port by hand -- it is quite simple. Let's go with the names from above:

# telnet 777
Connected to
Escape character is '^]'.
get existinguser@sample.citadel.org
Answer: 200 OK existinguser@sample.citadel.org
get NonexistentUser@sample.citadel.org
Answer: 500 REJECT noone here by that name.

Citadel: force Citadel Server to rebuild its user database (in case you suspect that aliases you added aren't working; this may happen if you add new domains)

  sendcommand IGAB

Here's the sample main.cf to follow. Remember to substitute "777" with whatever port number you are using.

Sample main.cf

# See /usr/share/postfix/main.cf.dist 
# for a commented, more complete version 
# you should use a greeting apropriate to your distro:
smtpd_banner = $myhostname ESMTP $mail_name (Debian/GNU) 
biff = no 

# appending .domain is the MUA's job. 
append_dot_mydomain = no 

# Uncomment the next line to generate "delayed mail" warnings 
#delay_warning_time = 4h

myhostname = sample.citadel.org 
myorigin = sample.citadel.org
# Debian puts this by default to:
# myorigin = /etc/mailname
mydestination = mail.sample.citadel.org, sample.citadel.org sample.local 
#relayhost = mynetworks = 
mailbox_size_limit = 0 
recipient_delimiter = + 
inet_interfaces = all 
# ------------------------------------------------ 
# checking rules. 
# get rid of anything useless as early as possible. 
# * stage one: check if the user is there. 
# * stage two: check the source. is its helo valid? else buye. 
# * stage three: check the sender etc. 
# * stage four: check the open relay Database. 
#               hosts registered here won't be accepted. 
# * stage five: check the content by regex. 
#               won't accept Windows executables of any kind. 
# * stage six: Do virus checking. 
#               reject some more extensions. 
# * stage seven: deliver it to citadel via local transport 
# make it bite harder if wanted.
#unknown_local_recipient_reject_code = 550 
#unknown_address_reject_code = 550 
#unknown_client_reject_code = 550 
#unknown_relay_recipient_reject_code = 550 
#unknown_virtual_alias_reject_code = 550 
#unknown_virtual_mailbox_reject_code = 550 
#unknown_address_reject_code = 550 
#unknown_client_reject_code = 550 
#unknown_hostname_reject_code = 550 
#unverified_recipient_reject_code = 550 
#unverified_sender_reject_code = 550 
#unverified_recipient_reject_code = 550 
# nope. don't wanna know. 
bounce_notice_recipient =  
# replace with the ip of your citadel server, 
# and 777 with the port you made it open its dict-tcp server
# in doubt check with netstat -lnp
# telnet ip port
smtpd_recipient_restrictions =
        reject_rbl_client zen.spamhaus.org,
        reject_rbl_client bl.spamcop.net,
        reject_rbl_client dnsbl.sorbs.net,
        reject_rbl_client l2.spews.dnsbl.sorbs.net,
        reject_rhsbl_client rhsbl.sorbs.net,
        reject_rhsbl_sender rhsbl.sorbs.net,
local_recipient_maps = tcp: $alias_maps
# if we deliver to citadel via lmtp, 
# do it for example like that:
# Postfix version 2.1 and older:
#local_transport = lmtp:unix:/var/run/citadel/lmtp.socket
mailbox_transport = lmtp:unix:/var/run/citadel/lmtp.socket
# check the output of
#     netstat -lnp 
# for your lmtp.sock location.

Integrating with RSpamD

If you want to use RSpamD via a postfix milter, here's howto do this. Adding RMilter as the quickstart guide suggests:

rmilter setup
smtpd_milters = unix:/var/run/rmilter/rmilter.sock
#or: smtpd_milters=inet:localhost:9900
milter_default_action = accept
milter_protocol = 6
milter_mail_macros = i {mail_addr} {client_addr} {client_name} {auth_authen}

You've got your basic setup up of rspamd & rmilter and running and now want to have a way to feed learn spams back into rspamd. We therefore create a public room naming it SPAM_LEARN, and add as a mailinglist recipient: spam@sample.local You arrange the DNS/hosts so it will point to that place.

We don't use dspamc, so we don't have an additional dependency on our postfix instance except for curl. We use postfix pipe for a user alias 'spam' to get the spammail over into rspamd:

spam: "|curl -X POST -H 'Password:openSesame' --data-binary @- 'http://rspamdHost:11334/learnspam'

You can find more articles about configuring postfix at many places on the web.

There are no social media links here. Enjoy a friendly Citadel community instead. Or go outside.