#!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, "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(); die "Could not fork" if !defined $child; 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"; } return if !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: Unexpected characters in plugin argument"); exit 3; # exiting the forked child process. } # 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 }