As mentioned earlier in the chapter, you can't use local aliases, .forward files, and mailing-list programs with virtual domains delivered by the virtual delivery agent. You've seen that you can easily set up aliases through the virtual_alias_maps parameter, but you cannot deliver messages to a command. In this last section, we'll look at working around that issue by demonstrating how to deliver virtual addresses to external programs. The first example sets up delivery to an autoreply program, and the second to a mailing-list manager.
Auto-responders are scripts or programs that process incoming messages and return a reply to the sender of the message without any human intervention. The autoreply program used in this example, inforeply.pl, is listed in Example 8-1. This program is meant to handle mail for a dedicated information email address. Users or customers can send a message to the address to request special information. Note that this simple example is inadequate as a general autoreply program, such as the Unix vacation command. It does not cache addresses it has already replied to, and it does not do full checking for addresses that should not receive automatic replies (see the sidebar). You might also like to enhance the program to return different types of information, based on the subject or a keyword in the body of the request messages.
Example 8-1. Simple automatic reply program
#!/usr/bin/perl -w # # inforeply.pl - Automatic email reply. # # All messages are logged to your mail log. Check the # log after executing the script to see the results. # # Set $UID to the uid of the process that runs the script. # Check the entry in master.cf that calls this script. Use # the uid of the account you assign to the user= attribute. # If you want to test the script from the command line, # set $UID to your own uid. # # Set $ENV_FROM to the envelope FROM address you want on # outgoing replies. By default it's blank, which will # use the NULL sender address <>. You can set it to an # address to receive bounces, but make sure you don't set # it to the same address that invokes the program, or # you'll create a mail loop. # # Point $INFOFILE to a text file that contains the text of # the outgoing reply. Include any headers you want in the # message such as Subject: and From:. The To: header is # set automatically based on the sender's address. Make # sure you have an empty line between your headers and the # body of the message. # # If necessary, change the path to sendmail in $MAILBIN. # # @MAILOPTS contains options to sendmail. Make changes if # necessary. The default options should work in most # situations. # # The calls to syslog require that your Perl installation # converted the necessary header files. See h2ph in your # Perl distribution. # require 5.004; # for setlogsock in Sys::Syslog module use strict; use Sys::Syslog qw(:DEFAULT setlogsock); # # Config options. Set these according to your needs. # my $UID = 500; my $ENV_FROM = ""; my $INFOFILE = "/home/autoresp/inforeply.txt"; my $MAILBIN = "/usr/sbin/sendmail"; my @MAILOPTS = ("-oi", "-tr", "$ENV_FROM"); my $SELF = "inforeply.pl"; # # end of config options my $EX_TEMPFAIL = 75; my $EX_UNAVAILABLE = 69; my $EX_OK = 0; my $sender; my $euid = $>; $SIG{PIPE} = &PipeHandler; $ENV{PATH} = "/bin:/usr/bin:/sbin:/usr/sbin"; setlogsock('unix'); openlog($SELF, 'ndelay,pid', 'user'); # # Check our environment. # if ( $euid != $UID ) { syslog('mail|err',"error: invalid uid: $> (expecting: $UID)"); exit($EX_TEMPFAIL); } if ( @ARGV != 1 ) { syslog('mail|err',"error: invalid invocation (expecting 1 argument)"); exit($EX_TEMPFAIL); } else { $sender = $ARGV[0]; if ( $sender =~ /([w-.%]+@[w.-]+)/ ) { # scrub address $sender = $1; } else { syslog('mail|err',"error: Illegal sender address"); exit($EX_UNAVAILABLE); } } if (! -x $MAILBIN ) { syslog('mail|err', "error: $MAILBIN not found or not executable"); exit($EX_TEMPFAIL); } if (! -f $INFOFILE ) { syslog('mail|err', "error: $INFOFILE not found"); exit($EX_TEMPFAIL); } # # Check sender exceptions. # if ($sender eq "" || $sender =~ /^owner-|-(request|owner)@|^(mailer-daemon|postmaster)@/i) { exit($EX_OK); } # # Check message contents for Precedence header. # while ( ) { last if (/^$/); exit($EX_OK) if (/^precedence:s+(bulk|list|junk)/i); } # # Open info file. # if (! open(INFO, "<$INFOFILE") ) { syslog('mail|err',"error: can't open $INFOFILE: %m"); exit($EX_TEMPFAIL); } # # Open pipe to mailer. # my $pid = open(MAIL, "|-") || exec("$MAILBIN", @MAILOPTS); # # Send reply. # print MAIL "To: $sender "; print MAIL while (); if (! close(MAIL) ) { syslog('mail|err',"error: failure invoking $MAILBIN: %m"); exit($EX_UNAVAILABLE); } close(INFO); syslog('mail|info',"sent reply to $sender"); exit($EX_OK); sub PipeHandler { syslog('mail|err',"error: broken pipe to mailer"); }
8.5.1 Configuring a Virtual Auto-Responder
To configure an auto-responder to work with virtual domains, you must create a special transport type in master.cf for delivery to the specific command. In order to have messages delivered to your new component, you have to map an address to the transport you created using transport maps.
Many auto-responders can handle only a single message at a time with only one recipient. You can limit the number of recipients to any transport type by setting a parameter of the form transport_destination_recipient_limit, where the string transport is the name of the transport type. If a transport called inforeply should be limited to only one recipient at a time, set the following parameter:
inforeply_destination_recipient_limit = 1
The following steps walk through setting up the email address info@ora.com to use inforeply.pl. The domain ora.com is configured as a virtual domain. The local domain on the host is example.com:
inforeply unix - n n - - pipe flags= user=autoresp argv=/usr/local/bin/inforeply.pl ${sender}
The pipe daemon is used to deliver messages to external commands. You can specify flags if your program requires any special options. (See the pipe(8) man page for information on the available options.) The user attribute is required for any pipe components in master.cf. The argv attribute must be specified last, and should start with the path to the autoreply command. There are several values that you can pass to your command when Postfix executes it. The values are supplied through special macros. In this example, the envelope sender address (${sender}) is passed. For the simple inforeply.pl responder, you need only the sender address, but you will often want the recipient (${recipient}) address, too, for auto-responders that can handle multiple recipient addresses. See the pipe(8) manpage for the list of available macros.
transport_maps = hash:/etc/postfix/transport
autoresp@ora.com inforeply
Now, all messages sent to autoresp@ora.com are delivered to the auto-responder.
# postmap /etc/postfix/transport
virtual_alias_maps = hash:/etc/postfix/virtual_alias
info@ora.com autoresp@ora.com service@oreilly.com
# postmap /etc/postfix/virtual_alias
Now messages sent to info@ora.com will be delivered to autoresp@ora.com and service@oreilly.com.
# postfix reload
When a message is sent to info@ora.com, Postfix first finds the destination address in the virtual_alias lookup table. The address points both to autoresp@ora.com and service@oreilly.com. Postfix finds autoresp@ora.com in the transport lookup table, which points to the inforeply transport in the master.cf file. The entry in master.cf pipes the message to the inforeply.pl program, which sends the reply to the original sender. Finally, the message is also resubmitted for delivery to service@oreilly.com.
8.5.2 Configuring a Virtual Mailing List Manager
In the next example, you'll set up a mailing list for a virtual domain. Mailing-list managers are discussed in Chapter 10. You may want to review that chapter before setting up your virtual mailing lists. This example creates a mailing list for Majordomo. You should first install and configure Majordomo according to the directions in Chapter 10.
Virtual mailing lists work by creating a parallel version of the list under a local domain. The local version is only used internally on your system. External users can use the virtual addresses and never know that the local version exists. When naming the local version, you may want to include the virtual domain name in some way to distinguish the list from lists for other virtual domains hosted on your system. The following procedure creates a mailing list at the virtual address astronomy@ora.com that is handled by the local version astronomy-ora@example.com:
# astronomy@ora.com list astronomy-ora: :include:/usr/local/majordomo/lists/astronomy owner-astronomy-ora: kdent@ora.com astronomy-ora-request: "|/usr/local/majordomo/wrapper request-answer astronomy-ora" astronomy-ora-approval: kdent@ora.com
# postaliases /usr/local/majordomo/aliases
# touch /usr/local/majordomo/lists/astronomy # chown majordom /usr/local/majordomo/lists/astronomy
# astronomy@ora.com list astronomy@ora.com astronomy-ora@localhost owner-astronomy@ora.com owner-astronomy-ora@localhost astronomy-request@ora.com astronomy-ora-request@localhost astronomy-approval@ora.com astronomy-ora-approval@localhost
# postmap virtual_alias
You should now be able to send messages to astronomy@ora.com for distribution to all of the addresses in your list file.
Introduction
Prerequisites
Postfix Architecture
General Configuration and Administration
Queue Management
Email and DNS
Local Delivery and POP/IMAP
Hosting Multiple Domains
Mail Relaying
Mailing Lists
Blocking Unsolicited Bulk Email
SASL Authentication
Transport Layer Security
Content Filtering
External Databases
Appendix A. Configuration Parameters
Appendix B. Postfix Commands
Appendix C. Compiling and Installing Postfix
Appendix D. Frequently Asked Questions