Qmail

From Request Tracker Wiki
Jump to navigation Jump to search

Adding RT Mailgate aliases to a qmail system

This is ridiculously easy:

# vi /var/qmail/alias/.qmail-rt
 |/opt/rt3/bin/rt-mailgate --queue general --action correspond --url http://your.rt.server/your/rt/path/
 
 # vi /var/qmail/alias/.qmail-rt-comment
 |/opt/rt3/bin/rt-mailgate --queue general --action comment --url http://your.rt.server/your/rt/path/
 
 

That's it. You don't need to restart, just make sure the qmail group can read the files.

If you want one of them to send an e-mail to an address AND hit mailgate:

# vi /var/qmail/alias/.qmail-rt-comment
 user@wherever.com
 |/opt/rt3/bin/rt-mailgate --queue general --action comment --url http://your.rt.server/your/rt/path/
 
 

- Anonymous

NOTE: Each queue must be identified and handled individually (to create a ticket in the appropriate queue if the message is not a reply to an existing ticket).

FastForward aliases fail

If you use qmail's fastforward utility, to implement a sendmail-like /etc/aliases file, be aware that it doesn't launch piped commands properly. The following aliases added to the file will do nothing:

rt: "|/opt/rt3/bin/rt-mailgate --queue general --action correspond --url http://your.rt.server/your/rt/path/"
rt-comment: "|/opt/rt3/bin/rt-mailgate --queue general --action comment --url http://your.rt.server/your/rt/path/"

Removing the quotes results in fastforward's newaliases command complaining about not being able to deliver to a file and not rebuilding the alias database.

Stick with the qmail extensions for its alias user, as described in the top section of this page.

- Ed Eaglehouse

Taking Advantage of qmail User Extension Addresses

We can leverage qmail's ability to handle "user extension" addresses (user name followed by a hyphenated string) to significantly reduce the number of aliases we have to maintain. Because the general email address for RT is rt@your.rt.server, qmail still needs the .qmail-rt alias. A link to .qmail-rt-default works well.

For all the other addresses that are created as we add new ticket queues to RT, we can use a single preprocessing script that will invoke rt-mailgate with appropriate arguments. Create an RT default alias file that will direct all addresses of the form rt-/anything/:

# vi /var/qmail/alias/.qmail-rt-default
 |/usr/local/bin/pre-rt-mailgate
 
 

The pre-rt-mailgate script, listed below, parses the message headers and invokes rt-mailgate with the necessary arguments. This script must be edited minimally to customize the default URL and rt-mailgate script location to match your RT installation. Look for the conspicuous commented sections.

Now as long as you stick to some normalized email address formats - by default rt-/queue/-/action/ - the script will identify the proper arguments to pass to rt-mailgate and you shouldn't have to create any other aliases or touch the script again. By the way, the optional /queue/ and /action/ items must not be hyphenated themselves or the regular expression used to identify the components may produce predictably bad results. But it reduces my administrative workload, and that's always good!

Some examples:

| Email Address                        | Queue      | Action     | URL                      |
 | rt@your.rt.server                    | general    | correspond | http://your.rt.server/rt |
 | rt-admin@your.rt.server              | rt-admin   | correspond | http://your.rt.server/rt |
 | rt-admin-comment@your.rt.server      | rt-admin   | comment    | http://your.rt.server/rt |
 | rt-otherqueue@your.rt.server         | otherqueue | correspond | http://your.rt.server/rt |
 | rt-otherqueue-comment@your.rt.server | otherqueue | comment    | http://your.rt.server/rt |
 
 

The pre-rt-mailgate script:

#!/usr/bin/perl -w
 #
 # This software was written by Edward F Eaglehouse of Automated Solutions
 # Corporation and derives code distributed with the RT Request Tracking
 # system written by Best Practical Solutions, LLC.
 #
 # COPYRIGHT
 #
 # This script is intended for use with Request Tracker in accordance with the
 # stated contribution policy of Best Practical Solutions, LLC.
 #
 # LICENSE:
 #
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License.
 #
 # CUSTOMIZATION:
 #
 # Look for the code sections beginning with "#===" comment blocks.
 
 =head1 NAME
 
 pre-rt-mailgate - Preprocessor for mail interface to RT3.
 
 =cut
 
 use strict;
 use warnings;
 
 use Getopt::Long;
 
 use constant EX_TEMPFAIL => 75;
 use constant BUFFER_SIZE => 8192;
 
 my %opts;
 GetOptions( \%opts, "help", "debug" );
 
 if ( $opts{'help'} ) {
    require Pod::Usage;
    import Pod::Usage;
    pod2usage("RT Mail Gateway preprocessor\n");
    exit 1;    # Don't want to succeed if this is really an email!
 }
 
 #===============================================================================
 # Customize public default values and special queue mappings here.
 # Depending on your preferences, you may also need to modify the behavior of
 # the get_mailgate_args function below.
 #===BEGIN=======================================================================
 our $mailgate_command = "/usr/local/rt3/bin/rt-mailgate";
 our %param_defaults = (
    "queue"  => "general",
    "action" => "correspond",
    "url"    => "http://your.rt.server/rt"
 );
 our %special_queues = (
    "admin" => "rt-admin",
 );
 #===END=========================================================================
 
 # Read the message in from STDIN
 my %message = write_down_message();
 
 unless( $message{'filename'} || $message{'content'} ) {
    print STDERR "$0: couldn't read message: $!\n";
    exit EX_TEMPFAIL;
 }
 
 # Find the message recipient so we can identify the default RT queue.
 my %header_info = parse_headers(message_content(%message));
 
 if ( !defined($header_info{'to'})) {
    # Message recipient is required in order to continue.
    print STDERR "$0: message contained no recipient\n";
    exit EX_TEMPFAIL;
 }
 
 # Define rt-mailgate arguments from parsed headers.
 my %params = get_mailgate_args(%header_info);
 
 # Build command line to launch rt-mailgate.
 # Parameters without defined values get only an option keyword.
 my @args = ( $mailgate_command );
 for my $param (keys %params) {
    @args = ( @args, "--$param" );
    @args = ( @args, $params{$param} ) if (defined($params{$param}));
 }
 
 # Turn debugging on to see the command to be executed.
 print STDERR "exec: $mailgate_command @args"."\n" if $opts{'debug'};
 
 # Pipe message through rt-mailgate with parsed arguments.
 open(OUTPUT, '|-') || exec { $args[0] } @args;
 print OUTPUT message_content(%message);
 close(OUTPUT);
 exit;
 
 END {
    unlink $message{'filename'} if $message{'filename'};
 }
 
 
 # Save content of message so we can reuse it later.
 # Returns 'filename' element if input was copied to a temporary file.
 # Returns 'content' element if input was stored in memory.
 sub write_down_message {
    use File::Temp qw(tempfile);
 
    local $@;
    my ($fh, $filename) = eval { tempfile() };
    if ( !$fh || $@ ) {
        print STDERR "$0: Couldn't create temp file, using memory\n";
        print STDERR "error: $@\n" if $@;
 
        my $message = \do { local (@ARGV, $/); <> };
        unless ( $$message =~ /\S/ ) {
            print STDERR "$0: no message passed on STDIN\n";
            exit 0;
        }
        $$message = $opts{'headers'} . $$message if $opts{'headers'};
        return ( content => $message );
    }
 
    binmode $fh;
    binmode \*STDIN;
 
    print $fh $opts{'headers'} if $opts{'headers'};
 
    my $buf; my $empty = 1;
    while(1) {
        my $status = read \*STDIN, $buf, BUFFER_SIZE;
        unless ( defined $status ) {
            print STDERR "$0: couldn't read message: $!\n";
            exit EX_TEMPFAIL;
        } elsif ( !$status ) {
            last;
        }
        $empty = 0 if $buf =~ /\S/;
        print $fh $buf;
    };
    close $fh;
 
    if ( $empty ) {
        print STDERR "$0: no message passed on STDIN\n";
        exit 0;
    }
    print STDERR "$0: temp file is '$filename'\n" if $opts{'debug'};
    return (filename => $filename);
 }
 
 
 # Get selected information from message header lines.
 # $info{'to'} gets the local mail address.
 # $info{'domain'} gets the domain of the mail address.
 sub parse_headers {
    my ( $content ) = @_;
    my %info;
    local ( $_ );
    foreach $_ (split(/\n/, $content)) {
        /^To:\s+([^@]+)(@(.*))?$/ && do {
            # Grab recipient and domain from message recipient.
            $info{'to'} = $1;
            $info{'domain'} = $3 if defined($3);
            last;
        };
 
        # Ignore lines beyond message header.
        last if /^\s+$?/;
    }
    print STDERR "$0: parsed headers\n" if $opts{'debug'};
    return ( %info );
 } # parse_headers()
 
 
 # Get arguments for rt-mailgate from parsed header information.
 sub get_mailgate_args {
    my ( %header_info ) = @_;
    my $recipient = lc($header_info{'to'});
    my $domain = lc($header_info{'domain'});
    my $queue;
    my $action;
    my $url;
 
    #===========================================================================
    # Expected regex pattern is "rt" "-" queue "-" action.
    # Customize your address pattern and variable assignments here.
    #===BEGIN===================================================================
    my $address_regex = '^rt(-(\w+)(-(.*))?)?$';
 
    ( undef, $queue, undef, $action, undef ) =
        $recipient =~ /$address_regex/;
    #===END=====================================================================
 
    # Identify queue.
    # Suggestion: define any non-standard queue names in %special_queues.
    if (defined($queue)) {
        $queue = lc($queue);
        if (exists($special_queues{$queue})) {
            # Handle special queue mappings.
            $queue = $special_queues{$queue};
        }
    } else {
        # Use default queue name.
        $queue = $param_defaults{'queue'};
    }
 
    # Identify action.
    if (defined($action)) {
        $action = lc($action);
    } else {
        $action = $param_defaults{'action'};
    }
 
    # Identify url.
    # Suggestion: you can select the RT url based on the recipient domain.
    $url = $param_defaults{'url'};
 
    # Return hash array of parameters and assigned values.
    return (
        'queue'  => $queue,
        'action' => $action,
        'url'    => $url
    );
 }
 
 # Return message content as a string.
 sub message_content {
    my %message = @_;
    my $content;
    local ( $_, $/ );
 
    if ($message{'content'}) {
        # Message content is stored in memory.
        return $message{'content'};
    } else {
        # Message content is stored in temporary file.
        open INPUT, "<", $message{'filename'} or do {
                print STDERR "$0: unable to read temporary file: $!\n";
                exit EX_TEMPFAIL;
        };
        $content = <INPUT>;
        close INPUT;
    }
    return $content;
 }
 
 
 
 =head1 SYNOPSIS
 
     pre-rt-mailgate --help : this text
 
 Usual invocation (from MTA):
 
     pre-rt-mailgate [ --debug ]
 
 
 
 See C<man rt-mailgate> for more.
 
 =head1 OPTIONS
 
 =over 3
 
 =item C<--debug> OPTIONAL
 
 Print debugging output to standard error
 
 =head1 DESCRIPTION
 
 Preprocesses messages destined for C<rt-mailgate>. It is intended to
 normalize how they are routed to appropriate RT queues and actions.
 This program extracts information from the message headers,
 then calls C<rt-mailgate> with arguments derived from that information.
 
 An email address containing a queue name and action will have
 that information extracted and passed to C<rt-mailgate>. This can reduce
 the amount of configuration that must be done for your MTA and
 possibly eliminate ongoing maintenance as queues are added and
 deleted from RT. Ideally this will be a one-time setup.
 
 =head1 SETUP
 
 As originally configured, mail to an address of the form
 rt-I<queue>-I<action> will be routed to C<rt-mailgate> with the options
 C<--queue> set to I<queue>, C<--action> set to I<action>, and C<--url> set to
 your RT default URL.
 
 The optional I<action> defaults to C<correspond>. The optional I<queue>
 defaults to C<general>. The URL defaults to your primary RT URL. All
 this was designed to be fairly easy to modify.
 
 Edit the contents of this script to assign appropriate defaults if
 you dislike what is already there. Based on the format of email
 addresses you will use to communicate with RT, you can modify the
 C<get_mailgate_args> function to change the regular expression
 and customize the behavior of parameter value assignment.
 
 This script as distributed must be customized. At the minumum,
 the default URL and the path to the rt-mailgate command must be
 edited to match your RT installation.
 
 If any invalid parameter values are submitted, this program simply
 passes them to C<rt-mailgate> to handle.
 
 =head1 ENVIRONMENT
 
 =over 4
 
 This program runs in the same environment as C<rt-mailgate>.
 
 =back 4
 
 =cut
 
 
 # EOF: pre-rt-mailgate
 
 

- Ed Eaglehouse