#!perl -w

=head1 NAME

qmail-queue

=head1 DESCRIPTION

This is the most common plugin used to queue incoming mails.  A
variation of this plugin would maybe forward the mail via smtp.

=head1 CONFIG

It takes one optional parameter, the location of qmail-queue.  This
makes it easy to use a qmail-queue replacement.

  queue/qmail-queue  /var/qmail/bin/another-qmail-queue

If set the environment variable QMAILQUEUE overrides this setting.

=cut

use strict;
use warnings;

use Qpsmtpd::Constants;
use POSIX ();

sub register {
    my ($self, $qp, @args) = @_;

    if (@args > 0) {
        $self->{_queue_exec} = $args[0];
        $self->log(LOGWARN, "WARNING: Ignoring additional arguments.")
          if @args > 1;
    }

    $self->{_queue_exec} ||= ($ENV{QMAIL} || '/var/qmail') . "/bin/qmail-queue";
    $self->{_queue_exec} = $ENV{QMAILQUEUE} if $ENV{QMAILQUEUE};
}

sub hook_queue {
    my ($self, $transaction) = @_;

    # these bits inspired by Peter Samuels "qmail-queue wrapper"
    pipe(MESSAGE_READER, MESSAGE_WRITER) or die "Could not create message pipe";
    pipe(ENVELOPE_READER, ENVELOPE_WRITER)
      or die "Could not create envelope pipe";

    local $SIG{PIPE} = sub { die 'SIGPIPE' };
    my $child = fork();

    !defined $child and die "Could not fork";

    if ($child) {

        # Parent
        my $oldfh = select MESSAGE_WRITER;
        $| = 1;
        select ENVELOPE_WRITER;
        $| = 1;
        select $oldfh;

        close MESSAGE_READER  or die "close msg reader fault";
        close ENVELOPE_READER or die "close envelope reader fault";

        $transaction->header->print(\*MESSAGE_WRITER);
        $transaction->body_resetpos;
        while (my $line = $transaction->body_getline) {
            print MESSAGE_WRITER $line;
        }
        close MESSAGE_WRITER;

        my @rcpt = map { "T" . $_->address } $transaction->recipients;
        my $from = "F" . ($transaction->sender->address || "");
        print ENVELOPE_WRITER "$from\0", join("\0", @rcpt), "\0\0"
          or return (DECLINED, "Could not print addresses to queue");

        close ENVELOPE_WRITER;
        waitpid($child, 0);
        my $exit_code = $? >> 8;
        $exit_code
          and return (DECLINED, "Unable to queue message ($exit_code)");

        my $msg_id = $transaction->header->get('Message-Id') || '';
        $msg_id =~ s/[\r\n].*//s;  # don't allow newlines in the Message-Id here
        $msg_id = "<$msg_id>" unless $msg_id =~ /^<.*>$/;    # surround in <>'s
        return (OK, "Queued! " . time . " qp $child $msg_id");
    }
    elsif (defined $child) {

        # Child
        close MESSAGE_WRITER  or exit 1;
        close ENVELOPE_WRITER or exit 2;

        # Untaint $self->{_queue_exec}
        my $queue_exec = $self->{_queue_exec};
        if ($queue_exec =~ /^(\/[\/\-\_\.a-z0-9A-Z]*)$/) {
            $queue_exec = $1;
        }
        else {
            $self->log(LOGERROR,
"FATAL ERROR: Unexpected characters in qmail-queue plugin argument"
            );

            # This exit is ok as we're exiting a forked child process.
            exit 3;
        }

        # save the original STDIN and STDOUT in case exec() fails below
        open(SAVE_STDIN,  "<&STDIN");
        open(SAVE_STDOUT, ">&STDOUT");

        POSIX::dup2(fileno(MESSAGE_READER), 0)
          or die "Unable to dup MESSAGE_READER: $!";
        POSIX::dup2(fileno(ENVELOPE_READER), 1)
          or die "Unable to dup ENVELOPE_READER: $!";

        my $ppid = getppid();
        $self->log(LOGNOTICE, "(for $ppid) Queuing to $queue_exec");

        my $rc = exec $queue_exec;

        # close the pipe
        close(MESSAGE_READER);
        close(MESSAGE_WRITER);

        exit 6;    # we'll only get here if the exec fails
    }
}