=head1 NAME lmtp - turn qpsmtpd into an LMTP server (RFC 2033) =head1 DESCRIPTION The B plugin turns qpsmtpd into an LMTP server, see RFC 2033. This includes =over 4 =item * removing the C and C keywords and adding the C keyword =item * change the greeting line to match C instead of C =item * returning the queue status for each recipient separately =back The RFC requirement of supporting ENHANCEDSTATUSCODES (RFC 2034) is not implemented: the core lacks support for it. =head1 CONFIGURATION The plugin requires that the wanted queue plugin and it's arguments are given as the arguments for the I plugin, e.g.: lmtp queue/maildir /home/mail/data/%d/%u Settings that may be changed via the usual qpsmtpd config mechanism (by default small files placed in the F diretory): =over 4 =item lmtpgreeting Set the line which will be sent as initial greeting, note that you B include the string C< LMTP > in this line (unlike qpsmtpd's core setting C). =back =head1 NOTES This plugin requires L from my L. Auth and TLS are currently not supported. Only C, C, C and C are supported as return values from queue plugins. The C<_DISCONNECT> variants will B disconnect the client, it just behaves like the non C<_DISCONNECT> variant. =cut use Qpsmtpd::Command; use Qpsmtpd::Constants; use POSIX qw(strftime); sub init { my ($self, $qp) = (shift, shift); my $queue = shift @_; die "No args given" unless $queue; $self->set_isa_plugin_args(@_); $self->isa_plugin($queue); } sub hook_ehlo { my $self = shift; $self->qp->respond(500, "Unknown command.", "Hint: try LHLO"); return(DONE, "DONE"); } *hook_helo = \&hook_ehlo; sub hook_connect { my ($self, $transaction) = @_; my $greeting = $self->qp->config("lmtpgreeting"); unless ($greeting) { $greeting = $self->qp->config("me") . " LMTP qpsmtpd " . $self->qp->version . " ready."; } $self->qp->respond(220, $greeting); return DONE; } sub hook_unrecognized_command { my ($self, $transaction, $cmd, @args) = @_; return DECLINED unless $cmd eq 'lhlo'; my $hello_host = shift @args; unless ($hello_host) { $self->qp->respond(501, "LHLO requires domain/address"); return DONE; } my $conn = $self->qp->connection; if ($conn->hello) { $self->qp->respond(503, "But you already said hello..."); return DONE; } $conn->hello("lhlo"); $conn->hello_host($hello_host); $self->qp->transaction; my @cap = $self->transaction->notes('capabilities') ? @{ $self->transaction->notes('capabilities') } : (); $self->qp->respond(250, $self->qp->config("me") . " Hi " . $conn->remote_info . " [" . $conn->remote_ip . "]", "PIPELINING", "8BITMIME", ($self->qp->config('databytes') ? "SIZE ". ($self->config('databytes'))[0] : ()), @cap, ); return DONE; } sub hook_data { my ($self, $transaction) = @_; unless ($transaction->recipients) { my $msg = "No valid recipients"; $self->qp->respond(503, $msg); return DONE; } return DECLINED; } sub hook_queue { my ($self, $transaction) = @_; my @rcpts = $transaction->recipients; $self->qp->respond(250, "OK, recipient codes follow"); foreach my $rcpt (@rcpts) { # set exactly one recipient and queue the message $transaction->recipients($rcpt); my ($rc, @msg) = $self->SUPER::hook_queue($transaction); next if $rc == DONE; if ($rc == OK) { $self->qp->respond(250, "Mail queued for $rcpt"); } elsif ($rc == DENY || $rc == DENY_DISCONNECT) { @msg or $msg[0] = "Queue denied for $rcpt"; $self->qp->respond(550, @msg); } elsif ($rc == DENYSOFT || $rc == DENYSOFT_DISCONNECT) { @msg or $msg[0] = "Temporary queue error for $rcpt"; $self->qp->respond(452, @msg); } else { my $code = Qpsmtpd::Constants::return_code($rc) ? Qpsmtpd::Constants::return_code($rc) : "UNKNOWN"; $self->qp->log(LOGERROR, "Queue plugin returned invalid constant $code"); $self->qp->respond(452, "$rcpt temporarily failed: internal error"); } } return DONE; } sub hook_received_line { my ($self, $transaction) = @_; return OK, "from " . $self->qp->connection->remote_info ." (". $self->connection->remote_ip . ")\n" ."\tby ".$self->qp->config('me')." (qpsmtpd/".$self->qp->version.")" ." with LMTP " .(strftime('%a, %d %b %Y %H:%M:%S %z', localtime)); } # vim: ts=4 sw=4 expandtab syn=perl