#!/usr/bin/perl
$ID = q$Id: mjdispatch,v 1.0 2002/06/29 01:48:30 eagle Exp $;
#
# mjdispatch -- Handles messages for majordomo under a qmail system.
#
# Copyright 1997, 1998, 1999, 2002 by Russ Allbery <rra@stanford.edu>
#
# This program is free software; you may redistribute it and/or modify it
# under the same terms as Perl itself.

##############################################################################
# Site configuration
##############################################################################

# The mailing list directory, which contains the actual list files.
$listdir = '/var/qmail/majordomo/lists';

# The default list owner, if none is given in $listdir/<list>.owner.
$defaultowner = 'someone@example.com';

# The full paths to several programs we need to know about.
$forward   = '/var/qmail/bin/forward';
$majordomo = '/var/qmail/majordomo/majordomo';
$resend    = '/var/qmail/majordomo/resend';

##############################################################################
# Modules and declarations
##############################################################################

require 5.001;

use strict;
use vars qw($ID $defaultowner $forward $listdir $majordomo $resend);

##############################################################################
# Address parsing
##############################################################################

# Attempts to parse local and determine what we're supposed to be doing with
# this message.  Returns a list where the first element is the mailing list to
# which the message relates, the second element is the list function (request,
# owner, approval, or send, where send indicates a message addressed to the
# list itself), and the third element is additional information (for example,
# the address for a VERP bounce).  We lowercase the address, assuming that
# mail to LIST should be handled the same as mail to list and List and lisT
# and so forth.
sub parse_local {
    local $_ = lc shift;

    # If the address exists as a file in the list directory, we'll assume that
    # it's a real list and that this message is therefore a submission for it.
    if (-r "$listdir/$_") { return ($_, 'send') }

    # Next, let's look for a message addressed to a VERP envelope sender.  Any
    # such message will have an = in the address to which it was addressed
    # since that represents an @ in the address which is bouncing.
    my ($list, $address) = /^(.*?)-owner-(.*=.*)/;
    if ($list) {
        $address =~ s/=/\@/;
        return ($list, 'owner', $address);
    }

    # Finally, check for various administrative functions.  Note that this
    # doesn't work if you have a list whose actual name ends in -request,
    # -owner, or -approval.  So don't do that.
    if    (/^(.*)-owner-?$/)  { return ($1, 'owner')    }
    elsif (/^(.*)-request$/)  { return ($1, 'request')  }
    elsif (/^(.*)-approval$/) { return ($1, 'approval') }
    else                      { return ()               }
}

##############################################################################
# Message dispatching
##############################################################################

# Resends the incoming message to the mailing list owner.  Note that the
# incoming message is still sitting on stdin, so all we have to do is exec a
# program that can deal with that.
sub resend_owner {
    my $list = shift;

    # Try to get the owner from a file.
    if (open (OWNER, "$listdir/$list.owner")) {
        my @owners = <OWNER>;
        chomp @owners;
        close OWNER;
        exec $forward, @owners if @owners;
    }

    # If we fell through for whatever reason, resend to the default owner.
    exec $forward, $defaultowner;

    # If that failed, we're in trouble.  Bounce the message with a temporary
    # failure code.
    warn "Unable to exec forward. (#4.3.0)\n";
    exit 111;
}

# Dispatches the incoming message to majordomo for a particular list.
sub resend_request {
    my $list = shift;

    # Pass the message to majordomo, giving it the affected list.
    exec $majordomo, '-l', $list;

    # If that failed, bounce the message with a temporary failure code.
    warn "Unable to exec majordomo. (#4.3.0)\n";
    exit 111;
}

# Dispatches the incoming message to resend for sending to the mailing list
# itself.
sub resend {
    my $list = shift;

    # Pass the message to resend, giving it the name of the list and the list
    # owner.  This works if resend is going to talk to something like
    # majordomo-inject.
    exec $resend, '-l', $list, "$list-owner";

    # If that failed, bounce the message with a temporary failure code.
    warn "Unable to exec resend. (#4.3.0)\n";
    exit 111;
}

##############################################################################
# Main routine
##############################################################################

# Parse the envelope recipient to figure out what we're doing.
my ($list, $function) = parse_local ($ENV{DEFAULT});

# Make sure this is a real list or reject the message.
if (!-r "$listdir/$list") {
    warn "Sorry, no mailbox here by that name. (#5.1.1)\n";
    exit 100;
}

# Dispatch the message appropriately depending on the results.
if    ($function eq 'owner')    { resend_owner ($list)   }
elsif ($function eq 'approval') { resend_owner ($list)   }
elsif ($function eq 'request')  { resend_request ($list) }
elsif ($function eq 'send')     { resend ($list)         }

# They tried to send to a nonexistent list.
warn "Sorry, no mailbox here by that name. (#5.1.1)\n";
exit 100;

##############################################################################
# Documentation
##############################################################################

=head1 NAME

mjdispatch - Handles messages for majordomo under a qmail system

=head1 SYNOPSIS

B<mjdispatch> < I<message>

=head1 DESCRIPTION

This script handles incoming mail for a Majordomo system running under
qmail.  It's designed to deal with mail to I<listname>, I<listname>-request,
I<listname>-owner, and I<listname>-approval.  The goal is to be able to hand
everything over to this script rather than having to maintain innumerable
different .qmail alias files for all of your lists (many of which contain
identical or programmatically-determinable content).

This script can be run out of F<.qmail-default> with:

    |/var/qmail/majordomo/mjdispatch

It requires I<list>.owner files in the list directory for every list that
doesn't have the default owner (specified at the top of this script).
Multiple addresses in that file are supported, but only to the allowable
command line length supported by your operating system, so don't put a
hundred addresses in there.

Messages sent to I<listname> will be passed off to Majordomo's B<resend>
program.  Messages sent to I<listname>-request will be passed off to the
B<majordomo> program itself.  Messages sent to I<listname>-owner or to
I<listname>-approval will be sent to the addresses listed in the
I<listname>.owner file.

Note that to work correctly with virtual domains, this script uses the
environment variable DEFAULT to extract the address that a message was sent
to.  If you're using a version of qmail prior to 1.03, this variable won't
be set and this script won't work.  You'll need to change DEFAULT to LOCAL
and possibly munge LOCAL if you're using virtual domains.

The path to the directory containing your list and list.owner files must be
set at the top of this script (it's F</var/qmail/majordomo/lists> by
default).

=head1 RETURN VALUE

For temporary errors, such as inability to exec a program, B<mjdispatch>
exits 111 to tell qmail to defer the the message and try to deliver it
again.  If the I<listname> file doesn't exist or if the address isn't of one
of the forms mentioned above, it instead exits 100 after printing a message
designed to simulate qmail's unknown user bounce.

=head1 ENVIRONMENT

=over 4

=item DEFAULT

Parsed to find the list name and the function being performed.  The list
name will be taken to be everything up to the last hyphen, and the function
will be taken to be everything after that, unless the content of this
variable matches a file name in the list directory (in which case, the whole
thing is taken as the list name).  This variable will be set by qmail to the
portion of the address matching the C<-default> part of the F<.qmail> file
name.

=back

=head1 FILES

=over 4

=item F</var/qmail/majordomo/lists>

The default mailing list directory (the directory containing the list files
and the list.owner files).  Edit the top of this script to change it.

=item F</var/qmail/majordomo/majordomo>

The default path to the B<majordomo> program.  Edit the top of this script
to change it.

=item F</var/qmail/majordomo/resend>

The default path to Majordomo's B<resend> program.  Edit the top of this
script to change it.

=back

Note that if set up properly, B<mjdispatch> will already be running as the
correct user, and therefore Majordomo's wrapper program won't be necessary.

=head1 SEE ALSO

forward(1)

For information about qmail, see L<http://cr.yp.to/qmail.html>.

For detailed information on how to install and use this program, see the
Majordomo with qmail FAQ at
L<http://www.eyrie.org/~eagle/faqs/mjqmail.html>.

Current versions of this program are available from its web site at
L<http://www.eyrie.org/~eagle/software/mjqmail/>.

=head1 AUTHOR

Russ Allbery <rra@stanford.edu>

=head1 COPYRIGHT AND LICENSE

Copyright 1997, 1998, 1999, 2002 by Russ Allbery <rra@stanford.edu>

This program is free software; you may redistribute it and/or modify it
under the same terms as Perl itself.

=cut
