Postfix - Exchange Server Mailrelay

A Postfix mailrelay that protects an Exchange Server

Patrick Ben Koetter

Ralf Hildebrandt

Revision History
Revision 0.3010 May 2003PK
Added example and notes for the transport map and translated german notes in the Makefile to english.
Revision 0.2007 May 2003RH
Added config for main.cf
Revision 0.1016 Jan 2003PK
Initial Release

Table of Contents

What should be protected?
Connections
Performance aka recipient lookups
MAPS built from LDAP queries
How to protect the server?
Firewall
Incoming connections
Outgoing connections
Exchange Server
Export LDAP query to file
Check output
Copy file to mailrelay
Build proto MAP
Configure Postfix
Commit changes

Junk mail is war. RFCs do not apply.

--Wietse Venema

Abstract

The Microsoft Exchange server is without any doubt a very powerful groupware server. It's security record and it's stability, when it comes to attacks, are not that famous. That's why many postmasters want to combine the groupware functionality with the security and stability of UNIX smtp gateways.

This is a little sketch to give you an idea how this can be done, using Postfix - an alternative to the widely-used Sendmail program.

What should be protected?

Connections

First and foremost the idea is to keep the Exchange Server from connecting directly to other servers and vice versa have other clients that come from the Internet connect directly to the Exchange server. This can easily be done by following man transport (5). You need to configure Postfix to accept mails and transport them to the final destination - your Exchange server.

Performance aka recipient lookups

In our scenario the Exchange server is the heart of our communication infrastructure and we want to keep it beating constantly without any disturbance. Part of running a service smoothly is making sure that anything that could disturb it is blocked as soon as possible.

If you run Postfix as a simple mailrelay it will have no notion of the users the Exchange Server holds. This means that by default Postfix will accept messages for its destination and transport those to the Exchange server. The Exchange server will accept the message, and notice later that the recipient doesn't even exist. In that case the mail will be bounced.

If it wasn't for SPAM this would be an OK approach. But SPAM is all over the place and some spammers try to get their message delivered using dictionary attacks. A dictionary attack equals a brute force attack. To withstand brute force you must be able to decide what's right and what's wrong very fast and you need lots of performance, stability (and a few nasty tricks).

[Note]Note

In a dictionary attack the attacker tries any username/alias it can think of in combination with the domainname the SMTP server is registered for, in order to get messages delivered to any user known by the mailserver. Thousands of messages are sent to the final destination every minute, challenging the mailserver to examine every message and make a decision on the recipients validity.

  • If the machine is configured to bounce mail to unknown local users, it will run into trouble due to the large number of messages that need to be bounced.

  • If the machine is configured to deliver mail destined for unknown user to e.g. postmaster, then postmaster's mailbox will be filled until the disk is full.

MAPS built from LDAP queries

The right thing™ to do, is to build maps that were built from files who were built from LDAP queries run against the Exchanges' ADS. Huh?

[Warning]Warning

Probably the first impulse will be to have Postfix do LDAP lookups on demand by connecting to the Exchange Server through LDAP and query for valid recipients. This is not recommended, because in critical situations e.g. during a dictionary attack there will be thousands of queries a minute, distracting the Exchange server from its primary job being a groupware server. There will be so many queries that the Exchange server will slow down so badly that the dictionary attack will result in a DoS to your groupware server.

The Exchange server can be queried for valid recipients using LDAP. However in a big Exchange Server installation there is a default limit that might permit a query from getting all valid recipients; a regular query is allowed to return at most 1.000 results. After that the server will not answer and further requests by the source that seeks recipients.

The solution is to use Microsoft's own scripting language and write a Visual Basic script that will tell the LDAP server not to give all results at once, but to hand them out in pages. The following steps sketch a way how to query an Exchange Server for valid recipients, copy them to the Postfix mailrelay and have Postfix use them to reject unknown recipients before they reach the Exchange Server.

How to protect the server?

Firewall

Put the Exchange server into your LAN and keep it hidden from the internet by a firewall. All you need to allow is:

Incoming connections

  • Allow incoming connections on Port 25 from your mailrelay to the exchange server

Outgoing connections

  • Allow outgoing connections on Port 25 from your exchange server to the mailrelay only

  • Allow outgoing connections from on Port 22 from your exchange server to the mailrelay

Exchange Server

Export LDAP query to file

The idea is to have a script query an Exchange server for proxyAddresses which is the correct attribute when you are looking for valid recipients.

Edit the script and change Set Container=GetObject("LDAP://CN=Users,DC=office,DC=example,DC=com") to fit your LDAP structure.

Example 1. export_recipients.vbs

Download export_recipients.vbs.

' Export all valid recipients (= proxyAddresses) into a
' file virtual.txt
'
' Ferdinand Hoffmann & Patrick Koetter
' 20021100901
' Shamelessly stolen from 
' http://www.microsoft.com/windows2000/techinfo/ \
' planning/activedirectory/bulksteps.asp


'Global variables
Dim Container
Dim OutPutFile
Dim FileSystem

'Initialize global variables
Set FileSystem = WScript.CreateObject("Scripting.FileSystemObject")
Set OutPutFile = FileSystem.CreateTextFile("virtual.txt", True)
Set Container=GetObject("LDAP://CN=Users,DC=office,DC=example,DC=com")

'Enumerate Container
EnumerateUsers Container

'Clean up
OutPutFile.Close
Set FileSystem = Nothing
Set Container = Nothing

'Say Finished when your done
WScript.Echo "Finished"
WScript.Quit(0)

'List all Users
Sub EnumerateUsers(Cont)
Dim User

'Go through all Users and select them
For Each User In Cont
Select Case LCase(User.Class)

'If you find Users
Case "user"
  'Select all proxyAddresses
  Dim Alias
  If Not IsEmpty(User.proxyAddresses) Then
    For Each Alias in User.proxyAddresses
    OutPutFile.WriteLine "alias: " & Alias
    'WScript.Echo Alias
  Next
  End If

Case "organizationalunit" , "container"
  EnumerateUsers User

End Select
Next
End Sub

Check output

Run the script and check the output. It should give you something like this:

alias: SMTP:newuser3@office.example.com 1
alias: SMTP:newuser@office.example.com
alias: SMTP:Administrator@office.example.com
alias: X400:c=us;a= ;p=Example Organiza;o=Exchange;s=Administrator;
alias: smtp:postmaster@office.example.com 2
alias: X400:c=us;a= ;p=Example Organiza;o=Exchange;s=Doe;g=Jon;
alias: SMTP:testuser@example.com
alias: SMTP:postfix@office.example.com
alias: X400:c=us;a= ;p=Example Organiza;o=Exchange;s=postfix;
1

SMTP is a valid recipient and it is a users mail address that any outgoing message will be rewritten to

2

smtp as an alias and a valid recipient for a user.

Copy file to mailrelay

Install and configure any secure copy mechanism, e.g. PuTTY to scp virtual.txt to the Postfix machine using key files for unattended copying. You can also use rsync over ssh from the cygwin package.

Use the Windows Scheduler to run export_recipients.vbs and scp as often as needed.

Build proto MAP

Parse the virtual.txt file for smtp and SMTP entries and write them into a Postfix map format to relay_recipients.proto. Then run postmap /path/to/relay_recipients.proto to make it a Postfix readable DB. We use this Makefile which can be invoked from cron using: cd /etc/postfix && make

Example 2. Makefile

Download Makefile

DB=db
all:    relay_recipients.$(DB)
# "all" means to build virtual.db

relay_recipients.proto:       virtual.txt
    awk -F: '/alias: (SMTP|smtp):/ {printf("%s\tOK\n",$$3)}' virtual.txt > relay_recipients.proto
# We need virtual.txt to build relay_recipients.proto
# awk will use ":" as field separator and for each line
# that contains "alias: (SMTP|smtp):" it will do:
# print the third row, insert a TAB, insert "OK" and add a newline
# into relay_recipients.proto

%.$(DB):             %.proto
    /usr/sbin/postmap $*.proto && mv $*.proto.$(DB) $*.$(DB)
# Building a *.db requires a *.proto file. If that exists,
# postmap is called to build the map from *.proto. If postmap is successful
# the *.proto map will be renamed to *.db

Configure Postfix

Once the makefile has built the recipient map we need to configure Postfix to make use of the map. We must also tell Postfix where to transport messages to as soon as they have passed the recipient test. In /etc/postfix/main.cf we add these parameters:

Example 3. /etc/postfix/main.cf

relay_domains = office.example.com, example.com
relay_recipient_maps = hash:/etc/postfix/relay_recipients
transport_maps = hash:/etc/postfix/transport

The parameter transport_maps in main.cf points to the location of the transport map. The map contains entries that tell Postfix how and where to transport messages for a given recipient domain to.

Example 4. /etc/postfix/transport

domain    smtp:[ho.st.na.me] 1
domain    smtp:[ip.ad.dr.es] 2
1

Hostnamens must be fully quallified domain names resolvable by Postfix.

[Note]Note

You may not be able to configure your DNS server to provide a separate MX entry for the host that messages should be transported to. In that case you simply put the hostname in square brackets and Postfix will not use the default MX a DNS-query would return.

2

Use an IP-Address, if Postfix cannot not resolve the hostname of the exchange server. Note that you must put the IP-Adress in square brackets.

Commit changes

Finally make Postfix reload it's configuration and it's maps by executing postfix reload.