On Wed, 31 May 2000, Peter Samuel wrote:
> 
> I too have a simple Pierl qmail-queue wrapper. Let me tidy it up and
> I'll post it to the list. It currently does nothing, but can be used
> to do whatever wrapping you feel is appropriate (provided you can
> write the code to do it).

I have attached qmail-queue-wrapper.pl. It is a generic qmail-queue
wrapper. It currently does nothing to a message except add an extra
header of the form

    Received: (qmail-queue-wrapper 24590 invoked from network);
        31 May 2000 07:16:44 -0000

It then simply hands the message over to the real qmail-queue.

If you know perl, you can modify it to do whatever you want - just
don't come crying to me if it doesn't work after you've modified it.

To install:

    install perl if you haven't got it on your system
    choose a non production system to test this on
    choose a quiet time
    save the perl file in /var/qmail/bin/qmail-queue-wrapper.pl
    stop qmail-smtpd
    stop qmail-qmqpd
    stop qmail

    cd /var/qmail/bin
    vi qmail-queue-wrapper.pl

        change the first line
        
            #!/pkgs/bin/perl -w
        
        to reflect where your perl binary really lives

    chown root qmail-queue-wrapper.pl
    chgrp qmail qmail-queue-wrapper.pl
    chmod 755 /tmp/qmail-queue-wrapper.pl
    # The wrapper program should NOT be setuid!!!
    mv qmail-queue qmail-queue.orig; mv qmail-queue-wrapper.pl qmail-queue

    start qmail
    start qmail-qmqpd
    start qmail-smtpd

There is a small chance that mail injected into the queue via
qmail-inject (and it's friends sendmail and datemail) will attempt to
call qmail-queue between the "mv" commands above. That's
why you should choose a quiet time.

Regards
Peter
----------
Peter Samuel                                [EMAIL PROTECTED]
Technical Consultant                        or at present:
eServ. Pty Ltd                              [EMAIL PROTECTED]
Phone: +61 2 9206 3410                      Fax: +61 2 9281 1301

"If you kill all your unhappy customers, you'll only have happy ones left"
#!/pkgs/bin/perl -w
#
# $Id: qmail-queue-wrapper.pl,v 1.1 2000/05/31 07:20:37 psamuel Exp $
#
# qmail-queue wrapper program.
#
# This program should be used when you wish to manipulate a mail
# message BEFORE it is placed in the queue. Possible uses include:
#
#    - header rewriting
#    - Firstname.Lastname replacements
#    - virus scanning
#    - anything else you can think of
#
# There are at least 2 ways of using this program:
#
#    1) Replace the original qmail-queue with this program:
#
#       mv /var/qmail/bin/qmail-queue /var/qmail/bin/qmail-queue.orig
#       cp qmail-queue-wrapper /var/qmail/bin/qmail-queue
#
#    Change the value of $qmailqueue below, to reflect the new name of
#    the original qmail-queue program. For example
#
#       my $qmailqueue = "/var/qmail/bin/qmail-queue.orig";
#
#    2) Recompile qmail with Bruce Guenter's QMAILQUEUE patch. (See
#    http://www.qmail.org/qmailqueue-patch). Then any program that
#    needs to use this program can be called with the environment
#    variable QMAILQUEUE set to /var/qmail/bin/qmail-queue-wrapper
#
# How does it work? The interface to the real qmail-queue is simple:
#
#     - the body of the message is read from file descriptor 0
#     - the envelope details are read from file descriptor 1.
#
# qmail-queue-wrapper also adheres to the same interface. After doing
# whatever manipulations are necessary, it calls the real qmail-queue
# and provides the message body on file descriptor 0 and the envelope
# details on file descriptor 1.
#
# Exit codes conform to those mentioned in the qmail-queue(8) manual page.
#
###########################################################################

require 5;
use strict;

my $child;
my $debug = 0;
my $envelope;
my %errors;
my @months;
my $new_received_header;
my $qmailqueue = "/var/qmail/bin/qmail-queue.orig";
my @recipients;
my $sender;

###########################################################################

&initialise();

if ($child = fork())
{
    # Parent

    my $timeout = 86400;                # See qmail-queue.c, line 20

    alarm($timeout);

    &fatal(82) unless close MESSAGE_READER;
    &fatal(82) unless close ENVELOPE_READER;

    &process_message();
    &process_envelope();

    # Wait for the child to terminate

    waitpid($child, 0);

    # Return with the exit status of the child

    exit($? % 255);
}
elsif (defined $child)
{
    # Child

    &fatal(82) unless close MESSAGE_WRITER;
    &fatal(82) unless close ENVELOPE_WRITER;

    &fatal(82) unless defined open(STDIN, "<&MESSAGE_READER");
    &fatal(82) unless defined open(STDOUT, "<&ENVELOPE_READER");

    if ($debug)
    {
        &debug_message("$$: Reading message from STDIN\n\n");

        while (<STDIN>)
        {
            &debug_message("$$: $_");
        }

        &fatal(82) unless close MESSAGE_READER;

        &debug_message("\n$$: ####################\n\n");
        &debug_message("$$: Reading envelope from STDOUT\n");

        while (<ENVELOPE_READER>)
        {
            s/\0/ /g;
            &debug_message("$$: $_\n");
        }

        &fatal(82) unless close ENVELOPE_READER;

        exit(0);
    }
    else
    {
        unless (exec $qmailqueue)
        {
            # We shouldn't be here unless the exec failed

            &fatal(82);
        }
    }
}
else
{
    # Unable to fork

    &fatal(82);
}

###########################################################################

sub initialise
{
    &prepare_months();
    &prepare_error_messages();
    &ignore_signals();
    &catch_signals();
    &generate_new_received_header();
    &setup_pipes();
}

sub prepare_months
{
    @months = (
        "Jan",  "Feb",  "Mar",  "Apr",
        "May",  "Jun",  "Jul",  "Aug",
        "Sep",  "Oct",  "Nov",  "Dec",
    );
}

sub prepare_error_messages
{
    # These are the exit codes and their meanings, as defined by the
    # real qmail-queue manual page. Many are not used by either the
    # real qmail-queue or this wrapper program.

    %errors = (
        11      =>      "Address too long",

        31      =>      "Mail server permanently refuses to send " .
                        "the message to any recipients",

                        # Not used by qmail-queue, but can be used by
                        # programs offering the same interface

        51      =>      "Out of memory",

        52      =>      "Timeout",

        53      =>      "Write error; e.g., disk full",

        54      =>      "Unable to read the message or envelope",

        55      =>      "Unable to read a configuration file",

                        # Not used by qmail-queue

        56      =>      "Problem making a network connection from this host",

                        # Not used by qmail-queue

        61      =>      "Problem with the qmail home directory",

        62      =>      "Problem with the queue directory",

        63      =>      "Problem with queue/pid",

        64      =>      "Problem with queue/mess",

        65      =>      "Problem with queue/intd",

        66      =>      "Problem with queue/todo",

        71      =>      "Mail server temporarily refuses to send " .
                        "the message to any recipients",

                        # Not used by qmail-queue

        72      =>      "Connection to mail server timed out",

                        # Not used by qmail-queue

        73      =>      "Connection to mail server rejected",

                        # Not used by qmail-queue

        74      =>      "Connection to mail server succeeded, but " .
                        "communication failed",

                        # Not used by qmail-queue

        81      =>      "Internal bug; e.g., segmentation fault",

        82      =>      "System resource problem",

                        # Undefined in qmail-queue. Specific to this
                        # wrapper program.

        91      =>      "Envelope format error",
    );
}

sub ignore_signals
{
    # The real qmail-queue ignores a bunch of signals, so we will too.

    # Ensure all signals are not being blocked.

    foreach (keys %SIG)
    {
        $SIG{$_} = 'DEFAULT';
    }

    # Ignore those signals that the real qmail-queue ignores.

    $SIG{'PIPE'}   = 'IGNORE';
    $SIG{'VTALRM'} = 'IGNORE';
    $SIG{'PROF'}   = 'IGNORE';
    $SIG{'QUIT'}   = 'IGNORE';
    $SIG{'INT'}    = 'IGNORE';
    $SIG{'HUP'}    = 'IGNORE';
    $SIG{'XCPU'}   = 'IGNORE' if (defined $SIG{'XCPU'});
    $SIG{'XFSZ'}   = 'IGNORE' if (defined $SIG{'XFSZ'});
}

sub catch_signals
{
    # The real qmail-queue catches a few signals, so we will too.

    $SIG{'ALRM'} = \&timeout;

    $SIG{'ILL'}  = \&internal_bug;
    $SIG{'ABRT'} = \&internal_bug;
    $SIG{'FPE'}  = \&internal_bug;
    $SIG{'BUS'}  = \&internal_bug;
    $SIG{'SEGV'} = \&internal_bug;
    $SIG{'SYS'}  = \&internal_bug if (defined $SIG{'SYS'});
    $SIG{'EMT'}  = \&internal_bug if (defined $SIG{'EMT'});
}

sub timeout
{
    &fatal(52);
}

sub internal_bug
{
    &fatal(81);
}

sub generate_new_received_header
{
    # Generate a Received: header of the form:
    # Received: (qmail 28672 invoked by alias); 16 Feb 2000 03:49:51 -0000

    my @user = getpwuid($<);
    my @date = gmtime();

    my $user;

    if ($user[0] eq "alias")
    {
        $user = "by alias";
    }
    elsif ($user[0] eq "qmaild")
    {
        $user = "from network";
    }
    elsif ($user[0] eq "qmails")
    {
        $user = "for bounce";
    }
    elsif (scalar @user == 0)
    {
        # This should never happen - ie the real user id should
        # always have a password entry.

        $user = "by uid $<";
    }
    else
    {
        $user = "by uid $user[2]";
    }

    $date[5] += 1900;

    my $date = "$date[3] $months[$date[4]] $date[5]";
    my $time = sprintf("%02d:%02d:%02d", $date[2], $date[1], $date[0]);

    $new_received_header =
        "Received: (qmail-queue-wrapper $$ invoked $user); $date $time -0000";
}

sub setup_pipes
{
    &fatal(82) unless pipe(MESSAGE_READER, MESSAGE_WRITER);
    &fatal(82) unless pipe(ENVELOPE_READER, ENVELOPE_WRITER);
    select(MESSAGE_WRITER); $| = 1;
    select(ENVELOPE_WRITER); $| = 1;
}

sub debug_message
{
    my ($message) = @_;

    print STDERR "$message";
}

sub fatal
{
    my ($errno) = @_;

    &debug_message("$errors{$errno}\n") if $debug;
    exit($errno);
}

sub process_message
{
    # If you plan on doing serious massaging of the message body, such
    # as virus scanning or MIME conversions, you should probably write
    # the message to a temporary file here. Once you have finished your
    # massaging you can read from the file. You could slurp the message
    # into memory but that may be a resource problem for you. Caveat
    # emptor!

    print MESSAGE_WRITER "$new_received_header\n";

    while (<STDIN>)
    {
        print MESSAGE_WRITER;
    }

    &fatal(82) unless close MESSAGE_WRITER;
}

sub process_envelope
{
    &read_envelope();

    # If you don't want to do any rigourous checking of the envelope,
    # simply comment out the &check_envelope() statement. The real
    # qmail-queue will perform the same checks anyway.

    &check_envelope();

    &close_envelope();
    print ENVELOPE_WRITER "$envelope";
    &fatal(82) unless close ENVELOPE_WRITER;
}

sub read_envelope
{
    # Read the message envelope from file descriptor 1. At startup this is
    # already assigned to the Perl filehandle STDOUT.

    # Duplicate file descriptor 1 for reading

    &fatal(54) unless defined open(DUP_STDOUT, "<&STDOUT");

    # Extract the envelope details. The stripping of the leading 'F'
    # and 'T' characters will be performed later.

    $envelope = <DUP_STDOUT>;
}

sub check_envelope
{
    # There MUST be some envelope details.

    &fatal(54) unless defined $envelope;

    # The envelope details MUST be terminated by two NULLS.

    &fatal(54) if ($envelope !~ /\0\0$/);

    ($sender, @recipients) = split(/\0/, $envelope);

    # If there are no recipients, we should exit here. However, the
    # real qmail-queue will quite happily accept messages with no
    # recipients, so we will too.

    # The sender address MUST begin with an 'F' and the recipient
    # address(es) MUST begin with a 'T'.

    &fatal(91) if ($sender !~ /^F/);

    foreach (@recipients)
    {
        &fatal(91) if ($_ !~ /^T/);
    }

    # None of the addresses may be greater than $address_length
    # characters. (Remember that each address has an extra leading
    # character at this stage, so it's just a "greater than" test,
    # rather than a "greater than or equal to" test).

    my $address_length = 1003;          # See qmail-queue.c, line 21

    foreach ($sender, @recipients)
    {
        &fatal(11) if (length($_) > $address_length);
    }

    # The sender AND recipient address(es) should contain a username,
    # an @ sign and a fully qualified domain name. However, the real
    # qmail-queue does not enforce this, so we won't either.
}

sub close_envelope
{
    # Close duplicated STDOUT

    &fatal(54) unless close DUP_STDOUT;
}

Reply via email to