Table of Contents

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 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

postconf -v 2>/dev/null|grep  '^mail_version'


postconf -m | grep tcp

should output tcp if your system has support for dict_tcp.

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

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

tcp /usr/lib/postfix/ dict_tcp_open  

As we're trying to use LMTP transport, postfix mustn't do that in a chroot (it won't find citadels lmtp socket there), so your /etc/postfix/ should contain a line like that:

lmtp      unix  -       -       n       -       -       lmtp

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

Take the from the last box(or add the matching lines to your config), replace 777 with your port and with your hostname. Make Postfix reload its 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:30 [postfix/master] terminating on signal 15 
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 its working

sending Mail to an existing user should look like that:

Aug 29 00:34:41 [postfix/smtpd] connect from[testmta ip]
Aug 29 00:34:41 [postfix/smtpd] E69F29ED1:[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] 


# telnet 777
Connected to
Escape character is '^]'.
Answer: 200 OK
Answer: 500 REJECT noone here by that name.
sendcommand IGAB

Here's the sample

to follow. Remember to substitute “777” with whatever port number you are using.


# See /usr/share/postfix/ 
# 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 = 
myorigin =
# Debian puts this by default to:
# myorigin = /etc/mailname
mydestination =, 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 =
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 heres howto do this. Adding RMilter as the quickstart guide sugests:

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 spamc, 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.