# -*- perl -*- =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 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); } else { $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"); my $child = fork(); not 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"); # Note - technically there's a race here because if the exec() below # fails and the writes to MESSAGE_WRITER block we get a deadlocked process. # This check to see if(eof(PIPE)) will catch "most" of these problems. die "Message pipe has been closed" if eof(MESSAGE_WRITER); $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|| "" ); die "Envelope pipe has been closed" if eof(ENVELOPE_WRITER); 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 qp $$ 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 } }