Articles tagged in postfix

  1. A recent Postfix log on spamming attempt

    I have received the following logs on my mail server regularly over the last 2-3 months, showing attempts of spammers trying to send me a junk mail. The log is generated by Postfix automatically, and send to my postmaster box.

     Out: 220 mx.yang.id.au ESMTP Postfix
     In:  POST / HTTP/1.0
     Out: 502 Error: command not implemented
     In:  Content-Type: text/plain
     Out: 502 Error: command not implemented
     In:  Content-Length: 1111
     Out: 502 Error: command not implemented
     In:  Host: mx.yang.id.au
     Out: 502 Error: command not implemented
     In:  X-Forwarded-For: [Spammer's fake real address]
     Out: 502 Error: command not implemented
     In:  Connection: Keep-Alive
     Out: 502 Error: command not implemented
     In:
     Out: 500 Error: bad syntax
     In:  RSET
     Out: 250 Ok
     In:  HELO yahoo.de
     Out: 250 mx.yang.id.au
     In:  MAIL FROM:<Spammer's fake Hotmail address>
     Out: 250 Ok
     In:  RCPT TO:
     Out: 554 Service unavailable; [Spammer's real IP] blocked using bl.spamcop.net,
         reason: Blocked - see http://www.spamcop.net/bl.shtml?Spammer's real IP
     In:  DATA
     Out: 554 Error: no valid recipients
     In:  To: <My real email address>
     Out: 502 Error: command not implemented
     In:  From: "eddie" <Spammer's another fake Hotmail address>
     Out: 221 Error: I can break rules, too. Goodbye.
    

    Lines that appear in green are requests sent out by spammer's program. Lines that appear in red are responses from my Postfix server. Fortunately I have used SpamCop as my RBL to block these offending SMTP transactions by the IP address they are originating. Interestingly that it tried to issue HTTP commands when it first connects to the mail server. It does a POST to the root index, and obviously Postfix would not handle it. I wonder whether it is trying to find some exploits against unsecured mail server, testing for a open proxy server running at port 25 (why would people do that) or something else. Another interesting observation is the response given by Postfix - instead of dropping the connection after first few invalid commands have been entered, it actually allows the spammer to continue and issue a RSET to change conversation back to SMTP protocol. The connection should have been dropped after a few lines of bogus requests.

    I did not go and look up the IP address of the offending host, but I suspect that it is probably one of the machines that have been infected by virus, and thus bow down to their dark lord, hmm, I mean spammer. Pitty that these zombies around the world have been aiding the ugly business of sending junk mails...

  2. Automating Aliases Generation for Mailman and Postfix

    I have recently upgraded the mailing list manager software for FOCUS mailing list to Mailman 2.1. I have been running Mailman 2.0 since its beta days 2 years ago, and it has been a great piece of software. FOCUS mailing list is quite light in traffic (average 70-100 posts a month across all lists), and Mailman with all its strength seems to be an over kill for this task. Nevertheless, I've also learnt a great deal of Python programming from its sources. Thanks to Barry Warsaw and all those who contribute to this project!

    This article documents my quest to work around generating email aliases for Postfix whenever a mailing list is added ore removed.

    Why?

    Setting up aliases between Mailman and MTA has always been tricky. For every mailing list hosted, you have to create several email aliases manually in order to pipe the incoming emails to Mailman's scripts. For example, these aliases need to be appended to /etc/aliases (or /etc/postfix/aliases for Postfix) when a new mailing list mbf was added.

    mbf:             "|/home/mailman/mail/mailman post mbf"
    mbf-admin:       "|/home/mailman/mail/mailman admin mbf"
    mbf-bounces:     "|/home/mailman/mail/mailman bounces mbf"
    mbf-confirm:     "|/home/mailman/mail/mailman confirm mbf"
    mbf-join:        "|/home/mailman/mail/mailman join mbf"
    mbf-leave:       "|/home/mailman/mail/mailman leave mbf"
    mbf-owner:       "|/home/mailman/mail/mailman owner mbf"
    mbf-request:     "|/home/mailman/mail/mailman request mbf"
    mbf-subscribe:   "|/home/mailman/mail/mailman subscribe mbf"
    mbf-unsubscribe: "|/home/mailman/mail/mailman unsubscribe mbf"
    

    Mailman 2.1 comes with automatic configuration for Postfix by generating aliases, virtual domain and associated configuration files every time a list is added or removed. However, it is not that flexible for me as I run quite a few virtual domains on my mail server, and the domain list.focus-unsw.org is dedicated for mailing lists. Instead of playing around the aliases file, I want to parse the "To:" address to determine which mailing list it is delivering to. Here's my approach...

    Mailman Installation

    Grab the source and install Mailman as normal. That includes creating the mailman user, configuring, compiling and installing Mailman, setting up crontab, etc. However, don't touch any MTA integration yet.

    I am aiming to dedicate domain list.focus-unsw.org for mailing lists - any emails sent to this domain is assuming to be reserved for Mailman to consume. It includes assigning an A record for list.focus-unsw.org.

    Postfix Configuration

    For /etc/postfix/main.cf, make sure you have the following configuration.

    recipient_delimiter = +
    virtual_maps = hash:/etc/postfix/virtual, regexp:/etc/postfix/virtual_regexp
    

    Virtual domain mapping file "/etc/postfix/virtual_regexp" contains regular expressions to rewrite all the emails sending to list.focus-unsw.org to go to the user mailman. For emails sending to foo-bar@list.focus-unsw.org, I want to rewrite them to go to mailman+foo-bar. Append these to this file:

    /^list.focus-unsw.org$/       IGNORED
    /^(.*)@list.focus-unsw.org$/  mailman+$1
    

    Mailman Configuration

    I am using a .forward to pipe all the emails sent to user mailman to Mailman's mail handling script. However, Mailman's script ($HOME/mail/mailman in 2.1) requires two arguments - a command and a list name. When Postfix is doing a local delivery, it will append Deliver-To: to the email header, detailing the exact recipient in the delivery. Therefore I wrote a Python script to sit between .forward and Mailman to parse the email, learn about the mailman command and list name from its Deliver-To: header, and then pipe the result to Mailman's script with correct parameters.

    Here's the .forward file in Mailman's home directory, which is /home/mailman in this case.

    "|/home/mailman/mail/filter.py"
    

    filter.py is the Python script I wrote, which can be downloaded here. It basically reads the email from standard input, line by line, until it reaches the Deliver-To: header. It then parse the header to work out required arguments to call /home/mailman/mail/mailman. It then pipes everything it receives to Mailman's process. Here's the syntax highlighted version:

    #!/usr/bin/env python
    
    from cStringIO import StringIO
    import os
    import re
    import sys
    
    output = StringIO()
    found  = 0
    
    re_search = re.compile(r'^Delivered-To:\s*mailman\+(\w+)(-(\w+))?@')
    
    for line in sys.stdin:
        if not found:
        m = re_search.search(line)
            if m is not None:
                lst = m.group(1)
            cmd = m.group(3)
            if cmd is None:
            cmd = 'post'
            elif cmd not in [
            'admin', 'bounces', 'confirm', 'join', 'leave', 'owner',
            'request', 'subscribe', 'unsubscribe',
            ]:
            print >> sys.stderr, 'Invalid command "%s"' % cmd
            break
    
            try:
            wrapper = "/home/mailman/mail/mailman '%s' '%s'" % \
                (cmd, lst.replace("'", "\\'"))
            wrapper = os.popen(wrapper, "w")
            except os.Error, ex:
            print >> sys.stderr, 'Cannot execute mailman.', ex
            break
    
            wrapper.write(output.getvalue())
            output = wrapper
            found  = 1
    
        elif line.strip() == '':
            print >> sys.stderr, 'Deliver-To header cannot be found.'
            break
    
        output.write(line)
    
    if not found:
        sys.exit(67)
    
    if output.close() is not None:
        sys.exit(67)
    

    Conclusion

    After it is done, there is no modification needs to be done to both Postfix or Mailman whenever new mailing lists are created, or abandoned lists are purged. It is probably less efficient because for every mailing list post delivered, two processes (mailman and filter.py) need to be started instead of just one. I have been running this script (with modifications every now and then) for the last two years without any problem. Originally it was actually written in Perl, but I have since decided that Python is a much better scripting language :)