3

I have Postfix + BitDefender FRAMS running as hygiene gateway before few Exchanges. BD FRAMS can learn it's Baesian filter by download SPAM and HAM emails from dedicated mailboxes. Because we have sustain flow of SPAM to unknown recipients, I have idea to redirect this useful source of SPAM emails directly to SPAM-learning mailbox.

My configs:

main.cf:

myhostname = posfix.example.com
smtpd_banner = $myhostname
#myorigin = example.com
mydestination =
local_recipient_maps =
#virtual_alias_maps = hash:/etc/postfix/virtual
local_transport = error:local mail delivery is disabled

mynetworks = /etc/postfix/mynetworks

smtpd_use_tls = yes
#smtpd_tls_auth_only = yes
smtpd_tls_key_file = /etc/ssl/certs/posfix.example.com.pem
smtpd_tls_cert_file = /etc/ssl/certs/posfix.example.com.pem
smtpd_tls_CApath = /etc/ssl/certs
smtpd_tls_loglevel = 1
smtpd_tls_received_header = yes
smtpd_tls_session_cache_timeout = 3600s

relay_domains = mysql:/etc/postfix/relay_domains
transport_maps = mysql:/etc/postfix/transport
relay_recipient_maps = mysql:/etc/postfix/relay_recipient_maps

show_user_unknown_table_name = no
unknown_local_recipient_reject_code = 550

mailbox_size_limit = 102400000000
message_size_limit = 102400000

smtpd_delay_reject = yes
smtpd_helo_required = yes

#smtpd_helo_restrictions =
#  permit_mynetworks
#  reject_invalid_hostname
#  permit

smtpd_restriction_classes = verify_sender
verify_sender =
  reject_unverified_sender

smtpd_sender_restrictions =
  permit_mynetworks

smtpd_recipient_restrictions =
  reject_unknown_recipient_domain
  reject_non_fqdn_recipient
  permit_mynetworks
  reject_authenticated_sender_login_mismatch
  reject_unauth_destination
  check_recipient_access hash:/etc/postfix/recipient_access
  check_sender_access hash:/etc/postfix/sender_access
  #check_helo_access pcre:/etc/postfix/helo_access
  reject_non_fqdn_sender
  reject_unknown_sender_domain
  reject_unlisted_sender
  reject_invalid_hostname
  reject_unauth_pipelining
  check_sender_mx_access cidr:/etc/postfix/sender_mx_access
  #reject_non_fqdn_hostname
  #reject_unverified_sender
  #reject_multi_recipient_bounce
  permit

#smtpd_data_restrictions =
  #reject_multi_recipient_bounce

# Added by BitDefender on Mon Nov  3 15:27:43 EET 2014
smtpd_milters=unix:/var/spool/postfix/BitDefender/bdmilterd.sock
milter_protocol = 2
milter_default_action = tempfail
milter_connect_timeout = 30s
milter_command_timeout = 30s
milter_content_timeout = 30s
# End of added lines

/etc/postfix/relay_domains:

user = postfix
password = postfix
dbname = postfix
query = SELECT name FROM relay_domains WHERE name='%s'

/etc/postfix/transport:

user = postfix
password = postfix
dbname = postfix
query = SELECT nexthop FROM relay_domains WHERE name='%s'

/etc/postfix/relay_recipient_maps:

user = postfix
password = postfix
dbname = postfix
query = SELECT email FROM relay_users WHERE email='%s'

my script for fill relay-users from AD (kindly share for you) for cron:

#!/usr/bin/python

__author__ = 'tiv'

import ldap
import MySQLdb

connections = {
    1: ['dc.example.com',  # AD domain controller
        'EXAMPLE\\user',  # AD user
        'password',  # AD user password
        'dc=example, dc=local',  # AD root DN
        'mail.example.com'],  # Exchange server

    2: ['dc.example1.com',  # AD domain controller
        'EXAMPLE1\\user',  # AD user
        'password',  # AD user password
        'dc=example1, dc=local',  # AD root DN
        'mail.example1.com'],  # Exchange server

    #3: 'dc.example2.com',  # AD domain controller
    #   'EXAMPLE2\\user',  # AD user
    #   'password',  # AD user password
    #   'dc=example2, dc=local',  # AD root DN
    #   'mail.example2.com'],  # Exchange server
}

mysql = ['localhost',  # host
         'postfix',  # user
         'postfix',  # password
         'postfix']  # schema

def main():
    try:
        emails = []
        domains = []
        for i in connections:
            connection = connections[i]
            print('Processing LDAP server ' + connection[0] + ':')
            basedn = connections[i][3]
            nexthop = connections[i][4]
            lc = ldapconnection(connection)
            ls = ldapsearch(lc, basedn)
            rl = resultlist(ls)
            emails.extend(rl[0])
            for domain in rl[1]:
                domains.append([domain, nexthop])
            print('Processing of LDAP server ' + connection[0] + ' completed.')
        createdb(emails, domains, mysql)
        print('Operation completed successfully!')
    except:
        print('Error processing of LDAP server ' + connection[0] + '!')
        pass

def ldapconnection(ldapserver):
    try:
        print(' Trying to connect to LDAP server ' + ldapserver[0] + '...')
        ldapconnection = ldap.initialize('ldap://' + ldapserver[0])
        ldapconnection.simple_bind_s(ldapserver[1], ldapserver[2])
        ldapconnection.protocol_version = ldap.VERSION3
        ldapconnection.set_option(ldap.OPT_REFERRALS, 0)
        print(' Connection to LDAP server ' + ldapserver[0] + ' succesfull.')
    except:
        print('Error connecting to LDAP server ' + ldapserver[0] + '!')
        pass
    return ldapconnection

def ldapsearch(ldapconnection, basedn):
    try:
        print(' Sending LDAP query request...')
        scope = ldap.SCOPE_SUBTREE
        filter = '(&(proxyAddresses=smtp:*)(!(objectClass=contact)))'
        attributes = ['proxyAddresses']
        searchresults = ldapconnection.search_s(basedn, scope, filter, attributes)
        print(' LDAP query request results received.')
    except:
        print('Error sending LDAP query request!')
        pass
    return searchresults

def resultlist(searchresults):
    try:
        print(' Processing LDAP query results...')
        emails = []
        domains = []
        for i in range(len(searchresults)):
            try:
                for j in range(len(searchresults[i][1]['proxyAddresses'])):
                    r = searchresults[i][1]['proxyAddresses'][j].lower()
                    if 'smtp:' in r:
                        email = r[5:]
                        emails.append(email)
                        domain = email.split("@")[1]
                        domains.append(domain)
            except:
                pass
        print(' LDAP query results processed.')
    except:
        print('Error processing LDAP query results!')
        pass
    return removedublicates(emails), removedublicates(domains)


def createdb(emails, domains, mysql):
    try:
        print('Connecting to DB ' + mysql[3] + '...')
        try:
            db = MySQLdb.connect(host=mysql[0], user=mysql[1], passwd=mysql[2])
            cursor = db.cursor()
            sql = 'CREATE SCHEMA IF NOT EXISTS ' + mysql[3]
            cursor.execute(sql)
            db.commit()
        except:
            pass
        try:
            db = MySQLdb.connect(host=mysql[0], user=mysql[1], passwd=mysql[2], db=mysql[3])
            cursor = db.cursor()
        except:
            print('Error connecting to DB ' + mysql[3] + '!')
        print(' Check schemas and tables...')
        sql = ['CREATE TABLE IF NOT EXISTS ' + mysql[3] + '.relay_users (id INT NOT NULL, email LONGTEXT NULL, PRIMARY KEY (id))',
               'CREATE TABLE IF NOT EXISTS ' + mysql[3] + '.relay_domains (id INT NOT NULL, name LONGTEXT NULL, nexthop LONGTEXT NULL, PRIMARY KEY (id))',
               'TRUNCATE ' + mysql[3] + '.relay_users',
               'TRUNCATE ' + mysql[3] + '.relay_domains']
        for i in range(len(sql)):
            cursor.execute(sql[i])
            db.commit()
        print(' Inserting domains...')
        for i in range(len(domains)):
            sql = 'INSERT INTO postfix.relay_domains (id, name, nexthop)' \
                  'VALUES ("' + str(i) + '", "' + domains[i][0] + '", "smtp:[' + domains[i][1] + ']")'
            cursor.execute(sql)
            db.commit()
        print(' Inserting emails...')
        for i in range(len(emails)):
            sql = 'INSERT INTO postfix.relay_users (id, email)' \
                  'VALUES ("' + str(i) + '", "' + emails[i] + '")'
            cursor.execute(sql)
            db.commit()
        db.close()
        print('Connection to DB ' + mysql[3] + ' closed.')
    except:
        print('Error while operating with DB ' + mysql[3] + '!')
        pass

def removedublicates(input):
    seen = set()
    seen_add = seen.add
    return [x for x in input if not (x in seen or seen_add(x))]

if __name__ == '__main__':
    main()

If somebody know, how to do this, I will appreciated. Thanks.

tiv
  • 53
  • 1
  • 6

2 Answers2

3

Your request is bit complicated because this matter. Postfix, by design, will execute something based on positive result from maps. For example in your transport_maps, it will send email to specific host if the recipient has positive result from maps lookup. But, you want to execute something - redirect to another mailbox - if there is negative result from your relay_recipient_maps lookup. With your maps type is hash, I think there are no other way unless involving sql/tcp map for implementing negative result logic in the map.

For sql solution, here is the idea:

In this example I'll use sqlite. Suppose that you have table with one column named relay_recipient. Now the query

select ifnull(select relay_recipient from mytable WHERE relay_recipient = %s, 'mailboxspam@example.com')

will return mailboxspam@example.com whenever the recipient doesn't exist in relay_recipient_maps. Place those maps on virtual_alias parameter. Then whenever the email sent to unknown user, then it will be aliased to mailboxspam@example.com

tpml7
  • 479
  • 1
  • 5
  • 21
  • Thank you for your idea! It's the simplest potential solution I've see before, so I try it ASAP, and let you know. – tiv Oct 25 '14 at 19:39
  • this method is not changing destination address, it's just say to Postfix that unknown recipient exist – tiv Nov 11 '14 at 16:43
  • Could you edit your original question to include (1) `main.cf` after tried my solution and (2) sqlmaps configuration file – tpml7 Nov 12 '14 at 06:15
2

+1 for @tmpl7 has a great idea about how to implement your scenario with sqlite

Now, the problem is you haven't implemented sql query for ifnull in virtual_alias_maps parameter. First define the virtual_alias_maps in main.cf

virtual_alias_maps = mysql:/etc/postfix/redirect2spamlearning

The content of /etc/postfix/redirect2spamlearning

user = postfix
password = postfix
dbname = postfix
query = SELECT IFNULL((SELECT email FROM relay_users WHERE email='%s'), 'spamlearn@example.com')

The logic is : If SELECT email FROM relay_users WHERE email='%s' doesn't return anything a.k.a null-value, then email will be redirected to spamlearn@example.com.

masegaloeh
  • 17,978
  • 9
  • 56
  • 104